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.
- package/README.md +208 -102
- package/dist/advanced.cjs +237 -2
- package/dist/advanced.cjs.map +1 -0
- package/dist/advanced.d.cts +90 -2
- package/dist/advanced.d.ts +90 -2
- package/dist/advanced.js +236 -1
- package/dist/advanced.js.map +1 -0
- package/dist/{contract-ULPQmiEs.d.ts → contract-CNR6BS75.d.cts} +216 -30
- package/dist/{contract-SYJFP0lM.d.cts → contract-COgxO5sv.d.ts} +216 -30
- package/dist/definition-CDOuvb5C.d.cts +111 -0
- package/dist/definition-xgk3YNBp.d.ts +111 -0
- package/dist/{driver-yFfvDMX5.js → driver-B5vdPohL.js} +59 -3
- package/dist/driver-B5vdPohL.js.map +1 -0
- package/dist/{driver-BD3DQOW6.cjs → driver-BL91gn7_.cjs} +107 -9
- package/dist/driver-BL91gn7_.cjs.map +1 -0
- package/dist/index.cjs +1637 -703
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -33
- package/dist/index.d.ts +65 -33
- package/dist/index.js +1432 -497
- package/dist/index.js.map +1 -1
- package/dist/local-sync.cjs +69 -103
- package/dist/local-sync.cjs.map +1 -1
- package/dist/local-sync.d.cts +14 -26
- package/dist/local-sync.d.ts +14 -26
- package/dist/local-sync.js +68 -96
- package/dist/local-sync.js.map +1 -1
- package/dist/react.cjs +4 -5
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +6 -8
- package/dist/react.d.ts +6 -8
- package/dist/react.js +4 -5
- package/dist/react.js.map +1 -1
- package/dist/record-B2yRhYsc.js +28 -0
- package/dist/record-B2yRhYsc.js.map +1 -0
- package/dist/record-ChYUi8PP.cjs +45 -0
- package/dist/record-ChYUi8PP.cjs.map +1 -0
- package/dist/scheduler--137L3j-.js +500 -0
- package/dist/scheduler--137L3j-.js.map +1 -0
- package/dist/scheduler-D4VthaaK.cjs +589 -0
- package/dist/scheduler-D4VthaaK.cjs.map +1 -0
- package/package.json +6 -3
- package/skills/doxum-runtime/SKILL.md +32 -7
- package/skills/doxum-runtime/agents/openai.yaml +1 -1
- package/skills/doxum-runtime/references/api.en.md +249 -0
- package/skills/doxum-runtime/references/api.zh-CN.md +242 -0
- package/skills/doxum-runtime/references/guide.en.md +18 -8
- package/skills/doxum-runtime/references/guide.zh-CN.md +14 -6
- package/skills/doxum-runtime/references/invariants.en.md +29 -5
- package/skills/doxum-runtime/references/invariants.zh-CN.md +24 -6
- package/skills/doxum-runtime/references/patterns.en.md +78 -18
- package/skills/doxum-runtime/references/patterns.zh-CN.md +76 -18
- package/skills/doxum-runtime/references/projections.en.md +209 -119
- package/skills/doxum-runtime/references/projections.zh-CN.md +200 -101
- package/dist/advanced-CLJCWkHa.d.cts +0 -176
- package/dist/advanced-CRNHfJBu.js +0 -1224
- package/dist/advanced-CRNHfJBu.js.map +0 -1
- package/dist/advanced-Cle5g3B4.cjs +0 -1517
- package/dist/advanced-Cle5g3B4.cjs.map +0 -1
- package/dist/advanced-d6J61er9.d.ts +0 -176
- package/dist/context-BcLKGfq8.cjs +0 -149
- package/dist/context-BcLKGfq8.cjs.map +0 -1
- package/dist/context-DFEaDUv5.js +0 -84
- package/dist/context-DFEaDUv5.js.map +0 -1
- package/dist/driver-BD3DQOW6.cjs.map +0 -1
- 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
|
-
`
|
|
105
|
-
|
|
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.
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
246
|
-
const
|
|
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
|
-
|
|
249
|
-
runtime.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
`
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
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
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
`
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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(
|
|
319
|
-
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
`
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
|
3
|
-
|
|
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
|