TypeScript client
A typed client for the Chelabase data API, built on fetch with no runtime dependencies.
@chelabase/client is a typed client for the data API (POST /data/v1/query). It has no runtime dependencies: it's built on fetch, so it runs in browsers, Node and edge runtimes.
The package lives in the repository (packages/client) and is a workspace package, not yet published to npm. Add it to a workspace app as "@chelabase/client": "workspace:*".
Create a client
The generated types (db.ts, from Studio's Download types or GET /data/v1/types) hold a Database type and a types constant from a branch's schema (see Generated types). Pass both to createClient, with the gateway's URL and a key:
import { } from "@chelabase/client";
import { type , } from "./db";
const = <>({ : "http://api.chelabase.localhost:8000", : "chk_pub_…", });The key is sent as a bearer token. A public key (role anon) is safe in a browser; keep a secret key on a server.
Read
db.from(table) starts a query; "billing.invoices" is a qualified name, and a bare name means the database's default schema. Builders are immutable and lazy: nothing is sent until you await (each await sends the request once). They also have .catch() and .finally().
const = await
.("orders")
.("id", "total", { : "slug", : "handle" })
.({ : "paid", : { : "100" }, : [{ : { : 2 } }, { : true }] })
.("created_at", "desc", "last")
.(50);
const first = [0];select() and returning() need at least one column. Leave select() out for every column. Filters are covered on Filters.
count() counts the matching rows as your role sees them (row-level security applies) and resolves to { count: number }. It sends only the table and the filter; a count after limit() or offset() throws.
const { } = await .("orders").({ : true }).();toTree() returns the exact JSON tree a builder sends, the query language of the data API:
const tree = .("orders").("id").({ : true }).(1).();Write
// Insert: all rows must have the same keys; returning is optional.
const created = await db
.from("orders")
.insert([{ total: "12.50", vip: false, history: [] }])
.returning("id", "total");
// Update and delete: .where() or .all() first.
await db.from("orders").update({ status: "paid" }).where({ id: created[0].id });
await db.from("orders").delete().where({ vip: false }).returning("*");
await db.from("billing.invoices").delete().all();An update or delete runs only after .where() or .all(). Before that, awaiting it or putting it in a batch is a compile error (and the server refuses it anyway). .returning() works before or after:
await db.from("orders").update({ status: "paid" });Call a function
const points = await .("add_points", { : "7c9e6679-7425-40de-944b-e07fc1f90ae7", : 5 });const next = await .("next_id");Batch
db.batch runs its operations in order in one transaction, all or nothing (at most 50), and resolves to a typed tuple with each result. Batches don't nest:
const [, ] = await .(() => [
.("orders").({ : "1.00", : false, : [] }).("id"),
.("add_points", { : "7c9e6679-7425-40de-944b-e07fc1f90ae7" }),
]);
const results = { , };More
- Filters
- Errors
- Big numbers:
int8andnumeric. - Generated types and other databases of a branch.
- App users:
db.auth.