Skip to content

Commit

Permalink
fix: caches work on unknown data types
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Xiphe committed Oct 24, 2022
1 parent b166100 commit c3603b1
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 66 deletions.
18 changes: 6 additions & 12 deletions src/adapters.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { Cache, CacheEntry } from './common';

export interface LRUishCache<Value> extends Omit<Cache<Value>, 'set'> {
export interface LRUishCache extends Omit<Cache, 'set'> {
set(
key: string,
value: CacheEntry<Value>,
value: CacheEntry<unknown>,
options?: { ttl?: number; start?: number },
): void;
}

export function lruCacheAdapter<Value>(
lruCache: LRUishCache<Value>,
): Cache<Value> {
export function lruCacheAdapter(lruCache: LRUishCache): Cache {
return {
name: lruCache.name || 'LRU',
set(key, value) {
Expand Down Expand Up @@ -50,9 +48,7 @@ export interface Redis3LikeCache {
multi(): Redis3Multi;
}

export function redis3CacheAdapter<Value>(
redisCache: Redis3LikeCache,
): Cache<Value> {
export function redis3CacheAdapter(redisCache: Redis3LikeCache): Cache {
return {
name: redisCache.name || 'Redis3',
set(key, value) {
Expand All @@ -78,7 +74,7 @@ export function redis3CacheAdapter<Value>(
});
},
get(key) {
return new Promise<CacheEntry<Value> | null | undefined>((res, rej) => {
return new Promise<CacheEntry | null | undefined>((res, rej) => {
redisCache.get(key, (err, reply) => {
if (err) {
rej(err);
Expand Down Expand Up @@ -118,9 +114,7 @@ export interface RedisLikeCache {
del(key: string): Promise<unknown>;
}

export function redisCacheAdapter<Value>(
redisCache: RedisLikeCache,
): Cache<Value> {
export function redisCacheAdapter(redisCache: RedisLikeCache): Cache {
return {
name: redisCache.name || 'Redis',
set(key, value) {
Expand Down
74 changes: 37 additions & 37 deletions src/cachified.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ beforeEach(() => {

describe('cachified', () => {
it('caches a value', async () => {
const cache = new Map<string, CacheEntry<string>>();
const cache = new Map<string, CacheEntry>();
const reporter = createReporter();
const reporter2 = createReporter();

Expand Down Expand Up @@ -84,7 +84,7 @@ describe('cachified', () => {
});

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

const value = await cachified({
cache,
Expand All @@ -110,7 +110,7 @@ describe('cachified', () => {
});

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

const value = await cachified({
cache,
Expand All @@ -133,7 +133,7 @@ describe('cachified', () => {
});

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

const value = await cachified({
cache,
Expand All @@ -156,7 +156,7 @@ describe('cachified', () => {
});

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

const value = cachified({
Expand All @@ -182,7 +182,7 @@ describe('cachified', () => {
});

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

const value = cachified({
cache,
Expand All @@ -197,7 +197,7 @@ describe('cachified', () => {
});

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

Expand Down Expand Up @@ -259,7 +259,7 @@ describe('cachified', () => {
});

it('supports migrating cached values', async () => {
const cache = new Map<string, CacheEntry<string>>();
const cache = new Map<string, CacheEntry>();
const reporter = createReporter();

cache.set('weather', createCacheEntry('☁️'));
Expand Down Expand Up @@ -292,7 +292,7 @@ describe('cachified', () => {
});

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

const value = cachified({
Expand Down Expand Up @@ -347,7 +347,7 @@ describe('cachified', () => {
});

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

cache.set('weather', createCacheEntry('☁️'));
Expand Down Expand Up @@ -389,7 +389,7 @@ describe('cachified', () => {
});

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

const value = await cachified({
cache,
Expand Down Expand Up @@ -422,7 +422,7 @@ describe('cachified', () => {
});

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

const value = await cachified({
cache,
Expand All @@ -445,7 +445,7 @@ describe('cachified', () => {
});

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

cache.set('test', createCacheEntry('ONE'));
Expand Down Expand Up @@ -477,7 +477,7 @@ describe('cachified', () => {
});

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

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

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

const value1 = await cachified({
cache,
Expand All @@ -519,7 +519,7 @@ describe('cachified', () => {
});

it('handles cache write fails', async () => {
const cache = new Map<string, CacheEntry<string>>();
const cache = new Map<string, CacheEntry>();
const setMock = jest.spyOn(cache, 'set');
const reporter = createReporter();
let i = 0;
Expand Down Expand Up @@ -562,7 +562,7 @@ describe('cachified', () => {
});

it('gets fresh value when ttl is exceeded', async () => {
const cache = new Map<string, CacheEntry<string>>();
const cache = new Map<string, CacheEntry>();
const reporter = createReporter();
let i = 0;
const getValue = () =>
Expand Down Expand Up @@ -617,7 +617,7 @@ describe('cachified', () => {
});

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

Expand Down Expand Up @@ -649,7 +649,7 @@ describe('cachified', () => {
});

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

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

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

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

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

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

Expand Down Expand Up @@ -931,7 +931,7 @@ describe('cachified', () => {
});

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

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

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

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

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

const value = await cachified({
Expand Down Expand Up @@ -1265,7 +1265,7 @@ describe('cachified', () => {

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

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

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

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

await cachified({
Expand All @@ -1329,7 +1329,7 @@ describe('verbose reporter', () => {
});

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

await cachified({
Expand All @@ -1350,7 +1350,7 @@ describe('verbose reporter', () => {
});

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

await cachified({
Expand All @@ -1393,7 +1393,7 @@ describe('verbose reporter', () => {
});

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

await cachified({
Expand All @@ -1413,7 +1413,7 @@ describe('verbose reporter', () => {
});

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

it('logs when cache revalidation fails', async () => {
const cache = new Map<string, CacheEntry<string>>();
const cache = new Map<string, CacheEntry>();
const logger = createLogger();
cache.set('test', createCacheEntry('ONE', { ttl: 5, swv: 10 }));
currentTime = 7;
Expand Down
2 changes: 1 addition & 1 deletion src/cachified.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { shouldRefresh } from './shouldRefresh';
// This is to prevent requesting multiple fresh values in parallel
// while revalidating or getting first value
// Keys are unique per cache but may be used by multiple caches
const pendingValuesByCache = new WeakMap<Cache<any>, Map<string, any>>();
const pendingValuesByCache = new WeakMap<Cache, Map<string, any>>();

export async function cachified<Value>(
options: CachifiedOptions<Value>,
Expand Down
Loading

0 comments on commit c3603b1

Please sign in to comment.