everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
99 lines (97 loc) • 3.68 kB
JavaScript
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