集合
集合
集合 是一张领域数据表外加其服务器行为。每个集合是 src/collections/<lower_snake_case_id>/ 下的一个目录。目录名就是集合 ID;模型不会重复声明它。
模型
import { defineModel, enums, text } from '@norbital-ai/bolt/authoring';
export default defineModel(
{
name: text().notNull(),
status: enums(['active', 'complete'])
},
{ description: 'Project site', recordLabel: 'name', icon: 'lucide:map-pin' }
); 模型只承载存储与数据标识:列,外加 description 、 recordLabel 、 icon 与 indexes 。展示归应用负责,因此枚举配色、默认排序与渲染器变体都不属于模型。封闭取值集合用 enums([...]) ,并把 recordLabel 指向用于在界面上标识记录的那一列——或那几列。把声明保存为 src/collections/sites/+model.ts 。
列类型
字段就是一列。Bolt 重新导出基础构建器( text 、 integer 、 boolean 、 uuid ),并添加带有类型化存储与对应 UI 行为的领域列类型:
| 列 | 存储为 | 说明 |
|---|---|---|
text() | text | 基础字符串字段 |
numeric() | numeric | 以 JS 数字读取。 numeric() 不接受任何选项——数字如何展示归应用管,而不归列管。整数用 integer ,仅仅看起来像数字的值(例如参考编号)用 text 。 |
instant({ precision }) | timestamptz | 日历日期( UTC ISO );`precision` 只收窄选择器 |
custom('instant_range', { multiple, precision }) | jsonb | 连续的瞬时区间( { start, end } );`end` 可为 null(开放区间)。多区间用 multiple: true |
custom('money', { allowedCurrencies }) | jsonb | 带 ISO 4217 货币代码的金额——平台自有的数据类型;重复声明是编译错误 |
vector({ dimensions }) | vector | 嵌入向量;维度在声明时固定 |
geolocation() | jsonb | GeoJSON 风格的点—— { geometry: { lon, lat }, formatted_address, ... } 。 |
phone() | text | 带电话专用编辑语义的电话号码 |
enums([...]) | text | 封闭取值集合;值在边界处校验。多选用 .array() |
file({ mimeTypes, multiple }) | jsonb | 完全内联存储为 FileRef ——storage_key、file_name、file_size 与 mime_type 随行。可选的 mimeTypes 过滤;多文件用 multiple: true |
custom(kind) | 视自定义类型而定 | 在 src/datatypes/<name>/ 中定义的命名自定义值——参见 UI 组件 |
列支持标准修饰符: .notNull() 、 .default(...) 、 .array() ,以及用于生成默认值的 sql 模板。每一行还会自动携带平台列 id 、 created_at 、 updated_at 与 row_version ——你永远不需要声明它们。
一个把其中几种组合起来的模型示例:
import {
custom, enums, file, geolocation, instant,
numeric, phone, text, vector
} from '@norbital-ai/bolt/authoring';
export default defineModel(
{
title: text().notNull(),
status: enums(['active', 'complete']).notNull().default('active'),
progress: numeric(),
starts_on: instant({ precision: 'day' }),
window: custom('instant_range'),
budget: custom('money'),
embedding: vector({ dimensions: 1536 }),
location: geolocation(),
contact: phone(),
report: file({ mimeTypes: ['application/pdf'] })
},
{ description: 'Project site', recordLabel: 'title' }
); 关联
唯一的 src/collections/+relationship.ts 角色为整个注册表定义关联,并使用其相邻的生成类型:
import type { Relationships } from './$types.js';
export default ((r) => ({
sites: { site_visits: r.many.site_visits() },
site_visits: {
site: r.one.sites({ from: r.site_visits.site_id, to: r.sites.id })
}
})) satisfies Relationships; 配套角色
服务器行为位于模型旁被识别的角色文件中。每个角色默认导出一份声明,并使用相邻的生成 ./$types.js ;不需要注册文件。
+hooks.ts——逐记录校验与同事务副作用( 钩子 )+pipelines.ts——规范的集合导入/导出行为( 流水线 )+integrations.ts——复用流水线的外部收发绑定( 集成 )+representation.svelte——由模式派生的表单覆盖( UI 组件 )
系统集合
每个工作区都随附一组固定的 系统集合 ,它们在 @norbital-ai/bolt 中定义,构建时被合并进你的清单,为身份、访问控制、审批与文件提供支撑。你不需要在租户 collections/ 中重新定义它们——像任何其他集合一样查询即可。
withSystemCollections 把这些系统模式合并进来。像 user 或 approval_request 这样的集合,不要在租户 +model.ts 中复制或覆盖——平台依赖它们的确切形态。你 可以 像任何其他集合一样从应用、钩子、自动化和远程函数中读取与查询它们。身份与访问
- user ——每个人对应一行:姓名、邮箱、管理员标志(normal 或 admin),以及所属团队。团队能做什么在
src/access/+teams.ts中声明——一份编译进发布的映射,而不是一行数据。 - session, account, verification, auth_config ——登录会话、已关联的凭据、验证令牌,以及用于签发会话的密钥。运行时是它们唯一的写入方。
- 策略不是数据行。一条策略是工作区源码中的一个
src/access/policies/+<name>.ts模块,与它所授权的集合一起被编译进清单。
从工作区看它们都是只读的:运行时自己的系统集合策略授予每个已认证主体 read ,从不授予写入,因此拥有某张表的运行时始终是它唯一的写入方。 user 还被进一步收窄——查询只能看到 id 与姓名,永远读不到邮箱。授权如何作用于领域集合参见 策略。
审批
- approval_request ——针对一次集合变更的一条审批流程,无论开启还是已关闭:它锁住哪条记录、有哪些步骤、当前状态如何。参见 审批工作流。
- requestor ——把一条审批请求与发起它的用户关联起来。
文件
- file() ——平台没有文件表。`file()` 列把元数据整体内联存储(
FileRef{storage_key、file_name、file_size、mime_type}),宿主 files 设施按 `storage_key` 解析字节。列是记录自己的字段,因此行谓词与字段掩码照常适用。
不属于集合的运行时表
运行时还会创建一些并非集合的表。它们由模式计划创建,而不是在集合注册表中声明,因此不带平台列,不会进入浏览器副本,也无法通过客户端读取:
bolt_collection_history——每个保留历史的集合(默认为全部集合,除非模型关闭它)每次创建、更新或删除都写入一行:操作类型、执行者,以及当时取值的快照。bolt_audit——只追加的平台流水账,按事件类型与主体记录:审批决定、以团队身份预览,以及已入队的通知。chat_session与chat_message——Agent 会话及其记录下来的每一轮对话;这两个是集合,是叶子表中的一个例外。bolt_notifications——应用内通知行,由通知设施写入与读取。
与领域集合的对比
领域集合是你的:薪资发放、发货、工单等。系统集合则是每个租户共享的平台底座——运行时拥有它们的形态,工作区只读取而不写入。
集合数据如何被读取
在租户应用中,集合数据通过 实时数据 层读取: client.db.<collection>.findMany 、 findFirst 与 count 作为针对本地副本的实时查询执行。服务器上的钩子与远程函数仍然使用 api.db ——实时查询与乐观变更是运营 UI 的浏览器读写路径。