(
config: Required<Omit<ClientConfig, 'fetch'>> & { fetch: FetchLike },
options: RequestOptions,
)
| 73 | * non-2xx, or {@link OpenWATimeoutError} on timeout. |
| 74 | */ |
| 75 | export async function request<T>( |
| 76 | config: Required<Omit<ClientConfig, 'fetch'>> & { fetch: FetchLike }, |
| 77 | options: RequestOptions, |
| 78 | ): Promise<T> { |
| 79 | const url = buildUrl(config.baseUrl, options.path, options.query); |
| 80 | const timeoutMs = options.timeoutMs ?? config.timeoutMs; |
| 81 | |
| 82 | const controller = new AbortController(); |
| 83 | const timer = setTimeout(() => controller.abort(), timeoutMs); |
| 84 | |
| 85 | // Auth and JSON content-type WIN over caller-supplied defaults/per-request headers — the SDK only |
| 86 | // ever sends a JSON body, and this matches the Python and PHP SDKs (which force JSON) and the |
| 87 | // documented "JSON headers win" contract. Put them last so a defaultHeaders Content-Type can't clobber. |
| 88 | const headers: Record<string, string> = { |
| 89 | ...config.defaultHeaders, |
| 90 | ...options.headers, |
| 91 | 'Content-Type': 'application/json', |
| 92 | 'X-API-Key': config.apiKey, |
| 93 | }; |
| 94 | |
| 95 | let res: Response; |
| 96 | try { |
| 97 | res = await config.fetch(url, { |
| 98 | method: options.method, |
| 99 | headers, |
| 100 | body: options.body !== undefined ? JSON.stringify(options.body) : undefined, |
| 101 | signal: controller.signal, |
| 102 | // Never auto-follow redirects: doing so would re-send the X-API-Key header |
| 103 | // to the redirect target (potentially a different origin). A 3xx surfaces |
| 104 | // as a non-2xx error instead. |
| 105 | redirect: 'manual', |
| 106 | }); |
| 107 | } catch (err) { |
| 108 | clearTimeout(timer); |
| 109 | if (err instanceof Error && err.name === 'AbortError') { |
| 110 | throw new OpenWATimeoutError(timeoutMs); |
| 111 | } |
| 112 | throw err; |
| 113 | } |
| 114 | clearTimeout(timer); |
| 115 | |
| 116 | if (!res.ok) { |
| 117 | const context = `${options.method} ${options.path}`; |
| 118 | const apiError = await OpenWAApiError.fromResponse(res, context); |
| 119 | throw classifyApiError(apiError.status, apiError.message, apiError.body, apiError.errorKind); |
| 120 | } |
| 121 | |
| 122 | if (res.status === 204) { |
| 123 | return null as T; |
| 124 | } |
| 125 | const text = await res.text(); |
| 126 | if (!text) return null as T; |
| 127 | try { |
| 128 | return JSON.parse(text) as T; |
| 129 | } catch { |
| 130 | return text as unknown as T; |
| 131 | } |
| 132 | } |
no test coverage detected