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 +87 -1
- package/dist/index.cjs +47 -17
- package/dist/index.d.cts +15 -4
- package/dist/index.d.ts +15 -4
- package/dist/index.js +46 -17
- package/package.json +13 -11
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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}::${
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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:
|
|
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 {
|
|
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 {
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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}::${
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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.
|
|
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.
|
|
25
|
-
"@keyv/redis": "^4.
|
|
26
|
-
"@
|
|
27
|
-
"@
|
|
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.
|
|
32
|
+
"tsup": "^8.5.0",
|
|
31
33
|
"typescript": "^5.8.3",
|
|
32
|
-
"vitest": "^3.
|
|
33
|
-
"xo": "^
|
|
34
|
+
"vitest": "^3.2.4",
|
|
35
|
+
"xo": "^1.1.1"
|
|
34
36
|
},
|
|
35
37
|
"dependencies": {
|
|
36
|
-
"hookified": "^1.
|
|
37
|
-
"keyv": "^5.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
|
}
|