- 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.
95 lines
2.4 KiB
TypeScript
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;
|
|
}
|
|
}
|