@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/dist/index.js CHANGED
@@ -127,18 +127,66 @@ var EOIMIndexEventType = /* @__PURE__ */ ((EOIMIndexEventType2) => {
127
127
  return EOIMIndexEventType2;
128
128
  })(EOIMIndexEventType || {});
129
129
 
130
+ // src/abstract/OIMIndexStore.ts
131
+ var OIMIndexStore = class {
132
+ };
133
+
134
+ // src/core/OIMIndexStoreMapDriven.ts
135
+ var OIMIndexStoreMapDriven = class extends OIMIndexStore {
136
+ constructor() {
137
+ super(...arguments);
138
+ this.pks = /* @__PURE__ */ new Map();
139
+ }
140
+ setOneByKey(key, pks) {
141
+ this.pks.set(key, pks);
142
+ }
143
+ removeOneByKey(key) {
144
+ this.pks.delete(key);
145
+ }
146
+ removeManyByKeys(keys) {
147
+ for (const key of keys) {
148
+ this.pks.delete(key);
149
+ }
150
+ }
151
+ getOneByKey(key) {
152
+ return this.pks.get(key);
153
+ }
154
+ getManyByKeys(keys) {
155
+ const result = /* @__PURE__ */ new Map();
156
+ for (const key of keys) {
157
+ const pks = this.getOneByKey(key);
158
+ if (pks !== void 0) {
159
+ result.set(key, pks);
160
+ }
161
+ }
162
+ return result;
163
+ }
164
+ getAllKeys() {
165
+ return Array.from(this.pks.keys());
166
+ }
167
+ getAll() {
168
+ return new Map(this.pks);
169
+ }
170
+ countAll() {
171
+ return this.pks.size;
172
+ }
173
+ clear() {
174
+ this.pks.clear();
175
+ }
176
+ };
177
+
130
178
  // src/abstract/OIMIndex.ts
