doxum 0.1.26 → 0.1.28

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 +86 -3
  2. package/dist/advanced.cjs +40 -30
  3. package/dist/advanced.cjs.map +1 -1
  4. package/dist/advanced.d.cts +34 -2
  5. package/dist/advanced.d.ts +34 -2
  6. package/dist/advanced.js +31 -22
  7. package/dist/advanced.js.map +1 -1
  8. package/dist/{definition-xgk3YNBp.d.ts → dependency-B3E65M1E.d.cts} +27 -3
  9. package/dist/{definition-CDOuvb5C.d.cts → dependency-BYbdDHNa.d.ts} +27 -3
  10. package/dist/driver-B5vdPohL.js.map +1 -1
  11. package/dist/driver-BL91gn7_.cjs.map +1 -1
  12. package/dist/index.cjs +885 -394
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +33 -18
  15. package/dist/index.d.ts +33 -18
  16. package/dist/index.js +784 -293
  17. package/dist/index.js.map +1 -1
  18. package/dist/local-sync.cjs.map +1 -1
  19. package/dist/local-sync.js.map +1 -1
  20. package/dist/transform-B4T3AcJr.js +938 -0
  21. package/dist/transform-B4T3AcJr.js.map +1 -0
  22. package/dist/transform-DUleAsG0.cjs +1105 -0
  23. package/dist/transform-DUleAsG0.cjs.map +1 -0
  24. package/package.json +1 -1
  25. package/skills/doxum-runtime/SKILL.md +16 -6
  26. package/skills/doxum-runtime/references/api.en.md +49 -15
  27. package/skills/doxum-runtime/references/api.zh-CN.md +47 -13
  28. package/skills/doxum-runtime/references/guide.en.md +2 -1
  29. package/skills/doxum-runtime/references/guide.zh-CN.md +2 -1
  30. package/skills/doxum-runtime/references/invariants.en.md +2 -2
  31. package/skills/doxum-runtime/references/invariants.zh-CN.md +2 -2
  32. package/skills/doxum-runtime/references/patterns.en.md +1 -1
  33. package/skills/doxum-runtime/references/patterns.zh-CN.md +1 -1
  34. package/skills/doxum-runtime/references/projections.en.md +77 -4
  35. package/skills/doxum-runtime/references/projections.zh-CN.md +74 -3
  36. package/dist/scheduler--137L3j-.js +0 -500
  37. package/dist/scheduler--137L3j-.js.map +0 -1
  38. package/dist/scheduler-D4VthaaK.cjs +0 -589
  39. package/dist/scheduler-D4VthaaK.cjs.map +0 -1
@@ -70,6 +70,39 @@ Only affected driver entries execute the selector. Per-entry equality suppresses
70
70
  `updated` output transition when the selected value is unchanged. Added, removed and
71
71
  order transitions retain their normal collection meaning.
72
72
 
73
+ Standard keyed structure reads use the same family:
74
+
75
+ ```ts
76
+ const ids = derive.keyed.keys(records);
77
+ const all = derive.keyed.values(records);
78
+ const entries = derive.keyed.entries(records);
79
+ const active = derive.keyed.get(records, activeRecordId);
80
+ ```
81
+
82
+ `keys` changes only for add/remove/order/reset and keeps its published array reference
83
+ on value-only updates. `values` and `entries` follow the keyed collection's formal order;
84
+ `entries` reuses unchanged tuple references. `get` binds exactly to the selected key,
85
+ including a currently missing key so a later add invalidates the scalar result.
86
+
87
+ Membership-changing derives are first-class rather than application-side incremental
88
+ collection patch loops:
89
+
90
+ ```ts
91
+ const visible = derive.keyed.filter(fields, field => field.visible);
92
+ const ordered = derive.keyed.subset(fields, visibleFieldIds);
93
+ const content = derive.keyed.compact(cards, card => card.content);
94
+ const bySection = derive.keyed.groupBy(records, record => record.sectionId);
95
+ const activeCollection = derive.keyed.singleton(activeRecord, record => record.id);
96
+ ```
97
+
98
+ `filter` preserves source values and source-relative order. `subset` uses
99
+ `orderedKeys ∩ source.keys` with orderedKeys order; missing requested keys remain latent,
100
+ and duplicate ordered keys are invalid. `compact` treats selected `undefined` as output
101
+ absence and uses optional per-entry equality for present values. `filter` and `compact`
102
+ accept the same named global/dynamic keyed dependencies as ordinary `derive.keyed`.
103
+ `groupBy` is the one-to-many reverse-index primitive and preserves source order inside
104
+ each bucket; `singleton` converts an optional scalar to a zero-or-one keyed projection.
105
+
73
106
  Dynamic keyed lookups stay in the same API:
