doxum 0.1.27 → 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 (37) hide show
  1. package/README.md +66 -3
  2. package/dist/advanced.cjs +40 -14
  3. package/dist/advanced.cjs.map +1 -1
  4. package/dist/advanced.d.cts +27 -2
  5. package/dist/advanced.d.ts +27 -2
  6. package/dist/advanced.js +27 -1
  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 +663 -344
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +23 -32
  15. package/dist/index.d.ts +23 -32
  16. package/dist/index.js +549 -230
  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/{scheduler-vzf3OcjT.js → transform-B4T3AcJr.js} +356 -11
  21. package/dist/transform-B4T3AcJr.js.map +1 -0
  22. package/dist/{scheduler-k1ZGR1PG.cjs → transform-DUleAsG0.cjs} +393 -12
  23. package/dist/transform-DUleAsG0.cjs.map +1 -0
  24. package/package.json +1 -1
  25. package/skills/doxum-runtime/SKILL.md +11 -5
  26. package/skills/doxum-runtime/references/api.en.md +40 -21
  27. package/skills/doxum-runtime/references/api.zh-CN.md +21 -2
  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 +47 -3
  35. package/skills/doxum-runtime/references/projections.zh-CN.md +45 -3
  36. package/dist/scheduler-k1ZGR1PG.cjs.map +0 -1
  37. package/dist/scheduler-vzf3OcjT.js.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-k1ZGR1PG.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
@@ -2736,41 +2736,6 @@ const isExternalSource = (source) => {
2736
2736
  };
2737
2737
  //#endregion
2738
2738
  //#region core/src/projection/derive/keyed.ts
2739
- const outputKind = (projection) => {
2740
- return require_scheduler.outputDefinitionOf(projection).kind;
2741
- };
2742
- const assertKeyedProjection = (projection, role) => {
2743
- if (outputKind(projection) !== "collection") throw new TypeError(`${role} must be a keyed collection projection.`);
2744
- };
2745
- const isKeyedDependency = (value) => {
2746
- if (!value || typeof value !== "object" || require_scheduler.isProjection(value)) return false;
2747
- const candidate = value;
2748
- return require_scheduler.isProjection(candidate.source) && typeof candidate.key === "function";
2749
- };
2750
- const bindKey = (index, outputKey, sourceKey) => {
2751
- const previous = index.forward.get(outputKey);
2752
- if (previous === sourceKey) return;
2753
- if (previous !== void 0) {
2754
- const dependents = index.reverse.get(previous);
2755
- dependents?.delete(outputKey);
2756
- if (dependents?.size === 0) index.reverse.delete(previous);
2757
- index.forward.delete(outputKey);
2758
- }
2759
- if (sourceKey === void 0) return;
2760
- index.forward.set(outputKey, sourceKey);
2761
- const dependents = index.reverse.get(sourceKey);
2762
- if (dependents) dependents.add(outputKey);
2763
- else index.reverse.set(sourceKey, new Set([outputKey]));
2764
- };
2765
- const unbindKey = (indexes, outputKey) => {
2766
- for (const index of indexes) if (index) bindKey(index, outputKey, void 0);
2767
- };
2768
- const createBindingIndex = () => ({
2769
- forward: /* @__PURE__ */ new Map(),
2770
- reverse: /* @__PURE__ */ new Map()
2771
- });
2772
- const emptyKeyedDependencies = Object.freeze(Object.create(null));
2773
- const absent = Symbol("keyed-transform-absent");
2774
2739
  const compileOrderedKeys = (value, role) => {
2775
2740
  if (!Array.isArray(value)) throw new TypeError(`${role} must be an array of string keys.`);
2776
2741
  const seen = /* @__PURE__ */ new Set();
@@ -2784,164 +2749,22 @@ const compileOrderedKeys = (value, role) => {
2784
2749
  requested: seen
2785
2750
  };
2786
2751
  };
