Initial commit: Crypto trader application

This commit is contained in:
2025-12-25 20:20:40 -05:00
commit 07a04c1bb8
47895 changed files with 2042266 additions and 0 deletions

21
frontend/node_modules/@tanstack/react-query/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-present Tanner Linsley
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

48
frontend/node_modules/@tanstack/react-query/README.md generated vendored Normal file
View File

@@ -0,0 +1,48 @@
<img src="https://static.scarf.sh/a.png?x-pxid=be2d8a11-9712-4c1d-9963-580b2d4fb133" />
![TanStack Query Header](https://github.com/TanStack/query/raw/main/media/repo-header.png)
Hooks for fetching, caching and updating asynchronous data in React
<a href="https://twitter.com/intent/tweet?button_hashtag=TanStack" target="\_parent">
<img alt="#TanStack" src="https://img.shields.io/twitter/url?color=%2308a0e9&label=%23TanStack&style=social&url=https%3A%2F%2Ftwitter.com%2Fintent%2Ftweet%3Fbutton_hashtag%3DTanStack">
</a><a href="https://discord.com/invite/WrRKjPJ" target="\_parent">
<img alt="" src="https://img.shields.io/badge/Discord-TanStack-%235865F2" />
</a><a href="https://github.com/TanStack/query/actions?query=workflow%3A%22react-query+tests%22">
<img src="https://github.com/TanStack/query/workflows/react-query%20tests/badge.svg" />
</a><a href="https://www.npmjs.com/package/@tanstack/query-core" target="\_parent">
<img alt="" src="https://img.shields.io/npm/dm/@tanstack/query-core.svg" />
</a><a href="https://bundlejs.com/?q=%40tanstack%2Freact-query&config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%2C%22react-dom%22%5D%7D%7D&badge=" target="\_parent">
<img alt="" src="https://deno.bundlejs.com/?q=@tanstack/react-query&config={%22esbuild%22:{%22external%22:[%22react%22,%22react-dom%22]}}&badge=detailed" />
</a><a href="#badge">
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a><a href="https://github.com/TanStack/query/discussions">
<img alt="Join the discussion on Github" src="https://img.shields.io/badge/Github%20Discussions%20%26%20Support-Chat%20now!-blue" />
</a><a href="https://bestofjs.org/projects/tanstack-query"><img alt="Best of JS" src="https://img.shields.io/endpoint?url=https://bestofjs-serverless.now.sh/api/project-badge?fullName=TanStack%2Fquery%26since=daily" /></a><a href="https://github.com/TanStack/query/" target="\_parent">
<img alt="" src="https://img.shields.io/github/stars/TanStack/query.svg?style=social&label=Star" />
</a><a href="https://twitter.com/tannerlinsley" target="\_parent">
<img alt="" src="https://img.shields.io/twitter/follow/tannerlinsley.svg?style=social&label=Follow" />
</a> <a href="https://gitpod.io/from-referrer/">
<img src="https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod" alt="Gitpod Ready-to-Code"/>
</a>
Enjoy this library? Try the entire [TanStack](https://tanstack.com)! [TanStack Table](https://github.com/TanStack/table), [TanStack Router](https://github.com/tanstack/router), [TanStack Virtual](https://github.com/tanstack/virtual), [React Charts](https://github.com/TanStack/react-charts), [React Ranger](https://github.com/TanStack/ranger)
## Visit [tanstack.com/query](https://tanstack.com/query) for docs, guides, API and more!
## Quick Features
- Transport/protocol/backend agnostic data fetching (REST, GraphQL, promises, whatever!)
- Auto Caching + Refetching (stale-while-revalidate, Window Refocus, Polling/Realtime)
- Parallel + Dependent Queries
- Mutations + Reactive Query Refetching
- Multi-layer Cache + Automatic Garbage Collection
- Paginated + Cursor-based Queries
- Load-More + Infinite Scroll Queries w/ Scroll Recovery
- Request Cancellation
- [React Suspense](https://react.dev/reference/react/Suspense) + Fetch-As-You-Render Query Prefetching
- Dedicated Devtools
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
<!-- Use the force, Luke -->

View File

@@ -0,0 +1,87 @@
{
"name": "@tanstack/react-query",
"version": "5.90.12",
"description": "Hooks for managing, caching and syncing asynchronous and remote data in React",
"author": "tannerlinsley",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/TanStack/query.git",
"directory": "packages/react-query"
},
"homepage": "https://tanstack.com/query",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"type": "module",
"types": "build/legacy/index.d.ts",
"main": "build/legacy/index.cjs",
"module": "build/legacy/index.js",
"react-native": "src/index.ts",
"exports": {
".": {
"@tanstack/custom-condition": "./src/index.ts",
"import": {
"types": "./build/modern/index.d.ts",
"default": "./build/modern/index.js"
},
"require": {
"types": "./build/modern/index.d.cts",
"default": "./build/modern/index.cjs"
}
},
"./package.json": "./package.json"
},
"sideEffects": false,
"files": [
"build",
"src",
"!src/__tests__",
"!build/codemods/node_modules",
"!build/codemods/vite.config.ts",
"!build/codemods/**/__testfixtures__",
"!build/codemods/**/__tests__"
],
"dependencies": {
"@tanstack/query-core": "5.90.12"
},
"devDependencies": {
"@testing-library/react": "^16.1.0",
"@testing-library/react-render-stream": "^2.0.0",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.3.4",
"cpy-cli": "^5.0.0",
"npm-run-all2": "^5.0.0",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"react-error-boundary": "^4.1.2",
"@tanstack/query-persist-client-core": "5.91.11",
"@tanstack/query-test-utils": "0.0.0"
},
"peerDependencies": {
"react": "^18 || ^19"
},
"scripts": {
"clean": "premove ./build ./coverage ./dist-ts",
"compile": "tsc --build",
"test:eslint": "eslint --concurrency=auto ./src",
"test:types": "npm-run-all --serial test:types:*",
"test:types:ts50": "node ../../node_modules/typescript50/lib/tsc.js --build tsconfig.legacy.json",
"test:types:ts51": "node ../../node_modules/typescript51/lib/tsc.js --build tsconfig.legacy.json",
"test:types:ts52": "node ../../node_modules/typescript52/lib/tsc.js --build tsconfig.legacy.json",
"test:types:ts53": "node ../../node_modules/typescript53/lib/tsc.js --build tsconfig.legacy.json",
"test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js --build tsconfig.legacy.json",
"test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js --build tsconfig.legacy.json",
"test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js --build tsconfig.legacy.json",
"test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js --build tsconfig.legacy.json",
"test:types:tscurrent": "tsc --build",
"test:lib": "vitest",
"test:lib:dev": "pnpm run test:lib --watch",
"test:build": "publint --strict && attw --pack",
"build": "pnpm build:tsup && pnpm build:codemods",
"build:tsup": "tsup --tsconfig tsconfig.prod.json",
"build:codemods": "cpy ../query-codemods/* ./build/codemods"
}
}

View File

@@ -0,0 +1,111 @@
'use client'
import * as React from 'react'
import { hydrate } from '@tanstack/query-core'
import { useQueryClient } from './QueryClientProvider'
import type {
DehydratedState,
HydrateOptions,
OmitKeyof,
QueryClient,
} from '@tanstack/query-core'
export interface HydrationBoundaryProps {
state: DehydratedState | null | undefined
options?: OmitKeyof<HydrateOptions, 'defaultOptions'> & {
defaultOptions?: OmitKeyof<
Exclude<HydrateOptions['defaultOptions'], undefined>,
'mutations'
>
}
children?: React.ReactNode
queryClient?: QueryClient
}
export const HydrationBoundary = ({
children,
options = {},
state,
queryClient,
}: HydrationBoundaryProps) => {
const client = useQueryClient(queryClient)
const optionsRef = React.useRef(options)
React.useEffect(() => {
optionsRef.current = options
})
// This useMemo is for performance reasons only, everything inside it must
// be safe to run in every render and code here should be read as "in render".
//
// This code needs to happen during the render phase, because after initial
// SSR, hydration needs to happen _before_ children render. Also, if hydrating
// during a transition, we want to hydrate as much as is safe in render so
// we can prerender as much as possible.
//
// For any queries that already exist in the cache, we want to hold back on
// hydrating until _after_ the render phase. The reason for this is that during
// transitions, we don't want the existing queries and observers to update to
// the new data on the current page, only _after_ the transition is committed.
// If the transition is aborted, we will have hydrated any _new_ queries, but
// we throw away the fresh data for any existing ones to avoid unexpectedly
// updating the UI.
const hydrationQueue: DehydratedState['queries'] | undefined =
React.useMemo(() => {
if (state) {
if (typeof state !== 'object') {
return
}
const queryCache = client.getQueryCache()
// State is supplied from the outside and we might as well fail
// gracefully if it has the wrong shape, so while we type `queries`
// as required, we still provide a fallback.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const queries = state.queries || []
const newQueries: DehydratedState['queries'] = []
const existingQueries: DehydratedState['queries'] = []
for (const dehydratedQuery of queries) {
const existingQuery = queryCache.get(dehydratedQuery.queryHash)
if (!existingQuery) {
newQueries.push(dehydratedQuery)
} else {
const hydrationIsNewer =
dehydratedQuery.state.dataUpdatedAt >
existingQuery.state.dataUpdatedAt ||
(dehydratedQuery.promise &&
existingQuery.state.status !== 'pending' &&
existingQuery.state.fetchStatus !== 'fetching' &&
dehydratedQuery.dehydratedAt !== undefined &&
dehydratedQuery.dehydratedAt >
existingQuery.state.dataUpdatedAt)
if (hydrationIsNewer) {
existingQueries.push(dehydratedQuery)
}
}
}
if (newQueries.length > 0) {
// It's actually fine to call this with queries/state that already exists
// in the cache, or is older. hydrate() is idempotent for queries.
// eslint-disable-next-line react-hooks/refs
hydrate(client, { queries: newQueries }, optionsRef.current)
}
if (existingQueries.length > 0) {
return existingQueries
}
}
return undefined
}, [client, state])
React.useEffect(() => {
if (hydrationQueue) {
hydrate(client, { queries: hydrationQueue }, optionsRef.current)
}
}, [client, hydrationQueue])
return children as React.ReactElement
}

View File

@@ -0,0 +1,7 @@
'use client'
import * as React from 'react'
const IsRestoringContext = React.createContext(false)
export const useIsRestoring = () => React.useContext(IsRestoringContext)
export const IsRestoringProvider = IsRestoringContext.Provider

View File

@@ -0,0 +1,45 @@
'use client'
import * as React from 'react'
import type { QueryClient } from '@tanstack/query-core'
export const QueryClientContext = React.createContext<QueryClient | undefined>(
undefined,
)
export const useQueryClient = (queryClient?: QueryClient) => {
const client = React.useContext(QueryClientContext)
if (queryClient) {
return queryClient
}
if (!client) {
throw new Error('No QueryClient set, use QueryClientProvider to set one')
}
return client
}
export type QueryClientProviderProps = {
client: QueryClient
children?: React.ReactNode
}
export const QueryClientProvider = ({
client,
children,
}: QueryClientProviderProps): React.JSX.Element => {
React.useEffect(() => {
client.mount()
return () => {
client.unmount()
}
}, [client])
return (
<QueryClientContext.Provider value={client}>
{children}
</QueryClientContext.Provider>
)
}

View File

@@ -0,0 +1,56 @@
'use client'
import * as React from 'react'
// CONTEXT
export type QueryErrorResetFunction = () => void
export type QueryErrorIsResetFunction = () => boolean
export type QueryErrorClearResetFunction = () => void
export interface QueryErrorResetBoundaryValue {
clearReset: QueryErrorClearResetFunction
isReset: QueryErrorIsResetFunction
reset: QueryErrorResetFunction
}
function createValue(): QueryErrorResetBoundaryValue {
let isReset = false
return {
clearReset: () => {
isReset = false
},
reset: () => {
isReset = true
},
isReset: () => {
return isReset
},
}
}
const QueryErrorResetBoundaryContext = React.createContext(createValue())
// HOOK
export const useQueryErrorResetBoundary = () =>
React.useContext(QueryErrorResetBoundaryContext)
// COMPONENT
export type QueryErrorResetBoundaryFunction = (
value: QueryErrorResetBoundaryValue,
) => React.ReactNode
export interface QueryErrorResetBoundaryProps {
children: QueryErrorResetBoundaryFunction | React.ReactNode
}
export const QueryErrorResetBoundary = ({
children,
}: QueryErrorResetBoundaryProps) => {
const [value] = React.useState(() => createValue())
return (
<QueryErrorResetBoundaryContext.Provider value={value}>
{typeof children === 'function' ? children(value) : children}
</QueryErrorResetBoundaryContext.Provider>
)
}

View File

@@ -0,0 +1,76 @@
'use client'
import * as React from 'react'
import { shouldThrowError } from '@tanstack/query-core'
import type {
DefaultedQueryObserverOptions,
Query,
QueryKey,
QueryObserverResult,
ThrowOnError,
} from '@tanstack/query-core'
import type { QueryErrorResetBoundaryValue } from './QueryErrorResetBoundary'
export const ensurePreventErrorBoundaryRetry = <
TQueryFnData,
TError,
TData,
TQueryData,
TQueryKey extends QueryKey,
>(
options: DefaultedQueryObserverOptions<
TQueryFnData,
TError,
TData,
TQueryData,
TQueryKey
>,
errorResetBoundary: QueryErrorResetBoundaryValue,
) => {
if (
options.suspense ||
options.throwOnError ||
options.experimental_prefetchInRender
) {
// Prevent retrying failed query if the error boundary has not been reset yet
if (!errorResetBoundary.isReset()) {
options.retryOnMount = false
}
}
}
export const useClearResetErrorBoundary = (
errorResetBoundary: QueryErrorResetBoundaryValue,
) => {
React.useEffect(() => {
errorResetBoundary.clearReset()
}, [errorResetBoundary])
}
export const getHasError = <
TData,
TError,
TQueryFnData,
TQueryData,
TQueryKey extends QueryKey,
>({
result,
errorResetBoundary,
throwOnError,
query,
suspense,
}: {
result: QueryObserverResult<TData, TError>
errorResetBoundary: QueryErrorResetBoundaryValue
throwOnError: ThrowOnError<TQueryFnData, TError, TQueryData, TQueryKey>
query: Query<TQueryFnData, TError, TQueryData, TQueryKey> | undefined
suspense: boolean | undefined
}) => {
return (
result.isError &&
!errorResetBoundary.isReset() &&
!result.isFetching &&
query &&
((suspense && result.data === undefined) ||
shouldThrowError(throwOnError, [result.error, query]))
)
}

View File

@@ -0,0 +1,56 @@
/* istanbul ignore file */
// Re-export core
export * from '@tanstack/query-core'
// React Query
export * from './types'
export { useQueries } from './useQueries'
export type { QueriesResults, QueriesOptions } from './useQueries'
export { useQuery } from './useQuery'
export { useSuspenseQuery } from './useSuspenseQuery'
export { useSuspenseInfiniteQuery } from './useSuspenseInfiniteQuery'
export { useSuspenseQueries } from './useSuspenseQueries'
export type {
SuspenseQueriesResults,
SuspenseQueriesOptions,
} from './useSuspenseQueries'
export { usePrefetchQuery } from './usePrefetchQuery'
export { usePrefetchInfiniteQuery } from './usePrefetchInfiniteQuery'
export { queryOptions } from './queryOptions'
export type {
DefinedInitialDataOptions,
UndefinedInitialDataOptions,
UnusedSkipTokenOptions,
} from './queryOptions'
export { infiniteQueryOptions } from './infiniteQueryOptions'
export type {
DefinedInitialDataInfiniteOptions,
UndefinedInitialDataInfiniteOptions,
UnusedSkipTokenInfiniteOptions,
} from './infiniteQueryOptions'
export {
QueryClientContext,
QueryClientProvider,
useQueryClient,
} from './QueryClientProvider'
export type { QueryClientProviderProps } from './QueryClientProvider'
export type { QueryErrorResetBoundaryProps } from './QueryErrorResetBoundary'
export { HydrationBoundary } from './HydrationBoundary'
export type { HydrationBoundaryProps } from './HydrationBoundary'
export type {
QueryErrorClearResetFunction,
QueryErrorIsResetFunction,
QueryErrorResetBoundaryFunction,
QueryErrorResetFunction,
} from './QueryErrorResetBoundary'
export {
QueryErrorResetBoundary,
useQueryErrorResetBoundary,
} from './QueryErrorResetBoundary'
export { useIsFetching } from './useIsFetching'
export { useIsMutating, useMutationState } from './useMutationState'
export { useMutation } from './useMutation'
export { mutationOptions } from './mutationOptions'
export { useInfiniteQuery } from './useInfiniteQuery'
export { useIsRestoring, IsRestoringProvider } from './IsRestoringProvider'

View File

@@ -0,0 +1,149 @@
import type {
DataTag,
DefaultError,
InfiniteData,
InitialDataFunction,
NonUndefinedGuard,
OmitKeyof,
QueryKey,
SkipToken,
} from '@tanstack/query-core'
import type { UseInfiniteQueryOptions } from './types'
export type UndefinedInitialDataInfiniteOptions<
TQueryFnData,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
> = UseInfiniteQueryOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
> & {
initialData?:
| undefined
| NonUndefinedGuard<InfiniteData<TQueryFnData, TPageParam>>
| InitialDataFunction<
NonUndefinedGuard<InfiniteData<TQueryFnData, TPageParam>>
>
}
export type UnusedSkipTokenInfiniteOptions<
TQueryFnData,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
> = OmitKeyof<
UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>,
'queryFn'
> & {
queryFn?: Exclude<
UseInfiniteQueryOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>['queryFn'],
SkipToken | undefined
>
}
export type DefinedInitialDataInfiniteOptions<
TQueryFnData,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
> = UseInfiniteQueryOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
> & {
initialData:
| NonUndefinedGuard<InfiniteData<TQueryFnData, TPageParam>>
| (() => NonUndefinedGuard<InfiniteData<TQueryFnData, TPageParam>>)
| undefined
}
export function infiniteQueryOptions<
TQueryFnData,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
>(
options: DefinedInitialDataInfiniteOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
): DefinedInitialDataInfiniteOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
> & {
queryKey: DataTag<TQueryKey, InfiniteData<TQueryFnData>, TError>
}
export function infiniteQueryOptions<
TQueryFnData,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
>(
options: UnusedSkipTokenInfiniteOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
): UnusedSkipTokenInfiniteOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
> & {
queryKey: DataTag<TQueryKey, InfiniteData<TQueryFnData>, TError>
}
export function infiniteQueryOptions<
TQueryFnData,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
>(
options: UndefinedInitialDataInfiniteOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
): UndefinedInitialDataInfiniteOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
> & {
queryKey: DataTag<TQueryKey, InfiniteData<TQueryFnData>, TError>
}
export function infiniteQueryOptions(options: unknown) {
return options
}

View File

@@ -0,0 +1,41 @@
import type { DefaultError, WithRequired } from '@tanstack/query-core'
import type { UseMutationOptions } from './types'
export function mutationOptions<
TData = unknown,
TError = DefaultError,
TVariables = void,
TOnMutateResult = unknown,
>(
options: WithRequired<
UseMutationOptions<TData, TError, TVariables, TOnMutateResult>,
'mutationKey'
>,
): WithRequired<
UseMutationOptions<TData, TError, TVariables, TOnMutateResult>,
'mutationKey'
>
export function mutationOptions<
TData = unknown,
TError = DefaultError,
TVariables = void,
TOnMutateResult = unknown,
>(
options: Omit<
UseMutationOptions<TData, TError, TVariables, TOnMutateResult>,
'mutationKey'
>,
): Omit<
UseMutationOptions<TData, TError, TVariables, TOnMutateResult>,
'mutationKey'
>
export function mutationOptions<
TData = unknown,
TError = DefaultError,
TVariables = void,
TOnMutateResult = unknown,
>(
options: UseMutationOptions<TData, TError, TVariables, TOnMutateResult>,
): UseMutationOptions<TData, TError, TVariables, TOnMutateResult> {
return options
}

View File

@@ -0,0 +1,87 @@
import type {
DataTag,
DefaultError,
InitialDataFunction,
NonUndefinedGuard,
OmitKeyof,
QueryFunction,
QueryKey,
SkipToken,
} from '@tanstack/query-core'
import type { UseQueryOptions } from './types'
export type UndefinedInitialDataOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> = UseQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
initialData?:
| undefined
| InitialDataFunction<NonUndefinedGuard<TQueryFnData>>
| NonUndefinedGuard<TQueryFnData>
}
export type UnusedSkipTokenOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> = OmitKeyof<
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
'queryFn'
> & {
queryFn?: Exclude<
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>['queryFn'],
SkipToken | undefined
>
}
export type DefinedInitialDataOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> = Omit<UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, 'queryFn'> & {
initialData:
| NonUndefinedGuard<TQueryFnData>
| (() => NonUndefinedGuard<TQueryFnData>)
queryFn?: QueryFunction<TQueryFnData, TQueryKey>
}
export function queryOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
): DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
queryKey: DataTag<TQueryKey, TQueryFnData, TError>
}
export function queryOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
options: UnusedSkipTokenOptions<TQueryFnData, TError, TData, TQueryKey>,
): UnusedSkipTokenOptions<TQueryFnData, TError, TData, TQueryKey> & {
queryKey: DataTag<TQueryKey, TQueryFnData, TError>
}
export function queryOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
options: UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
): UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
queryKey: DataTag<TQueryKey, TQueryFnData, TError>
}
export function queryOptions(options: unknown) {
return options
}

