mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-24 04:32:07 +08:00
197 lines
5.1 KiB
TypeScript
197 lines
5.1 KiB
TypeScript
import { unstable_serialize } from 'swr'
|
|
import useSWR, {
|
|
type SWRConfiguration,
|
|
type SWRResponse,
|
|
mutate as swrMutate,
|
|
} from 'swr'
|
|
|
|
type QueryKey = string | readonly unknown[]
|
|
type QueryDataUpdater<T> =
|
|
| T
|
|
| undefined
|
|
| ((current: T | undefined) => T | undefined)
|
|
|
|
type QueryOptions<T> = {
|
|
queryKey: QueryKey
|
|
queryFn: () => Promise<T> | 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<T> = SWRResponse<T> & {
|
|
isFetching: boolean
|
|
isPending: boolean
|
|
refetch: () => Promise<{ data: T | undefined }>
|
|
}
|
|
|
|
const serializeQueryKey = (queryKey: QueryKey) => unstable_serialize(queryKey)
|
|
|
|
const queryCache = new Map<string, unknown>()
|
|
|
|
const setCachedData = <T>(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 = <T>(queryKey: QueryKey): T | undefined => {
|
|
return queryCache.get(serializeQueryKey(queryKey)) as T | undefined
|
|
}
|
|
|
|
const updateCachedData = <T>(
|
|
queryKey: QueryKey,
|
|
updaterOrData: QueryDataUpdater<T>,
|
|
) => {
|
|
const current = getCacheData<T>(queryKey)
|
|
const next =
|
|
typeof updaterOrData === 'function'
|
|
? (updaterOrData as (current: T | undefined) => T | undefined)(current)
|
|
: updaterOrData
|
|
setCachedData(queryKey, next)
|
|
return next
|
|
}
|
|
|
|
export const setCacheData = <T>(
|
|
queryKey: QueryKey,
|
|
updaterOrData: QueryDataUpdater<T>,
|
|
) => {
|
|
const next = updateCachedData(queryKey, updaterOrData)
|
|
void swrMutate(queryKey, next, {
|
|
populateCache: true,
|
|
revalidate: false,
|
|
})
|
|
return next
|
|
}
|
|
|
|
export const setCacheDataAsync = async <T>(
|
|
queryKey: QueryKey,
|
|
updaterOrData: QueryDataUpdater<T>,
|
|
) => {
|
|
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 <T>(
|
|
queryKey: QueryKey,
|
|
queryFn: () => Promise<T> | T,
|
|
) => {
|
|
const data = await queryFn()
|
|
setCacheData(queryKey, data)
|
|
return data
|
|
}
|
|
|
|
export function useQuery<T>(options: QueryOptions<T>): QueryResult<T> {
|
|
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<T>(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 }
|
|
},
|
|
}
|
|
}
|