doxum 0.1.18 → 0.1.20

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 (57) hide show
  1. package/README.md +38 -3
  2. package/dist/{scope-d-L87NeC.js → access-B9XDWjHy.js} +54 -2
  3. package/dist/access-B9XDWjHy.js.map +1 -0
  4. package/dist/{scope-Ezs-Y3TY.cjs → access-EQ-XiFyl.cjs} +89 -1
  5. package/dist/access-EQ-XiFyl.cjs.map +1 -0
  6. package/dist/advanced-BIGP86NL.cjs +1361 -0
  7. package/dist/advanced-BIGP86NL.cjs.map +1 -0
  8. package/dist/advanced-Bcle5C2M.js +1188 -0
  9. package/dist/advanced-Bcle5C2M.js.map +1 -0
  10. package/dist/{advanced-c0F3gpLG.d.ts → advanced-CSjF-u_k.d.cts} +50 -12
  11. package/dist/{advanced-GTwIWcn6.d.cts → advanced-Du3O8O1A.d.ts} +50 -12
  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-BDQth1pe.d.cts} +4 -4
  17. package/dist/{contract-CATCCVu5.d.ts → contract-BlEl8Ibo.d.ts} +4 -4
  18. package/dist/{driver-BCyfqxsl.cjs → driver-DRoWiadc.cjs} +1 -1
  19. package/dist/{driver-BCyfqxsl.cjs.map → driver-DRoWiadc.cjs.map} +1 -1
  20. package/dist/{driver-CSnhMjbP.js → driver-DiN-0Mnc.js} +1 -1
  21. package/dist/{driver-CSnhMjbP.js.map → driver-DiN-0Mnc.js.map} +1 -1
  22. package/dist/index.cjs +1181 -2114
  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 +1133 -2066
  27. package/dist/index.js.map +1 -1
  28. package/dist/{integration-CxVhq-KD.js → integration-DHxVpbxc.js} +3 -2
  29. package/dist/{integration-CxVhq-KD.js.map → integration-DHxVpbxc.js.map} +1 -1
  30. package/dist/{integration-B7VDgAOJ.cjs → integration-DL6dLuu1.cjs} +4 -3
  31. package/dist/{integration-B7VDgAOJ.cjs.map → integration-DL6dLuu1.cjs.map} +1 -1
  32. package/dist/integration.cjs +2 -2
  33. package/dist/integration.d.cts +1 -1
  34. package/dist/integration.d.ts +1 -1
  35. package/dist/integration.js +2 -2
  36. package/dist/local-sync.cjs +1 -1
  37. package/dist/local-sync.d.cts +1 -1
  38. package/dist/local-sync.d.ts +1 -1
  39. package/dist/local-sync.js +1 -1
  40. package/dist/{notification-IQIJyH3Q.js → notification-AXT94XYC.js} +2 -55
  41. package/dist/notification-AXT94XYC.js.map +1 -0
  42. package/dist/{notification-CDSUfiip.cjs → notification-DGv-_G9t.cjs} +1 -90
  43. package/dist/notification-DGv-_G9t.cjs.map +1 -0
  44. package/dist/react.cjs +2 -2
  45. package/dist/react.js +2 -2
  46. package/package.json +1 -1
  47. package/skills/doxum-runtime/SKILL.md +2 -1
  48. package/skills/doxum-runtime/references/projections.en.md +41 -3
  49. package/skills/doxum-runtime/references/projections.zh-CN.md +36 -2
  50. package/dist/advanced-DgRnyRAc.js +0 -233
  51. package/dist/advanced-DgRnyRAc.js.map +0 -1
  52. package/dist/advanced-Dgu8-bPS.cjs +0 -274
  53. package/dist/advanced-Dgu8-bPS.cjs.map +0 -1
  54. package/dist/notification-CDSUfiip.cjs.map +0 -1
  55. package/dist/notification-IQIJyH3Q.js.map +0 -1
  56. package/dist/scope-Ezs-Y3TY.cjs.map +0 -1
  57. 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
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-Dgu8-bPS.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
3
+ const require_access = require("./access-EQ-XiFyl.cjs");
4
+ const require_advanced = require("./advanced-BIGP86NL.cjs");
5
+ const require_notification = require("./notification-DGv-_G9t.cjs");
6
+ const require_driver = require("./driver-DRoWiadc.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);
@@ -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;
@@ -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,2059 +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);
1106
- //#endregion
1107
- //#region core/src/projection/contract.ts
1108
- var ProjectionError = class extends Error {
1109
- constructor(phase, identity, revisions, cause) {
1110
- super(`Projection ${phase} failed: ${identity}`, { cause });
1111
- this.phase = phase;
1112
- this.identity = identity;
1113
- this.revisions = revisions;
1114
- this.name = "ProjectionError";
1115
- }
1116
- };
1117
- var ProjectionDisposedError = class extends Error {
1118
- constructor() {
1119
- super("Projection has been disposed.");
1120
- this.name = "ProjectionDisposedError";
1121
- }
1122
- };
1017
+ const read = (runtime, selector) => require_access.readWith(runtime, selector);
1123
1018
  //#endregion
1124
- //#region core/src/projection/scheduler.ts
1125
- const projectionHandles = /* @__PURE__ */ new WeakSet();
1126
- const createScheduler = (onError) => {
1127
- let disposed = false;
1128
- let depth = 0;
1129
- let batchSequence = 0;
1130
- let activeBatch;
1131
- let phase = "idle";
1132
- let sequence = 0;
1133
- const records = /* @__PURE__ */ new Map();
1134
- const nodes = /* @__PURE__ */ new Set();
1135
- const pending = /* @__PURE__ */ new Set();
1136
- const heap = [];
1137
- const queued = /* @__PURE__ */ new Set();
1138
- const completed = [];
1139
- const emissions = /* @__PURE__ */ new Set();
1140
- const errors = [];
1141
- let reportingFailures = [];
1142
- const guards = /* @__PURE__ */ new Set();
1143
- const cleanups = /* @__PURE__ */ new Set();
1144
- const assertActive = () => {
1145
- if (disposed) throw new ProjectionDisposedError();
1146
- };
1147
- const assertIdle = () => {
1148
- assertActive();
1149
- if (phase !== "idle") throw new Error("Projection cannot be re-entered during processing or notification.");
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;
1042
+ },
1043
+ output: Object.freeze({ set: (candidate) => {
1044
+ require_advanced.assertScope(active);
1045
+ staged = candidate;
1046
+ } })
1047
+ };
1150
1048
  };
1151
- const lock = (value) => guards.forEach((guard) => guard(value));
1152
- const enqueue = (node) => {
1153
- if (node.disposed || queued.has(node)) return;
1154
- queued.add(node);
1155
- require_issue.profile.projection("scheduledNodes");
1156
- let i = heap.length;
1157
- heap.push(node);
1158
- while (i > 0) {
1159
- const p = i - 1 >> 1;
1160
- if (heap[p].order < node.order) break;
1161
- heap[i] = heap[p];
1162
- i = p;
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;
1163
1056
  }
1164
- heap[i] = node;
1057
+ const candidate = staged;
1058
+ changed = !initialized || !isEqual(value, candidate);
1059
+ next = changed ? candidate : value;
1060
+ reset = nextReset;
1061
+ return changed;
1165
1062
  };
1166
- const pop = () => {
1167
- const first = heap[0];
1168
- const last = heap.pop();
1169
- if (heap.length) {
1170
- let i = 0;
1171
- while (i * 2 + 1 < heap.length) {
1172
- let c = i * 2 + 1;
1173
- if (c + 1 < heap.length && heap[c + 1].order < heap[c].order) c++;
1174
- if (last.order < heap[c].order) break;
1175
- heap[i] = heap[c];
1176
- i = c;
1177
- }
1178
- heap[i] = last;
1179
- }
1180
- return first;
1063
+ return {
1064
+ begin,
1065
+ seal,
1066
+ context: (active, cause) => {
1067
+ require_advanced.assertScope(active);
1068
+ return Object.freeze({
1069
+ kind: "value",
1070
+ value: next,
1071
+ revision,
1072
+ reset,
1073
+ cause
1074
+ });
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)
1181
1108
  };
1182
- const errorFor = (node, cause, kind = "processor") => new ProjectionError(kind, node.name, node.sources.map((source) => source.revision()), cause);
1183
- const capture = (source) => {
1184
- if (disposed) return;
1185
- if (phase !== "idle") {
1186
- source.fault = new ProjectionError("source", "reentrant source", [source.revision()], /* @__PURE__ */ new Error("Source changed while projection was running."));
1187
- errors.push(source.fault);
1188
- throw source.fault;
1189
- }
1190
- pending.add(source);
1191
- require_issue.profile.projection("sourceEvents");
1192
- if (source.fault) errors.push(source.fault);
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;
1193
1121
  };
1194
- const settle = () => {
1195
- if (disposed || depth || phase !== "idle") return;
1196
- phase = "compute";
1197
- lock(true);
1198
- try {
1199
- pending.forEach((source) => {
1200
- if (source.shouldSettle?.() ?? true) source.consumers.forEach(enqueue);
1201
- });
1202
- while (heap.length) {
1203
- const node = pop();
1204
- if (node.disposed) continue;
1205
- const wasFaulted = node.fault !== void 0;
1206
- require_issue.profile.projection("processedNodes");
1207
- let changed = false;
1208
- const blocked = node.sources.find((source) => source.fault || source.disposed);
1209
- if (blocked) node.fault = errorFor(node, blocked.fault ?? new ProjectionDisposedError(), "blocked");
1210
- else {
1211
- const build = wasFaulted || node.sources.some((source) => source.reset());
1212
- try {
1213
- changed = node.evaluate(build);
1214
- const failure = node.sources.find((source) => source.fault)?.fault;
1215
- if (failure) throw failure;
1216
- node.fault = void 0;
1217
- } catch (cause) {
1218
- const error = errorFor(node, cause);
1219
- errors.push(error);
1220
- node.fault = error;
1221
- if (!build) try {
1222
- changed = node.evaluate(true);
1223
- const failure = node.sources.find((source) => source.fault)?.fault;
1224
- if (failure) throw failure;
1225
- node.fault = void 0;
1226
- } catch (cause) {
1227
- node.fault = errorFor(node, cause);
1228
- errors.push(node.fault);
1229
- }
1230
- }
1231
- }
1232
- completed.push(node);
1233
- if (changed || wasFaulted || node.fault) {
1234
- require_issue.profile.projection("publishedNodes");
1235
- emissions.add(node);
1236
- node.consumers.forEach(enqueue);
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;
1133
+ },
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);
1144
+ },
1145
+ emit: state.emit,
1146
+ clear: state.clear,
1147
+ release: state.release
1237
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
1238
1175
  }
1239
- completed.forEach((node) => node.publish());
1240
- } finally {
1241
- lock(false);
1242
- phase = "idle";
1176
+ };
1177
+ });
1178
+ const begin = (active, initialize) => Object.freeze(bound.map((entry) => {
1179
+ if (entry.kind === "value") {
1180
+ const evaluation = entry.state.begin(active, initialize);
1181
+ return Object.freeze({
1182
+ kind: "value",
1183
+ ...evaluation
1184
+ });
1243
1185
  }
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);
1244
1196
  };
1245
- const flush = () => {
1246
- if (disposed || depth) return [];
1247
- if (pending.size || completed.length) require_issue.profile.projection("flushes");
1248
- phase = "notify";
1249
- lock(true);
1250
- const reportFailures = [];
1197
+ const run = (reset, runCause) => {
1198
+ cause = runCause;
1199
+ let active = true;
1251
1200
  try {
1252
- emissions.forEach((node) => node.emit((listener) => {
1253
- try {
1254
- listener();
1255
- } catch (cause) {
1256
- errors.push(errorFor(node, cause, "listener"));
1257
- }
1258
- }));
1259
- for (const error of errors.slice()) try {
1260
- onError(error);
1261
- } catch (cause) {
1262
- reportFailures.push(cause);
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
1211
+ });
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
1223
+ });
1224
+ require_advanced.assertSynchronous(result);
1225
+ if (result?.kind === "rebuild") throw new TypeError("Reset projection processor cannot rebuild again.");
1226
+ return seal(true);
1263
1227
  }
