doxum 0.1.5 → 0.1.6

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 (46) hide show
  1. package/README.md +46 -5
  2. package/dist/{contract-4E3moCbY.d.ts → contract-I99DUMbN.d.ts} +1 -1
  3. package/dist/index.cjs +73 -608
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +2 -63
  6. package/dist/index.d.ts +3 -64
  7. package/dist/index.js +11 -547
  8. package/dist/index.js.map +1 -1
  9. package/dist/integration-Bs3pOk65.cjs +35 -0
  10. package/dist/integration-Bs3pOk65.cjs.map +1 -0
  11. package/dist/integration-xupBIKjU.js +30 -0
  12. package/dist/integration-xupBIKjU.js.map +1 -0
  13. package/dist/integration.cjs +4 -14
  14. package/dist/integration.d.cts +2 -1
  15. package/dist/integration.d.ts +3 -2
  16. package/dist/integration.js +3 -14
  17. package/dist/local-sync.cjs +1 -1
  18. package/dist/local-sync.d.ts +1 -1
  19. package/dist/local-sync.js +1 -1
  20. package/dist/{ownership-CU-9DKJQ.cjs → ownership-CY0nPXGF.cjs} +4 -1
  21. package/dist/ownership-CY0nPXGF.cjs.map +1 -0
  22. package/dist/{ownership-B-VAPcce.js → ownership-CduRygE7.js} +4 -1
  23. package/dist/ownership-CduRygE7.js.map +1 -0
  24. package/dist/react.cjs +1 -1
  25. package/dist/react.js +1 -1
  26. package/dist/runtime-BJRXF0gq.js +1735 -0
  27. package/dist/runtime-BJRXF0gq.js.map +1 -0
  28. package/dist/runtime-CRijXjYp.cjs +1991 -0
  29. package/dist/runtime-CRijXjYp.cjs.map +1 -0
  30. package/dist/runtime-CUcvhFNP.d.ts +152 -0
  31. package/dist/runtime-C_vKHmhy.d.cts +152 -0
  32. package/package.json +1 -1
  33. package/skills/doxum-runtime/references/guide.en.md +30 -18
  34. package/skills/doxum-runtime/references/guide.zh-CN.md +22 -14
  35. package/skills/doxum-runtime/references/invariants.en.md +12 -9
  36. package/skills/doxum-runtime/references/invariants.zh-CN.md +2 -2
  37. package/skills/doxum-runtime/references/patterns.en.md +29 -56
  38. package/skills/doxum-runtime/references/patterns.zh-CN.md +21 -45
  39. package/dist/dependency-C5_R1tvQ.js +0 -496
  40. package/dist/dependency-C5_R1tvQ.js.map +0 -1
  41. package/dist/dependency-Dfzs3khk.cjs +0 -722
  42. package/dist/dependency-Dfzs3khk.cjs.map +0 -1
  43. package/dist/integration.cjs.map +0 -1
  44. package/dist/integration.js.map +0 -1
  45. package/dist/ownership-B-VAPcce.js.map +0 -1
  46. package/dist/ownership-CU-9DKJQ.cjs.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doxum",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "Doxum is a typed runtime for complex mutable documents.",
@@ -24,8 +24,8 @@ resolution.
24
24
  | Replace an entire trusted snapshot | `runtime.replace(document, options)` |
25
25
  | Observe one schema location | `schema.value` plus `runtime.subscribe` |
26
26
  | Observe a table or map | `schema.collection` plus `runtime.subscribe` |
27
- | Maintain mapped collection data | `createCollectionView` |
28
- | Maintain an aggregate or index | `createMaterializedView` |
27
+ | Maintain mapped collection data | `projection.map` |
28
+ | Maintain an aggregate or index | `projection.value / projection.collection` |
29
29
  | Read in React | `useDocumentSelector`, `useReadable`, or `useReadable` |
30
30
 
31
31
  Do not write a second mutable copy of the document. `createDocument` is the
@@ -293,26 +293,38 @@ comparison helpers in application modules.
293
293
 
294
294
  ## Build derived read models
295
295
 
