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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as isProjection, C as compileProjectionDependencies, D as snapshotCollectionView, E as mapRead, M as producerOf, N as projectionRef, O as defineProcessor, P as profile, S as collectKeyedInvalidation, T as collectionView, _ as assertScope, a as collectionChangeTouchesKey, b as ProjectionDisposedError, c as diffCollection, d as createKeyedDependencyRuntime, f as outputKind, g as snapshotArray, h as sameArray, i as collectionChangeIntersects, j as ownProjections, k as defineSource, l as assertKeyedProjection, m as iterableMatchesArray, n as keyedAbsent, o as collectionHasStructuralChange, p as createKeyRelation, s as createCollectionChange, t as createKeyedTransform, u as compileKeyedDependencies, v as assertSynchronous, w as snapshotDependencyValue, x as ProjectionError, y as createScheduler } from "./transform-A9Lx5R2l.js";
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
- if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== "string") throw new TypeError(`${name} entries must be [string key, value] tuples.`);
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
- const seen = /* @__PURE__ */ new Set();
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 values = Object.create(null);
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 = values.size ? PersistentKeyedIndex.from(values) : PersistentKeyedIndex.empty();
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 = PersistentKeyedIndex.empty();
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 published collection state. */
3895
+ /** Owns staged keyed intent, exact transitions and current collection state. */
3761
3896
  const createCollectionOutput = (binding) => {
3762
- const published = createCollectionState();
3897
+ const currentState = createCollectionState();
3763
3898
  const staged = /* @__PURE__ */ new Map();
3764
- let nextIds = published.ids();
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 Set();
3773
- const hasNext = (key) => staged.has(key) ? staged.get(key).present : !cleared && published.has(key);
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 : published.get(key);
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 published.ids()) if (hasNext(key)) result.push(key);
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 || !published.has(key))) result.push(key);
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 = published.ids();
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) : published.get(key);
3934
+ return next ? getNext(key) : currentState.get(key);
3797
3935
  },
3798
3936
  has: (key) => {
3799
3937
  assertScope(active);
3800
- return next ? hasNext(key) : published.has(key);
3938
+ return next ? hasNext(key) : currentState.has(key);
3801
3939
  },
3802
3940
  ids: () => {
3803
3941
  assertScope(active);
3804
- if (!next) return published.ids();
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 published.keys()) if (!staged.has(key)) staged.set(key, { present: false });
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, entry] of staged) {
3841
- const existed = published.has(key);
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 = published.get(key);
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: published.get(key)
4018
+ before: currentState.get(key)
3868
4019
  });
3869
4020
  else staged.delete(key);
3870
4021
  }
3871
- const ids = published.ids();
4022
+ const ids = currentState.ids();
3872
4023
  if (explicitOrder) {
3873
- const expectedLength = published.size() + addedEntries.length - removedEntries.length;
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(published.size() + addedEntries.length - removedEntries.length);
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
- current: () => published.read(binding.check),
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) published.install(staged, nextIds, change.kind === "reset");
3933
- if (change && initialized) revision++;
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
- Array.from(listeners).forEach((listener) => call(listener, change));
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.add(consumer),
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 = published.ids();
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
- published.release();
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 published state for one scalar Projection output. */
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 Set();
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
- const candidate = staged;
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
- publish: () => {
4043
- if (changed && initialized) revision++;
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
- profile.materialized.notification();
4049
- Array.from(listeners).forEach((listener) => call(listener, void 0));
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.add(consumer),
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
- return seal(reset);
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 order = scheduler.order();
4369
+ const id = scheduler.nextId();
4154
4370
  processor = {
4155
4371
  kind: "processor",
4156
- order,
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 = next;
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 || !unsubscribeSource && sourceRevision !== revision) evaluate();
4332
- return value;
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
- if (isMapLike(current)) {
4339
- change = diffCollection(previousCollection, current);
4340
- previousCollection = current;
4341
- if (!change) {
4342
- sourceRevision = source.revision();
4343
- return;
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
- } else previousCollection = void 0;
4561
+ }
4562
+ if (affected) evaluate();
4563
+ else {
4564
+ sourceRevision = revision;
4565
+ updateCollectionSnapshot(current);
4566
+ }
4346
4567
  }
4347
- if (!selectionAffects(selection, change)) {
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
- return;
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) unsubscribeSource = source.subscribe(onProjectionChange);
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 = state.current();
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) recovering = true;
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 published = boundary.output.current();
5518
- return !Object.is(latest, published) && equal(published, value) ? published : value;
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 published = source.equality === Object.is ? void 0 : boundary.output.current();
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 (published?.has(key)) {
5606
- const before = published.get(key);
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
- notify(state.listeners);
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 || state.tracked) return;
6011
+ if (state.ended) return;
5778
6012
  const current = source.current();
5779
- if (current.has(key)) attachActive(state, current.get(key));
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 (!change || change.kind === "reset") {
5815
- reconcile(current);
5816
- flushNotifications();
5817
- return;
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.subscribe(onChange);
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, requester) => scheduler.batch(() => {
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, state);
6394
+ return batch(run, options);
6142
6395
  }
6143
6396
  return Object.freeze({
6144
6397
  own,