doxum 0.1.27 → 0.1.29

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 (44) 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 → driver-B7RR-VfX.js} +31 -12
  11. package/dist/driver-B7RR-VfX.js.map +1 -0
  12. package/dist/{driver-BL91gn7_.cjs → driver-CN3dGxQI.cjs} +31 -12
  13. package/dist/driver-CN3dGxQI.cjs.map +1 -0
  14. package/dist/index.cjs +824 -436
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +23 -32
  17. package/dist/index.d.ts +23 -32
  18. package/dist/index.js +713 -325
  19. package/dist/index.js.map +1 -1
  20. package/dist/local-sync.cjs +1 -1
  21. package/dist/local-sync.cjs.map +1 -1
  22. package/dist/local-sync.js +1 -1
  23. package/dist/local-sync.js.map +1 -1
  24. package/dist/{scheduler-k1ZGR1PG.cjs → transform-B6YxfR0R.cjs} +550 -43
  25. package/dist/transform-B6YxfR0R.cjs.map +1 -0
  26. package/dist/transform-BQY9I6il.js +1034 -0
  27. package/dist/transform-BQY9I6il.js.map +1 -0
  28. package/package.json +1 -1
  29. package/skills/doxum-runtime/SKILL.md +11 -5
  30. package/skills/doxum-runtime/references/api.en.md +40 -21
  31. package/skills/doxum-runtime/references/api.zh-CN.md +21 -2
  32. package/skills/doxum-runtime/references/guide.en.md +2 -1
  33. package/skills/doxum-runtime/references/guide.zh-CN.md +2 -1
  34. package/skills/doxum-runtime/references/invariants.en.md +2 -2
  35. package/skills/doxum-runtime/references/invariants.zh-CN.md +2 -2
  36. package/skills/doxum-runtime/references/patterns.en.md +1 -1
  37. package/skills/doxum-runtime/references/patterns.zh-CN.md +1 -1
  38. package/skills/doxum-runtime/references/projections.en.md +47 -3
  39. package/skills/doxum-runtime/references/projections.zh-CN.md +45 -3
  40. package/dist/driver-B5vdPohL.js.map +0 -1
  41. package/dist/driver-BL91gn7_.cjs.map +0 -1
  42. package/dist/scheduler-k1ZGR1PG.cjs.map +0 -1
  43. package/dist/scheduler-vzf3OcjT.js +0 -593
  44. package/dist/scheduler-vzf3OcjT.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_scheduler = require("./scheduler-k1ZGR1PG.cjs");
2
+ const require_transform = require("./transform-B6YxfR0R.cjs");
3
3
  const require_record = require("./record-ChYUi8PP.cjs");
4
- const require_driver = require("./driver-BL91gn7_.cjs");
4
+ const require_driver = require("./driver-CN3dGxQI.cjs");
5
5
  //#region core/src/schema/model.ts
6
6
  const schemaNodeOf = (schema) => schema;
7
7
  const collectionEntryNode = (node) => node.kind === "map" || node.kind === "table" || node.kind === "list" ? node.value : void 0;
@@ -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 {
@@ -569,18 +569,44 @@ const moveSelection = (current, selection, position) => {
569
569
  selected.add(key);
570
570
  }
571
571
  const moved = [];
572
- const remaining = [];
573
- for (const key of current) (selected.has(key) ? moved : remaining).push(key);
572
+ let remainingLength = 0;
573
+ let anchorIndex = -1;
574
+ const anchoredKey = position && ("before" in position ? position.before : "after" in position ? position.after : void 0);
575
+ for (const key of current) {
576
+ if (selected.has(key)) {
577
+ moved.push(key);
578
+ continue;
579
+ }
580
+ if (key === anchoredKey) anchorIndex = remainingLength;
581
+ remainingLength++;
582
+ }
574
583
  if (moved.length !== selected.size) return { kind: "missing" };
575
- if (!valid(remaining, position)) return { kind: "invalid-anchor" };
576
- const insertion = index(remaining, position);
584
+ let insertion;
585
+ if (!position || "at" in position && position.at === "end") insertion = remainingLength;
586
+ else if ("at" in position) {
587
+ if (position.at !== "start") return { kind: "invalid-anchor" };
588
+ insertion = 0;
589
+ } else {
590
+ if (anchorIndex < 0) return { kind: "invalid-anchor" };
591
+ insertion = "before" in position ? anchorIndex : anchorIndex + 1;
592
+ }
593
+ const order = new Array(current.length);
594
+ let outputIndex = 0;
595
+ let remainingIndex = 0;
596
+ let inserted = false;
597
+ for (const key of current) {
598
+ if (selected.has(key)) continue;
599
+ if (!inserted && remainingIndex === insertion) {
600
+ for (const movedKey of moved) order[outputIndex++] = movedKey;
601
+ inserted = true;
602
+ }
603
+ order[outputIndex++] = key;
604
+ remainingIndex++;
605
+ }
606
+ if (!inserted) for (const movedKey of moved) order[outputIndex++] = movedKey;
577
607
  return {
578
608
  kind: "ok",
579
- order: [
580
- ...remaining.slice(0, insertion),
581
- ...moved,
582
- ...remaining.slice(insertion)
583
- ]
609
+ order
584
610
  };
585
611
  };
