54 lines
1.6 KiB
TypeScript
54 lines
1.6 KiB
TypeScript
import { mkdir, open, readFile, writeFile } from 'node:fs/promises';
|
|
import { dirname } from 'node:path';
|
|
|
|
export type BridgeState = {
|
|
cursors: Record<string, string>;
|
|
processedMessages: Record<string, { repliedNodeId: string; at: string }>;
|
|
};
|
|
|
|
const EMPTY_STATE: BridgeState = { cursors: {}, processedMessages: {} };
|
|
|
|
export class StateStore {
|
|
private state: BridgeState = structuredClone(EMPTY_STATE);
|
|
constructor(private readonly path: string) {}
|
|
|
|
async load(): Promise<void> {
|
|
await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
try {
|
|
const text = await readFile(this.path, 'utf8');
|
|
this.state = { ...structuredClone(EMPTY_STATE), ...JSON.parse(text) };
|
|
} catch (error) {
|
|
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
await this.save();
|
|
}
|
|
}
|
|
|
|
getCursor(key: string): string {
|
|
return this.state.cursors[key] ?? '0';
|
|
}
|
|
|
|
async setCursor(key: string, cursor: string): Promise<void> {
|
|
this.state.cursors[key] = cursor;
|
|
await this.save();
|
|
}
|
|
|
|
hasProcessed(messageId: string): boolean {
|
|
return Boolean(this.state.processedMessages[messageId]);
|
|
}
|
|
|
|
async markProcessed(messageId: string, repliedNodeId: string): Promise<void> {
|
|
this.state.processedMessages[messageId] = { repliedNodeId, at: new Date().toISOString() };
|
|
await this.save();
|
|
}
|
|
|
|
private async save(): Promise<void> {
|
|
const handle = await open(this.path, 'w', 0o600);
|
|
try {
|
|
await handle.writeFile(`${JSON.stringify(this.state, null, 2)}\n`, 'utf8');
|
|
await handle.chmod(0o600);
|
|
} finally {
|
|
await handle.close();
|
|
}
|
|
}
|
|
}
|