1264
- return Object.freeze([...errors.map((error) => Object.freeze({
1265
- phase: error.phase === "listener" ? "listener" : "processor",
1266
- error
1267
- })), ...reportFailures.map((error) => Object.freeze({
1268
- phase: "listener",
1269
- error
1270
- }))]);
1228
+ return seal(reset);
1271
1229
  } finally {
1272
- pending.forEach((source) => source.clear());
1273
- completed.forEach((node) => node.clear());
1274
- pending.clear();
1275
- queued.clear();
1276
- completed.length = 0;
1277
- emissions.clear();
1278
- errors.length = 0;
1279
- reportingFailures = reportFailures;
1280
- lock(false);
1281
- phase = "idle";
1230
+ active = false;
1282
1231
  }
1283
1232
  };
1284
- const run = () => {
1285
- settle();
1286
- const result = flush();
1287
- const failures = reportingFailures;
1288
- reportingFailures = [];
1289
- if (failures.length) throw new AggregateError(failures, "Projection error reporter failed.");
1290
- return result;
1291
- };
1292
- const batch = (optionsOrCallback, maybeCallback) => {
1293
- assertIdle();
1294
- const options = typeof optionsOrCallback === "function" ? void 0 : optionsOrCallback;
1295
- const callback = typeof optionsOrCallback === "function" ? optionsOrCallback : maybeCallback;
1296
- if (!callback) throw new TypeError("Projection batch requires a callback.");
1297
- if (depth === 0) activeBatch = Object.freeze({
1298
- id: ++batchSequence,
1299
- ...options?.cause === void 0 ? {} : { cause: options.cause }
1300
- });
1301
- depth++;
1302
- let failed = false;
1303
- try {
1304
- const value = callback();
1305
- assertSynchronous(value);
1306
- return value;
1307
- } catch (error) {
1308
- failed = true;
1309
- throw error;
1310
- } finally {
1311
- depth--;
1312
- if (!depth) try {
1313
- if (failed) try {
1314
- run();
1315
- } catch {}
1316
- else run();
1317
- } finally {
1318
- activeBatch = void 0;
1319
- }
1233
+ const order = scheduler.order();
1234
+ processor = {
1235
+ kind: "processor",
1236
+ order,
1237
+ name: definition.name ? `${definition.name} (${order})` : `projection-${order}`,
1238
+ dependencies: Object.freeze([...dependencies]),
1239
+ outputs: Object.freeze(bound.map((entry) => entry.output)),
1240
+ fault: void 0,
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;
1320
1256
  }
1321
1257
  };
1322
- return {
1323
- assertActive,
1324
- assertIdle,
1325
- capture,
1326
- settle,
1327
- flush,
1328
- run,
1329
- records,
1330
- guards,
1331
- cleanups,
1332
- get active() {
1333
- return !disposed;
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;
1271
+ };
1272
+ //#endregion
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);
1334
1290
  },
1335
- register(handle, record) {
1336
- assertIdle();
1337
- records.set(handle, record);
1338
- projectionHandles.add(handle);
1291
+ has: (key) => {
1292
+ trackKey(key);
1293
+ return source.has(key);
1339
1294
  },
1340
- unregisterSource(handle) {
1341
- const record = records.get(handle);
1342
- if (record) {
1343
- assertIdle();
1344
- if (record.consumers.size) throw new Error("Projection source is still used by a node.");
1345
- record.disposed = true;
1346
- pending.delete(record);
1347
- record.clear();
1348
- records.delete(handle);
1349
- }
1295
+ get size() {
1296
+ trackStructure();
1297
+ return source.size;
1350
1298
  },
1351
- releaseNode(handle) {
1352
- assertIdle();
1353
- const node = records.get(handle);
1354
- if (!node || !nodes.has(node)) throw new Error("Unknown projection node.");
1355
- if (node.consumers.size) throw new Error("Projection node is still used by another node.");
1356
- node.disposed = true;
1357
- node.sources.forEach((source) => source.consumers.delete(node));
1358
- node.release();
1359
- node.clear();
1360
- nodes.delete(node);
1361
- records.delete(handle);
1362
- pending.delete(node);
1363
- queued.delete(node);
1364
- emissions.delete(node);
1299
+ keys: () => {
1300
+ trackStructure();
1301
+ return source.keys();
1365
1302
  },
1366
- source(handle) {
1367
- assertActive();
1368
- const source = records.get(handle);
1369
- if (!source || source.disposed) throw new Error("Unknown, disposed, or foreign projection source.");
1370
- return source;
1303
+ values: () => {
1304
+ trackAll();
1305
+ return source.values();
1371
1306
  },
1372
- addNode(node) {
1373
- nodes.add(node);
1374
- node.sources.forEach((source) => source.consumers.add(node));
1307
+ entries: () => {
1308
+ trackAll();
1309
+ return source.entries();
1375
1310
  },
1376
- order: () => sequence++,
1377
- initialize(run) {
1378
- assertIdle();
1379
- phase = "compute";
1380
- lock(true);
1381
- try {
1382
- return run();
1383
- } finally {
1384
- lock(false);
1385
- phase = "idle";
1386
- }
1311
+ forEach: (callback, thisArg) => {
1312
+ trackAll();
1313
+ source.forEach((value, key) => callback.call(thisArg, value, key, view));
1387
1314
  },
1388
- batch,
1389
- batchContext: () => activeBatch,
1390
- dispose() {
1391
- if (disposed) return;
1392
- assertIdle();
1393
- disposed = true;
1394
- const failures = [];
1395
- cleanups.forEach((cleanup) => {
1396
- try {
1397
- cleanup();
1398
- } catch (error) {
1399
- failures.push(error);
1400
- }
1401
- });
1402
- cleanups.clear();
1403
- nodes.forEach((node) => {
1404
- node.disposed = true;
1405
- node.release();
1406
- node.consumers.clear();
1407
- });
1408
- records.forEach((source) => {
1409
- source.disposed = true;
1410
- source.consumers.clear();
1411
- source.clear();
1412
- });
1413
- nodes.clear();
1414
- records.clear();
1415
- pending.clear();
1416
- heap.length = 0;
1417
- completed.length = 0;
1418
- queued.clear();
1419
- emissions.clear();
1420
- guards.clear();
1421
- errors.length = 0;
1422
- reportingFailures = [];
1423
- if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
1315
+ [Symbol.iterator]: () => {
1316
+ trackAll();
1317
+ return source[Symbol.iterator]();
1424
1318
  }
1425
1319
  };
1320
+ return Object.freeze(view);
1426
1321
  };
1427
- const assertSynchronous = (value) => {
1428
- if (value != null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function") throw new TypeError("Projection callbacks must be synchronous.");
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;
1327
+ return false;
1429
1328
  };
1430
- const assertScope = (active) => {
1431
- if (!active()) throw new Error("Projection reader or writer is no longer active.");
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) => {
1336
+ let initialized = 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;
1347
+ const listeners = /* @__PURE__ */ new Set();
1348
+ const updateCollectionSnapshot = (current) => {
1349
+ const candidate = current ?? source.current();
1350
+ previousCollection = isMapLike(candidate) ? candidate : void 0;
1351
+ };
1352
+ const evaluate = () => {
1353
+ const tracker = {
1354
+ keys: /* @__PURE__ */ new Set(),
1355
+ all: false,
1356
+ structure: false
1357
+ };
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;
1367
+ }
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;
1377
+ }
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;
1401
+ }
1402
+ if (!selectionAffects(selection, change)) {
1403
+ sourceRevision = source.revision();
1404
+ updateCollectionSnapshot();
1405
+ return;
1406
+ }
1407
+ if (evaluate()) Array.from(listeners).forEach((listener) => listener());
1408
+ };
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;
1417
+ },
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
+ }
1430
+ });
1432
1431
  };
1433
1432
  //#endregion
1434
1433
  //#region core/src/projection/source.ts
