Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(vue-query): use distributive omit to preserve union #4562

Merged
merged 1 commit into from Nov 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/vue-query/src/__tests__/test-utils.ts
Expand Up @@ -50,3 +50,13 @@ export function successMutator<T>(param: T): Promise<T> {
export function errorMutator<T>(_: T): Promise<Error> {
return rejectFetcher()
}

export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <
T,
>() => T extends Y ? 1 : 2
? true
: false

export type Expect<T extends true> = T

export const doNotExecute = (_func: () => void) => true
82 changes: 82 additions & 0 deletions 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<InfiniteData<string> | 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<Equal<InfiniteData<string>, 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<Equal<null, typeof query.error>> = 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<Equal<undefined, typeof query.data>> = 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<Equal<unknown, typeof query.error>> = true
return result
}
})
})
})
69 changes: 69 additions & 0 deletions 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<string> }),
)

const result: Expect<Equal<string | undefined, typeof mutation.data>> =
true
return result
})
})

it('data should be defined when mutation is success', () => {
doNotExecute(() => {
const mutation = reactive(
useMutation({ mutationFn: successMutator<string> }),
)

if (mutation.isSuccess) {
const result: Expect<Equal<string, typeof mutation.data>> = true
return result
}
})
})

it('error should be null when mutation is success', () => {
doNotExecute(() => {
const mutation = reactive(
useMutation({ mutationFn: successMutator<string> }),
)

if (mutation.isSuccess) {
const result: Expect<Equal<null, typeof mutation.error>> = true
return result
}
})
})

it('data should be undefined when mutation is loading', () => {
doNotExecute(() => {
const mutation = reactive(
useMutation({ mutationFn: successMutator<string> }),
)

if (mutation.isLoading) {
const result: Expect<Equal<undefined, typeof mutation.data>> = true
return result
}
})
})

it('error should be defined when mutation is error', () => {
doNotExecute(() => {
const mutation = reactive(
useMutation({ mutationFn: successMutator<string> }),
)

if (mutation.isError) {
const result: Expect<Equal<unknown, typeof mutation.error>> = true
return result
}
})
})
})
78 changes: 78 additions & 0 deletions 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<Equal<string | undefined, typeof query.data>> = 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<Equal<string, typeof query.data>> = 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<Equal<null, typeof query.error>> = 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<Equal<undefined, typeof query.data>> = 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<Equal<unknown, typeof query.error>> = true
return result
}
})
})
})
4 changes: 4 additions & 0 deletions packages/vue-query/src/types.ts
Expand Up @@ -92,3 +92,7 @@ export type VueInfiniteQueryObserverOptions<
>[Property]
>
}

export type DistributiveOmit<T, K extends keyof any> = T extends any
? Omit<T, K>
: never
3 changes: 2 additions & 1 deletion packages/vue-query/src/useInfiniteQuery.ts
Expand Up @@ -13,6 +13,7 @@ import type { UseQueryReturnType } from './useBaseQuery'
import type {
WithQueryClientKey,
VueInfiniteQueryObserverOptions,
DistributiveOmit,
} from './types'

export type UseInfiniteQueryOptions<
Expand All @@ -35,7 +36,7 @@ type InfiniteQueryReturnType<TData, TError> = UseQueryReturnType<
TError,
InfiniteQueryObserverResult<TData, TError>
>
export type UseInfiniteQueryReturnType<TData, TError> = Omit<
export type UseInfiniteQueryReturnType<TData, TError> = DistributiveOmit<
InfiniteQueryReturnType<TData, TError>,
'fetchNextPage' | 'fetchPreviousPage' | 'refetch' | 'remove'
> & {
Expand Down
9 changes: 7 additions & 2 deletions packages/vue-query/src/useMutation.ts
Expand Up @@ -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<TData, TError, TVariables, TContext> = Omit<
type MutationResult<TData, TError, TVariables, TContext> = DistributiveOmit<
MutationObserverResult<TData, TError, TVariables, TContext>,
'mutate' | 'reset'
>
Expand Down
10 changes: 7 additions & 3 deletions packages/vue-query/src/useQuery.ts
Expand Up @@ -8,17 +8,21 @@ 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<TData, TError> = Omit<
export type UseQueryReturnType<TData, TError> = DistributiveOmit<
UQRT<TData, TError>,
'refetch' | 'remove'
> & {
refetch: QueryObserverResult<TData, TError>['refetch']
remove: QueryObserverResult<TData, TError>['remove']
}

export type UseQueryDefinedReturnType<TData, TError> = Omit<
export type UseQueryDefinedReturnType<TData, TError> = DistributiveOmit<
ToRefs<Readonly<DefinedQueryObserverResult<TData, TError>>>,
'refetch' | 'remove'
> & {
Expand Down