/* * ============================================================================= * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform * ----------------------------------------------------------------------------- * File: client/src/lib/queryClient.ts * * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * Website: https://www.spboucher.ai * Demo: https://www.vquant.ai * License: MIT (see LICENSE) * * Copyright © 2026 Simon-Pierre Boucher. All rights reserved. * ============================================================================= */ import { QueryClient, type QueryFunction } from "@tanstack/react-query"; async function throwIfResNotOk(res: Response) { if (!res.ok) { const text = (await res.text()) || res.statusText; throw new Error(`${res.status}: ${text}`); } } export async function apiRequest( method: string, url: string, data?: unknown, ): Promise { const res = await fetch(url, { method, headers: data ? { "Content-Type": "application/json" } : {}, body: data ? JSON.stringify(data) : undefined, credentials: "include", }); await throwIfResNotOk(res); return res; } type UnauthorizedBehavior = "returnNull" | "throw"; export const getQueryFn: (options: { on401: UnauthorizedBehavior; }) => QueryFunction = ({ on401: unauthorizedBehavior }) => async ({ queryKey }) => { const res = await fetch(queryKey.join("/") as string, { credentials: "include", }); if (unauthorizedBehavior === "returnNull" && res.status === 401) { return null; } await throwIfResNotOk(res); return await res.json(); }; export const queryClient = new QueryClient({ defaultOptions: { queries: { queryFn: getQueryFn({ on401: "returnNull" }), refetchOnWindowFocus: false, staleTime: 5 * 60 * 1000, // 5 minutes — reasonable for financial data gcTime: 30 * 60 * 1000, // 30 minutes garbage collection retry: 1, // 1 retry on transient failures }, mutations: { retry: false, }, }, });