Skip to content

Client and live UI

protobase/client is a typed fetch client for /api/v1; src/ui/data wraps it in TanStack Query hooks; src/ui/app is the admin that runs on both. Everything runs in the browser (or anywhere with fetch) and imports only protobase/schema.

import { createClient } from 'protobase/client'
import { where } from 'protobase/schema'
const api = createClient({ baseUrl: '/api/v1', onMetaVersion: (version) => { /* refetch meta when it changes */ } })
const page = await api.list(orders, { filter: where.eq('status', 'draft'), orderBy: 'createdAt desc', pageSize: 50, fields: ['id', 'number'] })
const { record, etag } = await api.get(orders, id)
await api.update(orders, id, { status: 'shipped' }, etag) // 412 rejects with PreconditionFailedError

Pass a resource builder to get its record type (InferRecord), or a name for loose records. Filters are AIP-160 text or where results (printed with printFilter). Errors are ApiError (status, slug, errors, filterErrors with spans). Also: search, create, remove, facets, series, histogram, seek, meta(etag?).

<App baseUrl="/api/v1" /> renders the shell from /api/meta (beside the API, so createClient derives it from baseUrl) and routes /:page (composed pages), /:resource (list) and /:resource/:key (record); it opens on the first page, or the first resource. ui takes the project’s custom components and action handlers. The list state lives in the URL: ?filter= (AIP-160), ?order_by=, ?layout=panel. Views come from examples/*/config/*/ui.ts. The sidebar’s recent-record groups and the user menu are configured there too: see Sidebar and User menu.

pnpm --filter erp db:up && pnpm --filter erp serve, then pnpm storybook. Storybook proxies /api to PROTOBASE_API (default http://localhost:8787); pick another API in the toolbar. Without a server the stories say so. pnpm screenshots:live writes screenshots/live/.