src / core / json.ts
src / core / json.ts
import { constants } from "node:fs";
import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
import { dirname, basename, join } from "node:path";
import { createId } from "./id";
export async function ensureDir(path: string): Promise<void> {
await mkdir(path, { recursive: true });
}
export function jsonStringify(value: unknown, pretty = true): string {
return JSON.stringify(
value,
(_key, item) => {
if (typeof item === "bigint") return item.toString();
if (item instanceof Error) {
return {
name: item.name,
message: item.message,
stack: item.stack,
};
}
return item;
},
pretty ? 2 : undefined,
);
}
export async function atomicWriteText(path: string, content: string): Promise<void> {
await ensureDir(dirname(path));
const temp = join(dirname(path), `.${basename(path)}.${createId("tmp")}`);
try {
const noFollow = "O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0;
const handle = await open(
temp,
constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollow,
0o600,
);
try {
await handle.writeFile(content, "utf8");
} finally {
await handle.close();
}
try {
await rename(temp, path);
} catch (error) {
const code = (error as NodeJS.ErrnoException).code;
if (code !== "EEXIST" && code !== "EPERM") throw error;
await rm(path, { force: true });
await rename(temp, path);
}
} finally {
await rm(temp, { force: true }).catch(() => undefined);
}
}
export async function atomicWriteJson(path: string, value: unknown): Promise<void> {
await atomicWriteText(path, `${jsonStringify(value)}\n`);
}
export async function readJson<T>(path: string): Promise<T> {
return JSON.parse(await readFile(path, "utf8")) as T;
}
export async function appendJsonLine(path: string, value: unknown): Promise<void> {
await ensureDir(dirname(path));
const noFollow = "O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0;
const handle = await open(
path,
constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND | noFollow,
0o600,
);
try {
await handle.writeFile(`${jsonStringify(value, false)}\n`, "utf8");
} finally {
await handle.close();
}
}
import { constants } from "node:fs";
import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
import { dirname, basename, join } from "node:path";
import { createId } from "./id";
export async function ensureDir(path: string): Promise<void> {
await mkdir(path, { recursive: true });
}
export function jsonStringify(value: unknown, pretty = true): string {
return JSON.stringify(
value,
(_key, item) => {
if (typeof item === "bigint") return item.toString();
if (item instanceof Error) {
return {
name: item.name,
message: item.message,
stack: item.stack,
};
}
return item;
},
pretty ? 2 : undefined,
);
}
export async function atomicWriteText(path: string, content: string): Promise<void> {
await ensureDir(dirname(path));
const temp = join(dirname(path), `.${basename(path)}.${createId("tmp")}`);
try {
const noFollow = "O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0;
const handle = await open(
temp,
constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollow,
0o600,
);
try {
await handle.writeFile(content, "utf8");
} finally {
await handle.close();
}
try {
await rename(temp, path);
} catch (error) {
const code = (error as NodeJS.ErrnoException).code;
if (code !== "EEXIST" && code !== "EPERM") throw error;
await rm(path, { force: true });
await rename(temp, path);
}
} finally {
await rm(temp, { force: true }).catch(() => undefined);
}
}
export async function atomicWriteJson(path: string, value: unknown): Promise<void> {
await atomicWriteText(path, `${jsonStringify(value)}\n`);
}
export async function readJson<T>(path: string): Promise<T> {
return JSON.parse(await readFile(path, "utf8")) as T;
}
export async function appendJsonLine(path: string, value: unknown): Promise<void> {
await ensureDir(dirname(path));
const noFollow = "O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0;
const handle = await open(
path,
constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND | noFollow,
0o600,
);
try {
await handle.writeFile(`${jsonStringify(value, false)}\n`, "utf8");
} finally {
await handle.close();
}
}