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