doxum 0.1.27 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +66 -3
  2. package/dist/advanced.cjs +40 -14
  3. package/dist/advanced.cjs.map +1 -1
  4. package/dist/advanced.d.cts +27 -2
  5. package/dist/advanced.d.ts +27 -2
  6. package/dist/advanced.js +27 -1
  7. package/dist/advanced.js.map +1 -1
  8. package/dist/{definition-xgk3YNBp.d.ts → dependency-B3E65M1E.d.cts} +27 -3
  9. package/dist/{definition-CDOuvb5C.d.cts → dependency-BYbdDHNa.d.ts} +27 -3
  10. package/dist/driver-B5vdPohL.js.map +1 -1
  11. package/dist/driver-BL91gn7_.cjs.map +1 -1
  12. package/dist/index.cjs +663 -344
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +23 -32
  15. package/dist/index.d.ts +23 -32
  16. package/dist/index.js +549 -230
  17. package/dist/index.js.map +1 -1
  18. package/dist/local-sync.cjs.map +1 -1
  19. package/dist/local-sync.js.map +1 -1
  20. package/dist/{scheduler-vzf3OcjT.js → transform-B4T3AcJr.js} +356 -11
  21. package/dist/transform-B4T3AcJr.js.map +1 -0
  22. package/dist/{scheduler-k1ZGR1PG.cjs → transform-DUleAsG0.cjs} +393 -12
  23. package/dist/transform-DUleAsG0.cjs.map +1 -0
  24. package/package.json +1 -1
  25. package/skills/doxum-runtime/SKILL.md +11 -5
  26. package/skills/doxum-runtime/references/api.en.md +40 -21
  27. package/skills/doxum-runtime/references/api.zh-CN.md +21 -2
  28. package/skills/doxum-runtime/references/guide.en.md +2 -1
  29. package/skills/doxum-runtime/references/guide.zh-CN.md +2 -1
  30. package/skills/doxum-runtime/references/invariants.en.md +2 -2
  31. package/skills/doxum-runtime/references/invariants.zh-CN.md +2 -2
  32. package/skills/doxum-runtime/references/patterns.en.md +1 -1
  33. package/skills/doxum-runtime/references/patterns.zh-CN.md +1 -1
  34. package/skills/doxum-runtime/references/projections.en.md +47 -3
  35. package/skills/doxum-runtime/references/projections.zh-CN.md +45 -3
  36. package/dist/scheduler-k1ZGR1PG.cjs.map +0 -1
  37. package/dist/scheduler-vzf3OcjT.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { S as profile, _ as isProjection, a as ProjectionError, b as producerOf, c as createCollectionChange, d as snapshotDependencyValue, f as collectionView, g as defineSource, h as defineProcessor, i as ProjectionDisposedError, l as diffCollection, m as snapshotCollectionView, n as assertSynchronous, o as collectionChange, p as mapRead, r as createScheduler, s as collectionHasStructuralChange, t as assertScope, u as compileProjectionDependencies, v as outputDefinitionOf, x as projectionRef, y as ownProjections } from "./scheduler-vzf3OcjT.js";
1
+ import { C as defineSource, D as projectionRef, E as producerOf, O as profile, S as defineProcessor, T as ownProjections, _ as compileProjectionDependencies, a as createKeyedDependencyRuntime, b as mapRead, c as assertScope, d as ProjectionDisposedError, f as ProjectionError, g as diffCollection, h as createCollectionChange, i as compileKeyedDependencies, l as assertSynchronous, m as collectionHasStructuralChange, n as keyedAbsent, o as outputKind, p as collectionChange, r as assertKeyedProjection, s as createKeyRelation, t as createKeyedTransform, u as createScheduler, v as snapshotDependencyValue, w as isProjection, x as snapshotCollectionView, y as collectionView } from "./transform-B4T3AcJr.js";
2
2
  import { n as isPlainObject, r as isRecord, t as installOwn } from "./record-B2yRhYsc.js";
3
3
  import { c as AddressIndex, d as invalidValue, f as children, g as validate, h as parent, i as contextOf, l as MutationRejected, m as is, o as decodeChanges, p as contains$1, r as bindContext, s as sealChanges, t as assertRuntimeWritable, u as fail } from "./driver-B5vdPohL.js";
4
4
  //#region core/src/schema/model.ts
@@ -2735,41 +2735,6 @@ const isExternalSource = (source) => {
2735
2735
  };
2736
2736
  //#endregion
2737
2737
  //#region core/src/projection/derive/keyed.ts
2738
- const outputKind = (projection) => {
2739
- return outputDefinitionOf(projection).kind;
2740
- };
2741
- const assertKeyedProjection = (projection, role) => {
2742
- if (outputKind(projection) !== "collection") throw new TypeError(`${role} must be a keyed collection projection.`);
2743
- };
2744
- const isKeyedDependency = (value) => {
2745
- if (!value || typeof value !== "object" || isProjection(value)) return false;
2746
- const candidate = value;
2747
- return isProjection(candidate.source) && typeof candidate.key === "function";
2748
- };
2749
- const bindKey = (index, outputKey, sourceKey) => {
2750
- const previous = index.forward.get(outputKey);
2751
- if (previous === sourceKey) return;
2752
- if (previous !== void 0) {
2753
- const dependents = index.reverse.get(previous);
2754
- dependents?.delete(outputKey);
2755
- if (dependents?.size === 0) index.reverse.delete(previous);
2756
- index.forward.delete(outputKey);
2757
- }
2758
- if (sourceKey === void 0) return;
2759
- index.forward.set(outputKey, sourceKey);
2760
- const dependents = index.reverse.get(sourceKey);
2761
- if (dependents) dependents.add(outputKey);
2762
- else index.reverse.set(sourceKey, new Set([outputKey]));
2763
- };
2764
- const unbindKey = (indexes, outputKey) => {
2765
- for (const index of indexes) if (index) bindKey(index, outputKey, void 0);
2766
- };
2767
- const createBindingIndex = () => ({
2768
- forward: /* @__PURE__ */ new Map(),
2769
- reverse: /* @__PURE__ */ new Map()
2770
- });
2771
- const emptyKeyedDependencies = Object.freeze(Object.create(null));
2772
- const absent = Symbol("keyed-transform-absent");
2773
2738
  const compileOrderedKeys = (value, role) => {
2774
2739
  if (!Array.isArray(value)) throw new TypeError(`${role} must be an array of string keys.`);
2775
2740
  const seen = /* @__PURE__ */ new Set();
@@ -2783,160 +2748,18 @@ const compileOrderedKeys = (value, role) => {
2783
2748
  requested: seen
2784
2749
  };
2785
2750
  };
