Created
August 7, 2024 22:21
-
-
Save lindesvard/e758b8eb04a319f4fc2cdab941cb7ed3 to your computer and use it in GitHub Desktop.
Cachable function
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { getRedisCache } from './redis'; | |
export function cacheable<T extends (...args: any) => any>( | |
fn: T, | |
expireInSec: number | |
) { | |
const cachePrefix = `cachable:${fn.name}`; | |
function stringify(obj: unknown): string { | |
if (obj === null) return 'null'; | |
if (obj === undefined) return 'undefined'; | |
if (typeof obj === 'boolean') return obj ? 'true' : 'false'; | |
if (typeof obj === 'number') return String(obj); | |
if (typeof obj === 'string') return obj; | |
if (typeof obj === 'function') return obj.toString(); | |
if (Array.isArray(obj)) { | |
return '[' + obj.map(stringify).join(',') + ']'; | |
} | |
if (typeof obj === 'object') { | |
const pairs = Object.entries(obj) | |
.sort(([a], [b]) => a.localeCompare(b)) | |
.map(([key, value]) => `${key}:${stringify(value)}`); | |
return pairs.join(':'); | |
} | |
// Fallback for any other types | |
return String(obj); | |
} | |
const getKey = (...args: Parameters<T>) => | |
`${cachePrefix}:${stringify(args)}`; | |
const cachedFn = async function ( | |
...args: Parameters<T> | |
): Promise<Awaited<ReturnType<T>>> { | |
// JSON.stringify here is not bullet proof since ordering of object keys matters etc | |
const key = getKey(...args); | |
const cached = await getRedisCache().get(key); | |
if (cached) { | |
try { | |
console.log('[cachable] Hit', key); | |
return JSON.parse(cached); | |
} catch (e) { | |
console.error('Failed to parse cache', e); | |
} | |
} | |
const result = await fn(...(args as any)); | |
console.log('[cachable] Miss', key); | |
if (result !== undefined || result !== null) { | |
getRedisCache().setex(key, expireInSec, JSON.stringify(result)); | |
} | |
return result; | |
}; | |
cachedFn.getKey = getKey; | |
cachedFn.clear = async function (...args: Parameters<T>) { | |
const key = getKey(...args); | |
console.log('[cachable] Clear', key); | |
return getRedisCache().del(key); | |
}; | |
return cachedFn; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment