Skip to content

Commit c3603b1

Browse files
committed
fix: caches work on unknown data types
the previous generic typing caused a situation where a single cache instance could not be reused on different cachified calls operating on varying data types This was never a design goal and happened as a over-optimization when implementing against tests instead of a rl-app 😅 fix: #5 BREAKING CHANGE: The `Cache` type is not generic anymore and always uses `unknown` for values You might need to adjust your cache implementations and caches to now work on unknown data types. Typescript should inform you where. Nothing to be done when you only used the build-in cache adapters.
1 parent b166100 commit c3603b1

File tree

7 files changed

+61
-66
lines changed

7 files changed

+61
-66
lines changed

src/adapters.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import { Cache, CacheEntry } from './common';
22

3-
export interface LRUishCache<Value> extends Omit<Cache<Value>, 'set'> {
3+
export interface LRUishCache extends Omit<Cache, 'set'> {
44
set(
55
key: string,
6-
value: CacheEntry<Value>,
6+
value: CacheEntry<unknown>,
77
options?: { ttl?: number; start?: number },
88
): void;
99
}
1010

11-
export function lruCacheAdapter<Value>(
12-
lruCache: LRUishCache<Value>,
13-
): Cache<Value> {
11+
export function lruCacheAdapter(lruCache: LRUishCache): Cache {
1412
return {
1513
name: lruCache.name || 'LRU',
1614
set(key, value) {
@@ -50,9 +48,7 @@ export interface Redis3LikeCache {
5048
multi(): Redis3Multi;
5149
}
5250

53-
export function redis3CacheAdapter<Value>(
54-
redisCache: Redis3LikeCache,
55-
): Cache<Value> {
51+
export function redis3CacheAdapter(redisCache: Redis3LikeCache): Cache {
5652
return {
5753
name: redisCache.name || 'Redis3',
5854
set(key, value) {
@@ -78,7 +74,7 @@ export function redis3CacheAdapter<Value>(
7874
});
7975
},
8076
get(key) {
81-
return new Promise<CacheEntry<Value> | null | undefined>((res, rej) => {
77+
return new Promise<CacheEntry | null | undefined>((res, rej) => {
8278
redisCache.get(key, (err, reply) => {
8379
if (err) {
8480
rej(err);
@@ -118,9 +114,7 @@ export interface RedisLikeCache {
118114
del(key: string): Promise<unknown>;
119115
}
120116

121-
export function redisCacheAdapter<Value>(
122-
redisCache: RedisLikeCache,
123-
): Cache<Value> {
117+
export function redisCacheAdapter(redisCache: RedisLikeCache): Cache {
124118
return {
125119
name: redisCache.name || 'Redis',
126120
set(key, value) {

src/cachified.spec.ts

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ beforeEach(() => {
3535

3636
describe('cachified', () => {
3737
it('caches a value', async () => {
38-
const cache = new Map<string, CacheEntry<string>>();
38+
const cache = new Map<string, CacheEntry>();
3939
const reporter = createReporter();
4040
const reporter2 = createReporter();
4141

@@ -84,7 +84,7 @@ describe('cachified', () => {
8484
});
8585

8686
it('immediately refreshes when ttl is 0', async () => {
87-
const cache = new Map<string, CacheEntry<string>>();
87+
const cache = new Map<string, CacheEntry>();
8888

8989
const value = await cachified({
9090
cache,
@@ -110,7 +110,7 @@ describe('cachified', () => {
110110
});
111111

112112
it('caches undefined values', async () => {
113-
const cache = new Map<string, CacheEntry<undefined>>();
113+
const cache = new Map<string, CacheEntry>();
114114

115115
const value = await cachified({
116116
cache,
@@ -133,7 +133,7 @@ describe('cachified', () => {
133133
});
134134

135135
it('caches null values', async () => {
136-
const cache = new Map<string, CacheEntry<null>>();
136+
const cache = new Map<string, CacheEntry>();
137137

138138
const value = await cachified({
139139
cache,
@@ -156,7 +156,7 @@ describe('cachified', () => {
156156
});
157157

158158
it('throws when no fresh value can be received for empty cache', async () => {
159-
const cache = new Map<string, CacheEntry<string>>();
159+
const cache = new Map<string, CacheEntry>();
160160
const reporter = createReporter();
161161

162162
const value = cachified({
@@ -182,7 +182,7 @@ describe('cachified', () => {
182182
});
183183

184184
it('throws when no forced fresh value can be received on empty cache', async () => {
185-
const cache = new Map<string, CacheEntry<string>>();
185+
const cache = new Map<string, CacheEntry>();
186186

187187
const value = cachified({
188188
cache,
@@ -197,7 +197,7 @@ describe('cachified', () => {
197197
});
198198

199199
it('throws when fresh value does not meet value check', async () => {
200-
const cache = new Map<string, CacheEntry<string>>();
200+
const cache = new Map<string, CacheEntry>();
201201
const reporter = createReporter();
202202
const reporter2 = createReporter();
203203

@@ -259,7 +259,7 @@ describe('cachified', () => {
259259
});
260260

261261
it('supports migrating cached values', async () => {
262-
const cache = new Map<string, CacheEntry<string>>();
262+
const cache = new Map<string, CacheEntry>();
263263
const reporter = createReporter();
264264

265265
cache.set('weather', createCacheEntry('☁️'));
@@ -292,7 +292,7 @@ describe('cachified', () => {
292292
});
293293

294294
it('supports async value checkers that throw', async () => {
295-
const cache = new Map<string, CacheEntry<string>>();
295+
const cache = new Map<string, CacheEntry>();
296296
const reporter = createReporter();
297297

298298
const value = cachified({
@@ -347,7 +347,7 @@ describe('cachified', () => {
347347
});
348348

349349
it('does not write migrated value to cache in case a new fresh value is already incoming', async () => {
350-
const cache = new Map<string, CacheEntry<string>>();
350+
const cache = new Map<string, CacheEntry>();
351351
const reporter = createReporter();
352352

353353
cache.set('weather', createCacheEntry('☁️'));
@@ -389,7 +389,7 @@ describe('cachified', () => {
389389
});
390390

391391
it('gets different values for different keys', async () => {
392-
const cache = new Map<string, CacheEntry<string>>();
392+
const cache = new Map<string, CacheEntry>();
393393

394394
const value = await cachified({
395395
cache,
@@ -422,7 +422,7 @@ describe('cachified', () => {
422422
});
423423

424424
it('gets fresh value when forced to', async () => {
425-
const cache = new Map<string, CacheEntry<string>>();
425+
const cache = new Map<string, CacheEntry>();
426426

427427
const value = await cachified({
428428
cache,
@@ -445,7 +445,7 @@ describe('cachified', () => {
445445
});
446446

447447
it('falls back to cache when forced fresh value fails', async () => {
448-
const cache = new Map<string, CacheEntry<string>>();
448+
const cache = new Map<string, CacheEntry>();
449449
const reporter = createReporter();
450450

451451
cache.set('test', createCacheEntry('ONE'));
@@ -477,7 +477,7 @@ describe('cachified', () => {
477477
});
478478

479479
it('does not fall back to outdated cache', async () => {
480-
const cache = new Map<string, CacheEntry<string>>();
480+
const cache = new Map<string, CacheEntry>();
481481
const reporter = createReporter();
482482

483483
cache.set('test', createCacheEntry('ONE', { ttl: 5 }));
@@ -497,7 +497,7 @@ describe('cachified', () => {
497497
});
498498

499499
it('it throws when cache fallback is disabled and getting fresh value fails', async () => {
500-
const cache = new Map<string, CacheEntry<string>>();
500+
const cache = new Map<string, CacheEntry>();
501501

502502
const value1 = await cachified({
503503
cache,
@@ -519,7 +519,7 @@ describe('cachified', () => {
519519
});
520520

521521
it('handles cache write fails', async () => {
522-
const cache = new Map<string, CacheEntry<string>>();
522+
const cache = new Map<string, CacheEntry>();
523523
const setMock = jest.spyOn(cache, 'set');
524524
const reporter = createReporter();
525525
let i = 0;
@@ -562,7 +562,7 @@ describe('cachified', () => {
562562
});
563563

564564
it('gets fresh value when ttl is exceeded', async () => {
565-
const cache = new Map<string, CacheEntry<string>>();
565+
const cache = new Map<string, CacheEntry>();
566566
const reporter = createReporter();
567567
let i = 0;
568568
const getValue = () =>
@@ -617,7 +617,7 @@ describe('cachified', () => {
617617
});
618618

619619
it('does not write to cache when ttl is exceeded before value is received', async () => {
620-
const cache = new Map<string, CacheEntry<string>>();
620+
const cache = new Map<string, CacheEntry>();
621621
const setMock = jest.spyOn(cache, 'set');
622622
const reporter = createReporter();
623623

@@ -649,7 +649,7 @@ describe('cachified', () => {
649649
});
650650

651651
it('reuses pending fresh value for parallel calls', async () => {
652-
const cache = new Map<string, CacheEntry<string>>();
652+
const cache = new Map<string, CacheEntry>();
653653
const reporter = createReporter();
654654
const getValue = (
655655
getFreshValue: CachifiedOptions<string>['getFreshValue'],
@@ -691,7 +691,7 @@ describe('cachified', () => {
691691
});
692692

693693
it('resolves earlier pending values with faster responses from later calls', async () => {
694-
const cache = new Map<string, CacheEntry<string>>();
694+
const cache = new Map<string, CacheEntry>();
695695
const getValue = (
696696
getFreshValue: CachifiedOptions<string>['getFreshValue'],
697697
) =>
@@ -726,7 +726,7 @@ describe('cachified', () => {
726726
});
727727

728728
it('uses stale cache while revalidating', async () => {
729-
const cache = new Map<string, CacheEntry<string>>();
729+
const cache = new Map<string, CacheEntry>();
730730
const reporter = createReporter();
731731
let i = 0;
732732
const getFreshValue = jest.fn(() => `value-${i++}`);
@@ -783,7 +783,7 @@ describe('cachified', () => {
783783
});
784784

785785
it('supports infinite stale while revalidate', async () => {
786-
const cache = new Map<string, CacheEntry<string>>();
786+
const cache = new Map<string, CacheEntry>();
787787
let i = 0;
788788
const getFreshValue = jest.fn(() => `value-${i++}`);
789789
const getValue = () =>
@@ -811,7 +811,7 @@ describe('cachified', () => {
811811
});
812812

813813
it('ignores errors when revalidating cache in the background', async () => {
814-
const cache = new Map<string, CacheEntry<string>>();
814+
const cache = new Map<string, CacheEntry>();
815815
const reporter = createReporter();
816816
let i = 0;
817817
const getFreshValue = jest.fn(() => `value-${i++}`);
@@ -867,7 +867,7 @@ describe('cachified', () => {
867867
});
868868

869869
it('gets fresh value in case cached one does not meet value check', async () => {
870-
const cache = new Map<string, CacheEntry<string>>();
870+
const cache = new Map<string, CacheEntry>();
871871
const reporter = createReporter();
872872
const reporter2 = createReporter();
873873

@@ -931,7 +931,7 @@ describe('cachified', () => {
931931
});
932932

933933
it('supports batch-getting fresh values', async () => {
934-
const cache = new Map<string, CacheEntry<string>>();
934+
const cache = new Map<string, CacheEntry>();
935935
cache.set('test-2', createCacheEntry('YOLO!', { swv: null }));
936936
const getValues = jest.fn((indexes: number[]) =>
937937
indexes.map((i) => `value-${i}`),
@@ -961,7 +961,7 @@ describe('cachified', () => {
961961
});
962962

963963
it('rejects all values when batch get fails', async () => {
964-
const cache = new Map<string, CacheEntry<string>>();
964+
const cache = new Map<string, CacheEntry>();
965965

966966
const batch = createBatch<string, any>(() => {
967967
throw new Error('🥊');
@@ -981,7 +981,7 @@ describe('cachified', () => {
981981
});
982982

983983
it('supports manual submission of batch', async () => {
984-
const cache = new Map<string, CacheEntry<string>>();
984+
const cache = new Map<string, CacheEntry>();
985985
const getValues = jest.fn((indexes: (number | string)[]) =>
986986
indexes.map((i) => `value-${i}`),
987987
);
@@ -1075,7 +1075,7 @@ describe('cachified', () => {
10751075
});
10761076

10771077
it('works with LRU cache', async () => {
1078-
const lru = new LRUCache<string, CacheEntry<string>>({ max: 5 });
1078+
const lru = new LRUCache<string, CacheEntry>({ max: 5 });
10791079
const cache = lruCacheAdapter(lru);
10801080

10811081
const value = await cachified({
@@ -1265,7 +1265,7 @@ describe('cachified', () => {
12651265

12661266
describe('verbose reporter', () => {
12671267
it('logs when cached value is invalid', async () => {
1268-
const cache = new Map<string, CacheEntry<string>>();
1268+
const cache = new Map<string, CacheEntry>();
12691269
const logger = createLogger();
12701270
cache.set('test', createCacheEntry('One'));
12711271

@@ -1286,7 +1286,7 @@ describe('verbose reporter', () => {
12861286
});
12871287

12881288
it('logs when getting a cached value fails', async () => {
1289-
const cache = new Map<string, CacheEntry<string>>();
1289+
const cache = new Map<string, CacheEntry>();
12901290
const logger = createLogger();
12911291
const getMock = jest.spyOn(cache, 'get');
12921292
getMock.mockImplementationOnce(() => {
@@ -1309,7 +1309,7 @@ describe('verbose reporter', () => {
13091309
});
13101310

13111311
it('logs when getting a fresh value fails', async () => {
1312-
const cache = new Map<string, CacheEntry<string>>();
1312+
const cache = new Map<string, CacheEntry>();
13131313
const logger = createLogger();
13141314

13151315
await cachified({
@@ -1329,7 +1329,7 @@ describe('verbose reporter', () => {
13291329
});
13301330

13311331
it('logs when fresh value is not written to cache', async () => {
1332-
const cache = new Map<string, CacheEntry<string>>();
1332+
const cache = new Map<string, CacheEntry>();
13331333
const logger = createLogger();
13341334

13351335
await cachified({
@@ -1350,7 +1350,7 @@ describe('verbose reporter', () => {
13501350
});
13511351

13521352
it('logs when writing to cache fails (using defaults)', async () => {
1353-
const cache = new Map<string, CacheEntry<string>>();
1353+
const cache = new Map<string, CacheEntry>();
13541354
const errorMock = jest.spyOn(console, 'error').mockImplementation(() => {
13551355
/* 🤫 */
13561356
});
@@ -1378,7 +1378,7 @@ describe('verbose reporter', () => {
13781378
it('falls back to Date when performance is not globally available', async () => {
13791379
const backup = global.performance;
13801380
delete (global as any).performance;
1381-
const cache = new Map<string, CacheEntry<string>>();
1381+
const cache = new Map<string, CacheEntry>();
13821382
const logger = createLogger();
13831383

13841384
await cachified({
@@ -1393,7 +1393,7 @@ describe('verbose reporter', () => {
13931393
});
13941394

13951395
it('logs when fresh value does not meet value check', async () => {
1396-
const cache = new Map<string, CacheEntry<string>>();
1396+
const cache = new Map<string, CacheEntry>();
13971397
const logger = createLogger();
13981398

13991399
await cachified({
@@ -1413,7 +1413,7 @@ describe('verbose reporter', () => {
14131413
});
14141414

14151415
it('logs when cache is successfully revalidated', async () => {
1416-
const cache = new Map<string, CacheEntry<string>>();
1416+
const cache = new Map<string, CacheEntry>();
14171417
const logger = createLogger();
14181418
cache.set('test', createCacheEntry('ONE', { ttl: 5, swv: 10 }));
14191419
currentTime = 7;
@@ -1435,7 +1435,7 @@ describe('verbose reporter', () => {
14351435
});
14361436

14371437
it('logs when cache revalidation fails', async () => {
1438-
const cache = new Map<string, CacheEntry<string>>();
1438+
const cache = new Map<string, CacheEntry>();
14391439
const logger = createLogger();
14401440
cache.set('test', createCacheEntry('ONE', { ttl: 5, swv: 10 }));
14411441
currentTime = 7;

src/cachified.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { shouldRefresh } from './shouldRefresh';
66
// This is to prevent requesting multiple fresh values in parallel
77
// while revalidating or getting first value
88
// Keys are unique per cache but may be used by multiple caches
9-
const pendingValuesByCache = new WeakMap<Cache<any>, Map<string, any>>();
9+
const pendingValuesByCache = new WeakMap<Cache, Map<string, any>>();
1010

1111
export async function cachified<Value>(
1212
options: CachifiedOptions<Value>,

0 commit comments

Comments
 (0)