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.
- package/README.md +66 -3
- package/dist/advanced.cjs +40 -14
- package/dist/advanced.cjs.map +1 -1
- package/dist/advanced.d.cts +27 -2
- package/dist/advanced.d.ts +27 -2
- package/dist/advanced.js +27 -1
- 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 +663 -344
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -32
- package/dist/index.d.ts +23 -32
- package/dist/index.js +549 -230
- 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/{scheduler-vzf3OcjT.js → transform-B4T3AcJr.js} +356 -11
- package/dist/transform-B4T3AcJr.js.map +1 -0
- package/dist/{scheduler-k1ZGR1PG.cjs → transform-DUleAsG0.cjs} +393 -12
- package/dist/transform-DUleAsG0.cjs.map +1 -0
- package/package.json +1 -1
- package/skills/doxum-runtime/SKILL.md +11 -5
- package/skills/doxum-runtime/references/api.en.md +40 -21
- package/skills/doxum-runtime/references/api.zh-CN.md +21 -2
- 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 +47 -3
- package/skills/doxum-runtime/references/projections.zh-CN.md +45 -3
- package/dist/scheduler-k1ZGR1PG.cjs.map +0 -1
- package/dist/scheduler-vzf3OcjT.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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(
|
|
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
|
|
2960
|
-
assertKeyedProjection(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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
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(
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
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())
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5397
|
-
if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
|
|
5716
|
+
throwCleanupFailures(failures, "Projection cleanup failed.");
|
|
5398
5717
|
}
|
|
5399
5718
|
});
|
|
5400
5719
|
};
|