296
- `createCollectionView` maps one table or map into stable ids, cached `item(id)`
297
- readables, and a lazy `all` array. It updates only affected entries where possible.
298
-
299
296
  ```ts
300
- import { createCollectionView } from 'doxum';
301
-
302
- const taskTitles = createCollectionView({
303
- runtime,
304
- source: taskSchema.collection(path => path.tasks),
305
- map: (_id, task) => task.title.get(),
297
+ const projection = createProjectionRuntime({ onError: error => console.error(error) });
298
+ const document = projection.document(runtime);
299
+ const notes = document.collection(path => path.notes);
300
+ const noteSummaries = projection.map(
301
+ notes,
302
+ (id, note) => ({ id, preview: note.body.get().slice(0, 80) }),
303
+ { isEqual: (a, b) => a.id === b.id && a.preview === b.preview }
304
+ );
305
+ const noteCount = projection.value({
306
+ sources: { notes },
307
+ build: ({ notes }) => ({
308
+ value: notes.read.ids().length,
309
+ update: ({ notes }) => ({
310
+ kind: 'changed',
311
+ value: notes.read.ids().length,
312
+ }),
313
+ }),
306
314
  });
307
-
308
- taskTitles.item('write-guide').current();
309
- taskTitles.all.current();
310
315
  ```
311
316
 
312
- Use `createMaterializedView` for an index or aggregate with custom incremental
313
- logic. A materialized view is derived state, not a caller-maintained cache. It
314
- may depend only on materialized views created earlier from the same runtime.
315
- Dispose every view when its owning feature is disposed.
317
+ projection.map produces stable ids/item readables and lazy all. Declare a
318
+ DocumentCollectionSource directly in any processor's sources, or use
319
+ projection.value and projection.collection for explicit incremental logic.
320
+ There is no automatic keyed dependency tracking. All sources belong to the
321
+ same projection owner, but may refer to different document runtimes.
322
+
323
+ Use projection.input(initial, { isEqual }) for boundary values; give processors
324
+ its source and keep set at the application boundary. fromReadable attaches an
325
+ existing external source without taking ownership of it. Batch synchronous
326
+ multi-source changes before the first commit. Dispose the projection at service
327
+ shutdown; React unmount only unsubscribes. Disposed handles throw.
316
328
 
317
329
  ## React integration
318
330
 
@@ -17,8 +17,8 @@
17
17
  | 整体替换可信快照 | `runtime.replace(document, options)` |
18
18
  | 监听一个 schema 位置 | `schema.value` + `runtime.subscribe` |
19
19
  | 监听 table 或 map | `schema.collection` + `runtime.subscribe` |
20
- | 维护映射后的集合数据 | `createCollectionView` |
21
- | 维护聚合或索引 | `createMaterializedView` |
20
+ | 维护映射后的集合数据 | `projection.map` |
21
+ | 维护聚合或索引 | `projection.value / projection.collection` |
22
22
  | 在 React 中读取 | `useDocumentSelector`、`useReadable` 或 `useReadable` |
23
23
 
24
24
  不要再维护一份可写的文档副本。`createDocument` 是 canonical state 的唯一所有者。
@@ -243,22 +243,30 @@ value selector 使用 `commit.impact.affects(target)`;table 或 map selector
243
243
 
244
244
  ## 构建派生读模型
245
245
 
246
- `createCollectionView` 将一个 table 或 map 映射为稳定的 ids、缓存的 `item(id)` readable 和惰性 `all` 数组;在可能的情况下只更新受影响的条目。
247
-
248
246
  ```ts
249
- import { createCollectionView } from 'doxum';
250
-
251
- const taskTitles = createCollectionView({
252
- runtime,
253
- source: taskSchema.collection(path => path.tasks),
254
- map: (_id, task) => task.title.get(),
247
+ const projection = createProjectionRuntime({ onError: error => console.error(error) });
248
+ const document = projection.document(runtime);
249
+ const notes = document.collection(path => path.notes);
250
+ const noteSummaries = projection.map(
251
+ notes,
252
+ (id, note) => ({ id, preview: note.body.get().slice(0, 80) }),
253
+ { isEqual: (a, b) => a.id === b.id && a.preview === b.preview }
254
+ );
255
+ const noteCount = projection.value({
256
+ sources: { notes },
257
+ build: ({ notes }) => ({
258
+ value: notes.read.ids().length,
259
+ update: ({ notes }) => ({
260
+ kind: 'changed',
261
+ value: notes.read.ids().length,
262
+ }),
263
+ }),
255
264
  });
256
-
257
- taskTitles.item('write-guide').current();
258
- taskTitles.all.current();
259
265
  ```
