doxum 0.1.23 → 0.1.26

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 (66) hide show
  1. package/README.md +208 -102
  2. package/dist/advanced.cjs +237 -2
  3. package/dist/advanced.cjs.map +1 -0
  4. package/dist/advanced.d.cts +90 -2
  5. package/dist/advanced.d.ts +90 -2
  6. package/dist/advanced.js +236 -1
  7. package/dist/advanced.js.map +1 -0
  8. package/dist/{contract-ULPQmiEs.d.ts → contract-CNR6BS75.d.cts} +216 -30
  9. package/dist/{contract-SYJFP0lM.d.cts → contract-COgxO5sv.d.ts} +216 -30
  10. package/dist/definition-CDOuvb5C.d.cts +111 -0
  11. package/dist/definition-xgk3YNBp.d.ts +111 -0
  12. package/dist/{driver-yFfvDMX5.js → driver-B5vdPohL.js} +59 -3
  13. package/dist/driver-B5vdPohL.js.map +1 -0
  14. package/dist/{driver-BD3DQOW6.cjs → driver-BL91gn7_.cjs} +107 -9
  15. package/dist/driver-BL91gn7_.cjs.map +1 -0
  16. package/dist/index.cjs +1637 -703
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +65 -33
  19. package/dist/index.d.ts +65 -33
  20. package/dist/index.js +1432 -497
  21. package/dist/index.js.map +1 -1
  22. package/dist/local-sync.cjs +69 -103
  23. package/dist/local-sync.cjs.map +1 -1
  24. package/dist/local-sync.d.cts +14 -26
  25. package/dist/local-sync.d.ts +14 -26
  26. package/dist/local-sync.js +68 -96
  27. package/dist/local-sync.js.map +1 -1
  28. package/dist/react.cjs +4 -5
  29. package/dist/react.cjs.map +1 -1
  30. package/dist/react.d.cts +6 -8
  31. package/dist/react.d.ts +6 -8
  32. package/dist/react.js +4 -5
  33. package/dist/react.js.map +1 -1
  34. package/dist/record-B2yRhYsc.js +28 -0
  35. package/dist/record-B2yRhYsc.js.map +1 -0
  36. package/dist/record-ChYUi8PP.cjs +45 -0
  37. package/dist/record-ChYUi8PP.cjs.map +1 -0
  38. package/dist/scheduler--137L3j-.js +500 -0
  39. package/dist/scheduler--137L3j-.js.map +1 -0
  40. package/dist/scheduler-D4VthaaK.cjs +589 -0
  41. package/dist/scheduler-D4VthaaK.cjs.map +1 -0
  42. package/package.json +6 -3
  43. package/skills/doxum-runtime/SKILL.md +32 -7
  44. package/skills/doxum-runtime/agents/openai.yaml +1 -1
  45. package/skills/doxum-runtime/references/api.en.md +249 -0
  46. package/skills/doxum-runtime/references/api.zh-CN.md +242 -0
  47. package/skills/doxum-runtime/references/guide.en.md +18 -8
  48. package/skills/doxum-runtime/references/guide.zh-CN.md +14 -6
  49. package/skills/doxum-runtime/references/invariants.en.md +29 -5
  50. package/skills/doxum-runtime/references/invariants.zh-CN.md +24 -6
  51. package/skills/doxum-runtime/references/patterns.en.md +78 -18
  52. package/skills/doxum-runtime/references/patterns.zh-CN.md +76 -18
  53. package/skills/doxum-runtime/references/projections.en.md +209 -119
  54. package/skills/doxum-runtime/references/projections.zh-CN.md +200 -101
  55. package/dist/advanced-CLJCWkHa.d.cts +0 -176
  56. package/dist/advanced-CRNHfJBu.js +0 -1224
  57. package/dist/advanced-CRNHfJBu.js.map +0 -1
  58. package/dist/advanced-Cle5g3B4.cjs +0 -1517
  59. package/dist/advanced-Cle5g3B4.cjs.map +0 -1
  60. package/dist/advanced-d6J61er9.d.ts +0 -176
  61. package/dist/context-BcLKGfq8.cjs +0 -149
  62. package/dist/context-BcLKGfq8.cjs.map +0 -1
  63. package/dist/context-DFEaDUv5.js +0 -84
  64. package/dist/context-DFEaDUv5.js.map +0 -1
  65. package/dist/driver-BD3DQOW6.cjs.map +0 -1
  66. package/dist/driver-yFfvDMX5.js.map +0 -1
