import crypto from 'node:crypto'; type FetchInput = Parameters[0]; type FetchInit = Parameters[1]; const requestsCache: Map> = new Map(); export const reusableFetch = async (input: FetchInput, init?: FetchInit): Promise<{ data: T; headers: Headers }> => { const response = await fetch(input, init); if (!response.ok) { throw new Error(`HTTP ${response.status} ${response.statusText}`); } const data = (await response.json()) as T; const headers = response.headers; return { data, headers }; }; export const cacheableFetch = async (input: FetchInput, init?: FetchInit) => { const cacheKeyData = JSON.stringify({ input, init }); const cacheKey = crypto.createHash('sha256').update(cacheKeyData).digest('hex'); const cachedResponse = requestsCache.get(cacheKey); if (cachedResponse) { const { data, headers } = await cachedResponse; const expiresAt = expiresHeaderToUnixtime(headers.get('Expires')); if (expiresAt > Date.now()) { return data as T; } requestsCache.delete(cacheKey); } try { const newRequest = reusableFetch(input, init); requestsCache.set(cacheKey, newRequest); const result = await newRequest; return result.data as T; } catch (err) { requestsCache.delete(cacheKey); throw err; } }; const expiresHeaderToUnixtime = (expires: string | null): number => { return expires ? new Date(expires).getTime() : Date.now() + 60_000; };