@oimdb/core 1.4.0 → 1.4.1

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
@@ -22,17 +22,18 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  EOIMCollectionEventType: () => EOIMCollectionEventType,
24
24
  EOIMComputedEventType: () => EOIMComputedEventType,
25
- EOIMEffectPhase: () => EOIMEffectPhase,
26
25
  EOIMEventQueueEventType: () => EOIMEventQueueEventType,
27
26
  EOIMEventQueueSchedulerEventType: () => EOIMEventQueueSchedulerEventType,
28
27
  EOIMIndexEventType: () => EOIMIndexEventType,
29
28
  EOIMObjectEventType: () => EOIMObjectEventType,
30
- EOIMUpdateEventCoalescerEventType: () => EOIMUpdateEventCoalescerEventType,
31
29
  OIMCollection: () => OIMCollection,
30
+ OIMCollectionByPkSelector: () => OIMCollectionByPkSelector,
31
+ OIMCollectionByPksSelector: () => OIMCollectionByPksSelector,
32
32
  OIMCollectionChangedFieldsWrapper: () => OIMCollectionChangedFieldsWrapper,
33
33
  OIMCollectionStore: () => OIMCollectionStore,
34
34
  OIMCollectionStoreMapDriven: () => OIMCollectionStoreMapDriven,
35
35
  OIMComparatorFactory: () => OIMComparatorFactory,
36
+ OIMComputativeRuntime: () => OIMComputativeRuntime,
36
37
  OIMComputed: () => OIMComputed,
37
38
  OIMDBSettings: () => OIMDBSettings,
38
39
  OIMEffect: () => OIMEffect,
