@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.js
CHANGED
|
@@ -422,265 +422,197 @@ var OIMIndexArrayBased = class extends OIMIndex {
|
|
|
422
422
|
}
|
|
423
423
|
};
|
|
424
424
|
|
|
425
|
-
// src/
|
|
426
|
-
var
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
//
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
this.
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
438
|
+
if (this.dirtyCarriers.length === 0) return;
|
|
439
|
+
const flushing = this.dirtyCarriers;
|
|
440
|
+
this.dirtyCarriers = [];
|
|
441
|
+
let i = 0;
|
|
442
|
+
try {
|
|
443
|
+
for (; i < flushing.length; i++) {
|
|
444
|
+
flushing[i].dirty = false;
|
|
445
|
+
this.notify(flushing[i]);
|
|
446
|
+
}
|
|
447
|
+
} finally {
|
|
448
|
+
for (; i < flushing.length; i++) flushing[i].dirty = false;
|
|
446
449
|
}
|
|
447
|
-
this.runSinglePass();
|
|
448
|
-
};
|
|
449
|
-
this.onAfterFlush = () => {
|
|
450
|
-
if (!this.isAfterFlushDeliveryScheduled) return;
|
|
451
|
-
this.isAfterFlushDeliveryScheduled = false;
|
|
452
|
-
this.deliverImmediate();
|
|
453
450
|
};
|
|
454
451
|
this.queue = queue;
|
|
455
|
-
this.
|
|
456
|
-
if (this.deliveryMode === "after_flush") {
|
|
457
|
-
this.unsubscribeAfterFlushDelivery = this.queue.emitter.on(
|
|
458
|
-
0 /* AFTER_FLUSH */,
|
|
459
|
-
this.onAfterFlush
|
|
460
|
-
);
|
|
461
|
-
}
|
|
452
|
+
this.resolver = resolver;
|
|
462
453
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
);
|
|
454
|
+
subscribeOnKey(key, handler) {
|
|
455
|
+
const carrier = this.resolver.getOrReserveCarrier(key);
|
|
456
|
+
this.addHandler(carrier, handler);
|
|
457
|
+
return () => this.removeHandler(carrier, handler);
|
|
458
|
+
}
|
|
459
|
+
subscribeOnKeys(keys, handler) {
|
|
460
|
+
const carriers = [];
|
|
461
|
+
for (let i = 0; i < keys.length; i++) {
|
|
462
|
+
const carrier = this.resolver.getOrReserveCarrier(keys[i]);
|
|
463
|
+
this.addHandler(carrier, handler);
|
|
464
|
+
carriers.push(carrier);
|
|
474
465
|
}
|
|
475
|
-
|
|
476
|
-
|
|
466
|
+
return () => {
|
|
467
|
+
for (let i = 0; i < carriers.length; i++) {
|
|
468
|
+
this.removeHandler(carriers[i], handler);
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
unsubscribeFromKey(key, handler) {
|
|
473
|
+
const carrier = this.resolver.findCarrier(key);
|
|
474
|
+
if (carrier) this.removeHandler(carrier, handler);
|
|
475
|
+
}
|
|
476
|
+
unsubscribeFromKeys(keys, handler) {
|
|
477
477
|
for (let i = 0; i < keys.length; i++) {
|
|
478
|
-
|
|
479
|
-
const handlers = this.keyHandlers.get(key);
|
|
480
|
-
if (!handlers || handlers.size === 0) continue;
|
|
481
|
-
this.updatedKeys.add(key);
|
|
482
|
-
didAddAny = true;
|
|
478
|
+
this.unsubscribeFromKey(keys[i], handler);
|
|
483
479
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
480
|
+
}
|
|
481
|
+
addHandler(carrier, handler) {
|
|
482
|
+
let subscribers = carrier.subscribers;
|
|
483
|
+
if (!subscribers) {
|
|
484
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
485
|
+
carrier.subscribers = subscribers;
|
|
488
486
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
487
|
+
subscribers.add(handler);
|
|
488
|
+
this.subscribedCarriers.add(carrier);
|
|
489
|
+
}
|
|
490
|
+
removeHandler(carrier, handler) {
|
|
491
|
+
const subscribers = carrier.subscribers;
|
|
492
|
+
if (!subscribers) return;
|
|
493
|
+
subscribers.delete(handler);
|
|
494
|
+
if (subscribers.size === 0) {
|
|
495
|
+
this.subscribedCarriers.delete(carrier);
|
|
496
|
+
this.resolver.onCarrierEmptied?.(carrier);
|
|
492
497
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
498
|
+
}
|
|
499
|
+
/** Fast path: the writer already holds the carrier. */
|
|
500
|
+
markUpdatedCarrier(carrier) {
|
|
501
|
+
this.assertNotInFlush();
|
|
502
|
+
const subscribers = carrier.subscribers;
|
|
503
|
+
if (!subscribers || subscribers.size === 0) return;
|
|
504
|
+
if (carrier.dirty) return;
|
|
505
|
+
carrier.dirty = true;
|
|
506
|
+
this.dirtyCarriers.push(carrier);
|
|
507
|
+
this.scheduleFlush();
|
|
496
508
|
}
|
|
497
509
|
markUpdatedKey(key) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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);
|
|
510
|
+
const carrier = this.resolver.findCarrier(key);
|
|
511
|
+
if (carrier) this.markUpdatedCarrier(carrier);
|
|
512
|
+
else this.assertNotInFlush();
|
|
513
|
+
}
|
|
514
|
+
markUpdatedKeys(keys) {
|
|
515
|
+
for (let i = 0; i < keys.length; i++) this.markUpdatedKey(keys[i]);
|
|
518
516
|
}
|
|
519
517
|
markAllUpdated() {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
);
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
this.keyHandlers.forEach((_handlers, key) => {
|
|
527
|
-
this.updatedKeys.add(key);
|
|
518
|
+
this.assertNotInFlush();
|
|
519
|
+
if (this.subscribedCarriers.size === 0) return;
|
|
520
|
+
this.subscribedCarriers.forEach((carrier) => {
|
|
521
|
+
if (carrier.dirty) return;
|
|
522
|
+
carrier.dirty = true;
|
|
523
|
+
this.dirtyCarriers.push(carrier);
|
|
528
524
|
});
|
|
529
|
-
|
|
530
|
-
this.deliverImmediate();
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
if (this.deliveryMode === "after_flush") {
|
|
534
|
-
this.isAfterFlushDeliveryScheduled = true;
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
if (this.isFlushEnqueued) return;
|
|
538
|
-
this.isFlushEnqueued = true;
|
|
539
|
-
this.dequeueFlush = this.queue.enqueue(this.onFlush);
|
|
540
|
-
}
|
|
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;
|
|
553
|
-
if (only) only();
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
const snapshot = [];
|
|
557
|
-
handlers.forEach((h) => snapshot.push(h));
|
|
558
|
-
for (let i = 0; i < snapshot.length; i++) {
|
|
559
|
-
const handler = snapshot[i];
|
|
560
|
-
if (handlers.has(handler)) handler();
|
|
561
|
-
}
|
|
525
|
+
this.scheduleFlush();
|
|
562
526
|
}
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
}
|
|
527
|
+
hasSubscriptions() {
|
|
528
|
+
return this.subscribedCarriers.size > 0;
|
|
585
529
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
this.dequeueFlush();
|
|
589
|
-
this.dequeueFlush = void 0;
|
|
590
|
-
}
|
|
591
|
-
if (this.unsubscribeAfterFlushDelivery) {
|
|
592
|
-
this.unsubscribeAfterFlushDelivery();
|
|
593
|
-
this.unsubscribeAfterFlushDelivery = void 0;
|
|
594
|
-
}
|
|
595
|
-
this.keyHandlers.forEach((handlers) => handlers.clear());
|
|
596
|
-
this.keyHandlers.clear();
|
|
597
|
-
this.updatedKeys.clear();
|
|
530
|
+
getHandlerCount(key) {
|
|
531
|
+
return this.resolver.findCarrier(key)?.subscribers?.size ?? 0;
|
|
598
532
|
}
|
|
599
|
-
/**
|
|
600
|
-
* Get performance metrics for monitoring and debugging
|
|
601
|
-
*/
|
|
602
533
|
getMetrics() {
|
|
603
534
|
let totalHandlers = 0;
|
|
604
|
-
this.
|
|
605
|
-
totalHandlers +=
|
|
535
|
+
this.subscribedCarriers.forEach((carrier) => {
|
|
536
|
+
totalHandlers += carrier.subscribers?.size ?? 0;
|
|
606
537
|
});
|
|
538
|
+
const totalKeys = this.subscribedCarriers.size;
|
|
607
539
|
return {
|
|
608
|
-
totalKeys
|
|
540
|
+
totalKeys,
|
|
609
541
|
totalHandlers,
|
|
610
|
-
averageHandlersPerKey:
|
|
611
|
-
queueLength: this.
|
|
542
|
+
averageHandlersPerKey: totalKeys > 0 ? totalHandlers / totalKeys : 0,
|
|
543
|
+
queueLength: this.dirtyCarriers.length
|
|
612
544
|
};
|
|
613
545
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
546
|
+
destroy() {
|
|
547
|
+
if (this.isFlushEnqueued) {
|
|
548
|
+
this.queue.cancelTask(this.onFlush);
|
|
549
|
+
this.isFlushEnqueued = false;
|
|
550
|
+
}
|
|
551
|
+
this.subscribedCarriers.forEach((carrier) => {
|
|
552
|
+
carrier.subscribers?.clear();
|
|
553
|
+
carrier.subscribers = void 0;
|
|
554
|
+
});
|
|
555
|
+
this.subscribedCarriers.clear();
|
|
556
|
+
for (let i = 0; i < this.dirtyCarriers.length; i++) {
|
|
557
|
+
this.dirtyCarriers[i].dirty = false;
|
|
558
|
+
}
|
|
559
|
+
this.dirtyCarriers = [];
|
|
560
|
+
this.isFlushEnqueued = false;
|
|
625
561
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
562
|
+
assertNotInFlush() {
|
|
563
|
+
if (this.queue.isInFlush) {
|
|
564
|
+
throw new Error(
|
|
565
|
+
"OIMCarrierKeyedEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
|
|
566
|
+
);
|
|
631
567
|
}
|
|
632
|
-
handlers.add(handler);
|
|
633
|
-
return () => {
|
|
634
|
-
this.unsubscribeFromKey(key, handler);
|
|
635
|
-
};
|
|
636
568
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
569
|
+
scheduleFlush() {
|
|
570
|
+
if (this.isFlushEnqueued) return;
|
|
571
|
+
this.isFlushEnqueued = true;
|
|
572
|
+
this.queue.enqueueTask(this.onFlush);
|
|
573
|
+
}
|
|
574
|
+
notify(carrier) {
|
|
575
|
+
const subscribers = carrier.subscribers;
|
|
576
|
+
if (!subscribers || subscribers.size === 0) return;
|
|
577
|
+
if (subscribers.size === 1) {
|
|
578
|
+
const only = subscribers.values().next().value;
|
|
579
|
+
if (only) only();
|
|
580
|
+
return;
|
|
644
581
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
}
|
|
651
|
-
this.isFlushEnqueued = false;
|
|
582
|
+
const snapshot = [];
|
|
583
|
+
subscribers.forEach((h) => snapshot.push(h));
|
|
584
|
+
for (let i = 0; i < snapshot.length; i++) {
|
|
585
|
+
const handler = snapshot[i];
|
|
586
|
+
if (subscribers.has(handler)) handler();
|
|
652
587
|
}
|
|
653
588
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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
|
-
};
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
// src/core/OIMKeyedCarrierResolver.ts
|
|
592
|
+
var OIMKeyedCarrierResolver = class {
|
|
593
|
+
constructor() {
|
|
594
|
+
this.carriers = /* @__PURE__ */ new Map();
|
|
672
595
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
596
|
+
getOrReserveCarrier(key) {
|
|
597
|
+
let carrier = this.carriers.get(key);
|
|
598
|
+
if (!carrier) {
|
|
599
|
+
carrier = { key };
|
|
600
|
+
this.carriers.set(key, carrier);
|
|
676
601
|
}
|
|
602
|
+
return carrier;
|
|
603
|
+
}
|
|
604
|
+
findCarrier(key) {
|
|
605
|
+
return this.carriers.get(key);
|
|
606
|
+
}
|
|
607
|
+
onCarrierEmptied(carrier) {
|
|
608
|
+
this.carriers.delete(carrier.key);
|
|
677
609
|
}
|
|
678
610
|
};
|
|
679
611
|
|
|
680
612
|
// src/abstract/OIMReactiveIndex.ts
|
|
681
613
|
var OIMReactiveIndex = class {
|
|
682
614
|
constructor(queue, createIndex) {
|
|
683
|
-
this.updateEmitter = new
|
|
615
|
+
this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierResolver());
|
|
684
616
|
this.index = createIndex(this.updateEmitter);
|
|
685
617
|
}
|
|
686
618
|
getEntitiesByKey(key) {
|
|
@@ -1011,192 +943,45 @@ var OIMCollection = class {
|
|
|
1011
943
|
removeManyByPks(pks) {
|
|
1012
944
|
if (pks.length === 0) return;
|
|
1013
945
|
this.store.removeManyByPks(pks);
|
|
1014
|
-
this.emitter.emit(0 /* UPDATE */, { pks });
|
|
1015
|
-
}
|
|
1016
|
-
clear() {
|
|
1017
|
-
this.store.clear();
|
|
1018
|
-
this.emitter.emit(0 /* UPDATE */, { pks: [] });
|
|
1019
|
-
}
|
|
1020
|
-
countAll() {
|
|
1021
|
-
return this.store.countAll();
|
|
1022
|
-
}
|
|
1023
|
-
getAll() {
|
|
1024
|
-
return this.store.getAll();
|
|
1025
|
-
}
|
|
1026
|
-
getAllPks() {
|
|
1027
|
-
return this.store.getAllPks();
|
|
1028
|
-
}
|
|
1029
|
-
upsertOneWithoutNotifications(entity) {
|
|
1030
|
-
const pk = this.selectPk(entity);
|
|
1031
|
-
return this.upsertOneWithoutNotificationsByPk(pk, entity);
|
|
1032
|
-
}
|
|
1033
|
-
upsertOneWithoutNotificationsByPk(pk, entity) {
|
|
1034
|
-
if (!pk) {
|
|
1035
|
-
throw new Error(
|
|
1036
|
-
`[OIMCollection]: PK is required to upsert an entity ${JSON.stringify(entity)}`
|
|
1037
|
-
);
|
|
1038
|
-
}
|
|
1039
|
-
const existingEntity = this.store.getOneByPk(pk);
|
|
1040
|
-
if (existingEntity) {
|
|
1041
|
-
return this.store.setOneByPk(
|
|
1042
|
-
pk,
|
|
1043
|
-
this.updateEntity(entity, existingEntity)
|
|
1044
|
-
);
|
|
1045
|
-
} else {
|
|
1046
|
-
return this.store.setOneByPk(pk, entity);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
destroy() {
|
|
1050
|
-
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();
|
|
946
|
+
this.emitter.emit(0 /* UPDATE */, { pks });
|
|
1140
947
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
948
|
+
clear() {
|
|
949
|
+
this.store.clear();
|
|
950
|
+
this.emitter.emit(0 /* UPDATE */, { pks: [] });
|
|
1143
951
|
}
|
|
1144
|
-
|
|
1145
|
-
return this.
|
|
952
|
+
countAll() {
|
|
953
|
+
return this.store.countAll();
|
|
1146
954
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
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
|
-
};
|
|
955
|
+
getAll() {
|
|
956
|
+
return this.store.getAll();
|
|
1159
957
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
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;
|
|
958
|
+
getAllPks() {
|
|
959
|
+
return this.store.getAllPks();
|
|
1172
960
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
961
|
+
upsertOneWithoutNotifications(entity) {
|
|
962
|
+
const pk = this.selectPk(entity);
|
|
963
|
+
return this.upsertOneWithoutNotificationsByPk(pk, entity);
|
|
964
|
+
}
|
|
965
|
+
upsertOneWithoutNotificationsByPk(pk, entity) {
|
|
966
|
+
if (!pk) {
|
|
1175
967
|
throw new Error(
|
|
1176
|
-
|
|
968
|
+
`[OIMCollection]: PK is required to upsert an entity ${JSON.stringify(entity)}`
|
|
1177
969
|
);
|
|
1178
970
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
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();
|
|
971
|
+
const existingEntity = this.store.getOneByPk(pk);
|
|
972
|
+
if (existingEntity) {
|
|
973
|
+
return this.store.setOneByPk(
|
|
974
|
+
pk,
|
|
975
|
+
this.updateEntity(entity, existingEntity)
|
|
976
|
+
);
|
|
977
|
+
} else {
|
|
978
|
+
return this.store.setOneByPk(pk, entity);
|
|
1198
979
|
}
|
|
1199
980
|
}
|
|
981
|
+
destroy() {
|
|
982
|
+
this.store.destroy();
|
|
983
|
+
this.emitter.offAll();
|
|
984
|
+
}
|
|
1200
985
|
};
|
|
1201
986
|
|
|
1202
987
|
// src/core/OIMReactiveCollection.ts
|
|
@@ -1210,7 +995,6 @@ var OIMReactiveCollection = class extends OIMCollection {
|
|
|
1210
995
|
this.runAnyUpdate = () => {
|
|
1211
996
|
if (!this.isAnyUpdateScheduled) return;
|
|
1212
997
|
this.isAnyUpdateScheduled = false;
|
|
1213
|
-
this.dequeueAnyUpdate = void 0;
|
|
1214
998
|
const pks = this.isAnyUpdateClearPending ? [] : Array.from(this.pendingAnyUpdatePks);
|
|
1215
999
|
this.pendingAnyUpdatePks.clear();
|
|
1216
1000
|
this.isAnyUpdateClearPending = false;
|
|
@@ -1350,13 +1134,13 @@ var OIMReactiveCollection = class extends OIMCollection {
|
|
|
1350
1134
|
ensureAnyUpdateEnqueued() {
|
|
1351
1135
|
if (this.isAnyUpdateScheduled) return;
|
|
1352
1136
|
this.isAnyUpdateScheduled = true;
|
|
1353
|
-
this.
|
|
1137
|
+
this.queue.enqueueTask(this.runAnyUpdate);
|
|
1354
1138
|
}
|
|
1355
1139
|
destroy() {
|
|
1356
1140
|
this.updateEmitter.destroy();
|
|
1357
|
-
if (this.
|
|
1358
|
-
this.
|
|
1359
|
-
this.
|
|
1141
|
+
if (this.isAnyUpdateScheduled) {
|
|
1142
|
+
this.queue.cancelTask(this.runAnyUpdate);
|
|
1143
|
+
this.isAnyUpdateScheduled = false;
|
|
1360
1144
|
}
|
|
1361
1145
|
this.anyUpdateHandlers.clear();
|
|
1362
1146
|
this.pendingAnyUpdatePks.clear();
|
|
@@ -1375,109 +1159,362 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
|
|
|
1375
1159
|
}
|
|
1376
1160
|
return;
|
|
1377
1161
|
}
|
|
1378
|
-
if (this.store.getOneByKey(key) !== void 0) {
|
|
1379
|
-
this.store.removeOneByKey(key);
|
|
1380
|
-
this.emitUpdate([key]);
|
|
1162
|
+
if (this.store.getOneByKey(key) !== void 0) {
|
|
1163
|
+
this.store.removeOneByKey(key);
|
|
1164
|
+
this.emitUpdate([key]);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
pushSlot(key, slot) {
|
|
1168
|
+
const list = this.getOrCreateList(key);
|
|
1169
|
+
const index = list.length;
|
|
1170
|
+
list.push(slot);
|
|
1171
|
+
this.emitUpdate([key]);
|
|
1172
|
+
return index;
|
|
1173
|
+
}
|
|
1174
|
+
insertSlotAt(key, index, slot) {
|
|
1175
|
+
const list = this.getOrCreateList(key);
|
|
1176
|
+
const safeIndex = Math.max(0, Math.min(index, list.length));
|
|
1177
|
+
list.splice(safeIndex, 0, slot);
|
|
1178
|
+
this.emitUpdate([key]);
|
|
1179
|
+
return safeIndex;
|
|
1180
|
+
}
|
|
1181
|
+
removeAt(key, index) {
|
|
1182
|
+
const list = this.store.getOneByKey(key);
|
|
1183
|
+
if (!list) return void 0;
|
|
1184
|
+
if (index < 0 || index >= list.length) return void 0;
|
|
1185
|
+
const [removed] = list.splice(index, 1);
|
|
1186
|
+
if (list.length === 0) {
|
|
1187
|
+
this.store.removeOneByKey(key);
|
|
1188
|
+
}
|
|
1189
|
+
this.emitUpdate([key]);
|
|
1190
|
+
return removed;
|
|
1191
|
+
}
|
|
1192
|
+
move(key, fromIndex, toIndex) {
|
|
1193
|
+
const list = this.store.getOneByKey(key);
|
|
1194
|
+
if (!list) return void 0;
|
|
1195
|
+
if (fromIndex < 0 || fromIndex >= list.length) return void 0;
|
|
1196
|
+
if (toIndex < 0 || toIndex >= list.length) return void 0;
|
|
1197
|
+
if (fromIndex === toIndex) return list[fromIndex];
|
|
1198
|
+
const [slot] = list.splice(fromIndex, 1);
|
|
1199
|
+
if (slot === void 0) return void 0;
|
|
1200
|
+
list.splice(toIndex, 0, slot);
|
|
1201
|
+
this.emitUpdate([key]);
|
|
1202
|
+
return slot;
|
|
1203
|
+
}
|
|
1204
|
+
resetSlots(key, slots) {
|
|
1205
|
+
if (slots.length === 0) {
|
|
1206
|
+
if (this.store.getOneByKey(key) !== void 0) {
|
|
1207
|
+
this.store.removeOneByKey(key);
|
|
1208
|
+
this.emitUpdate([key]);
|
|
1209
|
+
}
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
this.store.setOneByKey(key, slots.slice());
|
|
1213
|
+
this.emitUpdate([key]);
|
|
1214
|
+
}
|
|
1215
|
+
getOrCreateList(key) {
|
|
1216
|
+
let list = this.store.getOneByKey(key);
|
|
1217
|
+
if (!list) {
|
|
1218
|
+
list = [];
|
|
1219
|
+
this.store.setOneByKey(key, list);
|
|
1220
|
+
}
|
|
1221
|
+
return list;
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
// src/modules/wrapper/index/OIMCollectionIndexManualOrderedArrayBased.ts
|
|
1226
|
+
var OIMCollectionIndexManualOrderedArrayBased = class extends OIMIndexManualOrderedArrayBased {
|
|
1227
|
+
constructor(opts) {
|
|
1228
|
+
super();
|
|
1229
|
+
this.resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getSlotByPk(pk) : opts.resolveSlot;
|
|
1230
|
+
}
|
|
1231
|
+
push(key, pk) {
|
|
1232
|
+
return this.pushSlot(key, this.resolveRequiredSlot(pk));
|
|
1233
|
+
}
|
|
1234
|
+
insertAt(key, index, pk) {
|
|
1235
|
+
return this.insertSlotAt(key, index, this.resolveRequiredSlot(pk));
|
|
1236
|
+
}
|
|
1237
|
+
reset(key, pks) {
|
|
1238
|
+
this.resetSlots(key, this.resolveSlots(pks));
|
|
1239
|
+
}
|
|
1240
|
+
getEntitiesByKey(key) {
|
|
1241
|
+
return super.getEntitiesByKey(key);
|
|
1242
|
+
}
|
|
1243
|
+
getEntitiesByKeys(keys) {
|
|
1244
|
+
return super.getEntitiesByKeys(keys);
|
|
1245
|
+
}
|
|
1246
|
+
resolvePk(pk) {
|
|
1247
|
+
return this.resolveRequiredSlot(pk);
|
|
1248
|
+
}
|
|
1249
|
+
resolveSlots(pks) {
|
|
1250
|
+
const slots = [];
|
|
1251
|
+
slots.length = pks.length;
|
|
1252
|
+
for (let i = 0; i < pks.length; i++) {
|
|
1253
|
+
slots[i] = this.resolveRequiredSlot(pks[i]);
|
|
1254
|
+
}
|
|
1255
|
+
return slots;
|
|
1256
|
+
}
|
|
1257
|
+
resolveRequiredSlot(pk) {
|
|
1258
|
+
const slot = this.resolveSlot(pk);
|
|
1259
|
+
if (!slot) {
|
|
1260
|
+
throw new Error(
|
|
1261
|
+
`[OIMCollectionIndexManualOrderedArrayBased]: Unable to resolve slot for PK "${String(pk)}".`
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
return slot;
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
|
|
1268
|
+
// src/enums/EOIMEventQueueEventType.ts
|
|
1269
|
+
var EOIMEventQueueEventType = /* @__PURE__ */ ((EOIMEventQueueEventType2) => {
|
|
1270
|
+
EOIMEventQueueEventType2[EOIMEventQueueEventType2["AFTER_FLUSH"] = 0] = "AFTER_FLUSH";
|
|
1271
|
+
EOIMEventQueueEventType2[EOIMEventQueueEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
|
|
1272
|
+
return EOIMEventQueueEventType2;
|
|
1273
|
+
})(EOIMEventQueueEventType || {});
|
|
1274
|
+
|
|
1275
|
+
// src/core/OIMUpdateEventEmitter.ts
|
|
1276
|
+
var OIMUpdateEventEmitter = class {
|
|
1277
|
+
constructor(queue, deliveryMode = "queue") {
|
|
1278
|
+
this.keyHandlers = /* @__PURE__ */ new Map();
|
|
1279
|
+
this.isFlushEnqueued = false;
|
|
1280
|
+
this.updatedKeys = /* @__PURE__ */ new Set();
|
|
1281
|
+
this.isAfterFlushDeliveryScheduled = false;
|
|
1282
|
+
// Stable handler reference to avoid allocating a new closure on every HAS_CHANGES.
|
|
1283
|
+
this.onFlush = () => {
|
|
1284
|
+
this.isFlushEnqueued = false;
|
|
1285
|
+
if (this.keyHandlers.size === 0) {
|
|
1286
|
+
this.updatedKeys.clear();
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
this.runSinglePass();
|
|
1290
|
+
};
|
|
1291
|
+
this.onAfterFlush = () => {
|
|
1292
|
+
if (!this.isAfterFlushDeliveryScheduled) return;
|
|
1293
|
+
this.isAfterFlushDeliveryScheduled = false;
|
|
1294
|
+
this.deliverImmediate();
|
|
1295
|
+
};
|
|
1296
|
+
this.queue = queue;
|
|
1297
|
+
this.deliveryMode = deliveryMode;
|
|
1298
|
+
if (this.deliveryMode === "after_flush") {
|
|
1299
|
+
this.unsubscribeAfterFlushDelivery = this.queue.emitter.on(
|
|
1300
|
+
0 /* AFTER_FLUSH */,
|
|
1301
|
+
this.onAfterFlush
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Mark keys as updated and schedule delivery.
|
|
1307
|
+
*
|
|
1308
|
+
* If there are no subscriptions, this is a no-op and does not allocate or retain keys.
|
|
1309
|
+
*/
|
|
1310
|
+
markUpdatedKeys(keys) {
|
|
1311
|
+
if (keys.length === 0) return;
|
|
1312
|
+
if (this.deliveryMode === "queue" && this.queue.isInFlush) {
|
|
1313
|
+
throw new Error(
|
|
1314
|
+
"OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
if (this.keyHandlers.size === 0) return;
|
|
1318
|
+
let didAddAny = false;
|
|
1319
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1320
|
+
const key = keys[i];
|
|
1321
|
+
const handlers = this.keyHandlers.get(key);
|
|
1322
|
+
if (!handlers || handlers.size === 0) continue;
|
|
1323
|
+
this.updatedKeys.add(key);
|
|
1324
|
+
didAddAny = true;
|
|
1325
|
+
}
|
|
1326
|
+
if (!didAddAny) return;
|
|
1327
|
+
if (this.deliveryMode === "immediate") {
|
|
1328
|
+
this.deliverImmediate();
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
if (this.deliveryMode === "after_flush") {
|
|
1332
|
+
this.isAfterFlushDeliveryScheduled = true;
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
if (this.isFlushEnqueued) return;
|
|
1336
|
+
this.isFlushEnqueued = true;
|
|
1337
|
+
this.queue.enqueueTask(this.onFlush);
|
|
1338
|
+
}
|
|
1339
|
+
markUpdatedKey(key) {
|
|
1340
|
+
if (this.deliveryMode === "queue" && this.queue.isInFlush) {
|
|
1341
|
+
throw new Error(
|
|
1342
|
+
"OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
if (this.keyHandlers.size === 0) return;
|
|
1346
|
+
const handlers = this.keyHandlers.get(key);
|
|
1347
|
+
if (!handlers || handlers.size === 0) return;
|
|
1348
|
+
this.updatedKeys.add(key);
|
|
1349
|
+
if (this.deliveryMode === "immediate") {
|
|
1350
|
+
this.deliverImmediate();
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
if (this.deliveryMode === "after_flush") {
|
|
1354
|
+
this.isAfterFlushDeliveryScheduled = true;
|
|
1355
|
+
return;
|
|
1381
1356
|
}
|
|
1357
|
+
if (this.isFlushEnqueued) return;
|
|
1358
|
+
this.isFlushEnqueued = true;
|
|
1359
|
+
this.queue.enqueueTask(this.onFlush);
|
|
1382
1360
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
return index;
|
|
1389
|
-
}
|
|
1390
|
-
insertSlotAt(key, index, slot) {
|
|
1391
|
-
const list = this.getOrCreateList(key);
|
|
1392
|
-
const safeIndex = Math.max(0, Math.min(index, list.length));
|
|
1393
|
-
list.splice(safeIndex, 0, slot);
|
|
1394
|
-
this.emitUpdate([key]);
|
|
1395
|
-
return safeIndex;
|
|
1396
|
-
}
|
|
1397
|
-
removeAt(key, index) {
|
|
1398
|
-
const list = this.store.getOneByKey(key);
|
|
1399
|
-
if (!list) return void 0;
|
|
1400
|
-
if (index < 0 || index >= list.length) return void 0;
|
|
1401
|
-
const [removed] = list.splice(index, 1);
|
|
1402
|
-
if (list.length === 0) {
|
|
1403
|
-
this.store.removeOneByKey(key);
|
|
1361
|
+
markAllUpdated() {
|
|
1362
|
+
if (this.deliveryMode === "queue" && this.queue.isInFlush) {
|
|
1363
|
+
throw new Error(
|
|
1364
|
+
"OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
|
|
1365
|
+
);
|
|
1404
1366
|
}
|
|
1405
|
-
this.
|
|
1406
|
-
|
|
1367
|
+
if (this.keyHandlers.size === 0) return;
|
|
1368
|
+
this.keyHandlers.forEach((_handlers, key) => {
|
|
1369
|
+
this.updatedKeys.add(key);
|
|
1370
|
+
});
|
|
1371
|
+
if (this.deliveryMode === "immediate") {
|
|
1372
|
+
this.deliverImmediate();
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
if (this.deliveryMode === "after_flush") {
|
|
1376
|
+
this.isAfterFlushDeliveryScheduled = true;
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
if (this.isFlushEnqueued) return;
|
|
1380
|
+
this.isFlushEnqueued = true;
|
|
1381
|
+
this.queue.enqueueTask(this.onFlush);
|
|
1407
1382
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
if (!list) return void 0;
|
|
1411
|
-
if (fromIndex < 0 || fromIndex >= list.length) return void 0;
|
|
1412
|
-
if (toIndex < 0 || toIndex >= list.length) return void 0;
|
|
1413
|
-
if (fromIndex === toIndex) return list[fromIndex];
|
|
1414
|
-
const [slot] = list.splice(fromIndex, 1);
|
|
1415
|
-
if (slot === void 0) return void 0;
|
|
1416
|
-
list.splice(toIndex, 0, slot);
|
|
1417
|
-
this.emitUpdate([key]);
|
|
1418
|
-
return slot;
|
|
1383
|
+
deliverImmediate() {
|
|
1384
|
+
this.runSinglePass();
|
|
1419
1385
|
}
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1386
|
+
/**
|
|
1387
|
+
* Notifies all handlers for a key. The overwhelmingly common case is a
|
|
1388
|
+
* single subscriber per key (one component per pk), handled without any
|
|
1389
|
+
* allocation. For multiple subscribers a snapshot is taken so a handler may
|
|
1390
|
+
* (un)subscribe during iteration without corrupting the walk.
|
|
1391
|
+
*/
|
|
1392
|
+
notifyHandlers(handlers) {
|
|
1393
|
+
if (handlers.size === 1) {
|
|
1394
|
+
const only = handlers.values().next().value;
|
|
1395
|
+
if (only) only();
|
|
1426
1396
|
return;
|
|
1427
1397
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
if (!list) {
|
|
1434
|
-
list = [];
|
|
1435
|
-
this.store.setOneByKey(key, list);
|
|
1398
|
+
const snapshot = [];
|
|
1399
|
+
handlers.forEach((h) => snapshot.push(h));
|
|
1400
|
+
for (let i = 0; i < snapshot.length; i++) {
|
|
1401
|
+
const handler = snapshot[i];
|
|
1402
|
+
if (handlers.has(handler)) handler();
|
|
1436
1403
|
}
|
|
1437
|
-
return list;
|
|
1438
1404
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1405
|
+
/**
|
|
1406
|
+
* Deliver updated keys and notify handlers once.
|
|
1407
|
+
* Re-entrant updates during delivery are forbidden and will throw in markUpdatedKeys().
|
|
1408
|
+
*/
|
|
1409
|
+
runSinglePass() {
|
|
1410
|
+
if (this.updatedKeys.size === 0) return;
|
|
1411
|
+
const flushingKeys = this.updatedKeys;
|
|
1412
|
+
this.updatedKeys = /* @__PURE__ */ new Set();
|
|
1413
|
+
if (this.keyHandlers.size === 0 || flushingKeys.size === 0) return;
|
|
1414
|
+
if (flushingKeys.size * 2 < this.keyHandlers.size) {
|
|
1415
|
+
flushingKeys.forEach((key) => {
|
|
1416
|
+
const handlers = this.keyHandlers.get(key);
|
|
1417
|
+
if (!handlers || handlers.size === 0) return;
|
|
1418
|
+
this.notifyHandlers(handlers);
|
|
1419
|
+
});
|
|
1420
|
+
} else {
|
|
1421
|
+
this.keyHandlers.forEach((handlers, key) => {
|
|
1422
|
+
if (!flushingKeys.has(key)) return;
|
|
1423
|
+
if (!handlers || handlers.size === 0) return;
|
|
1424
|
+
this.notifyHandlers(handlers);
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1446
1427
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1428
|
+
destroy() {
|
|
1429
|
+
if (this.isFlushEnqueued) {
|
|
1430
|
+
this.queue.cancelTask(this.onFlush);
|
|
1431
|
+
this.isFlushEnqueued = false;
|
|
1432
|
+
}
|
|
1433
|
+
if (this.unsubscribeAfterFlushDelivery) {
|
|
1434
|
+
this.unsubscribeAfterFlushDelivery();
|
|
1435
|
+
this.unsubscribeAfterFlushDelivery = void 0;
|
|
1436
|
+
}
|
|
1437
|
+
this.keyHandlers.forEach((handlers) => handlers.clear());
|
|
1438
|
+
this.keyHandlers.clear();
|
|
1439
|
+
this.updatedKeys.clear();
|
|
1449
1440
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1441
|
+
/**
|
|
1442
|
+
* Get performance metrics for monitoring and debugging
|
|
1443
|
+
*/
|
|
1444
|
+
getMetrics() {
|
|
1445
|
+
let totalHandlers = 0;
|
|
1446
|
+
this.keyHandlers.forEach((handlers) => {
|
|
1447
|
+
totalHandlers += handlers.size;
|
|
1448
|
+
});
|
|
1449
|
+
return {
|
|
1450
|
+
totalKeys: this.keyHandlers.size,
|
|
1451
|
+
totalHandlers,
|
|
1452
|
+
averageHandlersPerKey: this.keyHandlers.size > 0 ? totalHandlers / this.keyHandlers.size : 0,
|
|
1453
|
+
queueLength: this.queue.length
|
|
1454
|
+
};
|
|
1452
1455
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1456
|
+
/**
|
|
1457
|
+
* Check if there are any active subscriptions
|
|
1458
|
+
*/
|
|
1459
|
+
hasSubscriptions() {
|
|
1460
|
+
return this.keyHandlers.size > 0;
|
|
1455
1461
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1462
|
+
/**
|
|
1463
|
+
* Get the number of handlers for a specific key
|
|
1464
|
+
*/
|
|
1465
|
+
getHandlerCount(key) {
|
|
1466
|
+
return this.keyHandlers.get(key)?.size ?? 0;
|
|
1458
1467
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1468
|
+
subscribeOnKey(key, handler) {
|
|
1469
|
+
let handlers = this.keyHandlers.get(key);
|
|
1470
|
+
if (!handlers) {
|
|
1471
|
+
handlers = /* @__PURE__ */ new Set();
|
|
1472
|
+
this.keyHandlers.set(key, handlers);
|
|
1473
|
+
}
|
|
1474
|
+
handlers.add(handler);
|
|
1475
|
+
return () => {
|
|
1476
|
+
this.unsubscribeFromKey(key, handler);
|
|
1477
|
+
};
|
|
1461
1478
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1479
|
+
unsubscribeFromKey(key, handler) {
|
|
1480
|
+
const handlers = this.keyHandlers.get(key);
|
|
1481
|
+
if (!handlers) return;
|
|
1482
|
+
handlers.delete(handler);
|
|
1483
|
+
if (handlers.size === 0) {
|
|
1484
|
+
this.keyHandlers.delete(key);
|
|
1485
|
+
this.updatedKeys.delete(key);
|
|
1486
|
+
}
|
|
1487
|
+
if (this.keyHandlers.size === 0) {
|
|
1488
|
+
this.updatedKeys.clear();
|
|
1489
|
+
if (this.isFlushEnqueued) {
|
|
1490
|
+
this.queue.cancelTask(this.onFlush);
|
|
1491
|
+
}
|
|
1492
|
+
this.isFlushEnqueued = false;
|
|
1493
|
+
}
|
|
1464
1494
|
}
|
|
1465
|
-
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1495
|
+
subscribeOnKeys(keys, handler) {
|
|
1496
|
+
const subscribedKeys = [];
|
|
1497
|
+
for (const key of keys) {
|
|
1498
|
+
let handlers = this.keyHandlers.get(key);
|
|
1499
|
+
if (!handlers) {
|
|
1500
|
+
handlers = /* @__PURE__ */ new Set();
|
|
1501
|
+
this.keyHandlers.set(key, handlers);
|
|
1502
|
+
}
|
|
1503
|
+
if (!handlers.has(handler)) {
|
|
1504
|
+
handlers.add(handler);
|
|
1505
|
+
subscribedKeys.push(key);
|
|
1506
|
+
}
|
|
1470
1507
|
}
|
|
1471
|
-
return
|
|
1508
|
+
return () => {
|
|
1509
|
+
for (const key of subscribedKeys) {
|
|
1510
|
+
this.unsubscribeFromKey(key, handler);
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1472
1513
|
}
|
|
1473
|
-
|
|
1474
|
-
const
|
|
1475
|
-
|
|
1476
|
-
throw new Error(
|
|
1477
|
-
`[OIMCollectionIndexManualOrderedArrayBased]: Unable to resolve slot for PK "${String(pk)}".`
|
|
1478
|
-
);
|
|
1514
|
+
unsubscribeFromKeys(keys, handler) {
|
|
1515
|
+
for (const key of keys) {
|
|
1516
|
+
this.unsubscribeFromKey(key, handler);
|
|
1479
1517
|
}
|
|
1480
|
-
return slot;
|
|
1481
1518
|
}
|
|
1482
1519
|
};
|
|
1483
1520
|
|
|
@@ -2408,17 +2445,15 @@ var OIMObject = class {
|
|
|
2408
2445
|
}
|
|
2409
2446
|
setProperty(key, value) {
|
|
2410
2447
|
this.store.setProperty(key, value);
|
|
2411
|
-
this.
|
|
2448
|
+
this.onUpdatedKey(key);
|
|
2412
2449
|
}
|
|
2413
2450
|
merge(draft) {
|
|
2414
2451
|
this.store.merge(draft);
|
|
2415
|
-
this.
|
|
2416
|
-
keys: Object.keys(draft)
|
|
2417
|
-
});
|
|
2452
|
+
this.onUpdatedKeys(Object.keys(draft));
|
|
2418
2453
|
}
|
|
2419
2454
|
delete(key) {
|
|
2420
2455
|
this.store.delete(key);
|
|
2421
|
-
this.
|
|
2456
|
+
this.onUpdatedKey(key);
|
|
2422
2457
|
}
|
|
2423
2458
|
get(key) {
|
|
2424
2459
|
return this.store.get(key);
|
|
@@ -2428,7 +2463,24 @@ var OIMObject = class {
|
|
|
2428
2463
|
}
|
|
2429
2464
|
clear() {
|
|
2430
2465
|
this.store.clear();
|
|
2431
|
-
this.
|
|
2466
|
+
this.onUpdatedKeys([]);
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Write fan-out hooks. Split single-key vs multi-key so a reactive subclass
|
|
2470
|
+
* can mark its keyed emitter on the hot single-property path without
|
|
2471
|
+
* allocating a `{ keys: [key] }` payload. The base only fires the general
|
|
2472
|
+
* UPDATE event, and only when something is actually subscribed to it — so an
|
|
2473
|
+
* unobserved write skips the payload allocation entirely.
|
|
2474
|
+
*/
|
|
2475
|
+
onUpdatedKey(key) {
|
|
2476
|
+
if (this.emitter.hasHandlers(0 /* UPDATE */)) {
|
|
2477
|
+
this.emitter.emit(0 /* UPDATE */, { keys: [key] });
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
onUpdatedKeys(keys) {
|
|
2481
|
+
if (this.emitter.hasHandlers(0 /* UPDATE */)) {
|
|
2482
|
+
this.emitter.emit(0 /* UPDATE */, { keys });
|
|
2483
|
+
}
|
|
2432
2484
|
}
|
|
2433
2485
|
count() {
|
|
2434
2486
|
return this.store.count();
|
|
@@ -2452,10 +2504,20 @@ var OIMObject = class {
|
|
|
2452
2504
|
var OIMReactiveObject = class extends OIMObject {
|
|
2453
2505
|
constructor(queue, opts) {
|
|
2454
2506
|
super(opts);
|
|
2455
|
-
this.updateEmitter = new
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2507
|
+
this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierResolver());
|
|
2508
|
+
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Mark the written key(s) on the keyed emitter directly — no general-event
|
|
2511
|
+
* bridge, no `{ keys }` payload on the single-key path. `super` still fires
|
|
2512
|
+
* the general UPDATE event for any low-level `emitter` subscribers.
|
|
2513
|
+
*/
|
|
2514
|
+
onUpdatedKey(key) {
|
|
2515
|
+
this.updateEmitter.markUpdatedKey(key);
|
|
2516
|
+
super.onUpdatedKey(key);
|
|
2517
|
+
}
|
|
2518
|
+
onUpdatedKeys(keys) {
|
|
2519
|
+
this.updateEmitter.markUpdatedKeys(keys);
|
|
2520
|
+
super.onUpdatedKeys(keys);
|
|
2459
2521
|
}
|
|
2460
2522
|
subscribeOnKey(key, handler) {
|
|
2461
2523
|
return this.updateEmitter.subscribeOnKey(key, handler);
|
|
@@ -2559,6 +2621,9 @@ var OIMEventQueue = class {
|
|
|
2559
2621
|
constructor(options = {}) {
|
|
2560
2622
|
this.emitter = new OIMEventEmitter();
|
|
2561
2623
|
this.tasks = /* @__PURE__ */ new Set();
|
|
2624
|
+
// Reused buffer swapped in during flush so the pending set is never copied
|
|
2625
|
+
// (no per-flush array snapshot) and new enqueues land in a fresh set.
|
|
2626
|
+
this.tasksSpare = /* @__PURE__ */ new Set();
|
|
2562
2627
|
this.isFlushing = false;
|
|
2563
2628
|
this.scheduler = options.scheduler;
|
|
2564
2629
|
if (this.scheduler) {
|
|
@@ -2587,6 +2652,22 @@ var OIMEventQueue = class {
|
|
|
2587
2652
|
this.tasks.delete(task);
|
|
2588
2653
|
};
|
|
2589
2654
|
}
|
|
2655
|
+
/**
|
|
2656
|
+
* Allocation-free enqueue for a **stable** function reference (e.g. a bound
|
|
2657
|
+
* `onFlush` method). The function is stored directly — no wrapper, no
|
|
2658
|
+
* returned closure — and cancelled via {@link cancelTask}. Enqueuing the
|
|
2659
|
+
* same reference twice is idempotent (Set-deduped); callers that may run a
|
|
2660
|
+
* task more than once per flush should use {@link enqueue} instead.
|
|
2661
|
+
*/
|
|
2662
|
+
enqueueTask(fn) {
|
|
2663
|
+
this.tasks.add(fn);
|
|
2664
|
+
this.ensureScheduled();
|
|
2665
|
+
}
|
|
2666
|
+
/** Cancel a task scheduled via {@link enqueueTask}; safe to call mid-flush. */
|
|
2667
|
+
cancelTask(fn) {
|
|
2668
|
+
this.tasks.delete(fn);
|
|
2669
|
+
this.flushing?.delete(fn);
|
|
2670
|
+
}
|
|
2590
2671
|
get isInFlush() {
|
|
2591
2672
|
return this.isFlushing;
|
|
2592
2673
|
}
|
|
@@ -2606,11 +2687,13 @@ var OIMEventQueue = class {
|
|
|
2606
2687
|
this.scheduler?.cancel();
|
|
2607
2688
|
this.isFlushing = true;
|
|
2608
2689
|
this.emitter.emit(1 /* BEFORE_FLUSH */, void 0);
|
|
2609
|
-
const
|
|
2610
|
-
this.tasks.
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2690
|
+
const flushing = this.tasks;
|
|
2691
|
+
this.tasks = this.tasksSpare;
|
|
2692
|
+
this.flushing = flushing;
|
|
2693
|
+
for (const task of flushing) task();
|
|
2694
|
+
flushing.clear();
|
|
2695
|
+
this.tasksSpare = flushing;
|
|
2696
|
+
this.flushing = void 0;
|
|
2614
2697
|
this.isFlushing = false;
|
|
2615
2698
|
this.emitter.emit(0 /* AFTER_FLUSH */, void 0);
|
|
2616
2699
|
}
|