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 page
The guaranteesOrdered savesTry itWhen loading failsSave and closeNo storage availableValidate and migrate saved dataBring your own storageLimits and timeoutsError codesThe guarantees
These behaviors are part of the 1.0 contract and are covered by the package’s tests.
| Situation | What restate does |
|---|---|
Several saves in a row | Runs them in order per store; a save waiting in the queue writes the newest value. |
A record failed to load | autoSave and bulk saves skip it with LOAD_FAILED; only an explicit save() replaces it. |
Component unmounts during a save | The requested save finishes; later writes to the destroyed store are ignored. |
Storage is unavailable | The store works in memory, status().available is false, saves reject. |
An adapter hangs | The operation times out after operationTimeoutMs and later work waits until the adapter settles. |
Too much pending work | New 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.
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 writesTry 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.
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.
function Document({ onClose }) {
const useDraft = useRestateScope((scope) => persistedRestate('', { scope, key: 'draft' }));
const saveAndClose = () => {
useDraft.save(); // finishes even after this component unmounts
onClose();
};
// …
}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.
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.
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.
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.
| Code | Meaning |
|---|---|
BULK_OPERATION_FAILED | A bulk call such as restateManager.ready or saveAll had failures; see error.failures. |
INDEXEDDB_ABORTED | An IndexedDB transaction was aborted. |
INDEXEDDB_BLOCKED | Opening the database was blocked by a connection in another tab. |
INDEXEDDB_CONNECTION_CLOSING | The database connection was closing when the request started. |
INDEXEDDB_OPEN_FAILED | The database could not be opened. |
INDEXEDDB_REQUEST_FAILED | An IndexedDB request failed. |
INDEXEDDB_STORE_MISSING | The object store does not exist in an already created database. |
INVALID_STORAGE_RECORD | A saved record is not a valid restate record. |
INVALID_STORED_VALUE | A value did not pass validate. |
LIFECYCLE_LIMIT | An updater kept starting and stopping itself. |
LOAD_FAILED | A record could not be loaded, so an automatic or bulk save did not replace it. |
MMKV_UNAVAILABLE | react-native-mmkv could not be loaded. |
MULTIPLE_ERRORS | Several callbacks failed in one operation; see error.errors. |
NOTIFICATION_LIMIT | Observers kept scheduling changes in a loop. |
OBSERVER_ERROR | A listener or lifecycle callback threw. |
OPERATION_ABORTED | A storage operation was cancelled. |
QUEUE_FULL | More operations were waiting than maxPendingOperations allows. |
REENTRANT_SETTER | A state updater tried to change the same store while it was running. |
SCOPE_DESTROYED | A destroyed scope was used to create stores. |
SEQUENCE_EXHAUSTED | An internal sequence ran out of identities. |
STORAGE_BLOCKED | An earlier adapter call has not settled after a timeout or abort. |
STORAGE_TIMEOUT | An operation took longer than operationTimeoutMs. |
STORAGE_UNAVAILABLE | Storage cannot be used here; the store keeps working in memory. |
STORAGE_VALUE_TOO_LARGE | The encoded value is longer than maxStorageCharacters. |
STORAGE_VERSION_MISMATCH | A record has another version and no migrate function handles it. |
STORE_DESTROYED | A destroyed store was subscribed to or asked to use storage. |