@argos-ci/api-client
Version:
229 lines (223 loc) • 7.46 kB
JavaScript
// 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
};