doxum 0.1.22 → 0.1.25

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 (84) hide show
  1. package/README.md +210 -105
  2. package/dist/advanced.cjs +237 -2
  3. package/dist/advanced.cjs.map +1 -0
  4. package/dist/advanced.d.cts +98 -2
  5. package/dist/advanced.d.ts +98 -2
  6. package/dist/advanced.js +236 -1
  7. package/dist/advanced.js.map +1 -0
  8. package/dist/{contract-CRrZ-3sF.d.cts → contract-DB8XvNt0.d.cts} +102 -99
  9. package/dist/{contract-CxiNcqMn.d.ts → contract-Dawq6LKS.d.ts} +102 -99
  10. package/dist/definition-CHVS-RDY.d.cts +110 -0
  11. package/dist/definition-DT1NpMul.d.ts +110 -0
  12. package/dist/driver-B5vdPohL.js +385 -0
  13. package/dist/driver-B5vdPohL.js.map +1 -0
  14. package/dist/driver-BL91gn7_.cjs +480 -0
  15. package/dist/driver-BL91gn7_.cjs.map +1 -0
  16. package/dist/index.cjs +3489 -954
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +66 -32
  19. package/dist/index.d.ts +66 -32
  20. package/dist/index.js +4114 -1579
  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 +6 -75
  29. package/dist/react.cjs.map +1 -1
  30. package/dist/react.d.cts +6 -9
  31. package/dist/react.d.ts +6 -9
  32. package/dist/react.js +7 -76
  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 +5 -8
  43. package/skills/doxum-runtime/SKILL.md +28 -8
  44. package/skills/doxum-runtime/agents/openai.yaml +1 -1
  45. package/skills/doxum-runtime/references/api.en.md +243 -0
  46. package/skills/doxum-runtime/references/api.zh-CN.md +236 -0
  47. package/skills/doxum-runtime/references/guide.en.md +26 -14
  48. package/skills/doxum-runtime/references/guide.zh-CN.md +21 -11
  49. package/skills/doxum-runtime/references/invariants.en.md +32 -6
  50. package/skills/doxum-runtime/references/invariants.zh-CN.md +26 -7
  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 +206 -119
  54. package/skills/doxum-runtime/references/projections.zh-CN.md +197 -101
  55. package/dist/access-CZXv_DIF.js +0 -958
  56. package/dist/access-CZXv_DIF.js.map +0 -1
  57. package/dist/access-D64MFRSm.cjs +0 -1101
  58. package/dist/access-D64MFRSm.cjs.map +0 -1
  59. package/dist/advanced-BTStm2cW.js +0 -1192
  60. package/dist/advanced-BTStm2cW.js.map +0 -1
  61. package/dist/advanced-CqrR_4dh.d.ts +0 -176
  62. package/dist/advanced-CyR5o3Xr.d.cts +0 -176
  63. package/dist/advanced-GKZ-CZ6a.cjs +0 -1365
  64. package/dist/advanced-GKZ-CZ6a.cjs.map +0 -1
  65. package/dist/driver-BC06FEMF.cjs +0 -241
  66. package/dist/driver-BC06FEMF.cjs.map +0 -1
  67. package/dist/driver-BqRjwFmU.js +0 -200
  68. package/dist/driver-BqRjwFmU.js.map +0 -1
  69. package/dist/integration-BqDw7e2w.cjs +0 -30
  70. package/dist/integration-BqDw7e2w.cjs.map +0 -1
  71. package/dist/integration-CSXaF8HP.js +0 -25
  72. package/dist/integration-CSXaF8HP.js.map +0 -1
  73. package/dist/integration.cjs +0 -13
  74. package/dist/integration.d.cts +0 -17
  75. package/dist/integration.d.ts +0 -17
  76. package/dist/integration.js +0 -5
  77. package/dist/issue-9U6YLYz3.js +0 -647
  78. package/dist/issue-9U6YLYz3.js.map +0 -1
  79. package/dist/issue-Bo0FIO3b.cjs +0 -886
  80. package/dist/issue-Bo0FIO3b.cjs.map +0 -1
  81. package/dist/notification-CrZHVx3a.js +0 -176
  82. package/dist/notification-CrZHVx3a.js.map +0 -1
  83. package/dist/notification-DFoA9bQY.cjs +0 -235
  84. package/dist/notification-DFoA9bQY.cjs.map +0 -1
package/README.md CHANGED
@@ -60,10 +60,14 @@ Committed observer failures are returned in `observerErrors` without rollback.
60
60
  ## Read And Observe
61
61
 
