@tanstack/query-core 5.76.0 → 5.77.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.
Files changed (100) hide show
  1. package/build/legacy/{hydration-BaHDIfRR.d.ts → hydration-B3ndIyL6.d.ts} +1 -0
  2. package/build/legacy/{hydration-n7FlH3vr.d.cts → hydration-GTiXepW_.d.cts} +1 -0
  3. package/build/legacy/hydration.cjs +48 -33
  4. package/build/legacy/hydration.cjs.map +1 -1
  5. package/build/legacy/hydration.d.cts +1 -1
  6. package/build/legacy/hydration.d.ts +1 -1
  7. package/build/legacy/hydration.js +48 -33
  8. package/build/legacy/hydration.js.map +1 -1
  9. package/build/legacy/index.d.cts +1 -1
  10. package/build/legacy/index.d.ts +1 -1
  11. package/build/legacy/infiniteQueryBehavior.d.cts +1 -1
  12. package/build/legacy/infiniteQueryBehavior.d.ts +1 -1
  13. package/build/legacy/infiniteQueryObserver.d.cts +1 -1
  14. package/build/legacy/infiniteQueryObserver.d.ts +1 -1
  15. package/build/legacy/mutation.d.cts +1 -1
  16. package/build/legacy/mutation.d.ts +1 -1
  17. package/build/legacy/mutationCache.d.cts +1 -1
  18. package/build/legacy/mutationCache.d.ts +1 -1
  19. package/build/legacy/mutationObserver.d.cts +1 -1
  20. package/build/legacy/mutationObserver.d.ts +1 -1
  21. package/build/legacy/queriesObserver.d.cts +1 -1
  22. package/build/legacy/queriesObserver.d.ts +1 -1
  23. package/build/legacy/query.d.cts +1 -1
  24. package/build/legacy/query.d.ts +1 -1
  25. package/build/legacy/queryCache.d.cts +1 -1
  26. package/build/legacy/queryCache.d.ts +1 -1
  27. package/build/legacy/queryClient.d.cts +1 -1
  28. package/build/legacy/queryClient.d.ts +1 -1
  29. package/build/legacy/queryObserver.d.cts +1 -1
  30. package/build/legacy/queryObserver.d.ts +1 -1
  31. package/build/legacy/retryer.d.cts +1 -1
  32. package/build/legacy/retryer.d.ts +1 -1
  33. package/build/legacy/streamedQuery.cjs +6 -4
  34. package/build/legacy/streamedQuery.cjs.map +1 -1
  35. package/build/legacy/streamedQuery.d.cts +8 -3
  36. package/build/legacy/streamedQuery.d.ts +8 -3
  37. package/build/legacy/streamedQuery.js +6 -4
  38. package/build/legacy/streamedQuery.js.map +1 -1
  39. package/build/legacy/thenable.cjs +17 -2
  40. package/build/legacy/thenable.cjs.map +1 -1
  41. package/build/legacy/thenable.d.cts +12 -1
  42. package/build/legacy/thenable.d.ts +12 -1
  43. package/build/legacy/thenable.js +15 -1
  44. package/build/legacy/thenable.js.map +1 -1
  45. package/build/legacy/types.d.cts +1 -1
  46. package/build/legacy/types.d.ts +1 -1
  47. package/build/legacy/utils.d.cts +1 -1
  48. package/build/legacy/utils.d.ts +1 -1
  49. package/build/modern/{hydration-BaHDIfRR.d.ts → hydration-B3ndIyL6.d.ts} +1 -0
  50. package/build/modern/{hydration-n7FlH3vr.d.cts → hydration-GTiXepW_.d.cts} +1 -0
  51. package/build/modern/hydration.cjs +47 -32
  52. package/build/modern/hydration.cjs.map +1 -1
  53. package/build/modern/hydration.d.cts +1 -1
  54. package/build/modern/hydration.d.ts +1 -1
  55. package/build/modern/hydration.js +47 -32
  56. package/build/modern/hydration.js.map +1 -1
  57. package/build/modern/index.d.cts +1 -1
  58. package/build/modern/index.d.ts +1 -1
  59. package/build/modern/infiniteQueryBehavior.d.cts +1 -1
  60. package/build/modern/infiniteQueryBehavior.d.ts +1 -1
  61. package/build/modern/infiniteQueryObserver.d.cts +1 -1
  62. package/build/modern/infiniteQueryObserver.d.ts +1 -1
  63. package/build/modern/mutation.d.cts +1 -1
  64. package/build/modern/mutation.d.ts +1 -1
  65. package/build/modern/mutationCache.d.cts +1 -1
  66. package/build/modern/mutationCache.d.ts +1 -1
  67. package/build/modern/mutationObserver.d.cts +1 -1
  68. package/build/modern/mutationObserver.d.ts +1 -1
  69. package/build/modern/queriesObserver.d.cts +1 -1
  70. package/build/modern/queriesObserver.d.ts +1 -1
  71. package/build/modern/query.d.cts +1 -1
  72. package/build/modern/query.d.ts +1 -1
  73. package/build/modern/queryCache.d.cts +1 -1
  74. package/build/modern/queryCache.d.ts +1 -1
  75. package/build/modern/queryClient.d.cts +1 -1
  76. package/build/modern/queryClient.d.ts +1 -1
  77. package/build/modern/queryObserver.d.cts +1 -1
  78. package/build/modern/queryObserver.d.ts +1 -1
  79. package/build/modern/retryer.d.cts +1 -1
  80. package/build/modern/retryer.d.ts +1 -1
  81. package/build/modern/streamedQuery.cjs +6 -4
  82. package/build/modern/streamedQuery.cjs.map +1 -1
  83. package/build/modern/streamedQuery.d.cts +8 -3
  84. package/build/modern/streamedQuery.d.ts +8 -3
  85. package/build/modern/streamedQuery.js +6 -4
  86. package/build/modern/streamedQuery.js.map +1 -1
  87. package/build/modern/thenable.cjs +16 -2
  88. package/build/modern/thenable.cjs.map +1 -1
  89. package/build/modern/thenable.d.cts +12 -1
  90. package/build/modern/thenable.d.ts +12 -1
  91. package/build/modern/thenable.js +14 -1
  92. package/build/modern/thenable.js.map +1 -1
  93. package/build/modern/types.d.cts +1 -1
  94. package/build/modern/types.d.ts +1 -1
  95. package/build/modern/utils.d.cts +1 -1
  96. package/build/modern/utils.d.ts +1 -1
  97. package/package.json +1 -1
  98. package/src/hydration.ts +73 -46
  99. package/src/streamedQuery.ts +11 -4
  100. package/src/thenable.ts +29 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/query-core",
