agent-ecosystem/src/main/services/discovery/SearchTextCache.ts
matt 75dfcf2d50 feat: implement SearchTextCache and SearchTextExtractor for efficient text extraction and caching
- Added SearchTextCache for LRU caching of extracted search text with mtime invalidation.
- Introduced SearchTextExtractor for lightweight extraction of searchable text from session messages.
- Updated SessionSearcher to utilize the new extractor and cache for improved search performance.
- Added tests for SearchTextCache and SearchTextExtractor to ensure functionality and correctness.
2026-02-22 02:03:22 +09:00

95 lines
2.4 KiB
TypeScript

/**
* 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<string, CacheEntry>();
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;
}
}