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