1435
- const createSources = (scheduler) => {
1436
- const documents = /* @__PURE__ */ new Map();
1437
- const readables = /* @__PURE__ */ new Map();
1438
- const externalSources = /* @__PURE__ */ new WeakMap();
1439
- const isCollectionSource = (value) => value.kind === "collection";
1440
- const document = (runtime) => {
1441
- scheduler.assertIdle();
1442
- const state = require_notification.accessOf(runtime);
1443
- if (state.disposed) throw new Error("Document has been disposed.");
1444
- const existing = documents.get(state);
1445
- if (existing) return existing;
1446
- const bindings = [];
1447
- let batch;
1448
- const make = (input) => {
1449
- const collection = "collection" in input ? input.collection : void 0;
1450
- const targets = "targets" in input ? input.targets : [input.collection];
1451
- if (state.disposed) throw new Error("Document has been disposed.");
1452
- targets.forEach((value) => {
1453
- if (!require_notification.belongs(value, state.schema)) throw new Error("Projection target belongs to another schema.");
1454
- });
1455
- 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])));
1456
- if (old) return old.handle;
1457
- let reset = false;
1458
- let orderChanged = false;
1459
- const keys = /* @__PURE__ */ new Set();
1460
- let baseline;
1461
- let baselineIds = Object.freeze([]);
1462
- let nextBaseline;
1463
- let nextBaselineIds = Object.freeze([]);
1464
- const collectionNode = collection ? require_issue.nodeAt(state.schema, collection.address, state.document) : void 0;
1465
- const entryNode = collectionNode && (collectionNode.kind === "map" || collectionNode.kind === "table") ? collectionNode.value : void 0;
1466
- const sameEntry = (left, right) => entryNode ? require_scope.equalValue(entryNode, left, right) : Object.is(left, right);
1467
- const record = {
1468
- consumers: /* @__PURE__ */ new Set(),
1469
- disposed: false,
1470
- fault: void 0,
1471
- revision: runtime.revision,
1472
- reset: () => reset,
1473
- clear: () => {
1474
- if (collection && nextBaseline) {
1475
- baseline = nextBaseline;
1476
- baselineIds = nextBaselineIds;
1477
- }
1478
- reset = false;
1479
- orderChanged = false;
1480
- batch = void 0;
1481
- keys.clear();
1482
- nextBaseline = void 0;
1483
- nextBaselineIds = Object.freeze([]);
1484
- },
1485
- context: (active) => {
1486
- assertScope(active);
1487
- if (state.disposed) throw new Error("Document has been disposed.");
1488
- const context = {
1489
- state,
1490
- active
1491
- };
1492
- const rawRead = collection ? require_scope.collectionAccess(context, collection.address) : void 0;
1493
- const snapshots = collection ? /* @__PURE__ */ new Map() : void 0;
1494
- const read = collection ? Object.freeze({
1495
- get: (key) => {
1496
- assertScope(active);
1497
- if (!snapshots.has(key)) snapshots.set(key, require_scope.snapshot(rawRead.get(key)));
1498
- return snapshots.get(key);
1499
- },
1500
- has: (key) => {
1501
- assertScope(active);
1502
- return rawRead.has(key);
1503
- },
1504
- ids: () => {
1505
- assertScope(active);
1506
- return rawRead.ids();
1507
- }
1508
- }) : require_scope.createAccess(context);
1509
- if (!collection) return Object.freeze({
1510
- kind: "document",
1511
- read,
1512
- revision: runtime.revision(),
1513
- reset: record.reset(),
1514
- cause: batch?.cause
1515
- });
1516
- if (!baseline) {
1517
- const collectionRead = read;
1518
- baseline = /* @__PURE__ */ new Map();
1519
- baselineIds = Object.freeze(require_scope.collectionAccess(context, collection.address).ids().slice());
1520
- for (const key of baselineIds) {
1521
- const value = collectionRead.get(key);
1522
- baseline.set(key, value);
1523
- }
1524
- }
1525
- const changed = [...keys];
1526
- const added = [];
1527
- const updated = [];
1528
- const removed = [];
1529
- const next = new Map(baseline);
1530
- const collectionRead = read;
1531
- for (const key of changed) {
1532
- const beforePresent = baseline.has(key);
1533
- const afterPresent = collectionRead.has(key);
1534
- const afterValue = afterPresent ? collectionRead.get(key) : void 0;
1535
- if (!beforePresent && afterPresent) {
1536
- added.push({
1537
- key,
1538
- after: afterValue
1539
- });
1540
- next.set(key, afterValue);
1541
- } else if (beforePresent && !afterPresent) {
1542
- removed.push({
1543
- key,
1544
- before: baseline.get(key)
1545
- });
1546
- next.delete(key);
1547
- } else if (beforePresent && afterPresent) {
1548
- const beforeValue = baseline.get(key);
1549
- if (!sameEntry(beforeValue, afterValue)) updated.push({
1550
- key,
1551
- before: beforeValue,
1552
- after: afterValue
1553
- });
1554
- next.set(key, afterValue);
1555
- }
1556
- }
1557
- const ids = record.reset() || orderChanged ? Object.freeze([...collectionRead.ids()]) : baselineIds;
1558
- if (record.reset()) {
1559
- next.clear();
1560
- for (const key of ids) {
1561
- const value = collectionRead.get(key);
1562
- next.set(key, value);
1563
- }
1564
- }
1565
- let order;
1566
- if (orderChanged) {
1567
- const beforeKeys = new Set(baselineIds);
1568
- const afterKeys = new Set(ids);
1569
- const beforeCommon = baselineIds.filter((key) => afterKeys.has(key));
1570
- const afterCommon = ids.filter((key) => beforeKeys.has(key));
1571
- if (beforeCommon.some((key, index) => afterCommon[index] !== key)) order = {
1572
- before: Object.freeze([...baselineIds]),
1573
- after: ids
1574
- };
1575
- }
1576
- const change = record.reset() ? Object.freeze({ kind: "reset" }) : added.length || updated.length || removed.length || order ? Object.freeze({
1577
- kind: "incremental",
1578
- added: Object.freeze(added),
1579
- updated: Object.freeze(updated),
1580
- removed: Object.freeze(removed),
1581
- ...order ? { order: Object.freeze(order) } : {}
1582
- }) : void 0;
1583
- nextBaseline = next;
1584
- nextBaselineIds = ids;
1585
- return Object.freeze({
1586
- kind: "collection",
1587
- read,
1588
- change,
1589
- revision: runtime.revision(),
1590
- cause: batch?.cause
1591
- });
1592
- }
1593
- };
1594
- const handle = collection ? { kind: "collection" } : {
1595
- collection: (pick) => {
1596
- scheduler.assertIdle();
1597
- return make({ collection: compilePath(state.schema, "collection", pick) });
1598
- },
1599
- targets: (...selected) => {
1600
- scheduler.assertIdle();
1601
- if (!selected.length) throw new TypeError("Expected at least one target.");
1602
- return make({ targets: selected.map((pick) => compilePath(state.schema, "value", pick)) });
1603
- }
1604
- };
1605
- Object.freeze(handle);
1606
- scheduler.register(handle, record);
1607
- const receive = (commit) => {
1608
- reset ||= commit.impact.kind === "reset";
1609
- if (collection) {
1610
- const change = collectionImpact(commit.impact, collection);
1611
- if (change.kind === "reset") reset = true;
1612
- else {
1613
- change.added.forEach((key) => keys.add(key));
1614
- change.removed.forEach((key) => keys.add(key));
1615
- change.updated.forEach((key) => keys.add(key));
1616
- orderChanged ||= Boolean(change.added.size || change.removed.size || change.orderChanged);
1617
- }
1618
- }
1619
- };
1620
- bindings.push({
1621
- handle,
1622
- targets,
1623
- record,
1624
- receive
1625
- });
1626
- return handle;
1627
- };
1628
- const handle = make({ targets: [] });
1629
- const guard = (locked) => {
1630
- state.projectionLocks = (state.projectionLocks ?? 0) + (locked ? 1 : -1);
1631
- };
1632
- scheduler.guards.add(guard);
1633
- const unsubscribe = require_notification.attachProjection(runtime, {
1634
- capture: (commit) => {
1635
- for (const binding of bindings) if (!binding.targets.length || binding.targets.some((value) => affectsTarget(commit.impact, value))) {
1636
- binding.receive(commit);
1637
- batch = scheduler.batchContext();
1638
- scheduler.capture(binding.record);
1639
- }
1640
- },
1641
- settle: scheduler.settle,
1642
- flush: scheduler.flush,
1643
- dispose: () => {
1644
- bindings.forEach(({ record }) => {
1645
- record.fault = new ProjectionError("source", "document disposed", [runtime.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
1646
- scheduler.capture(record);
1647
- });
1648
- scheduler.run();
1649
- }
1650
- });
1651
- scheduler.cleanups.add(() => {
1652
- unsubscribe();
1653
- scheduler.guards.delete(guard);
1654
- });
1655
- documents.set(state, handle);
1656
- return handle;
1657
- };
1658
- const valueSource = (initial, equal) => {
1659
- let value = initial;
1660
- let revision = 0;
1661
- let cause;
1662
- let reset = false;
1663
- const handle = Object.freeze({});
1664
- const record = {
1665
- consumers: /* @__PURE__ */ new Set(),
1666
- disposed: false,
1667
- fault: void 0,
1668
- revision: () => revision,
1669
- reset: () => reset,
1670
- clear: () => {
1671
- cause = void 0;
1672
- reset = false;
1673
- },
1674
- context: (active) => {
1675
- assertScope(active);
1676
- return Object.freeze({
1677
- kind: "value",
1678
- value,
1679
- revision,
1680
- reset,
1681
- cause
1682
- });
1683
- }
1684
- };
1685
- scheduler.register(handle, record);
1686
- const accept = (next, metadata) => {
1687
- scheduler.assertIdle();
1688
- const recovering = record.fault !== void 0;
1689
- const eventful = metadata !== void 0 && (metadata.reset === true || metadata.revision !== void 0 && metadata.revision !== revision || metadata.cause !== void 0);
1690
- if (equal(value, next) && !recovering && !eventful) return;
1691
- record.fault = void 0;
1692
- value = next;
1693
- revision = metadata?.revision ?? revision + 1;
1694
- cause = metadata?.cause ?? scheduler.batchContext()?.cause ?? cause;
1695
- reset ||= metadata?.reset ?? false;
1696
- scheduler.capture(record);
1697
- };
1698
- return {
1699
- source: handle,
1700
- accept,
1701
- set(next) {
1702
- accept(next);
1703
- scheduler.run();
1704
- }
1705
- };
1706
- };
1707
- const collectionInput = (initial) => {
1708
- const values = new Map(initial);
1709
- const touched = /* @__PURE__ */ new Map();
1710
- let beforeIds;
1711
- let revision = 0;
1712
- let cause;
1713
- const handle = Object.freeze({ kind: "collection" });
1714
- const change = () => {
1715
- const added = [];
1716
- const updated = [];
1717
- const removed = [];
1718
- for (const [key, before] of touched) {
1719
- const afterPresent = values.has(key);
1720
- if (!before.present && afterPresent) added.push({
1721
- key,
1722
- after: values.get(key)
1723
- });
1724
- else if (before.present && !afterPresent) removed.push({
1725
- key,
1726
- before: before.value
1727
- });
1728
- else if (before.present && afterPresent && !Object.is(before.value, values.get(key))) updated.push({
1729
- key,
1730
- before: before.value,
1731
- after: values.get(key)
1732
- });
1733
- }
1734
- let order;
1735
- if (beforeIds) {
1736
- const afterIds = [...values.keys()];
1737
- const beforeKeys = new Set(beforeIds);
1738
- const afterKeys = new Set(afterIds);
1739
- const beforeCommon = beforeIds.filter((key) => afterKeys.has(key));
1740
- const afterCommon = afterIds.filter((key) => beforeKeys.has(key));
1741
- if (beforeCommon.some((key, index) => key !== afterCommon[index])) order = Object.freeze({
1742
- before: beforeIds,
1743
- after: Object.freeze(afterIds)
1744
- });
1745
- }
1746
- if (!added.length && !updated.length && !removed.length && !order) return void 0;
1747
- return Object.freeze({
1748
- kind: "incremental",
1749
- added: Object.freeze(added),
1750
- updated: Object.freeze(updated),
1751
- removed: Object.freeze(removed),
1752
- ...order ? { order } : {}
1753
- });
1754
- };
1755
- const record = {
1756
- consumers: /* @__PURE__ */ new Set(),
1757
- disposed: false,
1758
- fault: void 0,
1759
- revision: () => revision,
1760
- reset: () => false,
1761
- shouldSettle: () => change() !== void 0,
1762
- clear: () => {
1763
- touched.clear();
1764
- beforeIds = void 0;
1765
- cause = void 0;
1766
- },
1767
- context: (active) => {
1768
- assertScope(active);
1769
- const read = Object.freeze({
1770
- get: (key) => {
1771
- assertScope(active);
1772
- return values.get(key);
1773
- },
1774
- has: (key) => {
1775
- assertScope(active);
1776
- return values.has(key);
1777
- },
1778
- ids: () => {
1779
- assertScope(active);
1780
- return Object.freeze([...values.keys()]);
1781
- }
1782
- });
1783
- return Object.freeze({
1784
- kind: "collection",
1785
- read,
1786
- change: change(),
1787
- revision,
1788
- cause
1789
- });
1790
- }
1791
- };
1792
- scheduler.register(handle, record);
1793
- return {
1794
- source: handle,
1795
- update(run) {
1796
- scheduler.assertIdle();
1797
- const staged = /* @__PURE__ */ new Map();
1798
- let active = true;
1799
- const draft = Object.freeze({
1800
- get: (key) => {
1801
- assertScope(() => active);
1802
- const entry = staged.get(key);
1803
- return entry ? entry.present ? entry.value : void 0 : values.get(key);
1804
- },
1805
- has: (key) => {
1806
- assertScope(() => active);
1807
- const entry = staged.get(key);
1808
- return entry ? entry.present : values.has(key);
1809
- },
1810
- set: (key, value) => {
1811
- assertScope(() => active);
1812
- if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1813
- staged.set(key, {
1814
- present: true,
1815
- value
1816
- });
1817
- },
1818
- remove: (key) => {
1819
- assertScope(() => active);
1820
- if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1821
- staged.set(key, { present: false });
1822
- }
1823
- });
1824
- try {
1825
- assertSynchronous(run(draft));
1826
- } finally {
1827
- active = false;
1828
- }
1829
- let changed = false;
1830
- for (const [key, entry] of staged) {
1831
- const present = values.has(key);
1832
- if (entry.present ? present && Object.is(values.get(key), entry.value) : !present) continue;
1833
- if (!touched.has(key)) touched.set(key, {
1834
- present,
1835
- value: values.get(key)
1836
- });
1837
- if (!beforeIds && present !== entry.present) beforeIds = Object.freeze([...values.keys()]);
1838
- if (entry.present) values.set(key, entry.value);
1839
- else values.delete(key);
1840
- changed = true;
1841
- }
1842
- if (!changed) return;
1843
- revision++;
1844
- cause = scheduler.batchContext()?.cause ?? cause;
1845
- scheduler.capture(record);
1846
- scheduler.run();
1847
- }
1848
- };
1849
- };
1850
- const externalValueSource = (port, equal) => {
1851
- scheduler.assertIdle();
1852
- const old = externalSources.get(port);
1853
- if (old) return old;
1854
- const input = valueSource(port.current(), equal);
1855
- const fail = (cause) => {
1856
- const record = scheduler.source(input.source);
1857
- record.fault = new ProjectionError("source", "external source", [record.revision()], cause);
1858
- scheduler.capture(record);
1859
- };
1860
- let unsubscribe;
1861
- try {
1862
- unsubscribe = port.subscribe((event) => {
1863
- try {
1864
- input.accept(event.value, {
1865
- revision: event.revision,
1866
- reset: event.reset ?? false,
1867
- cause: event.cause
1868
- });
1869
- } catch (cause) {
1870
- fail(cause);
1871
- }
1872
- scheduler.run();
1873
- });
1874
- } catch (cause) {
1875
- unsubscribe?.();
1876
- scheduler.unregisterSource(input.source);
1877
- throw cause;
1878
- }
1879
- scheduler.cleanups.add(() => unsubscribe?.());
1880
- externalSources.set(port, input.source);
1881
- return input.source;
1882
- };
1883
- const externalCollectionSource = (port) => {
1884
- scheduler.assertIdle();
1885
- const old = externalSources.get(port);
1886
- if (old) return old;
1887
- let read = port.current();
1888
- let previous = read;
1889
- let revision = port.revision();
1890
- let change;
1891
- let reset = false;
1892
- let orderChanged = false;
1893
- let cause;
1894
- let transitionKeys = /* @__PURE__ */ new Set();
1895
- let pending = false;
1896
- const handle = Object.freeze({ kind: "collection" });
1897
- const record = {
1898
- consumers: /* @__PURE__ */ new Set(),
1899
- disposed: false,
1900
- fault: void 0,
1901
- revision: () => revision,
1902
- reset: () => reset,
1903
- clear: () => {
1904
- previous = read;
1905
- pending = false;
1906
- change = void 0;
1907
- reset = false;
1908
- orderChanged = false;
1909
- cause = void 0;
1910
- transitionKeys.clear();
1911
- },
1912
- context: (active) => {
1913
- assertScope(active);
1914
- const currentRead = Object.freeze({
1915
- get: (key) => {
1916
- assertScope(active);
1917
- return read.get(key);
1918
- },
1919
- has: (key) => {
1920
- assertScope(active);
1921
- return read.has(key);
1922
- },
1923
- ids: () => {
1924
- assertScope(active);
1925
- return read.ids();
1926
- }
1927
- });
1928
- return Object.freeze({
1929
- kind: "collection",
1930
- read: currentRead,
1931
- change,
1932
- revision,
1933
- cause
1934
- });
1935
- }
1936
- };
1937
- scheduler.register(handle, record);
1938
- const fail = (error) => {
1939
- record.fault = new ProjectionError("source", "external collection source", [revision], error);
1940
- scheduler.capture(record);
1941
- };
1942
- const deriveChange = () => {
1943
- if (reset) return Object.freeze({ kind: "reset" });
1944
- const added = [];
1945
- const updated = [];
1946
- const removed = [];
1947
- for (const key of transitionKeys) {
1948
- const beforePresent = previous.has(key);
1949
- const afterPresent = read.has(key);
1950
- const before = beforePresent ? previous.get(key) : void 0;
1951
- const after = afterPresent ? read.get(key) : void 0;
1952
- if (beforePresent === afterPresent && Object.is(before, after)) continue;
1953
- if (!beforePresent && afterPresent) added.push({
1954
- key,
1955
- after
1956
- });
1957
- else if (beforePresent && !afterPresent) removed.push({
1958
- key,
1959
- before
1960
- });
1961
- else if (beforePresent && afterPresent) updated.push({
1962
- key,
1963
- before,
1964
- after
1965
- });
1966
- }
1967
- let order;
1968
- if (orderChanged) {
1969
- const beforeIds = previous.ids();
1970
- const afterIds = read.ids();
1971
- const beforeKeys = new Set(beforeIds);
1972
- const afterKeys = new Set(afterIds);
1973
- const beforeCommon = beforeIds.filter((key) => afterKeys.has(key));
1974
- const afterCommon = afterIds.filter((key) => beforeKeys.has(key));
1975
- if (beforeCommon.some((key, index) => afterCommon[index] !== key)) order = Object.freeze({
1976
- before: Object.freeze([...beforeIds]),
1977
- after: Object.freeze([...afterIds])
1978
- });
1979
- }
1980
- if (!added.length && !removed.length && !updated.length && !order) return void 0;
1981
- return Object.freeze({
1982
- kind: "incremental",
1983
- added: Object.freeze(added),
1984
- updated: Object.freeze(updated),
1985
- removed: Object.freeze(removed),
1986
- ...order ? { order } : {}
1987
- });
1988
- };
1989
- let unsubscribe;
1990
- try {
1991
- unsubscribe = port.subscribe((event) => {
1992
- try {
1993
- scheduler.assertIdle();
1994
- if (!pending) previous = event.previous;
1995
- read = port.current();
1996
- revision = event.revision;
1997
- if (event.impact?.kind === "incremental") {
1998
- event.impact.added.forEach((key) => transitionKeys.add(key));
1999
- event.impact.removed.forEach((key) => transitionKeys.add(key));
2000
- event.impact.updated.forEach((key) => transitionKeys.add(key));
2001
- orderChanged ||= Boolean(event.impact.added.size || event.impact.removed.size || event.impact.orderChanged);
2002
- } else if (!event.impact) {
2003
- previous.ids().forEach((key) => transitionKeys.add(key));
2004
- read.ids().forEach((key) => transitionKeys.add(key));
2005
- orderChanged = true;
2006
- }
2007
- reset ||= event.impact?.kind === "reset";
2008
- cause = event.cause ?? scheduler.batchContext()?.cause ?? cause;
2009
- change = deriveChange();
2010
- record.fault = void 0;
2011
- scheduler.capture(record);
2012
- pending = true;
2013
- } catch (error) {
2014
- fail(error);
2015
- }
2016
- scheduler.run();
2017
- });
2018
- } catch (error) {
2019
- unsubscribe?.();
2020
- scheduler.unregisterSource(handle);
2021
- throw error;
2022
- }
2023
- scheduler.cleanups.add(() => unsubscribe?.());
2024
- externalSources.set(port, handle);
2025
- return handle;
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;
2026
1447
  };
2027
- return {
2028
- dispose: () => {
2029
- documents.clear();
2030
- readables.clear();
2031
- },
2032
- document,
2033
- fromSource: (source, options) => externalValueSource(source, options?.isEqual ?? Object.is),
2034
- fromCollectionSource: (source) => externalCollectionSource(source),
2035
- input: (initial, options) => {
2036
- const { source, set } = valueSource(initial, options?.isEqual ?? Object.is);
2037
- return Object.freeze({
2038
- source,
2039
- set
2040
- });
1448
+ const output = {
1449
+ kind: "value",
1450
+ get owner() {
1451
+ return boundary;
2041
1452
  },
2042
- collectionInput,
2043
- fromReadable: (readable, options) => {
2044
- scheduler.assertIdle();
2045
- if (projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
2046
- const equal = options?.isEqual ?? Object.is;
2047
- let bindings = readables.get(readable);
2048
- const old = bindings?.get(equal);
2049
- if (old) return old.source;
2050
- const input = valueSource(readable.current(), equal);
2051
- if (bindings) {
2052
- bindings.set(equal, {
2053
- source: input.source,
2054
- accept: (value) => input.accept(value)
2055
- });
2056
- return input.source;
2057
- }
2058
- bindings = new Map([[equal, {
2059
- source: input.source,
2060
- accept: (value) => input.accept(value)
2061
- }]]);
2062
- const members = bindings;
2063
- const receive = (settle = true) => {
2064
- if (!scheduler.active) return;
2065
- let value;
2066
- try {
2067
- value = readable.current();
2068
- } catch (cause) {
2069
- for (const member of members.values()) {
2070
- const record = scheduler.source(member.source);
2071
- record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
2072
- scheduler.capture(record);
2073
- }
2074
- if (settle) scheduler.run();
2075
- return;
2076
- }
2077
- for (const member of members.values()) try {
2078
- member.accept(value);
2079
- } catch (cause) {
2080
- const record = scheduler.source(member.source);
2081
- record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
2082
- scheduler.capture(record);
2083
- }
2084
- if (settle) scheduler.run();
2085
- };
2086
- let unsubscribe;
2087
- let detach;
2088
- try {
2089
- const owner = require_notification.documentReadableOwner(readable);
2090
- if (owner) {
2091
- document(owner);
2092
- detach = require_notification.attachProjection(owner, {
2093
- capture: () => receive(false),
2094
- settle: scheduler.settle,
2095
- flush: scheduler.flush,
2096
- dispose: () => {
2097
- for (const member of members.values()) {
2098
- const record = scheduler.source(member.source);
2099
- record.fault = new ProjectionError("source", "document readable disposed", [record.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
2100
- scheduler.capture(record);
2101
- }
2102
- scheduler.run();
2103
- }
2104
- });
2105
- }
2106
- unsubscribe = readable.subscribe(() => receive());
2107
- input.accept(readable.current());
2108
- } catch (error) {
2109
- try {
2110
- unsubscribe?.();
2111
- detach?.();
2112
- } finally {
2113
- scheduler.unregisterSource(input.source);
2114
- }
2115
- throw error;
2116
- }
2117
- scheduler.cleanups.add(unsubscribe);
2118
- if (detach) scheduler.cleanups.add(detach);
2119
- readables.set(readable, bindings);
2120
- scheduler.run();
2121
- return input.source;
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);
2122
1463
  },
2123
- assertCollection: (handle) => {
2124
- scheduler.source(handle);
2125
- if (!isCollectionSource(handle)) throw new Error("map requires a collection source.");
2126
- }
1464
+ emit: state.emit,
1465
+ clear: state.clear,
1466
+ release: state.release
2127
1467
  };
2128
- };
2129
- //#endregion
2130
- //#region core/src/projection/node.ts
2131
- const createNode = (scheduler, spec, behavior) => {
2132
- scheduler.assertIdle();
2133
- const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
2134
- const order = scheduler.order();
2135
- const node = {
2136
- ...behavior,
2137
- publish: () => {
2138
- if (node.fault) behavior.clear();
2139
- else behavior.publish();
2140
- },
2141
- order,
2142
- name: spec.name ? `${spec.name} (${order})` : `projection-${order}`,
2143
- sources: entries.map(([, source]) => source),
2144
- consumers: /* @__PURE__ */ new Set(),
2145
- disposed: false,
1468
+ boundary = {
1469
+ kind: "source",
1470
+ name,
1471
+ outputs: Object.freeze([output]),
2146
1472
  fault: void 0,
2147
- evaluate: (build) => {
1473
+ disposed: false,
1474
+ pendingCause: () => pendingCause,
1475
+ recovering: () => recovering,
1476
+ prepare: (cause) => {
1477
+ contextCause = cause;
2148
1478
  let active = true;
2149
1479
  try {
2150
- const scopeActive = () => active;
2151
- const inputs = Object.fromEntries(entries.map(([key, source]) => [key, source.context(scopeActive)]));
2152
- return behavior.evaluate(inputs, build, scopeActive);
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
+ };
2153
1487
  } finally {
2154
1488
  active = false;
2155
1489
  }
2156
- }
2157
- };
2158
- scheduler.initialize(() => {
2159
- const invalid = node.sources.find((source) => source.fault || source.disposed);
2160
- if (invalid) throw invalid.fault ?? new ProjectionDisposedError();
2161
- node.evaluate(true);
2162
- node.publish();
2163
- node.clear();
2164
- });
2165
- const check = () => {
2166
- if (node.disposed || !scheduler.active) throw new ProjectionDisposedError();
2167
- if (node.fault) throw node.fault;
2168
- };
2169
- return {
2170
- node,
2171
- check,
2172
- install(handle) {
2173
- scheduler.register(handle, node);
2174
- scheduler.addNode(node);
2175
- }
2176
- };
2177
- };
2178
- //#endregion
2179
- //#region core/src/projection/value.ts
2180
- const createValue = (scheduler, spec, options) => {
2181
- let instance;
2182
- let value;
2183
- let next;
2184
- let initialized = false;
2185
- let revision = 0;
2186
- let changed = false;
2187
- let reset = false;
2188
- let cause;
2189
- const listeners = /* @__PURE__ */ new Set();
2190
- const owner = createNode(scheduler, spec, {
2191
- evaluate: (sources, build) => {
2192
- cause = Object.values(sources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
2193
- let candidate;
2194
- if (build || !instance) {
2195
- require_issue.profile.materialized.rebuilt();
2196
- instance = void 0;
2197
- const built = spec.build(sources);
2198
- assertSynchronous(built);
2199
- instance = built;
2200
- candidate = built.value;
2201
- } else {
2202
- require_issue.profile.materialized.updated();
2203
- const result = instance.update(sources);
2204
- assertSynchronous(result);
2205
- if (result.kind === "rebuild") {
2206
- instance = void 0;
2207
- const built = spec.build(sources);
2208
- assertSynchronous(built);
2209
- instance = built;
2210
- candidate = built.value;
2211
- build = true;
2212
- } else candidate = result.kind === "changed" ? result.value : value;
2213
- }
2214
- changed = !initialized || !(options?.isEqual ?? Object.is)(value, candidate);
2215
- next = changed ? candidate : value;
2216
- reset = build;
2217
- return changed;
2218
- },
2219
- context: (active) => {
2220
- assertScope(active);
2221
- return Object.freeze({
2222
- kind: "value",
2223
- value: next,
2224
- revision: revision + (changed ? 1 : 0),
2225
- reset,
2226
- cause
2227
- });
2228
- },
2229
- revision: () => revision,
2230
- reset: () => reset,
2231
- publish: () => {
2232
- if (changed && initialized) revision++;
2233
- value = next;
2234
- initialized = true;
2235
- },
2236
- emit: (call) => {
2237
- require_issue.profile.materialized.notification();
2238
- Array.from(listeners).forEach((listener) => call(listener));
2239
1490
  },
1491
+ publish: state.publish,
2240
1492
  clear: () => {
2241
- next = value;
2242
- changed = false;
2243
- reset = false;
2244
- cause = void 0;
1493
+ state.clear();
1494
+ pendingReset = false;
1495
+ pendingCause = void 0;
1496
+ contextCause = void 0;
1497
+ recovering = false;
2245
1498
  },
2246
1499
  release: () => {
2247
- listeners.clear();
2248
- instance = void 0;
2249
- value = next = void 0;
1500
+ cleanup?.();
1501
+ cleanup = void 0;
1502
+ state.release();
1503
+ output.consumers.clear();
2250
1504
  }
2251
- });
2252
- const handle = Object.freeze({
2253
- kind: "value",
2254
- current: () => {
2255
- owner.check();
2256
- return value;
2257
- },
2258
- revision: () => {
2259
- owner.check();
2260
- return revision;
2261
- },
2262
- subscribe: (listener) => {
2263
- owner.check();
2264
- listeners.add(listener);
2265
- return () => {
2266
- listeners.delete(listener);
2267
- };
2268
- }
2269
- });
2270
- owner.install(handle);
2271
- return handle;
2272
- };
2273
- //#endregion
2274
- //#region core/src/projection/collection.ts
2275
- const height = (node) => node?.height ?? 0;
2276
- const makeTreeNode = (key, value, left, right) => Object.freeze({
2277
- key,
2278
- value,
2279
- left,
2280
- right,
2281
- height: Math.max(height(left), height(right)) + 1
2282
- });
2283
- const rotateLeft = (node) => {
2284
- const right = node.right;
2285
- return makeTreeNode(right.key, right.value, makeTreeNode(node.key, node.value, node.left, right.left), right.right);
2286
- };
2287
- const rotateRight = (node) => {
2288
- const left = node.left;
2289
- return makeTreeNode(left.key, left.value, left.left, makeTreeNode(node.key, node.value, left.right, node.right));
2290
- };
2291
- const rebalance = (node) => {
2292
- const balance = height(node.left) - height(node.right);
2293
- if (balance > 1) {
2294
- if (height(node.left.left) < height(node.left.right)) return rotateRight(makeTreeNode(node.key, node.value, rotateLeft(node.left), node.right));
2295
- return rotateRight(node);
2296
- }
2297
- if (balance < -1) {
2298
- if (height(node.right.right) < height(node.right.left)) return rotateLeft(makeTreeNode(node.key, node.value, node.left, rotateRight(node.right)));
2299
- return rotateLeft(node);
2300
- }
2301
- return node;
2302
- };
2303
- const treeSet = (node, key, value) => {
2304
- if (!node) return makeTreeNode(key, value);
2305
- if (key === node.key) return makeTreeNode(key, value, node.left, node.right);
2306
- if (key < node.key) return rebalance(makeTreeNode(node.key, node.value, treeSet(node.left, key, value), node.right));
2307
- return rebalance(makeTreeNode(node.key, node.value, node.left, treeSet(node.right, key, value)));
2308
- };
2309
- const treeMin = (node) => node.left ? treeMin(node.left) : node;
2310
- const treeRemove = (node, key) => {
2311
- if (!node) return void 0;
2312
- if (key < node.key) return rebalance(makeTreeNode(node.key, node.value, treeRemove(node.left, key), node.right));
2313
- if (key > node.key) return rebalance(makeTreeNode(node.key, node.value, node.left, treeRemove(node.right, key)));
2314
- if (!node.left) return node.right;
2315
- if (!node.right) return node.left;
2316
- const next = treeMin(node.right);
2317
- return rebalance(makeTreeNode(next.key, next.value, node.left, treeRemove(node.right, next.key)));
2318
- };
2319
- const treeGet = (node, key) => {
2320
- let current = node;
2321
- while (current) {
2322
- if (key === current.key) return current.value;
2323
- current = key < current.key ? current.left : current.right;
2324
- }
2325
- };
2326
- const treeHas = (node, key) => {
2327
- let current = node;
2328
- while (current) {
2329
- if (key === current.key) return true;
2330
- current = key < current.key ? current.left : current.right;
2331
- }
2332
- return false;
2333
- };
2334
- const treeFromSorted = (entries, start = 0, end = entries.length) => {
2335
- if (start >= end) return void 0;
2336
- const middle = start + end >> 1;
2337
- const [key, value] = entries[middle];
2338
- return makeTreeNode(key, value, treeFromSorted(entries, start, middle), treeFromSorted(entries, middle + 1, end));
2339
- };
2340
- const createCollection = (scheduler, spec) => {
2341
- const values = /* @__PURE__ */ new Map();
2342
- let ids = Object.freeze([]);
2343
- let staged = /* @__PURE__ */ new Map();
2344
- let nextIds = ids;
2345
- let change;
2346
- let instance;
2347
- let initialized = false;
2348
- let reset = false;
2349
- let revision = 0;
2350
- let publishedRoot;
2351
- let cause;
2352
- const listeners = /* @__PURE__ */ new Set();
2353
- const changedKeys = /* @__PURE__ */ new Set();
2354
- let orderChanged = false;
2355
- const readPublished = (root, publishedIds) => Object.freeze({
2356
- get: (key) => {
2357
- owner.check();
2358
- return treeGet(root, key);
2359
- },
2360
- has: (key) => {
2361
- owner.check();
2362
- return treeHas(root, key);
2363
- },
2364
- ids: () => {
2365
- owner.check();
2366
- return publishedIds;
2367
- }
2368
- });
2369
- const hasNext = (key) => staged.has(key) ? staged.get(key).present : values.has(key);
2370
- const getNext = (key) => {
2371
- const entry = staged.get(key);
2372
- return entry ? entry.present ? entry.value : void 0 : values.get(key);
2373
1505
  };
2374
- const owner = createNode(scheduler, spec, {
2375
- evaluate: (sources, build, active) => {
2376
- cause = Object.values(sources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
2377
- staged = /* @__PURE__ */ new Map();
2378
- nextIds = ids;
2379
- change = void 0;
2380
- changedKeys.clear();
2381
- let explicitOrder;
2382
- let cleared = false;
2383
- const nextHas = (key) => staged.has(key) ? staged.get(key).present : !cleared && values.has(key);
2384
- const nextGet = (key) => {
2385
- const entry = staged.get(key);
2386
- return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : values.get(key);
2387
- };
2388
- const deriveIds = () => {
2389
- const result = cleared ? [] : ids.filter(nextHas);
2390
- for (const [key, entry] of staged) if (entry.present && (cleared || !values.has(key))) result.push(key);
2391
- return result;
2392
- };
2393
- const read = (next) => ({
2394
- get: (key) => {
2395
- assertScope(active);
2396
- return next ? nextGet(key) : values.get(key);
2397
- },
2398
- has: (key) => {
2399
- assertScope(active);
2400
- return next ? nextHas(key) : values.has(key);
2401
- },
2402
- ids: () => {
2403
- assertScope(active);
2404
- return next ? Object.freeze(explicitOrder ? explicitOrder.slice() : deriveIds()) : ids;
2405
- }
2406
- });
2407
- const input = {
2408
- sources,
2409
- previous: read(false),
2410
- next: read(true),
2411
- output: {
2412
- set: (key, value) => {
2413
- assertScope(active);
2414
- if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
2415
- staged.set(key, {
2416
- present: true,
2417
- value
2418
- });
2419
- },
2420
- remove: (key) => {
2421
- assertScope(active);
2422
- staged.set(key, { present: false });
2423
- },
2424
- order: (order) => {
2425
- assertScope(active);
2426
- explicitOrder = order.slice();
2427
- }
2428
- }
2429
- };
2430
- if (build || !instance) {
2431
- require_issue.profile.materialized.rebuilt();
2432
- instance = void 0;
2433
- cleared = true;
2434
- const built = spec.build(input);
2435
- assertSynchronous(built);
2436
- instance = built;
2437
- } else {
2438
- require_issue.profile.materialized.updated();
2439
- const result = instance.update(input);
2440
- assertSynchronous(result);
2441
- if (result?.kind === "rebuild") {
2442
- staged.clear();
2443
- cleared = true;
2444
- explicitOrder = void 0;
2445
- instance = void 0;
2446
- const built = spec.build(input);
2447
- assertSynchronous(built);
2448
- instance = built;
2449
- build = true;
2450
- }
2451
- }
2452
- if (cleared) {
2453
- for (const key of values.keys()) if (!staged.has(key)) staged.set(key, { present: false });
2454
- }
2455
- const added = /* @__PURE__ */ new Set();
2456
- const removed = /* @__PURE__ */ new Set();
2457
- const updated = /* @__PURE__ */ new Set();
2458
- require_issue.profile.projection("touchedKeys", staged.size);
2459
- for (const [key, entry] of staged) {
2460
- const existed = values.has(key);
2461
- if (entry.present) if (!existed) added.add(key);
2462
- else if (!(spec.isEqual ?? Object.is)(values.get(key), entry.value)) updated.add(key);
2463
- else staged.delete(key);
2464
- else if (existed) removed.add(key);
2465
- else staged.delete(key);
2466
- }
2467
- if (explicitOrder || added.size || removed.size) {
2468
- const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
2469
- 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.");
2470
- nextIds = ids.length === order.length && ids.every((key, i) => key === order[i]) ? ids : Object.freeze(order.slice());
2471
- }
2472
- orderChanged = nextIds !== ids;
2473
- [
2474
- ...added,
2475
- ...removed,
2476
- ...updated
2477
- ].forEach((key) => changedKeys.add(key));
2478
- require_issue.profile.projection("changedKeys", changedKeys.size);
2479
- const addedEntries = [];
2480
- const updatedEntries = [];
2481
- const removedEntries = [];
2482
- for (const key of changedKeys) {
2483
- const beforePresent = values.has(key);
2484
- const afterPresent = hasNext(key);
2485
- if (beforePresent === afterPresent) {
2486
- if (!beforePresent) continue;
2487
- if ((spec.isEqual ?? Object.is)(values.get(key), getNext(key))) continue;
2488
- }
2489
- if (!beforePresent) addedEntries.push({
2490
- key,
2491
- after: getNext(key)
2492
- });
2493
- else if (!afterPresent) removedEntries.push({
2494
- key,
2495
- before: values.get(key)
2496
- });
2497
- else updatedEntries.push({
2498
- key,
2499
- before: values.get(key),
2500
- after: getNext(key)
2501
- });
2502
- }
2503
- if (build) change = Object.freeze({ kind: "reset" });
2504
- else if (addedEntries.length || updatedEntries.length || removedEntries.length || orderChanged) {
2505
- let commonOrderChanged = false;
2506
- if (orderChanged) {
2507
- const before = ids.filter((key) => !removed.has(key));
2508
- const after = nextIds.filter((key) => !added.has(key));
2509
- commonOrderChanged = before.some((key, i) => after[i] !== key);
2510
- }
2511
- change = Object.freeze({
2512
- kind: "incremental",
2513
- added: Object.freeze(addedEntries),
2514
- updated: Object.freeze(updatedEntries),
2515
- removed: Object.freeze(removedEntries),
2516
- ...commonOrderChanged ? { order: Object.freeze({
2517
- before: Object.freeze([...ids]),
2518
- after: Object.freeze([...nextIds])
2519
- }) } : {}
2520
- });
2521
- }
2522
- reset = build;
2523
- return change !== void 0;
2524
- },
2525
- context: (active) => {
2526
- const input = {
2527
- kind: "collection",
2528
- read: {
2529
- get: (key) => {
2530
- assertScope(active);
2531
- return getNext(key);
2532
- },
2533
- has: (key) => {
2534
- assertScope(active);
2535
- return hasNext(key);
2536
- },
2537
- ids: () => {
2538
- assertScope(active);
2539
- return nextIds;
2540
- }
2541
- },
2542
- change,
2543
- revision: revision + (change ? 1 : 0),
2544
- cause
2545
- };
2546
- return Object.freeze(input);
2547
- },
2548
- revision: () => revision,
2549
- reset: () => reset,
2550
- publish: () => {
2551
- if (change) if (change.kind === "reset") {
2552
- const entries = [];
2553
- for (const [key, entry] of staged) if (entry.present) entries.push([key, entry.value]);
2554
- entries.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
2555
- publishedRoot = treeFromSorted(entries);
2556
- } else for (const [key, entry] of staged) publishedRoot = entry.present ? treeSet(publishedRoot, key, entry.value) : treeRemove(publishedRoot, key);
2557
- for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
2558
- else values.delete(key);
2559
- if (change) {
2560
- if (initialized) revision++;
2561
- }
2562
- ids = nextIds;
2563
- initialized = true;
2564
- },
2565
- emit: (call) => {
2566
- require_issue.profile.materialized.notification();
2567
- const resetEvent = Object.freeze({ kind: "reset" });
2568
- Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
2569
- },
2570
- clear: () => {
2571
- staged.clear();
2572
- nextIds = ids;
2573
- change = void 0;
2574
- reset = false;
2575
- changedKeys.clear();
2576
- cause = void 0;
2577
- orderChanged = false;
2578
- },
2579
- release: () => {
2580
- values.clear();
2581
- staged.clear();
2582
- publishedRoot = void 0;
2583
- listeners.clear();
2584
- ids = nextIds = Object.freeze([]);
2585
- instance = void 0;
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;
2586
1515
  }
2587
1516
  });
2588
- const handle = {
2589
- kind: "collection",
2590
- current: () => {
2591
- owner.check();
2592
- return readPublished(publishedRoot, ids);
2593
- },
2594
- revision: () => {
2595
- owner.check();
2596
- return revision;
2597
- },
2598
- subscribe: (listener) => {
2599
- owner.check();
2600
- listeners.add(listener);
2601
- return () => {
2602
- listeners.delete(listener);
2603
- };
2604
- }
2605
- };
2606
- owner.install(handle);
2607
- return Object.freeze(handle);
2608
- };
2609
- //#endregion
2610
- //#region core/src/projection/graph.ts
2611
- const createProjectionGraph = (options) => {
2612
- const scheduler = createScheduler(options.onError);
2613
- const sources = createSources(scheduler);
2614
- const value = (spec, options) => createValue(scheduler, spec, options);
2615
- const map = (source, mapper, options) => {
2616
- sources.assertCollection(source);
2617
- const calculate = (id, entry) => {
2618
- const value = mapper(id, entry);
2619
- assertSynchronous(value);
2620
- return value;
2621
- };
2622
- return createCollection(scheduler, {
2623
- sources: { source },
2624
- isEqual: options?.isEqual,
2625
- build: ({ sources, output }) => {
2626
- const read = sources.source.read;
2627
- const ids = read.ids();
2628
- require_issue.profile.collectionView.idsScanned(ids.length);
2629
- for (const id of ids) {
2630
- require_issue.profile.collectionView.mapped();
2631
- output.set(id, calculate(id, read.get(id)));
2632
- }
2633
- output.order(ids);
2634
- return { update: ({ sources, output }) => {
2635
- const input = sources.source;
2636
- const read = input.read;
2637
- if (input.change?.kind === "reset") return { kind: "rebuild" };
2638
- const change = input.change;
2639
- if (!change) return;
2640
- for (const entry of change.added) {
2641
- require_issue.profile.collectionView.mapped();
2642
- output.set(entry.key, calculate(entry.key, entry.after));
2643
- }
2644
- for (const entry of change.updated) {
2645
- require_issue.profile.collectionView.mapped();
2646
- output.set(entry.key, calculate(entry.key, entry.after));
2647
- }
2648
- for (const entry of change.removed) output.remove(entry.key);
2649
- if (change.added.length || change.removed.length || change.order) {
2650
- const ids = read.ids();
2651
- require_issue.profile.collectionView.idsScanned(ids.length);
2652
- output.order(ids);
2653
- }
2654
- } };
2655
- }
2656
- });
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);
2657
1524
  };
2658
- const graph = {
2659
- document: sources.document,
2660
- fromSource: (source, options) => sources.fromSource(source, options),
2661
- fromCollectionSource: (source) => sources.fromCollectionSource(source),
2662
- input: sources.input,
2663
- collectionInput: sources.collectionInput,
2664
- fromReadable: sources.fromReadable,
2665
- value,
2666
- collection: (spec) => createCollection(scheduler, spec),
2667
- map,
2668
- batch: scheduler.batch,
2669
- assertIdle: scheduler.assertIdle,
2670
- releaseNode: scheduler.releaseNode,
2671
- releaseInput: scheduler.unregisterSource,
2672
- dispose: () => {
2673
- try {
2674
- scheduler.dispose();
2675
- } finally {
2676
- if (!scheduler.active) sources.dispose();
2677
- }
2678
- }
1525
+ const fail = (cause) => {
1526
+ boundary.fault = sourceError(boundary, cause);
1527
+ recovering = false;
1528
+ scheduler.capture(boundary);
2679
1529
  };
2680
- return Object.freeze(graph);
2681
- };
2682
- //#endregion
2683
- //#region core/src/projection/runtime.ts
2684
- let activeTracker;
2685
- const trackKey = (key) => {
2686
- activeTracker?.keys.add(key);
2687
- };
2688
- const trackStructure = () => {
2689
- if (!activeTracker) return;
2690
- activeTracker.structure = true;
2691
- };
2692
- const trackAll = () => {
2693
- if (!activeTracker) return;
2694
- activeTracker.all = true;
2695
- };
2696
- const mapView = (read) => {
2697
- const keys = read.ids();
2698
- const source = /* @__PURE__ */ new Map();
2699
- for (const key of keys) source.set(key, read.get(key));
2700
- const view = {
2701
- get: (key) => {
2702
- trackKey(key);
2703
- return source.get(key);
2704
- },
2705
- has: (key) => {
2706
- trackKey(key);
2707
- return source.has(key);
2708
- },
2709
- get size() {
2710
- trackStructure();
2711
- return source.size;
2712
- },
2713
- keys: () => {
2714
- trackStructure();
2715
- return source.keys();
2716
- },
2717
- values: () => {
2718
- trackAll();
2719
- return source.values();
2720
- },
2721
- entries: () => {
2722
- trackAll();
2723
- return source.entries();
2724
- },
2725
- forEach: (callback, thisArg) => {
2726
- trackAll();
2727
- source.forEach((value, key) => callback.call(thisArg, value, key, view));
1530
+ return {
1531
+ producer: boundary,
1532
+ output,
1533
+ setLatest: (value) => {
1534
+ latest = value;
2728
1535
  },
2729
- [Symbol.iterator]: () => {
2730
- trackAll();
2731
- return source[Symbol.iterator]();
1536
+ mark,
1537
+ fail,
1538
+ detach: (next) => {
1539
+ cleanup = next;
2732
1540
  }
2733
1541
  };
2734
- return Object.freeze(view);
2735
1542
  };
2736
- const lazyMapView = (read) => {
2737
- const ids = read.ids();
2738
- const entries = function* () {
2739
- for (const key of ids) yield [key, read.get(key)];
2740
- };
2741
- const values = function* () {
2742
- for (const key of ids) yield read.get(key);
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;
2743
1557
  };
2744
- const view = {
2745
- get: (key) => {
2746
- trackKey(key);
2747
- return read.get(key);
2748
- },
2749
- has: (key) => {
2750
- trackKey(key);
2751
- return read.has(key);
2752
- },
2753
- get size() {
2754
- trackStructure();
2755
- return ids.length;
2756
- },
2757
- keys: () => {
2758
- trackStructure();
2759
- return ids[Symbol.iterator]();
1558
+ const output = {
1559
+ kind: "collection",
1560
+ get owner() {
1561
+ return boundary;
2760
1562
  },
2761
- values: () => {
2762
- trackAll();
2763
- return values();
1563
+ consumers: /* @__PURE__ */ new Set(),
1564
+ context: (active) => state.context(active, contextCause),
1565
+ current: () => state.current(check),
1566
+ revision: state.revision,
1567
+ reset: state.reset,
1568
+ subscribe: (listener) => {
1569
+ check();
1570
+ const wrapped = (change) => listener(change);
1571
+ state.subscribe(wrapped);
1572
+ return () => state.unsubscribe(wrapped);
2764
1573
  },
2765
- entries: () => {
2766
- trackAll();
2767
- return entries();
1574
+ emit: state.emit,
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
+ }
2768
1604
  },
2769
- forEach: (callback, thisArg) => {
2770
- trackAll();
2771
- for (const key of ids) callback.call(thisArg, read.get(key), key, view);
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();
2772
1615
  },
2773
- [Symbol.iterator]: () => {
2774
- trackAll();
2775
- return entries();
1616
+ release: () => {
1617
+ cleanup?.();
1618
+ cleanup = void 0;
1619
+ state.release();
1620
+ output.consumers.clear();
2776
1621
  }
2777
1622
  };
2778
- return Object.freeze(view);
2779
- };
2780
- const isMapLike = (value) => value instanceof Map || value !== null && typeof value === "object" && typeof value.get === "function" && typeof value.keys === "function" && typeof value.entries === "function";
2781
- const mapRead = (value) => ({
2782
- get: (key) => value.get(key),
2783
- has: (key) => value.has(key),
2784
- ids: () => Object.freeze([...value.keys()])
2785
- });
2786
- const mapChange = (previous, current) => {
2787
- const added = [];
2788
- const removed = [];
2789
- const updated = [];
2790
- for (const key of previous.keys()) if (!current.has(key)) removed.push({
2791
- key,
2792
- before: previous.get(key)
2793
- });
2794
- else if (!Object.is(previous.get(key), current.get(key))) updated.push({
2795
- key,
2796
- before: previous.get(key),
2797
- after: current.get(key)
2798
- });
2799
- for (const key of current.keys()) if (!previous.has(key)) added.push({
2800
- key,
2801
- after: current.get(key)
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;
1634
+ }
2802
1635
  });
2803
- const before = [...previous.keys()].filter((key) => current.has(key));
2804
- const after = [...current.keys()].filter((key) => previous.has(key));
2805
- const orderChanged = before.length !== after.length || before.some((key, index) => key !== after[index]);
2806
- if (!added.length && !removed.length && !updated.length && !orderChanged) return void 0;
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
+ };
2807
1652
  return {
2808
- kind: "incremental",
2809
- added: Object.freeze(added),
2810
- removed: Object.freeze(removed),
2811
- updated: Object.freeze(updated),
2812
- ...orderChanged ? { order: Object.freeze({
2813
- before: Object.freeze([...previous.keys()]),
2814
- after: Object.freeze([...current.keys()])
2815
- }) } : {}
1653
+ producer: boundary,
1654
+ output,
1655
+ mark,
1656
+ fail,
1657
+ detach: (next) => {
1658
+ cleanup = next;
1659
+ }
2816
1660
  };
2817
1661
  };
2818
- const sameSelection = (left, right) => {
2819
- if (left.all !== right.all || left.structure !== right.structure || left.keys.size !== right.keys.size) return false;
2820
- for (const key of left.keys) if (!right.keys.has(key)) return false;
2821
- return true;
2822
- };
2823
- const selectionAffects = (selection, change) => {
2824
- if (!change || change.kind === "reset") return true;
2825
- if (selection.all && (change.added.length || change.removed.length || change.updated.length || change.order)) return true;
2826
- if (selection.structure && (change.added.length || change.removed.length || change.order)) return true;
2827
- 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;
2828
- return false;
2829
- };
2830
- const currentValue = (event) => {
2831
- if (!event || typeof event !== "object") return event;
2832
- if ("kind" in event && event.kind === "value") return event.value;
2833
- if ("kind" in event && event.kind === "document") return require_scope.snapshot(event.read);
2834
- if ("kind" in event && event.kind === "collection") return mapView(event.read);
2835
- return event;
2836
- };
2837
- const sourceOf = (instance) => instance;
2838
- const createProjectionRuntime = (options) => {
2839
- const runtime = createProjectionGraph({ onError: options?.onError ?? (() => void 0) });
2840
- const materialized = /* @__PURE__ */ new WeakMap();
2841
- let disposed = false;
2842
- const scopes = /* @__PURE__ */ new Set();
2843
- const materialize = (projection, requester) => {
2844
- const owner = require_advanced.ownerOf(projection);
2845
- if (owner) {
2846
- if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
2847
- if (!requester.active) throw new ProjectionDisposedError();
2848
- }
2849
- const old = materialized.get(projection);
2850
- if (old) return old.node;
2851
- const definition = require_advanced.definitionOf(projection);
2852
- let instance;
2853
- let write;
2854
- switch (definition.kind) {
2855
- case "input": {
2856
- const input = runtime.input(definition.initial, { isEqual: definition.isEqual });
2857
- write = {
2858
- kind: "value-input",
2859
- source: input.source,
2860
- set: input.set
2861
- };
2862
- instance = runtime.value({
2863
- sources: { input: input.source },
2864
- build: (values) => ({
2865
- value: values.input.value,
2866
- update: (next) => ({
2867
- kind: "changed",
2868
- value: next.input.value
2869
- })
2870
- })
2871
- });
2872
- break;
2873
- }
2874
- case "collection-input": {
2875
- const source = runtime.collectionInput(definition.initial);
2876
- write = {
2877
- kind: "collection-input",
2878
- source: source.source,
2879
- update: source.update
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);
1675
+ };
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();
2880
1689
  };
2881
- instance = runtime.map(source.source, (_key, value) => value);
2882
- break;
1690
+ },
1691
+ close() {
1692
+ if (closed) return;
1693
+ closed = true;
1694
+ detach();
1695
+ bindings.clear();
1696
+ scheduler.guards.delete(guard);
1697
+ documents.delete(state);
2883
1698
  }
2884
- case "readable": {
2885
- const source = runtime.fromReadable(definition.readable, { isEqual: definition.isEqual });
2886
- instance = runtime.value({
2887
- sources: { source },
2888
- build: (values) => ({
2889
- value: values.source.value,
2890
- update: (next) => ({
2891
- kind: "changed",
2892
- value: next.source.value
2893
- })
2894
- })
2895
- });
2896
- break;
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();
2897
1709
  }
2898
- case "source-value":
2899
- {
2900
- const source = runtime.fromSource(definition.source);
2901
- instance = runtime.value({
2902
- sources: { source },
2903
- build: (values) => ({
2904
- value: values.source.value,
2905
- update: (next) => ({
2906
- kind: "changed",
2907
- value: next.source.value
2908
- })
2909
- })
2910
- });
1710
+ });
1711
+ documents.set(state, connection);
1712
+ return connection;
1713
+ };
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();
2911
1727
  }
2912
- break;
2913
- case "source-collection": {
2914
- const source = runtime.fromCollectionSource(definition.source);
2915
- instance = runtime.map(source, (_key, value) => value);
2916
- break;
2917
1728
  }
2918
- case "document": {
2919
- const document = runtime.document(definition.document);
2920
- if (!definition.selector) {
2921
- instance = runtime.value({
2922
- sources: { document },
2923
- build: (values) => ({
2924
- value: require_scope.snapshot(values.document.read),
2925
- update: (next) => ({
2926
- kind: "changed",
2927
- value: require_scope.snapshot(next.document.read)
2928
- })
2929
- })
1729
+ };
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
2930
1804
  });
2931
- break;
1805
+ scheduler.run();
2932
1806
  }
2933
- const state = require_notification.accessOf(definition.document);
1807
+ }
1808
+ };
1809
+ };
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) => {
2934
1829
  try {
2935
- const collection = document.collection(definition.selector);
2936
- instance = runtime.map(collection, (_id, entry) => entry);
2937
- break;
2938
- } catch {
2939
- const selected = document.targets(definition.selector);
2940
- const address = compilePath(state.schema, "value", definition.selector).address;
2941
- instance = runtime.value({
2942
- sources: { selected },
2943
- build: () => ({
2944
- value: require_issue.read(require_scope.snapshot(state.document), address, state.schema),
2945
- update: () => ({
2946
- kind: "changed",
2947
- value: require_issue.read(require_scope.snapshot(state.document), address, state.schema)
2948
- })
2949
- })
2950
- });
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();
2951
1865
  }
2952
- break;
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);
2953
1908
  }
2954
- case "derive": {
2955
- const sources = Object.fromEntries(definition.dependencies.map((dependency, index) => [`d${index}`, sourceOf(materialize(dependency, owner))]));
2956
- instance = runtime.value({
2957
- sources,
2958
- build: (values) => ({
2959
- value: definition.compute(...Object.values(values).map(currentValue)),
2960
- update: (next) => ({
2961
- kind: "changed",
2962
- value: definition.compute(...Object.values(next).map(currentValue))
2963
- })
2964
- })
2965
- }, { isEqual: definition.isEqual });
2966
- break;
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
+ }
1943
+ });
1944
+ unsubscribe = readable.subscribe(() => receive(true));
1945
+ connection = created;
1946
+ readables.set(readable, connection);
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;
1954
+ };
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);
2967
1964
  }