@@ -41,6 +42,8 @@ __export(index_exports, {
41
42
  OIMEffectDependencyKeyedCollection: () => OIMEffectDependencyKeyedCollection,
42
43
  OIMEffectDependencyKeyedIndex: () => OIMEffectDependencyKeyedIndex,
43
44
  OIMEffectDependencyKeyedObject: () => OIMEffectDependencyKeyedObject,
45
+ OIMEntitiesByIndexKeyArrayBasedSelector: () => OIMEntitiesByIndexKeyArrayBasedSelector,
46
+ OIMEntitiesByIndexKeySetBasedSelector: () => OIMEntitiesByIndexKeySetBasedSelector,
44
47
  OIMEntityUpdaterFactory: () => OIMEntityUpdaterFactory,
45
48
  OIMEventEmitter: () => OIMEventEmitter,
46
49
  OIMEventQueue: () => OIMEventQueue,
@@ -55,6 +58,10 @@ __export(index_exports, {
55
58
  OIMIndexManualArrayBased: () => OIMIndexManualArrayBased,
56
59
  OIMIndexManualOrderedArrayBased: () => OIMIndexManualOrderedArrayBased,
57
60
  OIMIndexManualSetBased: () => OIMIndexManualSetBased,
61
+ OIMIndexPksByKeyArrayBasedSelector: () => OIMIndexPksByKeyArrayBasedSelector,
62
+ OIMIndexPksByKeySetBasedSelector: () => OIMIndexPksByKeySetBasedSelector,
63
+ OIMIndexPksByKeysArrayBasedSelector: () => OIMIndexPksByKeysArrayBasedSelector,
64
+ OIMIndexPksByKeysSetBasedSelector: () => OIMIndexPksByKeysSetBasedSelector,
58
65
  OIMIndexSetBased: () => OIMIndexSetBased,
59
66
  OIMIndexStoreArrayBased: () => OIMIndexStoreArrayBased,
60
67
  OIMIndexStoreMapDrivenArrayBased: () => OIMIndexStoreMapDrivenArrayBased,
@@ -65,6 +72,8 @@ __export(index_exports, {
65
72
  OIMObjectStore: () => OIMObjectStore,
66
73
  OIMObjectStoreMapDriven: () => OIMObjectStoreMapDriven,
67
74
  OIMObjectStoreRecordDriven: () => OIMObjectStoreRecordDriven,
75
+ OIMObjectValueByKeySelector: () => OIMObjectValueByKeySelector,
76
+ OIMObjectValuesByKeysSelector: () => OIMObjectValuesByKeysSelector,
68
77
  OIMOrderedListIndexCommandStreamWrapper: () => OIMOrderedListIndexCommandStreamWrapper,
69
78
  OIMPkSelectorFactory: () => OIMPkSelectorFactory,
70
79
  OIMRICollection: () => OIMRICollection,
@@ -74,13 +83,7 @@ __export(index_exports, {
74
83
  OIMReactiveIndexManualSetBased: () => OIMReactiveIndexManualSetBased,
75
84
  OIMReactiveIndexSetBased: () => OIMReactiveIndexSetBased,
76
85
  OIMReactiveObject: () => OIMReactiveObject,
77
- OIMUpdateEventCoalescer: () => OIMUpdateEventCoalescer,
78
- OIMUpdateEventCoalescerCollection: () => OIMUpdateEventCoalescerCollection,
79
- OIMUpdateEventCoalescerComputed: () => OIMUpdateEventCoalescerComputed,
80
- OIMUpdateEventCoalescerIndex: () => OIMUpdateEventCoalescerIndex,
81
- OIMUpdateEventCoalescerManual: () => OIMUpdateEventCoalescerManual,
82
- OIMUpdateEventCoalescerObject: () => OIMUpdateEventCoalescerObject,
83
- OIMUpdateEventEmitter: () => OIMUpdateEventEmitter
86
+ OIMSelector: () => OIMSelector
84
87
  });
85
88
  module.exports = __toCommonJS(index_exports);
86
89
 
@@ -101,6 +104,11 @@ var OIMEventEmitter = class {
101
104
  constructor() {
102
105
  this.buckets = {};
103
106
  }
107
+ hasHandlers(event) {
108
+ const bucket = this.buckets[event];
109
+ if (!bucket) return false;
110
+ return bucket.indexByHandler.size > 0;
111
+ }
104
112
  getOrCreateBucket(event) {
105
113
  let bucket = this.buckets[event];
106
114
  if (!bucket) {
@@ -383,6 +391,12 @@ var OIMIndexSetBased = class {
383
391
  emitUpdate(keys) {
384
392
  this.emitter.emit(0 /* UPDATE */, { keys });
385
393
  }
394
+ /**
395
+ * Emit update event for a single key (fast-path override point).
396
+ */
397
+ emitUpdateOne(key) {
398
+ this.emitUpdate([key]);
399
+ }
386
400
  };
387
401
 
388
402
  // src/core/OIMIndexStoreMapDrivenArrayBased.ts
@@ -532,45 +546,34 @@ var OIMIndexArrayBased = class {
532
546
  emitUpdate(keys) {
533
547
  this.emitter.emit(0 /* UPDATE */, { keys });
534
548
  }
549
+ /**
550
+ * Emit update event for a single key (fast-path override point).
551
+ */
552
+ emitUpdateOne(key) {
553
+ this.emitUpdate([key]);
554
+ }
535
555
  };
536
556
 
537
- // src/enum/EOIMUpdateEventCoalescerEventType.ts
538
- var EOIMUpdateEventCoalescerEventType = /* @__PURE__ */ ((EOIMUpdateEventCoalescerEventType2) => {
539
- EOIMUpdateEventCoalescerEventType2[EOIMUpdateEventCoalescerEventType2["HAS_CHANGES"] = 0] = "HAS_CHANGES";
540
- EOIMUpdateEventCoalescerEventType2[EOIMUpdateEventCoalescerEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
541
- EOIMUpdateEventCoalescerEventType2[EOIMUpdateEventCoalescerEventType2["AFTER_FLUSH"] = 2] = "AFTER_FLUSH";
542
- return EOIMUpdateEventCoalescerEventType2;
543
- })(EOIMUpdateEventCoalescerEventType || {});
557
+ // src/enum/EOIMEventQueueEventType.ts
558
+ var EOIMEventQueueEventType = /* @__PURE__ */ ((EOIMEventQueueEventType2) => {
559
+ EOIMEventQueueEventType2[EOIMEventQueueEventType2["AFTER_FLUSH"] = 0] = "AFTER_FLUSH";
560
+ EOIMEventQueueEventType2[EOIMEventQueueEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
561
+ return EOIMEventQueueEventType2;
562
+ })(EOIMEventQueueEventType || {});
544
563
 
545
564
  // src/core/OIMUpdateEventEmitter.ts
546
565
  var OIMUpdateEventEmitter = class {
547
- constructor(opts) {
548
- this.keyHandlersBeforeFlush = /* @__PURE__ */ new Map();
566
+ constructor(queue, deliveryMode = "queue") {
549
567
  this.keyHandlers = /* @__PURE__ */ new Map();
550
- this.isDestroyed = false;
551
- this.isDispatching = false;
552
- this.isInPrePhase = false;
553
- this.hasPendingDuringPrePhase = false;
554
568
  this.isFlushEnqueued = false;
555
- this.handleHasChanges = () => {
556
- if (this.isDispatching && this.isInPrePhase) {
557
- this.hasPendingDuringPrePhase = true;
558
- return;
559
- }
560
- if (this.isFlushEnqueued) return;
561
- this.isFlushEnqueued = true;
562
- this.queue.enqueue(this.onFlush);
563
- };
569
+ this.updatedKeys = /* @__PURE__ */ new Set();
570
+ this.isAfterFlushDeliveryScheduled = false;
564
571
  // Stable handler reference to avoid allocating a new closure on every HAS_CHANGES.
565
572
  this.onFlush = () => {
566
573
  this.isFlushEnqueued = false;
567
- if (this.isDestroyed) {
568
- this.coalescer.clearUpdatedKeys();
569
- this.coalescer.clearFlushingUpdatedKeys();
570
- return;
571
- }
572
- if (this.keyHandlers.size === 0 && this.keyHandlersBeforeFlush.size === 0) {
573
- this.coalescer.clearUpdatedKeys();
574
+ this.dequeueFlush = void 0;
575
+ if (this.keyHandlers.size === 0) {
576
+ this.updatedKeys.clear();
574
577
  return;
575
578
  }
576
579
  const makeHandlerSnapshot = (handlers) => {
@@ -578,95 +581,133 @@ var OIMUpdateEventEmitter = class {
578
581
  handlers.forEach((h) => snapshot.push(h));
579
582
  return snapshot;
580
583
  };
581
- try {
582
- this.isDispatching = true;
583
- this.isInPrePhase = true;
584
- this.hasPendingDuringPrePhase = false;
585
- const allKeysForNormalPhase = this.runPrePhaseDrain(makeHandlerSnapshot);
586
- this.isInPrePhase = false;
587
- this.runHandlersPhase(allKeysForNormalPhase, makeHandlerSnapshot);
588
- } finally {
589
- this.isInPrePhase = false;
590
- this.isDispatching = false;
591
- }
584
+ this.runSinglePass(makeHandlerSnapshot);
592
585
  };
593
- this.coalescer = opts.coalescer;
594
- this.coalescer.emitter.on(
595
- 0 /* HAS_CHANGES */,
596
- this.handleHasChanges
597
- );
598
- this.queue = opts.queue;
586
+ this.onAfterFlush = () => {
587
+ if (!this.isAfterFlushDeliveryScheduled) return;
588
+ this.isAfterFlushDeliveryScheduled = false;
589
+ this.deliverImmediate();
590
+ };
591
+ this.queue = queue;
592
+ this.deliveryMode = deliveryMode;
593
+ if (this.deliveryMode === "after_flush") {
594
+ this.unsubscribeAfterFlushDelivery = this.queue.emitter.on(
595
+ 0 /* AFTER_FLUSH */,
596
+ this.onAfterFlush
597
+ );
598
+ }
599
599
  }
600
600
  /**
601
- * Helps to run computed values etc.
601
+ * Mark keys as updated and schedule delivery.
602
+ *
603
+ * If there are no subscriptions, this is a no-op and does not allocate or retain keys.
602
604
  */
603
- runPrePhaseDrain(makeHandlerSnapshot) {
604
- const allKeysForNormalPhase = /* @__PURE__ */ new Set();
605
- let hasMorePreWork = true;
606
- while (hasMorePreWork) {
607
- this.coalescer.markUpdatedKeysAsFlushing();
608
- const flushingKeys = this.coalescer.getFlushingUpdatedKeys();
609
- flushingKeys.forEach((key) => allKeysForNormalPhase.add(key));
610
- try {
611
- if (this.keyHandlersBeforeFlush.size > 0) {
612
- if (flushingKeys.size * 2 < this.keyHandlersBeforeFlush.size) {
613
- flushingKeys.forEach((key) => {
614
- if (this.isDestroyed) return;
615
- const handlers = this.keyHandlersBeforeFlush.get(key);
616
- if (!handlers || handlers.size === 0) return;
617
- const handlerSnapshot = makeHandlerSnapshot(handlers);
618
- for (let i = 0; i < handlerSnapshot.length; i++) {
619
- if (this.isDestroyed) return;
620
- const handler = handlerSnapshot[i];
621
- if (handlers.has(handler)) handler();
622
- }
623
- });
624
- } else {
625
- this.keyHandlersBeforeFlush.forEach((handlers, key) => {
626
- if (this.isDestroyed) return;
627
- if (!flushingKeys.has(key)) return;
628
- if (!handlers || handlers.size === 0) return;
629
- const handlerSnapshot = makeHandlerSnapshot(handlers);
630
- for (let i = 0; i < handlerSnapshot.length; i++) {
631
- if (this.isDestroyed) return;
632
- const handler = handlerSnapshot[i];
633
- if (handlers.has(handler)) handler();
634
- }
635
- });
636
- }
637
- }
638
- } finally {
639
- this.coalescer.clearFlushingUpdatedKeys();
640
- }
641
- const nextHasMorePreWork = this.hasPendingDuringPrePhase || this.coalescer.getUpdatedKeys().size > 0;
642
- this.hasPendingDuringPrePhase = false;
643
- hasMorePreWork = nextHasMorePreWork;
605
+ markUpdatedKeys(keys) {
606
+ if (keys.length === 0) return;
607
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
608
+ throw new Error(
609
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
610
+ );
611
+ }
612
+ if (this.keyHandlers.size === 0) return;
613
+ let didAddAny = false;
614
+ for (let i = 0; i < keys.length; i++) {
615
+ const key = keys[i];
616
+ const handlers = this.keyHandlers.get(key);
617
+ if (!handlers || handlers.size === 0) continue;
618
+ this.updatedKeys.add(key);
619
+ didAddAny = true;
620
+ }
621
+ if (!didAddAny) return;
622
+ if (this.deliveryMode === "immediate") {
623
+ this.deliverImmediate();
624
+ return;
625
+ }
626
+ if (this.deliveryMode === "after_flush") {
627
+ this.isAfterFlushDeliveryScheduled = true;
628
+ return;
629
+ }
630
+ if (this.isFlushEnqueued) return;
631
+ this.isFlushEnqueued = true;
632
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
633
+ }
634
+ markUpdatedKey(key) {
635
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
636
+ throw new Error(
637
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
638
+ );
639
+ }
640
+ if (this.keyHandlers.size === 0) return;
641
+ const handlers = this.keyHandlers.get(key);
642
+ if (!handlers || handlers.size === 0) return;
643
+ this.updatedKeys.add(key);
644
+ if (this.deliveryMode === "immediate") {
645
+ this.deliverImmediate();
646
+ return;
647
+ }
648
+ if (this.deliveryMode === "after_flush") {
649
+ this.isAfterFlushDeliveryScheduled = true;
650
+ return;
644
651
  }
645
- return allKeysForNormalPhase;
652
+ if (this.isFlushEnqueued) return;
653
+ this.isFlushEnqueued = true;
654
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
646
655
  }
647
- runHandlersPhase(allKeysForNormalPhase, makeHandlerSnapshot) {
648
- if (this.keyHandlers.size === 0 || allKeysForNormalPhase.size === 0)
656
+ markAllUpdated() {
657
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
658
+ throw new Error(
659
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
660
+ );
661
+ }
662
+ if (this.keyHandlers.size === 0) return;
663
+ this.keyHandlers.forEach((_handlers, key) => {
664
+ this.updatedKeys.add(key);
665
+ });
666
+ if (this.deliveryMode === "immediate") {
667
+ this.deliverImmediate();
668
+ return;
669
+ }
670
+ if (this.deliveryMode === "after_flush") {
671
+ this.isAfterFlushDeliveryScheduled = true;
649
672
  return;
650
- if (allKeysForNormalPhase.size * 2 < this.keyHandlers.size) {
651
- allKeysForNormalPhase.forEach((key) => {
652
- if (this.isDestroyed) return;
673
+ }
674
+ if (this.isFlushEnqueued) return;
675
+ this.isFlushEnqueued = true;
676
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
677
+ }
678
+ deliverImmediate() {
679
+ const makeHandlerSnapshot = (handlers) => {
680
+ const snapshot = [];
681
+ handlers.forEach((h) => snapshot.push(h));
682
+ return snapshot;
683
+ };
684
+ this.runSinglePass(makeHandlerSnapshot);
685
+ }
686
+ /**
687
+ * Deliver updated keys and notify handlers once.
688
+ * Re-entrant updates during delivery are forbidden and will throw in markUpdatedKeys().
689
+ */
690
+ runSinglePass(makeHandlerSnapshot) {
691
+ if (this.updatedKeys.size === 0) return;
692
+ const flushingKeys = this.updatedKeys;
693
+ this.updatedKeys = /* @__PURE__ */ new Set();
694
+ if (this.keyHandlers.size === 0 || flushingKeys.size === 0) return;
695
+ if (flushingKeys.size * 2 < this.keyHandlers.size) {
696
+ flushingKeys.forEach((key) => {
653
697
  const handlers = this.keyHandlers.get(key);
654
698
  if (!handlers || handlers.size === 0) return;
655
699
  const handlerSnapshot = makeHandlerSnapshot(handlers);
656
700
  for (let i = 0; i < handlerSnapshot.length; i++) {
657
- if (this.isDestroyed) return;
658
701
  const handler = handlerSnapshot[i];
659
702
  if (handlers.has(handler)) handler();
660
703
  }
661
704
  });
662
705
  } else {
663
706
  this.keyHandlers.forEach((handlers, key) => {
664
- if (this.isDestroyed) return;
665
- if (!allKeysForNormalPhase.has(key)) return;
707
+ if (!flushingKeys.has(key)) return;
666
708
  if (!handlers || handlers.size === 0) return;
667
709
  const handlerSnapshot = makeHandlerSnapshot(handlers);
668
710
  for (let i = 0; i < handlerSnapshot.length; i++) {
669
- if (this.isDestroyed) return;
670
711
  const handler = handlerSnapshot[i];
671
712
  if (handlers.has(handler)) handler();
672
713
  }
@@ -674,14 +715,17 @@ var OIMUpdateEventEmitter = class {
674
715
  }
675
716
  }
676
717
  destroy() {
677
- if (this.isDestroyed) return;
678
- this.isDestroyed = true;
679
- this.coalescer.emitter.off(
680
- 0 /* HAS_CHANGES */,
681
- this.handleHasChanges
682
- );
683
- this.keyHandlersBeforeFlush.clear();
718
+ if (this.dequeueFlush) {
719
+ this.dequeueFlush();
720
+ this.dequeueFlush = void 0;
721
+ }
722
+ if (this.unsubscribeAfterFlushDelivery) {
723
+ this.unsubscribeAfterFlushDelivery();
724
+ this.unsubscribeAfterFlushDelivery = void 0;
725
+ }
726
+ this.keyHandlers.forEach((handlers) => handlers.clear());
684
727
  this.keyHandlers.clear();
728
+ this.updatedKeys.clear();
685
729
  }
686
730
  /**
687
731
  * Get performance metrics for monitoring and debugging
@@ -702,32 +746,13 @@ var OIMUpdateEventEmitter = class {
702
746
  * Check if there are any active subscriptions
703
747
  */
704
748
  hasSubscriptions() {
705
- return this.keyHandlers.size > 0 || this.keyHandlersBeforeFlush.size > 0;
749
+ return this.keyHandlers.size > 0;
706
750
  }
707
751
  /**
708
752
  * Get the number of handlers for a specific key
709
753
  */
710
754
  getHandlerCount(key) {
711
- return (this.keyHandlers.get(key)?.size ?? 0) + (this.keyHandlersBeforeFlush.get(key)?.size ?? 0);
712
- }
713
- subscribeOnKeyBeforeFlush(key, handler) {
714
- let handlers = this.keyHandlersBeforeFlush.get(key);
715
- if (!handlers) {
716
- handlers = /* @__PURE__ */ new Set();
717
- this.keyHandlersBeforeFlush.set(key, handlers);
718
- }
719
- handlers.add(handler);
720
- return () => {
721
- this.unsubscribeFromKeyBeforeFlush(key, handler);
722
- };
723
- }
724
- unsubscribeFromKeyBeforeFlush(key, handler) {
725
- const handlers = this.keyHandlersBeforeFlush.get(key);
726
- if (!handlers) return;
727
- handlers.delete(handler);
728
- if (handlers.size === 0) {
729
- this.keyHandlersBeforeFlush.delete(key);
730
- }
755
+ return this.keyHandlers.get(key)?.size ?? 0;
731
756
  }
732
757
  subscribeOnKey(key, handler) {
733
758
  let handlers = this.keyHandlers.get(key);
@@ -746,6 +771,15 @@ var OIMUpdateEventEmitter = class {
746
771
  handlers.delete(handler);
747
772
  if (handlers.size === 0) {
748
773
  this.keyHandlers.delete(key);
774
+ this.updatedKeys.delete(key);
775
+ }
776
+ if (this.keyHandlers.size === 0) {
777
+ this.updatedKeys.clear();
778
+ if (this.dequeueFlush) {
779
+ this.dequeueFlush();
780
+ this.dequeueFlush = void 0;
781
+ }
782
+ this.isFlushEnqueued = false;
749
783
  }
750
784
  }
751
785
  subscribeOnKeys(keys, handler) {
@@ -767,30 +801,6 @@ var OIMUpdateEventEmitter = class {
767
801
  }
768
802
  };
769
803
  }
770
- subscribeOnKeysBeforeFlush(keys, handler) {
771
- const subscribedKeys = [];
772
- for (const key of keys) {
773
- let handlers = this.keyHandlersBeforeFlush.get(key);
774
- if (!handlers) {
775
- handlers = /* @__PURE__ */ new Set();
776
- this.keyHandlersBeforeFlush.set(key, handlers);
777
- }
778
- if (!handlers.has(handler)) {
779
- handlers.add(handler);
780
- subscribedKeys.push(key);
781
- }
782
- }
783
- return () => {
784
- for (const key of subscribedKeys) {
785
- this.unsubscribeFromKeyBeforeFlush(key, handler);
786
- }
787
- };
788
- }
789
- unsubscribeFromKeysBeforeFlush(keys, handler) {
790
- for (const key of keys) {
791
- this.unsubscribeFromKeyBeforeFlush(key, handler);
792
- }
793
- }
794
804
  unsubscribeFromKeys(keys, handler) {
795
805
  for (const key of keys) {
796
806
  this.unsubscribeFromKey(key, handler);
@@ -798,203 +808,11 @@ var OIMUpdateEventEmitter = class {
798
808
  }
799
809
  };
800
810
 
801
- // src/core/OIMIndexManualSetBased.ts
802
- var OIMIndexManualSetBased = class extends OIMIndexSetBased {
803
- constructor(options = {}) {
804
- super(options);
805
- }
806
- /**
807
- * Set primary keys for a specific index key, replacing any existing values.
808
- * Uses optional comparator to skip updates if PKs haven't actually changed.
809
- */
810
- setPks(key, pks) {
811
- const hasChanges = this.setPksWithComparison(key, new Set(pks));
812
- if (hasChanges) {
813
- this.emitUpdate([key]);
814
- }
815
- }
816
- /**
817
- * Add primary keys to a specific index key
818
- */
819
- addPks(key, pks) {
820
- if (pks.length === 0) return;
821
- let pksSet = this.store.getOneByKey(key);
822
- if (!pksSet) {
823
- pksSet = /* @__PURE__ */ new Set();
824
- this.store.setOneByKey(key, pksSet);
825
- }
826
- let hasChanges = false;
827
- for (const pk of pks) {
828
- if (!pksSet.has(pk)) {
829
- pksSet.add(pk);
830
- hasChanges = true;
831
- }
832
- }
833
- if (hasChanges) {
834
- this.store.setOneByKey(key, pksSet);
835
- this.emitUpdate([key]);
836
- }
837
- }
838
- /**
839
- * Remove primary keys from a specific index key
840
- */
841
- removePks(key, pks) {
842
- if (pks.length === 0) return;
843
- const pksSet = this.store.getOneByKey(key);
844
- if (!pksSet) return;
845
- let hasChanges = false;
846
- for (const pk of pks) {
847
- if (pksSet.delete(pk)) {
848
- hasChanges = true;
849
- }
850
- }
851
- if (pksSet.size === 0) {
852
- this.store.removeOneByKey(key);
853
- hasChanges = true;
854
- } else if (hasChanges) {
855
- this.store.setOneByKey(key, pksSet);
856
- }
857
- if (hasChanges) {
858
- this.emitUpdate([key]);
859
- }
860
- }
861
- /**
862
- * Clear all primary keys for a specific index key, or all keys if no key specified
863
- */
864
- clear(key) {
865
- if (key === void 0) {
866
- const allKeys = this.store.getAllKeys();
867
- if (allKeys.length > 0) {
868
- this.store.clear();
869
- this.emitUpdate(allKeys);
870
- }
871
- } else {
872
- if (this.store.getOneByKey(key) !== void 0) {
873
- this.store.removeOneByKey(key);
874
- this.emitUpdate([key]);
875
- }
876
- }
877
- }
878
- };
879
-
880
- // src/const/OIMDBSettings.ts
881
- var OIMDBSettings = class {
882
- };
883
- /**
884
- * Fixed key used in the manual index to store updated keys set in coalescers.
885
- * This constant is exported so users can create their own indexes and pass them to coalescers.
886
- */
887
- OIMDBSettings.UPDATED_KEYS_INDEX_KEY = "__updatedKeys__";
888
- OIMDBSettings.FLUSHING_KEYS_INDEX_KEY = "__flushingKeys__";
889
-
890
- // src/core/OIMUpdateEventCoalescer.ts
891
- var OIMUpdateEventCoalescer = class {
892
- constructor(index, flushingIndex) {
893
- this.emitter = new OIMEventEmitter();
894
- this.hasEmittedChanges = false;
895
- this.updatedKeysIndex = index ?? new OIMIndexManualSetBased({
896
- store: new OIMIndexStoreMapDrivenSetBased()
897
- });
898
- this.flushingUpdatedKeysIndex = flushingIndex ?? new OIMIndexManualSetBased({
899
- store: new OIMIndexStoreMapDrivenSetBased()
900
- });
901
- if (!this.updatedKeysIndex.hasKey(OIMDBSettings.UPDATED_KEYS_INDEX_KEY)) {
902
- this.updatedKeysIndex.setPks(
903
- OIMDBSettings.UPDATED_KEYS_INDEX_KEY,
904
- []
905
- );
906
- }
907
- }
908
- /**
909
- * Get the set of keys that have been updated since last clear
910
- */
911
- getUpdatedKeys() {
912
- return this.updatedKeysIndex.getPksByKey(
913
- OIMDBSettings.UPDATED_KEYS_INDEX_KEY
914
- );
915
- }
916
- getFlushingUpdatedKeys() {
917
- return this.flushingUpdatedKeysIndex.getPksByKey(
918
- OIMDBSettings.FLUSHING_KEYS_INDEX_KEY
919
- );
920
- }
921
- markUpdatedKeysAsFlushing() {
922
- this.flushingUpdatedKeysIndex.setPks(
923
- OIMDBSettings.FLUSHING_KEYS_INDEX_KEY,
924
- Array.from(this.getUpdatedKeys())
925
- );
926
- this.clearUpdatedKeys();
927
- }
928
- /**
929
- * Clear all updated keys and reset the changed state
930
- */
931
- clearUpdatedKeys() {
932
- this.emitter.emit(
933
- 1 /* BEFORE_FLUSH */,
934
- void 0
935
- );
936
- this.updatedKeysIndex.setPks(OIMDBSettings.UPDATED_KEYS_INDEX_KEY, []);
937
- this.hasEmittedChanges = false;
938
- this.emitter.emit(
939
- 2 /* AFTER_FLUSH */,
940
- void 0
941
- );
942
- }
943
- clearFlushingUpdatedKeys() {
944
- this.flushingUpdatedKeysIndex.setPks(
945
- OIMDBSettings.FLUSHING_KEYS_INDEX_KEY,
946
- []
947
- );
948
- }
949
- /**
950
- * Add keys to the updated set and emit HAS_CHANGES event if needed
951
- */
952
- addUpdatedKeys(keys) {
953
- this.updatedKeysIndex.addPks(
954
- OIMDBSettings.UPDATED_KEYS_INDEX_KEY,
955
- keys
956
- );
957
- if (!this.hasEmittedChanges) {
958
- this.hasEmittedChanges = true;
959
- this.emitter.emit(
960
- 0 /* HAS_CHANGES */,
961
- void 0
962
- );
963
- }
964
- }
965
- };
966
-
967
- // src/core/OIMUpdateEventCoalescerIndex.ts
968
- var OIMUpdateEventCoalescerIndex = class extends OIMUpdateEventCoalescer {
969
- constructor(indexEmitter) {
970
- super();
971
- this.indexEmitter = indexEmitter;
972
- /**
973
- * When the index is updated, we keep track of the updated keys.
974
- */
975
- this.handleUpdate = (payload) => {
976
- this.addUpdatedKeys(payload.keys);
977
- };
978
- this.indexEmitter.on(0 /* UPDATE */, this.handleUpdate);
979
- }
980
- destroy() {
981
- this.indexEmitter.off(0 /* UPDATE */, this.handleUpdate);
982
- this.emitter.offAll();
983
- this.clearUpdatedKeys();
984
- }
985
- };
986
-
987
811
  // src/abstract/OIMReactiveIndexSetBased.ts
988
812
  var OIMReactiveIndexSetBased = class {
989
- constructor(queue, opts) {
990
- this.index = opts?.index ?? this.createDefaultIndex();
991
- this.coalescer = new OIMUpdateEventCoalescerIndex(
992
- this.index.emitter
993
- );
994
- this.updateEventEmitter = new OIMUpdateEventEmitter({
995
- coalescer: this.coalescer,
996
- queue
997
- });
813
+ constructor(queue, createIndex) {
814
+ this.updateEmitter = new OIMUpdateEventEmitter(queue);
815
+ this.index = createIndex(this.updateEmitter);
998
816
  }
999
817
  getPksByKey(key) {
1000
818
  return this.index.getPksByKey(key);
@@ -1020,24 +838,38 @@ var OIMReactiveIndexSetBased = class {
1020
838
  getMetrics() {
1021
839
  return this.index.getMetrics();
1022
840
  }
841
+ subscribeOnKey(key, handler) {
842
+ return this.updateEmitter.subscribeOnKey(key, handler);
843
+ }
844
+ subscribeOnKeys(keys, handler) {
845
+ return this.updateEmitter.subscribeOnKeys(keys, handler);
846
+ }
847
+ unsubscribeFromKey(key, handler) {
848
+ this.updateEmitter.unsubscribeFromKey(key, handler);
849
+ }
850
+ unsubscribeFromKeys(keys, handler) {
851
+ this.updateEmitter.unsubscribeFromKeys(keys, handler);
852
+ }
853
+ hasSubscriptions() {
854
+ return this.updateEmitter.hasSubscriptions();
855
+ }
856
+ getHandlerCount(key) {
857
+ return this.updateEmitter.getHandlerCount(key);
858
+ }
859
+ getSubscriptionMetrics() {
860
+ return this.updateEmitter.getMetrics();
861
+ }
1023
862
  destroy() {
1024
- this.updateEventEmitter.destroy();
1025
- this.coalescer.destroy();
863
+ this.updateEmitter.destroy();
1026
864
  this.index.destroy();
1027
865
  }
1028
866
  };
1029
867
 
1030
868
  // src/abstract/OIMReactiveIndexArrayBased.ts
1031
869
  var OIMReactiveIndexArrayBased = class {
1032
- constructor(queue, opts) {
1033
- this.index = opts?.index ?? this.createDefaultIndex();
1034
- this.coalescer = new OIMUpdateEventCoalescerIndex(
1035
- this.index.emitter
1036
- );
1037
- this.updateEventEmitter = new OIMUpdateEventEmitter({
1038
- coalescer: this.coalescer,
1039
- queue
1040
- });
870
+ constructor(queue, createIndex) {
871
+ this.updateEmitter = new OIMUpdateEventEmitter(queue);
872
+ this.index = createIndex(this.updateEmitter);
1041
873
  }
1042
874
  getPksByKey(key) {
1043
875
  return this.index.getPksByKey(key);
@@ -1063,9 +895,29 @@ var OIMReactiveIndexArrayBased = class {
1063
895
  getMetrics() {
1064
896
  return this.index.getMetrics();
1065
897
  }
898
+ subscribeOnKey(key, handler) {
899
+ return this.updateEmitter.subscribeOnKey(key, handler);
900
+ }
901
+ subscribeOnKeys(keys, handler) {
902
+ return this.updateEmitter.subscribeOnKeys(keys, handler);
903
+ }
904
+ unsubscribeFromKey(key, handler) {
905
+ this.updateEmitter.unsubscribeFromKey(key, handler);
906
+ }
907
+ unsubscribeFromKeys(keys, handler) {
908
+ this.updateEmitter.unsubscribeFromKeys(keys, handler);
909
+ }
910
+ hasSubscriptions() {
911
+ return this.updateEmitter.hasSubscriptions();
912
+ }
913
+ getHandlerCount(key) {
914
+ return this.updateEmitter.getHandlerCount(key);
915
+ }
916
+ getSubscriptionMetrics() {
917
+ return this.updateEmitter.getMetrics();
918
+ }
1066
919
  destroy() {
1067
- this.updateEventEmitter.destroy();
1068
- this.coalescer.destroy();
920
+ this.updateEmitter.destroy();
1069
921
  this.index.destroy();
1070
922
  }
1071
923
  };
@@ -1237,47 +1089,131 @@ var OIMCollection = class {
1237
1089
  }
1238
1090
  };
1239
1091
 
1240
- // src/core/OIMUpdateEventCoalescerCollection.ts
1241
- var OIMUpdateEventCoalescerCollection = class extends OIMUpdateEventCoalescer {
1242
- constructor(collectionEmitter) {
1243
- super();
1244
- this.collectionEmitter = collectionEmitter;
1245
- /**
1246
- * When the collection is updated, we keep track of the updated pks.
1247
- */
1248
- this.handleUpdate = (payload) => {
1249
- this.addUpdatedKeys(payload.pks);
1250
- };
1251
- this.collectionEmitter.on(
1252
- 0 /* UPDATE */,
1253
- this.handleUpdate
1254
- );
1255
- }
1256
- destroy() {
1257
- this.collectionEmitter.off(
1258
- 0 /* UPDATE */,
1259
- this.handleUpdate
1260
- );
1261
- this.emitter.offAll();
1262
- this.clearUpdatedKeys();
1263
- }
1264
- };
1265
-
1266
1092
  // src/core/OIMReactiveCollection.ts
1267
1093
  var OIMReactiveCollection = class extends OIMCollection {
1268
1094
  constructor(queue, opts) {
1269
1095
  super(opts);
1270
- this.coalescer = new OIMUpdateEventCoalescerCollection(
1271
- this.emitter
1096
+ this.anyUpdateHandlers = /* @__PURE__ */ new Set();
1097
+ this.pendingAnyUpdatePks = /* @__PURE__ */ new Set();
1098
+ this.isAnyUpdateClearPending = false;
1099
+ this.isAnyUpdateScheduled = false;
1100
+ this.runAnyUpdate = () => {
1101
+ if (!this.isAnyUpdateScheduled) return;
1102
+ this.isAnyUpdateScheduled = false;
1103
+ this.dequeueAnyUpdate = void 0;
1104
+ const pks = this.isAnyUpdateClearPending ? [] : Array.from(this.pendingAnyUpdatePks);
1105
+ this.pendingAnyUpdatePks.clear();
1106
+ this.isAnyUpdateClearPending = false;
1107
+ const snapshot = Array.from(this.anyUpdateHandlers);
1108
+ for (let i = 0; i < snapshot.length; i++) snapshot[i](pks);
1109
+ };
1110
+ this.queue = queue;
1111
+ this.updateEmitter = new OIMUpdateEventEmitter(queue, "queue");
1112
+ }
1113
+ subscribeOnKey(pk, handler) {
1114
+ return this.updateEmitter.subscribeOnKey(pk, handler);
1115
+ }
1116
+ subscribeOnKeys(pks, handler) {
1117
+ return this.updateEmitter.subscribeOnKeys(pks, handler);
1118
+ }
1119
+ unsubscribeFromKey(pk, handler) {
1120
+ this.updateEmitter.unsubscribeFromKey(pk, handler);
1121
+ }
1122
+ unsubscribeFromKeys(pks, handler) {
1123
+ this.updateEmitter.unsubscribeFromKeys(pks, handler);
1124
+ }
1125
+ destroySubscriptions() {
1126
+ this.updateEmitter.destroy();
1127
+ }
1128
+ hasSubscriptions() {
1129
+ return this.updateEmitter.hasSubscriptions();
1130
+ }
1131
+ getHandlerCount(pk) {
1132
+ return this.updateEmitter.getHandlerCount(pk);
1133
+ }
1134
+ getMetrics() {
1135
+ return this.updateEmitter.getMetrics();
1136
+ }
1137
+ subscribeOnAnyUpdate(handler) {
1138
+ this.anyUpdateHandlers.add(handler);
1139
+ return () => {
1140
+ this.anyUpdateHandlers.delete(handler);
1141
+ };
1142
+ }
1143
+ upsertOneByPk(pk, entity) {
1144
+ this.upsertOneWithoutNotificationsByPk(pk, entity);
1145
+ this.updateEmitter.markUpdatedKey(pk);
1146
+ this.trackAnyUpdatePk(pk);
1147
+ }
1148
+ upsertOne(entity) {
1149
+ const pk = this.upsertOneWithoutNotifications(entity);
1150
+ this.updateEmitter.markUpdatedKey(pk);
1151
+ this.trackAnyUpdatePk(pk);
1152
+ }
1153
+ upsertMany(entities) {
1154
+ const pks = entities.map(
1155
+ (entity) => this.upsertOneWithoutNotifications(entity)
1272
1156
  );
1273
- this.updateEventEmitter = new OIMUpdateEventEmitter({
1274
- coalescer: this.coalescer,
1275
- queue
1276
- });
1157
+ this.updateEmitter.markUpdatedKeys(pks);
1158
+ this.trackAnyUpdatePks(pks);
1159
+ }
1160
+ removeOne(entity) {
1161
+ const pk = this.selectPk(entity);
1162
+ this.store.removeOneByPk(pk);
1163
+ this.updateEmitter.markUpdatedKey(pk);
1164
+ this.trackAnyUpdatePk(pk);
1165
+ }
1166
+ removeMany(entities) {
1167
+ const pks = entities.map(this.selectPk);
1168
+ this.store.removeManyByPks(pks);
1169
+ this.updateEmitter.markUpdatedKeys(pks);
1170
+ this.trackAnyUpdatePks(pks);
1171
+ }
1172
+ removeOneByPk(pk) {
1173
+ this.store.removeOneByPk(pk);
1174
+ this.updateEmitter.markUpdatedKey(pk);
1175
+ this.trackAnyUpdatePk(pk);
1176
+ }
1177
+ removeManyByPks(pks) {
1178
+ this.store.removeManyByPks(pks);
1179
+ this.updateEmitter.markUpdatedKeys(pks);
1180
+ this.trackAnyUpdatePks(pks);
1181
+ }
1182
+ clear() {
1183
+ this.store.clear();
1184
+ this.updateEmitter.markAllUpdated();
1185
+ this.trackAnyUpdateClear();
1186
+ }
1187
+ trackAnyUpdatePk(pk) {
1188
+ if (this.anyUpdateHandlers.size === 0) return;
1189
+ this.pendingAnyUpdatePks.add(pk);
1190
+ this.ensureAnyUpdateEnqueued();
1191
+ }
1192
+ trackAnyUpdatePks(pks) {
1193
+ if (this.anyUpdateHandlers.size === 0) return;
1194
+ for (let i = 0; i < pks.length; i++)
1195
+ this.pendingAnyUpdatePks.add(pks[i]);
1196
+ this.ensureAnyUpdateEnqueued();
1197
+ }
1198
+ trackAnyUpdateClear() {
1199
+ if (this.anyUpdateHandlers.size === 0) return;
1200
+ this.isAnyUpdateClearPending = true;
1201
+ this.pendingAnyUpdatePks.clear();
1202
+ this.ensureAnyUpdateEnqueued();
1203
+ }
1204
+ ensureAnyUpdateEnqueued() {
1205
+ if (this.isAnyUpdateScheduled) return;
1206
+ this.isAnyUpdateScheduled = true;
1207
+ this.dequeueAnyUpdate = this.queue.enqueue(this.runAnyUpdate);
1277
1208
  }
1278
1209
  destroy() {
1279
- this.updateEventEmitter.destroy();
1280
- this.coalescer.destroy();
1210
+ this.updateEmitter.destroy();
1211
+ if (this.dequeueAnyUpdate) {
1212
+ this.dequeueAnyUpdate();
1213
+ this.dequeueAnyUpdate = void 0;
1214
+ }
1215
+ this.anyUpdateHandlers.clear();
1216
+ this.pendingAnyUpdatePks.clear();
1281
1217
  super.destroy();
1282
1218
  }
1283
1219
  };
@@ -1377,50 +1313,142 @@ var OIMObject = class {
1377
1313
  }
1378
1314
  };
1379
1315
 
1380
- // src/core/OIMUpdateEventCoalescerObject.ts
1381
- var OIMUpdateEventCoalescerObject = class extends OIMUpdateEventCoalescer {
1382
- constructor(objectEmitter) {
1383
- super();
1384
- this.objectEmitter = objectEmitter;
1385
- /**
1386
- * When the object is updated, we keep track of the updated keys.
1387
- */
1388
- this.handleUpdate = (payload) => {
1389
- this.addUpdatedKeys(payload.keys);
1390
- };
1391
- this.objectEmitter.on(0 /* UPDATE */, this.handleUpdate);
1392
- }
1393
- destroy() {
1394
- this.objectEmitter.off(0 /* UPDATE */, this.handleUpdate);
1395
- this.emitter.offAll();
1396
- this.clearUpdatedKeys();
1397
- }
1398
- };
1399
-
1400
1316
  // src/core/OIMReactiveObject.ts
1401
1317
  var OIMReactiveObject = class extends OIMObject {
1402
1318
  constructor(queue, opts) {
1403
1319
  super(opts);
1404
- this.coalescer = new OIMUpdateEventCoalescerObject(this.emitter);
1405
- this.updateEventEmitter = new OIMUpdateEventEmitter({
1406
- coalescer: this.coalescer,
1407
- queue
1320
+ this.updateEmitter = new OIMUpdateEventEmitter(queue, "queue");
1321
+ this.emitter.on(0 /* UPDATE */, (payload) => {
1322
+ this.updateEmitter.markUpdatedKeys(payload.keys);
1408
1323
  });
1409
1324
  }
1325
+ subscribeOnKey(key, handler) {
1326
+ return this.updateEmitter.subscribeOnKey(key, handler);
1327
+ }
1328
+ subscribeOnKeys(keys, handler) {
1329
+ return this.updateEmitter.subscribeOnKeys(keys, handler);
1330
+ }
1331
+ unsubscribeFromKey(key, handler) {
1332
+ this.updateEmitter.unsubscribeFromKey(key, handler);
1333
+ }
1334
+ unsubscribeFromKeys(keys, handler) {
1335
+ this.updateEmitter.unsubscribeFromKeys(keys, handler);
1336
+ }
1337
+ hasSubscriptions() {
1338
+ return this.updateEmitter.hasSubscriptions();
1339
+ }
1340
+ getHandlerCount(key) {
1341
+ return this.updateEmitter.getHandlerCount(key);
1342
+ }
1343
+ getMetrics() {
1344
+ return this.updateEmitter.getMetrics();
1345
+ }
1410
1346
  destroy() {
1411
- this.updateEventEmitter.destroy();
1412
- this.coalescer.destroy();
1347
+ this.updateEmitter.destroy();
1413
1348
  super.destroy();
1414
1349
  }
1415
1350
  };
1416
1351
 
1352
+ // src/core/OIMIndexManualSetBased.ts
1353
+ var OIMIndexManualSetBased = class extends OIMIndexSetBased {
1354
+ constructor(options = {}) {
1355
+ super(options);
1356
+ }
1357
+ /**
1358
+ * Set primary keys for a specific index key, replacing any existing values.
1359
+ * Uses optional comparator to skip updates if PKs haven't actually changed.
1360
+ */
1361
+ setPks(key, pks) {
1362
+ const hasChanges = this.setPksWithComparison(key, new Set(pks));
1363
+ if (hasChanges) {
1364
+ this.emitUpdateOne(key);
1365
+ }
1366
+ }
1367
+ /**
1368
+ * Add primary keys to a specific index key
1369
+ */
1370
+ addPks(key, pks) {
1371
+ if (pks.length === 0) return;
1372
+ let pksSet = this.store.getOneByKey(key);
1373
+ if (!pksSet) {
1374
+ pksSet = /* @__PURE__ */ new Set();
1375
+ this.store.setOneByKey(key, pksSet);
1376
+ }
1377
+ let hasChanges = false;
1378
+ for (const pk of pks) {
1379
+ if (!pksSet.has(pk)) {
1380
+ pksSet.add(pk);
1381
+ hasChanges = true;
1382
+ }
1383
+ }
1384
+ if (hasChanges) {
1385
+ this.store.setOneByKey(key, pksSet);
1386
+ this.emitUpdateOne(key);
1387
+ }
1388
+ }
1389
+ /**
1390
+ * Remove primary keys from a specific index key
1391
+ */
1392
+ removePks(key, pks) {
1393
+ if (pks.length === 0) return;
1394
+ const pksSet = this.store.getOneByKey(key);
1395
+ if (!pksSet) return;
1396
+ let hasChanges = false;
1397
+ for (const pk of pks) {
1398
+ if (pksSet.delete(pk)) {
1399
+ hasChanges = true;
1400
+ }
1401
+ }
1402
+ if (pksSet.size === 0) {
1403
+ this.store.removeOneByKey(key);
1404
+ hasChanges = true;
1405
+ } else if (hasChanges) {
1406
+ this.store.setOneByKey(key, pksSet);
1407
+ }
1408
+ if (hasChanges) {
1409
+ this.emitUpdateOne(key);
1410
+ }
1411
+ }
1412
+ /**
1413
+ * Clear all primary keys for a specific index key, or all keys if no key specified
1414
+ */
1415
+ clear(key) {
1416
+ if (key === void 0) {
1417
+ const allKeys = this.store.getAllKeys();
1418
+ if (allKeys.length > 0) {
1419
+ this.store.clear();
1420
+ this.emitUpdate(allKeys);
1421
+ }
1422
+ } else {
1423
+ if (this.store.getOneByKey(key) !== void 0) {
1424
+ this.store.removeOneByKey(key);
1425
+ this.emitUpdateOne(key);
1426
+ }
1427
+ }
1428
+ }
1429
+ };
1430
+
1417
1431
  // src/core/OIMReactiveIndexManualSetBased.ts
1432
+ var OIMIndexManualSetBasedReactive = class extends OIMIndexManualSetBased {
1433
+ constructor(updateEmitter, opts) {
1434
+ super(opts);
1435
+ this.updateEmitter = updateEmitter;
1436
+ }
1437
+ emitUpdate(keys) {
1438
+ this.updateEmitter.markUpdatedKeys(keys);
1439
+ }
1440
+ emitUpdateOne(key) {
1441
+ this.updateEmitter.markUpdatedKey(key);
1442
+ }
1443
+ };
1418
1444
  var OIMReactiveIndexManualSetBased = class extends OIMReactiveIndexSetBased {
1419
1445
  constructor(queue, opts) {
1420
- super(queue, opts);
1421
- }
1422
- createDefaultIndex() {
1423
- return new OIMIndexManualSetBased();
1446
+ super(queue, (updateEmitter) => {
1447
+ return new OIMIndexManualSetBasedReactive(
1448
+ updateEmitter,
1449
+ opts?.indexOptions
1450
+ );
1451
+ });
1424
1452
  }
1425
1453
  setPks(key, pks) {
1426
1454
  this.index.setPks(key, pks);
@@ -1448,7 +1476,7 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
1448
1476
  setPks(key, pks) {
1449
1477
  const hasChanges = this.setPksWithComparison(key, pks);
1450
1478
  if (hasChanges) {
1451
- this.emitUpdate([key]);
1479
+ this.emitUpdateOne(key);
1452
1480
  }
1453
1481
  }
1454
1482
  /**
@@ -1471,7 +1499,7 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
1471
1499
  }
1472
1500
  if (hasChanges) {
1473
1501
  this.store.setOneByKey(key, Array.from(pksSet));
1474
- this.emitUpdate([key]);
1502
+ this.emitUpdateOne(key);
1475
1503
  }
1476
1504
  }
1477
1505
  /**
@@ -1490,7 +1518,7 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
1490
1518
  } else {
1491
1519
  this.store.setOneByKey(key, filtered);
1492
1520
  }
1493
- this.emitUpdate([key]);
1521
+ this.emitUpdateOne(key);
1494
1522
  }
1495
1523
  }
1496
1524
  /**
@@ -1506,19 +1534,33 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
1506
1534
  } else {
1507
1535
  if (this.store.getOneByKey(key) !== void 0) {
1508
1536
  this.store.removeOneByKey(key);
1509
- this.emitUpdate([key]);
1537
+ this.emitUpdateOne(key);
1510
1538
  }
1511
1539
  }
1512
1540
  }
1513
1541
  };
1514
1542
 
1515
1543
  // src/core/OIMReactiveIndexManualArrayBased.ts
1544
+ var OIMIndexManualArrayBasedReactive = class extends OIMIndexManualArrayBased {
1545
+ constructor(updateEmitter, opts) {
1546
+ super(opts);
1547
+ this.updateEmitter = updateEmitter;
1548
+ }
1549
+ emitUpdate(keys) {
1550
+ this.updateEmitter.markUpdatedKeys(keys);
1551
+ }
1552
+ emitUpdateOne(key) {
1553
+ this.updateEmitter.markUpdatedKey(key);
1554
+ }
1555
+ };
1516
1556
  var OIMReactiveIndexManualArrayBased = class extends OIMReactiveIndexArrayBased {
1517
1557
  constructor(queue, opts) {
1518
- super(queue, opts);
1519
- }
1520
- createDefaultIndex() {
1521
- return new OIMIndexManualArrayBased();
1558
+ super(queue, (updateEmitter) => {
1559
+ return new OIMIndexManualArrayBasedReactive(
1560
+ updateEmitter,
1561
+ opts?.indexOptions
1562
+ );
1563
+ });
1522
1564
  }
1523
1565
  setPks(key, pks) {
1524
1566
  this.index.setPks(key, pks);
@@ -1607,20 +1649,12 @@ var OIMComparatorFactory = class {
1607
1649
  }
1608
1650
  };
1609
1651
 
1610
- // src/enum/EOIMEventQueueEventType.ts
1611
- var EOIMEventQueueEventType = /* @__PURE__ */ ((EOIMEventQueueEventType2) => {
1612
- EOIMEventQueueEventType2[EOIMEventQueueEventType2["AFTER_FLUSH"] = 0] = "AFTER_FLUSH";
1613
- EOIMEventQueueEventType2[EOIMEventQueueEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
1614
- return EOIMEventQueueEventType2;
1615
- })(EOIMEventQueueEventType || {});
1616
-
1617
1652
  // src/core/OIMEventQueue.ts
1618
1653
  var OIMEventQueue = class {
1619
1654
  constructor(options = {}) {
1620
1655
  this.emitter = new OIMEventEmitter();
1621
- this.queue = [];
1656
+ this.tasks = /* @__PURE__ */ new Set();
1622
1657
  this.isFlushing = false;
1623
- this.pendingSchedule = false;
1624
1658
  this.scheduler = options.scheduler;
1625
1659
  if (this.scheduler) {
1626
1660
  this.flushBound = () => this.flush();
@@ -1631,62 +1665,76 @@ var OIMEventQueue = class {
1631
1665
  }
1632
1666
  }
1633
1667
  /**
1634
- * Add a function to the queue. If scheduler is configured and autoSchedule is enabled,
1635
- * automatically schedules a flush operation.
1668
+ * Enqueue a one-shot task to be executed on the next flush.
1669
+ * Returns a dequeue function that can be called to cancel the task before it runs.
1636
1670
  */
1637
1671
  enqueue(fn) {
1638
- this.queue.push(fn);
1639
- if (!this.scheduler) return;
1640
- if (this.isFlushing) {
1641
- this.pendingSchedule = true;
1642
- return;
1643
- }
1644
- if (this.queue.length === 1) {
1645
- this.scheduler.schedule();
1646
- }
1672
+ let isActive = true;
1673
+ const task = () => {
1674
+ if (!isActive) return;
1675
+ fn();
1676
+ };
1677
+ this.tasks.add(task);
1678
+ this.ensureScheduled();
1679
+ return () => {
1680
+ if (!isActive) return;
1681
+ isActive = false;
1682
+ this.tasks.delete(task);
1683
+ };
1684
+ }
1685
+ get isInFlush() {
1686
+ return this.isFlushing;
1687
+ }
1688
+ /**
1689
+ * Dequeue (cancel) a previously enqueued task by its dequeue function.
1690
+ */
1691
+ dequeue(dequeueFn) {
1692
+ dequeueFn();
1647
1693
  }
1648
1694
  /**
1649
- * Execute all queued functions and clear the queue.
1650
- * This method is safe to call multiple times and handles reentrancy.
1695
+ * Execute all pending tasks.
1696
+ *
1697
+ * This flush drains until the queue is empty. If tasks enqueue more tasks, they will be
1698
+ * executed within the same flush.
1651
1699
  */
1652
1700
  flush() {
1653
- if (this.queue.length === 0) return;
1654
1701
  this.scheduler?.cancel();
1655
1702
  this.isFlushing = true;
1656
1703
  this.emitter.emit(1 /* BEFORE_FLUSH */, void 0);
1657
- const currentQueue = this.queue.slice();
1658
- this.queue.length = 0;
1659
- for (let i = 0; i < currentQueue.length; i++) {
1660
- currentQueue[i]();
1704
+ const snapshot = Array.from(this.tasks);
1705
+ this.tasks.clear();
1706
+ for (let i = 0; i < snapshot.length; i++) {
1707
+ snapshot[i]();
1661
1708
  }
1662
- this.emitter.emit(0 /* AFTER_FLUSH */, void 0);
1663
1709
  this.isFlushing = false;
1664
- if (this.scheduler && this.pendingSchedule && this.queue.length > 0) {
1665
- this.pendingSchedule = false;
1666
- this.scheduler.schedule();
1667
- } else {
1668
- this.pendingSchedule = false;
1669
- }
1710
+ this.emitter.emit(0 /* AFTER_FLUSH */, void 0);
1670
1711
  }
1671
1712
  /**
1672
1713
  * Get the current number of queued functions.
1673
1714
  */
1674
1715
  get length() {
1675
- return this.queue.length;
1716
+ return this.tasks.size;
1676
1717
  }
1677
1718
  /**
1678
1719
  * Check if the queue is empty.
1679
1720
  */
1680
1721
  get isEmpty() {
1681
- return this.queue.length === 0;
1722
+ return this.tasks.size === 0;
1682
1723
  }
1683
1724
  /**
1684
1725
  * Clear the queue without executing functions and cancel any scheduled flush.
1685
1726
  */
1686
1727
  clear() {
1687
- this.queue.length = 0;
1728
+ this.tasks.clear();
1688
1729
  this.scheduler?.cancel();
1689
1730
  }
1731
+ ensureScheduled() {
1732
+ if (!this.scheduler) return;
1733
+ if (this.isFlushing) return;
1734
+ if (this.tasks.size === 1) {
1735
+ this.scheduler.schedule();
1736
+ }
1737
+ }
1690
1738
  /**
1691
1739
  * Clean up scheduler subscription when queue is no longer needed.
1692
1740
  */
@@ -1800,56 +1848,75 @@ var OIMMap2Keys = class {
1800
1848
  }
1801
1849
  };
1802
1850
 
1803
- // src/modules/wrapper/OIMUpdateEventCoalescerManual.ts
1804
- var OIMUpdateEventCoalescerManual = class extends OIMUpdateEventCoalescer {
1805
- markUpdatedKeys(keys) {
1806
- if (keys.length === 0) return;
1807
- this.addUpdatedKeys(keys);
1808
- }
1809
- destroy() {
1810
- this.emitter.offAll();
1811
- this.updatedKeysIndex.destroy();
1812
- this.flushingUpdatedKeysIndex.destroy();
1813
- }
1814
- };
1815
-
1816
1851
  // src/modules/wrapper/collection/OIMCollectionChangedFieldsWrapper.ts
1817
1852
  var OIMCollectionChangedFieldsWrapper = class {
1818
1853
  constructor(queue, collection, opts) {
1854
+ this.isInWrite = false;
1855
+ this.lastEntitySnapshotByPk = /* @__PURE__ */ new Map();
1819
1856
  this.changedFieldsByPk = /* @__PURE__ */ new Map();
1820
1857
  this.changedPksByField = /* @__PURE__ */ new Map();
1858
+ this.onAfterFlush = () => {
1859
+ this.clear();
1860
+ };
1861
+ this.onCollectionUpdate = (payload) => {
1862
+ if (this.isInWrite) return;
1863
+ if (payload.pks.length === 0) return;
1864
+ for (let i = 0; i < payload.pks.length; i++) {
1865
+ const pk = payload.pks[i];
1866
+ this.trackExternalMutationByPk(pk);
1867
+ }
1868
+ };
1821
1869
  this.collection = collection;
1822
1870
  this.selectPk = opts?.selectPk ?? (() => {
1823
1871
  throw new Error(
1824
1872
  `[OIMCollectionChangedFieldsWrapper]: selectPk was not provided. Use upsertOneByPk(...) or pass opts.selectPk.`
1825
1873
  );
1826
1874
  });
1827
- this.changedPksCoalescer = new OIMUpdateEventCoalescerManual();
1828
- this.changedPksEventEmitter = new OIMUpdateEventEmitter({
1829
- coalescer: this.changedPksCoalescer,
1830
- queue
1831
- });
1832
- this.changedFieldsCoalescer = new OIMUpdateEventCoalescerManual();
1833
- this.changedFieldsEventEmitter = new OIMUpdateEventEmitter({
1834
- coalescer: this.changedFieldsCoalescer,
1835
- queue
1836
- });
1875
+ this.changedPksEventEmitter = new OIMUpdateEventEmitter(
1876
+ queue,
1877
+ "after_flush"
1878
+ );
1879
+ this.changedFieldsEventEmitter = new OIMUpdateEventEmitter(queue, "after_flush");
1880
+ this.unsubscribeAfterFlush = queue.emitter.on(
1881
+ 0 /* AFTER_FLUSH */,
1882
+ this.onAfterFlush
1883
+ );
1884
+ const maybeReactive = collection;
1885
+ this.unsubscribeFromCollectionEmitter = maybeReactive.subscribeOnAnyUpdate ? maybeReactive.subscribeOnAnyUpdate((pks) => {
1886
+ this.onCollectionUpdate({
1887
+ pks
1888
+ });
1889
+ }) : collection.emitter.on(
1890
+ 0 /* UPDATE */,
1891
+ this.onCollectionUpdate
1892
+ );
1837
1893
  }
1838
1894
  upsertOneByPk(pk, patch) {
1839
- this.trackPatch(pk, patch);
1840
- this.collection.upsertOneByPk(pk, patch);
1895
+ this.withWrite(() => {
1896
+ this.trackPatch(pk, patch);
1897
+ this.collection.upsertOneByPk(pk, patch);
1898
+ });
1899
+ this.updateEntitySnapshot(pk);
1841
1900
  }
1842
1901
  upsertOne(entity) {
1843
- const pk = this.selectPk(entity);
1844
- this.trackPatch(pk, entity);
1845
- this.collection.upsertOne(entity);
1846
- }
1847
- upsertMany(entities) {
1848
- for (const entity of entities) {
1902
+ this.withWrite(() => {
1849
1903
  const pk = this.selectPk(entity);
1850
1904
  this.trackPatch(pk, entity);
1851
- }
1852
- this.collection.upsertMany(entities);
1905
+ this.collection.upsertOne(entity);
1906
+ });
1907
+ this.updateEntitySnapshot(this.selectPk(entity));
1908
+ }
1909
+ upsertMany(entities) {
1910
+ const pks = [];
1911
+ this.withWrite(() => {
1912
+ for (const entity of entities) {
1913
+ const pk = this.selectPk(entity);
1914
+ pks.push(pk);
1915
+ this.trackPatch(pk, entity);
1916
+ }
1917
+ this.collection.upsertMany(entities);
1918
+ });
1919
+ for (let i = 0; i < pks.length; i++) this.updateEntitySnapshot(pks[i]);
1853
1920
  }
1854
1921
  getChangedFieldsByPk(pk) {
1855
1922
  return this.changedFieldsByPk.get(pk) ?? /* @__PURE__ */ new Set();
@@ -1857,8 +1924,7 @@ var OIMCollectionChangedFieldsWrapper = class {
1857
1924
  consumeChangedFieldsByPk(pk) {
1858
1925
  const existing = this.changedFieldsByPk.get(pk);
1859
1926
  if (!existing) return /* @__PURE__ */ new Set();
1860
- this.changedFieldsByPk.delete(pk);
1861
- return existing;
1927
+ return new Set(existing);
1862
1928
  }
1863
1929
  getChangedPksByField(field) {
1864
1930
  return this.changedPksByField.get(field) ?? /* @__PURE__ */ new Set();
@@ -1866,8 +1932,7 @@ var OIMCollectionChangedFieldsWrapper = class {
1866
1932
  consumeChangedPksByField(field) {
1867
1933
  const existing = this.changedPksByField.get(field);
1868
1934
  if (!existing) return /* @__PURE__ */ new Set();
1869
- this.changedPksByField.delete(field);
1870
- return existing;
1935
+ return new Set(existing);
1871
1936
  }
1872
1937
  clear() {
1873
1938
  this.changedFieldsByPk.clear();
@@ -1876,18 +1941,60 @@ var OIMCollectionChangedFieldsWrapper = class {
1876
1941
  destroy() {
1877
1942
  this.changedPksEventEmitter.destroy();
1878
1943
  this.changedFieldsEventEmitter.destroy();
1879
- this.changedPksCoalescer.destroy();
1880
- this.changedFieldsCoalescer.destroy();
1944
+ this.unsubscribeAfterFlush();
1945
+ this.unsubscribeFromCollectionEmitter();
1881
1946
  this.clear();
1882
1947
  }
1883
- trackPatch(pk, patch) {
1884
- if (pk === void 0 || pk === null || pk === "") {
1885
- throw new Error(
1886
- `[OIMCollectionChangedFieldsWrapper]: PK is required to track changes.`
1887
- );
1948
+ withWrite(fn) {
1949
+ if (this.isInWrite) {
1950
+ fn();
1951
+ return;
1888
1952
  }
1889
- const keys = Object.keys(patch);
1890
- if (keys.length === 0) return;
1953
+ this.isInWrite = true;
1954
+ try {
1955
+ fn();
1956
+ } finally {
1957
+ this.isInWrite = false;
1958
+ }
1959
+ }
1960
+ trackExternalMutationByPk(pk) {
1961
+ const prev = this.lastEntitySnapshotByPk.get(pk);
1962
+ const next = this.collection.getOneByPk(pk);
1963
+ const changedKeys = this.diffChangedKeys(prev, next);
1964
+ this.updateEntitySnapshot(pk);
1965
+ if (changedKeys.length > 0) {
1966
+ this.trackChangedKeys(pk, changedKeys);
1967
+ } else {
1968
+ this.changedPksEventEmitter.markUpdatedKeys([pk]);
1969
+ }
1970
+ }
1971
+ diffChangedKeys(prev, next) {
1972
+ if (!prev && !next) return [];
1973
+ if (!prev && next) return Object.keys(next);
1974
+ if (prev && !next) return Object.keys(prev);
1975
+ const prevObj = prev;
1976
+ const nextObj = next;
1977
+ const keys = /* @__PURE__ */ new Set();
1978
+ Object.keys(prevObj).forEach((k) => keys.add(k));
1979
+ Object.keys(nextObj).forEach((k) => keys.add(k));
1980
+ const changed = [];
1981
+ keys.forEach((k) => {
1982
+ if (prevObj[k] !== nextObj[k])
1983
+ changed.push(k);
1984
+ });
1985
+ return changed;
1986
+ }
1987
+ updateEntitySnapshot(pk) {
1988
+ const entity = this.collection.getOneByPk(pk);
1989
+ if (!entity) {
1990
+ this.lastEntitySnapshotByPk.delete(pk);
1991
+ return;
1992
+ }
1993
+ this.lastEntitySnapshotByPk.set(pk, {
1994
+ ...entity
1995
+ });
1996
+ }
1997
+ trackChangedKeys(pk, keys) {
1891
1998
  let pkFields = this.changedFieldsByPk.get(pk);
1892
1999
  if (!pkFields) {
1893
2000
  pkFields = /* @__PURE__ */ new Set();
@@ -1902,8 +2009,18 @@ var OIMCollectionChangedFieldsWrapper = class {
1902
2009
  }
1903
2010
  fieldPks.add(pk);
1904
2011
  }
1905
- this.changedPksCoalescer.markUpdatedKeys([pk]);
1906
- this.changedFieldsCoalescer.markUpdatedKeys(keys);
2012
+ this.changedPksEventEmitter.markUpdatedKeys([pk]);
2013
+ this.changedFieldsEventEmitter.markUpdatedKeys(keys);
2014
+ }
2015
+ trackPatch(pk, patch) {
2016
+ if (pk === void 0 || pk === null || pk === "") {
2017
+ throw new Error(
2018
+ `[OIMCollectionChangedFieldsWrapper]: PK is required to track changes.`
2019
+ );
2020
+ }
2021
+ const keys = Object.keys(patch);
2022
+ if (keys.length === 0) return;
2023
+ this.trackChangedKeys(pk, keys);
1907
2024
  }
1908
2025
  };
1909
2026
 
@@ -1983,13 +2100,33 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1983
2100
  // src/modules/wrapper/index/OIMOrderedListIndexCommandStreamWrapper.ts
1984
2101
  var OIMOrderedListIndexCommandStreamWrapper = class {
1985
2102
  constructor(queue, index) {
2103
+ this.isInWrite = false;
1986
2104
  this.commandsByKey = /* @__PURE__ */ new Map();
1987
- this.coalescer = new OIMUpdateEventCoalescerManual();
1988
- this.commandsEventEmitter = new OIMUpdateEventEmitter({
1989
- coalescer: this.coalescer,
1990
- queue
1991
- });
2105
+ this.onAfterFlush = () => {
2106
+ this.commandsByKey.clear();
2107
+ };
2108
+ this.onIndexUpdate = (payload) => {
2109
+ if (this.isInWrite) return;
2110
+ if (payload.keys.length === 0) return;
2111
+ for (let i = 0; i < payload.keys.length; i++) {
2112
+ const key = payload.keys[i];
2113
+ const list = this.index.getPksByKey(key);
2114
+ this.appendCommand(key, { type: "set", keys: Array.from(list) });
2115
+ }
2116
+ };
2117
+ this.commandsEventEmitter = new OIMUpdateEventEmitter(
2118
+ queue,
2119
+ "after_flush"
2120
+ );
1992
2121
  this.index = index ?? new OIMIndexManualOrderedArrayBased();
2122
+ this.unsubscribeAfterFlush = queue.emitter.on(
2123
+ 0 /* AFTER_FLUSH */,
2124
+ this.onAfterFlush
2125
+ );
2126
+ this.unsubscribeFromIndexEmitter = this.index.emitter.on(
2127
+ 0 /* UPDATE */,
2128
+ this.onIndexUpdate
2129
+ );
1993
2130
  }
1994
2131
  getPksByKey(key) {
1995
2132
  return this.index.getPksByKey(key);
@@ -2001,27 +2138,35 @@ var OIMOrderedListIndexCommandStreamWrapper = class {
2001
2138
  return this.index.getKeys();
2002
2139
  }
2003
2140
  push(key, itemKey) {
2004
- const index = this.index.push(key, itemKey);
2005
- this.appendCommand(key, { type: "add", key: itemKey, index });
2141
+ this.withWrite(() => {
2142
+ const index = this.index.push(key, itemKey);
2143
+ this.appendCommand(key, { type: "add", key: itemKey, index });
2144
+ });
2006
2145
  }
2007
2146
  removeAt(key, index) {
2008
- const itemKey = this.index.removeAt(key, index);
2009
- if (itemKey === void 0) return;
2010
- this.appendCommand(key, { type: "remove", key: itemKey, index });
2147
+ this.withWrite(() => {
2148
+ const itemKey = this.index.removeAt(key, index);
2149
+ if (itemKey === void 0) return;
2150
+ this.appendCommand(key, { type: "remove", key: itemKey, index });
2151
+ });
2011
2152
  }
2012
2153
  move(key, fromIndex, toIndex) {
2013
- const itemKey = this.index.move(key, fromIndex, toIndex);
2014
- if (itemKey === void 0) return;
2015
- this.appendCommand(key, {
2016
- type: "move",
2017
- key: itemKey,
2018
- fromIndex,
2019
- toIndex
2154
+ this.withWrite(() => {
2155
+ const itemKey = this.index.move(key, fromIndex, toIndex);
2156
+ if (itemKey === void 0) return;
2157
+ this.appendCommand(key, {
2158
+ type: "move",
2159
+ key: itemKey,
2160
+ fromIndex,
2161
+ toIndex
2162
+ });
2020
2163
  });
2021
2164
  }
2022
- reset(key, keys) {
2023
- this.index.reset(key, keys);
2024
- this.appendCommand(key, { type: "reset", keys });
2165
+ set(key, keys) {
2166
+ this.withWrite(() => {
2167
+ this.index.reset(key, keys);
2168
+ this.appendCommand(key, { type: "set", keys });
2169
+ });
2025
2170
  }
2026
2171
  /**
2027
2172
  * Read-only peek at buffered commands without clearing them.
@@ -2036,8 +2181,7 @@ var OIMOrderedListIndexCommandStreamWrapper = class {
2036
2181
  consumeCommands(key) {
2037
2182
  const cmds = this.commandsByKey.get(key);
2038
2183
  if (!cmds || cmds.length === 0) return [];
2039
- this.commandsByKey.delete(key);
2040
- return cmds;
2184
+ return cmds.slice();
2041
2185
  }
2042
2186
  clear(key) {
2043
2187
  if (key === void 0) {
@@ -2050,17 +2194,38 @@ var OIMOrderedListIndexCommandStreamWrapper = class {
2050
2194
  }
2051
2195
  destroy() {
2052
2196
  this.commandsEventEmitter.destroy();
2053
- this.coalescer.destroy();
2197
+ this.unsubscribeAfterFlush();
2198
+ this.unsubscribeFromIndexEmitter();
2054
2199
  this.clear();
2055
2200
  }
2201
+ withWrite(fn) {
2202
+ if (this.isInWrite) {
2203
+ fn();
2204
+ return;
2205
+ }
2206
+ this.isInWrite = true;
2207
+ try {
2208
+ fn();
2209
+ } finally {
2210
+ this.isInWrite = false;
2211
+ }
2212
+ }
2056
2213
  appendCommand(key, cmd) {
2057
2214
  let cmds = this.commandsByKey.get(key);
2058
2215
  if (!cmds) {
2059
2216
  cmds = [];
2060
2217
  this.commandsByKey.set(key, cmds);
2061
2218
  }
2062
- cmds.push(cmd);
2063
- this.coalescer.markUpdatedKeys([key]);
2219
+ if (cmd.type === "set") {
2220
+ cmds.length = 0;
2221
+ cmds.push(cmd);
2222
+ } else if (cmds.length > 0 && cmds[0].type === "set") {
2223
+ const setCmd = cmds[0];
2224
+ setCmd.keys = Array.from(this.index.getPksByKey(key));
2225
+ } else {
2226
+ cmds.push(cmd);
2227
+ }
2228
+ this.commandsEventEmitter.markUpdatedKeys([key]);
2064
2229
  }
2065
2230
  };
2066
2231
 
@@ -2233,159 +2398,160 @@ var OIMEventQueueSchedulerFactory = class {
2233
2398
  }
2234
2399
  };
2235
2400
 
2401
+ // src/const/OIMDBSettings.ts
2402
+ var OIMDBSettings = class {
2403
+ };
2404
+ /**
2405
+ * Fixed key used in the manual index to store updated keys set in coalescers.
2406
+ * This constant is exported so users can create their own indexes and pass them to coalescers.
2407
+ */
2408
+ OIMDBSettings.UPDATED_KEYS_INDEX_KEY = "__updatedKeys__";
2409
+ OIMDBSettings.FLUSHING_KEYS_INDEX_KEY = "__flushingKeys__";
2410
+
2411
+ // src/modules/effect/core/OIMEffectDependencyComputed.ts
2412
+ var OIMEffectDependencyComputed = class {
2413
+ constructor(computed) {
2414
+ this.computed = computed;
2415
+ }
2416
+ subscribe(onUpdate) {
2417
+ return this.computed.emitter.on("update", onUpdate);
2418
+ }
2419
+ };
2420
+
2236
2421
  // src/modules/effect/core/OIMEffect.ts
2237
2422
  var OIMEffect = class {
2238
- constructor(queue, opts) {
2239
- this.unsubscribers = [];
2240
- this.isDestroyed = false;
2241
- this.isInFlush = false;
2242
- this.hasRunInThisFlush = false;
2423
+ constructor(runtime, opts) {
2424
+ this.sourceDeps = [];
2425
+ this.computedDeps = [];
2426
+ this.sourceUnsubscribers = [];
2427
+ this.computedUnsubscribers = [];
2428
+ this.isResubscribeEnqueued = false;
2429
+ this.isTemporarilyUnsubscribedForFlush = false;
2243
2430
  this.isScheduled = false;
2244
- this.handleQueueBeforeFlush = () => {
2245
- this.isInFlush = true;
2246
- this.hasRunInThisFlush = false;
2247
- };
2248
- this.handleQueueAfterFlush = () => {
2249
- this.isInFlush = false;
2250
- this.hasRunInThisFlush = false;
2251
- };
2252
2431
  this.runTask = () => {
2253
2432
  this.isScheduled = false;
2254
- if (this.isDestroyed) return;
2433
+ this.dequeueScheduled = void 0;
2255
2434
  this.runOnce();
2256
2435
  };
2257
- this.invalidate = () => {
2258
- if (this.isDestroyed) return;
2259
- this.onInvalidate?.();
2260
- if (this.isInFlush) {
2261
- if (this.hasRunInThisFlush) return;
2262
- this.hasRunInThisFlush = true;
2263
- this.runOnce();
2264
- return;
2436
+ this.onSourceInvalidate = () => {
2437
+ this.onUpdate?.();
2438
+ if (!this.isTemporarilyUnsubscribedForFlush) {
2439
+ this.isTemporarilyUnsubscribedForFlush = true;
2440
+ this.unsubscribeFromSourceDeps();
2441
+ this.ensureResubscribeAfterFlush();
2265
2442
  }
2266
2443
  if (this.isScheduled) return;
2267
2444
  this.isScheduled = true;
2268
- this.queue.enqueue(this.runTask);
2445
+ this.dequeueScheduled = this.runtime.schedule(this.runTask);
2269
2446
  };
2270
- this.queue = queue;
2271
- this.phase = opts.phase;
2272
- this.onInvalidate = opts.onInvalidate;
2447
+ this.onComputedInvalidate = () => {
2448
+ this.onUpdate?.();
2449
+ if (this.isScheduled) return;
2450
+ this.isScheduled = true;
2451
+ this.dequeueScheduled = this.runtime.schedule(this.runTask);
2452
+ };
2453
+ this.onAfterFlush = () => {
2454
+ if (!this.isResubscribeEnqueued) return;
2455
+ this.isResubscribeEnqueued = false;
2456
+ this.dequeueResubscribe = void 0;
2457
+ this.isTemporarilyUnsubscribedForFlush = false;
2458
+ if (this.sourceUnsubscribers.length === 0) {
2459
+ for (const dep of this.sourceDeps) {
2460
+ this.sourceUnsubscribers.push(
2461
+ dep.subscribe(this.onSourceInvalidate)
2462
+ );
2463
+ }
2464
+ }
2465
+ };
2466
+ this.runtime = runtime;
2467
+ this.onUpdate = opts.onUpdate;
2273
2468
  this.run = opts.run;
2274
2469
  this.deps = opts.deps ?? [];
2275
- this.queue.emitter.on(
2276
- 1 /* BEFORE_FLUSH */,
2277
- this.handleQueueBeforeFlush
2278
- );
2279
- this.queue.emitter.on(
2280
- 0 /* AFTER_FLUSH */,
2281
- this.handleQueueAfterFlush
2282
- );
2283
2470
  for (const dep of this.deps) {
2284
- this.unsubscribers.push(dep.subscribe(this.phase, this.invalidate));
2471
+ if (dep instanceof OIMEffectDependencyComputed) {
2472
+ this.computedDeps.push(dep);
2473
+ } else {
2474
+ this.sourceDeps.push(dep);
2475
+ }
2285
2476
  }
2477
+ this.subscribeToDeps();
2286
2478
  }
2287
2479
  destroy() {
2288
- if (this.isDestroyed) return;
2289
- this.isDestroyed = true;
2290
- for (const unsubscribe of this.unsubscribers) unsubscribe();
2291
- this.unsubscribers = [];
2292
- this.queue.emitter.off(
2293
- 1 /* BEFORE_FLUSH */,
2294
- this.handleQueueBeforeFlush
2295
- );
2296
- this.queue.emitter.off(
2297
- 0 /* AFTER_FLUSH */,
2298
- this.handleQueueAfterFlush
2299
- );
2480
+ if (this.dequeueScheduled) {
2481
+ this.dequeueScheduled();
2482
+ this.dequeueScheduled = void 0;
2483
+ }
2484
+ this.unsubscribeFromAllDeps();
2485
+ if (this.dequeueResubscribe) {
2486
+ this.dequeueResubscribe();
2487
+ this.dequeueResubscribe = void 0;
2488
+ }
2489
+ this.isResubscribeEnqueued = false;
2490
+ this.isTemporarilyUnsubscribedForFlush = false;
2300
2491
  }
2301
2492
  runOnce() {
2302
- if (this.isDestroyed) return;
2303
2493
  this.run();
2304
2494
  }
2495
+ subscribeToDeps() {
2496
+ if (this.sourceUnsubscribers.length > 0 || this.computedUnsubscribers.length > 0)
2497
+ return;
2498
+ for (const dep of this.sourceDeps) {
2499
+ this.sourceUnsubscribers.push(
2500
+ dep.subscribe(this.onSourceInvalidate)
2501
+ );
2502
+ }
2503
+ for (const dep of this.computedDeps) {
2504
+ this.computedUnsubscribers.push(
2505
+ dep.subscribe(this.onComputedInvalidate)
2506
+ );
2507
+ }
2508
+ }
2509
+ unsubscribeFromSourceDeps() {
2510
+ if (this.sourceUnsubscribers.length === 0) return;
2511
+ for (const unsubscribe of this.sourceUnsubscribers) unsubscribe();
2512
+ this.sourceUnsubscribers = [];
2513
+ }
2514
+ unsubscribeFromAllDeps() {
2515
+ this.unsubscribeFromSourceDeps();
2516
+ if (this.computedUnsubscribers.length === 0) return;
2517
+ for (const unsubscribe of this.computedUnsubscribers) unsubscribe();
2518
+ this.computedUnsubscribers = [];
2519
+ }
2520
+ ensureResubscribeAfterFlush() {
2521
+ if (this.isResubscribeEnqueued) return;
2522
+ this.isResubscribeEnqueued = true;
2523
+ this.dequeueResubscribe = this.runtime.scheduleAfterFlush(
2524
+ this.onAfterFlush
2525
+ );
2526
+ }
2305
2527
  };
2306
2528
 
2307
- // src/modules/effect/enum/EOIMEffectPhase.ts
2308
- var EOIMEffectPhase = /* @__PURE__ */ ((EOIMEffectPhase2) => {
2309
- EOIMEffectPhase2["PRE"] = "pre";
2310
- EOIMEffectPhase2["HANDLERS"] = "handlers";
2311
- return EOIMEffectPhase2;
2312
- })(EOIMEffectPhase || {});
2313
-
2314
2529
  // src/modules/computed/enum/EOIMComputedEventType.ts
2315
2530
  var EOIMComputedEventType = /* @__PURE__ */ ((EOIMComputedEventType2) => {
2316
2531
  EOIMComputedEventType2["UPDATE"] = "update";
2317
2532
  return EOIMComputedEventType2;
2318
2533
  })(EOIMComputedEventType || {});
2319
2534
 
2320
- // src/modules/computed/core/OIMUpdateEventCoalescerComputed.ts
2321
- var OIMUpdateEventCoalescerComputed = class extends OIMUpdateEventCoalescer {
2322
- constructor(computedEmitter) {
2323
- super();
2324
- this.computedEmitter = computedEmitter;
2325
- this.handleUpdate = (payload) => {
2326
- this.addUpdatedKeys(payload.keys);
2327
- };
2328
- this.computedEmitter.on(
2329
- "update" /* UPDATE */,
2330
- this.handleUpdate
2331
- );
2332
- }
2333
- destroy() {
2334
- this.computedEmitter.off(
2335
- "update" /* UPDATE */,
2336
- this.handleUpdate
2337
- );
2338
- this.emitter.offAll();
2339
- this.clearUpdatedKeys();
2340
- }
2341
- };
2342
-
2343
2535
  // src/modules/computed/core/OIMComputed.ts
2344
2536
  var OIMComputed = class {
2345
- constructor(queue, opts) {
2537
+ constructor(runtime, opts) {
2346
2538
  this.emitter = new OIMEventEmitter();
2347
2539
  this.hasValue = false;
2348
2540
  this.isDirty = true;
2349
- this.isInFlush = false;
2350
- this.hasRecomputedInThisFlush = false;
2351
- this.handleQueueBeforeFlush = () => {
2352
- this.isInFlush = true;
2353
- this.hasRecomputedInThisFlush = false;
2354
- };
2355
- this.handleQueueAfterFlush = () => {
2356
- this.isInFlush = false;
2357
- this.hasRecomputedInThisFlush = false;
2358
- };
2359
- this.queue = queue;
2541
+ this.runtime = runtime;
2360
2542
  this.compute = opts.compute;
2361
2543
  this.compare = opts.compare ?? Object.is;
2362
2544
  this.deps = opts.deps ?? [];
2363
- this.coalescer = new OIMUpdateEventCoalescerComputed(
2364
- this.emitter
2365
- );
2366
- this.updateEventEmitter = new OIMUpdateEventEmitter({
2367
- coalescer: this.coalescer,
2368
- queue: this.queue
2369
- });
2370
- this.queue.emitter.on(
2371
- 1 /* BEFORE_FLUSH */,
2372
- this.handleQueueBeforeFlush
2373
- );
2374
- this.queue.emitter.on(
2375
- 0 /* AFTER_FLUSH */,
2376
- this.handleQueueAfterFlush
2545
+ this.updateEventEmitter = new OIMUpdateEventEmitter(
2546
+ this.runtime.queue,
2547
+ "immediate"
2377
2548
  );
2378
- this.effect = new OIMEffect(this.queue, {
2549
+ this.effect = new OIMEffect(this.runtime, {
2379
2550
  deps: this.deps,
2380
- phase: "pre" /* PRE */,
2381
- onInvalidate: () => {
2551
+ onUpdate: () => {
2382
2552
  this.isDirty = true;
2383
2553
  },
2384
2554
  run: () => {
2385
- if (this.isInFlush) {
2386
- if (this.hasRecomputedInThisFlush) return;
2387
- this.hasRecomputedInThisFlush = true;
2388
- }
2389
2555
  this.recomputeAndEmitIfChanged();
2390
2556
  }
2391
2557
  });
@@ -2399,16 +2565,7 @@ var OIMComputed = class {
2399
2565
  }
2400
2566
  destroy() {
2401
2567
  this.effect.destroy();
2402
- this.queue.emitter.off(
2403
- 1 /* BEFORE_FLUSH */,
2404
- this.handleQueueBeforeFlush
2405
- );
2406
- this.queue.emitter.off(
2407
- 0 /* AFTER_FLUSH */,
2408
- this.handleQueueAfterFlush
2409
- );
2410
2568
  this.updateEventEmitter.destroy();
2411
- this.coalescer.destroy();
2412
2569
  this.emitter.offAll();
2413
2570
  }
2414
2571
  recomputeAndEmitIfChanged() {
@@ -2424,6 +2581,7 @@ var OIMComputed = class {
2424
2581
  this.emitter.emit("update" /* UPDATE */, {
2425
2582
  keys: ["value"]
2426
2583
  });
2584
+ this.updateEventEmitter.markUpdatedKey("value");
2427
2585
  }
2428
2586
  };
2429
2587
 
@@ -2432,31 +2590,21 @@ function isReadonlyArray(value) {
2432
2590
  return Array.isArray(value);
2433
2591
  }
2434
2592
  var OIMEffectDependencyKeyed = class {
2435
- constructor(updateEventEmitter, keyOrKeys) {
2436
- this.updateEventEmitter = updateEventEmitter;
2593
+ constructor(subscription, keyOrKeys) {
2594
+ this.subscription = subscription;
2437
2595
  if (isReadonlyArray(keyOrKeys)) {
2438
2596
  this.keys = keyOrKeys;
2439
2597
  } else {
2440
2598
  this.key = keyOrKeys;
2441
2599
  }
2442
2600
  }
2443
- subscribe(phase, onInvalidate) {
2444
- const isPre = phase === "pre" /* PRE */;
2601
+ subscribe(onUpdate) {
2445
2602
  if (this.keys !== void 0) {
2446
- return isPre ? this.updateEventEmitter.subscribeOnKeysBeforeFlush(
2447
- this.keys,
2448
- onInvalidate
2449
- ) : this.updateEventEmitter.subscribeOnKeys(
2450
- this.keys,
2451
- onInvalidate
2452
- );
2603
+ return this.subscription.subscribeOnKeys(this.keys, onUpdate);
2453
2604
  }
2454
2605
  if (this.key === void 0) return () => {
2455
2606
  };
2456
- return isPre ? this.updateEventEmitter.subscribeOnKeyBeforeFlush(
2457
- this.key,
2458
- onInvalidate
2459
- ) : this.updateEventEmitter.subscribeOnKey(this.key, onInvalidate);
2607
+ return this.subscription.subscribeOnKey(this.key, onUpdate);
2460
2608
  }
2461
2609
  };
2462
2610
 
@@ -2464,12 +2612,12 @@ var OIMEffectDependencyKeyed = class {
2464
2612
  var OIMEffectDependencyKeyedObject = class {
2465
2613
  constructor(object, keyOrKeys) {
2466
2614
  this.dep = new OIMEffectDependencyKeyed(
2467
- object.updateEventEmitter,
2615
+ object,
2468
2616
  keyOrKeys
2469
2617
  );
2470
2618
  }
2471
- subscribe(phase, onInvalidate) {
2472
- return this.dep.subscribe(phase, onInvalidate);
2619
+ subscribe(onUpdate) {
2620
+ return this.dep.subscribe(onUpdate);
2473
2621
  }
2474
2622
  };
2475
2623
 
@@ -2477,12 +2625,12 @@ var OIMEffectDependencyKeyedObject = class {
2477
2625
  var OIMEffectDependencyKeyedCollection = class {
2478
2626
  constructor(collection, pkOrPks) {
2479
2627
  this.dep = new OIMEffectDependencyKeyed(
2480
- collection.updateEventEmitter,
2628
+ collection,
2481
2629
  pkOrPks
2482
2630
  );
2483
2631
  }
2484
- subscribe(phase, onInvalidate) {
2485
- return this.dep.subscribe(phase, onInvalidate);
2632
+ subscribe(onUpdate) {
2633
+ return this.dep.subscribe(onUpdate);
2486
2634
  }
2487
2635
  };
2488
2636
 
@@ -2490,45 +2638,592 @@ var OIMEffectDependencyKeyedCollection = class {
2490
2638
  var OIMEffectDependencyKeyedIndex = class {
2491
2639
  constructor(index, keyOrKeys) {
2492
2640
  this.dep = new OIMEffectDependencyKeyed(
2493
- index.updateEventEmitter,
2641
+ index,
2494
2642
  keyOrKeys
2495
2643
  );
2496
2644
  }
2497
- subscribe(phase, onInvalidate) {
2498
- return this.dep.subscribe(phase, onInvalidate);
2645
+ subscribe(onUpdate) {
2646
+ return this.dep.subscribe(onUpdate);
2499
2647
  }
2500
2648
  };
2501
2649
 
2502
- // src/modules/effect/core/OIMEffectDependencyComputed.ts
2503
- var OIMEffectDependencyComputed = class {
2504
- constructor(computed) {
2505
- this.computed = computed;
2650
+ // src/modules/selector/core/OIMSelector.ts
2651
+ var OIMSelector = class {
2652
+ constructor(runtime, sourceDeps, computedDeps = []) {
2653
+ this.runtime = runtime;
2654
+ this.sourceDeps = sourceDeps;
2655
+ this.computedDeps = computedDeps;
2656
+ this.listeners = /* @__PURE__ */ new Set();
2657
+ this.sourceUnsubscribers = [];
2658
+ this.computedUnsubscribers = [];
2659
+ this.isResubscribeEnqueued = false;
2660
+ this.isTemporarilyUnsubscribedForFlush = false;
2661
+ this.isDeliveryEnqueued = false;
2662
+ this.hasLastValue = false;
2663
+ this.onSourceInvalidate = () => {
2664
+ if (this.listeners.size === 0) return;
2665
+ if (!this.isTemporarilyUnsubscribedForFlush) {
2666
+ this.isTemporarilyUnsubscribedForFlush = true;
2667
+ this.unsubscribeFromSourceDeps();
2668
+ this.ensureResubscribeAfterFlush();
2669
+ }
2670
+ if (this.isDeliveryEnqueued) return;
2671
+ this.isDeliveryEnqueued = true;
2672
+ this.dequeueDelivery = this.runtime.schedule(this.deliver);
2673
+ };
2674
+ this.onComputedInvalidate = () => {
2675
+ if (this.listeners.size === 0) return;
2676
+ if (this.isDeliveryEnqueued) return;
2677
+ this.isDeliveryEnqueued = true;
2678
+ this.dequeueDelivery = this.runtime.schedule(this.deliver);
2679
+ };
2680
+ this.deliver = () => {
2681
+ this.isDeliveryEnqueued = false;
2682
+ this.dequeueDelivery = void 0;
2683
+ if (this.listeners.size === 0) return;
2684
+ const next = this.getValue();
2685
+ if (this.hasLastValue && this.areEqual(this.lastValue, next)) return;
2686
+ this.setLastValue(next);
2687
+ const snapshot = Array.from(this.listeners);
2688
+ for (let i = 0; i < snapshot.length; i++) {
2689
+ const listener = snapshot[i];
2690
+ if (this.listeners.has(listener)) listener(next);
2691
+ }
2692
+ };
2693
+ this.onAfterFlush = () => {
2694
+ if (!this.isResubscribeEnqueued) return;
2695
+ this.isResubscribeEnqueued = false;
2696
+ this.dequeueResubscribe = void 0;
2697
+ if (this.listeners.size === 0) return;
2698
+ this.isTemporarilyUnsubscribedForFlush = false;
2699
+ if (this.sourceUnsubscribers.length === 0) {
2700
+ for (const dep of this.sourceDeps) {
2701
+ this.sourceUnsubscribers.push(
2702
+ dep.subscribe(this.onSourceInvalidate)
2703
+ );
2704
+ }
2705
+ }
2706
+ };
2707
+ }
2708
+ get queue() {
2709
+ return this.runtime.queue;
2710
+ }
2711
+ /**
2712
+ * Start watching this selector. The callback is called immediately with the current value,
2713
+ * then coalesced and delivered on `queue.flush()` when the selector invalidates.
2714
+ */
2715
+ watch(onValue) {
2716
+ this.listeners.add(onValue);
2717
+ if (this.listeners.size === 1) {
2718
+ this.subscribeToDeps();
2719
+ }
2720
+ const current = this.getValue();
2721
+ this.setLastValue(current);
2722
+ onValue(current);
2723
+ return () => {
2724
+ this.listeners.delete(onValue);
2725
+ if (this.listeners.size > 0) return;
2726
+ this.unsubscribeFromAllDeps();
2727
+ if (this.dequeueResubscribe) {
2728
+ this.dequeueResubscribe();
2729
+ this.dequeueResubscribe = void 0;
2730
+ }
2731
+ this.isResubscribeEnqueued = false;
2732
+ this.isTemporarilyUnsubscribedForFlush = false;
2733
+ if (this.dequeueDelivery) {
2734
+ this.dequeueDelivery();
2735
+ this.dequeueDelivery = void 0;
2736
+ }
2737
+ this.isDeliveryEnqueued = false;
2738
+ this.hasLastValue = false;
2739
+ };
2506
2740
  }
2507
- subscribe(phase, onInvalidate) {
2508
- if (phase === "pre" /* PRE */) {
2509
- return this.computed.emitter.on("update", onInvalidate);
2741
+ areEqual(prev, next) {
2742
+ return Object.is(prev, next);
2743
+ }
2744
+ setLastValue(value) {
2745
+ this.lastValue = value;
2746
+ this.hasLastValue = true;
2747
+ }
2748
+ subscribeToDeps() {
2749
+ if (this.sourceUnsubscribers.length > 0 || this.computedUnsubscribers.length > 0)
2750
+ return;
2751
+ for (const dep of this.sourceDeps) {
2752
+ this.sourceUnsubscribers.push(
2753
+ dep.subscribe(this.onSourceInvalidate)
2754
+ );
2510
2755
  }
2511
- return this.computed.updateEventEmitter.subscribeOnKey(
2512
- "value",
2513
- onInvalidate
2756
+ for (const dep of this.computedDeps) {
2757
+ this.computedUnsubscribers.push(
2758
+ dep.subscribe(this.onComputedInvalidate)
2759
+ );
2760
+ }
2761
+ }
2762
+ unsubscribeFromSourceDeps() {
2763
+ if (this.sourceUnsubscribers.length === 0) return;
2764
+ for (const unsub of this.sourceUnsubscribers) unsub();
2765
+ this.sourceUnsubscribers = [];
2766
+ }
2767
+ unsubscribeFromAllDeps() {
2768
+ this.unsubscribeFromSourceDeps();
2769
+ if (this.computedUnsubscribers.length === 0) return;
2770
+ for (const unsub of this.computedUnsubscribers) unsub();
2771
+ this.computedUnsubscribers = [];
2772
+ }
2773
+ ensureResubscribeAfterFlush() {
2774
+ if (this.isResubscribeEnqueued) return;
2775
+ this.isResubscribeEnqueued = true;
2776
+ this.dequeueResubscribe = this.runtime.scheduleAfterFlush(
2777
+ this.onAfterFlush
2514
2778
  );
2515
2779
  }
2516
2780
  };
2781
+
2782
+ // src/modules/selector/core/OIMSelectorSourceDependencyUpdateEventEmitterKey.ts
2783
+ var OIMSelectorSourceDependencyUpdateEventEmitterKey = class {
2784
+ constructor(subscription, key) {
2785
+ this.subscription = subscription;
2786
+ this.key = key;
2787
+ }
2788
+ subscribe(onUpdate) {
2789
+ return this.subscription.subscribeOnKey(this.key, onUpdate);
2790
+ }
2791
+ };
2792
+
2793
+ // src/modules/selector/core/OIMCollectionByPkSelector.ts
2794
+ var OIMCollectionByPkSelector = class extends OIMSelector {
2795
+ constructor(runtime, collection, pk) {
2796
+ super(runtime, [
2797
+ new OIMSelectorSourceDependencyUpdateEventEmitterKey(
2798
+ collection,
2799
+ pk
2800
+ )
2801
+ ]);
2802
+ this.collection = collection;
2803
+ this.pk = pk;
2804
+ }
2805
+ getValue() {
2806
+ return this.collection.getOneByPk(this.pk);
2807
+ }
2808
+ };
2809
+
2810
+ // src/modules/selector/core/OIMSelectorSourceDependencyUpdateEventEmitterKeys.ts
2811
+ var OIMSelectorSourceDependencyUpdateEventEmitterKeys = class {
2812
+ constructor(subscription, keys) {
2813
+ this.subscription = subscription;
2814
+ this.keys = keys;
2815
+ }
2816
+ subscribe(onUpdate) {
2817
+ return this.subscription.subscribeOnKeys(this.keys, onUpdate);
2818
+ }
2819
+ };
2820
+
2821
+ // src/modules/selector/core/OIMCollectionByPksSelector.ts
2822
+ var OIMCollectionByPksSelector = class extends OIMSelector {
2823
+ constructor(runtime, collection, pks) {
2824
+ super(runtime, [
2825
+ new OIMSelectorSourceDependencyUpdateEventEmitterKeys(
2826
+ collection,
2827
+ pks
2828
+ )
2829
+ ]);
2830
+ this.collection = collection;
2831
+ this.pks = pks;
2832
+ }
2833
+ getValue() {
2834
+ return this.pks.map((pk) => this.collection.getOneByPk(pk));
2835
+ }
2836
+ areEqual(prev, next) {
2837
+ if (prev === next) return true;
2838
+ if (prev.length !== next.length) return false;
2839
+ for (let i = 0; i < prev.length; i++) {
2840
+ if (prev[i] !== next[i]) return false;
2841
+ }
2842
+ return true;
2843
+ }
2844
+ };
2845
+
2846
+ // src/modules/selector/core/OIMObjectValueByKeySelector.ts
2847
+ var OIMObjectValueByKeySelector = class extends OIMSelector {
2848
+ constructor(runtime, obj, key) {
2849
+ super(runtime, [
2850
+ new OIMSelectorSourceDependencyUpdateEventEmitterKey(
2851
+ obj,
2852
+ key
2853
+ )
2854
+ ]);
2855
+ this.obj = obj;
2856
+ this.key = key;
2857
+ }
2858
+ getValue() {
2859
+ return this.obj.get(this.key);
2860
+ }
2861
+ };
2862
+
2863
+ // src/modules/selector/core/OIMObjectValuesByKeysSelector.ts
2864
+ var OIMObjectValuesByKeysSelector = class extends OIMSelector {
2865
+ constructor(runtime, obj, keys) {
2866
+ super(runtime, [
2867
+ new OIMSelectorSourceDependencyUpdateEventEmitterKeys(
2868
+ obj,
2869
+ keys
2870
+ )
2871
+ ]);
2872
+ this.obj = obj;
2873
+ this.keys = keys;
2874
+ }
2875
+ getValue() {
2876
+ return this.keys.map((k) => this.obj.get(k));
2877
+ }
2878
+ areEqual(prev, next) {
2879
+ if (prev === next) return true;
2880
+ if (prev.length !== next.length) return false;
2881
+ for (let i = 0; i < prev.length; i++) {
2882
+ if (prev[i] !== next[i]) return false;
2883
+ }
2884
+ return true;
2885
+ }
2886
+ };
2887
+
2888
+ // src/modules/selector/core/OIMIndexPksByKeySetBasedSelector.ts
2889
+ var OIMIndexPksByKeySetBasedSelector = class extends OIMSelector {
2890
+ constructor(runtime, reactiveIndex, key) {
2891
+ super(runtime, [
2892
+ new OIMSelectorSourceDependencyUpdateEventEmitterKey(
2893
+ reactiveIndex,
2894
+ key
2895
+ )
2896
+ ]);
2897
+ this.reactiveIndex = reactiveIndex;
2898
+ this.key = key;
2899
+ }
2900
+ getValue() {
2901
+ return this.reactiveIndex.getPksByKey(this.key);
2902
+ }
2903
+ };
2904
+
2905
+ // src/modules/selector/core/OIMIndexPksByKeyArrayBasedSelector.ts
2906
+ var OIMIndexPksByKeyArrayBasedSelector = class extends OIMSelector {
2907
+ constructor(runtime, reactiveIndex, key) {
2908
+ super(runtime, [
2909
+ new OIMSelectorSourceDependencyUpdateEventEmitterKey(
2910
+ reactiveIndex,
2911
+ key
2912
+ )
2913
+ ]);
2914
+ this.reactiveIndex = reactiveIndex;
2915
+ this.key = key;
2916
+ }
2917
+ getValue() {
2918
+ return this.reactiveIndex.getPksByKey(this.key);
2919
+ }
2920
+ areEqual(prev, next) {
2921
+ if (prev === next) return true;
2922
+ if (prev.length !== next.length) return false;
2923
+ for (let i = 0; i < prev.length; i++)
2924
+ if (prev[i] !== next[i]) return false;
2925
+ return true;
2926
+ }
2927
+ };
2928
+
2929
+ // src/modules/selector/core/OIMIndexPksByKeysSetBasedSelector.ts
2930
+ var OIMIndexPksByKeysSetBasedSelector = class extends OIMSelector {
2931
+ constructor(runtime, reactiveIndex, keys) {
2932
+ super(runtime, [
2933
+ new OIMSelectorSourceDependencyUpdateEventEmitterKeys(
2934
+ reactiveIndex,
2935
+ keys
2936
+ )
2937
+ ]);
2938
+ this.reactiveIndex = reactiveIndex;
2939
+ this.keys = keys;
2940
+ }
2941
+ getValue() {
2942
+ return this.keys.map((key) => Array.from(this.reactiveIndex.getPksByKey(key))).flat();
2943
+ }
2944
+ areEqual(prev, next) {
2945
+ if (prev === next) return true;
2946
+ if (prev.length !== next.length) return false;
2947
+ for (let i = 0; i < prev.length; i++)
2948
+ if (prev[i] !== next[i]) return false;
2949
+ return true;
2950
+ }
2951
+ };
2952
+
2953
+ // src/modules/selector/core/OIMIndexPksByKeysArrayBasedSelector.ts
2954
+ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
2955
+ constructor(runtime, reactiveIndex, keys) {
2956
+ super(runtime, [
2957
+ new OIMSelectorSourceDependencyUpdateEventEmitterKeys(
2958
+ reactiveIndex,
2959
+ keys
2960
+ )
2961
+ ]);
2962
+ this.reactiveIndex = reactiveIndex;
2963
+ this.keys = keys;
2964
+ }
2965
+ getValue() {
2966
+ return this.keys.map((key) => this.reactiveIndex.getPksByKey(key)).flat();
2967
+ }
2968
+ areEqual(prev, next) {
2969
+ if (prev === next) return true;
2970
+ if (prev.length !== next.length) return false;
2971
+ for (let i = 0; i < prev.length; i++)
2972
+ if (prev[i] !== next[i]) return false;
2973
+ return true;
2974
+ }
2975
+ };
2976
+
2977
+ // src/modules/selector/core/OIMSelectorSourceDependencyEntitiesByIndexKeySetBased.ts
2978
+ var OIMSelectorSourceDependencyEntitiesByIndexKeySetBased = class {
2979
+ constructor(collection, reactiveIndex, key) {
2980
+ this.collection = collection;
2981
+ this.reactiveIndex = reactiveIndex;
2982
+ this.key = key;
2983
+ this.currentPks = [];
2984
+ }
2985
+ subscribe(onUpdate) {
2986
+ const resubscribeCollection = () => {
2987
+ const nextPks = Array.from(
2988
+ this.reactiveIndex.getPksByKey(this.key)
2989
+ );
2990
+ if (this.sameArray(this.currentPks, nextPks)) return;
2991
+ this.unsubscribeFromCollectionKeys?.();
2992
+ this.unsubscribeFromCollectionKeys = void 0;
2993
+ this.currentPks = nextPks;
2994
+ if (this.currentPks.length === 0) return;
2995
+ this.unsubscribeFromCollectionKeys = this.collection.subscribeOnKeys(this.currentPks, onUpdate);
2996
+ };
2997
+ const onIndexUpdate = () => {
2998
+ resubscribeCollection();
2999
+ onUpdate();
3000
+ };
3001
+ resubscribeCollection();
3002
+ const unsubscribeFromIndex = this.reactiveIndex.subscribeOnKey(
3003
+ this.key,
3004
+ onIndexUpdate
3005
+ );
3006
+ return () => {
3007
+ unsubscribeFromIndex();
3008
+ this.unsubscribeFromCollectionKeys?.();
3009
+ this.unsubscribeFromCollectionKeys = void 0;
3010
+ this.currentPks = [];
3011
+ };
3012
+ }
3013
+ sameArray(a, b) {
3014
+ if (a === b) return true;
3015
+ if (a.length !== b.length) return false;
3016
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
3017
+ return true;
3018
+ }
3019
+ };
3020
+
3021
+ // src/modules/selector/core/OIMEntitiesByIndexKeySetBasedSelector.ts
3022
+ var OIMEntitiesByIndexKeySetBasedSelector = class extends OIMSelector {
3023
+ constructor(runtime, collection, reactiveIndex, key) {
3024
+ super(runtime, [
3025
+ new OIMSelectorSourceDependencyEntitiesByIndexKeySetBased(collection, reactiveIndex, key)
3026
+ ]);
3027
+ this.collection = collection;
3028
+ this.reactiveIndex = reactiveIndex;
3029
+ this.key = key;
3030
+ }
3031
+ getValue() {
3032
+ const pks = Array.from(this.reactiveIndex.getPksByKey(this.key));
3033
+ return pks.map((pk) => this.collection.getOneByPk(pk));
3034
+ }
3035
+ areEqual(prev, next) {
3036
+ if (prev === next) return true;
3037
+ if (prev.length !== next.length) return false;
3038
+ for (let i = 0; i < prev.length; i++)
3039
+ if (prev[i] !== next[i]) return false;
3040
+ return true;
3041
+ }
3042
+ };
3043
+
3044
+ // src/modules/selector/core/OIMSelectorSourceDependencyEntitiesByIndexKeyArrayBased.ts
3045
+ var OIMSelectorSourceDependencyEntitiesByIndexKeyArrayBased = class {
3046
+ constructor(collection, reactiveIndex, key) {
3047
+ this.collection = collection;
3048
+ this.reactiveIndex = reactiveIndex;
3049
+ this.key = key;
3050
+ this.currentPks = [];
3051
+ }
3052
+ subscribe(onUpdate) {
3053
+ const resubscribeCollection = () => {
3054
+ const nextPks = this.reactiveIndex.getPksByKey(this.key);
3055
+ if (this.sameArray(this.currentPks, nextPks)) return;
3056
+ this.unsubscribeFromCollectionKeys?.();
3057
+ this.unsubscribeFromCollectionKeys = void 0;
3058
+ this.currentPks = nextPks;
3059
+ if (this.currentPks.length === 0) return;
3060
+ this.unsubscribeFromCollectionKeys = this.collection.subscribeOnKeys(this.currentPks, onUpdate);
3061
+ };
3062
+ const onIndexUpdate = () => {
3063
+ resubscribeCollection();
3064
+ onUpdate();
3065
+ };
3066
+ resubscribeCollection();
3067
+ const unsubscribeFromIndex = this.reactiveIndex.subscribeOnKey(
3068
+ this.key,
3069
+ onIndexUpdate
3070
+ );
3071
+ return () => {
3072
+ unsubscribeFromIndex();
3073
+ this.unsubscribeFromCollectionKeys?.();
3074
+ this.unsubscribeFromCollectionKeys = void 0;
3075
+ this.currentPks = [];
3076
+ };
3077
+ }
3078
+ sameArray(a, b) {
3079
+ if (a === b) return true;
3080
+ if (a.length !== b.length) return false;
3081
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
3082
+ return true;
3083
+ }
3084
+ };
3085
+
3086
+ // src/modules/selector/core/OIMEntitiesByIndexKeyArrayBasedSelector.ts
3087
+ var OIMEntitiesByIndexKeyArrayBasedSelector = class extends OIMSelector {
3088
+ constructor(runtime, collection, reactiveIndex, key) {
3089
+ super(runtime, [
3090
+ new OIMSelectorSourceDependencyEntitiesByIndexKeyArrayBased(collection, reactiveIndex, key)
3091
+ ]);
3092
+ this.collection = collection;
3093
+ this.reactiveIndex = reactiveIndex;
3094
+ this.key = key;
3095
+ }
3096
+ getValue() {
3097
+ const pks = this.reactiveIndex.getPksByKey(this.key);
3098
+ return pks.map((pk) => this.collection.getOneByPk(pk));
3099
+ }
3100
+ areEqual(prev, next) {
3101
+ if (prev === next) return true;
3102
+ if (prev.length !== next.length) return false;
3103
+ for (let i = 0; i < prev.length; i++)
3104
+ if (prev[i] !== next[i]) return false;
3105
+ return true;
3106
+ }
3107
+ };
3108
+
3109
+ // src/modules/computative/core/OIMComputativeRuntime.ts
3110
+ var OIMComputativeRuntime = class {
3111
+ constructor(queue) {
3112
+ this.queue = queue;
3113
+ this.isFlushScheduled = false;
3114
+ this.isFlushing = false;
3115
+ this.minLevel = Infinity;
3116
+ this.maxLevel = -Infinity;
3117
+ this.dirtyByLevel = /* @__PURE__ */ new Map();
3118
+ this.afterFlushTasks = /* @__PURE__ */ new Set();
3119
+ this.queue.emitter.on(0 /* AFTER_FLUSH */, () => {
3120
+ if (!this.isFlushScheduled) return;
3121
+ this.flush();
3122
+ });
3123
+ }
3124
+ /**
3125
+ * Schedule a one-shot task to run on the next `queue.flush()` (AFTER_FLUSH),
3126
+ * ordered by `level` ascending.
3127
+ */
3128
+ schedule(task, level = 0) {
3129
+ let isActive = true;
3130
+ const wrapped = () => {
3131
+ if (!isActive) return;
3132
+ task();
3133
+ };
3134
+ let bucket = this.dirtyByLevel.get(level);
3135
+ if (!bucket) {
3136
+ bucket = /* @__PURE__ */ new Set();
3137
+ this.dirtyByLevel.set(level, bucket);
3138
+ }
3139
+ bucket.add(wrapped);
3140
+ if (level < this.minLevel) this.minLevel = level;
3141
+ if (level > this.maxLevel) this.maxLevel = level;
3142
+ if (!this.isFlushing) this.isFlushScheduled = true;
3143
+ return () => {
3144
+ if (!isActive) return;
3145
+ isActive = false;
3146
+ bucket.delete(wrapped);
3147
+ };
3148
+ }
3149
+ /**
3150
+ * Run once at the start of the next runtime flush (i.e. after queue.flush()).
3151
+ * Useful for resubscribing dependencies exactly once per source flush.
3152
+ */
3153
+ scheduleAfterFlush(task) {
3154
+ let isActive = true;
3155
+ const wrapped = () => {
3156
+ if (!isActive) return;
3157
+ task();
3158
+ };
3159
+ this.afterFlushTasks.add(wrapped);
3160
+ if (!this.isFlushing) this.isFlushScheduled = true;
3161
+ return () => {
3162
+ if (!isActive) return;
3163
+ isActive = false;
3164
+ this.afterFlushTasks.delete(wrapped);
3165
+ };
3166
+ }
3167
+ flush() {
3168
+ this.isFlushScheduled = false;
3169
+ if (this.dirtyByLevel.size === 0 && this.afterFlushTasks.size === 0)
3170
+ return;
3171
+ this.isFlushing = true;
3172
+ try {
3173
+ if (this.afterFlushTasks.size > 0) {
3174
+ const snapshot = Array.from(this.afterFlushTasks);
3175
+ this.afterFlushTasks.clear();
3176
+ for (let i = 0; i < snapshot.length; i++) snapshot[i]();
3177
+ }
3178
+ if (this.dirtyByLevel.size === 0) return;
3179
+ let level = this.minLevel;
3180
+ let max = this.maxLevel;
3181
+ while (level <= max) {
3182
+ for (; ; ) {
3183
+ const bucket = this.dirtyByLevel.get(level);
3184
+ if (!bucket || bucket.size === 0) break;
3185
+ this.dirtyByLevel.delete(level);
3186
+ const snapshot = Array.from(bucket);
3187
+ for (let i = 0; i < snapshot.length; i++) snapshot[i]();
3188
+ }
3189
+ if (this.maxLevel > max) max = this.maxLevel;
3190
+ level++;
3191
+ }
3192
+ } finally {
3193
+ this.isFlushing = false;
3194
+ if (this.dirtyByLevel.size === 0) {
3195
+ this.minLevel = Infinity;
3196
+ this.maxLevel = -Infinity;
3197
+ } else {
3198
+ let nextMin = Infinity;
3199
+ let nextMax = -Infinity;
3200
+ this.dirtyByLevel.forEach((_bucket, lvl) => {
3201
+ if (lvl < nextMin) nextMin = lvl;
3202
+ if (lvl > nextMax) nextMax = lvl;
3203
+ });
3204
+ this.minLevel = nextMin;
3205
+ this.maxLevel = nextMax;
3206
+ this.isFlushScheduled = true;
3207
+ }
3208
+ }
3209
+ }
3210
+ };
2517
3211
  // Annotate the CommonJS export names for ESM import in node:
2518
3212
  0 && (module.exports = {
2519
3213
  EOIMCollectionEventType,
2520
3214
  EOIMComputedEventType,
2521
- EOIMEffectPhase,
2522
3215
  EOIMEventQueueEventType,
2523
3216
  EOIMEventQueueSchedulerEventType,
2524
3217
  EOIMIndexEventType,
2525
3218
  EOIMObjectEventType,
2526
- EOIMUpdateEventCoalescerEventType,
2527
3219
  OIMCollection,
3220
+ OIMCollectionByPkSelector,
3221
+ OIMCollectionByPksSelector,
2528
3222
  OIMCollectionChangedFieldsWrapper,
2529
3223
  OIMCollectionStore,
2530
3224
  OIMCollectionStoreMapDriven,
2531
3225
  OIMComparatorFactory,
3226
+ OIMComputativeRuntime,
2532
3227
  OIMComputed,
2533
3228
  OIMDBSettings,
2534
3229
  OIMEffect,
@@ -2537,6 +3232,8 @@ var OIMEffectDependencyComputed = class {
2537
3232
  OIMEffectDependencyKeyedCollection,
2538
3233
  OIMEffectDependencyKeyedIndex,
2539
3234
  OIMEffectDependencyKeyedObject,
3235
+ OIMEntitiesByIndexKeyArrayBasedSelector,
3236
+ OIMEntitiesByIndexKeySetBasedSelector,
2540
3237
  OIMEntityUpdaterFactory,
2541
3238
  OIMEventEmitter,
2542
3239
  OIMEventQueue,
@@ -2551,6 +3248,10 @@ var OIMEffectDependencyComputed = class {
2551
3248
  OIMIndexManualArrayBased,
2552
3249
  OIMIndexManualOrderedArrayBased,
2553
3250
  OIMIndexManualSetBased,
3251
+ OIMIndexPksByKeyArrayBasedSelector,
3252
+ OIMIndexPksByKeySetBasedSelector,
3253
+ OIMIndexPksByKeysArrayBasedSelector,
3254
+ OIMIndexPksByKeysSetBasedSelector,
2554
3255
  OIMIndexSetBased,
2555
3256
  OIMIndexStoreArrayBased,
2556
3257
  OIMIndexStoreMapDrivenArrayBased,
@@ -2561,6 +3262,8 @@ var OIMEffectDependencyComputed = class {
2561
3262
  OIMObjectStore,
2562
3263
  OIMObjectStoreMapDriven,
2563
3264
  OIMObjectStoreRecordDriven,
3265
+ OIMObjectValueByKeySelector,
3266
+ OIMObjectValuesByKeysSelector,
2564
3267
  OIMOrderedListIndexCommandStreamWrapper,
2565
3268
  OIMPkSelectorFactory,
2566
3269
  OIMRICollection,
@@ -2570,11 +3273,5 @@ var OIMEffectDependencyComputed = class {
2570
3273
  OIMReactiveIndexManualSetBased,
2571
3274
  OIMReactiveIndexSetBased,
2572
3275
  OIMReactiveObject,
2573
- OIMUpdateEventCoalescer,
2574
- OIMUpdateEventCoalescerCollection,
2575
- OIMUpdateEventCoalescerComputed,
2576
- OIMUpdateEventCoalescerIndex,
2577
- OIMUpdateEventCoalescerManual,
2578
- OIMUpdateEventCoalescerObject,
2579
- OIMUpdateEventEmitter
3276
+ OIMSelector
2580
3277
  });