cachette 4.0.12 → 4.0.14
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/.prettierignore +15 -0
- package/.prettierrc +8 -0
- package/dist/src/lib/CacheClient.js +10 -9
- package/dist/src/lib/CacheClient.js.map +1 -1
- package/dist/src/lib/CacheInstance.js +4 -2
- package/dist/src/lib/CacheInstance.js.map +1 -1
- package/dist/src/lib/LocalCache.js +10 -4
- package/dist/src/lib/LocalCache.js.map +1 -1
- package/dist/src/lib/RedisCache.js +23 -18
- package/dist/src/lib/RedisCache.js.map +1 -1
- package/dist/src/lib/WriteThroughCache.js +5 -5
- package/dist/src/lib/WriteThroughCache.js.map +1 -1
- package/package.json +8 -6
- package/src/lib/CacheClient.ts +42 -55
- package/src/lib/CacheInstance.ts +6 -10
- package/src/lib/LocalCache.ts +14 -10
- package/src/lib/RedisCache.ts +43 -38
- package/src/lib/WriteThroughCache.ts +27 -27
- package/test/CacheClient_test.ts +25 -44
- package/test/CacheInstance_test.ts +12 -40
- package/test/LocalCache_test.ts +2 -8
- package/test/RedisCache_test.ts +20 -20
- package/test/WriteThroughCache_test.ts +34 -39
package/test/LocalCache_test.ts
CHANGED
|
@@ -2,9 +2,7 @@ import { expect } from 'chai';
|
|
|
2
2
|
|
|
3
3
|
import { LocalCache } from '../src/lib/LocalCache';
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
describe('LocalCache', () => {
|
|
7
|
-
|
|
8
6
|
it('can set values', async () => {
|
|
9
7
|
const cache = new LocalCache();
|
|
10
8
|
const wasSet = await cache.setValue('key', 'value');
|
|
@@ -50,13 +48,12 @@ describe('LocalCache', () => {
|
|
|
50
48
|
|
|
51
49
|
it('can set values with expiry', async () => {
|
|
52
50
|
const cache = new LocalCache();
|
|
53
|
-
await cache.setValue('key', 'value', .2);
|
|
51
|
+
await cache.setValue('key', 'value', 0.2);
|
|
54
52
|
let value = await cache.getValue('key');
|
|
55
53
|
expect(value).to.equal('value');
|
|
56
54
|
await sleep(250);
|
|
57
55
|
value = await cache.getValue('key');
|
|
58
56
|
expect(value).to.equal(undefined);
|
|
59
|
-
|
|
60
57
|
});
|
|
61
58
|
|
|
62
59
|
it('can delete a value', async () => {
|
|
@@ -67,7 +64,6 @@ describe('LocalCache', () => {
|
|
|
67
64
|
await cache.delValue('key');
|
|
68
65
|
|
|
69
66
|
expect(await cache.getValue('key')).not.to.exist;
|
|
70
|
-
|
|
71
67
|
});
|
|
72
68
|
|
|
73
69
|
it('will not grow in size past the maximum size', async () => {
|
|
@@ -92,8 +88,7 @@ describe('LocalCache', () => {
|
|
|
92
88
|
|
|
93
89
|
// restore
|
|
94
90
|
LocalCache['DEFAULT_MAX_ITEMS'] = origMax;
|
|
95
|
-
|
|
96
|
-
})
|
|
91
|
+
});
|
|
97
92
|
|
|
98
93
|
it('can get the remaining TTL of an item', async () => {
|
|
99
94
|
const cache = new LocalCache();
|
|
@@ -120,7 +115,6 @@ describe('LocalCache', () => {
|
|
|
120
115
|
expect(wasSet).to.be.true;
|
|
121
116
|
expect(cacheTtl).to.equal(0);
|
|
122
117
|
});
|
|
123
|
-
|
|
124
118
|
});
|
|
125
119
|
|
|
126
120
|
function sleep(ms: number): Promise<void> {
|
package/test/RedisCache_test.ts
CHANGED
|
@@ -3,9 +3,7 @@ import * as sinon from 'sinon';
|
|
|
3
3
|
|
|
4
4
|
import { RedisCache, SIZE_THRESHOLD_WARNING_BYTES } from '../src/lib/RedisCache';
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
describe('RedisCache', () => {
|
|
8
|
-
|
|
9
7
|
describe('constructor', () => {
|
|
10
8
|
it('will not crash the application given an invalid Redis URL', async () => {
|
|
11
9
|
let cache = new RedisCache('redis://localhost:9999');
|
|
@@ -17,20 +15,15 @@ describe('RedisCache', () => {
|
|
|
17
15
|
});
|
|
18
16
|
|
|
19
17
|
it('will raise an error if given a Redis URL without protocol', async () => {
|
|
20
|
-
expect(
|
|
21
|
-
() => new RedisCache('rer17kq3qdwc5wmy.4gzf3f.ng.0001.use1.cache.amazonaws.com'),
|
|
22
|
-
).to.throw();
|
|
18
|
+
expect(() => new RedisCache('rer17kq3qdwc5wmy.4gzf3f.ng.0001.use1.cache.amazonaws.com')).to.throw();
|
|
23
19
|
});
|
|
24
20
|
|
|
25
21
|
it('will raise an error if given a Redis URL with an invalid protocol', async () => {
|
|
26
|
-
expect(
|
|
27
|
-
() => new RedisCache('potato://rer17kq3qdwc5wmy.4gzf3f.ng.0001.use1.cache.amazonaws.com'),
|
|
28
|
-
).to.throw();
|
|
22
|
+
expect(() => new RedisCache('potato://rer17kq3qdwc5wmy.4gzf3f.ng.0001.use1.cache.amazonaws.com')).to.throw();
|
|
29
23
|
});
|
|
30
24
|
});
|
|
31
25
|
|
|
32
26
|
describe('value serialization', () => {
|
|
33
|
-
|
|
34
27
|
it('can serialize the null value', () => {
|
|
35
28
|
let value = RedisCache.serializeValue(null);
|
|
36
29
|
expect(value).to.equal(RedisCache.NULL_VALUE);
|
|
@@ -58,12 +51,15 @@ describe('RedisCache', () => {
|
|
|
58
51
|
value = RedisCache.deserializeValue(value);
|
|
59
52
|
expect(value).to.deep.equal(obj);
|
|
60
53
|
});
|
|
61
|
-
|
|
54
|
+
|
|
62
55
|
it('can serialize an object with a nested map', () => {
|
|
63
|
-
const mapStructure: Map<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
const mapStructure: Map<
|
|
57
|
+
string,
|
|
58
|
+
{
|
|
59
|
+
checksum: number;
|
|
60
|
+
originCommentId?: string;
|
|
61
|
+
}
|
|
62
|
+
> = new Map();
|
|
67
63
|
mapStructure.set('key1', { checksum: 1, originCommentId: 'c1' });
|
|
68
64
|
mapStructure.set('key2', { checksum: 2, originCommentId: 'c2' });
|
|
69
65
|
const obj = {
|
|
@@ -83,7 +79,7 @@ describe('RedisCache', () => {
|
|
|
83
79
|
value = RedisCache.deserializeValue(value);
|
|
84
80
|
expect(value).to.deep.equal(obj);
|
|
85
81
|
});
|
|
86
|
-
|
|
82
|
+
|
|
87
83
|
it('can serialize an object with a nested set', () => {
|
|
88
84
|
const setStructure: Set<string> = new Set();
|
|
89
85
|
setStructure.add('key1');
|
|
@@ -124,7 +120,6 @@ describe('RedisCache', () => {
|
|
|
124
120
|
const value = RedisCache.deserializeValue(null);
|
|
125
121
|
expect(value).to.equal(undefined);
|
|
126
122
|
});
|
|
127
|
-
|
|
128
123
|
});
|
|
129
124
|
|
|
130
125
|
describe('setValue', async () => {
|
|
@@ -237,7 +232,10 @@ describe('RedisCache', () => {
|
|
|
237
232
|
const key = `emits-warning-on-large-value-${Math.random()}`;
|
|
238
233
|
await cache.setValue(key, 'a'.repeat(SIZE_THRESHOLD_WARNING_BYTES));
|
|
239
234
|
|
|
240
|
-
const warningsAfterSetLargeKey = warnSpy
|
|
235
|
+
const warningsAfterSetLargeKey = warnSpy
|
|
236
|
+
.getCalls()
|
|
237
|
+
.map((c) => c.firstArg)
|
|
238
|
+
.filter((msg) => msg.includes('Writing large value to Redis!'));
|
|
241
239
|
expect(warningsAfterSetLargeKey.length).to.equal(1);
|
|
242
240
|
});
|
|
243
241
|
|
|
@@ -254,13 +252,15 @@ describe('RedisCache', () => {
|
|
|
254
252
|
const key = `doesnt-emit-warning-on-small-value-${Math.random()}`;
|
|
255
253
|
await cache.setValue(key, 'a'.repeat(SIZE_THRESHOLD_WARNING_BYTES - 1));
|
|
256
254
|
|
|
257
|
-
const warningsAfterSetLargeKey = warnSpy
|
|
255
|
+
const warningsAfterSetLargeKey = warnSpy
|
|
256
|
+
.getCalls()
|
|
257
|
+
.map((c) => c.firstArg)
|
|
258
|
+
.filter((msg) => msg.includes('Writing large value to Redis!'));
|
|
258
259
|
expect(warningsAfterSetLargeKey.length).to.equal(0);
|
|
259
260
|
});
|
|
260
261
|
});
|
|
261
262
|
|
|
262
263
|
describe('itemCount', async () => {
|
|
263
|
-
|
|
264
264
|
it('can count the items in the redis cache.', async function (): Promise<void> {
|
|
265
265
|
if (!process.env.TEST_REDIS_URL) {
|
|
266
266
|
this.skip();
|
|
@@ -293,7 +293,7 @@ describe('RedisCache', () => {
|
|
|
293
293
|
await cache.clear();
|
|
294
294
|
|
|
295
295
|
await cache.setValue('test1', 'value1');
|
|
296
|
-
|
|
296
|
+
|
|
297
297
|
const replicationAcknowledged = await cache.waitForReplication(0, 50);
|
|
298
298
|
|
|
299
299
|
// No replicas so we expect 0. This test basically confirms that waitForReplication doesn't crash. 🤷♂️
|
|
@@ -11,24 +11,18 @@ function makeFakeWriteThroughCache(): WriteThroughCache {
|
|
|
11
11
|
return cache;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
15
14
|
describe('WriteThroughCache', () => {
|
|
16
|
-
|
|
17
15
|
it('will fallback to using a local cache is no connection to redis is made', async () => {
|
|
18
|
-
|
|
19
16
|
const cache = new WriteThroughCache('redis://localhost:9999');
|
|
20
17
|
|
|
21
18
|
// We know the connection was not made, but we should still be able to use the local cache.
|
|
22
19
|
await cache.setValue('key', 'value');
|
|
23
20
|
const value = await cache.getValue('key');
|
|
24
21
|
expect(value).to.equal('value');
|
|
25
|
-
|
|
26
22
|
});
|
|
27
23
|
|
|
28
24
|
describe('setValue', () => {
|
|
29
|
-
|
|
30
25
|
it('will write the value in both caches', async () => {
|
|
31
|
-
|
|
32
26
|
const cache = makeFakeWriteThroughCache();
|
|
33
27
|
|
|
34
28
|
const response = await cache.setValue('key', 'value');
|
|
@@ -42,15 +36,11 @@ describe('WriteThroughCache', () => {
|
|
|
42
36
|
|
|
43
37
|
value = await cache['redisCacheForReading'].getValue('key');
|
|
44
38
|
expect(value).to.equal('value');
|
|
45
|
-
|
|
46
39
|
});
|
|
47
|
-
|
|
48
40
|
});
|
|
49
41
|
|
|
50
42
|
describe('getValue', () => {
|
|
51
|
-
|
|
52
43
|
it('will populate the local cache with right ttl when fetching from redis', async function (): Promise<void> {
|
|
53
|
-
|
|
54
44
|
if (!process.env.TEST_REDIS_URL) {
|
|
55
45
|
this.skip();
|
|
56
46
|
}
|
|
@@ -64,15 +54,18 @@ describe('WriteThroughCache', () => {
|
|
|
64
54
|
|
|
65
55
|
let value = await cache.getValue('key');
|
|
66
56
|
expect(value).to.equal('value');
|
|
67
|
-
sinon.assert.calledWith(
|
|
57
|
+
sinon.assert.calledWith(
|
|
58
|
+
spy,
|
|
59
|
+
'key',
|
|
60
|
+
'value',
|
|
61
|
+
sinon.match((ttl) => ttl > 99.9 && ttl <= 100),
|
|
62
|
+
);
|
|
68
63
|
|
|
69
64
|
value = await cache['localCache'].getValue('key');
|
|
70
65
|
expect(value).to.equal('value');
|
|
71
|
-
|
|
72
66
|
});
|
|
73
67
|
|
|
74
68
|
it('will get directly from the local cache if available', async () => {
|
|
75
|
-
|
|
76
69
|
const cache = makeFakeWriteThroughCache();
|
|
77
70
|
|
|
78
71
|
await cache['localCache'].setValue('key', 'value');
|
|
@@ -82,11 +75,9 @@ describe('WriteThroughCache', () => {
|
|
|
82
75
|
|
|
83
76
|
value = await cache['redisCacheForReading'].getValue('key');
|
|
84
77
|
expect(value).not.to.exist;
|
|
85
|
-
|
|
86
78
|
});
|
|
87
79
|
|
|
88
80
|
it('will populate the local cache for a null value', async () => {
|
|
89
|
-
|
|
90
81
|
const cache = makeFakeWriteThroughCache();
|
|
91
82
|
|
|
92
83
|
await cache['redisCacheForWriting'].setValue('key', null);
|
|
@@ -96,11 +87,9 @@ describe('WriteThroughCache', () => {
|
|
|
96
87
|
|
|
97
88
|
value = await cache['localCache'].getValue('key');
|
|
98
89
|
expect(value).to.equal(null);
|
|
99
|
-
|
|
100
90
|
});
|
|
101
91
|
|
|
102
92
|
it('will populate the local cache for an empty string', async () => {
|
|
103
|
-
|
|
104
93
|
const cache = makeFakeWriteThroughCache();
|
|
105
94
|
|
|
106
95
|
await cache['redisCacheForWriting'].setValue('key', '');
|
|
@@ -110,7 +99,6 @@ describe('WriteThroughCache', () => {
|
|
|
110
99
|
|
|
111
100
|
value = await cache['localCache'].getValue('key');
|
|
112
101
|
expect(value).to.equal('');
|
|
113
|
-
|
|
114
102
|
});
|
|
115
103
|
|
|
116
104
|
it('returns nothing if value in cache has expired', async function (): Promise<void> {
|
|
@@ -120,17 +108,14 @@ describe('WriteThroughCache', () => {
|
|
|
120
108
|
const cache = new WriteThroughCache(process.env.TEST_REDIS_URL as string);
|
|
121
109
|
await cache.setValue('fakeKey', 'fakeValue', 0.1);
|
|
122
110
|
// sleep 100 ms
|
|
123
|
-
await new Promise(resolve => setTimeout(resolve, 150));
|
|
111
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
124
112
|
const fakeValue = await cache.getValue('fakeKey');
|
|
125
113
|
expect(fakeValue).to.be.undefined;
|
|
126
114
|
});
|
|
127
|
-
|
|
128
115
|
});
|
|
129
116
|
|
|
130
117
|
describe('delValue', () => {
|
|
131
|
-
|
|
132
118
|
it('will delete values from both caches', async () => {
|
|
133
|
-
|
|
134
119
|
const cache = makeFakeWriteThroughCache();
|
|
135
120
|
|
|
136
121
|
await cache.setValue('key', 'value');
|
|
@@ -148,30 +133,26 @@ describe('WriteThroughCache', () => {
|
|
|
148
133
|
|
|
149
134
|
value = await cache['localCache'].getValue('key');
|
|
150
135
|
expect(value).not.to.exist;
|
|
151
|
-
|
|
152
136
|
});
|
|
153
|
-
|
|
154
137
|
});
|
|
155
138
|
|
|
156
139
|
describe('clear', () => {
|
|
157
|
-
|
|
158
140
|
it('clears from both caches', async () => {
|
|
159
141
|
const cache = makeFakeWriteThroughCache();
|
|
160
142
|
|
|
161
143
|
const base = [...Array(10).keys()];
|
|
162
|
-
await Promise.all(base.map(i => cache.setValue(`key${i}`, i)));
|
|
144
|
+
await Promise.all(base.map((i) => cache.setValue(`key${i}`, i)));
|
|
163
145
|
expect(await cache.itemCount()).to.equal(20);
|
|
164
146
|
|
|
165
|
-
let values = await Promise.all(base.map(i => cache.getValue(`key${i}`)));
|
|
147
|
+
let values = await Promise.all(base.map((i) => cache.getValue(`key${i}`)));
|
|
166
148
|
expect(values.sort()).to.eql(base);
|
|
167
149
|
|
|
168
150
|
await cache.clear();
|
|
169
151
|
expect(await cache.itemCount()).to.equal(0);
|
|
170
152
|
|
|
171
|
-
values = await Promise.all(base.map(i => cache.getValue(`key${i}`)));
|
|
172
|
-
values.forEach(value => expect(value).to.be.undefined);
|
|
153
|
+
values = await Promise.all(base.map((i) => cache.getValue(`key${i}`)));
|
|
154
|
+
values.forEach((value) => expect(value).to.be.undefined);
|
|
173
155
|
});
|
|
174
|
-
|
|
175
156
|
});
|
|
176
157
|
|
|
177
158
|
describe('metrics', () => {
|
|
@@ -187,7 +168,7 @@ describe('WriteThroughCache', () => {
|
|
|
187
168
|
afterEach(() => {
|
|
188
169
|
delete process.env.CACHETTE_METRICS_PERIOD_MINUTES;
|
|
189
170
|
sinonClock.restore();
|
|
190
|
-
})
|
|
171
|
+
});
|
|
191
172
|
|
|
192
173
|
it('stores metrics if env. var is set', async function () {
|
|
193
174
|
if (!process.env.TEST_REDIS_URL) {
|
|
@@ -282,25 +263,39 @@ describe('WriteThroughCache', () => {
|
|
|
282
263
|
await cache.getValue(key);
|
|
283
264
|
|
|
284
265
|
// Testing we emitted at requested period
|
|
285
|
-
const metricsReports0 = infoSpy
|
|
266
|
+
const metricsReports0 = infoSpy
|
|
267
|
+
.getCalls()
|
|
268
|
+
.map((c) => c.firstArg)
|
|
269
|
+
.filter((msg) => msg.includes('metrics'));
|
|
286
270
|
expect(metricsReports0.length).to.equal(0);
|
|
287
271
|
|
|
288
272
|
sinonClock.tick(59999); // t = 1min minus a millisecond
|
|
289
|
-
const metricsReports59s = infoSpy
|
|
273
|
+
const metricsReports59s = infoSpy
|
|
274
|
+
.getCalls()
|
|
275
|
+
.map((c) => c.firstArg)
|
|
276
|
+
.filter((msg) => msg.includes('metrics'));
|
|
290
277
|
expect(metricsReports59s.length).to.equal(0);
|
|
291
278
|
|
|
292
279
|
sinonClock.tick(2); // t = 1min plus a millisecond
|
|
293
|
-
const metricsReports61s = infoSpy
|
|
280
|
+
const metricsReports61s = infoSpy
|
|
281
|
+
.getCalls()
|
|
282
|
+
.map((c) => c.firstArg)
|
|
283
|
+
.filter((msg) => msg.includes('metrics'));
|
|
294
284
|
expect(metricsReports61s.length).to.equal(1);
|
|
295
|
-
expect(metricsReports61s[0]).to.equal(
|
|
285
|
+
expect(metricsReports61s[0]).to.equal(
|
|
286
|
+
'WriteThroughCache metrics during last 1 min - Total: 3, Local hits: 1 (33%), Redis hits: 1 (33%), Double misses: 1 (33%).',
|
|
287
|
+
);
|
|
296
288
|
|
|
297
289
|
// Causing no activity for a minute and testing we emit again
|
|
298
290
|
sinonClock.tick(60000); // t = 2min plus a millisecond
|
|
299
|
-
const metricsReports121s = infoSpy
|
|
291
|
+
const metricsReports121s = infoSpy
|
|
292
|
+
.getCalls()
|
|
293
|
+
.map((c) => c.firstArg)
|
|
294
|
+
.filter((msg) => msg.includes('metrics'));
|
|
300
295
|
expect(metricsReports121s.length).to.equal(2);
|
|
301
|
-
expect(metricsReports121s[1]).to.equal(
|
|
296
|
+
expect(metricsReports121s[1]).to.equal(
|
|
297
|
+
'WriteThroughCache metrics during last 1 min - Total: 0, Local hits: 0 (0%), Redis hits: 0 (0%), Double misses: 0 (0%).',
|
|
298
|
+
);
|
|
302
299
|
});
|
|
303
|
-
|
|
304
300
|
});
|
|
305
|
-
|
|
306
301
|
});
|