260
266
 
261
- 需要自定义增量逻辑的索引或聚合使用 `createMaterializedView`。materialized view 是派生状态,不是由调用方手工同步的 cache;它只能依赖同一 runtime 中更早创建的 materialized view。每个 view 都要由所属功能在销毁时释放。
267
+ projection.map 提供稳定的 ids/item readable 和惰性 all。DocumentCollectionSource 可直接用于自定义 processor 的 sources;projection.value 和 projection.collection 承载显式增量计算。不自动跟踪 keyed 依赖。sources 属于同一 projection owner,但可接入不同 document runtime。
268
+
269
+ projection.input(initial, { isEqual }) 用于边界值;应用保留 set,processor 只接收 source。fromReadable 接入已有外部 source,不取得其生命周期所有权。多 source 同步更新应在首次 commit 前进入 batch。projection 随 service 销毁,React unmount 只取消订阅,disposed handle 明确抛错。
262
270
 
263
271
  ## React 集成
264
272
 
@@ -117,15 +117,18 @@ and history stay settled.
117
117
 
118
118
  ## Derived state is declared, not synchronized by callers
119
119
 
120
- `CollectionView` derives one declared collection and incrementally maintains
121
- ids, keyed values, and a lazy aggregate array. `MaterializedView` derives one
122
- value from document reads, tracked impact dependencies, and optional earlier
123
- materialized sources. A materialized view can only depend on views of the same
124
- runtime that were created before it.
125
-
126
- Do not cache derived values inside canonical document state unless they are
127
- real domain data. Do not have UI code manually feed changes into a view. Dispose
128
- views and subscriptions with their owner.
120
+ ProjectionCollection and ProjectionValue share one explicit source graph and
121
+ publication mechanism. Only scoped processors write derived output. Application
122
+ code selects candidate keys from native impacts; Doxum owns final change,
123
+ equality, revision and notification. Never expose mutable private indexes or
124
+ retain scoped readers/writers. Updates must be synchronous.
125
+
126
+ All affected nodes settle before listeners; failures discard staged output and
127
+ attempt one fresh build before faulting. Faulted ancestors block descendants.
128
+ Listener exceptions are isolated. Source writes are forbidden during graph
129
+ processing and notification. Explicit batch defers graph publication, not
130
+ canonical commits or document listeners, and never promises source rollback.
131
+ Dispose the owner; do not dispose a node while it still has consumers.
129
132
 
130
133
  ## Framework and product boundaries
131
134
 
@@ -78,9 +78,9 @@ update 与 notification 窗口中禁止写入。processor、flush 和 listener e
78
78
 
79
79
  ## 派生状态应声明,而不是由调用方同步
80
80
 
81
- `CollectionView` 从一个已声明的 collection 派生,并增量维护 ids、keyed values 和惰性 aggregate array。`MaterializedView` 从 document read、跟踪到的 impact dependency 和可选的更早 materialized source 派生一个值。materialized view 只能依赖同一 runtime 中比它更早创建的 view。
81
+ ProjectionCollection 和 ProjectionValue 共用显式 source 图与发布机制。只有 scoped processor 写入派生输出。应用根据原生 impact 选择候选 key,Doxum 拥有最终 change、equality、revision 和 notification。不能泄漏可变私有索引,也不能保留 scoped reader/writer。update 必须同步。
82
82
 
83
- 不要把 derived value 缓存在 canonical document state 中,除非它本来就是领域数据。不要让 UI 手工将变更推入 view。view 与 subscription 都应随 owner dispose。
83
+ 受影响节点全部 settle 后再通知。失败丢弃候选输出,限一次全新 build 恢复,持续失败阻断下游;listener 错误逐个隔离。处理和通知期间禁止 source write。显式 batch 延迟派生图发布,不延迟 canonical commit 或 document listener,不承诺 source rollback。owner 负责 dispose,仍有消费者的节点不能单独释放。
84
84
 
85
85
  ## Framework 与产品边界
86
86
 
