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

experimental-features.mddocs/

0

# Experimental Features

1

2

The package includes experimental functionality for fine-grained, per-query persistence control. These features are marked as experimental and may change in future versions.

3

4

## Capabilities

5

6

### experimental_createQueryPersister Function

7

8

Creates a fine-grained query persister that enables per-query persistence control and storage management.

9

10

```typescript { .api }

11

/**

12

* Warning: experimental feature.

13

* Creates a fine-grained query persister for per-query persistence control

14

* Enables individual queries to be persisted to storage with custom configuration

15

* @param options - Storage and configuration options

16

* @returns Object with persister functions for query-level operations

17

*/

18

function experimental_createQueryPersister<TStorageValue = string>(

19

options: StoragePersisterOptions<TStorageValue>

20

): QueryPersister;

21

22

interface StoragePersisterOptions<TStorageValue = string> {

23

/** The storage client used for setting and retrieving items from cache */

24

storage: AsyncStorage<TStorageValue> | undefined | null;

25

/** How to serialize the data to storage (default: JSON.stringify) */

26

serialize?: (persistedQuery: PersistedQuery) => MaybePromise<TStorageValue>;

27

/** How to deserialize the data from storage (default: JSON.parse) */

28

deserialize?: (cachedString: TStorageValue) => MaybePromise<PersistedQuery>;

29

/** Cache invalidation string for version control */

30

buster?: string;

31

/** Max age in milliseconds (default: 24 hours) */

32

maxAge?: number;

33

/** Storage key prefix (default: 'tanstack-query') */

34

prefix?: string;

35

/** Query filters to narrow down which queries should be persisted */

36

filters?: QueryFilters;

37

}

38

39

interface QueryPersister {

40

/** Custom query function that handles persistence and fetching */

41

persisterFn: <T, TQueryKey extends QueryKey>(

42

queryFn: (context: QueryFunctionContext<TQueryKey>) => T | Promise<T>,

43

ctx: QueryFunctionContext<TQueryKey>,

44

query: Query

45

) => Promise<T>;

46

/** Persist a specific query to storage */

47

persistQuery: (query: Query) => Promise<void>;

48

/** Persist a query by its key */

49

persistQueryByKey: (queryKey: QueryKey, queryClient: QueryClient) => Promise<void>;

50

/** Retrieve a query from storage */

51

retrieveQuery: <T>(

52

queryHash: string,

53

afterRestoreMacroTask?: (persistedQuery: PersistedQuery) => void

54

) => Promise<T | undefined>;

55

/** Garbage collect expired queries from storage */

56

persisterGc: () => Promise<void>;

57

/** Restore multiple queries from storage */

58

restoreQueries: (

59

queryClient: QueryClient,

60

filters?: Pick<QueryFilters, 'queryKey' | 'exact'>

61

) => Promise<void>;

62

}

63

```

64

65

**Usage Example:**

66

67

```typescript

68

import { experimental_createQueryPersister } from '@tanstack/react-query-persist-client';

69

import { useQuery } from '@tanstack/react-query';

70

71

// Create the persister

72

const queryPersister = experimental_createQueryPersister({

73

storage: localStorage,

74

prefix: 'my-app-queries',

75

maxAge: 1000 * 60 * 60 * 2, // 2 hours

76

filters: {

77

// Only persist specific query types

78

queryKey: ['user-data'],

79

},

80

});

81

82

// Use with individual queries

83

function UserProfile({ userId }: { userId: string }) {

84

const { data } = useQuery({

85

queryKey: ['user-data', userId],

86

persister: queryPersister.persisterFn,

87

queryFn: async ({ queryKey }) => {

88

const response = await fetch(`/api/users/${queryKey[1]}`);

89

return response.json();

90

},

91

});

92

93

return <div>{data?.name}</div>;

94

}

95

```

96

97

### AsyncStorage Interface

98

99

Interface for storage backends used by the experimental persister.

100

101

```typescript { .api }

102

interface AsyncStorage<TStorageValue = string> {

103

/** Get an item from storage by key */

104

getItem: (key: string) => MaybePromise<TStorageValue | undefined | null>;

105

/** Set an item in storage */

106

setItem: (key: string, value: TStorageValue) => MaybePromise<unknown>;

107

/** Remove an item from storage */

108

removeItem: (key: string) => MaybePromise<void>;

109

/** Get all entries from storage (optional, needed for garbage collection) */

110

entries?: () => MaybePromise<Array<[key: string, value: TStorageValue]>>;

111

}

112

113

interface PersistedQuery {

114

/** Cache invalidation string */

115

buster: string;

116

/** Hash of the query key */

117

queryHash: string;

118

/** The original query key */

119

queryKey: QueryKey;

120

/** The query state including data and metadata */

121

state: QueryState;

122

}

123

124

type MaybePromise<T> = T | Promise<T>;

125

```

126

127

### Retry Strategies

128

129

Built-in retry strategies for handling persistence failures.

130

131

