doxum 0.1.26 → 0.1.28

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 (39) hide show
  1. package/README.md +86 -3
  2. package/dist/advanced.cjs +40 -30
  3. package/dist/advanced.cjs.map +1 -1
  4. package/dist/advanced.d.cts +34 -2
  5. package/dist/advanced.d.ts +34 -2
  6. package/dist/advanced.js +31 -22
  7. package/dist/advanced.js.map +1 -1
  8. package/dist/{definition-xgk3YNBp.d.ts → dependency-B3E65M1E.d.cts} +27 -3
  9. package/dist/{definition-CDOuvb5C.d.cts → dependency-BYbdDHNa.d.ts} +27 -3
  10. package/dist/driver-B5vdPohL.js.map +1 -1
  11. package/dist/driver-BL91gn7_.cjs.map +1 -1
  12. package/dist/index.cjs +885 -394
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +33 -18
  15. package/dist/index.d.ts +33 -18
  16. package/dist/index.js +784 -293
  17. package/dist/index.js.map +1 -1
  18. package/dist/local-sync.cjs.map +1 -1
  19. package/dist/local-sync.js.map +1 -1
  20. package/dist/transform-B4T3AcJr.js +938 -0
  21. package/dist/transform-B4T3AcJr.js.map +1 -0
  22. package/dist/transform-DUleAsG0.cjs +1105 -0
  23. package/dist/transform-DUleAsG0.cjs.map +1 -0
  24. package/package.json +1 -1
  25. package/skills/doxum-runtime/SKILL.md +16 -6
  26. package/skills/doxum-runtime/references/api.en.md +49 -15
  27. package/skills/doxum-runtime/references/api.zh-CN.md +47 -13
  28. package/skills/doxum-runtime/references/guide.en.md +2 -1
  29. package/skills/doxum-runtime/references/guide.zh-CN.md +2 -1
  30. package/skills/doxum-runtime/references/invariants.en.md +2 -2
  31. package/skills/doxum-runtime/references/invariants.zh-CN.md +2 -2
  32. package/skills/doxum-runtime/references/patterns.en.md +1 -1
  33. package/skills/doxum-runtime/references/patterns.zh-CN.md +1 -1
  34. package/skills/doxum-runtime/references/projections.en.md +77 -4
  35. package/skills/doxum-runtime/references/projections.zh-CN.md +74 -3
  36. package/dist/scheduler--137L3j-.js +0 -500
  37. package/dist/scheduler--137L3j-.js.map +0 -1
  38. package/dist/scheduler-D4VthaaK.cjs +0 -589
  39. package/dist/scheduler-D4VthaaK.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_scheduler = require("./scheduler-D4VthaaK.cjs");
2
+ const require_transform = require("./transform-DUleAsG0.cjs");
3
3
  const require_record = require("./record-ChYUi8PP.cjs");
4
4
  const require_driver = require("./driver-BL91gn7_.cjs");
5
5
  //#region core/src/schema/model.ts
