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.
- package/README.md +86 -3
- package/dist/advanced.cjs +40 -30
- package/dist/advanced.cjs.map +1 -1
- package/dist/advanced.d.cts +34 -2
- package/dist/advanced.d.ts +34 -2
- package/dist/advanced.js +31 -22
- package/dist/advanced.js.map +1 -1
- package/dist/{definition-xgk3YNBp.d.ts → dependency-B3E65M1E.d.cts} +27 -3
- package/dist/{definition-CDOuvb5C.d.cts → dependency-BYbdDHNa.d.ts} +27 -3
- package/dist/driver-B5vdPohL.js.map +1 -1
- package/dist/driver-BL91gn7_.cjs.map +1 -1
- package/dist/index.cjs +885 -394
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -18
- package/dist/index.d.ts +33 -18
- package/dist/index.js +784 -293
- package/dist/index.js.map +1 -1
- package/dist/local-sync.cjs.map +1 -1
- package/dist/local-sync.js.map +1 -1
- package/dist/transform-B4T3AcJr.js +938 -0
- package/dist/transform-B4T3AcJr.js.map +1 -0
- package/dist/transform-DUleAsG0.cjs +1105 -0
- package/dist/transform-DUleAsG0.cjs.map +1 -0
- package/package.json +1 -1
- package/skills/doxum-runtime/SKILL.md +16 -6
- package/skills/doxum-runtime/references/api.en.md +49 -15
- package/skills/doxum-runtime/references/api.zh-CN.md +47 -13
- package/skills/doxum-runtime/references/guide.en.md +2 -1
- package/skills/doxum-runtime/references/guide.zh-CN.md +2 -1
- package/skills/doxum-runtime/references/invariants.en.md +2 -2
- package/skills/doxum-runtime/references/invariants.zh-CN.md +2 -2
- package/skills/doxum-runtime/references/patterns.en.md +1 -1
- package/skills/doxum-runtime/references/patterns.zh-CN.md +1 -1
- package/skills/doxum-runtime/references/projections.en.md +77 -4
- package/skills/doxum-runtime/references/projections.zh-CN.md +74 -3
- package/dist/scheduler--137L3j-.js +0 -500
- package/dist/scheduler--137L3j-.js.map +0 -1
- package/dist/scheduler-D4VthaaK.cjs +0 -589
- 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 }) =>
|
|
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 }) =>
|
|
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
|