cacheable 1.8.3 → 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 +67 -5
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +67 -5
- package/package.json +4 -4
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
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -635,4 +635,4 @@ declare class Cacheable extends Hookified {
|
|
|
635
635
|
private setTtl;
|
|
636
636
|
}
|
|
637
637
|
|
|
638
|
-
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
|
@@ -635,4 +635,4 @@ declare class Cacheable extends Hookified {
|
|
|
635
635
|
private setTtl;
|
|
636
636
|
}
|
|
637
637
|
|
|
638
|
-
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
|
};
|
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,9 +22,9 @@
|
|
|
22
22
|
"private": false,
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@keyv/redis": "^3.0.1",
|
|
25
|
-
"@types/node": "^22.
|
|
25
|
+
"@types/node": "^22.9.0",
|
|
26
26
|
"@vitest/coverage-v8": "^2.1.4",
|
|
27
|
-
"lru-cache": "^11.0.
|
|
27
|
+
"lru-cache": "^11.0.2",
|
|
28
28
|
"rimraf": "^6.0.1",
|
|
29
29
|
"tsup": "^8.3.5",
|
|
30
30
|
"typescript": "^5.6.3",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"hookified": "^1.5.0",
|
|
36
|
-
"keyv": "^5.1
|
|
36
|
+
"keyv": "^5.2.1"
|
|
37
37
|
},
|
|
38
38
|
"keywords": [
|
|
39
39
|
"cacheable",
|