@oimdb/core 3.6.0 → 3.8.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
+ OIMBucketCarrierResolver: () => OIMBucketCarrierResolver,
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,
@@ -71,6 +74,7 @@ __export(index_exports, {
71
74
  OIMGlobalIndexManualArrayBased: () => OIMGlobalIndexManualArrayBased,
72
75
  OIMGlobalIndexManualSetBased: () => OIMGlobalIndexManualSetBased,
73
76
  OIMGlobalIndexSetBased: () => OIMGlobalIndexSetBased,
77
+ OIMGlobalIndexSlotMap: () => OIMGlobalIndexSlotMap,
74
78
  OIMIndex: () => OIMIndex,
75
79
  OIMIndexArrayBased: () => OIMIndexArrayBased,
76
80
  OIMIndexComparatorFactory: () => OIMIndexComparatorFactory,
@@ -87,6 +91,13 @@ __export(index_exports, {
87
91
  OIMIndexStoreMapDrivenArrayBased: () => OIMIndexStoreMapDrivenArrayBased,
88
92
  OIMIndexStoreMapDrivenSetBased: () => OIMIndexStoreMapDrivenSetBased,
89
93
  OIMIndexStoreSetBased: () => OIMIndexStoreSetBased,
94
+ OIMIndexStoreTrieDrivenArrayBased: () => OIMIndexStoreTrieDrivenArrayBased,
95
+ OIMIndexStoreTrieDrivenSetBased: () => OIMIndexStoreTrieDrivenSetBased,
96
+ OIMKeyDomainNative: () => OIMKeyDomainNative,
97
+ OIMKeyDomainTrie: () => OIMKeyDomainTrie,
98
+ OIMKeyPathCarrierResolver: () => OIMKeyPathCarrierResolver,
99
+ OIMKeyedBucketArrayBased: () => OIMKeyedBucketArrayBased,
100
+ OIMKeyedBucketSetBased: () => OIMKeyedBucketSetBased,
90
101
  OIMMap2Keys: () => OIMMap2Keys,
91
102
  OIMObject: () => OIMObject,
92
103
  OIMObjectStore: () => OIMObjectStore,
@@ -98,10 +109,13 @@ __export(index_exports, {
98
109
  OIMOrderedListCommandStream: () => OIMOrderedListCommandStream,
99
110
  OIMOrderedListCommandStreamDiffDriven: () => OIMOrderedListCommandStreamDiffDriven,
100
111
  OIMOrderedListMappedCommandStream: () => OIMOrderedListMappedCommandStream,
112
+ OIMPkCodecKeyPath: () => OIMPkCodecKeyPath,
101
113
  OIMPkSelectorFactory: () => OIMPkSelectorFactory,
102
114
  OIMReactiveCollection: () => OIMReactiveCollection,
103
115
  OIMReactiveCollectionGlobalIndexManualArrayBased: () => OIMReactiveCollectionGlobalIndexManualArrayBased,
104
116
  OIMReactiveCollectionGlobalIndexManualSetBased: () => OIMReactiveCollectionGlobalIndexManualSetBased,
117
+ OIMReactiveCollectionIndexCompositeTrieArrayBased: () => OIMReactiveCollectionIndexCompositeTrieArrayBased,
118
+ OIMReactiveCollectionIndexCompositeTrieSetBased: () => OIMReactiveCollectionIndexCompositeTrieSetBased,
105
119
  OIMReactiveCollectionIndexManualArrayBased: () => OIMReactiveCollectionIndexManualArrayBased,
106
120
  OIMReactiveCollectionIndexManualSetBased: () => OIMReactiveCollectionIndexManualSetBased,
107
121
  OIMReactiveGlobalIndex: () => OIMReactiveGlobalIndex,
@@ -116,10 +130,14 @@ __export(index_exports, {
116
130
  OIMReactiveIndexSetBased: () => OIMReactiveIndexSetBased,
117
131
  OIMReactiveObject: () => OIMReactiveObject,
118
132
  OIMSelector: () => OIMSelector,
133
+ OIMTrieMap: () => OIMTrieMap,
134
+ OIMTrieSet: () => OIMTrieSet,
119
135
  createInPlaceEntityUpdater: () => createInPlaceEntityUpdater,
120
136
  createMergeEntityUpdater: () => createMergeEntityUpdater,
121
137
  createOIMCollectionIndexFactory: () => createOIMCollectionIndexFactory,
122
138
  createOIMCollectionKit: () => createOIMCollectionKit,
139
+ createOIMDisposeScope: () => createOIMDisposeScope,
140
+ createOIMGlobalIndexSlotMap: () => createOIMGlobalIndexSlotMap,
123
141
  createOIMIndexSlotMap: () => createOIMIndexSlotMap,
124
142
  createOIMOrderedListCommandStreamDiffDriven: () => createOIMOrderedListCommandStreamDiffDriven,
125
143
  createOIMOrderedListMappedCommandStream: () => createOIMOrderedListMappedCommandStream,
@@ -356,21 +374,82 @@ var OIMEventQueueScheduler = class {
356
374
  }
357
375
  };
358
376
 
377
+ // src/core/OIMKeyedBucketSetBased.ts
378
+ var OIMKeyedBucketSetBased = class extends Set {
379
+ constructor(key) {
380
+ super();
381
+ this.key = key;
382
+ }
383
+ hasSubscribers() {
384
+ return this.subscribers !== void 0 && this.subscribers.size > 0;
385
+ }
386
+ /** Clearing the slot set also clears the pk membership — they stay in sync. */
387
+ clear() {
388
+ super.clear();
389
+ this.membership?.clear();
390
+ }
391
+ };
392
+
359
393
  // src/core/OIMIndexStoreMapDrivenSetBased.ts
360
394
  var OIMIndexStoreMapDrivenSetBased = class extends OIMIndexStoreSetBased {
361
395
  constructor() {
362
396
  super(...arguments);
397
+ // Live buckets (non-empty, or freshly promoted). Enumeration reads these only.
363
398
  this.slots = /* @__PURE__ */ new Map();
399
+ // Empty buckets kept alive solely by their subscribers, so a subscription
400
+ // survives a key going empty → re-filled. Invisible to reads/enumeration.
401
+ this.reservedBuckets = /* @__PURE__ */ new Map();
402
+ }
403
+ getOrReserveBucket(key) {
404
+ const live = this.slots.get(key);
405
+ if (live) return live;
406
+ let reserved = this.reservedBuckets.get(key);
407
+ if (!reserved) {
408
+ reserved = new OIMKeyedBucketSetBased(key);
409
+ this.reservedBuckets.set(key, reserved);
410
+ }
411
+ return reserved;
412
+ }
413
+ findBucket(key) {
414
+ return this.slots.get(key) ?? this.reservedBuckets.get(key);
415
+ }
416
+ retainBucket(bucket) {
417
+ if (bucket.size === 0) return;
418
+ this.reservedBuckets.delete(bucket.key);
419
+ if (!this.slots.has(bucket.key)) this.slots.set(bucket.key, bucket);
420
+ }
421
+ releaseBucket(bucket) {
422
+ if (bucket.size > 0) return;
423
+ this.slots.delete(bucket.key);
424
+ if (bucket.hasSubscribers()) {
425
+ this.reservedBuckets.set(bucket.key, bucket);
426
+ } else {
427
+ this.reservedBuckets.delete(bucket.key);
428
+ }
429
+ }
430
+ dropIfReserved(key) {
431
+ const reserved = this.reservedBuckets.get(key);
432
+ if (reserved && reserved.size === 0 && !reserved.hasSubscribers()) {
433
+ this.reservedBuckets.delete(key);
434
+ }
364
435
  }
365
436
  setOneByKey(key, slots) {
366
- this.slots.set(key, slots);
437
+ const bucket = this.getOrReserveBucket(key);
438
+ if (bucket !== slots) {
439
+ bucket.clear();
440
+ for (const slot of slots) bucket.add(slot);
441
+ }
442
+ if (bucket.size > 0) this.retainBucket(bucket);
443
+ else this.releaseBucket(bucket);
367
444
  }
368
445
  removeOneByKey(key) {
369
446
  this.slots.delete(key);
447
+ this.reservedBuckets.delete(key);
370
448
  }
371
449
  removeManyByKeys(keys) {
372
450
  for (const key of keys) {
373
451
  this.slots.delete(key);
452
+ this.reservedBuckets.delete(key);
374
453
  }
375
454
  }
376
455
  getOneByKey(key) {
@@ -379,10 +458,8 @@ var OIMIndexStoreMapDrivenSetBased = class extends OIMIndexStoreSetBased {
379
458
  getManyByKeys(keys) {
380
459
  const result = /* @__PURE__ */ new Map();
381
460
  for (const key of keys) {
382
- const slots = this.getOneByKey(key);
383
- if (slots !== void 0) {
384
- result.set(key, slots);
385
- }
461
+ const slots = this.slots.get(key);
462
+ if (slots !== void 0) result.set(key, slots);
386
463
  }
387
464
  return result;
388
465
  }
@@ -396,6 +473,12 @@ var OIMIndexStoreMapDrivenSetBased = class extends OIMIndexStoreSetBased {
396
473
  return this.slots.size;
397
474
  }
398
475
  clear() {
476
+ for (const bucket of this.slots.values()) {
477
+ bucket.clear();
478
+ if (bucket.hasSubscribers()) {
479
+ this.reservedBuckets.set(bucket.key, bucket);
480
+ }
481
+ }
399
482
  this.slots.clear();
400
483
  }
401
484
  };
@@ -403,10 +486,9 @@ var OIMIndexStoreMapDrivenSetBased = class extends OIMIndexStoreSetBased {
403
486
  // src/abstract/OIMIndexSetBased.ts
404
487
  var OIMIndexSetBased = class extends OIMIndex {
405
488
  constructor(options = {}) {
406
- super(
407
- options.store ?? new OIMIndexStoreMapDrivenSetBased(),
408
- options.comparePks
409
- );
489
+ const store = options.store ?? new OIMIndexStoreMapDrivenSetBased();
490
+ super(store, options.comparePks);
491
+ this.setStore = store;
410
492
  }
411
493
  getBucketSize(bucket) {
412
494
  return bucket.size;
@@ -434,22 +516,36 @@ var OIMIndexSetBased = class extends OIMIndex {
434
516
  for (const slot of slots) pks.add(slot.pk);
435
517
  return pks;
436
518
  }
437
- setSlotsWithComparison(key, newSlots) {
438
- if (this.comparePks) {
439
- const existingSlotsSet = this.store.getOneByKey(key);
440
- if (existingSlotsSet && existingSlotsSet.size === newSlots.size) {
441
- if (this.comparePks(
442
- Array.from(this.slotsToPks(existingSlotsSet)),
443
- Array.from(this.slotsToPks(newSlots))
444
- )) {
445
- return false;
446
- }
447
- } else if (!existingSlotsSet && newSlots.size === 0) {
448
- return false;
449
- }
450
- }
451
- this.store.setOneByKey(key, newSlots);
452
- return true;
519
+ /**
520
+ * True if a comparator is set and considers `bucket`'s current pks equal to
521
+ * `newSlots` — i.e. a `setSlots` would be a no-op and should not emit.
522
+ */
523
+ bucketMatchesComparator(bucket, newSlots) {
524
+ if (!this.comparePks) return false;
525
+ if (bucket.size !== newSlots.size) return false;
526
+ if (bucket.size === 0) return true;
527
+ return this.comparePks(
528
+ Array.from(this.slotsToPks(bucket)),
529
+ Array.from(this.slotsToPks(newSlots))
530
+ );
531
+ }
532
+ };
533
+
534
+ // src/core/OIMKeyedBucketArrayBased.ts
535
+ var OIMKeyedBucketArrayBased = class extends Array {
536
+ constructor(key) {
537
+ super();
538
+ this.key = key;
539
+ }
540
+ static get [Symbol.species]() {
541
+ return Array;
542
+ }
543
+ hasSubscribers() {
544
+ return this.subscribers !== void 0 && this.subscribers.size > 0;
545
+ }
546
+ /** Empty the bucket in place (Array has no `clear`). */
547
+ clear() {
548
+ this.length = 0;
453
549
  }
454
550
  };
455
551
 
@@ -457,17 +553,61 @@ var OIMIndexSetBased = class extends OIMIndex {
457
553
  var OIMIndexStoreMapDrivenArrayBased = class extends OIMIndexStoreArrayBased {
458
554
  constructor() {
459
555
  super(...arguments);
556
+ // Live buckets (non-empty). Enumeration reads these only.
460
557
  this.slots = /* @__PURE__ */ new Map();
558
+ // Empty buckets kept alive by their subscribers, invisible to reads.
559
+ this.reservedBuckets = /* @__PURE__ */ new Map();
560
+ }
561
+ getOrReserveBucket(key) {
562
+ const live = this.slots.get(key);
563
+ if (live) return live;
564
+ let reserved = this.reservedBuckets.get(key);
565
+ if (!reserved) {
566
+ reserved = new OIMKeyedBucketArrayBased(key);
567
+ this.reservedBuckets.set(key, reserved);
568
+ }
569
+ return reserved;
570
+ }
571
+ findBucket(key) {
572
+ return this.slots.get(key) ?? this.reservedBuckets.get(key);
573
+ }
574
+ retainBucket(bucket) {
575
+ if (bucket.length === 0) return;
576
+ this.reservedBuckets.delete(bucket.key);
577
+ if (!this.slots.has(bucket.key)) this.slots.set(bucket.key, bucket);
578
+ }
579
+ releaseBucket(bucket) {
580
+ if (bucket.length > 0) return;
581
+ this.slots.delete(bucket.key);
582
+ if (bucket.hasSubscribers()) {
583
+ this.reservedBuckets.set(bucket.key, bucket);
584
+ } else {
585
+ this.reservedBuckets.delete(bucket.key);
586
+ }
587
+ }
588
+ dropIfReserved(key) {
589
+ const reserved = this.reservedBuckets.get(key);
590
+ if (reserved && reserved.length === 0 && !reserved.hasSubscribers()) {
591
+ this.reservedBuckets.delete(key);
592
+ }
461
593
  }
462
594
  setOneByKey(key, slots) {
463
- this.slots.set(key, slots);
595
+ const bucket = this.getOrReserveBucket(key);
596
+ if (bucket !== slots) {
597
+ bucket.length = 0;
598
+ for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
599
+ }
600
+ if (bucket.length > 0) this.retainBucket(bucket);
601
+ else this.releaseBucket(bucket);
464
602
  }
465
603
  removeOneByKey(key) {
466
604
  this.slots.delete(key);
605
+ this.reservedBuckets.delete(key);
467
606
  }
468
607
  removeManyByKeys(keys) {
469
608
  for (const key of keys) {
470
609
  this.slots.delete(key);
610
+ this.reservedBuckets.delete(key);
471
611
  }
472
612
  }
473
613
  getOneByKey(key) {
@@ -476,10 +616,8 @@ var OIMIndexStoreMapDrivenArrayBased = class extends OIMIndexStoreArrayBased {
476
616
  getManyByKeys(keys) {
477
617
  const result = /* @__PURE__ */ new Map();
478
618
  for (const key of keys) {
479
- const slots = this.getOneByKey(key);
480
- if (slots !== void 0) {
481
- result.set(key, slots);
482
- }
619
+ const slots = this.slots.get(key);
620
+ if (slots !== void 0) result.set(key, slots);
483
621
  }
484
622
  return result;
485
623
  }
@@ -493,6 +631,12 @@ var OIMIndexStoreMapDrivenArrayBased = class extends OIMIndexStoreArrayBased {
493
631
  return this.slots.size;
494
632
  }
495
633
  clear() {
634
+ for (const bucket of this.slots.values()) {
635
+ bucket.length = 0;
636
+ if (bucket.hasSubscribers()) {
637
+ this.reservedBuckets.set(bucket.key, bucket);
638
+ }
639
+ }
496
640
  this.slots.clear();
497
641
  }
498
642
  };
@@ -500,10 +644,9 @@ var OIMIndexStoreMapDrivenArrayBased = class extends OIMIndexStoreArrayBased {
500
644
  // src/abstract/OIMIndexArrayBased.ts
501
645
  var OIMIndexArrayBased = class extends OIMIndex {
502
646
  constructor(options = {}) {
503
- super(
504
- options.store ?? new OIMIndexStoreMapDrivenArrayBased(),
505
- options.comparePks
506
- );
647
+ const store = options.store ?? new OIMIndexStoreMapDrivenArrayBased();
648
+ super(store, options.comparePks);
649
+ this.arrayStore = store;
507
650
  }
508
651
  getBucketSize(bucket) {
509
652
  return bucket.length;
@@ -521,10 +664,15 @@ var OIMIndexArrayBased = class extends OIMIndex {
521
664
  return result;
522
665
  }
523
666
  getSlotsByKey(key) {
524
- return this.store.getOneByKey(key) ?? [];
667
+ const bucket = this.store.getOneByKey(key);
668
+ return bucket ? bucket.slice() : [];
525
669
  }
526
670
  getSlotsByKeys(keys) {
527
- return this.store.getManyByKeys(keys);
671
+ const result = /* @__PURE__ */ new Map();
672
+ for (const [key, bucket] of this.store.getManyByKeys(keys)) {
673
+ result.set(key, bucket.slice());
674
+ }
675
+ return result;
528
676
  }
529
677
  slotsToPks(slots) {
530
678
  const pks = [];
@@ -532,22 +680,18 @@ var OIMIndexArrayBased = class extends OIMIndex {
532
680
  for (let i = 0; i < slots.length; i++) pks[i] = slots[i].pk;
533
681
  return pks;
534
682
  }
535
- setSlotsWithComparison(key, newSlots) {
536
- if (this.comparePks) {
537
- const existingSlotsArray = this.store.getOneByKey(key);
538
- if (existingSlotsArray && existingSlotsArray.length === newSlots.length) {
539
- if (this.comparePks(
540
- this.slotsToPks(existingSlotsArray),
541
- this.slotsToPks(newSlots)
542
- )) {
543
- return false;
544
- }
545
- } else if (!existingSlotsArray && newSlots.length === 0) {
546
- return false;
547
- }
548
- }
549
- this.store.setOneByKey(key, newSlots);
550
- return true;
683
+ /**
684
+ * True if a comparator is set and considers `bucket`'s pks equal to
685
+ * `newSlots` — i.e. a `setSlots` would be a no-op and should not emit.
686
+ */
687
+ bucketMatchesComparator(bucket, newSlots) {
688
+ if (!this.comparePks) return false;
689
+ if (bucket.length !== newSlots.length) return false;
690
+ if (bucket.length === 0) return true;
691
+ return this.comparePks(
692
+ this.slotsToPks(bucket),
693
+ this.slotsToPks(newSlots)
694
+ );
551
695
  }
552
696
  };
553
697
 
@@ -636,11 +780,19 @@ var OIMCarrierKeyedEmitter = class {
636
780
  this.scheduleFlush();
637
781
  }
638
782
  markUpdatedKey(key) {
783
+ if (this.subscribedCarriers.size === 0) {
784
+ this.assertNotInFlush();
785
+ return;
786
+ }
639
787
  const carrier = this.resolver.findCarrier(key);
640
788
  if (carrier) this.markUpdatedCarrier(carrier);
641
789
  else this.assertNotInFlush();
642
790
  }
643
791
  markUpdatedKeys(keys) {
792
+ if (this.subscribedCarriers.size === 0) {
793
+ this.assertNotInFlush();
794
+ return;
795
+ }
644
796
  for (let i = 0; i < keys.length; i++) this.markUpdatedKey(keys[i]);
645
797
  }
646
798
  markAllUpdated() {
@@ -740,8 +892,8 @@ var OIMKeyedCarrierResolver = class {
740
892
 
741
893
  // src/abstract/OIMReactiveIndex.ts
742
894
  var OIMReactiveIndex = class {
743
- constructor(queue, createIndex) {
744
- this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierResolver());
895
+ constructor(queue, createIndex, createResolver = () => new OIMKeyedCarrierResolver()) {
896
+ this.updateEmitter = new OIMCarrierKeyedEmitter(queue, createResolver());
745
897
  this.index = createIndex(this.updateEmitter);
746
898
  }
747
899
  getEntitiesByKey(key) {
@@ -797,8 +949,8 @@ var OIMReactiveIndex = class {
797
949
 
798
950
  // src/abstract/OIMReactiveIndexSetBased.ts
799
951
  var OIMReactiveIndexSetBased = class extends OIMReactiveIndex {
800
- constructor(queue, createIndex) {
801
- super(queue, createIndex);
952
+ constructor(queue, createIndex, createResolver) {
953
+ super(queue, createIndex, createResolver);
802
954
  }
803
955
  getPksByKey(key) {
804
956
  return this.index.getPksByKey(key);
@@ -816,8 +968,8 @@ var OIMReactiveIndexSetBased = class extends OIMReactiveIndex {
816
968
 
817
969
  // src/abstract/OIMReactiveIndexArrayBased.ts
818
970
  var OIMReactiveIndexArrayBased = class extends OIMReactiveIndex {
819
- constructor(queue, createIndex) {
820
- super(queue, createIndex);
971
+ constructor(queue, createIndex, createResolver) {
972
+ super(queue, createIndex, createResolver);
821
973
  }
822
974
  getPksByKey(key) {
823
975
  return this.index.getPksByKey(key);
@@ -1092,10 +1244,24 @@ var OIMPkSelectorFactory = class {
1092
1244
  }
1093
1245
  };
1094
1246
 
1247
+ // src/core/OIMKeyDomainNative.ts
1248
+ var OIMKeyDomainNative = class {
1249
+ createMap() {
1250
+ return /* @__PURE__ */ new Map();
1251
+ }
1252
+ createSet() {
1253
+ return /* @__PURE__ */ new Set();
1254
+ }
1255
+ canonicalize(key) {
1256
+ return key;
1257
+ }
1258
+ };
1259
+
1095
1260
  // src/core/OIMCollectionStoreMapDriven.ts
1096
1261
  var OIMCollectionStoreMapDriven = class extends OIMCollectionStore {
1097
1262
  constructor() {
1098
1263
  super(...arguments);
1264
+ this.keyDomain = new OIMKeyDomainNative();
1099
1265
  this.slots = /* @__PURE__ */ new Map();
1100
1266
  // Slots reserved by indexes for PKs whose entity has not arrived yet.
1101
1267
  // They carry `item: undefined` and live apart from `slots` so they do not
@@ -1268,6 +1434,10 @@ var OIMCollection = class {
1268
1434
  this.store = opts?.store ?? new OIMCollectionStoreMapDriven();
1269
1435
  this.updateEntity = opts?.updateEntity ?? new OIMEntityUpdaterFactory().createMergeEntityUpdater();
1270
1436
  }
1437
+ /** The collection's PK keying strategy — read by indexes and wrappers. */
1438
+ get keyDomain() {
1439
+ return this.store.keyDomain;
1440
+ }
1271
1441
  getOneByPk(pk) {
1272
1442
  return this.store.getOneByPk(pk);
1273
1443
  }
@@ -1348,15 +1518,12 @@ var OIMCollection = class {
1348
1518
  `[OIMCollection]: PK is required to upsert an entity ${JSON.stringify(entity)}`
1349
1519
  );
1350
1520
  }
1351
- const existingEntity = this.store.getOneByPk(pk);
1352
- if (existingEntity) {
1353
- return this.store.setOneByPk(
1354
- pk,
1355
- this.updateEntity(entity, existingEntity)
1356
- );
1357
- } else {
1358
- return this.store.setOneByPk(pk, entity);
1521
+ const slot = this.store.getSlotByPk(pk);
1522
+ if (slot !== void 0 && slot.item !== void 0) {
1523
+ slot.item = this.updateEntity(entity, slot.item);
1524
+ return slot;
1359
1525
  }
1526
+ return this.store.setOneByPk(pk, entity);
1360
1527
  }
1361
1528
  destroy() {
1362
1529
  this.store.destroy();
@@ -1369,13 +1536,13 @@ var OIMReactiveCollection = class extends OIMCollection {
1369
1536
  constructor(queue, opts) {
1370
1537
  super(opts);
1371
1538
  this.anyUpdateHandlers = /* @__PURE__ */ new Set();
1372
- this.pendingAnyUpdatePks = /* @__PURE__ */ new Set();
1539
+ this.pendingAnyUpdatePks = this.store.keyDomain.createSet();
1373
1540
  this.isAnyUpdateClearPending = false;
1374
1541
  this.isAnyUpdateScheduled = false;
1375
1542
  this.runAnyUpdate = () => {
1376
1543
  if (!this.isAnyUpdateScheduled) return;
1377
1544
  this.isAnyUpdateScheduled = false;
1378
- const pks = this.isAnyUpdateClearPending ? [] : Array.from(this.pendingAnyUpdatePks);
1545
+ const pks = this.isAnyUpdateClearPending ? [] : Array.from(this.pendingAnyUpdatePks.values());
1379
1546
  this.pendingAnyUpdatePks.clear();
1380
1547
  this.isAnyUpdateClearPending = false;
1381
1548
  const snapshot = Array.from(this.anyUpdateHandlers);
@@ -1534,38 +1701,40 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1534
1701
  if (key === void 0) {
1535
1702
  const keys = this.getKeys();
1536
1703
  if (keys.length > 0) {
1537
- this.store.clear();
1704
+ this.arrayStore.clear();
1538
1705
  this.emitUpdate(Array.from(keys));
1539
1706
  }
1540
1707
  return;
1541
1708
  }
1542
- if (this.store.getOneByKey(key) !== void 0) {
1543
- this.store.removeOneByKey(key);
1709
+ const bucket = this.arrayStore.findBucket(key);
1710
+ if (bucket && bucket.length > 0) {
1711
+ bucket.length = 0;
1712
+ this.arrayStore.releaseBucket(bucket);
1544
1713
  this.emitUpdate([key]);
1545
1714
  }
1546
1715
  }
1547
1716
  pushSlot(key, slot) {
1548
- const list = this.getOrCreateList(key);
1549
- const index = list.length;
1550
- list.push(slot);
1717
+ const bucket = this.arrayStore.getOrReserveBucket(key);
1718
+ const index = bucket.length;
1719
+ bucket.push(slot);
1720
+ this.arrayStore.retainBucket(bucket);
1551
1721
  this.emitUpdate([key]);
1552
1722
  return index;
1553
1723
  }
1554
1724
  insertSlotAt(key, index, slot) {
1555
- const list = this.getOrCreateList(key);
1556
- const safeIndex = Math.max(0, Math.min(index, list.length));
1557
- list.splice(safeIndex, 0, slot);
1725
+ const bucket = this.arrayStore.getOrReserveBucket(key);
1726
+ const safeIndex = Math.max(0, Math.min(index, bucket.length));
1727
+ bucket.splice(safeIndex, 0, slot);
1728
+ this.arrayStore.retainBucket(bucket);
1558
1729
  this.emitUpdate([key]);
1559
1730
  return safeIndex;
1560
1731
  }
1561
1732
  removeAt(key, index) {
1562
- const list = this.store.getOneByKey(key);
1563
- if (!list) return void 0;
1564
- if (index < 0 || index >= list.length) return void 0;
1565
- const [removed] = list.splice(index, 1);
1566
- if (list.length === 0) {
1567
- this.store.removeOneByKey(key);
1568
- }
1733
+ const bucket = this.arrayStore.findBucket(key);
1734
+ if (!bucket) return void 0;
1735
+ if (index < 0 || index >= bucket.length) return void 0;
1736
+ const [removed] = bucket.splice(index, 1);
1737
+ if (bucket.length === 0) this.arrayStore.releaseBucket(bucket);
1569
1738
  this.emitUpdate([key]);
1570
1739
  return removed;
1571
1740
  }
@@ -1574,35 +1743,33 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1574
1743
  * Returns the number actually removed (clamped to bounds; 0 if nothing).
1575
1744
  */
1576
1745
  removeRange(key, index, count) {
1577
- const list = this.store.getOneByKey(key);
1578
- if (!list) return 0;
1579
- if (index < 0 || index >= list.length || count <= 0) return 0;
1580
- const actual = Math.min(count, list.length - index);
1581
- list.splice(index, actual);
1582
- if (list.length === 0) {
1583
- this.store.removeOneByKey(key);
1584
- }
1746
+ const bucket = this.arrayStore.findBucket(key);
1747
+ if (!bucket) return 0;
1748
+ if (index < 0 || index >= bucket.length || count <= 0) return 0;
1749
+ const actual = Math.min(count, bucket.length - index);
1750
+ bucket.splice(index, actual);
1751
+ if (bucket.length === 0) this.arrayStore.releaseBucket(bucket);
1585
1752
  this.emitUpdate([key]);
1586
1753
  return actual;
1587
1754
  }
1588
1755
  move(key, fromIndex, toIndex) {
1589
- const list = this.store.getOneByKey(key);
1590
- if (!list) return void 0;
1591
- if (fromIndex < 0 || fromIndex >= list.length) return void 0;
1592
- if (toIndex < 0 || toIndex >= list.length) return void 0;
1593
- if (fromIndex === toIndex) return list[fromIndex];
1594
- const [slot] = list.splice(fromIndex, 1);
1756
+ const bucket = this.arrayStore.findBucket(key);
1757
+ if (!bucket) return void 0;
1758
+ if (fromIndex < 0 || fromIndex >= bucket.length) return void 0;
1759
+ if (toIndex < 0 || toIndex >= bucket.length) return void 0;
1760
+ if (fromIndex === toIndex) return bucket[fromIndex];
1761
+ const [slot] = bucket.splice(fromIndex, 1);
1595
1762
  if (slot === void 0) return void 0;
1596
- list.splice(toIndex, 0, slot);
1763
+ bucket.splice(toIndex, 0, slot);
1597
1764
  this.emitUpdate([key]);
1598
1765
  return slot;
1599
1766
  }
1600
1767
  /** Replace the slot at `index` in place. Returns the safe index, or -1. */
1601
1768
  setSlotAt(key, index, slot) {
1602
- const list = this.store.getOneByKey(key);
1603
- if (!list) return -1;
1604
- if (index < 0 || index >= list.length) return -1;
1605
- list[index] = slot;
1769
+ const bucket = this.arrayStore.findBucket(key);
1770
+ if (!bucket) return -1;
1771
+ if (index < 0 || index >= bucket.length) return -1;
1772
+ bucket[index] = slot;
1606
1773
  this.emitUpdate([key]);
1607
1774
  return index;
1608
1775
  }
@@ -1612,36 +1779,33 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1612
1779
  * Returns the number actually moved (0 if a no-op / out of bounds).
1613
1780
  */
1614
1781
  moveRange(key, from, to, count) {
1615
- const list = this.store.getOneByKey(key);
1616
- if (!list) return 0;
1617
- if (from < 0 || from >= list.length || count <= 0) return 0;
1618
- const actual = Math.min(count, list.length - from);
1619
- const insertAt = Math.max(0, Math.min(to, list.length - actual));
1782
+ const bucket = this.arrayStore.findBucket(key);
1783
+ if (!bucket) return 0;
1784
+ if (from < 0 || from >= bucket.length || count <= 0) return 0;
1785
+ const actual = Math.min(count, bucket.length - from);
1786
+ const insertAt = Math.max(0, Math.min(to, bucket.length - actual));
1620
1787
  if (insertAt === from) return 0;
1621
- const block = list.splice(from, actual);
1622
- list.splice(insertAt, 0, ...block);
1788
+ const block = bucket.splice(from, actual);
1789
+ bucket.splice(insertAt, 0, ...block);
1623
1790
  this.emitUpdate([key]);
1624
1791
  return actual;
1625
1792
  }
1626
1793
  resetSlots(key, slots) {
1627
1794
  if (slots.length === 0) {
1628
- if (this.store.getOneByKey(key) !== void 0) {
1629
- this.store.removeOneByKey(key);
1795
+ const bucket2 = this.arrayStore.findBucket(key);
1796
+ if (bucket2 && bucket2.length > 0) {
1797
+ bucket2.length = 0;
1798
+ this.arrayStore.releaseBucket(bucket2);
1630
1799
  this.emitUpdate([key]);
1631
1800
  }
1632
1801
  return;
1633
1802
  }
1634
- this.store.setOneByKey(key, slots.slice());
1803
+ const bucket = this.arrayStore.getOrReserveBucket(key);
1804
+ bucket.length = 0;
1805
+ for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
1806
+ this.arrayStore.retainBucket(bucket);
1635
1807
  this.emitUpdate([key]);
1636
1808
  }
1637
- getOrCreateList(key) {
1638
- let list = this.store.getOneByKey(key);
1639
- if (!list) {
1640
- list = [];
1641
- this.store.setOneByKey(key, list);
1642
- }
1643
- return list;
1644
- }
1645
1809
  };
1646
1810
 
1647
1811
  // src/core/OIMCollectionIndexManualOrderedArrayBased.ts
@@ -2159,43 +2323,68 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
2159
2323
  super(options);
2160
2324
  }
2161
2325
  setSlots(key, slots) {
2162
- const hasChanges = this.setSlotsWithComparison(key, slots);
2163
- if (hasChanges) {
2164
- this.emitUpdateOne(key);
2165
- }
2326
+ const bucket = this.arrayStore.getOrReserveBucket(key);
2327
+ if (this.bucketMatchesComparator(bucket, slots)) return;
2328
+ bucket.length = 0;
2329
+ for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
2330
+ if (bucket.length > 0) this.arrayStore.retainBucket(bucket);
2331
+ else this.arrayStore.releaseBucket(bucket);
2332
+ this.emitBucketChanged(bucket);
2166
2333
  }
2167
2334
  /**
2168
- * Appends pre-deduplicated slots to a key's bucket IN PLACE (no copy of the
2169
- * existing bucket) and emits once. Callers own dedup (so this stays O(added),
2170
- * not O(bucket)); used by collection-bound indexes for fast `addPks`.
2335
+ * Appends pre-deduplicated slots to a key's bucket IN PLACE and emits once.
2336
+ * O(added); callers own dedup.
2171
2337
  */
2172
2338
  appendSlots(key, slots) {
2173
2339
  if (slots.length === 0) return;
2174
- let bucket = this.store.getOneByKey(key);
2175
- if (!bucket) {
2176
- bucket = [];
2177
- this.store.setOneByKey(key, bucket);
2178
- }
2340
+ const bucket = this.arrayStore.getOrReserveBucket(key);
2179
2341
  for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
2180
- this.emitUpdateOne(key);
2342
+ this.arrayStore.retainBucket(bucket);
2343
+ this.emitBucketChanged(bucket);
2181
2344
  }
2182
2345
  /**
2183
- * Clear all primary keys for a specific index key, or all keys if no key specified
2346
+ * Clear all primary keys for a specific index key, or all keys if none given.
2184
2347
  */
2185
2348
  clear(key) {
2186
2349
  if (key === void 0) {
2187
- const allKeys = this.store.getAllKeys();
2350
+ const allKeys = this.arrayStore.getAllKeys();
2188
2351
  if (allKeys.length > 0) {
2189
- this.store.clear();
2352
+ this.arrayStore.clear();
2190
2353
  this.emitUpdate(allKeys);
2191
2354
  }
2192
2355
  } else {
2193
- if (this.store.getOneByKey(key) !== void 0) {
2194
- this.store.removeOneByKey(key);
2195
- this.emitUpdateOne(key);
2356
+ const bucket = this.arrayStore.findBucket(key);
2357
+ if (bucket && bucket.length > 0) {
2358
+ bucket.length = 0;
2359
+ this.arrayStore.releaseBucket(bucket);
2360
+ this.emitBucketChanged(bucket);
2196
2361
  }
2197
2362
  }
2198
2363
  }
2364
+ /**
2365
+ * Emit that a bucket changed. Base (non-reactive) path emits by key on the
2366
+ * plain event emitter; the reactive subclass overrides this to mark the
2367
+ * bucket carrier directly (O(1), no key→carrier lookup).
2368
+ */
2369
+ emitBucketChanged(bucket) {
2370
+ this.emitUpdateOne(bucket.key);
2371
+ }
2372
+ };
2373
+
2374
+ // src/core/OIMBucketCarrierResolver.ts
2375
+ var OIMBucketCarrierResolver = class {
2376
+ constructor(store) {
2377
+ this.store = store;
2378
+ }
2379
+ getOrReserveCarrier(key) {
2380
+ return this.store.getOrReserveBucket(key);
2381
+ }
2382
+ findCarrier(key) {
2383
+ return this.store.findBucket(key);
2384
+ }
2385
+ onCarrierEmptied(carrier) {
2386
+ this.store.dropIfReserved(carrier.key);
2387
+ }
2199
2388
  };
2200
2389
 
2201
2390
  // src/core/OIMReactiveIndexManualArrayBased.ts
@@ -2204,6 +2393,10 @@ var OIMIndexManualArrayBasedReactive = class extends OIMIndexManualArrayBased {
2204
2393
  super(opts);
2205
2394
  this.updateEmitter = updateEmitter;
2206
2395
  }
2396
+ // Deliver straight off the bucket the write just touched — O(1).
2397
+ emitBucketChanged(bucket) {
2398
+ this.updateEmitter.markUpdatedCarrier(bucket);
2399
+ }
2207
2400
  emitUpdate(keys) {
2208
2401
  this.updateEmitter.markUpdatedKeys(keys);
2209
2402
  }
@@ -2213,12 +2406,15 @@ var OIMIndexManualArrayBasedReactive = class extends OIMIndexManualArrayBased {
2213
2406
  };
2214
2407
  var OIMReactiveIndexManualArrayBased = class extends OIMReactiveIndexArrayBased {
2215
2408
  constructor(queue, opts) {
2216
- super(queue, (updateEmitter) => {
2217
- return new OIMIndexManualArrayBasedReactive(
2218
- updateEmitter,
2219
- opts?.indexOptions
2220
- );
2221
- });
2409
+ const store = opts?.indexOptions?.store ?? new OIMIndexStoreMapDrivenArrayBased();
2410
+ super(
2411
+ queue,
2412
+ (updateEmitter) => new OIMIndexManualArrayBasedReactive(updateEmitter, {
2413
+ comparePks: opts?.indexOptions?.comparePks,
2414
+ store
2415
+ }),
2416
+ () => new OIMBucketCarrierResolver(store)
2417
+ );
2222
2418
  }
2223
2419
  setSlots(key, slots) {
2224
2420
  this.index.setSlots(key, slots);
@@ -2235,6 +2431,7 @@ var OIMReactiveIndexManualArrayBased = class extends OIMReactiveIndexArrayBased
2235
2431
  var OIMReactiveCollectionIndexManualArrayBased = class extends OIMReactiveIndexManualArrayBased {
2236
2432
  constructor(queue, opts) {
2237
2433
  const resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.resolveSlot;
2434
+ const pkDomain = opts.collection?.keyDomain ?? new OIMKeyDomainNative();
2238
2435
  super(queue, {
2239
2436
  indexOptions: opts.indexOptions
2240
2437
  });
@@ -2242,9 +2439,10 @@ var OIMReactiveCollectionIndexManualArrayBased = class extends OIMReactiveIndexM
2242
2439
  // a Set from the whole bucket on every call). Kept in sync by setSlots/clear.
2243
2440
  this.pksByKey = /* @__PURE__ */ new Map();
2244
2441
  this.resolveSlot = resolveSlot;
2442
+ this.pkDomain = pkDomain;
2245
2443
  }
2246
2444
  setPks(key, pks) {
2247
- const pkSet = /* @__PURE__ */ new Set();
2445
+ const pkSet = this.pkDomain.createSet();
2248
2446
  for (let i = 0; i < pks.length; i++) pkSet.add(pks[i]);
2249
2447
  this.pksByKey.set(key, pkSet);
2250
2448
  super.setSlots(key, this.resolveSlots(pks));
@@ -2287,7 +2485,7 @@ var OIMReactiveCollectionIndexManualArrayBased = class extends OIMReactiveIndexM
2287
2485
  membershipOf(key) {
2288
2486
  let pkSet = this.pksByKey.get(key);
2289
2487
  if (!pkSet) {
2290
- pkSet = /* @__PURE__ */ new Set();
2488
+ pkSet = this.pkDomain.createSet();
2291
2489
  const existing = this.index.getSlotsByKey(key);
2292
2490
  for (let i = 0; i < existing.length; i++) {
2293
2491
  pkSet.add(existing[i].pk);
@@ -2321,24 +2519,98 @@ var OIMReactiveCollectionIndexManualArrayBased = class extends OIMReactiveIndexM
2321
2519
  var OIMIndexManualSetBased = class extends OIMIndexSetBased {
2322
2520
  constructor(options = {}) {
2323
2521
  super(options);
2522
+ this.pkDomain = options.pkDomain ?? new OIMKeyDomainNative();
2324
2523
  }
2325
2524
  setSlots(key, slots) {
2326
- const hasChanges = this.setSlotsWithComparison(key, new Set(slots));
2327
- if (hasChanges) {
2328
- this.emitUpdateOne(key);
2525
+ const bucket = this.setStore.getOrReserveBucket(key);
2526
+ this.replaceBucketContents(bucket, slots);
2527
+ }
2528
+ // --- pk-oriented writes: resolve the bucket ONCE and use its own membership
2529
+ // (pk → slot) for dedup/removal, so a composite (trie) key is walked a single
2530
+ // time instead of once for the bucket and once for a separate membership map.
2531
+ setPks(key, pks, resolveSlot) {
2532
+ const bucket = this.setStore.getOrReserveBucket(key);
2533
+ const slots = [];
2534
+ for (let i = 0; i < pks.length; i++) slots.push(resolveSlot(pks[i]));
2535
+ this.replaceBucketContents(bucket, slots);
2536
+ const membership = bucket.membership ?? (bucket.membership = this.pkDomain.createMap());
2537
+ membership.clear();
2538
+ for (let i = 0; i < pks.length; i++) {
2539
+ if (!membership.has(pks[i])) membership.set(pks[i], slots[i]);
2540
+ }
2541
+ }
2542
+ addPks(key, pks, resolveSlot) {
2543
+ if (pks.length === 0) return;
2544
+ const bucket = this.setStore.getOrReserveBucket(key);
2545
+ const membership = this.membershipOf(bucket);
2546
+ let changed = false;
2547
+ for (let i = 0; i < pks.length; i++) {
2548
+ const pk = pks[i];
2549
+ if (membership.has(pk)) continue;
2550
+ const slot = resolveSlot(pk);
2551
+ membership.set(pk, slot);
2552
+ if (!bucket.has(slot)) {
2553
+ bucket.add(slot);
2554
+ changed = true;
2555
+ }
2556
+ }
2557
+ if (!changed) return;
2558
+ this.setStore.retainBucket(bucket);
2559
+ this.emitBucketChanged(bucket);
2560
+ }
2561
+ removePks(key, pks) {
2562
+ if (pks.length === 0) return;
2563
+ const bucket = this.setStore.findBucket(key);
2564
+ if (!bucket || bucket.size === 0) return;
2565
+ const membership = this.membershipOf(bucket);
2566
+ let changed = false;
2567
+ for (let i = 0; i < pks.length; i++) {
2568
+ const slot = membership.get(pks[i]);
2569
+ if (slot) {
2570
+ membership.delete(pks[i]);
2571
+ if (bucket.delete(slot)) changed = true;
2572
+ }
2573
+ }
2574
+ if (!changed) return;
2575
+ if (bucket.size === 0) this.setStore.releaseBucket(bucket);
2576
+ this.emitBucketChanged(bucket);
2577
+ }
2578
+ /**
2579
+ * The bucket's pk → slot membership, lazily created and seeded from the
2580
+ * bucket's current slots (so it stays correct even if slots were written
2581
+ * through the lower-level `setSlots` path).
2582
+ */
2583
+ membershipOf(bucket) {
2584
+ let membership = bucket.membership;
2585
+ if (!membership) {
2586
+ membership = this.pkDomain.createMap();
2587
+ bucket.membership = membership;
2588
+ }
2589
+ if (membership.size === 0 && bucket.size > 0) {
2590
+ for (const slot of bucket) membership.set(slot.pk, slot);
2591
+ }
2592
+ return membership;
2593
+ }
2594
+ replaceBucketContents(bucket, slots) {
2595
+ if (this.comparePks) {
2596
+ const newSet = slots instanceof Set ? slots : new Set(slots);
2597
+ if (this.bucketMatchesComparator(bucket, newSet)) return;
2598
+ bucket.clear();
2599
+ for (const slot of newSet) bucket.add(slot);
2600
+ } else {
2601
+ bucket.clear();
2602
+ for (const slot of slots) bucket.add(slot);
2329
2603
  }
2604
+ if (bucket.size > 0) this.setStore.retainBucket(bucket);
2605
+ else this.setStore.releaseBucket(bucket);
2606
+ this.emitBucketChanged(bucket);
2330
2607
  }
2331
2608
  /**
2332
- * Adds slots to a key's bucket IN PLACE (no copy of the existing Set) and
2333
- * emits once. O(added). Used by collection-bound indexes for fast `addPks`.
2609
+ * Adds slots to a key's bucket IN PLACE and emits once. O(added).
2334
2610
  */
2335
2611
  addSlots(key, slots) {
2336
2612
  if (slots.length === 0) return;
2337
- let bucket = this.store.getOneByKey(key);
2338
- if (!bucket) {
2339
- bucket = /* @__PURE__ */ new Set();
2340
- this.store.setOneByKey(key, bucket);
2341
- }
2613
+ const bucket = this.setStore.getOrReserveBucket(key);
2342
2614
  let changed = false;
2343
2615
  for (let i = 0; i < slots.length; i++) {
2344
2616
  if (!bucket.has(slots[i])) {
@@ -2346,25 +2618,27 @@ var OIMIndexManualSetBased = class extends OIMIndexSetBased {
2346
2618
  changed = true;
2347
2619
  }
2348
2620
  }
2349
- if (changed) this.emitUpdateOne(key);
2621
+ if (!changed) return;
2622
+ this.setStore.retainBucket(bucket);
2623
+ this.emitBucketChanged(bucket);
2350
2624
  }
2351
2625
  /**
2352
2626
  * Removes slots from a key's bucket IN PLACE (O(removed)) and emits once.
2353
2627
  */
2354
2628
  removeSlots(key, slots) {
2355
2629
  if (slots.length === 0) return;
2356
- const bucket = this.store.getOneByKey(key);
2630
+ const bucket = this.setStore.findBucket(key);
2357
2631
  if (!bucket) return;
2358
2632
  let changed = false;
2359
2633
  for (let i = 0; i < slots.length; i++) {
2360
2634
  if (bucket.delete(slots[i])) changed = true;
2361
2635
  }
2362
2636
  if (!changed) return;
2363
- if (bucket.size === 0) this.store.removeOneByKey(key);
2364
- this.emitUpdateOne(key);
2637
+ if (bucket.size === 0) this.setStore.releaseBucket(bucket);
2638
+ this.emitBucketChanged(bucket);
2365
2639
  }
2366
2640
  /**
2367
- * Clear all primary keys for a specific index key, or all keys if no key specified
2641
+ * Clear all primary keys for a specific index key, or all keys if none given.
2368
2642
  */
2369
2643
  clear(key) {
2370
2644
  if (key === void 0) {
@@ -2374,12 +2648,22 @@ var OIMIndexManualSetBased = class extends OIMIndexSetBased {
2374
2648
  this.emitUpdate(allKeys);
2375
2649
  }
2376
2650
  } else {
2377
- if (this.store.getOneByKey(key) !== void 0) {
2378
- this.store.removeOneByKey(key);
2379
- this.emitUpdateOne(key);
2651
+ const bucket = this.setStore.findBucket(key);
2652
+ if (bucket && bucket.size > 0) {
2653
+ bucket.clear();
2654
+ this.setStore.releaseBucket(bucket);
2655
+ this.emitBucketChanged(bucket);
2380
2656
  }
2381
2657
  }
2382
2658
  }
2659
+ /**
2660
+ * Emit that a bucket changed. Base (non-reactive) path emits by key on the
2661
+ * plain event emitter; the reactive subclass overrides this to mark the
2662
+ * bucket carrier directly (O(1), no key→carrier lookup).
2663
+ */
2664
+ emitBucketChanged(bucket) {
2665
+ this.emitUpdateOne(bucket.key);
2666
+ }
2383
2667
  };
2384
2668
 
2385
2669
  // src/core/OIMReactiveIndexManualSetBased.ts
@@ -2388,6 +2672,12 @@ var OIMIndexManualSetBasedReactive = class extends OIMIndexManualSetBased {
2388
2672
  super(opts);
2389
2673
  this.updateEmitter = updateEmitter;
2390
2674
  }
2675
+ // Deliver straight off the bucket the write just touched — O(1), no
2676
+ // key→carrier lookup (the whole point of bucket-as-carrier).
2677
+ emitBucketChanged(bucket) {
2678
+ this.updateEmitter.markUpdatedCarrier(bucket);
2679
+ }
2680
+ // clear(all) still emits by key set (rare path).
2391
2681
  emitUpdate(keys) {
2392
2682
  this.updateEmitter.markUpdatedKeys(keys);
2393
2683
  }
@@ -2397,12 +2687,16 @@ var OIMIndexManualSetBasedReactive = class extends OIMIndexManualSetBased {
2397
2687
  };
2398
2688
  var OIMReactiveIndexManualSetBased = class extends OIMReactiveIndexSetBased {
2399
2689
  constructor(queue, opts) {
2400
- super(queue, (updateEmitter) => {
2401
- return new OIMIndexManualSetBasedReactive(
2402
- updateEmitter,
2403
- opts?.indexOptions
2404
- );
2405
- });
2690
+ const store = opts?.indexOptions?.store ?? new OIMIndexStoreMapDrivenSetBased();
2691
+ super(
2692
+ queue,
2693
+ (updateEmitter) => new OIMIndexManualSetBasedReactive(updateEmitter, {
2694
+ comparePks: opts?.indexOptions?.comparePks,
2695
+ store,
2696
+ pkDomain: opts?.pkDomain
2697
+ }),
2698
+ () => new OIMBucketCarrierResolver(store)
2699
+ );
2406
2700
  }
2407
2701
  setSlots(key, slots) {
2408
2702
  this.index.setSlots(key, slots);
@@ -2423,71 +2717,26 @@ var OIMReactiveCollectionIndexManualSetBased = class extends OIMReactiveIndexMan
2423
2717
  constructor(queue, opts) {
2424
2718
  const resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.resolveSlot;
2425
2719
  super(queue, {
2426
- indexOptions: opts.indexOptions
2720
+ indexOptions: opts.indexOptions,
2721
+ pkDomain: opts.collection?.keyDomain
2427
2722
  });
2428
- // Persistent per-key pk → slot map for O(1) add (dedup) and O(1) remove
2429
- // (slot lookup by pk). Kept in sync by setPks/addPks/removePks/clear.
2430
- this.slotByPk = /* @__PURE__ */ new Map();
2723
+ // Bound once so the hot pk-write path passes a stable function (no per-call
2724
+ // closure allocation).
2725
+ this.resolveRequiredSlot = (pk) => {
2726
+ const slot = this.resolveSlot(pk);
2727
+ if (!slot) return { pk, item: void 0 };
2728
+ return slot;
2729
+ };
2431
2730
  this.resolveSlot = resolveSlot;
2432
2731
  }
2433
2732
  setPks(key, pks) {
2434
- const map = /* @__PURE__ */ new Map();
2435
- const slots = [];
2436
- for (let i = 0; i < pks.length; i++) {
2437
- const pk = pks[i];
2438
- const slot = this.resolveRequiredSlot(pk);
2439
- if (!map.has(pk)) map.set(pk, slot);
2440
- slots.push(slot);
2441
- }
2442
- this.slotByPk.set(key, map);
2443
- super.setSlots(key, slots);
2733
+ this.index.setPks(key, pks, this.resolveRequiredSlot);
2444
2734
  }
2445
2735
  addPks(key, pks) {
2446
- if (pks.length === 0) return;
2447
- const map = this.membershipOf(key);
2448
- const newSlots = [];
2449
- for (const pk of pks) {
2450
- if (map.has(pk)) continue;
2451
- const slot = this.resolveRequiredSlot(pk);
2452
- map.set(pk, slot);
2453
- newSlots.push(slot);
2454
- }
2455
- if (newSlots.length > 0) this.addSlots(key, newSlots);
2736
+ this.index.addPks(key, pks, this.resolveRequiredSlot);
2456
2737
  }
2457
2738
  removePks(key, pks) {
2458
- if (pks.length === 0) return;
2459
- const map = this.membershipOf(key);
2460
- if (map.size === 0) return;
2461
- const removed = [];
2462
- for (const pk of pks) {
2463
- const slot = map.get(pk);
2464
- if (slot) {
2465
- map.delete(pk);
2466
- removed.push(slot);
2467
- }
2468
- }
2469
- if (removed.length === 0) return;
2470
- if (map.size === 0) this.slotByPk.delete(key);
2471
- this.removeSlots(key, removed);
2472
- }
2473
- clear(key) {
2474
- if (key === void 0) this.slotByPk.clear();
2475
- else this.slotByPk.delete(key);
2476
- super.clear(key);
2477
- }
2478
- /**
2479
- * The pk→slot map for a key, lazily seeded from the current bucket so it
2480
- * stays correct even if slots were set through a lower-level path.
2481
- */
2482
- membershipOf(key) {
2483
- let map = this.slotByPk.get(key);
2484
- if (!map) {
2485
- map = /* @__PURE__ */ new Map();
2486
- const existing = this.index.getSlotsByKey(key);
2487
- for (const slot of existing) map.set(slot.pk, slot);
2488
- this.slotByPk.set(key, map);
2489
- }
2490
- return map;
2739
+ this.index.removePks(key, pks);
2491
2740
  }
2492
2741
  getEntitiesByKey(key) {
2493
2742
  return super.getEntitiesByKey(key);
@@ -2495,50 +2744,459 @@ var OIMReactiveCollectionIndexManualSetBased = class extends OIMReactiveIndexMan
2495
2744
  getEntitiesByKeys(keys) {
2496
2745
  return super.getEntitiesByKeys(keys);
2497
2746
  }
2498
- resolveRequiredSlot(pk) {
2499
- const slot = this.resolveSlot(pk);
2500
- if (!slot) return { pk, item: void 0 };
2501
- return slot;
2502
- }
2503
2747
  };
2504
2748
 
2505
- // src/core/OIMDerivedCollectionIndexSetBased.ts
2506
- var OIMDerivedCollectionIndexSetBased = class extends OIMReactiveCollectionIndexManualSetBased {
2507
- constructor(queue, collection, opts) {
2508
- super(queue, {
2509
- collection,
2510
- indexOptions: opts.indexOptions
2511
- });
2512
- this.keysByPk = /* @__PURE__ */ new Map();
2513
- this.onCollectionUpdate = (pks) => {
2514
- if (pks.length === 0) {
2515
- this.rebuildFromCollection();
2516
- return;
2749
+ // src/core/OIMTrieMap.ts
2750
+ var OIMTrieMap = class {
2751
+ constructor() {
2752
+ this.root = { hasValue: false };
2753
+ this._size = 0;
2754
+ }
2755
+ /** Number of stored paths (terminal nodes), not the number of trie nodes. */
2756
+ get size() {
2757
+ return this._size;
2758
+ }
2759
+ set(path, value) {
2760
+ let node = this.root;
2761
+ for (let i = 0; i < path.length; i++) {
2762
+ let children = node.children;
2763
+ if (!children) {
2764
+ children = /* @__PURE__ */ new Map();
2765
+ node.children = children;
2517
2766
  }
2518
- for (const pk of pks) {
2519
- this.syncPk(pk);
2767
+ let next = children.get(path[i]);
2768
+ if (!next) {
2769
+ next = { hasValue: false };
2770
+ children.set(path[i], next);
2520
2771
  }
2521
- };
2522
- this.collection = collection;
2523
- this.selectIndexKeys = opts.selectIndexKeys;
2524
- this.unsubscribeFromCollection = collection.emitter.on(
2525
- 0 /* UPDATE */,
2526
- (payload) => this.onCollectionUpdate(payload.pks)
2527
- );
2528
- if (opts.buildInitial !== false) {
2529
- this.rebuildFromCollection();
2772
+ node = next;
2773
+ }
2774
+ if (!node.hasValue) {
2775
+ node.hasValue = true;
2776
+ this._size++;
2777
+ }
2778
+ node.value = value;
2779
+ }
2780
+ get(path) {
2781
+ const node = this.findNode(path);
2782
+ return node && node.hasValue ? node.value : void 0;
2783
+ }
2784
+ has(path) {
2785
+ const node = this.findNode(path);
2786
+ return !!node && node.hasValue;
2787
+ }
2788
+ delete(path) {
2789
+ const parents = [];
2790
+ let node = this.root;
2791
+ for (let i = 0; i < path.length; i++) {
2792
+ const next = node.children?.get(path[i]);
2793
+ if (!next) return false;
2794
+ parents.push(node);
2795
+ node = next;
2796
+ }
2797
+ if (!node.hasValue) return false;
2798
+ node.hasValue = false;
2799
+ node.value = void 0;
2800
+ this._size--;
2801
+ let child = node;
2802
+ for (let i = parents.length - 1; i >= 0; i--) {
2803
+ if (child.hasValue || child.children && child.children.size > 0) {
2804
+ break;
2805
+ }
2806
+ const parent = parents[i];
2807
+ parent.children.delete(path[i]);
2808
+ if (parent.children.size === 0) parent.children = void 0;
2809
+ child = parent;
2530
2810
  }
2811
+ return true;
2531
2812
  }
2532
- rebuildFromCollection() {
2533
- this.keysByPk.clear();
2534
- super.clear();
2535
- const slotsByKey = /* @__PURE__ */ new Map();
2536
- const slots = this.collection.getAllSlots();
2537
- for (const slot of slots) {
2538
- if (slot.item === void 0) continue;
2539
- const keys = this.normalizeKeys(this.selectIndexKeys(slot.item));
2540
- if (keys.length === 0) continue;
2541
- this.keysByPk.set(slot.pk, new Set(keys));
2813
+ clear() {
2814
+ this.root.children = void 0;
2815
+ this.root.value = void 0;
2816
+ this.root.hasValue = false;
2817
+ this._size = 0;
2818
+ }
2819
+ /** Depth-first iteration over `[path, value]` for every stored path. */
2820
+ *entries() {
2821
+ const stack = [{ node: this.root, path: [] }];
2822
+ while (stack.length > 0) {
2823
+ const frame = stack.pop();
2824
+ const { node, path } = frame;
2825
+ if (node.hasValue) {
2826
+ yield [path, node.value];
2827
+ }
2828
+ if (node.children) {
2829
+ for (const [segment, child] of node.children) {
2830
+ stack.push({ node: child, path: [...path, segment] });
2831
+ }
2832
+ }
2833
+ }
2834
+ }
2835
+ *keys() {
2836
+ for (const [path] of this.entries()) yield path;
2837
+ }
2838
+ *values() {
2839
+ for (const [, value] of this.entries()) yield value;
2840
+ }
2841
+ findNode(path) {
2842
+ let node = this.root;
2843
+ for (let i = 0; i < path.length; i++) {
2844
+ node = node.children?.get(path[i]);
2845
+ if (!node) return void 0;
2846
+ }
2847
+ return node;
2848
+ }
2849
+ };
2850
+
2851
+ // src/core/OIMIndexStoreTrieDrivenSetBased.ts
2852
+ var OIMIndexStoreTrieDrivenSetBased = class extends OIMIndexStoreSetBased {
2853
+ constructor() {
2854
+ super(...arguments);
2855
+ this.slots = new OIMTrieMap();
2856
+ this.reservedBuckets = new OIMTrieMap();
2857
+ }
2858
+ getOrReserveBucket(key) {
2859
+ const live = this.slots.get(key);
2860
+ if (live) return live;
2861
+ let reserved = this.reservedBuckets.get(key);
2862
+ if (!reserved) {
2863
+ reserved = new OIMKeyedBucketSetBased(key);
2864
+ this.reservedBuckets.set(key, reserved);
2865
+ }
2866
+ return reserved;
2867
+ }
2868
+ findBucket(key) {
2869
+ return this.slots.get(key) ?? this.reservedBuckets.get(key);
2870
+ }
2871
+ retainBucket(bucket) {
2872
+ if (bucket.size === 0) return;
2873
+ this.reservedBuckets.delete(bucket.key);
2874
+ if (!this.slots.has(bucket.key)) this.slots.set(bucket.key, bucket);
2875
+ }
2876
+ releaseBucket(bucket) {
2877
+ if (bucket.size > 0) return;
2878
+ this.slots.delete(bucket.key);
2879
+ if (bucket.hasSubscribers()) {
2880
+ this.reservedBuckets.set(bucket.key, bucket);
2881
+ } else {
2882
+ this.reservedBuckets.delete(bucket.key);
2883
+ }
2884
+ }
2885
+ dropIfReserved(key) {
2886
+ const reserved = this.reservedBuckets.get(key);
2887
+ if (reserved && reserved.size === 0 && !reserved.hasSubscribers()) {
2888
+ this.reservedBuckets.delete(key);
2889
+ }
2890
+ }
2891
+ setOneByKey(key, slots) {
2892
+ const bucket = this.getOrReserveBucket(key);
2893
+ if (bucket !== slots) {
2894
+ bucket.clear();
2895
+ for (const slot of slots) bucket.add(slot);
2896
+ }
2897
+ if (bucket.size > 0) this.retainBucket(bucket);
2898
+ else this.releaseBucket(bucket);
2899
+ }
2900
+ removeOneByKey(key) {
2901
+ this.slots.delete(key);
2902
+ this.reservedBuckets.delete(key);
2903
+ }
2904
+ removeManyByKeys(keys) {
2905
+ for (let i = 0; i < keys.length; i++) {
2906
+ this.slots.delete(keys[i]);
2907
+ this.reservedBuckets.delete(keys[i]);
2908
+ }
2909
+ }
2910
+ getOneByKey(key) {
2911
+ return this.slots.get(key);
2912
+ }
2913
+ getManyByKeys(keys) {
2914
+ const result = /* @__PURE__ */ new Map();
2915
+ for (let i = 0; i < keys.length; i++) {
2916
+ const slots = this.slots.get(keys[i]);
2917
+ if (slots !== void 0) result.set(keys[i], slots);
2918
+ }
2919
+ return result;
2920
+ }
2921
+ getAllKeys() {
2922
+ return Array.from(this.slots.keys());
2923
+ }
2924
+ getAll() {
2925
+ const result = /* @__PURE__ */ new Map();
2926
+ for (const [path, bucket] of this.slots.entries()) {
2927
+ result.set(path, bucket);
2928
+ }
2929
+ return result;
2930
+ }
2931
+ countAll() {
2932
+ return this.slots.size;
2933
+ }
2934
+ clear() {
2935
+ for (const bucket of this.slots.values()) {
2936
+ bucket.clear();
2937
+ if (bucket.hasSubscribers()) {
2938
+ this.reservedBuckets.set(bucket.key, bucket);
2939
+ }
2940
+ }
2941
+ this.slots.clear();
2942
+ }
2943
+ };
2944
+
2945
+ // src/core/OIMReactiveCollectionIndexCompositeTrieSetBased.ts
2946
+ var OIMReactiveCollectionIndexCompositeTrieSetBased = class extends OIMReactiveIndexManualSetBased {
2947
+ constructor(queue, opts) {
2948
+ const resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.resolveSlot;
2949
+ super(queue, {
2950
+ indexOptions: {
2951
+ comparePks: opts.indexOptions?.comparePks,
2952
+ store: opts.indexOptions?.store ?? new OIMIndexStoreTrieDrivenSetBased()
2953
+ },
2954
+ pkDomain: opts.collection?.keyDomain
2955
+ });
2956
+ this.resolveRequiredSlot = (pk) => {
2957
+ const slot = this.resolveSlot(pk);
2958
+ if (!slot) return { pk, item: void 0 };
2959
+ return slot;
2960
+ };
2961
+ this.resolveSlot = resolveSlot;
2962
+ }
2963
+ setPks(key, pks) {
2964
+ this.index.setPks(key, pks, this.resolveRequiredSlot);
2965
+ }
2966
+ addPks(key, pks) {
2967
+ this.index.addPks(key, pks, this.resolveRequiredSlot);
2968
+ }
2969
+ removePks(key, pks) {
2970
+ this.index.removePks(key, pks);
2971
+ }
2972
+ getEntitiesByKey(key) {
2973
+ return super.getEntitiesByKey(key);
2974
+ }
2975
+ getEntitiesByKeys(keys) {
2976
+ return super.getEntitiesByKeys(keys);
2977
+ }
2978
+ };
2979
+
2980
+ // src/core/OIMIndexStoreTrieDrivenArrayBased.ts
2981
+ var OIMIndexStoreTrieDrivenArrayBased = class extends OIMIndexStoreArrayBased {
2982
+ constructor() {
2983
+ super(...arguments);
2984
+ this.slots = new OIMTrieMap();
2985
+ this.reservedBuckets = new OIMTrieMap();
2986
+ }
2987
+ getOrReserveBucket(key) {
2988
+ const live = this.slots.get(key);
2989
+ if (live) return live;
2990
+ let reserved = this.reservedBuckets.get(key);
2991
+ if (!reserved) {
2992
+ reserved = new OIMKeyedBucketArrayBased(key);
2993
+ this.reservedBuckets.set(key, reserved);
2994
+ }
2995
+ return reserved;
2996
+ }
2997
+ findBucket(key) {
2998
+ return this.slots.get(key) ?? this.reservedBuckets.get(key);
2999
+ }
3000
+ retainBucket(bucket) {
3001
+ if (bucket.length === 0) return;
3002
+ this.reservedBuckets.delete(bucket.key);
3003
+ if (!this.slots.has(bucket.key)) this.slots.set(bucket.key, bucket);
3004
+ }
3005
+ releaseBucket(bucket) {
3006
+ if (bucket.length > 0) return;
3007
+ this.slots.delete(bucket.key);
3008
+ if (bucket.hasSubscribers()) {
3009
+ this.reservedBuckets.set(bucket.key, bucket);
3010
+ } else {
3011
+ this.reservedBuckets.delete(bucket.key);
3012
+ }
3013
+ }
3014
+ dropIfReserved(key) {
3015
+ const reserved = this.reservedBuckets.get(key);
3016
+ if (reserved && reserved.length === 0 && !reserved.hasSubscribers()) {
3017
+ this.reservedBuckets.delete(key);
3018
+ }
3019
+ }
3020
+ setOneByKey(key, slots) {
3021
+ const bucket = this.getOrReserveBucket(key);
3022
+ if (bucket !== slots) {
3023
+ bucket.length = 0;
3024
+ for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
3025
+ }
3026
+ if (bucket.length > 0) this.retainBucket(bucket);
3027
+ else this.releaseBucket(bucket);
3028
+ }
3029
+ removeOneByKey(key) {
3030
+ this.slots.delete(key);
3031
+ this.reservedBuckets.delete(key);
3032
+ }
3033
+ removeManyByKeys(keys) {
3034
+ for (let i = 0; i < keys.length; i++) {
3035
+ this.slots.delete(keys[i]);
3036
+ this.reservedBuckets.delete(keys[i]);
3037
+ }
3038
+ }
3039
+ getOneByKey(key) {
3040
+ return this.slots.get(key);
3041
+ }
3042
+ getManyByKeys(keys) {
3043
+ const result = /* @__PURE__ */ new Map();
3044
+ for (let i = 0; i < keys.length; i++) {
3045
+ const slots = this.slots.get(keys[i]);
3046
+ if (slots !== void 0) result.set(keys[i], slots);
3047
+ }
3048
+ return result;
3049
+ }
3050
+ getAllKeys() {
3051
+ return Array.from(this.slots.keys());
3052
+ }
3053
+ getAll() {
3054
+ const result = /* @__PURE__ */ new Map();
3055
+ for (const [path, bucket] of this.slots.entries()) {
3056
+ result.set(path, bucket);
3057
+ }
3058
+ return result;
3059
+ }
3060
+ countAll() {
3061
+ return this.slots.size;
3062
+ }
3063
+ clear() {
3064
+ for (const bucket of this.slots.values()) {
3065
+ bucket.length = 0;
3066
+ if (bucket.hasSubscribers()) {
3067
+ this.reservedBuckets.set(bucket.key, bucket);
3068
+ }
3069
+ }
3070
+ this.slots.clear();
3071
+ }
3072
+ };
3073
+
3074
+ // src/core/OIMReactiveCollectionIndexCompositeTrieArrayBased.ts
3075
+ var OIMReactiveCollectionIndexCompositeTrieArrayBased = class extends OIMReactiveIndexManualArrayBased {
3076
+ constructor(queue, opts) {
3077
+ const resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.resolveSlot;
3078
+ super(queue, {
3079
+ indexOptions: {
3080
+ comparePks: opts.indexOptions?.comparePks,
3081
+ store: opts.indexOptions?.store ?? new OIMIndexStoreTrieDrivenArrayBased()
3082
+ }
3083
+ });
3084
+ // Persistent per-key-path pk membership for O(1) `addPks` dedup. Trie-keyed
3085
+ // so a rebuilt key path matches by content.
3086
+ this.pksByKey = new OIMTrieMap();
3087
+ this.resolveSlot = resolveSlot;
3088
+ }
3089
+ setPks(key, pks) {
3090
+ const pkSet = /* @__PURE__ */ new Set();
3091
+ for (let i = 0; i < pks.length; i++) pkSet.add(pks[i]);
3092
+ this.pksByKey.set(key, pkSet);
3093
+ super.setSlots(key, this.resolveSlots(pks));
3094
+ }
3095
+ addPks(key, pks) {
3096
+ if (pks.length === 0) return;
3097
+ const pkSet = this.membershipOf(key);
3098
+ const newSlots = [];
3099
+ for (const pk of pks) {
3100
+ if (pkSet.has(pk)) continue;
3101
+ pkSet.add(pk);
3102
+ newSlots.push(this.resolveRequiredSlot(pk));
3103
+ }
3104
+ if (newSlots.length > 0) this.appendSlots(key, newSlots);
3105
+ }
3106
+ removePks(key, pks) {
3107
+ if (pks.length === 0) return;
3108
+ const pksToRemove = new Set(pks);
3109
+ const existingSlots = this.index.getSlotsByKey(key);
3110
+ const nextSlots = existingSlots.filter(
3111
+ (slot) => !pksToRemove.has(slot.pk)
3112
+ );
3113
+ if (nextSlots.length === existingSlots.length) return;
3114
+ const pkSet = this.pksByKey.get(key);
3115
+ if (pkSet) {
3116
+ for (let i = 0; i < pks.length; i++) pkSet.delete(pks[i]);
3117
+ }
3118
+ if (nextSlots.length === 0) this.clear(key);
3119
+ else super.setSlots(key, nextSlots);
3120
+ }
3121
+ clear(key) {
3122
+ if (key === void 0) this.pksByKey.clear();
3123
+ else this.pksByKey.delete(key);
3124
+ super.clear(key);
3125
+ }
3126
+ /**
3127
+ * The membership set for a key path, lazily seeded from the current bucket so
3128
+ * it stays correct even if slots were set through a lower-level path.
3129
+ */
3130
+ membershipOf(key) {
3131
+ let pkSet = this.pksByKey.get(key);
3132
+ if (!pkSet) {
3133
+ pkSet = /* @__PURE__ */ new Set();
3134
+ const existing = this.index.getSlotsByKey(key);
3135
+ for (let i = 0; i < existing.length; i++) {
3136
+ pkSet.add(existing[i].pk);
3137
+ }
3138
+ this.pksByKey.set(key, pkSet);
3139
+ }
3140
+ return pkSet;
3141
+ }
3142
+ getEntitiesByKey(key) {
3143
+ return super.getEntitiesByKey(key);
3144
+ }
3145
+ getEntitiesByKeys(keys) {
3146
+ return super.getEntitiesByKeys(keys);
3147
+ }
3148
+ resolveSlots(pks) {
3149
+ const slots = [];
3150
+ slots.length = pks.length;
3151
+ for (let i = 0; i < pks.length; i++) {
3152
+ slots[i] = this.resolveRequiredSlot(pks[i]);
3153
+ }
3154
+ return slots;
3155
+ }
3156
+ resolveRequiredSlot(pk) {
3157
+ const slot = this.resolveSlot(pk);
3158
+ if (!slot) return { pk, item: void 0 };
3159
+ return slot;
3160
+ }
3161
+ };
3162
+
3163
+ // src/core/OIMDerivedCollectionIndexSetBased.ts
3164
+ var OIMDerivedCollectionIndexSetBased = class extends OIMReactiveCollectionIndexManualSetBased {
3165
+ constructor(queue, collection, opts) {
3166
+ super(queue, {
3167
+ collection,
3168
+ indexOptions: opts.indexOptions
3169
+ });
3170
+ this.keysByPk = /* @__PURE__ */ new Map();
3171
+ this.onCollectionUpdate = (pks) => {
3172
+ if (pks.length === 0) {
3173
+ this.rebuildFromCollection();
3174
+ return;
3175
+ }
3176
+ for (const pk of pks) {
3177
+ this.syncPk(pk);
3178
+ }
3179
+ };
3180
+ this.collection = collection;
3181
+ this.selectIndexKeys = opts.selectIndexKeys;
3182
+ this.unsubscribeFromCollection = collection.emitter.on(
3183
+ 0 /* UPDATE */,
3184
+ (payload) => this.onCollectionUpdate(payload.pks)
3185
+ );
3186
+ if (opts.buildInitial !== false) {
3187
+ this.rebuildFromCollection();
3188
+ }
3189
+ }
3190
+ rebuildFromCollection() {
3191
+ this.keysByPk.clear();
3192
+ super.clear();
3193
+ const slotsByKey = /* @__PURE__ */ new Map();
3194
+ const slots = this.collection.getAllSlots();
3195
+ for (const slot of slots) {
3196
+ if (slot.item === void 0) continue;
3197
+ const keys = this.normalizeKeys(this.selectIndexKeys(slot.item));
3198
+ if (keys.length === 0) continue;
3199
+ this.keysByPk.set(slot.pk, new Set(keys));
2542
3200
  for (const key of keys) {
2543
3201
  let slotsForKey = slotsByKey.get(key);
2544
3202
  if (!slotsForKey) {
@@ -3194,8 +3852,32 @@ var OIMCollectionIndexFactory = class {
3194
3852
  this.queue = queue;
3195
3853
  this.collection = collection;
3196
3854
  }
3197
- setBasedIndex(opts = {}) {
3198
- return new OIMReactiveCollectionIndexManualSetBased(this.queue, {
3855
+ setBasedIndex(opts = {}) {
3856
+ return new OIMReactiveCollectionIndexManualSetBased(this.queue, {
3857
+ collection: this.collection,
3858
+ indexOptions: opts.indexOptions
3859
+ });
3860
+ }
3861
+ /**
3862
+ * Composite (trie-backed) manual Set-based index keyed by an
3863
+ * arbitrary-length key path, e.g. `setPks([userId, projectId, role], pks)`.
3864
+ * Key paths are matched by content in O(arity) — no string concatenation,
3865
+ * no separator collisions, each segment keeps its type. Primitive-keyed
3866
+ * indexes are unaffected and keep their native-`Map` fast path.
3867
+ */
3868
+ compositeSetIndex(opts = {}) {
3869
+ return new OIMReactiveCollectionIndexCompositeTrieSetBased(this.queue, {
3870
+ collection: this.collection,
3871
+ indexOptions: opts.indexOptions
3872
+ });
3873
+ }
3874
+ /**
3875
+ * Composite (trie-backed) manual Array-based (ordered) index keyed by an
3876
+ * arbitrary-length key path, e.g. `setPks([channelId, threadId], pks)`.
3877
+ * Ordered counterpart of `compositeSetIndex`.
3878
+ */
3879
+ compositeArrayIndex(opts = {}) {
3880
+ return new OIMReactiveCollectionIndexCompositeTrieArrayBased(this.queue, {
3199
3881
  collection: this.collection,
3200
3882
  indexOptions: opts.indexOptions
3201
3883
  });
@@ -3448,6 +4130,195 @@ var OIMReactiveObject = class extends OIMObject {
3448
4130
  }
3449
4131
  };
3450
4132
 
4133
+ // src/core/OIMTrieSet.ts
4134
+ var OIMTrieSet = class {
4135
+ constructor() {
4136
+ this.map = new OIMTrieMap();
4137
+ }
4138
+ get size() {
4139
+ return this.map.size;
4140
+ }
4141
+ add(key) {
4142
+ this.map.set(key, true);
4143
+ }
4144
+ has(key) {
4145
+ return this.map.has(key);
4146
+ }
4147
+ delete(key) {
4148
+ return this.map.delete(key);
4149
+ }
4150
+ *values() {
4151
+ yield* this.map.keys();
4152
+ }
4153
+ clear() {
4154
+ this.map.clear();
4155
+ }
4156
+ };
4157
+
4158
+ // src/core/OIMKeyDomainTrie.ts
4159
+ var OIMKeyDomainTrie = class {
4160
+ constructor() {
4161
+ // Interning pool: logical key (by content) → the one canonical reference.
4162
+ this.pool = new OIMTrieMap();
4163
+ }
4164
+ createMap() {
4165
+ return new OIMTrieMap();
4166
+ }
4167
+ createSet() {
4168
+ return new OIMTrieSet();
4169
+ }
4170
+ canonicalize(key) {
4171
+ const existing = this.pool.get(key);
4172
+ if (existing !== void 0) return existing;
4173
+ const canonical = Object.freeze(key.slice());
4174
+ this.pool.set(canonical, canonical);
4175
+ return canonical;
4176
+ }
4177
+ };
4178
+
4179
+ // src/core/OIMCollectionStoreTrieDriven.ts
4180
+ var OIMCollectionStoreTrieDriven = class extends OIMCollectionStore {
4181
+ constructor(keyDomain = new OIMKeyDomainTrie()) {
4182
+ super();
4183
+ this.keyDomain = keyDomain;
4184
+ this.slots = new OIMTrieMap();
4185
+ this.reservedSlots = new OIMTrieMap();
4186
+ }
4187
+ setOneByPk(pk, entity) {
4188
+ const slot = this.slots.get(pk);
4189
+ if (slot) {
4190
+ slot.item = entity;
4191
+ return slot;
4192
+ }
4193
+ const reserved = this.reservedSlots.get(pk);
4194
+ if (reserved) {
4195
+ reserved.item = entity;
4196
+ this.reservedSlots.delete(pk);
4197
+ this.slots.set(pk, reserved);
4198
+ return reserved;
4199
+ }
4200
+ const nextSlot = {
4201
+ pk: this.keyDomain.canonicalize(pk),
4202
+ item: entity
4203
+ };
4204
+ this.slots.set(pk, nextSlot);
4205
+ return nextSlot;
4206
+ }
4207
+ getOrReserveSlotByPk(pk) {
4208
+ const slot = this.slots.get(pk);
4209
+ if (slot) return slot;
4210
+ let reserved = this.reservedSlots.get(pk);
4211
+ if (!reserved) {
4212
+ reserved = { pk: this.keyDomain.canonicalize(pk), item: void 0 };
4213
+ this.reservedSlots.set(pk, reserved);
4214
+ }
4215
+ return reserved;
4216
+ }
4217
+ setManyByPks(pks, entities) {
4218
+ const slots = [];
4219
+ slots.length = pks.length;
4220
+ for (let i = 0; i < pks.length; i++) {
4221
+ slots[i] = this.setOneByPk(pks[i], entities[i]);
4222
+ }
4223
+ return slots;
4224
+ }
4225
+ getSlotByPk(pk) {
4226
+ return this.slots.get(pk);
4227
+ }
4228
+ findSlotByPk(pk) {
4229
+ return this.slots.get(pk) ?? this.reservedSlots.get(pk);
4230
+ }
4231
+ hasSubscribers(slot) {
4232
+ return slot.subscribers !== void 0 && slot.subscribers.size > 0;
4233
+ }
4234
+ getSlotsByPks(pks) {
4235
+ const result = [];
4236
+ result.length = pks.length;
4237
+ let writeIndex = 0;
4238
+ for (let i = 0; i < pks.length; i++) {
4239
+ const slot = this.slots.get(pks[i]);
4240
+ if (slot !== void 0) result[writeIndex++] = slot;
4241
+ }
4242
+ result.length = writeIndex;
4243
+ return result;
4244
+ }
4245
+ getAllSlots() {
4246
+ return Array.from(this.slots.values());
4247
+ }
4248
+ removeOneByPk(pk) {
4249
+ const slot = this.slots.get(pk);
4250
+ if (slot) {
4251
+ slot.item = void 0;
4252
+ this.slots.delete(pk);
4253
+ if (this.hasSubscribers(slot)) {
4254
+ this.reservedSlots.set(pk, slot);
4255
+ }
4256
+ return;
4257
+ }
4258
+ const reserved = this.reservedSlots.get(pk);
4259
+ if (reserved) {
4260
+ reserved.item = void 0;
4261
+ if (!this.hasSubscribers(reserved)) {
4262
+ this.reservedSlots.delete(pk);
4263
+ }
4264
+ }
4265
+ }
4266
+ removeManyByPks(pks) {
4267
+ for (const pk of pks) this.removeOneByPk(pk);
4268
+ }
4269
+ getOneByPk(pk) {
4270
+ return this.slots.get(pk)?.item;
4271
+ }
4272
+ getManyByPks(pks) {
4273
+ const result = [];
4274
+ result.length = pks.length;
4275
+ let writeIndex = 0;
4276
+ for (let i = 0; i < pks.length; i++) {
4277
+ const entity = this.getOneByPk(pks[i]);
4278
+ if (entity !== void 0) result[writeIndex++] = entity;
4279
+ }
4280
+ result.length = writeIndex;
4281
+ return result;
4282
+ }
4283
+ getAll() {
4284
+ const result = [];
4285
+ for (const slot of this.slots.values()) {
4286
+ if (slot.item !== void 0) result.push(slot.item);
4287
+ }
4288
+ return result;
4289
+ }
4290
+ countAll() {
4291
+ return this.slots.size;
4292
+ }
4293
+ clear() {
4294
+ const retained = [];
4295
+ for (const slot of this.slots.values()) {
4296
+ slot.item = void 0;
4297
+ if (this.hasSubscribers(slot)) retained.push(slot);
4298
+ }
4299
+ this.slots.clear();
4300
+ for (const slot of this.reservedSlots.values()) {
4301
+ slot.item = void 0;
4302
+ if (this.hasSubscribers(slot)) retained.push(slot);
4303
+ }
4304
+ this.reservedSlots.clear();
4305
+ for (const slot of retained) {
4306
+ this.reservedSlots.set(slot.pk, slot);
4307
+ }
4308
+ }
4309
+ getAllPks() {
4310
+ return Array.from(this.slots.keys());
4311
+ }
4312
+ destroy() {
4313
+ this.clear();
4314
+ for (const slot of this.reservedSlots.values()) {
4315
+ slot.subscribers?.clear();
4316
+ slot.subscribers = void 0;
4317
+ }
4318
+ this.reservedSlots.clear();
4319
+ }
4320
+ };
4321
+
3451
4322
  // src/core/OIMObjectStoreMapDriven.ts
3452
4323
  var OIMObjectStoreMapDriven = class extends OIMObjectStore {
3453
4324
  constructor() {
@@ -3495,6 +4366,27 @@ var OIMObjectStoreMapDriven = class extends OIMObjectStore {
3495
4366
  }
3496
4367
  };
3497
4368
 
4369
+ // src/core/OIMKeyPathCarrierResolver.ts
4370
+ var OIMKeyPathCarrierResolver = class {
4371
+ constructor() {
4372
+ this.carriers = new OIMTrieMap();
4373
+ }
4374
+ getOrReserveCarrier(key) {
4375
+ let carrier = this.carriers.get(key);
4376
+ if (!carrier) {
4377
+ carrier = { key };
4378
+ this.carriers.set(key, carrier);
4379
+ }
4380
+ return carrier;
4381
+ }
4382
+ findCarrier(key) {
4383
+ return this.carriers.get(key);
4384
+ }
4385
+ onCarrierEmptied(carrier) {
4386
+ this.carriers.delete(carrier.key);
4387
+ }
4388
+ };
4389
+
3498
4390
  // src/core/OIMComparatorFactory.ts
3499
4391
  var OIMComparatorFactory = class {
3500
4392
  createShallowComparator() {
@@ -3713,6 +4605,78 @@ var OIMMap2Keys = class {
3713
4605
  }
3714
4606
  };
3715
4607
 
4608
+ // src/core/OIMDisposeScope.ts
4609
+ var OIMDisposeScope = class _OIMDisposeScope {
4610
+ constructor() {
4611
+ this.disposables = [];
4612
+ this.disposed = false;
4613
+ }
4614
+ /** Number of not-yet-disposed items currently held. */
4615
+ get size() {
4616
+ return this.disposables.length;
4617
+ }
4618
+ get isDisposed() {
4619
+ return this.disposed;
4620
+ }
4621
+ /**
4622
+ * Register a disposable (object with `destroy()` or an unsubscribe function)
4623
+ * and return it unchanged, so it can be captured inline. If the scope is
4624
+ * already disposed the item is disposed immediately, so nothing leaks.
4625
+ */
4626
+ add(disposable) {
4627
+ if (this.disposed) {
4628
+ this.disposeOne(disposable);
4629
+ return disposable;
4630
+ }
4631
+ this.disposables.push(disposable);
4632
+ return disposable;
4633
+ }
4634
+ /** A nested scope, itself registered here — dispose it early or with the parent. */
4635
+ child() {
4636
+ return this.add(new _OIMDisposeScope());
4637
+ }
4638
+ /**
4639
+ * Dispose everything in reverse registration order, then clear. Idempotent.
4640
+ * If any `destroy()`/unsubscribe throws, the rest are still disposed and the
4641
+ * first error is rethrown once teardown completes.
4642
+ */
4643
+ destroy() {
4644
+ if (this.disposed) return;
4645
+ this.disposed = true;
4646
+ let firstError;
4647
+ let hasError = false;
4648
+ for (let i = this.disposables.length - 1; i >= 0; i--) {
4649
+ try {
4650
+ this.disposeOne(this.disposables[i]);
4651
+ } catch (error) {
4652
+ if (!hasError) {
4653
+ hasError = true;
4654
+ firstError = error;
4655
+ }
4656
+ }
4657
+ }
4658
+ this.disposables.length = 0;
4659
+ if (hasError) throw firstError;
4660
+ }
4661
+ disposeOne(disposable) {
4662
+ if (typeof disposable === "function") disposable();
4663
+ else disposable.destroy();
4664
+ }
4665
+ };
4666
+ function createOIMDisposeScope() {
4667
+ return new OIMDisposeScope();
4668
+ }
4669
+
4670
+ // src/core/OIMPkCodecKeyPath.ts
4671
+ var OIMPkCodecKeyPath = class {
4672
+ encode(pk) {
4673
+ return JSON.stringify(pk);
4674
+ }
4675
+ decode(encoded) {
4676
+ return JSON.parse(encoded);
4677
+ }
4678
+ };
4679
+
3716
4680
  // src/modules/compute/core/OIMComputeRuntime.ts
3717
4681
  var OIMComputeRuntime = class {
3718
4682
  constructor(queue) {
@@ -4316,6 +5280,27 @@ var OIMCollectionSelectors = class {
4316
5280
  key
4317
5281
  );
4318
5282
  }
5283
+ /**
5284
+ * Entities of a composite (key-path) set-based index for one key path, e.g.
5285
+ * `entitiesByCompositeSetIndexKey(index, [projectId, role])`.
5286
+ */
5287
+ entitiesByCompositeSetIndexKey(index, key) {
5288
+ return new OIMEntitiesByIndexKeySetBasedSelector(
5289
+ this.runtime,
5290
+ this.collection,
5291
+ index,
5292
+ key
5293
+ );
5294
+ }
5295
+ /** Entities of a composite (key-path) array-based index for one key path. */
5296
+ entitiesByCompositeArrayIndexKey(index, key) {
5297
+ return new OIMEntitiesByIndexKeyArrayBasedSelector(
5298
+ this.runtime,
5299
+ this.collection,
5300
+ index,
5301
+ key
5302
+ );
5303
+ }
4319
5304
  /** Entities of a keyless (whole-collection) array-based index, ordered. */
4320
5305
  entitiesByArrayGlobalIndex(index) {
4321
5306
  return new OIMEntitiesByGlobalIndexArrayBasedSelector(
@@ -4339,7 +5324,16 @@ function createOIMCollectionKit(queue, opts) {
4339
5324
  const collection = new OIMReactiveCollection(queue, opts);
4340
5325
  const indexFactory = new OIMCollectionIndexFactory(queue, collection);
4341
5326
  const select = new OIMCollectionSelectors(queue, collection);
4342
- return { queue, collection, indexFactory, select };
5327
+ const scope = new OIMDisposeScope();
5328
+ scope.add(collection);
5329
+ return {
5330
+ queue,
5331
+ collection,
5332
+ indexFactory,
5333
+ select,
5334
+ scope,
5335
+ destroy: () => scope.destroy()
5336
+ };
4343
5337
  }
4344
5338
 
4345
5339
  // src/modules/wrapper/collection/OIMCollectionChangedFields.ts
@@ -4726,6 +5720,56 @@ function createOIMIndexSlotMap(index, create) {
4726
5720
  return new OIMIndexSlotMap(index, create);
4727
5721
  }
4728
5722
 
5723
+ // src/modules/wrapper/index/OIMGlobalIndexSlotMap.ts
5724
+ var OIMGlobalIndexSlotMap = class _OIMGlobalIndexSlotMap {
5725
+ constructor(index, create) {
5726
+ this.cache = /* @__PURE__ */ new WeakMap();
5727
+ this.index = index;
5728
+ this.create = create;
5729
+ }
5730
+ /** The mapped object for a slot, created once and cached by slot identity. */
5731
+ get(slot) {
5732
+ let mapped = this.cache.get(slot);
5733
+ if (mapped === void 0 && !this.cache.has(slot)) {
5734
+ mapped = this.create(slot);
5735
+ this.cache.set(slot, mapped);
5736
+ }
5737
+ return mapped;
5738
+ }
5739
+ /**
5740
+ * Mapped objects for every slot in the index, with stable references. For a
5741
+ * set-based global index the order is unspecified (as the set is); for an
5742
+ * array-based one it follows the index order.
5743
+ */
5744
+ getAll() {
5745
+ const result = [];
5746
+ for (const slot of this.index.getSlots()) {
5747
+ result.push(this.get(slot));
5748
+ }
5749
+ return result;
5750
+ }
5751
+ /**
5752
+ * Chain another projection. `map(f)` returns a slot map over the SAME index
5753
+ * whose objects are `f(this.get(slot))`, cached by slot identity — both
5754
+ * levels hand back stable references and each `create` runs once per slot.
5755
+ */
5756
+ map(create) {
5757
+ return new _OIMGlobalIndexSlotMap(
5758
+ this.index,
5759
+ (slot) => create(this.get(slot))
5760
+ );
5761
+ }
5762
+ /** Subscribe to the index's changes — passthrough to the index. */
5763
+ subscribe(handler) {
5764
+ return this.index.subscribe(handler);
5765
+ }
5766
+ };
5767
+
5768
+ // src/modules/wrapper/index/createOIMGlobalIndexSlotMap.ts
5769
+ function createOIMGlobalIndexSlotMap(index, create) {
5770
+ return new OIMGlobalIndexSlotMap(index, create);
5771
+ }
5772
+
4729
5773
  // src/modules/wrapper/index/diffOrderedListByPk.ts
4730
5774
  function diffOrderedListByPk(prev, next) {
4731
5775
  const commands = [];
@@ -5478,6 +6522,7 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
5478
6522
  EOIMEventQueueSchedulerEventType,
5479
6523
  EOIMIndexEventType,
5480
6524
  EOIMObjectEventType,
6525
+ OIMBucketCarrierResolver,
5481
6526
  OIMCarrierKeyedEmitter,
5482
6527
  OIMCarrierSingleEmitter,
5483
6528
  OIMCollection,
@@ -5490,6 +6535,7 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
5490
6535
  OIMCollectionSelectors,
5491
6536
  OIMCollectionStore,
5492
6537
  OIMCollectionStoreMapDriven,
6538
+ OIMCollectionStoreTrieDriven,
5493
6539
  OIMComparatorFactory,
5494
6540
  OIMComputeRuntime,
5495
6541
  OIMComputed,
@@ -5498,6 +6544,7 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
5498
6544
  OIMDerivedCollectionGlobalIndexSetBased,
5499
6545
  OIMDerivedCollectionIndexArrayBased,
5500
6546
  OIMDerivedCollectionIndexSetBased,
6547
+ OIMDisposeScope,
5501
6548
  OIMEffect,
5502
6549
  OIMEffectDependencyComputed,
5503
6550
  OIMEffectDependencyKeyed,
@@ -5523,6 +6570,7 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
5523
6570
  OIMGlobalIndexManualArrayBased,
5524
6571
  OIMGlobalIndexManualSetBased,
5525
6572
  OIMGlobalIndexSetBased,
6573
+ OIMGlobalIndexSlotMap,
5526
6574
  OIMIndex,
5527
6575
  OIMIndexArrayBased,
5528
6576
  OIMIndexComparatorFactory,
@@ -5539,6 +6587,13 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
5539
6587
  OIMIndexStoreMapDrivenArrayBased,
5540
6588
  OIMIndexStoreMapDrivenSetBased,
5541
6589
  OIMIndexStoreSetBased,
6590
+ OIMIndexStoreTrieDrivenArrayBased,
6591
+ OIMIndexStoreTrieDrivenSetBased,
6592
+ OIMKeyDomainNative,
6593
+ OIMKeyDomainTrie,
6594
+ OIMKeyPathCarrierResolver,
6595
+ OIMKeyedBucketArrayBased,
6596
+ OIMKeyedBucketSetBased,
5542
6597
  OIMMap2Keys,
5543
6598
  OIMObject,
5544
6599
  OIMObjectStore,
@@ -5550,10 +6605,13 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
5550
6605
  OIMOrderedListCommandStream,
5551
6606
  OIMOrderedListCommandStreamDiffDriven,
5552
6607
  OIMOrderedListMappedCommandStream,
6608
+ OIMPkCodecKeyPath,
5553
6609
  OIMPkSelectorFactory,
5554
6610
  OIMReactiveCollection,
5555
6611
  OIMReactiveCollectionGlobalIndexManualArrayBased,
5556
6612
  OIMReactiveCollectionGlobalIndexManualSetBased,
6613
+ OIMReactiveCollectionIndexCompositeTrieArrayBased,
6614
+ OIMReactiveCollectionIndexCompositeTrieSetBased,
5557
6615
  OIMReactiveCollectionIndexManualArrayBased,
5558
6616
  OIMReactiveCollectionIndexManualSetBased,
5559
6617
  OIMReactiveGlobalIndex,
@@ -5568,10 +6626,14 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
5568
6626
  OIMReactiveIndexSetBased,
5569
6627
  OIMReactiveObject,
5570
6628
  OIMSelector,
6629
+ OIMTrieMap,
6630
+ OIMTrieSet,
5571
6631
  createInPlaceEntityUpdater,
5572
6632
  createMergeEntityUpdater,
5573
6633
  createOIMCollectionIndexFactory,
5574
6634
  createOIMCollectionKit,
6635
+ createOIMDisposeScope,
6636
+ createOIMGlobalIndexSlotMap,
5575
6637
  createOIMIndexSlotMap,
5576
6638
  createOIMOrderedListCommandStreamDiffDriven,
5577
6639
  createOIMOrderedListMappedCommandStream,