unstructured-client
Version:
<h3 align="center"> <img src="https://raw.githubusercontent.com/Unstructured-IO/unstructured/main/img/unstructured_logo.png" height="200" > </h3>
147 lines • 4.82 kB
JavaScript
/*
* Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.
*/
import { isConnectionError, isTimeoutError } from "./http.js";
const defaultBackoff = {
initialInterval: 500,
maxInterval: 60000,
exponent: 1.5,
maxElapsedTime: 3600000,
};
/**
* PermanentError is an error that is not recoverable. Throwing this error will
* cause a retry loop to terminate.
*/
export class PermanentError extends Error {
constructor(message, options) {
let msg = message;
if (options?.cause) {
msg += `: ${options.cause}`;
}
super(msg, options);
this.name = "PermanentError";
// In older runtimes, the cause field would not have been assigned through
// the super() call.
if (typeof this.cause === "undefined") {
this.cause = options?.cause;
}
Object.setPrototypeOf(this, PermanentError.prototype);
}
}
/**
* TemporaryError is an error is used to signal that an HTTP request can be
* retried as part of a retry loop. If retry attempts are exhausted and this
* error is thrown, the response will be returned to the caller.
*/
export class TemporaryError extends Error {
constructor(message, response) {
super(message);
this.response = response;
this.name = "TemporaryError";
Object.setPrototypeOf(this, TemporaryError.prototype);
}
}
export async function retry(fetchFn, options) {
switch (options.config.strategy) {
case "backoff":
return retryBackoff(wrapFetcher(fetchFn, {
statusCodes: options.statusCodes,
retryConnectionErrors: !!options.config.retryConnectionErrors,
}), options.config.backoff ?? defaultBackoff);
default:
return await fetchFn();
}
}
function wrapFetcher(fn, options) {
return async () => {
try {
const res = await fn();
if (isRetryableResponse(res, options.statusCodes)) {
throw new TemporaryError("Response failed with retryable status code", res);
}
return res;
}
catch (err) {
if (err instanceof TemporaryError) {
throw err;
}
if (options.retryConnectionErrors &&
(isTimeoutError(err) || isConnectionError(err))) {
throw err;
}
throw new PermanentError("Permanent error", { cause: err });
}
};
}
const codeRangeRE = new RegExp("^[0-9]xx$", "i");
function isRetryableResponse(res, statusCodes) {
const actual = `${res.status}`;
return statusCodes.some((code) => {
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;
});
}
async function retryBackoff(fn, strategy) {
const { maxElapsedTime, initialInterval, exponent, maxInterval } = strategy;
const start = Date.now();
let x = 0;
while (true) {
try {
const res = await fn();
return res;
}
catch (err) {
if (err instanceof PermanentError) {
throw err.cause;
}
const elapsed = Date.now() - start;
if (elapsed > maxElapsedTime) {
if (err instanceof TemporaryError) {
return err.response;
}
throw err;
}
let retryInterval = 0;
if (err instanceof TemporaryError) {
retryInterval = retryIntervalFromResponse(err.response);
}
if (retryInterval <= 0) {
retryInterval =
initialInterval * Math.pow(x, exponent) + Math.random() * 1000;
}
const d = Math.min(retryInterval, maxInterval);
await delay(d);
x++;
}
}
}
function retryIntervalFromResponse(res) {
const retryVal = res.headers.get("retry-after") || "";
if (!retryVal) {
return 0;
}
const parsedNumber = Number(retryVal);
if (Number.isInteger(parsedNumber)) {
return parsedNumber * 1000;
}
const parsedDate = Date.parse(retryVal);
if (Number.isInteger(parsedDate)) {
const deltaMS = parsedDate - Date.now();
return deltaMS > 0 ? Math.ceil(deltaMS) : 0;
}
return 0;
}
async function delay(delay) {
return new Promise((resolve) => setTimeout(resolve, delay));
}
//# sourceMappingURL=retries.js.map