petty-cache 3.5.0 → 3.6.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/LICENSE +21 -201
- package/README.md +56 -15
- package/eslint.config.js +1 -15
- package/index.js +178 -137
- package/package.json +28 -34
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.6.0] - 2026-02-12
|
|
10
|
+
### Changed
|
|
11
|
+
- Added the ability for `pettyCache.get`, `pettyCache.set`, and `pettyCache.patch` functions to support callbacks and promises.
|
|
12
|
+
|
|
9
13
|
## [3.5.0] - 2025-05-01
|
|
10
14
|
### Changed
|
|
11
15
|
- Added the ability for `pettyCache.del` functions to support callbacks and promises.
|
package/LICENSE
CHANGED
|
@@ -1,201 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
-
exercising permissions granted by this License.
|
|
25
|
-
|
|
26
|
-
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
-
including but not limited to software source code, documentation
|
|
28
|
-
source, and configuration files.
|
|
29
|
-
|
|
30
|
-
"Object" form shall mean any form resulting from mechanical
|
|
31
|
-
transformation or translation of a Source form, including but
|
|
32
|
-
not limited to compiled object code, generated documentation,
|
|
33
|
-
and conversions to other media types.
|
|
34
|
-
|
|
35
|
-
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
-
Object form, made available under the License, as indicated by a
|
|
37
|
-
copyright notice that is included in or attached to the work
|
|
38
|
-
(an example is provided in the Appendix below).
|
|
39
|
-
|
|
40
|
-
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
-
form, that is based on (or derived from) the Work and for which the
|
|
42
|
-
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
-
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
-
of this License, Derivative Works shall not include works that remain
|
|
45
|
-
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
-
the Work and Derivative Works thereof.
|
|
47
|
-
|
|
48
|
-
"Contribution" shall mean any work of authorship, including
|
|
49
|
-
the original version of the Work and any modifications or additions
|
|
50
|
-
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
-
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
-
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
-
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
-
means any form of electronic, verbal, or written communication sent
|
|
55
|
-
to the Licensor or its representatives, including but not limited to
|
|
56
|
-
communication on electronic mailing lists, source code control systems,
|
|
57
|
-
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
-
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
-
excluding communication that is conspicuously marked or otherwise
|
|
60
|
-
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
-
|
|
62
|
-
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
-
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
-
subsequently incorporated within the Work.
|
|
65
|
-
|
|
66
|
-
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
-
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
-
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
-
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
-
Work and such Derivative Works in Source or Object form.
|
|
72
|
-
|
|
73
|
-
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
-
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
-
(except as stated in this section) patent license to make, have made,
|
|
77
|
-
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
-
where such license applies only to those patent claims licensable
|
|
79
|
-
by such Contributor that are necessarily infringed by their
|
|
80
|
-
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
-
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
-
institute patent litigation against any entity (including a
|
|
83
|
-
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
-
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
-
or contributory patent infringement, then any patent licenses
|
|
86
|
-
granted to You under this License for that Work shall terminate
|
|
87
|
-
as of the date such litigation is filed.
|
|
88
|
-
|
|
89
|
-
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
-
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
-
modifications, and in Source or Object form, provided that You
|
|
92
|
-
meet the following conditions:
|
|
93
|
-
|
|
94
|
-
(a) You must give any other recipients of the Work or
|
|
95
|
-
Derivative Works a copy of this License; and
|
|
96
|
-
|
|
97
|
-
(b) You must cause any modified files to carry prominent notices
|
|
98
|
-
stating that You changed the files; and
|
|
99
|
-
|
|
100
|
-
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
-
that You distribute, all copyright, patent, trademark, and
|
|
102
|
-
attribution notices from the Source form of the Work,
|
|
103
|
-
excluding those notices that do not pertain to any part of
|
|
104
|
-
the Derivative Works; and
|
|
105
|
-
|
|
106
|
-
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
-
distribution, then any Derivative Works that You distribute must
|
|
108
|
-
include a readable copy of the attribution notices contained
|
|
109
|
-
within such NOTICE file, excluding those notices that do not
|
|
110
|
-
pertain to any part of the Derivative Works, in at least one
|
|
111
|
-
of the following places: within a NOTICE text file distributed
|
|
112
|
-
as part of the Derivative Works; within the Source form or
|
|
113
|
-
documentation, if provided along with the Derivative Works; or,
|
|
114
|
-
within a display generated by the Derivative Works, if and
|
|
115
|
-
wherever such third-party notices normally appear. The contents
|
|
116
|
-
of the NOTICE file are for informational purposes only and
|
|
117
|
-
do not modify the License. You may add Your own attribution
|
|
118
|
-
notices within Derivative Works that You distribute, alongside
|
|
119
|
-
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
-
that such additional attribution notices cannot be construed
|
|
121
|
-
as modifying the License.
|
|
122
|
-
|
|
123
|
-
You may add Your own copyright statement to Your modifications and
|
|
124
|
-
may provide additional or different license terms and conditions
|
|
125
|
-
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
-
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
-
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
-
the conditions stated in this License.
|
|
129
|
-
|
|
130
|
-
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
-
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
-
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
-
this License, without any additional terms or conditions.
|
|
134
|
-
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
-
the terms of any separate license agreement you may have executed
|
|
136
|
-
with Licensor regarding such Contributions.
|
|
137
|
-
|
|
138
|
-
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
-
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
-
except as required for reasonable and customary use in describing the
|
|
141
|
-
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
-
|
|
143
|
-
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
-
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
-
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
-
implied, including, without limitation, any warranties or conditions
|
|
148
|
-
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
-
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
-
appropriateness of using or redistributing the Work and assume any
|
|
151
|
-
risks associated with Your exercise of permissions under this License.
|
|
152
|
-
|
|
153
|
-
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
-
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
-
unless required by applicable law (such as deliberate and grossly
|
|
156
|
-
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
-
liable to You for damages, including any direct, indirect, special,
|
|
158
|
-
incidental, or consequential damages of any character arising as a
|
|
159
|
-
result of this License or out of the use or inability to use the
|
|
160
|
-
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
-
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
-
other commercial damages or losses), even if such Contributor
|
|
163
|
-
has been advised of the possibility of such damages.
|
|
164
|
-
|
|
165
|
-
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
-
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
-
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
-
or other liability obligations and/or rights consistent with this
|
|
169
|
-
License. However, in accepting such obligations, You may act only
|
|
170
|
-
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
-
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
-
defend, and hold each Contributor harmless for any liability
|
|
173
|
-
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
-
of your accepting any such warranty or additional liability.
|
|
175
|
-
|
|
176
|
-
END OF TERMS AND CONDITIONS
|
|
177
|
-
|
|
178
|
-
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
-
|
|
180
|
-
To apply the Apache License to your work, attach the following
|
|
181
|
-
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
182
|
-
replaced with your own identifying information. (Don't include
|
|
183
|
-
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
-
comment syntax for the file format. We also recommend that a
|
|
185
|
-
file or class name and description of purpose be included on the
|
|
186
|
-
same "printed page" as the copyright notice for easier
|
|
187
|
-
identification within third-party archives.
|
|
188
|
-
|
|
189
|
-
Copyright {yyyy} {name of copyright owner}
|
|
190
|
-
|
|
191
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
-
you may not use this file except in compliance with the License.
|
|
193
|
-
You may obtain a copy of the License at
|
|
194
|
-
|
|
195
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
-
|
|
197
|
-
Unless required by applicable law or agreed to in writing, software
|
|
198
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
-
See the License for the specific language governing permissions and
|
|
201
|
-
limitations under the License.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stores.com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# petty-cache
|
|
2
2
|
|
|
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)
|
|
5
5
|
|
|
6
6
|
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
7
|
|
|
@@ -28,8 +28,8 @@ Provides a pool of distributed locks with the ability to release a slot back to
|
|
|
28
28
|
|
|
29
29
|
```javascript
|
|
30
30
|
// Setup petty-cache
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const PettyCache = require('petty-cache');
|
|
32
|
+
const pettyCache = new PettyCache();
|
|
33
33
|
|
|
34
34
|
// Fetch some data
|
|
35
35
|
pettyCache.fetch('key', function(callback) {
|
|
@@ -71,7 +71,7 @@ Attempts to retrieve the values of the keys specified in the `keys` array. Any k
|
|
|
71
71
|
```javascript
|
|
72
72
|
// Let's assume a and b are already cached as 1 and 2
|
|
73
73
|
pettyCache.bulkFetch(['a', 'b', 'c', 'd'], function(keys, callback) {
|
|
74
|
-
|
|
74
|
+
const results = {};
|
|
75
75
|
|
|
76
76
|
keys.forEach(function(key) {
|
|
77
77
|
results[key] = key.toUpperCase();
|
|
@@ -145,6 +145,24 @@ pettyCache.set({ key1: 'one', key2: 2, key3: 'three' }, function(err) {
|
|
|
145
145
|
}
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
+
### pettyCache.del(key, [callback])
|
|
149
|
+
|
|
150
|
+
Deletes a value from both the in-memory cache and Redis. Supports both callbacks and promises.
|
|
151
|
+
|
|
152
|
+
**Example**
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
pettyCache.del('key', function(err) {
|
|
156
|
+
if (err) {
|
|
157
|
+
// Handle redis error
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
await pettyCache.del('key');
|
|
164
|
+
```
|
|
165
|
+
|
|
148
166
|
### pettyCache.fetch(key, cacheMissFunction, [options,] callback)
|
|
149
167
|
|
|
150
168
|
Attempts to retrieve the value from cache at the specified key. If it doesn't exist, it executes the specified cacheMissFunction that takes two parameters: an error and a value. `cacheMissFunction` should retrieve the expected value for the key from another source and pass it to the given callback. Either way, the resulting error or value is passed to `callback`.
|
|
@@ -222,9 +240,9 @@ pettyCache.fetchAndRefresh('key', function(callback) {
|
|
|
222
240
|
}
|
|
223
241
|
```
|
|
224
242
|
|
|
225
|
-
### pettyCache.get(key, callback)
|
|
243
|
+
### pettyCache.get(key, [callback])
|
|
226
244
|
|
|
227
|
-
Attempts to retrieve the value from cache at the specified key. Returns `null` if the key doesn't exist.
|
|
245
|
+
Attempts to retrieve the value from cache at the specified key. Returns `null` if the key doesn't exist. Supports both callbacks and promises.
|
|
228
246
|
|
|
229
247
|
**Example**
|
|
230
248
|
|
|
@@ -235,14 +253,18 @@ pettyCache.get('key', function(err, value) {
|
|
|
235
253
|
});
|
|
236
254
|
```
|
|
237
255
|
|
|
238
|
-
|
|
256
|
+
```javascript
|
|
257
|
+
const value = await pettyCache.get('key');
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### pettyCache.patch(key, value, [options, [callback]])
|
|
239
261
|
|
|
240
|
-
Updates an object at the given key with the property values provided. Sends an error to the callback if the key does not exist.
|
|
262
|
+
Updates an object at the given key with the property values provided. Sends an error to the callback if the key does not exist. Supports both callbacks and promises.
|
|
241
263
|
|
|
242
264
|
**Example**
|
|
243
265
|
|
|
244
266
|
```javascript
|
|
245
|
-
pettyCache.patch('key', { a: 1 }, function(
|
|
267
|
+
pettyCache.patch('key', { a: 1 }, function(err) {
|
|
246
268
|
if (err) {
|
|
247
269
|
// Handle redis or key not found error
|
|
248
270
|
}
|
|
@@ -251,6 +273,10 @@ pettyCache.patch('key', { a: 1 }, function(callback) {
|
|
|
251
273
|
});
|
|
252
274
|
```
|
|
253
275
|
|
|
276
|
+
```javascript
|
|
277
|
+
await pettyCache.patch('key', { a: 1 });
|
|
278
|
+
```
|
|
279
|
+
|
|
254
280
|
**Options**
|
|
255
281
|
|
|
256
282
|
```
|
|
@@ -269,9 +295,9 @@ pettyCache.patch('key', { a: 1 }, function(callback) {
|
|
|
269
295
|
}
|
|
270
296
|
```
|
|
271
297
|
|
|
272
|
-
### pettyCache.set(key, value, [options,
|
|
298
|
+
### pettyCache.set(key, value, [options, [callback]])
|
|
273
299
|
|
|
274
|
-
Unconditionally sets a value for a given key.
|
|
300
|
+
Unconditionally sets a value for a given key. Supports both callbacks and promises.
|
|
275
301
|
|
|
276
302
|
**Example**
|
|
277
303
|
|
|
@@ -283,6 +309,10 @@ pettyCache.set('key', { a: 'b' }, function(err) {
|
|
|
283
309
|
});
|
|
284
310
|
```
|
|
285
311
|
|
|
312
|
+
```javascript
|
|
313
|
+
await pettyCache.set('key', { a: 'b' });
|
|
314
|
+
```
|
|
315
|
+
|
|
286
316
|
**Options**
|
|
287
317
|
|
|
288
318
|
```
|
|
@@ -305,7 +335,7 @@ pettyCache.set('key', { a: 'b' }, function(err) {
|
|
|
305
335
|
|
|
306
336
|
### pettyCache.mutex.lock(key, [options, [callback]])
|
|
307
337
|
|
|
308
|
-
Attempts to acquire a distributed lock for the specified key. Optionally retries a specified number of times by waiting a specified amount of time between attempts.
|
|
338
|
+
Attempts to acquire a distributed lock for the specified key. Optionally retries a specified number of times by waiting a specified amount of time between attempts. Supports both callbacks and promises.
|
|
309
339
|
|
|
310
340
|
```javascript
|
|
311
341
|
pettyCache.mutex.lock('key', { retry: { interval: 100, times: 5 }, ttl: 1000 }, function(err) {
|
|
@@ -318,6 +348,13 @@ pettyCache.mutex.lock('key', { retry: { interval: 100, times: 5 }, ttl: 1000 },
|
|
|
318
348
|
});
|
|
319
349
|
```
|
|
320
350
|
|
|
351
|
+
```javascript
|
|
352
|
+
await pettyCache.mutex.lock('key', { retry: { interval: 100, times: 5 }, ttl: 1000 });
|
|
353
|
+
|
|
354
|
+
// We were able to acquire the lock. Do work and then unlock.
|
|
355
|
+
await pettyCache.mutex.unlock('key');
|
|
356
|
+
```
|
|
357
|
+
|
|
321
358
|
**Options**
|
|
322
359
|
|
|
323
360
|
```javascript
|
|
@@ -332,7 +369,7 @@ pettyCache.mutex.lock('key', { retry: { interval: 100, times: 5 }, ttl: 1000 },
|
|
|
332
369
|
|
|
333
370
|
### pettyCache.mutex.unlock(key, [callback])
|
|
334
371
|
|
|
335
|
-
Releases the distributed lock for the specified key.
|
|
372
|
+
Releases the distributed lock for the specified key. Supports both callbacks and promises.
|
|
336
373
|
|
|
337
374
|
```javascript
|
|
338
375
|
pettyCache.mutex.unlock('key', function(err) {
|
|
@@ -342,6 +379,10 @@ pettyCache.mutex.unlock('key', function(err) {
|
|
|
342
379
|
});
|
|
343
380
|
```
|
|
344
381
|
|
|
382
|
+
```javascript
|
|
383
|
+
await pettyCache.mutex.unlock('key');
|
|
384
|
+
```
|
|
385
|
+
|
|
345
386
|
## Semaphore
|
|
346
387
|
|
|
347
388
|
Provides a pool of distributed locks. Once a consumer acquires a lock they have the ability to release the lock back to the pool or mark the lock as "consumed" so that it's not used again.
|
|
@@ -471,6 +512,6 @@ pettyCache.semaphore.retrieveOrCreate('key', { size: 10 }, function(err) {
|
|
|
471
512
|
|
|
472
513
|
```javascript
|
|
473
514
|
{
|
|
474
|
-
size: 1 || function() {
|
|
515
|
+
size: 1 || function() { const x = 1 + 1; callback(null, x); } // The number of locks to create in the semaphore's pool. Optionally, size can be a `callback(err, size)` function.
|
|
475
516
|
}
|
|
476
517
|
```
|
package/eslint.config.js
CHANGED
|
@@ -4,30 +4,16 @@ const js = require('@eslint/js');
|
|
|
4
4
|
module.exports = [
|
|
5
5
|
js.configs.recommended,
|
|
6
6
|
{
|
|
7
|
-
ignores: ['node_modules/*'],
|
|
8
7
|
languageOptions: {
|
|
9
|
-
ecmaVersion: 2020,
|
|
10
|
-
sourceType: 'module',
|
|
11
|
-
parserOptions: {
|
|
12
|
-
ecmaFeatures: {
|
|
13
|
-
jsx: true
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
8
|
globals: {
|
|
17
|
-
...globals.
|
|
18
|
-
...globals.mocha,
|
|
19
|
-
...globals.node,
|
|
20
|
-
//added for 'fetch()' access
|
|
21
|
-
...globals.serviceworker
|
|
9
|
+
...globals.node
|
|
22
10
|
}
|
|
23
11
|
},
|
|
24
12
|
rules: {
|
|
25
13
|
'brace-style': ['error', '1tbs', { allowSingleLine: true }],
|
|
26
14
|
'comma-dangle': ['error', 'never'],
|
|
27
15
|
'dot-notation': 'error',
|
|
28
|
-
'no-array-constructor': 'error',
|
|
29
16
|
'no-console': 'error',
|
|
30
|
-
'no-fallthrough': 'off',
|
|
31
17
|
'no-inline-comments': 'warn',
|
|
32
18
|
'no-trailing-spaces': 'error',
|
|
33
19
|
'no-unused-vars': ['error', { caughtErrors: 'none' }],
|
package/index.js
CHANGED
|
@@ -18,16 +18,16 @@ function PettyCache() {
|
|
|
18
18
|
|
|
19
19
|
function bulkGetFromRedis(keys, callback) {
|
|
20
20
|
// Try to get values from Redis
|
|
21
|
-
redisClient.mget(keys,
|
|
21
|
+
redisClient.mget(keys, (err, data) => {
|
|
22
22
|
if (err) {
|
|
23
23
|
return callback(err);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const values = {};
|
|
27
27
|
|
|
28
|
-
for (
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
for (let i = 0; i < keys.length; i++) {
|
|
29
|
+
const key = keys[i];
|
|
30
|
+
const value = data[i];
|
|
31
31
|
|
|
32
32
|
if (value === null) {
|
|
33
33
|
values[key] = { exists: false };
|
|
@@ -61,7 +61,7 @@ function PettyCache() {
|
|
|
61
61
|
|
|
62
62
|
function getFromRedis(key, callback) {
|
|
63
63
|
// Try to get value from Redis
|
|
64
|
-
redisClient.get(key,
|
|
64
|
+
redisClient.get(key, (err, data) => {
|
|
65
65
|
if (err) {
|
|
66
66
|
return callback(err);
|
|
67
67
|
}
|
|
@@ -103,7 +103,7 @@ function PettyCache() {
|
|
|
103
103
|
/**
|
|
104
104
|
* @param {Array} keys - An array of keys.
|
|
105
105
|
*/
|
|
106
|
-
this.bulkFetch =
|
|
106
|
+
this.bulkFetch = (keys, func, options, callback) => {
|
|
107
107
|
// Options are optional
|
|
108
108
|
if (!callback) {
|
|
109
109
|
callback = options;
|
|
@@ -119,7 +119,7 @@ function PettyCache() {
|
|
|
119
119
|
const values = {};
|
|
120
120
|
|
|
121
121
|
// Try to get values from memory cache
|
|
122
|
-
for (
|
|
122
|
+
for (let i = _keys.length - 1; i >= 0; i--) {
|
|
123
123
|
const key = _keys[i];
|
|
124
124
|
const result = getFromMemoryCache(key);
|
|
125
125
|
|
|
@@ -137,12 +137,12 @@ function PettyCache() {
|
|
|
137
137
|
const _this = this;
|
|
138
138
|
|
|
139
139
|
// Try to get values from Redis
|
|
140
|
-
bulkGetFromRedis(_keys,
|
|
140
|
+
bulkGetFromRedis(_keys, (err, results) => {
|
|
141
141
|
if (err) {
|
|
142
142
|
return callback(err);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
for (
|
|
145
|
+
for (let i = _keys.length - 1; i >= 0; i--) {
|
|
146
146
|
const key = _keys[i];
|
|
147
147
|
const result = results[key];
|
|
148
148
|
|
|
@@ -161,7 +161,7 @@ function PettyCache() {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
// Execute the specified function for remaining keys
|
|
164
|
-
func(_keys,
|
|
164
|
+
func(_keys, (err, data) => {
|
|
165
165
|
if (err) {
|
|
166
166
|
return callback(err);
|
|
167
167
|
}
|
|
@@ -176,7 +176,7 @@ function PettyCache() {
|
|
|
176
176
|
/**
|
|
177
177
|
* @param {Array} keys - An array of keys.
|
|
178
178
|
*/
|
|
179
|
-
this.bulkGet =
|
|
179
|
+
this.bulkGet = (keys, callback) => {
|
|
180
180
|
// If there aren't any keys, return
|
|
181
181
|
if (!keys.length) {
|
|
182
182
|
return callback(null, {});
|
|
@@ -186,7 +186,7 @@ function PettyCache() {
|
|
|
186
186
|
const values = {};
|
|
187
187
|
|
|
188
188
|
// Try to get values from memory cache
|
|
189
|
-
for (
|
|
189
|
+
for (let i = _keys.length - 1; i >= 0; i--) {
|
|
190
190
|
const key = _keys[i];
|
|
191
191
|
const result = getFromMemoryCache(key);
|
|
192
192
|
|
|
@@ -202,14 +202,14 @@ function PettyCache() {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
// Try to get values from Redis
|
|
205
|
-
bulkGetFromRedis(_keys,
|
|
205
|
+
bulkGetFromRedis(_keys, (err, results) => {
|
|
206
206
|
if (err) {
|
|
207
207
|
return callback(err);
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
for (
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
for (let i = 0; i < _keys.length; i++) {
|
|
211
|
+
const key = _keys[i];
|
|
212
|
+
const result = results[key];
|
|
213
213
|
|
|
214
214
|
if (!result.exists) {
|
|
215
215
|
values[key] = null;
|
|
@@ -226,7 +226,7 @@ function PettyCache() {
|
|
|
226
226
|
});
|
|
227
227
|
};
|
|
228
228
|
|
|
229
|
-
this.bulkSet =
|
|
229
|
+
this.bulkSet = (values, options, callback) => {
|
|
230
230
|
// Options are optional
|
|
231
231
|
if (!callback) {
|
|
232
232
|
callback = options;
|
|
@@ -249,15 +249,15 @@ function PettyCache() {
|
|
|
249
249
|
batch.psetex(key, random(ttl.min, ttl.max), PettyCache.stringify(value));
|
|
250
250
|
});
|
|
251
251
|
|
|
252
|
-
batch.exec(
|
|
252
|
+
batch.exec((err) => {
|
|
253
253
|
callback(err);
|
|
254
254
|
});
|
|
255
255
|
};
|
|
256
256
|
|
|
257
|
-
this.del =
|
|
257
|
+
this.del = (key, callback) => {
|
|
258
258
|
const executor = () => {
|
|
259
259
|
return new Promise((resolve, reject) => {
|
|
260
|
-
redisClient.del(key,
|
|
260
|
+
redisClient.del(key, (err) => {
|
|
261
261
|
if (err) {
|
|
262
262
|
return reject(err);
|
|
263
263
|
}
|
|
@@ -277,7 +277,7 @@ function PettyCache() {
|
|
|
277
277
|
|
|
278
278
|
// Returns data from cache if available;
|
|
279
279
|
// otherwise executes the specified function and places the results in cache before returning the data.
|
|
280
|
-
this.fetch =
|
|
280
|
+
this.fetch = (key, func, options, callback) => {
|
|
281
281
|
options = options || {};
|
|
282
282
|
|
|
283
283
|
if (typeof options === 'function') {
|
|
@@ -286,10 +286,10 @@ function PettyCache() {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
// Default callback is a noop
|
|
289
|
-
callback = callback ||
|
|
289
|
+
callback = callback || (() => {});
|
|
290
290
|
|
|
291
291
|
// Try to get value from memory cache
|
|
292
|
-
|
|
292
|
+
let result = getFromMemoryCache(key);
|
|
293
293
|
|
|
294
294
|
// Return value from memory cache if it exists
|
|
295
295
|
if (result.exists) {
|
|
@@ -299,8 +299,8 @@ function PettyCache() {
|
|
|
299
299
|
const _this = this;
|
|
300
300
|
|
|
301
301
|
// Double-checked locking: http://en.wikipedia.org/wiki/Double-checked_locking
|
|
302
|
-
lock(`fetch-memory-cache-lock-${key}`,
|
|
303
|
-
async.reflect(
|
|
302
|
+
lock(`fetch-memory-cache-lock-${key}`, (releaseMemoryCacheLock) => {
|
|
303
|
+
async.reflect((callback) => {
|
|
304
304
|
// Try to get value from memory cache
|
|
305
305
|
result = getFromMemoryCache(key);
|
|
306
306
|
|
|
@@ -310,7 +310,7 @@ function PettyCache() {
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
// Try to get value from Redis
|
|
313
|
-
getFromRedis(key,
|
|
313
|
+
getFromRedis(key, (err, result) => {
|
|
314
314
|
if (err) {
|
|
315
315
|
return callback(err);
|
|
316
316
|
}
|
|
@@ -322,8 +322,8 @@ function PettyCache() {
|
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
// Double-checked locking: http://en.wikipedia.org/wiki/Double-checked_locking
|
|
325
|
-
lock(`fetch-redis-lock-${key}`,
|
|
326
|
-
async.reflect(
|
|
325
|
+
lock(`fetch-redis-lock-${key}`, (releaseRedisLock) => {
|
|
326
|
+
async.reflect((callback) => {
|
|
327
327
|
// Try to get value from memory cache
|
|
328
328
|
result = getFromMemoryCache(key);
|
|
329
329
|
|
|
@@ -333,7 +333,7 @@ function PettyCache() {
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
// Try to get value from Redis
|
|
336
|
-
getFromRedis(key, async
|
|
336
|
+
getFromRedis(key, async (err, result) => {
|
|
337
337
|
if (err) {
|
|
338
338
|
return callback(err);
|
|
339
339
|
}
|
|
@@ -350,7 +350,7 @@ function PettyCache() {
|
|
|
350
350
|
try {
|
|
351
351
|
const data = await func();
|
|
352
352
|
|
|
353
|
-
_this.set(key, data, options,
|
|
353
|
+
_this.set(key, data, options, (err) => {
|
|
354
354
|
callback(err, data);
|
|
355
355
|
});
|
|
356
356
|
} catch(err) {
|
|
@@ -358,18 +358,18 @@ function PettyCache() {
|
|
|
358
358
|
}
|
|
359
359
|
} else {
|
|
360
360
|
// If the function has arguments, there was a callback provided
|
|
361
|
-
func(
|
|
361
|
+
func((err, data) => {
|
|
362
362
|
if (err) {
|
|
363
363
|
return callback(err);
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
-
_this.set(key, data, options,
|
|
366
|
+
_this.set(key, data, options, (err) => {
|
|
367
367
|
callback(err, data);
|
|
368
368
|
});
|
|
369
369
|
});
|
|
370
370
|
}
|
|
371
371
|
});
|
|
372
|
-
})(releaseRedisLock(
|
|
372
|
+
})(releaseRedisLock((err, result) => {
|
|
373
373
|
if (result.error) {
|
|
374
374
|
return callback(result.error);
|
|
375
375
|
}
|
|
@@ -378,7 +378,7 @@ function PettyCache() {
|
|
|
378
378
|
}));
|
|
379
379
|
});
|
|
380
380
|
});
|
|
381
|
-
})(releaseMemoryCacheLock(
|
|
381
|
+
})(releaseMemoryCacheLock((err, result) => {
|
|
382
382
|
if (result.error) {
|
|
383
383
|
return callback(result.error);
|
|
384
384
|
}
|
|
@@ -388,7 +388,7 @@ function PettyCache() {
|
|
|
388
388
|
});
|
|
389
389
|
};
|
|
390
390
|
|
|
391
|
-
this.fetchAndRefresh =
|
|
391
|
+
this.fetchAndRefresh = (key, func, options, callback) => {
|
|
392
392
|
options = options || {};
|
|
393
393
|
|
|
394
394
|
if (typeof options === 'function') {
|
|
@@ -400,22 +400,22 @@ function PettyCache() {
|
|
|
400
400
|
const ttl = getTtl(options);
|
|
401
401
|
|
|
402
402
|
// Default callback is a noop
|
|
403
|
-
callback = callback ||
|
|
403
|
+
callback = callback || (() => {});
|
|
404
404
|
|
|
405
405
|
const _this = this;
|
|
406
406
|
|
|
407
407
|
if (!intervals[key]) {
|
|
408
408
|
const delay = ttl.min / 2;
|
|
409
409
|
|
|
410
|
-
intervals[key] = setInterval(
|
|
410
|
+
intervals[key] = setInterval(() => {
|
|
411
411
|
// This distributed lock prevents multiple clients from executing func at the same time
|
|
412
|
-
_this.mutex.lock(`interval-${key}`, { ttl: delay - 100 },
|
|
412
|
+
_this.mutex.lock(`interval-${key}`, { ttl: delay - 100 }, (err) => {
|
|
413
413
|
if (err) {
|
|
414
414
|
return;
|
|
415
415
|
}
|
|
416
416
|
|
|
417
417
|
// Execute the specified function and update cache
|
|
418
|
-
func(
|
|
418
|
+
func((err, data) => {
|
|
419
419
|
if (err) {
|
|
420
420
|
return;
|
|
421
421
|
}
|
|
@@ -429,46 +429,56 @@ function PettyCache() {
|
|
|
429
429
|
this.fetch(key, func, options, callback);
|
|
430
430
|
};
|
|
431
431
|
|
|
432
|
-
this.get =
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
// Return value from memory cache if it exists
|
|
437
|
-
if (result.exists) {
|
|
438
|
-
return callback(null, result.value);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Double-checked locking: http://en.wikipedia.org/wiki/Double-checked_locking
|
|
442
|
-
lock(`get-memory-cache-lock-${key}`, function(releaseMemoryCacheLock) {
|
|
443
|
-
async.reflect(function(callback) {
|
|
432
|
+
this.get = (key, callback) => {
|
|
433
|
+
const executor = () => {
|
|
434
|
+
return new Promise((resolve, reject) => {
|
|
444
435
|
// Try to get value from memory cache
|
|
445
|
-
result = getFromMemoryCache(key);
|
|
436
|
+
let result = getFromMemoryCache(key);
|
|
446
437
|
|
|
447
438
|
// Return value from memory cache if it exists
|
|
448
439
|
if (result.exists) {
|
|
449
|
-
return
|
|
440
|
+
return resolve(result.value);
|
|
450
441
|
}
|
|
451
442
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
443
|
+
// Double-checked locking: http://en.wikipedia.org/wiki/Double-checked_locking
|
|
444
|
+
lock(`get-memory-cache-lock-${key}`, (releaseMemoryCacheLock) => {
|
|
445
|
+
async.reflect((callback) => {
|
|
446
|
+
// Try to get value from memory cache
|
|
447
|
+
result = getFromMemoryCache(key);
|
|
456
448
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
449
|
+
// Return value from memory cache if it exists
|
|
450
|
+
if (result.exists) {
|
|
451
|
+
return callback(null, result.value);
|
|
452
|
+
}
|
|
460
453
|
|
|
461
|
-
|
|
462
|
-
|
|
454
|
+
getFromRedis(key, (err, result) => {
|
|
455
|
+
if (err) {
|
|
456
|
+
return callback(err);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (!result.exists) {
|
|
460
|
+
return callback(null, null);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
memoryCache.put(key, result.value, random(2000, 5000));
|
|
464
|
+
callback(null, result.value);
|
|
465
|
+
});
|
|
466
|
+
})(releaseMemoryCacheLock((err, result) => {
|
|
467
|
+
if (result.error) {
|
|
468
|
+
return reject(result.error);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
resolve(result.value);
|
|
472
|
+
}));
|
|
463
473
|
});
|
|
464
|
-
})
|
|
465
|
-
|
|
466
|
-
return callback(result.error);
|
|
467
|
-
}
|
|
474
|
+
});
|
|
475
|
+
};
|
|
468
476
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
}
|
|
477
|
+
if (callback) {
|
|
478
|
+
executor().then(result => callback(null, result)).catch(callback);
|
|
479
|
+
} else {
|
|
480
|
+
return executor();
|
|
481
|
+
}
|
|
472
482
|
};
|
|
473
483
|
|
|
474
484
|
this.mutex = {
|
|
@@ -489,7 +499,7 @@ function PettyCache() {
|
|
|
489
499
|
const executor = () => {
|
|
490
500
|
return new Promise((resolve, reject) => {
|
|
491
501
|
async.retry({ interval: options.retry.interval, times: options.retry.times }, callback => {
|
|
492
|
-
redisClient.set(key, '1', 'NX', 'PX', options.ttl,
|
|
502
|
+
redisClient.set(key, '1', 'NX', 'PX', options.ttl, (err, res) => {
|
|
493
503
|
if (err) {
|
|
494
504
|
return callback(err);
|
|
495
505
|
}
|
|
@@ -504,7 +514,7 @@ function PettyCache() {
|
|
|
504
514
|
|
|
505
515
|
callback();
|
|
506
516
|
});
|
|
507
|
-
},
|
|
517
|
+
}, (err) => {
|
|
508
518
|
if (err) {
|
|
509
519
|
return reject(err);
|
|
510
520
|
}
|
|
@@ -523,7 +533,7 @@ function PettyCache() {
|
|
|
523
533
|
unlock: (key, callback) => {
|
|
524
534
|
const executor = () => {
|
|
525
535
|
return new Promise((resolve, reject) => {
|
|
526
|
-
redisClient.del(key,
|
|
536
|
+
redisClient.del(key, (err) => {
|
|
527
537
|
if (err) {
|
|
528
538
|
return reject(err);
|
|
529
539
|
}
|
|
@@ -541,33 +551,51 @@ function PettyCache() {
|
|
|
541
551
|
}
|
|
542
552
|
};
|
|
543
553
|
|
|
544
|
-
this.patch =
|
|
545
|
-
if (!callback) {
|
|
554
|
+
this.patch = (key, value, options, callback) => {
|
|
555
|
+
if (!callback && typeof options === 'function') {
|
|
546
556
|
callback = options;
|
|
547
557
|
options = {};
|
|
548
558
|
}
|
|
549
559
|
|
|
560
|
+
options = options || {};
|
|
561
|
+
|
|
550
562
|
const _this = this;
|
|
551
563
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
564
|
+
const executor = () => {
|
|
565
|
+
return new Promise((resolve, reject) => {
|
|
566
|
+
_this.get(key, (err, data) => {
|
|
567
|
+
if (err) {
|
|
568
|
+
return reject(err);
|
|
569
|
+
}
|
|
556
570
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
571
|
+
if (!data) {
|
|
572
|
+
return reject(new Error(`Key ${key} does not exist`));
|
|
573
|
+
}
|
|
560
574
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
575
|
+
for (let k in value) {
|
|
576
|
+
data[k] = value[k];
|
|
577
|
+
}
|
|
564
578
|
|
|
565
|
-
|
|
566
|
-
|
|
579
|
+
_this.set(key, data, options, (err) => {
|
|
580
|
+
if (err) {
|
|
581
|
+
return reject(err);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
resolve();
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
if (callback) {
|
|
591
|
+
executor().then(result => callback(null, result)).catch(callback);
|
|
592
|
+
} else {
|
|
593
|
+
return executor();
|
|
594
|
+
}
|
|
567
595
|
};
|
|
568
596
|
|
|
569
597
|
this.semaphore = {
|
|
570
|
-
acquireLock:
|
|
598
|
+
acquireLock: (key, options, callback) => {
|
|
571
599
|
// Options are optional
|
|
572
600
|
if (!callback && typeof options === 'function') {
|
|
573
601
|
callback = options;
|
|
@@ -583,14 +611,14 @@ function PettyCache() {
|
|
|
583
611
|
|
|
584
612
|
const _this = this;
|
|
585
613
|
|
|
586
|
-
async.retry({ interval: options.retry.interval, times: options.retry.times },
|
|
614
|
+
async.retry({ interval: options.retry.interval, times: options.retry.times }, (callback) => {
|
|
587
615
|
// Mutex lock around semaphore
|
|
588
|
-
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } },
|
|
616
|
+
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } }, (err) => {
|
|
589
617
|
if (err) {
|
|
590
618
|
return callback(err);
|
|
591
619
|
}
|
|
592
620
|
|
|
593
|
-
redisClient.get(key,
|
|
621
|
+
redisClient.get(key, (err, data) => {
|
|
594
622
|
// If we encountered an error, unlock the mutex lock and return error
|
|
595
623
|
if (err) {
|
|
596
624
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
@@ -601,10 +629,10 @@ function PettyCache() {
|
|
|
601
629
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(new Error(`Semaphore ${key} doesn't exist.`)); });
|
|
602
630
|
}
|
|
603
631
|
|
|
604
|
-
|
|
632
|
+
const pool = JSON.parse(data);
|
|
605
633
|
|
|
606
634
|
// Try to find a slot that's available.
|
|
607
|
-
|
|
635
|
+
let index = pool.findIndex(s => s.status === 'available');
|
|
608
636
|
|
|
609
637
|
if (index === -1) {
|
|
610
638
|
index = pool.findIndex(s => s.ttl <= Date.now());
|
|
@@ -617,7 +645,7 @@ function PettyCache() {
|
|
|
617
645
|
|
|
618
646
|
pool[index] = { status: 'acquired', ttl: Date.now() + options.ttl };
|
|
619
647
|
|
|
620
|
-
redisClient.set(key, JSON.stringify(pool),
|
|
648
|
+
redisClient.set(key, JSON.stringify(pool), (err) => {
|
|
621
649
|
if (err) {
|
|
622
650
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
623
651
|
}
|
|
@@ -628,18 +656,18 @@ function PettyCache() {
|
|
|
628
656
|
});
|
|
629
657
|
}, callback);
|
|
630
658
|
},
|
|
631
|
-
consumeLock:
|
|
632
|
-
callback = callback ||
|
|
659
|
+
consumeLock: (key, index, callback) => {
|
|
660
|
+
callback = callback || (() => {});
|
|
633
661
|
|
|
634
662
|
const _this = this;
|
|
635
663
|
|
|
636
664
|
// Mutex lock around semaphore
|
|
637
|
-
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } },
|
|
665
|
+
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } }, (err) => {
|
|
638
666
|
if (err) {
|
|
639
667
|
return callback(err);
|
|
640
668
|
}
|
|
641
669
|
|
|
642
|
-
redisClient.get(key,
|
|
670
|
+
redisClient.get(key, (err, data) => {
|
|
643
671
|
// If we encountered an error, unlock the mutex lock and return error
|
|
644
672
|
if (err) {
|
|
645
673
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
@@ -650,7 +678,7 @@ function PettyCache() {
|
|
|
650
678
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(new Error(`Semaphore ${key} doesn't exist.`)); });
|
|
651
679
|
}
|
|
652
680
|
|
|
653
|
-
|
|
681
|
+
const pool = JSON.parse(data);
|
|
654
682
|
|
|
655
683
|
// Ensure index exists.
|
|
656
684
|
if (pool.length <= index) {
|
|
@@ -664,7 +692,7 @@ function PettyCache() {
|
|
|
664
692
|
pool[index] = { status: 'available' };
|
|
665
693
|
}
|
|
666
694
|
|
|
667
|
-
redisClient.set(key, JSON.stringify(pool),
|
|
695
|
+
redisClient.set(key, JSON.stringify(pool), (err) => {
|
|
668
696
|
if (err) {
|
|
669
697
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
670
698
|
}
|
|
@@ -674,17 +702,17 @@ function PettyCache() {
|
|
|
674
702
|
});
|
|
675
703
|
});
|
|
676
704
|
},
|
|
677
|
-
expand:
|
|
678
|
-
callback = callback ||
|
|
705
|
+
expand: (key, size, callback) => {
|
|
706
|
+
callback = callback || (() => {});
|
|
679
707
|
|
|
680
708
|
const _this = this;
|
|
681
709
|
|
|
682
|
-
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } },
|
|
710
|
+
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } }, (err) => {
|
|
683
711
|
if (err) {
|
|
684
712
|
return callback(err);
|
|
685
713
|
}
|
|
686
714
|
|
|
687
|
-
redisClient.get(key,
|
|
715
|
+
redisClient.get(key, (err, data) => {
|
|
688
716
|
// If we encountered an error, unlock the mutex lock and return error
|
|
689
717
|
if (err) {
|
|
690
718
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
@@ -695,7 +723,7 @@ function PettyCache() {
|
|
|
695
723
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(new Error(`Semaphore ${key} doesn't exist.`)); });
|
|
696
724
|
}
|
|
697
725
|
|
|
698
|
-
|
|
726
|
+
let pool = JSON.parse(data);
|
|
699
727
|
|
|
700
728
|
if (pool.length > size) {
|
|
701
729
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(new Error(`Cannot shrink pool, size is ${pool.length} and you requested a size of ${size}.`)); });
|
|
@@ -707,7 +735,7 @@ function PettyCache() {
|
|
|
707
735
|
|
|
708
736
|
pool = pool.concat(Array(size - pool.length).fill({ status: 'available' }));
|
|
709
737
|
|
|
710
|
-
redisClient.set(key, JSON.stringify(pool),
|
|
738
|
+
redisClient.set(key, JSON.stringify(pool), (err) => {
|
|
711
739
|
if (err) {
|
|
712
740
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
713
741
|
}
|
|
@@ -717,18 +745,18 @@ function PettyCache() {
|
|
|
717
745
|
});
|
|
718
746
|
});
|
|
719
747
|
},
|
|
720
|
-
releaseLock:
|
|
721
|
-
callback = callback ||
|
|
748
|
+
releaseLock: (key, index, callback) => {
|
|
749
|
+
callback = callback || (() => {});
|
|
722
750
|
|
|
723
751
|
const _this = this;
|
|
724
752
|
|
|
725
753
|
// Mutex lock around semaphore
|
|
726
|
-
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } },
|
|
754
|
+
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } }, (err) => {
|
|
727
755
|
if (err) {
|
|
728
756
|
return callback(err);
|
|
729
757
|
}
|
|
730
758
|
|
|
731
|
-
redisClient.get(key,
|
|
759
|
+
redisClient.get(key, (err, data) => {
|
|
732
760
|
// If we encountered an error, unlock the mutex lock and return error
|
|
733
761
|
if (err) {
|
|
734
762
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
@@ -739,7 +767,7 @@ function PettyCache() {
|
|
|
739
767
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(new Error(`Semaphore ${key} doesn't exist.`)); });
|
|
740
768
|
}
|
|
741
769
|
|
|
742
|
-
|
|
770
|
+
const pool = JSON.parse(data);
|
|
743
771
|
|
|
744
772
|
// Ensure index exists.
|
|
745
773
|
if (pool.length <= index) {
|
|
@@ -748,7 +776,7 @@ function PettyCache() {
|
|
|
748
776
|
|
|
749
777
|
pool[index] = { status: 'available' };
|
|
750
778
|
|
|
751
|
-
redisClient.set(key, JSON.stringify(pool),
|
|
779
|
+
redisClient.set(key, JSON.stringify(pool), (err) => {
|
|
752
780
|
if (err) {
|
|
753
781
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
754
782
|
}
|
|
@@ -758,19 +786,19 @@ function PettyCache() {
|
|
|
758
786
|
});
|
|
759
787
|
});
|
|
760
788
|
},
|
|
761
|
-
reset:
|
|
762
|
-
callback = callback ||
|
|
789
|
+
reset: (key, callback) => {
|
|
790
|
+
callback = callback || (() => {});
|
|
763
791
|
|
|
764
792
|
const _this = this;
|
|
765
793
|
|
|
766
794
|
// Mutex lock around semaphore
|
|
767
|
-
this.mutex.lock(`lock:${key}`, { retry: { times: 100 } },
|
|
795
|
+
this.mutex.lock(`lock:${key}`, { retry: { times: 100 } }, (err) => {
|
|
768
796
|
if (err) {
|
|
769
797
|
return callback(err);
|
|
770
798
|
}
|
|
771
799
|
|
|
772
800
|
// Try to get previously created semaphore
|
|
773
|
-
redisClient.get(key,
|
|
801
|
+
redisClient.get(key, (err, data) => {
|
|
774
802
|
// If we encountered an error, unlock the mutex lock and return error
|
|
775
803
|
if (err) {
|
|
776
804
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
@@ -781,10 +809,10 @@ function PettyCache() {
|
|
|
781
809
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(new Error(`Semaphore ${key} doesn't exist.`)); });
|
|
782
810
|
}
|
|
783
811
|
|
|
784
|
-
|
|
812
|
+
let pool = JSON.parse(data);
|
|
785
813
|
pool = Array(pool.length).fill({ status: 'available' });
|
|
786
814
|
|
|
787
|
-
redisClient.set(key, JSON.stringify(pool),
|
|
815
|
+
redisClient.set(key, JSON.stringify(pool), (err) => {
|
|
788
816
|
if (err) {
|
|
789
817
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
790
818
|
}
|
|
@@ -794,26 +822,26 @@ function PettyCache() {
|
|
|
794
822
|
});
|
|
795
823
|
});
|
|
796
824
|
},
|
|
797
|
-
retrieveOrCreate:
|
|
825
|
+
retrieveOrCreate: (key, options, callback) => {
|
|
798
826
|
// Options are optional
|
|
799
827
|
if (!callback && typeof options === 'function') {
|
|
800
828
|
callback = options;
|
|
801
829
|
options = {};
|
|
802
830
|
}
|
|
803
831
|
|
|
804
|
-
callback = callback ||
|
|
832
|
+
callback = callback || (() => {});
|
|
805
833
|
options = options || {};
|
|
806
834
|
|
|
807
835
|
const _this = this;
|
|
808
836
|
|
|
809
837
|
// Mutex lock around semaphore retrival or creation
|
|
810
|
-
this.mutex.lock(`lock:${key}`, { retry: { times: 100 } },
|
|
838
|
+
this.mutex.lock(`lock:${key}`, { retry: { times: 100 } }, (err) => {
|
|
811
839
|
if (err) {
|
|
812
840
|
return callback(err);
|
|
813
841
|
}
|
|
814
842
|
|
|
815
843
|
// Try to get previously created semaphore
|
|
816
|
-
redisClient.get(key,
|
|
844
|
+
redisClient.get(key, (err, data) => {
|
|
817
845
|
// If we encountered an error, unlock the mutex lock and return error
|
|
818
846
|
if (err) {
|
|
819
847
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
@@ -824,7 +852,7 @@ function PettyCache() {
|
|
|
824
852
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(null, JSON.parse(data)); });
|
|
825
853
|
}
|
|
826
854
|
|
|
827
|
-
|
|
855
|
+
const getSize = (callback) => {
|
|
828
856
|
if (typeof options.size === 'function') {
|
|
829
857
|
return options.size(callback);
|
|
830
858
|
}
|
|
@@ -832,15 +860,15 @@ function PettyCache() {
|
|
|
832
860
|
callback(null, Object.prototype.hasOwnProperty.call(options, 'size') ? options.size : 1);
|
|
833
861
|
};
|
|
834
862
|
|
|
835
|
-
getSize(
|
|
863
|
+
getSize((err, size) => {
|
|
836
864
|
// If we encountered an error, unlock the mutex lock and return error
|
|
837
865
|
if (err) {
|
|
838
866
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
839
867
|
}
|
|
840
868
|
|
|
841
|
-
|
|
869
|
+
const pool = Array(Math.max(size, 1)).fill({ status: 'available' });
|
|
842
870
|
|
|
843
|
-
redisClient.set(key, JSON.stringify(pool),
|
|
871
|
+
redisClient.set(key, JSON.stringify(pool), (err) => {
|
|
844
872
|
if (err) {
|
|
845
873
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
846
874
|
}
|
|
@@ -853,7 +881,7 @@ function PettyCache() {
|
|
|
853
881
|
}
|
|
854
882
|
};
|
|
855
883
|
|
|
856
|
-
this.set =
|
|
884
|
+
this.set = (key, value, options, callback) => {
|
|
857
885
|
options = options || {};
|
|
858
886
|
|
|
859
887
|
if (typeof options === 'function') {
|
|
@@ -864,18 +892,31 @@ function PettyCache() {
|
|
|
864
892
|
// Get TTL based on specified options
|
|
865
893
|
const ttl = getTtl(options);
|
|
866
894
|
|
|
867
|
-
|
|
868
|
-
|
|
895
|
+
const executor = () => {
|
|
896
|
+
return new Promise((resolve, reject) => {
|
|
897
|
+
// Store value in memory cache with a short expiration
|
|
898
|
+
memoryCache.put(key, value, random(2000, 5000));
|
|
869
899
|
|
|
870
|
-
|
|
871
|
-
|
|
900
|
+
// Store value in Redis
|
|
901
|
+
redisClient.psetex(key, random(ttl.min, ttl.max), PettyCache.stringify(value), (err) => {
|
|
902
|
+
if (err) {
|
|
903
|
+
return reject(err);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
resolve();
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
};
|
|
872
910
|
|
|
873
|
-
|
|
874
|
-
|
|
911
|
+
if (callback) {
|
|
912
|
+
executor().then(result => callback(null, result)).catch(callback);
|
|
913
|
+
} else {
|
|
914
|
+
return executor();
|
|
915
|
+
}
|
|
875
916
|
};
|
|
876
917
|
|
|
877
918
|
// Semaphore functions need to be bound to the main PettyCache object
|
|
878
|
-
for (
|
|
919
|
+
for (const method in this.semaphore) {
|
|
879
920
|
this.semaphore[method] = this.semaphore[method].bind(this);
|
|
880
921
|
}
|
|
881
922
|
}
|
|
@@ -888,8 +929,8 @@ function random(min, max) {
|
|
|
888
929
|
return Math.floor(Math.random() * (max - min + 1) + min);
|
|
889
930
|
}
|
|
890
931
|
|
|
891
|
-
PettyCache.parse =
|
|
892
|
-
return JSON.parse(text,
|
|
932
|
+
PettyCache.parse = (text) => {
|
|
933
|
+
return JSON.parse(text, (k, v) => {
|
|
893
934
|
if (v === '__NaN') {
|
|
894
935
|
return NaN;
|
|
895
936
|
} else if (v === '__null') {
|
|
@@ -902,8 +943,8 @@ PettyCache.parse = function(text) {
|
|
|
902
943
|
});
|
|
903
944
|
};
|
|
904
945
|
|
|
905
|
-
PettyCache.stringify =
|
|
906
|
-
return JSON.stringify(value,
|
|
946
|
+
PettyCache.stringify = (value) => {
|
|
947
|
+
return JSON.stringify(value, (k, v) => {
|
|
907
948
|
if (typeof v === 'number' && isNaN(v)) {
|
|
908
949
|
return '__NaN';
|
|
909
950
|
} else if (v === null) {
|
package/package.json
CHANGED
|
@@ -1,36 +1,30 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
},
|
|
31
|
-
"repository": {
|
|
32
|
-
"type": "git",
|
|
33
|
-
"url": "https://github.com/mediocre/petty-cache.git"
|
|
34
|
-
},
|
|
35
|
-
"version": "3.5.0"
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"async": "~3.2.6",
|
|
4
|
+
"lock": "~1.1.0",
|
|
5
|
+
"memory-cache": "~0.2.0",
|
|
6
|
+
"redis": "~3.1.0"
|
|
7
|
+
},
|
|
8
|
+
"description": "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 some extra features to avoid cache stampedes and thundering herds.",
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@eslint/js": "*",
|
|
11
|
+
"globals": "*"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/stores-com/petty-cache",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"cache",
|
|
16
|
+
"lock",
|
|
17
|
+
"mutex",
|
|
18
|
+
"redis",
|
|
19
|
+
"semaphore"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"main": "index.js",
|
|
23
|
+
"name": "petty-cache",
|
|
24
|
+
"repository": "https://github.com/stores-com/petty-cache",
|
|
25
|
+
"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"
|
|
28
|
+
},
|
|
29
|
+
"version": "3.6.0"
|
|
36
30
|
}
|