cacheable 1.8.2 → 1.8.4
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 +25 -1
- package/dist/index.cjs +71 -9
- package/dist/index.d.cts +7 -11
- package/dist/index.d.ts +7 -11
- package/dist/index.js +71 -9
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* Scalable and trusted storage engine by Keyv
|
|
18
18
|
* Memory Caching with LRU and Expiration `CacheableMemory`
|
|
19
19
|
* Resilient to failures with try/catch and offline
|
|
20
|
-
* Wrap / Memoization for Sync and Async Functions
|
|
20
|
+
* Wrap / Memoization for Sync and Async Functions with Stampede Protection
|
|
21
21
|
* Hooks and Events to extend functionality
|
|
22
22
|
* Shorthand for ttl in milliseconds `(1m = 60000) (1h = 3600000) (1d = 86400000)`
|
|
23
23
|
* Non-blocking operations for layer 2 caching
|
|
@@ -310,6 +310,30 @@ const wrappedFunction = cache.wrap(asyncFunction, options);
|
|
|
310
310
|
console.log(await wrappedFunction(2)); // 4
|
|
311
311
|
console.log(await wrappedFunction(2)); // 4 from cache
|
|
312
312
|
```
|
|
313
|
+
With `Cacheable` we have also included stampede protection so that a `Promise` based call will only be called once if multiple requests of the same are executed at the same time. Here is an example of how to test for stampede protection:
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
import { Cacheable } from 'cacheable';
|
|
317
|
+
const asyncFunction = async (value: number) => {
|
|
318
|
+
return value;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const cache = new Cacheable();
|
|
322
|
+
const options = {
|
|
323
|
+
ttl: '1h', // 1 hour
|
|
324
|
+
keyPrefix: 'p1', // key prefix. This is used if you have multiple functions and need to set a unique prefix.
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const wrappedFunction = cache.wrap(asyncFunction, options);
|
|
328
|
+
const promises = [];
|
|
329
|
+
for (let i = 0; i < 10; i++) {
|
|
330
|
+
promises.push(wrappedFunction(i));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const results = await Promise.all(promises); // all results should be the same
|
|
334
|
+
|
|
335
|
+
console.log(results); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
336
|
+
```
|
|
313
337
|
|
|
314
338
|
In this example we are wrapping an `async` function in a cache with a `ttl` of `1 hour`. This will cache the result of the function for `1 hour` and then expire the value. You can also wrap a `sync` function in a cache:
|
|
315
339
|
|
package/dist/index.cjs
CHANGED
|
@@ -126,6 +126,61 @@ function hash(object, algorithm = "sha256") {
|
|
|
126
126
|
return hasher.digest("hex");
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// src/coalesce-async.ts
|
|
130
|
+
var callbacks = /* @__PURE__ */ new Map();
|
|
131
|
+
function hasKey(key) {
|
|
132
|
+
return callbacks.has(key);
|
|
133
|
+
}
|
|
134
|
+
function addKey(key) {
|
|
135
|
+
callbacks.set(key, []);
|
|
136
|
+
}
|
|
137
|
+
function removeKey(key) {
|
|
138
|
+
callbacks.delete(key);
|
|
139
|
+
}
|
|
140
|
+
function addCallbackToKey(key, callback) {
|
|
141
|
+
const stash = getCallbacksByKey(key);
|
|
142
|
+
stash.push(callback);
|
|
143
|
+
callbacks.set(key, stash);
|
|
144
|
+
}
|
|
145
|
+
function getCallbacksByKey(key) {
|
|
146
|
+
return callbacks.get(key) ?? [];
|
|
147
|
+
}
|
|
148
|
+
async function enqueue(key) {
|
|
149
|
+
return new Promise((resolve, reject) => {
|
|
150
|
+
const callback = { resolve, reject };
|
|
151
|
+
addCallbackToKey(key, callback);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function dequeue(key) {
|
|
155
|
+
const stash = getCallbacksByKey(key);
|
|
156
|
+
removeKey(key);
|
|
157
|
+
return stash;
|
|
158
|
+
}
|
|
159
|
+
function coalesce(options) {
|
|
160
|
+
const { key, error, result } = options;
|
|
161
|
+
for (const callback of dequeue(key)) {
|
|
162
|
+
if (error) {
|
|
163
|
+
callback.reject(error);
|
|
164
|
+
} else {
|
|
165
|
+
callback.resolve(result);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function coalesceAsync(key, fnc) {
|
|
170
|
+
if (!hasKey(key)) {
|
|
171
|
+
addKey(key);
|
|
172
|
+
try {
|
|
173
|
+
const result = await Promise.resolve(fnc());
|
|
174
|
+
coalesce({ key, result });
|
|
175
|
+
return result;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
coalesce({ key, error });
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return enqueue(key);
|
|
182
|
+
}
|
|
183
|
+
|
|
129
184
|
// src/wrap.ts
|
|
130
185
|
function wrapSync(function_, options) {
|
|
131
186
|
const { ttl, keyPrefix, cache } = options;
|
|
@@ -142,11 +197,18 @@ function wrapSync(function_, options) {
|
|
|
142
197
|
function wrap(function_, options) {
|
|
143
198
|
const { ttl, keyPrefix, cache } = options;
|
|
144
199
|
return async function(...arguments_) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
value = await
|
|
149
|
-
|
|
200
|
+
let value;
|
|
201
|
+
try {
|
|
202
|
+
const cacheKey = createWrapKey(function_, arguments_, keyPrefix);
|
|
203
|
+
value = await cache.get(cacheKey);
|
|
204
|
+
if (value === void 0) {
|
|
205
|
+
value = await coalesceAsync(cacheKey, async () => {
|
|
206
|
+
const result = await function_(...arguments_);
|
|
207
|
+
await cache.set(cacheKey, result, ttl);
|
|
208
|
+
return result;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
150
212
|
}
|
|
151
213
|
return value;
|
|
152
214
|
};
|
|
@@ -713,8 +775,8 @@ var CacheableMemory = class {
|
|
|
713
775
|
*/
|
|
714
776
|
wrap(function_, options) {
|
|
715
777
|
const wrapOptions = {
|
|
716
|
-
ttl: options.
|
|
717
|
-
keyPrefix: options
|
|
778
|
+
ttl: options?.ttl ?? this._ttl,
|
|
779
|
+
keyPrefix: options?.keyPrefix,
|
|
718
780
|
cache: this
|
|
719
781
|
};
|
|
720
782
|
return wrapSync(function_, wrapOptions);
|
|
@@ -1522,8 +1584,8 @@ var Cacheable = class extends import_hookified.Hookified {
|
|
|
1522
1584
|
*/
|
|
1523
1585
|
wrap(function_, options) {
|
|
1524
1586
|
const wrapOptions = {
|
|
1525
|
-
ttl: options.
|
|
1526
|
-
keyPrefix: options
|
|
1587
|
+
ttl: options?.ttl ?? this._ttl,
|
|
1588
|
+
keyPrefix: options?.keyPrefix,
|
|
1527
1589
|
cache: this
|
|
1528
1590
|
};
|
|
1529
1591
|
return wrap(function_, wrapOptions);
|
package/dist/index.d.cts
CHANGED
|
@@ -109,20 +109,16 @@ type CacheableStoreItem = {
|
|
|
109
109
|
expires?: number;
|
|
110
110
|
};
|
|
111
111
|
|
|
112
|
-
type
|
|
112
|
+
type WrapFunctionOptions = {
|
|
113
113
|
ttl?: number | string;
|
|
114
114
|
keyPrefix?: string;
|
|
115
|
+
};
|
|
116
|
+
type WrapOptions = WrapFunctionOptions & {
|
|
115
117
|
cache: Cacheable;
|
|
116
118
|
};
|
|
117
|
-
type WrapSyncOptions = {
|
|
118
|
-
ttl?: number | string;
|
|
119
|
-
keyPrefix?: string;
|
|
119
|
+
type WrapSyncOptions = WrapFunctionOptions & {
|
|
120
120
|
cache: CacheableMemory;
|
|
121
121
|
};
|
|
122
|
-
type WrapFunctionOptions = {
|
|
123
|
-
ttl?: number | string;
|
|
124
|
-
keyPrefix: string;
|
|
125
|
-
};
|
|
126
122
|
type AnyFunction = (...arguments_: any[]) => any;
|
|
127
123
|
declare function wrapSync<T>(function_: AnyFunction, options: WrapSyncOptions): AnyFunction;
|
|
128
124
|
declare function wrap<T>(function_: AnyFunction, options: WrapOptions): AnyFunction;
|
|
@@ -369,7 +365,7 @@ declare class CacheableMemory {
|
|
|
369
365
|
* @param {Object} [options] - The options to wrap
|
|
370
366
|
* @returns {Function} - The wrapped function
|
|
371
367
|
*/
|
|
372
|
-
wrap<T>(function_: (...arguments_: any[]) => T, options
|
|
368
|
+
wrap<T>(function_: (...arguments_: any[]) => T, options?: WrapFunctionOptions): (...arguments_: any[]) => T;
|
|
373
369
|
private isPrimitive;
|
|
374
370
|
private concatStores;
|
|
375
371
|
private setTtl;
|
|
@@ -625,7 +621,7 @@ declare class Cacheable extends Hookified {
|
|
|
625
621
|
* @param {WrapOptions} [options] The options for the wrap function
|
|
626
622
|
* @returns {Function} The wrapped function
|
|
627
623
|
*/
|
|
628
|
-
wrap<T>(function_: (...arguments_: any[]) => T, options
|
|
624
|
+
wrap<T>(function_: (...arguments_: any[]) => T, options?: WrapFunctionOptions): (...arguments_: any[]) => T;
|
|
629
625
|
/**
|
|
630
626
|
* Will hash an object using the specified algorithm. The default algorithm is 'sha256'.
|
|
631
627
|
* @param {any} object the object to hash
|
|
@@ -639,4 +635,4 @@ declare class Cacheable extends Hookified {
|
|
|
639
635
|
private setTtl;
|
|
640
636
|
}
|
|
641
637
|
|
|
642
|
-
export { Cacheable, CacheableEvents, CacheableHooks, type CacheableItem, CacheableMemory, type CacheableOptions, CacheableStats, KeyvCacheableMemory, type WrapOptions, type WrapSyncOptions, shorthandToMilliseconds, shorthandToTime, wrap, wrapSync };
|
|
638
|
+
export { Cacheable, CacheableEvents, CacheableHooks, type CacheableItem, CacheableMemory, type CacheableMemoryOptions, type CacheableOptions, CacheableStats, KeyvCacheableMemory, type WrapOptions, type WrapSyncOptions, shorthandToMilliseconds, shorthandToTime, wrap, wrapSync };
|
package/dist/index.d.ts
CHANGED
|
@@ -109,20 +109,16 @@ type CacheableStoreItem = {
|
|
|
109
109
|
expires?: number;
|
|
110
110
|
};
|
|
111
111
|
|
|
112
|
-
type
|
|
112
|
+
type WrapFunctionOptions = {
|
|
113
113
|
ttl?: number | string;
|
|
114
114
|
keyPrefix?: string;
|
|
115
|
+
};
|
|
116
|
+
type WrapOptions = WrapFunctionOptions & {
|
|
115
117
|
cache: Cacheable;
|
|
116
118
|
};
|
|
117
|
-
type WrapSyncOptions = {
|
|
118
|
-
ttl?: number | string;
|
|
119
|
-
keyPrefix?: string;
|
|
119
|
+
type WrapSyncOptions = WrapFunctionOptions & {
|
|
120
120
|
cache: CacheableMemory;
|
|
121
121
|
};
|
|
122
|
-
type WrapFunctionOptions = {
|
|
123
|
-
ttl?: number | string;
|
|
124
|
-
keyPrefix: string;
|
|
125
|
-
};
|
|
126
122
|
type AnyFunction = (...arguments_: any[]) => any;
|
|
127
123
|
declare function wrapSync<T>(function_: AnyFunction, options: WrapSyncOptions): AnyFunction;
|
|
128
124
|
declare function wrap<T>(function_: AnyFunction, options: WrapOptions): AnyFunction;
|
|
@@ -369,7 +365,7 @@ declare class CacheableMemory {
|
|
|
369
365
|
* @param {Object} [options] - The options to wrap
|
|
370
366
|
* @returns {Function} - The wrapped function
|
|
371
367
|
*/
|
|
372
|
-
wrap<T>(function_: (...arguments_: any[]) => T, options
|
|
368
|
+
wrap<T>(function_: (...arguments_: any[]) => T, options?: WrapFunctionOptions): (...arguments_: any[]) => T;
|
|
373
369
|
private isPrimitive;
|
|
374
370
|
private concatStores;
|
|
375
371
|
private setTtl;
|
|
@@ -625,7 +621,7 @@ declare class Cacheable extends Hookified {
|
|
|
625
621
|
* @param {WrapOptions} [options] The options for the wrap function
|
|
626
622
|
* @returns {Function} The wrapped function
|
|
627
623
|
*/
|
|
628
|
-
wrap<T>(function_: (...arguments_: any[]) => T, options
|
|
624
|
+
wrap<T>(function_: (...arguments_: any[]) => T, options?: WrapFunctionOptions): (...arguments_: any[]) => T;
|
|
629
625
|
/**
|
|
630
626
|
* Will hash an object using the specified algorithm. The default algorithm is 'sha256'.
|
|
631
627
|
* @param {any} object the object to hash
|
|
@@ -639,4 +635,4 @@ declare class Cacheable extends Hookified {
|
|
|
639
635
|
private setTtl;
|
|
640
636
|
}
|
|
641
637
|
|
|
642
|
-
export { Cacheable, CacheableEvents, CacheableHooks, type CacheableItem, CacheableMemory, type CacheableOptions, CacheableStats, KeyvCacheableMemory, type WrapOptions, type WrapSyncOptions, shorthandToMilliseconds, shorthandToTime, wrap, wrapSync };
|
|
638
|
+
export { Cacheable, CacheableEvents, CacheableHooks, type CacheableItem, CacheableMemory, type CacheableMemoryOptions, type CacheableOptions, CacheableStats, KeyvCacheableMemory, type WrapOptions, type WrapSyncOptions, shorthandToMilliseconds, shorthandToTime, wrap, wrapSync };
|
package/dist/index.js
CHANGED
|
@@ -81,6 +81,61 @@ function hash(object, algorithm = "sha256") {
|
|
|
81
81
|
return hasher.digest("hex");
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
// src/coalesce-async.ts
|
|
85
|
+
var callbacks = /* @__PURE__ */ new Map();
|
|
86
|
+
function hasKey(key) {
|
|
87
|
+
return callbacks.has(key);
|
|
88
|
+
}
|
|
89
|
+
function addKey(key) {
|
|
90
|
+
callbacks.set(key, []);
|
|
91
|
+
}
|
|
92
|
+
function removeKey(key) {
|
|
93
|
+
callbacks.delete(key);
|
|
94
|
+
}
|
|
95
|
+
function addCallbackToKey(key, callback) {
|
|
96
|
+
const stash = getCallbacksByKey(key);
|
|
97
|
+
stash.push(callback);
|
|
98
|
+
callbacks.set(key, stash);
|
|
99
|
+
}
|
|
100
|
+
function getCallbacksByKey(key) {
|
|
101
|
+
return callbacks.get(key) ?? [];
|
|
102
|
+
}
|
|
103
|
+
async function enqueue(key) {
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
const callback = { resolve, reject };
|
|
106
|
+
addCallbackToKey(key, callback);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function dequeue(key) {
|
|
110
|
+
const stash = getCallbacksByKey(key);
|
|
111
|
+
removeKey(key);
|
|
112
|
+
return stash;
|
|
113
|
+
}
|
|
114
|
+
function coalesce(options) {
|
|
115
|
+
const { key, error, result } = options;
|
|
116
|
+
for (const callback of dequeue(key)) {
|
|
117
|
+
if (error) {
|
|
118
|
+
callback.reject(error);
|
|
119
|
+
} else {
|
|
120
|
+
callback.resolve(result);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function coalesceAsync(key, fnc) {
|
|
125
|
+
if (!hasKey(key)) {
|
|
126
|
+
addKey(key);
|
|
127
|
+
try {
|
|
128
|
+
const result = await Promise.resolve(fnc());
|
|
129
|
+
coalesce({ key, result });
|
|
130
|
+
return result;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
coalesce({ key, error });
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return enqueue(key);
|
|
137
|
+
}
|
|
138
|
+
|
|
84
139
|
// src/wrap.ts
|
|
85
140
|
function wrapSync(function_, options) {
|
|
86
141
|
const { ttl, keyPrefix, cache } = options;
|
|
@@ -97,11 +152,18 @@ function wrapSync(function_, options) {
|
|
|
97
152
|
function wrap(function_, options) {
|
|
98
153
|
const { ttl, keyPrefix, cache } = options;
|
|
99
154
|
return async function(...arguments_) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
value = await
|
|
104
|
-
|
|
155
|
+
let value;
|
|
156
|
+
try {
|
|
157
|
+
const cacheKey = createWrapKey(function_, arguments_, keyPrefix);
|
|
158
|
+
value = await cache.get(cacheKey);
|
|
159
|
+
if (value === void 0) {
|
|
160
|
+
value = await coalesceAsync(cacheKey, async () => {
|
|
161
|
+
const result = await function_(...arguments_);
|
|
162
|
+
await cache.set(cacheKey, result, ttl);
|
|
163
|
+
return result;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
105
167
|
}
|
|
106
168
|
return value;
|
|
107
169
|
};
|
|
@@ -668,8 +730,8 @@ var CacheableMemory = class {
|
|
|
668
730
|
*/
|
|
669
731
|
wrap(function_, options) {
|
|
670
732
|
const wrapOptions = {
|
|
671
|
-
ttl: options.
|
|
672
|
-
keyPrefix: options
|
|
733
|
+
ttl: options?.ttl ?? this._ttl,
|
|
734
|
+
keyPrefix: options?.keyPrefix,
|
|
673
735
|
cache: this
|
|
674
736
|
};
|
|
675
737
|
return wrapSync(function_, wrapOptions);
|
|
@@ -1480,8 +1542,8 @@ var Cacheable = class extends Hookified {
|
|
|
1480
1542
|
*/
|
|
1481
1543
|
wrap(function_, options) {
|
|
1482
1544
|
const wrapOptions = {
|
|
1483
|
-
ttl: options.
|
|
1484
|
-
keyPrefix: options
|
|
1545
|
+
ttl: options?.ttl ?? this._ttl,
|
|
1546
|
+
keyPrefix: options?.keyPrefix,
|
|
1485
1547
|
cache: this
|
|
1486
1548
|
};
|
|
1487
1549
|
return wrap(function_, wrapOptions);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cacheable",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.4",
|
|
4
4
|
"description": "Simple Caching Engine using Keyv",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -22,18 +22,18 @@
|
|
|
22
22
|
"private": false,
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@keyv/redis": "^3.0.1",
|
|
25
|
-
"@types/node": "^22.
|
|
26
|
-
"@vitest/coverage-v8": "^2.1.
|
|
27
|
-
"lru-cache": "^11.0.
|
|
25
|
+
"@types/node": "^22.9.0",
|
|
26
|
+
"@vitest/coverage-v8": "^2.1.4",
|
|
27
|
+
"lru-cache": "^11.0.2",
|
|
28
28
|
"rimraf": "^6.0.1",
|
|
29
29
|
"tsup": "^8.3.5",
|
|
30
30
|
"typescript": "^5.6.3",
|
|
31
|
-
"vitest": "^2.1.
|
|
31
|
+
"vitest": "^2.1.4",
|
|
32
32
|
"xo": "^0.59.3"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"hookified": "^1.
|
|
36
|
-
"keyv": "^5.1
|
|
35
|
+
"hookified": "^1.5.0",
|
|
36
|
+
"keyv": "^5.2.1"
|
|
37
37
|
},
|
|
38
38
|
"keywords": [
|
|
39
39
|
"cacheable",
|