/** * CassetteStore * * Reads and writes cassette files from a directory on disk. * Each cassette is stored as a JSON file named after the 32-char key hash. * Cassette files are intended to be committed to git — they are the * "recorded interactions" that allow tests to run without real API calls. */ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import type { CassetteKey, CassetteEntry } from './types.js'; import { buildCassetteKey } from './key.js'; export class CassetteStore { constructor(private readonly cassetteDir: string) {} private pathFor(keyHash: string): string { return join(this.cassetteDir, `${keyHash}.json`); } /** * Look up a cassette by its key. * Returns null if not found or if the file is corrupt. */ find(key: CassetteKey): CassetteEntry | null { const hash = buildCassetteKey(key); const path = this.pathFor(hash); if (!existsSync(path)) return null; try { return JSON.parse(readFileSync(path, 'utf-8')) as CassetteEntry; } catch { return null; } } /** * Save a cassette to disk. Creates the cassette directory if needed. * Prints the cassette filename so it's visible during recording runs. */ save(key: CassetteKey, entry: CassetteEntry): void { mkdirSync(this.cassetteDir, { recursive: true }); const hash = buildCassetteKey(key); const path = this.pathFor(hash); writeFileSync(path, JSON.stringify(entry, null, 2), 'utf-8'); console.log(`[cassette] recorded → ${hash}.json (${entry.recording.jsonlLines.length} lines)`); } }