/** * core/json-store.ts — durable JSON state files. * * Every persisted store here follows the same shape: read it, modify it, write * it back. That shape has one catastrophic failure mode, and it is silent: * * read fails -> caller substitutes an empty default -> write makes it permanent * * A file that is briefly unreadable, or corrupt from a half-finished write, * becomes an empty object. The next ordinary update — registering a device, * renaming a session — then persists that emptiness over real data and reports * success. Nothing errors, nothing logs, and the loss is only noticed later * when a push never arrives or every session has lost its name. * * The fix is to make "I could not read it" a THIRD outcome that callers cannot * accidentally treat as "it was empty": * * missing — legitimately absent, start fresh, writing is safe * ok — parsed, writing is safe * unreadable — exists but could not be parsed. NEVER overwrite: the bytes on * disk are the only copy of whatever is in there. * * Writes are atomic (temp + rename) so a crash mid-write cannot truncate a good * file into a corrupt one, and the previous contents are kept as `.bak` so even * a logic error upstream is recoverable. */ export type LoadResult = { status: "ok"; data: T; } | { status: "missing"; } | { status: "unreadable"; error: string; }; /** * Read a JSON store, distinguishing absent from unreadable. * * Deliberately returns a result rather than `T | null`: a nullable return is * what lets `?? {}` quietly turn corruption into an empty store. */ export declare function loadJson(path: string): LoadResult; /** * Write a JSON store atomically, keeping the previous contents as `.bak`. * * temp + rename means a reader never sees a partial file and a crash cannot * leave a truncated one, which is the usual way these files become corrupt in * the first place. */ export declare function saveJson(path: string, data: unknown, opts?: { backup?: boolean; }): void; /** * A store that refuses to save once its file has been found unreadable. * * Holding the flag matters as much as the initial check: these stores cache * their contents, so a single transient read failure at startup would * otherwise poison every later write for the lifetime of the process, long * after the file itself is readable again. */ export declare class GuardedStore { private readonly path; private readonly empty; private readonly label; private cache; private blocked; private blockedReason; constructor(path: string, empty: () => T, label: string); /** Contents, or an empty value when missing/unreadable. Never throws. */ load(): T; /** True when the backing file could not be read and must not be overwritten. */ isBlocked(): boolean; /** * Persist the cache. A no-op — loudly — when the file was unreadable, because * writing would replace data we were never able to see with the empty value * we substituted for it. */ save(): boolean; /** Drop the in-memory copy (and any block) so the next load re-reads disk. */ reset(): void; } //# sourceMappingURL=json-store.d.ts.map