cacheable 1.10.0 → 1.10.2

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
@@ -32,6 +32,7 @@
32
32
  * [TTL Propagation and Storage Tiering](#ttl-propagation-and-storage-tiering)
33
33
  * [Shorthand for Time to Live (ttl)](#shorthand-for-time-to-live-ttl)
34
34
  * [Non-Blocking Operations](#non-blocking-operations)
35
+ * [Non-Blocking with @keyv/redis](#non-blocking-with-keyvredis)
35
36
  * [CacheSync - Distributed Updates](#cachesync---distributed-updates)
36
37
  * [Cacheable Options](#cacheable-options)
37
38
  * [Cacheable Statistics (Instance Only)](#cacheable-statistics-instance-only)
@@ -44,6 +45,7 @@
44
45
  * [CacheableMemory - API](#cacheablememory---api)
45
46
  * [Keyv Storage Adapter - KeyvCacheableMemory](#keyv-storage-adapter---keyvcacheablememory)
46
47
  * [Wrap / Memoization for Sync and Async Functions](#wrap--memoization-for-sync-and-async-functions)
48
+ * [Get Or Set Memoization Function](#get-or-set-memoization-function)
47
49
  * [How to Contribute](#how-to-contribute)
48
50
  * [License and Copyright](#license-and-copyright)
49
51
 
@@ -255,6 +257,29 @@ raws.forEach((entry, idx) => {
255
257
 
256
258
  If you want your layer 2 (secondary) store to be non-blocking you can set the `nonBlocking` property to `true` in the options. This will make the secondary store non-blocking and will not wait for the secondary store to respond on `setting data`, `deleting data`, or `clearing data`. This is useful if you want to have a faster response time and not wait for the secondary store to respond.
257
259
 
260
+ # Non-Blocking with @keyv/redis
261
+
262
+ `@keyv/redis` is one of the most popular storage adapters used with `cacheable`. It provides a Redis-backed cache store that can be used as a secondary store. It is a bit complicated to setup as by default it causes hangs and blocking with its default configuration. To get past this you will need to configure the following:
263
+
264
+ Construct your own Redis client via the `createClient()` method from `@keyv/redis` with the following options:
265
+ * Set `disableOfflineQueue` to `true`
266
+ * Set `socket.reconnectStrategy` to `false`
267
+ In the KeyvRedis options:
268
+ * Set `throwOnConnectError` to `false`
269
+ In the Cacheable options:
270
+ * Set `nonBlocking` to `true`
271
+
272
+ We have also build a function to help with this called `createKeyvNonBlocking` inside the `@keyv/redis` package after version `4.6.0`. Here is an example of how to use it:
273
+
274
+ ```javascript
275
+ import { Cacheable } from 'cacheable';
276
+ import { createKeyvNonBlocking } from '@keyv/redis';
277
+
278
+ const secondary = createKeyvNonBlocking('redis://user:pass@localhost:6379');
279
+
280
+ const cache = new Cacheable({ secondary, nonBlocking: true });
281
+ ```
282
+
258
283
  # GetOrSet
259
284
 
260
285
  The `getOrSet` method provides a convenient way to implement the cache-aside pattern. It attempts to retrieve a value
@@ -355,7 +380,7 @@ _This does not enable statistics for your layer 2 cache as that is a distributed
355
380
  * `deleteMany([keys])`: Deletes multiple values from the cache.
356
381
  * `clear()`: Clears the cache stores. Be careful with this as it will clear both layer 1 and layer 2.
357
382
  * `wrap(function, WrapOptions)`: Wraps an `async` function in a cache.
358
- * `getOrSet(key, valueFunction, ttl?)`: Gets a value from cache or sets it if not found using the provided function.
383
+ * `getOrSet(GetOrSetKey, valueFunction, GetOrSetFunctionOptions)`: Gets a value from cache or sets it if not found using the provided function.
359
384
  * `disconnect()`: Disconnects from the cache stores.
360
385
  * `onHook(hook, callback)`: Sets a hook.
361
386
  * `removeHook(hook)`: Removes a hook.
@@ -614,6 +639,67 @@ console.log(wrappedFunction()); // error
614
639
  console.log(wrappedFunction()); // error from cache
615
640
  ```
616
641
 
642
+ If you would like to generate your own key for the wrapped function you can set the `createKey` property in the `wrap()` options. This is useful if you want to generate a key based on the arguments of the function or any other criteria.
643
+
644
+ ```javascript
645
+ const cache = new Cacheable();
646
+ const options: WrapOptions = {
647
+ cache,
648
+ keyPrefix: 'test',
649
+ createKey: (function_, arguments_, options: WrapOptions) => `customKey:${options?.keyPrefix}:${arguments_[0]}`,
650
+ };
651
+
652
+ const wrapped = wrap((argument: string) => `Result for ${argument}`, options);
653
+
654
+ const result1 = await wrapped('arg1');
655
+ const result2 = await wrapped('arg1'); // Should hit the cache
656
+
657
+ console.log(result1); // Result for arg1
658
+ console.log(result2); // Result for arg1 (from cache)
659
+ ```
660
+
661
+ We will pass in the `function` that is being wrapped, the `arguments` passed to the function, and the `options` used to wrap the function. You can then use these to generate a custom key for the cache.
662
+
663
+ # Get Or Set Memoization Function
664
+
665
+ The `getOrSet` method provides a convenient way to implement the cache-aside pattern. It attempts to retrieve a value from cache, and if not found, calls the provided function to compute the value and store it in cache before returning it. Here are the options:
666
+
667
+ ```typescript
668
+ export type GetOrSetFunctionOptions = {
669
+ ttl?: number | string;
670
+ cacheErrors?: boolean;
671
+ throwErrors?: boolean;
672
+ };
673
+ ```
674
+
675
+ Here is an example of how to use the `getOrSet` method:
676
+
677
+ ```javascript
678
+ import { Cacheable } from 'cacheable';
679
+ const cache = new Cacheable();
680
+ // Use getOrSet to fetch user data
681
+ const function_ = async () => Math.random() * 100;
682
+ const value = await cache.getOrSet('randomValue', function_, { ttl: '1h' });
683
+ console.log(value); // e.g. 42.123456789
684
+ ```
685
+
686
+ You can also use a function to compute the key for the function:
687
+
688
+ ```javascript
689
+ import { Cacheable, GetOrSetOptions } from 'cacheable';
690
+ const cache = new Cacheable();
691
+
692
+ // Function to generate a key based on options
693
+ const generateKey = (options?: GetOrSetOptions) => {
694
+ return `custom_key_:${options?.cacheId || 'default'}`;
695
+ };
696
+
697
+ const function_ = async () => Math.random() * 100;
698
+ const value = await cache.getOrSet(generateKey(), function_, { ttl: '1h' });
699
+ ```
700
+
701
+
702
+
617
703
  # How to Contribute
618
704
 
619
705
  You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`.
package/dist/index.cjs CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  KeyvCacheableMemory: () => KeyvCacheableMemory,
40
40
  KeyvHooks: () => import_keyv3.KeyvHooks,
41
41
  createKeyv: () => createKeyv,
42
+ getOrSet: () => getOrSet,
42
43
  shorthandToMilliseconds: () => shorthandToMilliseconds,
43
44
  shorthandToTime: () => shorthandToTime,
44
45
  wrap: () => wrap,
@@ -61,9 +62,7 @@ var shorthandToMilliseconds = (shorthand) => {
61
62
  if (Number.isNaN(Number(shorthand))) {
62
63
  const match = /^([\d.]+)\s*(ms|s|m|h|hr|d)$/i.exec(shorthand);
63
64
  if (!match) {
64
- throw new Error(
65
- `Unsupported time format: "${shorthand}". Use 'ms', 's', 'm', 'h', 'hr', or 'd'.`
66
- );
65
+ throw new Error(`Unsupported time format: "${shorthand}". Use 'ms', 's', 'm', 'h', 'hr', or 'd'.`);
67
66
  }
68
67
  const [, value, unit] = match;
69
68
  const numericValue = Number.parseFloat(value);
@@ -107,7 +106,7 @@ var shorthandToMilliseconds = (shorthand) => {
107
106
  return milliseconds;
108
107
  };
109
108
  var shorthandToTime = (shorthand, fromDate) => {
110
- fromDate ||= /* @__PURE__ */ new Date();
109
+ fromDate ??= /* @__PURE__ */ new Date();
111
110
  const milliseconds = shorthandToMilliseconds(shorthand);
112
111
  if (milliseconds === void 0) {
113
112
  return fromDate.getTime();
@@ -212,7 +211,10 @@ async function coalesceAsync(key, fnc) {
212
211
  function wrapSync(function_, options) {
213
212
  const { ttl, keyPrefix, cache } = options;
214
213
  return function(...arguments_) {
215
- const cacheKey = createWrapKey(function_, arguments_, keyPrefix);
214
+ let cacheKey = createWrapKey(function_, arguments_, keyPrefix);
215
+ if (options.createKey) {
216
+ cacheKey = options.createKey(function_, arguments_, options);
217
+ }
216
218
  let value = cache.get(cacheKey);
217
219
  if (value === void 0) {
218
220
  try {
@@ -229,19 +231,23 @@ function wrapSync(function_, options) {
229
231
  };
230
232
  }
231
233
  async function getOrSet(key, function_, options) {
232
- let value = await options.cache.get(key);
234
+ const keyString = typeof key === "function" ? key(options) : key;
235
+ let value = await options.cache.get(keyString);
233
236
  if (value === void 0) {
234
237
  const cacheId = options.cacheId ?? "default";
235
- const coalesceKey = `${cacheId}::${key}`;
238
+ const coalesceKey = `${cacheId}::${keyString}`;
236
239
  value = await coalesceAsync(coalesceKey, async () => {
237
240
  try {
238
241
  const result = await function_();
239
- await options.cache.set(key, result, options.ttl);
242
+ await options.cache.set(keyString, result, options.ttl);
240
243
  return result;
241
244
  } catch (error) {
242
245
  options.cache.emit("error", error);
243
246
  if (options.cacheErrors) {
244
- await options.cache.set(key, error, options.ttl);
247
+ await options.cache.set(keyString, error, options.ttl);
248
+ }
249
+ if (options.throwErrors) {
250
+ throw error;
245
251
  }
246
252
  }
247
253
  });
@@ -251,7 +257,10 @@ async function getOrSet(key, function_, options) {
251
257
  function wrap(function_, options) {
252
258
  const { keyPrefix, cache } = options;
253
259
  return async function(...arguments_) {
254
- const cacheKey = createWrapKey(function_, arguments_, keyPrefix);
260
+ let cacheKey = createWrapKey(function_, arguments_, keyPrefix);
261
+ if (options.createKey) {
262
+ cacheKey = options.createKey(function_, arguments_, options);
263
+ }
255
264
  return cache.getOrSet(cacheKey, async () => function_(...arguments_), options);
256
265
  };
257
266
  }
@@ -309,7 +318,7 @@ var DoublyLinkedList = class {
309
318
  this.head.prev = node;
310
319
  }
311
320
  this.head = node;
312
- this.tail ||= node;
321
+ this.tail ??= node;
313
322
  }
314
323
  // Get the oldest node (tail)
315
324
  getOldest() {
@@ -1417,7 +1426,11 @@ var Cacheable = class extends import_hookified2.Hookified {
1417
1426
  * @returns {void}
1418
1427
  */
1419
1428
  setPrimary(primary) {
1420
- this._primary = primary instanceof import_keyv2.Keyv ? primary : new import_keyv2.Keyv(primary);
1429
+ if (this.isKeyvInstance(primary)) {
1430
+ this._primary = primary;
1431
+ } else {
1432
+ this._primary = new import_keyv2.Keyv(primary);
1433
+ }
1421
1434
  this._primary.on("error", (error) => {
1422
1435
  this.emit("error" /* ERROR */, error);
1423
1436
  });
@@ -1428,11 +1441,22 @@ var Cacheable = class extends import_hookified2.Hookified {
1428
1441
  * @returns {void}
1429
1442
  */
1430
1443
  setSecondary(secondary) {
1431
- this._secondary = secondary instanceof import_keyv2.Keyv ? secondary : new import_keyv2.Keyv(secondary);
1444
+ if (this.isKeyvInstance(secondary)) {
1445
+ this._secondary = secondary;
1446
+ } else {
1447
+ this._secondary = new import_keyv2.Keyv(secondary);
1448
+ }
1432
1449
  this._secondary.on("error", (error) => {
1433
1450
  this.emit("error" /* ERROR */, error);
1434
1451
  });
1435
1452
  }
1453
+ isKeyvInstance(keyv) {
1454
+ if (keyv instanceof import_keyv2.Keyv) {
1455
+ return true;
1456
+ }
1457
+ const keyvMethods = ["generateIterator", "get", "getMany", "set", "setMany", "delete", "deleteMany", "has", "hasMany", "clear", "disconnect", "serialize", "deserialize"];
1458
+ return keyvMethods.every((method) => typeof keyv[method] === "function");
1459
+ }
1436
1460
  getNameSpace() {
1437
1461
  if (typeof this._namespace === "function") {
1438
1462
  return this._namespace();
@@ -1490,7 +1514,10 @@ var Cacheable = class extends import_hookified2.Hookified {
1490
1514
  if (!result[i] && secondaryResults[i]) {
1491
1515
  result[i] = secondaryResults[i];
1492
1516
  const cascadeTtl = getCascadingTtl(this._ttl, this._primary.ttl);
1493
- const expires = secondaryResults[i].expires;
1517
+ let { expires } = secondaryResults[i];
1518
+ if (expires === null) {
1519
+ expires = void 0;
1520
+ }
1494
1521
  const ttl = calculateTtlFromExpiration(cascadeTtl, expires);
1495
1522
  const setItem = { key, value: result[i].value, ttl };
1496
1523
  await this.hook("BEFORE_SECONDARY_SETS_PRIMARY" /* BEFORE_SECONDARY_SETS_PRIMARY */, setItem);
@@ -1746,9 +1773,10 @@ var Cacheable = class extends import_hookified2.Hookified {
1746
1773
  * Retrieves the value associated with the given key from the cache. If the key is not found,
1747
1774
  * invokes the provided function to calculate the value, stores it in the cache, and then returns it.
1748
1775
  *
1749
- * @param {string} key - The key to retrieve or set in the cache.
1776
+ * @param {GetOrSetKey} key - The key to retrieve or set in the cache. This can also be a function that returns a string key.
1777
+ * If a function is provided, it will be called with the cache options to generate the key.
1750
1778
  * @param {() => Promise<T>} function_ - The asynchronous function that computes the value to be cached if the key does not exist.
1751
- * @param {WrapFunctionOptions} [options] - Optional settings for caching, such as the time to live (TTL) or whether to cache errors.
1779
+ * @param {GetOrSetFunctionOptions} [options] - Optional settings for caching, such as the time to live (TTL) or whether to cache errors.
1752
1780
  * @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.
1753
1781
  */
1754
1782
  async getOrSet(key, function_, options) {
@@ -1756,7 +1784,8 @@ var Cacheable = class extends import_hookified2.Hookified {
1756
1784
  cache: this,
1757
1785
  cacheId: this._cacheId,
1758
1786
  ttl: options?.ttl ?? this._ttl,
1759
- cacheErrors: options?.cacheErrors
1787
+ cacheErrors: options?.cacheErrors,
1788
+ throwErrors: options?.throwErrors
1760
1789
  };
1761
1790
  return getOrSet(key, function_, getOrSetOptions);
1762
1791
  }
@@ -1828,6 +1857,7 @@ var Cacheable = class extends import_hookified2.Hookified {
1828
1857
  KeyvCacheableMemory,
1829
1858
  KeyvHooks,
1830
1859
  createKeyv,
1860
+ getOrSet,
1831
1861
  shorthandToMilliseconds,
1832
1862
  shorthandToTime,
1833
1863
  wrap,
package/dist/index.d.cts CHANGED
@@ -109,13 +109,21 @@ type CacheableStoreItem = {
109
109
  expires?: number;
110
110
  };
111
111
 
112
+ type GetOrSetKey = string | ((options?: GetOrSetOptions) => string);
112
113
  type GetOrSetFunctionOptions = {
113
114
  ttl?: number | string;
114
115
  cacheErrors?: boolean;
116
+ throwErrors?: boolean;
115
117
  };
118
+ type GetOrSetOptions = GetOrSetFunctionOptions & {
119
+ cacheId?: string;
120
+ cache: Cacheable;
121
+ };
122
+ type CreateWrapKey = (function_: AnyFunction, arguments_: any[], options?: WrapFunctionOptions) => string;
116
123
  type WrapFunctionOptions = {
117
124
  ttl?: number | string;
118
125
  keyPrefix?: string;
126
+ createKey?: CreateWrapKey;
119
127
  cacheErrors?: boolean;
120
128
  cacheId?: string;
121
129
  };
@@ -127,6 +135,7 @@ type WrapSyncOptions = WrapFunctionOptions & {
127
135
  };
128
136
  type AnyFunction = (...arguments_: any[]) => any;
129
137
  declare function wrapSync<T>(function_: AnyFunction, options: WrapSyncOptions): AnyFunction;
138
+ declare function getOrSet<T>(key: GetOrSetKey, function_: () => Promise<T>, options: GetOrSetOptions): Promise<T | undefined>;
130
139
  declare function wrap<T>(function_: AnyFunction, options: WrapOptions): AnyFunction;
131
140
 
132
141
  declare enum StoreHashAlgorithm {
@@ -611,6 +620,7 @@ declare class Cacheable extends Hookified {
611
620
  * @returns {void}
612
621
  */
613
622
  setSecondary(secondary: Keyv | KeyvStoreAdapter): void;
623
+ isKeyvInstance(keyv: any): boolean;
614
624
  getNameSpace(): string | undefined;
615
625
  /**
616
626
  * Retrieves an entry from the cache, with an optional “raw” mode.
@@ -727,12 +737,13 @@ declare class Cacheable extends Hookified {
727
737
  * Retrieves the value associated with the given key from the cache. If the key is not found,
728
738
  * invokes the provided function to calculate the value, stores it in the cache, and then returns it.
729
739
  *
730
- * @param {string} key - The key to retrieve or set in the cache.
740
+ * @param {GetOrSetKey} key - The key to retrieve or set in the cache. This can also be a function that returns a string key.
741
+ * If a function is provided, it will be called with the cache options to generate the key.
731
742
  * @param {() => Promise<T>} function_ - The asynchronous function that computes the value to be cached if the key does not exist.
732
- * @param {WrapFunctionOptions} [options] - Optional settings for caching, such as the time to live (TTL) or whether to cache errors.
743
+ * @param {GetOrSetFunctionOptions} [options] - Optional settings for caching, such as the time to live (TTL) or whether to cache errors.
733
744
  * @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.
734
745
  */
735
- getOrSet<T>(key: string, function_: () => Promise<T>, options?: GetOrSetFunctionOptions): Promise<T | undefined>;
746
+ getOrSet<T>(key: GetOrSetKey, function_: () => Promise<T>, options?: GetOrSetFunctionOptions): Promise<T | undefined>;
736
747
  /**
737
748
  * Will hash an object using the specified algorithm. The default algorithm is 'sha256'.
738
749
  * @param {any} object the object to hash
@@ -748,4 +759,4 @@ declare class Cacheable extends Hookified {
748
759
  private setTtl;
749
760
  }
750
761
 
751
- export { Cacheable, CacheableEvents, CacheableHooks, type CacheableItem, CacheableMemory, type CacheableMemoryOptions, type CacheableOptions, CacheableStats, KeyvCacheableMemory, type WrapOptions, type WrapSyncOptions, createKeyv, shorthandToMilliseconds, shorthandToTime, wrap, wrapSync };
762
+ export { Cacheable, CacheableEvents, CacheableHooks, type CacheableItem, CacheableMemory, type CacheableMemoryOptions, type CacheableOptions, CacheableStats, type GetOrSetFunctionOptions, type GetOrSetKey, type GetOrSetOptions, KeyvCacheableMemory, type WrapOptions, type WrapSyncOptions, createKeyv, getOrSet, shorthandToMilliseconds, shorthandToTime, wrap, wrapSync };
package/dist/index.d.ts CHANGED
@@ -109,13 +109,21 @@ type CacheableStoreItem = {
109
109
  expires?: number;
110
110
  };
111
111
 
112
+ type GetOrSetKey = string | ((options?: GetOrSetOptions) => string);
112
113
  type GetOrSetFunctionOptions = {
113
114
  ttl?: number | string;
114
115
  cacheErrors?: boolean;
116
+ throwErrors?: boolean;
115
117
  };
118
+ type GetOrSetOptions = GetOrSetFunctionOptions & {
119
+ cacheId?: string;
120
+ cache: Cacheable;
121
+ };
122
+ type CreateWrapKey = (function_: AnyFunction, arguments_: any[], options?: WrapFunctionOptions) => string;
116
123
  type WrapFunctionOptions = {
117
124
  ttl?: number | string;
118
125
  keyPrefix?: string;
126
+ createKey?: CreateWrapKey;
119
127
  cacheErrors?: boolean;
120
128
  cacheId?: string;
121
129
  };
@@ -127,6 +135,7 @@ type WrapSyncOptions = WrapFunctionOptions & {
127
135
  };
128
136
  type AnyFunction = (...arguments_: any[]) => any;
129
137
  declare function wrapSync<T>(function_: AnyFunction, options: WrapSyncOptions): AnyFunction;
138
+ declare function getOrSet<T>(key: GetOrSetKey, function_: () => Promise<T>, options: GetOrSetOptions): Promise<T | undefined>;
130
139
  declare function wrap<T>(function_: AnyFunction, options: WrapOptions): AnyFunction;
131
140
 
132
141
  declare enum StoreHashAlgorithm {
@@ -611,6 +620,7 @@ declare class Cacheable extends Hookified {
611
620
  * @returns {void}
612
621
  */
613
622
  setSecondary(secondary: Keyv | KeyvStoreAdapter): void;
623
+ isKeyvInstance(keyv: any): boolean;
614
624
  getNameSpace(): string | undefined;
615
625
  /**
616
626
  * Retrieves an entry from the cache, with an optional “raw” mode.
@@ -727,12 +737,13 @@ declare class Cacheable extends Hookified {
727
737
  * Retrieves the value associated with the given key from the cache. If the key is not found,
728
738
  * invokes the provided function to calculate the value, stores it in the cache, and then returns it.
729
739
  *
730
- * @param {string} key - The key to retrieve or set in the cache.
740
+ * @param {GetOrSetKey} key - The key to retrieve or set in the cache. This can also be a function that returns a string key.
741
+ * If a function is provided, it will be called with the cache options to generate the key.
731
742
  * @param {() => Promise<T>} function_ - The asynchronous function that computes the value to be cached if the key does not exist.
732
- * @param {WrapFunctionOptions} [options] - Optional settings for caching, such as the time to live (TTL) or whether to cache errors.
743
+ * @param {GetOrSetFunctionOptions} [options] - Optional settings for caching, such as the time to live (TTL) or whether to cache errors.
733
744
  * @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.
734
745
  */
735
- getOrSet<T>(key: string, function_: () => Promise<T>, options?: GetOrSetFunctionOptions): Promise<T | undefined>;
746
+ getOrSet<T>(key: GetOrSetKey, function_: () => Promise<T>, options?: GetOrSetFunctionOptions): Promise<T | undefined>;
736
747
  /**
737
748
  * Will hash an object using the specified algorithm. The default algorithm is 'sha256'.
738
749
  * @param {any} object the object to hash
@@ -748,4 +759,4 @@ declare class Cacheable extends Hookified {
748
759
  private setTtl;
749
760
  }
750
761
 
751
- export { Cacheable, CacheableEvents, CacheableHooks, type CacheableItem, CacheableMemory, type CacheableMemoryOptions, type CacheableOptions, CacheableStats, KeyvCacheableMemory, type WrapOptions, type WrapSyncOptions, createKeyv, shorthandToMilliseconds, shorthandToTime, wrap, wrapSync };
762
+ export { Cacheable, CacheableEvents, CacheableHooks, type CacheableItem, CacheableMemory, type CacheableMemoryOptions, type CacheableOptions, CacheableStats, type GetOrSetFunctionOptions, type GetOrSetKey, type GetOrSetOptions, KeyvCacheableMemory, type WrapOptions, type WrapSyncOptions, createKeyv, getOrSet, shorthandToMilliseconds, shorthandToTime, wrap, wrapSync };
package/dist/index.js CHANGED
@@ -15,9 +15,7 @@ var shorthandToMilliseconds = (shorthand) => {
15
15
  if (Number.isNaN(Number(shorthand))) {
16
16
  const match = /^([\d.]+)\s*(ms|s|m|h|hr|d)$/i.exec(shorthand);
17
17
  if (!match) {
18
- throw new Error(
19
- `Unsupported time format: "${shorthand}". Use 'ms', 's', 'm', 'h', 'hr', or 'd'.`
20
- );
18
+ throw new Error(`Unsupported time format: "${shorthand}". Use 'ms', 's', 'm', 'h', 'hr', or 'd'.`);
21
19
  }
22
20
  const [, value, unit] = match;
23
21
  const numericValue = Number.parseFloat(value);
@@ -61,7 +59,7 @@ var shorthandToMilliseconds = (shorthand) => {
61
59
  return milliseconds;
62
60
  };
63
61
  var shorthandToTime = (shorthand, fromDate) => {
64
- fromDate ||= /* @__PURE__ */ new Date();
62
+ fromDate ??= /* @__PURE__ */ new Date();
65
63
  const milliseconds = shorthandToMilliseconds(shorthand);
66
64
  if (milliseconds === void 0) {
67
65
  return fromDate.getTime();
@@ -168,7 +166,10 @@ async function coalesceAsync(key, fnc) {
168
166
  function wrapSync(function_, options) {
169
167
  const { ttl, keyPrefix, cache } = options;
170
168
  return function(...arguments_) {
171
- const cacheKey = createWrapKey(function_, arguments_, keyPrefix);
169
+ let cacheKey = createWrapKey(function_, arguments_, keyPrefix);
170
+ if (options.createKey) {
171
+ cacheKey = options.createKey(function_, arguments_, options);
172
+ }
172
173
  let value = cache.get(cacheKey);
173
174
  if (value === void 0) {
174
175
  try {
@@ -185,19 +186,23 @@ function wrapSync(function_, options) {
185
186
  };
186
187
  }
187
188
  async function getOrSet(key, function_, options) {
188
- let value = await options.cache.get(key);
189
+ const keyString = typeof key === "function" ? key(options) : key;
190
+ let value = await options.cache.get(keyString);
189
191
  if (value === void 0) {
190
192
  const cacheId = options.cacheId ?? "default";
191
- const coalesceKey = `${cacheId}::${key}`;
193
+ const coalesceKey = `${cacheId}::${keyString}`;
192
194
  value = await coalesceAsync(coalesceKey, async () => {
193
195
  try {
194
196
  const result = await function_();
195
- await options.cache.set(key, result, options.ttl);
197
+ await options.cache.set(keyString, result, options.ttl);
196
198
  return result;
197
199
  } catch (error) {
198
200
  options.cache.emit("error", error);
199
201
  if (options.cacheErrors) {
200
- await options.cache.set(key, error, options.ttl);
202
+ await options.cache.set(keyString, error, options.ttl);
203
+ }
204
+ if (options.throwErrors) {
205
+ throw error;
201
206
  }
202
207
  }
203
208
  });
@@ -207,7 +212,10 @@ async function getOrSet(key, function_, options) {
207
212
  function wrap(function_, options) {
208
213
  const { keyPrefix, cache } = options;
209
214
  return async function(...arguments_) {
210
- const cacheKey = createWrapKey(function_, arguments_, keyPrefix);
215
+ let cacheKey = createWrapKey(function_, arguments_, keyPrefix);
216
+ if (options.createKey) {
217
+ cacheKey = options.createKey(function_, arguments_, options);
218
+ }
211
219
  return cache.getOrSet(cacheKey, async () => function_(...arguments_), options);
212
220
  };
213
221
  }
@@ -265,7 +273,7 @@ var DoublyLinkedList = class {
265
273
  this.head.prev = node;
266
274
  }
267
275
  this.head = node;
268
- this.tail ||= node;
276
+ this.tail ??= node;
269
277
  }
270
278
  // Get the oldest node (tail)
271
279
  getOldest() {
@@ -1376,7 +1384,11 @@ var Cacheable = class extends Hookified2 {
1376
1384
  * @returns {void}
1377
1385
  */
1378
1386
  setPrimary(primary) {
1379
- this._primary = primary instanceof Keyv2 ? primary : new Keyv2(primary);
1387
+ if (this.isKeyvInstance(primary)) {
1388
+ this._primary = primary;
1389
+ } else {
1390
+ this._primary = new Keyv2(primary);
1391
+ }
1380
1392
  this._primary.on("error", (error) => {
1381
1393
  this.emit("error" /* ERROR */, error);
1382
1394
  });
@@ -1387,11 +1399,22 @@ var Cacheable = class extends Hookified2 {
1387
1399
  * @returns {void}
1388
1400
  */
1389
1401
  setSecondary(secondary) {
1390
- this._secondary = secondary instanceof Keyv2 ? secondary : new Keyv2(secondary);
1402
+ if (this.isKeyvInstance(secondary)) {
1403
+ this._secondary = secondary;
1404
+ } else {
1405
+ this._secondary = new Keyv2(secondary);
1406
+ }
1391
1407
  this._secondary.on("error", (error) => {
1392
1408
  this.emit("error" /* ERROR */, error);
1393
1409
  });
1394
1410
  }
1411
+ isKeyvInstance(keyv) {
1412
+ if (keyv instanceof Keyv2) {
1413
+ return true;
1414
+ }
1415
+ const keyvMethods = ["generateIterator", "get", "getMany", "set", "setMany", "delete", "deleteMany", "has", "hasMany", "clear", "disconnect", "serialize", "deserialize"];
1416
+ return keyvMethods.every((method) => typeof keyv[method] === "function");
1417
+ }
1395
1418
  getNameSpace() {
1396
1419
  if (typeof this._namespace === "function") {
1397
1420
  return this._namespace();
@@ -1449,7 +1472,10 @@ var Cacheable = class extends Hookified2 {
1449
1472
  if (!result[i] && secondaryResults[i]) {
1450
1473
  result[i] = secondaryResults[i];
1451
1474
  const cascadeTtl = getCascadingTtl(this._ttl, this._primary.ttl);
1452
- const expires = secondaryResults[i].expires;
1475
+ let { expires } = secondaryResults[i];
1476
+ if (expires === null) {
1477
+ expires = void 0;
1478
+ }
1453
1479
  const ttl = calculateTtlFromExpiration(cascadeTtl, expires);
1454
1480
  const setItem = { key, value: result[i].value, ttl };
1455
1481
  await this.hook("BEFORE_SECONDARY_SETS_PRIMARY" /* BEFORE_SECONDARY_SETS_PRIMARY */, setItem);
@@ -1705,9 +1731,10 @@ var Cacheable = class extends Hookified2 {
1705
1731
  * Retrieves the value associated with the given key from the cache. If the key is not found,
1706
1732
  * invokes the provided function to calculate the value, stores it in the cache, and then returns it.
1707
1733
  *
1708
- * @param {string} key - The key to retrieve or set in the cache.
1734
+ * @param {GetOrSetKey} key - The key to retrieve or set in the cache. This can also be a function that returns a string key.
1735
+ * If a function is provided, it will be called with the cache options to generate the key.
1709
1736
  * @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.
1737
+ * @param {GetOrSetFunctionOptions} [options] - Optional settings for caching, such as the time to live (TTL) or whether to cache errors.
1711
1738
  * @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
1739
  */
1713
1740
  async getOrSet(key, function_, options) {
@@ -1715,7 +1742,8 @@ var Cacheable = class extends Hookified2 {
1715
1742
  cache: this,
1716
1743
  cacheId: this._cacheId,
1717
1744
  ttl: options?.ttl ?? this._ttl,
1718
- cacheErrors: options?.cacheErrors
1745
+ cacheErrors: options?.cacheErrors,
1746
+ throwErrors: options?.throwErrors
1719
1747
  };
1720
1748
  return getOrSet(key, function_, getOrSetOptions);
1721
1749
  }
@@ -1786,6 +1814,7 @@ export {
1786
1814
  KeyvCacheableMemory,
1787
1815
  KeyvHooks,
1788
1816
  createKeyv,
1817
+ getOrSet,
1789
1818
  shorthandToMilliseconds,
1790
1819
  shorthandToTime,
1791
1820
  wrap,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cacheable",
3
- "version": "1.10.0",
3
+ "version": "1.10.2",
4
4
  "description": "High Performance Layer 1 / Layer 2 Caching with Keyv Storage",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -21,20 +21,22 @@
21
21
  "license": "MIT",
22
22
  "private": false,
23
23
  "devDependencies": {
24
- "@faker-js/faker": "^9.7.0",
25
- "@keyv/redis": "^4.4.0",
26
- "@types/node": "^22.15.3",
27
- "@vitest/coverage-v8": "^3.1.3",
24
+ "@faker-js/faker": "^9.8.0",
25
+ "@keyv/redis": "^4.5.0",
26
+ "@keyv/valkey": "^1.0.6",
27
+ "@types/eslint": "^9.6.1",
28
+ "@types/node": "^24.0.7",
29
+ "@vitest/coverage-v8": "^3.2.4",
28
30
  "lru-cache": "^11.1.0",
29
31
  "rimraf": "^6.0.1",
30
- "tsup": "^8.4.0",
32
+ "tsup": "^8.5.0",
31
33
  "typescript": "^5.8.3",
32
- "vitest": "^3.1.3",
33
- "xo": "^0.60.0"
34
+ "vitest": "^3.2.4",
35
+ "xo": "^1.1.1"
34
36
  },
35
37
  "dependencies": {
36
- "hookified": "^1.8.2",
37
- "keyv": "^5.3.3"
38
+ "hookified": "^1.10.0",
39
+ "keyv": "^5.3.4"
38
40
  },
39
41
  "keywords": [
40
42
  "cacheable",
@@ -70,7 +72,7 @@
70
72
  "build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean",
71
73
  "prepublish": "pnpm build",
72
74
  "test": "xo --fix && vitest run --coverage",
73
- "test:ci": "xo && vitest run",
75
+ "test:ci": "xo && vitest run --coverage",
74
76
  "clean": "rimraf ./dist ./coverage ./node_modules"
75
77
  }
76
78
  }