doxum 0.1.33 → 0.1.34

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 snapshotDependencyValue, 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-RghG_Qxs.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
@@ -3617,6 +3617,104 @@ function createValueDerive(dependencies, compute, equality = Object.is) {
3617
3617
  }
3618
3618
  const derive = Object.assign(createValueDerive, { keyed: keyedDerive });
3619
3619
  //#endregion
3620
+ //#region core/src/projection/collection/changes.ts
3621
+ /** Net transitions for one consumer's unconsumed interval; bounded by touched keys. */
3622
+ const createCollectionChanges = () => {
3623
+ let first;
3624
+ let beforeOrder = [];
3625
+ let afterOrder = [];
3626
+ let cached;
3627
+ let valid = false;
3628
+ let transitions;
3629
+ const merge = (change) => {
3630
+ const write = (key, before, after) => {
3631
+ const previous = transitions.get(key);
3632
+ const initial = previous?.before ?? before;
3633
+ if (initial.present === after.present && (!initial.present || after.present && Object.is(initial.value, after.value))) transitions.delete(key);
3634
+ else if (previous) previous.after = after;
3635
+ else transitions.set(key, {
3636
+ before,
3637
+ after
3638
+ });
3639
+ };
3640
+ for (const entry of change.added) write(entry.key, { present: false }, {
3641
+ present: true,
3642
+ value: entry.after
3643
+ });
3644
+ for (const entry of change.updated) write(entry.key, {
3645
+ present: true,
3646
+ value: entry.before
3647
+ }, {
3648
+ present: true,
3649
+ value: entry.after
3650
+ });
3651
+ for (const entry of change.removed) write(entry.key, {
3652
+ present: true,
3653
+ value: entry.before
3654
+ }, { present: false });
3655
+ };
3656
+ return {
3657
+ add(change, before, after) {
3658
+ valid = false;
3659
+ afterOrder = after;
3660
+ if (!first) {
3661
+ first = change;
3662
+ beforeOrder = before;
3663
+ return;
3664
+ }
3665
+ if (first.kind === "reset") return;
3666
+ if (change.kind === "reset") {
3667
+ first = change;
3668
+ transitions = void 0;
3669
+ return;
3670
+ }
3671
+ if (!transitions) {
3672
+ transitions = /* @__PURE__ */ new Map();
3673
+ merge(first);
3674
+ }
3675
+ merge(change);
3676
+ },
3677
+ current() {
3678
+ if (!transitions) return first;
3679
+ if (valid) return cached;
3680
+ const added = [];
3681
+ const updated = [];
3682
+ const removed = [];
3683
+ for (const [key, { before, after }] of transitions) if (!before.present) {
3684
+ if (after.present) added.push({
3685
+ key,
3686
+ after: after.value
3687
+ });
3688
+ } else if (!after.present) removed.push({
3689
+ key,
3690
+ before: before.value
3691
+ });
3692
+ else if (!Object.is(before.value, after.value)) updated.push({
3693
+ key,
3694
+ before: before.value,
3695
+ after: after.value
3696
+ });
3697
+ cached = createCollectionChange({
3698
+ added,
3699
+ updated,
3700
+ removed,
3701
+ beforeOrder,
3702
+ afterOrder
3703
+ });
3704
+ valid = true;
3705
+ return cached;
3706
+ },
3707
+ reset: () => first?.kind === "reset",
3708
+ clear() {
3709
+ first = void 0;
3710
+ cached = void 0;
3711
+ valid = false;
3712
+ transitions = void 0;
3713
+ beforeOrder = afterOrder = [];
3714
+ }
3715
+ };
3716
+ };
3717
+ //#endregion
3620
3718
  //#region core/src/projection/collection/index.ts
3621
3719
  const height = (node) => node?.height ?? 0;
