doxum 0.1.6 → 0.1.7

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 (45) hide show
  1. package/README.md +38 -13
  2. package/dist/{contract-I99DUMbN.d.ts → contract-DNZ4D53r.d.ts} +87 -76
  3. package/dist/{contract-uYJTmT8o.d.cts → contract-j3SLGAwh.d.cts} +87 -76
  4. package/dist/index.cjs +221 -349
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +3 -3
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.js +209 -327
  9. package/dist/index.js.map +1 -1
  10. package/dist/{integration-xupBIKjU.js → integration-B56u1l9V.js} +2 -2
  11. package/dist/integration-B56u1l9V.js.map +1 -0
  12. package/dist/{integration-Bs3pOk65.cjs → integration-CZwCwFBS.cjs} +2 -2
  13. package/dist/integration-CZwCwFBS.cjs.map +1 -0
  14. package/dist/integration.cjs +10 -2
  15. package/dist/integration.d.cts +3 -3
  16. package/dist/integration.d.ts +3 -3
  17. package/dist/integration.js +3 -3
  18. package/dist/local-sync.cjs +56 -17
  19. package/dist/local-sync.cjs.map +1 -1
  20. package/dist/local-sync.d.cts +2 -2
  21. package/dist/local-sync.d.ts +2 -2
  22. package/dist/local-sync.js +56 -17
  23. package/dist/local-sync.js.map +1 -1
  24. package/dist/react.cjs +27 -19
  25. package/dist/react.cjs.map +1 -1
  26. package/dist/react.js +27 -19
  27. package/dist/react.js.map +1 -1
  28. package/dist/{runtime-CRijXjYp.cjs → runtime-0mOFbe_H.cjs} +899 -451
  29. package/dist/runtime-0mOFbe_H.cjs.map +1 -0
  30. package/dist/{runtime-CUcvhFNP.d.ts → runtime-B22tuj9A.d.cts} +18 -20
  31. package/dist/{runtime-C_vKHmhy.d.cts → runtime-BFmhzPpZ.d.ts} +18 -20
  32. package/dist/{runtime-BJRXF0gq.js → runtime-DF1q9Gje.js} +823 -429
  33. package/dist/runtime-DF1q9Gje.js.map +1 -0
  34. package/package.json +1 -1
  35. package/skills/doxum-runtime/SKILL.md +2 -1
  36. package/skills/doxum-runtime/references/guide.en.md +18 -18
  37. package/skills/doxum-runtime/references/guide.zh-CN.md +12 -17
  38. package/skills/doxum-runtime/references/invariants.en.md +1 -1
  39. package/skills/doxum-runtime/references/invariants.zh-CN.md +1 -1
  40. package/skills/doxum-runtime/references/patterns.en.md +9 -20
  41. package/skills/doxum-runtime/references/patterns.zh-CN.md +5 -18
  42. package/dist/integration-Bs3pOk65.cjs.map +0 -1
  43. package/dist/integration-xupBIKjU.js.map +0 -1
  44. package/dist/runtime-BJRXF0gq.js.map +0 -1
  45. package/dist/runtime-CRijXjYp.cjs.map +0 -1
@@ -3,10 +3,19 @@ import { i as isRecord$1, t as cloneValue, u as profile } from "./ownership-CduR
3
3
  //#region core/src/schema.ts
4
4
  const node = (value) => Object.freeze(value);
5
5
  const field = () => node({ kind: "field" });