@@ -1,5 +1,8 @@
1
1
  # Doxum 使用指南
2
2
 
3
+ `doxum`、`doxum/advanced`、`doxum/react`、`doxum/local-sync` 的精确公开签名见
4
+ [API 参考](api.zh-CN.md)。本指南只保留选择原则与生命周期语义。
5
+
3
6
  ## 定义、修改与读取
4
7
 
5
8
  ```ts
@@ -16,7 +19,6 @@ document.update(draft => {
16
19
  const task = draft.tasks.get('a');
17
20
  if (task) task.done = !task.done;
18
21
  draft.tasks.put('b', { title: 'Review', done: false });
19
- return { warnings: [] };
20
22
  });
21
23
  const done = read(document, state => state.tasks.get('a')?.done);
22
24
  const tasks = read(document, state => snapshot(state.tasks));
@@ -62,8 +64,9 @@ tree 可以为空。`tree(field(...))` 要求每个已存在节点都拥有自
62
64
  `tree(optional(field(...)))` 才允许节点缺失 payload,而 `optional(tree(...))` 独立控制整棵
63
65
  tree 成员是否可以缺失。
64
66
 
65
- 校验器是纯同步函数或 Standard Schema v1,直接接收原始引用且不得修改它。
66
- 成功返回值被忽略,不复制输入,也不深度检查转换;数据转换在进入 Doxum 前完成。
67
+ 函数 validator 是同步 predicate/assertion:返回 `true` 或 `undefined` 成功,
68
+ 返回 `false` 拒绝,assertion 也可以 throw。Standard Schema v1 成功时必须按 identity
69
+ 返回原输入;transform output 会被拒绝。validator 不得修改输入。数据转换在进入 Doxum 前完成。
67
70
  parse(model, unknown) 复制校验后的结构并共享只读 payload;严格解析要求原子字段具备校验器。品牌键贯穿 map/table 方法、
68
71
  符号路径和 impact。路径回调描述地址,包括缺失键,订阅注册时解析。
69
72
  Core 的 `select(document, selector, equality?)` 追踪实际读取,在选择分支改变时更新
@@ -84,9 +87,14 @@ apply(changes, { expectedRevision }) 拒绝缺失或不匹配的本地基线;
84
87
  observerErrors 属于已提交结果。
85
88
 
86
89
  集合边界使用 `observe(document, path => path.tasks)`,纯派生值使用
87
- `derive([tasks, filter], (tasks, filter) => ...)`。定义是惰性的,由一个
88
- `createProjectionRuntime({ onError })` owner 物化和管理。保留状态、反向索引和
89
- keyed patch 放在隔离的 `doxum/advanced` incremental 入口。生命周期、draft、
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 }` 成员是逐 output key lookup,selector
95
+ 接收 `(entry, key, dependencies)`。Runtime 拥有 binding/reverse index,producer DAG
96
+ 仍保持显式。只有 `derive.keyed` 无法表达的 retained state、自定义跨 key index 或
97
+ keyed patch 才放到隔离的 `doxum/advanced` incremental 入口。生命周期、draft、
90
98
  selector 追踪、batch 与故障恢复见 [Projection 参考](projections.zh-CN.md)。
91
99
  tree 结构复用同一套 projection source 协议:`path.tree.rootId` 是 scalar,
92
100
  `path.tree.nodes` 是 keyed collection,`path.tree.nodes.item(id)` 是单节点 value。
@@ -1,5 +1,8 @@
1
1
  # Runtime Invariants
2
2
 
3
+ Public call shapes belong in [api.en.md](api.en.md); keep this file limited to
4
+ implementation invariants that affect design and review decisions.
5
+
3
6
  1. createDocument owns canonical state; Draft, apply and replace share MutationSession.
4
7
  runtime owns one execution/rollback/seal/publish boundary, also used by history.
5
8
  mutation/operations groups table/list/order/tree/replay commands by domain; session
@@ -21,23 +24,44 @@
21
24
  5. Atomic equality is Object.is; canonical structure retains atomic references under
22
25
  ownership contract. Snapshots copy structure and share readonly payloads, as do
23
26
  commits and history. No payload cloning or publication freezing. Validators receive
24
- original inputs and must be pure; successful output is ignored.
27
+ original inputs and must be pure; function validators are predicates/assertions and
28
+ Standard Schema success may not transform the canonical value.
25
29
  6. List identity is a stable key; replacement retains it. Anchor owns ordering.
