写入契约
写入契约
集合在模型旁的 `+collection.ts` 中声明可以写入什么——调用方可提交的输入、把它变成集合实际写入内容的 transform、一次写入所引发的通知,以及该集合作为搜索命令提供的相似度索引。没有它的模型是只读的:无论直接还是经由关联,都无法写入。
输入与负载
输入 是一份正向白名单——`columns` 列出字段,`with` 列出调用方可嵌套的关联操作——超出它的内容在任何逻辑运行之前就会被拒绝。 transform 对每个被接纳的批次运行一次,以工作区身份通过 `db` 读取,并按顺序为每个输入返回一份负载,或者拒绝——拒绝时什么都不会写入。没有 transform 时,解码后的输入就是负载。
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.' })
}
]
}
}); 操作
每个操作单独声明。`create` 与 `update` 指定各自的输入选择; delete: {} 暴露不带输入、不经 transform 的 delete:
| 操作 | 输入 | 引擎做什么 |
|---|---|---|
create | 所选列——非空且无默认值的列为必填——以及嵌套的 `create` 操作;不带 id | 分配 id,运行 transform,在一个事务中提交整张图 |
update | 所选列的部分值,以及显式的关联操作:`create`、`update`、`upsert`、`link`、`unlink`、`delete` | 断言观察到的行版本,对照 `existing` 运行 transform,提交图 |
delete | 仅 id | 读取级联闭包,按模型声明移除依赖行,并把每一条被移除的行记入历史 |
持久工作属于自动化,不属于 transform
transform 属于别人写入的一部分:它在事务之前运行,其读取会在事务内重新断言,读取预算为两轮。必须存活于写入之后的工作属于 自动化 ——定时、事件驱动或由代码通过 `api.automations.run` 启动,各自是一次持久化的后台运行。通知则属于 `notifications`:在提交中写入,由宿主投递。
选择最窄的角色
配套角色刻意重叠——选择最合适的一个: