@oimdb/core 3.7.0 → 3.9.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
@@ -26,6 +26,7 @@ __export(index_exports, {
26
26
  EOIMEventQueueSchedulerEventType: () => EOIMEventQueueSchedulerEventType,
27
27
  EOIMIndexEventType: () => EOIMIndexEventType,
28
28
  EOIMObjectEventType: () => EOIMObjectEventType,
29
+ OIMBucketCarrierProvider: () => OIMBucketCarrierProvider,
29
30
  OIMCarrierKeyedEmitter: () => OIMCarrierKeyedEmitter,
30
31
  OIMCarrierSingleEmitter: () => OIMCarrierSingleEmitter,
31
32
  OIMCollection: () => OIMCollection,
@@ -38,6 +39,7 @@ __export(index_exports, {
38
39
  OIMCollectionSelectors: () => OIMCollectionSelectors,
39
40
  OIMCollectionStore: () => OIMCollectionStore,
40
41
  OIMCollectionStoreMapDriven: () => OIMCollectionStoreMapDriven,
42
+ OIMCollectionStoreTrieDriven: () => OIMCollectionStoreTrieDriven,
41
43
  OIMComparatorFactory: () => OIMComparatorFactory,
42
44
  OIMComputeRuntime: () => OIMComputeRuntime,
43
45
  OIMComputed: () => OIMComputed,
@@ -46,6 +48,7 @@ __export(index_exports, {
46
48
  OIMDerivedCollectionGlobalIndexSetBased: () => OIMDerivedCollectionGlobalIndexSetBased,
47
49
  OIMDerivedCollectionIndexArrayBased: () => OIMDerivedCollectionIndexArrayBased,
48
50
  OIMDerivedCollectionIndexSetBased: () => OIMDerivedCollectionIndexSetBased,
51
+ OIMDisposeScope: () => OIMDisposeScope,
49
52
  OIMEffect: () => OIMEffect,
50
53
  OIMEffectDependencyComputed: () => OIMEffectDependencyComputed,
51
54
  OIMEffectDependencyKeyed: () => OIMEffectDependencyKeyed,
@@ -88,6 +91,11 @@ __export(index_exports, {
88
91
  OIMIndexStoreMapDrivenArrayBased: () => OIMIndexStoreMapDrivenArrayBased,
89
92
  OIMIndexStoreMapDrivenSetBased: () => OIMIndexStoreMapDrivenSetBased,
90
93
  OIMIndexStoreSetBased: () => OIMIndexStoreSetBased,
94
+ OIMIndexStoreTrieDrivenArrayBased: () => OIMIndexStoreTrieDrivenArrayBased,
95
+ OIMIndexStoreTrieDrivenSetBased: () => OIMIndexStoreTrieDrivenSetBased,
96
+ OIMKeyPathCarrierProvider: () => OIMKeyPathCarrierProvider,
97
+ OIMKeyedBucketArrayBased: () => OIMKeyedBucketArrayBased,
98
+ OIMKeyedBucketSetBased: () => OIMKeyedBucketSetBased,
91
99
  OIMMap2Keys: () => OIMMap2Keys,
92
100
  OIMObject: () => OIMObject,
93
101
  OIMObjectStore: () => OIMObjectStore,
@@ -99,10 +107,13 @@ __export(index_exports, {
99
107
  OIMOrderedListCommandStream: () => OIMOrderedListCommandStream,
100
108
  OIMOrderedListCommandStreamDiffDriven: () => OIMOrderedListCommandStreamDiffDriven,
101
109
  OIMOrderedListMappedCommandStream: () => OIMOrderedListMappedCommandStream,
110
+ OIMPkCodecKeyPath: () => OIMPkCodecKeyPath,
102
111
  OIMPkSelectorFactory: () => OIMPkSelectorFactory,
103
112
  OIMReactiveCollection: () => OIMReactiveCollection,
104
113
  OIMReactiveCollectionGlobalIndexManualArrayBased: () => OIMReactiveCollectionGlobalIndexManualArrayBased,
105
114
  OIMReactiveCollectionGlobalIndexManualSetBased: () => OIMReactiveCollectionGlobalIndexManualSetBased,
115
+ OIMReactiveCollectionIndexCompositeTrieArrayBased: () => OIMReactiveCollectionIndexCompositeTrieArrayBased,
116
+ OIMReactiveCollectionIndexCompositeTrieSetBased: () => OIMReactiveCollectionIndexCompositeTrieSetBased,
106
117
  OIMReactiveCollectionIndexManualArrayBased: () => OIMReactiveCollectionIndexManualArrayBased,
107
118
  OIMReactiveCollectionIndexManualSetBased: () => OIMReactiveCollectionIndexManualSetBased,
108
119
  OIMReactiveGlobalIndex: () => OIMReactiveGlobalIndex,
@@ -117,10 +128,13 @@ __export(index_exports, {
117
128
  OIMReactiveIndexSetBased: () => OIMReactiveIndexSetBased,
118
129
  OIMReactiveObject: () => OIMReactiveObject,
119
130
  OIMSelector: () => OIMSelector,
131
+ OIMTrieMap: () => OIMTrieMap,
132
+ OIMTrieSet: () => OIMTrieSet,
120
133
  createInPlaceEntityUpdater: () => createInPlaceEntityUpdater,
121
134
  createMergeEntityUpdater: () => createMergeEntityUpdater,
122
135
  createOIMCollectionIndexFactory: () => createOIMCollectionIndexFactory,
123
136
  createOIMCollectionKit: () => createOIMCollectionKit,
137
+ createOIMDisposeScope: () => createOIMDisposeScope,
124
138
  createOIMGlobalIndexSlotMap: () => createOIMGlobalIndexSlotMap,
125
139
  createOIMIndexSlotMap: () => createOIMIndexSlotMap,
126
140
  createOIMOrderedListCommandStreamDiffDriven: () => createOIMOrderedListCommandStreamDiffDriven,
@@ -358,21 +372,82 @@ var OIMEventQueueScheduler = class {
358
372
  }
359
373
  };
360
374
 
375
+ // src/core/OIMKeyedBucketSetBased.ts
376
+ var OIMKeyedBucketSetBased = class extends Set {
377
+ constructor(key) {
378
+ super();
379
+ this.key = key;
380
+ }
381
+ hasSubscribers() {
382
+ return this.subscribers !== void 0 && this.subscribers.size > 0;
383
+ }
384
+ /** Clearing the slot set also clears the pk membership — they stay in sync. */
385
+ clear() {
386
+ super.clear();
387
+ this.membership?.clear();
388
+ }
389
+ };
390
+
361
391
  // src/core/OIMIndexStoreMapDrivenSetBased.ts
362
392
  var OIMIndexStoreMapDrivenSetBased = class extends OIMIndexStoreSetBased {
363
393
  constructor() {
364
394
  super(...arguments);
395
+ // Live buckets (non-empty, or freshly promoted). Enumeration reads these only.
365
396
  this.slots = /* @__PURE__ */ new Map();
397
+ // Empty buckets kept alive solely by their subscribers, so a subscription
398
+ // survives a key going empty → re-filled. Invisible to reads/enumeration.
399
+ this.reservedBuckets = /* @__PURE__ */ new Map();
400
+ }
401
+ getOrReserveBucket(key) {
402
+ const live = this.slots.get(key);
403
+ if (live) return live;
404
+ let reserved = this.reservedBuckets.get(key);
405
+ if (!reserved) {
406
+ reserved = new OIMKeyedBucketSetBased(key);
407
+ this.reservedBuckets.set(key, reserved);
408
+ }
409
+ return reserved;
410
+ }
411
+ findBucket(key) {
412
+ return this.slots.get(key) ?? this.reservedBuckets.get(key);
413
+ }
414
+ retainBucket(bucket) {
415
+ if (bucket.size === 0) return;
416
+ this.reservedBuckets.delete(bucket.key);
417
+ if (!this.slots.has(bucket.key)) this.slots.set(bucket.key, bucket);
418
+ }
419
+ releaseBucket(bucket) {
420
+ if (bucket.size > 0) return;
421
+ this.slots.delete(bucket.key);
422
+ if (bucket.hasSubscribers()) {
423
+ this.reservedBuckets.set(bucket.key, bucket);
424
+ } else {
425
+ this.reservedBuckets.delete(bucket.key);
426
+ }
427
+ }
428
+ dropIfReserved(key) {
429
+ const reserved = this.reservedBuckets.get(key);
430
+ if (reserved && reserved.size === 0 && !reserved.hasSubscribers()) {
431
+ this.reservedBuckets.delete(key);
432
+ }
366
433
  }
367
434
  setOneByKey(key, slots) {
368
- this.slots.set(key, slots);
435
+ const bucket = this.getOrReserveBucket(key);
436
+ if (bucket !== slots) {
437
+ bucket.clear();
438
+ for (const slot of slots) bucket.add(slot);
439
+ }
440
+ if (bucket.size > 0) this.retainBucket(bucket);
441
+ else this.releaseBucket(bucket);
369
442
  }
370
443
  removeOneByKey(key) {
371
444
  this.slots.delete(key);
445
+ this.reservedBuckets.delete(key);
372
446
  }
373
447
  removeManyByKeys(keys) {
374
448
  for (const key of keys) {
375
449
  this.slots.delete(key);
450
+ this.reservedBuckets.delete(key);
376
451
  }
377
452
  }
378
453
  getOneByKey(key) {
@@ -381,10 +456,8 @@ var OIMIndexStoreMapDrivenSetBased = class extends OIMIndexStoreSetBased {
381
456
  getManyByKeys(keys) {
382
457
  const result = /* @__PURE__ */ new Map();
383
458
  for (const key of keys) {
384
- const slots = this.getOneByKey(key);
385
- if (slots !== void 0) {
386
- result.set(key, slots);
387
- }
459
+ const slots = this.slots.get(key);
460
+ if (slots !== void 0) result.set(key, slots);
388
461
  }
389
462
  return result;
390
463
  }
@@ -398,6 +471,12 @@ var OIMIndexStoreMapDrivenSetBased = class extends OIMIndexStoreSetBased {
398
471
  return this.slots.size;
399
472
  }
400
473
  clear() {
474
+ for (const bucket of this.slots.values()) {
475
+ bucket.clear();
476
+ if (bucket.hasSubscribers()) {
477
+ this.reservedBuckets.set(bucket.key, bucket);
478
+ }
479
+ }
401
480
  this.slots.clear();
402
481
  }
403
482
  };
@@ -405,10 +484,9 @@ var OIMIndexStoreMapDrivenSetBased = class extends OIMIndexStoreSetBased {
405
484
  // src/abstract/OIMIndexSetBased.ts
406
485
  var OIMIndexSetBased = class extends OIMIndex {
407
486
  constructor(options = {}) {
408
- super(
409
- options.store ?? new OIMIndexStoreMapDrivenSetBased(),
410
- options.comparePks
411
- );
487
+ const store = options.store ?? new OIMIndexStoreMapDrivenSetBased();
488
+ super(store, options.comparePks);
489
+ this.setStore = store;
412
490
  }
413
491
  getBucketSize(bucket) {
414
492
  return bucket.size;
@@ -436,22 +514,36 @@ var OIMIndexSetBased = class extends OIMIndex {
436
514
  for (const slot of slots) pks.add(slot.pk);
437
515
  return pks;
438
516
  }
439
- setSlotsWithComparison(key, newSlots) {
440
- if (this.comparePks) {
441
- const existingSlotsSet = this.store.getOneByKey(key);
442
- if (existingSlotsSet && existingSlotsSet.size === newSlots.size) {
443
- if (this.comparePks(
444
- Array.from(this.slotsToPks(existingSlotsSet)),
445
- Array.from(this.slotsToPks(newSlots))
446
- )) {
447
- return false;
448
- }
449
- } else if (!existingSlotsSet && newSlots.size === 0) {
450
- return false;
451
- }
452
- }
453
- this.store.setOneByKey(key, newSlots);
454
- return true;
517
+ /**
518
+ * True if a comparator is set and considers `bucket`'s current pks equal to
519
+ * `newSlots` — i.e. a `setSlots` would be a no-op and should not emit.
520
+ */
521
+ bucketMatchesComparator(bucket, newSlots) {
522
+ if (!this.comparePks) return false;
523
+ if (bucket.size !== newSlots.size) return false;
524
+ if (bucket.size === 0) return true;
525
+ return this.comparePks(
526
+ Array.from(this.slotsToPks(bucket)),
527
+ Array.from(this.slotsToPks(newSlots))
528
+ );
529
+ }
530
+ };
531
+
532
+ // src/core/OIMKeyedBucketArrayBased.ts
533
+ var OIMKeyedBucketArrayBased = class extends Array {
534
+ constructor(key) {
535
+ super();
536
+ this.key = key;
537
+ }
538
+ static get [Symbol.species]() {
539
+ return Array;
540
+ }
541
+ hasSubscribers() {
542
+ return this.subscribers !== void 0 && this.subscribers.size > 0;
543
+ }
544
+ /** Empty the bucket in place (Array has no `clear`). */
545
+ clear() {
546
+ this.length = 0;
455
547
  }
456
548
  };
457
549
 
@@ -459,17 +551,61 @@ var OIMIndexSetBased = class extends OIMIndex {
459
551
  var OIMIndexStoreMapDrivenArrayBased = class extends OIMIndexStoreArrayBased {
460
552
  constructor() {
461
553
  super(...arguments);
554
+ // Live buckets (non-empty). Enumeration reads these only.
462
555
  this.slots = /* @__PURE__ */ new Map();
556
+ // Empty buckets kept alive by their subscribers, invisible to reads.
557
+ this.reservedBuckets = /* @__PURE__ */ new Map();
558
+ }
559
+ getOrReserveBucket(key) {
560
+ const live = this.slots.get(key);
561
+ if (live) return live;
562
+ let reserved = this.reservedBuckets.get(key);
563
+ if (!reserved) {
564
+ reserved = new OIMKeyedBucketArrayBased(key);
565
+ this.reservedBuckets.set(key, reserved);
566
+ }
567
+ return reserved;
568
+ }
569
+ findBucket(key) {
570
+ return this.slots.get(key) ?? this.reservedBuckets.get(key);
571
+ }
572
+ retainBucket(bucket) {
573
+ if (bucket.length === 0) return;
574
+ this.reservedBuckets.delete(bucket.key);
575
+ if (!this.slots.has(bucket.key)) this.slots.set(bucket.key, bucket);
576
+ }
577
+ releaseBucket(bucket) {
578
+ if (bucket.length > 0) return;
579
+ this.slots.delete(bucket.key);
580
+ if (bucket.hasSubscribers()) {
581
+ this.reservedBuckets.set(bucket.key, bucket);
582
+ } else {
583
+ this.reservedBuckets.delete(bucket.key);
584
+ }
585
+ }
586
+ dropIfReserved(key) {
587
+ const reserved = this.reservedBuckets.get(key);
588
+ if (reserved && reserved.length === 0 && !reserved.hasSubscribers()) {
589
+ this.reservedBuckets.delete(key);
590
+ }
463
591
  }
464
592
  setOneByKey(key, slots) {
465
- this.slots.set(key, slots);
593
+ const bucket = this.getOrReserveBucket(key);
594
+ if (bucket !== slots) {
595
+ bucket.length = 0;
596
+ for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
597
+ }
598
+ if (bucket.length > 0) this.retainBucket(bucket);
599
+ else this.releaseBucket(bucket);
466
600
  }
467
601
  removeOneByKey(key) {
468
602
  this.slots.delete(key);
603
+ this.reservedBuckets.delete(key);
469
604
  }
470
605
  removeManyByKeys(keys) {
471
606
  for (const key of keys) {
472
607
  this.slots.delete(key);
608
+ this.reservedBuckets.delete(key);
473
609
  }
474
610
  }
475
611
  getOneByKey(key) {
@@ -478,10 +614,8 @@ var OIMIndexStoreMapDrivenArrayBased = class extends OIMIndexStoreArrayBased {
478
614
  getManyByKeys(keys) {
479
615
  const result = /* @__PURE__ */ new Map();
480
616
  for (const key of keys) {
481
- const slots = this.getOneByKey(key);
482
- if (slots !== void 0) {
483
- result.set(key, slots);
484
- }
617
+ const slots = this.slots.get(key);
618
+ if (slots !== void 0) result.set(key, slots);
485
619
  }
486
620
  return result;
487
621
  }
@@ -495,6 +629,12 @@ var OIMIndexStoreMapDrivenArrayBased = class extends OIMIndexStoreArrayBased {
495
629
  return this.slots.size;
496
630
  }
497
631
  clear() {
632
+ for (const bucket of this.slots.values()) {
633
+ bucket.length = 0;
634
+ if (bucket.hasSubscribers()) {
635
+ this.reservedBuckets.set(bucket.key, bucket);
636
+ }
637
+ }
498
638
  this.slots.clear();
499
639
  }
500
640
  };
@@ -502,10 +642,9 @@ var OIMIndexStoreMapDrivenArrayBased = class extends OIMIndexStoreArrayBased {
502
642
  // src/abstract/OIMIndexArrayBased.ts
503
643
  var OIMIndexArrayBased = class extends OIMIndex {
504
644
  constructor(options = {}) {
505
- super(
506
- options.store ?? new OIMIndexStoreMapDrivenArrayBased(),
507
- options.comparePks
508
- );
645
+ const store = options.store ?? new OIMIndexStoreMapDrivenArrayBased();
646
+ super(store, options.comparePks);
647
+ this.arrayStore = store;
509
648
  }
510
649
  getBucketSize(bucket) {
511
650
  return bucket.length;
@@ -523,10 +662,15 @@ var OIMIndexArrayBased = class extends OIMIndex {
523
662
  return result;
524
663
  }
525
664
  getSlotsByKey(key) {
526
- return this.store.getOneByKey(key) ?? [];
665
+ const bucket = this.store.getOneByKey(key);
666
+ return bucket ? bucket.slice() : [];
527
667
  }
528
668
  getSlotsByKeys(keys) {
529
- return this.store.getManyByKeys(keys);
669
+ const result = /* @__PURE__ */ new Map();
670
+ for (const [key, bucket] of this.store.getManyByKeys(keys)) {
671
+ result.set(key, bucket.slice());
672
+ }
673
+ return result;
530
674
  }
531
675
  slotsToPks(slots) {
532
676
  const pks = [];
@@ -534,28 +678,24 @@ var OIMIndexArrayBased = class extends OIMIndex {
534
678
  for (let i = 0; i < slots.length; i++) pks[i] = slots[i].pk;
535
679
  return pks;
536
680
  }
537
- setSlotsWithComparison(key, newSlots) {
538
- if (this.comparePks) {
539
- const existingSlotsArray = this.store.getOneByKey(key);
540
- if (existingSlotsArray && existingSlotsArray.length === newSlots.length) {
541
- if (this.comparePks(
542
- this.slotsToPks(existingSlotsArray),
543
- this.slotsToPks(newSlots)
544
- )) {
545
- return false;
546
- }
547
- } else if (!existingSlotsArray && newSlots.length === 0) {
548
- return false;
549
- }
550
- }
551
- this.store.setOneByKey(key, newSlots);
552
- return true;
681
+ /**
682
+ * True if a comparator is set and considers `bucket`'s pks equal to
683
+ * `newSlots` — i.e. a `setSlots` would be a no-op and should not emit.
684
+ */
685
+ bucketMatchesComparator(bucket, newSlots) {
686
+ if (!this.comparePks) return false;
687
+ if (bucket.length !== newSlots.length) return false;
688
+ if (bucket.length === 0) return true;
689
+ return this.comparePks(
690
+ this.slotsToPks(bucket),
691
+ this.slotsToPks(newSlots)
692
+ );
553
693
  }
554
694
  };
555
695
 
556
696
  // src/core/OIMCarrierKeyedEmitter.ts
557
697
  var OIMCarrierKeyedEmitter = class {
558
- constructor(queue, resolver) {
698
+ constructor(queue, carrierProvider) {
559
699
  // Carriers with at least one subscriber — for markAllUpdated / metrics.
560
700
  this.subscribedCarriers = /* @__PURE__ */ new Set();
561
701
  // Pending flush batch. A plain array + a `carrier.dirty` flag for dedup —
@@ -580,17 +720,17 @@ var OIMCarrierKeyedEmitter = class {
580
720
  }
581
721
  };
582
722
  this.queue = queue;
583
- this.resolver = resolver;
723
+ this.carrierProvider = carrierProvider;
584
724
  }
585
725
  subscribeOnKey(key, handler) {
586
- const carrier = this.resolver.getOrReserveCarrier(key);
726
+ const carrier = this.carrierProvider.getOrReserveCarrier(key);
587
727
  this.addHandler(carrier, handler);
588
728
  return () => this.removeHandler(carrier, handler);
589
729
  }
590
730
  subscribeOnKeys(keys, handler) {
591
731
  const carriers = [];
592
732
  for (let i = 0; i < keys.length; i++) {
593
- const carrier = this.resolver.getOrReserveCarrier(keys[i]);
733
+ const carrier = this.carrierProvider.getOrReserveCarrier(keys[i]);
594
734
  this.addHandler(carrier, handler);
595
735
  carriers.push(carrier);
596
736
  }
@@ -601,7 +741,7 @@ var OIMCarrierKeyedEmitter = class {
601
741
  };
602
742
  }
603
743
  unsubscribeFromKey(key, handler) {
604
- const carrier = this.resolver.findCarrier(key);
744
+ const carrier = this.carrierProvider.findCarrier(key);
605
745
  if (carrier) this.removeHandler(carrier, handler);
606
746
  }
607
747
  unsubscribeFromKeys(keys, handler) {
@@ -624,7 +764,7 @@ var OIMCarrierKeyedEmitter = class {
624
764
  subscribers.delete(handler);
625
765
  if (subscribers.size === 0) {
626
766
  this.subscribedCarriers.delete(carrier);
627
- this.resolver.onCarrierEmptied?.(carrier);
767
+ this.carrierProvider.onCarrierEmptied?.(carrier);
628
768
  }
629
769
  }
630
770
  /** Fast path: the writer already holds the carrier. */
@@ -638,11 +778,19 @@ var OIMCarrierKeyedEmitter = class {
638
778
  this.scheduleFlush();
639
779
  }
640
780
  markUpdatedKey(key) {
641
- const carrier = this.resolver.findCarrier(key);
781
+ if (this.subscribedCarriers.size === 0) {
782
+ this.assertNotInFlush();
783
+ return;
784
+ }
785
+ const carrier = this.carrierProvider.findCarrier(key);
642
786
  if (carrier) this.markUpdatedCarrier(carrier);
643
787
  else this.assertNotInFlush();
644
788
  }
645
789
  markUpdatedKeys(keys) {
790
+ if (this.subscribedCarriers.size === 0) {
791
+ this.assertNotInFlush();
792
+ return;
793
+ }
646
794
  for (let i = 0; i < keys.length; i++) this.markUpdatedKey(keys[i]);
647
795
  }
648
796
  markAllUpdated() {
@@ -659,7 +807,7 @@ var OIMCarrierKeyedEmitter = class {
659
807
  return this.subscribedCarriers.size > 0;
660
808
  }
661
809
  getHandlerCount(key) {
662
- return this.resolver.findCarrier(key)?.subscribers?.size ?? 0;
810
+ return this.carrierProvider.findCarrier(key)?.subscribers?.size ?? 0;
663
811
  }
664
812
  getMetrics() {
665
813
  let totalHandlers = 0;
@@ -719,8 +867,8 @@ var OIMCarrierKeyedEmitter = class {
719
867
  }
720
868
  };
721
869
 
722
- // src/core/OIMKeyedCarrierResolver.ts
723
- var OIMKeyedCarrierResolver = class {
870
+ // src/core/OIMKeyedCarrierProvider.ts
871
+ var OIMKeyedCarrierProvider = class {
724
872
  constructor() {
725
873
  this.carriers = /* @__PURE__ */ new Map();
726
874
  }
@@ -742,8 +890,8 @@ var OIMKeyedCarrierResolver = class {
742
890
 
743
891
  // src/abstract/OIMReactiveIndex.ts
744
892
  var OIMReactiveIndex = class {
745
- constructor(queue, createIndex) {
746
- this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierResolver());
893
+ constructor(queue, createIndex, createCarrierProvider = () => new OIMKeyedCarrierProvider()) {
894
+ this.updateEmitter = new OIMCarrierKeyedEmitter(queue, createCarrierProvider());
747
895
  this.index = createIndex(this.updateEmitter);
748
896
  }
749
897
  getEntitiesByKey(key) {
@@ -799,8 +947,8 @@ var OIMReactiveIndex = class {
799
947
 
800
948
  // src/abstract/OIMReactiveIndexSetBased.ts
801
949
  var OIMReactiveIndexSetBased = class extends OIMReactiveIndex {
802
- constructor(queue, createIndex) {
803
- super(queue, createIndex);
950
+ constructor(queue, createIndex, createCarrierProvider) {
951
+ super(queue, createIndex, createCarrierProvider);
804
952
  }
805
953
  getPksByKey(key) {
806
954
  return this.index.getPksByKey(key);
@@ -818,8 +966,8 @@ var OIMReactiveIndexSetBased = class extends OIMReactiveIndex {
818
966
 
819
967
  // src/abstract/OIMReactiveIndexArrayBased.ts
820
968
  var OIMReactiveIndexArrayBased = class extends OIMReactiveIndex {
821
- constructor(queue, createIndex) {
822
- super(queue, createIndex);
969
+ constructor(queue, createIndex, createCarrierProvider) {
970
+ super(queue, createIndex, createCarrierProvider);
823
971
  }
824
972
  getPksByKey(key) {
825
973
  return this.index.getPksByKey(key);
@@ -1282,6 +1430,14 @@ var OIMCollection = class {
1282
1430
  getOrReserveSlotByPk(pk) {
1283
1431
  return this.store.getOrReserveSlotByPk(pk);
1284
1432
  }
1433
+ /**
1434
+ * The existing slot for `pk` (live or reserved) WITHOUT creating one — used
1435
+ * by indexes to resolve a pk to its canonical slot for removal (delete by
1436
+ * reference), with no side effect for a pk that is not present.
1437
+ */
1438
+ findSlotByPk(pk) {
1439
+ return this.store.findSlotByPk(pk);
1440
+ }
1285
1441
  getSlotsByPks(pks) {
1286
1442
  return this.store.getSlotsByPks(pks);
1287
1443
  }
@@ -1350,15 +1506,12 @@ var OIMCollection = class {
1350
1506
  `[OIMCollection]: PK is required to upsert an entity ${JSON.stringify(entity)}`
1351
1507
  );
1352
1508
  }
1353
- const existingEntity = this.store.getOneByPk(pk);
1354
- if (existingEntity) {
1355
- return this.store.setOneByPk(
1356
- pk,
1357
- this.updateEntity(entity, existingEntity)
1358
- );
1359
- } else {
1360
- return this.store.setOneByPk(pk, entity);
1509
+ const slot = this.store.getSlotByPk(pk);
1510
+ if (slot !== void 0 && slot.item !== void 0) {
1511
+ slot.item = this.updateEntity(entity, slot.item);
1512
+ return slot;
1361
1513
  }
1514
+ return this.store.setOneByPk(pk, entity);
1362
1515
  }
1363
1516
  destroy() {
1364
1517
  this.store.destroy();
@@ -1371,13 +1524,15 @@ var OIMReactiveCollection = class extends OIMCollection {
1371
1524
  constructor(queue, opts) {
1372
1525
  super(opts);
1373
1526
  this.anyUpdateHandlers = /* @__PURE__ */ new Set();
1527
+ // Only ever holds `slot.pk` (one canonical reference per logical key), so a
1528
+ // native `Set` dedups correctly by reference even for composite pks.
1374
1529
  this.pendingAnyUpdatePks = /* @__PURE__ */ new Set();
1375
1530
  this.isAnyUpdateClearPending = false;
1376
1531
  this.isAnyUpdateScheduled = false;
1377
1532
  this.runAnyUpdate = () => {
1378
1533
  if (!this.isAnyUpdateScheduled) return;
1379
1534
  this.isAnyUpdateScheduled = false;
1380
- const pks = this.isAnyUpdateClearPending ? [] : Array.from(this.pendingAnyUpdatePks);
1535
+ const pks = this.isAnyUpdateClearPending ? [] : Array.from(this.pendingAnyUpdatePks.values());
1381
1536
  this.pendingAnyUpdatePks.clear();
1382
1537
  this.isAnyUpdateClearPending = false;
1383
1538
  const snapshot = Array.from(this.anyUpdateHandlers);
@@ -1536,38 +1691,40 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1536
1691
  if (key === void 0) {
1537
1692
  const keys = this.getKeys();
1538
1693
  if (keys.length > 0) {
1539
- this.store.clear();
1694
+ this.arrayStore.clear();
1540
1695
  this.emitUpdate(Array.from(keys));
1541
1696
  }
1542
1697
  return;
1543
1698
  }
1544
- if (this.store.getOneByKey(key) !== void 0) {
1545
- this.store.removeOneByKey(key);
1699
+ const bucket = this.arrayStore.findBucket(key);
1700
+ if (bucket && bucket.length > 0) {
1701
+ bucket.length = 0;
1702
+ this.arrayStore.releaseBucket(bucket);
1546
1703
  this.emitUpdate([key]);
1547
1704
  }
1548
1705
  }
1549
1706
  pushSlot(key, slot) {
1550
- const list = this.getOrCreateList(key);
1551
- const index = list.length;
1552
- list.push(slot);
1707
+ const bucket = this.arrayStore.getOrReserveBucket(key);
1708
+ const index = bucket.length;
1709
+ bucket.push(slot);
1710
+ this.arrayStore.retainBucket(bucket);
1553
1711
  this.emitUpdate([key]);
1554
1712
  return index;
1555
1713
  }
1556
1714
  insertSlotAt(key, index, slot) {
1557
- const list = this.getOrCreateList(key);
1558
- const safeIndex = Math.max(0, Math.min(index, list.length));
1559
- list.splice(safeIndex, 0, slot);
1715
+ const bucket = this.arrayStore.getOrReserveBucket(key);
1716
+ const safeIndex = Math.max(0, Math.min(index, bucket.length));
1717
+ bucket.splice(safeIndex, 0, slot);
1718
+ this.arrayStore.retainBucket(bucket);
1560
1719
  this.emitUpdate([key]);
1561
1720
  return safeIndex;
1562
1721
  }
1563
1722
  removeAt(key, index) {
1564
- const list = this.store.getOneByKey(key);
1565
- if (!list) return void 0;
1566
- if (index < 0 || index >= list.length) return void 0;
1567
- const [removed] = list.splice(index, 1);
1568
- if (list.length === 0) {
1569
- this.store.removeOneByKey(key);
1570
- }
1723
+ const bucket = this.arrayStore.findBucket(key);
1724
+ if (!bucket) return void 0;
1725
+ if (index < 0 || index >= bucket.length) return void 0;
1726
+ const [removed] = bucket.splice(index, 1);
1727
+ if (bucket.length === 0) this.arrayStore.releaseBucket(bucket);
1571
1728
  this.emitUpdate([key]);
1572
1729
  return removed;
1573
1730
  }
@@ -1576,35 +1733,33 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1576
1733
  * Returns the number actually removed (clamped to bounds; 0 if nothing).
1577
1734
  */
1578
1735
  removeRange(key, index, count) {
1579
- const list = this.store.getOneByKey(key);
1580
- if (!list) return 0;
1581
- if (index < 0 || index >= list.length || count <= 0) return 0;
1582
- const actual = Math.min(count, list.length - index);
1583
- list.splice(index, actual);
1584
- if (list.length === 0) {
1585
- this.store.removeOneByKey(key);
1586
- }
1736
+ const bucket = this.arrayStore.findBucket(key);
1737
+ if (!bucket) return 0;
1738
+ if (index < 0 || index >= bucket.length || count <= 0) return 0;
1739
+ const actual = Math.min(count, bucket.length - index);
1740
+ bucket.splice(index, actual);
1741
+ if (bucket.length === 0) this.arrayStore.releaseBucket(bucket);
1587
1742
  this.emitUpdate([key]);
1588
1743
  return actual;
1589
1744
  }
1590
1745
  move(key, fromIndex, toIndex) {
1591
- const list = this.store.getOneByKey(key);
1592
- if (!list) return void 0;
1593
- if (fromIndex < 0 || fromIndex >= list.length) return void 0;
1594
- if (toIndex < 0 || toIndex >= list.length) return void 0;
1595
- if (fromIndex === toIndex) return list[fromIndex];
1596
- const [slot] = list.splice(fromIndex, 1);
1746
+ const bucket = this.arrayStore.findBucket(key);
1747
+ if (!bucket) return void 0;
1748
+ if (fromIndex < 0 || fromIndex >= bucket.length) return void 0;
1749
+ if (toIndex < 0 || toIndex >= bucket.length) return void 0;
1750
+ if (fromIndex === toIndex) return bucket[fromIndex];
1751
+ const [slot] = bucket.splice(fromIndex, 1);
1597
1752
  if (slot === void 0) return void 0;
1598
- list.splice(toIndex, 0, slot);
1753
+ bucket.splice(toIndex, 0, slot);
1599
1754
  this.emitUpdate([key]);
1600
1755
  return slot;
1601
1756
  }
1602
1757
  /** Replace the slot at `index` in place. Returns the safe index, or -1. */
1603
1758
  setSlotAt(key, index, slot) {
1604
- const list = this.store.getOneByKey(key);
1605
- if (!list) return -1;
1606
- if (index < 0 || index >= list.length) return -1;
1607
- list[index] = slot;
1759
+ const bucket = this.arrayStore.findBucket(key);
1760
+ if (!bucket) return -1;
1761
+ if (index < 0 || index >= bucket.length) return -1;
1762
+ bucket[index] = slot;
1608
1763
  this.emitUpdate([key]);
1609
1764
  return index;
1610
1765
  }
@@ -1614,52 +1769,49 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1614
1769
  * Returns the number actually moved (0 if a no-op / out of bounds).
1615
1770
  */
1616
1771
  moveRange(key, from, to, count) {
1617
- const list = this.store.getOneByKey(key);
1618
- if (!list) return 0;
1619
- if (from < 0 || from >= list.length || count <= 0) return 0;
1620
- const actual = Math.min(count, list.length - from);
1621
- const insertAt = Math.max(0, Math.min(to, list.length - actual));
1772
+ const bucket = this.arrayStore.findBucket(key);
1773
+ if (!bucket) return 0;
1774
+ if (from < 0 || from >= bucket.length || count <= 0) return 0;
1775
+ const actual = Math.min(count, bucket.length - from);
1776
+ const insertAt = Math.max(0, Math.min(to, bucket.length - actual));
1622
1777
  if (insertAt === from) return 0;
1623
- const block = list.splice(from, actual);
1624
- list.splice(insertAt, 0, ...block);
1778
+ const block = bucket.splice(from, actual);
1779
+ bucket.splice(insertAt, 0, ...block);
1625
1780
  this.emitUpdate([key]);
1626
1781
  return actual;
1627
1782
  }
1628
1783
  resetSlots(key, slots) {
1629
1784
  if (slots.length === 0) {
1630
- if (this.store.getOneByKey(key) !== void 0) {
1631
- this.store.removeOneByKey(key);
1785
+ const bucket2 = this.arrayStore.findBucket(key);
1786
+ if (bucket2 && bucket2.length > 0) {
1787
+ bucket2.length = 0;
1788
+ this.arrayStore.releaseBucket(bucket2);
1632
1789
  this.emitUpdate([key]);
1633
1790
  }
1634
1791
  return;
1635
1792
  }
1636
- this.store.setOneByKey(key, slots.slice());
1793
+ const bucket = this.arrayStore.getOrReserveBucket(key);
1794
+ bucket.length = 0;
1795
+ for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
1796
+ this.arrayStore.retainBucket(bucket);
1637
1797
  this.emitUpdate([key]);
1638
1798
  }
1639
- getOrCreateList(key) {
1640
- let list = this.store.getOneByKey(key);
1641
- if (!list) {
1642
- list = [];
1643
- this.store.setOneByKey(key, list);
1644
- }
1645
- return list;
1646
- }
1647
1799
  };
1648
1800
 
1649
1801
  // src/core/OIMCollectionIndexManualOrderedArrayBased.ts
1650
1802
  var OIMCollectionIndexManualOrderedArrayBased = class extends OIMIndexManualOrderedArrayBased {
1651
1803
  constructor(opts) {
1652
1804
  super();
1653
- this.resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getSlotByPk(pk) : opts.resolveSlot;
1805
+ this.getSlot = opts.collection !== void 0 ? (pk) => opts.collection.getSlotByPk(pk) : opts.getSlot;
1654
1806
  }
1655
1807
  push(key, pk) {
1656
- return this.pushSlot(key, this.resolveRequiredSlot(pk));
1808
+ return this.pushSlot(key, this.getRequiredSlot(pk));
1657
1809
  }
1658
1810
  insertAt(key, index, pk) {
1659
- return this.insertSlotAt(key, index, this.resolveRequiredSlot(pk));
1811
+ return this.insertSlotAt(key, index, this.getRequiredSlot(pk));
1660
1812
  }
1661
1813
  reset(key, pks) {
1662
- this.resetSlots(key, this.resolveSlots(pks));
1814
+ this.resetSlots(key, this.getRequiredSlots(pks));
1663
1815
  }
1664
1816
  getEntitiesByKey(key) {
1665
1817
  return super.getEntitiesByKey(key);
@@ -1667,26 +1819,25 @@ var OIMCollectionIndexManualOrderedArrayBased = class extends OIMIndexManualOrde
1667
1819
  getEntitiesByKeys(keys) {
1668
1820
  return super.getEntitiesByKeys(keys);
1669
1821
  }
1670
- resolvePk(pk) {
1671
- return this.resolveRequiredSlot(pk);
1822
+ // Gets the slot for a pk, throwing if the (custom) getter has none — this
1823
+ // ordered index requires a real slot to place in order.
1824
+ getRequiredSlot(pk) {
1825
+ const slot = this.getSlot(pk);
1826
+ if (!slot) {
1827
+ throw new Error(
1828
+ `[OIMCollectionIndexManualOrderedArrayBased]: no slot for PK "${String(pk)}".`
1829
+ );
1830
+ }
1831
+ return slot;
1672
1832
  }
1673
- resolveSlots(pks) {
1833
+ getRequiredSlots(pks) {
1674
1834
  const slots = [];
1675
1835
  slots.length = pks.length;
1676
1836
  for (let i = 0; i < pks.length; i++) {
1677
- slots[i] = this.resolveRequiredSlot(pks[i]);
1837
+ slots[i] = this.getRequiredSlot(pks[i]);
1678
1838
  }
1679
1839
  return slots;
1680
1840
  }
1681
- resolveRequiredSlot(pk) {
1682
- const slot = this.resolveSlot(pk);
1683
- if (!slot) {
1684
- throw new Error(
1685
- `[OIMCollectionIndexManualOrderedArrayBased]: Unable to resolve slot for PK "${String(pk)}".`
1686
- );
1687
- }
1688
- return slot;
1689
- }
1690
1841
  };
1691
1842
 
1692
1843
  // src/enums/EOIMEventQueueEventType.ts
@@ -2105,18 +2256,18 @@ var OIMOrderedListCommandStream = class extends OIMOrderedListCommandBuffer {
2105
2256
  var OIMCollectionOrderedListCommandStream = class extends OIMOrderedListCommandStream {
2106
2257
  constructor(queue, opts) {
2107
2258
  const index = opts.index ?? new OIMCollectionIndexManualOrderedArrayBased(
2108
- opts.collection !== void 0 ? { collection: opts.collection } : { resolveSlot: opts.resolveSlot }
2259
+ opts.collection !== void 0 ? { collection: opts.collection } : { getSlot: opts.getSlot }
2109
2260
  );
2110
2261
  super(queue, index);
2111
2262
  this.index = index;
2112
2263
  }
2113
2264
  push(key, pk) {
2114
- const slot = this.resolvePk(pk);
2265
+ const slot = this.getRequiredSlot(pk);
2115
2266
  const index = this.index.pushSlot(key, slot);
2116
2267
  this.appendCommand(key, { type: "insert", index, item: slot });
2117
2268
  }
2118
2269
  insertAt(key, index, pk) {
2119
- const slot = this.resolvePk(pk);
2270
+ const slot = this.getRequiredSlot(pk);
2120
2271
  const safeIndex = this.index.insertSlotAt(key, index, slot);
2121
2272
  this.appendCommand(key, {
2122
2273
  type: "insert",
@@ -2126,7 +2277,7 @@ var OIMCollectionOrderedListCommandStream = class extends OIMOrderedListCommandS
2126
2277
  }
2127
2278
  /** Replace the element at `index` with the slot for `pk`, in place. */
2128
2279
  setAt(key, index, pk) {
2129
- const slot = this.resolvePk(pk);
2280
+ const slot = this.getRequiredSlot(pk);
2130
2281
  const safeIndex = this.index.setSlotAt(key, index, slot);
2131
2282
  if (safeIndex < 0) return;
2132
2283
  this.appendCommand(key, {
@@ -2136,7 +2287,7 @@ var OIMCollectionOrderedListCommandStream = class extends OIMOrderedListCommandS
2136
2287
  });
2137
2288
  }
2138
2289
  set(key, pks) {
2139
- const slots = pks.map((pk) => this.resolvePk(pk));
2290
+ const slots = pks.map((pk) => this.getRequiredSlot(pk));
2140
2291
  this.index.resetSlots(key, slots);
2141
2292
  this.appendResetCommand(key, slots);
2142
2293
  }
@@ -2148,8 +2299,8 @@ var OIMCollectionOrderedListCommandStream = class extends OIMOrderedListCommandS
2148
2299
  getEntitiesByKey(key) {
2149
2300
  return this.index.getEntitiesByKey(key);
2150
2301
  }
2151
- resolvePk(pk) {
2152
- return this.index.resolvePk(
2302
+ getRequiredSlot(pk) {
2303
+ return this.index.getRequiredSlot(
2153
2304
  pk
2154
2305
  );
2155
2306
  }
@@ -2161,43 +2312,68 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
2161
2312
  super(options);
2162
2313
  }
2163
2314
  setSlots(key, slots) {
2164
- const hasChanges = this.setSlotsWithComparison(key, slots);
2165
- if (hasChanges) {
2166
- this.emitUpdateOne(key);
2167
- }
2315
+ const bucket = this.arrayStore.getOrReserveBucket(key);
2316
+ if (this.bucketMatchesComparator(bucket, slots)) return;
2317
+ bucket.length = 0;
2318
+ for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
2319
+ if (bucket.length > 0) this.arrayStore.retainBucket(bucket);
2320
+ else this.arrayStore.releaseBucket(bucket);
2321
+ this.emitBucketChanged(bucket);
2168
2322
  }
2169
2323
  /**
2170
- * Appends pre-deduplicated slots to a key's bucket IN PLACE (no copy of the
2171
- * existing bucket) and emits once. Callers own dedup (so this stays O(added),
2172
- * not O(bucket)); used by collection-bound indexes for fast `addPks`.
2324
+ * Appends pre-deduplicated slots to a key's bucket IN PLACE and emits once.
2325
+ * O(added); callers own dedup.
2173
2326
  */
2174
2327
  appendSlots(key, slots) {
2175
2328
  if (slots.length === 0) return;
2176
- let bucket = this.store.getOneByKey(key);
2177
- if (!bucket) {
2178
- bucket = [];
2179
- this.store.setOneByKey(key, bucket);
2180
- }
2329
+ const bucket = this.arrayStore.getOrReserveBucket(key);
2181
2330
  for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
2182
- this.emitUpdateOne(key);
2331
+ this.arrayStore.retainBucket(bucket);
2332
+ this.emitBucketChanged(bucket);
2183
2333
  }
2184
2334
  /**
2185
- * Clear all primary keys for a specific index key, or all keys if no key specified
2335
+ * Clear all primary keys for a specific index key, or all keys if none given.
2186
2336
  */
2187
2337
  clear(key) {
2188
2338
  if (key === void 0) {
2189
- const allKeys = this.store.getAllKeys();
2339
+ const allKeys = this.arrayStore.getAllKeys();
2190
2340
  if (allKeys.length > 0) {
2191
- this.store.clear();
2341
+ this.arrayStore.clear();
2192
2342
  this.emitUpdate(allKeys);
2193
2343
  }
2194
2344
  } else {
2195
- if (this.store.getOneByKey(key) !== void 0) {
2196
- this.store.removeOneByKey(key);
2197
- this.emitUpdateOne(key);
2345
+ const bucket = this.arrayStore.findBucket(key);
2346
+ if (bucket && bucket.length > 0) {
2347
+ bucket.length = 0;
2348
+ this.arrayStore.releaseBucket(bucket);
2349
+ this.emitBucketChanged(bucket);
2198
2350
  }
2199
2351
  }
2200
2352
  }
2353
+ /**
2354
+ * Emit that a bucket changed. Base (non-reactive) path emits by key on the
2355
+ * plain event emitter; the reactive subclass overrides this to mark the
2356
+ * bucket carrier directly (O(1), no key→carrier lookup).
2357
+ */
2358
+ emitBucketChanged(bucket) {
2359
+ this.emitUpdateOne(bucket.key);
2360
+ }
2361
+ };
2362
+
2363
+ // src/core/OIMBucketCarrierProvider.ts
2364
+ var OIMBucketCarrierProvider = class {
2365
+ constructor(store) {
2366
+ this.store = store;
2367
+ }
2368
+ getOrReserveCarrier(key) {
2369
+ return this.store.getOrReserveBucket(key);
2370
+ }
2371
+ findCarrier(key) {
2372
+ return this.store.findBucket(key);
2373
+ }
2374
+ onCarrierEmptied(carrier) {
2375
+ this.store.dropIfReserved(carrier.key);
2376
+ }
2201
2377
  };
2202
2378
 
2203
2379
  // src/core/OIMReactiveIndexManualArrayBased.ts
@@ -2206,6 +2382,10 @@ var OIMIndexManualArrayBasedReactive = class extends OIMIndexManualArrayBased {
2206
2382
  super(opts);
2207
2383
  this.updateEmitter = updateEmitter;
2208
2384
  }
2385
+ // Deliver straight off the bucket the write just touched — O(1).
2386
+ emitBucketChanged(bucket) {
2387
+ this.updateEmitter.markUpdatedCarrier(bucket);
2388
+ }
2209
2389
  emitUpdate(keys) {
2210
2390
  this.updateEmitter.markUpdatedKeys(keys);
2211
2391
  }
@@ -2215,12 +2395,15 @@ var OIMIndexManualArrayBasedReactive = class extends OIMIndexManualArrayBased {
2215
2395
  };
2216
2396
  var OIMReactiveIndexManualArrayBased = class extends OIMReactiveIndexArrayBased {
2217
2397
  constructor(queue, opts) {
2218
- super(queue, (updateEmitter) => {
2219
- return new OIMIndexManualArrayBasedReactive(
2220
- updateEmitter,
2221
- opts?.indexOptions
2222
- );
2223
- });
2398
+ const store = opts?.indexOptions?.store ?? new OIMIndexStoreMapDrivenArrayBased();
2399
+ super(
2400
+ queue,
2401
+ (updateEmitter) => new OIMIndexManualArrayBasedReactive(updateEmitter, {
2402
+ comparePks: opts?.indexOptions?.comparePks,
2403
+ store
2404
+ }),
2405
+ () => new OIMBucketCarrierProvider(store)
2406
+ );
2224
2407
  }
2225
2408
  setSlots(key, slots) {
2226
2409
  this.index.setSlots(key, slots);
@@ -2236,50 +2419,61 @@ var OIMReactiveIndexManualArrayBased = class extends OIMReactiveIndexArrayBased
2236
2419
  // src/core/OIMReactiveCollectionIndexManualArrayBased.ts
2237
2420
  var OIMReactiveCollectionIndexManualArrayBased = class extends OIMReactiveIndexManualArrayBased {
2238
2421
  constructor(queue, opts) {
2239
- const resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.resolveSlot;
2422
+ const getSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.getSlot;
2240
2423
  super(queue, {
2241
2424
  indexOptions: opts.indexOptions
2242
2425
  });
2243
- // Persistent per-key pk membership for O(1) `addPks` dedup (avoids rebuilding
2244
- // a Set from the whole bucket on every call). Kept in sync by setSlots/clear.
2245
- this.pksByKey = /* @__PURE__ */ new Map();
2246
- this.resolveSlot = resolveSlot;
2426
+ // Persistent per-key membership for O(1) `addPks` dedup, keyed by the
2427
+ // canonical slot reference (correct for composite pks with a native Set — no
2428
+ // content-addressing needed, since slots are one reference per logical key).
2429
+ this.slotsByKey = /* @__PURE__ */ new Map();
2430
+ this.getSlot = getSlot;
2431
+ this.findSlotForRemoval = opts.collection !== void 0 ? (pk) => opts.collection.findSlotByPk(pk) : opts.getSlot;
2247
2432
  }
2248
2433
  setPks(key, pks) {
2249
- const pkSet = /* @__PURE__ */ new Set();
2250
- for (let i = 0; i < pks.length; i++) pkSet.add(pks[i]);
2251
- this.pksByKey.set(key, pkSet);
2252
- super.setSlots(key, this.resolveSlots(pks));
2434
+ const slots = this.getSlotsOrTransient(pks);
2435
+ this.slotsByKey.set(key, new Set(slots));
2436
+ super.setSlots(key, slots);
2253
2437
  }
2254
2438
  addPks(key, pks) {
2255
2439
  if (pks.length === 0) return;
2256
- const pkSet = this.membershipOf(key);
2440
+ const slotSet = this.membershipOf(key);
2257
2441
  const newSlots = [];
2258
2442
  for (const pk of pks) {
2259
- if (pkSet.has(pk)) continue;
2260
- pkSet.add(pk);
2261
- newSlots.push(this.resolveRequiredSlot(pk));
2443
+ const slot = this.getSlotOrTransient(pk);
2444
+ if (slotSet.has(slot)) continue;
2445
+ slotSet.add(slot);
2446
+ newSlots.push(slot);
2262
2447
  }
2263
2448
  if (newSlots.length > 0) this.appendSlots(key, newSlots);
2264
2449
  }
2265
2450
  removePks(key, pks) {
2266
2451
  if (pks.length === 0) return;
2267
- const pksToRemove = new Set(pks);
2452
+ const slotsToRemove = /* @__PURE__ */ new Set();
2453
+ const pksToRemove = /* @__PURE__ */ new Set();
2454
+ for (let i = 0; i < pks.length; i++) {
2455
+ const slot = this.findSlotForRemoval(pks[i]);
2456
+ if (slot) slotsToRemove.add(slot);
2457
+ else pksToRemove.add(pks[i]);
2458
+ }
2268
2459
  const existingSlots = this.index.getSlotsByKey(key);
2269
- const nextSlots = existingSlots.filter(
2270
- (slot) => !pksToRemove.has(slot.pk)
2271
- );
2272
- if (nextSlots.length === existingSlots.length) return;
2273
- const pkSet = this.pksByKey.get(key);
2274
- if (pkSet) {
2275
- for (let i = 0; i < pks.length; i++) pkSet.delete(pks[i]);
2460
+ const slotSet = this.slotsByKey.get(key);
2461
+ const nextSlots = [];
2462
+ for (let i = 0; i < existingSlots.length; i++) {
2463
+ const slot = existingSlots[i];
2464
+ if (slotsToRemove.has(slot) || pksToRemove.has(slot.pk)) {
2465
+ slotSet?.delete(slot);
2466
+ } else {
2467
+ nextSlots.push(slot);
2468
+ }
2276
2469
  }
2470
+ if (nextSlots.length === existingSlots.length) return;
2277
2471
  if (nextSlots.length === 0) this.clear(key);
2278
2472
  else super.setSlots(key, nextSlots);
2279
2473
  }
2280
2474
  clear(key) {
2281
- if (key === void 0) this.pksByKey.clear();
2282
- else this.pksByKey.delete(key);
2475
+ if (key === void 0) this.slotsByKey.clear();
2476
+ else this.slotsByKey.delete(key);
2283
2477
  super.clear(key);
2284
2478
  }
2285
2479
  /**
@@ -2287,16 +2481,12 @@ var OIMReactiveCollectionIndexManualArrayBased = class extends OIMReactiveIndexM
2287
2481
  * stays correct even if slots were set through a lower-level path.
2288
2482
  */
2289
2483
  membershipOf(key) {
2290
- let pkSet = this.pksByKey.get(key);
2291
- if (!pkSet) {
2292
- pkSet = /* @__PURE__ */ new Set();
2293
- const existing = this.index.getSlotsByKey(key);
2294
- for (let i = 0; i < existing.length; i++) {
2295
- pkSet.add(existing[i].pk);
2296
- }
2297
- this.pksByKey.set(key, pkSet);
2484
+ let slotSet = this.slotsByKey.get(key);
2485
+ if (!slotSet) {
2486
+ slotSet = new Set(this.index.getSlotsByKey(key));
2487
+ this.slotsByKey.set(key, slotSet);
2298
2488
  }
2299
- return pkSet;
2489
+ return slotSet;
2300
2490
  }
2301
2491
  getEntitiesByKey(key) {
2302
2492
  return super.getEntitiesByKey(key);
@@ -2304,16 +2494,16 @@ var OIMReactiveCollectionIndexManualArrayBased = class extends OIMReactiveIndexM
2304
2494
  getEntitiesByKeys(keys) {
2305
2495
  return super.getEntitiesByKeys(keys);
2306
2496
  }
2307
- resolveSlots(pks) {
2497
+ getSlotsOrTransient(pks) {
2308
2498
  const slots = [];
2309
2499
  slots.length = pks.length;
2310
2500
  for (let i = 0; i < pks.length; i++) {
2311
- slots[i] = this.resolveRequiredSlot(pks[i]);
2501
+ slots[i] = this.getSlotOrTransient(pks[i]);
2312
2502
  }
2313
2503
  return slots;
2314
2504
  }
2315
- resolveRequiredSlot(pk) {
2316
- const slot = this.resolveSlot(pk);
2505
+ getSlotOrTransient(pk) {
2506
+ const slot = this.getSlot(pk);
2317
2507
  if (!slot) return { pk, item: void 0 };
2318
2508
  return slot;
2319
2509
  }
@@ -2325,22 +2515,101 @@ var OIMIndexManualSetBased = class extends OIMIndexSetBased {
2325
2515
  super(options);
2326
2516
  }
2327
2517
  setSlots(key, slots) {
2328
- const hasChanges = this.setSlotsWithComparison(key, new Set(slots));
2329
- if (hasChanges) {
2330
- this.emitUpdateOne(key);
2518
+ const bucket = this.setStore.getOrReserveBucket(key);
2519
+ this.replaceBucketContents(bucket, slots);
2520
+ }
2521
+ // --- pk-oriented writes: resolve the bucket ONCE and use its own membership
2522
+ // (pk → slot) for dedup/removal, so a composite (trie) key is walked a single
2523
+ // time instead of once for the bucket and once for a separate membership map.
2524
+ setPks(key, pks, getSlot) {
2525
+ const bucket = this.setStore.getOrReserveBucket(key);
2526
+ const slots = [];
2527
+ for (let i = 0; i < pks.length; i++) slots.push(getSlot(pks[i]));
2528
+ this.replaceBucketContents(bucket, slots);
2529
+ const membership = bucket.membership ?? (bucket.membership = /* @__PURE__ */ new Map());
2530
+ membership.clear();
2531
+ for (let i = 0; i < slots.length; i++) {
2532
+ if (!membership.has(slots[i].pk)) {
2533
+ membership.set(slots[i].pk, slots[i]);
2534
+ }
2535
+ }
2536
+ }
2537
+ addPks(key, pks, getSlot) {
2538
+ if (pks.length === 0) return;
2539
+ const bucket = this.setStore.getOrReserveBucket(key);
2540
+ const membership = this.membershipOf(bucket);
2541
+ let changed = false;
2542
+ for (let i = 0; i < pks.length; i++) {
2543
+ const slot = getSlot(pks[i]);
2544
+ if (membership.has(slot.pk)) continue;
2545
+ membership.set(slot.pk, slot);
2546
+ if (!bucket.has(slot)) {
2547
+ bucket.add(slot);
2548
+ changed = true;
2549
+ }
2331
2550
  }
2551
+ if (!changed) return;
2552
+ this.setStore.retainBucket(bucket);
2553
+ this.emitBucketChanged(bucket);
2332
2554
  }
2333
2555
  /**
2334
- * Adds slots to a key's bucket IN PLACE (no copy of the existing Set) and
2335
- * emits once. O(added). Used by collection-bound indexes for fast `addPks`.
2556
+ * Removes by pk. `pks` must be canonical `slot.pk` references (membership is
2557
+ * keyed by them): for primitive pks that is just the pk; a collection-bound
2558
+ * composite index canonicalizes raw pks to `slot.pk` before calling this.
2559
+ */
2560
+ removePks(key, pks) {
2561
+ if (pks.length === 0) return;
2562
+ const bucket = this.setStore.findBucket(key);
2563
+ if (!bucket || bucket.size === 0) return;
2564
+ const membership = this.membershipOf(bucket);
2565
+ let changed = false;
2566
+ for (let i = 0; i < pks.length; i++) {
2567
+ const slot = membership.get(pks[i]);
2568
+ if (slot) {
2569
+ membership.delete(pks[i]);
2570
+ if (bucket.delete(slot)) changed = true;
2571
+ }
2572
+ }
2573
+ if (!changed) return;
2574
+ if (bucket.size === 0) this.setStore.releaseBucket(bucket);
2575
+ this.emitBucketChanged(bucket);
2576
+ }
2577
+ /**
2578
+ * The bucket's pk → slot membership, lazily created and seeded from the
2579
+ * bucket's current slots (so it stays correct even if slots were written
2580
+ * through the lower-level `setSlots` path).
2581
+ */
2582
+ membershipOf(bucket) {
2583
+ let membership = bucket.membership;
2584
+ if (!membership) {
2585
+ membership = /* @__PURE__ */ new Map();
2586
+ bucket.membership = membership;
2587
+ }
2588
+ if (membership.size === 0 && bucket.size > 0) {
2589
+ for (const slot of bucket) membership.set(slot.pk, slot);
2590
+ }
2591
+ return membership;
2592
+ }
2593
+ replaceBucketContents(bucket, slots) {
2594
+ if (this.comparePks) {
2595
+ const newSet = slots instanceof Set ? slots : new Set(slots);
2596
+ if (this.bucketMatchesComparator(bucket, newSet)) return;
2597
+ bucket.clear();
2598
+ for (const slot of newSet) bucket.add(slot);
2599
+ } else {
2600
+ bucket.clear();
2601
+ for (const slot of slots) bucket.add(slot);
2602
+ }
2603
+ if (bucket.size > 0) this.setStore.retainBucket(bucket);
2604
+ else this.setStore.releaseBucket(bucket);
2605
+ this.emitBucketChanged(bucket);
2606
+ }
2607
+ /**
2608
+ * Adds slots to a key's bucket IN PLACE and emits once. O(added).
2336
2609
  */
2337
2610
  addSlots(key, slots) {
2338
2611
  if (slots.length === 0) return;
2339
- let bucket = this.store.getOneByKey(key);
2340
- if (!bucket) {
2341
- bucket = /* @__PURE__ */ new Set();
2342
- this.store.setOneByKey(key, bucket);
2343
- }
2612
+ const bucket = this.setStore.getOrReserveBucket(key);
2344
2613
  let changed = false;
2345
2614
  for (let i = 0; i < slots.length; i++) {
2346
2615
  if (!bucket.has(slots[i])) {
@@ -2348,25 +2617,27 @@ var OIMIndexManualSetBased = class extends OIMIndexSetBased {
2348
2617
  changed = true;
2349
2618
  }
2350
2619
  }
2351
- if (changed) this.emitUpdateOne(key);
2620
+ if (!changed) return;
2621
+ this.setStore.retainBucket(bucket);
2622
+ this.emitBucketChanged(bucket);
2352
2623
  }
2353
2624
  /**
2354
2625
  * Removes slots from a key's bucket IN PLACE (O(removed)) and emits once.
2355
2626
  */
2356
2627
  removeSlots(key, slots) {
2357
2628
  if (slots.length === 0) return;
2358
- const bucket = this.store.getOneByKey(key);
2629
+ const bucket = this.setStore.findBucket(key);
2359
2630
  if (!bucket) return;
2360
2631
  let changed = false;
2361
2632
  for (let i = 0; i < slots.length; i++) {
2362
2633
  if (bucket.delete(slots[i])) changed = true;
2363
2634
  }
2364
2635
  if (!changed) return;
2365
- if (bucket.size === 0) this.store.removeOneByKey(key);
2366
- this.emitUpdateOne(key);
2636
+ if (bucket.size === 0) this.setStore.releaseBucket(bucket);
2637
+ this.emitBucketChanged(bucket);
2367
2638
  }
2368
2639
  /**
2369
- * Clear all primary keys for a specific index key, or all keys if no key specified
2640
+ * Clear all primary keys for a specific index key, or all keys if none given.
2370
2641
  */
2371
2642
  clear(key) {
2372
2643
  if (key === void 0) {
@@ -2376,12 +2647,22 @@ var OIMIndexManualSetBased = class extends OIMIndexSetBased {
2376
2647
  this.emitUpdate(allKeys);
2377
2648
  }
2378
2649
  } else {
2379
- if (this.store.getOneByKey(key) !== void 0) {
2380
- this.store.removeOneByKey(key);
2381
- this.emitUpdateOne(key);
2650
+ const bucket = this.setStore.findBucket(key);
2651
+ if (bucket && bucket.size > 0) {
2652
+ bucket.clear();
2653
+ this.setStore.releaseBucket(bucket);
2654
+ this.emitBucketChanged(bucket);
2382
2655
  }
2383
2656
  }
2384
2657
  }
2658
+ /**
2659
+ * Emit that a bucket changed. Base (non-reactive) path emits by key on the
2660
+ * plain event emitter; the reactive subclass overrides this to mark the
2661
+ * bucket carrier directly (O(1), no key→carrier lookup).
2662
+ */
2663
+ emitBucketChanged(bucket) {
2664
+ this.emitUpdateOne(bucket.key);
2665
+ }
2385
2666
  };
2386
2667
 
2387
2668
  // src/core/OIMReactiveIndexManualSetBased.ts
@@ -2390,6 +2671,12 @@ var OIMIndexManualSetBasedReactive = class extends OIMIndexManualSetBased {
2390
2671
  super(opts);
2391
2672
  this.updateEmitter = updateEmitter;
2392
2673
  }
2674
+ // Deliver straight off the bucket the write just touched — O(1), no
2675
+ // key→carrier lookup (the whole point of bucket-as-carrier).
2676
+ emitBucketChanged(bucket) {
2677
+ this.updateEmitter.markUpdatedCarrier(bucket);
2678
+ }
2679
+ // clear(all) still emits by key set (rare path).
2393
2680
  emitUpdate(keys) {
2394
2681
  this.updateEmitter.markUpdatedKeys(keys);
2395
2682
  }
@@ -2399,12 +2686,15 @@ var OIMIndexManualSetBasedReactive = class extends OIMIndexManualSetBased {
2399
2686
  };
2400
2687
  var OIMReactiveIndexManualSetBased = class extends OIMReactiveIndexSetBased {
2401
2688
  constructor(queue, opts) {
2402
- super(queue, (updateEmitter) => {
2403
- return new OIMIndexManualSetBasedReactive(
2404
- updateEmitter,
2405
- opts?.indexOptions
2406
- );
2407
- });
2689
+ const store = opts?.indexOptions?.store ?? new OIMIndexStoreMapDrivenSetBased();
2690
+ super(
2691
+ queue,
2692
+ (updateEmitter) => new OIMIndexManualSetBasedReactive(updateEmitter, {
2693
+ comparePks: opts?.indexOptions?.comparePks,
2694
+ store
2695
+ }),
2696
+ () => new OIMBucketCarrierProvider(store)
2697
+ );
2408
2698
  }
2409
2699
  setSlots(key, slots) {
2410
2700
  this.index.setSlots(key, slots);
@@ -2423,73 +2713,439 @@ var OIMReactiveIndexManualSetBased = class extends OIMReactiveIndexSetBased {
2423
2713
  // src/core/OIMReactiveCollectionIndexManualSetBased.ts
2424
2714
  var OIMReactiveCollectionIndexManualSetBased = class extends OIMReactiveIndexManualSetBased {
2425
2715
  constructor(queue, opts) {
2426
- const resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.resolveSlot;
2716
+ const getSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.getSlot;
2427
2717
  super(queue, {
2428
2718
  indexOptions: opts.indexOptions
2429
2719
  });
2430
- // Persistent per-key pk → slot map for O(1) add (dedup) and O(1) remove
2431
- // (slot lookup by pk). Kept in sync by setPks/addPks/removePks/clear.
2432
- this.slotByPk = /* @__PURE__ */ new Map();
2433
- this.resolveSlot = resolveSlot;
2720
+ // Bound once so the hot pk-write path passes a stable function (no per-call
2721
+ // closure allocation).
2722
+ this.getSlotOrTransient = (pk) => {
2723
+ const slot = this.getSlot(pk);
2724
+ if (!slot) return { pk, item: void 0 };
2725
+ return slot;
2726
+ };
2727
+ this.getSlot = getSlot;
2728
+ this.findSlotForRemoval = opts.collection !== void 0 ? (pk) => opts.collection.findSlotByPk(pk) : opts.getSlot;
2434
2729
  }
2435
2730
  setPks(key, pks) {
2436
- const map = /* @__PURE__ */ new Map();
2437
- const slots = [];
2731
+ this.index.setPks(key, pks, this.getSlotOrTransient);
2732
+ }
2733
+ addPks(key, pks) {
2734
+ this.index.addPks(key, pks, this.getSlotOrTransient);
2735
+ }
2736
+ removePks(key, pks) {
2737
+ const canonical = [];
2438
2738
  for (let i = 0; i < pks.length; i++) {
2439
- const pk = pks[i];
2440
- const slot = this.resolveRequiredSlot(pk);
2441
- if (!map.has(pk)) map.set(pk, slot);
2442
- slots.push(slot);
2739
+ const slot = this.findSlotForRemoval(pks[i]);
2740
+ canonical.push(slot ? slot.pk : pks[i]);
2443
2741
  }
2444
- this.slotByPk.set(key, map);
2445
- super.setSlots(key, slots);
2742
+ this.index.removePks(key, canonical);
2743
+ }
2744
+ getEntitiesByKey(key) {
2745
+ return super.getEntitiesByKey(key);
2746
+ }
2747
+ getEntitiesByKeys(keys) {
2748
+ return super.getEntitiesByKeys(keys);
2749
+ }
2750
+ };
2751
+
2752
+ // src/core/OIMTrieMap.ts
2753
+ var OIMTrieMap = class {
2754
+ constructor() {
2755
+ this.root = { hasValue: false };
2756
+ this._size = 0;
2757
+ }
2758
+ /** Number of stored paths (terminal nodes), not the number of trie nodes. */
2759
+ get size() {
2760
+ return this._size;
2761
+ }
2762
+ set(path, value) {
2763
+ let node = this.root;
2764
+ for (let i = 0; i < path.length; i++) {
2765
+ let children = node.children;
2766
+ if (!children) {
2767
+ children = /* @__PURE__ */ new Map();
2768
+ node.children = children;
2769
+ }
2770
+ let next = children.get(path[i]);
2771
+ if (!next) {
2772
+ next = { hasValue: false };
2773
+ children.set(path[i], next);
2774
+ }
2775
+ node = next;
2776
+ }
2777
+ if (!node.hasValue) {
2778
+ node.hasValue = true;
2779
+ this._size++;
2780
+ }
2781
+ node.value = value;
2782
+ }
2783
+ get(path) {
2784
+ const node = this.findNode(path);
2785
+ return node && node.hasValue ? node.value : void 0;
2786
+ }
2787
+ has(path) {
2788
+ const node = this.findNode(path);
2789
+ return !!node && node.hasValue;
2790
+ }
2791
+ delete(path) {
2792
+ const parents = [];
2793
+ let node = this.root;
2794
+ for (let i = 0; i < path.length; i++) {
2795
+ const next = node.children?.get(path[i]);
2796
+ if (!next) return false;
2797
+ parents.push(node);
2798
+ node = next;
2799
+ }
2800
+ if (!node.hasValue) return false;
2801
+ node.hasValue = false;
2802
+ node.value = void 0;
2803
+ this._size--;
2804
+ let child = node;
2805
+ for (let i = parents.length - 1; i >= 0; i--) {
2806
+ if (child.hasValue || child.children && child.children.size > 0) {
2807
+ break;
2808
+ }
2809
+ const parent = parents[i];
2810
+ parent.children.delete(path[i]);
2811
+ if (parent.children.size === 0) parent.children = void 0;
2812
+ child = parent;
2813
+ }
2814
+ return true;
2815
+ }
2816
+ clear() {
2817
+ this.root.children = void 0;
2818
+ this.root.value = void 0;
2819
+ this.root.hasValue = false;
2820
+ this._size = 0;
2821
+ }
2822
+ /** Depth-first iteration over `[path, value]` for every stored path. */
2823
+ *entries() {
2824
+ const stack = [{ node: this.root, path: [] }];
2825
+ while (stack.length > 0) {
2826
+ const frame = stack.pop();
2827
+ const { node, path } = frame;
2828
+ if (node.hasValue) {
2829
+ yield [path, node.value];
2830
+ }
2831
+ if (node.children) {
2832
+ for (const [segment, child] of node.children) {
2833
+ stack.push({ node: child, path: [...path, segment] });
2834
+ }
2835
+ }
2836
+ }
2837
+ }
2838
+ *keys() {
2839
+ for (const [path] of this.entries()) yield path;
2840
+ }
2841
+ *values() {
2842
+ for (const [, value] of this.entries()) yield value;
2843
+ }
2844
+ findNode(path) {
2845
+ let node = this.root;
2846
+ for (let i = 0; i < path.length; i++) {
2847
+ node = node.children?.get(path[i]);
2848
+ if (!node) return void 0;
2849
+ }
2850
+ return node;
2851
+ }
2852
+ };
2853
+
2854
+ // src/core/OIMIndexStoreTrieDrivenSetBased.ts
2855
+ var OIMIndexStoreTrieDrivenSetBased = class extends OIMIndexStoreSetBased {
2856
+ constructor() {
2857
+ super(...arguments);
2858
+ this.slots = new OIMTrieMap();
2859
+ this.reservedBuckets = new OIMTrieMap();
2860
+ }
2861
+ getOrReserveBucket(key) {
2862
+ const live = this.slots.get(key);
2863
+ if (live) return live;
2864
+ let reserved = this.reservedBuckets.get(key);
2865
+ if (!reserved) {
2866
+ reserved = new OIMKeyedBucketSetBased(key);
2867
+ this.reservedBuckets.set(key, reserved);
2868
+ }
2869
+ return reserved;
2870
+ }
2871
+ findBucket(key) {
2872
+ return this.slots.get(key) ?? this.reservedBuckets.get(key);
2873
+ }
2874
+ retainBucket(bucket) {
2875
+ if (bucket.size === 0) return;
2876
+ this.reservedBuckets.delete(bucket.key);
2877
+ if (!this.slots.has(bucket.key)) this.slots.set(bucket.key, bucket);
2878
+ }
2879
+ releaseBucket(bucket) {
2880
+ if (bucket.size > 0) return;
2881
+ this.slots.delete(bucket.key);
2882
+ if (bucket.hasSubscribers()) {
2883
+ this.reservedBuckets.set(bucket.key, bucket);
2884
+ } else {
2885
+ this.reservedBuckets.delete(bucket.key);
2886
+ }
2887
+ }
2888
+ dropIfReserved(key) {
2889
+ const reserved = this.reservedBuckets.get(key);
2890
+ if (reserved && reserved.size === 0 && !reserved.hasSubscribers()) {
2891
+ this.reservedBuckets.delete(key);
2892
+ }
2893
+ }
2894
+ setOneByKey(key, slots) {
2895
+ const bucket = this.getOrReserveBucket(key);
2896
+ if (bucket !== slots) {
2897
+ bucket.clear();
2898
+ for (const slot of slots) bucket.add(slot);
2899
+ }
2900
+ if (bucket.size > 0) this.retainBucket(bucket);
2901
+ else this.releaseBucket(bucket);
2902
+ }
2903
+ removeOneByKey(key) {
2904
+ this.slots.delete(key);
2905
+ this.reservedBuckets.delete(key);
2906
+ }
2907
+ removeManyByKeys(keys) {
2908
+ for (let i = 0; i < keys.length; i++) {
2909
+ this.slots.delete(keys[i]);
2910
+ this.reservedBuckets.delete(keys[i]);
2911
+ }
2912
+ }
2913
+ getOneByKey(key) {
2914
+ return this.slots.get(key);
2915
+ }
2916
+ getManyByKeys(keys) {
2917
+ const result = /* @__PURE__ */ new Map();
2918
+ for (let i = 0; i < keys.length; i++) {
2919
+ const slots = this.slots.get(keys[i]);
2920
+ if (slots !== void 0) result.set(keys[i], slots);
2921
+ }
2922
+ return result;
2923
+ }
2924
+ getAllKeys() {
2925
+ return Array.from(this.slots.keys());
2926
+ }
2927
+ getAll() {
2928
+ const result = /* @__PURE__ */ new Map();
2929
+ for (const [path, bucket] of this.slots.entries()) {
2930
+ result.set(path, bucket);
2931
+ }
2932
+ return result;
2933
+ }
2934
+ countAll() {
2935
+ return this.slots.size;
2936
+ }
2937
+ clear() {
2938
+ for (const bucket of this.slots.values()) {
2939
+ bucket.clear();
2940
+ if (bucket.hasSubscribers()) {
2941
+ this.reservedBuckets.set(bucket.key, bucket);
2942
+ }
2943
+ }
2944
+ this.slots.clear();
2945
+ }
2946
+ };
2947
+
2948
+ // src/core/OIMReactiveCollectionIndexCompositeTrieSetBased.ts
2949
+ var OIMReactiveCollectionIndexCompositeTrieSetBased = class extends OIMReactiveIndexManualSetBased {
2950
+ constructor(queue, opts) {
2951
+ const getSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.getSlot;
2952
+ super(queue, {
2953
+ indexOptions: {
2954
+ comparePks: opts.indexOptions?.comparePks,
2955
+ store: opts.indexOptions?.store ?? new OIMIndexStoreTrieDrivenSetBased()
2956
+ }
2957
+ });
2958
+ this.getSlotOrTransient = (pk) => {
2959
+ const slot = this.getSlot(pk);
2960
+ if (!slot) return { pk, item: void 0 };
2961
+ return slot;
2962
+ };
2963
+ this.getSlot = getSlot;
2964
+ this.findSlotForRemoval = opts.collection !== void 0 ? (pk) => opts.collection.findSlotByPk(pk) : opts.getSlot;
2965
+ }
2966
+ setPks(key, pks) {
2967
+ this.index.setPks(key, pks, this.getSlotOrTransient);
2968
+ }
2969
+ addPks(key, pks) {
2970
+ this.index.addPks(key, pks, this.getSlotOrTransient);
2971
+ }
2972
+ removePks(key, pks) {
2973
+ const canonical = [];
2974
+ for (let i = 0; i < pks.length; i++) {
2975
+ const slot = this.findSlotForRemoval(pks[i]);
2976
+ canonical.push(slot ? slot.pk : pks[i]);
2977
+ }
2978
+ this.index.removePks(key, canonical);
2979
+ }
2980
+ getEntitiesByKey(key) {
2981
+ return super.getEntitiesByKey(key);
2982
+ }
2983
+ getEntitiesByKeys(keys) {
2984
+ return super.getEntitiesByKeys(keys);
2985
+ }
2986
+ };
2987
+
2988
+ // src/core/OIMIndexStoreTrieDrivenArrayBased.ts
2989
+ var OIMIndexStoreTrieDrivenArrayBased = class extends OIMIndexStoreArrayBased {
2990
+ constructor() {
2991
+ super(...arguments);
2992
+ this.slots = new OIMTrieMap();
2993
+ this.reservedBuckets = new OIMTrieMap();
2994
+ }
2995
+ getOrReserveBucket(key) {
2996
+ const live = this.slots.get(key);
2997
+ if (live) return live;
2998
+ let reserved = this.reservedBuckets.get(key);
2999
+ if (!reserved) {
3000
+ reserved = new OIMKeyedBucketArrayBased(key);
3001
+ this.reservedBuckets.set(key, reserved);
3002
+ }
3003
+ return reserved;
3004
+ }
3005
+ findBucket(key) {
3006
+ return this.slots.get(key) ?? this.reservedBuckets.get(key);
3007
+ }
3008
+ retainBucket(bucket) {
3009
+ if (bucket.length === 0) return;
3010
+ this.reservedBuckets.delete(bucket.key);
3011
+ if (!this.slots.has(bucket.key)) this.slots.set(bucket.key, bucket);
3012
+ }
3013
+ releaseBucket(bucket) {
3014
+ if (bucket.length > 0) return;
3015
+ this.slots.delete(bucket.key);
3016
+ if (bucket.hasSubscribers()) {
3017
+ this.reservedBuckets.set(bucket.key, bucket);
3018
+ } else {
3019
+ this.reservedBuckets.delete(bucket.key);
3020
+ }
3021
+ }
3022
+ dropIfReserved(key) {
3023
+ const reserved = this.reservedBuckets.get(key);
3024
+ if (reserved && reserved.length === 0 && !reserved.hasSubscribers()) {
3025
+ this.reservedBuckets.delete(key);
3026
+ }
3027
+ }
3028
+ setOneByKey(key, slots) {
3029
+ const bucket = this.getOrReserveBucket(key);
3030
+ if (bucket !== slots) {
3031
+ bucket.length = 0;
3032
+ for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
3033
+ }
3034
+ if (bucket.length > 0) this.retainBucket(bucket);
3035
+ else this.releaseBucket(bucket);
3036
+ }
3037
+ removeOneByKey(key) {
3038
+ this.slots.delete(key);
3039
+ this.reservedBuckets.delete(key);
3040
+ }
3041
+ removeManyByKeys(keys) {
3042
+ for (let i = 0; i < keys.length; i++) {
3043
+ this.slots.delete(keys[i]);
3044
+ this.reservedBuckets.delete(keys[i]);
3045
+ }
3046
+ }
3047
+ getOneByKey(key) {
3048
+ return this.slots.get(key);
3049
+ }
3050
+ getManyByKeys(keys) {
3051
+ const result = /* @__PURE__ */ new Map();
3052
+ for (let i = 0; i < keys.length; i++) {
3053
+ const slots = this.slots.get(keys[i]);
3054
+ if (slots !== void 0) result.set(keys[i], slots);
3055
+ }
3056
+ return result;
3057
+ }
3058
+ getAllKeys() {
3059
+ return Array.from(this.slots.keys());
3060
+ }
3061
+ getAll() {
3062
+ const result = /* @__PURE__ */ new Map();
3063
+ for (const [path, bucket] of this.slots.entries()) {
3064
+ result.set(path, bucket);
3065
+ }
3066
+ return result;
3067
+ }
3068
+ countAll() {
3069
+ return this.slots.size;
3070
+ }
3071
+ clear() {
3072
+ for (const bucket of this.slots.values()) {
3073
+ bucket.length = 0;
3074
+ if (bucket.hasSubscribers()) {
3075
+ this.reservedBuckets.set(bucket.key, bucket);
3076
+ }
3077
+ }
3078
+ this.slots.clear();
3079
+ }
3080
+ };
3081
+
3082
+ // src/core/OIMReactiveCollectionIndexCompositeTrieArrayBased.ts
3083
+ var OIMReactiveCollectionIndexCompositeTrieArrayBased = class extends OIMReactiveIndexManualArrayBased {
3084
+ constructor(queue, opts) {
3085
+ const getSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.getSlot;
3086
+ super(queue, {
3087
+ indexOptions: {
3088
+ comparePks: opts.indexOptions?.comparePks,
3089
+ store: opts.indexOptions?.store ?? new OIMIndexStoreTrieDrivenArrayBased()
3090
+ }
3091
+ });
3092
+ // Persistent per-key-path pk membership for O(1) `addPks` dedup. Trie-keyed
3093
+ // so a rebuilt key path matches by content.
3094
+ this.pksByKey = new OIMTrieMap();
3095
+ this.getSlot = getSlot;
3096
+ }
3097
+ setPks(key, pks) {
3098
+ const pkSet = /* @__PURE__ */ new Set();
3099
+ for (let i = 0; i < pks.length; i++) pkSet.add(pks[i]);
3100
+ this.pksByKey.set(key, pkSet);
3101
+ super.setSlots(key, this.getSlotsOrTransient(pks));
2446
3102
  }
2447
3103
  addPks(key, pks) {
2448
3104
  if (pks.length === 0) return;
2449
- const map = this.membershipOf(key);
3105
+ const pkSet = this.membershipOf(key);
2450
3106
  const newSlots = [];
2451
3107
  for (const pk of pks) {
2452
- if (map.has(pk)) continue;
2453
- const slot = this.resolveRequiredSlot(pk);
2454
- map.set(pk, slot);
2455
- newSlots.push(slot);
3108
+ if (pkSet.has(pk)) continue;
3109
+ pkSet.add(pk);
3110
+ newSlots.push(this.getSlotOrTransient(pk));
2456
3111
  }
2457
- if (newSlots.length > 0) this.addSlots(key, newSlots);
3112
+ if (newSlots.length > 0) this.appendSlots(key, newSlots);
2458
3113
  }
2459
3114
  removePks(key, pks) {
2460
3115
  if (pks.length === 0) return;
2461
- const map = this.membershipOf(key);
2462
- if (map.size === 0) return;
2463
- const removed = [];
2464
- for (const pk of pks) {
2465
- const slot = map.get(pk);
2466
- if (slot) {
2467
- map.delete(pk);
2468
- removed.push(slot);
2469
- }
3116
+ const pksToRemove = new Set(pks);
3117
+ const existingSlots = this.index.getSlotsByKey(key);
3118
+ const nextSlots = existingSlots.filter(
3119
+ (slot) => !pksToRemove.has(slot.pk)
3120
+ );
3121
+ if (nextSlots.length === existingSlots.length) return;
3122
+ const pkSet = this.pksByKey.get(key);
3123
+ if (pkSet) {
3124
+ for (let i = 0; i < pks.length; i++) pkSet.delete(pks[i]);
2470
3125
  }
2471
- if (removed.length === 0) return;
2472
- if (map.size === 0) this.slotByPk.delete(key);
2473
- this.removeSlots(key, removed);
3126
+ if (nextSlots.length === 0) this.clear(key);
3127
+ else super.setSlots(key, nextSlots);
2474
3128
  }
2475
3129
  clear(key) {
2476
- if (key === void 0) this.slotByPk.clear();
2477
- else this.slotByPk.delete(key);
3130
+ if (key === void 0) this.pksByKey.clear();
3131
+ else this.pksByKey.delete(key);
2478
3132
  super.clear(key);
2479
3133
  }
2480
3134
  /**
2481
- * The pk→slot map for a key, lazily seeded from the current bucket so it
2482
- * stays correct even if slots were set through a lower-level path.
3135
+ * The membership set for a key path, lazily seeded from the current bucket so
3136
+ * it stays correct even if slots were set through a lower-level path.
2483
3137
  */
2484
3138
  membershipOf(key) {
2485
- let map = this.slotByPk.get(key);
2486
- if (!map) {
2487
- map = /* @__PURE__ */ new Map();
3139
+ let pkSet = this.pksByKey.get(key);
3140
+ if (!pkSet) {
3141
+ pkSet = /* @__PURE__ */ new Set();
2488
3142
  const existing = this.index.getSlotsByKey(key);
2489
- for (const slot of existing) map.set(slot.pk, slot);
2490
- this.slotByPk.set(key, map);
3143
+ for (let i = 0; i < existing.length; i++) {
3144
+ pkSet.add(existing[i].pk);
3145
+ }
3146
+ this.pksByKey.set(key, pkSet);
2491
3147
  }
2492
- return map;
3148
+ return pkSet;
2493
3149
  }
2494
3150
  getEntitiesByKey(key) {
2495
3151
  return super.getEntitiesByKey(key);
@@ -2497,8 +3153,16 @@ var OIMReactiveCollectionIndexManualSetBased = class extends OIMReactiveIndexMan
2497
3153
  getEntitiesByKeys(keys) {
2498
3154
  return super.getEntitiesByKeys(keys);
2499
3155
  }
2500
- resolveRequiredSlot(pk) {
2501
- const slot = this.resolveSlot(pk);
3156
+ getSlotsOrTransient(pks) {
3157
+ const slots = [];
3158
+ slots.length = pks.length;
3159
+ for (let i = 0; i < pks.length; i++) {
3160
+ slots[i] = this.getSlotOrTransient(pks[i]);
3161
+ }
3162
+ return slots;
3163
+ }
3164
+ getSlotOrTransient(pk) {
3165
+ const slot = this.getSlot(pk);
2502
3166
  if (!slot) return { pk, item: void 0 };
2503
3167
  return slot;
2504
3168
  }
@@ -2811,20 +3475,20 @@ var OIMReactiveGlobalIndexManualArrayBased = class extends OIMReactiveGlobalInde
2811
3475
  // src/core/OIMReactiveCollectionGlobalIndexManualArrayBased.ts
2812
3476
  var OIMReactiveCollectionGlobalIndexManualArrayBased = class extends OIMReactiveGlobalIndexManualArrayBased {
2813
3477
  constructor(queue, opts) {
2814
- const resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.resolveSlot;
3478
+ const getSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.getSlot;
2815
3479
  super(queue, { indexOptions: opts.indexOptions });
2816
3480
  // Single pk membership set for O(1) `addPks` dedup. Lazily seeded from the
2817
3481
  // bucket so it stays correct even after a raw `setSlots` (e.g. derived
2818
3482
  // rebuild), matching the keyed index's `membershipOf`.
2819
3483
  this.membership = /* @__PURE__ */ new Set();
2820
3484
  this.membershipSeeded = false;
2821
- this.resolveSlot = resolveSlot;
3485
+ this.getSlot = getSlot;
2822
3486
  }
2823
3487
  setPks(pks) {
2824
3488
  this.membership.clear();
2825
3489
  for (let i = 0; i < pks.length; i++) this.membership.add(pks[i]);
2826
3490
  this.membershipSeeded = true;
2827
- super.setSlots(this.resolveSlots(pks));
3491
+ super.setSlots(this.getSlotsOrTransient(pks));
2828
3492
  }
2829
3493
  addPks(pks) {
2830
3494
  if (pks.length === 0) return;
@@ -2833,7 +3497,7 @@ var OIMReactiveCollectionGlobalIndexManualArrayBased = class extends OIMReactive
2833
3497
  for (const pk of pks) {
2834
3498
  if (membership.has(pk)) continue;
2835
3499
  membership.add(pk);
2836
- newSlots.push(this.resolveRequiredSlot(pk));
3500
+ newSlots.push(this.getSlotOrTransient(pk));
2837
3501
  }
2838
3502
  if (newSlots.length > 0) super.appendSlots(newSlots);
2839
3503
  }
@@ -2870,16 +3534,16 @@ var OIMReactiveCollectionGlobalIndexManualArrayBased = class extends OIMReactive
2870
3534
  getEntities() {
2871
3535
  return super.getEntities();
2872
3536
  }
2873
- resolveSlots(pks) {
3537
+ getSlotsOrTransient(pks) {
2874
3538
  const slots = [];
2875
3539
  slots.length = pks.length;
2876
3540
  for (let i = 0; i < pks.length; i++) {
2877
- slots[i] = this.resolveRequiredSlot(pks[i]);
3541
+ slots[i] = this.getSlotOrTransient(pks[i]);
2878
3542
  }
2879
3543
  return slots;
2880
3544
  }
2881
- resolveRequiredSlot(pk) {
2882
- const slot = this.resolveSlot(pk);
3545
+ getSlotOrTransient(pk) {
3546
+ const slot = this.getSlot(pk);
2883
3547
  if (!slot) return { pk, item: void 0 };
2884
3548
  return slot;
2885
3549
  }
@@ -2959,18 +3623,18 @@ var OIMReactiveGlobalIndexManualSetBased = class extends OIMReactiveGlobalIndexS
2959
3623
  // src/core/OIMReactiveCollectionGlobalIndexManualSetBased.ts
2960
3624
  var OIMReactiveCollectionGlobalIndexManualSetBased = class extends OIMReactiveGlobalIndexManualSetBased {
2961
3625
  constructor(queue, opts) {
2962
- const resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.resolveSlot;
3626
+ const getSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.getSlot;
2963
3627
  super(queue, { indexOptions: opts.indexOptions });
2964
3628
  this.membership = /* @__PURE__ */ new Map();
2965
3629
  this.membershipSeeded = false;
2966
- this.resolveSlot = resolveSlot;
3630
+ this.getSlot = getSlot;
2967
3631
  }
2968
3632
  setPks(pks) {
2969
3633
  this.membership.clear();
2970
3634
  const slots = [];
2971
3635
  for (let i = 0; i < pks.length; i++) {
2972
3636
  const pk = pks[i];
2973
- const slot = this.resolveRequiredSlot(pk);
3637
+ const slot = this.getSlotOrTransient(pk);
2974
3638
  if (!this.membership.has(pk)) this.membership.set(pk, slot);
2975
3639
  slots.push(slot);
2976
3640
  }
@@ -2983,7 +3647,7 @@ var OIMReactiveCollectionGlobalIndexManualSetBased = class extends OIMReactiveGl
2983
3647
  const newSlots = [];
2984
3648
  for (const pk of pks) {
2985
3649
  if (membership.has(pk)) continue;
2986
- const slot = this.resolveRequiredSlot(pk);
3650
+ const slot = this.getSlotOrTransient(pk);
2987
3651
  membership.set(pk, slot);
2988
3652
  newSlots.push(slot);
2989
3653
  }
@@ -3022,8 +3686,8 @@ var OIMReactiveCollectionGlobalIndexManualSetBased = class extends OIMReactiveGl
3022
3686
  getEntities() {
3023
3687
  return super.getEntities();
3024
3688
  }
3025
- resolveRequiredSlot(pk) {
3026
- const slot = this.resolveSlot(pk);
3689
+ getSlotOrTransient(pk) {
3690
+ const slot = this.getSlot(pk);
3027
3691
  if (!slot) return { pk, item: void 0 };
3028
3692
  return slot;
3029
3693
  }
@@ -3202,6 +3866,30 @@ var OIMCollectionIndexFactory = class {
3202
3866
  indexOptions: opts.indexOptions
3203
3867
  });
3204
3868
  }
3869
+ /**
3870
+ * Composite (trie-backed) manual Set-based index keyed by an
3871
+ * arbitrary-length key path, e.g. `setPks([userId, projectId, role], pks)`.
3872
+ * Key paths are matched by content in O(arity) — no string concatenation,
3873
+ * no separator collisions, each segment keeps its type. Primitive-keyed
3874
+ * indexes are unaffected and keep their native-`Map` fast path.
3875
+ */
3876
+ compositeSetIndex(opts = {}) {
3877
+ return new OIMReactiveCollectionIndexCompositeTrieSetBased(this.queue, {
3878
+ collection: this.collection,
3879
+ indexOptions: opts.indexOptions
3880
+ });
3881
+ }
3882
+ /**
3883
+ * Composite (trie-backed) manual Array-based (ordered) index keyed by an
3884
+ * arbitrary-length key path, e.g. `setPks([channelId, threadId], pks)`.
3885
+ * Ordered counterpart of `compositeSetIndex`.
3886
+ */
3887
+ compositeArrayIndex(opts = {}) {
3888
+ return new OIMReactiveCollectionIndexCompositeTrieArrayBased(this.queue, {
3889
+ collection: this.collection,
3890
+ indexOptions: opts.indexOptions
3891
+ });
3892
+ }
3205
3893
  derivedSetIndex(selectIndexKeys, opts = {}) {
3206
3894
  return new OIMDerivedCollectionIndexSetBased(
3207
3895
  this.queue,
@@ -3408,7 +4096,7 @@ var OIMObject = class {
3408
4096
  var OIMReactiveObject = class extends OIMObject {
3409
4097
  constructor(queue, opts) {
3410
4098
  super(opts);
3411
- this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierResolver());
4099
+ this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierProvider());
3412
4100
  }
3413
4101
  /**
3414
4102
  * Mark the written key(s) on the keyed emitter directly — no general-event
@@ -3450,6 +4138,148 @@ var OIMReactiveObject = class extends OIMObject {
3450
4138
  }
3451
4139
  };
3452
4140
 
4141
+ // src/core/OIMCollectionStoreTrieDriven.ts
4142
+ var OIMCollectionStoreTrieDriven = class extends OIMCollectionStore {
4143
+ constructor() {
4144
+ super(...arguments);
4145
+ this.slots = new OIMTrieMap();
4146
+ this.reservedSlots = new OIMTrieMap();
4147
+ }
4148
+ setOneByPk(pk, entity) {
4149
+ const slot = this.slots.get(pk);
4150
+ if (slot) {
4151
+ slot.item = entity;
4152
+ return slot;
4153
+ }
4154
+ const reserved = this.reservedSlots.get(pk);
4155
+ if (reserved) {
4156
+ reserved.item = entity;
4157
+ this.reservedSlots.delete(pk);
4158
+ this.slots.set(pk, reserved);
4159
+ return reserved;
4160
+ }
4161
+ const nextSlot = {
4162
+ pk,
4163
+ item: entity
4164
+ };
4165
+ this.slots.set(pk, nextSlot);
4166
+ return nextSlot;
4167
+ }
4168
+ getOrReserveSlotByPk(pk) {
4169
+ const slot = this.slots.get(pk);
4170
+ if (slot) return slot;
4171
+ let reserved = this.reservedSlots.get(pk);
4172
+ if (!reserved) {
4173
+ reserved = { pk, item: void 0 };
4174
+ this.reservedSlots.set(pk, reserved);
4175
+ }
4176
+ return reserved;
4177
+ }
4178
+ setManyByPks(pks, entities) {
4179
+ const slots = [];
4180
+ slots.length = pks.length;
4181
+ for (let i = 0; i < pks.length; i++) {
4182
+ slots[i] = this.setOneByPk(pks[i], entities[i]);
4183
+ }
4184
+ return slots;
4185
+ }
4186
+ getSlotByPk(pk) {
4187
+ return this.slots.get(pk);
4188
+ }
4189
+ findSlotByPk(pk) {
4190
+ return this.slots.get(pk) ?? this.reservedSlots.get(pk);
4191
+ }
4192
+ hasSubscribers(slot) {
4193
+ return slot.subscribers !== void 0 && slot.subscribers.size > 0;
4194
+ }
4195
+ getSlotsByPks(pks) {
4196
+ const result = [];
4197
+ result.length = pks.length;
4198
+ let writeIndex = 0;
4199
+ for (let i = 0; i < pks.length; i++) {
4200
+ const slot = this.slots.get(pks[i]);
4201
+ if (slot !== void 0) result[writeIndex++] = slot;
4202
+ }
4203
+ result.length = writeIndex;
4204
+ return result;
4205
+ }
4206
+ getAllSlots() {
4207
+ return Array.from(this.slots.values());
4208
+ }
4209
+ removeOneByPk(pk) {
4210
+ const slot = this.slots.get(pk);
4211
+ if (slot) {
4212
+ slot.item = void 0;
4213
+ this.slots.delete(pk);
4214
+ if (this.hasSubscribers(slot)) {
4215
+ this.reservedSlots.set(pk, slot);
4216
+ }
4217
+ return;
4218
+ }
4219
+ const reserved = this.reservedSlots.get(pk);
4220
+ if (reserved) {
4221
+ reserved.item = void 0;
4222
+ if (!this.hasSubscribers(reserved)) {
4223
+ this.reservedSlots.delete(pk);
4224
+ }
4225
+ }
4226
+ }
4227
+ removeManyByPks(pks) {
4228
+ for (const pk of pks) this.removeOneByPk(pk);
4229
+ }
4230
+ getOneByPk(pk) {
4231
+ return this.slots.get(pk)?.item;
4232
+ }
4233
+ getManyByPks(pks) {
4234
+ const result = [];
4235
+ result.length = pks.length;
4236
+ let writeIndex = 0;
4237
+ for (let i = 0; i < pks.length; i++) {
4238
+ const entity = this.getOneByPk(pks[i]);
4239
+ if (entity !== void 0) result[writeIndex++] = entity;
4240
+ }
4241
+ result.length = writeIndex;
4242
+ return result;
4243
+ }
4244
+ getAll() {
4245
+ const result = [];
4246
+ for (const slot of this.slots.values()) {
4247
+ if (slot.item !== void 0) result.push(slot.item);
4248
+ }
4249
+ return result;
4250
+ }
4251
+ countAll() {
4252
+ return this.slots.size;
4253
+ }
4254
+ clear() {
4255
+ const retained = [];
4256
+ for (const slot of this.slots.values()) {
4257
+ slot.item = void 0;
4258
+ if (this.hasSubscribers(slot)) retained.push(slot);
4259
+ }
4260
+ this.slots.clear();
4261
+ for (const slot of this.reservedSlots.values()) {
4262
+ slot.item = void 0;
4263
+ if (this.hasSubscribers(slot)) retained.push(slot);
4264
+ }
4265
+ this.reservedSlots.clear();
4266
+ for (const slot of retained) {
4267
+ this.reservedSlots.set(slot.pk, slot);
4268
+ }
4269
+ }
4270
+ getAllPks() {
4271
+ return Array.from(this.slots.keys());
4272
+ }
4273
+ destroy() {
4274
+ this.clear();
4275
+ for (const slot of this.reservedSlots.values()) {
4276
+ slot.subscribers?.clear();
4277
+ slot.subscribers = void 0;
4278
+ }
4279
+ this.reservedSlots.clear();
4280
+ }
4281
+ };
4282
+
3453
4283
  // src/core/OIMObjectStoreMapDriven.ts
3454
4284
  var OIMObjectStoreMapDriven = class extends OIMObjectStore {
3455
4285
  constructor() {
@@ -3497,6 +4327,27 @@ var OIMObjectStoreMapDriven = class extends OIMObjectStore {
3497
4327
  }
3498
4328
  };
3499
4329
 
4330
+ // src/core/OIMKeyPathCarrierProvider.ts
4331
+ var OIMKeyPathCarrierProvider = class {
4332
+ constructor() {
4333
+ this.carriers = new OIMTrieMap();
4334
+ }
4335
+ getOrReserveCarrier(key) {
4336
+ let carrier = this.carriers.get(key);
4337
+ if (!carrier) {
4338
+ carrier = { key };
4339
+ this.carriers.set(key, carrier);
4340
+ }
4341
+ return carrier;
4342
+ }
4343
+ findCarrier(key) {
4344
+ return this.carriers.get(key);
4345
+ }
4346
+ onCarrierEmptied(carrier) {
4347
+ this.carriers.delete(carrier.key);
4348
+ }
4349
+ };
4350
+
3500
4351
  // src/core/OIMComparatorFactory.ts
3501
4352
  var OIMComparatorFactory = class {
3502
4353
  createShallowComparator() {
@@ -3715,6 +4566,103 @@ var OIMMap2Keys = class {
3715
4566
  }
3716
4567
  };
3717
4568
 
4569
+ // src/core/OIMDisposeScope.ts
4570
+ var OIMDisposeScope = class _OIMDisposeScope {
4571
+ constructor() {
4572
+ this.disposables = [];
4573
+ this.disposed = false;
4574
+ }
4575
+ /** Number of not-yet-disposed items currently held. */
4576
+ get size() {
4577
+ return this.disposables.length;
4578
+ }
4579
+ get isDisposed() {
4580
+ return this.disposed;
4581
+ }
4582
+ /**
4583
+ * Register a disposable (object with `destroy()` or an unsubscribe function)
4584
+ * and return it unchanged, so it can be captured inline. If the scope is
4585
+ * already disposed the item is disposed immediately, so nothing leaks.
4586
+ */
4587
+ add(disposable) {
4588
+ if (this.disposed) {
4589
+ this.disposeOne(disposable);
4590
+ return disposable;
4591
+ }
4592
+ this.disposables.push(disposable);
4593
+ return disposable;
4594
+ }
4595
+ /** A nested scope, itself registered here — dispose it early or with the parent. */
4596
+ child() {
4597
+ return this.add(new _OIMDisposeScope());
4598
+ }
4599
+ /**
4600
+ * Dispose everything in reverse registration order, then clear. Idempotent.
4601
+ * If any `destroy()`/unsubscribe throws, the rest are still disposed and the
4602
+ * first error is rethrown once teardown completes.
4603
+ */
4604
+ destroy() {
4605
+ if (this.disposed) return;
4606
+ this.disposed = true;
4607
+ let firstError;
4608
+ let hasError = false;
4609
+ for (let i = this.disposables.length - 1; i >= 0; i--) {
4610
+ try {
4611
+ this.disposeOne(this.disposables[i]);
4612
+ } catch (error) {
4613
+ if (!hasError) {
4614
+ hasError = true;
4615
+ firstError = error;
4616
+ }
4617
+ }
4618
+ }
4619
+ this.disposables.length = 0;
4620
+ if (hasError) throw firstError;
4621
+ }
4622
+ disposeOne(disposable) {
4623
+ if (typeof disposable === "function") disposable();
4624
+ else disposable.destroy();
4625
+ }
4626
+ };
4627
+ function createOIMDisposeScope() {
4628
+ return new OIMDisposeScope();
4629
+ }
4630
+
4631
+ // src/core/OIMTrieSet.ts
4632
+ var OIMTrieSet = class {
4633
+ constructor() {
4634
+ this.map = new OIMTrieMap();
4635
+ }
4636
+ get size() {
4637
+ return this.map.size;
4638
+ }
4639
+ add(key) {
4640
+ this.map.set(key, true);
4641
+ }
4642
+ has(key) {
4643
+ return this.map.has(key);
4644
+ }
4645
+ delete(key) {
4646
+ return this.map.delete(key);
4647
+ }
4648
+ *values() {
4649
+ yield* this.map.keys();
4650
+ }
4651
+ clear() {
4652
+ this.map.clear();
4653
+ }
4654
+ };
4655
+
4656
+ // src/core/OIMPkCodecKeyPath.ts
4657
+ var OIMPkCodecKeyPath = class {
4658
+ encode(pk) {
4659
+ return JSON.stringify(pk);
4660
+ }
4661
+ decode(encoded) {
4662
+ return JSON.parse(encoded);
4663
+ }
4664
+ };
4665
+
3718
4666
  // src/modules/compute/core/OIMComputeRuntime.ts
3719
4667
  var OIMComputeRuntime = class {
3720
4668
  constructor(queue) {
@@ -4318,6 +5266,27 @@ var OIMCollectionSelectors = class {
4318
5266
  key
4319
5267
  );
4320
5268
  }
5269
+ /**
5270
+ * Entities of a composite (key-path) set-based index for one key path, e.g.
5271
+ * `entitiesByCompositeSetIndexKey(index, [projectId, role])`.
5272
+ */
5273
+ entitiesByCompositeSetIndexKey(index, key) {
5274
+ return new OIMEntitiesByIndexKeySetBasedSelector(
5275
+ this.runtime,
5276
+ this.collection,
5277
+ index,
5278
+ key
5279
+ );
5280
+ }
5281
+ /** Entities of a composite (key-path) array-based index for one key path. */
5282
+ entitiesByCompositeArrayIndexKey(index, key) {
5283
+ return new OIMEntitiesByIndexKeyArrayBasedSelector(
5284
+ this.runtime,
5285
+ this.collection,
5286
+ index,
5287
+ key
5288
+ );
5289
+ }
4321
5290
  /** Entities of a keyless (whole-collection) array-based index, ordered. */
4322
5291
  entitiesByArrayGlobalIndex(index) {
4323
5292
  return new OIMEntitiesByGlobalIndexArrayBasedSelector(
@@ -4341,7 +5310,16 @@ function createOIMCollectionKit(queue, opts) {
4341
5310
  const collection = new OIMReactiveCollection(queue, opts);
4342
5311
  const indexFactory = new OIMCollectionIndexFactory(queue, collection);
4343
5312
  const select = new OIMCollectionSelectors(queue, collection);
4344
- return { queue, collection, indexFactory, select };
5313
+ const scope = new OIMDisposeScope();
5314
+ scope.add(collection);
5315
+ return {
5316
+ queue,
5317
+ collection,
5318
+ indexFactory,
5319
+ select,
5320
+ scope,
5321
+ destroy: () => scope.destroy()
5322
+ };
4345
5323
  }
4346
5324
 
4347
5325
  // src/modules/wrapper/collection/OIMCollectionChangedFields.ts
@@ -5322,35 +6300,20 @@ var OIMComputed = class {
5322
6300
  };
5323
6301
 
5324
6302
  // src/modules/effect/core/OIMEffectDependencyKeyed.ts
5325
- function isReadonlyArray(value) {
5326
- return Array.isArray(value);
5327
- }
5328
6303
  var OIMEffectDependencyKeyed = class {
5329
- constructor(source, keyOrKeys) {
6304
+ constructor(source, key) {
5330
6305
  this.source = source;
5331
- if (isReadonlyArray(keyOrKeys)) {
5332
- this.keys = keyOrKeys;
5333
- } else {
5334
- this.key = keyOrKeys;
5335
- }
6306
+ this.key = key;
5336
6307
  }
5337
6308
  subscribe(onUpdate) {
5338
- if (this.keys !== void 0) {
5339
- return this.source.subscribeOnKeys(this.keys, onUpdate);
5340
- }
5341
- if (this.key === void 0) return () => {
5342
- };
5343
6309
  return this.source.subscribeOnKey(this.key, onUpdate);
5344
6310
  }
5345
6311
  };
5346
6312
 
5347
6313
  // src/modules/effect/core/OIMEffectDependencyKeyedObject.ts
5348
6314
  var OIMEffectDependencyKeyedObject = class {
5349
- constructor(object, keyOrKeys) {
5350
- this.dep = new OIMEffectDependencyKeyed(
5351
- object,
5352
- keyOrKeys
5353
- );
6315
+ constructor(object, key) {
6316
+ this.dep = new OIMEffectDependencyKeyed(object, key);
5354
6317
  }
5355
6318
  get source() {
5356
6319
  return this.dep.source;
@@ -5362,11 +6325,8 @@ var OIMEffectDependencyKeyedObject = class {
5362
6325
 
5363
6326
  // src/modules/effect/core/OIMEffectDependencyKeyedCollection.ts
5364
6327
  var OIMEffectDependencyKeyedCollection = class {
5365
- constructor(collection, pkOrPks) {
5366
- this.dep = new OIMEffectDependencyKeyed(
5367
- collection,
5368
- pkOrPks
5369
- );
6328
+ constructor(collection, pk) {
6329
+ this.dep = new OIMEffectDependencyKeyed(collection, pk);
5370
6330
  }
5371
6331
  get source() {
5372
6332
  return this.dep.source;
@@ -5378,11 +6338,8 @@ var OIMEffectDependencyKeyedCollection = class {
5378
6338
 
5379
6339
  // src/modules/effect/core/OIMEffectDependencyKeyedIndex.ts
5380
6340
  var OIMEffectDependencyKeyedIndex = class {
5381
- constructor(index, keyOrKeys) {
5382
- this.dep = new OIMEffectDependencyKeyed(
5383
- index,
5384
- keyOrKeys
5385
- );
6341
+ constructor(index, key) {
6342
+ this.dep = new OIMEffectDependencyKeyed(index, key);
5386
6343
  }
5387
6344
  get source() {
5388
6345
  return this.dep.source;
@@ -5530,6 +6487,7 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
5530
6487
  EOIMEventQueueSchedulerEventType,
5531
6488
  EOIMIndexEventType,
5532
6489
  EOIMObjectEventType,
6490
+ OIMBucketCarrierProvider,
5533
6491
  OIMCarrierKeyedEmitter,
5534
6492
  OIMCarrierSingleEmitter,
5535
6493
  OIMCollection,
@@ -5542,6 +6500,7 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
5542
6500
  OIMCollectionSelectors,
5543
6501
  OIMCollectionStore,
5544
6502
  OIMCollectionStoreMapDriven,
6503
+ OIMCollectionStoreTrieDriven,
5545
6504
  OIMComparatorFactory,
5546
6505
  OIMComputeRuntime,
5547
6506
  OIMComputed,
@@ -5550,6 +6509,7 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
5550
6509
  OIMDerivedCollectionGlobalIndexSetBased,
5551
6510
  OIMDerivedCollectionIndexArrayBased,
5552
6511
  OIMDerivedCollectionIndexSetBased,
6512
+ OIMDisposeScope,
5553
6513
  OIMEffect,
5554
6514
  OIMEffectDependencyComputed,
5555
6515
  OIMEffectDependencyKeyed,
@@ -5592,6 +6552,11 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
5592
6552
  OIMIndexStoreMapDrivenArrayBased,
5593
6553
  OIMIndexStoreMapDrivenSetBased,
5594
6554
  OIMIndexStoreSetBased,
6555
+ OIMIndexStoreTrieDrivenArrayBased,
6556
+ OIMIndexStoreTrieDrivenSetBased,
6557
+ OIMKeyPathCarrierProvider,
6558
+ OIMKeyedBucketArrayBased,
6559
+ OIMKeyedBucketSetBased,
5595
6560
  OIMMap2Keys,
5596
6561
  OIMObject,
5597
6562
  OIMObjectStore,
@@ -5603,10 +6568,13 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
5603
6568
  OIMOrderedListCommandStream,
5604
6569
  OIMOrderedListCommandStreamDiffDriven,
5605
6570
  OIMOrderedListMappedCommandStream,
6571
+ OIMPkCodecKeyPath,
5606
6572
  OIMPkSelectorFactory,
5607
6573
  OIMReactiveCollection,
5608
6574
  OIMReactiveCollectionGlobalIndexManualArrayBased,
5609
6575
  OIMReactiveCollectionGlobalIndexManualSetBased,
6576
+ OIMReactiveCollectionIndexCompositeTrieArrayBased,
6577
+ OIMReactiveCollectionIndexCompositeTrieSetBased,
5610
6578
  OIMReactiveCollectionIndexManualArrayBased,
5611
6579
  OIMReactiveCollectionIndexManualSetBased,
5612
6580
  OIMReactiveGlobalIndex,
@@ -5621,10 +6589,13 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
5621
6589
  OIMReactiveIndexSetBased,
5622
6590
  OIMReactiveObject,
5623
6591
  OIMSelector,
6592
+ OIMTrieMap,
6593
+ OIMTrieSet,
5624
6594
  createInPlaceEntityUpdater,
5625
6595
  createMergeEntityUpdater,
5626
6596
  createOIMCollectionIndexFactory,
5627
6597
  createOIMCollectionKit,
6598
+ createOIMDisposeScope,
5628
6599
  createOIMGlobalIndexSlotMap,
5629
6600
  createOIMIndexSlotMap,
5630
6601
  createOIMOrderedListCommandStreamDiffDriven,