/*
 * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.
 * @generated-id: b24a69d15639
 */
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 {
    /** The underlying cause of the error. */
    cause;
    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 {
    response;
    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