restate / v1.0.0

Reliability by design

What restate guarantees when storage is slow, fails or is missing, and how to read the errors it reports.

On this pageThe guaranteesOrdered savesTry itWhen loading failsSave and closeNo storage availableValidate and migrate saved dataBring your own storageLimits and timeoutsError codes

The guarantees

These behaviors are part of the 1.0 contract and are covered by the package’s tests.

SituationWhat restate does
Several saves in a rowRuns them in order per store; a save waiting in the queue writes the newest value.
A record failed to loadautoSave and bulk saves skip it with LOAD_FAILED; only an explicit save() replaces it.
Component unmounts during a saveThe requested save finishes; later writes to the destroyed store are ignored.
Storage is unavailableThe store works in memory, status().available is false, saves reject.
An adapter hangsThe operation times out after operationTimeoutMs and later work waits until the adapter settles.
Too much pending workNew operations reject with QUEUE_FULL instead of growing without limit.

Ordered saves

Operations of one store never overlap. A save issued while another save waits takes the newer value, so saving on every change writes the latest state without filling the queue.

JavaScript / JSX
useDraft.set('A');
const first = useDraft.save(); // writes "A"
useDraft.set('B');
const second = useDraft.save(); // waits in the queue
useDraft.set('C');
const third = useDraft.save(); // joins the waiting save

await Promise.all([first, second, third]); // ['A', 'C', 'C'] — two writes

Try it

Slow storage down, make writes fail or remove storage entirely.

Preparing the example…

When loading fails

restateManager.ready rejects with BULK_OPERATION_FAILED and lists each failed store. Until that record loads, autoSave and saveAll do not replace it and report LOAD_FAILED.

Retry initialize() to keep your edits and save them, call restore() to discard them, or call save() to overwrite the record on purpose.

JavaScript / JSX
try {
  await restateManager.ready;
} catch (error) {
  // BULK_OPERATION_FAILED: one entry per store that could not load.
  for (const { store, error: cause } of error.failures) {
    console.error(store.key, cause.code);
  }
}

Save and close

A save requested before unmount still finishes, so a Save and close handler does not need to wait.

JavaScript / JSX
function Document({ onClose }) {
  const useDraft = useRestateScope((scope) => persistedRestate('', { scope, key: 'draft' }));

  const saveAndClose = () => {
    useDraft.save(); // finishes even after this component unmounts
    onClose();
  };
  // …
}

No storage available

Server rendering and blocked browser storage are not errors at startup. Check available to tell users that changes are kept in memory only.

JavaScript / JSX
const { available } = useDraft.status();

if (!available) {
  // Server rendering or blocked browser storage: the store works in memory
  // and save() rejects with STORAGE_UNAVAILABLE.
  showNotice('Changes are kept until you close this tab.');
}

Validate and migrate saved data

version, migrate and validate protect you from old or corrupted records. validate runs before save and after restore or migration.

JavaScript / JSX
import { persistedRestate } from '@kkwaenggwari/restate';

const useSettings = persistedRestate(
  { theme: 'system' },
  {
    key: 'settings',
    version: 2,
    migrate(value, from, to) {
      if (from === 1 && to === 2 && typeof value?.darkMode === 'boolean') {
        return { theme: value.darkMode ? 'dark' : 'light' };
      }
      throw new Error('Unsupported stored version');
    },
    validate: (value) => ['system', 'light', 'dark'].includes(value?.theme),
  },
);

Bring your own storage

Any object with getItem, setItem and removeItem works. Methods may return values or Promises; isAvailable is optional.

JavaScript / JSX
import { persistedRestate } from '@kkwaenggwari/restate';

const sessionStorageAdapter = {
  isAvailable: () => typeof sessionStorage !== 'undefined',
  getItem: (key) => sessionStorage.getItem(key),
  setItem: (key, value) => sessionStorage.setItem(key, value),
  removeItem: (key) => sessionStorage.removeItem(key),
};

export const useFilters = persistedRestate(
  { query: '' },
  {
    key: 'filters',
    storage: sessionStorageAdapter,
  },
);

Limits and timeouts

Tune how long an operation may run, how many may wait and how large a value may be.

JavaScript / JSX
persistedRestate(value, {
  key: 'draft',
  operationTimeoutMs: 10_000, // default 30000
  maxPendingOperations: 64, // default 256
  maxStorageCharacters: 1_000_000,
});

Error codes

Errors created by restate have name RestateError, a code and, for saved stores, key. Storage adapters and your callbacks may reject with their own errors.

CodeMeaning
BULK_OPERATION_FAILEDA bulk call such as restateManager.ready or saveAll had failures; see error.failures.
INDEXEDDB_ABORTEDAn IndexedDB transaction was aborted.
INDEXEDDB_BLOCKEDOpening the database was blocked by a connection in another tab.
INDEXEDDB_CONNECTION_CLOSINGThe database connection was closing when the request started.
INDEXEDDB_OPEN_FAILEDThe database could not be opened.
INDEXEDDB_REQUEST_FAILEDAn IndexedDB request failed.
INDEXEDDB_STORE_MISSINGThe object store does not exist in an already created database.
INVALID_STORAGE_RECORDA saved record is not a valid restate record.
INVALID_STORED_VALUEA value did not pass validate.
LIFECYCLE_LIMITAn updater kept starting and stopping itself.
LOAD_FAILEDA record could not be loaded, so an automatic or bulk save did not replace it.
MMKV_UNAVAILABLEreact-native-mmkv could not be loaded.
MULTIPLE_ERRORSSeveral callbacks failed in one operation; see error.errors.
NOTIFICATION_LIMITObservers kept scheduling changes in a loop.
OBSERVER_ERRORA listener or lifecycle callback threw.
OPERATION_ABORTEDA storage operation was cancelled.
QUEUE_FULLMore operations were waiting than maxPendingOperations allows.
REENTRANT_SETTERA state updater tried to change the same store while it was running.
SCOPE_DESTROYEDA destroyed scope was used to create stores.
SEQUENCE_EXHAUSTEDAn internal sequence ran out of identities.
STORAGE_BLOCKEDAn earlier adapter call has not settled after a timeout or abort.
STORAGE_TIMEOUTAn operation took longer than operationTimeoutMs.
STORAGE_UNAVAILABLEStorage cannot be used here; the store keeps working in memory.
STORAGE_VALUE_TOO_LARGEThe encoded value is longer than maxStorageCharacters.
STORAGE_VERSION_MISMATCHA record has another version and no migrate function handles it.
STORE_DESTROYEDA destroyed store was subscribed to or asked to use storage.

Search docs

Search titles, descriptions and text of every guide.