react-query-kit
Version:
🕊️ A toolkit for ReactQuery that make ReactQuery hooks more reusable and typesafe
848 lines (775 loc) • 22.3 kB
text/typescript
import type {
DataTag,
DefaultError,
DefinedUseInfiniteQueryResult,
DefinedUseQueryResult,
InfiniteData,
InfiniteQueryObserverSuccessResult,
MutationFunctionContext,
MutationKey,
QueryClient,
QueryFunction,
QueryFunctionContext,
QueryKey,
QueryKeyHashFunction,
QueryObserverSuccessResult,
SkipToken,
UseInfiniteQueryOptions,
UseInfiniteQueryResult,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from '/react-query'
// utils
type CompatibleWithV4<V5, V4> =
InfiniteData<unknown> extends UseInfiniteQueryResult<
InfiniteData<unknown>
>['data']
? V5
: V4
type CompatibleWithTwoV5<V6Generics, V5Generics> = UseInfiniteQueryOptions<
unknown,
DefaultError,
unknown,
QueryKey,
unknown
> extends never
? V5Generics
: V6Generics
type CompatibleUseInfiniteQueryOptions<TFnData, TData, TError, TPageParam> =
CompatibleWithV4<
CompatibleWithTwoV5<
UseInfiniteQueryOptions<TFnData, TError, TData, QueryKey, TPageParam>,
// @ts-ignore
UseInfiniteQueryOptions<
TFnData,
TError,
TData,
TFnData,
QueryKey,
TPageParam
>
>,
// @ts-ignore
UseInfiniteQueryOptions<TFnData, TError, TData, TFnData, QueryKey>
>
type CompatibleInfiniteData<TFnData, TPageParam> = CompatibleWithV4<
InfiniteData<TFnData, TPageParam>,
InfiniteData<TFnData>
>
type NonUndefinedGuard<T> = T extends undefined ? never : T
type WithRequired<T, K extends keyof T> = T & {
[_ in K]: {}
}
type DeepPartial<T> = T extends object
? {
[P in keyof T]?: DeepPartial<T[P]>
}
: T
type DefaultTo<T, D> = unknown extends T ? D : T
/**
* Register global options through declaration merging, e.g. set
* `strictVariables: true` to require `variables` at the call site.
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Register {}
// Variables can only be left out when the fetcher accepts `undefined`,
// which is what it receives then
type RequiresVariables<TVariables> = Register extends { strictVariables: true }
? undefined extends TVariables
? false
: true
: false
type WithRequiredVariables<TOptions, TVariables> =
RequiresVariables<TVariables> extends true
? TOptions & {
variables: CompatibleWithV4<TVariables | SkipToken, TVariables>
}
: TOptions
type QueryHookArgs<TOptions, TVariables> =
RequiresVariables<TVariables> extends true
? [
options: WithRequiredVariables<TOptions, TVariables>,
queryClient?: CompatibleWithV4<QueryClient, void>
]
: [options?: TOptions, queryClient?: CompatibleWithV4<QueryClient, void>]
export type CompatibleError = CompatibleWithV4<DefaultError, Error>
export type Fetcher<TFnData, TVariables = void, TPageParam = never> = (
variables: TVariables,
context: QueryFunctionContext<QueryKey, TPageParam>
) => TFnData | Promise<TFnData>
export type AdditionalQueryOptions<TFnData, TVariables, TPageParam = never> = {
fetcher: Fetcher<TFnData, TVariables, TPageParam>
variables?: TVariables
}
type inferMiddlewareHook<T extends (...args: any) => any> = (
options: inferCreateOptions<T>,
queryClient?: CompatibleWithV4<QueryClient, void>
) => ReturnType<T>
export type Middleware<
T extends (...args: any) => any = QueryHook<any, any, any>
> = (hook: inferMiddlewareHook<T>) => inferMiddlewareHook<T>
export type ExposeFetcher<TFnData, TVariables = void, TPageParam = never> = (
variables: TVariables,
context?: Partial<QueryFunctionContext<QueryKey, TPageParam>>
) => TFnData | Promise<TFnData>
export type ExposeMethods<TFnData, TVariables, TError, TPageParam = never> = {
fetcher: ExposeFetcher<TFnData, TVariables, TPageParam>
getKey: (
variables?: DeepPartial<TVariables>
) => CompatibleWithV4<
DataTag<
QueryKey,
[TPageParam] extends [never] ? TFnData : InfiniteData<TFnData, TPageParam>
>,
QueryKey
>
getFetchOptions: (
variables: TVariables extends void
? CompatibleWithV4<TVariables | SkipToken, TVariables> | void
: CompatibleWithV4<TVariables | SkipToken, TVariables>
) => Pick<
ReturnType<
ExposeMethods<TFnData, TVariables, TError, TPageParam>['getOptions']
>,
// @ts-ignore
[TPageParam] extends [never]
? 'queryKey' | 'queryFn' | 'queryKeyHashFn' | 'gcTime' | 'staleTime'
:
| 'queryKey'
| 'queryFn'
| 'queryKeyHashFn'
| 'gcTime'
| 'staleTime'
| 'getNextPageParam'
| 'getPreviousPageParam'
| 'initialPageParam'
>
getOptions: (
variables: TVariables extends void
? CompatibleWithV4<TVariables | SkipToken, TVariables> | void
: CompatibleWithV4<TVariables | SkipToken, TVariables>
) => [TPageParam] extends [never]
? CompatibleWithV4<
UseQueryOptions<TFnData, TError, TFnData, QueryKey> & {
queryKey: DataTag<QueryKey, TFnData>
queryFn?: Exclude<
UseQueryOptions<TFnData, TError, TFnData, QueryKey>['queryFn'],
SkipToken
>
},
// Not work to infer TError in v4
{
queryKey: QueryKey
queryFn: QueryFunction<TFnData, QueryKey>
queryKeyHashFn?: QueryKeyHashFunction<QueryKey>
}
>
: CompatibleUseInfiniteQueryOptions<
TFnData,
TFnData,
TError,
TPageParam
> & {
queryKey: CompatibleWithV4<
DataTag<QueryKey, InfiniteData<TFnData, TPageParam>>,
QueryKey
>
queryFn?: Exclude<
CompatibleUseInfiniteQueryOptions<
TFnData,
TFnData,
TError,
TPageParam
>['queryFn'],
SkipToken
>
}
}
type Clone<T> = T extends infer TClone ? TClone : never
// query hook
export interface CreateQueryOptions<
TFnData = unknown,
TVariables = void,
TError = CompatibleError
> extends Omit<
UseQueryOptions<TFnData, TError, TFnData, QueryKey>,
'queryKey' | 'queryFn' | 'select'
>,
AdditionalQueryOptions<TFnData, TVariables> {
queryKey: QueryKey
use?: Middleware<
QueryHook<Clone<TFnData>, Clone<TVariables>, Clone<TError>>
>[]
variables?: TVariables
}
export interface QueryHookOptions<TFnData, TError, TData, TVariables>
extends Omit<
UseQueryOptions<TFnData, TError, TData, QueryKey>,
'queryKey' | 'queryFn' | 'queryKeyHashFn'
> {
use?: Middleware<QueryHook<TFnData, TVariables, TError>>[]
variables?: CompatibleWithV4<TVariables | SkipToken, TVariables>
}
export interface DefinedQueryHookOptions<TFnData, TError, TData, TVariables>
extends Omit<
QueryHookOptions<TFnData, TError, TData, TVariables>,
'initialData'
> {
initialData: NonUndefinedGuard<TFnData> | (() => NonUndefinedGuard<TFnData>)
}
export type QueryHookResult<TData, TError> = UseQueryResult<TData, TError>
export type DefinedQueryHookResult<TData, TError> = DefinedUseQueryResult<
TData,
TError
>
export interface QueryHook<
TFnData = unknown,
TVariables = void,
TError = CompatibleError
> extends ExposeMethods<TFnData, TVariables, TError> {
<TData = TFnData>(
options: WithRequiredVariables<
DefinedQueryHookOptions<TFnData, TError, TData, TVariables>,
TVariables
>,
queryClient?: CompatibleWithV4<QueryClient, void>
): DefinedQueryHookResult<TData, TError>
<TData = TFnData>(
...args: QueryHookArgs<
QueryHookOptions<TFnData, TError, TData, TVariables>,
TVariables
>
): QueryHookResult<TData, TError>
}
// suspense query hook
export interface CreateSuspenseQueryOptions<
TFnData = unknown,
TVariables = void,
TError = CompatibleError
> extends Omit<
UseQueryOptions<TFnData, TError, TFnData, QueryKey>,
| 'queryKey'
| 'queryFn'
| 'enabled'
| 'select'
| 'suspense'
| 'throwOnError'
| 'placeholderData'
| 'keepPreviousData'
| 'useErrorBoundary'
>,
AdditionalQueryOptions<TFnData, TVariables> {
queryKey: QueryKey
use?: Middleware<
SuspenseQueryHook<Clone<TFnData>, Clone<TVariables>, Clone<TError>>
>[]
variables?: TVariables
}
export interface SuspenseQueryHookOptions<TFnData, TError, TData, TVariables>
extends Omit<
UseQueryOptions<TFnData, TError, TData, QueryKey>,
| 'queryKey'
| 'queryFn'
| 'queryKeyHashFn'
| 'enabled'
| 'suspense'
| 'throwOnError'
| 'placeholderData'
| 'keepPreviousData'
| 'useErrorBoundary'
> {
use?: Middleware<SuspenseQueryHook<TFnData, TVariables, TVariables>>[]
variables?: CompatibleWithV4<TVariables | SkipToken, TVariables>
}
export type SuspenseQueryHookResult<TData, TError> = Omit<
QueryObserverSuccessResult<TData, TError>,
'isPlaceholderData' | 'isPreviousData'
>
export interface SuspenseQueryHook<
TFnData = unknown,
TVariables = void,
TError = CompatibleError
> extends ExposeMethods<TFnData, TVariables, TError> {
<TData = TFnData>(
...args: QueryHookArgs<
SuspenseQueryHookOptions<TFnData, TError, TData, TVariables>,
TVariables
>
): SuspenseQueryHookResult<TData, TError>
}
// infinite query hook
export interface CreateInfiniteQueryOptions<
TFnData = unknown,
TVariables = void,
TError = CompatibleError,
TPageParam = number
> extends Omit<
CompatibleUseInfiniteQueryOptions<TFnData, TFnData, TError, TPageParam>,
'queryKey' | 'queryFn' | 'select'
>,
AdditionalQueryOptions<TFnData, TVariables, TPageParam> {
queryKey: QueryKey
use?: Middleware<
InfiniteQueryHook<
Clone<TFnData>,
Clone<TVariables>,
Clone<TError>,
Clone<TPageParam>
>
>[]
variables?: TVariables
}
export interface InfiniteQueryHookOptions<
TFnData,
TError,
TData,
TVariables,
TPageParam = number
> extends Omit<
CompatibleUseInfiniteQueryOptions<TFnData, TData, TError, TPageParam>,
| 'queryKey'
| 'queryFn'
| 'queryKeyHashFn'
| 'initialPageParam'
| 'getPreviousPageParam'
| 'getNextPageParam'
> {
use?: Middleware<InfiniteQueryHook<TFnData, TVariables, TError, TPageParam>>[]
variables?: CompatibleWithV4<TVariables | SkipToken, TVariables>
}
export interface DefinedInfiniteQueryHookOptions<
TFnData,
TError,
TData,
TVariables,
TPageParam = number
> extends Omit<
InfiniteQueryHookOptions<TFnData, TError, TData, TVariables, TPageParam>,
'initialData'
> {
initialData:
| NonUndefinedGuard<CompatibleInfiniteData<TFnData, TPageParam>>
| (() => NonUndefinedGuard<CompatibleInfiniteData<TFnData, TPageParam>>)
}
export type InfiniteQueryHookResult<TData, TError> = UseInfiniteQueryResult<
TData,
TError
>
export type DefinedInfiniteQueryHookResult<TData, TError> = CompatibleWithV4<
DefinedUseInfiniteQueryResult<TData, TError>,
WithRequired<UseInfiniteQueryResult<TData, TError>, 'data'>
>
export interface InfiniteQueryHook<
TFnData = unknown,
TVariables = void,
TError = CompatibleError,
TPageParam = number
> extends ExposeMethods<TFnData, TVariables, TError, TPageParam> {
<TData = CompatibleWithV4<InfiniteData<TFnData, TPageParam>, TFnData>>(
options: WithRequiredVariables<
DefinedInfiniteQueryHookOptions<
TFnData,
TError,
TData,
TVariables,
TPageParam
>,
TVariables
>,
queryClient?: CompatibleWithV4<QueryClient, void>
): DefinedInfiniteQueryHookResult<TData, TError>
<TData = CompatibleWithV4<InfiniteData<TFnData, TPageParam>, TFnData>>(
...args: QueryHookArgs<
InfiniteQueryHookOptions<TFnData, TError, TData, TVariables, TPageParam>,
TVariables
>
): InfiniteQueryHookResult<TData, TError>
}
// infinite sususpense query hook
export interface CreateSuspenseInfiniteQueryOptions<
TFnData = unknown,
TVariables = void,
TError = CompatibleError,
TPageParam = number
> extends Omit<
CompatibleUseInfiniteQueryOptions<TFnData, TFnData, TError, TPageParam>,
| 'queryKey'
| 'queryFn'
| 'enabled'
| 'select'
| 'suspense'
| 'throwOnError'
| 'placeholderData'
| 'keepPreviousData'
| 'useErrorBoundary'
>,
AdditionalQueryOptions<TFnData, TVariables, TPageParam> {
queryKey: QueryKey
use?: Middleware<
SuspenseInfiniteQueryHook<
Clone<TFnData>,
Clone<TVariables>,
Clone<TError>,
Clone<TPageParam>
>
>[]
variables?: TVariables
}
export interface SuspenseInfiniteQueryHookOptions<
TFnData,
TError,
TData,
TVariables,
TPageParam = number
> extends Omit<
CompatibleUseInfiniteQueryOptions<TFnData, TData, TError, TPageParam>,
| 'queryKey'
| 'queryFn'
| 'queryKeyHashFn'
| 'enabled'
| 'initialPageParam'
| 'getPreviousPageParam'
| 'getNextPageParam'
| 'suspense'
| 'throwOnError'
| 'placeholderData'
| 'keepPreviousData'
| 'useErrorBoundary'
> {
use?: Middleware<SuspenseInfiniteQueryHook<TFnData, TVariables, TVariables>>[]
variables?: CompatibleWithV4<TVariables | SkipToken, TVariables>
}
export type SuspenseInfiniteQueryHookResult<TData, TError> = Omit<
InfiniteQueryObserverSuccessResult<TData, TError>,
'isPlaceholderData' | 'isPreviousData'
>
export interface SuspenseInfiniteQueryHook<
TFnData = unknown,
TVariables = void,
TError = CompatibleError,
TPageParam = number
> extends ExposeMethods<TFnData, TVariables, TError, TPageParam> {
<TData = CompatibleWithV4<InfiniteData<TFnData, TPageParam>, TFnData>>(
...args: QueryHookArgs<
SuspenseInfiniteQueryHookOptions<
TFnData,
TError,
TData,
TVariables,
TPageParam
>,
TVariables
>
): SuspenseInfiniteQueryHookResult<TData, TError>
}
// mutation hook
export interface CreateMutationOptions<
TData = unknown,
TVariables = void,
TError = CompatibleError,
TContext = unknown
> extends UseMutationOptions<TData, TError, TVariables, TContext> {
use?: Middleware<MutationHook<TData, TVariables, TError>>[]
}
export interface MutationHookOptions<TData, TError, TVariables, TContext>
extends Omit<
UseMutationOptions<TData, TError, TVariables, TContext>,
'mutationFn' | 'mutationKey'
> {
use?: Middleware<MutationHook<TData, TVariables, TError>>[]
}
export type MutationHookResult<
TData = unknown,
TError = CompatibleError,
TVariables = void,
TContext = unknown
> = UseMutationResult<TData, TError, TVariables, TContext>
export type ExposeMutationFn<TData = unknown, TVariables = void> = [
TVariables
] extends [void]
? (
variables?: TVariables,
context?: Partial<MutationFunctionContext>
) => Promise<TData>
: (
variables: TVariables,
context?: Partial<MutationFunctionContext>
) => Promise<TData>
export interface ExposeMutationMethods<
TData = unknown,
TVariables = void,
TError = CompatibleError,
TDefaultContext = unknown
> {
getKey: () => MutationKey | undefined
getOptions: () => UseMutationOptions<
TData,
TError,
TVariables,
TDefaultContext
>
mutationFn: ExposeMutationFn<TData, TVariables>
}
export interface MutationHook<
TData = unknown,
TVariables = void,
TError = CompatibleError,
TDefaultContext = unknown
> extends ExposeMutationMethods<TData, TVariables, TError, TDefaultContext> {
<TContext = TDefaultContext>(
options?: MutationHookOptions<TData, TError, TVariables, TContext>,
queryClient?: CompatibleWithV4<QueryClient, void>
): MutationHookResult<TData, TError, TVariables, TContext>
}
// infer types
export type inferVariables<T> = T extends {
fetcher: ExposeFetcher<any, infer TVariables, infer _TPageParam>
}
? TVariables
: T extends ExposeMutationMethods<any, infer TVariables, any, any>
? TVariables
: never
export type inferData<T> = T extends {
fetcher: ExposeFetcher<infer TFnData, any, infer TPageParam>
}
? [TPageParam] extends [never]
? TFnData
: CompatibleInfiniteData<TFnData, TPageParam>
: T extends ExposeMutationMethods<infer TFnData, any, any, any>
? TFnData
: never
export type inferFnData<T> = T extends {
fetcher: ExposeFetcher<infer TFnData, any, infer _TPageParam>
}
? TFnData
: T extends ExposeMutationMethods<infer TFnData, any, any, any>
? TFnData
: never
export type inferError<T> = T extends ExposeMethods<any, any, infer TError>
? TError
: T extends ExposeMethods<any, any, infer TError, any>
? TError
: T extends ExposeMutationMethods<any, any, infer TError, any>
? TError
: never
export type inferOptions<T> = T extends QueryHook<
infer TFnData,
infer TVariables,
infer TError
>
? WithRequiredVariables<
QueryHookOptions<TFnData, TError, TFnData, TVariables>,
TVariables
>
: T extends SuspenseQueryHook<infer TFnData, infer TVariables, infer TError>
? WithRequiredVariables<
SuspenseQueryHookOptions<TFnData, TError, TFnData, TVariables>,
TVariables
>
: T extends InfiniteQueryHook<
infer TFnData,
infer TVariables,
infer TError,
infer TPageParam
>
? WithRequiredVariables<
InfiniteQueryHookOptions<
TFnData,
TError,
CompatibleWithV4<InfiniteData<TFnData, TPageParam>, TFnData>,
TVariables,
TPageParam
>,
TVariables
>
: T extends SuspenseInfiniteQueryHook<
infer TFnData,
infer TVariables,
infer TError,
infer TPageParam
>
? WithRequiredVariables<
SuspenseInfiniteQueryHookOptions<
TFnData,
TError,
CompatibleWithV4<InfiniteData<TFnData, TPageParam>, TFnData>,
TVariables,
TPageParam
>,
TVariables
>
: T extends MutationHook<infer TFnData, infer TVariables, infer TError>
? MutationHookOptions<TFnData, TError, TVariables, unknown>
: never
export type inferCreateOptions<T> = T extends QueryHook<
infer TFnData,
infer TVariables,
infer TError
>
? CreateQueryOptions<TFnData, TVariables, TError>
: T extends SuspenseQueryHook<infer TFnData, infer TVariables, infer TError>
? CreateSuspenseQueryOptions<TFnData, TVariables, TError>
: T extends InfiniteQueryHook<
infer TFnData,
infer TVariables,
infer TError,
infer TPageParam
>
? CreateInfiniteQueryOptions<TFnData, TVariables, TError, TPageParam>
: T extends SuspenseInfiniteQueryHook<
infer TFnData,
infer TVariables,
infer TError,
infer TPageParam
>
? CreateSuspenseInfiniteQueryOptions<TFnData, TVariables, TError, TPageParam>
: T extends MutationHook<
infer TFnData,
infer TVariables,
infer TError,
infer TContext
>
? CreateMutationOptions<TFnData, TVariables, TError, TContext>
: never
// router
export type RouterQueryOptions<
TFnData,
TVariables = void,
TError = CompatibleError
> = Omit<CreateQueryOptions<TFnData, TVariables, TError>, 'queryKey'>
export type RouterQuery<
TFnData,
TVariables = void,
TError = CompatibleError
> = RouterQueryOptions<TFnData, TVariables, TError> & {
_routerType: `q`
}
export type ResolvedRouterQuery<
TFnData,
TVariables = void,
TError = CompatibleError
> = {
useQuery: QueryHook<TFnData, TVariables, TError>
useSuspenseQuery: SuspenseQueryHook<TFnData, TVariables, TError>
} & ExposeMethods<TFnData, TVariables, TError>
export type RouterInfiniteQueryOptions<
TFnData,
TVariables = void,
TError = CompatibleError,
TPageParam = number
> = Omit<
CreateInfiniteQueryOptions<TFnData, TVariables, TError, TPageParam>,
'queryKey'
>
export type RouterInfiniteQuery<
TFnData,
TVariables = void,
TError = CompatibleError,
TPageParam = number
> = RouterInfiniteQueryOptions<
TFnData,
TVariables,
TError,
Clone<TPageParam>
> & {
_routerType: `inf`
}
export type ResolvedRouterInfiniteQuery<
TFnData,
TVariables = void,
TError = CompatibleError,
TPageParam = number
> = {
useInfiniteQuery: InfiniteQueryHook<TFnData, TVariables, TError, TPageParam>
useSuspenseInfiniteQuery: SuspenseInfiniteQueryHook<
TFnData,
TVariables,
TError,
TPageParam
>
} & ExposeMethods<TFnData, TVariables, TError, TPageParam>
export type RouterMutationOptions<
TData = unknown,
TVariables = void,
TError = CompatibleError,
TContext = unknown
> = Omit<
CreateMutationOptions<TData, TVariables, TError, TContext>,
'mutationKey'
>
export type RouterMutation<
TData = unknown,
TVariables = void,
TError = CompatibleError,
TContext = unknown
> = RouterMutationOptions<TData, TVariables, TError, TContext> & {
_routerType: `m`
}
export type ResolvedRouterMutation<
TData = unknown,
TVariables = void,
TError = CompatibleError,
TContext = unknown
> = {
useMutation: MutationHook<
TData,
DefaultTo<TVariables, void>,
DefaultTo<TError, CompatibleError>,
TContext
>
} & ExposeMutationMethods<
TData,
DefaultTo<TVariables, void>,
DefaultTo<TError, CompatibleError>,
TContext
>
export type RouterLeaf =
| RouterQuery<any, any, any>
| RouterInfiniteQuery<any, any, any, any>
| RouterMutation<any, any, any, any>
export type RouterConfig = {
_routerType?: never
} & {
[k: string]: RouterLeaf | RouterConfig
}
export type CreateRouter<TConfig extends RouterConfig> = {
[K in keyof TConfig]: TConfig[K] extends RouterMutation<
infer TFnData,
infer TVariables,
infer TError,
infer TContext
>
? ResolvedRouterMutation<
TFnData,
DefaultTo<TVariables, void>,
DefaultTo<TError, CompatibleError>,
TContext
>
: TConfig[K] extends RouterInfiniteQuery<
infer TFnData,
infer TVariables,
infer TError,
infer TPageParam
>
? ResolvedRouterInfiniteQuery<
TFnData,
DefaultTo<TVariables, void>,
DefaultTo<TError, CompatibleError>,
DefaultTo<TPageParam, number>
>
: TConfig[K] extends RouterQuery<
infer TFnData,
infer TVariables,
infer TError
>
? ResolvedRouterQuery<
TFnData,
DefaultTo<TVariables, void>,
DefaultTo<TError, CompatibleError>
>
: TConfig[K] extends RouterConfig
? CreateRouter<TConfig[K]>
: never
} & { getKey: () => QueryKey }