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 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
  [![Build Status](https://github.com/stores-com/petty-cache/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/stores-com/petty-cache/actions?query=workflow%3Abuild+branch%3Amain)
4
- [![Coverage Status](https://coveralls.io/repos/github/stores-com/petty-cache/badge.svg?branch=main)](https://coveralls.io/github/stores-com/petty-cache?branch=main)
4
+ [![Coverage Status](https://coveralls.io/repos/github/stores-com/petty-cache/badge.svg?branch=main&t=Pc1x8G)](https://coveralls.io/github/stores-com/petty-cache?branch=main)
5
+ [![npm version](https://img.shields.io/npm/v/petty-cache)](https://www.npmjs.com/package/petty-cache)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- * @param {Array} keys - An array of keys.
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
- // Options are optional
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
- // If there aren't any keys, return
114
- if (!keys.length) {
115
- return callback(null, {});
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
- const _keys = Array.from(new Set(keys));
119
- const values = {};
152
+ const _keys = Array.from(new Set(keys));
153
+ const values = {};
120
154
 
121
- // Try to get values from memory cache
122
- for (let i = _keys.length - 1; i >= 0; i--) {
123
- const key = _keys[i];
124
- const result = getFromMemoryCache(key);
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
- if (result.exists) {
127
- values[key] = result.value;
128
- _keys.splice(i, 1);
129
- }
130
- }
160
+ if (result.exists) {
161
+ values[key] = result.value;
162
+ _keys.splice(i, 1);
163
+ }
164
+ }
131
165
 
132
- // If there aren't any keys left, return
133
- if (!_keys.length) {
134
- return callback(null, values);
135
- }
166
+ // If there aren't any keys left, return
167
+ if (!_keys.length) {
168
+ return resolve(values);
169
+ }
136
170
 
137
- const _this = this;
171
+ // Try to get values from Redis
172
+ bulkGetFromRedis(_keys, (err, results) => {
173
+ if (err) {
174
+ return reject(err);
175
+ }
138
176
 
139
- // Try to get values from Redis
140
- bulkGetFromRedis(_keys, (err, results) => {
141
- if (err) {
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
- for (let i = _keys.length - 1; i >= 0; i--) {
146
- const key = _keys[i];
147
- const result = results[key];
181
+ if (result.exists) {
182
+ _keys.splice(i, 1);
183
+ values[key] = result.value;
148
184
 
149
- if (result.exists) {
150
- _keys.splice(i, 1);
151
- values[key] = result.value;
185
+ // Store value in memory cache with a short expiration
186
+ memoryCache.put(key, result.value, random(2000, 5000));
187
+ }
188
+ }
152
189
 
153
- // Store value in memory cache with a short expiration
154
- memoryCache.put(key, result.value, random(2000, 5000));
155
- }
156
- }
190
+ // If there aren't any keys left, return
191
+ if (!_keys.length) {
192
+ return resolve(values);
193
+ }
157
194
 
158
- // If there aren't any keys left, return
159
- if (!_keys.length) {
160
- return callback(null, values);
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
- // Execute the specified function for remaining keys
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
- Object.keys(data).forEach(key => values[key] = data[key]);
203
+ this.bulkSet(data, options, err => {
204
+ if (err) {
205
+ return reject(err);
206
+ }
170
207
 
171
- _this.bulkSet(data, options, err => callback(err, values));
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
- * @param {Array} keys - An array of keys.
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
- // If there aren't any keys, return
181
- if (!keys.length) {
182
- return callback(null, {});
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
- const _keys = Array.from(new Set(keys));
186
- const values = {};
236
+ const _keys = Array.from(new Set(keys));
237
+ const values = {};
187
238
 
188
- // Try to get values from memory cache
189
- for (let i = _keys.length - 1; i >= 0; i--) {
190
- const key = _keys[i];
191
- const result = getFromMemoryCache(key);
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
- if (result.exists) {
194
- values[key] = result.value;
195
- _keys.splice(i, 1);
196
- }
197
- }
244
+ if (result.exists) {
245
+ values[key] = result.value;
246
+ _keys.splice(i, 1);
247
+ }
248
+ }
198
249
 
199
- // If there aren't any keys left, return
200
- if (!_keys.length) {
201
- return callback(null, values);
202
- }
250
+ // If there aren't any keys left, return
251
+ if (!_keys.length) {
252
+ return resolve(values);
253
+ }
203
254
 
204
- // Try to get values from Redis
205
- bulkGetFromRedis(_keys, (err, results) => {
206
- if (err) {
207
- return callback(err);
208
- }
255
+ // Try to get values from Redis
256
+ bulkGetFromRedis(_keys, (err, results) => {
257
+ if (err) {
258
+ return reject(err);
259
+ }
209
260
 
210
- for (let i = 0; i < _keys.length; i++) {
211
- const key = _keys[i];
212
- const result = results[key];
261
+ for (let i = 0; i < _keys.length; i++) {
262
+ const key = _keys[i];
263
+ const result = results[key];
213
264
 
214
- if (!result.exists) {
215
- values[key] = null;
216
- continue;
217
- }
265
+ if (!result.exists) {
266
+ values[key] = null;
267
+ continue;
268
+ }
218
269
 
219
- values[key] = result.value;
270
+ values[key] = result.value;
220
271
 
221
- // Store value in memory cache with a short expiration
222
- memoryCache.put(key, result.value, random(2000, 5000));
223
- }
272
+ // Store value in memory cache with a short expiration
273
+ memoryCache.put(key, result.value, random(2000, 5000));
274
+ }
224
275
 
225
- callback(null, values);
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
- this.bulkSet = (values, options, callback) => {
230
- // Options are optional
231
- if (!callback) {
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
- // Get TTL based on specified options
237
- const ttl = getTtl(options);
302
+ const executor = () => {
303
+ return new Promise((resolve, reject) => {
304
+ // Get TTL based on specified options
305
+ const ttl = getTtl(options);
238
306
 
239
- // Redis does not have a MSETEX command so we batch commands: http://redis.js.org/#api-clientbatchcommands
240
- const batch = redisClient.batch();
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
- Object.keys(values).forEach(key => {
243
- const value = values[key];
310
+ Object.keys(values).forEach(key => {
311
+ const value = values[key];
244
312
 
245
- // Store value in memory cache with a short expiration
246
- memoryCache.put(key, value, random(2000, 5000));
313
+ // Store value in memory cache with a short expiration
314
+ memoryCache.put(key, value, random(2000, 5000));
247
315
 
248
- // Add Redis command
249
- batch.psetex(key, random(ttl.min, ttl.max), PettyCache.stringify(value));
250
- });
316
+ // Add Redis command
317
+ batch.psetex(key, random(ttl.min, ttl.max), PettyCache.stringify(value));
318
+ });
251
319
 
252
- batch.exec((err) => {
253
- callback(err);
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
- // Returns data from cache if available;
279
- // otherwise executes the specified function and places the results in cache before returning the data.
280
- this.fetch = (key, func, options, callback) => {
281
- options = options || {};
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
- this.fetchAndRefresh = (key, func, options, callback) => {
392
- options = options || {};
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
- lock: (key, options, callback) => {
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
- this.patch = (key, value, options, callback) => {
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
- acquireLock: (key, options, callback) => {
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
- retrieveOrCreate: (key, options, callback) => {
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
- this.set = (key, value, options, callback) => {
885
- options = options || {};
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": "https://github.com/stores-com/petty-cache",
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-concurrency=true --test-force-exit --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=lcov.info && coveralls < lcov.info",
27
- "test": "node --test --test-concurrency=true --test-force-exit --test-reporter=spec"
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.6.0"
33
+ "version": "3.7.0"
30
34
  }