petty-cache 3.4.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 +8 -0
- package/LICENSE +21 -201
- package/README.md +56 -15
- package/eslint.config.js +30 -0
- package/index.js +195 -143
- package/package.json +28 -32
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,14 @@ 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
|
+
|
|
13
|
+
## [3.5.0] - 2025-05-01
|
|
14
|
+
### Changed
|
|
15
|
+
- Added the ability for `pettyCache.del` functions to support callbacks and promises.
|
|
16
|
+
|
|
9
17
|
## [3.4.0] - 2025-02-21
|
|
10
18
|
### Changed
|
|
11
19
|
- Added the ability for `pettyCache.mutex` 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
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const globals = require('globals');
|
|
2
|
+
const js = require('@eslint/js');
|
|
3
|
+
|
|
4
|
+
module.exports = [
|
|
5
|
+
js.configs.recommended,
|
|
6
|
+
{
|
|
7
|
+
languageOptions: {
|
|
8
|
+
globals: {
|
|
9
|
+
...globals.node
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
rules: {
|
|
13
|
+
'brace-style': ['error', '1tbs', { allowSingleLine: true }],
|
|
14
|
+
'comma-dangle': ['error', 'never'],
|
|
15
|
+
'dot-notation': 'error',
|
|
16
|
+
'no-console': 'error',
|
|
17
|
+
'no-inline-comments': 'warn',
|
|
18
|
+
'no-trailing-spaces': 'error',
|
|
19
|
+
'no-unused-vars': ['error', { caughtErrors: 'none' }],
|
|
20
|
+
'object-curly-spacing': ['error', 'always'],
|
|
21
|
+
quotes: ['error', 'single'],
|
|
22
|
+
semi: ['error', 'always'],
|
|
23
|
+
'space-before-function-paren': ['error', {
|
|
24
|
+
anonymous: 'never',
|
|
25
|
+
named: 'never',
|
|
26
|
+
asyncArrow: 'always'
|
|
27
|
+
}]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
];
|
package/index.js
CHANGED
|
@@ -13,20 +13,21 @@ function PettyCache() {
|
|
|
13
13
|
redisClient = redis.createClient(...arguments);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
//eslint-disable-next-line no-console
|
|
16
17
|
redisClient.on('error', err => console.warn(`Warning: Redis reported a client error: ${err}`));
|
|
17
18
|
|
|
18
19
|
function bulkGetFromRedis(keys, callback) {
|
|
19
20
|
// Try to get values from Redis
|
|
20
|
-
redisClient.mget(keys,
|
|
21
|
+
redisClient.mget(keys, (err, data) => {
|
|
21
22
|
if (err) {
|
|
22
23
|
return callback(err);
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const values = {};
|
|
26
27
|
|
|
27
|
-
for (
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
for (let i = 0; i < keys.length; i++) {
|
|
29
|
+
const key = keys[i];
|
|
30
|
+
const value = data[i];
|
|
30
31
|
|
|
31
32
|
if (value === null) {
|
|
32
33
|
values[key] = { exists: false };
|
|
@@ -60,7 +61,7 @@ function PettyCache() {
|
|
|
60
61
|
|
|
61
62
|
function getFromRedis(key, callback) {
|
|
62
63
|
// Try to get value from Redis
|
|
63
|
-
redisClient.get(key,
|
|
64
|
+
redisClient.get(key, (err, data) => {
|
|
64
65
|
if (err) {
|
|
65
66
|
return callback(err);
|
|
66
67
|
}
|
|
@@ -102,7 +103,7 @@ function PettyCache() {
|
|
|
102
103
|
/**
|
|
103
104
|
* @param {Array} keys - An array of keys.
|
|
104
105
|
*/
|
|
105
|
-
this.bulkFetch =
|
|
106
|
+
this.bulkFetch = (keys, func, options, callback) => {
|
|
106
107
|
// Options are optional
|
|
107
108
|
if (!callback) {
|
|
108
109
|
callback = options;
|
|
@@ -118,7 +119,7 @@ function PettyCache() {
|
|
|
118
119
|
const values = {};
|
|
119
120
|
|
|
120
121
|
// Try to get values from memory cache
|
|
121
|
-
for (
|
|
122
|
+
for (let i = _keys.length - 1; i >= 0; i--) {
|
|
122
123
|
const key = _keys[i];
|
|
123
124
|
const result = getFromMemoryCache(key);
|
|
124
125
|
|
|
@@ -136,12 +137,12 @@ function PettyCache() {
|
|
|
136
137
|
const _this = this;
|
|
137
138
|
|
|
138
139
|
// Try to get values from Redis
|
|
139
|
-
bulkGetFromRedis(_keys,
|
|
140
|
+
bulkGetFromRedis(_keys, (err, results) => {
|
|
140
141
|
if (err) {
|
|
141
142
|
return callback(err);
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
for (
|
|
145
|
+
for (let i = _keys.length - 1; i >= 0; i--) {
|
|
145
146
|
const key = _keys[i];
|
|
146
147
|
const result = results[key];
|
|
147
148
|
|
|
@@ -160,7 +161,7 @@ function PettyCache() {
|
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
// Execute the specified function for remaining keys
|
|
163
|
-
func(_keys,
|
|
164
|
+
func(_keys, (err, data) => {
|
|
164
165
|
if (err) {
|
|
165
166
|
return callback(err);
|
|
166
167
|
}
|
|
@@ -175,7 +176,7 @@ function PettyCache() {
|
|
|
175
176
|
/**
|
|
176
177
|
* @param {Array} keys - An array of keys.
|
|
177
178
|
*/
|
|
178
|
-
this.bulkGet =
|
|
179
|
+
this.bulkGet = (keys, callback) => {
|
|
179
180
|
// If there aren't any keys, return
|
|
180
181
|
if (!keys.length) {
|
|
181
182
|
return callback(null, {});
|
|
@@ -185,7 +186,7 @@ function PettyCache() {
|
|
|
185
186
|
const values = {};
|
|
186
187
|
|
|
187
188
|
// Try to get values from memory cache
|
|
188
|
-
for (
|
|
189
|
+
for (let i = _keys.length - 1; i >= 0; i--) {
|
|
189
190
|
const key = _keys[i];
|
|
190
191
|
const result = getFromMemoryCache(key);
|
|
191
192
|
|
|
@@ -201,14 +202,14 @@ function PettyCache() {
|
|
|
201
202
|
}
|
|
202
203
|
|
|
203
204
|
// Try to get values from Redis
|
|
204
|
-
bulkGetFromRedis(_keys,
|
|
205
|
+
bulkGetFromRedis(_keys, (err, results) => {
|
|
205
206
|
if (err) {
|
|
206
207
|
return callback(err);
|
|
207
208
|
}
|
|
208
209
|
|
|
209
|
-
for (
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
for (let i = 0; i < _keys.length; i++) {
|
|
211
|
+
const key = _keys[i];
|
|
212
|
+
const result = results[key];
|
|
212
213
|
|
|
213
214
|
if (!result.exists) {
|
|
214
215
|
values[key] = null;
|
|
@@ -225,7 +226,7 @@ function PettyCache() {
|
|
|
225
226
|
});
|
|
226
227
|
};
|
|
227
228
|
|
|
228
|
-
this.bulkSet =
|
|
229
|
+
this.bulkSet = (values, options, callback) => {
|
|
229
230
|
// Options are optional
|
|
230
231
|
if (!callback) {
|
|
231
232
|
callback = options;
|
|
@@ -248,25 +249,35 @@ function PettyCache() {
|
|
|
248
249
|
batch.psetex(key, random(ttl.min, ttl.max), PettyCache.stringify(value));
|
|
249
250
|
});
|
|
250
251
|
|
|
251
|
-
batch.exec(
|
|
252
|
+
batch.exec((err) => {
|
|
252
253
|
callback(err);
|
|
253
254
|
});
|
|
254
255
|
};
|
|
255
256
|
|
|
256
|
-
this.del =
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
257
|
+
this.del = (key, callback) => {
|
|
258
|
+
const executor = () => {
|
|
259
|
+
return new Promise((resolve, reject) => {
|
|
260
|
+
redisClient.del(key, (err) => {
|
|
261
|
+
if (err) {
|
|
262
|
+
return reject(err);
|
|
263
|
+
}
|
|
261
264
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
+
memoryCache.del(key);
|
|
266
|
+
resolve();
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
if (callback) {
|
|
272
|
+
executor().then(result => callback(null, result)).catch(callback);
|
|
273
|
+
} else {
|
|
274
|
+
return executor();
|
|
275
|
+
}
|
|
265
276
|
};
|
|
266
277
|
|
|
267
278
|
// Returns data from cache if available;
|
|
268
279
|
// otherwise executes the specified function and places the results in cache before returning the data.
|
|
269
|
-
this.fetch =
|
|
280
|
+
this.fetch = (key, func, options, callback) => {
|
|
270
281
|
options = options || {};
|
|
271
282
|
|
|
272
283
|
if (typeof options === 'function') {
|
|
@@ -275,10 +286,10 @@ function PettyCache() {
|
|
|
275
286
|
}
|
|
276
287
|
|
|
277
288
|
// Default callback is a noop
|
|
278
|
-
callback = callback ||
|
|
289
|
+
callback = callback || (() => {});
|
|
279
290
|
|
|
280
291
|
// Try to get value from memory cache
|
|
281
|
-
|
|
292
|
+
let result = getFromMemoryCache(key);
|
|
282
293
|
|
|
283
294
|
// Return value from memory cache if it exists
|
|
284
295
|
if (result.exists) {
|
|
@@ -288,8 +299,8 @@ function PettyCache() {
|
|
|
288
299
|
const _this = this;
|
|
289
300
|
|
|
290
301
|
// Double-checked locking: http://en.wikipedia.org/wiki/Double-checked_locking
|
|
291
|
-
lock(`fetch-memory-cache-lock-${key}`,
|
|
292
|
-
async.reflect(
|
|
302
|
+
lock(`fetch-memory-cache-lock-${key}`, (releaseMemoryCacheLock) => {
|
|
303
|
+
async.reflect((callback) => {
|
|
293
304
|
// Try to get value from memory cache
|
|
294
305
|
result = getFromMemoryCache(key);
|
|
295
306
|
|
|
@@ -299,7 +310,7 @@ function PettyCache() {
|
|
|
299
310
|
}
|
|
300
311
|
|
|
301
312
|
// Try to get value from Redis
|
|
302
|
-
getFromRedis(key,
|
|
313
|
+
getFromRedis(key, (err, result) => {
|
|
303
314
|
if (err) {
|
|
304
315
|
return callback(err);
|
|
305
316
|
}
|
|
@@ -311,8 +322,8 @@ function PettyCache() {
|
|
|
311
322
|
}
|
|
312
323
|
|
|
313
324
|
// Double-checked locking: http://en.wikipedia.org/wiki/Double-checked_locking
|
|
314
|
-
lock(`fetch-redis-lock-${key}`,
|
|
315
|
-
async.reflect(
|
|
325
|
+
lock(`fetch-redis-lock-${key}`, (releaseRedisLock) => {
|
|
326
|
+
async.reflect((callback) => {
|
|
316
327
|
// Try to get value from memory cache
|
|
317
328
|
result = getFromMemoryCache(key);
|
|
318
329
|
|
|
@@ -322,7 +333,7 @@ function PettyCache() {
|
|
|
322
333
|
}
|
|
323
334
|
|
|
324
335
|
// Try to get value from Redis
|
|
325
|
-
getFromRedis(key, async
|
|
336
|
+
getFromRedis(key, async (err, result) => {
|
|
326
337
|
if (err) {
|
|
327
338
|
return callback(err);
|
|
328
339
|
}
|
|
@@ -339,7 +350,7 @@ function PettyCache() {
|
|
|
339
350
|
try {
|
|
340
351
|
const data = await func();
|
|
341
352
|
|
|
342
|
-
_this.set(key, data, options,
|
|
353
|
+
_this.set(key, data, options, (err) => {
|
|
343
354
|
callback(err, data);
|
|
344
355
|
});
|
|
345
356
|
} catch(err) {
|
|
@@ -347,18 +358,18 @@ function PettyCache() {
|
|
|
347
358
|
}
|
|
348
359
|
} else {
|
|
349
360
|
// If the function has arguments, there was a callback provided
|
|
350
|
-
func(
|
|
361
|
+
func((err, data) => {
|
|
351
362
|
if (err) {
|
|
352
363
|
return callback(err);
|
|
353
364
|
}
|
|
354
365
|
|
|
355
|
-
_this.set(key, data, options,
|
|
366
|
+
_this.set(key, data, options, (err) => {
|
|
356
367
|
callback(err, data);
|
|
357
368
|
});
|
|
358
369
|
});
|
|
359
370
|
}
|
|
360
371
|
});
|
|
361
|
-
})(releaseRedisLock(
|
|
372
|
+
})(releaseRedisLock((err, result) => {
|
|
362
373
|
if (result.error) {
|
|
363
374
|
return callback(result.error);
|
|
364
375
|
}
|
|
@@ -367,7 +378,7 @@ function PettyCache() {
|
|
|
367
378
|
}));
|
|
368
379
|
});
|
|
369
380
|
});
|
|
370
|
-
})(releaseMemoryCacheLock(
|
|
381
|
+
})(releaseMemoryCacheLock((err, result) => {
|
|
371
382
|
if (result.error) {
|
|
372
383
|
return callback(result.error);
|
|
373
384
|
}
|
|
@@ -377,7 +388,7 @@ function PettyCache() {
|
|
|
377
388
|
});
|
|
378
389
|
};
|
|
379
390
|
|
|
380
|
-
this.fetchAndRefresh =
|
|
391
|
+
this.fetchAndRefresh = (key, func, options, callback) => {
|
|
381
392
|
options = options || {};
|
|
382
393
|
|
|
383
394
|
if (typeof options === 'function') {
|
|
@@ -389,22 +400,22 @@ function PettyCache() {
|
|
|
389
400
|
const ttl = getTtl(options);
|
|
390
401
|
|
|
391
402
|
// Default callback is a noop
|
|
392
|
-
callback = callback ||
|
|
403
|
+
callback = callback || (() => {});
|
|
393
404
|
|
|
394
405
|
const _this = this;
|
|
395
406
|
|
|
396
407
|
if (!intervals[key]) {
|
|
397
408
|
const delay = ttl.min / 2;
|
|
398
409
|
|
|
399
|
-
intervals[key] = setInterval(
|
|
410
|
+
intervals[key] = setInterval(() => {
|
|
400
411
|
// This distributed lock prevents multiple clients from executing func at the same time
|
|
401
|
-
_this.mutex.lock(`interval-${key}`, { ttl: delay - 100 },
|
|
412
|
+
_this.mutex.lock(`interval-${key}`, { ttl: delay - 100 }, (err) => {
|
|
402
413
|
if (err) {
|
|
403
414
|
return;
|
|
404
415
|
}
|
|
405
416
|
|
|
406
417
|
// Execute the specified function and update cache
|
|
407
|
-
func(
|
|
418
|
+
func((err, data) => {
|
|
408
419
|
if (err) {
|
|
409
420
|
return;
|
|
410
421
|
}
|
|
@@ -418,46 +429,56 @@ function PettyCache() {
|
|
|
418
429
|
this.fetch(key, func, options, callback);
|
|
419
430
|
};
|
|
420
431
|
|
|
421
|
-
this.get =
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
// Return value from memory cache if it exists
|
|
426
|
-
if (result.exists) {
|
|
427
|
-
return callback(null, result.value);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Double-checked locking: http://en.wikipedia.org/wiki/Double-checked_locking
|
|
431
|
-
lock(`get-memory-cache-lock-${key}`, function(releaseMemoryCacheLock) {
|
|
432
|
-
async.reflect(function(callback) {
|
|
432
|
+
this.get = (key, callback) => {
|
|
433
|
+
const executor = () => {
|
|
434
|
+
return new Promise((resolve, reject) => {
|
|
433
435
|
// Try to get value from memory cache
|
|
434
|
-
result = getFromMemoryCache(key);
|
|
436
|
+
let result = getFromMemoryCache(key);
|
|
435
437
|
|
|
436
438
|
// Return value from memory cache if it exists
|
|
437
439
|
if (result.exists) {
|
|
438
|
-
return
|
|
440
|
+
return resolve(result.value);
|
|
439
441
|
}
|
|
440
442
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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);
|
|
445
448
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
+
// Return value from memory cache if it exists
|
|
450
|
+
if (result.exists) {
|
|
451
|
+
return callback(null, result.value);
|
|
452
|
+
}
|
|
449
453
|
|
|
450
|
-
|
|
451
|
-
|
|
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
|
+
}));
|
|
452
473
|
});
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
return callback(result.error);
|
|
456
|
-
}
|
|
474
|
+
});
|
|
475
|
+
};
|
|
457
476
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}
|
|
477
|
+
if (callback) {
|
|
478
|
+
executor().then(result => callback(null, result)).catch(callback);
|
|
479
|
+
} else {
|
|
480
|
+
return executor();
|
|
481
|
+
}
|
|
461
482
|
};
|
|
462
483
|
|
|
463
484
|
this.mutex = {
|
|
@@ -478,7 +499,7 @@ function PettyCache() {
|
|
|
478
499
|
const executor = () => {
|
|
479
500
|
return new Promise((resolve, reject) => {
|
|
480
501
|
async.retry({ interval: options.retry.interval, times: options.retry.times }, callback => {
|
|
481
|
-
redisClient.set(key, '1', 'NX', 'PX', options.ttl,
|
|
502
|
+
redisClient.set(key, '1', 'NX', 'PX', options.ttl, (err, res) => {
|
|
482
503
|
if (err) {
|
|
483
504
|
return callback(err);
|
|
484
505
|
}
|
|
@@ -493,7 +514,7 @@ function PettyCache() {
|
|
|
493
514
|
|
|
494
515
|
callback();
|
|
495
516
|
});
|
|
496
|
-
},
|
|
517
|
+
}, (err) => {
|
|
497
518
|
if (err) {
|
|
498
519
|
return reject(err);
|
|
499
520
|
}
|
|
@@ -512,7 +533,7 @@ function PettyCache() {
|
|
|
512
533
|
unlock: (key, callback) => {
|
|
513
534
|
const executor = () => {
|
|
514
535
|
return new Promise((resolve, reject) => {
|
|
515
|
-
redisClient.del(key,
|
|
536
|
+
redisClient.del(key, (err) => {
|
|
516
537
|
if (err) {
|
|
517
538
|
return reject(err);
|
|
518
539
|
}
|
|
@@ -530,33 +551,51 @@ function PettyCache() {
|
|
|
530
551
|
}
|
|
531
552
|
};
|
|
532
553
|
|
|
533
|
-
this.patch =
|
|
534
|
-
if (!callback) {
|
|
554
|
+
this.patch = (key, value, options, callback) => {
|
|
555
|
+
if (!callback && typeof options === 'function') {
|
|
535
556
|
callback = options;
|
|
536
557
|
options = {};
|
|
537
558
|
}
|
|
538
559
|
|
|
560
|
+
options = options || {};
|
|
561
|
+
|
|
539
562
|
const _this = this;
|
|
540
563
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
564
|
+
const executor = () => {
|
|
565
|
+
return new Promise((resolve, reject) => {
|
|
566
|
+
_this.get(key, (err, data) => {
|
|
567
|
+
if (err) {
|
|
568
|
+
return reject(err);
|
|
569
|
+
}
|
|
545
570
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
571
|
+
if (!data) {
|
|
572
|
+
return reject(new Error(`Key ${key} does not exist`));
|
|
573
|
+
}
|
|
549
574
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
575
|
+
for (let k in value) {
|
|
576
|
+
data[k] = value[k];
|
|
577
|
+
}
|
|
553
578
|
|
|
554
|
-
|
|
555
|
-
|
|
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
|
+
}
|
|
556
595
|
};
|
|
557
596
|
|
|
558
597
|
this.semaphore = {
|
|
559
|
-
acquireLock:
|
|
598
|
+
acquireLock: (key, options, callback) => {
|
|
560
599
|
// Options are optional
|
|
561
600
|
if (!callback && typeof options === 'function') {
|
|
562
601
|
callback = options;
|
|
@@ -572,14 +611,14 @@ function PettyCache() {
|
|
|
572
611
|
|
|
573
612
|
const _this = this;
|
|
574
613
|
|
|
575
|
-
async.retry({ interval: options.retry.interval, times: options.retry.times },
|
|
614
|
+
async.retry({ interval: options.retry.interval, times: options.retry.times }, (callback) => {
|
|
576
615
|
// Mutex lock around semaphore
|
|
577
|
-
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } },
|
|
616
|
+
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } }, (err) => {
|
|
578
617
|
if (err) {
|
|
579
618
|
return callback(err);
|
|
580
619
|
}
|
|
581
620
|
|
|
582
|
-
redisClient.get(key,
|
|
621
|
+
redisClient.get(key, (err, data) => {
|
|
583
622
|
// If we encountered an error, unlock the mutex lock and return error
|
|
584
623
|
if (err) {
|
|
585
624
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
@@ -590,10 +629,10 @@ function PettyCache() {
|
|
|
590
629
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(new Error(`Semaphore ${key} doesn't exist.`)); });
|
|
591
630
|
}
|
|
592
631
|
|
|
593
|
-
|
|
632
|
+
const pool = JSON.parse(data);
|
|
594
633
|
|
|
595
634
|
// Try to find a slot that's available.
|
|
596
|
-
|
|
635
|
+
let index = pool.findIndex(s => s.status === 'available');
|
|
597
636
|
|
|
598
637
|
if (index === -1) {
|
|
599
638
|
index = pool.findIndex(s => s.ttl <= Date.now());
|
|
@@ -606,7 +645,7 @@ function PettyCache() {
|
|
|
606
645
|
|
|
607
646
|
pool[index] = { status: 'acquired', ttl: Date.now() + options.ttl };
|
|
608
647
|
|
|
609
|
-
redisClient.set(key, JSON.stringify(pool),
|
|
648
|
+
redisClient.set(key, JSON.stringify(pool), (err) => {
|
|
610
649
|
if (err) {
|
|
611
650
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
612
651
|
}
|
|
@@ -617,18 +656,18 @@ function PettyCache() {
|
|
|
617
656
|
});
|
|
618
657
|
}, callback);
|
|
619
658
|
},
|
|
620
|
-
consumeLock:
|
|
621
|
-
callback = callback ||
|
|
659
|
+
consumeLock: (key, index, callback) => {
|
|
660
|
+
callback = callback || (() => {});
|
|
622
661
|
|
|
623
662
|
const _this = this;
|
|
624
663
|
|
|
625
664
|
// Mutex lock around semaphore
|
|
626
|
-
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } },
|
|
665
|
+
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } }, (err) => {
|
|
627
666
|
if (err) {
|
|
628
667
|
return callback(err);
|
|
629
668
|
}
|
|
630
669
|
|
|
631
|
-
redisClient.get(key,
|
|
670
|
+
redisClient.get(key, (err, data) => {
|
|
632
671
|
// If we encountered an error, unlock the mutex lock and return error
|
|
633
672
|
if (err) {
|
|
634
673
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
@@ -639,7 +678,7 @@ function PettyCache() {
|
|
|
639
678
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(new Error(`Semaphore ${key} doesn't exist.`)); });
|
|
640
679
|
}
|
|
641
680
|
|
|
642
|
-
|
|
681
|
+
const pool = JSON.parse(data);
|
|
643
682
|
|
|
644
683
|
// Ensure index exists.
|
|
645
684
|
if (pool.length <= index) {
|
|
@@ -653,7 +692,7 @@ function PettyCache() {
|
|
|
653
692
|
pool[index] = { status: 'available' };
|
|
654
693
|
}
|
|
655
694
|
|
|
656
|
-
redisClient.set(key, JSON.stringify(pool),
|
|
695
|
+
redisClient.set(key, JSON.stringify(pool), (err) => {
|
|
657
696
|
if (err) {
|
|
658
697
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
659
698
|
}
|
|
@@ -663,17 +702,17 @@ function PettyCache() {
|
|
|
663
702
|
});
|
|
664
703
|
});
|
|
665
704
|
},
|
|
666
|
-
expand:
|
|
667
|
-
callback = callback ||
|
|
705
|
+
expand: (key, size, callback) => {
|
|
706
|
+
callback = callback || (() => {});
|
|
668
707
|
|
|
669
708
|
const _this = this;
|
|
670
709
|
|
|
671
|
-
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } },
|
|
710
|
+
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } }, (err) => {
|
|
672
711
|
if (err) {
|
|
673
712
|
return callback(err);
|
|
674
713
|
}
|
|
675
714
|
|
|
676
|
-
redisClient.get(key,
|
|
715
|
+
redisClient.get(key, (err, data) => {
|
|
677
716
|
// If we encountered an error, unlock the mutex lock and return error
|
|
678
717
|
if (err) {
|
|
679
718
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
@@ -684,7 +723,7 @@ function PettyCache() {
|
|
|
684
723
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(new Error(`Semaphore ${key} doesn't exist.`)); });
|
|
685
724
|
}
|
|
686
725
|
|
|
687
|
-
|
|
726
|
+
let pool = JSON.parse(data);
|
|
688
727
|
|
|
689
728
|
if (pool.length > size) {
|
|
690
729
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(new Error(`Cannot shrink pool, size is ${pool.length} and you requested a size of ${size}.`)); });
|
|
@@ -696,7 +735,7 @@ function PettyCache() {
|
|
|
696
735
|
|
|
697
736
|
pool = pool.concat(Array(size - pool.length).fill({ status: 'available' }));
|
|
698
737
|
|
|
699
|
-
redisClient.set(key, JSON.stringify(pool),
|
|
738
|
+
redisClient.set(key, JSON.stringify(pool), (err) => {
|
|
700
739
|
if (err) {
|
|
701
740
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
702
741
|
}
|
|
@@ -706,18 +745,18 @@ function PettyCache() {
|
|
|
706
745
|
});
|
|
707
746
|
});
|
|
708
747
|
},
|
|
709
|
-
releaseLock:
|
|
710
|
-
callback = callback ||
|
|
748
|
+
releaseLock: (key, index, callback) => {
|
|
749
|
+
callback = callback || (() => {});
|
|
711
750
|
|
|
712
751
|
const _this = this;
|
|
713
752
|
|
|
714
753
|
// Mutex lock around semaphore
|
|
715
|
-
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } },
|
|
754
|
+
_this.mutex.lock(`lock:${key}`, { retry: { times: 100 } }, (err) => {
|
|
716
755
|
if (err) {
|
|
717
756
|
return callback(err);
|
|
718
757
|
}
|
|
719
758
|
|
|
720
|
-
redisClient.get(key,
|
|
759
|
+
redisClient.get(key, (err, data) => {
|
|
721
760
|
// If we encountered an error, unlock the mutex lock and return error
|
|
722
761
|
if (err) {
|
|
723
762
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
@@ -728,7 +767,7 @@ function PettyCache() {
|
|
|
728
767
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(new Error(`Semaphore ${key} doesn't exist.`)); });
|
|
729
768
|
}
|
|
730
769
|
|
|
731
|
-
|
|
770
|
+
const pool = JSON.parse(data);
|
|
732
771
|
|
|
733
772
|
// Ensure index exists.
|
|
734
773
|
if (pool.length <= index) {
|
|
@@ -737,7 +776,7 @@ function PettyCache() {
|
|
|
737
776
|
|
|
738
777
|
pool[index] = { status: 'available' };
|
|
739
778
|
|
|
740
|
-
redisClient.set(key, JSON.stringify(pool),
|
|
779
|
+
redisClient.set(key, JSON.stringify(pool), (err) => {
|
|
741
780
|
if (err) {
|
|
742
781
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
743
782
|
}
|
|
@@ -747,19 +786,19 @@ function PettyCache() {
|
|
|
747
786
|
});
|
|
748
787
|
});
|
|
749
788
|
},
|
|
750
|
-
reset:
|
|
751
|
-
callback = callback ||
|
|
789
|
+
reset: (key, callback) => {
|
|
790
|
+
callback = callback || (() => {});
|
|
752
791
|
|
|
753
792
|
const _this = this;
|
|
754
793
|
|
|
755
794
|
// Mutex lock around semaphore
|
|
756
|
-
this.mutex.lock(`lock:${key}`, { retry: { times: 100 } },
|
|
795
|
+
this.mutex.lock(`lock:${key}`, { retry: { times: 100 } }, (err) => {
|
|
757
796
|
if (err) {
|
|
758
797
|
return callback(err);
|
|
759
798
|
}
|
|
760
799
|
|
|
761
800
|
// Try to get previously created semaphore
|
|
762
|
-
redisClient.get(key,
|
|
801
|
+
redisClient.get(key, (err, data) => {
|
|
763
802
|
// If we encountered an error, unlock the mutex lock and return error
|
|
764
803
|
if (err) {
|
|
765
804
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
@@ -770,10 +809,10 @@ function PettyCache() {
|
|
|
770
809
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(new Error(`Semaphore ${key} doesn't exist.`)); });
|
|
771
810
|
}
|
|
772
811
|
|
|
773
|
-
|
|
812
|
+
let pool = JSON.parse(data);
|
|
774
813
|
pool = Array(pool.length).fill({ status: 'available' });
|
|
775
814
|
|
|
776
|
-
redisClient.set(key, JSON.stringify(pool),
|
|
815
|
+
redisClient.set(key, JSON.stringify(pool), (err) => {
|
|
777
816
|
if (err) {
|
|
778
817
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
779
818
|
}
|
|
@@ -783,26 +822,26 @@ function PettyCache() {
|
|
|
783
822
|
});
|
|
784
823
|
});
|
|
785
824
|
},
|
|
786
|
-
retrieveOrCreate:
|
|
825
|
+
retrieveOrCreate: (key, options, callback) => {
|
|
787
826
|
// Options are optional
|
|
788
827
|
if (!callback && typeof options === 'function') {
|
|
789
828
|
callback = options;
|
|
790
829
|
options = {};
|
|
791
830
|
}
|
|
792
831
|
|
|
793
|
-
callback = callback ||
|
|
832
|
+
callback = callback || (() => {});
|
|
794
833
|
options = options || {};
|
|
795
834
|
|
|
796
835
|
const _this = this;
|
|
797
836
|
|
|
798
837
|
// Mutex lock around semaphore retrival or creation
|
|
799
|
-
this.mutex.lock(`lock:${key}`, { retry: { times: 100 } },
|
|
838
|
+
this.mutex.lock(`lock:${key}`, { retry: { times: 100 } }, (err) => {
|
|
800
839
|
if (err) {
|
|
801
840
|
return callback(err);
|
|
802
841
|
}
|
|
803
842
|
|
|
804
843
|
// Try to get previously created semaphore
|
|
805
|
-
redisClient.get(key,
|
|
844
|
+
redisClient.get(key, (err, data) => {
|
|
806
845
|
// If we encountered an error, unlock the mutex lock and return error
|
|
807
846
|
if (err) {
|
|
808
847
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
@@ -813,7 +852,7 @@ function PettyCache() {
|
|
|
813
852
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(null, JSON.parse(data)); });
|
|
814
853
|
}
|
|
815
854
|
|
|
816
|
-
|
|
855
|
+
const getSize = (callback) => {
|
|
817
856
|
if (typeof options.size === 'function') {
|
|
818
857
|
return options.size(callback);
|
|
819
858
|
}
|
|
@@ -821,15 +860,15 @@ function PettyCache() {
|
|
|
821
860
|
callback(null, Object.prototype.hasOwnProperty.call(options, 'size') ? options.size : 1);
|
|
822
861
|
};
|
|
823
862
|
|
|
824
|
-
getSize(
|
|
863
|
+
getSize((err, size) => {
|
|
825
864
|
// If we encountered an error, unlock the mutex lock and return error
|
|
826
865
|
if (err) {
|
|
827
866
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
828
867
|
}
|
|
829
868
|
|
|
830
|
-
|
|
869
|
+
const pool = Array(Math.max(size, 1)).fill({ status: 'available' });
|
|
831
870
|
|
|
832
|
-
redisClient.set(key, JSON.stringify(pool),
|
|
871
|
+
redisClient.set(key, JSON.stringify(pool), (err) => {
|
|
833
872
|
if (err) {
|
|
834
873
|
return _this.mutex.unlock(`lock:${key}`, () => { callback(err); });
|
|
835
874
|
}
|
|
@@ -842,7 +881,7 @@ function PettyCache() {
|
|
|
842
881
|
}
|
|
843
882
|
};
|
|
844
883
|
|
|
845
|
-
this.set =
|
|
884
|
+
this.set = (key, value, options, callback) => {
|
|
846
885
|
options = options || {};
|
|
847
886
|
|
|
848
887
|
if (typeof options === 'function') {
|
|
@@ -853,18 +892,31 @@ function PettyCache() {
|
|
|
853
892
|
// Get TTL based on specified options
|
|
854
893
|
const ttl = getTtl(options);
|
|
855
894
|
|
|
856
|
-
|
|
857
|
-
|
|
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));
|
|
899
|
+
|
|
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
|
+
}
|
|
858
905
|
|
|
859
|
-
|
|
860
|
-
|
|
906
|
+
resolve();
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
};
|
|
861
910
|
|
|
862
|
-
|
|
863
|
-
|
|
911
|
+
if (callback) {
|
|
912
|
+
executor().then(result => callback(null, result)).catch(callback);
|
|
913
|
+
} else {
|
|
914
|
+
return executor();
|
|
915
|
+
}
|
|
864
916
|
};
|
|
865
917
|
|
|
866
918
|
// Semaphore functions need to be bound to the main PettyCache object
|
|
867
|
-
for (
|
|
919
|
+
for (const method in this.semaphore) {
|
|
868
920
|
this.semaphore[method] = this.semaphore[method].bind(this);
|
|
869
921
|
}
|
|
870
922
|
}
|
|
@@ -877,8 +929,8 @@ function random(min, max) {
|
|
|
877
929
|
return Math.floor(Math.random() * (max - min + 1) + min);
|
|
878
930
|
}
|
|
879
931
|
|
|
880
|
-
PettyCache.parse =
|
|
881
|
-
return JSON.parse(text,
|
|
932
|
+
PettyCache.parse = (text) => {
|
|
933
|
+
return JSON.parse(text, (k, v) => {
|
|
882
934
|
if (v === '__NaN') {
|
|
883
935
|
return NaN;
|
|
884
936
|
} else if (v === '__null') {
|
|
@@ -891,8 +943,8 @@ PettyCache.parse = function(text) {
|
|
|
891
943
|
});
|
|
892
944
|
};
|
|
893
945
|
|
|
894
|
-
PettyCache.stringify =
|
|
895
|
-
return JSON.stringify(value,
|
|
946
|
+
PettyCache.stringify = (value) => {
|
|
947
|
+
return JSON.stringify(value, (k, v) => {
|
|
896
948
|
if (typeof v === 'number' && isNaN(v)) {
|
|
897
949
|
return '__NaN';
|
|
898
950
|
} else if (v === null) {
|
package/package.json
CHANGED
|
@@ -1,34 +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
|
-
"type": "git",
|
|
31
|
-
"url": "https://github.com/mediocre/petty-cache.git"
|
|
32
|
-
},
|
|
33
|
-
"version": "3.4.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"
|
|
34
30
|
}
|