Write contract
Write contract
A collection declares what may be written in `+collection.ts` beside its model — the input a caller may submit, a transform that turns it into what the collection writes, the notifications a write raises, and any similarity indexes the collection offers as search commands. A model without one is read-only: nothing writes it, directly or through a relation.
Input and payload
The input is a positive allowlist — `columns` names fields, `with` names the relation actions a caller may nest — and anything outside it is refused before anything runs. The transform runs once per admitted batch, reads as the workspace through `db`, and returns one payload per input or refuses — a refusal writes nothing. Without a transform the decoded input is the payload.
import { defineCollection, refuse } from '@norbital-ai/bolt/authoring';
import { Effect } from 'effect';
import model from './+model.js';
export default defineCollection({
model,
create: { input: { columns: { name: true, status: true } } },
update: { input: { columns: { status: true } } },
delete: {},
transform: (inputs, { existing, db }) =>
Effect.gen(function* () {
const names = inputs.map((input, i) => input.name ?? existing[i]?.name ?? '');
const taken = yield* db.sites.count({ where: { name: { in: names } } });
if (taken) refuse('Site already exists.');
return inputs;
}),
notifications: {
committed: [
{
channel: 'inbox',
recipients: ({ requestor }) => [requestor],
message: () => ({ title: 'Site saved', body: 'The site was saved.' })
}
]
}
}); Operations
Each operation is declared on its own. `create` and `update` name their input selection; delete: {} exposes delete with no input and no transform:
| Operation | Input | What the engine does |
|---|---|---|
create | the selected columns — non-nullable, undefaulted ones required — and nested `create` actions; no id | allocates the id, runs the transform, commits the whole graph in one transaction |
update | a partial of the selected columns and explicit relation actions: `create`, `update`, `upsert`, `link`, `unlink`, `delete` | asserts the observed row versions, runs the transform against `existing`, commits the graph |
delete | the id only | reads the cascade closure, removes dependents as the model says, records every removed row in history |
Choose the narrowest role
Companion roles overlap on purpose — pick the narrowest one that fits:
- Write contract — what a caller may submit, mutation invariants, and the notices a write raises
- Pipelines — reusable bulk ingest and artifact contracts ( pipelines)
- Integrations — reliable external delivery that reuses pipelines ( integrations)