cachette 4.0.15 → 4.0.16
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.
|
@@ -51,6 +51,18 @@ class CacheInstance extends node_events_1.EventEmitter {
|
|
|
51
51
|
* simultaneous requests to the same resource in parallel.
|
|
52
52
|
*/
|
|
53
53
|
activeFetches = {};
|
|
54
|
+
async getValueWithErrorHandling(key, shouldCacheError) {
|
|
55
|
+
const cached = await this.getValue(key);
|
|
56
|
+
if (cached instanceof Error) {
|
|
57
|
+
if (shouldCacheError && shouldCacheError(cached)) {
|
|
58
|
+
throw cached;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return cached;
|
|
65
|
+
}
|
|
54
66
|
/**
|
|
55
67
|
* Get or fetch a value
|
|
56
68
|
*
|
|
@@ -66,15 +78,7 @@ class CacheInstance extends node_events_1.EventEmitter {
|
|
|
66
78
|
*/
|
|
67
79
|
async getOrFetchValue(key, ttl, fetchFunction, lockTtl, shouldCacheError) {
|
|
68
80
|
// already cached?
|
|
69
|
-
let cached = await this.
|
|
70
|
-
if (cached instanceof Error) {
|
|
71
|
-
if (shouldCacheError) {
|
|
72
|
-
throw cached;
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
cached = undefined;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
81
|
+
let cached = await this.getValueWithErrorHandling(key, shouldCacheError);
|
|
78
82
|
if (cached !== undefined) {
|
|
79
83
|
return cached;
|
|
80
84
|
}
|
|
@@ -83,54 +87,61 @@ class CacheInstance extends node_events_1.EventEmitter {
|
|
|
83
87
|
if (currentFetch) {
|
|
84
88
|
return currentFetch;
|
|
85
89
|
}
|
|
86
|
-
// I'm the one fetching
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (shouldCacheError) {
|
|
97
|
-
throw cachedValue;
|
|
90
|
+
// I'm the one fetching - immediately register the fetch promise to prevent race conditions
|
|
91
|
+
this.activeFetches[key] = (async () => {
|
|
92
|
+
let lock;
|
|
93
|
+
let lockErrorMessage;
|
|
94
|
+
try {
|
|
95
|
+
// get the lock if needed
|
|
96
|
+
if (lockTtl && this.isLockingSupported()) {
|
|
97
|
+
const lockName = `lock__${key}`;
|
|
98
|
+
try {
|
|
99
|
+
lock = await this.lock(lockName, lockTtl * 1000);
|
|
98
100
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
catch (lockError) {
|
|
102
|
+
lockErrorMessage = lockError instanceof Error ? lockError.message : String(lockError);
|
|
101
103
|
}
|
|
102
104
|
}
|
|
105
|
+
// check gain if the value has been populated while we previously checked
|
|
106
|
+
const cachedValue = await this.getValueWithErrorHandling(key, shouldCacheError);
|
|
103
107
|
if (cachedValue !== undefined) {
|
|
104
108
|
return cachedValue;
|
|
105
109
|
}
|
|
110
|
+
if (lockErrorMessage) {
|
|
111
|
+
throw new Error(`Failed to acquire lock for key ${key}. No active fetch or cached value found: ${lockErrorMessage}`);
|
|
112
|
+
}
|
|
113
|
+
// fetch!
|
|
114
|
+
let error;
|
|
115
|
+
let result;
|
|
116
|
+
try {
|
|
117
|
+
result = await fetchFunction();
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
error = err;
|
|
121
|
+
}
|
|
122
|
+
// cache! results: always, errors: only if satisfying user assertion
|
|
123
|
+
if (error && shouldCacheError && shouldCacheError(error)) {
|
|
124
|
+
await this.setValue(key, error, ttl);
|
|
125
|
+
}
|
|
126
|
+
else if (result !== undefined) {
|
|
127
|
+
await this.setValue(key, result, ttl);
|
|
128
|
+
}
|
|
129
|
+
if (error) {
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
106
133
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const fetchPromise = (this.activeFetches[key] = fetchFunction());
|
|
112
|
-
result = await fetchPromise;
|
|
113
|
-
}
|
|
114
|
-
catch (err) {
|
|
115
|
-
error = err;
|
|
116
|
-
}
|
|
117
|
-
// cache! results: always, errors: only if satisfying user assertion
|
|
118
|
-
if (error && shouldCacheError && shouldCacheError(error)) {
|
|
119
|
-
await this.setValue(key, error, ttl);
|
|
120
|
-
}
|
|
121
|
-
else if (result !== undefined) {
|
|
122
|
-
await this.setValue(key, result, ttl);
|
|
123
|
-
}
|
|
124
|
-
if (error) {
|
|
125
|
-
throw error;
|
|
134
|
+
finally {
|
|
135
|
+
if (lock) {
|
|
136
|
+
await this.unlock(lock);
|
|
137
|
+
}
|
|
126
138
|
}
|
|
127
|
-
|
|
139
|
+
})();
|
|
140
|
+
try {
|
|
141
|
+
return await this.activeFetches[key];
|
|
128
142
|
}
|
|
129
143
|
finally {
|
|
130
144
|
delete this.activeFetches[key];
|
|
131
|
-
if (lock) {
|
|
132
|
-
await this.unlock(lock);
|
|
133
|
-
}
|
|
134
145
|
}
|
|
135
146
|
}
|
|
136
147
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CacheInstance.js","sourceRoot":"","sources":["../../../src/lib/CacheInstance.ts"],"names":[],"mappings":";;;AAAA,6CAA2C;AAc3C,MAAsB,aAAc,SAAQ,0BAAY;IACtD,kDAAkD;IAC3C,MAAM,CAAC,gBAAgB,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAmC,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;IAC9F,MAAM,CAAC,mBAAmB,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,4BAAsC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;IAyE5G;;OAEG;IACI,kBAAkB;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;;;;OAQG;IACI,IAAI,CAAC,QAAgB,EAAE,KAAa,EAAE,KAAe;QAC1D,MAAM,IAAI,KAAK,CAAC,aAAa,CAAC,CAAC;IACjC,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,MAAM,CAAC,IAAU;QAC5B,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;IACtB,CAAC;IAED;;OAEG;IACI,OAAO,CAAC,MAAc;QAC3B,MAAM,IAAI,KAAK,CAAC,aAAa,CAAC,CAAC;IACjC,CAAC;IAED;;OAEG;IACI,IAAI;QACT,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACK,aAAa,GAA8C,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"CacheInstance.js","sourceRoot":"","sources":["../../../src/lib/CacheInstance.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/lib/CacheInstance.ts
CHANGED
|
@@ -139,6 +139,19 @@ export abstract class CacheInstance extends EventEmitter {
|
|
|
139
139
|
*/
|
|
140
140
|
private activeFetches: { [key: string]: Promise<CachableValue> } = {};
|
|
141
141
|
|
|
142
|
+
private async getValueWithErrorHandling(key: string, shouldCacheError?: (err: Error) => boolean): Promise<CachableValue> {
|
|
143
|
+
const cached = await this.getValue(key);
|
|
144
|
+
if (cached instanceof Error) {
|
|
145
|
+
if (shouldCacheError && shouldCacheError(cached)) {
|
|
146
|
+
throw cached;
|
|
147
|
+
} else {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return cached;
|
|
153
|
+
}
|
|
154
|
+
|
|
142
155
|
/**
|
|
143
156
|
* Get or fetch a value
|
|
144
157
|
*
|
|
@@ -160,14 +173,7 @@ export abstract class CacheInstance extends EventEmitter {
|
|
|
160
173
|
shouldCacheError?: (err: Error) => boolean,
|
|
161
174
|
): Promise<ReturnType<F>> {
|
|
162
175
|
// already cached?
|
|
163
|
-
let cached = await this.
|
|
164
|
-
if (cached instanceof Error) {
|
|
165
|
-
if (shouldCacheError) {
|
|
166
|
-
throw cached;
|
|
167
|
-
} else {
|
|
168
|
-
cached = undefined;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
176
|
+
let cached = await this.getValueWithErrorHandling(key, shouldCacheError);
|
|
171
177
|
if (cached !== undefined) {
|
|
172
178
|
return cached;
|
|
173
179
|
}
|
|
@@ -178,53 +184,63 @@ export abstract class CacheInstance extends EventEmitter {
|
|
|
178
184
|
return currentFetch;
|
|
179
185
|
}
|
|
180
186
|
|
|
181
|
-
// I'm the one fetching
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
cachedValue = undefined;
|
|
187
|
+
// I'm the one fetching - immediately register the fetch promise to prevent race conditions
|
|
188
|
+
this.activeFetches[key] = (async () => {
|
|
189
|
+
let lock: Lock | undefined;
|
|
190
|
+
let lockErrorMessage: string | undefined;
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
// get the lock if needed
|
|
194
|
+
if (lockTtl && this.isLockingSupported()) {
|
|
195
|
+
const lockName = `lock__${key}`;
|
|
196
|
+
try {
|
|
197
|
+
lock = await this.lock(lockName, lockTtl * 1000);
|
|
198
|
+
} catch (lockError) {
|
|
199
|
+
lockErrorMessage = lockError instanceof Error ? lockError.message : String(lockError);
|
|
195
200
|
}
|
|
196
201
|
}
|
|
202
|
+
|
|
203
|
+
// check gain if the value has been populated while we previously checked
|
|
204
|
+
const cachedValue = await this.getValueWithErrorHandling(key, shouldCacheError);
|
|
197
205
|
if (cachedValue !== undefined) {
|
|
198
206
|
return cachedValue;
|
|
199
207
|
}
|
|
200
|
-
}
|
|
201
208
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
try {
|
|
206
|
-
const fetchPromise = (this.activeFetches[key] = fetchFunction());
|
|
207
|
-
result = await fetchPromise;
|
|
208
|
-
} catch (err) {
|
|
209
|
-
error = err;
|
|
210
|
-
}
|
|
209
|
+
if (lockErrorMessage) {
|
|
210
|
+
throw new Error(`Failed to acquire lock for key ${key}. No active fetch or cached value found: ${lockErrorMessage}`);
|
|
211
|
+
}
|
|
211
212
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
213
|
+
// fetch!
|
|
214
|
+
let error: Error | undefined;
|
|
215
|
+
let result: any;
|
|
216
|
+
try {
|
|
217
|
+
result = await fetchFunction();
|
|
218
|
+
} catch (err) {
|
|
219
|
+
error = err;
|
|
220
|
+
}
|
|
218
221
|
|
|
219
|
-
|
|
220
|
-
|
|
222
|
+
// cache! results: always, errors: only if satisfying user assertion
|
|
223
|
+
if (error && shouldCacheError && shouldCacheError(error)) {
|
|
224
|
+
await this.setValue(key, error, ttl);
|
|
225
|
+
} else if (result !== undefined) {
|
|
226
|
+
await this.setValue(key, result, ttl);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (error) {
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
return result;
|
|
233
|
+
} finally {
|
|
234
|
+
if (lock) {
|
|
235
|
+
await this.unlock(lock);
|
|
236
|
+
}
|
|
221
237
|
}
|
|
222
|
-
|
|
238
|
+
})();
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
return await this.activeFetches[key];
|
|
223
242
|
} finally {
|
|
224
243
|
delete this.activeFetches[key];
|
|
225
|
-
if (lock) {
|
|
226
|
-
await this.unlock(lock);
|
|
227
|
-
}
|
|
228
244
|
}
|
|
229
245
|
}
|
|
230
246
|
}
|
package/test/CacheClient_test.ts
CHANGED
|
@@ -132,6 +132,50 @@ describe('CacheClient', () => {
|
|
|
132
132
|
expect(myObj.numCalled).to.equal(2); // from cache -> NO increase
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
+
it('correctly evaluates shouldCacheError callback when retrieving cached errors', async () => {
|
|
136
|
+
const cache = new LocalCache();
|
|
137
|
+
const key = 'test-key';
|
|
138
|
+
|
|
139
|
+
// Manually cache an error with a specific property
|
|
140
|
+
const cachedError = new Error('cached error');
|
|
141
|
+
cachedError['errorType'] = 'temporary';
|
|
142
|
+
await cache.setValue(key, cachedError, 60);
|
|
143
|
+
|
|
144
|
+
// Try to retrieve with a shouldCacheError that should reject this error
|
|
145
|
+
const result = await cache.getOrFetchValue(
|
|
146
|
+
key,
|
|
147
|
+
60,
|
|
148
|
+
async () => 'fresh value',
|
|
149
|
+
undefined,
|
|
150
|
+
(err) => err['errorType'] === 'permanent', // Only cache permanent errors
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Should return 'fresh value' because the cached error doesn't match the callback criteria
|
|
154
|
+
expect(result).to.equal('fresh value');
|
|
155
|
+
|
|
156
|
+
// Now try with a callback that accepts the cached error
|
|
157
|
+
const cachedError2 = new Error('cached error 2');
|
|
158
|
+
cachedError2['errorType'] = 'permanent';
|
|
159
|
+
await cache.setValue(key, cachedError2, 60);
|
|
160
|
+
|
|
161
|
+
let didThrow = false;
|
|
162
|
+
try {
|
|
163
|
+
await cache.getOrFetchValue(
|
|
164
|
+
key,
|
|
165
|
+
60,
|
|
166
|
+
async () => 'fresh value',
|
|
167
|
+
undefined,
|
|
168
|
+
(err) => err['errorType'] === 'permanent', // Only cache permanent errors
|
|
169
|
+
);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
didThrow = true;
|
|
172
|
+
expect(err.message).to.equal('cached error 2');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Should throw because the cached error matches the callback criteria
|
|
176
|
+
expect(didThrow).to.be.true;
|
|
177
|
+
});
|
|
178
|
+
|
|
135
179
|
it('protect against concurrent fetches', async () => {
|
|
136
180
|
const myObj = new MyClass();
|
|
137
181
|
const jobs: Promise<any>[] = [];
|
|
@@ -456,5 +456,63 @@ function runTests(name: string, cache: CacheInstance): void {
|
|
|
456
456
|
sinon.assert.calledTwice(lockSpy); // includes our own call above
|
|
457
457
|
sinon.assert.calledTwice(unlockSpy);
|
|
458
458
|
});
|
|
459
|
+
|
|
460
|
+
ifLockIt('returns cached value if lock acquisition fails and value was populated meanwhile', async () => {
|
|
461
|
+
const key = `key${Math.random()}`;
|
|
462
|
+
let numCalled = 0;
|
|
463
|
+
const object = {
|
|
464
|
+
fetch: async (v) => {
|
|
465
|
+
numCalled++;
|
|
466
|
+
return v;
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const fetchFunction = object.fetch.bind(object, 'newvalue');
|
|
471
|
+
|
|
472
|
+
// Restore the spy temporarily so we can stub it
|
|
473
|
+
lockSpy.restore();
|
|
474
|
+
|
|
475
|
+
const lockStub = sinon.stub(cache, 'lock').callsFake(async () => {
|
|
476
|
+
// Before failing, simulate the cache being populated
|
|
477
|
+
await cache.setValue(key, 'from-cache');
|
|
478
|
+
throw new Error('Lock acquisition failed');
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
const value = await cache.getOrFetchValue(key, 10, fetchFunction, 1);
|
|
482
|
+
|
|
483
|
+
expect(value).to.eql('from-cache');
|
|
484
|
+
expect(numCalled).to.eql(0); // never called fetch, got from cache
|
|
485
|
+
|
|
486
|
+
lockStub.restore();
|
|
487
|
+
lockSpy = sinon.spy(cache, 'lock');
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
ifLockIt('throws detailed error if lock fails with no activeFetch or cached value', async () => {
|
|
491
|
+
const key = `key${Math.random()}`;
|
|
492
|
+
const object = {
|
|
493
|
+
fetch: async (v) => v,
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
const fetchFunction = object.fetch.bind(object, 'newvalue');
|
|
497
|
+
|
|
498
|
+
// Restore the spy temporarily so we can stub it
|
|
499
|
+
lockSpy.restore();
|
|
500
|
+
|
|
501
|
+
const lockStub = sinon.stub(cache, 'lock').rejects(new Error('Lock acquisition failed'));
|
|
502
|
+
|
|
503
|
+
let thrownError: Error | undefined;
|
|
504
|
+
try {
|
|
505
|
+
await cache.getOrFetchValue(key, 10, fetchFunction, 1);
|
|
506
|
+
} catch (err) {
|
|
507
|
+
thrownError = err as Error;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
expect(thrownError).to.exist;
|
|
511
|
+
expect(thrownError!.message).to.include(`Failed to acquire lock for key ${key}. No active fetch or cached value found: Lock acquisition failed`);
|
|
512
|
+
|
|
513
|
+
// Restore and recreate the spy
|
|
514
|
+
lockStub.restore();
|
|
515
|
+
lockSpy = sinon.spy(cache, 'lock');
|
|
516
|
+
});
|
|
459
517
|
});
|
|
460
518
|
}
|