react-query
Version:
Hooks for managing, caching and syncing asynchronous and remote data in React
1 lines • 98.2 kB
Source Map (JSON)
{"version":3,"file":"react-query.min.mjs","sources":["../src/core/types.ts","../src/core/utils.ts","../src/core/config.ts","../src/core/queryObserver.ts","../src/core/query.ts","../src/core/queryCache.ts","../src/core/setFocusHandler.ts","../src/react/ReactQueryCacheProvider.tsx","../src/react/ReactQueryConfigProvider.tsx","../src/react/useMutation.ts","../src/react/utils.ts","../src/react/useIsFetching.ts","../src/react/useDefaultedMutationConfig.tsx","../src/react/useBaseQuery.ts","../src/react/useDefaultedQueryConfig.tsx","../src/react/useQuery.ts","../src/react/usePaginatedQuery.ts","../src/react/useInfiniteQuery.ts"],"sourcesContent":["import type { Query, FetchMoreOptions } from './query'\nimport type { QueryCache } from './queryCache'\n\nexport type QueryKeyObject =\n | object\n | { [key: string]: QueryKey }\n | { [key: number]: QueryKey }\n\nexport type QueryKeyPrimitive = string | boolean | number | null | undefined\n\nexport type QueryKeyWithoutObjectAndArray = QueryKeyPrimitive\n\nexport type QueryKeyWithoutObject =\n | QueryKeyWithoutObjectAndArray\n | readonly QueryKey[]\n\nexport type QueryKeyWithoutArray =\n | QueryKeyWithoutObjectAndArray\n | QueryKeyObject\n\nexport type QueryKey = QueryKeyWithoutObject | QueryKeyObject\n\nexport type ArrayQueryKey = QueryKey[]\n\nexport type QueryFunction<TResult> = (\n ...args: any[]\n) => TResult | Promise<TResult>\n\n// The tuple variants are only to infer types in the public API\nexport type TupleQueryKey = readonly [QueryKey, ...QueryKey[]]\n\nexport type TupleQueryFunction<TResult, TKey extends TupleQueryKey> = (\n ...args: TKey\n) => TResult | Promise<TResult>\n\nexport type InitialDataFunction<TResult> = () => TResult | undefined\n\nexport type InitialStaleFunction = () => boolean\n\nexport type QueryKeySerializerFunction = (\n queryKey: QueryKey\n) => [string, QueryKey[]]\n\nexport interface BaseQueryConfig<TResult, TError = unknown> {\n /**\n * Set this to `false` to disable automatic refetching when the query mounts or changes query keys.\n * To refetch the query, use the `refetch` method returned from the `useQuery` instance.\n */\n enabled?: boolean | unknown\n /**\n * If `false`, failed queries will not retry by default.\n * If `true`, failed queries will retry infinitely., failureCount: num\n * If set to an integer number, e.g. 3, failed queries will retry until the failed query count meets that number.\n * If set to a function `(failureCount, error) => boolean` failed queries will retry until the function returns false.\n */\n retry?: boolean | number | ((failureCount: number, error: TError) => boolean)\n retryDelay?: number | ((retryAttempt: number) => number)\n staleTime?: number\n cacheTime?: number\n isDataEqual?: (oldData: unknown, newData: unknown) => boolean\n queryFn?: QueryFunction<TResult>\n queryKey?: QueryKey\n queryKeySerializerFn?: QueryKeySerializerFunction\n queryFnParamsFilter?: (args: ArrayQueryKey) => ArrayQueryKey\n initialData?: TResult | InitialDataFunction<TResult>\n initialStale?: boolean | InitialStaleFunction\n infinite?: true\n}\n\nexport interface QueryObserverConfig<TResult, TError = unknown>\n extends BaseQueryConfig<TResult, TError> {\n /**\n * Set this to `false` to disable automatic refetching when the query mounts or changes query keys.\n * To refetch the query, use the `refetch` method returned from the `useQuery` instance.\n * Defaults to `true`.\n */\n enabled?: boolean | unknown\n /**\n * If set to a number, the query will continuously refetch at this frequency in milliseconds.\n * Defaults to `false`.\n */\n refetchInterval?: number\n /**\n * If set to `true`, the query will continue to refetch while their tab/window is in the background.\n * Defaults to `false`.\n */\n refetchIntervalInBackground?: boolean\n /**\n * Set this to `true` or `false` to enable/disable automatic refetching on window focus for this query.\n * Defaults to `true`.\n */\n refetchOnWindowFocus?: boolean\n /**\n * If set to `false`, will disable additional instances of a query to trigger background refetches.\n * Defaults to `true`.\n */\n refetchOnMount?: boolean\n /**\n * This callback will fire any time the query successfully fetches new data.\n */\n onSuccess?: (data: TResult) => void\n /**\n * This callback will fire if the query encounters an error and will be passed the error.\n */\n onError?: (err: TError) => void\n /**\n * This callback will fire any time the query is either successfully fetched or errors and be passed either the data or error.\n */\n onSettled?: (data: TResult | undefined, error: TError | null) => void\n /**\n * Whether errors should be thrown instead of setting the `error` property.\n * Defaults to `false`.\n */\n useErrorBoundary?: boolean\n /**\n * If set to `true`, the query will suspend when `status === 'loading'`\n * and throw errors when `status === 'error'`.\n * Defaults to `false`.\n */\n suspense?: boolean\n /**\n * Set this to `true` to keep the previous `data` when fetching based on a new query key.\n * Defaults to `false`.\n */\n keepPreviousData?: boolean\n /**\n * By default the query cache from the context is used, but a different cache can be specified.\n */\n queryCache?: QueryCache\n}\n\nexport interface QueryConfig<TResult, TError = unknown>\n extends QueryObserverConfig<TResult, TError> {}\n\nexport interface PaginatedQueryConfig<TResult, TError = unknown>\n extends QueryObserverConfig<TResult, TError> {}\n\nexport interface InfiniteQueryConfig<TResult, TError = unknown>\n extends QueryObserverConfig<TResult[], TError> {\n getFetchMore: (lastPage: TResult, allPages: TResult[]) => unknown\n}\n\nexport type IsFetchingMoreValue = 'previous' | 'next' | false\n\nexport enum QueryStatus {\n Idle = 'idle',\n Loading = 'loading',\n Error = 'error',\n Success = 'success',\n}\n\nexport interface QueryResultBase<TResult, TError = unknown> {\n canFetchMore: boolean | undefined\n clear: () => void\n data: TResult | undefined\n error: TError | null\n failureCount: number\n fetchMore: (\n fetchMoreVariable?: unknown,\n options?: FetchMoreOptions\n ) => Promise<TResult | undefined>\n isError: boolean\n isFetched: boolean\n isFetching: boolean\n isFetchingMore?: IsFetchingMoreValue\n isIdle: boolean\n isLoading: boolean\n isStale: boolean\n isSuccess: boolean\n query: Query<TResult, TError>\n refetch: () => Promise<void>\n status: QueryStatus\n updatedAt: number\n}\n\nexport interface QueryResult<TResult, TError = unknown>\n extends QueryResultBase<TResult, TError> {}\n\nexport interface PaginatedQueryResult<TResult, TError = unknown>\n extends QueryResultBase<TResult, TError> {\n resolvedData: TResult | undefined\n latestData: TResult | undefined\n}\n\nexport interface InfiniteQueryResult<TResult, TError = unknown>\n extends QueryResultBase<TResult[], TError> {}\n\nexport interface MutateConfig<\n TResult,\n TError = unknown,\n TVariables = unknown,\n TSnapshot = unknown\n> {\n onSuccess?: (data: TResult, variables: TVariables) => Promise<void> | void\n onError?: (\n error: TError,\n variables: TVariables,\n snapshotValue: TSnapshot\n ) => Promise<void> | void\n onSettled?: (\n data: undefined | TResult,\n error: TError | null,\n variables: TVariables,\n snapshotValue?: TSnapshot\n ) => Promise<void> | void\n throwOnError?: boolean\n}\n\nexport interface MutationConfig<\n TResult,\n TError = unknown,\n TVariables = unknown,\n TSnapshot = unknown\n> extends MutateConfig<TResult, TError, TVariables, TSnapshot> {\n onMutate?: (variables: TVariables) => Promise<TSnapshot> | TSnapshot\n useErrorBoundary?: boolean\n suspense?: boolean\n /**\n * By default the query cache from the context is used, but a different cache can be specified.\n */\n queryCache?: QueryCache\n}\n\nexport type MutationFunction<TResult, TVariables = unknown> = (\n variables: TVariables\n) => Promise<TResult>\n\nexport type MutateFunction<\n TResult,\n TError = unknown,\n TVariables = unknown,\n TSnapshot = unknown\n> = (\n variables?: TVariables,\n config?: MutateConfig<TResult, TError, TVariables, TSnapshot>\n) => Promise<TResult | undefined>\n\nexport type MutationResultPair<TResult, TError, TVariables, TSnapshot> = [\n MutateFunction<TResult, TError, TVariables, TSnapshot>,\n MutationResult<TResult, TError>\n]\n\nexport interface MutationResult<TResult, TError = unknown> {\n status: QueryStatus\n data: TResult | undefined\n error: TError | null\n isIdle: boolean\n isLoading: boolean\n isSuccess: boolean\n isError: boolean\n reset: () => void\n}\n\nexport interface ReactQueryConfig<TResult = unknown, TError = unknown> {\n queries?: ReactQueryQueriesConfig<TResult, TError>\n shared?: ReactQuerySharedConfig\n mutations?: ReactQueryMutationsConfig<TResult, TError>\n}\n\nexport interface ReactQuerySharedConfig {\n suspense?: boolean\n}\n\nexport interface ReactQueryQueriesConfig<TResult, TError>\n extends QueryObserverConfig<TResult, TError> {}\n\nexport interface ReactQueryMutationsConfig<\n TResult,\n TError = unknown,\n TVariables = unknown,\n TSnapshot = unknown\n> extends MutationConfig<TResult, TError, TVariables, TSnapshot> {}\n","import { QueryConfig, QueryStatus, QueryKey, QueryFunction } from './types'\n\n// TYPES\n\nexport type DataUpdateFunction<TInput, TOutput> = (input: TInput) => TOutput\n\nexport type Updater<TInput, TOutput> =\n | TOutput\n | DataUpdateFunction<TInput, TOutput>\n\ntype ConsoleFunction = (...args: any[]) => void\n\nexport interface ConsoleObject {\n log: ConsoleFunction\n warn: ConsoleFunction\n error: ConsoleFunction\n}\n\n// UTILS\n\nlet _uid = 0\nexport const uid = () => _uid++\nexport const cancelledError = {}\nexport const globalStateListeners = []\nexport const isServer = typeof window === 'undefined'\nexport function noop(): void {\n return void 0\n}\nexport let Console: ConsoleObject = console || {\n error: noop,\n warn: noop,\n log: noop,\n}\n\nexport function setConsole(c: ConsoleObject) {\n Console = c\n}\n\nexport function functionalUpdate<TInput, TOutput>(\n updater: Updater<TInput, TOutput>,\n input: TInput\n): TOutput {\n return typeof updater === 'function'\n ? (updater as DataUpdateFunction<TInput, TOutput>)(input)\n : updater\n}\n\nfunction stableStringifyReplacer(_key: string, value: any): unknown {\n if (typeof value === 'function') {\n throw new Error('Cannot stringify non JSON value')\n }\n\n if (isObject(value)) {\n return Object.keys(value)\n .sort()\n .reduce((result, key) => {\n result[key] = value[key]\n return result\n }, {} as any)\n }\n\n return value\n}\n\nexport function stableStringify(value: any): string {\n return JSON.stringify(value, stableStringifyReplacer)\n}\n\nexport function deepIncludes(a: any, b: any): boolean {\n if (a === b) {\n return true\n }\n\n if (typeof a !== typeof b) {\n return false\n }\n\n if (typeof a === 'object') {\n return !Object.keys(b).some(key => !deepIncludes(a[key], b[key]))\n }\n\n return false\n}\n\nexport function isDocumentVisible(): boolean {\n // document global can be unavailable in react native\n if (typeof document === 'undefined') {\n return true\n }\n return [undefined, 'visible', 'prerender'].includes(document.visibilityState)\n}\n\nexport function isOnline(): boolean {\n return navigator.onLine === undefined || navigator.onLine\n}\n\nexport function getQueryArgs<TResult, TError, TOptions = undefined>(\n args: any[]\n): [QueryKey, QueryConfig<TResult, TError>, TOptions] {\n let queryKey: QueryKey\n let queryFn: QueryFunction<TResult> | undefined\n let config: QueryConfig<TResult, TError> | undefined\n let options: TOptions\n\n if (isObject(args[0])) {\n queryKey = args[0].queryKey\n queryFn = args[0].queryFn\n config = args[0].config\n options = args[1]\n } else if (isObject(args[1])) {\n queryKey = args[0]\n config = args[1]\n options = args[2]\n } else {\n queryKey = args[0]\n queryFn = args[1]\n config = args[2]\n options = args[3]\n }\n\n config = config ? { queryKey, ...config } : { queryKey }\n\n if (queryFn) {\n config = { ...config, queryFn }\n }\n\n return [queryKey, config, options]\n}\n\nexport function deepEqual(a: any, b: any): boolean {\n return replaceEqualDeep(a, b) === a\n}\n\n/**\n * This function returns `a` if `b` is deeply equal.\n * If not, it will replace any deeply equal children of `b` with those of `a`.\n * This can be used for structural sharing between JSON values for example.\n */\nexport function replaceEqualDeep<T>(a: unknown, b: T): T\nexport function replaceEqualDeep(a: any, b: any): any {\n if (a === b) {\n return a\n }\n\n const array = Array.isArray(a) && Array.isArray(b)\n\n if (array || (isPlainObject(a) && isPlainObject(b))) {\n const aSize = array ? a.length : Object.keys(a).length\n const bItems = array ? b : Object.keys(b)\n const bSize = bItems.length\n const copy: any = array ? [] : {}\n\n let equalItems = 0\n\n for (let i = 0; i < bSize; i++) {\n const key = array ? i : bItems[i]\n copy[key] = replaceEqualDeep(a[key], b[key])\n if (copy[key] === a[key]) {\n equalItems++\n }\n }\n\n return aSize === bSize && equalItems === aSize ? a : copy\n }\n\n return b\n}\n\nexport function isObject(a: unknown): boolean {\n return a && typeof a === 'object' && !Array.isArray(a)\n}\n\n// Copied from: https://github.com/jonschlinkert/is-plain-object\nfunction isPlainObject(o: any): o is Object {\n if (!hasObjectPrototype(o)) {\n return false\n }\n\n // If has modified constructor\n const ctor = o.constructor\n if (typeof ctor === 'undefined') {\n return true\n }\n\n // If has modified prototype\n const prot = ctor.prototype\n if (!hasObjectPrototype(prot)) {\n return false\n }\n\n // If constructor does not have an Object-specific method\n if (!prot.hasOwnProperty('isPrototypeOf')) {\n return false\n }\n\n // Most likely a plain Object\n return true\n}\n\nfunction hasObjectPrototype(o: any): boolean {\n return Object.prototype.toString.call(o) === '[object Object]'\n}\n\nexport function getStatusProps<T extends QueryStatus>(status: T) {\n return {\n status,\n isLoading: status === QueryStatus.Loading,\n isSuccess: status === QueryStatus.Success,\n isError: status === QueryStatus.Error,\n isIdle: status === QueryStatus.Idle,\n }\n}\n","import { stableStringify } from './utils'\nimport {\n ArrayQueryKey,\n QueryKey,\n QueryKeySerializerFunction,\n ReactQueryConfig,\n QueryConfig,\n MutationConfig,\n} from './types'\n\n// TYPES\n\nexport interface ReactQueryConfigRef {\n current: ReactQueryConfig\n}\n\n// CONFIG\n\nexport const defaultQueryKeySerializerFn: QueryKeySerializerFunction = (\n queryKey: QueryKey\n): [string, ArrayQueryKey] => {\n try {\n let arrayQueryKey: ArrayQueryKey = Array.isArray(queryKey)\n ? queryKey\n : [queryKey]\n const queryHash = stableStringify(arrayQueryKey)\n arrayQueryKey = JSON.parse(queryHash)\n return [queryHash, arrayQueryKey]\n } catch {\n throw new Error('A valid query key is required!')\n }\n}\n\n/**\n * Config merging strategy\n *\n * When using hooks the config will be merged in the following order:\n *\n * 1. These defaults.\n * 2. Defaults from the hook query cache.\n * 3. Combined defaults from any config providers in the tree.\n * 4. Query/mutation config provided to the hook.\n *\n * When using a query cache directly the config will be merged in the following order:\n *\n * 1. These defaults.\n * 2. Defaults from the query cache.\n * 3. Query/mutation config provided to the query cache method.\n */\nexport const DEFAULT_CONFIG: ReactQueryConfig = {\n queries: {\n queryKeySerializerFn: defaultQueryKeySerializerFn,\n enabled: true,\n retry: 3,\n retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),\n staleTime: 0,\n cacheTime: 5 * 60 * 1000,\n refetchOnWindowFocus: true,\n refetchOnMount: true,\n },\n}\n\nexport function mergeReactQueryConfigs(\n a: ReactQueryConfig,\n b: ReactQueryConfig\n): ReactQueryConfig {\n return {\n shared: {\n ...a.shared,\n ...b.shared,\n },\n queries: {\n ...a.queries,\n ...b.queries,\n },\n mutations: {\n ...a.mutations,\n ...b.mutations,\n },\n }\n}\n\nexport function getDefaultedQueryConfig<TResult, TError>(\n queryCacheConfig?: ReactQueryConfig,\n contextConfig?: ReactQueryConfig,\n config?: QueryConfig<TResult, TError>,\n configOverrides?: QueryConfig<TResult, TError>\n): QueryConfig<TResult, TError> {\n return {\n ...DEFAULT_CONFIG.shared,\n ...DEFAULT_CONFIG.queries,\n ...queryCacheConfig?.shared,\n ...queryCacheConfig?.queries,\n ...contextConfig?.shared,\n ...contextConfig?.queries,\n ...config,\n ...configOverrides,\n } as QueryConfig<TResult, TError>\n}\n\nexport function getDefaultedMutationConfig<\n TResult,\n TError,\n TVariables,\n TSnapshot\n>(\n queryCacheConfig?: ReactQueryConfig,\n contextConfig?: ReactQueryConfig,\n config?: MutationConfig<TResult, TError, TVariables, TSnapshot>,\n configOverrides?: MutationConfig<TResult, TError, TVariables, TSnapshot>\n): MutationConfig<TResult, TError, TVariables, TSnapshot> {\n return {\n ...DEFAULT_CONFIG.shared,\n ...DEFAULT_CONFIG.mutations,\n ...queryCacheConfig?.shared,\n ...queryCacheConfig?.mutations,\n ...contextConfig?.shared,\n ...contextConfig?.mutations,\n ...config,\n ...configOverrides,\n } as MutationConfig<TResult, TError, TVariables, TSnapshot>\n}\n","import { getStatusProps, isServer, isDocumentVisible, Console } from './utils'\nimport type { QueryResult, QueryObserverConfig } from './types'\nimport type { Query, QueryState, Action, FetchMoreOptions } from './query'\n\nexport type UpdateListener<TResult, TError> = (\n result: QueryResult<TResult, TError>\n) => void\n\nexport class QueryObserver<TResult, TError> {\n config: QueryObserverConfig<TResult, TError>\n\n private currentQuery!: Query<TResult, TError>\n private currentResult!: QueryResult<TResult, TError>\n private previousResult?: QueryResult<TResult, TError>\n private updateListener?: UpdateListener<TResult, TError>\n private refetchIntervalId?: number\n private started?: boolean\n\n constructor(config: QueryObserverConfig<TResult, TError>) {\n this.config = config\n\n // Bind exposed methods\n this.clear = this.clear.bind(this)\n this.refetch = this.refetch.bind(this)\n this.fetchMore = this.fetchMore.bind(this)\n\n // Subscribe to the query\n this.updateQuery()\n }\n\n subscribe(listener?: UpdateListener<TResult, TError>): () => void {\n this.started = true\n this.updateListener = listener\n this.currentQuery.subscribeObserver(this)\n this.optionalFetch()\n this.updateRefetchInterval()\n return this.unsubscribe.bind(this)\n }\n\n unsubscribe(preventGC?: boolean): void {\n this.started = false\n this.updateListener = undefined\n this.clearRefetchInterval()\n this.currentQuery.unsubscribeObserver(this, preventGC)\n }\n\n updateConfig(config: QueryObserverConfig<TResult, TError>): void {\n const prevConfig = this.config\n this.config = config\n\n const updated = this.updateQuery()\n\n // Take no further actions if the observer did not start yet\n if (!this.started) {\n return\n }\n\n // If we subscribed to a new query, optionally fetch and update refetch\n if (updated) {\n this.optionalFetch()\n this.updateRefetchInterval()\n return\n }\n\n // Optionally fetch if the query became enabled\n if (config.enabled && !prevConfig.enabled) {\n this.optionalFetch()\n }\n\n // Update refetch interval if needed\n if (\n config.enabled !== prevConfig.enabled ||\n config.refetchInterval !== prevConfig.refetchInterval ||\n config.refetchIntervalInBackground !==\n prevConfig.refetchIntervalInBackground\n ) {\n this.updateRefetchInterval()\n }\n }\n\n getCurrentResult(): QueryResult<TResult, TError> {\n return this.currentResult\n }\n\n clear(): void {\n return this.currentQuery.clear()\n }\n\n async refetch(): Promise<void> {\n this.currentQuery.updateConfig(this.config)\n return this.currentQuery.refetch()\n }\n\n async fetchMore(\n fetchMoreVariable?: unknown,\n options?: FetchMoreOptions\n ): Promise<TResult | undefined> {\n this.currentQuery.updateConfig(this.config)\n return this.currentQuery.fetchMore(fetchMoreVariable, options)\n }\n\n async fetch(): Promise<TResult | undefined> {\n this.currentQuery.updateConfig(this.config)\n return this.currentQuery.fetch().catch(error => {\n Console.error(error)\n return undefined\n })\n }\n\n private optionalFetch(): void {\n if (\n this.config.enabled && // Don't auto refetch if disabled\n !(this.config.suspense && this.currentResult.isFetched) && // Don't refetch if in suspense mode and the data is already fetched\n this.currentResult.isStale && // Only refetch if stale\n (this.config.refetchOnMount || this.currentQuery.observers.length === 1)\n ) {\n this.fetch()\n }\n }\n\n private updateRefetchInterval(): void {\n if (isServer) {\n return\n }\n\n this.clearRefetchInterval()\n\n if (\n !this.config.enabled ||\n !this.config.refetchInterval ||\n this.config.refetchInterval < 0 ||\n this.config.refetchInterval === Infinity\n ) {\n return\n }\n\n this.refetchIntervalId = setInterval(() => {\n if (this.config.refetchIntervalInBackground || isDocumentVisible()) {\n this.fetch()\n }\n }, this.config.refetchInterval)\n }\n\n clearRefetchInterval(): void {\n if (this.refetchIntervalId) {\n clearInterval(this.refetchIntervalId)\n this.refetchIntervalId = undefined\n }\n }\n\n private createResult(): QueryResult<TResult, TError> {\n const { currentQuery, previousResult, config } = this\n\n const {\n canFetchMore,\n error,\n failureCount,\n isFetched,\n isFetching,\n isFetchingMore,\n isLoading,\n isStale,\n } = currentQuery.state\n\n let { data, status, updatedAt } = currentQuery.state\n\n // Keep previous data if needed\n if (config.keepPreviousData && isLoading && previousResult?.isSuccess) {\n data = previousResult.data\n updatedAt = previousResult.updatedAt\n status = previousResult.status\n }\n\n return {\n ...getStatusProps(status),\n canFetchMore,\n clear: this.clear,\n data,\n error,\n failureCount,\n fetchMore: this.fetchMore,\n isFetched,\n isFetching,\n isFetchingMore,\n isStale,\n query: currentQuery,\n refetch: this.refetch,\n updatedAt,\n }\n }\n\n private updateQuery(): boolean {\n const prevQuery = this.currentQuery\n\n // Remove the initial data when there is an existing query\n // because this data should not be used for a new query\n const config = prevQuery\n ? { ...this.config, initialData: undefined }\n : this.config\n\n const newQuery = config.queryCache!.buildQuery(config.queryKey, config)\n\n if (newQuery === prevQuery) {\n return false\n }\n\n this.previousResult = this.currentResult\n this.currentQuery = newQuery\n this.currentResult = this.createResult()\n\n if (this.started) {\n prevQuery?.unsubscribeObserver(this)\n this.currentQuery.subscribeObserver(this)\n }\n\n return true\n }\n\n onQueryUpdate(\n _state: QueryState<TResult, TError>,\n action: Action<TResult, TError>\n ): void {\n this.currentResult = this.createResult()\n\n const { data, error, isSuccess, isError } = this.currentResult\n\n if (action.type === 'Success' && isSuccess) {\n this.config.onSuccess?.(data!)\n this.config.onSettled?.(data!, null)\n this.updateRefetchInterval()\n } else if (action.type === 'Error' && isError) {\n this.config.onError?.(error!)\n this.config.onSettled?.(undefined, error!)\n this.updateRefetchInterval()\n }\n\n this.updateListener?.(this.currentResult)\n }\n}\n","import {\n isServer,\n functionalUpdate,\n cancelledError,\n isDocumentVisible,\n noop,\n Console,\n getStatusProps,\n Updater,\n replaceEqualDeep,\n} from './utils'\nimport {\n ArrayQueryKey,\n InfiniteQueryConfig,\n InitialDataFunction,\n IsFetchingMoreValue,\n QueryConfig,\n QueryFunction,\n QueryStatus,\n} from './types'\nimport type { QueryCache } from './queryCache'\nimport { QueryObserver, UpdateListener } from './queryObserver'\n\n// TYPES\n\ninterface QueryInitConfig<TResult, TError> {\n queryCache: QueryCache\n queryKey: ArrayQueryKey\n queryHash: string\n config: QueryConfig<TResult, TError>\n notifyGlobalListeners: (query: Query<TResult, TError>) => void\n}\n\nexport interface QueryState<TResult, TError> {\n canFetchMore?: boolean\n data?: TResult\n error: TError | null\n failureCount: number\n isError: boolean\n isFetched: boolean\n isFetching: boolean\n isFetchingMore: IsFetchingMoreValue\n isIdle: boolean\n isLoading: boolean\n isStale: boolean\n isSuccess: boolean\n status: QueryStatus\n throwInErrorBoundary?: boolean\n updatedAt: number\n}\n\ninterface FetchOptions {\n fetchMore?: FetchMoreOptions\n}\n\nexport interface FetchMoreOptions {\n fetchMoreVariable?: unknown\n previous: boolean\n}\n\nexport enum ActionType {\n Failed = 'Failed',\n MarkStale = 'MarkStale',\n Fetch = 'Fetch',\n Success = 'Success',\n Error = 'Error',\n SetState = 'SetState',\n}\n\ninterface FailedAction {\n type: ActionType.Failed\n}\n\ninterface MarkStaleAction {\n type: ActionType.MarkStale\n}\n\ninterface FetchAction {\n type: ActionType.Fetch\n}\n\ninterface SuccessAction<TResult> {\n type: ActionType.Success\n data: TResult | undefined\n isStale: boolean\n}\n\ninterface ErrorAction<TError> {\n type: ActionType.Error\n cancelled: boolean\n error: TError\n}\n\ninterface SetStateAction<TResult, TError> {\n type: ActionType.SetState\n updater: Updater<QueryState<TResult, TError>, QueryState<TResult, TError>>\n}\n\nexport type Action<TResult, TError> =\n | ErrorAction<TError>\n | FailedAction\n | FetchAction\n | MarkStaleAction\n | SetStateAction<TResult, TError>\n | SuccessAction<TResult>\n\n// CLASS\n\nexport class Query<TResult, TError> {\n queryCache: QueryCache\n queryKey: ArrayQueryKey\n queryHash: string\n config: QueryConfig<TResult, TError>\n observers: QueryObserver<TResult, TError>[]\n state: QueryState<TResult, TError>\n shouldContinueRetryOnFocus?: boolean\n promise?: Promise<TResult | undefined>\n\n private fetchMoreVariable?: unknown\n private pageVariables?: ArrayQueryKey[]\n private cacheTimeout?: number\n private retryTimeout?: number\n private staleTimeout?: number\n private cancelPromises?: () => void\n private cancelled?: typeof cancelledError | null\n private notifyGlobalListeners: (query: Query<TResult, TError>) => void\n\n constructor(init: QueryInitConfig<TResult, TError>) {\n this.config = init.config\n this.queryCache = init.queryCache\n this.queryKey = init.queryKey\n this.queryHash = init.queryHash\n this.notifyGlobalListeners = init.notifyGlobalListeners\n this.observers = []\n this.state = getDefaultState(init.config)\n\n if (init.config.infinite) {\n const infiniteConfig = init.config as InfiniteQueryConfig<TResult, TError>\n const infiniteData = (this.state.data as unknown) as TResult[] | undefined\n\n if (typeof infiniteData !== 'undefined') {\n this.fetchMoreVariable = infiniteConfig.getFetchMore(\n infiniteData[infiniteData.length - 1],\n infiniteData\n )\n this.state.canFetchMore = Boolean(this.fetchMoreVariable)\n }\n\n // Here we seed the pageVariables for the query\n if (!this.pageVariables) {\n this.pageVariables = [[...this.queryKey]]\n }\n }\n\n // If the query started with data, schedule\n // a stale timeout\n if (!isServer && this.state.data) {\n this.scheduleStaleTimeout()\n\n // Simulate a query healing process\n this.heal()\n\n // Schedule for garbage collection in case\n // nothing subscribes to this query\n this.scheduleGarbageCollection()\n }\n }\n\n updateConfig(config: QueryConfig<TResult, TError>): void {\n this.config = config\n }\n\n private dispatch(action: Action<TResult, TError>): void {\n this.state = queryReducer(this.state, action)\n this.observers.forEach(d => d.onQueryUpdate(this.state, action))\n this.notifyGlobalListeners(this)\n }\n\n scheduleStaleTimeout(): void {\n if (isServer) {\n return\n }\n\n this.clearStaleTimeout()\n\n if (this.state.isStale || this.config.staleTime === Infinity) {\n return\n }\n\n this.staleTimeout = setTimeout(() => {\n this.invalidate()\n }, this.config.staleTime)\n }\n\n invalidate(): void {\n this.clearStaleTimeout()\n\n if (this.state.isStale) {\n return\n }\n\n this.dispatch({ type: ActionType.MarkStale })\n }\n\n scheduleGarbageCollection(): void {\n if (isServer) {\n return\n }\n\n this.clearCacheTimeout()\n\n if (this.config.cacheTime === Infinity) {\n return\n }\n\n this.cacheTimeout = setTimeout(\n () => {\n this.clear()\n },\n typeof this.state.data === 'undefined' &&\n this.state.status !== QueryStatus.Error\n ? 0\n : this.config.cacheTime\n )\n }\n\n async refetch(): Promise<void> {\n try {\n await this.fetch()\n } catch (error) {\n Console.error(error)\n }\n }\n\n heal(): void {\n // Stop the query from being garbage collected\n this.clearCacheTimeout()\n\n // Mark the query as not cancelled\n this.cancelled = null\n }\n\n cancel(): void {\n this.cancelled = cancelledError\n\n if (this.cancelPromises) {\n this.cancelPromises()\n }\n\n delete this.promise\n }\n\n private clearTimersObservers(): void {\n this.observers.forEach(observer => {\n observer.clearRefetchInterval()\n })\n }\n\n private clearStaleTimeout() {\n if (this.staleTimeout) {\n clearTimeout(this.staleTimeout)\n this.staleTimeout = undefined\n }\n }\n\n private clearCacheTimeout() {\n if (this.cacheTimeout) {\n clearTimeout(this.cacheTimeout)\n this.cacheTimeout = undefined\n }\n }\n\n private clearRetryTimeout() {\n if (this.retryTimeout) {\n clearTimeout(this.retryTimeout)\n this.retryTimeout = undefined\n }\n }\n\n private setState(\n updater: Updater<QueryState<TResult, TError>, QueryState<TResult, TError>>\n ): void {\n this.dispatch({ type: ActionType.SetState, updater })\n }\n\n setData(updater: Updater<TResult | undefined, TResult>): void {\n const prevData = this.state.data\n\n // Get the new data\n let data: TResult | undefined = functionalUpdate(updater, prevData)\n\n // Structurally share data between prev and new data\n data = replaceEqualDeep(prevData, data)\n\n // Use prev data if an isDataEqual function is defined and returns `true`\n if (this.config.isDataEqual?.(prevData, data)) {\n data = prevData\n }\n\n const isStale = this.config.staleTime === 0\n\n // Set data and mark it as cached\n this.dispatch({\n type: ActionType.Success,\n data,\n isStale,\n })\n\n if (!isStale) {\n // Schedule a fresh invalidation!\n this.scheduleStaleTimeout()\n }\n }\n\n clear(): void {\n this.clearStaleTimeout()\n this.clearCacheTimeout()\n this.clearRetryTimeout()\n this.clearTimersObservers()\n this.cancel()\n delete this.queryCache.queries[this.queryHash]\n this.notifyGlobalListeners(this)\n }\n\n isEnabled(): boolean {\n return this.observers.some(observer => observer.config.enabled)\n }\n\n shouldRefetchOnWindowFocus(): boolean {\n return (\n this.isEnabled() &&\n this.state.isStale &&\n this.observers.some(observer => observer.config.refetchOnWindowFocus)\n )\n }\n\n subscribe(\n listener?: UpdateListener<TResult, TError>\n ): QueryObserver<TResult, TError> {\n const observer = new QueryObserver<TResult, TError>({\n queryCache: this.queryCache,\n queryKey: this.queryKey,\n ...this.config,\n })\n\n observer.subscribe(listener)\n\n return observer\n }\n\n subscribeObserver(observer: QueryObserver<TResult, TError>): void {\n this.observers.push(observer)\n this.heal()\n }\n\n unsubscribeObserver(\n observer: QueryObserver<TResult, TError>,\n preventGC?: boolean\n ): void {\n this.observers = this.observers.filter(x => x !== observer)\n\n if (!this.observers.length) {\n this.cancel()\n\n if (!preventGC) {\n // Schedule garbage collection\n this.scheduleGarbageCollection()\n }\n }\n }\n\n // Set up the core fetcher function\n private async tryFetchData(\n fn: QueryFunction<TResult>,\n args: ArrayQueryKey\n ): Promise<TResult> {\n try {\n // Perform the query\n const filter = this.config.queryFnParamsFilter\n const params = filter ? filter(args) : args\n\n // Perform the query\n const promiseOrValue = fn(...params)\n\n this.cancelPromises = () => (promiseOrValue as any)?.cancel?.()\n\n const data = await promiseOrValue\n delete this.shouldContinueRetryOnFocus\n\n delete this.cancelPromises\n if (this.cancelled) throw this.cancelled\n\n return data\n } catch (error) {\n delete this.cancelPromises\n if (this.cancelled) throw this.cancelled\n\n // Do we need to retry the request?\n if (\n this.config.retry === true ||\n this.state.failureCount < this.config.retry! ||\n (typeof this.config.retry === 'function' &&\n this.config.retry(this.state.failureCount, error))\n ) {\n // If we retry, increase the failureCount\n this.dispatch({ type: ActionType.Failed })\n\n // Only retry if the document is visible\n if (!isDocumentVisible()) {\n // set this flag to continue retries on focus\n this.shouldContinueRetryOnFocus = true\n // Resolve a\n return new Promise(noop)\n }\n\n delete this.shouldContinueRetryOnFocus\n\n // Determine the retryDelay\n const delay = functionalUpdate(\n this.config.retryDelay,\n this.state.failureCount\n )\n\n // Return a new promise with the retry\n return await new Promise((resolve, reject) => {\n // Keep track of the retry timeout\n this.retryTimeout = setTimeout(async () => {\n if (this.cancelled) return reject(this.cancelled)\n\n try {\n const data = await this.tryFetchData(fn, args)\n if (this.cancelled) return reject(this.cancelled)\n resolve(data)\n } catch (error) {\n if (this.cancelled) return reject(this.cancelled)\n reject(error)\n }\n }, delay)\n })\n }\n\n throw error\n }\n }\n\n async fetch(options?: FetchOptions): Promise<TResult | undefined> {\n let queryFn = this.config.queryFn\n\n if (!queryFn) {\n return\n }\n\n // If we are already fetching, return current promise\n if (this.promise) {\n return this.promise\n }\n\n if (this.config.infinite) {\n const infiniteConfig = this.config as InfiniteQueryConfig<TResult, TError>\n const infiniteData = (this.state.data as unknown) as TResult[] | undefined\n const fetchMore = options?.fetchMore\n\n const originalQueryFn = queryFn\n\n queryFn = async () => {\n const data: TResult[] = []\n const pageVariables = this.pageVariables ? [...this.pageVariables] : []\n const rebuiltPageVariables: ArrayQueryKey[] = []\n\n do {\n const args = pageVariables.shift()!\n\n if (!data.length) {\n // the first page query doesn't need to be rebuilt\n data.push(await originalQueryFn(...args))\n rebuiltPageVariables.push(args)\n } else {\n // get an up-to-date cursor based on the previous data set\n\n const nextCursor = infiniteConfig.getFetchMore(\n data[data.length - 1],\n data\n )\n\n // break early if there's no next cursor\n // otherwise we'll start from the beginning\n // which will cause unwanted duplication\n if (!nextCursor) {\n break\n }\n\n const pageArgs = [\n // remove the last argument (the previously saved cursor)\n ...args.slice(0, -1),\n nextCursor,\n ] as ArrayQueryKey\n\n data.push(await originalQueryFn(...pageArgs))\n rebuiltPageVariables.push(pageArgs)\n }\n } while (pageVariables.length)\n\n this.fetchMoreVariable = infiniteConfig.getFetchMore(\n data[data.length - 1],\n data\n )\n this.state.canFetchMore = Boolean(this.fetchMoreVariable)\n this.pageVariables = rebuiltPageVariables\n\n return (data as unknown) as TResult\n }\n\n if (fetchMore) {\n queryFn = async (...args: ArrayQueryKey) => {\n try {\n const { fetchMoreVariable, previous } = fetchMore\n\n this.setState(old => ({\n ...old,\n isFetchingMore: previous ? 'previous' : 'next',\n }))\n\n const newArgs = [...args, fetchMoreVariable] as ArrayQueryKey\n\n if (this.pageVariables) {\n this.pageVariables[previous ? 'unshift' : 'push'](newArgs)\n } else {\n this.pageVariables = [newArgs]\n }\n\n const newData = await originalQueryFn(...newArgs)\n\n let data\n\n if (!infiniteData) {\n data = [newData]\n } else if (previous) {\n data = [newData, ...infiniteData]\n } else {\n data = [...infiniteData, newData]\n }\n\n this.fetchMoreVariable = infiniteConfig.getFetchMore(newData, data)\n this.state.canFetchMore = Boolean(this.fetchMoreVariable)\n\n return (data as unknown) as TResult\n } finally {\n this.setState(old => ({\n ...old,\n isFetchingMore: false,\n }))\n }\n }\n }\n }\n\n this.promise = (async () => {\n // If there are any retries pending for this query, kill them\n this.cancelled = null\n\n try {\n // Set to fetching state if not already in it\n if (!this.state.isFetching) {\n this.dispatch({ type: ActionType.Fetch })\n }\n\n // Try to get the data\n const data = await this.tryFetchData(queryFn!, this.queryKey)\n\n this.setData(data)\n\n delete this.promise\n\n return data\n } catch (error) {\n this.dispatch({\n type: ActionType.Error,\n cancelled: error === this.cancelled,\n error,\n })\n\n delete this.promise\n\n if (error !== this.cancelled) {\n throw error\n }\n\n return\n }\n })()\n\n return this.promise\n }\n\n fetchMore(\n fetchMoreVariable?: unknown,\n options?: FetchMoreOptions\n ): Promise<TResult | undefined> {\n return this.fetch({\n fetchMore: {\n fetchMoreVariable: fetchMoreVariable ?? this.fetchMoreVariable,\n previous: options?.previous || false,\n },\n })\n }\n}\n\nfunction getDefaultState<TResult, TError>(\n config: QueryConfig<TResult, TError>\n): QueryState<TResult, TError> {\n const initialData =\n typeof config.initialData === 'function'\n ? (config.initialData as InitialDataFunction<TResult>)()\n : config.initialData\n\n const hasInitialData = typeof initialData !== 'undefined'\n\n const isStale =\n !config.enabled ||\n (typeof config.initialStale === 'function'\n ? config.initialStale()\n : config.initialStale ?? !hasInitialData)\n\n const initialStatus = hasInitialData\n ? QueryStatus.Success\n : config.enabled\n ? QueryStatus.Loading\n : QueryStatus.Idle\n\n return {\n ...getStatusProps(initialStatus),\n error: null,\n isFetched: false,\n isFetching: initialStatus === QueryStatus.Loading,\n isFetchingMore: false,\n failureCount: 0,\n isStale,\n data: initialData,\n updatedAt: hasInitialData ? Date.now() : 0,\n }\n}\n\nexport function queryReducer<TResult, TError>(\n state: QueryState<TResult, TError>,\n action: Action<TResult, TError>\n): QueryState<TResult, TError> {\n switch (action.type) {\n case ActionType.Failed:\n return {\n ...state,\n failureCount: state.failureCount + 1,\n }\n case ActionType.MarkStale:\n return {\n ...state,\n isStale: true,\n }\n case ActionType.Fetch:\n const status =\n typeof state.data !== 'undefined'\n ? QueryStatus.Success\n : QueryStatus.Loading\n return {\n ...state,\n ...getStatusProps(status),\n isFetching: true,\n failureCount: 0,\n }\n case ActionType.Success:\n return {\n ...state,\n ...getStatusProps(QueryStatus.Success),\n data: action.data,\n error: null,\n isStale: action.isStale,\n isFetched: true,\n isFetching: false,\n updatedAt: Date.now(),\n failureCount: 0,\n }\n case ActionType.Error:\n return {\n ...state,\n failureCount: state.failureCount + 1,\n isFetched: true,\n isFetching: false,\n isStale: true,\n ...(!action.cancelled && {\n ...getStatusProps(QueryStatus.Error),\n error: action.error,\n throwInErrorBoundary: true,\n }),\n }\n case ActionType.SetState:\n return functionalUpdate(action.updater, state)\n default:\n return state\n }\n}\n","import {\n isServer,\n getQueryArgs,\n deepIncludes,\n Console,\n isObject,\n Updater,\n} from './utils'\nimport { getDefaultedQueryConfig } from './config'\nimport { Query } from './query'\nimport {\n QueryConfig,\n QueryKey,\n QueryKeyWithoutObject,\n ReactQueryConfig,\n QueryKeyWithoutArray,\n QueryKeyWithoutObjectAndArray,\n TupleQueryFunction,\n TupleQueryKey,\n} from './types'\n\n// TYPES\n\ninterface QueryCacheConfig {\n frozen?: boolean\n defaultConfig?: ReactQueryConfig\n}\n\ninterface ClearOptions {\n notify?: boolean\n}\n\ninterface PrefetchQueryOptions {\n force?: boolean\n throwOnError?: boolean\n}\n\ninterface InvalidateQueriesOptions extends QueryPredicateOptions {\n refetchActive?: boolean\n refetchInactive?: boolean\n throwOnError?: boolean\n}\n\ninterface QueryPredicateOptions {\n exact?: boolean\n}\n\ntype QueryPredicate = QueryKey | QueryPredicateFn | true\n\ntype QueryPredicateFn = (query: Query<unknown, unknown>) => boolean\n\nexport interface PrefetchQueryObjectConfig<\n TResult,\n TError,\n TKey extends TupleQueryKey\n> {\n queryKey: QueryKey\n queryFn?: TupleQueryFunction<TResult, TKey>\n config?: QueryConfig<TResult, TError>\n options?: PrefetchQueryOptions\n}\n\ninterface QueryHashMap {\n [hash: string]: Query<any, any>\n}\n\ntype QueryCacheListener = (\n cache: QueryCache,\n query?: Query<unknown, unknown>\n) => void\n\n// CLASS\n\nexport class QueryCache {\n queries: QueryHashMap\n isFetching: number\n\n private config: QueryCacheConfig\n private globalListeners: QueryCacheListener[]\n\n constructor(config?: QueryCacheConfig) {\n this.config = config || {}\n\n // A frozen cache does not add new queries to the cache\n this.globalListeners = []\n\n this.queries = {}\n this.isFetching = 0\n }\n\n private notifyGlobalListeners(query?: Query<any, any>) {\n this.isFetching = Object.values(this.queries).reduce(\n (acc, query) => (query.state.isFetching ? acc + 1 : acc),\n 0\n )\n\n this.globalListeners.forEach(d => d(this, query))\n }\n\n getDefaultConfig() {\n return this.config.defaultConfig\n }\n\n getDefaultedQueryConfig<TResult, TError>(\n config?: QueryConfig<TResult, TError>\n ): QueryConfig<TResult, TError> {\n return getDefaultedQueryConfig(this.getDefaultConfig(), undefined, config, {\n queryCache: this,\n })\n }\n\n subscribe(listener: QueryCacheListener): () => void {\n this.globalListeners.push(listener)\n return () => {\n this.globalListeners.splice(this.globalListeners.indexOf(listener), 1)\n }\n }\n\n clear(options?: ClearOptions): void {\n Object.values(this.queries).forEach(query => query.clear())\n this.queries = {}\n if (options?.notify) {\n this.notifyGlobalListeners()\n }\n }\n\n getQueries<TResult = unknown, TError = unknown>(\n predicate: QueryPredicate,\n options?: QueryPredicateOptions\n ): Query<TResult, TError>[] {\n if (predicate === true) {\n return Object.values(this.queries)\n }\n\n let predicateFn: QueryPredicateFn\n\n if (typeof predicate === 'function') {\n predicateFn = predicate as QueryPredicateFn\n } else {\n const config = this.getDefaultedQueryConfig()\n const [queryHash, queryKey] = config.queryKeySerializerFn!(predicate)\n\n predicateFn = d =>\n options?.exact\n ? d.queryHash === queryHash\n : deepIncludes(d.queryKey, queryKey)\n }\n\n return Object.values(this.queries).filter(predicateFn)\n }\n\n getQuery<TResult, TError = unknown>(\n predicate: QueryPredicate\n ): Query<TResult, TError> | undefined {\n return this.getQueries<TResult, TError>(predicate, { exact: true })[0]\n }\n\n getQueryData<TResult>(predicate: QueryPredicate): TResult | undefined {\n return this.getQuery<TResult>(predicate)?.state.data\n }\n\n removeQueries(\n predicate: QueryPredicate,\n options?: QueryPredicateOptions\n ): void {\n this.getQueries(predicate, options).forEach(query => query.clear())\n }\n\n cancelQueries(\n predicate: QueryPredicate,\n options?: QueryPredicateOptions\n ): void {\n this.getQueries(predicate, options).forEach(query => query.cancel())\n }\n\n async invalidateQueries(\n predicate: QueryPredicate,\n options?: InvalidateQueriesOptions\n ): Promise<void> {\n const { refetchActive = true, refetchInactive = false, throwOnError } =\n options || {}\n\n try {\n await Promise.all(\n this.getQueries(predicate, options).map(query => {\n if (query.observers.length) {\n if (refetchActive && query.isEnabled()) {\n return query.fetch()\n }\n } else {\n if (refetchInactive) {\n return query.fetch()\n }\n }\n\n return query.invalidate()\n })\n )\n } catch (err) {\n if (throwOnError) {\n throw err\n }\n }\n }\n\n resetErrorBoundaries(): void {\n this.getQueries(true).forEach(query => {\n query.state.throwInErrorBoundary = false\n })\n }\n\n buildQuery<TResult, TError = unknown>(\n userQueryKey: QueryKey,\n queryConfig?: QueryConfig<TResult, TError>\n ): Query<TResult, TError> {\n const config = this.getDefaultedQueryConfig(queryConfig)\n\n const [queryHash, queryKey] = config.queryKeySerializerFn!(userQueryKey)\n\n let query\n\n if (this.queries[queryHash]) {\n query = this.queries[queryHash] as Query<TResult, TError>\n query.updateConfig(config)\n }\n\n if (!query) {\n query = new Query<TResult, TError>({\n queryCache: this,\n queryKey,\n queryHash,\n config,\n notifyGlobalListeners: query => {\n this.notifyGlobalListeners(query)\n },\n })\n\n if (!this.config.frozen) {\n this.queries[queryHash] = query\n\n if (isServer) {\n this.notifyGlobalListeners()\n } else {\n // Here, we setTimeout so as to not trigger\n // any setState's in parent components in the\n // middle of the render phase.\n setTimeout(() => {\n this.notifyGlobalListeners()\n })\n }\n }\n }\n\n return query\n }\n\n // Parameter syntax with optional prefetch options\n async prefetchQuery<TResult, TError, TKey extends QueryKeyWithoutObject>(\n queryKey: TKey,\n options?: PrefetchQueryOptions\n ): Promise<TResult | undefined>\n\n // Parameter syntax with config and optional prefetch options\n async prefetchQuery<TResult, TError, TKey extends QueryKeyWithoutObject>(\n queryKey: TKey,\n config: QueryConfig<TResult, TError>,\n options?: PrefetchQueryOptions\n ): Promise<TResult | undefined>\n\n // Parameter syntax with query function and optional prefetch options\n async prefetchQuery<\n TResult,\n TError,\n TKey extends QueryKeyWithoutObjectAndArray\n >(\n queryKey: TKey,\n queryFn: TupleQueryFunction<TResult, [TKey]>,\n options?: PrefetchQueryOptions\n ): Promise<TResult | undefined>\n\n async prefetchQuery<TResult, TError, TKey extends TupleQueryKey>(\n queryKey: TKey,\n queryFn: TupleQueryFunction<TResult, TKey>,\n options?: PrefetchQueryOptions\n ): Promise<TResult | undefined>\n\n // Parameter syntax with query function, config and optional