doxum 0.1.19 → 0.1.21

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 (65) hide show
  1. package/README.md +30 -11
  2. package/dist/{scope-d-L87NeC.js → access-BeSV5ve-.js} +114 -20
  3. package/dist/access-BeSV5ve-.js.map +1 -0
  4. package/dist/{scope-Ezs-Y3TY.cjs → access-Bienf28h.cjs} +149 -19
  5. package/dist/access-Bienf28h.cjs.map +1 -0
  6. package/dist/advanced-BRL5pN0y.cjs +1361 -0
  7. package/dist/advanced-BRL5pN0y.cjs.map +1 -0
  8. package/dist/advanced-COFJtWMT.js +1188 -0
  9. package/dist/advanced-COFJtWMT.js.map +1 -0
  10. package/dist/{advanced-C2fpjbK3.d.ts → advanced-DfKc7TZR.d.cts} +27 -22
  11. package/dist/{advanced-DlOYk1vi.d.cts → advanced-FgdTwu5b.d.ts} +27 -22
  12. package/dist/advanced.cjs +1 -1
  13. package/dist/advanced.d.cts +2 -2
  14. package/dist/advanced.d.ts +2 -2
  15. package/dist/advanced.js +1 -1
  16. package/dist/{contract-XmKnroJq.d.cts → contract-CdmDHRUq.d.cts} +8 -6
  17. package/dist/{contract-CATCCVu5.d.ts → contract-KG5XnQZt.d.ts} +8 -6
  18. package/dist/{driver-CSnhMjbP.js → driver-CLYJOGDQ.js} +2 -2
  19. package/dist/{driver-CSnhMjbP.js.map → driver-CLYJOGDQ.js.map} +1 -1
  20. package/dist/{driver-BCyfqxsl.cjs → driver-Djr3BVvo.cjs} +2 -2
  21. package/dist/{driver-BCyfqxsl.cjs.map → driver-Djr3BVvo.cjs.map} +1 -1
  22. package/dist/index.cjs +1150 -1993
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +2 -2
  25. package/dist/index.d.ts +2 -2
  26. package/dist/index.js +1105 -1948
  27. package/dist/index.js.map +1 -1
  28. package/dist/{integration-CxVhq-KD.js → integration-C4Isp8lJ.js} +3 -2
  29. package/dist/{integration-CxVhq-KD.js.map → integration-C4Isp8lJ.js.map} +1 -1
  30. package/dist/{integration-B7VDgAOJ.cjs → integration-Z6MsfFI7.cjs} +4 -3
  31. package/dist/{integration-B7VDgAOJ.cjs.map → integration-Z6MsfFI7.cjs.map} +1 -1
  32. package/dist/integration.cjs +3 -3
  33. package/dist/integration.d.cts +1 -1
  34. package/dist/integration.d.ts +1 -1
  35. package/dist/integration.js +3 -3
  36. package/dist/{issue-DhrNdQNg.cjs → issue-DjK1xp5K.cjs} +106 -22
  37. package/dist/issue-DjK1xp5K.cjs.map +1 -0
  38. package/dist/{issue-DVaGQGeP.js → issue-W0hT8u8D.js} +81 -15
  39. package/dist/issue-W0hT8u8D.js.map +1 -0
  40. package/dist/local-sync.cjs +2 -2
  41. package/dist/local-sync.d.cts +1 -1
  42. package/dist/local-sync.d.ts +1 -1
  43. package/dist/local-sync.js +2 -2
  44. package/dist/{notification-IQIJyH3Q.js → notification-Bkjau6B6.js} +3 -56
  45. package/dist/notification-Bkjau6B6.js.map +1 -0
  46. package/dist/{notification-CDSUfiip.cjs → notification-DJfouMEb.cjs} +2 -91
  47. package/dist/notification-DJfouMEb.cjs.map +1 -0
  48. package/dist/react.cjs +2 -2
  49. package/dist/react.js +2 -2
  50. package/package.json +1 -1
  51. package/skills/doxum-runtime/SKILL.md +4 -1
  52. package/skills/doxum-runtime/references/guide.en.md +5 -2
  53. package/skills/doxum-runtime/references/guide.zh-CN.md +5 -2
  54. package/skills/doxum-runtime/references/projections.en.md +23 -9
  55. package/skills/doxum-runtime/references/projections.zh-CN.md +18 -6
  56. package/dist/advanced-BYZCjNWL.js +0 -680
  57. package/dist/advanced-BYZCjNWL.js.map +0 -1
  58. package/dist/advanced-CMMNk49G.cjs +0 -763
  59. package/dist/advanced-CMMNk49G.cjs.map +0 -1
  60. package/dist/issue-DVaGQGeP.js.map +0 -1
  61. package/dist/issue-DhrNdQNg.cjs.map +0 -1
  62. package/dist/notification-CDSUfiip.cjs.map +0 -1
  63. package/dist/notification-IQIJyH3Q.js.map +0 -1
  64. package/dist/scope-Ezs-Y3TY.cjs.map +0 -1
  65. package/dist/scope-d-L87NeC.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,97 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_issue = require("./issue-DhrNdQNg.cjs");
3
- const require_scope = require("./scope-Ezs-Y3TY.cjs");
4
- const require_notification = require("./notification-CDSUfiip.cjs");
5
- const require_driver = require("./driver-BCyfqxsl.cjs");
6
- const require_advanced = require("./advanced-CMMNk49G.cjs");
7
- //#region core/src/schema.ts
8
- const node = (value) => Object.freeze(value);
9
- const field = (validator) => node({
10
- kind: "field",
11
- ...validator ? { validator } : {}
12
- });
13
- const optional = (value) => {
14
- if (![
15
- "field",
16
- "variant",
17
- "map",
18
- "list",
19
- "tree"
20
- ].includes(value.kind)) throw new TypeError("Only field, variant, map, list and tree nodes support optional presence.");
21
- return node({
22
- ...value,
23
- optional: true
24
- });
25
- };
26
- const object = (shape) => node({
27
- kind: "object",
28
- shape: Object.freeze({ ...shape })
29
- });
30
- const variant = (tag, variants) => node({
31
- kind: "variant",
32
- tag,
33
- variants: Object.freeze({ ...variants })
34
- });
35
- const table = (value, options) => node({
36
- kind: "table",
37
- value,
38
- ...options
39
- });
40
- const map = (value, options) => node({
41
- kind: "map",
42
- value,
43
- ...options
44
- });
45
- const list = (value, config) => node({
46
- kind: "list",
47
- keyOf: config.keyOf,
48
- value
49
- });
50
- const tree = (value) => node({
51
- kind: "tree",
52
- value
53
- });
54
- const paths = /* @__PURE__ */ new WeakMap();
55
- const pathProxy = (nodes, address, owner) => {
56
- const proxy = new Proxy({}, { get: (_target, property) => {
57
- if (typeof property !== "string") return void 0;
58
- if (nodes.every((node) => node.kind === "table" || node.kind === "map") && property === "item") return (id) => {
59
- if (typeof id !== "string") throw new TypeError("Collection keys must be strings.");
60
- for (const node of nodes) if (node.kind === "table" || node.kind === "map") {
61
- const invalid = require_scope.checkKey(node.key, id, [...address, id]);
62
- if (invalid) throw new TypeError(invalid.message);
63
- }
64
- return pathProxy(nodes.map((node) => node.value), [...address, id], owner);
65
- };
66
- const children = nodes.flatMap((node) => {
67
- if (node.kind === "object") return Object.hasOwn(node.shape, property) ? [node.shape[property]] : [];
68
- if (node.kind !== "variant") return [];
69
- if (property === node.tag) return [field()];
70
- return Object.values(node.variants).flatMap((branch) => Object.hasOwn(branch.shape, property) ? [branch.shape[property]] : []);
71
- });
72
- if (!children.length) throw new TypeError(`Invalid schema path segment: ${property}`);
73
- return pathProxy(children, [...address, property], owner);
74
- } });
75
- paths.set(proxy, {
76
- nodes,
77
- address,
78
- owner
79
- });
80
- return proxy;
81
- };
82
- const compilePath = (owner, kind, pick) => {
83
- const scope = {};
84
- const value = pick(pathProxy([owner], [], scope));
85
- const selected = typeof value === "object" && value !== null ? paths.get(value) : void 0;
86
- if (!selected || selected.owner !== scope) throw new TypeError("Selector must return a path from its callback.");
87
- if (kind === "collection" && !selected.nodes.every((node) => node.kind === "table" || node.kind === "map")) throw new TypeError("Collection selectors require a table or map path.");
88
- return Object.freeze({
89
- kind,
90
- schema: owner,
91
- address: Object.freeze([...selected.address])
92
- });
93
- };
94
- //#endregion
2
+ const require_issue = require("./issue-DjK1xp5K.cjs");
3
+ const require_access = require("./access-Bienf28h.cjs");
4
+ const require_advanced = require("./advanced-BRL5pN0y.cjs");
5
+ const require_notification = require("./notification-DJfouMEb.cjs");
6
+ const require_driver = require("./driver-Djr3BVvo.cjs");
95
7
  //#region core/src/impact.ts
96
8
  const queries = /* @__PURE__ */ new WeakMap();
97
9
  const affectsTarget = (impact, value) => queries.get(impact).affects(value);
@@ -183,8 +95,8 @@ const createImpact = (schema, changes) => {
183
95
  };
184
96
  const impact = Object.freeze({
185
97
  kind: reset ? "reset" : "incremental",
186
- affects: (pick) => implementation.affects(compilePath(schema, "value", pick)),
187
- collection: (pick) => implementation.collection(compilePath(schema, "collection", pick))
98
+ affects: (pick) => implementation.affects(require_advanced.compilePath(schema, "value", pick)),
99
+ collection: (pick) => implementation.collection(require_advanced.compilePath(schema, "collection", pick))
188
100
  });
189
101
  queries.set(impact, implementation);
190
102
  return impact;
