diff --git a/src/error.ts b/src/error.ts index 38052371..f862024a 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,20 +1,4 @@ -import type { - FetchContext, - FetchOptions, - FetchRequest, - FetchResponse, -} from "./fetch"; - -export interface IFetchError extends Error { - request?: FetchRequest; - options?: FetchOptions; - response?: FetchResponse; - data?: T; - status?: number; - statusText?: string; - statusCode?: number; - statusMessage?: string; -} +import type { FetchContext, IFetchError } from "./types"; export class FetchError extends Error implements IFetchError { constructor(message: string, opts?: { cause: unknown }) { diff --git a/src/fetch.ts b/src/fetch.ts index 6ea9ddbd..c0021c08 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -1,91 +1,19 @@ import type { Readable } from "node:stream"; import destr from "destr"; import { withBase, withQuery } from "ufo"; -import type { Fetch, RequestInfo, RequestInit, Response } from "./types"; import { createFetchError } from "./error"; import { isPayloadMethod, isJSONSerializable, detectResponseType, - ResponseType, - MappedType, mergeFetchOptions, } from "./utils"; - -export interface CreateFetchOptions { - // eslint-disable-next-line no-use-before-define - defaults?: FetchOptions; - fetch?: Fetch; - Headers?: typeof Headers; - AbortController?: typeof AbortController; -} - -export type FetchRequest = RequestInfo; -export interface FetchResponse extends Response { - _data?: T; -} -export interface SearchParameters { - [key: string]: any; -} - -export interface FetchContext { - request: FetchRequest; - // eslint-disable-next-line no-use-before-define - options: FetchOptions; - response?: FetchResponse; - error?: Error; -} - -export interface FetchOptions - extends Omit { - baseURL?: string; - body?: RequestInit["body"] | Record; - ignoreResponseError?: boolean; - params?: SearchParameters; - query?: SearchParameters; - parseResponse?: (responseText: string) => any; - responseType?: R; - - /** - * @experimental Set to "half" to enable duplex streaming. - * Will be automatically set to "half" when using a ReadableStream as body. - * https://fetch.spec.whatwg.org/#enumdef-requestduplex - */ - duplex?: "half" | undefined; - - /** timeout in milliseconds */ - timeout?: number; - - retry?: number | false; - /** Delay between retries in milliseconds. */ - retryDelay?: number; - /** Default is [408, 409, 425, 429, 500, 502, 503, 504] */ - retryStatusCodes?: number[]; - - onRequest?(context: FetchContext): Promise | void; - onRequestError?( - context: FetchContext & { error: Error } - ): Promise | void; - onResponse?( - context: FetchContext & { response: FetchResponse } - ): Promise | void; - onResponseError?( - context: FetchContext & { response: FetchResponse } - ): Promise | void; -} - -export interface $Fetch { - ( - request: FetchRequest, - options?: FetchOptions - ): Promise>; - raw( - request: FetchRequest, - options?: FetchOptions - ): Promise>>; - native: Fetch; - create(defaults: FetchOptions): $Fetch; -} +import type { + CreateFetchOptions, + FetchResponse, + FetchContext, + $Fetch, +} from "./types"; // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status const retryStatusCodes = new Set([ diff --git a/src/index.ts b/src/index.ts index 4893fbf1..e372db9c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,8 @@ import { createFetch } from "./base"; export * from "./base"; +export type * from "./types"; + // ref: https://github.com/tc39/proposal-global const _globalThis = (function () { if (typeof globalThis !== "undefined") { diff --git a/src/types.ts b/src/types.ts index b3ffc047..10a75d83 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,127 @@ +// -------------------------- +// $fetch API +// -------------------------- + +export interface $Fetch { + ( + request: FetchRequest, + options?: FetchOptions + ): Promise>; + raw( + request: FetchRequest, + options?: FetchOptions + ): Promise>>; + native: Fetch; + create(defaults: FetchOptions): $Fetch; +} + +// -------------------------- +// Context +// -------------------------- + +export interface FetchContext { + request: FetchRequest; + // eslint-disable-next-line no-use-before-define + options: FetchOptions; + response?: FetchResponse; + error?: Error; +} + +// -------------------------- +// Options +// -------------------------- + +export interface FetchOptions + extends Omit { + baseURL?: string; + body?: RequestInit["body"] | Record; + ignoreResponseError?: boolean; + params?: Record; + query?: Record; + parseResponse?: (responseText: string) => any; + responseType?: R; + + /** + * @experimental Set to "half" to enable duplex streaming. + * Will be automatically set to "half" when using a ReadableStream as body. + * https://fetch.spec.whatwg.org/#enumdef-requestduplex + */ + duplex?: "half" | undefined; + + /** timeout in milliseconds */ + timeout?: number; + + retry?: number | false; + /** Delay between retries in milliseconds. */ + retryDelay?: number; + /** Default is [408, 409, 425, 429, 500, 502, 503, 504] */ + retryStatusCodes?: number[]; + + onRequest?(context: FetchContext): Promise | void; + onRequestError?( + context: FetchContext & { error: Error } + ): Promise | void; + onResponse?( + context: FetchContext & { response: FetchResponse } + ): Promise | void; + onResponseError?( + context: FetchContext & { response: FetchResponse } + ): Promise | void; +} + +export interface CreateFetchOptions { + // eslint-disable-next-line no-use-before-define + defaults?: FetchOptions; + fetch?: Fetch; + Headers?: typeof Headers; + AbortController?: typeof AbortController; +} + +// -------------------------- +// Response Types +// -------------------------- + +export interface ResponseMap { + blob: Blob; + text: string; + arrayBuffer: ArrayBuffer; + stream: ReadableStream; +} + +export type ResponseType = keyof ResponseMap | "json"; + +export type MappedResponseType< + R extends ResponseType, + JsonType = any, +> = R extends keyof ResponseMap ? ResponseMap[R] : JsonType; + +export interface FetchResponse extends Response { + _data?: T; +} + +// -------------------------- +// Error +// -------------------------- + +export interface IFetchError extends Error { + request?: FetchRequest; + options?: FetchOptions; + response?: FetchResponse; + data?: T; + status?: number; + statusText?: string; + statusCode?: number; + statusMessage?: string; +} + +// -------------------------- +// Other types +// -------------------------- + export type Fetch = typeof globalThis.fetch; -export type RequestInfo = globalThis.RequestInfo; -export type RequestInit = globalThis.RequestInit; -export type Response = globalThis.Response; + +export type FetchRequest = RequestInfo; + +export interface SearchParameters { + [key: string]: any; +} diff --git a/src/utils.ts b/src/utils.ts index 86a5de98..41475651 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import type { FetchOptions } from "./fetch"; +import type { FetchOptions, ResponseType } from "./types"; const payloadMethods = new Set( Object.freeze(["PATCH", "POST", "PUT", "DELETE"]) @@ -39,19 +39,6 @@ const textTypes = new Set([ const JSON_RE = /^application\/(?:[\w!#$%&*.^`~-]*\+)?json(;.+)?$/i; -export interface ResponseMap { - blob: Blob; - text: string; - arrayBuffer: ArrayBuffer; - stream: ReadableStream; -} - -export type ResponseType = keyof ResponseMap | "json"; -export type MappedType< - R extends ResponseType, - JsonType = any, -> = R extends keyof ResponseMap ? ResponseMap[R] : JsonType; - // This provides reasonable defaults for the correct parser based on Content-Type header. export function detectResponseType(_contentType = ""): ResponseType { if (!_contentType) {