@oimdb/core 1.0.6 → 1.1.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/README.md CHANGED
@@ -65,9 +65,7 @@ const queue = new OIMEventQueue({
65
65
 
66
66
  // Create reactive collection
67
67
  const users = new OIMReactiveCollection<User, string>(queue, {
68
- collectionOpts: {
69
- selectPk: (user) => user.id
70
- }
68
+ selectPk: (user) => user.id
71
69
  });
72
70
 
73
71
  // Subscribe to key-specific updates
@@ -249,10 +247,8 @@ const deepMergeUpdater: TOIMEntityUpdater<User> = (newEntity, oldEntity) => {
249
247
  // Use custom updater with reactive collection
250
248
  const queue = new OIMEventQueue();
251
249
  const users = new OIMReactiveCollection<User, string>(queue, {
252
- collectionOpts: {
253
- selectPk: (user) => user.id,
254
- updateEntity: deepMergeUpdater
255
- }
250
+ selectPk: (user) => user.id,
251
+ updateEntity: deepMergeUpdater
256
252
  });
257
253
 
258
254
  // Now updates will use deep merge logic
@@ -393,9 +389,7 @@ Reactive collection with automatic change notifications and event coalescing.
393
389
 
394
390
  **Constructor:**
395
391
  ```typescript
396
- new OIMReactiveCollection(queue: OIMEventQueue, opts?: {
397
- collectionOpts?: TOIMCollectionOptions<TEntity, TPk>
398
- })
392
+ new OIMReactiveCollection(queue: OIMEventQueue, opts?: TOIMCollectionOptions<TEntity, TPk>)
399
393
  ```
400
394
 
401
395
  **Properties:**
@@ -411,19 +405,19 @@ new OIMReactiveCollection(queue: OIMEventQueue, opts?: {
411
405
  - `getOneByPk(pk: TPk): TEntity | undefined` - Get entity by primary key
412
406
  - `getManyByPks(pks: readonly TPk[]): Map<TPk, TEntity | undefined>` - Get multiple entities
413
407
 
414
- #### `OIMRICollection<TEntity, TPk, TIndexName, TIndexKey, TIndex, TReactiveIndex>`
408
+ #### `OIMRICollection<TEntity, TPk, TIndexName, TIndexKey, TIndex, TReactiveIndex, TReactiveIndexMap>`
415
409
  Reactive collection with integrated indexing capabilities.
416
410
 
417
411
  **Constructor:**
418
412
  ```typescript
419
413
  new OIMRICollection(queue: OIMEventQueue, opts: {
420
414
  collectionOpts?: TOIMCollectionOptions<TEntity, TPk>;
421
- indexes: Record<TIndexName, TReactiveIndex>;
415
+ indexes: TReactiveIndexMap;
422
416
  })
423
417
  ```
424
418
 
425
419
  **Properties:**
426
- - `indexes: Record<TIndexName, TReactiveIndex>` - Named reactive indexes
420
+ - `indexes: TReactiveIndexMap` - Named reactive indexes preserving index-to-name mapping
427
421
  - *(inherits all OIMReactiveCollection properties)*
428
422
 
429
423
  #### `OIMReactiveIndexManual<TKey, TPk>`
@@ -529,9 +523,7 @@ describe('OIMReactiveCollection', () => {
529
523
  });
530
524
 
531
525
  users = new OIMReactiveCollection(queue, {
532
- collectionOpts: {
533
- selectPk: (user) => user.id
534
- }
526
+ selectPk: (user) => user.id
535
527
  });
536
528
  });
537
529
 
package/dist/index.cjs CHANGED
@@ -28,6 +28,7 @@ __export(index_exports, {
28
28
  OIMCollectionStore: () => OIMCollectionStore,
29
29
  OIMCollectionStoreMapDriven: () => OIMCollectionStoreMapDriven,
30
30
  OIMComparatorFactory: () => OIMComparatorFactory,
31
+ OIMDBSettings: () => OIMDBSettings,
31
32
  OIMEntityUpdaterFactory: () => OIMEntityUpdaterFactory,
32
33
  OIMEventEmitter: () => OIMEventEmitter,
33
34
  OIMEventQueue: () => OIMEventQueue,
@@ -182,18 +183,66 @@ var EOIMIndexEventType = /* @__PURE__ */ ((EOIMIndexEventType2) => {
182
183
  return EOIMIndexEventType2;
183
184
  })(EOIMIndexEventType || {});
184
185
 
186
+ // src/abstract/OIMIndexStore.ts
187
+ var OIMIndexStore = class {
188
+ };
189
+
190
+ // src/core/OIMIndexStoreMapDriven.ts
191
+ var OIMIndexStoreMapDriven = class extends OIMIndexStore {
192
+ constructor() {
193
+ super(...arguments);
194
+ this.pks = /* @__PURE__ */ new Map();
195
+ }
196
+ setOneByKey(key, pks) {
197
+ this.pks.set(key, pks);
198
+ }
199
+ removeOneByKey(key) {
200
+ this.pks.delete(key);
201
+ }
202
+ removeManyByKeys(keys) {
203
+ for (const key of keys) {
204
+ this.pks.delete(key);
205
+ }
206
+ }
207
+ getOneByKey(key) {
208
+ return this.pks.get(key);
209
+ }
210
+ getManyByKeys(keys) {
211
+ const result = /* @__PURE__ */ new Map();
212
+ for (const key of keys) {
213
+ const pks = this.getOneByKey(key);
214
+ if (pks !== void 0) {
215
+ result.set(key, pks);
216
+ }
217
+ }
218
+ return result;
219
+ }
220
+ getAllKeys() {
221
+ return Array.from(this.pks.keys());
222
+ }
223
+ getAll() {
224
+ return new Map(this.pks);
225
+ }
226
+ countAll() {
227
+ return this.pks.size;
228
+ }
229
+ clear() {
230
+ this.pks.clear();
231
+ }
232
+ };
233
+
185
234
  // src/abstract/OIMIndex.ts
186
235
  var OIMIndex = class {
187
236
  constructor(options = {}) {
188
- this.pks = /* @__PURE__ */ new Map();
189
237
  this.emitter = new OIMEventEmitter();
190
238
  this.comparePks = options.comparePks;
239
+ this.store = options.store ?? new OIMIndexStoreMapDriven();
191
240
  }
192
241
  /**
193
242
  * Get primary keys for multiple index keys
194
243
  */
195
244
  getPksByKeys(keys) {
196
- return new Map(keys.map((key) => [key, this.getPks(key)]));
245
+ return this.store.getManyByKeys(keys);
197
246
  }
198
247
  /**
199
248
  * @deprecated Use getPksByKey instead
@@ -206,39 +255,39 @@ var OIMIndex = class {
206
255
  * Get primary keys for a specific index key
207
256
  */
208
257
  getPksByKey(key) {
209
- const pksSet = this.pks.get(key);
258
+ const pksSet = this.store.getOneByKey(key);
210
259
  return pksSet ? pksSet : /* @__PURE__ */ new Set();
211
260
  }
212
261
  /**
213
262
  * Check if an index key exists
214
263
  */
215
264
  hasKey(key) {
216
- return this.pks.has(key);
265
+ return this.store.getOneByKey(key) !== void 0;
217
266
  }
218
267
  /**
219
268
  * Get all index keys
220
269
  */
221
270
  getKeys() {
222
- return Array.from(this.pks.keys());
271
+ return this.store.getAllKeys();
223
272
  }
224
273
  /**
225
274
  * Get the number of primary keys for a specific index key
226
275
  */
227
276
  getKeySize(key) {
228
- const pksSet = this.pks.get(key);
277
+ const pksSet = this.store.getOneByKey(key);
229
278
  return pksSet ? pksSet.size : 0;
230
279
  }
231
280
  /**
232
281
  * Get total number of index keys
233
282
  */
234
283
  get size() {
235
- return this.pks.size;
284
+ return this.store.countAll();
236
285
  }
237
286
  /**
238
287
  * Check if the index is empty
239
288
  */
240
289
  get isEmpty() {
241
- return this.pks.size === 0;
290
+ return this.store.countAll() === 0;
242
291
  }
243
292
  /**
244
293
  * Get performance metrics for monitoring and debugging
@@ -247,15 +296,16 @@ var OIMIndex = class {
247
296
  let totalPks = 0;
248
297
  let maxBucketSize = 0;
249
298
  let minBucketSize = Infinity;
250
- for (const pksSet of this.pks.values()) {
299
+ const allPks = this.store.getAll();
300
+ for (const pksSet of allPks.values()) {
251
301
  totalPks += pksSet.size;
252
302
  maxBucketSize = Math.max(maxBucketSize, pksSet.size);
253
303
  minBucketSize = Math.min(minBucketSize, pksSet.size);
254
304
  }
255
305
  return {
256
- totalKeys: this.pks.size,
306
+ totalKeys: allPks.size,
257
307
  totalPks,
258
- averagePksPerKey: this.pks.size > 0 ? totalPks / this.pks.size : 0,
308
+ averagePksPerKey: allPks.size > 0 ? totalPks / allPks.size : 0,
259
309
  maxBucketSize: maxBucketSize === -Infinity ? 0 : maxBucketSize,
260
310
  minBucketSize: minBucketSize === Infinity ? 0 : minBucketSize
261
311
  };
@@ -265,21 +315,26 @@ var OIMIndex = class {
265
315
  */
266
316
  destroy() {
267
317
  this.emitter.offAll();
268
- this.pks.clear();
318
+ this.store.clear();
269
319
  }
270
320
  /**
271
321
  * Set primary keys for a specific index key with optional comparison.
272
322
  * If comparator is provided and returns true (no changes), skip the update.
273
323
  */
274
324
  setPksWithComparison(key, newPks) {
275
- const existingPks = this.getPks(key).values();
276
- if (this.comparePks && this.comparePks(
277
- Array.from(existingPks),
278
- Array.from(newPks.values())
279
- )) {
280
- return false;
325
+ if (this.comparePks) {
326
+ const existingPksSet = this.store.getOneByKey(key);
327
+ if (existingPksSet && existingPksSet.size === newPks.size) {
328
+ const existingPksArray = existingPksSet.size > 0 ? [...existingPksSet] : [];
329
+ const newPksArray = newPks.size > 0 ? [...newPks] : [];
330
+ if (this.comparePks(existingPksArray, newPksArray)) {
331
+ return false;
332
+ }
333
+ } else if (!existingPksSet && newPks.size === 0) {
334
+ return false;
335
+ }
281
336
  }
282
- this.pks.set(key, newPks);
337
+ this.store.setOneByKey(key, newPks);
283
338
  return true;
284
339
  }
285
340
  /**
@@ -414,35 +469,133 @@ var OIMUpdateEventEmitter = class {
414
469
  }
415
470
  };
416
471
 
472
+ // src/core/OIMIndexManual.ts
473
+ var OIMIndexManual = class extends OIMIndex {
474
+ constructor(options = {}) {
475
+ super(options);
476
+ }
477
+ /**
478
+ * Set primary keys for a specific index key, replacing any existing values.
479
+ * Uses optional comparator to skip updates if PKs haven't actually changed.
480
+ */
481
+ setPks(key, pks) {
482
+ const hasChanges = this.setPksWithComparison(key, new Set(pks));
483
+ if (hasChanges) {
484
+ this.emitUpdate([key]);
485
+ }
486
+ }
487
+ /**
488
+ * Add primary keys to a specific index key
489
+ */
490
+ addPks(key, pks) {
491
+ if (pks.length === 0) return;
492
+ let pksSet = this.store.getOneByKey(key);
493
+ if (!pksSet) {
494
+ pksSet = /* @__PURE__ */ new Set();
495
+ this.store.setOneByKey(key, pksSet);
496
+ }
497
+ let hasChanges = false;
498
+ for (const pk of pks) {
499
+ if (!pksSet.has(pk)) {
500
+ pksSet.add(pk);
501
+ hasChanges = true;
502
+ }
503
+ }
504
+ if (hasChanges) {
505
+ this.store.setOneByKey(key, pksSet);
506
+ this.emitUpdate([key]);
507
+ }
508
+ }
509
+ /**
510
+ * Remove primary keys from a specific index key
511
+ */
512
+ removePks(key, pks) {
513
+ if (pks.length === 0) return;
514
+ const pksSet = this.store.getOneByKey(key);
515
+ if (!pksSet) return;
516
+ let hasChanges = false;
517
+ for (const pk of pks) {
518
+ if (pksSet.delete(pk)) {
519
+ hasChanges = true;
520
+ }
521
+ }
522
+ if (pksSet.size === 0) {
523
+ this.store.removeOneByKey(key);
524
+ hasChanges = true;
525
+ } else if (hasChanges) {
526
+ this.store.setOneByKey(key, pksSet);
527
+ }
528
+ if (hasChanges) {
529
+ this.emitUpdate([key]);
530
+ }
531
+ }
532
+ /**
533
+ * Clear all primary keys for a specific index key, or all keys if no key specified
534
+ */
535
+ clear(key) {
536
+ if (key === void 0) {
537
+ const allKeys = this.store.getAllKeys();
538
+ if (allKeys.length > 0) {
539
+ this.store.clear();
540
+ this.emitUpdate(allKeys);
541
+ }
542
+ } else {
543
+ if (this.store.getOneByKey(key) !== void 0) {
544
+ this.store.removeOneByKey(key);
545
+ this.emitUpdate([key]);
546
+ }
547
+ }
548
+ }
549
+ };
550
+
551
+ // src/const/OIMDBSettings.ts
552
+ var OIMDBSettings = class {
553
+ };
554
+ /**
555
+ * Fixed key used in the manual index to store updated keys set in coalescers.
556
+ * This constant is exported so users can create their own indexes and pass them to coalescers.
557
+ */
558
+ OIMDBSettings.UPDATED_KEYS_INDEX_KEY = "__updatedKeys__";
559
+
417
560
  // src/core/OIMUpdateEventCoalescer.ts
418
561
  var OIMUpdateEventCoalescer = class {
419
- constructor() {
562
+ constructor(index) {
420
563
  this.emitter = new OIMEventEmitter();
421
- this.updatedKeys = /* @__PURE__ */ new Set();
422
564
  this.hasEmittedChanges = false;
565
+ this.updatedKeysIndex = index ?? new OIMIndexManual({
566
+ store: new OIMIndexStoreMapDriven()
567
+ });
568
+ if (!this.updatedKeysIndex.hasKey(OIMDBSettings.UPDATED_KEYS_INDEX_KEY)) {
569
+ this.updatedKeysIndex.setPks(
570
+ OIMDBSettings.UPDATED_KEYS_INDEX_KEY,
571
+ []
572
+ );
573
+ }
423
574
  }
424
575
  /**
425
576
  * Get the set of keys that have been updated since last clear
426
577
  */
427
578
  getUpdatedKeys() {
428
- return this.updatedKeys;
579
+ return this.updatedKeysIndex.getPksByKey(
580
+ OIMDBSettings.UPDATED_KEYS_INDEX_KEY
581
+ );
429
582
  }
430
583
  /**
431
584
  * Clear all updated keys and reset the changed state
432
585
  */
433
586
  clearUpdatedKeys() {
434
- this.updatedKeys.clear();
587
+ this.updatedKeysIndex.setPks(OIMDBSettings.UPDATED_KEYS_INDEX_KEY, []);
435
588
  this.hasEmittedChanges = false;
436
589
  }
437
590
  /**
438
591
  * Add keys to the updated set and emit HAS_CHANGES event if needed
439
592
  */
440
593
  addUpdatedKeys(keys) {
441
- const hadChanges = this.updatedKeys.size > 0;
442
- for (const key of keys) {
443
- this.updatedKeys.add(key);
444
- }
445
- if (!hadChanges && this.updatedKeys.size > 0 && !this.hasEmittedChanges) {
594
+ this.updatedKeysIndex.addPks(
595
+ OIMDBSettings.UPDATED_KEYS_INDEX_KEY,
596
+ keys
597
+ );
598
+ if (!this.hasEmittedChanges) {
446
599
  this.hasEmittedChanges = true;
447
600
  this.emitter.emit(
448
601
  0 /* HAS_CHANGES */,
@@ -461,23 +614,14 @@ var OIMUpdateEventCoalescerIndex = class extends OIMUpdateEventCoalescer {
461
614
  * When the index is updated, we keep track of the updated keys.
462
615
  */
463
616
  this.handleUpdate = (payload) => {
464
- const hadChanges = this.updatedKeys.size > 0;
465
- for (const key of payload.keys) {
466
- this.updatedKeys.add(key);
467
- }
468
- if (!hadChanges && this.updatedKeys.size > 0) {
469
- this.emitter.emit(
470
- 0 /* HAS_CHANGES */,
471
- void 0
472
- );
473
- }
617
+ this.addUpdatedKeys(payload.keys);
474
618
  };
475
619
  this.indexEmitter.on(0 /* UPDATE */, this.handleUpdate);
476
620
  }
477
621
  destroy() {
478
622
  this.indexEmitter.off(0 /* UPDATE */, this.handleUpdate);
479
623
  this.emitter.offAll();
480
- this.updatedKeys.clear();
624
+ this.clearUpdatedKeys();
481
625
  }
482
626
  };
483
627
 
@@ -548,14 +692,24 @@ var OIMCollectionStoreMapDriven = class extends OIMCollectionStore {
548
692
  }
549
693
  removeManyByPks(pks) {
550
694
  for (const pk of pks) {
551
- this.removeOneByPk(pk);
695
+ this.entities.delete(pk);
552
696
  }
553
697
  }
554
698
  getOneByPk(pk) {
555
699
  return this.entities.get(pk);
556
700
  }
557
701
  getManyByPks(pks) {
558
- return pks.map((pk) => this.getOneByPk(pk)).filter(Boolean);
702
+ const result = [];
703
+ result.length = pks.length;
704
+ let writeIndex = 0;
705
+ for (let i = 0; i < pks.length; i++) {
706
+ const entity = this.getOneByPk(pks[i]);
707
+ if (entity !== void 0) {
708
+ result[writeIndex++] = entity;
709
+ }
710
+ }
711
+ result.length = writeIndex;
712
+ return result;
559
713
  }
560
714
  getAll() {
561
715
  return Array.from(this.entities.values());
@@ -657,16 +811,7 @@ var OIMUpdateEventCoalescerCollection = class extends OIMUpdateEventCoalescer {
657
811
  * When the collection is updated, we keep track of the updated pks.
658
812
  */
659
813
  this.handleUpdate = (payload) => {
660
- const hadChanges = this.updatedKeys.size > 0;
661
- for (const pk of payload.pks) {
662
- this.updatedKeys.add(pk);
663
- }
664
- if (!hadChanges && this.updatedKeys.size > 0) {
665
- this.emitter.emit(
666
- 0 /* HAS_CHANGES */,
667
- void 0
668
- );
669
- }
814
+ this.addUpdatedKeys(payload.pks);
670
815
  };
671
816
  this.collectionEmitter.on(
672
817
  0 /* UPDATE */,
@@ -679,129 +824,22 @@ var OIMUpdateEventCoalescerCollection = class extends OIMUpdateEventCoalescer {
679
824
  this.handleUpdate
680
825
  );
681
826
  this.emitter.offAll();
682
- this.updatedKeys.clear();
827
+ this.clearUpdatedKeys();
683
828
  }
684
829
  };
685
830
 
686
831
  // src/core/OIMReactiveCollection.ts
687
- var OIMReactiveCollection = class {
832
+ var OIMReactiveCollection = class extends OIMCollection {
688
833
  constructor(queue, opts) {
689
- this.collection = new OIMCollection(opts?.collectionOpts);
834
+ super(opts);
690
835
  this.coalescer = new OIMUpdateEventCoalescerCollection(
691
- this.collection.emitter
836
+ this.emitter
692
837
  );
693
838
  this.updateEventEmitter = new OIMUpdateEventEmitter({
694
839
  coalescer: this.coalescer,
695
840
  queue
696
841
  });
697
842
  }
698
- upsertMany(entities) {
699
- this.collection.upsertMany(entities);
700
- }
701
- upsertOne(entity) {
702
- this.collection.upsertOne(entity);
703
- }
704
- removeMany(entities) {
705
- this.collection.removeMany(entities);
706
- }
707
- removeOne(entity) {
708
- this.collection.removeOne(entity);
709
- }
710
- getOneByPk(pk) {
711
- return this.collection.getOneByPk(pk);
712
- }
713
- getManyByPks(pks) {
714
- return this.collection.getManyByPks(pks);
715
- }
716
- clear() {
717
- this.collection.clear();
718
- }
719
- countAll() {
720
- return this.collection.countAll();
721
- }
722
- getAll() {
723
- return this.collection.getAll();
724
- }
725
- getAllPks() {
726
- return this.collection.getAllPks();
727
- }
728
- };
729
-
730
- // src/core/OIMIndexManual.ts
731
- var OIMIndexManual = class extends OIMIndex {
732
- constructor(options = {}) {
733
- super(options);
734
- }
735
- /**
736
- * Set primary keys for a specific index key, replacing any existing values.
737
- * Uses optional comparator to skip updates if PKs haven't actually changed.
738
- */
739
- setPks(key, pks) {
740
- const hasChanges = this.setPksWithComparison(key, new Set(pks));
741
- if (hasChanges) {
742
- this.emitUpdate([key]);
743
- }
744
- }
745
- /**
746
- * Add primary keys to a specific index key
747
- */
748
- addPks(key, pks) {
749
- if (pks.length === 0) return;
750
- let pksSet = this.pks.get(key);
751
- if (!pksSet) {
752
- pksSet = /* @__PURE__ */ new Set();
753
- this.pks.set(key, pksSet);
754
- }
755
- let hasChanges = false;
756
- for (const pk of pks) {
757
- const sizeBefore = pksSet.size;
758
- pksSet.add(pk);
759
- if (pksSet.size > sizeBefore) {
760
- hasChanges = true;
761
- }
762
- }
763
- if (hasChanges) {
764
- this.emitUpdate([key]);
765
- }
766
- }
767
- /**
768
- * Remove primary keys from a specific index key
769
- */
770
- removePks(key, pks) {
771
- if (pks.length === 0) return;
772
- const pksSet = this.pks.get(key);
773
- if (!pksSet) return;
774
- let hasChanges = false;
775
- for (const pk of pks) {
776
- if (pksSet.delete(pk)) {
777
- hasChanges = true;
778
- }
779
- }
780
- if (pksSet.size === 0) {
781
- this.pks.delete(key);
782
- hasChanges = true;
783
- }
784
- if (hasChanges) {
785
- this.emitUpdate([key]);
786
- }
787
- }
788
- /**
789
- * Clear all primary keys for a specific index key, or all keys if no key specified
790
- */
791
- clear(key) {
792
- if (key === void 0) {
793
- if (this.pks.size > 0) {
794
- const allKeys = Array.from(this.pks.keys());
795
- this.pks.clear();
796
- this.emitUpdate(allKeys);
797
- }
798
- } else {
799
- if (this.pks.has(key)) {
800
- this.pks.delete(key);
801
- this.emitUpdate([key]);
802
- }
803
- }
804
- }
805
843
  };
806
844
 
807
845
  // src/core/OIMReactiveIndexManual.ts
@@ -829,10 +867,8 @@ var OIMReactiveIndexManual = class extends OIMReactiveIndex {
829
867
  // src/core/OIMRICollection.ts
830
868
  var OIMRICollection = class extends OIMReactiveCollection {
831
869
  constructor(queue, opts) {
832
- super(queue, {
833
- collectionOpts: opts.collectionOpts
834
- });
835
- this.indexes = opts.indexes || {};
870
+ super(queue, opts.collectionOpts);
871
+ this.indexes = opts.indexes;
836
872
  }
837
873
  };
838
874
 
@@ -955,9 +991,7 @@ var OIMIndexComparatorFactory = class {
955
991
  if (existingSet.size !== newSet.size || existingSet.size !== existingPks.length) {
956
992
  return false;
957
993
  }
958
- const setValues = Array.from(existingSet);
959
- for (let i = 0; i < setValues.length; i++) {
960
- const pk = setValues[i];
994
+ for (const pk of existingSet) {
961
995
  if (!newSet.has(pk)) {
962
996
  return false;
963
997
  }
@@ -1201,6 +1235,7 @@ var OIMEventQueueSchedulerFactory = class {
1201
1235
  OIMCollectionStore,
1202
1236
  OIMCollectionStoreMapDriven,
1203
1237
  OIMComparatorFactory,
1238
+ OIMDBSettings,
1204
1239
  OIMEntityUpdaterFactory,
1205
1240
  OIMEventEmitter,
1206
1241
  OIMEventQueue,