131
179
  var OIMIndex = class {
132
180
  constructor(options = {}) {
133
- this.pks = /* @__PURE__ */ new Map();
134
181
  this.emitter = new OIMEventEmitter();
135
182
  this.comparePks = options.comparePks;
183
+ this.store = options.store ?? new OIMIndexStoreMapDriven();
136
184
  }
137
185
  /**
138
186
  * Get primary keys for multiple index keys
139
187
  */
140
188
  getPksByKeys(keys) {
141
- return new Map(keys.map((key) => [key, this.getPks(key)]));
189
+ return this.store.getManyByKeys(keys);
142
190
  }
143
191
  /**
144
192
  * @deprecated Use getPksByKey instead
@@ -151,39 +199,39 @@ var OIMIndex = class {
151
199
  * Get primary keys for a specific index key
152
200
  */
153
201
  getPksByKey(key) {
154
- const pksSet = this.pks.get(key);
202
+ const pksSet = this.store.getOneByKey(key);
155
203
  return pksSet ? pksSet : /* @__PURE__ */ new Set();
156
204
  }
157
205
  /**
158
206
  * Check if an index key exists
159
207
  */
160
208
  hasKey(key) {
161
- return this.pks.has(key);
209
+ return this.store.getOneByKey(key) !== void 0;
162
210
  }
163
211
  /**
164
212
  * Get all index keys
165
213
  */
166
214
  getKeys() {
167
- return Array.from(this.pks.keys());
215
+ return this.store.getAllKeys();
168
216
  }
169
217
  /**
170
218
  * Get the number of primary keys for a specific index key
171
219
  */
172
220
  getKeySize(key) {
173
- const pksSet = this.pks.get(key);
221
+ const pksSet = this.store.getOneByKey(key);
174
222
  return pksSet ? pksSet.size : 0;
175
223
  }
176
224
  /**
177
225
  * Get total number of index keys
178
226
  */
179
227
  get size() {
180
- return this.pks.size;
228
+ return this.store.countAll();
181
229
  }
182
230
  /**
183
231
  * Check if the index is empty
184
232
  */
185
233
  get isEmpty() {
186
- return this.pks.size === 0;
234
+ return this.store.countAll() === 0;
187
235
  }
188
236
  /**
189
237
  * Get performance metrics for monitoring and debugging
@@ -192,15 +240,16 @@ var OIMIndex = class {
192
240
  let totalPks = 0;
193
241
  let maxBucketSize = 0;
194
242
  let minBucketSize = Infinity;
195
- for (const pksSet of this.pks.values()) {
243
+ const allPks = this.store.getAll();
244
+ for (const pksSet of allPks.values()) {
196
245
  totalPks += pksSet.size;
197
246
  maxBucketSize = Math.max(maxBucketSize, pksSet.size);
198
247
  minBucketSize = Math.min(minBucketSize, pksSet.size);
199
248
  }
200
249
  return {
201
- totalKeys: this.pks.size,
250
+ totalKeys: allPks.size,
202
251
  totalPks,
203
- averagePksPerKey: this.pks.size > 0 ? totalPks / this.pks.size : 0,
252
+ averagePksPerKey: allPks.size > 0 ? totalPks / allPks.size : 0,
204
253
  maxBucketSize: maxBucketSize === -Infinity ? 0 : maxBucketSize,
205
254
  minBucketSize: minBucketSize === Infinity ? 0 : minBucketSize
206
255
  };
@@ -210,21 +259,26 @@ var OIMIndex = class {
210
259
  */
211
260
  destroy() {
212
261
  this.emitter.offAll();
213
- this.pks.clear();
262
+ this.store.clear();
214
263
  }
215
264
  /**
216
265
  * Set primary keys for a specific index key with optional comparison.
217
266
  * If comparator is provided and returns true (no changes), skip the update.
218
267
  */
219
268
  setPksWithComparison(key, newPks) {
220
- const existingPks = this.getPks(key).values();
221
- if (this.comparePks && this.comparePks(
222
- Array.from(existingPks),
223
- Array.from(newPks.values())
224
- )) {
225
- return false;
269
+ if (this.comparePks) {
270
+ const existingPksSet = this.store.getOneByKey(key);
271
+ if (existingPksSet && existingPksSet.size === newPks.size) {
272
+ const existingPksArray = existingPksSet.size > 0 ? [...existingPksSet] : [];
273
+ const newPksArray = newPks.size > 0 ? [...newPks] : [];
274
+ if (this.comparePks(existingPksArray, newPksArray)) {
275
+ return false;
276
+ }
277
+ } else if (!existingPksSet && newPks.size === 0) {
278
+ return false;
279
+ }
226
280
  }
227
- this.pks.set(key, newPks);
281
+ this.store.setOneByKey(key, newPks);
228
282
  return true;
229
283
  }
230
284
  /**
@@ -359,35 +413,133 @@ var OIMUpdateEventEmitter = class {
359
413
  }
360
414
  };
361
415
 
416
+ // src/core/OIMIndexManual.ts
417
+ var OIMIndexManual = class extends OIMIndex {
418
+ constructor(options = {}) {
419
+ super(options);
420
+ }
421
+ /**
422
+ * Set primary keys for a specific index key, replacing any existing values.
423
+ * Uses optional comparator to skip updates if PKs haven't actually changed.
424
+ */
425
+ setPks(key, pks) {
426
+ const hasChanges = this.setPksWithComparison(key, new Set(pks));
427
+ if (hasChanges) {
428
+ this.emitUpdate([key]);
429
+ }
430
+ }
431
+ /**
432
+ * Add primary keys to a specific index key
433
+ */
434
+ addPks(key, pks) {
435
+ if (pks.length === 0) return;
436
+ let pksSet = this.store.getOneByKey(key);
437
+ if (!pksSet) {
438
+ pksSet = /* @__PURE__ */ new Set();
439
+ this.store.setOneByKey(key, pksSet);
440
+ }
441
+ let hasChanges = false;
442
+ for (const pk of pks) {
443
+ if (!pksSet.has(pk)) {
444
+ pksSet.add(pk);
445
+ hasChanges = true;
446
+ }
447
+ }
448
+ if (hasChanges) {
449
+ this.store.setOneByKey(key, pksSet);
450
+ this.emitUpdate([key]);
451
+ }
452
+ }
453
+ /**
454
+ * Remove primary keys from a specific index key
455
+ */
456
+ removePks(key, pks) {
457
+ if (pks.length === 0) return;
458
+ const pksSet = this.store.getOneByKey(key);
459
+ if (!pksSet) return;
460
+ let hasChanges = false;
461
+ for (const pk of pks) {
462
+ if (pksSet.delete(pk)) {
463
+ hasChanges = true;
464
+ }
465
+ }
466
+ if (pksSet.size === 0) {
467
+ this.store.removeOneByKey(key);
468
+ hasChanges = true;
469
+ } else if (hasChanges) {
470
+ this.store.setOneByKey(key, pksSet);
471
+ }
472
+ if (hasChanges) {
473
+ this.emitUpdate([key]);
474
+ }
475
+ }
476
+ /**
477
+ * Clear all primary keys for a specific index key, or all keys if no key specified
478
+ */
479
+ clear(key) {
480
+ if (key === void 0) {
481
+ const allKeys = this.store.getAllKeys();
482
+ if (allKeys.length > 0) {
483
+ this.store.clear();
484
+ this.emitUpdate(allKeys);
485
+ }
486
+ } else {
487
+ if (this.store.getOneByKey(key) !== void 0) {
488
+ this.store.removeOneByKey(key);
489
+ this.emitUpdate([key]);
490
+ }
491
+ }
492
+ }
493
+ };
494
+
495
+ // src/const/OIMDBSettings.ts
496
+ var OIMDBSettings = class {
497
+ };
498
+ /**
499
+ * Fixed key used in the manual index to store updated keys set in coalescers.
500
+ * This constant is exported so users can create their own indexes and pass them to coalescers.
501
+ */
502
+ OIMDBSettings.UPDATED_KEYS_INDEX_KEY = "__updatedKeys__";
503
+
362
504
  // src/core/OIMUpdateEventCoalescer.ts
363
505
  var OIMUpdateEventCoalescer = class {
364
- constructor() {
506
+ constructor(index) {
365
507
  this.emitter = new OIMEventEmitter();
366
- this.updatedKeys = /* @__PURE__ */ new Set();
367
508
  this.hasEmittedChanges = false;
509
+ this.updatedKeysIndex = index ?? new OIMIndexManual({
510
+ store: new OIMIndexStoreMapDriven()
511
+ });
512
+ if (!this.updatedKeysIndex.hasKey(OIMDBSettings.UPDATED_KEYS_INDEX_KEY)) {
513
+ this.updatedKeysIndex.setPks(
514
+ OIMDBSettings.UPDATED_KEYS_INDEX_KEY,
515
+ []
516
+ );
517
+ }
368
518
  }
369
519
  /**
370
520
  * Get the set of keys that have been updated since last clear
371
521
  */
372
522
  getUpdatedKeys() {
373
- return this.updatedKeys;
523
+ return this.updatedKeysIndex.getPksByKey(
524
+ OIMDBSettings.UPDATED_KEYS_INDEX_KEY
525
+ );
374
526
  }
375
527
  /**
376
528
  * Clear all updated keys and reset the changed state
377
529
  */
378
530
  clearUpdatedKeys() {
379
- this.updatedKeys.clear();
531
+ this.updatedKeysIndex.setPks(OIMDBSettings.UPDATED_KEYS_INDEX_KEY, []);
380
532
  this.hasEmittedChanges = false;
381
533
  }
382
534
  /**
383
535
  * Add keys to the updated set and emit HAS_CHANGES event if needed
384
536
  */
385
537
  addUpdatedKeys(keys) {
386
- const hadChanges = this.updatedKeys.size > 0;
387
- for (const key of keys) {
388
- this.updatedKeys.add(key);
389
- }
390
- if (!hadChanges && this.updatedKeys.size > 0 && !this.hasEmittedChanges) {
538
+ this.updatedKeysIndex.addPks(
539
+ OIMDBSettings.UPDATED_KEYS_INDEX_KEY,
540
+ keys
541
+ );
542
+ if (!this.hasEmittedChanges) {
391
543
  this.hasEmittedChanges = true;
392
544
  this.emitter.emit(
393
545
  0 /* HAS_CHANGES */,
@@ -406,23 +558,14 @@ var OIMUpdateEventCoalescerIndex = class extends OIMUpdateEventCoalescer {
406
558
  * When the index is updated, we keep track of the updated keys.
407
559
  */
408
560
  this.handleUpdate = (payload) => {
409
- const hadChanges = this.updatedKeys.size > 0;
410
- for (const key of payload.keys) {
411
- this.updatedKeys.add(key);
412
- }
413
- if (!hadChanges && this.updatedKeys.size > 0) {
414
- this.emitter.emit(
415
- 0 /* HAS_CHANGES */,
416
- void 0
417
- );
418
- }
561
+ this.addUpdatedKeys(payload.keys);
419
562
  };
420
563
  this.indexEmitter.on(0 /* UPDATE */, this.handleUpdate);
421
564
  }
422
565
  destroy() {
423
566
  this.indexEmitter.off(0 /* UPDATE */, this.handleUpdate);
424
567
  this.emitter.offAll();
425
- this.updatedKeys.clear();
568
+ this.clearUpdatedKeys();
426
569
  }
427
570
  };
428
571
 
@@ -493,14 +636,24 @@ var OIMCollectionStoreMapDriven = class extends OIMCollectionStore {
493
636
  }
494
637
  removeManyByPks(pks) {
495
638
  for (const pk of pks) {
496
- this.removeOneByPk(pk);
639
+ this.entities.delete(pk);
497
640
  }
498
641
  }
499
642
  getOneByPk(pk) {
500
643
  return this.entities.get(pk);
501
644
  }
502
645
  getManyByPks(pks) {
503
- return pks.map((pk) => this.getOneByPk(pk)).filter(Boolean);
646
+ const result = [];
647
+ result.length = pks.length;
648
+ let writeIndex = 0;
649
+ for (let i = 0; i < pks.length; i++) {
650
+ const entity = this.getOneByPk(pks[i]);
651
+ if (entity !== void 0) {
652
+ result[writeIndex++] = entity;
653
+ }
654
+ }
655
+ result.length = writeIndex;
656
+ return result;
504
657
  }
505
658
  getAll() {
506
659
  return Array.from(this.entities.values());
@@ -602,16 +755,7 @@ var OIMUpdateEventCoalescerCollection = class extends OIMUpdateEventCoalescer {
602
755
  * When the collection is updated, we keep track of the updated pks.
603
756
  */
604
757
  this.handleUpdate = (payload) => {
605
- const hadChanges = this.updatedKeys.size > 0;
606
- for (const pk of payload.pks) {
607
- this.updatedKeys.add(pk);
608
- }
609
- if (!hadChanges && this.updatedKeys.size > 0) {
610
- this.emitter.emit(
611
- 0 /* HAS_CHANGES */,
612
- void 0
613
- );
614
- }
758
+ this.addUpdatedKeys(payload.pks);
615
759
  };
616
760
  this.collectionEmitter.on(
617
761
  0 /* UPDATE */,
@@ -624,129 +768,22 @@ var OIMUpdateEventCoalescerCollection = class extends OIMUpdateEventCoalescer {
624
768
  this.handleUpdate
625
769
  );
626
770
  this.emitter.offAll();
627
- this.updatedKeys.clear();
771
+ this.clearUpdatedKeys();
628
772
  }
629
773
  };
630
774
 
631
775
  // src/core/OIMReactiveCollection.ts
632
- var OIMReactiveCollection = class {
776
+ var OIMReactiveCollection = class extends OIMCollection {
633
777
  constructor(queue, opts) {
634
- this.collection = new OIMCollection(opts?.collectionOpts);
778
+ super(opts);
635
779
  this.coalescer = new OIMUpdateEventCoalescerCollection(
636
- this.collection.emitter
780
+ this.emitter
637
781
  );
638
782
  this.updateEventEmitter = new OIMUpdateEventEmitter({
639
783
  coalescer: this.coalescer,
640
784
  queue
641
785
  });
642
786
  }
643
- upsertMany(entities) {
644
- this.collection.upsertMany(entities);
645
- }
646
- upsertOne(entity) {
647
- this.collection.upsertOne(entity);
648
- }
649
- removeMany(entities) {
650
- this.collection.removeMany(entities);
651
- }
652
- removeOne(entity) {
653
- this.collection.removeOne(entity);
654
- }
655
- getOneByPk(pk) {
656
- return this.collection.getOneByPk(pk);
657
- }
658
- getManyByPks(pks) {
659
- return this.collection.getManyByPks(pks);
660
- }
661
- clear() {
662
- this.collection.clear();
663
- }
664
- countAll() {
665
- return this.collection.countAll();
666
- }
667
- getAll() {
668
- return this.collection.getAll();
669
- }
670
- getAllPks() {
671
- return this.collection.getAllPks();
672
- }
673
- };
674
-
675
- // src/core/OIMIndexManual.ts
676
- var OIMIndexManual = class extends OIMIndex {
677
- constructor(options = {}) {
678
- super(options);
679
- }
680
- /**
681
- * Set primary keys for a specific index key, replacing any existing values.
682
- * Uses optional comparator to skip updates if PKs haven't actually changed.
683
- */
684
- setPks(key, pks) {
685
- const hasChanges = this.setPksWithComparison(key, new Set(pks));
686
- if (hasChanges) {
687
- this.emitUpdate([key]);
688
- }
689
- }
690
- /**
691
- * Add primary keys to a specific index key
692
- */
693
- addPks(key, pks) {
694
- if (pks.length === 0) return;
695
- let pksSet = this.pks.get(key);
696
- if (!pksSet) {
697
- pksSet = /* @__PURE__ */ new Set();
698
- this.pks.set(key, pksSet);
699
- }
700
- let hasChanges = false;
701
- for (const pk of pks) {
702
- const sizeBefore = pksSet.size;
703
- pksSet.add(pk);
704
- if (pksSet.size > sizeBefore) {
705
- hasChanges = true;
706
- }
707
- }
708
- if (hasChanges) {
709
- this.emitUpdate([key]);
710
- }
711
- }
712
- /**
713
- * Remove primary keys from a specific index key
714
- */
715
- removePks(key, pks) {
716
- if (pks.length === 0) return;
717
- const pksSet = this.pks.get(key);
718
- if (!pksSet) return;
719
- let hasChanges = false;
720
- for (const pk of pks) {
721
- if (pksSet.delete(pk)) {
722
- hasChanges = true;
723
- }
724
- }
725
- if (pksSet.size === 0) {
726
- this.pks.delete(key);
727
- hasChanges = true;
728
- }
729
- if (hasChanges) {
730
- this.emitUpdate([key]);
731
- }
732
- }
733
- /**
734
- * Clear all primary keys for a specific index key, or all keys if no key specified
735
- */
736
- clear(key) {
737
- if (key === void 0) {
738
- if (this.pks.size > 0) {
739
- const allKeys = Array.from(this.pks.keys());
740
- this.pks.clear();
741
- this.emitUpdate(allKeys);
742
- }
743
- } else {
744
- if (this.pks.has(key)) {
745
- this.pks.delete(key);
746
- this.emitUpdate([key]);
747
- }
748
- }
749
- }
750
787
  };
751
788
 
752
789
  // src/core/OIMReactiveIndexManual.ts
@@ -774,10 +811,8 @@ var OIMReactiveIndexManual = class extends OIMReactiveIndex {
774
811
  // src/core/OIMRICollection.ts
775
812
  var OIMRICollection = class extends OIMReactiveCollection {
776
813
  constructor(queue, opts) {
777
- super(queue, {
778
- collectionOpts: opts.collectionOpts
779
- });
780
- this.indexes = opts.indexes || {};
814
+ super(queue, opts.collectionOpts);
815
+ this.indexes = opts.indexes;
781
816
  }
782
817
  };
783
818
 
@@ -900,9 +935,7 @@ var OIMIndexComparatorFactory = class {
900
935
  if (existingSet.size !== newSet.size || existingSet.size !== existingPks.length) {
901
936
  return false;
902
937
  }
903
- const setValues = Array.from(existingSet);
904
- for (let i = 0; i < setValues.length; i++) {
905
- const pk = setValues[i];
938
+ for (const pk of existingSet) {
906
939
  if (!newSet.has(pk)) {
907
940
  return false;
908
941
  }
@@ -1145,6 +1178,7 @@ export {
1145
1178
  OIMCollectionStore,
1146
1179
  OIMCollectionStoreMapDriven,
1147
1180
  OIMComparatorFactory,
1181
+ OIMDBSettings,
1148
1182
  OIMEntityUpdaterFactory,
1149
1183
  OIMEventEmitter,
1150
1184
  OIMEventQueue,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oimdb/core",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "description": "Core in-memory data library with type-safe indices, reactive subscriptions, and event processing",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -24,8 +24,10 @@
24
24
  },
25
25
  "scripts": {
26
26
  "build": "tsup src/index.ts --dts --format esm,cjs",
27
+ "test": "jest",
28
+ "type-check": "tsc --noEmit",
27
29
  "clean": "rimraf dist",
28
- "prepublishOnly": "npm run build"
30
+ "prepublishOnly": "npm run type-check && npm test && npm run build"
29
31
  },
30
32
  "files": [
31
33
  "dist"
@@ -33,6 +35,9 @@
33
35
  "author": "Andrei Baikov",
34
36
  "license": "MIT",
35
37
  "devDependencies": {
38
+ "@types/jest": "^29.5.8",
39
+ "jest": "^29.7.0",
40
+ "ts-jest": "^29.1.1",
36
41
  "tsup": "^8.5.0",
37
42
  "typescript": "^5.2.2",
38
43
  "rimraf": "^5.0.5"