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
package/README.md CHANGED
@@ -33,6 +33,9 @@ document.update(draft => {
33
33
 
34
34
  The root object is the schema identity. Multiple runtimes can share its definition;
35
35
  their data, revisions and subscriptions remain independent. Definitions are immutable.
36
+ `Schema` and `ObjectSchema` are portable public handles: a package can export an
37
+ inferred schema directly and emit its own declarations without writing `ReturnType`
38
+ aliases or naming Doxum's internal node representation.
36
39
 
37
40
  `object()` exposes editable structure with a closed schema: undeclared own properties,
38
41
  including symbols and non-enumerable properties, are rejected at input boundaries.
@@ -101,8 +104,8 @@ const history = useHistory(document.history);
101
104
 
102
105
  Document dependency tracking belongs to Core's `select`; the React adapter only
103
106
  subscribes to the resulting `Readable`. The core has no React dependency.
104
- `asReadable(document)` removes write capabilities while retaining selection,
105
- subscription and projection support.
107
+ `document.readonly()` returns a capability-stripped `ReadonlyDocument` alias for
108
+ boundaries that must not receive document write APIs.
106
109
 
107
110
  ## Containers And Parsing
108
111
 
@@ -119,9 +122,11 @@ subscription and projection support.
119
122
 
120
123
  Map/table key validators preserve branded string types through access, paths,
121
124
  projection keys and impact queries. Validators are pure, synchronous functions or
122
- Standard Schema v1 validators. They receive the original value, must not mutate it,
123
- and their successful output is ignored. Perform transformations before calling
124
- Doxum; it does not detect validator mutation or conversion. `parse(model, unknown)`
125
+ Standard Schema v1 validators. Function validators are predicates/assertions: return
126
+ `true` or `undefined` for success, `false` for rejection, or throw from an assertion.
127
+ Standard Schema validation must return the original value by identity; transformed
128
+ outputs are rejected. Validators must not mutate their input. Perform transformations
129
+ before calling Doxum. `parse(model, unknown)`
125
130
  validates and copies schema structure while sharing readonly payloads. Strict parsing
126
131
  requires validators for atomic fields; typed in-memory fields can omit them.
127
132
 
@@ -205,6 +210,10 @@ values support reverse replay but are not trusted as local undo data; the runtim
205
210
  captures its actual old state. Revision is a local baseline, not a distributed
206
211
  conflict-resolution protocol.
207
212
 
213
+ Local/system apply may opt out of history with `history: false`. Remote apply uses
214
+ `{ expectedRevision, source: 'remote' }` and always invalidates local history; it has
215
+ no independent history option.
216
+
208
217
  Published ChangeSets are readonly in their entirety, including envelope objects,
209
218
  addresses, transitions and order arrays. The runtime reuses validation of its own
210
219
  publications and normalized storage input by identity; fresh unknown input still
@@ -231,138 +240,230 @@ with `history: false` close the active group.
231
240
 
232
241
  ## Projections
233
242
 
243
+ Projection definitions are lazy and reusable. A `ProjectionRuntime` owns materialization,
244
+ incremental state, scheduling, publication, batching, recovery and Runtime-local inputs.
245
+
234
246
  ```ts
235
247
  import { createProjectionRuntime, derive, input, observe } from 'doxum';
236
248
 
237
249
  const tasks = observe(document, path => path.tasks);
238
250
  const filter = input<'all' | 'open'>('all');
239
- const visible = derive([tasks, filter], (all, mode) => {
240
- if (mode === 'all') return all;
241
- return new Map([...all].filter(([, task]) => !task.done));
242
- });
251
+
252
+ const visible = derive({ tasks, filter }, ({ tasks, filter }) =>
253
+ filter === 'all' ? tasks : new Map([...tasks].filter(([, task]) => !task.done))
254
+ );
243
255
 
244
256
  const runtime = createProjectionRuntime({ onError: console.error });
245
- runtime.get(visible);
246
- const selected = runtime.readable(visible, tasks => tasks.get('a'));
257
+
258
+ const current = runtime.read(visible);
259
+ const selected = runtime.select(visible, tasks => tasks.get('a'));
247
260
  const stop = selected.subscribe(() => selected.current());
248
- runtime.set(filter, 'open');
249
- runtime.batch({ cause: { action: 'refresh' } }, () => {
250
- runtime.set(filter, 'all');
251
- document.update(draft => {
252
- draft.title = 'Updated';
253
- });
254
- });
261
+
262
+ runtime.update(filter, 'open');
263
+ runtime.batch(
264
+ () => {
265
+ runtime.update(filter, 'all');
266
+ document.update(draft => {
267
+ draft.title = 'Updated';
268
+ });
269
+ },
270
+ { cause: { action: 'refresh' } }
271
+ );
272
+
255
273
  stop();
274
+ runtime.dispose();
256
275
  ```
257
276
 
258
- Root projection declarations are lazy and reusable; scoped declarations are
259
- lazy but tied to their scope. The Runtime owns `get`,
260
- `readable`, scalar `set`, keyed `update`, `batch`, `scope` and `dispose`;
261
- materialization, incremental state, publication and recovery stay inside that
262
- one Runtime. Collection values are
263
- immutable `ReadonlyMap`-like snapshots, while a `Readable` owns selector
264
- tracking, equality and subscription lifecycle.
277
+ The stable vocabulary is `read` for a synchronous current value, `select` for a
278
+ `Readable`, and `observe` for declaring a lazy source boundary. `ProjectionRuntime`
279
+ exposes `read`, `select`, `update`, `batch`, `scope` and `dispose`.
280
+ `ProjectionScope` mirrors `read`, `select`, `update`, `batch` and `dispose`, plus
281
+ `own(definitionOrTree)` for lifecycle ownership of lazy projection definitions.
282
+
283
+ ### Keyed projection
265
284
 
266
- Local projections share the parent Runtime's scheduler and materialization owner:
285
+ `derive.keyed` preserves a keyed driver's membership and order while deriving entries
286
+ independently. The simple form is a per-entry selector:
267
287
 
268
288
  ```ts
269
- const scope = runtime.scope();
270
- const localFilter = scope.input<'all' | 'open'>('all');
271
- const localVisible = scope.derive([tasks, localFilter], (tasks, filter) =>
272
- filter === 'all' ? tasks : new Map([...tasks].filter(([, task]) => !task.done))
289
+ const fieldValues = derive.keyed(records, record => record.values[fieldId]);
290
+ ```
291
+
292
+ The output keeps one key per source entry. The selector runs only for affected keys,
293
+ and the optional equality function suppresses `updated` output transitions when the
294
+ derived value is equal.
295
+
296
+ All keyed projection producers use the public `KeyedProjection<K,V>` handle, including
297
+ collection `observe`, `derive.keyed`, `incremental.collection`, and collection leaves
298
+ from `incremental.group`. `CollectionInput<K,V>` is the writable Runtime-local form of
299
+ the same keyed projection capability. These handles can be exported from downstream
300
+ packages without exposing processor change metadata.
301
+
302
+ Dynamic joins stay explicit in the dependency graph. Declare keyed lookups and ordinary
303
+ projection dependencies by name:
304
+
305
+ ```ts
306
+ const cardContent = derive.keyed(
307
+ items,
308
+ {
309
+ record: { source: records, key: item => item.recordId },
310
+ view: activeView,
311
+ fields: visibleFields,
312
+ },
313
+ (item, itemId, { record, view, fields }) => renderCard(itemId, item, record, view, fields)
273
314
  );
274
- scope.get(localVisible);
275
- scope.dispose(); // Releases local producers, state and subscriptions; parent tasks remain.
276
315
  ```
277
316
 
278
- For Runtime-local keyed state, `input.collection` publishes the same exact
279
- `CollectionChange` as document collections. One `update` edits one or many keys
280
- atomically; the enclosing Runtime batch coalesces accepted edits into one net
281
- change:
317
+ For `{ source, key }` dependencies, the Runtime owns output-key → source-key bindings
318
+ and reverse invalidation. A missing source entry resolves to `undefined` but remains
319
+ bound, so adding it later invalidates the dependent output keys. An ordinary projection
320
+ dependency invalidates the driver key set when its value changes. Processor code never
321
+ performs imperative Runtime reads to discover dependencies.
322
+
323
+ ### Runtime-local keyed state
324
+
325
+ Use `input.collection` for selection, expanded state, local overrides and other
326
+ Runtime-local keyed application/UI state:
282
327
 
283
328
  ```ts
284
- const overrides = input.collection<string, Task>();
285
- runtime.update(overrides, draft => {
286
- draft.set(taskId, task);
287
- draft.remove(oldTaskId);
329
+ const selection = input.collection<RowId, SelectionState>(
330
+ new Map(),
331
+ (previous, next) => previous.selected === next.selected
332
+ );
333
+
334
+ runtime.update(selection, draft => {
335
+ draft.set(rowId, { selected: true });
336
+ draft.remove(previousRowId);
288
337
  });
289
338
  ```
290
339
 
291
- `observe` is the single source boundary for documents, Doxum `Readable` values,
292
- and eventful external sources. External sources declare `kind: 'value'` or
293
- `kind: 'collection'`; collection invalidation remains keyed internally. Schema
294
- `map`, `table`, and `list(field, { keyOf })` paths are all observed as keyed
295
- collection projections. Trees expose structural observation without a second
296
- projection protocol: `path.tree.rootId` is a scalar source,
297
- `path.tree.nodes` is a keyed collection source, and
298
- `path.tree.nodes.item(id)` is a single-node value source. A schema `list` uses its
299
- `keyOf` identity and publishes in document order; ordinary array-valued `field(...)`
300
- nodes remain scalar values.
301
-
302
- Document projection sources route committed groups directly to their observed
303
- targets, merge affected locations for the current projection batch, then combine the
304
- previously published snapshot with the canonical final value. A touched tree node
305
- replaces only that node structure and the necessary ancestor containers; unchanged
306
- tree-node snapshots keep their identity. Whole aggregate tree values still use the
307
- public `{ rootId?, nodes: Record }` shape, so changing a node may shallow-copy the
308
- `nodes` record, while native `tree.nodes` observation remains keyed and touches only
309
- changed nodes.
310
-
311
- For retained state, reverse indexes and keyed patches, use the isolated advanced
312
- entry point. Whole-value processors use `incremental(...)`; keyed collection
313
- processors use `incremental.collection(...)`:
340
+ Its state is owned by the `ProjectionRuntime`, not the document, history or persistence.
341
+ Updates are synchronous and atomic. It publishes exact keyed `CollectionChange`
342
+ transitions and preserves order. The optional per-entry equality defaults to `Object.is`
343
+ and suppresses equivalent `set` operations. Callback and equality failures leave both
344
+ the published value and the next draft unchanged.
345
+
346
+ The processor-facing `CollectionChange` type is exported by `doxum/advanced`.
347
+
348
+ A root input definition can be materialized independently by several runtimes. Wrap any
349
+ definition in `scope.own(...)` when its lifecycle belongs to one scope; scalar inputs,
350
+ keyed inputs, derives and advanced group output trees all use the same ownership API.
351
+ Ownership must be assigned before that definition is first materialized as a root.
352
+
353
+ ### Sources and scopes
354
+
355
+ `observe` adapts documents, Doxum `Readable` values and exported external source
356
+ contracts. Document `map`, `table` and `list(field, { keyOf })` paths become keyed
357
+ collection projections. Trees reuse the same model: `path.tree.rootId` is scalar,
358
+ `path.tree.nodes` is keyed and `path.tree.nodes.item(id)` is a single-node value source.
359
+
360
+ ```ts
361
+ const scope = runtime.scope();
362
+ const localFilter = scope.own(input<'all' | 'open'>('all'));
363
+ const localVisible = scope.own(
364
+ derive({ tasks, filter: localFilter }, ({ tasks, filter }) =>
365
+ filter === 'all' ? tasks : new Map([...tasks].filter(([, task]) => !task.done))
366
+ )
367
+ );
368
+
369
+ scope.read(localVisible);
370
+ scope.dispose();
371
+ ```
372
+
373
+ A scope shares its parent Runtime's scheduler and source materialization but owns the
374
+ lifetime of definitions passed to `scope.own`, their materialized producers, state and
375
+ subscriptions. Scoped definitions may depend on root definitions; root definitions and
376
+ sibling scopes cannot depend on scoped definitions. A materialized root definition cannot
377
+ later be converted into a scoped definition.
378
+
379
+ ### Advanced processors
380
+
381
+ Use `doxum/advanced` only when pure `derive` / `derive.keyed` cannot express retained
382
+ state, cross-key indexes, or direct incremental patches. Dependencies are always named.
383
+ The processor definition is closed and always owns `process()`. Add `state()` only when
384
+ the processor actually needs Runtime-owned retained state.
314
385
 
315
386
  ```ts
316
387
  import { incremental } from 'doxum/advanced';
317
388
 
318
- const doubled = incremental.collection([tasks], ({ sources, output }) => {
319
- for (const [id, task] of sources[0]) output.set(id, task.value * 2);
320
- });
389
+ const doubled = incremental.collection(
390
+ { tasks },
391
+ {
392
+ state: () => ({ initialized: false }),
393
+ process: ({ values, changes, output, state, reset }) => {
394
+ if (reset) {
395
+ for (const [id, task] of values.tasks) {
396
+ output.set(id, task.value * 2);
397
+ }
398
+ output.order([...values.tasks.keys()]);
399
+ state.initialized = true;
400
+ return;
401
+ }
402
+
403
+ const change = changes.tasks;
404
+ if (!change || change.kind === 'reset') return;
405
+
406
+ for (const entry of change.added) output.set(entry.key, entry.after.value * 2);
407
+ for (const entry of change.updated) output.set(entry.key, entry.after.value * 2);
408
+ for (const entry of change.removed) output.remove(entry.key);
409
+ if (change.order) output.order([...values.tasks.keys()]);
410
+ },
411
+ }
412
+ );
413
+ ```
414
+
415
+ `incremental(...)` produces one scalar value projection.
416
+ `incremental.collection(...)` exposes keyed `previous`, `next` and `output`.
417
+ `incremental.group(...)` runs one processor for several atomic output leaves:
321
418
 
419
+ ```ts
322
420
  const render = incremental.group(
323
- [tasks],
324
- define => ({
325
- node: {
421
+ { tasks },
422
+ {
423
+ output: define => ({
326
424
  shell: define.collection<string, { readonly title: string }>(),
327
425
  content: define.collection<string, string>(),
426
+ count: define.value<number>(),
427
+ }),
428
+ state: () => ({ renders: 0 }),
429
+ process: ({ values, output, state }) => {
430
+ state.renders++;
431
+ for (const [id, task] of values.tasks) {
432
+ output.shell.set(id, { title: task.title });
433
+ output.content.set(id, task.title);
434
+ }
435
+ output.count.set(values.tasks.size);
328
436
  },
329
- labels: define.collection<string, string>(),
330
- chrome: define.value<{ readonly count: number }>(),
331
- revision: define.value<number>(),
332
- }),
333
- ({ sources, outputs }) => {
334
- for (const [id, task] of sources[0]) {
335
- outputs.node.shell.set(id, { title: task.title });
336
- outputs.node.content.set(id, task.title);
337
- outputs.labels.set(id, task.title);
338
- }
339
- outputs.chrome.set({ count: sources[0].size });
340
- outputs.revision.set(sources[0].size);
341
437
  }
342
438
  );
343
439
  ```
344
440
 
345
- Incremental processors receive a dependency-aligned `changes` tuple. Collection
346
- entries carry `added`/`updated`/`removed` transitions with complete
347
- `before`/`after` values, so a processor can patch indexes without rescanning the
348
- collection; scalar dependencies use `undefined` and the initial collection build
349
- reports `{ kind: 'reset' }`.
350
- `incremental.group` runs one processor for several named value and keyed collection
351
- outputs. `define.collection<K, V>(equality?)` creates a keyed leaf;
352
- `define.value<T>(equality?)` creates an ordinary scalar projection leaf. On an
353
- initial build or rebuild every value leaf must be set; on an incremental update an
354
- untouched value leaf keeps its published value. Its static nested namespace is only
355
- API organization: every leaf is an ordinary `Projection`, all changed leaves publish
356
- atomically in one causal settle, and downstream processors depend directly on those
357
- leaves. All leaves reference one producer, so reading any leaf materializes that
358
- producer once; split groups when outputs do not share computation or atomicity
359
- requirements. There is no group runtime, output event bus, or per-output runtime.
360
-
361
- In React, `useProjection(projection)` reads a value and
362
- `useProjection(projection, selector, equality?)` tracks keyed reads such as
363
- `tasks => tasks.get(taskId)`. Unrelated key changes do not execute that selector;
364
- equality only filters a selector result after a related change. `useInput(input)`
365
- returns the current value and its Runtime-local setter.
441
+ `define.collection<K,V>(equality?)` and `define.value<T>(equality?)` are output
442
+ declaration methods available only inside `output`. The returned object tree keeps the
443
+ same shape with `KeyedProjection<K,V>` collection leaves and `Projection<T>` value
444
+ leaves. Value leaves must be initialized on
445
+ initial build or Runtime recovery; untouched value leaves retain their value during a
446
+ normal incremental run.
447
+
448
+ `values` and `changes` use the dependency names. Collection changes are exact
449
+ `added` / `updated` / `removed` transitions plus optional `order`; scalar dependency
450
+ changes are `undefined`. `reset` means the processor must reconcile from current source
451
+ values. Ordinary source resets preserve retained state when one exists. If a processor
452
+ faults, the Runtime owns recovery, recreates declared state, and performs a reset
453
+ evaluation. Stateless processors use the same recovery path without a state object.
454
+
455
+ ### React
456
+
457
+ ```ts
458
+ const task = useProjection(tasks, tasks => tasks.get(taskId), equality);
459
+ const [mode, setMode] = useInput(filter);
460
+ const [selectedRows, updateSelectedRows] = useInput(selection);
461
+ ```
462
+
463
+ `useProjection(projection, selector, equality?)` uses the same keyed selector semantics
464
+ as Core. `useInput` handles both scalar `Input<T>` and `CollectionInput<K,V>`; collection
465
+ updates receive a keyed draft callback. `ProjectionProvider` accepts either a
466
+ `ProjectionRuntime` or `ProjectionScope`.
366
467
 
367
468
  See [projection contracts](docs/projections.md).
368
469
 
@@ -373,6 +474,11 @@ runtime. One leader writes synchronously and persists final changes asynchronous
373
474
  followers apply the durable sequence in order. `flush()` waits for persistence.
374
475
  Durability errors do not roll back an already visible commit.
375
476
 
477
+ Operational failures use one `LocalSyncError` contract. Error states and `onError`
478
+ receive that same typed error and preserve an underlying `cause` when one exists.
479
+ Exceptions thrown by consumers of `state.subscribe` stay outside synchronization
480
+ fault state and are not reported through operational `onError`.
481
+
376
482
  The storage format is IndexedDB version 5 with format version 3 records. Earlier
377
483
  databases are rejected without upgrading, deleting or converting their data.
378
484
  This JSON adapter rejects non-JSON atomic values. Network collaboration and
package/dist/advanced.cjs CHANGED
@@ -1,3 +1,238 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_advanced = require("./advanced-Cle5g3B4.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