UNPKG

@graphql-hive/core

Version:
150 lines (149 loc) • 7.62 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.URL = exports.http = void 0; exports.makeFetchCall = makeFetchCall; const tslib_1 = require("tslib"); const async_retry_1 = tslib_1.__importDefault(require("async-retry")); const signal_1 = require("@graphql-hive/signal"); const fetch_1 = require("@whatwg-node/fetch"); Object.defineProperty(exports, "URL", { enumerable: true, get: function () { return fetch_1.URL; } }); function get(endpoint, config) { return makeFetchCall(endpoint, { method: 'GET', headers: config.headers, timeout: config.timeout, retry: config.retry, fetchImplementation: config.fetchImplementation, logger: config.logger, isRequestOk: config.isRequestOk, }); } function post(endpoint, data, config) { return makeFetchCall(endpoint, Object.assign({ body: data, method: 'POST' }, config)); } exports.http = { get, post, }; async function makeFetchCall(endpoint, config) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; const logger = config.logger; const isRequestOk = (_a = config.isRequestOk) !== null && _a !== void 0 ? _a : (response => response.ok); let retries = 0; let minTimeout = 200; let maxTimeout = 2000; let factor = 1.2; const actionHeader = config.method === 'POST' ? { 'x-client-action-id': fetch_1.crypto.randomUUID() } : undefined; if (config.retry !== false) { retries = (_c = (_b = config.retry) === null || _b === void 0 ? void 0 : _b.retries) !== null && _c !== void 0 ? _c : 5; minTimeout = (_e = (_d = config.retry) === null || _d === void 0 ? void 0 : _d.minTimeout) !== null && _e !== void 0 ? _e : 200; maxTimeout = (_g = (_f = config.retry) === null || _f === void 0 ? void 0 : _f.maxTimeout) !== null && _g !== void 0 ? _g : 2000; factor = (_j = (_h = config.retry) === null || _h === void 0 ? void 0 : _h.factor) !== null && _j !== void 0 ? _j : 1.2; } return await (0, async_retry_1.default)(async (bail, attempt) => { var _a, _b, _c, _d, _e, _f, _g; const isFinalAttempt = attempt > retries; const requestId = fetch_1.crypto.randomUUID(); (_a = logger === null || logger === void 0 ? void 0 : logger.debug) === null || _a === void 0 ? void 0 : _a.call(logger, `${config.method} ${endpoint} (x-request-id=${requestId})` + (retries > 0 ? ' ' + getAttemptMessagePart(attempt, retries + 1) : '')); const getDuration = measureTime(); const timeoutSignal = AbortSignal.timeout((_b = config.timeout) !== null && _b !== void 0 ? _b : 20000); const signal = config.signal ? (0, signal_1.abortSignalAny)([config.signal, timeoutSignal]) : timeoutSignal; const response = await ((_c = config.fetchImplementation) !== null && _c !== void 0 ? _c : fetch_1.fetch)(endpoint, { method: config.method, body: config.body, headers: Object.assign(Object.assign({ 'x-request-id': requestId }, actionHeader), config.headers), signal, }).catch((error) => { var _a; const logErrorMessage = () => { var _a; const msg = `${config.method} ${endpoint} (x-request-id=${requestId}) failed ${getDuration()}. ` + getErrorMessage(error); if (isFinalAttempt) { logger === null || logger === void 0 ? void 0 : logger.error(msg); return; } (_a = logger === null || logger === void 0 ? void 0 : logger.debug) === null || _a === void 0 ? void 0 : _a.call(logger, msg); }; if (isAggregateError(error)) { for (const err of error.errors) { if (isFinalAttempt) { logger === null || logger === void 0 ? void 0 : logger.error(err); continue; } (_a = logger === null || logger === void 0 ? void 0 : logger.debug) === null || _a === void 0 ? void 0 : _a.call(logger, String(err)); } logErrorMessage(); throw new Error(`Unexpected HTTP error. (x-request-id=${requestId})`, { cause: error }); } logger === null || logger === void 0 ? void 0 : logger.error(error); logErrorMessage(); throw new Error(`Unexpected HTTP error. (x-request-id=${requestId})`, { cause: error }); }); if (((_d = config.signal) === null || _d === void 0 ? void 0 : _d.aborted) === true) { const error = (_e = config.signal.reason) !== null && _e !== void 0 ? _e : new Error('Request aborted externally.'); bail(error); throw error; } if (isRequestOk(response)) { (_f = logger === null || logger === void 0 ? void 0 : logger.debug) === null || _f === void 0 ? void 0 : _f.call(logger, `${config.method} ${endpoint} (x-request-id=${requestId}) succeeded with status ${response.status} ${getDuration()}.`); return response; } if (isFinalAttempt) { logger === null || logger === void 0 ? void 0 : logger.error(`${config.method} ${endpoint} (x-request-id=${requestId}) failed with status ${response.status} ${getDuration()}: ${(await response.text()) || '<empty response body>'}`); logger === null || logger === void 0 ? void 0 : logger.error(`${config.method} ${endpoint} (x-request-id=${requestId}) retry limit exceeded after ${attempt} attempts.`); } else { (_g = logger === null || logger === void 0 ? void 0 : logger.debug) === null || _g === void 0 ? void 0 : _g.call(logger, `${config.method} ${endpoint} (x-request-id=${requestId}) failed with status ${response.status} ${getDuration()}: ${(await response.text()) || '<empty response body>'}`); } const error = new Error(`${config.method} ${endpoint} (x-request-id=${requestId}) failed with status ${response.status}.`); if (response.status >= 400 && response.status < 500) { if (retries > 0) { logger === null || logger === void 0 ? void 0 : logger.error(`Abort retry because of status code ${response.status}.`); } bail(error); } throw error; }, { retries, minTimeout, maxTimeout, factor, }); } function getErrorMessage(error) { if (error && typeof error === 'object' && 'message' in error) { return String(error.message); } return '<no error message>'; } function getAttemptMessagePart(attempt, retry) { return `Attempt (${attempt}/${retry})`; } function measureTime() { const start = Date.now(); return () => '(' + formatTimestamp(Date.now() - start) + ')'; } function formatTimestamp(timestamp) { const milliseconds = timestamp % 1000; const seconds = Math.floor((timestamp / 1000) % 60); const minutes = Math.floor((timestamp / (1000 * 60)) % 60); const hours = Math.floor(timestamp / (1000 * 60 * 60)); const parts = []; if (hours > 0) { parts.push(`${hours}h`); } if (minutes > 0 || hours > 0) { // Include minutes if hours exist, even if minutes are 0 parts.push(`${minutes}m`); } if (seconds > 0 || minutes > 0 || hours > 0) { parts.push(`${seconds}s`); } parts.push(`${milliseconds}ms`); return parts.join(':'); } function isAggregateError(error) { return !!error && typeof error === 'object' && 'errors' in error && Array.isArray(error.errors); }