import { commandIds } from "@commands/ids"; import type { AppStore } from "@editor/store"; import type { ProjectFile } from "./format"; import { parseProject, serializeProject } from "./format"; import type { RecoveryStorage } from "@platform/projectStorage"; export const RECOVERY_STORAGE_KEY = "image-studio.recovery.v1"; type TimerHandle = number | ReturnType; export type ProjectLifecycle = { recover(): Promise; open(source: string): Promise; snapshot(): string; markSaved(): Promise; dispose(): void; }; export function createProjectLifecycle(options: { store: AppStore; storage: RecoveryStorage; debounceMs?: number; schedule?: (callback: () => void, delay: number) => TimerHandle; cancel?: (handle: TimerHandle) => void; }): ProjectLifecycle { const debounceMs = options.debounceMs ?? 750; const schedule = options.schedule ?? setTimeout; const cancel = options.cancel ?? ((handle: TimerHandle) => clearTimeout(handle)); let observedDocument = options.store.getState().document; let pending: TimerHandle | undefined; let disposed = false; const cancelPending = () => { if (pending === undefined) return; cancel(pending); pending = undefined; }; const unsubscribe = options.store.subscribe((state) => { if (state.document === observedDocument) return; observedDocument = state.document; cancelPending(); pending = schedule(() => { pending = undefined; void options.storage.write(RECOVERY_STORAGE_KEY, serializeProject(options.store.getState().document)).catch(() => undefined); }, debounceMs); }); const replace = (project: ProjectFile) => { options.store.dispatch(commandIds.projectOpen, { document: project.document }); cancelPending(); observedDocument = options.store.getState().document; return project; }; return { async recover() { const source = await options.storage.read(RECOVERY_STORAGE_KEY); if (!source || disposed) return undefined; try { return replace(parseProject(source)); } catch { await options.storage.remove(RECOVERY_STORAGE_KEY); return undefined; } }, async open(source) { const project = replace(parseProject(source)); await options.storage.remove(RECOVERY_STORAGE_KEY); return project; }, snapshot() { return serializeProject(options.store.getState().document); }, async markSaved() { cancelPending(); observedDocument = options.store.getState().document; await options.storage.remove(RECOVERY_STORAGE_KEY); }, dispose() { disposed = true; unsubscribe(); cancelPending(); }, }; }