@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.js CHANGED
@@ -15,6 +15,11 @@ var OIMEventEmitter = class {
15
15
  constructor() {
16
16
  this.buckets = {};
17
17
  }
18
+ hasHandlers(event) {
19
+ const bucket = this.buckets[event];
20
+ if (!bucket) return false;
21
+ return bucket.indexByHandler.size > 0;
22
+ }
18
23
  getOrCreateBucket(event) {
19
24
  let bucket = this.buckets[event];
20
25
  if (!bucket) {
@@ -297,6 +302,12 @@ var OIMIndexSetBased = class {
297
302
  emitUpdate(keys) {
298
303
  this.emitter.emit(0 /* UPDATE */, { keys });
299
304
  }
305
+ /**
306
+ * Emit update event for a single key (fast-path override point).
307
+ */
308
+ emitUpdateOne(key) {
309
+ this.emitUpdate([key]);
310
+ }
300
311
  };
301
312
 
302
313
  // src/core/OIMIndexStoreMapDrivenArrayBased.ts
@@ -446,45 +457,34 @@ var OIMIndexArrayBased = class {
446
457
  emitUpdate(keys) {
447
458
  this.emitter.emit(0 /* UPDATE */, { keys });
448
459
  }
460
+ /**
461
+ * Emit update event for a single key (fast-path override point).
462
+ */
463
+ emitUpdateOne(key) {
464
+ this.emitUpdate([key]);
465
+ }
449
466
  };
450
467
 
451
- // src/enum/EOIMUpdateEventCoalescerEventType.ts
452
- var EOIMUpdateEventCoalescerEventType = /* @__PURE__ */ ((EOIMUpdateEventCoalescerEventType2) => {
453
- EOIMUpdateEventCoalescerEventType2[EOIMUpdateEventCoalescerEventType2["HAS_CHANGES"] = 0] = "HAS_CHANGES";
454
- EOIMUpdateEventCoalescerEventType2[EOIMUpdateEventCoalescerEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
455
- EOIMUpdateEventCoalescerEventType2[EOIMUpdateEventCoalescerEventType2["AFTER_FLUSH"] = 2] = "AFTER_FLUSH";
456
- return EOIMUpdateEventCoalescerEventType2;
457
- })(EOIMUpdateEventCoalescerEventType || {});
468
+ // src/enum/EOIMEventQueueEventType.ts
469
+ var EOIMEventQueueEventType = /* @__PURE__ */ ((EOIMEventQueueEventType2) => {
470
+ EOIMEventQueueEventType2[EOIMEventQueueEventType2["AFTER_FLUSH"] = 0] = "AFTER_FLUSH";
471
+ EOIMEventQueueEventType2[EOIMEventQueueEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
472
+ return EOIMEventQueueEventType2;
473
+ })(EOIMEventQueueEventType || {});
458
474
 
459
475
  // src/core/OIMUpdateEventEmitter.ts
460
476
  var OIMUpdateEventEmitter = class {
461
- constructor(opts) {
462
- this.keyHandlersBeforeFlush = /* @__PURE__ */ new Map();
477
+ constructor(queue, deliveryMode = "queue") {
463
478
  this.keyHandlers = /* @__PURE__ */ new Map();
464
- this.isDestroyed = false;
465
- this.isDispatching = false;
466
- this.isInPrePhase = false;
467
- this.hasPendingDuringPrePhase = false;
468
479
  this.isFlushEnqueued = false;
469
- this.handleHasChanges = () => {
470
- if (this.isDispatching && this.isInPrePhase) {
471
- this.hasPendingDuringPrePhase = true;
472
- return;
473
- }
474
- if (this.isFlushEnqueued) return;
475
- this.isFlushEnqueued = true;
476
- this.queue.enqueue(this.onFlush);
477
- };
480
+ this.updatedKeys = /* @__PURE__ */ new Set();
481
+ this.isAfterFlushDeliveryScheduled = false;
478
482
  // Stable handler reference to avoid allocating a new closure on every HAS_CHANGES.
479
483
  this.onFlush = () => {
480
484
  this.isFlushEnqueued = false;
481
- if (this.isDestroyed) {
482
- this.coalescer.clearUpdatedKeys();
483
- this.coalescer.clearFlushingUpdatedKeys();
484
- return;
485
- }
486
- if (this.keyHandlers.size === 0 && this.keyHandlersBeforeFlush.size === 0) {
487
- this.coalescer.clearUpdatedKeys();
485
+ this.dequeueFlush = void 0;
486
+ if (this.keyHandlers.size === 0) {
487
+ this.updatedKeys.clear();
488
488
  return;
489
489
  }
490
490
  const makeHandlerSnapshot = (handlers) => {
@@ -492,95 +492,133 @@ var OIMUpdateEventEmitter = class {
492
492
  handlers.forEach((h) => snapshot.push(h));
493
493
  return snapshot;
494
494
  };
495
- try {
496
- this.isDispatching = true;
497
- this.isInPrePhase = true;
498
- this.hasPendingDuringPrePhase = false;
499
- const allKeysForNormalPhase = this.runPrePhaseDrain(makeHandlerSnapshot);
500
- this.isInPrePhase = false;
501
- this.runHandlersPhase(allKeysForNormalPhase, makeHandlerSnapshot);
502
- } finally {
503
- this.isInPrePhase = false;
504
- this.isDispatching = false;
505
- }
495
+ this.runSinglePass(makeHandlerSnapshot);
506
496
  };
507
- this.coalescer = opts.coalescer;
508
- this.coalescer.emitter.on(
509
- 0 /* HAS_CHANGES */,
510
- this.handleHasChanges
511
- );
512
- this.queue = opts.queue;
497
+ this.onAfterFlush = () => {
498
+ if (!this.isAfterFlushDeliveryScheduled) return;
499
+ this.isAfterFlushDeliveryScheduled = false;
500
+ this.deliverImmediate();
501
+ };
502
+ this.queue = queue;
503
+ this.deliveryMode = deliveryMode;
504
+ if (this.deliveryMode === "after_flush") {
505
+ this.unsubscribeAfterFlushDelivery = this.queue.emitter.on(
506
+ 0 /* AFTER_FLUSH */,
507
+ this.onAfterFlush
508
+ );
509
+ }
513
510
  }
514
511
  /**
515
- * Helps to run computed values etc.
512
+ * Mark keys as updated and schedule delivery.
513
+ *
514
+ * If there are no subscriptions, this is a no-op and does not allocate or retain keys.
516
515
  */
517
- runPrePhaseDrain(makeHandlerSnapshot) {
518
- const allKeysForNormalPhase = /* @__PURE__ */ new Set();
519
- let hasMorePreWork = true;
520
- while (hasMorePreWork) {
521
- this.coalescer.markUpdatedKeysAsFlushing();
522
- const flushingKeys = this.coalescer.getFlushingUpdatedKeys();
523
- flushingKeys.forEach((key) => allKeysForNormalPhase.add(key));
524
- try {
525
- if (this.keyHandlersBeforeFlush.size > 0) {
526
- if (flushingKeys.size * 2 < this.keyHandlersBeforeFlush.size) {
527
- flushingKeys.forEach((key) => {
528
- if (this.isDestroyed) return;
529
- const handlers = this.keyHandlersBeforeFlush.get(key);
530
- if (!handlers || handlers.size === 0) return;
531
- const handlerSnapshot = makeHandlerSnapshot(handlers);
532
- for (let i = 0; i < handlerSnapshot.length; i++) {
533
- if (this.isDestroyed) return;
534
- const handler = handlerSnapshot[i];
535
- if (handlers.has(handler)) handler();
536
- }
537
- });
538
- } else {
539
- this.keyHandlersBeforeFlush.forEach((handlers, key) => {
540
- if (this.isDestroyed) return;
541
- if (!flushingKeys.has(key)) return;
542
- if (!handlers || handlers.size === 0) return;
543
- const handlerSnapshot = makeHandlerSnapshot(handlers);
544
- for (let i = 0; i < handlerSnapshot.length; i++) {
545
- if (this.isDestroyed) return;
546
- const handler = handlerSnapshot[i];
547
- if (handlers.has(handler)) handler();
548
- }
549
- });
550
- }
551
- }
552
- } finally {
553
- this.coalescer.clearFlushingUpdatedKeys();
554
- }
555
- const nextHasMorePreWork = this.hasPendingDuringPrePhase || this.coalescer.getUpdatedKeys().size > 0;
556
- this.hasPendingDuringPrePhase = false;
557
- hasMorePreWork = nextHasMorePreWork;
516
+ markUpdatedKeys(keys) {
517
+ if (keys.length === 0) return;
518
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
519
+ throw new Error(
520
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
521
+ );
522
+ }
523
+ if (this.keyHandlers.size === 0) return;
524
+ let didAddAny = false;
525
+ for (let i = 0; i < keys.length; i++) {
526
+ const key = keys[i];
527
+ const handlers = this.keyHandlers.get(key);
528
+ if (!handlers || handlers.size === 0) continue;
529
+ this.updatedKeys.add(key);
530
+ didAddAny = true;
531
+ }
532
+ if (!didAddAny) return;
533
+ if (this.deliveryMode === "immediate") {
534
+ this.deliverImmediate();
535
+ return;
536
+ }
537
+ if (this.deliveryMode === "after_flush") {
538
+ this.isAfterFlushDeliveryScheduled = true;
539
+ return;
540
+ }
541
+ if (this.isFlushEnqueued) return;
542
+ this.isFlushEnqueued = true;
543
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
544
+ }
545
+ markUpdatedKey(key) {
546
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
547
+ throw new Error(
548
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
549
+ );
550
+ }
551
+ if (this.keyHandlers.size === 0) return;
552
+ const handlers = this.keyHandlers.get(key);
553
+ if (!handlers || handlers.size === 0) return;
554
+ this.updatedKeys.add(key);
555
+ if (this.deliveryMode === "immediate") {
556
+ this.deliverImmediate();
557
+ return;
558
+ }
559
+ if (this.deliveryMode === "after_flush") {
560
+ this.isAfterFlushDeliveryScheduled = true;
561
+ return;
558
562
  }
559
- return allKeysForNormalPhase;
563
+ if (this.isFlushEnqueued) return;
564
+ this.isFlushEnqueued = true;
565
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
560
566
  }
561
- runHandlersPhase(allKeysForNormalPhase, makeHandlerSnapshot) {
562
- if (this.keyHandlers.size === 0 || allKeysForNormalPhase.size === 0)
567
+ markAllUpdated() {
568
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
569
+ throw new Error(
570
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
571
+ );
572
+ }
573
+ if (this.keyHandlers.size === 0) return;
574
+ this.keyHandlers.forEach((_handlers, key) => {
575
+ this.updatedKeys.add(key);
576
+ });
577
+ if (this.deliveryMode === "immediate") {
578
+ this.deliverImmediate();
579
+ return;
580
+ }
581
+ if (this.deliveryMode === "after_flush") {
582
+ this.isAfterFlushDeliveryScheduled = true;
563
583
  return;
564
- if (allKeysForNormalPhase.size * 2 < this.keyHandlers.size) {
565
- allKeysForNormalPhase.forEach((key) => {
566
- if (this.isDestroyed) return;
584
+ }
585
+ if (this.isFlushEnqueued) return;
586
+ this.isFlushEnqueued = true;
587
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
588
+ }
589
+ deliverImmediate() {
590
+ const makeHandlerSnapshot = (handlers) => {
591
+ const snapshot = [];
592
+ handlers.forEach((h) => snapshot.push(h));
593
+ return snapshot;
594
+ };
595
+ this.runSinglePass(makeHandlerSnapshot);
596
+ }
597
+ /**
598
+ * Deliver updated keys and notify handlers once.
599
+ * Re-entrant updates during delivery are forbidden and will throw in markUpdatedKeys().
600
+ */
601
+ runSinglePass(makeHandlerSnapshot) {
602
+ if (this.updatedKeys.size === 0) return;
603
+ const flushingKeys = this.updatedKeys;
604
+ this.updatedKeys = /* @__PURE__ */ new Set();
605
+ if (this.keyHandlers.size === 0 || flushingKeys.size === 0) return;
606
+ if (flushingKeys.size * 2 < this.keyHandlers.size) {
607
+ flushingKeys.forEach((key) => {
567
608
  const handlers = this.keyHandlers.get(key);
568
609
  if (!handlers || handlers.size === 0) return;
569
610
  const handlerSnapshot = makeHandlerSnapshot(handlers);
570
611
  for (let i = 0; i < handlerSnapshot.length; i++) {
571
- if (this.isDestroyed) return;
572
612
  const handler = handlerSnapshot[i];
573
613
  if (handlers.has(handler)) handler();
574
614
  }
575
615
  });
576
616
  } else {
577
617
  this.keyHandlers.forEach((handlers, key) => {
578
- if (this.isDestroyed) return;
579
- if (!allKeysForNormalPhase.has(key)) return;
618
+ if (!flushingKeys.has(key)) return;
580
619
  if (!handlers || handlers.size === 0) return;
581
620
  const handlerSnapshot = makeHandlerSnapshot(handlers);
582
621
  for (let i = 0; i < handlerSnapshot.length; i++) {
583
- if (this.isDestroyed) return;
584
622
  const handler = handlerSnapshot[i];
585
623
  if (handlers.has(handler)) handler();
586
624
  }
@@ -588,14 +626,17 @@ var OIMUpdateEventEmitter = class {
588
626
  }
589
627
  }
590
628
  destroy() {
591
- if (this.isDestroyed) return;
592
- this.isDestroyed = true;
593
- this.coalescer.emitter.off(
594
- 0 /* HAS_CHANGES */,
595
- this.handleHasChanges
596
- );
597
- this.keyHandlersBeforeFlush.clear();
629
+ if (this.dequeueFlush) {
630
+ this.dequeueFlush();
631
+ this.dequeueFlush = void 0;
632
+ }
633
+ if (this.unsubscribeAfterFlushDelivery) {
634
+ this.unsubscribeAfterFlushDelivery();
635
+ this.unsubscribeAfterFlushDelivery = void 0;
636
+ }
637
+ this.keyHandlers.forEach((handlers) => handlers.clear());
598
638
  this.keyHandlers.clear();
639
+ this.updatedKeys.clear();
599
640
  }
600
641
  /**
601
642
  * Get performance metrics for monitoring and debugging
@@ -616,32 +657,13 @@ var OIMUpdateEventEmitter = class {
616
657
  * Check if there are any active subscriptions
617
658
  */
618
659
  hasSubscriptions() {
619
- return this.keyHandlers.size > 0 || this.keyHandlersBeforeFlush.size > 0;
660
+ return this.keyHandlers.size > 0;
620
661
  }
621
662
  /**
622
663
  * Get the number of handlers for a specific key
623
664
  */
624
665
  getHandlerCount(key) {
625
- return (this.keyHandlers.get(key)?.size ?? 0) + (this.keyHandlersBeforeFlush.get(key)?.size ?? 0);
626
- }
627
- subscribeOnKeyBeforeFlush(key, handler) {
628
- let handlers = this.keyHandlersBeforeFlush.get(key);
629
- if (!handlers) {
630
- handlers = /* @__PURE__ */ new Set();
631
- this.keyHandlersBeforeFlush.set(key, handlers);
632
- }
633
- handlers.add(handler);
634
- return () => {
635
- this.unsubscribeFromKeyBeforeFlush(key, handler);
636
- };
637
- }
638
- unsubscribeFromKeyBeforeFlush(key, handler) {
639
- const handlers = this.keyHandlersBeforeFlush.get(key);
640
- if (!handlers) return;
641
- handlers.delete(handler);
642
- if (handlers.size === 0) {
643
- this.keyHandlersBeforeFlush.delete(key);
644
- }
666
+ return this.keyHandlers.get(key)?.size ?? 0;
645
667
  }
646
668
  subscribeOnKey(key, handler) {
647
669
  let handlers = this.keyHandlers.get(key);
@@ -660,6 +682,15 @@ var OIMUpdateEventEmitter = class {
660
682
  handlers.delete(handler);
661
683
  if (handlers.size === 0) {
662
684
  this.keyHandlers.delete(key);
685
+ this.updatedKeys.delete(key);
686
+ }
687
+ if (this.keyHandlers.size === 0) {
688
+ this.updatedKeys.clear();
689
+ if (this.dequeueFlush) {
690
+ this.dequeueFlush();
691
+ this.dequeueFlush = void 0;
692
+ }
693
+ this.isFlushEnqueued = false;
663
694
  }
664
695
  }
665
696
  subscribeOnKeys(keys, handler) {
@@ -681,30 +712,6 @@ var OIMUpdateEventEmitter = class {
681
712
  }
682
713
  };
683
714
  }
684
- subscribeOnKeysBeforeFlush(keys, handler) {
685
- const subscribedKeys = [];
686
- for (const key of keys) {
687
- let handlers = this.keyHandlersBeforeFlush.get(key);
688
- if (!handlers) {
689
- handlers = /* @__PURE__ */ new Set();
690
- this.keyHandlersBeforeFlush.set(key, handlers);
691
- }
692
- if (!handlers.has(handler)) {
693
- handlers.add(handler);
694
- subscribedKeys.push(key);
695
- }
696
- }
697
- return () => {
698
- for (const key of subscribedKeys) {
699
- this.unsubscribeFromKeyBeforeFlush(key, handler);
700
- }
701
- };
702
- }
703
- unsubscribeFromKeysBeforeFlush(keys, handler) {
704
- for (const key of keys) {
705
- this.unsubscribeFromKeyBeforeFlush(key, handler);
706
- }
707
- }
708
715
  unsubscribeFromKeys(keys, handler) {
709
716
  for (const key of keys) {
710
717
  this.unsubscribeFromKey(key, handler);
@@ -712,203 +719,11 @@ var OIMUpdateEventEmitter = class {
712
719
  }
713
720
  };
714
721
 
715
- // src/core/OIMIndexManualSetBased.ts
716
- var OIMIndexManualSetBased = class extends OIMIndexSetBased {
717
- constructor(options = {}) {
718
- super(options);
719
- }
720
- /**
721
- * Set primary keys for a specific index key, replacing any existing values.
722
- * Uses optional comparator to skip updates if PKs haven't actually changed.
723
- */
724
- setPks(key, pks) {
725
- const hasChanges = this.setPksWithComparison(key, new Set(pks));
726
- if (hasChanges) {
727
- this.emitUpdate([key]);
728
- }
729
- }
730
- /**
731
- * Add primary keys to a specific index key
732
- */
733
- addPks(key, pks) {
734
- if (pks.length === 0) return;
735
- let pksSet = this.store.getOneByKey(key);
736
- if (!pksSet) {
737
- pksSet = /* @__PURE__ */ new Set();
738
- this.store.setOneByKey(key, pksSet);
739
- }
740
- let hasChanges = false;
741
- for (const pk of pks) {
742
- if (!pksSet.has(pk)) {
743
- pksSet.add(pk);
744
- hasChanges = true;
745
- }
746
- }
747
- if (hasChanges) {
748
- this.store.setOneByKey(key, pksSet);
749
- this.emitUpdate([key]);
750
- }
751
- }
752
- /**
753
- * Remove primary keys from a specific index key
754
- */
755
- removePks(key, pks) {
756
- if (pks.length === 0) return;
757
- const pksSet = this.store.getOneByKey(key);
758
- if (!pksSet) return;
759
- let hasChanges = false;
760
- for (const pk of pks) {
761
- if (pksSet.delete(pk)) {
762
- hasChanges = true;
763
- }
764
- }
765
- if (pksSet.size === 0) {
766
- this.store.removeOneByKey(key);
767
- hasChanges = true;
768
- } else if (hasChanges) {
769
- this.store.setOneByKey(key, pksSet);
770
- }
771
- if (hasChanges) {
772
- this.emitUpdate([key]);
773
- }
774
- }
775
- /**
776
- * Clear all primary keys for a specific index key, or all keys if no key specified
777
- */
778
- clear(key) {
779
- if (key === void 0) {
780
- const allKeys = this.store.getAllKeys();
781
- if (allKeys.length > 0) {
782
- this.store.clear();
783
- this.emitUpdate(allKeys);
784
- }
785
- } else {
786
- if (this.store.getOneByKey(key) !== void 0) {
787
- this.store.removeOneByKey(key);
788
- this.emitUpdate([key]);
789
- }
790
- }
791
- }
792
- };
793
-
794
- // src/const/OIMDBSettings.ts
795
- var OIMDBSettings = class {
796
- };
797
- /**
798
- * Fixed key used in the manual index to store updated keys set in coalescers.
799
- * This constant is exported so users can create their own indexes and pass them to coalescers.
800
- */
801
- OIMDBSettings.UPDATED_KEYS_INDEX_KEY = "__updatedKeys__";
802
- OIMDBSettings.FLUSHING_KEYS_INDEX_KEY = "__flushingKeys__";
803
-
804
- // src/core/OIMUpdateEventCoalescer.ts
805
- var OIMUpdateEventCoalescer = class {
806
- constructor(index, flushingIndex) {
807
- this.emitter = new OIMEventEmitter();
808
- this.hasEmittedChanges = false;
809
- this.updatedKeysIndex = index ?? new OIMIndexManualSetBased({
810
- store: new OIMIndexStoreMapDrivenSetBased()
811
- });
812
- this.flushingUpdatedKeysIndex = flushingIndex ?? new OIMIndexManualSetBased({
813
- store: new OIMIndexStoreMapDrivenSetBased()
814
- });
815
- if (!this.updatedKeysIndex.hasKey(OIMDBSettings.UPDATED_KEYS_INDEX_KEY)) {
816
- this.updatedKeysIndex.setPks(
817
- OIMDBSettings.UPDATED_KEYS_INDEX_KEY,
818
- []
819
- );
820
- }
821
- }
822
- /**
823
- * Get the set of keys that have been updated since last clear
824
- */
825
- getUpdatedKeys() {
826
- return this.updatedKeysIndex.getPksByKey(
827
- OIMDBSettings.UPDATED_KEYS_INDEX_KEY
828
- );
829
- }
830
- getFlushingUpdatedKeys() {
831
- return this.flushingUpdatedKeysIndex.getPksByKey(
832
- OIMDBSettings.FLUSHING_KEYS_INDEX_KEY
833
- );
834
- }
835
- markUpdatedKeysAsFlushing() {
836
- this.flushingUpdatedKeysIndex.setPks(
837
- OIMDBSettings.FLUSHING_KEYS_INDEX_KEY,
838
- Array.from(this.getUpdatedKeys())
839
- );
840
- this.clearUpdatedKeys();
841
- }
842
- /**
843
- * Clear all updated keys and reset the changed state
844
- */
845
- clearUpdatedKeys() {
846
- this.emitter.emit(
847
- 1 /* BEFORE_FLUSH */,
848
- void 0
849
- );
850
- this.updatedKeysIndex.setPks(OIMDBSettings.UPDATED_KEYS_INDEX_KEY, []);
851
- this.hasEmittedChanges = false;
852
- this.emitter.emit(
853
- 2 /* AFTER_FLUSH */,
854
- void 0
855
- );
856
- }
857
- clearFlushingUpdatedKeys() {
858
- this.flushingUpdatedKeysIndex.setPks(
859
- OIMDBSettings.FLUSHING_KEYS_INDEX_KEY,
860
- []
861
- );
862
- }
863
- /**
864
- * Add keys to the updated set and emit HAS_CHANGES event if needed
865
- */
866
- addUpdatedKeys(keys) {
867
- this.updatedKeysIndex.addPks(
868
- OIMDBSettings.UPDATED_KEYS_INDEX_KEY,
869
- keys
870
- );
871
- if (!this.hasEmittedChanges) {
872
- this.hasEmittedChanges = true;
873
- this.emitter.emit(
874
- 0 /* HAS_CHANGES */,
875
- void 0
876
- );
877
- }
878
- }
879
- };
880
-
881
- // src/core/OIMUpdateEventCoalescerIndex.ts
882
- var OIMUpdateEventCoalescerIndex = class extends OIMUpdateEventCoalescer {
883
- constructor(indexEmitter) {
884
- super();
885
- this.indexEmitter = indexEmitter;
886
- /**
887
- * When the index is updated, we keep track of the updated keys.
888
- */
889
- this.handleUpdate = (payload) => {
890
- this.addUpdatedKeys(payload.keys);
891
- };
892
- this.indexEmitter.on(0 /* UPDATE */, this.handleUpdate);
893
- }
894
- destroy() {
895
- this.indexEmitter.off(0 /* UPDATE */, this.handleUpdate);
896
- this.emitter.offAll();
897
- this.clearUpdatedKeys();
898
- }
899
- };
900
-
901
722
  // src/abstract/OIMReactiveIndexSetBased.ts
902
723
  var OIMReactiveIndexSetBased = class {
903
- constructor(queue, opts) {
904
- this.index = opts?.index ?? this.createDefaultIndex();
905
- this.coalescer = new OIMUpdateEventCoalescerIndex(
906
- this.index.emitter
907
- );
908
- this.updateEventEmitter = new OIMUpdateEventEmitter({
909
- coalescer: this.coalescer,
910
- queue
911
- });
724
+ constructor(queue, createIndex) {
725
+ this.updateEmitter = new OIMUpdateEventEmitter(queue);
726
+ this.index = createIndex(this.updateEmitter);
912
727
  }
913
728
  getPksByKey(key) {
914
729
  return this.index.getPksByKey(key);
@@ -934,24 +749,38 @@ var OIMReactiveIndexSetBased = class {
934
749
  getMetrics() {
935
750
  return this.index.getMetrics();
936
751
  }
752
+ subscribeOnKey(key, handler) {
753
+ return this.updateEmitter.subscribeOnKey(key, handler);
754
+ }
755
+ subscribeOnKeys(keys, handler) {
756
+ return this.updateEmitter.subscribeOnKeys(keys, handler);
757
+ }
758
+ unsubscribeFromKey(key, handler) {
759
+ this.updateEmitter.unsubscribeFromKey(key, handler);
760
+ }
761
+ unsubscribeFromKeys(keys, handler) {
762
+ this.updateEmitter.unsubscribeFromKeys(keys, handler);
763
+ }
764
+ hasSubscriptions() {
765
+ return this.updateEmitter.hasSubscriptions();
766
+ }
767
+ getHandlerCount(key) {
768
+ return this.updateEmitter.getHandlerCount(key);
769
+ }
770
+ getSubscriptionMetrics() {
771
+ return this.updateEmitter.getMetrics();
772
+ }
937
773
  destroy() {
938
- this.updateEventEmitter.destroy();
939
- this.coalescer.destroy();
774
+ this.updateEmitter.destroy();
940
775
  this.index.destroy();
941
776
  }
942
777
  };
943
778
 
944
779
  // src/abstract/OIMReactiveIndexArrayBased.ts
945
780
  var OIMReactiveIndexArrayBased = class {
946
- constructor(queue, opts) {
947
- this.index = opts?.index ?? this.createDefaultIndex();
948
- this.coalescer = new OIMUpdateEventCoalescerIndex(
949
- this.index.emitter
950
- );
951
- this.updateEventEmitter = new OIMUpdateEventEmitter({
952
- coalescer: this.coalescer,
953
- queue
954
- });
781
+ constructor(queue, createIndex) {
782
+ this.updateEmitter = new OIMUpdateEventEmitter(queue);
783
+ this.index = createIndex(this.updateEmitter);
955
784
  }
956
785
  getPksByKey(key) {
957
786
  return this.index.getPksByKey(key);
@@ -977,9 +806,29 @@ var OIMReactiveIndexArrayBased = class {
977
806
  getMetrics() {
978
807
  return this.index.getMetrics();
979
808
  }
809
+ subscribeOnKey(key, handler) {
810
+ return this.updateEmitter.subscribeOnKey(key, handler);
811
+ }
812
+ subscribeOnKeys(keys, handler) {
813
+ return this.updateEmitter.subscribeOnKeys(keys, handler);
814
+ }
815
+ unsubscribeFromKey(key, handler) {
816
+ this.updateEmitter.unsubscribeFromKey(key, handler);
817
+ }
818
+ unsubscribeFromKeys(keys, handler) {
819
+ this.updateEmitter.unsubscribeFromKeys(keys, handler);
820
+ }
821
+ hasSubscriptions() {
822
+ return this.updateEmitter.hasSubscriptions();
823
+ }
824
+ getHandlerCount(key) {
825
+ return this.updateEmitter.getHandlerCount(key);
826
+ }
827
+ getSubscriptionMetrics() {
828
+ return this.updateEmitter.getMetrics();
829
+ }
980
830
  destroy() {
981
- this.updateEventEmitter.destroy();
982
- this.coalescer.destroy();
831
+ this.updateEmitter.destroy();
983
832
  this.index.destroy();
984
833
  }
985
834
  };
@@ -1151,47 +1000,131 @@ var OIMCollection = class {
1151
1000
  }
1152
1001
  };
1153
1002
 
1154
- // src/core/OIMUpdateEventCoalescerCollection.ts
1155
- var OIMUpdateEventCoalescerCollection = class extends OIMUpdateEventCoalescer {
1156
- constructor(collectionEmitter) {
1157
- super();
1158
- this.collectionEmitter = collectionEmitter;
1159
- /**
1160
- * When the collection is updated, we keep track of the updated pks.
1161
- */
1162
- this.handleUpdate = (payload) => {
1163
- this.addUpdatedKeys(payload.pks);
1164
- };
1165
- this.collectionEmitter.on(
1166
- 0 /* UPDATE */,
1167
- this.handleUpdate
1168
- );
1169
- }
1170
- destroy() {
1171
- this.collectionEmitter.off(
1172
- 0 /* UPDATE */,
1173
- this.handleUpdate
1174
- );
1175
- this.emitter.offAll();
1176
- this.clearUpdatedKeys();
1177
- }
1178
- };
1179
-
1180
1003
  // src/core/OIMReactiveCollection.ts
1181
1004
  var OIMReactiveCollection = class extends OIMCollection {
1182
1005
  constructor(queue, opts) {
1183
1006
  super(opts);
1184
- this.coalescer = new OIMUpdateEventCoalescerCollection(
1185
- this.emitter
1007
+ this.anyUpdateHandlers = /* @__PURE__ */ new Set();
1008
+ this.pendingAnyUpdatePks = /* @__PURE__ */ new Set();
1009
+ this.isAnyUpdateClearPending = false;
1010
+ this.isAnyUpdateScheduled = false;
1011
+ this.runAnyUpdate = () => {
1012
+ if (!this.isAnyUpdateScheduled) return;
1013
+ this.isAnyUpdateScheduled = false;
1014
+ this.dequeueAnyUpdate = void 0;
1015
+ const pks = this.isAnyUpdateClearPending ? [] : Array.from(this.pendingAnyUpdatePks);
1016
+ this.pendingAnyUpdatePks.clear();
1017
+ this.isAnyUpdateClearPending = false;
1018
+ const snapshot = Array.from(this.anyUpdateHandlers);
1019
+ for (let i = 0; i < snapshot.length; i++) snapshot[i](pks);
1020
+ };
1021
+ this.queue = queue;
1022
+ this.updateEmitter = new OIMUpdateEventEmitter(queue, "queue");
1023
+ }
1024
+ subscribeOnKey(pk, handler) {
1025
+ return this.updateEmitter.subscribeOnKey(pk, handler);
1026
+ }
1027
+ subscribeOnKeys(pks, handler) {
1028
+ return this.updateEmitter.subscribeOnKeys(pks, handler);
1029
+ }
1030
+ unsubscribeFromKey(pk, handler) {
1031
+ this.updateEmitter.unsubscribeFromKey(pk, handler);
1032
+ }
1033
+ unsubscribeFromKeys(pks, handler) {
1034
+ this.updateEmitter.unsubscribeFromKeys(pks, handler);
1035
+ }
1036
+ destroySubscriptions() {
1037
+ this.updateEmitter.destroy();
1038
+ }
1039
+ hasSubscriptions() {
1040
+ return this.updateEmitter.hasSubscriptions();
1041
+ }
1042
+ getHandlerCount(pk) {
1043
+ return this.updateEmitter.getHandlerCount(pk);
1044
+ }
1045
+ getMetrics() {
1046
+ return this.updateEmitter.getMetrics();
1047
+ }
1048
+ subscribeOnAnyUpdate(handler) {
1049
+ this.anyUpdateHandlers.add(handler);
1050
+ return () => {
1051
+ this.anyUpdateHandlers.delete(handler);
1052
+ };
1053
+ }
1054
+ upsertOneByPk(pk, entity) {
1055
+ this.upsertOneWithoutNotificationsByPk(pk, entity);
1056
+ this.updateEmitter.markUpdatedKey(pk);
1057
+ this.trackAnyUpdatePk(pk);
1058
+ }
1059
+ upsertOne(entity) {
1060
+ const pk = this.upsertOneWithoutNotifications(entity);
1061
+ this.updateEmitter.markUpdatedKey(pk);
1062
+ this.trackAnyUpdatePk(pk);
1063
+ }
1064
+ upsertMany(entities) {
1065
+ const pks = entities.map(
1066
+ (entity) => this.upsertOneWithoutNotifications(entity)
1186
1067
  );
1187
- this.updateEventEmitter = new OIMUpdateEventEmitter({
1188
- coalescer: this.coalescer,
1189
- queue
1190
- });
1068
+ this.updateEmitter.markUpdatedKeys(pks);
1069
+ this.trackAnyUpdatePks(pks);
1070
+ }
1071
+ removeOne(entity) {
1072
+ const pk = this.selectPk(entity);
1073
+ this.store.removeOneByPk(pk);
1074
+ this.updateEmitter.markUpdatedKey(pk);
1075
+ this.trackAnyUpdatePk(pk);
1076
+ }
1077
+ removeMany(entities) {
1078
+ const pks = entities.map(this.selectPk);
1079
+ this.store.removeManyByPks(pks);
1080
+ this.updateEmitter.markUpdatedKeys(pks);
1081
+ this.trackAnyUpdatePks(pks);
1082
+ }
1083
+ removeOneByPk(pk) {
1084
+ this.store.removeOneByPk(pk);
1085
+ this.updateEmitter.markUpdatedKey(pk);
1086
+ this.trackAnyUpdatePk(pk);
1087
+ }
1088
+ removeManyByPks(pks) {
1089
+ this.store.removeManyByPks(pks);
1090
+ this.updateEmitter.markUpdatedKeys(pks);
1091
+ this.trackAnyUpdatePks(pks);
1092
+ }
1093
+ clear() {
1094
+ this.store.clear();
1095
+ this.updateEmitter.markAllUpdated();
1096
+ this.trackAnyUpdateClear();
1097
+ }
1098
+ trackAnyUpdatePk(pk) {
1099
+ if (this.anyUpdateHandlers.size === 0) return;
1100
+ this.pendingAnyUpdatePks.add(pk);
1101
+ this.ensureAnyUpdateEnqueued();
1102
+ }
1103
+ trackAnyUpdatePks(pks) {
1104
+ if (this.anyUpdateHandlers.size === 0) return;
1105
+ for (let i = 0; i < pks.length; i++)
1106
+ this.pendingAnyUpdatePks.add(pks[i]);
1107
+ this.ensureAnyUpdateEnqueued();
1108
+ }
1109
+ trackAnyUpdateClear() {
1110
+ if (this.anyUpdateHandlers.size === 0) return;
1111
+ this.isAnyUpdateClearPending = true;
1112
+ this.pendingAnyUpdatePks.clear();
1113
+ this.ensureAnyUpdateEnqueued();
1114
+ }
1115
+ ensureAnyUpdateEnqueued() {
1116
+ if (this.isAnyUpdateScheduled) return;
1117
+ this.isAnyUpdateScheduled = true;
1118
+ this.dequeueAnyUpdate = this.queue.enqueue(this.runAnyUpdate);
1191
1119
  }
1192
1120
  destroy() {
1193
- this.updateEventEmitter.destroy();
1194
- this.coalescer.destroy();
1121
+ this.updateEmitter.destroy();
1122
+ if (this.dequeueAnyUpdate) {
1123
+ this.dequeueAnyUpdate();
1124
+ this.dequeueAnyUpdate = void 0;
1125
+ }
1126
+ this.anyUpdateHandlers.clear();
1127
+ this.pendingAnyUpdatePks.clear();
1195
1128
  super.destroy();
1196
1129
  }
1197
1130
  };
@@ -1291,50 +1224,142 @@ var OIMObject = class {
1291
1224
  }
1292
1225
  };
1293
1226
 
1294
- // src/core/OIMUpdateEventCoalescerObject.ts
1295
- var OIMUpdateEventCoalescerObject = class extends OIMUpdateEventCoalescer {
1296
- constructor(objectEmitter) {
1297
- super();
1298
- this.objectEmitter = objectEmitter;
1299
- /**
1300
- * When the object is updated, we keep track of the updated keys.
1301
- */
1302
- this.handleUpdate = (payload) => {
1303
- this.addUpdatedKeys(payload.keys);
1304
- };
1305
- this.objectEmitter.on(0 /* UPDATE */, this.handleUpdate);
1306
- }
1307
- destroy() {
1308
- this.objectEmitter.off(0 /* UPDATE */, this.handleUpdate);
1309
- this.emitter.offAll();
1310
- this.clearUpdatedKeys();
1311
- }
1312
- };
1313
-
1314
1227
  // src/core/OIMReactiveObject.ts
1315
1228
  var OIMReactiveObject = class extends OIMObject {
1316
1229
  constructor(queue, opts) {
1317
1230
  super(opts);
1318
- this.coalescer = new OIMUpdateEventCoalescerObject(this.emitter);
1319
- this.updateEventEmitter = new OIMUpdateEventEmitter({
1320
- coalescer: this.coalescer,
1321
- queue
1231
+ this.updateEmitter = new OIMUpdateEventEmitter(queue, "queue");
1232
+ this.emitter.on(0 /* UPDATE */, (payload) => {
1233
+ this.updateEmitter.markUpdatedKeys(payload.keys);
1322
1234
  });
1323
1235
  }
1236
+ subscribeOnKey(key, handler) {
1237
+ return this.updateEmitter.subscribeOnKey(key, handler);
1238
+ }
1239
+ subscribeOnKeys(keys, handler) {
1240
+ return this.updateEmitter.subscribeOnKeys(keys, handler);
1241
+ }
1242
+ unsubscribeFromKey(key, handler) {
1243
+ this.updateEmitter.unsubscribeFromKey(key, handler);
1244
+ }
1245
+ unsubscribeFromKeys(keys, handler) {
1246
+ this.updateEmitter.unsubscribeFromKeys(keys, handler);
1247
+ }
1248
+ hasSubscriptions() {
1249
+ return this.updateEmitter.hasSubscriptions();
1250
+ }
1251
+ getHandlerCount(key) {
1252
+ return this.updateEmitter.getHandlerCount(key);
1253
+ }
1254
+ getMetrics() {
1255
+ return this.updateEmitter.getMetrics();
1256
+ }
1324
1257
  destroy() {
1325
- this.updateEventEmitter.destroy();
1326
- this.coalescer.destroy();
1258
+ this.updateEmitter.destroy();
1327
1259
  super.destroy();
1328
1260
  }
1329
1261
  };
1330
1262
 
1263
+ // src/core/OIMIndexManualSetBased.ts
1264
+ var OIMIndexManualSetBased = class extends OIMIndexSetBased {
1265
+ constructor(options = {}) {
1266
+ super(options);
1267
+ }
1268
+ /**
1269
+ * Set primary keys for a specific index key, replacing any existing values.
1270
+ * Uses optional comparator to skip updates if PKs haven't actually changed.
1271
+ */
1272
+ setPks(key, pks) {
1273
+ const hasChanges = this.setPksWithComparison(key, new Set(pks));
1274
+ if (hasChanges) {
1275
+ this.emitUpdateOne(key);
1276
+ }
1277
+ }
1278
+ /**
1279
+ * Add primary keys to a specific index key
1280
+ */
1281
+ addPks(key, pks) {
1282
+ if (pks.length === 0) return;
1283
+ let pksSet = this.store.getOneByKey(key);
1284
+ if (!pksSet) {
1285
+ pksSet = /* @__PURE__ */ new Set();
1286
+ this.store.setOneByKey(key, pksSet);
1287
+ }
1288
+ let hasChanges = false;
1289
+ for (const pk of pks) {
1290
+ if (!pksSet.has(pk)) {
1291
+ pksSet.add(pk);
1292
+ hasChanges = true;
1293
+ }
1294
+ }
1295
+ if (hasChanges) {
1296
+ this.store.setOneByKey(key, pksSet);
1297
+ this.emitUpdateOne(key);
1298
+ }
1299
+ }
1300
+ /**
1301
+ * Remove primary keys from a specific index key
1302
+ */
1303
+ removePks(key, pks) {
1304
+ if (pks.length === 0) return;
1305
+ const pksSet = this.store.getOneByKey(key);
1306
+ if (!pksSet) return;
1307
+ let hasChanges = false;
1308
+ for (const pk of pks) {
1309
+ if (pksSet.delete(pk)) {
1310
+ hasChanges = true;
1311
+ }
1312
+ }
1313
+ if (pksSet.size === 0) {
1314
+ this.store.removeOneByKey(key);
1315
+ hasChanges = true;
1316
+ } else if (hasChanges) {
1317
+ this.store.setOneByKey(key, pksSet);
1318
+ }
1319
+ if (hasChanges) {
1320
+ this.emitUpdateOne(key);
1321
+ }
1322
+ }
1323
+ /**
1324
+ * Clear all primary keys for a specific index key, or all keys if no key specified
1325
+ */
1326
+ clear(key) {
1327
+ if (key === void 0) {
1328
+ const allKeys = this.store.getAllKeys();
1329
+ if (allKeys.length > 0) {
1330
+ this.store.clear();
1331
+ this.emitUpdate(allKeys);
1332
+ }
1333
+ } else {
1334
+ if (this.store.getOneByKey(key) !== void 0) {
1335
+ this.store.removeOneByKey(key);
1336
+ this.emitUpdateOne(key);
1337
+ }
1338
+ }
1339
+ }
1340
+ };
1341
+
1331
1342
  // src/core/OIMReactiveIndexManualSetBased.ts
1343
+ var OIMIndexManualSetBasedReactive = class extends OIMIndexManualSetBased {
1344
+ constructor(updateEmitter, opts) {
1345
+ super(opts);
1346
+ this.updateEmitter = updateEmitter;
1347
+ }
1348
+ emitUpdate(keys) {
1349
+ this.updateEmitter.markUpdatedKeys(keys);
1350
+ }
1351
+ emitUpdateOne(key) {
1352
+ this.updateEmitter.markUpdatedKey(key);
1353
+ }
1354
+ };
1332
1355
  var OIMReactiveIndexManualSetBased = class extends OIMReactiveIndexSetBased {
1333
1356
  constructor(queue, opts) {
1334
- super(queue, opts);
1335
- }
1336
- createDefaultIndex() {
1337
- return new OIMIndexManualSetBased();
1357
+ super(queue, (updateEmitter) => {
1358
+ return new OIMIndexManualSetBasedReactive(
1359
+ updateEmitter,
1360
+ opts?.indexOptions
1361
+ );
1362
+ });
1338
1363
  }
1339
1364
  setPks(key, pks) {
1340
1365
  this.index.setPks(key, pks);
@@ -1362,7 +1387,7 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
1362
1387
  setPks(key, pks) {
1363
1388
  const hasChanges = this.setPksWithComparison(key, pks);
1364
1389
  if (hasChanges) {
1365
- this.emitUpdate([key]);
1390
+ this.emitUpdateOne(key);
1366
1391
  }
1367
1392
  }
1368
1393
  /**
@@ -1385,7 +1410,7 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
1385
1410
  }
1386
1411
  if (hasChanges) {
1387
1412
  this.store.setOneByKey(key, Array.from(pksSet));
1388
- this.emitUpdate([key]);
1413
+ this.emitUpdateOne(key);
1389
1414
  }
1390
1415
  }
1391
1416
  /**
@@ -1404,7 +1429,7 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
1404
1429
  } else {
1405
1430
  this.store.setOneByKey(key, filtered);
1406
1431
  }
1407
- this.emitUpdate([key]);
1432
+ this.emitUpdateOne(key);
1408
1433
  }
1409
1434
  }
1410
1435
  /**
@@ -1420,19 +1445,33 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
1420
1445
  } else {
1421
1446
  if (this.store.getOneByKey(key) !== void 0) {
1422
1447
  this.store.removeOneByKey(key);
1423
- this.emitUpdate([key]);
1448
+ this.emitUpdateOne(key);
1424
1449
  }
1425
1450
  }
1426
1451
  }
1427
1452
  };
1428
1453
 
1429
1454
  // src/core/OIMReactiveIndexManualArrayBased.ts
1455
+ var OIMIndexManualArrayBasedReactive = class extends OIMIndexManualArrayBased {
1456
+ constructor(updateEmitter, opts) {
1457
+ super(opts);
1458
+ this.updateEmitter = updateEmitter;
1459
+ }
1460
+ emitUpdate(keys) {
1461
+ this.updateEmitter.markUpdatedKeys(keys);
1462
+ }
1463
+ emitUpdateOne(key) {
1464
+ this.updateEmitter.markUpdatedKey(key);
1465
+ }
1466
+ };
1430
1467
  var OIMReactiveIndexManualArrayBased = class extends OIMReactiveIndexArrayBased {
1431
1468
  constructor(queue, opts) {
1432
- super(queue, opts);
1433
- }
1434
- createDefaultIndex() {
1435
- return new OIMIndexManualArrayBased();
1469
+ super(queue, (updateEmitter) => {
1470
+ return new OIMIndexManualArrayBasedReactive(
1471
+ updateEmitter,
1472
+ opts?.indexOptions
1473
+ );
1474
+ });
1436
1475
  }
1437
1476
  setPks(key, pks) {
1438
1477
  this.index.setPks(key, pks);
@@ -1521,20 +1560,12 @@ var OIMComparatorFactory = class {
1521
1560
  }
1522
1561
  };
1523
1562
 
1524
- // src/enum/EOIMEventQueueEventType.ts
1525
- var EOIMEventQueueEventType = /* @__PURE__ */ ((EOIMEventQueueEventType2) => {
1526
- EOIMEventQueueEventType2[EOIMEventQueueEventType2["AFTER_FLUSH"] = 0] = "AFTER_FLUSH";
1527
- EOIMEventQueueEventType2[EOIMEventQueueEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
1528
- return EOIMEventQueueEventType2;
1529
- })(EOIMEventQueueEventType || {});
1530
-
1531
1563
  // src/core/OIMEventQueue.ts
1532
1564
  var OIMEventQueue = class {
1533
1565
  constructor(options = {}) {
1534
1566
  this.emitter = new OIMEventEmitter();
1535
- this.queue = [];
1567
+ this.tasks = /* @__PURE__ */ new Set();
1536
1568
  this.isFlushing = false;
1537
- this.pendingSchedule = false;
1538
1569
  this.scheduler = options.scheduler;
1539
1570
  if (this.scheduler) {
1540
1571
  this.flushBound = () => this.flush();
@@ -1545,62 +1576,76 @@ var OIMEventQueue = class {
1545
1576
  }
1546
1577
  }
1547
1578
  /**
1548
- * Add a function to the queue. If scheduler is configured and autoSchedule is enabled,
1549
- * automatically schedules a flush operation.
1579
+ * Enqueue a one-shot task to be executed on the next flush.
1580
+ * Returns a dequeue function that can be called to cancel the task before it runs.
1550
1581
  */
1551
1582
  enqueue(fn) {
1552
- this.queue.push(fn);
1553
- if (!this.scheduler) return;
1554
- if (this.isFlushing) {
1555
- this.pendingSchedule = true;
1556
- return;
1557
- }
1558
- if (this.queue.length === 1) {
1559
- this.scheduler.schedule();
1560
- }
1583
+ let isActive = true;
1584
+ const task = () => {
1585
+ if (!isActive) return;
1586
+ fn();
1587
+ };
1588
+ this.tasks.add(task);
1589
+ this.ensureScheduled();
1590
+ return () => {
1591
+ if (!isActive) return;
1592
+ isActive = false;
1593
+ this.tasks.delete(task);
1594
+ };
1595
+ }
1596
+ get isInFlush() {
1597
+ return this.isFlushing;
1598
+ }
1599
+ /**
1600
+ * Dequeue (cancel) a previously enqueued task by its dequeue function.
1601
+ */
1602
+ dequeue(dequeueFn) {
1603
+ dequeueFn();
1561
1604
  }
1562
1605
  /**
1563
- * Execute all queued functions and clear the queue.
1564
- * This method is safe to call multiple times and handles reentrancy.
1606
+ * Execute all pending tasks.
1607
+ *
1608
+ * This flush drains until the queue is empty. If tasks enqueue more tasks, they will be
1609
+ * executed within the same flush.
1565
1610
  */
1566
1611
  flush() {
1567
- if (this.queue.length === 0) return;
1568
1612
  this.scheduler?.cancel();
1569
1613
  this.isFlushing = true;
1570
1614
  this.emitter.emit(1 /* BEFORE_FLUSH */, void 0);
1571
- const currentQueue = this.queue.slice();
1572
- this.queue.length = 0;
1573
- for (let i = 0; i < currentQueue.length; i++) {
1574
- currentQueue[i]();
1615
+ const snapshot = Array.from(this.tasks);
1616
+ this.tasks.clear();
1617
+ for (let i = 0; i < snapshot.length; i++) {
1618
+ snapshot[i]();
1575
1619
  }
1576
- this.emitter.emit(0 /* AFTER_FLUSH */, void 0);
1577
1620
  this.isFlushing = false;
1578
- if (this.scheduler && this.pendingSchedule && this.queue.length > 0) {
1579
- this.pendingSchedule = false;
1580
- this.scheduler.schedule();
1581
- } else {
1582
- this.pendingSchedule = false;
1583
- }
1621
+ this.emitter.emit(0 /* AFTER_FLUSH */, void 0);
1584
1622
  }
1585
1623
  /**
1586
1624
  * Get the current number of queued functions.
1587
1625
  */
1588
1626
  get length() {
1589
- return this.queue.length;
1627
+ return this.tasks.size;
1590
1628
  }
1591
1629
  /**
1592
1630
  * Check if the queue is empty.
1593
1631
  */
1594
1632
  get isEmpty() {
1595
- return this.queue.length === 0;
1633
+ return this.tasks.size === 0;
1596
1634
  }
1597
1635
  /**
1598
1636
  * Clear the queue without executing functions and cancel any scheduled flush.
1599
1637
  */
1600
1638
  clear() {
1601
- this.queue.length = 0;
1639
+ this.tasks.clear();
1602
1640
  this.scheduler?.cancel();
1603
1641
  }
1642
+ ensureScheduled() {
1643
+ if (!this.scheduler) return;
1644
+ if (this.isFlushing) return;
1645
+ if (this.tasks.size === 1) {
1646
+ this.scheduler.schedule();
1647
+ }
1648
+ }
1604
1649
  /**
1605
1650
  * Clean up scheduler subscription when queue is no longer needed.
1606
1651
  */
@@ -1714,56 +1759,75 @@ var OIMMap2Keys = class {
1714
1759
  }
1715
1760
  };
1716
1761
 
1717
- // src/modules/wrapper/OIMUpdateEventCoalescerManual.ts
1718
- var OIMUpdateEventCoalescerManual = class extends OIMUpdateEventCoalescer {
1719
- markUpdatedKeys(keys) {
1720
- if (keys.length === 0) return;
1721
- this.addUpdatedKeys(keys);
1722
- }
1723
- destroy() {
1724
- this.emitter.offAll();
1725
- this.updatedKeysIndex.destroy();
1726
- this.flushingUpdatedKeysIndex.destroy();
1727
- }
1728
- };
1729
-
1730
1762
  // src/modules/wrapper/collection/OIMCollectionChangedFieldsWrapper.ts
1731
1763
  var OIMCollectionChangedFieldsWrapper = class {
1732
1764
  constructor(queue, collection, opts) {
1765
+ this.isInWrite = false;
1766
+ this.lastEntitySnapshotByPk = /* @__PURE__ */ new Map();
1733
1767
  this.changedFieldsByPk = /* @__PURE__ */ new Map();
1734
1768
  this.changedPksByField = /* @__PURE__ */ new Map();
1769
+ this.onAfterFlush = () => {
1770
+ this.clear();
1771
+ };
1772
+ this.onCollectionUpdate = (payload) => {
1773
+ if (this.isInWrite) return;
1774
+ if (payload.pks.length === 0) return;
1775
+ for (let i = 0; i < payload.pks.length; i++) {
1776
+ const pk = payload.pks[i];
1777
+ this.trackExternalMutationByPk(pk);
1778
+ }
1779
+ };
1735
1780
  this.collection = collection;
1736
1781
  this.selectPk = opts?.selectPk ?? (() => {
1737
1782
  throw new Error(
1738
1783
  `[OIMCollectionChangedFieldsWrapper]: selectPk was not provided. Use upsertOneByPk(...) or pass opts.selectPk.`
1739
1784
  );
1740
1785
  });
1741
- this.changedPksCoalescer = new OIMUpdateEventCoalescerManual();
1742
- this.changedPksEventEmitter = new OIMUpdateEventEmitter({
1743
- coalescer: this.changedPksCoalescer,
1744
- queue
1745
- });
1746
- this.changedFieldsCoalescer = new OIMUpdateEventCoalescerManual();
1747
- this.changedFieldsEventEmitter = new OIMUpdateEventEmitter({
1748
- coalescer: this.changedFieldsCoalescer,
1749
- queue
1750
- });
1786
+ this.changedPksEventEmitter = new OIMUpdateEventEmitter(
1787
+ queue,
1788
+ "after_flush"
1789
+ );
1790
+ this.changedFieldsEventEmitter = new OIMUpdateEventEmitter(queue, "after_flush");
1791
+ this.unsubscribeAfterFlush = queue.emitter.on(
1792
+ 0 /* AFTER_FLUSH */,
1793
+ this.onAfterFlush
1794
+ );
1795
+ const maybeReactive = collection;
1796
+ this.unsubscribeFromCollectionEmitter = maybeReactive.subscribeOnAnyUpdate ? maybeReactive.subscribeOnAnyUpdate((pks) => {
1797
+ this.onCollectionUpdate({
1798
+ pks
1799
+ });
1800
+ }) : collection.emitter.on(
1801
+ 0 /* UPDATE */,
1802
+ this.onCollectionUpdate
1803
+ );
1751
1804
  }
1752
1805
  upsertOneByPk(pk, patch) {
1753
- this.trackPatch(pk, patch);
1754
- this.collection.upsertOneByPk(pk, patch);
1806
+ this.withWrite(() => {
1807
+ this.trackPatch(pk, patch);
1808
+ this.collection.upsertOneByPk(pk, patch);
1809
+ });
1810
+ this.updateEntitySnapshot(pk);
1755
1811
  }
1756
1812
  upsertOne(entity) {
1757
- const pk = this.selectPk(entity);
1758
- this.trackPatch(pk, entity);
1759
- this.collection.upsertOne(entity);
1760
- }
1761
- upsertMany(entities) {
1762
- for (const entity of entities) {
1813
+ this.withWrite(() => {
1763
1814
  const pk = this.selectPk(entity);
1764
1815
  this.trackPatch(pk, entity);
1765
- }
1766
- this.collection.upsertMany(entities);
1816
+ this.collection.upsertOne(entity);
1817
+ });
1818
+ this.updateEntitySnapshot(this.selectPk(entity));
1819
+ }
1820
+ upsertMany(entities) {
1821
+ const pks = [];
1822
+ this.withWrite(() => {
1823
+ for (const entity of entities) {
1824
+ const pk = this.selectPk(entity);
1825
+ pks.push(pk);
1826
+ this.trackPatch(pk, entity);
1827
+ }
1828
+ this.collection.upsertMany(entities);
1829
+ });
1830
+ for (let i = 0; i < pks.length; i++) this.updateEntitySnapshot(pks[i]);
1767
1831
  }
1768
1832
  getChangedFieldsByPk(pk) {
1769
1833
  return this.changedFieldsByPk.get(pk) ?? /* @__PURE__ */ new Set();
@@ -1771,8 +1835,7 @@ var OIMCollectionChangedFieldsWrapper = class {
1771
1835
  consumeChangedFieldsByPk(pk) {
1772
1836
  const existing = this.changedFieldsByPk.get(pk);
1773
1837
  if (!existing) return /* @__PURE__ */ new Set();
1774
- this.changedFieldsByPk.delete(pk);
1775
- return existing;
1838
+ return new Set(existing);
1776
1839
  }
1777
1840
  getChangedPksByField(field) {
1778
1841
  return this.changedPksByField.get(field) ?? /* @__PURE__ */ new Set();
@@ -1780,8 +1843,7 @@ var OIMCollectionChangedFieldsWrapper = class {
1780
1843
  consumeChangedPksByField(field) {
1781
1844
  const existing = this.changedPksByField.get(field);
1782
1845
  if (!existing) return /* @__PURE__ */ new Set();
1783
- this.changedPksByField.delete(field);
1784
- return existing;
1846
+ return new Set(existing);
1785
1847
  }
1786
1848
  clear() {
1787
1849
  this.changedFieldsByPk.clear();
@@ -1790,18 +1852,60 @@ var OIMCollectionChangedFieldsWrapper = class {
1790
1852
  destroy() {
1791
1853
  this.changedPksEventEmitter.destroy();
1792
1854
  this.changedFieldsEventEmitter.destroy();
1793
- this.changedPksCoalescer.destroy();
1794
- this.changedFieldsCoalescer.destroy();
1855
+ this.unsubscribeAfterFlush();
1856
+ this.unsubscribeFromCollectionEmitter();
1795
1857
  this.clear();
1796
1858
  }
1797
- trackPatch(pk, patch) {
1798
- if (pk === void 0 || pk === null || pk === "") {
1799
- throw new Error(
1800
- `[OIMCollectionChangedFieldsWrapper]: PK is required to track changes.`
1801
- );
1859
+ withWrite(fn) {
1860
+ if (this.isInWrite) {
1861
+ fn();
1862
+ return;
1802
1863
  }
1803
- const keys = Object.keys(patch);
1804
- if (keys.length === 0) return;
1864
+ this.isInWrite = true;
1865
+ try {
1866
+ fn();
1867
+ } finally {
1868
+ this.isInWrite = false;
1869
+ }
1870
+ }
1871
+ trackExternalMutationByPk(pk) {
1872
+ const prev = this.lastEntitySnapshotByPk.get(pk);
1873
+ const next = this.collection.getOneByPk(pk);
1874
+ const changedKeys = this.diffChangedKeys(prev, next);
1875
+ this.updateEntitySnapshot(pk);
1876
+ if (changedKeys.length > 0) {
1877
+ this.trackChangedKeys(pk, changedKeys);
1878
+ } else {
1879
+ this.changedPksEventEmitter.markUpdatedKeys([pk]);
1880
+ }
1881
+ }
1882
+ diffChangedKeys(prev, next) {
1883
+ if (!prev && !next) return [];
1884
+ if (!prev && next) return Object.keys(next);
1885
+ if (prev && !next) return Object.keys(prev);
1886
+ const prevObj = prev;
1887
+ const nextObj = next;
1888
+ const keys = /* @__PURE__ */ new Set();
1889
+ Object.keys(prevObj).forEach((k) => keys.add(k));
1890
+ Object.keys(nextObj).forEach((k) => keys.add(k));
1891
+ const changed = [];
1892
+ keys.forEach((k) => {
1893
+ if (prevObj[k] !== nextObj[k])
1894
+ changed.push(k);
1895
+ });
1896
+ return changed;
1897
+ }
1898
+ updateEntitySnapshot(pk) {
1899
+ const entity = this.collection.getOneByPk(pk);
1900
+ if (!entity) {
1901
+ this.lastEntitySnapshotByPk.delete(pk);
1902
+ return;
1903
+ }
1904
+ this.lastEntitySnapshotByPk.set(pk, {
1905
+ ...entity
1906
+ });
1907
+ }
1908
+ trackChangedKeys(pk, keys) {
1805
1909
  let pkFields = this.changedFieldsByPk.get(pk);
1806
1910
  if (!pkFields) {
1807
1911
  pkFields = /* @__PURE__ */ new Set();
@@ -1816,8 +1920,18 @@ var OIMCollectionChangedFieldsWrapper = class {
1816
1920
  }
1817
1921
  fieldPks.add(pk);
1818
1922
  }
1819
- this.changedPksCoalescer.markUpdatedKeys([pk]);
1820
- this.changedFieldsCoalescer.markUpdatedKeys(keys);
1923
+ this.changedPksEventEmitter.markUpdatedKeys([pk]);
1924
+ this.changedFieldsEventEmitter.markUpdatedKeys(keys);
1925
+ }
1926
+ trackPatch(pk, patch) {
1927
+ if (pk === void 0 || pk === null || pk === "") {
1928
+ throw new Error(
1929
+ `[OIMCollectionChangedFieldsWrapper]: PK is required to track changes.`
1930
+ );
1931
+ }
1932
+ const keys = Object.keys(patch);
1933
+ if (keys.length === 0) return;
1934
+ this.trackChangedKeys(pk, keys);
1821
1935
  }
1822
1936
  };
1823
1937
 
@@ -1897,13 +2011,33 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1897
2011
  // src/modules/wrapper/index/OIMOrderedListIndexCommandStreamWrapper.ts
1898
2012
  var OIMOrderedListIndexCommandStreamWrapper = class {
1899
2013
  constructor(queue, index) {
2014
+ this.isInWrite = false;
1900
2015
  this.commandsByKey = /* @__PURE__ */ new Map();
1901
- this.coalescer = new OIMUpdateEventCoalescerManual();
1902
- this.commandsEventEmitter = new OIMUpdateEventEmitter({
1903
- coalescer: this.coalescer,
1904
- queue
1905
- });
2016
+ this.onAfterFlush = () => {
2017
+ this.commandsByKey.clear();
2018
+ };
2019
+ this.onIndexUpdate = (payload) => {
2020
+ if (this.isInWrite) return;
2021
+ if (payload.keys.length === 0) return;
2022
+ for (let i = 0; i < payload.keys.length; i++) {
2023
+ const key = payload.keys[i];
2024
+ const list = this.index.getPksByKey(key);
2025
+ this.appendCommand(key, { type: "set", keys: Array.from(list) });
2026
+ }
2027
+ };
2028
+ this.commandsEventEmitter = new OIMUpdateEventEmitter(
2029
+ queue,
2030
+ "after_flush"
2031
+ );
1906
2032
  this.index = index ?? new OIMIndexManualOrderedArrayBased();
2033
+ this.unsubscribeAfterFlush = queue.emitter.on(
2034
+ 0 /* AFTER_FLUSH */,
2035
+ this.onAfterFlush
2036
+ );
2037
+ this.unsubscribeFromIndexEmitter = this.index.emitter.on(
2038
+ 0 /* UPDATE */,
2039
+ this.onIndexUpdate
2040
+ );
1907
2041
  }
1908
2042
  getPksByKey(key) {
1909
2043
  return this.index.getPksByKey(key);
@@ -1915,27 +2049,35 @@ var OIMOrderedListIndexCommandStreamWrapper = class {
1915
2049
  return this.index.getKeys();
1916
2050
  }
1917
2051
  push(key, itemKey) {
1918
- const index = this.index.push(key, itemKey);
1919
- this.appendCommand(key, { type: "add", key: itemKey, index });
2052
+ this.withWrite(() => {
2053
+ const index = this.index.push(key, itemKey);
2054
+ this.appendCommand(key, { type: "add", key: itemKey, index });
2055
+ });
1920
2056
  }
1921
2057
  removeAt(key, index) {
1922
- const itemKey = this.index.removeAt(key, index);
1923
- if (itemKey === void 0) return;
1924
- this.appendCommand(key, { type: "remove", key: itemKey, index });
2058
+ this.withWrite(() => {
2059
+ const itemKey = this.index.removeAt(key, index);
2060
+ if (itemKey === void 0) return;
2061
+ this.appendCommand(key, { type: "remove", key: itemKey, index });
2062
+ });
1925
2063
  }
1926
2064
  move(key, fromIndex, toIndex) {
1927
- const itemKey = this.index.move(key, fromIndex, toIndex);
1928
- if (itemKey === void 0) return;
1929
- this.appendCommand(key, {
1930
- type: "move",
1931
- key: itemKey,
1932
- fromIndex,
1933
- toIndex
2065
+ this.withWrite(() => {
2066
+ const itemKey = this.index.move(key, fromIndex, toIndex);
2067
+ if (itemKey === void 0) return;
2068
+ this.appendCommand(key, {
2069
+ type: "move",
2070
+ key: itemKey,
2071
+ fromIndex,
2072
+ toIndex
2073
+ });
1934
2074
  });
1935
2075
  }
1936
- reset(key, keys) {
1937
- this.index.reset(key, keys);
1938
- this.appendCommand(key, { type: "reset", keys });
2076
+ set(key, keys) {
2077
+ this.withWrite(() => {
2078
+ this.index.reset(key, keys);
2079
+ this.appendCommand(key, { type: "set", keys });
2080
+ });
1939
2081
  }
1940
2082
  /**
1941
2083
  * Read-only peek at buffered commands without clearing them.
@@ -1950,8 +2092,7 @@ var OIMOrderedListIndexCommandStreamWrapper = class {
1950
2092
  consumeCommands(key) {
1951
2093
  const cmds = this.commandsByKey.get(key);
1952
2094
  if (!cmds || cmds.length === 0) return [];
1953
- this.commandsByKey.delete(key);
1954
- return cmds;
2095
+ return cmds.slice();
1955
2096
  }
1956
2097
  clear(key) {
1957
2098
  if (key === void 0) {
@@ -1964,17 +2105,38 @@ var OIMOrderedListIndexCommandStreamWrapper = class {
1964
2105
  }
1965
2106
  destroy() {
1966
2107
  this.commandsEventEmitter.destroy();
1967
- this.coalescer.destroy();
2108
+ this.unsubscribeAfterFlush();
2109
+ this.unsubscribeFromIndexEmitter();
1968
2110
  this.clear();
1969
2111
  }
2112
+ withWrite(fn) {
2113
+ if (this.isInWrite) {
2114
+ fn();
2115
+ return;
2116
+ }
2117
+ this.isInWrite = true;
2118
+ try {
2119
+ fn();
2120
+ } finally {
2121
+ this.isInWrite = false;
2122
+ }
2123
+ }
1970
2124
  appendCommand(key, cmd) {
1971
2125
  let cmds = this.commandsByKey.get(key);
1972
2126
  if (!cmds) {
1973
2127
  cmds = [];
1974
2128
  this.commandsByKey.set(key, cmds);
1975
2129
  }
1976
- cmds.push(cmd);
1977
- this.coalescer.markUpdatedKeys([key]);
2130
+ if (cmd.type === "set") {
2131
+ cmds.length = 0;
2132
+ cmds.push(cmd);
2133
+ } else if (cmds.length > 0 && cmds[0].type === "set") {
2134
+ const setCmd = cmds[0];
2135
+ setCmd.keys = Array.from(this.index.getPksByKey(key));
2136
+ } else {
2137
+ cmds.push(cmd);
2138
+ }
2139
+ this.commandsEventEmitter.markUpdatedKeys([key]);
1978
2140
  }
1979
2141
  };
1980
2142
 
@@ -2147,159 +2309,160 @@ var OIMEventQueueSchedulerFactory = class {
2147
2309
  }
2148
2310
  };
2149
2311
 
2312
+ // src/const/OIMDBSettings.ts
2313
+ var OIMDBSettings = class {
2314
+ };
2315
+ /**
2316
+ * Fixed key used in the manual index to store updated keys set in coalescers.
2317
+ * This constant is exported so users can create their own indexes and pass them to coalescers.
2318
+ */
2319
+ OIMDBSettings.UPDATED_KEYS_INDEX_KEY = "__updatedKeys__";
2320
+ OIMDBSettings.FLUSHING_KEYS_INDEX_KEY = "__flushingKeys__";
2321
+
2322
+ // src/modules/effect/core/OIMEffectDependencyComputed.ts
2323
+ var OIMEffectDependencyComputed = class {
2324
+ constructor(computed) {
2325
+ this.computed = computed;
2326
+ }
2327
+ subscribe(onUpdate) {
2328
+ return this.computed.emitter.on("update", onUpdate);
2329
+ }
2330
+ };
2331
+
2150
2332
  // src/modules/effect/core/OIMEffect.ts
2151
2333
  var OIMEffect = class {
2152
- constructor(queue, opts) {
2153
- this.unsubscribers = [];
2154
- this.isDestroyed = false;
2155
- this.isInFlush = false;
2156
- this.hasRunInThisFlush = false;
2334
+ constructor(runtime, opts) {
2335
+ this.sourceDeps = [];
2336
+ this.computedDeps = [];
2337
+ this.sourceUnsubscribers = [];
2338
+ this.computedUnsubscribers = [];
2339
+ this.isResubscribeEnqueued = false;
2340
+ this.isTemporarilyUnsubscribedForFlush = false;
2157
2341
  this.isScheduled = false;
2158
- this.handleQueueBeforeFlush = () => {
2159
- this.isInFlush = true;
2160
- this.hasRunInThisFlush = false;
2161
- };
2162
- this.handleQueueAfterFlush = () => {
2163
- this.isInFlush = false;
2164
- this.hasRunInThisFlush = false;
2165
- };
2166
2342
  this.runTask = () => {
2167
2343
  this.isScheduled = false;
2168
- if (this.isDestroyed) return;
2344
+ this.dequeueScheduled = void 0;
2169
2345
  this.runOnce();
2170
2346
  };
2171
- this.invalidate = () => {
2172
- if (this.isDestroyed) return;
2173
- this.onInvalidate?.();
2174
- if (this.isInFlush) {
2175
- if (this.hasRunInThisFlush) return;
2176
- this.hasRunInThisFlush = true;
2177
- this.runOnce();
2178
- return;
2347
+ this.onSourceInvalidate = () => {
2348
+ this.onUpdate?.();
2349
+ if (!this.isTemporarilyUnsubscribedForFlush) {
2350
+ this.isTemporarilyUnsubscribedForFlush = true;
2351
+ this.unsubscribeFromSourceDeps();
2352
+ this.ensureResubscribeAfterFlush();
2179
2353
  }
2180
2354
  if (this.isScheduled) return;
2181
2355
  this.isScheduled = true;
2182
- this.queue.enqueue(this.runTask);
2356
+ this.dequeueScheduled = this.runtime.schedule(this.runTask);
2183
2357
  };
2184
- this.queue = queue;
2185
- this.phase = opts.phase;
2186
- this.onInvalidate = opts.onInvalidate;
2358
+ this.onComputedInvalidate = () => {
2359
+ this.onUpdate?.();
2360
+ if (this.isScheduled) return;
2361
+ this.isScheduled = true;
2362
+ this.dequeueScheduled = this.runtime.schedule(this.runTask);
2363
+ };
2364
+ this.onAfterFlush = () => {
2365
+ if (!this.isResubscribeEnqueued) return;
2366
+ this.isResubscribeEnqueued = false;
2367
+ this.dequeueResubscribe = void 0;
2368
+ this.isTemporarilyUnsubscribedForFlush = false;
2369
+ if (this.sourceUnsubscribers.length === 0) {
2370
+ for (const dep of this.sourceDeps) {
2371
+ this.sourceUnsubscribers.push(
2372
+ dep.subscribe(this.onSourceInvalidate)
2373
+ );
2374
+ }
2375
+ }
2376
+ };
2377
+ this.runtime = runtime;
2378
+ this.onUpdate = opts.onUpdate;
2187
2379
  this.run = opts.run;
2188
2380
  this.deps = opts.deps ?? [];
2189
- this.queue.emitter.on(
2190
- 1 /* BEFORE_FLUSH */,
2191
- this.handleQueueBeforeFlush
2192
- );
2193
- this.queue.emitter.on(
2194
- 0 /* AFTER_FLUSH */,
2195
- this.handleQueueAfterFlush
2196
- );
2197
2381
  for (const dep of this.deps) {
2198
- this.unsubscribers.push(dep.subscribe(this.phase, this.invalidate));
2382
+ if (dep instanceof OIMEffectDependencyComputed) {
2383
+ this.computedDeps.push(dep);
2384
+ } else {
2385
+ this.sourceDeps.push(dep);
2386
+ }
2199
2387
  }
2388
+ this.subscribeToDeps();
2200
2389
  }
2201
2390
  destroy() {
2202
- if (this.isDestroyed) return;
2203
- this.isDestroyed = true;
2204
- for (const unsubscribe of this.unsubscribers) unsubscribe();
2205
- this.unsubscribers = [];
2206
- this.queue.emitter.off(
2207
- 1 /* BEFORE_FLUSH */,
2208
- this.handleQueueBeforeFlush
2209
- );
2210
- this.queue.emitter.off(
2211
- 0 /* AFTER_FLUSH */,
2212
- this.handleQueueAfterFlush
2213
- );
2391
+ if (this.dequeueScheduled) {
2392
+ this.dequeueScheduled();
2393
+ this.dequeueScheduled = void 0;
2394
+ }
2395
+ this.unsubscribeFromAllDeps();
2396
+ if (this.dequeueResubscribe) {
2397
+ this.dequeueResubscribe();
2398
+ this.dequeueResubscribe = void 0;
2399
+ }
2400
+ this.isResubscribeEnqueued = false;
2401
+ this.isTemporarilyUnsubscribedForFlush = false;
2214
2402
  }
2215
2403
  runOnce() {
2216
- if (this.isDestroyed) return;
2217
2404
  this.run();
2218
2405
  }
2406
+ subscribeToDeps() {
2407
+ if (this.sourceUnsubscribers.length > 0 || this.computedUnsubscribers.length > 0)
2408
+ return;
2409
+ for (const dep of this.sourceDeps) {
2410
+ this.sourceUnsubscribers.push(
2411
+ dep.subscribe(this.onSourceInvalidate)
2412
+ );
2413
+ }
2414
+ for (const dep of this.computedDeps) {
2415
+ this.computedUnsubscribers.push(
2416
+ dep.subscribe(this.onComputedInvalidate)
2417
+ );
2418
+ }
2419
+ }
2420
+ unsubscribeFromSourceDeps() {
2421
+ if (this.sourceUnsubscribers.length === 0) return;
2422
+ for (const unsubscribe of this.sourceUnsubscribers) unsubscribe();
2423
+ this.sourceUnsubscribers = [];
2424
+ }
2425
+ unsubscribeFromAllDeps() {
2426
+ this.unsubscribeFromSourceDeps();
2427
+ if (this.computedUnsubscribers.length === 0) return;
2428
+ for (const unsubscribe of this.computedUnsubscribers) unsubscribe();
2429
+ this.computedUnsubscribers = [];
2430
+ }
2431
+ ensureResubscribeAfterFlush() {
2432
+ if (this.isResubscribeEnqueued) return;
2433
+ this.isResubscribeEnqueued = true;
2434
+ this.dequeueResubscribe = this.runtime.scheduleAfterFlush(
2435
+ this.onAfterFlush
2436
+ );
2437
+ }
2219
2438
  };
2220
2439
 
2221
- // src/modules/effect/enum/EOIMEffectPhase.ts
2222
- var EOIMEffectPhase = /* @__PURE__ */ ((EOIMEffectPhase2) => {
2223
- EOIMEffectPhase2["PRE"] = "pre";
2224
- EOIMEffectPhase2["HANDLERS"] = "handlers";
2225
- return EOIMEffectPhase2;
2226
- })(EOIMEffectPhase || {});
2227
-
2228
2440
  // src/modules/computed/enum/EOIMComputedEventType.ts
2229
2441
  var EOIMComputedEventType = /* @__PURE__ */ ((EOIMComputedEventType2) => {
2230
2442
  EOIMComputedEventType2["UPDATE"] = "update";
2231
2443
  return EOIMComputedEventType2;
2232
2444
  })(EOIMComputedEventType || {});
2233
2445
 
2234
- // src/modules/computed/core/OIMUpdateEventCoalescerComputed.ts
2235
- var OIMUpdateEventCoalescerComputed = class extends OIMUpdateEventCoalescer {
2236
- constructor(computedEmitter) {
2237
- super();
2238
- this.computedEmitter = computedEmitter;
2239
- this.handleUpdate = (payload) => {
2240
- this.addUpdatedKeys(payload.keys);
2241
- };
2242
- this.computedEmitter.on(
2243
- "update" /* UPDATE */,
2244
- this.handleUpdate
2245
- );
2246
- }
2247
- destroy() {
2248
- this.computedEmitter.off(
2249
- "update" /* UPDATE */,
2250
- this.handleUpdate
2251
- );
2252
- this.emitter.offAll();
2253
- this.clearUpdatedKeys();
2254
- }
2255
- };
2256
-
2257
2446
  // src/modules/computed/core/OIMComputed.ts
2258
2447
  var OIMComputed = class {
2259
- constructor(queue, opts) {
2448
+ constructor(runtime, opts) {
2260
2449
  this.emitter = new OIMEventEmitter();
2261
2450
  this.hasValue = false;
2262
2451
  this.isDirty = true;
2263
- this.isInFlush = false;
2264
- this.hasRecomputedInThisFlush = false;
2265
- this.handleQueueBeforeFlush = () => {
2266
- this.isInFlush = true;
2267
- this.hasRecomputedInThisFlush = false;
2268
- };
2269
- this.handleQueueAfterFlush = () => {
2270
- this.isInFlush = false;
2271
- this.hasRecomputedInThisFlush = false;
2272
- };
2273
- this.queue = queue;
2452
+ this.runtime = runtime;
2274
2453
  this.compute = opts.compute;
2275
2454
  this.compare = opts.compare ?? Object.is;
2276
2455
  this.deps = opts.deps ?? [];
2277
- this.coalescer = new OIMUpdateEventCoalescerComputed(
2278
- this.emitter
2279
- );
2280
- this.updateEventEmitter = new OIMUpdateEventEmitter({
2281
- coalescer: this.coalescer,
2282
- queue: this.queue
2283
- });
2284
- this.queue.emitter.on(
2285
- 1 /* BEFORE_FLUSH */,
2286
- this.handleQueueBeforeFlush
2287
- );
2288
- this.queue.emitter.on(
2289
- 0 /* AFTER_FLUSH */,
2290
- this.handleQueueAfterFlush
2456
+ this.updateEventEmitter = new OIMUpdateEventEmitter(
2457
+ this.runtime.queue,
2458
+ "immediate"
2291
2459
  );
2292
- this.effect = new OIMEffect(this.queue, {
2460
+ this.effect = new OIMEffect(this.runtime, {
2293
2461
  deps: this.deps,
2294
- phase: "pre" /* PRE */,
2295
- onInvalidate: () => {
2462
+ onUpdate: () => {
2296
2463
  this.isDirty = true;
2297
2464
  },
2298
2465
  run: () => {
2299
- if (this.isInFlush) {
2300
- if (this.hasRecomputedInThisFlush) return;
2301
- this.hasRecomputedInThisFlush = true;
2302
- }
2303
2466
  this.recomputeAndEmitIfChanged();
2304
2467
  }
2305
2468
  });
@@ -2313,16 +2476,7 @@ var OIMComputed = class {
2313
2476
  }
2314
2477
  destroy() {
2315
2478
  this.effect.destroy();
2316
- this.queue.emitter.off(
2317
- 1 /* BEFORE_FLUSH */,
2318
- this.handleQueueBeforeFlush
2319
- );
2320
- this.queue.emitter.off(
2321
- 0 /* AFTER_FLUSH */,
2322
- this.handleQueueAfterFlush
2323
- );
2324
2479
  this.updateEventEmitter.destroy();
2325
- this.coalescer.destroy();
2326
2480
  this.emitter.offAll();
2327
2481
  }
2328
2482
  recomputeAndEmitIfChanged() {
@@ -2338,6 +2492,7 @@ var OIMComputed = class {
2338
2492
  this.emitter.emit("update" /* UPDATE */, {
2339
2493
  keys: ["value"]
2340
2494
  });
2495
+ this.updateEventEmitter.markUpdatedKey("value");
2341
2496
  }
2342
2497
  };
2343
2498
 
@@ -2346,31 +2501,21 @@ function isReadonlyArray(value) {
2346
2501
  return Array.isArray(value);
2347
2502
  }
2348
2503
  var OIMEffectDependencyKeyed = class {
2349
- constructor(updateEventEmitter, keyOrKeys) {
2350
- this.updateEventEmitter = updateEventEmitter;
2504
+ constructor(subscription, keyOrKeys) {
2505
+ this.subscription = subscription;
2351
2506
  if (isReadonlyArray(keyOrKeys)) {
2352
2507
  this.keys = keyOrKeys;
2353
2508
  } else {
2354
2509
  this.key = keyOrKeys;
2355
2510
  }
2356
2511
  }
2357
- subscribe(phase, onInvalidate) {
2358
- const isPre = phase === "pre" /* PRE */;
2512
+ subscribe(onUpdate) {
2359
2513
  if (this.keys !== void 0) {
2360
- return isPre ? this.updateEventEmitter.subscribeOnKeysBeforeFlush(
2361
- this.keys,
2362
- onInvalidate
2363
- ) : this.updateEventEmitter.subscribeOnKeys(
2364
- this.keys,
2365
- onInvalidate
2366
- );
2514
+ return this.subscription.subscribeOnKeys(this.keys, onUpdate);
2367
2515
  }
2368
2516
  if (this.key === void 0) return () => {
2369
2517
  };
2370
- return isPre ? this.updateEventEmitter.subscribeOnKeyBeforeFlush(
2371
- this.key,
2372
- onInvalidate
2373
- ) : this.updateEventEmitter.subscribeOnKey(this.key, onInvalidate);
2518
+ return this.subscription.subscribeOnKey(this.key, onUpdate);
2374
2519
  }
2375
2520
  };
2376
2521
 
@@ -2378,12 +2523,12 @@ var OIMEffectDependencyKeyed = class {
2378
2523
  var OIMEffectDependencyKeyedObject = class {
2379
2524
  constructor(object, keyOrKeys) {
2380
2525
  this.dep = new OIMEffectDependencyKeyed(
2381
- object.updateEventEmitter,
2526
+ object,
2382
2527
  keyOrKeys
2383
2528
  );
2384
2529
  }
2385
- subscribe(phase, onInvalidate) {
2386
- return this.dep.subscribe(phase, onInvalidate);
2530
+ subscribe(onUpdate) {
2531
+ return this.dep.subscribe(onUpdate);
2387
2532
  }
2388
2533
  };
2389
2534
 
@@ -2391,12 +2536,12 @@ var OIMEffectDependencyKeyedObject = class {
2391
2536
  var OIMEffectDependencyKeyedCollection = class {
2392
2537
  constructor(collection, pkOrPks) {
2393
2538
  this.dep = new OIMEffectDependencyKeyed(
2394
- collection.updateEventEmitter,
2539
+ collection,
2395
2540
  pkOrPks
2396
2541
  );
2397
2542
  }
2398
- subscribe(phase, onInvalidate) {
2399
- return this.dep.subscribe(phase, onInvalidate);
2543
+ subscribe(onUpdate) {
2544
+ return this.dep.subscribe(onUpdate);
2400
2545
  }
2401
2546
  };
2402
2547
 
@@ -2404,44 +2549,591 @@ var OIMEffectDependencyKeyedCollection = class {
2404
2549
  var OIMEffectDependencyKeyedIndex = class {
2405
2550
  constructor(index, keyOrKeys) {
2406
2551
  this.dep = new OIMEffectDependencyKeyed(
2407
- index.updateEventEmitter,
2552
+ index,
2408
2553
  keyOrKeys
2409
2554
  );
2410
2555
  }
2411
- subscribe(phase, onInvalidate) {
2412
- return this.dep.subscribe(phase, onInvalidate);
2556
+ subscribe(onUpdate) {
2557
+ return this.dep.subscribe(onUpdate);
2413
2558
  }
2414
2559
  };
2415
2560
 
2416
- // src/modules/effect/core/OIMEffectDependencyComputed.ts
2417
- var OIMEffectDependencyComputed = class {
2418
- constructor(computed) {
2419
- this.computed = computed;
2561
+ // src/modules/selector/core/OIMSelector.ts
2562
+ var OIMSelector = class {
2563
+ constructor(runtime, sourceDeps, computedDeps = []) {
2564
+ this.runtime = runtime;
2565
+ this.sourceDeps = sourceDeps;
2566
+ this.computedDeps = computedDeps;
2567
+ this.listeners = /* @__PURE__ */ new Set();
2568
+ this.sourceUnsubscribers = [];
2569
+ this.computedUnsubscribers = [];
2570
+ this.isResubscribeEnqueued = false;
2571
+ this.isTemporarilyUnsubscribedForFlush = false;
2572
+ this.isDeliveryEnqueued = false;
2573
+ this.hasLastValue = false;
2574
+ this.onSourceInvalidate = () => {
2575
+ if (this.listeners.size === 0) return;
2576
+ if (!this.isTemporarilyUnsubscribedForFlush) {
2577
+ this.isTemporarilyUnsubscribedForFlush = true;
2578
+ this.unsubscribeFromSourceDeps();
2579
+ this.ensureResubscribeAfterFlush();
2580
+ }
2581
+ if (this.isDeliveryEnqueued) return;
2582
+ this.isDeliveryEnqueued = true;
2583
+ this.dequeueDelivery = this.runtime.schedule(this.deliver);
2584
+ };
2585
+ this.onComputedInvalidate = () => {
2586
+ if (this.listeners.size === 0) return;
2587
+ if (this.isDeliveryEnqueued) return;
2588
+ this.isDeliveryEnqueued = true;
2589
+ this.dequeueDelivery = this.runtime.schedule(this.deliver);
2590
+ };
2591
+ this.deliver = () => {
2592
+ this.isDeliveryEnqueued = false;
2593
+ this.dequeueDelivery = void 0;
2594
+ if (this.listeners.size === 0) return;
2595
+ const next = this.getValue();
2596
+ if (this.hasLastValue && this.areEqual(this.lastValue, next)) return;
2597
+ this.setLastValue(next);
2598
+ const snapshot = Array.from(this.listeners);
2599
+ for (let i = 0; i < snapshot.length; i++) {
2600
+ const listener = snapshot[i];
2601
+ if (this.listeners.has(listener)) listener(next);
2602
+ }
2603
+ };
2604
+ this.onAfterFlush = () => {
2605
+ if (!this.isResubscribeEnqueued) return;
2606
+ this.isResubscribeEnqueued = false;
2607
+ this.dequeueResubscribe = void 0;
2608
+ if (this.listeners.size === 0) return;
2609
+ this.isTemporarilyUnsubscribedForFlush = false;
2610
+ if (this.sourceUnsubscribers.length === 0) {
2611
+ for (const dep of this.sourceDeps) {
2612
+ this.sourceUnsubscribers.push(
2613
+ dep.subscribe(this.onSourceInvalidate)
2614
+ );
2615
+ }
2616
+ }
2617
+ };
2618
+ }
2619
+ get queue() {
2620
+ return this.runtime.queue;
2621
+ }
2622
+ /**
2623
+ * Start watching this selector. The callback is called immediately with the current value,
2624
+ * then coalesced and delivered on `queue.flush()` when the selector invalidates.
2625
+ */
2626
+ watch(onValue) {
2627
+ this.listeners.add(onValue);
2628
+ if (this.listeners.size === 1) {
2629
+ this.subscribeToDeps();
2630
+ }
2631
+ const current = this.getValue();
2632
+ this.setLastValue(current);
2633
+ onValue(current);
2634
+ return () => {
2635
+ this.listeners.delete(onValue);
2636
+ if (this.listeners.size > 0) return;
2637
+ this.unsubscribeFromAllDeps();
2638
+ if (this.dequeueResubscribe) {
2639
+ this.dequeueResubscribe();
2640
+ this.dequeueResubscribe = void 0;
2641
+ }
2642
+ this.isResubscribeEnqueued = false;
2643
+ this.isTemporarilyUnsubscribedForFlush = false;
2644
+ if (this.dequeueDelivery) {
2645
+ this.dequeueDelivery();
2646
+ this.dequeueDelivery = void 0;
2647
+ }
2648
+ this.isDeliveryEnqueued = false;
2649
+ this.hasLastValue = false;
2650
+ };
2420
2651
  }
2421
- subscribe(phase, onInvalidate) {
2422
- if (phase === "pre" /* PRE */) {
2423
- return this.computed.emitter.on("update", onInvalidate);
2652
+ areEqual(prev, next) {
2653
+ return Object.is(prev, next);
2654
+ }
2655
+ setLastValue(value) {
2656
+ this.lastValue = value;
2657
+ this.hasLastValue = true;
2658
+ }
2659
+ subscribeToDeps() {
2660
+ if (this.sourceUnsubscribers.length > 0 || this.computedUnsubscribers.length > 0)
2661
+ return;
2662
+ for (const dep of this.sourceDeps) {
2663
+ this.sourceUnsubscribers.push(
2664
+ dep.subscribe(this.onSourceInvalidate)
2665
+ );
2424
2666
  }
2425
- return this.computed.updateEventEmitter.subscribeOnKey(
2426
- "value",
2427
- onInvalidate
2667
+ for (const dep of this.computedDeps) {
2668
+ this.computedUnsubscribers.push(
2669
+ dep.subscribe(this.onComputedInvalidate)
2670
+ );
2671
+ }
2672
+ }
2673
+ unsubscribeFromSourceDeps() {
2674
+ if (this.sourceUnsubscribers.length === 0) return;
2675
+ for (const unsub of this.sourceUnsubscribers) unsub();
2676
+ this.sourceUnsubscribers = [];
2677
+ }
2678
+ unsubscribeFromAllDeps() {
2679
+ this.unsubscribeFromSourceDeps();
2680
+ if (this.computedUnsubscribers.length === 0) return;
2681
+ for (const unsub of this.computedUnsubscribers) unsub();
2682
+ this.computedUnsubscribers = [];
2683
+ }
2684
+ ensureResubscribeAfterFlush() {
2685
+ if (this.isResubscribeEnqueued) return;
2686
+ this.isResubscribeEnqueued = true;
2687
+ this.dequeueResubscribe = this.runtime.scheduleAfterFlush(
2688
+ this.onAfterFlush
2428
2689
  );
2429
2690
  }
2430
2691
  };
2692
+
2693
+ // src/modules/selector/core/OIMSelectorSourceDependencyUpdateEventEmitterKey.ts
2694
+ var OIMSelectorSourceDependencyUpdateEventEmitterKey = class {
2695
+ constructor(subscription, key) {
2696
+ this.subscription = subscription;
2697
+ this.key = key;
2698
+ }
2699
+ subscribe(onUpdate) {
2700
+ return this.subscription.subscribeOnKey(this.key, onUpdate);
2701
+ }
2702
+ };
2703
+
2704
+ // src/modules/selector/core/OIMCollectionByPkSelector.ts
2705
+ var OIMCollectionByPkSelector = class extends OIMSelector {
2706
+ constructor(runtime, collection, pk) {
2707
+ super(runtime, [
2708
+ new OIMSelectorSourceDependencyUpdateEventEmitterKey(
2709
+ collection,
2710
+ pk
2711
+ )
2712
+ ]);
2713
+ this.collection = collection;
2714
+ this.pk = pk;
2715
+ }
2716
+ getValue() {
2717
+ return this.collection.getOneByPk(this.pk);
2718
+ }
2719
+ };
2720
+
2721
+ // src/modules/selector/core/OIMSelectorSourceDependencyUpdateEventEmitterKeys.ts
2722
+ var OIMSelectorSourceDependencyUpdateEventEmitterKeys = class {
2723
+ constructor(subscription, keys) {
2724
+ this.subscription = subscription;
2725
+ this.keys = keys;
2726
+ }
2727
+ subscribe(onUpdate) {
2728
+ return this.subscription.subscribeOnKeys(this.keys, onUpdate);
2729
+ }
2730
+ };
2731
+
2732
+ // src/modules/selector/core/OIMCollectionByPksSelector.ts
2733
+ var OIMCollectionByPksSelector = class extends OIMSelector {
2734
+ constructor(runtime, collection, pks) {
2735
+ super(runtime, [
2736
+ new OIMSelectorSourceDependencyUpdateEventEmitterKeys(
2737
+ collection,
2738
+ pks
2739
+ )
2740
+ ]);
2741
+ this.collection = collection;
2742
+ this.pks = pks;
2743
+ }
2744
+ getValue() {
2745
+ return this.pks.map((pk) => this.collection.getOneByPk(pk));
2746
+ }
2747
+ areEqual(prev, next) {
2748
+ if (prev === next) return true;
2749
+ if (prev.length !== next.length) return false;
2750
+ for (let i = 0; i < prev.length; i++) {
2751
+ if (prev[i] !== next[i]) return false;
2752
+ }
2753
+ return true;
2754
+ }
2755
+ };
2756
+
2757
+ // src/modules/selector/core/OIMObjectValueByKeySelector.ts
2758
+ var OIMObjectValueByKeySelector = class extends OIMSelector {
2759
+ constructor(runtime, obj, key) {
2760
+ super(runtime, [
2761
+ new OIMSelectorSourceDependencyUpdateEventEmitterKey(
2762
+ obj,
2763
+ key
2764
+ )
2765
+ ]);
2766
+ this.obj = obj;
2767
+ this.key = key;
2768
+ }
2769
+ getValue() {
2770
+ return this.obj.get(this.key);
2771
+ }
2772
+ };
2773
+
2774
+ // src/modules/selector/core/OIMObjectValuesByKeysSelector.ts
2775
+ var OIMObjectValuesByKeysSelector = class extends OIMSelector {
2776
+ constructor(runtime, obj, keys) {
2777
+ super(runtime, [
2778
+ new OIMSelectorSourceDependencyUpdateEventEmitterKeys(
2779
+ obj,
2780
+ keys
2781
+ )
2782
+ ]);
2783
+ this.obj = obj;
2784
+ this.keys = keys;
2785
+ }
2786
+ getValue() {
2787
+ return this.keys.map((k) => this.obj.get(k));
2788
+ }
2789
+ areEqual(prev, next) {
2790
+ if (prev === next) return true;
2791
+ if (prev.length !== next.length) return false;
2792
+ for (let i = 0; i < prev.length; i++) {
2793
+ if (prev[i] !== next[i]) return false;
2794
+ }
2795
+ return true;
2796
+ }
2797
+ };
2798
+
2799
+ // src/modules/selector/core/OIMIndexPksByKeySetBasedSelector.ts
2800
+ var OIMIndexPksByKeySetBasedSelector = class extends OIMSelector {
2801
+ constructor(runtime, reactiveIndex, key) {
2802
+ super(runtime, [
2803
+ new OIMSelectorSourceDependencyUpdateEventEmitterKey(
2804
+ reactiveIndex,
2805
+ key
2806
+ )
2807
+ ]);
2808
+ this.reactiveIndex = reactiveIndex;
2809
+ this.key = key;
2810
+ }
2811
+ getValue() {
2812
+ return this.reactiveIndex.getPksByKey(this.key);
2813
+ }
2814
+ };
2815
+
2816
+ // src/modules/selector/core/OIMIndexPksByKeyArrayBasedSelector.ts
2817
+ var OIMIndexPksByKeyArrayBasedSelector = class extends OIMSelector {
2818
+ constructor(runtime, reactiveIndex, key) {
2819
+ super(runtime, [
2820
+ new OIMSelectorSourceDependencyUpdateEventEmitterKey(
2821
+ reactiveIndex,
2822
+ key
2823
+ )
2824
+ ]);
2825
+ this.reactiveIndex = reactiveIndex;
2826
+ this.key = key;
2827
+ }
2828
+ getValue() {
2829
+ return this.reactiveIndex.getPksByKey(this.key);
2830
+ }
2831
+ areEqual(prev, next) {
2832
+ if (prev === next) return true;
2833
+ if (prev.length !== next.length) return false;
2834
+ for (let i = 0; i < prev.length; i++)
2835
+ if (prev[i] !== next[i]) return false;
2836
+ return true;
2837
+ }
2838
+ };
2839
+
2840
+ // src/modules/selector/core/OIMIndexPksByKeysSetBasedSelector.ts
2841
+ var OIMIndexPksByKeysSetBasedSelector = class extends OIMSelector {
2842
+ constructor(runtime, reactiveIndex, keys) {
2843
+ super(runtime, [
2844
+ new OIMSelectorSourceDependencyUpdateEventEmitterKeys(
2845
+ reactiveIndex,
2846
+ keys
2847
+ )
2848
+ ]);
2849
+ this.reactiveIndex = reactiveIndex;
2850
+ this.keys = keys;
2851
+ }
2852
+ getValue() {
2853
+ return this.keys.map((key) => Array.from(this.reactiveIndex.getPksByKey(key))).flat();
2854
+ }
2855
+ areEqual(prev, next) {
2856
+ if (prev === next) return true;
2857
+ if (prev.length !== next.length) return false;
2858
+ for (let i = 0; i < prev.length; i++)
2859
+ if (prev[i] !== next[i]) return false;
2860
+ return true;
2861
+ }
2862
+ };
2863
+
2864
+ // src/modules/selector/core/OIMIndexPksByKeysArrayBasedSelector.ts
2865
+ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
2866
+ constructor(runtime, reactiveIndex, keys) {
2867
+ super(runtime, [
2868
+ new OIMSelectorSourceDependencyUpdateEventEmitterKeys(
2869
+ reactiveIndex,
2870
+ keys
2871
+ )
2872
+ ]);
2873
+ this.reactiveIndex = reactiveIndex;
2874
+ this.keys = keys;
2875
+ }
2876
+ getValue() {
2877
+ return this.keys.map((key) => this.reactiveIndex.getPksByKey(key)).flat();
2878
+ }
2879
+ areEqual(prev, next) {
2880
+ if (prev === next) return true;
2881
+ if (prev.length !== next.length) return false;
2882
+ for (let i = 0; i < prev.length; i++)
2883
+ if (prev[i] !== next[i]) return false;
2884
+ return true;
2885
+ }
2886
+ };
2887
+
2888
+ // src/modules/selector/core/OIMSelectorSourceDependencyEntitiesByIndexKeySetBased.ts
2889
+ var OIMSelectorSourceDependencyEntitiesByIndexKeySetBased = class {
2890
+ constructor(collection, reactiveIndex, key) {
2891
+ this.collection = collection;
2892
+ this.reactiveIndex = reactiveIndex;
2893
+ this.key = key;
2894
+ this.currentPks = [];
2895
+ }
2896
+ subscribe(onUpdate) {
2897
+ const resubscribeCollection = () => {
2898
+ const nextPks = Array.from(
2899
+ this.reactiveIndex.getPksByKey(this.key)
2900
+ );
2901
+ if (this.sameArray(this.currentPks, nextPks)) return;
2902
+ this.unsubscribeFromCollectionKeys?.();
2903
+ this.unsubscribeFromCollectionKeys = void 0;
2904
+ this.currentPks = nextPks;
2905
+ if (this.currentPks.length === 0) return;
2906
+ this.unsubscribeFromCollectionKeys = this.collection.subscribeOnKeys(this.currentPks, onUpdate);
2907
+ };
2908
+ const onIndexUpdate = () => {
2909
+ resubscribeCollection();
2910
+ onUpdate();
2911
+ };
2912
+ resubscribeCollection();
2913
+ const unsubscribeFromIndex = this.reactiveIndex.subscribeOnKey(
2914
+ this.key,
2915
+ onIndexUpdate
2916
+ );
2917
+ return () => {
2918
+ unsubscribeFromIndex();
2919
+ this.unsubscribeFromCollectionKeys?.();
2920
+ this.unsubscribeFromCollectionKeys = void 0;
2921
+ this.currentPks = [];
2922
+ };
2923
+ }
2924
+ sameArray(a, b) {
2925
+ if (a === b) return true;
2926
+ if (a.length !== b.length) return false;
2927
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
2928
+ return true;
2929
+ }
2930
+ };
2931
+
2932
+ // src/modules/selector/core/OIMEntitiesByIndexKeySetBasedSelector.ts
2933
+ var OIMEntitiesByIndexKeySetBasedSelector = class extends OIMSelector {
2934
+ constructor(runtime, collection, reactiveIndex, key) {
2935
+ super(runtime, [
2936
+ new OIMSelectorSourceDependencyEntitiesByIndexKeySetBased(collection, reactiveIndex, key)
2937
+ ]);
2938
+ this.collection = collection;
2939
+ this.reactiveIndex = reactiveIndex;
2940
+ this.key = key;
2941
+ }
2942
+ getValue() {
2943
+ const pks = Array.from(this.reactiveIndex.getPksByKey(this.key));
2944
+ return pks.map((pk) => this.collection.getOneByPk(pk));
2945
+ }
2946
+ areEqual(prev, next) {
2947
+ if (prev === next) return true;
2948
+ if (prev.length !== next.length) return false;
2949
+ for (let i = 0; i < prev.length; i++)
2950
+ if (prev[i] !== next[i]) return false;
2951
+ return true;
2952
+ }
2953
+ };
2954
+
2955
+ // src/modules/selector/core/OIMSelectorSourceDependencyEntitiesByIndexKeyArrayBased.ts
2956
+ var OIMSelectorSourceDependencyEntitiesByIndexKeyArrayBased = class {
2957
+ constructor(collection, reactiveIndex, key) {
2958
+ this.collection = collection;
2959
+ this.reactiveIndex = reactiveIndex;
2960
+ this.key = key;
2961
+ this.currentPks = [];
2962
+ }
2963
+ subscribe(onUpdate) {
2964
+ const resubscribeCollection = () => {
2965
+ const nextPks = this.reactiveIndex.getPksByKey(this.key);
2966
+ if (this.sameArray(this.currentPks, nextPks)) return;
2967
+ this.unsubscribeFromCollectionKeys?.();
2968
+ this.unsubscribeFromCollectionKeys = void 0;
2969
+ this.currentPks = nextPks;
2970
+ if (this.currentPks.length === 0) return;
2971
+ this.unsubscribeFromCollectionKeys = this.collection.subscribeOnKeys(this.currentPks, onUpdate);
2972
+ };
2973
+ const onIndexUpdate = () => {
2974
+ resubscribeCollection();
2975
+ onUpdate();
2976
+ };
2977
+ resubscribeCollection();
2978
+ const unsubscribeFromIndex = this.reactiveIndex.subscribeOnKey(
2979
+ this.key,
2980
+ onIndexUpdate
2981
+ );
2982
+ return () => {
2983
+ unsubscribeFromIndex();
2984
+ this.unsubscribeFromCollectionKeys?.();
2985
+ this.unsubscribeFromCollectionKeys = void 0;
2986
+ this.currentPks = [];
2987
+ };
2988
+ }
2989
+ sameArray(a, b) {
2990
+ if (a === b) return true;
2991
+ if (a.length !== b.length) return false;
2992
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
2993
+ return true;
2994
+ }
2995
+ };
2996
+
2997
+ // src/modules/selector/core/OIMEntitiesByIndexKeyArrayBasedSelector.ts
2998
+ var OIMEntitiesByIndexKeyArrayBasedSelector = class extends OIMSelector {
2999
+ constructor(runtime, collection, reactiveIndex, key) {
3000
+ super(runtime, [
3001
+ new OIMSelectorSourceDependencyEntitiesByIndexKeyArrayBased(collection, reactiveIndex, key)
3002
+ ]);
3003
+ this.collection = collection;
3004
+ this.reactiveIndex = reactiveIndex;
3005
+ this.key = key;
3006
+ }
3007
+ getValue() {
3008
+ const pks = this.reactiveIndex.getPksByKey(this.key);
3009
+ return pks.map((pk) => this.collection.getOneByPk(pk));
3010
+ }
3011
+ areEqual(prev, next) {
3012
+ if (prev === next) return true;
3013
+ if (prev.length !== next.length) return false;
3014
+ for (let i = 0; i < prev.length; i++)
3015
+ if (prev[i] !== next[i]) return false;
3016
+ return true;
3017
+ }
3018
+ };
3019
+
3020
+ // src/modules/computative/core/OIMComputativeRuntime.ts
3021
+ var OIMComputativeRuntime = class {
3022
+ constructor(queue) {
3023
+ this.queue = queue;
3024
+ this.isFlushScheduled = false;
3025
+ this.isFlushing = false;
3026
+ this.minLevel = Infinity;
3027
+ this.maxLevel = -Infinity;
3028
+ this.dirtyByLevel = /* @__PURE__ */ new Map();
3029
+ this.afterFlushTasks = /* @__PURE__ */ new Set();
3030
+ this.queue.emitter.on(0 /* AFTER_FLUSH */, () => {
3031
+ if (!this.isFlushScheduled) return;
3032
+ this.flush();
3033
+ });
3034
+ }
3035
+ /**
3036
+ * Schedule a one-shot task to run on the next `queue.flush()` (AFTER_FLUSH),
3037
+ * ordered by `level` ascending.
3038
+ */
3039
+ schedule(task, level = 0) {
3040
+ let isActive = true;
3041
+ const wrapped = () => {
3042
+ if (!isActive) return;
3043
+ task();
3044
+ };
3045
+ let bucket = this.dirtyByLevel.get(level);
3046
+ if (!bucket) {
3047
+ bucket = /* @__PURE__ */ new Set();
3048
+ this.dirtyByLevel.set(level, bucket);
3049
+ }
3050
+ bucket.add(wrapped);
3051
+ if (level < this.minLevel) this.minLevel = level;
3052
+ if (level > this.maxLevel) this.maxLevel = level;
3053
+ if (!this.isFlushing) this.isFlushScheduled = true;
3054
+ return () => {
3055
+ if (!isActive) return;
3056
+ isActive = false;
3057
+ bucket.delete(wrapped);
3058
+ };
3059
+ }
3060
+ /**
3061
+ * Run once at the start of the next runtime flush (i.e. after queue.flush()).
3062
+ * Useful for resubscribing dependencies exactly once per source flush.
3063
+ */
3064
+ scheduleAfterFlush(task) {
3065
+ let isActive = true;
3066
+ const wrapped = () => {
3067
+ if (!isActive) return;
3068
+ task();
3069
+ };
3070
+ this.afterFlushTasks.add(wrapped);
3071
+ if (!this.isFlushing) this.isFlushScheduled = true;
3072
+ return () => {
3073
+ if (!isActive) return;
3074
+ isActive = false;
3075
+ this.afterFlushTasks.delete(wrapped);
3076
+ };
3077
+ }
3078
+ flush() {
3079
+ this.isFlushScheduled = false;
3080
+ if (this.dirtyByLevel.size === 0 && this.afterFlushTasks.size === 0)
3081
+ return;
3082
+ this.isFlushing = true;
3083
+ try {
3084
+ if (this.afterFlushTasks.size > 0) {
3085
+ const snapshot = Array.from(this.afterFlushTasks);
3086
+ this.afterFlushTasks.clear();
3087
+ for (let i = 0; i < snapshot.length; i++) snapshot[i]();
3088
+ }
3089
+ if (this.dirtyByLevel.size === 0) return;
3090
+ let level = this.minLevel;
3091
+ let max = this.maxLevel;
3092
+ while (level <= max) {
3093
+ for (; ; ) {
3094
+ const bucket = this.dirtyByLevel.get(level);
3095
+ if (!bucket || bucket.size === 0) break;
3096
+ this.dirtyByLevel.delete(level);
3097
+ const snapshot = Array.from(bucket);
3098
+ for (let i = 0; i < snapshot.length; i++) snapshot[i]();
3099
+ }
3100
+ if (this.maxLevel > max) max = this.maxLevel;
3101
+ level++;
3102
+ }
3103
+ } finally {
3104
+ this.isFlushing = false;
3105
+ if (this.dirtyByLevel.size === 0) {
3106
+ this.minLevel = Infinity;
3107
+ this.maxLevel = -Infinity;
3108
+ } else {
3109
+ let nextMin = Infinity;
3110
+ let nextMax = -Infinity;
3111
+ this.dirtyByLevel.forEach((_bucket, lvl) => {
3112
+ if (lvl < nextMin) nextMin = lvl;
3113
+ if (lvl > nextMax) nextMax = lvl;
3114
+ });
3115
+ this.minLevel = nextMin;
3116
+ this.maxLevel = nextMax;
3117
+ this.isFlushScheduled = true;
3118
+ }
3119
+ }
3120
+ }
3121
+ };
2431
3122
  export {
2432
3123
  EOIMCollectionEventType,
2433
3124
  EOIMComputedEventType,
2434
- EOIMEffectPhase,
2435
3125
  EOIMEventQueueEventType,
2436
3126
  EOIMEventQueueSchedulerEventType,
2437
3127
  EOIMIndexEventType,
2438
3128
  EOIMObjectEventType,
2439
- EOIMUpdateEventCoalescerEventType,
2440
3129
  OIMCollection,
3130
+ OIMCollectionByPkSelector,
3131
+ OIMCollectionByPksSelector,
2441
3132
  OIMCollectionChangedFieldsWrapper,
2442
3133
  OIMCollectionStore,
2443
3134
  OIMCollectionStoreMapDriven,
2444
3135
  OIMComparatorFactory,
3136
+ OIMComputativeRuntime,
2445
3137
  OIMComputed,
2446
3138
  OIMDBSettings,
2447
3139
  OIMEffect,
@@ -2450,6 +3142,8 @@ export {
2450
3142
  OIMEffectDependencyKeyedCollection,
2451
3143
  OIMEffectDependencyKeyedIndex,
2452
3144
  OIMEffectDependencyKeyedObject,
3145
+ OIMEntitiesByIndexKeyArrayBasedSelector,
3146
+ OIMEntitiesByIndexKeySetBasedSelector,
2453
3147
  OIMEntityUpdaterFactory,
2454
3148
  OIMEventEmitter,
2455
3149
  OIMEventQueue,
@@ -2464,6 +3158,10 @@ export {
2464
3158
  OIMIndexManualArrayBased,
2465
3159
  OIMIndexManualOrderedArrayBased,
2466
3160
  OIMIndexManualSetBased,
3161
+ OIMIndexPksByKeyArrayBasedSelector,
3162
+ OIMIndexPksByKeySetBasedSelector,
3163
+ OIMIndexPksByKeysArrayBasedSelector,
3164
+ OIMIndexPksByKeysSetBasedSelector,
2467
3165
  OIMIndexSetBased,
2468
3166
  OIMIndexStoreArrayBased,
2469
3167
  OIMIndexStoreMapDrivenArrayBased,
@@ -2474,6 +3172,8 @@ export {
2474
3172
  OIMObjectStore,
2475
3173
  OIMObjectStoreMapDriven,
2476
3174
  OIMObjectStoreRecordDriven,
3175
+ OIMObjectValueByKeySelector,
3176
+ OIMObjectValuesByKeysSelector,
2477
3177
  OIMOrderedListIndexCommandStreamWrapper,
2478
3178
  OIMPkSelectorFactory,
2479
3179
  OIMRICollection,
@@ -2483,11 +3183,5 @@ export {
2483
3183
  OIMReactiveIndexManualSetBased,
2484
3184
  OIMReactiveIndexSetBased,
2485
3185
  OIMReactiveObject,
2486
- OIMUpdateEventCoalescer,
2487
- OIMUpdateEventCoalescerCollection,
2488
- OIMUpdateEventCoalescerComputed,
2489
- OIMUpdateEventCoalescerIndex,
2490
- OIMUpdateEventCoalescerManual,
2491
- OIMUpdateEventCoalescerObject,
2492
- OIMUpdateEventEmitter
3186
+ OIMSelector
2493
3187
  };