GET /api/sessions (and every session lookup) re-read and JSON.parsed every .jsonl session file synchronously on each request, blocking the agent's event loop. Parsed metadata is now cached per path (bounded to 500 entries) and invalidated when mtime or size changes.
35 lines
1.2 KiB
TypeScript
35 lines
1.2 KiB
TypeScript
export type FileStamp = { mtimeMs: number; size: number }
|
|
|
|
/**
|
|
* Small bounded cache of values derived from file contents, keyed by path and
|
|
* invalidated when the file's mtime or size changes. Oldest entries are evicted first.
|
|
*/
|
|
export function createStatCache<T>(maxEntries = 500) {
|
|
const entries = new Map<string, FileStamp & { value: T }>()
|
|
|
|
return {
|
|
/** Returns `{ value }` on a hit (the value itself may be null), `undefined` on a miss or stale entry. */
|
|
get(filePath: string, stamp: FileStamp): { value: T } | undefined {
|
|
const entry = entries.get(filePath)
|
|
if (!entry) return undefined
|
|
if (entry.mtimeMs !== stamp.mtimeMs || entry.size !== stamp.size) {
|
|
entries.delete(filePath)
|
|
return undefined
|
|
}
|
|
return { value: entry.value }
|
|
},
|
|
set(filePath: string, stamp: FileStamp, value: T): void {
|
|
entries.delete(filePath)
|
|
entries.set(filePath, { mtimeMs: stamp.mtimeMs, size: stamp.size, value })
|
|
while (entries.size > maxEntries) {
|
|
const oldest = entries.keys().next().value
|
|
if (oldest === undefined) break
|
|
entries.delete(oldest)
|
|
}
|
|
},
|
|
get size(): number {
|
|
return entries.size
|
|
},
|
|
}
|
|
}
|