@oimdb/core 3.0.1 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +581 -498
- package/dist/index.d.cts +138 -71
- package/dist/index.d.ts +138 -71
- package/dist/index.js +581 -498
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -524,265 +524,197 @@ var OIMIndexArrayBased = class extends OIMIndex {
|
|
|
524
524
|
}
|
|
525
525
|
};
|
|
526
526
|
|
|
527
|
-
// src/
|
|
528
|
-
var
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
//
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
this.keyHandlers = /* @__PURE__ */ new Map();
|
|
527
|
+
// src/core/OIMCarrierKeyedEmitter.ts
|
|
528
|
+
var OIMCarrierKeyedEmitter = class {
|
|
529
|
+
constructor(queue, resolver) {
|
|
530
|
+
// Carriers with at least one subscriber — for markAllUpdated / metrics.
|
|
531
|
+
this.subscribedCarriers = /* @__PURE__ */ new Set();
|
|
532
|
+
// Pending flush batch. A plain array + a `carrier.dirty` flag for dedup —
|
|
533
|
+
// marking is the hottest write-path op, and pushing to an array with a
|
|
534
|
+
// boolean check is ~4x cheaper than a `Set` identity-hash add. The flag
|
|
535
|
+
// doubles as membership: a carrier is in this array iff `carrier.dirty`.
|
|
536
|
+
this.dirtyCarriers = [];
|
|
538
537
|
this.isFlushEnqueued = false;
|
|
539
|
-
this.updatedKeys = /* @__PURE__ */ new Set();
|
|
540
|
-
this.isAfterFlushDeliveryScheduled = false;
|
|
541
|
-
// Stable handler reference to avoid allocating a new closure on every HAS_CHANGES.
|
|
542
538
|
this.onFlush = () => {
|
|
543
539
|
this.isFlushEnqueued = false;
|
|
544
|
-
this.
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
540
|
+
if (this.dirtyCarriers.length === 0) return;
|
|
541
|
+
const flushing = this.dirtyCarriers;
|
|
542
|
+
this.dirtyCarriers = [];
|
|
543
|
+
let i = 0;
|
|
544
|
+
try {
|
|
545
|
+
for (; i < flushing.length; i++) {
|
|
546
|
+
flushing[i].dirty = false;
|
|
547
|
+
this.notify(flushing[i]);
|
|
548
|
+
}
|
|
549
|
+
} finally {
|
|
550
|
+
for (; i < flushing.length; i++) flushing[i].dirty = false;
|
|
548
551
|
}
|
|
549
|
-
this.runSinglePass();
|
|
550
|
-
};
|
|
551
|
-
this.onAfterFlush = () => {
|
|
552
|
-
if (!this.isAfterFlushDeliveryScheduled) return;
|
|
553
|
-
this.isAfterFlushDeliveryScheduled = false;
|
|
554
|
-
this.deliverImmediate();
|
|
555
552
|
};
|
|
556
553
|
this.queue = queue;
|
|
557
|
-
this.
|
|
558
|
-
if (this.deliveryMode === "after_flush") {
|
|
559
|
-
this.unsubscribeAfterFlushDelivery = this.queue.emitter.on(
|
|
560
|
-
0 /* AFTER_FLUSH */,
|
|
561
|
-
this.onAfterFlush
|
|
562
|
-
);
|
|
563
|
-
}
|
|
554
|
+
this.resolver = resolver;
|
|
564
555
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
);
|
|
556
|
+
subscribeOnKey(key, handler) {
|
|
557
|
+
const carrier = this.resolver.getOrReserveCarrier(key);
|
|
558
|
+
this.addHandler(carrier, handler);
|
|
559
|
+
return () => this.removeHandler(carrier, handler);
|
|
560
|
+
}
|
|
561
|
+
subscribeOnKeys(keys, handler) {
|
|
562
|
+
const carriers = [];
|
|
563
|
+
for (let i = 0; i < keys.length; i++) {
|
|
564
|
+
const carrier = this.resolver.getOrReserveCarrier(keys[i]);
|
|
565
|
+
this.addHandler(carrier, handler);
|
|
566
|
+
carriers.push(carrier);
|
|
576
567
|
}
|
|
577
|
-
|
|
578
|
-
|
|
568
|
+
return () => {
|
|
569
|
+
for (let i = 0; i < carriers.length; i++) {
|
|
570
|
+
this.removeHandler(carriers[i], handler);
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
unsubscribeFromKey(key, handler) {
|
|
575
|
+
const carrier = this.resolver.findCarrier(key);
|
|
576
|
+
if (carrier) this.removeHandler(carrier, handler);
|
|
577
|
+
}
|
|
578
|
+
unsubscribeFromKeys(keys, handler) {
|
|
579
579
|
for (let i = 0; i < keys.length; i++) {
|
|
580
|
-
|
|
581
|
-
const handlers = this.keyHandlers.get(key);
|
|
582
|
-
if (!handlers || handlers.size === 0) continue;
|
|
583
|
-
this.updatedKeys.add(key);
|
|
584
|
-
didAddAny = true;
|
|
580
|
+
this.unsubscribeFromKey(keys[i], handler);
|
|
585
581
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
582
|
+
}
|
|
583
|
+
addHandler(carrier, handler) {
|
|
584
|
+
let subscribers = carrier.subscribers;
|
|
585
|
+
if (!subscribers) {
|
|
586
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
587
|
+
carrier.subscribers = subscribers;
|
|
590
588
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
589
|
+
subscribers.add(handler);
|
|
590
|
+
this.subscribedCarriers.add(carrier);
|
|
591
|
+
}
|
|
592
|
+
removeHandler(carrier, handler) {
|
|
593
|
+
const subscribers = carrier.subscribers;
|
|
594
|
+
if (!subscribers) return;
|
|
595
|
+
subscribers.delete(handler);
|
|
596
|
+
if (subscribers.size === 0) {
|
|
597
|
+
this.subscribedCarriers.delete(carrier);
|
|
598
|
+
this.resolver.onCarrierEmptied?.(carrier);
|
|
594
599
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
600
|
+
}
|
|
601
|
+
/** Fast path: the writer already holds the carrier. */
|
|
602
|
+
markUpdatedCarrier(carrier) {
|
|
603
|
+
this.assertNotInFlush();
|
|
604
|
+
const subscribers = carrier.subscribers;
|
|
605
|
+
if (!subscribers || subscribers.size === 0) return;
|
|
606
|
+
if (carrier.dirty) return;
|
|
607
|
+
carrier.dirty = true;
|
|
608
|
+
this.dirtyCarriers.push(carrier);
|
|
609
|
+
this.scheduleFlush();
|
|
598
610
|
}
|
|
599
611
|
markUpdatedKey(key) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
const handlers = this.keyHandlers.get(key);
|
|
607
|
-
if (!handlers || handlers.size === 0) return;
|
|
608
|
-
this.updatedKeys.add(key);
|
|
609
|
-
if (this.deliveryMode === "immediate") {
|
|
610
|
-
this.deliverImmediate();
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
if (this.deliveryMode === "after_flush") {
|
|
614
|
-
this.isAfterFlushDeliveryScheduled = true;
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
if (this.isFlushEnqueued) return;
|
|
618
|
-
this.isFlushEnqueued = true;
|
|
619
|
-
this.dequeueFlush = this.queue.enqueue(this.onFlush);
|
|
612
|
+
const carrier = this.resolver.findCarrier(key);
|
|
613
|
+
if (carrier) this.markUpdatedCarrier(carrier);
|
|
614
|
+
else this.assertNotInFlush();
|
|
615
|
+
}
|
|
616
|
+
markUpdatedKeys(keys) {
|
|
617
|
+
for (let i = 0; i < keys.length; i++) this.markUpdatedKey(keys[i]);
|
|
620
618
|
}
|
|
621
619
|
markAllUpdated() {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
);
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
this.keyHandlers.forEach((_handlers, key) => {
|
|
629
|
-
this.updatedKeys.add(key);
|
|
620
|
+
this.assertNotInFlush();
|
|
621
|
+
if (this.subscribedCarriers.size === 0) return;
|
|
622
|
+
this.subscribedCarriers.forEach((carrier) => {
|
|
623
|
+
if (carrier.dirty) return;
|
|
624
|
+
carrier.dirty = true;
|
|
625
|
+
this.dirtyCarriers.push(carrier);
|
|
630
626
|
});
|
|
631
|
-
|
|
632
|
-
this.deliverImmediate();
|
|
633
|
-
return;
|
|
634
|
-
}
|
|
635
|
-
if (this.deliveryMode === "after_flush") {
|
|
636
|
-
this.isAfterFlushDeliveryScheduled = true;
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
if (this.isFlushEnqueued) return;
|
|
640
|
-
this.isFlushEnqueued = true;
|
|
641
|
-
this.dequeueFlush = this.queue.enqueue(this.onFlush);
|
|
642
|
-
}
|
|
643
|
-
deliverImmediate() {
|
|
644
|
-
this.runSinglePass();
|
|
645
|
-
}
|
|
646
|
-
/**
|
|
647
|
-
* Notifies all handlers for a key. The overwhelmingly common case is a
|
|
648
|
-
* single subscriber per key (one component per pk), handled without any
|
|
649
|
-
* allocation. For multiple subscribers a snapshot is taken so a handler may
|
|
650
|
-
* (un)subscribe during iteration without corrupting the walk.
|
|
651
|
-
*/
|
|
652
|
-
notifyHandlers(handlers) {
|
|
653
|
-
if (handlers.size === 1) {
|
|
654
|
-
const only = handlers.values().next().value;
|
|
655
|
-
if (only) only();
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
const snapshot = [];
|
|
659
|
-
handlers.forEach((h) => snapshot.push(h));
|
|
660
|
-
for (let i = 0; i < snapshot.length; i++) {
|
|
661
|
-
const handler = snapshot[i];
|
|
662
|
-
if (handlers.has(handler)) handler();
|
|
663
|
-
}
|
|
627
|
+
this.scheduleFlush();
|
|
664
628
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
* Re-entrant updates during delivery are forbidden and will throw in markUpdatedKeys().
|
|
668
|
-
*/
|
|
669
|
-
runSinglePass() {
|
|
670
|
-
if (this.updatedKeys.size === 0) return;
|
|
671
|
-
const flushingKeys = this.updatedKeys;
|
|
672
|
-
this.updatedKeys = /* @__PURE__ */ new Set();
|
|
673
|
-
if (this.keyHandlers.size === 0 || flushingKeys.size === 0) return;
|
|
674
|
-
if (flushingKeys.size * 2 < this.keyHandlers.size) {
|
|
675
|
-
flushingKeys.forEach((key) => {
|
|
676
|
-
const handlers = this.keyHandlers.get(key);
|
|
677
|
-
if (!handlers || handlers.size === 0) return;
|
|
678
|
-
this.notifyHandlers(handlers);
|
|
679
|
-
});
|
|
680
|
-
} else {
|
|
681
|
-
this.keyHandlers.forEach((handlers, key) => {
|
|
682
|
-
if (!flushingKeys.has(key)) return;
|
|
683
|
-
if (!handlers || handlers.size === 0) return;
|
|
684
|
-
this.notifyHandlers(handlers);
|
|
685
|
-
});
|
|
686
|
-
}
|
|
629
|
+
hasSubscriptions() {
|
|
630
|
+
return this.subscribedCarriers.size > 0;
|
|
687
631
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
this.dequeueFlush();
|
|
691
|
-
this.dequeueFlush = void 0;
|
|
692
|
-
}
|
|
693
|
-
if (this.unsubscribeAfterFlushDelivery) {
|
|
694
|
-
this.unsubscribeAfterFlushDelivery();
|
|
695
|
-
this.unsubscribeAfterFlushDelivery = void 0;
|
|
696
|
-
}
|
|
697
|
-
this.keyHandlers.forEach((handlers) => handlers.clear());
|
|
698
|
-
this.keyHandlers.clear();
|
|
699
|
-
this.updatedKeys.clear();
|
|
632
|
+
getHandlerCount(key) {
|
|
633
|
+
return this.resolver.findCarrier(key)?.subscribers?.size ?? 0;
|
|
700
634
|
}
|
|
701
|
-
/**
|
|
702
|
-
* Get performance metrics for monitoring and debugging
|
|
703
|
-
*/
|
|
704
635
|
getMetrics() {
|
|
705
636
|
let totalHandlers = 0;
|
|
706
|
-
this.
|
|
707
|
-
totalHandlers +=
|
|
637
|
+
this.subscribedCarriers.forEach((carrier) => {
|
|
638
|
+
totalHandlers += carrier.subscribers?.size ?? 0;
|
|
708
639
|
});
|
|
640
|
+
const totalKeys = this.subscribedCarriers.size;
|
|
709
641
|
return {
|
|
710
|
-
totalKeys
|
|
642
|
+
totalKeys,
|
|
711
643
|
totalHandlers,
|
|
712
|
-
averageHandlersPerKey:
|
|
713
|
-
queueLength: this.
|
|
644
|
+
averageHandlersPerKey: totalKeys > 0 ? totalHandlers / totalKeys : 0,
|
|
645
|
+
queueLength: this.dirtyCarriers.length
|
|
714
646
|
};
|
|
715
647
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
648
|
+
destroy() {
|
|
649
|
+
if (this.isFlushEnqueued) {
|
|
650
|
+
this.queue.cancelTask(this.onFlush);
|
|
651
|
+
this.isFlushEnqueued = false;
|
|
652
|
+
}
|
|
653
|
+
this.subscribedCarriers.forEach((carrier) => {
|
|
654
|
+
carrier.subscribers?.clear();
|
|
655
|
+
carrier.subscribers = void 0;
|
|
656
|
+
});
|
|
657
|
+
this.subscribedCarriers.clear();
|
|
658
|
+
for (let i = 0; i < this.dirtyCarriers.length; i++) {
|
|
659
|
+
this.dirtyCarriers[i].dirty = false;
|
|
660
|
+
}
|
|
661
|
+
this.dirtyCarriers = [];
|
|
662
|
+
this.isFlushEnqueued = false;
|
|
727
663
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
664
|
+
assertNotInFlush() {
|
|
665
|
+
if (this.queue.isInFlush) {
|
|
666
|
+
throw new Error(
|
|
667
|
+
"OIMCarrierKeyedEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
|
|
668
|
+
);
|
|
733
669
|
}
|
|
734
|
-
handlers.add(handler);
|
|
735
|
-
return () => {
|
|
736
|
-
this.unsubscribeFromKey(key, handler);
|
|
737
|
-
};
|
|
738
670
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
671
|
+
scheduleFlush() {
|
|
672
|
+
if (this.isFlushEnqueued) return;
|
|
673
|
+
this.isFlushEnqueued = true;
|
|
674
|
+
this.queue.enqueueTask(this.onFlush);
|
|
675
|
+
}
|
|
676
|
+
notify(carrier) {
|
|
677
|
+
const subscribers = carrier.subscribers;
|
|
678
|
+
if (!subscribers || subscribers.size === 0) return;
|
|
679
|
+
if (subscribers.size === 1) {
|
|
680
|
+
const only = subscribers.values().next().value;
|
|
681
|
+
if (only) only();
|
|
682
|
+
return;
|
|
746
683
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}
|
|
753
|
-
this.isFlushEnqueued = false;
|
|
684
|
+
const snapshot = [];
|
|
685
|
+
subscribers.forEach((h) => snapshot.push(h));
|
|
686
|
+
for (let i = 0; i < snapshot.length; i++) {
|
|
687
|
+
const handler = snapshot[i];
|
|
688
|
+
if (subscribers.has(handler)) handler();
|
|
754
689
|
}
|
|
755
690
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
this.keyHandlers.set(key, handlers);
|
|
763
|
-
}
|
|
764
|
-
if (!handlers.has(handler)) {
|
|
765
|
-
handlers.add(handler);
|
|
766
|
-
subscribedKeys.push(key);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
return () => {
|
|
770
|
-
for (const key of subscribedKeys) {
|
|
771
|
-
this.unsubscribeFromKey(key, handler);
|
|
772
|
-
}
|
|
773
|
-
};
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
// src/core/OIMKeyedCarrierResolver.ts
|
|
694
|
+
var OIMKeyedCarrierResolver = class {
|
|
695
|
+
constructor() {
|
|
696
|
+
this.carriers = /* @__PURE__ */ new Map();
|
|
774
697
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
698
|
+
getOrReserveCarrier(key) {
|
|
699
|
+
let carrier = this.carriers.get(key);
|
|
700
|
+
if (!carrier) {
|
|
701
|
+
carrier = { key };
|
|
702
|
+
this.carriers.set(key, carrier);
|
|
778
703
|
}
|
|
704
|
+
return carrier;
|
|
705
|
+
}
|
|
706
|
+
findCarrier(key) {
|
|
707
|
+
return this.carriers.get(key);
|
|
708
|
+
}
|
|
709
|
+
onCarrierEmptied(carrier) {
|
|
710
|
+
this.carriers.delete(carrier.key);
|
|
779
711
|
}
|
|
780
712
|
};
|
|
781
713
|
|
|
782
714
|
// src/abstract/OIMReactiveIndex.ts
|
|
783
715
|
var OIMReactiveIndex = class {
|
|
784
716
|
constructor(queue, createIndex) {
|
|
785
|
-
this.updateEmitter = new
|
|
717
|
+
this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierResolver());
|
|
786
718
|
this.index = createIndex(this.updateEmitter);
|
|
787
719
|
}
|
|
788
720
|
getEntitiesByKey(key) {
|
|
@@ -1113,192 +1045,45 @@ var OIMCollection = class {
|
|
|
1113
1045
|
removeManyByPks(pks) {
|
|
1114
1046
|
if (pks.length === 0) return;
|
|
1115
1047
|
this.store.removeManyByPks(pks);
|
|
1116
|
-
this.emitter.emit(0 /* UPDATE */, { pks });
|
|
1117
|
-
}
|
|
1118
|
-
clear() {
|
|
1119
|
-
this.store.clear();
|
|
1120
|
-
this.emitter.emit(0 /* UPDATE */, { pks: [] });
|
|
1121
|
-
}
|
|
1122
|
-
countAll() {
|
|
1123
|
-
return this.store.countAll();
|
|
1124
|
-
}
|
|
1125
|
-
getAll() {
|
|
1126
|
-
return this.store.getAll();
|
|
1127
|
-
}
|
|
1128
|
-
getAllPks() {
|
|
1129
|
-
return this.store.getAllPks();
|
|
1130
|
-
}
|
|
1131
|
-
upsertOneWithoutNotifications(entity) {
|
|
1132
|
-
const pk = this.selectPk(entity);
|
|
1133
|
-
return this.upsertOneWithoutNotificationsByPk(pk, entity);
|
|
1134
|
-
}
|
|
1135
|
-
upsertOneWithoutNotificationsByPk(pk, entity) {
|
|
1136
|
-
if (!pk) {
|
|
1137
|
-
throw new Error(
|
|
1138
|
-
`[OIMCollection]: PK is required to upsert an entity ${JSON.stringify(entity)}`
|
|
1139
|
-
);
|
|
1140
|
-
}
|
|
1141
|
-
const existingEntity = this.store.getOneByPk(pk);
|
|
1142
|
-
if (existingEntity) {
|
|
1143
|
-
return this.store.setOneByPk(
|
|
1144
|
-
pk,
|
|
1145
|
-
this.updateEntity(entity, existingEntity)
|
|
1146
|
-
);
|
|
1147
|
-
} else {
|
|
1148
|
-
return this.store.setOneByPk(pk, entity);
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
destroy() {
|
|
1152
|
-
this.store.destroy();
|
|
1153
|
-
this.emitter.offAll();
|
|
1154
|
-
}
|
|
1155
|
-
};
|
|
1156
|
-
|
|
1157
|
-
// src/core/OIMCarrierKeyedEmitter.ts
|
|
1158
|
-
var OIMCarrierKeyedEmitter = class {
|
|
1159
|
-
constructor(queue, resolver) {
|
|
1160
|
-
// Carriers with at least one subscriber — for markAllUpdated / metrics.
|
|
1161
|
-
this.subscribedCarriers = /* @__PURE__ */ new Set();
|
|
1162
|
-
this.dirtyCarriers = /* @__PURE__ */ new Set();
|
|
1163
|
-
this.isFlushEnqueued = false;
|
|
1164
|
-
this.onFlush = () => {
|
|
1165
|
-
this.isFlushEnqueued = false;
|
|
1166
|
-
this.dequeueFlush = void 0;
|
|
1167
|
-
if (this.dirtyCarriers.size === 0) return;
|
|
1168
|
-
const flushing = this.dirtyCarriers;
|
|
1169
|
-
this.dirtyCarriers = /* @__PURE__ */ new Set();
|
|
1170
|
-
flushing.forEach((carrier) => this.notify(carrier));
|
|
1171
|
-
};
|
|
1172
|
-
this.queue = queue;
|
|
1173
|
-
this.resolver = resolver;
|
|
1174
|
-
}
|
|
1175
|
-
subscribeOnKey(key, handler) {
|
|
1176
|
-
const carrier = this.resolver.getOrReserveCarrier(key);
|
|
1177
|
-
this.addHandler(carrier, handler);
|
|
1178
|
-
return () => this.removeHandler(carrier, handler);
|
|
1179
|
-
}
|
|
1180
|
-
subscribeOnKeys(keys, handler) {
|
|
1181
|
-
const carriers = [];
|
|
1182
|
-
for (let i = 0; i < keys.length; i++) {
|
|
1183
|
-
const carrier = this.resolver.getOrReserveCarrier(keys[i]);
|
|
1184
|
-
this.addHandler(carrier, handler);
|
|
1185
|
-
carriers.push(carrier);
|
|
1186
|
-
}
|
|
1187
|
-
return () => {
|
|
1188
|
-
for (let i = 0; i < carriers.length; i++) {
|
|
1189
|
-
this.removeHandler(carriers[i], handler);
|
|
1190
|
-
}
|
|
1191
|
-
};
|
|
1192
|
-
}
|
|
1193
|
-
unsubscribeFromKey(key, handler) {
|
|
1194
|
-
const carrier = this.resolver.findCarrier(key);
|
|
1195
|
-
if (carrier) this.removeHandler(carrier, handler);
|
|
1196
|
-
}
|
|
1197
|
-
unsubscribeFromKeys(keys, handler) {
|
|
1198
|
-
for (let i = 0; i < keys.length; i++) {
|
|
1199
|
-
this.unsubscribeFromKey(keys[i], handler);
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
addHandler(carrier, handler) {
|
|
1203
|
-
let subscribers = carrier.subscribers;
|
|
1204
|
-
if (!subscribers) {
|
|
1205
|
-
subscribers = /* @__PURE__ */ new Set();
|
|
1206
|
-
carrier.subscribers = subscribers;
|
|
1207
|
-
}
|
|
1208
|
-
subscribers.add(handler);
|
|
1209
|
-
this.subscribedCarriers.add(carrier);
|
|
1210
|
-
}
|
|
1211
|
-
removeHandler(carrier, handler) {
|
|
1212
|
-
const subscribers = carrier.subscribers;
|
|
1213
|
-
if (!subscribers) return;
|
|
1214
|
-
subscribers.delete(handler);
|
|
1215
|
-
if (subscribers.size === 0) {
|
|
1216
|
-
this.subscribedCarriers.delete(carrier);
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
/** Fast path: the writer already holds the carrier. */
|
|
1220
|
-
markUpdatedCarrier(carrier) {
|
|
1221
|
-
this.assertNotInFlush();
|
|
1222
|
-
const subscribers = carrier.subscribers;
|
|
1223
|
-
if (!subscribers || subscribers.size === 0) return;
|
|
1224
|
-
this.dirtyCarriers.add(carrier);
|
|
1225
|
-
this.scheduleFlush();
|
|
1226
|
-
}
|
|
1227
|
-
markUpdatedKey(key) {
|
|
1228
|
-
const carrier = this.resolver.findCarrier(key);
|
|
1229
|
-
if (carrier) this.markUpdatedCarrier(carrier);
|
|
1230
|
-
else this.assertNotInFlush();
|
|
1231
|
-
}
|
|
1232
|
-
markUpdatedKeys(keys) {
|
|
1233
|
-
for (let i = 0; i < keys.length; i++) this.markUpdatedKey(keys[i]);
|
|
1234
|
-
}
|
|
1235
|
-
markAllUpdated() {
|
|
1236
|
-
this.assertNotInFlush();
|
|
1237
|
-
if (this.subscribedCarriers.size === 0) return;
|
|
1238
|
-
this.subscribedCarriers.forEach(
|
|
1239
|
-
(carrier) => this.dirtyCarriers.add(carrier)
|
|
1240
|
-
);
|
|
1241
|
-
this.scheduleFlush();
|
|
1048
|
+
this.emitter.emit(0 /* UPDATE */, { pks });
|
|
1242
1049
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1050
|
+
clear() {
|
|
1051
|
+
this.store.clear();
|
|
1052
|
+
this.emitter.emit(0 /* UPDATE */, { pks: [] });
|
|
1245
1053
|
}
|
|
1246
|
-
|
|
1247
|
-
return this.
|
|
1054
|
+
countAll() {
|
|
1055
|
+
return this.store.countAll();
|
|
1248
1056
|
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
this.subscribedCarriers.forEach((carrier) => {
|
|
1252
|
-
totalHandlers += carrier.subscribers?.size ?? 0;
|
|
1253
|
-
});
|
|
1254
|
-
const totalKeys = this.subscribedCarriers.size;
|
|
1255
|
-
return {
|
|
1256
|
-
totalKeys,
|
|
1257
|
-
totalHandlers,
|
|
1258
|
-
averageHandlersPerKey: totalKeys > 0 ? totalHandlers / totalKeys : 0,
|
|
1259
|
-
queueLength: this.dirtyCarriers.size
|
|
1260
|
-
};
|
|
1057
|
+
getAll() {
|
|
1058
|
+
return this.store.getAll();
|
|
1261
1059
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
this.dequeueFlush();
|
|
1265
|
-
this.dequeueFlush = void 0;
|
|
1266
|
-
}
|
|
1267
|
-
this.subscribedCarriers.forEach((carrier) => {
|
|
1268
|
-
carrier.subscribers?.clear();
|
|
1269
|
-
carrier.subscribers = void 0;
|
|
1270
|
-
});
|
|
1271
|
-
this.subscribedCarriers.clear();
|
|
1272
|
-
this.dirtyCarriers.clear();
|
|
1273
|
-
this.isFlushEnqueued = false;
|
|
1060
|
+
getAllPks() {
|
|
1061
|
+
return this.store.getAllPks();
|
|
1274
1062
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1063
|
+
upsertOneWithoutNotifications(entity) {
|
|
1064
|
+
const pk = this.selectPk(entity);
|
|
1065
|
+
return this.upsertOneWithoutNotificationsByPk(pk, entity);
|
|
1066
|
+
}
|
|
1067
|
+
upsertOneWithoutNotificationsByPk(pk, entity) {
|
|
1068
|
+
if (!pk) {
|
|
1277
1069
|
throw new Error(
|
|
1278
|
-
|
|
1070
|
+
`[OIMCollection]: PK is required to upsert an entity ${JSON.stringify(entity)}`
|
|
1279
1071
|
);
|
|
1280
1072
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
if (!subscribers || subscribers.size === 0) return;
|
|
1290
|
-
if (subscribers.size === 1) {
|
|
1291
|
-
const only = subscribers.values().next().value;
|
|
1292
|
-
if (only) only();
|
|
1293
|
-
return;
|
|
1294
|
-
}
|
|
1295
|
-
const snapshot = [];
|
|
1296
|
-
subscribers.forEach((h) => snapshot.push(h));
|
|
1297
|
-
for (let i = 0; i < snapshot.length; i++) {
|
|
1298
|
-
const handler = snapshot[i];
|
|
1299
|
-
if (subscribers.has(handler)) handler();
|
|
1073
|
+
const existingEntity = this.store.getOneByPk(pk);
|
|
1074
|
+
if (existingEntity) {
|
|
1075
|
+
return this.store.setOneByPk(
|
|
1076
|
+
pk,
|
|
1077
|
+
this.updateEntity(entity, existingEntity)
|
|
1078
|
+
);
|
|
1079
|
+
} else {
|
|
1080
|
+
return this.store.setOneByPk(pk, entity);
|
|
1300
1081
|
}
|
|
1301
1082
|
}
|
|
1083
|
+
destroy() {
|
|
1084
|
+
this.store.destroy();
|
|
1085
|
+
this.emitter.offAll();
|
|
1086
|
+
}
|
|
1302
1087
|
};
|
|
1303
1088
|
|
|
1304
1089
|
// src/core/OIMReactiveCollection.ts
|
|
@@ -1312,7 +1097,6 @@ var OIMReactiveCollection = class extends OIMCollection {
|
|
|
1312
1097
|
this.runAnyUpdate = () => {
|
|
1313
1098
|
if (!this.isAnyUpdateScheduled) return;
|
|
1314
1099
|
this.isAnyUpdateScheduled = false;
|
|
1315
|
-
this.dequeueAnyUpdate = void 0;
|
|
1316
1100
|
const pks = this.isAnyUpdateClearPending ? [] : Array.from(this.pendingAnyUpdatePks);
|
|
1317
1101
|
this.pendingAnyUpdatePks.clear();
|
|
1318
1102
|
this.isAnyUpdateClearPending = false;
|
|
@@ -1452,13 +1236,13 @@ var OIMReactiveCollection = class extends OIMCollection {
|
|
|
1452
1236
|
ensureAnyUpdateEnqueued() {
|
|
1453
1237
|
if (this.isAnyUpdateScheduled) return;
|
|
1454
1238
|
this.isAnyUpdateScheduled = true;
|
|
1455
|
-
this.
|
|
1239
|
+
this.queue.enqueueTask(this.runAnyUpdate);
|
|
1456
1240
|
}
|
|
1457
1241
|
destroy() {
|
|
1458
1242
|
this.updateEmitter.destroy();
|
|
1459
|
-
if (this.
|
|
1460
|
-
this.
|
|
1461
|
-
this.
|
|
1243
|
+
if (this.isAnyUpdateScheduled) {
|
|
1244
|
+
this.queue.cancelTask(this.runAnyUpdate);
|
|
1245
|
+
this.isAnyUpdateScheduled = false;
|
|
1462
1246
|
}
|
|
1463
1247
|
this.anyUpdateHandlers.clear();
|
|
1464
1248
|
this.pendingAnyUpdatePks.clear();
|
|
@@ -1477,109 +1261,362 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
|
|
|
1477
1261
|
}
|
|
1478
1262
|
return;
|
|
1479
1263
|
}
|
|
1480
|
-
if (this.store.getOneByKey(key) !== void 0) {
|
|
1481
|
-
this.store.removeOneByKey(key);
|
|
1482
|
-
this.emitUpdate([key]);
|
|
1264
|
+
if (this.store.getOneByKey(key) !== void 0) {
|
|
1265
|
+
this.store.removeOneByKey(key);
|
|
1266
|
+
this.emitUpdate([key]);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
pushSlot(key, slot) {
|
|
1270
|
+
const list = this.getOrCreateList(key);
|
|
1271
|
+
const index = list.length;
|
|
1272
|
+
list.push(slot);
|
|
1273
|
+
this.emitUpdate([key]);
|
|
1274
|
+
return index;
|
|
1275
|
+
}
|
|
1276
|
+
insertSlotAt(key, index, slot) {
|
|
1277
|
+
const list = this.getOrCreateList(key);
|
|
1278
|
+
const safeIndex = Math.max(0, Math.min(index, list.length));
|
|
1279
|
+
list.splice(safeIndex, 0, slot);
|
|
1280
|
+
this.emitUpdate([key]);
|
|
1281
|
+
return safeIndex;
|
|
1282
|
+
}
|
|
1283
|
+
removeAt(key, index) {
|
|
1284
|
+
const list = this.store.getOneByKey(key);
|
|
1285
|
+
if (!list) return void 0;
|
|
1286
|
+
if (index < 0 || index >= list.length) return void 0;
|
|
1287
|
+
const [removed] = list.splice(index, 1);
|
|
1288
|
+
if (list.length === 0) {
|
|
1289
|
+
this.store.removeOneByKey(key);
|
|
1290
|
+
}
|
|
1291
|
+
this.emitUpdate([key]);
|
|
1292
|
+
return removed;
|
|
1293
|
+
}
|
|
1294
|
+
move(key, fromIndex, toIndex) {
|
|
1295
|
+
const list = this.store.getOneByKey(key);
|
|
1296
|
+
if (!list) return void 0;
|
|
1297
|
+
if (fromIndex < 0 || fromIndex >= list.length) return void 0;
|
|
1298
|
+
if (toIndex < 0 || toIndex >= list.length) return void 0;
|
|
1299
|
+
if (fromIndex === toIndex) return list[fromIndex];
|
|
1300
|
+
const [slot] = list.splice(fromIndex, 1);
|
|
1301
|
+
if (slot === void 0) return void 0;
|
|
1302
|
+
list.splice(toIndex, 0, slot);
|
|
1303
|
+
this.emitUpdate([key]);
|
|
1304
|
+
return slot;
|
|
1305
|
+
}
|
|
1306
|
+
resetSlots(key, slots) {
|
|
1307
|
+
if (slots.length === 0) {
|
|
1308
|
+
if (this.store.getOneByKey(key) !== void 0) {
|
|
1309
|
+
this.store.removeOneByKey(key);
|
|
1310
|
+
this.emitUpdate([key]);
|
|
1311
|
+
}
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
this.store.setOneByKey(key, slots.slice());
|
|
1315
|
+
this.emitUpdate([key]);
|
|
1316
|
+
}
|
|
1317
|
+
getOrCreateList(key) {
|
|
1318
|
+
let list = this.store.getOneByKey(key);
|
|
1319
|
+
if (!list) {
|
|
1320
|
+
list = [];
|
|
1321
|
+
this.store.setOneByKey(key, list);
|
|
1322
|
+
}
|
|
1323
|
+
return list;
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
// src/modules/wrapper/index/OIMCollectionIndexManualOrderedArrayBased.ts
|
|
1328
|
+
var OIMCollectionIndexManualOrderedArrayBased = class extends OIMIndexManualOrderedArrayBased {
|
|
1329
|
+
constructor(opts) {
|
|
1330
|
+
super();
|
|
1331
|
+
this.resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getSlotByPk(pk) : opts.resolveSlot;
|
|
1332
|
+
}
|
|
1333
|
+
push(key, pk) {
|
|
1334
|
+
return this.pushSlot(key, this.resolveRequiredSlot(pk));
|
|
1335
|
+
}
|
|
1336
|
+
insertAt(key, index, pk) {
|
|
1337
|
+
return this.insertSlotAt(key, index, this.resolveRequiredSlot(pk));
|
|
1338
|
+
}
|
|
1339
|
+
reset(key, pks) {
|
|
1340
|
+
this.resetSlots(key, this.resolveSlots(pks));
|
|
1341
|
+
}
|
|
1342
|
+
getEntitiesByKey(key) {
|
|
1343
|
+
return super.getEntitiesByKey(key);
|
|
1344
|
+
}
|
|
1345
|
+
getEntitiesByKeys(keys) {
|
|
1346
|
+
return super.getEntitiesByKeys(keys);
|
|
1347
|
+
}
|
|
1348
|
+
resolvePk(pk) {
|
|
1349
|
+
return this.resolveRequiredSlot(pk);
|
|
1350
|
+
}
|
|
1351
|
+
resolveSlots(pks) {
|
|
1352
|
+
const slots = [];
|
|
1353
|
+
slots.length = pks.length;
|
|
1354
|
+
for (let i = 0; i < pks.length; i++) {
|
|
1355
|
+
slots[i] = this.resolveRequiredSlot(pks[i]);
|
|
1356
|
+
}
|
|
1357
|
+
return slots;
|
|
1358
|
+
}
|
|
1359
|
+
resolveRequiredSlot(pk) {
|
|
1360
|
+
const slot = this.resolveSlot(pk);
|
|
1361
|
+
if (!slot) {
|
|
1362
|
+
throw new Error(
|
|
1363
|
+
`[OIMCollectionIndexManualOrderedArrayBased]: Unable to resolve slot for PK "${String(pk)}".`
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
return slot;
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
// src/enums/EOIMEventQueueEventType.ts
|
|
1371
|
+
var EOIMEventQueueEventType = /* @__PURE__ */ ((EOIMEventQueueEventType2) => {
|
|
1372
|
+
EOIMEventQueueEventType2[EOIMEventQueueEventType2["AFTER_FLUSH"] = 0] = "AFTER_FLUSH";
|
|
1373
|
+
EOIMEventQueueEventType2[EOIMEventQueueEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
|
|
1374
|
+
return EOIMEventQueueEventType2;
|
|
1375
|
+
})(EOIMEventQueueEventType || {});
|
|
1376
|
+
|
|
1377
|
+
// src/core/OIMUpdateEventEmitter.ts
|
|
1378
|
+
var OIMUpdateEventEmitter = class {
|
|
1379
|
+
constructor(queue, deliveryMode = "queue") {
|
|
1380
|
+
this.keyHandlers = /* @__PURE__ */ new Map();
|
|
1381
|
+
this.isFlushEnqueued = false;
|
|
1382
|
+
this.updatedKeys = /* @__PURE__ */ new Set();
|
|
1383
|
+
this.isAfterFlushDeliveryScheduled = false;
|
|
1384
|
+
// Stable handler reference to avoid allocating a new closure on every HAS_CHANGES.
|
|
1385
|
+
this.onFlush = () => {
|
|
1386
|
+
this.isFlushEnqueued = false;
|
|
1387
|
+
if (this.keyHandlers.size === 0) {
|
|
1388
|
+
this.updatedKeys.clear();
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
this.runSinglePass();
|
|
1392
|
+
};
|
|
1393
|
+
this.onAfterFlush = () => {
|
|
1394
|
+
if (!this.isAfterFlushDeliveryScheduled) return;
|
|
1395
|
+
this.isAfterFlushDeliveryScheduled = false;
|
|
1396
|
+
this.deliverImmediate();
|
|
1397
|
+
};
|
|
1398
|
+
this.queue = queue;
|
|
1399
|
+
this.deliveryMode = deliveryMode;
|
|
1400
|
+
if (this.deliveryMode === "after_flush") {
|
|
1401
|
+
this.unsubscribeAfterFlushDelivery = this.queue.emitter.on(
|
|
1402
|
+
0 /* AFTER_FLUSH */,
|
|
1403
|
+
this.onAfterFlush
|
|
1404
|
+
);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Mark keys as updated and schedule delivery.
|
|
1409
|
+
*
|
|
1410
|
+
* If there are no subscriptions, this is a no-op and does not allocate or retain keys.
|
|
1411
|
+
*/
|
|
1412
|
+
markUpdatedKeys(keys) {
|
|
1413
|
+
if (keys.length === 0) return;
|
|
1414
|
+
if (this.deliveryMode === "queue" && this.queue.isInFlush) {
|
|
1415
|
+
throw new Error(
|
|
1416
|
+
"OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
if (this.keyHandlers.size === 0) return;
|
|
1420
|
+
let didAddAny = false;
|
|
1421
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1422
|
+
const key = keys[i];
|
|
1423
|
+
const handlers = this.keyHandlers.get(key);
|
|
1424
|
+
if (!handlers || handlers.size === 0) continue;
|
|
1425
|
+
this.updatedKeys.add(key);
|
|
1426
|
+
didAddAny = true;
|
|
1427
|
+
}
|
|
1428
|
+
if (!didAddAny) return;
|
|
1429
|
+
if (this.deliveryMode === "immediate") {
|
|
1430
|
+
this.deliverImmediate();
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
if (this.deliveryMode === "after_flush") {
|
|
1434
|
+
this.isAfterFlushDeliveryScheduled = true;
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
if (this.isFlushEnqueued) return;
|
|
1438
|
+
this.isFlushEnqueued = true;
|
|
1439
|
+
this.queue.enqueueTask(this.onFlush);
|
|
1440
|
+
}
|
|
1441
|
+
markUpdatedKey(key) {
|
|
1442
|
+
if (this.deliveryMode === "queue" && this.queue.isInFlush) {
|
|
1443
|
+
throw new Error(
|
|
1444
|
+
"OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
if (this.keyHandlers.size === 0) return;
|
|
1448
|
+
const handlers = this.keyHandlers.get(key);
|
|
1449
|
+
if (!handlers || handlers.size === 0) return;
|
|
1450
|
+
this.updatedKeys.add(key);
|
|
1451
|
+
if (this.deliveryMode === "immediate") {
|
|
1452
|
+
this.deliverImmediate();
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
if (this.deliveryMode === "after_flush") {
|
|
1456
|
+
this.isAfterFlushDeliveryScheduled = true;
|
|
1457
|
+
return;
|
|
1483
1458
|
}
|
|
1459
|
+
if (this.isFlushEnqueued) return;
|
|
1460
|
+
this.isFlushEnqueued = true;
|
|
1461
|
+
this.queue.enqueueTask(this.onFlush);
|
|
1484
1462
|
}
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
return index;
|
|
1491
|
-
}
|
|
1492
|
-
insertSlotAt(key, index, slot) {
|
|
1493
|
-
const list = this.getOrCreateList(key);
|
|
1494
|
-
const safeIndex = Math.max(0, Math.min(index, list.length));
|
|
1495
|
-
list.splice(safeIndex, 0, slot);
|
|
1496
|
-
this.emitUpdate([key]);
|
|
1497
|
-
return safeIndex;
|
|
1498
|
-
}
|
|
1499
|
-
removeAt(key, index) {
|
|
1500
|
-
const list = this.store.getOneByKey(key);
|
|
1501
|
-
if (!list) return void 0;
|
|
1502
|
-
if (index < 0 || index >= list.length) return void 0;
|
|
1503
|
-
const [removed] = list.splice(index, 1);
|
|
1504
|
-
if (list.length === 0) {
|
|
1505
|
-
this.store.removeOneByKey(key);
|
|
1463
|
+
markAllUpdated() {
|
|
1464
|
+
if (this.deliveryMode === "queue" && this.queue.isInFlush) {
|
|
1465
|
+
throw new Error(
|
|
1466
|
+
"OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
|
|
1467
|
+
);
|
|
1506
1468
|
}
|
|
1507
|
-
this.
|
|
1508
|
-
|
|
1469
|
+
if (this.keyHandlers.size === 0) return;
|
|
1470
|
+
this.keyHandlers.forEach((_handlers, key) => {
|
|
1471
|
+
this.updatedKeys.add(key);
|
|
1472
|
+
});
|
|
1473
|
+
if (this.deliveryMode === "immediate") {
|
|
1474
|
+
this.deliverImmediate();
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
if (this.deliveryMode === "after_flush") {
|
|
1478
|
+
this.isAfterFlushDeliveryScheduled = true;
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
if (this.isFlushEnqueued) return;
|
|
1482
|
+
this.isFlushEnqueued = true;
|
|
1483
|
+
this.queue.enqueueTask(this.onFlush);
|
|
1509
1484
|
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
if (!list) return void 0;
|
|
1513
|
-
if (fromIndex < 0 || fromIndex >= list.length) return void 0;
|
|
1514
|
-
if (toIndex < 0 || toIndex >= list.length) return void 0;
|
|
1515
|
-
if (fromIndex === toIndex) return list[fromIndex];
|
|
1516
|
-
const [slot] = list.splice(fromIndex, 1);
|
|
1517
|
-
if (slot === void 0) return void 0;
|
|
1518
|
-
list.splice(toIndex, 0, slot);
|
|
1519
|
-
this.emitUpdate([key]);
|
|
1520
|
-
return slot;
|
|
1485
|
+
deliverImmediate() {
|
|
1486
|
+
this.runSinglePass();
|
|
1521
1487
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1488
|
+
/**
|
|
1489
|
+
* Notifies all handlers for a key. The overwhelmingly common case is a
|
|
1490
|
+
* single subscriber per key (one component per pk), handled without any
|
|
1491
|
+
* allocation. For multiple subscribers a snapshot is taken so a handler may
|
|
1492
|
+
* (un)subscribe during iteration without corrupting the walk.
|
|
1493
|
+
*/
|
|
1494
|
+
notifyHandlers(handlers) {
|
|
1495
|
+
if (handlers.size === 1) {
|
|
1496
|
+
const only = handlers.values().next().value;
|
|
1497
|
+
if (only) only();
|
|
1528
1498
|
return;
|
|
1529
1499
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
if (!list) {
|
|
1536
|
-
list = [];
|
|
1537
|
-
this.store.setOneByKey(key, list);
|
|
1500
|
+
const snapshot = [];
|
|
1501
|
+
handlers.forEach((h) => snapshot.push(h));
|
|
1502
|
+
for (let i = 0; i < snapshot.length; i++) {
|
|
1503
|
+
const handler = snapshot[i];
|
|
1504
|
+
if (handlers.has(handler)) handler();
|
|
1538
1505
|
}
|
|
1539
|
-
return list;
|
|
1540
1506
|
}
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1507
|
+
/**
|
|
1508
|
+
* Deliver updated keys and notify handlers once.
|
|
1509
|
+
* Re-entrant updates during delivery are forbidden and will throw in markUpdatedKeys().
|
|
1510
|
+
*/
|
|
1511
|
+
runSinglePass() {
|
|
1512
|
+
if (this.updatedKeys.size === 0) return;
|
|
1513
|
+
const flushingKeys = this.updatedKeys;
|
|
1514
|
+
this.updatedKeys = /* @__PURE__ */ new Set();
|
|
1515
|
+
if (this.keyHandlers.size === 0 || flushingKeys.size === 0) return;
|
|
1516
|
+
if (flushingKeys.size * 2 < this.keyHandlers.size) {
|
|
1517
|
+
flushingKeys.forEach((key) => {
|
|
1518
|
+
const handlers = this.keyHandlers.get(key);
|
|
1519
|
+
if (!handlers || handlers.size === 0) return;
|
|
1520
|
+
this.notifyHandlers(handlers);
|
|
1521
|
+
});
|
|
1522
|
+
} else {
|
|
1523
|
+
this.keyHandlers.forEach((handlers, key) => {
|
|
1524
|
+
if (!flushingKeys.has(key)) return;
|
|
1525
|
+
if (!handlers || handlers.size === 0) return;
|
|
1526
|
+
this.notifyHandlers(handlers);
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1548
1529
|
}
|
|
1549
|
-
|
|
1550
|
-
|
|
1530
|
+
destroy() {
|
|
1531
|
+
if (this.isFlushEnqueued) {
|
|
1532
|
+
this.queue.cancelTask(this.onFlush);
|
|
1533
|
+
this.isFlushEnqueued = false;
|
|
1534
|
+
}
|
|
1535
|
+
if (this.unsubscribeAfterFlushDelivery) {
|
|
1536
|
+
this.unsubscribeAfterFlushDelivery();
|
|
1537
|
+
this.unsubscribeAfterFlushDelivery = void 0;
|
|
1538
|
+
}
|
|
1539
|
+
this.keyHandlers.forEach((handlers) => handlers.clear());
|
|
1540
|
+
this.keyHandlers.clear();
|
|
1541
|
+
this.updatedKeys.clear();
|
|
1551
1542
|
}
|
|
1552
|
-
|
|
1553
|
-
|
|
1543
|
+
/**
|
|
1544
|
+
* Get performance metrics for monitoring and debugging
|
|
1545
|
+
*/
|
|
1546
|
+
getMetrics() {
|
|
1547
|
+
let totalHandlers = 0;
|
|
1548
|
+
this.keyHandlers.forEach((handlers) => {
|
|
1549
|
+
totalHandlers += handlers.size;
|
|
1550
|
+
});
|
|
1551
|
+
return {
|
|
1552
|
+
totalKeys: this.keyHandlers.size,
|
|
1553
|
+
totalHandlers,
|
|
1554
|
+
averageHandlersPerKey: this.keyHandlers.size > 0 ? totalHandlers / this.keyHandlers.size : 0,
|
|
1555
|
+
queueLength: this.queue.length
|
|
1556
|
+
};
|
|
1554
1557
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1558
|
+
/**
|
|
1559
|
+
* Check if there are any active subscriptions
|
|
1560
|
+
*/
|
|
1561
|
+
hasSubscriptions() {
|
|
1562
|
+
return this.keyHandlers.size > 0;
|
|
1557
1563
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1564
|
+
/**
|
|
1565
|
+
* Get the number of handlers for a specific key
|
|
1566
|
+
*/
|
|
1567
|
+
getHandlerCount(key) {
|
|
1568
|
+
return this.keyHandlers.get(key)?.size ?? 0;
|
|
1560
1569
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1570
|
+
subscribeOnKey(key, handler) {
|
|
1571
|
+
let handlers = this.keyHandlers.get(key);
|
|
1572
|
+
if (!handlers) {
|
|
1573
|
+
handlers = /* @__PURE__ */ new Set();
|
|
1574
|
+
this.keyHandlers.set(key, handlers);
|
|
1575
|
+
}
|
|
1576
|
+
handlers.add(handler);
|
|
1577
|
+
return () => {
|
|
1578
|
+
this.unsubscribeFromKey(key, handler);
|
|
1579
|
+
};
|
|
1563
1580
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1581
|
+
unsubscribeFromKey(key, handler) {
|
|
1582
|
+
const handlers = this.keyHandlers.get(key);
|
|
1583
|
+
if (!handlers) return;
|
|
1584
|
+
handlers.delete(handler);
|
|
1585
|
+
if (handlers.size === 0) {
|
|
1586
|
+
this.keyHandlers.delete(key);
|
|
1587
|
+
this.updatedKeys.delete(key);
|
|
1588
|
+
}
|
|
1589
|
+
if (this.keyHandlers.size === 0) {
|
|
1590
|
+
this.updatedKeys.clear();
|
|
1591
|
+
if (this.isFlushEnqueued) {
|
|
1592
|
+
this.queue.cancelTask(this.onFlush);
|
|
1593
|
+
}
|
|
1594
|
+
this.isFlushEnqueued = false;
|
|
1595
|
+
}
|
|
1566
1596
|
}
|
|
1567
|
-
|
|
1568
|
-
const
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1597
|
+
subscribeOnKeys(keys, handler) {
|
|
1598
|
+
const subscribedKeys = [];
|
|
1599
|
+
for (const key of keys) {
|
|
1600
|
+
let handlers = this.keyHandlers.get(key);
|
|
1601
|
+
if (!handlers) {
|
|
1602
|
+
handlers = /* @__PURE__ */ new Set();
|
|
1603
|
+
this.keyHandlers.set(key, handlers);
|
|
1604
|
+
}
|
|
1605
|
+
if (!handlers.has(handler)) {
|
|
1606
|
+
handlers.add(handler);
|
|
1607
|
+
subscribedKeys.push(key);
|
|
1608
|
+
}
|
|
1572
1609
|
}
|
|
1573
|
-
return
|
|
1610
|
+
return () => {
|
|
1611
|
+
for (const key of subscribedKeys) {
|
|
1612
|
+
this.unsubscribeFromKey(key, handler);
|
|
1613
|
+
}
|
|
1614
|
+
};
|
|
1574
1615
|
}
|
|
1575
|
-
|
|
1576
|
-
const
|
|
1577
|
-
|
|
1578
|
-
throw new Error(
|
|
1579
|
-
`[OIMCollectionIndexManualOrderedArrayBased]: Unable to resolve slot for PK "${String(pk)}".`
|
|
1580
|
-
);
|
|
1616
|
+
unsubscribeFromKeys(keys, handler) {
|
|
1617
|
+
for (const key of keys) {
|
|
1618
|
+
this.unsubscribeFromKey(key, handler);
|
|
1581
1619
|
}
|
|
1582
|
-
return slot;
|
|
1583
1620
|
}
|
|
1584
1621
|
};
|
|
1585
1622
|
|
|
@@ -2510,17 +2547,15 @@ var OIMObject = class {
|
|
|
2510
2547
|
}
|
|
2511
2548
|
setProperty(key, value) {
|
|
2512
2549
|
this.store.setProperty(key, value);
|
|
2513
|
-
this.
|
|
2550
|
+
this.onUpdatedKey(key);
|
|
2514
2551
|
}
|
|
2515
2552
|
merge(draft) {
|
|
2516
2553
|
this.store.merge(draft);
|
|
2517
|
-
this.
|
|
2518
|
-
keys: Object.keys(draft)
|
|
2519
|
-
});
|
|
2554
|
+
this.onUpdatedKeys(Object.keys(draft));
|
|
2520
2555
|
}
|
|
2521
2556
|
delete(key) {
|
|
2522
2557
|
this.store.delete(key);
|
|
2523
|
-
this.
|
|
2558
|
+
this.onUpdatedKey(key);
|
|
2524
2559
|
}
|
|
2525
2560
|
get(key) {
|
|
2526
2561
|
return this.store.get(key);
|
|
@@ -2530,7 +2565,24 @@ var OIMObject = class {
|
|
|
2530
2565
|
}
|
|
2531
2566
|
clear() {
|
|
2532
2567
|
this.store.clear();
|
|
2533
|
-
this.
|
|
2568
|
+
this.onUpdatedKeys([]);
|
|
2569
|
+
}
|
|
2570
|
+
/**
|
|
2571
|
+
* Write fan-out hooks. Split single-key vs multi-key so a reactive subclass
|
|
2572
|
+
* can mark its keyed emitter on the hot single-property path without
|
|
2573
|
+
* allocating a `{ keys: [key] }` payload. The base only fires the general
|
|
2574
|
+
* UPDATE event, and only when something is actually subscribed to it — so an
|
|
2575
|
+
* unobserved write skips the payload allocation entirely.
|
|
2576
|
+
*/
|
|
2577
|
+
onUpdatedKey(key) {
|
|
2578
|
+
if (this.emitter.hasHandlers(0 /* UPDATE */)) {
|
|
2579
|
+
this.emitter.emit(0 /* UPDATE */, { keys: [key] });
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
onUpdatedKeys(keys) {
|
|
2583
|
+
if (this.emitter.hasHandlers(0 /* UPDATE */)) {
|
|
2584
|
+
this.emitter.emit(0 /* UPDATE */, { keys });
|
|
2585
|
+
}
|
|
2534
2586
|
}
|
|
2535
2587
|
count() {
|
|
2536
2588
|
return this.store.count();
|
|
@@ -2554,10 +2606,20 @@ var OIMObject = class {
|
|
|
2554
2606
|
var OIMReactiveObject = class extends OIMObject {
|
|
2555
2607
|
constructor(queue, opts) {
|
|
2556
2608
|
super(opts);
|
|
2557
|
-
this.updateEmitter = new
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2609
|
+
this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierResolver());
|
|
2610
|
+
}
|
|
2611
|
+
/**
|
|
2612
|
+
* Mark the written key(s) on the keyed emitter directly — no general-event
|
|
2613
|
+
* bridge, no `{ keys }` payload on the single-key path. `super` still fires
|
|
2614
|
+
* the general UPDATE event for any low-level `emitter` subscribers.
|
|
2615
|
+
*/
|
|
2616
|
+
onUpdatedKey(key) {
|
|
2617
|
+
this.updateEmitter.markUpdatedKey(key);
|
|
2618
|
+
super.onUpdatedKey(key);
|
|
2619
|
+
}
|
|
2620
|
+
onUpdatedKeys(keys) {
|
|
2621
|
+
this.updateEmitter.markUpdatedKeys(keys);
|
|
2622
|
+
super.onUpdatedKeys(keys);
|
|
2561
2623
|
}
|
|
2562
2624
|
subscribeOnKey(key, handler) {
|
|
2563
2625
|
return this.updateEmitter.subscribeOnKey(key, handler);
|
|
@@ -2661,6 +2723,9 @@ var OIMEventQueue = class {
|
|
|
2661
2723
|
constructor(options = {}) {
|
|
2662
2724
|
this.emitter = new OIMEventEmitter();
|
|
2663
2725
|
this.tasks = /* @__PURE__ */ new Set();
|
|
2726
|
+
// Reused buffer swapped in during flush so the pending set is never copied
|
|
2727
|
+
// (no per-flush array snapshot) and new enqueues land in a fresh set.
|
|
2728
|
+
this.tasksSpare = /* @__PURE__ */ new Set();
|
|
2664
2729
|
this.isFlushing = false;
|
|
2665
2730
|
this.scheduler = options.scheduler;
|
|
2666
2731
|
if (this.scheduler) {
|
|
@@ -2689,6 +2754,22 @@ var OIMEventQueue = class {
|
|
|
2689
2754
|
this.tasks.delete(task);
|
|
2690
2755
|
};
|
|
2691
2756
|
}
|
|
2757
|
+
/**
|
|
2758
|
+
* Allocation-free enqueue for a **stable** function reference (e.g. a bound
|
|
2759
|
+
* `onFlush` method). The function is stored directly — no wrapper, no
|
|
2760
|
+
* returned closure — and cancelled via {@link cancelTask}. Enqueuing the
|
|
2761
|
+
* same reference twice is idempotent (Set-deduped); callers that may run a
|
|
2762
|
+
* task more than once per flush should use {@link enqueue} instead.
|
|
2763
|
+
*/
|
|
2764
|
+
enqueueTask(fn) {
|
|
2765
|
+
this.tasks.add(fn);
|
|
2766
|
+
this.ensureScheduled();
|
|
2767
|
+
}
|
|
2768
|
+
/** Cancel a task scheduled via {@link enqueueTask}; safe to call mid-flush. */
|
|
2769
|
+
cancelTask(fn) {
|
|
2770
|
+
this.tasks.delete(fn);
|
|
2771
|
+
this.flushing?.delete(fn);
|
|
2772
|
+
}
|
|
2692
2773
|
get isInFlush() {
|
|
2693
2774
|
return this.isFlushing;
|
|
2694
2775
|
}
|
|
@@ -2708,11 +2789,13 @@ var OIMEventQueue = class {
|
|
|
2708
2789
|
this.scheduler?.cancel();
|
|
2709
2790
|
this.isFlushing = true;
|
|
2710
2791
|
this.emitter.emit(1 /* BEFORE_FLUSH */, void 0);
|
|
2711
|
-
const
|
|
2712
|
-
this.tasks.
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2792
|
+
const flushing = this.tasks;
|
|
2793
|
+
this.tasks = this.tasksSpare;
|
|
2794
|
+
this.flushing = flushing;
|
|
2795
|
+
for (const task of flushing) task();
|
|
2796
|
+
flushing.clear();
|
|
2797
|
+
this.tasksSpare = flushing;
|
|
2798
|
+
this.flushing = void 0;
|
|
2716
2799
|
this.isFlushing = false;
|
|
2717
2800
|
this.emitter.emit(0 /* AFTER_FLUSH */, void 0);
|
|
2718
2801
|
}
|