import { atom, type WritableAtom } from 'nanostores' import { readKey, writeKey } from './storage' // A nanostore that auto-persists. Reads its seed from localStorage through the // storage choke point (so every read/write is observable in one place) and // writes back on every change — no per-atom subscribe boilerplate. // // export const $foo = persistentAtom('hermes.desktop.foo', false, Codecs.bool) // Maps a value to/from its stored string form. `decode` only ever sees a real // stored string (absence falls back); `encode` returning null removes the key. export interface Codec { decode(raw: string): T encode(value: T): null | string } export const Codecs = { bool: { decode: raw => raw === 'true', encode: (value: boolean) => String(value) } as Codec, nullableText: { decode: raw => raw, encode: value => value } as Codec, text: { decode: raw => raw, encode: (value: string) => value } as Codec, // Mirrors storedStringArray/persistStringArray: drops non-strings, empty → removed. stringArray: { decode: raw => { const parsed = JSON.parse(raw) as unknown return Array.isArray(parsed) ? parsed.filter((item): item is string => typeof item === 'string' && item.length > 0) : [] }, encode: value => (value.length === 0 ? null : JSON.stringify(value)) } as Codec, // Mirrors storedStringRecord/persistStringRecord: keeps only string values. stringRecord: { decode: raw => { const parsed = JSON.parse(raw) as unknown if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { return {} } return Object.fromEntries( Object.entries(parsed).filter((entry): entry is [string, string] => typeof entry[1] === 'string') ) }, encode: value => JSON.stringify(value) } as Codec>, /** JSON with an optional sanitizer for untrusted persisted shapes. */ json(sanitize?: (value: unknown) => T): Codec { return { decode: raw => { const parsed = JSON.parse(raw) as unknown return sanitize ? sanitize(parsed) : (parsed as T) }, encode: value => JSON.stringify(value) } } } export function persistentAtom(key: string, fallback: T, codec: Codec = Codecs.json()): WritableAtom { const raw = readKey(key) let initial = fallback if (raw !== null) { try { initial = codec.decode(raw) } catch { initial = fallback } } const $value = atom(initial) $value.subscribe(value => writeKey(key, codec.encode(value))) return $value }