Typed client SDK

The 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.

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:

import { createClient } from "/akurai-client.js";

const client = await createClient();   // same-origin; awaits GET /api/_meta

Pass a base URL to target another origin:

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:

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.

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):

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:

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 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.

  1. Submitting with validation issues surfaces the server's { error } message

inline (thrown as an AkuraiError).