Files
markone/packages/web/src/api.ts

90 lines
2.1 KiB
TypeScript

import { type DefaultError, type UseMutationOptions, type queryOptions, useQuery } from "@tanstack/react-query"
import { useNavigate } from "@tanstack/react-router"
import { useEffect } from "react"
enum ApiErrorCode {
BadRequestBody = "BadRequestBody",
LinkUnreachable = "LinkUnreachable",
UnsupportedWebsite = "UnsupportedWebsite",
}
class BadRequestError extends Error {
constructor(
public readonly code: ApiErrorCode,
public readonly message: string = "Server returned status 400",
) {
super()
}
}
class InternalError extends Error {}
class UnauthenticatedError extends Error {}
class NotFoundError extends Error {}
interface ErrorBody {
code: string
message?: string
}
type QueryKey =
| ["bookmarks" | "tags", ...ReadonlyArray<unknown>]
| ["bookmarks" | "tags", string, ...ReadonlyArray<unknown>]
async function fetchApi(route: string, init?: RequestInit): Promise<Response> {
const response = await fetch(`${import.meta.env.VITE_API_URL}/api${route}`, {
credentials: "include",
...init,
})
switch (response.status) {
case 200:
case 204:
return response
case 400: {
const body: ErrorBody = await response.json()
throw new BadRequestError(body.code as ApiErrorCode, body.message)
}
case 401: {
throw new UnauthenticatedError()
}
case 404:
throw new NotFoundError()
default:
throw new InternalError()
}
}
function useAuthenticatedQuery<TData>(queryKey: QueryKey, fn: () => Promise<TData>) {
const query = useQuery({
queryKey,
queryFn: fn,
retry: false,
})
const navigate = useNavigate()
useEffect(() => {
if (query.error && query.error instanceof UnauthenticatedError) {
navigate({ to: "/login", replace: true })
}
}, [query.error, navigate])
return query
}
function mutationOptions<TData = unknown, TError = DefaultError, TVariables = void, TContext = unknown>(
options: UseMutationOptions<TData, TError, TVariables, TContext>,
): UseMutationOptions<TData, TError, TVariables, TContext> {
return options
}
export {
ApiErrorCode,
BadRequestError,
InternalError,
UnauthenticatedError,
NotFoundError,
fetchApi,
useAuthenticatedQuery,
mutationOptions,
}
export type { QueryKey }