2787
- const createKeyedTransform = (source, dependencySpecs, select, equality, name) => {
2788
- assertKeyedProjection(source, "Keyed derive source");
2789
- const dependencies = [source];
2790
- const dependencyNames = [];
2791
- const keyed = [];
2792
- if (dependencySpecs !== void 0) {
2793
- if (!require_record.isPlainObject(dependencySpecs) || require_scheduler.isProjection(dependencySpecs)) throw new TypeError("Keyed derive dependencies must be a plain object.");
2794
- for (const name of Reflect.ownKeys(dependencySpecs)) {
2795
- if (typeof name !== "string") throw new TypeError("Keyed derive dependency names must be strings.");
2796
- const descriptor = Object.getOwnPropertyDescriptor(dependencySpecs, name);
2797
- if (!descriptor?.enumerable || !("value" in descriptor)) throw new TypeError("Keyed derive dependencies must be enumerable data properties.");
2798
- const dependency = descriptor.value;
2799
- dependencyNames.push(name);
2800
- if (require_scheduler.isProjection(dependency)) {
2801
- dependencies.push(dependency);
2802
- keyed.push(void 0);
2803
- continue;
2804
- }
2805
- if (!isKeyedDependency(dependency)) throw new TypeError("Keyed derive dependencies must be projections or keyed lookups.");
2806
- assertKeyedProjection(dependency.source, "Dynamic keyed dependency source");
2807
- dependencies.push(dependency.source);
2808
- keyed.push(dependency);
2809
- }
2810
- }
2811
- const [projection] = require_scheduler.defineProcessor({
2812
- dependencies,
2813
- outputs: [{
2814
- kind: "collection",
2815
- equality: equality ?? Object.is
2816
- }],
2817
- create: () => {
2818
- const bindings = keyed.map((dependency) => dependency ? createBindingIndex() : void 0);
2819
- const globalValues = keyed.map(() => void 0);
2820
- const revisions = [];
2821
- const clearBindings = () => {
2822
- for (const index of bindings) {
2823
- index?.forward.clear();
2824
- index?.reverse.clear();
2825
- }
2826
- };
2827
- const rememberRevisions = (sources) => {
2828
- revisions.length = sources.length;
2829
- for (let index = 0; index < sources.length; index++) revisions[index] = sources[index].revision;
2830
- };
2831
- return {
2832
- evaluate: (evaluation) => {
2833
- const driver = evaluation.sources[0];
2834
- const output = evaluation.outputs[0];
2835
- if (driver.kind !== "collection" || output.kind !== "collection") throw new Error("Keyed derive requires collection input and output.");
2836
- for (let index = 0; index < keyed.length; index++) {
2837
- if (keyed[index]) continue;
2838
- const dependencySource = evaluation.sources[index + 1];
2839
- if (evaluation.reset || revisions[index + 1] !== dependencySource.revision) globalValues[index] = require_scheduler.snapshotDependencyValue(dependencySource);
2840
- }
2841
- const project = (key) => {
2842
- if (!driver.read.has(key)) return;
2843
- const value = driver.read.get(key);
2844
- let dependencyValues = emptyKeyedDependencies;
2845
- if (keyed.length) {
2846
- const values = Object.create(null);
2847
- for (let index = 0; index < keyed.length; index++) {
2848
- const dependency = keyed[index];
2849
- if (!dependency) {
2850
- values[dependencyNames[index]] = globalValues[index];
2851
- continue;
2852
- }
2853
- const sourceContext = evaluation.sources[index + 1];
2854
- if (sourceContext.kind !== "collection") throw new TypeError("Dynamic keyed dependency resolved to a non-collection source.");
2855
- const selectedKey = dependency.key(value, key);
2856
- require_scheduler.assertSynchronous(selectedKey);
2857
- if (selectedKey !== void 0 && typeof selectedKey !== "string") throw new TypeError("Dynamic keyed dependency keys must be strings or undefined.");
2858
- bindKey(bindings[index], key, selectedKey);
2859
- values[dependencyNames[index]] = selectedKey === void 0 ? void 0 : sourceContext.read.get(selectedKey);
2860
- }
2861
- dependencyValues = Object.freeze(values);
2862
- }
2863
- const next = select(value, key, dependencyValues);
2864
- require_scheduler.assertSynchronous(next);
2865
- if (next === absent) output.output.remove(key);
2866
- else output.output.set(key, next);
2867
- };
2868
- if (evaluation.reset) {
2869
- clearBindings();
2870
- const ids = driver.read.ids();
2871
- for (const key of ids) project(key);
2872
- output.output.order(ids.filter((key) => output.next.has(key)));
2873
- rememberRevisions(evaluation.sources);
2874
- return;
2875
- }
2876
- const dirty = /* @__PURE__ */ new Set();
2877
- let all = false;
2878
- let updateOrder = false;
2879
- let membershipChanged = false;
2880
- if (revisions[0] !== driver.revision) {
2881
- const change = driver.change;
2882
- if (!change || change.kind === "reset") {
2883
- all = true;
2884
- updateOrder = true;
2885
- } else {
2886
- for (const entry of change.removed) {
2887
- membershipChanged ||= output.previous.has(entry.key);
2888
- output.output.remove(entry.key);
2889
- unbindKey(bindings, entry.key);
2890
- dirty.delete(entry.key);
2891
- }
2892
- for (const entry of change.added) dirty.add(entry.key);
2893
- for (const entry of change.updated) dirty.add(entry.key);
2894
- updateOrder = Boolean(change.order);
2895
- }
2896
- }
2897
- for (let index = 0; index < keyed.length; index++) {
2898
- const sourceContext = evaluation.sources[index + 1];
2899
- if (revisions[index + 1] === sourceContext.revision) continue;
2900
- if (!keyed[index]) {
2901
- all = true;
2902
- continue;
2903
- }
2904
- if (sourceContext.kind !== "collection" || !sourceContext.change) {
2905
- all = true;
2906
- continue;
2907
- }
2908
- if (sourceContext.change.kind === "reset") {
2909
- all = true;
2910
- continue;
2911
- }
2912
- const reverse = bindings[index].reverse;
2913
- for (const sourceKey of require_scheduler.collectionChange.keys(sourceContext.change)) for (const outputKey of reverse.get(sourceKey) ?? []) dirty.add(outputKey);
2914
- }
2915
- if (all) for (const key of driver.read.ids()) dirty.add(key);
2916
- for (const key of dirty) {
2917
- if (!driver.read.has(key)) continue;
2918
- const beforePresent = output.previous.has(key);
2919
- project(key);
2920
- membershipChanged ||= beforePresent !== output.next.has(key);
2921
- }
2922
- if (updateOrder || membershipChanged) output.output.order(driver.read.ids().filter((key) => output.next.has(key)));
2923
- rememberRevisions(evaluation.sources);
2924
- },
2925
- release: () => {
2926
- clearBindings();
2927
- revisions.length = 0;
2928
- }
2929
- };
2930
- },
2931
- name
2932
- });
2933
- return projection;
2934
- };
2935
2752
  function createKeyedDerive(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
2936
2753
  const dependencySpecs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
2937
2754
  const select = dependencySpecs === void 0 ? dependenciesOrSelect : selectOrEquality;
2938
2755
  const equality = dependencySpecs === void 0 ? selectOrEquality : maybeEquality;
2939
2756
  if (typeof select !== "function") throw new TypeError("Keyed derive requires a selector.");
2940
- return createKeyedTransform(source, dependencySpecs, (value, key, dependencies) => dependencySpecs === void 0 ? select(value, key) : select(value, key, dependencies), equality, "derive.keyed");
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
+ });
2941
2764
  }
