UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

102 lines (100 loc) 4.02 kB
const require_runtime = require('./_virtual/_rolldown/runtime.cjs'); let effect = require("effect"); //#region src/http-client.ts var FetchNetworkError = class extends effect.Data.TaggedError("FetchNetworkError") {}; var FetchTimeoutError = class extends effect.Data.TaggedError("FetchTimeoutError") {}; var FetchHttpError = class extends effect.Data.TaggedError("FetchHttpError") {}; const DEFAULT_TIMEOUT = "10 seconds"; const DEFAULT_RETRIES = 3; const EXPONENTIAL_BASE = effect.Duration.seconds(1); const EXPONENTIAL_CAP = effect.Duration.seconds(15); const isRetryable = (error) => { if (error instanceof FetchNetworkError) return true; if (error instanceof FetchTimeoutError) return true; if (error instanceof FetchHttpError) return error.status >= 500; return false; }; const fetchRawEff = (url, options) => effect.Effect.tryPromise({ try: async () => { const timeoutMs = effect.Duration.toMillis(effect.Duration.decode(options?.timeout ?? DEFAULT_TIMEOUT)); const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); try { return await fetch(url, { method: options?.method ?? "GET", headers: options?.headers, body: options?.body, redirect: options?.redirect, signal: controller.signal }); } catch (error) { if (error instanceof Error && error.name === "AbortError") throw new FetchTimeoutError({ url }); throw new FetchNetworkError({ url, cause: error }); } finally { clearTimeout(timer); } }, catch: (error) => { if (error instanceof FetchNetworkError || error instanceof FetchTimeoutError) return error; return new FetchNetworkError({ url, cause: error }); } }); const fetchEff = (url, options) => fetchRawEff(url, options).pipe(effect.Effect.flatMap((response) => { if (response.ok) return effect.Effect.succeed(response); return effect.Effect.fail(new FetchHttpError({ url, status: response.status, statusText: response.statusText })); })); const retrySchedule = effect.Schedule.exponential(EXPONENTIAL_BASE).pipe(effect.Schedule.upTo(EXPONENTIAL_CAP), effect.Schedule.intersect(effect.Schedule.recurs(DEFAULT_RETRIES))); const fetchWithRetryEff = (url, options) => { const retries = options?.retries ?? DEFAULT_RETRIES; if (retries <= 0) return fetchEff(url, options); const schedule = options?.retries !== void 0 ? effect.Schedule.exponential(EXPONENTIAL_BASE).pipe(effect.Schedule.upTo(EXPONENTIAL_CAP), effect.Schedule.intersect(effect.Schedule.recurs(retries))) : retrySchedule; return fetchEff(url, options).pipe(effect.Effect.retry({ schedule, while: isRetryable })); }; const getCache = /* @__PURE__ */ new Map(); const GET_CACHE_TTL_MS = 3e4; function isCacheable(_url, options) { if (options?.method && options.method !== "GET") return false; if (options?.body) return false; return true; } const fetchResponse = (url, options) => effect.Effect.runPromise(fetchRawEff(url, options)); const fetchJsonOrNull = async (url, options) => { if (isCacheable(url, options)) { const cached = getCache.get(url); if (cached && cached.expiresAt > Date.now()) return cached.data; } const retries = options?.retries ?? DEFAULT_RETRIES; const eff = retries > 0 ? fetchWithRetryEff(url, { ...options, retries }) : fetchEff(url, options); try { const data = await (await effect.Effect.runPromise(eff)).json(); if (isCacheable(url, options)) getCache.set(url, { data, expiresAt: Date.now() + GET_CACHE_TTL_MS }); return data; } catch (error) { const msg = error instanceof FetchNetworkError ? `[http] Network error: ${error.url}${String(error.cause)}` : error instanceof FetchTimeoutError ? `[http] Timeout: ${error.url}` : error instanceof FetchHttpError ? `[http] HTTP ${error.status}: ${error.url}` : `[http] Unknown error while fetching ${url}`; console.error(msg); return null; } }; //#endregion exports.fetchJsonOrNull = fetchJsonOrNull; exports.fetchResponse = fetchResponse; //# sourceMappingURL=http-client.cjs.map