/** * Generic in-memory file cache keyed by absolute path. * * Use case: "read a small file → parse → repeat the same read often". * * Invariants: * - Cache validity = (mtimeMs, size) match the on-disk stat result. * - On miss/stale, the caller-provided `compute(absPath, stats)` is invoked * and the result is stored along with the new (mtimeMs, size). * - LRU bounded by `maxEntries` (default 5000). * - Optional `ttlMs`: within the TTL window after last check, skip stat * and trust the cached value. Useful for files that rarely change and * where stat is expensive (e.g. OneDrive paths). * * Not in scope (yet): * - Disk persistence across process restarts * - fs.watch / inotify-based invalidation * - Async compute (this version is sync to match listSessions' sync API) * * See docs/2026-04-30-list-sessions-optimization.md */ import { type Stats } from 'fs'; export interface FileCacheOptions { /** Logging label, e.g. "session-jsonl". */ name: string; /** Compute function invoked on miss/stale. Receives absolute path. */ compute: (absPath: string, stats: Stats) => T; /** LRU upper bound. Default 5000. */ maxEntries?: number; /** * If > 0, skip `stat` within this window from the last check. Useful for * slow-stat filesystems (OneDrive, network drives) where the file is known * to change rarely. */ ttlMs?: number; /** If true, emit hit/miss/evict logs to console.log. */ debug?: boolean; } export interface FileCacheStats { hits: number; misses: number; staleMisses: number; computeErrors: number; invalidations: number; evictions: number; ttlSkips: number; } export declare class FileCache { private readonly name; private readonly compute; private readonly maxEntries; private readonly ttlMs; private readonly debug; private readonly entries; private readonly _stats; constructor(opts: FileCacheOptions); /** * Get cached value for `path`. Returns null if the file does not exist or * compute fails (compute exceptions are caught and logged). Note: if `T` * itself includes `null` (e.g. `FileCache`), a cached `null` * value is also returned as-is, which is indistinguishable from the * miss/error sentinel — callers that need to disambiguate should use a * non-nullable `T` (wrap the payload in an object). */ get(path: string): T | null; /** * Write-through: insert or update an entry without recomputing. * If `stats` is omitted, stat is performed inline. */ set(path: string, value: T, stats?: Stats): void; /** Drop a single entry. Next get will recompute. */ invalidate(path: string): void; invalidateAll(): void; /** * Drop entries whose absolute path is not in `existingPaths`. Use after * directory listings to garbage-collect deleted files. */ prune(existingPaths: Set): number; /** Current number of cached entries. */ size(): number; /** Read-only stats snapshot. */ stats(): Readonly & { size: number; }; /** Reset stats counters (entries unchanged). For tests. */ resetStats(): void; private enforceLruBound; }