@zhengxs/http
Version:
A lightweight cross-platform http request library
474 lines (404 loc) • 18.7 kB
TypeScript
/// <reference types="node" />
import { Agent } from 'node:http';
import { Readable } from 'node:stream';
import { ReadStream } from 'node:fs';
export declare abstract class AbstractPage<Item> implements AsyncIterable<Item> {
#private;
protected options: FinalRequestOptions;
protected response: Response;
protected body: unknown;
constructor(client: APIClient, response: Response, body: unknown, options: FinalRequestOptions);
/**
* @deprecated Use nextPageInfo instead
*/
abstract nextPageParams(): Partial<Record<string, unknown>> | null;
abstract nextPageInfo(): PageInfo | null;
abstract getPaginatedItems(): Item[];
hasNextPage(): boolean;
getNextPage(): Promise<this>;
iterPages(): AsyncGenerator<AbstractPage<Item>, void, unknown>;
[Symbol.asyncIterator](): AsyncGenerator<Awaited<Item>, void, unknown>;
}
export declare class APIClient {
baseURL: string;
maxRetries: number;
timeout: number;
httpAgent: Agent | undefined;
protected fetch: Fetch;
protected idempotencyHeader?: string;
constructor({ baseURL, maxRetries, timeout, // 10 minutes
httpAgent, fetch: overrideFetch, }: APIClientOptions);
/**
* Override this to add your own auth headers.
*
* ```ts
* {
* Authorization: 'Bearer 123',
* }
* ```
*/
protected authHeaders(_opts: FinalRequestOptions): PromiseOrValue<ReqHeaders>;
/**
* Override this to add your own default headers.
*/
protected defaultHeaders(opts: FinalRequestOptions): Promise<ReqHeaders>;
protected defaultQuery(): DefaultQuery | undefined;
/**
* Override this to add your own headers validation:
*/
protected validateHeaders(_headers: ReqHeaders, _customHeaders: ReqHeaders): void;
protected defaultIdempotencyKey(): string;
get<Req extends NonNullable<unknown>, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp>;
post<Req extends NonNullable<unknown>, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp>;
patch<Req extends NonNullable<unknown>, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp>;
put<Req extends NonNullable<unknown>, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp>;
delete<Req extends NonNullable<unknown>, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp>;
private methodRequest;
getAPIList<Item, PageClass extends AbstractPage<Item> = AbstractPage<Item>>(path: string, Page: new (...args: any[]) => PageClass, opts?: RequestOptions<any>): PagePromise<PageClass, Item>;
private calculateContentLength;
protected buildRequest<Req extends NonNullable<unknown>>(options: FinalRequestOptions<Req>): Promise<{
req: RequestInit_2;
url: string;
timeout: number;
}>;
/**
* Used as a callback for mutating the given `RequestInit` object.
*
* This is useful for cases where you want to add certain headers based off of
* the request properties, e.g. `method` or `url`.
*/
protected prepareRequest(_request: RequestInit_2, _config: {
url: string;
options: FinalRequestOptions;
}): Promise<void>;
protected parseHeaders(headers: HeadersInit_2 | null | undefined): Record<string, string>;
protected makeStatusError(status: number | undefined, error: NonNullable<unknown> | undefined, message: string | undefined, headers: ReqHeaders | undefined): APIError;
request<Req extends NonNullable<unknown>, Rsp>(options: PromiseOrValue<FinalRequestOptions<Req>>, remainingRetries?: number | null): APIPromise<Rsp>;
protected makeRequest(optionsInput: PromiseOrValue<FinalRequestOptions>, retriesRemaining: number | null): Promise<APIResponseProps>;
simple<Req extends NonNullable<unknown>, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp>;
protected makeSimpleRequest(optionsInput: PromiseOrValue<FinalRequestOptions>, retriesRemaining?: number | null): Promise<APIResponseProps>;
requestAPIList<Item = unknown, PageClass extends AbstractPage<Item> = AbstractPage<Item>>(Page: new (...args: ConstructorParameters<typeof AbstractPage>) => PageClass, options: FinalRequestOptions): PagePromise<PageClass, Item>;
buildURL<Req extends Record<string, unknown>>(path: string, query: Req | null | undefined): string;
protected stringifyQuery(query: Record<string, unknown>): string;
fetchWithTimeout(url: RequestInfo_2, init: RequestInit_2 | undefined, ms: number, controller: AbortController): Promise<Response>;
protected getRequestClient(): RequestClient;
private shouldRetry;
private retryRequest;
private calculateDefaultRetryTimeoutMillis;
protected getUserAgent(): string;
static create(baseURL: string, options?: Omit<APIClientOptions, 'baseURL'>): APIClient;
}
export declare interface APIClientOptions {
baseURL: string;
maxRetries?: number | undefined;
timeout?: number | undefined;
httpAgent?: Agent | undefined;
fetch?: Fetch | undefined;
}
export declare class APIConnectionError extends APIError {
readonly status: undefined;
constructor({ message, cause, }: {
message?: string;
cause?: Error | undefined;
});
}
export declare class APIConnectionTimeoutError extends APIConnectionError {
constructor({ message }?: {
message?: string;
});
}
export declare class APIError extends HttpException {
readonly status: number | undefined;
readonly headers: ReqHeaders | undefined;
readonly error: NonNullable<unknown> | undefined;
readonly code: string | null | undefined;
readonly param: string | null | undefined;
readonly type: string | undefined;
constructor(status: number | undefined, error: NonNullable<unknown> | undefined, message: string | undefined, headers: ReqHeaders | undefined);
private static makeMessage;
static generate(status: number | undefined, errorResponse: NonNullable<unknown> | undefined, message: string | undefined, headers: ReqHeaders | undefined): APIError;
}
/**
* A subclass of `Promise` providing additional helper methods
* for interacting with the SDK.
*/
export declare class APIPromise<T> extends Promise<T> {
private responsePromise;
private parseResponse;
private parsedPromise;
constructor(responsePromise: Promise<APIResponseProps>, parseResponse?: (props: APIResponseProps) => PromiseOrValue<T>);
_thenUnwrap<U>(transform: (data: T) => U): APIPromise<U>;
/**
* Gets the raw `Response` instance instead of parsing the response
* data.
*
* If you want to parse the response body but still get the `Response`
* instance, you can use {@link withResponse()}.
*/
asResponse(): Promise<Response>;
/**
* Gets the parsed response data and the raw `Response` instance.
*
* If you just want to get the raw `Response` instance without parsing it,
* you can use {@link asResponse()}.
*/
withResponse(): Promise<{
data: T;
response: Response;
}>;
private parse;
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
finally(onfinally?: (() => void) | undefined | null): Promise<T>;
}
export declare type APIResponseProps = {
response: Response;
options: FinalRequestOptions;
controller: AbortController;
};
export declare class APIUserAbortError extends APIError {
readonly status: undefined;
constructor({ message }?: {
message?: string;
});
}
export declare class AuthenticationError extends APIError {
readonly status: 401;
}
export declare class BadRequestError extends APIError {
readonly status: 400;
}
/**
* Intended to match web.Blob, node.Blob, node-fetch.Blob, etc.
*/
export declare interface BlobLike {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */
readonly size: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */
readonly type: string;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */
text(): Promise<string>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */
slice(start?: number, end?: number): BlobLike;
}
declare type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | Uint8Array | DataView;
declare type BlobPart_2 = string | ArrayBuffer | ArrayBufferView | Blob | Uint8Array | DataView;
export { BlobPart_2 as BlobPart }
export declare type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined;
export declare const castToError: (err: any) => Error;
export declare class ConflictError extends APIError {
readonly status: 409;
}
export declare const createForm: <T = Record<string, unknown>>(body: T | undefined) => Promise<FormData>;
export declare const createResponseHeaders: (headers: Awaited<ReturnType<Fetch>>['headers']) => Record<string, string>;
export declare function debug(action: string, ...args: any[]): void;
export declare function defaultParseResponse<T>(props: APIResponseProps): Promise<T>;
export declare type DefaultQuery = Record<string, string | undefined>;
export declare type Fetch = (url: RequestInfo_2, init?: RequestInit_2) => Promise<Response>;
/**
* Intended to match web.File, node.File, node-fetch.File, etc.
*/
export declare interface FileLike extends BlobLike {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */
readonly lastModified: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */
readonly name: string;
}
export declare type FinalRequestOptions<Req extends NonNullable<unknown> = Record<string, unknown> | Readable> = RequestOptions<Req> & {
method: HTTPMethod;
path: string;
};
export declare function hasOwn(obj: NonNullable<unknown>, key: string): boolean;
declare type HeadersInit_2 = globalThis.HeadersInit;
export declare class HttpException extends Error {
}
export declare type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
export declare class InternalServerError extends APIError {
}
export declare const isAbsoluteURL: (url: string) => boolean;
/**
* The BlobLike type omits arrayBuffer() because `@types/node-fetch@^2.6.4` lacks it; but this check
* adds the arrayBuffer() method type because it is available and used at runtime
*/
export declare const isBlobLike: (value: any) => value is BlobLike & {
arrayBuffer(): Promise<ArrayBuffer>;
};
export declare function isEmptyObj(obj: NonNullable<unknown> | null | undefined): boolean;
export declare const isFileLike: (value: any) => value is FileLike;
export declare const isMultipartBody: (body: any) => body is MultipartBody;
export declare const isRequestOptions: (obj: unknown) => obj is RequestOptions<Record<string, unknown> | Readable>;
export declare const isResponseLike: (value: any) => value is ResponseLike;
export declare const isUploadable: (value: any) => value is Uploadable;
export declare type KeysEnum<T> = {
[P in keyof Required<T>]: true;
};
/**
* A re-implementation of httpx's `LineDecoder` in Python that handles incrementally
* reading lines from text.
*
* https://github.com/encode/httpx/blob/920333ea98118e9cf617f246905d7b202510941c/httpx/_decoders.py#L258
*/
export declare class LineDecoder {
static NEWLINE_CHARS: Set<string>;
static NEWLINE_REGEXP: RegExp;
buffer: string[];
trailingCR: boolean;
textDecoder: any;
constructor();
decode(chunk: Bytes): string[];
decodeText(bytes: Bytes): string;
flush(): string[];
}
/**
* Returns a multipart/form-data request if any part of the given request body contains a File / Blob value.
* Otherwise returns the request as is.
*/
export declare const maybeMultipartFormRequestOptions: <T extends {} = Record<string, unknown>>(opts: RequestOptions<T>) => Promise<RequestOptions<MultipartBody | T>>;
/**
* Disclaimer: modules in _shims aren't intended to be imported by SDK users.
*/
declare class MultipartBody {
body: any;
constructor(body: any);
get [Symbol.toStringTag](): string;
}
export declare const multipartFormRequestOptions: <T extends {} = Record<string, unknown>>(opts: RequestOptions<T>) => Promise<RequestOptions<MultipartBody | T>>;
export declare class NotFoundError extends APIError {
readonly status: 404;
}
export declare type PageInfo = {
url: URL;
} | {
params: Record<string, unknown> | null;
};
/**
* This subclass of Promise will resolve to an instantiated Page once the request completes.
*
* It also implements AsyncIterable to allow auto-paginating iteration on an unawaited list call, eg:
*
* ```ts
* for await (const item of client.items.list()) {
* console.log(item)
* }
* ```
*/
export declare class PagePromise<PageClass extends AbstractPage<Item>, Item = ReturnType<PageClass['getPaginatedItems']>[number]> extends APIPromise<PageClass> implements AsyncIterable<Item> {
constructor(client: APIClient, request: Promise<APIResponseProps>, Page: new (...args: ConstructorParameters<typeof AbstractPage>) => PageClass);
/**
* Allow auto-paginating iteration on an un awaited list call, eg:
*
* ```ts
* for await (const item of client.items.list()) {
* console.log(item)
* }
* ```
*/
[Symbol.asyncIterator](): AsyncGenerator<Awaited<Item>, void, unknown>;
}
export declare class PermissionDeniedError extends APIError {
readonly status: 403;
}
export declare type PromiseOrValue<T> = T | Promise<T>;
export declare class RateLimitError extends APIError {
readonly status: 429;
}
/**
* Most browsers don't yet have async iterable support for ReadableStream,
* and Node has a very different way of reading bytes from its "ReadableStream".
*
* This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490
*/
export declare function readableStreamAsyncIterable<T>(stream: any): AsyncIterableIterator<T>;
export declare type ReqHeaders = Record<string, string | null | undefined>;
export declare type RequestClient = {
fetch: Fetch;
};
declare type RequestInfo_2 = globalThis.RequestInfo;
declare type RequestInit_2 = globalThis.RequestInit;
export declare type RequestOptions<Req extends NonNullable<unknown> = Record<string, unknown> | Readable> = {
method?: HTTPMethod;
path?: string;
query?: Req | undefined;
body?: Req | undefined;
headers?: ReqHeaders | undefined;
maxRetries?: number;
stream?: boolean | undefined;
timeout?: number;
httpAgent?: Agent;
signal?: AbortSignal | undefined | null;
idempotencyKey?: string;
duplex?: 'half' | 'full' | undefined;
__binaryResponse?: boolean | undefined;
};
/**
* Intended to match web.Response, node.Response, node-fetch.Response, etc.
*/
export declare interface ResponseLike {
url: string;
blob(): Promise<BlobLike>;
}
export declare const safeJSON: (text: string) => any;
export declare type ServerSentEvent = {
event: string | null;
data: string;
raw: string[];
};
export declare const sleep: (ms: number) => Promise<unknown>;
export declare class SSEDecoder {
private data;
private event;
private chunks;
constructor();
decode(line: string): ServerSentEvent | null;
}
export declare class Stream<Item> implements AsyncIterable<Item> {
private iterator;
controller: AbortController;
constructor(iterator: () => AsyncIterator<Item>, controller: AbortController);
static fromSSEResponse<Item>(response: Response, controller: AbortController): Stream<Item>;
/**
* Generates a Stream from a newline-separated ReadableStream
* where each item is a JSON value.
*/
static fromReadableStream<Item>(readableStream: globalThis.ReadableStream, controller: AbortController): Stream<Item>;
[Symbol.asyncIterator](): AsyncIterator<Item>;
/**
* Splits the stream into two streams which can be
* independently read from at different speeds.
*/
tee(): [Stream<Item>, Stream<Item>];
/**
* Converts this stream to a newline-separated ReadableStream of
* JSON stringified values in the stream
* which can be turned back into a Stream with `Stream.fromReadableStream()`.
*/
toReadableStream(): globalThis.ReadableStream;
}
/**
* Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats
* @param value - the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s
* @param name - the name of the file. If omitted, toFile will try to determine a file name from bits if possible
* @param options - additional properties
* @returns a {@link File} with the given properties
*/
export declare function toFile(value: ToFileInput | PromiseLike<ToFileInput>, name?: string | null | undefined, options?: FilePropertyBag | undefined): Promise<FileLike>;
export declare type ToFileInput = Uploadable | Exclude<BlobLikePart, string> | AsyncIterable<BlobLikePart>;
export declare class UnprocessableEntityError extends APIError {
readonly status: 422;
}
/**
* Typically, this is a native "File" class.
*
* We provide the {@link toFile} utility to convert a variety of objects
* into the File class.
*
* For convenience, you can also pass a fetch Response, or in Node,
* the result of fs.createReadStream().
*/
export declare type Uploadable = FileLike | ResponseLike | ReadStream;
/**
* https://stackoverflow.com/a/2117523
*/
export declare const uuid4: () => string;
export declare const validatePositiveInteger: (name: string, n: unknown) => number;
export { }