or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-persistence.mdexperimental-features.mdindex.mdpersister-interface.mdreact-provider.md

persister-interface.mddocs/

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.