doxum 0.1.40 → 0.1.42

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,6 +1,6 @@
1
- import { A as ownProjections, C as snapshotProjectionValues, D as defineProcessor, E as snapshotCollectionView, M as projectionRef, N as profile, O as defineSource, S as compileProjectionDependencies, T as mapRead, _ as assertSynchronous, a as collectionChangeTouchesKey, b as ProjectionError, c as assertKeyedProjection, d as outputKind, f as createKeyRelation, g as assertScope, h as snapshotArray, i as collectionChangeIntersects, j as producerOf, k as isProjection, l as compileKeyedDependencies, m as sameArray, n as keyedAbsent, o as collectionHasStructuralChange, p as iterableMatchesArray, s as createCollectionChange, t as createKeyedTransform, u as createKeyedDependencyRuntime, v as createScheduler, w as collectionView, x as collectKeyedInvalidation, y as ProjectionDisposedError } from "./transform--WCn6_v0.js";
1
+ import { A as ownProjections, C as snapshotProjectionValues, D as defineProcessor, E as snapshotCollectionView, M as projectionRef, N as profile, O as defineSource, S as compileProjectionDependencies, T as mapRead, _ as assertSynchronous, a as collectionChangeTouchesKey, b as ProjectionError, c as assertKeyedProjection, d as outputKind, f as createKeyRelation, g as assertScope, h as snapshotArray, i as collectionChangeIntersects, j as producerOf, k as isProjection, l as compileKeyedDependencies, m as sameArray, n as keyedAbsent, o as collectionHasStructuralChange, p as iterableMatchesArray, s as createCollectionChange, t as createKeyedTransform, u as createKeyedDependencyRuntime, v as createScheduler, w as collectionView, x as collectKeyedInvalidation, y as ProjectionDisposedError } from "./transform-BvDYPUMm.js";
2
2
  import { n as isPlainObject, r as isRecord, t as installOwn } from "./record-B2yRhYsc.js";
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";
3
+ import { _ as validate, c as addListener, d as fail, f as invalidValue, g as parent, h as is, i as contextOf, l as AddressIndex, m as contains$1, o as decodeChanges, p as children, r as bindContext, s as sealChanges, t as assertRuntimeWritable, u as MutationRejected } from "./driver-B7Hu6D0u.js";
4
4
  //#region core/src/schema/model.ts
5
5
  const schemaNodeOf = (schema) => schema;
6
6
  const collectionEntryNode = (node) => node.kind === "map" || node.kind === "table" || node.kind === "list" ? node.value : void 0;
@@ -651,7 +651,7 @@ function remove$2(session, container, ids) {
651
651
  for (const id of removed) session.writeMember(container, id, void 0, "remove");
652
652
  table.ids = table.ids.filter((id) => !removed.has(id));
653
653
  }
