Client
Client
A page imports one value: import { bolt } from '$bolt'. $bolt is the generated alias of @norbital-ai/bolt/page, typed by the workspace’s names index with the same types as server code, so an unknown collection, field, query, action, input or message key is a type error in bolt check, exactly as on the server.
<script lang="ts">
// src/data/collection/job_assignments/+representation.svelte (templates/field-operations, excerpt)
import { bolt, live } from '$bolt';
import type { RecordView } from '@norbital-ai/ui';
let { view }: { view: RecordView<'job_assignments'> } = $props();
const record = $derived(view.mode === 'update' ? view.record : null);
// one row by id, with the fields it names
const site = live(() => record === null ? null
: bolt.live(bolt.get('sites', record.site_id, { name: true })));
// a page of rows: every read states limit or all
const variations = live(() =>
record == null
? null
: bolt.live(bolt.read('variation_requests', {
where: { job_assignment_id: { eq: record.id } },
orderBy: { requested_at: 'desc' },
limit: 50
}))
);
</script> Reads
read(collection, { where?, select?, orderBy?, search?, limit | all })— a page of rows. There is no default page size: every read stateslimitorall: true.get(collection, id, select?)— one row, ornull.aggregate(collection, { count?, sum?, avg?, min?, max?, by?, where? })— one row over the scope, or pages of groups.similar(collection, '<search>', input, options)— a named similarity search.history(collection, id, { at? })— a row’s revisions, or its state at an instant, a revision, or an approval’s restore point.query('<c>.<q>', input)— a collection query.
Each returns a query that has not run: await reads once, and bolt.live(q) keeps it live. Reads reject only on failure.
Writes and runs
// src/app/field_ops_controller/+dispatch.page.svelte (templates/field-operations, excerpt)
import { Effect } from 'effect';
import { bolt, fork } from '$bolt';
import type { Id } from '@norbital-ai/bolt';
import { downloadTextFiles } from '@norbital-ai/ui';
let review = $state<{ id: Id<'sys_run'> } | null>(null);
function runReview() {
const started = bolt.start('review_job_assignment_suspicion', {});
review = { id: started.id }; // only plain data enters $state
fork(started);
}
// src/app/hr_controller/payroll/+payroll.page.svelte (templates/hr-payroll, excerpt)
const exportPayroll = (ids: readonly Id<'payroll_run'>[]) =>
bolt.query('payroll_run.export_payroll', { ids }).pipe(
Effect.tap(({ documents }) => Effect.sync(() => downloadTextFiles(documents, 'payroll-exports.zip')))
); // return the Effect to toolbar.actions act('<c>.create' | '<c>.update' | '<c>.delete' | '<c>.<action>', input)— every write. It answers anOutcomeand never rejects.start(automation, input)— starts an automation run and returns itsRunHandle, which can be awaited.upload(file, '<c>.<field>')— stores a file for afilefield, checkingacceptandmaxbefore the bytes are stored.fileUrl(ref)is its download link, served only while the viewer may read the field that holds it.approvals.process(requestId, decision)andapprovals.withdraw(requestId)— approval decisions.
Who and where
bolt.actor is who is looking at the page; key rows by actor.id, never by a label. bolt.t(key, vars?) translates a catalog key, and bolt.locale is the viewer’s locale. Sign-in, sign-out and invitations are shell pages, not part of bolt.
Views do most of this for you
@norbital-ai/ui views read, page, filter, write and open records on their own from one of prop. Reach for bolt directly for a computed panel, a dashboard figure or a custom button: see UI components.