fix(infra): support non-durable text writes

This commit is contained in:
Vincent Koc
2026-05-07 05:08:20 -07:00
parent 3a89e20b7b
commit 11a038207b
2 changed files with 38 additions and 1 deletions

View File

@@ -96,6 +96,18 @@ describe("json file helpers", () => {
});
});
it("can skip durable fsync work for hot state writes", async () => {
await withTempDir({ prefix: "openclaw-json-files-" }, async (base) => {
const filePath = path.join(base, "state.json");
const openSpy = vi.spyOn(fs, "open");
await writeTextAtomic(filePath, "new", { durable: false });
expect(openSpy).not.toHaveBeenCalled();
await expect(fs.readFile(filePath, "utf8")).resolves.toBe("new");
});
});
it("preserves text when Windows rename reports EPERM", async () => {
await withTempDir({ prefix: "openclaw-json-files-" }, async (base) => {
const filePath = path.join(base, "state.json");

View File

@@ -1,4 +1,6 @@
import "./fs-safe-defaults.js";
import { replaceFileAtomic } from "./replace-file.js";
export {
JsonFileReadError,
readJson,
@@ -17,5 +19,28 @@ export {
writeJson as writeJsonAtomic,
writeJsonSync,
} from "@openclaw/fs-safe/json";
export { writeTextAtomic } from "@openclaw/fs-safe/atomic";
export { createAsyncLock } from "@openclaw/fs-safe/advanced";
export type WriteTextAtomicOptions = {
mode?: number;
dirMode?: number;
trailingNewline?: boolean;
durable?: boolean;
};
export async function writeTextAtomic(
filePath: string,
content: string,
options?: WriteTextAtomicOptions,
): Promise<void> {
const payload = options?.trailingNewline && !content.endsWith("\n") ? `${content}\n` : content;
await replaceFileAtomic({
filePath,
content: payload,
mode: options?.mode ?? 0o600,
dirMode: options?.dirMode ?? 0o777 & ~process.umask(),
copyFallbackOnPermissionError: true,
syncTempFile: options?.durable !== false,
syncParentDir: options?.durable !== false,
});
}