View File

@@ -0,0 +1,80 @@
import type {
DefaultError,
DefaultedQueryObserverOptions,
Query,
QueryKey,
QueryObserver,
QueryObserverResult,
} from '@tanstack/query-core'
import type { QueryErrorResetBoundaryValue } from './QueryErrorResetBoundary'
export const defaultThrowOnError = <
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
_error: TError,
query: Query<TQueryFnData, TError, TData, TQueryKey>,
) => query.state.data === undefined
export const ensureSuspenseTimers = (
defaultedOptions: DefaultedQueryObserverOptions<any, any, any, any, any>,
) => {
if (defaultedOptions.suspense) {
// Handle staleTime to ensure minimum 1000ms in Suspense mode
// This prevents unnecessary refetching when components remount after suspending
const MIN_SUSPENSE_TIME_MS = 1000
const clamp = (value: number | 'static' | undefined) =>
value === 'static'
? value
: Math.max(value ?? MIN_SUSPENSE_TIME_MS, MIN_SUSPENSE_TIME_MS)
const originalStaleTime = defaultedOptions.staleTime
defaultedOptions.staleTime =
typeof originalStaleTime === 'function'
? (...args) => clamp(originalStaleTime(...args))
: clamp(originalStaleTime)
if (typeof defaultedOptions.gcTime === 'number') {
defaultedOptions.gcTime = Math.max(
defaultedOptions.gcTime,
MIN_SUSPENSE_TIME_MS,
)
}
}
}
export const willFetch = (
result: QueryObserverResult<any, any>,
isRestoring: boolean,
) => result.isLoading && result.isFetching && !isRestoring
export const shouldSuspend = (
defaultedOptions:
| DefaultedQueryObserverOptions<any, any, any, any, any>
| undefined,
result: QueryObserverResult<any, any>,
) => defaultedOptions?.suspense && result.isPending
export const fetchOptimistic = <
TQueryFnData,
TError,
TData,
TQueryData,
TQueryKey extends QueryKey,
>(
defaultedOptions: DefaultedQueryObserverOptions<
TQueryFnData,
TError,
TData,
TQueryData,
TQueryKey
>,
observer: QueryObserver<TQueryFnData, TError, TData, TQueryData, TQueryKey>,
errorResetBoundary: QueryErrorResetBoundaryValue,
) =>
observer.fetchOptimistic(defaultedOptions).catch(() => {
errorResetBoundary.clearReset()
})