2968
- case "incremental-value": {
2969
- const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, owner))]));
2970
- instance = runtime.value({
2971
- sources,
2972
- build: definition.build
2973
- }, { isEqual: definition.isEqual });
2974
- break;
1965
+ };
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);
1978
+ }
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));
1989
+ }
1990
+ scheduler.run();
1991
+ });
1992
+ } catch (cause) {
1993
+ created.close();
1994
+ throw cause;
2975
1995
  }
2976
- case "incremental-collection": {
2977
- const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, owner))]));
2978
- instance = runtime.collection({
2979
- sources,
2980
- build: definition.build,
2981
- isEqual: definition.isEqual,
2982
- name: definition.name
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();
2003
+ });
2004
+ return boundary;
2005
+ };
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
+ };
2031
+ try {
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();
2983
2059
  });
2984
- break;
2060
+ } catch (cause) {
2061
+ created.close();
2062
+ throw cause;
2985
2063
  }
2064
+ connection = created;
2065
+ externalCollections.set(source, connection);
2986
2066
  }
2987
- const record = write ? {
2988
- node: instance,
2989
- ...write
2990
- } : {
2991
- kind: "projection",
2992
- node: instance
2993
- };
2994
- materialized.set(projection, record);
2995
- if (owner) owner.materialized.push(projection);
2996
- return instance;
2067
+ connection.members.add(boundary);
2068
+ boundary.detach(() => {
2069
+ connection.members.delete(boundary);
2070
+ if (!connection.members.size) connection.close();
2071
+ });
2072
+ return boundary;
2073
+ };
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
+ }
2085
+ },
2086
+ dispose() {
2087
+ documents.forEach((connection) => connection.close());
2088
+ readables.forEach((connection) => connection.close());
2089
+ documents.clear();
2090
+ readables.clear();
2091
+ }
2092
+ };
2093
+ };
2094
+ //#endregion
2095
+ //#region core/src/projection/runtime.ts
2096
+ const createProjectionRuntime = (options) => {
2097
+ const scheduler = require_advanced.createScheduler(options?.onError ?? (() => void 0));
2098
+ const sources = createSourceRegistry(scheduler);
2099
+ const materialized = /* @__PURE__ */ new WeakMap();
2100
+ const publicViews = /* @__PURE__ */ new WeakMap();
2101
+ const scopes = /* @__PURE__ */ new Set();
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();
2108
+ };
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
+ });
2132
+ }
2133
+ materialized.set(definition, result);
2134
+ rememberScoped(definition, requester);
2135
+ return result;
2997
2136
  };
