history-file.ts
Language TypeScript1.9 KB48 lines
/*** Reading and writing `data/dashboard-history.json`. Node only.** Split out of history.ts because the Cloudflare Worker needs `rowFrom`,* `previousRun` and `delta` but has no filesystem, and a static `node:fs` import* resolves whether or not the symbol is used. The Worker keeps the same rows in* KV; only the storage differs.*/import { readFile, writeFile, mkdir } from 'node:fs/promises'import { dirname } from 'node:path'import { fileURLToPath } from 'node:url'import { KEEP, type HistoryRow } from './history.js'export const HISTORY_FILE = new URL('../../../data/dashboard-history.json', import.meta.url)export async function readHistory(file: URL = HISTORY_FILE): Promise<HistoryRow[]> {try {const parsed: unknown = JSON.parse(await readFile(file, 'utf8'))if (!Array.isArray(parsed)) return []// A hand-edited or half-written file must not take the build down; a missing// delta is a smaller problem than a dashboard that will not render.return parsed.filter((r): r is HistoryRow => !!r && typeof r === 'object' && typeof (r as HistoryRow).day === 'string')} catch {return []}}/*** Append today's row, replacing any earlier row for the same day, and trim.* Returns the rows as they were before this run, so the caller can diff against* them without racing itself.*/export async function recordRun(row: HistoryRow,file: URL = HISTORY_FILE,): Promise<HistoryRow[]> {const before = await readHistory(file)const kept = before.filter((r) => r.day !== row.day)const next = [...kept, row].sort((a, b) => a.day.localeCompare(b.day)).slice(-KEEP)try {await mkdir(dirname(fileURLToPath(file)), { recursive: true })await writeFile(file, `${JSON.stringify(next, null, 2)}\n`, 'utf8')} catch {// Read-only checkout, CI, whatever. Deltas stop working; nothing else does.}return before}