@@ -188,68 +188,41 @@ as values. Use `target.address`, `target.id`, `target.belongs`, and
188
188
  `target.bucket` rather than duplicating their interpretation in a framework
189
189
  adapter or local helper.
190
190
 
191
- ## Build a collection view for mapped rows
192
-
193
- Use a collection view when a table or map needs a reusable read model. Its
194
- mapping callback can read the entry's typed fields; `isEqual` lets the view
195
- retain a previous mapped value when a remapped row is semantically unchanged.
191
+ ## Build explicit projections
196
192
 
197
193
  ```ts
198
- const notes = documentSchema.collection(path => path.notes);
199
-
200
- const noteSummaries = createCollectionView({
201
- runtime,
202
- source: notes,
203
- map: (id, note) => ({ id, preview: note.body.get().slice(0, 80) }),
204
- isEqual: (left, right) => left.id === right.id && left.preview === right.preview,
205
- });
206
-
207
- const stop = noteSummaries.item('a').subscribe(() => rerenderRow('a'));
208
- const all = noteSummaries.all.current();
209
- ```
210
-
211
- Do not make callers push changes into the view. It listens to its declared
212
- source and recomputes from runtime state. Dispose `noteSummaries` and `stop`
213
- with the UI or service that owns them.
214
-
215
- ## Build a materialized aggregate or index
216
-
217
- Use a materialized view when you own an aggregate that can update from a
218
- commit. During `update`, inspect the impact through the provided `impact`
219
- object. That records the actual dependencies, so future unrelated commits can
220
- skip this update.
221
-
222
- ```ts
223
- const notes = documentSchema.collection(path => path.notes);
224
-
225
- const noteCount = createMaterializedView(runtime, {
226
- build: ({ read }) => ({
227
- value: read.notes.ids().length,
228
- update: ({ impact, read }) => {
229
- const change = impact.collection(notes);
230
- if (
231
- change.kind === 'incremental' &&
232
- change.added.size === 0 &&
233
- change.removed.size === 0 &&
234
- change.updated.size === 0 &&
235
- !change.orderChanged
236
- ) {
237
- return { kind: 'unchanged' };
238
- }
239
- return {
240
- kind: 'changed',
241
- value: read.notes.ids().length,
242
- change,
243
- };
244
- },
194
+ const projection = createProjectionRuntime({ onError: error => console.error(error) });
195
+ const document = projection.document(runtime);
196
+ const notes = document.collection(path => path.notes);
197
+ const noteSummaries = projection.map(
198
+ notes,
199
+ (id, note) => ({ id, preview: note.body.get().slice(0, 80) }),
200
+ { isEqual: (a, b) => a.id === b.id && a.preview === b.preview }
201
+ );
202
+ const noteCount = projection.value({
203
+ sources: { notes },
204
+ build: ({ notes }) => ({
205
+ value: notes.read.ids().length,
206
+ update: ({ notes }) => ({
207
+ kind: 'changed',
208
+ value: notes.read.ids().length,
209
+ }),
245
210
  }),
246
211
  });
247
212
  ```
248
213
 
249
- Return `rebuild` if local incremental logic cannot safely handle a commit.
250
- Views created later can name earlier views in `sources`; Doxum processes that
251
- graph in creation order and flushes it before external listeners observe the
252
- commit.
214
+ Use map for one-to-one collection transforms. For custom indexes, use
215
+ projection.collection with declared sources and scoped writer.set/remove/order/replace.
216
+ Its previous and next readers separate published values from staged writes.
217
+ Inspect each document source commit.impact, or an upstream collection change,
218
+ to determine candidate keys. Dependencies are explicit and fixed, not learned
219
+ from reads. Doxum determines final changes through equality.
220
+
221
+ A failed update discards its instance and attempts one fresh build. Persistent
222
+ faults block descendants; independent branches continue. Call rebuild through
223
+ the node API, never manually emit. Dispose the projection with its owner.
224
+ Wrap document mutation and editor cleanup in projection.batch before the first
225
+ commit; reads inside the batch remain at the last published projection.
253
226
 
254
227
  ## Bind read models in React
255
228
 
