跳到主要内容

写入契约

写入契约

集合在模型旁的 `+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`:在提交中写入,由宿主投递。

选择最窄的角色

配套角色刻意重叠——选择最合适的一个:

  • 写入契约 ——调用方可提交什么、变更不变量,以及写入引发的通知
  • 流水线 ——可复用的批量摄取与产物约定( 流水线)
  • 集成 ——复用流水线的可靠外部投递( 集成)