@tanstack/query-core 5.100.0 → 5.100.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.
@@ -134,7 +134,19 @@ function hydrate(client, dehydratedState, options) {
134
134
  const { fetchStatus: _ignored, ...serializedState } = state;
135
135
  query.setState({
136
136
  ...serializedState,
137
- data
137
+ data,
138
+ // If the query was pending at the moment of dehydration, but resolved to have data
139
+ // before hydration, we can assume the query should be hydrated as successful.
140
+ //
141
+ // Since you can opt into dehydrating failed queries, and those can have data from
142
+ // previous successful fetches, we make sure we only do this for pending queries.
143
+ ...state.status === "pending" && data !== void 0 && {
144
+ status: "success",
145
+ // Preserve existing fetchStatus if the existing query is actively fetching.
146
+ ...!existingQueryIsFetching && {
147
+ fetchStatus: "idle"
148
+ }
149
+ }
138
150
  });
139
151
  }
140
152
  } else {
@@ -153,11 +165,15 @@ function hydrate(client, dehydratedState, options) {
153
165
  ...state,
154
166
  data,
155
167
  fetchStatus: "idle",
156
- status: data !== void 0 ? "success" : state.status
168
+ // Like above, if the query was pending at the moment of dehydration but has data,
169
+ // we can assume it should be hydrated as successful.
170
+ status: state.status === "pending" && data !== void 0 ? "success" : state.status
157
171
  }
158
172
  );
159
173
  }