2786
- const createKeyedTransform = (source, dependencySpecs, select, equality, name) => {
2787
- assertKeyedProjection(source, "Keyed derive source");
2788
- const dependencies = [source];
2789
- const dependencyNames = [];
2790
- const keyed = [];
2791
- if (dependencySpecs !== void 0) {
2792
- if (!isPlainObject(dependencySpecs) || isProjection(dependencySpecs)) throw new TypeError("Keyed derive dependencies must be a plain object.");
2793
- for (const name of Reflect.ownKeys(dependencySpecs)) {
2794
- if (typeof name !== "string") throw new TypeError("Keyed derive dependency names must be strings.");
2795
- const descriptor = Object.getOwnPropertyDescriptor(dependencySpecs, name);
2796
- if (!descriptor?.enumerable || !("value" in descriptor)) throw new TypeError("Keyed derive dependencies must be enumerable data properties.");
2797
- const dependency = descriptor.value;
2798
- dependencyNames.push(name);
2799
- if (isProjection(dependency)) {
2800
- dependencies.push(dependency);
2801
- keyed.push(void 0);
2802
- continue;
2803
- }
2804
- if (!isKeyedDependency(dependency)) throw new TypeError("Keyed derive dependencies must be projections or keyed lookups.");
2805
- assertKeyedProjection(dependency.source, "Dynamic keyed dependency source");
2806
- dependencies.push(dependency.source);
2807
- keyed.push(dependency);
2808
- }
2809
- }
2810
- const [projection] = defineProcessor({
2811
- dependencies,
2812
- outputs: [{
2813
- kind: "collection",
2814
- equality: equality ?? Object.is
2815
- }],
2816
- create: () => {
2817
- const bindings = keyed.map((dependency) => dependency ? createBindingIndex() : void 0);
2818
- const globalValues = keyed.map(() => void 0);
2819
- const revisions = [];
2820
- const clearBindings = () => {
2821
- for (const index of bindings) {
2822
- index?.forward.clear();
2823
- index?.reverse.clear();
2824
- }
2825
- };
2826
- const rememberRevisions = (sources) => {
2827
- revisions.length = sources.length;
2828
- for (let index = 0; index < sources.length; index++) revisions[index] = sources[index].revision;
2829
- };
2830
- return {
2831
- evaluate: (evaluation) => {
2832
- const driver = evaluation.sources[0];
2833
- const output = evaluation.outputs[0];
2834
- if (driver.kind !== "collection" || output.kind !== "collection") throw new Error("Keyed derive requires collection input and output.");
2835
- for (let index = 0; index < keyed.length; index++) {
2836
- if (keyed[index]) continue;
2837
- const dependencySource = evaluation.sources[index + 1];
2838
- if (evaluation.reset || revisions[index + 1] !== dependencySource.revision) globalValues[index] = snapshotDependencyValue(dependencySource);
2839
- }
2840
- const project = (key) => {
2841
- if (!driver.read.has(key)) return;
2842
- const value = driver.read.get(key);
2843
- let dependencyValues = emptyKeyedDependencies;
2844
- if (keyed.length) {
2845
- const values = Object.create(null);
2846
- for (let index = 0; index < keyed.length; index++) {
2847
- const dependency = keyed[index];
2848
- if (!dependency) {
2849
- values[dependencyNames[index]] = globalValues[index];
2850
- continue;
2851
- }
2852
- const sourceContext = evaluation.sources[index + 1];
2853
- if (sourceContext.kind !== "collection") throw new TypeError("Dynamic keyed dependency resolved to a non-collection source.");
2854
- const selectedKey = dependency.key(value, key);
2855
- assertSynchronous(selectedKey);
2856
- if (selectedKey !== void 0 && typeof selectedKey !== "string") throw new TypeError("Dynamic keyed dependency keys must be strings or undefined.");
2857
- bindKey(bindings[index], key, selectedKey);
2858
- values[dependencyNames[index]] = selectedKey === void 0 ? void 0 : sourceContext.read.get(selectedKey);
2859
- }
2860
- dependencyValues = Object.freeze(values);
2861
- }
2862
- const next = select(value, key, dependencyValues);
2863
- assertSynchronous(next);
2864
- if (next === absent) output.output.remove(key);
2865
- else output.output.set(key, next);
2866
- };
2867
- if (evaluation.reset) {
2868
- clearBindings();
2869
- const ids = driver.read.ids();
2870
- for (const key of ids) project(key);
2871
- output.output.order(ids.filter((key) => output.next.has(key)));
2872
- rememberRevisions(evaluation.sources);
2873
- return;
2874
- }
2875
- const dirty = /* @__PURE__ */ new Set();
2876
- let all = false;
2877
- let updateOrder = false;
2878
- let membershipChanged = false;
2879
- if (revisions[0] !== driver.revision) {
2880
- const change = driver.change;
2881
- if (!change || change.kind === "reset") {
2882
- all = true;
2883
- updateOrder = true;
2884
- } else {
2885
- for (const entry of change.removed) {
2886
- membershipChanged ||= output.previous.has(entry.key);
2887
- output.output.remove(entry.key);
2888
- unbindKey(bindings, entry.key);
2889
- dirty.delete(entry.key);
2890
- }
2891
- for (const entry of change.added) dirty.add(entry.key);
2892
- for (const entry of change.updated) dirty.add(entry.key);
2893
- updateOrder = Boolean(change.order);
2894
- }
2895
- }
2896
- for (let index = 0; index < keyed.length; index++) {
2897
- const sourceContext = evaluation.sources[index + 1];
2898
- if (revisions[index + 1] === sourceContext.revision) continue;
2899
- if (!keyed[index]) {
2900
- all = true;
2901
- continue;
2902
- }
2903
- if (sourceContext.kind !== "collection" || !sourceContext.change) {
2904
- all = true;
2905
- continue;
2906
- }
2907
- if (sourceContext.change.kind === "reset") {
2908
- all = true;
2909
- continue;
2910
- }
2911
- const reverse = bindings[index].reverse;
2912
- for (const sourceKey of collectionChange.keys(sourceContext.change)) for (const outputKey of reverse.get(sourceKey) ?? []) dirty.add(outputKey);
2913
- }
2914
- if (all) for (const key of driver.read.ids()) dirty.add(key);
2915
- for (const key of dirty) {
2916
- if (!driver.read.has(key)) continue;
2917
- const beforePresent = output.previous.has(key);
2918
- project(key);
2919
- membershipChanged ||= beforePresent !== output.next.has(key);
2920
- }
2921
- if (updateOrder || membershipChanged) output.output.order(driver.read.ids().filter((key) => output.next.has(key)));
2922
- rememberRevisions(evaluation.sources);
2923
- },
2924
- release: () => {
2925
- clearBindings();
2926
- revisions.length = 0;
2927
- }
2928
- };
2929
- },
2930
- name
2931
- });
2932
- return projection;
2933
- };
2934
2751
  function createKeyedDerive(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
2935
2752
  const dependencySpecs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
2936
2753
  const select = dependencySpecs === void 0 ? dependenciesOrSelect : selectOrEquality;
2937
2754
  const equality = dependencySpecs === void 0 ? selectOrEquality : maybeEquality;
2938
2755
  if (typeof select !== "function") throw new TypeError("Keyed derive requires a selector.");
2939
- return createKeyedTransform(source, dependencySpecs, (value, key, dependencies) => dependencySpecs === void 0 ? select(value, key) : select(value, key, dependencies), equality, "derive.keyed");
2756
+ return createKeyedTransform({
2757
+ source,
2758
+ dependencies: dependencySpecs,
2759
+ equality,
2760
+ name: "derive.keyed",
2761
+ evaluate: ({ value, key, dependencies }) => dependencySpecs === void 0 ? select(value, key) : select(value, key, dependencies)
2762
+ });
2940
2763
  }