3622
3720
  const node = (key, value, left, right) => {
@@ -3711,7 +3809,7 @@ var PersistentKeyedIndex = class PersistentKeyedIndex {
3711
3809
  const createCollectionState = (initial) => {
3712
3810
  const values = new Map(initial);
3713
3811
  let ids = Object.freeze([...values.keys()]);
3714
- let index = values.size ? PersistentKeyedIndex.from(values) : PersistentKeyedIndex.empty();
3812
+ let index;
3715
3813
  return {
3716
3814
  get: (key) => values.get(key),
3717
3815
  has: (key) => values.has(key),
@@ -3719,7 +3817,7 @@ const createCollectionState = (initial) => {
3719
3817
  size: () => values.size,
3720
3818
  ids: () => ids,
3721
3819
  read: (check) => {
3722
- const snapshotIndex = index;
3820
+ const snapshotIndex = index ??= PersistentKeyedIndex.from(values);
3723
3821
  const snapshotIds = ids;
3724
3822
  return Object.freeze({
3725
3823
  get: (key) => {
@@ -3740,9 +3838,9 @@ const createCollectionState = (initial) => {
3740
3838
  if (reset) {
3741
3839
  values.clear();
3742
3840
  for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
3743
- index = PersistentKeyedIndex.from(values);
3841
+ if (index) index = PersistentKeyedIndex.from(values);
3744
3842
  } else for (const [key, entry] of staged) {
3745
- index = entry.present ? index.set(key, entry.value) : index.remove(key);
3843
+ if (index) index = entry.present ? index.set(key, entry.value) : index.remove(key);
3746
3844
  if (entry.present) values.set(key, entry.value);
3747
3845
  else values.delete(key);
3748
3846
  }
@@ -3751,17 +3849,17 @@ const createCollectionState = (initial) => {
3751
3849
  release: () => {
3752
3850
  values.clear();
3753
3851
  ids = Object.freeze([]);
3754
- index = PersistentKeyedIndex.empty();
3852
+ index = void 0;
3755
3853
  }
3756
3854
  };
3757
3855
  };
3758
3856
  //#endregion
3759
3857
  //#region core/src/projection/output/collection.ts
3760
- /** Owns staged keyed intent, exact transitions and published collection state. */
3858
+ /** Owns staged keyed intent, exact transitions and current collection state. */
3761
3859
  const createCollectionOutput = (binding) => {
3762
- const published = createCollectionState();
3860
+ const currentState = createCollectionState();
3763
3861
  const staged = /* @__PURE__ */ new Map();
3764
- let nextIds = published.ids();
3862
+ let nextIds = currentState.ids();
3765
3863
  let change;
3766
3864
  let initialized = false;
3767
3865
  let reset = false;
@@ -3769,23 +3867,26 @@ const createCollectionOutput = (binding) => {
3769
3867
  let explicitOrder;
3770
3868
  let cleared = false;
3771
3869
  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);
3870
+ const consumers = /* @__PURE__ */ new Map();
3871
+ const notifications = createCollectionChanges();
3872
+ const observers = /* @__PURE__ */ new Set();
3873
+ let baseline;
3874
+ const hasNext = (key) => staged.has(key) ? staged.get(key).present : !cleared && currentState.has(key);
3774
3875
  const getNext = (key) => {
3775
3876
  const entry = staged.get(key);
3776
- return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : published.get(key);
3877
+ return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : currentState.get(key);
3777
3878
  };
3778
3879
  const deriveIds = () => {
3779
3880
  const result = [];
3780
3881
  if (!cleared) {
3781
- for (const key of published.ids()) if (hasNext(key)) result.push(key);
3882
+ for (const key of currentState.ids()) if (hasNext(key)) result.push(key);
3782
3883
  }
3783
- for (const [key, entry] of staged) if (entry.present && (cleared || !published.has(key))) result.push(key);
3884
+ for (const [key, entry] of staged) if (entry.present && (cleared || !currentState.has(key))) result.push(key);
3784
3885
  return result;
3785
3886
  };
3786
3887
  const begin = (active, initialize) => {
3787
3888
  staged.clear();
3788
- nextIds = published.ids();
3889
+ nextIds = currentState.ids();
3789
3890
  change = void 0;
3790
3891
  explicitOrder = void 0;
3791
3892
  cleared = initialize;
@@ -3793,15 +3894,15 @@ const createCollectionOutput = (binding) => {
3793
3894
  const read = (next) => ({
3794
3895
  get: (key) => {
3795
3896
  assertScope(active);
3796
- return next ? getNext(key) : published.get(key);
3897
+ return next ? getNext(key) : currentState.get(key);
3797
3898
  },
3798
3899
  has: (key) => {
3799
3900
  assertScope(active);
3800
- return next ? hasNext(key) : published.has(key);
3901
+ return next ? hasNext(key) : currentState.has(key);
3801
3902
  },
3802
3903
  ids: () => {
3803
3904
  assertScope(active);
3804
- if (!next) return published.ids();
3905
+ if (!next) return currentState.ids();
3805
3906
  if (explicitOrder) return explicitOrder;
3806
3907
  return Object.freeze(deriveIds());
3807
3908
  }
@@ -3831,15 +3932,28 @@ const createCollectionOutput = (binding) => {
3831
3932
  };
3832
3933
  const seal = (nextReset, isEqual) => {
3833
3934
  if (cleared) {
3834
- for (const key of published.keys()) if (!staged.has(key)) staged.set(key, { present: false });
3935
+ for (const key of currentState.keys()) if (!staged.has(key)) staged.set(key, { present: false });
3835
3936
  }
3836
3937
  profile.projection("touchedKeys", staged.size);
3837
3938
  const addedEntries = [];
3838
3939
  const updatedEntries = [];
3839
3940
  const removedEntries = [];
3840
- for (const [key, entry] of staged) {
3841
- const existed = published.has(key);
3941
+ for (const [key, original] of staged) {
3942
+ let entry = original;
3943
+ const existed = currentState.has(key);
3842
3944
  if (entry.present) {
3945
+ if (baseline?.has(key) && (!existed || !Object.is(currentState.get(key), baseline.get(key)))) {
3946
+ const before = baseline.get(key);
3947
+ const equivalent = isEqual(before, entry.value);
3948
+ assertSynchronous(equivalent);
3949
+ if (equivalent) {
3950
+ entry = {
3951
+ present: true,
3952
+ value: before
3953
+ };
3954
+ staged.set(key, entry);
3955
+ }
3956
+ }
3843
3957
  if (!existed) {
3844
3958
  addedEntries.push({
3845
3959
  key,
@@ -3847,7 +3961,7 @@ const createCollectionOutput = (binding) => {
3847
3961
  });
3848
3962
  continue;
3849
3963
  }
3850
- const before = published.get(key);
3964
+ const before = currentState.get(key);
3851
3965
  const equal = isEqual(before, entry.value);
3852
3966
  assertSynchronous(equal);
3853
3967
  if (!equal) updatedEntries.push({
@@ -3864,13 +3978,13 @@ const createCollectionOutput = (binding) => {
3864
3978
  }
3865
3979
  if (existed) removedEntries.push({
3866
3980
  key,
3867
- before: published.get(key)
3981
+ before: currentState.get(key)
3868
3982
  });
3869
3983
  else staged.delete(key);
3870
3984
  }
3871
- const ids = published.ids();
3985
+ const ids = currentState.ids();
3872
3986
  if (explicitOrder) {
3873
- const expectedLength = published.size() + addedEntries.length - removedEntries.length;
3987
+ const expectedLength = currentState.size() + addedEntries.length - removedEntries.length;
3874
3988
  const seen = /* @__PURE__ */ new Set();
3875
3989
  for (const key of explicitOrder) {
3876
3990
  if (seen.has(key) || !hasNext(key)) throw new TypeError("Projection order must contain every key exactly once.");
@@ -3879,7 +3993,7 @@ const createCollectionOutput = (binding) => {
3879
3993
  if (seen.size !== expectedLength) throw new TypeError("Projection order must contain every key exactly once.");
3880
3994
  nextIds = sameArray(ids, explicitOrder) ? ids : explicitOrder;
3881
3995
  } else if (addedEntries.length || removedEntries.length) {
3882
- const order = new Array(published.size() + addedEntries.length - removedEntries.length);
3996
+ const order = new Array(currentState.size() + addedEntries.length - removedEntries.length);
3883
3997
  let index = 0;
3884
3998
  for (const key of ids) if (hasNext(key)) order[index++] = key;
3885
3999
  for (const entry of addedEntries) order[index++] = entry.key;
@@ -3896,7 +4010,7 @@ const createCollectionOutput = (binding) => {
3896
4010
  reset = nextReset;
3897
4011
  return change !== void 0;
3898
4012
  };
3899
- const context = (active, cause) => Object.freeze({
4013
+ const context = (active, cause, consumer) => Object.freeze({
3900
4014
  kind: "collection",
3901
4015
  read: {
3902
4016
  get: (key) => {
@@ -3912,9 +4026,9 @@ const createCollectionOutput = (binding) => {
3912
4026
  return nextIds;
3913
4027
  }
3914
4028
  },
3915
- change,
4029
+ change: consumer ? consumers.get(consumer)?.current() : change,
3916
4030
  revision,
3917
- reset,
4031
+ reset: consumer ? consumers.get(consumer)?.reset() ?? false : reset,
3918
4032
  cause
3919
4033
  });
3920
4034
  return {
@@ -3924,35 +4038,59 @@ const createCollectionOutput = (binding) => {
3924
4038
  },
3925
4039
  begin,
3926
4040
  seal,
3927
- context: (active) => context(active, binding.cause()),
3928
- current: () => published.read(binding.check),
4041
+ context: (active, consumer) => context(active, binding.cause(), consumer),
4042
+ acknowledge: (consumer) => consumers.get(consumer)?.clear(),
4043
+ pending: (consumer) => consumers.get(consumer)?.current() !== void 0,
4044
+ baseline: () => baseline ?? currentState.read(binding.check),
4045
+ current: () => currentState.read(binding.check),
3929
4046
  revision: () => revision,
3930
- reset: () => reset,
4047
+ reset: (consumer) => consumer ? consumers.get(consumer)?.reset() ?? false : reset,
3931
4048
  publish: () => {
3932
- if (change) published.install(staged, nextIds, change.kind === "reset");
3933
- if (change && initialized) revision++;
4049
+ if (change) {
4050
+ if (initialized) {
4051
+ baseline ??= currentState.read(() => void 0);
4052
+ notifications.add(change, currentState.ids(), nextIds);
4053
+ for (const pending of consumers.values()) pending.add(change, currentState.ids(), nextIds);
4054
+ revision++;
4055
+ }
4056
+ currentState.install(staged, nextIds, change.kind === "reset");
4057
+ }
3934
4058
  initialized = true;
3935
4059
  },
3936
- emit: (call) => {
3937
- Array.from(listeners).forEach((listener) => call(listener, change));
4060
+ emit: (call, force) => {
4061
+ const net = notifications.current();
4062
+ for (const listener of [...observers]) call(listener, net);
4063
+ if (net || force) for (const listener of [...listeners]) call(listener, net);
4064
+ },
4065
+ finish: () => {
4066
+ baseline = void 0;
4067
+ notifications.clear();
4068
+ },
4069
+ observe: (listener) => {
4070
+ binding.check();
4071
+ observers.add(listener);
4072
+ return () => observers.delete(listener);
3938
4073
  },
3939
4074
  hasConsumers: () => consumers.size > 0,
3940
- forEachConsumer: (run) => consumers.forEach(run),
3941
- attachConsumer: (consumer) => consumers.add(consumer),
4075
+ forEachConsumer: (run) => consumers.forEach((_pending, consumer) => run(consumer)),
4076
+ attachConsumer: (consumer) => consumers.set(consumer, createCollectionChanges()),
3942
4077
  detachConsumer: (consumer) => consumers.delete(consumer),
3943
4078
  clear: () => {
3944
4079
  staged.clear();
3945
- nextIds = published.ids();
4080
+ nextIds = currentState.ids();
3946
4081
  change = void 0;
3947
4082
  reset = false;
3948
4083
  explicitOrder = void 0;
3949
4084
  cleared = false;
3950
4085
  },
3951
4086
  release: () => {
3952
- published.release();
4087
+ currentState.release();
3953
4088
  staged.clear();
3954
4089
  listeners.clear();
3955
4090
  consumers.clear();
4091
+ observers.clear();
4092
+ baseline = void 0;
4093
+ notifications.clear();
3956
4094
  nextIds = Object.freeze([]);
3957
4095
  change = void 0;
3958
4096
  initialized = false;
@@ -3971,7 +4109,7 @@ const createCollectionOutput = (binding) => {
3971
4109
  //#endregion
3972
4110
  //#region core/src/projection/output/value.ts
3973
4111
  const unset = Symbol("projection-value-unset");
3974
- /** Owns staged and published state for one scalar Projection output. */
4112
+ /** Owns staged and current state for one scalar Projection output. */
3975
4113
  const createValueOutput = (binding) => {
3976
4114
  let value;
3977
4115
  let staged = unset;
@@ -3981,7 +4119,10 @@ const createValueOutput = (binding) => {
3981
4119
  let changed = false;
3982
4120
  let reset = false;
3983
4121
  const listeners = /* @__PURE__ */ new Set();
3984
- const consumers = /* @__PURE__ */ new Set();
4122
+ const consumers = /* @__PURE__ */ new Map();
4123
+ const observers = /* @__PURE__ */ new Set();
4124
+ let baseline = unset;
4125
+ let resetRevision = -1;
3985
4126
  const begin = (active, initialize) => {
3986
4127
  staged = unset;
3987
4128
  next = value;
@@ -4008,7 +4149,12 @@ const createValueOutput = (binding) => {
4008
4149
  next = value;
4009
4150
  return false;
4010
4151
  }
4011
- const candidate = staged;
4152
+ let candidate = staged;
4153
+ if (baseline !== unset && !Object.is(value, baseline)) {
4154
+ const equivalent = isEqual(baseline, candidate);
4155
+ assertSynchronous(equivalent);
4156
+ if (equivalent) candidate = baseline;
4157
+ }
4012
4158
  const equal = initialized ? isEqual(value, candidate) : false;
4013
4159
  assertSynchronous(equal);
4014
4160
  changed = !equal;
@@ -4023,13 +4169,13 @@ const createValueOutput = (binding) => {
4023
4169
  },
4024
4170
  begin,
4025
4171
  seal,
4026
- context: (active) => {
4172
+ context: (active, consumer) => {
4027
4173
  assertScope(active);
4028
4174
  return Object.freeze({
4029
4175
  kind: "value",
4030
4176
  value: next,
4031
4177
  revision,
4032
- reset,
4178
+ reset: consumer ? (consumers.get(consumer)?.revision ?? -1) < resetRevision : reset,
4033
4179
  cause: binding.cause()
4034
4180
  });
4035
4181
  },
@@ -4038,19 +4184,48 @@ const createValueOutput = (binding) => {
4038
4184
  return value;
4039
4185
  },
4040
4186
  revision: () => revision,
4041
- reset: () => reset,
4042
- publish: () => {
4043
- if (changed && initialized) revision++;
4187
+ reset: (consumer) => consumer ? (consumers.get(consumer)?.revision ?? -1) < resetRevision : reset,
4188
+ acknowledge: (consumer) => {
4189
+ if (consumers.has(consumer)) consumers.set(consumer, {
4190
+ revision,
4191
+ value
4192
+ });
4193
+ },
4194
+ pending: (consumer) => {
4195
+ const previous = consumers.get(consumer);
4196
+ return !previous || previous.revision < resetRevision || !Object.is(previous.value, value);
4197
+ },
4198
+ baseline: () => baseline === unset ? value : baseline,
4199
+ publish: (forceReset) => {
4200
+ if (initialized && (changed || forceReset)) {
4201
+ if (baseline === unset) baseline = value;
4202
+ revision++;
4203
+ if (reset) resetRevision = revision;
4204
+ }
4044
4205
  if (changed) value = next;
4045
4206
  initialized = true;
4046
4207
  },
4047
- emit: (call) => {
4048
- profile.materialized.notification();
4049
- Array.from(listeners).forEach((listener) => call(listener, void 0));
4208
+ emit: (call, force) => {
4209
+ for (const listener of [...observers]) call(listener, void 0);
4210
+ if (force || baseline !== unset && !Object.is(baseline, value)) {
4211
+ profile.materialized.notification();
4212
+ for (const listener of [...listeners]) call(listener, void 0);
4213
+ }
4214
+ },
4215
+ finish: () => {
4216
+ baseline = unset;
4217
+ },
4218
+ observe: (listener) => {
4219
+ binding.check();
4220
+ observers.add(listener);
4221
+ return () => observers.delete(listener);
4050
4222
  },
4051
4223
  hasConsumers: () => consumers.size > 0,
4052
- forEachConsumer: (run) => consumers.forEach(run),
4053
- attachConsumer: (consumer) => consumers.add(consumer),
4224
+ forEachConsumer: (run) => consumers.forEach((_revision, consumer) => run(consumer)),
4225
+ attachConsumer: (consumer) => consumers.set(consumer, {
4226
+ revision,
4227
+ value
4228
+ }),
4054
4229
  detachConsumer: (consumer) => consumers.delete(consumer),
4055
4230
  clear: () => {
4056
4231
  staged = unset;
@@ -4061,6 +4236,8 @@ const createValueOutput = (binding) => {
4061
4236
  release: () => {
4062
4237
  listeners.clear();
4063
4238
  consumers.clear();
4239
+ observers.clear();
4240
+ baseline = unset;
4064
4241
  staged = unset;
4065
4242
  value = next = void 0;
4066
4243
  initialized = false;
@@ -4131,7 +4308,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
4131
4308
  let active = true;
4132
4309
  try {
4133
4310
  const scopeActive = () => active;
4134
- const sources = Object.freeze(dependencies.map((output) => output.context(scopeActive)));
4311
+ const sources = Object.freeze(dependencies.map((output) => output.context(scopeActive, processor)));
4135
4312
  const outputs = begin(scopeActive, reset);
4136
4313
  if (recreate && instance) {
4137
4314
  instance.release?.();
@@ -4145,16 +4322,17 @@ const createProcessor = (scheduler, definition, dependencies) => {
4145
4322
  sources,
4146
4323
  outputs
4147
4324
  }));
4148
- return seal(reset);
4325
+ const changed = seal(reset);
4326
+ dependencies.forEach((output) => output.acknowledge(processor));
4327
+ return changed;
4149
4328
  } finally {
4150
4329
  active = false;
4151
4330
  }
4152
4331
  };
4153
- const order = scheduler.order();
4332
+ const id = scheduler.nextId();
4154
4333
  processor = {
4155
4334
  kind: "processor",
4156
- order,
4157
- name: definition.name ? `${definition.name} (${order})` : `projection-${order}`,
4335
+ name: definition.name ? `${definition.name} (${id})` : `projection-${id}`,
4158
4336
  dependencies: Object.freeze([...dependencies]),
4159
4337
  outputs: Object.freeze(bound.map((entry) => entry.state)),
4160
4338
  fault: void 0,
@@ -4281,7 +4459,9 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
4281
4459
  let initialized = false;
4282
4460
  let value;
4283
4461
  let selectedRevision = 0;
4462
+ let notified;
4284
4463
  let sourceRevision = -1;
4464
+ let notificationRevision = -1;
4285
4465
  let selection = Object.freeze({
4286
4466
  keys: /* @__PURE__ */ new Set(),
4287
4467
  all: true,
@@ -4317,8 +4497,9 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
4317
4497
  });
4318
4498
  const changed = !initialized || !equality(value, next);
4319
4499
  if (changed) {
4500
+ const candidate = unsubscribeSource && equality(notified, next) ? notified : next;
4320
4501
  if (initialized) selectedRevision++;
4321
- value = next;
4502
+ value = candidate;
4322
4503
  }
4323
4504
  selection = nextSelection;
4324
4505
  initialized = true;
@@ -4328,31 +4509,44 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
4328
4509
  };
4329
4510
  const ensureCurrent = () => {
4330
4511
  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) {
4512
+ if (!initialized) evaluate();
4513
+ else if (sourceRevision !== revision) {
4337
4514
  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;
4515
+ let affected = true;
4516
+ if (previousCollection && isMapLike(current) && !selection.all) {
4517
+ affected = selection.structure && !iterableMatchesArray(current.keys(), [...previousCollection.keys()]);
4518
+ if (!affected) {
4519
+ for (const key of selection.keys) if (previousCollection.has(key) !== current.has(key) || !Object.is(previousCollection.get(key), current.get(key))) {
4520
+ affected = true;
4521
+ break;
4522
+ }
4344
4523
  }
4345
- } else previousCollection = void 0;
4524
+ }
4525
+ if (affected) evaluate();
4526
+ else {
4527
+ sourceRevision = revision;
4528
+ updateCollectionSnapshot(current);
4529
+ }
4346
4530
  }
4347
- if (!selectionAffects(selection, change)) {
4531
+ return value;
4532
+ };
4533
+ const onProjectionChange = (incoming) => {
4534
+ if (sourceRevision === notificationRevision && sourceRevision !== source.revision() && incoming && !selectionAffects(selection, incoming)) {
4348
4535
  sourceRevision = source.revision();
4349
4536
  updateCollectionSnapshot();
4350
- return;
4537
+ } else ensureCurrent();
4538
+ notificationRevision = sourceRevision;
4539
+ if (!equality(notified, value)) {
4540
+ notified = value;
4541
+ notifyProjectionListeners(listeners, "Projection selector listeners failed.");
4351
4542
  }
4352
- if (evaluate()) notifyProjectionListeners(listeners, "Projection selector listeners failed.");
4353
4543
  };
4354
4544
  const installSource = () => {
4355
- if (!unsubscribeSource) unsubscribeSource = source.subscribe(onProjectionChange);
4545
+ if (!unsubscribeSource) {
4546
+ notified = value;
4547
+ notificationRevision = sourceRevision;
4548
+ unsubscribeSource = source.observe(onProjectionChange);
4549
+ }
4356
4550
  };
4357
4551
  return Object.freeze({
4358
4552
  current: ensureCurrent,
@@ -4734,13 +4928,12 @@ const createValueBoundary = (scheduler, name, initial, equality, prepare, clearS
4734
4928
  active = false;
4735
4929
  }
4736
4930
  },
4737
- publish: state.publish,
4931
+ publish: () => state.publish(pendingReset),
4738
4932
  clear: () => {
4739
4933
  const failed = boundary.fault !== void 0;
4740
4934
  state.clear();
4741
4935
  pendingReset = false;
4742
4936
  pendingCause = void 0;
4743
- contextCause = void 0;
4744
4937
  recovering = false;
4745
4938
  clearSource(failed);
4746
4939
  },
@@ -4796,6 +4989,7 @@ const createValueBoundary = (scheduler, name, initial, equality, prepare, clearS
4796
4989
  return {
4797
4990
  producer: boundary,
4798
4991
  output,
4992
+ baseline: state.baseline,
4799
4993
  mark,
4800
4994
  fail,
4801
4995
  detach: (next) => {
@@ -4815,7 +5009,7 @@ const stageCollectionRead = (state, read, active, options) => {
4815
5009
  }
4816
5010
  evaluation.output.order(ids);
4817
5011
  } else {
4818
- const previous = state.current();
5012
+ const previous = evaluation.previous;
4819
5013
  let currentIds;
4820
5014
  const ids = () => currentIds ??= read.ids();
4821
5015
  if (options.candidates) for (const key of options.candidates) if (read.has(key)) {
@@ -4896,7 +5090,6 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
4896
5090
  state.clear();
4897
5091
  pendingReset = false;
4898
5092
  pendingCause = void 0;
4899
- contextCause = void 0;
4900
5093
  recovering = false;
4901
5094
  fullScan = false;
4902
5095
  orderMayChange = false;
@@ -4949,7 +5142,11 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
4949
5142
  fullScan ||= metadata.fullScan ?? false;
4950
5143
  orderMayChange ||= metadata.orderMayChange ?? false;
4951
5144
  if (metadata.candidates) for (const key of metadata.candidates) candidates.add(key);
4952
- if (boundary.fault) recovering = true;
5145
+ if (boundary.fault) {
5146
+ recovering = true;
5147
+ fullScan = true;
5148
+ orderMayChange = true;
5149
+ }
4953
5150
  scheduler.capture(boundary);
4954
5151
  };
4955
5152
  const fail = (cause) => {
@@ -4960,6 +5157,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
4960
5157
  return {
4961
5158
  producer: boundary,
4962
5159
  output,
5160
+ baseline: state.baseline,
4963
5161
  mark,
4964
5162
  fail,
4965
5163
  detach: (next) => {
@@ -5509,13 +5707,12 @@ const materializeInputSource = (scheduler, definition) => {
5509
5707
  ...boundary,
5510
5708
  input: {
5511
5709
  kind: "value",
5512
- read: () => latest,
5513
5710
  set(value) {
5514
5711
  const next = scheduler.acceptInput(() => {
5515
5712
  if (equal(latest, value)) return latest;
5516
5713
  if (source.equality === Object.is) return value;
5517
- const published = boundary.output.current();
5518
- return !Object.is(latest, published) && equal(published, value) ? published : value;
5714
+ const baseline = boundary.baseline();
5715
+ return !Object.is(latest, baseline) && equal(baseline, value) ? baseline : value;
5519
5716
  });
5520
5717
  if (Object.is(latest, next)) return;
5521
5718
  latest = next;
@@ -5526,7 +5723,6 @@ const materializeInputSource = (scheduler, definition) => {
5526
5723
  };
5527
5724
  }
5528
5725
  const values = createCollectionState(source.initial);
5529
- let view;
5530
5726
  const latest = {
5531
5727
  get: values.get,
5532
5728
  has: values.has,
@@ -5535,17 +5731,11 @@ const materializeInputSource = (scheduler, definition) => {
5535
5731
  const boundary = createCollectionBoundary(scheduler, "projection collection input", () => latest, Object.is, () => void 0);
5536
5732
  boundary.detach(() => {
5537
5733
  values.release();
5538
- view = void 0;
5539
5734
  });
5540
- const check = () => {
5541
- scheduler.assertActive();
5542
- if (boundary.producer.disposed) throw new ProjectionDisposedError();
5543
- };
5544
5735
  return {
5545
5736
  ...boundary,
5546
5737
  input: {
5547
5738
  kind: "collection",
5548
- read: () => view ??= collectionView(values.read(check)),
5549
5739
  update(run) {
5550
5740
  const accepted = scheduler.acceptInput(() => {
5551
5741
  const staged = /* @__PURE__ */ new Map();
@@ -5592,7 +5782,7 @@ const materializeInputSource = (scheduler, definition) => {
5592
5782
  } finally {
5593
5783
  active = false;
5594
5784
  }
5595
- const published = source.equality === Object.is ? void 0 : boundary.output.current();
5785
+ const baseline = source.equality === Object.is ? void 0 : boundary.baseline();
5596
5786
  for (const [key, entry] of staged) {
5597
5787
  const existed = values.has(key);
5598
5788
  if (!entry.present) {
@@ -5602,8 +5792,8 @@ const materializeInputSource = (scheduler, definition) => {
5602
5792
  const previous = values.get(key);
5603
5793
  let next = entry.value;
5604
5794
  if (existed && equal(previous, next)) next = previous;
5605
- else if (published?.has(key)) {
5606
- const before = published.get(key);
5795
+ else if (baseline?.has(key)) {
5796
+ const before = baseline.get(key);
5607
5797
  if ((!existed || !Object.is(previous, before)) && equal(before, next)) next = before;
5608
5798
  }
5609
5799
  if (existed && Object.is(previous, next)) staged.delete(key);
@@ -5628,7 +5818,6 @@ const materializeInputSource = (scheduler, definition) => {
5628
5818
  });
5629
5819
  if (!accepted.staged.size && !accepted.structural) return;
5630
5820
  values.install(accepted.staged, accepted.order, false);
5631
- view = void 0;
5632
5821
  boundary.mark({
5633
5822
  candidates: accepted.staged.keys(),
5634
5823
  orderMayChange: accepted.structural
@@ -5681,6 +5870,11 @@ const createProjectionItems = (source, checkOwner) => {
5681
5870
  const waiting = /* @__PURE__ */ new Map();
5682
5871
  let keysValue = Object.freeze([...source.current().keys()]);
5683
5872
  let keysRevision = 0;
5873
+ let notifiedKeys = keysValue;
5874
+ let settledRevision = source.revision();
5875
+ let keysSourceRevision = settledRevision;
5876
+ const pendingKeys = /* @__PURE__ */ new Set();
5877
+ const changedStates = /* @__PURE__ */ new Set();
5684
5878
  const keyListeners = /* @__PURE__ */ new Set();
5685
5879
  const check = () => {
5686
5880
  if (disposed) throw new ProjectionDisposedError();
@@ -5716,7 +5910,7 @@ const createProjectionItems = (source, checkOwner) => {
5716
5910
  }
5717
5911
  state.value = value;
5718
5912
  state.revision++;
5719
- notify(state.listeners);
5913
+ changedStates.add(state);
5720
5914
  };
5721
5915
  const attachActive = (state, value) => {
5722
5916
  if (state.ended) return;
@@ -5759,10 +5953,12 @@ const createProjectionItems = (source, checkOwner) => {
5759
5953
  if (extra) for (const state of extra) publishState(state, value);
5760
5954
  };
5761
5955
  const updateKeys = (current) => {
5956
+ const revision = source.revision();
5957
+ if (keysSourceRevision === revision) return;
5958
+ keysSourceRevision = revision;
5762
5959
  if (iterableMatchesArray(current.keys(), keysValue)) return;
5763
- keysValue = Object.freeze([...current.keys()]);
5960
+ keysValue = iterableMatchesArray(current.keys(), notifiedKeys) ? notifiedKeys : Object.freeze([...current.keys()]);
5764
5961
  keysRevision++;
5765
- notify(keyListeners);
5766
5962
  };
5767
5963
  const createItem = (key, initial) => {
5768
5964
  const state = {
@@ -5771,12 +5967,15 @@ const createProjectionItems = (source, checkOwner) => {
5771
5967
  tracked: initial !== void 0,
5772
5968
  ended: false,
5773
5969
  value: initial?.value,
5970
+ notified: initial?.value,
5774
5971
  revision: 0
5775
5972
  };
5776
5973
  const ensureObserved = () => {
5777
- if (state.ended || state.tracked) return;
5974
+ if (state.ended) return;
5778
5975
  const current = source.current();
5779
- if (current.has(key)) attachActive(state, current.get(key));
5976
+ if (source.revision() !== settledRevision) pendingKeys.add(key);
5977
+ if (!state.tracked && current.has(key)) attachActive(state, current.get(key));
5978
+ else if (state.tracked) publishState(state, current.get(key));
5780
5979
  };
5781
5980
  state.readable = Object.freeze({
5782
5981
  current: () => {
@@ -5811,25 +6010,45 @@ const createProjectionItems = (source, checkOwner) => {
5811
6010
  const onChange = (change) => {
5812
6011
  check();
5813
6012
  const current = source.current();
5814
- if (!change || change.kind === "reset") {
5815
- reconcile(current);
5816
- flushNotifications();
5817
- return;
6013
+ if (change?.kind === "reset") reconcile(current);
6014
+ else {
6015
+ if (change) {
6016
+ for (const entry of change.removed) terminateKey(entry.key);
6017
+ for (const entry of change.added) activateWaiting(entry.key, entry.after);
6018
+ for (const entry of change.updated) updateKey(entry.key, entry.after);
6019
+ if (change.added.length || change.removed.length || change.order) updateKeys(current);
6020
+ }
6021
+ for (const key of pendingKeys) if (!current.has(key)) terminateKey(key);
6022
+ else {
6023
+ updateKey(key, current.get(key));
6024
+ activateWaiting(key, current.get(key));
6025
+ }
6026
+ }
6027
+ pendingKeys.clear();
6028
+ settledRevision = source.revision();
6029
+ for (const state of changedStates) if (!Object.is(state.notified, state.value)) {
6030
+ state.notified = state.value;
6031
+ notify(state.listeners);
6032
+ }
6033
+ changedStates.clear();
6034
+ if (keysValue !== notifiedKeys) updateKeys(current);
6035
+ else keysSourceRevision = settledRevision;
6036
+ if (keysValue !== notifiedKeys) {
6037
+ notifiedKeys = keysValue;
6038
+ notify(keyListeners);
5818
6039
  }
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
6040
  flushNotifications();
5824
6041
  };
5825
- const stop = source.subscribe(onChange);
6042
+ const stop = source.observe(onChange);
5826
6043
  const keys = Object.freeze({
5827
6044
  current: () => {
5828
6045
  check();
6046
+ updateKeys(source.current());
5829
6047
  return keysValue;
5830
6048
  },
5831
6049
  revision: () => {
5832
6050
  check();
6051
+ updateKeys(source.current());
5833
6052
  return keysRevision;
5834
6053
  },
5835
6054
  subscribe: (listener) => {
@@ -5843,11 +6062,12 @@ const createProjectionItems = (source, checkOwner) => {
5843
6062
  keys,
5844
6063
  get: (key) => {
5845
6064
  check();
6065
+ const current = source.current();
6066
+ if (source.revision() !== settledRevision) pendingKeys.add(key);
5846
6067
  const canonical = active.get(key);
5847
6068
  if (canonical) return canonical.readable;
5848
6069
  const pending = waiting.get(key)?.values().next().value;
5849
6070
  if (pending) return pending.readable;
5850
- const current = source.current();
5851
6071
  if (current.has(key)) {
5852
6072
  const state = createItem(key, {
5853
6073
  present: true,
@@ -5873,6 +6093,8 @@ const createProjectionItems = (source, checkOwner) => {
5873
6093
  followers.clear();
5874
6094
  waiting.clear();
5875
6095
  notifications.length = 0;
6096
+ pendingKeys.clear();
6097
+ changedStates.clear();
5876
6098
  }
5877
6099
  };
5878
6100
  };
@@ -5933,6 +6155,7 @@ const createProjectionRuntime = (options) => {
5933
6155
  } else {
5934
6156
  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
6157
  const dependencies = definition.dependencies.map((dependency) => resolveOutput(dependency, requester));
6158
+ dependencies.forEach((output) => scheduler.ensureCurrent(output));
5936
6159
  const processor = createProcessor(scheduler, definition, Object.freeze(dependencies));
5937
6160
  result = Object.freeze({
5938
6161
  record: processor,
@@ -5956,6 +6179,7 @@ const createProjectionRuntime = (options) => {
5956
6179
  if (output.owner.fault) throw output.owner.fault;
5957
6180
  };
5958
6181
  const publicCurrent = (output) => {
6182
+ scheduler.ensureCurrent(output);
5959
6183
  assertOutput(output);
5960
6184
  const current = output.current();
5961
6185
  const revision = output.revision();
@@ -5987,10 +6211,12 @@ const createProjectionRuntime = (options) => {
5987
6211
  kind: output.kind,
5988
6212
  current: () => readProjection(projection, requester),
5989
6213
  revision: () => {
6214
+ scheduler.ensureCurrent(output);
5990
6215
  assertOutput(output);
5991
6216
  return output.revision();
5992
6217
  },
5993
- subscribe: output.subscribe
6218
+ subscribe: output.subscribe,
6219
+ observe: output.observe
5994
6220
  });
5995
6221
  };
5996
6222
  const projectionItems = (projection, requester) => {
@@ -6001,6 +6227,7 @@ const createProjectionRuntime = (options) => {
6001
6227
  if (existing) return existing.items;
6002
6228
  const controller = createProjectionItems(readableSource(projection, requester), () => {
6003
6229
  if (requester && !requester.active) throw new ProjectionDisposedError();
6230
+ scheduler.ensureCurrent(output);
6004
6231
  assertOutput(output);
6005
6232
  });
6006
6233
  registry.set(output, controller);
@@ -6035,18 +6262,7 @@ const createProjectionRuntime = (options) => {
6035
6262
  function update(target, valueOrRun) {
6036
6263
  updateInput(target, valueOrRun);
6037
6264
  }
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);
6265
+ const batch = (run, options) => scheduler.batch(run, options);
6050
6266
  const disposeScope = (state) => {
6051
6267
  if (!state.active) return;
6052
6268
  scheduler.assertIdle();
@@ -6138,7 +6354,7 @@ const createProjectionRuntime = (options) => {
6138
6354
  }
6139
6355
  function scopeBatch(run, options) {
6140
6356
  assertActive();
6141
- return batch(run, options, state);
6357
+ return batch(run, options);
6142
6358
  }
6143
6359
  return Object.freeze({
6144
6360
  own,