Skip to content

Commit f279ad2

Browse files
feat(vue-query): add 'mutationOptions' (#10381)
* feat(vue-query): add 'mutationOptions' * ci: apply automated fixes * chore(.changeset): add changeset for 'mutationOptions' in vue-query * feat(vue-query/mutationOptions): add getter support and fix test descriptions to match actual behavior * refactor(vue-query): rename 'VueMutationOptions' to 'MutationOptions' * docs(vue-query): add 'mutationOptions' reference page * test(vue-query/mutationOptions): add runtime test for getter without 'mutationKey' * docs(vue-query): add 'mutationOptions' to config.json navigation --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 012d323 commit f279ad2

File tree

9 files changed

+749
-14
lines changed

9 files changed

+749
-14
lines changed

.changeset/many-symbols-write.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/vue-query': minor
3+
---
4+
5+
feat(vue-query): add 'mutationOptions'

docs/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,10 @@
10671067
"label": "infiniteQueryOptions",
10681068
"to": "framework/vue/reference/infiniteQueryOptions"
10691069
},
1070+
{
1071+
"label": "mutationOptions",
1072+
"to": "framework/vue/reference/mutationOptions"
1073+
},
10701074
{
10711075
"label": "usePrefetchQuery",
10721076
"to": "framework/vue/reference/usePrefetchQuery"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
id: mutationOptions
3+
title: mutationOptions
4+
ref: docs/framework/react/reference/mutationOptions.md
5+
---
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
import { assertType, describe, expectTypeOf, it } from 'vitest'
2+
import { QueryClient } from '@tanstack/query-core'
3+
import { useMutation } from '../useMutation'
4+
import { useIsMutating, useMutationState } from '../useMutationState'
5+
import { mutationOptions } from '../mutationOptions'
6+
import type {
7+
DefaultError,
8+
MutationFunctionContext,
9+
MutationState,
10+
WithRequired,
11+
} from '@tanstack/query-core'
12+
import type { Ref } from 'vue-demi'
13+
import type { MutationOptions } from '../types'
14+
15+
describe('mutationOptions', () => {
16+
it('should not allow excess properties', () => {
17+
mutationOptions({
18+
// @ts-expect-error this is a good error, because onMutates does not exist!
19+
mutationFn: () => Promise.resolve(5),
20+
mutationKey: ['key'],
21+
onMutates: 1000,
22+
onSuccess: (data) => {
23+
expectTypeOf(data).toEqualTypeOf<number>()
24+
},
25+
})
26+
})
27+
28+
it('should infer types for callbacks', () => {
29+
mutationOptions({
30+
mutationFn: () => Promise.resolve(5),
31+
mutationKey: ['key'],
32+
onSuccess: (data) => {
33+
expectTypeOf(data).toEqualTypeOf<number>()
34+
},
35+
})
36+
})
37+
38+
it('should infer types for onError callback', () => {
39+
mutationOptions({
40+
mutationFn: () => {
41+
throw new Error('fail')
42+
},
43+
mutationKey: ['key'],
44+
onError: (error) => {
45+
expectTypeOf(error).toEqualTypeOf<DefaultError>()
46+
},
47+
})
48+
})
49+
50+
it('should infer types for variables', () => {
51+
mutationOptions<number, DefaultError, { id: string }>({
52+
mutationFn: (vars) => {
53+
expectTypeOf(vars).toEqualTypeOf<{ id: string }>()
54+
return Promise.resolve(5)
55+
},
56+
mutationKey: ['with-vars'],
57+
})
58+
})
59+
60+
it('should infer result type correctly', () => {
61+
mutationOptions<number, DefaultError, void, { name: string }>({
62+
mutationFn: () => Promise.resolve(5),
63+
mutationKey: ['key'],
64+
onMutate: () => {
65+
return { name: 'onMutateResult' }
66+
},
67+
onSuccess: (_data, _variables, onMutateResult) => {
68+
expectTypeOf(onMutateResult).toEqualTypeOf<{ name: string }>()
69+
},
70+
})
71+
})
72+
73+
it('should infer context type correctly', () => {
74+
mutationOptions<number>({
75+
mutationFn: (_variables, context) => {
76+
expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
77+
return Promise.resolve(5)
78+
},
79+
mutationKey: ['key'],
80+
onMutate: (_variables, context) => {
81+
expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
82+
},
83+
onSuccess: (_data, _variables, _onMutateResult, context) => {
84+
expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
85+
},
86+
onError: (_error, _variables, _onMutateResult, context) => {
87+
expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
88+
},
89+
onSettled: (_data, _error, _variables, _onMutateResult, context) => {
90+
expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
91+
},
92+
})
93+
})
94+
95+
it('should error if mutationFn return type mismatches TData', () => {
96+
assertType(
97+
mutationOptions<number>({
98+
// @ts-expect-error this is a good error, because return type is string, not number
99+
mutationFn: async () => Promise.resolve('wrong return'),
100+
}),
101+
)
102+
})
103+
104+
it('should allow mutationKey to be omitted', () => {
105+
return mutationOptions({
106+
mutationFn: () => Promise.resolve(123),
107+
onSuccess: (data) => {
108+
expectTypeOf(data).toEqualTypeOf<number>()
109+
},
110+
})
111+
})
112+
113+
it('should infer all types when not explicitly provided', () => {
114+
expectTypeOf(
115+
mutationOptions({
116+
mutationFn: (id: string) => Promise.resolve(id.length),
117+
mutationKey: ['key'],
118+
onSuccess: (data) => {
119+
expectTypeOf(data).toEqualTypeOf<number>()
120+
},
121+
}),
122+
).toEqualTypeOf<
123+
WithRequired<
124+
MutationOptions<number, DefaultError, string, unknown>,
125+
'mutationKey'
126+
>
127+
>()
128+
expectTypeOf(
129+
mutationOptions({
130+
mutationFn: (id: string) => Promise.resolve(id.length),
131+
onSuccess: (data) => {
132+
expectTypeOf(data).toEqualTypeOf<number>()
133+
},
134+
}),
135+
).toEqualTypeOf<
136+
Omit<
137+
MutationOptions<number, DefaultError, string, unknown>,
138+
'mutationKey'
139+
>
140+
>()
141+
})
142+
143+
it('should work when used with useMutation', () => {
144+
const mutation = useMutation(
145+
mutationOptions({
146+
mutationKey: ['key'],
147+
mutationFn: () => Promise.resolve('data'),
148+
onSuccess: (data) => {
149+
expectTypeOf(data).toEqualTypeOf<string>()
150+
},
151+
}),
152+
)
153+
expectTypeOf(mutation.data.value).toEqualTypeOf<string | undefined>()
154+
155+
// should allow when used with useMutation without mutationKey
156+
useMutation(
157+
mutationOptions({
158+
mutationFn: () => Promise.resolve('data'),
159+
onSuccess: (data) => {
160+
expectTypeOf(data).toEqualTypeOf<string>()
161+
},
162+
}),
163+
)
164+
})
165+
166+
it('should work when used with useIsMutating', () => {
167+
const isMutating = useIsMutating(
168+
mutationOptions({
169+
mutationKey: ['key'],
170+
mutationFn: () => Promise.resolve(5),
171+
}),
172+
)
173+
expectTypeOf(isMutating).toEqualTypeOf<Ref<number>>()
174+
175+
useIsMutating(
176+
// @ts-expect-error filters should have mutationKey
177+
mutationOptions({
178+
mutationFn: () => Promise.resolve(5),
179+
}),
180+
)
181+
})
182+
183+
it('should work when used with queryClient.isMutating', () => {
184+
const queryClient = new QueryClient()
185+
186+
const isMutating = queryClient.isMutating(
187+
mutationOptions({
188+
mutationKey: ['key'],
189+
mutationFn: () => Promise.resolve(5),
190+
}),
191+
)
192+
expectTypeOf(isMutating).toEqualTypeOf<number>()
193+
194+
queryClient.isMutating(
195+
// @ts-expect-error filters should have mutationKey
196+
mutationOptions({
197+
mutationFn: () => Promise.resolve(5),
198+
}),
199+
)
200+
})
201+
202+
it('should work when used with useMutationState', () => {
203+
const mutationState = useMutationState({
204+
filters: mutationOptions({
205+
mutationKey: ['key'],
206+
mutationFn: () => Promise.resolve(5),
207+
}),
208+
})
209+
expectTypeOf(mutationState.value).toEqualTypeOf<
210+
Array<MutationState<unknown, Error, unknown, unknown>>
211+
>()
212+
213+
useMutationState({
214+
// @ts-expect-error filters should have mutationKey
215+
filters: mutationOptions({
216+
mutationFn: () => Promise.resolve(5),
217+
}),
218+
})
219+
})
220+
221+
it('should allow getter and infer types correctly', () => {
222+
const options = mutationOptions(() => ({
223+
mutationKey: ['key'] as const,
224+
mutationFn: () => Promise.resolve('data'),
225+
onSuccess: (data) => {
226+
expectTypeOf(data).toEqualTypeOf<string>()
227+
},
228+
}))
229+
230+
const resolved = options()
231+
expectTypeOf(resolved.mutationFn).not.toBeUndefined()
232+
expectTypeOf(resolved.mutationKey).not.toBeUndefined()
233+
})
234+
235+
it('should allow getter without mutationKey', () => {
236+
const options = mutationOptions(() => ({
237+
mutationFn: () => Promise.resolve(5),
238+
onSuccess: (data) => {
239+
expectTypeOf(data).toEqualTypeOf<number>()
240+
},
241+
}))
242+
243+
const resolved = options()
244+
expectTypeOf(resolved.mutationFn).not.toBeUndefined()
245+
})
246+
})

0 commit comments

Comments
 (0)