62
62
  ```ts
63
- import { read, snapshot } from 'doxum';
63
+ import { read, select, snapshot } from 'doxum';
64
64
 
65
65
  const title = read(document, state => state.title);
66
66
  const tasks = read(document, state => snapshot(state.tasks));
67
+ const selectedTitle = select(document, state => state.title);
68
+ const stop = selectedTitle.subscribe(() => {
69
+ console.log(selectedTitle.current());
70
+ });
67
71
  document.subscribe(
68
72
  path => path.tasks.item('b').title,
69
73
  commit => {
@@ -81,6 +85,10 @@ supported types, while classes/functions need application-specific handling.
81
85
  Use ordinary properties for fine-grained reads. Data callbacks read real values;
82
86
  path callbacks describe symbolic schema locations, including missing entries.
83
87
  Subscription paths compile once during registration.
88
+ `select(document, selector, equality?)` returns a standard `Readable`. Core
89
+ tracks exactly the document locations read by the selector, rebinds dependencies
90
+ when selector branches change, and preserves the previous selected reference and
91
+ publication revision when `equality` reports no result change.
84
92
 
85
93
  React integration:
86
94
 
@@ -91,9 +99,10 @@ const title = useDocumentSelector(document, state => state.title);
91
99
  const history = useHistory(document.history);
92
100
  ```
93
101
 
94
- React tracks fields actually read and updates dependencies when the selector
95
- branches. The core has no React dependency. `asReadable(document)` removes write
96
- capabilities while retaining selection, subscription and projection support.
102
+ Document dependency tracking belongs to Core's `select`; the React adapter only
103
+ subscribes to the resulting `Readable`. The core has no React dependency.
104
+ `document.readonly()` returns a capability-stripped `ReadonlyDocument` alias for
105
+ boundaries that must not receive document write APIs.
97
106
 
98
107
  ## Containers And Parsing
99
108
 
@@ -110,9 +119,11 @@ capabilities while retaining selection, subscription and projection support.
110
119
 
111
120
  Map/table key validators preserve branded string types through access, paths,
112
121
  projection keys and impact queries. Validators are pure, synchronous functions or
113
- Standard Schema v1 validators. They receive the original value, must not mutate it,
114
- and their successful output is ignored. Perform transformations before calling
115
- Doxum; it does not detect validator mutation or conversion. `parse(model, unknown)`
122
+ Standard Schema v1 validators. Function validators are predicates/assertions: return
123
+ `true` or `undefined` for success, `false` for rejection, or throw from an assertion.
124
+ Standard Schema validation must return the original value by identity; transformed
125
+ outputs are rejected. Validators must not mutate their input. Perform transformations
126
+ before calling Doxum. `parse(model, unknown)`
116
127
  validates and copies schema structure while sharing readonly payloads. Strict parsing
117
128
  requires validators for atomic fields; typed in-memory fields can omit them.
118
129
 
@@ -196,6 +207,10 @@ values support reverse replay but are not trusted as local undo data; the runtim
196
207
  captures its actual old state. Revision is a local baseline, not a distributed
197
208
  conflict-resolution protocol.
198
209
 
210
+ Local/system apply may opt out of history with `history: false`. Remote apply uses
211
+ `{ expectedRevision, source: 'remote' }` and always invalidates local history; it has
212
+ no independent history option.
213
+
199
214
  Published ChangeSets are readonly in their entirety, including envelope objects,
200
215
  addresses, transitions and order arrays. The runtime reuses validation of its own
201
216
  publications and normalized storage input by identity; fresh unknown input still
@@ -222,138 +237,223 @@ with `history: false` close the active group.
222
237
 
223
238
  ## Projections
224
239
 
