import { createHash, randomUUID } from "node:crypto"; import { chmodSync, closeSync, existsSync, fsyncSync, linkSync, mkdirSync, openSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs"; import { dirname } from "node:path"; const JSON_LIMIT = 512 * 1024; export function stableDigest(value: unknown): string { return createHash("sha256").update(canonicalJson(value)).digest("hex"); } export function canonicalJson(value: unknown): string { return JSON.stringify(canonicalize(value)); } export function fileSha256(path: string): string { return createHash("sha256").update(readFileSync(path)).digest("hex"); } export function writeImmutableJson(path: string, value: unknown): boolean { const body = `${JSON.stringify(value, null, 2)}\n`; if (Buffer.byteLength(body) > JSON_LIMIT) { throw new Error(`immutable record exceeds ${JSON_LIMIT} bytes`); } mkdirSync(dirname(path), { recursive: true, mode: 0o700 }); const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`; let fd: number | undefined; try { fd = openSync(temporary, "wx", 0o600); writeFileSync(fd, body, "utf8"); fsyncSync(fd); closeSync(fd); fd = undefined; try { linkSync(temporary, path); chmodSync(path, 0o600); fsyncParentDirectory(path); return true; } catch (error) { if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error; const existing = readFileSync(path, "utf8"); if (existing !== body) throw new Error(`immutable record already exists at ${path}`); return false; } } finally { if (fd !== undefined) closeSync(fd); if (existsSync(temporary)) unlinkSync(temporary); } } export function fsyncParentDirectory(path: string): void { const directory = dirname(path); const fd = openSync(directory, "r"); try { fsyncSync(fd); } finally { closeSync(fd); } } export function readJsonRecord(path: string, label: string): T { if (!existsSync(path)) throw new Error(`${label} does not exist at ${path}`); const size = statSync(path).size; if (size <= 0 || size > JSON_LIMIT) throw new Error(`${label} has invalid size ${size}`); try { return JSON.parse(readFileSync(path, "utf8")) as T; } catch (error) { throw new Error(`cannot parse ${label} at ${path}: ${(error as Error).message}`); } } function canonicalize(value: unknown): unknown { if (Array.isArray(value)) return value.map(canonicalize); if (value && typeof value === "object") { return Object.fromEntries( Object.entries(value as Record) .filter(([, item]) => item !== undefined) .sort(([a], [b]) => a.localeCompare(b)) .map(([key, item]) => [key, canonicalize(item)]), ); } return value; }