View File

@@ -0,0 +1,242 @@
/* istanbul ignore file */
import type {
DefaultError,
DefinedInfiniteQueryObserverResult,
DefinedQueryObserverResult,
DistributiveOmit,
FetchQueryOptions,
InfiniteQueryObserverOptions,
InfiniteQueryObserverResult,
MutateFunction,
MutationObserverOptions,
MutationObserverResult,
OmitKeyof,
Override,
QueryKey,
QueryObserverOptions,
QueryObserverResult,
SkipToken,
} from '@tanstack/query-core'
export type AnyUseBaseQueryOptions = UseBaseQueryOptions<
any,
any,
any,
any,
any
>
export interface UseBaseQueryOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> extends QueryObserverOptions<
TQueryFnData,
TError,
TData,
TQueryData,
TQueryKey
> {
/**
* Set this to `false` to unsubscribe this observer from updates to the query cache.
* Defaults to `true`.
*/
subscribed?: boolean
}
export interface UsePrefetchQueryOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> extends OmitKeyof<
FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
'queryFn'
> {
queryFn?: Exclude<
FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey>['queryFn'],
SkipToken
>
}
export type AnyUseQueryOptions = UseQueryOptions<any, any, any, any>
export interface UseQueryOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> extends OmitKeyof<
UseBaseQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>,
'suspense'
> {}
export type AnyUseSuspenseQueryOptions = UseSuspenseQueryOptions<
any,
any,
any,
any
>
export interface UseSuspenseQueryOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> extends OmitKeyof<
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
'queryFn' | 'enabled' | 'throwOnError' | 'placeholderData'
> {
queryFn?: Exclude<
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>['queryFn'],
SkipToken
>
}
export type AnyUseInfiniteQueryOptions = UseInfiniteQueryOptions<
any,
any,
any,
any,
any
>
export interface UseInfiniteQueryOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
> extends OmitKeyof<
InfiniteQueryObserverOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
'suspense'
> {
/**
* Set this to `false` to unsubscribe this observer from updates to the query cache.
* Defaults to `true`.
*/
subscribed?: boolean
}
export type AnyUseSuspenseInfiniteQueryOptions =
UseSuspenseInfiniteQueryOptions<any, any, any, any, any>
export interface UseSuspenseInfiniteQueryOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
> extends OmitKeyof<
UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>,
'queryFn' | 'enabled' | 'throwOnError' | 'placeholderData'
> {
queryFn?: Exclude<
UseInfiniteQueryOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>['queryFn'],
SkipToken
>
}
export type UseBaseQueryResult<
TData = unknown,
TError = DefaultError,
> = QueryObserverResult<TData, TError>
export type UseQueryResult<
TData = unknown,
TError = DefaultError,
> = UseBaseQueryResult<TData, TError>
export type UseSuspenseQueryResult<
TData = unknown,
TError = DefaultError,
> = DistributiveOmit<
DefinedQueryObserverResult<TData, TError>,
'isPlaceholderData' | 'promise'
>
export type DefinedUseQueryResult<
TData = unknown,
TError = DefaultError,
> = DefinedQueryObserverResult<TData, TError>
export type UseInfiniteQueryResult<
TData = unknown,
TError = DefaultError,
> = InfiniteQueryObserverResult<TData, TError>
export type DefinedUseInfiniteQueryResult<
TData = unknown,
TError = DefaultError,
> = DefinedInfiniteQueryObserverResult<TData, TError>
export type UseSuspenseInfiniteQueryResult<
TData = unknown,
TError = DefaultError,
> = OmitKeyof<
DefinedInfiniteQueryObserverResult<TData, TError>,
'isPlaceholderData' | 'promise'
>
export type AnyUseMutationOptions = UseMutationOptions<any, any, any, any>
export interface UseMutationOptions<
TData = unknown,
TError = DefaultError,
TVariables = void,
TOnMutateResult = unknown,
> extends OmitKeyof<
MutationObserverOptions<TData, TError, TVariables, TOnMutateResult>,
'_defaulted'
> {}
export type UseMutateFunction<
TData = unknown,
TError = DefaultError,
TVariables = void,
TOnMutateResult = unknown,
> = (
...args: Parameters<
MutateFunction<TData, TError, TVariables, TOnMutateResult>
>
) => void
export type UseMutateAsyncFunction<
TData = unknown,
TError = DefaultError,
TVariables = void,
TOnMutateResult = unknown,
> = MutateFunction<TData, TError, TVariables, TOnMutateResult>
export type UseBaseMutationResult<
TData = unknown,
TError = DefaultError,
TVariables = unknown,
TOnMutateResult = unknown,
> = Override<
MutationObserverResult<TData, TError, TVariables, TOnMutateResult>,
{ mutate: UseMutateFunction<TData, TError, TVariables, TOnMutateResult> }
> & {
mutateAsync: UseMutateAsyncFunction<
TData,
TError,
TVariables,
TOnMutateResult
>
}
export type UseMutationResult<
TData = unknown,
TError = DefaultError,
TVariables = unknown,
TOnMutateResult = unknown,
> = UseBaseMutationResult<TData, TError, TVariables, TOnMutateResult>

