# Mutations This guide covers how to use mutations for POST, PUT, PATCH, and DELETE operations with `@qualisero/openapi-endpoint`. ## What is a Mutation? Mutations are used for creating, updating, or deleting data on your API. They wrap TanStack Query's `useMutation` composable with type safety based on your OpenAPI specification. ## Basic Mutation Usage ### Mutation Without Path Parameters ```typescript import { api } from './api/init' // Simple mutation for creating data const createPet = api.createPet.useMutation({ onSuccess: (data) => { console.log('Pet created:', data) }, onError: (error) => { console.error('Failed to create pet:', error) }, }) // Execute mutation await createPet.mutateAsync({ data: { name: 'Fluffy', species: 'cat' }, }) ``` ### Mutation With Path Parameters ```typescript import { api } from './api/init' // Mutation with path parameters const updatePet = api.updatePet.useMutation({ petId: '123' }) // Execute mutation await updatePet.mutateAsync({ data: { name: 'Updated Fluffy' }, }) ``` #### Deferred Path Parameters If path parameters aren't available at hook creation time, you can omit them and provide them later when calling `mutateAsync`: ```typescript import { api } from './api/init' // Create mutation without path parameters (isEnabled is initially false) const updatePet = api.updatePet.useMutation() // Later, when you have the petId: await updatePet.mutateAsync({ data: { name: 'Updated Fluffy' }, pathParams: { petId: '789' }, }) ``` You can also pass mutation options while deferring path params: ```typescript const updatePet = api.updatePet.useMutation(undefined, { invalidateOperations: { listPets: {} }, }) // ...later: await updatePet.mutateAsync({ data: { name: 'Updated' }, pathParams: { petId: '789' }, }) ``` This is useful when: - Path parameters are loaded asynchronously (e.g., from user selection) - The same mutation component is reused across different items - You need to dynamically provide IDs at execution time ### Mutation With Query Parameters ```typescript import { api } from './api/init' // Mutation with query parameters const createPet = api.createPet.useMutation( {}, { queryParams: { userId: '456' }, }, ) // Execute mutation await createPet.mutateAsync({ data: { name: 'Fluffy' }, }) ``` ## Mutation Methods The mutation object provides two methods for executing the mutation: ### `mutate` Executes mutation imperatively (fire and forget): ```typescript const createPet = api.createPet.useMutation() createPet.mutate({ data: { name: 'Fluffy' }, }) // Doesn't return promise, continues immediately ``` ### `mutateAsync` Executes mutation and returns a promise: ```typescript const createPet = api.createPet.useMutation() try { const result = await createPet.mutateAsync({ data: { name: 'Fluffy' }, }) console.log('Created:', result) } catch (error) { console.error('Error:', error) } ``` ## Axios Configuration The `axiosOptions` parameter lets you pass custom Axios configuration options to mutations. This is useful for authentication headers, timeout settings, request/response transformations, upload progress tracking, and more. ### Custom Headers ```typescript const createPet = api.createPet.useMutation( {}, { axiosOptions: { headers: { 'X-Custom-Header': 'custom-value', Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', }, }, }, ) ``` ### Timeout Configuration ```typescript const createPet = api.createPet.useMutation( {}, { axiosOptions: { timeout: 10000, // 10 second timeout for mutations }, }, ) ``` ### Upload Progress Tracking ```typescript const uploadProgress = ref(0) const uploadMutation = api.uploadFile.useMutation( {}, { axiosOptions: { onUploadProgress: (progressEvent) => { if (progressEvent.total) { uploadProgress.value = Math.round((progressEvent.loaded * 100) / progressEvent.total) } }, }, }, ) // In template ``` ### Request/Response Transformation ```typescript const createPet = api.createPet.useMutation( {}, { axiosOptions: { transformRequest: [ (data) => { // Apply custom transformation before sending return JSON.stringify({ ...data, timestamp: Date.now() }) }, ], }, }, ) ``` ### Overriding Options at Call Time You can override axios options when calling `mutate()` or `mutateAsync()`: ```typescript const createPet = api.createPet.useMutation( {}, { axiosOptions: { timeout: 5000, headers: { 'X-Setup-Header': 'setup-value' }, }, }, ) // Override options at call time await createPet.mutateAsync({ data: { name: 'Fluffy' }, axiosOptions: { timeout: 10000, // Overrides the 5000 timeout headers: { 'X-Call-Header': 'call-value', // Additional headers Authorization: 'Bearer new-token', // Override specific header }, }, }) ``` ### Custom Properties The `AxiosRequestConfigExtended` type supports arbitrary custom properties beyond standard Axios options: ```typescript const createPet = api.createPet.useMutation( {}, { axiosOptions: { // Standard Axios options timeout: 5000, headers: { 'X-Custom-Header': 'value' }, // Custom properties for interceptors or middleware manualErrorHandling: true, handledByAxios: false, customRetryCount: 3, requestMetadata: { requestId: 'req-123', source: 'create-pet', }, }, }, ) ``` ### Common Axios Options for Mutations | Option | Type | Description | | -------------------- | ------------- | -------------------------------------------------- | | `headers` | `object` | Custom request headers (useful for auth tokens) | | `timeout` | `number` | Request timeout in milliseconds | | `baseURL` | `string` | Override base URL for this request | | `auth` | `object` | Basic authentication `{ username, password }` | | `withCredentials` | `boolean` | Include cookies in cross-origin requests | | `params` | `object` | URL parameters (merged with queryParams) | | `onUploadProgress` | `function` | Upload progress callback (useful for file uploads) | | `onDownloadProgress` | `function` | Download progress callback | | `signal` | `AbortSignal` | Request cancellation | | `transformRequest` | `array` | Request data transformers | | `transformResponse` | `array` | Response data transformers | For more advanced Axios configuration patterns and examples, see [Axios Configuration guide](./08-axios-configuration.md). ## Mutation Options ### Success Handler ```typescript const createPet = api.createPet.useMutation( {}, { onSuccess: (data, variables, context) => { console.log('Success!', data) // Response data console.log('Variables:', variables) // Input parameters console.log('Context:', context) // Mutation context }, }, ) ``` ### Error Handler ```typescript const createPet = api.createPet.useMutation( {}, { onError: (error, variables, context) => { console.error('Error:', error) console.error('Variables:', variables) // Show user-friendly error message alert(`Failed to create pet: ${error.message}`) }, }, ) ``` ### Settled Handler ```typescript const createPet = api.createPet.useMutation( {}, { onSettled: (data, error, variables, context) => { console.log('Mutation completed') // Always runs, regardless of success or error }, }, ) ``` ### Optimistic Updates ```typescript import { useQueryClient } from '@tanstack/vue-query' import { api } from './api/init' const queryClient = useQueryClient() const petQuery = api.getPet.useQuery({ petId: '123' }) const updatePet = api.updatePet.useMutation( { petId: '123' }, { onMutate: async (variables) => { // Cancel outgoing queries await queryClient.cancelQueries({ queryKey: petQuery.queryKey.value }) // Snapshot previous value const previousPet = queryClient.getQueryData(petQuery.queryKey.value) // Optimistically update queryClient.setQueryData(petQuery.queryKey.value, variables.data) // Return context with snapshot return { previousPet } }, onError: (error, variables, context) => { // Rollback on error if (context?.previousPet) { queryClient.setQueryData(petQuery.queryKey.value, context.previousPet) } }, }, ) ``` ## Mutation State The mutation object provides reactive state properties: ```typescript const createPet = api.createPet.useMutation() console.log(createPet.isPending.value) // true while mutation is in progress console.log(createPet.isSuccess.value) // true if mutation succeeded console.log(createPet.isError.value) // true if mutation failed console.log(createPet.error.value) // Error object if mutation failed console.log(createPet.data.value) // Response data if mutation succeeded ``` ## Automatic Cache Management By default, mutations automatically: 1. **Update cache** - Insert returned data into the cache 2. **Invalidate queries** - Mark matching queries as stale to trigger refetch 3. **Invalidate list queries** - Automatically invalidate list endpoints ```typescript // Automatic cache management (default) const createPet = api.createPet.useMutation({ petId: '123' }) // This will: // 1. Update cache for getPet/123 with returned data // 2. Invalidate getPet/123 query // 3. Invalidate listPets query (detected as related list) ``` ## Manual Cache Control ### Disable Automatic Invalidation ```typescript const updatePet = api.updatePet.useMutation( { petId: '123' }, { dontInvalidate: true, // Don't auto-invalidate dontUpdateCache: true, // Don't auto-update cache }, ) ``` ### Specify Operations to Invalidate ```typescript const createPet = api.createPet.useMutation( { petId: '123' }, { invalidateOperations: ['listPets', 'getUserPets'], }, ) ``` ### Manually Refetch Endpoints ```typescript const petListQuery = api.listPets.useQuery() const createPet = api.createPet.useMutation( { petId: '123' }, { refetchEndpoints: [petListQuery], // Refetch these endpoints }, ) ``` ## Common Mutation Patterns ### Form Submission ```vue ``` ### Delete with Confirmation ```vue ``` ### Update with Optimistic UI ```vue ``` ## What's Next? - [Reactive Parameters](./04-reactive-parameters.md) - Learn about reactive query and path parameters - [Cache Management](./06-cache-management.md) - Learn about advanced cache control strategies