diff --git a/packages/vue-query/src/__tests__/test-utils.ts b/packages/vue-query/src/__tests__/test-utils.ts index fa1efb7e75..b9a019de40 100644 --- a/packages/vue-query/src/__tests__/test-utils.ts +++ b/packages/vue-query/src/__tests__/test-utils.ts @@ -50,3 +50,13 @@ export function successMutator(param: T): Promise { export function errorMutator(_: T): Promise { return rejectFetcher() } + +export type Equal = (() => T extends X ? 1 : 2) extends < + T, +>() => T extends Y ? 1 : 2 + ? true + : false + +export type Expect = T + +export const doNotExecute = (_func: () => void) => true diff --git a/packages/vue-query/src/__tests__/useInfiniteQuery.types.test.tsx b/packages/vue-query/src/__tests__/useInfiniteQuery.types.test.tsx new file mode 100644 index 0000000000..bf555dc2ae --- /dev/null +++ b/packages/vue-query/src/__tests__/useInfiniteQuery.types.test.tsx @@ -0,0 +1,82 @@ +import { InfiniteData } from '@tanstack/query-core' +import { reactive } from 'vue' +import { useInfiniteQuery } from '../useInfiniteQuery' +import { doNotExecute, Equal, Expect, simpleFetcher } from './test-utils' + +describe('Discriminated union return type', () => { + it('data should be possibly undefined by default', () => { + doNotExecute(() => { + const query = reactive( + useInfiniteQuery({ + queryFn: simpleFetcher, + }), + ) + + const result: Expect< + Equal | undefined, typeof query.data> + > = true + return result + }) + }) + + it('data should be defined when query is success', () => { + doNotExecute(() => { + const query = reactive( + useInfiniteQuery({ + queryFn: simpleFetcher, + }), + ) + + if (query.isSuccess) { + const result: Expect, typeof query.data>> = + true + return result + } + }) + }) + + it('error should be null when query is success', () => { + doNotExecute(() => { + const query = reactive( + useInfiniteQuery({ + queryFn: simpleFetcher, + }), + ) + + if (query.isSuccess) { + const result: Expect> = true + return result + } + }) + }) + + it('data should be undefined when query is loading', () => { + doNotExecute(() => { + const query = reactive( + useInfiniteQuery({ + queryFn: simpleFetcher, + }), + ) + + if (query.isLoading) { + const result: Expect> = true + return result + } + }) + }) + + it('error should be defined when query is error', () => { + doNotExecute(() => { + const query = reactive( + useInfiniteQuery({ + queryFn: simpleFetcher, + }), + ) + + if (query.isError) { + const result: Expect> = true + return result + } + }) + }) +}) diff --git a/packages/vue-query/src/__tests__/useMutation.types.test.tsx b/packages/vue-query/src/__tests__/useMutation.types.test.tsx new file mode 100644 index 0000000000..6555442c96 --- /dev/null +++ b/packages/vue-query/src/__tests__/useMutation.types.test.tsx @@ -0,0 +1,69 @@ +import { reactive } from 'vue' +import { useMutation } from '../useMutation' +import { doNotExecute, Equal, Expect, successMutator } from './test-utils' + +describe('Discriminated union return type', () => { + it('data should be possibly undefined by default', () => { + doNotExecute(() => { + const mutation = reactive( + useMutation({ mutationFn: successMutator }), + ) + + const result: Expect> = + true + return result + }) + }) + + it('data should be defined when mutation is success', () => { + doNotExecute(() => { + const mutation = reactive( + useMutation({ mutationFn: successMutator }), + ) + + if (mutation.isSuccess) { + const result: Expect> = true + return result + } + }) + }) + + it('error should be null when mutation is success', () => { + doNotExecute(() => { + const mutation = reactive( + useMutation({ mutationFn: successMutator }), + ) + + if (mutation.isSuccess) { + const result: Expect> = true + return result + } + }) + }) + + it('data should be undefined when mutation is loading', () => { + doNotExecute(() => { + const mutation = reactive( + useMutation({ mutationFn: successMutator }), + ) + + if (mutation.isLoading) { + const result: Expect> = true + return result + } + }) + }) + + it('error should be defined when mutation is error', () => { + doNotExecute(() => { + const mutation = reactive( + useMutation({ mutationFn: successMutator }), + ) + + if (mutation.isError) { + const result: Expect> = true + return result + } + }) + }) +}) diff --git a/packages/vue-query/src/__tests__/useQuery.types.test.tsx b/packages/vue-query/src/__tests__/useQuery.types.test.tsx new file mode 100644 index 0000000000..4a4fb9f18a --- /dev/null +++ b/packages/vue-query/src/__tests__/useQuery.types.test.tsx @@ -0,0 +1,78 @@ +import { reactive } from 'vue' +import { useQuery } from '../useQuery' +import { doNotExecute, Equal, Expect, simpleFetcher } from './test-utils' + +describe('Discriminated union return type', () => { + it('data should be possibly undefined by default', () => { + doNotExecute(() => { + const query = reactive( + useQuery({ + queryFn: simpleFetcher, + }), + ) + + const result: Expect> = true + return result + }) + }) + + it('data should be defined when query is success', () => { + doNotExecute(() => { + const query = reactive( + useQuery({ + queryFn: simpleFetcher, + }), + ) + + if (query.isSuccess) { + const result: Expect> = true + return result + } + }) + }) + + it('error should be null when query is success', () => { + doNotExecute(() => { + const query = reactive( + useQuery({ + queryFn: simpleFetcher, + }), + ) + + if (query.isSuccess) { + const result: Expect> = true + return result + } + }) + }) + + it('data should be undefined when query is loading', () => { + doNotExecute(() => { + const query = reactive( + useQuery({ + queryFn: simpleFetcher, + }), + ) + + if (query.isLoading) { + const result: Expect> = true + return result + } + }) + }) + + it('error should be defined when query is error', () => { + doNotExecute(() => { + const query = reactive( + useQuery({ + queryFn: simpleFetcher, + }), + ) + + if (query.isError) { + const result: Expect> = true + return result + } + }) + }) +}) diff --git a/packages/vue-query/src/types.ts b/packages/vue-query/src/types.ts index 28d6a6436e..9c0e0d473d 100644 --- a/packages/vue-query/src/types.ts +++ b/packages/vue-query/src/types.ts @@ -92,3 +92,7 @@ export type VueInfiniteQueryObserverOptions< >[Property] > } + +export type DistributiveOmit = T extends any + ? Omit + : never diff --git a/packages/vue-query/src/useInfiniteQuery.ts b/packages/vue-query/src/useInfiniteQuery.ts index 550d3a9280..6b55c53c04 100644 --- a/packages/vue-query/src/useInfiniteQuery.ts +++ b/packages/vue-query/src/useInfiniteQuery.ts @@ -13,6 +13,7 @@ import type { UseQueryReturnType } from './useBaseQuery' import type { WithQueryClientKey, VueInfiniteQueryObserverOptions, + DistributiveOmit, } from './types' export type UseInfiniteQueryOptions< @@ -35,7 +36,7 @@ type InfiniteQueryReturnType = UseQueryReturnType< TError, InfiniteQueryObserverResult > -export type UseInfiniteQueryReturnType = Omit< +export type UseInfiniteQueryReturnType = DistributiveOmit< InfiniteQueryReturnType, 'fetchNextPage' | 'fetchPreviousPage' | 'refetch' | 'remove' > & { diff --git a/packages/vue-query/src/useMutation.ts b/packages/vue-query/src/useMutation.ts index 42a8201f63..bb668236dd 100644 --- a/packages/vue-query/src/useMutation.ts +++ b/packages/vue-query/src/useMutation.ts @@ -16,12 +16,17 @@ import type { MutationObserverResult, MutationObserverOptions, } from '@tanstack/query-core' -import type { WithQueryClientKey, MaybeRef, MaybeRefDeep } from './types' +import type { + WithQueryClientKey, + MaybeRef, + MaybeRefDeep, + DistributiveOmit, +} from './types' import { MutationObserver } from '@tanstack/query-core' import { cloneDeepUnref, updateState, isMutationKey } from './utils' import { useQueryClient } from './useQueryClient' -type MutationResult = Omit< +type MutationResult = DistributiveOmit< MutationObserverResult, 'mutate' | 'reset' > diff --git a/packages/vue-query/src/useQuery.ts b/packages/vue-query/src/useQuery.ts index 1189340222..a63ab007ad 100644 --- a/packages/vue-query/src/useQuery.ts +++ b/packages/vue-query/src/useQuery.ts @@ -8,9 +8,13 @@ import type { } from '@tanstack/query-core' import { useBaseQuery } from './useBaseQuery' import type { UseQueryReturnType as UQRT } from './useBaseQuery' -import type { WithQueryClientKey, VueQueryObserverOptions } from './types' +import type { + WithQueryClientKey, + VueQueryObserverOptions, + DistributiveOmit, +} from './types' -export type UseQueryReturnType = Omit< +export type UseQueryReturnType = DistributiveOmit< UQRT, 'refetch' | 'remove' > & { @@ -18,7 +22,7 @@ export type UseQueryReturnType = Omit< remove: QueryObserverResult['remove'] } -export type UseQueryDefinedReturnType = Omit< +export type UseQueryDefinedReturnType = DistributiveOmit< ToRefs>>, 'refetch' | 'remove' > & {