26
30
  Tree owns reciprocal, connected, acyclic, empty-or-single-root topology.
27
- 7. ObjectNode owns schema identity; one RuntimeContext owns instance identity for the
28
- runtime, history and read-only aliases. The shared path compiler and Core impact target
31
+ 7. Public `Schema` / `ObjectSchema` handles own schema identity while concrete nodes stay
32
+ internal. Schema model, path compilation, compiled layout and value validation/copy
33
+ have separate internal owners; foundation model/layout modules do not depend on
34
+ document/projection Runtime code. One RuntimeContext owns instance identity for the runtime, history and
35
+ `document.readonly()` aliases. The shared path compiler and Core impact target
29
36
  algorithms own addressing/identity/equality/bucketing and exact matching. React sees
30
37
  only Readable/select contracts. Notification matches grouped changes directly without
31
38
  building commit impact indexes.
32
39
  8. Apply requires expectedRevision and captures actual local old state. Local reset is
33
40
  reversible; remote commits invalidate history. Groups travel in one session.
34
- 9. Projections declare sources and settle before listeners. Notification failures leave
35
- commits accepted. Batch defers projection publication, not document commits/listeners.
41
+ 9. Projections declare producer sources and settle before listeners. The producer DAG
42
+ remains static. `derive.keyed` declares extra sources in one named dependency object;
43
+ `{ source, key }` members may bind each output key to a key in an explicitly declared
44
+ keyed source. The materialized Runtime owns those bindings and reverse indexes,
45
+ including bindings to currently missing source entries. Processors do not create graph
46
+ dependencies through imperative Runtime reads. A scope adds lifecycle ownership only
47
+ through `scope.own`; scoped definitions may depend on root definitions, while root and
48
+ sibling scopes cannot depend on scoped definitions. Notification failures leave commits
49
+ accepted. Batch defers projection publication, not document commits/listeners.
50
+ Lazy definition metadata does not materialize sources. Input, observe and source
51
+ materialization remain separate boundaries. One output object owns staged/published
52
+ state, revision, listeners and graph-facing consumer capability; scheduler alone
53
+ attaches/detaches dependency edges. Collection-input callback/equality failure installs
54
+ no partial Runtime-local state. Cleanup is exhaustive after ownership is detached;
55
+ initialization failure releases already-created projection resources and preserves the
56
+ original error. Document dirty/pending state has one write owner; structural
57
+ materialization only reads it.
36
58
  10. Local sync uses Web Lock leadership and contiguous durable sequence. Writes precede
37
59
  asynchronous persistence. Version 5 / format 3 rejects old databases without editing
38
60
  them. External replace and remote apply are prohibited while attached.
39
61
  11. Core is framework-neutral. Public exports are deliberate; root dist is generated.
40
62
  12. Test malformed input, partial rollback, history, impact, disposal and bounded work.
63
+ Dependency de-duplication uses target-owned canonical bucketing plus exact equality;
64
+ do not fall back to a linear all-target scan or an ad-hoc string path grammar.
41
65
  Delete obsolete APIs and parallel protocols.
42
66
 
43
67
  Network intent, authorization, collaborative undo and persist-before-visible acceptance
@@ -1,5 +1,7 @@
1
1
  # Runtime 不变量
2
2
 
3
+ 公开调用形态统一放在 [api.zh-CN.md](api.zh-CN.md);本文只保留影响实现和 review 的不变量。
4
+
3
5
  1. createDocument 唯一拥有 canonical state;Draft、apply、replace 共用 MutationSession。
4
6
  runtime 统一执行、回滚、seal、发布边界,history 同样复用。
5
7
  mutation/operations 按 table/list/order/tree/replay 分类完整操作,session 持有写入内核。
@@ -15,21 +17,37 @@
15
17
  capture、restore 和按域 seal 分离;当前顺序确实变化后才复制最终 order,净变化为零也保留执行期间的回滚基线。
16
18
  5. 原子值按 Object.is 比较,canonical 结构依所有权契约保留原子引用;
17
19
  快照只复制结构,payload 与 commit、history 共享且只读,不做深复制或发布冻结。
18
- 校验器直接读取原始输入,必须纯同步,成功返回值被忽略。
20
+ validator 直接读取原始输入且必须纯同步;函数形式是 predicate/assertion,
21
+ Standard Schema 成功时不能 transform canonical value。
19
22
  6. list 以稳定键标记身份,替换值必须保留键。anchor 拥有排序语义;
