feat: add atomic JSON file state persistence for production runtime

This commit is contained in:
Codex
2026-02-18 12:57:45 +00:00
parent 9b3cdbff1a
commit 0c2124e4cf
2 changed files with 72 additions and 0 deletions

37
src/lib/state-store.js Normal file
View File

@@ -0,0 +1,37 @@
"use strict";
const fs = require("node:fs/promises");
const path = require("node:path");
class JsonFileStateStore {
constructor(filePath) {
this.filePath = filePath;
}
async load() {
try {
const raw = await fs.readFile(this.filePath, "utf8");
return JSON.parse(raw);
} catch (error) {
if (error && error.code === "ENOENT") {
return null;
}
throw error;
}
}
async save(state) {
const dir = path.dirname(this.filePath);
await fs.mkdir(dir, { recursive: true });
const tempPath = `${this.filePath}.tmp`;
const payload = JSON.stringify(state, null, 2);
await fs.writeFile(tempPath, payload, "utf8");
await fs.rename(tempPath, this.filePath);
}
}
module.exports = {
JsonFileStateStore,
};

35
test/state-store.test.js Normal file
View File

@@ -0,0 +1,35 @@
"use strict";
const test = require("node:test");
const assert = require("node:assert/strict");
const os = require("node:os");
const path = require("node:path");
const fs = require("node:fs/promises");
const { JsonFileStateStore } = require("../src/lib/state-store");
test("load returns null when state file does not exist", async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "xartaudio-state-test-"));
const store = new JsonFileStateStore(path.join(tempDir, "missing.json"));
const data = await store.load();
assert.equal(data, null);
});
test("save and load roundtrip state", async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "xartaudio-state-test-"));
const filePath = path.join(tempDir, "state.json");
const store = new JsonFileStateStore(filePath);
const expected = {
version: 1,
engine: {
jobs: { "1": { id: "1", status: "completed" } },
nextJobId: 2,
},
};
await store.save(expected);
const actual = await store.load();
assert.deepEqual(actual, expected);
});