@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
JavaScript
//#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 };