| 138 | } |
| 139 | |
| 140 | export function make(options: ClientOptions) { |
| 141 | const fetch = options.fetch ?? globalThis.fetch |
| 142 | |
| 143 | const prepare = (descriptor: RequestDescriptor, requestOptions?: RequestOptions) => { |
| 144 | const url = new URL(descriptor.path, options.baseUrl) |
| 145 | for (const [key, value] of Object.entries(descriptor.query ?? {})) appendQuery(url.searchParams, key, value) |
| 146 | const headers = new Headers(options.headers) |
| 147 | for (const [key, value] of Object.entries(descriptor.headers ?? {})) { |
| 148 | if (value !== undefined && value !== null) headers.set(key, String(value)) |
| 149 | } |
| 150 | for (const [key, value] of new Headers(requestOptions?.headers)) headers.set(key, value) |
| 151 | if (descriptor.body !== undefined && !headers.has("content-type")) headers.set("content-type", "application/json") |
| 152 | return { |
| 153 | url, |
| 154 | init: { |
| 155 | method: descriptor.method, |
| 156 | signal: requestOptions?.signal, |
| 157 | headers, |
| 158 | body: descriptor.body === undefined ? undefined : JSON.stringify(descriptor.body), |
| 159 | } satisfies RequestInit, |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | const execute = async (descriptor: RequestDescriptor, requestOptions?: RequestOptions) => { |
| 164 | try { |
| 165 | const prepared = prepare(descriptor, requestOptions) |
| 166 | return await fetch(prepared.url, prepared.init) |
| 167 | } catch (cause) { |
| 168 | throw new ClientError("Transport", { cause }) |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | const responseError = async (response: Response, descriptor: RequestDescriptor): Promise<never> => { |
| 173 | if (descriptor.declaredStatuses.includes(response.status)) throw await json(response) |
| 174 | try { |
| 175 | await response.body?.cancel() |
| 176 | } catch {} |
| 177 | throw new ClientError("UnexpectedStatus", { cause: { status: response.status } }) |
| 178 | } |
| 179 | |
| 180 | const request = async <A>(descriptor: RequestDescriptor, requestOptions?: RequestOptions): Promise<A> => { |
| 181 | const response = await execute(descriptor, requestOptions) |
| 182 | if (response.status !== descriptor.successStatus) return responseError(response, descriptor) |
| 183 | if (descriptor.empty) { |
| 184 | try { |
| 185 | await response.body?.cancel() |
| 186 | } catch {} |
| 187 | return undefined as A |
| 188 | } |
| 189 | return (await json(response)) as A |
| 190 | } |
| 191 | |
| 192 | const sse = <A>(descriptor: RequestDescriptor, requestOptions?: RequestOptions): AsyncIterable<A> => ({ |
| 193 | async *[Symbol.asyncIterator]() { |
| 194 | const response = await execute(descriptor, requestOptions) |
| 195 | if (response.status !== descriptor.successStatus) await responseError(response, descriptor) |
| 196 | if (!isContentType(response, "text/event-stream")) { |
| 197 | try { |