3
- "version": "5.76.0",
3
+ "version": "5.77.0",
4
4
  "description": "The framework agnostic core that powers TanStack Query",
5
5
  "author": "tannerlinsley",
6
6
  "license": "MIT",
package/src/hydration.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { tryResolveSync } from './thenable'
1
2
  import type {
2
3
  DefaultError,
3
4
  MutationKey,
@@ -46,6 +47,10 @@ interface DehydratedQuery {
46
47
  state: QueryState
47
48
  promise?: Promise<unknown>
48
49
  meta?: QueryMeta
50
+ // This is only optional because older versions of Query might have dehydrated
51
+ // without it which we need to handle for backwards compatibility.
52
+ // This should be changed to required in the future.
53
+ dehydratedAt?: number
49
54
  }
50
55
 
51
56
  export interface DehydratedState {
@@ -74,6 +79,7 @@ function dehydrateQuery(
74
79
  shouldRedactErrors: (error: unknown) => boolean,
75
80
  ): DehydratedQuery {
76
81
  return {
82
+ dehydratedAt: Date.now(),
77
83
  state: {
78
84
  ...query.state,
79
85
  ...(query.state.data !== undefined && {
@@ -189,52 +195,73 @@ export function hydrate(
189
195
  )
190
196
  })
191
197
 
192
- queries.forEach(({ queryKey, state, queryHash, meta, promise }) => {
193
- let query = queryCache.get(queryHash)
194
-
195
- const data =
196
- state.data === undefined ? state.data : deserializeData(state.data)
197
-
198
- // Do not hydrate if an existing query exists with newer data
199
- if (query) {
200
- if (query.state.dataUpdatedAt < state.dataUpdatedAt) {
201
- // omit fetchStatus from dehydrated state
202
- // so that query stays in its current fetchStatus
203
- const { fetchStatus: _ignored, ...serializedState } = state
204
- query.setState({
205
- ...serializedState,
206
- data,
198
+ queries.forEach(
199
+ ({ queryKey, state, queryHash, meta, promise, dehydratedAt }) => {
200
+ const syncData = promise ? tryResolveSync(promise) : undefined
201
+ const rawData = state.data === undefined ? syncData?.data : state.data
202
+ const data = rawData === undefined ? rawData : deserializeData(rawData)
203
+
204
+ let query = queryCache.get(queryHash)
205
+ const existingQueryIsPending = query?.state.status === 'pending'
206
+
207
+ // Do not hydrate if an existing query exists with newer data
208
+ if (query) {
209
+ const hasNewerSyncData =
210
+ syncData &&
211
+ // We only need this undefined check to handle older dehydration
212
+ // payloads that might not have dehydratedAt
213
+ dehydratedAt !== undefined &&
214
+ dehydratedAt > query.state.dataUpdatedAt
215
+ if (
216
+ state.dataUpdatedAt > query.state.dataUpdatedAt ||
217
+ hasNewerSyncData
218
+ ) {
219
+ // omit fetchStatus from dehydrated state
220
+ // so that query stays in its current fetchStatus
221
+ const { fetchStatus: _ignored, ...serializedState } = state
222
+ query.setState({
223
+ ...serializedState,
224
+ data,
225
+ })
226
+ }
227
+ } else {
228
+ // Restore query
229
+ query = queryCache.build(
230
+ client,
231
+ {
232
+ ...client.getDefaultOptions().hydrate?.queries,
233
+ ...options?.defaultOptions?.queries,
234
+ queryKey,
235
+ queryHash,
236
+ meta,
237
+ },
238
+ // Reset fetch status to idle to avoid
239
+ // query being stuck in fetching state upon hydration
240
+ {
241
+ ...state,
242
+ data,
243
+ fetchStatus: 'idle',
244
+ status: data !== undefined ? 'success' : state.status,
245
+ },
246
+ )
247
+ }
248
+
249
+ if (
250
+ promise &&
251
+ !existingQueryIsPending &&
252
+ // Only hydrate if dehydration is newer than any existing data,
253
+ // this is always true for new queries
254
+ (dehydratedAt === undefined || dehydratedAt > query.state.dataUpdatedAt)
255
+ ) {
256
+ // This doesn't actually fetch - it just creates a retryer
257
+ // which will re-use the passed `initialPromise`
258
+ // Note that we need to call these even when data was synchronously
259
+ // available, as we still need to set up the retryer
260
+ void query.fetch(undefined, {
261
+ // RSC transformed promises are not thenable
262
+ initialPromise: Promise.resolve(promise).then(deserializeData),
207
263
  })
208
264
  }
209
- } else {
210
- // Restore query
211
- query = queryCache.build(
212
- client,
213
- {
214
- ...client.getDefaultOptions().hydrate?.queries,
215
- ...options?.defaultOptions?.queries,
216
- queryKey,
217
- queryHash,
218
- meta,
219
- },
220
- // Reset fetch status to idle to avoid
221
- // query being stuck in fetching state upon hydration
222
- {
223
- ...state,
224
- data,
225
- fetchStatus: 'idle',
226
- },
227
- )
228
- }
229
-
230
- if (promise) {
231
- // Note: `Promise.resolve` required cause
232
- // RSC transformed promises are not thenable
233
- const initialPromise = Promise.resolve(promise).then(deserializeData)
234
-
235
- // this doesn't actually fetch - it just creates a retryer
236
- // which will re-use the passed `initialPromise`
237
- void query.fetch(undefined, { initialPromise })
238
- }
239
- })
265
+ },
266
+ )
240
267
  }
@@ -1,3 +1,4 @@
1
+ import { addToEnd } from './utils'
1
2
  import type { QueryFunction, QueryFunctionContext, QueryKey } from './types'
2
3
 
3
4
  /**
@@ -9,7 +10,11 @@ import type { QueryFunction, QueryFunctionContext, QueryKey } from './types'
9
10
  * @param refetchMode - Defines how re-fetches are handled.
10
11
  * Defaults to `'reset'`, erases all data and puts the query back into `pending` state.
11
12
  * Set to `'append'` to append new data to the existing data.
12
- * Set to `'replace'` to write the data to the cache at the end of the stream.
13
+ * Set to `'replace'` to write all data to the cache once the stream ends.
14
+ * @param maxChunks - The maximum number of chunks to keep in the cache.
15
+ * Defaults to `undefined`, meaning all chunks will be kept.
16
+ * If `undefined` or `0`, the number of chunks is unlimited.
17
+ * If the number of chunks exceeds this number, the oldest chunk will be removed.
13
18
  */
14
19
  export function streamedQuery<
15
20
  TQueryFnData = unknown,
@@ -17,11 +22,13 @@ export function streamedQuery<
17
22
  >({
18
23
  queryFn,
19
24
  refetchMode = 'reset',
25
+ maxChunks,
20
26
  }: {
21
27
  queryFn: (
22
28
  context: QueryFunctionContext<TQueryKey>,
23
29
  ) => AsyncIterable<TQueryFnData> | Promise<AsyncIterable<TQueryFnData>>
24
30
  refetchMode?: 'append' | 'reset' | 'replace'
31
+ maxChunks?: number
25
32
  }): QueryFunction<Array<TQueryFnData>, TQueryKey> {
26
33
  return async (context) => {
27
34
  const query = context.client
@@ -38,7 +45,7 @@ export function streamedQuery<
38
45
  })
39
46
  }
40
47
 
41
- const result: Array<TQueryFnData> = []
48
+ let result: Array<TQueryFnData> = []
42
49
  const stream = await queryFn(context)
43
50
 
44
51
  for await (const chunk of stream) {
@@ -51,11 +58,11 @@ export function streamedQuery<
51
58
  context.client.setQueryData<Array<TQueryFnData>>(
52
59
  context.queryKey,
53
60
  (prev = []) => {
54
- return prev.concat([chunk])
61
+ return addToEnd(prev, chunk, maxChunks)
55
62
  },
56
63
  )
57
64
  }
58
- result.push(chunk)
65
+ result = addToEnd(result, chunk, maxChunks)
59
66
  }
60
67
 
61
68
  // finalize result: replace-refetching needs to write to the cache
package/src/thenable.ts CHANGED
@@ -7,6 +7,8 @@
7
7
  * @see https://github.com/facebook/react/blob/4f604941569d2e8947ce1460a0b2997e835f37b9/packages/react-debug-tools/src/ReactDebugHooks.js#L224-L227
8
8
  */
9
9
 
10
+ import { noop } from './utils'
11
+
10
12
  interface Fulfilled<T> {
11
13
  status: 'fulfilled'
12
14
  value: T
@@ -80,3 +82,30 @@ export function pendingThenable<T>(): PendingThenable<T> {
80
82
 
81
83
  return thenable
82
84
  }
85
+
86
+ /**
87
+ * This function takes a Promise-like input and detects whether the data
88
+ * is synchronously available or not.
89
+ *
90
+ * It does not inspect .status, .value or .reason properties of the promise,
91
+ * as those are not always available, and the .status of React's promises
92
+ * should not be considered part of the public API.
93
+ */
94
+ export function tryResolveSync(promise: Promise<unknown> | Thenable<unknown>) {
95
+ let data: unknown
96
+
97
+ promise
98
+ .then((result) => {
99
+ data = result
100
+ return result
101
+ })
102
+ // .catch can be unavailable on certain kinds of thenable's
103
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
104
+ ?.catch(noop)
105
+
106
+ if (data !== undefined) {
107
+ return { data }
108
+ }
109
+
110
+ return undefined
111
+ }