@@ -278,7 +190,7 @@ const createHistory = (input) => {
278
190
  current: () => state,
279
191
  revision: () => revision,
280
192
  subscribe: (listener) => {
281
- if (disposed) throw new require_notification.DocumentDisposedError();
193
+ if (disposed) throw new require_access.DocumentDisposedError();
282
194
  listeners.add(listener);
283
195
  return () => {
284
196
  listeners.delete(listener);
@@ -373,7 +285,7 @@ const installMember = (parent, key, present, value) => {
373
285
  });
374
286
  };
375
287
  const orderOf = (node, value) => {
376
- if (node.kind === "list") return value.map(node.keyOf);
288
+ if (node.kind === "list") return require_issue.toArray(require_issue.indexedKeys(value, node.keyOf));
377
289
  if (node.kind === "table") return [...value.ids];
378
290
  throw new Error("Order requires a table or list.");
379
291
  };
@@ -403,8 +315,8 @@ const copyNode = (node) => ({
403
315
  });
404
316
  const transition = (node, key, beforePresent, before, afterPresent, after) => {
405
317
  if (beforePresent === afterPresent && (!beforePresent || Object.is(before, after))) return;
406
- if (node.kind !== "field" && beforePresent === afterPresent && require_scope.equalValue(node, before, after)) return;
407
- const value = afterPresent ? node.kind === "field" ? after : require_scope.copyValue(node, after) : void 0;
318
+ if (node.kind !== "field" && beforePresent === afterPresent && require_access.equalValue(node, before, after)) return;
319
+ const value = afterPresent ? node.kind === "field" ? after : require_access.copyValue(node, after) : void 0;
408
320
  require_issue.profile.recorder("transitions");
409
321
  return !beforePresent ? {
410
322
  key,
@@ -477,7 +389,7 @@ const restore = (fact, schema, root, skip = 0) => {
477
389
  const at = skip ? fact.at.slice(skip) : fact.at;
478
390
  if (fact.kind === "member") {
479
391
  const member = fact.member;
480
- const value = member.present ? require_scope.copyValue(member.node, member.value) : void 0;
392
+ const value = member.present ? require_access.copyValue(member.node, member.value) : void 0;
481
393
  if (!at.length) return value;
482
394
  const location = require_issue.resolveLocated(schema, root, at);
483
395
  if (!location) throw new Error("Cannot restore an unresolved member.");
@@ -496,7 +408,7 @@ const restore = (fact, schema, root, skip = 0) => {
496
408
  return root;
497
409
  };
498
410
  const reconstructBefore = (node, current, records, offset) => {
499
- let before = require_scope.copyValue(node, current);
411
+ let before = require_access.copyValue(node, current);
500
412
  for (let i = records.length - 1; i >= 0; i--) if (records[i].kind !== "order") before = restore(records[i], node, before, offset);
501
413
  for (const record of records) if (record.kind === "order") before = restore(record, node, before, offset);
502
414
  return before;
@@ -515,7 +427,7 @@ const sealMembers = (fact, changes) => {
515
427
  }
516
428
  let order;
517
429
  if (fact.order) {
518
- const current = fact.node.kind === "list" ? require_issue.keys(fact.value, fact.node.keyOf) : fact.value.ids;
430
+ const current = fact.node.kind === "list" ? require_issue.indexedKeys(fact.value, fact.node.keyOf) : fact.value.ids;
519
431
  if (!require_issue.equal(fact.order, current)) {
520
432
  const after = orderOf(fact.node, fact.value);
521
433
  require_issue.profile.recorder("publishedOrderItems", after.length);
@@ -536,7 +448,7 @@ const sealTree = (fact, state, changes) => {
536
448
  for (const [id, before] of fact.nodes) {
537
449
  const after = Object.hasOwn(tree.nodes, id) ? tree.nodes[id] : void 0;
538
450
  if (!before && !after) continue;
539
- if (before && after && before.parentId === after.parentId && require_issue.equal(before.children, after.children) && Object.hasOwn(before, "value") === Object.hasOwn(after, "value") && require_scope.equalValue(node.value, before.value, after.value)) continue;
451
+ if (before && after && before.parentId === after.parentId && require_issue.equal(before.children, after.children) && Object.hasOwn(before, "value") === Object.hasOwn(after, "value") && require_access.equalValue(node.value, before.value, after.value)) continue;
540
452
  nodes.push(!before ? {
541
453
  id,
542
454
  kind: "added",
@@ -744,7 +656,7 @@ var ChangeRecorder = class {
744
656
  const fact = facts[i];
745
657
  if (fact.kind === "members") for (const member of fact.members?.values() ?? []) {
746
658
  if (!member) continue;
747
- installMember(fact.parent, require_issue.memberKey(fact, member.key), member.present, member.present ? require_scope.copyValue(member.node, member.value) : void 0);
659
+ installMember(fact.parent, require_issue.memberKey(fact, member.key), member.present, member.present ? require_access.copyValue(member.node, member.value) : void 0);
748
660
  }
749
661
  else if (fact.kind === "tree") restore(fact, this.state.schema, this.state.document);
750
662
  }
@@ -752,12 +664,12 @@ var ChangeRecorder = class {
752
664
  }
753
665
  seal() {
754
666
  if (this.resetBefore) {
755
- if (require_scope.equalValue(this.state.schema, this.resetBefore.value, this.state.document)) return require_driver.sealChanges([]);
667
+ if (require_access.equalValue(this.state.schema, this.resetBefore.value, this.state.document)) return require_driver.sealChanges([]);
756
668
  require_issue.profile.recorder("sealed");
757
669
  return require_driver.sealChanges([{
758
670
  kind: "reset",
759
671
  before: this.resetBefore.value,
760
- after: require_scope.copyValue(this.state.schema, this.state.document)
672
+ after: require_access.copyValue(this.state.schema, this.state.document)
761
673
  }]);
762
674
  }
763
675
  const changes = [];
@@ -784,7 +696,7 @@ var MutationSession = class {
784
696
  this.resolvedRoot = state.document;
785
697
  }
786
698
  validate(node, value, at) {
787
- const issue = require_scope.checkValue(node, value, at);
699
+ const issue = require_access.checkValue(node, value, at);
788
700
  if (issue) require_issue.invalidValue(issue);
789
701
  }
790
702
  resolveContainer(at) {
@@ -806,7 +718,7 @@ var MutationSession = class {
806
718
  this.validate(this.state.schema, value, at);
807
719
  if (Object.is(this.state.document, value)) return;
808
720
  this.recorder.reset();
809
- this.state.document = require_scope.copyValue(this.state.schema, value);
721
+ this.state.document = require_access.copyValue(this.state.schema, value);
810
722
  this.invalidate();
811
723
  return;
812
724
  }
@@ -848,12 +760,12 @@ var MutationSession = class {
848
760
  const previous = container.parent[physicalKey];
849
761
  if (existed === present && (!present || Object.is(previous, value))) return false;
850
762
  if (parentNode.kind === "map" || parentNode.kind === "table") {
851
- const issue = require_scope.checkKey(parentNode.key, key, []);
763
+ const issue = require_access.checkKey(parentNode.key, key, []);
852
764
  if (issue) return require_issue.fail(container.at.concat(key, ...issue.address), "invalid-key", issue.message);
853
765
  }
854
766
  if (present) {
855
767
  if (node.kind === "field") {
856
- const issue = require_scope.checkValue(node, value, []);
768
+ const issue = require_access.checkValue(node, value, []);
857
769
  if (issue) return require_issue.invalidValue(issue, container.at.concat(key, ...issue.address));
858
770
  } else this.validate(node, value, container.at.concat(key));
859
771
  if (parentNode.kind === "list" && parentNode.keyOf(value) !== key) return require_issue.fail(container.at.concat(key), "invalid-list-key", "Replacing an item must retain its addressed key.");
@@ -861,7 +773,7 @@ var MutationSession = class {
861
773
  const membershipChanged = entry && existed !== present;
862
774
  if (membershipChanged && (parentNode.kind === "list" || parentNode.kind === "table")) this.recorder.order(container);
863
775
  this.recorder.member(container, key, member, physicalKey);
864
- const next = present ? node.kind === "field" ? value : require_scope.copyValue(node, value) : void 0;
776
+ const next = present ? node.kind === "field" ? value : require_access.copyValue(node, value) : void 0;
865
777
  installMember(container.parent, physicalKey, present, next);
866
778
  if (node.kind !== "field" || membershipChanged) this.invalidate();
867
779
  return membershipChanged;
@@ -931,18 +843,18 @@ function apply(session, changes, direction) {
931
843
  //#region core/src/runtime.ts
932
844
  const createDocument = (input) => {
933
845
  if (input.schema.kind !== "object") throw new TypeError("Document schema must be a root object node.");
934
- const invalid = require_scope.checkValue(input.schema, input.initial);
935
- if (invalid) throw new require_scope.ParseError(invalid);
846
+ const invalid = require_access.checkValue(input.schema, input.initial);
847
+ if (invalid) throw new require_access.ParseError(invalid);
936
848
  const state = {
937
849
  schema: input.schema,
938
- document: require_scope.copyValue(input.schema, input.initial),
850
+ document: require_access.copyValue(input.schema, input.initial),
939
851
  disposed: false
940
852
  };
941
853
  let revision = 0, busy = false;
942
854
  let runtime, notification;
943
855
  const idle = () => {
944
- if (state.disposed) throw new require_notification.DocumentDisposedError();
945
- if (busy || require_notification.accessOf(runtime).projectionLocks) throw new require_notification.DocumentReentrancyError();
856
+ if (state.disposed) throw new require_access.DocumentDisposedError();
857
+ if (busy || require_access.accessOf(runtime).projectionLocks) throw new require_access.DocumentReentrancyError();
946
858
  };
947
859
  const writable = (intent) => {
948
860
  idle();
@@ -1000,7 +912,7 @@ const createDocument = (input) => {
1000
912
  issues: [error.issue],
1001
913
  revision
1002
914
  };
1003
- if (intent.kind === "update" && error instanceof require_notification.TransactionRejected) return {
915
+ if (intent.kind === "update" && error instanceof require_access.TransactionRejected) return {
1004
916
  status: "rejected",
1005
917
  issues: error.issues,
1006
918
  revision
@@ -1033,7 +945,7 @@ const createDocument = (input) => {
1033
945
  kind: "update",
1034
946
  source
1035
947
  }, options?.history ?? true, (session) => {
1036
- value = run(require_scope.createAccess({
948
+ value = run(require_access.createAccess({
1037
949
  state,
1038
950
  session
1039
951
  }));
@@ -1059,15 +971,15 @@ const createDocument = (input) => {
1059
971
  }, source !== "remote", (session) => session.replace([], value));
1060
972
  },
1061
973
  snapshot: () => {
1062
- if (state.disposed) throw new require_notification.DocumentDisposedError();
1063
- return require_scope.copyValue(input.schema, state.document);
974
+ if (state.disposed) throw new require_access.DocumentDisposedError();
975
+ return require_access.copyValue(input.schema, state.document);
1064
976
  },
1065
977
  subscribe: ((pick, listener) => {
1066
- if (state.disposed) throw new require_notification.DocumentDisposedError();
978
+ if (state.disposed) throw new require_access.DocumentDisposedError();
1067
979
  if (!listener) return require_notification.subscribeRoot(notification, pick);
1068
980
  const picks = Array.isArray(pick) ? pick : [pick];
1069
981
  if (!picks.length) throw new TypeError("Expected at least one subscription path.");
1070
- return require_notification.subscribeTargets(notification, picks.map((p) => compilePath(input.schema, "value", p)), listener);
982
+ return require_notification.subscribeTargets(notification, picks.map((p) => require_advanced.compilePath(input.schema, "value", p)), listener);
1071
983
  }),
1072
984
  history: history.api,
1073
985
  dispose: () => {
@@ -1082,7 +994,7 @@ const createDocument = (input) => {
1082
994
  }
1083
995
  }
1084
996
  };
1085
- require_notification.bindRuntimeAccess(runtime, state);
997
+ require_access.bindRuntimeAccess(runtime, state);
1086
998
  require_driver.bindRuntimeDriver(runtime);
1087
999
  notification = require_notification.createNotification(runtime);
1088
1000
  require_notification.bindDocumentReadable(history.api, runtime);
@@ -1096,1953 +1008,1227 @@ const asReadable = (runtime) => {
1096
1008
  revision: runtime.revision,
1097
1009
  subscribe: runtime.subscribe
1098
1010
  };
1099
- require_notification.bindRuntimeAccess(readable, require_notification.accessOf(runtime));
1011
+ require_access.bindRuntimeAccess(readable, require_access.accessOf(runtime));
1100
1012
  require_notification.shareNotification(runtime, readable);
1101
1013
  return Object.freeze(readable);
1102
1014
  };
1103
1015
  //#endregion
1104
1016
  //#region core/src/projection/select.ts
1105
- const read = (runtime, selector) => require_notification.readWith(runtime, selector);
1017
+ const read = (runtime, selector) => require_access.readWith(runtime, selector);
1106
1018
  //#endregion
1107
- //#region core/src/projection/source.ts
1108
- const createSources = (scheduler) => {
1109
- const documents = /* @__PURE__ */ new Map();
1110
- const readables = /* @__PURE__ */ new Map();
1111
- const externalSources = /* @__PURE__ */ new WeakMap();
1112
- const isCollectionSource = (value) => value.kind === "collection";
1113
- const document = (runtime) => {
1114
- scheduler.assertIdle();
1115
- const state = require_notification.accessOf(runtime);
1116
- if (state.disposed) throw new Error("Document has been disposed.");
1117
- const existing = documents.get(state);
1118
- if (existing) return existing;
1119
- const bindings = [];
1120
- let batch;
1121
- const make = (input) => {
1122
- const collection = "collection" in input ? input.collection : void 0;
1123
- const targets = "targets" in input ? input.targets : [input.collection];
1124
- if (state.disposed) throw new Error("Document has been disposed.");
1125
- targets.forEach((value) => {
1126
- if (!require_notification.belongs(value, state.schema)) throw new Error("Projection target belongs to another schema.");
1127
- });
1128
- const old = bindings.find((entry) => Boolean(collection) === isCollectionSource(entry.handle) && entry.targets.length === targets.length && entry.targets.every((value, i) => require_notification.same(value, targets[i])));
1129
- if (old) return old.handle;
1130
- let reset = false;
1131
- let orderChanged = false;
1132
- const keys = /* @__PURE__ */ new Set();
1133
- let baseline;
1134
- let baselineIds = Object.freeze([]);
1135
- let nextBaseline;
1136
- let nextBaselineIds = Object.freeze([]);
1137
- const collectionNode = collection ? require_issue.nodeAt(state.schema, collection.address, state.document) : void 0;
1138
- const entryNode = collectionNode && (collectionNode.kind === "map" || collectionNode.kind === "table") ? collectionNode.value : void 0;
1139
- const sameEntry = (left, right) => entryNode ? require_scope.equalValue(entryNode, left, right) : Object.is(left, right);
1140
- const record = {
1141
- consumers: /* @__PURE__ */ new Set(),
1142
- disposed: false,
1143
- fault: void 0,
1144
- revision: runtime.revision,
1145
- reset: () => reset,
1146
- clear: () => {
1147
- if (collection && nextBaseline) {
1148
- baseline = nextBaseline;
1149
- baselineIds = nextBaselineIds;
1150
- }
1151
- reset = false;
1152
- orderChanged = false;
1153
- batch = void 0;
1154
- keys.clear();
1155
- nextBaseline = void 0;
1156
- nextBaselineIds = Object.freeze([]);
1157
- },
1158
- context: (active) => {
1159
- require_advanced.assertScope(active);
1160
- if (state.disposed) throw new Error("Document has been disposed.");
1161
- const context = {
1162
- state,
1163
- active
1164
- };
1165
- const rawRead = collection ? require_scope.collectionAccess(context, collection.address) : void 0;
1166
- const snapshots = collection ? /* @__PURE__ */ new Map() : void 0;
1167
- const read = collection ? Object.freeze({
1168
- get: (key) => {
1169
- require_advanced.assertScope(active);
1170
- if (!snapshots.has(key)) snapshots.set(key, require_scope.snapshot(rawRead.get(key)));
1171
- return snapshots.get(key);
1172
- },
1173
- has: (key) => {
1174
- require_advanced.assertScope(active);
1175
- return rawRead.has(key);
1176
- },
1177
- ids: () => {
1178
- require_advanced.assertScope(active);
1179
- return rawRead.ids();
1180
- }
1181
- }) : require_scope.createAccess(context);
1182
- if (!collection) return Object.freeze({
1183
- kind: "document",
1184
- read,
1185
- revision: runtime.revision(),
1186
- reset: record.reset(),
1187
- cause: batch?.cause
1188
- });
1189
- if (!baseline) {
1190
- const collectionRead = read;
1191
- baseline = /* @__PURE__ */ new Map();
1192
- baselineIds = Object.freeze(require_scope.collectionAccess(context, collection.address).ids().slice());
1193
- for (const key of baselineIds) {
1194
- const value = collectionRead.get(key);
1195
- baseline.set(key, value);
1196
- }
1197
- }
1198
- const changed = [...keys];
1199
- const added = [];
1200
- const updated = [];
1201
- const removed = [];
1202
- const next = new Map(baseline);
1203
- const collectionRead = read;
1204
- for (const key of changed) {
1205
- const beforePresent = baseline.has(key);
1206
- const afterPresent = collectionRead.has(key);
1207
- const afterValue = afterPresent ? collectionRead.get(key) : void 0;
1208
- if (!beforePresent && afterPresent) {
1209
- added.push({
1210
- key,
1211
- after: afterValue
1212
- });
1213
- next.set(key, afterValue);
1214
- } else if (beforePresent && !afterPresent) {
1215
- removed.push({
1216
- key,
1217
- before: baseline.get(key)
1218
- });
1219
- next.delete(key);
1220
- } else if (beforePresent && afterPresent) {
1221
- const beforeValue = baseline.get(key);
1222
- if (!sameEntry(beforeValue, afterValue)) updated.push({
1223
- key,
1224
- before: beforeValue,
1225
- after: afterValue
1226
- });
1227
- next.set(key, afterValue);
1228
- }
1229
- }
1230
- const ids = record.reset() || orderChanged ? Object.freeze([...collectionRead.ids()]) : baselineIds;
1231
- if (record.reset()) {
1232
- next.clear();
1233
- for (const key of ids) {
1234
- const value = collectionRead.get(key);
1235
- next.set(key, value);
1236
- }
1237
- }
1238
- let order;
1239
- if (orderChanged) {
1240
- const beforeKeys = new Set(baselineIds);
1241
- const afterKeys = new Set(ids);
1242
- const beforeCommon = baselineIds.filter((key) => afterKeys.has(key));
1243
- const afterCommon = ids.filter((key) => beforeKeys.has(key));
1244
- if (beforeCommon.some((key, index) => afterCommon[index] !== key)) order = {
1245
- before: Object.freeze([...baselineIds]),
1246
- after: ids
1247
- };
1248
- }
1249
- const change = record.reset() ? Object.freeze({ kind: "reset" }) : added.length || updated.length || removed.length || order ? Object.freeze({
1250
- kind: "incremental",
1251
- added: Object.freeze(added),
1252
- updated: Object.freeze(updated),
1253
- removed: Object.freeze(removed),
1254
- ...order ? { order: Object.freeze(order) } : {}
1255
- }) : void 0;
1256
- nextBaseline = next;
1257
- nextBaselineIds = ids;
1258
- return Object.freeze({
1259
- kind: "collection",
1260
- read,
1261
- change,
1262
- revision: runtime.revision(),
1263
- cause: batch?.cause
1264
- });
1265
- }
1266
- };
1267
- const handle = collection ? { kind: "collection" } : {
1268
- collection: (pick) => {
1269
- scheduler.assertIdle();
1270
- return make({ collection: compilePath(state.schema, "collection", pick) });
1271
- },
1272
- targets: (...selected) => {
1273
- scheduler.assertIdle();
1274
- if (!selected.length) throw new TypeError("Expected at least one target.");
1275
- return make({ targets: selected.map((pick) => compilePath(state.schema, "value", pick)) });
1276
- }
1277
- };
1278
- Object.freeze(handle);
1279
- scheduler.register(handle, record);
1280
- const receive = (commit) => {
1281
- reset ||= commit.impact.kind === "reset";
1282
- if (collection) {
1283
- const change = collectionImpact(commit.impact, collection);
1284
- if (change.kind === "reset") reset = true;
1285
- else {
1286
- change.added.forEach((key) => keys.add(key));
1287
- change.removed.forEach((key) => keys.add(key));
1288
- change.updated.forEach((key) => keys.add(key));
1289
- orderChanged ||= Boolean(change.added.size || change.removed.size || change.orderChanged);
1290
- }
1291
- }
1292
- };
1293
- bindings.push({
1294
- handle,
1295
- targets,
1296
- record,
1297
- receive
1298
- });
1299
- return handle;
1300
- };
1301
- const handle = make({ targets: [] });
1302
- const guard = (locked) => {
1303
- state.projectionLocks = (state.projectionLocks ?? 0) + (locked ? 1 : -1);
1304
- };
1305
- scheduler.guards.add(guard);
1306
- const unsubscribe = require_notification.attachProjection(runtime, {
1307
- capture: (commit) => {
1308
- for (const binding of bindings) if (!binding.targets.length || binding.targets.some((value) => affectsTarget(commit.impact, value))) {
1309
- binding.receive(commit);
1310
- batch = scheduler.batchContext();
1311
- scheduler.capture(binding.record);
1312
- }
1313
- },
1314
- settle: scheduler.settle,
1315
- flush: scheduler.flush,
1316
- dispose: () => {
1317
- bindings.forEach(({ record }) => {
1318
- record.fault = new require_advanced.ProjectionError("source", "document disposed", [runtime.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
1319
- scheduler.capture(record);
1320
- });
1321
- scheduler.run();
1322
- }
1323
- });
1324
- scheduler.cleanups.add(() => {
1325
- unsubscribe();
1326
- scheduler.guards.delete(guard);
1327
- });
1328
- documents.set(state, handle);
1329
- return handle;
1330
- };
1331
- const valueSource = (initial, equal) => {
1332
- let value = initial;
1333
- let revision = 0;
1334
- let cause;
1335
- let reset = false;
1336
- const handle = Object.freeze({});
1337
- const record = {
1338
- consumers: /* @__PURE__ */ new Set(),
1339
- disposed: false,
1340
- fault: void 0,
1341
- revision: () => revision,
1342
- reset: () => reset,
1343
- clear: () => {
1344
- cause = void 0;
1345
- reset = false;
1019
+ //#region core/src/projection/value-output.ts
1020
+ const unset = Symbol("projection-value-unset");
1021
+ /** Owns staged and published state for one scalar Projection output. */
1022
+ const createValueOutput = () => {
1023
+ let value;
1024
+ let staged = unset;
1025
+ let next;
1026
+ let initialized = false;
1027
+ let revision = 0;
1028
+ let changed = false;
1029
+ let reset = false;
1030
+ const listeners = /* @__PURE__ */ new Set();
1031
+ const begin = (active, initialize) => {
1032
+ staged = unset;
1033
+ next = value;
1034
+ changed = false;
1035
+ reset = initialize;
1036
+ return {
1037
+ previous: initialized ? value : void 0,
1038
+ next: () => {
1039
+ require_advanced.assertScope(active);
1040
+ if (staged !== unset) return staged;
1041
+ return initialize ? void 0 : initialized ? value : void 0;
1346
1042
  },
1347
- context: (active) => {
1043
+ output: Object.freeze({ set: (candidate) => {
1348
1044
  require_advanced.assertScope(active);
1349
- return Object.freeze({
1350
- kind: "value",
1351
- value,
1352
- revision,
1353
- reset,
1354
- cause
1355
- });
1356
- }
1357
- };
1358
- scheduler.register(handle, record);
1359
- const accept = (next, metadata) => {
1360
- scheduler.assertIdle();
1361
- const recovering = record.fault !== void 0;
1362
- const eventful = metadata !== void 0 && (metadata.reset === true || metadata.revision !== void 0 && metadata.revision !== revision || metadata.cause !== void 0);
1363
- if (equal(value, next) && !recovering && !eventful) return;
1364
- record.fault = void 0;
1365
- value = next;
1366
- revision = metadata?.revision ?? revision + 1;
1367
- cause = metadata?.cause ?? scheduler.batchContext()?.cause ?? cause;
1368
- reset ||= metadata?.reset ?? false;
1369
- scheduler.capture(record);
1370
- };
1371
- return {
1372
- source: handle,
1373
- accept,
1374
- set(next) {
1375
- accept(next);
1376
- scheduler.run();
1377
- }
1045
+ staged = candidate;
1046
+ } })
1378
1047
  };
1379
1048
  };
1380
- const collectionInput = (initial) => {
1381
- const values = new Map(initial);
1382
- const touched = /* @__PURE__ */ new Map();
1383
- let beforeIds;
1384
- let revision = 0;
1385
- let cause;
1386
- const handle = Object.freeze({ kind: "collection" });
1387
- const change = () => {
1388
- const added = [];
1389
- const updated = [];
1390
- const removed = [];
1391
- for (const [key, before] of touched) {
1392
- const afterPresent = values.has(key);
1393
- if (!before.present && afterPresent) added.push({
1394
- key,
1395
- after: values.get(key)
1396
- });
1397
- else if (before.present && !afterPresent) removed.push({
1398
- key,
1399
- before: before.value
1400
- });
1401
- else if (before.present && afterPresent && !Object.is(before.value, values.get(key))) updated.push({
1402
- key,
1403
- before: before.value,
1404
- after: values.get(key)
1405
- });
1406
- }
1407
- let order;
1408
- if (beforeIds) {
1409
- const afterIds = [...values.keys()];
1410
- const beforeKeys = new Set(beforeIds);
1411
- const afterKeys = new Set(afterIds);
1412
- const beforeCommon = beforeIds.filter((key) => afterKeys.has(key));
1413
- const afterCommon = afterIds.filter((key) => beforeKeys.has(key));
1414
- if (beforeCommon.some((key, index) => key !== afterCommon[index])) order = Object.freeze({
1415
- before: beforeIds,
1416
- after: Object.freeze(afterIds)
1417
- });
1418
- }
1419
- if (!added.length && !updated.length && !removed.length && !order) return void 0;
1049
+ const seal = (nextReset, isEqual) => {
1050
+ if (reset && staged === unset) throw new TypeError("Value output must be initialized on reset.");
1051
+ if (staged === unset) {
1052
+ reset = nextReset;
1053
+ changed = false;
1054
+ next = value;
1055
+ return false;
1056
+ }
1057
+ const candidate = staged;
1058
+ changed = !initialized || !isEqual(value, candidate);
1059
+ next = changed ? candidate : value;
1060
+ reset = nextReset;
1061
+ return changed;
1062
+ };
1063
+ return {
1064
+ begin,
1065
+ seal,
1066
+ context: (active, cause) => {
1067
+ require_advanced.assertScope(active);
1420
1068
  return Object.freeze({
1421
- kind: "incremental",
1422
- added: Object.freeze(added),
1423
- updated: Object.freeze(updated),
1424
- removed: Object.freeze(removed),
1425
- ...order ? { order } : {}
1069
+ kind: "value",
1070
+ value: next,
1071
+ revision,
1072
+ reset,
1073
+ cause
1426
1074
  });
1427
- };
1428
- const record = {
1429
- consumers: /* @__PURE__ */ new Set(),
1430
- disposed: false,
1431
- fault: void 0,
1432
- revision: () => revision,
1433
- reset: () => false,
1434
- shouldSettle: () => change() !== void 0,
1435
- clear: () => {
1436
- touched.clear();
1437
- beforeIds = void 0;
1438
- cause = void 0;
1439
- },
1440
- context: (active) => {
1441
- require_advanced.assertScope(active);
1442
- const read = Object.freeze({
1443
- get: (key) => {
1444
- require_advanced.assertScope(active);
1445
- return values.get(key);
1446
- },
1447
- has: (key) => {
1448
- require_advanced.assertScope(active);
1449
- return values.has(key);
1450
- },
1451
- ids: () => {
1452
- require_advanced.assertScope(active);
1453
- return Object.freeze([...values.keys()]);
1454
- }
1455
- });
1456
- return Object.freeze({
1457
- kind: "collection",
1458
- read,
1459
- change: change(),
1460
- revision,
1461
- cause
1462
- });
1463
- }
1464
- };
1465
- scheduler.register(handle, record);
1466
- return {
1467
- source: handle,
1468
- update(run) {
1469
- scheduler.assertIdle();
1470
- const staged = /* @__PURE__ */ new Map();
1471
- let active = true;
1472
- const draft = Object.freeze({
1473
- get: (key) => {
1474
- require_advanced.assertScope(() => active);
1475
- const entry = staged.get(key);
1476
- return entry ? entry.present ? entry.value : void 0 : values.get(key);
1477
- },
1478
- has: (key) => {
1479
- require_advanced.assertScope(() => active);
1480
- const entry = staged.get(key);
1481
- return entry ? entry.present : values.has(key);
1482
- },
1483
- set: (key, value) => {
1484
- require_advanced.assertScope(() => active);
1485
- if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1486
- staged.set(key, {
1487
- present: true,
1488
- value
1489
- });
1490
- },
1491
- remove: (key) => {
1492
- require_advanced.assertScope(() => active);
1493
- if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1494
- staged.set(key, { present: false });
1495
- }
1496
- });
1497
- try {
1498
- require_advanced.assertSynchronous(run(draft));
1499
- } finally {
1500
- active = false;
1501
- }
1502
- let changed = false;
1503
- for (const [key, entry] of staged) {
1504
- const present = values.has(key);
1505
- if (entry.present ? present && Object.is(values.get(key), entry.value) : !present) continue;
1506
- if (!touched.has(key)) touched.set(key, {
1507
- present,
1508
- value: values.get(key)
1509
- });
1510
- if (!beforeIds && present !== entry.present) beforeIds = Object.freeze([...values.keys()]);
1511
- if (entry.present) values.set(key, entry.value);
1512
- else values.delete(key);
1513
- changed = true;
1514
- }
1515
- if (!changed) return;
1516
- revision++;
1517
- cause = scheduler.batchContext()?.cause ?? cause;
1518
- scheduler.capture(record);
1519
- scheduler.run();
1520
- }
1521
- };
1075
+ },
1076
+ current: (check) => {
1077
+ check();
1078
+ return value;
1079
+ },
1080
+ revision: () => revision,
1081
+ reset: () => reset,
1082
+ publish: () => {
1083
+ if (changed && initialized) revision++;
1084
+ if (changed) value = next;
1085
+ initialized = true;
1086
+ },
1087
+ emit: (call) => {
1088
+ require_issue.profile.materialized.notification();
1089
+ Array.from(listeners).forEach((listener) => call(listener));
1090
+ },
1091
+ clear: () => {
1092
+ staged = unset;
1093
+ next = value;
1094
+ changed = false;
1095
+ reset = false;
1096
+ },
1097
+ release: () => {
1098
+ listeners.clear();
1099
+ staged = unset;
1100
+ value = next = void 0;
1101
+ initialized = false;
1102
+ revision = 0;
1103
+ changed = false;
1104
+ reset = false;
1105
+ },
1106
+ subscribe: (listener) => listeners.add(listener),
1107
+ unsubscribe: (listener) => listeners.delete(listener)
1522
1108
  };
1523
- const externalValueSource = (port, equal) => {
1524
- scheduler.assertIdle();
1525
- const old = externalSources.get(port);
1526
- if (old) return old;
1527
- const input = valueSource(port.current(), equal);
1528
- const fail = (cause) => {
1529
- const record = scheduler.source(input.source);
1530
- record.fault = new require_advanced.ProjectionError("source", "external source", [record.revision()], cause);
1531
- scheduler.capture(record);
1532
- };
1533
- let unsubscribe;
1534
- try {
1535
- unsubscribe = port.subscribe((event) => {
1536
- try {
1537
- input.accept(event.value, {
1538
- revision: event.revision,
1539
- reset: event.reset ?? false,
1540
- cause: event.cause
1541
- });
1542
- } catch (cause) {
1543
- fail(cause);
1544
- }
1545
- scheduler.run();
1546
- });
1547
- } catch (cause) {
1548
- unsubscribe?.();
1549
- scheduler.unregisterSource(input.source);
1550
- throw cause;
1551
- }
1552
- scheduler.cleanups.add(() => unsubscribe?.());
1553
- externalSources.set(port, input.source);
1554
- return input.source;
1109
+ };
1110
+ //#endregion
1111
+ //#region core/src/projection/processor.ts
1112
+ /** Materializes every derive/incremental/group definition through one processor lifecycle. */
1113
+ const createProcessor = (scheduler, definition, dependencies) => {
1114
+ scheduler.assertIdle();
1115
+ let processor;
1116
+ let instance;
1117
+ let cause;
1118
+ const check = () => {
1119
+ if (!scheduler.active || processor.disposed) throw new require_advanced.ProjectionDisposedError();
1120
+ if (processor.fault) throw processor.fault;
1555
1121
  };
1556
- const externalCollectionSource = (port) => {
1557
- scheduler.assertIdle();
1558
- const old = externalSources.get(port);
1559
- if (old) return old;
1560
- let read = port.current();
1561
- let previous = read;
1562
- let revision = port.revision();
1563
- let change;
1564
- let reset = false;
1565
- let orderChanged = false;
1566
- let cause;
1567
- let transitionKeys = /* @__PURE__ */ new Set();
1568
- let pending = false;
1569
- const handle = Object.freeze({ kind: "collection" });
1570
- const record = {
1571
- consumers: /* @__PURE__ */ new Set(),
1572
- disposed: false,
1573
- fault: void 0,
1574
- revision: () => revision,
1575
- reset: () => reset,
1576
- clear: () => {
1577
- previous = read;
1578
- pending = false;
1579
- change = void 0;
1580
- reset = false;
1581
- orderChanged = false;
1582
- cause = void 0;
1583
- transitionKeys.clear();
1584
- },
1585
- context: (active) => {
1586
- require_advanced.assertScope(active);
1587
- const currentRead = Object.freeze({
1588
- get: (key) => {
1589
- require_advanced.assertScope(active);
1590
- return read.get(key);
1122
+ const bound = definition.outputs.map((outputDefinition) => {
1123
+ if (outputDefinition.kind === "value") {
1124
+ const state = createValueOutput();
1125
+ return {
1126
+ kind: "value",
1127
+ definition: outputDefinition,
1128
+ state,
1129
+ output: {
1130
+ kind: "value",
1131
+ get owner() {
1132
+ return processor;
1591
1133
  },
1592
- has: (key) => {
1593
- require_advanced.assertScope(active);
1594
- return read.has(key);
1134
+ consumers: /* @__PURE__ */ new Set(),
1135
+ context: (active) => state.context(active, cause),
1136
+ current: () => state.current(check),
1137
+ revision: state.revision,
1138
+ reset: state.reset,
1139
+ subscribe: (listener) => {
1140
+ check();
1141
+ const wrapped = () => listener();
1142
+ state.subscribe(wrapped);
1143
+ return () => state.unsubscribe(wrapped);
1595
1144
  },
1596
- ids: () => {
1597
- require_advanced.assertScope(active);
1598
- return read.ids();
1599
- }
1600
- });
1601
- return Object.freeze({
1602
- kind: "collection",
1603
- read: currentRead,
1604
- change,
1605
- revision,
1606
- cause
1607
- });
1145
+ emit: state.emit,
1146
+ clear: state.clear,
1147
+ release: state.release
1148
+ }
1149
+ };
1150
+ }
1151
+ const state = require_advanced.createCollectionOutput();
1152
+ return {
1153
+ kind: "collection",
1154
+ definition: outputDefinition,
1155
+ state,
1156
+ output: {
1157
+ kind: "collection",
1158
+ get owner() {
1159
+ return processor;
1160
+ },
1161
+ consumers: /* @__PURE__ */ new Set(),
1162
+ context: (active) => state.context(active, cause),
1163
+ current: () => state.current(check),
1164
+ revision: state.revision,
1165
+ reset: state.reset,
1166
+ subscribe: (listener) => {
1167
+ check();
1168
+ const wrapped = (change) => listener(change);
1169
+ state.subscribe(wrapped);
1170
+ return () => state.unsubscribe(wrapped);
1171
+ },
1172
+ emit: state.emit,
1173
+ clear: state.clear,
1174
+ release: state.release
1608
1175
  }
1609
1176
  };
1610
- scheduler.register(handle, record);
1611
- const fail = (error) => {
1612
- record.fault = new require_advanced.ProjectionError("source", "external collection source", [revision], error);
1613
- scheduler.capture(record);
1614
- };
1615
- const deriveChange = () => {
1616
- if (reset) return Object.freeze({ kind: "reset" });
1617
- const added = [];
1618
- const updated = [];
1619
- const removed = [];
1620
- for (const key of transitionKeys) {
1621
- const beforePresent = previous.has(key);
1622
- const afterPresent = read.has(key);
1623
- const before = beforePresent ? previous.get(key) : void 0;
1624
- const after = afterPresent ? read.get(key) : void 0;
1625
- if (beforePresent === afterPresent && Object.is(before, after)) continue;
1626
- if (!beforePresent && afterPresent) added.push({
1627
- key,
1628
- after
1629
- });
1630
- else if (beforePresent && !afterPresent) removed.push({
1631
- key,
1632
- before
1633
- });
1634
- else if (beforePresent && afterPresent) updated.push({
1635
- key,
1636
- before,
1637
- after
1638
- });
1639
- }
1640
- let order;
1641
- if (orderChanged) {
1642
- const beforeIds = previous.ids();
1643
- const afterIds = read.ids();
1644
- const beforeKeys = new Set(beforeIds);
1645
- const afterKeys = new Set(afterIds);
1646
- const beforeCommon = beforeIds.filter((key) => afterKeys.has(key));
1647
- const afterCommon = afterIds.filter((key) => beforeKeys.has(key));
1648
- if (beforeCommon.some((key, index) => afterCommon[index] !== key)) order = Object.freeze({
1649
- before: Object.freeze([...beforeIds]),
1650
- after: Object.freeze([...afterIds])
1651
- });
1652
- }
1653
- if (!added.length && !removed.length && !updated.length && !order) return void 0;
1177
+ });
1178
+ const begin = (active, initialize) => Object.freeze(bound.map((entry) => {
1179
+ if (entry.kind === "value") {
1180
+ const evaluation = entry.state.begin(active, initialize);
1654
1181
  return Object.freeze({
1655
- kind: "incremental",
1656
- added: Object.freeze(added),
1657
- updated: Object.freeze(updated),
1658
- removed: Object.freeze(removed),
1659
- ...order ? { order } : {}
1660
- });
1661
- };
1662
- let unsubscribe;
1663
- try {
1664
- unsubscribe = port.subscribe((event) => {
1665
- try {
1666
- scheduler.assertIdle();
1667
- if (!pending) previous = event.previous;
1668
- read = port.current();
1669
- revision = event.revision;
1670
- if (event.impact?.kind === "incremental") {
1671
- event.impact.added.forEach((key) => transitionKeys.add(key));
1672
- event.impact.removed.forEach((key) => transitionKeys.add(key));
1673
- event.impact.updated.forEach((key) => transitionKeys.add(key));
1674
- orderChanged ||= Boolean(event.impact.added.size || event.impact.removed.size || event.impact.orderChanged);
1675
- } else if (!event.impact) {
1676
- previous.ids().forEach((key) => transitionKeys.add(key));
1677
- read.ids().forEach((key) => transitionKeys.add(key));
1678
- orderChanged = true;
1679
- }
1680
- reset ||= event.impact?.kind === "reset";
1681
- cause = event.cause ?? scheduler.batchContext()?.cause ?? cause;
1682
- change = deriveChange();
1683
- record.fault = void 0;
1684
- scheduler.capture(record);
1685
- pending = true;
1686
- } catch (error) {
1687
- fail(error);
1688
- }
1689
- scheduler.run();
1182
+ kind: "value",
1183
+ ...evaluation
1690
1184
  });
1691
- } catch (error) {
1692
- unsubscribe?.();
1693
- scheduler.unregisterSource(handle);
1694
- throw error;
1695
1185
  }
1696
- scheduler.cleanups.add(() => unsubscribe?.());
1697
- externalSources.set(port, handle);
1698
- return handle;
1186
+ const evaluation = entry.state.begin(active, initialize);
1187
+ return Object.freeze({
1188
+ kind: "collection",
1189
+ ...evaluation
1190
+ });
1191
+ }));
1192
+ const seal = (reset) => {
1193
+ const changed = [];
1194
+ for (const entry of bound) if (entry.state.seal(reset, entry.definition.equality)) changed.push(entry.output);
1195
+ return Object.freeze(changed);
1699
1196
  };
1700
- return {
1701
- dispose: () => {
1702
- documents.clear();
1703
- readables.clear();
1704
- },
1705
- document,
1706
- fromSource: (source, options) => externalValueSource(source, options?.isEqual ?? Object.is),
1707
- fromCollectionSource: (source) => externalCollectionSource(source),
1708
- input: (initial, options) => {
1709
- const { source, set } = valueSource(initial, options?.isEqual ?? Object.is);
1710
- return Object.freeze({
1711
- source,
1712
- set
1197
+ const run = (reset, runCause) => {
1198
+ cause = runCause;
1199
+ let active = true;
1200
+ try {
1201
+ const scopeActive = () => active;
1202
+ const sources = Object.freeze(dependencies.map((output) => output.context(scopeActive)));
1203
+ let outputs = begin(scopeActive, reset);
1204
+ if (!instance) instance = definition.create();
1205
+ require_issue.profile.materialized[reset ? "rebuilt" : "updated"]();
1206
+ let result = instance.evaluate({
1207
+ reset,
1208
+ cause: runCause,
1209
+ sources,
1210
+ outputs
1713
1211
  });
1714
- },
1715
- collectionInput,
1716
- fromReadable: (readable, options) => {
1717
- scheduler.assertIdle();
1718
- if (require_advanced.projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
1719
- const equal = options?.isEqual ?? Object.is;
1720
- let bindings = readables.get(readable);
1721
- const old = bindings?.get(equal);
1722
- if (old) return old.source;
1723
- const input = valueSource(readable.current(), equal);
1724
- if (bindings) {
1725
- bindings.set(equal, {
1726
- source: input.source,
1727
- accept: (value) => input.accept(value)
1212
+ require_advanced.assertSynchronous(result);
1213
+ if (result?.kind === "rebuild") {
1214
+ if (reset) throw new TypeError("Initial or reset projection processor cannot rebuild.");
1215
+ instance.release?.();
1216
+ instance = definition.create();
1217
+ outputs = begin(scopeActive, true);
1218
+ result = instance.evaluate({
1219
+ reset: true,
1220
+ cause: runCause,
1221
+ sources,
1222
+ outputs
1728
1223
  });
1729
- return input.source;
1224
+ require_advanced.assertSynchronous(result);
1225
+ if (result?.kind === "rebuild") throw new TypeError("Reset projection processor cannot rebuild again.");
1226
+ return seal(true);
1730
1227
  }
1731
- bindings = new Map([[equal, {
1732
- source: input.source,
1733
- accept: (value) => input.accept(value)
1734
- }]]);
1735
- const members = bindings;
1736
- const receive = (settle = true) => {
1737
- if (!scheduler.active) return;
1738
- let value;
1739
- try {
1740
- value = readable.current();
1741
- } catch (cause) {
1742
- for (const member of members.values()) {
1743
- const record = scheduler.source(member.source);
1744
- record.fault = new require_advanced.ProjectionError("source", "external readable", [record.revision()], cause);
1745
- scheduler.capture(record);
1746
- }
1747
- if (settle) scheduler.run();
1748
- return;
1749
- }
1750
- for (const member of members.values()) try {
1751
- member.accept(value);
1752
- } catch (cause) {
1753
- const record = scheduler.source(member.source);
1754
- record.fault = new require_advanced.ProjectionError("source", "external readable", [record.revision()], cause);
1755
- scheduler.capture(record);
1756
- }
1757
- if (settle) scheduler.run();
1758
- };
1759
- let unsubscribe;
1760
- let detach;
1761
- try {
1762
- const owner = require_notification.documentReadableOwner(readable);
1763
- if (owner) {
1764
- document(owner);
1765
- detach = require_notification.attachProjection(owner, {
1766
- capture: () => receive(false),
1767
- settle: scheduler.settle,
1768
- flush: scheduler.flush,
1769
- dispose: () => {
1770
- for (const member of members.values()) {
1771
- const record = scheduler.source(member.source);
1772
- record.fault = new require_advanced.ProjectionError("source", "document readable disposed", [record.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
1773
- scheduler.capture(record);
1774
- }
1775
- scheduler.run();
1776
- }
1777
- });
1778
- }
1779
- unsubscribe = readable.subscribe(() => receive());
1780
- input.accept(readable.current());
1781
- } catch (error) {
1782
- try {
1783
- unsubscribe?.();
1784
- detach?.();
1785
- } finally {
1786
- scheduler.unregisterSource(input.source);
1787
- }
1788
- throw error;
1789
- }
1790
- scheduler.cleanups.add(unsubscribe);
1791
- if (detach) scheduler.cleanups.add(detach);
1792
- readables.set(readable, bindings);
1793
- scheduler.run();
1794
- return input.source;
1795
- },
1796
- assertCollection: (handle) => {
1797
- scheduler.source(handle);
1798
- if (!isCollectionSource(handle)) throw new Error("map requires a collection source.");
1228
+ return seal(reset);
1229
+ } finally {
1230
+ active = false;
1799
1231
  }
1800
1232
  };
1801
- };
1802
- //#endregion
1803
- //#region core/src/projection/node.ts
1804
- const createNode = (scheduler, spec, behavior) => {
1805
- scheduler.assertIdle();
1806
- const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
1807
1233
  const order = scheduler.order();
1808
- const node = {
1809
- owner: void 0,
1810
- ...behavior,
1811
- publish: () => {
1812
- if (node.fault) behavior.clear();
1813
- else behavior.publish();
1814
- },
1234
+ processor = {
1235
+ kind: "processor",
1815
1236
  order,
1816
- name: spec.name ? `${spec.name} (${order})` : `projection-${order}`,
1817
- sources: entries.map(([, source]) => source),
1818
- consumers: /* @__PURE__ */ new Set(),
1819
- disposed: false,
1237
+ name: definition.name ? `${definition.name} (${order})` : `projection-${order}`,
1238
+ dependencies: Object.freeze([...dependencies]),
1239
+ outputs: Object.freeze(bound.map((entry) => entry.output)),
1820
1240
  fault: void 0,
1821
- evaluate: (build) => {
1822
- let active = true;
1823
- try {
1824
- const scopeActive = () => active;
1825
- const inputs = Object.fromEntries(entries.map(([key, source]) => [key, source.context(scopeActive)]));
1826
- return behavior.evaluate(inputs, build, scopeActive);
1827
- } finally {
1828
- active = false;
1829
- }
1830
- }
1831
- };
1832
- node.owner = node;
1833
- scheduler.initialize(() => {
1834
- const invalid = node.sources.find((source) => source.fault || source.disposed);
1835
- if (invalid) throw invalid.fault ?? new require_advanced.ProjectionDisposedError();
1836
- node.evaluate(true);
1837
- node.publish();
1838
- node.clear();
1839
- });
1840
- const check = () => {
1841
- if (node.disposed || !scheduler.active) throw new require_advanced.ProjectionDisposedError();
1842
- if (node.fault) throw node.fault;
1843
- };
1844
- return {
1845
- node,
1846
- check,
1847
- install(handle) {
1848
- scheduler.register(handle, node);
1849
- scheduler.addNode(node);
1241
+ disposed: false,
1242
+ evaluate: run,
1243
+ publish: () => bound.forEach((entry) => entry.state.publish()),
1244
+ clear: () => {
1245
+ bound.forEach((entry) => entry.state.clear());
1246
+ cause = void 0;
1247
+ },
1248
+ release: () => {
1249
+ instance?.release?.();
1250
+ instance = void 0;
1251
+ bound.forEach((entry) => {
1252
+ entry.output.consumers.clear();
1253
+ entry.state.release();
1254
+ });
1255
+ cause = void 0;
1850
1256
  }
1851
1257
  };
1258
+ try {
1259
+ scheduler.initialize(() => {
1260
+ const invalid = dependencies.find((output) => output.owner.fault || output.owner.disposed);
1261
+ if (invalid) throw invalid.owner.fault ?? new require_advanced.ProjectionDisposedError();
1262
+ if (processor.evaluate(true, void 0).length) processor.publish();
1263
+ processor.clear();
1264
+ });
1265
+ } catch (error) {
1266
+ processor.release();
1267
+ throw error;
1268
+ }
1269
+ scheduler.addProcessor(processor);
1270
+ return processor;
1852
1271
  };
1853
1272
  //#endregion
1854
- //#region core/src/projection/value.ts
1855
- const createValue = (scheduler, spec, options) => {
1856
- let instance;
1857
- let value;
1858
- let next;
1859
- let initialized = false;
1860
- let revision = 0;
1861
- let changed = false;
1862
- let reset = false;
1863
- let cause;
1864
- const listeners = /* @__PURE__ */ new Set();
1865
- const owner = createNode(scheduler, spec, {
1866
- evaluate: (sources, build) => {
1867
- cause = Object.values(sources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
1868
- let candidate;
1869
- if (build || !instance) {
1870
- require_issue.profile.materialized.rebuilt();
1871
- instance = void 0;
1872
- const built = spec.build(sources);
1873
- require_advanced.assertSynchronous(built);
1874
- instance = built;
1875
- candidate = built.value;
1876
- } else {
1877
- require_issue.profile.materialized.updated();
1878
- const result = instance.update(sources);
1879
- require_advanced.assertSynchronous(result);
1880
- if (result.kind === "rebuild") {
1881
- instance = void 0;
1882
- const built = spec.build(sources);
1883
- require_advanced.assertSynchronous(built);
1884
- instance = built;
1885
- candidate = built.value;
1886
- build = true;
1887
- } else candidate = result.kind === "changed" ? result.value : value;
1888
- }
1889
- changed = !initialized || !(options?.isEqual ?? Object.is)(value, candidate);
1890
- next = changed ? candidate : value;
1891
- reset = build;
1892
- return changed;
1273
+ //#region core/src/projection/projection-readable.ts
1274
+ let activeTracker;
1275
+ const trackKey = (key) => {
1276
+ activeTracker?.keys.add(key);
1277
+ };
1278
+ const trackStructure = () => {
1279
+ if (activeTracker) activeTracker.structure = true;
1280
+ };
1281
+ const trackAll = () => {
1282
+ if (activeTracker) activeTracker.all = true;
1283
+ };
1284
+ const isMapLike = (value) => value instanceof Map || value !== null && typeof value === "object" && typeof value.get === "function" && typeof value.keys === "function" && typeof value.entries === "function";
1285
+ const trackedMap = (source) => {
1286
+ const view = {
1287
+ get: (key) => {
1288
+ trackKey(key);
1289
+ return source.get(key);
1893
1290
  },
1894
- context: (active) => {
1895
- require_advanced.assertScope(active);
1896
- return Object.freeze({
1897
- kind: "value",
1898
- value: next,
1899
- revision: revision + (changed ? 1 : 0),
1900
- reset,
1901
- cause
1902
- });
1291
+ has: (key) => {
1292
+ trackKey(key);
1293
+ return source.has(key);
1903
1294
  },
1904
- revision: () => revision,
1905
- reset: () => reset,
1906
- publish: () => {
1907
- if (changed && initialized) revision++;
1908
- value = next;
1909
- initialized = true;
1295
+ get size() {
1296
+ trackStructure();
1297
+ return source.size;
1910
1298
  },
1911
- emit: (call) => {
1912
- require_issue.profile.materialized.notification();
1913
- Array.from(listeners).forEach((listener) => call(listener));
1299
+ keys: () => {
1300
+ trackStructure();
1301
+ return source.keys();
1914
1302
  },
1915
- clear: () => {
1916
- next = value;
1917
- changed = false;
1918
- reset = false;
1919
- cause = void 0;
1303
+ values: () => {
1304
+ trackAll();
1305
+ return source.values();
1920
1306
  },
1921
- release: () => {
1922
- listeners.clear();
1923
- instance = void 0;
1924
- value = next = void 0;
1925
- }
1926
- });
1927
- const handle = Object.freeze({
1928
- kind: "value",
1929
- current: () => {
1930
- owner.check();
1931
- return value;
1307
+ entries: () => {
1308
+ trackAll();
1309
+ return source.entries();
1932
1310
  },
1933
- revision: () => {
1934
- owner.check();
1935
- return revision;
1311
+ forEach: (callback, thisArg) => {
1312
+ trackAll();
1313
+ source.forEach((value, key) => callback.call(thisArg, value, key, view));
1936
1314
  },
1937
- subscribe: (listener) => {
1938
- owner.check();
1939
- listeners.add(listener);
1940
- return () => {
1941
- listeners.delete(listener);
1942
- };
1315
+ [Symbol.iterator]: () => {
1316
+ trackAll();
1317
+ return source[Symbol.iterator]();
1943
1318
  }
1944
- });
1945
- owner.install(handle);
1946
- return handle;
1947
- };
1948
- //#endregion
1949
- //#region core/src/projection/collection-state.ts
1950
- const height = (node) => node?.height ?? 0;
1951
- const makeTreeNode = (key, value, left, right) => Object.freeze({
1952
- key,
1953
- value,
1954
- left,
1955
- right,
1956
- height: Math.max(height(left), height(right)) + 1
1957
- });
1958
- const rotateLeft = (node) => {
1959
- const right = node.right;
1960
- return makeTreeNode(right.key, right.value, makeTreeNode(node.key, node.value, node.left, right.left), right.right);
1961
- };
1962
- const rotateRight = (node) => {
1963
- const left = node.left;
1964
- return makeTreeNode(left.key, left.value, left.left, makeTreeNode(node.key, node.value, left.right, node.right));
1965
- };
1966
- const rebalance = (node) => {
1967
- const balance = height(node.left) - height(node.right);
1968
- if (balance > 1) {
1969
- if (height(node.left.left) < height(node.left.right)) return rotateRight(makeTreeNode(node.key, node.value, rotateLeft(node.left), node.right));
1970
- return rotateRight(node);
1971
- }
1972
- if (balance < -1) {
1973
- if (height(node.right.right) < height(node.right.left)) return rotateLeft(makeTreeNode(node.key, node.value, node.left, rotateRight(node.right)));
1974
- return rotateLeft(node);
1975
- }
1976
- return node;
1977
- };
1978
- const treeSet = (node, key, value) => {
1979
- if (!node) return makeTreeNode(key, value);
1980
- if (key === node.key) return makeTreeNode(key, value, node.left, node.right);
1981
- if (key < node.key) return rebalance(makeTreeNode(node.key, node.value, treeSet(node.left, key, value), node.right));
1982
- return rebalance(makeTreeNode(node.key, node.value, node.left, treeSet(node.right, key, value)));
1983
- };
1984
- const treeMin = (node) => node.left ? treeMin(node.left) : node;
1985
- const treeRemove = (node, key) => {
1986
- if (!node) return void 0;
1987
- if (key < node.key) return rebalance(makeTreeNode(node.key, node.value, treeRemove(node.left, key), node.right));
1988
- if (key > node.key) return rebalance(makeTreeNode(node.key, node.value, node.left, treeRemove(node.right, key)));
1989
- if (!node.left) return node.right;
1990
- if (!node.right) return node.left;
1991
- const next = treeMin(node.right);
1992
- return rebalance(makeTreeNode(next.key, next.value, node.left, treeRemove(node.right, next.key)));
1993
- };
1994
- const treeGet = (node, key) => {
1995
- let current = node;
1996
- while (current) {
1997
- if (key === current.key) return current.value;
1998
- current = key < current.key ? current.left : current.right;
1999
- }
1319
+ };
1320
+ return Object.freeze(view);
2000
1321
  };
2001
- const treeHas = (node, key) => {
2002
- let current = node;
2003
- while (current) {
2004
- if (key === current.key) return true;
2005
- current = key < current.key ? current.left : current.right;
2006
- }
1322
+ const selectionAffects = (selection, change) => {
1323
+ if (!change || change.kind === "reset") return true;
1324
+ if (selection.all && (change.added.length || change.removed.length || change.updated.length || change.order)) return true;
1325
+ if (selection.structure && (change.added.length || change.removed.length || change.order)) return true;
1326
+ for (const key of selection.keys) if (change.added.some((entry) => entry.key === key) || change.removed.some((entry) => entry.key === key) || change.updated.some((entry) => entry.key === key)) return true;
2007
1327
  return false;
2008
1328
  };
2009
- const treeFromSorted = (entries, start = 0, end = entries.length) => {
2010
- if (start >= end) return void 0;
2011
- const middle = start + end >> 1;
2012
- const [key, value] = entries[middle];
2013
- return makeTreeNode(key, value, treeFromSorted(entries, start, middle), treeFromSorted(entries, middle + 1, end));
2014
- };
2015
- /**
2016
- * The one keyed publication kernel used by ordinary collection nodes and
2017
- * processor-group output leaves. It owns staged intent, exact transitions and
2018
- * the persistent published snapshot; callers only provide the processor
2019
- * callback and the equality policy.
2020
- */
2021
- const createCollectionState = () => {
2022
- const values = /* @__PURE__ */ new Map();
2023
- let ids = Object.freeze([]);
2024
- let staged = /* @__PURE__ */ new Map();
2025
- let nextIds = ids;
2026
- let change;
1329
+ const createDirectReadable = (source) => Object.freeze({
1330
+ current: source.current,
1331
+ revision: source.revision,
1332
+ subscribe: (listener) => source.subscribe(() => listener())
1333
+ });
1334
+ /** Consumer-side selector tracking; it never mutates processor dependencies. */
1335
+ const createSelectorReadable = (source, selector, equality = Object.is) => {
2027
1336
  let initialized = false;
2028
- let reset = false;
2029
- let revision = 0;
2030
- let publishedRoot;
2031
- let changedKeys = /* @__PURE__ */ new Set();
2032
- let orderChanged = false;
2033
- let explicitOrder;
2034
- let cleared = false;
1337
+ let value;
1338
+ let selectedRevision = 0;
1339
+ let sourceRevision = -1;
1340
+ let selection = Object.freeze({
1341
+ keys: /* @__PURE__ */ new Set(),
1342
+ all: true,
1343
+ structure: false
1344
+ });
1345
+ let previousCollection;
1346
+ let unsubscribeSource;
2035
1347
  const listeners = /* @__PURE__ */ new Set();
2036
- const hasNext = (key) => staged.has(key) ? staged.get(key).present : !cleared && values.has(key);
2037
- const getNext = (key) => {
2038
- const entry = staged.get(key);
2039
- return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : values.get(key);
2040
- };
2041
- const deriveIds = () => {
2042
- const result = cleared ? [] : ids.filter(hasNext);
2043
- for (const [key, entry] of staged) if (entry.present && (cleared || !values.has(key))) result.push(key);
2044
- return result;
1348
+ const updateCollectionSnapshot = (current) => {
1349
+ const candidate = current ?? source.current();
1350
+ previousCollection = isMapLike(candidate) ? candidate : void 0;
2045
1351
  };
2046
- const begin = (active, nextReset) => {
2047
- staged = /* @__PURE__ */ new Map();
2048
- nextIds = ids;
2049
- change = void 0;
2050
- changedKeys = /* @__PURE__ */ new Set();
2051
- explicitOrder = void 0;
2052
- orderChanged = false;
2053
- cleared = nextReset;
2054
- const read = (next) => ({
2055
- get: (key) => {
2056
- require_advanced.assertScope(active);
2057
- return next ? getNext(key) : values.get(key);
2058
- },
2059
- has: (key) => {
2060
- require_advanced.assertScope(active);
2061
- return next ? hasNext(key) : values.has(key);
2062
- },
2063
- ids: () => {
2064
- require_advanced.assertScope(active);
2065
- return next ? Object.freeze(explicitOrder ? [...explicitOrder] : [...deriveIds()]) : ids;
2066
- }
2067
- });
2068
- return {
2069
- previous: read(false),
2070
- next: read(true),
2071
- output: {
2072
- set: (key, value) => {
2073
- require_advanced.assertScope(active);
2074
- if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
2075
- staged.set(key, {
2076
- present: true,
2077
- value
2078
- });
2079
- },
2080
- remove: (key) => {
2081
- require_advanced.assertScope(active);
2082
- staged.set(key, { present: false });
2083
- },
2084
- order: (order) => {
2085
- require_advanced.assertScope(active);
2086
- explicitOrder = Object.freeze([...order]);
2087
- }
2088
- }
1352
+ const evaluate = () => {
1353
+ const tracker = {
1354
+ keys: /* @__PURE__ */ new Set(),
1355
+ all: false,
1356
+ structure: false
2089
1357
  };
2090
- };
2091
- const seal = (nextReset, isEqual) => {
2092
- if (cleared) {
2093
- for (const key of values.keys()) if (!staged.has(key)) staged.set(key, { present: false });
1358
+ const previousTracker = activeTracker;
1359
+ activeTracker = tracker;
1360
+ let current;
1361
+ let next;
1362
+ try {
1363
+ current = source.current();
1364
+ next = selector(isMapLike(current) ? trackedMap(current) : current);
1365
+ } finally {
1366
+ activeTracker = previousTracker;
2094
1367
  }
2095
- require_issue.profile.projection("touchedKeys", staged.size);
2096
- const added = /* @__PURE__ */ new Set();
2097
- const removed = /* @__PURE__ */ new Set();
2098
- const updated = /* @__PURE__ */ new Set();
2099
- for (const [key, entry] of staged) {
2100
- const existed = values.has(key);
2101
- if (entry.present) {
2102
- if (!existed) added.add(key);
2103
- else if (!isEqual(values.get(key), entry.value)) updated.add(key);
2104
- else if (!nextReset) staged.delete(key);
2105
- } else if (existed) removed.add(key);
2106
- else staged.delete(key);
1368
+ const nextSelection = Object.freeze({
1369
+ keys: new Set(tracker.keys),
1370
+ all: tracker.all || isMapLike(current) && tracker.keys.size === 0 && !tracker.structure,
1371
+ structure: tracker.structure
1372
+ });
1373
+ const changed = !initialized || !equality(value, next);
1374
+ if (changed) {
1375
+ if (initialized) selectedRevision++;
1376
+ value = next;
2107
1377
  }
2108
- if (explicitOrder || added.size || removed.size) {
2109
- const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
2110
- if (new Set(order).size !== order.length || order.length !== values.size + added.size - removed.size || order.some((key) => !hasNext(key))) throw new TypeError("Projection order must contain every key exactly once.");
2111
- nextIds = ids.length === order.length && ids.every((key, index) => key === order[index]) ? ids : Object.freeze([...order]);
2112
- } else nextIds = ids;
2113
- orderChanged = nextIds !== ids;
2114
- for (const key of added) changedKeys.add(key);
2115
- for (const key of removed) changedKeys.add(key);
2116
- for (const key of updated) changedKeys.add(key);
2117
- require_issue.profile.projection("changedKeys", changedKeys.size);
2118
- const addedEntries = [];
2119
- const updatedEntries = [];
2120
- const removedEntries = [];
2121
- for (const key of changedKeys) {
2122
- const beforePresent = values.has(key);
2123
- const afterPresent = hasNext(key);
2124
- if (beforePresent === afterPresent) {
2125
- if (!beforePresent || isEqual(values.get(key), getNext(key))) continue;
2126
- }
2127
- if (!beforePresent) addedEntries.push({
2128
- key,
2129
- after: getNext(key)
2130
- });
2131
- else if (!afterPresent) removedEntries.push({
2132
- key,
2133
- before: values.get(key)
2134
- });
2135
- else updatedEntries.push({
2136
- key,
2137
- before: values.get(key),
2138
- after: getNext(key)
2139
- });
1378
+ selection = nextSelection;
1379
+ initialized = true;
1380
+ sourceRevision = source.revision();
1381
+ updateCollectionSnapshot(current);
1382
+ return changed;
1383
+ };
1384
+ const ensureCurrent = () => {
1385
+ const revision = source.revision();
1386
+ if (!initialized || !unsubscribeSource && sourceRevision !== revision) evaluate();
1387
+ return value;
1388
+ };
1389
+ const onProjectionChange = (incoming) => {
1390
+ let change = incoming;
1391
+ if (!change && previousCollection) {
1392
+ const current = source.current();
1393
+ if (isMapLike(current)) {
1394
+ change = require_advanced.collectionChangeBetween(previousCollection, current);
1395
+ previousCollection = current;
1396
+ if (!change) {
1397
+ sourceRevision = source.revision();
1398
+ return;
1399
+ }
1400
+ } else previousCollection = void 0;
2140
1401
  }
2141
- if (nextReset) change = Object.freeze({ kind: "reset" });
2142
- else if (addedEntries.length || updatedEntries.length || removedEntries.length || orderChanged) {
2143
- let commonOrderChanged = false;
2144
- if (orderChanged) {
2145
- const before = ids.filter((key) => !removed.has(key));
2146
- const after = nextIds.filter((key) => !added.has(key));
2147
- commonOrderChanged = before.some((key, index) => after[index] !== key);
2148
- }
2149
- change = Object.freeze({
2150
- kind: "incremental",
2151
- added: Object.freeze(addedEntries),
2152
- updated: Object.freeze(updatedEntries),
2153
- removed: Object.freeze(removedEntries),
2154
- ...commonOrderChanged ? { order: Object.freeze({
2155
- before: Object.freeze([...ids]),
2156
- after: Object.freeze([...nextIds])
2157
- }) } : {}
2158
- });
1402
+ if (!selectionAffects(selection, change)) {
1403
+ sourceRevision = source.revision();
1404
+ updateCollectionSnapshot();
1405
+ return;
2159
1406
  }
2160
- reset = nextReset;
2161
- return change !== void 0;
1407
+ if (evaluate()) Array.from(listeners).forEach((listener) => listener());
2162
1408
  };
2163
- const context = (active, cause) => Object.freeze({
2164
- kind: "collection",
2165
- read: {
2166
- get: (key) => {
2167
- require_advanced.assertScope(active);
2168
- return getNext(key);
2169
- },
2170
- has: (key) => {
2171
- require_advanced.assertScope(active);
2172
- return hasNext(key);
2173
- },
2174
- ids: () => {
2175
- require_advanced.assertScope(active);
2176
- return nextIds;
2177
- }
1409
+ const installSource = () => {
1410
+ if (!unsubscribeSource) unsubscribeSource = source.subscribe(onProjectionChange);
1411
+ };
1412
+ return Object.freeze({
1413
+ current: ensureCurrent,
1414
+ revision: () => {
1415
+ ensureCurrent();
1416
+ return selectedRevision;
2178
1417
  },
2179
- change,
2180
- revision: revision + (change ? 1 : 0),
2181
- cause
1418
+ subscribe: (listener) => {
1419
+ ensureCurrent();
1420
+ listeners.add(listener);
1421
+ installSource();
1422
+ return () => {
1423
+ listeners.delete(listener);
1424
+ if (!listeners.size && unsubscribeSource) {
1425
+ unsubscribeSource();
1426
+ unsubscribeSource = void 0;
1427
+ }
1428
+ };
1429
+ }
2182
1430
  });
2183
- const current = (check) => {
2184
- const root = publishedRoot;
2185
- const publishedIds = ids;
2186
- return Object.freeze({
2187
- get: (key) => {
2188
- check();
2189
- return treeGet(root, key);
2190
- },
2191
- has: (key) => {
2192
- check();
2193
- return treeHas(root, key);
2194
- },
2195
- ids: () => {
2196
- check();
2197
- return publishedIds;
2198
- }
2199
- });
1431
+ };
1432
+ //#endregion
1433
+ //#region core/src/projection/source.ts
1434
+ const sourceError = (source, cause) => new require_advanced.ProjectionError("source", source.name, [source.outputs[0].revision()], cause);
1435
+ const createValueBoundary = (scheduler, name, initial, equality) => {
1436
+ const state = createValueOutput();
1437
+ let boundary;
1438
+ let latest = initial;
1439
+ let pendingReset = false;
1440
+ let pendingCause;
1441
+ let contextCause;
1442
+ let recovering = false;
1443
+ let cleanup;
1444
+ const check = () => {
1445
+ if (!scheduler.active || boundary.disposed) throw new require_advanced.ProjectionDisposedError();
1446
+ if (boundary.fault) throw boundary.fault;
2200
1447
  };
2201
- return {
2202
- begin,
2203
- seal,
2204
- context,
2205
- current,
2206
- revision: () => revision,
2207
- reset: () => reset,
2208
- publish: () => {
2209
- if (change) if (change.kind === "reset") {
2210
- const entries = [];
2211
- for (const [key, entry] of staged) if (entry.present) entries.push([key, entry.value]);
2212
- entries.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
2213
- publishedRoot = treeFromSorted(entries);
2214
- } else for (const [key, entry] of staged) publishedRoot = entry.present ? treeSet(publishedRoot, key, entry.value) : treeRemove(publishedRoot, key);
2215
- for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
2216
- else values.delete(key);
2217
- if (change && initialized) revision++;
2218
- ids = nextIds;
2219
- initialized = true;
1448
+ const output = {
1449
+ kind: "value",
1450
+ get owner() {
1451
+ return boundary;
2220
1452
  },
2221
- emit: (call) => {
2222
- const resetEvent = Object.freeze({ kind: "reset" });
2223
- Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
1453
+ consumers: /* @__PURE__ */ new Set(),
1454
+ context: (active) => state.context(active, contextCause),
1455
+ current: () => state.current(check),
1456
+ revision: state.revision,
1457
+ reset: state.reset,
1458
+ subscribe: (listener) => {
1459
+ check();
1460
+ const wrapped = () => listener();
1461
+ state.subscribe(wrapped);
1462
+ return () => state.unsubscribe(wrapped);
1463
+ },
1464
+ emit: state.emit,
1465
+ clear: state.clear,
1466
+ release: state.release
1467
+ };
1468
+ boundary = {
1469
+ kind: "source",
1470
+ name,
1471
+ outputs: Object.freeze([output]),
1472
+ fault: void 0,
1473
+ disposed: false,
1474
+ pendingCause: () => pendingCause,
1475
+ recovering: () => recovering,
1476
+ prepare: (cause) => {
1477
+ contextCause = cause;
1478
+ let active = true;
1479
+ try {
1480
+ state.begin(() => active, pendingReset).output.set(latest);
1481
+ const changed = state.seal(pendingReset, equality);
1482
+ recovering = false;
1483
+ return {
1484
+ changed,
1485
+ force: pendingReset
1486
+ };
1487
+ } finally {
1488
+ active = false;
1489
+ }
2224
1490
  },
1491
+ publish: state.publish,
2225
1492
  clear: () => {
2226
- staged.clear();
2227
- nextIds = ids;
2228
- change = void 0;
2229
- reset = false;
2230
- changedKeys.clear();
2231
- explicitOrder = void 0;
2232
- orderChanged = false;
2233
- cleared = false;
1493
+ state.clear();
1494
+ pendingReset = false;
1495
+ pendingCause = void 0;
1496
+ contextCause = void 0;
1497
+ recovering = false;
2234
1498
  },
2235
1499
  release: () => {
2236
- values.clear();
2237
- staged.clear();
2238
- publishedRoot = void 0;
2239
- listeners.clear();
2240
- ids = nextIds = Object.freeze([]);
2241
- change = void 0;
2242
- initialized = false;
2243
- revision = 0;
2244
- },
2245
- subscribe: (listener) => {
2246
- listeners.add(listener);
1500
+ cleanup?.();
1501
+ cleanup = void 0;
1502
+ state.release();
1503
+ output.consumers.clear();
1504
+ }
1505
+ };
1506
+ scheduler.initialize(() => {
1507
+ let active = true;
1508
+ try {
1509
+ state.begin(() => active, true).output.set(initial);
1510
+ state.seal(true, equality);
1511
+ state.publish();
1512
+ state.clear();
1513
+ } finally {
1514
+ active = false;
1515
+ }
1516
+ });
1517
+ scheduler.addSource(boundary);
1518
+ const mark = (metadata = {}) => {
1519
+ scheduler.assertIdle();
1520
+ pendingReset ||= metadata.reset ?? false;
1521
+ if (metadata.cause !== void 0) pendingCause ??= metadata.cause;
1522
+ if (boundary.fault) recovering = true;
1523
+ scheduler.capture(boundary);
1524
+ };
1525
+ const fail = (cause) => {
1526
+ boundary.fault = sourceError(boundary, cause);
1527
+ recovering = false;
1528
+ scheduler.capture(boundary);
1529
+ };
1530
+ return {
1531
+ producer: boundary,
1532
+ output,
1533
+ setLatest: (value) => {
1534
+ latest = value;
2247
1535
  },
2248
- unsubscribe: (listener) => {
2249
- listeners.delete(listener);
1536
+ mark,
1537
+ fail,
1538
+ detach: (next) => {
1539
+ cleanup = next;
2250
1540
  }
2251
1541
  };
2252
1542
  };
2253
- //#endregion
2254
- //#region core/src/projection/collection.ts
2255
- const createCollection = (scheduler, spec) => {
2256
- const state = createCollectionState();
2257
- let instance;
2258
- let cause;
2259
- const owner = createNode(scheduler, spec, {
2260
- evaluate: (sources, build, active) => {
2261
- cause = Object.values(sources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
2262
- const input = state.begin(active, build);
2263
- if (build || !instance) {
2264
- require_issue.profile.materialized.rebuilt();
2265
- instance = void 0;
2266
- const built = spec.build({
2267
- sources,
2268
- ...input
2269
- });
2270
- require_advanced.assertSynchronous(built);
2271
- instance = built;
2272
- } else {
2273
- require_issue.profile.materialized.updated();
2274
- const result = instance.update({
2275
- sources,
2276
- ...input
2277
- });
2278
- require_advanced.assertSynchronous(result);
2279
- if (result?.kind === "rebuild") {
2280
- const rebuildInput = state.begin(active, true);
2281
- instance = void 0;
2282
- const built = spec.build({
2283
- sources,
2284
- ...rebuildInput
2285
- });
2286
- require_advanced.assertSynchronous(built);
2287
- instance = built;
2288
- build = true;
2289
- }
2290
- }
2291
- return state.seal(build, spec.isEqual ?? Object.is);
1543
+ const createCollectionBoundary = (scheduler, name, readLatest, equality) => {
1544
+ const state = require_advanced.createCollectionOutput();
1545
+ let boundary;
1546
+ let pendingReset = false;
1547
+ let pendingCause;
1548
+ let contextCause;
1549
+ let recovering = false;
1550
+ let fullScan = false;
1551
+ let orderMayChange = false;
1552
+ const candidates = /* @__PURE__ */ new Set();
1553
+ let cleanup;
1554
+ const check = () => {
1555
+ if (!scheduler.active || boundary.disposed) throw new require_advanced.ProjectionDisposedError();
1556
+ if (boundary.fault) throw boundary.fault;
1557
+ };
1558
+ const output = {
1559
+ kind: "collection",
1560
+ get owner() {
1561
+ return boundary;
2292
1562
  },
2293
- context: (active) => state.context(active, cause),
1563
+ consumers: /* @__PURE__ */ new Set(),
1564
+ context: (active) => state.context(active, contextCause),
1565
+ current: () => state.current(check),
2294
1566
  revision: state.revision,
2295
1567
  reset: state.reset,
2296
- publish: state.publish,
1568
+ subscribe: (listener) => {
1569
+ check();
1570
+ const wrapped = (change) => listener(change);
1571
+ state.subscribe(wrapped);
1572
+ return () => state.unsubscribe(wrapped);
1573
+ },
2297
1574
  emit: state.emit,
2298
1575
  clear: state.clear,
1576
+ release: state.release
1577
+ };
1578
+ boundary = {
1579
+ kind: "source",
1580
+ name,
1581
+ outputs: Object.freeze([output]),
1582
+ fault: void 0,
1583
+ disposed: false,
1584
+ pendingCause: () => pendingCause,
1585
+ recovering: () => recovering,
1586
+ prepare: (cause) => {
1587
+ contextCause = cause;
1588
+ let active = true;
1589
+ try {
1590
+ const changed = require_advanced.stageCollectionRead(state, readLatest(() => active), () => active, {
1591
+ reset: pendingReset,
1592
+ ...fullScan ? {} : { candidates },
1593
+ orderMayChange,
1594
+ isEqual: equality
1595
+ });
1596
+ recovering = false;
1597
+ return {
1598
+ changed,
1599
+ force: pendingReset
1600
+ };
1601
+ } finally {
1602
+ active = false;
1603
+ }
1604
+ },
1605
+ publish: state.publish,
1606
+ clear: () => {
1607
+ state.clear();
1608
+ pendingReset = false;
1609
+ pendingCause = void 0;
1610
+ contextCause = void 0;
1611
+ recovering = false;
1612
+ fullScan = false;
1613
+ orderMayChange = false;
1614
+ candidates.clear();
1615
+ },
2299
1616
  release: () => {
1617
+ cleanup?.();
1618
+ cleanup = void 0;
2300
1619
  state.release();
2301
- instance = void 0;
1620
+ output.consumers.clear();
1621
+ }
1622
+ };
1623
+ scheduler.initialize(() => {
1624
+ let active = true;
1625
+ try {
1626
+ require_advanced.stageCollectionRead(state, readLatest(() => active), () => active, {
1627
+ reset: true,
1628
+ isEqual: equality
1629
+ });
1630
+ state.publish();
1631
+ state.clear();
1632
+ } finally {
1633
+ active = false;
2302
1634
  }
2303
1635
  });
2304
- const handle = {
2305
- kind: "collection",
2306
- current: () => {
2307
- owner.check();
2308
- return state.current(owner.check);
2309
- },
2310
- revision: () => {
2311
- owner.check();
2312
- return state.revision();
2313
- },
2314
- subscribe: (listener) => {
2315
- owner.check();
2316
- state.subscribe(listener);
2317
- return () => state.unsubscribe(listener);
1636
+ scheduler.addSource(boundary);
1637
+ const mark = (metadata = {}) => {
1638
+ scheduler.assertIdle();
1639
+ pendingReset ||= metadata.reset ?? false;
1640
+ if (metadata.cause !== void 0) pendingCause ??= metadata.cause;
1641
+ fullScan ||= metadata.fullScan ?? false;
1642
+ orderMayChange ||= metadata.orderMayChange ?? false;
1643
+ if (metadata.candidates) for (const key of metadata.candidates) candidates.add(key);
1644
+ if (boundary.fault) recovering = true;
1645
+ scheduler.capture(boundary);
1646
+ };
1647
+ const fail = (cause) => {
1648
+ boundary.fault = sourceError(boundary, cause);
1649
+ recovering = false;
1650
+ scheduler.capture(boundary);
1651
+ };
1652
+ return {
1653
+ producer: boundary,
1654
+ output,
1655
+ mark,
1656
+ fail,
1657
+ detach: (next) => {
1658
+ cleanup = next;
2318
1659
  }
2319
1660
  };
2320
- owner.install(handle);
2321
- return Object.freeze(handle);
2322
1661
  };
2323
- //#endregion
2324
- //#region core/src/projection/group.ts
2325
- /**
2326
- * Materializes one processor callback with several keyed output leaves. The
2327
- * scheduler sees one node and the output leaves remain ordinary source records
2328
- * for dependency, revision and listener matching.
2329
- */
2330
- const createCollectionGroup = (scheduler, spec) => {
2331
- scheduler.assertIdle();
2332
- const sources = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
2333
- const states = spec.outputs.map(() => createCollectionState());
2334
- let instance;
2335
- let cause;
2336
- let changedOutputs = Object.freeze([]);
2337
- let node;
2338
- const order = scheduler.order();
2339
- const outputs = states.map((state, index) => {
2340
- const output = {
2341
- owner: void 0,
2342
- consumers: /* @__PURE__ */ new Set(),
2343
- context: (active) => state.context(active, cause),
2344
- revision: state.revision,
2345
- reset: state.reset,
2346
- fault: void 0,
2347
- disposed: false,
2348
- clear: state.clear,
2349
- emit: state.emit
1662
+ const createSourceRegistry = (scheduler) => {
1663
+ const documents = /* @__PURE__ */ new Map();
1664
+ const readables = /* @__PURE__ */ new Map();
1665
+ const externalValues = /* @__PURE__ */ new WeakMap();
1666
+ const externalCollections = /* @__PURE__ */ new WeakMap();
1667
+ const documentConnection = (runtime) => {
1668
+ const state = require_access.accessOf(runtime);
1669
+ const cached = documents.get(state);
1670
+ if (cached) return cached;
1671
+ if (state.disposed) throw new Error("Document has been disposed.");
1672
+ const bindings = /* @__PURE__ */ new Set();
1673
+ const guard = (locked) => {
1674
+ state.projectionLocks = (state.projectionLocks ?? 0) + (locked ? 1 : -1);
2350
1675
  };
2351
- Object.defineProperty(output, "fault", {
2352
- configurable: false,
2353
- enumerable: true,
2354
- get: () => node.fault,
2355
- set: (value) => {
2356
- node.fault = value;
1676
+ scheduler.guards.add(guard);
1677
+ let closed = false;
1678
+ let detach = () => void 0;
1679
+ const connection = {
1680
+ add(binding) {
1681
+ if (closed) throw new Error("Document projection connection is closed.");
1682
+ bindings.add(binding);
1683
+ let active = true;
1684
+ return () => {
1685
+ if (!active) return;
1686
+ active = false;
1687
+ bindings.delete(binding);
1688
+ if (!bindings.size) connection.close();
1689
+ };
1690
+ },
1691
+ close() {
1692
+ if (closed) return;
1693
+ closed = true;
1694
+ detach();
1695
+ bindings.clear();
1696
+ scheduler.guards.delete(guard);
1697
+ documents.delete(state);
1698
+ }
1699
+ };
1700
+ detach = require_notification.attachProjection(runtime, {
1701
+ capture: (commit) => {
1702
+ for (const binding of bindings) if (binding.matches(commit)) binding.capture(commit);
1703
+ },
1704
+ settle: scheduler.settle,
1705
+ flush: scheduler.flush,
1706
+ dispose: () => {
1707
+ for (const binding of bindings) binding.dispose();
1708
+ scheduler.run();
2357
1709
  }
2358
1710
  });
2359
- return output;
2360
- });
2361
- const check = () => {
2362
- if (node.disposed || !scheduler.active) throw new require_advanced.ProjectionDisposedError();
2363
- if (node.fault) throw node.fault;
1711
+ documents.set(state, connection);
1712
+ return connection;
2364
1713
  };
2365
- const evaluate = (inputSources, build, active) => {
2366
- cause = Object.values(inputSources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
2367
- const makeInput = (reset) => {
2368
- const evaluations = states.map((state) => state.begin(active, reset));
2369
- return {
2370
- sources: inputSources,
2371
- previous: evaluations.map((evaluation) => evaluation.previous),
2372
- next: evaluations.map((evaluation) => evaluation.next),
2373
- outputs: evaluations.map((evaluation) => evaluation.output)
2374
- };
1714
+ const valueInput = (definition) => {
1715
+ if (definition.source.kind !== "value-input") throw new Error("Invalid value input source.");
1716
+ const source = definition.source;
1717
+ const boundary = createValueBoundary(scheduler, "projection input", source.initial, source.equality);
1718
+ return {
1719
+ ...boundary,
1720
+ write: {
1721
+ kind: "value",
1722
+ set(value) {
1723
+ scheduler.assertIdle();
1724
+ boundary.setLatest(value);
1725
+ boundary.mark();
1726
+ scheduler.run();
1727
+ }
1728
+ }
2375
1729
  };
2376
- let input = makeInput(build);
2377
- if (build || !instance) {
2378
- require_issue.profile.materialized.rebuilt();
2379
- instance = void 0;
2380
- const built = spec.build(input);
2381
- require_advanced.assertSynchronous(built);
2382
- instance = built;
2383
- } else {
2384
- require_issue.profile.materialized.updated();
2385
- const result = instance.update(input);
2386
- require_advanced.assertSynchronous(result);
2387
- if (result?.kind === "rebuild") {
2388
- input = makeInput(true);
2389
- instance = void 0;
2390
- const built = spec.build(input);
2391
- require_advanced.assertSynchronous(built);
2392
- instance = built;
2393
- build = true;
1730
+ };
1731
+ const collectionInput = (definition) => {
1732
+ if (definition.source.kind !== "collection-input") throw new Error("Invalid collection input source.");
1733
+ const values = new Map(definition.source.initial);
1734
+ const boundary = createCollectionBoundary(scheduler, "projection collection input", () => require_advanced.mapRead(values), definition.output.equality);
1735
+ return {
1736
+ ...boundary,
1737
+ write: {
1738
+ kind: "collection",
1739
+ update(run) {
1740
+ scheduler.assertIdle();
1741
+ const staged = /* @__PURE__ */ new Map();
1742
+ const operations = [];
1743
+ let active = true;
1744
+ const draft = Object.freeze({
1745
+ get: (key) => {
1746
+ require_advanced.assertScope(() => active);
1747
+ const entry = staged.get(key);
1748
+ return entry ? entry.present ? entry.value : void 0 : values.get(key);
1749
+ },
1750
+ has: (key) => {
1751
+ require_advanced.assertScope(() => active);
1752
+ const entry = staged.get(key);
1753
+ return entry ? entry.present : values.has(key);
1754
+ },
1755
+ set: (key, value) => {
1756
+ require_advanced.assertScope(() => active);
1757
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1758
+ staged.set(key, {
1759
+ present: true,
1760
+ value
1761
+ });
1762
+ operations.push({
1763
+ kind: "set",
1764
+ key,
1765
+ value
1766
+ });
1767
+ },
1768
+ remove: (key) => {
1769
+ require_advanced.assertScope(() => active);
1770
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1771
+ staged.set(key, { present: false });
1772
+ operations.push({
1773
+ kind: "remove",
1774
+ key
1775
+ });
1776
+ }
1777
+ });
1778
+ try {
1779
+ require_advanced.assertSynchronous(run(draft));
1780
+ } finally {
1781
+ active = false;
1782
+ }
1783
+ const candidates = /* @__PURE__ */ new Set();
1784
+ let structural = false;
1785
+ let changed = false;
1786
+ for (const operation of operations) {
1787
+ const present = values.has(operation.key);
1788
+ if (operation.kind === "set") {
1789
+ if (present && Object.is(values.get(operation.key), operation.value)) continue;
1790
+ structural ||= !present;
1791
+ values.set(operation.key, operation.value);
1792
+ } else {
1793
+ if (!present) continue;
1794
+ structural = true;
1795
+ values.delete(operation.key);
1796
+ }
1797
+ candidates.add(operation.key);
1798
+ changed = true;
1799
+ }
1800
+ if (!changed) return;
1801
+ boundary.mark({
1802
+ candidates,
1803
+ orderMayChange: structural
1804
+ });
1805
+ scheduler.run();
1806
+ }
2394
1807
  }
2395
- }
2396
- changedOutputs = Object.freeze(states.flatMap((state, index) => state.seal(build, spec.outputs[index].isEqual ?? Object.is) ? [outputs[index]] : []));
2397
- return changedOutputs.length > 0;
1808
+ };
2398
1809
  };
2399
- node = {
2400
- owner: void 0,
2401
- order,
2402
- name: `processor-group (${order})`,
2403
- sources: sources.map(([, source]) => source),
2404
- consumers: /* @__PURE__ */ new Set(),
2405
- outputs,
2406
- disposed: false,
2407
- fault: void 0,
2408
- context: (active) => {
2409
- require_advanced.assertScope(active);
2410
- return Object.freeze({ kind: "group" });
2411
- },
2412
- revision: () => Math.max(0, ...states.map((state) => state.revision())),
2413
- reset: () => states.some((state) => state.reset()),
2414
- evaluate: (build) => {
2415
- let active = true;
2416
- try {
2417
- const scopeActive = () => active;
2418
- return evaluate(Object.fromEntries(sources.map(([key, source]) => [key, source.context(scopeActive)])), build, scopeActive);
2419
- } finally {
2420
- active = false;
1810
+ const documentValue = (definition) => {
1811
+ if (definition.source.kind !== "document-value") throw new Error("Invalid document value source.");
1812
+ const { document, selector } = definition.source;
1813
+ const state = require_access.accessOf(document);
1814
+ const address = selector?.address ?? Object.freeze([]);
1815
+ const schemaNode = () => require_issue.nodeAt(state.schema, address, state.document);
1816
+ const read = () => {
1817
+ if (state.disposed) throw new Error("Document has been disposed.");
1818
+ const node = schemaNode();
1819
+ if (!node) return void 0;
1820
+ return require_access.copyValue(node, selector ? require_issue.read(state.document, address, state.schema) : state.document);
1821
+ };
1822
+ const boundary = createValueBoundary(scheduler, "document source", read(), (left, right) => {
1823
+ const node = schemaNode();
1824
+ return node ? require_access.equalValue(node, left, right) : Object.is(left, right);
1825
+ });
1826
+ const remove = documentConnection(document).add({
1827
+ matches: (commit) => !selector || affectsTarget(commit.impact, selector),
1828
+ capture: (commit) => {
1829
+ try {
1830
+ boundary.setLatest(read());
1831
+ boundary.mark({ reset: commit.impact.kind === "reset" });
1832
+ } catch (cause) {
1833
+ boundary.fail(cause);
1834
+ }
1835
+ },
1836
+ dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
1837
+ });
1838
+ boundary.detach(remove);
1839
+ return boundary;
1840
+ };
1841
+ const documentCollection = (definition) => {
1842
+ if (definition.source.kind !== "document-collection") throw new Error("Invalid document collection source.");
1843
+ const { document, selector } = definition.source;
1844
+ const state = require_access.accessOf(document);
1845
+ const read = (active) => {
1846
+ if (state.disposed) throw new Error("Document has been disposed.");
1847
+ const raw = require_access.collectionAccess({
1848
+ state,
1849
+ active
1850
+ }, selector.address);
1851
+ const snapshots = /* @__PURE__ */ new Map();
1852
+ return Object.freeze({
1853
+ get: (key) => {
1854
+ require_advanced.assertScope(active);
1855
+ if (!snapshots.has(key)) snapshots.set(key, require_access.snapshot(raw.get(key)));
1856
+ return snapshots.get(key);
1857
+ },
1858
+ has: (key) => {
1859
+ require_advanced.assertScope(active);
1860
+ return raw.has(key);
1861
+ },
1862
+ ids: () => {
1863
+ require_advanced.assertScope(active);
1864
+ return raw.ids();
1865
+ }
1866
+ });
1867
+ };
1868
+ const collectionNode = () => require_issue.nodeAt(state.schema, selector.address, state.document);
1869
+ const boundary = createCollectionBoundary(scheduler, "document collection source", read, (left, right) => {
1870
+ const node = collectionNode();
1871
+ const entry = node && require_advanced.collectionEntryNode(node);
1872
+ return entry ? require_access.equalValue(entry, left, right) : Object.is(left, right);
1873
+ });
1874
+ const remove = documentConnection(document).add({
1875
+ matches: (commit) => affectsTarget(commit.impact, selector),
1876
+ capture: (commit) => {
1877
+ const impact = collectionImpact(commit.impact, selector);
1878
+ if (impact.kind === "reset") {
1879
+ boundary.mark({ reset: true });
1880
+ return;
1881
+ }
1882
+ const candidates = new Set([
1883
+ ...impact.added,
1884
+ ...impact.updated,
1885
+ ...impact.removed
1886
+ ]);
1887
+ boundary.mark({
1888
+ candidates,
1889
+ orderMayChange: Boolean(impact.added.size || impact.removed.size || impact.orderChanged)
1890
+ });
1891
+ },
1892
+ dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
1893
+ });
1894
+ boundary.detach(remove);
1895
+ return boundary;
1896
+ };
1897
+ const readable = (definition) => {
1898
+ if (definition.source.kind !== "readable") throw new Error("Invalid Readable source.");
1899
+ const { readable, equality } = definition.source;
1900
+ let initial = readable.current();
1901
+ const boundary = createValueBoundary(scheduler, "external readable", initial, equality);
1902
+ const member = {
1903
+ boundary,
1904
+ receive(value, metadata) {
1905
+ initial = value;
1906
+ boundary.setLatest(value);
1907
+ boundary.mark(metadata);
2421
1908
  }
2422
- },
2423
- changedOutputs: () => changedOutputs,
2424
- publish: () => {
2425
- if (node.fault) states.forEach((state) => state.clear());
2426
- else states.forEach((state) => state.publish());
2427
- },
2428
- emit: () => void 0,
2429
- clear: () => {
2430
- states.forEach((state) => state.clear());
2431
- changedOutputs = Object.freeze([]);
2432
- cause = void 0;
2433
- },
2434
- release: () => {
2435
- states.forEach((state) => state.release());
2436
- outputs.forEach((output) => {
2437
- output.disposed = true;
2438
- output.consumers.clear();
1909
+ };
1910
+ let connection = readables.get(readable);
1911
+ if (!connection) {
1912
+ const members = /* @__PURE__ */ new Set();
1913
+ let closed = false;
1914
+ let unsubscribe;
1915
+ let detachDocument;
1916
+ const receive = (settle) => {
1917
+ if (closed || !scheduler.active) return;
1918
+ try {
1919
+ const value = readable.current();
1920
+ members.forEach((next) => next.receive(value));
1921
+ } catch (cause) {
1922
+ members.forEach((next) => next.boundary.fail(cause));
1923
+ }
1924
+ if (settle) scheduler.run();
1925
+ };
1926
+ const created = {
1927
+ members,
1928
+ close() {
1929
+ if (closed) return;
1930
+ closed = true;
1931
+ unsubscribe?.();
1932
+ detachDocument?.();
1933
+ readables.delete(readable);
1934
+ }
1935
+ };
1936
+ const owner = require_notification.documentReadableOwner(readable);
1937
+ if (owner) detachDocument = documentConnection(owner).add({
1938
+ matches: () => true,
1939
+ capture: () => receive(false),
1940
+ dispose: () => {
1941
+ members.forEach((next) => next.boundary.fail(/* @__PURE__ */ new Error("Document has been disposed.")));
1942
+ }
2439
1943
  });
2440
- instance = void 0;
1944
+ unsubscribe = readable.subscribe(() => receive(true));
1945
+ connection = created;
1946
+ readables.set(readable, connection);
2441
1947
  }
1948
+ connection.members.add(member);
1949
+ boundary.detach(() => {
1950
+ connection.members.delete(member);
1951
+ if (!connection.members.size) connection.close();
1952
+ });
1953
+ return boundary;
2442
1954
  };
2443
- node.owner = node;
2444
- outputs.forEach((output) => {
2445
- output.owner = node;
2446
- });
2447
- scheduler.initialize(() => {
2448
- const invalid = node.sources.find((source) => source.fault || source.disposed);
2449
- if (invalid) throw invalid.fault ?? new require_advanced.ProjectionDisposedError();
2450
- node.evaluate(true);
2451
- node.publish();
2452
- node.clear();
2453
- });
2454
- const handles = outputs.map((output, index) => {
2455
- const handle = {
2456
- kind: "collection",
2457
- current: () => {
2458
- check();
2459
- return states[index].current(check);
2460
- },
2461
- revision: () => {
2462
- check();
2463
- return states[index].revision();
2464
- },
2465
- subscribe: (listener) => {
2466
- check();
2467
- states[index].subscribe(listener);
2468
- return () => states[index].unsubscribe(listener);
1955
+ const externalValue = (definition) => {
1956
+ if (definition.source.kind !== "external-value") throw new Error("Invalid external value source.");
1957
+ const { source, equality } = definition.source;
1958
+ const boundary = createValueBoundary(scheduler, "external source", source.current(), equality);
1959
+ const member = {
1960
+ boundary,
1961
+ receive(value, metadata) {
1962
+ boundary.setLatest(value);
1963
+ boundary.mark(metadata);
2469
1964
  }
2470
1965
  };
2471
- scheduler.register(handle, output);
2472
- return Object.freeze(handle);
2473
- });
2474
- scheduler.addNode(node);
2475
- return Object.freeze(handles);
2476
- };
2477
- //#endregion
2478
- //#region core/src/projection/graph.ts
2479
- const createProjectionGraph = (options) => {
2480
- const scheduler = require_advanced.createScheduler(options.onError);
2481
- const sources = createSources(scheduler);
2482
- const value = (spec, options) => createValue(scheduler, spec, options);
2483
- const map = (source, mapper, options) => {
2484
- sources.assertCollection(source);
2485
- const calculate = (id, entry) => {
2486
- const value = mapper(id, entry);
2487
- require_advanced.assertSynchronous(value);
2488
- return value;
2489
- };
2490
- return createCollection(scheduler, {
2491
- sources: { source },
2492
- isEqual: options?.isEqual,
2493
- build: ({ sources, output }) => {
2494
- const read = sources.source.read;
2495
- const ids = read.ids();
2496
- require_issue.profile.collectionView.idsScanned(ids.length);
2497
- for (const id of ids) {
2498
- require_issue.profile.collectionView.mapped();
2499
- output.set(id, calculate(id, read.get(id)));
1966
+ let connection = externalValues.get(source);
1967
+ if (!connection) {
1968
+ const members = /* @__PURE__ */ new Set();
1969
+ let closed = false;
1970
+ let unsubscribe;
1971
+ const created = {
1972
+ members,
1973
+ close() {
1974
+ if (closed) return;
1975
+ closed = true;
1976
+ unsubscribe?.();
1977
+ externalValues.delete(source);
2500
1978
  }
2501
- output.order(ids);
2502
- return { update: ({ sources, output }) => {
2503
- const input = sources.source;
2504
- const read = input.read;
2505
- if (input.change?.kind === "reset") return { kind: "rebuild" };
2506
- const change = input.change;
2507
- if (!change) return;
2508
- for (const entry of change.added) {
2509
- require_issue.profile.collectionView.mapped();
2510
- output.set(entry.key, calculate(entry.key, entry.after));
2511
- }
2512
- for (const entry of change.updated) {
2513
- require_issue.profile.collectionView.mapped();
2514
- output.set(entry.key, calculate(entry.key, entry.after));
2515
- }
2516
- for (const entry of change.removed) output.remove(entry.key);
2517
- if (change.added.length || change.removed.length || change.order) {
2518
- const ids = read.ids();
2519
- require_issue.profile.collectionView.idsScanned(ids.length);
2520
- output.order(ids);
1979
+ };
1980
+ try {
1981
+ unsubscribe = source.subscribe((event) => {
1982
+ try {
1983
+ members.forEach((next) => next.receive(event.value, {
1984
+ reset: event.reset,
1985
+ cause: event.cause
1986
+ }));
1987
+ } catch (cause) {
1988
+ members.forEach((next) => next.boundary.fail(cause));
2521
1989
  }
2522
- } };
1990
+ scheduler.run();
1991
+ });
1992
+ } catch (cause) {
1993
+ created.close();
1994
+ throw cause;
2523
1995
  }
1996
+ connection = created;
1997
+ externalValues.set(source, connection);
1998
+ }
1999
+ connection.members.add(member);
2000
+ boundary.detach(() => {
2001
+ connection.members.delete(member);
2002
+ if (!connection.members.size) connection.close();
2524
2003
  });
2004
+ return boundary;
2525
2005
  };
2526
- const graph = {
2527
- document: sources.document,
2528
- fromSource: (source, options) => sources.fromSource(source, options),
2529
- fromCollectionSource: (source) => sources.fromCollectionSource(source),
2530
- input: sources.input,
2531
- collectionInput: sources.collectionInput,
2532
- fromReadable: sources.fromReadable,
2533
- value,
2534
- collection: (spec) => createCollection(scheduler, spec),
2535
- group: (spec) => createCollectionGroup(scheduler, spec),
2536
- map,
2537
- batch: scheduler.batch,
2538
- assertIdle: scheduler.assertIdle,
2539
- releaseNode: scheduler.releaseNode,
2540
- releaseInput: scheduler.unregisterSource,
2541
- dispose: () => {
2006
+ const externalCollection = (definition) => {
2007
+ if (definition.source.kind !== "external-collection") throw new Error("Invalid external collection source.");
2008
+ const source = definition.source.source;
2009
+ const boundary = createCollectionBoundary(scheduler, "external collection source", () => {
2010
+ const current = source.current();
2011
+ return Object.freeze({
2012
+ get: (key) => current.get(key),
2013
+ has: (key) => current.has(key),
2014
+ ids: () => current.ids()
2015
+ });
2016
+ }, definition.output.equality);
2017
+ let connection = externalCollections.get(source);
2018
+ if (!connection) {
2019
+ const members = /* @__PURE__ */ new Set();
2020
+ let closed = false;
2021
+ let unsubscribe;
2022
+ const created = {
2023
+ members,
2024
+ close() {
2025
+ if (closed) return;
2026
+ closed = true;
2027
+ unsubscribe?.();
2028
+ externalCollections.delete(source);
2029
+ }
2030
+ };
2542
2031
  try {
2543
- scheduler.dispose();
2544
- } finally {
2545
- if (!scheduler.active) sources.dispose();
2032
+ unsubscribe = source.subscribe((event) => {
2033
+ try {
2034
+ const impact = event.impact;
2035
+ if (impact?.kind === "reset") members.forEach((next) => next.mark({
2036
+ reset: true,
2037
+ cause: event.cause
2038
+ }));
2039
+ else if (impact?.kind === "incremental") {
2040
+ const candidates = new Set([
2041
+ ...impact.added,
2042
+ ...impact.updated,
2043
+ ...impact.removed
2044
+ ]);
2045
+ members.forEach((next) => next.mark({
2046
+ candidates,
2047
+ orderMayChange: Boolean(impact.added.size || impact.removed.size || impact.orderChanged),
2048
+ cause: event.cause
2049
+ }));
2050
+ } else members.forEach((next) => next.mark({
2051
+ fullScan: true,
2052
+ orderMayChange: true,
2053
+ cause: event.cause
2054
+ }));
2055
+ } catch (cause) {
2056
+ members.forEach((next) => next.fail(cause));
2057
+ }
2058
+ scheduler.run();
2059
+ });
2060
+ } catch (cause) {
2061
+ created.close();
2062
+ throw cause;
2546
2063
  }
2064
+ connection = created;
2065
+ externalCollections.set(source, connection);
2547
2066
  }
2067
+ connection.members.add(boundary);
2068
+ boundary.detach(() => {
2069
+ connection.members.delete(boundary);
2070
+ if (!connection.members.size) connection.close();
2071
+ });
2072
+ return boundary;
2548
2073
  };
2549
- return Object.freeze(graph);
2550
- };
2551
- //#endregion
2552
- //#region core/src/projection/runtime.ts
2553
- let activeTracker;
2554
- const trackKey = (key) => {
2555
- activeTracker?.keys.add(key);
2556
- };
2557
- const trackStructure = () => {
2558
- if (!activeTracker) return;
2559
- activeTracker.structure = true;
2560
- };
2561
- const trackAll = () => {
2562
- if (!activeTracker) return;
2563
- activeTracker.all = true;
2564
- };
2565
- const mapView = (read) => {
2566
- const keys = read.ids();
2567
- const source = /* @__PURE__ */ new Map();
2568
- for (const key of keys) source.set(key, read.get(key));
2569
- const view = {
2570
- get: (key) => {
2571
- trackKey(key);
2572
- return source.get(key);
2573
- },
2574
- has: (key) => {
2575
- trackKey(key);
2576
- return source.has(key);
2577
- },
2578
- get size() {
2579
- trackStructure();
2580
- return source.size;
2581
- },
2582
- keys: () => {
2583
- trackStructure();
2584
- return source.keys();
2585
- },
2586
- values: () => {
2587
- trackAll();
2588
- return source.values();
2589
- },
2590
- entries: () => {
2591
- trackAll();
2592
- return source.entries();
2593
- },
2594
- forEach: (callback, thisArg) => {
2595
- trackAll();
2596
- source.forEach((value, key) => callback.call(thisArg, value, key, view));
2597
- },
2598
- [Symbol.iterator]: () => {
2599
- trackAll();
2600
- return source[Symbol.iterator]();
2601
- }
2602
- };
2603
- return Object.freeze(view);
2604
- };
2605
- const lazyMapView = (read) => {
2606
- const ids = read.ids();
2607
- const entries = function* () {
2608
- for (const key of ids) yield [key, read.get(key)];
2609
- };
2610
- const values = function* () {
2611
- for (const key of ids) yield read.get(key);
2612
- };
2613
- const view = {
2614
- get: (key) => {
2615
- trackKey(key);
2616
- return read.get(key);
2617
- },
2618
- has: (key) => {
2619
- trackKey(key);
2620
- return read.has(key);
2621
- },
2622
- get size() {
2623
- trackStructure();
2624
- return ids.length;
2625
- },
2626
- keys: () => {
2627
- trackStructure();
2628
- return ids[Symbol.iterator]();
2629
- },
2630
- values: () => {
2631
- trackAll();
2632
- return values();
2633
- },
2634
- entries: () => {
2635
- trackAll();
2636
- return entries();
2637
- },
2638
- forEach: (callback, thisArg) => {
2639
- trackAll();
2640
- for (const key of ids) callback.call(thisArg, read.get(key), key, view);
2074
+ return {
2075
+ materialize(definition) {
2076
+ switch (definition.source.kind) {
2077
+ case "value-input": return valueInput(definition);
2078
+ case "collection-input": return collectionInput(definition);
2079
+ case "document-value": return documentValue(definition);
2080
+ case "document-collection": return documentCollection(definition);
2081
+ case "readable": return readable(definition);
2082
+ case "external-value": return externalValue(definition);
2083
+ case "external-collection": return externalCollection(definition);
2084
+ }
2641
2085
  },
2642
- [Symbol.iterator]: () => {
2643
- trackAll();
2644
- return entries();
2086
+ dispose() {
2087
+ documents.forEach((connection) => connection.close());
2088
+ readables.forEach((connection) => connection.close());
2089
+ documents.clear();
2090
+ readables.clear();
2645
2091
  }
2646
2092
  };
2647
- return Object.freeze(view);
2648
- };
2649
- const isMapLike = (value) => value instanceof Map || value !== null && typeof value === "object" && typeof value.get === "function" && typeof value.keys === "function" && typeof value.entries === "function";
2650
- const mapRead = (value) => ({
2651
- get: (key) => value.get(key),
2652
- has: (key) => value.has(key),
2653
- ids: () => Object.freeze([...value.keys()])
2654
- });
2655
- const mapChange = (previous, current) => {
2656
- const added = [];
2657
- const removed = [];
2658
- const updated = [];
2659
- for (const key of previous.keys()) if (!current.has(key)) removed.push({
2660
- key,
2661
- before: previous.get(key)
2662
- });
2663
- else if (!Object.is(previous.get(key), current.get(key))) updated.push({
2664
- key,
2665
- before: previous.get(key),
2666
- after: current.get(key)
2667
- });
2668
- for (const key of current.keys()) if (!previous.has(key)) added.push({
2669
- key,
2670
- after: current.get(key)
2671
- });
2672
- const before = [...previous.keys()].filter((key) => current.has(key));
2673
- const after = [...current.keys()].filter((key) => previous.has(key));
2674
- const orderChanged = before.length !== after.length || before.some((key, index) => key !== after[index]);
2675
- if (!added.length && !removed.length && !updated.length && !orderChanged) return void 0;
2676
- return {
2677
- kind: "incremental",
2678
- added: Object.freeze(added),
2679
- removed: Object.freeze(removed),
2680
- updated: Object.freeze(updated),
2681
- ...orderChanged ? { order: Object.freeze({
2682
- before: Object.freeze([...previous.keys()]),
2683
- after: Object.freeze([...current.keys()])
2684
- }) } : {}
2685
- };
2686
- };
2687
- const sameSelection = (left, right) => {
2688
- if (left.all !== right.all || left.structure !== right.structure || left.keys.size !== right.keys.size) return false;
2689
- for (const key of left.keys) if (!right.keys.has(key)) return false;
2690
- return true;
2691
- };
2692
- const selectionAffects = (selection, change) => {
2693
- if (!change || change.kind === "reset") return true;
2694
- if (selection.all && (change.added.length || change.removed.length || change.updated.length || change.order)) return true;
2695
- if (selection.structure && (change.added.length || change.removed.length || change.order)) return true;
2696
- for (const key of selection.keys) if (change.added.some((entry) => entry.key === key) || change.removed.some((entry) => entry.key === key) || change.updated.some((entry) => entry.key === key)) return true;
2697
- return false;
2698
2093
  };
2699
- const currentValue = (event) => {
2700
- if (!event || typeof event !== "object") return event;
2701
- if ("kind" in event && event.kind === "value") return event.value;
2702
- if ("kind" in event && event.kind === "document") return require_scope.snapshot(event.read);
2703
- if ("kind" in event && event.kind === "collection") return mapView(event.read);
2704
- return event;
2705
- };
2706
- const sourceOf = (instance) => instance;
2094
+ //#endregion
2095
+ //#region core/src/projection/runtime.ts
2707
2096
  const createProjectionRuntime = (options) => {
2708
- const runtime = createProjectionGraph({ onError: options?.onError ?? (() => void 0) });
2097
+ const scheduler = require_advanced.createScheduler(options?.onError ?? (() => void 0));
2098
+ const sources = createSourceRegistry(scheduler);
2709
2099
  const materialized = /* @__PURE__ */ new WeakMap();
2710
- const groups = /* @__PURE__ */ new WeakMap();
2711
- let disposed = false;
2100
+ const publicViews = /* @__PURE__ */ new WeakMap();
2712
2101
  const scopes = /* @__PURE__ */ new Set();
2713
- let materialize;
2714
- const materializeGroup = (definition, requester) => {
2715
- const existing = groups.get(definition);
2716
- if (existing) return existing;
2717
- if (definition.projections.map((projection) => require_advanced.ownerOf(projection)).filter((owner) => owner !== void 0).some((owner) => owner !== requester)) throw new TypeError("Projection group belongs to another scope.");
2718
- const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, requester))]));
2719
- const nodes = runtime.group({
2720
- sources,
2721
- outputs: definition.outputs,
2722
- build: definition.build
2723
- });
2724
- const group = Object.freeze({
2725
- definition,
2726
- nodes
2727
- });
2728
- groups.set(definition, group);
2729
- for (let index = 0; index < definition.projections.length; index++) materialized.set(definition.projections[index], {
2730
- kind: "projection",
2731
- node: nodes[index]
2732
- });
2733
- if (requester) requester.materialized.push(definition);
2734
- return group;
2102
+ const assertAccess = (definition, requester) => {
2103
+ scheduler.assertActive();
2104
+ const owner = definition.owner;
2105
+ if (owner === void 0) return;
2106
+ if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
2107
+ if (!requester?.active) throw new require_advanced.ProjectionDisposedError();
2735
2108
  };
2736
- materialize = (projection, requester) => {
2737
- const owner = require_advanced.ownerOf(projection);
2738
- if (owner) {
2739
- if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
2740
- if (!requester.active) throw new require_advanced.ProjectionDisposedError();
2741
- }
2742
- const old = materialized.get(projection);
2743
- if (old) return old.node;
2744
- const definition = require_advanced.definitionOf(projection);
2745
- if (definition.kind === "incremental-group-output") return materializeGroup(definition.group, requester).nodes[definition.output];
2746
- let instance;
2747
- let write;
2748
- switch (definition.kind) {
2749
- case "input": {
2750
- const input = runtime.input(definition.initial, { isEqual: definition.isEqual });
2751
- write = {
2752
- kind: "value-input",
2753
- source: input.source,
2754
- set: input.set
2755
- };
2756
- instance = runtime.value({
2757
- sources: { input: input.source },
2758
- build: (values) => ({
2759
- value: values.input.value,
2760
- update: (next) => ({
2761
- kind: "changed",
2762
- value: next.input.value
2763
- })
2764
- })
2765
- });
2766
- break;
2767
- }
2768
- case "collection-input": {
2769
- const source = runtime.collectionInput(definition.initial);
2770
- write = {
2771
- kind: "collection-input",
2772
- source: source.source,
2773
- update: source.update
2774
- };
2775
- instance = runtime.map(source.source, (_key, value) => value);
2776
- break;
2777
- }
2778
- case "readable": {
2779
- const source = runtime.fromReadable(definition.readable, { isEqual: definition.isEqual });
2780
- instance = runtime.value({
2781
- sources: { source },
2782
- build: (values) => ({
2783
- value: values.source.value,
2784
- update: (next) => ({
2785
- kind: "changed",
2786
- value: next.source.value
2787
- })
2788
- })
2789
- });
2790
- break;
2791
- }
2792
- case "source-value":
2793
- {
2794
- const source = runtime.fromSource(definition.source);
2795
- instance = runtime.value({
2796
- sources: { source },
2797
- build: (values) => ({
2798
- value: values.source.value,
2799
- update: (next) => ({
2800
- kind: "changed",
2801
- value: next.source.value
2802
- })
2803
- })
2804
- });
2805
- }
2806
- break;
2807
- case "source-collection": {
2808
- const source = runtime.fromCollectionSource(definition.source);
2809
- instance = runtime.map(source, (_key, value) => value);
2810
- break;
2811
- }
2812
- case "document": {
2813
- const document = runtime.document(definition.document);
2814
- if (!definition.selector) {
2815
- instance = runtime.value({
2816
- sources: { document },
2817
- build: (values) => ({
2818
- value: require_scope.snapshot(values.document.read),
2819
- update: (next) => ({
2820
- kind: "changed",
2821
- value: require_scope.snapshot(next.document.read)
2822
- })
2823
- })
2824
- });
2825
- break;
2826
- }
2827
- const state = require_notification.accessOf(definition.document);
2828
- try {
2829
- const collection = document.collection(definition.selector);
2830
- instance = runtime.map(collection, (_id, entry) => entry);
2831
- break;
2832
- } catch {
2833
- const selected = document.targets(definition.selector);
2834
- const address = compilePath(state.schema, "value", definition.selector).address;
2835
- instance = runtime.value({
2836
- sources: { selected },
2837
- build: () => ({
2838
- value: require_issue.read(require_scope.snapshot(state.document), address, state.schema),
2839
- update: () => ({
2840
- kind: "changed",
2841
- value: require_issue.read(require_scope.snapshot(state.document), address, state.schema)
2842
- })
2843
- })
2844
- });
2845
- }
2846
- break;
2847
- }
2848
- case "derive": {
2849
- const sources = Object.fromEntries(definition.dependencies.map((dependency, index) => [`d${index}`, sourceOf(materialize(dependency, owner))]));
2850
- instance = runtime.value({
2851
- sources,
2852
- build: (values) => ({
2853
- value: definition.compute(...Object.values(values).map(currentValue)),
2854
- update: (next) => ({
2855
- kind: "changed",
2856
- value: definition.compute(...Object.values(next).map(currentValue))
2857
- })
2858
- })
2859
- }, { isEqual: definition.isEqual });
2860
- break;
2861
- }
2862
- case "incremental-value": {
2863
- const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, owner))]));
2864
- instance = runtime.value({
2865
- sources,
2866
- build: definition.build
2867
- }, { isEqual: definition.isEqual });
2868
- break;
2869
- }
2870
- case "incremental-collection": {
2871
- const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, owner))]));
2872
- instance = runtime.collection({
2873
- sources,
2874
- build: definition.build,
2875
- isEqual: definition.isEqual,
2876
- name: definition.name
2877
- });
2878
- break;
2879
- }
2109
+ const rememberScoped = (definition, requester) => {
2110
+ if (requester && definition.owner === requester) requester.materialized.push(definition);
2111
+ };
2112
+ const materializeProducer = (definition, requester) => {
2113
+ assertAccess(definition, requester);
2114
+ const existing = materialized.get(definition);
2115
+ if (existing) return existing;
2116
+ let result;
2117
+ if (definition.kind === "source") {
2118
+ const source = sources.materialize(definition);
2119
+ result = Object.freeze({
2120
+ record: source.producer,
2121
+ outputs: Object.freeze([source.output]),
2122
+ ...source.write ? { write: source.write } : {}
2123
+ });
2124
+ } else {
2125
+ if (definition.owner === void 0 && definition.dependencies.some((dependency) => require_advanced.producerOf(dependency).owner !== void 0)) throw new TypeError("A root projection cannot depend on a scoped projection.");
2126
+ const dependencies = definition.dependencies.map((dependency) => resolveOutput(dependency, requester));
2127
+ const processor = createProcessor(scheduler, definition, Object.freeze(dependencies));
2128
+ result = Object.freeze({
2129
+ record: processor,
2130
+ outputs: processor.outputs
2131
+ });
2880
2132
  }
2881
- const record = write ? {
2882
- node: instance,
2883
- ...write
2884
- } : {
2885
- kind: "projection",
2886
- node: instance
2887
- };
2888
- materialized.set(projection, record);
2889
- if (owner) owner.materialized.push(projection);
2890
- return instance;
2133
+ materialized.set(definition, result);
2134
+ rememberScoped(definition, requester);
2135
+ return result;
2136
+ };
2137
+ const resolveOutput = (projection, requester) => {
2138
+ const ref = require_advanced.projectionRef(projection);
2139
+ const output = materializeProducer(ref.producer, requester).outputs[ref.output];
2140
+ if (!output) throw new TypeError("Projection output does not exist.");
2141
+ return output;
2142
+ };
2143
+ const assertOutput = (output) => {
2144
+ scheduler.assertActive();
2145
+ if (output.owner.disposed) throw new require_advanced.ProjectionDisposedError();
2146
+ if (output.owner.fault) throw output.owner.fault;
2891
2147
  };
2892
- const get = (projection, requester) => {
2893
- const instance = materialize(projection, requester);
2894
- const current = instance.current();
2895
- if (instance.kind === "collection") {
2896
- const record = materialized.get(projection) ?? materialized.get(instance);
2897
- const existing = record?.collection;
2898
- const revision = instance.revision();
2899
- if (existing?.revision === revision) return existing.value;
2900
- const value = lazyMapView(current);
2901
- if (record) record.collection = {
2148
+ const publicCurrent = (output) => {
2149
+ assertOutput(output);
2150
+ const current = output.current();
2151
+ const revision = output.revision();
2152
+ const cached = publicViews.get(output);
2153
+ if (cached?.revision === revision) return cached.value;
2154
+ if (output.kind === "collection") {
2155
+ const value = require_advanced.collectionView(current);
2156
+ publicViews.set(output, {
2902
2157
  revision,
2903
2158
  value
2904
- };
2159
+ });
2905
2160
  return value;
2906
2161
  }
2907
2162
  if (isMapLike(current)) {
2908
- const record = materialized.get(projection) ?? materialized.get(instance);
2909
- const existing = record?.collection;
2910
- const revision = instance.revision();
2911
- if (existing?.revision === revision) return existing.value;
2912
- const value = mapView(mapRead(current));
2913
- if (record) record.collection = {
2163
+ const value = require_advanced.snapshotCollectionView(require_advanced.mapRead(current));
2164
+ publicViews.set(output, {
2914
2165
  revision,
2915
2166
  value
2916
- };
2167
+ });
2917
2168
  return value;
2918
2169
  }
2170
+ publicViews.delete(output);
2919
2171
  return current;
2920
2172
  };
2921
- const makeReadable = (projection, selector, equality = Object.is, requester) => {
2922
- if (!selector) return Object.freeze({
2923
- current: () => get(projection, requester),
2924
- revision: () => materialize(projection, requester).revision(),
2925
- subscribe: (listener) => materialize(projection, requester).subscribe(listener)
2926
- });
2927
- let initialized = false;
2928
- let value;
2929
- let selectedRevision = 0;
2930
- let sourceRevision = -1;
2931
- let selection = Object.freeze({
2932
- keys: /* @__PURE__ */ new Set(),
2933
- all: true,
2934
- structure: false
2935
- });
2936
- let previousCollection;
2937
- let unsubscribeSource;
2938
- const listeners = /* @__PURE__ */ new Set();
2939
- const updateCollectionSnapshot = () => {
2940
- const current = get(projection, requester);
2941
- previousCollection = isMapLike(current) ? current : void 0;
2942
- };
2943
- const evaluate = () => {
2944
- const tracker = {
2945
- keys: /* @__PURE__ */ new Set(),
2946
- all: false,
2947
- structure: false
2948
- };
2949
- const previousTracker = activeTracker;
2950
- activeTracker = tracker;
2951
- let source;
2952
- let next;
2953
- try {
2954
- source = get(projection, requester);
2955
- next = selector(source);
2956
- } finally {
2957
- activeTracker = previousTracker;
2958
- }
2959
- const nextSelection = Object.freeze({
2960
- keys: new Set(tracker.keys),
2961
- all: tracker.all || isMapLike(source) && tracker.keys.size === 0,
2962
- structure: tracker.structure
2963
- });
2964
- const changed = !initialized || !equality(value, next);
2965
- if (changed) {
2966
- if (initialized) selectedRevision++;
2967
- value = next;
2968
- }
2969
- selection = nextSelection;
2970
- initialized = true;
2971
- sourceRevision = materialize(projection, requester).revision();
2972
- updateCollectionSnapshot();
2973
- return changed;
2974
- };
2975
- const ensureCurrent = () => {
2976
- const revision = materialize(projection, requester).revision();
2977
- if (!initialized || !unsubscribeSource && sourceRevision !== revision) evaluate();
2978
- return value;
2979
- };
2980
- const onProjectionChange = (incoming) => {
2981
- let change = incoming;
2982
- if (!change && previousCollection) {
2983
- const current = get(projection, requester);
2984
- if (isMapLike(current)) {
2985
- change = mapChange(previousCollection, current);
2986
- previousCollection = current;
2987
- if (!change) {
2988
- sourceRevision = materialize(projection, requester).revision();
2989
- return;
2990
- }
2991
- } else previousCollection = void 0;
2992
- }
2993
- if (!selectionAffects(selection, change)) {
2994
- sourceRevision = materialize(projection, requester).revision();
2995
- updateCollectionSnapshot();
2996
- return;
2997
- }
2998
- const previousSelection = selection;
2999
- const changed = evaluate();
3000
- if (unsubscribeSource && !sameSelection(previousSelection, selection)) {
3001
- unsubscribeSource();
3002
- unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
3003
- }
3004
- if (changed) Array.from(listeners).forEach((listener) => listener());
3005
- };
3006
- const installSource = () => {
3007
- if (!unsubscribeSource) unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
3008
- };
2173
+ const get = (projection, requester) => publicCurrent(resolveOutput(projection, requester));
2174
+ const readableSource = (projection, requester) => {
2175
+ const output = resolveOutput(projection, requester);
3009
2176
  return Object.freeze({
3010
- current: ensureCurrent,
2177
+ kind: output.kind,
2178
+ current: () => get(projection, requester),
3011
2179
  revision: () => {
3012
- ensureCurrent();
3013
- return selectedRevision;
2180
+ assertOutput(output);
2181
+ return output.revision();
3014
2182
  },
3015
- subscribe: (listener) => {
3016
- ensureCurrent();
3017
- listeners.add(listener);
3018
- installSource();
3019
- return () => {
3020
- listeners.delete(listener);
3021
- if (!listeners.size && unsubscribeSource) {
3022
- unsubscribeSource();
3023
- unsubscribeSource = void 0;
3024
- }
3025
- };
3026
- }
2183
+ subscribe: output.subscribe
3027
2184
  });
3028
2185
  };
2186
+ const makeReadable = (projection, selector, equality, requester) => {
2187
+ const source = readableSource(projection, requester);
2188
+ return selector ? createSelectorReadable(source, selector, equality) : createDirectReadable(source);
2189
+ };
3029
2190
  function readable(projection, selector, equality) {
3030
2191
  return makeReadable(projection, selector, equality);
3031
2192
  }
2193
+ const inputWrite = (target, requester) => {
2194
+ const ref = require_advanced.projectionRef(target);
2195
+ if (ref.output !== 0 || ref.producer.kind !== "source") throw new TypeError("Projection is not an input.");
2196
+ if (ref.producer.source.kind !== "value-input" && ref.producer.source.kind !== "collection-input") throw new TypeError("Projection is not an input.");
2197
+ const write = materializeProducer(ref.producer, requester).write;
2198
+ if (!write) throw new TypeError("Projection input is not writable.");
2199
+ return write;
2200
+ };
3032
2201
  const setInput = (target, value, requester) => {
3033
- materialize(target, requester);
3034
- const record = materialized.get(target);
3035
- if (record?.kind !== "value-input") throw new TypeError("Projection is not a value input.");
3036
- record.set(value);
2202
+ const write = inputWrite(target, requester);
2203
+ if (write.kind !== "value") throw new TypeError("Projection is not a value input.");
2204
+ write.set(value);
3037
2205
  };
3038
2206
  const updateInput = (target, run, requester) => {
3039
- materialize(target, requester);
3040
- const record = materialized.get(target);
3041
- if (record?.kind !== "collection-input") throw new TypeError("Projection is not a collection input.");
3042
- record.update(run);
2207
+ const write = inputWrite(target, requester);
2208
+ if (write.kind !== "collection") throw new TypeError("Projection is not a collection input.");
2209
+ write.update(run);
2210
+ };
2211
+ function batch(optionsOrCallback, maybeCallback) {
2212
+ return typeof optionsOrCallback === "function" ? scheduler.batch(optionsOrCallback) : scheduler.batch(optionsOrCallback, maybeCallback);
2213
+ }
2214
+ const disposeScope = (state) => {
2215
+ if (!state.active) return;
2216
+ scheduler.assertIdle();
2217
+ state.active = false;
2218
+ state.subscriptions.forEach((unsubscribe) => unsubscribe());
2219
+ state.subscriptions.clear();
2220
+ for (let index = state.materialized.length - 1; index >= 0; index--) {
2221
+ const definition = state.materialized[index];
2222
+ const current = materialized.get(definition);
2223
+ if (!current) continue;
2224
+ scheduler.releaseProducer(current.record);
2225
+ materialized.delete(definition);
2226
+ }
2227
+ state.materialized.length = 0;
2228
+ scopes.delete(state);
3043
2229
  };
3044
2230
  const createScope = () => {
3045
- runtime.assertIdle();
2231
+ scheduler.assertIdle();
3046
2232
  const state = {
3047
2233
  active: true,
3048
2234
  materialized: [],
@@ -3051,33 +2237,26 @@ const createProjectionRuntime = (options) => {
3051
2237
  scopes.add(state);
3052
2238
  const assertActive = () => {
3053
2239
  if (!state.active) throw new require_advanced.ProjectionDisposedError();
2240
+ scheduler.assertActive();
3054
2241
  };
3055
2242
  const own = (projection) => {
3056
2243
  assertActive();
3057
- const definition = require_advanced.definitionOf(projection);
3058
- const dependencies = definition.kind === "derive" ? definition.dependencies : definition.kind === "incremental-value" || definition.kind === "incremental-collection" ? Object.values(definition.dependencies) : definition.kind === "incremental-group-output" ? Object.values(definition.group.dependencies) : [];
3059
- for (const dependency of dependencies) {
3060
- const dependencyOwner = require_advanced.ownerOf(dependency);
3061
- if (dependencyOwner && dependencyOwner !== state) throw new TypeError("A scope cannot depend on another scope.");
3062
- }
3063
- require_advanced.ownDefinition(projection, state);
3064
- return projection;
2244
+ return require_advanced.ownProjection(projection, state);
3065
2245
  };
3066
- const scopedInput = Object.assign((initial, equality) => own(require_advanced.input(initial, equality)), { collection: (initial) => own(require_advanced.input.collection(initial)) });
3067
- const scopedDerive = (dependencies, compute, equality) => own(require_advanced.derive(dependencies, compute, equality));
3068
2246
  const ownOutputTree = (tree) => {
3069
2247
  if (require_advanced.isProjection(tree)) return own(tree);
3070
2248
  if (!tree || typeof tree !== "object") throw new TypeError("Incremental group output tree must be an object.");
3071
2249
  for (const value of Object.values(tree)) ownOutputTree(value);
3072
2250
  return tree;
3073
2251
  };
2252
+ const scopedInput = Object.assign((initial, equality) => own(require_advanced.input(initial, equality)), { collection: (initial) => own(require_advanced.input.collection(initial)) });
2253
+ const scopedDerive = (dependencies, compute, equality) => own(require_advanced.derive(dependencies, compute, equality));
3074
2254
  const scopedIncremental = Object.assign((dependencies, processor) => own(require_advanced.incremental(dependencies, processor)), {
3075
2255
  collection: (dependencies, processor) => own(require_advanced.incremental.collection(dependencies, processor)),
3076
2256
  group: (dependencies, defineOutputs, processor) => ownOutputTree(require_advanced.incremental.group(dependencies, defineOutputs, processor))
3077
2257
  });
3078
2258
  function scopeReadable(projection, selector, equality) {
3079
2259
  assertActive();
3080
- materialize(projection, state);
3081
2260
  const source = makeReadable(projection, selector, equality, state);
3082
2261
  return Object.freeze({
3083
2262
  current: () => {
@@ -3105,7 +2284,7 @@ const createProjectionRuntime = (options) => {
3105
2284
  }
3106
2285
  function scopeBatch(optionsOrCallback, maybeCallback) {
3107
2286
  assertActive();
3108
- return typeof optionsOrCallback === "function" ? runtime.batch(optionsOrCallback) : runtime.batch(optionsOrCallback, maybeCallback);
2287
+ return typeof optionsOrCallback === "function" ? batch(optionsOrCallback) : batch(optionsOrCallback, maybeCallback);
3109
2288
  }
3110
2289
  return Object.freeze({
3111
2290
  input: scopedInput,
@@ -3125,41 +2304,19 @@ const createProjectionRuntime = (options) => {
3125
2304
  updateInput(target, run, state);
3126
2305
  },
3127
2306
  batch: scopeBatch,
3128
- dispose: () => {
3129
- if (!state.active) return;
3130
- runtime.assertIdle();
3131
- state.active = false;
3132
- state.subscriptions.forEach((unsubscribe) => unsubscribe());
3133
- for (const definition of state.materialized.reverse()) {
3134
- const group = groups.get(definition);
3135
- if (group) {
3136
- runtime.releaseNode(group.nodes[0]);
3137
- groups.delete(definition);
3138
- group.definition.projections.forEach((projection) => materialized.delete(projection));
3139
- continue;
3140
- }
3141
- const record = materialized.get(definition);
3142
- if (!record) continue;
3143
- runtime.releaseNode(record.node);
3144
- if (record.kind !== "projection") runtime.releaseInput(record.source);
3145
- materialized.delete(definition);
3146
- }
3147
- state.materialized.length = 0;
3148
- scopes.delete(state);
3149
- }
2307
+ dispose: () => disposeScope(state)
3150
2308
  });
3151
2309
  };
3152
- const store = {
2310
+ return Object.freeze({
3153
2311
  get,
3154
2312
  readable,
3155
2313
  set: setInput,
3156
2314
  update: updateInput,
3157
- batch: runtime.batch,
2315
+ batch,
3158
2316
  scope: createScope,
3159
2317
  dispose: () => {
3160
- if (disposed) return;
3161
- runtime.assertIdle();
3162
- disposed = true;
2318
+ if (!scheduler.active) return;
2319
+ scheduler.assertIdle();
3163
2320
  for (const scope of scopes) {
3164
2321
  scope.active = false;
3165
2322
  scope.subscriptions.forEach((unsubscribe) => unsubscribe());
@@ -3167,35 +2324,35 @@ const createProjectionRuntime = (options) => {
3167
2324
  scope.materialized.length = 0;
3168
2325
  }
3169
2326
  scopes.clear();
3170
- runtime.dispose();
2327
+ sources.dispose();
2328
+ scheduler.dispose();
3171
2329
  }
3172
- };
3173
- return Object.freeze(store);
2330
+ });
3174
2331
  };
3175
2332
  //#endregion
3176
- exports.DocumentDisposedError = require_notification.DocumentDisposedError;
3177
- exports.DocumentReentrancyError = require_notification.DocumentReentrancyError;
3178
- exports.ParseError = require_scope.ParseError;
2333
+ exports.DocumentDisposedError = require_access.DocumentDisposedError;
2334
+ exports.DocumentReentrancyError = require_access.DocumentReentrancyError;
2335
+ exports.ParseError = require_access.ParseError;
3179
2336
  exports.ProjectionDisposedError = require_advanced.ProjectionDisposedError;
3180
2337
  exports.ProjectionError = require_advanced.ProjectionError;
3181
- exports.TransactionRejected = require_notification.TransactionRejected;
2338
+ exports.TransactionRejected = require_access.TransactionRejected;
3182
2339
  exports.asReadable = asReadable;
3183
2340
  exports.createDocument = createDocument;
3184
2341
  exports.createProjectionRuntime = createProjectionRuntime;
3185
2342
  exports.derive = require_advanced.derive;
3186
- exports.field = field;
2343
+ exports.field = require_advanced.field;
3187
2344
  exports.input = require_advanced.input;
3188
- exports.list = list;
3189
- exports.map = map;
3190
- exports.object = object;
2345
+ exports.list = require_advanced.list;
2346
+ exports.map = require_advanced.map;
2347
+ exports.object = require_advanced.object;
3191
2348
  exports.observe = require_advanced.observe;
3192
- exports.optional = optional;
3193
- exports.parse = require_scope.parse;
2349
+ exports.optional = require_advanced.optional;
2350
+ exports.parse = require_access.parse;
3194
2351
  exports.read = read;
3195
- exports.replace = require_scope.replace;
3196
- exports.snapshot = require_scope.snapshot;
3197
- exports.table = table;
3198
- exports.tree = tree;
3199
- exports.variant = variant;
2352
+ exports.replace = require_access.replace;
2353
+ exports.snapshot = require_access.snapshot;
2354
+ exports.table = require_advanced.table;
2355
+ exports.tree = require_advanced.tree;
2356
+ exports.variant = require_advanced.variant;
3200
2357
 
3201
2358
  //# sourceMappingURL=index.cjs.map