@oimdb/core 3.10.0 → 3.11.0

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
@@ -44,6 +44,7 @@ __export(index_exports, {
44
44
  OIMComputeRuntime: () => OIMComputeRuntime,
45
45
  OIMComputed: () => OIMComputed,
46
46
  OIMDBSettings: () => OIMDBSettings,
47
+ OIMDerivedCollection: () => OIMDerivedCollection,
47
48
  OIMDerivedCollectionGlobalIndexArrayBased: () => OIMDerivedCollectionGlobalIndexArrayBased,
48
49
  OIMDerivedCollectionGlobalIndexSetBased: () => OIMDerivedCollectionGlobalIndexSetBased,
49
50
  OIMDerivedCollectionIndexArrayBased: () => OIMDerivedCollectionIndexArrayBased,
@@ -139,7 +140,11 @@ __export(index_exports, {
139
140
  createOIMIndexSlotMap: () => createOIMIndexSlotMap,
140
141
  createOIMOrderedListCommandStreamDiffDriven: () => createOIMOrderedListCommandStreamDiffDriven,
141
142
  createOIMOrderedListMappedCommandStream: () => createOIMOrderedListMappedCommandStream,
142
- diffOrderedListByPk: () => diffOrderedListByPk
143
+ diffOrderedListByPk: () => diffOrderedListByPk,
144
+ getOIMComputeRuntime: () => getOIMComputeRuntime,
145
+ on: () => on,
146
+ peekOIMComputeRuntime: () => peekOIMComputeRuntime,
147
+ setOIMComputeRuntime: () => setOIMComputeRuntime
143
148
  });
144
149
  module.exports = __toCommonJS(index_exports);
145
150
 
@@ -370,6 +375,25 @@ var OIMEventQueueScheduler = class {
370
375
  flush() {
371
376
  this.emitter.emit(0 /* FLUSH */, void 0);
372
377
  }
378
+ /**
379
+ * Detach a Node timer handle from the event loop, returning it unchanged.
380
+ *
381
+ * A pending flush must never be a reason for a process to stay alive: if
382
+ * nothing else is running there is no UI left to update, and the flush is
383
+ * moot. Without this a scheduled-but-not-yet-run flush keeps Node awake —
384
+ * which is why a test suite that leaves a queue scheduled reports
385
+ * "a worker process has failed to exit gracefully".
386
+ *
387
+ * In the browser `setTimeout` returns a number, which has no `unref`, so the
388
+ * call is guarded and the handle passes straight through.
389
+ */
390
+ unref(handle) {
391
+ const maybeUnrefable = handle;
392
+ if (typeof maybeUnrefable?.unref === "function") {
393
+ maybeUnrefable.unref();
394
+ }
395
+ return handle;
396
+ }
373
397
  };
374
398
 
375
399
  // src/core/OIMKeyedBucketSetBased.ts
@@ -704,19 +728,28 @@ var OIMCarrierKeyedEmitter = class {
704
728
  // doubles as membership: a carrier is in this array iff `carrier.dirty`.
705
729
  this.dirtyCarriers = [];
706
730
  this.isFlushEnqueued = false;
731
+ // Number of live subscriptions whose single handler spans 2+ distinct
732
+ // carriers (a `subscribeOnKeys` over distinct keys). While this is > 0 a
733
+ // flush coalesces delivery so such a handler fires at most ONCE per flush,
734
+ // not once per changed carrier — the callback carries no key, so extra
735
+ // fires are pure waste. At 0 the hot single-subscriber path pays nothing:
736
+ // no per-flush Set, no membership checks.
737
+ this.multiCarrierSubscriptions = 0;
707
738
  this.onFlush = () => {
708
739
  this.isFlushEnqueued = false;
709
740
  if (this.dirtyCarriers.length === 0) return;
710
741
  const flushing = this.dirtyCarriers;
711
742
  this.dirtyCarriers = [];
743
+ const delivered = this.multiCarrierSubscriptions > 0 ? this.deliveredPool ?? (this.deliveredPool = /* @__PURE__ */ new Set()) : void 0;
712
744
  let i = 0;
713
745
  try {
714
746
  for (; i < flushing.length; i++) {
715
747
  flushing[i].dirty = false;
716
- this.notify(flushing[i]);
748
+ this.notify(flushing[i], delivered);
717
749
  }
718
750
  } finally {
719
751
  for (; i < flushing.length; i++) flushing[i].dirty = false;
752
+ delivered?.clear();
720
753
  }
721
754
  };
722
755
  this.queue = queue;
@@ -734,7 +767,13 @@ var OIMCarrierKeyedEmitter = class {
734
767
  this.addHandler(carrier, handler);
735
768
  carriers.push(carrier);
736
769
  }
770
+ const spansMultipleCarriers = carriers.length > 1;
771
+ if (spansMultipleCarriers) this.multiCarrierSubscriptions++;
772
+ let unsubscribed = false;
737
773
  return () => {
774
+ if (unsubscribed) return;
775
+ unsubscribed = true;
776
+ if (spansMultipleCarriers) this.multiCarrierSubscriptions--;
738
777
  for (let i = 0; i < carriers.length; i++) {
739
778
  this.removeHandler(carriers[i], handler);
740
779
  }
@@ -850,19 +889,31 @@ var OIMCarrierKeyedEmitter = class {
850
889
  this.isFlushEnqueued = true;
851
890
  this.queue.enqueue(this.onFlush);
852
891
  }
853
- notify(carrier) {
892
+ notify(carrier, delivered) {
854
893
  const subscribers = carrier.subscribers;
855
894
  if (!subscribers || subscribers.size === 0) return;
856
895
  if (subscribers.size === 1) {
857
896
  const only = subscribers.values().next().value;
858
- if (only) only();
897
+ if (!only) return;
898
+ if (delivered) {
899
+ const size = delivered.size;
900
+ delivered.add(only);
901
+ if (delivered.size === size) return;
902
+ }
903
+ only();
859
904
  return;
860
905
  }
861
906
  const snapshot = [];
862
907
  subscribers.forEach((h) => snapshot.push(h));
863
908
  for (let i = 0; i < snapshot.length; i++) {
864
909
  const handler = snapshot[i];
865
- if (subscribers.has(handler)) handler();
910
+ if (!subscribers.has(handler)) continue;
911
+ if (delivered) {
912
+ const size = delivered.size;
913
+ delivered.add(handler);
914
+ if (delivered.size === size) continue;
915
+ }
916
+ handler();
866
917
  }
867
918
  }
868
919
  };
@@ -1484,8 +1535,11 @@ var OIMCollection = class {
1484
1535
  this.emitter.emit(0 /* UPDATE */, { pks });
1485
1536
  }
1486
1537
  clear() {
1538
+ const pks = this.store.getAllPks();
1487
1539
  this.store.clear();
1488
- this.emitter.emit(0 /* UPDATE */, { pks: [] });
1540
+ if (pks.length > 0) {
1541
+ this.emitter.emit(0 /* UPDATE */, { pks });
1542
+ }
1489
1543
  }
1490
1544
  countAll() {
1491
1545
  return this.store.countAll();
@@ -1527,14 +1581,12 @@ var OIMReactiveCollection = class extends OIMCollection {
1527
1581
  // Only ever holds `slot.pk` (one canonical reference per logical key), so a
1528
1582
  // native `Set` dedups correctly by reference even for composite pks.
1529
1583
  this.pendingAnyUpdatePks = /* @__PURE__ */ new Set();
1530
- this.isAnyUpdateClearPending = false;
1531
1584
  this.isAnyUpdateScheduled = false;
1532
1585
  this.runAnyUpdate = () => {
1533
1586
  if (!this.isAnyUpdateScheduled) return;
1534
1587
  this.isAnyUpdateScheduled = false;
1535
- const pks = this.isAnyUpdateClearPending ? [] : Array.from(this.pendingAnyUpdatePks.values());
1588
+ const pks = Array.from(this.pendingAnyUpdatePks.values());
1536
1589
  this.pendingAnyUpdatePks.clear();
1537
- this.isAnyUpdateClearPending = false;
1538
1590
  const snapshot = Array.from(this.anyUpdateHandlers);
1539
1591
  for (let i = 0; i < snapshot.length; i++) snapshot[i](pks);
1540
1592
  };
@@ -1574,30 +1626,43 @@ var OIMReactiveCollection = class extends OIMCollection {
1574
1626
  this.anyUpdateHandlers.delete(handler);
1575
1627
  };
1576
1628
  }
1577
- // The legacy `emitter` (EOIMCollectionEventType.UPDATE) coexists with the
1578
- // keyed `updateEmitter`. Build its payload only when something is actually
1579
- // subscribed — otherwise every upsert/remove allocates a throwaway
1580
- // `{ pks }` object (and array) that nobody reads.
1581
- notifyLegacy(pk) {
1629
+ // Two notification channels coexist on a reactive collection; they serve
1630
+ // different audiences and NEITHER is obsolete:
1631
+ // • the keyed `updateEmitter` — per-key/per-carrier pings ("did MY key
1632
+ // change?", payload-less `void`), for leaf subscribers like React hooks
1633
+ // that declare the keys they care about up front;
1634
+ // • this `emitter` UPDATE event — the aggregate/batch channel that carries
1635
+ // the full list of changed pks in one SYNCHRONOUS callback, for
1636
+ // consumers that do NOT know their keys ahead of time and must re-derive
1637
+ // over the batch: derived indexes/collections, changed-fields, persist,
1638
+ // snapshot-manager. `pks` always enumerates exactly what changed — even
1639
+ // clear() lists every removed key, so an empty array never occurs.
1640
+ // It is not a per-key emitter with fewer features — it is the opposite
1641
+ // shape (enumerated batch, not "my key"), which is why it stays.
1642
+ //
1643
+ // Emit on the batch channel only when something is actually subscribed —
1644
+ // otherwise every upsert/remove allocates a throwaway `{ pks }` object (and
1645
+ // array) that nobody reads.
1646
+ notifyBatch(pk) {
1582
1647
  if (this.emitter.hasHandlers(0 /* UPDATE */)) {
1583
1648
  this.emitter.emit(0 /* UPDATE */, { pks: [pk] });
1584
1649
  }
1585
1650
  }
1586
- notifyLegacyMany(pks) {
1651
+ notifyBatchMany(pks) {
1587
1652
  if (this.emitter.hasHandlers(0 /* UPDATE */)) {
1588
1653
  this.emitter.emit(0 /* UPDATE */, { pks });
1589
1654
  }
1590
1655
  }
1591
1656
  upsertOneByPk(pk, entity) {
1592
1657
  const slot = this.upsertOneWithoutNotificationsByPk(pk, entity);
1593
- this.notifyLegacy(pk);
1658
+ this.notifyBatch(pk);
1594
1659
  this.updateEmitter.markUpdatedCarrier(slot);
1595
1660
  this.trackAnyUpdatePk(pk);
1596
1661
  return slot;
1597
1662
  }
1598
1663
  upsertOne(entity) {
1599
1664
  const slot = this.upsertOneWithoutNotifications(entity);
1600
- this.notifyLegacy(slot.pk);
1665
+ this.notifyBatch(slot.pk);
1601
1666
  this.updateEmitter.markUpdatedCarrier(slot);
1602
1667
  this.trackAnyUpdatePk(slot.pk);
1603
1668
  return slot;
@@ -1608,7 +1673,7 @@ var OIMReactiveCollection = class extends OIMCollection {
1608
1673
  (entity) => this.upsertOneWithoutNotifications(entity)
1609
1674
  );
1610
1675
  const pks = slots.map((slot) => slot.pk);
1611
- this.notifyLegacyMany(pks);
1676
+ this.notifyBatchMany(pks);
1612
1677
  for (let i = 0; i < slots.length; i++) {
1613
1678
  this.updateEmitter.markUpdatedCarrier(slots[i]);
1614
1679
  }
@@ -1618,7 +1683,7 @@ var OIMReactiveCollection = class extends OIMCollection {
1618
1683
  removeOne(entity) {
1619
1684
  const pk = this.selectPk(entity);
1620
1685
  this.store.removeOneByPk(pk);
1621
- this.notifyLegacy(pk);
1686
+ this.notifyBatch(pk);
1622
1687
  this.updateEmitter.markUpdatedKey(pk);
1623
1688
  this.trackAnyUpdatePk(pk);
1624
1689
  }
@@ -1626,30 +1691,32 @@ var OIMReactiveCollection = class extends OIMCollection {
1626
1691
  if (entities.length === 0) return;
1627
1692
  const pks = entities.map(this.selectPk);
1628
1693
  this.store.removeManyByPks(pks);
1629
- this.notifyLegacyMany(pks);
1694
+ this.notifyBatchMany(pks);
1630
1695
  this.updateEmitter.markUpdatedKeys(pks);
1631
1696
  this.trackAnyUpdatePks(pks);
1632
1697
  }
1633
1698
  removeOneByPk(pk) {
1634
1699
  this.store.removeOneByPk(pk);
1635
- this.notifyLegacy(pk);
1700
+ this.notifyBatch(pk);
1636
1701
  this.updateEmitter.markUpdatedKey(pk);
1637
1702
  this.trackAnyUpdatePk(pk);
1638
1703
  }
1639
1704
  removeManyByPks(pks) {
1640
1705
  if (pks.length === 0) return;
1641
1706
  this.store.removeManyByPks(pks);
1642
- this.notifyLegacyMany(pks);
1707
+ this.notifyBatchMany(pks);
1643
1708
  this.updateEmitter.markUpdatedKeys(pks);
1644
1709
  this.trackAnyUpdatePks(pks);
1645
1710
  }
1646
1711
  clear() {
1712
+ const needsPks = this.emitter.hasHandlers(0 /* UPDATE */) || this.anyUpdateHandlers.size > 0;
1713
+ const pks = needsPks ? this.store.getAllPks() : void 0;
1647
1714
  this.store.clear();
1648
- if (this.emitter.hasHandlers(0 /* UPDATE */)) {
1649
- this.emitter.emit(0 /* UPDATE */, { pks: [] });
1715
+ if (pks && pks.length > 0) {
1716
+ this.notifyBatchMany(pks);
1717
+ this.trackAnyUpdatePks(pks);
1650
1718
  }
1651
1719
  this.updateEmitter.markAllUpdated();
1652
- this.trackAnyUpdateClear();
1653
1720
  }
1654
1721
  trackAnyUpdatePk(pk) {
1655
1722
  if (this.anyUpdateHandlers.size === 0) return;
@@ -1658,14 +1725,9 @@ var OIMReactiveCollection = class extends OIMCollection {
1658
1725
  }
1659
1726
  trackAnyUpdatePks(pks) {
1660
1727
  if (this.anyUpdateHandlers.size === 0) return;
1661
- for (let i = 0; i < pks.length; i++)
1728
+ for (let i = 0; i < pks.length; i++) {
1662
1729
  this.pendingAnyUpdatePks.add(pks[i]);
1663
- this.ensureAnyUpdateEnqueued();
1664
- }
1665
- trackAnyUpdateClear() {
1666
- if (this.anyUpdateHandlers.size === 0) return;
1667
- this.isAnyUpdateClearPending = true;
1668
- this.pendingAnyUpdatePks.clear();
1730
+ }
1669
1731
  this.ensureAnyUpdateEnqueued();
1670
1732
  }
1671
1733
  ensureAnyUpdateEnqueued() {
@@ -2098,8 +2160,13 @@ var OIMUpdateEventEmitter = class {
2098
2160
  var OIMOrderedListCommandBuffer = class {
2099
2161
  constructor(queue) {
2100
2162
  this.commandsByKey = /* @__PURE__ */ new Map();
2163
+ // Keys whose buffered leading `reset` is stale: an edit after the reset
2164
+ // should be folded into it, but we defer the (O(list-size)) rebuild to the
2165
+ // next read instead of redoing it on every subsequent edit in the batch.
2166
+ this.staleResetKeys = /* @__PURE__ */ new Set();
2101
2167
  this.onAfterFlush = () => {
2102
2168
  this.commandsByKey.clear();
2169
+ this.staleResetKeys.clear();
2103
2170
  };
2104
2171
  this.commandsEventEmitter = new OIMUpdateEventEmitter(
2105
2172
  queue,
@@ -2114,9 +2181,11 @@ var OIMOrderedListCommandBuffer = class {
2114
2181
  return this.commandsEventEmitter.subscribeOnKey(key, handler);
2115
2182
  }
2116
2183
  getBufferedCommands(key) {
2184
+ this.materializeStaleReset(key);
2117
2185
  return this.commandsByKey.get(key) ?? [];
2118
2186
  }
2119
2187
  consumeCommands(key) {
2188
+ this.materializeStaleReset(key);
2120
2189
  const cmds = this.commandsByKey.get(key);
2121
2190
  if (!cmds || cmds.length === 0) return [];
2122
2191
  return cmds.slice();
@@ -2125,6 +2194,20 @@ var OIMOrderedListCommandBuffer = class {
2125
2194
  this.commandsEventEmitter.destroy();
2126
2195
  this.unsubscribeAfterFlush();
2127
2196
  this.commandsByKey.clear();
2197
+ this.staleResetKeys.clear();
2198
+ }
2199
+ /**
2200
+ * Rebuild a deferred leading `reset` from the current order, once, at read
2201
+ * time. All the post-reset edits that were folded in are already reflected
2202
+ * in the driver's backing order, so a single rebuild here is equivalent to
2203
+ * having recomputed the reset on each of those edits.
2204
+ */
2205
+ materializeStaleReset(key) {
2206
+ if (!this.staleResetKeys.delete(key)) return;
2207
+ const cmds = this.commandsByKey.get(key);
2208
+ if (cmds && cmds.length > 0 && cmds[0].type === "reset") {
2209
+ cmds[0] = this.createResetCommand(this.getItemsByKey(key));
2210
+ }
2128
2211
  }
2129
2212
  appendCommand(key, cmd) {
2130
2213
  let cmds = this.commandsByKey.get(key);
@@ -2135,8 +2218,9 @@ var OIMOrderedListCommandBuffer = class {
2135
2218
  if (cmd.type === "reset") {
2136
2219
  cmds.length = 0;
2137
2220
  cmds.push(cmd);
2221
+ this.staleResetKeys.delete(key);
2138
2222
  } else if (cmds.length > 0 && cmds[0].type === "reset") {
2139
- cmds[0] = this.createResetCommand(this.getItemsByKey(key));
2223
+ this.staleResetKeys.add(key);
2140
2224
  } else {
2141
2225
  cmds.push(cmd);
2142
2226
  }
@@ -3178,10 +3262,6 @@ var OIMDerivedCollectionIndexSetBased = class extends OIMReactiveCollectionIndex
3178
3262
  });
3179
3263
  this.keysByPk = /* @__PURE__ */ new Map();
3180
3264
  this.onCollectionUpdate = (pks) => {
3181
- if (pks.length === 0) {
3182
- this.rebuildFromCollection();
3183
- return;
3184
- }
3185
3265
  for (const pk of pks) {
3186
3266
  this.syncPk(pk);
3187
3267
  }
@@ -3283,10 +3363,6 @@ var OIMDerivedCollectionIndexArrayBased = class extends OIMReactiveCollectionInd
3283
3363
  // touching an unrelated field can skip re-sorting when the value is unchanged.
3284
3364
  this.orderValueByPk = /* @__PURE__ */ new Map();
3285
3365
  this.onCollectionUpdate = (pks) => {
3286
- if (pks.length === 0) {
3287
- this.rebuildFromCollection();
3288
- return;
3289
- }
3290
3366
  const ordered = !!(this.compareEntities || this.orderBy);
3291
3367
  const keysToSort = ordered ? /* @__PURE__ */ new Set() : null;
3292
3368
  for (const pk of pks) {
@@ -3703,10 +3779,6 @@ var OIMDerivedCollectionGlobalIndexArrayBased = class extends OIMReactiveCollect
3703
3779
  // an unrelated field can skip re-sorting when the value is unchanged.
3704
3780
  this.orderValueByPk = /* @__PURE__ */ new Map();
3705
3781
  this.onCollectionUpdate = (pks) => {
3706
- if (pks.length === 0) {
3707
- this.rebuildFromCollection();
3708
- return;
3709
- }
3710
3782
  const ordered = !!(this.compareEntities || this.orderBy);
3711
3783
  let needsSort = false;
3712
3784
  for (const pk of pks) {
@@ -3804,10 +3876,6 @@ var OIMDerivedCollectionGlobalIndexSetBased = class extends OIMReactiveCollectio
3804
3876
  super(queue, { collection, indexOptions: opts.indexOptions });
3805
3877
  this.presentPks = /* @__PURE__ */ new Set();
3806
3878
  this.onCollectionUpdate = (pks) => {
3807
- if (pks.length === 0) {
3808
- this.rebuildFromCollection();
3809
- return;
3810
- }
3811
3879
  for (const pk of pks) {
3812
3880
  const prev = this.presentPks.has(pk);
3813
3881
  const slot = this.collection.getSlotByPk(pk);
@@ -3983,6 +4051,55 @@ function createOIMCollectionIndexFactory(queue, collection) {
3983
4051
  return new OIMCollectionIndexFactory(queue, collection);
3984
4052
  }
3985
4053
 
4054
+ // src/core/OIMDerivedCollection.ts
4055
+ var OIMDerivedCollection = class extends OIMReactiveCollection {
4056
+ constructor(queue, source, derive, opts) {
4057
+ super(queue, {
4058
+ store: opts?.store,
4059
+ // Each sync recomputes the WHOLE derived entity → replace, not merge.
4060
+ updateEntity: (draft) => draft
4061
+ });
4062
+ this.onSourceUpdate = (pks) => {
4063
+ for (let i = 0; i < pks.length; i++) this.syncPk(pks[i]);
4064
+ };
4065
+ this.source = source;
4066
+ this.derive = derive;
4067
+ this.compareDerived = opts?.compare ?? Object.is;
4068
+ this.unsubscribeFromSource = source.emitter.on(
4069
+ 0 /* UPDATE */,
4070
+ (payload) => this.onSourceUpdate(payload.pks)
4071
+ );
4072
+ if (opts?.buildInitial !== false) {
4073
+ this.rebuildFromSource();
4074
+ }
4075
+ }
4076
+ rebuildFromSource() {
4077
+ for (const pk of this.getAllPks()) {
4078
+ if (this.source.getOneByPk(pk) === void 0) {
4079
+ this.removeOneByPk(pk);
4080
+ }
4081
+ }
4082
+ for (const slot of this.source.getAllSlots()) {
4083
+ if (slot.item !== void 0) this.syncPk(slot.pk);
4084
+ }
4085
+ }
4086
+ destroy() {
4087
+ this.unsubscribeFromSource();
4088
+ super.destroy();
4089
+ }
4090
+ syncPk(pk) {
4091
+ const sourceEntity = this.source.getOneByPk(pk);
4092
+ if (sourceEntity === void 0) {
4093
+ if (this.getOneByPk(pk) !== void 0) this.removeOneByPk(pk);
4094
+ return;
4095
+ }
4096
+ const next = this.derive(sourceEntity, pk);
4097
+ const prev = this.getOneByPk(pk);
4098
+ if (prev !== void 0 && this.compareDerived(prev, next)) return;
4099
+ this.upsertOneByPk(pk, next);
4100
+ }
4101
+ };
4102
+
3986
4103
  // src/core/OIMObjectStoreRecordDriven.ts
3987
4104
  var OIMObjectStoreRecordDriven = class extends OIMObjectStore {
3988
4105
  constructor(initial) {
@@ -4055,8 +4172,11 @@ var OIMObject = class {
4055
4172
  return this.store.getAll();
4056
4173
  }
4057
4174
  clear() {
4175
+ const keys = this.store.keys();
4058
4176
  this.store.clear();
4059
- this.onUpdatedKeys([]);
4177
+ if (keys.length > 0) {
4178
+ this.onUpdatedKeys(keys);
4179
+ }
4060
4180
  }
4061
4181
  /**
4062
4182
  * Write fan-out hooks. Split single-key vs multi-key so a reactive subclass
@@ -4359,8 +4479,9 @@ var OIMComparatorFactory = class {
4359
4479
  if (ka.length !== kb.length) return true;
4360
4480
  for (let i = 0; i < ka.length; i++) {
4361
4481
  const k = ka[i];
4362
- if (b[k] !== a[k])
4482
+ if (b[k] !== a[k]) {
4363
4483
  return true;
4484
+ }
4364
4485
  }
4365
4486
  return false;
4366
4487
  };
@@ -4410,10 +4531,14 @@ var OIMEventQueue = class {
4410
4531
  return this.isFlushing;
4411
4532
  }
4412
4533
  /**
4413
- * Execute all pending tasks.
4534
+ * Execute the tasks pending at the moment the flush starts — a single pass,
4535
+ * not a drain-to-empty.
4414
4536
  *
4415
- * This flush drains until the queue is empty. If tasks enqueue more tasks, they will be
4416
- * executed within the same flush.
4537
+ * The pending set is swapped out for an empty spare up front and the flush
4538
+ * iterates that captured set. Tasks enqueued during the flush land in the
4539
+ * fresh set and run on the NEXT flush, not this one. (Contrast
4540
+ * `OIMComputeRuntime.flush()`, which re-reads its bucket per level and so
4541
+ * does drain same-level tasks scheduled mid-flush.)
4417
4542
  */
4418
4543
  flush() {
4419
4544
  this.scheduler?.cancel();
@@ -4687,20 +4812,92 @@ var OIMPkCodecKeyPath = class {
4687
4812
  }
4688
4813
  };
4689
4814
 
4815
+ // src/modules/effect/core/OIMEffectDependencyComputed.ts
4816
+ var OIMEffectDependencyComputed = class {
4817
+ constructor(source) {
4818
+ this.source = source;
4819
+ }
4820
+ subscribe(onUpdate) {
4821
+ return this.source.emitter.on("update", onUpdate);
4822
+ }
4823
+ };
4824
+
4690
4825
  // src/modules/compute/core/OIMComputeRuntime.ts
4691
4826
  var OIMComputeRuntime = class {
4692
4827
  constructor(queue) {
4693
4828
  this.queue = queue;
4694
4829
  this.isFlushScheduled = false;
4695
4830
  this.isFlushing = false;
4831
+ // Active level range (min > max means nothing pending).
4696
4832
  this.minLevel = Infinity;
4697
4833
  this.maxLevel = -Infinity;
4698
- this.dirtyByLevel = /* @__PURE__ */ new Map();
4834
+ this.dirtyByLevel = [];
4699
4835
  this.afterFlushTasks = /* @__PURE__ */ new Set();
4700
- this.queue.emitter.on(0 /* AFTER_FLUSH */, () => {
4701
- if (!this.isFlushScheduled) return;
4702
- this.flush();
4703
- });
4836
+ // Topological depth per computed node — the engine is the ONLY owner of
4837
+ // levels; nodes never expose their own. A node's dependents look up its depth
4838
+ // here (by the computed reference their dependency wraps). Written once at
4839
+ // construction, never on the hot flush path.
4840
+ this.levelByComputed = /* @__PURE__ */ new WeakMap();
4841
+ this._isDestroyed = false;
4842
+ this.unsubscribeFromQueue = this.queue.emitter.on(
4843
+ 0 /* AFTER_FLUSH */,
4844
+ () => {
4845
+ if (!this.isFlushScheduled) return;
4846
+ this.flush();
4847
+ }
4848
+ );
4849
+ }
4850
+ /**
4851
+ * Whether {@link destroy} has run. A destroyed runtime is detached from its
4852
+ * queue and must not be reused — `getOIMComputeRuntime` reads this to hand
4853
+ * out a fresh runtime instead of a dead one.
4854
+ */
4855
+ get isDestroyed() {
4856
+ return this._isDestroyed;
4857
+ }
4858
+ /**
4859
+ * Detach from the queue and drop everything pending.
4860
+ *
4861
+ * Without this the AFTER_FLUSH subscription lives as long as the queue does,
4862
+ * even once every computed / effect on this runtime is gone — harmless (the
4863
+ * handler early-returns) but impossible to unwind. Destroying does NOT
4864
+ * destroy the nodes scheduled on it; destroy them first, then the runtime.
4865
+ * Idempotent.
4866
+ */
4867
+ destroy() {
4868
+ if (this._isDestroyed) return;
4869
+ this._isDestroyed = true;
4870
+ this.unsubscribeFromQueue();
4871
+ for (let i = 0; i < this.dirtyByLevel.length; i++) {
4872
+ this.dirtyByLevel[i]?.clear();
4873
+ }
4874
+ this.afterFlushTasks.clear();
4875
+ this.minLevel = Infinity;
4876
+ this.maxLevel = -Infinity;
4877
+ this.isFlushScheduled = false;
4878
+ }
4879
+ /**
4880
+ * Topological depth for a node with these dependencies: one above the deepest
4881
+ * computed dependency (source-only → 0). The engine owns this — callers pass
4882
+ * their dependencies and get back a level, they never compute it themselves.
4883
+ */
4884
+ computeLevel(deps) {
4885
+ let max = -1;
4886
+ for (const dep of deps) {
4887
+ if (dep instanceof OIMEffectDependencyComputed) {
4888
+ const depLevel = this.levelByComputed.get(dep.source) ?? 0;
4889
+ if (depLevel > max) max = depLevel;
4890
+ }
4891
+ }
4892
+ return max + 1;
4893
+ }
4894
+ /** Record a computed's depth so its dependents can look it up. */
4895
+ registerLevel(computed, level) {
4896
+ this.levelByComputed.set(computed, level);
4897
+ }
4898
+ /** The recorded topological depth of a computed (0 if unknown). Introspection. */
4899
+ getLevel(computed) {
4900
+ return this.levelByComputed.get(computed) ?? 0;
4704
4901
  }
4705
4902
  /**
4706
4903
  * Schedule a one-shot task to run on the next `queue.flush()` (AFTER_FLUSH),
@@ -4712,10 +4909,10 @@ var OIMComputeRuntime = class {
4712
4909
  if (!isActive) return;
4713
4910
  task();
4714
4911
  };
4715
- let bucket = this.dirtyByLevel.get(level);
4716
- if (!bucket) {
4912
+ let bucket = this.dirtyByLevel[level];
4913
+ if (bucket === void 0) {
4717
4914
  bucket = /* @__PURE__ */ new Set();
4718
- this.dirtyByLevel.set(level, bucket);
4915
+ this.dirtyByLevel[level] = bucket;
4719
4916
  }
4720
4917
  bucket.add(wrapped);
4721
4918
  if (level < this.minLevel) this.minLevel = level;
@@ -4747,7 +4944,7 @@ var OIMComputeRuntime = class {
4747
4944
  }
4748
4945
  flush() {
4749
4946
  this.isFlushScheduled = false;
4750
- if (this.dirtyByLevel.size === 0 && this.afterFlushTasks.size === 0)
4947
+ if (this.minLevel > this.maxLevel && this.afterFlushTasks.size === 0)
4751
4948
  return;
4752
4949
  this.isFlushing = true;
4753
4950
  try {
@@ -4756,15 +4953,15 @@ var OIMComputeRuntime = class {
4756
4953
  this.afterFlushTasks.clear();
4757
4954
  for (let i = 0; i < snapshot.length; i++) snapshot[i]();
4758
4955
  }
4759
- if (this.dirtyByLevel.size === 0) return;
4956
+ if (this.minLevel > this.maxLevel) return;
4760
4957
  let level = this.minLevel;
4761
4958
  let max = this.maxLevel;
4762
4959
  while (level <= max) {
4763
4960
  for (; ; ) {
4764
- const bucket = this.dirtyByLevel.get(level);
4765
- if (!bucket || bucket.size === 0) break;
4766
- this.dirtyByLevel.delete(level);
4961
+ const bucket = this.dirtyByLevel[level];
4962
+ if (bucket === void 0 || bucket.size === 0) break;
4767
4963
  const snapshot = Array.from(bucket);
4964
+ bucket.clear();
4768
4965
  for (let i = 0; i < snapshot.length; i++) snapshot[i]();
4769
4966
  }
4770
4967
  if (this.maxLevel > max) max = this.maxLevel;
@@ -4772,24 +4969,38 @@ var OIMComputeRuntime = class {
4772
4969
  }
4773
4970
  } finally {
4774
4971
  this.isFlushing = false;
4775
- if (this.dirtyByLevel.size === 0) {
4776
- this.minLevel = Infinity;
4777
- this.maxLevel = -Infinity;
4778
- } else {
4779
- let nextMin = Infinity;
4780
- let nextMax = -Infinity;
4781
- this.dirtyByLevel.forEach((_bucket, lvl) => {
4782
- if (lvl < nextMin) nextMin = lvl;
4783
- if (lvl > nextMax) nextMax = lvl;
4784
- });
4785
- this.minLevel = nextMin;
4786
- this.maxLevel = nextMax;
4787
- this.isFlushScheduled = true;
4972
+ this.recomputePendingRange();
4973
+ }
4974
+ }
4975
+ recomputePendingRange() {
4976
+ let nextMin = Infinity;
4977
+ let nextMax = -Infinity;
4978
+ const buckets = this.dirtyByLevel;
4979
+ for (let i = 0; i < buckets.length; i++) {
4980
+ const bucket = buckets[i];
4981
+ if (bucket !== void 0 && bucket.size > 0) {
4982
+ if (i < nextMin) nextMin = i;
4983
+ if (i > nextMax) nextMax = i;
4788
4984
  }
4789
4985
  }
4986
+ this.minLevel = nextMin;
4987
+ this.maxLevel = nextMax;
4988
+ if (nextMin <= nextMax) this.isFlushScheduled = true;
4790
4989
  }
4791
4990
  };
4792
4991
 
4992
+ // src/dx/oimComputeRuntimeByQueue.ts
4993
+ var oimComputeRuntimeByQueue = /* @__PURE__ */ new WeakMap();
4994
+
4995
+ // src/dx/getOIMComputeRuntime.ts
4996
+ function getOIMComputeRuntime(queue) {
4997
+ const runtime = oimComputeRuntimeByQueue.get(queue);
4998
+ if (runtime !== void 0 && !runtime.isDestroyed) return runtime;
4999
+ const nextRuntime = new OIMComputeRuntime(queue);
5000
+ oimComputeRuntimeByQueue.set(queue, nextRuntime);
5001
+ return nextRuntime;
5002
+ }
5003
+
4793
5004
  // src/modules/selector/core/OIMSelector.ts
4794
5005
  var OIMSelector = class {
4795
5006
  constructor(runtime, sourceDeps, computedDeps = []) {
@@ -5050,8 +5261,9 @@ var OIMEntitiesByIndexKeyArrayBasedSelector = class extends OIMSelector {
5050
5261
  areEqual(prev, next) {
5051
5262
  if (prev === next) return true;
5052
5263
  if (prev.length !== next.length) return false;
5053
- for (let i = 0; i < prev.length; i++)
5264
+ for (let i = 0; i < prev.length; i++) {
5054
5265
  if (prev[i] !== next[i]) return false;
5266
+ }
5055
5267
  return true;
5056
5268
  }
5057
5269
  };
@@ -5121,8 +5333,9 @@ var OIMEntitiesByIndexKeySetBasedSelector = class extends OIMSelector {
5121
5333
  areEqual(prev, next) {
5122
5334
  if (prev === next) return true;
5123
5335
  if (prev.length !== next.length) return false;
5124
- for (let i = 0; i < prev.length; i++)
5336
+ for (let i = 0; i < prev.length; i++) {
5125
5337
  if (prev[i] !== next[i]) return false;
5338
+ }
5126
5339
  return true;
5127
5340
  }
5128
5341
  };
@@ -5186,8 +5399,9 @@ var OIMEntitiesByGlobalIndexArrayBasedSelector = class extends OIMSelector {
5186
5399
  areEqual(prev, next) {
5187
5400
  if (prev === next) return true;
5188
5401
  if (prev.length !== next.length) return false;
5189
- for (let i = 0; i < prev.length; i++)
5402
+ for (let i = 0; i < prev.length; i++) {
5190
5403
  if (prev[i] !== next[i]) return false;
5404
+ }
5191
5405
  return true;
5192
5406
  }
5193
5407
  };
@@ -5252,8 +5466,9 @@ var OIMEntitiesByGlobalIndexSetBasedSelector = class extends OIMSelector {
5252
5466
  areEqual(prev, next) {
5253
5467
  if (prev === next) return true;
5254
5468
  if (prev.length !== next.length) return false;
5255
- for (let i = 0; i < prev.length; i++)
5469
+ for (let i = 0; i < prev.length; i++) {
5256
5470
  if (prev[i] !== next[i]) return false;
5471
+ }
5257
5472
  return true;
5258
5473
  }
5259
5474
  };
@@ -5262,7 +5477,7 @@ var OIMEntitiesByGlobalIndexSetBasedSelector = class extends OIMSelector {
5262
5477
  var OIMCollectionSelectors = class {
5263
5478
  constructor(queue, collection) {
5264
5479
  this.collection = collection;
5265
- this.runtime = new OIMComputeRuntime(queue);
5480
+ this.runtime = getOIMComputeRuntime(queue);
5266
5481
  }
5267
5482
  byPk(pk) {
5268
5483
  return new OIMCollectionByPkSelector(this.runtime, this.collection, pk);
@@ -5329,127 +5544,408 @@ var OIMCollectionSelectors = class {
5329
5544
  }
5330
5545
  };
5331
5546
 
5332
- // src/dx/OIMCollectionKit.ts
5333
- function createOIMCollectionKit(queue, opts) {
5334
- const collection = new OIMReactiveCollection(queue, opts);
5335
- const indexFactory = new OIMCollectionIndexFactory(queue, collection);
5336
- const select = new OIMCollectionSelectors(queue, collection);
5337
- const scope = new OIMDisposeScope();
5338
- scope.add(collection);
5339
- return {
5340
- queue,
5341
- collection,
5342
- indexFactory,
5343
- select,
5344
- scope,
5345
- destroy: () => scope.destroy()
5346
- };
5347
- }
5547
+ // src/modules/effect/core/OIMEffectDependencyKeyed.ts
5548
+ var OIMEffectDependencyKeyed = class {
5549
+ constructor(source, key) {
5550
+ this.source = source;
5551
+ this.key = key;
5552
+ }
5553
+ subscribe(onUpdate) {
5554
+ return this.source.subscribeOnKey(this.key, onUpdate);
5555
+ }
5556
+ };
5348
5557
 
5349
- // src/modules/wrapper/collection/OIMCollectionChangedFields.ts
5350
- var OIMCollectionChangedFields = class {
5351
- constructor(queue, collection, opts) {
5352
- this.isInWrite = false;
5353
- this.lastEntitySnapshotByPk = /* @__PURE__ */ new Map();
5354
- // PKs written through this wrapper during the current flush. Their fields are
5355
- // already tracked accurately via trackPatch, so the flush-time external-write
5356
- // handler skips them — no redundant diff + snapshot re-clone. Cleared per flush.
5357
- this.writtenThroughThisFlush = /* @__PURE__ */ new Set();
5358
- this.changedFieldsByPk = /* @__PURE__ */ new Map();
5359
- this.changedPksByField = /* @__PURE__ */ new Map();
5360
- this.onAfterFlush = () => {
5361
- this.clear();
5362
- this.writtenThroughThisFlush.clear();
5363
- };
5364
- this.onCollectionUpdate = (payload) => {
5365
- if (this.isInWrite) return;
5366
- if (payload.pks.length === 0) return;
5367
- for (let i = 0; i < payload.pks.length; i++) {
5368
- const pk = payload.pks[i];
5369
- if (this.writtenThroughThisFlush.has(pk)) continue;
5370
- this.trackExternalMutationByPk(pk);
5371
- }
5372
- };
5373
- this.collection = collection;
5374
- this.detectExternalMutations = opts?.detectExternalMutations !== false;
5375
- this.selectPk = opts?.selectPk ?? (() => {
5376
- throw new Error(
5377
- `[OIMCollectionChangedFields]: selectPk was not provided. Use upsertOneByPk(...) or pass opts.selectPk.`
5378
- );
5379
- });
5380
- this.changedPksEventEmitter = new OIMUpdateEventEmitter(
5381
- queue,
5382
- "after_flush"
5383
- );
5384
- this.changedFieldsEventEmitter = new OIMUpdateEventEmitter(queue, "after_flush");
5385
- this.unsubscribeAfterFlush = queue.emitter.on(
5386
- 0 /* AFTER_FLUSH */,
5387
- this.onAfterFlush
5388
- );
5389
- if (this.detectExternalMutations) {
5390
- const maybeReactive = collection;
5391
- this.unsubscribeFromCollectionEmitter = maybeReactive.subscribeOnAnyUpdate ? maybeReactive.subscribeOnAnyUpdate((pks) => {
5392
- this.onCollectionUpdate({
5393
- pks
5394
- });
5395
- }) : collection.emitter.on(
5396
- 0 /* UPDATE */,
5397
- this.onCollectionUpdate
5398
- );
5399
- }
5558
+ // src/modules/effect/core/OIMEffectDependencyKeyedCollection.ts
5559
+ var OIMEffectDependencyKeyedCollection = class {
5560
+ constructor(collection, pk) {
5561
+ this.dep = new OIMEffectDependencyKeyed(collection, pk);
5400
5562
  }
5401
- upsertOneByPk(pk, patch) {
5402
- const slot = this.withWrite(() => {
5403
- this.trackPatch(pk, patch);
5404
- return this.collection.upsertOneByPk(pk, patch);
5405
- });
5406
- this.updateEntitySnapshot(pk);
5407
- return slot;
5563
+ get source() {
5564
+ return this.dep.source;
5408
5565
  }
5409
- upsertOne(entity) {
5410
- const slot = this.withWrite(() => {
5411
- const pk = this.selectPk(entity);
5412
- this.trackPatch(pk, entity);
5413
- return this.collection.upsertOne(entity);
5414
- });
5415
- this.updateEntitySnapshot(slot.pk);
5416
- return slot;
5566
+ subscribe(onUpdate) {
5567
+ return this.dep.subscribe(onUpdate);
5417
5568
  }
5418
- upsertMany(entities) {
5419
- const pks = [];
5420
- const slots = this.withWrite(() => {
5421
- for (const entity of entities) {
5422
- const pk = this.selectPk(entity);
5423
- pks.push(pk);
5424
- this.trackPatch(pk, entity);
5425
- }
5426
- return this.collection.upsertMany(entities);
5427
- });
5428
- for (let i = 0; i < pks.length; i++) this.updateEntitySnapshot(pks[i]);
5429
- return slots;
5569
+ };
5570
+
5571
+ // src/modules/effect/core/OIMEffectDependencyKeyedIndex.ts
5572
+ var OIMEffectDependencyKeyedIndex = class {
5573
+ constructor(index, key) {
5574
+ this.dep = new OIMEffectDependencyKeyed(index, key);
5430
5575
  }
5431
- getChangedFieldsByPk(pk) {
5432
- return this.changedFieldsByPk.get(pk) ?? /* @__PURE__ */ new Set();
5576
+ get source() {
5577
+ return this.dep.source;
5433
5578
  }
5434
- consumeChangedFieldsByPk(pk) {
5435
- const existing = this.changedFieldsByPk.get(pk);
5436
- if (!existing) return /* @__PURE__ */ new Set();
5437
- return new Set(existing);
5579
+ subscribe(onUpdate) {
5580
+ return this.dep.subscribe(onUpdate);
5438
5581
  }
5439
- getChangedPksByField(field) {
5440
- return this.changedPksByField.get(field) ?? /* @__PURE__ */ new Set();
5582
+ };
5583
+
5584
+ // src/modules/effect/core/OIMEffectDependencyKeyedObject.ts
5585
+ var OIMEffectDependencyKeyedObject = class {
5586
+ constructor(object, key) {
5587
+ this.dep = new OIMEffectDependencyKeyed(object, key);
5441
5588
  }
5442
- consumeChangedPksByField(field) {
5443
- const existing = this.changedPksByField.get(field);
5444
- if (!existing) return /* @__PURE__ */ new Set();
5445
- return new Set(existing);
5589
+ get source() {
5590
+ return this.dep.source;
5446
5591
  }
5447
- clear() {
5448
- this.changedFieldsByPk.clear();
5449
- this.changedPksByField.clear();
5592
+ subscribe(onUpdate) {
5593
+ return this.dep.subscribe(onUpdate);
5450
5594
  }
5451
- destroy() {
5452
- this.changedPksEventEmitter.destroy();
5595
+ };
5596
+
5597
+ // src/dx/on.ts
5598
+ var on = {
5599
+ /** Depend on one PK of a collection. */
5600
+ collection: (collection, pk) => new OIMEffectDependencyKeyedCollection(collection, pk),
5601
+ /** Depend on one key of an index. */
5602
+ index: (index, key) => new OIMEffectDependencyKeyedIndex(index, key),
5603
+ /** Depend on one key of a reactive object. */
5604
+ object: (object, key) => new OIMEffectDependencyKeyedObject(object, key),
5605
+ /** Depend on another computed. */
5606
+ computed: (computed) => new OIMEffectDependencyComputed(computed)
5607
+ };
5608
+
5609
+ // src/modules/effect/core/OIMEffect.ts
5610
+ var OIMEffect = class {
5611
+ constructor(runtime, opts, level) {
5612
+ this.sourceDeps = [];
5613
+ this.computedDeps = [];
5614
+ this.sourceUnsubscribers = [];
5615
+ this.computedUnsubscribers = [];
5616
+ this.isResubscribeEnqueued = false;
5617
+ this.isTemporarilyUnsubscribedForFlush = false;
5618
+ this.isScheduled = false;
5619
+ this.runTask = () => {
5620
+ this.isScheduled = false;
5621
+ this.dequeueScheduled = void 0;
5622
+ this.runOnce();
5623
+ };
5624
+ this.onSourceInvalidate = () => {
5625
+ this.onUpdate?.();
5626
+ if (!this.isTemporarilyUnsubscribedForFlush) {
5627
+ this.isTemporarilyUnsubscribedForFlush = true;
5628
+ this.unsubscribeFromSourceDeps();
5629
+ this.ensureResubscribeAfterFlush();
5630
+ }
5631
+ if (this.isScheduled) return;
5632
+ this.isScheduled = true;
5633
+ this.dequeueScheduled = this.runtime.schedule(this.runTask, this.level);
5634
+ };
5635
+ this.onComputedInvalidate = () => {
5636
+ this.onUpdate?.();
5637
+ if (this.isScheduled) return;
5638
+ this.isScheduled = true;
5639
+ this.dequeueScheduled = this.runtime.schedule(this.runTask, this.level);
5640
+ };
5641
+ this.onAfterFlush = () => {
5642
+ if (!this.isResubscribeEnqueued) return;
5643
+ this.isResubscribeEnqueued = false;
5644
+ this.dequeueResubscribe = void 0;
5645
+ this.isTemporarilyUnsubscribedForFlush = false;
5646
+ if (this.sourceUnsubscribers.length === 0) {
5647
+ for (const dep of this.sourceDeps) {
5648
+ this.sourceUnsubscribers.push(
5649
+ dep.subscribe(this.onSourceInvalidate)
5650
+ );
5651
+ }
5652
+ }
5653
+ };
5654
+ this.runtime = runtime;
5655
+ this.onUpdate = opts.onUpdate;
5656
+ this.run = opts.run;
5657
+ this.deps = opts.deps ?? [];
5658
+ for (const dep of this.deps) {
5659
+ if (dep instanceof OIMEffectDependencyComputed) {
5660
+ this.computedDeps.push(dep);
5661
+ } else {
5662
+ this.sourceDeps.push(dep);
5663
+ }
5664
+ }
5665
+ this.level = level ?? this.runtime.computeLevel(this.deps);
5666
+ this.subscribeToDeps();
5667
+ }
5668
+ destroy() {
5669
+ if (this.dequeueScheduled) {
5670
+ this.dequeueScheduled();
5671
+ this.dequeueScheduled = void 0;
5672
+ }
5673
+ this.unsubscribeFromAllDeps();
5674
+ if (this.dequeueResubscribe) {
5675
+ this.dequeueResubscribe();
5676
+ this.dequeueResubscribe = void 0;
5677
+ }
5678
+ this.isResubscribeEnqueued = false;
5679
+ this.isTemporarilyUnsubscribedForFlush = false;
5680
+ }
5681
+ runOnce() {
5682
+ this.run();
5683
+ }
5684
+ subscribeToDeps() {
5685
+ if (this.sourceUnsubscribers.length > 0 || this.computedUnsubscribers.length > 0)
5686
+ return;
5687
+ for (const dep of this.sourceDeps) {
5688
+ this.sourceUnsubscribers.push(
5689
+ dep.subscribe(this.onSourceInvalidate)
5690
+ );
5691
+ }
5692
+ for (const dep of this.computedDeps) {
5693
+ this.computedUnsubscribers.push(
5694
+ dep.subscribe(this.onComputedInvalidate)
5695
+ );
5696
+ }
5697
+ }
5698
+ unsubscribeFromSourceDeps() {
5699
+ if (this.sourceUnsubscribers.length === 0) return;
5700
+ for (const unsubscribe of this.sourceUnsubscribers) unsubscribe();
5701
+ this.sourceUnsubscribers = [];
5702
+ }
5703
+ unsubscribeFromAllDeps() {
5704
+ this.unsubscribeFromSourceDeps();
5705
+ if (this.computedUnsubscribers.length === 0) return;
5706
+ for (const unsubscribe of this.computedUnsubscribers) unsubscribe();
5707
+ this.computedUnsubscribers = [];
5708
+ }
5709
+ ensureResubscribeAfterFlush() {
5710
+ if (this.isResubscribeEnqueued) return;
5711
+ this.isResubscribeEnqueued = true;
5712
+ this.dequeueResubscribe = this.runtime.scheduleAfterFlush(
5713
+ this.onAfterFlush
5714
+ );
5715
+ }
5716
+ };
5717
+
5718
+ // src/modules/computed/enums/EOIMComputedEventType.ts
5719
+ var EOIMComputedEventType = /* @__PURE__ */ ((EOIMComputedEventType2) => {
5720
+ EOIMComputedEventType2["UPDATE"] = "update";
5721
+ return EOIMComputedEventType2;
5722
+ })(EOIMComputedEventType || {});
5723
+
5724
+ // src/modules/computed/core/OIMComputed.ts
5725
+ var OIMComputed = class {
5726
+ constructor(runtime, opts) {
5727
+ this.emitter = new OIMEventEmitter();
5728
+ this.hasValue = false;
5729
+ this.isDirty = true;
5730
+ this.runtime = runtime;
5731
+ this.compute = opts.compute;
5732
+ this.compare = opts.compare ?? Object.is;
5733
+ this._deps = opts.deps ?? [];
5734
+ this.updateEventEmitter = new OIMUpdateEventEmitter(
5735
+ this.runtime.queue,
5736
+ "immediate"
5737
+ );
5738
+ const level = this.runtime.computeLevel(this._deps);
5739
+ this.runtime.registerLevel(this, level);
5740
+ this.effect = new OIMEffect(
5741
+ this.runtime,
5742
+ {
5743
+ deps: this._deps,
5744
+ onUpdate: () => {
5745
+ this.isDirty = true;
5746
+ },
5747
+ run: () => {
5748
+ this.recomputeAndEmitIfChanged();
5749
+ }
5750
+ },
5751
+ level
5752
+ );
5753
+ }
5754
+ get needsRecompute() {
5755
+ return this.isDirty;
5756
+ }
5757
+ get isReady() {
5758
+ return this.hasValue;
5759
+ }
5760
+ get deps() {
5761
+ return this._deps;
5762
+ }
5763
+ get() {
5764
+ this.recomputeAndEmitIfChanged();
5765
+ return this.value;
5766
+ }
5767
+ getIfReady() {
5768
+ return this.hasValue ? this.value : void 0;
5769
+ }
5770
+ destroy() {
5771
+ this.effect.destroy();
5772
+ this.updateEventEmitter.destroy();
5773
+ this.emitter.offAll();
5774
+ }
5775
+ recomputeAndEmitIfChanged() {
5776
+ if (!this.isDirty) return;
5777
+ const prev = this.value;
5778
+ const hadPrev = this.hasValue;
5779
+ const next = this.compute();
5780
+ this.value = next;
5781
+ this.hasValue = true;
5782
+ this.isDirty = false;
5783
+ const changed = !hadPrev || !this.compare(prev, next);
5784
+ if (!changed) return;
5785
+ this.emitter.emit("update" /* UPDATE */, {
5786
+ keys: ["value"]
5787
+ });
5788
+ this.updateEventEmitter.markUpdatedKey("value");
5789
+ }
5790
+ };
5791
+
5792
+ // src/dx/OIMCollectionKit.ts
5793
+ function createOIMCollectionKit(queue, opts) {
5794
+ const collection = new OIMReactiveCollection(queue, opts);
5795
+ const indexFactory = new OIMCollectionIndexFactory(queue, collection);
5796
+ const select = new OIMCollectionSelectors(queue, collection);
5797
+ const runtime = getOIMComputeRuntime(queue);
5798
+ const scope = new OIMDisposeScope();
5799
+ scope.add(collection);
5800
+ return {
5801
+ queue,
5802
+ collection,
5803
+ indexFactory,
5804
+ select,
5805
+ on,
5806
+ computed: (deps, compute, options) => {
5807
+ const computed = new OIMComputed(runtime, {
5808
+ deps,
5809
+ compute,
5810
+ compare: options?.compare
5811
+ });
5812
+ scope.add(computed);
5813
+ return computed;
5814
+ },
5815
+ effect: (deps, run, options) => {
5816
+ const effect = new OIMEffect(runtime, {
5817
+ deps,
5818
+ run,
5819
+ onUpdate: options?.onUpdate
5820
+ });
5821
+ scope.add(effect);
5822
+ return effect;
5823
+ },
5824
+ scope,
5825
+ destroy: () => scope.destroy()
5826
+ };
5827
+ }
5828
+
5829
+ // src/dx/peekOIMComputeRuntime.ts
5830
+ function peekOIMComputeRuntime(queue) {
5831
+ const runtime = oimComputeRuntimeByQueue.get(queue);
5832
+ return runtime === void 0 || runtime.isDestroyed ? void 0 : runtime;
5833
+ }
5834
+
5835
+ // src/dx/setOIMComputeRuntime.ts
5836
+ function setOIMComputeRuntime(queue, runtime) {
5837
+ if (runtime.queue !== queue) {
5838
+ throw new Error(
5839
+ "setOIMComputeRuntime: the runtime is attached to a different queue. A runtime drains on its own queue and would never run here."
5840
+ );
5841
+ }
5842
+ oimComputeRuntimeByQueue.set(queue, runtime);
5843
+ }
5844
+
5845
+ // src/modules/wrapper/collection/OIMCollectionChangedFields.ts
5846
+ var OIMCollectionChangedFields = class {
5847
+ constructor(queue, collection, opts) {
5848
+ this.isInWrite = false;
5849
+ this.lastEntitySnapshotByPk = /* @__PURE__ */ new Map();
5850
+ // PKs written through this wrapper during the current flush. Their fields are
5851
+ // already tracked accurately via trackPatch, so the flush-time external-write
5852
+ // handler skips them — no redundant diff + snapshot re-clone. Cleared per flush.
5853
+ this.writtenThroughThisFlush = /* @__PURE__ */ new Set();
5854
+ this.changedFieldsByPk = /* @__PURE__ */ new Map();
5855
+ this.changedPksByField = /* @__PURE__ */ new Map();
5856
+ this.onAfterFlush = () => {
5857
+ this.clear();
5858
+ this.writtenThroughThisFlush.clear();
5859
+ };
5860
+ this.onCollectionUpdate = (payload) => {
5861
+ if (this.isInWrite) return;
5862
+ if (payload.pks.length === 0) return;
5863
+ for (let i = 0; i < payload.pks.length; i++) {
5864
+ const pk = payload.pks[i];
5865
+ if (this.writtenThroughThisFlush.has(pk)) continue;
5866
+ this.trackExternalMutationByPk(pk);
5867
+ }
5868
+ };
5869
+ this.collection = collection;
5870
+ this.detectExternalMutations = opts?.detectExternalMutations !== false;
5871
+ this.selectPk = opts?.selectPk ?? (() => {
5872
+ throw new Error(
5873
+ `[OIMCollectionChangedFields]: selectPk was not provided. Use upsertOneByPk(...) or pass opts.selectPk.`
5874
+ );
5875
+ });
5876
+ this.changedPksEventEmitter = new OIMUpdateEventEmitter(
5877
+ queue,
5878
+ "after_flush"
5879
+ );
5880
+ this.changedFieldsEventEmitter = new OIMUpdateEventEmitter(queue, "after_flush");
5881
+ this.unsubscribeAfterFlush = queue.emitter.on(
5882
+ 0 /* AFTER_FLUSH */,
5883
+ this.onAfterFlush
5884
+ );
5885
+ if (this.detectExternalMutations) {
5886
+ const maybeReactive = collection;
5887
+ this.unsubscribeFromCollectionEmitter = maybeReactive.subscribeOnAnyUpdate ? maybeReactive.subscribeOnAnyUpdate((pks) => {
5888
+ this.onCollectionUpdate({
5889
+ pks
5890
+ });
5891
+ }) : collection.emitter.on(
5892
+ 0 /* UPDATE */,
5893
+ this.onCollectionUpdate
5894
+ );
5895
+ }
5896
+ }
5897
+ upsertOneByPk(pk, patch) {
5898
+ const slot = this.withWrite(() => {
5899
+ this.trackPatch(pk, patch);
5900
+ return this.collection.upsertOneByPk(pk, patch);
5901
+ });
5902
+ this.updateEntitySnapshot(pk);
5903
+ return slot;
5904
+ }
5905
+ upsertOne(entity) {
5906
+ const slot = this.withWrite(() => {
5907
+ const pk = this.selectPk(entity);
5908
+ this.trackPatch(pk, entity);
5909
+ return this.collection.upsertOne(entity);
5910
+ });
5911
+ this.updateEntitySnapshot(slot.pk);
5912
+ return slot;
5913
+ }
5914
+ upsertMany(entities) {
5915
+ const pks = [];
5916
+ const slots = this.withWrite(() => {
5917
+ for (const entity of entities) {
5918
+ const pk = this.selectPk(entity);
5919
+ pks.push(pk);
5920
+ this.trackPatch(pk, entity);
5921
+ }
5922
+ return this.collection.upsertMany(entities);
5923
+ });
5924
+ for (let i = 0; i < pks.length; i++) this.updateEntitySnapshot(pks[i]);
5925
+ return slots;
5926
+ }
5927
+ getChangedFieldsByPk(pk) {
5928
+ return this.changedFieldsByPk.get(pk) ?? /* @__PURE__ */ new Set();
5929
+ }
5930
+ consumeChangedFieldsByPk(pk) {
5931
+ const existing = this.changedFieldsByPk.get(pk);
5932
+ if (!existing) return /* @__PURE__ */ new Set();
5933
+ return new Set(existing);
5934
+ }
5935
+ getChangedPksByField(field) {
5936
+ return this.changedPksByField.get(field) ?? /* @__PURE__ */ new Set();
5937
+ }
5938
+ consumeChangedPksByField(field) {
5939
+ const existing = this.changedPksByField.get(field);
5940
+ if (!existing) return /* @__PURE__ */ new Set();
5941
+ return new Set(existing);
5942
+ }
5943
+ clear() {
5944
+ this.changedFieldsByPk.clear();
5945
+ this.changedPksByField.clear();
5946
+ }
5947
+ destroy() {
5948
+ this.changedPksEventEmitter.destroy();
5453
5949
  this.changedFieldsEventEmitter.destroy();
5454
5950
  this.unsubscribeAfterFlush();
5455
5951
  this.unsubscribeFromCollectionEmitter?.();
@@ -5488,8 +5984,9 @@ var OIMCollectionChangedFields = class {
5488
5984
  Object.keys(nextObj).forEach((k) => keys.add(k));
5489
5985
  const changed = [];
5490
5986
  keys.forEach((k) => {
5491
- if (prevObj[k] !== nextObj[k])
5987
+ if (prevObj[k] !== nextObj[k]) {
5492
5988
  changed.push(k);
5989
+ }
5493
5990
  });
5494
5991
  return changed;
5495
5992
  }
@@ -5655,10 +6152,15 @@ var OIMOrderedListMappedCommandStream = class _OIMOrderedListMappedCommandStream
5655
6152
  try {
5656
6153
  const subs = this.subscribers.get(key);
5657
6154
  if (subs && subs.size > 0) {
5658
- const snapshot = Array.from(subs);
5659
- for (let s = 0; s < snapshot.length; s++) {
5660
- const handler = snapshot[s];
5661
- if (subs.has(handler)) handler();
6155
+ if (subs.size === 1) {
6156
+ const only = subs.values().next().value;
6157
+ if (only) only();
6158
+ } else {
6159
+ const snapshot = Array.from(subs);
6160
+ for (let s = 0; s < snapshot.length; s++) {
6161
+ const handler = snapshot[s];
6162
+ if (subs.has(handler)) handler();
6163
+ }
5662
6164
  }
5663
6165
  }
5664
6166
  } finally {
@@ -5952,10 +6454,13 @@ var OIMEventQueueSchedulerAnimationFrame = class extends OIMEventQueueScheduler
5952
6454
  this.flush();
5953
6455
  });
5954
6456
  } else {
5955
- this.frameId = setTimeout(() => {
5956
- this.frameId = void 0;
5957
- this.flush();
5958
- }, 16);
6457
+ this.frameId = this.unref(
6458
+ setTimeout(() => {
6459
+ this.frameId = void 0;
6460
+ this.flush();
6461
+ }, 16)
6462
+ // ~60fps
6463
+ );
5959
6464
  }
5960
6465
  }
5961
6466
  cancel() {
@@ -6001,10 +6506,12 @@ var OIMEventQueueSchedulerTimeout = class extends OIMEventQueueScheduler {
6001
6506
  }
6002
6507
  schedule() {
6003
6508
  if (this.timeoutId !== void 0) return;
6004
- this.timeoutId = setTimeout(() => {
6005
- this.timeoutId = void 0;
6006
- this.flush();
6007
- }, this.delay);
6509
+ this.timeoutId = this.unref(
6510
+ setTimeout(() => {
6511
+ this.timeoutId = void 0;
6512
+ this.flush();
6513
+ }, this.delay)
6514
+ );
6008
6515
  }
6009
6516
  cancel() {
6010
6517
  if (this.timeoutId === void 0) return;
@@ -6038,13 +6545,13 @@ var OIMEventQueueSchedulerImmediate = class extends OIMEventQueueScheduler {
6038
6545
  this.flush();
6039
6546
  };
6040
6547
  if (this.useSetImmediate) {
6041
- this.immediateId = setImmediate(callback);
6548
+ this.immediateId = this.unref(setImmediate(callback));
6042
6549
  } else if (this.useMessageChannel && this.messageChannel) {
6043
6550
  this.immediateId = 1;
6044
6551
  this.pendingCallback = callback;
6045
6552
  this.messageChannel.port1.postMessage(null);
6046
6553
  } else {
6047
- this.immediateId = setTimeout(callback, 0);
6554
+ this.immediateId = this.unref(setTimeout(callback, 0));
6048
6555
  }
6049
6556
  }
6050
6557
  cancel() {
@@ -6137,242 +6644,6 @@ var OIMDBSettings = class {
6137
6644
  OIMDBSettings.UPDATED_KEYS_INDEX_KEY = "__updatedKeys__";
6138
6645
  OIMDBSettings.FLUSHING_KEYS_INDEX_KEY = "__flushingKeys__";
6139
6646
 
6140
- // src/modules/effect/core/OIMEffectDependencyComputed.ts
6141
- var OIMEffectDependencyComputed = class {
6142
- constructor(source) {
6143
- this.source = source;
6144
- }
6145
- subscribe(onUpdate) {
6146
- return this.source.emitter.on("update", onUpdate);
6147
- }
6148
- };
6149
-
6150
- // src/modules/effect/core/OIMEffect.ts
6151
- var OIMEffect = class {
6152
- constructor(runtime, opts) {
6153
- this.sourceDeps = [];
6154
- this.computedDeps = [];
6155
- this.sourceUnsubscribers = [];
6156
- this.computedUnsubscribers = [];
6157
- this.isResubscribeEnqueued = false;
6158
- this.isTemporarilyUnsubscribedForFlush = false;
6159
- this.isScheduled = false;
6160
- this.runTask = () => {
6161
- this.isScheduled = false;
6162
- this.dequeueScheduled = void 0;
6163
- this.runOnce();
6164
- };
6165
- this.onSourceInvalidate = () => {
6166
- this.onUpdate?.();
6167
- if (!this.isTemporarilyUnsubscribedForFlush) {
6168
- this.isTemporarilyUnsubscribedForFlush = true;
6169
- this.unsubscribeFromSourceDeps();
6170
- this.ensureResubscribeAfterFlush();
6171
- }
6172
- if (this.isScheduled) return;
6173
- this.isScheduled = true;
6174
- this.dequeueScheduled = this.runtime.schedule(this.runTask);
6175
- };
6176
- this.onComputedInvalidate = () => {
6177
- this.onUpdate?.();
6178
- if (this.isScheduled) return;
6179
- this.isScheduled = true;
6180
- this.dequeueScheduled = this.runtime.schedule(this.runTask);
6181
- };
6182
- this.onAfterFlush = () => {
6183
- if (!this.isResubscribeEnqueued) return;
6184
- this.isResubscribeEnqueued = false;
6185
- this.dequeueResubscribe = void 0;
6186
- this.isTemporarilyUnsubscribedForFlush = false;
6187
- if (this.sourceUnsubscribers.length === 0) {
6188
- for (const dep of this.sourceDeps) {
6189
- this.sourceUnsubscribers.push(
6190
- dep.subscribe(this.onSourceInvalidate)
6191
- );
6192
- }
6193
- }
6194
- };
6195
- this.runtime = runtime;
6196
- this.onUpdate = opts.onUpdate;
6197
- this.run = opts.run;
6198
- this.deps = opts.deps ?? [];
6199
- for (const dep of this.deps) {
6200
- if (dep instanceof OIMEffectDependencyComputed) {
6201
- this.computedDeps.push(dep);
6202
- } else {
6203
- this.sourceDeps.push(dep);
6204
- }
6205
- }
6206
- this.subscribeToDeps();
6207
- }
6208
- destroy() {
6209
- if (this.dequeueScheduled) {
6210
- this.dequeueScheduled();
6211
- this.dequeueScheduled = void 0;
6212
- }
6213
- this.unsubscribeFromAllDeps();
6214
- if (this.dequeueResubscribe) {
6215
- this.dequeueResubscribe();
6216
- this.dequeueResubscribe = void 0;
6217
- }
6218
- this.isResubscribeEnqueued = false;
6219
- this.isTemporarilyUnsubscribedForFlush = false;
6220
- }
6221
- runOnce() {
6222
- this.run();
6223
- }
6224
- subscribeToDeps() {
6225
- if (this.sourceUnsubscribers.length > 0 || this.computedUnsubscribers.length > 0)
6226
- return;
6227
- for (const dep of this.sourceDeps) {
6228
- this.sourceUnsubscribers.push(
6229
- dep.subscribe(this.onSourceInvalidate)
6230
- );
6231
- }
6232
- for (const dep of this.computedDeps) {
6233
- this.computedUnsubscribers.push(
6234
- dep.subscribe(this.onComputedInvalidate)
6235
- );
6236
- }
6237
- }
6238
- unsubscribeFromSourceDeps() {
6239
- if (this.sourceUnsubscribers.length === 0) return;
6240
- for (const unsubscribe of this.sourceUnsubscribers) unsubscribe();
6241
- this.sourceUnsubscribers = [];
6242
- }
6243
- unsubscribeFromAllDeps() {
6244
- this.unsubscribeFromSourceDeps();
6245
- if (this.computedUnsubscribers.length === 0) return;
6246
- for (const unsubscribe of this.computedUnsubscribers) unsubscribe();
6247
- this.computedUnsubscribers = [];
6248
- }
6249
- ensureResubscribeAfterFlush() {
6250
- if (this.isResubscribeEnqueued) return;
6251
- this.isResubscribeEnqueued = true;
6252
- this.dequeueResubscribe = this.runtime.scheduleAfterFlush(
6253
- this.onAfterFlush
6254
- );
6255
- }
6256
- };
6257
-
6258
- // src/modules/computed/enums/EOIMComputedEventType.ts
6259
- var EOIMComputedEventType = /* @__PURE__ */ ((EOIMComputedEventType2) => {
6260
- EOIMComputedEventType2["UPDATE"] = "update";
6261
- return EOIMComputedEventType2;
6262
- })(EOIMComputedEventType || {});
6263
-
6264
- // src/modules/computed/core/OIMComputed.ts
6265
- var OIMComputed = class {
6266
- constructor(runtime, opts) {
6267
- this.emitter = new OIMEventEmitter();
6268
- this.hasValue = false;
6269
- this.isDirty = true;
6270
- this.runtime = runtime;
6271
- this.compute = opts.compute;
6272
- this.compare = opts.compare ?? Object.is;
6273
- this._deps = opts.deps ?? [];
6274
- this.updateEventEmitter = new OIMUpdateEventEmitter(
6275
- this.runtime.queue,
6276
- "immediate"
6277
- );
6278
- this.effect = new OIMEffect(this.runtime, {
6279
- deps: this._deps,
6280
- onUpdate: () => {
6281
- this.isDirty = true;
6282
- },
6283
- run: () => {
6284
- this.recomputeAndEmitIfChanged();
6285
- }
6286
- });
6287
- }
6288
- get needsRecompute() {
6289
- return this.isDirty;
6290
- }
6291
- get isReady() {
6292
- return this.hasValue;
6293
- }
6294
- get deps() {
6295
- return this._deps;
6296
- }
6297
- get() {
6298
- this.recomputeAndEmitIfChanged();
6299
- return this.value;
6300
- }
6301
- getIfReady() {
6302
- return this.hasValue ? this.value : void 0;
6303
- }
6304
- destroy() {
6305
- this.effect.destroy();
6306
- this.updateEventEmitter.destroy();
6307
- this.emitter.offAll();
6308
- }
6309
- recomputeAndEmitIfChanged() {
6310
- if (!this.isDirty) return;
6311
- const prev = this.value;
6312
- const hadPrev = this.hasValue;
6313
- const next = this.compute();
6314
- this.value = next;
6315
- this.hasValue = true;
6316
- this.isDirty = false;
6317
- const changed = !hadPrev || !this.compare(prev, next);
6318
- if (!changed) return;
6319
- this.emitter.emit("update" /* UPDATE */, {
6320
- keys: ["value"]
6321
- });
6322
- this.updateEventEmitter.markUpdatedKey("value");
6323
- }
6324
- };
6325
-
6326
- // src/modules/effect/core/OIMEffectDependencyKeyed.ts
6327
- var OIMEffectDependencyKeyed = class {
6328
- constructor(source, key) {
6329
- this.source = source;
6330
- this.key = key;
6331
- }
6332
- subscribe(onUpdate) {
6333
- return this.source.subscribeOnKey(this.key, onUpdate);
6334
- }
6335
- };
6336
-
6337
- // src/modules/effect/core/OIMEffectDependencyKeyedObject.ts
6338
- var OIMEffectDependencyKeyedObject = class {
6339
- constructor(object, key) {
6340
- this.dep = new OIMEffectDependencyKeyed(object, key);
6341
- }
6342
- get source() {
6343
- return this.dep.source;
6344
- }
6345
- subscribe(onUpdate) {
6346
- return this.dep.subscribe(onUpdate);
6347
- }
6348
- };
6349
-
6350
- // src/modules/effect/core/OIMEffectDependencyKeyedCollection.ts
6351
- var OIMEffectDependencyKeyedCollection = class {
6352
- constructor(collection, pk) {
6353
- this.dep = new OIMEffectDependencyKeyed(collection, pk);
6354
- }
6355
- get source() {
6356
- return this.dep.source;
6357
- }
6358
- subscribe(onUpdate) {
6359
- return this.dep.subscribe(onUpdate);
6360
- }
6361
- };
6362
-
6363
- // src/modules/effect/core/OIMEffectDependencyKeyedIndex.ts
6364
- var OIMEffectDependencyKeyedIndex = class {
6365
- constructor(index, key) {
6366
- this.dep = new OIMEffectDependencyKeyed(index, key);
6367
- }
6368
- get source() {
6369
- return this.dep.source;
6370
- }
6371
- subscribe(onUpdate) {
6372
- return this.dep.subscribe(onUpdate);
6373
- }
6374
- };
6375
-
6376
6647
  // src/modules/selector/core/OIMObjectValueByKeySelector.ts
6377
6648
  var OIMObjectValueByKeySelector = class extends OIMSelector {
6378
6649
  constructor(runtime, obj, key) {
@@ -6450,8 +6721,9 @@ var OIMIndexPksByKeyArrayBasedSelector = class extends OIMSelector {
6450
6721
  areEqual(prev, next) {
6451
6722
  if (prev === next) return true;
6452
6723
  if (prev.length !== next.length) return false;
6453
- for (let i = 0; i < prev.length; i++)
6724
+ for (let i = 0; i < prev.length; i++) {
6454
6725
  if (prev[i] !== next[i]) return false;
6726
+ }
6455
6727
  return true;
6456
6728
  }
6457
6729
  };
@@ -6474,8 +6746,9 @@ var OIMIndexPksByKeysSetBasedSelector = class extends OIMSelector {
6474
6746
  areEqual(prev, next) {
6475
6747
  if (prev === next) return true;
6476
6748
  if (prev.length !== next.length) return false;
6477
- for (let i = 0; i < prev.length; i++)
6749
+ for (let i = 0; i < prev.length; i++) {
6478
6750
  if (prev[i] !== next[i]) return false;
6751
+ }
6479
6752
  return true;
6480
6753
  }
6481
6754
  };
@@ -6498,8 +6771,9 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
6498
6771
  areEqual(prev, next) {
6499
6772
  if (prev === next) return true;
6500
6773
  if (prev.length !== next.length) return false;
6501
- for (let i = 0; i < prev.length; i++)
6774
+ for (let i = 0; i < prev.length; i++) {
6502
6775
  if (prev[i] !== next[i]) return false;
6776
+ }
6503
6777
  return true;
6504
6778
  }
6505
6779
  };
@@ -6529,6 +6803,7 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
6529
6803
  OIMComputeRuntime,
6530
6804
  OIMComputed,
6531
6805
  OIMDBSettings,
6806
+ OIMDerivedCollection,
6532
6807
  OIMDerivedCollectionGlobalIndexArrayBased,
6533
6808
  OIMDerivedCollectionGlobalIndexSetBased,
6534
6809
  OIMDerivedCollectionIndexArrayBased,
@@ -6624,5 +6899,9 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
6624
6899
  createOIMIndexSlotMap,
6625
6900
  createOIMOrderedListCommandStreamDiffDriven,
6626
6901
  createOIMOrderedListMappedCommandStream,
6627
- diffOrderedListByPk
6902
+ diffOrderedListByPk,
6903
+ getOIMComputeRuntime,
6904
+ on,
6905
+ peekOIMComputeRuntime,
6906
+ setOIMComputeRuntime
6628
6907
  });