Skip to content

Commit 09397ca

Browse files
authored
feat(solid-query): add 'mutationOptions' (#10138)
1 parent e924517 commit 09397ca

6 files changed

Lines changed: 836 additions & 0 deletions

File tree

.changeset/quick-taxes-cover.md

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

0 commit comments

Comments
 (0)