View File

@@ -0,0 +1,170 @@
'use client'
import * as React from 'react'
import { isServer, noop, notifyManager } from '@tanstack/query-core'
import { useQueryClient } from './QueryClientProvider'
import { useQueryErrorResetBoundary } from './QueryErrorResetBoundary'
import {
ensurePreventErrorBoundaryRetry,
getHasError,
useClearResetErrorBoundary,
} from './errorBoundaryUtils'
import { useIsRestoring } from './IsRestoringProvider'
import {
ensureSuspenseTimers,
fetchOptimistic,
shouldSuspend,
willFetch,
} from './suspense'
import type {
QueryClient,
QueryKey,
QueryObserver,
QueryObserverResult,
} from '@tanstack/query-core'
import type { UseBaseQueryOptions } from './types'
export function useBaseQuery<
TQueryFnData,
TError,
TData,
TQueryData,
TQueryKey extends QueryKey,
>(
options: UseBaseQueryOptions<
TQueryFnData,
TError,
TData,
TQueryData,
TQueryKey
>,
Observer: typeof QueryObserver,
queryClient?: QueryClient,
): QueryObserverResult<TData, TError> {
if (process.env.NODE_ENV !== 'production') {
if (typeof options !== 'object' || Array.isArray(options)) {
throw new Error(
'Bad argument type. Starting with v5, only the "Object" form is allowed when calling query related functions. Please use the error stack to find the culprit call. More info here: https://tanstack.com/query/latest/docs/react/guides/migrating-to-v5#supports-a-single-signature-one-object',
)
}
}
const isRestoring = useIsRestoring()
const errorResetBoundary = useQueryErrorResetBoundary()
const client = useQueryClient(queryClient)
const defaultedOptions = client.defaultQueryOptions(options)
;(client.getDefaultOptions().queries as any)?._experimental_beforeQuery?.(
defaultedOptions,
)
if (process.env.NODE_ENV !== 'production') {
if (!defaultedOptions.queryFn) {
console.error(
`[${defaultedOptions.queryHash}]: No queryFn was passed as an option, and no default queryFn was found. The queryFn parameter is only optional when using a default queryFn. More info here: https://tanstack.com/query/latest/docs/framework/react/guides/default-query-function`,
)
}
}
// Make sure results are optimistically set in fetching state before subscribing or updating options
defaultedOptions._optimisticResults = isRestoring
? 'isRestoring'
: 'optimistic'
ensureSuspenseTimers(defaultedOptions)
ensurePreventErrorBoundaryRetry(defaultedOptions, errorResetBoundary)
useClearResetErrorBoundary(errorResetBoundary)
// this needs to be invoked before creating the Observer because that can create a cache entry
const isNewCacheEntry = !client
.getQueryCache()
.get(defaultedOptions.queryHash)
const [observer] = React.useState(
() =>
new Observer<TQueryFnData, TError, TData, TQueryData, TQueryKey>(
client,
defaultedOptions,
),
)
// note: this must be called before useSyncExternalStore
const result = observer.getOptimisticResult(defaultedOptions)
const shouldSubscribe = !isRestoring && options.subscribed !== false
React.useSyncExternalStore(
React.useCallback(
(onStoreChange) => {
const unsubscribe = shouldSubscribe
? observer.subscribe(notifyManager.batchCalls(onStoreChange))
: noop
// Update result to make sure we did not miss any query updates
// between creating the observer and subscribing to it.
observer.updateResult()
return unsubscribe
},
[observer, shouldSubscribe],
),
() => observer.getCurrentResult(),
() => observer.getCurrentResult(),
)
React.useEffect(() => {
observer.setOptions(defaultedOptions)
}, [defaultedOptions, observer])
// Handle suspense
if (shouldSuspend(defaultedOptions, result)) {
throw fetchOptimistic(defaultedOptions, observer, errorResetBoundary)
}
// Handle error boundary
if (
getHasError({
result,
errorResetBoundary,
throwOnError: defaultedOptions.throwOnError,
query: client
.getQueryCache()
.get<
TQueryFnData,
TError,
TQueryData,
TQueryKey
>(defaultedOptions.queryHash),
suspense: defaultedOptions.suspense,
})
) {
throw result.error
}
;(client.getDefaultOptions().queries as any)?._experimental_afterQuery?.(
defaultedOptions,
result,
)
if (
defaultedOptions.experimental_prefetchInRender &&
!isServer &&
willFetch(result, isRestoring)
) {
const promise = isNewCacheEntry
? // Fetch immediately on render in order to ensure `.promise` is resolved even if the component is unmounted
fetchOptimistic(defaultedOptions, observer, errorResetBoundary)
: // subscribe to the "cache promise" so that we can finalize the currentThenable once data comes in
client.getQueryCache().get(defaultedOptions.queryHash)?.promise
promise?.catch(noop).finally(() => {
// `.updateResult()` will trigger `.#currentThenable` to finalize
observer.updateResult()
})
}
// Handle result property usage tracking
return !defaultedOptions.notifyOnChangeProps
? observer.trackResult(result)
: result
}

