@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.js CHANGED
@@ -422,265 +422,198 @@ var OIMIndexArrayBased = class extends OIMIndex {
422
422
  }
423
423
  };
424
424
 
425
- // src/enums/EOIMEventQueueEventType.ts
426
- var EOIMEventQueueEventType = /* @__PURE__ */ ((EOIMEventQueueEventType2) => {
427
- EOIMEventQueueEventType2[EOIMEventQueueEventType2["AFTER_FLUSH"] = 0] = "AFTER_FLUSH";
428
- EOIMEventQueueEventType2[EOIMEventQueueEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
429
- return EOIMEventQueueEventType2;
430
- })(EOIMEventQueueEventType || {});
431
-
432
- // src/core/OIMUpdateEventEmitter.ts
433
- var OIMUpdateEventEmitter = class {
434
- constructor(queue, deliveryMode = "queue") {
435
- this.keyHandlers = /* @__PURE__ */ new Map();
425
+ // src/core/OIMCarrierKeyedEmitter.ts
426
+ var OIMCarrierKeyedEmitter = class {
427
+ constructor(queue, resolver) {
428
+ // Carriers with at least one subscriber — for markAllUpdated / metrics.
429
+ this.subscribedCarriers = /* @__PURE__ */ new Set();
430
+ // Pending flush batch. A plain array + a `carrier.dirty` flag for dedup —
431
+ // marking is the hottest write-path op, and pushing to an array with a
432
+ // boolean check is ~4x cheaper than a `Set` identity-hash add. The flag
433
+ // doubles as membership: a carrier is in this array iff `carrier.dirty`.
434
+ this.dirtyCarriers = [];
436
435
  this.isFlushEnqueued = false;
437
- this.updatedKeys = /* @__PURE__ */ new Set();
438
- this.isAfterFlushDeliveryScheduled = false;
439
- // Stable handler reference to avoid allocating a new closure on every HAS_CHANGES.
440
436
  this.onFlush = () => {
441
437
  this.isFlushEnqueued = false;
442
438
  this.dequeueFlush = void 0;
443
- if (this.keyHandlers.size === 0) {
444
- this.updatedKeys.clear();
445
- return;
439
+ if (this.dirtyCarriers.length === 0) return;
440
+ const flushing = this.dirtyCarriers;
441
+ this.dirtyCarriers = [];
442
+ let i = 0;
443
+ try {
444
+ for (; i < flushing.length; i++) {
445
+ flushing[i].dirty = false;
446
+ this.notify(flushing[i]);
447
+ }
448
+ } finally {
449
+ for (; i < flushing.length; i++) flushing[i].dirty = false;
446
450
  }
447
- this.runSinglePass();
448
- };
449
- this.onAfterFlush = () => {
450
- if (!this.isAfterFlushDeliveryScheduled) return;
451
- this.isAfterFlushDeliveryScheduled = false;
452
- this.deliverImmediate();
453
451
  };
454
452
  this.queue = queue;
455
- this.deliveryMode = deliveryMode;
456
- if (this.deliveryMode === "after_flush") {
457
- this.unsubscribeAfterFlushDelivery = this.queue.emitter.on(
458
- 0 /* AFTER_FLUSH */,
459
- this.onAfterFlush
460
- );
461
- }
453
+ this.resolver = resolver;
462
454
  }
463
- /**
464
- * Mark keys as updated and schedule delivery.
465
- *
466
- * If there are no subscriptions, this is a no-op and does not allocate or retain keys.
467
- */
468
- markUpdatedKeys(keys) {
469
- if (keys.length === 0) return;
470
- if (this.deliveryMode === "queue" && this.queue.isInFlush) {
471
- throw new Error(
472
- "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
473
- );
455
+ subscribeOnKey(key, handler) {
456
+ const carrier = this.resolver.getOrReserveCarrier(key);
457
+ this.addHandler(carrier, handler);
458
+ return () => this.removeHandler(carrier, handler);
459
+ }
460
+ subscribeOnKeys(keys, handler) {
461
+ const carriers = [];
462
+ for (let i = 0; i < keys.length; i++) {
463
+ const carrier = this.resolver.getOrReserveCarrier(keys[i]);
464
+ this.addHandler(carrier, handler);
465
+ carriers.push(carrier);
474
466
  }
475
- if (this.keyHandlers.size === 0) return;
476
- let didAddAny = false;
467
+ return () => {
468
+ for (let i = 0; i < carriers.length; i++) {
469
+ this.removeHandler(carriers[i], handler);
470
+ }
471
+ };
472
+ }
473
+ unsubscribeFromKey(key, handler) {
474
+ const carrier = this.resolver.findCarrier(key);
475
+ if (carrier) this.removeHandler(carrier, handler);
476
+ }
477
+ unsubscribeFromKeys(keys, handler) {
477
478
  for (let i = 0; i < keys.length; i++) {
478
- const key = keys[i];
479
- const handlers = this.keyHandlers.get(key);
480
- if (!handlers || handlers.size === 0) continue;
481
- this.updatedKeys.add(key);
482
- didAddAny = true;
479
+ this.unsubscribeFromKey(keys[i], handler);
483
480
  }
484
- if (!didAddAny) return;
485
- if (this.deliveryMode === "immediate") {
486
- this.deliverImmediate();
487
- return;
481
+ }
482
+ addHandler(carrier, handler) {
483
+ let subscribers = carrier.subscribers;
484
+ if (!subscribers) {
485
+ subscribers = /* @__PURE__ */ new Set();
486
+ carrier.subscribers = subscribers;
488
487
  }
489
- if (this.deliveryMode === "after_flush") {
490
- this.isAfterFlushDeliveryScheduled = true;
491
- return;
488
+ subscribers.add(handler);
489
+ this.subscribedCarriers.add(carrier);
490
+ }
491
+ removeHandler(carrier, handler) {
492
+ const subscribers = carrier.subscribers;
493
+ if (!subscribers) return;
494
+ subscribers.delete(handler);
495
+ if (subscribers.size === 0) {
496
+ this.subscribedCarriers.delete(carrier);
497
+ this.resolver.onCarrierEmptied?.(carrier);
492
498
  }
493
- if (this.isFlushEnqueued) return;
494
- this.isFlushEnqueued = true;
495
- this.dequeueFlush = this.queue.enqueue(this.onFlush);
499
+ }
500
+ /** Fast path: the writer already holds the carrier. */
501
+ markUpdatedCarrier(carrier) {
502
+ this.assertNotInFlush();
503
+ const subscribers = carrier.subscribers;
504
+ if (!subscribers || subscribers.size === 0) return;
505
+ if (carrier.dirty) return;
506
+ carrier.dirty = true;
507
+ this.dirtyCarriers.push(carrier);
508
+ this.scheduleFlush();
496
509
  }
497
510
  markUpdatedKey(key) {
498
- if (this.deliveryMode === "queue" && this.queue.isInFlush) {
499
- throw new Error(
500
- "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
501
- );
502
- }
503
- if (this.keyHandlers.size === 0) return;
504
- const handlers = this.keyHandlers.get(key);
505
- if (!handlers || handlers.size === 0) return;
506
- this.updatedKeys.add(key);
507
- if (this.deliveryMode === "immediate") {
508
- this.deliverImmediate();
509
- return;
510
- }
511
- if (this.deliveryMode === "after_flush") {
512
- this.isAfterFlushDeliveryScheduled = true;
513
- return;
514
- }
515
- if (this.isFlushEnqueued) return;
516
- this.isFlushEnqueued = true;
517
- this.dequeueFlush = this.queue.enqueue(this.onFlush);
511
+ const carrier = this.resolver.findCarrier(key);
512
+ if (carrier) this.markUpdatedCarrier(carrier);
513
+ else this.assertNotInFlush();
514
+ }
515
+ markUpdatedKeys(keys) {
516
+ for (let i = 0; i < keys.length; i++) this.markUpdatedKey(keys[i]);
518
517
  }
519
518
  markAllUpdated() {
520
- if (this.deliveryMode === "queue" && this.queue.isInFlush) {
521
- throw new Error(
522
- "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
523
- );
519
+ this.assertNotInFlush();
520
+ if (this.subscribedCarriers.size === 0) return;
521
+ this.subscribedCarriers.forEach((carrier) => {
522
+ if (carrier.dirty) return;
523
+ carrier.dirty = true;
524
+ this.dirtyCarriers.push(carrier);
525
+ });
526
+ this.scheduleFlush();
527
+ }
528
+ hasSubscriptions() {
529
+ return this.subscribedCarriers.size > 0;
530
+ }
531
+ getHandlerCount(key) {
532
+ return this.resolver.findCarrier(key)?.subscribers?.size ?? 0;
533
+ }
534
+ getMetrics() {
535
+ let totalHandlers = 0;
536
+ this.subscribedCarriers.forEach((carrier) => {
537
+ totalHandlers += carrier.subscribers?.size ?? 0;
538
+ });
539
+ const totalKeys = this.subscribedCarriers.size;
540
+ return {
541
+ totalKeys,
542
+ totalHandlers,
543
+ averageHandlersPerKey: totalKeys > 0 ? totalHandlers / totalKeys : 0,
544
+ queueLength: this.dirtyCarriers.length
545
+ };
546
+ }
547
+ destroy() {
548
+ if (this.dequeueFlush) {
549
+ this.dequeueFlush();
550
+ this.dequeueFlush = void 0;
524
551
  }
525
- if (this.keyHandlers.size === 0) return;
526
- this.keyHandlers.forEach((_handlers, key) => {
527
- this.updatedKeys.add(key);
552
+ this.subscribedCarriers.forEach((carrier) => {
553
+ carrier.subscribers?.clear();
554
+ carrier.subscribers = void 0;
528
555
  });
529
- if (this.deliveryMode === "immediate") {
530
- this.deliverImmediate();
531
- return;
556
+ this.subscribedCarriers.clear();
557
+ for (let i = 0; i < this.dirtyCarriers.length; i++) {
558
+ this.dirtyCarriers[i].dirty = false;
532
559
  }
533
- if (this.deliveryMode === "after_flush") {
534
- this.isAfterFlushDeliveryScheduled = true;
535
- return;
560
+ this.dirtyCarriers = [];
561
+ this.isFlushEnqueued = false;
562
+ }
563
+ assertNotInFlush() {
564
+ if (this.queue.isInFlush) {
565
+ throw new Error(
566
+ "OIMCarrierKeyedEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
567
+ );
536
568
  }
569
+ }
570
+ scheduleFlush() {
537
571
  if (this.isFlushEnqueued) return;
538
572
  this.isFlushEnqueued = true;
539
573
  this.dequeueFlush = this.queue.enqueue(this.onFlush);
540
574
  }
541
- deliverImmediate() {
542
- this.runSinglePass();
543
- }
544
- /**
545
- * Notifies all handlers for a key. The overwhelmingly common case is a
546
- * single subscriber per key (one component per pk), handled without any
547
- * allocation. For multiple subscribers a snapshot is taken so a handler may
548
- * (un)subscribe during iteration without corrupting the walk.
549
- */
550
- notifyHandlers(handlers) {
551
- if (handlers.size === 1) {
552
- const only = handlers.values().next().value;
575
+ notify(carrier) {
576
+ const subscribers = carrier.subscribers;
577
+ if (!subscribers || subscribers.size === 0) return;
578
+ if (subscribers.size === 1) {
579
+ const only = subscribers.values().next().value;
553
580
  if (only) only();
554
581
  return;
555
582
  }
556
583
  const snapshot = [];
557
- handlers.forEach((h) => snapshot.push(h));
584
+ subscribers.forEach((h) => snapshot.push(h));
558
585
  for (let i = 0; i < snapshot.length; i++) {
559
586
  const handler = snapshot[i];
560
- if (handlers.has(handler)) handler();
561
- }
562
- }
563
- /**
564
- * Deliver updated keys and notify handlers once.
565
- * Re-entrant updates during delivery are forbidden and will throw in markUpdatedKeys().
566
- */
567
- runSinglePass() {
568
- if (this.updatedKeys.size === 0) return;
569
- const flushingKeys = this.updatedKeys;
570
- this.updatedKeys = /* @__PURE__ */ new Set();
571
- if (this.keyHandlers.size === 0 || flushingKeys.size === 0) return;
572
- if (flushingKeys.size * 2 < this.keyHandlers.size) {
573
- flushingKeys.forEach((key) => {
574
- const handlers = this.keyHandlers.get(key);
575
- if (!handlers || handlers.size === 0) return;
576
- this.notifyHandlers(handlers);
577
- });
578
- } else {
579
- this.keyHandlers.forEach((handlers, key) => {
580
- if (!flushingKeys.has(key)) return;
581
- if (!handlers || handlers.size === 0) return;
582
- this.notifyHandlers(handlers);
583
- });
584
- }
585
- }
586
- destroy() {
587
- if (this.dequeueFlush) {
588
- this.dequeueFlush();
589
- this.dequeueFlush = void 0;
590
- }
591
- if (this.unsubscribeAfterFlushDelivery) {
592
- this.unsubscribeAfterFlushDelivery();
593
- this.unsubscribeAfterFlushDelivery = void 0;
587
+ if (subscribers.has(handler)) handler();
594
588
  }
595
- this.keyHandlers.forEach((handlers) => handlers.clear());
596
- this.keyHandlers.clear();
597
- this.updatedKeys.clear();
598
- }
599
- /**
600
- * Get performance metrics for monitoring and debugging
601
- */
602
- getMetrics() {
603
- let totalHandlers = 0;
604
- this.keyHandlers.forEach((handlers) => {
605
- totalHandlers += handlers.size;
606
- });
607
- return {
608
- totalKeys: this.keyHandlers.size,
609
- totalHandlers,
610
- averageHandlersPerKey: this.keyHandlers.size > 0 ? totalHandlers / this.keyHandlers.size : 0,
611
- queueLength: this.queue.length
612
- };
613
- }
614
- /**
615
- * Check if there are any active subscriptions
616
- */
617
- hasSubscriptions() {
618
- return this.keyHandlers.size > 0;
619
589
  }
620
- /**
621
- * Get the number of handlers for a specific key
622
- */
623
- getHandlerCount(key) {
624
- return this.keyHandlers.get(key)?.size ?? 0;
625
- }
626
- subscribeOnKey(key, handler) {
627
- let handlers = this.keyHandlers.get(key);
628
- if (!handlers) {
629
- handlers = /* @__PURE__ */ new Set();
630
- this.keyHandlers.set(key, handlers);
631
- }
632
- handlers.add(handler);
633
- return () => {
634
- this.unsubscribeFromKey(key, handler);
635
- };
590
+ };
591
+
592
+ // src/core/OIMKeyedCarrierResolver.ts
593
+ var OIMKeyedCarrierResolver = class {
594
+ constructor() {
595
+ this.carriers = /* @__PURE__ */ new Map();
636
596
  }
637
- unsubscribeFromKey(key, handler) {
638
- const handlers = this.keyHandlers.get(key);
639
- if (!handlers) return;
640
- handlers.delete(handler);
641
- if (handlers.size === 0) {
642
- this.keyHandlers.delete(key);
643
- this.updatedKeys.delete(key);
644
- }
645
- if (this.keyHandlers.size === 0) {
646
- this.updatedKeys.clear();
647
- if (this.dequeueFlush) {
648
- this.dequeueFlush();
649
- this.dequeueFlush = void 0;
650
- }
651
- this.isFlushEnqueued = false;
597
+ getOrReserveCarrier(key) {
598
+ let carrier = this.carriers.get(key);
599
+ if (!carrier) {
600
+ carrier = { key };
601
+ this.carriers.set(key, carrier);
652
602
  }
603
+ return carrier;
653
604
  }
654
- subscribeOnKeys(keys, handler) {
655
- const subscribedKeys = [];
656
- for (const key of keys) {
657
- let handlers = this.keyHandlers.get(key);
658
- if (!handlers) {
659
- handlers = /* @__PURE__ */ new Set();
660
- this.keyHandlers.set(key, handlers);
661
- }
662
- if (!handlers.has(handler)) {
663
- handlers.add(handler);
664
- subscribedKeys.push(key);
665
- }
666
- }
667
- return () => {
668
- for (const key of subscribedKeys) {
669
- this.unsubscribeFromKey(key, handler);
670
- }
671
- };
605
+ findCarrier(key) {
606
+ return this.carriers.get(key);
672
607
  }
673
- unsubscribeFromKeys(keys, handler) {
674
- for (const key of keys) {
675
- this.unsubscribeFromKey(key, handler);
676
- }
608
+ onCarrierEmptied(carrier) {
609
+ this.carriers.delete(carrier.key);
677
610
  }
678
611
  };
679
612
 
680
613
  // src/abstract/OIMReactiveIndex.ts
681
614
  var OIMReactiveIndex = class {
682
615
  constructor(queue, createIndex) {
683
- this.updateEmitter = new OIMUpdateEventEmitter(queue);
616
+ this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierResolver());
684
617
  this.index = createIndex(this.updateEmitter);
685
618
  }
686
619
  getEntitiesByKey(key) {
@@ -1048,154 +981,7 @@ var OIMCollection = class {
1048
981
  }
1049
982
  destroy() {
1050
983
  this.store.destroy();
1051
- this.emitter.offAll();
1052
- }
1053
- };
1054
-
1055
- // src/core/OIMCarrierKeyedEmitter.ts
1056
- var OIMCarrierKeyedEmitter = class {
1057
- constructor(queue, resolver) {
1058
- // Carriers with at least one subscriber — for markAllUpdated / metrics.
1059
- this.subscribedCarriers = /* @__PURE__ */ new Set();
1060
- this.dirtyCarriers = /* @__PURE__ */ new Set();
1061
- this.isFlushEnqueued = false;
1062
- this.onFlush = () => {
1063
- this.isFlushEnqueued = false;
1064
- this.dequeueFlush = void 0;
1065
- if (this.dirtyCarriers.size === 0) return;
1066
- const flushing = this.dirtyCarriers;
1067
- this.dirtyCarriers = /* @__PURE__ */ new Set();
1068
- flushing.forEach((carrier) => this.notify(carrier));
1069
- };
1070
- this.queue = queue;
1071
- this.resolver = resolver;
1072
- }
1073
- subscribeOnKey(key, handler) {
1074
- const carrier = this.resolver.getOrReserveCarrier(key);
1075
- this.addHandler(carrier, handler);
1076
- return () => this.removeHandler(carrier, handler);
1077
- }
1078
- subscribeOnKeys(keys, handler) {
1079
- const carriers = [];
1080
- for (let i = 0; i < keys.length; i++) {
1081
- const carrier = this.resolver.getOrReserveCarrier(keys[i]);
1082
- this.addHandler(carrier, handler);
1083
- carriers.push(carrier);
1084
- }
1085
- return () => {
1086
- for (let i = 0; i < carriers.length; i++) {
1087
- this.removeHandler(carriers[i], handler);
1088
- }
1089
- };
1090
- }
1091
- unsubscribeFromKey(key, handler) {
1092
- const carrier = this.resolver.findCarrier(key);
1093
- if (carrier) this.removeHandler(carrier, handler);
1094
- }
1095
- unsubscribeFromKeys(keys, handler) {
1096
- for (let i = 0; i < keys.length; i++) {
1097
- this.unsubscribeFromKey(keys[i], handler);
1098
- }
1099
- }
1100
- addHandler(carrier, handler) {
1101
- let subscribers = carrier.subscribers;
1102
- if (!subscribers) {
1103
- subscribers = /* @__PURE__ */ new Set();
1104
- carrier.subscribers = subscribers;
1105
- }
1106
- subscribers.add(handler);
1107
- this.subscribedCarriers.add(carrier);
1108
- }
1109
- removeHandler(carrier, handler) {
1110
- const subscribers = carrier.subscribers;
1111
- if (!subscribers) return;
1112
- subscribers.delete(handler);
1113
- if (subscribers.size === 0) {
1114
- this.subscribedCarriers.delete(carrier);
1115
- }
1116
- }
1117
- /** Fast path: the writer already holds the carrier. */
1118
- markUpdatedCarrier(carrier) {
1119
- this.assertNotInFlush();
1120
- const subscribers = carrier.subscribers;
1121
- if (!subscribers || subscribers.size === 0) return;
1122
- this.dirtyCarriers.add(carrier);
1123
- this.scheduleFlush();
1124
- }
1125
- markUpdatedKey(key) {
1126
- const carrier = this.resolver.findCarrier(key);
1127
- if (carrier) this.markUpdatedCarrier(carrier);
1128
- else this.assertNotInFlush();
1129
- }
1130
- markUpdatedKeys(keys) {
1131
- for (let i = 0; i < keys.length; i++) this.markUpdatedKey(keys[i]);
1132
- }
1133
- markAllUpdated() {
1134
- this.assertNotInFlush();
1135
- if (this.subscribedCarriers.size === 0) return;
1136
- this.subscribedCarriers.forEach(
1137
- (carrier) => this.dirtyCarriers.add(carrier)
1138
- );
1139
- this.scheduleFlush();
1140
- }
1141
- hasSubscriptions() {
1142
- return this.subscribedCarriers.size > 0;
1143
- }
1144
- getHandlerCount(key) {
1145
- return this.resolver.findCarrier(key)?.subscribers?.size ?? 0;
1146
- }
1147
- getMetrics() {
1148
- let totalHandlers = 0;
1149
- this.subscribedCarriers.forEach((carrier) => {
1150
- totalHandlers += carrier.subscribers?.size ?? 0;
1151
- });
1152
- const totalKeys = this.subscribedCarriers.size;
1153
- return {
1154
- totalKeys,
1155
- totalHandlers,
1156
- averageHandlersPerKey: totalKeys > 0 ? totalHandlers / totalKeys : 0,
1157
- queueLength: this.dirtyCarriers.size
1158
- };
1159
- }
1160
- destroy() {
1161
- if (this.dequeueFlush) {
1162
- this.dequeueFlush();
1163
- this.dequeueFlush = void 0;
1164
- }
1165
- this.subscribedCarriers.forEach((carrier) => {
1166
- carrier.subscribers?.clear();
1167
- carrier.subscribers = void 0;
1168
- });
1169
- this.subscribedCarriers.clear();
1170
- this.dirtyCarriers.clear();
1171
- this.isFlushEnqueued = false;
1172
- }
1173
- assertNotInFlush() {
1174
- if (this.queue.isInFlush) {
1175
- throw new Error(
1176
- "OIMCarrierKeyedEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
1177
- );
1178
- }
1179
- }
1180
- scheduleFlush() {
1181
- if (this.isFlushEnqueued) return;
1182
- this.isFlushEnqueued = true;
1183
- this.dequeueFlush = this.queue.enqueue(this.onFlush);
1184
- }
1185
- notify(carrier) {
1186
- const subscribers = carrier.subscribers;
1187
- if (!subscribers || subscribers.size === 0) return;
1188
- if (subscribers.size === 1) {
1189
- const only = subscribers.values().next().value;
1190
- if (only) only();
1191
- return;
1192
- }
1193
- const snapshot = [];
1194
- subscribers.forEach((h) => snapshot.push(h));
1195
- for (let i = 0; i < snapshot.length; i++) {
1196
- const handler = snapshot[i];
1197
- if (subscribers.has(handler)) handler();
1198
- }
984
+ this.emitter.offAll();
1199
985
  }
1200
986
  };
1201
987
 
@@ -1481,6 +1267,261 @@ var OIMCollectionIndexManualOrderedArrayBased = class extends OIMIndexManualOrde
1481
1267
  }
1482
1268
  };
1483
1269
 
1270
+ // src/enums/EOIMEventQueueEventType.ts
1271
+ var EOIMEventQueueEventType = /* @__PURE__ */ ((EOIMEventQueueEventType2) => {
1272
+ EOIMEventQueueEventType2[EOIMEventQueueEventType2["AFTER_FLUSH"] = 0] = "AFTER_FLUSH";
1273
+ EOIMEventQueueEventType2[EOIMEventQueueEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
1274
+ return EOIMEventQueueEventType2;
1275
+ })(EOIMEventQueueEventType || {});
1276
+
1277
+ // src/core/OIMUpdateEventEmitter.ts
1278
+ var OIMUpdateEventEmitter = class {
1279
+ constructor(queue, deliveryMode = "queue") {
1280
+ this.keyHandlers = /* @__PURE__ */ new Map();
1281
+ this.isFlushEnqueued = false;
1282
+ this.updatedKeys = /* @__PURE__ */ new Set();
1283
+ this.isAfterFlushDeliveryScheduled = false;
1284
+ // Stable handler reference to avoid allocating a new closure on every HAS_CHANGES.
1285
+ this.onFlush = () => {
1286
+ this.isFlushEnqueued = false;
1287
+ this.dequeueFlush = void 0;
1288
+ if (this.keyHandlers.size === 0) {
1289
+ this.updatedKeys.clear();
1290
+ return;
1291
+ }
1292
+ this.runSinglePass();
1293
+ };
1294
+ this.onAfterFlush = () => {
1295
+ if (!this.isAfterFlushDeliveryScheduled) return;
1296
+ this.isAfterFlushDeliveryScheduled = false;
1297
+ this.deliverImmediate();
1298
+ };
1299
+ this.queue = queue;
1300
+ this.deliveryMode = deliveryMode;
1301
+ if (this.deliveryMode === "after_flush") {
1302
+ this.unsubscribeAfterFlushDelivery = this.queue.emitter.on(
1303
+ 0 /* AFTER_FLUSH */,
1304
+ this.onAfterFlush
1305
+ );
1306
+ }
1307
+ }
1308
+ /**
1309
+ * Mark keys as updated and schedule delivery.
1310
+ *
1311
+ * If there are no subscriptions, this is a no-op and does not allocate or retain keys.
1312
+ */
1313
+ markUpdatedKeys(keys) {
1314
+ if (keys.length === 0) return;
1315
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
1316
+ throw new Error(
1317
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
1318
+ );
1319
+ }
1320
+ if (this.keyHandlers.size === 0) return;
1321
+ let didAddAny = false;
1322
+ for (let i = 0; i < keys.length; i++) {
1323
+ const key = keys[i];
1324
+ const handlers = this.keyHandlers.get(key);
1325
+ if (!handlers || handlers.size === 0) continue;
1326
+ this.updatedKeys.add(key);
1327
+ didAddAny = true;
1328
+ }
1329
+ if (!didAddAny) return;
1330
+ if (this.deliveryMode === "immediate") {
1331
+ this.deliverImmediate();
1332
+ return;
1333
+ }
1334
+ if (this.deliveryMode === "after_flush") {
1335
+ this.isAfterFlushDeliveryScheduled = true;
1336
+ return;
1337
+ }
1338
+ if (this.isFlushEnqueued) return;
1339
+ this.isFlushEnqueued = true;
1340
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
1341
+ }
1342
+ markUpdatedKey(key) {
1343
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
1344
+ throw new Error(
1345
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
1346
+ );
1347
+ }
1348
+ if (this.keyHandlers.size === 0) return;
1349
+ const handlers = this.keyHandlers.get(key);
1350
+ if (!handlers || handlers.size === 0) return;
1351
+ this.updatedKeys.add(key);
1352
+ if (this.deliveryMode === "immediate") {
1353
+ this.deliverImmediate();
1354
+ return;
1355
+ }
1356
+ if (this.deliveryMode === "after_flush") {
1357
+ this.isAfterFlushDeliveryScheduled = true;
1358
+ return;
1359
+ }
1360
+ if (this.isFlushEnqueued) return;
1361
+ this.isFlushEnqueued = true;
1362
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
1363
+ }
1364
+ markAllUpdated() {
1365
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
1366
+ throw new Error(
1367
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
1368
+ );
1369
+ }
1370
+ if (this.keyHandlers.size === 0) return;
1371
+ this.keyHandlers.forEach((_handlers, key) => {
1372
+ this.updatedKeys.add(key);
1373
+ });
1374
+ if (this.deliveryMode === "immediate") {
1375
+ this.deliverImmediate();
1376
+ return;
1377
+ }
1378
+ if (this.deliveryMode === "after_flush") {
1379
+ this.isAfterFlushDeliveryScheduled = true;
1380
+ return;
1381
+ }
1382
+ if (this.isFlushEnqueued) return;
1383
+ this.isFlushEnqueued = true;
1384
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
1385
+ }
1386
+ deliverImmediate() {
1387
+ this.runSinglePass();
1388
+ }
1389
+ /**
1390
+ * Notifies all handlers for a key. The overwhelmingly common case is a
1391
+ * single subscriber per key (one component per pk), handled without any
1392
+ * allocation. For multiple subscribers a snapshot is taken so a handler may
1393
+ * (un)subscribe during iteration without corrupting the walk.
1394
+ */
1395
+ notifyHandlers(handlers) {
1396
+ if (handlers.size === 1) {
1397
+ const only = handlers.values().next().value;
1398
+ if (only) only();
1399
+ return;
1400
+ }
1401
+ const snapshot = [];
1402
+ handlers.forEach((h) => snapshot.push(h));
1403
+ for (let i = 0; i < snapshot.length; i++) {
1404
+ const handler = snapshot[i];
1405
+ if (handlers.has(handler)) handler();
1406
+ }
1407
+ }
1408
+ /**
1409
+ * Deliver updated keys and notify handlers once.
1410
+ * Re-entrant updates during delivery are forbidden and will throw in markUpdatedKeys().
1411
+ */
1412
+ runSinglePass() {
1413
+ if (this.updatedKeys.size === 0) return;
1414
+ const flushingKeys = this.updatedKeys;
1415
+ this.updatedKeys = /* @__PURE__ */ new Set();
1416
+ if (this.keyHandlers.size === 0 || flushingKeys.size === 0) return;
1417
+ if (flushingKeys.size * 2 < this.keyHandlers.size) {
1418
+ flushingKeys.forEach((key) => {
1419
+ const handlers = this.keyHandlers.get(key);
1420
+ if (!handlers || handlers.size === 0) return;
1421
+ this.notifyHandlers(handlers);
1422
+ });
1423
+ } else {
1424
+ this.keyHandlers.forEach((handlers, key) => {
1425
+ if (!flushingKeys.has(key)) return;
1426
+ if (!handlers || handlers.size === 0) return;
1427
+ this.notifyHandlers(handlers);
1428
+ });
1429
+ }
1430
+ }
1431
+ destroy() {
1432
+ if (this.dequeueFlush) {
1433
+ this.dequeueFlush();
1434
+ this.dequeueFlush = void 0;
1435
+ }
1436
+ if (this.unsubscribeAfterFlushDelivery) {
1437
+ this.unsubscribeAfterFlushDelivery();
1438
+ this.unsubscribeAfterFlushDelivery = void 0;
1439
+ }
1440
+ this.keyHandlers.forEach((handlers) => handlers.clear());
1441
+ this.keyHandlers.clear();
1442
+ this.updatedKeys.clear();
1443
+ }
1444
+ /**
1445
+ * Get performance metrics for monitoring and debugging
1446
+ */
1447
+ getMetrics() {
1448
+ let totalHandlers = 0;
1449
+ this.keyHandlers.forEach((handlers) => {
1450
+ totalHandlers += handlers.size;
1451
+ });
1452
+ return {
1453
+ totalKeys: this.keyHandlers.size,
1454
+ totalHandlers,
1455
+ averageHandlersPerKey: this.keyHandlers.size > 0 ? totalHandlers / this.keyHandlers.size : 0,
1456
+ queueLength: this.queue.length
1457
+ };
1458
+ }
1459
+ /**
1460
+ * Check if there are any active subscriptions
1461
+ */
1462
+ hasSubscriptions() {
1463
+ return this.keyHandlers.size > 0;
1464
+ }
1465
+ /**
1466
+ * Get the number of handlers for a specific key
1467
+ */
1468
+ getHandlerCount(key) {
1469
+ return this.keyHandlers.get(key)?.size ?? 0;
1470
+ }
1471
+ subscribeOnKey(key, handler) {
1472
+ let handlers = this.keyHandlers.get(key);
1473
+ if (!handlers) {
1474
+ handlers = /* @__PURE__ */ new Set();
1475
+ this.keyHandlers.set(key, handlers);
1476
+ }
1477
+ handlers.add(handler);
1478
+ return () => {
1479
+ this.unsubscribeFromKey(key, handler);
1480
+ };
1481
+ }
1482
+ unsubscribeFromKey(key, handler) {
1483
+ const handlers = this.keyHandlers.get(key);
1484
+ if (!handlers) return;
1485
+ handlers.delete(handler);
1486
+ if (handlers.size === 0) {
1487
+ this.keyHandlers.delete(key);
1488
+ this.updatedKeys.delete(key);
1489
+ }
1490
+ if (this.keyHandlers.size === 0) {
1491
+ this.updatedKeys.clear();
1492
+ if (this.dequeueFlush) {
1493
+ this.dequeueFlush();
1494
+ this.dequeueFlush = void 0;
1495
+ }
1496
+ this.isFlushEnqueued = false;
1497
+ }
1498
+ }
1499
+ subscribeOnKeys(keys, handler) {
1500
+ const subscribedKeys = [];
1501
+ for (const key of keys) {
1502
+ let handlers = this.keyHandlers.get(key);
1503
+ if (!handlers) {
1504
+ handlers = /* @__PURE__ */ new Set();
1505
+ this.keyHandlers.set(key, handlers);
1506
+ }
1507
+ if (!handlers.has(handler)) {
1508
+ handlers.add(handler);
1509
+ subscribedKeys.push(key);
1510
+ }
1511
+ }
1512
+ return () => {
1513
+ for (const key of subscribedKeys) {
1514
+ this.unsubscribeFromKey(key, handler);
1515
+ }
1516
+ };
1517
+ }
1518
+ unsubscribeFromKeys(keys, handler) {
1519
+ for (const key of keys) {
1520
+ this.unsubscribeFromKey(key, handler);
1521
+ }
1522
+ }
1523
+ };
1524
+
1484
1525
  // src/modules/wrapper/index/OIMOrderedListCommandStream.ts
1485
1526
  var OIMOrderedListCommandStream = class {
1486
1527
  constructor(queue, index) {