import type { Api } from "Api"; import { FetchPromise, type ModuleApi, type ResponseObject } from "modules/ModuleApi"; import useSWR, { type SWRConfiguration, useSWRConfig, type Middleware, type SWRHook } from "swr"; import useSWRInfinite from "swr/infinite"; import { useApi } from "../ClientProvider"; import { useState } from "react"; export const useApiQuery = < Data, RefineFn extends (data: ResponseObject) => unknown = (data: ResponseObject) => Data, >( fn: (api: Api) => FetchPromise, options?: SWRConfiguration & { enabled?: boolean; refine?: RefineFn }, ) => { const api = useApi(); const promise = fn(api); const refine = options?.refine ?? ((data: any) => data); const fetcher = () => promise.execute().then(refine); const key = promise.key(); type RefinedData = RefineFn extends (data: ResponseObject) => infer R ? R : Data; const swr = useSWR(options?.enabled === false ? null : key, fetcher, options); return { ...swr, promise, key, api, }; }; /** @attention: highly experimental, use with caution! */ export const useApiInfiniteQuery = < Data, RefineFn extends (data: ResponseObject) => unknown = (data: ResponseObject) => Data, >( fn: (api: Api, page: number) => FetchPromise, options?: SWRConfiguration & { refine?: RefineFn; pageSize?: number }, ) => { const [endReached, setEndReached] = useState(false); const api = useApi(); const promise = (page: number) => fn(api, page); const refine = options?.refine ?? ((data: any) => data); type RefinedData = RefineFn extends (data: ResponseObject) => infer R ? R : Data; // @ts-ignore const swr = useSWRInfinite( (index, previousPageData: any) => { if (index > 0 && previousPageData && previousPageData.length < (options?.pageSize ?? 0)) { setEndReached(true); return null; // reached the end } return promise(index).request.url; }, (url: string) => { return new FetchPromise(new Request(url), { fetcher: api.fetcher }, refine).execute(); }, { revalidateFirstPage: false, }, ); // @ts-ignore const data = swr.data ? [].concat(...swr.data) : []; return { ...swr, _data: swr.data, data, endReached, promise: promise(swr.size), key: promise(swr.size).key(), api, }; }; export const useInvalidate = (options?: { exact?: boolean }) => { const mutate = useSWRConfig().mutate; const api = useApi(); return async (arg?: string | ((api: Api) => FetchPromise | ModuleApi)) => { let key = ""; if (typeof arg === "string") { key = arg; } else if (typeof arg === "function") { key = arg(api).key(); } if (options?.exact) return mutate(key); return mutate((k) => typeof k === "string" && k.startsWith(key)); }; }; const mountOnceCache = new Map(); /** * Simple middleware to only load on first mount. */ export const mountOnce: Middleware = (useSWRNext: SWRHook) => (key, fetcher, config) => { if (typeof key === "string") { if (mountOnceCache.has(key)) { return useSWRNext(key, fetcher, { ...config, revalidateOnMount: false, }); } const swr = useSWRNext(key, fetcher, config); if (swr.data) { mountOnceCache.set(key, true); } return swr; } return useSWRNext(key, fetcher, config); };