import { useQuery, useSuspenseQuery } from "@tanstack/react-query"; import { useAppSelector } from "../state/hooks"; export const API_PATH = "api/v1"; export type SuccessAPIResponse = { status: "success"; data: T; warnings?: string[]; }; export type ErrorAPIResponse = { status: "error"; errorType: string; error: string; }; export type APIResponse = SuccessAPIResponse | ErrorAPIResponse; const createQueryFn = ({ pathPrefix, path, params, }: { pathPrefix: string; path: string; params?: Record; }) => async ({ signal }: { signal: AbortSignal }) => { const queryString = params ? `?${new URLSearchParams(params).toString()}` : ""; try { const res = await fetch( `${pathPrefix}/${API_PATH}${path}${queryString}`, { cache: "no-store", credentials: "same-origin", signal, } ); if ( !res.ok && !res.headers.get("content-type")?.startsWith("application/json") ) { // For example, Prometheus may send a 503 Service Unavailable response // with a "text/plain" content type when it's starting up. But the API // may also respond with a JSON error message and the same error code. throw new Error(res.statusText); } const apiRes = (await res.json()) as APIResponse; if (apiRes.status === "error") { throw new Error( apiRes.error !== undefined ? apiRes.error : 'missing "error" field in response JSON' ); } return apiRes as SuccessAPIResponse; } catch (error) { if (!(error instanceof Error)) { throw new Error("Unknown error"); } switch (error.name) { case "TypeError": throw new Error("Network error or unable to reach the server"); case "SyntaxError": throw new Error("Invalid JSON response"); default: throw error; } } }; type QueryOptions = { key?: string; path: string; params?: Record; enabled?: boolean; }; export const useAPIQuery = ({ key, path, params, enabled, }: QueryOptions) => { const pathPrefix = useAppSelector((state) => state.settings.pathPrefix); return useQuery>({ queryKey: key ? [key] : [path, params], retry: false, refetchOnWindowFocus: false, gcTime: 0, enabled, queryFn: createQueryFn({ pathPrefix, path, params }), }); }; export const useSuspenseAPIQuery = ({ key, path, params }: QueryOptions) => { const pathPrefix = useAppSelector((state) => state.settings.pathPrefix); return useSuspenseQuery>({ queryKey: key ? [key] : [path, params], retry: false, refetchOnWindowFocus: false, gcTime: 0, queryFn: createQueryFn({ pathPrefix, path, params }), }); };