2942
2765
  const createKeyedKeys = (source) => {
2943
- assertKeyedProjection(source, "Keyed keys source");
2944
- const [projection] = require_scheduler.defineProcessor({
2766
+ require_transform.assertKeyedProjection(source, "Keyed keys source");
2767
+ const [projection] = require_transform.defineProcessor({
2945
2768
  dependencies: [source],
2946
2769
  outputs: [{
2947
2770
  kind: "value",
@@ -2951,15 +2774,15 @@ const createKeyedKeys = (source) => {
2951
2774
  const driver = evaluation.sources[0];
2952
2775
  const output = evaluation.outputs[0];
2953
2776
  if (driver.kind !== "collection" || output.kind !== "value") throw new Error("derive.keyed.keys requires collection input and value output.");
2954
- if (evaluation.reset || !driver.change || require_scheduler.collectionHasStructuralChange(driver.change)) output.output.set(driver.read.ids());
2777
+ if (evaluation.reset || !driver.change || require_transform.collectionHasStructuralChange(driver.change)) output.output.set(driver.read.ids());
2955
2778
  } }),
2956
2779
  name: "derive.keyed.keys"
2957
2780
  });
2958
2781
  return projection;
2959
2782
  };
2960
- const createKeyedValues = (source) => {
2961
- assertKeyedProjection(source, "Keyed values source");
2962
- const [projection] = require_scheduler.defineProcessor({
2783
+ const createOrderedSnapshot = (source, name, make) => {
2784
+ require_transform.assertKeyedProjection(source, `${name} source`);
2785
+ const [projection] = require_transform.defineProcessor({
2963
2786
  dependencies: [source],
2964
2787
  outputs: [{
2965
2788
  kind: "value",
@@ -2969,33 +2792,32 @@ const createKeyedValues = (source) => {
2969
2792
  let indexes = /* @__PURE__ */ new Map();
2970
2793
  const rebuild = (driver) => {
2971
2794
  const nextIndexes = /* @__PURE__ */ new Map();
2972
- const ids = driver.read.ids();
2973
- const values = Object.freeze(ids.map((key, index) => {
2795
+ const result = Object.freeze(driver.read.ids().map((key, index) => {
2974
2796
  nextIndexes.set(key, index);
2975
- return driver.read.get(key);
2797
+ return make(key, driver.read.get(key));
2976
2798
  }));
2977
2799
  indexes = nextIndexes;
2978
- return values;
2800
+ return result;
2979
2801
  };
2980
2802
  const rebuildIncremental = (driver, change, previous) => {
2981
2803
  const changedValues = /* @__PURE__ */ new Map();
2982
2804
  for (const entry of change.added) changedValues.set(entry.key, entry.after);
2983
2805
  for (const entry of change.updated) changedValues.set(entry.key, entry.after);
2984
2806
  const nextIndexes = /* @__PURE__ */ new Map();
2985
- const values = Object.freeze(driver.read.ids().map((key, index) => {
2807
+ const result = Object.freeze(driver.read.ids().map((key, index) => {
2986
2808
  nextIndexes.set(key, index);
2987
- if (changedValues.has(key)) return changedValues.get(key);
2809
+ if (changedValues.has(key)) return make(key, changedValues.get(key));
2988
2810
  const previousIndex = indexes.get(key);
2989
- return previousIndex === void 0 ? driver.read.get(key) : previous[previousIndex];
2811
+ return previousIndex === void 0 ? make(key, driver.read.get(key)) : previous[previousIndex];
2990
2812
  }));
2991
2813
  indexes = nextIndexes;
2992
- return values;
2814
+ return result;
2993
2815
  };
2994
2816
  return {
2995
2817
  evaluate: (evaluation) => {
2996
2818
  const driver = evaluation.sources[0];
2997
2819
  const output = evaluation.outputs[0];
2998
- if (driver.kind !== "collection" || output.kind !== "value") throw new Error("derive.keyed.values requires collection input and value output.");
2820
+ if (driver.kind !== "collection" || output.kind !== "value") throw new Error(`${name} requires collection input and value output.`);
2999
2821
  const change = driver.change;
3000
2822
  if (evaluation.reset || !change || change.kind === "reset") {
3001
2823
  output.output.set(rebuild(driver));
@@ -3006,7 +2828,7 @@ const createKeyedValues = (source) => {
3006
2828
  output.output.set(rebuild(driver));
3007
2829
  return;
3008
2830
  }
3009
- if (require_scheduler.collectionHasStructuralChange(change)) {
2831
+ if (require_transform.collectionHasStructuralChange(change)) {
3010
2832
  output.output.set(rebuildIncremental(driver, change, previous));
3011
2833
  return;
3012
2834
  }
@@ -3018,14 +2840,59 @@ const createKeyedValues = (source) => {
3018
2840
  output.output.set(rebuild(driver));
3019
2841
  return;
3020
2842
  }
3021
- next[index] = entry.after;
2843
+ next[index] = make(entry.key, entry.after);
3022
2844
  }
3023
2845
  output.output.set(Object.freeze(next));
3024
2846
  },
3025
2847
  release: () => indexes.clear()
3026
2848
  };
3027
2849
  },
3028
- name: "derive.keyed.values"
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"
3029
2896
  });
3030
2897
  return projection;
3031
2898
  };
@@ -3033,28 +2900,40 @@ function createKeyedFilter(source, dependenciesOrPredicate, maybePredicate) {
3033
2900
  const dependencySpecs = typeof dependenciesOrPredicate === "function" ? void 0 : dependenciesOrPredicate;
3034
2901
  const predicate = dependencySpecs === void 0 ? dependenciesOrPredicate : maybePredicate;
3035
2902
  if (typeof predicate !== "function") throw new TypeError("Keyed filter requires a predicate.");
3036
- return createKeyedTransform(source, dependencySpecs, (value, key, dependencies) => {
3037
- const included = dependencySpecs === void 0 ? predicate(value, key) : predicate(value, key, dependencies);
3038
- require_scheduler.assertSynchronous(included);
3039
- if (typeof included !== "boolean") throw new TypeError("Keyed filter predicate must return boolean.");
3040
- return included ? value : absent;
3041
- }, Object.is, "derive.keyed.filter");
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
+ });
3042
2915
  }
3043
2916
  function createKeyedCompact(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
3044
2917
  const dependencySpecs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
3045
2918
  const select = dependencySpecs === void 0 ? dependenciesOrSelect : selectOrEquality;
3046
2919
  const equality = dependencySpecs === void 0 ? selectOrEquality : maybeEquality;
3047
2920
  if (typeof select !== "function") throw new TypeError("Keyed compact requires a selector.");
3048
- return createKeyedTransform(source, dependencySpecs, (value, key, dependencies) => {
3049
- const next = dependencySpecs === void 0 ? select(value, key) : select(value, key, dependencies);
3050
- require_scheduler.assertSynchronous(next);
3051
- return next === void 0 ? absent : next;
3052
- }, equality, "derive.keyed.compact");
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;
2930
+ }
2931
+ });
3053
2932
  }
3054
2933
  const createKeyedSubset = (source, orderedKeys) => {
3055
- assertKeyedProjection(source, "Keyed subset source");
3056
- const keyProjection = require_scheduler.isProjection(orderedKeys) ? orderedKeys : void 0;
3057
- if (keyProjection && outputKind(keyProjection) !== "value") throw new TypeError("Keyed subset ordered keys must be a value projection.");
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.");
3058
2937
  const staticSelection = keyProjection ? void 0 : (() => {
3059
2938
  const compiled = compileOrderedKeys(orderedKeys, "Keyed subset ordered keys");
3060
2939
  return {
@@ -3062,7 +2941,7 @@ const createKeyedSubset = (source, orderedKeys) => {
3062
2941
  requested: compiled.requested
3063
2942
  };
3064
2943
  })();
3065
- const [projection] = require_scheduler.defineProcessor({
2944
+ const [projection] = require_transform.defineProcessor({
3066
2945
  dependencies: keyProjection ? [source, keyProjection] : [source],
3067
2946
  outputs: [{
3068
2947
  kind: "collection",
@@ -3146,9 +3025,198 @@ const createKeyedSubset = (source, orderedKeys) => {
3146
3025
  });
3147
3026
  return projection;
3148
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);
3038
+ }
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,
3049
+ outputs: [{
3050
+ kind: "collection",
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
+ }
3056
+ }],
3057
+ create: () => {
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;
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);
3129
+ };
3130
+ return {
3131
+ evaluate: (evaluation) => {
3132
+ const driver = evaluation.sources[0];
3133
+ const output = evaluation.outputs[0];
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);
3136
+ if (evaluation.reset) {
3137
+ rebuild(driver, evaluation.sources, output);
3138
+ dependencies.remember(evaluation.sources);
3139
+ return;
3140
+ }
3141
+ const dirty = /* @__PURE__ */ new Set();
3142
+ const affected = /* @__PURE__ */ new Set();
3143
+ let all = false;
3144
+ let structural = false;
3145
+ let orderDirty = false;
3146
+ if (dependencies.driverChanged(driver)) {
3147
+ const change = driver.change;
3148
+ if (!change || change.kind === "reset") {
3149
+ rebuild(driver, evaluation.sources, output);
3150
+ dependencies.remember(evaluation.sources);
3151
+ return;
3152
+ } else {
3153
+ for (const entry of change.removed) {
3154
+ orderDirty ||= removeSourceKey(entry.key, affected);
3155
+ dependencies.remove(entry.key);
3156
+ dirty.delete(entry.key);
3157
+ }
3158
+ for (const entry of change.added) dirty.add(entry.key);
3159
+ for (const entry of change.updated) dirty.add(entry.key);
3160
+ structural = Boolean(change.added.length || change.removed.length || change.order);
3161
+ orderDirty ||= structural;
3162
+ if (structural) refreshOrderIndex(driver);
3163
+ }
3164
+ }
3165
+ all ||= dependencies.collectInvalidated(evaluation.sources, dirty);
3166
+ if (all) for (const key of driver.read.ids()) dirty.add(key);
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);
3171
+ },
3172
+ release: () => {
3173
+ relation.clear();
3174
+ orderIndex.clear();
3175
+ groupRanks.clear();
3176
+ dependencies.release();
3177
+ }
3178
+ };
3179
+ },
3180
+ name: "derive.keyed.groupBy"
3181
+ });
3182
+ return projection;
3183
+ }
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"
3210
+ });
3211
+ return projection;
3212
+ };
3149
3213
  const keyedDerive = Object.assign(createKeyedDerive, {
3150
3214
  keys: createKeyedKeys,
3151
3215
  values: createKeyedValues,
3216
+ entries: createKeyedEntries,
3217
+ get: createKeyedGet,
3218
+ groupBy: createKeyedGroupBy,
3219
+ singleton: createKeyedSingleton,
3152
3220
  subset: createKeyedSubset,
3153
3221
  filter: createKeyedFilter,
3154
3222
  compact: createKeyedCompact
@@ -3156,10 +3224,10 @@ const keyedDerive = Object.assign(createKeyedDerive, {
3156
3224
  //#endregion
3157
3225
  //#region core/src/projection/derive.ts
3158
3226
  function createValueDerive(dependencies, compute, equality = Object.is) {
3159
- const compiled = require_scheduler.compileProjectionDependencies(dependencies, "Derive");
3227
+ const compiled = require_transform.compileProjectionDependencies(dependencies, "Derive");
3160
3228
  const names = compiled.names;
3161
3229
  const ordered = compiled.projections;
3162
- const [projection] = require_scheduler.defineProcessor({
3230
+ const [projection] = require_transform.defineProcessor({
3163
3231
  dependencies: ordered,
3164
3232
  outputs: [{
3165
3233
  kind: "value",
@@ -3169,9 +3237,9 @@ function createValueDerive(dependencies, compute, equality = Object.is) {
3169
3237
  const output = evaluation.outputs[0];
3170
3238
  if (output.kind !== "value") throw new Error("derive requires a value output.");
3171
3239
  const values = Object.create(null);
3172
- for (let index = 0; index < names.length; index++) values[names[index]] = require_scheduler.snapshotDependencyValue(evaluation.sources[index]);
3240
+ for (let index = 0; index < names.length; index++) values[names[index]] = require_transform.snapshotDependencyValue(evaluation.sources[index]);
3173
3241
  const next = compute(Object.freeze(values));
3174
- require_scheduler.assertSynchronous(next);
3242
+ require_transform.assertSynchronous(next);
3175
3243
  output.output.set(next);
3176
3244
  } })
3177
3245
  });
@@ -3182,7 +3250,7 @@ const derive = Object.assign(createValueDerive, { keyed: keyedDerive });
3182
3250
  //#region core/src/projection/collection/index.ts
3183
3251
  const height = (node) => node?.height ?? 0;
3184
3252
  const node = (key, value, left, right) => {
3185
- require_scheduler.profile.collectionIndex.node();
3253
+ require_transform.profile.collectionIndex.node();
3186
3254
  return Object.freeze({
3187
3255
  key,
3188
3256
  value,
@@ -3243,7 +3311,7 @@ var PersistentKeyedIndex = class PersistentKeyedIndex {
3243
3311
  }
3244
3312
  static from(entries) {
3245
3313
  const sorted = [...entries].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
3246
- require_scheduler.profile.collectionIndex.build(sorted.length);
3314
+ require_transform.profile.collectionIndex.build(sorted.length);
3247
3315
  return new PersistentKeyedIndex(fromSorted(sorted));
3248
3316
  }
3249
3317
  get(key) {
@@ -3348,15 +3416,15 @@ const createCollectionOutput = (binding) => {
3348
3416
  reset = initialize;
3349
3417
  const read = (next) => ({
3350
3418
  get: (key) => {
3351
- require_scheduler.assertScope(active);
3419
+ require_transform.assertScope(active);
3352
3420
  return next ? getNext(key) : published.get(key);
3353
3421
  },
3354
3422
  has: (key) => {
3355
- require_scheduler.assertScope(active);
3423
+ require_transform.assertScope(active);
3356
3424
  return next ? hasNext(key) : published.has(key);
3357
3425
  },
3358
3426
  ids: () => {
3359
- require_scheduler.assertScope(active);
3427
+ require_transform.assertScope(active);
3360
3428
  return next ? Object.freeze(explicitOrder ? [...explicitOrder] : [...deriveIds()]) : published.ids();
3361
3429
  }
3362
3430
  });
@@ -3365,7 +3433,7 @@ const createCollectionOutput = (binding) => {
3365
3433
  next: read(true),
3366
3434
  output: {
3367
3435
  set: (key, value) => {
3368
- require_scheduler.assertScope(active);
3436
+ require_transform.assertScope(active);
3369
3437
  if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
3370
3438
  staged.set(key, {
3371
3439
  present: true,
@@ -3373,11 +3441,11 @@ const createCollectionOutput = (binding) => {
3373
3441
  });
3374
3442
  },
3375
3443
  remove: (key) => {
3376
- require_scheduler.assertScope(active);
3444
+ require_transform.assertScope(active);
3377
3445
  staged.set(key, { present: false });
3378
3446
  },
3379
3447
  order: (order) => {
3380
- require_scheduler.assertScope(active);
3448
+ require_transform.assertScope(active);
3381
3449
  explicitOrder = Object.freeze([...order]);
3382
3450
  }
3383
3451
  }
@@ -3387,7 +3455,7 @@ const createCollectionOutput = (binding) => {
3387
3455
  if (cleared) {
3388
3456
  for (const key of published.keys()) if (!staged.has(key)) staged.set(key, { present: false });
3389
3457
  }
3390
- require_scheduler.profile.projection("touchedKeys", staged.size);
3458
+ require_transform.profile.projection("touchedKeys", staged.size);
3391
3459
  const added = /* @__PURE__ */ new Set();
3392
3460
  const removed = /* @__PURE__ */ new Set();
3393
3461
  const addedEntries = [];
@@ -3427,8 +3495,8 @@ const createCollectionOutput = (binding) => {
3427
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.");
3428
3496
  nextIds = ids.length === order.length && ids.every((key, index) => key === order[index]) ? ids : Object.freeze([...order]);
3429
3497
  } else nextIds = ids;
3430
- require_scheduler.profile.projection("changedKeys", addedEntries.length + updatedEntries.length + removedEntries.length);
3431
- change = nextReset ? Object.freeze({ kind: "reset" }) : require_scheduler.createCollectionChange({
3498
+ require_transform.profile.projection("changedKeys", addedEntries.length + updatedEntries.length + removedEntries.length);
3499
+ change = nextReset ? Object.freeze({ kind: "reset" }) : require_transform.createCollectionChange({
3432
3500
  added: addedEntries,
3433
3501
  updated: updatedEntries,
3434
3502
  removed: removedEntries,
@@ -3442,15 +3510,15 @@ const createCollectionOutput = (binding) => {
3442
3510
  kind: "collection",
3443
3511
  read: {
3444
3512
  get: (key) => {
3445
- require_scheduler.assertScope(active);
3513
+ require_transform.assertScope(active);
3446
3514
  return getNext(key);
3447
3515
  },
3448
3516
  has: (key) => {
3449
- require_scheduler.assertScope(active);
3517
+ require_transform.assertScope(active);
3450
3518
  return hasNext(key);
3451
3519
  },
3452
3520
  ids: () => {
3453
- require_scheduler.assertScope(active);
3521
+ require_transform.assertScope(active);
3454
3522
  return nextIds;
3455
3523
  }
3456
3524
  },
@@ -3476,7 +3544,7 @@ const createCollectionOutput = (binding) => {
3476
3544
  initialized = true;
3477
3545
  },
3478
3546
  emit: (call) => {
3479
- Array.from(listeners).forEach((listener) => call(listener));
3547
+ Array.from(listeners).forEach((listener) => call(listener, change));
3480
3548
  },
3481
3549
  hasConsumers: () => consumers.size > 0,
3482
3550
  forEachConsumer: (run) => consumers.forEach(run),
@@ -3532,12 +3600,12 @@ const createValueOutput = (binding) => {
3532
3600
  return {
3533
3601
  previous: initialized ? value : void 0,
3534
3602
  next: () => {
3535
- require_scheduler.assertScope(active);
3603
+ require_transform.assertScope(active);
3536
3604
  if (staged !== unset) return staged;
3537
3605
  return initialize ? void 0 : initialized ? value : void 0;
3538
3606
  },
3539
3607
  output: Object.freeze({ set: (candidate) => {
3540
- require_scheduler.assertScope(active);
3608
+ require_transform.assertScope(active);
3541
3609
  staged = candidate;
3542
3610
  } })
3543
3611
  };
@@ -3564,7 +3632,7 @@ const createValueOutput = (binding) => {
3564
3632
  begin,
3565
3633
  seal,
3566
3634
  context: (active) => {
3567
- require_scheduler.assertScope(active);
3635
+ require_transform.assertScope(active);
3568
3636
  return Object.freeze({
3569
3637
  kind: "value",
3570
3638
  value: next,
@@ -3585,8 +3653,8 @@ const createValueOutput = (binding) => {
3585
3653
  initialized = true;
3586
3654
  },
3587
3655
  emit: (call) => {
3588
- require_scheduler.profile.materialized.notification();
3589
- Array.from(listeners).forEach((listener) => call(listener));
3656
+ require_transform.profile.materialized.notification();
3657
+ Array.from(listeners).forEach((listener) => call(listener, void 0));
3590
3658
  },
3591
3659
  hasConsumers: () => consumers.size > 0,
3592
3660
  forEachConsumer: (run) => consumers.forEach(run),
@@ -3624,7 +3692,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
3624
3692
  let instance;
3625
3693
  let cause;
3626
3694
  const check = () => {
3627
- if (!scheduler.active || processor.disposed) throw new require_scheduler.ProjectionDisposedError();
3695
+ if (!scheduler.active || processor.disposed) throw new require_transform.ProjectionDisposedError();
3628
3696
  if (processor.fault) throw processor.fault;
3629
3697
  };
3630
3698
  const bound = definition.outputs.map((outputDefinition) => {
@@ -3678,8 +3746,8 @@ const createProcessor = (scheduler, definition, dependencies) => {
3678
3746
  instance = void 0;
3679
3747
  }
3680
3748
  if (!instance) instance = definition.create();
3681
- require_scheduler.profile.materialized[reset ? "rebuilt" : "updated"]();
3682
- require_scheduler.assertSynchronous(instance.evaluate({
3749
+ require_transform.profile.materialized[reset ? "rebuilt" : "updated"]();
3750
+ require_transform.assertSynchronous(instance.evaluate({
3683
3751
  reset,
3684
3752
  cause: runCause,
3685
3753
  sources,
@@ -3726,7 +3794,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
3726
3794
  try {
3727
3795
  scheduler.initialize(() => {
3728
3796
  const invalid = dependencies.find((output) => output.owner.fault || output.owner.disposed);
3729
- if (invalid) throw invalid.owner.fault ?? new require_scheduler.ProjectionDisposedError();
3797
+ if (invalid) throw invalid.owner.fault ?? new require_transform.ProjectionDisposedError();
3730
3798
  if (processor.evaluate(true, void 0).length) processor.publish();
3731
3799
  processor.clear();
3732
3800
  });
@@ -3741,6 +3809,20 @@ const createProcessor = (scheduler, definition, dependencies) => {
3741
3809
  return processor;
3742
3810
  };
3743
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
3744
3826
  //#region core/src/projection/readable/selection.ts
3745
3827
  let activeTracker;
3746
3828
  const trackKey = (key) => {
@@ -3793,8 +3875,8 @@ const trackedMap = (source) => {
3793
3875
  const selectionAffects = (selection, change) => {
3794
3876
  if (!change || change.kind === "reset") return true;
3795
3877
  if (selection.all) return true;
3796
- if (selection.structure && require_scheduler.collectionHasStructuralChange(change)) return true;
3797
- for (const key of require_scheduler.collectionChange.keys(change)) if (selection.keys.has(key)) 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;
3798
3880
  return false;
3799
3881
  };
3800
3882
  const createDirectReadable = (source) => Object.freeze({
@@ -3862,7 +3944,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
3862
3944
  if (!change && previousCollection) {
3863
3945
  const current = source.current();
3864
3946
  if (isMapLike(current)) {
3865
- change = require_scheduler.diffCollection(previousCollection, current);
3947
+ change = require_transform.diffCollection(previousCollection, current);
3866
3948
  previousCollection = current;
3867
3949
  if (!change) {
3868
3950
  sourceRevision = source.revision();
@@ -3875,7 +3957,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
3875
3957
  updateCollectionSnapshot();
3876
3958
  return;
3877
3959
  }
3878
- if (evaluate()) Array.from(listeners).forEach((listener) => listener());
3960
+ if (evaluate()) notifyProjectionListeners(listeners, "Projection selector listeners failed.");
3879
3961
  };
3880
3962
  const installSource = () => {
3881
3963
  if (!unsubscribeSource) unsubscribeSource = source.subscribe(onProjectionChange);
@@ -3906,7 +3988,7 @@ const hasDocumentDirty = (dirty) => dirty.replace || dirty.order || dirty.treeRo
3906
3988
  const equivalent = (node, left, right) => node.kind === "field" ? Object.is(left, right) : equalValue(node, left, right);
3907
3989
  const replaceValue = (node, previous, current) => equivalent(node, previous, current) ? previous : copyValue(node, current);
3908
3990
  const cloneRecord = (value) => {
3909
- require_scheduler.profile.copy.shallowRecord(Object.getOwnPropertyNames(value).length);
3991
+ require_transform.profile.copy.shallowRecord(Object.getOwnPropertyNames(value).length);
3910
3992
  return Object.create(Object.getPrototypeOf(value), Object.getOwnPropertyDescriptors(value));
3911
3993
  };
3912
3994
  const materializeRecordChild = (node, previous, current, key, dirty) => {
@@ -4212,7 +4294,7 @@ const mergeCollectionChanges = (selector, pending, changes) => {
4212
4294
  };
4213
4295
  //#endregion
4214
4296
  //#region core/src/projection/source/boundary.ts
4215
- const sourceError = (cause) => new require_scheduler.ProjectionError("source", cause);
4297
+ const sourceError = (cause) => new require_transform.ProjectionError("source", cause);
4216
4298
  const createValueBoundary = (scheduler, name, initial, equality, prepare, clearSource) => {
4217
4299
  let boundary;
4218
4300
  let pendingReset = false;
@@ -4221,7 +4303,7 @@ const createValueBoundary = (scheduler, name, initial, equality, prepare, clearS
4221
4303
  let recovering = false;
4222
4304
  let cleanup;
4223
4305
  const check = () => {
4224
- if (!scheduler.active || boundary.disposed) throw new require_scheduler.ProjectionDisposedError();
4306
+ if (!scheduler.active || boundary.disposed) throw new require_transform.ProjectionDisposedError();
4225
4307
  if (boundary.fault) throw boundary.fault;
4226
4308
  };
4227
4309
  const state = createValueOutput({
@@ -4328,23 +4410,23 @@ const stageCollectionRead = (state, read, active, options) => {
4328
4410
  const evaluation = state.begin(active, options.reset);
4329
4411
  if (options.reset) {
4330
4412
  const ids = read.ids();
4331
- require_scheduler.profile.collectionView.idsScanned(ids.length);
4413
+ require_transform.profile.collectionView.idsScanned(ids.length);
4332
4414
  for (const key of ids) {
4333
- require_scheduler.profile.collectionView.mapped();
4415
+ require_transform.profile.collectionView.mapped();
4334
4416
  evaluation.output.set(key, read.get(key));
4335
4417
  }
4336
4418
  evaluation.output.order(ids);
4337
4419
  } else {
4338
4420
  const previous = state.current();
4339
4421
  const keys = options.candidates ? [...options.candidates] : [...new Set([...previous.ids(), ...read.ids()])];
4340
- if (!options.candidates) require_scheduler.profile.collectionView.idsScanned(keys.length);
4422
+ if (!options.candidates) require_transform.profile.collectionView.idsScanned(keys.length);
4341
4423
  for (const key of keys) if (read.has(key)) {
4342
- require_scheduler.profile.collectionView.mapped();
4424
+ require_transform.profile.collectionView.mapped();
4343
4425
  evaluation.output.set(key, read.get(key));
4344
4426
  } else evaluation.output.remove(key);
4345
4427
  if (options.orderMayChange || !options.candidates) {
4346
4428
  const ids = read.ids();
4347
- if (options.candidates) require_scheduler.profile.collectionView.idsScanned(ids.length);
4429
+ if (options.candidates) require_transform.profile.collectionView.idsScanned(ids.length);
4348
4430
  evaluation.output.order(ids);
4349
4431
  }
4350
4432
  }
@@ -4361,7 +4443,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
4361
4443
  const candidates = /* @__PURE__ */ new Set();
4362
4444
  let cleanup;
4363
4445
  const check = () => {
4364
- if (!scheduler.active || boundary.disposed) throw new require_scheduler.ProjectionDisposedError();
4446
+ if (!scheduler.active || boundary.disposed) throw new require_transform.ProjectionDisposedError();
4365
4447
  if (boundary.fault) throw boundary.fault;
4366
4448
  };
4367
4449
  const state = createCollectionOutput({
@@ -4640,7 +4722,7 @@ const createDocumentSourceRegistry = (scheduler) => {
4640
4722
  const replacement = createDocumentDirty();
4641
4723
  if (pending.reset) markDocumentReplace(replacement, []);
4642
4724
  const ids = () => {
4643
- require_scheduler.assertScope(active);
4725
+ require_transform.assertScope(active);
4644
4726
  if (treeNodes) return node?.kind === "tree" && require_record.isRecord(value) && require_record.isRecord(value.nodes) ? Object.freeze(Object.keys(value.nodes)) : Object.freeze([]);
4645
4727
  if (node?.kind === "map" && require_record.isRecord(value)) return Object.freeze(Object.keys(value));
4646
4728
  if (node?.kind === "table" && require_record.isRecord(value) && Array.isArray(value.ids)) return Object.freeze([...value.ids]);
@@ -4648,7 +4730,7 @@ const createDocumentSourceRegistry = (scheduler) => {
4648
4730
  return Object.freeze([]);
4649
4731
  };
4650
4732
  const has = (key) => {
4651
- require_scheduler.assertScope(active);
4733
+ require_transform.assertScope(active);
4652
4734
  if (treeNodes) return Boolean(node?.kind === "tree" && require_record.isRecord(value) && require_record.isRecord(value.nodes) && Object.hasOwn(value.nodes, key));
4653
4735
  if (node?.kind === "map" && require_record.isRecord(value)) return Object.hasOwn(value, key);
4654
4736
  if (node?.kind === "table" && require_record.isRecord(value) && require_record.isRecord(value.byId)) return Object.hasOwn(value.byId, key);
@@ -4669,7 +4751,7 @@ const createDocumentSourceRegistry = (scheduler) => {
4669
4751
  };
4670
4752
  return Object.freeze({
4671
4753
  get: (key) => {
4672
- require_scheduler.assertScope(active);
4754
+ require_transform.assertScope(active);
4673
4755
  if (!snapshots.has(key)) if (!has(key)) snapshots.set(key, void 0);
4674
4756
  else {
4675
4757
  const current = raw(key);
@@ -5016,7 +5098,7 @@ const materializeInputSource = (scheduler, definition) => {
5016
5098
  }
5017
5099
  if (source.kind !== "collection-input") throw new Error("Invalid projection input source.");
5018
5100
  const values = new Map(source.initial);
5019
- 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);
5020
5102
  return {
5021
5103
  ...boundary,
5022
5104
  write: {
@@ -5029,17 +5111,17 @@ const materializeInputSource = (scheduler, definition) => {
5029
5111
  let active = true;
5030
5112
  const draft = Object.freeze({
5031
5113
  get: (key) => {
5032
- require_scheduler.assertScope(() => active);
5114
+ require_transform.assertScope(() => active);
5033
5115
  const entry = staged.get(key);
5034
5116
  return entry ? entry.present ? entry.value : void 0 : values.get(key);
5035
5117
  },
5036
5118
  has: (key) => {
5037
- require_scheduler.assertScope(() => active);
5119
+ require_transform.assertScope(() => active);
5038
5120
  const entry = staged.get(key);
5039
5121
  return entry ? entry.present : values.has(key);
5040
5122
  },
5041
5123
  set: (key, value) => {
5042
- require_scheduler.assertScope(() => active);
5124
+ require_transform.assertScope(() => active);
5043
5125
  if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
5044
5126
  const previous = staged.get(key);
5045
5127
  structural ||= previous ? !previous.present : !values.has(key);
@@ -5054,7 +5136,7 @@ const materializeInputSource = (scheduler, definition) => {
5054
5136
  });
5055
5137
  },
5056
5138
  remove: (key) => {
5057
- require_scheduler.assertScope(() => active);
5139
+ require_transform.assertScope(() => active);
5058
5140
  if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
5059
5141
  const previous = staged.get(key);
5060
5142
  const present = previous ? previous.present : values.has(key);
@@ -5067,7 +5149,7 @@ const materializeInputSource = (scheduler, definition) => {
5067
5149
  }
5068
5150
  });
5069
5151
  try {
5070
- require_scheduler.assertSynchronous(run(draft));
5152
+ require_transform.assertSynchronous(run(draft));
5071
5153
  } finally {
5072
5154
  active = false;
5073
5155
  }
@@ -5132,19 +5214,249 @@ const createSourceRegistry = (scheduler) => {
5132
5214
  };
5133
5215
  };
5134
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
5135
5423
  //#region core/src/projection/runtime.ts
5136
5424
  const createProjectionRuntime = (options) => {
5137
- const scheduler = require_scheduler.createScheduler(options?.onError ?? (() => void 0));
5425
+ const scheduler = require_transform.createScheduler(options?.onError ?? (() => void 0));
5138
5426
  const sources = createSourceRegistry(scheduler);
5139
5427
  const materialized = /* @__PURE__ */ new WeakMap();
5140
5428
  const publicViews = /* @__PURE__ */ new WeakMap();
5141
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
+ };
5142
5454
  const assertAccess = (definition, requester) => {
5143
5455
  scheduler.assertActive();
5144
5456
  const owner = definition.owner;
5145
5457
  if (owner === void 0) return;
5146
5458
  if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
5147
- if (!requester?.active) throw new require_scheduler.ProjectionDisposedError();
5459
+ if (!requester?.active) throw new require_transform.ProjectionDisposedError();
5148
5460
  };
5149
5461
  const rememberScoped = (definition, requester) => {
5150
5462
  if (requester && definition.owner === requester) requester.materialized.push(definition);
@@ -5162,7 +5474,7 @@ const createProjectionRuntime = (options) => {
5162
5474
  ...source.write ? { write: source.write } : {}
5163
5475
  });
5164
5476
  } else {
5165
- 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.");
5166
5478
  const dependencies = definition.dependencies.map((dependency) => resolveOutput(dependency, requester));
5167
5479
  const processor = createProcessor(scheduler, definition, Object.freeze(dependencies));
5168
5480
  result = Object.freeze({
@@ -5176,14 +5488,14 @@ const createProjectionRuntime = (options) => {
5176
5488
  return result;
5177
5489
  };
5178
5490
  const resolveOutput = (projection, requester) => {
5179
- const ref = require_scheduler.projectionRef(projection);
5491
+ const ref = require_transform.projectionRef(projection);
5180
5492
  const output = materializeProducer(ref.producer, requester).outputs[ref.output];
5181
5493
  if (!output) throw new TypeError("Projection output does not exist.");
5182
5494
  return output;
5183
5495
  };
5184
5496
  const assertOutput = (output) => {
5185
5497
  scheduler.assertActive();
5186
- if (output.owner.disposed) throw new require_scheduler.ProjectionDisposedError();
5498
+ if (output.owner.disposed) throw new require_transform.ProjectionDisposedError();
5187
5499
  if (output.owner.fault) throw output.owner.fault;
5188
5500
  };
5189
5501
  const publicCurrent = (output) => {
@@ -5193,7 +5505,7 @@ const createProjectionRuntime = (options) => {
5193
5505
  const cached = publicViews.get(output);
5194
5506
  if (cached?.revision === revision) return cached.value;
5195
5507
  if (output.kind === "collection") {
5196
- const value = require_scheduler.collectionView(current);
5508
+ const value = require_transform.collectionView(current);
5197
5509
  publicViews.set(output, {
5198
5510
  revision,
5199
5511
  value
@@ -5201,7 +5513,7 @@ const createProjectionRuntime = (options) => {
5201
5513
  return value;
5202
5514
  }
5203
5515
  if (isMapLike(current)) {
5204
- const value = require_scheduler.snapshotCollectionView(require_scheduler.mapRead(current));
5516
+ const value = require_transform.snapshotCollectionView(require_transform.mapRead(current));
5205
5517
  publicViews.set(output, {
5206
5518
  revision,
5207
5519
  value
@@ -5224,6 +5536,19 @@ const createProjectionRuntime = (options) => {
5224
5536
  subscribe: output.subscribe
5225
5537
  });
5226
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
+ };
5227
5552
  const makeReadable = (projection, selector, equality, requester) => {
5228
5553
  const source = readableSource(projection, requester);
5229
5554
  return selector ? createSelectorReadable(source, selector, equality) : createDirectReadable(source);
@@ -5232,7 +5557,7 @@ const createProjectionRuntime = (options) => {
5232
5557
  return makeReadable(projection, selector, equality);
5233
5558
  }
5234
5559
  const inputWrite = (target, requester) => {
5235
- const ref = require_scheduler.projectionRef(target);
5560
+ const ref = require_transform.projectionRef(target);
5236
5561
  if (ref.output !== 0 || ref.producer.kind !== "source") throw new TypeError("Projection is not an input.");
5237
5562
  if (ref.producer.source.kind !== "value-input" && ref.producer.source.kind !== "collection-input") throw new TypeError("Projection is not an input.");
5238
5563
  const write = materializeProducer(ref.producer, requester).write;
@@ -5257,13 +5582,7 @@ const createProjectionRuntime = (options) => {
5257
5582
  scheduler.assertIdle();
5258
5583
  state.active = false;
5259
5584
  const failures = [];
5260
- const subscriptions = [...state.subscriptions];
5261
- state.subscriptions.clear();
5262
- for (const unsubscribe of subscriptions) try {
5263
- unsubscribe();
5264
- } catch (error) {
5265
- failures.push(error);
5266
- }
5585
+ disposeScopeConsumers(state, failures);
5267
5586
  for (let index = state.materialized.length - 1; index >= 0; index--) {
5268
5587
  const definition = state.materialized[index];
5269
5588
  const current = materialized.get(definition);
@@ -5278,25 +5597,26 @@ const createProjectionRuntime = (options) => {
5278
5597
  }
5279
5598
  state.materialized.length = 0;
5280
5599
  scopes.delete(state);
5281
- if (failures.length) throw failures[0];
5600
+ throwCleanupFailures(failures, "Projection scope cleanup failed.");
5282
5601
  };
5283
5602
  const createScope = () => {
5284
5603
  scheduler.assertIdle();
5285
5604
  const state = {
5286
5605
  active: true,
5287
5606
  materialized: [],
5288
- subscriptions: /* @__PURE__ */ new Set()
5607
+ subscriptions: /* @__PURE__ */ new Set(),
5608
+ items: /* @__PURE__ */ new Map()
5289
5609
  };
5290
5610
  scopes.add(state);
5291
5611
  const assertActive = () => {
5292
- if (!state.active) throw new require_scheduler.ProjectionDisposedError();
5612
+ if (!state.active) throw new require_transform.ProjectionDisposedError();
5293
5613
  scheduler.assertActive();
5294
5614
  };
5295
5615
  function own(target) {
5296
5616
  assertActive();
5297
5617
  const projections = [];
5298
5618
  const collect = (value) => {
5299
- if (require_scheduler.isProjection(value)) {
5619
+ if (require_transform.isProjection(value)) {
5300
5620
  projections.push(value);
5301
5621
  return;
5302
5622
  }
@@ -5306,13 +5626,13 @@ const createProjectionRuntime = (options) => {
5306
5626
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
5307
5627
  if (!descriptor?.enumerable || !("value" in descriptor)) throw new TypeError("Projection ownership tree must use enumerable data properties.");
5308
5628
  const child = descriptor.value;
5309
- 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.");
5310
5630
  collect(child);
5311
5631
  }
5312
5632
  };
5313
5633
  collect(target);
5314
5634
  if (!projections.length) throw new TypeError("Projection ownership tree must contain a projection.");
5315
- require_scheduler.ownProjections(projections, state);
5635
+ require_transform.ownProjections(projections, state);
5316
5636
  return target;
5317
5637
  }
5318
5638
  function scopeSelect(projection, selector, equality) {
@@ -5357,6 +5677,10 @@ const createProjectionRuntime = (options) => {
5357
5677
  return readProjection(projection, state);
5358
5678
  },
5359
5679
  select: scopeSelect,
5680
+ items: (projection) => {
5681
+ assertActive();
5682
+ return projectionItems(projection, state);
5683
+ },
5360
5684
  update: scopeUpdate,
5361
5685
  batch: scopeBatch,
5362
5686
  dispose: () => disposeScope(state)
@@ -5365,6 +5689,7 @@ const createProjectionRuntime = (options) => {
5365
5689
  return Object.freeze({
5366
5690
  read: readProjection,
5367
5691
  select: selectProjection,
5692
+ items: projectionItems,
5368
5693
  update,
5369
5694
  batch,
5370
5695
  scope: createScope,
@@ -5374,16 +5699,11 @@ const createProjectionRuntime = (options) => {
5374
5699
  const failures = [];
5375
5700
  for (const scope of scopes) {
5376
5701
  scope.active = false;
5377
- const subscriptions = [...scope.subscriptions];
5378
- scope.subscriptions.clear();
5379
- for (const unsubscribe of subscriptions) try {
5380
- unsubscribe();
5381
- } catch (error) {
5382
- failures.push(error);
5383
- }
5702
+ disposeScopeConsumers(scope, failures);
5384
5703
  scope.materialized.length = 0;
5385
5704
  }
5386
5705
  scopes.clear();
5706
+ disposeItemControllers(rootItems, failures);
5387
5707
  try {
5388
5708
  sources.dispose();
5389
5709
  } catch (error) {
@@ -5394,8 +5714,7 @@ const createProjectionRuntime = (options) => {
5394
5714
  } catch (error) {
5395
5715
  failures.push(error);
5396
5716
  }
5397
- if (failures.length === 1) throw failures[0];
5398
- if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
5717
+ throwCleanupFailures(failures, "Projection cleanup failed.");
5399
5718
  }
5400
5719
  });
5401
5720
  };
@@ -5403,8 +5722,8 @@ const createProjectionRuntime = (options) => {
5403
5722
  exports.DocumentDisposedError = DocumentDisposedError;
5404
5723
  exports.DocumentReentrancyError = DocumentReentrancyError;
5405
5724
  exports.ParseError = ParseError;
5406
- exports.ProjectionDisposedError = require_scheduler.ProjectionDisposedError;
5407
- exports.ProjectionError = require_scheduler.ProjectionError;
5725
+ exports.ProjectionDisposedError = require_transform.ProjectionDisposedError;
5726
+ exports.ProjectionError = require_transform.ProjectionError;
5408
5727
  exports.TransactionRejected = TransactionRejected;
5409
5728
  exports.createDocument = createDocument;
5410
5729
  exports.createProjectionRuntime = createProjectionRuntime;