import type { CatalystResult, Logger } from '../types/index'; import { CatalystAnalyst } from './CatalystAnalyst'; export class CatalystCache { private static readonly TTL_MS = 15 * 60 * 1000; // 15 minutes private cached: CatalystResult | null = null; private cachedAt: number | null = null; private isRefreshing = false; private analyst: CatalystAnalyst; private logger: Pick; constructor({ logger }: { logger?: Pick } = {}) { this.analyst = new CatalystAnalyst({ logger }); this.logger = logger ?? { write: (msg: string) => process.stdout.write(msg) }; } async get(): Promise { const now = Date.now(); const isStale = !this.cachedAt || now - this.cachedAt > CatalystCache.TTL_MS; if (!isStale && this.cached) { return this.cached; } if (this.isRefreshing) { // Return stale cache while refresh in progress if (this.cached) { return this.cached; } // If no cache exists yet, wait for refresh to complete return new Promise((resolve) => { const checkInterval = setInterval(() => { if (!this.isRefreshing && this.cached) { clearInterval(checkInterval); resolve(this.cached!); } }, 100); // Timeout after 30s setTimeout(() => clearInterval(checkInterval), 30000); }); } // Trigger refresh this.isRefreshing = true; try { this.logger.write('📡 Refreshing catalyst cache...\n'); this.cached = await this.analyst.run(); this.cachedAt = now; } catch (error) { this.logger.write(`⚠️ Catalyst refresh failed: ${error}\n`); // Return stale cache on error if (!this.cached) { this.cached = { tickers: [], tickerFrequency: {}, stories: [] }; } } finally { this.isRefreshing = false; } return this.cached; } isExpired(): boolean { if (!this.cachedAt) return true; return Date.now() - this.cachedAt > CatalystCache.TTL_MS; } clear(): void { this.cached = null; this.cachedAt = null; } }