unstructured-client
Version:
<h3 align="center"> <img src="https://raw.githubusercontent.com/Unstructured-IO/unstructured/main/img/unstructured_logo.png" height="200" > </h3>
324 lines (269 loc) • 8.88 kB
text/typescript
/*
* Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.
*/
export type Fetcher = (
input: RequestInfo | URL,
init?: RequestInit,
) => Promise<Response>;
export type Awaitable<T> = T | Promise<T>;
const DEFAULT_FETCHER: Fetcher = (input, init) => {
// If input is a Request and init is undefined, Bun will discard the method,
// headers, body and other options that were set on the request object.
// Node.js and browers would ignore an undefined init value. This check is
// therefore needed for interop with Bun.
if (init == null) {
return fetch(input);
} else {
return fetch(input, init);
}
};
export type RequestInput = {
/**
* The URL the request will use.
*/
url: URL;
/**
* Options used to create a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request).
*/
options?: RequestInit | undefined;
};
export interface HTTPClientOptions {
fetcher?: Fetcher;
}
export type BeforeRequestHook = (req: Request) => Awaitable<Request | void>;
export type RequestErrorHook = (err: unknown, req: Request) => Awaitable<void>;
export type ResponseHook = (res: Response, req: Request) => Awaitable<void>;
export class HTTPClient {
private fetcher: Fetcher;
private requestHooks: BeforeRequestHook[] = [];
private requestErrorHooks: RequestErrorHook[] = [];
private responseHooks: ResponseHook[] = [];
constructor(private options: HTTPClientOptions = {}) {
this.fetcher = options.fetcher || DEFAULT_FETCHER;
}
async request(request: Request): Promise<Response> {
let req = request;
for (const hook of this.requestHooks) {
const nextRequest = await hook(req);
if (nextRequest) {
req = nextRequest;
}
}
try {
const res = await this.fetcher(req);
for (const hook of this.responseHooks) {
await hook(res, req);
}
return res;
} catch (err) {
for (const hook of this.requestErrorHooks) {
await hook(err, req);
}
throw err;
}
}
/**
* Registers a hook that is called before a request is made. The hook function
* can mutate the request or return a new request. This may be useful to add
* additional information to request such as request IDs and tracing headers.
*/
addHook(hook: "beforeRequest", fn: BeforeRequestHook): this;
/**
* Registers a hook that is called when a request cannot be made due to a
* network error.
*/
addHook(hook: "requestError", fn: RequestErrorHook): this;
/**
* Registers a hook that is called when a response has been received from the
* server.
*/
addHook(hook: "response", fn: ResponseHook): this;
addHook(
...args:
| [hook: "beforeRequest", fn: BeforeRequestHook]
| [hook: "requestError", fn: RequestErrorHook]
| [hook: "response", fn: ResponseHook]
) {
if (args[0] === "beforeRequest") {
this.requestHooks.push(args[1]);
} else if (args[0] === "requestError") {
this.requestErrorHooks.push(args[1]);
} else if (args[0] === "response") {
this.responseHooks.push(args[1]);
} else {
throw new Error(`Invalid hook type: ${args[0]}`);
}
return this;
}
/** Removes a hook that was previously registered with `addHook`. */
removeHook(hook: "beforeRequest", fn: BeforeRequestHook): this;
/** Removes a hook that was previously registered with `addHook`. */
removeHook(hook: "requestError", fn: RequestErrorHook): this;
/** Removes a hook that was previously registered with `addHook`. */
removeHook(hook: "response", fn: ResponseHook): this;
removeHook(
...args:
| [hook: "beforeRequest", fn: BeforeRequestHook]
| [hook: "requestError", fn: RequestErrorHook]
| [hook: "response", fn: ResponseHook]
): this {
let target: unknown[];
if (args[0] === "beforeRequest") {
target = this.requestHooks;
} else if (args[0] === "requestError") {
target = this.requestErrorHooks;
} else if (args[0] === "response") {
target = this.responseHooks;
} else {
throw new Error(`Invalid hook type: ${args[0]}`);
}
const index = target.findIndex((v) => v === args[1]);
if (index >= 0) {
target.splice(index, 1);
}
return this;
}
clone(): HTTPClient {
const child = new HTTPClient(this.options);
child.requestHooks = this.requestHooks.slice();
child.requestErrorHooks = this.requestErrorHooks.slice();
child.responseHooks = this.responseHooks.slice();
return child;
}
}
export type StatusCodePredicate = number | string | (number | string)[];
// A semicolon surrounded by optional whitespace characters is used to separate
// segments in a media type string.
const mediaParamSeparator = /\s*;\s*/g;
export function matchContentType(response: Response, pattern: string): boolean {
// `*` is a special case which means anything is acceptable.
if (pattern === "*") {
return true;
}
let contentType =
response.headers.get("content-type")?.trim() || "application/octet-stream";
contentType = contentType.toLowerCase();
const wantParts = pattern.toLowerCase().trim().split(mediaParamSeparator);
const [wantType = "", ...wantParams] = wantParts;
if (wantType.split("/").length !== 2) {
return false;
}
const gotParts = contentType.split(mediaParamSeparator);
const [gotType = "", ...gotParams] = gotParts;
const [type = "", subtype = ""] = gotType.split("/");
if (!type || !subtype) {
return false;
}
if (
wantType !== "*/*" &&
gotType !== wantType &&
`${type}/*` !== wantType &&
`*/${subtype}` !== wantType
) {
return false;
}
if (gotParams.length < wantParams.length) {
return false;
}
const params = new Set(gotParams);
for (const wantParam of wantParams) {
if (!params.has(wantParam)) {
return false;
}
}
return true;
}
const codeRangeRE = new RegExp("^[0-9]xx$", "i");
export function matchStatusCode(
response: Response,
codes: StatusCodePredicate,
): boolean {
const actual = `${response.status}`;
const expectedCodes = Array.isArray(codes) ? codes : [codes];
if (!expectedCodes.length) {
return false;
}
return expectedCodes.some((ec) => {
const code = `${ec}`;
if (code === "default") {
return true;
}
if (!codeRangeRE.test(`${code}`)) {
return code === actual;
}
const expectFamily = code.charAt(0);
if (!expectFamily) {
throw new Error("Invalid status code range");
}
const actualFamily = actual.charAt(0);
if (!actualFamily) {
throw new Error(`Invalid response status code: ${actual}`);
}
return actualFamily === expectFamily;
});
}
export function matchResponse(
response: Response,
code: StatusCodePredicate,
contentTypePattern: string,
): boolean {
return (
matchStatusCode(response, code) &&
matchContentType(response, contentTypePattern)
);
}
/**
* Uses various heurisitics to determine if an error is a connection error.
*/
export function isConnectionError(err: unknown): boolean {
if (typeof err !== "object" || err == null) {
return false;
}
// Covers fetch in Deno as well
const isBrowserErr =
err instanceof TypeError &&
err.message.toLowerCase().startsWith("failed to fetch");
const isNodeErr =
err instanceof TypeError &&
err.message.toLowerCase().startsWith("fetch failed");
const isBunErr = "name" in err && err.name === "ConnectionError";
const isGenericErr =
"code" in err &&
typeof err.code === "string" &&
err.code.toLowerCase() === "econnreset";
return isBrowserErr || isNodeErr || isGenericErr || isBunErr;
}
/**
* Uses various heurisitics to determine if an error is a timeout error.
*/
export function isTimeoutError(err: unknown): boolean {
if (typeof err !== "object" || err == null) {
return false;
}
// Fetch in browser, Node.js, Bun, Deno
const isNative = "name" in err && err.name === "TimeoutError";
const isLegacyNative = "code" in err && err.code === 23;
// Node.js HTTP client and Axios
const isGenericErr =
"code" in err &&
typeof err.code === "string" &&
err.code.toLowerCase() === "econnaborted";
return isNative || isLegacyNative || isGenericErr;
}
/**
* Uses various heurisitics to determine if an error is a abort error.
*/
export function isAbortError(err: unknown): boolean {
if (typeof err !== "object" || err == null) {
return false;
}
// Fetch in browser, Node.js, Bun, Deno
const isNative = "name" in err && err.name === "AbortError";
const isLegacyNative = "code" in err && err.code === 20;
// Node.js HTTP client and Axios
const isGenericErr =
"code" in err &&
typeof err.code === "string" &&
err.code.toLowerCase() === "econnaborted";
return isNative || isLegacyNative || isGenericErr;
}