2998
- const get = (projection, requester) => {
2999
- const instance = materialize(projection, requester);
3000
- const current = instance.current();
3001
- if (instance.kind === "collection") {
3002
- const record = materialized.get(projection) ?? materialized.get(instance);
3003
- const existing = record?.collection;
3004
- const revision = instance.revision();
3005
- if (existing?.revision === revision) return existing.value;
3006
- const value = lazyMapView(current);
3007
- if (record) record.collection = {
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;
2147
+ };
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, {
3008
2157
  revision,
3009
2158
  value
3010
- };
2159
+ });
3011
2160
  return value;
3012
2161
  }
3013
2162
  if (isMapLike(current)) {
3014
- const record = materialized.get(projection) ?? materialized.get(instance);
3015
- const existing = record?.collection;
3016
- const revision = instance.revision();
3017
- if (existing?.revision === revision) return existing.value;
3018
- const value = mapView(mapRead(current));
3019
- if (record) record.collection = {
2163
+ const value = require_advanced.snapshotCollectionView(require_advanced.mapRead(current));
2164
+ publicViews.set(output, {
3020
2165
  revision,
3021
2166
  value
3022
- };
2167
+ });
3023
2168
  return value;
3024
2169
  }
2170
+ publicViews.delete(output);
3025
2171
  return current;
