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

alpine-persist

63 linesSource

Gate Alpine.js UI on Persist hydration (Alpine.plugin + useHydrated / $hydrated). Use when wiring HydrationSignal into Alpine data components.

Alpine hydration gate

@stainless-code/persist/frameworks/alpine is plugin-first: Alpine.plugin(persist) registers $hydrated and enables reactive useHydrated. Call destroy() from Alpine.data teardown.

Install

shell
bun add @stainless-code/persist alpinejs

Peer: alpinejs >=3.0.0 (types via @types/alpinejs — package ships none).

Minimal wiring

ts
import Alpine from "alpinejs";
import persist, {
  useHydrated,
} from "@stainless-code/persist/frameworks/alpine";

Alpine.plugin(persist);

Alpine.data("prefs", () => {
  const hydration = useHydrated(prefsHydration);
  return {
    get hydrated() {
      return hydration.hydrated;
    },
    destroy() {
      hydration.destroy();
    },
  };
});

Template: x-show="$hydrated(prefsHydration).hydrated" — $hydrated(signal) returns a bag ({ hydrated, destroy }), not a boolean (a bare bag is always truthy in x-show). Magic caches per element.

Common mistakes

  • Skipping Alpine.plugin(persist). useHydrated falls back to a plain object + one-time non-prod warn.
  • x-show="$hydrated(...)" without .hydrated.
  • Not forwarding destroy() from Alpine.data.

API surface

  • default/persist(Alpine) — plugin; registers $hydrated(signal) → HydratedBag
  • useHydrated(signal) → { hydrated; destroy() } (null signal → { hydrated: true })