/** * Mutation options factory for TanStack Query integration. * Creates type-safe mutation options from Eden route definitions. */ import type { MutationFunction, UseMutationOptions, } from "@tanstack/react-query" import { getMutationKey } from "../keys/queryKey" import type { EdenMutationKey } from "../keys/types" import type { EmptyToVoid } from "../utils/types" // ============================================================================ // Input Types // ============================================================================ /** Reserved options that are set by the library */ type ReservedOptions = "mutationKey" | "mutationFn" /** Base options for Eden requests */ interface EdenMutationBaseOptions { eden?: { /** Custom context for the mutation */ context?: Record } } /** Result metadata added to mutation options */ export interface EdenMutationOptionsResult { eden: { path: string } } // ============================================================================ // Option Types // ============================================================================ /** * Input options for creating mutation options. * Omits reserved options that are set by the library. */ export type EdenMutationOptionsIn = Omit< UseMutationOptions, ReservedOptions > & EdenMutationBaseOptions /** * Output options returned by edenMutationOptions. * Includes the mutation key and eden metadata. * mutationFn is guaranteed to be defined. * Uses EmptyToVoid so mutate() can be called without args when input is empty. */ export interface EdenMutationOptionsOut extends UseMutationOptions, TContext>, EdenMutationOptionsResult { mutationKey: EdenMutationKey mutationFn: MutationFunction> } /** * Arguments for creating mutation options. */ export interface EdenMutationOptionsArgs { /** Path segments (e.g., ['api', 'users', 'post']) */ path: string[] /** Function to perform the mutation */ mutate: (input: TInput) => Promise /** Optional mutation options */ opts?: EdenMutationOptionsIn } // ============================================================================ // Implementation // ============================================================================ /** * Creates TanStack Query mutation options for an Eden route. * * @example * ```typescript * const options = edenMutationOptions({ * path: ['api', 'users', 'post'], * mutate: async (input) => { * const response = await edenClient.api.users.post(input) * return response.data * }, * opts: { * onMutate: (variables) => { * // Called before mutation * return { previousData: [...] } // context * }, * onSuccess: (data, variables, context) => { * // Called on success * }, * onError: (error, variables, context) => { * // Rollback using context * }, * onSettled: (data, error, variables, context) => { * // Always called * queryClient.invalidateQueries({ queryKey: ['users'] }) * }, * }, * }) * * // Use with useMutation * const mutation = useMutation(options) * mutation.mutate({ name: 'John' }) * ``` */ export function edenMutationOptions< TInput, TOutput, TError = Error, TContext = unknown, >( args: EdenMutationOptionsArgs, ): EdenMutationOptionsOut { const { path, mutate, opts } = args const mutationKey = getMutationKey({ path }) const mutationFn: MutationFunction> = async ( input, _context, ) => { return await mutate(input as TInput) } // Cast opts to match EmptyToVoid for variables type const outputOpts = opts as unknown as Omit< UseMutationOptions, TContext>, ReservedOptions > return { ...outputOpts, mutationKey, mutationFn, eden: { path: path.join("."), }, } }