240
+ Projection definitions are lazy and reusable. A `ProjectionRuntime` owns materialization,
241
+ incremental state, scheduling, publication, batching, recovery and Runtime-local inputs.
242
+
225
243
  ```ts
226
244
  import { createProjectionRuntime, derive, input, observe } from 'doxum';
227
245
 
228
246
  const tasks = observe(document, path => path.tasks);
229
247
  const filter = input<'all' | 'open'>('all');
230
- const visible = derive([tasks, filter], (all, mode) => {
231
- if (mode === 'all') return all;
232
- return new Map([...all].filter(([, task]) => !task.done));
233
- });
248
+
249
+ const visible = derive({ tasks, filter }, ({ tasks, filter }) =>
250
+ filter === 'all' ? tasks : new Map([...tasks].filter(([, task]) => !task.done))
251
+ );
234
252
 
235
253
  const runtime = createProjectionRuntime({ onError: console.error });
236
- runtime.get(visible);
237
- const selected = runtime.readable(visible, tasks => tasks.get('a'));
254
+
255
+ const current = runtime.read(visible);
256
+ const selected = runtime.select(visible, tasks => tasks.get('a'));
238
257
  const stop = selected.subscribe(() => selected.current());
239
- runtime.set(filter, 'open');
240
- runtime.batch({ cause: { action: 'refresh' } }, () => {
241
- runtime.set(filter, 'all');
242
- document.update(draft => {
243
- draft.title = 'Updated';
244
- });
245
- });
258
+
259
+ runtime.update(filter, 'open');
260
+ runtime.batch(
261
+ () => {
262
+ runtime.update(filter, 'all');
263
+ document.update(draft => {
264
+ draft.title = 'Updated';
265
+ });
266
+ },
267
+ { cause: { action: 'refresh' } }
268
+ );
269
+
246
270
  stop();
271
+ runtime.dispose();
272
+ ```
273
+
274
+ The stable vocabulary is `read` for a synchronous current value, `select` for a
275
+ `Readable`, and `observe` for declaring a lazy source boundary. `ProjectionRuntime`
276
+ exposes `read`, `select`, `update`, `batch`, `scope` and `dispose`.
277
+ `ProjectionScope` mirrors `read`, `select`, `update`, `batch` and `dispose`, plus
278
+ `own(definitionOrTree)` for lifecycle ownership of lazy projection definitions.
279
+
280
+ ### Keyed projection
281
+
282
+ `derive.keyed` preserves a keyed driver's membership and order while deriving entries
283
+ independently. The simple form is a per-entry selector:
284
+
285
+ ```ts
286
+ const fieldValues = derive.keyed(records, record => record.values[fieldId]);
247
287
  ```
248
288
 
249
- Root projection declarations are lazy and reusable; scoped declarations are
250
- lazy but tied to their scope. The Runtime owns `get`,
251
- `readable`, scalar `set`, keyed `update`, `batch`, `scope` and `dispose`;
252
- materialization, incremental state, publication and recovery stay inside that
253
- one Runtime. Collection values are
254
- immutable `ReadonlyMap`-like snapshots, while a `Readable` owns selector
255
- tracking, equality and subscription lifecycle.
289
+ The output keeps one key per source entry. The selector runs only for affected keys,
290
+ and the optional equality function suppresses `updated` output transitions when the
291
+ derived value is equal.
256
292
 
257
- Local projections share the parent Runtime's scheduler and materialization owner:
293
+ Dynamic joins stay explicit in the dependency graph. Declare keyed lookups and ordinary
294
+ projection dependencies by name:
258
295
 
259
296
  ```ts
260
- const scope = runtime.scope();
261
- const localFilter = scope.input<'all' | 'open'>('all');
262
- const localVisible = scope.derive([tasks, localFilter], (tasks, filter) =>
263
- filter === 'all' ? tasks : new Map([...tasks].filter(([, task]) => !task.done))
297
+ const cardContent = derive.keyed(
298
+ items,
299
+ {
300
+ record: { source: records, key: item => item.recordId },
301
+ view: activeView,
302
+ fields: visibleFields,
303
+ },
304
+ (item, itemId, { record, view, fields }) => renderCard(itemId, item, record, view, fields)
264
305
  );
265
- scope.get(localVisible);
266
- scope.dispose(); // Releases local producers, state and subscriptions; parent tasks remain.
267
306
  ```
268
307
 
269
- For Runtime-local keyed state, `input.collection` publishes the same exact
270
- `CollectionChange` as document collections. One `update` edits one or many keys
271
- atomically; the enclosing Runtime batch coalesces accepted edits into one net
272
- change:
308
+ For `{ source, key }` dependencies, the Runtime owns output-key → source-key bindings
309
+ and reverse invalidation. A missing source entry resolves to `undefined` but remains
310
+ bound, so adding it later invalidates the dependent output keys. An ordinary projection
311
+ dependency invalidates the driver key set when its value changes. Processor code never
312
+ performs imperative Runtime reads to discover dependencies.
313
+
314
+ ### Runtime-local keyed state
315
+
316
+ Use `input.collection` for selection, expanded state, local overrides and other
317
+ Runtime-local keyed application/UI state:
273
318
 
274
319
  ```ts
275
- const overrides = input.collection<string, Task>();
276
- runtime.update(overrides, draft => {
277
- draft.set(taskId, task);
278
- draft.remove(oldTaskId);
320
+ const selection = input.collection<RowId, SelectionState>(
321
+ new Map(),
322
+ (previous, next) => previous.selected === next.selected
323
+ );
324
+
325
+ runtime.update(selection, draft => {
326
+ draft.set(rowId, { selected: true });
327
+ draft.remove(previousRowId);
279
328
  });
280
329
  ```
