diff --git a/packages/http/src/index.ts b/packages/http/src/index.ts index ed23d8b..9d70444 100644 --- a/packages/http/src/index.ts +++ b/packages/http/src/index.ts @@ -19,12 +19,12 @@ export interface HTTP { [Context.current]: Context (url: string | URL, config?: HTTP.RequestConfig): Promise> (method: HTTP.Method, url: string | URL, config?: HTTP.RequestConfig): Promise> - get(url: string, config?: HTTP.RequestConfig): Promise - delete(url: string, config?: HTTP.RequestConfig): Promise + get: HTTP.Request1 + delete: HTTP.Request1 + patch: HTTP.Request2 + post: HTTP.Request2 + put: HTTP.Request2 head(url: string, config?: HTTP.RequestConfig): Promise - patch(url: string, data?: any, config?: HTTP.RequestConfig): Promise - post(url: string, data?: any, config?: HTTP.RequestConfig): Promise - put(url: string, data?: any, config?: HTTP.RequestConfig): Promise /** @deprecated use `ctx.http()` instead */ axios(url: string, config?: HTTP.RequestConfig): Promise> ws(url: string, config?: HTTP.RequestConfig): Promise @@ -49,6 +49,20 @@ export namespace HTTP { | 'text' | 'stream' + export interface Request1 { + (url: string, config?: HTTP.RequestConfig & { responseType: 'arraybuffer' }): Promise + (url: string, config?: HTTP.RequestConfig & { responseType: 'stream' }): Promise> + (url: string, config?: HTTP.RequestConfig & { responseType: 'text' }): Promise + (url: string, config?: HTTP.RequestConfig): Promise + } + + export interface Request2 { + (url: string, data?: any, config?: HTTP.RequestConfig & { responseType: 'arraybuffer' }): Promise + (url: string, data?: any, config?: HTTP.RequestConfig & { responseType: 'stream' }): Promise> + (url: string, data?: any, config?: HTTP.RequestConfig & { responseType: 'text' }): Promise + (url: string, data?: any, config?: HTTP.RequestConfig): Promise + } + export interface Config { headers?: Dict timeout?: number @@ -62,6 +76,7 @@ export namespace HTTP { method?: Method params?: Dict data?: any + keepAlive?: boolean responseType?: ResponseType } @@ -70,6 +85,7 @@ export namespace HTTP { } export interface Response { + url: string data: T status: number statusText: string @@ -93,8 +109,11 @@ export function apply(ctx: Context, config?: HTTP.Config) { } } - const intercept = caller[Context.intercept] - merge(intercept.http) + let intercept = caller[Context.intercept] + while (intercept) { + merge(intercept.http) + intercept = Object.getPrototypeOf(intercept) + } merge(init) return result } @@ -107,6 +126,17 @@ export function apply(ctx: Context, config?: HTTP.Config) { } } + function decode(response: Response) { + const type = response.headers.get('Content-Type') + if (type === 'application/json') { + return response.json() + } else if (type?.startsWith('text/')) { + return response.text() + } else { + return response.arrayBuffer() + } + } + const http = async function http(this: Context, ...args: any[]) { let method: HTTP.Method | undefined if (typeof args[1] === 'string' || args[1] instanceof URL) { @@ -124,19 +154,43 @@ export function apply(ctx: Context, config?: HTTP.Config) { }, config.timeout) this.on('dispose', () => clearTimeout(timer)) } - const response = await fetch(url, { + + const raw = await fetch(url, { method, body: config.data, headers: config.headers, signal: controller.signal, + }).catch((cause) => { + const error = new HTTP.Error(cause.message) + error.cause = cause + throw error }) - const data = await response.json() - return { - data, - status: response.status, - statusText: response.statusText, - headers: Object.fromEntries(response.headers), + + const response: HTTP.Response = { + data: null, + url: raw.url, + status: raw.status, + statusText: raw.statusText, + headers: Object.fromEntries(raw.headers), + } + + if (!raw.ok) { + const error = new HTTP.Error(raw.statusText) + error.response = response + try { + response.data = await decode(raw) + } catch {} + throw error + } + + if (config.responseType === 'arraybuffer') { + response.data = await raw.arrayBuffer() + } else if (config.responseType === 'stream') { + response.data = raw.body + } else { + response.data = await decode(raw) } + return response } as HTTP for (const method of ['get', 'delete'] as const) {