654
- function replace$3(session, container, id, value) {
654
+ function replace$4(session, container, id, value) {
655
655
  const { at, node, value: tableValue } = container;
656
656
  if (node.kind !== "table") return fail(at, "invalid-collection", "Expected a table.");
657
657
  const table = tableValue;
@@ -674,7 +674,7 @@ function insert$1(session, container, value, position) {
674
674
  insert$2(items, index(keys$1, position), value);
675
675
  session.invalidate();
676
676
  }
677
- function replace$2(session, container, id, value) {
677
+ function replace$3(session, container, id, value) {
678
678
  const { at, node, value: items } = container;
679
679
  if (node.kind !== "list") return fail(at, "invalid-list-key", "Expected a list.");
680
680
  const index = indexedKeys(items, node.keyOf).index(id);
@@ -720,7 +720,7 @@ const insert = (session, container, id, value, position) => {
720
720
  else tree.rootId = id;
721
721
  session.invalidate();
722
722
  };
723
- const replace$1 = (session, container, id, value) => {
723
+ const replace$2 = (session, container, id, value) => {
724
724
  const { at, node: schema, value: tree } = container;
725
725
  const entry = node$1(tree, id);
726
726
  if (!entry) return fail(at, "missing-tree-node", "Tree node does not exist.");
@@ -1179,7 +1179,7 @@ const createAccess = (context, initial = []) => {
1179
1179
  const [valueOrId, value] = args;
1180
1180
  if (args.length === 1) return replaceCollection(target, node, valueOrId);
1181
1181
  const writable = writableCollection(target, node);
1182
- replace$3(context.session, writable, valueOrId, value);
1182
+ replace$4(context.session, writable, valueOrId, value);
1183
1183
  };
1184
1184
  return orderedMethod(target, property, node);
1185
1185
  };
@@ -1196,7 +1196,7 @@ const createAccess = (context, initial = []) => {
1196
1196
  const [valueOrId, value] = args;
1197
1197
  if (args.length === 1) return replaceCollection(target, node, valueOrId);
1198
1198
  const writable = writableCollection(target, node);
1199
- replace$2(context.session, writable, valueOrId, value);
1199
+ replace$3(context.session, writable, valueOrId, value);
1200
1200
  };
1201
1201
  return orderedMethod(target, property, node);
1202
1202
  };
@@ -1218,7 +1218,7 @@ const createAccess = (context, initial = []) => {
1218
1218
  const [valueOrId, value] = args;
1219
1219
  if (args.length === 1) return replaceCollection(target, node, valueOrId);
1220
1220
  const writable = writableTree(target, node);
1221
- replace$1(context.session, writable, valueOrId, value);
1221
+ replace$2(context.session, writable, valueOrId, value);
1222
1222
  };
1223
1223
  const current = () => collectionValue(target, node);
1224
1224
  if (property === "rootId") return () => {
@@ -1622,10 +1622,7 @@ const createHistory = (input) => {
1622
1622
  revision: () => revision,
1623
1623
  subscribe: (listener) => {
1624
1624
  if (disposed) throw new DocumentDisposedError();
1625
- listeners.add(listener);
1626
- return () => {
1627
- listeners.delete(listener);
1628
- };
1625
+ return addListener(listeners, listener);
1629
1626
  },
1630
1627
  undo: () => {
1631
1628
  input.assertIdle();
@@ -2296,6 +2293,7 @@ const addRoot = (notification, listener) => {
2296
2293
  };
2297
2294
  notification.root.add(entry);
2298
2295
  return () => {
2296
+ if (!entry.active) return;
2299
2297
  entry.active = false;
2300
2298
  if (!notification.notifying) notification.root.delete(entry);
2301
2299
  else notification.cleanups.add(() => {
@@ -2669,7 +2667,9 @@ const select = (document, selector, equality = Object.is) => {
2669
2667
  };
2670
2668
  };
2671
2669
  const installSubscription = () => {
2672
- unsubscribeDocument?.();
2670
+ const stop = unsubscribeDocument;
2671
+ unsubscribeDocument = void 0;
2672
+ stop?.();
2673
2673
  unsubscribeDocument = dependencies.length > 0 ? context.notifications.subscribeTargets(dependencies, () => {
2674
2674
  const result = evaluate();
2675
2675
  if (result.rebound) installSubscription();
@@ -2689,15 +2689,22 @@ const select = (document, selector, equality = Object.is) => {
2689
2689
  },
2690
2690
  subscribe: (listener) => {
2691
2691
  ensureCurrent();
2692
- listeners.add(listener);
2693
- if (listeners.size === 1) installSubscription();
2694
- return () => {
2695
- listeners.delete(listener);
2692
+ const unsubscribe = addListener(listeners, listener, () => {
2696
2693
  if (!listeners.size) {
2697
- unsubscribeDocument?.();
2694
+ const stop = unsubscribeDocument;
2698
2695
  unsubscribeDocument = void 0;
2696
+ stop?.();
2699
2697
  }
2700
- };
2698
+ });
2699
+ try {
2700
+ if (listeners.size === 1) installSubscription();
2701
+ } catch (error) {
2702
+ try {
2703
+ unsubscribe();
2704
+ } catch {}
2705
+ throw error;
2706
+ }
2707
+ return unsubscribe;
2701
2708
  }
2702
2709
  });
2703
2710
  };
@@ -3687,6 +3694,33 @@ function createValueDerive(dependencies, compute, equality = Object.is) {
3687
3694
  }
3688
3695
  const derive = Object.assign(createValueDerive, { keyed: keyedDerive });
3689
3696
  //#endregion
3697
+ //#region core/src/projection/cleanup.ts
3698
+ /** Owner-local, cancellable LIFO callbacks. Lifecycle admission stays with the owner. */
3699
+ const createCleanupStack = () => {
3700
+ const entries = /* @__PURE__ */ new Set();
3701
+ return {
3702
+ add(callback) {
3703
+ const entry = () => {
3704
+ assertSynchronous(callback());
3705
+ };
3706
+ entries.add(entry);
3707
+ return () => {
3708
+ entries.delete(entry);
3709
+ };
3710
+ },
3711
+ drain(failures) {
3712
+ for (const entry of [...entries].reverse()) {
3713
+ if (!entries.delete(entry)) continue;
3714
+ try {
3715
+ entry();
3716
+ } catch (error) {
3717
+ failures.push(error);
3718
+ }
3719
+ }
3720
+ }
3721
+ };
3722
+ };
3723
+ //#endregion
3690
3724
  //#region core/src/projection/collection/changes.ts
3691
3725
  /** Net transitions for one consumer's unconsumed interval; bounded by touched keys. */
3692
3726
  const createCollectionChanges = () => {
@@ -3797,49 +3831,109 @@ const node = (key, value, left, right) => {
3797
3831
  height: Math.max(height(left), height(right)) + 1
3798
3832
  });
3799
3833
  };
3800
- const rotateLeft = (current) => {
3801
- const right = current.right;
3802
- return node(right.key, right.value, node(current.key, current.value, current.left, right.left), right.right);
3803
- };
3804
- const rotateRight = (current) => {
3805
- const left = current.left;
3806
- return node(left.key, left.value, left.left, node(current.key, current.value, left.right, current.right));
3807
- };
3808
- const rebalance = (current) => {
3809
- const balance = height(current.left) - height(current.right);
3810
- if (balance > 1) {
3811
- if (height(current.left.left) < height(current.left.right)) return rotateRight(node(current.key, current.value, rotateLeft(current.left), current.right));
3812
- return rotateRight(current);
3813
- }
3814
- if (balance < -1) {
3815
- if (height(current.right.right) < height(current.right.left)) return rotateLeft(node(current.key, current.value, current.left, rotateRight(current.right)));
3816
- return rotateLeft(current);
3817
- }
3818
- return current;
3819
- };
3820
- const setNode = (current, key, value) => {
3821
- if (!current) return node(key, value);
3822
- if (key === current.key) return node(key, value, current.left, current.right);
3823
- if (key < current.key) return rebalance(node(current.key, current.value, setNode(current.left, key, value), current.right));
3824
- return rebalance(node(current.key, current.value, current.left, setNode(current.right, key, value)));
3825
- };
3826
- const minNode = (current) => current.left ? minNode(current.left) : current;
3827
- const removeNode = (current, key) => {
3828
- if (!current) return void 0;
3829
- if (key < current.key) return rebalance(node(current.key, current.value, removeNode(current.left, key), current.right));
3830
- if (key > current.key) return rebalance(node(current.key, current.value, current.left, removeNode(current.right, key)));
3834
+ /** Local repair after a join descent or a single deletion (height difference at most two). */
3835
+ const balance = (key, value, left, right) => {
3836
+ if (height(left) > height(right) + 1) {
3837
+ const heavy = left;
3838
+ if (height(heavy.left) >= height(heavy.right)) return node(heavy.key, heavy.value, heavy.left, node(key, value, heavy.right, right));
3839
+ const middle = heavy.right;
3840
+ return node(middle.key, middle.value, node(heavy.key, heavy.value, heavy.left, middle.left), node(key, value, middle.right, right));
3841
+ }
3842
+ if (height(right) > height(left) + 1) {
3843
+ const heavy = right;
3844
+ if (height(heavy.right) >= height(heavy.left)) return node(heavy.key, heavy.value, node(key, value, left, heavy.left), heavy.right);
3845
+ const middle = heavy.left;
3846
+ return node(middle.key, middle.value, node(key, value, left, middle.left), node(heavy.key, heavy.value, middle.right, heavy.right));
3847
+ }
3848
+ return node(key, value, left, right);
3849
+ };
3850
+ /** All left keys precede key, all right keys follow it; heights may differ arbitrarily. */
3851
+ const join = (key, value, left, right) => {
3852
+ if (height(left) > height(right) + 1) {
3853
+ profile.collectionIndex.visit();
3854
+ return balance(left.key, left.value, left.left, join(key, value, left.right, right));
3855
+ }
3856
+ if (height(right) > height(left) + 1) {
3857
+ profile.collectionIndex.visit();
3858
+ return balance(right.key, right.value, join(key, value, left, right.left), right.right);
3859
+ }
3860
+ return node(key, value, left, right);
3861
+ };
3862
+ const removeMin = (current) => {
3863
+ profile.collectionIndex.visit();
3831
3864
  if (!current.left) return current.right;
3832
- if (!current.right) return current.left;
3833
- const next = minNode(current.right);
3834
- return rebalance(node(next.key, next.value, current.left, removeNode(current.right, next.key)));
3865
+ return balance(current.key, current.value, removeMin(current.left), current.right);
3866
+ };
3867
+ const concat = (left, right) => {
3868
+ if (!left) return right;
3869
+ if (!right) return left;
3870
+ let first = right;
3871
+ while (first.left) {
3872
+ profile.collectionIndex.visit();
3873
+ first = first.left;
3874
+ }
3875
+ return join(first.key, first.value, left, removeMin(right));
3876
+ };
3877
+ const replace$1 = (current, entry, left, right) => {
3878
+ if (entry && !entry.present) return concat(left, right);
3879
+ const value = entry ? entry.value : current.value;
3880
+ if (left === current.left && right === current.right && Object.is(value, current.value)) return current;
3881
+ return join(current.key, value, left, right);
3882
+ };
3883
+ const edit = (current, key, entry) => {
3884
+ if (!current) return entry.present ? node(key, entry.value) : void 0;
3885
+ profile.collectionIndex.visit();
3886
+ if (key === current.key) return replace$1(current, entry, current.left, current.right);
3887
+ if (key < current.key) {
3888
+ const left = edit(current.left, key, entry);
3889
+ return left === current.left ? current : balance(current.key, current.value, left, current.right);
3890
+ }
3891
+ const right = edit(current.right, key, entry);
3892
+ return right === current.right ? current : balance(current.key, current.value, current.left, right);
3893
+ };
3894
+ /** Builds a new subtree in linear time, ignoring removals of absent keys. */
3895
+ const buildChanges = (keys, changes, start, end) => {
3896
+ let count = 0;
3897
+ for (let i = start; i < end; i++) if (changes.get(keys[i]).present) count++;
3898
+ let cursor = start;
3899
+ const build = (size) => {
3900
+ if (!size) return void 0;
3901
+ const leftSize = size >> 1;
3902
+ const left = build(leftSize);
3903
+ let key;
3904
+ let entry;
3905
+ do {
3906
+ key = keys[cursor++];
3907
+ entry = changes.get(key);
3908
+ } while (!entry.present);
3909
+ return node(key, entry.value, left, build(size - leftSize - 1));
3910
+ };
3911
+ return build(count);
3912
+ };
3913
+ const applyChanges = (current, keys, changes, start, end) => {
3914
+ if (start === end) return current;
3915
+ if (end - start === 1) return edit(current, keys[start], changes.get(keys[start]));
3916
+ if (!current) return buildChanges(keys, changes, start, end);
3917
+ profile.collectionIndex.visit();
3918
+ let low = start;
3919
+ let high = end;
3920
+ while (low < high) {
3921
+ const middle = low + high >>> 1;
3922
+ if (keys[middle] < current.key) low = middle + 1;
3923
+ else high = middle;
3924
+ }
3925
+ const matched = low < end && keys[low] === current.key;
3926
+ const left = applyChanges(current.left, keys, changes, start, low);
3927
+ const right = applyChanges(current.right, keys, changes, matched ? low + 1 : low, end);
3928
+ return replace$1(current, matched ? changes.get(current.key) : void 0, left, right);
3835
3929
  };
3836
3930
  const fromSorted = (entries, start = 0, end = entries.length) => {
3837
3931
  if (start >= end) return void 0;
3838
- const middle = start + end >> 1;
3932
+ const middle = start + end >>> 1;
3839
3933
  const [key, value] = entries[middle];
3840
3934
  return node(key, value, fromSorted(entries, start, middle), fromSorted(entries, middle + 1, end));
3841
3935
  };
3842
- /** Immutable keyed lookup used to keep published collection snapshots durable across revisions. */
3936
+ /** Immutable keyed lookup used to keep collection snapshots durable across revisions. */
3843
3937
  var PersistentKeyedIndex = class PersistentKeyedIndex {
3844
3938
  constructor(root) {
3845
3939
  this.root = root;
@@ -3847,6 +3941,7 @@ var PersistentKeyedIndex = class PersistentKeyedIndex {
3847
3941
  static empty() {
3848
3942
  return new PersistentKeyedIndex();
3849
3943
  }
3944
+ /** The input must contain unique keys. Formal collection order is owned separately. */
3850
3945
  static from(entries) {
3851
3946
  const sorted = [...entries].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
3852
3947
  profile.collectionIndex.build(sorted.length);
@@ -3867,11 +3962,19 @@ var PersistentKeyedIndex = class PersistentKeyedIndex {
3867
3962
  }
3868
3963
  return false;
3869
3964
  }
3870
- set(key, value) {
3871
- return new PersistentKeyedIndex(setNode(this.root, key, value));
3872
- }
3873
- remove(key) {
3874
- return new PersistentKeyedIndex(removeNode(this.root, key));
3965
+ /** Applies accepted final entry intents together; never calls user equality or changes old roots. */
3966
+ apply(changes) {
3967
+ if (!changes.size) return this;
3968
+ profile.collectionIndex.batch(changes.size > 1 ? changes.size : 0);
3969
+ let root;
3970
+ if (changes.size === 1) {
3971
+ const [key, entry] = changes.entries().next().value;
3972
+ root = edit(this.root, key, entry);
3973
+ } else {
3974
+ const keys = [...changes.keys()].sort();
3975
+ root = applyChanges(this.root, keys, changes, 0, keys.length);
3976
+ }
3977
+ return root === this.root ? this : new PersistentKeyedIndex(root);
3875
3978
  }
3876
3979
  };
3877
3980
  //#endregion
@@ -3909,10 +4012,11 @@ const createCollectionState = (initial) => {
3909
4012
  values.clear();
3910
4013
  for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
3911
4014
  if (index) index = PersistentKeyedIndex.from(values);
3912
- } else for (const [key, entry] of staged) {
3913
- if (index) index = entry.present ? index.set(key, entry.value) : index.remove(key);
3914
- if (entry.present) values.set(key, entry.value);
4015
+ } else {
4016
+ const nextIndex = index?.apply(staged);
4017
+ for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
3915
4018
  else values.delete(key);
4019
+ index = nextIndex;
3916
4020
  }
3917
4021
  ids = nextIds;
3918
4022
  },
@@ -4138,8 +4242,7 @@ const createCollectionOutput = (binding) => {
4138
4242
  },
4139
4243
  observe: (listener) => {
4140
4244
  binding.check();
4141
- observers.add(listener);
4142
- return () => observers.delete(listener);
4245
+ return addListener(observers, listener);
4143
4246
  },
4144
4247
  hasConsumers: () => consumers.size > 0,
4145
4248
  forEachConsumer: (run) => consumers.forEach((_pending, consumer) => run(consumer)),
@@ -4171,8 +4274,7 @@ const createCollectionOutput = (binding) => {
4171
4274
  },
4172
4275
  subscribe: (listener) => {
4173
4276
  binding.check();
4174
- listeners.add(listener);
4175
- return () => listeners.delete(listener);
4277
+ return addListener(listeners, listener);
4176
4278
  }
4177
4279
  };
4178
4280
  };
@@ -4287,8 +4389,7 @@ const createValueOutput = (binding) => {
4287
4389
  },
4288
4390
  observe: (listener) => {
4289
4391
  binding.check();
4290
- observers.add(listener);
4291
- return () => observers.delete(listener);
4392
+ return addListener(observers, listener);
4292
4393
  },
4293
4394
  hasConsumers: () => consumers.size > 0,
4294
4395
  forEachConsumer: (run) => consumers.forEach((_revision, consumer) => run(consumer)),
@@ -4317,8 +4418,7 @@ const createValueOutput = (binding) => {
4317
4418
  },
4318
4419
  subscribe: (listener) => {
4319
4420
  binding.check();
4320
- listeners.add(listener);
4321
- return () => listeners.delete(listener);
4421
+ return addListener(listeners, listener);
4322
4422
  }
4323
4423
  };
4324
4424
  };
@@ -4522,7 +4622,7 @@ const selectionAffects = (selection, change) => {
4522
4622
  const createDirectReadable = (source) => Object.freeze({
4523
4623
  current: source.current,
4524
4624
  revision: source.revision,
4525
- subscribe: (listener) => source.subscribe(() => listener())
4625
+ subscribe: source.subscribe
4526
4626
  });
4527
4627
  /** Consumer-side selector tracking; it never mutates processor dependencies. */
4528
4628
  const createSelectorReadable = (source, selector, equality = Object.is) => {
@@ -4626,15 +4726,22 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
4626
4726
  },
4627
4727
  subscribe: (listener) => {
4628
4728
  ensureCurrent();
4629
- listeners.add(listener);
4630
- installSource();
4631
- return () => {
4632
- listeners.delete(listener);
4633
- if (!listeners.size && unsubscribeSource) {
4634
- unsubscribeSource();
4729
+ const unsubscribe = addListener(listeners, listener, () => {
4730
+ if (!listeners.size) {
4731
+ const stop = unsubscribeSource;
4635
4732
  unsubscribeSource = void 0;
4733
+ stop?.();
4636
4734
  }
4637
- };
4735
+ });
4736
+ try {
4737
+ installSource();
4738
+ } catch (error) {
4739
+ try {
4740
+ unsubscribe();
4741
+ } catch {}
4742
+ throw error;
4743
+ }
4744
+ return unsubscribe;
4638
4745
  }
4639
4746
  });
4640
4747
  };
@@ -5932,7 +6039,7 @@ const createSourceRegistry = (scheduler) => {
5932
6039
  //#endregion
5933
6040
  //#region core/src/projection/readable/keyed.ts
5934
6041
  /** Runtime-owned keyed consumer family over one materialized collection output. */
5935
- const createProjectionItems = (source, checkOwner) => {
6042
+ const createProjectionItems = (source, checkOwner, checkSubscription) => {
5936
6043
  if (source.kind !== "collection") throw new TypeError("Projection items require a keyed collection projection.");
5937
6044
  let disposed = false;
5938
6045
  const active = /* @__PURE__ */ new Map();
@@ -6061,12 +6168,12 @@ const createProjectionItems = (source, checkOwner) => {
6061
6168
  subscribe: (listener) => {
6062
6169
  check();
6063
6170
  ensureObserved();
6064
- state.listeners.add(listener);
6065
- if (!state.tracked && !state.ended) registerWaiting(state);
6066
- return () => {
6067
- state.listeners.delete(listener);
6171
+ checkSubscription();
6172
+ const unsubscribe = addListener(state.listeners, listener, () => {
6068
6173
  if (!state.listeners.size && !state.tracked) unregisterWaiting(state);
6069
- };
6174
+ });
6175
+ if (!state.tracked && !state.ended) registerWaiting(state);
6176
+ return unsubscribe;
6070
6177
  }
6071
6178
  });
6072
6179
  return state;
@@ -6123,8 +6230,8 @@ const createProjectionItems = (source, checkOwner) => {
6123
6230
  },
6124
6231
  subscribe: (listener) => {
6125
6232
  check();
6126
- keyListeners.add(listener);
6127
- return () => keyListeners.delete(listener);
6233
+ checkSubscription();
6234
+ return addListener(keyListeners, listener);
6128
6235
  }
6129
6236
  });
6130
6237
  return {
@@ -6132,6 +6239,7 @@ const createProjectionItems = (source, checkOwner) => {
6132
6239
  keys,
6133
6240
  get: (key) => {
6134
6241
  check();
6242
+ checkSubscription();
6135
6243
  const current = source.current();
6136
6244
  if (source.revision() !== settledRevision) pendingKeys.add(key);
6137
6245
  const canonical = active.get(key);
@@ -6176,6 +6284,7 @@ const createProjectionRuntime = (options) => {
6176
6284
  const materialized = /* @__PURE__ */ new WeakMap();
6177
6285
  const publicViews = /* @__PURE__ */ new WeakMap();
6178
6286
  const scopes = /* @__PURE__ */ new Set();
6287
+ const cleanups = createCleanupStack();
6179
6288
  const rootItems = /* @__PURE__ */ new Map();
6180
6289
  const disposeItemControllers = (registry, failures) => {
6181
6290
  const controllers = [...registry.values()];
@@ -6205,7 +6314,7 @@ const createProjectionRuntime = (options) => {
6205
6314
  const owner = definition.owner;
6206
6315
  if (owner === void 0) return;
6207
6316
  if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
6208
- if (!requester?.active) throw new ProjectionDisposedError();
6317
+ if (requester?.status === "disposed") throw new ProjectionDisposedError();
6209
6318
  };
6210
6319
  const rememberScoped = (definition, requester) => {
6211
6320
  if (requester && definition.owner === requester) requester.materialized.push(definition);
@@ -6285,25 +6394,33 @@ const createProjectionRuntime = (options) => {
6285
6394
  assertOutput(output);
6286
6395
  return output.revision();
6287
6396
  },
6288
- subscribe: output.subscribe,
6289
- observe: output.observe
6397
+ subscribe: (listener) => {
6398
+ scheduler.assertCommand();
6399
+ return output.subscribe(listener);
6400
+ },
6401
+ observe: (listener) => {
6402
+ scheduler.assertCommand();
6403
+ return output.observe(listener);
6404
+ }
6290
6405
  });
6291
6406
  };
6292
6407
  const projectionItems = (projection, requester) => {
6408
+ scheduler.assertCommand();
6293
6409
  const output = resolveOutput(projection, requester);
6294
6410
  if (output.kind !== "collection") throw new TypeError("Projection items require a keyed collection projection.");
6295
6411
  const registry = requester?.items ?? rootItems;
6296
6412
  const existing = registry.get(output);
6297
6413
  if (existing) return existing.items;
6298
6414
  const controller = createProjectionItems(readableSource(projection, requester), () => {
6299
- if (requester && !requester.active) throw new ProjectionDisposedError();
6415
+ if (requester?.status === "disposed") throw new ProjectionDisposedError();
6300
6416
  scheduler.ensureCurrent(output);
6301
6417
  assertOutput(output);
6302
- });
6418
+ }, scheduler.assertCommand);
6303
6419
  registry.set(output, controller);
6304
6420
  return controller.items;
6305
6421
  };
6306
6422
  const makeReadable = (projection, selector, equality, requester) => {
6423
+ scheduler.assertCommand();
6307
6424
  const source = readableSource(projection, requester);
6308
6425
  return selector ? createSelectorReadable(source, selector, equality) : createDirectReadable(source);
6309
6426
  };
@@ -6311,8 +6428,8 @@ const createProjectionRuntime = (options) => {
6311
6428
  return makeReadable(projection, selector, equality);
6312
6429
  }
6313
6430
  const inputAccess = (target, requester) => {
6314
- scheduler.assertIdle();
6315
- if (requester && !requester.active) throw new ProjectionDisposedError();
6431
+ scheduler.assertCommand();
6432
+ if (requester?.status === "disposed") throw new ProjectionDisposedError();
6316
6433
  const ref = projectionRef(target);
6317
6434
  if (ref.output !== 0 || ref.producer.kind !== "source") throw new TypeError("Projection is not an input.");
6318
6435
  if (ref.producer.source.kind !== "value-input" && ref.producer.source.kind !== "collection-input") throw new TypeError("Projection is not an input.");
@@ -6333,11 +6450,9 @@ const createProjectionRuntime = (options) => {
6333
6450
  updateInput(target, valueOrRun);
6334
6451
  }
6335
6452
  const batch = (run, options) => scheduler.batch(run, options);
6336
- const disposeScope = (state) => {
6337
- if (!state.active) return;
6338
- scheduler.assertIdle();
6339
- state.active = false;
6340
- const failures = [];
6453
+ const releaseScope = (state, failures) => {
6454
+ state.status = "disposed";
6455
+ scopes.delete(state);
6341
6456
  disposeScopeConsumers(state, failures);
6342
6457
  for (let index = state.materialized.length - 1; index >= 0; index--) {
6343
6458
  const definition = state.materialized[index];
@@ -6352,24 +6467,38 @@ const createProjectionRuntime = (options) => {
6352
6467
  }
6353
6468
  }
6354
6469
  state.materialized.length = 0;
6355
- scopes.delete(state);
6470
+ };
6471
+ const disposeScope = (state) => {
6472
+ if (state.status !== "active") return;
6473
+ scheduler.assertCommand();
6474
+ state.status = "closing";
6475
+ const failures = [];
6476
+ try {
6477
+ scheduler.cleanup(() => state.cleanups.drain(failures));
6478
+ } catch (error) {
6479
+ failures.push(error);
6480
+ } finally {
6481
+ scheduler.releaseOwned(() => releaseScope(state, failures));
6482
+ }
6356
6483
  throwCleanupFailures(failures, "Projection scope cleanup failed.");
6357
6484
  };
6358
6485
  const createScope = () => {
6359
- scheduler.assertIdle();
6486
+ scheduler.assertCommand();
6360
6487
  const state = {
6361
- active: true,
6488
+ status: "active",
6489
+ cleanups: createCleanupStack(),
6362
6490
  materialized: [],
6363
6491
  subscriptions: /* @__PURE__ */ new Set(),
6364
6492
  items: /* @__PURE__ */ new Map()
6365
6493
  };
6366
6494
  scopes.add(state);
6367
6495
  const assertActive = () => {
6368
- if (!state.active) throw new ProjectionDisposedError();
6496
+ if (state.status === "disposed") throw new ProjectionDisposedError();
6369
6497
  scheduler.assertActive();
6370
6498
  };
6371
6499
  function own(target) {
6372
6500
  assertActive();
6501
+ scheduler.assertCommand();
6373
6502
  const projections = [];
6374
6503
  const collect = (value) => {
6375
6504
  if (isProjection(value)) {
@@ -6439,6 +6568,11 @@ const createProjectionRuntime = (options) => {
6439
6568
  },
6440
6569
  update: scopeUpdate,
6441
6570
  batch: scopeBatch,
6571
+ onDispose: (callback) => {
6572
+ assertActive();
6573
+ scheduler.assertCommand();
6574
+ return state.cleanups.add(callback);
6575
+ },
6442
6576
  dispose: () => disposeScope(state)
6443
6577
  });
6444
6578
  };
@@ -6449,24 +6583,33 @@ const createProjectionRuntime = (options) => {
6449
6583
  update,
6450
6584
  batch,
6451
6585
  scope: createScope,
6586
+ onDispose: (callback) => {
6587
+ scheduler.assertCommand();
6588
+ return cleanups.add(callback);
6589
+ },
6452
6590
  dispose: () => {
6453
- if (!scheduler.active) return;
6454
- scheduler.assertIdle();
6591
+ if (!scheduler.beginClose()) return;
6455
6592
  const failures = [];
6456
- for (const scope of scopes) {
6457
- scope.active = false;
6458
- disposeScopeConsumers(scope, failures);
6459
- scope.materialized.length = 0;
6460
- }
6461
- scopes.clear();
6462
- disposeItemControllers(rootItems, failures);
6593
+ const owned = [...scopes].reverse();
6594
+ for (const scope of owned) scope.status = "closing";
6463
6595
  try {
6464
- sources.dispose();
6596
+ scheduler.cleanup(() => {
6597
+ for (const scope of owned) scope.cleanups.drain(failures);
6598
+ cleanups.drain(failures);
6599
+ });
6465
6600
  } catch (error) {
6466
6601
  failures.push(error);
6467
6602
  }
6468
6603
  try {
6469
- scheduler.dispose();
6604
+ scheduler.dispose(() => {
6605
+ for (const scope of owned) {
6606
+ scope.status = "disposed";
6607
+ scope.materialized.length = 0;
6608
+ disposeScopeConsumers(scope, failures);
6609
+ }
6610
+ scopes.clear();
6611
+ disposeItemControllers(rootItems, failures);
6612
+ }, sources.dispose);
6470
6613
  } catch (error) {
6471
6614
  failures.push(error);
6472
6615
  }
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_record = require("./record-ChYUi8PP.cjs");
3
- const require_driver = require("./driver-CN3dGxQI.cjs");
3
+ const require_driver = require("./driver-i4G4iVX0.cjs");
4
4
  //#region core/src/local-sync/error.ts
5
5
  var LocalSyncError = class extends Error {
6
6
  code;
@@ -467,10 +467,7 @@ const attachLocalSync = async (input) => {
467
467
  revision: () => stateRevision,
468
468
  subscribe: (listener) => {
469
469
  if (disposed) throw new LocalSyncError("disposed", "Local sync has been disposed.");
470
- stateListeners.add(listener);
471
- return () => {
472
- stateListeners.delete(listener);
473
- };
470
+ return require_driver.addListener(stateListeners, listener);
474
471
  }
475
472
  }),
476
473
  flush: async () => {
@@ -1,4 +1,4 @@
1
- import { W as ObjectSchema, g as Readable, s as DocumentRuntime } from "./contract-CNR6BS75.cjs";
1
+ import { W as ObjectSchema, g as Readable, s as DocumentRuntime } from "./contract-DovG1Yp-.cjs";
2
2
 
3
3
  //#region core/src/local-sync/json.d.ts
4
4
  type JsonPrimitive = null | boolean | number | string;