View File

@@ -0,0 +1,81 @@
'use client'
import { InfiniteQueryObserver } from '@tanstack/query-core'
import { useBaseQuery } from './useBaseQuery'
import type {
DefaultError,
InfiniteData,
QueryClient,
QueryKey,
QueryObserver,
} from '@tanstack/query-core'
import type {
DefinedUseInfiniteQueryResult,
UseInfiniteQueryOptions,
UseInfiniteQueryResult,
} from './types'
import type {
DefinedInitialDataInfiniteOptions,
UndefinedInitialDataInfiniteOptions,
} from './infiniteQueryOptions'
export function useInfiniteQuery<
TQueryFnData,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
>(
options: DefinedInitialDataInfiniteOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
queryClient?: QueryClient,
): DefinedUseInfiniteQueryResult<TData, TError>
export function useInfiniteQuery<
TQueryFnData,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
>(
options: UndefinedInitialDataInfiniteOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
queryClient?: QueryClient,
): UseInfiniteQueryResult<TData, TError>
export function useInfiniteQuery<
TQueryFnData,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
>(
options: UseInfiniteQueryOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
queryClient?: QueryClient,
): UseInfiniteQueryResult<TData, TError>
export function useInfiniteQuery(
options: UseInfiniteQueryOptions,
queryClient?: QueryClient,
) {
return useBaseQuery(
options,
InfiniteQueryObserver as typeof QueryObserver,
queryClient,
)
}

View File

@@ -0,0 +1,24 @@
'use client'
import * as React from 'react'
import { notifyManager } from '@tanstack/query-core'
import { useQueryClient } from './QueryClientProvider'
import type { QueryClient, QueryFilters } from '@tanstack/query-core'
export function useIsFetching(
filters?: QueryFilters,
queryClient?: QueryClient,
): number {
const client = useQueryClient(queryClient)
const queryCache = client.getQueryCache()
return React.useSyncExternalStore(
React.useCallback(
(onStoreChange) =>
queryCache.subscribe(notifyManager.batchCalls(onStoreChange)),
[queryCache],
),
() => client.isFetching(filters),
() => client.isFetching(filters),
)
}

View File

@@ -0,0 +1,69 @@
'use client'
import * as React from 'react'
import {
MutationObserver,
noop,
notifyManager,
shouldThrowError,
} from '@tanstack/query-core'
import { useQueryClient } from './QueryClientProvider'
import type {
UseMutateFunction,
UseMutationOptions,
UseMutationResult,
} from './types'
import type { DefaultError, QueryClient } from '@tanstack/query-core'
// HOOK
export function useMutation<
TData = unknown,
TError = DefaultError,
TVariables = void,
TOnMutateResult = unknown,
>(
options: UseMutationOptions<TData, TError, TVariables, TOnMutateResult>,
queryClient?: QueryClient,
): UseMutationResult<TData, TError, TVariables, TOnMutateResult> {
const client = useQueryClient(queryClient)
const [observer] = React.useState(
() =>
new MutationObserver<TData, TError, TVariables, TOnMutateResult>(
client,
options,
),
)
React.useEffect(() => {
observer.setOptions(options)
}, [observer, options])
const result = React.useSyncExternalStore(
React.useCallback(
(onStoreChange) =>
observer.subscribe(notifyManager.batchCalls(onStoreChange)),
[observer],
),
() => observer.getCurrentResult(),
() => observer.getCurrentResult(),
)
const mutate = React.useCallback<
UseMutateFunction<TData, TError, TVariables, TOnMutateResult>
>(
(variables, mutateOptions) => {
observer.mutate(variables, mutateOptions).catch(noop)
},
[observer],
)
if (
result.error &&
shouldThrowError(observer.options.throwOnError, [result.error])
) {
throw result.error
}
return { ...result, mutate, mutateAsync: result.mutate }
}

View File

