UNPKG

@argos-ci/api-client

Version:
229 lines (223 loc) 7.46 kB
// src/index.ts import createFetchClient from "openapi-fetch"; // ../../node_modules/.pnpm/is-network-error@1.1.0/node_modules/is-network-error/index.js var objectToString = Object.prototype.toString; var isError = (value) => objectToString.call(value) === "[object Error]"; var errorMessages = /* @__PURE__ */ new Set([ "network error", // Chrome "Failed to fetch", // Chrome "NetworkError when attempting to fetch resource.", // Firefox "The Internet connection appears to be offline.", // Safari 16 "Load failed", // Safari 17+ "Network request failed", // `cross-fetch` "fetch failed", // Undici (Node.js) "terminated" // Undici (Node.js) ]); function isNetworkError(error) { const isValid = error && isError(error) && error.name === "TypeError" && typeof error.message === "string"; if (!isValid) { return false; } if (error.message === "Load failed") { return error.stack === void 0; } return errorMessages.has(error.message); } // ../../node_modules/.pnpm/p-retry@7.0.0/node_modules/p-retry/index.js function validateRetries(retries) { if (typeof retries === "number") { if (retries < 0) { throw new TypeError("Expected `retries` to be a non-negative number."); } if (Number.isNaN(retries)) { throw new TypeError("Expected `retries` to be a valid number or Infinity, got NaN."); } } else if (retries !== void 0) { throw new TypeError("Expected `retries` to be a number or Infinity."); } } function validateNumberOption(name, value, { min = 0, allowInfinity = false } = {}) { if (value === void 0) { return; } if (typeof value !== "number" || Number.isNaN(value)) { throw new TypeError(`Expected \`${name}\` to be a number${allowInfinity ? " or Infinity" : ""}.`); } if (!allowInfinity && !Number.isFinite(value)) { throw new TypeError(`Expected \`${name}\` to be a finite number.`); } if (value < min) { throw new TypeError(`Expected \`${name}\` to be \u2265 ${min}.`); } } var AbortError = class extends Error { constructor(message) { super(); if (message instanceof Error) { this.originalError = message; ({ message } = message); } else { this.originalError = new Error(message); this.originalError.stack = this.stack; } this.name = "AbortError"; this.message = message; } }; var createRetryContext = (error, attemptNumber, options) => { const retriesLeft = options.retries - (attemptNumber - 1); return Object.freeze({ error, attemptNumber, retriesLeft }); }; function calculateDelay(attempt, options) { const random = options.randomize ? Math.random() + 1 : 1; let timeout = Math.round(random * Math.max(options.minTimeout, 1) * options.factor ** (attempt - 1)); timeout = Math.min(timeout, options.maxTimeout); return timeout; } async function onAttemptFailure(error, attemptNumber, options, startTime, maxRetryTime) { let normalizedError = error; if (!(normalizedError instanceof Error)) { normalizedError = new TypeError(`Non-error was thrown: "${normalizedError}". You should only throw errors.`); } if (normalizedError instanceof AbortError) { throw normalizedError.originalError; } if (normalizedError instanceof TypeError && !isNetworkError(normalizedError)) { throw normalizedError; } const context = createRetryContext(normalizedError, attemptNumber, options); await options.onFailedAttempt(context); const currentTime = Date.now(); if (currentTime - startTime >= maxRetryTime || attemptNumber >= options.retries + 1 || !await options.shouldRetry(context)) { throw normalizedError; } const delayTime = calculateDelay(attemptNumber, options); const timeLeft = maxRetryTime - (currentTime - startTime); if (timeLeft <= 0) { throw normalizedError; } const finalDelay = Math.min(delayTime, timeLeft); if (finalDelay > 0) { await new Promise((resolve, reject) => { const onAbort = () => { clearTimeout(timeoutToken); options.signal?.removeEventListener("abort", onAbort); reject(options.signal.reason); }; const timeoutToken = setTimeout(() => { options.signal?.removeEventListener("abort", onAbort); resolve(); }, finalDelay); if (options.unref) { timeoutToken.unref?.(); } options.signal?.addEventListener("abort", onAbort, { once: true }); }); } options.signal?.throwIfAborted(); } async function pRetry(input, options = {}) { options = { ...options }; validateRetries(options.retries); if (Object.hasOwn(options, "forever")) { throw new Error("The `forever` option is no longer supported. For many use-cases, you can set `retries: Infinity` instead."); } options.retries ??= 10; options.factor ??= 2; options.minTimeout ??= 1e3; options.maxTimeout ??= Number.POSITIVE_INFINITY; options.randomize ??= false; options.onFailedAttempt ??= () => { }; options.shouldRetry ??= () => true; validateNumberOption("factor", options.factor, { min: 0, allowInfinity: false }); validateNumberOption("minTimeout", options.minTimeout, { min: 0, allowInfinity: false }); validateNumberOption("maxTimeout", options.maxTimeout, { min: 0, allowInfinity: true }); const resolvedMaxRetryTime = options.maxRetryTime ?? Number.POSITIVE_INFINITY; validateNumberOption("maxRetryTime", resolvedMaxRetryTime, { min: 0, allowInfinity: true }); if (!(options.factor > 0)) { options.factor = 1; } options.signal?.throwIfAborted(); let attemptNumber = 0; const startTime = Date.now(); const maxRetryTime = resolvedMaxRetryTime; while (attemptNumber < options.retries + 1) { attemptNumber++; try { options.signal?.throwIfAborted(); const result = await input(attemptNumber); options.signal?.throwIfAborted(); return result; } catch (error) { await onAttemptFailure(error, attemptNumber, options, startTime, maxRetryTime); } } throw new Error("Retry attempts exhausted without throwing an error."); } // src/debug.ts import createDebug from "debug"; var KEY = "@argos-ci/api-client"; var debug = createDebug(KEY); // src/schema.ts var schema_exports = {}; // src/index.ts function createClient(options) { const { baseUrl } = options || {}; return createFetchClient({ baseUrl: baseUrl || "https://api.argos-ci.com/v2/", headers: { Authorization: `Bearer ${options.authToken}` }, fetch: (input) => { return pRetry( async () => { const response = await fetch(input.clone()); if (response.status >= 500) { throw new APIError("Internal Server Error"); } return response; }, { retries: 3, onFailedAttempt: (context) => { debug("API request failed", context.error.message); if (context.retriesLeft > 0) { debug(`Retrying API request... (${context.retriesLeft} left)`); } } } ); } }); } var APIError = class extends Error { constructor(message) { super(message); } }; function throwAPIError(error) { debug("API error", error); const detailMessage = error.details?.map((detail) => detail.message).join(", "); throw new APIError( detailMessage ? `${error.error}: ${detailMessage}` : error.error ); } export { APIError, schema_exports as ArgosAPISchema, createClient, throwAPIError };