3026
2172
  };
3027
- const makeReadable = (projection, selector, equality = Object.is, requester) => {
3028
- if (!selector) return Object.freeze({
3029
- current: () => get(projection, requester),
3030
- revision: () => materialize(projection, requester).revision(),
3031
- subscribe: (listener) => materialize(projection, requester).subscribe(listener)
3032
- });
3033
- let initialized = false;
3034
- let value;
3035
- let selectedRevision = 0;
3036
- let sourceRevision = -1;
3037
- let selection = Object.freeze({
3038
- keys: /* @__PURE__ */ new Set(),
3039
- all: true,
3040
- structure: false
3041
- });
3042
- let previousCollection;
3043
- let unsubscribeSource;
3044
- const listeners = /* @__PURE__ */ new Set();
3045
- const updateCollectionSnapshot = () => {
3046
- const current = get(projection, requester);
3047
- previousCollection = isMapLike(current) ? current : void 0;
3048
- };
3049
- const evaluate = () => {
3050
- const tracker = {
3051
- keys: /* @__PURE__ */ new Set(),
3052
- all: false,
3053
- structure: false
3054
- };
3055
- const previousTracker = activeTracker;
3056
- activeTracker = tracker;
3057
- let source;
3058
- let next;
3059
- try {
3060
- source = get(projection, requester);
3061
- next = selector(source);
3062
- } finally {
3063
- activeTracker = previousTracker;
3064
- }
3065
- const nextSelection = Object.freeze({
3066
- keys: new Set(tracker.keys),
3067
- all: tracker.all || isMapLike(source) && tracker.keys.size === 0,
3068
- structure: tracker.structure
3069
- });
3070
- const changed = !initialized || !equality(value, next);
3071
- if (changed) {
3072
- if (initialized) selectedRevision++;
3073
- value = next;
3074
- }
3075
- selection = nextSelection;
3076
- initialized = true;
3077
- sourceRevision = materialize(projection, requester).revision();
3078
- updateCollectionSnapshot();
3079
- return changed;
3080
- };
3081
- const ensureCurrent = () => {
3082
- const revision = materialize(projection, requester).revision();
3083
- if (!initialized || !unsubscribeSource && sourceRevision !== revision) evaluate();
3084
- return value;
3085
- };
3086
- const onProjectionChange = (incoming) => {
3087
- let change = incoming;
3088
- if (!change && previousCollection) {
3089
- const current = get(projection, requester);
3090
- if (isMapLike(current)) {
3091
- change = mapChange(previousCollection, current);
3092
- previousCollection = current;
3093
- if (!change) {
3094
- sourceRevision = materialize(projection, requester).revision();
3095
- return;
3096
- }
3097
- } else previousCollection = void 0;
3098
- }
3099
- if (!selectionAffects(selection, change)) {
3100
- sourceRevision = materialize(projection, requester).revision();
3101
- updateCollectionSnapshot();
3102
- return;
3103
- }
3104
- const previousSelection = selection;
3105
- const changed = evaluate();
3106
- if (unsubscribeSource && !sameSelection(previousSelection, selection)) {
3107
- unsubscribeSource();
3108
- unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
3109
- }
3110
- if (changed) Array.from(listeners).forEach((listener) => listener());
3111
- };
3112
- const installSource = () => {
3113
- if (!unsubscribeSource) unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
3114
- };
2173
+ const get = (projection, requester) => publicCurrent(resolveOutput(projection, requester));
2174
+ const readableSource = (projection, requester) => {
2175
+ const output = resolveOutput(projection, requester);
3115
2176
  return Object.freeze({
3116
- current: ensureCurrent,
2177
+ kind: output.kind,
2178
+ current: () => get(projection, requester),
3117
2179
  revision: () => {
3118
- ensureCurrent();
3119
- return selectedRevision;
2180
+ assertOutput(output);
2181
+ return output.revision();
3120
2182
  },
3121
- subscribe: (listener) => {
3122
- ensureCurrent();
3123
- listeners.add(listener);
3124
- installSource();
3125
- return () => {
3126
- listeners.delete(listener);
3127
- if (!listeners.size && unsubscribeSource) {
3128
- unsubscribeSource();
3129
- unsubscribeSource = void 0;
3130
- }
3131
- };
3132
- }
2183
+ subscribe: output.subscribe
3133
2184
  });
3134
2185
  };
2186
+ const makeReadable = (projection, selector, equality, requester) => {
2187
+ const source = readableSource(projection, requester);
2188
+ return selector ? createSelectorReadable(source, selector, equality) : createDirectReadable(source);
2189
+ };
3135
2190
  function readable(projection, selector, equality) {
3136
2191
  return makeReadable(projection, selector, equality);
3137
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
+ };
3138
2201
  const setInput = (target, value, requester) => {
3139
- materialize(target, requester);
3140
- const record = materialized.get(target);
3141
- if (record?.kind !== "value-input") throw new TypeError("Projection is not a value input.");
3142
- 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);
3143
2205
  };