6
- const optional = (value) => node({
7
- ...value,
8
- optional: true
9
- });
6
+ const optional = (value) => {
7
+ if (![
8
+ "field",
9
+ "variant",
10
+ "dict",
11
+ "list",
12
+ "tree"
13
+ ].includes(value.kind)) throw new TypeError("Only field, variant, dict, list and tree nodes support optional presence.");
14
+ return node({
15
+ ...value,
16
+ optional: true
17
+ });
18
+ };
10
19
  const object = (shape) => node({
11
20
  kind: "object",
12
21
  shape
@@ -16,10 +25,6 @@ const variant = (tag, variants) => node({
16
25
  tag,
17
26
  variants
18
27
  });
19
- const single = (value) => node({
20
- kind: "single",
21
- value
22
- });
23
28
  const table = (value) => node({
24
29
  kind: "table",
25
30
  value
@@ -28,26 +33,55 @@ const map = (value) => node({
28
33
  kind: "map",
29
34
  value
30
35
  });
31
- const record = () => node({ kind: "record" });
32
36
  const dict = () => node({ kind: "dict" });
33
37
  const list = (config) => ({
34
38
  kind: "list",
35
39
  keyOf: config.keyOf
36
40
  });
37
41
  const tree = () => node({ kind: "tree" });
38
- const pathProxy = (address) => new Proxy({ address }, { get: (_target, property) => {
39
- if (property === "address") return address;
40
- if (property === "item") return (id) => pathProxy([...address, id]);
41
- if (typeof property === "symbol") return void 0;
42
- return pathProxy([...address, property]);
43
- } });
42
+ const paths = /* @__PURE__ */ new WeakMap();
43
+ const collectionSchemas = /* @__PURE__ */ new WeakMap();
44
+ const collectionSchema = (selector) => {
45
+ const node = collectionSchemas.get(selector);
46
+ if (!node) throw new TypeError("Expected a schema-owned collection selector.");
47
+ return node;
48
+ };
49
+ const pathProxy = (nodes, address, owner) => {
50
+ const proxy = new Proxy({}, { get: (_target, property) => {
51
+ if (typeof property !== "string") return void 0;
52
+ if (nodes.every((node) => node.kind === "table" || node.kind === "map") && property === "item") return (id) => {
53
+ if (typeof id !== "string") throw new TypeError("Collection keys must be strings.");
54
+ return pathProxy(nodes.map((node) => node.value), [...address, id], owner);
55
+ };
56
+ const children = nodes.flatMap((node) => {
57
+ if (node.kind === "object") return node.shape[property] ? [node.shape[property]] : [];
58
+ if (node.kind !== "variant") return [];
59
+ if (property === node.tag) return [field()];
60
+ return Object.values(node.variants).flatMap((branch) => branch.shape[property] ? [branch.shape[property]] : []);
61
+ });
62
+ if (!children.length) throw new TypeError(`Invalid schema path segment: ${property}`);
63
+ return pathProxy(children, [...address, property], owner);
64
+ } });
65
+ paths.set(proxy, {
66
+ nodes,
67
+ address,
68
+ owner
69
+ });
70
+ return proxy;
71
+ };
44
72
  const select = (owner, kind, pick) => {
45
- const selected = pick(pathProxy([]));
46
- return Object.freeze({
73
+ const scope = {};
74
+ const value = pick(pathProxy([object(owner.shape)], [], scope));
75
+ const selected = typeof value === "object" && value !== null ? paths.get(value) : void 0;
76
+ if (!selected || selected.owner !== scope) throw new TypeError("Selector must return a path from its callback.");
77
+ if (kind === "collection" && !selected.nodes.every((node) => node.kind === "table" || node.kind === "map")) throw new TypeError("Collection selectors require a table or map path.");
78
+ const result = Object.freeze({
47
79
  kind,
48
80
  schema: owner,
49
- address: Object.freeze([...selected.address ?? []])
81
+ address: Object.freeze([...selected.address])
50
82
  });
83
+ if (kind === "collection") collectionSchemas.set(result, selected.nodes[0]);
84
+ return result;
51
85
  };
52
86
  const schema = (shape) => {
53
87
  const result = {
@@ -79,11 +113,6 @@ const hashText = (value, seed) => {
79
113
  };
80
114
  const appendAddressHash = (hash, segment) => hashText(segment, hashText("/", hash));
81
115
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
82
- const unwrap = (node) => {
83
- let current = node;
84
- while (current?.kind === "single") current = current.value;
85
- return current;
86
- };
87
116
  const variantNode = (node, value) => {
88
117
  const tag = isRecord(value) && typeof value[node.tag] === "string" ? value[node.tag] : void 0;
89
118
  return node.variants[String(tag ?? Object.keys(node.variants)[0] ?? "")];
@@ -93,12 +122,12 @@ const schemaRoot$1 = (schema) => ({
93
122
  shape: schema.shape
94
123
  });
95
124
  const step = (nodeInput, value, segment) => {
96
- let node = unwrap(nodeInput);
125
+ let node = nodeInput;
97
126
  if (!node) return {
98
127
  node: void 0,
99
128
  dynamic: false
100
129
  };
101
- if (node.kind === "variant") node = unwrap(variantNode(node, value));
130
+ if (node.kind === "variant") node = variantNode(node, value);
102
131
  if (!node) return {
103
132
  node: void 0,
104
133
  dynamic: false
@@ -111,7 +140,7 @@ const step = (nodeInput, value, segment) => {
111
140
  node: node.value,
112
141
  dynamic: true
113
142
  };
114
- if (node.kind === "record" || node.kind === "dict" || node.kind === "list" || node.kind === "tree") return {
143
+ if (node.kind === "dict" || node.kind === "list" || node.kind === "tree") return {
115
144
  node,
116
145
  dynamic: true
117
146
  };
@@ -162,7 +191,7 @@ const nodeAt = (schema, address, document) => {
162
191
  value = readSegment(value, segment);
163
192
  if (!node) return void 0;
164
193
  }
165
- return unwrap(node);
194
+ return node;
166
195
  };
167
196
  const readSegment = (value, segment) => {
168
197
  if (!isRecord(value) && !Array.isArray(value)) return void 0;
@@ -276,25 +305,93 @@ const debugKey = (address) => {
276
305
  //#endregion
277
306
  //#region core/src/impact-target.ts
278
307
  var impact_target_exports = /* @__PURE__ */ __exportAll({
279
- address: () => address,
308
+ address: () => address$1,
280
309
  belongs: () => belongs,
281
310
  bucket: () => bucket,
282
311
  id: () => id,
283
312
  same: () => same
284
313
  });
285
- const address = (target) => "at" in target ? target.at : target.address;
314
+ const address$1 = (target) => "at" in target ? target.at : target.address;
286
315
  const id = (target) => target.kind === "collection" && "id" in target ? target.id : void 0;
287
316
  const belongs = (target, schema) => !("schema" in target) || target.schema === schema;
288
317
  const same = (left, right) => {
289
318
  if (left.kind !== right.kind) return false;
290
319
  if ("schema" in left && "schema" in right && left.schema !== right.schema) return false;
291
- const leftAddress = address(left);
292
- const rightAddress = address(right);
320
+ const leftAddress = address$1(left);
321
+ const rightAddress = address$1(right);
293
322
  if (leftAddress.length !== rightAddress.length) return false;
294
323
  for (let index = 0; index < leftAddress.length; index += 1) if (leftAddress[index] !== rightAddress[index]) return false;
295
324
  return left.kind !== "collection" || id(left) === id(right);
296
325
  };
297
- const bucket = (target) => address(target)[0];
326
+ const bucket = (target) => address$1(target)[0];
327
+ //#endregion
328
+ //#region core/src/runtime/contract.ts
329
+ var DocumentReentrancyError = class extends Error {
330
+ constructor() {
331
+ super("Document writes cannot be re-entered during an update or notification.");
332
+ this.name = "DocumentReentrancyError";
333
+ }
334
+ };
335
+ var DocumentDisposedError = class extends Error {
336
+ constructor() {
337
+ super("Doxum runtime has been disposed.");
338
+ this.name = "DocumentDisposedError";
339
+ }
340
+ };
341
+ //#endregion
342
+ //#region core/src/mutation/anchor.ts
343
+ const length = (keys) => keys.length;
344
+ const keyAt = (keys, index) => Array.isArray(keys) ? keys[index] : keys.at(index);
345
+ const keyIndex = (keys, key) => Array.isArray(keys) ? keys.indexOf(key) : keys.index(key);
346
+ const keys = (values, keyOf) => ({
347
+ length: values.length,
348
+ at: (index) => index >= 0 && index < values.length ? keyOf(values[index]) : void 0,
349
+ index: (key) => {
350
+ for (let index = 0; index < values.length; index += 1) if (keyOf(values[index]) === key) return index;
351
+ return -1;
352
+ }
353
+ });
354
+ const index = (keys, anchor) => {
355
+ if (!anchor) return length(keys);
356
+ if ("at" in anchor) return anchor.at === "start" ? 0 : length(keys);
357
+ if ("before" in anchor) return keyIndex(keys, anchor.before);
358
+ return keyIndex(keys, anchor.after) + 1;
359
+ };
360
+ const at = (keys, position) => position <= 0 ? { at: "start" } : { after: keyAt(keys, position - 1) };
361
+ const valid = (keys, anchor) => {
362
+ if (!anchor) return true;
363
+ if (typeof anchor !== "object" || anchor === null) return false;
364
+ if (Number("at" in anchor) + Number("before" in anchor) + Number("after" in anchor) !== 1) return false;
365
+ if ("at" in anchor) return anchor.at === "start" || anchor.at === "end";
366
+ return keyIndex(keys, "before" in anchor ? anchor.before : anchor.after) >= 0;
367
+ };
368
+ const afterRemove = (keys, removedIndex, anchor) => {
369
+ const position = index(keys, anchor);
370
+ return position > removedIndex ? position - 1 : position;
371
+ };
372
+ const validPositions = (length, positions) => {
373
+ let previous = -1;
374
+ for (let index = 0; index < positions.length; index += 1) {
375
+ const position = positions[index];
376
+ if (!Number.isInteger(position) || position < 0 || position <= previous || position > length + index) return false;
377
+ previous = position;
378
+ }
379
+ return true;
380
+ };
381
+ const restore = (keys, inserted, positions) => {
382
+ const currentLength = keys.length;
383
+ if (inserted.length !== positions.length || !validPositions(currentLength, positions)) throw new Error("Ordered restore positions are invalid.");
384
+ let readIndex = currentLength - 1;
385
+ let insertedIndex = inserted.length - 1;
386
+ keys.length = currentLength + inserted.length;
387
+ for (let writeIndex = keys.length - 1; writeIndex >= 0; writeIndex -= 1) if (insertedIndex >= 0 && positions[insertedIndex] === writeIndex) {
388
+ keys[writeIndex] = inserted[insertedIndex];
389
+ insertedIndex -= 1;
390
+ } else {
391
+ keys[writeIndex] = keys[readIndex];
392
+ readIndex -= 1;
393
+ }
394
+ };
298
395
  //#endregion
299
396
  //#region core/src/access/reader.ts
300
397
  const schemaRoot = (schema) => object(schema.shape);
@@ -325,12 +422,40 @@ const readerFor = (node, context, address = []) => {
325
422
  collectValue(context, address);
326
423
  return readAt(context, address);
327
424
  } };
328
- if (node.kind === "dict" || node.kind === "record") return { get: () => {
329
- collectValue(context, address);
330
- profile.reader.structuralSnapshot();
331
- return cloneValue(readAt(context, address), "reader");
332
- } };
425
+ if (node.kind === "dict") return {
426
+ get: (key) => {
427
+ collectValue(context, address);
428
+ const value = readAt(context, address);
429
+ return cloneValue(isRecord$1(value) && Object.prototype.hasOwnProperty.call(value, key) ? value[key] : void 0, "reader");
430
+ },
431
+ has: (key) => {
432
+ collectValue(context, address);
433
+ const value = readAt(context, address);
434
+ return isRecord$1(value) && Object.prototype.hasOwnProperty.call(value, key);
435
+ },
436
+ keys: () => {
437
+ collectValue(context, address);
438
+ const value = readAt(context, address);
439
+ return isRecord$1(value) ? Object.keys(value) : [];
440
+ },
441
+ values: () => {
442
+ collectValue(context, address);
443
+ profile.reader.structuralSnapshot();
444
+ return cloneValue(readAt(context, address) ?? {}, "reader");
445
+ }
446
+ };
333
447
  if (node.kind === "list") return {
448
+ get: (key) => {
449
+ collectValue(context, address);
450
+ const value = readAt(context, address);
451
+ if (!Array.isArray(value)) return void 0;
452
+ return cloneValue(value[keys(value, node.keyOf).index(key)], "reader");
453
+ },
454
+ has: (key) => {
455
+ collectValue(context, address);
456
+ const value = readAt(context, address);
457
+ return Array.isArray(value) && keys(value, node.keyOf).index(key) >= 0;
458
+ },
334
459
  values: () => {
335
460
  collectValue(context, address);
336
461
  const value = readAt(context, address);
@@ -408,14 +533,10 @@ const readerFor = (node, context, address = []) => {
408
533
  }
409
534
  };
410
535
  }
411
- if (node.kind === "single") return readerFor(node.value, context, address);
412
- if (node.kind === "variant") return new Proxy({}, { get: (_target, property) => {
413
- if (typeof property === "symbol") return void 0;
414
- const value = readAt(context, address);
415
- const tag = isRecord$1(value) && typeof value[node.tag] === "string" ? String(value[node.tag]) : void 0;
416
- const child = (tag ? node.variants[tag] : void 0)?.shape[property];
417
- return child ? readerFor(child, context, [...address, property]) : void 0;
418
- } });
536
+ if (node.kind === "variant") return { get: () => {
537
+ collectValue(context, address);
538
+ return cloneValue(readAt(context, address), "reader");
539
+ } };
419
540
  let children;
420
541
  return new Proxy({}, { get: (_target, property) => {
421
542
  if (typeof property === "symbol") return void 0;
@@ -437,19 +558,191 @@ const documentReader = (schema, root, active, dependencies) => {
437
558
  });
438
559
  };
439
560
  //#endregion
440
- //#region core/src/runtime/contract.ts
441
- var DocumentReentrancyError = class extends Error {
442
- constructor() {
443
- super("Document writes cannot be re-entered during an update or notification.");
444
- this.name = "DocumentReentrancyError";
561
+ //#region core/src/mutation/footprint.ts
562
+ const sameAddress = (left, right) => left.length === right.length && left.every((segment, index) => segment === right[index]);
563
+ const containsAddress = (parent, child) => parent.length <= child.length && parent.every((segment, index) => segment === child[index]);
564
+ const address = (value) => Array.isArray(value) && value.every((segment) => typeof segment === "string") ? Object.freeze(value.slice()) : void 0;
565
+ const targetKey = (target) => {
566
+ const at = target.at.join("\0");
567
+ if (target.kind === "dictionary-key" || target.kind === "list-item") return `${target.kind}:${at}:${target.key}`;
568
+ if (target.kind === "entity" || target.kind === "tree-node") return `${target.kind}:${at}:${target.id}`;
569
+ return `${target.kind}:${at}`;
570
+ };
571
+ const freezeTarget = (target) => Object.freeze({
572
+ ...target,
573
+ at: Object.freeze(target.at.slice())
574
+ });
575
+ const add = (targets, target) => {
576
+ const stable = freezeTarget(target);
577
+ targets.set(targetKey(stable), stable);
578
+ };
579
+ const commandFootprint = (operations) => {
580
+ const targets = /* @__PURE__ */ new Map();
581
+ for (const operation of operations) switch (operation.type) {
582
+ case "field.set":
583
+ case "field.clear":
584
+ case "value.clear":
585
+ case "variant.replace":
586
+ add(targets, {
587
+ kind: "value",
588
+ at: operation.at
589
+ });
590
+ break;
591
+ case "dict.replace":
592
+ add(targets, {
593
+ kind: "dictionary",
594
+ at: operation.at
595
+ });
596
+ break;
597
+ case "dict.set":
598
+ case "dict.delete":
599
+ add(targets, {
600
+ kind: "dictionary-key",
601
+ at: operation.at,
602
+ key: operation.key
603
+ });
604
+ break;
605
+ case "entity.create":
606
+ add(targets, {
607
+ kind: "collection",
608
+ at: operation.at
609
+ });
610
+ for (const entry of operation.entries) add(targets, {
611
+ kind: "entity",
612
+ at: operation.at,
613
+ id: entry.id
614
+ });
615
+ break;
616
+ case "entity.remove":
617
+ add(targets, {
618
+ kind: "collection",
619
+ at: operation.at
620
+ });
621
+ for (const id of operation.ids) add(targets, {
622
+ kind: "entity",
623
+ at: operation.at,
624
+ id
625
+ });
626
+ break;
627
+ case "entity.move":
628
+ add(targets, {
629
+ kind: "collection",
630
+ at: operation.at
631
+ });
632
+ add(targets, {
633
+ kind: "entity",
634
+ at: operation.at,
635
+ id: operation.id
636
+ });
637
+ break;
638
+ case "list.insert":
639
+ case "list.move":
640
+ case "list.remove":
641
+ add(targets, {
642
+ kind: "list",
643
+ at: operation.at
644
+ });
645
+ add(targets, {
646
+ kind: "list-item",
647
+ at: operation.at,
648
+ key: operation.key
649
+ });
650
+ break;
651
+ case "list.replace":
652
+ add(targets, {
653
+ kind: "list",
654
+ at: operation.at
655
+ });
656
+ for (const key of operation.keys) add(targets, {
657
+ kind: "list-item",
658
+ at: operation.at,
659
+ key
660
+ });
661
+ break;
662
+ case "tree.insert":
663
+ case "tree.move":
664
+ case "tree.remove":
665
+ case "tree.set":
666
+ add(targets, {
667
+ kind: "tree",
668
+ at: operation.at
669
+ });
670
+ add(targets, {
671
+ kind: "tree-node",
672
+ at: operation.at,
673
+ id: operation.treeNodeId
674
+ });
675
+ break;
676
+ case "tree.replace":
677
+ add(targets, {
678
+ kind: "tree",
679
+ at: operation.at
680
+ });
681
+ break;
445
682
  }
683
+ return Object.freeze(Array.from(targets.values()));
446
684
  };
447
- var DocumentDisposedError = class extends Error {
448
- constructor() {
449
- super("Doxum runtime has been disposed.");
450
- this.name = "DocumentDisposedError";
685
+ const overlapsValueAndEntity = (value, entity) => containsAddress([...entity.at, entity.id], value.at) || containsAddress(value.at, [...entity.at, entity.id]);
686
+ const targetsOverlap = (left, right) => {
687
+ if (left.kind === "value" && right.kind === "value") return containsAddress(left.at, right.at) || containsAddress(right.at, left.at);
688
+ if (left.kind === "value" && right.kind === "entity") return overlapsValueAndEntity(left, right);
689
+ if (left.kind === "entity" && right.kind === "value") return overlapsValueAndEntity(right, left);
690
+ if (left.kind === "dictionary" && right.kind === "dictionary") return sameAddress(left.at, right.at);
691
+ if (left.kind === "dictionary" && right.kind === "dictionary-key") return sameAddress(left.at, right.at);
692
+ if (left.kind === "dictionary-key" && right.kind === "dictionary") return sameAddress(left.at, right.at);
693
+ if (left.kind === "dictionary-key" && right.kind === "dictionary-key") return sameAddress(left.at, right.at) && left.key === right.key;
694
+ if (left.kind === "collection" && right.kind === "collection") return sameAddress(left.at, right.at);
695
+ if (left.kind === "collection" && right.kind === "entity") return sameAddress(left.at, right.at);
696
+ if (left.kind === "entity" && right.kind === "collection") return sameAddress(left.at, right.at);
697
+ if (left.kind === "entity" && right.kind === "entity") return sameAddress(left.at, right.at) && left.id === right.id;
698
+ if (left.kind === "list" && (right.kind === "list" || right.kind === "list-item")) return sameAddress(left.at, right.at);
699
+ if (right.kind === "list" && left.kind === "list-item") return sameAddress(left.at, right.at);
700
+ if (left.kind === "list-item" && right.kind === "list-item") return sameAddress(left.at, right.at) && left.key === right.key;
701
+ if (left.kind === "tree" && (right.kind === "tree" || right.kind === "tree-node")) return sameAddress(left.at, right.at);
702
+ if (right.kind === "tree" && left.kind === "tree-node") return sameAddress(left.at, right.at);
703
+ if (left.kind === "tree-node" && right.kind === "tree-node") return sameAddress(left.at, right.at) && left.id === right.id;
704
+ return false;
705
+ };
706
+ const footprintsOverlap = (left, right) => left.some((leftTarget) => right.some((rightTarget) => targetsOverlap(leftTarget, rightTarget)));
707
+ const decodedTarget = (value) => {
708
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
709
+ const record = value;
710
+ const at = address(record.at);
711
+ if (!at || typeof record.kind !== "string") return void 0;
712
+ switch (record.kind) {
713
+ case "value":
714
+ case "dictionary":
715
+ case "collection":
716
+ case "list":
717
+ case "tree": return freezeTarget({
718
+ kind: record.kind,
719
+ at
720
+ });
721
+ case "dictionary-key":
722
+ case "list-item": return typeof record.key === "string" ? freezeTarget({
723
+ kind: record.kind,
724
+ at,
725
+ key: record.key
726
+ }) : void 0;
727
+ case "entity":
728
+ case "tree-node": return typeof record.id === "string" ? freezeTarget({
729
+ kind: record.kind,
730
+ at,
731
+ id: record.id
732
+ }) : void 0;
733
+ default: return;
451
734
  }
452
735
  };
736
+ const decodeCommandFootprint = (value) => {
737
+ if (!Array.isArray(value)) return void 0;
738
+ const targets = /* @__PURE__ */ new Map();
739
+ for (const entry of value) {
740
+ const target = decodedTarget(entry);
741
+ if (!target) return void 0;
742
+ targets.set(targetKey(target), target);
743
+ }
744
+ return Object.freeze(Array.from(targets.values()));
745
+ };
453
746
  //#endregion
454
747
  //#region core/src/runtime/access.ts
455
748
  const states = /* @__PURE__ */ new WeakMap();
@@ -476,6 +769,11 @@ const readWith = (runtime, run, dependencies) => {
476
769
  //#region core/src/runtime/notification.ts
477
770
  const ROOT_BUCKET = Symbol("document-root");
478
771
  const notifications = /* @__PURE__ */ new WeakMap();
772
+ const readableOwners = /* @__PURE__ */ new WeakMap();
773
+ const bindDocumentReadable = (readable, runtime) => {
774
+ readableOwners.set(readable, runtime);
775
+ };
776
+ const documentReadableOwner = (readable) => readableOwners.get(readable);
479
777
  const key = (value) => bucket(value) ?? ROOT_BUCKET;
480
778
  const createNotification = (runtime) => {
481
779
  const notification = {
@@ -550,7 +848,7 @@ const subscribeTargets = (notification, targets, listener) => {
550
848
  });
551
849
  };
552
850
  };
553
- const notify = (notification, commit) => {
851
+ const notify = (notification, commit, afterSettle) => {
554
852
  notification.notifying = true;
555
853
  const errors = [];
556
854
  const call = (listener) => {
@@ -601,6 +899,7 @@ const notify = (notification, commit) => {
601
899
  }));
602
900
  }
603
901
  }
902
+ if (afterSettle) errors.push(...afterSettle());
604
903
  const candidates = notification.candidates;
605
904
  candidates.clear();
606
905
  if (commit.impact.kind === "reset") notification.filtered.forEach((entry) => candidates.add(entry));
@@ -974,239 +1273,34 @@ const assertScope = (active) => {
974
1273
  if (!active()) throw new Error("Projection reader or writer is no longer active.");
975
1274
  };
976
1275
  //#endregion
977
- //#region core/src/projection/source.ts
978
- const createSources = (scheduler) => {
979
- const documents = /* @__PURE__ */ new Map();
980
- const readables = /* @__PURE__ */ new Map();
981
- const collections = /* @__PURE__ */ new WeakMap();
982
- const document = (runtime) => {
983
- scheduler.assertIdle();
984
- const state = accessOf(runtime);
985
- if (state.disposed) throw new Error("Document has been disposed.");
986
- const existing = documents.get(state);
987
- if (existing) return existing;
988
- const bindings = [];
989
- const make = (targets, collection) => {
990
- if (state.disposed) throw new Error("Document has been disposed.");
991
- targets.forEach((value) => {
992
- if (!belongs(value, state.schema)) throw new Error("Projection target belongs to another schema.");
993
- });
994
- const old = bindings.find((entry) => Boolean(collection) === collections.has(entry.handle) && entry.targets.length === targets.length && entry.targets.every((value, i) => same(value, targets[i])));
995
- if (old) return old.handle;
996
- let commits = [];
997
- const record = {
998
- consumers: /* @__PURE__ */ new Set(),
999
- disposed: false,
1000
- fault: void 0,
1001
- revision: runtime.revision,
1002
- reset: () => commits.some((commit) => commit.kind === "replace"),
1003
- clear: () => {
1004
- commits = [];
1005
- },
1006
- context: (active) => {
1007
- assertScope(active);
1008
- if (state.disposed) throw new Error("Document has been disposed.");
1009
- const read = collection ? readerFor(nodeAt(state.schema, collection.address), {
1010
- root: () => state.document,
1011
- active
1012
- }, collection.address) : documentReader(state.schema, () => state.document, active);
1013
- return Object.freeze({
1014
- read,
1015
- target: collection,
1016
- revision: runtime.revision(),
1017
- commits: Object.freeze(commits.slice()),
1018
- reset: record.reset()
1019
- });
1020
- }
1021
- };
1022
- const handle = collection ? {} : {
1023
- collection: (pick) => {
1024
- scheduler.assertIdle();
1025
- const selector = state.schema.collection(pick);
1026
- const node = nodeAt(state.schema, selector.address);
1027
- if (node?.kind !== "table" && node?.kind !== "map") throw new TypeError("Expected a table or map collection path.");
1028
- return make([selector], selector);
1029
- },
1030
- targets: (...selected) => {
1031
- scheduler.assertIdle();
1032
- if (!selected.length) throw new TypeError("Expected at least one target.");
1033
- return make(selected);
1034
- }
1035
- };
1036
- Object.freeze(handle);
1037
- bindings.push({
1038
- handle,
1039
- targets,
1040
- record
1041
- });
1042
- if (collection) collections.set(handle, {
1043
- source: handle,
1044
- target: collection
1045
- });
1046
- scheduler.register(handle, record);
1047
- receivers.set(record, (commit) => {
1048
- commits.push(commit);
1049
- });
1050
- return handle;
1051
- };
1052
- const receivers = /* @__PURE__ */ new Map();
1053
- const handle = make([]);
1054
- const guard = (locked) => {
1055
- state.projectionLocks = (state.projectionLocks ?? 0) + (locked ? 1 : -1);
1056
- };
1057
- scheduler.guards.add(guard);
1058
- const unsubscribe = attachProjection(runtime, {
1059
- capture: (commit) => {
1060
- for (const binding of bindings) if (!binding.targets.length || binding.targets.some(commit.impact.affects)) {
1061
- receivers.get(binding.record)(commit);
1062
- scheduler.capture(binding.record);
1063
- }
1064
- },
1065
- settle: scheduler.settle,
1066
- flush: scheduler.flush,
1067
- dispose: () => {
1068
- bindings.forEach(({ record }) => {
1069
- record.fault = new ProjectionError("source", "document disposed", [runtime.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
1070
- scheduler.capture(record);
1071
- });
1072
- scheduler.run();
1276
+ //#region core/src/projection/node.ts
1277
+ const createNode = (scheduler, spec, behavior) => {
1278
+ scheduler.assertIdle();
1279
+ const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
1280
+ const order = scheduler.order();
1281
+ const node = {
1282
+ ...behavior,
1283
+ publish: () => {
1284
+ if (node.fault) behavior.clear();
1285
+ else behavior.publish();
1286
+ },
1287
+ order,
1288
+ name: spec.name ? `${spec.name} (${order})` : `projection-${order}`,
1289
+ sources: entries.map(([, source]) => source),
1290
+ consumers: /* @__PURE__ */ new Set(),
1291
+ disposed: false,
1292
+ fault: void 0,
1293
+ forced: false,
1294
+ statusChanged: false,
1295
+ evaluate: (build) => {
1296
+ let active = true;
1297
+ try {
1298
+ const inputs = Object.fromEntries(entries.map(([key, source]) => [key, source.context(() => active)]));
1299
+ return behavior.evaluate(inputs, build, () => active);
1300
+ } finally {
1301
+ active = false;
1073
1302
  }
1074
- });
1075
- scheduler.cleanups.add(() => {
1076
- unsubscribe();
1077
- scheduler.guards.delete(guard);
1078
- });
1079
- documents.set(state, handle);
1080
- return handle;
1081
- };
1082
- const valueSource = (initial, equal) => {
1083
- let value = initial;
1084
- let previous = initial;
1085
- let revision = 0;
1086
- const handle = Object.freeze({});
1087
- const record = {
1088
- consumers: /* @__PURE__ */ new Set(),
1089
- disposed: false,
1090
- fault: void 0,
1091
- revision: () => revision,
1092
- reset: () => false,
1093
- clear: () => {
1094
- previous = value;
1095
- },
1096
- context: (active) => {
1097
- assertScope(active);
1098
- return Object.freeze({
1099
- value,
1100
- previous,
1101
- revision,
1102
- changed: !equal(previous, value),
1103
- reset: false
1104
- });
1105
- }
1106
- };
1107
- scheduler.register(handle, record);
1108
- const accept = (next) => {
1109
- scheduler.assertIdle();
1110
- const recovering = record.fault !== void 0;
1111
- if (equal(value, next) && !recovering) return;
1112
- record.fault = void 0;
1113
- value = next;
1114
- revision++;
1115
- scheduler.capture(record);
1116
- };
1117
- return {
1118
- source: handle,
1119
- accept,
1120
- set(next) {
1121
- accept(next);
1122
- scheduler.run();
1123
- }
1124
- };
1125
- };
1126
- return {
1127
- dispose: () => {
1128
- documents.clear();
1129
- readables.clear();
1130
- },
1131
- document,
1132
- input: (initial, options) => {
1133
- const { source, set } = valueSource(initial, options?.isEqual ?? Object.is);
1134
- return Object.freeze({
1135
- source,
1136
- set
1137
- });
1138
- },
1139
- fromReadable: (readable, options) => {
1140
- scheduler.assertIdle();
1141
- if (projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
1142
- const old = readables.get(readable);
1143
- if (old) return old;
1144
- const input = valueSource(readable.current(), options?.isEqual ?? Object.is);
1145
- const receive = () => {
1146
- if (!scheduler.active) return;
1147
- try {
1148
- input.accept(readable.current());
1149
- } catch (cause) {
1150
- const record = scheduler.source(input.source);
1151
- record.fault = new ProjectionError("source", "external readable", [readable.revision()], cause);
1152
- scheduler.capture(record);
1153
- }
1154
- scheduler.run();
1155
- };
1156
- let unsubscribe;
1157
- try {
1158
- unsubscribe = readable.subscribe(receive);
1159
- input.accept(readable.current());
1160
- } catch (error) {
1161
- try {
1162
- unsubscribe?.();
1163
- } finally {
1164
- scheduler.unregisterSource(input.source);
1165
- }
1166
- throw error;
1167
- }
1168
- scheduler.cleanups.add(unsubscribe);
1169
- readables.set(readable, input.source);
1170
- scheduler.run();
1171
- return input.source;
1172
- },
1173
- collectionTarget: (handle) => {
1174
- scheduler.source(handle);
1175
- const value = collections.get(handle);
1176
- if (!value) throw new Error("map requires a document collection source.");
1177
- return value.target;
1178
- }
1179
- };
1180
- };
1181
- //#endregion
1182
- //#region core/src/projection/node.ts
1183
- const createNode = (scheduler, spec, behavior) => {
1184
- scheduler.assertIdle();
1185
- const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
1186
- const order = scheduler.order();
1187
- const node = {
1188
- ...behavior,
1189
- publish: () => {
1190
- if (node.fault) behavior.clear();
1191
- else behavior.publish();
1192
- },
1193
- order,
1194
- name: spec.name ? `${spec.name} (${order})` : `projection-${order}`,
1195
- sources: entries.map(([, source]) => source),
1196
- consumers: /* @__PURE__ */ new Set(),
1197
- disposed: false,
1198
- fault: void 0,
1199
- forced: false,
1200
- statusChanged: false,
1201
- evaluate: (build) => {
1202
- let active = true;
1203
- try {
1204
- const inputs = Object.fromEntries(entries.map(([key, source]) => [key, source.context(() => active)]));
1205
- return behavior.evaluate(inputs, build, () => active);
1206
- } finally {
1207
- active = false;
1208
- }
1209
- }
1303
+ }
1210
1304
  };
1211
1305
  scheduler.initialize(() => {
1212
1306
  const invalid = node.sources.find((source) => source.fault || source.disposed);
@@ -1231,103 +1325,8 @@ const createNode = (scheduler, spec, behavior) => {
1231
1325
  };
1232
1326
  };
1233
1327
  //#endregion
1234
- //#region core/src/projection/value.ts
1235
- const createValue = (scheduler, spec) => {
1236
- let instance;
1237
- let value;
1238
- let next;
1239
- let previous;
1240
- let initialized = false;
1241
- let revision = 0;
1242
- let changed = false;
1243
- let reset = false;
1244
- const listeners = /* @__PURE__ */ new Set();
1245
- const owner = createNode(scheduler, spec, {
1246
- evaluate: (sources, build) => {
1247
- let candidate;
1248
- if (build || !instance) {
1249
- profile.materialized.rebuilt();
1250
- instance = void 0;
1251
- const built = spec.build(sources);
1252
- assertSynchronous(built);
1253
- instance = built;
1254
- candidate = built.value;
1255
- } else {
1256
- profile.materialized.updated();
1257
- const result = instance.update(sources);
1258
- assertSynchronous(result);
1259
- if (result.kind === "rebuild") {
1260
- instance = void 0;
1261
- const built = spec.build(sources);
1262
- assertSynchronous(built);
1263
- instance = built;
1264
- candidate = built.value;
1265
- build = true;
1266
- } else candidate = result.kind === "changed" ? result.value : value;
1267
- }
1268
- previous = value;
1269
- changed = !initialized || !(spec.isEqual ?? Object.is)(value, candidate);
1270
- next = changed ? candidate : value;
1271
- reset = build;
1272
- return changed;
1273
- },
1274
- context: (active) => {
1275
- assertScope(active);
1276
- return Object.freeze({
1277
- value: next,
1278
- previous,
1279
- changed,
1280
- revision: revision + (changed ? 1 : 0),
1281
- reset
1282
- });
1283
- },
1284
- revision: () => revision,
1285
- reset: () => reset,
1286
- publish: () => {
1287
- if (changed && initialized) revision++;
1288
- value = next;
1289
- initialized = true;
1290
- },
1291
- emit: (call) => {
1292
- profile.materialized.notification();
1293
- Array.from(listeners).forEach((listener) => call(listener));
1294
- },
1295
- clear: () => {
1296
- next = value;
1297
- previous = value;
1298
- changed = false;
1299
- reset = false;
1300
- },
1301
- release: () => {
1302
- listeners.clear();
1303
- instance = void 0;
1304
- value = next = previous = void 0;
1305
- }
1306
- });
1307
- const handle = Object.freeze({
1308
- current: () => {
1309
- owner.check();
1310
- return value;
1311
- },
1312
- revision: () => {
1313
- owner.check();
1314
- return revision;
1315
- },
1316
- subscribe: (listener) => {
1317
- owner.check();
1318
- listeners.add(listener);
1319
- return () => {
1320
- listeners.delete(listener);
1321
- };
1322
- },
1323
- rebuild: owner.rebuild,
1324
- dispose: owner.dispose
1325
- });
1326
- owner.install(handle);
1327
- return handle;
1328
- };
1329
- //#endregion
1330
1328
  //#region core/src/projection/collection.ts
1329
+ const collectionHandles = /* @__PURE__ */ new WeakSet();
1331
1330
  const readonlySet = (values) => {
1332
1331
  const set = new Set(values);
1333
1332
  const view = Object.freeze({
@@ -1655,10 +1654,374 @@ const createCollection = (scheduler, spec) => {
1655
1654
  };
1656
1655
  projectionHandles.add(handle.ids);
1657
1656
  projectionHandles.add(handle.all);
1657
+ collectionHandles.add(handle);
1658
1658
  owner.install(handle);
1659
1659
  return Object.freeze(handle);
1660
1660
  };
1661
1661
  //#endregion
1662
+ //#region core/src/projection/source.ts
1663
+ const createSources = (scheduler) => {
1664
+ const documents = /* @__PURE__ */ new Map();
1665
+ const readables = /* @__PURE__ */ new Map();
1666
+ const collections = /* @__PURE__ */ new WeakSet();
1667
+ const document = (runtime) => {
1668
+ scheduler.assertIdle();
1669
+ const state = accessOf(runtime);
1670
+ if (state.disposed) throw new Error("Document has been disposed.");
1671
+ const existing = documents.get(state);
1672
+ if (existing) return existing;
1673
+ const bindings = [];
1674
+ const make = (targets, collection) => {
1675
+ if (state.disposed) throw new Error("Document has been disposed.");
1676
+ targets.forEach((value) => {
1677
+ if (!belongs(value, state.schema)) throw new Error("Projection target belongs to another schema.");
1678
+ });
1679
+ const old = bindings.find((entry) => Boolean(collection) === collections.has(entry.handle) && entry.targets.length === targets.length && entry.targets.every((value, i) => same(value, targets[i])));
1680
+ if (old) return old.handle;
1681
+ let commits = [];
1682
+ let reset = false;
1683
+ let orderDirty = false;
1684
+ const keys = /* @__PURE__ */ new Set();
1685
+ let candidates;
1686
+ const record = {
1687
+ consumers: /* @__PURE__ */ new Set(),
1688
+ disposed: false,
1689
+ fault: void 0,
1690
+ revision: runtime.revision,
1691
+ reset: () => reset,
1692
+ clear: () => {
1693
+ commits = [];
1694
+ reset = orderDirty = false;
1695
+ keys.clear();
1696
+ candidates = void 0;
1697
+ },
1698
+ context: (active) => {
1699
+ assertScope(active);
1700
+ if (state.disposed) throw new Error("Document has been disposed.");
1701
+ const read = collection ? readerFor(nodeAt(state.schema, collection.address, state.document) ?? collectionSchema(collection), {
1702
+ root: () => state.document,
1703
+ active
1704
+ }, collection.address) : documentReader(state.schema, () => state.document, active);
1705
+ return Object.freeze({
1706
+ read,
1707
+ target: collection,
1708
+ revision: runtime.revision(),
1709
+ commits: Object.freeze(commits.slice()),
1710
+ reset: record.reset(),
1711
+ ...collection ? { candidates: candidates ??= Object.freeze({
1712
+ keys: Object.freeze([...keys]),
1713
+ orderDirty
1714
+ }) } : {}
1715
+ });
1716
+ }
1717
+ };
1718
+ const handle = collection ? {} : {
1719
+ collection: (pick) => {
1720
+ scheduler.assertIdle();
1721
+ const selector = state.schema.collection(pick);
1722
+ return make([selector], selector);
1723
+ },
1724
+ targets: (...selected) => {
1725
+ scheduler.assertIdle();
1726
+ if (!selected.length) throw new TypeError("Expected at least one target.");
1727
+ return make(selected);
1728
+ }
1729
+ };
1730
+ Object.freeze(handle);
1731
+ bindings.push({
1732
+ handle,
1733
+ targets,
1734
+ record
1735
+ });
1736
+ if (collection) collections.add(handle);
1737
+ scheduler.register(handle, record);
1738
+ receivers.set(record, (commit) => {
1739
+ commits.push(commit);
1740
+ reset ||= commit.kind === "replace";
1741
+ if (collection) {
1742
+ const change = commit.impact.collection(collection);
1743
+ if (change.kind === "reset") reset = true;
1744
+ else {
1745
+ change.added.forEach((key) => keys.add(key));
1746
+ change.removed.forEach((key) => keys.add(key));
1747
+ change.updated.forEach((key) => keys.add(key));
1748
+ orderDirty ||= Boolean(change.added.size || change.removed.size || change.orderChanged);
1749
+ }
1750
+ candidates = void 0;
1751
+ }
1752
+ });
1753
+ return handle;
1754
+ };
1755
+ const receivers = /* @__PURE__ */ new Map();
1756
+ const handle = make([]);
1757
+ const guard = (locked) => {
1758
+ state.projectionLocks = (state.projectionLocks ?? 0) + (locked ? 1 : -1);
1759
+ };
1760
+ scheduler.guards.add(guard);
1761
+ const unsubscribe = attachProjection(runtime, {
1762
+ capture: (commit) => {
1763
+ for (const binding of bindings) if (!binding.targets.length || binding.targets.some(commit.impact.affects)) {
1764
+ receivers.get(binding.record)(commit);
1765
+ scheduler.capture(binding.record);
1766
+ }
1767
+ },
1768
+ settle: scheduler.settle,
1769
+ flush: scheduler.flush,
1770
+ dispose: () => {
1771
+ bindings.forEach(({ record }) => {
1772
+ record.fault = new ProjectionError("source", "document disposed", [runtime.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
1773
+ scheduler.capture(record);
1774
+ });
1775
+ scheduler.run();
1776
+ }
1777
+ });
1778
+ scheduler.cleanups.add(() => {
1779
+ unsubscribe();
1780
+ scheduler.guards.delete(guard);
1781
+ });
1782
+ documents.set(state, handle);
1783
+ return handle;
1784
+ };
1785
+ const valueSource = (initial, equal) => {
1786
+ let value = initial;
1787
+ let previous = initial;
1788
+ let revision = 0;
1789
+ const handle = Object.freeze({});
1790
+ const record = {
1791
+ consumers: /* @__PURE__ */ new Set(),
1792
+ disposed: false,
1793
+ fault: void 0,
1794
+ revision: () => revision,
1795
+ reset: () => false,
1796
+ clear: () => {
1797
+ previous = value;
1798
+ },
1799
+ context: (active) => {
1800
+ assertScope(active);
1801
+ return Object.freeze({
1802
+ value,
1803
+ previous,
1804
+ revision,
1805
+ changed: !equal(previous, value),
1806
+ reset: false
1807
+ });
1808
+ }
1809
+ };
1810
+ scheduler.register(handle, record);
1811
+ const accept = (next) => {
1812
+ scheduler.assertIdle();
1813
+ const recovering = record.fault !== void 0;
1814
+ if (equal(value, next) && !recovering) return;
1815
+ record.fault = void 0;
1816
+ value = next;
1817
+ revision++;
1818
+ scheduler.capture(record);
1819
+ };
1820
+ return {
1821
+ source: handle,
1822
+ accept,
1823
+ set(next) {
1824
+ accept(next);
1825
+ scheduler.run();
1826
+ }
1827
+ };
1828
+ };
1829
+ return {
1830
+ dispose: () => {
1831
+ documents.clear();
1832
+ readables.clear();
1833
+ },
1834
+ document,
1835
+ input: (initial, options) => {
1836
+ const { source, set } = valueSource(initial, options?.isEqual ?? Object.is);
1837
+ return Object.freeze({
1838
+ source,
1839
+ set
1840
+ });
1841
+ },
1842
+ fromReadable: (readable, options) => {
1843
+ scheduler.assertIdle();
1844
+ if (projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
1845
+ const equal = options?.isEqual ?? Object.is;
1846
+ let bindings = readables.get(readable);
1847
+ const old = bindings?.get(equal);
1848
+ if (old) return old.source;
1849
+ const input = valueSource(readable.current(), equal);
1850
+ if (bindings) {
1851
+ bindings.set(equal, {
1852
+ source: input.source,
1853
+ accept: (value) => input.accept(value)
1854
+ });
1855
+ return input.source;
1856
+ }
1857
+ bindings = new Map([[equal, {
1858
+ source: input.source,
1859
+ accept: (value) => input.accept(value)
1860
+ }]]);
1861
+ const members = bindings;
1862
+ const receive = (settle = true) => {
1863
+ if (!scheduler.active) return;
1864
+ let value;
1865
+ try {
1866
+ value = readable.current();
1867
+ } catch (cause) {
1868
+ for (const member of members.values()) {
1869
+ const record = scheduler.source(member.source);
1870
+ record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
1871
+ scheduler.capture(record);
1872
+ }
1873
+ if (settle) scheduler.run();
1874
+ return;
1875
+ }
1876
+ for (const member of members.values()) try {
1877
+ member.accept(value);
1878
+ } catch (cause) {
1879
+ const record = scheduler.source(member.source);
1880
+ record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
1881
+ scheduler.capture(record);
1882
+ }
1883
+ if (settle) scheduler.run();
1884
+ };
1885
+ let unsubscribe;
1886
+ let detach;
1887
+ try {
1888
+ const owner = documentReadableOwner(readable);
1889
+ if (owner) {
1890
+ document(owner);
1891
+ detach = attachProjection(owner, {
1892
+ capture: () => receive(false),
1893
+ settle: scheduler.settle,
1894
+ flush: scheduler.flush,
1895
+ dispose: () => {
1896
+ for (const member of members.values()) {
1897
+ const record = scheduler.source(member.source);
1898
+ record.fault = new ProjectionError("source", "document readable disposed", [record.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
1899
+ scheduler.capture(record);
1900
+ }
1901
+ scheduler.run();
1902
+ }
1903
+ });
1904
+ }
1905
+ unsubscribe = readable.subscribe(() => receive());
1906
+ input.accept(readable.current());
1907
+ } catch (error) {
1908
+ try {
1909
+ unsubscribe?.();
1910
+ detach?.();
1911
+ } finally {
1912
+ scheduler.unregisterSource(input.source);
1913
+ }
1914
+ throw error;
1915
+ }
1916
+ scheduler.cleanups.add(unsubscribe);
1917
+ if (detach) scheduler.cleanups.add(detach);
1918
+ readables.set(readable, bindings);
1919
+ scheduler.run();
1920
+ return input.source;
1921
+ },
1922
+ assertCollection: (handle) => {
1923
+ scheduler.source(handle);
1924
+ if (!collections.has(handle) && !collectionHandles.has(handle)) throw new Error("map requires a collection source.");
1925
+ }
1926
+ };
1927
+ };
1928
+ //#endregion
1929
+ //#region core/src/projection/value.ts
1930
+ const createValue = (scheduler, spec, options) => {
1931
+ let instance;
1932
+ let value;
1933
+ let next;
1934
+ let previous;
1935
+ let initialized = false;
1936
+ let revision = 0;
1937
+ let changed = false;
1938
+ let reset = false;
1939
+ const listeners = /* @__PURE__ */ new Set();
1940
+ const owner = createNode(scheduler, spec, {
1941
+ evaluate: (sources, build) => {
1942
+ let candidate;
1943
+ if (build || !instance) {
1944
+ profile.materialized.rebuilt();
1945
+ instance = void 0;
1946
+ const built = spec.build(sources);
1947
+ assertSynchronous(built);
1948
+ instance = built;
1949
+ candidate = built.value;
1950
+ } else {
1951
+ profile.materialized.updated();
1952
+ const result = instance.update(sources);
1953
+ assertSynchronous(result);
1954
+ if (result.kind === "rebuild") {
1955
+ instance = void 0;
1956
+ const built = spec.build(sources);
1957
+ assertSynchronous(built);
1958
+ instance = built;
1959
+ candidate = built.value;
1960
+ build = true;
1961
+ } else candidate = result.kind === "changed" ? result.value : value;
1962
+ }
1963
+ previous = value;
1964
+ changed = !initialized || !(options?.isEqual ?? Object.is)(value, candidate);
1965
+ next = changed ? candidate : value;
1966
+ reset = build;
1967
+ return changed;
1968
+ },
1969
+ context: (active) => {
1970
+ assertScope(active);
1971
+ return Object.freeze({
1972
+ value: next,
1973
+ previous,
1974
+ changed,
1975
+ revision: revision + (changed ? 1 : 0),
1976
+ reset
1977
+ });
1978
+ },
1979
+ revision: () => revision,
1980
+ reset: () => reset,
1981
+ publish: () => {
1982
+ if (changed && initialized) revision++;
1983
+ value = next;
1984
+ initialized = true;
1985
+ },
1986
+ emit: (call) => {
1987
+ profile.materialized.notification();
1988
+ Array.from(listeners).forEach((listener) => call(listener));
1989
+ },
1990
+ clear: () => {
1991
+ next = value;
1992
+ previous = value;
1993
+ changed = false;
1994
+ reset = false;
1995
+ },
1996
+ release: () => {
1997
+ listeners.clear();
1998
+ instance = void 0;
1999
+ value = next = previous = void 0;
2000
+ }
2001
+ });
2002
+ const handle = Object.freeze({
2003
+ current: () => {
2004
+ owner.check();
2005
+ return value;
2006
+ },
2007
+ revision: () => {
2008
+ owner.check();
2009
+ return revision;
2010
+ },
2011
+ subscribe: (listener) => {
2012
+ owner.check();
2013
+ listeners.add(listener);
2014
+ return () => {
2015
+ listeners.delete(listener);
2016
+ };
2017
+ },
2018
+ rebuild: owner.rebuild,
2019
+ dispose: owner.dispose
2020
+ });
2021
+ owner.install(handle);
2022
+ return handle;
2023
+ };
2024
+ //#endregion
1662
2025
  //#region core/src/projection/runtime.ts
1663
2026
  const debug = /* @__PURE__ */ new WeakMap();
1664
2027
  const projectionDebug = (runtime) => {
@@ -1669,54 +2032,85 @@ const projectionDebug = (runtime) => {
1669
2032
  const createProjectionRuntime = (options) => {
1670
2033
  const scheduler = createScheduler(options.onError);
1671
2034
  const sources = createSources(scheduler);
2035
+ function value(input, computeOrOptions, options) {
2036
+ if (typeof computeOrOptions !== "function") return createValue(scheduler, input, computeOrOptions);
2037
+ const compute = (input) => {
2038
+ const result = computeOrOptions(input);
2039
+ assertSynchronous(result);
2040
+ return result;
2041
+ };
2042
+ return createValue(scheduler, {
2043
+ sources: input,
2044
+ build: (input) => ({
2045
+ value: compute(input),
2046
+ update: (input) => ({
2047
+ kind: "changed",
2048
+ value: compute(input)
2049
+ })
2050
+ })
2051
+ }, options);
2052
+ }
2053
+ const map = (source, mapper, options) => {
2054
+ sources.assertCollection(source);
2055
+ const calculate = (id, entry) => {
2056
+ const value = mapper(id, entry);
2057
+ assertSynchronous(value);
2058
+ return value;
2059
+ };
2060
+ return createCollection(scheduler, {
2061
+ sources: { source },
2062
+ isEqual: options?.isEqual,
2063
+ build: ({ sources, writer }) => {
2064
+ const input = sources.source;
2065
+ const read = "read" in input ? input.read : input;
2066
+ const ids = read.ids();
2067
+ profile.collectionView.idsScanned(ids.length);
2068
+ for (const id of ids) {
2069
+ profile.collectionView.mapped();
2070
+ writer.set(id, calculate(id, read.get(id)));
2071
+ }
2072
+ writer.order(ids);
2073
+ return { update: ({ sources, writer }) => {
2074
+ const input = sources.source;
2075
+ if (input.reset) return { kind: "rebuild" };
2076
+ const read = "read" in input ? input.read : input;
2077
+ let keys;
2078
+ let structural;
2079
+ if ("candidates" in input) {
2080
+ keys = input.candidates.keys;
2081
+ structural = input.candidates.orderDirty;
2082
+ } else {
2083
+ const change = input.change;
2084
+ if (!change) return;
2085
+ if (change.kind === "reset") return { kind: "rebuild" };
2086
+ keys = new Set([
2087
+ ...change.added,
2088
+ ...change.removed,
2089
+ ...change.updated
2090
+ ]);
2091
+ structural = Boolean(change.added.size || change.removed.size || change.orderChanged);
2092
+ }
2093
+ for (const key of keys) if (!read.has(key)) writer.remove(key);
2094
+ else {
2095
+ profile.collectionView.mapped();
2096
+ writer.set(key, calculate(key, read.get(key)));
2097
+ }
2098
+ if (structural) {
2099
+ const ids = read.ids();
2100
+ profile.collectionView.idsScanned(ids.length);
2101
+ writer.order(ids);
2102
+ }
2103
+ } };
2104
+ }
2105
+ });
2106
+ };
1672
2107
  const runtime = {
1673
2108
  document: sources.document,
1674
2109
  input: sources.input,
1675
2110
  fromReadable: sources.fromReadable,
1676
- value: (spec) => createValue(scheduler, spec),
1677
- collection: (spec) => createCollection(scheduler, spec),
1678
- map: (source, mapper, options) => {
1679
- sources.collectionTarget(source);
1680
- return createCollection(scheduler, {
1681
- sources: { source },
1682
- isEqual: options?.isEqual,
1683
- build: ({ sources, writer }) => {
1684
- const ids = sources.source.read.ids();
1685
- profile.collectionView.idsScanned(ids.length);
1686
- for (const id of ids) {
1687
- profile.collectionView.mapped();
1688
- writer.set(id, mapper(id, sources.source.read.get(id)));
1689
- }
1690
- writer.order(ids);
1691
- return { update: ({ sources, writer }) => {
1692
- const input = sources.source;
1693
- const keys = /* @__PURE__ */ new Set();
1694
- let structural = false;
1695
- for (const commit of input.commits) {
1696
- const impact = commit.impact.collection(input.target);
1697
- if (impact.kind === "reset") return { kind: "rebuild" };
1698
- impact.added.forEach((key) => keys.add(key));
1699
- impact.removed.forEach((key) => keys.add(key));
1700
- impact.updated.forEach((key) => keys.add(key));
1701
- structural ||= Boolean(impact.added.size || impact.removed.size || impact.orderChanged);
1702
- }
1703
- for (const key of keys) {
1704
- const entry = input.read.get(key);
1705
- if (!entry) writer.remove(key);
1706
- else {
1707
- profile.collectionView.mapped();
1708
- writer.set(key, mapper(key, entry));
1709
- }
1710
- }
1711
- if (structural) {
1712
- const ids = input.read.ids();
1713
- profile.collectionView.idsScanned(ids.length);
1714
- writer.order(ids);
1715
- }
1716
- } };
1717
- }
1718
- });
1719
- },
2111
+ value,
2112
+ collection: () => (spec) => createCollection(scheduler, spec),
2113
+ map,
1720
2114
  batch: scheduler.batch,
1721
2115
  dispose: () => {
1722
2116
  try {
@@ -1730,6 +2124,6 @@ const createProjectionRuntime = (options) => {
1730
2124
  return Object.freeze(runtime);
1731
2125
  };
1732
2126
  //#endregion
1733
- export { dict as A, tree as B, debugKey as C, resolveAddress as D, read as E, optional as F, record as I, schema as L, list as M, map as N, resolveLocated as O, object as P, single as R, contains as S, overlaps as T, variant as V, address as _, createNotification as a, impact_target_exports as b, shareNotification as c, accessOf as d, bindRuntimeAccess as f, documentReader as g, DocumentReentrancyError as h, ProjectionError as i, field as j, set as k, subscribeRoot as l, DocumentDisposedError as m, projectionDebug as n, disposeNotification as o, readWith as p, ProjectionDisposedError as r, notify as s, createProjectionRuntime as t, subscribeTargets as u, belongs as v, locate as w, same as x, id as y, table as z };
2127
+ export { id as A, set as B, restore as C, DocumentReentrancyError as D, DocumentDisposedError as E, locate as F, object as G, field as H, overlaps as I, table as J, optional as K, read as L, same as M, contains as N, address$1 as O, debugKey as P, resolveAddress as R, keys as S, validPositions as T, list as U, dict as V, map as W, variant as X, tree as Y, footprintsOverlap as _, bindDocumentReadable as a, at as b, notify as c, subscribeTargets as d, accessOf as f, decodeCommandFootprint as g, commandFootprint as h, ProjectionError as i, impact_target_exports as j, belongs as k, shareNotification as l, readWith as m, projectionDebug as n, createNotification as o, bindRuntimeAccess as p, schema as q, ProjectionDisposedError as r, disposeNotification as s, createProjectionRuntime as t, subscribeRoot as u, documentReader as v, valid as w, index as x, afterRemove as y, resolveLocated as z };
1734
2128
 
1735
- //# sourceMappingURL=runtime-BJRXF0gq.js.map
2129
+ //# sourceMappingURL=runtime-DF1q9Gje.js.map