2941
2764
  const createKeyedKeys = (source) => {
2942
2765
  assertKeyedProjection(source, "Keyed keys source");
@@ -2956,8 +2779,8 @@ const createKeyedKeys = (source) => {
2956
2779
  });
2957
2780
  return projection;
2958
2781
  };
2959
- const createKeyedValues = (source) => {
2960
- assertKeyedProjection(source, "Keyed values source");
2782
+ const createOrderedSnapshot = (source, name, make) => {
2783
+ assertKeyedProjection(source, `${name} source`);
2961
2784
  const [projection] = defineProcessor({
2962
2785
  dependencies: [source],
2963
2786
  outputs: [{
@@ -2968,33 +2791,32 @@ const createKeyedValues = (source) => {
2968
2791
  let indexes = /* @__PURE__ */ new Map();
2969
2792
  const rebuild = (driver) => {
2970
2793
  const nextIndexes = /* @__PURE__ */ new Map();
2971
- const ids = driver.read.ids();
2972
- const values = Object.freeze(ids.map((key, index) => {
2794
+ const result = Object.freeze(driver.read.ids().map((key, index) => {
2973
2795
  nextIndexes.set(key, index);
2974
- return driver.read.get(key);
2796
+ return make(key, driver.read.get(key));
2975
2797
  }));
2976
2798
  indexes = nextIndexes;
2977
- return values;
2799
+ return result;
2978
2800
  };
2979
2801
  const rebuildIncremental = (driver, change, previous) => {
2980
2802
  const changedValues = /* @__PURE__ */ new Map();
2981
2803
  for (const entry of change.added) changedValues.set(entry.key, entry.after);
2982
2804
  for (const entry of change.updated) changedValues.set(entry.key, entry.after);
2983
2805
  const nextIndexes = /* @__PURE__ */ new Map();
2984
- const values = Object.freeze(driver.read.ids().map((key, index) => {
2806
+ const result = Object.freeze(driver.read.ids().map((key, index) => {
2985
2807
  nextIndexes.set(key, index);
2986
- if (changedValues.has(key)) return changedValues.get(key);
2808
+ if (changedValues.has(key)) return make(key, changedValues.get(key));
2987
2809
  const previousIndex = indexes.get(key);
2988
- return previousIndex === void 0 ? driver.read.get(key) : previous[previousIndex];
2810
+ return previousIndex === void 0 ? make(key, driver.read.get(key)) : previous[previousIndex];
2989
2811
  }));
2990
2812
  indexes = nextIndexes;
2991
- return values;
2813
+ return result;
2992
2814
  };
2993
2815
  return {
2994
2816
  evaluate: (evaluation) => {
2995
2817
  const driver = evaluation.sources[0];
2996
2818
  const output = evaluation.outputs[0];
2997
- if (driver.kind !== "collection" || output.kind !== "value") throw new Error("derive.keyed.values requires collection input and value output.");
2819
+ if (driver.kind !== "collection" || output.kind !== "value") throw new Error(`${name} requires collection input and value output.`);
2998
2820
  const change = driver.change;
2999
2821
  if (evaluation.reset || !change || change.kind === "reset") {
3000
2822
  output.output.set(rebuild(driver));
@@ -3017,14 +2839,59 @@ const createKeyedValues = (source) => {
3017
2839
  output.output.set(rebuild(driver));
3018
2840
  return;
3019
2841
  }
3020
- next[index] = entry.after;
2842
+ next[index] = make(entry.key, entry.after);
3021
2843
  }
3022
2844
  output.output.set(Object.freeze(next));
3023
2845
  },
3024
2846
  release: () => indexes.clear()
3025
2847
  };
3026
2848
  },
3027
- name: "derive.keyed.values"
2849
+ name
2850
+ });
2851
+ return projection;
2852
+ };
2853
+ const createKeyedValues = (source) => createOrderedSnapshot(source, "derive.keyed.values", (_key, value) => value);
2854
+ const createKeyedEntries = (source) => createOrderedSnapshot(source, "derive.keyed.entries", (key, value) => Object.freeze([key, value]));
2855
+ const createKeyedGet = (source, key, equality = Object.is) => {
2856
+ assertKeyedProjection(source, "derive.keyed.get source");
2857
+ if (outputKind(key) !== "value") throw new TypeError("derive.keyed.get key must be a scalar projection.");
2858
+ const [projection] = defineProcessor({
2859
+ dependencies: [source, key],
2860
+ outputs: [{
2861
+ kind: "value",
2862
+ equality
2863
+ }],
2864
+ create: () => {
2865
+ let sourceRevision = -1;
2866
+ let keyRevision = -1;
2867
+ let selected;
2868
+ return { evaluate: (evaluation) => {
2869
+ const collection = evaluation.sources[0];
2870
+ const selectedKey = evaluation.sources[1];
2871
+ const output = evaluation.outputs[0];
2872
+ if (collection.kind !== "collection" || selectedKey.kind !== "value" || output.kind !== "value") throw new Error("derive.keyed.get resolved invalid input/output kinds.");
2873
+ let affected = evaluation.reset;
2874
+ if (evaluation.reset || keyRevision !== selectedKey.revision) {
2875
+ const candidate = selectedKey.value;
2876
+ if (candidate !== void 0 && typeof candidate !== "string") throw new TypeError("derive.keyed.get key must resolve to a string or undefined.");
2877
+ selected = candidate;
2878
+ affected = true;
2879
+ } else if (sourceRevision !== collection.revision) {
2880
+ const change = collection.change;
2881
+ if (!change || change.kind === "reset") affected = true;
2882
+ else if (selected !== void 0) {
2883
+ for (const changedKey of collectionChange.keys(change)) if (changedKey === selected) {
2884
+ affected = true;
2885
+ break;
2886
+ }
2887
+ }
2888
+ }
2889
+ if (affected) output.output.set(selected === void 0 ? void 0 : collection.read.get(selected));
2890
+ sourceRevision = collection.revision;
2891
+ keyRevision = selectedKey.revision;
2892
+ } };
2893
+ },
2894
+ name: "derive.keyed.get"
3028
2895
  });
3029
2896
  return projection;
3030
2897
  };
@@ -3032,23 +2899,35 @@ function createKeyedFilter(source, dependenciesOrPredicate, maybePredicate) {
3032
2899
  const dependencySpecs = typeof dependenciesOrPredicate === "function" ? void 0 : dependenciesOrPredicate;
3033
2900
  const predicate = dependencySpecs === void 0 ? dependenciesOrPredicate : maybePredicate;
3034
2901
  if (typeof predicate !== "function") throw new TypeError("Keyed filter requires a predicate.");
3035
- return createKeyedTransform(source, dependencySpecs, (value, key, dependencies) => {
3036
- const included = dependencySpecs === void 0 ? predicate(value, key) : predicate(value, key, dependencies);
3037
- assertSynchronous(included);
3038
- if (typeof included !== "boolean") throw new TypeError("Keyed filter predicate must return boolean.");
3039
- return included ? value : absent;
3040
- }, Object.is, "derive.keyed.filter");
2902
+ return createKeyedTransform({
2903
+ source,
2904
+ dependencies: dependencySpecs,
2905
+ equality: Object.is,
2906
+ name: "derive.keyed.filter",
2907
+ evaluate: ({ value, key, dependencies }) => {
2908
+ const included = dependencySpecs === void 0 ? predicate(value, key) : predicate(value, key, dependencies);
2909
+ assertSynchronous(included);
2910
+ if (typeof included !== "boolean") throw new TypeError("Keyed filter predicate must return boolean.");
2911
+ return included ? value : keyedAbsent;
2912
+ }
2913
+ });
3041
2914
  }
3042
2915
  function createKeyedCompact(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
3043
2916
  const dependencySpecs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
3044
2917
  const select = dependencySpecs === void 0 ? dependenciesOrSelect : selectOrEquality;
3045
2918
  const equality = dependencySpecs === void 0 ? selectOrEquality : maybeEquality;
3046
2919
  if (typeof select !== "function") throw new TypeError("Keyed compact requires a selector.");
3047
- return createKeyedTransform(source, dependencySpecs, (value, key, dependencies) => {
3048
- const next = dependencySpecs === void 0 ? select(value, key) : select(value, key, dependencies);
3049
- assertSynchronous(next);
3050
- return next === void 0 ? absent : next;
3051
- }, equality, "derive.keyed.compact");
2920
+ return createKeyedTransform({
2921
+ source,
2922
+ dependencies: dependencySpecs,
2923
+ equality,
2924
+ name: "derive.keyed.compact",
2925
+ evaluate: ({ value, key, dependencies }) => {
2926
+ const next = dependencySpecs === void 0 ? select(value, key) : select(value, key, dependencies);
2927
+ assertSynchronous(next);
2928
+ return next === void 0 ? keyedAbsent : next;
2929
+ }
2930
+ });
3052
2931
  }
3053
2932
  const createKeyedSubset = (source, orderedKeys) => {
3054
2933
  assertKeyedProjection(source, "Keyed subset source");
@@ -3145,9 +3024,198 @@ const createKeyedSubset = (source, orderedKeys) => {
3145
3024
  });
3146
3025
  return projection;
3147
3026
  };
3027
+ const normalizeGroups = (value) => {
3028
+ const groups = typeof value === "string" ? [value] : value;
3029
+ if (!Array.isArray(groups)) throw new TypeError("derive.keyed.groupBy selector must return a string or an array of strings.");
3030
+ const seen = /* @__PURE__ */ new Set();
3031
+ const result = [];
3032
+ for (const group of groups) {
3033
+ if (typeof group !== "string") throw new TypeError("derive.keyed.groupBy selector must return only string group keys.");
3034
+ if (seen.has(group)) throw new TypeError("derive.keyed.groupBy selector must not return duplicate group keys.");
3035
+ seen.add(group);
3036
+ result.push(group);
3037
+ }
3038
+ return Object.freeze(result);
3039
+ };
3040
+ function createKeyedGroupBy(source, dependenciesOrSelector, maybeSelector) {
3041
+ assertKeyedProjection(source, "derive.keyed.groupBy source");
3042
+ const dependencySpecs = typeof dependenciesOrSelector === "function" ? void 0 : dependenciesOrSelector;
3043
+ const selector = dependencySpecs === void 0 ? dependenciesOrSelector : maybeSelector;
3044
+ if (typeof selector !== "function") throw new TypeError("derive.keyed.groupBy requires a selector.");
3045
+ const compiled = compileKeyedDependencies(source, dependencySpecs, "derive.keyed.groupBy");
3046
+ const [projection] = defineProcessor({
3047
+ dependencies: compiled.projections,
3048
+ outputs: [{
3049
+ kind: "collection",
3050
+ equality: (previous, next) => {
3051
+ const left = previous;
3052
+ const right = next;
3053
+ return left.length === right.length && left.every((key, index) => key === right[index]);
3054
+ }
3055
+ }],
3056
+ create: () => {
3057
+ const dependencies = createKeyedDependencyRuntime(compiled);
3058
+ const relation = createKeyRelation();
3059
+ const orderIndex = /* @__PURE__ */ new Map();
3060
+ const groupRanks = /* @__PURE__ */ new Map();
3061
+ const refreshOrderIndex = (driver) => {
3062
+ orderIndex.clear();
3063
+ driver.read.ids().forEach((key, index) => orderIndex.set(key, index));
3064
+ };
3065
+ const selectGroups = (driver, sources, key, affected) => {
3066
+ if (!driver.read.has(key)) return false;
3067
+ const value = driver.read.get(key);
3068
+ const dependencyValues = dependencies.resolve(value, key, sources);
3069
+ const selected = dependencySpecs === void 0 ? selector(value, key) : selector(value, key, dependencyValues);
3070
+ assertSynchronous(selected);
3071
+ const next = normalizeGroups(selected);
3072
+ const previous = relation.forward(key) ?? Object.freeze([]);
3073
+ if (!relation.replace(key, next)) return false;
3074
+ for (const group of previous) affected.add(group);
3075
+ for (const group of next) affected.add(group);
3076
+ return true;
3077
+ };
3078
+ const updateGroup = (group, output) => {
3079
+ const reverse = relation.reverse(group);
3080
+ if (!reverse?.size) {
3081
+ groupRanks.delete(group);
3082
+ output.output.remove(group);
3083
+ return;
3084
+ }
3085
+ const members = [...reverse].sort((left, right) => (orderIndex.get(left) ?? Number.MAX_SAFE_INTEGER) - (orderIndex.get(right) ?? Number.MAX_SAFE_INTEGER));
3086
+ const first = members[0];
3087
+ const source = orderIndex.get(first);
3088
+ const selector = relation.forward(first)?.indexOf(group) ?? -1;
3089
+ if (source === void 0 || selector < 0) throw new Error("derive.keyed.groupBy relation is inconsistent with source order.");
3090
+ groupRanks.set(group, {
3091
+ source,
3092
+ selector
3093
+ });
3094
+ output.output.set(group, Object.freeze(members));
3095
+ };
3096
+ const publishGroupOrder = (output) => {
3097
+ const order = [...groupRanks.entries()].sort(([leftKey, left], [rightKey, right]) => {
3098
+ if (left.source !== right.source) return left.source - right.source;
3099
+ if (left.selector !== right.selector) return left.selector - right.selector;
3100
+ return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
3101
+ }).map(([group]) => group);
3102
+ output.output.order(order);
3103
+ };
3104
+ const rebuild = (driver, sources, output) => {
3105
+ dependencies.clearBindings();
3106
+ relation.clear();
3107
+ groupRanks.clear();
3108
+ refreshOrderIndex(driver);
3109
+ const affected = /* @__PURE__ */ new Set();
3110
+ for (const key of driver.read.ids()) selectGroups(driver, sources, key, affected);
3111
+ for (const key of output.previous.ids()) affected.add(key);
3112
+ for (const group of affected) updateGroup(group, output);
3113
+ publishGroupOrder(output);
3114
+ };
3115
+ const publishAffected = (affected, output, orderDirty) => {
3116
+ for (const group of affected) updateGroup(group, output);
3117
+ if (orderDirty) publishGroupOrder(output);
3118
+ };
3119
+ const removeSourceKey = (key, affected) => {
3120
+ const previous = relation.forward(key);
3121
+ if (!previous) return false;
3122
+ for (const group of previous) affected.add(group);
3123
+ return relation.delete(key);
3124
+ };
3125
+ const markAllGroups = (output, affected) => {
3126
+ for (const group of relation.rights()) affected.add(group);
3127
+ for (const group of output.previous.ids()) affected.add(group);
3128
+ };
3129
+ return {
3130
+ evaluate: (evaluation) => {
3131
+ const driver = evaluation.sources[0];
3132
+ const output = evaluation.outputs[0];
3133
+ if (driver.kind !== "collection" || output.kind !== "collection") throw new Error("derive.keyed.groupBy requires collection input and output.");
3134
+ dependencies.prepare(evaluation.sources, evaluation.reset);
3135
+ if (evaluation.reset) {
3136
+ rebuild(driver, evaluation.sources, output);
3137
+ dependencies.remember(evaluation.sources);
3138
+ return;
3139
+ }
3140
+ const dirty = /* @__PURE__ */ new Set();
3141
+ const affected = /* @__PURE__ */ new Set();
3142
+ let all = false;
3143
+ let structural = false;
3144
+ let orderDirty = false;
3145
+ if (dependencies.driverChanged(driver)) {
3146
+ const change = driver.change;
3147
+ if (!change || change.kind === "reset") {
3148
+ rebuild(driver, evaluation.sources, output);
3149
+ dependencies.remember(evaluation.sources);
3150
+ return;
3151
+ } else {
3152
+ for (const entry of change.removed) {
3153
+ orderDirty ||= removeSourceKey(entry.key, affected);
3154
+ dependencies.remove(entry.key);
3155
+ dirty.delete(entry.key);
3156
+ }
3157
+ for (const entry of change.added) dirty.add(entry.key);
3158
+ for (const entry of change.updated) dirty.add(entry.key);
3159
+ structural = Boolean(change.added.length || change.removed.length || change.order);
3160
+ orderDirty ||= structural;
3161
+ if (structural) refreshOrderIndex(driver);
3162
+ }
3163
+ }
3164
+ all ||= dependencies.collectInvalidated(evaluation.sources, dirty);
3165
+ if (all) for (const key of driver.read.ids()) dirty.add(key);
3166
+ for (const key of dirty) orderDirty ||= selectGroups(driver, evaluation.sources, key, affected);
3167
+ if (structural) markAllGroups(output, affected);
3168
+ publishAffected(affected, output, orderDirty);
3169
+ dependencies.remember(evaluation.sources);
3170
+ },
3171
+ release: () => {
3172
+ relation.clear();
3173
+ orderIndex.clear();
3174
+ groupRanks.clear();
3175
+ dependencies.release();
3176
+ }
3177
+ };
3178
+ },
3179
+ name: "derive.keyed.groupBy"
3180
+ });
3181
+ return projection;
3182
+ }
3183
+ const createKeyedSingleton = (source, keyOf, equality = Object.is) => {
3184
+ if (outputKind(source) !== "value") throw new TypeError("derive.keyed.singleton source must be a scalar projection.");
3185
+ if (typeof keyOf !== "function") throw new TypeError("derive.keyed.singleton requires a key selector.");
3186
+ const [projection] = defineProcessor({
3187
+ dependencies: [source],
3188
+ outputs: [{
3189
+ kind: "collection",
3190
+ equality
3191
+ }],
3192
+ create: () => ({ evaluate: (evaluation) => {
3193
+ const scalar = evaluation.sources[0];
3194
+ const output = evaluation.outputs[0];
3195
+ if (scalar.kind !== "value" || output.kind !== "collection") throw new Error("derive.keyed.singleton resolved invalid input/output kinds.");
3196
+ const previous = output.previous.ids();
3197
+ if (scalar.value === void 0) {
3198
+ for (const key of previous) output.output.remove(key);
3199
+ return;
3200
+ }
3201
+ const key = keyOf(scalar.value);
3202
+ assertSynchronous(key);
3203
+ if (typeof key !== "string") throw new TypeError("derive.keyed.singleton key selector must return a string.");
3204
+ for (const previousKey of previous) if (previousKey !== key) output.output.remove(previousKey);
3205
+ output.output.set(key, scalar.value);
3206
+ if (previous.length !== 1 || previous[0] !== key) output.output.order([key]);
3207
+ } }),
3208
+ name: "derive.keyed.singleton"
3209
+ });
3210
+ return projection;
3211
+ };
3148
3212
  const keyedDerive = Object.assign(createKeyedDerive, {
3149
3213
  keys: createKeyedKeys,
3150
3214
  values: createKeyedValues,
3215
+ entries: createKeyedEntries,
3216
+ get: createKeyedGet,
3217
+ groupBy: createKeyedGroupBy,
3218
+ singleton: createKeyedSingleton,
3151
3219
  subset: createKeyedSubset,
3152
3220
  filter: createKeyedFilter,
3153
3221
  compact: createKeyedCompact
@@ -3475,7 +3543,7 @@ const createCollectionOutput = (binding) => {
3475
3543
  initialized = true;
3476
3544
  },
3477
3545
  emit: (call) => {
3478
- Array.from(listeners).forEach((listener) => call(listener));
3546
+ Array.from(listeners).forEach((listener) => call(listener, change));
3479
3547
  },
3480
3548
  hasConsumers: () => consumers.size > 0,
3481
3549
  forEachConsumer: (run) => consumers.forEach(run),
@@ -3585,7 +3653,7 @@ const createValueOutput = (binding) => {
3585
3653
  },
3586
3654
  emit: (call) => {
3587
3655
  profile.materialized.notification();
3588
- Array.from(listeners).forEach((listener) => call(listener));
3656
+ Array.from(listeners).forEach((listener) => call(listener, void 0));
3589
3657
  },
3590
3658
  hasConsumers: () => consumers.size > 0,
3591
3659
  forEachConsumer: (run) => consumers.forEach(run),
@@ -3740,6 +3808,20 @@ const createProcessor = (scheduler, definition, dependencies) => {
3740
3808
  return processor;
3741
3809
  };
3742
3810
  //#endregion
3811
+ //#region core/src/projection/readable/listeners.ts
3812
+ /** Exhaustively notifies one projection-readable listener snapshot. */
3813
+ const notifyProjectionListeners = (listeners, message) => {
3814
+ const snapshot = [...listeners];
3815
+ const failures = [];
3816
+ for (const listener of snapshot) try {
3817
+ listener();
3818
+ } catch (error) {
3819
+ failures.push(error);
3820
+ }
3821
+ if (failures.length === 1) throw failures[0];
3822
+ if (failures.length) throw new AggregateError(failures, message);
3823
+ };
3824
+ //#endregion
3743
3825
  //#region core/src/projection/readable/selection.ts
3744
3826
  let activeTracker;
3745
3827
  const trackKey = (key) => {
@@ -3874,7 +3956,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
3874
3956
  updateCollectionSnapshot();
3875
3957
  return;
3876
3958
  }
3877
- if (evaluate()) Array.from(listeners).forEach((listener) => listener());
3959
+ if (evaluate()) notifyProjectionListeners(listeners, "Projection selector listeners failed.");
3878
3960
  };
3879
3961
  const installSource = () => {
3880
3962
  if (!unsubscribeSource) unsubscribeSource = source.subscribe(onProjectionChange);
@@ -5131,6 +5213,212 @@ const createSourceRegistry = (scheduler) => {
5131
5213
  };
5132
5214
  };
5133
5215
  //#endregion
5216
+ //#region core/src/projection/readable/keyed.ts
5217
+ const sameKeys = (previous, next) => previous.length === next.length && previous.every((key, index) => key === next[index]);
5218
+ /** Runtime-owned keyed consumer family over one materialized collection output. */
5219
+ const createProjectionItems = (source, checkOwner) => {
5220
+ if (source.kind !== "collection") throw new TypeError("Projection items require a keyed collection projection.");
5221
+ let disposed = false;
5222
+ const active = /* @__PURE__ */ new Map();
5223
+ const followers = /* @__PURE__ */ new Map();
5224
+ const waiting = /* @__PURE__ */ new Map();
5225
+ let keysValue = Object.freeze([...source.current().keys()]);
5226
+ let keysRevision = 0;
5227
+ const keyListeners = /* @__PURE__ */ new Set();
5228
+ const check = () => {
5229
+ if (disposed) throw new ProjectionDisposedError();
5230
+ checkOwner();
5231
+ };
5232
+ const notifications = [];
5233
+ const notify = (listeners) => {
5234
+ for (const listener of listeners) notifications.push(listener);
5235
+ };
5236
+ const flushNotifications = () => {
5237
+ notifyProjectionListeners(notifications.splice(0), "Projection item listeners failed.");
5238
+ };
5239
+ const unregisterWaiting = (state) => {
5240
+ const states = waiting.get(state.key);
5241
+ states?.delete(state);
5242
+ if (states?.size === 0) waiting.delete(state.key);
5243
+ };
5244
+ const registerWaiting = (state) => {
5245
+ if (state.tracked || state.ended) return;
5246
+ const states = waiting.get(state.key);
5247
+ if (states) states.add(state);
5248
+ else waiting.set(state.key, new Set([state]));
5249
+ };
5250
+ const publishState = (state, value) => {
5251
+ if (state.ended) return;
5252
+ if (Object.is(state.value, value)) {
5253
+ state.value = value;
5254
+ return;
5255
+ }
5256
+ state.value = value;
5257
+ state.revision++;
5258
+ notify(state.listeners);
5259
+ };
5260
+ const attachActive = (state, value) => {
5261
+ if (state.ended) return;
5262
+ unregisterWaiting(state);
5263
+ state.tracked = true;
5264
+ const canonical = active.get(state.key);
5265
+ if (!canonical) active.set(state.key, state);
5266
+ else if (canonical !== state) {
5267
+ const states = followers.get(state.key);
5268
+ if (states) states.add(state);
5269
+ else followers.set(state.key, new Set([state]));
5270
+ }
5271
+ publishState(state, value);
5272
+ };
5273
+ const terminateState = (state) => {
5274
+ if (state.ended) return;
5275
+ unregisterWaiting(state);
5276
+ state.tracked = false;
5277
+ publishState(state, void 0);
5278
+ state.ended = true;
5279
+ };
5280
+ const statesFor = (key) => {
5281
+ const result = [];
5282
+ const canonical = active.get(key);
5283
+ if (canonical) result.push(canonical);
5284
+ const extra = followers.get(key);
5285
+ if (extra) result.push(...extra);
5286
+ return result;
5287
+ };
5288
+ const terminateKey = (key) => {
5289
+ for (const state of statesFor(key)) terminateState(state);
5290
+ active.delete(key);
5291
+ followers.delete(key);
5292
+ };
5293
+ const activateWaiting = (key, value) => {
5294
+ const states = waiting.get(key);
5295
+ if (!states?.size) return;
5296
+ waiting.delete(key);
5297
+ for (const state of states) attachActive(state, value);
5298
+ };
5299
+ const updateKey = (key, value) => {
5300
+ for (const state of statesFor(key)) publishState(state, value);
5301
+ };
5302
+ const updateKeys = (current) => {
5303
+ const next = Object.freeze([...current.keys()]);
5304
+ if (sameKeys(keysValue, next)) return;
5305
+ keysValue = next;
5306
+ keysRevision++;
5307
+ notify(keyListeners);
5308
+ };
5309
+ const createItem = (key, initial) => {
5310
+ const state = {
5311
+ key,
5312
+ listeners: /* @__PURE__ */ new Set(),
5313
+ tracked: initial !== void 0,
5314
+ ended: false,
5315
+ value: initial?.value,
5316
+ revision: 0
5317
+ };
5318
+ const ensureObserved = () => {
5319
+ if (state.ended || state.tracked) return;
5320
+ const current = source.current();
5321
+ if (current.has(key)) attachActive(state, current.get(key));
5322
+ };
5323
+ state.readable = Object.freeze({
5324
+ current: () => {
5325
+ check();
5326
+ ensureObserved();
5327
+ return state.value;
5328
+ },
5329
+ revision: () => {
5330
+ check();
5331
+ ensureObserved();
5332
+ return state.revision;
5333
+ },
5334
+ subscribe: (listener) => {
5335
+ check();
5336
+ ensureObserved();
5337
+ state.listeners.add(listener);
5338
+ if (!state.tracked && !state.ended) registerWaiting(state);
5339
+ return () => {
5340
+ state.listeners.delete(listener);
5341
+ if (!state.listeners.size && !state.tracked) unregisterWaiting(state);
5342
+ };
5343
+ }
5344
+ });
5345
+ return state;
5346
+ };
5347
+ const reconcile = (current) => {
5348
+ for (const key of [...active.keys()]) if (!current.has(key)) terminateKey(key);
5349
+ else updateKey(key, current.get(key));
5350
+ for (const key of [...waiting.keys()]) if (current.has(key)) activateWaiting(key, current.get(key));
5351
+ updateKeys(current);
5352
+ };
5353
+ const onChange = (change) => {
5354
+ check();
5355
+ const current = source.current();
5356
+ if (!change || change.kind === "reset") {
5357
+ reconcile(current);
5358
+ flushNotifications();
5359
+ return;
5360
+ }
5361
+ for (const entry of change.removed) terminateKey(entry.key);
5362
+ for (const entry of change.added) activateWaiting(entry.key, entry.after);
5363
+ for (const entry of change.updated) updateKey(entry.key, entry.after);
5364
+ if (change.added.length || change.removed.length || change.order) updateKeys(current);
5365
+ flushNotifications();
5366
+ };
5367
+ const stop = source.subscribe(onChange);
5368
+ const keys = Object.freeze({
5369
+ current: () => {
5370
+ check();
5371
+ return keysValue;
5372
+ },
5373
+ revision: () => {
5374
+ check();
5375
+ return keysRevision;
5376
+ },
5377
+ subscribe: (listener) => {
5378
+ check();
5379
+ keyListeners.add(listener);
5380
+ return () => keyListeners.delete(listener);
5381
+ }
5382
+ });
5383
+ return {
5384
+ items: Object.freeze({
5385
+ keys,
5386
+ get: (key) => {
5387
+ check();
5388
+ const canonical = active.get(key);
5389
+ if (canonical) return canonical.readable;
5390
+ const pending = waiting.get(key)?.values().next().value;
5391
+ if (pending) return pending.readable;
5392
+ const current = source.current();
5393
+ if (current.has(key)) {
5394
+ const state = createItem(key, {
5395
+ present: true,
5396
+ value: current.get(key)
5397
+ });
5398
+ active.set(key, state);
5399
+ return state.readable;
5400
+ }
5401
+ return createItem(key).readable;
5402
+ }
5403
+ }),
5404
+ dispose() {
5405
+ if (disposed) return;
5406
+ disposed = true;
5407
+ stop();
5408
+ keyListeners.clear();
5409
+ const known = /* @__PURE__ */ new Set();
5410
+ active.forEach((state) => known.add(state));
5411
+ followers.forEach((states) => states.forEach((state) => known.add(state)));
5412
+ waiting.forEach((states) => states.forEach((state) => known.add(state)));
5413
+ known.forEach((state) => state.listeners.clear());
5414
+ active.clear();
5415
+ followers.clear();
5416
+ waiting.clear();
5417
+ notifications.length = 0;
5418
+ }
5419
+ };
5420
+ };
5421
+ //#endregion
5134
5422
  //#region core/src/projection/runtime.ts
5135
5423
  const createProjectionRuntime = (options) => {
5136
5424
  const scheduler = createScheduler(options?.onError ?? (() => void 0));
@@ -5138,6 +5426,30 @@ const createProjectionRuntime = (options) => {
5138
5426
  const materialized = /* @__PURE__ */ new WeakMap();
5139
5427
  const publicViews = /* @__PURE__ */ new WeakMap();
5140
5428
  const scopes = /* @__PURE__ */ new Set();
5429
+ const rootItems = /* @__PURE__ */ new Map();
5430
+ const disposeItemControllers = (registry, failures) => {
5431
+ const controllers = [...registry.values()];
5432
+ registry.clear();
5433
+ for (const controller of controllers) try {
5434
+ controller.dispose();
5435
+ } catch (error) {
5436
+ failures.push(error);
5437
+ }
5438
+ };
5439
+ const disposeScopeConsumers = (state, failures) => {
5440
+ disposeItemControllers(state.items, failures);
5441
+ const subscriptions = [...state.subscriptions];
5442
+ state.subscriptions.clear();
5443
+ for (const unsubscribe of subscriptions) try {
5444
+ unsubscribe();
5445
+ } catch (error) {
5446
+ failures.push(error);
5447
+ }
5448
+ };
5449
+ const throwCleanupFailures = (failures, message) => {
5450
+ if (failures.length === 1) throw failures[0];
5451
+ if (failures.length) throw new AggregateError(failures, message);
5452
+ };
5141
5453
  const assertAccess = (definition, requester) => {
5142
5454
  scheduler.assertActive();
5143
5455
  const owner = definition.owner;
@@ -5223,6 +5535,19 @@ const createProjectionRuntime = (options) => {
5223
5535
  subscribe: output.subscribe
5224
5536
  });
5225
5537
  };
5538
+ const projectionItems = (projection, requester) => {
5539
+ const output = resolveOutput(projection, requester);
5540
+ if (output.kind !== "collection") throw new TypeError("Projection items require a keyed collection projection.");
5541
+ const registry = requester?.items ?? rootItems;
5542
+ const existing = registry.get(output);
5543
+ if (existing) return existing.items;
5544
+ const controller = createProjectionItems(readableSource(projection, requester), () => {
5545
+ if (requester && !requester.active) throw new ProjectionDisposedError();
5546
+ assertOutput(output);
5547
+ });
5548
+ registry.set(output, controller);
5549
+ return controller.items;
5550
+ };
5226
5551
  const makeReadable = (projection, selector, equality, requester) => {
5227
5552
  const source = readableSource(projection, requester);
5228
5553
  return selector ? createSelectorReadable(source, selector, equality) : createDirectReadable(source);
@@ -5256,13 +5581,7 @@ const createProjectionRuntime = (options) => {
5256
5581
  scheduler.assertIdle();
5257
5582
  state.active = false;
5258
5583
  const failures = [];
5259
- const subscriptions = [...state.subscriptions];
5260
- state.subscriptions.clear();
5261
- for (const unsubscribe of subscriptions) try {
5262
- unsubscribe();
5263
- } catch (error) {
5264
- failures.push(error);
5265
- }
5584
+ disposeScopeConsumers(state, failures);
5266
5585
  for (let index = state.materialized.length - 1; index >= 0; index--) {
5267
5586
  const definition = state.materialized[index];
5268
5587
  const current = materialized.get(definition);
@@ -5277,14 +5596,15 @@ const createProjectionRuntime = (options) => {
5277
5596
  }
5278
5597
  state.materialized.length = 0;
5279
5598
  scopes.delete(state);
5280
- if (failures.length) throw failures[0];
5599
+ throwCleanupFailures(failures, "Projection scope cleanup failed.");
5281
5600
  };
5282
5601
  const createScope = () => {
5283
5602
  scheduler.assertIdle();
5284
5603
  const state = {
5285
5604
  active: true,
5286
5605
  materialized: [],
5287
- subscriptions: /* @__PURE__ */ new Set()
5606
+ subscriptions: /* @__PURE__ */ new Set(),
5607
+ items: /* @__PURE__ */ new Map()
5288
5608
  };
5289
5609
  scopes.add(state);
5290
5610
  const assertActive = () => {
@@ -5356,6 +5676,10 @@ const createProjectionRuntime = (options) => {
5356
5676
  return readProjection(projection, state);
5357
5677
  },
5358
5678
  select: scopeSelect,
5679
+ items: (projection) => {
5680
+ assertActive();
5681
+ return projectionItems(projection, state);
5682
+ },
5359
5683
  update: scopeUpdate,
5360
5684
  batch: scopeBatch,
5361
5685
  dispose: () => disposeScope(state)
@@ -5364,6 +5688,7 @@ const createProjectionRuntime = (options) => {
5364
5688
  return Object.freeze({
5365
5689
  read: readProjection,
5366
5690
  select: selectProjection,
5691
+ items: projectionItems,
5367
5692
  update,
5368
5693
  batch,
5369
5694
  scope: createScope,
@@ -5373,16 +5698,11 @@ const createProjectionRuntime = (options) => {
5373
5698
  const failures = [];
5374
5699
  for (const scope of scopes) {
5375
5700
  scope.active = false;
5376
- const subscriptions = [...scope.subscriptions];
5377
- scope.subscriptions.clear();
5378
- for (const unsubscribe of subscriptions) try {
5379
- unsubscribe();
5380
- } catch (error) {
5381
- failures.push(error);
5382
- }
5701
+ disposeScopeConsumers(scope, failures);
5383
5702
  scope.materialized.length = 0;
5384
5703
  }
5385
5704
  scopes.clear();
5705
+ disposeItemControllers(rootItems, failures);
5386
5706
  try {
5387
5707
  sources.dispose();
5388
5708
  } catch (error) {
@@ -5393,8 +5713,7 @@ const createProjectionRuntime = (options) => {
5393
5713
  } catch (error) {
5394
5714
  failures.push(error);
5395
5715
  }
5396
- if (failures.length === 1) throw failures[0];
5397
- if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
5716
+ throwCleanupFailures(failures, "Projection cleanup failed.");
5398
5717
  }
5399
5718
  });
5400
5719
  };