(apiKey: string, deps: Deps)
| 35 | const timeoutMs = 15_000; |
| 36 | |
| 37 | export function createTrpcClient(apiKey: string, deps: Deps): TrpcClient { |
| 38 | const authHeader = { Authorization: `Bearer ${apiKey}` }; |
| 39 | const { logger } = deps; |
| 40 | |
| 41 | async function withLogging<T>( |
| 42 | path: string, |
| 43 | call: () => Promise<WireResult<T>>, |
| 44 | ): Promise<WireResult<T>> { |
| 45 | logger?.debug(`→ ${path}`); |
| 46 | const result = await call(); |
| 47 | if (result.ok) { |
| 48 | logger?.debug(`← ${path} ok`); |
| 49 | } else { |
| 50 | const e = result.error; |
| 51 | const raw = e.kind === "http" ? e.body : e.cause.message; |
| 52 | const clipped = raw.length > 200 ? raw.slice(0, 200) + "…" : raw; |
| 53 | const msg = e.kind === "http" ? `${e.status} ${clipped}` : clipped; |
| 54 | logger?.warn(`← ${path} error (${e.kind}): ${msg}`); |
| 55 | } |
| 56 | return result; |
| 57 | } |
| 58 | |
| 59 | return { |
| 60 | query: (path, input, schema) => { |
| 61 | const encoded = encodeURIComponent( |
| 62 | JSON.stringify(superjson.serialize(input)), |
| 63 | ); |
| 64 | const url = `${deps.baseUrl}/api/trpc/${path}?input=${encoded}`; |
| 65 | return withLogging(path, () => |
| 66 | send( |
| 67 | deps.fetch, |
| 68 | url, |
| 69 | { |
| 70 | headers: authHeader, |
| 71 | method: "GET", |
| 72 | signal: AbortSignal.timeout(timeoutMs), |
| 73 | }, |
| 74 | schema, |
| 75 | ), |
| 76 | ); |
| 77 | }, |
| 78 | mutation: (path, input, schema) => { |
| 79 | const url = `${deps.baseUrl}/api/trpc/${path}`; |
| 80 | const body = JSON.stringify(superjson.serialize(input)); |
| 81 | return withLogging(path, () => |
| 82 | send( |
| 83 | deps.fetch, |
| 84 | url, |
| 85 | { |
| 86 | body, |
| 87 | headers: { ...authHeader, "content-type": "application/json" }, |
| 88 | method: "POST", |
| 89 | signal: AbortSignal.timeout(timeoutMs), |
| 90 | }, |
| 91 | schema, |
| 92 | ), |
| 93 | ); |
| 94 | }, |
no test coverage detected