0
# Persister Interface
1
2
The Persister interface provides an abstraction for implementing custom storage solutions. It defines the contract for storing, retrieving, and removing persisted cache data across different storage backends.
3
4
## Capabilities
5
6
### Persister Interface
7
8
Core interface that all storage implementations must follow.
9
10
```typescript { .api }
11
/**
12
* Interface for storing and restoring cache to/from a persisted location
13
* Implementations can use any storage backend (localStorage, IndexedDB, etc.)
14
*/
15
interface Persister {
16
/**
17
* Store persisted client data to storage
18
* @param persistClient - The client data to persist including metadata
19
* @returns Promise or void when storage completes
20
*/
21
persistClient: (persistClient: PersistedClient) => Promisable<void>;
22
23
/**
24
* Retrieve persisted client data from storage
25
* @returns Promise resolving to stored data or undefined if none exists
26
*/
27
restoreClient: () => Promisable<PersistedClient | undefined>;
28
29
/**
30
* Remove persisted client data from storage
31
* Called when data is expired, invalid, or needs cleanup
32
* @returns Promise or void when removal completes
33
*/
34
removeClient: () => Promisable<void>;
35
}
36
37
interface PersistedClient {
38
/** Unix timestamp when the data was persisted */
39
timestamp: number;
40
/** Cache invalidation string for version control */
41
buster: string;
42
/** Dehydrated query client state containing queries and mutations */
43
clientState: DehydratedState;
44
}
45
46
type Promisable<T> = T | PromiseLike<T>;
47
```
48
49
## Storage Implementation Examples
50
51
### localStorage Implementation
52
53
```typescript
54
import type { Persister, PersistedClient } from '@tanstack/react-query-persist-client';
55
56
function createLocalStoragePersister(key: string = 'queryClient'): Persister {
57
return {
58
persistClient: (client: PersistedClient) => {
59
localStorage.setItem(key, JSON.stringify(client));
60
},
61
62
restoreClient: (): PersistedClient | undefined => {
63
const stored = localStorage.getItem(key);
64
return stored ? JSON.parse(stored) : undefined;
65
},
66
67
removeClient: () => {
68
localStorage.removeItem(key);
69
},
70
};
71
}
72
73
// Usage
74
const persister = createLocalStoragePersister('myAppCache');
75
```
76
77
### IndexedDB Implementation
78
79
```typescript
80
function createIndexedDBPersister(dbName: string, storeName: string): Persister {
81
const openDB = () => {
82
return new Promise<IDBDatabase>((resolve, reject) => {
83
const request = indexedDB.open(dbName, 1);
84
85
request.onerror = () => reject(request.error);
86
request.onsuccess = () => resolve(request.result);
87
88
request.onupgradeneeded = () => {
89
const db = request.result;
90
if (!db.objectStoreNames.contains(storeName)) {
91
db.createObjectStore(storeName);
92
}
93
};
94
});
95
};
96
97
return {
98
persistClient: async (client: PersistedClient) => {
99
const db = await openDB();
100
const transaction = db.transaction([storeName], 'readwrite');
101
const store = transaction.objectStore(storeName);
102
store.put(client, 'queryClient');
103
},
104
105
restoreClient: async (): Promise<PersistedClient | undefined> => {
106
const db = await openDB();
107
const transaction = db.transaction([storeName], 'readonly');
108
const store = transaction.objectStore(storeName);
109
110
return new Promise((resolve, reject) => {
111
const request = store.get('queryClient');
112
request.onerror = () => reject(request.error);
113
request.onsuccess = () => resolve(request.result);
114
});
115
},
116
117
removeClient: async () => {
118
const db = await openDB();
119
const transaction = db.transaction([storeName], 'readwrite');
120
const store = transaction.objectStore(storeName);
121
store.delete('queryClient');
122
},
123
};
124
}
125
126
// Usage
127
const persister = createIndexedDBPersister('MyAppDB', 'queryCache');
128
```
129
130
### Async Storage Implementation (React Native)
131
132
```typescript
133
import AsyncStorage from '@react-native-async-storage/async-storage';
134
135
function createAsyncStoragePersister(key: string = 'queryClient'): Persister {
136
return {
137
persistClient: async (client: PersistedClient) => {
138
await AsyncStorage.setItem(key, JSON.stringify(client));
139
},
140
141
restoreClient: async (): Promise<PersistedClient | undefined> => {
142
const stored = await AsyncStorage.getItem(key);
143
return stored ? JSON.parse(stored) : undefined;
144
},
145
146
removeClient: async () => {
147
await AsyncStorage.removeItem(key);
148
},
149
};
150
}
151
```
152
153
### Remote Storage Implementation
154
155
```typescript
156
function createRemoteStoragePersister(
157
apiUrl: string,
158
headers: Record<string, string> = {}
159
): Persister {
160
return {
161
persistClient: async (client: PersistedClient) => {
162
await fetch(`${apiUrl}/cache`, {
163
method: 'POST',
164
headers: { 'Content-Type': 'application/json', ...headers },
165
body: JSON.stringify(client),
166
});
167
},
168
169
restoreClient: async (): Promise<PersistedClient | undefined> => {
170
try {
171
const response = await fetch(`${apiUrl}/cache`, {
172
headers,
173
});
174
175
if (response.ok) {
176
return await response.json();
177
}
178
return undefined;
179
} catch {
180
return undefined;
181
}
182
},
183
184
removeClient: async () => {
185
await fetch(`${apiUrl}/cache`, {
186
method: 'DELETE',
187
headers,
188
});
189
},
190
};
191
}
192
```
193
194
### Compressed Storage Implementation
195
196
```typescript
197
import { compress, decompress } from 'lz-string';
198
199
function createCompressedPersister(basePersister: Persister): Persister {
200
return {
201
persistClient: async (client: PersistedClient) => {
202
const compressed = {
203
...client,
204
clientState: compress(JSON.stringify(client.clientState)),
205
};
206
await basePersister.persistClient(compressed as any);
207
},
208
209
restoreClient: async (): Promise<PersistedClient | undefined> => {
210
const stored = await basePersister.restoreClient() as any;
211
if (!stored) return undefined;
212
213
return {
214
...stored,
215
clientState: JSON.parse(decompress(stored.clientState)),
216
};
217
},
218
219
removeClient: () => basePersister.removeClient(),
220
};
221
}
222
223
// Usage
224
const compressedPersister = createCompressedPersister(
225
createLocalStoragePersister()
226
);
227
```
228
229
### Multi-Storage Implementation
230
231
```typescript
232
function createMultiStoragePersister(persisters: Persister[]): Persister {
233
return {
234
persistClient: async (client: PersistedClient) => {
235
// Save to all storage backends
236
await Promise.allSettled(
237
persisters.map(p => p.persistClient(client))
238
);
239
},
240
241
restoreClient: async (): Promise<PersistedClient | undefined> => {
242
// Try each persister until one succeeds
243
for (const persister of persisters) {
244
try {
245
const result = await persister.restoreClient();
246
if (result) return result;
247
} catch {
248
// Continue to next persister
249
}
250
}
251
return undefined;
252
},
253
254
removeClient: async () => {
255
// Remove from all storage backends
256
await Promise.allSettled(
257
persisters.map(p => p.removeClient())
258
);
259
},
260
};
261
}
262
263
// Usage: Try IndexedDB first, fallback to localStorage
264
const persister = createMultiStoragePersister([
265
createIndexedDBPersister('MyApp', 'cache'),
266
createLocalStoragePersister(),
267
]);
268
```
269
270
## PersistedClient Structure
271
272
The `PersistedClient` interface contains all data needed to restore a query client:
273
274
```typescript { .api }
275
interface PersistedClient {
276
/**
277
* Unix timestamp (Date.now()) when the data was persisted
278
* Used for age validation against maxAge option
279
*/
280
timestamp: number;
281
282
/**
283
* Version string used for cache invalidation
284
* Data is removed if buster doesn't match current buster
285
*/
286
buster: string;
287
288
/**
289
* Dehydrated state from @tanstack/query-core
290
* Contains serialized queries, mutations, and metadata
291
*/
292
clientState: DehydratedState;
293
}
294
```
295
296
The `clientState` contains the actual query and mutation data as produced by TanStack Query's `dehydrate()` function, including query keys, data, timestamps, and status information.