@oimdb/core 1.4.0 → 1.5.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
@@ -22,17 +22,18 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  EOIMCollectionEventType: () => EOIMCollectionEventType,
24
24
  EOIMComputedEventType: () => EOIMComputedEventType,
25
- EOIMEffectPhase: () => EOIMEffectPhase,
26
25
  EOIMEventQueueEventType: () => EOIMEventQueueEventType,
27
26
  EOIMEventQueueSchedulerEventType: () => EOIMEventQueueSchedulerEventType,
28
27
  EOIMIndexEventType: () => EOIMIndexEventType,
29
28
  EOIMObjectEventType: () => EOIMObjectEventType,
30
- EOIMUpdateEventCoalescerEventType: () => EOIMUpdateEventCoalescerEventType,
31
29
  OIMCollection: () => OIMCollection,
30
+ OIMCollectionByPkSelector: () => OIMCollectionByPkSelector,
31
+ OIMCollectionByPksSelector: () => OIMCollectionByPksSelector,
32
32
  OIMCollectionChangedFieldsWrapper: () => OIMCollectionChangedFieldsWrapper,
33
33
  OIMCollectionStore: () => OIMCollectionStore,
34
34
  OIMCollectionStoreMapDriven: () => OIMCollectionStoreMapDriven,
35
35
  OIMComparatorFactory: () => OIMComparatorFactory,
36
+ OIMComputativeRuntime: () => OIMComputativeRuntime,
36
37
  OIMComputed: () => OIMComputed,
37
38
  OIMDBSettings: () => OIMDBSettings,
38
39
  OIMEffect: () => OIMEffect,
