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

84 lines (83 loc) • 2.49 kB
//#region src/adapters/frameworks/alpine.ts let alpineRuntime; let warnedMissingRuntime = false; /** Reactive holder when the plugin has run; plain object fallback for unit tests. */ function alpineReactive(seed) { if (alpineRuntime) return alpineRuntime.reactive(seed); if (process.env.NODE_ENV !== "production" && !warnedMissingRuntime) { warnedMissingRuntime = true; console.warn("[@stainless-code/persist/frameworks/alpine] useHydrated called before Alpine.plugin(persist); updates won't be reactive."); } return seed; } /** * Mount a `HydrationSignal` into Alpine reactivity — reactive `{ hydrated }` * for `x-show` / `x-text`. Call after `Alpine.plugin(persist)`. Null signal → * `{ hydrated: true }`. Tear down with `bag.destroy()` from `Alpine.data`. * Template `$hydrated(signal)` caches per element and cleans up on remove. * * @example * ```ts * 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(); }, * }; * }); * ``` */ function useHydrated(signal) { let unsubscribe; const bag = alpineReactive({ hydrated: signal?.isHydrated() ?? true, destroy() { unsubscribe?.(); unsubscribe = void 0; } }); if (signal) unsubscribe = signal.subscribeHydrated(() => { bag.hydrated = signal.isHydrated(); }); return bag; } /** Per-element cache so `$hydrated(signal)` re-evals don't stack subscriptions. */ const magicBags = /* @__PURE__ */ new WeakMap(); /** * Alpine plugin — stores the runtime for `Alpine.reactive` and registers * `$hydrated` (`(signal) => useHydrated(signal)`). * * @example * ```ts * import persist from "@stainless-code/persist/frameworks/alpine"; * Alpine.plugin(persist); * ``` */ function persist(Alpine) { alpineRuntime = Alpine; warnedMissingRuntime = false; Alpine.magic("hydrated", (el, { cleanup }) => { return (signal) => { let map = magicBags.get(el); if (!map) { map = /* @__PURE__ */ new Map(); magicBags.set(el, map); cleanup(() => { for (const bag of map.values()) bag.destroy(); magicBags.delete(el); }); } const key = signal ?? null; let bag = map.get(key); if (!bag) { bag = useHydrated(signal); map.set(key, bag); } return bag; }; }); } //#endregion export { persist as default, persist, useHydrated };