@@ -0,0 +1,75 @@
'use client'
import * as React from 'react'
import { notifyManager, replaceEqualDeep } from '@tanstack/query-core'
import { useQueryClient } from './QueryClientProvider'
import type {
Mutation,
MutationCache,
MutationFilters,
MutationState,
QueryClient,
} from '@tanstack/query-core'
export function useIsMutating(
filters?: MutationFilters,
queryClient?: QueryClient,
): number {
const client = useQueryClient(queryClient)
return useMutationState(
{ filters: { ...filters, status: 'pending' } },
client,
).length
}
type MutationStateOptions<TResult = MutationState> = {
filters?: MutationFilters
select?: (mutation: Mutation) => TResult
}
function getResult<TResult = MutationState>(
mutationCache: MutationCache,
options: MutationStateOptions<TResult>,
): Array<TResult> {
return mutationCache
.findAll(options.filters)
.map(
(mutation): TResult =>
(options.select ? options.select(mutation) : mutation.state) as TResult,
)
}
export function useMutationState<TResult = MutationState>(
options: MutationStateOptions<TResult> = {},
queryClient?: QueryClient,
): Array<TResult> {
const mutationCache = useQueryClient(queryClient).getMutationCache()
const optionsRef = React.useRef(options)
const result = React.useRef<Array<TResult>>(null)
if (result.current === null) {
result.current = getResult(mutationCache, options)
}
React.useEffect(() => {
optionsRef.current = options
})
return React.useSyncExternalStore(
React.useCallback(
(onStoreChange) =>
mutationCache.subscribe(() => {
const nextResult = replaceEqualDeep(
result.current,
getResult(mutationCache, optionsRef.current),
)
if (result.current !== nextResult) {
result.current = nextResult
notifyManager.schedule(onStoreChange)
}
}),
[mutationCache],
),
() => result.current,
() => result.current,
)!
}

View File

@@ -0,0 +1,30 @@
import { useQueryClient } from './QueryClientProvider'
import type {
DefaultError,
FetchInfiniteQueryOptions,
QueryClient,
QueryKey,
} from '@tanstack/query-core'
export function usePrefetchInfiniteQuery<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
>(
options: FetchInfiniteQueryOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
queryClient?: QueryClient,
) {
const client = useQueryClient(queryClient)
if (!client.getQueryState(options.queryKey)) {
client.prefetchInfiniteQuery(options)
}
}

View File

@@ -0,0 +1,19 @@
import { useQueryClient } from './QueryClientProvider'
import type { DefaultError, QueryClient, QueryKey } from '@tanstack/query-core'
import type { UsePrefetchQueryOptions } from './types'
export function usePrefetchQuery<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
options: UsePrefetchQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
queryClient?: QueryClient,
) {
const client = useQueryClient(queryClient)
if (!client.getQueryState(options.queryKey)) {
client.prefetchQuery(options)
}
}

View File

@@ -0,0 +1,332 @@
'use client'
import * as React from 'react'
import {
QueriesObserver,
QueryObserver,
noop,
notifyManager,
} from '@tanstack/query-core'
import { useQueryClient } from './QueryClientProvider'
import { useIsRestoring } from './IsRestoringProvider'
import { useQueryErrorResetBoundary } from './QueryErrorResetBoundary'
import {
ensurePreventErrorBoundaryRetry,
getHasError,
useClearResetErrorBoundary,
} from './errorBoundaryUtils'
import {
ensureSuspenseTimers,
fetchOptimistic,
shouldSuspend,
willFetch,
} from './suspense'
import type {
DefinedUseQueryResult,
UseQueryOptions,
UseQueryResult,
} from './types'
import type {
DefaultError,
OmitKeyof,
QueriesObserverOptions,
QueriesPlaceholderDataFunction,
QueryClient,
QueryFunction,
QueryKey,
QueryObserverOptions,
ThrowOnError,
} from '@tanstack/query-core'
// This defines the `UseQueryOptions` that are accepted in `QueriesOptions` & `GetOptions`.
// `placeholderData` function always gets undefined passed
type UseQueryOptionsForUseQueries<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> = OmitKeyof<
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
'placeholderData' | 'subscribed'
> & {
placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction<TQueryFnData>
}
// Avoid TS depth-limit error in case of large array literal
type MAXIMUM_DEPTH = 20
// Widen the type of the symbol to enable type inference even if skipToken is not immutable.
type SkipTokenForUseQueries = symbol
type GetUseQueryOptionsForUseQueries<T> =
// Part 1: responsible for applying explicit type parameter to function arguments, if object { queryFnData: TQueryFnData, error: TError, data: TData }
T extends {
queryFnData: infer TQueryFnData
error?: infer TError
data: infer TData
}
? UseQueryOptionsForUseQueries<TQueryFnData, TError, TData>
: T extends { queryFnData: infer TQueryFnData; error?: infer TError }
? UseQueryOptionsForUseQueries<TQueryFnData, TError>
: T extends { data: infer TData; error?: infer TError }
? UseQueryOptionsForUseQueries<unknown, TError, TData>
: // Part 2: responsible for applying explicit type parameter to function arguments, if tuple [TQueryFnData, TError, TData]
T extends [infer TQueryFnData, infer TError, infer TData]
? UseQueryOptionsForUseQueries<TQueryFnData, TError, TData>
: T extends [infer TQueryFnData, infer TError]
? UseQueryOptionsForUseQueries<TQueryFnData, TError>
: T extends [infer TQueryFnData]
? UseQueryOptionsForUseQueries<TQueryFnData>
: // Part 3: responsible for inferring and enforcing type if no explicit parameter was provided
T extends {
queryFn?:
| QueryFunction<infer TQueryFnData, infer TQueryKey>
| SkipTokenForUseQueries
select?: (data: any) => infer TData
throwOnError?: ThrowOnError<any, infer TError, any, any>
}
? UseQueryOptionsForUseQueries<
TQueryFnData,
unknown extends TError ? DefaultError : TError,
unknown extends TData ? TQueryFnData : TData,
TQueryKey
>
: // Fallback
UseQueryOptionsForUseQueries
// A defined initialData setting should return a DefinedUseQueryResult rather than UseQueryResult
type GetDefinedOrUndefinedQueryResult<T, TData, TError = unknown> = T extends {
initialData?: infer TInitialData
}
? unknown extends TInitialData
? UseQueryResult<TData, TError>
: TInitialData extends TData
? DefinedUseQueryResult<TData, TError>
: TInitialData extends () => infer TInitialDataResult
? unknown extends TInitialDataResult
? UseQueryResult<TData, TError>
: TInitialDataResult extends TData
? DefinedUseQueryResult<TData, TError>
: UseQueryResult<TData, TError>
: UseQueryResult<TData, TError>
: UseQueryResult<TData, TError>
type GetUseQueryResult<T> =
// Part 1: responsible for mapping explicit type parameter to function result, if object
T extends { queryFnData: any; error?: infer TError; data: infer TData }
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
: T extends { queryFnData: infer TQueryFnData; error?: infer TError }
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError>
: T extends { data: infer TData; error?: infer TError }
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
: // Part 2: responsible for mapping explicit type parameter to function result, if tuple
T extends [any, infer TError, infer TData]
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
: T extends [infer TQueryFnData, infer TError]
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError>
: T extends [infer TQueryFnData]
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData>
: // Part 3: responsible for mapping inferred type to results, if no explicit parameter was provided
T extends {
queryFn?:
| QueryFunction<infer TQueryFnData, any>
| SkipTokenForUseQueries
select?: (data: any) => infer TData
throwOnError?: ThrowOnError<any, infer TError, any, any>
}
? GetDefinedOrUndefinedQueryResult<
T,
unknown extends TData ? TQueryFnData : TData,
unknown extends TError ? DefaultError : TError
>
: // Fallback
UseQueryResult
/**
* QueriesOptions reducer recursively unwraps function arguments to infer/enforce type param
*/
export type QueriesOptions<
T extends Array<any>,
TResults extends Array<any> = [],
TDepth extends ReadonlyArray<number> = [],
> = TDepth['length'] extends MAXIMUM_DEPTH
? Array<UseQueryOptionsForUseQueries>
: T extends []
? []
: T extends [infer Head]
? [...TResults, GetUseQueryOptionsForUseQueries<Head>]
: T extends [infer Head, ...infer Tails]
? QueriesOptions<
[...Tails],
[...TResults, GetUseQueryOptionsForUseQueries<Head>],
[...TDepth, 1]
>
: ReadonlyArray<unknown> extends T
? T
: // If T is *some* array but we couldn't assign unknown[] to it, then it must hold some known/homogenous type!
// use this to infer the param types in the case of Array.map() argument
T extends Array<
UseQueryOptionsForUseQueries<
infer TQueryFnData,
infer TError,
infer TData,
infer TQueryKey
>
>
? Array<
UseQueryOptionsForUseQueries<
TQueryFnData,
TError,
TData,
TQueryKey
>
>
: // Fallback
Array<UseQueryOptionsForUseQueries>
/**
* QueriesResults reducer recursively maps type param to results
*/
export type QueriesResults<
T extends Array<any>,
TResults extends Array<any> = [],
TDepth extends ReadonlyArray<number> = [],
> = TDepth['length'] extends MAXIMUM_DEPTH
? Array<UseQueryResult>
: T extends []
? []
: T extends [infer Head]
? [...TResults, GetUseQueryResult<Head>]
: T extends [infer Head, ...infer Tails]
? QueriesResults<
[...Tails],
[...TResults, GetUseQueryResult<Head>],
[...TDepth, 1]
>
: { [K in keyof T]: GetUseQueryResult<T[K]> }
export function useQueries<
T extends Array<any>,
TCombinedResult = QueriesResults<T>,
>(
{
queries,
...options
}: {
queries:
| readonly [...QueriesOptions<T>]
| readonly [...{ [K in keyof T]: GetUseQueryOptionsForUseQueries<T[K]> }]
combine?: (result: QueriesResults<T>) => TCombinedResult
subscribed?: boolean
},
queryClient?: QueryClient,
): TCombinedResult {
const client = useQueryClient(queryClient)
const isRestoring = useIsRestoring()
const errorResetBoundary = useQueryErrorResetBoundary()
const defaultedQueries = React.useMemo(
() =>
queries.map((opts) => {
const defaultedOptions = client.defaultQueryOptions(
opts as QueryObserverOptions,
)
// Make sure the results are already in fetching state before subscribing or updating options
defaultedOptions._optimisticResults = isRestoring
? 'isRestoring'
: 'optimistic'
return defaultedOptions
}),
[queries, client, isRestoring],
)
defaultedQueries.forEach((query) => {
ensureSuspenseTimers(query)
ensurePreventErrorBoundaryRetry(query, errorResetBoundary)
})
useClearResetErrorBoundary(errorResetBoundary)
const [observer] = React.useState(
() =>
new QueriesObserver<TCombinedResult>(
client,
defaultedQueries,
options as QueriesObserverOptions<TCombinedResult>,
),
)
// note: this must be called before useSyncExternalStore
const [optimisticResult, getCombinedResult, trackResult] =
observer.getOptimisticResult(
defaultedQueries,
(options as QueriesObserverOptions<TCombinedResult>).combine,
)
const shouldSubscribe = !isRestoring && options.subscribed !== false
React.useSyncExternalStore(
React.useCallback(
(onStoreChange) =>
shouldSubscribe
? observer.subscribe(notifyManager.batchCalls(onStoreChange))
: noop,
[observer, shouldSubscribe],
),
() => observer.getCurrentResult(),
() => observer.getCurrentResult(),
)
React.useEffect(() => {
observer.setQueries(
defaultedQueries,
options as QueriesObserverOptions<TCombinedResult>,
)
}, [defaultedQueries, options, observer])
const shouldAtLeastOneSuspend = optimisticResult.some((result, index) =>
shouldSuspend(defaultedQueries[index], result),
)
const suspensePromises = shouldAtLeastOneSuspend
? optimisticResult.flatMap((result, index) => {
const opts = defaultedQueries[index]
if (opts) {
const queryObserver = new QueryObserver(client, opts)
if (shouldSuspend(opts, result)) {
return fetchOptimistic(opts, queryObserver, errorResetBoundary)
} else if (willFetch(result, isRestoring)) {
void fetchOptimistic(opts, queryObserver, errorResetBoundary)
}
}
return []
})
: []
if (suspensePromises.length > 0) {
throw Promise.all(suspensePromises)
}
const firstSingleResultWhichShouldThrow = optimisticResult.find(
(result, index) => {
const query = defaultedQueries[index]
return (
query &&
getHasError({
result,
errorResetBoundary,
throwOnError: query.throwOnError,
query: client.getQueryCache().get(query.queryHash),
suspense: query.suspense,
})
)
},
)
if (firstSingleResultWhichShouldThrow?.error) {
throw firstSingleResultWhichShouldThrow.error
}
return getCombinedResult(trackResult())
}

