or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

client-persistence.mdindex.mdquery-persistence.mdretry-strategies.md

retry-strategies.mddocs/

0

# Retry Strategies

1

2

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.

3

4

## Capabilities

5

6

### Persist Retryer Function Type

7

8

Defines the signature for retry strategy functions that handle persistence failures.

9

10

```typescript { .api }

11

/**

12

* Function type for retry strategies when persistence fails

13

* @param props - Object containing the failed persistence context

14

* @returns Modified PersistedClient or undefined if retry should give up

15

*/

16

type PersistRetryer = (props: {

17

persistedClient: PersistedClient;

18

error: Error;

19

errorCount: number;

20

}) => PersistedClient | undefined;

21

```

22

23

**Parameters:**

24

- `persistedClient` - The client state that failed to persist

25

- `error` - The error that occurred during persistence

26

- `errorCount` - Number of retry attempts so far

27

28

**Returns:**

29

- `PersistedClient` - Modified client state to retry persistence with

30

- `undefined` - Give up and don't retry

31

32

### Remove Oldest Query Strategy

33

34

Built-in retry strategy that removes the oldest query from the cache to reduce storage size when persistence fails.

35

36

```typescript { .api }

37

/**

38

* Retry strategy that removes the oldest query from cache to reduce size

39

* Sorts queries by dataUpdatedAt and removes the oldest one

40

*/

41

const removeOldestQuery: PersistRetryer;

42

```

43

44

**Usage Example:**

45

46

```typescript

47

import {

48

persistQueryClient,

49

removeOldestQuery,

50

type PersistRetryer,

51

type PersistedClient

52

} from "@tanstack/query-persist-client-core";

53

54

// Simple persister with retry logic

55

function createRetryPersister(baseStorage: Storage, retryStrategy: PersistRetryer) {

56

return {

57

async persistClient(persistedClient: PersistedClient) {

58

let attempts = 0;

59

let currentClient = persistedClient;

60

61

while (attempts < 3) {

62

try {

63

const serialized = JSON.stringify(currentClient);

64

baseStorage.setItem("queryClient", serialized);

65

return; // Success

66

} catch (error) {

67

attempts++;

68

console.warn(`Persistence attempt ${attempts} failed:`, error);

69

70

// Try retry strategy

71

const modifiedClient = retryStrategy({

72

persistedClient: currentClient,

73

error: error as Error,

74

errorCount: attempts

75

});

76

77

if (!modifiedClient) {

78

throw new Error(`Persistence failed after ${attempts} attempts`);

79

}

80

81

currentClient = modifiedClient;

82

}

83

}

84

85

throw new Error("Max retry attempts exceeded");

86

},

87

88

async restoreClient() {

89

const stored = baseStorage.getItem("queryClient");

90

return stored ? JSON.parse(stored) : undefined;

91

},

92

93

async removeClient() {

94

baseStorage.removeItem("queryClient");

95

}

96

};

97

}

98

99

// Use the retry persister with removeOldestQuery strategy

100

const queryClient = new QueryClient();

101

const retryPersister = createRetryPersister(localStorage, removeOldestQuery);

102

103

const [unsubscribe, restorePromise] = persistQueryClient({

104

queryClient,

105

persister: retryPersister,

106

buster: "app-v1.0"

107

});

108

```

109

110

## Custom Retry Strategies

111

112

You can implement custom retry strategies to handle different failure scenarios:

113

114

