cacheable 1.8.9 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -15
- package/dist/index.cjs +75 -29
- package/dist/index.d.cts +45 -13
- package/dist/index.d.ts +45 -13
- package/dist/index.js +75 -29
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* [Basic Usage](#basic-usage)
|
|
30
30
|
* [Hooks and Events](#hooks-and-events)
|
|
31
31
|
* [Storage Tiering and Caching](#storage-tiering-and-caching)
|
|
32
|
+
* [TTL Propagation and Storage Tiering](#ttl-propagation-and-storage-tiering)
|
|
32
33
|
* [Shorthand for Time to Live (ttl)](#shorthand-for-time-to-live-ttl)
|
|
33
34
|
* [Non-Blocking Operations](#non-blocking-operations)
|
|
34
35
|
* [CacheSync - Distributed Updates](#cachesync---distributed-updates)
|
|
@@ -38,8 +39,8 @@
|
|
|
38
39
|
* [CacheableMemory - In-Memory Cache](#cacheablememory---in-memory-cache)
|
|
39
40
|
* [CacheableMemory Options](#cacheablememory-options)
|
|
40
41
|
* [CacheableMemory - API](#cacheablememory---api)
|
|
41
|
-
* [Wrap / Memoization for Sync and Async Functions](#wrap--memoization-for-sync-and-async-functions)
|
|
42
42
|
* [Keyv Storage Adapter - KeyvCacheableMemory](#keyv-storage-adapter---keyvcacheablememory)
|
|
43
|
+
* [Wrap / Memoization for Sync and Async Functions](#wrap--memoization-for-sync-and-async-functions)
|
|
43
44
|
* [How to Contribute](#how-to-contribute)
|
|
44
45
|
* [License and Copyright](#license-and-copyright)
|
|
45
46
|
|
|
@@ -98,6 +99,7 @@ The following hooks are available for you to extend the functionality of `cachea
|
|
|
98
99
|
* `AFTER_GET`: This is called after the `get()` method is called.
|
|
99
100
|
* `BEFORE_GET_MANY`: This is called before the `getMany()` method is called.
|
|
100
101
|
* `AFTER_GET_MANY`: This is called after the `getMany()` method is called.
|
|
102
|
+
* `BEFORE_SECONDARY_SETS_PRIMARY`: This is called when the secondary store sets the value in the primary store.
|
|
101
103
|
|
|
102
104
|
An example of how to use these hooks:
|
|
103
105
|
|
|
@@ -110,6 +112,19 @@ cacheable.onHook(CacheableHooks.BEFORE_SET, (data) => {
|
|
|
110
112
|
});
|
|
111
113
|
```
|
|
112
114
|
|
|
115
|
+
Here is an example of how to use `BEFORE_SECONDARY_SETS_PRIMARY` hook:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
import { Cacheable, CacheableHooks } from 'cacheable';
|
|
119
|
+
import KeyvRedis from '@keyv/redis';
|
|
120
|
+
const secondary = new KeyvRedis('redis://user:pass@localhost:6379');
|
|
121
|
+
const cache = new Cacheable({secondary});
|
|
122
|
+
cache.onHook(CacheableHooks.BEFORE_SECONDARY_SETS_PRIMARY, (data) => {
|
|
123
|
+
console.log(`before secondary sets primary: ${data.key} ${data.value} ${data.ttl}`);
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
This is called when the secondary store sets the value in the primary store. This is useful if you want to do something before the value is set in the primary store such as manipulating the ttl or the value.
|
|
127
|
+
|
|
113
128
|
# Storage Tiering and Caching
|
|
114
129
|
|
|
115
130
|
`cacheable` is built as a layer 1 and layer 2 caching engine by default. The purpose is to have your layer 1 be fast and your layer 2 be more persistent. The primary store is the layer 1 cache and the secondary store is the layer 2 cache. By adding the secondary store you are enabling layer 2 caching. By default the operations are blocking but fault tolerant:
|
|
@@ -119,6 +134,48 @@ cacheable.onHook(CacheableHooks.BEFORE_SET, (data) => {
|
|
|
119
134
|
* `Deleting Data`: Deletes the value from the primary store and secondary store at the same time waiting for both to respond.
|
|
120
135
|
* `Clearing Data`: Clears the primary store and secondary store at the same time waiting for both to respond.
|
|
121
136
|
|
|
137
|
+
When `Getting Data` if the value does not exist in the primary store it will try to get it from the secondary store. If the secondary store returns the value it will set it in the primary store. Because we use [TTL Propagation](#ttl-propagation-and-storage-tiering) the value will be set in the primary store with the TTL of the secondary store unless the time to live (TTL) is greater than the primary store which will then use the TTL of the primary store. An example of this is:
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
import { Cacheable } from 'cacheable';
|
|
141
|
+
import KeyvRedis from '@keyv/redis';
|
|
142
|
+
const secondary = new KeyvRedis('redis://user:pass@localhost:6379', { ttl: 1000 });
|
|
143
|
+
const cache = new Cacheable({secondary, ttl: 100});
|
|
144
|
+
|
|
145
|
+
await cache.set('key', 'value'); // sets the value in the primary store with a ttl of 100 ms and secondary store with a ttl of 1000 ms
|
|
146
|
+
|
|
147
|
+
await sleep(500); // wait for .5 seconds
|
|
148
|
+
|
|
149
|
+
const value = await cache.get('key'); // gets the value from the secondary store and now sets the value in the primary store with a ttl of 500 ms which is what is left from the secondary store
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
In this example the primary store has a ttl of `100 ms` and the secondary store has a ttl of `1000 ms`. Because the ttl is greater in the secondary store it will default to setting ttl value in the primary store.
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
import { Cacheable } from 'cacheable';
|
|
156
|
+
import {Keyv} from 'keyv';
|
|
157
|
+
import KeyvRedis from '@keyv/redis';
|
|
158
|
+
const primary = new Keyv({ ttl: 200 });
|
|
159
|
+
const secondary = new KeyvRedis('redis://user:pass@localhost:6379', { ttl: 1000 });
|
|
160
|
+
const cache = new Cacheable({primary, secondary});
|
|
161
|
+
|
|
162
|
+
await cache.set('key', 'value'); // sets the value in the primary store with a ttl of 100 ms and secondary store with a ttl of 1000 ms
|
|
163
|
+
|
|
164
|
+
await sleep(200); // wait for .2 seconds
|
|
165
|
+
|
|
166
|
+
const value = await cache.get('key'); // gets the value from the secondary store and now sets the value in the primary store with a ttl of 200 ms which is what the primary store is set with
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
# TTL Propagation and Storage Tiering
|
|
170
|
+
|
|
171
|
+
Cacheable TTL propagation is a feature that allows you to set a time to live (TTL) for the cache. By default the TTL is set in the following order:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
ttl = set at the function ?? storage adapter ttl ?? cacheable ttl
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
This means that if you set a TTL at the function level it will override the storage adapter TTL and the cacheable TTL. If you do not set a TTL at the function level it will use the storage adapter TTL and then the cacheable TTL. If you do not set a TTL at all it will use the default TTL of `undefined` which is disabled.
|
|
178
|
+
|
|
122
179
|
# Shorthand for Time to Live (ttl)
|
|
123
180
|
|
|
124
181
|
By default `Cacheable` and `CacheableMemory` the `ttl` is in milliseconds but you can use shorthand for the time to live. Here are the following shorthand values:
|
|
@@ -157,6 +214,40 @@ cache.ttl = -1; // sets the default ttl to 0 which is disabled
|
|
|
157
214
|
console.log(cache.ttl); // undefined
|
|
158
215
|
```
|
|
159
216
|
|
|
217
|
+
## Retrieving raw cache entries
|
|
218
|
+
|
|
219
|
+
The `get` and `getMany` methods support a `raw` option, which returns the full stored metadata (`StoredDataRaw<T>`) instead of just the value:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { Cacheable } from 'cacheable';
|
|
223
|
+
|
|
224
|
+
const cache = new Cacheable();
|
|
225
|
+
|
|
226
|
+
// store a value
|
|
227
|
+
await cache.set('user:1', { name: 'Alice' });
|
|
228
|
+
|
|
229
|
+
// default: only the value
|
|
230
|
+
const user = await cache.get<{ name: string }>('user:1');
|
|
231
|
+
console.log(user); // { name: 'Alice' }
|
|
232
|
+
|
|
233
|
+
// with raw: full record including expiration
|
|
234
|
+
const raw = await cache.get<{ name: string }>('user:1', { raw: true });
|
|
235
|
+
console.log(raw.value); // { name: 'Alice' }
|
|
236
|
+
console.log(raw.expires); // e.g. 1677628495000 or null
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// getMany with raw option
|
|
241
|
+
await cache.set('a', 1);
|
|
242
|
+
await cache.set('b', 2);
|
|
243
|
+
|
|
244
|
+
const raws = await cache.getMany<number>(['a', 'b'], { raw: true });
|
|
245
|
+
raws.forEach((entry, idx) => {
|
|
246
|
+
console.log(`key=${['a','b'][idx]}, value=${entry?.value}, expires=${entry?.expires}`);
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
|
|
160
251
|
# Non-Blocking Operations
|
|
161
252
|
|
|
162
253
|
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.
|
|
@@ -215,7 +306,9 @@ _This does not enable statistics for your layer 2 cache as that is a distributed
|
|
|
215
306
|
* `set(key, value, ttl?)`: Sets a value in the cache.
|
|
216
307
|
* `setMany([{key, value, ttl?}])`: Sets multiple values in the cache.
|
|
217
308
|
* `get(key)`: Gets a value from the cache.
|
|
309
|
+
* `get(key, { raw: true })`: Gets a raw value from the cache.
|
|
218
310
|
* `getMany([keys])`: Gets multiple values from the cache.
|
|
311
|
+
* `getMany([keys], { raw: true })`: Gets multiple raw values from the cache.
|
|
219
312
|
* `has(key)`: Checks if a value exists in the cache.
|
|
220
313
|
* `hasMany([keys])`: Checks if multiple values exist in the cache.
|
|
221
314
|
* `take(key)`: Takes a value from the cache and deletes it.
|
|
@@ -289,6 +382,20 @@ By default we use lazy expiration deletion which means on `get` and `getMany` ty
|
|
|
289
382
|
* `stopIntervalCheck()`: Stops the interval check for expired keys.
|
|
290
383
|
* `hash(object: any, algorithm = 'sha256'): string`: Hashes an object with the algorithm. Default is `sha256`.
|
|
291
384
|
|
|
385
|
+
# Keyv Storage Adapter - KeyvCacheableMemory
|
|
386
|
+
|
|
387
|
+
`cacheable` comes with a built-in storage adapter for Keyv called `KeyvCacheableMemory`. This takes `CacheableMemory` and creates a storage adapter for Keyv. This is useful if you want to use `CacheableMemory` as a storage adapter for Keyv. Here is an example of how to use `KeyvCacheableMemory`:
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
import { Keyv } from 'keyv';
|
|
391
|
+
import { KeyvCacheableMemory } from 'cacheable';
|
|
392
|
+
|
|
393
|
+
const keyv = new Keyv({ store: new KeyvCacheableMemory() });
|
|
394
|
+
await keyv.set('foo', 'bar');
|
|
395
|
+
const value = await keyv.get('foo');
|
|
396
|
+
console.log(value); // bar
|
|
397
|
+
```
|
|
398
|
+
|
|
292
399
|
# Wrap / Memoization for Sync and Async Functions
|
|
293
400
|
|
|
294
401
|
`Cacheable` and `CacheableMemory` has a feature called `wrap` that allows you to wrap a function in a cache. This is useful for memoization and caching the results of a function. You can wrap a `sync` or `async` function in a cache. Here is an example of how to use the `wrap` function:
|
|
@@ -363,20 +470,6 @@ console.log(wrappedFunction()); // error
|
|
|
363
470
|
console.log(wrappedFunction()); // error from cache
|
|
364
471
|
```
|
|
365
472
|
|
|
366
|
-
# Keyv Storage Adapter - KeyvCacheableMemory
|
|
367
|
-
|
|
368
|
-
`cacheable` comes with a built-in storage adapter for Keyv called `KeyvCacheableMemory`. This takes `CacheableMemory` and creates a storage adapter for Keyv. This is useful if you want to use `CacheableMemory` as a storage adapter for Keyv. Here is an example of how to use `KeyvCacheableMemory`:
|
|
369
|
-
|
|
370
|
-
```javascript
|
|
371
|
-
import { Keyv } from 'keyv';
|
|
372
|
-
import { KeyvCacheableMemory } from 'cacheable';
|
|
373
|
-
|
|
374
|
-
const keyv = new Keyv({ store: new KeyvCacheableMemory() });
|
|
375
|
-
await keyv.set('foo', 'bar');
|
|
376
|
-
const value = await keyv.get('foo');
|
|
377
|
-
console.log(value); // bar
|
|
378
|
-
```
|
|
379
|
-
|
|
380
473
|
# How to Contribute
|
|
381
474
|
|
|
382
475
|
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
|
@@ -613,6 +613,7 @@ var CacheableMemory = class extends import_hookified.Hookified {
|
|
|
613
613
|
delete(key) {
|
|
614
614
|
const store = this.getStore(key);
|
|
615
615
|
store.delete(key);
|
|
616
|
+
this._hashCache.delete(key);
|
|
616
617
|
}
|
|
617
618
|
/**
|
|
618
619
|
* Delete the keys
|
|
@@ -697,7 +698,7 @@ var CacheableMemory = class extends import_hookified.Hookified {
|
|
|
697
698
|
*/
|
|
698
699
|
hashKey(key) {
|
|
699
700
|
const cacheHashNumber = this._hashCache.get(key);
|
|
700
|
-
if (cacheHashNumber) {
|
|
701
|
+
if (typeof cacheHashNumber === "number") {
|
|
701
702
|
return cacheHashNumber;
|
|
702
703
|
}
|
|
703
704
|
let hash2 = 0;
|
|
@@ -1147,6 +1148,35 @@ var CacheableStats = class {
|
|
|
1147
1148
|
}
|
|
1148
1149
|
};
|
|
1149
1150
|
|
|
1151
|
+
// src/ttl.ts
|
|
1152
|
+
function getTtlFromExpires(expires) {
|
|
1153
|
+
if (expires === void 0 || expires === null) {
|
|
1154
|
+
return void 0;
|
|
1155
|
+
}
|
|
1156
|
+
const now = Date.now();
|
|
1157
|
+
if (expires < now) {
|
|
1158
|
+
return void 0;
|
|
1159
|
+
}
|
|
1160
|
+
return expires - now;
|
|
1161
|
+
}
|
|
1162
|
+
function getCascadingTtl(cacheableTtl, primaryTtl, secondaryTtl) {
|
|
1163
|
+
return secondaryTtl ?? primaryTtl ?? shorthandToMilliseconds(cacheableTtl);
|
|
1164
|
+
}
|
|
1165
|
+
function calculateTtlFromExpiration(ttl, expires) {
|
|
1166
|
+
const ttlFromExpires = getTtlFromExpires(expires);
|
|
1167
|
+
const expiresFromTtl = ttl ? Date.now() + ttl : void 0;
|
|
1168
|
+
if (ttlFromExpires === void 0) {
|
|
1169
|
+
return ttl;
|
|
1170
|
+
}
|
|
1171
|
+
if (expiresFromTtl === void 0) {
|
|
1172
|
+
return ttlFromExpires;
|
|
1173
|
+
}
|
|
1174
|
+
if (expires > expiresFromTtl) {
|
|
1175
|
+
return ttl;
|
|
1176
|
+
}
|
|
1177
|
+
return ttlFromExpires;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1150
1180
|
// src/index.ts
|
|
1151
1181
|
var import_keyv3 = require("keyv");
|
|
1152
1182
|
var CacheableHooks = /* @__PURE__ */ ((CacheableHooks2) => {
|
|
@@ -1158,6 +1188,7 @@ var CacheableHooks = /* @__PURE__ */ ((CacheableHooks2) => {
|
|
|
1158
1188
|
CacheableHooks2["AFTER_GET"] = "AFTER_GET";
|
|
1159
1189
|
CacheableHooks2["BEFORE_GET_MANY"] = "BEFORE_GET_MANY";
|
|
1160
1190
|
CacheableHooks2["AFTER_GET_MANY"] = "AFTER_GET_MANY";
|
|
1191
|
+
CacheableHooks2["BEFORE_SECONDARY_SETS_PRIMARY"] = "BEFORE_SECONDARY_SETS_PRIMARY";
|
|
1161
1192
|
return CacheableHooks2;
|
|
1162
1193
|
})(CacheableHooks || {});
|
|
1163
1194
|
var CacheableEvents = /* @__PURE__ */ ((CacheableEvents2) => {
|
|
@@ -1366,25 +1397,26 @@ var Cacheable = class extends import_hookified2.Hookified {
|
|
|
1366
1397
|
}
|
|
1367
1398
|
return this._namespace;
|
|
1368
1399
|
}
|
|
1369
|
-
|
|
1370
|
-
* Gets the value of the key. If the key does not exist in the primary store then it will check the secondary store.
|
|
1371
|
-
* @param {string} key The key to get the value of
|
|
1372
|
-
* @returns {Promise<T | undefined>} The value of the key or undefined if the key does not exist
|
|
1373
|
-
*/
|
|
1374
|
-
async get(key) {
|
|
1400
|
+
async get(key, options = {}) {
|
|
1375
1401
|
let result;
|
|
1402
|
+
const { raw = false } = options;
|
|
1376
1403
|
try {
|
|
1377
1404
|
await this.hook("BEFORE_GET" /* BEFORE_GET */, key);
|
|
1378
|
-
result = await this._primary.get(key);
|
|
1405
|
+
result = await this._primary.get(key, { raw: true });
|
|
1406
|
+
let ttl;
|
|
1379
1407
|
if (!result && this._secondary) {
|
|
1380
|
-
const
|
|
1381
|
-
if (
|
|
1382
|
-
result =
|
|
1383
|
-
const
|
|
1384
|
-
|
|
1408
|
+
const secondaryResult = await this.getSecondaryRawResults(key);
|
|
1409
|
+
if (secondaryResult?.value) {
|
|
1410
|
+
result = secondaryResult;
|
|
1411
|
+
const cascadeTtl = getCascadingTtl(this._ttl, this._primary.ttl);
|
|
1412
|
+
const expires = secondaryResult.expires ?? void 0;
|
|
1413
|
+
ttl = calculateTtlFromExpiration(cascadeTtl, expires);
|
|
1414
|
+
const setItem = { key, value: result.value, ttl };
|
|
1415
|
+
await this.hook("BEFORE_SECONDARY_SETS_PRIMARY" /* BEFORE_SECONDARY_SETS_PRIMARY */, setItem);
|
|
1416
|
+
await this._primary.set(setItem.key, setItem.value, setItem.ttl);
|
|
1385
1417
|
}
|
|
1386
1418
|
}
|
|
1387
|
-
await this.hook("AFTER_GET" /* AFTER_GET */, { key, result });
|
|
1419
|
+
await this.hook("AFTER_GET" /* AFTER_GET */, { key, result, ttl });
|
|
1388
1420
|
} catch (error) {
|
|
1389
1421
|
this.emit("error" /* ERROR */, error);
|
|
1390
1422
|
}
|
|
@@ -1396,18 +1428,14 @@ var Cacheable = class extends import_hookified2.Hookified {
|
|
|
1396
1428
|
}
|
|
1397
1429
|
this.stats.incrementGets();
|
|
1398
1430
|
}
|
|
1399
|
-
return result;
|
|
1431
|
+
return raw ? result : result?.value;
|
|
1400
1432
|
}
|
|
1401
|
-
|
|
1402
|
-
* Gets the values of the keys. If the key does not exist in the primary store then it will check the secondary store.
|
|
1403
|
-
* @param {string[]} keys The keys to get the values of
|
|
1404
|
-
* @returns {Promise<Array<T | undefined>>} The values of the keys or undefined if the key does not exist
|
|
1405
|
-
*/
|
|
1406
|
-
async getMany(keys) {
|
|
1433
|
+
async getMany(keys, options = {}) {
|
|
1407
1434
|
let result = [];
|
|
1435
|
+
const { raw = false } = options;
|
|
1408
1436
|
try {
|
|
1409
1437
|
await this.hook("BEFORE_GET_MANY" /* BEFORE_GET_MANY */, keys);
|
|
1410
|
-
result = await this._primary.get(keys);
|
|
1438
|
+
result = await this._primary.get(keys, { raw: true });
|
|
1411
1439
|
if (this._secondary) {
|
|
1412
1440
|
const missingKeys = [];
|
|
1413
1441
|
for (const [i, key] of keys.entries()) {
|
|
@@ -1415,12 +1443,16 @@ var Cacheable = class extends import_hookified2.Hookified {
|
|
|
1415
1443
|
missingKeys.push(key);
|
|
1416
1444
|
}
|
|
1417
1445
|
}
|
|
1418
|
-
const
|
|
1419
|
-
for (const [i, key] of keys.entries()) {
|
|
1420
|
-
if (!result[i] &&
|
|
1421
|
-
result[i] =
|
|
1422
|
-
const
|
|
1423
|
-
|
|
1446
|
+
const secondaryResults = await this.getManySecondaryRawResults(missingKeys);
|
|
1447
|
+
for await (const [i, key] of keys.entries()) {
|
|
1448
|
+
if (!result[i] && secondaryResults[i]) {
|
|
1449
|
+
result[i] = secondaryResults[i];
|
|
1450
|
+
const cascadeTtl = getCascadingTtl(this._ttl, this._primary.ttl);
|
|
1451
|
+
const expires = secondaryResults[i].expires;
|
|
1452
|
+
const ttl = calculateTtlFromExpiration(cascadeTtl, expires);
|
|
1453
|
+
const setItem = { key, value: result[i].value, ttl };
|
|
1454
|
+
await this.hook("BEFORE_SECONDARY_SETS_PRIMARY" /* BEFORE_SECONDARY_SETS_PRIMARY */, setItem);
|
|
1455
|
+
await this._primary.set(setItem.key, setItem.value, setItem.ttl);
|
|
1424
1456
|
}
|
|
1425
1457
|
}
|
|
1426
1458
|
}
|
|
@@ -1438,7 +1470,7 @@ var Cacheable = class extends import_hookified2.Hookified {
|
|
|
1438
1470
|
}
|
|
1439
1471
|
this.stats.incrementGets();
|
|
1440
1472
|
}
|
|
1441
|
-
return result;
|
|
1473
|
+
return raw ? result : result.map((item) => item?.value);
|
|
1442
1474
|
}
|
|
1443
1475
|
/**
|
|
1444
1476
|
* Sets the value of the key. If the secondary store is set then it will also set the value in the secondary store.
|
|
@@ -1677,6 +1709,20 @@ var Cacheable = class extends import_hookified2.Hookified {
|
|
|
1677
1709
|
hash(object, algorithm = "sha256") {
|
|
1678
1710
|
return hash(object, algorithm);
|
|
1679
1711
|
}
|
|
1712
|
+
async getSecondaryRawResults(key) {
|
|
1713
|
+
let result;
|
|
1714
|
+
if (this._secondary) {
|
|
1715
|
+
result = await this._secondary.get(key, { raw: true });
|
|
1716
|
+
}
|
|
1717
|
+
return result;
|
|
1718
|
+
}
|
|
1719
|
+
async getManySecondaryRawResults(keys) {
|
|
1720
|
+
let result = new Array();
|
|
1721
|
+
if (this._secondary) {
|
|
1722
|
+
result = await this._secondary.get(keys, { raw: true });
|
|
1723
|
+
}
|
|
1724
|
+
return result;
|
|
1725
|
+
}
|
|
1680
1726
|
async deleteManyKeyv(keyv, keys) {
|
|
1681
1727
|
const promises = [];
|
|
1682
1728
|
for (const key of keys) {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KeyvStoreAdapter, StoredData, Keyv } from 'keyv';
|
|
1
|
+
import { KeyvStoreAdapter, StoredData, Keyv, StoredDataRaw } from 'keyv';
|
|
2
2
|
export { Keyv, KeyvHooks, KeyvOptions, KeyvStoreAdapter } from 'keyv';
|
|
3
3
|
import { Hookified } from 'hookified';
|
|
4
4
|
|
|
@@ -423,7 +423,8 @@ declare enum CacheableHooks {
|
|
|
423
423
|
BEFORE_GET = "BEFORE_GET",
|
|
424
424
|
AFTER_GET = "AFTER_GET",
|
|
425
425
|
BEFORE_GET_MANY = "BEFORE_GET_MANY",
|
|
426
|
-
AFTER_GET_MANY = "AFTER_GET_MANY"
|
|
426
|
+
AFTER_GET_MANY = "AFTER_GET_MANY",
|
|
427
|
+
BEFORE_SECONDARY_SETS_PRIMARY = "BEFORE_SECONDARY_SETS_PRIMARY"
|
|
427
428
|
}
|
|
428
429
|
declare enum CacheableEvents {
|
|
429
430
|
ERROR = "error"
|
|
@@ -593,17 +594,46 @@ declare class Cacheable extends Hookified {
|
|
|
593
594
|
setSecondary(secondary: Keyv | KeyvStoreAdapter): void;
|
|
594
595
|
getNameSpace(): string | undefined;
|
|
595
596
|
/**
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
597
|
+
* Retrieves an entry from the cache, with an optional “raw” mode.
|
|
598
|
+
*
|
|
599
|
+
* Checks the primary store first; if not found and a secondary store is configured,
|
|
600
|
+
* it will fetch from the secondary, repopulate the primary, and return the result.
|
|
601
|
+
*
|
|
602
|
+
* @typeParam T - The expected type of the stored value.
|
|
603
|
+
* @param {string} key - The cache key to retrieve.
|
|
604
|
+
* @param {{ raw?: boolean }} [opts] - Options for retrieval.
|
|
605
|
+
* @param {boolean} [opts.raw=false] - If `true`, returns the full raw data object
|
|
606
|
+
* (`StoredDataRaw<T>`); otherwise returns just the value.
|
|
607
|
+
* @returns {Promise<T | StoredDataRaw<T> | undefined>}
|
|
608
|
+
* A promise that resolves to the cached value (or raw data) if found, or `undefined`.
|
|
609
|
+
*/
|
|
610
|
+
get<T>(key: string, options?: {
|
|
611
|
+
raw?: false;
|
|
612
|
+
}): Promise<T | undefined>;
|
|
613
|
+
get<T>(key: string, options: {
|
|
614
|
+
raw: true;
|
|
615
|
+
}): Promise<StoredDataRaw<T>>;
|
|
616
|
+
/**
|
|
617
|
+
* Retrieves multiple entries from the cache.
|
|
618
|
+
* Checks the primary store for each key; if a key is missing and a secondary store is configured,
|
|
619
|
+
* it will fetch from the secondary store, repopulate the primary store, and return the results.
|
|
620
|
+
*
|
|
621
|
+
* @typeParam T - The expected type of the stored values.
|
|
622
|
+
* @param {string[]} keys - The cache keys to retrieve.
|
|
623
|
+
* @param {{ raw?: boolean }} [options] - Options for retrieval.
|
|
624
|
+
* @param {boolean} [options.raw=false] - When `true`, returns an array of raw data objects (`StoredDataRaw<T>`);
|
|
625
|
+
* when `false`, returns an array of unwrapped values (`T`) or `undefined` for misses.
|
|
626
|
+
* @returns {Promise<Array<T | undefined>> | Promise<Array<StoredDataRaw<T>>>}
|
|
627
|
+
* A promise that resolves to:
|
|
628
|
+
* - `Array<T | undefined>` if `raw` is `false` (default).
|
|
629
|
+
* - `Array<StoredDataRaw<T>>` if `raw` is `true`.
|
|
630
|
+
*/
|
|
631
|
+
getMany<T>(keys: string[], options?: {
|
|
632
|
+
raw?: false;
|
|
633
|
+
}): Promise<Array<T | undefined>>;
|
|
634
|
+
getMany<T>(keys: string[], options: {
|
|
635
|
+
raw: true;
|
|
636
|
+
}): Promise<Array<StoredDataRaw<T>>>;
|
|
607
637
|
/**
|
|
608
638
|
* Sets the value of the key. If the secondary store is set then it will also set the value in the secondary store.
|
|
609
639
|
* @param {string} key the key to set the value of
|
|
@@ -681,6 +711,8 @@ declare class Cacheable extends Hookified {
|
|
|
681
711
|
* @returns {string} the hash of the object
|
|
682
712
|
*/
|
|
683
713
|
hash(object: any, algorithm?: string): string;
|
|
714
|
+
private getSecondaryRawResults;
|
|
715
|
+
private getManySecondaryRawResults;
|
|
684
716
|
private deleteManyKeyv;
|
|
685
717
|
private setManyKeyv;
|
|
686
718
|
private hasManyKeyv;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KeyvStoreAdapter, StoredData, Keyv } from 'keyv';
|
|
1
|
+
import { KeyvStoreAdapter, StoredData, Keyv, StoredDataRaw } from 'keyv';
|
|
2
2
|
export { Keyv, KeyvHooks, KeyvOptions, KeyvStoreAdapter } from 'keyv';
|
|
3
3
|
import { Hookified } from 'hookified';
|
|
4
4
|
|
|
@@ -423,7 +423,8 @@ declare enum CacheableHooks {
|
|
|
423
423
|
BEFORE_GET = "BEFORE_GET",
|
|
424
424
|
AFTER_GET = "AFTER_GET",
|
|
425
425
|
BEFORE_GET_MANY = "BEFORE_GET_MANY",
|
|
426
|
-
AFTER_GET_MANY = "AFTER_GET_MANY"
|
|
426
|
+
AFTER_GET_MANY = "AFTER_GET_MANY",
|
|
427
|
+
BEFORE_SECONDARY_SETS_PRIMARY = "BEFORE_SECONDARY_SETS_PRIMARY"
|
|
427
428
|
}
|
|
428
429
|
declare enum CacheableEvents {
|
|
429
430
|
ERROR = "error"
|
|
@@ -593,17 +594,46 @@ declare class Cacheable extends Hookified {
|
|
|
593
594
|
setSecondary(secondary: Keyv | KeyvStoreAdapter): void;
|
|
594
595
|
getNameSpace(): string | undefined;
|
|
595
596
|
/**
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
597
|
+
* Retrieves an entry from the cache, with an optional “raw” mode.
|
|
598
|
+
*
|
|
599
|
+
* Checks the primary store first; if not found and a secondary store is configured,
|
|
600
|
+
* it will fetch from the secondary, repopulate the primary, and return the result.
|
|
601
|
+
*
|
|
602
|
+
* @typeParam T - The expected type of the stored value.
|
|
603
|
+
* @param {string} key - The cache key to retrieve.
|
|
604
|
+
* @param {{ raw?: boolean }} [opts] - Options for retrieval.
|
|
605
|
+
* @param {boolean} [opts.raw=false] - If `true`, returns the full raw data object
|
|
606
|
+
* (`StoredDataRaw<T>`); otherwise returns just the value.
|
|
607
|
+
* @returns {Promise<T | StoredDataRaw<T> | undefined>}
|
|
608
|
+
* A promise that resolves to the cached value (or raw data) if found, or `undefined`.
|
|
609
|
+
*/
|
|
610
|
+
get<T>(key: string, options?: {
|
|
611
|
+
raw?: false;
|
|
612
|
+
}): Promise<T | undefined>;
|
|
613
|
+
get<T>(key: string, options: {
|
|
614
|
+
raw: true;
|
|
615
|
+
}): Promise<StoredDataRaw<T>>;
|
|
616
|
+
/**
|
|
617
|
+
* Retrieves multiple entries from the cache.
|
|
618
|
+
* Checks the primary store for each key; if a key is missing and a secondary store is configured,
|
|
619
|
+
* it will fetch from the secondary store, repopulate the primary store, and return the results.
|
|
620
|
+
*
|
|
621
|
+
* @typeParam T - The expected type of the stored values.
|
|
622
|
+
* @param {string[]} keys - The cache keys to retrieve.
|
|
623
|
+
* @param {{ raw?: boolean }} [options] - Options for retrieval.
|
|
624
|
+
* @param {boolean} [options.raw=false] - When `true`, returns an array of raw data objects (`StoredDataRaw<T>`);
|
|
625
|
+
* when `false`, returns an array of unwrapped values (`T`) or `undefined` for misses.
|
|
626
|
+
* @returns {Promise<Array<T | undefined>> | Promise<Array<StoredDataRaw<T>>>}
|
|
627
|
+
* A promise that resolves to:
|
|
628
|
+
* - `Array<T | undefined>` if `raw` is `false` (default).
|
|
629
|
+
* - `Array<StoredDataRaw<T>>` if `raw` is `true`.
|
|
630
|
+
*/
|
|
631
|
+
getMany<T>(keys: string[], options?: {
|
|
632
|
+
raw?: false;
|
|
633
|
+
}): Promise<Array<T | undefined>>;
|
|
634
|
+
getMany<T>(keys: string[], options: {
|
|
635
|
+
raw: true;
|
|
636
|
+
}): Promise<Array<StoredDataRaw<T>>>;
|
|
607
637
|
/**
|
|
608
638
|
* Sets the value of the key. If the secondary store is set then it will also set the value in the secondary store.
|
|
609
639
|
* @param {string} key the key to set the value of
|
|
@@ -681,6 +711,8 @@ declare class Cacheable extends Hookified {
|
|
|
681
711
|
* @returns {string} the hash of the object
|
|
682
712
|
*/
|
|
683
713
|
hash(object: any, algorithm?: string): string;
|
|
714
|
+
private getSecondaryRawResults;
|
|
715
|
+
private getManySecondaryRawResults;
|
|
684
716
|
private deleteManyKeyv;
|
|
685
717
|
private setManyKeyv;
|
|
686
718
|
private hasManyKeyv;
|
package/dist/index.js
CHANGED
|
@@ -569,6 +569,7 @@ var CacheableMemory = class extends Hookified {
|
|
|
569
569
|
delete(key) {
|
|
570
570
|
const store = this.getStore(key);
|
|
571
571
|
store.delete(key);
|
|
572
|
+
this._hashCache.delete(key);
|
|
572
573
|
}
|
|
573
574
|
/**
|
|
574
575
|
* Delete the keys
|
|
@@ -653,7 +654,7 @@ var CacheableMemory = class extends Hookified {
|
|
|
653
654
|
*/
|
|
654
655
|
hashKey(key) {
|
|
655
656
|
const cacheHashNumber = this._hashCache.get(key);
|
|
656
|
-
if (cacheHashNumber) {
|
|
657
|
+
if (typeof cacheHashNumber === "number") {
|
|
657
658
|
return cacheHashNumber;
|
|
658
659
|
}
|
|
659
660
|
let hash2 = 0;
|
|
@@ -1103,6 +1104,35 @@ var CacheableStats = class {
|
|
|
1103
1104
|
}
|
|
1104
1105
|
};
|
|
1105
1106
|
|
|
1107
|
+
// src/ttl.ts
|
|
1108
|
+
function getTtlFromExpires(expires) {
|
|
1109
|
+
if (expires === void 0 || expires === null) {
|
|
1110
|
+
return void 0;
|
|
1111
|
+
}
|
|
1112
|
+
const now = Date.now();
|
|
1113
|
+
if (expires < now) {
|
|
1114
|
+
return void 0;
|
|
1115
|
+
}
|
|
1116
|
+
return expires - now;
|
|
1117
|
+
}
|
|
1118
|
+
function getCascadingTtl(cacheableTtl, primaryTtl, secondaryTtl) {
|
|
1119
|
+
return secondaryTtl ?? primaryTtl ?? shorthandToMilliseconds(cacheableTtl);
|
|
1120
|
+
}
|
|
1121
|
+
function calculateTtlFromExpiration(ttl, expires) {
|
|
1122
|
+
const ttlFromExpires = getTtlFromExpires(expires);
|
|
1123
|
+
const expiresFromTtl = ttl ? Date.now() + ttl : void 0;
|
|
1124
|
+
if (ttlFromExpires === void 0) {
|
|
1125
|
+
return ttl;
|
|
1126
|
+
}
|
|
1127
|
+
if (expiresFromTtl === void 0) {
|
|
1128
|
+
return ttlFromExpires;
|
|
1129
|
+
}
|
|
1130
|
+
if (expires > expiresFromTtl) {
|
|
1131
|
+
return ttl;
|
|
1132
|
+
}
|
|
1133
|
+
return ttlFromExpires;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1106
1136
|
// src/index.ts
|
|
1107
1137
|
import {
|
|
1108
1138
|
KeyvHooks,
|
|
@@ -1117,6 +1147,7 @@ var CacheableHooks = /* @__PURE__ */ ((CacheableHooks2) => {
|
|
|
1117
1147
|
CacheableHooks2["AFTER_GET"] = "AFTER_GET";
|
|
1118
1148
|
CacheableHooks2["BEFORE_GET_MANY"] = "BEFORE_GET_MANY";
|
|
1119
1149
|
CacheableHooks2["AFTER_GET_MANY"] = "AFTER_GET_MANY";
|
|
1150
|
+
CacheableHooks2["BEFORE_SECONDARY_SETS_PRIMARY"] = "BEFORE_SECONDARY_SETS_PRIMARY";
|
|
1120
1151
|
return CacheableHooks2;
|
|
1121
1152
|
})(CacheableHooks || {});
|
|
1122
1153
|
var CacheableEvents = /* @__PURE__ */ ((CacheableEvents2) => {
|
|
@@ -1325,25 +1356,26 @@ var Cacheable = class extends Hookified2 {
|
|
|
1325
1356
|
}
|
|
1326
1357
|
return this._namespace;
|
|
1327
1358
|
}
|
|
1328
|
-
|
|
1329
|
-
* Gets the value of the key. If the key does not exist in the primary store then it will check the secondary store.
|
|
1330
|
-
* @param {string} key The key to get the value of
|
|
1331
|
-
* @returns {Promise<T | undefined>} The value of the key or undefined if the key does not exist
|
|
1332
|
-
*/
|
|
1333
|
-
async get(key) {
|
|
1359
|
+
async get(key, options = {}) {
|
|
1334
1360
|
let result;
|
|
1361
|
+
const { raw = false } = options;
|
|
1335
1362
|
try {
|
|
1336
1363
|
await this.hook("BEFORE_GET" /* BEFORE_GET */, key);
|
|
1337
|
-
result = await this._primary.get(key);
|
|
1364
|
+
result = await this._primary.get(key, { raw: true });
|
|
1365
|
+
let ttl;
|
|
1338
1366
|
if (!result && this._secondary) {
|
|
1339
|
-
const
|
|
1340
|
-
if (
|
|
1341
|
-
result =
|
|
1342
|
-
const
|
|
1343
|
-
|
|
1367
|
+
const secondaryResult = await this.getSecondaryRawResults(key);
|
|
1368
|
+
if (secondaryResult?.value) {
|
|
1369
|
+
result = secondaryResult;
|
|
1370
|
+
const cascadeTtl = getCascadingTtl(this._ttl, this._primary.ttl);
|
|
1371
|
+
const expires = secondaryResult.expires ?? void 0;
|
|
1372
|
+
ttl = calculateTtlFromExpiration(cascadeTtl, expires);
|
|
1373
|
+
const setItem = { key, value: result.value, ttl };
|
|
1374
|
+
await this.hook("BEFORE_SECONDARY_SETS_PRIMARY" /* BEFORE_SECONDARY_SETS_PRIMARY */, setItem);
|
|
1375
|
+
await this._primary.set(setItem.key, setItem.value, setItem.ttl);
|
|
1344
1376
|
}
|
|
1345
1377
|
}
|
|
1346
|
-
await this.hook("AFTER_GET" /* AFTER_GET */, { key, result });
|
|
1378
|
+
await this.hook("AFTER_GET" /* AFTER_GET */, { key, result, ttl });
|
|
1347
1379
|
} catch (error) {
|
|
1348
1380
|
this.emit("error" /* ERROR */, error);
|
|
1349
1381
|
}
|
|
@@ -1355,18 +1387,14 @@ var Cacheable = class extends Hookified2 {
|
|
|
1355
1387
|
}
|
|
1356
1388
|
this.stats.incrementGets();
|
|
1357
1389
|
}
|
|
1358
|
-
return result;
|
|
1390
|
+
return raw ? result : result?.value;
|
|
1359
1391
|
}
|
|
1360
|
-
|
|
1361
|
-
* Gets the values of the keys. If the key does not exist in the primary store then it will check the secondary store.
|
|
1362
|
-
* @param {string[]} keys The keys to get the values of
|
|
1363
|
-
* @returns {Promise<Array<T | undefined>>} The values of the keys or undefined if the key does not exist
|
|
1364
|
-
*/
|
|
1365
|
-
async getMany(keys) {
|
|
1392
|
+
async getMany(keys, options = {}) {
|
|
1366
1393
|
let result = [];
|
|
1394
|
+
const { raw = false } = options;
|
|
1367
1395
|
try {
|
|
1368
1396
|
await this.hook("BEFORE_GET_MANY" /* BEFORE_GET_MANY */, keys);
|
|
1369
|
-
result = await this._primary.get(keys);
|
|
1397
|
+
result = await this._primary.get(keys, { raw: true });
|
|
1370
1398
|
if (this._secondary) {
|
|
1371
1399
|
const missingKeys = [];
|
|
1372
1400
|
for (const [i, key] of keys.entries()) {
|
|
@@ -1374,12 +1402,16 @@ var Cacheable = class extends Hookified2 {
|
|
|
1374
1402
|
missingKeys.push(key);
|
|
1375
1403
|
}
|
|
1376
1404
|
}
|
|
1377
|
-
const
|
|
1378
|
-
for (const [i, key] of keys.entries()) {
|
|
1379
|
-
if (!result[i] &&
|
|
1380
|
-
result[i] =
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1405
|
+
const secondaryResults = await this.getManySecondaryRawResults(missingKeys);
|
|
1406
|
+
for await (const [i, key] of keys.entries()) {
|
|
1407
|
+
if (!result[i] && secondaryResults[i]) {
|
|
1408
|
+
result[i] = secondaryResults[i];
|
|
1409
|
+
const cascadeTtl = getCascadingTtl(this._ttl, this._primary.ttl);
|
|
1410
|
+
const expires = secondaryResults[i].expires;
|
|
1411
|
+
const ttl = calculateTtlFromExpiration(cascadeTtl, expires);
|
|
1412
|
+
const setItem = { key, value: result[i].value, ttl };
|
|
1413
|
+
await this.hook("BEFORE_SECONDARY_SETS_PRIMARY" /* BEFORE_SECONDARY_SETS_PRIMARY */, setItem);
|
|
1414
|
+
await this._primary.set(setItem.key, setItem.value, setItem.ttl);
|
|
1383
1415
|
}
|
|
1384
1416
|
}
|
|
1385
1417
|
}
|
|
@@ -1397,7 +1429,7 @@ var Cacheable = class extends Hookified2 {
|
|
|
1397
1429
|
}
|
|
1398
1430
|
this.stats.incrementGets();
|
|
1399
1431
|
}
|
|
1400
|
-
return result;
|
|
1432
|
+
return raw ? result : result.map((item) => item?.value);
|
|
1401
1433
|
}
|
|
1402
1434
|
/**
|
|
1403
1435
|
* Sets the value of the key. If the secondary store is set then it will also set the value in the secondary store.
|
|
@@ -1636,6 +1668,20 @@ var Cacheable = class extends Hookified2 {
|
|
|
1636
1668
|
hash(object, algorithm = "sha256") {
|
|
1637
1669
|
return hash(object, algorithm);
|
|
1638
1670
|
}
|
|
1671
|
+
async getSecondaryRawResults(key) {
|
|
1672
|
+
let result;
|
|
1673
|
+
if (this._secondary) {
|
|
1674
|
+
result = await this._secondary.get(key, { raw: true });
|
|
1675
|
+
}
|
|
1676
|
+
return result;
|
|
1677
|
+
}
|
|
1678
|
+
async getManySecondaryRawResults(keys) {
|
|
1679
|
+
let result = new Array();
|
|
1680
|
+
if (this._secondary) {
|
|
1681
|
+
result = await this._secondary.get(keys, { raw: true });
|
|
1682
|
+
}
|
|
1683
|
+
return result;
|
|
1684
|
+
}
|
|
1639
1685
|
async deleteManyKeyv(keyv, keys) {
|
|
1640
1686
|
const promises = [];
|
|
1641
1687
|
for (const key of keys) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cacheable",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
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,20 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"private": false,
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@faker-js/faker": "^9.
|
|
25
|
-
"@keyv/redis": "^4.
|
|
26
|
-
"@types/node": "^22.
|
|
27
|
-
"@vitest/coverage-v8": "^3.
|
|
28
|
-
"lru-cache": "^11.0
|
|
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",
|
|
28
|
+
"lru-cache": "^11.1.0",
|
|
29
29
|
"rimraf": "^6.0.1",
|
|
30
30
|
"tsup": "^8.4.0",
|
|
31
|
-
"typescript": "^5.8.
|
|
32
|
-
"vitest": "^3.
|
|
31
|
+
"typescript": "^5.8.3",
|
|
32
|
+
"vitest": "^3.1.3",
|
|
33
33
|
"xo": "^0.60.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"hookified": "^1.
|
|
37
|
-
"keyv": "^5.3.
|
|
36
|
+
"hookified": "^1.8.2",
|
|
37
|
+
"keyv": "^5.3.3"
|
|
38
38
|
},
|
|
39
39
|
"keywords": [
|
|
40
40
|
"cacheable",
|