doxum 0.1.26 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -3
- package/dist/advanced.cjs +40 -30
- package/dist/advanced.cjs.map +1 -1
- package/dist/advanced.d.cts +34 -2
- package/dist/advanced.d.ts +34 -2
- package/dist/advanced.js +31 -22
- package/dist/advanced.js.map +1 -1
- package/dist/{definition-xgk3YNBp.d.ts → dependency-B3E65M1E.d.cts} +27 -3
- package/dist/{definition-CDOuvb5C.d.cts → dependency-BYbdDHNa.d.ts} +27 -3
- package/dist/driver-B5vdPohL.js.map +1 -1
- package/dist/driver-BL91gn7_.cjs.map +1 -1
- package/dist/index.cjs +885 -394
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -18
- package/dist/index.d.ts +33 -18
- package/dist/index.js +784 -293
- package/dist/index.js.map +1 -1
- package/dist/local-sync.cjs.map +1 -1
- package/dist/local-sync.js.map +1 -1
- package/dist/transform-B4T3AcJr.js +938 -0
- package/dist/transform-B4T3AcJr.js.map +1 -0
- package/dist/transform-DUleAsG0.cjs +1105 -0
- package/dist/transform-DUleAsG0.cjs.map +1 -0
- package/package.json +1 -1
- package/skills/doxum-runtime/SKILL.md +16 -6
- package/skills/doxum-runtime/references/api.en.md +49 -15
- package/skills/doxum-runtime/references/api.zh-CN.md +47 -13
- package/skills/doxum-runtime/references/guide.en.md +2 -1
- package/skills/doxum-runtime/references/guide.zh-CN.md +2 -1
- package/skills/doxum-runtime/references/invariants.en.md +2 -2
- package/skills/doxum-runtime/references/invariants.zh-CN.md +2 -2
- package/skills/doxum-runtime/references/patterns.en.md +1 -1
- package/skills/doxum-runtime/references/patterns.zh-CN.md +1 -1
- package/skills/doxum-runtime/references/projections.en.md +77 -4
- package/skills/doxum-runtime/references/projections.zh-CN.md +74 -3
- package/dist/scheduler--137L3j-.js +0 -500
- package/dist/scheduler--137L3j-.js.map +0 -1
- package/dist/scheduler-D4VthaaK.cjs +0 -589
- package/dist/scheduler-D4VthaaK.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
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
|
|
@@ -2734,268 +2734,517 @@ const isExternalSource = (source) => {
|
|
|
2734
2734
|
return (candidate.kind === "value" || candidate.kind === "collection") && typeof candidate.current === "function" && typeof candidate.revision === "function" && typeof candidate.subscribe === "function";
|
|
2735
2735
|
};
|
|
2736
2736
|
//#endregion
|
|
2737
|
-
//#region core/src/projection/derive.ts
|
|
2738
|
-
const
|
|
2739
|
-
|
|
2740
|
-
const
|
|
2741
|
-
const
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
};
|
|
2746
|
-
const assertKeyedProjection = (projection, role) => {
|
|
2747
|
-
if (outputKind(projection) !== "collection") throw new TypeError(`${role} must be a keyed collection projection.`);
|
|
2748
|
-
};
|
|
2749
|
-
const isKeyedDependency = (value) => {
|
|
2750
|
-
if (!value || typeof value !== "object" || isProjection(value)) return false;
|
|
2751
|
-
const candidate = value;
|
|
2752
|
-
return isProjection(candidate.source) && typeof candidate.key === "function";
|
|
2753
|
-
};
|
|
2754
|
-
const bindKey = (index, outputKey, sourceKey) => {
|
|
2755
|
-
const previous = index.forward.get(outputKey);
|
|
2756
|
-
if (previous === sourceKey) return;
|
|
2757
|
-
if (previous !== void 0) {
|
|
2758
|
-
const dependents = index.reverse.get(previous);
|
|
2759
|
-
dependents?.delete(outputKey);
|
|
2760
|
-
if (dependents?.size === 0) index.reverse.delete(previous);
|
|
2761
|
-
index.forward.delete(outputKey);
|
|
2737
|
+
//#region core/src/projection/derive/keyed.ts
|
|
2738
|
+
const compileOrderedKeys = (value, role) => {
|
|
2739
|
+
if (!Array.isArray(value)) throw new TypeError(`${role} must be an array of string keys.`);
|
|
2740
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2741
|
+
for (const key of value) {
|
|
2742
|
+
if (typeof key !== "string") throw new TypeError(`${role} must contain only string keys.`);
|
|
2743
|
+
if (seen.has(key)) throw new TypeError(`${role} must not contain duplicate keys.`);
|
|
2744
|
+
seen.add(key);
|
|
2762
2745
|
}
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
const
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2746
|
+
return {
|
|
2747
|
+
keys: value,
|
|
2748
|
+
requested: seen
|
|
2749
|
+
};
|
|
2750
|
+
};
|
|
2751
|
+
function createKeyedDerive(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
|
|
2752
|
+
const dependencySpecs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
|
|
2753
|
+
const select = dependencySpecs === void 0 ? dependenciesOrSelect : selectOrEquality;
|
|
2754
|
+
const equality = dependencySpecs === void 0 ? selectOrEquality : maybeEquality;
|
|
2755
|
+
if (typeof select !== "function") throw new TypeError("Keyed derive requires a selector.");
|
|
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
|
+
});
|
|
2763
|
+
}
|
|
2764
|
+
const createKeyedKeys = (source) => {
|
|
2765
|
+
assertKeyedProjection(source, "Keyed keys source");
|
|
2781
2766
|
const [projection] = defineProcessor({
|
|
2782
|
-
dependencies:
|
|
2767
|
+
dependencies: [source],
|
|
2783
2768
|
outputs: [{
|
|
2784
2769
|
kind: "value",
|
|
2785
|
-
equality
|
|
2770
|
+
equality: Object.is
|
|
2786
2771
|
}],
|
|
2787
2772
|
create: () => ({ evaluate: (evaluation) => {
|
|
2773
|
+
const driver = evaluation.sources[0];
|
|
2788
2774
|
const output = evaluation.outputs[0];
|
|
2789
|
-
if (output.kind !== "value") throw new Error("derive requires
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
assertSynchronous(next);
|
|
2794
|
-
output.output.set(next);
|
|
2795
|
-
} })
|
|
2775
|
+
if (driver.kind !== "collection" || output.kind !== "value") throw new Error("derive.keyed.keys requires collection input and value output.");
|
|
2776
|
+
if (evaluation.reset || !driver.change || collectionHasStructuralChange(driver.change)) output.output.set(driver.read.ids());
|
|
2777
|
+
} }),
|
|
2778
|
+
name: "derive.keyed.keys"
|
|
2796
2779
|
});
|
|
2797
2780
|
return projection;
|
|
2781
|
+
};
|
|
2782
|
+
const createOrderedSnapshot = (source, name, make) => {
|
|
2783
|
+
assertKeyedProjection(source, `${name} source`);
|
|
2784
|
+
const [projection] = defineProcessor({
|
|
2785
|
+
dependencies: [source],
|
|
2786
|
+
outputs: [{
|
|
2787
|
+
kind: "value",
|
|
2788
|
+
equality: Object.is
|
|
2789
|
+
}],
|
|
2790
|
+
create: () => {
|
|
2791
|
+
let indexes = /* @__PURE__ */ new Map();
|
|
2792
|
+
const rebuild = (driver) => {
|
|
2793
|
+
const nextIndexes = /* @__PURE__ */ new Map();
|
|
2794
|
+
const result = Object.freeze(driver.read.ids().map((key, index) => {
|
|
2795
|
+
nextIndexes.set(key, index);
|
|
2796
|
+
return make(key, driver.read.get(key));
|
|
2797
|
+
}));
|
|
2798
|
+
indexes = nextIndexes;
|
|
2799
|
+
return result;
|
|
2800
|
+
};
|
|
2801
|
+
const rebuildIncremental = (driver, change, previous) => {
|
|
2802
|
+
const changedValues = /* @__PURE__ */ new Map();
|
|
2803
|
+
for (const entry of change.added) changedValues.set(entry.key, entry.after);
|
|
2804
|
+
for (const entry of change.updated) changedValues.set(entry.key, entry.after);
|
|
2805
|
+
const nextIndexes = /* @__PURE__ */ new Map();
|
|
2806
|
+
const result = Object.freeze(driver.read.ids().map((key, index) => {
|
|
2807
|
+
nextIndexes.set(key, index);
|
|
2808
|
+
if (changedValues.has(key)) return make(key, changedValues.get(key));
|
|
2809
|
+
const previousIndex = indexes.get(key);
|
|
2810
|
+
return previousIndex === void 0 ? make(key, driver.read.get(key)) : previous[previousIndex];
|
|
2811
|
+
}));
|
|
2812
|
+
indexes = nextIndexes;
|
|
2813
|
+
return result;
|
|
2814
|
+
};
|
|
2815
|
+
return {
|
|
2816
|
+
evaluate: (evaluation) => {
|
|
2817
|
+
const driver = evaluation.sources[0];
|
|
2818
|
+
const output = evaluation.outputs[0];
|
|
2819
|
+
if (driver.kind !== "collection" || output.kind !== "value") throw new Error(`${name} requires collection input and value output.`);
|
|
2820
|
+
const change = driver.change;
|
|
2821
|
+
if (evaluation.reset || !change || change.kind === "reset") {
|
|
2822
|
+
output.output.set(rebuild(driver));
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
const previous = output.previous;
|
|
2826
|
+
if (!previous) {
|
|
2827
|
+
output.output.set(rebuild(driver));
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2830
|
+
if (collectionHasStructuralChange(change)) {
|
|
2831
|
+
output.output.set(rebuildIncremental(driver, change, previous));
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
if (!change.updated.length) return;
|
|
2835
|
+
const next = [...previous];
|
|
2836
|
+
for (const entry of change.updated) {
|
|
2837
|
+
const index = indexes.get(entry.key);
|
|
2838
|
+
if (index === void 0) {
|
|
2839
|
+
output.output.set(rebuild(driver));
|
|
2840
|
+
return;
|
|
2841
|
+
}
|
|
2842
|
+
next[index] = make(entry.key, entry.after);
|
|
2843
|
+
}
|
|
2844
|
+
output.output.set(Object.freeze(next));
|
|
2845
|
+
},
|
|
2846
|
+
release: () => indexes.clear()
|
|
2847
|
+
};
|
|
2848
|
+
},
|
|
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"
|
|
2895
|
+
});
|
|
2896
|
+
return projection;
|
|
2897
|
+
};
|
|
2898
|
+
function createKeyedFilter(source, dependenciesOrPredicate, maybePredicate) {
|
|
2899
|
+
const dependencySpecs = typeof dependenciesOrPredicate === "function" ? void 0 : dependenciesOrPredicate;
|
|
2900
|
+
const predicate = dependencySpecs === void 0 ? dependenciesOrPredicate : maybePredicate;
|
|
2901
|
+
if (typeof predicate !== "function") throw new TypeError("Keyed filter requires a predicate.");
|
|
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
|
+
});
|
|
2798
2914
|
}
|
|
2799
|
-
function
|
|
2800
|
-
assertKeyedProjection(source, "Keyed derive source");
|
|
2915
|
+
function createKeyedCompact(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
|
|
2801
2916
|
const dependencySpecs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
|
|
2802
2917
|
const select = dependencySpecs === void 0 ? dependenciesOrSelect : selectOrEquality;
|
|
2803
2918
|
const equality = dependencySpecs === void 0 ? selectOrEquality : maybeEquality;
|
|
2804
|
-
if (typeof select !== "function") throw new TypeError("Keyed
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
const dependency = descriptor.value;
|
|
2815
|
-
dependencyNames.push(name);
|
|
2816
|
-
if (isProjection(dependency)) {
|
|
2817
|
-
dependencies.push(dependency);
|
|
2818
|
-
keyed.push(void 0);
|
|
2819
|
-
continue;
|
|
2820
|
-
}
|
|
2821
|
-
if (!isKeyedDependency(dependency)) throw new TypeError("Keyed derive dependencies must be projections or keyed lookups.");
|
|
2822
|
-
assertKeyedProjection(dependency.source, "Dynamic keyed dependency source");
|
|
2823
|
-
dependencies.push(dependency.source);
|
|
2824
|
-
keyed.push(dependency);
|
|
2919
|
+
if (typeof select !== "function") throw new TypeError("Keyed compact requires a selector.");
|
|
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;
|
|
2825
2929
|
}
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
const createKeyedSubset = (source, orderedKeys) => {
|
|
2933
|
+
assertKeyedProjection(source, "Keyed subset source");
|
|
2934
|
+
const keyProjection = isProjection(orderedKeys) ? orderedKeys : void 0;
|
|
2935
|
+
if (keyProjection && outputKind(keyProjection) !== "value") throw new TypeError("Keyed subset ordered keys must be a value projection.");
|
|
2936
|
+
const staticSelection = keyProjection ? void 0 : (() => {
|
|
2937
|
+
const compiled = compileOrderedKeys(orderedKeys, "Keyed subset ordered keys");
|
|
2938
|
+
return {
|
|
2939
|
+
keys: Object.freeze([...compiled.keys]),
|
|
2940
|
+
requested: compiled.requested
|
|
2941
|
+
};
|
|
2942
|
+
})();
|
|
2943
|
+
const [projection] = defineProcessor({
|
|
2944
|
+
dependencies: keyProjection ? [source, keyProjection] : [source],
|
|
2945
|
+
outputs: [{
|
|
2946
|
+
kind: "collection",
|
|
2947
|
+
equality: Object.is
|
|
2948
|
+
}],
|
|
2949
|
+
create: () => {
|
|
2950
|
+
let keys = staticSelection?.keys;
|
|
2951
|
+
let requested = staticSelection?.requested;
|
|
2952
|
+
let keyRevision = -1;
|
|
2953
|
+
return {
|
|
2954
|
+
evaluate: (evaluation) => {
|
|
2955
|
+
const driver = evaluation.sources[0];
|
|
2956
|
+
const output = evaluation.outputs[0];
|
|
2957
|
+
if (driver.kind !== "collection" || output.kind !== "collection") throw new Error("derive.keyed.subset requires collection input and output.");
|
|
2958
|
+
let keysChanged = false;
|
|
2959
|
+
if (keyProjection) {
|
|
2960
|
+
const sourceContext = evaluation.sources[1];
|
|
2961
|
+
if (sourceContext?.kind !== "value") throw new TypeError("Keyed subset ordered keys resolved to a collection.");
|
|
2962
|
+
if (keys === void 0 || sourceContext.reset || keyRevision !== sourceContext.revision) {
|
|
2963
|
+
const compiled = compileOrderedKeys(sourceContext.value, "Keyed subset ordered keys");
|
|
2964
|
+
keys = compiled.keys;
|
|
2965
|
+
requested = compiled.requested;
|
|
2966
|
+
keyRevision = sourceContext.revision;
|
|
2967
|
+
keysChanged = true;
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
if (!keys || !requested) throw new Error("Keyed subset keys were not initialized.");
|
|
2971
|
+
const activeKeys = keys;
|
|
2972
|
+
const activeRequested = requested;
|
|
2973
|
+
const orderedPresentKeys = () => activeKeys.filter((key) => driver.read.has(key));
|
|
2974
|
+
const rebuild = () => {
|
|
2975
|
+
const nextKeys = orderedPresentKeys();
|
|
2976
|
+
for (const key of nextKeys) output.output.set(key, driver.read.get(key));
|
|
2977
|
+
output.output.order(nextKeys);
|
|
2978
|
+
};
|
|
2979
|
+
const reconcileKeys = () => {
|
|
2980
|
+
const nextKeys = orderedPresentKeys();
|
|
2981
|
+
for (const key of output.previous.ids()) if (!activeRequested.has(key) || !driver.read.has(key)) output.output.remove(key);
|
|
2982
|
+
for (const key of nextKeys) if (!output.previous.has(key)) output.output.set(key, driver.read.get(key));
|
|
2983
|
+
output.output.order(nextKeys);
|
|
2984
|
+
};
|
|
2985
|
+
const change = driver.change;
|
|
2986
|
+
if (evaluation.reset || change?.kind === "reset") rebuild();
|
|
2987
|
+
else if (keysChanged) {
|
|
2988
|
+
reconcileKeys();
|
|
2989
|
+
if (change) for (const entry of change.updated) {
|
|
2990
|
+
const key = entry.key;
|
|
2991
|
+
if (activeRequested.has(key) && output.previous.has(key)) output.output.set(key, entry.after);
|
|
2992
|
+
}
|
|
2993
|
+
} else if (change) {
|
|
2994
|
+
let updateOrder = false;
|
|
2995
|
+
for (const entry of change.removed) {
|
|
2996
|
+
const key = entry.key;
|
|
2997
|
+
if (!activeRequested.has(key)) continue;
|
|
2998
|
+
output.output.remove(key);
|
|
2999
|
+
updateOrder = true;
|
|
3000
|
+
}
|
|
3001
|
+
for (const entry of change.added) {
|
|
3002
|
+
const key = entry.key;
|
|
3003
|
+
if (!activeRequested.has(key)) continue;
|
|
3004
|
+
output.output.set(key, entry.after);
|
|
3005
|
+
updateOrder = true;
|
|
3006
|
+
}
|
|
3007
|
+
for (const entry of change.updated) {
|
|
3008
|
+
const key = entry.key;
|
|
3009
|
+
if (activeRequested.has(key)) output.output.set(key, entry.after);
|
|
3010
|
+
}
|
|
3011
|
+
if (updateOrder) output.output.order(orderedPresentKeys());
|
|
3012
|
+
}
|
|
3013
|
+
},
|
|
3014
|
+
release: () => {
|
|
3015
|
+
if (keyProjection) {
|
|
3016
|
+
keys = void 0;
|
|
3017
|
+
requested = void 0;
|
|
3018
|
+
keyRevision = -1;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
};
|
|
3022
|
+
},
|
|
3023
|
+
name: "derive.keyed.subset"
|
|
3024
|
+
});
|
|
3025
|
+
return projection;
|
|
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);
|
|
2826
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");
|
|
2827
3046
|
const [projection] = defineProcessor({
|
|
2828
|
-
dependencies,
|
|
3047
|
+
dependencies: compiled.projections,
|
|
2829
3048
|
outputs: [{
|
|
2830
3049
|
kind: "collection",
|
|
2831
|
-
equality:
|
|
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
|
+
}
|
|
2832
3055
|
}],
|
|
2833
3056
|
create: () => {
|
|
2834
|
-
const
|
|
2835
|
-
const
|
|
2836
|
-
const
|
|
2837
|
-
|
|
2838
|
-
const
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
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;
|
|
2842
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);
|
|
2843
3128
|
};
|
|
2844
3129
|
return {
|
|
2845
3130
|
evaluate: (evaluation) => {
|
|
2846
3131
|
const driver = evaluation.sources[0];
|
|
2847
3132
|
const output = evaluation.outputs[0];
|
|
2848
|
-
if (driver.kind !== "collection" || output.kind !== "collection") throw new Error("
|
|
2849
|
-
|
|
2850
|
-
for (let index = 0; index < keyed.length; index++) {
|
|
2851
|
-
if (keyed[index]) continue;
|
|
2852
|
-
if (evaluation.reset || revisions[index + 1] !== currentRevisions[index + 1]) globalValues[index] = sourceValue(evaluation.sources[index + 1]);
|
|
2853
|
-
}
|
|
2854
|
-
const project = (key) => {
|
|
2855
|
-
if (!driver.read.has(key)) return;
|
|
2856
|
-
const value = driver.read.get(key);
|
|
2857
|
-
const dependencyValues = Object.create(null);
|
|
2858
|
-
for (let index = 0; index < keyed.length; index++) {
|
|
2859
|
-
const dependency = keyed[index];
|
|
2860
|
-
if (!dependency) {
|
|
2861
|
-
dependencyValues[dependencyNames[index]] = globalValues[index];
|
|
2862
|
-
continue;
|
|
2863
|
-
}
|
|
2864
|
-
const sourceContext = evaluation.sources[index + 1];
|
|
2865
|
-
if (sourceContext.kind !== "collection") throw new TypeError("Dynamic keyed dependency resolved to a non-collection source.");
|
|
2866
|
-
const selectedKey = dependency.key(value, key);
|
|
2867
|
-
assertSynchronous(selectedKey);
|
|
2868
|
-
if (selectedKey !== void 0 && typeof selectedKey !== "string") throw new TypeError("Dynamic keyed dependency keys must be strings or undefined.");
|
|
2869
|
-
bindKey(bindings[index], key, selectedKey);
|
|
2870
|
-
dependencyValues[dependencyNames[index]] = selectedKey === void 0 ? void 0 : sourceContext.read.get(selectedKey);
|
|
2871
|
-
}
|
|
2872
|
-
const next = dependencySpecs === void 0 ? select(value, key) : select(value, key, Object.freeze(dependencyValues));
|
|
2873
|
-
assertSynchronous(next);
|
|
2874
|
-
output.output.set(key, next);
|
|
2875
|
-
};
|
|
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);
|
|
2876
3135
|
if (evaluation.reset) {
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
for (const key of ids) project(key);
|
|
2880
|
-
output.output.order(ids);
|
|
2881
|
-
revisions = Object.freeze(currentRevisions);
|
|
3136
|
+
rebuild(driver, evaluation.sources, output);
|
|
3137
|
+
dependencies.remember(evaluation.sources);
|
|
2882
3138
|
return;
|
|
2883
3139
|
}
|
|
2884
3140
|
const dirty = /* @__PURE__ */ new Set();
|
|
3141
|
+
const affected = /* @__PURE__ */ new Set();
|
|
2885
3142
|
let all = false;
|
|
2886
|
-
let
|
|
2887
|
-
|
|
3143
|
+
let structural = false;
|
|
3144
|
+
let orderDirty = false;
|
|
3145
|
+
if (dependencies.driverChanged(driver)) {
|
|
2888
3146
|
const change = driver.change;
|
|
2889
3147
|
if (!change || change.kind === "reset") {
|
|
2890
|
-
|
|
2891
|
-
|
|
3148
|
+
rebuild(driver, evaluation.sources, output);
|
|
3149
|
+
dependencies.remember(evaluation.sources);
|
|
3150
|
+
return;
|
|
2892
3151
|
} else {
|
|
2893
3152
|
for (const entry of change.removed) {
|
|
2894
|
-
|
|
2895
|
-
|
|
3153
|
+
orderDirty ||= removeSourceKey(entry.key, affected);
|
|
3154
|
+
dependencies.remove(entry.key);
|
|
2896
3155
|
dirty.delete(entry.key);
|
|
2897
3156
|
}
|
|
2898
3157
|
for (const entry of change.added) dirty.add(entry.key);
|
|
2899
3158
|
for (const entry of change.updated) dirty.add(entry.key);
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
for (let index = 0; index < keyed.length; index++) {
|
|
2904
|
-
if (revisions[index + 1] === currentRevisions[index + 1]) continue;
|
|
2905
|
-
if (!keyed[index]) {
|
|
2906
|
-
all = true;
|
|
2907
|
-
continue;
|
|
2908
|
-
}
|
|
2909
|
-
const sourceContext = evaluation.sources[index + 1];
|
|
2910
|
-
if (sourceContext.kind !== "collection" || !sourceContext.change) {
|
|
2911
|
-
all = true;
|
|
2912
|
-
continue;
|
|
3159
|
+
structural = Boolean(change.added.length || change.removed.length || change.order);
|
|
3160
|
+
orderDirty ||= structural;
|
|
3161
|
+
if (structural) refreshOrderIndex(driver);
|
|
2913
3162
|
}
|
|
2914
|
-
if (sourceContext.change.kind === "reset") {
|
|
2915
|
-
all = true;
|
|
2916
|
-
continue;
|
|
2917
|
-
}
|
|
2918
|
-
const reverse = bindings[index].reverse;
|
|
2919
|
-
for (const transition of sourceContext.change.added) for (const outputKey of reverse.get(transition.key) ?? []) dirty.add(outputKey);
|
|
2920
|
-
for (const transition of sourceContext.change.updated) for (const outputKey of reverse.get(transition.key) ?? []) dirty.add(outputKey);
|
|
2921
|
-
for (const transition of sourceContext.change.removed) for (const outputKey of reverse.get(transition.key) ?? []) dirty.add(outputKey);
|
|
2922
3163
|
}
|
|
3164
|
+
all ||= dependencies.collectInvalidated(evaluation.sources, dirty);
|
|
2923
3165
|
if (all) for (const key of driver.read.ids()) dirty.add(key);
|
|
2924
|
-
for (const key of dirty)
|
|
2925
|
-
if (
|
|
2926
|
-
|
|
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);
|
|
2927
3170
|
},
|
|
2928
3171
|
release: () => {
|
|
2929
|
-
|
|
2930
|
-
|
|
3172
|
+
relation.clear();
|
|
3173
|
+
orderIndex.clear();
|
|
3174
|
+
groupRanks.clear();
|
|
3175
|
+
dependencies.release();
|
|
2931
3176
|
}
|
|
2932
3177
|
};
|
|
2933
3178
|
},
|
|
2934
|
-
name: "derive.keyed"
|
|
3179
|
+
name: "derive.keyed.groupBy"
|
|
2935
3180
|
});
|
|
2936
3181
|
return projection;
|
|
2937
3182
|
}
|
|
2938
|
-
const
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
const
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
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"
|
|
2961
3209
|
});
|
|
3210
|
+
return projection;
|
|
2962
3211
|
};
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
3212
|
+
const keyedDerive = Object.assign(createKeyedDerive, {
|
|
3213
|
+
keys: createKeyedKeys,
|
|
3214
|
+
values: createKeyedValues,
|
|
3215
|
+
entries: createKeyedEntries,
|
|
3216
|
+
get: createKeyedGet,
|
|
3217
|
+
groupBy: createKeyedGroupBy,
|
|
3218
|
+
singleton: createKeyedSingleton,
|
|
3219
|
+
subset: createKeyedSubset,
|
|
3220
|
+
filter: createKeyedFilter,
|
|
3221
|
+
compact: createKeyedCompact
|
|
3222
|
+
});
|
|
3223
|
+
//#endregion
|
|
3224
|
+
//#region core/src/projection/derive.ts
|
|
3225
|
+
function createValueDerive(dependencies, compute, equality = Object.is) {
|
|
3226
|
+
const compiled = compileProjectionDependencies(dependencies, "Derive");
|
|
3227
|
+
const names = compiled.names;
|
|
3228
|
+
const ordered = compiled.projections;
|
|
3229
|
+
const [projection] = defineProcessor({
|
|
3230
|
+
dependencies: ordered,
|
|
3231
|
+
outputs: [{
|
|
3232
|
+
kind: "value",
|
|
3233
|
+
equality
|
|
3234
|
+
}],
|
|
3235
|
+
create: () => ({ evaluate: (evaluation) => {
|
|
3236
|
+
const output = evaluation.outputs[0];
|
|
3237
|
+
if (output.kind !== "value") throw new Error("derive requires a value output.");
|
|
3238
|
+
const values = Object.create(null);
|
|
3239
|
+
for (let index = 0; index < names.length; index++) values[names[index]] = snapshotDependencyValue(evaluation.sources[index]);
|
|
3240
|
+
const next = compute(Object.freeze(values));
|
|
3241
|
+
assertSynchronous(next);
|
|
3242
|
+
output.output.set(next);
|
|
3243
|
+
} })
|
|
2990
3244
|
});
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
...change.updated.map((entry) => entry.key),
|
|
2995
|
-
...change.removed.map((entry) => entry.key)
|
|
2996
|
-
]);
|
|
2997
|
-
const collectionHasStructuralChange = (change) => change.kind === "reset" || Boolean(change.added.length || change.removed.length || change.order);
|
|
2998
|
-
const collectionHasAnyChange = (change) => change.kind === "reset" || Boolean(change.added.length || change.updated.length || change.removed.length || change.order);
|
|
3245
|
+
return projection;
|
|
3246
|
+
}
|
|
3247
|
+
const derive = Object.assign(createValueDerive, { keyed: keyedDerive });
|
|
2999
3248
|
//#endregion
|
|
3000
3249
|
//#region core/src/projection/collection/index.ts
|
|
3001
3250
|
const height = (node) => node?.height ?? 0;
|
|
@@ -3208,15 +3457,36 @@ const createCollectionOutput = (binding) => {
|
|
|
3208
3457
|
profile.projection("touchedKeys", staged.size);
|
|
3209
3458
|
const added = /* @__PURE__ */ new Set();
|
|
3210
3459
|
const removed = /* @__PURE__ */ new Set();
|
|
3211
|
-
const
|
|
3460
|
+
const addedEntries = [];
|
|
3461
|
+
const updatedEntries = [];
|
|
3462
|
+
const removedEntries = [];
|
|
3212
3463
|
for (const [key, entry] of staged) {
|
|
3213
3464
|
const existed = published.has(key);
|
|
3214
3465
|
if (entry.present) {
|
|
3215
|
-
if (!existed)
|
|
3216
|
-
|
|
3466
|
+
if (!existed) {
|
|
3467
|
+
added.add(key);
|
|
3468
|
+
addedEntries.push({
|
|
3469
|
+
key,
|
|
3470
|
+
after: entry.value
|
|
3471
|
+
});
|
|
3472
|
+
continue;
|
|
3473
|
+
}
|
|
3474
|
+
const before = published.get(key);
|
|
3475
|
+
if (!isEqual(before, entry.value)) updatedEntries.push({
|
|
3476
|
+
key,
|
|
3477
|
+
before,
|
|
3478
|
+
after: entry.value
|
|
3479
|
+
});
|
|
3217
3480
|
else if (!nextReset) staged.delete(key);
|
|
3218
|
-
|
|
3219
|
-
|
|
3481
|
+
continue;
|
|
3482
|
+
}
|
|
3483
|
+
if (existed) {
|
|
3484
|
+
removed.add(key);
|
|
3485
|
+
removedEntries.push({
|
|
3486
|
+
key,
|
|
3487
|
+
before: published.get(key)
|
|
3488
|
+
});
|
|
3489
|
+
} else staged.delete(key);
|
|
3220
3490
|
}
|
|
3221
3491
|
const ids = published.ids();
|
|
3222
3492
|
if (explicitOrder || added.size || removed.size) {
|
|
@@ -3224,35 +3494,7 @@ const createCollectionOutput = (binding) => {
|
|
|
3224
3494
|
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.");
|
|
3225
3495
|
nextIds = ids.length === order.length && ids.every((key, index) => key === order[index]) ? ids : Object.freeze([...order]);
|
|
3226
3496
|
} else nextIds = ids;
|
|
3227
|
-
|
|
3228
|
-
added.forEach((key) => changedKeys.add(key));
|
|
3229
|
-
removed.forEach((key) => changedKeys.add(key));
|
|
3230
|
-
updated.forEach((key) => changedKeys.add(key));
|
|
3231
|
-
profile.projection("changedKeys", changedKeys.size);
|
|
3232
|
-
const addedEntries = [];
|
|
3233
|
-
const updatedEntries = [];
|
|
3234
|
-
const removedEntries = [];
|
|
3235
|
-
for (const key of changedKeys) {
|
|
3236
|
-
const beforePresent = published.has(key);
|
|
3237
|
-
const afterPresent = hasNext(key);
|
|
3238
|
-
if (beforePresent === afterPresent) {
|
|
3239
|
-
if (!beforePresent) continue;
|
|
3240
|
-
if (isEqual !== Object.is && isEqual(published.get(key), getNext(key))) continue;
|
|
3241
|
-
}
|
|
3242
|
-
if (!beforePresent) addedEntries.push({
|
|
3243
|
-
key,
|
|
3244
|
-
after: getNext(key)
|
|
3245
|
-
});
|
|
3246
|
-
else if (!afterPresent) removedEntries.push({
|
|
3247
|
-
key,
|
|
3248
|
-
before: published.get(key)
|
|
3249
|
-
});
|
|
3250
|
-
else updatedEntries.push({
|
|
3251
|
-
key,
|
|
3252
|
-
before: published.get(key),
|
|
3253
|
-
after: getNext(key)
|
|
3254
|
-
});
|
|
3255
|
-
}
|
|
3497
|
+
profile.projection("changedKeys", addedEntries.length + updatedEntries.length + removedEntries.length);
|
|
3256
3498
|
change = nextReset ? Object.freeze({ kind: "reset" }) : createCollectionChange({
|
|
3257
3499
|
added: addedEntries,
|
|
3258
3500
|
updated: updatedEntries,
|
|
@@ -3301,7 +3543,7 @@ const createCollectionOutput = (binding) => {
|
|
|
3301
3543
|
initialized = true;
|
|
3302
3544
|
},
|
|
3303
3545
|
emit: (call) => {
|
|
3304
|
-
Array.from(listeners).forEach((listener) => call(listener));
|
|
3546
|
+
Array.from(listeners).forEach((listener) => call(listener, change));
|
|
3305
3547
|
},
|
|
3306
3548
|
hasConsumers: () => consumers.size > 0,
|
|
3307
3549
|
forEachConsumer: (run) => consumers.forEach(run),
|
|
@@ -3411,7 +3653,7 @@ const createValueOutput = (binding) => {
|
|
|
3411
3653
|
},
|
|
3412
3654
|
emit: (call) => {
|
|
3413
3655
|
profile.materialized.notification();
|
|
3414
|
-
Array.from(listeners).forEach((listener) => call(listener));
|
|
3656
|
+
Array.from(listeners).forEach((listener) => call(listener, void 0));
|
|
3415
3657
|
},
|
|
3416
3658
|
hasConsumers: () => consumers.size > 0,
|
|
3417
3659
|
forEachConsumer: (run) => consumers.forEach(run),
|
|
@@ -3566,6 +3808,20 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
3566
3808
|
return processor;
|
|
3567
3809
|
};
|
|
3568
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
|
|
3569
3825
|
//#region core/src/projection/readable/selection.ts
|
|
3570
3826
|
let activeTracker;
|
|
3571
3827
|
const trackKey = (key) => {
|
|
@@ -3617,10 +3873,9 @@ const trackedMap = (source) => {
|
|
|
3617
3873
|
};
|
|
3618
3874
|
const selectionAffects = (selection, change) => {
|
|
3619
3875
|
if (!change || change.kind === "reset") return true;
|
|
3620
|
-
if (selection.all
|
|
3876
|
+
if (selection.all) return true;
|
|
3621
3877
|
if (selection.structure && collectionHasStructuralChange(change)) return true;
|
|
3622
|
-
const
|
|
3623
|
-
for (const key of selection.keys) if (changedKeys.has(key)) return true;
|
|
3878
|
+
for (const key of collectionChange.keys(change)) if (selection.keys.has(key)) return true;
|
|
3624
3879
|
return false;
|
|
3625
3880
|
};
|
|
3626
3881
|
const createDirectReadable = (source) => Object.freeze({
|
|
@@ -3701,7 +3956,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
|
|
|
3701
3956
|
updateCollectionSnapshot();
|
|
3702
3957
|
return;
|
|
3703
3958
|
}
|
|
3704
|
-
if (evaluate())
|
|
3959
|
+
if (evaluate()) notifyProjectionListeners(listeners, "Projection selector listeners failed.");
|
|
3705
3960
|
};
|
|
3706
3961
|
const installSource = () => {
|
|
3707
3962
|
if (!unsubscribeSource) unsubscribeSource = source.subscribe(onProjectionChange);
|
|
@@ -4162,7 +4417,7 @@ const stageCollectionRead = (state, read, active, options) => {
|
|
|
4162
4417
|
evaluation.output.order(ids);
|
|
4163
4418
|
} else {
|
|
4164
4419
|
const previous = state.current();
|
|
4165
|
-
const keys = options.candidates ? [...
|
|
4420
|
+
const keys = options.candidates ? [...options.candidates] : [...new Set([...previous.ids(), ...read.ids()])];
|
|
4166
4421
|
if (!options.candidates) profile.collectionView.idsScanned(keys.length);
|
|
4167
4422
|
for (const key of keys) if (read.has(key)) {
|
|
4168
4423
|
profile.collectionView.mapped();
|
|
@@ -4851,6 +5106,7 @@ const materializeInputSource = (scheduler, definition) => {
|
|
|
4851
5106
|
scheduler.assertIdle();
|
|
4852
5107
|
const staged = /* @__PURE__ */ new Map();
|
|
4853
5108
|
const operations = [];
|
|
5109
|
+
let structural = false;
|
|
4854
5110
|
let active = true;
|
|
4855
5111
|
const draft = Object.freeze({
|
|
4856
5112
|
get: (key) => {
|
|
@@ -4866,6 +5122,8 @@ const materializeInputSource = (scheduler, definition) => {
|
|
|
4866
5122
|
set: (key, value) => {
|
|
4867
5123
|
assertScope(() => active);
|
|
4868
5124
|
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
5125
|
+
const previous = staged.get(key);
|
|
5126
|
+
structural ||= previous ? !previous.present : !values.has(key);
|
|
4869
5127
|
staged.set(key, {
|
|
4870
5128
|
present: true,
|
|
4871
5129
|
value
|
|
@@ -4879,8 +5137,11 @@ const materializeInputSource = (scheduler, definition) => {
|
|
|
4879
5137
|
remove: (key) => {
|
|
4880
5138
|
assertScope(() => active);
|
|
4881
5139
|
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
5140
|
+
const previous = staged.get(key);
|
|
5141
|
+
const present = previous ? previous.present : values.has(key);
|
|
5142
|
+
structural ||= present;
|
|
4882
5143
|
staged.set(key, { present: false });
|
|
4883
|
-
operations.push({
|
|
5144
|
+
if (present) operations.push({
|
|
4884
5145
|
kind: "remove",
|
|
4885
5146
|
key
|
|
4886
5147
|
});
|
|
@@ -4891,32 +5152,25 @@ const materializeInputSource = (scheduler, definition) => {
|
|
|
4891
5152
|
} finally {
|
|
4892
5153
|
active = false;
|
|
4893
5154
|
}
|
|
5155
|
+
if (!operations.length) return;
|
|
4894
5156
|
const candidates = /* @__PURE__ */ new Set();
|
|
4895
|
-
|
|
4896
|
-
const
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
value: operation.value
|
|
4908
|
-
});
|
|
4909
|
-
} else {
|
|
4910
|
-
if (!present) continue;
|
|
4911
|
-
structural = true;
|
|
4912
|
-
overlay.set(operation.key, { present: false });
|
|
4913
|
-
}
|
|
4914
|
-
accepted.push(operation);
|
|
4915
|
-
candidates.add(operation.key);
|
|
5157
|
+
const retained = /* @__PURE__ */ new Map();
|
|
5158
|
+
for (const [key, entry] of staged) {
|
|
5159
|
+
const existed = values.has(key);
|
|
5160
|
+
if (entry.present) {
|
|
5161
|
+
if (!existed) {
|
|
5162
|
+
candidates.add(key);
|
|
5163
|
+
continue;
|
|
5164
|
+
}
|
|
5165
|
+
const previous = values.get(key);
|
|
5166
|
+
if (source.equality(previous, entry.value)) retained.set(key, previous);
|
|
5167
|
+
else candidates.add(key);
|
|
5168
|
+
} else if (existed) candidates.add(key);
|
|
4916
5169
|
}
|
|
4917
|
-
if (!
|
|
4918
|
-
for (const operation of
|
|
5170
|
+
if (!candidates.size && !structural) return;
|
|
5171
|
+
for (const operation of operations) if (operation.kind === "set") values.set(operation.key, operation.value);
|
|
4919
5172
|
else values.delete(operation.key);
|
|
5173
|
+
for (const [key, value] of retained) if (values.has(key)) values.set(key, value);
|
|
4920
5174
|
boundary.mark({
|
|
4921
5175
|
candidates,
|
|
4922
5176
|
orderMayChange: structural
|
|
@@ -4959,6 +5213,212 @@ const createSourceRegistry = (scheduler) => {
|
|
|
4959
5213
|
};
|
|
4960
5214
|
};
|
|
4961
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
|
|
4962
5422
|
//#region core/src/projection/runtime.ts
|
|
4963
5423
|
const createProjectionRuntime = (options) => {
|
|
4964
5424
|
const scheduler = createScheduler(options?.onError ?? (() => void 0));
|
|
@@ -4966,6 +5426,30 @@ const createProjectionRuntime = (options) => {
|
|
|
4966
5426
|
const materialized = /* @__PURE__ */ new WeakMap();
|
|
4967
5427
|
const publicViews = /* @__PURE__ */ new WeakMap();
|
|
4968
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
|
+
};
|
|
4969
5453
|
const assertAccess = (definition, requester) => {
|
|
4970
5454
|
scheduler.assertActive();
|
|
4971
5455
|
const owner = definition.owner;
|
|
@@ -5051,6 +5535,19 @@ const createProjectionRuntime = (options) => {
|
|
|
5051
5535
|
subscribe: output.subscribe
|
|
5052
5536
|
});
|
|
5053
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
|
+
};
|
|
5054
5551
|
const makeReadable = (projection, selector, equality, requester) => {
|
|
5055
5552
|
const source = readableSource(projection, requester);
|
|
5056
5553
|
return selector ? createSelectorReadable(source, selector, equality) : createDirectReadable(source);
|
|
@@ -5084,13 +5581,7 @@ const createProjectionRuntime = (options) => {
|
|
|
5084
5581
|
scheduler.assertIdle();
|
|
5085
5582
|
state.active = false;
|
|
5086
5583
|
const failures = [];
|
|
5087
|
-
|
|
5088
|
-
state.subscriptions.clear();
|
|
5089
|
-
for (const unsubscribe of subscriptions) try {
|
|
5090
|
-
unsubscribe();
|
|
5091
|
-
} catch (error) {
|
|
5092
|
-
failures.push(error);
|
|
5093
|
-
}
|
|
5584
|
+
disposeScopeConsumers(state, failures);
|
|
5094
5585
|
for (let index = state.materialized.length - 1; index >= 0; index--) {
|
|
5095
5586
|
const definition = state.materialized[index];
|
|
5096
5587
|
const current = materialized.get(definition);
|
|
@@ -5105,14 +5596,15 @@ const createProjectionRuntime = (options) => {
|
|
|
5105
5596
|
}
|
|
5106
5597
|
state.materialized.length = 0;
|
|
5107
5598
|
scopes.delete(state);
|
|
5108
|
-
|
|
5599
|
+
throwCleanupFailures(failures, "Projection scope cleanup failed.");
|
|
5109
5600
|
};
|
|
5110
5601
|
const createScope = () => {
|
|
5111
5602
|
scheduler.assertIdle();
|
|
5112
5603
|
const state = {
|
|
5113
5604
|
active: true,
|
|
5114
5605
|
materialized: [],
|
|
5115
|
-
subscriptions: /* @__PURE__ */ new Set()
|
|
5606
|
+
subscriptions: /* @__PURE__ */ new Set(),
|
|
5607
|
+
items: /* @__PURE__ */ new Map()
|
|
5116
5608
|
};
|
|
5117
5609
|
scopes.add(state);
|
|
5118
5610
|
const assertActive = () => {
|
|
@@ -5184,6 +5676,10 @@ const createProjectionRuntime = (options) => {
|
|
|
5184
5676
|
return readProjection(projection, state);
|
|
5185
5677
|
},
|
|
5186
5678
|
select: scopeSelect,
|
|
5679
|
+
items: (projection) => {
|
|
5680
|
+
assertActive();
|
|
5681
|
+
return projectionItems(projection, state);
|
|
5682
|
+
},
|
|
5187
5683
|
update: scopeUpdate,
|
|
5188
5684
|
batch: scopeBatch,
|
|
5189
5685
|
dispose: () => disposeScope(state)
|
|
@@ -5192,6 +5688,7 @@ const createProjectionRuntime = (options) => {
|
|
|
5192
5688
|
return Object.freeze({
|
|
5193
5689
|
read: readProjection,
|
|
5194
5690
|
select: selectProjection,
|
|
5691
|
+
items: projectionItems,
|
|
5195
5692
|
update,
|
|
5196
5693
|
batch,
|
|
5197
5694
|
scope: createScope,
|
|
@@ -5201,16 +5698,11 @@ const createProjectionRuntime = (options) => {
|
|
|
5201
5698
|
const failures = [];
|
|
5202
5699
|
for (const scope of scopes) {
|
|
5203
5700
|
scope.active = false;
|
|
5204
|
-
|
|
5205
|
-
scope.subscriptions.clear();
|
|
5206
|
-
for (const unsubscribe of subscriptions) try {
|
|
5207
|
-
unsubscribe();
|
|
5208
|
-
} catch (error) {
|
|
5209
|
-
failures.push(error);
|
|
5210
|
-
}
|
|
5701
|
+
disposeScopeConsumers(scope, failures);
|
|
5211
5702
|
scope.materialized.length = 0;
|
|
5212
5703
|
}
|
|
5213
5704
|
scopes.clear();
|
|
5705
|
+
disposeItemControllers(rootItems, failures);
|
|
5214
5706
|
try {
|
|
5215
5707
|
sources.dispose();
|
|
5216
5708
|
} catch (error) {
|
|
@@ -5221,8 +5713,7 @@ const createProjectionRuntime = (options) => {
|
|
|
5221
5713
|
} catch (error) {
|
|
5222
5714
|
failures.push(error);
|
|
5223
5715
|
}
|
|
5224
|
-
|
|
5225
|
-
if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
|
|
5716
|
+
throwCleanupFailures(failures, "Projection cleanup failed.");
|
|
5226
5717
|
}
|
|
5227
5718
|
});
|
|
5228
5719
|
};
|