@tanstack/query-core 5.38.0 → 5.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/legacy/{types-BtrVwz9w.d.ts → hydration-DJZYTIMr.d.ts} +50 -6
- package/build/legacy/{types-BvcshvE9.d.cts → hydration-XP7CH-6g.d.cts} +50 -6
- package/build/legacy/hydration.cjs +40 -23
- package/build/legacy/hydration.cjs.map +1 -1
- package/build/legacy/hydration.d.cts +1 -34
- package/build/legacy/hydration.d.ts +1 -34
- package/build/legacy/hydration.js +40 -23
- package/build/legacy/hydration.js.map +1 -1
- package/build/legacy/index.d.cts +1 -2
- package/build/legacy/index.d.ts +1 -2
- package/build/legacy/infiniteQueryBehavior.cjs +1 -12
- package/build/legacy/infiniteQueryBehavior.cjs.map +1 -1
- package/build/legacy/infiniteQueryBehavior.d.cts +1 -1
- package/build/legacy/infiniteQueryBehavior.d.ts +1 -1
- package/build/legacy/infiniteQueryBehavior.js +2 -13
- package/build/legacy/infiniteQueryBehavior.js.map +1 -1
- package/build/legacy/infiniteQueryObserver.d.cts +1 -1
- package/build/legacy/infiniteQueryObserver.d.ts +1 -1
- package/build/legacy/mutation.d.cts +1 -1
- package/build/legacy/mutation.d.ts +1 -1
- package/build/legacy/mutationCache.d.cts +1 -1
- package/build/legacy/mutationCache.d.ts +1 -1
- package/build/legacy/mutationObserver.d.cts +1 -1
- package/build/legacy/mutationObserver.d.ts +1 -1
- package/build/legacy/queriesObserver.d.cts +1 -1
- package/build/legacy/queriesObserver.d.ts +1 -1
- package/build/legacy/query.cjs +13 -21
- package/build/legacy/query.cjs.map +1 -1
- package/build/legacy/query.d.cts +1 -1
- package/build/legacy/query.d.ts +1 -1
- package/build/legacy/query.js +14 -22
- package/build/legacy/query.js.map +1 -1
- package/build/legacy/queryCache.cjs.map +1 -1
- package/build/legacy/queryCache.d.cts +1 -1
- package/build/legacy/queryCache.d.ts +1 -1
- package/build/legacy/queryCache.js.map +1 -1
- package/build/legacy/queryClient.cjs +4 -2
- package/build/legacy/queryClient.cjs.map +1 -1
- package/build/legacy/queryClient.d.cts +1 -1
- package/build/legacy/queryClient.d.ts +1 -1
- package/build/legacy/queryClient.js +5 -2
- package/build/legacy/queryClient.js.map +1 -1
- package/build/legacy/queryObserver.cjs +8 -7
- package/build/legacy/queryObserver.cjs.map +1 -1
- package/build/legacy/queryObserver.d.cts +1 -1
- package/build/legacy/queryObserver.d.ts +1 -1
- package/build/legacy/queryObserver.js +9 -7
- package/build/legacy/queryObserver.js.map +1 -1
- package/build/legacy/retryer.cjs +2 -1
- package/build/legacy/retryer.cjs.map +1 -1
- package/build/legacy/retryer.d.cts +1 -1
- package/build/legacy/retryer.d.ts +1 -1
- package/build/legacy/retryer.js +2 -1
- package/build/legacy/retryer.js.map +1 -1
- package/build/legacy/types.cjs.map +1 -1
- package/build/legacy/types.d.cts +1 -1
- package/build/legacy/types.d.ts +1 -1
- package/build/legacy/utils.cjs +23 -0
- package/build/legacy/utils.cjs.map +1 -1
- package/build/legacy/utils.d.cts +1 -1
- package/build/legacy/utils.d.ts +1 -1
- package/build/legacy/utils.js +21 -0
- package/build/legacy/utils.js.map +1 -1
- package/build/modern/{types-BtrVwz9w.d.ts → hydration-DJZYTIMr.d.ts} +50 -6
- package/build/modern/{types-BvcshvE9.d.cts → hydration-XP7CH-6g.d.cts} +50 -6
- package/build/modern/hydration.cjs +35 -20
- package/build/modern/hydration.cjs.map +1 -1
- package/build/modern/hydration.d.cts +1 -34
- package/build/modern/hydration.d.ts +1 -34
- package/build/modern/hydration.js +35 -20
- package/build/modern/hydration.js.map +1 -1
- package/build/modern/index.d.cts +1 -2
- package/build/modern/index.d.ts +1 -2
- package/build/modern/infiniteQueryBehavior.cjs +1 -12
- package/build/modern/infiniteQueryBehavior.cjs.map +1 -1
- package/build/modern/infiniteQueryBehavior.d.cts +1 -1
- package/build/modern/infiniteQueryBehavior.d.ts +1 -1
- package/build/modern/infiniteQueryBehavior.js +2 -13
- package/build/modern/infiniteQueryBehavior.js.map +1 -1
- package/build/modern/infiniteQueryObserver.d.cts +1 -1
- package/build/modern/infiniteQueryObserver.d.ts +1 -1
- package/build/modern/mutation.d.cts +1 -1
- package/build/modern/mutation.d.ts +1 -1
- package/build/modern/mutationCache.d.cts +1 -1
- package/build/modern/mutationCache.d.ts +1 -1
- package/build/modern/mutationObserver.d.cts +1 -1
- package/build/modern/mutationObserver.d.ts +1 -1
- package/build/modern/queriesObserver.d.cts +1 -1
- package/build/modern/queriesObserver.d.ts +1 -1
- package/build/modern/query.cjs +12 -21
- package/build/modern/query.cjs.map +1 -1
- package/build/modern/query.d.cts +1 -1
- package/build/modern/query.d.ts +1 -1
- package/build/modern/query.js +13 -22
- package/build/modern/query.js.map +1 -1
- package/build/modern/queryCache.cjs.map +1 -1
- package/build/modern/queryCache.d.cts +1 -1
- package/build/modern/queryCache.d.ts +1 -1
- package/build/modern/queryCache.js.map +1 -1
- package/build/modern/queryClient.cjs +4 -2
- package/build/modern/queryClient.cjs.map +1 -1
- package/build/modern/queryClient.d.cts +1 -1
- package/build/modern/queryClient.d.ts +1 -1
- package/build/modern/queryClient.js +5 -2
- package/build/modern/queryClient.js.map +1 -1
- package/build/modern/queryObserver.cjs +8 -7
- package/build/modern/queryObserver.cjs.map +1 -1
- package/build/modern/queryObserver.d.cts +1 -1
- package/build/modern/queryObserver.d.ts +1 -1
- package/build/modern/queryObserver.js +9 -7
- package/build/modern/queryObserver.js.map +1 -1
- package/build/modern/retryer.cjs +2 -1
- package/build/modern/retryer.cjs.map +1 -1
- package/build/modern/retryer.d.cts +1 -1
- package/build/modern/retryer.d.ts +1 -1
- package/build/modern/retryer.js +2 -1
- package/build/modern/retryer.js.map +1 -1
- package/build/modern/types.cjs.map +1 -1
- package/build/modern/types.d.cts +1 -1
- package/build/modern/types.d.ts +1 -1
- package/build/modern/utils.cjs +23 -0
- package/build/modern/utils.cjs.map +1 -1
- package/build/modern/utils.d.cts +1 -1
- package/build/modern/utils.d.ts +1 -1
- package/build/modern/utils.js +21 -0
- package/build/modern/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/hydration.test.tsx +170 -0
- package/src/__tests__/queryObserver.test.tsx +26 -0
- package/src/hydration.ts +43 -21
- package/src/infiniteQueryBehavior.ts +2 -17
- package/src/query.ts +24 -29
- package/src/queryCache.ts +6 -1
- package/src/queryClient.ts +5 -2
- package/src/queryObserver.ts +14 -12
- package/src/retryer.ts +6 -1
- package/src/types.ts +13 -2
- package/src/utils.ts +50 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test, vi } from 'vitest'
|
|
2
|
+
import { waitFor } from '@testing-library/react'
|
|
2
3
|
import { QueryCache } from '../queryCache'
|
|
3
4
|
import { dehydrate, hydrate } from '../hydration'
|
|
4
5
|
import { MutationCache } from '../mutationCache'
|
|
@@ -174,6 +175,84 @@ describe('dehydration and rehydration', () => {
|
|
|
174
175
|
hydrationClient.clear()
|
|
175
176
|
})
|
|
176
177
|
|
|
178
|
+
test('should respect query defaultOptions specified on the QueryClient', async () => {
|
|
179
|
+
const queryCache = new QueryCache()
|
|
180
|
+
const queryClient = createQueryClient({
|
|
181
|
+
queryCache,
|
|
182
|
+
defaultOptions: {
|
|
183
|
+
dehydrate: { shouldDehydrateQuery: () => true },
|
|
184
|
+
},
|
|
185
|
+
})
|
|
186
|
+
await queryClient.prefetchQuery({
|
|
187
|
+
queryKey: ['string'],
|
|
188
|
+
retry: 0,
|
|
189
|
+
queryFn: () => Promise.reject(new Error('error')),
|
|
190
|
+
})
|
|
191
|
+
const dehydrated = dehydrate(queryClient)
|
|
192
|
+
expect(dehydrated.queries.length).toBe(1)
|
|
193
|
+
expect(dehydrated.queries[0]?.state.error).toStrictEqual(new Error('error'))
|
|
194
|
+
const stringified = JSON.stringify(dehydrated)
|
|
195
|
+
const parsed = JSON.parse(stringified)
|
|
196
|
+
const hydrationCache = new QueryCache()
|
|
197
|
+
const hydrationClient = createQueryClient({
|
|
198
|
+
queryCache: hydrationCache,
|
|
199
|
+
defaultOptions: { hydrate: { queries: { retry: 10 } } },
|
|
200
|
+
})
|
|
201
|
+
hydrate(hydrationClient, parsed, {
|
|
202
|
+
defaultOptions: { queries: { gcTime: 10 } },
|
|
203
|
+
})
|
|
204
|
+
expect(hydrationCache.find({ queryKey: ['string'] })?.options.retry).toBe(
|
|
205
|
+
10,
|
|
206
|
+
)
|
|
207
|
+
expect(hydrationCache.find({ queryKey: ['string'] })?.options.gcTime).toBe(
|
|
208
|
+
10,
|
|
209
|
+
)
|
|
210
|
+
queryClient.clear()
|
|
211
|
+
hydrationClient.clear()
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
test('should respect mutation defaultOptions specified on the QueryClient', async () => {
|
|
215
|
+
const mutationCache = new MutationCache()
|
|
216
|
+
const queryClient = createQueryClient({
|
|
217
|
+
mutationCache,
|
|
218
|
+
defaultOptions: {
|
|
219
|
+
dehydrate: {
|
|
220
|
+
shouldDehydrateMutation: (mutation) => mutation.state.data === 'done',
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
})
|
|
224
|
+
await executeMutation(
|
|
225
|
+
queryClient,
|
|
226
|
+
{
|
|
227
|
+
mutationKey: ['string'],
|
|
228
|
+
mutationFn: () => Promise.resolve('done'),
|
|
229
|
+
},
|
|
230
|
+
undefined,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
const dehydrated = dehydrate(queryClient)
|
|
234
|
+
expect(dehydrated.mutations.length).toBe(1)
|
|
235
|
+
expect(dehydrated.mutations[0]?.state.data).toBe('done')
|
|
236
|
+
const stringified = JSON.stringify(dehydrated)
|
|
237
|
+
const parsed = JSON.parse(stringified)
|
|
238
|
+
const hydrationCache = new MutationCache()
|
|
239
|
+
const hydrationClient = createQueryClient({
|
|
240
|
+
mutationCache: hydrationCache,
|
|
241
|
+
defaultOptions: { hydrate: { mutations: { retry: 10 } } },
|
|
242
|
+
})
|
|
243
|
+
hydrate(hydrationClient, parsed, {
|
|
244
|
+
defaultOptions: { mutations: { gcTime: 10 } },
|
|
245
|
+
})
|
|
246
|
+
expect(
|
|
247
|
+
hydrationCache.find({ mutationKey: ['string'] })?.options.retry,
|
|
248
|
+
).toBe(10)
|
|
249
|
+
expect(
|
|
250
|
+
hydrationCache.find({ mutationKey: ['string'] })?.options.gcTime,
|
|
251
|
+
).toBe(10)
|
|
252
|
+
queryClient.clear()
|
|
253
|
+
hydrationClient.clear()
|
|
254
|
+
})
|
|
255
|
+
|
|
177
256
|
test('should work with complex keys', async () => {
|
|
178
257
|
const queryCache = new QueryCache()
|
|
179
258
|
const queryClient = createQueryClient({ queryCache })
|
|
@@ -738,4 +817,95 @@ describe('dehydration and rehydration', () => {
|
|
|
738
817
|
|
|
739
818
|
onlineMock.mockRestore()
|
|
740
819
|
})
|
|
820
|
+
|
|
821
|
+
test('should dehydrate promises for pending queries', async () => {
|
|
822
|
+
const queryCache = new QueryCache()
|
|
823
|
+
const queryClient = createQueryClient({
|
|
824
|
+
queryCache,
|
|
825
|
+
defaultOptions: { dehydrate: { shouldDehydrateQuery: () => true } },
|
|
826
|
+
})
|
|
827
|
+
await queryClient.prefetchQuery({
|
|
828
|
+
queryKey: ['success'],
|
|
829
|
+
queryFn: () => fetchData('success'),
|
|
830
|
+
})
|
|
831
|
+
|
|
832
|
+
const promise = queryClient.prefetchQuery({
|
|
833
|
+
queryKey: ['pending'],
|
|
834
|
+
queryFn: () => fetchData('pending', 10),
|
|
835
|
+
})
|
|
836
|
+
const dehydrated = dehydrate(queryClient)
|
|
837
|
+
|
|
838
|
+
expect(dehydrated.queries[0]?.promise).toBeUndefined()
|
|
839
|
+
expect(dehydrated.queries[1]?.promise).toBeInstanceOf(Promise)
|
|
840
|
+
|
|
841
|
+
await promise
|
|
842
|
+
queryClient.clear()
|
|
843
|
+
})
|
|
844
|
+
|
|
845
|
+
test('should hydrate promises even without observers', async () => {
|
|
846
|
+
const queryCache = new QueryCache()
|
|
847
|
+
const queryClient = createQueryClient({
|
|
848
|
+
queryCache,
|
|
849
|
+
defaultOptions: { dehydrate: { shouldDehydrateQuery: () => true } },
|
|
850
|
+
})
|
|
851
|
+
await queryClient.prefetchQuery({
|
|
852
|
+
queryKey: ['success'],
|
|
853
|
+
queryFn: () => fetchData('success'),
|
|
854
|
+
})
|
|
855
|
+
|
|
856
|
+
void queryClient.prefetchQuery({
|
|
857
|
+
queryKey: ['pending'],
|
|
858
|
+
queryFn: () => fetchData('pending', 20),
|
|
859
|
+
})
|
|
860
|
+
const dehydrated = dehydrate(queryClient)
|
|
861
|
+
// no stringify/parse here because promises can't be serialized to json
|
|
862
|
+
// but nextJs still can do it
|
|
863
|
+
|
|
864
|
+
const hydrationCache = new QueryCache()
|
|
865
|
+
const hydrationClient = createQueryClient({
|
|
866
|
+
queryCache: hydrationCache,
|
|
867
|
+
})
|
|
868
|
+
|
|
869
|
+
hydrate(hydrationClient, dehydrated)
|
|
870
|
+
|
|
871
|
+
expect(hydrationCache.find({ queryKey: ['success'] })?.state.data).toBe(
|
|
872
|
+
'success',
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
expect(hydrationCache.find({ queryKey: ['pending'] })?.state).toMatchObject(
|
|
876
|
+
{
|
|
877
|
+
data: undefined,
|
|
878
|
+
dataUpdateCount: 0,
|
|
879
|
+
dataUpdatedAt: 0,
|
|
880
|
+
error: null,
|
|
881
|
+
errorUpdateCount: 0,
|
|
882
|
+
errorUpdatedAt: 0,
|
|
883
|
+
fetchFailureCount: 0,
|
|
884
|
+
fetchFailureReason: null,
|
|
885
|
+
fetchMeta: null,
|
|
886
|
+
fetchStatus: 'fetching',
|
|
887
|
+
isInvalidated: false,
|
|
888
|
+
status: 'pending',
|
|
889
|
+
},
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
await waitFor(() =>
|
|
893
|
+
expect(
|
|
894
|
+
hydrationCache.find({ queryKey: ['pending'] })?.state,
|
|
895
|
+
).toMatchObject({
|
|
896
|
+
data: 'pending',
|
|
897
|
+
dataUpdateCount: 1,
|
|
898
|
+
dataUpdatedAt: expect.any(Number),
|
|
899
|
+
error: null,
|
|
900
|
+
errorUpdateCount: 0,
|
|
901
|
+
errorUpdatedAt: 0,
|
|
902
|
+
fetchFailureCount: 0,
|
|
903
|
+
fetchFailureReason: null,
|
|
904
|
+
fetchMeta: null,
|
|
905
|
+
fetchStatus: 'idle',
|
|
906
|
+
isInvalidated: false,
|
|
907
|
+
status: 'success',
|
|
908
|
+
}),
|
|
909
|
+
)
|
|
910
|
+
})
|
|
741
911
|
})
|
|
@@ -910,4 +910,30 @@ describe('queryObserver', () => {
|
|
|
910
910
|
const result = observer.getCurrentResult()
|
|
911
911
|
expect(result.isStale).toBe(false)
|
|
912
912
|
})
|
|
913
|
+
|
|
914
|
+
test('should allow staleTime as a function', async () => {
|
|
915
|
+
const key = queryKey()
|
|
916
|
+
const observer = new QueryObserver(queryClient, {
|
|
917
|
+
queryKey: key,
|
|
918
|
+
queryFn: async () => {
|
|
919
|
+
await sleep(5)
|
|
920
|
+
return {
|
|
921
|
+
data: 'data',
|
|
922
|
+
staleTime: 20,
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
staleTime: (query) => query.state.data?.staleTime ?? 0,
|
|
926
|
+
})
|
|
927
|
+
const results: Array<QueryObserverResult<unknown>> = []
|
|
928
|
+
const unsubscribe = observer.subscribe((x) => {
|
|
929
|
+
if (x.data) {
|
|
930
|
+
results.push(x)
|
|
931
|
+
}
|
|
932
|
+
})
|
|
933
|
+
|
|
934
|
+
await waitFor(() => expect(results[0]?.isStale).toBe(false))
|
|
935
|
+
await waitFor(() => expect(results[1]?.isStale).toBe(true))
|
|
936
|
+
|
|
937
|
+
unsubscribe()
|
|
938
|
+
})
|
|
913
939
|
})
|
package/src/hydration.ts
CHANGED
|
@@ -37,6 +37,7 @@ interface DehydratedQuery {
|
|
|
37
37
|
queryHash: string
|
|
38
38
|
queryKey: QueryKey
|
|
39
39
|
state: QueryState
|
|
40
|
+
promise?: Promise<unknown>
|
|
40
41
|
meta?: QueryMeta
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -65,6 +66,16 @@ function dehydrateQuery(query: Query): DehydratedQuery {
|
|
|
65
66
|
state: query.state,
|
|
66
67
|
queryKey: query.queryKey,
|
|
67
68
|
queryHash: query.queryHash,
|
|
69
|
+
...(query.state.status === 'pending' && {
|
|
70
|
+
promise: query.promise?.catch((error) => {
|
|
71
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
72
|
+
console.error(
|
|
73
|
+
`A query that was dehydrated as pending ended up rejecting. [${query.queryHash}]: ${error}; The error will be redacted in production builds`,
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
return Promise.reject(new Error('redacted'))
|
|
77
|
+
}),
|
|
78
|
+
}),
|
|
68
79
|
...(query.meta && { meta: query.meta }),
|
|
69
80
|
}
|
|
70
81
|
}
|
|
@@ -82,7 +93,9 @@ export function dehydrate(
|
|
|
82
93
|
options: DehydrateOptions = {},
|
|
83
94
|
): DehydratedState {
|
|
84
95
|
const filterMutation =
|
|
85
|
-
options.shouldDehydrateMutation ??
|
|
96
|
+
options.shouldDehydrateMutation ??
|
|
97
|
+
client.getDefaultOptions().dehydrate?.shouldDehydrateMutation ??
|
|
98
|
+
defaultShouldDehydrateMutation
|
|
86
99
|
|
|
87
100
|
const mutations = client
|
|
88
101
|
.getMutationCache()
|
|
@@ -92,7 +105,9 @@ export function dehydrate(
|
|
|
92
105
|
)
|
|
93
106
|
|
|
94
107
|
const filterQuery =
|
|
95
|
-
options.shouldDehydrateQuery ??
|
|
108
|
+
options.shouldDehydrateQuery ??
|
|
109
|
+
client.getDefaultOptions().dehydrate?.shouldDehydrateQuery ??
|
|
110
|
+
defaultShouldDehydrateQuery
|
|
96
111
|
|
|
97
112
|
const queries = client
|
|
98
113
|
.getQueryCache()
|
|
@@ -123,6 +138,7 @@ export function hydrate(
|
|
|
123
138
|
mutationCache.build(
|
|
124
139
|
client,
|
|
125
140
|
{
|
|
141
|
+
...client.getDefaultOptions().hydrate?.mutations,
|
|
126
142
|
...options?.defaultOptions?.mutations,
|
|
127
143
|
...mutationOptions,
|
|
128
144
|
},
|
|
@@ -130,8 +146,8 @@ export function hydrate(
|
|
|
130
146
|
)
|
|
131
147
|
})
|
|
132
148
|
|
|
133
|
-
queries.forEach(({ queryKey, state, queryHash, meta }) => {
|
|
134
|
-
|
|
149
|
+
queries.forEach(({ queryKey, state, queryHash, meta, promise }) => {
|
|
150
|
+
let query = queryCache.get(queryHash)
|
|
135
151
|
|
|
136
152
|
// Do not hydrate if an existing query exists with newer data
|
|
137
153
|
if (query) {
|
|
@@ -141,24 +157,30 @@ export function hydrate(
|
|
|
141
157
|
const { fetchStatus: _ignored, ...dehydratedQueryState } = state
|
|
142
158
|
query.setState(dehydratedQueryState)
|
|
143
159
|
}
|
|
144
|
-
|
|
160
|
+
} else {
|
|
161
|
+
// Restore query
|
|
162
|
+
query = queryCache.build(
|
|
163
|
+
client,
|
|
164
|
+
{
|
|
165
|
+
...client.getDefaultOptions().hydrate?.queries,
|
|
166
|
+
...options?.defaultOptions?.queries,
|
|
167
|
+
queryKey,
|
|
168
|
+
queryHash,
|
|
169
|
+
meta,
|
|
170
|
+
},
|
|
171
|
+
// Reset fetch status to idle to avoid
|
|
172
|
+
// query being stuck in fetching state upon hydration
|
|
173
|
+
{
|
|
174
|
+
...state,
|
|
175
|
+
fetchStatus: 'idle',
|
|
176
|
+
},
|
|
177
|
+
)
|
|
145
178
|
}
|
|
146
179
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
{
|
|
151
|
-
|
|
152
|
-
queryKey,
|
|
153
|
-
queryHash,
|
|
154
|
-
meta,
|
|
155
|
-
},
|
|
156
|
-
// Reset fetch status to idle to avoid
|
|
157
|
-
// query being stuck in fetching state upon hydration
|
|
158
|
-
{
|
|
159
|
-
...state,
|
|
160
|
-
fetchStatus: 'idle',
|
|
161
|
-
},
|
|
162
|
-
)
|
|
180
|
+
if (promise) {
|
|
181
|
+
// this doesn't actually fetch - it just creates a retryer
|
|
182
|
+
// which will re-use the passed `initialPromise`
|
|
183
|
+
void query.fetch(undefined, { initialPromise: promise })
|
|
184
|
+
}
|
|
163
185
|
})
|
|
164
186
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addToEnd, addToStart,
|
|
1
|
+
import { addToEnd, addToStart, ensureQueryFn } from './utils'
|
|
2
2
|
import type { QueryBehavior } from './query'
|
|
3
3
|
import type {
|
|
4
4
|
InfiniteData,
|
|
@@ -37,22 +37,7 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
|
|
|
37
37
|
})
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
const queryFn =
|
|
42
|
-
context.options.queryFn && context.options.queryFn !== skipToken
|
|
43
|
-
? context.options.queryFn
|
|
44
|
-
: () => {
|
|
45
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
46
|
-
if (context.options.queryFn === skipToken) {
|
|
47
|
-
console.error(
|
|
48
|
-
`Attempted to invoke queryFn when set to skipToken. This is likely a configuration error. Query hash: '${context.options.queryHash}'`,
|
|
49
|
-
)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return Promise.reject(
|
|
53
|
-
new Error(`Missing queryFn: '${context.options.queryHash}'`),
|
|
54
|
-
)
|
|
55
|
-
}
|
|
40
|
+
const queryFn = ensureQueryFn(context.options, context.fetchOptions)
|
|
56
41
|
|
|
57
42
|
// Create function to fetch a page
|
|
58
43
|
const fetchPage = async (
|
package/src/query.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { noop, replaceData,
|
|
1
|
+
import { ensureQueryFn, noop, replaceData, timeUntilStale } from './utils'
|
|
2
2
|
import { notifyManager } from './notifyManager'
|
|
3
3
|
import { canFetch, createRetryer, isCancelledError } from './retryer'
|
|
4
4
|
import { Removable } from './removable'
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
FetchStatus,
|
|
9
9
|
InitialDataFunction,
|
|
10
10
|
OmitKeyof,
|
|
11
|
+
QueryFunction,
|
|
11
12
|
QueryFunctionContext,
|
|
12
13
|
QueryKey,
|
|
13
14
|
QueryMeta,
|
|
@@ -82,9 +83,10 @@ export interface FetchMeta {
|
|
|
82
83
|
fetchMore?: { direction: FetchDirection }
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
export interface FetchOptions {
|
|
86
|
+
export interface FetchOptions<TData = unknown> {
|
|
86
87
|
cancelRefetch?: boolean
|
|
87
88
|
meta?: FetchMeta
|
|
89
|
+
initialPromise?: Promise<TData>
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
interface FailedAction<TError> {
|
|
@@ -182,6 +184,10 @@ export class Query<
|
|
|
182
184
|
return this.options.meta
|
|
183
185
|
}
|
|
184
186
|
|
|
187
|
+
get promise(): Promise<TData> | undefined {
|
|
188
|
+
return this.#retryer?.promise
|
|
189
|
+
}
|
|
190
|
+
|
|
185
191
|
setOptions(
|
|
186
192
|
options?: QueryOptions<TQueryFnData, TError, TData, TQueryKey>,
|
|
187
193
|
): void {
|
|
@@ -330,7 +336,7 @@ export class Query<
|
|
|
330
336
|
|
|
331
337
|
fetch(
|
|
332
338
|
options?: QueryOptions<TQueryFnData, TError, TData, TQueryKey>,
|
|
333
|
-
fetchOptions?: FetchOptions
|
|
339
|
+
fetchOptions?: FetchOptions<TQueryFnData>,
|
|
334
340
|
): Promise<TData> {
|
|
335
341
|
if (this.state.fetchStatus !== 'idle') {
|
|
336
342
|
if (this.state.data !== undefined && fetchOptions?.cancelRefetch) {
|
|
@@ -368,15 +374,6 @@ export class Query<
|
|
|
368
374
|
|
|
369
375
|
const abortController = new AbortController()
|
|
370
376
|
|
|
371
|
-
// Create query function context
|
|
372
|
-
const queryFnContext: OmitKeyof<
|
|
373
|
-
QueryFunctionContext<TQueryKey>,
|
|
374
|
-
'signal'
|
|
375
|
-
> = {
|
|
376
|
-
queryKey: this.queryKey,
|
|
377
|
-
meta: this.meta,
|
|
378
|
-
}
|
|
379
|
-
|
|
380
377
|
// Adds an enumerable signal property to the object that
|
|
381
378
|
// which sets abortSignalConsumed to true when the signal
|
|
382
379
|
// is read.
|
|
@@ -390,36 +387,31 @@ export class Query<
|
|
|
390
387
|
})
|
|
391
388
|
}
|
|
392
389
|
|
|
393
|
-
addSignalProperty(queryFnContext)
|
|
394
|
-
|
|
395
390
|
// Create fetch function
|
|
396
391
|
const fetchFn = () => {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
392
|
+
const queryFn = ensureQueryFn(this.options, fetchOptions)
|
|
393
|
+
|
|
394
|
+
// Create query function context
|
|
395
|
+
const queryFnContext: OmitKeyof<
|
|
396
|
+
QueryFunctionContext<TQueryKey>,
|
|
397
|
+
'signal'
|
|
398
|
+
> = {
|
|
399
|
+
queryKey: this.queryKey,
|
|
400
|
+
meta: this.meta,
|
|
403
401
|
}
|
|
404
402
|
|
|
405
|
-
|
|
406
|
-
return Promise.reject(
|
|
407
|
-
new Error(`Missing queryFn: '${this.options.queryHash}'`),
|
|
408
|
-
)
|
|
409
|
-
}
|
|
403
|
+
addSignalProperty(queryFnContext)
|
|
410
404
|
|
|
411
405
|
this.#abortSignalConsumed = false
|
|
412
406
|
if (this.options.persister) {
|
|
413
407
|
return this.options.persister(
|
|
414
|
-
|
|
408
|
+
queryFn as QueryFunction<any>,
|
|
415
409
|
queryFnContext as QueryFunctionContext<TQueryKey>,
|
|
416
410
|
this as unknown as Query,
|
|
417
411
|
)
|
|
418
412
|
}
|
|
419
413
|
|
|
420
|
-
return
|
|
421
|
-
queryFnContext as QueryFunctionContext<TQueryKey>,
|
|
422
|
-
)
|
|
414
|
+
return queryFn(queryFnContext as QueryFunctionContext<TQueryKey>)
|
|
423
415
|
}
|
|
424
416
|
|
|
425
417
|
// Trigger behavior hook
|
|
@@ -483,6 +475,9 @@ export class Query<
|
|
|
483
475
|
|
|
484
476
|
// Try to fetch the data
|
|
485
477
|
this.#retryer = createRetryer({
|
|
478
|
+
initialPromise: fetchOptions?.initialPromise as
|
|
479
|
+
| Promise<TData>
|
|
480
|
+
| undefined,
|
|
486
481
|
fn: context.fetchFn as () => Promise<TData>,
|
|
487
482
|
abort: abortController.abort.bind(abortController),
|
|
488
483
|
onSuccess: (data) => {
|
package/src/queryCache.ts
CHANGED
|
@@ -97,7 +97,12 @@ export class QueryCache extends Subscribable<QueryCacheListener> {
|
|
|
97
97
|
this.#queries = new Map<string, Query>()
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
build<
|
|
100
|
+
build<
|
|
101
|
+
TQueryFnData = unknown,
|
|
102
|
+
TError = DefaultError,
|
|
103
|
+
TData = TQueryFnData,
|
|
104
|
+
TQueryKey extends QueryKey = QueryKey,
|
|
105
|
+
>(
|
|
101
106
|
client: QueryClient,
|
|
102
107
|
options: WithRequired<
|
|
103
108
|
QueryOptions<TQueryFnData, TError, TData, TQueryKey>,
|
package/src/queryClient.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
hashQueryKeyByOptions,
|
|
5
5
|
noop,
|
|
6
6
|
partialMatchKey,
|
|
7
|
+
resolveStaleTime,
|
|
7
8
|
skipToken,
|
|
8
9
|
} from './utils'
|
|
9
10
|
import { QueryCache } from './queryCache'
|
|
@@ -142,7 +143,7 @@ export class QueryClient {
|
|
|
142
143
|
|
|
143
144
|
if (
|
|
144
145
|
options.revalidateIfStale &&
|
|
145
|
-
query.isStaleByTime(defaultedOptions.staleTime)
|
|
146
|
+
query.isStaleByTime(resolveStaleTime(defaultedOptions.staleTime, query))
|
|
146
147
|
) {
|
|
147
148
|
void this.prefetchQuery(defaultedOptions)
|
|
148
149
|
}
|
|
@@ -343,7 +344,9 @@ export class QueryClient {
|
|
|
343
344
|
|
|
344
345
|
const query = this.#queryCache.build(this, defaultedOptions)
|
|
345
346
|
|
|
346
|
-
return query.isStaleByTime(
|
|
347
|
+
return query.isStaleByTime(
|
|
348
|
+
resolveStaleTime(defaultedOptions.staleTime, query),
|
|
349
|
+
)
|
|
347
350
|
? query.fetch(defaultedOptions)
|
|
348
351
|
: Promise.resolve(query.state.data as TData)
|
|
349
352
|
}
|
package/src/queryObserver.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
isValidTimeout,
|
|
4
4
|
noop,
|
|
5
5
|
replaceData,
|
|
6
|
+
resolveStaleTime,
|
|
6
7
|
shallowEqualObjects,
|
|
7
8
|
timeUntilStale,
|
|
8
9
|
} from './utils'
|
|
@@ -190,7 +191,8 @@ export class QueryObserver<
|
|
|
190
191
|
mounted &&
|
|
191
192
|
(this.#currentQuery !== prevQuery ||
|
|
192
193
|
this.options.enabled !== prevOptions.enabled ||
|
|
193
|
-
this.options.staleTime !==
|
|
194
|
+
resolveStaleTime(this.options.staleTime, this.#currentQuery) !==
|
|
195
|
+
resolveStaleTime(prevOptions.staleTime, this.#currentQuery))
|
|
194
196
|
) {
|
|
195
197
|
this.#updateStaleTimeout()
|
|
196
198
|
}
|
|
@@ -318,7 +320,7 @@ export class QueryObserver<
|
|
|
318
320
|
}
|
|
319
321
|
|
|
320
322
|
#executeFetch(
|
|
321
|
-
fetchOptions?: ObserverFetchOptions,
|
|
323
|
+
fetchOptions?: Omit<ObserverFetchOptions, 'initialPromise'>,
|
|
322
324
|
): Promise<TQueryData | undefined> {
|
|
323
325
|
// Make sure we reference the latest query as the current one might have been removed
|
|
324
326
|
this.#updateQuery()
|
|
@@ -338,19 +340,16 @@ export class QueryObserver<
|
|
|
338
340
|
|
|
339
341
|
#updateStaleTimeout(): void {
|
|
340
342
|
this.#clearStaleTimeout()
|
|
343
|
+
const staleTime = resolveStaleTime(
|
|
344
|
+
this.options.staleTime,
|
|
345
|
+
this.#currentQuery,
|
|
346
|
+
)
|
|
341
347
|
|
|
342
|
-
if (
|
|
343
|
-
isServer ||
|
|
344
|
-
this.#currentResult.isStale ||
|
|
345
|
-
!isValidTimeout(this.options.staleTime)
|
|
346
|
-
) {
|
|
348
|
+
if (isServer || this.#currentResult.isStale || !isValidTimeout(staleTime)) {
|
|
347
349
|
return
|
|
348
350
|
}
|
|
349
351
|
|
|
350
|
-
const time = timeUntilStale(
|
|
351
|
-
this.#currentResult.dataUpdatedAt,
|
|
352
|
-
this.options.staleTime,
|
|
353
|
-
)
|
|
352
|
+
const time = timeUntilStale(this.#currentResult.dataUpdatedAt, staleTime)
|
|
354
353
|
|
|
355
354
|
// The timeout is sometimes triggered 1 ms before the stale time expiration.
|
|
356
355
|
// To mitigate this issue we always add 1 ms to the timeout.
|
|
@@ -742,7 +741,10 @@ function isStale(
|
|
|
742
741
|
query: Query<any, any, any, any>,
|
|
743
742
|
options: QueryObserverOptions<any, any, any, any, any>,
|
|
744
743
|
): boolean {
|
|
745
|
-
return
|
|
744
|
+
return (
|
|
745
|
+
options.enabled !== false &&
|
|
746
|
+
query.isStaleByTime(resolveStaleTime(options.staleTime, query))
|
|
747
|
+
)
|
|
746
748
|
}
|
|
747
749
|
|
|
748
750
|
// this function would decide if we will update the observer's 'current'
|
package/src/retryer.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { CancelOptions, DefaultError, NetworkMode } from './types'
|
|
|
7
7
|
|
|
8
8
|
interface RetryerConfig<TData = unknown, TError = DefaultError> {
|
|
9
9
|
fn: () => TData | Promise<TData>
|
|
10
|
+
initialPromise?: Promise<TData>
|
|
10
11
|
abort?: () => void
|
|
11
12
|
onError?: (error: TError) => void
|
|
12
13
|
onSuccess?: (data: TData) => void
|
|
@@ -146,9 +147,13 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
|
|
|
146
147
|
|
|
147
148
|
let promiseOrValue: any
|
|
148
149
|
|
|
150
|
+
// we can re-use config.initialPromise on the first call of run()
|
|
151
|
+
const initialPromise =
|
|
152
|
+
failureCount === 0 ? config.initialPromise : undefined
|
|
153
|
+
|
|
149
154
|
// Execute query
|
|
150
155
|
try {
|
|
151
|
-
promiseOrValue = config.fn()
|
|
156
|
+
promiseOrValue = initialPromise ?? config.fn()
|
|
152
157
|
} catch (error) {
|
|
153
158
|
promiseOrValue = Promise.reject(error)
|
|
154
159
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* istanbul ignore file */
|
|
2
2
|
|
|
3
|
+
import type { DehydrateOptions, HydrateOptions } from './hydration'
|
|
3
4
|
import type { MutationState } from './mutation'
|
|
4
5
|
import type { FetchDirection, Query, QueryBehavior } from './query'
|
|
5
6
|
import type { RetryDelayValue, RetryValue } from './retryer'
|
|
@@ -46,6 +47,13 @@ export type QueryFunction<
|
|
|
46
47
|
TPageParam = never,
|
|
47
48
|
> = (context: QueryFunctionContext<TQueryKey, TPageParam>) => T | Promise<T>
|
|
48
49
|
|
|
50
|
+
export type StaleTime<
|
|
51
|
+
TQueryFnData = unknown,
|
|
52
|
+
TError = DefaultError,
|
|
53
|
+
TData = TQueryFnData,
|
|
54
|
+
TQueryKey extends QueryKey = QueryKey,
|
|
55
|
+
> = number | ((query: Query<TQueryFnData, TError, TData, TQueryKey>) => number)
|
|
56
|
+
|
|
49
57
|
export type QueryPersister<
|
|
50
58
|
T = unknown,
|
|
51
59
|
TQueryKey extends QueryKey = QueryKey,
|
|
@@ -253,8 +261,9 @@ export interface QueryObserverOptions<
|
|
|
253
261
|
/**
|
|
254
262
|
* The time in milliseconds after data is considered stale.
|
|
255
263
|
* If set to `Infinity`, the data will never be considered stale.
|
|
264
|
+
* If set to a function, the function will be executed with the query to compute a `staleTime`.
|
|
256
265
|
*/
|
|
257
|
-
staleTime?:
|
|
266
|
+
staleTime?: StaleTime<TQueryFnData, TError, TQueryData, TQueryKey>
|
|
258
267
|
/**
|
|
259
268
|
* If set to a number, the query will continuously refetch at this frequency in milliseconds.
|
|
260
269
|
* If set to a function, the function will be executed with the latest data and query to compute a frequency
|
|
@@ -426,7 +435,7 @@ export interface FetchQueryOptions<
|
|
|
426
435
|
* The time in milliseconds after data is considered stale.
|
|
427
436
|
* If the data is fresh it will be returned from the cache.
|
|
428
437
|
*/
|
|
429
|
-
staleTime?:
|
|
438
|
+
staleTime?: StaleTime<TQueryFnData, TError, TData, TQueryKey>
|
|
430
439
|
}
|
|
431
440
|
|
|
432
441
|
export interface EnsureQueryDataOptions<
|
|
@@ -1119,6 +1128,8 @@ export interface DefaultOptions<TError = DefaultError> {
|
|
|
1119
1128
|
'suspense' | 'queryKey'
|
|
1120
1129
|
>
|
|
1121
1130
|
mutations?: MutationObserverOptions<unknown, TError, unknown, unknown>
|
|
1131
|
+
hydrate?: HydrateOptions['defaultOptions']
|
|
1132
|
+
dehydrate?: DehydrateOptions
|
|
1122
1133
|
}
|
|
1123
1134
|
|
|
1124
1135
|
export interface CancelOptions {
|