cacheable 0.2.9 → 0.8.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/LICENSE +19 -0
- package/README.md +136 -141
- package/dist/index.cjs +448 -0
- package/dist/index.d.cts +104 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.js +421 -0
- package/package.json +37 -26
- package/.npmignore +0 -3
- package/.travis.yml +0 -4
- package/Makefile +0 -7
- package/index.js +0 -1
- package/lib/cacheable.js +0 -162
- package/lib/registry.js +0 -170
- package/test/cacheable.test.js +0 -195
- package/test/mocha.opts +0 -2
- package/test/support/user.js +0 -26
package/dist/index.js
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { Keyv } from "keyv";
|
|
3
|
+
import { Hookified } from "hookified";
|
|
4
|
+
|
|
5
|
+
// src/stats.ts
|
|
6
|
+
var CacheableStats = class {
|
|
7
|
+
_hits = 0;
|
|
8
|
+
_misses = 0;
|
|
9
|
+
_gets = 0;
|
|
10
|
+
_sets = 0;
|
|
11
|
+
_deletes = 0;
|
|
12
|
+
_clears = 0;
|
|
13
|
+
_vsize = 0;
|
|
14
|
+
_ksize = 0;
|
|
15
|
+
_count = 0;
|
|
16
|
+
_enabled = false;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
if (options?.enabled) {
|
|
19
|
+
this._enabled = options.enabled;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
get enabled() {
|
|
23
|
+
return this._enabled;
|
|
24
|
+
}
|
|
25
|
+
set enabled(enabled) {
|
|
26
|
+
this._enabled = enabled;
|
|
27
|
+
}
|
|
28
|
+
get hits() {
|
|
29
|
+
return this._hits;
|
|
30
|
+
}
|
|
31
|
+
get misses() {
|
|
32
|
+
return this._misses;
|
|
33
|
+
}
|
|
34
|
+
get gets() {
|
|
35
|
+
return this._gets;
|
|
36
|
+
}
|
|
37
|
+
get sets() {
|
|
38
|
+
return this._sets;
|
|
39
|
+
}
|
|
40
|
+
get deletes() {
|
|
41
|
+
return this._deletes;
|
|
42
|
+
}
|
|
43
|
+
get clears() {
|
|
44
|
+
return this._clears;
|
|
45
|
+
}
|
|
46
|
+
get vsize() {
|
|
47
|
+
return this._vsize;
|
|
48
|
+
}
|
|
49
|
+
get ksize() {
|
|
50
|
+
return this._ksize;
|
|
51
|
+
}
|
|
52
|
+
get count() {
|
|
53
|
+
return this._count;
|
|
54
|
+
}
|
|
55
|
+
incrementHits() {
|
|
56
|
+
if (!this._enabled) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this._hits++;
|
|
60
|
+
}
|
|
61
|
+
incrementMisses() {
|
|
62
|
+
if (!this._enabled) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
this._misses++;
|
|
66
|
+
}
|
|
67
|
+
incrementGets() {
|
|
68
|
+
if (!this._enabled) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
this._gets++;
|
|
72
|
+
}
|
|
73
|
+
incrementSets() {
|
|
74
|
+
if (!this._enabled) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this._sets++;
|
|
78
|
+
}
|
|
79
|
+
incrementDeletes() {
|
|
80
|
+
if (!this._enabled) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this._deletes++;
|
|
84
|
+
}
|
|
85
|
+
incrementClears() {
|
|
86
|
+
if (!this._enabled) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this._clears++;
|
|
90
|
+
}
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
92
|
+
incrementVSize(value) {
|
|
93
|
+
if (!this._enabled) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this._vsize += this.roughSizeOfObject(value);
|
|
97
|
+
}
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
99
|
+
decreaseVSize(value) {
|
|
100
|
+
if (!this._enabled) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this._vsize -= this.roughSizeOfObject(value);
|
|
104
|
+
}
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
106
|
+
incrementKSize(key) {
|
|
107
|
+
if (!this._enabled) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
this._ksize += this.roughSizeOfString(key);
|
|
111
|
+
}
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
113
|
+
decreaseKSize(key) {
|
|
114
|
+
if (!this._enabled) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this._ksize -= this.roughSizeOfString(key);
|
|
118
|
+
}
|
|
119
|
+
incrementCount() {
|
|
120
|
+
if (!this._enabled) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this._count++;
|
|
124
|
+
}
|
|
125
|
+
descreaseCount() {
|
|
126
|
+
if (!this._enabled) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this._count--;
|
|
130
|
+
}
|
|
131
|
+
setCount(count) {
|
|
132
|
+
if (!this._enabled) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this._count = count;
|
|
136
|
+
}
|
|
137
|
+
roughSizeOfString(value) {
|
|
138
|
+
return value.length * 2;
|
|
139
|
+
}
|
|
140
|
+
roughSizeOfObject(object) {
|
|
141
|
+
const objectList = [];
|
|
142
|
+
const stack = [object];
|
|
143
|
+
let bytes = 0;
|
|
144
|
+
while (stack.length > 0) {
|
|
145
|
+
const value = stack.pop();
|
|
146
|
+
if (typeof value === "boolean") {
|
|
147
|
+
bytes += 4;
|
|
148
|
+
} else if (typeof value === "string") {
|
|
149
|
+
bytes += value.length * 2;
|
|
150
|
+
} else if (typeof value === "number") {
|
|
151
|
+
bytes += 8;
|
|
152
|
+
} else if (typeof value === "object" && value !== null && !objectList.includes(value)) {
|
|
153
|
+
objectList.push(value);
|
|
154
|
+
for (const key in value) {
|
|
155
|
+
bytes += key.length * 2;
|
|
156
|
+
stack.push(value[key]);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return bytes;
|
|
161
|
+
}
|
|
162
|
+
reset() {
|
|
163
|
+
this._hits = 0;
|
|
164
|
+
this._misses = 0;
|
|
165
|
+
this._gets = 0;
|
|
166
|
+
this._sets = 0;
|
|
167
|
+
this._deletes = 0;
|
|
168
|
+
this._clears = 0;
|
|
169
|
+
this._vsize = 0;
|
|
170
|
+
this._ksize = 0;
|
|
171
|
+
this._count = 0;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// src/index.ts
|
|
176
|
+
var CacheableHooks = /* @__PURE__ */ ((CacheableHooks2) => {
|
|
177
|
+
CacheableHooks2["BEFORE_SET"] = "BEFORE_SET";
|
|
178
|
+
CacheableHooks2["AFTER_SET"] = "AFTER_SET";
|
|
179
|
+
CacheableHooks2["BEFORE_SET_MANY"] = "BEFORE_SET_MANY";
|
|
180
|
+
CacheableHooks2["AFTER_SET_MANY"] = "AFTER_SET_MANY";
|
|
181
|
+
CacheableHooks2["BEFORE_GET"] = "BEFORE_GET";
|
|
182
|
+
CacheableHooks2["AFTER_GET"] = "AFTER_GET";
|
|
183
|
+
CacheableHooks2["BEFORE_GET_MANY"] = "BEFORE_GET_MANY";
|
|
184
|
+
CacheableHooks2["AFTER_GET_MANY"] = "AFTER_GET_MANY";
|
|
185
|
+
return CacheableHooks2;
|
|
186
|
+
})(CacheableHooks || {});
|
|
187
|
+
var CacheableEvents = /* @__PURE__ */ ((CacheableEvents2) => {
|
|
188
|
+
CacheableEvents2["ERROR"] = "error";
|
|
189
|
+
return CacheableEvents2;
|
|
190
|
+
})(CacheableEvents || {});
|
|
191
|
+
var Cacheable = class extends Hookified {
|
|
192
|
+
_primary = new Keyv();
|
|
193
|
+
_secondary;
|
|
194
|
+
_nonBlocking = false;
|
|
195
|
+
_stats = new CacheableStats({ enabled: false });
|
|
196
|
+
constructor(options) {
|
|
197
|
+
super();
|
|
198
|
+
if (options?.primary) {
|
|
199
|
+
this.setPrimary(options.primary);
|
|
200
|
+
}
|
|
201
|
+
if (options?.secondary) {
|
|
202
|
+
this.setSecondary(options.secondary);
|
|
203
|
+
}
|
|
204
|
+
if (options?.nonBlocking) {
|
|
205
|
+
this._nonBlocking = options.nonBlocking;
|
|
206
|
+
}
|
|
207
|
+
if (options?.stats) {
|
|
208
|
+
this._stats.enabled = options.stats;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
get stats() {
|
|
212
|
+
return this._stats;
|
|
213
|
+
}
|
|
214
|
+
get primary() {
|
|
215
|
+
return this._primary;
|
|
216
|
+
}
|
|
217
|
+
set primary(primary) {
|
|
218
|
+
this._primary = primary;
|
|
219
|
+
}
|
|
220
|
+
get secondary() {
|
|
221
|
+
return this._secondary;
|
|
222
|
+
}
|
|
223
|
+
set secondary(secondary) {
|
|
224
|
+
this._secondary = secondary;
|
|
225
|
+
}
|
|
226
|
+
get nonBlocking() {
|
|
227
|
+
return this._nonBlocking;
|
|
228
|
+
}
|
|
229
|
+
set nonBlocking(nonBlocking) {
|
|
230
|
+
this._nonBlocking = nonBlocking;
|
|
231
|
+
}
|
|
232
|
+
setPrimary(primary) {
|
|
233
|
+
this._primary = primary instanceof Keyv ? primary : new Keyv(primary);
|
|
234
|
+
}
|
|
235
|
+
setSecondary(secondary) {
|
|
236
|
+
this._secondary = secondary instanceof Keyv ? secondary : new Keyv(secondary);
|
|
237
|
+
}
|
|
238
|
+
async get(key) {
|
|
239
|
+
let result;
|
|
240
|
+
try {
|
|
241
|
+
await this.hook("BEFORE_GET" /* BEFORE_GET */, key);
|
|
242
|
+
result = await this._primary.get(key);
|
|
243
|
+
if (!result && this._secondary) {
|
|
244
|
+
result = await this._secondary.get(key);
|
|
245
|
+
if (result) {
|
|
246
|
+
await this._primary.set(key, result);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
await this.hook("AFTER_GET" /* AFTER_GET */, { key, result });
|
|
250
|
+
} catch (error) {
|
|
251
|
+
await this.emit("error" /* ERROR */, error);
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
async getMany(keys) {
|
|
256
|
+
let result = [];
|
|
257
|
+
try {
|
|
258
|
+
await this.hook("BEFORE_GET_MANY" /* BEFORE_GET_MANY */, keys);
|
|
259
|
+
result = await this._primary.get(keys);
|
|
260
|
+
if (this._secondary) {
|
|
261
|
+
const missingKeys = [];
|
|
262
|
+
for (const [i, key] of keys.entries()) {
|
|
263
|
+
if (!result[i]) {
|
|
264
|
+
missingKeys.push(key);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const secondaryResult = await this._secondary.get(missingKeys);
|
|
268
|
+
for (const [i, key] of keys.entries()) {
|
|
269
|
+
if (!result[i] && secondaryResult[i]) {
|
|
270
|
+
result[i] = secondaryResult[i];
|
|
271
|
+
await this._primary.set(key, secondaryResult[i]);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
await this.hook("AFTER_GET_MANY" /* AFTER_GET_MANY */, { keys, result });
|
|
276
|
+
} catch (error) {
|
|
277
|
+
await this.emit("error" /* ERROR */, error);
|
|
278
|
+
}
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
async set(key, value, ttl) {
|
|
282
|
+
let result = false;
|
|
283
|
+
try {
|
|
284
|
+
await this.hook("BEFORE_SET" /* BEFORE_SET */, { key, value, ttl });
|
|
285
|
+
const promises = [];
|
|
286
|
+
promises.push(this._primary.set(key, value, ttl));
|
|
287
|
+
if (this._secondary) {
|
|
288
|
+
promises.push(this._secondary.set(key, value, ttl));
|
|
289
|
+
}
|
|
290
|
+
if (this._nonBlocking) {
|
|
291
|
+
result = await Promise.race(promises);
|
|
292
|
+
} else {
|
|
293
|
+
const results = await Promise.all(promises);
|
|
294
|
+
result = results[0];
|
|
295
|
+
}
|
|
296
|
+
await this.hook("AFTER_SET" /* AFTER_SET */, { key, value, ttl });
|
|
297
|
+
} catch (error) {
|
|
298
|
+
await this.emit("error" /* ERROR */, error);
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
302
|
+
async setMany(items) {
|
|
303
|
+
let result = false;
|
|
304
|
+
try {
|
|
305
|
+
await this.hook("BEFORE_SET_MANY" /* BEFORE_SET_MANY */, items);
|
|
306
|
+
result = await this.setManyKeyv(this._primary, items);
|
|
307
|
+
if (this._secondary) {
|
|
308
|
+
if (this._nonBlocking) {
|
|
309
|
+
this.setManyKeyv(this._secondary, items);
|
|
310
|
+
} else {
|
|
311
|
+
await this.setManyKeyv(this._secondary, items);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
await this.hook("AFTER_SET_MANY" /* AFTER_SET_MANY */, items);
|
|
315
|
+
} catch (error) {
|
|
316
|
+
await this.emit("error" /* ERROR */, error);
|
|
317
|
+
}
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
async take(key) {
|
|
321
|
+
const result = await this.get(key);
|
|
322
|
+
await this.delete(key);
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
async takeMany(keys) {
|
|
326
|
+
const result = await this.getMany(keys);
|
|
327
|
+
await this.deleteMany(keys);
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
330
|
+
async has(key) {
|
|
331
|
+
let result = await this._primary.has(key);
|
|
332
|
+
if (!result && this._secondary) {
|
|
333
|
+
result = await this._secondary.has(key);
|
|
334
|
+
}
|
|
335
|
+
return result;
|
|
336
|
+
}
|
|
337
|
+
async hasMany(keys) {
|
|
338
|
+
const result = await this.hasManyKeyv(this._primary, keys);
|
|
339
|
+
const missingKeys = [];
|
|
340
|
+
for (const [i, key] of keys.entries()) {
|
|
341
|
+
if (!result[i] && this._secondary) {
|
|
342
|
+
missingKeys.push(key);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (missingKeys.length > 0 && this._secondary) {
|
|
346
|
+
const secondary = await this.hasManyKeyv(this._secondary, keys);
|
|
347
|
+
for (const [i, key] of keys.entries()) {
|
|
348
|
+
if (!result[i] && secondary[i]) {
|
|
349
|
+
result[i] = secondary[i];
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
async delete(key) {
|
|
356
|
+
const result = await this._primary.delete(key);
|
|
357
|
+
if (this._secondary) {
|
|
358
|
+
if (this._nonBlocking) {
|
|
359
|
+
this._secondary.delete(key);
|
|
360
|
+
} else {
|
|
361
|
+
await this._secondary.delete(key);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
366
|
+
async deleteMany(keys) {
|
|
367
|
+
const result = await this.deleteManyKeyv(this._primary, keys);
|
|
368
|
+
if (this._secondary) {
|
|
369
|
+
if (this._nonBlocking) {
|
|
370
|
+
this.deleteManyKeyv(this._secondary, keys);
|
|
371
|
+
} else {
|
|
372
|
+
await this.deleteManyKeyv(this._secondary, keys);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
async clear() {
|
|
378
|
+
const promises = [];
|
|
379
|
+
promises.push(this._primary.clear());
|
|
380
|
+
if (this._secondary) {
|
|
381
|
+
promises.push(this._secondary.clear());
|
|
382
|
+
}
|
|
383
|
+
await (this._nonBlocking ? Promise.race(promises) : Promise.all(promises));
|
|
384
|
+
}
|
|
385
|
+
async disconnect() {
|
|
386
|
+
const promises = [];
|
|
387
|
+
promises.push(this._primary.disconnect());
|
|
388
|
+
if (this._secondary) {
|
|
389
|
+
promises.push(this._secondary.disconnect());
|
|
390
|
+
}
|
|
391
|
+
await (this._nonBlocking ? Promise.race(promises) : Promise.all(promises));
|
|
392
|
+
}
|
|
393
|
+
async deleteManyKeyv(keyv, keys) {
|
|
394
|
+
const promises = [];
|
|
395
|
+
for (const key of keys) {
|
|
396
|
+
promises.push(keyv.delete(key));
|
|
397
|
+
}
|
|
398
|
+
await Promise.all(promises);
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
async setManyKeyv(keyv, items) {
|
|
402
|
+
const promises = [];
|
|
403
|
+
for (const item of items) {
|
|
404
|
+
promises.push(keyv.set(item.key, item.value, item.ttl));
|
|
405
|
+
}
|
|
406
|
+
await Promise.all(promises);
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
async hasManyKeyv(keyv, keys) {
|
|
410
|
+
const promises = [];
|
|
411
|
+
for (const key of keys) {
|
|
412
|
+
promises.push(keyv.has(key));
|
|
413
|
+
}
|
|
414
|
+
return Promise.all(promises);
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
export {
|
|
418
|
+
Cacheable,
|
|
419
|
+
CacheableEvents,
|
|
420
|
+
CacheableHooks
|
|
421
|
+
};
|
package/package.json
CHANGED
|
@@ -1,33 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cacheable",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Simple Caching Engine using Keyv",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"require": "./dist/index.cjs",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
13
14
|
},
|
|
15
|
+
"repository": "https://github.com/jaredwray/cacheable.git",
|
|
16
|
+
"author": "Jared Wray <me@jaredwray.com>",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"private": false,
|
|
14
19
|
"devDependencies": {
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"lru-cache": "
|
|
18
|
-
"
|
|
20
|
+
"@keyv/redis": "^3.0.1",
|
|
21
|
+
"@vitest/coverage-v8": "^2.1.1",
|
|
22
|
+
"lru-cache": "^10.4.3",
|
|
23
|
+
"rimraf": "^6.0.1",
|
|
24
|
+
"ts-node": "^10.9.2",
|
|
25
|
+
"tsup": "^8.2.4",
|
|
26
|
+
"typescript": "^5.5.4",
|
|
27
|
+
"vitest": "^2.1.1",
|
|
28
|
+
"xo": "^0.59.3"
|
|
19
29
|
},
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"hookified": "^0.7.1",
|
|
32
|
+
"keyv": "^5.0.1"
|
|
23
33
|
},
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"cacheable",
|
|
28
|
-
"cache",
|
|
29
|
-
"cached"
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"LICENSE"
|
|
30
37
|
],
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean",
|
|
40
|
+
"test": "xo --fix && vitest run --coverage",
|
|
41
|
+
"test:ci": "xo && vitest run",
|
|
42
|
+
"clean": "rimraf ./dist ./coverage ./node_modules"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/.npmignore
DELETED
package/.travis.yml
DELETED
package/Makefile
DELETED
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = require('./lib/cacheable.js');
|
package/lib/cacheable.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
var Storeman = require('storeman');
|
|
2
|
-
var keyf = require('keyf')
|
|
3
|
-
var debug = require('debug')('cacheable:debug')
|
|
4
|
-
|
|
5
|
-
var log = require('debug')('cacheable:log')
|
|
6
|
-
var __slice = Array.prototype.slice
|
|
7
|
-
|
|
8
|
-
// all key pattern definations
|
|
9
|
-
var allkeys = {}
|
|
10
|
-
var RE_KEY_PATTERN = /(%j)?{([\w\.]+)}/g
|
|
11
|
-
var _REALNAME = '__cachedname'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* A cached wrapper for async funtions
|
|
16
|
-
*
|
|
17
|
-
* Options:
|
|
18
|
-
*
|
|
19
|
-
* - `prefix` prefix for all keys under this cached manager
|
|
20
|
-
* - `silent` to ignore `storage.get` error
|
|
21
|
-
*
|
|
22
|
-
*/
|
|
23
|
-
function Cacheable(options) {
|
|
24
|
-
if (!(this instanceof Cacheable)) return new Cacheable(options)
|
|
25
|
-
if (!options.prefix && options.prefix !== '') {
|
|
26
|
-
options.prefix = 'cached:'
|
|
27
|
-
}
|
|
28
|
-
this.silent = options.silent === false ? false : true
|
|
29
|
-
this.helpers = {}
|
|
30
|
-
Storeman.call(this, options);
|
|
31
|
-
}
|
|
32
|
-
// Inherit Storeman to get a bunch of `get`, `set` methods
|
|
33
|
-
require('util').inherits(Cacheable, Storeman)
|
|
34
|
-
|
|
35
|
-
Cacheable.prototype.debug = debug
|
|
36
|
-
// default key for wrapping standalone functions
|
|
37
|
-
Cacheable.prototype.DEFAULT_KEY = '{_fn_}:%j{0}'
|
|
38
|
-
|
|
39
|
-
Cacheable.prototype._applykey = function applykey(ctx, key, fn, args) {
|
|
40
|
-
return Cacheable.keyReplacer.call(ctx, key, fn, args)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Wraps an async funtion, automaticly cache the result
|
|
45
|
-
*
|
|
46
|
-
* The async function must have a signature of `fn(arg1, [arg2...,] callback)`.
|
|
47
|
-
* The `arg1` can be an options object, if `options.fresh` is passed as true,
|
|
48
|
-
* cache will not be used.
|
|
49
|
-
*/
|
|
50
|
-
Cacheable.prototype.wrap = function(fn, key, ttl, ctx) {
|
|
51
|
-
var cached = this
|
|
52
|
-
|
|
53
|
-
if ('number' == typeof key) {
|
|
54
|
-
// second parameter as ttl
|
|
55
|
-
ctx = ttl
|
|
56
|
-
ttl = key
|
|
57
|
-
key = null
|
|
58
|
-
}
|
|
59
|
-
if ('function' == typeof key || 'object' == typeof key) {
|
|
60
|
-
// second parameter as context
|
|
61
|
-
ctx = key
|
|
62
|
-
key = null
|
|
63
|
-
}
|
|
64
|
-
key = key || cached.DEFAULT_KEY
|
|
65
|
-
|
|
66
|
-
if (!getName(fn) && ~key.indexOf('{_fn_}')) {
|
|
67
|
-
throw new Error('Cache key referred to "{_fn_}", but the function doesn\'t have a name')
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// try formating the key in advance
|
|
71
|
-
var uniqkey = cached._applykey(ctx, key, fn, [])
|
|
72
|
-
if (uniqkey in allkeys) {
|
|
73
|
-
log('Possible key conflict -> fn: [%s], key: %s', getName(fn), uniqkey)
|
|
74
|
-
} else {
|
|
75
|
-
allkeys[uniqkey] = null
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
debug('wrapping `%s`, key: "%s", ttl: %s', getName(fn) || '[anonymous]', uniqkey, ttl)
|
|
79
|
-
|
|
80
|
-
return function cacheWrapped() {
|
|
81
|
-
var self = ctx || this
|
|
82
|
-
var args = __slice.apply(arguments)
|
|
83
|
-
var callback = args[args.length - 1]
|
|
84
|
-
// the real key
|
|
85
|
-
var _key = cached._applykey(self, key, fn, args)
|
|
86
|
-
|
|
87
|
-
// doesn't need a callback
|
|
88
|
-
// just do the job as there is no cache
|
|
89
|
-
if (typeof callback !== 'function') {
|
|
90
|
-
debug('called with no callback, skip "%s"', _key)
|
|
91
|
-
return run()
|
|
92
|
-
}
|
|
93
|
-
// when `options.fresh` is passed as true,
|
|
94
|
-
// don't use cache
|
|
95
|
-
if ('object' == typeof args[0] && args[0].fresh) {
|
|
96
|
-
return run()
|
|
97
|
-
}
|
|
98
|
-
// cache key is not fully formatted means some args are not passed in
|
|
99
|
-
if (_key.match(RE_KEY_PATTERN)) {
|
|
100
|
-
debug('cache key not fully formatted, skip "%s"', _key)
|
|
101
|
-
return run()
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function run() {
|
|
105
|
-
fn.apply(self, args)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function done(err, reply) {
|
|
109
|
-
return callback.call(self, err, reply)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
cached.get(_key, function(err, reply) {
|
|
113
|
-
if (err) {
|
|
114
|
-
if (!cached.silent) {
|
|
115
|
-
// ignore cache error unless not silent
|
|
116
|
-
return done(err)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// cache found
|
|
120
|
-
if (reply !== undefined) {
|
|
121
|
-
return done(null, reply)
|
|
122
|
-
}
|
|
123
|
-
// make sure we save cache after callback
|
|
124
|
-
args[args.length - 1] = function(err, result) {
|
|
125
|
-
function _done(){
|
|
126
|
-
callback.call(self, err, result)
|
|
127
|
-
}
|
|
128
|
-
if (err || result === undefined) {
|
|
129
|
-
return _done()
|
|
130
|
-
}
|
|
131
|
-
// save the cache
|
|
132
|
-
cached.set(_key, result, ttl, _done)
|
|
133
|
-
}
|
|
134
|
-
run()
|
|
135
|
-
})
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Get function's realname
|
|
141
|
-
*/
|
|
142
|
-
function getName(fn) {
|
|
143
|
-
return fn[_REALNAME] || fn.name
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* How to replace a cache key
|
|
148
|
-
*/
|
|
149
|
-
Cacheable.keyReplacer = function(key, fn, args) {
|
|
150
|
-
var self = this
|
|
151
|
-
var data = {
|
|
152
|
-
_fn_: fn && getName(fn),
|
|
153
|
-
_model_: self[_REALNAME]
|
|
154
|
-
}
|
|
155
|
-
return keyf(key, data).call(self, args)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
Cacheable._REALNAME = _REALNAME
|
|
159
|
-
|
|
160
|
-
module.exports = Cacheable
|
|
161
|
-
|
|
162
|
-
require('./registry')
|