586
612
  const index = (keys, anchor) => {
@@ -817,7 +843,7 @@ const sameTreeTarget = (left, right) => {
817
843
  return left.kind !== "node" || right.kind === "node" && left.id === right.id;
818
844
  };
819
845
  const same = (left, right) => {
820
- require_scheduler.profile.dependency.comparison();
846
+ require_transform.profile.dependency.comparison();
821
847
  if ("schema" in left && "schema" in right && left.schema !== right.schema) return false;
822
848
  if (!sameTreeTarget(treeTarget(left), treeTarget(right))) return false;
823
849
  if (left.kind !== right.kind) return false;
@@ -825,7 +851,7 @@ const same = (left, right) => {
825
851
  const rightAddress = address(right);
826
852
  if (leftAddress.length !== rightAddress.length) return false;
827
853
  for (let index = 0; index < leftAddress.length; index += 1) {
828
- require_scheduler.profile.dependency.segmentCompared();
854
+ require_transform.profile.dependency.segmentCompared();
829
855
  if (leftAddress[index] !== rightAddress[index]) return false;
830
856
  }
831
857
  return left.kind !== "collection" || id(left) === id(right);
@@ -938,7 +964,7 @@ const locations = /* @__PURE__ */ new WeakMap();
938
964
  const locationOf = (value) => locations.get(value);
939
965
  const addressOf = (target) => {
940
966
  if (target.at) return target.at;
941
- require_scheduler.profile.access("addresses");
967
+ require_transform.profile.access("addresses");
942
968
  return target.at = addressOf(target.parent).concat(target.key);
943
969
  };
944
970
  const assertActive = (context) => {
@@ -960,7 +986,7 @@ const snapshot = (value) => {
960
986
  if (!location) return value;
961
987
  const { context } = location;
962
988
  const at = addressOf(location.target);
963
- require_scheduler.profile.access("snapshots");
989
+ require_transform.profile.access("snapshots");
964
990
  collect(context, at);
965
991
  const resolved = resolve(context, location.target);
966
992
  if (!resolved.node) throw new Error("The selected schema address no longer exists.");
@@ -975,7 +1001,7 @@ const resolve = (context, target) => {
975
1001
  assertActive(context);
976
1002
  const generation = context.session?.generation ?? 0;
977
1003
  if (target.generation !== generation) {
978
- require_scheduler.profile.access("resolutions");
1004
+ require_transform.profile.access("resolutions");
979
1005
  if (!target.parent && !target.at.length) {
980
1006
  target.node = context.state.schema;
981
1007
  target.value = context.state.document;
@@ -990,7 +1016,7 @@ const resolve = (context, target) => {
990
1016
  return target;
991
1017
  };
992
1018
  const createAccess = (context, initial = []) => {
993
- require_scheduler.profile.access("scopes");
1019
+ require_transform.profile.access("scopes");
994
1020
  const mutable = () => {
995
1021
  assertActive(context);
996
1022
  if (!context.session) throw new TypeError("Cannot modify read-only document access.");
@@ -1001,13 +1027,13 @@ const createAccess = (context, initial = []) => {
1001
1027
  return target.container = session.bind(addressOf(target), target.node, target.value);
1002
1028
  };
1003
1029
  const childAt = (target, key) => {
1004
- require_scheduler.profile.access("addresses");
1030
+ require_transform.profile.access("addresses");
1005
1031
  return addressOf(target).concat(key);
1006
1032
  };
1007
1033
  const access = (target) => {
1008
1034
  if (target.proxy) return target.proxy;
1009
1035
  const proxy = new Proxy(target, handler);
1010
- require_scheduler.profile.access("proxies");
1036
+ require_transform.profile.access("proxies");
1011
1037
  locations.set(proxy, {
1012
1038
  context,
1013
1039
  target
@@ -1365,14 +1391,25 @@ const compilePath = (owner, kind, pick) => {
1365
1391
  //#endregion
1366
1392
  //#region core/src/address/relation.ts
1367
1393
  const contains = (parent, child) => {
1368
- require_scheduler.profile.address.prefixComparison();
1394
+ require_transform.profile.address.prefixComparison();
1369
1395
  if (parent.length > child.length) return false;
1370
1396
  for (let index = 0; index < parent.length; index += 1) {
1371
- require_scheduler.profile.address.segmentCompared();
1397
+ require_transform.profile.address.segmentCompared();
1372
1398
  if (parent[index] !== child[index]) return false;
1373
1399
  }
1374
1400
  return true;
1375
1401
  };
1402
+ /** Compare a virtual `[...base, segment]` address with an existing address without allocating it. */
1403
+ const compareExtended = (base, segment, other) => {
1404
+ require_transform.profile.address.prefixComparison();
1405
+ const length = base.length + 1;
1406
+ const common = Math.min(length, other.length);
1407
+ for (let index = 0; index < common; index += 1) {
1408
+ require_transform.profile.address.segmentCompared();
1409
+ if ((index < base.length ? base[index] : segment) !== other[index]) return "disjoint";
1410
+ }
1411
+ return length === other.length ? "equal" : length < other.length ? "ancestor" : "descendant";
1412
+ };
1376
1413
  //#endregion
1377
1414
  //#region core/src/impact.ts
1378
1415
  const createImpact = (schema, changes) => {
@@ -1381,7 +1418,7 @@ const createImpact = (schema, changes) => {
1381
1418
  let orders;
1382
1419
  const index = () => {
1383
1420
  if (!values) {
1384
- require_scheduler.profile.impact.index();
1421
+ require_transform.profile.impact.index();
1385
1422
  values = new require_driver.AddressIndex();
1386
1423
  orders = new require_driver.AddressIndex();
1387
1424
  visitChangedLocations(changes, (at) => values.add(at, true), (at) => orders.add(at, true));
@@ -1390,7 +1427,7 @@ const createImpact = (schema, changes) => {
1390
1427
  };
1391
1428
  const cache = /* @__PURE__ */ new Map();
1392
1429
  const affects = (value) => {
1393
- require_scheduler.profile.impact.affects();
1430
+ require_transform.profile.impact.affects();
1394
1431
  if (!belongs(value, schema)) return false;
1395
1432
  if (reset) return true;
1396
1433
  return affected(value, index(), orders);
@@ -1701,7 +1738,7 @@ const transition = (node, key, beforePresent, before, afterPresent, after) => {
1701
1738
  if (beforePresent === afterPresent && (!beforePresent || Object.is(before, after))) return;
1702
1739
  if (node.kind !== "field" && beforePresent === afterPresent && equalValue(node, before, after)) return;
1703
1740
  const value = afterPresent ? node.kind === "field" ? after : copyValue(node, after) : void 0;
1704
- require_scheduler.profile.recorder("transitions");
1741
+ require_transform.profile.recorder("transitions");
1705
1742
  return !beforePresent ? {
1706
1743
  key,
1707
1744
  kind: "added",
@@ -1756,7 +1793,7 @@ const diffMember = (changes, members, node, at, key, beforePresent, before, afte
1756
1793
  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
1794
  let order;
1758
1795
  if (node.kind === "table" && !equal(a.ids, b.ids)) {
1759
- require_scheduler.profile.recorder("publishedOrderItems", b.ids.length);
1796
+ require_transform.profile.recorder("publishedOrderItems", b.ids.length);
1760
1797
  order = {
1761
1798
  before: a.ids,
1762
1799
  after: [...b.ids]
@@ -1799,7 +1836,7 @@ const reconstructBefore = (node, current, records, offset) => {
1799
1836
  };
1800
1837
  const sealMembers = (fact, changes) => {
1801
1838
  const members = [];
1802
- for (const member of fact.members?.values() ?? []) {
1839
+ if (fact.members) for (const member of fact.members.values()) {
1803
1840
  if (!member) continue;
1804
1841
  const key = memberKey(fact, member.key);
1805
1842
  const present = Object.hasOwn(fact.parent, key);
@@ -1814,7 +1851,7 @@ const sealMembers = (fact, changes) => {
1814
1851
  const current = fact.node.kind === "list" ? indexedKeys(fact.value, fact.node.keyOf) : fact.value.ids;
1815
1852
  if (!equal(fact.order, current)) {
1816
1853
  const after = orderOf(fact.node, fact.value);
1817
- require_scheduler.profile.recorder("publishedOrderItems", after.length);
1854
+ require_transform.profile.recorder("publishedOrderItems", after.length);
1818
1855
  order = {
1819
1856
  before: fact.order,
1820
1857
  after
@@ -1872,7 +1909,7 @@ var ChangeRecorder = class {
1872
1909
  this.indexedGroups = true;
1873
1910
  for (const fact of this.facts) if (fact.kind === "members") {
1874
1911
  index.add(fact.at, fact);
1875
- require_scheduler.profile.recorder("indexedGroups");
1912
+ require_transform.profile.recorder("indexedGroups");
1876
1913
  }
1877
1914
  }
1878
1915
  return index;
@@ -1883,7 +1920,7 @@ var ChangeRecorder = class {
1883
1920
  absorb(at, node, value, present) {
1884
1921
  const index = this.indexGroups();
1885
1922
  const children = [];
1886
- index.query((fact) => {
1923
+ index.forEachOverlap(at, (fact) => {
1887
1924
  if (fact.kind === "members") {
1888
1925
  if (fact.at.length < at.length) {
1889
1926
  if (fact.at.length + 1 === at.length) {
@@ -1902,18 +1939,19 @@ var ChangeRecorder = class {
1902
1939
  at: fact.at,
1903
1940
  group: fact
1904
1941
  });
1905
- for (const member of fact.members?.values() ?? []) {
1942
+ if (fact.members) for (const member of fact.members.values()) {
1906
1943
  if (!member) continue;
1907
- const memberAt = fact.at.concat(member.key);
1908
- if (contains(at, memberAt)) children.push({
1944
+ const compared = compareExtended(fact.at, member.key, at);
1945
+ if (compared !== "equal" && compared !== "descendant") continue;
1946
+ children.push({
1909
1947
  kind: "member",
1910
- at: memberAt,
1948
+ at: fact.at.concat(member.key),
1911
1949
  group: fact,
1912
1950
  member
1913
1951
  });
1914
1952
  }
1915
1953
  } else if (contains(at, fact.at)) children.push(fact);
1916
- })(at);
1954
+ });
1917
1955
  if (present) value = reconstructBefore(node, value, children, at.length);
1918
1956
  this.releaseCovered(children);
1919
1957
  return value;
@@ -1921,7 +1959,7 @@ var ChangeRecorder = class {
1921
1959
  releaseCovered(children) {
1922
1960
  let changedGroups;
1923
1961
  for (const child of children) {
1924
- require_scheduler.profile.recorder("absorbed");
1962
+ require_transform.profile.recorder("absorbed");
1925
1963
  if (child.kind === "member") {
1926
1964
  const { group, member } = child;
1927
1965
  forgetMember(group, member.key);
@@ -1931,15 +1969,17 @@ var ChangeRecorder = class {
1931
1969
  (changedGroups ??= /* @__PURE__ */ new Set()).add(child.group);
1932
1970
  } else this.unregister(child);
1933
1971
  }
1934
- for (const group of changedGroups ?? []) if (emptyGroup(group)) this.unregister(group);
1972
+ if (changedGroups) {
1973
+ for (const group of changedGroups) if (emptyGroup(group)) this.unregister(group);
1974
+ }
1935
1975
  }
1936
1976
  register(fact) {
1937
1977
  this.facts.add(fact);
1938
1978
  if (fact.kind === "members") {
1939
1979
  this.groups.set(fact.parent, fact);
1940
- require_scheduler.profile.recorder("groups");
1980
+ require_transform.profile.recorder("groups");
1941
1981
  if (!this.indexedGroups) return;
1942
- require_scheduler.profile.recorder("indexedGroups");
1982
+ require_transform.profile.recorder("indexedGroups");
1943
1983
  }
1944
1984
  (this.index ??= new require_driver.AddressIndex()).add(fact.at, fact);
1945
1985
  }
@@ -1969,14 +2009,14 @@ var ChangeRecorder = class {
1969
2009
  if (group.layout === "dynamic") (group.members ??= /* @__PURE__ */ new Map()).set(key, member);
1970
2010
  else if (definition.kind === "fixed") group.members[definition.slot] = member;
1971
2011
  else throw new Error("A fixed member group requires a fixed schema member.");
1972
- require_scheduler.profile.recorder("facts");
2012
+ require_transform.profile.recorder("facts");
1973
2013
  }
1974
2014
  reset() {
1975
2015
  if (this.resetBefore) return;
1976
2016
  const value = this.absorb([], this.state.schema, this.state.document, true);
1977
2017
  this.resetBefore = { value };
1978
2018
  this.groups = /* @__PURE__ */ new WeakMap();
1979
- require_scheduler.profile.recorder("facts");
2019
+ require_transform.profile.recorder("facts");
1980
2020
  }
1981
2021
  createGroup(container) {
1982
2022
  const group = container.layout.kind === "fixed" ? {
@@ -2001,13 +2041,13 @@ var ChangeRecorder = class {
2001
2041
  return group;
2002
2042
  }
2003
2043
  order(container) {
2004
- require_scheduler.profile.recorder("orderCaptures");
2044
+ require_transform.profile.recorder("orderCaptures");
2005
2045
  let group = this.groups.get(container.parent);
2006
2046
  if (group?.order || this.covered(container.at)) return;
2007
2047
  group ??= this.createGroup(container);
2008
2048
  group.order = orderOf(container.node, container.value);
2009
- require_scheduler.profile.recorder("orderSnapshots");
2010
- require_scheduler.profile.recorder("orderItems", group.order.length);
2049
+ require_transform.profile.recorder("orderSnapshots");
2050
+ require_transform.profile.recorder("orderItems", group.order.length);
2011
2051
  }
2012
2052
  tree(container, id) {
2013
2053
  const { at, value: tree } = container;
@@ -2025,7 +2065,7 @@ var ChangeRecorder = class {
2025
2065
  this.register(fact);
2026
2066
  }
2027
2067
  if (id !== void 0 && !fact.nodes.has(id)) {
2028
- require_scheduler.profile.recorder("treeNodes");
2068
+ require_transform.profile.recorder("treeNodes");
2029
2069
  fact.nodes.set(id, Object.hasOwn(tree.nodes, id) ? copyTreeNode(tree.nodes[id]) : void 0);
2030
2070
  }
2031
2071
  }
@@ -2048,7 +2088,7 @@ var ChangeRecorder = class {
2048
2088
  seal() {
2049
2089
  if (this.resetBefore) {
2050
2090
  if (equalValue(this.state.schema, this.resetBefore.value, this.state.document)) return require_driver.sealChanges([]);
2051
- require_scheduler.profile.recorder("sealed");
2091
+ require_transform.profile.recorder("sealed");
2052
2092
  return require_driver.sealChanges([{
2053
2093
  kind: "reset",
2054
2094
  before: this.resetBefore.value,
@@ -2059,7 +2099,7 @@ var ChangeRecorder = class {
2059
2099
  for (const fact of this.facts) if (fact.kind === "members") sealMembers(fact, changes);
2060
2100
  else sealTree(fact, this.state, changes);
2061
2101
  const result = require_driver.sealChanges(changes);
2062
- require_scheduler.profile.recorder("sealed", result.changes.length);
2102
+ require_transform.profile.recorder("sealed", result.changes.length);
2063
2103
  return result;
2064
2104
  }
2065
2105
  };
@@ -2664,7 +2704,7 @@ const select = (document, selector, equality = Object.is) => {
2664
2704
  };
2665
2705
  //#endregion
2666
2706
  //#region core/src/projection/input.ts
2667
- const valueInput = (initial, equality = Object.is) => require_scheduler.defineSource({
2707
+ const valueInput = (initial, equality = Object.is) => require_transform.defineSource({
2668
2708
  kind: "value-input",
2669
2709
  initial,
2670
2710
  equality
@@ -2678,7 +2718,7 @@ const collectionInput = (initial = /* @__PURE__ */ new Map(), equality = Object.
2678
2718
  if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
2679
2719
  entries.set(key, value);
2680
2720
  }
2681
- return require_scheduler.defineSource({
2721
+ return require_transform.defineSource({
2682
2722
  kind: "collection-input",
2683
2723
  initial: entries,
2684
2724
  equality
@@ -2691,13 +2731,13 @@ const input = Object.assign(valueInput, { collection: collectionInput });
2691
2731
  //#endregion
2692
2732
  //#region core/src/projection/observe.ts
2693
2733
  function observe(source, selector) {
2694
- if (isExternalSource(source)) return source.kind === "collection" ? require_scheduler.defineSource({
2734
+ if (isExternalSource(source)) return source.kind === "collection" ? require_transform.defineSource({
2695
2735
  kind: "external-collection",
2696
2736
  source
2697
2737
  }, {
2698
2738
  kind: "collection",
2699
2739
  equality: Object.is
2700
- }) : require_scheduler.defineSource({
2740
+ }) : require_transform.defineSource({
2701
2741
  kind: "external-value",
2702
2742
  source,
2703
2743
  equality: Object.is
@@ -2705,7 +2745,7 @@ function observe(source, selector) {
2705
2745
  kind: "value",
2706
2746
  equality: Object.is
2707
2747
  });
2708
- if ("current" in source && typeof source.current === "function") return require_scheduler.defineSource({
2748
+ if ("current" in source && typeof source.current === "function") return require_transform.defineSource({
2709
2749
  kind: "readable",
2710
2750
  readable: source,
2711
2751
  equality: Object.is
@@ -2720,7 +2760,7 @@ function observe(source, selector) {
2720
2760
  schema: state.schema,
2721
2761
  address: Object.freeze([])
2722
2762
  });
2723
- return require_scheduler.defineSource({
2763
+ return require_transform.defineSource({
2724
2764
  kind: "document",
2725
2765
  document,
2726
2766
  selector: selected
@@ -2736,41 +2776,6 @@ const isExternalSource = (source) => {
2736
2776
  };
2737
2777
  //#endregion
2738
2778
  //#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
2779
  const compileOrderedKeys = (value, role) => {
2775
2780
  if (!Array.isArray(value)) throw new TypeError(`${role} must be an array of string keys.`);
2776
2781
  const seen = /* @__PURE__ */ new Set();
@@ -2784,164 +2789,22 @@ const compileOrderedKeys = (value, role) => {
2784
2789
  requested: seen
2785
2790
  };
2786
2791
  };
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
2792
  function createKeyedDerive(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
2936
2793
  const dependencySpecs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
2937
2794
  const select = dependencySpecs === void 0 ? dependenciesOrSelect : selectOrEquality;
2938
2795
  const equality = dependencySpecs === void 0 ? selectOrEquality : maybeEquality;
2939
2796
  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");
2797
+ return require_transform.createKeyedTransform({
2798
+ source,
2799
+ dependencies: dependencySpecs,
2800
+ equality,
2801
+ name: "derive.keyed",
2802
+ evaluate: ({ value, key, dependencies }) => dependencySpecs === void 0 ? select(value, key) : select(value, key, dependencies)
2803
+ });
2941
2804
  }
2942
2805
  const createKeyedKeys = (source) => {
2943
- assertKeyedProjection(source, "Keyed keys source");
2944
- const [projection] = require_scheduler.defineProcessor({
2806
+ require_transform.assertKeyedProjection(source, "Keyed keys source");
2807
+ const [projection] = require_transform.defineProcessor({
2945
2808
  dependencies: [source],
2946
2809
  outputs: [{
2947
2810
  kind: "value",
@@ -2951,15 +2814,15 @@ const createKeyedKeys = (source) => {
2951
2814
  const driver = evaluation.sources[0];
2952
2815
  const output = evaluation.outputs[0];
2953
2816
  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());
2817
+ if (evaluation.reset || !driver.change || require_transform.collectionHasStructuralChange(driver.change)) output.output.set(driver.read.ids());
2955
2818
  } }),
2956
2819
  name: "derive.keyed.keys"
2957
2820
  });
2958
2821
  return projection;
2959
2822
  };
2960
- const createKeyedValues = (source) => {
2961
- assertKeyedProjection(source, "Keyed values source");
2962
- const [projection] = require_scheduler.defineProcessor({
2823
+ const createOrderedSnapshot = (source, name, make) => {
2824
+ require_transform.assertKeyedProjection(source, `${name} source`);
2825
+ const [projection] = require_transform.defineProcessor({
2963
2826
  dependencies: [source],
2964
2827
  outputs: [{
2965
2828
  kind: "value",
@@ -2969,33 +2832,32 @@ const createKeyedValues = (source) => {
2969
2832
  let indexes = /* @__PURE__ */ new Map();
2970
2833
  const rebuild = (driver) => {
2971
2834
  const nextIndexes = /* @__PURE__ */ new Map();
2972
- const ids = driver.read.ids();
2973
- const values = Object.freeze(ids.map((key, index) => {
2835
+ const result = Object.freeze(driver.read.ids().map((key, index) => {
2974
2836
  nextIndexes.set(key, index);
2975
- return driver.read.get(key);
2837
+ return make(key, driver.read.get(key));
2976
2838
  }));
2977
2839
  indexes = nextIndexes;
2978
- return values;
2840
+ return result;
2979
2841
  };
2980
2842
  const rebuildIncremental = (driver, change, previous) => {
2981
2843
  const changedValues = /* @__PURE__ */ new Map();
2982
2844
  for (const entry of change.added) changedValues.set(entry.key, entry.after);
2983
2845
  for (const entry of change.updated) changedValues.set(entry.key, entry.after);
2984
2846
  const nextIndexes = /* @__PURE__ */ new Map();
2985
- const values = Object.freeze(driver.read.ids().map((key, index) => {
2847
+ const result = Object.freeze(driver.read.ids().map((key, index) => {
2986
2848
  nextIndexes.set(key, index);
2987
- if (changedValues.has(key)) return changedValues.get(key);
2849
+ if (changedValues.has(key)) return make(key, changedValues.get(key));
2988
2850
  const previousIndex = indexes.get(key);
2989
- return previousIndex === void 0 ? driver.read.get(key) : previous[previousIndex];
2851
+ return previousIndex === void 0 ? make(key, driver.read.get(key)) : previous[previousIndex];
2990
2852
  }));
2991
2853
  indexes = nextIndexes;
2992
- return values;
2854
+ return result;
2993
2855
  };
2994
2856
  return {
2995
2857
  evaluate: (evaluation) => {
2996
2858
  const driver = evaluation.sources[0];
2997
2859
  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.");
2860
+ if (driver.kind !== "collection" || output.kind !== "value") throw new Error(`${name} requires collection input and value output.`);
2999
2861
  const change = driver.change;
3000
2862
  if (evaluation.reset || !change || change.kind === "reset") {
3001
2863
  output.output.set(rebuild(driver));
@@ -3006,7 +2868,7 @@ const createKeyedValues = (source) => {
3006
2868
  output.output.set(rebuild(driver));
3007
2869
  return;
3008
2870
  }
3009
- if (require_scheduler.collectionHasStructuralChange(change)) {
2871
+ if (require_transform.collectionHasStructuralChange(change)) {
3010
2872
  output.output.set(rebuildIncremental(driver, change, previous));
3011
2873
  return;
3012
2874
  }
@@ -3018,14 +2880,54 @@ const createKeyedValues = (source) => {
3018
2880
  output.output.set(rebuild(driver));
3019
2881
  return;
3020
2882
  }
3021
- next[index] = entry.after;
2883
+ next[index] = make(entry.key, entry.after);
3022
2884
  }
3023
2885
  output.output.set(Object.freeze(next));
3024
2886
  },
3025
2887
  release: () => indexes.clear()
3026
2888
  };
3027
2889
  },
3028
- name: "derive.keyed.values"
2890
+ name
2891
+ });
2892
+ return projection;
2893
+ };
2894
+ const createKeyedValues = (source) => createOrderedSnapshot(source, "derive.keyed.values", (_key, value) => value);
2895
+ const createKeyedEntries = (source) => createOrderedSnapshot(source, "derive.keyed.entries", (key, value) => Object.freeze([key, value]));
2896
+ const createKeyedGet = (source, key, equality = Object.is) => {
2897
+ require_transform.assertKeyedProjection(source, "derive.keyed.get source");
2898
+ if (require_transform.outputKind(key) !== "value") throw new TypeError("derive.keyed.get key must be a scalar projection.");
2899
+ const [projection] = require_transform.defineProcessor({
2900
+ dependencies: [source, key],
2901
+ outputs: [{
2902
+ kind: "value",
2903
+ equality
2904
+ }],
2905
+ create: () => {
2906
+ let sourceRevision = -1;
2907
+ let keyRevision = -1;
2908
+ let selected;
2909
+ return { evaluate: (evaluation) => {
2910
+ const collection = evaluation.sources[0];
2911
+ const selectedKey = evaluation.sources[1];
2912
+ const output = evaluation.outputs[0];
2913
+ if (collection.kind !== "collection" || selectedKey.kind !== "value" || output.kind !== "value") throw new Error("derive.keyed.get resolved invalid input/output kinds.");
2914
+ let affected = evaluation.reset;
2915
+ if (evaluation.reset || keyRevision !== selectedKey.revision) {
2916
+ const candidate = selectedKey.value;
2917
+ if (candidate !== void 0 && typeof candidate !== "string") throw new TypeError("derive.keyed.get key must resolve to a string or undefined.");
2918
+ selected = candidate;
2919
+ affected = true;
2920
+ } else if (sourceRevision !== collection.revision) {
2921
+ const change = collection.change;
2922
+ if (!change || change.kind === "reset") affected = true;
2923
+ else if (selected !== void 0) affected = require_transform.collectionChangeTouchesKey(change, selected);
2924
+ }
2925
+ if (affected) output.output.set(selected === void 0 ? void 0 : collection.read.get(selected));
2926
+ sourceRevision = collection.revision;
2927
+ keyRevision = selectedKey.revision;
2928
+ } };
2929
+ },
2930
+ name: "derive.keyed.get"
3029
2931
  });
3030
2932
  return projection;
3031
2933
  };
@@ -3033,28 +2935,40 @@ function createKeyedFilter(source, dependenciesOrPredicate, maybePredicate) {
3033
2935
  const dependencySpecs = typeof dependenciesOrPredicate === "function" ? void 0 : dependenciesOrPredicate;
3034
2936
  const predicate = dependencySpecs === void 0 ? dependenciesOrPredicate : maybePredicate;
3035
2937
  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");
2938
+ return require_transform.createKeyedTransform({
2939
+ source,
2940
+ dependencies: dependencySpecs,
2941
+ equality: Object.is,
2942
+ name: "derive.keyed.filter",
2943
+ evaluate: ({ value, key, dependencies }) => {
2944
+ const included = dependencySpecs === void 0 ? predicate(value, key) : predicate(value, key, dependencies);
2945
+ require_transform.assertSynchronous(included);
2946
+ if (typeof included !== "boolean") throw new TypeError("Keyed filter predicate must return boolean.");
2947
+ return included ? value : require_transform.keyedAbsent;
2948
+ }
2949
+ });
3042
2950
  }
3043
2951
  function createKeyedCompact(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
3044
2952
  const dependencySpecs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
3045
2953
  const select = dependencySpecs === void 0 ? dependenciesOrSelect : selectOrEquality;
3046
2954
  const equality = dependencySpecs === void 0 ? selectOrEquality : maybeEquality;
3047
2955
  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");
2956
+ return require_transform.createKeyedTransform({
2957
+ source,
2958
+ dependencies: dependencySpecs,
2959
+ equality,
2960
+ name: "derive.keyed.compact",
2961
+ evaluate: ({ value, key, dependencies }) => {
2962
+ const next = dependencySpecs === void 0 ? select(value, key) : select(value, key, dependencies);
2963
+ require_transform.assertSynchronous(next);
2964
+ return next === void 0 ? require_transform.keyedAbsent : next;
2965
+ }
2966
+ });
3053
2967
  }
3054
2968
  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.");
2969
+ require_transform.assertKeyedProjection(source, "Keyed subset source");
2970
+ const keyProjection = require_transform.isProjection(orderedKeys) ? orderedKeys : void 0;
2971
+ if (keyProjection && require_transform.outputKind(keyProjection) !== "value") throw new TypeError("Keyed subset ordered keys must be a value projection.");
3058
2972
  const staticSelection = keyProjection ? void 0 : (() => {
3059
2973
  const compiled = compileOrderedKeys(orderedKeys, "Keyed subset ordered keys");
3060
2974
  return {
@@ -3062,7 +2976,7 @@ const createKeyedSubset = (source, orderedKeys) => {
3062
2976
  requested: compiled.requested
3063
2977
  };
3064
2978
  })();
3065
- const [projection] = require_scheduler.defineProcessor({
2979
+ const [projection] = require_transform.defineProcessor({
3066
2980
  dependencies: keyProjection ? [source, keyProjection] : [source],
3067
2981
  outputs: [{
3068
2982
  kind: "collection",
@@ -3146,9 +3060,199 @@ const createKeyedSubset = (source, orderedKeys) => {
3146
3060
  });
3147
3061
  return projection;
3148
3062
  };
3063
+ const normalizeGroups = (value) => {
3064
+ const groups = typeof value === "string" ? [value] : value;
3065
+ if (!Array.isArray(groups)) throw new TypeError("derive.keyed.groupBy selector must return a string or an array of strings.");
3066
+ const seen = /* @__PURE__ */ new Set();
3067
+ const result = [];
3068
+ for (const group of groups) {
3069
+ if (typeof group !== "string") throw new TypeError("derive.keyed.groupBy selector must return only string group keys.");
3070
+ if (seen.has(group)) throw new TypeError("derive.keyed.groupBy selector must not return duplicate group keys.");
3071
+ seen.add(group);
3072
+ result.push(group);
3073
+ }
3074
+ return Object.freeze(result);
3075
+ };
3076
+ function createKeyedGroupBy(source, dependenciesOrSelector, maybeSelector) {
3077
+ require_transform.assertKeyedProjection(source, "derive.keyed.groupBy source");
3078
+ const dependencySpecs = typeof dependenciesOrSelector === "function" ? void 0 : dependenciesOrSelector;
3079
+ const selector = dependencySpecs === void 0 ? dependenciesOrSelector : maybeSelector;
3080
+ if (typeof selector !== "function") throw new TypeError("derive.keyed.groupBy requires a selector.");
3081
+ const compiled = require_transform.compileKeyedDependencies(source, dependencySpecs, "derive.keyed.groupBy");
3082
+ const [projection] = require_transform.defineProcessor({
3083
+ dependencies: compiled.projections,
3084
+ outputs: [{
3085
+ kind: "collection",
3086
+ equality: (previous, next) => {
3087
+ return require_transform.sameArray(previous, next);
3088
+ }
3089
+ }],
3090
+ create: () => {
3091
+ const dependencies = require_transform.createKeyedDependencyRuntime(compiled);
3092
+ const relation = require_transform.createKeyRelation();
3093
+ const orderIndex = /* @__PURE__ */ new Map();
3094
+ const groupRanks = /* @__PURE__ */ new Map();
3095
+ const refreshOrderIndex = (driver) => {
3096
+ orderIndex.clear();
3097
+ driver.read.ids().forEach((key, index) => orderIndex.set(key, index));
3098
+ };
3099
+ const selectGroups = (driver, sources, key, affected) => {
3100
+ if (!driver.read.has(key)) return false;
3101
+ const value = driver.read.get(key);
3102
+ const dependencyValues = dependencies.resolve(value, key, sources);
3103
+ const selected = dependencySpecs === void 0 ? selector(value, key) : selector(value, key, dependencyValues);
3104
+ require_transform.assertSynchronous(selected);
3105
+ const next = normalizeGroups(selected);
3106
+ const previous = relation.forward(key) ?? Object.freeze([]);
3107
+ if (!relation.replace(key, next)) return false;
3108
+ for (const group of previous) affected.add(group);
3109
+ for (const group of next) affected.add(group);
3110
+ return true;
3111
+ };
3112
+ const updateGroup = (group, output) => {
3113
+ const reverse = relation.reverse(group);
3114
+ if (!reverse?.size) {
3115
+ groupRanks.delete(group);
3116
+ output.output.remove(group);
3117
+ return;
3118
+ }
3119
+ const members = [...reverse].sort((left, right) => (orderIndex.get(left) ?? Number.MAX_SAFE_INTEGER) - (orderIndex.get(right) ?? Number.MAX_SAFE_INTEGER));
3120
+ const first = members[0];
3121
+ const source = orderIndex.get(first);
3122
+ const selector = relation.forward(first)?.indexOf(group) ?? -1;
3123
+ if (source === void 0 || selector < 0) throw new Error("derive.keyed.groupBy relation is inconsistent with source order.");
3124
+ groupRanks.set(group, {
3125
+ source,
3126
+ selector
3127
+ });
3128
+ output.output.set(group, Object.freeze(members));
3129
+ };
3130
+ const publishGroupOrder = (output) => {
3131
+ const order = [...groupRanks.keys()];
3132
+ order.sort((leftKey, rightKey) => {
3133
+ const left = groupRanks.get(leftKey);
3134
+ const right = groupRanks.get(rightKey);
3135
+ if (left.source !== right.source) return left.source - right.source;
3136
+ if (left.selector !== right.selector) return left.selector - right.selector;
3137
+ return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
3138
+ });
3139
+ output.output.order(order);
3140
+ };
3141
+ const rebuild = (driver, sources, output) => {
3142
+ dependencies.clearBindings();
3143
+ relation.clear();
3144
+ groupRanks.clear();
3145
+ refreshOrderIndex(driver);
3146
+ const affected = /* @__PURE__ */ new Set();
3147
+ for (const key of driver.read.ids()) selectGroups(driver, sources, key, affected);
3148
+ for (const key of output.previous.ids()) affected.add(key);
3149
+ for (const group of affected) updateGroup(group, output);
3150
+ publishGroupOrder(output);
3151
+ };
3152
+ const publishAffected = (affected, output, orderDirty) => {
3153
+ for (const group of affected) updateGroup(group, output);
3154
+ if (orderDirty) publishGroupOrder(output);
3155
+ };
3156
+ const removeSourceKey = (key, affected) => {
3157
+ const previous = relation.forward(key);
3158
+ if (!previous) return false;
3159
+ for (const group of previous) affected.add(group);
3160
+ return relation.delete(key);
3161
+ };
3162
+ const markAllGroups = (output, affected) => {
3163
+ for (const group of relation.rights()) affected.add(group);
3164
+ for (const group of output.previous.ids()) affected.add(group);
3165
+ };
3166
+ return {
3167
+ evaluate: (evaluation) => {
3168
+ const driver = evaluation.sources[0];
3169
+ const output = evaluation.outputs[0];
3170
+ if (driver.kind !== "collection" || output.kind !== "collection") throw new Error("derive.keyed.groupBy requires collection input and output.");
3171
+ dependencies.prepare(evaluation.sources, evaluation.reset);
3172
+ if (evaluation.reset) {
3173
+ rebuild(driver, evaluation.sources, output);
3174
+ dependencies.remember(evaluation.sources);
3175
+ return;
3176
+ }
3177
+ const dirty = /* @__PURE__ */ new Set();
3178
+ const affected = /* @__PURE__ */ new Set();
3179
+ let all = false;
3180
+ let structural = false;
3181
+ let orderDirty = false;
3182
+ if (dependencies.driverChanged(driver)) {
3183
+ const change = driver.change;
3184
+ if (!change || change.kind === "reset") {
3185
+ rebuild(driver, evaluation.sources, output);
3186
+ dependencies.remember(evaluation.sources);
3187
+ return;
3188
+ } else {
3189
+ for (const entry of change.removed) {
3190
+ orderDirty ||= removeSourceKey(entry.key, affected);
3191
+ dependencies.remove(entry.key);
3192
+ dirty.delete(entry.key);
3193
+ }
3194
+ for (const entry of change.added) dirty.add(entry.key);
3195
+ for (const entry of change.updated) dirty.add(entry.key);
3196
+ structural = Boolean(change.added.length || change.removed.length || change.order);
3197
+ orderDirty ||= structural;
3198
+ if (structural) refreshOrderIndex(driver);
3199
+ }
3200
+ }
3201
+ all ||= dependencies.collectInvalidated(evaluation.sources, dirty);
3202
+ if (all) for (const key of driver.read.ids()) dirty.add(key);
3203
+ for (const key of dirty) orderDirty ||= selectGroups(driver, evaluation.sources, key, affected);
3204
+ if (structural) markAllGroups(output, affected);
3205
+ publishAffected(affected, output, orderDirty);
3206
+ dependencies.remember(evaluation.sources);
3207
+ },
3208
+ release: () => {
3209
+ relation.clear();
3210
+ orderIndex.clear();
3211
+ groupRanks.clear();
3212
+ dependencies.release();
3213
+ }
3214
+ };
3215
+ },
3216
+ name: "derive.keyed.groupBy"
3217
+ });
3218
+ return projection;
3219
+ }
3220
+ const createKeyedSingleton = (source, keyOf, equality = Object.is) => {
3221
+ if (require_transform.outputKind(source) !== "value") throw new TypeError("derive.keyed.singleton source must be a scalar projection.");
3222
+ if (typeof keyOf !== "function") throw new TypeError("derive.keyed.singleton requires a key selector.");
3223
+ const [projection] = require_transform.defineProcessor({
3224
+ dependencies: [source],
3225
+ outputs: [{
3226
+ kind: "collection",
3227
+ equality
3228
+ }],
3229
+ create: () => ({ evaluate: (evaluation) => {
3230
+ const scalar = evaluation.sources[0];
3231
+ const output = evaluation.outputs[0];
3232
+ if (scalar.kind !== "value" || output.kind !== "collection") throw new Error("derive.keyed.singleton resolved invalid input/output kinds.");
3233
+ const previous = output.previous.ids();
3234
+ if (scalar.value === void 0) {
3235
+ for (const key of previous) output.output.remove(key);
3236
+ return;
3237
+ }
3238
+ const key = keyOf(scalar.value);
3239
+ require_transform.assertSynchronous(key);
3240
+ if (typeof key !== "string") throw new TypeError("derive.keyed.singleton key selector must return a string.");
3241
+ for (const previousKey of previous) if (previousKey !== key) output.output.remove(previousKey);
3242
+ output.output.set(key, scalar.value);
3243
+ if (previous.length !== 1 || previous[0] !== key) output.output.order([key]);
3244
+ } }),
3245
+ name: "derive.keyed.singleton"
3246
+ });
3247
+ return projection;
3248
+ };
3149
3249
  const keyedDerive = Object.assign(createKeyedDerive, {
3150
3250
  keys: createKeyedKeys,
3151
3251
  values: createKeyedValues,
3252
+ entries: createKeyedEntries,
3253
+ get: createKeyedGet,
3254
+ groupBy: createKeyedGroupBy,
3255
+ singleton: createKeyedSingleton,
3152
3256
  subset: createKeyedSubset,
3153
3257
  filter: createKeyedFilter,
3154
3258
  compact: createKeyedCompact
@@ -3156,10 +3260,10 @@ const keyedDerive = Object.assign(createKeyedDerive, {
3156
3260
  //#endregion
3157
3261
  //#region core/src/projection/derive.ts
3158
3262
  function createValueDerive(dependencies, compute, equality = Object.is) {
3159
- const compiled = require_scheduler.compileProjectionDependencies(dependencies, "Derive");
3263
+ const compiled = require_transform.compileProjectionDependencies(dependencies, "Derive");
3160
3264
  const names = compiled.names;
3161
3265
  const ordered = compiled.projections;
3162
- const [projection] = require_scheduler.defineProcessor({
3266
+ const [projection] = require_transform.defineProcessor({
3163
3267
  dependencies: ordered,
3164
3268
  outputs: [{
3165
3269
  kind: "value",
@@ -3169,9 +3273,9 @@ function createValueDerive(dependencies, compute, equality = Object.is) {
3169
3273
  const output = evaluation.outputs[0];
3170
3274
  if (output.kind !== "value") throw new Error("derive requires a value output.");
3171
3275
  const values = Object.create(null);
3172
- for (let index = 0; index < names.length; index++) values[names[index]] = require_scheduler.snapshotDependencyValue(evaluation.sources[index]);
3276
+ for (let index = 0; index < names.length; index++) values[names[index]] = require_transform.snapshotDependencyValue(evaluation.sources[index]);
3173
3277
  const next = compute(Object.freeze(values));
3174
- require_scheduler.assertSynchronous(next);
3278
+ require_transform.assertSynchronous(next);
3175
3279
  output.output.set(next);
3176
3280
  } })
3177
3281
  });
@@ -3182,7 +3286,7 @@ const derive = Object.assign(createValueDerive, { keyed: keyedDerive });
3182
3286
  //#region core/src/projection/collection/index.ts
3183
3287
  const height = (node) => node?.height ?? 0;
3184
3288
  const node = (key, value, left, right) => {
3185
- require_scheduler.profile.collectionIndex.node();
3289
+ require_transform.profile.collectionIndex.node();
3186
3290
  return Object.freeze({
3187
3291
  key,
3188
3292
  value,
@@ -3243,7 +3347,7 @@ var PersistentKeyedIndex = class PersistentKeyedIndex {
3243
3347
  }
3244
3348
  static from(entries) {
3245
3349
  const sorted = [...entries].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
3246
- require_scheduler.profile.collectionIndex.build(sorted.length);
3350
+ require_transform.profile.collectionIndex.build(sorted.length);
3247
3351
  return new PersistentKeyedIndex(fromSorted(sorted));
3248
3352
  }
3249
3353
  get(key) {
@@ -3300,13 +3404,14 @@ const createPublishedCollection = () => {
3300
3404
  },
3301
3405
  publish: (staged, nextIds, reset) => {
3302
3406
  if (reset) {
3303
- const entries = [];
3304
- for (const [key, entry] of staged) if (entry.present) entries.push([key, entry.value]);
3305
- index = PersistentKeyedIndex.from(entries);
3306
3407
  values.clear();
3307
- } else for (const [key, entry] of staged) index = entry.present ? index.set(key, entry.value) : index.remove(key);
3308
- for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
3309
- else values.delete(key);
3408
+ for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
3409
+ index = PersistentKeyedIndex.from(values);
3410
+ } else for (const [key, entry] of staged) {
3411
+ index = entry.present ? index.set(key, entry.value) : index.remove(key);
3412
+ if (entry.present) values.set(key, entry.value);
3413
+ else values.delete(key);
3414
+ }
3310
3415
  ids = nextIds;
3311
3416
  },
3312
3417
  release: () => {
@@ -3319,7 +3424,7 @@ const createPublishedCollection = () => {
3319
3424
  /** Owns staged keyed intent, exact transitions and published collection state. */
3320
3425
  const createCollectionOutput = (binding) => {
3321
3426
  const published = createPublishedCollection();
3322
- let staged = /* @__PURE__ */ new Map();
3427
+ const staged = /* @__PURE__ */ new Map();
3323
3428
  let nextIds = published.ids();
3324
3429
  let change;
3325
3430
  let initialized = false;
@@ -3335,12 +3440,15 @@ const createCollectionOutput = (binding) => {
3335
3440
  return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : published.get(key);
3336
3441
  };
3337
3442
  const deriveIds = () => {
3338
- const result = cleared ? [] : published.ids().filter(hasNext);
3443
+ const result = [];
3444
+ if (!cleared) {
3445
+ for (const key of published.ids()) if (hasNext(key)) result.push(key);
3446
+ }
3339
3447
  for (const [key, entry] of staged) if (entry.present && (cleared || !published.has(key))) result.push(key);
3340
3448
  return result;
3341
3449
  };
3342
3450
  const begin = (active, initialize) => {
3343
- staged = /* @__PURE__ */ new Map();
3451
+ staged.clear();
3344
3452
  nextIds = published.ids();
3345
3453
  change = void 0;
3346
3454
  explicitOrder = void 0;
@@ -3348,16 +3456,18 @@ const createCollectionOutput = (binding) => {
3348
3456
  reset = initialize;
3349
3457
  const read = (next) => ({
3350
3458
  get: (key) => {
3351
- require_scheduler.assertScope(active);
3459
+ require_transform.assertScope(active);
3352
3460
  return next ? getNext(key) : published.get(key);
3353
3461
  },
3354
3462
  has: (key) => {
3355
- require_scheduler.assertScope(active);
3463
+ require_transform.assertScope(active);
3356
3464
  return next ? hasNext(key) : published.has(key);
3357
3465
  },
3358
3466
  ids: () => {
3359
- require_scheduler.assertScope(active);
3360
- return next ? Object.freeze(explicitOrder ? [...explicitOrder] : [...deriveIds()]) : published.ids();
3467
+ require_transform.assertScope(active);
3468
+ if (!next) return published.ids();
3469
+ if (explicitOrder) return explicitOrder;
3470
+ return Object.freeze(deriveIds());
3361
3471
  }
3362
3472
  });
3363
3473
  return {
@@ -3365,7 +3475,7 @@ const createCollectionOutput = (binding) => {
3365
3475
  next: read(true),
3366
3476
  output: {
3367
3477
  set: (key, value) => {
3368
- require_scheduler.assertScope(active);
3478
+ require_transform.assertScope(active);
3369
3479
  if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
3370
3480
  staged.set(key, {
3371
3481
  present: true,
@@ -3373,12 +3483,12 @@ const createCollectionOutput = (binding) => {
3373
3483
  });
3374
3484
  },
3375
3485
  remove: (key) => {
3376
- require_scheduler.assertScope(active);
3486
+ require_transform.assertScope(active);
3377
3487
  staged.set(key, { present: false });
3378
3488
  },
3379
3489
  order: (order) => {
3380
- require_scheduler.assertScope(active);
3381
- explicitOrder = Object.freeze([...order]);
3490
+ require_transform.assertScope(active);
3491
+ explicitOrder = require_transform.snapshotArray(order);
3382
3492
  }
3383
3493
  }
3384
3494
  };
@@ -3387,9 +3497,7 @@ const createCollectionOutput = (binding) => {
3387
3497
  if (cleared) {
3388
3498
  for (const key of published.keys()) if (!staged.has(key)) staged.set(key, { present: false });
3389
3499
  }
3390
- require_scheduler.profile.projection("touchedKeys", staged.size);
3391
- const added = /* @__PURE__ */ new Set();
3392
- const removed = /* @__PURE__ */ new Set();
3500
+ require_transform.profile.projection("touchedKeys", staged.size);
3393
3501
  const addedEntries = [];
3394
3502
  const updatedEntries = [];
3395
3503
  const removedEntries = [];
@@ -3397,7 +3505,6 @@ const createCollectionOutput = (binding) => {
3397
3505
  const existed = published.has(key);
3398
3506
  if (entry.present) {
3399
3507
  if (!existed) {
3400
- added.add(key);
3401
3508
  addedEntries.push({
3402
3509
  key,
3403
3510
  after: entry.value
@@ -3413,22 +3520,31 @@ const createCollectionOutput = (binding) => {
3413
3520
  else if (!nextReset) staged.delete(key);
3414
3521
  continue;
3415
3522
  }
3416
- if (existed) {
3417
- removed.add(key);
3418
- removedEntries.push({
3419
- key,
3420
- before: published.get(key)
3421
- });
3422
- } else staged.delete(key);
3523
+ if (existed) removedEntries.push({
3524
+ key,
3525
+ before: published.get(key)
3526
+ });
3527
+ else staged.delete(key);
3423
3528
  }
3424
3529
  const ids = published.ids();
3425
- if (explicitOrder || added.size || removed.size) {
3426
- const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
3427
- 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
- nextIds = ids.length === order.length && ids.every((key, index) => key === order[index]) ? ids : Object.freeze([...order]);
3530
+ if (explicitOrder) {
3531
+ const expectedLength = published.size() + addedEntries.length - removedEntries.length;
3532
+ const seen = /* @__PURE__ */ new Set();
3533
+ for (const key of explicitOrder) {
3534
+ if (seen.has(key) || !hasNext(key)) throw new TypeError("Projection order must contain every key exactly once.");
3535
+ seen.add(key);
3536
+ }
3537
+ if (seen.size !== expectedLength) throw new TypeError("Projection order must contain every key exactly once.");
3538
+ nextIds = require_transform.sameArray(ids, explicitOrder) ? ids : explicitOrder;
3539
+ } else if (addedEntries.length || removedEntries.length) {
3540
+ const order = new Array(published.size() + addedEntries.length - removedEntries.length);
3541
+ let index = 0;
3542
+ for (const key of ids) if (hasNext(key)) order[index++] = key;
3543
+ for (const entry of addedEntries) order[index++] = entry.key;
3544
+ nextIds = require_transform.sameArray(ids, order) ? ids : Object.freeze(order);
3429
3545
  } 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({
3546
+ require_transform.profile.projection("changedKeys", addedEntries.length + updatedEntries.length + removedEntries.length);
3547
+ change = nextReset ? Object.freeze({ kind: "reset" }) : require_transform.createCollectionChange({
3432
3548
  added: addedEntries,
3433
3549
  updated: updatedEntries,
3434
3550
  removed: removedEntries,
@@ -3442,15 +3558,15 @@ const createCollectionOutput = (binding) => {
3442
3558
  kind: "collection",
3443
3559
  read: {
3444
3560
  get: (key) => {
3445
- require_scheduler.assertScope(active);
3561
+ require_transform.assertScope(active);
3446
3562
  return getNext(key);
3447
3563
  },
3448
3564
  has: (key) => {
3449
- require_scheduler.assertScope(active);
3565
+ require_transform.assertScope(active);
3450
3566
  return hasNext(key);
3451
3567
  },
3452
3568
  ids: () => {
3453
- require_scheduler.assertScope(active);
3569
+ require_transform.assertScope(active);
3454
3570
  return nextIds;
3455
3571
  }
3456
3572
  },
@@ -3476,7 +3592,7 @@ const createCollectionOutput = (binding) => {
3476
3592
  initialized = true;
3477
3593
  },
3478
3594
  emit: (call) => {
3479
- Array.from(listeners).forEach((listener) => call(listener));
3595
+ Array.from(listeners).forEach((listener) => call(listener, change));
3480
3596
  },
3481
3597
  hasConsumers: () => consumers.size > 0,
3482
3598
  forEachConsumer: (run) => consumers.forEach(run),
@@ -3532,12 +3648,12 @@ const createValueOutput = (binding) => {
3532
3648
  return {
3533
3649
  previous: initialized ? value : void 0,
3534
3650
  next: () => {
3535
- require_scheduler.assertScope(active);
3651
+ require_transform.assertScope(active);
3536
3652
  if (staged !== unset) return staged;
3537
3653
  return initialize ? void 0 : initialized ? value : void 0;
3538
3654
  },
3539
3655
  output: Object.freeze({ set: (candidate) => {
3540
- require_scheduler.assertScope(active);
3656
+ require_transform.assertScope(active);
3541
3657
  staged = candidate;
3542
3658
  } })
3543
3659
  };
@@ -3564,7 +3680,7 @@ const createValueOutput = (binding) => {
3564
3680
  begin,
3565
3681
  seal,
3566
3682
  context: (active) => {
3567
- require_scheduler.assertScope(active);
3683
+ require_transform.assertScope(active);
3568
3684
  return Object.freeze({
3569
3685
  kind: "value",
3570
3686
  value: next,
@@ -3585,8 +3701,8 @@ const createValueOutput = (binding) => {
3585
3701
  initialized = true;
3586
3702
  },
3587
3703
  emit: (call) => {
3588
- require_scheduler.profile.materialized.notification();
3589
- Array.from(listeners).forEach((listener) => call(listener));
3704
+ require_transform.profile.materialized.notification();
3705
+ Array.from(listeners).forEach((listener) => call(listener, void 0));
3590
3706
  },
3591
3707
  hasConsumers: () => consumers.size > 0,
3592
3708
  forEachConsumer: (run) => consumers.forEach(run),
@@ -3624,7 +3740,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
3624
3740
  let instance;
3625
3741
  let cause;
3626
3742
  const check = () => {
3627
- if (!scheduler.active || processor.disposed) throw new require_scheduler.ProjectionDisposedError();
3743
+ if (!scheduler.active || processor.disposed) throw new require_transform.ProjectionDisposedError();
3628
3744
  if (processor.fault) throw processor.fault;
3629
3745
  };
3630
3746
  const bound = definition.outputs.map((outputDefinition) => {
@@ -3678,8 +3794,8 @@ const createProcessor = (scheduler, definition, dependencies) => {
3678
3794
  instance = void 0;
3679
3795
  }
3680
3796
  if (!instance) instance = definition.create();
3681
- require_scheduler.profile.materialized[reset ? "rebuilt" : "updated"]();
3682
- require_scheduler.assertSynchronous(instance.evaluate({
3797
+ require_transform.profile.materialized[reset ? "rebuilt" : "updated"]();
3798
+ require_transform.assertSynchronous(instance.evaluate({
3683
3799
  reset,
3684
3800
  cause: runCause,
3685
3801
  sources,
@@ -3726,7 +3842,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
3726
3842
  try {
3727
3843
  scheduler.initialize(() => {
3728
3844
  const invalid = dependencies.find((output) => output.owner.fault || output.owner.disposed);
3729
- if (invalid) throw invalid.owner.fault ?? new require_scheduler.ProjectionDisposedError();
3845
+ if (invalid) throw invalid.owner.fault ?? new require_transform.ProjectionDisposedError();
3730
3846
  if (processor.evaluate(true, void 0).length) processor.publish();
3731
3847
  processor.clear();
3732
3848
  });
@@ -3741,6 +3857,21 @@ const createProcessor = (scheduler, definition, dependencies) => {
3741
3857
  return processor;
3742
3858
  };
3743
3859
  //#endregion
3860
+ //#region core/src/projection/readable/listeners.ts
3861
+ /** Exhaustively notifies an already-stable projection listener snapshot. */
3862
+ const notifyProjectionListenerSnapshot = (listeners, message) => {
3863
+ let failures;
3864
+ for (const listener of listeners) try {
3865
+ listener();
3866
+ } catch (error) {
3867
+ (failures ??= []).push(error);
3868
+ }
3869
+ if (failures?.length === 1) throw failures[0];
3870
+ if (failures?.length) throw new AggregateError(failures, message);
3871
+ };
3872
+ /** Snapshots a mutable listener collection before exhaustive notification. */
3873
+ const notifyProjectionListeners = (listeners, message) => notifyProjectionListenerSnapshot([...listeners], message);
3874
+ //#endregion
3744
3875
  //#region core/src/projection/readable/selection.ts
3745
3876
  let activeTracker;
3746
3877
  const trackKey = (key) => {
@@ -3793,9 +3924,8 @@ const trackedMap = (source) => {
3793
3924
  const selectionAffects = (selection, change) => {
3794
3925
  if (!change || change.kind === "reset") return true;
3795
3926
  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;
3798
- return false;
3927
+ if (selection.structure && require_transform.collectionHasStructuralChange(change)) return true;
3928
+ return require_transform.collectionChangeIntersects(change, selection.keys);
3799
3929
  };
3800
3930
  const createDirectReadable = (source) => Object.freeze({
3801
3931
  current: source.current,
@@ -3837,7 +3967,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
3837
3967
  activeTracker = previousTracker;
3838
3968
  }
3839
3969
  const nextSelection = Object.freeze({
3840
- keys: new Set(tracker.keys),
3970
+ keys: tracker.keys,
3841
3971
  all: tracker.all || isMapLike(current) && tracker.keys.size === 0 && !tracker.structure,
3842
3972
  structure: tracker.structure
3843
3973
  });
@@ -3862,7 +3992,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
3862
3992
  if (!change && previousCollection) {
3863
3993
  const current = source.current();
3864
3994
  if (isMapLike(current)) {
3865
- change = require_scheduler.diffCollection(previousCollection, current);
3995
+ change = require_transform.diffCollection(previousCollection, current);
3866
3996
  previousCollection = current;
3867
3997
  if (!change) {
3868
3998
  sourceRevision = source.revision();
@@ -3875,7 +4005,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
3875
4005
  updateCollectionSnapshot();
3876
4006
  return;
3877
4007
  }
3878
- if (evaluate()) Array.from(listeners).forEach((listener) => listener());
4008
+ if (evaluate()) notifyProjectionListeners(listeners, "Projection selector listeners failed.");
3879
4009
  };
3880
4010
  const installSource = () => {
3881
4011
  if (!unsubscribeSource) unsubscribeSource = source.subscribe(onProjectionChange);
@@ -3906,7 +4036,7 @@ const hasDocumentDirty = (dirty) => dirty.replace || dirty.order || dirty.treeRo
3906
4036
  const equivalent = (node, left, right) => node.kind === "field" ? Object.is(left, right) : equalValue(node, left, right);
3907
4037
  const replaceValue = (node, previous, current) => equivalent(node, previous, current) ? previous : copyValue(node, current);
3908
4038
  const cloneRecord = (value) => {
3909
- require_scheduler.profile.copy.shallowRecord(Object.getOwnPropertyNames(value).length);
4039
+ require_transform.profile.copy.shallowRecord(Object.getOwnPropertyNames(value).length);
3910
4040
  return Object.create(Object.getPrototypeOf(value), Object.getOwnPropertyDescriptors(value));
3911
4041
  };
3912
4042
  const materializeRecordChild = (node, previous, current, key, dirty) => {
@@ -4079,10 +4209,11 @@ const clearDocumentDirty = (dirty) => {
4079
4209
  dirty.treeNodes.clear();
4080
4210
  dirty.children.clear();
4081
4211
  };
4082
- const locate = (dirty, path) => {
4212
+ const locateRange = (dirty, path, start, tail) => {
4083
4213
  let current = dirty;
4084
- for (const key of path) {
4214
+ for (let index = start; index < path.length; index++) {
4085
4215
  if (current.replace) return void 0;
4216
+ const key = path[index];
4086
4217
  let child = current.children.get(key);
4087
4218
  if (!child) {
4088
4219
  child = createDocumentDirty();
@@ -4090,10 +4221,17 @@ const locate = (dirty, path) => {
4090
4221
  }
4091
4222
  current = child;
4092
4223
  }
4093
- return current;
4224
+ if (tail === void 0) return current;
4225
+ if (current.replace) return void 0;
4226
+ let child = current.children.get(tail);
4227
+ if (!child) {
4228
+ child = createDocumentDirty();
4229
+ current.children.set(tail, child);
4230
+ }
4231
+ return child;
4094
4232
  };
4095
- const markDocumentReplace = (dirty, path) => {
4096
- const current = locate(dirty, path);
4233
+ const replaceAt = (dirty, path, start, tail) => {
4234
+ const current = locateRange(dirty, path, start, tail);
4097
4235
  if (!current) return;
4098
4236
  current.replace = true;
4099
4237
  current.order = false;
@@ -4101,15 +4239,18 @@ const markDocumentReplace = (dirty, path) => {
4101
4239
  current.treeNodes.clear();
4102
4240
  current.children.clear();
4103
4241
  };
4104
- const markDocumentOrder = (dirty, path) => {
4105
- const current = locate(dirty, path);
4242
+ const markDocumentReplace = (dirty, path) => {
4243
+ replaceAt(dirty, path, 0);
4244
+ };
4245
+ const markDocumentOrderRange = (dirty, path, start) => {
4246
+ const current = locateRange(dirty, path, start);
4106
4247
  if (current && !current.replace) current.order = true;
4107
4248
  };
4108
- const markDocumentTree = (dirty, path, root, nodes) => {
4109
- const current = locate(dirty, path);
4249
+ const markDocumentTree = (dirty, path, start, root, nodes) => {
4250
+ const current = locateRange(dirty, path, start);
4110
4251
  if (!current || current.replace) return;
4111
4252
  current.treeRoot ||= root;
4112
- for (const id of nodes) current.treeNodes.add(id);
4253
+ for (const node of nodes) current.treeNodes.add(node.id);
4113
4254
  };
4114
4255
  const createCollectionPending = () => ({
4115
4256
  reset: false,
@@ -4139,19 +4280,19 @@ const mergeValueChanges = (selector, dirty, changes) => {
4139
4280
  const at = selector.address;
4140
4281
  for (const change of changes.changes) {
4141
4282
  if (change.kind === "reset") {
4142
- markDocumentReplace(dirty, []);
4283
+ replaceAt(dirty, at, at.length);
4143
4284
  return;
4144
4285
  }
4145
4286
  if (change.kind === "members") {
4146
4287
  for (const member of change.members) {
4147
- const changed = [...change.at, member.key];
4148
- if (contains(changed, at)) markDocumentReplace(dirty, []);
4149
- else if (contains(at, changed)) markDocumentReplace(dirty, changed.slice(at.length));
4288
+ const compared = compareExtended(change.at, member.key, at);
4289
+ if (compared === "ancestor" || compared === "equal") replaceAt(dirty, at, at.length);
4290
+ else if (compared === "descendant") replaceAt(dirty, change.at, at.length, member.key);
4150
4291
  }
4151
- if (change.order && contains(at, change.at)) markDocumentOrder(dirty, change.at.slice(at.length));
4292
+ if (change.order && contains(at, change.at)) markDocumentOrderRange(dirty, change.at, at.length);
4152
4293
  continue;
4153
4294
  }
4154
- if (contains(at, change.at)) markDocumentTree(dirty, change.at.slice(at.length), change.before !== change.after, change.nodes.map((node) => node.id));
4295
+ if (contains(at, change.at)) markDocumentTree(dirty, change.at, at.length, change.before !== change.after, change.nodes);
4155
4296
  }
4156
4297
  };
4157
4298
  const mergeCollectionChanges = (selector, pending, changes) => {
@@ -4163,13 +4304,15 @@ const mergeCollectionChanges = (selector, pending, changes) => {
4163
4304
  return;
4164
4305
  }
4165
4306
  if (selector.tree?.kind === "nodes") {
4166
- if (change.kind === "members") {
4167
- for (const member of change.members) if (contains([...change.at, member.key], at)) {
4307
+ if (change.kind === "members") for (const member of change.members) {
4308
+ const compared = compareExtended(change.at, member.key, at);
4309
+ if (compared === "ancestor" || compared === "equal") {
4168
4310
  resetCollectionPending(pending);
4169
4311
  return;
4170
4312
  }
4171
- } else if (change.at.length === at.length && contains(change.at, at)) for (const node of change.nodes) {
4172
- markDocumentReplace(dirtyEntry(pending, node.id), []);
4313
+ }
4314
+ else if (change.at.length === at.length && contains(change.at, at)) for (const node of change.nodes) {
4315
+ replaceAt(dirtyEntry(pending, node.id), at, at.length);
4173
4316
  pending.order ||= node.kind !== "updated";
4174
4317
  }
4175
4318
  else if (contains(change.at, at)) {
@@ -4180,39 +4323,32 @@ const mergeCollectionChanges = (selector, pending, changes) => {
4180
4323
  }
4181
4324
  if (change.kind === "members") {
4182
4325
  for (const member of change.members) {
4183
- const changed = [...change.at, member.key];
4184
- if (contains(changed, at)) {
4185
- resetCollectionPending(pending);
4186
- return;
4187
- }
4188
- if (!contains(at, changed)) continue;
4189
- const relative = changed.slice(at.length);
4190
- if (!relative.length) {
4326
+ const compared = compareExtended(change.at, member.key, at);
4327
+ if (compared === "ancestor" || compared === "equal") {
4191
4328
  resetCollectionPending(pending);
4192
4329
  return;
4193
4330
  }
4194
- markDocumentReplace(dirtyEntry(pending, relative[0]), relative.slice(1));
4195
- if (relative.length === 1 && member.kind !== "updated") pending.order = true;
4196
- }
4197
- if (change.order && contains(at, change.at)) {
4198
- const relative = change.at.slice(at.length);
4199
- if (!relative.length) pending.order = true;
4200
- else markDocumentOrder(dirtyEntry(pending, relative[0]), relative.slice(1));
4331
+ if (compared !== "descendant") continue;
4332
+ if (at.length === change.at.length) {
4333
+ replaceAt(dirtyEntry(pending, member.key), change.at, change.at.length);
4334
+ if (member.kind !== "updated") pending.order = true;
4335
+ } else replaceAt(dirtyEntry(pending, change.at[at.length]), change.at, at.length + 1, member.key);
4201
4336
  }
4337
+ if (change.order && contains(at, change.at)) if (change.at.length === at.length) pending.order = true;
4338
+ else markDocumentOrderRange(dirtyEntry(pending, change.at[at.length]), change.at, at.length + 1);
4202
4339
  continue;
4203
4340
  }
4204
4341
  if (!contains(at, change.at)) continue;
4205
- const relative = change.at.slice(at.length);
4206
- if (!relative.length) {
4342
+ if (change.at.length === at.length) {
4207
4343
  resetCollectionPending(pending);
4208
4344
  return;
4209
4345
  }
4210
- markDocumentTree(dirtyEntry(pending, relative[0]), relative.slice(1), change.before !== change.after, change.nodes.map((node) => node.id));
4346
+ markDocumentTree(dirtyEntry(pending, change.at[at.length]), change.at, at.length + 1, change.before !== change.after, change.nodes);
4211
4347
  }
4212
4348
  };
4213
4349
  //#endregion
4214
4350
  //#region core/src/projection/source/boundary.ts
4215
- const sourceError = (cause) => new require_scheduler.ProjectionError("source", cause);
4351
+ const sourceError = (cause) => new require_transform.ProjectionError("source", cause);
4216
4352
  const createValueBoundary = (scheduler, name, initial, equality, prepare, clearSource) => {
4217
4353
  let boundary;
4218
4354
  let pendingReset = false;
@@ -4221,7 +4357,7 @@ const createValueBoundary = (scheduler, name, initial, equality, prepare, clearS
4221
4357
  let recovering = false;
4222
4358
  let cleanup;
4223
4359
  const check = () => {
4224
- if (!scheduler.active || boundary.disposed) throw new require_scheduler.ProjectionDisposedError();
4360
+ if (!scheduler.active || boundary.disposed) throw new require_transform.ProjectionDisposedError();
4225
4361
  if (boundary.fault) throw boundary.fault;
4226
4362
  };
4227
4363
  const state = createValueOutput({
@@ -4328,24 +4464,37 @@ const stageCollectionRead = (state, read, active, options) => {
4328
4464
  const evaluation = state.begin(active, options.reset);
4329
4465
  if (options.reset) {
4330
4466
  const ids = read.ids();
4331
- require_scheduler.profile.collectionView.idsScanned(ids.length);
4467
+ require_transform.profile.collectionView.idsScanned(ids.length);
4332
4468
  for (const key of ids) {
4333
- require_scheduler.profile.collectionView.mapped();
4469
+ require_transform.profile.collectionView.mapped();
4334
4470
  evaluation.output.set(key, read.get(key));
4335
4471
  }
4336
4472
  evaluation.output.order(ids);
4337
4473
  } else {
4338
4474
  const previous = state.current();
4339
- const keys = options.candidates ? [...options.candidates] : [...new Set([...previous.ids(), ...read.ids()])];
4340
- if (!options.candidates) require_scheduler.profile.collectionView.idsScanned(keys.length);
4341
- for (const key of keys) if (read.has(key)) {
4342
- require_scheduler.profile.collectionView.mapped();
4475
+ let currentIds;
4476
+ const ids = () => currentIds ??= read.ids();
4477
+ if (options.candidates) for (const key of options.candidates) if (read.has(key)) {
4478
+ require_transform.profile.collectionView.mapped();
4343
4479
  evaluation.output.set(key, read.get(key));
4344
4480
  } else evaluation.output.remove(key);
4481
+ else {
4482
+ const previousIds = previous.ids();
4483
+ const nextIds = ids();
4484
+ require_transform.profile.collectionView.idsScanned(previousIds.length + nextIds.length);
4485
+ for (const key of previousIds) if (read.has(key)) {
4486
+ require_transform.profile.collectionView.mapped();
4487
+ evaluation.output.set(key, read.get(key));
4488
+ } else evaluation.output.remove(key);
4489
+ for (const key of nextIds) if (!previous.has(key)) {
4490
+ require_transform.profile.collectionView.mapped();
4491
+ evaluation.output.set(key, read.get(key));
4492
+ }
4493
+ }
4345
4494
  if (options.orderMayChange || !options.candidates) {
4346
- const ids = read.ids();
4347
- if (options.candidates) require_scheduler.profile.collectionView.idsScanned(ids.length);
4348
- evaluation.output.order(ids);
4495
+ const nextIds = ids();
4496
+ if (options.candidates) require_transform.profile.collectionView.idsScanned(nextIds.length);
4497
+ evaluation.output.order(nextIds);
4349
4498
  }
4350
4499
  }
4351
4500
  return state.seal(options.reset, options.isEqual ?? Object.is);
@@ -4361,7 +4510,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
4361
4510
  const candidates = /* @__PURE__ */ new Set();
4362
4511
  let cleanup;
4363
4512
  const check = () => {
4364
- if (!scheduler.active || boundary.disposed) throw new require_scheduler.ProjectionDisposedError();
4513
+ if (!scheduler.active || boundary.disposed) throw new require_transform.ProjectionDisposedError();
4365
4514
  if (boundary.fault) throw boundary.fault;
4366
4515
  };
4367
4516
  const state = createCollectionOutput({
@@ -4639,16 +4788,18 @@ const createDocumentSourceRegistry = (scheduler) => {
4639
4788
  const snapshots = /* @__PURE__ */ new Map();
4640
4789
  const replacement = createDocumentDirty();
4641
4790
  if (pending.reset) markDocumentReplace(replacement, []);
4791
+ let cachedIds;
4642
4792
  const ids = () => {
4643
- require_scheduler.assertScope(active);
4644
- if (treeNodes) return node?.kind === "tree" && require_record.isRecord(value) && require_record.isRecord(value.nodes) ? Object.freeze(Object.keys(value.nodes)) : Object.freeze([]);
4645
- if (node?.kind === "map" && require_record.isRecord(value)) return Object.freeze(Object.keys(value));
4646
- if (node?.kind === "table" && require_record.isRecord(value) && Array.isArray(value.ids)) return Object.freeze([...value.ids]);
4647
- if (node?.kind === "list" && Array.isArray(value)) return Object.freeze(toArray(indexedKeys(value, node.keyOf)));
4648
- return Object.freeze([]);
4793
+ require_transform.assertScope(active);
4794
+ if (cachedIds) return cachedIds;
4795
+ if (treeNodes) return cachedIds = node?.kind === "tree" && require_record.isRecord(value) && require_record.isRecord(value.nodes) ? Object.freeze(Object.keys(value.nodes)) : Object.freeze([]);
4796
+ if (node?.kind === "map" && require_record.isRecord(value)) return cachedIds = Object.freeze(Object.keys(value));
4797
+ if (node?.kind === "table" && require_record.isRecord(value) && Array.isArray(value.ids)) return cachedIds = Object.freeze([...value.ids]);
4798
+ if (node?.kind === "list" && Array.isArray(value)) return cachedIds = Object.freeze(toArray(indexedKeys(value, node.keyOf)));
4799
+ return cachedIds = Object.freeze([]);
4649
4800
  };
4650
4801
  const has = (key) => {
4651
- require_scheduler.assertScope(active);
4802
+ require_transform.assertScope(active);
4652
4803
  if (treeNodes) return Boolean(node?.kind === "tree" && require_record.isRecord(value) && require_record.isRecord(value.nodes) && Object.hasOwn(value.nodes, key));
4653
4804
  if (node?.kind === "map" && require_record.isRecord(value)) return Object.hasOwn(value, key);
4654
4805
  if (node?.kind === "table" && require_record.isRecord(value) && require_record.isRecord(value.byId)) return Object.hasOwn(value.byId, key);
@@ -4669,7 +4820,7 @@ const createDocumentSourceRegistry = (scheduler) => {
4669
4820
  };
4670
4821
  return Object.freeze({
4671
4822
  get: (key) => {
4672
- require_scheduler.assertScope(active);
4823
+ require_transform.assertScope(active);
4673
4824
  if (!snapshots.has(key)) if (!has(key)) snapshots.set(key, void 0);
4674
4825
  else {
4675
4826
  const current = raw(key);
@@ -5016,7 +5167,7 @@ const materializeInputSource = (scheduler, definition) => {
5016
5167
  }
5017
5168
  if (source.kind !== "collection-input") throw new Error("Invalid projection input source.");
5018
5169
  const values = new Map(source.initial);
5019
- const boundary = createCollectionBoundary(scheduler, "projection collection input", () => require_scheduler.mapRead(values), definition.output.equality, () => void 0);
5170
+ const boundary = createCollectionBoundary(scheduler, "projection collection input", () => require_transform.mapRead(values), definition.output.equality, () => void 0);
5020
5171
  return {
5021
5172
  ...boundary,
5022
5173
  write: {
@@ -5029,17 +5180,17 @@ const materializeInputSource = (scheduler, definition) => {
5029
5180
  let active = true;
5030
5181
  const draft = Object.freeze({
5031
5182
  get: (key) => {
5032
- require_scheduler.assertScope(() => active);
5183
+ require_transform.assertScope(() => active);
5033
5184
  const entry = staged.get(key);
5034
5185
  return entry ? entry.present ? entry.value : void 0 : values.get(key);
5035
5186
  },
5036
5187
  has: (key) => {
5037
- require_scheduler.assertScope(() => active);
5188
+ require_transform.assertScope(() => active);
5038
5189
  const entry = staged.get(key);
5039
5190
  return entry ? entry.present : values.has(key);
5040
5191
  },
5041
5192
  set: (key, value) => {
5042
- require_scheduler.assertScope(() => active);
5193
+ require_transform.assertScope(() => active);
5043
5194
  if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
5044
5195
  const previous = staged.get(key);
5045
5196
  structural ||= previous ? !previous.present : !values.has(key);
@@ -5054,7 +5205,7 @@ const materializeInputSource = (scheduler, definition) => {
5054
5205
  });
5055
5206
  },
5056
5207
  remove: (key) => {
5057
- require_scheduler.assertScope(() => active);
5208
+ require_transform.assertScope(() => active);
5058
5209
  if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
5059
5210
  const previous = staged.get(key);
5060
5211
  const present = previous ? previous.present : values.has(key);
@@ -5067,7 +5218,7 @@ const materializeInputSource = (scheduler, definition) => {
5067
5218
  }
5068
5219
  });
5069
5220
  try {
5070
- require_scheduler.assertSynchronous(run(draft));
5221
+ require_transform.assertSynchronous(run(draft));
5071
5222
  } finally {
5072
5223
  active = false;
5073
5224
  }
@@ -5132,19 +5283,249 @@ const createSourceRegistry = (scheduler) => {
5132
5283
  };
5133
5284
  };
5134
5285
  //#endregion
5286
+ //#region core/src/projection/readable/keyed.ts
5287
+ /** Runtime-owned keyed consumer family over one materialized collection output. */
5288
+ const createProjectionItems = (source, checkOwner) => {
5289
+ if (source.kind !== "collection") throw new TypeError("Projection items require a keyed collection projection.");
5290
+ let disposed = false;
5291
+ const active = /* @__PURE__ */ new Map();
5292
+ const followers = /* @__PURE__ */ new Map();
5293
+ const waiting = /* @__PURE__ */ new Map();
5294
+ let keysValue = Object.freeze([...source.current().keys()]);
5295
+ let keysRevision = 0;
5296
+ const keyListeners = /* @__PURE__ */ new Set();
5297
+ const check = () => {
5298
+ if (disposed) throw new require_transform.ProjectionDisposedError();
5299
+ checkOwner();
5300
+ };
5301
+ const notifications = [];
5302
+ const notify = (listeners) => {
5303
+ for (const listener of listeners) notifications.push(listener);
5304
+ };
5305
+ const flushNotifications = () => {
5306
+ try {
5307
+ notifyProjectionListenerSnapshot(notifications, "Projection item listeners failed.");
5308
+ } finally {
5309
+ notifications.length = 0;
5310
+ }
5311
+ };
5312
+ const unregisterWaiting = (state) => {
5313
+ const states = waiting.get(state.key);
5314
+ states?.delete(state);
5315
+ if (states?.size === 0) waiting.delete(state.key);
5316
+ };
5317
+ const registerWaiting = (state) => {
5318
+ if (state.tracked || state.ended) return;
5319
+ const states = waiting.get(state.key);
5320
+ if (states) states.add(state);
5321
+ else waiting.set(state.key, new Set([state]));
5322
+ };
5323
+ const publishState = (state, value) => {
5324
+ if (state.ended) return;
5325
+ if (Object.is(state.value, value)) {
5326
+ state.value = value;
5327
+ return;
5328
+ }
5329
+ state.value = value;
5330
+ state.revision++;
5331
+ notify(state.listeners);
5332
+ };
5333
+ const attachActive = (state, value) => {
5334
+ if (state.ended) return;
5335
+ unregisterWaiting(state);
5336
+ state.tracked = true;
5337
+ const canonical = active.get(state.key);
5338
+ if (!canonical) active.set(state.key, state);
5339
+ else if (canonical !== state) {
5340
+ const states = followers.get(state.key);
5341
+ if (states) states.add(state);
5342
+ else followers.set(state.key, new Set([state]));
5343
+ }
5344
+ publishState(state, value);
5345
+ };
5346
+ const terminateState = (state) => {
5347
+ if (state.ended) return;
5348
+ unregisterWaiting(state);
5349
+ state.tracked = false;
5350
+ publishState(state, void 0);
5351
+ state.ended = true;
5352
+ };
5353
+ const terminateKey = (key) => {
5354
+ const canonical = active.get(key);
5355
+ if (canonical) terminateState(canonical);
5356
+ const extra = followers.get(key);
5357
+ if (extra) for (const state of extra) terminateState(state);
5358
+ active.delete(key);
5359
+ followers.delete(key);
5360
+ };
5361
+ const activateWaiting = (key, value) => {
5362
+ const states = waiting.get(key);
5363
+ if (!states?.size) return;
5364
+ waiting.delete(key);
5365
+ for (const state of states) attachActive(state, value);
5366
+ };
5367
+ const updateKey = (key, value) => {
5368
+ const canonical = active.get(key);
5369
+ if (canonical) publishState(canonical, value);
5370
+ const extra = followers.get(key);
5371
+ if (extra) for (const state of extra) publishState(state, value);
5372
+ };
5373
+ const updateKeys = (current) => {
5374
+ if (require_transform.iterableMatchesArray(current.keys(), keysValue)) return;
5375
+ keysValue = Object.freeze([...current.keys()]);
5376
+ keysRevision++;
5377
+ notify(keyListeners);
5378
+ };
5379
+ const createItem = (key, initial) => {
5380
+ const state = {
5381
+ key,
5382
+ listeners: /* @__PURE__ */ new Set(),
5383
+ tracked: initial !== void 0,
5384
+ ended: false,
5385
+ value: initial?.value,
5386
+ revision: 0
5387
+ };
5388
+ const ensureObserved = () => {
5389
+ if (state.ended || state.tracked) return;
5390
+ const current = source.current();
5391
+ if (current.has(key)) attachActive(state, current.get(key));
5392
+ };
5393
+ state.readable = Object.freeze({
5394
+ current: () => {
5395
+ check();
5396
+ ensureObserved();
5397
+ return state.value;
5398
+ },
5399
+ revision: () => {
5400
+ check();
5401
+ ensureObserved();
5402
+ return state.revision;
5403
+ },
5404
+ subscribe: (listener) => {
5405
+ check();
5406
+ ensureObserved();
5407
+ state.listeners.add(listener);
5408
+ if (!state.tracked && !state.ended) registerWaiting(state);
5409
+ return () => {
5410
+ state.listeners.delete(listener);
5411
+ if (!state.listeners.size && !state.tracked) unregisterWaiting(state);
5412
+ };
5413
+ }
5414
+ });
5415
+ return state;
5416
+ };
5417
+ const reconcile = (current) => {
5418
+ for (const key of active.keys()) if (!current.has(key)) terminateKey(key);
5419
+ else updateKey(key, current.get(key));
5420
+ for (const key of waiting.keys()) if (current.has(key)) activateWaiting(key, current.get(key));
5421
+ updateKeys(current);
5422
+ };
5423
+ const onChange = (change) => {
5424
+ check();
5425
+ const current = source.current();
5426
+ if (!change || change.kind === "reset") {
5427
+ reconcile(current);
5428
+ flushNotifications();
5429
+ return;
5430
+ }
5431
+ for (const entry of change.removed) terminateKey(entry.key);
5432
+ for (const entry of change.added) activateWaiting(entry.key, entry.after);
5433
+ for (const entry of change.updated) updateKey(entry.key, entry.after);
5434
+ if (change.added.length || change.removed.length || change.order) updateKeys(current);
5435
+ flushNotifications();
5436
+ };
5437
+ const stop = source.subscribe(onChange);
5438
+ const keys = Object.freeze({
5439
+ current: () => {
5440
+ check();
5441
+ return keysValue;
5442
+ },
5443
+ revision: () => {
5444
+ check();
5445
+ return keysRevision;
5446
+ },
5447
+ subscribe: (listener) => {
5448
+ check();
5449
+ keyListeners.add(listener);
5450
+ return () => keyListeners.delete(listener);
5451
+ }
5452
+ });
5453
+ return {
5454
+ items: Object.freeze({
5455
+ keys,
5456
+ get: (key) => {
5457
+ check();
5458
+ const canonical = active.get(key);
5459
+ if (canonical) return canonical.readable;
5460
+ const pending = waiting.get(key)?.values().next().value;
5461
+ if (pending) return pending.readable;
5462
+ const current = source.current();
5463
+ if (current.has(key)) {
5464
+ const state = createItem(key, {
5465
+ present: true,
5466
+ value: current.get(key)
5467
+ });
5468
+ active.set(key, state);
5469
+ return state.readable;
5470
+ }
5471
+ return createItem(key).readable;
5472
+ }
5473
+ }),
5474
+ dispose() {
5475
+ if (disposed) return;
5476
+ disposed = true;
5477
+ stop();
5478
+ keyListeners.clear();
5479
+ const known = /* @__PURE__ */ new Set();
5480
+ active.forEach((state) => known.add(state));
5481
+ followers.forEach((states) => states.forEach((state) => known.add(state)));
5482
+ waiting.forEach((states) => states.forEach((state) => known.add(state)));
5483
+ known.forEach((state) => state.listeners.clear());
5484
+ active.clear();
5485
+ followers.clear();
5486
+ waiting.clear();
5487
+ notifications.length = 0;
5488
+ }
5489
+ };
5490
+ };
5491
+ //#endregion
5135
5492
  //#region core/src/projection/runtime.ts
5136
5493
  const createProjectionRuntime = (options) => {
5137
- const scheduler = require_scheduler.createScheduler(options?.onError ?? (() => void 0));
5494
+ const scheduler = require_transform.createScheduler(options?.onError ?? (() => void 0));
5138
5495
  const sources = createSourceRegistry(scheduler);
5139
5496
  const materialized = /* @__PURE__ */ new WeakMap();
5140
5497
  const publicViews = /* @__PURE__ */ new WeakMap();
5141
5498
  const scopes = /* @__PURE__ */ new Set();
5499
+ const rootItems = /* @__PURE__ */ new Map();
5500
+ const disposeItemControllers = (registry, failures) => {
5501
+ const controllers = [...registry.values()];
5502
+ registry.clear();
5503
+ for (const controller of controllers) try {
5504
+ controller.dispose();
5505
+ } catch (error) {
5506
+ failures.push(error);
5507
+ }
5508
+ };
5509
+ const disposeScopeConsumers = (state, failures) => {
5510
+ disposeItemControllers(state.items, failures);
5511
+ const subscriptions = [...state.subscriptions];
5512
+ state.subscriptions.clear();
5513
+ for (const unsubscribe of subscriptions) try {
5514
+ unsubscribe();
5515
+ } catch (error) {
5516
+ failures.push(error);
5517
+ }
5518
+ };
5519
+ const throwCleanupFailures = (failures, message) => {
5520
+ if (failures.length === 1) throw failures[0];
5521
+ if (failures.length) throw new AggregateError(failures, message);
5522
+ };
5142
5523
  const assertAccess = (definition, requester) => {
5143
5524
  scheduler.assertActive();
5144
5525
  const owner = definition.owner;
5145
5526
  if (owner === void 0) return;
5146
5527
  if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
5147
- if (!requester?.active) throw new require_scheduler.ProjectionDisposedError();
5528
+ if (!requester?.active) throw new require_transform.ProjectionDisposedError();
5148
5529
  };
5149
5530
  const rememberScoped = (definition, requester) => {
5150
5531
  if (requester && definition.owner === requester) requester.materialized.push(definition);
@@ -5162,7 +5543,7 @@ const createProjectionRuntime = (options) => {
5162
5543
  ...source.write ? { write: source.write } : {}
5163
5544
  });
5164
5545
  } 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.");
5546
+ 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
5547
  const dependencies = definition.dependencies.map((dependency) => resolveOutput(dependency, requester));
5167
5548
  const processor = createProcessor(scheduler, definition, Object.freeze(dependencies));
5168
5549
  result = Object.freeze({
@@ -5176,14 +5557,14 @@ const createProjectionRuntime = (options) => {
5176
5557
  return result;
5177
5558
  };
5178
5559
  const resolveOutput = (projection, requester) => {
5179
- const ref = require_scheduler.projectionRef(projection);
5560
+ const ref = require_transform.projectionRef(projection);
5180
5561
  const output = materializeProducer(ref.producer, requester).outputs[ref.output];
5181
5562
  if (!output) throw new TypeError("Projection output does not exist.");
5182
5563
  return output;
5183
5564
  };
5184
5565
  const assertOutput = (output) => {
5185
5566
  scheduler.assertActive();
5186
- if (output.owner.disposed) throw new require_scheduler.ProjectionDisposedError();
5567
+ if (output.owner.disposed) throw new require_transform.ProjectionDisposedError();
5187
5568
  if (output.owner.fault) throw output.owner.fault;
5188
5569
  };
5189
5570
  const publicCurrent = (output) => {
@@ -5193,7 +5574,7 @@ const createProjectionRuntime = (options) => {
5193
5574
  const cached = publicViews.get(output);
5194
5575
  if (cached?.revision === revision) return cached.value;
5195
5576
  if (output.kind === "collection") {
5196
- const value = require_scheduler.collectionView(current);
5577
+ const value = require_transform.collectionView(current);
5197
5578
  publicViews.set(output, {
5198
5579
  revision,
5199
5580
  value
@@ -5201,7 +5582,7 @@ const createProjectionRuntime = (options) => {
5201
5582
  return value;
5202
5583
  }
5203
5584
  if (isMapLike(current)) {
5204
- const value = require_scheduler.snapshotCollectionView(require_scheduler.mapRead(current));
5585
+ const value = require_transform.snapshotCollectionView(require_transform.mapRead(current));
5205
5586
  publicViews.set(output, {
5206
5587
  revision,
5207
5588
  value
@@ -5224,6 +5605,19 @@ const createProjectionRuntime = (options) => {
5224
5605
  subscribe: output.subscribe
5225
5606
  });
5226
5607
  };
5608
+ const projectionItems = (projection, requester) => {
5609
+ const output = resolveOutput(projection, requester);
5610
+ if (output.kind !== "collection") throw new TypeError("Projection items require a keyed collection projection.");
5611
+ const registry = requester?.items ?? rootItems;
5612
+ const existing = registry.get(output);
5613
+ if (existing) return existing.items;
5614
+ const controller = createProjectionItems(readableSource(projection, requester), () => {
5615
+ if (requester && !requester.active) throw new require_transform.ProjectionDisposedError();
5616
+ assertOutput(output);
5617
+ });
5618
+ registry.set(output, controller);
5619
+ return controller.items;
5620
+ };
5227
5621
  const makeReadable = (projection, selector, equality, requester) => {
5228
5622
  const source = readableSource(projection, requester);
5229
5623
  return selector ? createSelectorReadable(source, selector, equality) : createDirectReadable(source);
@@ -5232,7 +5626,7 @@ const createProjectionRuntime = (options) => {
5232
5626
  return makeReadable(projection, selector, equality);
5233
5627
  }
5234
5628
  const inputWrite = (target, requester) => {
5235
- const ref = require_scheduler.projectionRef(target);
5629
+ const ref = require_transform.projectionRef(target);
5236
5630
  if (ref.output !== 0 || ref.producer.kind !== "source") throw new TypeError("Projection is not an input.");
5237
5631
  if (ref.producer.source.kind !== "value-input" && ref.producer.source.kind !== "collection-input") throw new TypeError("Projection is not an input.");
5238
5632
  const write = materializeProducer(ref.producer, requester).write;
@@ -5257,13 +5651,7 @@ const createProjectionRuntime = (options) => {
5257
5651
  scheduler.assertIdle();
5258
5652
  state.active = false;
5259
5653
  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
- }
5654
+ disposeScopeConsumers(state, failures);
5267
5655
  for (let index = state.materialized.length - 1; index >= 0; index--) {
5268
5656
  const definition = state.materialized[index];
5269
5657
  const current = materialized.get(definition);
@@ -5278,25 +5666,26 @@ const createProjectionRuntime = (options) => {
5278
5666
  }
5279
5667
  state.materialized.length = 0;
5280
5668
  scopes.delete(state);
5281
- if (failures.length) throw failures[0];
5669
+ throwCleanupFailures(failures, "Projection scope cleanup failed.");
5282
5670
  };
5283
5671
  const createScope = () => {
5284
5672
  scheduler.assertIdle();
5285
5673
  const state = {
5286
5674
  active: true,
5287
5675
  materialized: [],
5288
- subscriptions: /* @__PURE__ */ new Set()
5676
+ subscriptions: /* @__PURE__ */ new Set(),
5677
+ items: /* @__PURE__ */ new Map()
5289
5678
  };
5290
5679
  scopes.add(state);
5291
5680
  const assertActive = () => {
5292
- if (!state.active) throw new require_scheduler.ProjectionDisposedError();
5681
+ if (!state.active) throw new require_transform.ProjectionDisposedError();
5293
5682
  scheduler.assertActive();
5294
5683
  };
5295
5684
  function own(target) {
5296
5685
  assertActive();
5297
5686
  const projections = [];
5298
5687
  const collect = (value) => {
5299
- if (require_scheduler.isProjection(value)) {
5688
+ if (require_transform.isProjection(value)) {
5300
5689
  projections.push(value);
5301
5690
  return;
5302
5691
  }
@@ -5306,13 +5695,13 @@ const createProjectionRuntime = (options) => {
5306
5695
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
5307
5696
  if (!descriptor?.enumerable || !("value" in descriptor)) throw new TypeError("Projection ownership tree must use enumerable data properties.");
5308
5697
  const child = descriptor.value;
5309
- if (!require_scheduler.isProjection(child) && !require_record.isPlainObject(child)) throw new TypeError("Projection ownership tree leaves must be projections.");
5698
+ if (!require_transform.isProjection(child) && !require_record.isPlainObject(child)) throw new TypeError("Projection ownership tree leaves must be projections.");
5310
5699
  collect(child);
5311
5700
  }
5312
5701
  };
5313
5702
  collect(target);
5314
5703
  if (!projections.length) throw new TypeError("Projection ownership tree must contain a projection.");
5315
- require_scheduler.ownProjections(projections, state);
5704
+ require_transform.ownProjections(projections, state);
5316
5705
  return target;
5317
5706
  }
5318
5707
  function scopeSelect(projection, selector, equality) {
@@ -5357,6 +5746,10 @@ const createProjectionRuntime = (options) => {
5357
5746
  return readProjection(projection, state);
5358
5747
  },
5359
5748
  select: scopeSelect,
5749
+ items: (projection) => {
5750
+ assertActive();
5751
+ return projectionItems(projection, state);
5752
+ },
5360
5753
  update: scopeUpdate,
5361
5754
  batch: scopeBatch,
5362
5755
  dispose: () => disposeScope(state)
@@ -5365,6 +5758,7 @@ const createProjectionRuntime = (options) => {
5365
5758
  return Object.freeze({
5366
5759
  read: readProjection,
5367
5760
  select: selectProjection,
5761
+ items: projectionItems,
5368
5762
  update,
5369
5763
  batch,
5370
5764
  scope: createScope,
@@ -5374,16 +5768,11 @@ const createProjectionRuntime = (options) => {
5374
5768
  const failures = [];
5375
5769
  for (const scope of scopes) {
5376
5770
  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
- }
5771
+ disposeScopeConsumers(scope, failures);
5384
5772
  scope.materialized.length = 0;
5385
5773
  }
5386
5774
  scopes.clear();
5775
+ disposeItemControllers(rootItems, failures);
5387
5776
  try {
5388
5777
  sources.dispose();
5389
5778
  } catch (error) {
@@ -5394,8 +5783,7 @@ const createProjectionRuntime = (options) => {
5394
5783
  } catch (error) {
5395
5784
  failures.push(error);
5396
5785
  }
5397
- if (failures.length === 1) throw failures[0];
5398
- if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
5786
+ throwCleanupFailures(failures, "Projection cleanup failed.");
5399
5787
  }
5400
5788
  });
5401
5789
  };
@@ -5403,8 +5791,8 @@ const createProjectionRuntime = (options) => {
5403
5791
  exports.DocumentDisposedError = DocumentDisposedError;
5404
5792
  exports.DocumentReentrancyError = DocumentReentrancyError;
5405
5793
  exports.ParseError = ParseError;
5406
- exports.ProjectionDisposedError = require_scheduler.ProjectionDisposedError;
5407
- exports.ProjectionError = require_scheduler.ProjectionError;
5794
+ exports.ProjectionDisposedError = require_transform.ProjectionDisposedError;
5795
+ exports.ProjectionError = require_transform.ProjectionError;
5408
5796
  exports.TransactionRejected = TransactionRejected;
5409
5797
  exports.createDocument = createDocument;
5410
5798
  exports.createProjectionRuntime = createProjectionRuntime;