/** * File-based event store implementation. * Appends events as JSON lines to a file on disk. * Suitable for single-instance dev/staging deployments. */ import { readFile, appendFile, writeFile, mkdir } from 'node:fs/promises'; import { dirname } from 'node:path'; import type { EventStore, StoredEvent, EventStoreQuery } from './types.js'; export interface FileStoreOptions { filePath: string; } export class FileEventStore implements EventStore { private readonly filePath: string; constructor(options: FileStoreOptions) { this.filePath = options.filePath; } async append(event: StoredEvent): Promise { await mkdir(dirname(this.filePath), { recursive: true }); await appendFile(this.filePath, JSON.stringify(event) + '\n', 'utf-8'); } async query(q: EventStoreQuery): Promise { const all = await this.readAll(); let results = all; if (q.userId) results = results.filter(e => e.userId === q.userId); if (q.type) results = results.filter(e => e.type === q.type); if (q.after) results = results.filter(e => e.timestamp > q.after!); if (q.before) results = results.filter(e => e.timestamp < q.before!); if (q.limit && q.limit > 0) { results = results.slice(-q.limit); } return results; } async recent(limit = 50): Promise { const all = await this.readAll(); return all.slice(-limit); } async count(): Promise { const all = await this.readAll(); return all.length; } async clear(): Promise { await writeFile(this.filePath, '', 'utf-8'); } private async readAll(): Promise { try { const content = await readFile(this.filePath, 'utf-8'); return content .split('\n') .filter(line => line.trim()) .map(line => JSON.parse(line) as StoredEvent); } catch { return []; } } }