doxum 0.1.28 → 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +3 -2
  2. package/dist/advanced.cjs +1 -1
  3. package/dist/advanced.js +1 -1
  4. package/dist/{driver-B5vdPohL.js → driver-B7RR-VfX.js} +31 -12
  5. package/dist/driver-B7RR-VfX.js.map +1 -0
  6. package/dist/{driver-BL91gn7_.cjs → driver-CN3dGxQI.cjs} +31 -12
  7. package/dist/driver-CN3dGxQI.cjs.map +1 -0
  8. package/dist/index.cjs +206 -133
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.js +206 -133
  11. package/dist/index.js.map +1 -1
  12. package/dist/local-sync.cjs +1 -1
  13. package/dist/local-sync.js +1 -1
  14. package/dist/{transform-DUleAsG0.cjs → transform-B6YxfR0R.cjs} +176 -50
  15. package/dist/transform-B6YxfR0R.cjs.map +1 -0
  16. package/dist/{transform-B4T3AcJr.js → transform-BQY9I6il.js} +147 -51
  17. package/dist/transform-BQY9I6il.js.map +1 -0
  18. package/package.json +3 -2
  19. package/skills/doxum-runtime/SKILL.md +64 -82
  20. package/skills/doxum-runtime/agents/openai.yaml +2 -2
  21. package/skills/doxum-runtime/references/document-runtime.md +370 -0
  22. package/skills/doxum-runtime/references/integrations.md +215 -0
  23. package/skills/doxum-runtime/references/{invariants.en.md → invariants.md} +5 -2
  24. package/skills/doxum-runtime/references/projections.md +467 -0
  25. package/skills/doxum-runtime/references/public-api.md +623 -0
  26. package/skills/doxum-runtime/references/recipes.md +493 -0
  27. package/dist/driver-B5vdPohL.js.map +0 -1
  28. package/dist/driver-BL91gn7_.cjs.map +0 -1
  29. package/dist/transform-B4T3AcJr.js.map +0 -1
  30. package/dist/transform-DUleAsG0.cjs.map +0 -1
  31. package/skills/doxum-runtime/references/api.en.md +0 -283
  32. package/skills/doxum-runtime/references/api.zh-CN.md +0 -276
  33. package/skills/doxum-runtime/references/guide.en.md +0 -138
  34. package/skills/doxum-runtime/references/guide.zh-CN.md +0 -116
  35. package/skills/doxum-runtime/references/invariants.zh-CN.md +0 -53
  36. package/skills/doxum-runtime/references/patterns.en.md +0 -162
  37. package/skills/doxum-runtime/references/patterns.zh-CN.md +0 -159
  38. package/skills/doxum-runtime/references/projections.en.md +0 -313
  39. package/skills/doxum-runtime/references/projections.zh-CN.md +0 -306
