/** * SearchTextCache - LRU cache for extracted search text with mtime invalidation. * * Caches SearchTextResult per session file path. Entries are small (~1KB each, * just text + metadata), so 200 entries is a reasonable default. * * Invalidation: mtime comparison on get(). If the file's mtime has changed * since caching, the entry is considered stale and undefined is returned. * No TTL needed — mtime check is sufficient. */ import type { SearchableEntry } from './SearchTextExtractor'; interface CacheEntry { entries: SearchableEntry[]; sessionTitle: string | undefined; mtimeMs: number; } export class SearchTextCache { private readonly cache = new Map(); private readonly maxSize: number; constructor(maxSize: number = 200) { this.maxSize = maxSize; } /** * Get cached entries for a file path if the mtime matches. * Returns undefined if not cached or stale. */ get( filePath: string, mtimeMs: number ): { entries: SearchableEntry[]; sessionTitle: string | undefined } | undefined { const entry = this.cache.get(filePath); if (!entry) return undefined; // Stale — file was modified since we cached it if (entry.mtimeMs !== mtimeMs) { this.cache.delete(filePath); return undefined; } // LRU: delete and re-insert to move to end (most recent) this.cache.delete(filePath); this.cache.set(filePath, entry); return { entries: entry.entries, sessionTitle: entry.sessionTitle }; } /** * Cache extracted entries for a file path. */ set( filePath: string, mtimeMs: number, entries: SearchableEntry[], sessionTitle: string | undefined ): void { // If already exists, delete first to update position this.cache.delete(filePath); // Evict oldest if at capacity if (this.cache.size >= this.maxSize) { const oldest = this.cache.keys().next().value; if (oldest !== undefined) { this.cache.delete(oldest); } } this.cache.set(filePath, { entries, sessionTitle, mtimeMs }); } /** * Remove a specific entry from the cache. */ invalidate(filePath: string): void { this.cache.delete(filePath); } /** * Clear all cached entries. */ clear(): void { this.cache.clear(); } /** * Current number of cached entries. */ get size(): number { return this.cache.size; } }