* wip * wip * wip * format * wip(mobile): lint and format * cleanup * nits * nits * idiomatic react * nit * format all root files
66 lines
2.1 KiB
TypeScript
66 lines
2.1 KiB
TypeScript
import { MutationCache, QueryCache, QueryClient } from '@tanstack/react-query';
|
|
import { toast } from 'sonner-native';
|
|
import { ApiError, logError, reportError, toUserMessage } from '@/lib/errors';
|
|
import { useAuthStore } from '@/stores/auth-store';
|
|
|
|
declare module '@tanstack/react-query' {
|
|
interface Register {
|
|
queryMeta: { toastOnError?: boolean };
|
|
mutationMeta: { 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;
|
|
}
|
|
|
|
// A 401 surfaced through react-query means the server rejected our bearer
|
|
// token. This is the *only* place that turns that into an auth state change —
|
|
// the apiClient is a dumb transport. Direct apiClient callers (signIn,
|
|
// restoreSession, signInToFirebase) handle their own 401s explicitly.
|
|
function handleUnauthorized(err: unknown): void {
|
|
if (err instanceof ApiError && err.status === 401) {
|
|
void useAuthStore.getState().invalidateSession();
|
|
}
|
|
}
|
|
|
|
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) => {
|
|
handleUnauthorized(err);
|
|
logError(err, { scope: 'query', queryKey: query.queryKey });
|
|
if (query.meta?.toastOnError) {
|
|
toast.error(toUserMessage(err));
|
|
}
|
|
},
|
|
}),
|
|
mutationCache: new MutationCache({
|
|
onError: (err, _variables, _context, mutation) => {
|
|
handleUnauthorized(err);
|
|
reportError(err, {
|
|
scope: 'mutation',
|
|
mutationKey: mutation.options.mutationKey,
|
|
});
|
|
if (mutation.meta?.suppressToast) return;
|
|
toast.error(toUserMessage(err));
|
|
},
|
|
}),
|
|
});
|
|
}
|