limited-cache 1.1.1 → 2.1.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 +19 -5
- package/CODE_OF_CONDUCT.md +103 -47
- package/{CONTRIBUTING.md → CONTRIBUTING.template.md} +4 -2
- package/LICENSE +1 -1
- package/README.md +26 -18
- package/dist/core/LimitedCache.d.ts +4 -3
- package/dist/core/LimitedCache.d.ts.map +1 -0
- package/dist/core/LimitedCache.js +29 -0
- package/dist/core/LimitedCache.js.map +1 -0
- package/dist/core/LimitedCacheObject.d.ts +5 -3
- package/dist/core/LimitedCacheObject.d.ts.map +1 -0
- package/dist/core/LimitedCacheObject.js +52 -0
- package/dist/core/LimitedCacheObject.js.map +1 -0
- package/dist/core/builtIns.d.ts +12 -11
- package/dist/core/builtIns.d.ts.map +1 -0
- package/dist/core/builtIns.js +5 -0
- package/dist/core/builtIns.js.map +1 -0
- package/dist/core/defaultOptions.d.ts +6 -6
- package/dist/core/defaultOptions.d.ts.map +1 -0
- package/dist/core/defaultOptions.js +14 -0
- package/dist/core/defaultOptions.js.map +1 -0
- package/dist/core/limitedCacheUtil.d.ts +13 -12
- package/dist/core/limitedCacheUtil.d.ts.map +1 -0
- package/dist/core/limitedCacheUtil.js +14 -0
- package/dist/core/limitedCacheUtil.js.map +1 -0
- package/dist/core/lowLevelFunctions.d.ts +14 -13
- package/dist/core/lowLevelFunctions.d.ts.map +1 -0
- package/dist/core/lowLevelFunctions.js +240 -0
- package/dist/core/lowLevelFunctions.js.map +1 -0
- package/dist/index.cjs +388 -0
- package/dist/index.d.ts +7 -10
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -8
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +58 -60
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +86 -73
- package/src/__tests__/LimitedCache.test.ts +186 -0
- package/src/__tests__/LimitedCacheObject.test.ts +150 -0
- package/src/__tests__/lowLevelFunctions.test.ts +430 -0
- package/src/__tests__/scenarios/keys.test.ts +67 -0
- package/src/__tests__/scenarios/maxCacheSize.test.ts +126 -0
- package/src/__tests__/scenarios/maxCacheTime.test.ts +150 -0
- package/src/__tests__/scenarios/values.test.ts +49 -0
- package/src/__tests__/typeChecks/LimitedCacheObjectTypes.test.ts +70 -0
- package/src/__tests__/typeChecks/LimitedCacheTypes.test.ts +70 -0
- package/src/__tests__/typeChecks/lowLevelFunctionsTypes.test.ts +70 -0
- package/src/core/LimitedCache.ts +1 -1
- package/src/core/LimitedCacheObject.ts +15 -3
- package/src/core/defaultOptions.ts +1 -2
- package/src/core/limitedCacheUtil.ts +1 -1
- package/src/core/lowLevelFunctions.ts +6 -6
- package/src/index.ts +0 -8
- package/dist/limited-cache.cjs.development.js +0 -456
- package/dist/limited-cache.cjs.development.js.map +0 -1
- package/dist/limited-cache.cjs.production.min.js +0 -2
- package/dist/limited-cache.cjs.production.min.js.map +0 -1
- package/dist/limited-cache.esm.js +0 -436
- package/dist/limited-cache.esm.js.map +0 -1
- package/legacy-types/ts3.x/dist/core/LimitedCache.d.ts +0 -3
- package/legacy-types/ts3.x/dist/core/LimitedCacheObject.d.ts +0 -3
- package/legacy-types/ts3.x/dist/core/builtIns.d.ts +0 -11
- package/legacy-types/ts3.x/dist/core/defaultOptions.d.ts +0 -6
- package/legacy-types/ts3.x/dist/core/limitedCacheUtil.d.ts +0 -12
- package/legacy-types/ts3.x/dist/core/lowLevelFunctions.d.ts +0 -13
- package/legacy-types/ts3.x/dist/index.d.ts +0 -10
- package/legacy-types/ts3.x/dist/types.d.ts +0 -60
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { describe, beforeEach, expect, it, vitest } from 'vitest';
|
|
2
|
+
import { defaultOptions } from '../core/defaultOptions';
|
|
3
|
+
import {
|
|
4
|
+
isCacheMeta,
|
|
5
|
+
upgradeCacheMeta,
|
|
6
|
+
lowLevelInit,
|
|
7
|
+
lowLevelGetOne,
|
|
8
|
+
lowLevelGetAll,
|
|
9
|
+
lowLevelHas,
|
|
10
|
+
lowLevelSet,
|
|
11
|
+
lowLevelRemove,
|
|
12
|
+
lowLevelReset,
|
|
13
|
+
} from '../core/lowLevelFunctions';
|
|
14
|
+
import { LimitedCacheMeta } from '../types';
|
|
15
|
+
|
|
16
|
+
describe('lowLevelFunctions', () => {
|
|
17
|
+
describe('isCacheMeta', () => {
|
|
18
|
+
it('accepts cacheMeta shapes', () => {
|
|
19
|
+
expect(
|
|
20
|
+
isCacheMeta({
|
|
21
|
+
limitedCacheMetaVersion: 123,
|
|
22
|
+
options: defaultOptions,
|
|
23
|
+
cache: {},
|
|
24
|
+
keyList: [],
|
|
25
|
+
keyInfo: {},
|
|
26
|
+
opsLeft: 100,
|
|
27
|
+
}),
|
|
28
|
+
).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
it('rejects invalid cacheMeta shapes', () => {
|
|
31
|
+
expect(
|
|
32
|
+
// @ts-expect-error Invalid cacheMeta shape
|
|
33
|
+
isCacheMeta({
|
|
34
|
+
cache: {},
|
|
35
|
+
}),
|
|
36
|
+
).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('upgradeCacheMeta', () => {
|
|
41
|
+
it('warns and upgrades if given an older, incompatible cacheMeta', () => {
|
|
42
|
+
const consoleWarnSpy = vitest.spyOn(console, 'warn').mockReturnValueOnce();
|
|
43
|
+
|
|
44
|
+
const cacheMeta = lowLevelInit();
|
|
45
|
+
cacheMeta.limitedCacheMetaVersion = 1;
|
|
46
|
+
upgradeCacheMeta(cacheMeta);
|
|
47
|
+
|
|
48
|
+
expect(cacheMeta.limitedCacheMetaVersion).toEqual(2);
|
|
49
|
+
|
|
50
|
+
const consoleErrorCalls = consoleWarnSpy.mock.calls;
|
|
51
|
+
expect(consoleErrorCalls.length).toBe(1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('throws if given an invalid cacheMeta', () => {
|
|
55
|
+
expect(() => {
|
|
56
|
+
// @ts-expect-error Invalid cacheMeta shape
|
|
57
|
+
upgradeCacheMeta({
|
|
58
|
+
cache: {},
|
|
59
|
+
});
|
|
60
|
+
}).toThrowError();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('lowLevelInit', () => {
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
vitest.restoreAllMocks();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('clones the default options', () => {
|
|
70
|
+
const myCacheMeta = lowLevelInit();
|
|
71
|
+
|
|
72
|
+
expect(myCacheMeta.options).toEqual(defaultOptions);
|
|
73
|
+
expect(myCacheMeta.options === defaultOptions).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('clones provided options', () => {
|
|
77
|
+
const myOptions = {
|
|
78
|
+
maxCacheSize: 123,
|
|
79
|
+
maxCacheTime: 456,
|
|
80
|
+
warnIfItemPurgedBeforeTime: 999,
|
|
81
|
+
opLimit: 10,
|
|
82
|
+
scanLimit: 100,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const myCacheMeta = lowLevelInit(myOptions);
|
|
86
|
+
|
|
87
|
+
expect(myCacheMeta.options).toEqual(myOptions);
|
|
88
|
+
expect(myCacheMeta.options === myOptions).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('merges provided options with defaults', () => {
|
|
92
|
+
const myOptions = {
|
|
93
|
+
maxCacheSize: 123,
|
|
94
|
+
maxCacheTime: 456,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const myCacheMeta = lowLevelInit(myOptions);
|
|
98
|
+
|
|
99
|
+
expect(myCacheMeta.options).toEqual({
|
|
100
|
+
maxCacheSize: 123,
|
|
101
|
+
maxCacheTime: 456,
|
|
102
|
+
warnIfItemPurgedBeforeTime: 5000,
|
|
103
|
+
opLimit: 200,
|
|
104
|
+
scanLimit: 50,
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('accepts an existing cacheMeta', () => {
|
|
109
|
+
const existingCacheMeta = lowLevelInit();
|
|
110
|
+
const myCacheMeta = lowLevelInit(existingCacheMeta);
|
|
111
|
+
|
|
112
|
+
expect(existingCacheMeta).toEqual(myCacheMeta);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('warns and upgrades if given an older, incompatible cacheMeta', () => {
|
|
116
|
+
const consoleWarnSpy = vitest.spyOn(console, 'warn').mockReturnValueOnce();
|
|
117
|
+
|
|
118
|
+
const existingCacheMeta = lowLevelInit();
|
|
119
|
+
existingCacheMeta.limitedCacheMetaVersion = 1;
|
|
120
|
+
const myCacheMeta = lowLevelInit(existingCacheMeta);
|
|
121
|
+
|
|
122
|
+
expect(existingCacheMeta).toEqual(myCacheMeta);
|
|
123
|
+
expect(existingCacheMeta.limitedCacheMetaVersion).toEqual(2);
|
|
124
|
+
|
|
125
|
+
const consoleErrorCalls = consoleWarnSpy.mock.calls;
|
|
126
|
+
expect(consoleErrorCalls.length).toBe(1);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('lowLevelHas', () => {
|
|
131
|
+
let myCacheMeta: LimitedCacheMeta;
|
|
132
|
+
beforeEach(() => {
|
|
133
|
+
vitest.restoreAllMocks();
|
|
134
|
+
myCacheMeta = lowLevelInit({
|
|
135
|
+
maxCacheTime: 1000,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('has a missing key', () => {
|
|
140
|
+
const result = lowLevelHas(myCacheMeta, 'abc');
|
|
141
|
+
expect(result).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('has a present key', () => {
|
|
145
|
+
myCacheMeta.cache['abc'] = 123;
|
|
146
|
+
myCacheMeta.keyInfo['abc'] = [Date.now(), 0];
|
|
147
|
+
|
|
148
|
+
const result = lowLevelHas(myCacheMeta, 'abc');
|
|
149
|
+
expect(result).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('has an expired key', () => {
|
|
153
|
+
myCacheMeta.cache['abc'] = 123;
|
|
154
|
+
myCacheMeta.keyInfo['abc'] = [1, 0];
|
|
155
|
+
|
|
156
|
+
const result = lowLevelHas(myCacheMeta, 'abc');
|
|
157
|
+
expect(result).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('lowLevelGetOne', () => {
|
|
162
|
+
let myCacheMeta: LimitedCacheMeta;
|
|
163
|
+
beforeEach(() => {
|
|
164
|
+
vitest.restoreAllMocks();
|
|
165
|
+
myCacheMeta = lowLevelInit({
|
|
166
|
+
maxCacheTime: 1000,
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('get a missing key', () => {
|
|
171
|
+
const result = lowLevelGetOne(myCacheMeta, 'abc');
|
|
172
|
+
expect(result).toBeUndefined();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('get a present key', () => {
|
|
176
|
+
// Danger: Manually manipulating internals, because otherwise we can't test 'get' separately from 'set'
|
|
177
|
+
myCacheMeta.cache['abc'] = 123;
|
|
178
|
+
myCacheMeta.keyList = ['abc'];
|
|
179
|
+
myCacheMeta.keyInfo['abc'] = [Date.now(), 0];
|
|
180
|
+
|
|
181
|
+
const result = lowLevelGetOne(myCacheMeta, 'abc');
|
|
182
|
+
expect(result).toEqual(123);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('get an expired key', () => {
|
|
186
|
+
// Danger: Manually manipulating internals, because otherwise we can't test 'get' separately from 'set'
|
|
187
|
+
myCacheMeta.cache['abc'] = 123;
|
|
188
|
+
myCacheMeta.keyList = ['abc'];
|
|
189
|
+
myCacheMeta.keyInfo['abc'] = [1, 0];
|
|
190
|
+
|
|
191
|
+
const result = lowLevelGetOne(myCacheMeta, 'abc');
|
|
192
|
+
expect(result).toEqual(undefined);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('lowLevelGetAll', () => {
|
|
197
|
+
let myCacheMeta: LimitedCacheMeta;
|
|
198
|
+
beforeEach(() => {
|
|
199
|
+
vitest.restoreAllMocks();
|
|
200
|
+
myCacheMeta = lowLevelInit({
|
|
201
|
+
maxCacheTime: 1000,
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('get all when empty', () => {
|
|
206
|
+
const result = lowLevelGetAll(myCacheMeta);
|
|
207
|
+
expect(result).toEqual({});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('get all when not empty', () => {
|
|
211
|
+
// Danger: Manually manipulating internals, because otherwise we can't test 'get' separately from 'set'
|
|
212
|
+
myCacheMeta.cache['abc'] = 123;
|
|
213
|
+
myCacheMeta.cache['def'] = 456;
|
|
214
|
+
myCacheMeta.keyList = ['abc', 'def'];
|
|
215
|
+
myCacheMeta.keyInfo['abc'] = [Date.now(), 0];
|
|
216
|
+
myCacheMeta.keyInfo['def'] = [Date.now(), 0];
|
|
217
|
+
|
|
218
|
+
const result = lowLevelGetAll(myCacheMeta);
|
|
219
|
+
expect(result).toEqual({ abc: 123, def: 456 });
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('get all when all expired', () => {
|
|
223
|
+
// Danger: Manually manipulating internals, because otherwise we can't test 'get' separately from 'set'
|
|
224
|
+
myCacheMeta.cache['abc'] = 123;
|
|
225
|
+
myCacheMeta.cache['def'] = 456;
|
|
226
|
+
myCacheMeta.keyList = ['abc', 'def'];
|
|
227
|
+
myCacheMeta.keyInfo['abc'] = [1, 0];
|
|
228
|
+
myCacheMeta.keyInfo['def'] = [1, 0];
|
|
229
|
+
|
|
230
|
+
const result = lowLevelGetAll(myCacheMeta);
|
|
231
|
+
expect(result).toEqual({});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('get all when some expired', () => {
|
|
235
|
+
// Danger: Manually manipulating internals, because otherwise we can't test 'get' separately from 'set'
|
|
236
|
+
myCacheMeta.cache['abc'] = 123;
|
|
237
|
+
myCacheMeta.cache['def'] = 456;
|
|
238
|
+
myCacheMeta.keyList = ['abc', 'def'];
|
|
239
|
+
myCacheMeta.keyInfo['abc'] = [1, 0];
|
|
240
|
+
myCacheMeta.keyInfo['def'] = [Date.now(), 0];
|
|
241
|
+
|
|
242
|
+
const result = lowLevelGetAll(myCacheMeta);
|
|
243
|
+
expect(result).toEqual({ def: 456 });
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('lowLevelSet', () => {
|
|
248
|
+
let myCacheMeta: LimitedCacheMeta;
|
|
249
|
+
beforeEach(() => {
|
|
250
|
+
vitest.restoreAllMocks();
|
|
251
|
+
myCacheMeta = lowLevelInit({
|
|
252
|
+
maxCacheTime: 1000,
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('returns a mutated cacheMeta', () => {
|
|
257
|
+
const returnedCacheMeta = lowLevelSet(myCacheMeta, 'abc', 123);
|
|
258
|
+
|
|
259
|
+
expect(returnedCacheMeta).toStrictEqual(myCacheMeta);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('set a new key', () => {
|
|
263
|
+
myCacheMeta = lowLevelSet(myCacheMeta, 'abc', 123);
|
|
264
|
+
|
|
265
|
+
expect(myCacheMeta.cache).toEqual({ abc: 123 });
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('set several new keys', () => {
|
|
269
|
+
myCacheMeta = lowLevelSet(myCacheMeta, 'abc', 123);
|
|
270
|
+
myCacheMeta = lowLevelSet(myCacheMeta, 'def', 456);
|
|
271
|
+
|
|
272
|
+
expect(myCacheMeta.cache).toEqual({ abc: 123, def: 456 });
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('overwrite a key', () => {
|
|
276
|
+
myCacheMeta = lowLevelSet(myCacheMeta, 'abc', 123);
|
|
277
|
+
myCacheMeta = lowLevelSet(myCacheMeta, 'abc', 456);
|
|
278
|
+
|
|
279
|
+
expect(myCacheMeta.cache).toEqual({ abc: 456 });
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('removes items to stay within the cache size', () => {
|
|
283
|
+
myCacheMeta = lowLevelInit({
|
|
284
|
+
maxCacheSize: 5,
|
|
285
|
+
maxCacheTime: 1000,
|
|
286
|
+
opLimit: Number.MAX_SAFE_INTEGER,
|
|
287
|
+
warnIfItemPurgedBeforeTime: 0,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Set 5 items, then 5 more
|
|
291
|
+
for (let n = 1; n <= 5; n++) {
|
|
292
|
+
myCacheMeta = lowLevelSet(myCacheMeta, `n=${n}`, n);
|
|
293
|
+
}
|
|
294
|
+
expect(myCacheMeta.cache).toEqual({
|
|
295
|
+
'n=1': 1,
|
|
296
|
+
'n=2': 2,
|
|
297
|
+
'n=3': 3,
|
|
298
|
+
'n=4': 4,
|
|
299
|
+
'n=5': 5,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
for (let n = 6; n <= 10; n++) {
|
|
303
|
+
myCacheMeta = lowLevelSet(myCacheMeta, `n=${n}`, n);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
expect(myCacheMeta.cache).toEqual({
|
|
307
|
+
'n=1': undefined,
|
|
308
|
+
'n=2': undefined,
|
|
309
|
+
'n=3': undefined,
|
|
310
|
+
'n=4': undefined,
|
|
311
|
+
'n=5': undefined,
|
|
312
|
+
'n=6': 6,
|
|
313
|
+
'n=7': 7,
|
|
314
|
+
'n=8': 8,
|
|
315
|
+
'n=9': 9,
|
|
316
|
+
'n=10': 10,
|
|
317
|
+
});
|
|
318
|
+
// Double-check the keys, because jest can give false positives on the undefined ones above
|
|
319
|
+
expect(Object.keys(myCacheMeta.cache)).toEqual([
|
|
320
|
+
'n=1',
|
|
321
|
+
'n=2',
|
|
322
|
+
'n=3',
|
|
323
|
+
'n=4',
|
|
324
|
+
'n=5',
|
|
325
|
+
'n=6',
|
|
326
|
+
'n=7',
|
|
327
|
+
'n=8',
|
|
328
|
+
'n=9',
|
|
329
|
+
'n=10',
|
|
330
|
+
]);
|
|
331
|
+
|
|
332
|
+
// And even though the cache hasn't been cleaned up yet, the keys should reflect the proper state
|
|
333
|
+
expect(lowLevelHas(myCacheMeta, 'n=1')).toEqual(false);
|
|
334
|
+
expect(lowLevelHas(myCacheMeta, 'n=5')).toEqual(false);
|
|
335
|
+
expect(lowLevelHas(myCacheMeta, 'n=8')).toEqual(true);
|
|
336
|
+
expect(lowLevelHas(myCacheMeta, 'n=10')).toEqual(true);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('performs maintenance after many actions', () => {
|
|
340
|
+
myCacheMeta = lowLevelInit({
|
|
341
|
+
maxCacheSize: 10,
|
|
342
|
+
maxCacheTime: 1000,
|
|
343
|
+
opLimit: 1,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Automaintenance should be done after 10 "set" actions (i.e., on the 11th)
|
|
347
|
+
for (let n = 1; n <= 10; n++) {
|
|
348
|
+
myCacheMeta = lowLevelSet(myCacheMeta, `n=${n}`, n);
|
|
349
|
+
}
|
|
350
|
+
for (let n = 2; n <= 9; n++) {
|
|
351
|
+
myCacheMeta = lowLevelRemove(myCacheMeta, `n=${n}`);
|
|
352
|
+
}
|
|
353
|
+
expect(myCacheMeta.cache).toEqual({
|
|
354
|
+
'n=1': 1,
|
|
355
|
+
'n=2': undefined,
|
|
356
|
+
'n=3': undefined,
|
|
357
|
+
'n=4': undefined,
|
|
358
|
+
'n=5': undefined,
|
|
359
|
+
'n=6': undefined,
|
|
360
|
+
'n=7': undefined,
|
|
361
|
+
'n=8': undefined,
|
|
362
|
+
'n=9': undefined,
|
|
363
|
+
'n=10': 10,
|
|
364
|
+
});
|
|
365
|
+
// Double-check the keys, because jest can give false positives on the undefined ones above
|
|
366
|
+
expect(Object.keys(myCacheMeta.cache)).toEqual([
|
|
367
|
+
'n=1',
|
|
368
|
+
'n=2',
|
|
369
|
+
'n=3',
|
|
370
|
+
'n=4',
|
|
371
|
+
'n=5',
|
|
372
|
+
'n=6',
|
|
373
|
+
'n=7',
|
|
374
|
+
'n=8',
|
|
375
|
+
'n=9',
|
|
376
|
+
'n=10',
|
|
377
|
+
]);
|
|
378
|
+
|
|
379
|
+
myCacheMeta = lowLevelSet(myCacheMeta, 'n=11', 11);
|
|
380
|
+
|
|
381
|
+
// Now the internal data (including keys) should be trimmed
|
|
382
|
+
expect(myCacheMeta.cache).toEqual({ 'n=1': 1, 'n=10': 10, 'n=11': 11 });
|
|
383
|
+
expect(Object.keys(myCacheMeta.cache)).toEqual(['n=1', 'n=10', 'n=11']);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
describe('lowLevelRemove', () => {
|
|
388
|
+
let myCacheMeta: LimitedCacheMeta;
|
|
389
|
+
beforeEach(() => {
|
|
390
|
+
vitest.restoreAllMocks();
|
|
391
|
+
myCacheMeta = lowLevelInit();
|
|
392
|
+
myCacheMeta = lowLevelSet(myCacheMeta, 'abc', 123);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('removes an absent key', () => {
|
|
396
|
+
myCacheMeta = lowLevelRemove(myCacheMeta, 'ghi');
|
|
397
|
+
|
|
398
|
+
expect(myCacheMeta.cache).toEqual({ abc: 123 });
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('removes a present key', () => {
|
|
402
|
+
myCacheMeta = lowLevelRemove(myCacheMeta, 'abc');
|
|
403
|
+
|
|
404
|
+
expect(myCacheMeta.cache).toEqual({ abc: undefined });
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('removes a present key multiple times', () => {
|
|
408
|
+
myCacheMeta = lowLevelRemove(myCacheMeta, 'abc');
|
|
409
|
+
expect(myCacheMeta.cache).toEqual({ abc: undefined });
|
|
410
|
+
|
|
411
|
+
myCacheMeta = lowLevelRemove(myCacheMeta, 'abc');
|
|
412
|
+
expect(myCacheMeta.cache).toEqual({ abc: undefined });
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
describe('lowLevelReset', () => {
|
|
417
|
+
let myCacheMeta: LimitedCacheMeta;
|
|
418
|
+
beforeEach(() => {
|
|
419
|
+
vitest.restoreAllMocks();
|
|
420
|
+
myCacheMeta = lowLevelInit();
|
|
421
|
+
myCacheMeta = lowLevelSet(myCacheMeta, 'abc', 123);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('does what it says', () => {
|
|
425
|
+
myCacheMeta = lowLevelReset(myCacheMeta);
|
|
426
|
+
|
|
427
|
+
expect(myCacheMeta.cache).toEqual({});
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, beforeEach, expect, it } from 'vitest';
|
|
2
|
+
import { limitedCacheUtil, LimitedCacheMeta } from '../../index';
|
|
3
|
+
|
|
4
|
+
describe('key names', () => {
|
|
5
|
+
let myCacheMeta: LimitedCacheMeta;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
myCacheMeta = limitedCacheUtil.init();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('builtins: hasOwnProperty', () => {
|
|
11
|
+
expect(limitedCacheUtil.has(myCacheMeta, 'hasOwnProperty')).toEqual(false);
|
|
12
|
+
expect(limitedCacheUtil.get(myCacheMeta, 'hasOwnProperty')).toEqual(undefined);
|
|
13
|
+
|
|
14
|
+
limitedCacheUtil.set(myCacheMeta, 'hasOwnProperty', 123);
|
|
15
|
+
|
|
16
|
+
expect(limitedCacheUtil.has(myCacheMeta, 'hasOwnProperty')).toEqual(true);
|
|
17
|
+
expect(limitedCacheUtil.get(myCacheMeta, 'hasOwnProperty')).toEqual(123);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('builtins: constructor', () => {
|
|
21
|
+
expect(limitedCacheUtil.has(myCacheMeta, 'constructor')).toEqual(false);
|
|
22
|
+
expect(limitedCacheUtil.get(myCacheMeta, 'constructor')).toEqual(undefined);
|
|
23
|
+
|
|
24
|
+
limitedCacheUtil.set(myCacheMeta, 'constructor', 123);
|
|
25
|
+
|
|
26
|
+
expect(limitedCacheUtil.has(myCacheMeta, 'constructor')).toEqual(true);
|
|
27
|
+
expect(limitedCacheUtil.get(myCacheMeta, 'constructor')).toEqual(123);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('empty string', () => {
|
|
31
|
+
limitedCacheUtil.set(myCacheMeta, '', 123);
|
|
32
|
+
|
|
33
|
+
expect(limitedCacheUtil.has(myCacheMeta, '')).toEqual(true);
|
|
34
|
+
expect(limitedCacheUtil.get(myCacheMeta, '')).toEqual(123);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('number', () => {
|
|
38
|
+
// @ts-expect-error description here
|
|
39
|
+
limitedCacheUtil.set(myCacheMeta, 123, 123);
|
|
40
|
+
|
|
41
|
+
// @ts-expect-error description here
|
|
42
|
+
expect(limitedCacheUtil.has(myCacheMeta, 123)).toEqual(true);
|
|
43
|
+
// @ts-expect-error description here
|
|
44
|
+
expect(limitedCacheUtil.get(myCacheMeta, 123)).toEqual(123);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('special character: "-"', () => {
|
|
48
|
+
limitedCacheUtil.set(myCacheMeta, 'abc-123', 123);
|
|
49
|
+
|
|
50
|
+
expect(limitedCacheUtil.has(myCacheMeta, 'abc-123')).toEqual(true);
|
|
51
|
+
expect(limitedCacheUtil.get(myCacheMeta, 'abc-123')).toEqual(123);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('special character: "."', () => {
|
|
55
|
+
limitedCacheUtil.set(myCacheMeta, 'abc.123', 123);
|
|
56
|
+
|
|
57
|
+
expect(limitedCacheUtil.has(myCacheMeta, 'abc.123')).toEqual(true);
|
|
58
|
+
expect(limitedCacheUtil.get(myCacheMeta, 'abc.123')).toEqual(123);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('special character: " "', () => {
|
|
62
|
+
limitedCacheUtil.set(myCacheMeta, 'abc 123', 123);
|
|
63
|
+
|
|
64
|
+
expect(limitedCacheUtil.has(myCacheMeta, 'abc 123')).toEqual(true);
|
|
65
|
+
expect(limitedCacheUtil.get(myCacheMeta, 'abc 123')).toEqual(123);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { describe, beforeEach, expect, it, vitest } from 'vitest';
|
|
2
|
+
import { LimitedCache, LimitedCacheInstance } from '../../index';
|
|
3
|
+
|
|
4
|
+
// To avoid race conditions or timing issues, since some expect() checks can take 10+ ms when busy,
|
|
5
|
+
// we use a long cache timeout even for 'immediate' expiration, and use delays slightly longer than that
|
|
6
|
+
const CACHE_TIMEOUT = 20;
|
|
7
|
+
const timeoutPromise = (): Promise<null> =>
|
|
8
|
+
new Promise((resolve) => setTimeout(resolve, CACHE_TIMEOUT + 2));
|
|
9
|
+
|
|
10
|
+
describe('maxCacheSize scenarios', () => {
|
|
11
|
+
let myCache: LimitedCacheInstance;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vitest.restoreAllMocks();
|
|
14
|
+
myCache = LimitedCache({
|
|
15
|
+
maxCacheSize: 20,
|
|
16
|
+
maxCacheTime: Number.MAX_SAFE_INTEGER,
|
|
17
|
+
opLimit: Number.MAX_SAFE_INTEGER,
|
|
18
|
+
warnIfItemPurgedBeforeTime: 0,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('removes items to stay within the cache size, with unique keys', () => {
|
|
23
|
+
// Populate items into new keys: there should remain 20 items
|
|
24
|
+
for (let n = 1; n <= 1000; n++) {
|
|
25
|
+
myCache.set(`n=${n}`, n);
|
|
26
|
+
|
|
27
|
+
const allKeys = Object.keys(myCache.getAll());
|
|
28
|
+
expect(allKeys.length).toEqual(Math.min(20, n));
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('removes items to stay within the cache size, with overlapping keys', () => {
|
|
33
|
+
// Populate 20 items
|
|
34
|
+
for (let n = 1; n <= 20; n++) {
|
|
35
|
+
myCache.set(`n=${n}`, n);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Populate items into progressive larger sets of keys: there should remain 20 items
|
|
39
|
+
for (let n = 1; n <= 10; n++) {
|
|
40
|
+
myCache.set(`n=${n}`, n);
|
|
41
|
+
|
|
42
|
+
const allKeys = Object.keys(myCache.getAll());
|
|
43
|
+
expect(allKeys.length).toEqual(20);
|
|
44
|
+
}
|
|
45
|
+
for (let n = 1; n <= 100; n++) {
|
|
46
|
+
myCache.set(`n=${n}`, n);
|
|
47
|
+
|
|
48
|
+
const allKeys = Object.keys(myCache.getAll());
|
|
49
|
+
expect(allKeys.length).toEqual(20);
|
|
50
|
+
}
|
|
51
|
+
for (let n = 1; n <= 1000; n++) {
|
|
52
|
+
myCache.set(`n=${n}`, n);
|
|
53
|
+
|
|
54
|
+
const allKeys = Object.keys(myCache.getAll());
|
|
55
|
+
expect(allKeys.length).toEqual(20);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('removes items to stay within the cache size, with randomized keys', () => {
|
|
60
|
+
// Populate 20 items
|
|
61
|
+
for (let n = 1; n <= 20; n++) {
|
|
62
|
+
myCache.set(`n=${n}`, n);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Now keep populating random values into a limited set of keys: there should remain 20 items
|
|
66
|
+
for (let i = 0; i < 1000; i++) {
|
|
67
|
+
// Keys will span 1 to 100
|
|
68
|
+
const n = Math.floor(Math.random() * 100) + 1;
|
|
69
|
+
myCache.set(`n=${n}`, i);
|
|
70
|
+
|
|
71
|
+
const allKeys = Object.keys(myCache.getAll());
|
|
72
|
+
expect(allKeys.length).toEqual(20);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('removes expired items aggressively', async () => {
|
|
77
|
+
myCache = LimitedCache({
|
|
78
|
+
maxCacheSize: 5,
|
|
79
|
+
maxCacheTime: CACHE_TIMEOUT,
|
|
80
|
+
opLimit: Number.MAX_SAFE_INTEGER,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Fill the cache
|
|
84
|
+
for (let n = 1; n <= 5; n++) {
|
|
85
|
+
myCache.set(`n=${n}`, n);
|
|
86
|
+
}
|
|
87
|
+
expect(Object.keys(myCache.getAll())).toEqual(['n=1', 'n=2', 'n=3', 'n=4', 'n=5']);
|
|
88
|
+
|
|
89
|
+
await timeoutPromise();
|
|
90
|
+
|
|
91
|
+
// Fill with different values
|
|
92
|
+
for (let n = 6; n <= 10; n++) {
|
|
93
|
+
myCache.set(`n=${n}`, n);
|
|
94
|
+
}
|
|
95
|
+
expect(Object.keys(myCache.getAll())).toEqual(['n=6', 'n=7', 'n=8', 'n=9', 'n=10']);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('warns if a freshly-added item is pushed out too quickly', async () => {
|
|
99
|
+
myCache = LimitedCache({
|
|
100
|
+
maxCacheSize: 5,
|
|
101
|
+
maxCacheTime: 1000,
|
|
102
|
+
opLimit: Number.MAX_SAFE_INTEGER,
|
|
103
|
+
warnIfItemPurgedBeforeTime: 1000,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const consoleWarnSpy = vitest.spyOn(console, 'warn').mockReturnValueOnce();
|
|
107
|
+
|
|
108
|
+
for (let n = 1; n <= 5; n++) {
|
|
109
|
+
myCache.set(`n=${n}`, n);
|
|
110
|
+
}
|
|
111
|
+
// This pushes out the still-fresh `n=1`
|
|
112
|
+
myCache.set('abc', 123);
|
|
113
|
+
|
|
114
|
+
expect(Object.keys(myCache.getAll())).toEqual(['n=2', 'n=3', 'n=4', 'n=5', 'abc']);
|
|
115
|
+
|
|
116
|
+
// And we should have seen a warning about the force-removed key
|
|
117
|
+
const consoleWarnCalls = consoleWarnSpy.mock.calls;
|
|
118
|
+
expect(consoleWarnCalls.length).toBe(1);
|
|
119
|
+
const [warningString, warningObject] = consoleWarnCalls[0];
|
|
120
|
+
expect(warningString).toBe(
|
|
121
|
+
'Purged an item from cache while it was still fresh: you may want to increase maxCacheSize',
|
|
122
|
+
);
|
|
123
|
+
expect(typeof warningObject).toBe('object');
|
|
124
|
+
expect(warningObject.key).toBe('n=1');
|
|
125
|
+
});
|
|
126
|
+
});
|