quickback-provider
QuickbackProvider plus TanStack Query hooks and role helpers over the emitted client — the base every other item builds on.
npx shadcn add @quickback/quickback-providerInstalls components/quickback-provider.tsx and @tanstack/react-query@^5.
The other items import their hooks from it.
<QuickbackProvider>
import { createClient } from "@/lib/quickback.client"
import { QuickbackProvider } from "@/components/quickback-provider"
const client = createClient({ baseUrl: "https://api.example.com" })
<QuickbackProvider client={client} roles={roles}>
<App />
</QuickbackProvider>| Prop | Type | Description |
|---|---|---|
client | Client | The result of createClient() from the emitted @/lib/quickback.client. Required. |
roles | readonly string[] | The signed-in caller's roles, e.g. the active organization member role from Better Auth. A UI hint only. See Roles. |
realtime | { subscribe(handler) => unsubscribe } | Source of realtime frames for useLive. Defaults to client.realtime when the emitted client has one. |
queryClient | QueryClient | Share your app's TanStack Query cache. Omit it and the provider creates one that retries network errors and 5xx twice and never retries a 4xx. |
children | ReactNode |
Hooks
Every hook takes a schema-registry table name ("jobs"). The name is
resolved through the client's resources table, so generic UI never
hard-codes a URL. An unknown name throws Unknown Quickback resource "<name>".
Calling a verb the table has no route for throws "<table>" has no <verb> route.
| Hook | Returns | Notes |
|---|---|---|
useQuickback() | Client | The typed client. Use it for anything the hooks don't cover. |
useRoles() | readonly string[] | undefined | The roles passed to the provider. |
useSchema() | query of SchemaRegistry | GET /api/v1/schema. Cached for the session. |
useTableMeta(table) | TableMeta | undefined | One table from the registry. undefined while the registry loads. |
useOps(table) | string[] | The operations that have a route (list, get, create, …). |
useList(table, params?) | query of { data, pagination } | params takes list params, plus view to read a named view and enabled. Keeps the previous page while the next one loads. |
useInfiniteList(table, params?) | infinite query | Pages with the keyset cursor (nextCursor → startingAfter). Falls back to offset when the server returns no cursor, for example when sorting on a masked column. |
useRecord(table, id) | query of the record | Disabled while id is undefined. |
useCreate(table) | mutation (body) => row | |
useUpdate(table) | mutation ({ id, body }) => row | |
useDelete(table) | mutation (id) => void | |
useAction(table, action) | mutation ({ id?, params?, input }) => output | Record-bound actions need id. Standalone actions with path parameters need params. |
useLive(table, enabled = true) | boolean | Refetches the table when a postgres_changes frame for it arrives. Returns whether a realtime source is wired up. |
useDebouncedValue(value, ms = 250) | T | value, updated only after it stops changing for ms. |
Every mutation invalidates all of the table's cached queries.
import { useList, useUpdate } from "@/components/quickback-provider"
function OpenJobs() {
const jobs = useList("jobs", { filter: { status: "open" }, sort: "-createdAt", limit: 10 })
const update = useUpdate("jobs")
if (jobs.isPending) return <p>Loading…</p>
return jobs.data?.data.map((job) => (
<button key={String(job.id)} onClick={() => update.mutate({ id: String(job.id), body: { status: "closed" } })}>
Close {String(job.title)}
</button>
))
}The hooks are deliberately untyped by name (Row = Record<string, unknown>),
because they serve generic UI. For typed rows, call the client directly:
useQuickback().jobs.list() returns Api.jobs.Row[].
Roles
The emitted client has no session method, so the provider only knows the caller's roles if you pass them. Read the role from your auth client:
const member = auth.useActiveMember() // better-auth organizationClient()
<QuickbackProvider client={client} roles={member.data ? [member.data.role] : undefined}>Roles are a UI hint. The API stays authoritative:
- Masking. A masked column is read-only unless the
roles satisfy its
showrule. With noroles, every masked column is read-only. A value that arrives masked ([REDACTED],j***@…) is always read-only. - Access. When the roles plainly fail an operation's
access.roles, the form's Delete button is hidden and the action dialog shows a role message instead of its form. The client can't decide hierarchy (member+),record, or FGA rules, so those controls stay available and the API answers.
Realtime
useLive(table) refetches the table on its change frames. ResourceTable
calls it unless live={false}. The frames come from client.realtime when
the emitted client has one, which it does when the project enables
realtime. Otherwise pass any
{ subscribe(handler) => unsubscribe } source as realtime. See
Using Realtime for the ticket and
WebSocket handshake.
Helpers
The other items use these, and you can too:
| Export | Description |
|---|---|
describeError(error) | { message, fields, status? }. message is the problem's detail, then its title, then error.message. fields maps field names to messages, from QuickbackError.fields. |
isMasked(meta, column, roles, value?) | Whether the column may reach these roles masked. |
canAccess(access, roles) | Best-effort role check. Returns true for rules the client can't decide. |
canWrite(meta, op, roles) | canAccess for create / update / delete from the registry. |
getDisplayColumn(tableMeta) | The column that names a record: the table's displayColumn, else the first of name, title, label, … |
formatLabel(name) / columnLabel(name, col?) | salaryMin → Salary Min. An FK column reads as its target: companyId → Company. |
REDACTED | "[REDACTED]", the value the API sends in place of a redacted value. |
Types: TableMeta, ColumnMeta, ActionMeta (all derived from the emitted
SchemaRegistry), Row, Page, RealtimeSource, and RealtimeFrame.
Auth
Sign users in for the components — Better Auth cookies in the browser, bearer tokens in React Native and other non-browser clients.
resource-table
Table for any Quickback resource or view — cursor pagination, server search, sort, column picker, realtime refresh — with columns from the schema registry.