@@ -157,58 +157,34 @@ const unsubscribe = runtime.subscribe([notes, body], commit => {
157
157
 
158
158
  只有需要将两个 impact target 当作值比较时,使用 `target.same(left, right)`。应使用 `target.address`、`target.id`、`target.belongs` 与 `target.bucket`,不要在 framework adapter 或局部 helper 中复制它们的解释逻辑。
159
159
 
160
- ## 用 CollectionView 构建映射行
161
-
162
- 当一个 table 或 map 需要可复用 read model 时,使用 collection view。mapping callback 可以读取 entry 的类型化字段;`isEqual` 允许 view 在重新映射出的 row 语义不变时保留旧值。
160
+ ## 构建显式派生图
163
161
 
164
162
  ```ts
165
- const notes = documentSchema.collection(path => path.notes);
166
-
167
- const noteSummaries = createCollectionView({
168
- runtime,
169
- source: notes,
170
- map: (id, note) => ({ id, preview: note.body.get().slice(0, 80) }),
171
- isEqual: (left, right) => left.id === right.id && left.preview === right.preview,
163
+ const projection = createProjectionRuntime({ onError: error => console.error(error) });
164
+ const document = projection.document(runtime);
165
+ const notes = document.collection(path => path.notes);
166
+ const noteSummaries = projection.map(
167
+ notes,
168
+ (id, note) => ({ id, preview: note.body.get().slice(0, 80) }),
169
+ { isEqual: (a, b) => a.id === b.id && a.preview === b.preview }
170
+ );
171
+ const noteCount = projection.value({
172
+ sources: { notes },
173
+ build: ({ notes }) => ({
174
+ value: notes.read.ids().length,
175
+ update: ({ notes }) => ({
176
+ kind: 'changed',
177
+ value: notes.read.ids().length,
178
+ }),
179
+ }),
172
180
  });
173
-
174
- const stop = noteSummaries.item('a').subscribe(() => rerenderRow('a'));
175
- const all = noteSummaries.all.current();
176
181
  ```
177
182
 
178
- 不要让调用方把变更推入 view。它会监听声明的 source 并从 runtime state 重新计算。`noteSummaries` 和 `stop` 应随所属 UI 或 service 一起 dispose。
179
-
180
- ## 用 MaterializedView 构建聚合或索引
181
-
182
- 当你拥有可以根据 commit 增量更新的 aggregate 时,使用 materialized view。`update` 期间通过传入的 `impact` 对象检查影响范围;这会记录真实依赖,使之后无关的 commit 可以跳过此次 update。
183
+ map 用于保留 key 和顺序的一对一映射。自定义索引使用 projection.collection,声明 sources,并通过 scoped writer.set/remove/order/replace 更新。previous 与 next 分别读取上次输出和本轮暂存结果。
183
184
 
184
- ```ts
185
- const notes = documentSchema.collection(path => path.notes);
186
-
187
- const noteCount = createMaterializedView(runtime, {
188
- build: ({ read }) => ({
189
- value: read.notes.ids().length,
190
- update: ({ impact, read }) => {
191
- const change = impact.collection(notes);
192
- if (
193
- change.kind === 'incremental' &&
194
- change.added.size === 0 &&
195
- change.removed.size === 0 &&
196
- change.updated.size === 0 &&
197
- !change.orderChanged
198
- ) {
199
- return { kind: 'unchanged' };
200
- }
201
- return {
202
- kind: 'changed',
203
- value: read.notes.ids().length,
204
- change,
205
- };
206
- },
207
- }),
208
- });
209
- ```
185
+ 根据每个 document source 的 commits 中的 impact,或上游 collection change,决定候选 key。依赖显式固定,不通过 reader 自动学习。Doxum 根据 equality 决定最终 change。
210
186
 
211
- 局部增量逻辑无法安全处理一个 commit 时,返回 `rebuild`。后创建的 view 可以在 `sources` 中声明更早创建的 view;Doxum 会按创建顺序处理该图,并在外部 listener 看到 commit 前将其 flush 完成。
187
+ update 失败后丢弃实例并限一次全新 build 恢复;持续失败阻断下游,独立分支继续。rebuild 经过同一调度图,不手动 emit。projection 随 owner dispose。使用 projection.batch 包住首次 document commit 之前到 editor cleanup 结束的完整同步动作;batch 内派生读取保持上次发布状态。
212
188
 
213
189
  ## 在 React 中绑定读模型
214
190