3144
2206
  const updateInput = (target, run, requester) => {
3145
- materialize(target, requester);
3146
- const record = materialized.get(target);
3147
- if (record?.kind !== "collection-input") throw new TypeError("Projection is not a collection input.");
3148
- 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);
3149
2229
  };
3150
2230
  const createScope = () => {
3151
- runtime.assertIdle();
2231
+ scheduler.assertIdle();
3152
2232
  const state = {
3153
2233
  active: true,
3154
2234
  materialized: [],
@@ -3156,25 +2236,27 @@ const createProjectionRuntime = (options) => {
3156
2236
  };
3157
2237
  scopes.add(state);
3158
2238
  const assertActive = () => {
3159
- if (!state.active) throw new ProjectionDisposedError();
2239
+ if (!state.active) throw new require_advanced.ProjectionDisposedError();
2240
+ scheduler.assertActive();
3160
2241
  };
3161
2242
  const own = (projection) => {
3162
2243
  assertActive();
3163
- const definition = require_advanced.definitionOf(projection);
3164
- const dependencies = definition.kind === "derive" ? definition.dependencies : definition.kind === "incremental-value" || definition.kind === "incremental-collection" ? Object.values(definition.dependencies) : [];
3165
- for (const dependency of dependencies) {
3166
- const dependencyOwner = require_advanced.ownerOf(dependency);
3167
- if (dependencyOwner && dependencyOwner !== state) throw new TypeError("A scope cannot depend on another scope.");
3168
- }
3169
- require_advanced.ownDefinition(projection, state);
3170
- return projection;
2244
+ return require_advanced.ownProjection(projection, state);
2245
+ };
2246
+ const ownOutputTree = (tree) => {
2247
+ if (require_advanced.isProjection(tree)) return own(tree);
2248
+ if (!tree || typeof tree !== "object") throw new TypeError("Incremental group output tree must be an object.");
2249
+ for (const value of Object.values(tree)) ownOutputTree(value);
2250
+ return tree;
3171
2251
  };
3172
2252
  const scopedInput = Object.assign((initial, equality) => own(require_advanced.input(initial, equality)), { collection: (initial) => own(require_advanced.input.collection(initial)) });
3173
2253
  const scopedDerive = (dependencies, compute, equality) => own(require_advanced.derive(dependencies, compute, equality));
3174
- const scopedIncremental = Object.assign((dependencies, processor) => own(require_advanced.incremental(dependencies, processor)), { collection: (dependencies, processor) => own(require_advanced.incremental.collection(dependencies, processor)) });
2254
+ const scopedIncremental = Object.assign((dependencies, processor) => own(require_advanced.incremental(dependencies, processor)), {
2255
+ collection: (dependencies, processor) => own(require_advanced.incremental.collection(dependencies, processor)),
2256
+ group: (dependencies, defineOutputs, processor) => ownOutputTree(require_advanced.incremental.group(dependencies, defineOutputs, processor))
2257
+ });
3175
2258
  function scopeReadable(projection, selector, equality) {
3176
2259
  assertActive();
3177
- materialize(projection, state);
3178
2260
  const source = makeReadable(projection, selector, equality, state);
3179
2261
  return Object.freeze({
3180
2262
  current: () => {
@@ -3202,7 +2284,7 @@ const createProjectionRuntime = (options) => {
3202
2284
  }
3203
2285
  function scopeBatch(optionsOrCallback, maybeCallback) {
3204
2286
  assertActive();
3205
- return typeof optionsOrCallback === "function" ? runtime.batch(optionsOrCallback) : runtime.batch(optionsOrCallback, maybeCallback);
2287
+ return typeof optionsOrCallback === "function" ? batch(optionsOrCallback) : batch(optionsOrCallback, maybeCallback);
3206
2288
  }
3207
2289
  return Object.freeze({
3208
2290
  input: scopedInput,
@@ -3222,34 +2304,19 @@ const createProjectionRuntime = (options) => {
3222
2304
  updateInput(target, run, state);
3223
2305
  },
3224
2306
  batch: scopeBatch,
3225
- dispose: () => {
3226
- if (!state.active) return;
3227
- runtime.assertIdle();
3228
- state.active = false;
3229
- state.subscriptions.forEach((unsubscribe) => unsubscribe());
3230
- for (const definition of state.materialized.reverse()) {
3231
- const record = materialized.get(definition);
3232
- if (!record) continue;
3233
- runtime.releaseNode(record.node);
3234
- if (record.kind !== "projection") runtime.releaseInput(record.source);
3235
- materialized.delete(definition);
3236
- }
3237
- state.materialized.length = 0;
3238
- scopes.delete(state);
3239
- }
2307
+ dispose: () => disposeScope(state)
3240
2308
  });
3241
2309
  };
3242
- const store = {
2310
+ return Object.freeze({
3243
2311
  get,
3244
2312
  readable,
3245
2313
  set: setInput,
3246
2314
  update: updateInput,
3247
- batch: runtime.batch,
2315
+ batch,
3248
2316
  scope: createScope,
3249
2317
  dispose: () => {
3250
- if (disposed) return;
3251
- runtime.assertIdle();
3252
- disposed = true;
2318
+ if (!scheduler.active) return;
2319
+ scheduler.assertIdle();
3253
2320
  for (const scope of scopes) {
3254
2321
  scope.active = false;
3255
2322
  scope.subscriptions.forEach((unsubscribe) => unsubscribe());
@@ -3257,35 +2324,35 @@ const createProjectionRuntime = (options) => {
3257
2324
  scope.materialized.length = 0;
3258
2325
  }
3259
2326
  scopes.clear();
3260
- runtime.dispose();
2327
+ sources.dispose();
2328
+ scheduler.dispose();
3261
2329
  }
3262
- };
3263
- return Object.freeze(store);
2330
+ });
3264
2331
  };
3265
2332
  //#endregion
3266
- exports.DocumentDisposedError = require_notification.DocumentDisposedError;
3267
- exports.DocumentReentrancyError = require_notification.DocumentReentrancyError;
3268
- exports.ParseError = require_scope.ParseError;
3269
- exports.ProjectionDisposedError = ProjectionDisposedError;
3270
- exports.ProjectionError = ProjectionError;
3271
- exports.TransactionRejected = require_notification.TransactionRejected;
2333
+ exports.DocumentDisposedError = require_access.DocumentDisposedError;
2334
+ exports.DocumentReentrancyError = require_access.DocumentReentrancyError;
2335
+ exports.ParseError = require_access.ParseError;
2336
+ exports.ProjectionDisposedError = require_advanced.ProjectionDisposedError;
2337
+ exports.ProjectionError = require_advanced.ProjectionError;
2338
+ exports.TransactionRejected = require_access.TransactionRejected;
3272
2339
  exports.asReadable = asReadable;
3273
2340
  exports.createDocument = createDocument;
3274
2341
  exports.createProjectionRuntime = createProjectionRuntime;
3275
2342
  exports.derive = require_advanced.derive;
3276
- exports.field = field;
2343
+ exports.field = require_advanced.field;
3277
2344
  exports.input = require_advanced.input;
3278
- exports.list = list;
3279
- exports.map = map;
3280
- exports.object = object;
2345
+ exports.list = require_advanced.list;
2346
+ exports.map = require_advanced.map;
2347
+ exports.object = require_advanced.object;
3281
2348
  exports.observe = require_advanced.observe;
3282
- exports.optional = optional;
3283
- exports.parse = require_scope.parse;
2349
+ exports.optional = require_advanced.optional;
2350
+ exports.parse = require_access.parse;
3284
2351
  exports.read = read;
3285
- exports.replace = require_scope.replace;
3286
- exports.snapshot = require_scope.snapshot;
3287
- exports.table = table;
3288
- exports.tree = tree;
3289
- 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;
3290
2357
 
3291
2358
  //# sourceMappingURL=index.cjs.map