"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AsyncCaller = void 0; const p_retry_1 = __importDefault(require("p-retry")); const p_queue_1 = __importDefault(require("p-queue")); const STATUS_NO_RETRY = [ 400, 401, 403, 404, 405, 406, 407, 408, 409, // Conflict ]; /** * A class that can be used to make async calls with concurrency and retry logic. * * This is useful for making calls to any kind of "expensive" external resource, * be it because it's rate-limited, subject to network issues, etc. * * Concurrent calls are limited by the `maxConcurrency` parameter, which defaults * to `Infinity`. This means that by default, all calls will be made in parallel. * * Retries are limited by the `maxRetries` parameter, which defaults to 6. This * means that by default, each call will be retried up to 6 times, with an * exponential backoff between each attempt. */ class AsyncCaller { constructor(params) { Object.defineProperty(this, "maxConcurrency", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "maxRetries", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "queue", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.maxConcurrency = params.maxConcurrency ?? Infinity; this.maxRetries = params.maxRetries ?? 6; const PQueue = "default" in p_queue_1.default ? p_queue_1.default.default : p_queue_1.default; this.queue = new PQueue({ concurrency: this.maxConcurrency }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any call(callable, ...args) { return this.queue.add(() => (0, p_retry_1.default)(() => callable(...args).catch((error) => { // eslint-disable-next-line no-instanceof/no-instanceof if (error instanceof Error) { throw error; } else { throw new Error(error); } }), { onFailedAttempt(error) { if (error.message.startsWith("Cancel") || error.message.startsWith("TimeoutError") || error.message.startsWith("AbortError")) { throw error; } // eslint-disable-next-line @typescript-eslint/no-explicit-any if (error?.code === "ECONNABORTED") { throw error; } // eslint-disable-next-line @typescript-eslint/no-explicit-any const status = error?.response?.status; if (status && STATUS_NO_RETRY.includes(+status)) { throw error; } }, retries: this.maxRetries, randomize: true, // If needed we can change some of the defaults here, // but they're quite sensible. }), { throwOnTimeout: true }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any callWithOptions(options, callable, ...args) { // Note this doesn't cancel the underlying request, // when available prefer to use the signal option of the underlying call if (options.signal) { return Promise.race([ this.call(callable, ...args), new Promise((_, reject) => { options.signal?.addEventListener("abort", () => { reject(new Error("AbortError")); }); }), ]); } return this.call(callable, ...args); } fetch(...args) { return this.call(() => fetch(...args).then((res) => (res.ok ? res : Promise.reject(res)))); } } exports.AsyncCaller = AsyncCaller;