{"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":"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"}