cacheable 1.9.0 → 1.10.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
@@ -78,7 +78,7 @@ import {
78
78
  import { Hookified } from "hookified";
79
79
 
80
80
  // src/hash.ts
81
- import * as crypto from "node:crypto";
81
+ import * as crypto from "crypto";
82
82
  function hash(object, algorithm = "sha256") {
83
83
  const objectString = JSON.stringify(object);
84
84
  if (!crypto.getHashes().includes(algorithm)) {
@@ -88,6 +88,26 @@ function hash(object, algorithm = "sha256") {
88
88
  hasher.update(objectString);
89
89
  return hasher.digest("hex");
90
90
  }
91
+ function hashToNumber(object, min = 0, max = 10, algorithm = "sha256") {
92
+ const objectString = JSON.stringify(object);
93
+ if (!crypto.getHashes().includes(algorithm)) {
94
+ throw new Error(`Unsupported hash algorithm: '${algorithm}'`);
95
+ }
96
+ const hasher = crypto.createHash(algorithm);
97
+ hasher.update(objectString);
98
+ const hashHex = hasher.digest("hex");
99
+ const hashNumber = Number.parseInt(hashHex, 16);
100
+ const range = max - min + 1;
101
+ return min + hashNumber % range;
102
+ }
103
+ function djb2Hash(string_, min = 0, max = 10) {
104
+ let hash2 = 5381;
105
+ for (let i = 0; i < string_.length; i++) {
106
+ hash2 = hash2 * 33 ^ string_.charCodeAt(i);
107
+ }
108
+ const range = max - min + 1;
109
+ return min + Math.abs(hash2) % range;
110
+ }
91
111
 
92
112
  // src/coalesce-async.ts
93
113
  var callbacks = /* @__PURE__ */ new Map();
@@ -164,29 +184,31 @@ function wrapSync(function_, options) {
164
184
  return value;
165
185
  };
166
186
  }
187
+ async function getOrSet(key, function_, options) {
188
+ let value = await options.cache.get(key);
189
+ if (value === void 0) {
190
+ const cacheId = options.cacheId ?? "default";
191
+ const coalesceKey = `${cacheId}::${key}`;
192
+ value = await coalesceAsync(coalesceKey, async () => {
193
+ try {
194
+ const result = await function_();
195
+ await options.cache.set(key, result, options.ttl);
196
+ return result;
197
+ } catch (error) {
198
+ options.cache.emit("error", error);
199
+ if (options.cacheErrors) {
200
+ await options.cache.set(key, error, options.ttl);
201
+ }
202
+ }
203
+ });
204
+ }
205
+ return value;
206
+ }
167
207
  function wrap(function_, options) {
168
- const { ttl, keyPrefix, cache } = options;
208
+ const { keyPrefix, cache } = options;
169
209
  return async function(...arguments_) {
170
- let value;
171
210
  const cacheKey = createWrapKey(function_, arguments_, keyPrefix);
172
- value = await cache.get(cacheKey);
173
- if (value === void 0) {
174
- const cacheId = options.cacheId ?? "default";
175
- const coalesceKey = `${cacheId}::${cacheKey}`;
176
- value = await coalesceAsync(coalesceKey, async () => {
177
- try {
178
- const result = await function_(...arguments_);
179
- await cache.set(cacheKey, result, ttl);
180
- return result;
181
- } catch (error) {
182
- cache.emit("error", error);
183
- if (options.cacheErrors) {
184
- await cache.set(cacheKey, error, ttl);
185
- }
186
- }
187
- });
188
- }
189
- return value;
211
+ return cache.getOrSet(cacheKey, async () => function_(...arguments_), options);
190
212
  };
191
213
  }
192
214
  function createWrapKey(function_, arguments_, keyPrefix) {
@@ -270,19 +292,14 @@ var DoublyLinkedList = class {
270
292
  };
271
293
 
272
294
  // src/memory.ts
295
+ var defaultStoreHashSize = 16;
296
+ var maximumMapSize = 16777216;
273
297
  var CacheableMemory = class extends Hookified {
274
298
  _lru = new DoublyLinkedList();
275
- _hashCache = /* @__PURE__ */ new Map();
276
- _hash0 = /* @__PURE__ */ new Map();
277
- _hash1 = /* @__PURE__ */ new Map();
278
- _hash2 = /* @__PURE__ */ new Map();
279
- _hash3 = /* @__PURE__ */ new Map();
280
- _hash4 = /* @__PURE__ */ new Map();
281
- _hash5 = /* @__PURE__ */ new Map();
282
- _hash6 = /* @__PURE__ */ new Map();
283
- _hash7 = /* @__PURE__ */ new Map();
284
- _hash8 = /* @__PURE__ */ new Map();
285
- _hash9 = /* @__PURE__ */ new Map();
299
+ _storeHashSize = defaultStoreHashSize;
300
+ _storeHashAlgorithm = "djb2Hash" /* djb2Hash */;
301
+ // Default is djb2Hash
302
+ _store = Array.from({ length: this._storeHashSize }, () => /* @__PURE__ */ new Map());
286
303
  _ttl;
287
304
  // Turned off by default
288
305
  _useClone = true;
@@ -305,12 +322,23 @@ var CacheableMemory = class extends Hookified {
305
322
  if (options?.useClone !== void 0) {
306
323
  this._useClone = options.useClone;
307
324
  }
325
+ if (options?.storeHashSize && options.storeHashSize > 0) {
326
+ this._storeHashSize = options.storeHashSize;
327
+ }
308
328
  if (options?.lruSize) {
309
- this._lruSize = options.lruSize;
329
+ if (options.lruSize > maximumMapSize) {
330
+ this.emit("error", new Error(`LRU size cannot be larger than ${maximumMapSize} due to Map limitations.`));
331
+ } else {
332
+ this._lruSize = options.lruSize;
333
+ }
310
334
  }
311
335
  if (options?.checkInterval) {
312
336
  this._checkInterval = options.checkInterval;
313
337
  }
338
+ if (options?.storeHashAlgorithm) {
339
+ this._storeHashAlgorithm = options.storeHashAlgorithm;
340
+ }
341
+ this._store = Array.from({ length: this._storeHashSize }, () => /* @__PURE__ */ new Map());
314
342
  this.startIntervalCheck();
315
343
  }
316
344
  /**
@@ -343,17 +371,25 @@ var CacheableMemory = class extends Hookified {
343
371
  }
344
372
  /**
345
373
  * Gets the size of the LRU cache
346
- * @returns {number} - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0.
374
+ * @returns {number} - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm.
347
375
  */
348
376
  get lruSize() {
349
377
  return this._lruSize;
350
378
  }
351
379
  /**
352
380
  * Sets the size of the LRU cache
353
- * @param {number} value - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0.
381
+ * @param {number} value - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm.
354
382
  */
355
383
  set lruSize(value) {
384
+ if (value > maximumMapSize) {
385
+ this.emit("error", new Error(`LRU size cannot be larger than ${maximumMapSize} due to Map limitations.`));
386
+ return;
387
+ }
356
388
  this._lruSize = value;
389
+ if (this._lruSize === 0) {
390
+ this._lru = new DoublyLinkedList();
391
+ return;
392
+ }
357
393
  this.lruResize();
358
394
  }
359
395
  /**
@@ -375,21 +411,85 @@ var CacheableMemory = class extends Hookified {
375
411
  * @returns {number} - The size of the cache
376
412
  */
377
413
  get size() {
378
- return this._hash0.size + this._hash1.size + this._hash2.size + this._hash3.size + this._hash4.size + this._hash5.size + this._hash6.size + this._hash7.size + this._hash8.size + this._hash9.size;
414
+ let size = 0;
415
+ for (const store of this._store) {
416
+ size += store.size;
417
+ }
418
+ return size;
419
+ }
420
+ /**
421
+ * Gets the number of hash stores
422
+ * @returns {number} - The number of hash stores
423
+ */
424
+ get storeHashSize() {
425
+ return this._storeHashSize;
426
+ }
427
+ /**
428
+ * Sets the number of hash stores. This will recreate the store and all data will be cleared
429
+ * @param {number} value - The number of hash stores
430
+ */
431
+ set storeHashSize(value) {
432
+ if (value === this._storeHashSize) {
433
+ return;
434
+ }
435
+ this._storeHashSize = value;
436
+ this._store = Array.from({ length: this._storeHashSize }, () => /* @__PURE__ */ new Map());
437
+ }
438
+ /**
439
+ * Gets the store hash algorithm
440
+ * @returns {StoreHashAlgorithm | StoreHashAlgorithmFunction} - The store hash algorithm
441
+ */
442
+ get storeHashAlgorithm() {
443
+ return this._storeHashAlgorithm;
444
+ }
445
+ /**
446
+ * Sets the store hash algorithm. This will recreate the store and all data will be cleared
447
+ * @param {StoreHashAlgorithm | StoreHashAlgorithmFunction} value - The store hash algorithm
448
+ */
449
+ set storeHashAlgorithm(value) {
450
+ this._storeHashAlgorithm = value;
379
451
  }
380
452
  /**
381
453
  * Gets the keys
382
454
  * @returns {IterableIterator<string>} - The keys
383
455
  */
384
456
  get keys() {
385
- return this.concatStores().keys();
457
+ const keys = new Array();
458
+ for (const store of this._store) {
459
+ for (const key of store.keys()) {
460
+ const item = store.get(key);
461
+ if (item && this.hasExpired(item)) {
462
+ store.delete(key);
463
+ continue;
464
+ }
465
+ keys.push(key);
466
+ }
467
+ }
468
+ return keys.values();
386
469
  }
387
470
  /**
388
471
  * Gets the items
389
472
  * @returns {IterableIterator<CacheableStoreItem>} - The items
390
473
  */
391
474
  get items() {
392
- return this.concatStores().values();
475
+ const items = new Array();
476
+ for (const store of this._store) {
477
+ for (const item of store.values()) {
478
+ if (this.hasExpired(item)) {
479
+ store.delete(item.key);
480
+ continue;
481
+ }
482
+ items.push(item);
483
+ }
484
+ }
485
+ return items.values();
486
+ }
487
+ /**
488
+ * Gets the store
489
+ * @returns {Array<Map<string, CacheableStoreItem>>} - The store
490
+ */
491
+ get store() {
492
+ return this._store;
393
493
  }
394
494
  /**
395
495
  * Gets the value of the key
@@ -402,7 +502,7 @@ var CacheableMemory = class extends Hookified {
402
502
  if (!item) {
403
503
  return void 0;
404
504
  }
405
- if (item.expires && item.expires && Date.now() > item.expires) {
505
+ if (item.expires && Date.now() > item.expires) {
406
506
  store.delete(key);
407
507
  return void 0;
408
508
  }
@@ -569,7 +669,6 @@ var CacheableMemory = class extends Hookified {
569
669
  delete(key) {
570
670
  const store = this.getStore(key);
571
671
  store.delete(key);
572
- this._hashCache.delete(key);
573
672
  }
574
673
  /**
575
674
  * Delete the keys
@@ -586,17 +685,7 @@ var CacheableMemory = class extends Hookified {
586
685
  * @returns {void}
587
686
  */
588
687
  clear() {
589
- this._hash0.clear();
590
- this._hash1.clear();
591
- this._hash2.clear();
592
- this._hash3.clear();
593
- this._hash4.clear();
594
- this._hash5.clear();
595
- this._hash6.clear();
596
- this._hash7.clear();
597
- this._hash8.clear();
598
- this._hash9.clear();
599
- this._hashCache.clear();
688
+ this._store = Array.from({ length: this._storeHashSize }, () => /* @__PURE__ */ new Map());
600
689
  this._lru = new DoublyLinkedList();
601
690
  }
602
691
  /**
@@ -605,66 +694,27 @@ var CacheableMemory = class extends Hookified {
605
694
  * @returns {CacheableHashStore} - The store
606
695
  */
607
696
  getStore(key) {
608
- const hash2 = this.hashKey(key);
609
- return this.getStoreFromHash(hash2);
697
+ const hash2 = this.getKeyStoreHash(key);
698
+ this._store[hash2] ||= /* @__PURE__ */ new Map();
699
+ return this._store[hash2];
610
700
  }
611
701
  /**
612
- * Get the store based on the hash (internal use)
613
- * @param {number} hash
614
- * @returns {Map<string, CacheableStoreItem>}
702
+ * Hash the key for which store to go to (internal use)
703
+ * @param {string} key - The key to hash
704
+ * Available algorithms are: SHA256, SHA1, MD5, and djb2Hash.
705
+ * @returns {number} - The hashed key as a number
615
706
  */
616
- getStoreFromHash(hash2) {
617
- switch (hash2) {
618
- case 1: {
619
- return this._hash1;
620
- }
621
- case 2: {
622
- return this._hash2;
623
- }
624
- case 3: {
625
- return this._hash3;
626
- }
627
- case 4: {
628
- return this._hash4;
629
- }
630
- case 5: {
631
- return this._hash5;
632
- }
633
- case 6: {
634
- return this._hash6;
635
- }
636
- case 7: {
637
- return this._hash7;
638
- }
639
- case 8: {
640
- return this._hash8;
641
- }
642
- case 9: {
643
- return this._hash9;
644
- }
645
- default: {
646
- return this._hash0;
647
- }
707
+ getKeyStoreHash(key) {
708
+ if (this._store.length === 1) {
709
+ return 0;
648
710
  }
649
- }
650
- /**
651
- * Hash the key (internal use)
652
- * @param key
653
- * @returns {number} from 0 to 9
654
- */
655
- hashKey(key) {
656
- const cacheHashNumber = this._hashCache.get(key);
657
- if (typeof cacheHashNumber === "number") {
658
- return cacheHashNumber;
711
+ if (this._storeHashAlgorithm === "djb2Hash" /* djb2Hash */) {
712
+ return djb2Hash(key, 0, this._storeHashSize);
659
713
  }
660
- let hash2 = 0;
661
- const primeMultiplier = 31;
662
- for (let i = 0; i < key.length; i++) {
663
- hash2 = hash2 * primeMultiplier + key.charCodeAt(i);
714
+ if (typeof this._storeHashAlgorithm === "function") {
715
+ return this._storeHashAlgorithm(key, this._storeHashSize);
664
716
  }
665
- const result = Math.abs(hash2) % 10;
666
- this._hashCache.set(key, result);
667
- return result;
717
+ return hashToNumber(key, 0, this._storeHashSize, this._storeHashAlgorithm);
668
718
  }
669
719
  /**
670
720
  * Clone the value. This is for internal use
@@ -700,13 +750,10 @@ var CacheableMemory = class extends Hookified {
700
750
  this._lru.moveToFront(key);
701
751
  }
702
752
  /**
703
- * Resize the LRU cache. This is for internal use
753
+ * Resize the LRU cache. This is for internal use.
704
754
  * @returns {void}
705
755
  */
706
756
  lruResize() {
707
- if (this._lruSize === 0) {
708
- return;
709
- }
710
757
  while (this._lru.size > this._lruSize) {
711
758
  const oldestKey = this._lru.getOldest();
712
759
  if (oldestKey) {
@@ -720,10 +767,11 @@ var CacheableMemory = class extends Hookified {
720
767
  * @returns {void}
721
768
  */
722
769
  checkExpiration() {
723
- const stores = this.concatStores();
724
- for (const item of stores.values()) {
725
- if (item.expires && Date.now() > item.expires) {
726
- this.delete(item.key);
770
+ for (const store of this._store) {
771
+ for (const item of store.values()) {
772
+ if (item.expires && Date.now() > item.expires) {
773
+ store.delete(item.key);
774
+ }
727
775
  }
728
776
  }
729
777
  }
@@ -752,15 +800,6 @@ var CacheableMemory = class extends Hookified {
752
800
  this._interval = 0;
753
801
  this._checkInterval = 0;
754
802
  }
755
- /**
756
- * Hash the object. This is for internal use
757
- * @param {any} object - The object to hash
758
- * @param {string} [algorithm='sha256'] - The algorithm to hash
759
- * @returns {string} - The hashed string
760
- */
761
- hash(object, algorithm = "sha256") {
762
- return hash(object, algorithm);
763
- }
764
803
  /**
765
804
  * Wrap the function for caching
766
805
  * @param {Function} function_ - The function to wrap
@@ -785,9 +824,6 @@ var CacheableMemory = class extends Hookified {
785
824
  }
786
825
  return result;
787
826
  }
788
- concatStores() {
789
- return new Map([...this._hash0, ...this._hash1, ...this._hash2, ...this._hash3, ...this._hash4, ...this._hash5, ...this._hash6, ...this._hash7, ...this._hash8, ...this._hash9]);
790
- }
791
827
  setTtl(ttl) {
792
828
  if (typeof ttl === "string" || ttl === void 0) {
793
829
  this._ttl = ttl;
@@ -797,6 +833,12 @@ var CacheableMemory = class extends Hookified {
797
833
  this._ttl = void 0;
798
834
  }
799
835
  }
836
+ hasExpired(item) {
837
+ if (item.expires && Date.now() > item.expires) {
838
+ return true;
839
+ }
840
+ return false;
841
+ }
800
842
  };
801
843
 
802
844
  // src/keyv-memory.ts
@@ -1659,6 +1701,24 @@ var Cacheable = class extends Hookified2 {
1659
1701
  };
1660
1702
  return wrap(function_, wrapOptions);
1661
1703
  }
1704
+ /**
1705
+ * Retrieves the value associated with the given key from the cache. If the key is not found,
1706
+ * invokes the provided function to calculate the value, stores it in the cache, and then returns it.
1707
+ *
1708
+ * @param {string} key - The key to retrieve or set in the cache.
1709
+ * @param {() => Promise<T>} function_ - The asynchronous function that computes the value to be cached if the key does not exist.
1710
+ * @param {WrapFunctionOptions} [options] - Optional settings for caching, such as the time to live (TTL) or whether to cache errors.
1711
+ * @return {Promise<T | undefined>} - A promise that resolves to the cached or newly computed value, or undefined if an error occurs and caching is not configured for errors.
1712
+ */
1713
+ async getOrSet(key, function_, options) {
1714
+ const getOrSetOptions = {
1715
+ cache: this,
1716
+ cacheId: this._cacheId,
1717
+ ttl: options?.ttl ?? this._ttl,
1718
+ cacheErrors: options?.cacheErrors
1719
+ };
1720
+ return getOrSet(key, function_, getOrSetOptions);
1721
+ }
1662
1722
  /**
1663
1723
  * Will hash an object using the specified algorithm. The default algorithm is 'sha256'.
1664
1724
  * @param {any} object the object to hash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cacheable",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "High Performance Layer 1 / Layer 2 Caching with Keyv Storage",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",