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 PreconditionFailedErrorPass 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?).
The admin app
Section titled “The admin app”<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.
Global search
Section titled “Global search”The search box in the top bar looks in every resource the user can read that has search fields (.search(...) in its data config) and a view in the sidebar or the user menu. Its placeholder names them, such as “Search organizations, services and more”; without any such resource there is no box.
From two characters on, it asks each of those resources’ list API for search("<text>") with the user’s token, five records each, so row filters and hidden fields apply exactly as on the lists and it never offers a record the user could not open. The results are grouped by resource and named by each record’s title. ⌘K (Ctrl+K) focuses the box, or opens it on a phone; the arrow keys move through the results, Enter or a click opens the record, and Escape clears the box, then leaves it. A resource whose search fails says so in its group.
Live stories
Section titled “Live stories”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/.