Migrating to 3.0

3.0 removes APIs that were deprecated or never meant to be public, types persistence end to end, and fixes how development builds are detected. It also adds official Vue, Svelte and Lit bindings, and an Astro integration. Stores themselves are unchanged: a defineStore definition from 2.x works as it is.

All packages now share one version number, so @quantajs/core, @quantajs/react, @quantajs/devtools, @quantajs/vue, @quantajs/svelte, @quantajs/lit and @quantajs/astro are released together as 3.0.0. Update them together: @quantajs/react and @quantajs/devtools now require the other QuantaJS packages from the same major.

npm install @quantajs/core@3 @quantajs/react@3

Coming from 2.0? Apply Migrating to 2.1 first.

At a glance

2.x3.0
useStore('cart'), hasStore('cart')useCartStore(), or container.get('cart') / container.has('cart')removed
store.notifyAll()Subscribers are called on every changeremoved
nextTick()await Promise.resolve()removed
reactiveEffecteffectremoved
CommonMigrations, MigrationManager, createMigrationManagerPlain functions in persist.migrationsremoved
createPersistenceManagerThe persist store optionremoved
debounce, Logger, createLoggerYour own utilities; logger and LogLevel remainremoved
pauseTracking, resumeTrackinguntrack(fn)removed
sanitizePayload, safeJsonParse, safeJsonReviverApplied automatically to persisted and hydrated dataremoved
RawActions, GetterDefinitions, ActionDefinition, InferActions, StoreInstance, StoreOptionsSee Deprecated typesremoved types
Persistence options typed with anyTyped from the store's statetypes
validator saw the transformed output on saveSees the slice before transform.out, as on loadbehaviour
Stored keys outside include still loadedOnly persisted keys loadbehaviour
Dev warnings could print in production browser buildsOff in production buildsbehaviour

Removed APIs

Store lookup by name

useStore(name) and hasStore(name) looked stores up by name and returned them untyped. Call the definition, which is typed and resolves against the right container:

- import { useStore } from '@quantajs/core';
- const cart = useStore('cart');
+ import { useCartStore } from './stores/cart';
+ const cart = useCartStore();

For the rare lookup by name, ask the container: container.get('cart') and container.has('cart'). See Containers.

store.notifyAll()

Subscribers are called on every change, so there is nothing to flush. Remove the calls.

nextTick()

Effects run synchronously when state changes, so nextTick only ever waited for a resolved promise. Use that directly where you still need to yield:

- await nextTick();
+ await Promise.resolve();

- nextTick(() => measure());
+ Promise.resolve().then(() => measure());

@quantajs/react no longer re-exports it either.

reactiveEffect

It was the same function as effect. Rename the import.

Migration helpers

CommonMigrations, MigrationManager and createMigrationManager are gone. A migration is a plain function from the stored data to the next version's shape:

  persist: {
    adapter: new LocalStorageAdapter('app'),
    version: 3,
    migrations: {
-     2: CommonMigrations.addProperty('flags', {}),
-     3: CommonMigrations.renameProperty('user', 'currentUser'),
+     2: (data) => ({ ...data, flags: {} }),
+     3: ({ user, ...rest }) => ({ ...rest, currentUser: user }),
    },
  },

createPersistenceManager

Stores create their persistence manager from the persist option, and expose it as store.$persist. There was no supported way to use it on its own.

Utilities

debounce, Logger and createLogger were internal utilities. logger and LogLevel remain, for controlling what QuantaJS itself prints; see Logger. Use your own utilities for application code.

Tracking and sanitising helpers

pauseTracking() and resumeTracking() were the internals behind untrack. Use untrack, which also restores tracking if the function throws:

- const previous = pauseTracking();
- const value = state.count;
- resumeTracking(previous);
+ const value = untrack(() => state.count);

sanitizePayload(), safeJsonParse() and safeJsonReviver() are no longer exported. Persisted data and container snapshots are still sanitised automatically, and the default deserialize still drops __proto__, constructor and prototype keys.

Deprecated types

RemovedUse
RawActionsActionsTree
GetterDefinitionsGettersTree
ActionDefinitionActionsTree
InferActionsBoundActions
StoreInstanceStore
StoreOptionsStoreDefinitionOptions

Persistence

Persistence is typed from the store's state, without any. Most stores need no changes; TypeScript points out the ones that do.

  • include and exclude accept only state keys.
  • serialize receives the envelope it actually encodes, { data, version, timestamp, storeName }. It was typed as receiving the state. deserialize returns unknown, which is checked before use.
  • migrations, transform.in and validator receive StoredState, a Record<string, unknown>: loaded data is unverified until your validator has checked it. transform.out receives the state slice.
  • Custom adapters exchange strings: read() returns string | null and write() takes a string. IndexedDBAdapter.read() returns null for a record that is not a string.
  • PersistedData: storeName is always set, and the unused checksum field is gone.

Two behaviours changed:

  • The validator runs on the same shape both ways. When saving, it now checks the slice before transform.out, the same state-shaped data it checks after transform.in when loading. If your validator was written against the transformed output, update it.
  • Only persisted keys load. Removing a key from include, or adding it to exclude, now also stops its previously stored value from loading into state.

See Persistence for the full order of steps on save and load.

Development builds

QuantaJS now detects development from process.env.NODE_ENV, which your bundler replaces at build time.

  • Production browser builds are quiet. Before, the check could not be resolved by bundlers and fell back to development, so apps printed QuantaJS development warnings in production.
  • DevTools appear in development. In Vite apps, mountDevTools() and <QuantaDevTools /> now show the panel in development without visible.

Nothing to change in your code.

React

  • <QuantaProvider> without a container, and useLocalStore, now work under StrictMode. StrictMode's simulated unmount used to dispose the container they went on using.
  • useLocalStore now re-renders the component when its store changes, like useQuanta. If you worked around this with a manual subscription, remove it.
  • shallow now lives in @quantajs/core. @quantajs/react still re-exports it, so imports keep working.

New in 3.0

  • Vue and Svelte bindings: @quantajs/vue and @quantajs/svelte, with the same useQuanta, useQuantaValue, useQuantaActions and useLocalStore as React.
  • Lit bindings: @quantajs/lit, the same four as reactive controllers for web components.
  • Astro integration: @quantajs/astro gives each request its own container, carries server state to the islands, and lets React, Vue and Svelte islands share one store.
  • setDefaultContainerResolver, for any server that renders components without passing a container: resolve the default container per request, for example from AsyncLocalStorage. See Containers.
  • CookieAdapter, to persist small state, such as a theme, in a cookie the server also receives. See Persistence.
  • watch takes equals, to compare a source's values with shallow or your own function instead of Object.is. See watch.

Spot something that needs improving?

Edit page