Menu
Public repository / master
AkurAI-Framework / site/content/docs/client-sdk.md
Latest change 1d2bfb32048e3c8b890ef369116a9fa65bac00ec - Add typed client SDK (akurai-client.js), demo page, and docs by Ólafur Búi Ólafsson
# Typed client SDK
The [auto-API](/docs/auto-api) gives you a full REST surface for every
collection you declare. The client SDK — `akurai-client.js` — wraps that surface
in a small, typed JavaScript client so you call `client.posts.create(...)`
instead of hand-rolling `fetch` URLs, methods, and error handling. It's the
framework's "tRPC analog": one generated client whose shape mirrors your data
model.
True to the rest of the stack, **there is no build step**. `akurai-client.js` is
a native ES module the browser loads directly. "Typed" means thorough JSDoc
`@typedef` + `@param`/`@returns` annotations — editors and TypeScript-in-checkJS
read them for completions and type-checking, with nothing to compile.
See it live on the [client SDK demo](/client-demo).
## Connecting
The client is generated at runtime from the manifest at `/api/_meta`, so it
always matches the collections your server actually declared. Because it must
fetch that manifest first, `createClient` is async:
```js
import { createClient } from "/akurai-client.js";
const client = await createClient(); // same-origin; awaits GET /api/_meta
```
Pass a base URL to target another origin:
```js
const client = await createClient("https://example.com");
```
On connect it logs a breadcrumb:
```
[client] connected, 1 collection(s): posts
```
The raw manifest and the discovered collection names stay on the client:
```js
client.meta; // the full /api/_meta document
client.collections; // the `collections` array from the manifest
client.collectionNames; // ["posts", ...]
```
## Per-collection methods
For every collection in the manifest, the client gets an accessor keyed by the
collection's name (`client.posts`, `client.<yourCollection>`, …). Each accessor
has six methods:
| Method | Calls | Returns |
| ------ | ----- | ------- |
| `list(opts?)` | `GET /api/collections/<name>/records` | `AkuraiRecord[]` (newest first) |
| `create(data)` | `POST .../records` | the created `AkuraiRecord` |
| `get(id)` | `GET .../records/<id>` | one `AkuraiRecord` |
| `update(id, patch)` | `PATCH .../records/<id>` | the updated `AkuraiRecord` |
| `delete(id)` | `DELETE .../records/<id>` | `void` (resolves on 204) |
| `search(query, opts?)` | `GET .../records?search=<query>` | `AkuraiRecord[]` |
`opts` accepts `{ limit }`, which becomes `?limit=` on `list` and `search`.
```js
const posts = await client.posts.list({ limit: 20 });
const made = await client.posts.create({ title: "Hello", body: "a world" });
const one = await client.posts.get(made.id);
await client.posts.update(made.id, { title: "Hello again" });
const hits = await client.posts.search("world", { limit: 5 });
await client.posts.delete(made.id);
```
## Errors
Any non-2xx response throws an `AkuraiError` carrying the HTTP `status`, the
request `url`, and the server's `{ error }` message (the auto-API returns that
on `400`/`404`):
```js
import { AkuraiError } from "/akurai-client.js";
try {
await client.posts.create({}); // missing a required field → 400
} catch (err) {
if (err instanceof AkuraiError) {
console.error(err.status, err.message); // 400, "AkurAI API 400 on … : title is required"
}
}
```
## The typing approach (no build step)
Types are expressed entirely in JSDoc, so editors give you completions and
inline type-checking with nothing to compile:
- `AkuraiRecord` — a stored record (`id`, `created`, plus your declared fields).
- `AkuraiField` / `AkuraiCollectionMeta` — the manifest's schema shape.
- `AkuraiCollectionClient` — the six-method bundle each `client.<name>` exposes.
- `AkuraiClient` — the connected client (`meta`, `collections`,
`collectionNames`, `baseUrl`, plus the dynamic per-collection accessors).
Add `// @ts-check` at the top of a consuming module (or enable `checkJs` in your
editor) and you get type errors for things like passing a number where a record
body is expected — without ever leaving plain `.js`.
Because accessor keys (`posts`, …) are generated from your manifest, they can't
be statically enumerated in the typedef; enumerate them at runtime with
`client.collectionNames`.
## Verifying it works
The live site ships a demo `posts` collection. Open
[`/client-demo`](/client-demo) and:
1. **Console** shows `[client] connected, N collection(s): posts`.
2. The **Posts** panel lists existing records via `client.posts.list()`.
3. Typing a title and pressing **Create** calls `client.posts.create()`, then
re-lists — the new post appears at the top.
4. Submitting with validation issues surfaces the server's `{ error }` message
inline (thrown as an `AkuraiError`).