```typescript { .api }

132

/**

133

* Function type for handling persistence retry scenarios

134

* Called when persistence fails to determine recovery strategy

135

*/

136

type PersistRetryer = (props: {

137

persistedClient: PersistedClient;

138

error: Error;

139

errorCount: number;

140

}) => PersistedClient | undefined;

141

142

/**

143

* Built-in retry strategy that removes the oldest query when persistence fails

144

* Useful for storage quota limitations

145

*/

146

function removeOldestQuery(props: {

147

persistedClient: PersistedClient;

148

error: Error;

149

errorCount: number;

150

}): PersistedClient | undefined;

151

```

152

153

## Advanced Usage Examples

154

155

### Custom Storage Implementation

156

157

```typescript

158

// Create a custom storage that combines localStorage with compression

159

class CompressedStorage implements AsyncStorage<string> {

160

async getItem(key: string) {

161

const compressed = localStorage.getItem(key);

162

return compressed ? LZString.decompress(compressed) : null;

163

}

164

165

async setItem(key: string, value: string) {

166

const compressed = LZString.compress(value);

167

localStorage.setItem(key, compressed);

168

}

169

170

async removeItem(key: string) {

171

localStorage.removeItem(key);

172

}

173

174

async entries() {

175

const entries: Array<[string, string]> = [];

176

for (let i = 0; i < localStorage.length; i++) {

177

const key = localStorage.key(i);

178

if (key) {

179

const value = await this.getItem(key);

180

if (value) entries.push([key, value]);

181

}

182

}

183

return entries;

184

}

185

}

186

187

const persister = experimental_createQueryPersister({

188

storage: new CompressedStorage(),

189

prefix: 'compressed-queries',

190

});

191

```

192

193

### Selective Query Persistence

194

195

```typescript

196

// Only persist queries that match specific criteria

197

const selectivePersister = experimental_createQueryPersister({

198

storage: localStorage,

199

filters: {

200

// Only persist user-specific data

201

predicate: (query) => {

202

const [scope] = query.queryKey as [string, ...any[]];

203

return ['user-profile', 'user-settings', 'user-preferences'].includes(scope);

204

},

205

},

206

// Custom serializer that excludes sensitive data

207

serialize: (persistedQuery) => {

208

const sanitized = {

209

...persistedQuery,

210

state: {

211

...persistedQuery.state,

212

data: sanitizeUserData(persistedQuery.state.data),

213

},

214

};

215

return JSON.stringify(sanitized);

216

},

217

});

218

```

219

220

### Manual Garbage Collection

221

222

```typescript

223

// Set up periodic garbage collection

224

const persister = experimental_createQueryPersister({

225

storage: localStorage,

226

maxAge: 1000 * 60 * 60 * 24, // 24 hours

227

});

228

229

// Run garbage collection every hour

230

setInterval(async () => {

231

try {

232

await persister.persisterGc();

233

console.log('Query cache garbage collection completed');

234

} catch (error) {

235

console.error('Garbage collection failed:', error);

236

}

237

}, 1000 * 60 * 60); // 1 hour

238

```

239

240

### Batch Query Restoration

241

242

```typescript

243

import { QueryClient } from '@tanstack/react-query';

244

245

const queryClient = new QueryClient();

246

const persister = experimental_createQueryPersister({

247

storage: localStorage,

248

});

249

250

// Restore all cached user data on app startup

251

async function restoreUserQueries(userId: string) {

252

await persister.restoreQueries(queryClient, {

253

queryKey: ['user-data', userId],

254

exact: false, // Match all queries starting with this key

255

});

256

}

257

258

// Usage in app initialization

259

async function initializeApp(userId: string) {

260

await restoreUserQueries(userId);

261

// Now user-specific queries are restored from cache

262

}

263

```

264

265

### Custom Query Function with Persistence

266

267

```typescript

268

// Create a custom query function that handles persistence

269

function createPersistedQueryFn<T>(

270

baseFetchFn: () => Promise<T>,

271

persister: ReturnType<typeof experimental_createQueryPersister>

272

) {

273

return async (context: QueryFunctionContext) => {

274

// Let persister handle restoration and caching

275

return persister.persisterFn(

276

() => baseFetchFn(),

277

context,

278

context.query // Query instance from context

279

);

280

};

281

}

282

283

// Usage

284

const fetchUserData = createPersistedQueryFn(

285

() => fetch('/api/user').then(r => r.json()),

286

persister

287

);

288

289

function UserComponent() {

290

const { data } = useQuery({

291

queryKey: ['user'],

292

queryFn: fetchUserData,

293

});

294

295

return <div>{data?.name}</div>;

296

}

297

```

298

299

## Constants

300

301

```typescript { .api }

302

/** Default prefix used for storage keys */

303

const PERSISTER_KEY_PREFIX = 'tanstack-query';

304

```

305

306

Storage keys are formed as `${prefix}-${queryHash}` where prefix defaults to `PERSISTER_KEY_PREFIX`.