Typed client
How to install and use @data-backend/client, the typed client for the v1 API of Data. It is for developers writing Bun or Node code that calls Data through the gateway; authorization and concurrency refusals reach you as typed errors and are never hidden.
Read byApplication developer
Install#
npm install @data-backend/client
bun add @data-backend/client
# or pin the release tarball (same package; its version is the client version):
bun add https://github.com/ggpwnkthx/platform/releases/download/v1.0.9/data-backend-client-1.0.9.tgzThe client runs on Bun and Node and has no dependencies. Client 1.x works with any deployment 1.y where y >= x (spec §13).
Construct#
import { DataClient } from '@data-backend/client'
const client = new DataClient({
baseUrl: 'https://api.gateway.test',
workspaceId: '22222222-2222-4222-8222-222222222222',
headers: { cookie: sessionCookie },
})| Option | Meaning |
|---|---|
baseUrl | Deployment URL, through the gateway. |
workspaceId | Workspace for every call. |
headers | Sent on every call, such as the session cookie. |
fetch | Transport; defaults to global fetch. |
signal | Cancels calls. |
The client never signs a request, stores Access credentials or logs in. Your credentials travel in the transport, and the gateway turns them into a signed identity.
Methods#
| Method | Returns |
|---|---|
me() | Principal, tenant, workspace, route, API version. |
schema(), collection(key) | Metadata of the collections you may use, or of one. |
list(key, options) | One page. |
listAll(key, options) | Every page, in sequence. |
get(key, id), getWithValidator(key, id) | One record; the second adds its ETag. |
create(key, body, { idempotencyKey }) | The creation receipt. |
update(key, id, body, { etag }) | The record and its new ETag. |
remove(key, id, { etag }) | Nothing; deletes the record. |
versions(key, id), version(key, id, version) | A record's history, or one version. |
rollback(key, id, version, { etag }) | The record and its new ETag. |
aggregate(key, options) | Aggregate groups. |
audit({ limit, cursor }) | A page of audit metadata. |
Write#
A create needs an idempotency key; an update or delete needs the current validator.
const created = await client.create(
'articles',
{ title: 'Launch notes', body: 'Initial content.' },
{ idempotencyKey: crypto.randomUUID() }
)
const { record, etag } = await client.getWithValidator('articles', created.id)
const updated = await client.update('articles', created.id, { title: 'Revised' }, { etag })
// updated.etag validates the new versionOn 412 PRECONDITION_FAILED, read the record again and reapply your change (spec §9.4).
Versions#
A rollback is a write: it needs the current validator, the policies authorize it like an update, and the record moves to the next version number (spec §9.8).
const versions = await client.versions('articles', created.id)
// versions[0].version, .operation, .supersededAt
const previous = await client.version('articles', created.id, versions[0].version)
await client.rollback('articles', created.id, versions[0].version, { etag })Page#
for await (const rows of client.listAll('articles', { limit: 100 })) {
for (const row of rows) console.log(row.title)
}Break out of the loop to bound the traversal. Later pages reflect concurrent changes, because pages are not a snapshot.
Collection types#
The client knows no deployment's collections. Generate a type map and pass it as a type parameter:
import type { Collections } from './generated/collections'
const client = new DataClient<Collections>({ baseUrl, workspaceId })
const page = await client.list('articles') // typed article rowsWithout a map, rows are RecordValue and keys are plain strings. Collection names and field types follow the deployment manifest, outside the client's major version.
In this repository the generator writes packages/client/src/deployment.ts; another deployment writes a file of the same shape in its own repository:
bun run scripts/dev/generate-client.ts # write the file
bun run scripts/dev/generate-client.ts --check # fail on a stale fileErrors#
Every failure throws ApiError with the service code, status, message, field details and request ID. Switch on the code (spec §9.7):
import { ApiError } from '@data-backend/client'
try {
await client.update('articles', id, { title: 'x' }, { etag })
} catch (error) {
if (error instanceof ApiError && error.code === 'PRECONDITION_FAILED') {
// the record changed
}
}Package versioning#
| Bump | Means |
|---|---|
| patch | Nothing clients depend on changes. |
| minor | A new method or option; existing code keeps working. |
| major | An answer's shape, a method name or an option's meaning changes. |
bun run check:client-package packs the client with the release command, installs the tarball in a consumer outside the repository, imports it by its public name and calls the API under Bun and Node.