281
330
 
282
- `observe` is the single source boundary for documents, Doxum `Readable` values,
283
- and eventful external sources. External sources declare `kind: 'value'` or
284
- `kind: 'collection'`; collection invalidation remains keyed internally. Schema
285
- `map`, `table`, and `list(field, { keyOf })` paths are all observed as keyed
286
- collection projections. Trees expose structural observation without a second
287
- projection protocol: `path.tree.rootId` is a scalar source,
288
- `path.tree.nodes` is a keyed collection source, and
289
- `path.tree.nodes.item(id)` is a single-node value source. A schema `list` uses its
290
- `keyOf` identity and publishes in document order; ordinary array-valued `field(...)`
291
- nodes remain scalar values.
292
-
293
- Document projection sources route committed groups directly to their observed
294
- targets, merge affected locations for the current projection batch, then combine the
295
- previously published snapshot with the canonical final value. A touched tree node
296
- replaces only that node structure and the necessary ancestor containers; unchanged
297
- tree-node snapshots keep their identity. Whole aggregate tree values still use the
298
- public `{ rootId?, nodes: Record }` shape, so changing a node may shallow-copy the
299
- `nodes` record, while native `tree.nodes` observation remains keyed and touches only
300
- changed nodes.
301
-
302
- For retained state, reverse indexes and keyed patches, use the isolated advanced
303
- entry point. Whole-value processors use `incremental(...)`; keyed collection
304
- processors use `incremental.collection(...)`:
331
+ Its state is owned by the `ProjectionRuntime`, not the document, history or persistence.
332
+ Updates are synchronous and atomic. It publishes exact keyed `CollectionChange`
333
+ transitions and preserves order. The optional per-entry equality defaults to `Object.is`
334
+ and suppresses equivalent `set` operations. Callback and equality failures leave both
335
+ the published value and the next draft unchanged.
336
+
337
+ The processor-facing `CollectionChange` type is exported by `doxum/advanced`.
338
+
339
+ A root input definition can be materialized independently by several runtimes. Wrap any
340
+ definition in `scope.own(...)` when its lifecycle belongs to one scope; scalar inputs,
341
+ keyed inputs, derives and advanced group output trees all use the same ownership API.
342
+ Ownership must be assigned before that definition is first materialized as a root.
343
+
344
+ ### Sources and scopes
345
+
346
+ `observe` adapts documents, Doxum `Readable` values and exported external source
347
+ contracts. Document `map`, `table` and `list(field, { keyOf })` paths become keyed
348
+ collection projections. Trees reuse the same model: `path.tree.rootId` is scalar,
349
+ `path.tree.nodes` is keyed and `path.tree.nodes.item(id)` is a single-node value source.
350
+
351
+ ```ts
352
+ const scope = runtime.scope();
353
+ const localFilter = scope.own(input<'all' | 'open'>('all'));
354
+ const localVisible = scope.own(
355
+ derive({ tasks, filter: localFilter }, ({ tasks, filter }) =>
356
+ filter === 'all' ? tasks : new Map([...tasks].filter(([, task]) => !task.done))
357
+ )
358
+ );
359
+
360
+ scope.read(localVisible);
361
+ scope.dispose();
362
+ ```
363
+
364
+ A scope shares its parent Runtime's scheduler and source materialization but owns the
365
+ lifetime of definitions passed to `scope.own`, their materialized producers, state and
366
+ subscriptions. Scoped definitions may depend on root definitions; root definitions and
367
+ sibling scopes cannot depend on scoped definitions. A materialized root definition cannot
368
+ later be converted into a scoped definition.
369
+
370
+ ### Advanced processors
371
+
372
+ Use `doxum/advanced` only when pure `derive` / `derive.keyed` cannot express retained
373
+ state, cross-key indexes, or direct incremental patches. Dependencies are always named.
374
+ The processor definition is closed and always owns `process()`. Add `state()` only when
375
+ the processor actually needs Runtime-owned retained state.
305
376
 
