Skip to content

Commit 8cea6f7

Browse files
committed
fix(experimental_createPersister): setQueryData, ensureQueryData supports persister
1 parent 8e7c34f commit 8cea6f7

File tree

6 files changed

+120
-52
lines changed

6 files changed

+120
-52
lines changed

packages/query-core/src/infiniteQueryBehavior.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
116116
}
117117
if (context.options.persister) {
118118
context.fetchFn = () => {
119-
return context.options.persister?.(
119+
return context.options.persister?.persisterFn(
120120
fetchFn as any,
121121
{
122122
queryKey: context.queryKey,

packages/query-core/src/query.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ export class Query<
394394
}
395395
this.#abortSignalConsumed = false
396396
if (this.options.persister) {
397-
return this.options.persister(
397+
return this.options.persister.persisterFn(
398398
this.options.queryFn,
399399
queryFnContext as QueryFunctionContext<TQueryKey>,
400400
this as unknown as Query,

packages/query-core/src/queryClient.ts

+30-7
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export class QueryClient {
122122
return this.#queryCache.find({ queryKey })?.state.data
123123
}
124124

125-
ensureQueryData<
125+
async ensureQueryData<
126126
TQueryFnData,
127127
TError = DefaultError,
128128
TData = TQueryFnData,
@@ -132,9 +132,27 @@ export class QueryClient {
132132
): Promise<TData> {
133133
const cachedData = this.getQueryData<TData>(options.queryKey)
134134

135-
return cachedData !== undefined
136-
? Promise.resolve(cachedData)
137-
: this.fetchQuery(options)
135+
if (cachedData !== undefined) {
136+
return Promise.resolve(cachedData)
137+
}
138+
139+
// Check persister if defined
140+
const defaultedOptions = this.defaultQueryOptions({
141+
queryKey: options.queryKey,
142+
})
143+
144+
if (defaultedOptions.persister) {
145+
const persistedData =
146+
await defaultedOptions.persister.restoreQuery<TData>(
147+
options.queryHash || hashKey(options.queryKey),
148+
)
149+
150+
if (persistedData != null) {
151+
return Promise.resolve(persistedData)
152+
}
153+
}
154+
155+
return this.fetchQuery(options)
138156
}
139157

140158
getQueriesData<TQueryFnData = unknown>(
@@ -181,9 +199,14 @@ export class QueryClient {
181199
QueryKey
182200
>({ queryKey })
183201

184-
return this.#queryCache
185-
.build(this, defaultedOptions)
186-
.setData(data, { ...options, manual: true })
202+
const newQuery = this.#queryCache.build(this, defaultedOptions)
203+
const result = newQuery.setData(data, { ...options, manual: true })
204+
205+
if (defaultedOptions.persister) {
206+
defaultedOptions.persister.persistQuery(newQuery)
207+
}
208+
209+
return result
187210
}
188211

189212
setQueriesData<TQueryFnData>(

packages/query-core/src/types.ts

+18-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* istanbul ignore file */
22

33
import type { MutationState } from './mutation'
4-
import type { FetchDirection, Query, QueryBehavior } from './query'
4+
import type { FetchDirection, Query, QueryBehavior, QueryState } from './query'
55
import type { RetryDelayValue, RetryValue } from './retryer'
66
import type { QueryFilters, QueryTypeFilter } from './utils'
77
import type { QueryCache } from './queryCache'
@@ -148,11 +148,23 @@ export interface QueryOptions<
148148
*/
149149
gcTime?: number
150150
queryFn?: QueryFunction<TQueryFnData, TQueryKey, TPageParam>
151-
persister?: QueryPersister<
152-
NoInfer<TQueryFnData>,
153-
NoInfer<TQueryKey>,
154-
NoInfer<TPageParam>
155-
>
151+
persister?: {
152+
persisterFn: QueryPersister<
153+
NoInfer<TQueryFnData>,
154+
NoInfer<TQueryKey>,
155+
NoInfer<TPageParam>
156+
>
157+
persistQuery: (query: Query) => Promise<void>
158+
restoreQuery: <T>(
159+
queryHash: string,
160+
afterRestoreMacroTask?: (persistedQuery: {
161+
buster: string
162+
queryHash: string
163+
queryKey: QueryKey
164+
state: QueryState
165+
}) => void,
166+
) => Promise<T | undefined>
167+
}
156168
queryHash?: string
157169
queryKey?: TQueryKey
158170
queryKeyHashFn?: QueryKeyHashFunction<TQueryKey>

packages/query-persist-client-core/src/__tests__/createPersister.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ function setupPersister(
3636

3737
const queryFn = vi.fn()
3838

39-
const persisterFn = experimental_createPersister(persisterOptions)
39+
const { persisterFn } = experimental_createPersister(persisterOptions)
4040

4141
const query = new Query({
4242
cache: new QueryCache(),
@@ -202,7 +202,7 @@ describe('createPersister', () => {
202202
storageKey,
203203
JSON.stringify({
204204
buster: '',
205-
state: { dataUpdatedAt },
205+
state: { dataUpdatedAt, data: '' },
206206
}),
207207
)
208208

@@ -231,7 +231,7 @@ describe('createPersister', () => {
231231
storageKey,
232232
JSON.stringify({
233233
buster: '',
234-
state: { dataUpdatedAt: Date.now() },
234+
state: { dataUpdatedAt: Date.now(), data: '' },
235235
}),
236236
)
237237

@@ -325,7 +325,7 @@ describe('createPersister', () => {
325325
storageKey,
326326
JSON.stringify({
327327
buster: '',
328-
state: { dataUpdatedAt: Date.now() },
328+
state: { dataUpdatedAt: Date.now(), data: '' },
329329
}),
330330
)
331331

packages/query-persist-client-core/src/createPersister.ts

+66-33
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,12 @@ export function experimental_createPersister<TStorageValue = string>({
9191
prefix = PERSISTER_KEY_PREFIX,
9292
filters,
9393
}: StoragePersisterOptions<TStorageValue>) {
94-
return async function persisterFn<T, TQueryKey extends QueryKey>(
95-
queryFn: (context: QueryFunctionContext<TQueryKey>) => T | Promise<T>,
96-
context: QueryFunctionContext<TQueryKey>,
97-
query: Query,
94+
async function restoreQuery<T>(
95+
queryHash: string,
96+
afterRestoreMacroTask?: (persistedQuery: PersistedQuery) => void,
9897
) {
99-
const storageKey = `${prefix}-${query.queryHash}`
100-
const matchesFilter = filters ? matchQuery(filters, query) : true
101-
102-
// Try to restore only if we do not have any data in the cache and we have persister defined
103-
if (matchesFilter && query.state.data === undefined && storage != null) {
98+
if (storage != null) {
99+
const storageKey = `${prefix}-${queryHash}`
104100
try {
105101
const storedData = await storage.getItem(storageKey)
106102
if (storedData) {
@@ -113,20 +109,12 @@ export function experimental_createPersister<TStorageValue = string>({
113109
if (expired || busted) {
114110
await storage.removeItem(storageKey)
115111
} else {
116-
// Just after restoring we want to get fresh data from the server if it's stale
117-
setTimeout(() => {
118-
// Set proper updatedAt, since resolving in the first pass overrides those values
119-
query.setState({
120-
dataUpdatedAt: persistedQuery.state.dataUpdatedAt,
121-
errorUpdatedAt: persistedQuery.state.errorUpdatedAt,
122-
})
123-
124-
if (query.isStale()) {
125-
query.fetch()
126-
}
127-
}, 0)
112+
if (afterRestoreMacroTask) {
113+
// Just after restoring we want to get fresh data from the server if it's stale
114+
setTimeout(() => afterRestoreMacroTask(persistedQuery), 0)
115+
}
128116
// We must resolve the promise here, as otherwise we will have `loading` state in the app until `queryFn` resolves
129-
return Promise.resolve(persistedQuery.state.data as T)
117+
return persistedQuery.state.data as T
130118
}
131119
} else {
132120
await storage.removeItem(storageKey)
@@ -143,24 +131,69 @@ export function experimental_createPersister<TStorageValue = string>({
143131
}
144132
}
145133

134+
return
135+
}
136+
137+
async function persistQuery(query: Query) {
138+
if (storage != null) {
139+
const storageKey = `${prefix}-${query.queryHash}`
140+
storage.setItem(
141+
storageKey,
142+
await serialize({
143+
state: query.state,
144+
queryKey: query.queryKey,
145+
queryHash: query.queryHash,
146+
buster: buster,
147+
}),
148+
)
149+
}
150+
}
151+
152+
async function persisterFn<T, TQueryKey extends QueryKey>(
153+
queryFn: (context: QueryFunctionContext<TQueryKey>) => T | Promise<T>,
154+
ctx: QueryFunctionContext<TQueryKey>,
155+
query: Query,
156+
) {
157+
const matchesFilter = filters ? matchQuery(filters, query) : true
158+
159+
// Try to restore only if we do not have any data in the cache and we have persister defined
160+
if (matchesFilter && query.state.data === undefined && storage != null) {
161+
const restoredData = await restoreQuery(
162+
query.queryHash,
163+
(persistedQuery: PersistedQuery) => {
164+
// Set proper updatedAt, since resolving in the first pass overrides those values
165+
query.setState({
166+
dataUpdatedAt: persistedQuery.state.dataUpdatedAt,
167+
errorUpdatedAt: persistedQuery.state.errorUpdatedAt,
168+
})
169+
170+
if (query.isStale()) {
171+
query.fetch()
172+
}
173+
},
174+
)
175+
176+
if (restoredData != null) {
177+
return Promise.resolve(restoredData as T)
178+
}
179+
}
180+
146181
// If we did not restore, or restoration failed - fetch
147-
const queryFnResult = await queryFn(context)
182+
const queryFnResult = await queryFn(ctx)
148183

149184
if (matchesFilter && storage != null) {
150185
// Persist if we have storage defined, we use timeout to get proper state to be persisted
151-
setTimeout(async () => {
152-
storage.setItem(
153-
storageKey,
154-
await serialize({
155-
state: query.state,
156-
queryKey: query.queryKey,
157-
queryHash: query.queryHash,
158-
buster: buster,
159-
}),
160-
)
186+
setTimeout(() => {
187+
persistQuery(query)
161188
}, 0)
162189
}
163190

164191
return Promise.resolve(queryFnResult)
165192
}
193+
194+
return {
195+
persisterFn,
196+
persistQuery,
197+
restoreQuery,
198+
}
166199
}

0 commit comments

Comments
 (0)