doxum 0.1.32 → 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 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 createCollectionChange, a as createKeyedDependencyRuntime, b as sameArray, c as assertScope, d as ProjectionDisposedError, f as ProjectionError, g as collectionHasStructuralChange, h as collectionChangeTouchesKey, i as compileKeyedDependencies, j as producerOf, k as isProjection, l as assertSynchronous, m as collectionChangeIntersects, n as keyedAbsent, o as outputKind, r as assertKeyedProjection, s as createKeyRelation, t as createKeyedTransform, u as createScheduler, v as diffCollection, w as collectionView, x as snapshotArray, y as iterableMatchesArray } from "./transform-gOtqcdHe.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
@@ -2774,6 +2774,108 @@ 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/flat-map.ts
2778
+ function createKeyedFlatMap(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
2779
+ const name = "derive.keyed.flatMap";
2780
+ assertKeyedProjection(source, `${name} source`);
2781
+ const specs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
2782
+ const select = specs === void 0 ? dependenciesOrSelect : selectOrEquality;
2783
+ const equality = specs === void 0 ? selectOrEquality : maybeEquality;
2784
+ if (typeof select !== "function") throw new TypeError(`${name} requires a selector.`);
2785
+ if (equality !== void 0 && typeof equality !== "function") throw new TypeError(`${name} equality must be a function.`);
2786
+ const compiled = compileKeyedDependencies(source, specs, name);
2787
+ const [projection] = defineProcessor({
2788
+ dependencies: compiled.projections,
2789
+ outputs: [{
2790
+ kind: "collection",
2791
+ equality: equality ?? Object.is
2792
+ }],
2793
+ name,
2794
+ create: () => {
2795
+ const dependencies = createKeyedDependencyRuntime(compiled);
2796
+ const childrenByParent = /* @__PURE__ */ new Map();
2797
+ const ownerByOutput = /* @__PURE__ */ new Map();
2798
+ return {
2799
+ evaluate: (evaluation) => {
2800
+ const driver = evaluation.sources[0];
2801
+ const output = evaluation.outputs[0];
2802
+ if (driver.kind !== "collection" || output.kind !== "collection") throw new Error(`${name} requires collection input and output.`);
2803
+ const plan = collectKeyedInvalidation(driver, evaluation.sources, dependencies, evaluation.reset);
2804
+ dependencies.prepare(evaluation.sources, plan.rebuild);
2805
+ if (plan.rebuild) {
2806
+ dependencies.clearBindings();
2807
+ childrenByParent.clear();
2808
+ ownerByOutput.clear();
2809
+ }
2810
+ const replaced = new Set(plan.keys);
2811
+ for (const entry of plan.removed) replaced.add(entry.key);
2812
+ const proposals = /* @__PURE__ */ new Map();
2813
+ const segments = /* @__PURE__ */ new Map();
2814
+ let orderDirty = plan.structural;
2815
+ for (const parent of replaced) {
2816
+ if (!driver.read.has(parent)) continue;
2817
+ const value = driver.read.get(parent);
2818
+ const entries = select(value, parent, dependencies.resolve(value, parent, evaluation.sources));
2819
+ assertSynchronous(entries);
2820
+ if (!Array.isArray(entries)) throw new TypeError(`${name} selector must return an array of entries.`);
2821
+ const keys = [];
2822
+ 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.`);
2824
+ const [key, child] = entry;
2825
+ const owner = ownerByOutput.get(key);
2826
+ if (proposals.has(key) || owner !== void 0 && !replaced.has(owner)) throw new TypeError(`${name} duplicate key ${JSON.stringify(key)}.`);
2827
+ proposals.set(key, {
2828
+ parent,
2829
+ value: child
2830
+ });
2831
+ keys.push(key);
2832
+ }
2833
+ const previous = childrenByParent.get(parent);
2834
+ const unchanged = previous ? sameArray(previous, keys) : keys.length === 0;
2835
+ if (!unchanged) orderDirty = true;
2836
+ if (keys.length) segments.set(parent, unchanged && previous ? previous : Object.freeze(keys));
2837
+ }
2838
+ if (plan.rebuild) {
2839
+ if (!evaluation.reset) {
2840
+ for (const key of output.previous.ids()) if (!proposals.has(key)) output.output.remove(key);
2841
+ }
2842
+ } else for (const parent of replaced) {
2843
+ const previous = childrenByParent.get(parent);
2844
+ if (previous) for (const key of previous) {
2845
+ if (!proposals.has(key)) output.output.remove(key);
2846
+ ownerByOutput.delete(key);
2847
+ }
2848
+ childrenByParent.delete(parent);
2849
+ }
2850
+ for (const entry of plan.removed) dependencies.remove(entry.key);
2851
+ for (const [parent, keys] of segments) childrenByParent.set(parent, keys);
2852
+ for (const [key, entry] of proposals) {
2853
+ ownerByOutput.set(key, entry.parent);
2854
+ output.output.set(key, entry.value);
2855
+ }
2856
+ if (orderDirty) {
2857
+ const order = [];
2858
+ const parents = driver.read.ids();
2859
+ for (const parent of parents) {
2860
+ const keys = childrenByParent.get(parent);
2861
+ if (keys) for (const key of keys) order.push(key);
2862
+ }
2863
+ profile.collectionView.idsScanned(parents.length + order.length);
2864
+ output.output.order(order);
2865
+ }
2866
+ dependencies.remember(evaluation.sources);
2867
+ },
2868
+ release: () => {
2869
+ childrenByParent.clear();
2870
+ ownerByOutput.clear();
2871
+ dependencies.release();
2872
+ }
2873
+ };
2874
+ }
2875
+ });
2876
+ return projection;
2877
+ }
2878
+ //#endregion
2777
2879
  //#region core/src/projection/keyed/composition.ts
2778
2880
  const assertEquality = (equality, role) => {
2779
2881
  if (equality !== void 0 && typeof equality !== "function") throw new TypeError(`${role} equality must be a function.`);
@@ -3440,44 +3542,26 @@ function createKeyedGroupBy(source, dependenciesOrSelector, maybeSelector) {
3440
3542
  const driver = evaluation.sources[0];
3441
3543
  const output = evaluation.outputs[0];
3442
3544
  if (driver.kind !== "collection" || output.kind !== "collection") throw new Error("derive.keyed.groupBy requires collection input and output.");
3443
- dependencies.prepare(evaluation.sources, evaluation.reset);
3444
- if (evaluation.reset) {
3545
+ const invalidation = collectKeyedInvalidation(driver, evaluation.sources, dependencies, evaluation.reset);
3546
+ dependencies.prepare(evaluation.sources, invalidation.rebuild);
3547
+ if (invalidation.rebuild) {
3445
3548
  rebuild(driver, evaluation.sources, output);
3446
3549
  dependencies.remember(evaluation.sources);
3447
3550
  return;
3448
3551
  }
3449
- const dirty = /* @__PURE__ */ new Set();
3450
3552
  const affected = /* @__PURE__ */ new Set();
3451
- let all = false;
3452
- let structural = false;
3453
- let orderDirty = false;
3454
- if (dependencies.driverChanged(driver)) {
3455
- const change = driver.change;
3456
- if (!change || change.kind === "reset") {
3457
- rebuild(driver, evaluation.sources, output);
3458
- dependencies.remember(evaluation.sources);
3459
- return;
3460
- } else {
3461
- for (const entry of change.removed) {
3462
- const changed = removeSourceKey(entry.key, affected);
3463
- orderDirty ||= changed;
3464
- dependencies.remove(entry.key);
3465
- dirty.delete(entry.key);
3466
- }
3467
- for (const entry of change.added) dirty.add(entry.key);
3468
- for (const entry of change.updated) dirty.add(entry.key);
3469
- structural = Boolean(change.added.length || change.removed.length || change.order);
3470
- orderDirty ||= structural;
3471
- if (structural) refreshOrderIndex(driver);
3472
- }
3553
+ let orderDirty = invalidation.structural;
3554
+ for (const entry of invalidation.removed) {
3555
+ const changed = removeSourceKey(entry.key, affected);
3556
+ orderDirty ||= changed;
3557
+ dependencies.remove(entry.key);
3473
3558
  }
3474
- all ||= dependencies.collectInvalidated(evaluation.sources, dirty);
3475
- if (all) for (const key of driver.read.ids()) dirty.add(key);
3476
- for (const key of dirty) {
3559
+ if (invalidation.structural) refreshOrderIndex(driver);
3560
+ for (const key of invalidation.keys) {
3477
3561
  const changed = selectGroups(driver, evaluation.sources, key, affected);
3478
3562
  orderDirty ||= changed;
3479
3563
  }
3480
- if (structural) markAllGroups(output, affected);
3564
+ if (invalidation.structural) markAllGroups(output, affected);
3481
3565
  publishAffected(affected, output, orderDirty);
3482
3566
  dependencies.remember(evaluation.sources);
3483
3567
  },
@@ -3501,6 +3585,7 @@ const keyedDerive = Object.assign(createKeyedDerive, {
3501
3585
  from: createKeyedFrom,
3502
3586
  merge: createKeyedMerge,
3503
3587
  groupBy: createKeyedGroupBy,
3588
+ flatMap: createKeyedFlatMap,
3504
3589
  singleton: createKeyedSingleton,
3505
3590
  subset: createKeyedSubset,
3506
3591
  filter: createKeyedFilter,
@@ -3532,6 +3617,104 @@ function createValueDerive(dependencies, compute, equality = Object.is) {
3532
3617
  }
3533
3618
  const derive = Object.assign(createValueDerive, { keyed: keyedDerive });
3534
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
3535
3718
  //#region core/src/projection/collection/index.ts
3536
3719
  const height = (node) => node?.height ?? 0;
3537
3720
  const node = (key, value, left, right) => {
@@ -3622,11 +3805,11 @@ var PersistentKeyedIndex = class PersistentKeyedIndex {
3622
3805
  }
3623
3806
  };
3624
3807
  //#endregion
3625
- //#region core/src/projection/output/collection.ts
3626
- const createPublishedCollection = () => {
3627
- const values = /* @__PURE__ */ new Map();
3628
- let ids = Object.freeze([]);
3629
- let index = PersistentKeyedIndex.empty();
3808
+ //#region core/src/projection/collection/state.ts
3809
+ const createCollectionState = (initial) => {
3810
+ const values = new Map(initial);
3811
+ let ids = Object.freeze([...values.keys()]);
3812
+ let index;
3630
3813
  return {
3631
3814
  get: (key) => values.get(key),
3632
3815
  has: (key) => values.has(key),
@@ -3634,7 +3817,7 @@ const createPublishedCollection = () => {
3634
3817
  size: () => values.size,
3635
3818
  ids: () => ids,
3636
3819
  read: (check) => {
3637
- const snapshotIndex = index;
3820
+ const snapshotIndex = index ??= PersistentKeyedIndex.from(values);
3638
3821
  const snapshotIds = ids;
3639
3822
  return Object.freeze({
3640
3823
  get: (key) => {
@@ -3651,13 +3834,13 @@ const createPublishedCollection = () => {
3651
3834
  }
3652
3835
  });
3653
3836
  },
3654
- publish: (staged, nextIds, reset) => {
3837
+ install: (staged, nextIds, reset) => {
3655
3838
  if (reset) {
3656
3839
  values.clear();
3657
3840
  for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
3658
- index = PersistentKeyedIndex.from(values);
3841
+ if (index) index = PersistentKeyedIndex.from(values);
3659
3842
  } else for (const [key, entry] of staged) {
3660
- 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);
3661
3844
  if (entry.present) values.set(key, entry.value);
3662
3845
  else values.delete(key);
3663
3846
  }
@@ -3666,15 +3849,17 @@ const createPublishedCollection = () => {
3666
3849
  release: () => {
3667
3850
  values.clear();
3668
3851
  ids = Object.freeze([]);
3669
- index = PersistentKeyedIndex.empty();
3852
+ index = void 0;
3670
3853
  }
3671
3854
  };
3672
3855
  };
3673
- /** Owns staged keyed intent, exact transitions and published collection state. */
3856
+ //#endregion
3857
+ //#region core/src/projection/output/collection.ts
3858
+ /** Owns staged keyed intent, exact transitions and current collection state. */
3674
3859
  const createCollectionOutput = (binding) => {
3675
- const published = createPublishedCollection();
3860
+ const currentState = createCollectionState();
3676
3861
  const staged = /* @__PURE__ */ new Map();
3677
- let nextIds = published.ids();
3862
+ let nextIds = currentState.ids();
3678
3863
  let change;
3679
3864
  let initialized = false;
3680
3865
  let reset = false;
@@ -3682,23 +3867,26 @@ const createCollectionOutput = (binding) => {
3682
3867
  let explicitOrder;
3683
3868
  let cleared = false;
3684
3869
  const listeners = /* @__PURE__ */ new Set();
3685
- const consumers = /* @__PURE__ */ new Set();
3686
- 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);
3687
3875
  const getNext = (key) => {
3688
3876
  const entry = staged.get(key);
3689
- 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);
3690
3878
  };
3691
3879
  const deriveIds = () => {
3692
3880
  const result = [];
3693
3881
  if (!cleared) {
3694
- 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);
3695
3883
  }
3696
- 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);
3697
3885
  return result;
3698
3886
  };
3699
3887
  const begin = (active, initialize) => {
3700
3888
  staged.clear();
3701
- nextIds = published.ids();
3889
+ nextIds = currentState.ids();
3702
3890
  change = void 0;
3703
3891
  explicitOrder = void 0;
3704
3892
  cleared = initialize;
@@ -3706,15 +3894,15 @@ const createCollectionOutput = (binding) => {
3706
3894
  const read = (next) => ({
3707
3895
  get: (key) => {
3708
3896
  assertScope(active);
3709
- return next ? getNext(key) : published.get(key);
3897
+ return next ? getNext(key) : currentState.get(key);
3710
3898
  },
3711
3899
  has: (key) => {
3712
3900
  assertScope(active);
3713
- return next ? hasNext(key) : published.has(key);
3901
+ return next ? hasNext(key) : currentState.has(key);
3714
3902
  },
3715
3903
  ids: () => {
3716
3904
  assertScope(active);
3717
- if (!next) return published.ids();
3905
+ if (!next) return currentState.ids();
3718
3906
  if (explicitOrder) return explicitOrder;
3719
3907
  return Object.freeze(deriveIds());
3720
3908
  }
@@ -3744,15 +3932,28 @@ const createCollectionOutput = (binding) => {
3744
3932
  };
3745
3933
  const seal = (nextReset, isEqual) => {
3746
3934
  if (cleared) {
3747
- 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 });
3748
3936
  }
3749
3937
  profile.projection("touchedKeys", staged.size);
3750
3938
  const addedEntries = [];
3751
3939
  const updatedEntries = [];
3752
3940
  const removedEntries = [];
3753
- for (const [key, entry] of staged) {
3754
- const existed = published.has(key);
3941
+ for (const [key, original] of staged) {
3942
+ let entry = original;
3943
+ const existed = currentState.has(key);
3755
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
+ }
3756
3957
  if (!existed) {
3757
3958
  addedEntries.push({
3758
3959
  key,
@@ -3760,24 +3961,30 @@ const createCollectionOutput = (binding) => {
3760
3961
  });
3761
3962
  continue;
3762
3963
  }
3763
- const before = published.get(key);
3764
- if (!isEqual(before, entry.value)) updatedEntries.push({
3964
+ const before = currentState.get(key);
3965
+ const equal = isEqual(before, entry.value);
3966
+ assertSynchronous(equal);
3967
+ if (!equal) updatedEntries.push({
3765
3968
  key,
3766
3969
  before,
3767
3970
  after: entry.value
3768
3971
  });
3769
- else if (!nextReset) staged.delete(key);
3972
+ else if (nextReset) staged.set(key, {
3973
+ present: true,
3974
+ value: before
3975
+ });
3976
+ else staged.delete(key);
3770
3977
  continue;
3771
3978
  }
3772
3979
  if (existed) removedEntries.push({
3773
3980
  key,
3774
- before: published.get(key)
3981
+ before: currentState.get(key)
3775
3982
  });
3776
3983
  else staged.delete(key);
3777
3984
  }
3778
- const ids = published.ids();
3985
+ const ids = currentState.ids();
3779
3986
  if (explicitOrder) {
3780
- const expectedLength = published.size() + addedEntries.length - removedEntries.length;
3987
+ const expectedLength = currentState.size() + addedEntries.length - removedEntries.length;
3781
3988
  const seen = /* @__PURE__ */ new Set();
3782
3989
  for (const key of explicitOrder) {
3783
3990
  if (seen.has(key) || !hasNext(key)) throw new TypeError("Projection order must contain every key exactly once.");
@@ -3786,7 +3993,7 @@ const createCollectionOutput = (binding) => {
3786
3993
  if (seen.size !== expectedLength) throw new TypeError("Projection order must contain every key exactly once.");
3787
3994
  nextIds = sameArray(ids, explicitOrder) ? ids : explicitOrder;
3788
3995
  } else if (addedEntries.length || removedEntries.length) {
3789
- const order = new Array(published.size() + addedEntries.length - removedEntries.length);
3996
+ const order = new Array(currentState.size() + addedEntries.length - removedEntries.length);
3790
3997
  let index = 0;
3791
3998
  for (const key of ids) if (hasNext(key)) order[index++] = key;
3792
3999
  for (const entry of addedEntries) order[index++] = entry.key;
@@ -3803,7 +4010,7 @@ const createCollectionOutput = (binding) => {
3803
4010
  reset = nextReset;
3804
4011
  return change !== void 0;
3805
4012
  };
3806
- const context = (active, cause) => Object.freeze({
4013
+ const context = (active, cause, consumer) => Object.freeze({
3807
4014
  kind: "collection",
3808
4015
  read: {
3809
4016
  get: (key) => {
@@ -3819,9 +4026,9 @@ const createCollectionOutput = (binding) => {
3819
4026
  return nextIds;
3820
4027
  }
3821
4028
  },
3822
- change,
4029
+ change: consumer ? consumers.get(consumer)?.current() : change,
3823
4030
  revision,
3824
- reset,
4031
+ reset: consumer ? consumers.get(consumer)?.reset() ?? false : reset,
3825
4032
  cause
3826
4033
  });
3827
4034
  return {
@@ -3831,35 +4038,59 @@ const createCollectionOutput = (binding) => {
3831
4038
  },
3832
4039
  begin,
3833
4040
  seal,
3834
- context: (active) => context(active, binding.cause()),
3835
- 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),
3836
4046
  revision: () => revision,
3837
- reset: () => reset,
4047
+ reset: (consumer) => consumer ? consumers.get(consumer)?.reset() ?? false : reset,
3838
4048
  publish: () => {
3839
- if (change) published.publish(staged, nextIds, change.kind === "reset");
3840
- 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
+ }
3841
4058
  initialized = true;
3842
4059
  },
3843
- emit: (call) => {
3844
- 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);
3845
4073
  },
3846
4074
  hasConsumers: () => consumers.size > 0,
3847
- forEachConsumer: (run) => consumers.forEach(run),
3848
- attachConsumer: (consumer) => consumers.add(consumer),
4075
+ forEachConsumer: (run) => consumers.forEach((_pending, consumer) => run(consumer)),
4076
+ attachConsumer: (consumer) => consumers.set(consumer, createCollectionChanges()),
3849
4077
  detachConsumer: (consumer) => consumers.delete(consumer),
3850
4078
  clear: () => {
3851
4079
  staged.clear();
3852
- nextIds = published.ids();
4080
+ nextIds = currentState.ids();
3853
4081
  change = void 0;
3854
4082
  reset = false;
3855
4083
  explicitOrder = void 0;
3856
4084
  cleared = false;
3857
4085
  },
3858
4086
  release: () => {
3859
- published.release();
4087
+ currentState.release();
3860
4088
  staged.clear();
3861
4089
  listeners.clear();
3862
4090
  consumers.clear();
4091
+ observers.clear();
4092
+ baseline = void 0;
4093
+ notifications.clear();
3863
4094
  nextIds = Object.freeze([]);
3864
4095
  change = void 0;
3865
4096
  initialized = false;
@@ -3878,7 +4109,7 @@ const createCollectionOutput = (binding) => {
3878
4109
  //#endregion
3879
4110
  //#region core/src/projection/output/value.ts
3880
4111
  const unset = Symbol("projection-value-unset");
3881
- /** Owns staged and published state for one scalar Projection output. */
4112
+ /** Owns staged and current state for one scalar Projection output. */
3882
4113
  const createValueOutput = (binding) => {
3883
4114
  let value;
3884
4115
  let staged = unset;
@@ -3888,7 +4119,10 @@ const createValueOutput = (binding) => {
3888
4119
  let changed = false;
3889
4120
  let reset = false;
3890
4121
  const listeners = /* @__PURE__ */ new Set();
3891
- 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;
3892
4126
  const begin = (active, initialize) => {
3893
4127
  staged = unset;
3894
4128
  next = value;
@@ -3915,8 +4149,15 @@ const createValueOutput = (binding) => {
3915
4149
  next = value;
3916
4150
  return false;
3917
4151
  }
3918
- const candidate = staged;
3919
- changed = !initialized || !isEqual(value, candidate);
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
+ }
4158
+ const equal = initialized ? isEqual(value, candidate) : false;
4159
+ assertSynchronous(equal);
4160
+ changed = !equal;
3920
4161
  next = changed ? candidate : value;
3921
4162
  reset = nextReset;
3922
4163
  return changed;
@@ -3928,13 +4169,13 @@ const createValueOutput = (binding) => {
3928
4169
  },
3929
4170
  begin,
3930
4171
  seal,
3931
- context: (active) => {
4172
+ context: (active, consumer) => {
3932
4173
  assertScope(active);
3933
4174
  return Object.freeze({
3934
4175
  kind: "value",
3935
4176
  value: next,
3936
4177
  revision,
3937
- reset,
4178
+ reset: consumer ? (consumers.get(consumer)?.revision ?? -1) < resetRevision : reset,
3938
4179
  cause: binding.cause()
3939
4180
  });
3940
4181
  },
@@ -3943,19 +4184,48 @@ const createValueOutput = (binding) => {
3943
4184
  return value;
3944
4185
  },
3945
4186
  revision: () => revision,
3946
- reset: () => reset,
3947
- publish: () => {
3948
- 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
+ }
3949
4205
  if (changed) value = next;
3950
4206
  initialized = true;
3951
4207
  },
3952
- emit: (call) => {
3953
- profile.materialized.notification();
3954
- 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);
3955
4222
  },
3956
4223
  hasConsumers: () => consumers.size > 0,
3957
- forEachConsumer: (run) => consumers.forEach(run),
3958
- 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
+ }),
3959
4229
  detachConsumer: (consumer) => consumers.delete(consumer),
3960
4230
  clear: () => {
3961
4231
  staged = unset;
@@ -3966,6 +4236,8 @@ const createValueOutput = (binding) => {
3966
4236
  release: () => {
3967
4237
  listeners.clear();
3968
4238
  consumers.clear();
4239
+ observers.clear();
4240
+ baseline = unset;
3969
4241
  staged = unset;
3970
4242
  value = next = void 0;
3971
4243
  initialized = false;
@@ -4036,7 +4308,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
4036
4308
  let active = true;
4037
4309
  try {
4038
4310
  const scopeActive = () => active;
4039
- const sources = Object.freeze(dependencies.map((output) => output.context(scopeActive)));
4311
+ const sources = Object.freeze(dependencies.map((output) => output.context(scopeActive, processor)));
4040
4312
  const outputs = begin(scopeActive, reset);
4041
4313
  if (recreate && instance) {
4042
4314
  instance.release?.();
@@ -4050,16 +4322,17 @@ const createProcessor = (scheduler, definition, dependencies) => {
4050
4322
  sources,
4051
4323
  outputs
4052
4324
  }));
4053
- return seal(reset);
4325
+ const changed = seal(reset);
4326
+ dependencies.forEach((output) => output.acknowledge(processor));
4327
+ return changed;
4054
4328
  } finally {
4055
4329
  active = false;
4056
4330
  }
4057
4331
  };
4058
- const order = scheduler.order();
4332
+ const id = scheduler.nextId();
4059
4333
  processor = {
4060
4334
  kind: "processor",
4061
- order,
4062
- name: definition.name ? `${definition.name} (${order})` : `projection-${order}`,
4335
+ name: definition.name ? `${definition.name} (${id})` : `projection-${id}`,
4063
4336
  dependencies: Object.freeze([...dependencies]),
4064
4337
  outputs: Object.freeze(bound.map((entry) => entry.state)),
4065
4338
  fault: void 0,
@@ -4186,7 +4459,9 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
4186
4459
  let initialized = false;
4187
4460
  let value;
4188
4461
  let selectedRevision = 0;
4462
+ let notified;
4189
4463
  let sourceRevision = -1;
4464
+ let notificationRevision = -1;
4190
4465
  let selection = Object.freeze({
4191
4466
  keys: /* @__PURE__ */ new Set(),
4192
4467
  all: true,
@@ -4222,8 +4497,9 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
4222
4497
  });
4223
4498
  const changed = !initialized || !equality(value, next);
4224
4499
  if (changed) {
4500
+ const candidate = unsubscribeSource && equality(notified, next) ? notified : next;
4225
4501
  if (initialized) selectedRevision++;
4226
- value = next;
4502
+ value = candidate;
4227
4503
  }
4228
4504
  selection = nextSelection;
4229
4505
  initialized = true;
@@ -4233,31 +4509,44 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
4233
4509
  };
4234
4510
  const ensureCurrent = () => {
4235
4511
  const revision = source.revision();
4236
- if (!initialized || !unsubscribeSource && sourceRevision !== revision) evaluate();
4237
- return value;
4238
- };
4239
- const onProjectionChange = (incoming) => {
4240
- let change = incoming;
4241
- if (!change && previousCollection) {
4512
+ if (!initialized) evaluate();
4513
+ else if (sourceRevision !== revision) {
4242
4514
  const current = source.current();
4243
- if (isMapLike(current)) {
4244
- change = diffCollection(previousCollection, current);
4245
- previousCollection = current;
4246
- if (!change) {
4247
- sourceRevision = source.revision();
4248
- 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
+ }
4249
4523
  }
4250
- } else previousCollection = void 0;
4524
+ }
4525
+ if (affected) evaluate();
4526
+ else {
4527
+ sourceRevision = revision;
4528
+ updateCollectionSnapshot(current);
4529
+ }
4251
4530
  }
4252
- if (!selectionAffects(selection, change)) {
4531
+ return value;
4532
+ };
4533
+ const onProjectionChange = (incoming) => {
4534
+ if (sourceRevision === notificationRevision && sourceRevision !== source.revision() && incoming && !selectionAffects(selection, incoming)) {
4253
4535
  sourceRevision = source.revision();
4254
4536
  updateCollectionSnapshot();
4255
- return;
4537
+ } else ensureCurrent();
4538
+ notificationRevision = sourceRevision;
4539
+ if (!equality(notified, value)) {
4540
+ notified = value;
4541
+ notifyProjectionListeners(listeners, "Projection selector listeners failed.");
4256
4542
  }
4257
- if (evaluate()) notifyProjectionListeners(listeners, "Projection selector listeners failed.");
4258
4543
  };
4259
4544
  const installSource = () => {
4260
- if (!unsubscribeSource) unsubscribeSource = source.subscribe(onProjectionChange);
4545
+ if (!unsubscribeSource) {
4546
+ notified = value;
4547
+ notificationRevision = sourceRevision;
4548
+ unsubscribeSource = source.observe(onProjectionChange);
4549
+ }
4261
4550
  };
4262
4551
  return Object.freeze({
4263
4552
  current: ensureCurrent,
@@ -4639,13 +4928,12 @@ const createValueBoundary = (scheduler, name, initial, equality, prepare, clearS
4639
4928
  active = false;
4640
4929
  }
4641
4930
  },
4642
- publish: state.publish,
4931
+ publish: () => state.publish(pendingReset),
4643
4932
  clear: () => {
4644
4933
  const failed = boundary.fault !== void 0;
4645
4934
  state.clear();
4646
4935
  pendingReset = false;
4647
4936
  pendingCause = void 0;
4648
- contextCause = void 0;
4649
4937
  recovering = false;
4650
4938
  clearSource(failed);
4651
4939
  },
@@ -4701,6 +4989,7 @@ const createValueBoundary = (scheduler, name, initial, equality, prepare, clearS
4701
4989
  return {
4702
4990
  producer: boundary,
4703
4991
  output,
4992
+ baseline: state.baseline,
4704
4993
  mark,
4705
4994
  fail,
4706
4995
  detach: (next) => {
@@ -4720,7 +5009,7 @@ const stageCollectionRead = (state, read, active, options) => {
4720
5009
  }
4721
5010
  evaluation.output.order(ids);
4722
5011
  } else {
4723
- const previous = state.current();
5012
+ const previous = evaluation.previous;
4724
5013
  let currentIds;
4725
5014
  const ids = () => currentIds ??= read.ids();
4726
5015
  if (options.candidates) for (const key of options.candidates) if (read.has(key)) {
@@ -4801,7 +5090,6 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
4801
5090
  state.clear();
4802
5091
  pendingReset = false;
4803
5092
  pendingCause = void 0;
4804
- contextCause = void 0;
4805
5093
  recovering = false;
4806
5094
  fullScan = false;
4807
5095
  orderMayChange = false;
@@ -4854,7 +5142,11 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
4854
5142
  fullScan ||= metadata.fullScan ?? false;
4855
5143
  orderMayChange ||= metadata.orderMayChange ?? false;
4856
5144
  if (metadata.candidates) for (const key of metadata.candidates) candidates.add(key);
4857
- if (boundary.fault) recovering = true;
5145
+ if (boundary.fault) {
5146
+ recovering = true;
5147
+ fullScan = true;
5148
+ orderMayChange = true;
5149
+ }
4858
5150
  scheduler.capture(boundary);
4859
5151
  };
4860
5152
  const fail = (cause) => {
@@ -4865,6 +5157,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
4865
5157
  return {
4866
5158
  producer: boundary,
4867
5159
  output,
5160
+ baseline: state.baseline,
4868
5161
  mark,
4869
5162
  fail,
4870
5163
  detach: (next) => {
@@ -5398,101 +5691,136 @@ const createExternalSourceRegistry = (scheduler, documents) => {
5398
5691
  //#region core/src/projection/source/input.ts
5399
5692
  const materializeInputSource = (scheduler, definition) => {
5400
5693
  const source = definition.source;
5694
+ if (source.kind !== "value-input" && source.kind !== "collection-input") throw new Error("Invalid projection input source.");
5695
+ const equal = (previous, next) => {
5696
+ const result = source.equality(previous, next);
5697
+ assertSynchronous(result);
5698
+ return result;
5699
+ };
5401
5700
  if (source.kind === "value-input") {
5402
5701
  let latest = source.initial;
5403
- const boundary = createValueBoundary(scheduler, "projection input", source.initial, source.equality, () => latest, () => void 0);
5702
+ const boundary = createValueBoundary(scheduler, "projection input", source.initial, Object.is, () => latest, () => void 0);
5703
+ boundary.detach(() => {
5704
+ latest = void 0;
5705
+ });
5404
5706
  return {
5405
5707
  ...boundary,
5406
- write: {
5708
+ input: {
5407
5709
  kind: "value",
5408
5710
  set(value) {
5409
- scheduler.assertIdle();
5410
- latest = value;
5711
+ const next = scheduler.acceptInput(() => {
5712
+ if (equal(latest, value)) return latest;
5713
+ if (source.equality === Object.is) return value;
5714
+ const baseline = boundary.baseline();
5715
+ return !Object.is(latest, baseline) && equal(baseline, value) ? baseline : value;
5716
+ });
5717
+ if (Object.is(latest, next)) return;
5718
+ latest = next;
5411
5719
  boundary.mark();
5412
5720
  scheduler.run();
5413
5721
  }
5414
5722
  }
5415
5723
  };
5416
5724
  }
5417
- if (source.kind !== "collection-input") throw new Error("Invalid projection input source.");
5418
- const values = new Map(source.initial);
5419
- const boundary = createCollectionBoundary(scheduler, "projection collection input", () => mapRead(values), definition.output.equality, () => void 0);
5725
+ const values = createCollectionState(source.initial);
5726
+ const latest = {
5727
+ get: values.get,
5728
+ has: values.has,
5729
+ ids: values.ids
5730
+ };
5731
+ const boundary = createCollectionBoundary(scheduler, "projection collection input", () => latest, Object.is, () => void 0);
5732
+ boundary.detach(() => {
5733
+ values.release();
5734
+ });
5420
5735
  return {
5421
5736
  ...boundary,
5422
- write: {
5737
+ input: {
5423
5738
  kind: "collection",
5424
5739
  update(run) {
5425
- scheduler.assertIdle();
5426
- const staged = /* @__PURE__ */ new Map();
5427
- const operations = [];
5428
- let structural = false;
5429
- let active = true;
5430
- const draft = Object.freeze({
5431
- get: (key) => {
5432
- assertScope(() => active);
5433
- const entry = staged.get(key);
5434
- return entry ? entry.present ? entry.value : void 0 : values.get(key);
5435
- },
5436
- has: (key) => {
5437
- assertScope(() => active);
5740
+ const accepted = scheduler.acceptInput(() => {
5741
+ const staged = /* @__PURE__ */ new Map();
5742
+ const appended = /* @__PURE__ */ new Set();
5743
+ let structural = false;
5744
+ let active = true;
5745
+ const has = (key) => {
5438
5746
  const entry = staged.get(key);
5439
5747
  return entry ? entry.present : values.has(key);
5440
- },
5441
- set: (key, value) => {
5442
- assertScope(() => active);
5443
- if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
5444
- const previous = staged.get(key);
5445
- structural ||= previous ? !previous.present : !values.has(key);
5446
- staged.set(key, {
5447
- present: true,
5448
- value
5449
- });
5450
- operations.push({
5451
- kind: "set",
5452
- key,
5453
- value
5454
- });
5455
- },
5456
- remove: (key) => {
5457
- assertScope(() => active);
5458
- if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
5459
- const previous = staged.get(key);
5460
- const present = previous ? previous.present : values.has(key);
5461
- structural ||= present;
5462
- staged.set(key, { present: false });
5463
- if (present) operations.push({
5464
- kind: "remove",
5465
- key
5466
- });
5748
+ };
5749
+ const draft = Object.freeze({
5750
+ get: (key) => {
5751
+ assertScope(() => active);
5752
+ const entry = staged.get(key);
5753
+ return entry ? entry.present ? entry.value : void 0 : values.get(key);
5754
+ },
5755
+ has: (key) => {
5756
+ assertScope(() => active);
5757
+ return has(key);
5758
+ },
5759
+ set: (key, value) => {
5760
+ assertScope(() => active);
5761
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
5762
+ if (!has(key)) {
5763
+ appended.add(key);
5764
+ structural = true;
5765
+ }
5766
+ staged.set(key, {
5767
+ present: true,
5768
+ value
5769
+ });
5770
+ },
5771
+ remove: (key) => {
5772
+ assertScope(() => active);
5773
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
5774
+ if (!has(key)) return;
5775
+ appended.delete(key);
5776
+ structural = true;
5777
+ staged.set(key, { present: false });
5778
+ }
5779
+ });
5780
+ try {
5781
+ assertSynchronous(run(draft));
5782
+ } finally {
5783
+ active = false;
5467
5784
  }
5468
- });
5469
- try {
5470
- assertSynchronous(run(draft));
5471
- } finally {
5472
- active = false;
5473
- }
5474
- if (!operations.length) return;
5475
- const candidates = /* @__PURE__ */ new Set();
5476
- const retained = /* @__PURE__ */ new Map();
5477
- for (const [key, entry] of staged) {
5478
- const existed = values.has(key);
5479
- if (entry.present) {
5480
- if (!existed) {
5481
- candidates.add(key);
5785
+ const baseline = source.equality === Object.is ? void 0 : boundary.baseline();
5786
+ for (const [key, entry] of staged) {
5787
+ const existed = values.has(key);
5788
+ if (!entry.present) {
5789
+ if (!existed) staged.delete(key);
5482
5790
  continue;
5483
5791
  }
5484
5792
  const previous = values.get(key);
5485
- if (source.equality(previous, entry.value)) retained.set(key, previous);
5486
- else candidates.add(key);
5487
- } else if (existed) candidates.add(key);
5488
- }
5489
- if (!candidates.size && !structural) return;
5490
- for (const operation of operations) if (operation.kind === "set") values.set(operation.key, operation.value);
5491
- else values.delete(operation.key);
5492
- for (const [key, value] of retained) if (values.has(key)) values.set(key, value);
5793
+ let next = entry.value;
5794
+ if (existed && equal(previous, next)) next = previous;
5795
+ else if (baseline?.has(key)) {
5796
+ const before = baseline.get(key);
5797
+ if ((!existed || !Object.is(previous, before)) && equal(before, next)) next = before;
5798
+ }
5799
+ if (existed && Object.is(previous, next)) staged.delete(key);
5800
+ else if (!Object.is(entry.value, next)) staged.set(key, {
5801
+ present: true,
5802
+ value: next
5803
+ });
5804
+ }
5805
+ const beforeOrder = values.ids();
5806
+ let order = beforeOrder;
5807
+ if (structural) {
5808
+ const next = [];
5809
+ for (const key of beforeOrder) if (has(key) && !appended.has(key)) next.push(key);
5810
+ for (const key of appended) next.push(key);
5811
+ if (!sameArray(beforeOrder, next)) order = Object.freeze(next);
5812
+ }
5813
+ return {
5814
+ staged,
5815
+ order,
5816
+ structural: order !== beforeOrder
5817
+ };
5818
+ });
5819
+ if (!accepted.staged.size && !accepted.structural) return;
5820
+ values.install(accepted.staged, accepted.order, false);
5493
5821
  boundary.mark({
5494
- candidates,
5495
- orderMayChange: structural
5822
+ candidates: accepted.staged.keys(),
5823
+ orderMayChange: accepted.structural
5496
5824
  });
5497
5825
  scheduler.run();
5498
5826
  }
@@ -5542,6 +5870,11 @@ const createProjectionItems = (source, checkOwner) => {
5542
5870
  const waiting = /* @__PURE__ */ new Map();
5543
5871
  let keysValue = Object.freeze([...source.current().keys()]);
5544
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();
5545
5878
  const keyListeners = /* @__PURE__ */ new Set();
5546
5879
  const check = () => {
5547
5880
  if (disposed) throw new ProjectionDisposedError();
@@ -5577,7 +5910,7 @@ const createProjectionItems = (source, checkOwner) => {
5577
5910
  }
5578
5911
  state.value = value;
5579
5912
  state.revision++;
5580
- notify(state.listeners);
5913
+ changedStates.add(state);
5581
5914
  };
5582
5915
  const attachActive = (state, value) => {
5583
5916
  if (state.ended) return;
@@ -5620,10 +5953,12 @@ const createProjectionItems = (source, checkOwner) => {
5620
5953
  if (extra) for (const state of extra) publishState(state, value);
5621
5954
  };
5622
5955
  const updateKeys = (current) => {
5956
+ const revision = source.revision();
5957
+ if (keysSourceRevision === revision) return;
5958
+ keysSourceRevision = revision;
5623
5959
  if (iterableMatchesArray(current.keys(), keysValue)) return;
5624
- keysValue = Object.freeze([...current.keys()]);
5960
+ keysValue = iterableMatchesArray(current.keys(), notifiedKeys) ? notifiedKeys : Object.freeze([...current.keys()]);
5625
5961
  keysRevision++;
5626
- notify(keyListeners);
5627
5962
  };
5628
5963
  const createItem = (key, initial) => {
5629
5964
  const state = {
@@ -5632,12 +5967,15 @@ const createProjectionItems = (source, checkOwner) => {
5632
5967
  tracked: initial !== void 0,
5633
5968
  ended: false,
5634
5969
  value: initial?.value,
5970
+ notified: initial?.value,
5635
5971
  revision: 0
5636
5972
  };
5637
5973
  const ensureObserved = () => {
5638
- if (state.ended || state.tracked) return;
5974
+ if (state.ended) return;
5639
5975
  const current = source.current();
5640
- 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));
5641
5979
  };
5642
5980
  state.readable = Object.freeze({
5643
5981
  current: () => {
@@ -5672,25 +6010,45 @@ const createProjectionItems = (source, checkOwner) => {
5672
6010
  const onChange = (change) => {
5673
6011
  check();
5674
6012
  const current = source.current();
5675
- if (!change || change.kind === "reset") {
5676
- reconcile(current);
5677
- flushNotifications();
5678
- 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);
5679
6039
  }
5680
- for (const entry of change.removed) terminateKey(entry.key);
5681
- for (const entry of change.added) activateWaiting(entry.key, entry.after);
5682
- for (const entry of change.updated) updateKey(entry.key, entry.after);
5683
- if (change.added.length || change.removed.length || change.order) updateKeys(current);
5684
6040
  flushNotifications();
5685
6041
  };
5686
- const stop = source.subscribe(onChange);
6042
+ const stop = source.observe(onChange);
5687
6043
  const keys = Object.freeze({
5688
6044
  current: () => {
5689
6045
  check();
6046
+ updateKeys(source.current());
5690
6047
  return keysValue;
5691
6048
  },
5692
6049
  revision: () => {
5693
6050
  check();
6051
+ updateKeys(source.current());
5694
6052
  return keysRevision;
5695
6053
  },
5696
6054
  subscribe: (listener) => {
@@ -5704,11 +6062,12 @@ const createProjectionItems = (source, checkOwner) => {
5704
6062
  keys,
5705
6063
  get: (key) => {
5706
6064
  check();
6065
+ const current = source.current();
6066
+ if (source.revision() !== settledRevision) pendingKeys.add(key);
5707
6067
  const canonical = active.get(key);
5708
6068
  if (canonical) return canonical.readable;
5709
6069
  const pending = waiting.get(key)?.values().next().value;
5710
6070
  if (pending) return pending.readable;
5711
- const current = source.current();
5712
6071
  if (current.has(key)) {
5713
6072
  const state = createItem(key, {
5714
6073
  present: true,
@@ -5734,6 +6093,8 @@ const createProjectionItems = (source, checkOwner) => {
5734
6093
  followers.clear();
5735
6094
  waiting.clear();
5736
6095
  notifications.length = 0;
6096
+ pendingKeys.clear();
6097
+ changedStates.clear();
5737
6098
  }
5738
6099
  };
5739
6100
  };
@@ -5789,11 +6150,12 @@ const createProjectionRuntime = (options) => {
5789
6150
  result = Object.freeze({
5790
6151
  record: source.producer,
5791
6152
  outputs: Object.freeze([source.output]),
5792
- ...source.write ? { write: source.write } : {}
6153
+ ...source.input ? { input: source.input } : {}
5793
6154
  });
5794
6155
  } else {
5795
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.");
5796
6157
  const dependencies = definition.dependencies.map((dependency) => resolveOutput(dependency, requester));
6158
+ dependencies.forEach((output) => scheduler.ensureCurrent(output));
5797
6159
  const processor = createProcessor(scheduler, definition, Object.freeze(dependencies));
5798
6160
  result = Object.freeze({
5799
6161
  record: processor,
@@ -5817,6 +6179,7 @@ const createProjectionRuntime = (options) => {
5817
6179
  if (output.owner.fault) throw output.owner.fault;
5818
6180
  };
5819
6181
  const publicCurrent = (output) => {
6182
+ scheduler.ensureCurrent(output);
5820
6183
  assertOutput(output);
5821
6184
  const current = output.current();
5822
6185
  const revision = output.revision();
@@ -5848,10 +6211,12 @@ const createProjectionRuntime = (options) => {
5848
6211
  kind: output.kind,
5849
6212
  current: () => readProjection(projection, requester),
5850
6213
  revision: () => {
6214
+ scheduler.ensureCurrent(output);
5851
6215
  assertOutput(output);
5852
6216
  return output.revision();
5853
6217
  },
5854
- subscribe: output.subscribe
6218
+ subscribe: output.subscribe,
6219
+ observe: output.observe
5855
6220
  });
5856
6221
  };
5857
6222
  const projectionItems = (projection, requester) => {
@@ -5862,6 +6227,7 @@ const createProjectionRuntime = (options) => {
5862
6227
  if (existing) return existing.items;
5863
6228
  const controller = createProjectionItems(readableSource(projection, requester), () => {
5864
6229
  if (requester && !requester.active) throw new ProjectionDisposedError();
6230
+ scheduler.ensureCurrent(output);
5865
6231
  assertOutput(output);
5866
6232
  });
5867
6233
  registry.set(output, controller);
@@ -5874,16 +6240,18 @@ const createProjectionRuntime = (options) => {
5874
6240
  function selectProjection(projection, selector, equality) {
5875
6241
  return makeReadable(projection, selector, equality);
5876
6242
  }
5877
- const inputWrite = (target, requester) => {
6243
+ const inputAccess = (target, requester) => {
6244
+ scheduler.assertIdle();
6245
+ if (requester && !requester.active) throw new ProjectionDisposedError();
5878
6246
  const ref = projectionRef(target);
5879
6247
  if (ref.output !== 0 || ref.producer.kind !== "source") throw new TypeError("Projection is not an input.");
5880
6248
  if (ref.producer.source.kind !== "value-input" && ref.producer.source.kind !== "collection-input") throw new TypeError("Projection is not an input.");
5881
- const write = materializeProducer(ref.producer, requester).write;
5882
- if (!write) throw new TypeError("Projection input is not writable.");
5883
- return write;
6249
+ const access = materializeProducer(ref.producer, requester).input;
6250
+ if (!access) throw new TypeError("Projection input is not writable.");
6251
+ return access;
5884
6252
  };
5885
6253
  const updateInput = (target, valueOrRun, requester) => {
5886
- const write = inputWrite(target, requester);
6254
+ const write = inputAccess(target, requester);
5887
6255
  if (write.kind === "value") {
5888
6256
  write.set(valueOrRun);
5889
6257
  return;