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 = DistributiveOmit< MutationObserverResult, 'mutate' | 'reset' > export type UseMutationOptions< TData = unknown, TError = DefaultError, TVariables = void, TOnMutateResult = unknown, > = | MaybeRefDeep> | (() => MaybeRefDeep< MutationOptions >) type MutateSyncFunction< TData = unknown, TError = DefaultError, TVariables = void, TOnMutateResult = unknown, > = ( ...options: Parameters< MutateFunction > ) => void export type UseMutationReturnType< TData, TError, TVariables, TOnMutateResult, TResult = MutationResult, > = ToRefs> & { mutate: MutateSyncFunction mutateAsync: MutateFunction 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 * * * * ``` * * @example * Rendering the mutation's own state, rather than just firing it off: * ```vue * * * * ``` * * @example * Optimistic update via `onMutate`, rolling back on `onError`: * ```vue * * * * ``` * * @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 * * * * ``` * * @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 * * * * ``` */ export function useMutation< TData = unknown, TError = DefaultError, TVariables = void, TOnMutateResult = unknown, >( options: UseMutationOptions, queryClient?: QueryClient, ): UseMutationReturnType { 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 > ) => { 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> > watch( () => state.error, (error) => { if ( error && shouldThrowError(defaultedOptions.value.throwOnError, [error as TError]) ) { throw error } }, ) return { ...resultRefs, mutate, mutateAsync: state.mutate, reset: state.reset, } }