UNPKG

everything-dev

Version:

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

99 lines (97 loc) • 3.68 kB
import { createORPCClient, onError } from "@orpc/client"; import { RPCLink } from "@orpc/client/fetch"; import { createTanstackQueryUtils } from "@orpc/tanstack-query"; import { useRouter } from "@tanstack/react-router"; //#region src/ui/api.ts function createRpcLink(config, url, headers) { return new RPCLink({ origin: config.hostUrl, url, interceptors: [onError((error) => { if (typeof window === "undefined") return; if (error && typeof error === "object" && "message" in error) { const message = String(error.message).toLowerCase(); if (message.includes("fetch") || message.includes("network") || message.includes("failed to fetch")) import("sonner").then(({ toast }) => { const message = config.connectionError?.() ?? { title: "Unable to connect to API", description: "The API is currently unavailable. Please try again later." }; toast.error(message.title, { id: "api-connection-error", description: message.description }); }).catch(() => {}); } })], fetch(fetchUrl, options) { return fetchWithRateLimitRetry(fetchUrl, { ...options, credentials: "include", headers: headers ? { ...Object.fromEntries(headers), ...options?.headers } : options?.headers }); } }); } const RATE_LIMIT_MAX_RETRIES = 3; const RATE_LIMIT_RETRY_CAP_MS = 2e3; /** * A 429 from the host's edge limiter is transient saturation, not an * application failure — retry within the server's `Retry-After` (capped) so * queries and the session check self-heal instead of surfacing errors. */ async function fetchWithRateLimitRetry(fetchUrl, options) { let response = await fetch(fetchUrl, options); for (let attempt = 1; attempt <= RATE_LIMIT_MAX_RETRIES && response.status === 429; attempt++) { const retryAfterSeconds = Number(response.headers.get("retry-after")); const delay = Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0 ? Math.min(retryAfterSeconds * 1e3, RATE_LIMIT_RETRY_CAP_MS) : 100 * attempt; await new Promise((resolve) => setTimeout(resolve, delay)); response = await fetch(fetchUrl, options); } return response; } function buildClient(config, url, headers) { if (!config.hostUrl) throw new Error("Missing runtime host URL"); return createORPCClient(createRpcLink(config, url, headers)); } const browserClients = /* @__PURE__ */ new Map(); function memoizedClient(config, url, headers) { if (typeof window !== "undefined" && !headers) { const key = `${config.hostUrl}${url}`; const memoized = browserClients.get(key); if (memoized) return memoized; const client = buildClient(config, url, headers); browserClients.set(key, client); return client; } return buildClient(config, url, headers); } function createApiClient(config, headers) { return memoizedClient(config, config.rpcBase, headers); } function createPluginApiClient(pluginKey, config, headers) { return memoizedClient(config, `${config.rpcBase}/${pluginKey}`, headers); } function createServiceClients(config, keys, headers) { const clients = {}; for (const key of keys) if (key === "api") clients[key] = createApiClient(config, headers); else clients[key] = createPluginApiClient(key, config, headers); return clients; } function useClientContext() { return useRouter().options.context; } function useApiClient() { return useClientContext().apiClient; } function useOrpc() { return createTanstackQueryUtils(useApiClient()); } function usePluginClients() { return useClientContext().pluginClients ?? {}; } //#endregion export { createApiClient, createPluginApiClient, createServiceClients, useApiClient, useOrpc, usePluginClients }; //# sourceMappingURL=api.mjs.map