nusm axolotlnusm

Overview

A persistence-ready state manager built on the small, reactive core of TanStack Store.

nusm — Non Uniform State Manager, pronounced “noose em” — is a typed wrapper around @tanstack/store for application state, whether that state stays in memory or crosses a storage boundary.

It keeps the store model intentionally familiar: read store.state, call store.setState, and subscribe to changes. When you opt into an adapter, nusm adds a deliberate lifecycle around that core: hydrate, validate, merge, persist, synchronize, and inspect.

Why nusm?

Most state starts simple and only later needs persistence. Adding it at that point often spreads storage reads, serialization, hydration flags, migrations, and write scheduling throughout the application. nusm keeps those concerns behind one store boundary.

CapabilityWhat it gives you
TanStack Store semanticsA small observable store and familiar setState updates
Adapter-based persistenceBrowser adapters out of the box and a compact custom contract
Entire or slice persistenceStore everything, or write only the fields that should survive
Controlled hydrationValidate, transform, discard, or merge persisted values
Scheduled writesDebounce persistence with @tanstack/pacer or write immediately
External synchronizationReact to adapter events such as browser storage changes
React integrationA selector-based useStore hook with shallow or deep equality
Optional DevtoolsInspect and edit memory, persisted state, hydration, and history

The mental model

initial state

    ├── no adapter ───────────────► ready immediately

    └── adapter configured

          ├── read persisted units
          ├── validate / discard / merge
          ├── seed missing values
          └── resolve store.ready ─► updates persist through the adapter

A nusm store is still a TanStack Store. Persistence is additive. Without an adapter, ready resolves immediately and hydration.overall is not_configured.

One state model, different lifetimes

adapter and persist are optional. You can keep all application state in nusm while choosing persistence independently for each store:

import {
  createLocalStorageAdapter,
  createNusmStore,
  createSessionStorageAdapter,
} from "nusm"

// Resets on reload.
export const workspace = createNusmStore({ selectedId: null })

// Survives reloads in this tab, then expires with the tab session.
export const session = createNusmStore(
  { evidence: [], startedAt: Date.now() },
  {
    adapter: createSessionStorageAdapter(),
    persist: { strategy: "entire" },
    storeId: "session",
  },
)

// Returns in later browser sessions.
export const preferences = createNusmStore(
  { theme: "system" },
  {
    adapter: createLocalStorageAdapter(),
    persist: { strategy: "entire" },
    storeId: "preferences",
  },
)

Choose storage by the state lifetime. State that should not outlive a tab can still belong in a nusm store. Use sessionStorage when it should survive a refresh within that tab. Use slice persistence when only selected fields in one store should survive.

A small example

import {
  createLocalStorageAdapter,
  createNusmStore,
} from "nusm"

type Preferences = {
  accent: "cyan" | "violet"
  compact: boolean
}

export const preferences = createNusmStore<Preferences>(
  { accent: "cyan", compact: false },
  {
    adapter: createLocalStorageAdapter(),
    devtools: { name: "Preferences" },
    persist: { strategy: "entire" },
    storeId: "preferences",
  },
)

await preferences.ready
preferences.setState((state) => ({ ...state, compact: true }))

The adapter key is nusm:preferences:entire. Updates are serialized with SuperJSON and, for local storage, written on a trailing 50 ms schedule.

Where to go next

On this page