@stainless-code/persist
Version:
Hydration-aware persistence middleware for reactive stores (storage × codec seams, TanStack Store adapters, React hydration hook)
55 lines • 2.77 kB
text/typescript
//#region src/hydration.d.ts
/**
* Reactive hydration signal — the framework-agnostic subscribe target an
* adapter mounts into its external-store mechanism (React
* `useSyncExternalStore` via `useHydrated`, Svelte `createSubscriber` /
* readable stores, Solid `from`, Vue `shallowRef` + watch). Non-generic:
* state reads stay on the store (`useSelector`); the signal only exposes
* hydration.
*
* ADAPTER CONTRACT — what an adapter may rely on and must do:
* - `subscribeHydrated(listener)` supports multiple concurrent subscribers
* and returns an idempotent unsubscribe. Each call is an independent
* subscription, even for the same listener function.
* - Listeners are NOT invoked on subscribe (no initial notification) and
* carry NO payload — this is a pull model: (re)read `isHydrated()` after
* attaching, and on every notification.
* - Transitions that happen while nothing is subscribed are not replayed;
* the snapshot re-read on attach recovers the current state.
* - SSR: render `hydrated = true` on the server (no storage server-side,
* nothing to gate) — this policy lives in each adapter, not in the signal;
* see `useHydrated`'s server snapshot for the reference implementation.
* - A `null` signal means "no persistence" and must render hydrated: `true`.
*/
interface HydrationSignal {
subscribeHydrated: (listener: () => void) => () => void;
isHydrated: () => boolean;
}
/**
* Minimal structural source `toHydrationSignal` reads from — `PersistApi`
* satisfies it, and so does any object exposing a hydration lifecycle
* (the signal layer has no dependency on persist types).
*/
interface HydrationSource {
hasHydrated: () => boolean;
onHydrate: (listener: () => void) => () => void;
onFinishHydration: (listener: () => void) => () => void;
}
/**
* Derive a `HydrationSignal` from a `HydrationSource` (e.g. a `PersistApi`),
* bridging `onHydrate` / `onFinishHydration` into one external-store
* subscribe target that notifies on either transition. Null-tolerant:
* `null` / `undefined` in → `null` out, so a conditional-persist consumer
* drops its hydration ternary (`persist ? toHydrationSignal(persist) : null`
* → `toHydrationSignal(persist)`).
*/
declare function toHydrationSignal(source: HydrationSource | null | undefined): HydrationSignal | null;
/**
* Always-hydrated `HydrationSignal` for the no-persist path — a uniform
* handle instead of a `null` branch at the call site. Solves null-tolerance
* once in the core so framework adapters stay dumb (subscribe + snapshot,
* nothing else).
*/
declare function alwaysHydratedSignal(): HydrationSignal;
//#endregion
export { toHydrationSignal as i, HydrationSource as n, alwaysHydratedSignal as r, HydrationSignal as t };