interface CreateCacheOptions { expires?: number; // Default expiration time for all cache entries } interface SetCacheOptions { expires?: number; // Override expiration for individual cache entries } export function createCache( createOpts: CreateCacheOptions = {}, ) { const cache = new Map(); return { get(key: string): T | undefined { const entry = cache.get(key); if (!entry) return undefined; const now = Date.now(); if (entry.expiresAt && entry.expiresAt <= now) { cache.delete(key); // Remove expired entry return undefined; } return entry.value; // Return value if not expired }, set(key: string, value: T | unknown, opts: SetCacheOptions = {}) { const now = Date.now(); const expiresIn = opts.expires ?? createOpts.expires; const expiresAt = expiresIn ? now + expiresIn : undefined; cache.set(key, { value: value as T, expiresAt }); }, cleanup() { const now = Date.now(); for (const [key, entry] of cache.entries()) { if (entry.expiresAt && entry.expiresAt <= now) { cache.delete(key); } } }, info(): { count: number; sizeInKB: number } { // Cleanup expired entries before calculating info this.cleanup(); // Count the number of objects in the cache const count = cache.size; // Approximate the size in KB by serializing each key and value let totalBytes = 0; for (const [key, entry] of cache.entries()) { const keySize = new TextEncoder().encode(key).length; const valueSize = new TextEncoder().encode( JSON.stringify(entry.value), ).length; totalBytes += keySize + valueSize; } const sizeInKB = totalBytes / 1024; // Convert bytes to kilobytes return { count, sizeInKB }; }, has(key: string): boolean { const entry = cache.get(key); if (!entry) return false; const now = Date.now(); if (entry.expiresAt && entry.expiresAt <= now) { cache.delete(key); // Remove expired entry return false; } return true; }, keys(): string[] { this.cleanup(); // Cleanup before returning keys return Array.from(cache.keys()); }, size(): number { this.cleanup(); // Cleanup before returning size return cache.size; }, }; }