Utilities for persisting TanStack Query cache data to various storage backends with restore functionality
—
Configurable retry mechanisms for handling persistence failures due to storage quotas, size limits, or other constraints. When persistence operations fail, retry strategies can modify the data being persisted to resolve the issue.
Defines the signature for retry strategy functions that handle persistence failures.
/**
* Function type for retry strategies when persistence fails
* @param props - Object containing the failed persistence context
* @returns Modified PersistedClient or undefined if retry should give up
*/
type PersistRetryer = (props: {
persistedClient: PersistedClient;
error: Error;
errorCount: number;
}) => PersistedClient | undefined;Parameters:
persistedClient - The client state that failed to persisterror - The error that occurred during persistenceerrorCount - Number of retry attempts so farReturns:
PersistedClient - Modified client state to retry persistence withundefined - Give up and don't retryBuilt-in retry strategy that removes the oldest query from the cache to reduce storage size when persistence fails.
/**
* Retry strategy that removes the oldest query from cache to reduce size
* Sorts queries by dataUpdatedAt and removes the oldest one
*/
const removeOldestQuery: PersistRetryer;Usage Example:
import {
persistQueryClient,
removeOldestQuery,
type PersistRetryer,
type PersistedClient
} from "@tanstack/query-persist-client-core";
// Simple persister with retry logic
function createRetryPersister(baseStorage: Storage, retryStrategy: PersistRetryer) {
return {
async persistClient(persistedClient: PersistedClient) {
let attempts = 0;
let currentClient = persistedClient;
while (attempts < 3) {
try {
const serialized = JSON.stringify(currentClient);
baseStorage.setItem("queryClient", serialized);
return; // Success
} catch (error) {
attempts++;
console.warn(`Persistence attempt ${attempts} failed:`, error);
// Try retry strategy
const modifiedClient = retryStrategy({
persistedClient: currentClient,
error: error as Error,
errorCount: attempts
});
if (!modifiedClient) {
throw new Error(`Persistence failed after ${attempts} attempts`);
}
currentClient = modifiedClient;
}
}
throw new Error("Max retry attempts exceeded");
},
async restoreClient() {
const stored = baseStorage.getItem("queryClient");
return stored ? JSON.parse(stored) : undefined;
},
async removeClient() {
baseStorage.removeItem("queryClient");
}
};
}
// Use the retry persister with removeOldestQuery strategy
const queryClient = new QueryClient();
const retryPersister = createRetryPersister(localStorage, removeOldestQuery);
const [unsubscribe, restorePromise] = persistQueryClient({
queryClient,
persister: retryPersister,
buster: "app-v1.0"
});You can implement custom retry strategies to handle different failure scenarios:
// Remove queries by specific criteria
const removeStaleQueries: PersistRetryer = ({ persistedClient, error, errorCount }) => {
const mutations = [...persistedClient.clientState.mutations];
const queries = [...persistedClient.clientState.queries];
// Remove stale queries (older than 1 hour)
const oneHourAgo = Date.now() - (1000 * 60 * 60);
const freshQueries = queries.filter(query =>
query.state.dataUpdatedAt > oneHourAgo
);
if (freshQueries.length < queries.length) {
return {
...persistedClient,
clientState: { mutations, queries: freshQueries }
};
}
return undefined; // No stale queries to remove, give up
};
// Remove mutations to reduce size
const removeMutations: PersistRetryer = ({ persistedClient, error, errorCount }) => {
if (persistedClient.clientState.mutations.length > 0) {
return {
...persistedClient,
clientState: {
mutations: [], // Remove all mutations
queries: [...persistedClient.clientState.queries]
}
};
}
return undefined; // No mutations to remove
};
// Progressive data reduction strategy
const progressiveReduction: PersistRetryer = ({ persistedClient, error, errorCount }) => {
const mutations = [...persistedClient.clientState.mutations];
let queries = [...persistedClient.clientState.queries];
if (errorCount === 1) {
// First attempt: remove mutations
return {
...persistedClient,
clientState: { mutations: [], queries }
};
} else if (errorCount === 2) {
// Second attempt: remove oldest 25% of queries
const sortedQueries = queries
.sort((a, b) => a.state.dataUpdatedAt - b.state.dataUpdatedAt);
const keepCount = Math.ceil(queries.length * 0.75);
queries = sortedQueries.slice(-keepCount);
return {
...persistedClient,
clientState: { mutations: [], queries }
};
} else if (errorCount === 3) {
// Final attempt: keep only the most recent 5 queries
const recentQueries = queries
.sort((a, b) => b.state.dataUpdatedAt - a.state.dataUpdatedAt)
.slice(0, 5);
return {
...persistedClient,
clientState: { mutations: [], queries: recentQueries }
};
}
return undefined; // Give up after 3 attempts
};interface StoragePersisterOptions<TStorageValue = string> {
/** The storage client used for setting and retrieving items from cache.
* For SSR pass in `undefined`. */
storage: AsyncStorage<TStorageValue> | undefined | null;
/**
* How to serialize the data to storage.
* @default `JSON.stringify`
*/
serialize?: (persistedQuery: PersistedQuery) => MaybePromise<TStorageValue>;
/**
* How to deserialize the data from storage.
* @default `JSON.parse`
*/
deserialize?: (cachedString: TStorageValue) => MaybePromise<PersistedQuery>;
/**
* A unique string that can be used to forcefully invalidate existing caches,
* if they do not share the same buster string
*/
buster?: string;
/**
* The max-allowed age of the cache in milliseconds.
* If a persisted cache is found that is older than this
* time, it will be discarded
* @default 24 hours
*/
maxAge?: number;
/**
* Prefix to be used for storage key.
* Storage key is a combination of prefix and query hash in a form of `prefix-queryHash`.
* @default 'tanstack-query'
*/
prefix?: string;
/**
* Filters to narrow down which Queries should be persisted.
*/
filters?: QueryFilters;
}interface PersistedQuery {
buster: string;
queryHash: string;
queryKey: QueryKey;
state: QueryState;
}/** Default prefix for storage keys used in query persistence */
const PERSISTER_KEY_PREFIX = 'tanstack-query';Retry strategies are designed to handle common persistence failures:
undefined when no more reductions are possibleInstall with Tessl CLI
npx tessl i tessl/npm-tanstack--query-persist-client-core