Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: export all types #280

Merged
merged 7 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 1 addition & 17 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,4 @@
import type {
FetchContext,
FetchOptions,
FetchRequest,
FetchResponse,
} from "./fetch";

export interface IFetchError<T = any> extends Error {
request?: FetchRequest;
options?: FetchOptions;
response?: FetchResponse<T>;
data?: T;
status?: number;
statusText?: string;
statusCode?: number;
statusMessage?: string;
}
import type { FetchContext, IFetchError } from "./types";

export class FetchError<T = any> extends Error implements IFetchError<T> {
constructor(message: string, opts?: { cause: unknown }) {
Expand Down
84 changes: 6 additions & 78 deletions src/fetch.ts
Original file line number Diff line number Diff line change
@@ -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<T> extends Response {
_data?: T;
}
export interface SearchParameters {
[key: string]: any;
}

export interface FetchContext<T = any, R extends ResponseType = ResponseType> {
request: FetchRequest;
// eslint-disable-next-line no-use-before-define
options: FetchOptions<R>;
response?: FetchResponse<T>;
error?: Error;
}

export interface FetchOptions<R extends ResponseType = ResponseType>
extends Omit<RequestInit, "body"> {
baseURL?: string;
body?: RequestInit["body"] | Record<string, any>;
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> | void;
onRequestError?(
context: FetchContext & { error: Error }
): Promise<void> | void;
onResponse?(
context: FetchContext & { response: FetchResponse<R> }
): Promise<void> | void;
onResponseError?(
context: FetchContext & { response: FetchResponse<R> }
): Promise<void> | void;
}

export interface $Fetch {
<T = any, R extends ResponseType = "json">(
request: FetchRequest,
options?: FetchOptions<R>
): Promise<MappedType<R, T>>;
raw<T = any, R extends ResponseType = "json">(
request: FetchRequest,
options?: FetchOptions<R>
): Promise<FetchResponse<MappedType<R, T>>>;
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([
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
129 changes: 126 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,127 @@
// --------------------------
// $fetch API
// --------------------------

export interface $Fetch {
<T = any, R extends ResponseType = "json">(
request: FetchRequest,
options?: FetchOptions<R>
): Promise<MappedResponseType<R, T>>;
raw<T = any, R extends ResponseType = "json">(
request: FetchRequest,
options?: FetchOptions<R>
): Promise<FetchResponse<MappedResponseType<R, T>>>;
native: Fetch;
create(defaults: FetchOptions): $Fetch;
}

// --------------------------
// Context
// --------------------------

export interface FetchContext<T = any, R extends ResponseType = ResponseType> {
request: FetchRequest;
// eslint-disable-next-line no-use-before-define
options: FetchOptions<R>;
response?: FetchResponse<T>;
error?: Error;
}

// --------------------------
// Options
// --------------------------

export interface FetchOptions<R extends ResponseType = ResponseType>
extends Omit<RequestInit, "body"> {
baseURL?: string;
body?: RequestInit["body"] | Record<string, any>;
ignoreResponseError?: boolean;
params?: Record<string, any>;
query?: Record<string, any>;
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> | void;
onRequestError?(
context: FetchContext & { error: Error }
): Promise<void> | void;
onResponse?(
context: FetchContext & { response: FetchResponse<R> }
): Promise<void> | void;
onResponseError?(
context: FetchContext & { response: FetchResponse<R> }
): Promise<void> | 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<Uint8Array>;
}

export type ResponseType = keyof ResponseMap | "json";

export type MappedResponseType<
R extends ResponseType,
JsonType = any,
> = R extends keyof ResponseMap ? ResponseMap[R] : JsonType;

export interface FetchResponse<T> extends Response {
_data?: T;
}

// --------------------------
// Error
// --------------------------

export interface IFetchError<T = any> extends Error {
request?: FetchRequest;
options?: FetchOptions;
response?: FetchResponse<T>;
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;
}
15 changes: 1 addition & 14 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -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"])
Expand Down Expand Up @@ -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<Uint8Array>;
}

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) {
Expand Down