```typescript

115

// Remove queries by specific criteria

116

const removeStaleQueries: PersistRetryer = ({ persistedClient, error, errorCount }) => {

117

const mutations = [...persistedClient.clientState.mutations];

118

const queries = [...persistedClient.clientState.queries];

119

120

// Remove stale queries (older than 1 hour)

121

const oneHourAgo = Date.now() - (1000 * 60 * 60);

122

const freshQueries = queries.filter(query =>

123

query.state.dataUpdatedAt > oneHourAgo

124

);

125

126

if (freshQueries.length < queries.length) {

127

return {

128

...persistedClient,

129

clientState: { mutations, queries: freshQueries }

130

};

131

}

132

133

return undefined; // No stale queries to remove, give up

134

};

135

136

// Remove mutations to reduce size

137

const removeMutations: PersistRetryer = ({ persistedClient, error, errorCount }) => {

138

if (persistedClient.clientState.mutations.length > 0) {

139

return {

140

...persistedClient,

141

clientState: {

142

mutations: [], // Remove all mutations

143

queries: [...persistedClient.clientState.queries]

144

}

145

};

146

}

147

148

return undefined; // No mutations to remove

149

};

150

151

// Progressive data reduction strategy

152

const progressiveReduction: PersistRetryer = ({ persistedClient, error, errorCount }) => {

153

const mutations = [...persistedClient.clientState.mutations];

154

let queries = [...persistedClient.clientState.queries];

155

156

if (errorCount === 1) {

157

// First attempt: remove mutations

158

return {

159

...persistedClient,

160

clientState: { mutations: [], queries }

161

};

162

} else if (errorCount === 2) {

163

// Second attempt: remove oldest 25% of queries

164

const sortedQueries = queries

165

.sort((a, b) => a.state.dataUpdatedAt - b.state.dataUpdatedAt);

166

const keepCount = Math.ceil(queries.length * 0.75);

167

queries = sortedQueries.slice(-keepCount);

168

169

return {

170

...persistedClient,

171

clientState: { mutations: [], queries }

172

};

173

} else if (errorCount === 3) {

174

// Final attempt: keep only the most recent 5 queries

175

const recentQueries = queries

176

.sort((a, b) => b.state.dataUpdatedAt - a.state.dataUpdatedAt)

177

.slice(0, 5);

178

179

return {

180

...persistedClient,

181

clientState: { mutations: [], queries: recentQueries }

182

};

183

}

184

185

return undefined; // Give up after 3 attempts

186

};

187

```

188

189

## Storage Configuration

190

191

### Storage Options

192

193

```typescript { .api }

194

interface StoragePersisterOptions<TStorageValue = string> {

195

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

196

* For SSR pass in `undefined`. */

197

storage: AsyncStorage<TStorageValue> | undefined | null;

198

/**

199

* How to serialize the data to storage.

200

* @default `JSON.stringify`

201

*/

202

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

203

/**

204

* How to deserialize the data from storage.

205

* @default `JSON.parse`

206

*/

207

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

208

/**

209

* A unique string that can be used to forcefully invalidate existing caches,

210

* if they do not share the same buster string

211

*/

212

buster?: string;

213

/**

214

* The max-allowed age of the cache in milliseconds.

215

* If a persisted cache is found that is older than this

216

* time, it will be discarded

217

* @default 24 hours

218

*/

219

maxAge?: number;

220

/**

221

* Prefix to be used for storage key.

222

* Storage key is a combination of prefix and query hash in a form of `prefix-queryHash`.

223

* @default 'tanstack-query'

224

*/

225

prefix?: string;

226

/**

227

* Filters to narrow down which Queries should be persisted.

228

*/

229

filters?: QueryFilters;

230

}

231

```

232

233

### Persisted Query Data Structure

234

235

```typescript { .api }

236

interface PersistedQuery {

237

buster: string;

238

queryHash: string;

239

queryKey: QueryKey;

240

state: QueryState;

241

}

242

```

243

244

## Constants

245

246

```typescript { .api }

247

/** Default prefix for storage keys used in query persistence */

248

const PERSISTER_KEY_PREFIX = 'tanstack-query';

249

```

250

251

## Error Scenarios

252

253

Retry strategies are designed to handle common persistence failures:

254

255

- **Storage Quota Exceeded**: Most common scenario where storage is full

256

- **Individual Item Too Large**: When a single query's data exceeds storage limits

257

- **Serialization Failures**: When data contains non-serializable values

258

- **Network Errors**: In remote storage scenarios (IndexedDB, remote APIs)

259

- **Permission Errors**: Storage access denied by browser or system

260

261

## Best Practices

262

263

1. **Implement Progressive Reduction**: Start by removing least important data (mutations, stale queries)

264

2. **Set Retry Limits**: Avoid infinite retry loops by limiting attempts

265

3. **Log Failures**: Track which strategies work in your application's context

266

4. **Test Storage Limits**: Understand your target storage backend's limitations

267

5. **Graceful Degradation**: Always return `undefined` when no more reductions are possible