@oimdb/core 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -524,265 +524,198 @@ var OIMIndexArrayBased = class extends OIMIndex {
524
524
  }
525
525
  };
526
526
 
527
- // src/enums/EOIMEventQueueEventType.ts
528
- var EOIMEventQueueEventType = /* @__PURE__ */ ((EOIMEventQueueEventType2) => {
529
- EOIMEventQueueEventType2[EOIMEventQueueEventType2["AFTER_FLUSH"] = 0] = "AFTER_FLUSH";
530
- EOIMEventQueueEventType2[EOIMEventQueueEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
531
- return EOIMEventQueueEventType2;
532
- })(EOIMEventQueueEventType || {});
533
-
534
- // src/core/OIMUpdateEventEmitter.ts
535
- var OIMUpdateEventEmitter = class {
536
- constructor(queue, deliveryMode = "queue") {
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
540
  this.dequeueFlush = void 0;
545
- if (this.keyHandlers.size === 0) {
546
- this.updatedKeys.clear();
547
- return;
541
+ if (this.dirtyCarriers.length === 0) return;
542
+ const flushing = this.dirtyCarriers;
543
+ this.dirtyCarriers = [];
544
+ let i = 0;
545
+ try {
546
+ for (; i < flushing.length; i++) {
547
+ flushing[i].dirty = false;
548
+ this.notify(flushing[i]);
549
+ }
550
+ } finally {
551
+ for (; i < flushing.length; i++) flushing[i].dirty = false;
548
552
  }
549
- this.runSinglePass();
550
- };
551
- this.onAfterFlush = () => {
552
- if (!this.isAfterFlushDeliveryScheduled) return;
553
- this.isAfterFlushDeliveryScheduled = false;
554
- this.deliverImmediate();
555
553
  };
556
554
  this.queue = queue;
557
- this.deliveryMode = deliveryMode;
558
- if (this.deliveryMode === "after_flush") {
559
- this.unsubscribeAfterFlushDelivery = this.queue.emitter.on(
560
- 0 /* AFTER_FLUSH */,
561
- this.onAfterFlush
562
- );
563
- }
555
+ this.resolver = resolver;
564
556
  }
565
- /**
566
- * Mark keys as updated and schedule delivery.
567
- *
568
- * If there are no subscriptions, this is a no-op and does not allocate or retain keys.
569
- */
570
- markUpdatedKeys(keys) {
571
- if (keys.length === 0) return;
572
- if (this.deliveryMode === "queue" && this.queue.isInFlush) {
573
- throw new Error(
574
- "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
575
- );
557
+ subscribeOnKey(key, handler) {
558
+ const carrier = this.resolver.getOrReserveCarrier(key);
559
+ this.addHandler(carrier, handler);
560
+ return () => this.removeHandler(carrier, handler);
561
+ }
562
+ subscribeOnKeys(keys, handler) {
563
+ const carriers = [];
564
+ for (let i = 0; i < keys.length; i++) {
565
+ const carrier = this.resolver.getOrReserveCarrier(keys[i]);
566
+ this.addHandler(carrier, handler);
567
+ carriers.push(carrier);
576
568
  }
577
- if (this.keyHandlers.size === 0) return;
578
- let didAddAny = false;
569
+ return () => {
570
+ for (let i = 0; i < carriers.length; i++) {
571
+ this.removeHandler(carriers[i], handler);
572
+ }
573
+ };
574
+ }
575
+ unsubscribeFromKey(key, handler) {
576
+ const carrier = this.resolver.findCarrier(key);
577
+ if (carrier) this.removeHandler(carrier, handler);
578
+ }
579
+ unsubscribeFromKeys(keys, handler) {
579
580
  for (let i = 0; i < keys.length; i++) {
580
- const key = keys[i];
581
- const handlers = this.keyHandlers.get(key);
582
- if (!handlers || handlers.size === 0) continue;
583
- this.updatedKeys.add(key);
584
- didAddAny = true;
581
+ this.unsubscribeFromKey(keys[i], handler);
585
582
  }
586
- if (!didAddAny) return;
587
- if (this.deliveryMode === "immediate") {
588
- this.deliverImmediate();
589
- return;
583
+ }
584
+ addHandler(carrier, handler) {
585
+ let subscribers = carrier.subscribers;
586
+ if (!subscribers) {
587
+ subscribers = /* @__PURE__ */ new Set();
588
+ carrier.subscribers = subscribers;
590
589
  }
591
- if (this.deliveryMode === "after_flush") {
592
- this.isAfterFlushDeliveryScheduled = true;
593
- return;
590
+ subscribers.add(handler);
591
+ this.subscribedCarriers.add(carrier);
592
+ }
593
+ removeHandler(carrier, handler) {
594
+ const subscribers = carrier.subscribers;
595
+ if (!subscribers) return;
596
+ subscribers.delete(handler);
597
+ if (subscribers.size === 0) {
598
+ this.subscribedCarriers.delete(carrier);
599
+ this.resolver.onCarrierEmptied?.(carrier);
594
600
  }
595
- if (this.isFlushEnqueued) return;
596
- this.isFlushEnqueued = true;
597
- this.dequeueFlush = this.queue.enqueue(this.onFlush);
601
+ }
602
+ /** Fast path: the writer already holds the carrier. */
603
+ markUpdatedCarrier(carrier) {
604
+ this.assertNotInFlush();
605
+ const subscribers = carrier.subscribers;
606
+ if (!subscribers || subscribers.size === 0) return;
607
+ if (carrier.dirty) return;
608
+ carrier.dirty = true;
609
+ this.dirtyCarriers.push(carrier);
610
+ this.scheduleFlush();
598
611
  }
599
612
  markUpdatedKey(key) {
600
- if (this.deliveryMode === "queue" && this.queue.isInFlush) {
601
- throw new Error(
602
- "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
603
- );
604
- }
605
- if (this.keyHandlers.size === 0) return;
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);
613
+ const carrier = this.resolver.findCarrier(key);
614
+ if (carrier) this.markUpdatedCarrier(carrier);
615
+ else this.assertNotInFlush();
616
+ }
617
+ markUpdatedKeys(keys) {
618
+ for (let i = 0; i < keys.length; i++) this.markUpdatedKey(keys[i]);
620
619
  }
621
620
  markAllUpdated() {
622
- if (this.deliveryMode === "queue" && this.queue.isInFlush) {
623
- throw new Error(
624
- "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
625
- );
621
+ this.assertNotInFlush();
622
+ if (this.subscribedCarriers.size === 0) return;
623
+ this.subscribedCarriers.forEach((carrier) => {
624
+ if (carrier.dirty) return;
625
+ carrier.dirty = true;
626
+ this.dirtyCarriers.push(carrier);
627
+ });
628
+ this.scheduleFlush();
629
+ }
630
+ hasSubscriptions() {
631
+ return this.subscribedCarriers.size > 0;
632
+ }
633
+ getHandlerCount(key) {
634
+ return this.resolver.findCarrier(key)?.subscribers?.size ?? 0;
635
+ }
636
+ getMetrics() {
637
+ let totalHandlers = 0;
638
+ this.subscribedCarriers.forEach((carrier) => {
639
+ totalHandlers += carrier.subscribers?.size ?? 0;
640
+ });
641
+ const totalKeys = this.subscribedCarriers.size;
642
+ return {
643
+ totalKeys,
644
+ totalHandlers,
645
+ averageHandlersPerKey: totalKeys > 0 ? totalHandlers / totalKeys : 0,
646
+ queueLength: this.dirtyCarriers.length
647
+ };
648
+ }
649
+ destroy() {
650
+ if (this.dequeueFlush) {
651
+ this.dequeueFlush();
652
+ this.dequeueFlush = void 0;
626
653
  }
627
- if (this.keyHandlers.size === 0) return;
628
- this.keyHandlers.forEach((_handlers, key) => {
629
- this.updatedKeys.add(key);
654
+ this.subscribedCarriers.forEach((carrier) => {
655
+ carrier.subscribers?.clear();
656
+ carrier.subscribers = void 0;
630
657
  });
631
- if (this.deliveryMode === "immediate") {
632
- this.deliverImmediate();
633
- return;
658
+ this.subscribedCarriers.clear();
659
+ for (let i = 0; i < this.dirtyCarriers.length; i++) {
660
+ this.dirtyCarriers[i].dirty = false;
634
661
  }
635
- if (this.deliveryMode === "after_flush") {
636
- this.isAfterFlushDeliveryScheduled = true;
637
- return;
662
+ this.dirtyCarriers = [];
663
+ this.isFlushEnqueued = false;
664
+ }
665
+ assertNotInFlush() {
666
+ if (this.queue.isInFlush) {
667
+ throw new Error(
668
+ "OIMCarrierKeyedEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
669
+ );
638
670
  }
671
+ }
672
+ scheduleFlush() {
639
673
  if (this.isFlushEnqueued) return;
640
674
  this.isFlushEnqueued = true;
641
675
  this.dequeueFlush = this.queue.enqueue(this.onFlush);
642
676
  }
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;
677
+ notify(carrier) {
678
+ const subscribers = carrier.subscribers;
679
+ if (!subscribers || subscribers.size === 0) return;
680
+ if (subscribers.size === 1) {
681
+ const only = subscribers.values().next().value;
655
682
  if (only) only();
656
683
  return;
657
684
  }
658
685
  const snapshot = [];
659
- handlers.forEach((h) => snapshot.push(h));
686
+ subscribers.forEach((h) => snapshot.push(h));
660
687
  for (let i = 0; i < snapshot.length; i++) {
661
688
  const handler = snapshot[i];
662
- if (handlers.has(handler)) handler();
663
- }
664
- }
665
- /**
666
- * Deliver updated keys and notify handlers once.
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
- }
687
- }
688
- destroy() {
689
- if (this.dequeueFlush) {
690
- this.dequeueFlush();
691
- this.dequeueFlush = void 0;
692
- }
693
- if (this.unsubscribeAfterFlushDelivery) {
694
- this.unsubscribeAfterFlushDelivery();
695
- this.unsubscribeAfterFlushDelivery = void 0;
689
+ if (subscribers.has(handler)) handler();
696
690
  }
697
- this.keyHandlers.forEach((handlers) => handlers.clear());
698
- this.keyHandlers.clear();
699
- this.updatedKeys.clear();
700
- }
701
- /**
702
- * Get performance metrics for monitoring and debugging
703
- */
704
- getMetrics() {
705
- let totalHandlers = 0;
706
- this.keyHandlers.forEach((handlers) => {
707
- totalHandlers += handlers.size;
708
- });
709
- return {
710
- totalKeys: this.keyHandlers.size,
711
- totalHandlers,
712
- averageHandlersPerKey: this.keyHandlers.size > 0 ? totalHandlers / this.keyHandlers.size : 0,
713
- queueLength: this.queue.length
714
- };
715
- }
716
- /**
717
- * Check if there are any active subscriptions
718
- */
719
- hasSubscriptions() {
720
- return this.keyHandlers.size > 0;
721
691
  }
722
- /**
723
- * Get the number of handlers for a specific key
724
- */
725
- getHandlerCount(key) {
726
- return this.keyHandlers.get(key)?.size ?? 0;
727
- }
728
- subscribeOnKey(key, handler) {
729
- let handlers = this.keyHandlers.get(key);
730
- if (!handlers) {
731
- handlers = /* @__PURE__ */ new Set();
732
- this.keyHandlers.set(key, handlers);
733
- }
734
- handlers.add(handler);
735
- return () => {
736
- this.unsubscribeFromKey(key, handler);
737
- };
692
+ };
693
+
694
+ // src/core/OIMKeyedCarrierResolver.ts
695
+ var OIMKeyedCarrierResolver = class {
696
+ constructor() {
697
+ this.carriers = /* @__PURE__ */ new Map();
738
698
  }
739
- unsubscribeFromKey(key, handler) {
740
- const handlers = this.keyHandlers.get(key);
741
- if (!handlers) return;
742
- handlers.delete(handler);
743
- if (handlers.size === 0) {
744
- this.keyHandlers.delete(key);
745
- this.updatedKeys.delete(key);
746
- }
747
- if (this.keyHandlers.size === 0) {
748
- this.updatedKeys.clear();
749
- if (this.dequeueFlush) {
750
- this.dequeueFlush();
751
- this.dequeueFlush = void 0;
752
- }
753
- this.isFlushEnqueued = false;
699
+ getOrReserveCarrier(key) {
700
+ let carrier = this.carriers.get(key);
701
+ if (!carrier) {
702
+ carrier = { key };
703
+ this.carriers.set(key, carrier);
754
704
  }
705
+ return carrier;
755
706
  }
756
- subscribeOnKeys(keys, handler) {
757
- const subscribedKeys = [];
758
- for (const key of keys) {
759
- let handlers = this.keyHandlers.get(key);
760
- if (!handlers) {
761
- handlers = /* @__PURE__ */ new Set();
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
- };
707
+ findCarrier(key) {
708
+ return this.carriers.get(key);
774
709
  }
775
- unsubscribeFromKeys(keys, handler) {
776
- for (const key of keys) {
777
- this.unsubscribeFromKey(key, handler);
778
- }
710
+ onCarrierEmptied(carrier) {
711
+ this.carriers.delete(carrier.key);
779
712
  }
780
713
  };
781
714
 
782
715
  // src/abstract/OIMReactiveIndex.ts
783
716
  var OIMReactiveIndex = class {
784
717
  constructor(queue, createIndex) {
785
- this.updateEmitter = new OIMUpdateEventEmitter(queue);
718
+ this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierResolver());
786
719
  this.index = createIndex(this.updateEmitter);
787
720
  }
788
721
  getEntitiesByKey(key) {
@@ -1150,154 +1083,7 @@ var OIMCollection = class {
1150
1083
  }
1151
1084
  destroy() {
1152
1085
  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();
1242
- }
1243
- hasSubscriptions() {
1244
- return this.subscribedCarriers.size > 0;
1245
- }
1246
- getHandlerCount(key) {
1247
- return this.resolver.findCarrier(key)?.subscribers?.size ?? 0;
1248
- }
1249
- getMetrics() {
1250
- let totalHandlers = 0;
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
- };
1261
- }
1262
- destroy() {
1263
- if (this.dequeueFlush) {
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;
1274
- }
1275
- assertNotInFlush() {
1276
- if (this.queue.isInFlush) {
1277
- throw new Error(
1278
- "OIMCarrierKeyedEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
1279
- );
1280
- }
1281
- }
1282
- scheduleFlush() {
1283
- if (this.isFlushEnqueued) return;
1284
- this.isFlushEnqueued = true;
1285
- this.dequeueFlush = this.queue.enqueue(this.onFlush);
1286
- }
1287
- notify(carrier) {
1288
- const subscribers = carrier.subscribers;
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();
1300
- }
1086
+ this.emitter.offAll();
1301
1087
  }
1302
1088
  };
1303
1089
 
@@ -1583,6 +1369,261 @@ var OIMCollectionIndexManualOrderedArrayBased = class extends OIMIndexManualOrde
1583
1369
  }
1584
1370
  };
1585
1371
 
1372
+ // src/enums/EOIMEventQueueEventType.ts
1373
+ var EOIMEventQueueEventType = /* @__PURE__ */ ((EOIMEventQueueEventType2) => {
1374
+ EOIMEventQueueEventType2[EOIMEventQueueEventType2["AFTER_FLUSH"] = 0] = "AFTER_FLUSH";
1375
+ EOIMEventQueueEventType2[EOIMEventQueueEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
1376
+ return EOIMEventQueueEventType2;
1377
+ })(EOIMEventQueueEventType || {});
1378
+
1379
+ // src/core/OIMUpdateEventEmitter.ts
1380
+ var OIMUpdateEventEmitter = class {
1381
+ constructor(queue, deliveryMode = "queue") {
1382
+ this.keyHandlers = /* @__PURE__ */ new Map();
1383
+ this.isFlushEnqueued = false;
1384
+ this.updatedKeys = /* @__PURE__ */ new Set();
1385
+ this.isAfterFlushDeliveryScheduled = false;
1386
+ // Stable handler reference to avoid allocating a new closure on every HAS_CHANGES.
1387
+ this.onFlush = () => {
1388
+ this.isFlushEnqueued = false;
1389
+ this.dequeueFlush = void 0;
1390
+ if (this.keyHandlers.size === 0) {
1391
+ this.updatedKeys.clear();
1392
+ return;
1393
+ }
1394
+ this.runSinglePass();
1395
+ };
1396
+ this.onAfterFlush = () => {
1397
+ if (!this.isAfterFlushDeliveryScheduled) return;
1398
+ this.isAfterFlushDeliveryScheduled = false;
1399
+ this.deliverImmediate();
1400
+ };
1401
+ this.queue = queue;
1402
+ this.deliveryMode = deliveryMode;
1403
+ if (this.deliveryMode === "after_flush") {
1404
+ this.unsubscribeAfterFlushDelivery = this.queue.emitter.on(
1405
+ 0 /* AFTER_FLUSH */,
1406
+ this.onAfterFlush
1407
+ );
1408
+ }
1409
+ }
1410
+ /**
1411
+ * Mark keys as updated and schedule delivery.
1412
+ *
1413
+ * If there are no subscriptions, this is a no-op and does not allocate or retain keys.
1414
+ */
1415
+ markUpdatedKeys(keys) {
1416
+ if (keys.length === 0) return;
1417
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
1418
+ throw new Error(
1419
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
1420
+ );
1421
+ }
1422
+ if (this.keyHandlers.size === 0) return;
1423
+ let didAddAny = false;
1424
+ for (let i = 0; i < keys.length; i++) {
1425
+ const key = keys[i];
1426
+ const handlers = this.keyHandlers.get(key);
1427
+ if (!handlers || handlers.size === 0) continue;
1428
+ this.updatedKeys.add(key);
1429
+ didAddAny = true;
1430
+ }
1431
+ if (!didAddAny) return;
1432
+ if (this.deliveryMode === "immediate") {
1433
+ this.deliverImmediate();
1434
+ return;
1435
+ }
1436
+ if (this.deliveryMode === "after_flush") {
1437
+ this.isAfterFlushDeliveryScheduled = true;
1438
+ return;
1439
+ }
1440
+ if (this.isFlushEnqueued) return;
1441
+ this.isFlushEnqueued = true;
1442
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
1443
+ }
1444
+ markUpdatedKey(key) {
1445
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
1446
+ throw new Error(
1447
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
1448
+ );
1449
+ }
1450
+ if (this.keyHandlers.size === 0) return;
1451
+ const handlers = this.keyHandlers.get(key);
1452
+ if (!handlers || handlers.size === 0) return;
1453
+ this.updatedKeys.add(key);
1454
+ if (this.deliveryMode === "immediate") {
1455
+ this.deliverImmediate();
1456
+ return;
1457
+ }
1458
+ if (this.deliveryMode === "after_flush") {
1459
+ this.isAfterFlushDeliveryScheduled = true;
1460
+ return;
1461
+ }
1462
+ if (this.isFlushEnqueued) return;
1463
+ this.isFlushEnqueued = true;
1464
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
1465
+ }
1466
+ markAllUpdated() {
1467
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
1468
+ throw new Error(
1469
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
1470
+ );
1471
+ }
1472
+ if (this.keyHandlers.size === 0) return;
1473
+ this.keyHandlers.forEach((_handlers, key) => {
1474
+ this.updatedKeys.add(key);
1475
+ });
1476
+ if (this.deliveryMode === "immediate") {
1477
+ this.deliverImmediate();
1478
+ return;
1479
+ }
1480
+ if (this.deliveryMode === "after_flush") {
1481
+ this.isAfterFlushDeliveryScheduled = true;
1482
+ return;
1483
+ }
1484
+ if (this.isFlushEnqueued) return;
1485
+ this.isFlushEnqueued = true;
1486
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
1487
+ }
1488
+ deliverImmediate() {
1489
+ this.runSinglePass();
1490
+ }
1491
+ /**
1492
+ * Notifies all handlers for a key. The overwhelmingly common case is a
1493
+ * single subscriber per key (one component per pk), handled without any
1494
+ * allocation. For multiple subscribers a snapshot is taken so a handler may
1495
+ * (un)subscribe during iteration without corrupting the walk.
1496
+ */
1497
+ notifyHandlers(handlers) {
1498
+ if (handlers.size === 1) {
1499
+ const only = handlers.values().next().value;
1500
+ if (only) only();
1501
+ return;
1502
+ }
1503
+ const snapshot = [];
1504
+ handlers.forEach((h) => snapshot.push(h));
1505
+ for (let i = 0; i < snapshot.length; i++) {
1506
+ const handler = snapshot[i];
1507
+ if (handlers.has(handler)) handler();
1508
+ }
1509
+ }
1510
+ /**
1511
+ * Deliver updated keys and notify handlers once.
1512
+ * Re-entrant updates during delivery are forbidden and will throw in markUpdatedKeys().
1513
+ */
1514
+ runSinglePass() {
1515
+ if (this.updatedKeys.size === 0) return;
1516
+ const flushingKeys = this.updatedKeys;
1517
+ this.updatedKeys = /* @__PURE__ */ new Set();
1518
+ if (this.keyHandlers.size === 0 || flushingKeys.size === 0) return;
1519
+ if (flushingKeys.size * 2 < this.keyHandlers.size) {
1520
+ flushingKeys.forEach((key) => {
1521
+ const handlers = this.keyHandlers.get(key);
1522
+ if (!handlers || handlers.size === 0) return;
1523
+ this.notifyHandlers(handlers);
1524
+ });
1525
+ } else {
1526
+ this.keyHandlers.forEach((handlers, key) => {
1527
+ if (!flushingKeys.has(key)) return;
1528
+ if (!handlers || handlers.size === 0) return;
1529
+ this.notifyHandlers(handlers);
1530
+ });
1531
+ }
1532
+ }
1533
+ destroy() {
1534
+ if (this.dequeueFlush) {
1535
+ this.dequeueFlush();
1536
+ this.dequeueFlush = void 0;
1537
+ }
1538
+ if (this.unsubscribeAfterFlushDelivery) {
1539
+ this.unsubscribeAfterFlushDelivery();
1540
+ this.unsubscribeAfterFlushDelivery = void 0;
1541
+ }
1542
+ this.keyHandlers.forEach((handlers) => handlers.clear());
1543
+ this.keyHandlers.clear();
1544
+ this.updatedKeys.clear();
1545
+ }
1546
+ /**
1547
+ * Get performance metrics for monitoring and debugging
1548
+ */
1549
+ getMetrics() {
1550
+ let totalHandlers = 0;
1551
+ this.keyHandlers.forEach((handlers) => {
1552
+ totalHandlers += handlers.size;
1553
+ });
1554
+ return {
1555
+ totalKeys: this.keyHandlers.size,
1556
+ totalHandlers,
1557
+ averageHandlersPerKey: this.keyHandlers.size > 0 ? totalHandlers / this.keyHandlers.size : 0,
1558
+ queueLength: this.queue.length
1559
+ };
1560
+ }
1561
+ /**
1562
+ * Check if there are any active subscriptions
1563
+ */
1564
+ hasSubscriptions() {
1565
+ return this.keyHandlers.size > 0;
1566
+ }
1567
+ /**
1568
+ * Get the number of handlers for a specific key
1569
+ */
1570
+ getHandlerCount(key) {
1571
+ return this.keyHandlers.get(key)?.size ?? 0;
1572
+ }
1573
+ subscribeOnKey(key, handler) {
1574
+ let handlers = this.keyHandlers.get(key);
1575
+ if (!handlers) {
1576
+ handlers = /* @__PURE__ */ new Set();
1577
+ this.keyHandlers.set(key, handlers);
1578
+ }
1579
+ handlers.add(handler);
1580
+ return () => {
1581
+ this.unsubscribeFromKey(key, handler);
1582
+ };
1583
+ }
1584
+ unsubscribeFromKey(key, handler) {
1585
+ const handlers = this.keyHandlers.get(key);
1586
+ if (!handlers) return;
1587
+ handlers.delete(handler);
1588
+ if (handlers.size === 0) {
1589
+ this.keyHandlers.delete(key);
1590
+ this.updatedKeys.delete(key);
1591
+ }
1592
+ if (this.keyHandlers.size === 0) {
1593
+ this.updatedKeys.clear();
1594
+ if (this.dequeueFlush) {
1595
+ this.dequeueFlush();
1596
+ this.dequeueFlush = void 0;
1597
+ }
1598
+ this.isFlushEnqueued = false;
1599
+ }
1600
+ }
1601
+ subscribeOnKeys(keys, handler) {
1602
+ const subscribedKeys = [];
1603
+ for (const key of keys) {
1604
+ let handlers = this.keyHandlers.get(key);
1605
+ if (!handlers) {
1606
+ handlers = /* @__PURE__ */ new Set();
1607
+ this.keyHandlers.set(key, handlers);
1608
+ }
1609
+ if (!handlers.has(handler)) {
1610
+ handlers.add(handler);
1611
+ subscribedKeys.push(key);
1612
+ }
1613
+ }
1614
+ return () => {
1615
+ for (const key of subscribedKeys) {
1616
+ this.unsubscribeFromKey(key, handler);
1617
+ }
1618
+ };
1619
+ }
1620
+ unsubscribeFromKeys(keys, handler) {
1621
+ for (const key of keys) {
1622
+ this.unsubscribeFromKey(key, handler);
1623
+ }
1624
+ }
1625
+ };
1626
+
1586
1627
  // src/modules/wrapper/index/OIMOrderedListCommandStream.ts
1587
1628
  var OIMOrderedListCommandStream = class {
1588
1629
  constructor(queue, index) {