306
377
  ```ts
307
378
  import { incremental } from 'doxum/advanced';
308
379
 
309
- const doubled = incremental.collection([tasks], ({ sources, output }) => {
310
- for (const [id, task] of sources[0]) output.set(id, task.value * 2);
311
- });
380
+ const doubled = incremental.collection(
381
+ { tasks },
382
+ {
383
+ state: () => ({ initialized: false }),
384
+ process: ({ values, changes, output, state, reset }) => {
385
+ if (reset) {
386
+ for (const [id, task] of values.tasks) {
387
+ output.set(id, task.value * 2);
388
+ }
389
+ output.order([...values.tasks.keys()]);
390
+ state.initialized = true;
391
+ return;
392
+ }
393
+
394
+ const change = changes.tasks;
395
+ if (!change || change.kind === 'reset') return;
396
+
397
+ for (const entry of change.added) output.set(entry.key, entry.after.value * 2);
398
+ for (const entry of change.updated) output.set(entry.key, entry.after.value * 2);
399
+ for (const entry of change.removed) output.remove(entry.key);
400
+ if (change.order) output.order([...values.tasks.keys()]);
401
+ },
402
+ }
403
+ );
404
+ ```
405
+
406
+ `incremental(...)` produces one scalar value projection.
407
+ `incremental.collection(...)` exposes keyed `previous`, `next` and `output`.
408
+ `incremental.group(...)` runs one processor for several atomic output leaves:
312
409
 
410
+ ```ts
313
411
  const render = incremental.group(
314
- [tasks],
315
- define => ({
316
- node: {
412
+ { tasks },
413
+ {
414
+ output: define => ({
317
415
  shell: define.collection<string, { readonly title: string }>(),
318
416
  content: define.collection<string, string>(),
417
+ count: define.value<number>(),
418
+ }),
419
+ state: () => ({ renders: 0 }),
420
+ process: ({ values, output, state }) => {
421
+ state.renders++;
422
+ for (const [id, task] of values.tasks) {
423
+ output.shell.set(id, { title: task.title });
424
+ output.content.set(id, task.title);
425
+ }
426
+ output.count.set(values.tasks.size);
319
427
  },
320
- labels: define.collection<string, string>(),
321
- chrome: define.value<{ readonly count: number }>(),
322
- revision: define.value<number>(),
323
- }),
324
- ({ sources, outputs }) => {
325
- for (const [id, task] of sources[0]) {
326
- outputs.node.shell.set(id, { title: task.title });
327
- outputs.node.content.set(id, task.title);
328
- outputs.labels.set(id, task.title);
329
- }
330
- outputs.chrome.set({ count: sources[0].size });
331
- outputs.revision.set(sources[0].size);
332
428
  }
333
429
  );
334
430
  ```
335
431
 
336
- Incremental processors receive a dependency-aligned `changes` tuple. Collection
337
- entries carry `added`/`updated`/`removed` transitions with complete
338
- `before`/`after` values, so a processor can patch indexes without rescanning the
339
- collection; scalar dependencies use `undefined` and the initial collection build
340
- reports `{ kind: 'reset' }`.
341
- `incremental.group` runs one processor for several named value and keyed collection
342
- outputs. `define.collection<K, V>(equality?)` creates a keyed leaf;
343
- `define.value<T>(equality?)` creates an ordinary scalar projection leaf. On an
344
- initial build or rebuild every value leaf must be set; on an incremental update an
345
- untouched value leaf keeps its published value. Its static nested namespace is only
346
- API organization: every leaf is an ordinary `Projection`, all changed leaves publish
347
- atomically in one causal settle, and downstream processors depend directly on those
348
- leaves. All leaves reference one producer, so reading any leaf materializes that
349
- producer once; split groups when outputs do not share computation or atomicity
350
- requirements. There is no group runtime, output event bus, or per-output runtime.
351
-
352
- In React, `useProjection(projection)` reads a value and
353
- `useProjection(projection, selector, equality?)` tracks keyed reads such as
354
- `tasks => tasks.get(taskId)`. Unrelated key changes do not execute that selector;
355
- equality only filters a selector result after a related change. `useInput(input)`
356
- returns the current value and its Runtime-local setter.
432
+ `define.collection<K,V>(equality?)` and `define.value<T>(equality?)` are output
433
+ declaration methods available only inside `output`. The returned object tree keeps the
434
+ same shape with ordinary `Projection` leaves. Value leaves must be initialized on
435
+ initial build or Runtime recovery; untouched value leaves retain their value during a
436
+ normal incremental run.
437
+
438
+ `values` and `changes` use the dependency names. Collection changes are exact
439
+ `added` / `updated` / `removed` transitions plus optional `order`; scalar dependency
440
+ changes are `undefined`. `reset` means the processor must reconcile from current source
441
+ values. Ordinary source resets preserve retained state when one exists. If a processor
442
+ faults, the Runtime owns recovery, recreates declared state, and performs a reset
443
+ evaluation. Stateless processors use the same recovery path without a state object.
444
+
445
+ ### React
446
+
447
+ ```ts
448
+ const task = useProjection(tasks, tasks => tasks.get(taskId), equality);
449
+ const [mode, setMode] = useInput(filter);
450
+ const [selectedRows, updateSelectedRows] = useInput(selection);
451
+ ```
452
+
453
+ `useProjection(projection, selector, equality?)` uses the same keyed selector semantics
454
+ as Core. `useInput` handles both scalar `Input<T>` and `CollectionInput<K,V>`; collection
455
+ updates receive a keyed draft callback. `ProjectionProvider` accepts either a
456
+ `ProjectionRuntime` or `ProjectionScope`.
357
457
 
