2024-03-07 04:16:54 -08:00
|
|
|
import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
|
2024-03-08 04:38:11 -08:00
|
|
|
import { useAppSelector } from "../state/hooks";
|
2024-02-21 02:13:48 -08:00
|
|
|
|
|
|
|
export const API_PATH = "api/v1";
|
|
|
|
|
2024-03-07 04:16:54 -08:00
|
|
|
export type SuccessAPIResponse<T> = {
|
|
|
|
status: "success";
|
|
|
|
data: T;
|
|
|
|
warnings?: string[];
|
|
|
|
};
|
2024-02-21 02:13:48 -08:00
|
|
|
|
2024-03-07 04:16:54 -08:00
|
|
|
export type ErrorAPIResponse = {
|
|
|
|
status: "error";
|
|
|
|
errorType: string;
|
|
|
|
error: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type APIResponse<T> = SuccessAPIResponse<T> | ErrorAPIResponse;
|
|
|
|
|
2024-03-07 12:00:43 -08:00
|
|
|
const createQueryFn =
|
2024-03-08 04:38:11 -08:00
|
|
|
<T>({
|
|
|
|
pathPrefix,
|
|
|
|
path,
|
|
|
|
params,
|
|
|
|
}: {
|
|
|
|
pathPrefix: string;
|
|
|
|
path: string;
|
|
|
|
params?: Record<string, string>;
|
|
|
|
}) =>
|
2024-03-07 12:00:43 -08:00
|
|
|
async ({ signal }: { signal: AbortSignal }) => {
|
|
|
|
const queryString = params
|
|
|
|
? `?${new URLSearchParams(params).toString()}`
|
|
|
|
: "";
|
|
|
|
|
|
|
|
try {
|
2024-03-08 04:38:11 -08:00
|
|
|
const res = await fetch(
|
|
|
|
`${pathPrefix}/${API_PATH}${path}${queryString}`,
|
|
|
|
{
|
|
|
|
cache: "no-store",
|
|
|
|
credentials: "same-origin",
|
|
|
|
signal,
|
|
|
|
}
|
|
|
|
);
|
2024-03-07 12:00:43 -08:00
|
|
|
|
|
|
|
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<T>;
|
|
|
|
|
|
|
|
if (apiRes.status === "error") {
|
|
|
|
throw new Error(
|
|
|
|
apiRes.error !== undefined
|
|
|
|
? apiRes.error
|
|
|
|
: 'missing "error" field in response JSON'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return apiRes as SuccessAPIResponse<T>;
|
|
|
|
} 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 = {
|
2024-03-07 04:16:54 -08:00
|
|
|
key?: string;
|
|
|
|
path: string;
|
|
|
|
params?: Record<string, string>;
|
|
|
|
enabled?: boolean;
|
2024-03-07 12:00:43 -08:00
|
|
|
};
|
|
|
|
|
2024-03-08 04:38:11 -08:00
|
|
|
export const useAPIQuery = <T>({
|
|
|
|
key,
|
|
|
|
path,
|
|
|
|
params,
|
|
|
|
enabled,
|
|
|
|
}: QueryOptions) => {
|
|
|
|
const pathPrefix = useAppSelector((state) => state.settings.pathPrefix);
|
|
|
|
|
|
|
|
return useQuery<SuccessAPIResponse<T>>({
|
2024-03-07 12:00:43 -08:00
|
|
|
queryKey: key ? [key] : [path, params],
|
2024-03-07 04:16:54 -08:00
|
|
|
retry: false,
|
|
|
|
refetchOnWindowFocus: false,
|
|
|
|
gcTime: 0,
|
|
|
|
enabled,
|
2024-03-08 04:38:11 -08:00
|
|
|
queryFn: createQueryFn({ pathPrefix, path, params }),
|
2024-03-07 04:16:54 -08:00
|
|
|
});
|
2024-03-08 04:38:11 -08:00
|
|
|
};
|
2024-03-07 04:16:54 -08:00
|
|
|
|
2024-03-08 04:38:11 -08:00
|
|
|
export const useSuspenseAPIQuery = <T>({ key, path, params }: QueryOptions) => {
|
|
|
|
const pathPrefix = useAppSelector((state) => state.settings.pathPrefix);
|
|
|
|
|
|
|
|
return useSuspenseQuery<SuccessAPIResponse<T>>({
|
2024-03-07 12:00:43 -08:00
|
|
|
queryKey: key ? [key] : [path, params],
|
2024-02-21 02:13:48 -08:00
|
|
|
retry: false,
|
|
|
|
refetchOnWindowFocus: false,
|
|
|
|
gcTime: 0,
|
2024-03-08 04:38:11 -08:00
|
|
|
queryFn: createQueryFn({ pathPrefix, path, params }),
|
2024-02-21 02:13:48 -08:00
|
|
|
});
|
2024-03-08 04:38:11 -08:00
|
|
|
};
|