/** * JSONL File Operations * * Utilities for reading and writing JSONL (JSON Lines) files. * Each line is a valid JSON object, making it append-friendly and streamable. */ import { mkdir, appendFile, readFile, writeFile } from "node:fs/promises"; import { existsSync } from "node:fs"; import { dirname } from "node:path"; /** * Ensure directory exists for a file path */ export async function ensureDir(filePath: string): Promise { const dir = dirname(filePath); if (!existsSync(dir)) { await mkdir(dir, { recursive: true }); } } /** * Append a record to a JSONL file */ export async function appendJsonl(filePath: string, record: T): Promise { await ensureDir(filePath); const line = JSON.stringify(record) + "\n"; await appendFile(filePath, line, "utf-8"); } /** * Read all records from a JSONL file */ export async function readJsonl(filePath: string): Promise { if (!existsSync(filePath)) { return []; } const content = await readFile(filePath, "utf-8"); const lines = content.trim().split("\n").filter(line => line.length > 0); return lines.map((line, index) => { try { return JSON.parse(line) as T; } catch (error) { console.warn(`Failed to parse JSONL line ${index + 1}: ${error}`); return null; } }).filter((record): record is T => record !== null); } /** * Read records from a JSONL file with filtering */ export async function queryJsonl( filePath: string, predicate: (record: T) => boolean, options?: { limit?: number } ): Promise { const records = await readJsonl(filePath); let filtered = records.filter(predicate); if (options?.limit && options.limit > 0) { filtered = filtered.slice(0, options.limit); } return filtered; } /** * Update a record in a JSONL file by rewriting * Note: This is O(n) - for frequent updates consider a different storage */ export async function updateJsonl( filePath: string, predicate: (record: T) => boolean, updater: (record: T) => T ): Promise { const records = await readJsonl(filePath); let updated = false; const newRecords = records.map(record => { if (predicate(record)) { updated = true; return updater(record); } return record; }); if (updated) { const content = newRecords.map(r => JSON.stringify(r)).join("\n") + "\n"; await writeFile(filePath, content, "utf-8"); } return updated; } /** * Find a single record in a JSONL file */ export async function findJsonl( filePath: string, predicate: (record: T) => boolean ): Promise { const records = await readJsonl(filePath); return records.find(predicate) ?? null; }