description: "面向开发者与维护者的会话投影注册表说明,用于向客户端载体提供日志派生逐会话状态的完整当前值,或维护驱动约定。"
English | 中文
当客户端需要当前的逐会话状态(例如待办事项、目标或对话统计)而不应自行重放原始事件日志时,使用 dsh-session-projection。领域根据已提交的会话事件定义同步投影,客户端则通过快照与变更通知接收经过 schema 校验的完整 JSON 值。快照标明所有返回值共同反映到的最后一个事件,因此载体可以把状态与对应的历史切面配对。投影状态可以通过检查点加快冷读,而仅供 host 使用的投影不会暴露给客户端。
在客户端载体需要日志派生会话状态的当前值处挂载 dsh-session-projection。领域插件注册单元;载体读取快照并订阅变更流;两侧互不相识。
当领域保存客户端应看到、但不应自行重新派生的状态——todo 清单、goal 快照、对话统计——时选择本包。注册表在已提交事件上主动驱动单元,因此任何已注册单元的值按构造即为当前值。当维护的是无客户端读取的 host-only 记账时跳过:不带 wire 块的单元保持 host-only。host 读取方要么在插件 inject 中声明 sessionProjections,要么在注册表或必需 key 缺席时明确失败。贡献方可以继续通过 ctx.inject(['sessionProjections'], ...) 保持可选注册。
领域为每个状态键贡献一个 ProjectionDefinition:一个 key、状态 schema、初始状态、同步折叠 apply(state, event)、可选的 wire 块(把状态投影为客户端视图),以及状态字段或折叠语义变化时递增的 stateVersion:
const definition = {
key: 'todo',
stateSchema: todoStateSchema,
stateVersion: 1,
init: (_header, _inheritedEventCount) => ({ items: [] }),
apply: (state, event) => event.type === 'todo/upsert'
? { items: event.data.items }
: state,
wire: {
viewSchema: todoViewSchema,
view: state => ({ items: state.items }),
},
}
init(header, inheritedEventCount) 同时接收轻量元数据与精确的 fork 继承切点;它不得从 firstLiveSeq 或 session/end-seed 推断该切点。apply 必须同步,且对与单元无关的事件必须返回同一个状态引用——引用不变意味着零下游工作。注册表用 Object.is 比较相邻的 wire.view 原始结果;对象或数组 view 若要在仅内部 state 变化时抑制发布,就必须复用引用,结构相同的新对象仍算变化。携带状态的日志事件必须携带变更后的完整状态,绝不携带裸增量。
register(definition) 安装单元;具有相同 key 和 stateVersion 的注册方共享其 cell,版本不兼容或 stateVersion 非法时会 throw。注册是挂在调用方 fiber 上的 effect,因此最后一个注册方卸载后会移除 key 及其缓存 cell。载体用 snapshot(session) 对每个客户端可见单元读取一致的同步切面——{ asOfSeq, values },其中 asOfSeq 是所有值共同反映到的最后一个事件的 seq——并用 onChanged(listener) 订阅逐变更通知。stateOf(session, key) 读取一个单元的实时只读 host 状态,不计算无关视图。
const dispose = ctx.sessionProjections.register(definition)
const { asOfSeq, values } = ctx.sessionProjections.snapshot(session)
必须使用投影状态的领域把 sessionProjections 声明为 Cordis 服务依赖;可选贡献方可以在 ctx.inject(['sessionProjections'], …) 下注册。载体使用 ctx.get('sessionProjections'),注册表缺席时省略自己的块或帧。
系统通过 checkpoint(session) 为每个单元的状态创建检查点,client-visible 与 host-only 一视同仁;同级包 session-projection-cache 持久化这些检查点,使冷读跳过全量日志加载。检查点水位使用 SessionSeqCursor(空日志为 -1),回放起点使用 SessionLogOffset;restoreFloor 与 restore 实现读取流程,且不会混淆已有事件与日志间隙。
当包级约定不够用时阅读以下页面。它们从单元约定逐步进入读模型子系统与持久缓存。
无——注册表只为已入日志的会话状态提供面向客户端的读模型,不注册任何模型可见内容。
无;投影从不组装或发送提供方请求。
这些限制说明投影注册表在大规模下何时需要特别处理。它们是当前包约束,不是任务积压。
dsh-session-projection-cache 的组合改由持久行播种该折叠。wire.viewSchema.parse 能拒绝返回 Promise 的 view,但阻塞的 apply、或读取撕裂的非会话状态的 apply,只能靠评审把关。