View File

@@ -0,0 +1,52 @@
'use client'
import { QueryObserver } from '@tanstack/query-core'
import { useBaseQuery } from './useBaseQuery'
import type {
DefaultError,
NoInfer,
QueryClient,
QueryKey,
} from '@tanstack/query-core'
import type {
DefinedUseQueryResult,
UseQueryOptions,
UseQueryResult,
} from './types'
import type {
DefinedInitialDataOptions,
UndefinedInitialDataOptions,
} from './queryOptions'
export function useQuery<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
queryClient?: QueryClient,
): DefinedUseQueryResult<NoInfer<TData>, TError>
export function useQuery<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
options: UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
queryClient?: QueryClient,
): UseQueryResult<NoInfer<TData>, TError>
export function useQuery<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
queryClient?: QueryClient,
): UseQueryResult<NoInfer<TData>, TError>
export function useQuery(options: UseQueryOptions, queryClient?: QueryClient) {
return useBaseQuery(options, QueryObserver, queryClient)
}

View File

@@ -0,0 +1,50 @@
'use client'
import { InfiniteQueryObserver, skipToken } from '@tanstack/query-core'
import { useBaseQuery } from './useBaseQuery'
import { defaultThrowOnError } from './suspense'
import type {
DefaultError,
InfiniteData,
InfiniteQueryObserverSuccessResult,
QueryClient,
QueryKey,
QueryObserver,
} from '@tanstack/query-core'
import type {
UseSuspenseInfiniteQueryOptions,
UseSuspenseInfiniteQueryResult,
} from './types'
export function useSuspenseInfiniteQuery<
TQueryFnData,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
>(
options: UseSuspenseInfiniteQueryOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
queryClient?: QueryClient,
): UseSuspenseInfiniteQueryResult<TData, TError> {
if (process.env.NODE_ENV !== 'production') {
if ((options.queryFn as any) === skipToken) {
console.error('skipToken is not allowed for useSuspenseInfiniteQuery')
}
}
return useBaseQuery(
{
...options,
enabled: true,
suspense: true,
throwOnError: defaultThrowOnError,
},
InfiniteQueryObserver as typeof QueryObserver,
queryClient,
) as InfiniteQueryObserverSuccessResult<TData, TError>
}

View File

