@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.js CHANGED
@@ -227,21 +227,82 @@ var OIMEventQueueScheduler = class {
227
227
  }
228
228
  };
229
229
 
230
+ // src/core/OIMKeyedBucketSetBased.ts
231
+ var OIMKeyedBucketSetBased = class extends Set {
232
+ constructor(key) {
233
+ super();
234
+ this.key = key;
235
+ }
236
+ hasSubscribers() {
237
+ return this.subscribers !== void 0 && this.subscribers.size > 0;
238
+ }
239
+ /** Clearing the slot set also clears the pk membership — they stay in sync. */
240
+ clear() {
241
+ super.clear();
242
+ this.membership?.clear();
243
+ }
244
+ };
245
+
230
246
  // src/core/OIMIndexStoreMapDrivenSetBased.ts
231
247
  var OIMIndexStoreMapDrivenSetBased = class extends OIMIndexStoreSetBased {
232
248
  constructor() {
233
249
  super(...arguments);
250
+ // Live buckets (non-empty, or freshly promoted). Enumeration reads these only.
234
251
  this.slots = /* @__PURE__ */ new Map();
252
+ // Empty buckets kept alive solely by their subscribers, so a subscription
253
+ // survives a key going empty → re-filled. Invisible to reads/enumeration.
254
+ this.reservedBuckets = /* @__PURE__ */ new Map();
255
+ }
256
+ getOrReserveBucket(key) {
257
+ const live = this.slots.get(key);
258
+ if (live) return live;
259
+ let reserved = this.reservedBuckets.get(key);
260
+ if (!reserved) {
261
+ reserved = new OIMKeyedBucketSetBased(key);
262
+ this.reservedBuckets.set(key, reserved);
263
+ }
264
+ return reserved;
265
+ }
266
+ findBucket(key) {
267
+ return this.slots.get(key) ?? this.reservedBuckets.get(key);
268
+ }
269
+ retainBucket(bucket) {
270
+ if (bucket.size === 0) return;
271
+ this.reservedBuckets.delete(bucket.key);
272
+ if (!this.slots.has(bucket.key)) this.slots.set(bucket.key, bucket);
273
+ }
274
+ releaseBucket(bucket) {
275
+ if (bucket.size > 0) return;
276
+ this.slots.delete(bucket.key);
277
+ if (bucket.hasSubscribers()) {
278
+ this.reservedBuckets.set(bucket.key, bucket);
279
+ } else {
280
+ this.reservedBuckets.delete(bucket.key);
281
+ }
282
+ }
283
+ dropIfReserved(key) {
284
+ const reserved = this.reservedBuckets.get(key);
285
+ if (reserved && reserved.size === 0 && !reserved.hasSubscribers()) {
286
+ this.reservedBuckets.delete(key);
287
+ }
235
288
  }
236
289
  setOneByKey(key, slots) {
237
- this.slots.set(key, slots);
290
+ const bucket = this.getOrReserveBucket(key);
291
+ if (bucket !== slots) {
292
+ bucket.clear();
293
+ for (const slot of slots) bucket.add(slot);
294
+ }
295
+ if (bucket.size > 0) this.retainBucket(bucket);
296
+ else this.releaseBucket(bucket);
238
297
  }
239
298
  removeOneByKey(key) {
240
299
  this.slots.delete(key);
300
+ this.reservedBuckets.delete(key);
241
301
  }
242
302
  removeManyByKeys(keys) {
243
303
  for (const key of keys) {
244
304
  this.slots.delete(key);
305
+ this.reservedBuckets.delete(key);
245
306
  }
246
307
  }
247
308
  getOneByKey(key) {
@@ -250,10 +311,8 @@ var OIMIndexStoreMapDrivenSetBased = class extends OIMIndexStoreSetBased {
250
311
  getManyByKeys(keys) {
251
312
  const result = /* @__PURE__ */ new Map();
252
313
  for (const key of keys) {
253
- const slots = this.getOneByKey(key);
254
- if (slots !== void 0) {
255
- result.set(key, slots);
256
- }
314
+ const slots = this.slots.get(key);
315
+ if (slots !== void 0) result.set(key, slots);
257
316
  }
258
317
  return result;
259
318
  }
@@ -267,6 +326,12 @@ var OIMIndexStoreMapDrivenSetBased = class extends OIMIndexStoreSetBased {
267
326
  return this.slots.size;
268
327
  }
269
328
  clear() {
329
+ for (const bucket of this.slots.values()) {
330
+ bucket.clear();
331
+ if (bucket.hasSubscribers()) {
332
+ this.reservedBuckets.set(bucket.key, bucket);
333
+ }
334
+ }
270
335
  this.slots.clear();
271
336
  }
272
337
  };
@@ -274,10 +339,9 @@ var OIMIndexStoreMapDrivenSetBased = class extends OIMIndexStoreSetBased {
274
339
  // src/abstract/OIMIndexSetBased.ts
275
340
  var OIMIndexSetBased = class extends OIMIndex {
276
341
  constructor(options = {}) {
277
- super(
278
- options.store ?? new OIMIndexStoreMapDrivenSetBased(),
279
- options.comparePks
280
- );
342
+ const store = options.store ?? new OIMIndexStoreMapDrivenSetBased();
343
+ super(store, options.comparePks);
344
+ this.setStore = store;
281
345
  }
282
346
  getBucketSize(bucket) {
283
347
  return bucket.size;
@@ -305,22 +369,36 @@ var OIMIndexSetBased = class extends OIMIndex {
305
369
  for (const slot of slots) pks.add(slot.pk);
306
370
  return pks;
307
371
  }
308
- setSlotsWithComparison(key, newSlots) {
309
- if (this.comparePks) {
310
- const existingSlotsSet = this.store.getOneByKey(key);
311
- if (existingSlotsSet && existingSlotsSet.size === newSlots.size) {
312
- if (this.comparePks(
313
- Array.from(this.slotsToPks(existingSlotsSet)),
314
- Array.from(this.slotsToPks(newSlots))
315
- )) {
316
- return false;
317
- }
318
- } else if (!existingSlotsSet && newSlots.size === 0) {
319
- return false;
320
- }
321
- }
322
- this.store.setOneByKey(key, newSlots);
323
- return true;
372
+ /**
373
+ * True if a comparator is set and considers `bucket`'s current pks equal to
374
+ * `newSlots` — i.e. a `setSlots` would be a no-op and should not emit.
375
+ */
376
+ bucketMatchesComparator(bucket, newSlots) {
377
+ if (!this.comparePks) return false;
378
+ if (bucket.size !== newSlots.size) return false;
379
+ if (bucket.size === 0) return true;
380
+ return this.comparePks(
381
+ Array.from(this.slotsToPks(bucket)),
382
+ Array.from(this.slotsToPks(newSlots))
383
+ );
384
+ }
385
+ };
386
+
387
+ // src/core/OIMKeyedBucketArrayBased.ts
388
+ var OIMKeyedBucketArrayBased = class extends Array {
389
+ constructor(key) {
390
+ super();
391
+ this.key = key;
392
+ }
393
+ static get [Symbol.species]() {
394
+ return Array;
395
+ }
396
+ hasSubscribers() {
397
+ return this.subscribers !== void 0 && this.subscribers.size > 0;
398
+ }
399
+ /** Empty the bucket in place (Array has no `clear`). */
400
+ clear() {
401
+ this.length = 0;
324
402
  }
325
403
  };
326
404
 
@@ -328,17 +406,61 @@ var OIMIndexSetBased = class extends OIMIndex {
328
406
  var OIMIndexStoreMapDrivenArrayBased = class extends OIMIndexStoreArrayBased {
329
407
  constructor() {
330
408
  super(...arguments);
409
+ // Live buckets (non-empty). Enumeration reads these only.
331
410
  this.slots = /* @__PURE__ */ new Map();
411
+ // Empty buckets kept alive by their subscribers, invisible to reads.
412
+ this.reservedBuckets = /* @__PURE__ */ new Map();
413
+ }
414
+ getOrReserveBucket(key) {
415
+ const live = this.slots.get(key);
416
+ if (live) return live;
417
+ let reserved = this.reservedBuckets.get(key);
418
+ if (!reserved) {
419
+ reserved = new OIMKeyedBucketArrayBased(key);
420
+ this.reservedBuckets.set(key, reserved);
421
+ }
422
+ return reserved;
423
+ }
424
+ findBucket(key) {
425
+ return this.slots.get(key) ?? this.reservedBuckets.get(key);
426
+ }
427
+ retainBucket(bucket) {
428
+ if (bucket.length === 0) return;
429
+ this.reservedBuckets.delete(bucket.key);
430
+ if (!this.slots.has(bucket.key)) this.slots.set(bucket.key, bucket);
431
+ }
432
+ releaseBucket(bucket) {
433
+ if (bucket.length > 0) return;
434
+ this.slots.delete(bucket.key);
435
+ if (bucket.hasSubscribers()) {
436
+ this.reservedBuckets.set(bucket.key, bucket);
437
+ } else {
438
+ this.reservedBuckets.delete(bucket.key);
439
+ }
440
+ }
441
+ dropIfReserved(key) {
442
+ const reserved = this.reservedBuckets.get(key);
443
+ if (reserved && reserved.length === 0 && !reserved.hasSubscribers()) {
444
+ this.reservedBuckets.delete(key);
445
+ }
332
446
  }
333
447
  setOneByKey(key, slots) {
334
- this.slots.set(key, slots);
448
+ const bucket = this.getOrReserveBucket(key);
449
+ if (bucket !== slots) {
450
+ bucket.length = 0;
451
+ for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
452
+ }
453
+ if (bucket.length > 0) this.retainBucket(bucket);
454
+ else this.releaseBucket(bucket);
335
455
  }
336
456
  removeOneByKey(key) {
337
457
  this.slots.delete(key);
458
+ this.reservedBuckets.delete(key);
338
459
  }
339
460
  removeManyByKeys(keys) {
340
461
  for (const key of keys) {
341
462
  this.slots.delete(key);
463
+ this.reservedBuckets.delete(key);
342
464
  }
343
465
  }
344
466
  getOneByKey(key) {
@@ -347,10 +469,8 @@ var OIMIndexStoreMapDrivenArrayBased = class extends OIMIndexStoreArrayBased {
347
469
  getManyByKeys(keys) {
348
470
  const result = /* @__PURE__ */ new Map();
349
471
  for (const key of keys) {
350
- const slots = this.getOneByKey(key);
351
- if (slots !== void 0) {
352
- result.set(key, slots);
353
- }
472
+ const slots = this.slots.get(key);
473
+ if (slots !== void 0) result.set(key, slots);
354
474
  }
355
475
  return result;
356
476
  }
@@ -364,6 +484,12 @@ var OIMIndexStoreMapDrivenArrayBased = class extends OIMIndexStoreArrayBased {
364
484
  return this.slots.size;
365
485
  }
366
486
  clear() {
487
+ for (const bucket of this.slots.values()) {
488
+ bucket.length = 0;
489
+ if (bucket.hasSubscribers()) {
490
+ this.reservedBuckets.set(bucket.key, bucket);
491
+ }
492
+ }
367
493
  this.slots.clear();
368
494
  }
369
495
  };
@@ -371,10 +497,9 @@ var OIMIndexStoreMapDrivenArrayBased = class extends OIMIndexStoreArrayBased {
371
497
  // src/abstract/OIMIndexArrayBased.ts
372
498
  var OIMIndexArrayBased = class extends OIMIndex {
373
499
  constructor(options = {}) {
374
- super(
375
- options.store ?? new OIMIndexStoreMapDrivenArrayBased(),
376
- options.comparePks
377
- );
500
+ const store = options.store ?? new OIMIndexStoreMapDrivenArrayBased();
501
+ super(store, options.comparePks);
502
+ this.arrayStore = store;
378
503
  }
379
504
  getBucketSize(bucket) {
380
505
  return bucket.length;
@@ -392,10 +517,15 @@ var OIMIndexArrayBased = class extends OIMIndex {
392
517
  return result;
393
518
  }
394
519
  getSlotsByKey(key) {
395
- return this.store.getOneByKey(key) ?? [];
520
+ const bucket = this.store.getOneByKey(key);
521
+ return bucket ? bucket.slice() : [];
396
522
  }
397
523
  getSlotsByKeys(keys) {
398
- return this.store.getManyByKeys(keys);
524
+ const result = /* @__PURE__ */ new Map();
525
+ for (const [key, bucket] of this.store.getManyByKeys(keys)) {
526
+ result.set(key, bucket.slice());
527
+ }
528
+ return result;
399
529
  }
400
530
  slotsToPks(slots) {
401
531
  const pks = [];
@@ -403,28 +533,24 @@ var OIMIndexArrayBased = class extends OIMIndex {
403
533
  for (let i = 0; i < slots.length; i++) pks[i] = slots[i].pk;
404
534
  return pks;
405
535
  }
406
- setSlotsWithComparison(key, newSlots) {
407
- if (this.comparePks) {
408
- const existingSlotsArray = this.store.getOneByKey(key);
409
- if (existingSlotsArray && existingSlotsArray.length === newSlots.length) {
410
- if (this.comparePks(
411
- this.slotsToPks(existingSlotsArray),
412
- this.slotsToPks(newSlots)
413
- )) {
414
- return false;
415
- }
416
- } else if (!existingSlotsArray && newSlots.length === 0) {
417
- return false;
418
- }
419
- }
420
- this.store.setOneByKey(key, newSlots);
421
- return true;
536
+ /**
537
+ * True if a comparator is set and considers `bucket`'s pks equal to
538
+ * `newSlots` — i.e. a `setSlots` would be a no-op and should not emit.
539
+ */
540
+ bucketMatchesComparator(bucket, newSlots) {
541
+ if (!this.comparePks) return false;
542
+ if (bucket.length !== newSlots.length) return false;
543
+ if (bucket.length === 0) return true;
544
+ return this.comparePks(
545
+ this.slotsToPks(bucket),
546
+ this.slotsToPks(newSlots)
547
+ );
422
548
  }
423
549
  };
424
550
 
425
551
  // src/core/OIMCarrierKeyedEmitter.ts
426
552
  var OIMCarrierKeyedEmitter = class {
427
- constructor(queue, resolver) {
553
+ constructor(queue, carrierProvider) {
428
554
  // Carriers with at least one subscriber — for markAllUpdated / metrics.
429
555
  this.subscribedCarriers = /* @__PURE__ */ new Set();
430
556
  // Pending flush batch. A plain array + a `carrier.dirty` flag for dedup —
@@ -449,17 +575,17 @@ var OIMCarrierKeyedEmitter = class {
449
575
  }
450
576
  };
451
577
  this.queue = queue;
452
- this.resolver = resolver;
578
+ this.carrierProvider = carrierProvider;
453
579
  }
454
580
  subscribeOnKey(key, handler) {
455
- const carrier = this.resolver.getOrReserveCarrier(key);
581
+ const carrier = this.carrierProvider.getOrReserveCarrier(key);
456
582
  this.addHandler(carrier, handler);
457
583
  return () => this.removeHandler(carrier, handler);
458
584
  }
459
585
  subscribeOnKeys(keys, handler) {
460
586
  const carriers = [];
461
587
  for (let i = 0; i < keys.length; i++) {
462
- const carrier = this.resolver.getOrReserveCarrier(keys[i]);
588
+ const carrier = this.carrierProvider.getOrReserveCarrier(keys[i]);
463
589
  this.addHandler(carrier, handler);
464
590
  carriers.push(carrier);
465
591
  }
@@ -470,7 +596,7 @@ var OIMCarrierKeyedEmitter = class {
470
596
  };
471
597
  }
472
598
  unsubscribeFromKey(key, handler) {
473
- const carrier = this.resolver.findCarrier(key);
599
+ const carrier = this.carrierProvider.findCarrier(key);
474
600
  if (carrier) this.removeHandler(carrier, handler);
475
601
  }
476
602
  unsubscribeFromKeys(keys, handler) {
@@ -493,7 +619,7 @@ var OIMCarrierKeyedEmitter = class {
493
619
  subscribers.delete(handler);
494
620
  if (subscribers.size === 0) {
495
621
  this.subscribedCarriers.delete(carrier);
496
- this.resolver.onCarrierEmptied?.(carrier);
622
+ this.carrierProvider.onCarrierEmptied?.(carrier);
497
623
  }
498
624
  }
499
625
  /** Fast path: the writer already holds the carrier. */
@@ -507,11 +633,19 @@ var OIMCarrierKeyedEmitter = class {
507
633
  this.scheduleFlush();
508
634
  }
509
635
  markUpdatedKey(key) {
510
- const carrier = this.resolver.findCarrier(key);
636
+ if (this.subscribedCarriers.size === 0) {
637
+ this.assertNotInFlush();
638
+ return;
639
+ }
640
+ const carrier = this.carrierProvider.findCarrier(key);
511
641
  if (carrier) this.markUpdatedCarrier(carrier);
512
642
  else this.assertNotInFlush();
513
643
  }
514
644
  markUpdatedKeys(keys) {
645
+ if (this.subscribedCarriers.size === 0) {
646
+ this.assertNotInFlush();
647
+ return;
648
+ }
515
649
  for (let i = 0; i < keys.length; i++) this.markUpdatedKey(keys[i]);
516
650
  }
517
651
  markAllUpdated() {
@@ -528,7 +662,7 @@ var OIMCarrierKeyedEmitter = class {
528
662
  return this.subscribedCarriers.size > 0;
529
663
  }
530
664
  getHandlerCount(key) {
531
- return this.resolver.findCarrier(key)?.subscribers?.size ?? 0;
665
+ return this.carrierProvider.findCarrier(key)?.subscribers?.size ?? 0;
532
666
  }
533
667
  getMetrics() {
534
668
  let totalHandlers = 0;
@@ -588,8 +722,8 @@ var OIMCarrierKeyedEmitter = class {
588
722
  }
589
723
  };
590
724
 
591
- // src/core/OIMKeyedCarrierResolver.ts
592
- var OIMKeyedCarrierResolver = class {
725
+ // src/core/OIMKeyedCarrierProvider.ts
726
+ var OIMKeyedCarrierProvider = class {
593
727
  constructor() {
594
728
  this.carriers = /* @__PURE__ */ new Map();
595
729
  }
@@ -611,8 +745,8 @@ var OIMKeyedCarrierResolver = class {
611
745
 
612
746
  // src/abstract/OIMReactiveIndex.ts
613
747
  var OIMReactiveIndex = class {
614
- constructor(queue, createIndex) {
615
- this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierResolver());
748
+ constructor(queue, createIndex, createCarrierProvider = () => new OIMKeyedCarrierProvider()) {
749
+ this.updateEmitter = new OIMCarrierKeyedEmitter(queue, createCarrierProvider());
616
750
  this.index = createIndex(this.updateEmitter);
617
751
  }
618
752
  getEntitiesByKey(key) {
@@ -668,8 +802,8 @@ var OIMReactiveIndex = class {
668
802
 
669
803
  // src/abstract/OIMReactiveIndexSetBased.ts
670
804
  var OIMReactiveIndexSetBased = class extends OIMReactiveIndex {
671
- constructor(queue, createIndex) {
672
- super(queue, createIndex);
805
+ constructor(queue, createIndex, createCarrierProvider) {
806
+ super(queue, createIndex, createCarrierProvider);
673
807
  }
674
808
  getPksByKey(key) {
675
809
  return this.index.getPksByKey(key);
@@ -687,8 +821,8 @@ var OIMReactiveIndexSetBased = class extends OIMReactiveIndex {
687
821
 
688
822
  // src/abstract/OIMReactiveIndexArrayBased.ts
689
823
  var OIMReactiveIndexArrayBased = class extends OIMReactiveIndex {
690
- constructor(queue, createIndex) {
691
- super(queue, createIndex);
824
+ constructor(queue, createIndex, createCarrierProvider) {
825
+ super(queue, createIndex, createCarrierProvider);
692
826
  }
693
827
  getPksByKey(key) {
694
828
  return this.index.getPksByKey(key);
@@ -1151,6 +1285,14 @@ var OIMCollection = class {
1151
1285
  getOrReserveSlotByPk(pk) {
1152
1286
  return this.store.getOrReserveSlotByPk(pk);
1153
1287
  }
1288
+ /**
1289
+ * The existing slot for `pk` (live or reserved) WITHOUT creating one — used
1290
+ * by indexes to resolve a pk to its canonical slot for removal (delete by
1291
+ * reference), with no side effect for a pk that is not present.
1292
+ */
1293
+ findSlotByPk(pk) {
1294
+ return this.store.findSlotByPk(pk);
1295
+ }
1154
1296
  getSlotsByPks(pks) {
1155
1297
  return this.store.getSlotsByPks(pks);
1156
1298
  }
@@ -1219,15 +1361,12 @@ var OIMCollection = class {
1219
1361
  `[OIMCollection]: PK is required to upsert an entity ${JSON.stringify(entity)}`
1220
1362
  );
1221
1363
  }
1222
- const existingEntity = this.store.getOneByPk(pk);
1223
- if (existingEntity) {
1224
- return this.store.setOneByPk(
1225
- pk,
1226
- this.updateEntity(entity, existingEntity)
1227
- );
1228
- } else {
1229
- return this.store.setOneByPk(pk, entity);
1364
+ const slot = this.store.getSlotByPk(pk);
1365
+ if (slot !== void 0 && slot.item !== void 0) {
1366
+ slot.item = this.updateEntity(entity, slot.item);
1367
+ return slot;
1230
1368
  }
1369
+ return this.store.setOneByPk(pk, entity);
1231
1370
  }
1232
1371
  destroy() {
1233
1372
  this.store.destroy();
@@ -1240,13 +1379,15 @@ var OIMReactiveCollection = class extends OIMCollection {
1240
1379
  constructor(queue, opts) {
1241
1380
  super(opts);
1242
1381
  this.anyUpdateHandlers = /* @__PURE__ */ new Set();
1382
+ // Only ever holds `slot.pk` (one canonical reference per logical key), so a
1383
+ // native `Set` dedups correctly by reference even for composite pks.
1243
1384
  this.pendingAnyUpdatePks = /* @__PURE__ */ new Set();
1244
1385
  this.isAnyUpdateClearPending = false;
1245
1386
  this.isAnyUpdateScheduled = false;
1246
1387
  this.runAnyUpdate = () => {
1247
1388
  if (!this.isAnyUpdateScheduled) return;
1248
1389
  this.isAnyUpdateScheduled = false;
1249
- const pks = this.isAnyUpdateClearPending ? [] : Array.from(this.pendingAnyUpdatePks);
1390
+ const pks = this.isAnyUpdateClearPending ? [] : Array.from(this.pendingAnyUpdatePks.values());
1250
1391
  this.pendingAnyUpdatePks.clear();
1251
1392
  this.isAnyUpdateClearPending = false;
1252
1393
  const snapshot = Array.from(this.anyUpdateHandlers);
@@ -1405,38 +1546,40 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1405
1546
  if (key === void 0) {
1406
1547
  const keys = this.getKeys();
1407
1548
  if (keys.length > 0) {
1408
- this.store.clear();
1549
+ this.arrayStore.clear();
1409
1550
  this.emitUpdate(Array.from(keys));
1410
1551
  }
1411
1552
  return;
1412
1553
  }
1413
- if (this.store.getOneByKey(key) !== void 0) {
1414
- this.store.removeOneByKey(key);
1554
+ const bucket = this.arrayStore.findBucket(key);
1555
+ if (bucket && bucket.length > 0) {
1556
+ bucket.length = 0;
1557
+ this.arrayStore.releaseBucket(bucket);
1415
1558
  this.emitUpdate([key]);
1416
1559
  }
1417
1560
  }
1418
1561
  pushSlot(key, slot) {
1419
- const list = this.getOrCreateList(key);
1420
- const index = list.length;
1421
- list.push(slot);
1562
+ const bucket = this.arrayStore.getOrReserveBucket(key);
1563
+ const index = bucket.length;
1564
+ bucket.push(slot);
1565
+ this.arrayStore.retainBucket(bucket);
1422
1566
  this.emitUpdate([key]);
1423
1567
  return index;
1424
1568
  }
1425
1569
  insertSlotAt(key, index, slot) {
1426
- const list = this.getOrCreateList(key);
1427
- const safeIndex = Math.max(0, Math.min(index, list.length));
1428
- list.splice(safeIndex, 0, slot);
1570
+ const bucket = this.arrayStore.getOrReserveBucket(key);
1571
+ const safeIndex = Math.max(0, Math.min(index, bucket.length));
1572
+ bucket.splice(safeIndex, 0, slot);
1573
+ this.arrayStore.retainBucket(bucket);
1429
1574
  this.emitUpdate([key]);
1430
1575
  return safeIndex;
1431
1576
  }
1432
1577
  removeAt(key, index) {
1433
- const list = this.store.getOneByKey(key);
1434
- if (!list) return void 0;
1435
- if (index < 0 || index >= list.length) return void 0;
1436
- const [removed] = list.splice(index, 1);
1437
- if (list.length === 0) {
1438
- this.store.removeOneByKey(key);
1439
- }
1578
+ const bucket = this.arrayStore.findBucket(key);
1579
+ if (!bucket) return void 0;
1580
+ if (index < 0 || index >= bucket.length) return void 0;
1581
+ const [removed] = bucket.splice(index, 1);
1582
+ if (bucket.length === 0) this.arrayStore.releaseBucket(bucket);
1440
1583
  this.emitUpdate([key]);
1441
1584
  return removed;
1442
1585
  }
@@ -1445,35 +1588,33 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1445
1588
  * Returns the number actually removed (clamped to bounds; 0 if nothing).
1446
1589
  */
1447
1590
  removeRange(key, index, count) {
1448
- const list = this.store.getOneByKey(key);
1449
- if (!list) return 0;
1450
- if (index < 0 || index >= list.length || count <= 0) return 0;
1451
- const actual = Math.min(count, list.length - index);
1452
- list.splice(index, actual);
1453
- if (list.length === 0) {
1454
- this.store.removeOneByKey(key);
1455
- }
1591
+ const bucket = this.arrayStore.findBucket(key);
1592
+ if (!bucket) return 0;
1593
+ if (index < 0 || index >= bucket.length || count <= 0) return 0;
1594
+ const actual = Math.min(count, bucket.length - index);
1595
+ bucket.splice(index, actual);
1596
+ if (bucket.length === 0) this.arrayStore.releaseBucket(bucket);
1456
1597
  this.emitUpdate([key]);
1457
1598
  return actual;
1458
1599
  }
1459
1600
  move(key, fromIndex, toIndex) {
1460
- const list = this.store.getOneByKey(key);
1461
- if (!list) return void 0;
1462
- if (fromIndex < 0 || fromIndex >= list.length) return void 0;
1463
- if (toIndex < 0 || toIndex >= list.length) return void 0;
1464
- if (fromIndex === toIndex) return list[fromIndex];
1465
- const [slot] = list.splice(fromIndex, 1);
1601
+ const bucket = this.arrayStore.findBucket(key);
1602
+ if (!bucket) return void 0;
1603
+ if (fromIndex < 0 || fromIndex >= bucket.length) return void 0;
1604
+ if (toIndex < 0 || toIndex >= bucket.length) return void 0;
1605
+ if (fromIndex === toIndex) return bucket[fromIndex];
1606
+ const [slot] = bucket.splice(fromIndex, 1);
1466
1607
  if (slot === void 0) return void 0;
1467
- list.splice(toIndex, 0, slot);
1608
+ bucket.splice(toIndex, 0, slot);
1468
1609
  this.emitUpdate([key]);
1469
1610
  return slot;
1470
1611
  }
1471
1612
  /** Replace the slot at `index` in place. Returns the safe index, or -1. */
1472
1613
  setSlotAt(key, index, slot) {
1473
- const list = this.store.getOneByKey(key);
1474
- if (!list) return -1;
1475
- if (index < 0 || index >= list.length) return -1;
1476
- list[index] = slot;
1614
+ const bucket = this.arrayStore.findBucket(key);
1615
+ if (!bucket) return -1;
1616
+ if (index < 0 || index >= bucket.length) return -1;
1617
+ bucket[index] = slot;
1477
1618
  this.emitUpdate([key]);
1478
1619
  return index;
1479
1620
  }
@@ -1483,52 +1624,49 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1483
1624
  * Returns the number actually moved (0 if a no-op / out of bounds).
1484
1625
  */
1485
1626
  moveRange(key, from, to, count) {
1486
- const list = this.store.getOneByKey(key);
1487
- if (!list) return 0;
1488
- if (from < 0 || from >= list.length || count <= 0) return 0;
1489
- const actual = Math.min(count, list.length - from);
1490
- const insertAt = Math.max(0, Math.min(to, list.length - actual));
1627
+ const bucket = this.arrayStore.findBucket(key);
1628
+ if (!bucket) return 0;
1629
+ if (from < 0 || from >= bucket.length || count <= 0) return 0;
1630
+ const actual = Math.min(count, bucket.length - from);
1631
+ const insertAt = Math.max(0, Math.min(to, bucket.length - actual));
1491
1632
  if (insertAt === from) return 0;
1492
- const block = list.splice(from, actual);
1493
- list.splice(insertAt, 0, ...block);
1633
+ const block = bucket.splice(from, actual);
1634
+ bucket.splice(insertAt, 0, ...block);
1494
1635
  this.emitUpdate([key]);
1495
1636
  return actual;
1496
1637
  }
1497
1638
  resetSlots(key, slots) {
1498
1639
  if (slots.length === 0) {
1499
- if (this.store.getOneByKey(key) !== void 0) {
1500
- this.store.removeOneByKey(key);
1640
+ const bucket2 = this.arrayStore.findBucket(key);
1641
+ if (bucket2 && bucket2.length > 0) {
1642
+ bucket2.length = 0;
1643
+ this.arrayStore.releaseBucket(bucket2);
1501
1644
  this.emitUpdate([key]);
1502
1645
  }
1503
1646
  return;
1504
1647
  }
1505
- this.store.setOneByKey(key, slots.slice());
1648
+ const bucket = this.arrayStore.getOrReserveBucket(key);
1649
+ bucket.length = 0;
1650
+ for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
1651
+ this.arrayStore.retainBucket(bucket);
1506
1652
  this.emitUpdate([key]);
1507
1653
  }
1508
- getOrCreateList(key) {
1509
- let list = this.store.getOneByKey(key);
1510
- if (!list) {
1511
- list = [];
1512
- this.store.setOneByKey(key, list);
1513
- }
1514
- return list;
1515
- }
1516
1654
  };
1517
1655
 
1518
1656
  // src/core/OIMCollectionIndexManualOrderedArrayBased.ts
1519
1657
  var OIMCollectionIndexManualOrderedArrayBased = class extends OIMIndexManualOrderedArrayBased {
1520
1658
  constructor(opts) {
1521
1659
  super();
1522
- this.resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getSlotByPk(pk) : opts.resolveSlot;
1660
+ this.getSlot = opts.collection !== void 0 ? (pk) => opts.collection.getSlotByPk(pk) : opts.getSlot;
1523
1661
  }
1524
1662
  push(key, pk) {
1525
- return this.pushSlot(key, this.resolveRequiredSlot(pk));
1663
+ return this.pushSlot(key, this.getRequiredSlot(pk));
1526
1664
  }
1527
1665
  insertAt(key, index, pk) {
1528
- return this.insertSlotAt(key, index, this.resolveRequiredSlot(pk));
1666
+ return this.insertSlotAt(key, index, this.getRequiredSlot(pk));
1529
1667
  }
1530
1668
  reset(key, pks) {
1531
- this.resetSlots(key, this.resolveSlots(pks));
1669
+ this.resetSlots(key, this.getRequiredSlots(pks));
1532
1670
  }
1533
1671
  getEntitiesByKey(key) {
1534
1672
  return super.getEntitiesByKey(key);
@@ -1536,26 +1674,25 @@ var OIMCollectionIndexManualOrderedArrayBased = class extends OIMIndexManualOrde
1536
1674
  getEntitiesByKeys(keys) {
1537
1675
  return super.getEntitiesByKeys(keys);
1538
1676
  }
1539
- resolvePk(pk) {
1540
- return this.resolveRequiredSlot(pk);
1677
+ // Gets the slot for a pk, throwing if the (custom) getter has none — this
1678
+ // ordered index requires a real slot to place in order.
1679
+ getRequiredSlot(pk) {
1680
+ const slot = this.getSlot(pk);
1681
+ if (!slot) {
1682
+ throw new Error(
1683
+ `[OIMCollectionIndexManualOrderedArrayBased]: no slot for PK "${String(pk)}".`
1684
+ );
1685
+ }
1686
+ return slot;
1541
1687
  }
1542
- resolveSlots(pks) {
1688
+ getRequiredSlots(pks) {
1543
1689
  const slots = [];
1544
1690
  slots.length = pks.length;
1545
1691
  for (let i = 0; i < pks.length; i++) {
1546
- slots[i] = this.resolveRequiredSlot(pks[i]);
1692
+ slots[i] = this.getRequiredSlot(pks[i]);
1547
1693
  }
1548
1694
  return slots;
1549
1695
  }
1550
- resolveRequiredSlot(pk) {
1551
- const slot = this.resolveSlot(pk);
1552
- if (!slot) {
1553
- throw new Error(
1554
- `[OIMCollectionIndexManualOrderedArrayBased]: Unable to resolve slot for PK "${String(pk)}".`
1555
- );
1556
- }
1557
- return slot;
1558
- }
1559
1696
  };
1560
1697
 
1561
1698
  // src/enums/EOIMEventQueueEventType.ts
@@ -1974,18 +2111,18 @@ var OIMOrderedListCommandStream = class extends OIMOrderedListCommandBuffer {
1974
2111
  var OIMCollectionOrderedListCommandStream = class extends OIMOrderedListCommandStream {
1975
2112
  constructor(queue, opts) {
1976
2113
  const index = opts.index ?? new OIMCollectionIndexManualOrderedArrayBased(
1977
- opts.collection !== void 0 ? { collection: opts.collection } : { resolveSlot: opts.resolveSlot }
2114
+ opts.collection !== void 0 ? { collection: opts.collection } : { getSlot: opts.getSlot }
1978
2115
  );
1979
2116
  super(queue, index);
1980
2117
  this.index = index;
1981
2118
  }
1982
2119
  push(key, pk) {
1983
- const slot = this.resolvePk(pk);
2120
+ const slot = this.getRequiredSlot(pk);
1984
2121
  const index = this.index.pushSlot(key, slot);
1985
2122
  this.appendCommand(key, { type: "insert", index, item: slot });
1986
2123
  }
1987
2124
  insertAt(key, index, pk) {
1988
- const slot = this.resolvePk(pk);
2125
+ const slot = this.getRequiredSlot(pk);
1989
2126
  const safeIndex = this.index.insertSlotAt(key, index, slot);
1990
2127
  this.appendCommand(key, {
1991
2128
  type: "insert",
@@ -1995,7 +2132,7 @@ var OIMCollectionOrderedListCommandStream = class extends OIMOrderedListCommandS
1995
2132
  }
1996
2133
  /** Replace the element at `index` with the slot for `pk`, in place. */
1997
2134
  setAt(key, index, pk) {
1998
- const slot = this.resolvePk(pk);
2135
+ const slot = this.getRequiredSlot(pk);
1999
2136
  const safeIndex = this.index.setSlotAt(key, index, slot);
2000
2137
  if (safeIndex < 0) return;
2001
2138
  this.appendCommand(key, {
@@ -2005,7 +2142,7 @@ var OIMCollectionOrderedListCommandStream = class extends OIMOrderedListCommandS
2005
2142
  });
2006
2143
  }
2007
2144
  set(key, pks) {
2008
- const slots = pks.map((pk) => this.resolvePk(pk));
2145
+ const slots = pks.map((pk) => this.getRequiredSlot(pk));
2009
2146
  this.index.resetSlots(key, slots);
2010
2147
  this.appendResetCommand(key, slots);
2011
2148
  }
@@ -2017,8 +2154,8 @@ var OIMCollectionOrderedListCommandStream = class extends OIMOrderedListCommandS
2017
2154
  getEntitiesByKey(key) {
2018
2155
  return this.index.getEntitiesByKey(key);
2019
2156
  }
2020
- resolvePk(pk) {
2021
- return this.index.resolvePk(
2157
+ getRequiredSlot(pk) {
2158
+ return this.index.getRequiredSlot(
2022
2159
  pk
2023
2160
  );
2024
2161
  }
@@ -2030,43 +2167,68 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
2030
2167
  super(options);
2031
2168
  }
2032
2169
  setSlots(key, slots) {
2033
- const hasChanges = this.setSlotsWithComparison(key, slots);
2034
- if (hasChanges) {
2035
- this.emitUpdateOne(key);
2036
- }
2170
+ const bucket = this.arrayStore.getOrReserveBucket(key);
2171
+ if (this.bucketMatchesComparator(bucket, slots)) return;
2172
+ bucket.length = 0;
2173
+ for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
2174
+ if (bucket.length > 0) this.arrayStore.retainBucket(bucket);
2175
+ else this.arrayStore.releaseBucket(bucket);
2176
+ this.emitBucketChanged(bucket);
2037
2177
  }
2038
2178
  /**
2039
- * Appends pre-deduplicated slots to a key's bucket IN PLACE (no copy of the
2040
- * existing bucket) and emits once. Callers own dedup (so this stays O(added),
2041
- * not O(bucket)); used by collection-bound indexes for fast `addPks`.
2179
+ * Appends pre-deduplicated slots to a key's bucket IN PLACE and emits once.
2180
+ * O(added); callers own dedup.
2042
2181
  */
2043
2182
  appendSlots(key, slots) {
2044
2183
  if (slots.length === 0) return;
2045
- let bucket = this.store.getOneByKey(key);
2046
- if (!bucket) {
2047
- bucket = [];
2048
- this.store.setOneByKey(key, bucket);
2049
- }
2184
+ const bucket = this.arrayStore.getOrReserveBucket(key);
2050
2185
  for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
2051
- this.emitUpdateOne(key);
2186
+ this.arrayStore.retainBucket(bucket);
2187
+ this.emitBucketChanged(bucket);
2052
2188
  }
2053
2189
  /**
2054
- * Clear all primary keys for a specific index key, or all keys if no key specified
2190
+ * Clear all primary keys for a specific index key, or all keys if none given.
2055
2191
  */
2056
2192
  clear(key) {
2057
2193
  if (key === void 0) {
2058
- const allKeys = this.store.getAllKeys();
2194
+ const allKeys = this.arrayStore.getAllKeys();
2059
2195
  if (allKeys.length > 0) {
2060
- this.store.clear();
2196
+ this.arrayStore.clear();
2061
2197
  this.emitUpdate(allKeys);
2062
2198
  }
2063
2199
  } else {
2064
- if (this.store.getOneByKey(key) !== void 0) {
2065
- this.store.removeOneByKey(key);
2066
- this.emitUpdateOne(key);
2200
+ const bucket = this.arrayStore.findBucket(key);
2201
+ if (bucket && bucket.length > 0) {
2202
+ bucket.length = 0;
2203
+ this.arrayStore.releaseBucket(bucket);
2204
+ this.emitBucketChanged(bucket);
2067
2205
  }
2068
2206
  }
2069
2207
  }
2208
+ /**
2209
+ * Emit that a bucket changed. Base (non-reactive) path emits by key on the
2210
+ * plain event emitter; the reactive subclass overrides this to mark the
2211
+ * bucket carrier directly (O(1), no key→carrier lookup).
2212
+ */
2213
+ emitBucketChanged(bucket) {
2214
+ this.emitUpdateOne(bucket.key);
2215
+ }
2216
+ };
2217
+
2218
+ // src/core/OIMBucketCarrierProvider.ts
2219
+ var OIMBucketCarrierProvider = class {
2220
+ constructor(store) {
2221
+ this.store = store;
2222
+ }
2223
+ getOrReserveCarrier(key) {
2224
+ return this.store.getOrReserveBucket(key);
2225
+ }
2226
+ findCarrier(key) {
2227
+ return this.store.findBucket(key);
2228
+ }
2229
+ onCarrierEmptied(carrier) {
2230
+ this.store.dropIfReserved(carrier.key);
2231
+ }
2070
2232
  };
2071
2233
 
2072
2234
  // src/core/OIMReactiveIndexManualArrayBased.ts
@@ -2075,6 +2237,10 @@ var OIMIndexManualArrayBasedReactive = class extends OIMIndexManualArrayBased {
2075
2237
  super(opts);
2076
2238
  this.updateEmitter = updateEmitter;
2077
2239
  }
2240
+ // Deliver straight off the bucket the write just touched — O(1).
2241
+ emitBucketChanged(bucket) {
2242
+ this.updateEmitter.markUpdatedCarrier(bucket);
2243
+ }
2078
2244
  emitUpdate(keys) {
2079
2245
  this.updateEmitter.markUpdatedKeys(keys);
2080
2246
  }
@@ -2084,12 +2250,15 @@ var OIMIndexManualArrayBasedReactive = class extends OIMIndexManualArrayBased {
2084
2250
  };
2085
2251
  var OIMReactiveIndexManualArrayBased = class extends OIMReactiveIndexArrayBased {
2086
2252
  constructor(queue, opts) {
2087
- super(queue, (updateEmitter) => {
2088
- return new OIMIndexManualArrayBasedReactive(
2089
- updateEmitter,
2090
- opts?.indexOptions
2091
- );
2092
- });
2253
+ const store = opts?.indexOptions?.store ?? new OIMIndexStoreMapDrivenArrayBased();
2254
+ super(
2255
+ queue,
2256
+ (updateEmitter) => new OIMIndexManualArrayBasedReactive(updateEmitter, {
2257
+ comparePks: opts?.indexOptions?.comparePks,
2258
+ store
2259
+ }),
2260
+ () => new OIMBucketCarrierProvider(store)
2261
+ );
2093
2262
  }
2094
2263
  setSlots(key, slots) {
2095
2264
  this.index.setSlots(key, slots);
@@ -2105,50 +2274,61 @@ var OIMReactiveIndexManualArrayBased = class extends OIMReactiveIndexArrayBased
2105
2274
  // src/core/OIMReactiveCollectionIndexManualArrayBased.ts
2106
2275
  var OIMReactiveCollectionIndexManualArrayBased = class extends OIMReactiveIndexManualArrayBased {
2107
2276
  constructor(queue, opts) {
2108
- const resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.resolveSlot;
2277
+ const getSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.getSlot;
2109
2278
  super(queue, {
2110
2279
  indexOptions: opts.indexOptions
2111
2280
  });
2112
- // Persistent per-key pk membership for O(1) `addPks` dedup (avoids rebuilding
2113
- // a Set from the whole bucket on every call). Kept in sync by setSlots/clear.
2114
- this.pksByKey = /* @__PURE__ */ new Map();
2115
- this.resolveSlot = resolveSlot;
2281
+ // Persistent per-key membership for O(1) `addPks` dedup, keyed by the
2282
+ // canonical slot reference (correct for composite pks with a native Set — no
2283
+ // content-addressing needed, since slots are one reference per logical key).
2284
+ this.slotsByKey = /* @__PURE__ */ new Map();
2285
+ this.getSlot = getSlot;
2286
+ this.findSlotForRemoval = opts.collection !== void 0 ? (pk) => opts.collection.findSlotByPk(pk) : opts.getSlot;
2116
2287
  }
2117
2288
  setPks(key, pks) {
2118
- const pkSet = /* @__PURE__ */ new Set();
2119
- for (let i = 0; i < pks.length; i++) pkSet.add(pks[i]);
2120
- this.pksByKey.set(key, pkSet);
2121
- super.setSlots(key, this.resolveSlots(pks));
2289
+ const slots = this.getSlotsOrTransient(pks);
2290
+ this.slotsByKey.set(key, new Set(slots));
2291
+ super.setSlots(key, slots);
2122
2292
  }
2123
2293
  addPks(key, pks) {
2124
2294
  if (pks.length === 0) return;
2125
- const pkSet = this.membershipOf(key);
2295
+ const slotSet = this.membershipOf(key);
2126
2296
  const newSlots = [];
2127
2297
  for (const pk of pks) {
2128
- if (pkSet.has(pk)) continue;
2129
- pkSet.add(pk);
2130
- newSlots.push(this.resolveRequiredSlot(pk));
2298
+ const slot = this.getSlotOrTransient(pk);
2299
+ if (slotSet.has(slot)) continue;
2300
+ slotSet.add(slot);
2301
+ newSlots.push(slot);
2131
2302
  }
2132
2303
  if (newSlots.length > 0) this.appendSlots(key, newSlots);
2133
2304
  }
2134
2305
  removePks(key, pks) {
2135
2306
  if (pks.length === 0) return;
2136
- const pksToRemove = new Set(pks);
2307
+ const slotsToRemove = /* @__PURE__ */ new Set();
2308
+ const pksToRemove = /* @__PURE__ */ new Set();
2309
+ for (let i = 0; i < pks.length; i++) {
2310
+ const slot = this.findSlotForRemoval(pks[i]);
2311
+ if (slot) slotsToRemove.add(slot);
2312
+ else pksToRemove.add(pks[i]);
2313
+ }
2137
2314
  const existingSlots = this.index.getSlotsByKey(key);
2138
- const nextSlots = existingSlots.filter(
2139
- (slot) => !pksToRemove.has(slot.pk)
2140
- );
2141
- if (nextSlots.length === existingSlots.length) return;
2142
- const pkSet = this.pksByKey.get(key);
2143
- if (pkSet) {
2144
- for (let i = 0; i < pks.length; i++) pkSet.delete(pks[i]);
2315
+ const slotSet = this.slotsByKey.get(key);
2316
+ const nextSlots = [];
2317
+ for (let i = 0; i < existingSlots.length; i++) {
2318
+ const slot = existingSlots[i];
2319
+ if (slotsToRemove.has(slot) || pksToRemove.has(slot.pk)) {
2320
+ slotSet?.delete(slot);
2321
+ } else {
2322
+ nextSlots.push(slot);
2323
+ }
2145
2324
  }
2325
+ if (nextSlots.length === existingSlots.length) return;
2146
2326
  if (nextSlots.length === 0) this.clear(key);
2147
2327
  else super.setSlots(key, nextSlots);
2148
2328
  }
2149
2329
  clear(key) {
2150
- if (key === void 0) this.pksByKey.clear();
2151
- else this.pksByKey.delete(key);
2330
+ if (key === void 0) this.slotsByKey.clear();
2331
+ else this.slotsByKey.delete(key);
2152
2332
  super.clear(key);
2153
2333
  }
2154
2334
  /**
@@ -2156,16 +2336,12 @@ var OIMReactiveCollectionIndexManualArrayBased = class extends OIMReactiveIndexM
2156
2336
  * stays correct even if slots were set through a lower-level path.
2157
2337
  */
2158
2338
  membershipOf(key) {
2159
- let pkSet = this.pksByKey.get(key);
2160
- if (!pkSet) {
2161
- pkSet = /* @__PURE__ */ new Set();
2162
- const existing = this.index.getSlotsByKey(key);
2163
- for (let i = 0; i < existing.length; i++) {
2164
- pkSet.add(existing[i].pk);
2165
- }
2166
- this.pksByKey.set(key, pkSet);
2339
+ let slotSet = this.slotsByKey.get(key);
2340
+ if (!slotSet) {
2341
+ slotSet = new Set(this.index.getSlotsByKey(key));
2342
+ this.slotsByKey.set(key, slotSet);
2167
2343
  }
2168
- return pkSet;
2344
+ return slotSet;
2169
2345
  }
2170
2346
  getEntitiesByKey(key) {
2171
2347
  return super.getEntitiesByKey(key);
@@ -2173,16 +2349,16 @@ var OIMReactiveCollectionIndexManualArrayBased = class extends OIMReactiveIndexM
2173
2349
  getEntitiesByKeys(keys) {
2174
2350
  return super.getEntitiesByKeys(keys);
2175
2351
  }
2176
- resolveSlots(pks) {
2352
+ getSlotsOrTransient(pks) {
2177
2353
  const slots = [];
2178
2354
  slots.length = pks.length;
2179
2355
  for (let i = 0; i < pks.length; i++) {
2180
- slots[i] = this.resolveRequiredSlot(pks[i]);
2356
+ slots[i] = this.getSlotOrTransient(pks[i]);
2181
2357
  }
2182
2358
  return slots;
2183
2359
  }
2184
- resolveRequiredSlot(pk) {
2185
- const slot = this.resolveSlot(pk);
2360
+ getSlotOrTransient(pk) {
2361
+ const slot = this.getSlot(pk);
2186
2362
  if (!slot) return { pk, item: void 0 };
2187
2363
  return slot;
2188
2364
  }
@@ -2194,22 +2370,101 @@ var OIMIndexManualSetBased = class extends OIMIndexSetBased {
2194
2370
  super(options);
2195
2371
  }
2196
2372
  setSlots(key, slots) {
2197
- const hasChanges = this.setSlotsWithComparison(key, new Set(slots));
2198
- if (hasChanges) {
2199
- this.emitUpdateOne(key);
2373
+ const bucket = this.setStore.getOrReserveBucket(key);
2374
+ this.replaceBucketContents(bucket, slots);
2375
+ }
2376
+ // --- pk-oriented writes: resolve the bucket ONCE and use its own membership
2377
+ // (pk → slot) for dedup/removal, so a composite (trie) key is walked a single
2378
+ // time instead of once for the bucket and once for a separate membership map.
2379
+ setPks(key, pks, getSlot) {
2380
+ const bucket = this.setStore.getOrReserveBucket(key);
2381
+ const slots = [];
2382
+ for (let i = 0; i < pks.length; i++) slots.push(getSlot(pks[i]));
2383
+ this.replaceBucketContents(bucket, slots);
2384
+ const membership = bucket.membership ?? (bucket.membership = /* @__PURE__ */ new Map());
2385
+ membership.clear();
2386
+ for (let i = 0; i < slots.length; i++) {
2387
+ if (!membership.has(slots[i].pk)) {
2388
+ membership.set(slots[i].pk, slots[i]);
2389
+ }
2390
+ }
2391
+ }
2392
+ addPks(key, pks, getSlot) {
2393
+ if (pks.length === 0) return;
2394
+ const bucket = this.setStore.getOrReserveBucket(key);
2395
+ const membership = this.membershipOf(bucket);
2396
+ let changed = false;
2397
+ for (let i = 0; i < pks.length; i++) {
2398
+ const slot = getSlot(pks[i]);
2399
+ if (membership.has(slot.pk)) continue;
2400
+ membership.set(slot.pk, slot);
2401
+ if (!bucket.has(slot)) {
2402
+ bucket.add(slot);
2403
+ changed = true;
2404
+ }
2200
2405
  }
2406
+ if (!changed) return;
2407
+ this.setStore.retainBucket(bucket);
2408
+ this.emitBucketChanged(bucket);
2201
2409
  }
2202
2410
  /**
2203
- * Adds slots to a key's bucket IN PLACE (no copy of the existing Set) and
2204
- * emits once. O(added). Used by collection-bound indexes for fast `addPks`.
2411
+ * Removes by pk. `pks` must be canonical `slot.pk` references (membership is
2412
+ * keyed by them): for primitive pks that is just the pk; a collection-bound
2413
+ * composite index canonicalizes raw pks to `slot.pk` before calling this.
2414
+ */
2415
+ removePks(key, pks) {
2416
+ if (pks.length === 0) return;
2417
+ const bucket = this.setStore.findBucket(key);
2418
+ if (!bucket || bucket.size === 0) return;
2419
+ const membership = this.membershipOf(bucket);
2420
+ let changed = false;
2421
+ for (let i = 0; i < pks.length; i++) {
2422
+ const slot = membership.get(pks[i]);
2423
+ if (slot) {
2424
+ membership.delete(pks[i]);
2425
+ if (bucket.delete(slot)) changed = true;
2426
+ }
2427
+ }
2428
+ if (!changed) return;
2429
+ if (bucket.size === 0) this.setStore.releaseBucket(bucket);
2430
+ this.emitBucketChanged(bucket);
2431
+ }
2432
+ /**
2433
+ * The bucket's pk → slot membership, lazily created and seeded from the
2434
+ * bucket's current slots (so it stays correct even if slots were written
2435
+ * through the lower-level `setSlots` path).
2436
+ */
2437
+ membershipOf(bucket) {
2438
+ let membership = bucket.membership;
2439
+ if (!membership) {
2440
+ membership = /* @__PURE__ */ new Map();
2441
+ bucket.membership = membership;
2442
+ }
2443
+ if (membership.size === 0 && bucket.size > 0) {
2444
+ for (const slot of bucket) membership.set(slot.pk, slot);
2445
+ }
2446
+ return membership;
2447
+ }
2448
+ replaceBucketContents(bucket, slots) {
2449
+ if (this.comparePks) {
2450
+ const newSet = slots instanceof Set ? slots : new Set(slots);
2451
+ if (this.bucketMatchesComparator(bucket, newSet)) return;
2452
+ bucket.clear();
2453
+ for (const slot of newSet) bucket.add(slot);
2454
+ } else {
2455
+ bucket.clear();
2456
+ for (const slot of slots) bucket.add(slot);
2457
+ }
2458
+ if (bucket.size > 0) this.setStore.retainBucket(bucket);
2459
+ else this.setStore.releaseBucket(bucket);
2460
+ this.emitBucketChanged(bucket);
2461
+ }
2462
+ /**
2463
+ * Adds slots to a key's bucket IN PLACE and emits once. O(added).
2205
2464
  */
2206
2465
  addSlots(key, slots) {
2207
2466
  if (slots.length === 0) return;
2208
- let bucket = this.store.getOneByKey(key);
2209
- if (!bucket) {
2210
- bucket = /* @__PURE__ */ new Set();
2211
- this.store.setOneByKey(key, bucket);
2212
- }
2467
+ const bucket = this.setStore.getOrReserveBucket(key);
2213
2468
  let changed = false;
2214
2469
  for (let i = 0; i < slots.length; i++) {
2215
2470
  if (!bucket.has(slots[i])) {
@@ -2217,25 +2472,27 @@ var OIMIndexManualSetBased = class extends OIMIndexSetBased {
2217
2472
  changed = true;
2218
2473
  }
2219
2474
  }
2220
- if (changed) this.emitUpdateOne(key);
2475
+ if (!changed) return;
2476
+ this.setStore.retainBucket(bucket);
2477
+ this.emitBucketChanged(bucket);
2221
2478
  }
2222
2479
  /**
2223
2480
  * Removes slots from a key's bucket IN PLACE (O(removed)) and emits once.
2224
2481
  */
2225
2482
  removeSlots(key, slots) {
2226
2483
  if (slots.length === 0) return;
2227
- const bucket = this.store.getOneByKey(key);
2484
+ const bucket = this.setStore.findBucket(key);
2228
2485
  if (!bucket) return;
2229
2486
  let changed = false;
2230
2487
  for (let i = 0; i < slots.length; i++) {
2231
2488
  if (bucket.delete(slots[i])) changed = true;
2232
2489
  }
2233
2490
  if (!changed) return;
2234
- if (bucket.size === 0) this.store.removeOneByKey(key);
2235
- this.emitUpdateOne(key);
2491
+ if (bucket.size === 0) this.setStore.releaseBucket(bucket);
2492
+ this.emitBucketChanged(bucket);
2236
2493
  }
2237
2494
  /**
2238
- * Clear all primary keys for a specific index key, or all keys if no key specified
2495
+ * Clear all primary keys for a specific index key, or all keys if none given.
2239
2496
  */
2240
2497
  clear(key) {
2241
2498
  if (key === void 0) {
@@ -2245,12 +2502,22 @@ var OIMIndexManualSetBased = class extends OIMIndexSetBased {
2245
2502
  this.emitUpdate(allKeys);
2246
2503
  }
2247
2504
  } else {
2248
- if (this.store.getOneByKey(key) !== void 0) {
2249
- this.store.removeOneByKey(key);
2250
- this.emitUpdateOne(key);
2505
+ const bucket = this.setStore.findBucket(key);
2506
+ if (bucket && bucket.size > 0) {
2507
+ bucket.clear();
2508
+ this.setStore.releaseBucket(bucket);
2509
+ this.emitBucketChanged(bucket);
2251
2510
  }
2252
2511
  }
2253
2512
  }
2513
+ /**
2514
+ * Emit that a bucket changed. Base (non-reactive) path emits by key on the
2515
+ * plain event emitter; the reactive subclass overrides this to mark the
2516
+ * bucket carrier directly (O(1), no key→carrier lookup).
2517
+ */
2518
+ emitBucketChanged(bucket) {
2519
+ this.emitUpdateOne(bucket.key);
2520
+ }
2254
2521
  };
2255
2522
 
2256
2523
  // src/core/OIMReactiveIndexManualSetBased.ts
@@ -2259,6 +2526,12 @@ var OIMIndexManualSetBasedReactive = class extends OIMIndexManualSetBased {
2259
2526
  super(opts);
2260
2527
  this.updateEmitter = updateEmitter;
2261
2528
  }
2529
+ // Deliver straight off the bucket the write just touched — O(1), no
2530
+ // key→carrier lookup (the whole point of bucket-as-carrier).
2531
+ emitBucketChanged(bucket) {
2532
+ this.updateEmitter.markUpdatedCarrier(bucket);
2533
+ }
2534
+ // clear(all) still emits by key set (rare path).
2262
2535
  emitUpdate(keys) {
2263
2536
  this.updateEmitter.markUpdatedKeys(keys);
2264
2537
  }
@@ -2268,12 +2541,15 @@ var OIMIndexManualSetBasedReactive = class extends OIMIndexManualSetBased {
2268
2541
  };
2269
2542
  var OIMReactiveIndexManualSetBased = class extends OIMReactiveIndexSetBased {
2270
2543
  constructor(queue, opts) {
2271
- super(queue, (updateEmitter) => {
2272
- return new OIMIndexManualSetBasedReactive(
2273
- updateEmitter,
2274
- opts?.indexOptions
2275
- );
2276
- });
2544
+ const store = opts?.indexOptions?.store ?? new OIMIndexStoreMapDrivenSetBased();
2545
+ super(
2546
+ queue,
2547
+ (updateEmitter) => new OIMIndexManualSetBasedReactive(updateEmitter, {
2548
+ comparePks: opts?.indexOptions?.comparePks,
2549
+ store
2550
+ }),
2551
+ () => new OIMBucketCarrierProvider(store)
2552
+ );
2277
2553
  }
2278
2554
  setSlots(key, slots) {
2279
2555
  this.index.setSlots(key, slots);
@@ -2292,73 +2568,439 @@ var OIMReactiveIndexManualSetBased = class extends OIMReactiveIndexSetBased {
2292
2568
  // src/core/OIMReactiveCollectionIndexManualSetBased.ts
2293
2569
  var OIMReactiveCollectionIndexManualSetBased = class extends OIMReactiveIndexManualSetBased {
2294
2570
  constructor(queue, opts) {
2295
- const resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.resolveSlot;
2571
+ const getSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.getSlot;
2296
2572
  super(queue, {
2297
2573
  indexOptions: opts.indexOptions
2298
2574
  });
2299
- // Persistent per-key pk → slot map for O(1) add (dedup) and O(1) remove
2300
- // (slot lookup by pk). Kept in sync by setPks/addPks/removePks/clear.
2301
- this.slotByPk = /* @__PURE__ */ new Map();
2302
- this.resolveSlot = resolveSlot;
2575
+ // Bound once so the hot pk-write path passes a stable function (no per-call
2576
+ // closure allocation).
2577
+ this.getSlotOrTransient = (pk) => {
2578
+ const slot = this.getSlot(pk);
2579
+ if (!slot) return { pk, item: void 0 };
2580
+ return slot;
2581
+ };
2582
+ this.getSlot = getSlot;
2583
+ this.findSlotForRemoval = opts.collection !== void 0 ? (pk) => opts.collection.findSlotByPk(pk) : opts.getSlot;
2303
2584
  }
2304
2585
  setPks(key, pks) {
2305
- const map = /* @__PURE__ */ new Map();
2306
- const slots = [];
2586
+ this.index.setPks(key, pks, this.getSlotOrTransient);
2587
+ }
2588
+ addPks(key, pks) {
2589
+ this.index.addPks(key, pks, this.getSlotOrTransient);
2590
+ }
2591
+ removePks(key, pks) {
2592
+ const canonical = [];
2307
2593
  for (let i = 0; i < pks.length; i++) {
2308
- const pk = pks[i];
2309
- const slot = this.resolveRequiredSlot(pk);
2310
- if (!map.has(pk)) map.set(pk, slot);
2311
- slots.push(slot);
2594
+ const slot = this.findSlotForRemoval(pks[i]);
2595
+ canonical.push(slot ? slot.pk : pks[i]);
2312
2596
  }
2313
- this.slotByPk.set(key, map);
2314
- super.setSlots(key, slots);
2597
+ this.index.removePks(key, canonical);
2598
+ }
2599
+ getEntitiesByKey(key) {
2600
+ return super.getEntitiesByKey(key);
2601
+ }
2602
+ getEntitiesByKeys(keys) {
2603
+ return super.getEntitiesByKeys(keys);
2604
+ }
2605
+ };
2606
+
2607
+ // src/core/OIMTrieMap.ts
2608
+ var OIMTrieMap = class {
2609
+ constructor() {
2610
+ this.root = { hasValue: false };
2611
+ this._size = 0;
2612
+ }
2613
+ /** Number of stored paths (terminal nodes), not the number of trie nodes. */
2614
+ get size() {
2615
+ return this._size;
2616
+ }
2617
+ set(path, value) {
2618
+ let node = this.root;
2619
+ for (let i = 0; i < path.length; i++) {
2620
+ let children = node.children;
2621
+ if (!children) {
2622
+ children = /* @__PURE__ */ new Map();
2623
+ node.children = children;
2624
+ }
2625
+ let next = children.get(path[i]);
2626
+ if (!next) {
2627
+ next = { hasValue: false };
2628
+ children.set(path[i], next);
2629
+ }
2630
+ node = next;
2631
+ }
2632
+ if (!node.hasValue) {
2633
+ node.hasValue = true;
2634
+ this._size++;
2635
+ }
2636
+ node.value = value;
2637
+ }
2638
+ get(path) {
2639
+ const node = this.findNode(path);
2640
+ return node && node.hasValue ? node.value : void 0;
2641
+ }
2642
+ has(path) {
2643
+ const node = this.findNode(path);
2644
+ return !!node && node.hasValue;
2645
+ }
2646
+ delete(path) {
2647
+ const parents = [];
2648
+ let node = this.root;
2649
+ for (let i = 0; i < path.length; i++) {
2650
+ const next = node.children?.get(path[i]);
2651
+ if (!next) return false;
2652
+ parents.push(node);
2653
+ node = next;
2654
+ }
2655
+ if (!node.hasValue) return false;
2656
+ node.hasValue = false;
2657
+ node.value = void 0;
2658
+ this._size--;
2659
+ let child = node;
2660
+ for (let i = parents.length - 1; i >= 0; i--) {
2661
+ if (child.hasValue || child.children && child.children.size > 0) {
2662
+ break;
2663
+ }
2664
+ const parent = parents[i];
2665
+ parent.children.delete(path[i]);
2666
+ if (parent.children.size === 0) parent.children = void 0;
2667
+ child = parent;
2668
+ }
2669
+ return true;
2670
+ }
2671
+ clear() {
2672
+ this.root.children = void 0;
2673
+ this.root.value = void 0;
2674
+ this.root.hasValue = false;
2675
+ this._size = 0;
2676
+ }
2677
+ /** Depth-first iteration over `[path, value]` for every stored path. */
2678
+ *entries() {
2679
+ const stack = [{ node: this.root, path: [] }];
2680
+ while (stack.length > 0) {
2681
+ const frame = stack.pop();
2682
+ const { node, path } = frame;
2683
+ if (node.hasValue) {
2684
+ yield [path, node.value];
2685
+ }
2686
+ if (node.children) {
2687
+ for (const [segment, child] of node.children) {
2688
+ stack.push({ node: child, path: [...path, segment] });
2689
+ }
2690
+ }
2691
+ }
2692
+ }
2693
+ *keys() {
2694
+ for (const [path] of this.entries()) yield path;
2695
+ }
2696
+ *values() {
2697
+ for (const [, value] of this.entries()) yield value;
2698
+ }
2699
+ findNode(path) {
2700
+ let node = this.root;
2701
+ for (let i = 0; i < path.length; i++) {
2702
+ node = node.children?.get(path[i]);
2703
+ if (!node) return void 0;
2704
+ }
2705
+ return node;
2706
+ }
2707
+ };
2708
+
2709
+ // src/core/OIMIndexStoreTrieDrivenSetBased.ts
2710
+ var OIMIndexStoreTrieDrivenSetBased = class extends OIMIndexStoreSetBased {
2711
+ constructor() {
2712
+ super(...arguments);
2713
+ this.slots = new OIMTrieMap();
2714
+ this.reservedBuckets = new OIMTrieMap();
2715
+ }
2716
+ getOrReserveBucket(key) {
2717
+ const live = this.slots.get(key);
2718
+ if (live) return live;
2719
+ let reserved = this.reservedBuckets.get(key);
2720
+ if (!reserved) {
2721
+ reserved = new OIMKeyedBucketSetBased(key);
2722
+ this.reservedBuckets.set(key, reserved);
2723
+ }
2724
+ return reserved;
2725
+ }
2726
+ findBucket(key) {
2727
+ return this.slots.get(key) ?? this.reservedBuckets.get(key);
2728
+ }
2729
+ retainBucket(bucket) {
2730
+ if (bucket.size === 0) return;
2731
+ this.reservedBuckets.delete(bucket.key);
2732
+ if (!this.slots.has(bucket.key)) this.slots.set(bucket.key, bucket);
2733
+ }
2734
+ releaseBucket(bucket) {
2735
+ if (bucket.size > 0) return;
2736
+ this.slots.delete(bucket.key);
2737
+ if (bucket.hasSubscribers()) {
2738
+ this.reservedBuckets.set(bucket.key, bucket);
2739
+ } else {
2740
+ this.reservedBuckets.delete(bucket.key);
2741
+ }
2742
+ }
2743
+ dropIfReserved(key) {
2744
+ const reserved = this.reservedBuckets.get(key);
2745
+ if (reserved && reserved.size === 0 && !reserved.hasSubscribers()) {
2746
+ this.reservedBuckets.delete(key);
2747
+ }
2748
+ }
2749
+ setOneByKey(key, slots) {
2750
+ const bucket = this.getOrReserveBucket(key);
2751
+ if (bucket !== slots) {
2752
+ bucket.clear();
2753
+ for (const slot of slots) bucket.add(slot);
2754
+ }
2755
+ if (bucket.size > 0) this.retainBucket(bucket);
2756
+ else this.releaseBucket(bucket);
2757
+ }
2758
+ removeOneByKey(key) {
2759
+ this.slots.delete(key);
2760
+ this.reservedBuckets.delete(key);
2761
+ }
2762
+ removeManyByKeys(keys) {
2763
+ for (let i = 0; i < keys.length; i++) {
2764
+ this.slots.delete(keys[i]);
2765
+ this.reservedBuckets.delete(keys[i]);
2766
+ }
2767
+ }
2768
+ getOneByKey(key) {
2769
+ return this.slots.get(key);
2770
+ }
2771
+ getManyByKeys(keys) {
2772
+ const result = /* @__PURE__ */ new Map();
2773
+ for (let i = 0; i < keys.length; i++) {
2774
+ const slots = this.slots.get(keys[i]);
2775
+ if (slots !== void 0) result.set(keys[i], slots);
2776
+ }
2777
+ return result;
2778
+ }
2779
+ getAllKeys() {
2780
+ return Array.from(this.slots.keys());
2781
+ }
2782
+ getAll() {
2783
+ const result = /* @__PURE__ */ new Map();
2784
+ for (const [path, bucket] of this.slots.entries()) {
2785
+ result.set(path, bucket);
2786
+ }
2787
+ return result;
2788
+ }
2789
+ countAll() {
2790
+ return this.slots.size;
2791
+ }
2792
+ clear() {
2793
+ for (const bucket of this.slots.values()) {
2794
+ bucket.clear();
2795
+ if (bucket.hasSubscribers()) {
2796
+ this.reservedBuckets.set(bucket.key, bucket);
2797
+ }
2798
+ }
2799
+ this.slots.clear();
2800
+ }
2801
+ };
2802
+
2803
+ // src/core/OIMReactiveCollectionIndexCompositeTrieSetBased.ts
2804
+ var OIMReactiveCollectionIndexCompositeTrieSetBased = class extends OIMReactiveIndexManualSetBased {
2805
+ constructor(queue, opts) {
2806
+ const getSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.getSlot;
2807
+ super(queue, {
2808
+ indexOptions: {
2809
+ comparePks: opts.indexOptions?.comparePks,
2810
+ store: opts.indexOptions?.store ?? new OIMIndexStoreTrieDrivenSetBased()
2811
+ }
2812
+ });
2813
+ this.getSlotOrTransient = (pk) => {
2814
+ const slot = this.getSlot(pk);
2815
+ if (!slot) return { pk, item: void 0 };
2816
+ return slot;
2817
+ };
2818
+ this.getSlot = getSlot;
2819
+ this.findSlotForRemoval = opts.collection !== void 0 ? (pk) => opts.collection.findSlotByPk(pk) : opts.getSlot;
2820
+ }
2821
+ setPks(key, pks) {
2822
+ this.index.setPks(key, pks, this.getSlotOrTransient);
2823
+ }
2824
+ addPks(key, pks) {
2825
+ this.index.addPks(key, pks, this.getSlotOrTransient);
2826
+ }
2827
+ removePks(key, pks) {
2828
+ const canonical = [];
2829
+ for (let i = 0; i < pks.length; i++) {
2830
+ const slot = this.findSlotForRemoval(pks[i]);
2831
+ canonical.push(slot ? slot.pk : pks[i]);
2832
+ }
2833
+ this.index.removePks(key, canonical);
2834
+ }
2835
+ getEntitiesByKey(key) {
2836
+ return super.getEntitiesByKey(key);
2837
+ }
2838
+ getEntitiesByKeys(keys) {
2839
+ return super.getEntitiesByKeys(keys);
2840
+ }
2841
+ };
2842
+
2843
+ // src/core/OIMIndexStoreTrieDrivenArrayBased.ts
2844
+ var OIMIndexStoreTrieDrivenArrayBased = class extends OIMIndexStoreArrayBased {
2845
+ constructor() {
2846
+ super(...arguments);
2847
+ this.slots = new OIMTrieMap();
2848
+ this.reservedBuckets = new OIMTrieMap();
2849
+ }
2850
+ getOrReserveBucket(key) {
2851
+ const live = this.slots.get(key);
2852
+ if (live) return live;
2853
+ let reserved = this.reservedBuckets.get(key);
2854
+ if (!reserved) {
2855
+ reserved = new OIMKeyedBucketArrayBased(key);
2856
+ this.reservedBuckets.set(key, reserved);
2857
+ }
2858
+ return reserved;
2859
+ }
2860
+ findBucket(key) {
2861
+ return this.slots.get(key) ?? this.reservedBuckets.get(key);
2862
+ }
2863
+ retainBucket(bucket) {
2864
+ if (bucket.length === 0) return;
2865
+ this.reservedBuckets.delete(bucket.key);
2866
+ if (!this.slots.has(bucket.key)) this.slots.set(bucket.key, bucket);
2867
+ }
2868
+ releaseBucket(bucket) {
2869
+ if (bucket.length > 0) return;
2870
+ this.slots.delete(bucket.key);
2871
+ if (bucket.hasSubscribers()) {
2872
+ this.reservedBuckets.set(bucket.key, bucket);
2873
+ } else {
2874
+ this.reservedBuckets.delete(bucket.key);
2875
+ }
2876
+ }
2877
+ dropIfReserved(key) {
2878
+ const reserved = this.reservedBuckets.get(key);
2879
+ if (reserved && reserved.length === 0 && !reserved.hasSubscribers()) {
2880
+ this.reservedBuckets.delete(key);
2881
+ }
2882
+ }
2883
+ setOneByKey(key, slots) {
2884
+ const bucket = this.getOrReserveBucket(key);
2885
+ if (bucket !== slots) {
2886
+ bucket.length = 0;
2887
+ for (let i = 0; i < slots.length; i++) bucket.push(slots[i]);
2888
+ }
2889
+ if (bucket.length > 0) this.retainBucket(bucket);
2890
+ else this.releaseBucket(bucket);
2891
+ }
2892
+ removeOneByKey(key) {
2893
+ this.slots.delete(key);
2894
+ this.reservedBuckets.delete(key);
2895
+ }
2896
+ removeManyByKeys(keys) {
2897
+ for (let i = 0; i < keys.length; i++) {
2898
+ this.slots.delete(keys[i]);
2899
+ this.reservedBuckets.delete(keys[i]);
2900
+ }
2901
+ }
2902
+ getOneByKey(key) {
2903
+ return this.slots.get(key);
2904
+ }
2905
+ getManyByKeys(keys) {
2906
+ const result = /* @__PURE__ */ new Map();
2907
+ for (let i = 0; i < keys.length; i++) {
2908
+ const slots = this.slots.get(keys[i]);
2909
+ if (slots !== void 0) result.set(keys[i], slots);
2910
+ }
2911
+ return result;
2912
+ }
2913
+ getAllKeys() {
2914
+ return Array.from(this.slots.keys());
2915
+ }
2916
+ getAll() {
2917
+ const result = /* @__PURE__ */ new Map();
2918
+ for (const [path, bucket] of this.slots.entries()) {
2919
+ result.set(path, bucket);
2920
+ }
2921
+ return result;
2922
+ }
2923
+ countAll() {
2924
+ return this.slots.size;
2925
+ }
2926
+ clear() {
2927
+ for (const bucket of this.slots.values()) {
2928
+ bucket.length = 0;
2929
+ if (bucket.hasSubscribers()) {
2930
+ this.reservedBuckets.set(bucket.key, bucket);
2931
+ }
2932
+ }
2933
+ this.slots.clear();
2934
+ }
2935
+ };
2936
+
2937
+ // src/core/OIMReactiveCollectionIndexCompositeTrieArrayBased.ts
2938
+ var OIMReactiveCollectionIndexCompositeTrieArrayBased = class extends OIMReactiveIndexManualArrayBased {
2939
+ constructor(queue, opts) {
2940
+ const getSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.getSlot;
2941
+ super(queue, {
2942
+ indexOptions: {
2943
+ comparePks: opts.indexOptions?.comparePks,
2944
+ store: opts.indexOptions?.store ?? new OIMIndexStoreTrieDrivenArrayBased()
2945
+ }
2946
+ });
2947
+ // Persistent per-key-path pk membership for O(1) `addPks` dedup. Trie-keyed
2948
+ // so a rebuilt key path matches by content.
2949
+ this.pksByKey = new OIMTrieMap();
2950
+ this.getSlot = getSlot;
2951
+ }
2952
+ setPks(key, pks) {
2953
+ const pkSet = /* @__PURE__ */ new Set();
2954
+ for (let i = 0; i < pks.length; i++) pkSet.add(pks[i]);
2955
+ this.pksByKey.set(key, pkSet);
2956
+ super.setSlots(key, this.getSlotsOrTransient(pks));
2315
2957
  }
2316
2958
  addPks(key, pks) {
2317
2959
  if (pks.length === 0) return;
2318
- const map = this.membershipOf(key);
2960
+ const pkSet = this.membershipOf(key);
2319
2961
  const newSlots = [];
2320
2962
  for (const pk of pks) {
2321
- if (map.has(pk)) continue;
2322
- const slot = this.resolveRequiredSlot(pk);
2323
- map.set(pk, slot);
2324
- newSlots.push(slot);
2963
+ if (pkSet.has(pk)) continue;
2964
+ pkSet.add(pk);
2965
+ newSlots.push(this.getSlotOrTransient(pk));
2325
2966
  }
2326
- if (newSlots.length > 0) this.addSlots(key, newSlots);
2967
+ if (newSlots.length > 0) this.appendSlots(key, newSlots);
2327
2968
  }
2328
2969
  removePks(key, pks) {
2329
2970
  if (pks.length === 0) return;
2330
- const map = this.membershipOf(key);
2331
- if (map.size === 0) return;
2332
- const removed = [];
2333
- for (const pk of pks) {
2334
- const slot = map.get(pk);
2335
- if (slot) {
2336
- map.delete(pk);
2337
- removed.push(slot);
2338
- }
2971
+ const pksToRemove = new Set(pks);
2972
+ const existingSlots = this.index.getSlotsByKey(key);
2973
+ const nextSlots = existingSlots.filter(
2974
+ (slot) => !pksToRemove.has(slot.pk)
2975
+ );
2976
+ if (nextSlots.length === existingSlots.length) return;
2977
+ const pkSet = this.pksByKey.get(key);
2978
+ if (pkSet) {
2979
+ for (let i = 0; i < pks.length; i++) pkSet.delete(pks[i]);
2339
2980
  }
2340
- if (removed.length === 0) return;
2341
- if (map.size === 0) this.slotByPk.delete(key);
2342
- this.removeSlots(key, removed);
2981
+ if (nextSlots.length === 0) this.clear(key);
2982
+ else super.setSlots(key, nextSlots);
2343
2983
  }
2344
2984
  clear(key) {
2345
- if (key === void 0) this.slotByPk.clear();
2346
- else this.slotByPk.delete(key);
2985
+ if (key === void 0) this.pksByKey.clear();
2986
+ else this.pksByKey.delete(key);
2347
2987
  super.clear(key);
2348
2988
  }
2349
2989
  /**
2350
- * The pk→slot map for a key, lazily seeded from the current bucket so it
2351
- * stays correct even if slots were set through a lower-level path.
2990
+ * The membership set for a key path, lazily seeded from the current bucket so
2991
+ * it stays correct even if slots were set through a lower-level path.
2352
2992
  */
2353
2993
  membershipOf(key) {
2354
- let map = this.slotByPk.get(key);
2355
- if (!map) {
2356
- map = /* @__PURE__ */ new Map();
2994
+ let pkSet = this.pksByKey.get(key);
2995
+ if (!pkSet) {
2996
+ pkSet = /* @__PURE__ */ new Set();
2357
2997
  const existing = this.index.getSlotsByKey(key);
2358
- for (const slot of existing) map.set(slot.pk, slot);
2359
- this.slotByPk.set(key, map);
2998
+ for (let i = 0; i < existing.length; i++) {
2999
+ pkSet.add(existing[i].pk);
3000
+ }
3001
+ this.pksByKey.set(key, pkSet);
2360
3002
  }
2361
- return map;
3003
+ return pkSet;
2362
3004
  }
2363
3005
  getEntitiesByKey(key) {
2364
3006
  return super.getEntitiesByKey(key);
@@ -2366,8 +3008,16 @@ var OIMReactiveCollectionIndexManualSetBased = class extends OIMReactiveIndexMan
2366
3008
  getEntitiesByKeys(keys) {
2367
3009
  return super.getEntitiesByKeys(keys);
2368
3010
  }
2369
- resolveRequiredSlot(pk) {
2370
- const slot = this.resolveSlot(pk);
3011
+ getSlotsOrTransient(pks) {
3012
+ const slots = [];
3013
+ slots.length = pks.length;
3014
+ for (let i = 0; i < pks.length; i++) {
3015
+ slots[i] = this.getSlotOrTransient(pks[i]);
3016
+ }
3017
+ return slots;
3018
+ }
3019
+ getSlotOrTransient(pk) {
3020
+ const slot = this.getSlot(pk);
2371
3021
  if (!slot) return { pk, item: void 0 };
2372
3022
  return slot;
2373
3023
  }
@@ -2680,20 +3330,20 @@ var OIMReactiveGlobalIndexManualArrayBased = class extends OIMReactiveGlobalInde
2680
3330
  // src/core/OIMReactiveCollectionGlobalIndexManualArrayBased.ts
2681
3331
  var OIMReactiveCollectionGlobalIndexManualArrayBased = class extends OIMReactiveGlobalIndexManualArrayBased {
2682
3332
  constructor(queue, opts) {
2683
- const resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.resolveSlot;
3333
+ const getSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.getSlot;
2684
3334
  super(queue, { indexOptions: opts.indexOptions });
2685
3335
  // Single pk membership set for O(1) `addPks` dedup. Lazily seeded from the
2686
3336
  // bucket so it stays correct even after a raw `setSlots` (e.g. derived
2687
3337
  // rebuild), matching the keyed index's `membershipOf`.
2688
3338
  this.membership = /* @__PURE__ */ new Set();
2689
3339
  this.membershipSeeded = false;
2690
- this.resolveSlot = resolveSlot;
3340
+ this.getSlot = getSlot;
2691
3341
  }
2692
3342
  setPks(pks) {
2693
3343
  this.membership.clear();
2694
3344
  for (let i = 0; i < pks.length; i++) this.membership.add(pks[i]);
2695
3345
  this.membershipSeeded = true;
2696
- super.setSlots(this.resolveSlots(pks));
3346
+ super.setSlots(this.getSlotsOrTransient(pks));
2697
3347
  }
2698
3348
  addPks(pks) {
2699
3349
  if (pks.length === 0) return;
@@ -2702,7 +3352,7 @@ var OIMReactiveCollectionGlobalIndexManualArrayBased = class extends OIMReactive
2702
3352
  for (const pk of pks) {
2703
3353
  if (membership.has(pk)) continue;
2704
3354
  membership.add(pk);
2705
- newSlots.push(this.resolveRequiredSlot(pk));
3355
+ newSlots.push(this.getSlotOrTransient(pk));
2706
3356
  }
2707
3357
  if (newSlots.length > 0) super.appendSlots(newSlots);
2708
3358
  }
@@ -2739,16 +3389,16 @@ var OIMReactiveCollectionGlobalIndexManualArrayBased = class extends OIMReactive
2739
3389
  getEntities() {
2740
3390
  return super.getEntities();
2741
3391
  }
2742
- resolveSlots(pks) {
3392
+ getSlotsOrTransient(pks) {
2743
3393
  const slots = [];
2744
3394
  slots.length = pks.length;
2745
3395
  for (let i = 0; i < pks.length; i++) {
2746
- slots[i] = this.resolveRequiredSlot(pks[i]);
3396
+ slots[i] = this.getSlotOrTransient(pks[i]);
2747
3397
  }
2748
3398
  return slots;
2749
3399
  }
2750
- resolveRequiredSlot(pk) {
2751
- const slot = this.resolveSlot(pk);
3400
+ getSlotOrTransient(pk) {
3401
+ const slot = this.getSlot(pk);
2752
3402
  if (!slot) return { pk, item: void 0 };
2753
3403
  return slot;
2754
3404
  }
@@ -2828,18 +3478,18 @@ var OIMReactiveGlobalIndexManualSetBased = class extends OIMReactiveGlobalIndexS
2828
3478
  // src/core/OIMReactiveCollectionGlobalIndexManualSetBased.ts
2829
3479
  var OIMReactiveCollectionGlobalIndexManualSetBased = class extends OIMReactiveGlobalIndexManualSetBased {
2830
3480
  constructor(queue, opts) {
2831
- const resolveSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.resolveSlot;
3481
+ const getSlot = opts.collection !== void 0 ? (pk) => opts.collection.getOrReserveSlotByPk(pk) : opts.getSlot;
2832
3482
  super(queue, { indexOptions: opts.indexOptions });
2833
3483
  this.membership = /* @__PURE__ */ new Map();
2834
3484
  this.membershipSeeded = false;
2835
- this.resolveSlot = resolveSlot;
3485
+ this.getSlot = getSlot;
2836
3486
  }
2837
3487
  setPks(pks) {
2838
3488
  this.membership.clear();
2839
3489
  const slots = [];
2840
3490
  for (let i = 0; i < pks.length; i++) {
2841
3491
  const pk = pks[i];
2842
- const slot = this.resolveRequiredSlot(pk);
3492
+ const slot = this.getSlotOrTransient(pk);
2843
3493
  if (!this.membership.has(pk)) this.membership.set(pk, slot);
2844
3494
  slots.push(slot);
2845
3495
  }
@@ -2852,7 +3502,7 @@ var OIMReactiveCollectionGlobalIndexManualSetBased = class extends OIMReactiveGl
2852
3502
  const newSlots = [];
2853
3503
  for (const pk of pks) {
2854
3504
  if (membership.has(pk)) continue;
2855
- const slot = this.resolveRequiredSlot(pk);
3505
+ const slot = this.getSlotOrTransient(pk);
2856
3506
  membership.set(pk, slot);
2857
3507
  newSlots.push(slot);
2858
3508
  }
@@ -2891,8 +3541,8 @@ var OIMReactiveCollectionGlobalIndexManualSetBased = class extends OIMReactiveGl
2891
3541
  getEntities() {
2892
3542
  return super.getEntities();
2893
3543
  }
2894
- resolveRequiredSlot(pk) {
2895
- const slot = this.resolveSlot(pk);
3544
+ getSlotOrTransient(pk) {
3545
+ const slot = this.getSlot(pk);
2896
3546
  if (!slot) return { pk, item: void 0 };
2897
3547
  return slot;
2898
3548
  }
@@ -3071,6 +3721,30 @@ var OIMCollectionIndexFactory = class {
3071
3721
  indexOptions: opts.indexOptions
3072
3722
  });
3073
3723
  }
3724
+ /**
3725
+ * Composite (trie-backed) manual Set-based index keyed by an
3726
+ * arbitrary-length key path, e.g. `setPks([userId, projectId, role], pks)`.
3727
+ * Key paths are matched by content in O(arity) — no string concatenation,
3728
+ * no separator collisions, each segment keeps its type. Primitive-keyed
3729
+ * indexes are unaffected and keep their native-`Map` fast path.
3730
+ */
3731
+ compositeSetIndex(opts = {}) {
3732
+ return new OIMReactiveCollectionIndexCompositeTrieSetBased(this.queue, {
3733
+ collection: this.collection,
3734
+ indexOptions: opts.indexOptions
3735
+ });
3736
+ }
3737
+ /**
3738
+ * Composite (trie-backed) manual Array-based (ordered) index keyed by an
3739
+ * arbitrary-length key path, e.g. `setPks([channelId, threadId], pks)`.
3740
+ * Ordered counterpart of `compositeSetIndex`.
3741
+ */
3742
+ compositeArrayIndex(opts = {}) {
3743
+ return new OIMReactiveCollectionIndexCompositeTrieArrayBased(this.queue, {
3744
+ collection: this.collection,
3745
+ indexOptions: opts.indexOptions
3746
+ });
3747
+ }
3074
3748
  derivedSetIndex(selectIndexKeys, opts = {}) {
3075
3749
  return new OIMDerivedCollectionIndexSetBased(
3076
3750
  this.queue,
@@ -3277,7 +3951,7 @@ var OIMObject = class {
3277
3951
  var OIMReactiveObject = class extends OIMObject {
3278
3952
  constructor(queue, opts) {
3279
3953
  super(opts);
3280
- this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierResolver());
3954
+ this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierProvider());
3281
3955
  }
3282
3956
  /**
3283
3957
  * Mark the written key(s) on the keyed emitter directly — no general-event
@@ -3319,6 +3993,148 @@ var OIMReactiveObject = class extends OIMObject {
3319
3993
  }
3320
3994
  };
3321
3995
 
3996
+ // src/core/OIMCollectionStoreTrieDriven.ts
3997
+ var OIMCollectionStoreTrieDriven = class extends OIMCollectionStore {
3998
+ constructor() {
3999
+ super(...arguments);
4000
+ this.slots = new OIMTrieMap();
4001
+ this.reservedSlots = new OIMTrieMap();
4002
+ }
4003
+ setOneByPk(pk, entity) {
4004
+ const slot = this.slots.get(pk);
4005
+ if (slot) {
4006
+ slot.item = entity;
4007
+ return slot;
4008
+ }
4009
+ const reserved = this.reservedSlots.get(pk);
4010
+ if (reserved) {
4011
+ reserved.item = entity;
4012
+ this.reservedSlots.delete(pk);
4013
+ this.slots.set(pk, reserved);
4014
+ return reserved;
4015
+ }
4016
+ const nextSlot = {
4017
+ pk,
4018
+ item: entity
4019
+ };
4020
+ this.slots.set(pk, nextSlot);
4021
+ return nextSlot;
4022
+ }
4023
+ getOrReserveSlotByPk(pk) {
4024
+ const slot = this.slots.get(pk);
4025
+ if (slot) return slot;
4026
+ let reserved = this.reservedSlots.get(pk);
4027
+ if (!reserved) {
4028
+ reserved = { pk, item: void 0 };
4029
+ this.reservedSlots.set(pk, reserved);
4030
+ }
4031
+ return reserved;
4032
+ }
4033
+ setManyByPks(pks, entities) {
4034
+ const slots = [];
4035
+ slots.length = pks.length;
4036
+ for (let i = 0; i < pks.length; i++) {
4037
+ slots[i] = this.setOneByPk(pks[i], entities[i]);
4038
+ }
4039
+ return slots;
4040
+ }
4041
+ getSlotByPk(pk) {
4042
+ return this.slots.get(pk);
4043
+ }
4044
+ findSlotByPk(pk) {
4045
+ return this.slots.get(pk) ?? this.reservedSlots.get(pk);
4046
+ }
4047
+ hasSubscribers(slot) {
4048
+ return slot.subscribers !== void 0 && slot.subscribers.size > 0;
4049
+ }
4050
+ getSlotsByPks(pks) {
4051
+ const result = [];
4052
+ result.length = pks.length;
4053
+ let writeIndex = 0;
4054
+ for (let i = 0; i < pks.length; i++) {
4055
+ const slot = this.slots.get(pks[i]);
4056
+ if (slot !== void 0) result[writeIndex++] = slot;
4057
+ }
4058
+ result.length = writeIndex;
4059
+ return result;
4060
+ }
4061
+ getAllSlots() {
4062
+ return Array.from(this.slots.values());
4063
+ }
4064
+ removeOneByPk(pk) {
4065
+ const slot = this.slots.get(pk);
4066
+ if (slot) {
4067
+ slot.item = void 0;
4068
+ this.slots.delete(pk);
4069
+ if (this.hasSubscribers(slot)) {
4070
+ this.reservedSlots.set(pk, slot);
4071
+ }
4072
+ return;
4073
+ }
4074
+ const reserved = this.reservedSlots.get(pk);
4075
+ if (reserved) {
4076
+ reserved.item = void 0;
4077
+ if (!this.hasSubscribers(reserved)) {
4078
+ this.reservedSlots.delete(pk);
4079
+ }
4080
+ }
4081
+ }
4082
+ removeManyByPks(pks) {
4083
+ for (const pk of pks) this.removeOneByPk(pk);
4084
+ }
4085
+ getOneByPk(pk) {
4086
+ return this.slots.get(pk)?.item;
4087
+ }
4088
+ getManyByPks(pks) {
4089
+ const result = [];
4090
+ result.length = pks.length;
4091
+ let writeIndex = 0;
4092
+ for (let i = 0; i < pks.length; i++) {
4093
+ const entity = this.getOneByPk(pks[i]);
4094
+ if (entity !== void 0) result[writeIndex++] = entity;
4095
+ }
4096
+ result.length = writeIndex;
4097
+ return result;
4098
+ }
4099
+ getAll() {
4100
+ const result = [];
4101
+ for (const slot of this.slots.values()) {
4102
+ if (slot.item !== void 0) result.push(slot.item);
4103
+ }
4104
+ return result;
4105
+ }
4106
+ countAll() {
4107
+ return this.slots.size;
4108
+ }
4109
+ clear() {
4110
+ const retained = [];
4111
+ for (const slot of this.slots.values()) {
4112
+ slot.item = void 0;
4113
+ if (this.hasSubscribers(slot)) retained.push(slot);
4114
+ }
4115
+ this.slots.clear();
4116
+ for (const slot of this.reservedSlots.values()) {
4117
+ slot.item = void 0;
4118
+ if (this.hasSubscribers(slot)) retained.push(slot);
4119
+ }
4120
+ this.reservedSlots.clear();
4121
+ for (const slot of retained) {
4122
+ this.reservedSlots.set(slot.pk, slot);
4123
+ }
4124
+ }
4125
+ getAllPks() {
4126
+ return Array.from(this.slots.keys());
4127
+ }
4128
+ destroy() {
4129
+ this.clear();
4130
+ for (const slot of this.reservedSlots.values()) {
4131
+ slot.subscribers?.clear();
4132
+ slot.subscribers = void 0;
4133
+ }
4134
+ this.reservedSlots.clear();
4135
+ }
4136
+ };
4137
+
3322
4138
  // src/core/OIMObjectStoreMapDriven.ts
3323
4139
  var OIMObjectStoreMapDriven = class extends OIMObjectStore {
3324
4140
  constructor() {
@@ -3366,6 +4182,27 @@ var OIMObjectStoreMapDriven = class extends OIMObjectStore {
3366
4182
  }
3367
4183
  };
3368
4184
 
4185
+ // src/core/OIMKeyPathCarrierProvider.ts
4186
+ var OIMKeyPathCarrierProvider = class {
4187
+ constructor() {
4188
+ this.carriers = new OIMTrieMap();
4189
+ }
4190
+ getOrReserveCarrier(key) {
4191
+ let carrier = this.carriers.get(key);
4192
+ if (!carrier) {
4193
+ carrier = { key };
4194
+ this.carriers.set(key, carrier);
4195
+ }
4196
+ return carrier;
4197
+ }
4198
+ findCarrier(key) {
4199
+ return this.carriers.get(key);
4200
+ }
4201
+ onCarrierEmptied(carrier) {
4202
+ this.carriers.delete(carrier.key);
4203
+ }
4204
+ };
4205
+
3369
4206
  // src/core/OIMComparatorFactory.ts
3370
4207
  var OIMComparatorFactory = class {
3371
4208
  createShallowComparator() {
@@ -3584,6 +4421,103 @@ var OIMMap2Keys = class {
3584
4421
  }
3585
4422
  };
3586
4423
 
4424
+ // src/core/OIMDisposeScope.ts
4425
+ var OIMDisposeScope = class _OIMDisposeScope {
4426
+ constructor() {
4427
+ this.disposables = [];
4428
+ this.disposed = false;
4429
+ }
4430
+ /** Number of not-yet-disposed items currently held. */
4431
+ get size() {
4432
+ return this.disposables.length;
4433
+ }
4434
+ get isDisposed() {
4435
+ return this.disposed;
4436
+ }
4437
+ /**
4438
+ * Register a disposable (object with `destroy()` or an unsubscribe function)
4439
+ * and return it unchanged, so it can be captured inline. If the scope is
4440
+ * already disposed the item is disposed immediately, so nothing leaks.
4441
+ */
4442
+ add(disposable) {
4443
+ if (this.disposed) {
4444
+ this.disposeOne(disposable);
4445
+ return disposable;
4446
+ }
4447
+ this.disposables.push(disposable);
4448
+ return disposable;
4449
+ }
4450
+ /** A nested scope, itself registered here — dispose it early or with the parent. */
4451
+ child() {
4452
+ return this.add(new _OIMDisposeScope());
4453
+ }
4454
+ /**
4455
+ * Dispose everything in reverse registration order, then clear. Idempotent.
4456
+ * If any `destroy()`/unsubscribe throws, the rest are still disposed and the
4457
+ * first error is rethrown once teardown completes.
4458
+ */
4459
+ destroy() {
4460
+ if (this.disposed) return;
4461
+ this.disposed = true;
4462
+ let firstError;
4463
+ let hasError = false;
4464
+ for (let i = this.disposables.length - 1; i >= 0; i--) {
4465
+ try {
4466
+ this.disposeOne(this.disposables[i]);
4467
+ } catch (error) {
4468
+ if (!hasError) {
4469
+ hasError = true;
4470
+ firstError = error;
4471
+ }
4472
+ }
4473
+ }
4474
+ this.disposables.length = 0;
4475
+ if (hasError) throw firstError;
4476
+ }
4477
+ disposeOne(disposable) {
4478
+ if (typeof disposable === "function") disposable();
4479
+ else disposable.destroy();
4480
+ }
4481
+ };
4482
+ function createOIMDisposeScope() {
4483
+ return new OIMDisposeScope();
4484
+ }
4485
+
4486
+ // src/core/OIMTrieSet.ts
4487
+ var OIMTrieSet = class {
4488
+ constructor() {
4489
+ this.map = new OIMTrieMap();
4490
+ }
4491
+ get size() {
4492
+ return this.map.size;
4493
+ }
4494
+ add(key) {
4495
+ this.map.set(key, true);
4496
+ }
4497
+ has(key) {
4498
+ return this.map.has(key);
4499
+ }
4500
+ delete(key) {
4501
+ return this.map.delete(key);
4502
+ }
4503
+ *values() {
4504
+ yield* this.map.keys();
4505
+ }
4506
+ clear() {
4507
+ this.map.clear();
4508
+ }
4509
+ };
4510
+
4511
+ // src/core/OIMPkCodecKeyPath.ts
4512
+ var OIMPkCodecKeyPath = class {
4513
+ encode(pk) {
4514
+ return JSON.stringify(pk);
4515
+ }
4516
+ decode(encoded) {
4517
+ return JSON.parse(encoded);
4518
+ }
4519
+ };
4520
+
3587
4521
  // src/modules/compute/core/OIMComputeRuntime.ts
3588
4522
  var OIMComputeRuntime = class {
3589
4523
  constructor(queue) {
@@ -4187,6 +5121,27 @@ var OIMCollectionSelectors = class {
4187
5121
  key
4188
5122
  );
4189
5123
  }
5124
+ /**
5125
+ * Entities of a composite (key-path) set-based index for one key path, e.g.
5126
+ * `entitiesByCompositeSetIndexKey(index, [projectId, role])`.
5127
+ */
5128
+ entitiesByCompositeSetIndexKey(index, key) {
5129
+ return new OIMEntitiesByIndexKeySetBasedSelector(
5130
+ this.runtime,
5131
+ this.collection,
5132
+ index,
5133
+ key
5134
+ );
5135
+ }
5136
+ /** Entities of a composite (key-path) array-based index for one key path. */
5137
+ entitiesByCompositeArrayIndexKey(index, key) {
5138
+ return new OIMEntitiesByIndexKeyArrayBasedSelector(
5139
+ this.runtime,
5140
+ this.collection,
5141
+ index,
5142
+ key
5143
+ );
5144
+ }
4190
5145
  /** Entities of a keyless (whole-collection) array-based index, ordered. */
4191
5146
  entitiesByArrayGlobalIndex(index) {
4192
5147
  return new OIMEntitiesByGlobalIndexArrayBasedSelector(
@@ -4210,7 +5165,16 @@ function createOIMCollectionKit(queue, opts) {
4210
5165
  const collection = new OIMReactiveCollection(queue, opts);
4211
5166
  const indexFactory = new OIMCollectionIndexFactory(queue, collection);
4212
5167
  const select = new OIMCollectionSelectors(queue, collection);
4213
- return { queue, collection, indexFactory, select };
5168
+ const scope = new OIMDisposeScope();
5169
+ scope.add(collection);
5170
+ return {
5171
+ queue,
5172
+ collection,
5173
+ indexFactory,
5174
+ select,
5175
+ scope,
5176
+ destroy: () => scope.destroy()
5177
+ };
4214
5178
  }
4215
5179
 
4216
5180
  // src/modules/wrapper/collection/OIMCollectionChangedFields.ts
@@ -5191,35 +6155,20 @@ var OIMComputed = class {
5191
6155
  };
5192
6156
 
5193
6157
  // src/modules/effect/core/OIMEffectDependencyKeyed.ts
5194
- function isReadonlyArray(value) {
5195
- return Array.isArray(value);
5196
- }
5197
6158
  var OIMEffectDependencyKeyed = class {
5198
- constructor(source, keyOrKeys) {
6159
+ constructor(source, key) {
5199
6160
  this.source = source;
5200
- if (isReadonlyArray(keyOrKeys)) {
5201
- this.keys = keyOrKeys;
5202
- } else {
5203
- this.key = keyOrKeys;
5204
- }
6161
+ this.key = key;
5205
6162
  }
5206
6163
  subscribe(onUpdate) {
5207
- if (this.keys !== void 0) {
5208
- return this.source.subscribeOnKeys(this.keys, onUpdate);
5209
- }
5210
- if (this.key === void 0) return () => {
5211
- };
5212
6164
  return this.source.subscribeOnKey(this.key, onUpdate);
5213
6165
  }
5214
6166
  };
5215
6167
 
5216
6168
  // src/modules/effect/core/OIMEffectDependencyKeyedObject.ts
5217
6169
  var OIMEffectDependencyKeyedObject = class {
5218
- constructor(object, keyOrKeys) {
5219
- this.dep = new OIMEffectDependencyKeyed(
5220
- object,
5221
- keyOrKeys
5222
- );
6170
+ constructor(object, key) {
6171
+ this.dep = new OIMEffectDependencyKeyed(object, key);
5223
6172
  }
5224
6173
  get source() {
5225
6174
  return this.dep.source;
@@ -5231,11 +6180,8 @@ var OIMEffectDependencyKeyedObject = class {
5231
6180
 
5232
6181
  // src/modules/effect/core/OIMEffectDependencyKeyedCollection.ts
5233
6182
  var OIMEffectDependencyKeyedCollection = class {
5234
- constructor(collection, pkOrPks) {
5235
- this.dep = new OIMEffectDependencyKeyed(
5236
- collection,
5237
- pkOrPks
5238
- );
6183
+ constructor(collection, pk) {
6184
+ this.dep = new OIMEffectDependencyKeyed(collection, pk);
5239
6185
  }
5240
6186
  get source() {
5241
6187
  return this.dep.source;
@@ -5247,11 +6193,8 @@ var OIMEffectDependencyKeyedCollection = class {
5247
6193
 
5248
6194
  // src/modules/effect/core/OIMEffectDependencyKeyedIndex.ts
5249
6195
  var OIMEffectDependencyKeyedIndex = class {
5250
- constructor(index, keyOrKeys) {
5251
- this.dep = new OIMEffectDependencyKeyed(
5252
- index,
5253
- keyOrKeys
5254
- );
6196
+ constructor(index, key) {
6197
+ this.dep = new OIMEffectDependencyKeyed(index, key);
5255
6198
  }
5256
6199
  get source() {
5257
6200
  return this.dep.source;
@@ -5398,6 +6341,7 @@ export {
5398
6341
  EOIMEventQueueSchedulerEventType,
5399
6342
  EOIMIndexEventType,
5400
6343
  EOIMObjectEventType,
6344
+ OIMBucketCarrierProvider,
5401
6345
  OIMCarrierKeyedEmitter,
5402
6346
  OIMCarrierSingleEmitter,
5403
6347
  OIMCollection,
@@ -5410,6 +6354,7 @@ export {
5410
6354
  OIMCollectionSelectors,
5411
6355
  OIMCollectionStore,
5412
6356
  OIMCollectionStoreMapDriven,
6357
+ OIMCollectionStoreTrieDriven,
5413
6358
  OIMComparatorFactory,
5414
6359
  OIMComputeRuntime,
5415
6360
  OIMComputed,
@@ -5418,6 +6363,7 @@ export {
5418
6363
  OIMDerivedCollectionGlobalIndexSetBased,
5419
6364
  OIMDerivedCollectionIndexArrayBased,
5420
6365
  OIMDerivedCollectionIndexSetBased,
6366
+ OIMDisposeScope,
5421
6367
  OIMEffect,
5422
6368
  OIMEffectDependencyComputed,
5423
6369
  OIMEffectDependencyKeyed,
@@ -5460,6 +6406,11 @@ export {
5460
6406
  OIMIndexStoreMapDrivenArrayBased,
5461
6407
  OIMIndexStoreMapDrivenSetBased,
5462
6408
  OIMIndexStoreSetBased,
6409
+ OIMIndexStoreTrieDrivenArrayBased,
6410
+ OIMIndexStoreTrieDrivenSetBased,
6411
+ OIMKeyPathCarrierProvider,
6412
+ OIMKeyedBucketArrayBased,
6413
+ OIMKeyedBucketSetBased,
5463
6414
  OIMMap2Keys,
5464
6415
  OIMObject,
5465
6416
  OIMObjectStore,
@@ -5471,10 +6422,13 @@ export {
5471
6422
  OIMOrderedListCommandStream,
5472
6423
  OIMOrderedListCommandStreamDiffDriven,
5473
6424
  OIMOrderedListMappedCommandStream,
6425
+ OIMPkCodecKeyPath,
5474
6426
  OIMPkSelectorFactory,
5475
6427
  OIMReactiveCollection,
5476
6428
  OIMReactiveCollectionGlobalIndexManualArrayBased,
5477
6429
  OIMReactiveCollectionGlobalIndexManualSetBased,
6430
+ OIMReactiveCollectionIndexCompositeTrieArrayBased,
6431
+ OIMReactiveCollectionIndexCompositeTrieSetBased,
5478
6432
  OIMReactiveCollectionIndexManualArrayBased,
5479
6433
  OIMReactiveCollectionIndexManualSetBased,
5480
6434
  OIMReactiveGlobalIndex,
@@ -5489,10 +6443,13 @@ export {
5489
6443
  OIMReactiveIndexSetBased,
5490
6444
  OIMReactiveObject,
5491
6445
  OIMSelector,
6446
+ OIMTrieMap,
6447
+ OIMTrieSet,
5492
6448
  createInPlaceEntityUpdater,
5493
6449
  createMergeEntityUpdater,
5494
6450
  createOIMCollectionIndexFactory,
5495
6451
  createOIMCollectionKit,
6452
+ createOIMDisposeScope,
5496
6453
  createOIMGlobalIndexSlotMap,
5497
6454
  createOIMIndexSlotMap,
5498
6455
  createOIMOrderedListCommandStreamDiffDriven,