import { MutationCache, QueryCache, QueryClient, } from "@tanstack/react-query"; import { toast } from "sonner"; import { ApiError, logError, reportError, toUserMessage } from "@/lib/errors"; declare module "@tanstack/react-query" { interface Register { queryMeta: { /** Show a sonner error toast automatically on query failure. */ toastOnError?: boolean; }; mutationMeta: { /** Opt out of the default sonner error toast (caller handles feedback). */ suppressToast?: boolean; }; } } function shouldRetryQuery(failureCount: number, err: unknown): boolean { if (err instanceof ApiError) { // Retry only on transient status codes; 4xx generally won't succeed on retry. if (err.status === 408 || err.status === 429) return failureCount < 2; if (err.status >= 400 && err.status < 500) return false; } return failureCount < 2; } export function createQueryClient(): QueryClient { return new QueryClient({ defaultOptions: { queries: { retry: shouldRetryQuery, refetchOnWindowFocus: false, }, mutations: { // Mutations have side effects — never auto-retry. retry: 0, }, }, queryCache: new QueryCache({ onError: (err, query) => { logError(err, { scope: "query", queryKey: query.queryKey }); if (query.meta?.toastOnError) { toast.error(toUserMessage(err)); } }, }), mutationCache: new MutationCache({ onError: (err, _variables, _context, mutation) => { reportError(err, { scope: "mutation", mutationKey: mutation.options.mutationKey, }); if (mutation.meta?.suppressToast) return; toast.error(toUserMessage(err)); }, }), }); }