import QuickLRU from '@alloc/quick-lru' import { paramsToCacheKey } from '@/utils/formatUtil' const DEFAULT_MAX_SIZE = 50 interface CachedRequestOptions { /** * Maximum number of items to store in the cache * @default 50 */ maxSize?: number /** * Function to generate a cache key from parameters */ cacheKeyFn?: (params: unknown) => string } /** * Composable that wraps a function with memoization, request deduplication, and abort handling. */ export function useCachedRequest( requestFunction: ( params: TParams, signal?: AbortSignal ) => Promise, options: CachedRequestOptions = {} ) { const { maxSize = DEFAULT_MAX_SIZE, cacheKeyFn = paramsToCacheKey } = options const cache = new QuickLRU({ maxSize }) const pendingRequests = new Map>() const abortControllers = new Map() const executeAndCacheCall = async ( params: TParams, cacheKey: string ): Promise => { try { const controller = new AbortController() abortControllers.set(cacheKey, controller) const responsePromise = requestFunction(params, controller.signal) pendingRequests.set(cacheKey, responsePromise) const result = await responsePromise cache.set(cacheKey, result) return result } catch (err) { // Set cache on error to prevent retrying bad requests cache.set(cacheKey, null) return null } finally { pendingRequests.delete(cacheKey) abortControllers.delete(cacheKey) } } const handlePendingRequest = async ( pendingRequest: Promise ): Promise => { try { return await pendingRequest } catch (err) { console.error('Error in pending request:', err) return null } } const abortAllRequests = () => { for (const controller of abortControllers.values()) { controller.abort() } } /** * Cancel and clear any pending requests */ const cancel = () => { abortAllRequests() abortControllers.clear() pendingRequests.clear() } /** * Cached version of the request function */ const call = async (params: TParams): Promise => { const cacheKey = cacheKeyFn(params) const cachedResult = cache.get(cacheKey) if (cachedResult !== undefined) return cachedResult const pendingRequest = pendingRequests.get(cacheKey) if (pendingRequest) return handlePendingRequest(pendingRequest) return executeAndCacheCall(params, cacheKey) } return { call, cancel, clear: () => cache.clear() } }