@tanstack/persister
Version:
Utilities for persisting state to local storage, session storage, indexedDB, and more.
143 lines (142 loc) • 6.15 kB
text/typescript
import { Persister } from './persister.cjs';
import { RequiredKeys } from './types.cjs';
export interface PersistedStorage<TState, TSelected extends Partial<TState> = TState> {
buster?: string;
state: TSelected | undefined;
timestamp: number;
}
/**
* Configuration options for creating a browser-based state persister.
*
* The persister can use either localStorage (persists across browser sessions) or
* sessionStorage (cleared when browser tab/window closes) to store serialized state.
*/
export interface StoragePersisterOptions<TState, TSelected extends Partial<TState> = TState> {
/**
* A version string used to invalidate cached state. When changed, any existing
* stored state will be considered invalid and cleared.
*/
buster?: string;
/**
* The default state to use if no state is found in storage.
*/
defaultState?: TState;
/**
* Optional function to customize how state is deserialized after loading from storage.
* By default, JSON.parse is used.
*
* Optionally, consider using SuperJSON for better deserialization of complex objects. See https://github.com/flightcontrolhq/superjson
*/
deserializer?: (state: string) => PersistedStorage<TSelected>;
/**
* Unique identifier used as the storage key for persisting state.
*/
key: string;
/**
* Maximum age in milliseconds before stored state is considered expired.
* When exceeded, the state will be cleared and treated as if it doesn't exist.
*/
maxAge?: number;
/**
* Optional callback that runs after state is successfully loaded.
*/
onLoadState?: (state: TSelected | undefined, storagePersister: StoragePersister<TState, TSelected>) => void;
/**
* Optional callback that runs after state is unable to be loaded.
*/
onLoadStateError?: (error: Error, storagePersister: StoragePersister<TState, TSelected>) => void;
/**
* Optional callback that runs after state is successfully saved.
*/
onSaveState?: (state: TSelected, storagePersister: StoragePersister<TState, TSelected>) => void;
/**
* Optional callback that runs after state is unable to be saved.
* For example, if the storage is full (localStorage >= 5MB)
*/
onSaveStateError?: (error: Error, storagePersister: StoragePersister<TState, TSelected>) => void;
/**
* Optional function to customize how state is serialized before saving to storage.
* By default, JSON.stringify is used.
*
* Optionally, consider using SuperJSON for better serialization of complex objects. See https://github.com/flightcontrolhq/superjson
*/
serializer?: (state: PersistedStorage<TSelected>) => string;
/**
* Optional function to filter which parts of the state are persisted and loaded.
* When provided, only the filtered state will be saved to storage and returned when loading.
* This is useful for excluding sensitive or temporary data from persistence.
*
* Note: Don't use this to replace the serialization. Use the `serializer` option instead for that.
*/
select?: (state: TState) => TSelected;
/**
* The browser storage implementation to use for persisting state.
* Typically window.localStorage or window.sessionStorage.
*
* Defaults to window.localStorage.
*/
storage?: Storage | null;
}
type DefaultOptions = RequiredKeys<Partial<StoragePersisterOptions<any>>, 'deserializer' | 'serializer' | 'storage'>;
/**
* A persister that saves state to browser local/session storage.
*
* The persister can use either localStorage (persists across browser sessions) or
* sessionStorage (cleared when browser tab/window closes). State is automatically
* serialized to JSON when saving and deserialized when loading.
*
* Optionally, a `buster` string can be provided to force cache busting by storing it in the value.
* Optionally, a `maxAge` (in ms) can be provided to expire the stored state after a certain duration.
* Optionally, callbacks can be provided to run after state is saved or loaded.
*
* @example
* ```ts
* const persister = new StoragePersister({
* key: 'my-rate-limiter', // required
* storage: window.localStorage,
* buster: 'v2',
* maxAge: 1000 * 60 * 60, // 1 hour
* stateTransform: (state) => ({
* // Only persist specific parts of the state
* count: state.count,
* lastReset: state.lastReset,
* // Exclude sensitive or temporary data
* }),
* onSaveState: (key, state) => console.log('State saved:', key, state),
* onLoadState: (key, state) => console.log('State loaded:', key, state),
* onLoadStateError: (key, error) => console.error('Error loading state:', key, error),
* onSaveStateError: (key, error) => console.error('Error saving state:', key, error)
* })
* ```
*/
export declare class StoragePersister<TState, TSelected extends Partial<TState> = TState> extends Persister<TState, TSelected> {
options: StoragePersisterOptions<TState, TSelected> & DefaultOptions;
constructor(initialOptions: StoragePersisterOptions<TState, TSelected>);
/**
* Updates the persister options
*/
setOptions: (newOptions: Partial<StoragePersisterOptions<TState, TSelected>>) => void;
/**
* Saves the state to storage
*/
saveState: (state: TState | TSelected) => void;
/**
* Loads the state from storage
*/
loadState: () => TSelected | undefined;
/**
* Handles the storage event and ignores events triggered by the current tab.
*
* The storage event is only fired in other tabs/windows, not the one that made the change.
* However, some browsers or environments may fire it in the same tab, so we check if the event's storageArea
* matches the persister's storage and ignore if not.
*/
private handleStorageChange;
subscribeToStorage: () => void;
unsubscribeFromStorage: () => void;
/**
* Clears the state from storage or sets the default state if provided and specified to be used
*/
clearState: (useDefaultState?: boolean) => void;
}
export {};