20
23
  tree 拥有双向一致、连通、无环、空树或单根的拓扑约束。
21
- 7. ObjectNode 拥有定义身份;runtime、history 和只读 alias 共享唯一 RuntimeContext
22
- 实例身份。共享路径 compiler 与 Core impact target 算法拥有寻址、身份、相等、
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 算法拥有寻址、身份、相等、
23
29
  分桶和精确匹配;React 只消费 Readable/select 契约。通知直接匹配分组变化,
24
30
  不构建 commit impact 索引。
25
31
  8. apply 要求 expectedRevision,记录本地真实旧状态。本地 reset 可撤销,
26
32
  remote commit 使 history 失效;group 在单个 session 中旅行。
27
- 9. projection 显式声明 source,先于监听结算。通知失败不撤销提交;
28
- batch 推迟投影发布,不推迟文档提交与文档监听。
33
+ 9. projection 显式声明 producer source,先于监听结算,producer DAG 保持静态。
34
+ `derive.keyed` 用一个命名依赖对象声明额外 source;其中 `{ source, key }` 成员可以
35
+ 把每个 output key 绑定到已显式声明 keyed 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 只读该状态。
29
46
  10. local-sync 使用 Web Lock 领导权和连续 durable seq,先可见后异步持久化。
30
47
  版本 5 / 格式 3 拒绝旧数据库并保留原数据,附着期间禁止外部 replace 和 remote apply。
31
48
  11. core 框架无关,公开导出需明确用途;根 dist 为构建产物。
32
49
  12. 测试 malformed 输入、部分失败回滚、history、impact、dispose 和大集合工作量。
33
- 删除旧 API 和平行协议。
50
+ dependency 去重使用 impact target owner 提供的 canonical bucket,再做精确 equality;
51
+ 不得退回全量线性扫描或新增字符串 path grammar。删除旧 API 和平行协议。
34
52
 
35
53
  网络意图、鉴权、协作撤销、先持久化后可见属于独立需求,不是隐藏 runtime 能力。
@@ -1,5 +1,7 @@
1
1
  # Doxum Patterns
2
2
 
3
+ Exact call shapes and callback fields are listed in the [API reference](api.en.md).
4
+
3
5
  ## Payload Or Structure
4
6
 
5
7
  ```ts
@@ -19,15 +21,27 @@ Edit position.x, replace stroke whole, and use rows.replace(key, value) for keye
19
21
  const model = object({
20
22
  entries: map(object({ rows: table(object({ title: field<string>() })) })),
21
23
  });
22
- const document = createDocument({ schema: model, initial: { entries: {} } });
24
+ const document = createDocument({
25
+ schema: model,
26
+ initial: {
27
+ entries: {
28
+ a: { rows: { ids: ['x'], byId: { x: { title: 'First' } } } },
29
+ },
30
+ },
31
+ });
23
32
  document.update(draft => {
24
- draft.entries.put('a', { rows: { ids: ['x'], byId: { x: { title: 'First' } } } });
25
- draft.entries.get('a')!.rows.get('x')!.title = 'Updated';
33
+ const entry = draft.entries.get('a')!;
34
+ replace(entry, 'rows', {
35
+ ids: ['y'],
36
+ byId: { y: { title: 'Replacement' } },
37
+ });
26
38
  });
27
39
  ```
28
40
 
29
- Map entries use put. Use top-level replace for object/variant members whose Draft
30
- type contains collection tools; collection replace(next) handles whole containers.
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.
31
45
 
32
46
  ## Domain Keys
33
47
 
