Skip to content

client.db

client.db is your app’s scalable database. You define tables (columns + indexes) in the Muhkoo portal; each table is backed by its own isolated, stateful edge instance and reachable through a sanitized REST gateway. This namespace wraps that gateway with a typed, ergonomic surface.

Unlike client.kv (per-user, encrypted-at-rest key/value), the database is app-scoped, queryable, and shared across your app’s users — authorized by the app key (the appId + environment are resolved server-side from the key, so a key can only ever reach its own app’s tables).

table<T extends Record<string, unknown>>(name: string): DbTable<T>

Returns a handle to a table. The optional type parameter gives the row operations a typed shape:

interface Todo { _id: number; title: string; done: boolean; rank: number }
const todos = client.db.table<Todo>("todos");

If no column is marked as the primary key in the portal, an auto-incrementing _id integer is added for you.

insert(values: Partial<T>): Promise<{ row: T; id: unknown }>

Inserts a row. Returns the created row and its primary-key value. Every field is validated against the table’s schema (type-checked, NOT NULL enforced); unknown columns are rejected.

const { id } = await todos.insert({ title: "Buy milk", done: false, rank: 1 });
get(id: string | number): Promise<T | null>

Fetches a row by primary key, or null if it doesn’t exist.

query(query?: DbQuery): Promise<{ rows: T[]; nextCursor: string | null }>

Lists rows with filters, ordering, and keyset pagination. Pass the nextCursor from a previous result back as cursor to fetch the next page.

const { rows, nextCursor } = await todos.query({
where: [{ column: "done", op: "eq", value: false }],
orderBy: { column: "rank", dir: "asc" },
limit: 50,
});
interface DbQuery {
select?: string[]; // columns to return; omit for all
where?: DbWhereCondition[]; // AND-combined
orderBy?: { column: string; dir?: "asc" | "desc" };
limit?: number; // clamped server-side to 100
cursor?: string; // a previous result's nextCursor
}
interface DbWhereCondition {
column: string;
op: DbFilterOp;
value: unknown;
}

Operators (DbFilterOp): eq, neq, gt, gte, lt, lte, in (value is an array), like (raw SQL LIKE pattern on a text column), likeStartsWith / likeContains (the server builds and escapes the pattern for you). Range operators (gt/gte/lt/lte) apply to integer, real, and timestamp columns; the like family applies to text columns.

update(id: string | number, values: Partial<T>): Promise<{ row: T }>

Updates a row by primary key. You can’t change the primary key.

delete(id: string | number): Promise<number>

Deletes a row by primary key. Resolves to the number of rows removed (0 or 1).

TypeStored asJS value
textTEXTstring
integerINTEGERnumber (safe integer)
realREALnumber
booleanINTEGER (0/1)boolean
timestampINTEGER (epoch ms)number or ISO string
jsonTEXTany JSON-serializable value

The REST gateway is the only way to reach the database — your front end never talks to the database directly. All input is bound as parameters (never string-concatenated), column and operator names are validated against the table’s schema, and there is no raw-SQL passthrough. Results are bounded (limit is capped; pagination is keyset, not offset).

StatusMeaning
400Invalid input — unknown column, bad operator/type, or a malformed filter.
401No app key — pass apiKey to the Client.
402Free-tier database quota exceeded (rows read/written or storage).
404Table or row not found.
409Constraint violation (e.g. a unique column), or an unsupported (destructive) schema migration.
507Table at its single-instance capacity (sharding required).