Changelog
July 23, 2026
Patch Changes
- #39
4387948Thanks @SutuSebastian! - Ship Intent skills for the full public surface — corepersist,persist-*(sources/codecs/backends/transport), and<framework>-persist. Re-runnpx @tanstack/intent@latest installif agent config still points atskills/tanstack-store.
July 22, 2026
Minor Changes
-
#36
4cc9ec6Thanks @SutuSebastian! - Add./codecs/standard-schema— sync~standardcodec,PersistStoragewraps (withStandardSchema/withStandardSchemaAsync), and JSON factories. Types vendored; no runtime peer.createStoragerethrowsPersistDecodeRethrowErrorfrom decode (wrong-lane / programmer errors — not clearCorrupt).Remove
./codecs/zod. Migrate tocreateStandardSchemaStorage(getStorage, schema)(Zod ≥3.24 / v4 via~standard). Encode writes schemavalue(defaults/transforms). Yup / async~standard.validate→ async lane (async hydrate — gate UI).
July 20, 2026
Minor Changes
-
#34
470ef1dThanks @SutuSebastian! - Add Alpine framework adapter (./frameworks/alpine) —useHydrated+$hydratedplugin overHydrationSignal. -
#34
470ef1dThanks @SutuSebastian! - Add LitHydrationControllerframework adapter (./frameworks/lit). -
#31
f7d4abcThanks @SutuSebastian! - Add first-party Pinia source adapter (./sources/pinia) with shape-namedpersistStoreoverpersistSource. -
#31
f7d4abcThanks @SutuSebastian! - Add first-party Redux source adapter (./sources/redux) withpersistStoreand companionpersistableReducer(classic + RTK).
July 19, 2026
Patch Changes
- #20
cb6ca1dThanks @SutuSebastian! - Ship the public docs site at https://stainless-code.com/persist (apps/docs); README is now the npm landing digest that links there.
July 6, 2026
Minor Changes
-
#7
934af37Thanks @SutuSebastian! - Add./frameworks/angularand./frameworks/preacthydration adapters over theHydrationSignalseam../frameworks/angular(peer@angular/core >=17.0.0):useHydrated(signal)→ readonlySignal<boolean>. Call inside a component’s injection context (effect()requires it)../frameworks/preact(peerpreact >=10.19.0):useHydrated(signal)→{ hydrated }viauseSyncExternalStore(preact/compat).@ts-expect-erroron the 3-arg call (Preact types omitgetServerSnapshot; runtime ignores it).
-
#7
934af37Thanks @SutuSebastian! - Add./transport/crosstabsubpath —createBroadcastCrossTab, a zero-depBroadcastChannelbridge for cross-tab sync over backends that fire nostorageevents (IndexedDB). Returns{ crossTabEventTarget, wrap, close }: pass the target ascrossTabEventTargetandwrap(storage)asstorageso writes/removes broadcast to other tabs. PostsstorageArea: nullon every event so key-only matching applies in every tab (each tab owns its own backend instance — reference equality onrawwould fail across tabs). GuardsBroadcastChannelavailability (SSR, Node <18) and posts after the write settles so receivers rehydrate into committed state. -
#7
934af37Thanks @SutuSebastian! - Add two zero-dep storage wrappers over theStateStorageseam — both async web-global adapters (no peer dep), composing withcreateStorage(backend, codec):./backends/encrypted—createEncryptedStorage(getStorage, { key }): AES-GCM via WebCrypto (crypto.subtle). Each stored value isbase64(iv).base64(ciphertext); the AES-GCM auth tag means a wrong key or tampered ciphertext throws on decrypt → the backend’s asyncgetItemrejects → persist-core reports it viaonErrorphase"hydrate"(NOT the codec’sclearCorruptOnFailurepath, which only fires when the codec throws parsing a raw value). Returnsundefinedwhencrypto.subtleis unavailable;createStoragethen returnsundefined(the consumer can fall back, orpersistSourceno-ops if no storage resolves)../backends/compressed—createCompressedStorage(getStorage, { format? }): nativeCompressionStream/DecompressionStream(gzip|deflate|deflate-raw, defaultgzip); output is base64 so it stays string-wire. Returnsundefinedwhen the stream APIs are unavailable. Stacks withcreateEncryptedStorage(compress-then-encrypt is the standard order).
Design note: encryption + compression are backend wrappers, not sync
StorageCodecs, becausecrypto.subtleand the stream APIs are async and theStorageCodecseam is sync. The codec serializes the envelope (sync); the wrapper encrypts/compresses the serialized string (async).Also adds a README comparison table vs zustand-persist / redux-persist / @tanstack/query-persist-client / pinia-persist, and a migration guide with option-mapping tables + port snippets for each incumbent.
-
#7
934af37Thanks @SutuSebastian! - AddcreateMigrationChain— a zero-dep core helper that builds amigratecallback from a per-version step chain. Plug intoPersistOptions.migrate.steps[N]takes vN → v(N+1); the chain walks from the stored version toversion, awaiting each.onNewer(default"throw"— a downgrade is a bug) /onOlder(default"discard"— dropped support for that version).- Eager construction validation: a gap in the covered range, an out-of-range key, or a non-integer version throws now.
- Beyond TanStack
buster(discards on mismatch) — transforms instead.
-
#7
934af37Thanks @SutuSebastian! - Add./backends/node-fs—nodeFsStateStorage({ dir }), an asyncStateStorage<string>over Nodefs.promises(one file per key underdir). No peer dep (node:fsis a Node built-in). Keys are sanitized to filename-safe segments with a short hash suffix (app:prefs:v1→app_prefs_v1.<hash>) so distinct keys that sanitize to the same segment don’t collide on one file;.././empty keys are refused; missing files map tonull(no throw); the dir is created lazily on first write. Compose withcreateStorage(() => nodeFsStateStorage({ dir }), codec). Unblocks server / SSR / CLI persistence.Also adds export/pack validation (
check:pack:@arethetypeswrong/cli+publint+knip) and README storage + codec decision matrices. -
#7
934af37Thanks @SutuSebastian! - Add three React Native storage subpaths over theStateStorageseam, mirroring the./backends/idbtemplate (own subpath, optional peer, no cross-entry value imports):./backends/async-storage(peer@react-native-async-storage/async-storage >=1.0.0) —asyncStorageStateStorage/createAsyncStorage. Fully async, string-wire;useHydratedgating mandatory. Accepts a customAsyncStorageinstance (e.g.getLegacyStorage()) to namespace../backends/mmkv(peerreact-native-mmkv >=4.0.0) —mmkvStateStorage/createMmkvStorage({ id, path?, encryptionKey? }). Synchronous (no hydration gate needed); uses the v4createMMKVfactory +getString/set/removeAPI. PairencryptionKeyfor secrets-at-rest../backends/secure-store(peerexpo-secure-store >=12.0.0) —secureStoreStateStorage/createSecureStoreStorage. OS keychain/keystore, async, ~2KB value limit per key — for small secrets (auth tokens), not large state; pairpartializeto persist a tiny slice.
All three compose via
createJSONStorage(jsonCodec default); swap codecs withcreateStorage(backend, codec). -
#7
934af37Thanks @SutuSebastian! - AddcreateSessionStorage— a zero-dep core DX factory oversessionStorage(per-tab;crossTabis meaningless). ReturnsundefinedwhensessionStorageis unavailable (SSR / non-DOM) or defined-but-broken (Node 22+ half-built global). No new subpath — exports from the core.entry. -
#7
934af37Thanks @SutuSebastian! - Add./frameworks/solidand./frameworks/vuehydration subpaths —useHydrated(signal)over theHydrationSignalseam, mirroring the React adapter (./frameworks/react)../frameworks/solid(peersolid-js >=1.6.0): returns a SolidAccessor<boolean>viafrom; the subscription is owned by the reactive scope and cleaned up on scope dispose. Uses thefrom(producer, initialValue)overload so the accessor isAccessor<boolean>(notboolean | undefined); readsisHydrated()for the initial value (pull-model signal — no initial notification)../frameworks/vue(peervue >=3.3.0): returns a VueRef<boolean>viashallowRef; subscription cleaned up viaonScopeDispose— call insidesetup()or aneffectScope().
Both render
trueon the server (the no-opPersistApiis always-hydrated, so the signal istrueserver-side) — matching theHydrationSignaladapter contract. Each ships as its own subpath with the peer as optional, no cross-entry value imports. -
#7
934af37Thanks @SutuSebastian! - Add four source adapters over thePersistableSourceseam — shape-named (not library-named): same persistable shape → same name → same merge semantics, regardless of library; the subpath carries the library. Each is a thinpersistSourcewrapper mapping the library’s store API:./sources/zustand(peerzustand >=4.0.0):persistStore(store, opts)— zustand’sgetState/setState/subscribemap directly. Same name + shallow-spread merge as./sources/tanstack-store’spersistStore(same shape); alias one if importing both../sources/jotai(peerjotai >=2.0.0):persistAtom(store, atom, opts)— wraps a writable atom + jotaiStore; replace-merge default (likepersistAtomfrom./sources/tanstack-store) so primitive atoms don’t hydrate to{}../sources/valtio(peervaltio >=1.0.0):persistProxy(proxyObject, opts)—snapshotfor reads,Object.assignfor writes,subscribefor changes../sources/mobx(peermobx >=6.0.0):persistObservable(observable, opts)—toJSfor reads,Object.assignfor writes,observefor changes.
Each is its own subpath with the peer optional, no cross-entry value imports. README “Wrapping your store” recipe section shows both the shipped adapter + the underlying
persistSourcemapping for customization. -
#7
934af37Thanks @SutuSebastian! - Reorganize the public subpath namespace. Adapter subpaths are now category-prefixed. Breaking (early package; consumers must update imports) — the four subpaths that existed onmainare renamed:./seroval→./codecs/seroval./idb→./backends/idb./tanstack-store→./sources/tanstack-store./react→./frameworks/react
The rest of the surface (
./codecs/zod,./backends/{async-storage,mmkv,secure-store,encrypted,compressed,node-fs},./transport/crosstab,./sources/{zustand,jotai,valtio,mobx},./frameworks/{solid,vue,svelte,svelte-store,angular,preact}) is NEW — added by sibling changesets, not renames.The
.(core) entry is unchanged. -
#7
934af37Thanks @SutuSebastian! - Add Svelte hydration adapters over theHydrationSignalseam, covering both pre- and post-runes Svelte. Two subpaths becausesvelte/reactivity(runes) is Svelte 5+ and would break a Svelte 4 import — each subpath owns its dep range:./frameworks/svelte(requires Svelte ≥5.7 at runtime forcreateSubscriber; the package peer stays>=3.0.0shared with./frameworks/svelte-store) —hydratedRune(signal)viasvelte/reactivitycreateSubscriber. Returns{ readonly current: boolean }; readcurrentinside a reactive context ($derived/$effect/component/{#if}). Subscription owned by the reactive context, cleaned up on context dispose. Post-runes../frameworks/svelte-store(peersvelte >=3.0.0) —hydratedStore(signal)viasvelte/storereadable. ReturnsReadable<boolean>; auto-subscribe with$hydratedStore. Works on Svelte 4 (pre-runes) AND Svelte 5 (for users who prefer the store API). Subscription tied to the store’s subscriber lifecycle.
Both render
trueon the server (no-opPersistApiis always-hydrated) — matching theHydrationSignaladapter contract. Each is its own subpath withsvelteoptional, no cross-entry value imports. -
#7
934af37Thanks @SutuSebastian! - Add./codecs/zodsubpath —zodCodec/createZodStorage, a schema-gated codec over theStorageCodecseam.encodevalidatesstateagainst aZodTypebefore serializing the envelope (invalid state never persists; the throw surfaces viaonErrorphase"write").decodeparses + validates the storedstate; a validation failure throws into persist-core’s corrupt-payload path → returnsnull, or withclearCorruptOnFailureremoves the key. Validatesstateonly —version/timestamp/busterstay the envelope’s concern.zodis an optional peer (>=3.20.0, stable across v3/v4 viaZodType+.parse).
July 4, 2026
Patch Changes
- #4
efc5614Thanks @SutuSebastian! -createStoragenow shape-checks the resolved backend and treats one missinggetItem/setItem/removeItemas unavailable. Fixes the Node 22+ SSR crash wherelocalStorageexists as an object (so the availability lookup doesn’t throw) but its methods areundefinedwithout a valid--localstorage-filepath — previously this passed availability and threwstorage.getItem is not a functioninsidehydrate; nowpersistSource/persistStore/persistAtomcollapse to the no-opPersistApi.
July 3, 2026
Minor Changes
- #2
dbf0428Thanks @SutuSebastian! - Ship consumer Agent Skills via TanStack Intent. Addsskills/tanstack-store/SKILL.md(packaged in the npm tarball), thetanstack-intentkeyword for registry discovery,intent:validate/intent:stalescripts,intent validategated inprepublishOnly, and acheck-skills.ymlCI workflow for skill validation + post-release staleness review. No runtime API change.