@@ -45,7 +59,7 @@ const model = object({ people: map(object({ name: field(text) }), { key: personI
45
59
  const initial = parse(model, { people: { 'person:1': { name: 'Ada' } } });
46
60
  ```
47
61
 
48
- Brands flow through map/table methods, symbolic paths and collection impact.
62
+ Branded key types flow through map/table methods, symbolic paths and collection impact.
49
63
 
50
64
  ## Ordered Edits, History And Replay
51
65
 
@@ -85,18 +99,64 @@ envelopes or treat its container address as whole-container invalidation.
85
99
  ## Projection And React
86
100
 
87
101
  ```ts
88
- const tasks = observe(document, path => path.tasks);
89
- const titles = derive([tasks], tasks => new Map([...tasks].map(([id, task]) => [id, task.title])));
90
- const total = derive([titles], titles => titles.size);
91
- const zoom = input(1);
92
- const scaled = derive([total, zoom], (total, zoom) => total * zoom);
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>();
93
117
  const runtime = createProjectionRuntime({ onError: console.error });
94
- runtime.get(scaled);
95
- const title = runtime.get(tasks).get(taskId);
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 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
+ );
96
157
  ```
97
158
 
98
- Provide the Runtime through `ProjectionProvider`, then use `useProjection` for
99
- projection definitions and `useProjection(projection, selector)` for keyed reads.
100
- `useInput` returns a value and setter. Advanced collection processors in `doxum/advanced` stage
101
- `output.set/remove/order` and use scoped previous/next reads. Processor
102
- dependencies remain explicit even though React selectors track actual reads.
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,5 +1,7 @@
1
1
  # Doxum 常见模式
2
2
 
3
+ 精确调用形态和 callback 字段见 [API 参考](api.zh-CN.md)。
4
+
3
5
  ## 原子值与结构
4
6
 
5
7
  ```ts
@@ -19,15 +21,26 @@ position.x 细粒度赋值;stroke 整体替换;rows.replace(key, value) 按
19
21
  const model = object({
20
22
  entries: map(object({ rows: table(object({ title: field<string>() })) })),
21
23
  });
22
- const document = createDocument({ schema: model, initial: { entries: {} } });
24
+ const document = createDocument({
25
+ schema: model,
26
+ initial: {
27
+ entries: {
28
+ a: { rows: { ids: ['x'], byId: { x: { title: 'First' } } } },
29
+ },
30
+ },
31
+ });
23
32
  document.update(draft => {
24
- draft.entries.put('a', { rows: { ids: ['x'], byId: { x: { title: 'First' } } } });
25
- draft.entries.get('a')!.rows.get('x')!.title = 'Updated';
33
+ const entry = draft.entries.get('a')!;
34
+ replace(entry, 'rows', {
35
+ ids: ['y'],
36
+ byId: { y: { title: 'Replacement' } },
37
+ });
26
38
  });
27
39
  ```
28
40
 
29
- map 条目使用 put。Draft 类型含集合方法的 object/variant 成员使用顶层 replace;
30
- 集合整体替换使用集合自身的 replace(next),并经同一 mutation session 修改。
41
+ Draft 类型含集合方法的 object/variant 成员使用顶层 `replace(parent, key, value)`;
42
+ map membership 仍使用 `put`/`remove`。当集合本身就是 mutation target 时,整体替换
43
+ 使用集合自身的 `replace(next)`。
31
44
 
32
45
  ## 领域键
33
46
 
@@ -45,7 +58,7 @@ const model = object({ people: map(object({ name: field(text) }), { key: personI
45
58
  const initial = parse(model, { people: { 'person:1': { name: 'Ada' } } });
46
59
  ```
47
60
 
48
- 品牌类型贯穿 map/table 方法、符号路径和 collection impact。
61
+ 品牌键类型贯穿 map/table 方法、符号路径和 collection impact。
49
62
 
50
63
  ## 顺序、History 与重放
51
64
 
@@ -84,18 +97,63 @@ document.apply(
84
97
  ## 投影与 React
85
98
 
86
99
  ```ts
87
- const tasks = observe(document, path => path.tasks);
88
- const titles = derive([tasks], tasks => new Map([...tasks].map(([id, task]) => [id, task.title])));
89
- const total = derive([titles], titles => titles.size);
90
- const zoom = input(1);
91
- const scaled = derive([total, zoom], (total, zoom) => total * zoom);
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>();
92
115
  const runtime = createProjectionRuntime({ onError: console.error });
93
- runtime.get(scaled);
94
- const title = runtime.get(tasks).get(taskId);
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 负责 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
+ );
95
154
  ```
96
155
 
97
- React 使用 `ProjectionProvider` 提供 Runtime,再用 `useProjection` 读取 Projection,单键读取写成
98
- `useProjection(projection, selector)`;`useInput` 返回值和 setter。高级集合 processor
99
- 从 `doxum/advanced` 引入,在同步 callback 中使用
100
- `output.set/remove/order` 与 previous/next。Processor 依赖仍显式声明,
101
- React selector 追踪只属于消费端。
156
+ `define.value` / `define.collection` 只存在于 `output` 声明 callback 内,返回的嵌套
157
+ object 会映射为同一 processor 的普通 Projection leaves。依赖按名称读取;只有需要
158
+ retained state 时才声明 `state()`。processor fault 后由 Runtime 重新创建已声明的 state
159
+ 并恢复。