74
107
 
75
108
  ```ts
@@ -77,17 +110,22 @@ const cardContent = derive.keyed(
77
110
  items,
78
111
  {
79
112
  record: { source: records, key: item => item.recordId },
113
+ related: { source: records, keys: item => item.relatedRecordIds },
80
114
  view: activeView,
81
115
  fields: visibleFields,
82
116
  },
83
- (item, itemId, { record, view, fields }) => renderCard(itemId, item, record, view, fields)
117
+ (item, itemId, { record, related, view, fields }) =>
118
+ renderCard(itemId, item, record, related, view, fields)
84
119
  );
85
120
  ```
86
121
 
87
122
  A plain Projection dependency invalidates the driver key set when it changes.
88
123
  `{ source, key }` declares an output-key → dynamic source-key dependency. The Runtime
89
124
  owns forward bindings and reverse invalidation. Missing selected entries remain bound,
90
- so adding that source key later invalidates its dependents.
125
+ so adding that source key later invalidates its dependents. `{ source, keys }` binds one
126
+ driver key to an ordered duplicate-free set of source keys and resolves to a readonly map
127
+ of currently present selected entries. Source order-only changes do not invalidate keyed
128
+ lookup dependencies.
91
129
 
92
130
  This is the keyed join protocol. Do not add a second join abstraction, wildcard
93
131
  document-path grammar, or processor-side imperative dependency discovery.
@@ -98,6 +136,8 @@ document-path grammar, or processor-side imperative dependency discovery.
98
136
  const value = runtime.read(visible);
99
137
  const selected = runtime.select(visible, rows => rows.get(rowId), equality);
100
138
  const stop = selected.subscribe(listener);
139
+ const itemFamily = runtime.items(records);
140
+ const record = itemFamily.get(rowId);
101
141
 
102
142
  runtime.update(filter, 'open');
