@tanstack/query-core 5.38.0 → 5.40.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-BvcshvE9.d.cts → hydration-DAs3cQH5.d.cts} +44 -3
- package/build/legacy/{types-BtrVwz9w.d.ts → hydration-yBB_smkL.d.ts} +44 -3
- 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.d.cts +1 -1
- package/build/legacy/queryCache.d.ts +1 -1
- package/build/legacy/queryClient.d.cts +1 -1
- package/build/legacy/queryClient.d.ts +1 -1
- 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.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 +18 -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 +17 -0
- package/build/legacy/utils.js.map +1 -1
- package/build/modern/{types-BvcshvE9.d.cts → hydration-DAs3cQH5.d.cts} +44 -3
- package/build/modern/{types-BtrVwz9w.d.ts → hydration-yBB_smkL.d.ts} +44 -3
- 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.d.cts +1 -1
- package/build/modern/queryCache.d.ts +1 -1
- package/build/modern/queryClient.d.cts +1 -1
- package/build/modern/queryClient.d.ts +1 -1
- 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.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 +18 -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 +17 -0
- package/build/modern/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/hydration.test.tsx +170 -0
- package/src/hydration.ts +43 -21
- package/src/infiniteQueryBehavior.ts +2 -17
- package/src/query.ts +24 -29
- package/src/queryObserver.ts +1 -1
- package/src/retryer.ts +6 -1
- package/src/types.ts +3 -0
- package/src/utils.ts +36 -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
|
})
|
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/queryObserver.ts
CHANGED
|
@@ -318,7 +318,7 @@ export class QueryObserver<
|
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
#executeFetch(
|
|
321
|
-
fetchOptions?: ObserverFetchOptions,
|
|
321
|
+
fetchOptions?: Omit<ObserverFetchOptions, 'initialPromise'>,
|
|
322
322
|
): Promise<TQueryData | undefined> {
|
|
323
323
|
// Make sure we reference the latest query as the current one might have been removed
|
|
324
324
|
this.#updateQuery()
|
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'
|
|
@@ -1119,6 +1120,8 @@ export interface DefaultOptions<TError = DefaultError> {
|
|
|
1119
1120
|
'suspense' | 'queryKey'
|
|
1120
1121
|
>
|
|
1121
1122
|
mutations?: MutationObserverOptions<unknown, TError, unknown, unknown>
|
|
1123
|
+
hydrate?: HydrateOptions['defaultOptions']
|
|
1124
|
+
dehydrate?: DehydrateOptions
|
|
1122
1125
|
}
|
|
1123
1126
|
|
|
1124
1127
|
export interface CancelOptions {
|
package/src/utils.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import type { Mutation } from './mutation'
|
|
2
|
-
import type { Query } from './query'
|
|
3
1
|
import type {
|
|
4
2
|
FetchStatus,
|
|
5
3
|
MutationKey,
|
|
6
4
|
MutationStatus,
|
|
5
|
+
QueryFunction,
|
|
7
6
|
QueryKey,
|
|
8
7
|
QueryOptions,
|
|
9
8
|
} from './types'
|
|
9
|
+
import type { Mutation } from './mutation'
|
|
10
|
+
import type { FetchOptions, Query } from './query'
|
|
10
11
|
|
|
11
12
|
// TYPES
|
|
12
13
|
|
|
@@ -349,3 +350,36 @@ export function addToStart<T>(items: Array<T>, item: T, max = 0): Array<T> {
|
|
|
349
350
|
|
|
350
351
|
export const skipToken = Symbol()
|
|
351
352
|
export type SkipToken = typeof skipToken
|
|
353
|
+
|
|
354
|
+
export const ensureQueryFn = <
|
|
355
|
+
TQueryFnData = unknown,
|
|
356
|
+
TQueryKey extends QueryKey = QueryKey,
|
|
357
|
+
>(
|
|
358
|
+
options: {
|
|
359
|
+
queryFn?: QueryFunction<TQueryFnData, TQueryKey> | SkipToken
|
|
360
|
+
queryHash?: string
|
|
361
|
+
},
|
|
362
|
+
fetchOptions?: FetchOptions<TQueryFnData>,
|
|
363
|
+
): QueryFunction<TQueryFnData, TQueryKey> => {
|
|
364
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
365
|
+
if (options.queryFn === skipToken) {
|
|
366
|
+
console.error(
|
|
367
|
+
`Attempted to invoke queryFn when set to skipToken. This is likely a configuration error. Query hash: '${options.queryHash}'`,
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// if we attempt to retry a fetch that was triggered from an initialPromise
|
|
373
|
+
// when we don't have a queryFn yet, we can't retry, so we just return the already rejected initialPromise
|
|
374
|
+
// if an observer has already mounted, we will be able to retry with that queryFn
|
|
375
|
+
if (!options.queryFn && fetchOptions?.initialPromise) {
|
|
376
|
+
return () => fetchOptions.initialPromise!
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (!options.queryFn || options.queryFn === skipToken) {
|
|
380
|
+
return () =>
|
|
381
|
+
Promise.reject(new Error(`Missing queryFn: '${options.queryHash}'`))
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return options.queryFn
|
|
385
|
+
}
|