From f4cab35e26db21bc9fe9aabed5adb4b720ed5b26 Mon Sep 17 00:00:00 2001 From: arkon Date: Mon, 19 Jan 2026 11:51:32 +0100 Subject: [PATCH] perf: add debounced writes to state-store - Debounce save() calls by 500ms to batch multiple updates - Add saveNow() for immediate persistence when needed - Add flush() method for shutdown cleanup - Reduces disk I/O from O(n) to O(1) for rapid state changes Co-Authored-By: Claude Opus 4.5 --- src/state-store.ts | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/state-store.ts b/src/state-store.ts index fef1e978..40bfd958 100644 --- a/src/state-store.ts +++ b/src/state-store.ts @@ -3,9 +3,14 @@ import { homedir } from 'node:os'; import { dirname, join } from 'node:path'; import { AppState, createInitialState } from './types.js'; +// Debounce delay for batching state writes (ms) +const SAVE_DEBOUNCE_MS = 500; + export class StateStore { private state: AppState; private filePath: string; + private saveTimeout: NodeJS.Timeout | null = null; + private dirty: boolean = false; constructor(filePath?: string) { this.filePath = filePath || join(homedir(), '.claudeman', 'state.json'); @@ -42,11 +47,36 @@ export class StateStore { return createInitialState(); } + // Debounced save - batches multiple updates into a single write save(): void { + this.dirty = true; + if (this.saveTimeout) { + return; // Already scheduled + } + this.saveTimeout = setTimeout(() => { + this.saveNow(); + }, SAVE_DEBOUNCE_MS); + } + + // Immediate save - use when you need guaranteed persistence + saveNow(): void { + if (this.saveTimeout) { + clearTimeout(this.saveTimeout); + this.saveTimeout = null; + } + if (!this.dirty) { + return; + } + this.dirty = false; this.ensureDir(); writeFileSync(this.filePath, JSON.stringify(this.state, null, 2), 'utf-8'); } + // Flush any pending saves (call before shutdown) + flush(): void { + this.saveNow(); + } + getState(): AppState { return this.state; } @@ -108,7 +138,7 @@ export class StateStore { reset(): void { this.state = createInitialState(); this.state.config.stateFilePath = this.filePath; - this.save(); + this.saveNow(); // Immediate save for reset operations } }