export interface WorkspaceQueryCacheEntry { readonly resolvedAtUnixMs: number; readonly stale: boolean; readonly value: TValue; readonly version: number; } export interface WorkspaceQueryCache { invalidate(key?: string): void; read(key: string): WorkspaceQueryCacheEntry | null; request( key: string, load: () => Promise ): Promise>; write(key: string, value: TValue): WorkspaceQueryCacheEntry; } type MutableWorkspaceQueryCacheEntry = Omit< WorkspaceQueryCacheEntry, "stale" > & { stale: boolean }; const DEFAULT_MAX_ENTRIES = 24; export function createWorkspaceQueryCache(options?: { maxEntries?: number; now?: () => number; }): WorkspaceQueryCache { const maxEntries = Math.max(1, options?.maxEntries ?? DEFAULT_MAX_ENTRIES); const now = options?.now ?? Date.now; const entries = new Map>(); const requests = new Map>>(); let version = 0; const touch = ( key: string, entry: MutableWorkspaceQueryCacheEntry ): void => { entries.delete(key); entries.set(key, entry); }; const trim = (): void => { while (entries.size > maxEntries) { const oldestKey = entries.keys().next().value; if (typeof oldestKey !== "string") return; entries.delete(oldestKey); } }; const write = ( key: string, value: TValue ): MutableWorkspaceQueryCacheEntry => { const entry: MutableWorkspaceQueryCacheEntry = { resolvedAtUnixMs: now(), stale: false, value, version: ++version }; touch(key, entry); trim(); return entry; }; return { invalidate(key) { if (key !== undefined) { const entry = entries.get(key); if (entry) entry.stale = true; return; } for (const entry of entries.values()) entry.stale = true; }, read(key) { const entry = entries.get(key); if (!entry) return null; touch(key, entry); return entry; }, request(key, load) { const active = requests.get(key); if (active) return active; const request = load() .then((value): WorkspaceQueryCacheEntry => { return write(key, value); }) .finally(() => { if (requests.get(key) === request) requests.delete(key); }); requests.set(key, request); return request; }, write(key, value) { return write(key, value); } }; }