import { LRUCache } from "lru-cache"; export interface Cached { (...args: Args): Promise; /** Drop all cached entries, forcing the next call to re-fetch. */ clear(): void; } export function cached( f: (...args: Args) => Promise, makeKey: (...args: Args) => string, options?: LRUCache.Options }, unknown>, missTtl?: number, ): Cached { const cache = new LRUCache(options || { max: 500 }); const fn = function (...args: Args): Promise { const key = makeKey(...args); const hit = cache.get(key); if (hit !== undefined) return hit.promise; const promise = f(...args); // Evict on rejection so the next caller can retry with a fresh request. promise.then( value => { if (missTtl !== undefined && (value === null || value === undefined)) { const entry = cache.get(key); if (entry?.promise === promise) cache.set(key, entry, { ttl: missTtl }); } }, () => { cache.delete(key); }, ); cache.set(key, { promise }); return promise; } as Cached; fn.clear = () => cache.clear(); return fn; }