@@ -1,138 +0,0 @@
1
- # Doxum Guide
2
-
3
- For exact signatures across `doxum`, `doxum/advanced`, `doxum/react`, and
4
- `doxum/local-sync`, use the [API reference](api.en.md). This guide focuses on choices
5
- and lifecycle semantics.
6
-
7
- ## Define, Update And Read
8
-
9
- ```ts
10
- import { createDocument, field, map, object, read, snapshot, type Infer } from 'doxum';
11
-
12
- const task = object({ title: field<string>(), done: field<boolean>() });
13
- const model = object({ tasks: map(task) });
14
- type DocumentValue = Infer<typeof model>;
15
- const document = createDocument({
16
- schema: model,
17
- initial: { tasks: { a: { title: 'Write', done: false } } },
18
- });
19
- document.update(draft => {
20
- const task = draft.tasks.get('a');
21
- if (task) task.done = !task.done;
22
- draft.tasks.put('b', { title: 'Review', done: false });
23
- });
24
- const done = read(document, state => state.tasks.get('a')?.done);
25
- const tasks = read(document, state => snapshot(state.tasks));
26
- document.subscribe(
27
- path => path.tasks.item('a').done,
28
- commit => console.log(commit.changes)
29
- );
30
- ```
31
-
32
- Root object is definition identity; runtime owns its data/revision. Updates are
33
- synchronous and atomic. Reads see preceding writes. Draft and the Read passed to a
34
- `read` selector are borrowed for the synchronous callback and must not escape.
35
- Throw TransactionRejected for expected rejection; ordinary throws
36
- restore all work and rethrow unchanged. False/undefined returns are business values.
37
-
38
- Object exposes editable members; field is atomic, including arrays and objects.
39
- Atomic values are deeply readonly in Infer and scoped access. Inputs, snapshots,
40
- commits and history share payload references. Object/variant structure accepts only
41
- declared own properties (plus the variant discriminant); extra string, symbol and
42
- non-enumerable properties are rejected. Use maps for dynamic keys and fields for
43
- arbitrary payload objects. Never mutate payloads through any alias,
44
- even after removal. Snapshot copies schema structure only; snapshot(rawPayload)
45
- returns its original readonly reference. Published data is not frozen. Field copiers
46
- are not supported; mutable exports and serialization belong to application boundaries.
47
-
48
- Infer preserves optional properties and flat variant unions. Read/Draft contain
49
- collection tools. Maps use explicit methods; table/list/tree overload replace for
50
- member and whole-container replacement. `replace(parent, key, inferValue)` handles
51
- plain replacement of object or variant members that contain collection data.
52
-
53
- ## Containers And Validation
54
-
55
- | Definition | Data | Draft methods |
56
- | -------------------------------- | ------------- | --------------------------------------------------------- |
57
- | map(valueSchema, { key }?) | record | get/has/ids/put/remove/replace |
58
- | table(objectOrVariant, { key }?) | ids/byId | get/has/ids/create/remove/move/reorder/replace |
59
- | list(field, { keyOf }) | array | get/has/ids/insert/remove/move/reorder/replace |
60
- | tree(field) | rootId?/nodes | get/has/rootId/parent/children/insert/remove/move/replace |
61
-
62
- Read scopes expose only read methods. Map supports field/object/variant values;
63
- put is an upsert and removing a missing key is a no-op. `replace(id, value)` is
64
- existing-only for table/list/tree, preserves table/list order, and changes only a
65
- tree payload. `replace(value)` replaces a whole collection. List member replacement
66
- retains the addressed key. Simple arrays and strokes can be one
67
- atomic field. Optional supports field/variant/map/list/tree. Absent differs from
68
- present undefined. Variant tags are readonly; change branch by whole replacement.
69
- For table/list, `move(key | readonly key[], anchor?)` preserves a moved selection's
70
- current relative order and resolves the anchor after removal. `reorder(keys)` requires
71
- an exact permutation of current membership and changes only order.
72
- Trees may be empty. `tree(field(...))` requires an own `value` on every existing
73
- node; `tree(optional(field(...)))` explicitly permits missing node payloads, while
74
- `optional(tree(...))` independently permits the whole tree member to be absent.
75
-
76
- Function validators are synchronous predicates/assertions: `true` or `undefined`
77
- succeeds, `false` rejects, and assertions may throw. Standard Schema v1 validators
78
- must return the original input by identity; transformed output is rejected. Validators
79
- must not mutate input. Transform values before entering Doxum. parse(model, unknown) copies validated
80
- structure and shares readonly payloads; strict parse requires atomic validators.
81
- Branded map/table keys flow through methods, symbolic paths and impact.
82
- Path callbacks describe locations, including absent entries, and compile at registration.
83
- Core `select(document, selector, equality?)` tracks actual reads, changes dependencies
84
- when branching, and returns a standard `Readable`. React `useDocumentSelector`
85
- only adapts that Core readable to React.
86
-
87
- ## Changes And Consumers
88
-
89
- Commits contain revision/source/changes/impact. ChangeSet groups members by owning
90
- container, with direct added/removed/updated transitions and optional before/after
91
- order in the same group. Order-only groups have empty members; standalone order
92
- changes and duplicate groups are invalid. Each group is applied completely before
93
- the next. Touched tree
94
- nodes retain structural semantics; reset is an explicit whole-document transition.
95
- Root member groups remain incremental. Net-zero changes do not publish.
96
- apply(changes, { expectedRevision }) rejects a missing or mismatched local baseline.
97
- Received before values are untrusted; local undo records actual old state.
98
- History travels complete ChangeSets; grouped travel is atomic. Local replace is a
99
- reversible root reset; remote commits invalidate local history. Observer errors occur
100
- after acceptance.
101
-
102
- Use `observe(document, path => path.tasks)` for a collection boundary and
103
- `derive({ tasks, filter }, ({ tasks, filter }) => ...)` for pure values. Definitions are
104
- lazy and materialized by one `createProjectionRuntime({ onError })` owner. When a
105
- keyed source determines output keys/order, map entries independently with
106
- `derive.keyed(entries, entry => entry.label)`. Dynamic keyed lookup stays in that
107
- same derivation through a named dependency object; plain Projection members are
108
- global dependencies, `{ source, key }` members are singular per-output-key lookups,
109
- and `{ source, keys }` members are ordered plural lookups. The
110
- selector receives `(entry, key, dependencies)`, while the Runtime owns the
111
- binding/reverse index and the producer DAG remains explicit. Use isolated
112
- `doxum/advanced` incremental entry points only for retained state, custom cross-key
113
- indexes or keyed patches that `derive.keyed` cannot express. See the [projection
114
- reference](projections.en.md) for lifecycle, draft semantics, selector tracking,
115
- batching and recovery.
116
- Tree structure uses the same projection source protocols: observe `path.tree.rootId`
117
- as a scalar, `path.tree.nodes` as a keyed collection, or
118
- `path.tree.nodes.item(id)` as a single node. Committed tree-node groups route directly
119
- to affected keyed entries and the existing `CollectionOutput` publishes ordinary
120
- `CollectionChange` entries; unrelated nodes keep stable snapshot references instead
121
- of being resnapshotted.
122
- `input` and `observe(source)` connect external boundary values, including
123
- eventful value and collection sources. Dispose the
124
- Runtime with the owning service. `runtime.batch` defers projection settlement and
125
- listeners, not document commits or listeners. Reads inside a batch see the last
126
- publication; no cross-document rollback is provided. React uses one
127
- `useProjection` hook with an optional selector and `useInput` for setters.
128
-
129
- doxum/local-sync attaches IndexedDB and Web Lock leadership. Only the leader writes;
130
- followers replay contiguous durable sequence. Writes become visible before async
131
- persistence; flush waits for durability. External replace and external remote-marked
132
- apply are forbidden while attached. Version 5 / format 3 rejects old storage without
133
- deleting or migrating it. The adapter accepts JSON values only. Change limits count
134
- logical members and tree nodes, not just outer groups.
135
- Limits govern new local commits only; existing durable commits remain readable
136
- under smaller current limits. Treat the whole published ChangeSet as readonly:
137
- its identity carries reusable structural validation, not authority to skip local
138
- revision, schema or actual-before checks.
@@ -1,116 +0,0 @@
1
- # Doxum 使用指南
2
-
3
- `doxum`、`doxum/advanced`、`doxum/react`、`doxum/local-sync` 的精确公开签名见
4
- [API 参考](api.zh-CN.md)。本指南只保留选择原则与生命周期语义。
5
-
6
- ## 定义、修改与读取
7
-
8
- ```ts
9
- import { createDocument, field, map, object, read, snapshot, type Infer } from 'doxum';
10
-
11
- const task = object({ title: field<string>(), done: field<boolean>() });
12
- const model = object({ tasks: map(task) });
13
- type DocumentValue = Infer<typeof model>;
14
- const document = createDocument({
15
- schema: model,
16
- initial: { tasks: { a: { title: 'Write', done: false } } },
17
- });
18
- document.update(draft => {
19
- const task = draft.tasks.get('a');
20
- if (task) task.done = !task.done;
21
- draft.tasks.put('b', { title: 'Review', done: false });
22
- });
23
- const done = read(document, state => state.tasks.get('a')?.done);
24
- const tasks = read(document, state => snapshot(state.tasks));
25
- document.subscribe(
26
- path => path.tasks.item('a').done,
27
- commit => console.log(commit.changes)
28
- );
29
- ```
30
-
31
- 根 object 是定义身份,runtime 拥有状态与 revision。事务同步且原子,修改立即可读。
32
- Draft 和 `read` selector 接收的 Read 是同步回调内的借用视图,不得逃逸。预期拒绝抛 TransactionRejected;普通异常完整恢复后
33
- 原样抛出。正常返回 false/undefined 是业务结果,不表示拒绝。
34
-
35
- object 暴露可编辑成员;field 是原子值,包括对象和数组。Infer 与作用域内原子值深只读;
36
- 输入、快照、commit 和 history 共享 payload 引用,删除后也不能通过任何别名修改它。
37
- object/variant 结构只接受 schema 声明的自身属性及 variant 判别字段;额外的字符串、Symbol 和不可枚举属性都会被拒绝。
38
- 动态键使用 map,任意对象内部数据使用 field;这个限制不检查 field 的 payload 内部。
39
- snapshot 只复制 schema 结构;snapshot(rawPayload) 返回原始只读引用。
40
- 发布数据不做运行时冻结,不再支持字段 copier。需要可修改副本或序列化时由应用边界显式处理。
41
-
42
- Infer 保留 optional 属性和扁平 variant 联合。Read/Draft 带集合方法。map 使用显式方法;
43
- table/list/tree 的 replace 同时支持成员和整体替换。含集合数据的 object/variant 成员
44
- 使用 `replace(parent, key, inferValue)` 传入普通 Infer 数据。
45
-
46
- ## 容器与校验
47
-
48
- | 声明 | 数据 | Draft 方法 |
49
- | -------------------------------- | ------------- | --------------------------------------------------------- |
50
- | map(valueSchema, { key }?) | record | get/has/ids/put/remove/replace |
51
- | table(objectOrVariant, { key }?) | ids/byId | get/has/ids/create/remove/move/reorder/replace |
52
- | list(field, { keyOf }) | 普通数组 | get/has/ids/insert/remove/move/reorder/replace |
53
- | tree(field) | rootId?/nodes | get/has/rootId/parent/children/insert/remove/move/replace |
54
-
55
- Read 只暴露读取方法。map 支持 field/object/variant;put 是 upsert,remove 缺失键是 no-op。
56
- table/list/tree 的 `replace(id, value)` 只替换已存在成员,保持 table/list 顺序,且 tree
57
- 只改变 payload;`replace(value)` 替换整个集合。list 替换项必须保持寻址键。
58
- 简单数组与笔画可作为一个原子 field。optional 支持 field/variant/map/list/tree,
59
- 缺失与存在的 undefined 不同。variant tag 只读,通过整体替换切换分支。
60
- table/list 的 `move(key | readonly key[], anchor?)` 会保持 moved selection 当前的相对顺序,
61
- 并在移除 selection 后解析 anchor;`reorder(keys)` 要求与当前 membership 完全一致的排列,
62
- 只改变顺序而不改变成员值。
63
- tree 可以为空。`tree(field(...))` 要求每个已存在节点都拥有自己的 `value`;只有
64
- `tree(optional(field(...)))` 才允许节点缺失 payload,而 `optional(tree(...))` 独立控制整棵
65
- tree 成员是否可以缺失。
66
-
67
- 函数 validator 是同步 predicate/assertion:返回 `true` 或 `undefined` 成功,
68
- 返回 `false` 拒绝,assertion 也可以 throw。Standard Schema v1 成功时必须按 identity
69
- 返回原输入;transform output 会被拒绝。validator 不得修改输入。数据转换在进入 Doxum 前完成。
70
- parse(model, unknown) 复制校验后的结构并共享只读 payload;严格解析要求原子字段具备校验器。品牌键贯穿 map/table 方法、
71
- 符号路径和 impact。路径回调描述地址,包括缺失键,订阅注册时解析。
72
- Core 的 `select(document, selector, equality?)` 追踪实际读取,在选择分支改变时更新
73
- 依赖,并返回标准 `Readable`;React 的 `useDocumentSelector` 只负责把这个 Core
74
- readable 接到 React。
75
-
76
- ## 变化与消费者
77
-
78
- commit 包含 revision/source/changes/impact。ChangeSet 按所属容器分组,成员以
79
- added/removed/updated 携带直接 before/after,不使用 Presence 包装。
80
- 顺序变化放在同一 members 分组的可选 order.before/after 中;纯排序的 members 为空。
81
- 不接受独立 order 记录或同地址重复分组,apply 逐组完成成员与顺序安装。
82
- tree 保留拓扑语义,reset 显式表达整体根过渡;根成员分组仍是增量变化。
83
- 净零事务不发布。
84
- apply(changes, { expectedRevision }) 拒绝缺失或不匹配的本地基线;来包 before
85
- 不作为本地 undo 真值,记录真实旧状态。history 重放完整 ChangeSet,分组原子旅行。
86
- 本地 replace 是可撤销根重置,remote commit 使 history 失效。
87
- observerErrors 属于已提交结果。
88
-
89
- 集合边界使用 `observe(document, path => path.tasks)`,纯派生值使用
90
- `derive({ tasks, filter }, ({ tasks, filter }) => ...)`。定义是惰性的,由一个
91
- `createProjectionRuntime({ onError })` owner 物化和管理。当一个 keyed source 决定
92
- output key/order 时,用 `derive.keyed(entries, entry => entry.label)` 独立映射每个
93
- entry;跨 collection 的动态 key lookup 继续在同一个 derivation 中用命名依赖对象声明:
94
- 普通 Projection 成员是全局依赖,`{ source, key }` 是 singular lookup,`{ source, keys }`
95
- 是有序 plural lookup,selector
96
- 接收 `(entry, key, dependencies)`。Runtime 拥有 binding/reverse index,producer DAG
97
- 仍保持显式。只有 `derive.keyed` 无法表达的 retained state、自定义跨 key index 或
98
- keyed patch 才放到隔离的 `doxum/advanced` incremental 入口。生命周期、draft、
99
- selector 追踪、batch 与故障恢复见 [Projection 参考](projections.zh-CN.md)。
100
- tree 结构复用同一套 projection source 协议:`path.tree.rootId` 是 scalar,
101
- `path.tree.nodes` 是 keyed collection,`path.tree.nodes.item(id)` 是单节点 value。
102
- committed tree node group 直接路由到受影响的 keyed entry,由现有
103
- `CollectionOutput` 发布普通 `CollectionChange`;未变化节点保持稳定的 snapshot
104
- 引用,不再因单节点变化而全部重新 snapshot。
105
- `input` 与 `observe(source)` 接入外部边界值,包括带事件的 value 和 collection
106
- source。随所属服务 dispose Runtime。
107
- `runtime.batch` 推迟投影结算与通知,但不推迟文档提交和文档通知;内部读取上次发布值,
108
- 不提供跨文档回滚。React 只保留一个带可选 selector 的 `useProjection`,输入使用
109
- `useInput`。
110
-
111
- doxum/local-sync 附着 IndexedDB 和 Web Lock 领导权,只有 leader 写入,
112
- follower 连续重放 durable seq。先可见后异步落盘,flush 等待持久化。
113
- 附着期间禁止外部 replace 和外部标记 remote 的 apply。版本 5 / 格式 3 拒绝旧存储,
114
- 保留数据且不迁移。适配器仅接收 JSON 值,变化数量限制统计逻辑成员和树节点,不能按外层分组数绕过。
115
- 限额仅约束新产生的本地提交;减小当前限额不影响既有持久化提交的读取。
116
- 整个已发布 ChangeSet 都必须只读,其身份可复用结构校验结果,但不跳过本地 revision、schema 和真实 before 检查。
@@ -1,53 +0,0 @@
1
- # Runtime 不变量
2
-
3
- 公开调用形态统一放在 [api.zh-CN.md](api.zh-CN.md);本文只保留影响实现和 review 的不变量。
4
-
5
- 1. createDocument 唯一拥有 canonical state;Draft、apply、replace 共用 MutationSession。
6
- runtime 统一执行、回滚、seal、发布边界,history 同样复用。
7
- mutation/operations 按 table/list/order/tree/replay 分类完整操作,session 持有写入内核。
8
- scope 经 session 绑定已解析事实,同 generation 复用 handle;这些均为内部实现边界。
9
- 2. 公共读写同步且有作用域,禁止重入写入。失败恢复之前全部工作;普通异常原样抛出,
10
- TransactionRejected 转换为 application issues。内部 readWith 返回借用 reader,
11
- reader、子 proxy 和 collection method 不得逃逸同步回调。
12
- 3. mutation/changes.ts 统一解析 unknown ChangeSet;schema 为寻址真值。
13
- 拒绝父子重叠事实与同地址重复分组。逐容器安装成员及可选顺序,不接受独立 order 记录。
14
- 4. ChangeRecorder 按所属容器分组记录首次成员旧值,唯一拥有顺序基线和触及树节点。
15
- 有序组一次发布 members 与 order;树容器没有普通成员 layout,拒绝 members replay。
16
- 树命令直接捕获触及节点,不经过 session 回调协议。回滚不调用用户回调或校验器,seal 只发布净变化。
17
- capture、restore 和按域 seal 分离;当前顺序确实变化后才复制最终 order,净变化为零也保留执行期间的回滚基线。
18
- 5. 原子值按 Object.is 比较,canonical 结构依所有权契约保留原子引用;
19
- 快照只复制结构,payload 与 commit、history 共享且只读,不做深复制或发布冻结。
20
- validator 直接读取原始输入且必须纯同步;函数形式是 predicate/assertion,
21
- Standard Schema 成功时不能 transform canonical value。
22
- 6. list 以稳定键标记身份,替换值必须保留键。anchor 拥有排序语义;
23
- tree 拥有双向一致、连通、无环、空树或单根的拓扑约束。
24
- 7. 公开 `Schema` / `ObjectSchema` handle 拥有定义身份,concrete node 留在内部;
25
- schema model、path compiler、compiled layout、value validation/copy 各有唯一内部 owner,
26
- foundation model/layout 不依赖 document/projection Runtime。runtime、history 和
27
- `document.readonly()` alias 共享唯一 RuntimeContext 实例身份。
28
- 共享路径 compiler 与 Core impact target 算法拥有寻址、身份、相等、
29
- 分桶和精确匹配;React 只消费 Readable/select 契约。通知直接匹配分组变化,
30
- 不构建 commit impact 索引。
31
- 8. apply 要求 expectedRevision,记录本地真实旧状态。本地 reset 可撤销,
32
- remote commit 使 history 失效;group 在单个 session 中旅行。
33
- 9. projection 显式声明 producer source,先于监听结算,producer DAG 保持静态。
34
- `derive.keyed` 用一个命名依赖对象声明额外 source;`{ source, key }` 为每个 output key
35
- 绑定一个 source key,`{ source, keys }` 绑定有序且无重复的 source-key 集合。物化后的 Runtime
36
- 拥有这些 binding 和 reverse index,包括当前尚不存在的 source entry。processor
37
- 不通过 imperative Runtime read 创建图依赖。scope 只通过 `scope.own` 增加 lifecycle
38
- ownership;scoped definition 可以依赖 root definition,root 与 sibling scope 不能依赖
39
- scoped definition。通知失败不撤销提交;batch 推迟投影发布,不推迟文档提交与文档监听。
40
- lazy definition metadata 不负责物化 source;input、observe、source materialization 分层。
41
- 一个 output object 同时拥有 staged/published state、revision、listener 和 graph-facing
42
- consumer capability,只有 scheduler 连接/断开 dependency edge。collection input 的 callback
43
- 或 entry equality 抛错时不得安装部分 Runtime-local state。cleanup 先摘除 ownership,再穷尽
44
- 释放;初始化失败释放已创建的 projection resource,同时保留原始异常。document dirty/pending
45
- state 只有一个写 owner,structural materialization 只读该状态。
46
- 10. local-sync 使用 Web Lock 领导权和连续 durable seq,先可见后异步持久化。
47
- 版本 5 / 格式 3 拒绝旧数据库并保留原数据,附着期间禁止外部 replace 和 remote apply。
48
- 11. core 框架无关,公开导出需明确用途;根 dist 为构建产物。
49
- 12. 测试 malformed 输入、部分失败回滚、history、impact、dispose 和大集合工作量。
50
- dependency 去重使用 impact target owner 提供的 canonical bucket,再做精确 equality;
51
- 不得退回全量线性扫描或新增字符串 path grammar。删除旧 API 和平行协议。
52
-
53
- 网络意图、鉴权、协作撤销、先持久化后可见属于独立需求,不是隐藏 runtime 能力。
@@ -1,162 +0,0 @@
1
- # Doxum Patterns
2
-
3
- Exact call shapes and callback fields are listed in the [API reference](api.en.md).
4
-
5
- ## Payload Or Structure
6
-
7
- ```ts
8
- const point = object({ x: field<number>(), y: field<number>() });
9
- const model = object({
10
- position: point,
11
- stroke: field<readonly { x: number; y: number }[]>(),
12
- rows: list(field<{ id: string; label: string }>(), { keyOf: row => row.id }),
13
- });
14
- ```
15
-
16
- Edit position.x, replace stroke whole, and use rows.replace(key, value) for keyed item edits.
17
-
18
- ## Complex Replacement
19
-
20
- ```ts
21
- const model = object({
22
- entries: map(object({ rows: table(object({ title: field<string>() })) })),
23
- });
24
- const document = createDocument({
25
- schema: model,
26
- initial: {
27
- entries: {
28
- a: { rows: { ids: ['x'], byId: { x: { title: 'First' } } } },
29
- },
30
- },
31
- });
32
- document.update(draft => {
33
- const entry = draft.entries.get('a')!;
34
- replace(entry, 'rows', {
35
- ids: ['y'],
36
- byId: { y: { title: 'Replacement' } },
37
- });
38
- });
39
- ```
40
-
41
- Use top-level `replace(parent, key, value)` for object/variant members whose Draft
42
- type contains collection tools. Map membership still uses `put`/`remove`; a
43
- collection's own `replace(next)` handles replacement when the collection itself is
44
- the mutation target.
45
-
46
- ## Domain Keys
47
-
48
- ```ts
49
- type PersonId = string & { readonly __person: unique symbol };
50
- const personId = (value: unknown): PersonId => {
51
- if (typeof value !== 'string' || !value.startsWith('person:')) throw new Error('Person ID');
52
- return value as PersonId;
53
- };
54
- const text = (value: unknown): string => {
55
- if (typeof value !== 'string') throw new Error('String required');
56
- return value;
57
- };
58
- const model = object({ people: map(object({ name: field(text) }), { key: personId }) });
59
- const initial = parse(model, { people: { 'person:1': { name: 'Ada' } } });
60
- ```
61
-
62
- Branded key types flow through map/table methods, symbolic paths and collection impact.
63
-
64
- ## Ordered Edits, History And Replay
65
-
66
- Use table.create/remove/move with anchors such as { at: 'start' } or { before: id }.
67
- Tree insert/move accept { parentId, index }; index is the final position after removal.
68
- Never directly edit topology records.
69
-
70
- A history group begins with document.history.group(); end() groups completed commits,
71
- cancel() restores its start. undo/redo travel complete ChangeSets atomically.
72
- Apply incoming changes with an expectedRevision and adapter-validated transport order.
73
- Remote commits invalidate local history. Local revision is not a distributed clock.
74
-
75
- Incoming member changes share the owning container address:
76
-
77
- ```ts
78
- document.apply(
79
- {
80
- changes: [
81
- {
82
- kind: 'members',
83
- at: ['tasks', 'a'],
84
- members: [
85
- { key: 'complete', kind: 'updated', before: false, after: true },
86
- { key: 'title', kind: 'updated', before: 'First', after: 'Done' },
87
- ],
88
- },
89
- ],
90
- },
91
- { expectedRevision: document.revision() }
92
- );
93
- ```
94
-
95
- Use one group per container. Added members carry only after, removed members only
96
- before; present undefined is still a value. Never expand a group into legacy value
97
- envelopes or treat its container address as whole-container invalidation.
98
-
99
- ## Projection And React
100
-
101
- ```ts
102
- const rows = observe(document, path => path.rows);
103
- const metadata = observe(document, path => path.metadata);
104
- const density = input<'compact' | 'comfortable'>('comfortable');
105
-
106
- const labels = derive.keyed(rows, row => row.label);
107
- const decorated = derive.keyed(
108
- rows,
109
- {
110
- meta: { source: metadata, key: row => row.metadataId },
111
- density,
112
- },
113
- (row, _rowId, { meta, density }) => formatRow(row, meta, density)
114
- );
115
- const count = derive({ labels }, ({ labels }) => labels.size);
116
- const selection = input.collection<RowId, boolean>();
117
- const runtime = createProjectionRuntime({ onError: console.error });
118
- runtime.read(decorated);
119
- runtime.read(count);
120
- runtime.update(density, 'compact');
121
- runtime.update(selection, draft => draft.set(rowId, true));
122
- ```
123
-
124
- Use named-object `derive` for aggregate values. Use `derive.keyed` when one keyed
125
- driver owns output keys/order. Its named dependency form handles singular/plural dynamic keyed lookups;
126
- the Runtime owns the binding/reverse index. Use `input.collection` for Runtime-local
127
- keyed UI/application state rather than canonical document state.
128
-
129
- Provide the Runtime or a scope through `ProjectionProvider`. `useProjection` reads
130
- projections, including `useProjection(projection, selector, equality?)`; `useInput`
131
- handles both scalar and collection inputs. Document selectors use
132
- `useDocumentSelector(document, selector, equality?)` independently of the provider.
133
-
134
- ## Multi-output incremental processor
135
-
136
- Use this only when outputs share retained state or cross-key work that pure `derive`
137
- and `derive.keyed` cannot express:
138
-
139
- ```ts
140
- const render = incremental.group(
141
- { scene },
142
- {
143
- output: define => ({
144
- cards: define.collection<string, Card>(),
145
- count: define.value<number>(),
146
- }),
147
- state: () => ({ runs: 0 }),
148
- process: ({ values, output, state }) => {
149
- state.runs++;
150
- const cards = buildCards(values.scene);
151
- for (const [id, card] of cards) output.cards.set(id, card);
152
- output.cards.order([...cards.keys()]);
153
- output.count.set(cards.size);
154
- },
155
- }
156
- );
157
- ```
158
-
159
- `define.value` / `define.collection` exist only inside the `output` declaration.
160
- The returned nested object mirrors ordinary Projection leaves from one processor.
161
- Dependencies are named; declare `state()` only when retained state is needed. Runtime
162
- recovery recreates declared state after a processor fault.
@@ -1,159 +0,0 @@
1
- # Doxum 常见模式
2
-
3
- 精确调用形态和 callback 字段见 [API 参考](api.zh-CN.md)。
4
-
5
- ## 原子值与结构
6
-
7
- ```ts
8
- const point = object({ x: field<number>(), y: field<number>() });
9
- const model = object({
10
- position: point,
11
- stroke: field<readonly { x: number; y: number }[]>(),
12
- rows: list(field<{ id: string; label: string }>(), { keyOf: row => row.id }),
13
- });
14
- ```
15
-
16
- position.x 细粒度赋值;stroke 整体替换;rows.replace(key, value) 按稳定键替换项。
17
-
18
- ## 含集合的整体替换
19
-
20
- ```ts
21
- const model = object({
22
- entries: map(object({ rows: table(object({ title: field<string>() })) })),
23
- });
24
- const document = createDocument({
25
- schema: model,
26
- initial: {
27
- entries: {
28
- a: { rows: { ids: ['x'], byId: { x: { title: 'First' } } } },
29
- },
30
- },
31
- });
32
- document.update(draft => {
33
- const entry = draft.entries.get('a')!;
34
- replace(entry, 'rows', {
35
- ids: ['y'],
36
- byId: { y: { title: 'Replacement' } },
37
- });
38
- });
39
- ```
40
-
41
- Draft 类型含集合方法的 object/variant 成员使用顶层 `replace(parent, key, value)`;
42
- map membership 仍使用 `put`/`remove`。当集合本身就是 mutation target 时,整体替换
43
- 使用集合自身的 `replace(next)`。
44
-
45
- ## 领域键
46
-
47
- ```ts
48
- type PersonId = string & { readonly __person: unique symbol };
49
- const personId = (value: unknown): PersonId => {
50
- if (typeof value !== 'string' || !value.startsWith('person:')) throw new Error('Person ID');
51
- return value as PersonId;
52
- };
53
- const text = (value: unknown): string => {
54
- if (typeof value !== 'string') throw new Error('String required');
55
- return value;
56
- };
57
- const model = object({ people: map(object({ name: field(text) }), { key: personId }) });
58
- const initial = parse(model, { people: { 'person:1': { name: 'Ada' } } });
59
- ```
60
-
61
- 品牌键类型贯穿 map/table 方法、符号路径和 collection impact。
62
-
63
- ## 顺序、History 与重放
64
-
65
- table.create/remove/move 使用 { at: 'start' } 或 { before: id } 等 anchor。
66
- tree.insert/move 接收 { parentId, index },index 表示移除后的最终位置,
67
- 不能直接修改拓扑记录。
68
-
69
- document.history.group() 开始分组,end() 分组已完成提交,cancel() 恢复起点;
70
- undo/redo 原子重放完整 ChangeSet。外部 apply 必须提供 expectedRevision,
71
- 适配器还须校验传输顺序。remote commit 使本地 history 失效;
72
- 本地 revision 不是分布式时钟。
73
-
74
- 输入的成员变化共享所属容器地址:
75
-
76
- ```ts
77
- document.apply(
78
- {
79
- changes: [
80
- {
81
- kind: 'members',
82
- at: ['tasks', 'a'],
83
- members: [
84
- { key: 'complete', kind: 'updated', before: false, after: true },
85
- { key: 'title', kind: 'updated', before: 'First', after: 'Done' },
86
- ],
87
- },
88
- ],
89
- },
90
- { expectedRevision: document.revision() }
91
- );
92
- ```
93
-
94
- 每个容器只能有一组。added 仅携带 after,removed 仅携带 before;存在的 undefined
95
- 仍是一个值。不要展开成旧 value envelope,也不要将分组地址当作整个容器失效。
96
-
97
- ## 投影与 React
98
-
99
- ```ts
100
- const rows = observe(document, path => path.rows);
101
- const metadata = observe(document, path => path.metadata);
102
- const density = input<'compact' | 'comfortable'>('comfortable');
103
-
104
- const labels = derive.keyed(rows, row => row.label);
105
- const decorated = derive.keyed(
106
- rows,
107
- {
108
- meta: { source: metadata, key: row => row.metadataId },
109
- density,
110
- },
111
- (row, _rowId, { meta, density }) => formatRow(row, meta, density)
112
- );
113
- const count = derive({ labels }, ({ labels }) => labels.size);
114
- const selection = input.collection<RowId, boolean>();
115
- const runtime = createProjectionRuntime({ onError: console.error });
116
- runtime.read(decorated);
117
- runtime.read(count);
118
- runtime.update(density, 'compact');
119
- runtime.update(selection, draft => draft.set(rowId, true));
120
- ```
121
-
122
- 聚合值使用 named-object `derive`。一个 keyed driver 拥有 output key/order 时使用
123
- `derive.keyed`;named dependency form 负责 singular/plural dynamic keyed lookup,binding/reverse
124
- index 由 Runtime 拥有。`input.collection` 用于 Runtime-local 的 keyed UI/application
125
- state,不进入 canonical document state。
126
-
127
- React 用 `ProjectionProvider` 提供 Runtime 或 scope;`useProjection` 支持
128
- `useProjection(projection, selector, equality?)`,`useInput` 同时支持 scalar 与
129
- collection input。Document selector 独立使用
130
- `useDocumentSelector(document, selector, equality?)`。
131
-
132
- ## 多输出 incremental processor
133
-
134
- 只有 pure `derive` / `derive.keyed` 无法表达共享 retained state 或 cross-key work 时使用:
135
-
136
- ```ts
137
- const render = incremental.group(
138
- { scene },
139
- {
140
- output: define => ({
141
- cards: define.collection<string, Card>(),
142
- count: define.value<number>(),
143
- }),
144
- state: () => ({ runs: 0 }),
145
- process: ({ values, output, state }) => {
146
- state.runs++;
147
- const cards = buildCards(values.scene);
148
- for (const [id, card] of cards) output.cards.set(id, card);
149
- output.cards.order([...cards.keys()]);
150
- output.count.set(cards.size);
151
- },
152
- }
153
- );
154
- ```
155
-
156
- `define.value` / `define.collection` 只存在于 `output` 声明 callback 内,返回的嵌套
157
- object 会映射为同一 processor 的普通 Projection leaves。依赖按名称读取;只有需要
158
- retained state 时才声明 `state()`。processor fault 后由 Runtime 重新创建已声明的 state
159
- 并恢复。