358
458
  See [projection contracts](docs/projections.md).
359
459
 
@@ -364,6 +464,11 @@ runtime. One leader writes synchronously and persists final changes asynchronous
364
464
  followers apply the durable sequence in order. `flush()` waits for persistence.
365
465
  Durability errors do not roll back an already visible commit.
366
466
 
467
+ Operational failures use one `LocalSyncError` contract. Error states and `onError`
468
+ receive that same typed error and preserve an underlying `cause` when one exists.
469
+ Exceptions thrown by consumers of `state.subscribe` stay outside synchronization
470
+ fault state and are not reported through operational `onError`.
471
+
367
472
  The storage format is IndexedDB version 5 with format version 3 records. Earlier
368
473
  databases are rejected without upgrading, deleting or converting their data.
369
474
  This JSON adapter rejects non-JSON atomic values. Network collaboration and
@@ -395,7 +500,7 @@ It also reports structural generation advances and distinguishes captured order
395
500
  baselines from final published order copies. Architecture workloads include order
396
501
  round trips and repeated tree edits to expose costs hidden by commit-only benchmarks.
397
502
 
398
- Builds produce root `dist` ESM/CJS/declarations for `doxum`, `doxum/integration`,
503
+ Builds produce root `dist` ESM/CJS/declarations for `doxum`,
399
504
  `doxum/local-sync`, `doxum/react` and `doxum/advanced`. Source ownership is described in
400
505
  [architecture](docs/architecture.md) and [AGENTS.md](AGENTS.md).
401
506
  The runtime shares one transaction lifecycle; complete mutation operations are
