import useSWR, { type SWRConfiguration, type SWRResponse, mutate as swrMutate, } from 'swr' type QueryKey = string | readonly unknown[] type QueryDataUpdater = | T | undefined | ((current: T | undefined) => T | undefined) type QueryOptions = { queryKey: QueryKey queryFn: () => Promise | T enabled?: boolean initialData?: T | (() => T | undefined) placeholderData?: T | (() => T | undefined) staleTime?: number retry?: number | false retryDelay?: number | ((attempt: number) => number) refetchInterval?: number | false refetchIntervalInBackground?: boolean revalidateOnMount?: boolean refetchOnWindowFocus?: boolean refetchOnReconnect?: boolean } type QueryResult = SWRResponse & { isFetching: boolean isPending: boolean refetch: () => Promise<{ data: T | undefined }> } export const swrConfig: SWRConfiguration = { dedupingInterval: 2000, errorRetryCount: 3, errorRetryInterval: 5000, revalidateOnFocus: false, } export const setCacheData = ( queryKey: QueryKey, updaterOrData: QueryDataUpdater, ) => swrMutate(queryKey, updaterOrData, { populateCache: true, revalidate: false, }) export const revalidateQuery = (queryKey: QueryKey) => swrMutate(queryKey) export const revalidateQueries = (queryKeys: readonly QueryKey[]) => Promise.all(queryKeys.map(revalidateQuery)) export const removeCacheData = (queryKey: QueryKey) => setCacheData(queryKey, undefined) export const fetchCacheData = async ( queryKey: QueryKey, queryFn: () => Promise | T, ) => { const data = await queryFn() await setCacheData(queryKey, data) return data } export function useQuery(options: QueryOptions): QueryResult { const { queryKey, queryFn, enabled = true, initialData, placeholderData, retry, retryDelay, refetchInterval, refetchIntervalInBackground, revalidateOnMount, refetchOnWindowFocus, refetchOnReconnect, staleTime, } = options const fallbackDataSource = initialData ?? placeholderData const fallbackData = typeof fallbackDataSource === 'function' ? (fallbackDataSource as () => T | undefined)() : fallbackDataSource const swr = useSWR(enabled ? queryKey : null, queryFn, { dedupingInterval: staleTime, errorRetryCount: retry === false ? 0 : retry, errorRetryInterval: typeof retryDelay === 'number' ? retryDelay : swrConfig.errorRetryInterval, fallbackData, keepPreviousData: placeholderData !== undefined, onErrorRetry: (_error, _key, config, revalidate, { retryCount }) => { const maxRetries = config.errorRetryCount if (maxRetries !== undefined && retryCount > maxRetries) return const interval = typeof retryDelay === 'function' ? retryDelay(Math.max(retryCount - 1, 0)) : config.errorRetryInterval setTimeout(() => { revalidate({ retryCount, dedupe: true }) }, interval) }, revalidateOnFocus: refetchOnWindowFocus, revalidateOnMount, revalidateOnReconnect: refetchOnReconnect, refreshInterval: refetchInterval || 0, refreshWhenHidden: refetchIntervalInBackground ?? false, }) return { ...swr, isFetching: swr.isValidating, isPending: swr.isLoading, refetch: async () => ({ data: await swr.mutate() }), } }