doxum 0.1.33 → 0.1.35
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 +18 -7
- package/dist/advanced.cjs +1 -1
- package/dist/advanced.js +1 -1
- package/dist/index.cjs +395 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -11
- package/dist/index.d.ts +13 -11
- package/dist/index.js +395 -142
- package/dist/index.js.map +1 -1
- package/dist/{transform-A9Lx5R2l.js → transform-3mj0eStL.js} +147 -152
- package/dist/transform-3mj0eStL.js.map +1 -0
- package/dist/{transform-CSPGdBe5.cjs → transform-D45KWMzj.cjs} +148 -159
- package/dist/transform-D45KWMzj.cjs.map +1 -0
- package/package.json +1 -1
- package/skills/doxum-runtime/SKILL.md +3 -3
- package/skills/doxum-runtime/references/integrations.md +2 -2
- package/skills/doxum-runtime/references/invariants.md +6 -3
- package/skills/doxum-runtime/references/projections.md +48 -20
- package/skills/doxum-runtime/references/public-api.md +22 -21
- package/skills/doxum-runtime/references/recipes.md +25 -2
- package/dist/transform-A9Lx5R2l.js.map +0 -1
- package/dist/transform-CSPGdBe5.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as ownProjections, C as snapshotProjectionValues, D as defineProcessor, E as snapshotCollectionView, M as projectionRef, N as profile, O as defineSource, S as compileProjectionDependencies, T as mapRead, _ as assertSynchronous, a as collectionChangeTouchesKey, b as ProjectionError, c as assertKeyedProjection, d as outputKind, f as createKeyRelation, g as assertScope, h as snapshotArray, i as collectionChangeIntersects, j as producerOf, k as isProjection, l as compileKeyedDependencies, m as sameArray, n as keyedAbsent, o as collectionHasStructuralChange, p as iterableMatchesArray, s as createCollectionChange, t as createKeyedTransform, u as createKeyedDependencyRuntime, v as createScheduler, w as collectionView, x as collectKeyedInvalidation, y as ProjectionDisposedError } from "./transform-3mj0eStL.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-B7RR-VfX.js";
|
|
4
4
|
//#region core/src/schema/model.ts
|
|
@@ -2774,6 +2774,12 @@ const isExternalSource = (source) => {
|
|
|
2774
2774
|
return (candidate.kind === "value" || candidate.kind === "collection") && typeof candidate.current === "function" && typeof candidate.revision === "function" && typeof candidate.subscribe === "function";
|
|
2775
2775
|
};
|
|
2776
2776
|
//#endregion
|
|
2777
|
+
//#region core/src/projection/keyed/entries.ts
|
|
2778
|
+
/** Shared entry boundary; membership and duplicate policy belong to each operation. */
|
|
2779
|
+
function assertKeyedEntry(entry, name) {
|
|
2780
|
+
if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== "string") throw new TypeError(`${name} entries must be [string key, value] tuples.`);
|
|
2781
|
+
}
|
|
2782
|
+
//#endregion
|
|
2777
2783
|
//#region core/src/projection/keyed/flat-map.ts
|
|
2778
2784
|
function createKeyedFlatMap(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
|
|
2779
2785
|
const name = "derive.keyed.flatMap";
|
|
@@ -2820,7 +2826,7 @@ function createKeyedFlatMap(source, dependenciesOrSelect, selectOrEquality, mayb
|
|
|
2820
2826
|
if (!Array.isArray(entries)) throw new TypeError(`${name} selector must return an array of entries.`);
|
|
2821
2827
|
const keys = [];
|
|
2822
2828
|
for (const entry of entries) {
|
|
2823
|
-
|
|
2829
|
+
assertKeyedEntry(entry, name);
|
|
2824
2830
|
const [key, child] = entry;
|
|
2825
2831
|
const owner = ownerByOutput.get(key);
|
|
2826
2832
|
if (proposals.has(key) || owner !== void 0 && !replaced.has(owner)) throw new TypeError(`${name} duplicate key ${JSON.stringify(key)}.`);
|
|
@@ -2907,6 +2913,25 @@ const assertCollectionSources = (sources, expected) => {
|
|
|
2907
2913
|
if (sources.length !== expected) throw new Error("derive.keyed.merge resolved an invalid dependency count.");
|
|
2908
2914
|
for (const source of sources) if (source.kind !== "collection") throw new Error("derive.keyed.merge requires collection inputs.");
|
|
2909
2915
|
};
|
|
2916
|
+
/** Complete ordered result reconciliation, without retaining a second member/value cache. */
|
|
2917
|
+
const stageKeyedSnapshot = (values, output, keyOf, valueOf, name) => {
|
|
2918
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2919
|
+
const order = new Array(values.length);
|
|
2920
|
+
const previous = output.previous.ids();
|
|
2921
|
+
profile.collectionView.idsScanned(values.length + previous.length);
|
|
2922
|
+
for (let index = 0; index < values.length; index++) {
|
|
2923
|
+
const value = values[index];
|
|
2924
|
+
const key = keyOf(value);
|
|
2925
|
+
assertSynchronous(key);
|
|
2926
|
+
if (typeof key !== "string") throw new TypeError(`${name} key selector must return a string.`);
|
|
2927
|
+
if (seen.has(key)) throw new TypeError(`${name} duplicate key ${JSON.stringify(key)}.`);
|
|
2928
|
+
seen.add(key);
|
|
2929
|
+
order[index] = key;
|
|
2930
|
+
output.output.set(key, valueOf(value));
|
|
2931
|
+
}
|
|
2932
|
+
for (const key of previous) if (!seen.has(key)) output.output.remove(key);
|
|
2933
|
+
output.output.order(order);
|
|
2934
|
+
};
|
|
2910
2935
|
function createKeyedFrom(source, keyOf, equality) {
|
|
2911
2936
|
if (typeof keyOf !== "function") throw new TypeError("derive.keyed.from requires a key selector.");
|
|
2912
2937
|
const outputEquality = assertEquality(equality, "derive.keyed.from");
|
|
@@ -2936,25 +2961,38 @@ function createKeyedFrom(source, keyOf, equality) {
|
|
|
2936
2961
|
values = scalar.value;
|
|
2937
2962
|
}
|
|
2938
2963
|
if (!values) throw new Error("derive.keyed.from has no source values.");
|
|
2939
|
-
|
|
2940
|
-
const order = new Array(values.length);
|
|
2941
|
-
for (let index = 0; index < values.length; index++) {
|
|
2942
|
-
const value = values[index];
|
|
2943
|
-
const key = keyOf(value);
|
|
2944
|
-
assertSynchronous(key);
|
|
2945
|
-
if (typeof key !== "string") throw new TypeError("derive.keyed.from key selector must return a string.");
|
|
2946
|
-
if (seen.has(key)) throw new TypeError(`derive.keyed.from duplicate key ${JSON.stringify(key)}.`);
|
|
2947
|
-
seen.add(key);
|
|
2948
|
-
order[index] = key;
|
|
2949
|
-
output.output.set(key, value);
|
|
2950
|
-
}
|
|
2951
|
-
for (const previousKey of output.previous.ids()) if (!seen.has(previousKey)) output.output.remove(previousKey);
|
|
2952
|
-
output.output.order(order);
|
|
2964
|
+
stageKeyedSnapshot(values, output, keyOf, (value) => value, "derive.keyed.from");
|
|
2953
2965
|
} }),
|
|
2954
2966
|
name: "derive.keyed.from"
|
|
2955
2967
|
});
|
|
2956
2968
|
return projection;
|
|
2957
2969
|
}
|
|
2970
|
+
function createKeyedFromEntries(dependencies, compute, equality) {
|
|
2971
|
+
const name = "derive.keyed.fromEntries";
|
|
2972
|
+
const compiled = compileProjectionDependencies(dependencies, name);
|
|
2973
|
+
if (typeof compute !== "function") throw new TypeError(`${name} requires a compute callback.`);
|
|
2974
|
+
const outputEquality = assertEquality(equality, name);
|
|
2975
|
+
const [projection] = defineProcessor({
|
|
2976
|
+
dependencies: compiled.projections,
|
|
2977
|
+
outputs: [{
|
|
2978
|
+
kind: "collection",
|
|
2979
|
+
equality: outputEquality
|
|
2980
|
+
}],
|
|
2981
|
+
name,
|
|
2982
|
+
create: () => ({ evaluate: (evaluation) => {
|
|
2983
|
+
const output = evaluation.outputs[0];
|
|
2984
|
+
if (output.kind !== "collection") throw new Error(`${name} requires a collection output.`);
|
|
2985
|
+
const entries = compute(snapshotProjectionValues(compiled.names, evaluation.sources));
|
|
2986
|
+
assertSynchronous(entries);
|
|
2987
|
+
if (!Array.isArray(entries)) throw new TypeError(`${name} compute must return an array of entries.`);
|
|
2988
|
+
stageKeyedSnapshot(entries, output, (entry) => {
|
|
2989
|
+
assertKeyedEntry(entry, name);
|
|
2990
|
+
return entry[0];
|
|
2991
|
+
}, (entry) => entry[1], name);
|
|
2992
|
+
} })
|
|
2993
|
+
});
|
|
2994
|
+
return projection;
|
|
2995
|
+
}
|
|
2958
2996
|
function createKeyedMerge(sources, options) {
|
|
2959
2997
|
if (!Array.isArray(sources)) throw new TypeError("derive.keyed.merge sources must be an array of keyed projections.");
|
|
2960
2998
|
const capturedSources = snapshotArray(sources);
|
|
@@ -3583,6 +3621,7 @@ const keyedDerive = Object.assign(createKeyedDerive, {
|
|
|
3583
3621
|
entries: createKeyedEntries,
|
|
3584
3622
|
get: createKeyedGet,
|
|
3585
3623
|
from: createKeyedFrom,
|
|
3624
|
+
fromEntries: createKeyedFromEntries,
|
|
3586
3625
|
merge: createKeyedMerge,
|
|
3587
3626
|
groupBy: createKeyedGroupBy,
|
|
3588
3627
|
flatMap: createKeyedFlatMap,
|
|
@@ -3606,9 +3645,7 @@ function createValueDerive(dependencies, compute, equality = Object.is) {
|
|
|
3606
3645
|
create: () => ({ evaluate: (evaluation) => {
|
|
3607
3646
|
const output = evaluation.outputs[0];
|
|
3608
3647
|
if (output.kind !== "value") throw new Error("derive requires a value output.");
|
|
3609
|
-
const
|
|
3610
|
-
for (let index = 0; index < names.length; index++) values[names[index]] = snapshotDependencyValue(evaluation.sources[index]);
|
|
3611
|
-
const next = compute(Object.freeze(values));
|
|
3648
|
+
const next = compute(snapshotProjectionValues(names, evaluation.sources));
|
|
3612
3649
|
assertSynchronous(next);
|
|
3613
3650
|
output.output.set(next);
|
|
3614
3651
|
} })
|
|
@@ -3617,6 +3654,104 @@ function createValueDerive(dependencies, compute, equality = Object.is) {
|
|
|
3617
3654
|
}
|
|
3618
3655
|
const derive = Object.assign(createValueDerive, { keyed: keyedDerive });
|
|
3619
3656
|
//#endregion
|
|
3657
|
+
//#region core/src/projection/collection/changes.ts
|
|
3658
|
+
/** Net transitions for one consumer's unconsumed interval; bounded by touched keys. */
|
|
3659
|
+
const createCollectionChanges = () => {
|
|
3660
|
+
let first;
|
|
3661
|
+
let beforeOrder = [];
|
|
3662
|
+
let afterOrder = [];
|
|
3663
|
+
let cached;
|
|
3664
|
+
let valid = false;
|
|
3665
|
+
let transitions;
|
|
3666
|
+
const merge = (change) => {
|
|
3667
|
+
const write = (key, before, after) => {
|
|
3668
|
+
const previous = transitions.get(key);
|
|
3669
|
+
const initial = previous?.before ?? before;
|
|
3670
|
+
if (initial.present === after.present && (!initial.present || after.present && Object.is(initial.value, after.value))) transitions.delete(key);
|
|
3671
|
+
else if (previous) previous.after = after;
|
|
3672
|
+
else transitions.set(key, {
|
|
3673
|
+
before,
|
|
3674
|
+
after
|
|
3675
|
+
});
|
|
3676
|
+
};
|
|
3677
|
+
for (const entry of change.added) write(entry.key, { present: false }, {
|
|
3678
|
+
present: true,
|
|
3679
|
+
value: entry.after
|
|
3680
|
+
});
|
|
3681
|
+
for (const entry of change.updated) write(entry.key, {
|
|
3682
|
+
present: true,
|
|
3683
|
+
value: entry.before
|
|
3684
|
+
}, {
|
|
3685
|
+
present: true,
|
|
3686
|
+
value: entry.after
|
|
3687
|
+
});
|
|
3688
|
+
for (const entry of change.removed) write(entry.key, {
|
|
3689
|
+
present: true,
|
|
3690
|
+
value: entry.before
|
|
3691
|
+
}, { present: false });
|
|
3692
|
+
};
|
|
3693
|
+
return {
|
|
3694
|
+
add(change, before, after) {
|
|
3695
|
+
valid = false;
|
|
3696
|
+
afterOrder = after;
|
|
3697
|
+
if (!first) {
|
|
3698
|
+
first = change;
|
|
3699
|
+
beforeOrder = before;
|
|
3700
|
+
return;
|
|
3701
|
+
}
|
|
3702
|
+
if (first.kind === "reset") return;
|
|
3703
|
+
if (change.kind === "reset") {
|
|
3704
|
+
first = change;
|
|
3705
|
+
transitions = void 0;
|
|
3706
|
+
return;
|
|
3707
|
+
}
|
|
3708
|
+
if (!transitions) {
|
|
3709
|
+
transitions = /* @__PURE__ */ new Map();
|
|
3710
|
+
merge(first);
|
|
3711
|
+
}
|
|
3712
|
+
merge(change);
|
|
3713
|
+
},
|
|
3714
|
+
current() {
|
|
3715
|
+
if (!transitions) return first;
|
|
3716
|
+
if (valid) return cached;
|
|
3717
|
+
const added = [];
|
|
3718
|
+
const updated = [];
|
|
3719
|
+
const removed = [];
|
|
3720
|
+
for (const [key, { before, after }] of transitions) if (!before.present) {
|
|
3721
|
+
if (after.present) added.push({
|
|
3722
|
+
key,
|
|
3723
|
+
after: after.value
|
|
3724
|
+
});
|
|
3725
|
+
} else if (!after.present) removed.push({
|
|
3726
|
+
key,
|
|
3727
|
+
before: before.value
|
|
3728
|
+
});
|
|
3729
|
+
else if (!Object.is(before.value, after.value)) updated.push({
|
|
3730
|
+
key,
|
|
3731
|
+
before: before.value,
|
|
3732
|
+
after: after.value
|
|
3733
|
+
});
|
|
3734
|
+
cached = createCollectionChange({
|
|
3735
|
+
added,
|
|
3736
|
+
updated,
|
|
3737
|
+
removed,
|
|
3738
|
+
beforeOrder,
|
|
3739
|
+
afterOrder
|
|
3740
|
+
});
|
|
3741
|
+
valid = true;
|
|
3742
|
+
return cached;
|
|
3743
|
+
},
|
|
3744
|
+
reset: () => first?.kind === "reset",
|
|
3745
|
+
clear() {
|
|
3746
|
+
first = void 0;
|
|
3747
|
+
cached = void 0;
|
|
3748
|
+
valid = false;
|
|
3749
|
+
transitions = void 0;
|
|
3750
|
+
beforeOrder = afterOrder = [];
|
|
3751
|
+
}
|
|
3752
|
+
};
|
|
3753
|
+
};
|
|
3754
|
+
//#endregion
|
|
3620
3755
|
//#region core/src/projection/collection/index.ts
|
|
3621
3756
|
const height = (node) => node?.height ?? 0;
|
|
3622
3757
|
const node = (key, value, left, right) => {
|
|
@@ -3711,7 +3846,7 @@ var PersistentKeyedIndex = class PersistentKeyedIndex {
|
|
|
3711
3846
|
const createCollectionState = (initial) => {
|
|
3712
3847
|
const values = new Map(initial);
|
|
3713
3848
|
let ids = Object.freeze([...values.keys()]);
|
|
3714
|
-
let index
|
|
3849
|
+
let index;
|
|
3715
3850
|
return {
|
|
3716
3851
|
get: (key) => values.get(key),
|
|
3717
3852
|
has: (key) => values.has(key),
|
|
@@ -3719,7 +3854,7 @@ const createCollectionState = (initial) => {
|
|
|
3719
3854
|
size: () => values.size,
|
|
3720
3855
|
ids: () => ids,
|
|
3721
3856
|
read: (check) => {
|
|
3722
|
-
const snapshotIndex = index;
|
|
3857
|
+
const snapshotIndex = index ??= PersistentKeyedIndex.from(values);
|
|
3723
3858
|
const snapshotIds = ids;
|
|
3724
3859
|
return Object.freeze({
|
|
3725
3860
|
get: (key) => {
|
|
@@ -3740,9 +3875,9 @@ const createCollectionState = (initial) => {
|
|
|
3740
3875
|
if (reset) {
|
|
3741
3876
|
values.clear();
|
|
3742
3877
|
for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
|
|
3743
|
-
index = PersistentKeyedIndex.from(values);
|
|
3878
|
+
if (index) index = PersistentKeyedIndex.from(values);
|
|
3744
3879
|
} else for (const [key, entry] of staged) {
|
|
3745
|
-
index = entry.present ? index.set(key, entry.value) : index.remove(key);
|
|
3880
|
+
if (index) index = entry.present ? index.set(key, entry.value) : index.remove(key);
|
|
3746
3881
|
if (entry.present) values.set(key, entry.value);
|
|
3747
3882
|
else values.delete(key);
|
|
3748
3883
|
}
|
|
@@ -3751,17 +3886,17 @@ const createCollectionState = (initial) => {
|
|
|
3751
3886
|
release: () => {
|
|
3752
3887
|
values.clear();
|
|
3753
3888
|
ids = Object.freeze([]);
|
|
3754
|
-
index =
|
|
3889
|
+
index = void 0;
|
|
3755
3890
|
}
|
|
3756
3891
|
};
|
|
3757
3892
|
};
|
|
3758
3893
|
//#endregion
|
|
3759
3894
|
//#region core/src/projection/output/collection.ts
|
|
3760
|
-
/** Owns staged keyed intent, exact transitions and
|
|
3895
|
+
/** Owns staged keyed intent, exact transitions and current collection state. */
|
|
3761
3896
|
const createCollectionOutput = (binding) => {
|
|
3762
|
-
const
|
|
3897
|
+
const currentState = createCollectionState();
|
|
3763
3898
|
const staged = /* @__PURE__ */ new Map();
|
|
3764
|
-
let nextIds =
|
|
3899
|
+
let nextIds = currentState.ids();
|
|
3765
3900
|
let change;
|
|
3766
3901
|
let initialized = false;
|
|
3767
3902
|
let reset = false;
|
|
@@ -3769,23 +3904,26 @@ const createCollectionOutput = (binding) => {
|
|
|
3769
3904
|
let explicitOrder;
|
|
3770
3905
|
let cleared = false;
|
|
3771
3906
|
const listeners = /* @__PURE__ */ new Set();
|
|
3772
|
-
const consumers = /* @__PURE__ */ new
|
|
3773
|
-
const
|
|
3907
|
+
const consumers = /* @__PURE__ */ new Map();
|
|
3908
|
+
const notifications = createCollectionChanges();
|
|
3909
|
+
const observers = /* @__PURE__ */ new Set();
|
|
3910
|
+
let baseline;
|
|
3911
|
+
const hasNext = (key) => staged.has(key) ? staged.get(key).present : !cleared && currentState.has(key);
|
|
3774
3912
|
const getNext = (key) => {
|
|
3775
3913
|
const entry = staged.get(key);
|
|
3776
|
-
return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 :
|
|
3914
|
+
return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : currentState.get(key);
|
|
3777
3915
|
};
|
|
3778
3916
|
const deriveIds = () => {
|
|
3779
3917
|
const result = [];
|
|
3780
3918
|
if (!cleared) {
|
|
3781
|
-
for (const key of
|
|
3919
|
+
for (const key of currentState.ids()) if (hasNext(key)) result.push(key);
|
|
3782
3920
|
}
|
|
3783
|
-
for (const [key, entry] of staged) if (entry.present && (cleared || !
|
|
3921
|
+
for (const [key, entry] of staged) if (entry.present && (cleared || !currentState.has(key))) result.push(key);
|
|
3784
3922
|
return result;
|
|
3785
3923
|
};
|
|
3786
3924
|
const begin = (active, initialize) => {
|
|
3787
3925
|
staged.clear();
|
|
3788
|
-
nextIds =
|
|
3926
|
+
nextIds = currentState.ids();
|
|
3789
3927
|
change = void 0;
|
|
3790
3928
|
explicitOrder = void 0;
|
|
3791
3929
|
cleared = initialize;
|
|
@@ -3793,15 +3931,15 @@ const createCollectionOutput = (binding) => {
|
|
|
3793
3931
|
const read = (next) => ({
|
|
3794
3932
|
get: (key) => {
|
|
3795
3933
|
assertScope(active);
|
|
3796
|
-
return next ? getNext(key) :
|
|
3934
|
+
return next ? getNext(key) : currentState.get(key);
|
|
3797
3935
|
},
|
|
3798
3936
|
has: (key) => {
|
|
3799
3937
|
assertScope(active);
|
|
3800
|
-
return next ? hasNext(key) :
|
|
3938
|
+
return next ? hasNext(key) : currentState.has(key);
|
|
3801
3939
|
},
|
|
3802
3940
|
ids: () => {
|
|
3803
3941
|
assertScope(active);
|
|
3804
|
-
if (!next) return
|
|
3942
|
+
if (!next) return currentState.ids();
|
|
3805
3943
|
if (explicitOrder) return explicitOrder;
|
|
3806
3944
|
return Object.freeze(deriveIds());
|
|
3807
3945
|
}
|
|
@@ -3831,15 +3969,28 @@ const createCollectionOutput = (binding) => {
|
|
|
3831
3969
|
};
|
|
3832
3970
|
const seal = (nextReset, isEqual) => {
|
|
3833
3971
|
if (cleared) {
|
|
3834
|
-
for (const key of
|
|
3972
|
+
for (const key of currentState.keys()) if (!staged.has(key)) staged.set(key, { present: false });
|
|
3835
3973
|
}
|
|
3836
3974
|
profile.projection("touchedKeys", staged.size);
|
|
3837
3975
|
const addedEntries = [];
|
|
3838
3976
|
const updatedEntries = [];
|
|
3839
3977
|
const removedEntries = [];
|
|
3840
|
-
for (const [key,
|
|
3841
|
-
|
|
3978
|
+
for (const [key, original] of staged) {
|
|
3979
|
+
let entry = original;
|
|
3980
|
+
const existed = currentState.has(key);
|
|
3842
3981
|
if (entry.present) {
|
|
3982
|
+
if (baseline?.has(key) && (!existed || !Object.is(currentState.get(key), baseline.get(key)))) {
|
|
3983
|
+
const before = baseline.get(key);
|
|
3984
|
+
const equivalent = isEqual(before, entry.value);
|
|
3985
|
+
assertSynchronous(equivalent);
|
|
3986
|
+
if (equivalent) {
|
|
3987
|
+
entry = {
|
|
3988
|
+
present: true,
|
|
3989
|
+
value: before
|
|
3990
|
+
};
|
|
3991
|
+
staged.set(key, entry);
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3843
3994
|
if (!existed) {
|
|
3844
3995
|
addedEntries.push({
|
|
3845
3996
|
key,
|
|
@@ -3847,7 +3998,7 @@ const createCollectionOutput = (binding) => {
|
|
|
3847
3998
|
});
|
|
3848
3999
|
continue;
|
|
3849
4000
|
}
|
|
3850
|
-
const before =
|
|
4001
|
+
const before = currentState.get(key);
|
|
3851
4002
|
const equal = isEqual(before, entry.value);
|
|
3852
4003
|
assertSynchronous(equal);
|
|
3853
4004
|
if (!equal) updatedEntries.push({
|
|
@@ -3864,13 +4015,13 @@ const createCollectionOutput = (binding) => {
|
|
|
3864
4015
|
}
|
|
3865
4016
|
if (existed) removedEntries.push({
|
|
3866
4017
|
key,
|
|
3867
|
-
before:
|
|
4018
|
+
before: currentState.get(key)
|
|
3868
4019
|
});
|
|
3869
4020
|
else staged.delete(key);
|
|
3870
4021
|
}
|
|
3871
|
-
const ids =
|
|
4022
|
+
const ids = currentState.ids();
|
|
3872
4023
|
if (explicitOrder) {
|
|
3873
|
-
const expectedLength =
|
|
4024
|
+
const expectedLength = currentState.size() + addedEntries.length - removedEntries.length;
|
|
3874
4025
|
const seen = /* @__PURE__ */ new Set();
|
|
3875
4026
|
for (const key of explicitOrder) {
|
|
3876
4027
|
if (seen.has(key) || !hasNext(key)) throw new TypeError("Projection order must contain every key exactly once.");
|
|
@@ -3879,7 +4030,7 @@ const createCollectionOutput = (binding) => {
|
|
|
3879
4030
|
if (seen.size !== expectedLength) throw new TypeError("Projection order must contain every key exactly once.");
|
|
3880
4031
|
nextIds = sameArray(ids, explicitOrder) ? ids : explicitOrder;
|
|
3881
4032
|
} else if (addedEntries.length || removedEntries.length) {
|
|
3882
|
-
const order = new Array(
|
|
4033
|
+
const order = new Array(currentState.size() + addedEntries.length - removedEntries.length);
|
|
3883
4034
|
let index = 0;
|
|
3884
4035
|
for (const key of ids) if (hasNext(key)) order[index++] = key;
|
|
3885
4036
|
for (const entry of addedEntries) order[index++] = entry.key;
|
|
@@ -3896,7 +4047,7 @@ const createCollectionOutput = (binding) => {
|
|
|
3896
4047
|
reset = nextReset;
|
|
3897
4048
|
return change !== void 0;
|
|
3898
4049
|
};
|
|
3899
|
-
const context = (active, cause) => Object.freeze({
|
|
4050
|
+
const context = (active, cause, consumer) => Object.freeze({
|
|
3900
4051
|
kind: "collection",
|
|
3901
4052
|
read: {
|
|
3902
4053
|
get: (key) => {
|
|
@@ -3912,9 +4063,9 @@ const createCollectionOutput = (binding) => {
|
|
|
3912
4063
|
return nextIds;
|
|
3913
4064
|
}
|
|
3914
4065
|
},
|
|
3915
|
-
change,
|
|
4066
|
+
change: consumer ? consumers.get(consumer)?.current() : change,
|
|
3916
4067
|
revision,
|
|
3917
|
-
reset,
|
|
4068
|
+
reset: consumer ? consumers.get(consumer)?.reset() ?? false : reset,
|
|
3918
4069
|
cause
|
|
3919
4070
|
});
|
|
3920
4071
|
return {
|
|
@@ -3924,35 +4075,59 @@ const createCollectionOutput = (binding) => {
|
|
|
3924
4075
|
},
|
|
3925
4076
|
begin,
|
|
3926
4077
|
seal,
|
|
3927
|
-
context: (active) => context(active, binding.cause()),
|
|
3928
|
-
|
|
4078
|
+
context: (active, consumer) => context(active, binding.cause(), consumer),
|
|
4079
|
+
acknowledge: (consumer) => consumers.get(consumer)?.clear(),
|
|
4080
|
+
pending: (consumer) => consumers.get(consumer)?.current() !== void 0,
|
|
4081
|
+
baseline: () => baseline ?? currentState.read(binding.check),
|
|
4082
|
+
current: () => currentState.read(binding.check),
|
|
3929
4083
|
revision: () => revision,
|
|
3930
|
-
reset: () => reset,
|
|
4084
|
+
reset: (consumer) => consumer ? consumers.get(consumer)?.reset() ?? false : reset,
|
|
3931
4085
|
publish: () => {
|
|
3932
|
-
if (change)
|
|
3933
|
-
|
|
4086
|
+
if (change) {
|
|
4087
|
+
if (initialized) {
|
|
4088
|
+
baseline ??= currentState.read(() => void 0);
|
|
4089
|
+
notifications.add(change, currentState.ids(), nextIds);
|
|
4090
|
+
for (const pending of consumers.values()) pending.add(change, currentState.ids(), nextIds);
|
|
4091
|
+
revision++;
|
|
4092
|
+
}
|
|
4093
|
+
currentState.install(staged, nextIds, change.kind === "reset");
|
|
4094
|
+
}
|
|
3934
4095
|
initialized = true;
|
|
3935
4096
|
},
|
|
3936
|
-
emit: (call) => {
|
|
3937
|
-
|
|
4097
|
+
emit: (call, force) => {
|
|
4098
|
+
const net = notifications.current();
|
|
4099
|
+
for (const listener of [...observers]) call(listener, net);
|
|
4100
|
+
if (net || force) for (const listener of [...listeners]) call(listener, net);
|
|
4101
|
+
},
|
|
4102
|
+
finish: () => {
|
|
4103
|
+
baseline = void 0;
|
|
4104
|
+
notifications.clear();
|
|
4105
|
+
},
|
|
4106
|
+
observe: (listener) => {
|
|
4107
|
+
binding.check();
|
|
4108
|
+
observers.add(listener);
|
|
4109
|
+
return () => observers.delete(listener);
|
|
3938
4110
|
},
|
|
3939
4111
|
hasConsumers: () => consumers.size > 0,
|
|
3940
|
-
forEachConsumer: (run) => consumers.forEach(run),
|
|
3941
|
-
attachConsumer: (consumer) => consumers.
|
|
4112
|
+
forEachConsumer: (run) => consumers.forEach((_pending, consumer) => run(consumer)),
|
|
4113
|
+
attachConsumer: (consumer) => consumers.set(consumer, createCollectionChanges()),
|
|
3942
4114
|
detachConsumer: (consumer) => consumers.delete(consumer),
|
|
3943
4115
|
clear: () => {
|
|
3944
4116
|
staged.clear();
|
|
3945
|
-
nextIds =
|
|
4117
|
+
nextIds = currentState.ids();
|
|
3946
4118
|
change = void 0;
|
|
3947
4119
|
reset = false;
|
|
3948
4120
|
explicitOrder = void 0;
|
|
3949
4121
|
cleared = false;
|
|
3950
4122
|
},
|
|
3951
4123
|
release: () => {
|
|
3952
|
-
|
|
4124
|
+
currentState.release();
|
|
3953
4125
|
staged.clear();
|
|
3954
4126
|
listeners.clear();
|
|
3955
4127
|
consumers.clear();
|
|
4128
|
+
observers.clear();
|
|
4129
|
+
baseline = void 0;
|
|
4130
|
+
notifications.clear();
|
|
3956
4131
|
nextIds = Object.freeze([]);
|
|
3957
4132
|
change = void 0;
|
|
3958
4133
|
initialized = false;
|
|
@@ -3971,7 +4146,7 @@ const createCollectionOutput = (binding) => {
|
|
|
3971
4146
|
//#endregion
|
|
3972
4147
|
//#region core/src/projection/output/value.ts
|
|
3973
4148
|
const unset = Symbol("projection-value-unset");
|
|
3974
|
-
/** Owns staged and
|
|
4149
|
+
/** Owns staged and current state for one scalar Projection output. */
|
|
3975
4150
|
const createValueOutput = (binding) => {
|
|
3976
4151
|
let value;
|
|
3977
4152
|
let staged = unset;
|
|
@@ -3981,7 +4156,10 @@ const createValueOutput = (binding) => {
|
|
|
3981
4156
|
let changed = false;
|
|
3982
4157
|
let reset = false;
|
|
3983
4158
|
const listeners = /* @__PURE__ */ new Set();
|
|
3984
|
-
const consumers = /* @__PURE__ */ new
|
|
4159
|
+
const consumers = /* @__PURE__ */ new Map();
|
|
4160
|
+
const observers = /* @__PURE__ */ new Set();
|
|
4161
|
+
let baseline = unset;
|
|
4162
|
+
let resetRevision = -1;
|
|
3985
4163
|
const begin = (active, initialize) => {
|
|
3986
4164
|
staged = unset;
|
|
3987
4165
|
next = value;
|
|
@@ -4008,7 +4186,12 @@ const createValueOutput = (binding) => {
|
|
|
4008
4186
|
next = value;
|
|
4009
4187
|
return false;
|
|
4010
4188
|
}
|
|
4011
|
-
|
|
4189
|
+
let candidate = staged;
|
|
4190
|
+
if (baseline !== unset && !Object.is(value, baseline)) {
|
|
4191
|
+
const equivalent = isEqual(baseline, candidate);
|
|
4192
|
+
assertSynchronous(equivalent);
|
|
4193
|
+
if (equivalent) candidate = baseline;
|
|
4194
|
+
}
|
|
4012
4195
|
const equal = initialized ? isEqual(value, candidate) : false;
|
|
4013
4196
|
assertSynchronous(equal);
|
|
4014
4197
|
changed = !equal;
|
|
@@ -4023,13 +4206,13 @@ const createValueOutput = (binding) => {
|
|
|
4023
4206
|
},
|
|
4024
4207
|
begin,
|
|
4025
4208
|
seal,
|
|
4026
|
-
context: (active) => {
|
|
4209
|
+
context: (active, consumer) => {
|
|
4027
4210
|
assertScope(active);
|
|
4028
4211
|
return Object.freeze({
|
|
4029
4212
|
kind: "value",
|
|
4030
4213
|
value: next,
|
|
4031
4214
|
revision,
|
|
4032
|
-
reset,
|
|
4215
|
+
reset: consumer ? (consumers.get(consumer)?.revision ?? -1) < resetRevision : reset,
|
|
4033
4216
|
cause: binding.cause()
|
|
4034
4217
|
});
|
|
4035
4218
|
},
|
|
@@ -4038,19 +4221,48 @@ const createValueOutput = (binding) => {
|
|
|
4038
4221
|
return value;
|
|
4039
4222
|
},
|
|
4040
4223
|
revision: () => revision,
|
|
4041
|
-
reset: () => reset,
|
|
4042
|
-
|
|
4043
|
-
if (
|
|
4224
|
+
reset: (consumer) => consumer ? (consumers.get(consumer)?.revision ?? -1) < resetRevision : reset,
|
|
4225
|
+
acknowledge: (consumer) => {
|
|
4226
|
+
if (consumers.has(consumer)) consumers.set(consumer, {
|
|
4227
|
+
revision,
|
|
4228
|
+
value
|
|
4229
|
+
});
|
|
4230
|
+
},
|
|
4231
|
+
pending: (consumer) => {
|
|
4232
|
+
const previous = consumers.get(consumer);
|
|
4233
|
+
return !previous || previous.revision < resetRevision || !Object.is(previous.value, value);
|
|
4234
|
+
},
|
|
4235
|
+
baseline: () => baseline === unset ? value : baseline,
|
|
4236
|
+
publish: (forceReset) => {
|
|
4237
|
+
if (initialized && (changed || forceReset)) {
|
|
4238
|
+
if (baseline === unset) baseline = value;
|
|
4239
|
+
revision++;
|
|
4240
|
+
if (reset) resetRevision = revision;
|
|
4241
|
+
}
|
|
4044
4242
|
if (changed) value = next;
|
|
4045
4243
|
initialized = true;
|
|
4046
4244
|
},
|
|
4047
|
-
emit: (call) => {
|
|
4048
|
-
|
|
4049
|
-
|
|
4245
|
+
emit: (call, force) => {
|
|
4246
|
+
for (const listener of [...observers]) call(listener, void 0);
|
|
4247
|
+
if (force || baseline !== unset && !Object.is(baseline, value)) {
|
|
4248
|
+
profile.materialized.notification();
|
|
4249
|
+
for (const listener of [...listeners]) call(listener, void 0);
|
|
4250
|
+
}
|
|
4251
|
+
},
|
|
4252
|
+
finish: () => {
|
|
4253
|
+
baseline = unset;
|
|
4254
|
+
},
|
|
4255
|
+
observe: (listener) => {
|
|
4256
|
+
binding.check();
|
|
4257
|
+
observers.add(listener);
|
|
4258
|
+
return () => observers.delete(listener);
|
|
4050
4259
|
},
|
|
4051
4260
|
hasConsumers: () => consumers.size > 0,
|
|
4052
|
-
forEachConsumer: (run) => consumers.forEach(run),
|
|
4053
|
-
attachConsumer: (consumer) => consumers.
|
|
4261
|
+
forEachConsumer: (run) => consumers.forEach((_revision, consumer) => run(consumer)),
|
|
4262
|
+
attachConsumer: (consumer) => consumers.set(consumer, {
|
|
4263
|
+
revision,
|
|
4264
|
+
value
|
|
4265
|
+
}),
|
|
4054
4266
|
detachConsumer: (consumer) => consumers.delete(consumer),
|
|
4055
4267
|
clear: () => {
|
|
4056
4268
|
staged = unset;
|
|
@@ -4061,6 +4273,8 @@ const createValueOutput = (binding) => {
|
|
|
4061
4273
|
release: () => {
|
|
4062
4274
|
listeners.clear();
|
|
4063
4275
|
consumers.clear();
|
|
4276
|
+
observers.clear();
|
|
4277
|
+
baseline = unset;
|
|
4064
4278
|
staged = unset;
|
|
4065
4279
|
value = next = void 0;
|
|
4066
4280
|
initialized = false;
|
|
@@ -4131,7 +4345,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
4131
4345
|
let active = true;
|
|
4132
4346
|
try {
|
|
4133
4347
|
const scopeActive = () => active;
|
|
4134
|
-
const sources = Object.freeze(dependencies.map((output) => output.context(scopeActive)));
|
|
4348
|
+
const sources = Object.freeze(dependencies.map((output) => output.context(scopeActive, processor)));
|
|
4135
4349
|
const outputs = begin(scopeActive, reset);
|
|
4136
4350
|
if (recreate && instance) {
|
|
4137
4351
|
instance.release?.();
|
|
@@ -4145,16 +4359,17 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
4145
4359
|
sources,
|
|
4146
4360
|
outputs
|
|
4147
4361
|
}));
|
|
4148
|
-
|
|
4362
|
+
const changed = seal(reset);
|
|
4363
|
+
dependencies.forEach((output) => output.acknowledge(processor));
|
|
4364
|
+
return changed;
|
|
4149
4365
|
} finally {
|
|
4150
4366
|
active = false;
|
|
4151
4367
|
}
|
|
4152
4368
|
};
|
|
4153
|
-
const
|
|
4369
|
+
const id = scheduler.nextId();
|
|
4154
4370
|
processor = {
|
|
4155
4371
|
kind: "processor",
|
|
4156
|
-
|
|
4157
|
-
name: definition.name ? `${definition.name} (${order})` : `projection-${order}`,
|
|
4372
|
+
name: definition.name ? `${definition.name} (${id})` : `projection-${id}`,
|
|
4158
4373
|
dependencies: Object.freeze([...dependencies]),
|
|
4159
4374
|
outputs: Object.freeze(bound.map((entry) => entry.state)),
|
|
4160
4375
|
fault: void 0,
|
|
@@ -4281,7 +4496,9 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
|
|
|
4281
4496
|
let initialized = false;
|
|
4282
4497
|
let value;
|
|
4283
4498
|
let selectedRevision = 0;
|
|
4499
|
+
let notified;
|
|
4284
4500
|
let sourceRevision = -1;
|
|
4501
|
+
let notificationRevision = -1;
|
|
4285
4502
|
let selection = Object.freeze({
|
|
4286
4503
|
keys: /* @__PURE__ */ new Set(),
|
|
4287
4504
|
all: true,
|
|
@@ -4317,8 +4534,9 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
|
|
|
4317
4534
|
});
|
|
4318
4535
|
const changed = !initialized || !equality(value, next);
|
|
4319
4536
|
if (changed) {
|
|
4537
|
+
const candidate = unsubscribeSource && equality(notified, next) ? notified : next;
|
|
4320
4538
|
if (initialized) selectedRevision++;
|
|
4321
|
-
value =
|
|
4539
|
+
value = candidate;
|
|
4322
4540
|
}
|
|
4323
4541
|
selection = nextSelection;
|
|
4324
4542
|
initialized = true;
|
|
@@ -4328,31 +4546,44 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
|
|
|
4328
4546
|
};
|
|
4329
4547
|
const ensureCurrent = () => {
|
|
4330
4548
|
const revision = source.revision();
|
|
4331
|
-
if (!initialized
|
|
4332
|
-
|
|
4333
|
-
};
|
|
4334
|
-
const onProjectionChange = (incoming) => {
|
|
4335
|
-
let change = incoming;
|
|
4336
|
-
if (!change && previousCollection) {
|
|
4549
|
+
if (!initialized) evaluate();
|
|
4550
|
+
else if (sourceRevision !== revision) {
|
|
4337
4551
|
const current = source.current();
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
if (!
|
|
4342
|
-
|
|
4343
|
-
|
|
4552
|
+
let affected = true;
|
|
4553
|
+
if (previousCollection && isMapLike(current) && !selection.all) {
|
|
4554
|
+
affected = selection.structure && !iterableMatchesArray(current.keys(), [...previousCollection.keys()]);
|
|
4555
|
+
if (!affected) {
|
|
4556
|
+
for (const key of selection.keys) if (previousCollection.has(key) !== current.has(key) || !Object.is(previousCollection.get(key), current.get(key))) {
|
|
4557
|
+
affected = true;
|
|
4558
|
+
break;
|
|
4559
|
+
}
|
|
4344
4560
|
}
|
|
4345
|
-
}
|
|
4561
|
+
}
|
|
4562
|
+
if (affected) evaluate();
|
|
4563
|
+
else {
|
|
4564
|
+
sourceRevision = revision;
|
|
4565
|
+
updateCollectionSnapshot(current);
|
|
4566
|
+
}
|
|
4346
4567
|
}
|
|
4347
|
-
|
|
4568
|
+
return value;
|
|
4569
|
+
};
|
|
4570
|
+
const onProjectionChange = (incoming) => {
|
|
4571
|
+
if (sourceRevision === notificationRevision && sourceRevision !== source.revision() && incoming && !selectionAffects(selection, incoming)) {
|
|
4348
4572
|
sourceRevision = source.revision();
|
|
4349
4573
|
updateCollectionSnapshot();
|
|
4350
|
-
|
|
4574
|
+
} else ensureCurrent();
|
|
4575
|
+
notificationRevision = sourceRevision;
|
|
4576
|
+
if (!equality(notified, value)) {
|
|
4577
|
+
notified = value;
|
|
4578
|
+
notifyProjectionListeners(listeners, "Projection selector listeners failed.");
|
|
4351
4579
|
}
|
|
4352
|
-
if (evaluate()) notifyProjectionListeners(listeners, "Projection selector listeners failed.");
|
|
4353
4580
|
};
|
|
4354
4581
|
const installSource = () => {
|
|
4355
|
-
if (!unsubscribeSource)
|
|
4582
|
+
if (!unsubscribeSource) {
|
|
4583
|
+
notified = value;
|
|
4584
|
+
notificationRevision = sourceRevision;
|
|
4585
|
+
unsubscribeSource = source.observe(onProjectionChange);
|
|
4586
|
+
}
|
|
4356
4587
|
};
|
|
4357
4588
|
return Object.freeze({
|
|
4358
4589
|
current: ensureCurrent,
|
|
@@ -4734,13 +4965,12 @@ const createValueBoundary = (scheduler, name, initial, equality, prepare, clearS
|
|
|
4734
4965
|
active = false;
|
|
4735
4966
|
}
|
|
4736
4967
|
},
|
|
4737
|
-
publish: state.publish,
|
|
4968
|
+
publish: () => state.publish(pendingReset),
|
|
4738
4969
|
clear: () => {
|
|
4739
4970
|
const failed = boundary.fault !== void 0;
|
|
4740
4971
|
state.clear();
|
|
4741
4972
|
pendingReset = false;
|
|
4742
4973
|
pendingCause = void 0;
|
|
4743
|
-
contextCause = void 0;
|
|
4744
4974
|
recovering = false;
|
|
4745
4975
|
clearSource(failed);
|
|
4746
4976
|
},
|
|
@@ -4796,6 +5026,7 @@ const createValueBoundary = (scheduler, name, initial, equality, prepare, clearS
|
|
|
4796
5026
|
return {
|
|
4797
5027
|
producer: boundary,
|
|
4798
5028
|
output,
|
|
5029
|
+
baseline: state.baseline,
|
|
4799
5030
|
mark,
|
|
4800
5031
|
fail,
|
|
4801
5032
|
detach: (next) => {
|
|
@@ -4815,7 +5046,7 @@ const stageCollectionRead = (state, read, active, options) => {
|
|
|
4815
5046
|
}
|
|
4816
5047
|
evaluation.output.order(ids);
|
|
4817
5048
|
} else {
|
|
4818
|
-
const previous =
|
|
5049
|
+
const previous = evaluation.previous;
|
|
4819
5050
|
let currentIds;
|
|
4820
5051
|
const ids = () => currentIds ??= read.ids();
|
|
4821
5052
|
if (options.candidates) for (const key of options.candidates) if (read.has(key)) {
|
|
@@ -4896,7 +5127,6 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
|
|
|
4896
5127
|
state.clear();
|
|
4897
5128
|
pendingReset = false;
|
|
4898
5129
|
pendingCause = void 0;
|
|
4899
|
-
contextCause = void 0;
|
|
4900
5130
|
recovering = false;
|
|
4901
5131
|
fullScan = false;
|
|
4902
5132
|
orderMayChange = false;
|
|
@@ -4949,7 +5179,11 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
|
|
|
4949
5179
|
fullScan ||= metadata.fullScan ?? false;
|
|
4950
5180
|
orderMayChange ||= metadata.orderMayChange ?? false;
|
|
4951
5181
|
if (metadata.candidates) for (const key of metadata.candidates) candidates.add(key);
|
|
4952
|
-
if (boundary.fault)
|
|
5182
|
+
if (boundary.fault) {
|
|
5183
|
+
recovering = true;
|
|
5184
|
+
fullScan = true;
|
|
5185
|
+
orderMayChange = true;
|
|
5186
|
+
}
|
|
4953
5187
|
scheduler.capture(boundary);
|
|
4954
5188
|
};
|
|
4955
5189
|
const fail = (cause) => {
|
|
@@ -4960,6 +5194,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
|
|
|
4960
5194
|
return {
|
|
4961
5195
|
producer: boundary,
|
|
4962
5196
|
output,
|
|
5197
|
+
baseline: state.baseline,
|
|
4963
5198
|
mark,
|
|
4964
5199
|
fail,
|
|
4965
5200
|
detach: (next) => {
|
|
@@ -5509,13 +5744,12 @@ const materializeInputSource = (scheduler, definition) => {
|
|
|
5509
5744
|
...boundary,
|
|
5510
5745
|
input: {
|
|
5511
5746
|
kind: "value",
|
|
5512
|
-
read: () => latest,
|
|
5513
5747
|
set(value) {
|
|
5514
5748
|
const next = scheduler.acceptInput(() => {
|
|
5515
5749
|
if (equal(latest, value)) return latest;
|
|
5516
5750
|
if (source.equality === Object.is) return value;
|
|
5517
|
-
const
|
|
5518
|
-
return !Object.is(latest,
|
|
5751
|
+
const baseline = boundary.baseline();
|
|
5752
|
+
return !Object.is(latest, baseline) && equal(baseline, value) ? baseline : value;
|
|
5519
5753
|
});
|
|
5520
5754
|
if (Object.is(latest, next)) return;
|
|
5521
5755
|
latest = next;
|
|
@@ -5526,7 +5760,6 @@ const materializeInputSource = (scheduler, definition) => {
|
|
|
5526
5760
|
};
|
|
5527
5761
|
}
|
|
5528
5762
|
const values = createCollectionState(source.initial);
|
|
5529
|
-
let view;
|
|
5530
5763
|
const latest = {
|
|
5531
5764
|
get: values.get,
|
|
5532
5765
|
has: values.has,
|
|
@@ -5535,17 +5768,11 @@ const materializeInputSource = (scheduler, definition) => {
|
|
|
5535
5768
|
const boundary = createCollectionBoundary(scheduler, "projection collection input", () => latest, Object.is, () => void 0);
|
|
5536
5769
|
boundary.detach(() => {
|
|
5537
5770
|
values.release();
|
|
5538
|
-
view = void 0;
|
|
5539
5771
|
});
|
|
5540
|
-
const check = () => {
|
|
5541
|
-
scheduler.assertActive();
|
|
5542
|
-
if (boundary.producer.disposed) throw new ProjectionDisposedError();
|
|
5543
|
-
};
|
|
5544
5772
|
return {
|
|
5545
5773
|
...boundary,
|
|
5546
5774
|
input: {
|
|
5547
5775
|
kind: "collection",
|
|
5548
|
-
read: () => view ??= collectionView(values.read(check)),
|
|
5549
5776
|
update(run) {
|
|
5550
5777
|
const accepted = scheduler.acceptInput(() => {
|
|
5551
5778
|
const staged = /* @__PURE__ */ new Map();
|
|
@@ -5592,7 +5819,7 @@ const materializeInputSource = (scheduler, definition) => {
|
|
|
5592
5819
|
} finally {
|
|
5593
5820
|
active = false;
|
|
5594
5821
|
}
|
|
5595
|
-
const
|
|
5822
|
+
const baseline = source.equality === Object.is ? void 0 : boundary.baseline();
|
|
5596
5823
|
for (const [key, entry] of staged) {
|
|
5597
5824
|
const existed = values.has(key);
|
|
5598
5825
|
if (!entry.present) {
|
|
@@ -5602,8 +5829,8 @@ const materializeInputSource = (scheduler, definition) => {
|
|
|
5602
5829
|
const previous = values.get(key);
|
|
5603
5830
|
let next = entry.value;
|
|
5604
5831
|
if (existed && equal(previous, next)) next = previous;
|
|
5605
|
-
else if (
|
|
5606
|
-
const before =
|
|
5832
|
+
else if (baseline?.has(key)) {
|
|
5833
|
+
const before = baseline.get(key);
|
|
5607
5834
|
if ((!existed || !Object.is(previous, before)) && equal(before, next)) next = before;
|
|
5608
5835
|
}
|
|
5609
5836
|
if (existed && Object.is(previous, next)) staged.delete(key);
|
|
@@ -5628,7 +5855,6 @@ const materializeInputSource = (scheduler, definition) => {
|
|
|
5628
5855
|
});
|
|
5629
5856
|
if (!accepted.staged.size && !accepted.structural) return;
|
|
5630
5857
|
values.install(accepted.staged, accepted.order, false);
|
|
5631
|
-
view = void 0;
|
|
5632
5858
|
boundary.mark({
|
|
5633
5859
|
candidates: accepted.staged.keys(),
|
|
5634
5860
|
orderMayChange: accepted.structural
|
|
@@ -5681,6 +5907,11 @@ const createProjectionItems = (source, checkOwner) => {
|
|
|
5681
5907
|
const waiting = /* @__PURE__ */ new Map();
|
|
5682
5908
|
let keysValue = Object.freeze([...source.current().keys()]);
|
|
5683
5909
|
let keysRevision = 0;
|
|
5910
|
+
let notifiedKeys = keysValue;
|
|
5911
|
+
let settledRevision = source.revision();
|
|
5912
|
+
let keysSourceRevision = settledRevision;
|
|
5913
|
+
const pendingKeys = /* @__PURE__ */ new Set();
|
|
5914
|
+
const changedStates = /* @__PURE__ */ new Set();
|
|
5684
5915
|
const keyListeners = /* @__PURE__ */ new Set();
|
|
5685
5916
|
const check = () => {
|
|
5686
5917
|
if (disposed) throw new ProjectionDisposedError();
|
|
@@ -5716,7 +5947,7 @@ const createProjectionItems = (source, checkOwner) => {
|
|
|
5716
5947
|
}
|
|
5717
5948
|
state.value = value;
|
|
5718
5949
|
state.revision++;
|
|
5719
|
-
|
|
5950
|
+
changedStates.add(state);
|
|
5720
5951
|
};
|
|
5721
5952
|
const attachActive = (state, value) => {
|
|
5722
5953
|
if (state.ended) return;
|
|
@@ -5759,10 +5990,12 @@ const createProjectionItems = (source, checkOwner) => {
|
|
|
5759
5990
|
if (extra) for (const state of extra) publishState(state, value);
|
|
5760
5991
|
};
|
|
5761
5992
|
const updateKeys = (current) => {
|
|
5993
|
+
const revision = source.revision();
|
|
5994
|
+
if (keysSourceRevision === revision) return;
|
|
5995
|
+
keysSourceRevision = revision;
|
|
5762
5996
|
if (iterableMatchesArray(current.keys(), keysValue)) return;
|
|
5763
|
-
keysValue = Object.freeze([...current.keys()]);
|
|
5997
|
+
keysValue = iterableMatchesArray(current.keys(), notifiedKeys) ? notifiedKeys : Object.freeze([...current.keys()]);
|
|
5764
5998
|
keysRevision++;
|
|
5765
|
-
notify(keyListeners);
|
|
5766
5999
|
};
|
|
5767
6000
|
const createItem = (key, initial) => {
|
|
5768
6001
|
const state = {
|
|
@@ -5771,12 +6004,15 @@ const createProjectionItems = (source, checkOwner) => {
|
|
|
5771
6004
|
tracked: initial !== void 0,
|
|
5772
6005
|
ended: false,
|
|
5773
6006
|
value: initial?.value,
|
|
6007
|
+
notified: initial?.value,
|
|
5774
6008
|
revision: 0
|
|
5775
6009
|
};
|
|
5776
6010
|
const ensureObserved = () => {
|
|
5777
|
-
if (state.ended
|
|
6011
|
+
if (state.ended) return;
|
|
5778
6012
|
const current = source.current();
|
|
5779
|
-
if (
|
|
6013
|
+
if (source.revision() !== settledRevision) pendingKeys.add(key);
|
|
6014
|
+
if (!state.tracked && current.has(key)) attachActive(state, current.get(key));
|
|
6015
|
+
else if (state.tracked) publishState(state, current.get(key));
|
|
5780
6016
|
};
|
|
5781
6017
|
state.readable = Object.freeze({
|
|
5782
6018
|
current: () => {
|
|
@@ -5811,25 +6047,45 @@ const createProjectionItems = (source, checkOwner) => {
|
|
|
5811
6047
|
const onChange = (change) => {
|
|
5812
6048
|
check();
|
|
5813
6049
|
const current = source.current();
|
|
5814
|
-
if (
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
6050
|
+
if (change?.kind === "reset") reconcile(current);
|
|
6051
|
+
else {
|
|
6052
|
+
if (change) {
|
|
6053
|
+
for (const entry of change.removed) terminateKey(entry.key);
|
|
6054
|
+
for (const entry of change.added) activateWaiting(entry.key, entry.after);
|
|
6055
|
+
for (const entry of change.updated) updateKey(entry.key, entry.after);
|
|
6056
|
+
if (change.added.length || change.removed.length || change.order) updateKeys(current);
|
|
6057
|
+
}
|
|
6058
|
+
for (const key of pendingKeys) if (!current.has(key)) terminateKey(key);
|
|
6059
|
+
else {
|
|
6060
|
+
updateKey(key, current.get(key));
|
|
6061
|
+
activateWaiting(key, current.get(key));
|
|
6062
|
+
}
|
|
6063
|
+
}
|
|
6064
|
+
pendingKeys.clear();
|
|
6065
|
+
settledRevision = source.revision();
|
|
6066
|
+
for (const state of changedStates) if (!Object.is(state.notified, state.value)) {
|
|
6067
|
+
state.notified = state.value;
|
|
6068
|
+
notify(state.listeners);
|
|
6069
|
+
}
|
|
6070
|
+
changedStates.clear();
|
|
6071
|
+
if (keysValue !== notifiedKeys) updateKeys(current);
|
|
6072
|
+
else keysSourceRevision = settledRevision;
|
|
6073
|
+
if (keysValue !== notifiedKeys) {
|
|
6074
|
+
notifiedKeys = keysValue;
|
|
6075
|
+
notify(keyListeners);
|
|
5818
6076
|
}
|
|
5819
|
-
for (const entry of change.removed) terminateKey(entry.key);
|
|
5820
|
-
for (const entry of change.added) activateWaiting(entry.key, entry.after);
|
|
5821
|
-
for (const entry of change.updated) updateKey(entry.key, entry.after);
|
|
5822
|
-
if (change.added.length || change.removed.length || change.order) updateKeys(current);
|
|
5823
6077
|
flushNotifications();
|
|
5824
6078
|
};
|
|
5825
|
-
const stop = source.
|
|
6079
|
+
const stop = source.observe(onChange);
|
|
5826
6080
|
const keys = Object.freeze({
|
|
5827
6081
|
current: () => {
|
|
5828
6082
|
check();
|
|
6083
|
+
updateKeys(source.current());
|
|
5829
6084
|
return keysValue;
|
|
5830
6085
|
},
|
|
5831
6086
|
revision: () => {
|
|
5832
6087
|
check();
|
|
6088
|
+
updateKeys(source.current());
|
|
5833
6089
|
return keysRevision;
|
|
5834
6090
|
},
|
|
5835
6091
|
subscribe: (listener) => {
|
|
@@ -5843,11 +6099,12 @@ const createProjectionItems = (source, checkOwner) => {
|
|
|
5843
6099
|
keys,
|
|
5844
6100
|
get: (key) => {
|
|
5845
6101
|
check();
|
|
6102
|
+
const current = source.current();
|
|
6103
|
+
if (source.revision() !== settledRevision) pendingKeys.add(key);
|
|
5846
6104
|
const canonical = active.get(key);
|
|
5847
6105
|
if (canonical) return canonical.readable;
|
|
5848
6106
|
const pending = waiting.get(key)?.values().next().value;
|
|
5849
6107
|
if (pending) return pending.readable;
|
|
5850
|
-
const current = source.current();
|
|
5851
6108
|
if (current.has(key)) {
|
|
5852
6109
|
const state = createItem(key, {
|
|
5853
6110
|
present: true,
|
|
@@ -5873,6 +6130,8 @@ const createProjectionItems = (source, checkOwner) => {
|
|
|
5873
6130
|
followers.clear();
|
|
5874
6131
|
waiting.clear();
|
|
5875
6132
|
notifications.length = 0;
|
|
6133
|
+
pendingKeys.clear();
|
|
6134
|
+
changedStates.clear();
|
|
5876
6135
|
}
|
|
5877
6136
|
};
|
|
5878
6137
|
};
|
|
@@ -5933,6 +6192,7 @@ const createProjectionRuntime = (options) => {
|
|
|
5933
6192
|
} else {
|
|
5934
6193
|
if (definition.owner === void 0 && definition.dependencies.some((dependency) => producerOf(dependency).owner !== void 0)) throw new TypeError("A root projection cannot depend on a scoped projection.");
|
|
5935
6194
|
const dependencies = definition.dependencies.map((dependency) => resolveOutput(dependency, requester));
|
|
6195
|
+
dependencies.forEach((output) => scheduler.ensureCurrent(output));
|
|
5936
6196
|
const processor = createProcessor(scheduler, definition, Object.freeze(dependencies));
|
|
5937
6197
|
result = Object.freeze({
|
|
5938
6198
|
record: processor,
|
|
@@ -5956,6 +6216,7 @@ const createProjectionRuntime = (options) => {
|
|
|
5956
6216
|
if (output.owner.fault) throw output.owner.fault;
|
|
5957
6217
|
};
|
|
5958
6218
|
const publicCurrent = (output) => {
|
|
6219
|
+
scheduler.ensureCurrent(output);
|
|
5959
6220
|
assertOutput(output);
|
|
5960
6221
|
const current = output.current();
|
|
5961
6222
|
const revision = output.revision();
|
|
@@ -5987,10 +6248,12 @@ const createProjectionRuntime = (options) => {
|
|
|
5987
6248
|
kind: output.kind,
|
|
5988
6249
|
current: () => readProjection(projection, requester),
|
|
5989
6250
|
revision: () => {
|
|
6251
|
+
scheduler.ensureCurrent(output);
|
|
5990
6252
|
assertOutput(output);
|
|
5991
6253
|
return output.revision();
|
|
5992
6254
|
},
|
|
5993
|
-
subscribe: output.subscribe
|
|
6255
|
+
subscribe: output.subscribe,
|
|
6256
|
+
observe: output.observe
|
|
5994
6257
|
});
|
|
5995
6258
|
};
|
|
5996
6259
|
const projectionItems = (projection, requester) => {
|
|
@@ -6001,6 +6264,7 @@ const createProjectionRuntime = (options) => {
|
|
|
6001
6264
|
if (existing) return existing.items;
|
|
6002
6265
|
const controller = createProjectionItems(readableSource(projection, requester), () => {
|
|
6003
6266
|
if (requester && !requester.active) throw new ProjectionDisposedError();
|
|
6267
|
+
scheduler.ensureCurrent(output);
|
|
6004
6268
|
assertOutput(output);
|
|
6005
6269
|
});
|
|
6006
6270
|
registry.set(output, controller);
|
|
@@ -6035,18 +6299,7 @@ const createProjectionRuntime = (options) => {
|
|
|
6035
6299
|
function update(target, valueOrRun) {
|
|
6036
6300
|
updateInput(target, valueOrRun);
|
|
6037
6301
|
}
|
|
6038
|
-
const batch = (run, options
|
|
6039
|
-
let active = true;
|
|
6040
|
-
const read = (target) => {
|
|
6041
|
-
assertScope(() => active);
|
|
6042
|
-
return inputAccess(target, requester).read();
|
|
6043
|
-
};
|
|
6044
|
-
try {
|
|
6045
|
-
return run(read);
|
|
6046
|
-
} finally {
|
|
6047
|
-
active = false;
|
|
6048
|
-
}
|
|
6049
|
-
}, options);
|
|
6302
|
+
const batch = (run, options) => scheduler.batch(run, options);
|
|
6050
6303
|
const disposeScope = (state) => {
|
|
6051
6304
|
if (!state.active) return;
|
|
6052
6305
|
scheduler.assertIdle();
|
|
@@ -6138,7 +6391,7 @@ const createProjectionRuntime = (options) => {
|
|
|
6138
6391
|
}
|
|
6139
6392
|
function scopeBatch(run, options) {
|
|
6140
6393
|
assertActive();
|
|
6141
|
-
return batch(run, options
|
|
6394
|
+
return batch(run, options);
|
|
6142
6395
|
}
|
|
6143
6396
|
return Object.freeze({
|
|
6144
6397
|
own,
|