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