Skip to content

@stainless-code/persist

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

persist

82 linesSource

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.

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)

StateBackendHelper / codec
JSON-ablelocalStoragecreateJSONStorage
Set/Map/DatelocalStorageseroval codec
Structured cloneIndexedDBidentityCodec

See also: persist-* for sources / codecs / backends / transport; *-persist for UI frameworks.