import { chmod, link, open, readFile, rename, unlink, } from "node:fs/promises"; import { dirname, join } from "node:path"; import { randomUUID } from "node:crypto"; export type AtomicWritePoint = | "after-file-sync" | "before-commit" | "after-commit" | "after-directory-sync"; export type AtomicFaultInjector = ( point: AtomicWritePoint, ) => void | Promise; export interface AtomicJsonWriteOptions { readonly createOnly?: boolean; readonly fault?: AtomicFaultInjector; } /** * Writes one private JSON record through a same-directory atomic commit. * * `createOnly` uses an exclusive hard-link commit so a contender can never * replace an existing record. Replacement uses rename. In both cases the * complete file is synced before it becomes visible and the parent directory * is synced after the namespace mutation. */ export async function writeAtomicJson( path: string, value: unknown, options: AtomicJsonWriteOptions = {}, ): Promise { const parent = dirname(path); const temporaryPath = join(parent, `.pi-amq-${randomUUID()}.tmp`); const contents = `${JSON.stringify(value)}\n`; let committed = false; try { const handle = await open(temporaryPath, "wx", 0o600); try { await handle.writeFile(contents, "utf8"); await handle.sync(); } finally { await handle.close(); } await chmod(temporaryPath, 0o600); await options.fault?.("after-file-sync"); await options.fault?.("before-commit"); if (options.createOnly) { await link(temporaryPath, path); committed = true; await unlink(temporaryPath); } else { await rename(temporaryPath, path); committed = true; } await options.fault?.("after-commit"); await syncDirectory(parent); await options.fault?.("after-directory-sync"); } catch (error) { if (!committed) await removeIfPresent(temporaryPath); throw error; } } export async function readJson( path: string, ): Promise { let contents: string; try { contents = await readFile(path, "utf8"); } catch (error) { if (isNodeError(error) && error.code === "ENOENT") return undefined; throw error; } return JSON.parse(contents) as unknown; } export async function syncDirectory(path: string): Promise { let handle; try { handle = await open(path, "r"); await handle.sync(); } catch (error) { if (!isUnsupportedDirectorySync(error)) throw error; } finally { await handle?.close(); } } async function removeIfPresent(path: string): Promise { try { await unlink(path); } catch (error) { if (!isNodeError(error) || error.code !== "ENOENT") throw error; } } function isUnsupportedDirectorySync(error: unknown): boolean { return isNodeError(error) && (error.code === "EINVAL" || error.code === "ENOTSUP" || error.code === "EISDIR"); } function isNodeError(error: unknown): error is NodeJS.ErrnoException { return error instanceof Error && "code" in error; }