@@ -59,9 +59,9 @@ function tree(value) {
59
59
  const length$1 = (keys) => keys.length;
60
60
  const keyAt = (keys, index) => Array.isArray(keys) ? keys[index] : keys.at(index);
61
61
  const equal = (left, right) => {
62
- require_scheduler.profile.equality.call();
62
+ require_transform.profile.equality.call();
63
63
  if (left === right) return true;
64
- require_scheduler.profile.equality.container();
64
+ require_transform.profile.equality.container();
65
65
  if (left.length !== right.length) return false;
66
66
  for (let index = 0; index < left.length; index++) if (left[index] !== keyAt(right, index)) return false;
67
67
  return true;
@@ -84,7 +84,7 @@ const knownKeys = (values) => {
84
84
  if (!positions) {
85
85
  positions = /* @__PURE__ */ new Map();
86
86
  for (let index = 0; index < values.length; index++) positions.set(values[index], index);
87
- require_scheduler.profile.address.listIndex(values.length);
87
+ require_transform.profile.address.listIndex(values.length);
88
88
  }
89
89
  return positions.get(key) ?? -1;
90
90
  }
@@ -108,7 +108,7 @@ const indexedKeys = (values, keyOf) => {
108
108
  if (!positions) {
109
109
  positions = /* @__PURE__ */ new Map();
110
110
  for (let index = 0; index < values.length; index++) positions.set(keyOf(values[index]), index);
111
- require_scheduler.profile.address.listIndex(values.length);
111
+ require_transform.profile.address.listIndex(values.length);
112
112
  }
113
113
  return positions.get(key) ?? -1;
114
114
  }
@@ -187,7 +187,7 @@ const compiledShape = (node, value) => {
187
187
  if (!layout) {
188
188
  const members = /* @__PURE__ */ new Map();
189
189
  for (const key of Object.keys(shape).sort()) {
190
- require_scheduler.profile.address.schemaStep();
190
+ require_transform.profile.address.schemaStep();
191
191
  members.set(key, {
192
192
  kind: "fixed",
193
193
  key,
@@ -341,7 +341,7 @@ const checkTreePayload = (node, value, address, strict = false) => {
341
341
  return present ? checkValue(node, value.value, address, strict) : void 0;
342
342
  };
343
343
  const copyTreeNode = (value) => {
344
- require_scheduler.profile.copy.treeNode();
344
+ require_transform.profile.copy.treeNode();
345
345
  return {
346
346
  ...value,
347
347
  children: [...value.children]
@@ -355,7 +355,7 @@ const equalTreeNode = (left, right) => {
355
355
  /** Copy editable structure; atomic payloads retain their immutable ownership contract. */
356
356
  const copyValue = (node, value) => {
357
357
  if (value === void 0 || node.kind === "field") return value;
358
- require_scheduler.profile.copy.structure();
358
+ require_transform.profile.copy.structure();
359
359
  if ((node.kind === "object" || node.kind === "variant") && require_record.isRecord(value)) {
360
360
  const result = Object.create(Object.getPrototypeOf(value));
361
361
  const { members } = compiledShape(node, value);
@@ -519,8 +519,8 @@ const resolveWithPrefix = (schema, root, address, prefix) => {
519
519
  }
520
520
  let cacheable = true;
521
521
  for (let index = start; index < address.length - 1; index += 1) {
522
- require_scheduler.profile.address.schemaStep();
523
- require_scheduler.profile.address.documentStep();
522
+ require_transform.profile.address.schemaStep();
523
+ require_transform.profile.address.documentStep();
524
524
  const resolved = step(node, current, address[index]);
525
525
  if (!resolved) return void 0;
526
526
  if (node?.kind !== "object" && node?.kind !== "table" && node?.kind !== "map") cacheable = false;
@@ -538,8 +538,8 @@ const resolveWithPrefix = (schema, root, address, prefix) => {
538
538
  };
539
539
  /** Resolves one member from an already current schema/container pair. */
540
540
  const resolveChild = (node, current, last) => {
541
- require_scheduler.profile.address.schemaStep();
542
- require_scheduler.profile.address.documentStep();
541
+ require_transform.profile.address.schemaStep();
542
+ require_transform.profile.address.documentStep();
543
543
  const resolved = step(node, current, last);
544
544
  if (!resolved || !require_record.isRecord(current) && !Array.isArray(current)) return void 0;
545
545
  if (node?.kind === "table" && require_record.isRecord(current) && require_record.isRecord(current.byId)) return {
@@ -817,7 +817,7 @@ const sameTreeTarget = (left, right) => {
817
817
  return left.kind !== "node" || right.kind === "node" && left.id === right.id;
818
818
  };
819
819
  const same = (left, right) => {
820
- require_scheduler.profile.dependency.comparison();
820
+ require_transform.profile.dependency.comparison();
821
821
  if ("schema" in left && "schema" in right && left.schema !== right.schema) return false;
822
822
  if (!sameTreeTarget(treeTarget(left), treeTarget(right))) return false;
823
823
  if (left.kind !== right.kind) return false;
@@ -825,7 +825,7 @@ const same = (left, right) => {
825
825
  const rightAddress = address(right);
826
826
  if (leftAddress.length !== rightAddress.length) return false;
827
827
  for (let index = 0; index < leftAddress.length; index += 1) {
828
- require_scheduler.profile.dependency.segmentCompared();
828
+ require_transform.profile.dependency.segmentCompared();
829
829
  if (leftAddress[index] !== rightAddress[index]) return false;
830
830
  }
831
831
  return left.kind !== "collection" || id(left) === id(right);
@@ -938,7 +938,7 @@ const locations = /* @__PURE__ */ new WeakMap();
938
938
  const locationOf = (value) => locations.get(value);
939
939
  const addressOf = (target) => {
940
940
  if (target.at) return target.at;
941
- require_scheduler.profile.access("addresses");
941
+ require_transform.profile.access("addresses");
942
942
  return target.at = addressOf(target.parent).concat(target.key);
943
943
  };
944
944
  const assertActive = (context) => {
@@ -960,7 +960,7 @@ const snapshot = (value) => {
960
960
  if (!location) return value;
961
961
  const { context } = location;
962
962
  const at = addressOf(location.target);
963
- require_scheduler.profile.access("snapshots");
963
+ require_transform.profile.access("snapshots");
964
964
  collect(context, at);
965
965
  const resolved = resolve(context, location.target);
966
966
  if (!resolved.node) throw new Error("The selected schema address no longer exists.");
@@ -975,7 +975,7 @@ const resolve = (context, target) => {
975
975
  assertActive(context);
976
976
  const generation = context.session?.generation ?? 0;
977
977
  if (target.generation !== generation) {
978
- require_scheduler.profile.access("resolutions");
978
+ require_transform.profile.access("resolutions");
979
979
  if (!target.parent && !target.at.length) {
980
980
  target.node = context.state.schema;
981
981
  target.value = context.state.document;
@@ -990,7 +990,7 @@ const resolve = (context, target) => {
990
990
  return target;
991
991
  };
992
992
  const createAccess = (context, initial = []) => {
993
- require_scheduler.profile.access("scopes");
993
+ require_transform.profile.access("scopes");
994
994
  const mutable = () => {
995
995
  assertActive(context);
996
996
  if (!context.session) throw new TypeError("Cannot modify read-only document access.");
@@ -1001,13 +1001,13 @@ const createAccess = (context, initial = []) => {
1001
1001
  return target.container = session.bind(addressOf(target), target.node, target.value);
1002
1002
  };
1003
1003
  const childAt = (target, key) => {
1004
- require_scheduler.profile.access("addresses");
1004
+ require_transform.profile.access("addresses");
1005
1005
  return addressOf(target).concat(key);
1006
1006
  };
1007
1007
  const access = (target) => {
1008
1008
  if (target.proxy) return target.proxy;
1009
1009
  const proxy = new Proxy(target, handler);
1010
- require_scheduler.profile.access("proxies");
1010
+ require_transform.profile.access("proxies");
1011
1011
  locations.set(proxy, {
1012
1012
  context,
1013
1013
  target
@@ -1365,10 +1365,10 @@ const compilePath = (owner, kind, pick) => {
1365
1365
  //#endregion
1366
1366
  //#region core/src/address/relation.ts
1367
1367
  const contains = (parent, child) => {
1368
- require_scheduler.profile.address.prefixComparison();
1368
+ require_transform.profile.address.prefixComparison();
1369
1369
  if (parent.length > child.length) return false;
1370
1370
  for (let index = 0; index < parent.length; index += 1) {
1371
- require_scheduler.profile.address.segmentCompared();
1371
+ require_transform.profile.address.segmentCompared();
1372
1372
  if (parent[index] !== child[index]) return false;
1373
1373
  }
1374
1374
  return true;
@@ -1381,7 +1381,7 @@ const createImpact = (schema, changes) => {
1381
1381
  let orders;
1382
1382
  const index = () => {
1383
1383
  if (!values) {
1384
- require_scheduler.profile.impact.index();
1384
+ require_transform.profile.impact.index();
1385
1385
  values = new require_driver.AddressIndex();
1386
1386
  orders = new require_driver.AddressIndex();
1387
1387
  visitChangedLocations(changes, (at) => values.add(at, true), (at) => orders.add(at, true));
@@ -1390,7 +1390,7 @@ const createImpact = (schema, changes) => {
1390
1390
  };
1391
1391
  const cache = /* @__PURE__ */ new Map();
1392
1392
  const affects = (value) => {
1393
- require_scheduler.profile.impact.affects();
1393
+ require_transform.profile.impact.affects();
1394
1394
  if (!belongs(value, schema)) return false;
1395
1395
  if (reset) return true;
1396
1396
  return affected(value, index(), orders);
@@ -1701,7 +1701,7 @@ const transition = (node, key, beforePresent, before, afterPresent, after) => {
1701
1701
  if (beforePresent === afterPresent && (!beforePresent || Object.is(before, after))) return;
1702
1702
  if (node.kind !== "field" && beforePresent === afterPresent && equalValue(node, before, after)) return;
1703
1703
  const value = afterPresent ? node.kind === "field" ? after : copyValue(node, after) : void 0;
1704
- require_scheduler.profile.recorder("transitions");
1704
+ require_transform.profile.recorder("transitions");
1705
1705
  return !beforePresent ? {
1706
1706
  key,
1707
1707
  kind: "added",
@@ -1756,7 +1756,7 @@ const diffMember = (changes, members, node, at, key, beforePresent, before, afte
1756
1756
  for (const id of Object.keys(right)) if (!Object.hasOwn(left, id)) diffMember(changes, children, node.value, childAt, id, false, void 0, true, right[id]);
1757
1757
  let order;
1758
1758
  if (node.kind === "table" && !equal(a.ids, b.ids)) {
1759
- require_scheduler.profile.recorder("publishedOrderItems", b.ids.length);
1759
+ require_transform.profile.recorder("publishedOrderItems", b.ids.length);
1760
1760
  order = {
1761
1761
  before: a.ids,
1762
1762
  after: [...b.ids]
@@ -1814,7 +1814,7 @@ const sealMembers = (fact, changes) => {
1814
1814
  const current = fact.node.kind === "list" ? indexedKeys(fact.value, fact.node.keyOf) : fact.value.ids;
1815
1815
  if (!equal(fact.order, current)) {
1816
1816
  const after = orderOf(fact.node, fact.value);
1817
- require_scheduler.profile.recorder("publishedOrderItems", after.length);
1817
+ require_transform.profile.recorder("publishedOrderItems", after.length);
1818
1818
  order = {
1819
1819
  before: fact.order,
1820
1820
  after
@@ -1872,7 +1872,7 @@ var ChangeRecorder = class {
1872
1872
  this.indexedGroups = true;
1873
1873
  for (const fact of this.facts) if (fact.kind === "members") {
1874
1874
  index.add(fact.at, fact);
1875
- require_scheduler.profile.recorder("indexedGroups");
1875
+ require_transform.profile.recorder("indexedGroups");
1876
1876
  }
1877
1877
  }
1878
1878
  return index;
@@ -1921,7 +1921,7 @@ var ChangeRecorder = class {
1921
1921
  releaseCovered(children) {
1922
1922
  let changedGroups;
1923
1923
  for (const child of children) {
1924
- require_scheduler.profile.recorder("absorbed");
1924
+ require_transform.profile.recorder("absorbed");
1925
1925
  if (child.kind === "member") {
1926
1926
  const { group, member } = child;
1927
1927
  forgetMember(group, member.key);
@@ -1937,9 +1937,9 @@ var ChangeRecorder = class {
1937
1937
  this.facts.add(fact);
1938
1938
  if (fact.kind === "members") {
1939
1939
  this.groups.set(fact.parent, fact);
1940
- require_scheduler.profile.recorder("groups");
1940
+ require_transform.profile.recorder("groups");
1941
1941
  if (!this.indexedGroups) return;
1942
- require_scheduler.profile.recorder("indexedGroups");
1942
+ require_transform.profile.recorder("indexedGroups");
1943
1943
  }
1944
1944
  (this.index ??= new require_driver.AddressIndex()).add(fact.at, fact);
1945
1945
  }
@@ -1969,14 +1969,14 @@ var ChangeRecorder = class {
1969
1969
  if (group.layout === "dynamic") (group.members ??= /* @__PURE__ */ new Map()).set(key, member);
1970
1970
  else if (definition.kind === "fixed") group.members[definition.slot] = member;
1971
1971
  else throw new Error("A fixed member group requires a fixed schema member.");
1972
- require_scheduler.profile.recorder("facts");
1972
+ require_transform.profile.recorder("facts");
1973
1973
  }
1974
1974
  reset() {
1975
1975
  if (this.resetBefore) return;
1976
1976
  const value = this.absorb([], this.state.schema, this.state.document, true);
1977
1977
  this.resetBefore = { value };
1978
1978
  this.groups = /* @__PURE__ */ new WeakMap();
1979
- require_scheduler.profile.recorder("facts");
1979
+ require_transform.profile.recorder("facts");
1980
1980
  }
1981
1981
  createGroup(container) {
1982
1982
  const group = container.layout.kind === "fixed" ? {
@@ -2001,13 +2001,13 @@ var ChangeRecorder = class {
2001
2001
  return group;
2002
2002
  }
2003
2003
  order(container) {
2004
- require_scheduler.profile.recorder("orderCaptures");
2004
+ require_transform.profile.recorder("orderCaptures");
2005
2005
  let group = this.groups.get(container.parent);
2006
2006
  if (group?.order || this.covered(container.at)) return;
2007
2007
  group ??= this.createGroup(container);
2008
2008
  group.order = orderOf(container.node, container.value);
2009
- require_scheduler.profile.recorder("orderSnapshots");
2010
- require_scheduler.profile.recorder("orderItems", group.order.length);
2009
+ require_transform.profile.recorder("orderSnapshots");
2010
+ require_transform.profile.recorder("orderItems", group.order.length);
2011
2011
  }
2012
2012
  tree(container, id) {
2013
2013
  const { at, value: tree } = container;
@@ -2025,7 +2025,7 @@ var ChangeRecorder = class {
2025
2025
  this.register(fact);
2026
2026
  }
2027
2027
  if (id !== void 0 && !fact.nodes.has(id)) {
2028
- require_scheduler.profile.recorder("treeNodes");
2028
+ require_transform.profile.recorder("treeNodes");
2029
2029
  fact.nodes.set(id, Object.hasOwn(tree.nodes, id) ? copyTreeNode(tree.nodes[id]) : void 0);
2030
2030
  }
2031
2031
  }
@@ -2048,7 +2048,7 @@ var ChangeRecorder = class {
2048
2048
  seal() {
2049
2049
  if (this.resetBefore) {
2050
2050
  if (equalValue(this.state.schema, this.resetBefore.value, this.state.document)) return require_driver.sealChanges([]);
2051
- require_scheduler.profile.recorder("sealed");
2051
+ require_transform.profile.recorder("sealed");
2052
2052
  return require_driver.sealChanges([{
2053
2053
  kind: "reset",
2054
2054
  before: this.resetBefore.value,
@@ -2059,7 +2059,7 @@ var ChangeRecorder = class {
2059
2059
  for (const fact of this.facts) if (fact.kind === "members") sealMembers(fact, changes);
2060
2060
  else sealTree(fact, this.state, changes);
2061
2061
  const result = require_driver.sealChanges(changes);
2062
- require_scheduler.profile.recorder("sealed", result.changes.length);
2062
+ require_transform.profile.recorder("sealed", result.changes.length);
2063
2063
  return result;
2064
2064
  }
2065
2065
  };
@@ -2664,7 +2664,7 @@ const select = (document, selector, equality = Object.is) => {
2664
2664
  };
2665
2665
  //#endregion
2666
2666
  //#region core/src/projection/input.ts
2667
- const valueInput = (initial, equality = Object.is) => require_scheduler.defineSource({
2667
+ const valueInput = (initial, equality = Object.is) => require_transform.defineSource({
2668
2668
  kind: "value-input",
2669
2669
  initial,
2670
2670
  equality
@@ -2678,7 +2678,7 @@ const collectionInput = (initial = /* @__PURE__ */ new Map(), equality = Object.
2678
2678
  if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
2679
2679
  entries.set(key, value);
2680
2680
  }
2681
- return require_scheduler.defineSource({
2681
+ return require_transform.defineSource({
2682
2682
  kind: "collection-input",
2683
2683
  initial: entries,
2684
2684
  equality
@@ -2691,13 +2691,13 @@ const input = Object.assign(valueInput, { collection: collectionInput });
2691
2691
  //#endregion
2692
2692
  //#region core/src/projection/observe.ts
2693
2693
  function observe(source, selector) {
2694
- if (isExternalSource(source)) return source.kind === "collection" ? require_scheduler.defineSource({
2694
+ if (isExternalSource(source)) return source.kind === "collection" ? require_transform.defineSource({
2695
2695
  kind: "external-collection",
2696
2696
  source
2697
2697
  }, {
2698
2698
  kind: "collection",
2699
2699
  equality: Object.is
2700
- }) : require_scheduler.defineSource({
2700
+ }) : require_transform.defineSource({
2701
2701
  kind: "external-value",
2702
2702
  source,
2703
2703
  equality: Object.is
@@ -2705,7 +2705,7 @@ function observe(source, selector) {
2705
2705
  kind: "value",
2706
2706
  equality: Object.is
2707
2707
  });
2708
- if ("current" in source && typeof source.current === "function") return require_scheduler.defineSource({
2708
+ if ("current" in source && typeof source.current === "function") return require_transform.defineSource({
2709
2709
  kind: "readable",
2710
2710
  readable: source,
2711
2711
  equality: Object.is
@@ -2720,7 +2720,7 @@ function observe(source, selector) {
2720
2720
  schema: state.schema,
2721
2721
  address: Object.freeze([])
2722
2722
  });
2723
- return require_scheduler.defineSource({
2723
+ return require_transform.defineSource({
2724
2724
  kind: "document",
2725
2725
  document,
2726
2726
  selector: selected
@@ -2735,273 +2735,522 @@ const isExternalSource = (source) => {
2735
2735
  return (candidate.kind === "value" || candidate.kind === "collection") && typeof candidate.current === "function" && typeof candidate.revision === "function" && typeof candidate.subscribe === "function";
2736
2736
  };
2737
2737
  //#endregion
2738
- //#region core/src/projection/derive.ts
2739
- const sourceValue = (source) => source.kind === "value" ? source.value : require_scheduler.snapshotCollectionView(source.read);
2740
- const outputKind = (projection) => {
2741
- const ref = require_scheduler.projectionRef(projection);
2742
- const definition = ref.producer;
2743
- const output = definition.kind === "source" ? definition.output : definition.outputs[ref.output];
2744
- if (!output) throw new TypeError("Projection output does not exist.");
2745
- return output.kind;
2746
- };
2747
- const assertKeyedProjection = (projection, role) => {
2748
- if (outputKind(projection) !== "collection") throw new TypeError(`${role} must be a keyed collection projection.`);
2749
- };
2750
- const isKeyedDependency = (value) => {
2751
- if (!value || typeof value !== "object" || require_scheduler.isProjection(value)) return false;
2752
- const candidate = value;
2753
- return require_scheduler.isProjection(candidate.source) && typeof candidate.key === "function";
2754
- };
2755
- const bindKey = (index, outputKey, sourceKey) => {
2756
- const previous = index.forward.get(outputKey);
2757
- if (previous === sourceKey) return;
2758
- if (previous !== void 0) {
2759
- const dependents = index.reverse.get(previous);
2760
- dependents?.delete(outputKey);
2761
- if (dependents?.size === 0) index.reverse.delete(previous);
2762
- index.forward.delete(outputKey);
2738
+ //#region core/src/projection/derive/keyed.ts
2739
+ const compileOrderedKeys = (value, role) => {
2740
+ if (!Array.isArray(value)) throw new TypeError(`${role} must be an array of string keys.`);
2741
+ const seen = /* @__PURE__ */ new Set();
2742
+ for (const key of value) {
2743
+ if (typeof key !== "string") throw new TypeError(`${role} must contain only string keys.`);
2744
+ if (seen.has(key)) throw new TypeError(`${role} must not contain duplicate keys.`);
2745
+ seen.add(key);
2763
2746
  }
2764
- if (sourceKey === void 0) return;
2765
- index.forward.set(outputKey, sourceKey);
2766
- const dependents = index.reverse.get(sourceKey);
2767
- if (dependents) dependents.add(outputKey);
2768
- else index.reverse.set(sourceKey, new Set([outputKey]));
2769
- };
2770
- const unbindKey = (indexes, outputKey) => {
2771
- for (const index of indexes) bindKey(index, outputKey, void 0);
2772
- };
2773
- const createBindingIndex = () => ({
2774
- forward: /* @__PURE__ */ new Map(),
2775
- reverse: /* @__PURE__ */ new Map()
2776
- });
2777
- function createValueDerive(dependencies, compute, equality = Object.is) {
2778
- if (!require_record.isPlainObject(dependencies) || require_scheduler.isProjection(dependencies)) throw new TypeError("Derive dependencies must be a plain object.");
2779
- const names = Object.keys(dependencies);
2780
- const ordered = names.map((name) => dependencies[name]);
2781
- ordered.forEach(require_scheduler.projectionRef);
2782
- const [projection] = require_scheduler.defineProcessor({
2783
- dependencies: ordered,
2747
+ return {
2748
+ keys: value,
2749
+ requested: seen
2750
+ };
2751
+ };
2752
+ function createKeyedDerive(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
2753
+ const dependencySpecs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
2754
+ const select = dependencySpecs === void 0 ? dependenciesOrSelect : selectOrEquality;
2755
+ const equality = dependencySpecs === void 0 ? selectOrEquality : maybeEquality;
2756
+ if (typeof select !== "function") throw new TypeError("Keyed derive requires a selector.");
2757
+ return require_transform.createKeyedTransform({
2758
+ source,
2759
+ dependencies: dependencySpecs,
2760
+ equality,
2761
+ name: "derive.keyed",
2762
+ evaluate: ({ value, key, dependencies }) => dependencySpecs === void 0 ? select(value, key) : select(value, key, dependencies)
2763
+ });
2764
+ }
2765
+ const createKeyedKeys = (source) => {
2766
+ require_transform.assertKeyedProjection(source, "Keyed keys source");
2767
+ const [projection] = require_transform.defineProcessor({
2768
+ dependencies: [source],
2784
2769
  outputs: [{
2785
2770
  kind: "value",
2786
- equality
2771
+ equality: Object.is
2787
2772
  }],
2788
2773
  create: () => ({ evaluate: (evaluation) => {
2774
+ const driver = evaluation.sources[0];
2789
2775
  const output = evaluation.outputs[0];
2790
- if (output.kind !== "value") throw new Error("derive requires a value output.");
2791
- const values = Object.create(null);
2792
- for (let index = 0; index < names.length; index++) values[names[index]] = sourceValue(evaluation.sources[index]);
2793
- const next = compute(Object.freeze(values));
2794
- require_scheduler.assertSynchronous(next);
2795
- output.output.set(next);
2796
- } })
2776
+ if (driver.kind !== "collection" || output.kind !== "value") throw new Error("derive.keyed.keys requires collection input and value output.");
2777
+ if (evaluation.reset || !driver.change || require_transform.collectionHasStructuralChange(driver.change)) output.output.set(driver.read.ids());
2778
+ } }),
2779
+ name: "derive.keyed.keys"
2797
2780
  });
2798
2781
  return projection;
2782
+ };
2783
+ const createOrderedSnapshot = (source, name, make) => {
2784
+ require_transform.assertKeyedProjection(source, `${name} source`);
2785
+ const [projection] = require_transform.defineProcessor({
2786
+ dependencies: [source],
2787
+ outputs: [{
2788
+ kind: "value",
2789
+ equality: Object.is
2790
+ }],
2791
+ create: () => {
2792
+ let indexes = /* @__PURE__ */ new Map();
2793
+ const rebuild = (driver) => {
2794
+ const nextIndexes = /* @__PURE__ */ new Map();
2795
+ const result = Object.freeze(driver.read.ids().map((key, index) => {
2796
+ nextIndexes.set(key, index);
2797
+ return make(key, driver.read.get(key));
2798
+ }));
2799
+ indexes = nextIndexes;
2800
+ return result;
2801
+ };
2802
+ const rebuildIncremental = (driver, change, previous) => {
2803
+ const changedValues = /* @__PURE__ */ new Map();
2804
+ for (const entry of change.added) changedValues.set(entry.key, entry.after);
2805
+ for (const entry of change.updated) changedValues.set(entry.key, entry.after);
2806
+ const nextIndexes = /* @__PURE__ */ new Map();
2807
+ const result = Object.freeze(driver.read.ids().map((key, index) => {
2808
+ nextIndexes.set(key, index);
2809
+ if (changedValues.has(key)) return make(key, changedValues.get(key));
2810
+ const previousIndex = indexes.get(key);
2811
+ return previousIndex === void 0 ? make(key, driver.read.get(key)) : previous[previousIndex];
2812
+ }));
2813
+ indexes = nextIndexes;
2814
+ return result;
2815
+ };
2816
+ return {
2817
+ evaluate: (evaluation) => {
2818
+ const driver = evaluation.sources[0];
2819
+ const output = evaluation.outputs[0];
2820
+ if (driver.kind !== "collection" || output.kind !== "value") throw new Error(`${name} requires collection input and value output.`);
2821
+ const change = driver.change;
2822
+ if (evaluation.reset || !change || change.kind === "reset") {
2823
+ output.output.set(rebuild(driver));
2824
+ return;
2825
+ }
2826
+ const previous = output.previous;
2827
+ if (!previous) {
2828
+ output.output.set(rebuild(driver));
2829
+ return;
2830
+ }
2831
+ if (require_transform.collectionHasStructuralChange(change)) {
2832
+ output.output.set(rebuildIncremental(driver, change, previous));
2833
+ return;
2834
+ }
2835
+ if (!change.updated.length) return;
2836
+ const next = [...previous];
2837
+ for (const entry of change.updated) {
2838
+ const index = indexes.get(entry.key);
2839
+ if (index === void 0) {
2840
+ output.output.set(rebuild(driver));
2841
+ return;
2842
+ }
2843
+ next[index] = make(entry.key, entry.after);
2844
+ }
2845
+ output.output.set(Object.freeze(next));
2846
+ },
2847
+ release: () => indexes.clear()
2848
+ };
2849
+ },
2850
+ name
2851
+ });
2852
+ return projection;
2853
+ };
2854
+ const createKeyedValues = (source) => createOrderedSnapshot(source, "derive.keyed.values", (_key, value) => value);
2855
+ const createKeyedEntries = (source) => createOrderedSnapshot(source, "derive.keyed.entries", (key, value) => Object.freeze([key, value]));
2856
+ const createKeyedGet = (source, key, equality = Object.is) => {
2857
+ require_transform.assertKeyedProjection(source, "derive.keyed.get source");
2858
+ if (require_transform.outputKind(key) !== "value") throw new TypeError("derive.keyed.get key must be a scalar projection.");
2859
+ const [projection] = require_transform.defineProcessor({
2860
+ dependencies: [source, key],
2861
+ outputs: [{
2862
+ kind: "value",
2863
+ equality
2864
+ }],
2865
+ create: () => {
2866
+ let sourceRevision = -1;
2867
+ let keyRevision = -1;
2868
+ let selected;
2869
+ return { evaluate: (evaluation) => {
2870
+ const collection = evaluation.sources[0];
2871
+ const selectedKey = evaluation.sources[1];
2872
+ const output = evaluation.outputs[0];
2873
+ if (collection.kind !== "collection" || selectedKey.kind !== "value" || output.kind !== "value") throw new Error("derive.keyed.get resolved invalid input/output kinds.");
2874
+ let affected = evaluation.reset;
2875
+ if (evaluation.reset || keyRevision !== selectedKey.revision) {
2876
+ const candidate = selectedKey.value;
2877
+ if (candidate !== void 0 && typeof candidate !== "string") throw new TypeError("derive.keyed.get key must resolve to a string or undefined.");
2878
+ selected = candidate;
2879
+ affected = true;
2880
+ } else if (sourceRevision !== collection.revision) {
2881
+ const change = collection.change;
2882
+ if (!change || change.kind === "reset") affected = true;
2883
+ else if (selected !== void 0) {
2884
+ for (const changedKey of require_transform.collectionChange.keys(change)) if (changedKey === selected) {
2885
+ affected = true;
2886
+ break;
2887
+ }
2888
+ }
2889
+ }
2890
+ if (affected) output.output.set(selected === void 0 ? void 0 : collection.read.get(selected));
2891
+ sourceRevision = collection.revision;
2892
+ keyRevision = selectedKey.revision;
2893
+ } };
2894
+ },
2895
+ name: "derive.keyed.get"
2896
+ });
2897
+ return projection;
2898
+ };
2899
+ function createKeyedFilter(source, dependenciesOrPredicate, maybePredicate) {
2900
+ const dependencySpecs = typeof dependenciesOrPredicate === "function" ? void 0 : dependenciesOrPredicate;
2901
+ const predicate = dependencySpecs === void 0 ? dependenciesOrPredicate : maybePredicate;
2902
+ if (typeof predicate !== "function") throw new TypeError("Keyed filter requires a predicate.");
2903
+ return require_transform.createKeyedTransform({
2904
+ source,
2905
+ dependencies: dependencySpecs,
2906
+ equality: Object.is,
2907
+ name: "derive.keyed.filter",
2908
+ evaluate: ({ value, key, dependencies }) => {
2909
+ const included = dependencySpecs === void 0 ? predicate(value, key) : predicate(value, key, dependencies);
2910
+ require_transform.assertSynchronous(included);
2911
+ if (typeof included !== "boolean") throw new TypeError("Keyed filter predicate must return boolean.");
2912
+ return included ? value : require_transform.keyedAbsent;
2913
+ }
2914
+ });
2799
2915
  }
2800
- function createKeyedDerive(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
2801
- assertKeyedProjection(source, "Keyed derive source");
2916
+ function createKeyedCompact(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
2802
2917
  const dependencySpecs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
2803
2918
  const select = dependencySpecs === void 0 ? dependenciesOrSelect : selectOrEquality;
2804
2919
  const equality = dependencySpecs === void 0 ? selectOrEquality : maybeEquality;
2805
- if (typeof select !== "function") throw new TypeError("Keyed derive requires a selector.");
2806
- const dependencies = [source];
2807
- const dependencyNames = [];
2808
- const keyed = [];
2809
- if (dependencySpecs !== void 0) {
2810
- if (!require_record.isPlainObject(dependencySpecs) || require_scheduler.isProjection(dependencySpecs)) throw new TypeError("Keyed derive dependencies must be a plain object.");
2811
- for (const name of Reflect.ownKeys(dependencySpecs)) {
2812
- if (typeof name !== "string") throw new TypeError("Keyed derive dependency names must be strings.");
2813
- const descriptor = Object.getOwnPropertyDescriptor(dependencySpecs, name);
2814
- if (!descriptor?.enumerable || !("value" in descriptor)) throw new TypeError("Keyed derive dependencies must be enumerable data properties.");
2815
- const dependency = descriptor.value;
2816
- dependencyNames.push(name);
2817
- if (require_scheduler.isProjection(dependency)) {
2818
- dependencies.push(dependency);
2819
- keyed.push(void 0);
2820
- continue;
2821
- }
2822
- if (!isKeyedDependency(dependency)) throw new TypeError("Keyed derive dependencies must be projections or keyed lookups.");
2823
- assertKeyedProjection(dependency.source, "Dynamic keyed dependency source");
2824
- dependencies.push(dependency.source);
2825
- keyed.push(dependency);
2920
+ if (typeof select !== "function") throw new TypeError("Keyed compact requires a selector.");
2921
+ return require_transform.createKeyedTransform({
2922
+ source,
2923
+ dependencies: dependencySpecs,
2924
+ equality,
2925
+ name: "derive.keyed.compact",
2926
+ evaluate: ({ value, key, dependencies }) => {
2927
+ const next = dependencySpecs === void 0 ? select(value, key) : select(value, key, dependencies);
2928
+ require_transform.assertSynchronous(next);
2929
+ return next === void 0 ? require_transform.keyedAbsent : next;
2826
2930
  }
2931
+ });
2932
+ }
2933
+ const createKeyedSubset = (source, orderedKeys) => {
2934
+ require_transform.assertKeyedProjection(source, "Keyed subset source");
2935
+ const keyProjection = require_transform.isProjection(orderedKeys) ? orderedKeys : void 0;
2936
+ if (keyProjection && require_transform.outputKind(keyProjection) !== "value") throw new TypeError("Keyed subset ordered keys must be a value projection.");
2937
+ const staticSelection = keyProjection ? void 0 : (() => {
2938
+ const compiled = compileOrderedKeys(orderedKeys, "Keyed subset ordered keys");
2939
+ return {
2940
+ keys: Object.freeze([...compiled.keys]),
2941
+ requested: compiled.requested
2942
+ };
2943
+ })();
2944
+ const [projection] = require_transform.defineProcessor({
2945
+ dependencies: keyProjection ? [source, keyProjection] : [source],
2946
+ outputs: [{
2947
+ kind: "collection",
2948
+ equality: Object.is
2949
+ }],
2950
+ create: () => {
2951
+ let keys = staticSelection?.keys;
2952
+ let requested = staticSelection?.requested;
2953
+ let keyRevision = -1;
2954
+ return {
2955
+ evaluate: (evaluation) => {
2956
+ const driver = evaluation.sources[0];
2957
+ const output = evaluation.outputs[0];
2958
+ if (driver.kind !== "collection" || output.kind !== "collection") throw new Error("derive.keyed.subset requires collection input and output.");
2959
+ let keysChanged = false;
2960
+ if (keyProjection) {
2961
+ const sourceContext = evaluation.sources[1];
2962
+ if (sourceContext?.kind !== "value") throw new TypeError("Keyed subset ordered keys resolved to a collection.");
2963
+ if (keys === void 0 || sourceContext.reset || keyRevision !== sourceContext.revision) {
2964
+ const compiled = compileOrderedKeys(sourceContext.value, "Keyed subset ordered keys");
2965
+ keys = compiled.keys;
2966
+ requested = compiled.requested;
2967
+ keyRevision = sourceContext.revision;
2968
+ keysChanged = true;
2969
+ }
2970
+ }
2971
+ if (!keys || !requested) throw new Error("Keyed subset keys were not initialized.");
2972
+ const activeKeys = keys;
2973
+ const activeRequested = requested;
2974
+ const orderedPresentKeys = () => activeKeys.filter((key) => driver.read.has(key));
2975
+ const rebuild = () => {
2976
+ const nextKeys = orderedPresentKeys();
2977
+ for (const key of nextKeys) output.output.set(key, driver.read.get(key));
2978
+ output.output.order(nextKeys);
2979
+ };
2980
+ const reconcileKeys = () => {
2981
+ const nextKeys = orderedPresentKeys();
2982
+ for (const key of output.previous.ids()) if (!activeRequested.has(key) || !driver.read.has(key)) output.output.remove(key);
2983
+ for (const key of nextKeys) if (!output.previous.has(key)) output.output.set(key, driver.read.get(key));
2984
+ output.output.order(nextKeys);
2985
+ };
2986
+ const change = driver.change;
2987
+ if (evaluation.reset || change?.kind === "reset") rebuild();
2988
+ else if (keysChanged) {
2989
+ reconcileKeys();
2990
+ if (change) for (const entry of change.updated) {
2991
+ const key = entry.key;
2992
+ if (activeRequested.has(key) && output.previous.has(key)) output.output.set(key, entry.after);
2993
+ }
2994
+ } else if (change) {
2995
+ let updateOrder = false;
2996
+ for (const entry of change.removed) {
2997
+ const key = entry.key;
2998
+ if (!activeRequested.has(key)) continue;
2999
+ output.output.remove(key);
3000
+ updateOrder = true;
3001
+ }
3002
+ for (const entry of change.added) {
3003
+ const key = entry.key;
3004
+ if (!activeRequested.has(key)) continue;
3005
+ output.output.set(key, entry.after);
3006
+ updateOrder = true;
3007
+ }
3008
+ for (const entry of change.updated) {
3009
+ const key = entry.key;
3010
+ if (activeRequested.has(key)) output.output.set(key, entry.after);
3011
+ }
3012
+ if (updateOrder) output.output.order(orderedPresentKeys());
3013
+ }
3014
+ },
3015
+ release: () => {
3016
+ if (keyProjection) {
3017
+ keys = void 0;
3018
+ requested = void 0;
3019
+ keyRevision = -1;
3020
+ }
3021
+ }
3022
+ };
3023
+ },
3024
+ name: "derive.keyed.subset"
3025
+ });
3026
+ return projection;
3027
+ };
3028
+ const normalizeGroups = (value) => {
3029
+ const groups = typeof value === "string" ? [value] : value;
3030
+ if (!Array.isArray(groups)) throw new TypeError("derive.keyed.groupBy selector must return a string or an array of strings.");
3031
+ const seen = /* @__PURE__ */ new Set();
3032
+ const result = [];
3033
+ for (const group of groups) {
3034
+ if (typeof group !== "string") throw new TypeError("derive.keyed.groupBy selector must return only string group keys.");
3035
+ if (seen.has(group)) throw new TypeError("derive.keyed.groupBy selector must not return duplicate group keys.");
3036
+ seen.add(group);
3037
+ result.push(group);
2827
3038
  }
2828
- const [projection] = require_scheduler.defineProcessor({
2829
- dependencies,
3039
+ return Object.freeze(result);
3040
+ };
3041
+ function createKeyedGroupBy(source, dependenciesOrSelector, maybeSelector) {
3042
+ require_transform.assertKeyedProjection(source, "derive.keyed.groupBy source");
3043
+ const dependencySpecs = typeof dependenciesOrSelector === "function" ? void 0 : dependenciesOrSelector;
3044
+ const selector = dependencySpecs === void 0 ? dependenciesOrSelector : maybeSelector;
3045
+ if (typeof selector !== "function") throw new TypeError("derive.keyed.groupBy requires a selector.");
3046
+ const compiled = require_transform.compileKeyedDependencies(source, dependencySpecs, "derive.keyed.groupBy");
3047
+ const [projection] = require_transform.defineProcessor({
3048
+ dependencies: compiled.projections,
2830
3049
  outputs: [{
2831
3050
  kind: "collection",
2832
- equality: equality ?? Object.is
3051
+ equality: (previous, next) => {
3052
+ const left = previous;
3053
+ const right = next;
3054
+ return left.length === right.length && left.every((key, index) => key === right[index]);
3055
+ }
2833
3056
  }],
2834
3057
  create: () => {
2835
- const bindings = keyed.map((dependency) => dependency ? createBindingIndex() : void 0);
2836
- const bindingIndexes = bindings.filter((index) => index !== void 0);
2837
- const globalValues = keyed.map(() => void 0);
2838
- let revisions = Object.freeze([]);
2839
- const clearBindings = () => {
2840
- for (const index of bindings) {
2841
- index?.forward.clear();
2842
- index?.reverse.clear();
3058
+ const dependencies = require_transform.createKeyedDependencyRuntime(compiled);
3059
+ const relation = require_transform.createKeyRelation();
3060
+ const orderIndex = /* @__PURE__ */ new Map();
3061
+ const groupRanks = /* @__PURE__ */ new Map();
3062
+ const refreshOrderIndex = (driver) => {
3063
+ orderIndex.clear();
3064
+ driver.read.ids().forEach((key, index) => orderIndex.set(key, index));
3065
+ };
3066
+ const selectGroups = (driver, sources, key, affected) => {
3067
+ if (!driver.read.has(key)) return false;
3068
+ const value = driver.read.get(key);
3069
+ const dependencyValues = dependencies.resolve(value, key, sources);
3070
+ const selected = dependencySpecs === void 0 ? selector(value, key) : selector(value, key, dependencyValues);
3071
+ require_transform.assertSynchronous(selected);
3072
+ const next = normalizeGroups(selected);
3073
+ const previous = relation.forward(key) ?? Object.freeze([]);
3074
+ if (!relation.replace(key, next)) return false;
3075
+ for (const group of previous) affected.add(group);
3076
+ for (const group of next) affected.add(group);
3077
+ return true;
3078
+ };
3079
+ const updateGroup = (group, output) => {
3080
+ const reverse = relation.reverse(group);
3081
+ if (!reverse?.size) {
3082
+ groupRanks.delete(group);
3083
+ output.output.remove(group);
3084
+ return;
2843
3085
  }
3086
+ const members = [...reverse].sort((left, right) => (orderIndex.get(left) ?? Number.MAX_SAFE_INTEGER) - (orderIndex.get(right) ?? Number.MAX_SAFE_INTEGER));
3087
+ const first = members[0];
3088
+ const source = orderIndex.get(first);
3089
+ const selector = relation.forward(first)?.indexOf(group) ?? -1;
3090
+ if (source === void 0 || selector < 0) throw new Error("derive.keyed.groupBy relation is inconsistent with source order.");
3091
+ groupRanks.set(group, {
3092
+ source,
3093
+ selector
3094
+ });
3095
+ output.output.set(group, Object.freeze(members));
3096
+ };
3097
+ const publishGroupOrder = (output) => {
3098
+ const order = [...groupRanks.entries()].sort(([leftKey, left], [rightKey, right]) => {
3099
+ if (left.source !== right.source) return left.source - right.source;
3100
+ if (left.selector !== right.selector) return left.selector - right.selector;
3101
+ return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
3102
+ }).map(([group]) => group);
3103
+ output.output.order(order);
3104
+ };
3105
+ const rebuild = (driver, sources, output) => {
3106
+ dependencies.clearBindings();
3107
+ relation.clear();
3108
+ groupRanks.clear();
3109
+ refreshOrderIndex(driver);
3110
+ const affected = /* @__PURE__ */ new Set();
3111
+ for (const key of driver.read.ids()) selectGroups(driver, sources, key, affected);
3112
+ for (const key of output.previous.ids()) affected.add(key);
3113
+ for (const group of affected) updateGroup(group, output);
3114
+ publishGroupOrder(output);
3115
+ };
3116
+ const publishAffected = (affected, output, orderDirty) => {
3117
+ for (const group of affected) updateGroup(group, output);
3118
+ if (orderDirty) publishGroupOrder(output);
3119
+ };
3120
+ const removeSourceKey = (key, affected) => {
3121
+ const previous = relation.forward(key);
3122
+ if (!previous) return false;
3123
+ for (const group of previous) affected.add(group);
3124
+ return relation.delete(key);
3125
+ };
3126
+ const markAllGroups = (output, affected) => {
3127
+ for (const group of relation.rights()) affected.add(group);
3128
+ for (const group of output.previous.ids()) affected.add(group);
2844
3129
  };
2845
3130
  return {
2846
3131
  evaluate: (evaluation) => {
2847
3132
  const driver = evaluation.sources[0];
2848
3133
  const output = evaluation.outputs[0];
2849
- if (driver.kind !== "collection" || output.kind !== "collection") throw new Error("Keyed derive requires collection input and output.");
2850
- const currentRevisions = evaluation.sources.map((current) => current.revision);
2851
- for (let index = 0; index < keyed.length; index++) {
2852
- if (keyed[index]) continue;
2853
- if (evaluation.reset || revisions[index + 1] !== currentRevisions[index + 1]) globalValues[index] = sourceValue(evaluation.sources[index + 1]);
2854
- }
2855
- const project = (key) => {
2856
- if (!driver.read.has(key)) return;
2857
- const value = driver.read.get(key);
2858
- const dependencyValues = Object.create(null);
2859
- for (let index = 0; index < keyed.length; index++) {
2860
- const dependency = keyed[index];
2861
- if (!dependency) {
2862
- dependencyValues[dependencyNames[index]] = globalValues[index];
2863
- continue;
2864
- }
2865
- const sourceContext = evaluation.sources[index + 1];
2866
- if (sourceContext.kind !== "collection") throw new TypeError("Dynamic keyed dependency resolved to a non-collection source.");
2867
- const selectedKey = dependency.key(value, key);
2868
- require_scheduler.assertSynchronous(selectedKey);
2869
- if (selectedKey !== void 0 && typeof selectedKey !== "string") throw new TypeError("Dynamic keyed dependency keys must be strings or undefined.");
2870
- bindKey(bindings[index], key, selectedKey);
2871
- dependencyValues[dependencyNames[index]] = selectedKey === void 0 ? void 0 : sourceContext.read.get(selectedKey);
2872
- }
2873
- const next = dependencySpecs === void 0 ? select(value, key) : select(value, key, Object.freeze(dependencyValues));
2874
- require_scheduler.assertSynchronous(next);
2875
- output.output.set(key, next);
2876
- };
3134
+ if (driver.kind !== "collection" || output.kind !== "collection") throw new Error("derive.keyed.groupBy requires collection input and output.");
3135
+ dependencies.prepare(evaluation.sources, evaluation.reset);
2877
3136
  if (evaluation.reset) {
2878
- clearBindings();
2879
- const ids = driver.read.ids();
2880
- for (const key of ids) project(key);
2881
- output.output.order(ids);
2882
- revisions = Object.freeze(currentRevisions);
3137
+ rebuild(driver, evaluation.sources, output);
3138
+ dependencies.remember(evaluation.sources);
2883
3139
  return;
2884
3140
  }
2885
3141
  const dirty = /* @__PURE__ */ new Set();
3142
+ const affected = /* @__PURE__ */ new Set();
2886
3143
  let all = false;
2887
- let updateOrder = false;
2888
- if (revisions[0] !== currentRevisions[0]) {
3144
+ let structural = false;
3145
+ let orderDirty = false;
3146
+ if (dependencies.driverChanged(driver)) {
2889
3147
  const change = driver.change;
2890
3148
  if (!change || change.kind === "reset") {
2891
- all = true;
2892
- updateOrder = true;
3149
+ rebuild(driver, evaluation.sources, output);
3150
+ dependencies.remember(evaluation.sources);
3151
+ return;
2893
3152
  } else {
2894
3153
  for (const entry of change.removed) {
2895
- output.output.remove(entry.key);
2896
- unbindKey(bindingIndexes, entry.key);
3154
+ orderDirty ||= removeSourceKey(entry.key, affected);
3155
+ dependencies.remove(entry.key);
2897
3156
  dirty.delete(entry.key);
2898
3157
  }
2899
3158
  for (const entry of change.added) dirty.add(entry.key);
2900
3159
  for (const entry of change.updated) dirty.add(entry.key);
2901
- updateOrder = Boolean(change.added.length || change.removed.length || change.order);
2902
- }
2903
- }
2904
- for (let index = 0; index < keyed.length; index++) {
2905
- if (revisions[index + 1] === currentRevisions[index + 1]) continue;
2906
- if (!keyed[index]) {
2907
- all = true;
2908
- continue;
2909
- }
2910
- const sourceContext = evaluation.sources[index + 1];
2911
- if (sourceContext.kind !== "collection" || !sourceContext.change) {
2912
- all = true;
2913
- continue;
2914
- }
2915
- if (sourceContext.change.kind === "reset") {
2916
- all = true;
2917
- continue;
3160
+ structural = Boolean(change.added.length || change.removed.length || change.order);
3161
+ orderDirty ||= structural;
3162
+ if (structural) refreshOrderIndex(driver);
2918
3163
  }
2919
- const reverse = bindings[index].reverse;
2920
- for (const transition of sourceContext.change.added) for (const outputKey of reverse.get(transition.key) ?? []) dirty.add(outputKey);
2921
- for (const transition of sourceContext.change.updated) for (const outputKey of reverse.get(transition.key) ?? []) dirty.add(outputKey);
2922
- for (const transition of sourceContext.change.removed) for (const outputKey of reverse.get(transition.key) ?? []) dirty.add(outputKey);
2923
3164
  }
3165
+ all ||= dependencies.collectInvalidated(evaluation.sources, dirty);
2924
3166
  if (all) for (const key of driver.read.ids()) dirty.add(key);
2925
- for (const key of dirty) if (driver.read.has(key)) project(key);
2926
- if (updateOrder) output.output.order(driver.read.ids());
2927
- revisions = Object.freeze(currentRevisions);
3167
+ for (const key of dirty) orderDirty ||= selectGroups(driver, evaluation.sources, key, affected);
3168
+ if (structural) markAllGroups(output, affected);
3169
+ publishAffected(affected, output, orderDirty);
3170
+ dependencies.remember(evaluation.sources);
2928
3171
  },
2929
3172
  release: () => {
2930
- clearBindings();
2931
- revisions = Object.freeze([]);
3173
+ relation.clear();
3174
+ orderIndex.clear();
3175
+ groupRanks.clear();
3176
+ dependencies.release();
2932
3177
  }
2933
3178
  };
2934
3179
  },
2935
- name: "derive.keyed"
3180
+ name: "derive.keyed.groupBy"
2936
3181
  });
2937
3182
  return projection;
2938
3183
  }
2939
- const derive = Object.assign(createValueDerive, { keyed: createKeyedDerive });
2940
- //#endregion
2941
- //#region core/src/projection/collection/change.ts
2942
- const commonOrderChanged = (before, after, added, removed) => {
2943
- const beforeCommon = before.filter((key) => !removed.has(key));
2944
- const afterCommon = after.filter((key) => !added.has(key));
2945
- return beforeCommon.some((key, index) => afterCommon[index] !== key);
2946
- };
2947
- /** Builds the canonical incremental CollectionChange from already-known net transitions. */
2948
- const createCollectionChange = (input) => {
2949
- const addedKeys = new Set(input.added.map((entry) => entry.key));
2950
- const removedKeys = new Set(input.removed.map((entry) => entry.key));
2951
- const orderChanged = input.beforeOrder !== input.afterOrder && commonOrderChanged(input.beforeOrder, input.afterOrder, addedKeys, removedKeys);
2952
- if (!input.added.length && !input.updated.length && !input.removed.length && !orderChanged) return void 0;
2953
- return Object.freeze({
2954
- kind: "incremental",
2955
- added: Object.freeze([...input.added]),
2956
- updated: Object.freeze([...input.updated]),
2957
- removed: Object.freeze([...input.removed]),
2958
- ...orderChanged ? { order: Object.freeze({
2959
- before: Object.freeze([...input.beforeOrder]),
2960
- after: Object.freeze([...input.afterOrder])
2961
- }) } : {}
3184
+ const createKeyedSingleton = (source, keyOf, equality = Object.is) => {
3185
+ if (require_transform.outputKind(source) !== "value") throw new TypeError("derive.keyed.singleton source must be a scalar projection.");
3186
+ if (typeof keyOf !== "function") throw new TypeError("derive.keyed.singleton requires a key selector.");
3187
+ const [projection] = require_transform.defineProcessor({
3188
+ dependencies: [source],
3189
+ outputs: [{
3190
+ kind: "collection",
3191
+ equality
3192
+ }],
3193
+ create: () => ({ evaluate: (evaluation) => {
3194
+ const scalar = evaluation.sources[0];
3195
+ const output = evaluation.outputs[0];
3196
+ if (scalar.kind !== "value" || output.kind !== "collection") throw new Error("derive.keyed.singleton resolved invalid input/output kinds.");
3197
+ const previous = output.previous.ids();
3198
+ if (scalar.value === void 0) {
3199
+ for (const key of previous) output.output.remove(key);
3200
+ return;
3201
+ }
3202
+ const key = keyOf(scalar.value);
3203
+ require_transform.assertSynchronous(key);
3204
+ if (typeof key !== "string") throw new TypeError("derive.keyed.singleton key selector must return a string.");
3205
+ for (const previousKey of previous) if (previousKey !== key) output.output.remove(previousKey);
3206
+ output.output.set(key, scalar.value);
3207
+ if (previous.length !== 1 || previous[0] !== key) output.output.order([key]);
3208
+ } }),
3209
+ name: "derive.keyed.singleton"
2962
3210
  });
3211
+ return projection;
2963
3212
  };
2964
- /** Exact fallback diff between two published map-like snapshots. */
2965
- const diffCollection = (previous, current, isEqual = Object.is) => {
2966
- const added = [];
2967
- const updated = [];
2968
- const removed = [];
2969
- for (const [key, before] of previous) if (!current.has(key)) removed.push({
2970
- key,
2971
- before
2972
- });
2973
- else {
2974
- const after = current.get(key);
2975
- if (!isEqual(before, after)) updated.push({
2976
- key,
2977
- before,
2978
- after
2979
- });
2980
- }
2981
- for (const [key, after] of current) if (!previous.has(key)) added.push({
2982
- key,
2983
- after
2984
- });
2985
- return createCollectionChange({
2986
- added,
2987
- updated,
2988
- removed,
2989
- beforeOrder: [...previous.keys()],
2990
- afterOrder: [...current.keys()]
3213
+ const keyedDerive = Object.assign(createKeyedDerive, {
3214
+ keys: createKeyedKeys,
3215
+ values: createKeyedValues,
3216
+ entries: createKeyedEntries,
3217
+ get: createKeyedGet,
3218
+ groupBy: createKeyedGroupBy,
3219
+ singleton: createKeyedSingleton,
3220
+ subset: createKeyedSubset,
3221
+ filter: createKeyedFilter,
3222
+ compact: createKeyedCompact
3223
+ });
3224
+ //#endregion
3225
+ //#region core/src/projection/derive.ts
3226
+ function createValueDerive(dependencies, compute, equality = Object.is) {
3227
+ const compiled = require_transform.compileProjectionDependencies(dependencies, "Derive");
3228
+ const names = compiled.names;
3229
+ const ordered = compiled.projections;
3230
+ const [projection] = require_transform.defineProcessor({
3231
+ dependencies: ordered,
3232
+ outputs: [{
3233
+ kind: "value",
3234
+ equality
3235
+ }],
3236
+ create: () => ({ evaluate: (evaluation) => {
3237
+ const output = evaluation.outputs[0];
3238
+ if (output.kind !== "value") throw new Error("derive requires a value output.");
3239
+ const values = Object.create(null);
3240
+ for (let index = 0; index < names.length; index++) values[names[index]] = require_transform.snapshotDependencyValue(evaluation.sources[index]);
3241
+ const next = compute(Object.freeze(values));
3242
+ require_transform.assertSynchronous(next);
3243
+ output.output.set(next);
3244
+ } })
2991
3245
  });
2992
- };
2993
- const collectionChangedKeys = (change) => change.kind === "reset" ? /* @__PURE__ */ new Set() : new Set([
2994
- ...change.added.map((entry) => entry.key),
2995
- ...change.updated.map((entry) => entry.key),
2996
- ...change.removed.map((entry) => entry.key)
2997
- ]);
2998
- const collectionHasStructuralChange = (change) => change.kind === "reset" || Boolean(change.added.length || change.removed.length || change.order);
2999
- const collectionHasAnyChange = (change) => change.kind === "reset" || Boolean(change.added.length || change.updated.length || change.removed.length || change.order);
3246
+ return projection;
3247
+ }
3248
+ const derive = Object.assign(createValueDerive, { keyed: keyedDerive });
3000
3249
  //#endregion
3001
3250
  //#region core/src/projection/collection/index.ts
3002
3251
  const height = (node) => node?.height ?? 0;
3003
3252
  const node = (key, value, left, right) => {
3004
- require_scheduler.profile.collectionIndex.node();
3253
+ require_transform.profile.collectionIndex.node();
3005
3254
  return Object.freeze({
3006
3255
  key,
3007
3256
  value,
@@ -3062,7 +3311,7 @@ var PersistentKeyedIndex = class PersistentKeyedIndex {
3062
3311
  }
3063
3312
  static from(entries) {
3064
3313
  const sorted = [...entries].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
3065
- require_scheduler.profile.collectionIndex.build(sorted.length);
3314
+ require_transform.profile.collectionIndex.build(sorted.length);
3066
3315
  return new PersistentKeyedIndex(fromSorted(sorted));
3067
3316
  }
3068
3317
  get(key) {
@@ -3167,15 +3416,15 @@ const createCollectionOutput = (binding) => {
3167
3416
  reset = initialize;
3168
3417
  const read = (next) => ({
3169
3418
  get: (key) => {
3170
- require_scheduler.assertScope(active);
3419
+ require_transform.assertScope(active);
3171
3420
  return next ? getNext(key) : published.get(key);
3172
3421
  },
3173
3422
  has: (key) => {
3174
- require_scheduler.assertScope(active);
3423
+ require_transform.assertScope(active);
3175
3424
  return next ? hasNext(key) : published.has(key);
3176
3425
  },
3177
3426
  ids: () => {
3178
- require_scheduler.assertScope(active);
3427
+ require_transform.assertScope(active);
3179
3428
  return next ? Object.freeze(explicitOrder ? [...explicitOrder] : [...deriveIds()]) : published.ids();
3180
3429
  }
3181
3430
  });
@@ -3184,7 +3433,7 @@ const createCollectionOutput = (binding) => {
3184
3433
  next: read(true),
3185
3434
  output: {
3186
3435
  set: (key, value) => {
3187
- require_scheduler.assertScope(active);
3436
+ require_transform.assertScope(active);
3188
3437
  if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
3189
3438
  staged.set(key, {
3190
3439
  present: true,
@@ -3192,11 +3441,11 @@ const createCollectionOutput = (binding) => {
3192
3441
  });
3193
3442
  },
3194
3443
  remove: (key) => {
3195
- require_scheduler.assertScope(active);
3444
+ require_transform.assertScope(active);
3196
3445
  staged.set(key, { present: false });
3197
3446
  },
3198
3447
  order: (order) => {
3199
- require_scheduler.assertScope(active);
3448
+ require_transform.assertScope(active);
3200
3449
  explicitOrder = Object.freeze([...order]);
3201
3450
  }
3202
3451
  }
@@ -3206,18 +3455,39 @@ const createCollectionOutput = (binding) => {
3206
3455
  if (cleared) {
3207
3456
  for (const key of published.keys()) if (!staged.has(key)) staged.set(key, { present: false });
3208
3457
  }
3209
- require_scheduler.profile.projection("touchedKeys", staged.size);
3458
+ require_transform.profile.projection("touchedKeys", staged.size);
3210
3459
  const added = /* @__PURE__ */ new Set();
3211
3460
  const removed = /* @__PURE__ */ new Set();
3212
- const updated = /* @__PURE__ */ new Set();
3461
+ const addedEntries = [];
3462
+ const updatedEntries = [];
3463
+ const removedEntries = [];
3213
3464
  for (const [key, entry] of staged) {
3214
3465
  const existed = published.has(key);
3215
3466
  if (entry.present) {
3216
- if (!existed) added.add(key);
3217
- else if (!isEqual(published.get(key), entry.value)) updated.add(key);
3467
+ if (!existed) {
3468
+ added.add(key);
3469
+ addedEntries.push({
3470
+ key,
3471
+ after: entry.value
3472
+ });
3473
+ continue;
3474
+ }
3475
+ const before = published.get(key);
3476
+ if (!isEqual(before, entry.value)) updatedEntries.push({
3477
+ key,
3478
+ before,
3479
+ after: entry.value
3480
+ });
3218
3481
  else if (!nextReset) staged.delete(key);
3219
- } else if (existed) removed.add(key);
3220
- else staged.delete(key);
3482
+ continue;
3483
+ }
3484
+ if (existed) {
3485
+ removed.add(key);
3486
+ removedEntries.push({
3487
+ key,
3488
+ before: published.get(key)
3489
+ });
3490
+ } else staged.delete(key);
3221
3491
  }
3222
3492
  const ids = published.ids();
3223
3493
  if (explicitOrder || added.size || removed.size) {
@@ -3225,36 +3495,8 @@ const createCollectionOutput = (binding) => {
3225
3495
  if (new Set(order).size !== order.length || order.length !== published.size() + added.size - removed.size || order.some((key) => !hasNext(key))) throw new TypeError("Projection order must contain every key exactly once.");
3226
3496
  nextIds = ids.length === order.length && ids.every((key, index) => key === order[index]) ? ids : Object.freeze([...order]);
3227
3497
  } else nextIds = ids;
3228
- const changedKeys = /* @__PURE__ */ new Set();
3229
- added.forEach((key) => changedKeys.add(key));
3230
- removed.forEach((key) => changedKeys.add(key));
3231
- updated.forEach((key) => changedKeys.add(key));
3232
- require_scheduler.profile.projection("changedKeys", changedKeys.size);
3233
- const addedEntries = [];
3234
- const updatedEntries = [];
3235
- const removedEntries = [];
3236
- for (const key of changedKeys) {
3237
- const beforePresent = published.has(key);
3238
- const afterPresent = hasNext(key);
3239
- if (beforePresent === afterPresent) {
3240
- if (!beforePresent) continue;
3241
- if (isEqual !== Object.is && isEqual(published.get(key), getNext(key))) continue;
3242
- }
3243
- if (!beforePresent) addedEntries.push({
3244
- key,
3245
- after: getNext(key)
3246
- });
3247
- else if (!afterPresent) removedEntries.push({
3248
- key,
3249
- before: published.get(key)
3250
- });
3251
- else updatedEntries.push({
3252
- key,
3253
- before: published.get(key),
3254
- after: getNext(key)
3255
- });
3256
- }
3257
- change = nextReset ? Object.freeze({ kind: "reset" }) : createCollectionChange({
3498
+ require_transform.profile.projection("changedKeys", addedEntries.length + updatedEntries.length + removedEntries.length);
3499
+ change = nextReset ? Object.freeze({ kind: "reset" }) : require_transform.createCollectionChange({
3258
3500
  added: addedEntries,
3259
3501
  updated: updatedEntries,
3260
3502
  removed: removedEntries,
@@ -3268,15 +3510,15 @@ const createCollectionOutput = (binding) => {
3268
3510
  kind: "collection",
3269
3511
  read: {
3270
3512
  get: (key) => {
3271
- require_scheduler.assertScope(active);
3513
+ require_transform.assertScope(active);
3272
3514
  return getNext(key);
3273
3515
  },
3274
3516
  has: (key) => {
3275
- require_scheduler.assertScope(active);
3517
+ require_transform.assertScope(active);
3276
3518
  return hasNext(key);
3277
3519
  },
3278
3520
  ids: () => {
3279
- require_scheduler.assertScope(active);
3521
+ require_transform.assertScope(active);
3280
3522
  return nextIds;
3281
3523
  }
3282
3524
  },
@@ -3302,7 +3544,7 @@ const createCollectionOutput = (binding) => {
3302
3544
  initialized = true;
3303
3545
  },
3304
3546
  emit: (call) => {
3305
- Array.from(listeners).forEach((listener) => call(listener));
3547
+ Array.from(listeners).forEach((listener) => call(listener, change));
3306
3548
  },
3307
3549
  hasConsumers: () => consumers.size > 0,
3308
3550
  forEachConsumer: (run) => consumers.forEach(run),
@@ -3358,12 +3600,12 @@ const createValueOutput = (binding) => {
3358
3600
  return {
3359
3601
  previous: initialized ? value : void 0,
3360
3602
  next: () => {
3361
- require_scheduler.assertScope(active);
3603
+ require_transform.assertScope(active);
3362
3604
  if (staged !== unset) return staged;
3363
3605
  return initialize ? void 0 : initialized ? value : void 0;
3364
3606
  },
3365
3607
  output: Object.freeze({ set: (candidate) => {
3366
- require_scheduler.assertScope(active);
3608
+ require_transform.assertScope(active);
3367
3609
  staged = candidate;
3368
3610
  } })
3369
3611
  };
@@ -3390,7 +3632,7 @@ const createValueOutput = (binding) => {
3390
3632
  begin,
3391
3633
  seal,
3392
3634
  context: (active) => {
3393
- require_scheduler.assertScope(active);
3635
+ require_transform.assertScope(active);
3394
3636
  return Object.freeze({
3395
3637
  kind: "value",
3396
3638
  value: next,
@@ -3411,8 +3653,8 @@ const createValueOutput = (binding) => {
3411
3653
  initialized = true;
3412
3654
  },
3413
3655
  emit: (call) => {
3414
- require_scheduler.profile.materialized.notification();
3415
- Array.from(listeners).forEach((listener) => call(listener));
3656
+ require_transform.profile.materialized.notification();
3657
+ Array.from(listeners).forEach((listener) => call(listener, void 0));
3416
3658
  },
3417
3659
  hasConsumers: () => consumers.size > 0,
3418
3660
  forEachConsumer: (run) => consumers.forEach(run),
@@ -3450,7 +3692,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
3450
3692
  let instance;
3451
3693
  let cause;
3452
3694
  const check = () => {
3453
- if (!scheduler.active || processor.disposed) throw new require_scheduler.ProjectionDisposedError();
3695
+ if (!scheduler.active || processor.disposed) throw new require_transform.ProjectionDisposedError();
3454
3696
  if (processor.fault) throw processor.fault;
3455
3697
  };
3456
3698
  const bound = definition.outputs.map((outputDefinition) => {
@@ -3504,8 +3746,8 @@ const createProcessor = (scheduler, definition, dependencies) => {
3504
3746
  instance = void 0;
3505
3747
  }
3506
3748
  if (!instance) instance = definition.create();
3507
- require_scheduler.profile.materialized[reset ? "rebuilt" : "updated"]();
3508
- require_scheduler.assertSynchronous(instance.evaluate({
3749
+ require_transform.profile.materialized[reset ? "rebuilt" : "updated"]();
3750
+ require_transform.assertSynchronous(instance.evaluate({
3509
3751
  reset,
3510
3752
  cause: runCause,
3511
3753
  sources,
@@ -3552,7 +3794,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
3552
3794
  try {
3553
3795
  scheduler.initialize(() => {
3554
3796
  const invalid = dependencies.find((output) => output.owner.fault || output.owner.disposed);
3555
- if (invalid) throw invalid.owner.fault ?? new require_scheduler.ProjectionDisposedError();
3797
+ if (invalid) throw invalid.owner.fault ?? new require_transform.ProjectionDisposedError();
3556
3798
  if (processor.evaluate(true, void 0).length) processor.publish();
3557
3799
  processor.clear();
3558
3800
  });
@@ -3567,6 +3809,20 @@ const createProcessor = (scheduler, definition, dependencies) => {
3567
3809
  return processor;
3568
3810
  };
3569
3811
  //#endregion
3812
+ //#region core/src/projection/readable/listeners.ts
3813
+ /** Exhaustively notifies one projection-readable listener snapshot. */
3814
+ const notifyProjectionListeners = (listeners, message) => {
3815
+ const snapshot = [...listeners];
3816
+ const failures = [];
3817
+ for (const listener of snapshot) try {
3818
+ listener();
3819
+ } catch (error) {
3820
+ failures.push(error);
3821
+ }
3822
+ if (failures.length === 1) throw failures[0];
3823
+ if (failures.length) throw new AggregateError(failures, message);
3824
+ };
3825
+ //#endregion
3570
3826
  //#region core/src/projection/readable/selection.ts
3571
3827
  let activeTracker;
3572
3828
  const trackKey = (key) => {
@@ -3618,10 +3874,9 @@ const trackedMap = (source) => {
3618
3874
  };
3619
3875
  const selectionAffects = (selection, change) => {
3620
3876
  if (!change || change.kind === "reset") return true;
3621
- if (selection.all && collectionHasAnyChange(change)) return true;
3622
- if (selection.structure && collectionHasStructuralChange(change)) return true;
3623
- const changedKeys = collectionChangedKeys(change);
3624
- for (const key of selection.keys) if (changedKeys.has(key)) return true;
3877
+ if (selection.all) return true;
3878
+ if (selection.structure && require_transform.collectionHasStructuralChange(change)) return true;
3879
+ for (const key of require_transform.collectionChange.keys(change)) if (selection.keys.has(key)) return true;
3625
3880
  return false;
3626
3881
  };
3627
3882
  const createDirectReadable = (source) => Object.freeze({
@@ -3689,7 +3944,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
3689
3944
  if (!change && previousCollection) {
3690
3945
  const current = source.current();
3691
3946
  if (isMapLike(current)) {
3692
- change = diffCollection(previousCollection, current);
3947
+ change = require_transform.diffCollection(previousCollection, current);
3693
3948
  previousCollection = current;
3694
3949
  if (!change) {
3695
3950
  sourceRevision = source.revision();
@@ -3702,7 +3957,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
3702
3957
  updateCollectionSnapshot();
3703
3958
  return;
3704
3959
  }
3705
- if (evaluate()) Array.from(listeners).forEach((listener) => listener());
3960
+ if (evaluate()) notifyProjectionListeners(listeners, "Projection selector listeners failed.");
3706
3961
  };
3707
3962
  const installSource = () => {
3708
3963
  if (!unsubscribeSource) unsubscribeSource = source.subscribe(onProjectionChange);
@@ -3733,7 +3988,7 @@ const hasDocumentDirty = (dirty) => dirty.replace || dirty.order || dirty.treeRo
3733
3988
  const equivalent = (node, left, right) => node.kind === "field" ? Object.is(left, right) : equalValue(node, left, right);
3734
3989
  const replaceValue = (node, previous, current) => equivalent(node, previous, current) ? previous : copyValue(node, current);
3735
3990
  const cloneRecord = (value) => {
3736
- require_scheduler.profile.copy.shallowRecord(Object.getOwnPropertyNames(value).length);
3991
+ require_transform.profile.copy.shallowRecord(Object.getOwnPropertyNames(value).length);
3737
3992
  return Object.create(Object.getPrototypeOf(value), Object.getOwnPropertyDescriptors(value));
3738
3993
  };
3739
3994
  const materializeRecordChild = (node, previous, current, key, dirty) => {
@@ -4039,7 +4294,7 @@ const mergeCollectionChanges = (selector, pending, changes) => {
4039
4294
  };
4040
4295
  //#endregion
4041
4296
  //#region core/src/projection/source/boundary.ts
4042
- const sourceError = (cause) => new require_scheduler.ProjectionError("source", cause);
4297
+ const sourceError = (cause) => new require_transform.ProjectionError("source", cause);
4043
4298
  const createValueBoundary = (scheduler, name, initial, equality, prepare, clearSource) => {
4044
4299
  let boundary;
4045
4300
  let pendingReset = false;
@@ -4048,7 +4303,7 @@ const createValueBoundary = (scheduler, name, initial, equality, prepare, clearS
4048
4303
  let recovering = false;
4049
4304
  let cleanup;
4050
4305
  const check = () => {
4051
- if (!scheduler.active || boundary.disposed) throw new require_scheduler.ProjectionDisposedError();
4306
+ if (!scheduler.active || boundary.disposed) throw new require_transform.ProjectionDisposedError();
4052
4307
  if (boundary.fault) throw boundary.fault;
4053
4308
  };
4054
4309
  const state = createValueOutput({
@@ -4155,23 +4410,23 @@ const stageCollectionRead = (state, read, active, options) => {
4155
4410
  const evaluation = state.begin(active, options.reset);
4156
4411
  if (options.reset) {
4157
4412
  const ids = read.ids();
4158
- require_scheduler.profile.collectionView.idsScanned(ids.length);
4413
+ require_transform.profile.collectionView.idsScanned(ids.length);
4159
4414
  for (const key of ids) {
4160
- require_scheduler.profile.collectionView.mapped();
4415
+ require_transform.profile.collectionView.mapped();
4161
4416
  evaluation.output.set(key, read.get(key));
4162
4417
  }
4163
4418
  evaluation.output.order(ids);
4164
4419
  } else {
4165
4420
  const previous = state.current();
4166
- const keys = options.candidates ? [...new Set(options.candidates)] : [...new Set([...previous.ids(), ...read.ids()])];
4167
- if (!options.candidates) require_scheduler.profile.collectionView.idsScanned(keys.length);
4421
+ const keys = options.candidates ? [...options.candidates] : [...new Set([...previous.ids(), ...read.ids()])];
4422
+ if (!options.candidates) require_transform.profile.collectionView.idsScanned(keys.length);
4168
4423
  for (const key of keys) if (read.has(key)) {
4169
- require_scheduler.profile.collectionView.mapped();
4424
+ require_transform.profile.collectionView.mapped();
4170
4425
  evaluation.output.set(key, read.get(key));
4171
4426
  } else evaluation.output.remove(key);
4172
4427
  if (options.orderMayChange || !options.candidates) {
4173
4428
  const ids = read.ids();
4174
- if (options.candidates) require_scheduler.profile.collectionView.idsScanned(ids.length);
4429
+ if (options.candidates) require_transform.profile.collectionView.idsScanned(ids.length);
4175
4430
  evaluation.output.order(ids);
4176
4431
  }
4177
4432
  }
@@ -4188,7 +4443,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
4188
4443
  const candidates = /* @__PURE__ */ new Set();
4189
4444
  let cleanup;
4190
4445
  const check = () => {
4191
- if (!scheduler.active || boundary.disposed) throw new require_scheduler.ProjectionDisposedError();
4446
+ if (!scheduler.active || boundary.disposed) throw new require_transform.ProjectionDisposedError();
4192
4447
  if (boundary.fault) throw boundary.fault;
4193
4448
  };
4194
4449
  const state = createCollectionOutput({
@@ -4467,7 +4722,7 @@ const createDocumentSourceRegistry = (scheduler) => {
4467
4722
  const replacement = createDocumentDirty();
4468
4723
  if (pending.reset) markDocumentReplace(replacement, []);
4469
4724
  const ids = () => {
4470
- require_scheduler.assertScope(active);
4725
+ require_transform.assertScope(active);
4471
4726
  if (treeNodes) return node?.kind === "tree" && require_record.isRecord(value) && require_record.isRecord(value.nodes) ? Object.freeze(Object.keys(value.nodes)) : Object.freeze([]);
4472
4727
  if (node?.kind === "map" && require_record.isRecord(value)) return Object.freeze(Object.keys(value));
4473
4728
  if (node?.kind === "table" && require_record.isRecord(value) && Array.isArray(value.ids)) return Object.freeze([...value.ids]);
@@ -4475,7 +4730,7 @@ const createDocumentSourceRegistry = (scheduler) => {
4475
4730
  return Object.freeze([]);
4476
4731
  };
4477
4732
  const has = (key) => {
4478
- require_scheduler.assertScope(active);
4733
+ require_transform.assertScope(active);
4479
4734
  if (treeNodes) return Boolean(node?.kind === "tree" && require_record.isRecord(value) && require_record.isRecord(value.nodes) && Object.hasOwn(value.nodes, key));
4480
4735
  if (node?.kind === "map" && require_record.isRecord(value)) return Object.hasOwn(value, key);
4481
4736
  if (node?.kind === "table" && require_record.isRecord(value) && require_record.isRecord(value.byId)) return Object.hasOwn(value.byId, key);
@@ -4496,7 +4751,7 @@ const createDocumentSourceRegistry = (scheduler) => {
4496
4751
  };
4497
4752
  return Object.freeze({
4498
4753
  get: (key) => {
4499
- require_scheduler.assertScope(active);
4754
+ require_transform.assertScope(active);
4500
4755
  if (!snapshots.has(key)) if (!has(key)) snapshots.set(key, void 0);
4501
4756
  else {
4502
4757
  const current = raw(key);
@@ -4843,7 +5098,7 @@ const materializeInputSource = (scheduler, definition) => {
4843
5098
  }
4844
5099
  if (source.kind !== "collection-input") throw new Error("Invalid projection input source.");
4845
5100
  const values = new Map(source.initial);
4846
- const boundary = createCollectionBoundary(scheduler, "projection collection input", () => require_scheduler.mapRead(values), definition.output.equality, () => void 0);
5101
+ const boundary = createCollectionBoundary(scheduler, "projection collection input", () => require_transform.mapRead(values), definition.output.equality, () => void 0);
4847
5102
  return {
4848
5103
  ...boundary,
4849
5104
  write: {
@@ -4852,21 +5107,24 @@ const materializeInputSource = (scheduler, definition) => {
4852
5107
  scheduler.assertIdle();
4853
5108
  const staged = /* @__PURE__ */ new Map();
4854
5109
  const operations = [];
5110
+ let structural = false;
4855
5111
  let active = true;
4856
5112
  const draft = Object.freeze({
4857
5113
  get: (key) => {
4858
- require_scheduler.assertScope(() => active);
5114
+ require_transform.assertScope(() => active);
4859
5115
  const entry = staged.get(key);
4860
5116
  return entry ? entry.present ? entry.value : void 0 : values.get(key);
4861
5117
  },
4862
5118
  has: (key) => {
4863
- require_scheduler.assertScope(() => active);
5119
+ require_transform.assertScope(() => active);
4864
5120
  const entry = staged.get(key);
4865
5121
  return entry ? entry.present : values.has(key);
4866
5122
  },
4867
5123
  set: (key, value) => {
4868
- require_scheduler.assertScope(() => active);
5124
+ require_transform.assertScope(() => active);
4869
5125
  if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
5126
+ const previous = staged.get(key);
5127
+ structural ||= previous ? !previous.present : !values.has(key);
4870
5128
  staged.set(key, {
4871
5129
  present: true,
4872
5130
  value
@@ -4878,46 +5136,42 @@ const materializeInputSource = (scheduler, definition) => {
4878
5136
  });
4879
5137
  },
4880
5138
  remove: (key) => {
4881
- require_scheduler.assertScope(() => active);
5139
+ require_transform.assertScope(() => active);
4882
5140
  if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
5141
+ const previous = staged.get(key);
5142
+ const present = previous ? previous.present : values.has(key);
5143
+ structural ||= present;
4883
5144
  staged.set(key, { present: false });
4884
- operations.push({
5145
+ if (present) operations.push({
4885
5146
  kind: "remove",
4886
5147
  key
4887
5148
  });
4888
5149
  }
4889
5150
  });
4890
5151
  try {
4891
- require_scheduler.assertSynchronous(run(draft));
5152
+ require_transform.assertSynchronous(run(draft));
4892
5153
  } finally {
4893
5154
  active = false;
4894
5155
  }
5156
+ if (!operations.length) return;
4895
5157
  const candidates = /* @__PURE__ */ new Set();
4896
- let structural = false;
4897
- const accepted = [];
4898
- const overlay = /* @__PURE__ */ new Map();
4899
- for (const operation of operations) {
4900
- const overlayValue = overlay.get(operation.key);
4901
- const present = overlayValue ? overlayValue.present : values.has(operation.key);
4902
- if (operation.kind === "set") {
4903
- const previous = overlayValue ? overlayValue.present ? overlayValue.value : void 0 : values.get(operation.key);
4904
- if (present && source.equality(previous, operation.value)) continue;
4905
- structural ||= !present;
4906
- overlay.set(operation.key, {
4907
- present: true,
4908
- value: operation.value
4909
- });
4910
- } else {
4911
- if (!present) continue;
4912
- structural = true;
4913
- overlay.set(operation.key, { present: false });
4914
- }
4915
- accepted.push(operation);
4916
- candidates.add(operation.key);
5158
+ const retained = /* @__PURE__ */ new Map();
5159
+ for (const [key, entry] of staged) {
5160
+ const existed = values.has(key);
5161
+ if (entry.present) {
5162
+ if (!existed) {
5163
+ candidates.add(key);
5164
+ continue;
5165
+ }
5166
+ const previous = values.get(key);
5167
+ if (source.equality(previous, entry.value)) retained.set(key, previous);
5168
+ else candidates.add(key);
5169
+ } else if (existed) candidates.add(key);
4917
5170
  }
4918
- if (!accepted.length) return;
4919
- for (const operation of accepted) if (operation.kind === "set") values.set(operation.key, operation.value);
5171
+ if (!candidates.size && !structural) return;
5172
+ for (const operation of operations) if (operation.kind === "set") values.set(operation.key, operation.value);
4920
5173
  else values.delete(operation.key);
5174
+ for (const [key, value] of retained) if (values.has(key)) values.set(key, value);
4921
5175
  boundary.mark({
4922
5176
  candidates,
4923
5177
  orderMayChange: structural
@@ -4960,19 +5214,249 @@ const createSourceRegistry = (scheduler) => {
4960
5214
  };
4961
5215
  };
4962
5216
  //#endregion
5217
+ //#region core/src/projection/readable/keyed.ts
5218
+ const sameKeys = (previous, next) => previous.length === next.length && previous.every((key, index) => key === next[index]);
5219
+ /** Runtime-owned keyed consumer family over one materialized collection output. */
5220
+ const createProjectionItems = (source, checkOwner) => {
5221
+ if (source.kind !== "collection") throw new TypeError("Projection items require a keyed collection projection.");
5222
+ let disposed = false;
5223
+ const active = /* @__PURE__ */ new Map();
5224
+ const followers = /* @__PURE__ */ new Map();
5225
+ const waiting = /* @__PURE__ */ new Map();
5226
+ let keysValue = Object.freeze([...source.current().keys()]);
5227
+ let keysRevision = 0;
5228
+ const keyListeners = /* @__PURE__ */ new Set();
5229
+ const check = () => {
5230
+ if (disposed) throw new require_transform.ProjectionDisposedError();
5231
+ checkOwner();
5232
+ };
5233
+ const notifications = [];
5234
+ const notify = (listeners) => {
5235
+ for (const listener of listeners) notifications.push(listener);
5236
+ };
5237
+ const flushNotifications = () => {
5238
+ notifyProjectionListeners(notifications.splice(0), "Projection item listeners failed.");
5239
+ };
5240
+ const unregisterWaiting = (state) => {
5241
+ const states = waiting.get(state.key);
5242
+ states?.delete(state);
5243
+ if (states?.size === 0) waiting.delete(state.key);
5244
+ };
5245
+ const registerWaiting = (state) => {
5246
+ if (state.tracked || state.ended) return;
5247
+ const states = waiting.get(state.key);
5248
+ if (states) states.add(state);
5249
+ else waiting.set(state.key, new Set([state]));
5250
+ };
5251
+ const publishState = (state, value) => {
5252
+ if (state.ended) return;
5253
+ if (Object.is(state.value, value)) {
5254
+ state.value = value;
5255
+ return;
5256
+ }
5257
+ state.value = value;
5258
+ state.revision++;
5259
+ notify(state.listeners);
5260
+ };
5261
+ const attachActive = (state, value) => {
5262
+ if (state.ended) return;
5263
+ unregisterWaiting(state);
5264
+ state.tracked = true;
5265
+ const canonical = active.get(state.key);
5266
+ if (!canonical) active.set(state.key, state);
5267
+ else if (canonical !== state) {
5268
+ const states = followers.get(state.key);
5269
+ if (states) states.add(state);
5270
+ else followers.set(state.key, new Set([state]));
5271
+ }
5272
+ publishState(state, value);
5273
+ };
5274
+ const terminateState = (state) => {
5275
+ if (state.ended) return;
5276
+ unregisterWaiting(state);
5277
+ state.tracked = false;
5278
+ publishState(state, void 0);
5279
+ state.ended = true;
5280
+ };
5281
+ const statesFor = (key) => {
5282
+ const result = [];
5283
+ const canonical = active.get(key);
5284
+ if (canonical) result.push(canonical);
5285
+ const extra = followers.get(key);
5286
+ if (extra) result.push(...extra);
5287
+ return result;
5288
+ };
5289
+ const terminateKey = (key) => {
5290
+ for (const state of statesFor(key)) terminateState(state);
5291
+ active.delete(key);
5292
+ followers.delete(key);
5293
+ };
5294
+ const activateWaiting = (key, value) => {
5295
+ const states = waiting.get(key);
5296
+ if (!states?.size) return;
5297
+ waiting.delete(key);
5298
+ for (const state of states) attachActive(state, value);
5299
+ };
5300
+ const updateKey = (key, value) => {
5301
+ for (const state of statesFor(key)) publishState(state, value);
5302
+ };
5303
+ const updateKeys = (current) => {
5304
+ const next = Object.freeze([...current.keys()]);
5305
+ if (sameKeys(keysValue, next)) return;
5306
+ keysValue = next;
5307
+ keysRevision++;
5308
+ notify(keyListeners);
5309
+ };
5310
+ const createItem = (key, initial) => {
5311
+ const state = {
5312
+ key,
5313
+ listeners: /* @__PURE__ */ new Set(),
5314
+ tracked: initial !== void 0,
5315
+ ended: false,
5316
+ value: initial?.value,
5317
+ revision: 0
5318
+ };
5319
+ const ensureObserved = () => {
5320
+ if (state.ended || state.tracked) return;
5321
+ const current = source.current();
5322
+ if (current.has(key)) attachActive(state, current.get(key));
5323
+ };
5324
+ state.readable = Object.freeze({
5325
+ current: () => {
5326
+ check();
5327
+ ensureObserved();
5328
+ return state.value;
5329
+ },
5330
+ revision: () => {
5331
+ check();
5332
+ ensureObserved();
5333
+ return state.revision;
5334
+ },
5335
+ subscribe: (listener) => {
5336
+ check();
5337
+ ensureObserved();
5338
+ state.listeners.add(listener);
5339
+ if (!state.tracked && !state.ended) registerWaiting(state);
5340
+ return () => {
5341
+ state.listeners.delete(listener);
5342
+ if (!state.listeners.size && !state.tracked) unregisterWaiting(state);
5343
+ };
5344
+ }
5345
+ });
5346
+ return state;
5347
+ };
5348
+ const reconcile = (current) => {
5349
+ for (const key of [...active.keys()]) if (!current.has(key)) terminateKey(key);
5350
+ else updateKey(key, current.get(key));
5351
+ for (const key of [...waiting.keys()]) if (current.has(key)) activateWaiting(key, current.get(key));
5352
+ updateKeys(current);
5353
+ };
5354
+ const onChange = (change) => {
5355
+ check();
5356
+ const current = source.current();
5357
+ if (!change || change.kind === "reset") {
5358
+ reconcile(current);
5359
+ flushNotifications();
5360
+ return;
5361
+ }
5362
+ for (const entry of change.removed) terminateKey(entry.key);
5363
+ for (const entry of change.added) activateWaiting(entry.key, entry.after);
5364
+ for (const entry of change.updated) updateKey(entry.key, entry.after);
5365
+ if (change.added.length || change.removed.length || change.order) updateKeys(current);
5366
+ flushNotifications();
5367
+ };
5368
+ const stop = source.subscribe(onChange);
5369
+ const keys = Object.freeze({
5370
+ current: () => {
5371
+ check();
5372
+ return keysValue;
5373
+ },
5374
+ revision: () => {
5375
+ check();
5376
+ return keysRevision;
5377
+ },
5378
+ subscribe: (listener) => {
5379
+ check();
5380
+ keyListeners.add(listener);
5381
+ return () => keyListeners.delete(listener);
5382
+ }
5383
+ });
5384
+ return {
5385
+ items: Object.freeze({
5386
+ keys,
5387
+ get: (key) => {
5388
+ check();
5389
+ const canonical = active.get(key);
5390
+ if (canonical) return canonical.readable;
5391
+ const pending = waiting.get(key)?.values().next().value;
5392
+ if (pending) return pending.readable;
5393
+ const current = source.current();
5394
+ if (current.has(key)) {
5395
+ const state = createItem(key, {
5396
+ present: true,
5397
+ value: current.get(key)
5398
+ });
5399
+ active.set(key, state);
5400
+ return state.readable;
5401
+ }
5402
+ return createItem(key).readable;
5403
+ }
5404
+ }),
5405
+ dispose() {
5406
+ if (disposed) return;
5407
+ disposed = true;
5408
+ stop();
5409
+ keyListeners.clear();
5410
+ const known = /* @__PURE__ */ new Set();
5411
+ active.forEach((state) => known.add(state));
5412
+ followers.forEach((states) => states.forEach((state) => known.add(state)));
5413
+ waiting.forEach((states) => states.forEach((state) => known.add(state)));
5414
+ known.forEach((state) => state.listeners.clear());
5415
+ active.clear();
5416
+ followers.clear();
5417
+ waiting.clear();
5418
+ notifications.length = 0;
5419
+ }
5420
+ };
5421
+ };
5422
+ //#endregion
4963
5423
  //#region core/src/projection/runtime.ts
4964
5424
  const createProjectionRuntime = (options) => {
4965
- const scheduler = require_scheduler.createScheduler(options?.onError ?? (() => void 0));
5425
+ const scheduler = require_transform.createScheduler(options?.onError ?? (() => void 0));
4966
5426
  const sources = createSourceRegistry(scheduler);
4967
5427
  const materialized = /* @__PURE__ */ new WeakMap();
4968
5428
  const publicViews = /* @__PURE__ */ new WeakMap();
4969
5429
  const scopes = /* @__PURE__ */ new Set();
5430
+ const rootItems = /* @__PURE__ */ new Map();
5431
+ const disposeItemControllers = (registry, failures) => {
5432
+ const controllers = [...registry.values()];
5433
+ registry.clear();
5434
+ for (const controller of controllers) try {
5435
+ controller.dispose();
5436
+ } catch (error) {
5437
+ failures.push(error);
5438
+ }
5439
+ };
5440
+ const disposeScopeConsumers = (state, failures) => {
5441
+ disposeItemControllers(state.items, failures);
5442
+ const subscriptions = [...state.subscriptions];
5443
+ state.subscriptions.clear();
5444
+ for (const unsubscribe of subscriptions) try {
5445
+ unsubscribe();
5446
+ } catch (error) {
5447
+ failures.push(error);
5448
+ }
5449
+ };
5450
+ const throwCleanupFailures = (failures, message) => {
5451
+ if (failures.length === 1) throw failures[0];
5452
+ if (failures.length) throw new AggregateError(failures, message);
5453
+ };
4970
5454
  const assertAccess = (definition, requester) => {
4971
5455
  scheduler.assertActive();
4972
5456
  const owner = definition.owner;
4973
5457
  if (owner === void 0) return;
4974
5458
  if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
4975
- if (!requester?.active) throw new require_scheduler.ProjectionDisposedError();
5459
+ if (!requester?.active) throw new require_transform.ProjectionDisposedError();
4976
5460
  };
4977
5461
  const rememberScoped = (definition, requester) => {
4978
5462
  if (requester && definition.owner === requester) requester.materialized.push(definition);
@@ -4990,7 +5474,7 @@ const createProjectionRuntime = (options) => {
4990
5474
  ...source.write ? { write: source.write } : {}
4991
5475
  });
4992
5476
  } else {
4993
- if (definition.owner === void 0 && definition.dependencies.some((dependency) => require_scheduler.producerOf(dependency).owner !== void 0)) throw new TypeError("A root projection cannot depend on a scoped projection.");
5477
+ if (definition.owner === void 0 && definition.dependencies.some((dependency) => require_transform.producerOf(dependency).owner !== void 0)) throw new TypeError("A root projection cannot depend on a scoped projection.");
4994
5478
  const dependencies = definition.dependencies.map((dependency) => resolveOutput(dependency, requester));
4995
5479
  const processor = createProcessor(scheduler, definition, Object.freeze(dependencies));
4996
5480
  result = Object.freeze({
@@ -5004,14 +5488,14 @@ const createProjectionRuntime = (options) => {
5004
5488
  return result;
5005
5489
  };
5006
5490
  const resolveOutput = (projection, requester) => {
5007
- const ref = require_scheduler.projectionRef(projection);
5491
+ const ref = require_transform.projectionRef(projection);
5008
5492
  const output = materializeProducer(ref.producer, requester).outputs[ref.output];
5009
5493
  if (!output) throw new TypeError("Projection output does not exist.");
5010
5494
  return output;
5011
5495
  };
5012
5496
  const assertOutput = (output) => {
5013
5497
  scheduler.assertActive();
5014
- if (output.owner.disposed) throw new require_scheduler.ProjectionDisposedError();
5498
+ if (output.owner.disposed) throw new require_transform.ProjectionDisposedError();
5015
5499
  if (output.owner.fault) throw output.owner.fault;
5016
5500
  };
5017
5501
  const publicCurrent = (output) => {
@@ -5021,7 +5505,7 @@ const createProjectionRuntime = (options) => {
5021
5505
  const cached = publicViews.get(output);
5022
5506
  if (cached?.revision === revision) return cached.value;
5023
5507
  if (output.kind === "collection") {
5024
- const value = require_scheduler.collectionView(current);
5508
+ const value = require_transform.collectionView(current);
5025
5509
  publicViews.set(output, {
5026
5510
  revision,
5027
5511
  value
@@ -5029,7 +5513,7 @@ const createProjectionRuntime = (options) => {
5029
5513
  return value;
5030
5514
  }
5031
5515
  if (isMapLike(current)) {
5032
- const value = require_scheduler.snapshotCollectionView(require_scheduler.mapRead(current));
5516
+ const value = require_transform.snapshotCollectionView(require_transform.mapRead(current));
5033
5517
  publicViews.set(output, {
5034
5518
  revision,
5035
5519
  value
@@ -5052,6 +5536,19 @@ const createProjectionRuntime = (options) => {
5052
5536
  subscribe: output.subscribe
5053
5537
  });
5054
5538
  };
5539
+ const projectionItems = (projection, requester) => {
5540
+ const output = resolveOutput(projection, requester);
5541
+ if (output.kind !== "collection") throw new TypeError("Projection items require a keyed collection projection.");
5542
+ const registry = requester?.items ?? rootItems;
5543
+ const existing = registry.get(output);
5544
+ if (existing) return existing.items;
5545
+ const controller = createProjectionItems(readableSource(projection, requester), () => {
5546
+ if (requester && !requester.active) throw new require_transform.ProjectionDisposedError();
5547
+ assertOutput(output);
5548
+ });
5549
+ registry.set(output, controller);
5550
+ return controller.items;
5551
+ };
5055
5552
  const makeReadable = (projection, selector, equality, requester) => {
5056
5553
  const source = readableSource(projection, requester);
5057
5554
  return selector ? createSelectorReadable(source, selector, equality) : createDirectReadable(source);
@@ -5060,7 +5557,7 @@ const createProjectionRuntime = (options) => {
5060
5557
  return makeReadable(projection, selector, equality);
5061
5558
  }
5062
5559
  const inputWrite = (target, requester) => {
5063
- const ref = require_scheduler.projectionRef(target);
5560
+ const ref = require_transform.projectionRef(target);
5064
5561
  if (ref.output !== 0 || ref.producer.kind !== "source") throw new TypeError("Projection is not an input.");
5065
5562
  if (ref.producer.source.kind !== "value-input" && ref.producer.source.kind !== "collection-input") throw new TypeError("Projection is not an input.");
5066
5563
  const write = materializeProducer(ref.producer, requester).write;
@@ -5085,13 +5582,7 @@ const createProjectionRuntime = (options) => {
5085
5582
  scheduler.assertIdle();
5086
5583
  state.active = false;
5087
5584
  const failures = [];
5088
- const subscriptions = [...state.subscriptions];
5089
- state.subscriptions.clear();
5090
- for (const unsubscribe of subscriptions) try {
5091
- unsubscribe();
5092
- } catch (error) {
5093
- failures.push(error);
5094
- }
5585
+ disposeScopeConsumers(state, failures);
5095
5586
  for (let index = state.materialized.length - 1; index >= 0; index--) {
5096
5587
  const definition = state.materialized[index];
5097
5588
  const current = materialized.get(definition);
@@ -5106,25 +5597,26 @@ const createProjectionRuntime = (options) => {
5106
5597
  }
5107
5598
  state.materialized.length = 0;
5108
5599
  scopes.delete(state);
5109
- if (failures.length) throw failures[0];
5600
+ throwCleanupFailures(failures, "Projection scope cleanup failed.");
5110
5601
  };
5111
5602
  const createScope = () => {
5112
5603
  scheduler.assertIdle();
5113
5604
  const state = {
5114
5605
  active: true,
5115
5606
  materialized: [],
5116
- subscriptions: /* @__PURE__ */ new Set()
5607
+ subscriptions: /* @__PURE__ */ new Set(),
5608
+ items: /* @__PURE__ */ new Map()
5117
5609
  };
5118
5610
  scopes.add(state);
5119
5611
  const assertActive = () => {
5120
- if (!state.active) throw new require_scheduler.ProjectionDisposedError();
5612
+ if (!state.active) throw new require_transform.ProjectionDisposedError();
5121
5613
  scheduler.assertActive();
5122
5614
  };
5123
5615
  function own(target) {
5124
5616
  assertActive();
5125
5617
  const projections = [];
5126
5618
  const collect = (value) => {
5127
- if (require_scheduler.isProjection(value)) {
5619
+ if (require_transform.isProjection(value)) {
5128
5620
  projections.push(value);
5129
5621
  return;
5130
5622
  }
@@ -5134,13 +5626,13 @@ const createProjectionRuntime = (options) => {
5134
5626
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
5135
5627
  if (!descriptor?.enumerable || !("value" in descriptor)) throw new TypeError("Projection ownership tree must use enumerable data properties.");
5136
5628
  const child = descriptor.value;
5137
- if (!require_scheduler.isProjection(child) && !require_record.isPlainObject(child)) throw new TypeError("Projection ownership tree leaves must be projections.");
5629
+ if (!require_transform.isProjection(child) && !require_record.isPlainObject(child)) throw new TypeError("Projection ownership tree leaves must be projections.");
5138
5630
  collect(child);
5139
5631
  }
5140
5632
  };
5141
5633
  collect(target);
5142
5634
  if (!projections.length) throw new TypeError("Projection ownership tree must contain a projection.");
5143
- require_scheduler.ownProjections(projections, state);
5635
+ require_transform.ownProjections(projections, state);
5144
5636
  return target;
5145
5637
  }
5146
5638
  function scopeSelect(projection, selector, equality) {
@@ -5185,6 +5677,10 @@ const createProjectionRuntime = (options) => {
5185
5677
  return readProjection(projection, state);
5186
5678
  },
5187
5679
  select: scopeSelect,
5680
+ items: (projection) => {
5681
+ assertActive();
5682
+ return projectionItems(projection, state);
5683
+ },
5188
5684
  update: scopeUpdate,
5189
5685
  batch: scopeBatch,
5190
5686
  dispose: () => disposeScope(state)
@@ -5193,6 +5689,7 @@ const createProjectionRuntime = (options) => {
5193
5689
  return Object.freeze({
5194
5690
  read: readProjection,
5195
5691
  select: selectProjection,
5692
+ items: projectionItems,
5196
5693
  update,
5197
5694
  batch,
5198
5695
  scope: createScope,
@@ -5202,16 +5699,11 @@ const createProjectionRuntime = (options) => {
5202
5699
  const failures = [];
5203
5700
  for (const scope of scopes) {
5204
5701
  scope.active = false;
5205
- const subscriptions = [...scope.subscriptions];
5206
- scope.subscriptions.clear();
5207
- for (const unsubscribe of subscriptions) try {
5208
- unsubscribe();
5209
- } catch (error) {
5210
- failures.push(error);
5211
- }
5702
+ disposeScopeConsumers(scope, failures);
5212
5703
  scope.materialized.length = 0;
5213
5704
  }
5214
5705
  scopes.clear();
5706
+ disposeItemControllers(rootItems, failures);
5215
5707
  try {
5216
5708
  sources.dispose();
5217
5709
  } catch (error) {
@@ -5222,8 +5714,7 @@ const createProjectionRuntime = (options) => {
5222
5714
  } catch (error) {
5223
5715
  failures.push(error);
5224
5716
  }
5225
- if (failures.length === 1) throw failures[0];
5226
- if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
5717
+ throwCleanupFailures(failures, "Projection cleanup failed.");
5227
5718
  }
5228
5719
  });
5229
5720
  };
@@ -5231,8 +5722,8 @@ const createProjectionRuntime = (options) => {
5231
5722
  exports.DocumentDisposedError = DocumentDisposedError;
5232
5723
  exports.DocumentReentrancyError = DocumentReentrancyError;
5233
5724
  exports.ParseError = ParseError;
5234
- exports.ProjectionDisposedError = require_scheduler.ProjectionDisposedError;
5235
- exports.ProjectionError = require_scheduler.ProjectionError;
5725
+ exports.ProjectionDisposedError = require_transform.ProjectionDisposedError;
5726
+ exports.ProjectionError = require_transform.ProjectionError;
5236
5727
  exports.TransactionRejected = TransactionRejected;
5237
5728
  exports.createDocument = createDocument;
5238
5729
  exports.createProjectionRuntime = createProjectionRuntime;