Skip to content

Commit f6d92a7

Browse files
authored
fix(vue-query): use distributive omit to preserve union (#4562)
1 parent 70fd5bb commit f6d92a7

8 files changed

+259
-6
lines changed

packages/vue-query/src/__tests__/test-utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,13 @@ export function successMutator<T>(param: T): Promise<T> {
5050
export function errorMutator<T>(_: T): Promise<Error> {
5151
return rejectFetcher()
5252
}
53+
54+
export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <
55+
T,
56+
>() => T extends Y ? 1 : 2
57+
? true
58+
: false
59+
60+
export type Expect<T extends true> = T
61+
62+
export const doNotExecute = (_func: () => void) => true
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { InfiniteData } from '@tanstack/query-core'
2+
import { reactive } from 'vue'
3+
import { useInfiniteQuery } from '../useInfiniteQuery'
4+
import { doNotExecute, Equal, Expect, simpleFetcher } from './test-utils'
5+
6+
describe('Discriminated union return type', () => {
7+
it('data should be possibly undefined by default', () => {
8+
doNotExecute(() => {
9+
const query = reactive(
10+
useInfiniteQuery({
11+
queryFn: simpleFetcher,
12+
}),
13+
)
14+
15+
const result: Expect<
16+
Equal<InfiniteData<string> | undefined, typeof query.data>
17+
> = true
18+
return result
19+
})
20+
})
21+
22+
it('data should be defined when query is success', () => {
23+
doNotExecute(() => {
24+
const query = reactive(
25+
useInfiniteQuery({
26+
queryFn: simpleFetcher,
27+
}),
28+
)
29+
30+
if (query.isSuccess) {
31+
const result: Expect<Equal<InfiniteData<string>, typeof query.data>> =
32+
true
33+
return result
34+
}
35+
})
36+
})
37+
38+
it('error should be null when query is success', () => {
39+
doNotExecute(() => {
40+
const query = reactive(
41+
useInfiniteQuery({
42+
queryFn: simpleFetcher,
43+
}),
44+
)
45+
46+
if (query.isSuccess) {
47+
const result: Expect<Equal<null, typeof query.error>> = true
48+
return result
49+
}
50+
})
51+
})
52+
53+
it('data should be undefined when query is loading', () => {
54+
doNotExecute(() => {
55+
const query = reactive(
56+
useInfiniteQuery({
57+
queryFn: simpleFetcher,
58+
}),
59+
)
60+
61+
if (query.isLoading) {
62+
const result: Expect<Equal<undefined, typeof query.data>> = true
63+
return result
64+
}
65+
})
66+
})
67+
68+
it('error should be defined when query is error', () => {
69+
doNotExecute(() => {
70+
const query = reactive(
71+
useInfiniteQuery({
72+
queryFn: simpleFetcher,
73+
}),
74+
)
75+
76+
if (query.isError) {
77+
const result: Expect<Equal<unknown, typeof query.error>> = true
78+
return result
79+
}
80+
})
81+
})
82+
})
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { reactive } from 'vue'
2+
import { useMutation } from '../useMutation'
3+
import { doNotExecute, Equal, Expect, successMutator } from './test-utils'
4+
5+
describe('Discriminated union return type', () => {
6+
it('data should be possibly undefined by default', () => {
7+
doNotExecute(() => {
8+
const mutation = reactive(
9+
useMutation({ mutationFn: successMutator<string> }),
10+
)
11+
12+
const result: Expect<Equal<string | undefined, typeof mutation.data>> =
13+
true
14+
return result
15+
})
16+
})
17+
18+
it('data should be defined when mutation is success', () => {
19+
doNotExecute(() => {
20+
const mutation = reactive(
21+
useMutation({ mutationFn: successMutator<string> }),
22+
)
23+
24+
if (mutation.isSuccess) {
25+
const result: Expect<Equal<string, typeof mutation.data>> = true
26+
return result
27+
}
28+
})
29+
})
30+
31+
it('error should be null when mutation is success', () => {
32+
doNotExecute(() => {
33+
const mutation = reactive(
34+
useMutation({ mutationFn: successMutator<string> }),
35+
)
36+
37+
if (mutation.isSuccess) {
38+
const result: Expect<Equal<null, typeof mutation.error>> = true
39+
return result
40+
}
41+
})
42+
})
43+
44+
it('data should be undefined when mutation is loading', () => {
45+
doNotExecute(() => {
46+
const mutation = reactive(
47+
useMutation({ mutationFn: successMutator<string> }),
48+
)
49+
50+
if (mutation.isLoading) {
51+
const result: Expect<Equal<undefined, typeof mutation.data>> = true
52+
return result
53+
}
54+
})
55+
})
56+
57+
it('error should be defined when mutation is error', () => {
58+
doNotExecute(() => {
59+
const mutation = reactive(
60+
useMutation({ mutationFn: successMutator<string> }),
61+
)
62+
63+
if (mutation.isError) {
64+
const result: Expect<Equal<unknown, typeof mutation.error>> = true
65+
return result
66+
}
67+
})
68+
})
69+
})
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { reactive } from 'vue'
2+
import { useQuery } from '../useQuery'
3+
import { doNotExecute, Equal, Expect, simpleFetcher } from './test-utils'
4+
5+
describe('Discriminated union return type', () => {
6+
it('data should be possibly undefined by default', () => {
7+
doNotExecute(() => {
8+
const query = reactive(
9+
useQuery({
10+
queryFn: simpleFetcher,
11+
}),
12+
)
13+
14+
const result: Expect<Equal<string | undefined, typeof query.data>> = true
15+
return result
16+
})
17+
})
18+
19+
it('data should be defined when query is success', () => {
20+
doNotExecute(() => {
21+
const query = reactive(
22+
useQuery({
23+
queryFn: simpleFetcher,
24+
}),
25+
)
26+
27+
if (query.isSuccess) {
28+
const result: Expect<Equal<string, typeof query.data>> = true
29+
return result
30+
}
31+
})
32+
})
33+
34+
it('error should be null when query is success', () => {
35+
doNotExecute(() => {
36+
const query = reactive(
37+
useQuery({
38+
queryFn: simpleFetcher,
39+
}),
40+
)
41+
42+
if (query.isSuccess) {
43+
const result: Expect<Equal<null, typeof query.error>> = true
44+
return result
45+
}
46+
})
47+
})
48+
49+
it('data should be undefined when query is loading', () => {
50+
doNotExecute(() => {
51+
const query = reactive(
52+
useQuery({
53+
queryFn: simpleFetcher,
54+
}),
55+
)
56+
57+
if (query.isLoading) {
58+
const result: Expect<Equal<undefined, typeof query.data>> = true
59+
return result
60+
}
61+
})
62+
})
63+
64+
it('error should be defined when query is error', () => {
65+
doNotExecute(() => {
66+
const query = reactive(
67+
useQuery({
68+
queryFn: simpleFetcher,
69+
}),
70+
)
71+
72+
if (query.isError) {
73+
const result: Expect<Equal<unknown, typeof query.error>> = true
74+
return result
75+
}
76+
})
77+
})
78+
})

packages/vue-query/src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,7 @@ export type VueInfiniteQueryObserverOptions<
9292
>[Property]
9393
>
9494
}
95+
96+
export type DistributiveOmit<T, K extends keyof any> = T extends any
97+
? Omit<T, K>
98+
: never

packages/vue-query/src/useInfiniteQuery.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { UseQueryReturnType } from './useBaseQuery'
1313
import type {
1414
WithQueryClientKey,
1515
VueInfiniteQueryObserverOptions,
16+
DistributiveOmit,
1617
} from './types'
1718

1819
export type UseInfiniteQueryOptions<
@@ -35,7 +36,7 @@ type InfiniteQueryReturnType<TData, TError> = UseQueryReturnType<
3536
TError,
3637
InfiniteQueryObserverResult<TData, TError>
3738
>
38-
export type UseInfiniteQueryReturnType<TData, TError> = Omit<
39+
export type UseInfiniteQueryReturnType<TData, TError> = DistributiveOmit<
3940
InfiniteQueryReturnType<TData, TError>,
4041
'fetchNextPage' | 'fetchPreviousPage' | 'refetch' | 'remove'
4142
> & {

packages/vue-query/src/useMutation.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@ import type {
1616
MutationObserverResult,
1717
MutationObserverOptions,
1818
} from '@tanstack/query-core'
19-
import type { WithQueryClientKey, MaybeRef, MaybeRefDeep } from './types'
19+
import type {
20+
WithQueryClientKey,
21+
MaybeRef,
22+
MaybeRefDeep,
23+
DistributiveOmit,
24+
} from './types'
2025
import { MutationObserver } from '@tanstack/query-core'
2126
import { cloneDeepUnref, updateState, isMutationKey } from './utils'
2227
import { useQueryClient } from './useQueryClient'
2328

24-
type MutationResult<TData, TError, TVariables, TContext> = Omit<
29+
type MutationResult<TData, TError, TVariables, TContext> = DistributiveOmit<
2530
MutationObserverResult<TData, TError, TVariables, TContext>,
2631
'mutate' | 'reset'
2732
>

packages/vue-query/src/useQuery.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,21 @@ import type {
88
} from '@tanstack/query-core'
99
import { useBaseQuery } from './useBaseQuery'
1010
import type { UseQueryReturnType as UQRT } from './useBaseQuery'
11-
import type { WithQueryClientKey, VueQueryObserverOptions } from './types'
11+
import type {
12+
WithQueryClientKey,
13+
VueQueryObserverOptions,
14+
DistributiveOmit,
15+
} from './types'
1216

13-
export type UseQueryReturnType<TData, TError> = Omit<
17+
export type UseQueryReturnType<TData, TError> = DistributiveOmit<
1418
UQRT<TData, TError>,
1519
'refetch' | 'remove'
1620
> & {
1721
refetch: QueryObserverResult<TData, TError>['refetch']
1822
remove: QueryObserverResult<TData, TError>['remove']
1923
}
2024

21-
export type UseQueryDefinedReturnType<TData, TError> = Omit<
25+
export type UseQueryDefinedReturnType<TData, TError> = DistributiveOmit<
2226
ToRefs<Readonly<DefinedQueryObserverResult<TData, TError>>>,
2327
'refetch' | 'remove'
2428
> & {

0 commit comments

Comments
 (0)