package/dist/advanced.cjs CHANGED
@@ -1,3 +1,238 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_advanced = require("./advanced-GKZ-CZ6a.cjs");
3
- exports.incremental = require_advanced.incremental;
2
+ const require_scheduler = require("./scheduler-D4VthaaK.cjs");
3
+ const require_record = require("./record-ChYUi8PP.cjs");
4
+ //#region core/src/projection/advanced.ts
5
+ const resetCollectionChange = Object.freeze({ kind: "reset" });
6
+ const publicValue = (source) => source.kind === "value" ? source.value : require_scheduler.collectionView(source.read);
7
+ const compileDependencies = (dependencies) => {
8
+ if (!require_record.isPlainObject(dependencies) || require_scheduler.isProjection(dependencies)) throw new TypeError("Incremental dependencies must be a plain object.");
9
+ const names = [];
10
+ const projections = [];
11
+ for (const key of Reflect.ownKeys(dependencies)) {
12
+ if (typeof key !== "string") throw new TypeError("Incremental dependency names must be strings.");
13
+ const descriptor = Object.getOwnPropertyDescriptor(dependencies, key);
14
+ if (!descriptor?.enumerable || !("value" in descriptor)) throw new TypeError("Incremental dependencies must be enumerable data properties.");
15
+ require_scheduler.projectionRef(descriptor.value);
16
+ names.push(key);
17
+ projections.push(descriptor.value);
18
+ }
19
+ return Object.freeze({
20
+ names: Object.freeze(names),
21
+ projections: Object.freeze(projections)
22
+ });
23
+ };
24
+ const publicInputs = (sources, names, reset) => {
25
+ const values = Object.create(null);
26
+ const changes = Object.create(null);
27
+ for (let index = 0; index < names.length; index++) {
28
+ const source = sources[index];
29
+ values[names[index]] = publicValue(source);
30
+ changes[names[index]] = source.kind === "collection" ? reset ? resetCollectionChange : source.change : void 0;
31
+ }
32
+ return Object.freeze({
33
+ values: Object.freeze(values),
34
+ changes: Object.freeze(changes)
35
+ });
36
+ };
37
+ function validateDefinition(definition, allowed, label) {
38
+ if (!require_record.isPlainObject(definition)) throw new TypeError(`${label} definition must be a plain object.`);
39
+ const allowedKeys = new Set(allowed);
40
+ for (const key of Reflect.ownKeys(definition)) {
41
+ if (typeof key !== "string" || !allowedKeys.has(key)) throw new TypeError(`${label} definition contains an unknown property.`);
42
+ const descriptor = Object.getOwnPropertyDescriptor(definition, key);
43
+ if (!descriptor?.enumerable || !("value" in descriptor)) throw new TypeError(`${label} definition must contain enumerable data properties.`);
44
+ }
45
+ }
46
+ const validateRetainedState = (definition, label) => {
47
+ if (Object.prototype.hasOwnProperty.call(definition, "state") && typeof definition.state !== "function") throw new TypeError(`${label} state must be a function when provided.`);
48
+ };
49
+ const initializeRetainedState = (definition) => {
50
+ if (definition.state === void 0) return Object.freeze({});
51
+ const state = definition.state();
52
+ require_scheduler.assertSynchronous(state);
53
+ return Object.freeze({ state });
54
+ };
55
+ function createIncrementalValue(dependencies, definition) {
56
+ const compiled = compileDependencies(dependencies);
57
+ validateDefinition(definition, ["state", "process"], "Incremental value");
58
+ validateRetainedState(definition, "Incremental value");
59
+ if (typeof definition.process !== "function") throw new TypeError("Incremental value definition requires a process function.");
60
+ const [projection] = require_scheduler.defineProcessor({
61
+ dependencies: compiled.projections,
62
+ outputs: [{
63
+ kind: "value",
64
+ equality: Object.is
65
+ }],
66
+ create: () => {
67
+ const state = initializeRetainedState(definition);
68
+ return { evaluate: (evaluation) => {
69
+ const output = evaluation.outputs[0];
70
+ if (output.kind !== "value") throw new Error("Incremental value output is invalid.");
71
+ const publicInput = publicInputs(evaluation.sources, compiled.names, evaluation.reset);
72
+ const result = definition.process({
73
+ values: publicInput.values,
74
+ changes: publicInput.changes,
75
+ previous: output.previous,
76
+ reset: evaluation.reset,
77
+ cause: evaluation.cause,
78
+ ...state
79
+ });
80
+ require_scheduler.assertSynchronous(result);
81
+ output.output.set(result);
82
+ } };
83
+ }
84
+ });
85
+ return projection;
86
+ }
87
+ function createIncrementalCollection(dependencies, definition) {
88
+ const compiled = compileDependencies(dependencies);
89
+ validateDefinition(definition, ["state", "process"], "Incremental collection");
90
+ validateRetainedState(definition, "Incremental collection");
91
+ if (typeof definition.process !== "function") throw new TypeError("Incremental collection definition requires a process function.");
92
+ const [projection] = require_scheduler.defineProcessor({
93
+ dependencies: compiled.projections,
94
+ outputs: [{
95
+ kind: "collection",
96
+ equality: Object.is
97
+ }],
98
+ create: () => {
99
+ const state = initializeRetainedState(definition);
100
+ return { evaluate: (evaluation) => {
101
+ const output = evaluation.outputs[0];
102
+ if (output.kind !== "collection") throw new Error("Incremental collection output is invalid.");
103
+ const publicInput = publicInputs(evaluation.sources, compiled.names, evaluation.reset);
104
+ require_scheduler.assertSynchronous(definition.process({
105
+ values: publicInput.values,
106
+ changes: publicInput.changes,
107
+ previous: output.previous,
108
+ next: output.next,
109
+ reset: evaluation.reset,
110
+ cause: evaluation.cause,
111
+ output: output.output,
112
+ ...state
113
+ }));
114
+ } };
115
+ }
116
+ });
117
+ return projection;
118
+ }
119
+ const isPlainGroupNamespace = (value, metadata) => require_record.isPlainObject(value) && !metadata.has(value);
120
+ const compileGroupShape = (value, path, metadata, used, outputs) => {
121
+ if (value !== null && typeof value === "object") {
122
+ const output = metadata.get(value);
123
+ if (output) {
124
+ if (used.has(value)) throw new TypeError("An incremental group output cannot be reused.");
125
+ used.add(value);
126
+ const index = outputs.length;
127
+ outputs.push({
128
+ kind: output.kind,
129
+ equality: output.equality,
130
+ path: Object.freeze([...path])
131
+ });
132
+ return index;
133
+ }
134
+ }
135
+ if (!isPlainGroupNamespace(value, metadata)) throw new TypeError("Incremental group outputs must be a static object tree.");
136
+ for (const key of Reflect.ownKeys(value)) {
137
+ if (typeof key !== "string") throw new TypeError("Incremental group output names must be strings.");
138
+ if (!Object.prototype.propertyIsEnumerable.call(value, key)) throw new TypeError("Incremental group namespaces must contain enumerable properties.");
139
+ }
140
+ const keys = Object.keys(value);
141
+ if (!keys.length) throw new TypeError("Incremental group namespaces cannot be empty.");
142
+ const result = Object.create(null);
143
+ for (const key of keys) {
144
+ if (key === "__proto__" || key === "prototype" || key === "constructor") throw new TypeError(`Invalid incremental group output name: ${key}`);
145
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
146
+ if (!descriptor || !("value" in descriptor)) throw new TypeError("Incremental group namespaces cannot contain accessors.");
147
+ result[key] = compileGroupShape(descriptor.value, [...path, key], metadata, used, outputs);
148
+ }
149
+ return Object.freeze(result);
150
+ };
151
+ const hydrateGroupShape = (shape, values) => {
152
+ if (typeof shape === "number") return values[shape];
153
+ const result = {};
154
+ for (const [key, child] of Object.entries(shape)) result[key] = hydrateGroupShape(child, values);
155
+ return Object.freeze(result);
156
+ };
157
+ const hydrateGroupReads = (shape, outputs, next) => {
158
+ if (typeof shape === "number") {
159
+ const output = outputs[shape];
160
+ return next && output.kind === "value" ? output.next() : next ? output.next : output.previous;
161
+ }
162
+ const result = {};
163
+ for (const [key, child] of Object.entries(shape)) if (typeof child === "number" && next && outputs[child].kind === "value") Object.defineProperty(result, key, {
164
+ enumerable: true,
165
+ configurable: false,
166
+ get: () => outputs[child].next()
167
+ });
168
+ else result[key] = hydrateGroupReads(child, outputs, next);
169
+ return Object.freeze(result);
170
+ };
171
+ function createIncrementalGroup(dependencies, definition) {
172
+ const compiled = compileDependencies(dependencies);
173
+ validateDefinition(definition, [
174
+ "output",
175
+ "state",
176
+ "process"
177
+ ], "Incremental group");
178
+ validateRetainedState(definition, "Incremental group");
179
+ if (typeof definition.output !== "function" || typeof definition.process !== "function") throw new TypeError("Incremental group definition requires output and process functions.");
180
+ let active = true;
181
+ const metadata = /* @__PURE__ */ new Map();
182
+ const declared = definition.output({
183
+ collection: (equality = Object.is) => {
184
+ if (!active) throw new TypeError("Incremental group output declarations are synchronous.");
185
+ const descriptor = Object.freeze({});
186
+ metadata.set(descriptor, {
187
+ kind: "collection",
188
+ equality
189
+ });
190
+ return descriptor;
191
+ },
192
+ value: (equality = Object.is) => {
193
+ if (!active) throw new TypeError("Incremental group output declarations are synchronous.");
194
+ const descriptor = Object.freeze({});
195
+ metadata.set(descriptor, {
196
+ kind: "value",
197
+ equality
198
+ });
199
+ return descriptor;
200
+ }
201
+ });
202
+ require_scheduler.assertSynchronous(declared);
203
+ active = false;
204
+ const outputs = [];
205
+ const used = /* @__PURE__ */ new Set();
206
+ const shape = compileGroupShape(declared, [], metadata, used, outputs);
207
+ if (typeof shape === "number") throw new TypeError("Incremental group declarations must return an output namespace.");
208
+ if (used.size === 0 || used.size !== metadata.size) throw new TypeError("Every incremental group output must be returned by the declaration.");
209
+ return hydrateGroupShape(shape, require_scheduler.defineProcessor({
210
+ dependencies: compiled.projections,
211
+ outputs,
212
+ create: () => {
213
+ const state = initializeRetainedState(definition);
214
+ return { evaluate: (evaluation) => {
215
+ const publicInput = publicInputs(evaluation.sources, compiled.names, evaluation.reset);
216
+ require_scheduler.assertSynchronous(definition.process({
217
+ values: publicInput.values,
218
+ changes: publicInput.changes,
219
+ previous: hydrateGroupReads(shape, evaluation.outputs, false),
220
+ next: hydrateGroupReads(shape, evaluation.outputs, true),
221
+ output: hydrateGroupShape(shape, evaluation.outputs.map((output) => output.output)),
222
+ reset: evaluation.reset,
223
+ cause: evaluation.cause,
224
+ ...state
225
+ }));
226
+ } };
227
+ },
228
+ name: `processor-group:${outputs.map((output) => output.path?.join(".") ?? "").join(",")}`
229
+ }));
230
+ }
231
+ const incremental = Object.assign(createIncrementalValue, {
232
+ collection: createIncrementalCollection,
233
+ group: createIncrementalGroup
234
+ });
235
+ //#endregion
236
+ exports.incremental = incremental;
237
+
238
+ //# sourceMappingURL=advanced.cjs.map