@@ -0,0 +1,211 @@
'use client'
import { skipToken } from '@tanstack/query-core'
import { useQueries } from './useQueries'
import { defaultThrowOnError } from './suspense'
import type { UseSuspenseQueryOptions, UseSuspenseQueryResult } from './types'
import type {
DefaultError,
QueryClient,
QueryFunction,
ThrowOnError,
} from '@tanstack/query-core'
// Avoid TS depth-limit error in case of large array literal
type MAXIMUM_DEPTH = 20
// Widen the type of the symbol to enable type inference even if skipToken is not immutable.
type SkipTokenForUseQueries = symbol
type GetUseSuspenseQueryOptions<T> =
// Part 1: responsible for applying explicit type parameter to function arguments, if object { queryFnData: TQueryFnData, error: TError, data: TData }
T extends {
queryFnData: infer TQueryFnData
error?: infer TError
data: infer TData
}
? UseSuspenseQueryOptions<TQueryFnData, TError, TData>
: T extends { queryFnData: infer TQueryFnData; error?: infer TError }
? UseSuspenseQueryOptions<TQueryFnData, TError>
: T extends { data: infer TData; error?: infer TError }
? UseSuspenseQueryOptions<unknown, TError, TData>
: // Part 2: responsible for applying explicit type parameter to function arguments, if tuple [TQueryFnData, TError, TData]
T extends [infer TQueryFnData, infer TError, infer TData]
? UseSuspenseQueryOptions<TQueryFnData, TError, TData>
: T extends [infer TQueryFnData, infer TError]
? UseSuspenseQueryOptions<TQueryFnData, TError>
: T extends [infer TQueryFnData]
? UseSuspenseQueryOptions<TQueryFnData>
: // Part 3: responsible for inferring and enforcing type if no explicit parameter was provided
T extends {
queryFn?:
| QueryFunction<infer TQueryFnData, infer TQueryKey>
| SkipTokenForUseQueries
select?: (data: any) => infer TData
throwOnError?: ThrowOnError<any, infer TError, any, any>
}
? UseSuspenseQueryOptions<
TQueryFnData,
TError,
TData,
TQueryKey
>
: T extends {
queryFn?:
| QueryFunction<infer TQueryFnData, infer TQueryKey>
| SkipTokenForUseQueries
throwOnError?: ThrowOnError<any, infer TError, any, any>
}
? UseSuspenseQueryOptions<
TQueryFnData,
TError,
TQueryFnData,
TQueryKey
>
: // Fallback
UseSuspenseQueryOptions
type GetUseSuspenseQueryResult<T> =
// Part 1: responsible for mapping explicit type parameter to function result, if object
T extends { queryFnData: any; error?: infer TError; data: infer TData }
? UseSuspenseQueryResult<TData, TError>
: T extends { queryFnData: infer TQueryFnData; error?: infer TError }
? UseSuspenseQueryResult<TQueryFnData, TError>
: T extends { data: infer TData; error?: infer TError }
? UseSuspenseQueryResult<TData, TError>
: // Part 2: responsible for mapping explicit type parameter to function result, if tuple
T extends [any, infer TError, infer TData]
? UseSuspenseQueryResult<TData, TError>
: T extends [infer TQueryFnData, infer TError]
? UseSuspenseQueryResult<TQueryFnData, TError>
: T extends [infer TQueryFnData]
? UseSuspenseQueryResult<TQueryFnData>
: // Part 3: responsible for mapping inferred type to results, if no explicit parameter was provided
T extends {
queryFn?:
| QueryFunction<infer TQueryFnData, any>
| SkipTokenForUseQueries
select?: (data: any) => infer TData
throwOnError?: ThrowOnError<any, infer TError, any, any>
}
? UseSuspenseQueryResult<
unknown extends TData ? TQueryFnData : TData,
unknown extends TError ? DefaultError : TError
>
: T extends {
queryFn?:
| QueryFunction<infer TQueryFnData, any>
| SkipTokenForUseQueries
throwOnError?: ThrowOnError<any, infer TError, any, any>
}
? UseSuspenseQueryResult<
TQueryFnData,
unknown extends TError ? DefaultError : TError
>
: // Fallback
UseSuspenseQueryResult
/**
* SuspenseQueriesOptions reducer recursively unwraps function arguments to infer/enforce type param
*/
export type SuspenseQueriesOptions<
T extends Array<any>,
TResults extends Array<any> = [],
TDepth extends ReadonlyArray<number> = [],
> = TDepth['length'] extends MAXIMUM_DEPTH
? Array<UseSuspenseQueryOptions>
: T extends []
? []
: T extends [infer Head]
? [...TResults, GetUseSuspenseQueryOptions<Head>]
: T extends [infer Head, ...infer Tails]
? SuspenseQueriesOptions<
[...Tails],
[...TResults, GetUseSuspenseQueryOptions<Head>],
[...TDepth, 1]
>
: Array<unknown> extends T
? T
: // If T is *some* array but we couldn't assign unknown[] to it, then it must hold some known/homogenous type!
// use this to infer the param types in the case of Array.map() argument
T extends Array<
UseSuspenseQueryOptions<
infer TQueryFnData,
infer TError,
infer TData,
infer TQueryKey
>
>
? Array<
UseSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey>
>
: // Fallback
Array<UseSuspenseQueryOptions>
/**
* SuspenseQueriesResults reducer recursively maps type param to results
*/
export type SuspenseQueriesResults<
T extends Array<any>,
TResults extends Array<any> = [],
TDepth extends ReadonlyArray<number> = [],
> = TDepth['length'] extends MAXIMUM_DEPTH
? Array<UseSuspenseQueryResult>
: T extends []
? []
: T extends [infer Head]
? [...TResults, GetUseSuspenseQueryResult<Head>]
: T extends [infer Head, ...infer Tails]
? SuspenseQueriesResults<
[...Tails],
[...TResults, GetUseSuspenseQueryResult<Head>],
[...TDepth, 1]
>
: { [K in keyof T]: GetUseSuspenseQueryResult<T[K]> }
export function useSuspenseQueries<
T extends Array<any>,
TCombinedResult = SuspenseQueriesResults<T>,
>(
options: {
queries:
| readonly [...SuspenseQueriesOptions<T>]
| readonly [...{ [K in keyof T]: GetUseSuspenseQueryOptions<T[K]> }]
combine?: (result: SuspenseQueriesResults<T>) => TCombinedResult
},
queryClient?: QueryClient,
): TCombinedResult
export function useSuspenseQueries<
T extends Array<any>,
TCombinedResult = SuspenseQueriesResults<T>,
>(
options: {
queries: readonly [...SuspenseQueriesOptions<T>]
combine?: (result: SuspenseQueriesResults<T>) => TCombinedResult
},
queryClient?: QueryClient,
): TCombinedResult
export function useSuspenseQueries(options: any, queryClient?: QueryClient) {
return useQueries(
{
...options,
queries: options.queries.map((query: any) => {
if (process.env.NODE_ENV !== 'production') {
if (query.queryFn === skipToken) {
console.error('skipToken is not allowed for useSuspenseQueries')
}
}
return {
...query,
suspense: true,
throwOnError: defaultThrowOnError,
enabled: true,
placeholderData: undefined,
}
}),
},
queryClient,
)
}

View File

@@ -0,0 +1,34 @@
'use client'
import { QueryObserver, skipToken } from '@tanstack/query-core'
import { useBaseQuery } from './useBaseQuery'
import { defaultThrowOnError } from './suspense'
import type { UseSuspenseQueryOptions, UseSuspenseQueryResult } from './types'
import type { DefaultError, QueryClient, QueryKey } from '@tanstack/query-core'
export function useSuspenseQuery<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
options: UseSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
queryClient?: QueryClient,
): UseSuspenseQueryResult<TData, TError> {
if (process.env.NODE_ENV !== 'production') {
if ((options.queryFn as any) === skipToken) {
console.error('skipToken is not allowed for useSuspenseQuery')
}
}
return useBaseQuery(
{
...options,
enabled: true,
suspense: true,
throwOnError: defaultThrowOnError,
placeholderData: undefined,
},
QueryObserver,
queryClient,
) as UseSuspenseQueryResult<TData, TError>
}