// packages/dapp-kit/src/hooks/shared/useSafeMutation.ts import type { Result } from '@sodax/sdk'; import { useMutation, type MutateOptions, type UseMutationOptions, type UseMutationResult, } from '@tanstack/react-query'; import { useCallback } from 'react'; /** * Return shape of every dapp-kit mutation hook. Extends `UseMutationResult` with one extra * method, `mutateAsyncSafe`, that never rejects — it returns the SDK's `Result` shape so * callers can branch on `.ok` without `try/catch`. * * The underlying `mutationFn` still throws on SDK failure, so React Query's native error model * (`isError`, `error`, `onError`, `retry`, `throwOnError`, devtools) keeps working as documented. */ export type SafeUseMutationResult = UseMutationResult< TData, TError, TVars, TContext > & { /** * Like `mutateAsync` but never rejects. Returns `Result` so callers can branch * on `.ok` without `try/catch`. * * Use this for imperative flows where rejection-style errors are awkward — e.g. sequential * `if (!hasAllowance) await approve(...); await action(...)` chains where the user-reject * case is the modal failure mode, not an exceptional one. */ mutateAsyncSafe: ( vars: TVars, options?: MutateOptions, ) => Promise>; }; /** * Wraps a `Promise` (typically `mutateAsync(vars)`) into a `Promise>` that never * rejects. Pure, side-effect-free — extracted for unit testing. */ export async function toResult(promise: Promise): Promise> { try { const value = await promise; return { ok: true, value }; } catch (error) { return { ok: false, error }; } } /** * Drop-in replacement for `useMutation` that augments the result with `mutateAsyncSafe`. Used * by every dapp-kit mutation hook so consumers can pick rejection-style (`mutateAsync`) or * Result-style (`mutateAsyncSafe`) ergonomics without the hook author having to think about it. */ export function useSafeMutation( options: UseMutationOptions, ): SafeUseMutationResult { const mutation = useMutation(options); const { mutateAsync } = mutation; const mutateAsyncSafe = useCallback( (vars: TVars, opts?: MutateOptions): Promise> => toResult(mutateAsync(vars, opts)), [mutateAsync], ); return { ...mutation, mutateAsyncSafe }; }