snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
autofix.ci / autofix (push) Failing after 4s
Release / Release (push) Has been skipped
GitHub Actions Security Analysis / Run zizmor (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-16 01:13:08 +05:00
commit 3a013ccdfe
3570 changed files with 490194 additions and 0 deletions

View File

@@ -0,0 +1,306 @@
import {
computed,
getCurrentScope,
onScopeDispose,
reactive,
readonly,
shallowReactive,
shallowReadonly,
toRefs,
watch,
} from 'vue-demi'
import { MutationObserver, shouldThrowError } from '@tanstack/query-core'
import { cloneDeepUnref, updateState } from './utils'
import { useQueryClient } from './useQueryClient'
import type { ToRefs } from 'vue-demi'
import type {
DefaultError,
DistributiveOmit,
MutateFunction,
MutationObserverResult,
} from '@tanstack/query-core'
import type { MaybeRefDeep, MutationOptions } from './types'
import type { QueryClient } from './queryClient'
type MutationResult<TData, TError, TVariables, TOnMutateResult> =
DistributiveOmit<
MutationObserverResult<TData, TError, TVariables, TOnMutateResult>,
'mutate' | 'reset'
>
export type UseMutationOptions<
TData = unknown,
TError = DefaultError,
TVariables = void,
TOnMutateResult = unknown,
> =
| MaybeRefDeep<MutationOptions<TData, TError, TVariables, TOnMutateResult>>
| (() => MaybeRefDeep<
MutationOptions<TData, TError, TVariables, TOnMutateResult>
>)
type MutateSyncFunction<
TData = unknown,
TError = DefaultError,
TVariables = void,
TOnMutateResult = unknown,
> = (
...options: Parameters<
MutateFunction<TData, TError, TVariables, TOnMutateResult>
>
) => void
export type UseMutationReturnType<
TData,
TError,
TVariables,
TOnMutateResult,
TResult = MutationResult<TData, TError, TVariables, TOnMutateResult>,
> = ToRefs<Readonly<TResult>> & {
mutate: MutateSyncFunction<TData, TError, TVariables, TOnMutateResult>
mutateAsync: MutateFunction<TData, TError, TVariables, TOnMutateResult>
reset: MutationObserverResult<
TData,
TError,
TVariables,
TOnMutateResult
>['reset']
}
/**
* Unlike queries, mutations are typically used to create/update/delete data or perform server side-effects.
* `useMutation` is the composable for that.
*
* `options` may be a plain object, a `ref`, or a reactive getter (`() => ({ ... })`) — pass a getter
* if the options themselves depend on other reactive state.
*
* @see {@link mutationOptions} to share these options across multiple `useMutation` call sites, or to look
* the mutation up elsewhere via its `mutationKey` (e.g. with `useMutationState`).
* @param options - The {@link UseMutationOptions} to use — everything you can pass to `useMutation`.
* @param queryClient - Use this to use a custom `QueryClient`. Otherwise, the one provided by `VueQueryPlugin`
* will be used.
* @returns `mutate`/`mutateAsync` also accept per-call `onSuccess`/`onError`/`onSettled` callbacks as a second
* argument, useful for triggering call-site side effects (e.g. navigation) without coupling them to the shared
* mutation definition. Hook-level callbacks (passed to `options`) fire for every mutation; per-call
* callbacks fire only for the latest call you've made.
*
* @example
* ```vue
* <script setup lang="ts">
* import { useMutation, useQueryClient } from '@tanstack/vue-query'
*
* const queryClient = useQueryClient()
*
* const addMutation = useMutation({
* mutationFn: addTodo,
* onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
* })
*
* function onAdd() {
* addMutation.mutate('Item', {
* onError: (error) => console.error('Failed to add item:', error),
* })
* }
* </script>
*
* <template>
* <button @click="onAdd">Add</button>
* </template>
* ```
*
* @example
* Rendering the mutation's own state, rather than just firing it off:
* ```vue
* <script setup lang="ts">
* import { useMutation, useQueryClient } from '@tanstack/vue-query'
*
* const queryClient = useQueryClient()
*
* const addMutation = useMutation({
* mutationFn: addTodo,
* onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
* })
* </script>
*
* <template>
* <div v-if="addMutation.isPending.value">Adding todo...</div>
* <div v-else>
* <div v-if="addMutation.isError.value">An error occurred: {{ addMutation.error.value.message }}</div>
* <button @click="addMutation.mutate('Item')">Add</button>
* </div>
* </template>
* ```
*
* @example
* Optimistic update via `onMutate`, rolling back on `onError`:
* ```vue
* <script setup lang="ts">
* import { useMutation, useQueryClient } from '@tanstack/vue-query'
*
* const queryClient = useQueryClient()
*
* const addMutation = useMutation({
* mutationFn: addTodo,
* onMutate: async (newTodo: string) => {
* await queryClient.cancelQueries({ queryKey: ['todos'] })
* const previousTodos = queryClient.getQueryData<Array<string>>(['todos'])
*
* queryClient.setQueryData<Array<string>>(['todos'], (old) => [
* ...(old ?? []),
* newTodo,
* ])
*
* // Passed to `onError` as `onMutateResult` if the mutation fails.
* return { previousTodos }
* },
* onError: (_err, _newTodo, onMutateResult) => {
* queryClient.setQueryData(['todos'], onMutateResult?.previousTodos)
* },
* onSettled: () => {
* queryClient.invalidateQueries({ queryKey: ['todos'] })
* },
* })
* </script>
*
* <template>
* <button @click="addMutation.mutate('Item')">Add</button>
* </template>
* ```
*
* @example
* Callbacks passed per call to `mutate` only fire for the last call — `mutateAsync` gives you a
* promise per call instead, so you can wait for all of them when they succeed:
* ```vue
* <script setup lang="ts">
* import { useMutation, useQueryClient } from '@tanstack/vue-query'
*
* const queryClient = useQueryClient()
*
* const addMutation = useMutation({
* mutationFn: addTodo,
* onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
* })
*
* async function handleAddAll(todos: Array<string>) {
* try {
* await Promise.all(todos.map((todo) => addMutation.mutateAsync(todo)))
* } catch (error) {
* console.error('Failed to add todos:', error)
* }
* }
* </script>
*
* <template>
* <button @click="handleAddAll(['Todo 1', 'Todo 2', 'Todo 3'])">Add all</button>
* </template>
* ```
*
* @example
* If some of the mutations above can fail independently of the others, and you want to know which ones
* did — rather than losing that information the moment the first one rejects — swap `Promise.all` for
* `Promise.allSettled`:
* ```vue
* <script setup lang="ts">
* import { useMutation, useQueryClient } from '@tanstack/vue-query'
*
* const queryClient = useQueryClient()
*
* const addMutation = useMutation({
* mutationFn: addTodo,
* onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
* })
*
* async function handleAddAll(todos: Array<string>) {
* const addResults = await Promise.allSettled(
* todos.map((todo) => addMutation.mutateAsync(todo)),
* )
*
* addResults.forEach((addResult, index) => {
* if (addResult.status === 'rejected') {
* console.error(`Failed to add "${todos[index]}":`, addResult.reason)
* }
* })
* }
* </script>
*
* <template>
* <button @click="handleAddAll(['Todo 1', 'Todo 2', 'Todo 3'])">Add all</button>
* </template>
* ```
*/
export function useMutation<
TData = unknown,
TError = DefaultError,
TVariables = void,
TOnMutateResult = unknown,
>(
options: UseMutationOptions<TData, TError, TVariables, TOnMutateResult>,
queryClient?: QueryClient,
): UseMutationReturnType<TData, TError, TVariables, TOnMutateResult> {
if (process.env.NODE_ENV === 'development') {
if (!getCurrentScope()) {
console.warn(
'vue-query composable like "useQuery()" should only be used inside a "setup()" function or a running effect scope. They might otherwise lead to memory leaks.',
)
}
}
const client = queryClient || useQueryClient()
const defaultedOptions = computed(() => {
const resolvedOptions = typeof options === 'function' ? options() : options
return client.defaultMutationOptions(cloneDeepUnref(resolvedOptions))
})
const observer = new MutationObserver(client, defaultedOptions.value)
const state = defaultedOptions.value.shallow
? shallowReactive(observer.getCurrentResult())
: reactive(observer.getCurrentResult())
const unsubscribe = observer.subscribe((result) => {
updateState(state, result)
})
const mutate = (
...args: Parameters<
MutateFunction<TData, TError, TVariables, TOnMutateResult>
>
) => {
observer.mutate(args[0] as TVariables, args[1]).catch(() => {
// This is intentional
})
}
watch(defaultedOptions, () => {
observer.setOptions(defaultedOptions.value)
})
onScopeDispose(() => {
unsubscribe()
})
const readonlyState = defaultedOptions.value.shallow
? shallowReadonly(state)
: readonly(state)
const resultRefs = toRefs(readonlyState) as ToRefs<
Readonly<MutationResult<TData, TError, TVariables, TOnMutateResult>>
>
watch(
() => state.error,
(error) => {
if (
error &&
shouldThrowError(defaultedOptions.value.throwOnError, [error as TError])
) {
throw error
}
},
)
return {
...resultRefs,
mutate,
mutateAsync: state.mutate,
reset: state.reset,
}
}