@oimdb/core 3.11.0 → 3.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +41 -18
- package/dist/index.d.cts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +41 -18
- package/llms.txt +3 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -728,19 +728,13 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
728
728
|
// doubles as membership: a carrier is in this array iff `carrier.dirty`.
|
|
729
729
|
this.dirtyCarriers = [];
|
|
730
730
|
this.isFlushEnqueued = false;
|
|
731
|
-
// Number of live subscriptions whose single handler spans 2+ distinct
|
|
732
|
-
// carriers (a `subscribeOnKeys` over distinct keys). While this is > 0 a
|
|
733
|
-
// flush coalesces delivery so such a handler fires at most ONCE per flush,
|
|
734
|
-
// not once per changed carrier — the callback carries no key, so extra
|
|
735
|
-
// fires are pure waste. At 0 the hot single-subscriber path pays nothing:
|
|
736
|
-
// no per-flush Set, no membership checks.
|
|
737
|
-
this.multiCarrierSubscriptions = 0;
|
|
738
731
|
this.onFlush = () => {
|
|
739
732
|
this.isFlushEnqueued = false;
|
|
740
733
|
if (this.dirtyCarriers.length === 0) return;
|
|
741
734
|
const flushing = this.dirtyCarriers;
|
|
742
735
|
this.dirtyCarriers = [];
|
|
743
|
-
const
|
|
736
|
+
const multiCarrierHandlers = this.multiCarrierHandlers;
|
|
737
|
+
const delivered = multiCarrierHandlers !== void 0 && multiCarrierHandlers.size > 0 ? this.deliveredPool ?? (this.deliveredPool = /* @__PURE__ */ new Set()) : void 0;
|
|
744
738
|
let i = 0;
|
|
745
739
|
try {
|
|
746
740
|
for (; i < flushing.length; i++) {
|
|
@@ -768,12 +762,14 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
768
762
|
carriers.push(carrier);
|
|
769
763
|
}
|
|
770
764
|
const spansMultipleCarriers = carriers.length > 1;
|
|
771
|
-
if (spansMultipleCarriers) this.
|
|
765
|
+
if (spansMultipleCarriers) this.retainMultiCarrierHandler(handler);
|
|
772
766
|
let unsubscribed = false;
|
|
773
767
|
return () => {
|
|
774
768
|
if (unsubscribed) return;
|
|
775
769
|
unsubscribed = true;
|
|
776
|
-
if (spansMultipleCarriers)
|
|
770
|
+
if (spansMultipleCarriers) {
|
|
771
|
+
this.releaseMultiCarrierHandler(handler);
|
|
772
|
+
}
|
|
777
773
|
for (let i = 0; i < carriers.length; i++) {
|
|
778
774
|
this.removeHandler(carriers[i], handler);
|
|
779
775
|
}
|
|
@@ -784,10 +780,25 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
784
780
|
if (carrier) this.removeHandler(carrier, handler);
|
|
785
781
|
}
|
|
786
782
|
unsubscribeFromKeys(keys, handler) {
|
|
783
|
+
if (keys.length > 1) this.releaseMultiCarrierHandler(handler);
|
|
787
784
|
for (let i = 0; i < keys.length; i++) {
|
|
788
785
|
this.unsubscribeFromKey(keys[i], handler);
|
|
789
786
|
}
|
|
790
787
|
}
|
|
788
|
+
/** Take a reference for a handler whose subscription spans several carriers. */
|
|
789
|
+
retainMultiCarrierHandler(handler) {
|
|
790
|
+
const handlers = this.multiCarrierHandlers ?? (this.multiCarrierHandlers = /* @__PURE__ */ new Map());
|
|
791
|
+
handlers.set(handler, (handlers.get(handler) ?? 0) + 1);
|
|
792
|
+
}
|
|
793
|
+
/** Drop one reference; the handler leaves the set when none are left. */
|
|
794
|
+
releaseMultiCarrierHandler(handler) {
|
|
795
|
+
const handlers = this.multiCarrierHandlers;
|
|
796
|
+
if (handlers === void 0) return;
|
|
797
|
+
const count = handlers.get(handler);
|
|
798
|
+
if (count === void 0) return;
|
|
799
|
+
if (count > 1) handlers.set(handler, count - 1);
|
|
800
|
+
else handlers.delete(handler);
|
|
801
|
+
}
|
|
791
802
|
addHandler(carrier, handler) {
|
|
792
803
|
let subscribers = carrier.subscribers;
|
|
793
804
|
if (!subscribers) {
|
|
@@ -876,6 +887,8 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
876
887
|
}
|
|
877
888
|
this.dirtyCarriers = [];
|
|
878
889
|
this.isFlushEnqueued = false;
|
|
890
|
+
this.multiCarrierHandlers?.clear();
|
|
891
|
+
this.deliveredPool?.clear();
|
|
879
892
|
}
|
|
880
893
|
assertNotInFlush() {
|
|
881
894
|
if (this.queue.isInFlush) {
|
|
@@ -892,13 +905,12 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
892
905
|
notify(carrier, delivered) {
|
|
893
906
|
const subscribers = carrier.subscribers;
|
|
894
907
|
if (!subscribers || subscribers.size === 0) return;
|
|
908
|
+
const multiCarrierHandlers = delivered ? this.multiCarrierHandlers : void 0;
|
|
895
909
|
if (subscribers.size === 1) {
|
|
896
910
|
const only = subscribers.values().next().value;
|
|
897
911
|
if (!only) return;
|
|
898
|
-
if (delivered) {
|
|
899
|
-
|
|
900
|
-
delivered.add(only);
|
|
901
|
-
if (delivered.size === size) return;
|
|
912
|
+
if (this.isAlreadyDelivered(only, delivered, multiCarrierHandlers)) {
|
|
913
|
+
return;
|
|
902
914
|
}
|
|
903
915
|
only();
|
|
904
916
|
return;
|
|
@@ -908,14 +920,25 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
908
920
|
for (let i = 0; i < snapshot.length; i++) {
|
|
909
921
|
const handler = snapshot[i];
|
|
910
922
|
if (!subscribers.has(handler)) continue;
|
|
911
|
-
if (delivered) {
|
|
912
|
-
|
|
913
|
-
delivered.add(handler);
|
|
914
|
-
if (delivered.size === size) continue;
|
|
923
|
+
if (this.isAlreadyDelivered(handler, delivered, multiCarrierHandlers)) {
|
|
924
|
+
continue;
|
|
915
925
|
}
|
|
916
926
|
handler();
|
|
917
927
|
}
|
|
918
928
|
}
|
|
929
|
+
/**
|
|
930
|
+
* Whether this handler already fired in the current flush and must be
|
|
931
|
+
* skipped. Only multi-carrier handlers are tracked; everything else always
|
|
932
|
+
* delivers. Membership is recorded via the size delta of a single `add` —
|
|
933
|
+
* one hash probe instead of `has` + `add`.
|
|
934
|
+
*/
|
|
935
|
+
isAlreadyDelivered(handler, delivered, multiCarrierHandlers) {
|
|
936
|
+
if (delivered === void 0) return false;
|
|
937
|
+
if (multiCarrierHandlers?.has(handler) !== true) return false;
|
|
938
|
+
const size = delivered.size;
|
|
939
|
+
delivered.add(handler);
|
|
940
|
+
return delivered.size === size;
|
|
941
|
+
}
|
|
919
942
|
};
|
|
920
943
|
|
|
921
944
|
// src/core/OIMKeyedCarrierProvider.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -509,13 +509,17 @@ declare class OIMCarrierKeyedEmitter<TKey extends TOIMKey, TCarrier extends IOIM
|
|
|
509
509
|
private readonly subscribedCarriers;
|
|
510
510
|
private dirtyCarriers;
|
|
511
511
|
private isFlushEnqueued;
|
|
512
|
-
private
|
|
512
|
+
private multiCarrierHandlers?;
|
|
513
513
|
private deliveredPool?;
|
|
514
514
|
constructor(queue: OIMEventQueue, carrierProvider: IOIMCarrierProvider<TKey, TCarrier>);
|
|
515
515
|
subscribeOnKey(key: TKey, handler: TOIMEventHandler<void>): () => void;
|
|
516
516
|
subscribeOnKeys(keys: readonly TKey[], handler: TOIMEventHandler<void>): () => void;
|
|
517
517
|
unsubscribeFromKey(key: TKey, handler: TOIMEventHandler<void>): void;
|
|
518
518
|
unsubscribeFromKeys(keys: readonly TKey[], handler: TOIMEventHandler<void>): void;
|
|
519
|
+
/** Take a reference for a handler whose subscription spans several carriers. */
|
|
520
|
+
private retainMultiCarrierHandler;
|
|
521
|
+
/** Drop one reference; the handler leaves the set when none are left. */
|
|
522
|
+
private releaseMultiCarrierHandler;
|
|
519
523
|
private addHandler;
|
|
520
524
|
private removeHandler;
|
|
521
525
|
/** Fast path: the writer already holds the carrier. */
|
|
@@ -536,6 +540,13 @@ declare class OIMCarrierKeyedEmitter<TKey extends TOIMKey, TCarrier extends IOIM
|
|
|
536
540
|
private scheduleFlush;
|
|
537
541
|
private readonly onFlush;
|
|
538
542
|
private notify;
|
|
543
|
+
/**
|
|
544
|
+
* Whether this handler already fired in the current flush and must be
|
|
545
|
+
* skipped. Only multi-carrier handlers are tracked; everything else always
|
|
546
|
+
* delivers. Membership is recorded via the size delta of a single `add` —
|
|
547
|
+
* one hash probe instead of `has` + `add`.
|
|
548
|
+
*/
|
|
549
|
+
private isAlreadyDelivered;
|
|
539
550
|
}
|
|
540
551
|
|
|
541
552
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -509,13 +509,17 @@ declare class OIMCarrierKeyedEmitter<TKey extends TOIMKey, TCarrier extends IOIM
|
|
|
509
509
|
private readonly subscribedCarriers;
|
|
510
510
|
private dirtyCarriers;
|
|
511
511
|
private isFlushEnqueued;
|
|
512
|
-
private
|
|
512
|
+
private multiCarrierHandlers?;
|
|
513
513
|
private deliveredPool?;
|
|
514
514
|
constructor(queue: OIMEventQueue, carrierProvider: IOIMCarrierProvider<TKey, TCarrier>);
|
|
515
515
|
subscribeOnKey(key: TKey, handler: TOIMEventHandler<void>): () => void;
|
|
516
516
|
subscribeOnKeys(keys: readonly TKey[], handler: TOIMEventHandler<void>): () => void;
|
|
517
517
|
unsubscribeFromKey(key: TKey, handler: TOIMEventHandler<void>): void;
|
|
518
518
|
unsubscribeFromKeys(keys: readonly TKey[], handler: TOIMEventHandler<void>): void;
|
|
519
|
+
/** Take a reference for a handler whose subscription spans several carriers. */
|
|
520
|
+
private retainMultiCarrierHandler;
|
|
521
|
+
/** Drop one reference; the handler leaves the set when none are left. */
|
|
522
|
+
private releaseMultiCarrierHandler;
|
|
519
523
|
private addHandler;
|
|
520
524
|
private removeHandler;
|
|
521
525
|
/** Fast path: the writer already holds the carrier. */
|
|
@@ -536,6 +540,13 @@ declare class OIMCarrierKeyedEmitter<TKey extends TOIMKey, TCarrier extends IOIM
|
|
|
536
540
|
private scheduleFlush;
|
|
537
541
|
private readonly onFlush;
|
|
538
542
|
private notify;
|
|
543
|
+
/**
|
|
544
|
+
* Whether this handler already fired in the current flush and must be
|
|
545
|
+
* skipped. Only multi-carrier handlers are tracked; everything else always
|
|
546
|
+
* delivers. Membership is recorded via the size delta of a single `add` —
|
|
547
|
+
* one hash probe instead of `has` + `add`.
|
|
548
|
+
*/
|
|
549
|
+
private isAlreadyDelivered;
|
|
539
550
|
}
|
|
540
551
|
|
|
541
552
|
/**
|
package/dist/index.js
CHANGED
|
@@ -578,19 +578,13 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
578
578
|
// doubles as membership: a carrier is in this array iff `carrier.dirty`.
|
|
579
579
|
this.dirtyCarriers = [];
|
|
580
580
|
this.isFlushEnqueued = false;
|
|
581
|
-
// Number of live subscriptions whose single handler spans 2+ distinct
|
|
582
|
-
// carriers (a `subscribeOnKeys` over distinct keys). While this is > 0 a
|
|
583
|
-
// flush coalesces delivery so such a handler fires at most ONCE per flush,
|
|
584
|
-
// not once per changed carrier — the callback carries no key, so extra
|
|
585
|
-
// fires are pure waste. At 0 the hot single-subscriber path pays nothing:
|
|
586
|
-
// no per-flush Set, no membership checks.
|
|
587
|
-
this.multiCarrierSubscriptions = 0;
|
|
588
581
|
this.onFlush = () => {
|
|
589
582
|
this.isFlushEnqueued = false;
|
|
590
583
|
if (this.dirtyCarriers.length === 0) return;
|
|
591
584
|
const flushing = this.dirtyCarriers;
|
|
592
585
|
this.dirtyCarriers = [];
|
|
593
|
-
const
|
|
586
|
+
const multiCarrierHandlers = this.multiCarrierHandlers;
|
|
587
|
+
const delivered = multiCarrierHandlers !== void 0 && multiCarrierHandlers.size > 0 ? this.deliveredPool ?? (this.deliveredPool = /* @__PURE__ */ new Set()) : void 0;
|
|
594
588
|
let i = 0;
|
|
595
589
|
try {
|
|
596
590
|
for (; i < flushing.length; i++) {
|
|
@@ -618,12 +612,14 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
618
612
|
carriers.push(carrier);
|
|
619
613
|
}
|
|
620
614
|
const spansMultipleCarriers = carriers.length > 1;
|
|
621
|
-
if (spansMultipleCarriers) this.
|
|
615
|
+
if (spansMultipleCarriers) this.retainMultiCarrierHandler(handler);
|
|
622
616
|
let unsubscribed = false;
|
|
623
617
|
return () => {
|
|
624
618
|
if (unsubscribed) return;
|
|
625
619
|
unsubscribed = true;
|
|
626
|
-
if (spansMultipleCarriers)
|
|
620
|
+
if (spansMultipleCarriers) {
|
|
621
|
+
this.releaseMultiCarrierHandler(handler);
|
|
622
|
+
}
|
|
627
623
|
for (let i = 0; i < carriers.length; i++) {
|
|
628
624
|
this.removeHandler(carriers[i], handler);
|
|
629
625
|
}
|
|
@@ -634,10 +630,25 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
634
630
|
if (carrier) this.removeHandler(carrier, handler);
|
|
635
631
|
}
|
|
636
632
|
unsubscribeFromKeys(keys, handler) {
|
|
633
|
+
if (keys.length > 1) this.releaseMultiCarrierHandler(handler);
|
|
637
634
|
for (let i = 0; i < keys.length; i++) {
|
|
638
635
|
this.unsubscribeFromKey(keys[i], handler);
|
|
639
636
|
}
|
|
640
637
|
}
|
|
638
|
+
/** Take a reference for a handler whose subscription spans several carriers. */
|
|
639
|
+
retainMultiCarrierHandler(handler) {
|
|
640
|
+
const handlers = this.multiCarrierHandlers ?? (this.multiCarrierHandlers = /* @__PURE__ */ new Map());
|
|
641
|
+
handlers.set(handler, (handlers.get(handler) ?? 0) + 1);
|
|
642
|
+
}
|
|
643
|
+
/** Drop one reference; the handler leaves the set when none are left. */
|
|
644
|
+
releaseMultiCarrierHandler(handler) {
|
|
645
|
+
const handlers = this.multiCarrierHandlers;
|
|
646
|
+
if (handlers === void 0) return;
|
|
647
|
+
const count = handlers.get(handler);
|
|
648
|
+
if (count === void 0) return;
|
|
649
|
+
if (count > 1) handlers.set(handler, count - 1);
|
|
650
|
+
else handlers.delete(handler);
|
|
651
|
+
}
|
|
641
652
|
addHandler(carrier, handler) {
|
|
642
653
|
let subscribers = carrier.subscribers;
|
|
643
654
|
if (!subscribers) {
|
|
@@ -726,6 +737,8 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
726
737
|
}
|
|
727
738
|
this.dirtyCarriers = [];
|
|
728
739
|
this.isFlushEnqueued = false;
|
|
740
|
+
this.multiCarrierHandlers?.clear();
|
|
741
|
+
this.deliveredPool?.clear();
|
|
729
742
|
}
|
|
730
743
|
assertNotInFlush() {
|
|
731
744
|
if (this.queue.isInFlush) {
|
|
@@ -742,13 +755,12 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
742
755
|
notify(carrier, delivered) {
|
|
743
756
|
const subscribers = carrier.subscribers;
|
|
744
757
|
if (!subscribers || subscribers.size === 0) return;
|
|
758
|
+
const multiCarrierHandlers = delivered ? this.multiCarrierHandlers : void 0;
|
|
745
759
|
if (subscribers.size === 1) {
|
|
746
760
|
const only = subscribers.values().next().value;
|
|
747
761
|
if (!only) return;
|
|
748
|
-
if (delivered) {
|
|
749
|
-
|
|
750
|
-
delivered.add(only);
|
|
751
|
-
if (delivered.size === size) return;
|
|
762
|
+
if (this.isAlreadyDelivered(only, delivered, multiCarrierHandlers)) {
|
|
763
|
+
return;
|
|
752
764
|
}
|
|
753
765
|
only();
|
|
754
766
|
return;
|
|
@@ -758,14 +770,25 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
758
770
|
for (let i = 0; i < snapshot.length; i++) {
|
|
759
771
|
const handler = snapshot[i];
|
|
760
772
|
if (!subscribers.has(handler)) continue;
|
|
761
|
-
if (delivered) {
|
|
762
|
-
|
|
763
|
-
delivered.add(handler);
|
|
764
|
-
if (delivered.size === size) continue;
|
|
773
|
+
if (this.isAlreadyDelivered(handler, delivered, multiCarrierHandlers)) {
|
|
774
|
+
continue;
|
|
765
775
|
}
|
|
766
776
|
handler();
|
|
767
777
|
}
|
|
768
778
|
}
|
|
779
|
+
/**
|
|
780
|
+
* Whether this handler already fired in the current flush and must be
|
|
781
|
+
* skipped. Only multi-carrier handlers are tracked; everything else always
|
|
782
|
+
* delivers. Membership is recorded via the size delta of a single `add` —
|
|
783
|
+
* one hash probe instead of `has` + `add`.
|
|
784
|
+
*/
|
|
785
|
+
isAlreadyDelivered(handler, delivered, multiCarrierHandlers) {
|
|
786
|
+
if (delivered === void 0) return false;
|
|
787
|
+
if (multiCarrierHandlers?.has(handler) !== true) return false;
|
|
788
|
+
const size = delivered.size;
|
|
789
|
+
delivered.add(handler);
|
|
790
|
+
return delivered.size === size;
|
|
791
|
+
}
|
|
769
792
|
};
|
|
770
793
|
|
|
771
794
|
// src/core/OIMKeyedCarrierProvider.ts
|
package/llms.txt
CHANGED
|
@@ -86,7 +86,9 @@ byRole.subscribeOnKey('admin', () => {
|
|
|
86
86
|
queue.flush(); // delivers subscription
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
**`subscribeOnKeys(keys, handler)` delivery is coalesced per flush:** the handler fires at most ONCE per flush no matter how many of the subscribed keys changed in it (the callback gets no key, so per-key fan-out would be pure waste). This holds across all reactive primitives — sync (collection / index / object, via `OIMCarrierKeyedEmitter`) and the `@oimdb/async` nodes (via `OIMUpdateEventEmitterAsync`) alike. `subscribeOnKey` (single key) is unchanged. Handler identity is preserved, so `unsubscribeFromKeys(keys, handler)` still removes a `subscribeOnKeys` subscription.
|
|
89
|
+
**`subscribeOnKeys(keys, handler)` delivery is coalesced per flush:** the handler fires at most ONCE per flush no matter how many of the subscribed keys changed in it (the callback gets no key, so per-key fan-out would be pure waste). This holds across all reactive primitives — sync (collection / index / object, via `OIMCarrierKeyedEmitter`) and the `@oimdb/async` nodes (via `OIMUpdateEventEmitterAsync`) alike. `subscribeOnKey` (single key) is unchanged. Handler identity is preserved, so `unsubscribeFromKeys(keys, handler)` still removes a `subscribeOnKeys` subscription — and releases the coalescing exactly like the returned unsubscribe closure does.
|
|
90
|
+
|
|
91
|
+
**Coalescing is scoped to the subscription, not to the handler.** It applies only to handlers registered through a multi-key `subscribeOnKeys`. If the same function holds several *independent* `subscribeOnKey` subscriptions, it still gets one delivery per changed key — an unrelated multi-key subscription elsewhere on the same collection/index never changes that.
|
|
90
92
|
|
|
91
93
|
Membership writes are incremental (touch only the changed pks):
|
|
92
94
|
- `addPks(key, pks)` / `removePks(key, pks)` — **set-based**: O(1) per pk for both. **array-based**: `addPks` O(1), `removePks` O(bucket) (ordered, must scan).
|
package/package.json
CHANGED