@tanstack/query-core 5.48.0 → 5.49.1

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 (90) hide show
  1. package/build/legacy/focusManager.cjs.map +1 -1
  2. package/build/legacy/focusManager.js.map +1 -1
  3. package/build/legacy/{hydration-CTf4vERU.d.ts → hydration-ByKLEQMr.d.ts} +3 -1
  4. package/build/legacy/{hydration-Cm3oOWor.d.cts → hydration-DfqGmvZi.d.cts} +3 -1
  5. package/build/legacy/hydration.cjs +32 -15
  6. package/build/legacy/hydration.cjs.map +1 -1
  7. package/build/legacy/hydration.d.cts +1 -1
  8. package/build/legacy/hydration.d.ts +1 -1
  9. package/build/legacy/hydration.js +32 -15
  10. package/build/legacy/hydration.js.map +1 -1
  11. package/build/legacy/index.d.cts +1 -1
  12. package/build/legacy/index.d.ts +1 -1
  13. package/build/legacy/infiniteQueryBehavior.d.cts +1 -1
  14. package/build/legacy/infiniteQueryBehavior.d.ts +1 -1
  15. package/build/legacy/infiniteQueryObserver.d.cts +1 -1
  16. package/build/legacy/infiniteQueryObserver.d.ts +1 -1
  17. package/build/legacy/mutation.d.cts +1 -1
  18. package/build/legacy/mutation.d.ts +1 -1
  19. package/build/legacy/mutationCache.d.cts +1 -1
  20. package/build/legacy/mutationCache.d.ts +1 -1
  21. package/build/legacy/mutationObserver.d.cts +1 -1
  22. package/build/legacy/mutationObserver.d.ts +1 -1
  23. package/build/legacy/onlineManager.cjs.map +1 -1
  24. package/build/legacy/onlineManager.js.map +1 -1
  25. package/build/legacy/queriesObserver.d.cts +1 -1
  26. package/build/legacy/queriesObserver.d.ts +1 -1
  27. package/build/legacy/query.d.cts +1 -1
  28. package/build/legacy/query.d.ts +1 -1
  29. package/build/legacy/queryCache.d.cts +1 -1
  30. package/build/legacy/queryCache.d.ts +1 -1
  31. package/build/legacy/queryClient.cjs.map +1 -1
  32. package/build/legacy/queryClient.d.cts +1 -1
  33. package/build/legacy/queryClient.d.ts +1 -1
  34. package/build/legacy/queryClient.js.map +1 -1
  35. package/build/legacy/queryObserver.d.cts +1 -1
  36. package/build/legacy/queryObserver.d.ts +1 -1
  37. package/build/legacy/retryer.d.cts +1 -1
  38. package/build/legacy/retryer.d.ts +1 -1
  39. package/build/legacy/types.d.cts +1 -1
  40. package/build/legacy/types.d.ts +1 -1
  41. package/build/legacy/utils.d.cts +1 -1
  42. package/build/legacy/utils.d.ts +1 -1
  43. package/build/modern/focusManager.cjs.map +1 -1
  44. package/build/modern/focusManager.js.map +1 -1
  45. package/build/modern/{hydration-CTf4vERU.d.ts → hydration-ByKLEQMr.d.ts} +3 -1
  46. package/build/modern/{hydration-Cm3oOWor.d.cts → hydration-DfqGmvZi.d.cts} +3 -1
  47. package/build/modern/hydration.cjs +24 -8
  48. package/build/modern/hydration.cjs.map +1 -1
  49. package/build/modern/hydration.d.cts +1 -1
  50. package/build/modern/hydration.d.ts +1 -1
  51. package/build/modern/hydration.js +24 -8
  52. package/build/modern/hydration.js.map +1 -1
  53. package/build/modern/index.d.cts +1 -1
  54. package/build/modern/index.d.ts +1 -1
  55. package/build/modern/infiniteQueryBehavior.d.cts +1 -1
  56. package/build/modern/infiniteQueryBehavior.d.ts +1 -1
  57. package/build/modern/infiniteQueryObserver.d.cts +1 -1
  58. package/build/modern/infiniteQueryObserver.d.ts +1 -1
  59. package/build/modern/mutation.d.cts +1 -1
  60. package/build/modern/mutation.d.ts +1 -1
  61. package/build/modern/mutationCache.d.cts +1 -1
  62. package/build/modern/mutationCache.d.ts +1 -1
  63. package/build/modern/mutationObserver.d.cts +1 -1
  64. package/build/modern/mutationObserver.d.ts +1 -1
  65. package/build/modern/onlineManager.cjs.map +1 -1
  66. package/build/modern/onlineManager.js.map +1 -1
  67. package/build/modern/queriesObserver.d.cts +1 -1
  68. package/build/modern/queriesObserver.d.ts +1 -1
  69. package/build/modern/query.d.cts +1 -1
  70. package/build/modern/query.d.ts +1 -1
  71. package/build/modern/queryCache.d.cts +1 -1
  72. package/build/modern/queryCache.d.ts +1 -1
  73. package/build/modern/queryClient.cjs.map +1 -1
  74. package/build/modern/queryClient.d.cts +1 -1
  75. package/build/modern/queryClient.d.ts +1 -1
  76. package/build/modern/queryClient.js.map +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/types.d.cts +1 -1
  82. package/build/modern/types.d.ts +1 -1
  83. package/build/modern/utils.d.cts +1 -1
  84. package/build/modern/utils.d.ts +1 -1
  85. package/package.json +1 -1
  86. package/src/__tests__/hydration.test.tsx +85 -2
  87. package/src/focusManager.ts +2 -2
  88. package/src/hydration.ts +41 -14
  89. package/src/onlineManager.ts +1 -1
  90. package/src/queryClient.ts +3 -1
