doxum 0.1.23 → 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 (66) hide show
  1. package/README.md +198 -102
  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-ULPQmiEs.d.ts → contract-DB8XvNt0.d.cts} +89 -30
  9. package/dist/{contract-SYJFP0lM.d.cts → contract-Dawq6LKS.d.ts} +89 -30
  10. package/dist/definition-CHVS-RDY.d.cts +110 -0
  11. package/dist/definition-DT1NpMul.d.ts +110 -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 +66 -33
  19. package/dist/index.d.ts +66 -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 +5 -3
  43. package/skills/doxum-runtime/SKILL.md +26 -7
  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 +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 +206 -119
  54. package/skills/doxum-runtime/references/projections.zh-CN.md +197 -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
@@ -101,8 +101,8 @@ const history = useHistory(document.history);
101
101
 
102
102
  Document dependency tracking belongs to Core's `select`; the React adapter only
103
103
  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.
104
+ `document.readonly()` returns a capability-stripped `ReadonlyDocument` alias for
105
+ boundaries that must not receive document write APIs.
106
106
 
107
107
  ## Containers And Parsing
108
108
 
@@ -119,9 +119,11 @@ subscription and projection support.
119
119
 
120
120
  Map/table key validators preserve branded string types through access, paths,
121
121
  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)`
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)`
125
127
  validates and copies schema structure while sharing readonly payloads. Strict parsing
126
128
  requires validators for atomic fields; typed in-memory fields can omit them.
127
129
 
@@ -205,6 +207,10 @@ values support reverse replay but are not trusted as local undo data; the runtim
205
207
  captures its actual old state. Revision is a local baseline, not a distributed
206
208
  conflict-resolution protocol.
207
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
+
208
214
  Published ChangeSets are readonly in their entirety, including envelope objects,
209
215
  addresses, transitions and order arrays. The runtime reuses validation of its own
210
216
  publications and normalized storage input by identity; fresh unknown input still
@@ -231,138 +237,223 @@ with `history: false` close the active group.
231
237
 
232
238
  ## Projections
233
239
 
240
+ Projection definitions are lazy and reusable. A `ProjectionRuntime` owns materialization,
241
+ incremental state, scheduling, publication, batching, recovery and Runtime-local inputs.
242
+
234
243
  ```ts
235
244
  import { createProjectionRuntime, derive, input, observe } from 'doxum';
236
245
 
237
246
  const tasks = observe(document, path => path.tasks);
238
247
  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
- });
248
+
249
+ const visible = derive({ tasks, filter }, ({ tasks, filter }) =>
250
+ filter === 'all' ? tasks : new Map([...tasks].filter(([, task]) => !task.done))
251
+ );
243
252
 
244
253
  const runtime = createProjectionRuntime({ onError: console.error });
245
- runtime.get(visible);
246
- 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'));
247
257
  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
- });
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
+
255
270
  stop();
271
+ runtime.dispose();
256
272
  ```
257
273
 
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.
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
265
281
 
266
- Local projections share the parent Runtime's scheduler and materialization owner:
282
+ `derive.keyed` preserves a keyed driver's membership and order while deriving entries
283
+ independently. The simple form is a per-entry selector:
267
284
 
268
285
  ```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))
286
+ const fieldValues = derive.keyed(records, record => record.values[fieldId]);
287
+ ```
288
+
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.
292
+
293
+ Dynamic joins stay explicit in the dependency graph. Declare keyed lookups and ordinary
294
+ projection dependencies by name:
295
+
296
+ ```ts
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)
273
305
  );
274
- scope.get(localVisible);
275
- scope.dispose(); // Releases local producers, state and subscriptions; parent tasks remain.
276
306
  ```
277
307
 
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:
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:
282
318
 
283
319
  ```ts
284
- const overrides = input.collection<string, Task>();
285
- runtime.update(overrides, draft => {
286
- draft.set(taskId, task);
287
- 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);
288
328
  });
289
329
  ```
290
330
 
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(...)`:
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.
314
376
 
315
377
  ```ts
316
378
  import { incremental } from 'doxum/advanced';
317
379
 
318
- const doubled = incremental.collection([tasks], ({ sources, output }) => {
319
- for (const [id, task] of sources[0]) output.set(id, task.value * 2);
320
- });
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:
321
409
 
410
+ ```ts
322
411
  const render = incremental.group(
323
- [tasks],
324
- define => ({
325
- node: {
412
+ { tasks },
413
+ {
414
+ output: define => ({
326
415
  shell: define.collection<string, { readonly title: string }>(),
327
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);
328
427
  },
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
428
  }
342
429
  );
343
430
  ```
344
431
 
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.
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`.
366
457
 
367
458
  See [projection contracts](docs/projections.md).
368
459
 
@@ -373,6 +464,11 @@ runtime. One leader writes synchronously and persists final changes asynchronous
373
464
  followers apply the durable sequence in order. `flush()` waits for persistence.
374
465
  Durability errors do not roll back an already visible commit.
375
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
+
376
472
  The storage format is IndexedDB version 5 with format version 3 records. Earlier
377
473
  databases are rejected without upgrading, deleting or converting their data.
378
474
  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