import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import type { DomainError } from "../../errors/generic-domain-error.js"; import type { ObservationStore } from "../../../domain/ports/observation-store.port.js"; import { Ok, type Result, type Observation } from "@tff/core"; import type { Candidate } from "../../../shared/value-objects/candidate.js"; import type { Pattern } from "../../../shared/value-objects/pattern.js"; export class JsonlStoreAdapter implements ObservationStore { private readonly sessionsPath: string; private readonly patternsPath: string; private readonly candidatesPath: string; constructor(basePath: string) { this.sessionsPath = join(basePath, "sessions.jsonl"); this.patternsPath = join(basePath, "patterns.jsonl"); this.candidatesPath = join(basePath, "candidates.jsonl"); } async appendObservation(obs: Observation): Promise> { await mkdir(join(this.sessionsPath, ".."), { recursive: true }); await appendFile(this.sessionsPath, `${JSON.stringify(obs)}\n`); return Ok(undefined); } async readObservations(): Promise> { return this.readJsonl(this.sessionsPath); } async writePatterns(patterns: Pattern[]): Promise> { return this.writeJsonl(this.patternsPath, patterns); } async readPatterns(): Promise> { return this.readJsonl(this.patternsPath); } async writeCandidates(candidates: Candidate[]): Promise> { return this.writeJsonl(this.candidatesPath, candidates); } async readCandidates(): Promise> { return this.readJsonl(this.candidatesPath); } private async readJsonl(path: string): Promise> { try { const content = await readFile(path, "utf-8"); const lines = content .trim() .split("\n") .filter((l) => l.length > 0); return Ok(lines.map((l) => JSON.parse(l) as T)); } catch { return Ok([]); } } private async writeJsonl(path: string, items: T[]): Promise> { await mkdir(join(path, ".."), { recursive: true }); const content = `${items.map((i) => JSON.stringify(i)).join("\n")}\n`; await writeFile(path, content); return Ok(undefined); } }