nusm axolotlnusm

nusm vs. @tanstack/store

Understand what nusm adds, what remains unchanged, and when the smaller TanStack Store surface is enough.

nusm is not a competing reactive engine. It builds directly on @tanstack/store, returns a TanStack Store, and adds a persistence lifecycle around it.

At a glance

Capability@tanstack/storenusm
Observable state containerYesYes, inherited
state, setState, and subscribeYesYes, inherited
Batched updatesYesYes, re-exported batch
Built-in persistence lifecycleNoYes
Entire-store persistenceBring your ownBuilt in
Selective slice persistenceBring your ownBuilt in
Async hydration readinessBring your ownready, isReady, hydration
Validation and migration hookBring your ownhydrate.validate
Merge policyBring your ownDeep merge or custom hydrate.merge
Debounced writesBring your ownAdapter pacer policy
Browser storage adaptersNolocalStorage, sessionStorage, IndexedDB
Custom storage contractApplication-specificTyped NusmAdapter
Cross-tab adapter eventsBring your ownSupported through subscribe
React selector hookSeparate integrationnusm/react
Store-specific inspectorGeneral TanStack toolingOptional nusm/devtools plugin

What stays the same

The everyday update model remains TanStack Store:

store.state
store.setState((state) => ({ ...state, count: state.count + 1 }))
store.subscribe(listener)

nusm also re-exports Store and batch from the root package:

import { batch, Store } from "nusm"

This makes nusm a low-friction choice when some stores need persistence and others do not.

An in-memory nusm store needs no persistence configuration:

import { createNusmStore } from "nusm"

const transientUi = createNusmStore({ activeDialog: null })

Using nusm for transient state does not write that state to persistence storage. It keeps one store type across the application while leaving persistence as a per-store choice.

What nusm adds

createNusmStore returns a NusmStore<TState>, which extends Store<TState> with:

interface NusmStore<TState> extends Store<TState> {
  readonly devtoolsInstanceId: string
  readonly hydration: ReadonlyHydrationStatus
  readonly isReady: boolean
  ready: Promise<void>
}

The adapter and persistence options define how state crosses the boundary between volatile memory and durable storage.

Choose @tanstack/store directly when

  • You want to use TanStack Store without the nusm lifecycle fields or dependencies.
  • You do not need a hydration boundary.
  • Storage is already owned by another application layer.
  • You want the smallest possible API and dependency surface.

Choose nusm when

  • You want one store contract for both in-memory and persisted state.
  • A store must survive a reload or browser session.
  • You need selected fields persisted without splitting one logical store.
  • Stored data needs validation, transformation, or custom merging.
  • Rapid updates should be paced before storage writes.
  • Cross-tab or external storage changes should flow back into live state.
  • Developers need to compare memory and adapter state in Devtools.

Migration from TanStack Store

Start by replacing store construction. Existing update and subscription code can usually remain unchanged.

// Before
import { Store } from "@tanstack/store"
const settings = new Store({ theme: "system" })

// After
import { createLocalStorageAdapter, createNusmStore } from "nusm"
const settings = createNusmStore(
  { theme: "system" },
  {
    adapter: createLocalStorageAdapter(),
    persist: { strategy: "entire" },
    storeId: "settings",
  },
)

await settings.ready

Add the readiness boundary before code that assumes persisted state is present. Then decide whether entire-store or slice persistence best matches the data's ownership.

nusm targets the current TanStack Store API directly. It does not provide legacy Derived or Effect compatibility exports.

On this page