/* eslint-disable @typescript-eslint/no-explicit-any */ import { useInfiniteQuery, useMutation, useQuery, useQueryClient, type MutateFunction, type QueryClient, type UseInfiniteQueryOptions, type UseMutationOptions, type UseQueryOptions, } from '@tanstack/react-query'; import { createContext } from 'react'; import { DEFAULT_QUERY_ENDPOINT, FetchFn, QUERY_KEY_PREFIX, fetcher, makeUrl, marshal, type APIContext, } from './common'; /** * Context for configuring react hooks. */ export const RequestHandlerContext = createContext({ endpoint: DEFAULT_QUERY_ENDPOINT, fetch: undefined, }); /** * Context provider. */ export const Provider = RequestHandlerContext.Provider; /** * Creates a react-query query. * * @param model The name of the model under query. * @param url The request URL. * @param args The request args object, URL-encoded and appended as "?q=" parameter * @param options The react-query options object * @returns useQuery hook */ export function query(model: string, url: string, args?: unknown, options?: UseQueryOptions, fetch?: FetchFn) { const reqUrl = makeUrl(url, args); return useQuery({ queryKey: [QUERY_KEY_PREFIX + model, url, args], queryFn: () => fetcher(reqUrl, undefined, fetch, false), ...options, }); } /** * Creates a react-query infinite query. * * @param model The name of the model under query. * @param url The request URL. * @param args The initial request args object, URL-encoded and appended as "?q=" parameter * @param options The react-query infinite query options object * @returns useInfiniteQuery hook */ export function infiniteQuery( model: string, url: string, args?: unknown, options?: UseInfiniteQueryOptions, fetch?: FetchFn ) { return useInfiniteQuery({ queryKey: [QUERY_KEY_PREFIX + model, url, args], queryFn: ({ pageParam }) => { return fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false); }, ...options, }); } /** * Creates a POST mutation with react-query. * * @param model The name of the model under mutation. * @param url The request URL. * @param options The react-query options. * @param invalidateQueries Whether to invalidate queries after mutation. * @returns useMutation hooks */ export function postMutation( model: string, url: string, options?: Omit, 'mutationFn'>, fetch?: FetchFn, invalidateQueries = true, checkReadBack?: C ) { const queryClient = useQueryClient(); const mutationFn = (data: any) => fetcher( url, { method: 'POST', headers: { 'content-type': 'application/json', }, body: marshal(data), }, fetch, checkReadBack ) as Promise; const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); const mutation = useMutation(finalOptions); return mutation; } /** * Creates a PUT mutation with react-query. * * @param model The name of the model under mutation. * @param url The request URL. * @param options The react-query options. * @param invalidateQueries Whether to invalidate queries after mutation. * @returns useMutation hooks */ export function putMutation( model: string, url: string, options?: Omit, 'mutationFn'>, fetch?: FetchFn, invalidateQueries = true, checkReadBack?: C ) { const queryClient = useQueryClient(); const mutationFn = (data: any) => fetcher( url, { method: 'PUT', headers: { 'content-type': 'application/json', }, body: marshal(data), }, fetch, checkReadBack ) as Promise; const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); const mutation = useMutation(finalOptions); return mutation; } /** * Creates a DELETE mutation with react-query. * * @param model The name of the model under mutation. * @param url The request URL. * @param options The react-query options. * @param invalidateQueries Whether to invalidate queries after mutation. * @returns useMutation hooks */ export function deleteMutation( model: string, url: string, options?: Omit, 'mutationFn'>, fetch?: FetchFn, invalidateQueries = true, checkReadBack?: C ) { const queryClient = useQueryClient(); const mutationFn = (data: any) => fetcher( makeUrl(url, data), { method: 'DELETE', }, fetch, checkReadBack ) as Promise; const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); const mutation = useMutation(finalOptions); return mutation; } function mergeOptions( model: string, options: Omit, 'mutationFn'> | undefined, invalidateQueries: boolean, mutationFn: MutateFunction, queryClient: QueryClient ): UseMutationOptions { const result = { ...options, mutationFn }; if (options?.onSuccess || invalidateQueries) { result.onSuccess = (...args) => { if (invalidateQueries) { queryClient.invalidateQueries([QUERY_KEY_PREFIX + model]); } return options?.onSuccess?.(...args); }; } return result; }