@@ -15,6 +15,11 @@ async function fetchData<TData>(value: TData, ms?: number): Promise<TData> {
15
15
  return value
16
16
  }
17
17
 
18
+ async function fetchDate(value: string, ms?: number): Promise<Date> {
19
+ await sleep(ms || 0)
20
+ return new Date(value)
21
+ }
22
+
18
23
  describe('dehydration and rehydration', () => {
19
24
  test('should work with serializable values', async () => {
20
25
  const queryCache = new QueryCache()
@@ -914,13 +919,14 @@ describe('dehydration and rehydration', () => {
914
919
  defaultOptions: {
915
920
  dehydrate: {
916
921
  shouldDehydrateQuery: () => true,
922
+ serializeData: (data) => data.toISOString(),
917
923
  },
918
924
  },
919
925
  })
920
926
 
921
927
  const promise = queryClient.prefetchQuery({
922
928
  queryKey: ['transformedStringToDate'],
923
- queryFn: () => fetchData('2024-01-01T00:00:00.000Z', 20),
929
+ queryFn: () => fetchDate('2024-01-01T00:00:00.000Z', 20),
924
930
  })
925
931
  const dehydrated = dehydrate(queryClient)
926
932
  expect(dehydrated.queries[0]?.promise).toBeInstanceOf(Promise)
@@ -928,7 +934,7 @@ describe('dehydration and rehydration', () => {
928
934
  const hydrationClient = createQueryClient({
929
935
  defaultOptions: {
930
936
  hydrate: {
931
- transformPromise: (p) => p.then((d) => new Date(d)),
937
+ deserializeData: (data) => new Date(data),
932
938
  },
933
939
  },
934
940
  })
@@ -943,4 +949,81 @@ describe('dehydration and rehydration', () => {
943
949
 
944
950
  queryClient.clear()
945
951
  })
952
+
953
+ test('should transform query data if promise is already resolved', async () => {
954
+ const queryClient = createQueryClient({
955
+ defaultOptions: {
956
+ dehydrate: {
957
+ shouldDehydrateQuery: () => true,
958
+ serializeData: (data) => data.toISOString(),
959
+ },
960
+ },
961
+ })
962
+
963
+ const promise = queryClient.prefetchQuery({
964
+ queryKey: ['transformedStringToDate'],
965
+ queryFn: () => fetchDate('2024-01-01T00:00:00.000Z', 0),
966
+ })
967
+ await sleep(20)
968
+ const dehydrated = dehydrate(queryClient)
969
+
970
+ const hydrationClient = createQueryClient({
971
+ defaultOptions: {
972
+ hydrate: {
973
+ deserializeData: (data) => new Date(data),
974
+ },
975
+ },
976
+ })
977
+
978
+ hydrate(hydrationClient, dehydrated)
979
+ await promise
980
+ await waitFor(() =>
981
+ expect(
982
+ hydrationClient.getQueryData(['transformedStringToDate']),
983
+ ).toBeInstanceOf(Date),
984
+ )
985
+
986
+ queryClient.clear()
987
+ })
988
+
989
+ test('should overwrite query in cache if hydrated query is newer (with transformation)', async () => {
990
+ const hydrationClient = createQueryClient({
991
+ defaultOptions: {
992
+ hydrate: {
993
+ deserializeData: (data) => new Date(data),
994
+ },
995
+ },
996
+ })
997
+ await hydrationClient.prefetchQuery({
998
+ queryKey: ['date'],
999
+ queryFn: () => fetchDate('2024-01-01T00:00:00.000Z', 5),
1000
+ })
1001
+
1002
+ // ---
1003
+
1004
+ const queryClient = createQueryClient({
1005
+ defaultOptions: {
1006
+ dehydrate: {
1007
+ shouldDehydrateQuery: () => true,
1008
+ serializeData: (data) => data.toISOString(),
1009
+ },
1010
+ },
1011
+ })
1012
+ await queryClient.prefetchQuery({
1013
+ queryKey: ['date'],
1014
+ queryFn: () => fetchDate('2024-01-02T00:00:00.000Z', 10),
1015
+ })
1016
+ const dehydrated = dehydrate(queryClient)
1017
+
1018
+ // ---
1019
+
1020
+ hydrate(hydrationClient, dehydrated)
1021
+
1022
+ expect(hydrationClient.getQueryData(['date'])).toStrictEqual(
1023
+ new Date('2024-01-02T00:00:00.000Z'),
1024
+ )
1025
+
1026
+ queryClient.clear()
1027
+ hydrationClient.clear()
1028
+ })
946
1029
  })
@@ -17,7 +17,7 @@ export class FocusManager extends Subscribable<Listener> {
17
17
  super()
18
18
  this.#setup = (onFocus) => {
19
19
  // addEventListener does not exist in React Native, but window does
20
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
20
+ // eslint-disable-next-line ts/no-unnecessary-condition
21
21
  if (!isServer && window.addEventListener) {
22
22
  const listener = () => onFocus()
23
23
  // Listen to visibilitychange
@@ -78,7 +78,7 @@ export class FocusManager extends Subscribable<Listener> {
78
78
  }
79
79
 
80
80
  // document global can be unavailable in react native
81
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
81
+ // eslint-disable-next-line ts/no-unnecessary-condition
82
82
  return globalThis.document?.visibilityState !== 'hidden'
83
83
  }
84
84
  }
package/src/hydration.ts CHANGED
@@ -13,15 +13,20 @@ import type { Query, QueryState } from './query'
13
13
  import type { Mutation, MutationState } from './mutation'
14
14
 
15
15
  // TYPES
16
+ type TransformerFn = (data: any) => any
17
+ function defaultTransformerFn(data: any): any {
18
+ return data
19
+ }
16
20
 
17
21
  export interface DehydrateOptions {
22
+ serializeData?: TransformerFn
18
23
  shouldDehydrateMutation?: (mutation: Mutation) => boolean
19
24
  shouldDehydrateQuery?: (query: Query) => boolean
20
25
  }
21
26
 
22
27
  export interface HydrateOptions {
23
28
  defaultOptions?: {
24
- transformPromise?: (promise: Promise<any>) => Promise<any>
29
+ deserializeData?: TransformerFn
25
30
  queries?: QueryOptions
26
31
  mutations?: MutationOptions<unknown, DefaultError, unknown, unknown>
27
32
  }
@@ -62,13 +67,21 @@ function dehydrateMutation(mutation: Mutation): DehydratedMutation {
62
67
  // consuming the de/rehydrated data, typically with useQuery on the client.
63
68
  // Sometimes it might make sense to prefetch data on the server and include
64
69
  // in the html-payload, but not consume it on the initial render.
65
- function dehydrateQuery(query: Query): DehydratedQuery {
70
+ function dehydrateQuery(
71
+ query: Query,
72
+ serializeData: TransformerFn,
73
+ ): DehydratedQuery {
66
74
  return {
67
- state: query.state,
75
+ state: {
76
+ ...query.state,
77
+ ...(query.state.data !== undefined && {
78
+ data: serializeData(query.state.data),
79
+ }),
80
+ },
68
81
  queryKey: query.queryKey,
69
82
  queryHash: query.queryHash,
70
83
  ...(query.state.status === 'pending' && {
71
- promise: query.promise?.catch((error) => {
84
+ promise: query.promise?.then(serializeData).catch((error) => {
72
85
  if (process.env.NODE_ENV !== 'production') {
73
86
  console.error(
74
87
  `A query that was dehydrated as pending ended up rejecting. [${query.queryHash}]: ${error}; The error will be redacted in production builds`,
@@ -110,10 +123,17 @@ export function dehydrate(
110
123
  client.getDefaultOptions().dehydrate?.shouldDehydrateQuery ??
111
124
  defaultShouldDehydrateQuery
112
125
 
126
+ const serializeData =
127
+ options.serializeData ??
128
+ client.getDefaultOptions().dehydrate?.serializeData ??
129
+ defaultTransformerFn
130
+
113
131
  const queries = client
114
132
  .getQueryCache()
115
133
  .getAll()
116
- .flatMap((query) => (filterQuery(query) ? [dehydrateQuery(query)] : []))
134
+ .flatMap((query) =>
135
+ filterQuery(query) ? [dehydrateQuery(query, serializeData)] : [],
136
+ )
117
137
 
118
138
  return { mutations, queries }
119
139
  }
@@ -129,10 +149,14 @@ export function hydrate(
129
149
 
130
150
  const mutationCache = client.getMutationCache()
131
151
  const queryCache = client.getQueryCache()
152
+ const deserializeData =
153
+ options?.defaultOptions?.deserializeData ??
154
+ client.getDefaultOptions().hydrate?.deserializeData ??
155
+ defaultTransformerFn
132
156
 
133
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
157
+ // eslint-disable-next-line ts/no-unnecessary-condition
134
158
  const mutations = (dehydratedState as DehydratedState).mutations || []
135
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
159
+ // eslint-disable-next-line ts/no-unnecessary-condition
136
160
  const queries = (dehydratedState as DehydratedState).queries || []
137
161
 
138
162
  mutations.forEach(({ state, ...mutationOptions }) => {
@@ -150,13 +174,19 @@ export function hydrate(
150
174
  queries.forEach(({ queryKey, state, queryHash, meta, promise }) => {
151
175
  let query = queryCache.get(queryHash)
152
176
 
177
+ const data =
178
+ state.data === undefined ? state.data : deserializeData(state.data)
179
+
153
180
  // Do not hydrate if an existing query exists with newer data
154
181
  if (query) {
155
182
  if (query.state.dataUpdatedAt < state.dataUpdatedAt) {
156
183
  // omit fetchStatus from dehydrated state
157
184
  // so that query stays in its current fetchStatus
158
- const { fetchStatus: _ignored, ...dehydratedQueryState } = state
159
- query.setState(dehydratedQueryState)
185
+ const { fetchStatus: _ignored, ...serializedState } = state
186
+ query.setState({
187
+ ...serializedState,
188
+ data,
189
+ })
160
190
  }
161
191
  } else {
162
192
  // Restore query
@@ -173,19 +203,16 @@ export function hydrate(
173
203
  // query being stuck in fetching state upon hydration
174
204
  {
175
205
  ...state,
206
+ data,
176
207
  fetchStatus: 'idle',
177
208
  },
178
209
  )
179
210
  }
180
211
 
181
212
  if (promise) {
182
- const transformPromise =
183
- client.getDefaultOptions().hydrate?.transformPromise
184
-
185
213
  // Note: `Promise.resolve` required cause
186
214
  // RSC transformed promises are not thenable
187
- const initialPromise =
188
- transformPromise?.(Promise.resolve(promise)) ?? promise
215
+ const initialPromise = Promise.resolve(promise).then(deserializeData)
189
216
 
190
217
  // this doesn't actually fetch - it just creates a retryer
191
218
  // which will re-use the passed `initialPromise`
@@ -14,7 +14,7 @@ export class OnlineManager extends Subscribable<Listener> {
14
14
  super()
15
15
  this.#setup = (onOnline) => {
16
16
  // addEventListener does not exist in React Native, but window does
17
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
17
+ // eslint-disable-next-line ts/no-unnecessary-condition
18
18
  if (!isServer && window.addEventListener) {
19
19
  const onlineListener = () => onOnline(true)
20
20
  const offlineListener = () => onOnline(false)
@@ -13,10 +13,10 @@ import { focusManager } from './focusManager'
13
13
  import { onlineManager } from './onlineManager'
14
14
  import { notifyManager } from './notifyManager'
15
15
  import { infiniteQueryBehavior } from './infiniteQueryBehavior'
16
- import type { DataTag, NoInfer, OmitKeyof } from './types'
17
16
  import type { QueryState } from './query'
18
17
  import type {
19
18
  CancelOptions,
19
+ DataTag,
20
20
  DefaultError,
21
21
  DefaultOptions,
22
22
  DefaultedQueryObserverOptions,
@@ -29,6 +29,8 @@ import type {
29
29
  MutationKey,
30
30
  MutationObserverOptions,
31
31
  MutationOptions,
32
+ NoInfer,
33
+ OmitKeyof,
32
34
  QueryClientConfig,
33
35
  QueryKey,
34
36
  QueryObserverOptions,