export interface Cache { get: (key: string) => Promise set: (key: string, data: Data) => Promise } export type CacheU = Cache /** * TODO: Rewrite this function */ export async function fetchCachedData(cache: CacheU, key: string, getData: (cache: CacheU) => Promise, validateData: (data: Data) => Data) { /** * Potential bug: validateData may throw if the schema changes while cached data stays in the same format * Mitigation: drop the cache if validate throws */ const maybeData = await cache.get(key) if (maybeData) { return validateData(maybeData as Data /* validation should throw if the type doesn't match */) } else { const data = await refreshCachedData(cache, key, getData) return validateData(data as Data /* validation should throw if the type doesn't match */) } } export async function refreshCachedData(cache: Cache, key: string, getData: (cache: Cache) => Promise) { const data = await getData(cache) await cache.set(key, data) return data } export function getNoopCache(): Cache { return { get: async (key) => undefined, set: async (key, data) => undefined, } } export const getTestCache = (): Cache => ({ get: async (key) => { throw new Error('Tests must not use cache') }, set: async (key, data) => { throw new Error('Tests must not use cache') }, }) export const toRefreshedCache = (cache: Cache): Cache => ({ get: async (key) => undefined, // if get returns undefined, then ensureCachedData will call resetCachedData set: cache.set, }) export const toPartiallyRefreshedCache = (cache: Cache, refreshCacheKeyPrefixes: string[]): Cache => ({ get: async (key) => { if (shouldRefreshCache(key, refreshCacheKeyPrefixes)) { return undefined } else { return cache.get(key) } }, set: cache.set, }) function shouldRefreshCache(key: string, refreshCacheKeyPrefixes: string[]) { return refreshCacheKeyPrefixes.find(p => key.startsWith(p)) } export const toSerializedCache = (serialize: (data: ExternalData) => InternalData, deserialize: (data: InternalData) => ExternalData) => (cache: Cache): Cache => ({ get: async (key) => { const data = await cache.get(key) return data ? deserialize(data) : undefined }, set: async (key, data) => { return cache.set(key, serialize(data)) }, })