UNPKG

everything-dev

Version:

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

1 lines • 8.56 kB
{"version":3,"file":"api.cjs","names":["RPCLink","onError","createORPCClient","useRouter","createTanstackQueryUtils"],"sources":["../../src/ui/api.ts"],"sourcesContent":["import { createORPCClient, onError } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport type { ContractRouterClient, RouterContract } from \"@orpc/contract\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { useRouter } from \"@tanstack/react-router\";\n\nexport type { RouterContract };\n\n/**\n * Localized copy for the API connection-failure toast — the app's authored\n * override seam (ADR 0023). Called at error time, so the implementation may\n * resolve the active locale itself.\n */\nexport type ApiConnectionErrorCopy = () => { title: string; description: string };\n\n/** typed client for the app's default router contract (router-context default) */\nexport type ApiClient<T extends RouterContract = RouterContract> = ContractRouterClient<T>;\n\nexport interface ClientServiceConfig {\n hostUrl: string;\n rpcBase: `/${string}`;\n connectionError?: ApiConnectionErrorCopy;\n}\n\ntype ClientRouterContext = {\n apiClient?: unknown;\n pluginClients?: Record<string, unknown>;\n};\n\nexport type { ClientRouterContext };\n\nfunction createRpcLink(config: ClientServiceConfig, url: `/${string}`, headers?: Headers) {\n return new RPCLink({\n origin: config.hostUrl,\n url,\n interceptors: [\n onError((error: unknown) => {\n if (typeof window === \"undefined\") {\n return;\n }\n\n if (error && typeof error === \"object\" && \"message\" in error) {\n const message = String(error.message).toLowerCase();\n if (\n message.includes(\"fetch\") ||\n message.includes(\"network\") ||\n message.includes(\"failed to fetch\")\n ) {\n void import(\"sonner\")\n .then(({ toast }) => {\n const message = config.connectionError?.() ?? {\n title: \"Unable to connect to API\",\n description: \"The API is currently unavailable. Please try again later.\",\n };\n toast.error(message.title, {\n id: \"api-connection-error\",\n description: message.description,\n });\n })\n .catch(() => {});\n }\n }\n }),\n ],\n fetch(fetchUrl: RequestInfo | URL, options?: RequestInit) {\n return fetchWithRateLimitRetry(fetchUrl, {\n ...options,\n credentials: \"include\",\n headers: headers\n ? { ...Object.fromEntries(headers), ...(options?.headers as Record<string, string>) }\n : options?.headers,\n });\n },\n });\n}\n\nconst RATE_LIMIT_MAX_RETRIES = 3;\nconst RATE_LIMIT_RETRY_CAP_MS = 2_000;\n\n/**\n * A 429 from the host's edge limiter is transient saturation, not an\n * application failure — retry within the server's `Retry-After` (capped) so\n * queries and the session check self-heal instead of surfacing errors.\n */\nasync function fetchWithRateLimitRetry(\n fetchUrl: RequestInfo | URL,\n options?: RequestInit,\n): Promise<Response> {\n let response = await fetch(fetchUrl, options);\n for (let attempt = 1; attempt <= RATE_LIMIT_MAX_RETRIES && response.status === 429; attempt++) {\n const retryAfterSeconds = Number(response.headers.get(\"retry-after\"));\n const delay =\n Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0\n ? Math.min(retryAfterSeconds * 1000, RATE_LIMIT_RETRY_CAP_MS)\n : 100 * attempt;\n await new Promise((resolve) => setTimeout(resolve, delay));\n response = await fetch(fetchUrl, options);\n }\n return response;\n}\n\nfunction buildClient<T extends RouterContract>(\n config: ClientServiceConfig,\n url: `/${string}`,\n headers?: Headers,\n): ContractRouterClient<T> {\n if (!config.hostUrl) {\n throw new Error(\"Missing runtime host URL\");\n }\n return createORPCClient(createRpcLink(config, url, headers)) as ContractRouterClient<T>;\n}\n\nconst browserClients = new Map<string, unknown>();\n\nfunction memoizedClient<T extends RouterContract>(\n config: ClientServiceConfig,\n url: `/${string}`,\n headers?: Headers,\n): ContractRouterClient<T> {\n if (typeof window !== \"undefined\" && !headers) {\n const key = `${config.hostUrl}${url}`;\n const memoized = browserClients.get(key);\n if (memoized) {\n return memoized as ContractRouterClient<T>;\n }\n const client = buildClient<T>(config, url, headers);\n browserClients.set(key, client);\n return client;\n }\n\n return buildClient<T>(config, url, headers);\n}\n\nexport function createApiClient<T extends RouterContract = RouterContract>(\n config: ClientServiceConfig,\n headers?: Headers,\n): ContractRouterClient<T> {\n return memoizedClient<T>(config, config.rpcBase, headers);\n}\n\nexport function createPluginApiClient<T extends RouterContract = RouterContract>(\n pluginKey: string,\n config: ClientServiceConfig,\n headers?: Headers,\n): ContractRouterClient<T> {\n return memoizedClient<T>(config, `${config.rpcBase}/${pluginKey}`, headers);\n}\n\nexport function createServiceClients<\n C extends Record<string, RouterContract>,\n K extends keyof C & string = keyof C & string,\n>(\n config: ClientServiceConfig,\n keys: readonly K[],\n headers?: Headers,\n): Pick<{ [P in keyof C]: ContractRouterClient<C[P]> }, K> {\n const clients: Record<string, unknown> = {};\n for (const key of keys) {\n if (key === \"api\") {\n clients[key] = createApiClient<C[typeof key]>(config, headers);\n } else {\n clients[key] = createPluginApiClient<C[typeof key]>(key, config, headers);\n }\n }\n return clients as Pick<{ [P in keyof C]: ContractRouterClient<C[P]> }, K>;\n}\n\nfunction useClientContext(): ClientRouterContext {\n return useRouter().options.context as ClientRouterContext;\n}\n\nexport function useApiClient<T extends RouterContract = RouterContract>(): ContractRouterClient<T> {\n return useClientContext().apiClient as ContractRouterClient<T>;\n}\n\nexport function useOrpc<T extends RouterContract = RouterContract>() {\n return createTanstackQueryUtils(useApiClient<T>());\n}\n\nexport function usePluginClients<C extends Record<string, unknown> = Record<string, unknown>>(): C {\n return (useClientContext().pluginClients ?? {}) as C;\n}\n"],"mappings":";;;;;;;AA+BA,SAAS,cAAc,QAA6B,KAAmB,SAAmB;CACxF,OAAO,IAAIA,2BAAQ;EACjB,QAAQ,OAAO;EACf;EACA,cAAc,KACZC,uBAAS,UAAmB;GAC1B,IAAI,OAAO,WAAW,aACpB;GAGF,IAAI,SAAS,OAAO,UAAU,YAAY,aAAa,OAAO;IAC5D,MAAM,UAAU,OAAO,MAAM,OAAO,CAAC,CAAC,YAAY;IAClD,IACE,QAAQ,SAAS,OAAO,KACxB,QAAQ,SAAS,SAAS,KAC1B,QAAQ,SAAS,iBAAiB,GAElC,AAAK,OAAO,SAAS,CAClB,MAAM,EAAE,YAAY;KACnB,MAAM,UAAU,OAAO,kBAAkB,KAAK;MAC5C,OAAO;MACP,aAAa;KACf;KACA,MAAM,MAAM,QAAQ,OAAO;MACzB,IAAI;MACJ,aAAa,QAAQ;KACvB,CAAC;IACH,CAAC,CAAC,CACD,YAAY,CAAC,CAAC;GAErB;EACF,CAAC,CACH;EACA,MAAM,UAA6B,SAAuB;GACxD,OAAO,wBAAwB,UAAU;IACvC,GAAG;IACH,aAAa;IACb,SAAS,UACL;KAAE,GAAG,OAAO,YAAY,OAAO;KAAG,GAAI,SAAS;IAAmC,IAClF,SAAS;GACf,CAAC;EACH;CACF,CAAC;AACH;AAEA,MAAM,yBAAyB;AAC/B,MAAM,0BAA0B;;;;;;AAOhC,eAAe,wBACb,UACA,SACmB;CACnB,IAAI,WAAW,MAAM,MAAM,UAAU,OAAO;CAC5C,KAAK,IAAI,UAAU,GAAG,WAAW,0BAA0B,SAAS,WAAW,KAAK,WAAW;EAC7F,MAAM,oBAAoB,OAAO,SAAS,QAAQ,IAAI,aAAa,CAAC;EACpE,MAAM,QACJ,OAAO,SAAS,iBAAiB,KAAK,oBAAoB,IACtD,KAAK,IAAI,oBAAoB,KAAM,uBAAuB,IAC1D,MAAM;EACZ,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,KAAK,CAAC;EACzD,WAAW,MAAM,MAAM,UAAU,OAAO;CAC1C;CACA,OAAO;AACT;AAEA,SAAS,YACP,QACA,KACA,SACyB;CACzB,IAAI,CAAC,OAAO,SACV,MAAM,IAAI,MAAM,0BAA0B;CAE5C,WAAOC,+BAAiB,cAAc,QAAQ,KAAK,OAAO,CAAC;AAC7D;AAEA,MAAM,iCAAiB,IAAI,IAAqB;AAEhD,SAAS,eACP,QACA,KACA,SACyB;CACzB,IAAI,OAAO,WAAW,eAAe,CAAC,SAAS;EAC7C,MAAM,MAAM,GAAG,OAAO,UAAU;EAChC,MAAM,WAAW,eAAe,IAAI,GAAG;EACvC,IAAI,UACF,OAAO;EAET,MAAM,SAAS,YAAe,QAAQ,KAAK,OAAO;EAClD,eAAe,IAAI,KAAK,MAAM;EAC9B,OAAO;CACT;CAEA,OAAO,YAAe,QAAQ,KAAK,OAAO;AAC5C;AAEA,SAAgB,gBACd,QACA,SACyB;CACzB,OAAO,eAAkB,QAAQ,OAAO,SAAS,OAAO;AAC1D;AAEA,SAAgB,sBACd,WACA,QACA,SACyB;CACzB,OAAO,eAAkB,QAAQ,GAAG,OAAO,QAAQ,GAAG,aAAa,OAAO;AAC5E;AAEA,SAAgB,qBAId,QACA,MACA,SACyD;CACzD,MAAM,UAAmC,CAAC;CAC1C,KAAK,MAAM,OAAO,MAChB,IAAI,QAAQ,OACV,QAAQ,OAAO,gBAA+B,QAAQ,OAAO;MAE7D,QAAQ,OAAO,sBAAqC,KAAK,QAAQ,OAAO;CAG5E,OAAO;AACT;AAEA,SAAS,mBAAwC;CAC/C,WAAOC,kCAAU,CAAC,CAAC,QAAQ;AAC7B;AAEA,SAAgB,eAAmF;CACjG,OAAO,iBAAiB,CAAC,CAAC;AAC5B;AAEA,SAAgB,UAAqD;CACnE,WAAOC,+CAAyB,aAAgB,CAAC;AACnD;AAEA,SAAgB,mBAAmF;CACjG,OAAQ,iBAAiB,CAAC,CAAC,iBAAiB,CAAC;AAC/C"}