Live data
Live data
This is how a tenant app touches data. Reads are live queries , writes are optimistic mutations , and both go through $bolt/client — you never open a connection, manage a cache, or invalidate anything. Bolt’s sync engine keeps every client current; this page is the author-facing half of that contract.
Reads are live queries
Every read is a live query registered with and evaluated by the server under the acting principal’s policy. Use client.db.<collection>.findMany , findFirst , or count . findMany and findFirst with a contiguous limit are live prefixes registered on sync.connect. count, findGrouped, a cursored after page, semantic search, and a declared similarity (nearest) search are one-shot and are never filed live. The browser holds only the current answer in memory. When a commit changes a live prefix’s dependencies, the host advances that prefix and the stream carries a version-fenced keyed delta — or a reset that forces the browser to re-register.
import { client } from '$bolt/client';
const orders = client.db.orders.findMany({
where: { status: { eq: 'open' } },
with: { customer: true },
orderBy: { created_at: 'desc' }
});
// Mounted once, registered with the server, then updated by SSE apply frames. On first mount, the client sends one sync.connect handshake that registers every live prefix and returns its current rows plus the plan the host will file. Reconnect re-registers those prefixes; a missed wake closes the stream. There is no changelog to replay and no digest of held ids.
Writes are optimistic mutations
Browser writes use client.collection.<collection>.create(input) . It resolves with an optimistic result whose durability is memory and a settlement handle. The Machine pushes the idempotent graph over HTTP, and the authority reports its outcome through sync. client.collection.<collection>.pending counts writes while their enqueue operation is in flight; authoritative status belongs to each returned settlement handle.
client.collection.cost_estimates.update(id, input)
input = the declared columns + explicit relation actions
│ enqueue in this tab's memory
▼
optimistic row projects over held in-memory query answers
│ collections.write push with idempotency key
▼
server: refuse anything outside the selection → policy → transform →
approval route → commit the root + every nested action
│ → history + SyncChange capture (one transaction)
▼
host advances registered live prefixes for changed collections
│ one ordered SSE apply frame
▼
keyed deltas or resets + authoritative write settlement
(accepted | rebased | rejected | quarantined)
> The input is the collection’s declared selection: its columns, and under each relation the explicit actions it accepts — `create`, `update`, `upsert`, `link`, `unlink`, `delete`. An omitted relation or an empty array does nothing; nothing is ever deleted by omission. A create carries no id — the server allocates it. The root and every nested action commit atomically.
Generated types describe that nested graph without casts or compatibility wrappers. Queries own current, loading, and error; mutations own pending. Components do not duplicate query data, refresh, loading, error, or mutation state.
import { client } from '$bolt/client';
const write = await client.collection.cost_estimates.update(id, {
status: 'submitted',
line_items: {
create: [{ description: 'Scaffolding', amount: 1200 }],
update: [{ id: lineId, set: { amount: 950 } }],
delete: [{ id: staleId }]
}
});
// Relation actions are explicit. An omitted relation or an empty array does
// nothing; nothing is ever deleted by omission. The root and every nested
// action commit atomically.
write.row; // optimistic projection; durability is this tab's memory
await write.settlement.settled; // authoritative outcome arrives through sync A write returns an optimistic row, not a durable server record. Write-only and row-filtered policies may permit a write without permitting the matching read, so the registered live query remains the source of authorized current data. The settlement handle reports accepted, rebased, rejected, or quarantined; a write routed to approval is committed provisionally under a hold and settles as pending approval.
The invariant
The server-authoritative answer is the only committed truth. The browser keeps mounted answers in memory and applies only version-fenced keyed deltas, with no invalidate , refetch , or revalidate anywhere in app code.
Related guides
- Collections — define the models live queries read
- Apps — compose operational UIs on top of live reads
- Sync engine — how registered queries, SSE apply frames, and mutation pushes work
- Policies — scope every server-authoritative answer