160
- if (promise && !existingQueryIsPending && !existingQueryIsFetching && // Only hydrate if dehydration is newer than any existing data,
174
+ if (promise && // If the data was synchronously available, there is no need to set up
175
+ // a retryer and thus no reason to call fetch
176
+ !syncData && !existingQueryIsPending && !existingQueryIsFetching && // Only hydrate if dehydration is newer than any existing data,
161
177
  // this is always true for new queries
162
178
  (dehydratedAt === void 0 || dehydratedAt > query.state.dataUpdatedAt)) {
163
179
  query.fetch(void 0, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hydration.ts"],"sourcesContent":["import { tryResolveSync } from './thenable'\nimport { noop } from './utils'\nimport type {\n DefaultError,\n MutationKey,\n MutationMeta,\n MutationOptions,\n MutationScope,\n QueryKey,\n QueryMeta,\n QueryOptions,\n} from './types'\nimport type { QueryClient } from './queryClient'\nimport type { Query, QueryState } from './query'\nimport type { Mutation, MutationState } from './mutation'\n\n// TYPES\ntype TransformerFn = (data: any) => any\nfunction defaultTransformerFn(data: any): any {\n return data\n}\n\nexport interface DehydrateOptions {\n serializeData?: TransformerFn\n shouldDehydrateMutation?: (mutation: Mutation) => boolean\n shouldDehydrateQuery?: (query: Query) => boolean\n shouldRedactErrors?: (error: unknown) => boolean\n}\n\nexport interface HydrateOptions {\n defaultOptions?: {\n deserializeData?: TransformerFn\n queries?: QueryOptions\n mutations?: MutationOptions<unknown, DefaultError, unknown, unknown>\n }\n}\n\ninterface DehydratedMutation {\n mutationKey?: MutationKey\n state: MutationState\n meta?: MutationMeta\n scope?: MutationScope\n}\n\ninterface DehydratedQuery {\n queryHash: string\n queryKey: QueryKey\n state: QueryState\n promise?: Promise<unknown>\n meta?: QueryMeta\n // This is only optional because older versions of Query might have dehydrated\n // without it which we need to handle for backwards compatibility.\n // This should be changed to required in the future.\n dehydratedAt?: number\n}\n\nexport interface DehydratedState {\n mutations: Array<DehydratedMutation>\n queries: Array<DehydratedQuery>\n}\n\n// FUNCTIONS\n\nfunction dehydrateMutation(mutation: Mutation): DehydratedMutation {\n return {\n mutationKey: mutation.options.mutationKey,\n state: mutation.state,\n ...(mutation.options.scope && { scope: mutation.options.scope }),\n ...(mutation.meta && { meta: mutation.meta }),\n }\n}\n\n// Most config is not dehydrated but instead meant to configure again when\n// consuming the de/rehydrated data, typically with useQuery on the client.\n// Sometimes it might make sense to prefetch data on the server and include\n// in the html-payload, but not consume it on the initial render.\nfunction dehydrateQuery(\n query: Query,\n serializeData: TransformerFn,\n shouldRedactErrors: (error: unknown) => boolean,\n): DehydratedQuery {\n const dehydratePromise = () => {\n const promise = query.promise?.then(serializeData).catch((error) => {\n if (!shouldRedactErrors(error)) {\n // Reject original error if it should not be redacted\n return Promise.reject(error)\n }\n // If not in production, log original error before rejecting redacted error\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n `A query that was dehydrated as pending ended up rejecting. [${query.queryHash}]: ${error}; The error will be redacted in production builds`,\n )\n }\n return Promise.reject(new Error('redacted'))\n })\n\n // Avoid unhandled promise rejections\n // We need the promise we dehydrate to reject to get the correct result into\n // the query cache, but we also want to avoid unhandled promise rejections\n // in whatever environment the prefetches are happening in.\n promise?.catch(noop)\n\n return promise\n }\n\n return {\n dehydratedAt: Date.now(),\n state: {\n ...query.state,\n ...(query.state.data !== undefined && {\n data: serializeData(query.state.data),\n }),\n },\n queryKey: query.queryKey,\n queryHash: query.queryHash,\n ...(query.state.status === 'pending' && {\n promise: dehydratePromise(),\n }),\n ...(query.meta && { meta: query.meta }),\n }\n}\n\nexport function defaultShouldDehydrateMutation(mutation: Mutation) {\n return mutation.state.isPaused\n}\n\nexport function defaultShouldDehydrateQuery(query: Query) {\n return query.state.status === 'success'\n}\n\nfunction defaultShouldRedactErrors(_: unknown) {\n return true\n}\n\nexport function dehydrate(\n client: QueryClient,\n options: DehydrateOptions = {},\n): DehydratedState {\n const filterMutation =\n options.shouldDehydrateMutation ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateMutation ??\n defaultShouldDehydrateMutation\n\n const mutations = client\n .getMutationCache()\n .getAll()\n .flatMap((mutation) =>\n filterMutation(mutation) ? [dehydrateMutation(mutation)] : [],\n )\n\n const filterQuery =\n options.shouldDehydrateQuery ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateQuery ??\n defaultShouldDehydrateQuery\n\n const shouldRedactErrors =\n options.shouldRedactErrors ??\n client.getDefaultOptions().dehydrate?.shouldRedactErrors ??\n defaultShouldRedactErrors\n\n const serializeData =\n options.serializeData ??\n client.getDefaultOptions().dehydrate?.serializeData ??\n defaultTransformerFn\n\n const queries = client\n .getQueryCache()\n .getAll()\n .flatMap((query) =>\n filterQuery(query)\n ? [dehydrateQuery(query, serializeData, shouldRedactErrors)]\n : [],\n )\n\n return { mutations, queries }\n}\n\nexport function hydrate(\n client: QueryClient,\n dehydratedState: unknown,\n options?: HydrateOptions,\n): void {\n if (typeof dehydratedState !== 'object' || dehydratedState === null) {\n return\n }\n\n const mutationCache = client.getMutationCache()\n const queryCache = client.getQueryCache()\n const deserializeData =\n options?.defaultOptions?.deserializeData ??\n client.getDefaultOptions().hydrate?.deserializeData ??\n defaultTransformerFn\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const mutations = (dehydratedState as DehydratedState).mutations || []\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const queries = (dehydratedState as DehydratedState).queries || []\n\n mutations.forEach(({ state, ...mutationOptions }) => {\n mutationCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.mutations,\n ...options?.defaultOptions?.mutations,\n ...mutationOptions,\n },\n state,\n )\n })\n\n queries.forEach(\n ({ queryKey, state, queryHash, meta, promise, dehydratedAt }) => {\n const syncData = promise ? tryResolveSync(promise) : undefined\n const rawData = state.data === undefined ? syncData?.data : state.data\n const data = rawData === undefined ? rawData : deserializeData(rawData)\n\n let query = queryCache.get(queryHash)\n const existingQueryIsPending = query?.state.status === 'pending'\n const existingQueryIsFetching = query?.state.fetchStatus === 'fetching'\n\n // Do not hydrate if an existing query exists with newer data\n if (query) {\n const hasNewerSyncData =\n syncData &&\n // We only need this undefined check to handle older dehydration\n // payloads that might not have dehydratedAt\n dehydratedAt !== undefined &&\n dehydratedAt > query.state.dataUpdatedAt\n if (\n state.dataUpdatedAt > query.state.dataUpdatedAt ||\n hasNewerSyncData\n ) {\n // omit fetchStatus from dehydrated state\n // so that query stays in its current fetchStatus\n const { fetchStatus: _ignored, ...serializedState } = state\n query.setState({\n ...serializedState,\n data,\n })\n }\n } else {\n // Restore query\n query = queryCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.queries,\n ...options?.defaultOptions?.queries,\n queryKey,\n queryHash,\n meta,\n },\n // Reset fetch status to idle to avoid\n // query being stuck in fetching state upon hydration\n {\n ...state,\n data,\n fetchStatus: 'idle',\n status: data !== undefined ? 'success' : state.status,\n },\n )\n }\n\n if (\n promise &&\n !existingQueryIsPending &&\n !existingQueryIsFetching &&\n // Only hydrate if dehydration is newer than any existing data,\n // this is always true for new queries\n (dehydratedAt === undefined || dehydratedAt > query.state.dataUpdatedAt)\n ) {\n // This doesn't actually fetch - it just creates a retryer\n // which will re-use the passed `initialPromise`\n // Note that we need to call these even when data was synchronously\n // available, as we still need to set up the retryer\n query\n .fetch(undefined, {\n // RSC transformed promises are not thenable\n initialPromise: Promise.resolve(promise).then(deserializeData),\n })\n // Avoid unhandled promise rejections\n .catch(noop)\n }\n },\n )\n}\n"],"mappings":"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","names":["_a","_b"]}
1
+ {"version":3,"sources":["../../src/hydration.ts"],"sourcesContent":["import { tryResolveSync } from './thenable'\nimport { noop } from './utils'\nimport type {\n DefaultError,\n MutationKey,\n MutationMeta,\n MutationOptions,\n MutationScope,\n QueryKey,\n QueryMeta,\n QueryOptions,\n} from './types'\nimport type { QueryClient } from './queryClient'\nimport type { Query, QueryState } from './query'\nimport type { Mutation, MutationState } from './mutation'\n\n// TYPES\ntype TransformerFn = (data: any) => any\nfunction defaultTransformerFn(data: any): any {\n return data\n}\n\nexport interface DehydrateOptions {\n serializeData?: TransformerFn\n shouldDehydrateMutation?: (mutation: Mutation) => boolean\n shouldDehydrateQuery?: (query: Query) => boolean\n shouldRedactErrors?: (error: unknown) => boolean\n}\n\nexport interface HydrateOptions {\n defaultOptions?: {\n deserializeData?: TransformerFn\n queries?: QueryOptions\n mutations?: MutationOptions<unknown, DefaultError, unknown, unknown>\n }\n}\n\ninterface DehydratedMutation {\n mutationKey?: MutationKey\n state: MutationState\n meta?: MutationMeta\n scope?: MutationScope\n}\n\ninterface DehydratedQuery {\n queryHash: string\n queryKey: QueryKey\n state: QueryState\n promise?: Promise<unknown>\n meta?: QueryMeta\n // This is only optional because older versions of Query might have dehydrated\n // without it which we need to handle for backwards compatibility.\n // This should be changed to required in the future.\n dehydratedAt?: number\n}\n\nexport interface DehydratedState {\n mutations: Array<DehydratedMutation>\n queries: Array<DehydratedQuery>\n}\n\n// FUNCTIONS\n\nfunction dehydrateMutation(mutation: Mutation): DehydratedMutation {\n return {\n mutationKey: mutation.options.mutationKey,\n state: mutation.state,\n ...(mutation.options.scope && { scope: mutation.options.scope }),\n ...(mutation.meta && { meta: mutation.meta }),\n }\n}\n\n// Most config is not dehydrated but instead meant to configure again when\n// consuming the de/rehydrated data, typically with useQuery on the client.\n// Sometimes it might make sense to prefetch data on the server and include\n// in the html-payload, but not consume it on the initial render.\nfunction dehydrateQuery(\n query: Query,\n serializeData: TransformerFn,\n shouldRedactErrors: (error: unknown) => boolean,\n): DehydratedQuery {\n const dehydratePromise = () => {\n const promise = query.promise?.then(serializeData).catch((error) => {\n if (!shouldRedactErrors(error)) {\n // Reject original error if it should not be redacted\n return Promise.reject(error)\n }\n // If not in production, log original error before rejecting redacted error\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n `A query that was dehydrated as pending ended up rejecting. [${query.queryHash}]: ${error}; The error will be redacted in production builds`,\n )\n }\n return Promise.reject(new Error('redacted'))\n })\n\n // Avoid unhandled promise rejections\n // We need the promise we dehydrate to reject to get the correct result into\n // the query cache, but we also want to avoid unhandled promise rejections\n // in whatever environment the prefetches are happening in.\n promise?.catch(noop)\n\n return promise\n }\n\n return {\n dehydratedAt: Date.now(),\n state: {\n ...query.state,\n ...(query.state.data !== undefined && {\n data: serializeData(query.state.data),\n }),\n },\n queryKey: query.queryKey,\n queryHash: query.queryHash,\n ...(query.state.status === 'pending' && {\n promise: dehydratePromise(),\n }),\n ...(query.meta && { meta: query.meta }),\n }\n}\n\nexport function defaultShouldDehydrateMutation(mutation: Mutation) {\n return mutation.state.isPaused\n}\n\nexport function defaultShouldDehydrateQuery(query: Query) {\n return query.state.status === 'success'\n}\n\nfunction defaultShouldRedactErrors(_: unknown) {\n return true\n}\n\nexport function dehydrate(\n client: QueryClient,\n options: DehydrateOptions = {},\n): DehydratedState {\n const filterMutation =\n options.shouldDehydrateMutation ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateMutation ??\n defaultShouldDehydrateMutation\n\n const mutations = client\n .getMutationCache()\n .getAll()\n .flatMap((mutation) =>\n filterMutation(mutation) ? [dehydrateMutation(mutation)] : [],\n )\n\n const filterQuery =\n options.shouldDehydrateQuery ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateQuery ??\n defaultShouldDehydrateQuery\n\n const shouldRedactErrors =\n options.shouldRedactErrors ??\n client.getDefaultOptions().dehydrate?.shouldRedactErrors ??\n defaultShouldRedactErrors\n\n const serializeData =\n options.serializeData ??\n client.getDefaultOptions().dehydrate?.serializeData ??\n defaultTransformerFn\n\n const queries = client\n .getQueryCache()\n .getAll()\n .flatMap((query) =>\n filterQuery(query)\n ? [dehydrateQuery(query, serializeData, shouldRedactErrors)]\n : [],\n )\n\n return { mutations, queries }\n}\n\nexport function hydrate(\n client: QueryClient,\n dehydratedState: unknown,\n options?: HydrateOptions,\n): void {\n if (typeof dehydratedState !== 'object' || dehydratedState === null) {\n return\n }\n\n const mutationCache = client.getMutationCache()\n const queryCache = client.getQueryCache()\n const deserializeData =\n options?.defaultOptions?.deserializeData ??\n client.getDefaultOptions().hydrate?.deserializeData ??\n defaultTransformerFn\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const mutations = (dehydratedState as DehydratedState).mutations || []\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const queries = (dehydratedState as DehydratedState).queries || []\n\n mutations.forEach(({ state, ...mutationOptions }) => {\n mutationCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.mutations,\n ...options?.defaultOptions?.mutations,\n ...mutationOptions,\n },\n state,\n )\n })\n\n queries.forEach(\n ({ queryKey, state, queryHash, meta, promise, dehydratedAt }) => {\n const syncData = promise ? tryResolveSync(promise) : undefined\n const rawData = state.data === undefined ? syncData?.data : state.data\n const data = rawData === undefined ? rawData : deserializeData(rawData)\n\n let query = queryCache.get(queryHash)\n const existingQueryIsPending = query?.state.status === 'pending'\n const existingQueryIsFetching = query?.state.fetchStatus === 'fetching'\n\n // Do not hydrate if an existing query exists with newer data\n if (query) {\n const hasNewerSyncData =\n syncData &&\n // We only need this undefined check to handle older dehydration\n // payloads that might not have dehydratedAt\n dehydratedAt !== undefined &&\n dehydratedAt > query.state.dataUpdatedAt\n if (\n state.dataUpdatedAt > query.state.dataUpdatedAt ||\n hasNewerSyncData\n ) {\n // Omit fetchStatus from dehydrated state so that query stays in its current fetchStatus\n const { fetchStatus: _ignored, ...serializedState } = state\n query.setState({\n ...serializedState,\n data,\n // If the query was pending at the moment of dehydration, but resolved to have data\n // before hydration, we can assume the query should be hydrated as successful.\n //\n // Since you can opt into dehydrating failed queries, and those can have data from\n // previous successful fetches, we make sure we only do this for pending queries.\n ...(state.status === 'pending' &&\n data !== undefined && {\n status: 'success' as const,\n // Preserve existing fetchStatus if the existing query is actively fetching.\n ...(!existingQueryIsFetching && {\n fetchStatus: 'idle' as const,\n }),\n }),\n })\n }\n } else {\n // Restore query\n query = queryCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.queries,\n ...options?.defaultOptions?.queries,\n queryKey,\n queryHash,\n meta,\n },\n // Reset fetch status to idle to avoid\n // query being stuck in fetching state upon hydration\n {\n ...state,\n data,\n fetchStatus: 'idle',\n // Like above, if the query was pending at the moment of dehydration but has data,\n // we can assume it should be hydrated as successful.\n status:\n state.status === 'pending' && data !== undefined\n ? 'success'\n : state.status,\n },\n )\n }\n\n if (\n promise &&\n // If the data was synchronously available, there is no need to set up\n // a retryer and thus no reason to call fetch\n !syncData &&\n !existingQueryIsPending &&\n !existingQueryIsFetching &&\n // Only hydrate if dehydration is newer than any existing data,\n // this is always true for new queries\n (dehydratedAt === undefined || dehydratedAt > query.state.dataUpdatedAt)\n ) {\n // This doesn't actually fetch - it just creates a retryer\n // which will re-use the passed `initialPromise`\n query\n .fetch(undefined, {\n // RSC transformed promises are not thenable\n initialPromise: Promise.resolve(promise).then(deserializeData),\n })\n // Avoid unhandled promise rejections\n .catch(noop)\n }\n },\n )\n}\n"],"mappings":"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","names":["_a","_b"]}
@@ -109,7 +109,19 @@ function hydrate(client, dehydratedState, options) {
109
109
  const { fetchStatus: _ignored, ...serializedState } = state;
110
110
  query.setState({
111
111
  ...serializedState,
112
- data
112
+ data,
113
+ // If the query was pending at the moment of dehydration, but resolved to have data
114
+ // before hydration, we can assume the query should be hydrated as successful.
115
+ //
116
+ // Since you can opt into dehydrating failed queries, and those can have data from
117
+ // previous successful fetches, we make sure we only do this for pending queries.
118
+ ...state.status === "pending" && data !== void 0 && {
119
+ status: "success",
120
+ // Preserve existing fetchStatus if the existing query is actively fetching.
121
+ ...!existingQueryIsFetching && {
122
+ fetchStatus: "idle"
123
+ }
124
+ }
113
125
  });
114
126
  }
115
127
  } else {
@@ -128,11 +140,15 @@ function hydrate(client, dehydratedState, options) {
128
140
  ...state,
129
141
  data,
130
142
  fetchStatus: "idle",
131
- status: data !== void 0 ? "success" : state.status
143
+ // Like above, if the query was pending at the moment of dehydration but has data,
144
+ // we can assume it should be hydrated as successful.
145
+ status: state.status === "pending" && data !== void 0 ? "success" : state.status
132
146
  }
133
147
  );
134
148
  }
135
- if (promise && !existingQueryIsPending && !existingQueryIsFetching && // Only hydrate if dehydration is newer than any existing data,
149
+ if (promise && // If the data was synchronously available, there is no need to set up
150
+ // a retryer and thus no reason to call fetch
151
+ !syncData && !existingQueryIsPending && !existingQueryIsFetching && // Only hydrate if dehydration is newer than any existing data,
136
152
  // this is always true for new queries
137
153
  (dehydratedAt === void 0 || dehydratedAt > query.state.dataUpdatedAt)) {
138
154
  query.fetch(void 0, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hydration.ts"],"sourcesContent":["import { tryResolveSync } from './thenable'\nimport { noop } from './utils'\nimport type {\n DefaultError,\n MutationKey,\n MutationMeta,\n MutationOptions,\n MutationScope,\n QueryKey,\n QueryMeta,\n QueryOptions,\n} from './types'\nimport type { QueryClient } from './queryClient'\nimport type { Query, QueryState } from './query'\nimport type { Mutation, MutationState } from './mutation'\n\n// TYPES\ntype TransformerFn = (data: any) => any\nfunction defaultTransformerFn(data: any): any {\n return data\n}\n\nexport interface DehydrateOptions {\n serializeData?: TransformerFn\n shouldDehydrateMutation?: (mutation: Mutation) => boolean\n shouldDehydrateQuery?: (query: Query) => boolean\n shouldRedactErrors?: (error: unknown) => boolean\n}\n\nexport interface HydrateOptions {\n defaultOptions?: {\n deserializeData?: TransformerFn\n queries?: QueryOptions\n mutations?: MutationOptions<unknown, DefaultError, unknown, unknown>\n }\n}\n\ninterface DehydratedMutation {\n mutationKey?: MutationKey\n state: MutationState\n meta?: MutationMeta\n scope?: MutationScope\n}\n\ninterface DehydratedQuery {\n queryHash: string\n queryKey: QueryKey\n state: QueryState\n promise?: Promise<unknown>\n meta?: QueryMeta\n // This is only optional because older versions of Query might have dehydrated\n // without it which we need to handle for backwards compatibility.\n // This should be changed to required in the future.\n dehydratedAt?: number\n}\n\nexport interface DehydratedState {\n mutations: Array<DehydratedMutation>\n queries: Array<DehydratedQuery>\n}\n\n// FUNCTIONS\n\nfunction dehydrateMutation(mutation: Mutation): DehydratedMutation {\n return {\n mutationKey: mutation.options.mutationKey,\n state: mutation.state,\n ...(mutation.options.scope && { scope: mutation.options.scope }),\n ...(mutation.meta && { meta: mutation.meta }),\n }\n}\n\n// Most config is not dehydrated but instead meant to configure again when\n// consuming the de/rehydrated data, typically with useQuery on the client.\n// Sometimes it might make sense to prefetch data on the server and include\n// in the html-payload, but not consume it on the initial render.\nfunction dehydrateQuery(\n query: Query,\n serializeData: TransformerFn,\n shouldRedactErrors: (error: unknown) => boolean,\n): DehydratedQuery {\n const dehydratePromise = () => {\n const promise = query.promise?.then(serializeData).catch((error) => {\n if (!shouldRedactErrors(error)) {\n // Reject original error if it should not be redacted\n return Promise.reject(error)\n }\n // If not in production, log original error before rejecting redacted error\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n `A query that was dehydrated as pending ended up rejecting. [${query.queryHash}]: ${error}; The error will be redacted in production builds`,\n )\n }\n return Promise.reject(new Error('redacted'))\n })\n\n // Avoid unhandled promise rejections\n // We need the promise we dehydrate to reject to get the correct result into\n // the query cache, but we also want to avoid unhandled promise rejections\n // in whatever environment the prefetches are happening in.\n promise?.catch(noop)\n\n return promise\n }\n\n return {\n dehydratedAt: Date.now(),\n state: {\n ...query.state,\n ...(query.state.data !== undefined && {\n data: serializeData(query.state.data),\n }),\n },\n queryKey: query.queryKey,\n queryHash: query.queryHash,\n ...(query.state.status === 'pending' && {\n promise: dehydratePromise(),\n }),\n ...(query.meta && { meta: query.meta }),\n }\n}\n\nexport function defaultShouldDehydrateMutation(mutation: Mutation) {\n return mutation.state.isPaused\n}\n\nexport function defaultShouldDehydrateQuery(query: Query) {\n return query.state.status === 'success'\n}\n\nfunction defaultShouldRedactErrors(_: unknown) {\n return true\n}\n\nexport function dehydrate(\n client: QueryClient,\n options: DehydrateOptions = {},\n): DehydratedState {\n const filterMutation =\n options.shouldDehydrateMutation ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateMutation ??\n defaultShouldDehydrateMutation\n\n const mutations = client\n .getMutationCache()\n .getAll()\n .flatMap((mutation) =>\n filterMutation(mutation) ? [dehydrateMutation(mutation)] : [],\n )\n\n const filterQuery =\n options.shouldDehydrateQuery ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateQuery ??\n defaultShouldDehydrateQuery\n\n const shouldRedactErrors =\n options.shouldRedactErrors ??\n client.getDefaultOptions().dehydrate?.shouldRedactErrors ??\n defaultShouldRedactErrors\n\n const serializeData =\n options.serializeData ??\n client.getDefaultOptions().dehydrate?.serializeData ??\n defaultTransformerFn\n\n const queries = client\n .getQueryCache()\n .getAll()\n .flatMap((query) =>\n filterQuery(query)\n ? [dehydrateQuery(query, serializeData, shouldRedactErrors)]\n : [],\n )\n\n return { mutations, queries }\n}\n\nexport function hydrate(\n client: QueryClient,\n dehydratedState: unknown,\n options?: HydrateOptions,\n): void {\n if (typeof dehydratedState !== 'object' || dehydratedState === null) {\n return\n }\n\n const mutationCache = client.getMutationCache()\n const queryCache = client.getQueryCache()\n const deserializeData =\n options?.defaultOptions?.deserializeData ??\n client.getDefaultOptions().hydrate?.deserializeData ??\n defaultTransformerFn\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const mutations = (dehydratedState as DehydratedState).mutations || []\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const queries = (dehydratedState as DehydratedState).queries || []\n\n mutations.forEach(({ state, ...mutationOptions }) => {\n mutationCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.mutations,\n ...options?.defaultOptions?.mutations,\n ...mutationOptions,\n },\n state,\n )\n })\n\n queries.forEach(\n ({ queryKey, state, queryHash, meta, promise, dehydratedAt }) => {\n const syncData = promise ? tryResolveSync(promise) : undefined\n const rawData = state.data === undefined ? syncData?.data : state.data\n const data = rawData === undefined ? rawData : deserializeData(rawData)\n\n let query = queryCache.get(queryHash)\n const existingQueryIsPending = query?.state.status === 'pending'\n const existingQueryIsFetching = query?.state.fetchStatus === 'fetching'\n\n // Do not hydrate if an existing query exists with newer data\n if (query) {\n const hasNewerSyncData =\n syncData &&\n // We only need this undefined check to handle older dehydration\n // payloads that might not have dehydratedAt\n dehydratedAt !== undefined &&\n dehydratedAt > query.state.dataUpdatedAt\n if (\n state.dataUpdatedAt > query.state.dataUpdatedAt ||\n hasNewerSyncData\n ) {\n // omit fetchStatus from dehydrated state\n // so that query stays in its current fetchStatus\n const { fetchStatus: _ignored, ...serializedState } = state\n query.setState({\n ...serializedState,\n data,\n })\n }\n } else {\n // Restore query\n query = queryCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.queries,\n ...options?.defaultOptions?.queries,\n queryKey,\n queryHash,\n meta,\n },\n // Reset fetch status to idle to avoid\n // query being stuck in fetching state upon hydration\n {\n ...state,\n data,\n fetchStatus: 'idle',\n status: data !== undefined ? 'success' : state.status,\n },\n )\n }\n\n if (\n promise &&\n !existingQueryIsPending &&\n !existingQueryIsFetching &&\n // Only hydrate if dehydration is newer than any existing data,\n // this is always true for new queries\n (dehydratedAt === undefined || dehydratedAt > query.state.dataUpdatedAt)\n ) {\n // This doesn't actually fetch - it just creates a retryer\n // which will re-use the passed `initialPromise`\n // Note that we need to call these even when data was synchronously\n // available, as we still need to set up the retryer\n query\n .fetch(undefined, {\n // RSC transformed promises are not thenable\n initialPromise: Promise.resolve(promise).then(deserializeData),\n })\n // Avoid unhandled promise rejections\n .catch(noop)\n }\n },\n )\n}\n"],"mappings":"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","names":["_a","_b"]}
1
+ {"version":3,"sources":["../../src/hydration.ts"],"sourcesContent":["import { tryResolveSync } from './thenable'\nimport { noop } from './utils'\nimport type {\n DefaultError,\n MutationKey,\n MutationMeta,\n MutationOptions,\n MutationScope,\n QueryKey,\n QueryMeta,\n QueryOptions,\n} from './types'\nimport type { QueryClient } from './queryClient'\nimport type { Query, QueryState } from './query'\nimport type { Mutation, MutationState } from './mutation'\n\n// TYPES\ntype TransformerFn = (data: any) => any\nfunction defaultTransformerFn(data: any): any {\n return data\n}\n\nexport interface DehydrateOptions {\n serializeData?: TransformerFn\n shouldDehydrateMutation?: (mutation: Mutation) => boolean\n shouldDehydrateQuery?: (query: Query) => boolean\n shouldRedactErrors?: (error: unknown) => boolean\n}\n\nexport interface HydrateOptions {\n defaultOptions?: {\n deserializeData?: TransformerFn\n queries?: QueryOptions\n mutations?: MutationOptions<unknown, DefaultError, unknown, unknown>\n }\n}\n\ninterface DehydratedMutation {\n mutationKey?: MutationKey\n state: MutationState\n meta?: MutationMeta\n scope?: MutationScope\n}\n\ninterface DehydratedQuery {\n queryHash: string\n queryKey: QueryKey\n state: QueryState\n promise?: Promise<unknown>\n meta?: QueryMeta\n // This is only optional because older versions of Query might have dehydrated\n // without it which we need to handle for backwards compatibility.\n // This should be changed to required in the future.\n dehydratedAt?: number\n}\n\nexport interface DehydratedState {\n mutations: Array<DehydratedMutation>\n queries: Array<DehydratedQuery>\n}\n\n// FUNCTIONS\n\nfunction dehydrateMutation(mutation: Mutation): DehydratedMutation {\n return {\n mutationKey: mutation.options.mutationKey,\n state: mutation.state,\n ...(mutation.options.scope && { scope: mutation.options.scope }),\n ...(mutation.meta && { meta: mutation.meta }),\n }\n}\n\n// Most config is not dehydrated but instead meant to configure again when\n// consuming the de/rehydrated data, typically with useQuery on the client.\n// Sometimes it might make sense to prefetch data on the server and include\n// in the html-payload, but not consume it on the initial render.\nfunction dehydrateQuery(\n query: Query,\n serializeData: TransformerFn,\n shouldRedactErrors: (error: unknown) => boolean,\n): DehydratedQuery {\n const dehydratePromise = () => {\n const promise = query.promise?.then(serializeData).catch((error) => {\n if (!shouldRedactErrors(error)) {\n // Reject original error if it should not be redacted\n return Promise.reject(error)\n }\n // If not in production, log original error before rejecting redacted error\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n `A query that was dehydrated as pending ended up rejecting. [${query.queryHash}]: ${error}; The error will be redacted in production builds`,\n )\n }\n return Promise.reject(new Error('redacted'))\n })\n\n // Avoid unhandled promise rejections\n // We need the promise we dehydrate to reject to get the correct result into\n // the query cache, but we also want to avoid unhandled promise rejections\n // in whatever environment the prefetches are happening in.\n promise?.catch(noop)\n\n return promise\n }\n\n return {\n dehydratedAt: Date.now(),\n state: {\n ...query.state,\n ...(query.state.data !== undefined && {\n data: serializeData(query.state.data),\n }),\n },\n queryKey: query.queryKey,\n queryHash: query.queryHash,\n ...(query.state.status === 'pending' && {\n promise: dehydratePromise(),\n }),\n ...(query.meta && { meta: query.meta }),\n }\n}\n\nexport function defaultShouldDehydrateMutation(mutation: Mutation) {\n return mutation.state.isPaused\n}\n\nexport function defaultShouldDehydrateQuery(query: Query) {\n return query.state.status === 'success'\n}\n\nfunction defaultShouldRedactErrors(_: unknown) {\n return true\n}\n\nexport function dehydrate(\n client: QueryClient,\n options: DehydrateOptions = {},\n): DehydratedState {\n const filterMutation =\n options.shouldDehydrateMutation ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateMutation ??\n defaultShouldDehydrateMutation\n\n const mutations = client\n .getMutationCache()\n .getAll()\n .flatMap((mutation) =>\n filterMutation(mutation) ? [dehydrateMutation(mutation)] : [],\n )\n\n const filterQuery =\n options.shouldDehydrateQuery ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateQuery ??\n defaultShouldDehydrateQuery\n\n const shouldRedactErrors =\n options.shouldRedactErrors ??\n client.getDefaultOptions().dehydrate?.shouldRedactErrors ??\n defaultShouldRedactErrors\n\n const serializeData =\n options.serializeData ??\n client.getDefaultOptions().dehydrate?.serializeData ??\n defaultTransformerFn\n\n const queries = client\n .getQueryCache()\n .getAll()\n .flatMap((query) =>\n filterQuery(query)\n ? [dehydrateQuery(query, serializeData, shouldRedactErrors)]\n : [],\n )\n\n return { mutations, queries }\n}\n\nexport function hydrate(\n client: QueryClient,\n dehydratedState: unknown,\n options?: HydrateOptions,\n): void {\n if (typeof dehydratedState !== 'object' || dehydratedState === null) {\n return\n }\n\n const mutationCache = client.getMutationCache()\n const queryCache = client.getQueryCache()\n const deserializeData =\n options?.defaultOptions?.deserializeData ??\n client.getDefaultOptions().hydrate?.deserializeData ??\n defaultTransformerFn\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const mutations = (dehydratedState as DehydratedState).mutations || []\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const queries = (dehydratedState as DehydratedState).queries || []\n\n mutations.forEach(({ state, ...mutationOptions }) => {\n mutationCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.mutations,\n ...options?.defaultOptions?.mutations,\n ...mutationOptions,\n },\n state,\n )\n })\n\n queries.forEach(\n ({ queryKey, state, queryHash, meta, promise, dehydratedAt }) => {\n const syncData = promise ? tryResolveSync(promise) : undefined\n const rawData = state.data === undefined ? syncData?.data : state.data\n const data = rawData === undefined ? rawData : deserializeData(rawData)\n\n let query = queryCache.get(queryHash)\n const existingQueryIsPending = query?.state.status === 'pending'\n const existingQueryIsFetching = query?.state.fetchStatus === 'fetching'\n\n // Do not hydrate if an existing query exists with newer data\n if (query) {\n const hasNewerSyncData =\n syncData &&\n // We only need this undefined check to handle older dehydration\n // payloads that might not have dehydratedAt\n dehydratedAt !== undefined &&\n dehydratedAt > query.state.dataUpdatedAt\n if (\n state.dataUpdatedAt > query.state.dataUpdatedAt ||\n hasNewerSyncData\n ) {\n // Omit fetchStatus from dehydrated state so that query stays in its current fetchStatus\n const { fetchStatus: _ignored, ...serializedState } = state\n query.setState({\n ...serializedState,\n data,\n // If the query was pending at the moment of dehydration, but resolved to have data\n // before hydration, we can assume the query should be hydrated as successful.\n //\n // Since you can opt into dehydrating failed queries, and those can have data from\n // previous successful fetches, we make sure we only do this for pending queries.\n ...(state.status === 'pending' &&\n data !== undefined && {\n status: 'success' as const,\n // Preserve existing fetchStatus if the existing query is actively fetching.\n ...(!existingQueryIsFetching && {\n fetchStatus: 'idle' as const,\n }),\n }),\n })\n }\n } else {\n // Restore query\n query = queryCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.queries,\n ...options?.defaultOptions?.queries,\n queryKey,\n queryHash,\n meta,\n },\n // Reset fetch status to idle to avoid\n // query being stuck in fetching state upon hydration\n {\n ...state,\n data,\n fetchStatus: 'idle',\n // Like above, if the query was pending at the moment of dehydration but has data,\n // we can assume it should be hydrated as successful.\n status:\n state.status === 'pending' && data !== undefined\n ? 'success'\n : state.status,\n },\n )\n }\n\n if (\n promise &&\n // If the data was synchronously available, there is no need to set up\n // a retryer and thus no reason to call fetch\n !syncData &&\n !existingQueryIsPending &&\n !existingQueryIsFetching &&\n // Only hydrate if dehydration is newer than any existing data,\n // this is always true for new queries\n (dehydratedAt === undefined || dehydratedAt > query.state.dataUpdatedAt)\n ) {\n // This doesn't actually fetch - it just creates a retryer\n // which will re-use the passed `initialPromise`\n query\n .fetch(undefined, {\n // RSC transformed promises are not thenable\n initialPromise: Promise.resolve(promise).then(deserializeData),\n })\n // Avoid unhandled promise rejections\n .catch(noop)\n }\n },\n )\n}\n"],"mappings":"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","names":["_a","_b"]}
@@ -129,7 +129,19 @@ function hydrate(client, dehydratedState, options) {
129
129
  const { fetchStatus: _ignored, ...serializedState } = state;
130
130
  query.setState({
131
131
  ...serializedState,
132
- data
132
+ data,
133
+ // If the query was pending at the moment of dehydration, but resolved to have data
134
+ // before hydration, we can assume the query should be hydrated as successful.
135
+ //
136
+ // Since you can opt into dehydrating failed queries, and those can have data from
137
+ // previous successful fetches, we make sure we only do this for pending queries.
138
+ ...state.status === "pending" && data !== void 0 && {
139
+ status: "success",
140
+ // Preserve existing fetchStatus if the existing query is actively fetching.
141
+ ...!existingQueryIsFetching && {
142
+ fetchStatus: "idle"
143
+ }
144
+ }
133
145
  });
134
146
  }
135
147
  } else {
@@ -148,11 +160,15 @@ function hydrate(client, dehydratedState, options) {
148
160
  ...state,
149
161
  data,
150
162
  fetchStatus: "idle",
151
- status: data !== void 0 ? "success" : state.status
163
+ // Like above, if the query was pending at the moment of dehydration but has data,
164
+ // we can assume it should be hydrated as successful.
165
+ status: state.status === "pending" && data !== void 0 ? "success" : state.status
152
166
  }
153
167
  );
154
168
  }
155
- if (promise && !existingQueryIsPending && !existingQueryIsFetching && // Only hydrate if dehydration is newer than any existing data,
169
+ if (promise && // If the data was synchronously available, there is no need to set up
170
+ // a retryer and thus no reason to call fetch
171
+ !syncData && !existingQueryIsPending && !existingQueryIsFetching && // Only hydrate if dehydration is newer than any existing data,
156
172
  // this is always true for new queries
157
173
  (dehydratedAt === void 0 || dehydratedAt > query.state.dataUpdatedAt)) {
158
174
  query.fetch(void 0, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hydration.ts"],"sourcesContent":["import { tryResolveSync } from './thenable'\nimport { noop } from './utils'\nimport type {\n DefaultError,\n MutationKey,\n MutationMeta,\n MutationOptions,\n MutationScope,\n QueryKey,\n QueryMeta,\n QueryOptions,\n} from './types'\nimport type { QueryClient } from './queryClient'\nimport type { Query, QueryState } from './query'\nimport type { Mutation, MutationState } from './mutation'\n\n// TYPES\ntype TransformerFn = (data: any) => any\nfunction defaultTransformerFn(data: any): any {\n return data\n}\n\nexport interface DehydrateOptions {\n serializeData?: TransformerFn\n shouldDehydrateMutation?: (mutation: Mutation) => boolean\n shouldDehydrateQuery?: (query: Query) => boolean\n shouldRedactErrors?: (error: unknown) => boolean\n}\n\nexport interface HydrateOptions {\n defaultOptions?: {\n deserializeData?: TransformerFn\n queries?: QueryOptions\n mutations?: MutationOptions<unknown, DefaultError, unknown, unknown>\n }\n}\n\ninterface DehydratedMutation {\n mutationKey?: MutationKey\n state: MutationState\n meta?: MutationMeta\n scope?: MutationScope\n}\n\ninterface DehydratedQuery {\n queryHash: string\n queryKey: QueryKey\n state: QueryState\n promise?: Promise<unknown>\n meta?: QueryMeta\n // This is only optional because older versions of Query might have dehydrated\n // without it which we need to handle for backwards compatibility.\n // This should be changed to required in the future.\n dehydratedAt?: number\n}\n\nexport interface DehydratedState {\n mutations: Array<DehydratedMutation>\n queries: Array<DehydratedQuery>\n}\n\n// FUNCTIONS\n\nfunction dehydrateMutation(mutation: Mutation): DehydratedMutation {\n return {\n mutationKey: mutation.options.mutationKey,\n state: mutation.state,\n ...(mutation.options.scope && { scope: mutation.options.scope }),\n ...(mutation.meta && { meta: mutation.meta }),\n }\n}\n\n// Most config is not dehydrated but instead meant to configure again when\n// consuming the de/rehydrated data, typically with useQuery on the client.\n// Sometimes it might make sense to prefetch data on the server and include\n// in the html-payload, but not consume it on the initial render.\nfunction dehydrateQuery(\n query: Query,\n serializeData: TransformerFn,\n shouldRedactErrors: (error: unknown) => boolean,\n): DehydratedQuery {\n const dehydratePromise = () => {\n const promise = query.promise?.then(serializeData).catch((error) => {\n if (!shouldRedactErrors(error)) {\n // Reject original error if it should not be redacted\n return Promise.reject(error)\n }\n // If not in production, log original error before rejecting redacted error\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n `A query that was dehydrated as pending ended up rejecting. [${query.queryHash}]: ${error}; The error will be redacted in production builds`,\n )\n }\n return Promise.reject(new Error('redacted'))\n })\n\n // Avoid unhandled promise rejections\n // We need the promise we dehydrate to reject to get the correct result into\n // the query cache, but we also want to avoid unhandled promise rejections\n // in whatever environment the prefetches are happening in.\n promise?.catch(noop)\n\n return promise\n }\n\n return {\n dehydratedAt: Date.now(),\n state: {\n ...query.state,\n ...(query.state.data !== undefined && {\n data: serializeData(query.state.data),\n }),\n },\n queryKey: query.queryKey,\n queryHash: query.queryHash,\n ...(query.state.status === 'pending' && {\n promise: dehydratePromise(),\n }),\n ...(query.meta && { meta: query.meta }),\n }\n}\n\nexport function defaultShouldDehydrateMutation(mutation: Mutation) {\n return mutation.state.isPaused\n}\n\nexport function defaultShouldDehydrateQuery(query: Query) {\n return query.state.status === 'success'\n}\n\nfunction defaultShouldRedactErrors(_: unknown) {\n return true\n}\n\nexport function dehydrate(\n client: QueryClient,\n options: DehydrateOptions = {},\n): DehydratedState {\n const filterMutation =\n options.shouldDehydrateMutation ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateMutation ??\n defaultShouldDehydrateMutation\n\n const mutations = client\n .getMutationCache()\n .getAll()\n .flatMap((mutation) =>\n filterMutation(mutation) ? [dehydrateMutation(mutation)] : [],\n )\n\n const filterQuery =\n options.shouldDehydrateQuery ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateQuery ??\n defaultShouldDehydrateQuery\n\n const shouldRedactErrors =\n options.shouldRedactErrors ??\n client.getDefaultOptions().dehydrate?.shouldRedactErrors ??\n defaultShouldRedactErrors\n\n const serializeData =\n options.serializeData ??\n client.getDefaultOptions().dehydrate?.serializeData ??\n defaultTransformerFn\n\n const queries = client\n .getQueryCache()\n .getAll()\n .flatMap((query) =>\n filterQuery(query)\n ? [dehydrateQuery(query, serializeData, shouldRedactErrors)]\n : [],\n )\n\n return { mutations, queries }\n}\n\nexport function hydrate(\n client: QueryClient,\n dehydratedState: unknown,\n options?: HydrateOptions,\n): void {\n if (typeof dehydratedState !== 'object' || dehydratedState === null) {\n return\n }\n\n const mutationCache = client.getMutationCache()\n const queryCache = client.getQueryCache()\n const deserializeData =\n options?.defaultOptions?.deserializeData ??\n client.getDefaultOptions().hydrate?.deserializeData ??\n defaultTransformerFn\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const mutations = (dehydratedState as DehydratedState).mutations || []\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const queries = (dehydratedState as DehydratedState).queries || []\n\n mutations.forEach(({ state, ...mutationOptions }) => {\n mutationCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.mutations,\n ...options?.defaultOptions?.mutations,\n ...mutationOptions,\n },\n state,\n )\n })\n\n queries.forEach(\n ({ queryKey, state, queryHash, meta, promise, dehydratedAt }) => {\n const syncData = promise ? tryResolveSync(promise) : undefined\n const rawData = state.data === undefined ? syncData?.data : state.data\n const data = rawData === undefined ? rawData : deserializeData(rawData)\n\n let query = queryCache.get(queryHash)\n const existingQueryIsPending = query?.state.status === 'pending'\n const existingQueryIsFetching = query?.state.fetchStatus === 'fetching'\n\n // Do not hydrate if an existing query exists with newer data\n if (query) {\n const hasNewerSyncData =\n syncData &&\n // We only need this undefined check to handle older dehydration\n // payloads that might not have dehydratedAt\n dehydratedAt !== undefined &&\n dehydratedAt > query.state.dataUpdatedAt\n if (\n state.dataUpdatedAt > query.state.dataUpdatedAt ||\n hasNewerSyncData\n ) {\n // omit fetchStatus from dehydrated state\n // so that query stays in its current fetchStatus\n const { fetchStatus: _ignored, ...serializedState } = state\n query.setState({\n ...serializedState,\n data,\n })\n }\n } else {\n // Restore query\n query = queryCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.queries,\n ...options?.defaultOptions?.queries,\n queryKey,\n queryHash,\n meta,\n },\n // Reset fetch status to idle to avoid\n // query being stuck in fetching state upon hydration\n {\n ...state,\n data,\n fetchStatus: 'idle',\n status: data !== undefined ? 'success' : state.status,\n },\n )\n }\n\n if (\n promise &&\n !existingQueryIsPending &&\n !existingQueryIsFetching &&\n // Only hydrate if dehydration is newer than any existing data,\n // this is always true for new queries\n (dehydratedAt === undefined || dehydratedAt > query.state.dataUpdatedAt)\n ) {\n // This doesn't actually fetch - it just creates a retryer\n // which will re-use the passed `initialPromise`\n // Note that we need to call these even when data was synchronously\n // available, as we still need to set up the retryer\n query\n .fetch(undefined, {\n // RSC transformed promises are not thenable\n initialPromise: Promise.resolve(promise).then(deserializeData),\n })\n // Avoid unhandled promise rejections\n .catch(noop)\n }\n },\n )\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/hydration.ts"],"sourcesContent":["import { tryResolveSync } from './thenable'\nimport { noop } from './utils'\nimport type {\n DefaultError,\n MutationKey,\n MutationMeta,\n MutationOptions,\n MutationScope,\n QueryKey,\n QueryMeta,\n QueryOptions,\n} from './types'\nimport type { QueryClient } from './queryClient'\nimport type { Query, QueryState } from './query'\nimport type { Mutation, MutationState } from './mutation'\n\n// TYPES\ntype TransformerFn = (data: any) => any\nfunction defaultTransformerFn(data: any): any {\n return data\n}\n\nexport interface DehydrateOptions {\n serializeData?: TransformerFn\n shouldDehydrateMutation?: (mutation: Mutation) => boolean\n shouldDehydrateQuery?: (query: Query) => boolean\n shouldRedactErrors?: (error: unknown) => boolean\n}\n\nexport interface HydrateOptions {\n defaultOptions?: {\n deserializeData?: TransformerFn\n queries?: QueryOptions\n mutations?: MutationOptions<unknown, DefaultError, unknown, unknown>\n }\n}\n\ninterface DehydratedMutation {\n mutationKey?: MutationKey\n state: MutationState\n meta?: MutationMeta\n scope?: MutationScope\n}\n\ninterface DehydratedQuery {\n queryHash: string\n queryKey: QueryKey\n state: QueryState\n promise?: Promise<unknown>\n meta?: QueryMeta\n // This is only optional because older versions of Query might have dehydrated\n // without it which we need to handle for backwards compatibility.\n // This should be changed to required in the future.\n dehydratedAt?: number\n}\n\nexport interface DehydratedState {\n mutations: Array<DehydratedMutation>\n queries: Array<DehydratedQuery>\n}\n\n// FUNCTIONS\n\nfunction dehydrateMutation(mutation: Mutation): DehydratedMutation {\n return {\n mutationKey: mutation.options.mutationKey,\n state: mutation.state,\n ...(mutation.options.scope && { scope: mutation.options.scope }),\n ...(mutation.meta && { meta: mutation.meta }),\n }\n}\n\n// Most config is not dehydrated but instead meant to configure again when\n// consuming the de/rehydrated data, typically with useQuery on the client.\n// Sometimes it might make sense to prefetch data on the server and include\n// in the html-payload, but not consume it on the initial render.\nfunction dehydrateQuery(\n query: Query,\n serializeData: TransformerFn,\n shouldRedactErrors: (error: unknown) => boolean,\n): DehydratedQuery {\n const dehydratePromise = () => {\n const promise = query.promise?.then(serializeData).catch((error) => {\n if (!shouldRedactErrors(error)) {\n // Reject original error if it should not be redacted\n return Promise.reject(error)\n }\n // If not in production, log original error before rejecting redacted error\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n `A query that was dehydrated as pending ended up rejecting. [${query.queryHash}]: ${error}; The error will be redacted in production builds`,\n )\n }\n return Promise.reject(new Error('redacted'))\n })\n\n // Avoid unhandled promise rejections\n // We need the promise we dehydrate to reject to get the correct result into\n // the query cache, but we also want to avoid unhandled promise rejections\n // in whatever environment the prefetches are happening in.\n promise?.catch(noop)\n\n return promise\n }\n\n return {\n dehydratedAt: Date.now(),\n state: {\n ...query.state,\n ...(query.state.data !== undefined && {\n data: serializeData(query.state.data),\n }),\n },\n queryKey: query.queryKey,\n queryHash: query.queryHash,\n ...(query.state.status === 'pending' && {\n promise: dehydratePromise(),\n }),\n ...(query.meta && { meta: query.meta }),\n }\n}\n\nexport function defaultShouldDehydrateMutation(mutation: Mutation) {\n return mutation.state.isPaused\n}\n\nexport function defaultShouldDehydrateQuery(query: Query) {\n return query.state.status === 'success'\n}\n\nfunction defaultShouldRedactErrors(_: unknown) {\n return true\n}\n\nexport function dehydrate(\n client: QueryClient,\n options: DehydrateOptions = {},\n): DehydratedState {\n const filterMutation =\n options.shouldDehydrateMutation ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateMutation ??\n defaultShouldDehydrateMutation\n\n const mutations = client\n .getMutationCache()\n .getAll()\n .flatMap((mutation) =>\n filterMutation(mutation) ? [dehydrateMutation(mutation)] : [],\n )\n\n const filterQuery =\n options.shouldDehydrateQuery ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateQuery ??\n defaultShouldDehydrateQuery\n\n const shouldRedactErrors =\n options.shouldRedactErrors ??\n client.getDefaultOptions().dehydrate?.shouldRedactErrors ??\n defaultShouldRedactErrors\n\n const serializeData =\n options.serializeData ??\n client.getDefaultOptions().dehydrate?.serializeData ??\n defaultTransformerFn\n\n const queries = client\n .getQueryCache()\n .getAll()\n .flatMap((query) =>\n filterQuery(query)\n ? [dehydrateQuery(query, serializeData, shouldRedactErrors)]\n : [],\n )\n\n return { mutations, queries }\n}\n\nexport function hydrate(\n client: QueryClient,\n dehydratedState: unknown,\n options?: HydrateOptions,\n): void {\n if (typeof dehydratedState !== 'object' || dehydratedState === null) {\n return\n }\n\n const mutationCache = client.getMutationCache()\n const queryCache = client.getQueryCache()\n const deserializeData =\n options?.defaultOptions?.deserializeData ??\n client.getDefaultOptions().hydrate?.deserializeData ??\n defaultTransformerFn\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const mutations = (dehydratedState as DehydratedState).mutations || []\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const queries = (dehydratedState as DehydratedState).queries || []\n\n mutations.forEach(({ state, ...mutationOptions }) => {\n mutationCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.mutations,\n ...options?.defaultOptions?.mutations,\n ...mutationOptions,\n },\n state,\n )\n })\n\n queries.forEach(\n ({ queryKey, state, queryHash, meta, promise, dehydratedAt }) => {\n const syncData = promise ? tryResolveSync(promise) : undefined\n const rawData = state.data === undefined ? syncData?.data : state.data\n const data = rawData === undefined ? rawData : deserializeData(rawData)\n\n let query = queryCache.get(queryHash)\n const existingQueryIsPending = query?.state.status === 'pending'\n const existingQueryIsFetching = query?.state.fetchStatus === 'fetching'\n\n // Do not hydrate if an existing query exists with newer data\n if (query) {\n const hasNewerSyncData =\n syncData &&\n // We only need this undefined check to handle older dehydration\n // payloads that might not have dehydratedAt\n dehydratedAt !== undefined &&\n dehydratedAt > query.state.dataUpdatedAt\n if (\n state.dataUpdatedAt > query.state.dataUpdatedAt ||\n hasNewerSyncData\n ) {\n // Omit fetchStatus from dehydrated state so that query stays in its current fetchStatus\n const { fetchStatus: _ignored, ...serializedState } = state\n query.setState({\n ...serializedState,\n data,\n // If the query was pending at the moment of dehydration, but resolved to have data\n // before hydration, we can assume the query should be hydrated as successful.\n //\n // Since you can opt into dehydrating failed queries, and those can have data from\n // previous successful fetches, we make sure we only do this for pending queries.\n ...(state.status === 'pending' &&\n data !== undefined && {\n status: 'success' as const,\n // Preserve existing fetchStatus if the existing query is actively fetching.\n ...(!existingQueryIsFetching && {\n fetchStatus: 'idle' as const,\n }),\n }),\n })\n }\n } else {\n // Restore query\n query = queryCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.queries,\n ...options?.defaultOptions?.queries,\n queryKey,\n queryHash,\n meta,\n },\n // Reset fetch status to idle to avoid\n // query being stuck in fetching state upon hydration\n {\n ...state,\n data,\n fetchStatus: 'idle',\n // Like above, if the query was pending at the moment of dehydration but has data,\n // we can assume it should be hydrated as successful.\n status:\n state.status === 'pending' && data !== undefined\n ? 'success'\n : state.status,\n },\n )\n }\n\n if (\n promise &&\n // If the data was synchronously available, there is no need to set up\n // a retryer and thus no reason to call fetch\n !syncData &&\n !existingQueryIsPending &&\n !existingQueryIsFetching &&\n // Only hydrate if dehydration is newer than any existing data,\n // this is always true for new queries\n (dehydratedAt === undefined || dehydratedAt > query.state.dataUpdatedAt)\n ) {\n // This doesn't actually fetch - it just creates a retryer\n // which will re-use the passed `initialPromise`\n query\n .fetch(undefined, {\n // RSC transformed promises are not thenable\n initialPromise: Promise.resolve(promise).then(deserializeData),\n })\n // Avoid unhandled promise rejections\n .catch(noop)\n }\n },\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAA+B;AAC/B,mBAAqB;AAiBrB,SAAS,qBAAqB,MAAgB;AAC5C,SAAO;AACT;AA2CA,SAAS,kBAAkB,UAAwC;AACjE,SAAO;AAAA,IACL,aAAa,SAAS,QAAQ;AAAA,IAC9B,OAAO,SAAS;AAAA,IAChB,GAAI,SAAS,QAAQ,SAAS,EAAE,OAAO,SAAS,QAAQ,MAAM;AAAA,IAC9D,GAAI,SAAS,QAAQ,EAAE,MAAM,SAAS,KAAK;AAAA,EAC7C;AACF;AAMA,SAAS,eACP,OACA,eACA,oBACiB;AACjB,QAAM,mBAAmB,MAAM;AAC7B,UAAM,UAAU,MAAM,SAAS,KAAK,aAAa,EAAE,MAAM,CAAC,UAAU;AAClE,UAAI,CAAC,mBAAmB,KAAK,GAAG;AAE9B,eAAO,QAAQ,OAAO,KAAK;AAAA,MAC7B;AAEA,UAAI,QAAQ,IAAI,aAAa,cAAc;AACzC,gBAAQ;AAAA,UACN,+DAA+D,MAAM,SAAS,MAAM,KAAK;AAAA,QAC3F;AAAA,MACF;AACA,aAAO,QAAQ,OAAO,IAAI,MAAM,UAAU,CAAC;AAAA,IAC7C,CAAC;AAMD,aAAS,MAAM,iBAAI;AAEnB,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,cAAc,KAAK,IAAI;AAAA,IACvB,OAAO;AAAA,MACL,GAAG,MAAM;AAAA,MACT,GAAI,MAAM,MAAM,SAAS,UAAa;AAAA,QACpC,MAAM,cAAc,MAAM,MAAM,IAAI;AAAA,MACtC;AAAA,IACF;AAAA,IACA,UAAU,MAAM;AAAA,IAChB,WAAW,MAAM;AAAA,IACjB,GAAI,MAAM,MAAM,WAAW,aAAa;AAAA,MACtC,SAAS,iBAAiB;AAAA,IAC5B;AAAA,IACA,GAAI,MAAM,QAAQ,EAAE,MAAM,MAAM,KAAK;AAAA,EACvC;AACF;AAEO,SAAS,+BAA+B,UAAoB;AACjE,SAAO,SAAS,MAAM;AACxB;AAEO,SAAS,4BAA4B,OAAc;AACxD,SAAO,MAAM,MAAM,WAAW;AAChC;AAEA,SAAS,0BAA0B,GAAY;AAC7C,SAAO;AACT;AAEO,SAAS,UACd,QACA,UAA4B,CAAC,GACZ;AACjB,QAAM,iBACJ,QAAQ,2BACR,OAAO,kBAAkB,EAAE,WAAW,2BACtC;AAEF,QAAM,YAAY,OACf,iBAAiB,EACjB,OAAO,EACP;AAAA,IAAQ,CAAC,aACR,eAAe,QAAQ,IAAI,CAAC,kBAAkB,QAAQ,CAAC,IAAI,CAAC;AAAA,EAC9D;AAEF,QAAM,cACJ,QAAQ,wBACR,OAAO,kBAAkB,EAAE,WAAW,wBACtC;AAEF,QAAM,qBACJ,QAAQ,sBACR,OAAO,kBAAkB,EAAE,WAAW,sBACtC;AAEF,QAAM,gBACJ,QAAQ,iBACR,OAAO,kBAAkB,EAAE,WAAW,iBACtC;AAEF,QAAM,UAAU,OACb,cAAc,EACd,OAAO,EACP;AAAA,IAAQ,CAAC,UACR,YAAY,KAAK,IACb,CAAC,eAAe,OAAO,eAAe,kBAAkB,CAAC,IACzD,CAAC;AAAA,EACP;AAEF,SAAO,EAAE,WAAW,QAAQ;AAC9B;AAEO,SAAS,QACd,QACA,iBACA,SACM;AACN,MAAI,OAAO,oBAAoB,YAAY,oBAAoB,MAAM;AACnE;AAAA,EACF;AAEA,QAAM,gBAAgB,OAAO,iBAAiB;AAC9C,QAAM,aAAa,OAAO,cAAc;AACxC,QAAM,kBACJ,SAAS,gBAAgB,mBACzB,OAAO,kBAAkB,EAAE,SAAS,mBACpC;AAGF,QAAM,YAAa,gBAAoC,aAAa,CAAC;AAErE,QAAM,UAAW,gBAAoC,WAAW,CAAC;AAEjE,YAAU,QAAQ,CAAC,EAAE,OAAO,GAAG,gBAAgB,MAAM;AACnD,kBAAc;AAAA,MACZ;AAAA,MACA;AAAA,QACE,GAAG,OAAO,kBAAkB,EAAE,SAAS;AAAA,QACvC,GAAG,SAAS,gBAAgB;AAAA,QAC5B,GAAG;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AAED,UAAQ;AAAA,IACN,CAAC,EAAE,UAAU,OAAO,WAAW,MAAM,SAAS,aAAa,MAAM;AAC/D,YAAM,WAAW,cAAU,gCAAe,OAAO,IAAI;AACrD,YAAM,UAAU,MAAM,SAAS,SAAY,UAAU,OAAO,MAAM;AAClE,YAAM,OAAO,YAAY,SAAY,UAAU,gBAAgB,OAAO;AAEtE,UAAI,QAAQ,WAAW,IAAI,SAAS;AACpC,YAAM,yBAAyB,OAAO,MAAM,WAAW;AACvD,YAAM,0BAA0B,OAAO,MAAM,gBAAgB;AAG7D,UAAI,OAAO;AACT,cAAM,mBACJ;AAAA;AAAA,QAGA,iBAAiB,UACjB,eAAe,MAAM,MAAM;AAC7B,YACE,MAAM,gBAAgB,MAAM,MAAM,iBAClC,kBACA;AAEA,gBAAM,EAAE,aAAa,UAAU,GAAG,gBAAgB,IAAI;AACtD,gBAAM,SAAS;AAAA,YACb,GAAG;AAAA,YACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAMA,GAAI,MAAM,WAAW,aACnB,SAAS,UAAa;AAAA,cACpB,QAAQ;AAAA;AAAA,cAER,GAAI,CAAC,2BAA2B;AAAA,gBAC9B,aAAa;AAAA,cACf;AAAA,YACF;AAAA,UACJ,CAAC;AAAA,QACH;AAAA,MACF,OAAO;AAEL,gBAAQ,WAAW;AAAA,UACjB;AAAA,UACA;AAAA,YACE,GAAG,OAAO,kBAAkB,EAAE,SAAS;AAAA,YACvC,GAAG,SAAS,gBAAgB;AAAA,YAC5B;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA;AAAA;AAAA,UAGA;AAAA,YACE,GAAG;AAAA,YACH;AAAA,YACA,aAAa;AAAA;AAAA;AAAA,YAGb,QACE,MAAM,WAAW,aAAa,SAAS,SACnC,YACA,MAAM;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAEA,UACE;AAAA;AAAA,MAGA,CAAC,YACD,CAAC,0BACD,CAAC;AAAA;AAAA,OAGA,iBAAiB,UAAa,eAAe,MAAM,MAAM,gBAC1D;AAGA,cACG,MAAM,QAAW;AAAA;AAAA,UAEhB,gBAAgB,QAAQ,QAAQ,OAAO,EAAE,KAAK,eAAe;AAAA,QAC/D,CAAC,EAEA,MAAM,iBAAI;AAAA,MACf;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -102,7 +102,19 @@ function hydrate(client, dehydratedState, options) {
102
102
  const { fetchStatus: _ignored, ...serializedState } = state;
103
103
  query.setState({
104
104
  ...serializedState,
105
- data
105
+ data,
106
+ // If the query was pending at the moment of dehydration, but resolved to have data
107
+ // before hydration, we can assume the query should be hydrated as successful.
108
+ //
109
+ // Since you can opt into dehydrating failed queries, and those can have data from
110
+ // previous successful fetches, we make sure we only do this for pending queries.
111
+ ...state.status === "pending" && data !== void 0 && {
112
+ status: "success",
113
+ // Preserve existing fetchStatus if the existing query is actively fetching.
114
+ ...!existingQueryIsFetching && {
115
+ fetchStatus: "idle"
116
+ }
117
+ }
106
118
  });
107
119
  }
108
120
  } else {
@@ -121,11 +133,15 @@ function hydrate(client, dehydratedState, options) {
121
133
  ...state,
122
134
  data,
123
135
  fetchStatus: "idle",
124
- status: data !== void 0 ? "success" : state.status
136
+ // Like above, if the query was pending at the moment of dehydration but has data,
137
+ // we can assume it should be hydrated as successful.
138
+ status: state.status === "pending" && data !== void 0 ? "success" : state.status
125
139
  }
126
140
  );
127
141
  }
128
- if (promise && !existingQueryIsPending && !existingQueryIsFetching && // Only hydrate if dehydration is newer than any existing data,
142
+ if (promise && // If the data was synchronously available, there is no need to set up
143
+ // a retryer and thus no reason to call fetch
144
+ !syncData && !existingQueryIsPending && !existingQueryIsFetching && // Only hydrate if dehydration is newer than any existing data,
129
145
  // this is always true for new queries
130
146
  (dehydratedAt === void 0 || dehydratedAt > query.state.dataUpdatedAt)) {
131
147
  query.fetch(void 0, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hydration.ts"],"sourcesContent":["import { tryResolveSync } from './thenable'\nimport { noop } from './utils'\nimport type {\n DefaultError,\n MutationKey,\n MutationMeta,\n MutationOptions,\n MutationScope,\n QueryKey,\n QueryMeta,\n QueryOptions,\n} from './types'\nimport type { QueryClient } from './queryClient'\nimport type { Query, QueryState } from './query'\nimport type { Mutation, MutationState } from './mutation'\n\n// TYPES\ntype TransformerFn = (data: any) => any\nfunction defaultTransformerFn(data: any): any {\n return data\n}\n\nexport interface DehydrateOptions {\n serializeData?: TransformerFn\n shouldDehydrateMutation?: (mutation: Mutation) => boolean\n shouldDehydrateQuery?: (query: Query) => boolean\n shouldRedactErrors?: (error: unknown) => boolean\n}\n\nexport interface HydrateOptions {\n defaultOptions?: {\n deserializeData?: TransformerFn\n queries?: QueryOptions\n mutations?: MutationOptions<unknown, DefaultError, unknown, unknown>\n }\n}\n\ninterface DehydratedMutation {\n mutationKey?: MutationKey\n state: MutationState\n meta?: MutationMeta\n scope?: MutationScope\n}\n\ninterface DehydratedQuery {\n queryHash: string\n queryKey: QueryKey\n state: QueryState\n promise?: Promise<unknown>\n meta?: QueryMeta\n // This is only optional because older versions of Query might have dehydrated\n // without it which we need to handle for backwards compatibility.\n // This should be changed to required in the future.\n dehydratedAt?: number\n}\n\nexport interface DehydratedState {\n mutations: Array<DehydratedMutation>\n queries: Array<DehydratedQuery>\n}\n\n// FUNCTIONS\n\nfunction dehydrateMutation(mutation: Mutation): DehydratedMutation {\n return {\n mutationKey: mutation.options.mutationKey,\n state: mutation.state,\n ...(mutation.options.scope && { scope: mutation.options.scope }),\n ...(mutation.meta && { meta: mutation.meta }),\n }\n}\n\n// Most config is not dehydrated but instead meant to configure again when\n// consuming the de/rehydrated data, typically with useQuery on the client.\n// Sometimes it might make sense to prefetch data on the server and include\n// in the html-payload, but not consume it on the initial render.\nfunction dehydrateQuery(\n query: Query,\n serializeData: TransformerFn,\n shouldRedactErrors: (error: unknown) => boolean,\n): DehydratedQuery {\n const dehydratePromise = () => {\n const promise = query.promise?.then(serializeData).catch((error) => {\n if (!shouldRedactErrors(error)) {\n // Reject original error if it should not be redacted\n return Promise.reject(error)\n }\n // If not in production, log original error before rejecting redacted error\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n `A query that was dehydrated as pending ended up rejecting. [${query.queryHash}]: ${error}; The error will be redacted in production builds`,\n )\n }\n return Promise.reject(new Error('redacted'))\n })\n\n // Avoid unhandled promise rejections\n // We need the promise we dehydrate to reject to get the correct result into\n // the query cache, but we also want to avoid unhandled promise rejections\n // in whatever environment the prefetches are happening in.\n promise?.catch(noop)\n\n return promise\n }\n\n return {\n dehydratedAt: Date.now(),\n state: {\n ...query.state,\n ...(query.state.data !== undefined && {\n data: serializeData(query.state.data),\n }),\n },\n queryKey: query.queryKey,\n queryHash: query.queryHash,\n ...(query.state.status === 'pending' && {\n promise: dehydratePromise(),\n }),\n ...(query.meta && { meta: query.meta }),\n }\n}\n\nexport function defaultShouldDehydrateMutation(mutation: Mutation) {\n return mutation.state.isPaused\n}\n\nexport function defaultShouldDehydrateQuery(query: Query) {\n return query.state.status === 'success'\n}\n\nfunction defaultShouldRedactErrors(_: unknown) {\n return true\n}\n\nexport function dehydrate(\n client: QueryClient,\n options: DehydrateOptions = {},\n): DehydratedState {\n const filterMutation =\n options.shouldDehydrateMutation ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateMutation ??\n defaultShouldDehydrateMutation\n\n const mutations = client\n .getMutationCache()\n .getAll()\n .flatMap((mutation) =>\n filterMutation(mutation) ? [dehydrateMutation(mutation)] : [],\n )\n\n const filterQuery =\n options.shouldDehydrateQuery ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateQuery ??\n defaultShouldDehydrateQuery\n\n const shouldRedactErrors =\n options.shouldRedactErrors ??\n client.getDefaultOptions().dehydrate?.shouldRedactErrors ??\n defaultShouldRedactErrors\n\n const serializeData =\n options.serializeData ??\n client.getDefaultOptions().dehydrate?.serializeData ??\n defaultTransformerFn\n\n const queries = client\n .getQueryCache()\n .getAll()\n .flatMap((query) =>\n filterQuery(query)\n ? [dehydrateQuery(query, serializeData, shouldRedactErrors)]\n : [],\n )\n\n return { mutations, queries }\n}\n\nexport function hydrate(\n client: QueryClient,\n dehydratedState: unknown,\n options?: HydrateOptions,\n): void {\n if (typeof dehydratedState !== 'object' || dehydratedState === null) {\n return\n }\n\n const mutationCache = client.getMutationCache()\n const queryCache = client.getQueryCache()\n const deserializeData =\n options?.defaultOptions?.deserializeData ??\n client.getDefaultOptions().hydrate?.deserializeData ??\n defaultTransformerFn\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const mutations = (dehydratedState as DehydratedState).mutations || []\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const queries = (dehydratedState as DehydratedState).queries || []\n\n mutations.forEach(({ state, ...mutationOptions }) => {\n mutationCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.mutations,\n ...options?.defaultOptions?.mutations,\n ...mutationOptions,\n },\n state,\n )\n })\n\n queries.forEach(\n ({ queryKey, state, queryHash, meta, promise, dehydratedAt }) => {\n const syncData = promise ? tryResolveSync(promise) : undefined\n const rawData = state.data === undefined ? syncData?.data : state.data\n const data = rawData === undefined ? rawData : deserializeData(rawData)\n\n let query = queryCache.get(queryHash)\n const existingQueryIsPending = query?.state.status === 'pending'\n const existingQueryIsFetching = query?.state.fetchStatus === 'fetching'\n\n // Do not hydrate if an existing query exists with newer data\n if (query) {\n const hasNewerSyncData =\n syncData &&\n // We only need this undefined check to handle older dehydration\n // payloads that might not have dehydratedAt\n dehydratedAt !== undefined &&\n dehydratedAt > query.state.dataUpdatedAt\n if (\n state.dataUpdatedAt > query.state.dataUpdatedAt ||\n hasNewerSyncData\n ) {\n // omit fetchStatus from dehydrated state\n // so that query stays in its current fetchStatus\n const { fetchStatus: _ignored, ...serializedState } = state\n query.setState({\n ...serializedState,\n data,\n })\n }\n } else {\n // Restore query\n query = queryCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.queries,\n ...options?.defaultOptions?.queries,\n queryKey,\n queryHash,\n meta,\n },\n // Reset fetch status to idle to avoid\n // query being stuck in fetching state upon hydration\n {\n ...state,\n data,\n fetchStatus: 'idle',\n status: data !== undefined ? 'success' : state.status,\n },\n )\n }\n\n if (\n promise &&\n !existingQueryIsPending &&\n !existingQueryIsFetching &&\n // Only hydrate if dehydration is newer than any existing data,\n // this is always true for new queries\n (dehydratedAt === undefined || dehydratedAt > query.state.dataUpdatedAt)\n ) {\n // This doesn't actually fetch - it just creates a retryer\n // which will re-use the passed `initialPromise`\n // Note that we need to call these even when data was synchronously\n // available, as we still need to set up the retryer\n query\n .fetch(undefined, {\n // RSC transformed promises are not thenable\n initialPromise: Promise.resolve(promise).then(deserializeData),\n })\n // Avoid unhandled promise rejections\n .catch(noop)\n }\n },\n )\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/hydration.ts"],"sourcesContent":["import { tryResolveSync } from './thenable'\nimport { noop } from './utils'\nimport type {\n DefaultError,\n MutationKey,\n MutationMeta,\n MutationOptions,\n MutationScope,\n QueryKey,\n QueryMeta,\n QueryOptions,\n} from './types'\nimport type { QueryClient } from './queryClient'\nimport type { Query, QueryState } from './query'\nimport type { Mutation, MutationState } from './mutation'\n\n// TYPES\ntype TransformerFn = (data: any) => any\nfunction defaultTransformerFn(data: any): any {\n return data\n}\n\nexport interface DehydrateOptions {\n serializeData?: TransformerFn\n shouldDehydrateMutation?: (mutation: Mutation) => boolean\n shouldDehydrateQuery?: (query: Query) => boolean\n shouldRedactErrors?: (error: unknown) => boolean\n}\n\nexport interface HydrateOptions {\n defaultOptions?: {\n deserializeData?: TransformerFn\n queries?: QueryOptions\n mutations?: MutationOptions<unknown, DefaultError, unknown, unknown>\n }\n}\n\ninterface DehydratedMutation {\n mutationKey?: MutationKey\n state: MutationState\n meta?: MutationMeta\n scope?: MutationScope\n}\n\ninterface DehydratedQuery {\n queryHash: string\n queryKey: QueryKey\n state: QueryState\n promise?: Promise<unknown>\n meta?: QueryMeta\n // This is only optional because older versions of Query might have dehydrated\n // without it which we need to handle for backwards compatibility.\n // This should be changed to required in the future.\n dehydratedAt?: number\n}\n\nexport interface DehydratedState {\n mutations: Array<DehydratedMutation>\n queries: Array<DehydratedQuery>\n}\n\n// FUNCTIONS\n\nfunction dehydrateMutation(mutation: Mutation): DehydratedMutation {\n return {\n mutationKey: mutation.options.mutationKey,\n state: mutation.state,\n ...(mutation.options.scope && { scope: mutation.options.scope }),\n ...(mutation.meta && { meta: mutation.meta }),\n }\n}\n\n// Most config is not dehydrated but instead meant to configure again when\n// consuming the de/rehydrated data, typically with useQuery on the client.\n// Sometimes it might make sense to prefetch data on the server and include\n// in the html-payload, but not consume it on the initial render.\nfunction dehydrateQuery(\n query: Query,\n serializeData: TransformerFn,\n shouldRedactErrors: (error: unknown) => boolean,\n): DehydratedQuery {\n const dehydratePromise = () => {\n const promise = query.promise?.then(serializeData).catch((error) => {\n if (!shouldRedactErrors(error)) {\n // Reject original error if it should not be redacted\n return Promise.reject(error)\n }\n // If not in production, log original error before rejecting redacted error\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n `A query that was dehydrated as pending ended up rejecting. [${query.queryHash}]: ${error}; The error will be redacted in production builds`,\n )\n }\n return Promise.reject(new Error('redacted'))\n })\n\n // Avoid unhandled promise rejections\n // We need the promise we dehydrate to reject to get the correct result into\n // the query cache, but we also want to avoid unhandled promise rejections\n // in whatever environment the prefetches are happening in.\n promise?.catch(noop)\n\n return promise\n }\n\n return {\n dehydratedAt: Date.now(),\n state: {\n ...query.state,\n ...(query.state.data !== undefined && {\n data: serializeData(query.state.data),\n }),\n },\n queryKey: query.queryKey,\n queryHash: query.queryHash,\n ...(query.state.status === 'pending' && {\n promise: dehydratePromise(),\n }),\n ...(query.meta && { meta: query.meta }),\n }\n}\n\nexport function defaultShouldDehydrateMutation(mutation: Mutation) {\n return mutation.state.isPaused\n}\n\nexport function defaultShouldDehydrateQuery(query: Query) {\n return query.state.status === 'success'\n}\n\nfunction defaultShouldRedactErrors(_: unknown) {\n return true\n}\n\nexport function dehydrate(\n client: QueryClient,\n options: DehydrateOptions = {},\n): DehydratedState {\n const filterMutation =\n options.shouldDehydrateMutation ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateMutation ??\n defaultShouldDehydrateMutation\n\n const mutations = client\n .getMutationCache()\n .getAll()\n .flatMap((mutation) =>\n filterMutation(mutation) ? [dehydrateMutation(mutation)] : [],\n )\n\n const filterQuery =\n options.shouldDehydrateQuery ??\n client.getDefaultOptions().dehydrate?.shouldDehydrateQuery ??\n defaultShouldDehydrateQuery\n\n const shouldRedactErrors =\n options.shouldRedactErrors ??\n client.getDefaultOptions().dehydrate?.shouldRedactErrors ??\n defaultShouldRedactErrors\n\n const serializeData =\n options.serializeData ??\n client.getDefaultOptions().dehydrate?.serializeData ??\n defaultTransformerFn\n\n const queries = client\n .getQueryCache()\n .getAll()\n .flatMap((query) =>\n filterQuery(query)\n ? [dehydrateQuery(query, serializeData, shouldRedactErrors)]\n : [],\n )\n\n return { mutations, queries }\n}\n\nexport function hydrate(\n client: QueryClient,\n dehydratedState: unknown,\n options?: HydrateOptions,\n): void {\n if (typeof dehydratedState !== 'object' || dehydratedState === null) {\n return\n }\n\n const mutationCache = client.getMutationCache()\n const queryCache = client.getQueryCache()\n const deserializeData =\n options?.defaultOptions?.deserializeData ??\n client.getDefaultOptions().hydrate?.deserializeData ??\n defaultTransformerFn\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const mutations = (dehydratedState as DehydratedState).mutations || []\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const queries = (dehydratedState as DehydratedState).queries || []\n\n mutations.forEach(({ state, ...mutationOptions }) => {\n mutationCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.mutations,\n ...options?.defaultOptions?.mutations,\n ...mutationOptions,\n },\n state,\n )\n })\n\n queries.forEach(\n ({ queryKey, state, queryHash, meta, promise, dehydratedAt }) => {\n const syncData = promise ? tryResolveSync(promise) : undefined\n const rawData = state.data === undefined ? syncData?.data : state.data\n const data = rawData === undefined ? rawData : deserializeData(rawData)\n\n let query = queryCache.get(queryHash)\n const existingQueryIsPending = query?.state.status === 'pending'\n const existingQueryIsFetching = query?.state.fetchStatus === 'fetching'\n\n // Do not hydrate if an existing query exists with newer data\n if (query) {\n const hasNewerSyncData =\n syncData &&\n // We only need this undefined check to handle older dehydration\n // payloads that might not have dehydratedAt\n dehydratedAt !== undefined &&\n dehydratedAt > query.state.dataUpdatedAt\n if (\n state.dataUpdatedAt > query.state.dataUpdatedAt ||\n hasNewerSyncData\n ) {\n // Omit fetchStatus from dehydrated state so that query stays in its current fetchStatus\n const { fetchStatus: _ignored, ...serializedState } = state\n query.setState({\n ...serializedState,\n data,\n // If the query was pending at the moment of dehydration, but resolved to have data\n // before hydration, we can assume the query should be hydrated as successful.\n //\n // Since you can opt into dehydrating failed queries, and those can have data from\n // previous successful fetches, we make sure we only do this for pending queries.\n ...(state.status === 'pending' &&\n data !== undefined && {\n status: 'success' as const,\n // Preserve existing fetchStatus if the existing query is actively fetching.\n ...(!existingQueryIsFetching && {\n fetchStatus: 'idle' as const,\n }),\n }),\n })\n }\n } else {\n // Restore query\n query = queryCache.build(\n client,\n {\n ...client.getDefaultOptions().hydrate?.queries,\n ...options?.defaultOptions?.queries,\n queryKey,\n queryHash,\n meta,\n },\n // Reset fetch status to idle to avoid\n // query being stuck in fetching state upon hydration\n {\n ...state,\n data,\n fetchStatus: 'idle',\n // Like above, if the query was pending at the moment of dehydration but has data,\n // we can assume it should be hydrated as successful.\n status:\n state.status === 'pending' && data !== undefined\n ? 'success'\n : state.status,\n },\n )\n }\n\n if (\n promise &&\n // If the data was synchronously available, there is no need to set up\n // a retryer and thus no reason to call fetch\n !syncData &&\n !existingQueryIsPending &&\n !existingQueryIsFetching &&\n // Only hydrate if dehydration is newer than any existing data,\n // this is always true for new queries\n (dehydratedAt === undefined || dehydratedAt > query.state.dataUpdatedAt)\n ) {\n // This doesn't actually fetch - it just creates a retryer\n // which will re-use the passed `initialPromise`\n query\n .fetch(undefined, {\n // RSC transformed promises are not thenable\n initialPromise: Promise.resolve(promise).then(deserializeData),\n })\n // Avoid unhandled promise rejections\n .catch(noop)\n }\n },\n )\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/query-core",
3
- "version": "5.100.0",
3
+ "version": "5.100.1",
4
4
  "description": "The framework agnostic core that powers TanStack Query",
5
5
  "author": "tannerlinsley",
6
6
  "license": "MIT",
package/src/hydration.ts CHANGED
@@ -230,12 +230,24 @@ export function hydrate(
230
230
  state.dataUpdatedAt > query.state.dataUpdatedAt ||
231
231
  hasNewerSyncData
232
232
  ) {
233
- // omit fetchStatus from dehydrated state
234
- // so that query stays in its current fetchStatus
233
+ // Omit fetchStatus from dehydrated state so that query stays in its current fetchStatus
235
234
  const { fetchStatus: _ignored, ...serializedState } = state
236
235
  query.setState({
237
236
  ...serializedState,
238
237
  data,
238
+ // If the query was pending at the moment of dehydration, but resolved to have data
239
+ // before hydration, we can assume the query should be hydrated as successful.
240
+ //
241
+ // Since you can opt into dehydrating failed queries, and those can have data from
242
+ // previous successful fetches, we make sure we only do this for pending queries.
243
+ ...(state.status === 'pending' &&
244
+ data !== undefined && {
245
+ status: 'success' as const,
246
+ // Preserve existing fetchStatus if the existing query is actively fetching.
247
+ ...(!existingQueryIsFetching && {
248
+ fetchStatus: 'idle' as const,
249
+ }),
250
+ }),
239
251
  })
240
252
  }
241
253
  } else {
@@ -255,13 +267,21 @@ export function hydrate(
255
267
  ...state,
256
268
  data,
257
269
  fetchStatus: 'idle',
258
- status: data !== undefined ? 'success' : state.status,
270
+ // Like above, if the query was pending at the moment of dehydration but has data,
271
+ // we can assume it should be hydrated as successful.
272
+ status:
273
+ state.status === 'pending' && data !== undefined
274
+ ? 'success'
275
+ : state.status,
259
276
  },
260
277
  )
261
278
  }
262
279
 
263
280
  if (
264
281
  promise &&
282
+ // If the data was synchronously available, there is no need to set up
283
+ // a retryer and thus no reason to call fetch
284
+ !syncData &&
265
285
  !existingQueryIsPending &&
266
286
  !existingQueryIsFetching &&
267
287
  // Only hydrate if dehydration is newer than any existing data,
@@ -270,8 +290,6 @@ export function hydrate(
270
290
  ) {
271
291
  // This doesn't actually fetch - it just creates a retryer
272
292
  // which will re-use the passed `initialPromise`
273
- // Note that we need to call these even when data was synchronously
274
- // available, as we still need to set up the retryer
275
293
  query
276
294
  .fetch(undefined, {
277
295
  // RSC transformed promises are not thenable