petty-cache 3.6.0 → 3.7.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/CHANGELOG.md +4 -0
- package/README.md +3 -1
- package/index.js +317 -128
- package/package.json +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [3.7.0] - 2026-03-12
|
|
10
|
+
### Changed
|
|
11
|
+
- Added the ability for `pettyCache.bulkGet`, `pettyCache.bulkSet`, and `pettyCache.bulkFetch` functions to support callbacks and promises.
|
|
12
|
+
|
|
9
13
|
## [3.6.0] - 2026-02-12
|
|
10
14
|
### Changed
|
|
11
15
|
- Added the ability for `pettyCache.get`, `pettyCache.set`, and `pettyCache.patch` functions to support callbacks and promises.
|
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# petty-cache
|
|
2
2
|
|
|
3
3
|
[](https://github.com/stores-com/petty-cache/actions?query=workflow%3Abuild+branch%3Amain)
|
|
4
|
-
[](https://coveralls.io/github/stores-com/petty-cache?branch=main)
|
|
4
|
+
[](https://coveralls.io/github/stores-com/petty-cache?branch=main)
|
|
5
|
+
[](https://www.npmjs.com/package/petty-cache)
|
|
6
|
+
[](LICENSE)
|
|
5
7
|
|
|
6
8
|
A cache module for Node.js that uses a two-level cache (in-memory cache for recently accessed data plus Redis for distributed caching) with automatic serialization plus some extra features to avoid cache stampedes and thundering herds.
|
|
7
9
|
|
package/index.js
CHANGED
|
@@ -3,6 +3,11 @@ const lock = require('lock').Lock();
|
|
|
3
3
|
const memoryCache = require('memory-cache');
|
|
4
4
|
const redis = require('redis');
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Creates a new PettyCache instance backed by Redis.
|
|
8
|
+
* Accepts the same arguments as redis.createClient(), or an existing RedisClient instance.
|
|
9
|
+
* @param {...*} args - Either a RedisClient instance, or arguments forwarded to redis.createClient().
|
|
10
|
+
*/
|
|
6
11
|
function PettyCache() {
|
|
7
12
|
const intervals = {};
|
|
8
13
|
let redisClient;
|
|
@@ -16,6 +21,11 @@ function PettyCache() {
|
|
|
16
21
|
//eslint-disable-next-line no-console
|
|
17
22
|
redisClient.on('error', err => console.warn(`Warning: Redis reported a client error: ${err}`));
|
|
18
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Fetches multiple keys from Redis.
|
|
26
|
+
* @param {string[]} keys
|
|
27
|
+
* @param {Function} callback - callback(err, values) where values maps each key to {exists, value}.
|
|
28
|
+
*/
|
|
19
29
|
function bulkGetFromRedis(keys, callback) {
|
|
20
30
|
// Try to get values from Redis
|
|
21
31
|
redisClient.mget(keys, (err, data) => {
|
|
@@ -41,6 +51,11 @@ function PettyCache() {
|
|
|
41
51
|
});
|
|
42
52
|
}
|
|
43
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Fetches a single key from the in-process memory cache.
|
|
56
|
+
* @param {string} key
|
|
57
|
+
* @returns {{exists: boolean, value: *}}
|
|
58
|
+
*/
|
|
44
59
|
function getFromMemoryCache(key) {
|
|
45
60
|
// Try to get value from memory cache
|
|
46
61
|
const value = memoryCache.get(key);
|
|
@@ -59,6 +74,11 @@ function PettyCache() {
|
|
|
59
74
|
return { exists: false };
|
|
60
75
|
}
|
|
61
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Fetches a single key from Redis.
|
|
79
|
+
* @param {string} key
|
|
80
|
+
* @param {Function} callback - callback(err, {exists, value}).
|
|
81
|
+
*/
|
|
62
82
|
function getFromRedis(key, callback) {
|
|
63
83
|
// Try to get value from Redis
|
|
64
84
|
redisClient.get(key, (err, data) => {
|
|
@@ -75,6 +95,12 @@ function PettyCache() {
|
|
|
75
95
|
});
|
|
76
96
|
}
|
|
77
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Resolves TTL options into a {min, max} object in milliseconds. Defaults to 30–60 seconds.
|
|
100
|
+
* @param {Object} options
|
|
101
|
+
* @param {number|Object} [options.ttl] - Fixed ms value, or an object with min/max properties.
|
|
102
|
+
* @returns {{min: number, max: number}}
|
|
103
|
+
*/
|
|
78
104
|
function getTtl(options) {
|
|
79
105
|
// Default TTL is 30-60 seconds
|
|
80
106
|
const ttl = {
|
|
@@ -101,159 +127,219 @@ function PettyCache() {
|
|
|
101
127
|
}
|
|
102
128
|
|
|
103
129
|
/**
|
|
104
|
-
*
|
|
130
|
+
* Returns data from cache for each key if available; otherwise executes func for the missing keys
|
|
131
|
+
* and stores the results in cache before returning. Supports both callback and promise styles.
|
|
132
|
+
* @param {Array} keys - An array of cache keys.
|
|
133
|
+
* @param {Function} func - Function called with missing keys and a callback: (keys, callback).
|
|
134
|
+
* @param {Object} [options] - Optional settings.
|
|
135
|
+
* @param {number|Object} [options.ttl] - TTL in ms, or object with min/max properties.
|
|
136
|
+
* @param {Function} [callback] - Optional callback(err, values). If omitted, returns a Promise.
|
|
137
|
+
* @returns {Promise|undefined} Resolves with an object mapping each key to its cached value.
|
|
105
138
|
*/
|
|
106
|
-
this.bulkFetch = (keys, func, options, callback) => {
|
|
107
|
-
|
|
108
|
-
if (!callback) {
|
|
139
|
+
this.bulkFetch = (keys, func, options = {}, callback) => {
|
|
140
|
+
if (typeof options === 'function') {
|
|
109
141
|
callback = options;
|
|
110
142
|
options = {};
|
|
111
143
|
}
|
|
112
144
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
145
|
+
const executor = () => {
|
|
146
|
+
return new Promise((resolve, reject) => {
|
|
147
|
+
// If there aren't any keys, return
|
|
148
|
+
if (!keys.length) {
|
|
149
|
+
return resolve({});
|
|
150
|
+
}
|
|
117
151
|
|
|
118
|
-
|
|
119
|
-
|
|
152
|
+
const _keys = Array.from(new Set(keys));
|
|
153
|
+
const values = {};
|
|
120
154
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
155
|
+
// Try to get values from memory cache
|
|
156
|
+
for (let i = _keys.length - 1; i >= 0; i--) {
|
|
157
|
+
const key = _keys[i];
|
|
158
|
+
const result = getFromMemoryCache(key);
|
|
125
159
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
160
|
+
if (result.exists) {
|
|
161
|
+
values[key] = result.value;
|
|
162
|
+
_keys.splice(i, 1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
131
165
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
166
|
+
// If there aren't any keys left, return
|
|
167
|
+
if (!_keys.length) {
|
|
168
|
+
return resolve(values);
|
|
169
|
+
}
|
|
136
170
|
|
|
137
|
-
|
|
171
|
+
// Try to get values from Redis
|
|
172
|
+
bulkGetFromRedis(_keys, (err, results) => {
|
|
173
|
+
if (err) {
|
|
174
|
+
return reject(err);
|
|
175
|
+
}
|
|
138
176
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return callback(err);
|
|
143
|
-
}
|
|
177
|
+
for (let i = _keys.length - 1; i >= 0; i--) {
|
|
178
|
+
const key = _keys[i];
|
|
179
|
+
const result = results[key];
|
|
144
180
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
181
|
+
if (result.exists) {
|
|
182
|
+
_keys.splice(i, 1);
|
|
183
|
+
values[key] = result.value;
|
|
148
184
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
185
|
+
// Store value in memory cache with a short expiration
|
|
186
|
+
memoryCache.put(key, result.value, random(2000, 5000));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
152
189
|
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
190
|
+
// If there aren't any keys left, return
|
|
191
|
+
if (!_keys.length) {
|
|
192
|
+
return resolve(values);
|
|
193
|
+
}
|
|
157
194
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
195
|
+
// Execute the specified function for remaining keys
|
|
196
|
+
func(_keys, (err, data) => {
|
|
197
|
+
if (err) {
|
|
198
|
+
return reject(err);
|
|
199
|
+
}
|
|
162
200
|
|
|
163
|
-
|
|
164
|
-
func(_keys, (err, data) => {
|
|
165
|
-
if (err) {
|
|
166
|
-
return callback(err);
|
|
167
|
-
}
|
|
201
|
+
Object.keys(data).forEach(key => values[key] = data[key]);
|
|
168
202
|
|
|
169
|
-
|
|
203
|
+
this.bulkSet(data, options, err => {
|
|
204
|
+
if (err) {
|
|
205
|
+
return reject(err);
|
|
206
|
+
}
|
|
170
207
|
|
|
171
|
-
|
|
208
|
+
resolve(values);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
});
|
|
172
212
|
});
|
|
173
|
-
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (callback) {
|
|
216
|
+
executor().then(result => callback(null, result)).catch(callback);
|
|
217
|
+
} else {
|
|
218
|
+
return executor();
|
|
219
|
+
}
|
|
174
220
|
};
|
|
175
221
|
|
|
176
222
|
/**
|
|
177
|
-
*
|
|
223
|
+
* Gets cached values for an array of keys. Supports both callback and promise styles.
|
|
224
|
+
* @param {Array} keys - An array of cache keys.
|
|
225
|
+
* @param {Function} [callback] - Optional callback(err, values). If omitted, returns a Promise.
|
|
226
|
+
* @returns {Promise|undefined} Resolves with an object mapping each key to its value, or null if not found.
|
|
178
227
|
*/
|
|
179
228
|
this.bulkGet = (keys, callback) => {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
229
|
+
const executor = () => {
|
|
230
|
+
return new Promise((resolve, reject) => {
|
|
231
|
+
// If there aren't any keys, return
|
|
232
|
+
if (!keys.length) {
|
|
233
|
+
return resolve({});
|
|
234
|
+
}
|
|
184
235
|
|
|
185
|
-
|
|
186
|
-
|
|
236
|
+
const _keys = Array.from(new Set(keys));
|
|
237
|
+
const values = {};
|
|
187
238
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
239
|
+
// Try to get values from memory cache
|
|
240
|
+
for (let i = _keys.length - 1; i >= 0; i--) {
|
|
241
|
+
const key = _keys[i];
|
|
242
|
+
const result = getFromMemoryCache(key);
|
|
192
243
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
244
|
+
if (result.exists) {
|
|
245
|
+
values[key] = result.value;
|
|
246
|
+
_keys.splice(i, 1);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
198
249
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
250
|
+
// If there aren't any keys left, return
|
|
251
|
+
if (!_keys.length) {
|
|
252
|
+
return resolve(values);
|
|
253
|
+
}
|
|
203
254
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
255
|
+
// Try to get values from Redis
|
|
256
|
+
bulkGetFromRedis(_keys, (err, results) => {
|
|
257
|
+
if (err) {
|
|
258
|
+
return reject(err);
|
|
259
|
+
}
|
|
209
260
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
261
|
+
for (let i = 0; i < _keys.length; i++) {
|
|
262
|
+
const key = _keys[i];
|
|
263
|
+
const result = results[key];
|
|
213
264
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
265
|
+
if (!result.exists) {
|
|
266
|
+
values[key] = null;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
218
269
|
|
|
219
|
-
|
|
270
|
+
values[key] = result.value;
|
|
220
271
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
272
|
+
// Store value in memory cache with a short expiration
|
|
273
|
+
memoryCache.put(key, result.value, random(2000, 5000));
|
|
274
|
+
}
|
|
224
275
|
|
|
225
|
-
|
|
226
|
-
|
|
276
|
+
resolve(values);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
if (callback) {
|
|
282
|
+
executor().then(result => callback(null, result)).catch(callback);
|
|
283
|
+
} else {
|
|
284
|
+
return executor();
|
|
285
|
+
}
|
|
227
286
|
};
|
|
228
287
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
288
|
+
/**
|
|
289
|
+
* Sets multiple key/value pairs in cache simultaneously. Supports both callback and promise styles.
|
|
290
|
+
* @param {Object} values - An object mapping cache keys to their values.
|
|
291
|
+
* @param {Object} [options] - Optional settings.
|
|
292
|
+
* @param {number|Object} [options.ttl] - TTL in ms, or object with min/max properties.
|
|
293
|
+
* @param {Function} [callback] - Optional callback(err). If omitted, returns a Promise.
|
|
294
|
+
* @returns {Promise|undefined}
|
|
295
|
+
*/
|
|
296
|
+
this.bulkSet = (values, options = {}, callback) => {
|
|
297
|
+
if (typeof options === 'function') {
|
|
232
298
|
callback = options;
|
|
233
299
|
options = {};
|
|
234
300
|
}
|
|
235
301
|
|
|
236
|
-
|
|
237
|
-
|
|
302
|
+
const executor = () => {
|
|
303
|
+
return new Promise((resolve, reject) => {
|
|
304
|
+
// Get TTL based on specified options
|
|
305
|
+
const ttl = getTtl(options);
|
|
238
306
|
|
|
239
|
-
|
|
240
|
-
|
|
307
|
+
// Redis does not have a MSETEX command so we batch commands: http://redis.js.org/#api-clientbatchcommands
|
|
308
|
+
const batch = redisClient.batch();
|
|
241
309
|
|
|
242
|
-
|
|
243
|
-
|
|
310
|
+
Object.keys(values).forEach(key => {
|
|
311
|
+
const value = values[key];
|
|
244
312
|
|
|
245
|
-
|
|
246
|
-
|
|
313
|
+
// Store value in memory cache with a short expiration
|
|
314
|
+
memoryCache.put(key, value, random(2000, 5000));
|
|
247
315
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
316
|
+
// Add Redis command
|
|
317
|
+
batch.psetex(key, random(ttl.min, ttl.max), PettyCache.stringify(value));
|
|
318
|
+
});
|
|
251
319
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
320
|
+
batch.exec((err) => {
|
|
321
|
+
if (err) {
|
|
322
|
+
return reject(err);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
resolve();
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
if (callback) {
|
|
331
|
+
executor().then(result => callback(null, result)).catch(callback);
|
|
332
|
+
} else {
|
|
333
|
+
return executor();
|
|
334
|
+
}
|
|
255
335
|
};
|
|
256
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Deletes a key from both the memory cache and Redis. Supports both callback and promise styles.
|
|
339
|
+
* @param {string} key - The cache key to delete.
|
|
340
|
+
* @param {Function} [callback] - Optional callback(err). If omitted, returns a Promise.
|
|
341
|
+
* @returns {Promise|undefined}
|
|
342
|
+
*/
|
|
257
343
|
this.del = (key, callback) => {
|
|
258
344
|
const executor = () => {
|
|
259
345
|
return new Promise((resolve, reject) => {
|
|
@@ -275,11 +361,16 @@ function PettyCache() {
|
|
|
275
361
|
}
|
|
276
362
|
};
|
|
277
363
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
364
|
+
/**
|
|
365
|
+
* Returns data from cache if available; otherwise executes func, stores the result, and returns it.
|
|
366
|
+
* Uses double-checked locking to prevent cache stampedes. Supports async and callback func signatures.
|
|
367
|
+
* @param {string} key - The cache key.
|
|
368
|
+
* @param {Function} func - Called on cache miss. Use func(callback) for callbacks or async func() for promises.
|
|
369
|
+
* @param {Object} [options] - Optional settings.
|
|
370
|
+
* @param {number|Object} [options.ttl] - TTL in ms, or object with min/max properties.
|
|
371
|
+
* @param {Function} [callback] - Optional callback(err, value). Defaults to a noop.
|
|
372
|
+
*/
|
|
373
|
+
this.fetch = (key, func, options = {}, callback) => {
|
|
283
374
|
if (typeof options === 'function') {
|
|
284
375
|
callback = options;
|
|
285
376
|
options = {};
|
|
@@ -388,9 +479,16 @@ function PettyCache() {
|
|
|
388
479
|
});
|
|
389
480
|
};
|
|
390
481
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
482
|
+
/**
|
|
483
|
+
* Like fetch(), but also sets up a background interval to proactively refresh the cached value
|
|
484
|
+
* before it expires, preventing cache misses under sustained load.
|
|
485
|
+
* @param {string} key - The cache key.
|
|
486
|
+
* @param {Function} func - Called on cache miss and on each refresh interval: func(callback).
|
|
487
|
+
* @param {Object} [options] - Optional settings.
|
|
488
|
+
* @param {number|Object} [options.ttl] - TTL in ms, or object with min/max properties.
|
|
489
|
+
* @param {Function} [callback] - Optional callback(err, value). Defaults to a noop.
|
|
490
|
+
*/
|
|
491
|
+
this.fetchAndRefresh = (key, func, options = {}, callback) => {
|
|
394
492
|
if (typeof options === 'function') {
|
|
395
493
|
callback = options;
|
|
396
494
|
options = {};
|
|
@@ -429,6 +527,12 @@ function PettyCache() {
|
|
|
429
527
|
this.fetch(key, func, options, callback);
|
|
430
528
|
};
|
|
431
529
|
|
|
530
|
+
/**
|
|
531
|
+
* Gets a cached value. Supports both callback and promise styles.
|
|
532
|
+
* @param {string} key - The cache key.
|
|
533
|
+
* @param {Function} [callback] - Optional callback(err, value). If omitted, returns a Promise.
|
|
534
|
+
* @returns {Promise|undefined} Resolves with the cached value, or null if not found.
|
|
535
|
+
*/
|
|
432
536
|
this.get = (key, callback) => {
|
|
433
537
|
const executor = () => {
|
|
434
538
|
return new Promise((resolve, reject) => {
|
|
@@ -482,15 +586,24 @@ function PettyCache() {
|
|
|
482
586
|
};
|
|
483
587
|
|
|
484
588
|
this.mutex = {
|
|
485
|
-
|
|
589
|
+
/**
|
|
590
|
+
* Acquires a distributed mutex lock in Redis. Supports both callback and promise styles.
|
|
591
|
+
* @param {string} key - The lock key.
|
|
592
|
+
* @param {Object} [options] - Optional settings.
|
|
593
|
+
* @param {number} [options.ttl=1000] - Lock TTL in ms.
|
|
594
|
+
* @param {Object} [options.retry] - Retry options.
|
|
595
|
+
* @param {number} [options.retry.times=1] - Number of acquisition attempts.
|
|
596
|
+
* @param {number} [options.retry.interval=100] - Delay between retries in ms.
|
|
597
|
+
* @param {Function} [callback] - Optional callback(err). If omitted, returns a Promise.
|
|
598
|
+
* @returns {Promise|undefined}
|
|
599
|
+
*/
|
|
600
|
+
lock: (key, options = {}, callback) => {
|
|
486
601
|
// Options are optional
|
|
487
602
|
if (!callback && typeof options === 'function') {
|
|
488
603
|
callback = options;
|
|
489
604
|
options = {};
|
|
490
605
|
}
|
|
491
606
|
|
|
492
|
-
options = options || {};
|
|
493
|
-
|
|
494
607
|
options.retry = Object.hasOwn(options, 'retry') ? options.retry : {};
|
|
495
608
|
options.retry.interval = Object.hasOwn(options.retry, 'interval') ? options.retry.interval : 100;
|
|
496
609
|
options.retry.times = Object.hasOwn(options.retry, 'times') ? options.retry.times : 1;
|
|
@@ -530,6 +643,12 @@ function PettyCache() {
|
|
|
530
643
|
return executor();
|
|
531
644
|
}
|
|
532
645
|
},
|
|
646
|
+
/**
|
|
647
|
+
* Releases a distributed mutex lock in Redis. Supports both callback and promise styles.
|
|
648
|
+
* @param {string} key - The lock key to release.
|
|
649
|
+
* @param {Function} [callback] - Optional callback(err). If omitted, returns a Promise.
|
|
650
|
+
* @returns {Promise|undefined}
|
|
651
|
+
*/
|
|
533
652
|
unlock: (key, callback) => {
|
|
534
653
|
const executor = () => {
|
|
535
654
|
return new Promise((resolve, reject) => {
|
|
@@ -551,14 +670,22 @@ function PettyCache() {
|
|
|
551
670
|
}
|
|
552
671
|
};
|
|
553
672
|
|
|
554
|
-
|
|
673
|
+
/**
|
|
674
|
+
* Updates specific properties of a cached object without replacing the whole value.
|
|
675
|
+
* Supports both callback and promise styles.
|
|
676
|
+
* @param {string} key - The cache key of the object to patch.
|
|
677
|
+
* @param {Object} value - Properties to merge into the cached object.
|
|
678
|
+
* @param {Object} [options] - Optional settings passed to set().
|
|
679
|
+
* @param {number|Object} [options.ttl] - TTL in ms, or object with min/max properties.
|
|
680
|
+
* @param {Function} [callback] - Optional callback(err). If omitted, returns a Promise.
|
|
681
|
+
* @returns {Promise|undefined}
|
|
682
|
+
*/
|
|
683
|
+
this.patch = (key, value, options = {}, callback) => {
|
|
555
684
|
if (!callback && typeof options === 'function') {
|
|
556
685
|
callback = options;
|
|
557
686
|
options = {};
|
|
558
687
|
}
|
|
559
688
|
|
|
560
|
-
options = options || {};
|
|
561
|
-
|
|
562
689
|
const _this = this;
|
|
563
690
|
|
|
564
691
|
const executor = () => {
|
|
@@ -595,15 +722,23 @@ function PettyCache() {
|
|
|
595
722
|
};
|
|
596
723
|
|
|
597
724
|
this.semaphore = {
|
|
598
|
-
|
|
725
|
+
/**
|
|
726
|
+
* Acquires a slot in an existing semaphore pool. Retries if no slot is currently available.
|
|
727
|
+
* @param {string} key - The semaphore key.
|
|
728
|
+
* @param {Object} [options] - Optional settings.
|
|
729
|
+
* @param {number} [options.ttl=1000] - Slot TTL in ms; expired slots may be reclaimed.
|
|
730
|
+
* @param {Object} [options.retry] - Retry options.
|
|
731
|
+
* @param {number} [options.retry.times=1] - Number of acquisition attempts.
|
|
732
|
+
* @param {number} [options.retry.interval=100] - Delay between retries in ms.
|
|
733
|
+
* @param {Function} callback - callback(err, index) where index is the acquired slot number.
|
|
734
|
+
*/
|
|
735
|
+
acquireLock: (key, options = {}, callback) => {
|
|
599
736
|
// Options are optional
|
|
600
737
|
if (!callback && typeof options === 'function') {
|
|
601
738
|
callback = options;
|
|
602
739
|
options = {};
|
|
603
740
|
}
|
|
604
741
|
|
|
605
|
-
options = options || {};
|
|
606
|
-
|
|
607
742
|
options.retry = Object.prototype.hasOwnProperty.call(options, 'retry') ? options.retry : {};
|
|
608
743
|
options.retry.interval = Object.prototype.hasOwnProperty.call(options.retry, 'interval') ? options.retry.interval : 100;
|
|
609
744
|
options.retry.times = Object.prototype.hasOwnProperty.call(options.retry, 'times') ? options.retry.times : 1;
|
|
@@ -656,6 +791,13 @@ function PettyCache() {
|
|
|
656
791
|
});
|
|
657
792
|
}, callback);
|
|
658
793
|
},
|
|
794
|
+
/**
|
|
795
|
+
* Permanently consumes a semaphore slot, marking it consumed rather than available.
|
|
796
|
+
* Ensures at least one slot always remains non-consumed.
|
|
797
|
+
* @param {string} key - The semaphore key.
|
|
798
|
+
* @param {number} index - The slot index to consume.
|
|
799
|
+
* @param {Function} [callback] - Optional callback(err). Defaults to a noop.
|
|
800
|
+
*/
|
|
659
801
|
consumeLock: (key, index, callback) => {
|
|
660
802
|
callback = callback || (() => {});
|
|
661
803
|
|
|
@@ -702,6 +844,12 @@ function PettyCache() {
|
|
|
702
844
|
});
|
|
703
845
|
});
|
|
704
846
|
},
|
|
847
|
+
/**
|
|
848
|
+
* Increases the size of an existing semaphore pool. Cannot shrink a pool.
|
|
849
|
+
* @param {string} key - The semaphore key.
|
|
850
|
+
* @param {number} size - The desired pool size (must be >= current size).
|
|
851
|
+
* @param {Function} [callback] - Optional callback(err). Defaults to a noop.
|
|
852
|
+
*/
|
|
705
853
|
expand: (key, size, callback) => {
|
|
706
854
|
callback = callback || (() => {});
|
|
707
855
|
|
|
@@ -745,6 +893,12 @@ function PettyCache() {
|
|
|
745
893
|
});
|
|
746
894
|
});
|
|
747
895
|
},
|
|
896
|
+
/**
|
|
897
|
+
* Releases an acquired semaphore slot, marking it available again.
|
|
898
|
+
* @param {string} key - The semaphore key.
|
|
899
|
+
* @param {number} index - The slot index to release.
|
|
900
|
+
* @param {Function} [callback] - Optional callback(err). Defaults to a noop.
|
|
901
|
+
*/
|
|
748
902
|
releaseLock: (key, index, callback) => {
|
|
749
903
|
callback = callback || (() => {});
|
|
750
904
|
|
|
@@ -786,6 +940,11 @@ function PettyCache() {
|
|
|
786
940
|
});
|
|
787
941
|
});
|
|
788
942
|
},
|
|
943
|
+
/**
|
|
944
|
+
* Resets all slots in an existing semaphore pool to available.
|
|
945
|
+
* @param {string} key - The semaphore key.
|
|
946
|
+
* @param {Function} [callback] - Optional callback(err, pool). Defaults to a noop.
|
|
947
|
+
*/
|
|
789
948
|
reset: (key, callback) => {
|
|
790
949
|
callback = callback || (() => {});
|
|
791
950
|
|
|
@@ -822,7 +981,14 @@ function PettyCache() {
|
|
|
822
981
|
});
|
|
823
982
|
});
|
|
824
983
|
},
|
|
825
|
-
|
|
984
|
+
/**
|
|
985
|
+
* Retrieves an existing semaphore pool, or creates one if it doesn't exist.
|
|
986
|
+
* @param {string} key - The semaphore key.
|
|
987
|
+
* @param {Object} [options] - Optional settings.
|
|
988
|
+
* @param {number|Function} [options.size=1] - Pool size, or a function(callback) that resolves the size.
|
|
989
|
+
* @param {Function} [callback] - Optional callback(err, pool). Defaults to a noop.
|
|
990
|
+
*/
|
|
991
|
+
retrieveOrCreate: (key, options = {}, callback) => {
|
|
826
992
|
// Options are optional
|
|
827
993
|
if (!callback && typeof options === 'function') {
|
|
828
994
|
callback = options;
|
|
@@ -830,7 +996,6 @@ function PettyCache() {
|
|
|
830
996
|
}
|
|
831
997
|
|
|
832
998
|
callback = callback || (() => {});
|
|
833
|
-
options = options || {};
|
|
834
999
|
|
|
835
1000
|
const _this = this;
|
|
836
1001
|
|
|
@@ -881,9 +1046,16 @@ function PettyCache() {
|
|
|
881
1046
|
}
|
|
882
1047
|
};
|
|
883
1048
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1049
|
+
/**
|
|
1050
|
+
* Stores a value in both the memory cache and Redis. Supports both callback and promise styles.
|
|
1051
|
+
* @param {string} key - The cache key.
|
|
1052
|
+
* @param {*} value - The value to cache.
|
|
1053
|
+
* @param {Object} [options] - Optional settings.
|
|
1054
|
+
* @param {number|Object} [options.ttl] - TTL in ms, or object with min/max properties.
|
|
1055
|
+
* @param {Function} [callback] - Optional callback(err). If omitted, returns a Promise.
|
|
1056
|
+
* @returns {Promise|undefined}
|
|
1057
|
+
*/
|
|
1058
|
+
this.set = (key, value, options = {}, callback) => {
|
|
887
1059
|
if (typeof options === 'function') {
|
|
888
1060
|
callback = options;
|
|
889
1061
|
options = {};
|
|
@@ -921,6 +1093,12 @@ function PettyCache() {
|
|
|
921
1093
|
}
|
|
922
1094
|
}
|
|
923
1095
|
|
|
1096
|
+
/**
|
|
1097
|
+
* Returns a random integer between min and max, inclusive.
|
|
1098
|
+
* @param {number} min
|
|
1099
|
+
* @param {number} max
|
|
1100
|
+
* @returns {number}
|
|
1101
|
+
*/
|
|
924
1102
|
function random(min, max) {
|
|
925
1103
|
if (min === max) {
|
|
926
1104
|
return min;
|
|
@@ -929,6 +1107,11 @@ function random(min, max) {
|
|
|
929
1107
|
return Math.floor(Math.random() * (max - min + 1) + min);
|
|
930
1108
|
}
|
|
931
1109
|
|
|
1110
|
+
/**
|
|
1111
|
+
* Parses a JSON string produced by PettyCache.stringify(), restoring NaN, null, and undefined.
|
|
1112
|
+
* @param {string} text
|
|
1113
|
+
* @returns {*}
|
|
1114
|
+
*/
|
|
932
1115
|
PettyCache.parse = (text) => {
|
|
933
1116
|
return JSON.parse(text, (k, v) => {
|
|
934
1117
|
if (v === '__NaN') {
|
|
@@ -943,6 +1126,12 @@ PettyCache.parse = (text) => {
|
|
|
943
1126
|
});
|
|
944
1127
|
};
|
|
945
1128
|
|
|
1129
|
+
/**
|
|
1130
|
+
* Serializes a value to JSON, encoding NaN, null, and undefined as sentinel strings
|
|
1131
|
+
* so they survive a Redis round-trip and can be restored by PettyCache.parse().
|
|
1132
|
+
* @param {*} value
|
|
1133
|
+
* @returns {string}
|
|
1134
|
+
*/
|
|
946
1135
|
PettyCache.stringify = (value) => {
|
|
947
1136
|
return JSON.stringify(value, (k, v) => {
|
|
948
1137
|
if (typeof v === 'number' && isNaN(v)) {
|
package/package.json
CHANGED
|
@@ -21,10 +21,14 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"main": "index.js",
|
|
23
23
|
"name": "petty-cache",
|
|
24
|
-
"repository":
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/stores-com/petty-cache.git"
|
|
27
|
+
},
|
|
25
28
|
"scripts": {
|
|
26
|
-
"coveralls": "node --test --test-
|
|
27
|
-
"test": "node --test --test-
|
|
29
|
+
"coveralls": "node --test --test-force-exit --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=lcov.info && coveralls < lcov.info",
|
|
30
|
+
"test": "node --test --test-force-exit --test-reporter=spec",
|
|
31
|
+
"test:only": "node --test --test-force-exit --test-only --test-reporter=spec"
|
|
28
32
|
},
|
|
29
|
-
"version": "3.
|
|
33
|
+
"version": "3.7.0"
|
|
30
34
|
}
|