集合
集合
集合 是一张领域数据表外加其服务器行为。每个集合是 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 、 history 、 exclusions 与 embedding 。展示归应用负责,因此枚举配色、默认排序与渲染器变体都不属于模型。封闭取值集合用 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 ;不需要注册文件。
+collection.ts——写入契约:输入选择、transform、删除与通知( 写入契约 )+pipelines.ts——规范的集合导入/导出行为( 流水线 )+integrations.ts——复用流水线的外部收发绑定( 集成 )+representation.svelte——每个面向用户的集合都必须编写的创建/展示/编辑表单( UI 组件 )
系统集合
每个工作区都随附一组固定的 系统集合 ,它们在 @norbital-ai/bolt 中定义,构建时被合并进你的清单,为身份、访问控制、审批与文件提供支撑。你不需要在租户 collections/ 中重新定义它们——像任何其他集合一样查询即可。
withSystemCollections 把这些系统模式合并进来。像 user 或 approval_request 这样的集合,不要在租户 +model.ts 中复制或覆盖——平台依赖它们的确切形态。你 可以 像任何其他集合一样从应用、transform、自动化和远程函数中读取与查询它们。身份与访问
- 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——每个保留历史的集合(默认为全部集合,除非模型关闭它)每次创建、更新或删除都写入一行:操作类型、执行者,以及当时取值的快照。历史按每条记录最近 256 个修订裁剪。bolt_audit——平台审批事件流水账,按事件类型与主体记录。conversation与conversation_message——会话聚合:一个持久会话、其有序消息,以及其背后受栅栏保护的轮次与计量用量。plan 与 automation_run 也一并发布。telemetry——每一轮对话、模型调用、工具调用、写入与失败调用一行,采用 OpenTelemetry 日志形态,带 severity、event、attributes 与关联 id。运行时为每个租户保留记录,并按宿主保留窗口裁剪;只有管理员可以读取。bolt_notifications——应用内通知行,由通知设施写入与读取。
与领域集合的对比
领域集合是你的:薪资发放、发货、工单等。系统集合则是每个租户共享的平台底座——运行时拥有它们的形态,工作区只读取而不写入。
搜索与相似度
每个集合都有平台搜索命令: /text 搜索显式开启搜索的字段, /semantic 搜索平台维护的嵌入,每个声明的相似度索引还会生成一个 /<index> 命令。浏览器只发送索引名与表单目标——从不发送原始向量。
文本搜索按字段显式开启( text({ search: true }) );当模型元数据用 embedding 声明了参与字段时,语义搜索即存在,平台在每次写入时生成向量:
import { defineModel, text } from '@norbital-ai/bolt/authoring';
export default defineModel(
{
name: text({ search: true }).notNull(),
notes: text({ search: true })
},
{
description: 'Product',
recordLabel: 'name',
// The platform maintains a record_embedding vector over the named fields on every
// write, so /semantic searches the collection without any server code.
embedding: { fields: ['name', 'notes'], dimensions: 1536 }
}
); 领域自己的“最近”定义在写入契约旁声明; similarity 会成为该集合的 /<name> 命令:
import { defineCollection } from '@norbital-ai/bolt/authoring';
import model from './+model.js';
export default defineCollection({
model,
// A declared index becomes the /<name> search command in every table toolbar.
similarity: {
by_colour: {
label: 'Closest colour',
column: 'lab_vector',
metric: 'l2',
input: {
l: { kind: 'number', label: 'L*', min: 0, max: 100, step: 0.1 },
a: { kind: 'number', label: 'a*' },
b: { kind: 'number', label: 'b*' },
line: { kind: 'reference', collection: 'lines', label: 'Line' }
},
// Fills the vector column from the row on every write.
embed: (row) => ({ lab_vector: [row.lab_l, row.lab_a, row.lab_b] }),
target: (input) => ({
probe: [Number(input['l']), Number(input['a']), Number(input['b'])],
// A reference control narrows by equality instead of re-measuring.
where: { line_id: String(input['line']) }
})
}
}
}); 服务器代码直接用 api.db.<collection>.findNearest({ column, probe, metric?, where? }) 读取最近邻,由数据库的向量索引应答;声明的相似度搜索是一次性的,永远不是实时前缀。
集合数据如何被读取
在租户应用中,集合数据通过 实时数据 层读取: client.db.<collection>.findMany 、 findFirst 与 count 与 findGrouped 是一次性聚合读取;只有带连续 limit 的 findMany 与 findFirst 是实时前缀。服务器上的 transform 与函数仍然使用 api.db ——实时查询与乐观变更是运营 UI 的浏览器读写路径。
服务器端读取还包括 findNearest:基于 vector() 列的向量索引最近邻读取——column 加 probe,可选 metric 与 where——由租户数据库的索引应答,行按测得距离由近及远返回,每行附带 distance。它与其他任何读取一样受集合策略约束;声明的索引与平台命令见“搜索与相似度”一节。