75 lines
1.7 KiB
TypeScript
75 lines
1.7 KiB
TypeScript
import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
import { dirname } from "node:path";
|
|
import type { AppState } from "./types.js";
|
|
|
|
function defaultState(): AppState {
|
|
return {
|
|
sessions: [],
|
|
messages: [],
|
|
tasks: [],
|
|
workers: [],
|
|
approvals: [],
|
|
deviceBindings: [],
|
|
events: [],
|
|
};
|
|
}
|
|
|
|
export class FileStore {
|
|
private state: AppState;
|
|
private readonly backupFilePath: string;
|
|
private readonly tempFilePath: string;
|
|
|
|
constructor(private readonly filePath: string) {
|
|
this.backupFilePath = `${filePath}.bak`;
|
|
this.tempFilePath = `${filePath}.tmp`;
|
|
this.ensureDirectory();
|
|
this.state = this.load();
|
|
}
|
|
|
|
get snapshot(): AppState {
|
|
return structuredClone(this.state);
|
|
}
|
|
|
|
mutate<T>(mutator: (state: AppState) => T): T {
|
|
const result = mutator(this.state);
|
|
this.save();
|
|
return result;
|
|
}
|
|
|
|
reset(): AppState {
|
|
this.state = defaultState();
|
|
this.save();
|
|
return this.snapshot;
|
|
}
|
|
|
|
private ensureDirectory(): void {
|
|
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
}
|
|
|
|
private load(): AppState {
|
|
for (const path of [this.filePath, this.backupFilePath]) {
|
|
if (!existsSync(path)) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
const raw = readFileSync(path, "utf8");
|
|
return { ...defaultState(), ...(JSON.parse(raw) as AppState) };
|
|
} catch {
|
|
// try backup/default
|
|
}
|
|
}
|
|
|
|
return defaultState();
|
|
}
|
|
|
|
private save(): void {
|
|
const content = `${JSON.stringify(this.state, null, 2)}\n`;
|
|
writeFileSync(this.tempFilePath, content, "utf8");
|
|
if (existsSync(this.filePath)) {
|
|
copyFileSync(this.filePath, this.backupFilePath);
|
|
}
|
|
renameSync(this.tempFilePath, this.filePath);
|
|
}
|
|
}
|