import { unstable_serialize } from 'swr' 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 }> } const serializeQueryKey = (queryKey: QueryKey) => unstable_serialize(queryKey) const queryCache = new Map() const setCachedData = (queryKey: QueryKey, data: T | undefined) => { const cacheKey = serializeQueryKey(queryKey) if (data === undefined) { queryCache.delete(cacheKey) } else { queryCache.set(cacheKey, data) } } export const swrConfig: SWRConfiguration = { dedupingInterval: 2000, errorRetryCount: 3, errorRetryInterval: 5000, revalidateOnFocus: false, } export const getCacheData = (queryKey: QueryKey): T | undefined => { return queryCache.get(serializeQueryKey(queryKey)) as T | undefined } const updateCachedData = ( queryKey: QueryKey, updaterOrData: QueryDataUpdater, ) => { const current = getCacheData(queryKey) const next = typeof updaterOrData === 'function' ? (updaterOrData as (current: T | undefined) => T | undefined)(current) : updaterOrData setCachedData(queryKey, next) return next } export const setCacheData = ( queryKey: QueryKey, updaterOrData: QueryDataUpdater, ) => { const next = updateCachedData(queryKey, updaterOrData) void swrMutate(queryKey, next, { populateCache: true, revalidate: false, }) return next } export const setCacheDataAsync = async ( queryKey: QueryKey, updaterOrData: QueryDataUpdater, ) => { const next = updateCachedData(queryKey, updaterOrData) await swrMutate(queryKey, next, { populateCache: true, revalidate: false, }) return next } export const revalidateQuery = async (queryKey: QueryKey) => { const data = await swrMutate(queryKey) if (data !== undefined) { setCachedData(queryKey, data) } return data } export const revalidateQueries = (queryKeys: readonly QueryKey[]) => Promise.all(queryKeys.map(revalidateQuery)) export const removeCacheData = (queryKey: QueryKey) => { setCachedData(queryKey, undefined) return swrMutate(queryKey, undefined, { populateCache: true, revalidate: false, }) } export const fetchCacheData = async ( queryKey: QueryKey, queryFn: () => Promise | T, ) => { const data = await queryFn() 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 serializedKey = serializeQueryKey(queryKey) if (enabled && fallbackData !== undefined && !queryCache.has(serializedKey)) { setCachedData(queryKey, fallbackData) } 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, onSuccess: (data) => { setCachedData(queryKey, data) }, }) return { ...swr, isFetching: swr.isValidating, isPending: swr.isLoading, refetch: async () => { const data = await swr.mutate() if (data !== undefined) { setCachedData(queryKey, data) } return { data } }, } }