useTrackedMutation.ts
1 // Copyright (c) 2026 VPL Solutions. All rights reserved. 2 // Licensed under the MIT License. See LICENSE for details. 3 4 import { useMutation, type UseMutationOptions } from '@tanstack/react-query'; 5 import { useDiagnostics } from './useDiagnosticsHook'; 6 7 interface TrackingMeta { 8 service: string; 9 operation: string; 10 } 11 12 /** 13 * Wraps useMutation to automatically record calls to the Diagnostics panel. 14 * Expects the response to have optional `elapsed_ms` and `request_id` fields. 15 */ 16 export function useTrackedMutation<TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown>( 17 meta: TrackingMeta, 18 options: UseMutationOptions<TData, TError, TVariables, TOnMutateResult>, 19 ) { 20 const { recordCall } = useDiagnostics(); 21 const start = { current: 0 }; 22 23 return useMutation<TData, TError, TVariables, TOnMutateResult>({ 24 ...options, 25 onMutate: (variables, context) => { 26 start.current = performance.now(); 27 return options.onMutate?.(variables, context) as TOnMutateResult | Promise<TOnMutateResult>; 28 }, 29 onSuccess: (data, variables, onMutateResult, context) => { 30 const elapsed = Math.round(performance.now() - start.current); 31 const resp = data as Record<string, unknown> | null; 32 recordCall({ 33 service: meta.service, 34 operation: meta.operation, 35 status: 'ok', 36 latencyMs: (resp?.elapsed_ms as number) ?? elapsed, 37 requestId: resp?.request_id as string | undefined, 38 }); 39 return options.onSuccess?.(data, variables, onMutateResult, context); 40 }, 41 onError: (error, variables, onMutateResult, context) => { 42 const elapsed = Math.round(performance.now() - start.current); 43 recordCall({ 44 service: meta.service, 45 operation: meta.operation, 46 status: 'error', 47 latencyMs: elapsed, 48 }); 49 return options.onError?.(error, variables, onMutateResult, context); 50 }, 51 }); 52 }