import { logger } from '../logging.ts'; import type { CacheDb } from './classes.cachedb.ts'; // deno-lint-ignore no-explicit-any type DocumentClass = { getInstances: (filter: any) => Promise<{ delete: () => Promise }[]> }; const DEFAULT_INTERVAL_MS = 60 * 60 * 1000; // 1 hour /** * Periodically cleans up expired cached documents. */ export class CacheCleaner { private intervalId: number | null = null; private intervalMs: number; private documentClasses: DocumentClass[] = []; private cacheDb: CacheDb; constructor(cacheDb: CacheDb, intervalMs = DEFAULT_INTERVAL_MS) { this.cacheDb = cacheDb; this.intervalMs = intervalMs; } /** Register a document class for cleanup */ registerClass(cls: DocumentClass): void { this.documentClasses.push(cls); } start(): void { if (this.intervalId !== null) return; this.intervalId = setInterval(() => { this.clean().catch((err) => { logger.error(`CacheCleaner error: ${err}`); }); }, this.intervalMs); // Unref so the interval doesn't prevent process exit Deno.unrefTimer(this.intervalId); logger.debug(`CacheCleaner started (interval: ${this.intervalMs}ms)`); } stop(): void { if (this.intervalId !== null) { clearInterval(this.intervalId); this.intervalId = null; logger.debug('CacheCleaner stopped'); } } /** Run a single cleanup pass */ async clean(): Promise { const now = Date.now(); let totalDeleted = 0; for (const cls of this.documentClasses) { try { const expired = await cls.getInstances({ expiresAt: { $lt: now } }); for (const doc of expired) { await doc.delete(); totalDeleted++; } } catch (err) { logger.error(`CacheCleaner: failed to clean class: ${err}`); } } if (totalDeleted > 0) { logger.debug(`CacheCleaner: deleted ${totalDeleted} expired document(s)`); } return totalDeleted; } }