UNPKG

@stainless-code/persist

Version:

Hydration-aware persistence for any reactive store — zero-dep persistSource core; codecs, backends, cross-tab transport, source + framework hydration adapters ship as opt-in recipes

82 lines (63 loc) • 3.04 kB
--- name: persist description: Core @stainless-code/persist concepts — persistSource, PersistOptions, PersistApi, hydration gate, throttle, cross-tab, migrate. Load before framework hydration skills or when wiring a custom PersistableSource. license: MIT metadata: type: core library: "@stainless-code/persist" library_version: "0.4.1" sources: - stainless-code/persist:src/core/persist-core.ts - stainless-code/persist:docs/architecture.md - stainless-code/persist:README.md --- # Persist Zero-dep `persistSource(source, options)` owns hydrate → subscribe → write. First-party `./sources/*` adapters map library APIs onto `PersistableSource` (`getState` / `setState` / `subscribe`) and may add opinions (default `merge`, reducer wraps, assign vs `$patch`). ## When to use this skill - Custom store shape (no `persist-*` adapter). - Shared Options / `PersistApi` semantics across adapters. - Before `react-persist` (and other framework hydration skills). Library-specific wiring → the matching `persist-*` composition skill. ## Minimal custom source ```ts import { createJSONStorage, persistSource } from "@stainless-code/persist"; const persist = persistSource( { getState: () => state, setState: (updater) => { state = updater(state); }, subscribe: (listener) => { listeners.add(listener); return { unsubscribe: () => listeners.delete(listener) }; }, }, { name: "app:custom:v1", storage: createJSONStorage(() => localStorage), }, ); ``` ## Contracts agents must not invent - **Hydration write gate** — `setState` before hydrate settles does not clobber storage. Don't double-gate manually. - **`throttleMs`** — trailing-only; first write waits out the window. `destroy()` flushes pending writes. - **`maxAge`** — opt-in; prefs should not silently expire. - **`instanceof Promise`** on reads — not thenable duck-typing. - **`PersistDecodeRethrowError`** — decode errors that must rethrow / skip `clearCorrupt` (e.g. wrong sync/async schema lane). - **`migrate`** — called once with `(state, fromVersion)`; multi-step chains → `createMigrationChain`. - **Teardown** — `persist.destroy()` for non-singleton lifetimes. ## `PersistApi` (keep the reference) `rehydrate()`, `hasHydrated()`, `onHydrate`, `onFinishHydration`, `setOptions`, `clearStorage`, `getOptions`, `destroy`. ## Hydration signal for UI ```ts import { toHydrationSignal } from "@stainless-code/persist"; export const hydration = toHydrationSignal(persist); // frameworks/react → useHydrated(hydration) — see react-persist ``` ## Backend × codec (short) | State | Backend | Helper / codec | | ------------------ | -------------- | ------------------- | | JSON-able | `localStorage` | `createJSONStorage` | | `Set`/`Map`/`Date` | `localStorage` | seroval codec | | Structured clone | IndexedDB | `identityCodec` | See also: `persist-*` for sources / codecs / backends / transport; `*-persist` for UI frameworks.