export class APICache { constructor({ maxSize = 250, ttl = 1000 * 60 * 20 } = {}) { this.maxSize = maxSize; this.ttl = ttl; this.store = new Map(); } makeKey(namespace, key) { return `${namespace}:${String(key)}`; } isExpired(entry) { return !entry || Date.now() - entry.ts > entry.ttl; } pruneToSize() { if (this.store.size <= this.maxSize) return; const entries = [...this.store.entries()].sort((a, b) => a[1].ts - b[1].ts); const overflow = this.store.size - this.maxSize; for (let i = 0; i < overflow; i += 1) { this.store.delete(entries[i][0]); } } async get(namespace, key) { const cacheKey = this.makeKey(namespace, key); const entry = this.store.get(cacheKey); if (!entry) return null; if (this.isExpired(entry)) { this.store.delete(cacheKey); return null; } return entry.value; } async set(namespace, key, value, ttl = this.ttl) { const cacheKey = this.makeKey(namespace, key); this.store.set(cacheKey, { value, ts: Date.now(), ttl }); this.pruneToSize(); } clearExpired() { for (const [key, entry] of this.store.entries()) { if (this.isExpired(entry)) this.store.delete(key); } } async clear() { this.store.clear(); } getCacheStats() { const namespaces = new Set(); for (const key of this.store.keys()) namespaces.add(key.split(':', 1)[0]); return { size: this.store.size, namespaces: [...namespaces], maxSize: this.maxSize, ttlMs: this.ttl, }; } }