2e7860637e
- Restructured server layer with 5 domains: shared, screener, portfolio, calls, finance - Migrated 58 TypeScript files to domain-driven structure - Updated CLAUDE.md with new architecture documentation - Added .gitignore rules for .md files (except CLAUDE.md) - Removed unused CatalystAnalyst import from app.ts - Fixed lint errors: removed unused imports, fixed regex escape, added console suppressions - Verified no sensitive data in git history - Server code compiles cleanly with TypeScript strict mode
72 lines
2.1 KiB
TypeScript
72 lines
2.1 KiB
TypeScript
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<Logger, 'write'>;
|
|
|
|
constructor({ logger }: { logger?: Pick<Logger, 'write'> } = {}) {
|
|
this.analyst = new CatalystAnalyst({ logger });
|
|
this.logger = logger ?? { write: (msg: string) => process.stdout.write(msg) };
|
|
}
|
|
|
|
async get(): Promise<CatalystResult> {
|
|
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;
|
|
}
|
|
}
|