Wrapping stores
Persist zustand, jotai, valtio, mobx, pinia, redux, or any custom PersistableSource with thin source adapters.
Same getState/setState/subscribe shape → same adapter name → same merge semantics, regardless of library. Naming is shape-based (persistStore / persistAtom / persistProxy / persistObservable); the subpath carries the library. Importing two same-shape adapters into one module? Alias one: import { persistStore as persistZustand } from "@stainless-code/persist/sources/zustand". Escape hatch: pass a custom PersistableSource to persistSource directly.
zustand
import { create } from "zustand";
import { createJSONStorage } from "@stainless-code/persist";
import { persistStore } from "@stainless-code/persist/sources/zustand";
const usePrefs = create(() => ({ theme: "light" as const }));
const persist = persistStore(usePrefs, {
name: "app:prefs:v1",
storage: createJSONStorage(() => localStorage),
});
Or pass a custom PersistableSource to persistSource directly — the adapter is a thin wrapper over getState/setState/subscribe.
jotai
import { atom, createStore } from "jotai";
import { createJSONStorage } from "@stainless-code/persist";
import { persistAtom } from "@stainless-code/persist/sources/jotai";
const store = createStore();
const themeAtom = atom<"light" | "dark">("light");
const persist = persistAtom(store, themeAtom, {
name: "app:theme:v1",
storage: createJSONStorage(() => localStorage),
});
Or pass a custom PersistableSource to persistSource directly — the adapter is a thin wrapper over getState/setState/subscribe.
valtio
import { proxy } from "valtio";
import { createJSONStorage } from "@stainless-code/persist";
import { persistProxy } from "@stainless-code/persist/sources/valtio";
const prefs = proxy({ theme: "light" as const });
const persist = persistProxy(prefs, {
name: "app:prefs:v1",
storage: createJSONStorage(() => localStorage),
});
Or pass a custom PersistableSource to persistSource directly — the adapter is a thin wrapper over getState/setState/subscribe.
mobx
import { observable } from "mobx";
import { createJSONStorage } from "@stainless-code/persist";
import { persistObservable } from "@stainless-code/persist/sources/mobx";
const prefs = observable.object({ theme: "light" as const });
const persist = persistObservable(prefs, {
name: "app:prefs:v1",
storage: createJSONStorage(() => localStorage),
});
Or pass a custom PersistableSource to persistSource directly — the adapter is a thin wrapper over getState/setState/subscribe.
pinia
import { defineStore } from "pinia";
import { createJSONStorage } from "@stainless-code/persist";
import { persistStore } from "@stainless-code/persist/sources/pinia";
const usePrefs = defineStore("prefs", {
state: () => ({ theme: "light" as const }),
});
const persist = persistStore(usePrefs(), {
name: "app:prefs:v1",
storage: createJSONStorage(() => localStorage),
});
redux
Wrap the root reducer with persistableReducer (not each slice) so hydrate can replace state. Alias if you also import redux-persist’s persistStore.
import { createStore } from "redux";
import { createJSONStorage } from "@stainless-code/persist";
import {
persistStore,
persistableReducer,
} from "@stainless-code/persist/sources/redux";
const store = createStore(persistableReducer(rootReducer));
const persist = persistStore(store, {
name: "app:root:v1",
storage: createJSONStorage(() => localStorage),
});
Any other store
import { createJSONStorage, persistSource } from "@stainless-code/persist";
const persist = persistSource(
{
getState: () => myStore.getState(),
setState: (updater) => myStore.setState(updater),
subscribe: (listener) => ({
unsubscribe: myStore.subscribe(() => listener()),
}),
},
{
name: "app:custom:v1",
storage: createJSONStorage(() => localStorage),
},
);