@@ -41,6 +42,8 @@ __export(index_exports, {
41
42
  OIMEffectDependencyKeyedCollection: () => OIMEffectDependencyKeyedCollection,
42
43
  OIMEffectDependencyKeyedIndex: () => OIMEffectDependencyKeyedIndex,
43
44
  OIMEffectDependencyKeyedObject: () => OIMEffectDependencyKeyedObject,
45
+ OIMEntitiesByIndexKeyArrayBasedSelector: () => OIMEntitiesByIndexKeyArrayBasedSelector,
46
+ OIMEntitiesByIndexKeySetBasedSelector: () => OIMEntitiesByIndexKeySetBasedSelector,
44
47
  OIMEntityUpdaterFactory: () => OIMEntityUpdaterFactory,
45
48
  OIMEventEmitter: () => OIMEventEmitter,
46
49
  OIMEventQueue: () => OIMEventQueue,
@@ -55,6 +58,10 @@ __export(index_exports, {
55
58
  OIMIndexManualArrayBased: () => OIMIndexManualArrayBased,
56
59
  OIMIndexManualOrderedArrayBased: () => OIMIndexManualOrderedArrayBased,
57
60
  OIMIndexManualSetBased: () => OIMIndexManualSetBased,
61
+ OIMIndexPksByKeyArrayBasedSelector: () => OIMIndexPksByKeyArrayBasedSelector,
62
+ OIMIndexPksByKeySetBasedSelector: () => OIMIndexPksByKeySetBasedSelector,
63
+ OIMIndexPksByKeysArrayBasedSelector: () => OIMIndexPksByKeysArrayBasedSelector,
64
+ OIMIndexPksByKeysSetBasedSelector: () => OIMIndexPksByKeysSetBasedSelector,
58
65
  OIMIndexSetBased: () => OIMIndexSetBased,
59
66
  OIMIndexStoreArrayBased: () => OIMIndexStoreArrayBased,
60
67
  OIMIndexStoreMapDrivenArrayBased: () => OIMIndexStoreMapDrivenArrayBased,
@@ -65,6 +72,8 @@ __export(index_exports, {
65
72
  OIMObjectStore: () => OIMObjectStore,
66
73
  OIMObjectStoreMapDriven: () => OIMObjectStoreMapDriven,
67
74
  OIMObjectStoreRecordDriven: () => OIMObjectStoreRecordDriven,
75
+ OIMObjectValueByKeySelector: () => OIMObjectValueByKeySelector,
76
+ OIMObjectValuesByKeysSelector: () => OIMObjectValuesByKeysSelector,
68
77
  OIMOrderedListIndexCommandStreamWrapper: () => OIMOrderedListIndexCommandStreamWrapper,
69
78
  OIMPkSelectorFactory: () => OIMPkSelectorFactory,
70
79
  OIMRICollection: () => OIMRICollection,
@@ -74,13 +83,7 @@ __export(index_exports, {
74
83
  OIMReactiveIndexManualSetBased: () => OIMReactiveIndexManualSetBased,
75
84
  OIMReactiveIndexSetBased: () => OIMReactiveIndexSetBased,
76
85
  OIMReactiveObject: () => OIMReactiveObject,
77
- OIMUpdateEventCoalescer: () => OIMUpdateEventCoalescer,
78
- OIMUpdateEventCoalescerCollection: () => OIMUpdateEventCoalescerCollection,
79
- OIMUpdateEventCoalescerComputed: () => OIMUpdateEventCoalescerComputed,
80
- OIMUpdateEventCoalescerIndex: () => OIMUpdateEventCoalescerIndex,
81
- OIMUpdateEventCoalescerManual: () => OIMUpdateEventCoalescerManual,
82
- OIMUpdateEventCoalescerObject: () => OIMUpdateEventCoalescerObject,
83
- OIMUpdateEventEmitter: () => OIMUpdateEventEmitter
86
+ OIMSelector: () => OIMSelector
84
87
  });
85
88
  module.exports = __toCommonJS(index_exports);
86
89
 
@@ -101,6 +104,11 @@ var OIMEventEmitter = class {
101
104
  constructor() {
102
105
  this.buckets = {};
103
106
  }
107
+ hasHandlers(event) {
108
+ const bucket = this.buckets[event];
109
+ if (!bucket) return false;
110
+ return bucket.indexByHandler.size > 0;
111
+ }
104
112
  getOrCreateBucket(event) {
105
113
  let bucket = this.buckets[event];
106
114
  if (!bucket) {
@@ -231,58 +239,75 @@ var EOIMIndexEventType = /* @__PURE__ */ ((EOIMIndexEventType2) => {
231
239
  var OIMIndexStoreMapDrivenSetBased = class extends OIMIndexStoreSetBased {
232
240
  constructor() {
233
241
  super(...arguments);
234
- this.pks = /* @__PURE__ */ new Map();
242
+ this.slots = /* @__PURE__ */ new Map();
235
243
  }
236
- setOneByKey(key, pks) {
237
- this.pks.set(key, pks);
244
+ setOneByKey(key, slots) {
245
+ this.slots.set(key, slots);
238
246
  }
239
247
  removeOneByKey(key) {
240
- this.pks.delete(key);
248
+ this.slots.delete(key);
241
249
  }
242
250
  removeManyByKeys(keys) {
243
251
  for (const key of keys) {
244
- this.pks.delete(key);
252
+ this.slots.delete(key);
245
253
  }
246
254
  }
247
255
  getOneByKey(key) {
248
- return this.pks.get(key);
256
+ return this.slots.get(key);
249
257
  }
250
258
  getManyByKeys(keys) {
251
259
  const result = /* @__PURE__ */ new Map();
252
260
  for (const key of keys) {
253
- const pks = this.getOneByKey(key);
254
- if (pks !== void 0) {
255
- result.set(key, pks);
261
+ const slots = this.getOneByKey(key);
262
+ if (slots !== void 0) {
263
+ result.set(key, slots);
256
264
  }
257
265
  }
258
266
  return result;
259
267
  }
260
268
  getAllKeys() {
261
- return Array.from(this.pks.keys());
269
+ return Array.from(this.slots.keys());
262
270
  }
263
271
  getAll() {
264
- return new Map(this.pks);
272
+ return new Map(this.slots);
265
273
  }
266
274
  countAll() {
267
- return this.pks.size;
275
+ return this.slots.size;
268
276
  }
269
277
  clear() {
270
- this.pks.clear();
278
+ this.slots.clear();
271
279
  }
272
280
  };
273
281
 
274
282
  // src/abstract/OIMIndexSetBased.ts
275
283
  var OIMIndexSetBased = class {
276
284
  constructor(options = {}) {
285
+ this.fallbackSlots = /* @__PURE__ */ new Map();
277
286
  this.emitter = new OIMEventEmitter();
278
287
  this.comparePks = options.comparePks;
279
288
  this.store = options.store ?? new OIMIndexStoreMapDrivenSetBased();
289
+ this.resolveSlot = options.resolveSlot;
280
290
  }
281
291
  /**
282
292
  * Get primary keys for multiple index keys
283
293
  */
284
294
  getPksByKeys(keys) {
285
- return this.store.getManyByKeys(keys);
295
+ const result = /* @__PURE__ */ new Map();
296
+ const slotsByKey = this.store.getManyByKeys(keys);
297
+ for (const [key, slots] of slotsByKey) {
298
+ result.set(key, this.slotsToPks(slots));
299
+ }
300
+ return result;
301
+ }
302
+ setSlotResolver(resolveSlot) {
303
+ this.resolveSlot = resolveSlot;
304
+ const allSlots = this.store.getAll();
305
+ for (const [key, slots] of allSlots) {
306
+ this.store.setOneByKey(
307
+ key,
308
+ this.resolveSlots(this.slotsToPks(slots))
309
+ );
310
+ }
286
311
  }
287
312
  /**
288
313
  * @deprecated Use getPksByKey instead
@@ -295,8 +320,15 @@ var OIMIndexSetBased = class {
295
320
  * Get primary keys for a specific index key
296
321
  */
297
322
  getPksByKey(key) {
298
- const pksSet = this.store.getOneByKey(key);
299
- return pksSet ? pksSet : /* @__PURE__ */ new Set();
323
+ const slotsSet = this.store.getOneByKey(key);
324
+ return slotsSet ? this.slotsToPks(slotsSet) : /* @__PURE__ */ new Set();
325
+ }
326
+ getSlotsByKey(key) {
327
+ const slotsSet = this.store.getOneByKey(key);
328
+ return slotsSet ? slotsSet : /* @__PURE__ */ new Set();
329
+ }
330
+ getSlotsByKeys(keys) {
331
+ return this.store.getManyByKeys(keys);
300
332
  }
301
333
  /**
302
334
  * Check if an index key exists
@@ -314,8 +346,8 @@ var OIMIndexSetBased = class {
314
346
  * Get the number of primary keys for a specific index key
315
347
  */
316
348
  getKeySize(key) {
317
- const pksSet = this.store.getOneByKey(key);
318
- return pksSet ? pksSet.size : 0;
349
+ const slotsSet = this.store.getOneByKey(key);
350
+ return slotsSet ? slotsSet.size : 0;
319
351
  }
320
352
  /**
321
353
  * Get total number of index keys
@@ -337,10 +369,10 @@ var OIMIndexSetBased = class {
337
369
  let maxBucketSize = 0;
338
370
  let minBucketSize = Infinity;
339
371
  const allPks = this.store.getAll();
340
- for (const pksSet of allPks.values()) {
341
- totalPks += pksSet.size;
342
- maxBucketSize = Math.max(maxBucketSize, pksSet.size);
343
- minBucketSize = Math.min(minBucketSize, pksSet.size);
372
+ for (const slotsSet of allPks.values()) {
373
+ totalPks += slotsSet.size;
374
+ maxBucketSize = Math.max(maxBucketSize, slotsSet.size);
375
+ minBucketSize = Math.min(minBucketSize, slotsSet.size);
344
376
  }
345
377
  return {
346
378
  totalKeys: allPks.size,
@@ -362,92 +394,147 @@ var OIMIndexSetBased = class {
362
394
  * If comparator is provided and returns true (no changes), skip the update.
363
395
  */
364
396
  setPksWithComparison(key, newPks) {
397
+ return this.setSlotsWithComparison(key, this.resolveSlots(newPks));
398
+ }
399
+ setSlotsWithComparison(key, newSlots) {
365
400
  if (this.comparePks) {
366
- const existingPksSet = this.store.getOneByKey(key);
367
- if (existingPksSet && existingPksSet.size === newPks.size) {
368
- const existingPksArray = existingPksSet.size > 0 ? [...existingPksSet] : [];
369
- const newPksArray = newPks.size > 0 ? [...newPks] : [];
401
+ const existingSlotsSet = this.store.getOneByKey(key);
402
+ if (existingSlotsSet && existingSlotsSet.size === newSlots.size) {
403
+ const existingPksArray = Array.from(
404
+ this.slotsToPks(existingSlotsSet)
405
+ );
406
+ const newPksArray = Array.from(this.slotsToPks(newSlots));
370
407
  if (this.comparePks(existingPksArray, newPksArray)) {
371
408
  return false;
372
409
  }
373
- } else if (!existingPksSet && newPks.size === 0) {
410
+ } else if (!existingSlotsSet && newSlots.size === 0) {
374
411
  return false;
375
412
  }
376
413
  }
377
- this.store.setOneByKey(key, newPks);
414
+ this.store.setOneByKey(key, newSlots);
378
415
  return true;
379
416
  }
417
+ resolveSlots(pks) {
418
+ const slots = /* @__PURE__ */ new Set();
419
+ for (const pk of pks) slots.add(this.getOrCreateSlot(pk));
420
+ return slots;
421
+ }
422
+ getOrCreateSlot(pk) {
423
+ const resolved = this.resolveSlot?.(pk);
424
+ if (resolved) return resolved;
425
+ let fallback = this.fallbackSlots.get(pk);
426
+ if (!fallback) {
427
+ fallback = { pk, item: void 0 };
428
+ this.fallbackSlots.set(pk, fallback);
429
+ }
430
+ return fallback;
431
+ }
432
+ slotsToPks(slots) {
433
+ const pks = /* @__PURE__ */ new Set();
434
+ for (const slot of slots) pks.add(slot.pk);
435
+ return pks;
436
+ }
380
437
  /**
381
438
  * Emit update event with changed keys
382
439
  */
383
440
  emitUpdate(keys) {
384
441
  this.emitter.emit(0 /* UPDATE */, { keys });
385
442
  }
443
+ /**
444
+ * Emit update event for a single key (fast-path override point).
445
+ */
446
+ emitUpdateOne(key) {
447
+ this.emitUpdate([key]);
448
+ }
386
449
  };
387
450
 
388
451
  // src/core/OIMIndexStoreMapDrivenArrayBased.ts
389
452
  var OIMIndexStoreMapDrivenArrayBased = class extends OIMIndexStoreArrayBased {
390
453
  constructor() {
391
454
  super(...arguments);
392
- this.pks = /* @__PURE__ */ new Map();
455
+ this.slots = /* @__PURE__ */ new Map();
393
456
  }
394
- setOneByKey(key, pks) {
395
- this.pks.set(key, pks);
457
+ setOneByKey(key, slots) {
458
+ this.slots.set(key, slots);
396
459
  }
397
460
  removeOneByKey(key) {
398
- this.pks.delete(key);
461
+ this.slots.delete(key);
399
462
  }
400
463
  removeManyByKeys(keys) {
401
464
  for (const key of keys) {
402
- this.pks.delete(key);
465
+ this.slots.delete(key);
403
466
  }
404
467
  }
405
468
  getOneByKey(key) {
406
- return this.pks.get(key);
469
+ return this.slots.get(key);
407
470
  }
408
471
  getManyByKeys(keys) {
409
472
  const result = /* @__PURE__ */ new Map();
410
473
  for (const key of keys) {
411
- const pks = this.getOneByKey(key);
412
- if (pks !== void 0) {
413
- result.set(key, pks);
474
+ const slots = this.getOneByKey(key);
475
+ if (slots !== void 0) {
476
+ result.set(key, slots);
414
477
  }
415
478
  }
416
479
  return result;
417
480
  }
418
481
  getAllKeys() {
419
- return Array.from(this.pks.keys());
482
+ return Array.from(this.slots.keys());
420
483
  }
421
484
  getAll() {
422
- return new Map(this.pks);
485
+ return new Map(this.slots);
423
486
  }
424
487
  countAll() {
425
- return this.pks.size;
488
+ return this.slots.size;
426
489
  }
427
490
  clear() {
428
- this.pks.clear();
491
+ this.slots.clear();
429
492
  }
430
493
  };
431
494
 
432
495
  // src/abstract/OIMIndexArrayBased.ts
433
496
  var OIMIndexArrayBased = class {
434
497
  constructor(options = {}) {
498
+ this.fallbackSlots = /* @__PURE__ */ new Map();
435
499
  this.emitter = new OIMEventEmitter();
436
500
  this.comparePks = options.comparePks;
437
501
  this.store = options.store ?? new OIMIndexStoreMapDrivenArrayBased();
502
+ this.resolveSlot = options.resolveSlot;
438
503
  }
439
504
  /**
440
505
  * Get primary keys for multiple index keys
441
506
  */
442
507
  getPksByKeys(keys) {
443
- return this.store.getManyByKeys(keys);
508
+ const result = /* @__PURE__ */ new Map();
509
+ const slotsByKey = this.store.getManyByKeys(keys);
510
+ for (const [key, slots] of slotsByKey) {
511
+ result.set(key, this.slotsToPks(slots));
512
+ }
513
+ return result;
514
+ }
515
+ setSlotResolver(resolveSlot) {
516
+ this.resolveSlot = resolveSlot;
517
+ const allSlots = this.store.getAll();
518
+ for (const [key, slots] of allSlots) {
519
+ this.store.setOneByKey(
520
+ key,
521
+ slots.map((slot) => this.getOrCreateSlot(slot.pk))
522
+ );
523
+ }
444
524
  }
445
525
  /**
446
526
  * Get primary keys for a specific index key
447
527
  */
448
528
  getPksByKey(key) {
449
- const pksArray = this.store.getOneByKey(key);
450
- return pksArray ? pksArray : [];
529
+ const slotsArray = this.store.getOneByKey(key);
530
+ return slotsArray ? this.slotsToPks(slotsArray) : [];
531
+ }
532
+ getSlotsByKey(key) {
533
+ const slotsArray = this.store.getOneByKey(key);
534
+ return slotsArray ? slotsArray : [];
535
+ }
536
+ getSlotsByKeys(keys) {
537
+ return this.store.getManyByKeys(keys);
451
538
  }
452
539
  /**
453
540
  * Check if an index key exists
@@ -465,8 +552,8 @@ var OIMIndexArrayBased = class {
465
552
  * Get the number of primary keys for a specific index key
466
553
  */
467
554
  getKeySize(key) {
468
- const pksArray = this.store.getOneByKey(key);
469
- return pksArray ? pksArray.length : 0;
555
+ const slotsArray = this.store.getOneByKey(key);
556
+ return slotsArray ? slotsArray.length : 0;
470
557
  }
471
558
  /**
472
559
  * Get total number of index keys
@@ -488,10 +575,10 @@ var OIMIndexArrayBased = class {
488
575
  let maxBucketSize = 0;
489
576
  let minBucketSize = Infinity;
490
577
  const allPks = this.store.getAll();
491
- for (const pksArray of allPks.values()) {
492
- totalPks += pksArray.length;
493
- maxBucketSize = Math.max(maxBucketSize, pksArray.length);
494
- minBucketSize = Math.min(minBucketSize, pksArray.length);
578
+ for (const slotsArray of allPks.values()) {
579
+ totalPks += slotsArray.length;
580
+ maxBucketSize = Math.max(maxBucketSize, slotsArray.length);
581
+ minBucketSize = Math.min(minBucketSize, slotsArray.length);
495
582
  }
496
583
  return {
497
584
  totalKeys: allPks.size,
@@ -513,64 +600,83 @@ var OIMIndexArrayBased = class {
513
600
  * If comparator is provided and returns true (no changes), skip the update.
514
601
  */
515
602
  setPksWithComparison(key, newPks) {
603
+ return this.setSlotsWithComparison(key, this.resolveSlots(newPks));
604
+ }
605
+ setSlotsWithComparison(key, newSlots) {
516
606
  if (this.comparePks) {
517
- const existingPksArray = this.store.getOneByKey(key);
518
- if (existingPksArray && existingPksArray.length === newPks.length) {
519
- if (this.comparePks(existingPksArray, newPks)) {
607
+ const existingSlotsArray = this.store.getOneByKey(key);
608
+ if (existingSlotsArray && existingSlotsArray.length === newSlots.length) {
609
+ if (this.comparePks(
610
+ this.slotsToPks(existingSlotsArray),
611
+ this.slotsToPks(newSlots)
612
+ )) {
520
613
  return false;
521
614
  }
522
- } else if (!existingPksArray && newPks.length === 0) {
615
+ } else if (!existingSlotsArray && newSlots.length === 0) {
523
616
  return false;
524
617
  }
525
618
  }
526
- this.store.setOneByKey(key, newPks);
619
+ this.store.setOneByKey(key, newSlots);
527
620
  return true;
528
621
  }
622
+ resolveSlots(pks) {
623
+ const slots = [];
624
+ slots.length = pks.length;
625
+ for (let i = 0; i < pks.length; i++) {
626
+ slots[i] = this.getOrCreateSlot(pks[i]);
627
+ }
628
+ return slots;
629
+ }
630
+ getOrCreateSlot(pk) {
631
+ const resolved = this.resolveSlot?.(pk);
632
+ if (resolved) return resolved;
633
+ let fallback = this.fallbackSlots.get(pk);
634
+ if (!fallback) {
635
+ fallback = { pk, item: void 0 };
636
+ this.fallbackSlots.set(pk, fallback);
637
+ }
638
+ return fallback;
639
+ }
640
+ slotsToPks(slots) {
641
+ const pks = [];
642
+ pks.length = slots.length;
643
+ for (let i = 0; i < slots.length; i++) pks[i] = slots[i].pk;
644
+ return pks;
645
+ }
529
646
  /**
530
647
  * Emit update event with changed keys
531
648
  */
532
649
  emitUpdate(keys) {
533
650
  this.emitter.emit(0 /* UPDATE */, { keys });
534
651
  }
652
+ /**
653
+ * Emit update event for a single key (fast-path override point).
654
+ */
655
+ emitUpdateOne(key) {
656
+ this.emitUpdate([key]);
657
+ }
535
658
  };
536
659
 
537
- // src/enum/EOIMUpdateEventCoalescerEventType.ts
538
- var EOIMUpdateEventCoalescerEventType = /* @__PURE__ */ ((EOIMUpdateEventCoalescerEventType2) => {
539
- EOIMUpdateEventCoalescerEventType2[EOIMUpdateEventCoalescerEventType2["HAS_CHANGES"] = 0] = "HAS_CHANGES";
540
- EOIMUpdateEventCoalescerEventType2[EOIMUpdateEventCoalescerEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
541
- EOIMUpdateEventCoalescerEventType2[EOIMUpdateEventCoalescerEventType2["AFTER_FLUSH"] = 2] = "AFTER_FLUSH";
542
- return EOIMUpdateEventCoalescerEventType2;
543
- })(EOIMUpdateEventCoalescerEventType || {});
660
+ // src/enum/EOIMEventQueueEventType.ts
661
+ var EOIMEventQueueEventType = /* @__PURE__ */ ((EOIMEventQueueEventType2) => {
662
+ EOIMEventQueueEventType2[EOIMEventQueueEventType2["AFTER_FLUSH"] = 0] = "AFTER_FLUSH";
663
+ EOIMEventQueueEventType2[EOIMEventQueueEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
664
+ return EOIMEventQueueEventType2;
665
+ })(EOIMEventQueueEventType || {});
544
666
 
545
667
  // src/core/OIMUpdateEventEmitter.ts
546
668
  var OIMUpdateEventEmitter = class {
547
- constructor(opts) {
548
- this.keyHandlersBeforeFlush = /* @__PURE__ */ new Map();
669
+ constructor(queue, deliveryMode = "queue") {
549
670
  this.keyHandlers = /* @__PURE__ */ new Map();
550
- this.isDestroyed = false;
551
- this.isDispatching = false;
552
- this.isInPrePhase = false;
553
- this.hasPendingDuringPrePhase = false;
554
671
  this.isFlushEnqueued = false;
555
- this.handleHasChanges = () => {
556
- if (this.isDispatching && this.isInPrePhase) {
557
- this.hasPendingDuringPrePhase = true;
558
- return;
559
- }
560
- if (this.isFlushEnqueued) return;
561
- this.isFlushEnqueued = true;
562
- this.queue.enqueue(this.onFlush);
563
- };
672
+ this.updatedKeys = /* @__PURE__ */ new Set();
673
+ this.isAfterFlushDeliveryScheduled = false;
564
674
  // Stable handler reference to avoid allocating a new closure on every HAS_CHANGES.
565
675
  this.onFlush = () => {
566
676
  this.isFlushEnqueued = false;
567
- if (this.isDestroyed) {
568
- this.coalescer.clearUpdatedKeys();
569
- this.coalescer.clearFlushingUpdatedKeys();
570
- return;
571
- }
572
- if (this.keyHandlers.size === 0 && this.keyHandlersBeforeFlush.size === 0) {
573
- this.coalescer.clearUpdatedKeys();
677
+ this.dequeueFlush = void 0;
678
+ if (this.keyHandlers.size === 0) {
679
+ this.updatedKeys.clear();
574
680
  return;
575
681
  }
576
682
  const makeHandlerSnapshot = (handlers) => {
@@ -578,95 +684,133 @@ var OIMUpdateEventEmitter = class {
578
684
  handlers.forEach((h) => snapshot.push(h));
579
685
  return snapshot;
580
686
  };
581
- try {
582
- this.isDispatching = true;
583
- this.isInPrePhase = true;
584
- this.hasPendingDuringPrePhase = false;
585
- const allKeysForNormalPhase = this.runPrePhaseDrain(makeHandlerSnapshot);
586
- this.isInPrePhase = false;
587
- this.runHandlersPhase(allKeysForNormalPhase, makeHandlerSnapshot);
588
- } finally {
589
- this.isInPrePhase = false;
590
- this.isDispatching = false;
591
- }
687
+ this.runSinglePass(makeHandlerSnapshot);
592
688
  };
593
- this.coalescer = opts.coalescer;
594
- this.coalescer.emitter.on(
595
- 0 /* HAS_CHANGES */,
596
- this.handleHasChanges
597
- );
598
- this.queue = opts.queue;
689
+ this.onAfterFlush = () => {
690
+ if (!this.isAfterFlushDeliveryScheduled) return;
691
+ this.isAfterFlushDeliveryScheduled = false;
692
+ this.deliverImmediate();
693
+ };
694
+ this.queue = queue;
695
+ this.deliveryMode = deliveryMode;
696
+ if (this.deliveryMode === "after_flush") {
697
+ this.unsubscribeAfterFlushDelivery = this.queue.emitter.on(
698
+ 0 /* AFTER_FLUSH */,
699
+ this.onAfterFlush
700
+ );
701
+ }
599
702
  }
600
703
  /**
601
- * Helps to run computed values etc.
704
+ * Mark keys as updated and schedule delivery.
705
+ *
706
+ * If there are no subscriptions, this is a no-op and does not allocate or retain keys.
602
707
  */
603
- runPrePhaseDrain(makeHandlerSnapshot) {
604
- const allKeysForNormalPhase = /* @__PURE__ */ new Set();
605
- let hasMorePreWork = true;
606
- while (hasMorePreWork) {
607
- this.coalescer.markUpdatedKeysAsFlushing();
608
- const flushingKeys = this.coalescer.getFlushingUpdatedKeys();
609
- flushingKeys.forEach((key) => allKeysForNormalPhase.add(key));
610
- try {
611
- if (this.keyHandlersBeforeFlush.size > 0) {
612
- if (flushingKeys.size * 2 < this.keyHandlersBeforeFlush.size) {
613
- flushingKeys.forEach((key) => {
614
- if (this.isDestroyed) return;
615
- const handlers = this.keyHandlersBeforeFlush.get(key);
616
- if (!handlers || handlers.size === 0) return;
617
- const handlerSnapshot = makeHandlerSnapshot(handlers);
618
- for (let i = 0; i < handlerSnapshot.length; i++) {
619
- if (this.isDestroyed) return;
620
- const handler = handlerSnapshot[i];
621
- if (handlers.has(handler)) handler();
622
- }
623
- });
624
- } else {
625
- this.keyHandlersBeforeFlush.forEach((handlers, key) => {
626
- if (this.isDestroyed) return;
627
- if (!flushingKeys.has(key)) return;
628
- if (!handlers || handlers.size === 0) return;
629
- const handlerSnapshot = makeHandlerSnapshot(handlers);
630
- for (let i = 0; i < handlerSnapshot.length; i++) {
631
- if (this.isDestroyed) return;
632
- const handler = handlerSnapshot[i];
633
- if (handlers.has(handler)) handler();
634
- }
635
- });
636
- }
637
- }
638
- } finally {
639
- this.coalescer.clearFlushingUpdatedKeys();
640
- }
641
- const nextHasMorePreWork = this.hasPendingDuringPrePhase || this.coalescer.getUpdatedKeys().size > 0;
642
- this.hasPendingDuringPrePhase = false;
643
- hasMorePreWork = nextHasMorePreWork;
708
+ markUpdatedKeys(keys) {
709
+ if (keys.length === 0) return;
710
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
711
+ throw new Error(
712
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
713
+ );
714
+ }
715
+ if (this.keyHandlers.size === 0) return;
716
+ let didAddAny = false;
717
+ for (let i = 0; i < keys.length; i++) {
718
+ const key = keys[i];
719
+ const handlers = this.keyHandlers.get(key);
720
+ if (!handlers || handlers.size === 0) continue;
721
+ this.updatedKeys.add(key);
722
+ didAddAny = true;
723
+ }
724
+ if (!didAddAny) return;
725
+ if (this.deliveryMode === "immediate") {
726
+ this.deliverImmediate();
727
+ return;
728
+ }
729
+ if (this.deliveryMode === "after_flush") {
730
+ this.isAfterFlushDeliveryScheduled = true;
731
+ return;
732
+ }
733
+ if (this.isFlushEnqueued) return;
734
+ this.isFlushEnqueued = true;
735
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
736
+ }
737
+ markUpdatedKey(key) {
738
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
739
+ throw new Error(
740
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
741
+ );
742
+ }
743
+ if (this.keyHandlers.size === 0) return;
744
+ const handlers = this.keyHandlers.get(key);
745
+ if (!handlers || handlers.size === 0) return;
746
+ this.updatedKeys.add(key);
747
+ if (this.deliveryMode === "immediate") {
748
+ this.deliverImmediate();
749
+ return;
750
+ }
751
+ if (this.deliveryMode === "after_flush") {
752
+ this.isAfterFlushDeliveryScheduled = true;
753
+ return;
644
754
  }
645
- return allKeysForNormalPhase;
755
+ if (this.isFlushEnqueued) return;
756
+ this.isFlushEnqueued = true;
757
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
646
758
  }
647
- runHandlersPhase(allKeysForNormalPhase, makeHandlerSnapshot) {
648
- if (this.keyHandlers.size === 0 || allKeysForNormalPhase.size === 0)
759
+ markAllUpdated() {
760
+ if (this.deliveryMode === "queue" && this.queue.isInFlush) {
761
+ throw new Error(
762
+ "OIMUpdateEventEmitter: updates during queue.flush() are not allowed. Finish all writes before calling queue.flush()."
763
+ );
764
+ }
765
+ if (this.keyHandlers.size === 0) return;
766
+ this.keyHandlers.forEach((_handlers, key) => {
767
+ this.updatedKeys.add(key);
768
+ });
769
+ if (this.deliveryMode === "immediate") {
770
+ this.deliverImmediate();
771
+ return;
772
+ }
773
+ if (this.deliveryMode === "after_flush") {
774
+ this.isAfterFlushDeliveryScheduled = true;
649
775
  return;
650
- if (allKeysForNormalPhase.size * 2 < this.keyHandlers.size) {
651
- allKeysForNormalPhase.forEach((key) => {
652
- if (this.isDestroyed) return;
776
+ }
777
+ if (this.isFlushEnqueued) return;
778
+ this.isFlushEnqueued = true;
779
+ this.dequeueFlush = this.queue.enqueue(this.onFlush);
780
+ }
781
+ deliverImmediate() {
782
+ const makeHandlerSnapshot = (handlers) => {
783
+ const snapshot = [];
784
+ handlers.forEach((h) => snapshot.push(h));
785
+ return snapshot;
786
+ };
787
+ this.runSinglePass(makeHandlerSnapshot);
788
+ }
789
+ /**
790
+ * Deliver updated keys and notify handlers once.
791
+ * Re-entrant updates during delivery are forbidden and will throw in markUpdatedKeys().
792
+ */
793
+ runSinglePass(makeHandlerSnapshot) {
794
+ if (this.updatedKeys.size === 0) return;
795
+ const flushingKeys = this.updatedKeys;
796
+ this.updatedKeys = /* @__PURE__ */ new Set();
797
+ if (this.keyHandlers.size === 0 || flushingKeys.size === 0) return;
798
+ if (flushingKeys.size * 2 < this.keyHandlers.size) {
799
+ flushingKeys.forEach((key) => {
653
800
  const handlers = this.keyHandlers.get(key);
654
801
  if (!handlers || handlers.size === 0) return;
655
802
  const handlerSnapshot = makeHandlerSnapshot(handlers);
656
803
  for (let i = 0; i < handlerSnapshot.length; i++) {
657
- if (this.isDestroyed) return;
658
804
  const handler = handlerSnapshot[i];
659
805
  if (handlers.has(handler)) handler();
660
806
  }
661
807
  });
662
808
  } else {
663
809
  this.keyHandlers.forEach((handlers, key) => {
664
- if (this.isDestroyed) return;
665
- if (!allKeysForNormalPhase.has(key)) return;
810
+ if (!flushingKeys.has(key)) return;
666
811
  if (!handlers || handlers.size === 0) return;
667
812
  const handlerSnapshot = makeHandlerSnapshot(handlers);
668
813
  for (let i = 0; i < handlerSnapshot.length; i++) {
669
- if (this.isDestroyed) return;
670
814
  const handler = handlerSnapshot[i];
671
815
  if (handlers.has(handler)) handler();
672
816
  }
@@ -674,14 +818,17 @@ var OIMUpdateEventEmitter = class {
674
818
  }
675
819
  }
676
820
  destroy() {
677
- if (this.isDestroyed) return;
678
- this.isDestroyed = true;
679
- this.coalescer.emitter.off(
680
- 0 /* HAS_CHANGES */,
681
- this.handleHasChanges
682
- );
683
- this.keyHandlersBeforeFlush.clear();
821
+ if (this.dequeueFlush) {
822
+ this.dequeueFlush();
823
+ this.dequeueFlush = void 0;
824
+ }
825
+ if (this.unsubscribeAfterFlushDelivery) {
826
+ this.unsubscribeAfterFlushDelivery();
827
+ this.unsubscribeAfterFlushDelivery = void 0;
828
+ }
829
+ this.keyHandlers.forEach((handlers) => handlers.clear());
684
830
  this.keyHandlers.clear();
831
+ this.updatedKeys.clear();
685
832
  }
686
833
  /**
687
834
  * Get performance metrics for monitoring and debugging
@@ -702,32 +849,13 @@ var OIMUpdateEventEmitter = class {
702
849
  * Check if there are any active subscriptions
703
850
  */
704
851
  hasSubscriptions() {
705
- return this.keyHandlers.size > 0 || this.keyHandlersBeforeFlush.size > 0;
852
+ return this.keyHandlers.size > 0;
706
853
  }
707
854
  /**
708
855
  * Get the number of handlers for a specific key
709
856
  */
710
857
  getHandlerCount(key) {
711
- return (this.keyHandlers.get(key)?.size ?? 0) + (this.keyHandlersBeforeFlush.get(key)?.size ?? 0);
712
- }
713
- subscribeOnKeyBeforeFlush(key, handler) {
714
- let handlers = this.keyHandlersBeforeFlush.get(key);
715
- if (!handlers) {
716
- handlers = /* @__PURE__ */ new Set();
717
- this.keyHandlersBeforeFlush.set(key, handlers);
718
- }
719
- handlers.add(handler);
720
- return () => {
721
- this.unsubscribeFromKeyBeforeFlush(key, handler);
722
- };
723
- }
724
- unsubscribeFromKeyBeforeFlush(key, handler) {
725
- const handlers = this.keyHandlersBeforeFlush.get(key);
726
- if (!handlers) return;
727
- handlers.delete(handler);
728
- if (handlers.size === 0) {
729
- this.keyHandlersBeforeFlush.delete(key);
730
- }
858
+ return this.keyHandlers.get(key)?.size ?? 0;
731
859
  }
732
860
  subscribeOnKey(key, handler) {
733
861
  let handlers = this.keyHandlers.get(key);
@@ -746,6 +874,15 @@ var OIMUpdateEventEmitter = class {
746
874
  handlers.delete(handler);
747
875
  if (handlers.size === 0) {
748
876
  this.keyHandlers.delete(key);
877
+ this.updatedKeys.delete(key);
878
+ }
879
+ if (this.keyHandlers.size === 0) {
880
+ this.updatedKeys.clear();
881
+ if (this.dequeueFlush) {
882
+ this.dequeueFlush();
883
+ this.dequeueFlush = void 0;
884
+ }
885
+ this.isFlushEnqueued = false;
749
886
  }
750
887
  }
751
888
  subscribeOnKeys(keys, handler) {
@@ -767,30 +904,6 @@ var OIMUpdateEventEmitter = class {
767
904
  }
768
905
  };
769
906
  }
770
- subscribeOnKeysBeforeFlush(keys, handler) {
771
- const subscribedKeys = [];
772
- for (const key of keys) {
773
- let handlers = this.keyHandlersBeforeFlush.get(key);
774
- if (!handlers) {
775
- handlers = /* @__PURE__ */ new Set();
776
- this.keyHandlersBeforeFlush.set(key, handlers);
777
- }
778
- if (!handlers.has(handler)) {
779
- handlers.add(handler);
780
- subscribedKeys.push(key);
781
- }
782
- }
783
- return () => {
784
- for (const key of subscribedKeys) {
785
- this.unsubscribeFromKeyBeforeFlush(key, handler);
786
- }
787
- };
788
- }
789
- unsubscribeFromKeysBeforeFlush(keys, handler) {
790
- for (const key of keys) {
791
- this.unsubscribeFromKeyBeforeFlush(key, handler);
792
- }
793
- }
794
907
  unsubscribeFromKeys(keys, handler) {
795
908
  for (const key of keys) {
796
909
  this.unsubscribeFromKey(key, handler);
@@ -798,203 +911,11 @@ var OIMUpdateEventEmitter = class {
798
911
  }
799
912
  };
800
913
 
801
- // src/core/OIMIndexManualSetBased.ts
802
- var OIMIndexManualSetBased = class extends OIMIndexSetBased {
803
- constructor(options = {}) {
804
- super(options);
805
- }
806
- /**
807
- * Set primary keys for a specific index key, replacing any existing values.
808
- * Uses optional comparator to skip updates if PKs haven't actually changed.
809
- */
810
- setPks(key, pks) {
811
- const hasChanges = this.setPksWithComparison(key, new Set(pks));
812
- if (hasChanges) {
813
- this.emitUpdate([key]);
814
- }
815
- }
816
- /**
817
- * Add primary keys to a specific index key
818
- */
819
- addPks(key, pks) {
820
- if (pks.length === 0) return;
821
- let pksSet = this.store.getOneByKey(key);
822
- if (!pksSet) {
823
- pksSet = /* @__PURE__ */ new Set();
824
- this.store.setOneByKey(key, pksSet);
825
- }
826
- let hasChanges = false;
827
- for (const pk of pks) {
828
- if (!pksSet.has(pk)) {
829
- pksSet.add(pk);
830
- hasChanges = true;
831
- }
832
- }
833
- if (hasChanges) {
834
- this.store.setOneByKey(key, pksSet);
835
- this.emitUpdate([key]);
836
- }
837
- }
838
- /**
839
- * Remove primary keys from a specific index key
840
- */
841
- removePks(key, pks) {
842
- if (pks.length === 0) return;
843
- const pksSet = this.store.getOneByKey(key);
844
- if (!pksSet) return;
845
- let hasChanges = false;
846
- for (const pk of pks) {
847
- if (pksSet.delete(pk)) {
848
- hasChanges = true;
849
- }
850
- }
851
- if (pksSet.size === 0) {
852
- this.store.removeOneByKey(key);
853
- hasChanges = true;
854
- } else if (hasChanges) {
855
- this.store.setOneByKey(key, pksSet);
856
- }
857
- if (hasChanges) {
858
- this.emitUpdate([key]);
859
- }
860
- }
861
- /**
862
- * Clear all primary keys for a specific index key, or all keys if no key specified
863
- */
864
- clear(key) {
865
- if (key === void 0) {
866
- const allKeys = this.store.getAllKeys();
867
- if (allKeys.length > 0) {
868
- this.store.clear();
869
- this.emitUpdate(allKeys);
870
- }
871
- } else {
872
- if (this.store.getOneByKey(key) !== void 0) {
873
- this.store.removeOneByKey(key);
874
- this.emitUpdate([key]);
875
- }
876
- }
877
- }
878
- };
879
-
880
- // src/const/OIMDBSettings.ts
881
- var OIMDBSettings = class {
882
- };
883
- /**
884
- * Fixed key used in the manual index to store updated keys set in coalescers.
885
- * This constant is exported so users can create their own indexes and pass them to coalescers.
886
- */
887
- OIMDBSettings.UPDATED_KEYS_INDEX_KEY = "__updatedKeys__";
888
- OIMDBSettings.FLUSHING_KEYS_INDEX_KEY = "__flushingKeys__";
889
-
890
- // src/core/OIMUpdateEventCoalescer.ts
891
- var OIMUpdateEventCoalescer = class {
892
- constructor(index, flushingIndex) {
893
- this.emitter = new OIMEventEmitter();
894
- this.hasEmittedChanges = false;
895
- this.updatedKeysIndex = index ?? new OIMIndexManualSetBased({
896
- store: new OIMIndexStoreMapDrivenSetBased()
897
- });
898
- this.flushingUpdatedKeysIndex = flushingIndex ?? new OIMIndexManualSetBased({
899
- store: new OIMIndexStoreMapDrivenSetBased()
900
- });
901
- if (!this.updatedKeysIndex.hasKey(OIMDBSettings.UPDATED_KEYS_INDEX_KEY)) {
902
- this.updatedKeysIndex.setPks(
903
- OIMDBSettings.UPDATED_KEYS_INDEX_KEY,
904
- []
905
- );
906
- }
907
- }
908
- /**
909
- * Get the set of keys that have been updated since last clear
910
- */
911
- getUpdatedKeys() {
912
- return this.updatedKeysIndex.getPksByKey(
913
- OIMDBSettings.UPDATED_KEYS_INDEX_KEY
914
- );
915
- }
916
- getFlushingUpdatedKeys() {
917
- return this.flushingUpdatedKeysIndex.getPksByKey(
918
- OIMDBSettings.FLUSHING_KEYS_INDEX_KEY
919
- );
920
- }
921
- markUpdatedKeysAsFlushing() {
922
- this.flushingUpdatedKeysIndex.setPks(
923
- OIMDBSettings.FLUSHING_KEYS_INDEX_KEY,
924
- Array.from(this.getUpdatedKeys())
925
- );
926
- this.clearUpdatedKeys();
927
- }
928
- /**
929
- * Clear all updated keys and reset the changed state
930
- */
931
- clearUpdatedKeys() {
932
- this.emitter.emit(
933
- 1 /* BEFORE_FLUSH */,
934
- void 0
935
- );
936
- this.updatedKeysIndex.setPks(OIMDBSettings.UPDATED_KEYS_INDEX_KEY, []);
937
- this.hasEmittedChanges = false;
938
- this.emitter.emit(
939
- 2 /* AFTER_FLUSH */,
940
- void 0
941
- );
942
- }
943
- clearFlushingUpdatedKeys() {
944
- this.flushingUpdatedKeysIndex.setPks(
945
- OIMDBSettings.FLUSHING_KEYS_INDEX_KEY,
946
- []
947
- );
948
- }
949
- /**
950
- * Add keys to the updated set and emit HAS_CHANGES event if needed
951
- */
952
- addUpdatedKeys(keys) {
953
- this.updatedKeysIndex.addPks(
954
- OIMDBSettings.UPDATED_KEYS_INDEX_KEY,
955
- keys
956
- );
957
- if (!this.hasEmittedChanges) {
958
- this.hasEmittedChanges = true;
959
- this.emitter.emit(
960
- 0 /* HAS_CHANGES */,
961
- void 0
962
- );
963
- }
964
- }
965
- };
966
-
967
- // src/core/OIMUpdateEventCoalescerIndex.ts
968
- var OIMUpdateEventCoalescerIndex = class extends OIMUpdateEventCoalescer {
969
- constructor(indexEmitter) {
970
- super();
971
- this.indexEmitter = indexEmitter;
972
- /**
973
- * When the index is updated, we keep track of the updated keys.
974
- */
975
- this.handleUpdate = (payload) => {
976
- this.addUpdatedKeys(payload.keys);
977
- };
978
- this.indexEmitter.on(0 /* UPDATE */, this.handleUpdate);
979
- }
980
- destroy() {
981
- this.indexEmitter.off(0 /* UPDATE */, this.handleUpdate);
982
- this.emitter.offAll();
983
- this.clearUpdatedKeys();
984
- }
985
- };
986
-
987
914
  // src/abstract/OIMReactiveIndexSetBased.ts
988
915
  var OIMReactiveIndexSetBased = class {
989
- constructor(queue, opts) {
990
- this.index = opts?.index ?? this.createDefaultIndex();
991
- this.coalescer = new OIMUpdateEventCoalescerIndex(
992
- this.index.emitter
993
- );
994
- this.updateEventEmitter = new OIMUpdateEventEmitter({
995
- coalescer: this.coalescer,
996
- queue
997
- });
916
+ constructor(queue, createIndex) {
917
+ this.updateEmitter = new OIMUpdateEventEmitter(queue);
918
+ this.index = createIndex(this.updateEmitter);
998
919
  }
999
920
  getPksByKey(key) {
1000
921
  return this.index.getPksByKey(key);
@@ -1002,6 +923,12 @@ var OIMReactiveIndexSetBased = class {
1002
923
  getPksByKeys(keys) {
1003
924
  return this.index.getPksByKeys(keys);
1004
925
  }
926
+ getSlotsByKey(key) {
927
+ return this.index.getSlotsByKey(key);
928
+ }
929
+ getSlotsByKeys(keys) {
930
+ return this.index.getSlotsByKeys(keys);
931
+ }
1005
932
  hasKey(key) {
1006
933
  return this.index.hasKey(key);
1007
934
  }
@@ -1020,24 +947,38 @@ var OIMReactiveIndexSetBased = class {
1020
947
  getMetrics() {
1021
948
  return this.index.getMetrics();
1022
949
  }
1023
- destroy() {
1024
- this.updateEventEmitter.destroy();
1025
- this.coalescer.destroy();
1026
- this.index.destroy();
950
+ subscribeOnKey(key, handler) {
951
+ return this.updateEmitter.subscribeOnKey(key, handler);
1027
952
  }
1028
- };
1029
-
953
+ subscribeOnKeys(keys, handler) {
954
+ return this.updateEmitter.subscribeOnKeys(keys, handler);
955
+ }
956
+ unsubscribeFromKey(key, handler) {
957
+ this.updateEmitter.unsubscribeFromKey(key, handler);
958
+ }
959
+ unsubscribeFromKeys(keys, handler) {
960
+ this.updateEmitter.unsubscribeFromKeys(keys, handler);
961
+ }
962
+ hasSubscriptions() {
963
+ return this.updateEmitter.hasSubscriptions();
964
+ }
965
+ getHandlerCount(key) {
966
+ return this.updateEmitter.getHandlerCount(key);
967
+ }
968
+ getSubscriptionMetrics() {
969
+ return this.updateEmitter.getMetrics();
970
+ }
971
+ destroy() {
972
+ this.updateEmitter.destroy();
973
+ this.index.destroy();
974
+ }
975
+ };
976
+
1030
977
  // src/abstract/OIMReactiveIndexArrayBased.ts
1031
978
  var OIMReactiveIndexArrayBased = class {
1032
- constructor(queue, opts) {
1033
- this.index = opts?.index ?? this.createDefaultIndex();
1034
- this.coalescer = new OIMUpdateEventCoalescerIndex(
1035
- this.index.emitter
1036
- );
1037
- this.updateEventEmitter = new OIMUpdateEventEmitter({
1038
- coalescer: this.coalescer,
1039
- queue
1040
- });
979
+ constructor(queue, createIndex) {
980
+ this.updateEmitter = new OIMUpdateEventEmitter(queue);
981
+ this.index = createIndex(this.updateEmitter);
1041
982
  }
1042
983
  getPksByKey(key) {
1043
984
  return this.index.getPksByKey(key);
@@ -1045,6 +986,12 @@ var OIMReactiveIndexArrayBased = class {
1045
986
  getPksByKeys(keys) {
1046
987
  return this.index.getPksByKeys(keys);
1047
988
  }
989
+ getSlotsByKey(key) {
990
+ return this.index.getSlotsByKey(key);
991
+ }
992
+ getSlotsByKeys(keys) {
993
+ return this.index.getSlotsByKeys(keys);
994
+ }
1048
995
  hasKey(key) {
1049
996
  return this.index.hasKey(key);
1050
997
  }
@@ -1063,9 +1010,29 @@ var OIMReactiveIndexArrayBased = class {
1063
1010
  getMetrics() {
1064
1011
  return this.index.getMetrics();
1065
1012
  }
1013
+ subscribeOnKey(key, handler) {
1014
+ return this.updateEmitter.subscribeOnKey(key, handler);
1015
+ }
1016
+ subscribeOnKeys(keys, handler) {
1017
+ return this.updateEmitter.subscribeOnKeys(keys, handler);
1018
+ }
1019
+ unsubscribeFromKey(key, handler) {
1020
+ this.updateEmitter.unsubscribeFromKey(key, handler);
1021
+ }
1022
+ unsubscribeFromKeys(keys, handler) {
1023
+ this.updateEmitter.unsubscribeFromKeys(keys, handler);
1024
+ }
1025
+ hasSubscriptions() {
1026
+ return this.updateEmitter.hasSubscriptions();
1027
+ }
1028
+ getHandlerCount(key) {
1029
+ return this.updateEmitter.getHandlerCount(key);
1030
+ }
1031
+ getSubscriptionMetrics() {
1032
+ return this.updateEmitter.getMetrics();
1033
+ }
1066
1034
  destroy() {
1067
- this.updateEventEmitter.destroy();
1068
- this.coalescer.destroy();
1035
+ this.updateEmitter.destroy();
1069
1036
  this.index.destroy();
1070
1037
  }
1071
1038
  };
@@ -1085,26 +1052,52 @@ var OIMPkSelectorFactory = class {
1085
1052
  var OIMCollectionStoreMapDriven = class extends OIMCollectionStore {
1086
1053
  constructor() {
1087
1054
  super(...arguments);
1088
- this.entities = /* @__PURE__ */ new Map();
1055
+ this.slots = /* @__PURE__ */ new Map();
1089
1056
  }
1090
1057
  setOneByPk(pk, entity) {
1091
- this.entities.set(pk, entity);
1058
+ const slot = this.slots.get(pk);
1059
+ if (slot) {
1060
+ slot.item = entity;
1061
+ } else {
1062
+ this.slots.set(pk, { pk, item: entity });
1063
+ }
1092
1064
  }
1093
1065
  setManyByPks(pks, entities) {
1094
1066
  for (let i = 0; i < pks.length; i++) {
1095
- this.entities.set(pks[i], entities[i]);
1067
+ this.setOneByPk(pks[i], entities[i]);
1068
+ }
1069
+ }
1070
+ getSlotByPk(pk) {
1071
+ return this.slots.get(pk);
1072
+ }
1073
+ getSlotsByPks(pks) {
1074
+ const result = [];
1075
+ result.length = pks.length;
1076
+ let writeIndex = 0;
1077
+ for (let i = 0; i < pks.length; i++) {
1078
+ const slot = this.slots.get(pks[i]);
1079
+ if (slot !== void 0) {
1080
+ result[writeIndex++] = slot;
1081
+ }
1096
1082
  }
1083
+ result.length = writeIndex;
1084
+ return result;
1085
+ }
1086
+ getAllSlots() {
1087
+ return Array.from(this.slots.values());
1097
1088
  }
1098
1089
  removeOneByPk(pk) {
1099
- this.entities.delete(pk);
1090
+ const slot = this.slots.get(pk);
1091
+ if (slot) slot.item = void 0;
1092
+ this.slots.delete(pk);
1100
1093
  }
1101
1094
  removeManyByPks(pks) {
1102
1095
  for (const pk of pks) {
1103
- this.entities.delete(pk);
1096
+ this.removeOneByPk(pk);
1104
1097
  }
1105
1098
  }
1106
1099
  getOneByPk(pk) {
1107
- return this.entities.get(pk);
1100
+ return this.slots.get(pk)?.item;
1108
1101
  }
1109
1102
  getManyByPks(pks) {
1110
1103
  const result = [];
@@ -1120,16 +1113,23 @@ var OIMCollectionStoreMapDriven = class extends OIMCollectionStore {
1120
1113
  return result;
1121
1114
  }
1122
1115
  getAll() {
1123
- return Array.from(this.entities.values());
1116
+ const result = [];
1117
+ for (const slot of this.slots.values()) {
1118
+ if (slot.item !== void 0) result.push(slot.item);
1119
+ }
1120
+ return result;
1124
1121
  }
1125
1122
  countAll() {
1126
- return this.entities.size;
1123
+ return this.slots.size;
1127
1124
  }
1128
1125
  clear() {
1129
- this.entities.clear();
1126
+ for (const slot of this.slots.values()) {
1127
+ slot.item = void 0;
1128
+ }
1129
+ this.slots.clear();
1130
1130
  }
1131
1131
  getAllPks() {
1132
- return Array.from(this.entities.keys());
1132
+ return Array.from(this.slots.keys());
1133
1133
  }
1134
1134
  destroy() {
1135
1135
  this.clear();
@@ -1165,6 +1165,15 @@ var OIMCollection = class {
1165
1165
  getManyByPks(pks) {
1166
1166
  return this.store.getManyByPks(pks);
1167
1167
  }
1168
+ getSlotByPk(pk) {
1169
+ return this.store.getSlotByPk(pk);
1170
+ }
1171
+ getSlotsByPks(pks) {
1172
+ return this.store.getSlotsByPks(pks);
1173
+ }
1174
+ getAllSlots() {
1175
+ return this.store.getAllSlots();
1176
+ }
1168
1177
  upsertOneByPk(pk, entity) {
1169
1178
  this.upsertOneWithoutNotificationsByPk(pk, entity);
1170
1179
  this.emitter.emit(0 /* UPDATE */, { pks: [pk] });
@@ -1237,47 +1246,139 @@ var OIMCollection = class {
1237
1246
  }
1238
1247
  };
1239
1248
 
1240
- // src/core/OIMUpdateEventCoalescerCollection.ts
1241
- var OIMUpdateEventCoalescerCollection = class extends OIMUpdateEventCoalescer {
1242
- constructor(collectionEmitter) {
1243
- super();
1244
- this.collectionEmitter = collectionEmitter;
1245
- /**
1246
- * When the collection is updated, we keep track of the updated pks.
1247
- */
1248
- this.handleUpdate = (payload) => {
1249
- this.addUpdatedKeys(payload.pks);
1250
- };
1251
- this.collectionEmitter.on(
1252
- 0 /* UPDATE */,
1253
- this.handleUpdate
1254
- );
1255
- }
1256
- destroy() {
1257
- this.collectionEmitter.off(
1258
- 0 /* UPDATE */,
1259
- this.handleUpdate
1260
- );
1261
- this.emitter.offAll();
1262
- this.clearUpdatedKeys();
1263
- }
1264
- };
1265
-
1266
1249
  // src/core/OIMReactiveCollection.ts
1267
1250
  var OIMReactiveCollection = class extends OIMCollection {
1268
1251
  constructor(queue, opts) {
1269
1252
  super(opts);
1270
- this.coalescer = new OIMUpdateEventCoalescerCollection(
1271
- this.emitter
1253
+ this.anyUpdateHandlers = /* @__PURE__ */ new Set();
1254
+ this.pendingAnyUpdatePks = /* @__PURE__ */ new Set();
1255
+ this.isAnyUpdateClearPending = false;
1256
+ this.isAnyUpdateScheduled = false;
1257
+ this.runAnyUpdate = () => {
1258
+ if (!this.isAnyUpdateScheduled) return;
1259
+ this.isAnyUpdateScheduled = false;
1260
+ this.dequeueAnyUpdate = void 0;
1261
+ const pks = this.isAnyUpdateClearPending ? [] : Array.from(this.pendingAnyUpdatePks);
1262
+ this.pendingAnyUpdatePks.clear();
1263
+ this.isAnyUpdateClearPending = false;
1264
+ const snapshot = Array.from(this.anyUpdateHandlers);
1265
+ for (let i = 0; i < snapshot.length; i++) snapshot[i](pks);
1266
+ };
1267
+ this.queue = queue;
1268
+ this.updateEmitter = new OIMUpdateEventEmitter(queue, "queue");
1269
+ }
1270
+ subscribeOnKey(pk, handler) {
1271
+ return this.updateEmitter.subscribeOnKey(pk, handler);
1272
+ }
1273
+ subscribeOnKeys(pks, handler) {
1274
+ return this.updateEmitter.subscribeOnKeys(pks, handler);
1275
+ }
1276
+ unsubscribeFromKey(pk, handler) {
1277
+ this.updateEmitter.unsubscribeFromKey(pk, handler);
1278
+ }
1279
+ unsubscribeFromKeys(pks, handler) {
1280
+ this.updateEmitter.unsubscribeFromKeys(pks, handler);
1281
+ }
1282
+ destroySubscriptions() {
1283
+ this.updateEmitter.destroy();
1284
+ }
1285
+ hasSubscriptions() {
1286
+ return this.updateEmitter.hasSubscriptions();
1287
+ }
1288
+ getHandlerCount(pk) {
1289
+ return this.updateEmitter.getHandlerCount(pk);
1290
+ }
1291
+ getMetrics() {
1292
+ return this.updateEmitter.getMetrics();
1293
+ }
1294
+ subscribeOnAnyUpdate(handler) {
1295
+ this.anyUpdateHandlers.add(handler);
1296
+ return () => {
1297
+ this.anyUpdateHandlers.delete(handler);
1298
+ };
1299
+ }
1300
+ upsertOneByPk(pk, entity) {
1301
+ this.upsertOneWithoutNotificationsByPk(pk, entity);
1302
+ this.emitter.emit(0 /* UPDATE */, { pks: [pk] });
1303
+ this.updateEmitter.markUpdatedKey(pk);
1304
+ this.trackAnyUpdatePk(pk);
1305
+ }
1306
+ upsertOne(entity) {
1307
+ const pk = this.upsertOneWithoutNotifications(entity);
1308
+ this.emitter.emit(0 /* UPDATE */, { pks: [pk] });
1309
+ this.updateEmitter.markUpdatedKey(pk);
1310
+ this.trackAnyUpdatePk(pk);
1311
+ }
1312
+ upsertMany(entities) {
1313
+ const pks = entities.map(
1314
+ (entity) => this.upsertOneWithoutNotifications(entity)
1272
1315
  );
1273
- this.updateEventEmitter = new OIMUpdateEventEmitter({
1274
- coalescer: this.coalescer,
1275
- queue
1276
- });
1316
+ this.emitter.emit(0 /* UPDATE */, { pks });
1317
+ this.updateEmitter.markUpdatedKeys(pks);
1318
+ this.trackAnyUpdatePks(pks);
1319
+ }
1320
+ removeOne(entity) {
1321
+ const pk = this.selectPk(entity);
1322
+ this.store.removeOneByPk(pk);
1323
+ this.emitter.emit(0 /* UPDATE */, { pks: [pk] });
1324
+ this.updateEmitter.markUpdatedKey(pk);
1325
+ this.trackAnyUpdatePk(pk);
1326
+ }
1327
+ removeMany(entities) {
1328
+ const pks = entities.map(this.selectPk);
1329
+ this.store.removeManyByPks(pks);
1330
+ this.emitter.emit(0 /* UPDATE */, { pks });
1331
+ this.updateEmitter.markUpdatedKeys(pks);
1332
+ this.trackAnyUpdatePks(pks);
1333
+ }
1334
+ removeOneByPk(pk) {
1335
+ this.store.removeOneByPk(pk);
1336
+ this.emitter.emit(0 /* UPDATE */, { pks: [pk] });
1337
+ this.updateEmitter.markUpdatedKey(pk);
1338
+ this.trackAnyUpdatePk(pk);
1339
+ }
1340
+ removeManyByPks(pks) {
1341
+ this.store.removeManyByPks(pks);
1342
+ this.emitter.emit(0 /* UPDATE */, { pks });
1343
+ this.updateEmitter.markUpdatedKeys(pks);
1344
+ this.trackAnyUpdatePks(pks);
1345
+ }
1346
+ clear() {
1347
+ this.store.clear();
1348
+ this.emitter.emit(0 /* UPDATE */, { pks: [] });
1349
+ this.updateEmitter.markAllUpdated();
1350
+ this.trackAnyUpdateClear();
1351
+ }
1352
+ trackAnyUpdatePk(pk) {
1353
+ if (this.anyUpdateHandlers.size === 0) return;
1354
+ this.pendingAnyUpdatePks.add(pk);
1355
+ this.ensureAnyUpdateEnqueued();
1356
+ }
1357
+ trackAnyUpdatePks(pks) {
1358
+ if (this.anyUpdateHandlers.size === 0) return;
1359
+ for (let i = 0; i < pks.length; i++)
1360
+ this.pendingAnyUpdatePks.add(pks[i]);
1361
+ this.ensureAnyUpdateEnqueued();
1362
+ }
1363
+ trackAnyUpdateClear() {
1364
+ if (this.anyUpdateHandlers.size === 0) return;
1365
+ this.isAnyUpdateClearPending = true;
1366
+ this.pendingAnyUpdatePks.clear();
1367
+ this.ensureAnyUpdateEnqueued();
1368
+ }
1369
+ ensureAnyUpdateEnqueued() {
1370
+ if (this.isAnyUpdateScheduled) return;
1371
+ this.isAnyUpdateScheduled = true;
1372
+ this.dequeueAnyUpdate = this.queue.enqueue(this.runAnyUpdate);
1277
1373
  }
1278
1374
  destroy() {
1279
- this.updateEventEmitter.destroy();
1280
- this.coalescer.destroy();
1375
+ this.updateEmitter.destroy();
1376
+ if (this.dequeueAnyUpdate) {
1377
+ this.dequeueAnyUpdate();
1378
+ this.dequeueAnyUpdate = void 0;
1379
+ }
1380
+ this.anyUpdateHandlers.clear();
1381
+ this.pendingAnyUpdatePks.clear();
1281
1382
  super.destroy();
1282
1383
  }
1283
1384
  };
@@ -1377,54 +1478,158 @@ var OIMObject = class {
1377
1478
  }
1378
1479
  };
1379
1480
 
1380
- // src/core/OIMUpdateEventCoalescerObject.ts
1381
- var OIMUpdateEventCoalescerObject = class extends OIMUpdateEventCoalescer {
1382
- constructor(objectEmitter) {
1383
- super();
1384
- this.objectEmitter = objectEmitter;
1385
- /**
1386
- * When the object is updated, we keep track of the updated keys.
1387
- */
1388
- this.handleUpdate = (payload) => {
1389
- this.addUpdatedKeys(payload.keys);
1390
- };
1391
- this.objectEmitter.on(0 /* UPDATE */, this.handleUpdate);
1392
- }
1393
- destroy() {
1394
- this.objectEmitter.off(0 /* UPDATE */, this.handleUpdate);
1395
- this.emitter.offAll();
1396
- this.clearUpdatedKeys();
1397
- }
1398
- };
1399
-
1400
1481
  // src/core/OIMReactiveObject.ts
1401
1482
  var OIMReactiveObject = class extends OIMObject {
1402
1483
  constructor(queue, opts) {
1403
1484
  super(opts);
1404
- this.coalescer = new OIMUpdateEventCoalescerObject(this.emitter);
1405
- this.updateEventEmitter = new OIMUpdateEventEmitter({
1406
- coalescer: this.coalescer,
1407
- queue
1485
+ this.updateEmitter = new OIMUpdateEventEmitter(queue, "queue");
1486
+ this.emitter.on(0 /* UPDATE */, (payload) => {
1487
+ this.updateEmitter.markUpdatedKeys(payload.keys);
1408
1488
  });
1409
1489
  }
1490
+ subscribeOnKey(key, handler) {
1491
+ return this.updateEmitter.subscribeOnKey(key, handler);
1492
+ }
1493
+ subscribeOnKeys(keys, handler) {
1494
+ return this.updateEmitter.subscribeOnKeys(keys, handler);
1495
+ }
1496
+ unsubscribeFromKey(key, handler) {
1497
+ this.updateEmitter.unsubscribeFromKey(key, handler);
1498
+ }
1499
+ unsubscribeFromKeys(keys, handler) {
1500
+ this.updateEmitter.unsubscribeFromKeys(keys, handler);
1501
+ }
1502
+ hasSubscriptions() {
1503
+ return this.updateEmitter.hasSubscriptions();
1504
+ }
1505
+ getHandlerCount(key) {
1506
+ return this.updateEmitter.getHandlerCount(key);
1507
+ }
1508
+ getMetrics() {
1509
+ return this.updateEmitter.getMetrics();
1510
+ }
1410
1511
  destroy() {
1411
- this.updateEventEmitter.destroy();
1412
- this.coalescer.destroy();
1512
+ this.updateEmitter.destroy();
1413
1513
  super.destroy();
1414
1514
  }
1415
1515
  };
1416
1516
 
1517
+ // src/core/OIMIndexManualSetBased.ts
1518
+ var OIMIndexManualSetBased = class extends OIMIndexSetBased {
1519
+ constructor(options = {}) {
1520
+ super(options);
1521
+ }
1522
+ /**
1523
+ * Set primary keys for a specific index key, replacing any existing values.
1524
+ * Uses optional comparator to skip updates if PKs haven't actually changed.
1525
+ */
1526
+ setPks(key, pks) {
1527
+ const hasChanges = this.setPksWithComparison(key, new Set(pks));
1528
+ if (hasChanges) {
1529
+ this.emitUpdateOne(key);
1530
+ }
1531
+ }
1532
+ setSlots(key, slots) {
1533
+ const hasChanges = this.setSlotsWithComparison(key, new Set(slots));
1534
+ if (hasChanges) {
1535
+ this.emitUpdateOne(key);
1536
+ }
1537
+ }
1538
+ /**
1539
+ * Add primary keys to a specific index key
1540
+ */
1541
+ addPks(key, pks) {
1542
+ if (pks.length === 0) return;
1543
+ let slotsSet = this.store.getOneByKey(key);
1544
+ if (!slotsSet) {
1545
+ slotsSet = /* @__PURE__ */ new Set();
1546
+ this.store.setOneByKey(key, slotsSet);
1547
+ }
1548
+ const pksSet = this.slotsToPks(slotsSet);
1549
+ let hasChanges = false;
1550
+ for (const pk of pks) {
1551
+ if (!pksSet.has(pk)) {
1552
+ pksSet.add(pk);
1553
+ slotsSet.add(this.getOrCreateSlot(pk));
1554
+ hasChanges = true;
1555
+ }
1556
+ }
1557
+ if (hasChanges) {
1558
+ this.store.setOneByKey(key, slotsSet);
1559
+ this.emitUpdateOne(key);
1560
+ }
1561
+ }
1562
+ /**
1563
+ * Remove primary keys from a specific index key
1564
+ */
1565
+ removePks(key, pks) {
1566
+ if (pks.length === 0) return;
1567
+ const slotsSet = this.store.getOneByKey(key);
1568
+ if (!slotsSet) return;
1569
+ let hasChanges = false;
1570
+ const pksToRemove = new Set(pks);
1571
+ for (const slot of Array.from(slotsSet)) {
1572
+ if (pksToRemove.has(slot.pk) && slotsSet.delete(slot)) {
1573
+ hasChanges = true;
1574
+ }
1575
+ }
1576
+ if (slotsSet.size === 0) {
1577
+ this.store.removeOneByKey(key);
1578
+ hasChanges = true;
1579
+ } else if (hasChanges) {
1580
+ this.store.setOneByKey(key, slotsSet);
1581
+ }
1582
+ if (hasChanges) {
1583
+ this.emitUpdateOne(key);
1584
+ }
1585
+ }
1586
+ /**
1587
+ * Clear all primary keys for a specific index key, or all keys if no key specified
1588
+ */
1589
+ clear(key) {
1590
+ if (key === void 0) {
1591
+ const allKeys = this.store.getAllKeys();
1592
+ if (allKeys.length > 0) {
1593
+ this.store.clear();
1594
+ this.emitUpdate(allKeys);
1595
+ }
1596
+ } else {
1597
+ if (this.store.getOneByKey(key) !== void 0) {
1598
+ this.store.removeOneByKey(key);
1599
+ this.emitUpdateOne(key);
1600
+ }
1601
+ }
1602
+ }
1603
+ };
1604
+
1417
1605
  // src/core/OIMReactiveIndexManualSetBased.ts
1606
+ var OIMIndexManualSetBasedReactive = class extends OIMIndexManualSetBased {
1607
+ constructor(updateEmitter, opts) {
1608
+ super(opts);
1609
+ this.updateEmitter = updateEmitter;
1610
+ }
1611
+ emitUpdate(keys) {
1612
+ this.updateEmitter.markUpdatedKeys(keys);
1613
+ }
1614
+ emitUpdateOne(key) {
1615
+ this.updateEmitter.markUpdatedKey(key);
1616
+ }
1617
+ };
1418
1618
  var OIMReactiveIndexManualSetBased = class extends OIMReactiveIndexSetBased {
1419
1619
  constructor(queue, opts) {
1420
- super(queue, opts);
1421
- }
1422
- createDefaultIndex() {
1423
- return new OIMIndexManualSetBased();
1620
+ super(queue, (updateEmitter) => {
1621
+ return new OIMIndexManualSetBasedReactive(
1622
+ updateEmitter,
1623
+ opts?.indexOptions
1624
+ );
1625
+ });
1424
1626
  }
1425
1627
  setPks(key, pks) {
1426
1628
  this.index.setPks(key, pks);
1427
1629
  }
1630
+ setSlots(key, slots) {
1631
+ this.index.setSlots(key, slots);
1632
+ }
1428
1633
  addPks(key, pks) {
1429
1634
  this.index.addPks(key, pks);
1430
1635
  }
@@ -1448,7 +1653,13 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
1448
1653
  setPks(key, pks) {
1449
1654
  const hasChanges = this.setPksWithComparison(key, pks);
1450
1655
  if (hasChanges) {
1451
- this.emitUpdate([key]);
1656
+ this.emitUpdateOne(key);
1657
+ }
1658
+ }
1659
+ setSlots(key, slots) {
1660
+ const hasChanges = this.setSlotsWithComparison(key, slots);
1661
+ if (hasChanges) {
1662
+ this.emitUpdateOne(key);
1452
1663
  }
1453
1664
  }
1454
1665
  /**
@@ -1456,22 +1667,24 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
1456
1667
  */
1457
1668
  addPks(key, pks) {
1458
1669
  if (pks.length === 0) return;
1459
- let pksArray = this.store.getOneByKey(key);
1460
- if (!pksArray) {
1461
- pksArray = [];
1462
- this.store.setOneByKey(key, pksArray);
1670
+ let slotsArray = this.store.getOneByKey(key);
1671
+ if (!slotsArray) {
1672
+ slotsArray = [];
1673
+ this.store.setOneByKey(key, slotsArray);
1463
1674
  }
1464
- const pksSet = new Set(pksArray);
1675
+ const pksSet = new Set(slotsArray.map((slot) => slot.pk));
1465
1676
  let hasChanges = false;
1677
+ const nextSlots = slotsArray.slice();
1466
1678
  for (const pk of pks) {
1467
1679
  if (!pksSet.has(pk)) {
1468
1680
  pksSet.add(pk);
1681
+ nextSlots.push(this.getOrCreateSlot(pk));
1469
1682
  hasChanges = true;
1470
1683
  }
1471
1684
  }
1472
1685
  if (hasChanges) {
1473
- this.store.setOneByKey(key, Array.from(pksSet));
1474
- this.emitUpdate([key]);
1686
+ this.store.setOneByKey(key, nextSlots);
1687
+ this.emitUpdateOne(key);
1475
1688
  }
1476
1689
  }
1477
1690
  /**
@@ -1479,18 +1692,18 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
1479
1692
  */
1480
1693
  removePks(key, pks) {
1481
1694
  if (pks.length === 0) return;
1482
- const pksArray = this.store.getOneByKey(key);
1483
- if (!pksArray) return;
1695
+ const slotsArray = this.store.getOneByKey(key);
1696
+ if (!slotsArray) return;
1484
1697
  const pksSet = new Set(pks);
1485
- const filtered = pksArray.filter((pk) => !pksSet.has(pk));
1486
- const hasChanges = filtered.length !== pksArray.length;
1698
+ const filtered = slotsArray.filter((slot) => !pksSet.has(slot.pk));
1699
+ const hasChanges = filtered.length !== slotsArray.length;
1487
1700
  if (hasChanges) {
1488
1701
  if (filtered.length === 0) {
1489
1702
  this.store.removeOneByKey(key);
1490
1703
  } else {
1491
1704
  this.store.setOneByKey(key, filtered);
1492
1705
  }
1493
- this.emitUpdate([key]);
1706
+ this.emitUpdateOne(key);
1494
1707
  }
1495
1708
  }
1496
1709
  /**
@@ -1506,23 +1719,40 @@ var OIMIndexManualArrayBased = class extends OIMIndexArrayBased {
1506
1719
  } else {
1507
1720
  if (this.store.getOneByKey(key) !== void 0) {
1508
1721
  this.store.removeOneByKey(key);
1509
- this.emitUpdate([key]);
1722
+ this.emitUpdateOne(key);
1510
1723
  }
1511
1724
  }
1512
1725
  }
1513
1726
  };
1514
1727
 
1515
1728
  // src/core/OIMReactiveIndexManualArrayBased.ts
1729
+ var OIMIndexManualArrayBasedReactive = class extends OIMIndexManualArrayBased {
1730
+ constructor(updateEmitter, opts) {
1731
+ super(opts);
1732
+ this.updateEmitter = updateEmitter;
1733
+ }
1734
+ emitUpdate(keys) {
1735
+ this.updateEmitter.markUpdatedKeys(keys);
1736
+ }
1737
+ emitUpdateOne(key) {
1738
+ this.updateEmitter.markUpdatedKey(key);
1739
+ }
1740
+ };
1516
1741
  var OIMReactiveIndexManualArrayBased = class extends OIMReactiveIndexArrayBased {
1517
1742
  constructor(queue, opts) {
1518
- super(queue, opts);
1519
- }
1520
- createDefaultIndex() {
1521
- return new OIMIndexManualArrayBased();
1743
+ super(queue, (updateEmitter) => {
1744
+ return new OIMIndexManualArrayBasedReactive(
1745
+ updateEmitter,
1746
+ opts?.indexOptions
1747
+ );
1748
+ });
1522
1749
  }
1523
1750
  setPks(key, pks) {
1524
1751
  this.index.setPks(key, pks);
1525
1752
  }
1753
+ setSlots(key, slots) {
1754
+ this.index.setSlots(key, slots);
1755
+ }
1526
1756
  addPks(key, pks) {
1527
1757
  this.index.addPks(key, pks);
1528
1758
  }
@@ -1539,6 +1769,10 @@ var OIMRICollection = class extends OIMReactiveCollection {
1539
1769
  constructor(queue, opts) {
1540
1770
  super(queue, opts.collectionOpts);
1541
1771
  this.indexes = opts.indexes || {};
1772
+ const indexes = Object.values(this.indexes);
1773
+ for (const index of indexes) {
1774
+ index.index.setSlotResolver((pk) => this.getSlotByPk(pk));
1775
+ }
1542
1776
  }
1543
1777
  };
1544
1778
 
@@ -1607,20 +1841,12 @@ var OIMComparatorFactory = class {
1607
1841
  }
1608
1842
  };
1609
1843
 
1610
- // src/enum/EOIMEventQueueEventType.ts
1611
- var EOIMEventQueueEventType = /* @__PURE__ */ ((EOIMEventQueueEventType2) => {
1612
- EOIMEventQueueEventType2[EOIMEventQueueEventType2["AFTER_FLUSH"] = 0] = "AFTER_FLUSH";
1613
- EOIMEventQueueEventType2[EOIMEventQueueEventType2["BEFORE_FLUSH"] = 1] = "BEFORE_FLUSH";
1614
- return EOIMEventQueueEventType2;
1615
- })(EOIMEventQueueEventType || {});
1616
-
1617
1844
  // src/core/OIMEventQueue.ts
1618
1845
  var OIMEventQueue = class {
1619
1846
  constructor(options = {}) {
1620
1847
  this.emitter = new OIMEventEmitter();
1621
- this.queue = [];
1848
+ this.tasks = /* @__PURE__ */ new Set();
1622
1849
  this.isFlushing = false;
1623
- this.pendingSchedule = false;
1624
1850
  this.scheduler = options.scheduler;
1625
1851
  if (this.scheduler) {
1626
1852
  this.flushBound = () => this.flush();
@@ -1631,62 +1857,76 @@ var OIMEventQueue = class {
1631
1857
  }
1632
1858
  }
1633
1859
  /**
1634
- * Add a function to the queue. If scheduler is configured and autoSchedule is enabled,
1635
- * automatically schedules a flush operation.
1860
+ * Enqueue a one-shot task to be executed on the next flush.
1861
+ * Returns a dequeue function that can be called to cancel the task before it runs.
1636
1862
  */
1637
1863
  enqueue(fn) {
1638
- this.queue.push(fn);
1639
- if (!this.scheduler) return;
1640
- if (this.isFlushing) {
1641
- this.pendingSchedule = true;
1642
- return;
1643
- }
1644
- if (this.queue.length === 1) {
1645
- this.scheduler.schedule();
1646
- }
1864
+ let isActive = true;
1865
+ const task = () => {
1866
+ if (!isActive) return;
1867
+ fn();
1868
+ };
1869
+ this.tasks.add(task);
1870
+ this.ensureScheduled();
1871
+ return () => {
1872
+ if (!isActive) return;
1873
+ isActive = false;
1874
+ this.tasks.delete(task);
1875
+ };
1876
+ }
1877
+ get isInFlush() {
1878
+ return this.isFlushing;
1647
1879
  }
1648
1880
  /**
1649
- * Execute all queued functions and clear the queue.
1650
- * This method is safe to call multiple times and handles reentrancy.
1881
+ * Dequeue (cancel) a previously enqueued task by its dequeue function.
1651
1882
  */
1652
- flush() {
1653
- if (this.queue.length === 0) return;
1654
- this.scheduler?.cancel();
1883
+ dequeue(dequeueFn) {
1884
+ dequeueFn();
1885
+ }
1886
+ /**
1887
+ * Execute all pending tasks.
1888
+ *
1889
+ * This flush drains until the queue is empty. If tasks enqueue more tasks, they will be
1890
+ * executed within the same flush.
1891
+ */
1892
+ flush() {
1893
+ this.scheduler?.cancel();
1655
1894
  this.isFlushing = true;
1656
1895
  this.emitter.emit(1 /* BEFORE_FLUSH */, void 0);
1657
- const currentQueue = this.queue.slice();
1658
- this.queue.length = 0;
1659
- for (let i = 0; i < currentQueue.length; i++) {
1660
- currentQueue[i]();
1896
+ const snapshot = Array.from(this.tasks);
1897
+ this.tasks.clear();
1898
+ for (let i = 0; i < snapshot.length; i++) {
1899
+ snapshot[i]();
1661
1900
  }
1662
- this.emitter.emit(0 /* AFTER_FLUSH */, void 0);
1663
1901
  this.isFlushing = false;
1664
- if (this.scheduler && this.pendingSchedule && this.queue.length > 0) {
1665
- this.pendingSchedule = false;
1666
- this.scheduler.schedule();
1667
- } else {
1668
- this.pendingSchedule = false;
1669
- }
1902
+ this.emitter.emit(0 /* AFTER_FLUSH */, void 0);
1670
1903
  }
1671
1904
  /**
1672
1905
  * Get the current number of queued functions.
1673
1906
  */
1674
1907
  get length() {
1675
- return this.queue.length;
1908
+ return this.tasks.size;
1676
1909
  }
1677
1910
  /**
1678
1911
  * Check if the queue is empty.
1679
1912
  */
1680
1913
  get isEmpty() {
1681
- return this.queue.length === 0;
1914
+ return this.tasks.size === 0;
1682
1915
  }
1683
1916
  /**
1684
1917
  * Clear the queue without executing functions and cancel any scheduled flush.
1685
1918
  */
1686
1919
  clear() {
1687
- this.queue.length = 0;
1920
+ this.tasks.clear();
1688
1921
  this.scheduler?.cancel();
1689
1922
  }
1923
+ ensureScheduled() {
1924
+ if (!this.scheduler) return;
1925
+ if (this.isFlushing) return;
1926
+ if (this.tasks.size === 1) {
1927
+ this.scheduler.schedule();
1928
+ }
1929
+ }
1690
1930
  /**
1691
1931
  * Clean up scheduler subscription when queue is no longer needed.
1692
1932
  */
@@ -1800,56 +2040,75 @@ var OIMMap2Keys = class {
1800
2040
  }
1801
2041
  };
1802
2042
 
1803
- // src/modules/wrapper/OIMUpdateEventCoalescerManual.ts
1804
- var OIMUpdateEventCoalescerManual = class extends OIMUpdateEventCoalescer {
1805
- markUpdatedKeys(keys) {
1806
- if (keys.length === 0) return;
1807
- this.addUpdatedKeys(keys);
1808
- }
1809
- destroy() {
1810
- this.emitter.offAll();
1811
- this.updatedKeysIndex.destroy();
1812
- this.flushingUpdatedKeysIndex.destroy();
1813
- }
1814
- };
1815
-
1816
2043
  // src/modules/wrapper/collection/OIMCollectionChangedFieldsWrapper.ts
1817
2044
  var OIMCollectionChangedFieldsWrapper = class {
1818
2045
  constructor(queue, collection, opts) {
2046
+ this.isInWrite = false;
2047
+ this.lastEntitySnapshotByPk = /* @__PURE__ */ new Map();
1819
2048
  this.changedFieldsByPk = /* @__PURE__ */ new Map();
1820
2049
  this.changedPksByField = /* @__PURE__ */ new Map();
2050
+ this.onAfterFlush = () => {
2051
+ this.clear();
2052
+ };
2053
+ this.onCollectionUpdate = (payload) => {
2054
+ if (this.isInWrite) return;
2055
+ if (payload.pks.length === 0) return;
2056
+ for (let i = 0; i < payload.pks.length; i++) {
2057
+ const pk = payload.pks[i];
2058
+ this.trackExternalMutationByPk(pk);
2059
+ }
2060
+ };
1821
2061
  this.collection = collection;
1822
2062
  this.selectPk = opts?.selectPk ?? (() => {
1823
2063
  throw new Error(
1824
2064
  `[OIMCollectionChangedFieldsWrapper]: selectPk was not provided. Use upsertOneByPk(...) or pass opts.selectPk.`
1825
2065
  );
1826
2066
  });
1827
- this.changedPksCoalescer = new OIMUpdateEventCoalescerManual();
1828
- this.changedPksEventEmitter = new OIMUpdateEventEmitter({
1829
- coalescer: this.changedPksCoalescer,
1830
- queue
1831
- });
1832
- this.changedFieldsCoalescer = new OIMUpdateEventCoalescerManual();
1833
- this.changedFieldsEventEmitter = new OIMUpdateEventEmitter({
1834
- coalescer: this.changedFieldsCoalescer,
1835
- queue
1836
- });
2067
+ this.changedPksEventEmitter = new OIMUpdateEventEmitter(
2068
+ queue,
2069
+ "after_flush"
2070
+ );
2071
+ this.changedFieldsEventEmitter = new OIMUpdateEventEmitter(queue, "after_flush");
2072
+ this.unsubscribeAfterFlush = queue.emitter.on(
2073
+ 0 /* AFTER_FLUSH */,
2074
+ this.onAfterFlush
2075
+ );
2076
+ const maybeReactive = collection;
2077
+ this.unsubscribeFromCollectionEmitter = maybeReactive.subscribeOnAnyUpdate ? maybeReactive.subscribeOnAnyUpdate((pks) => {
2078
+ this.onCollectionUpdate({
2079
+ pks
2080
+ });
2081
+ }) : collection.emitter.on(
2082
+ 0 /* UPDATE */,
2083
+ this.onCollectionUpdate
2084
+ );
1837
2085
  }
1838
2086
  upsertOneByPk(pk, patch) {
1839
- this.trackPatch(pk, patch);
1840
- this.collection.upsertOneByPk(pk, patch);
2087
+ this.withWrite(() => {
2088
+ this.trackPatch(pk, patch);
2089
+ this.collection.upsertOneByPk(pk, patch);
2090
+ });
2091
+ this.updateEntitySnapshot(pk);
1841
2092
  }
1842
2093
  upsertOne(entity) {
1843
- const pk = this.selectPk(entity);
1844
- this.trackPatch(pk, entity);
1845
- this.collection.upsertOne(entity);
1846
- }
1847
- upsertMany(entities) {
1848
- for (const entity of entities) {
2094
+ this.withWrite(() => {
1849
2095
  const pk = this.selectPk(entity);
1850
2096
  this.trackPatch(pk, entity);
1851
- }
1852
- this.collection.upsertMany(entities);
2097
+ this.collection.upsertOne(entity);
2098
+ });
2099
+ this.updateEntitySnapshot(this.selectPk(entity));
2100
+ }
2101
+ upsertMany(entities) {
2102
+ const pks = [];
2103
+ this.withWrite(() => {
2104
+ for (const entity of entities) {
2105
+ const pk = this.selectPk(entity);
2106
+ pks.push(pk);
2107
+ this.trackPatch(pk, entity);
2108
+ }
2109
+ this.collection.upsertMany(entities);
2110
+ });
2111
+ for (let i = 0; i < pks.length; i++) this.updateEntitySnapshot(pks[i]);
1853
2112
  }
1854
2113
  getChangedFieldsByPk(pk) {
1855
2114
  return this.changedFieldsByPk.get(pk) ?? /* @__PURE__ */ new Set();
@@ -1857,8 +2116,7 @@ var OIMCollectionChangedFieldsWrapper = class {
1857
2116
  consumeChangedFieldsByPk(pk) {
1858
2117
  const existing = this.changedFieldsByPk.get(pk);
1859
2118
  if (!existing) return /* @__PURE__ */ new Set();
1860
- this.changedFieldsByPk.delete(pk);
1861
- return existing;
2119
+ return new Set(existing);
1862
2120
  }
1863
2121
  getChangedPksByField(field) {
1864
2122
  return this.changedPksByField.get(field) ?? /* @__PURE__ */ new Set();
@@ -1866,8 +2124,7 @@ var OIMCollectionChangedFieldsWrapper = class {
1866
2124
  consumeChangedPksByField(field) {
1867
2125
  const existing = this.changedPksByField.get(field);
1868
2126
  if (!existing) return /* @__PURE__ */ new Set();
1869
- this.changedPksByField.delete(field);
1870
- return existing;
2127
+ return new Set(existing);
1871
2128
  }
1872
2129
  clear() {
1873
2130
  this.changedFieldsByPk.clear();
@@ -1876,18 +2133,60 @@ var OIMCollectionChangedFieldsWrapper = class {
1876
2133
  destroy() {
1877
2134
  this.changedPksEventEmitter.destroy();
1878
2135
  this.changedFieldsEventEmitter.destroy();
1879
- this.changedPksCoalescer.destroy();
1880
- this.changedFieldsCoalescer.destroy();
2136
+ this.unsubscribeAfterFlush();
2137
+ this.unsubscribeFromCollectionEmitter();
1881
2138
  this.clear();
1882
2139
  }
1883
- trackPatch(pk, patch) {
1884
- if (pk === void 0 || pk === null || pk === "") {
1885
- throw new Error(
1886
- `[OIMCollectionChangedFieldsWrapper]: PK is required to track changes.`
1887
- );
2140
+ withWrite(fn) {
2141
+ if (this.isInWrite) {
2142
+ fn();
2143
+ return;
1888
2144
  }
1889
- const keys = Object.keys(patch);
1890
- if (keys.length === 0) return;
2145
+ this.isInWrite = true;
2146
+ try {
2147
+ fn();
2148
+ } finally {
2149
+ this.isInWrite = false;
2150
+ }
2151
+ }
2152
+ trackExternalMutationByPk(pk) {
2153
+ const prev = this.lastEntitySnapshotByPk.get(pk);
2154
+ const next = this.collection.getOneByPk(pk);
2155
+ const changedKeys = this.diffChangedKeys(prev, next);
2156
+ this.updateEntitySnapshot(pk);
2157
+ if (changedKeys.length > 0) {
2158
+ this.trackChangedKeys(pk, changedKeys);
2159
+ } else {
2160
+ this.changedPksEventEmitter.markUpdatedKeys([pk]);
2161
+ }
2162
+ }
2163
+ diffChangedKeys(prev, next) {
2164
+ if (!prev && !next) return [];
2165
+ if (!prev && next) return Object.keys(next);
2166
+ if (prev && !next) return Object.keys(prev);
2167
+ const prevObj = prev;
2168
+ const nextObj = next;
2169
+ const keys = /* @__PURE__ */ new Set();
2170
+ Object.keys(prevObj).forEach((k) => keys.add(k));
2171
+ Object.keys(nextObj).forEach((k) => keys.add(k));
2172
+ const changed = [];
2173
+ keys.forEach((k) => {
2174
+ if (prevObj[k] !== nextObj[k])
2175
+ changed.push(k);
2176
+ });
2177
+ return changed;
2178
+ }
2179
+ updateEntitySnapshot(pk) {
2180
+ const entity = this.collection.getOneByPk(pk);
2181
+ if (!entity) {
2182
+ this.lastEntitySnapshotByPk.delete(pk);
2183
+ return;
2184
+ }
2185
+ this.lastEntitySnapshotByPk.set(pk, {
2186
+ ...entity
2187
+ });
2188
+ }
2189
+ trackChangedKeys(pk, keys) {
1891
2190
  let pkFields = this.changedFieldsByPk.get(pk);
1892
2191
  if (!pkFields) {
1893
2192
  pkFields = /* @__PURE__ */ new Set();
@@ -1902,8 +2201,18 @@ var OIMCollectionChangedFieldsWrapper = class {
1902
2201
  }
1903
2202
  fieldPks.add(pk);
1904
2203
  }
1905
- this.changedPksCoalescer.markUpdatedKeys([pk]);
1906
- this.changedFieldsCoalescer.markUpdatedKeys(keys);
2204
+ this.changedPksEventEmitter.markUpdatedKeys([pk]);
2205
+ this.changedFieldsEventEmitter.markUpdatedKeys(keys);
2206
+ }
2207
+ trackPatch(pk, patch) {
2208
+ if (pk === void 0 || pk === null || pk === "") {
2209
+ throw new Error(
2210
+ `[OIMCollectionChangedFieldsWrapper]: PK is required to track changes.`
2211
+ );
2212
+ }
2213
+ const keys = Object.keys(patch);
2214
+ if (keys.length === 0) return;
2215
+ this.trackChangedKeys(pk, keys);
1907
2216
  }
1908
2217
  };
1909
2218
 
@@ -1926,14 +2235,14 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1926
2235
  push(key, itemKey) {
1927
2236
  const list = this.getOrCreateList(key);
1928
2237
  const index = list.length;
1929
- list.push(itemKey);
2238
+ list.push(this.getOrCreateSlot(itemKey));
1930
2239
  this.emitUpdate([key]);
1931
2240
  return index;
1932
2241
  }
1933
2242
  insertAt(key, index, itemKey) {
1934
2243
  const list = this.getOrCreateList(key);
1935
2244
  const safeIndex = Math.max(0, Math.min(index, list.length));
1936
- list.splice(safeIndex, 0, itemKey);
2245
+ list.splice(safeIndex, 0, this.getOrCreateSlot(itemKey));
1937
2246
  this.emitUpdate([key]);
1938
2247
  }
1939
2248
  removeAt(key, index) {
@@ -1945,19 +2254,19 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1945
2254
  this.store.removeOneByKey(key);
1946
2255
  }
1947
2256
  this.emitUpdate([key]);
1948
- return removed;
2257
+ return removed?.pk;
1949
2258
  }
1950
2259
  move(key, fromIndex, toIndex) {
1951
2260
  const list = this.store.getOneByKey(key);
1952
2261
  if (!list) return void 0;
1953
2262
  if (fromIndex < 0 || fromIndex >= list.length) return void 0;
1954
2263
  if (toIndex < 0 || toIndex >= list.length) return void 0;
1955
- if (fromIndex === toIndex) return list[fromIndex];
2264
+ if (fromIndex === toIndex) return list[fromIndex]?.pk;
1956
2265
  const [itemKey] = list.splice(fromIndex, 1);
1957
2266
  if (itemKey === void 0) return void 0;
1958
2267
  list.splice(toIndex, 0, itemKey);
1959
2268
  this.emitUpdate([key]);
1960
- return itemKey;
2269
+ return itemKey.pk;
1961
2270
  }
1962
2271
  reset(key, itemKeys) {
1963
2272
  if (itemKeys.length === 0) {
@@ -1967,7 +2276,7 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1967
2276
  }
1968
2277
  return;
1969
2278
  }
1970
- this.store.setOneByKey(key, Array.from(itemKeys));
2279
+ this.store.setOneByKey(key, this.resolveSlots(itemKeys));
1971
2280
  this.emitUpdate([key]);
1972
2281
  }
1973
2282
  getOrCreateList(key) {
@@ -1983,13 +2292,33 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1983
2292
  // src/modules/wrapper/index/OIMOrderedListIndexCommandStreamWrapper.ts
1984
2293
  var OIMOrderedListIndexCommandStreamWrapper = class {
1985
2294
  constructor(queue, index) {
2295
+ this.isInWrite = false;
1986
2296
  this.commandsByKey = /* @__PURE__ */ new Map();
1987
- this.coalescer = new OIMUpdateEventCoalescerManual();
1988
- this.commandsEventEmitter = new OIMUpdateEventEmitter({
1989
- coalescer: this.coalescer,
1990
- queue
1991
- });
2297
+ this.onAfterFlush = () => {
2298
+ this.commandsByKey.clear();
2299
+ };
2300
+ this.onIndexUpdate = (payload) => {
2301
+ if (this.isInWrite) return;
2302
+ if (payload.keys.length === 0) return;
2303
+ for (let i = 0; i < payload.keys.length; i++) {
2304
+ const key = payload.keys[i];
2305
+ const list = this.index.getPksByKey(key);
2306
+ this.appendCommand(key, { type: "set", keys: Array.from(list) });
2307
+ }
2308
+ };
2309
+ this.commandsEventEmitter = new OIMUpdateEventEmitter(
2310
+ queue,
2311
+ "after_flush"
2312
+ );
1992
2313
  this.index = index ?? new OIMIndexManualOrderedArrayBased();
2314
+ this.unsubscribeAfterFlush = queue.emitter.on(
2315
+ 0 /* AFTER_FLUSH */,
2316
+ this.onAfterFlush
2317
+ );
2318
+ this.unsubscribeFromIndexEmitter = this.index.emitter.on(
2319
+ 0 /* UPDATE */,
2320
+ this.onIndexUpdate
2321
+ );
1993
2322
  }
1994
2323
  getPksByKey(key) {
1995
2324
  return this.index.getPksByKey(key);
@@ -2001,27 +2330,35 @@ var OIMOrderedListIndexCommandStreamWrapper = class {
2001
2330
  return this.index.getKeys();
2002
2331
  }
2003
2332
  push(key, itemKey) {
2004
- const index = this.index.push(key, itemKey);
2005
- this.appendCommand(key, { type: "add", key: itemKey, index });
2333
+ this.withWrite(() => {
2334
+ const index = this.index.push(key, itemKey);
2335
+ this.appendCommand(key, { type: "add", key: itemKey, index });
2336
+ });
2006
2337
  }
2007
2338
  removeAt(key, index) {
2008
- const itemKey = this.index.removeAt(key, index);
2009
- if (itemKey === void 0) return;
2010
- this.appendCommand(key, { type: "remove", key: itemKey, index });
2339
+ this.withWrite(() => {
2340
+ const itemKey = this.index.removeAt(key, index);
2341
+ if (itemKey === void 0) return;
2342
+ this.appendCommand(key, { type: "remove", key: itemKey, index });
2343
+ });
2011
2344
  }
2012
2345
  move(key, fromIndex, toIndex) {
2013
- const itemKey = this.index.move(key, fromIndex, toIndex);
2014
- if (itemKey === void 0) return;
2015
- this.appendCommand(key, {
2016
- type: "move",
2017
- key: itemKey,
2018
- fromIndex,
2019
- toIndex
2346
+ this.withWrite(() => {
2347
+ const itemKey = this.index.move(key, fromIndex, toIndex);
2348
+ if (itemKey === void 0) return;
2349
+ this.appendCommand(key, {
2350
+ type: "move",
2351
+ key: itemKey,
2352
+ fromIndex,
2353
+ toIndex
2354
+ });
2020
2355
  });
2021
2356
  }
2022
- reset(key, keys) {
2023
- this.index.reset(key, keys);
2024
- this.appendCommand(key, { type: "reset", keys });
2357
+ set(key, keys) {
2358
+ this.withWrite(() => {
2359
+ this.index.reset(key, keys);
2360
+ this.appendCommand(key, { type: "set", keys });
2361
+ });
2025
2362
  }
2026
2363
  /**
2027
2364
  * Read-only peek at buffered commands without clearing them.
@@ -2036,8 +2373,7 @@ var OIMOrderedListIndexCommandStreamWrapper = class {
2036
2373
  consumeCommands(key) {
2037
2374
  const cmds = this.commandsByKey.get(key);
2038
2375
  if (!cmds || cmds.length === 0) return [];
2039
- this.commandsByKey.delete(key);
2040
- return cmds;
2376
+ return cmds.slice();
2041
2377
  }
2042
2378
  clear(key) {
2043
2379
  if (key === void 0) {
@@ -2050,17 +2386,38 @@ var OIMOrderedListIndexCommandStreamWrapper = class {
2050
2386
  }
2051
2387
  destroy() {
2052
2388
  this.commandsEventEmitter.destroy();
2053
- this.coalescer.destroy();
2389
+ this.unsubscribeAfterFlush();
2390
+ this.unsubscribeFromIndexEmitter();
2054
2391
  this.clear();
2055
2392
  }
2393
+ withWrite(fn) {
2394
+ if (this.isInWrite) {
2395
+ fn();
2396
+ return;
2397
+ }
2398
+ this.isInWrite = true;
2399
+ try {
2400
+ fn();
2401
+ } finally {
2402
+ this.isInWrite = false;
2403
+ }
2404
+ }
2056
2405
  appendCommand(key, cmd) {
2057
2406
  let cmds = this.commandsByKey.get(key);
2058
2407
  if (!cmds) {
2059
2408
  cmds = [];
2060
2409
  this.commandsByKey.set(key, cmds);
2061
2410
  }
2062
- cmds.push(cmd);
2063
- this.coalescer.markUpdatedKeys([key]);
2411
+ if (cmd.type === "set") {
2412
+ cmds.length = 0;
2413
+ cmds.push(cmd);
2414
+ } else if (cmds.length > 0 && cmds[0].type === "set") {
2415
+ const setCmd = cmds[0];
2416
+ setCmd.keys = Array.from(this.index.getPksByKey(key));
2417
+ } else {
2418
+ cmds.push(cmd);
2419
+ }
2420
+ this.commandsEventEmitter.markUpdatedKeys([key]);
2064
2421
  }
2065
2422
  };
2066
2423
 
@@ -2233,159 +2590,160 @@ var OIMEventQueueSchedulerFactory = class {
2233
2590
  }
2234
2591
  };
2235
2592
 
2593
+ // src/const/OIMDBSettings.ts
2594
+ var OIMDBSettings = class {
2595
+ };
2596
+ /**
2597
+ * Fixed key used in the manual index to store updated keys set in coalescers.
2598
+ * This constant is exported so users can create their own indexes and pass them to coalescers.
2599
+ */
2600
+ OIMDBSettings.UPDATED_KEYS_INDEX_KEY = "__updatedKeys__";
2601
+ OIMDBSettings.FLUSHING_KEYS_INDEX_KEY = "__flushingKeys__";
2602
+
2603
+ // src/modules/effect/core/OIMEffectDependencyComputed.ts
2604
+ var OIMEffectDependencyComputed = class {
2605
+ constructor(computed) {
2606
+ this.computed = computed;
2607
+ }
2608
+ subscribe(onUpdate) {
2609
+ return this.computed.emitter.on("update", onUpdate);
2610
+ }
2611
+ };
2612
+
2236
2613
  // src/modules/effect/core/OIMEffect.ts
2237
2614
  var OIMEffect = class {
2238
- constructor(queue, opts) {
2239
- this.unsubscribers = [];
2240
- this.isDestroyed = false;
2241
- this.isInFlush = false;
2242
- this.hasRunInThisFlush = false;
2615
+ constructor(runtime, opts) {
2616
+ this.sourceDeps = [];
2617
+ this.computedDeps = [];
2618
+ this.sourceUnsubscribers = [];
2619
+ this.computedUnsubscribers = [];
2620
+ this.isResubscribeEnqueued = false;
2621
+ this.isTemporarilyUnsubscribedForFlush = false;
2243
2622
  this.isScheduled = false;
2244
- this.handleQueueBeforeFlush = () => {
2245
- this.isInFlush = true;
2246
- this.hasRunInThisFlush = false;
2247
- };
2248
- this.handleQueueAfterFlush = () => {
2249
- this.isInFlush = false;
2250
- this.hasRunInThisFlush = false;
2251
- };
2252
2623
  this.runTask = () => {
2253
2624
  this.isScheduled = false;
2254
- if (this.isDestroyed) return;
2625
+ this.dequeueScheduled = void 0;
2255
2626
  this.runOnce();
2256
2627
  };
2257
- this.invalidate = () => {
2258
- if (this.isDestroyed) return;
2259
- this.onInvalidate?.();
2260
- if (this.isInFlush) {
2261
- if (this.hasRunInThisFlush) return;
2262
- this.hasRunInThisFlush = true;
2263
- this.runOnce();
2264
- return;
2628
+ this.onSourceInvalidate = () => {
2629
+ this.onUpdate?.();
2630
+ if (!this.isTemporarilyUnsubscribedForFlush) {
2631
+ this.isTemporarilyUnsubscribedForFlush = true;
2632
+ this.unsubscribeFromSourceDeps();
2633
+ this.ensureResubscribeAfterFlush();
2265
2634
  }
2266
2635
  if (this.isScheduled) return;
2267
2636
  this.isScheduled = true;
2268
- this.queue.enqueue(this.runTask);
2637
+ this.dequeueScheduled = this.runtime.schedule(this.runTask);
2269
2638
  };
2270
- this.queue = queue;
2271
- this.phase = opts.phase;
2272
- this.onInvalidate = opts.onInvalidate;
2639
+ this.onComputedInvalidate = () => {
2640
+ this.onUpdate?.();
2641
+ if (this.isScheduled) return;
2642
+ this.isScheduled = true;
2643
+ this.dequeueScheduled = this.runtime.schedule(this.runTask);
2644
+ };
2645
+ this.onAfterFlush = () => {
2646
+ if (!this.isResubscribeEnqueued) return;
2647
+ this.isResubscribeEnqueued = false;
2648
+ this.dequeueResubscribe = void 0;
2649
+ this.isTemporarilyUnsubscribedForFlush = false;
2650
+ if (this.sourceUnsubscribers.length === 0) {
2651
+ for (const dep of this.sourceDeps) {
2652
+ this.sourceUnsubscribers.push(
2653
+ dep.subscribe(this.onSourceInvalidate)
2654
+ );
2655
+ }
2656
+ }
2657
+ };
2658
+ this.runtime = runtime;
2659
+ this.onUpdate = opts.onUpdate;
2273
2660
  this.run = opts.run;
2274
2661
  this.deps = opts.deps ?? [];
2275
- this.queue.emitter.on(
2276
- 1 /* BEFORE_FLUSH */,
2277
- this.handleQueueBeforeFlush
2278
- );
2279
- this.queue.emitter.on(
2280
- 0 /* AFTER_FLUSH */,
2281
- this.handleQueueAfterFlush
2282
- );
2283
2662
  for (const dep of this.deps) {
2284
- this.unsubscribers.push(dep.subscribe(this.phase, this.invalidate));
2663
+ if (dep instanceof OIMEffectDependencyComputed) {
2664
+ this.computedDeps.push(dep);
2665
+ } else {
2666
+ this.sourceDeps.push(dep);
2667
+ }
2285
2668
  }
2669
+ this.subscribeToDeps();
2286
2670
  }
2287
2671
  destroy() {
2288
- if (this.isDestroyed) return;
2289
- this.isDestroyed = true;
2290
- for (const unsubscribe of this.unsubscribers) unsubscribe();
2291
- this.unsubscribers = [];
2292
- this.queue.emitter.off(
2293
- 1 /* BEFORE_FLUSH */,
2294
- this.handleQueueBeforeFlush
2295
- );
2296
- this.queue.emitter.off(
2297
- 0 /* AFTER_FLUSH */,
2298
- this.handleQueueAfterFlush
2299
- );
2672
+ if (this.dequeueScheduled) {
2673
+ this.dequeueScheduled();
2674
+ this.dequeueScheduled = void 0;
2675
+ }
2676
+ this.unsubscribeFromAllDeps();
2677
+ if (this.dequeueResubscribe) {
2678
+ this.dequeueResubscribe();
2679
+ this.dequeueResubscribe = void 0;
2680
+ }
2681
+ this.isResubscribeEnqueued = false;
2682
+ this.isTemporarilyUnsubscribedForFlush = false;
2300
2683
  }
2301
2684
  runOnce() {
2302
- if (this.isDestroyed) return;
2303
2685
  this.run();
2304
2686
  }
2687
+ subscribeToDeps() {
2688
+ if (this.sourceUnsubscribers.length > 0 || this.computedUnsubscribers.length > 0)
2689
+ return;
2690
+ for (const dep of this.sourceDeps) {
2691
+ this.sourceUnsubscribers.push(
2692
+ dep.subscribe(this.onSourceInvalidate)
2693
+ );
2694
+ }
2695
+ for (const dep of this.computedDeps) {
2696
+ this.computedUnsubscribers.push(
2697
+ dep.subscribe(this.onComputedInvalidate)
2698
+ );
2699
+ }
2700
+ }
2701
+ unsubscribeFromSourceDeps() {
2702
+ if (this.sourceUnsubscribers.length === 0) return;
2703
+ for (const unsubscribe of this.sourceUnsubscribers) unsubscribe();
2704
+ this.sourceUnsubscribers = [];
2705
+ }
2706
+ unsubscribeFromAllDeps() {
2707
+ this.unsubscribeFromSourceDeps();
2708
+ if (this.computedUnsubscribers.length === 0) return;
2709
+ for (const unsubscribe of this.computedUnsubscribers) unsubscribe();
2710
+ this.computedUnsubscribers = [];
2711
+ }
2712
+ ensureResubscribeAfterFlush() {
2713
+ if (this.isResubscribeEnqueued) return;
2714
+ this.isResubscribeEnqueued = true;
2715
+ this.dequeueResubscribe = this.runtime.scheduleAfterFlush(
2716
+ this.onAfterFlush
2717
+ );
2718
+ }
2305
2719
  };
2306
2720
 
2307
- // src/modules/effect/enum/EOIMEffectPhase.ts
2308
- var EOIMEffectPhase = /* @__PURE__ */ ((EOIMEffectPhase2) => {
2309
- EOIMEffectPhase2["PRE"] = "pre";
2310
- EOIMEffectPhase2["HANDLERS"] = "handlers";
2311
- return EOIMEffectPhase2;
2312
- })(EOIMEffectPhase || {});
2313
-
2314
2721
  // src/modules/computed/enum/EOIMComputedEventType.ts
2315
2722
  var EOIMComputedEventType = /* @__PURE__ */ ((EOIMComputedEventType2) => {
2316
2723
  EOIMComputedEventType2["UPDATE"] = "update";
2317
2724
  return EOIMComputedEventType2;
2318
2725
  })(EOIMComputedEventType || {});
2319
2726
 
2320
- // src/modules/computed/core/OIMUpdateEventCoalescerComputed.ts
2321
- var OIMUpdateEventCoalescerComputed = class extends OIMUpdateEventCoalescer {
2322
- constructor(computedEmitter) {
2323
- super();
2324
- this.computedEmitter = computedEmitter;
2325
- this.handleUpdate = (payload) => {
2326
- this.addUpdatedKeys(payload.keys);
2327
- };
2328
- this.computedEmitter.on(
2329
- "update" /* UPDATE */,
2330
- this.handleUpdate
2331
- );
2332
- }
2333
- destroy() {
2334
- this.computedEmitter.off(
2335
- "update" /* UPDATE */,
2336
- this.handleUpdate
2337
- );
2338
- this.emitter.offAll();
2339
- this.clearUpdatedKeys();
2340
- }
2341
- };
2342
-
2343
2727
  // src/modules/computed/core/OIMComputed.ts
2344
2728
  var OIMComputed = class {
2345
- constructor(queue, opts) {
2729
+ constructor(runtime, opts) {
2346
2730
  this.emitter = new OIMEventEmitter();
2347
2731
  this.hasValue = false;
2348
2732
  this.isDirty = true;
2349
- this.isInFlush = false;
2350
- this.hasRecomputedInThisFlush = false;
2351
- this.handleQueueBeforeFlush = () => {
2352
- this.isInFlush = true;
2353
- this.hasRecomputedInThisFlush = false;
2354
- };
2355
- this.handleQueueAfterFlush = () => {
2356
- this.isInFlush = false;
2357
- this.hasRecomputedInThisFlush = false;
2358
- };
2359
- this.queue = queue;
2733
+ this.runtime = runtime;
2360
2734
  this.compute = opts.compute;
2361
2735
  this.compare = opts.compare ?? Object.is;
2362
2736
  this.deps = opts.deps ?? [];
2363
- this.coalescer = new OIMUpdateEventCoalescerComputed(
2364
- this.emitter
2365
- );
2366
- this.updateEventEmitter = new OIMUpdateEventEmitter({
2367
- coalescer: this.coalescer,
2368
- queue: this.queue
2369
- });
2370
- this.queue.emitter.on(
2371
- 1 /* BEFORE_FLUSH */,
2372
- this.handleQueueBeforeFlush
2737
+ this.updateEventEmitter = new OIMUpdateEventEmitter(
2738
+ this.runtime.queue,
2739
+ "immediate"
2373
2740
  );
2374
- this.queue.emitter.on(
2375
- 0 /* AFTER_FLUSH */,
2376
- this.handleQueueAfterFlush
2377
- );
2378
- this.effect = new OIMEffect(this.queue, {
2741
+ this.effect = new OIMEffect(this.runtime, {
2379
2742
  deps: this.deps,
2380
- phase: "pre" /* PRE */,
2381
- onInvalidate: () => {
2743
+ onUpdate: () => {
2382
2744
  this.isDirty = true;
2383
2745
  },
2384
2746
  run: () => {
2385
- if (this.isInFlush) {
2386
- if (this.hasRecomputedInThisFlush) return;
2387
- this.hasRecomputedInThisFlush = true;
2388
- }
2389
2747
  this.recomputeAndEmitIfChanged();
2390
2748
  }
2391
2749
  });
@@ -2399,16 +2757,7 @@ var OIMComputed = class {
2399
2757
  }
2400
2758
  destroy() {
2401
2759
  this.effect.destroy();
2402
- this.queue.emitter.off(
2403
- 1 /* BEFORE_FLUSH */,
2404
- this.handleQueueBeforeFlush
2405
- );
2406
- this.queue.emitter.off(
2407
- 0 /* AFTER_FLUSH */,
2408
- this.handleQueueAfterFlush
2409
- );
2410
2760
  this.updateEventEmitter.destroy();
2411
- this.coalescer.destroy();
2412
2761
  this.emitter.offAll();
2413
2762
  }
2414
2763
  recomputeAndEmitIfChanged() {
@@ -2424,6 +2773,7 @@ var OIMComputed = class {
2424
2773
  this.emitter.emit("update" /* UPDATE */, {
2425
2774
  keys: ["value"]
2426
2775
  });
2776
+ this.updateEventEmitter.markUpdatedKey("value");
2427
2777
  }
2428
2778
  };
2429
2779
 
@@ -2432,31 +2782,21 @@ function isReadonlyArray(value) {
2432
2782
  return Array.isArray(value);
2433
2783
  }
2434
2784
  var OIMEffectDependencyKeyed = class {
2435
- constructor(updateEventEmitter, keyOrKeys) {
2436
- this.updateEventEmitter = updateEventEmitter;
2785
+ constructor(subscription, keyOrKeys) {
2786
+ this.subscription = subscription;
2437
2787
  if (isReadonlyArray(keyOrKeys)) {
2438
2788
  this.keys = keyOrKeys;
2439
2789
  } else {
2440
2790
  this.key = keyOrKeys;
2441
2791
  }
2442
2792
  }
2443
- subscribe(phase, onInvalidate) {
2444
- const isPre = phase === "pre" /* PRE */;
2793
+ subscribe(onUpdate) {
2445
2794
  if (this.keys !== void 0) {
2446
- return isPre ? this.updateEventEmitter.subscribeOnKeysBeforeFlush(
2447
- this.keys,
2448
- onInvalidate
2449
- ) : this.updateEventEmitter.subscribeOnKeys(
2450
- this.keys,
2451
- onInvalidate
2452
- );
2795
+ return this.subscription.subscribeOnKeys(this.keys, onUpdate);
2453
2796
  }
2454
2797
  if (this.key === void 0) return () => {
2455
2798
  };
2456
- return isPre ? this.updateEventEmitter.subscribeOnKeyBeforeFlush(
2457
- this.key,
2458
- onInvalidate
2459
- ) : this.updateEventEmitter.subscribeOnKey(this.key, onInvalidate);
2799
+ return this.subscription.subscribeOnKey(this.key, onUpdate);
2460
2800
  }
2461
2801
  };
2462
2802
 
@@ -2464,12 +2804,12 @@ var OIMEffectDependencyKeyed = class {
2464
2804
  var OIMEffectDependencyKeyedObject = class {
2465
2805
  constructor(object, keyOrKeys) {
2466
2806
  this.dep = new OIMEffectDependencyKeyed(
2467
- object.updateEventEmitter,
2807
+ object,
2468
2808
  keyOrKeys
2469
2809
  );
2470
2810
  }
2471
- subscribe(phase, onInvalidate) {
2472
- return this.dep.subscribe(phase, onInvalidate);
2811
+ subscribe(onUpdate) {
2812
+ return this.dep.subscribe(onUpdate);
2473
2813
  }
2474
2814
  };
2475
2815
 
@@ -2477,12 +2817,12 @@ var OIMEffectDependencyKeyedObject = class {
2477
2817
  var OIMEffectDependencyKeyedCollection = class {
2478
2818
  constructor(collection, pkOrPks) {
2479
2819
  this.dep = new OIMEffectDependencyKeyed(
2480
- collection.updateEventEmitter,
2820
+ collection,
2481
2821
  pkOrPks
2482
2822
  );
2483
2823
  }
2484
- subscribe(phase, onInvalidate) {
2485
- return this.dep.subscribe(phase, onInvalidate);
2824
+ subscribe(onUpdate) {
2825
+ return this.dep.subscribe(onUpdate);
2486
2826
  }
2487
2827
  };
2488
2828
 
@@ -2490,45 +2830,610 @@ var OIMEffectDependencyKeyedCollection = class {
2490
2830
  var OIMEffectDependencyKeyedIndex = class {
2491
2831
  constructor(index, keyOrKeys) {
2492
2832
  this.dep = new OIMEffectDependencyKeyed(
2493
- index.updateEventEmitter,
2833
+ index,
2494
2834
  keyOrKeys
2495
2835
  );
2496
2836
  }
2497
- subscribe(phase, onInvalidate) {
2498
- return this.dep.subscribe(phase, onInvalidate);
2837
+ subscribe(onUpdate) {
2838
+ return this.dep.subscribe(onUpdate);
2499
2839
  }
2500
2840
  };
2501
2841
 
2502
- // src/modules/effect/core/OIMEffectDependencyComputed.ts
2503
- var OIMEffectDependencyComputed = class {
2504
- constructor(computed) {
2505
- this.computed = computed;
2842
+ // src/modules/selector/core/OIMSelector.ts
2843
+ var OIMSelector = class {
2844
+ constructor(runtime, sourceDeps, computedDeps = []) {
2845
+ this.runtime = runtime;
2846
+ this.sourceDeps = sourceDeps;
2847
+ this.computedDeps = computedDeps;
2848
+ this.listeners = /* @__PURE__ */ new Set();
2849
+ this.sourceUnsubscribers = [];
2850
+ this.computedUnsubscribers = [];
2851
+ this.isResubscribeEnqueued = false;
2852
+ this.isTemporarilyUnsubscribedForFlush = false;
2853
+ this.isDeliveryEnqueued = false;
2854
+ this.hasLastValue = false;
2855
+ this.onSourceInvalidate = () => {
2856
+ if (this.listeners.size === 0) return;
2857
+ if (!this.isTemporarilyUnsubscribedForFlush) {
2858
+ this.isTemporarilyUnsubscribedForFlush = true;
2859
+ this.unsubscribeFromSourceDeps();
2860
+ this.ensureResubscribeAfterFlush();
2861
+ }
2862
+ if (this.isDeliveryEnqueued) return;
2863
+ this.isDeliveryEnqueued = true;
2864
+ this.dequeueDelivery = this.runtime.schedule(this.deliver);
2865
+ };
2866
+ this.onComputedInvalidate = () => {
2867
+ if (this.listeners.size === 0) return;
2868
+ if (this.isDeliveryEnqueued) return;
2869
+ this.isDeliveryEnqueued = true;
2870
+ this.dequeueDelivery = this.runtime.schedule(this.deliver);
2871
+ };
2872
+ this.deliver = () => {
2873
+ this.isDeliveryEnqueued = false;
2874
+ this.dequeueDelivery = void 0;
2875
+ if (this.listeners.size === 0) return;
2876
+ const next = this.getValue();
2877
+ if (this.hasLastValue && this.areEqual(this.lastValue, next)) return;
2878
+ this.setLastValue(next);
2879
+ const snapshot = Array.from(this.listeners);
2880
+ for (let i = 0; i < snapshot.length; i++) {
2881
+ const listener = snapshot[i];
2882
+ if (this.listeners.has(listener)) listener(next);
2883
+ }
2884
+ };
2885
+ this.onAfterFlush = () => {
2886
+ if (!this.isResubscribeEnqueued) return;
2887
+ this.isResubscribeEnqueued = false;
2888
+ this.dequeueResubscribe = void 0;
2889
+ if (this.listeners.size === 0) return;
2890
+ this.isTemporarilyUnsubscribedForFlush = false;
2891
+ if (this.sourceUnsubscribers.length === 0) {
2892
+ for (const dep of this.sourceDeps) {
2893
+ this.sourceUnsubscribers.push(
2894
+ dep.subscribe(this.onSourceInvalidate)
2895
+ );
2896
+ }
2897
+ }
2898
+ };
2899
+ }
2900
+ get queue() {
2901
+ return this.runtime.queue;
2902
+ }
2903
+ /**
2904
+ * Start watching this selector. The callback is called immediately with the current value,
2905
+ * then coalesced and delivered on `queue.flush()` when the selector invalidates.
2906
+ */
2907
+ watch(onValue) {
2908
+ this.listeners.add(onValue);
2909
+ if (this.listeners.size === 1) {
2910
+ this.subscribeToDeps();
2911
+ }
2912
+ const current = this.getValue();
2913
+ this.setLastValue(current);
2914
+ onValue(current);
2915
+ return () => {
2916
+ this.listeners.delete(onValue);
2917
+ if (this.listeners.size > 0) return;
2918
+ this.unsubscribeFromAllDeps();
2919
+ if (this.dequeueResubscribe) {
2920
+ this.dequeueResubscribe();
2921
+ this.dequeueResubscribe = void 0;
2922
+ }
2923
+ this.isResubscribeEnqueued = false;
2924
+ this.isTemporarilyUnsubscribedForFlush = false;
2925
+ if (this.dequeueDelivery) {
2926
+ this.dequeueDelivery();
2927
+ this.dequeueDelivery = void 0;
2928
+ }
2929
+ this.isDeliveryEnqueued = false;
2930
+ this.hasLastValue = false;
2931
+ };
2932
+ }
2933
+ areEqual(prev, next) {
2934
+ return Object.is(prev, next);
2935
+ }
2936
+ setLastValue(value) {
2937
+ this.lastValue = value;
2938
+ this.hasLastValue = true;
2939
+ }
2940
+ subscribeToDeps() {
2941
+ if (this.sourceUnsubscribers.length > 0 || this.computedUnsubscribers.length > 0)
2942
+ return;
2943
+ for (const dep of this.sourceDeps) {
2944
+ this.sourceUnsubscribers.push(
2945
+ dep.subscribe(this.onSourceInvalidate)
2946
+ );
2947
+ }
2948
+ for (const dep of this.computedDeps) {
2949
+ this.computedUnsubscribers.push(
2950
+ dep.subscribe(this.onComputedInvalidate)
2951
+ );
2952
+ }
2953
+ }
2954
+ unsubscribeFromSourceDeps() {
2955
+ if (this.sourceUnsubscribers.length === 0) return;
2956
+ for (const unsub of this.sourceUnsubscribers) unsub();
2957
+ this.sourceUnsubscribers = [];
2958
+ }
2959
+ unsubscribeFromAllDeps() {
2960
+ this.unsubscribeFromSourceDeps();
2961
+ if (this.computedUnsubscribers.length === 0) return;
2962
+ for (const unsub of this.computedUnsubscribers) unsub();
2963
+ this.computedUnsubscribers = [];
2964
+ }
2965
+ ensureResubscribeAfterFlush() {
2966
+ if (this.isResubscribeEnqueued) return;
2967
+ this.isResubscribeEnqueued = true;
2968
+ this.dequeueResubscribe = this.runtime.scheduleAfterFlush(
2969
+ this.onAfterFlush
2970
+ );
2971
+ }
2972
+ };
2973
+
2974
+ // src/modules/selector/core/OIMSelectorSourceDependencyUpdateEventEmitterKey.ts
2975
+ var OIMSelectorSourceDependencyUpdateEventEmitterKey = class {
2976
+ constructor(subscription, key) {
2977
+ this.subscription = subscription;
2978
+ this.key = key;
2979
+ }
2980
+ subscribe(onUpdate) {
2981
+ return this.subscription.subscribeOnKey(this.key, onUpdate);
2982
+ }
2983
+ };
2984
+
2985
+ // src/modules/selector/core/OIMCollectionByPkSelector.ts
2986
+ var OIMCollectionByPkSelector = class extends OIMSelector {
2987
+ constructor(runtime, collection, pk) {
2988
+ super(runtime, [
2989
+ new OIMSelectorSourceDependencyUpdateEventEmitterKey(
2990
+ collection,
2991
+ pk
2992
+ )
2993
+ ]);
2994
+ this.collection = collection;
2995
+ this.pk = pk;
2996
+ }
2997
+ getValue() {
2998
+ return this.collection.getOneByPk(this.pk);
2999
+ }
3000
+ };
3001
+
3002
+ // src/modules/selector/core/OIMSelectorSourceDependencyUpdateEventEmitterKeys.ts
3003
+ var OIMSelectorSourceDependencyUpdateEventEmitterKeys = class {
3004
+ constructor(subscription, keys) {
3005
+ this.subscription = subscription;
3006
+ this.keys = keys;
3007
+ }
3008
+ subscribe(onUpdate) {
3009
+ return this.subscription.subscribeOnKeys(this.keys, onUpdate);
3010
+ }
3011
+ };
3012
+
3013
+ // src/modules/selector/core/OIMCollectionByPksSelector.ts
3014
+ var OIMCollectionByPksSelector = class extends OIMSelector {
3015
+ constructor(runtime, collection, pks) {
3016
+ super(runtime, [
3017
+ new OIMSelectorSourceDependencyUpdateEventEmitterKeys(
3018
+ collection,
3019
+ pks
3020
+ )
3021
+ ]);
3022
+ this.collection = collection;
3023
+ this.pks = pks;
3024
+ }
3025
+ getValue() {
3026
+ return this.pks.map((pk) => this.collection.getOneByPk(pk));
3027
+ }
3028
+ areEqual(prev, next) {
3029
+ if (prev === next) return true;
3030
+ if (prev.length !== next.length) return false;
3031
+ for (let i = 0; i < prev.length; i++) {
3032
+ if (prev[i] !== next[i]) return false;
3033
+ }
3034
+ return true;
3035
+ }
3036
+ };
3037
+
3038
+ // src/modules/selector/core/OIMObjectValueByKeySelector.ts
3039
+ var OIMObjectValueByKeySelector = class extends OIMSelector {
3040
+ constructor(runtime, obj, key) {
3041
+ super(runtime, [
3042
+ new OIMSelectorSourceDependencyUpdateEventEmitterKey(
3043
+ obj,
3044
+ key
3045
+ )
3046
+ ]);
3047
+ this.obj = obj;
3048
+ this.key = key;
3049
+ }
3050
+ getValue() {
3051
+ return this.obj.get(this.key);
3052
+ }
3053
+ };
3054
+
3055
+ // src/modules/selector/core/OIMObjectValuesByKeysSelector.ts
3056
+ var OIMObjectValuesByKeysSelector = class extends OIMSelector {
3057
+ constructor(runtime, obj, keys) {
3058
+ super(runtime, [
3059
+ new OIMSelectorSourceDependencyUpdateEventEmitterKeys(
3060
+ obj,
3061
+ keys
3062
+ )
3063
+ ]);
3064
+ this.obj = obj;
3065
+ this.keys = keys;
3066
+ }
3067
+ getValue() {
3068
+ return this.keys.map((k) => this.obj.get(k));
3069
+ }
3070
+ areEqual(prev, next) {
3071
+ if (prev === next) return true;
3072
+ if (prev.length !== next.length) return false;
3073
+ for (let i = 0; i < prev.length; i++) {
3074
+ if (prev[i] !== next[i]) return false;
3075
+ }
3076
+ return true;
3077
+ }
3078
+ };
3079
+
3080
+ // src/modules/selector/core/OIMIndexPksByKeySetBasedSelector.ts
3081
+ var OIMIndexPksByKeySetBasedSelector = class extends OIMSelector {
3082
+ constructor(runtime, reactiveIndex, key) {
3083
+ super(runtime, [
3084
+ new OIMSelectorSourceDependencyUpdateEventEmitterKey(
3085
+ reactiveIndex,
3086
+ key
3087
+ )
3088
+ ]);
3089
+ this.reactiveIndex = reactiveIndex;
3090
+ this.key = key;
3091
+ }
3092
+ getValue() {
3093
+ return this.reactiveIndex.getPksByKey(this.key);
3094
+ }
3095
+ };
3096
+
3097
+ // src/modules/selector/core/OIMIndexPksByKeyArrayBasedSelector.ts
3098
+ var OIMIndexPksByKeyArrayBasedSelector = class extends OIMSelector {
3099
+ constructor(runtime, reactiveIndex, key) {
3100
+ super(runtime, [
3101
+ new OIMSelectorSourceDependencyUpdateEventEmitterKey(
3102
+ reactiveIndex,
3103
+ key
3104
+ )
3105
+ ]);
3106
+ this.reactiveIndex = reactiveIndex;
3107
+ this.key = key;
3108
+ }
3109
+ getValue() {
3110
+ return this.reactiveIndex.getPksByKey(this.key);
3111
+ }
3112
+ areEqual(prev, next) {
3113
+ if (prev === next) return true;
3114
+ if (prev.length !== next.length) return false;
3115
+ for (let i = 0; i < prev.length; i++)
3116
+ if (prev[i] !== next[i]) return false;
3117
+ return true;
3118
+ }
3119
+ };
3120
+
3121
+ // src/modules/selector/core/OIMIndexPksByKeysSetBasedSelector.ts
3122
+ var OIMIndexPksByKeysSetBasedSelector = class extends OIMSelector {
3123
+ constructor(runtime, reactiveIndex, keys) {
3124
+ super(runtime, [
3125
+ new OIMSelectorSourceDependencyUpdateEventEmitterKeys(
3126
+ reactiveIndex,
3127
+ keys
3128
+ )
3129
+ ]);
3130
+ this.reactiveIndex = reactiveIndex;
3131
+ this.keys = keys;
3132
+ }
3133
+ getValue() {
3134
+ return this.keys.map((key) => Array.from(this.reactiveIndex.getPksByKey(key))).flat();
3135
+ }
3136
+ areEqual(prev, next) {
3137
+ if (prev === next) return true;
3138
+ if (prev.length !== next.length) return false;
3139
+ for (let i = 0; i < prev.length; i++)
3140
+ if (prev[i] !== next[i]) return false;
3141
+ return true;
3142
+ }
3143
+ };
3144
+
3145
+ // src/modules/selector/core/OIMIndexPksByKeysArrayBasedSelector.ts
3146
+ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
3147
+ constructor(runtime, reactiveIndex, keys) {
3148
+ super(runtime, [
3149
+ new OIMSelectorSourceDependencyUpdateEventEmitterKeys(
3150
+ reactiveIndex,
3151
+ keys
3152
+ )
3153
+ ]);
3154
+ this.reactiveIndex = reactiveIndex;
3155
+ this.keys = keys;
3156
+ }
3157
+ getValue() {
3158
+ return this.keys.map((key) => this.reactiveIndex.getPksByKey(key)).flat();
3159
+ }
3160
+ areEqual(prev, next) {
3161
+ if (prev === next) return true;
3162
+ if (prev.length !== next.length) return false;
3163
+ for (let i = 0; i < prev.length; i++)
3164
+ if (prev[i] !== next[i]) return false;
3165
+ return true;
3166
+ }
3167
+ };
3168
+
3169
+ // src/modules/selector/core/OIMSelectorSourceDependencyEntitiesByIndexKeySetBased.ts
3170
+ var OIMSelectorSourceDependencyEntitiesByIndexKeySetBased = class {
3171
+ constructor(collection, reactiveIndex, key) {
3172
+ this.collection = collection;
3173
+ this.reactiveIndex = reactiveIndex;
3174
+ this.key = key;
3175
+ this.currentPks = [];
3176
+ }
3177
+ subscribe(onUpdate) {
3178
+ const resubscribeCollection = () => {
3179
+ const nextPks = Array.from(
3180
+ this.reactiveIndex.getSlotsByKey(this.key),
3181
+ (slot) => slot.pk
3182
+ );
3183
+ if (this.sameArray(this.currentPks, nextPks)) return;
3184
+ this.unsubscribeFromCollectionKeys?.();
3185
+ this.unsubscribeFromCollectionKeys = void 0;
3186
+ this.currentPks = nextPks;
3187
+ if (this.currentPks.length === 0) return;
3188
+ this.unsubscribeFromCollectionKeys = this.collection.subscribeOnKeys(this.currentPks, onUpdate);
3189
+ };
3190
+ const onIndexUpdate = () => {
3191
+ resubscribeCollection();
3192
+ onUpdate();
3193
+ };
3194
+ resubscribeCollection();
3195
+ const unsubscribeFromIndex = this.reactiveIndex.subscribeOnKey(
3196
+ this.key,
3197
+ onIndexUpdate
3198
+ );
3199
+ return () => {
3200
+ unsubscribeFromIndex();
3201
+ this.unsubscribeFromCollectionKeys?.();
3202
+ this.unsubscribeFromCollectionKeys = void 0;
3203
+ this.currentPks = [];
3204
+ };
2506
3205
  }
2507
- subscribe(phase, onInvalidate) {
2508
- if (phase === "pre" /* PRE */) {
2509
- return this.computed.emitter.on("update", onInvalidate);
3206
+ sameArray(a, b) {
3207
+ if (a === b) return true;
3208
+ if (a.length !== b.length) return false;
3209
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
3210
+ return true;
3211
+ }
3212
+ };
3213
+
3214
+ // src/modules/selector/core/OIMEntitiesByIndexKeySetBasedSelector.ts
3215
+ var OIMEntitiesByIndexKeySetBasedSelector = class extends OIMSelector {
3216
+ constructor(runtime, collection, reactiveIndex, key) {
3217
+ super(runtime, [
3218
+ new OIMSelectorSourceDependencyEntitiesByIndexKeySetBased(collection, reactiveIndex, key)
3219
+ ]);
3220
+ this.collection = collection;
3221
+ this.reactiveIndex = reactiveIndex;
3222
+ this.key = key;
3223
+ this.reactiveIndex.index.setSlotResolver(
3224
+ (pk) => this.collection.getSlotByPk(pk)
3225
+ );
3226
+ }
3227
+ getValue() {
3228
+ const slots = this.reactiveIndex.getSlotsByKey(this.key);
3229
+ const entities = [];
3230
+ entities.length = slots.size;
3231
+ let writeIndex = 0;
3232
+ for (const slot of slots) {
3233
+ entities[writeIndex++] = slot.item;
2510
3234
  }
2511
- return this.computed.updateEventEmitter.subscribeOnKey(
2512
- "value",
2513
- onInvalidate
3235
+ return entities;
3236
+ }
3237
+ areEqual(prev, next) {
3238
+ if (prev === next) return true;
3239
+ if (prev.length !== next.length) return false;
3240
+ for (let i = 0; i < prev.length; i++)
3241
+ if (prev[i] !== next[i]) return false;
3242
+ return true;
3243
+ }
3244
+ };
3245
+
3246
+ // src/modules/selector/core/OIMSelectorSourceDependencyEntitiesByIndexKeyArrayBased.ts
3247
+ var OIMSelectorSourceDependencyEntitiesByIndexKeyArrayBased = class {
3248
+ constructor(collection, reactiveIndex, key) {
3249
+ this.collection = collection;
3250
+ this.reactiveIndex = reactiveIndex;
3251
+ this.key = key;
3252
+ this.currentPks = [];
3253
+ }
3254
+ subscribe(onUpdate) {
3255
+ const resubscribeCollection = () => {
3256
+ const nextPks = this.reactiveIndex.getSlotsByKey(this.key).map((slot) => slot.pk);
3257
+ if (this.sameArray(this.currentPks, nextPks)) return;
3258
+ this.unsubscribeFromCollectionKeys?.();
3259
+ this.unsubscribeFromCollectionKeys = void 0;
3260
+ this.currentPks = nextPks;
3261
+ if (this.currentPks.length === 0) return;
3262
+ this.unsubscribeFromCollectionKeys = this.collection.subscribeOnKeys(this.currentPks, onUpdate);
3263
+ };
3264
+ const onIndexUpdate = () => {
3265
+ resubscribeCollection();
3266
+ onUpdate();
3267
+ };
3268
+ resubscribeCollection();
3269
+ const unsubscribeFromIndex = this.reactiveIndex.subscribeOnKey(
3270
+ this.key,
3271
+ onIndexUpdate
3272
+ );
3273
+ return () => {
3274
+ unsubscribeFromIndex();
3275
+ this.unsubscribeFromCollectionKeys?.();
3276
+ this.unsubscribeFromCollectionKeys = void 0;
3277
+ this.currentPks = [];
3278
+ };
3279
+ }
3280
+ sameArray(a, b) {
3281
+ if (a === b) return true;
3282
+ if (a.length !== b.length) return false;
3283
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
3284
+ return true;
3285
+ }
3286
+ };
3287
+
3288
+ // src/modules/selector/core/OIMEntitiesByIndexKeyArrayBasedSelector.ts
3289
+ var OIMEntitiesByIndexKeyArrayBasedSelector = class extends OIMSelector {
3290
+ constructor(runtime, collection, reactiveIndex, key) {
3291
+ super(runtime, [
3292
+ new OIMSelectorSourceDependencyEntitiesByIndexKeyArrayBased(collection, reactiveIndex, key)
3293
+ ]);
3294
+ this.collection = collection;
3295
+ this.reactiveIndex = reactiveIndex;
3296
+ this.key = key;
3297
+ this.reactiveIndex.index.setSlotResolver(
3298
+ (pk) => this.collection.getSlotByPk(pk)
2514
3299
  );
2515
3300
  }
3301
+ getValue() {
3302
+ const slots = this.reactiveIndex.getSlotsByKey(this.key);
3303
+ const entities = [];
3304
+ entities.length = slots.length;
3305
+ for (let i = 0; i < slots.length; i++) {
3306
+ entities[i] = slots[i].item;
3307
+ }
3308
+ return entities;
3309
+ }
3310
+ areEqual(prev, next) {
3311
+ if (prev === next) return true;
3312
+ if (prev.length !== next.length) return false;
3313
+ for (let i = 0; i < prev.length; i++)
3314
+ if (prev[i] !== next[i]) return false;
3315
+ return true;
3316
+ }
3317
+ };
3318
+
3319
+ // src/modules/computative/core/OIMComputativeRuntime.ts
3320
+ var OIMComputativeRuntime = class {
3321
+ constructor(queue) {
3322
+ this.queue = queue;
3323
+ this.isFlushScheduled = false;
3324
+ this.isFlushing = false;
3325
+ this.minLevel = Infinity;
3326
+ this.maxLevel = -Infinity;
3327
+ this.dirtyByLevel = /* @__PURE__ */ new Map();
3328
+ this.afterFlushTasks = /* @__PURE__ */ new Set();
3329
+ this.queue.emitter.on(0 /* AFTER_FLUSH */, () => {
3330
+ if (!this.isFlushScheduled) return;
3331
+ this.flush();
3332
+ });
3333
+ }
3334
+ /**
3335
+ * Schedule a one-shot task to run on the next `queue.flush()` (AFTER_FLUSH),
3336
+ * ordered by `level` ascending.
3337
+ */
3338
+ schedule(task, level = 0) {
3339
+ let isActive = true;
3340
+ const wrapped = () => {
3341
+ if (!isActive) return;
3342
+ task();
3343
+ };
3344
+ let bucket = this.dirtyByLevel.get(level);
3345
+ if (!bucket) {
3346
+ bucket = /* @__PURE__ */ new Set();
3347
+ this.dirtyByLevel.set(level, bucket);
3348
+ }
3349
+ bucket.add(wrapped);
3350
+ if (level < this.minLevel) this.minLevel = level;
3351
+ if (level > this.maxLevel) this.maxLevel = level;
3352
+ if (!this.isFlushing) this.isFlushScheduled = true;
3353
+ return () => {
3354
+ if (!isActive) return;
3355
+ isActive = false;
3356
+ bucket.delete(wrapped);
3357
+ };
3358
+ }
3359
+ /**
3360
+ * Run once at the start of the next runtime flush (i.e. after queue.flush()).
3361
+ * Useful for resubscribing dependencies exactly once per source flush.
3362
+ */
3363
+ scheduleAfterFlush(task) {
3364
+ let isActive = true;
3365
+ const wrapped = () => {
3366
+ if (!isActive) return;
3367
+ task();
3368
+ };
3369
+ this.afterFlushTasks.add(wrapped);
3370
+ if (!this.isFlushing) this.isFlushScheduled = true;
3371
+ return () => {
3372
+ if (!isActive) return;
3373
+ isActive = false;
3374
+ this.afterFlushTasks.delete(wrapped);
3375
+ };
3376
+ }
3377
+ flush() {
3378
+ this.isFlushScheduled = false;
3379
+ if (this.dirtyByLevel.size === 0 && this.afterFlushTasks.size === 0)
3380
+ return;
3381
+ this.isFlushing = true;
3382
+ try {
3383
+ if (this.afterFlushTasks.size > 0) {
3384
+ const snapshot = Array.from(this.afterFlushTasks);
3385
+ this.afterFlushTasks.clear();
3386
+ for (let i = 0; i < snapshot.length; i++) snapshot[i]();
3387
+ }
3388
+ if (this.dirtyByLevel.size === 0) return;
3389
+ let level = this.minLevel;
3390
+ let max = this.maxLevel;
3391
+ while (level <= max) {
3392
+ for (; ; ) {
3393
+ const bucket = this.dirtyByLevel.get(level);
3394
+ if (!bucket || bucket.size === 0) break;
3395
+ this.dirtyByLevel.delete(level);
3396
+ const snapshot = Array.from(bucket);
3397
+ for (let i = 0; i < snapshot.length; i++) snapshot[i]();
3398
+ }
3399
+ if (this.maxLevel > max) max = this.maxLevel;
3400
+ level++;
3401
+ }
3402
+ } finally {
3403
+ this.isFlushing = false;
3404
+ if (this.dirtyByLevel.size === 0) {
3405
+ this.minLevel = Infinity;
3406
+ this.maxLevel = -Infinity;
3407
+ } else {
3408
+ let nextMin = Infinity;
3409
+ let nextMax = -Infinity;
3410
+ this.dirtyByLevel.forEach((_bucket, lvl) => {
3411
+ if (lvl < nextMin) nextMin = lvl;
3412
+ if (lvl > nextMax) nextMax = lvl;
3413
+ });
3414
+ this.minLevel = nextMin;
3415
+ this.maxLevel = nextMax;
3416
+ this.isFlushScheduled = true;
3417
+ }
3418
+ }
3419
+ }
2516
3420
  };
2517
3421
  // Annotate the CommonJS export names for ESM import in node:
2518
3422
  0 && (module.exports = {
2519
3423
  EOIMCollectionEventType,
2520
3424
  EOIMComputedEventType,
2521
- EOIMEffectPhase,
2522
3425
  EOIMEventQueueEventType,
2523
3426
  EOIMEventQueueSchedulerEventType,
2524
3427
  EOIMIndexEventType,
2525
3428
  EOIMObjectEventType,
2526
- EOIMUpdateEventCoalescerEventType,
2527
3429
  OIMCollection,
3430
+ OIMCollectionByPkSelector,
3431
+ OIMCollectionByPksSelector,
2528
3432
  OIMCollectionChangedFieldsWrapper,
2529
3433
  OIMCollectionStore,
2530
3434
  OIMCollectionStoreMapDriven,
2531
3435
  OIMComparatorFactory,
3436
+ OIMComputativeRuntime,
2532
3437
  OIMComputed,
2533
3438
  OIMDBSettings,
2534
3439
  OIMEffect,
@@ -2537,6 +3442,8 @@ var OIMEffectDependencyComputed = class {
2537
3442
  OIMEffectDependencyKeyedCollection,
2538
3443
  OIMEffectDependencyKeyedIndex,
2539
3444
  OIMEffectDependencyKeyedObject,
3445
+ OIMEntitiesByIndexKeyArrayBasedSelector,
3446
+ OIMEntitiesByIndexKeySetBasedSelector,
2540
3447
  OIMEntityUpdaterFactory,
2541
3448
  OIMEventEmitter,
2542
3449
  OIMEventQueue,
@@ -2551,6 +3458,10 @@ var OIMEffectDependencyComputed = class {
2551
3458
  OIMIndexManualArrayBased,
2552
3459
  OIMIndexManualOrderedArrayBased,
2553
3460
  OIMIndexManualSetBased,
3461
+ OIMIndexPksByKeyArrayBasedSelector,
3462
+ OIMIndexPksByKeySetBasedSelector,
3463
+ OIMIndexPksByKeysArrayBasedSelector,
3464
+ OIMIndexPksByKeysSetBasedSelector,
2554
3465
  OIMIndexSetBased,
2555
3466
  OIMIndexStoreArrayBased,
2556
3467
  OIMIndexStoreMapDrivenArrayBased,
@@ -2561,6 +3472,8 @@ var OIMEffectDependencyComputed = class {
2561
3472
  OIMObjectStore,
2562
3473
  OIMObjectStoreMapDriven,
2563
3474
  OIMObjectStoreRecordDriven,
3475
+ OIMObjectValueByKeySelector,
3476
+ OIMObjectValuesByKeysSelector,
2564
3477
  OIMOrderedListIndexCommandStreamWrapper,
2565
3478
  OIMPkSelectorFactory,
2566
3479
  OIMRICollection,
@@ -2570,11 +3483,5 @@ var OIMEffectDependencyComputed = class {
2570
3483
  OIMReactiveIndexManualSetBased,
2571
3484
  OIMReactiveIndexSetBased,
2572
3485
  OIMReactiveObject,
2573
- OIMUpdateEventCoalescer,
2574
- OIMUpdateEventCoalescerCollection,
2575
- OIMUpdateEventCoalescerComputed,
2576
- OIMUpdateEventCoalescerIndex,
2577
- OIMUpdateEventCoalescerManual,
2578
- OIMUpdateEventCoalescerObject,
2579
- OIMUpdateEventEmitter
3486
+ OIMSelector
2580
3487
  });