103
143
  runtime.update(selection, draft => {
@@ -111,6 +151,12 @@ runtime.batch(run, { cause });
111
151
  `select` returns the standard `Readable`. Keyed selectors track relevant key/structure
112
152
  reads; equality filters the result only after a related invalidation.
113
153
 
154
+ `runtime.items(keyedProjection)` returns one Runtime-owned keyed Readable family with
155
+ ordered `keys` and `get(key)`. An item's Readable identity is stable for one published
156
+ membership generation. Remove retires that generation; a later re-add creates a new
157
+ identity. Subscribed missing items remain latent and can activate on a future add. The
158
+ same API exists on `ProjectionScope` and follows that scope's disposal lifetime.
159
+
114
160
  `CollectionInputDraft` exposes `get`, `has`, `set`, `remove` and expires with the
115
161
  synchronous edit callback. A throwing callback applies none of that edit. Collection
116
162
  input equality is per entry and defaults to `Object.is`; it runs before accepted local
@@ -155,14 +201,18 @@ type CollectionChange<K extends string, V> =
155
201
  Import `CollectionChange` from `doxum/advanced` when an advanced processor signature
156
202
  needs it. Root projection consumers do not import this transport type.
157
203
 
204
+ `collectionChange.keys(change)` from `doxum/advanced` accepts an incremental change and
205
+ lazily yields added, updated and removed keys in that order. It does not interpret reset
206
+ or turn order changes into entry changes; callers decide those semantics explicitly.
207
+
158
208
  Initial materialization and source reset report `reset` to advanced processors.
159
209
  Incremental transitions are exact net changes across the settled Runtime batch.
160
210
 
161
211
  ## Advanced processors
162
212
 
163
213
  Import `incremental` from `doxum/advanced` only when retained state, cross-key indexes,
164
- or direct incremental output patching cannot be expressed cleanly with `derive` or
165
- `derive.keyed`.
214
+ or direct incremental output patching cannot be expressed cleanly with `derive` or the
215
+ `derive.keyed` family.
166
216
 
167
217
  All advanced processors use named dependencies plus a required `process`. Add `state()`
168
218
  only when the processor needs retained state:
@@ -195,6 +245,29 @@ const weights = incremental.collection(
195
245
  collection change metadata. Collection processors additionally expose keyed
196
246
  `previous`, `next`, and a borrowed `output` draft.
197
247
 
248
+ For independent retained state per driver key, use `incremental.keyed`:
249
+
250
+ ```ts
251
+ const totals = incremental.keyed(
252
+ sections,
253
+ { records: { source: records, keys: section => section.recordIds } },
254
+ {
255
+ state: () => ({ runs: 0 }),
256
+ process: ({ dependencies, state }) => {
257
+ state.runs++;
258
+ return [...dependencies.records.values()].reduce((sum, record) => sum + record.score, 0);
259
+ },
260
+ }
261
+ );
262
+ ```
263
+
264
+ The driver owns output membership/order. Ordinary dependencies invalidate every current
265
+ driver key; singular/plural keyed dependencies reverse-route only affected keys. Order-only
266
+ driver changes do not execute `process`. Remove releases that key's state, re-add creates
267
+ a new membership lifecycle, reset intersections retain state, and processor recovery
268
+ recreates all per-key state. The process returns only its own output value and does not
269
+ receive a collection draft.
270
+
198
271
  Multi-output processors use one closed group definition:
199
272
 
200
273
  ```ts
@@ -68,6 +68,39 @@ const fieldValues = derive.keyed(records, record => record.values[fieldId]);
68
68
  只有受影响 driver entry 会执行 selector。per-entry equality 判定 selected value 相等时,
69
69
  该 key 不发布 `updated`;added、removed、order 语义保持不变。
70
70
 
71
+ 标准 keyed structure read 仍属于同一个 family:
72
+
73
+ ```ts
74
+ const ids = derive.keyed.keys(records);
75
+ const all = derive.keyed.values(records);
76
+ const entries = derive.keyed.entries(records);
77
+ const active = derive.keyed.get(records, activeRecordId);
78
+ ```
79
+
80
+ `keys` 只在 add/remove/order/reset 时变化,value-only update 保持已发布 array 的引用与
81
+ revision;`values` / `entries` 始终遵循 keyed collection 的正式顺序,`entries` 会复用
82
+ 未变化 entry 的 tuple 引用。`get` 只绑定当前 scalar-selected key;即使 key 当前缺失,
83
+ binding 仍保留,因此以后 add 会正确触发 scalar result。
84
+
85
+ membership-changing derive 也作为正式 primitive,而不是让应用手写 incremental
86
+ collection patch:
87
+
88
+ ```ts
89
+ const visible = derive.keyed.filter(fields, field => field.visible);
90
+ const ordered = derive.keyed.subset(fields, visibleFieldIds);
91
+ const content = derive.keyed.compact(cards, card => card.content);
92
+ const bySection = derive.keyed.groupBy(records, record => record.sectionId);
93
+ const activeCollection = derive.keyed.singleton(activeRecord, record => record.id);
94
+ ```
95
+
96
+ `filter` 保留 source value 与 source-relative order。`subset` 使用
97
+ `orderedKeys ∩ source.keys`,顺序由 orderedKeys 决定;当前缺失的 requested key 保持
98
+ latent,后续 source add 时自动出现,duplicate ordered keys 非法。`compact` 将 selected
99
+ `undefined` 解释为 output absence,对 present value 使用可选 per-entry equality。
100
+ `filter` / `compact` 与普通 `derive.keyed` 共用 named global/dynamic keyed dependencies。
101
+ `groupBy` 是通用一对多 reverse-index primitive,bucket member 遵循 source 正式顺序;
102
+ `singleton` 将 optional scalar 转成 0/1 keyed projection。
103
+
71
104
  Dynamic keyed lookup 仍使用同一个 API:
72
105
 
73
106
  ```ts
@@ -75,17 +108,21 @@ const cardContent = derive.keyed(
75
108
  items,
76
109
  {
77
110
  record: { source: records, key: item => item.recordId },
111
+ related: { source: records, keys: item => item.relatedRecordIds },
78
112
  view: activeView,
79
113
  fields: visibleFields,
80
114
  },
81
- (item, itemId, { record, view, fields }) => renderCard(itemId, item, record, view, fields)
115
+ (item, itemId, { record, related, view, fields }) =>
116
+ renderCard(itemId, item, record, related, view, fields)
82
117
  );
83
118
  ```
84
119
 
85
120
  普通 Projection dependency 变化时使 driver key set 失效。`{ source, key }` 声明
86
121
  output-key → dynamic source-key dependency;Runtime 拥有 forward binding 和 reverse
87
122
  invalidation。selected source entry 缺失时仍保留 binding,因此以后新增该 source key
88
- 会正确触发 dependent output key。
123
+ 会正确触发 dependent output key。`{ source, keys }` 将一个 driver key 绑定到一个有序、
124
+ 无重复的 source-key 集合,dependency value 是按声明顺序包含当前存在 entry 的 readonly
125
+ map;source 仅 order 变化不会使 keyed lookup 失效。
89
126
 
90
127
  这就是 keyed join 协议。不要再添加第二套 join abstraction、wildcard document path
91
128
  grammar 或 processor 内 imperative dependency discovery。
@@ -96,6 +133,8 @@ grammar 或 processor 内 imperative dependency discovery。
96
133
  const value = runtime.read(visible);
97
134
  const selected = runtime.select(visible, rows => rows.get(rowId), equality);
98
135
  const stop = selected.subscribe(listener);
136
+ const itemFamily = runtime.items(records);
137
+ const record = itemFamily.get(rowId);
99
138
 
100
139
  runtime.update(filter, 'open');
101
140
  runtime.update(selection, draft => {
@@ -109,6 +148,12 @@ runtime.batch(run, { cause });
109
148
  `select` 返回标准 `Readable`。keyed selector 追踪相关 key/structure 读取;只有发生
110
149
  相关 invalidation 后才执行 selector,随后 equality 决定是否发布结果。
111
150
 
151
+ `runtime.items(keyedProjection)` 返回一个 Runtime-owned keyed Readable family:`keys`
152
+ 表示正式有序 membership,`get(key)` 在一次 published membership generation 内保持
153
+ Readable identity。remove 会结束这一 generation;同 key 以后 re-add 会得到新 identity。
154
+ 已订阅的 missing item 可以保持 latent,并在未来 add 时激活。`ProjectionScope.items`
155
+ 使用相同语义,并随 scope disposal 一起结束。
156
+
112
157
  `CollectionInputDraft` 提供 `get`、`has`、`set`、`remove`,同步 edit callback 返回后
113
158
  失效。callback throw 时该次 edit 不应用。collection input equality 按 entry 比较,
114
159
  默认 `Object.is`,并在接受的本地状态安装前完成;equality 抛错时,published value 与
@@ -152,13 +197,17 @@ type CollectionChange<K extends string, V> =
152
197
  advanced processor 需要显式标注该 transport type 时,从 `doxum/advanced` 导入
153
198
  `CollectionChange`;root projection consumer 不需要从 package root 导入它。
154
199
 
200
+ `doxum/advanced` 的 `collectionChange.keys(change)` 接受 incremental change,按
201
+ added → updated → removed 惰性遍历 entry-transition keys。它不解释 reset,也不会把
202
+ order change 转成 entry change;这些语义由调用方显式决定。
203
+
155
204
  initial materialization 和 source reset 对 advanced processor 报告 `reset`。
156
205
  incremental transition 是 settle 后 batch 边界上的 exact net change。
157
206
 
158
207
  ## Advanced processor
159
208
 
160
209
  只有 retained state、cross-key index 或直接 incremental output patch 无法用
161
- `derive` / `derive.keyed` 清晰表达时,才从 `doxum/advanced` 引入 `incremental`。
210
+ `derive` / `derive.keyed` family 清晰表达时,才从 `doxum/advanced` 引入 `incremental`。
162
211
 
163
212
  所有 advanced processor 都使用 named dependencies,并且 `process` 必需。只有确实需要
164
213
  retained state 时才声明 `state()`:
@@ -191,6 +240,28 @@ const weights = incremental.collection(
191
240
  metadata。collection processor 额外提供 keyed `previous`、`next` 和 borrowed
192
241
  `output` draft。
193
242
 
243
+ 每个 driver key 需要独立 retained state 时使用 `incremental.keyed`:
244
+
245
+ ```ts
246
+ const totals = incremental.keyed(
247
+ sections,
248
+ { records: { source: records, keys: section => section.recordIds } },
249
+ {
250
+ state: () => ({ runs: 0 }),
251
+ process: ({ dependencies, state }) => {
252
+ state.runs++;
253
+ return [...dependencies.records.values()].reduce((sum, record) => sum + record.score, 0);
254
+ },
255
+ }
256
+ );
257
+ ```
258
+
259
+ driver 独占 output membership/order。普通 dependency 变化会 invalidate 当前所有 driver
260
+ key;singular/plural keyed dependency 通过 reverse routing 只运行真正受影响的 key。
261
+ driver 仅 order 变化不会执行 `process`。remove 释放该 key 的 state,re-add 建立新的
262
+ membership lifecycle;reset intersection 保留 state,processor fault recovery 重建全部
263
+ per-key state。process 只返回自身 output value,不暴露 collection draft。
264
+
194
265
  多输出 processor 使用一个 closed group definition:
195
266
 
196
267
  ```ts