UNPKG

@graphql-hive/core

Version:
173 lines (172 loc) • 6.53 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createAgent = createAgent; const version_js_1 = require("../version.js"); const http_client_js_1 = require("./http-client.js"); const utils_js_1 = require("./utils.js"); const defaultCircuitBreakerConfiguration = { errorThresholdPercentage: 50, volumeThreshold: 10, resetTimeout: 30000, }; function createAgent(pluginOptions, { data, body, headers = () => ({}), }) { var _a; const options = Object.assign(Object.assign({ timeout: 30000, enabled: true, minTimeout: 200, maxRetries: 3, sendInterval: 10000, maxSize: 25, name: 'hive-client', version: version_js_1.version }, pluginOptions), { circuitBreaker: pluginOptions.circuitBreaker == null || pluginOptions.circuitBreaker === true ? defaultCircuitBreakerConfiguration : pluginOptions.circuitBreaker === false ? null : pluginOptions.circuitBreaker }); const logger = (0, utils_js_1.createHiveLogger)((_a = pluginOptions.logger) !== null && _a !== void 0 ? _a : console, '[agent]'); const enabled = options.enabled !== false; let timeoutID = null; function schedule() { if (timeoutID) { clearTimeout(timeoutID); } timeoutID = setTimeout(send, options.sendInterval); } let scheduled = false; let inProgressCaptures = []; function capture(event) { if (event instanceof Promise) { const promise = captureAsync(event); inProgressCaptures.push(promise); void promise.finally(() => { inProgressCaptures = inProgressCaptures.filter(p => p !== promise); }); } else { captureSync(event); } } async function captureAsync(event) { captureSync(await event); } function captureSync(event) { // Calling capture starts the schedule if (!scheduled) { scheduled = true; schedule(); } data.set(event); if (data.size() >= options.maxSize) { logger.debug('Sending immediately'); setImmediate(() => send({ throwOnError: false, skipSchedule: true })); } } function sendImmediately(event) { data.set(event); logger.debug('Sending immediately'); return send({ throwOnError: true, skipSchedule: true }); } async function sendHTTPCall(buffer) { const signal = breaker.getSignal(); return await http_client_js_1.http.post(options.endpoint, buffer, { headers: Object.assign({ accept: 'application/json', 'content-type': 'application/json', Authorization: `Bearer ${options.token}`, 'User-Agent': `${options.name}/${options.version}` }, headers()), timeout: options.timeout, retry: { retries: options.maxRetries, factor: 2, }, logger, fetchImplementation: pluginOptions.fetch, signal, }); } async function send(sendOptions) { if (!data.size() || !enabled) { if (!(sendOptions === null || sendOptions === void 0 ? void 0 : sendOptions.skipSchedule)) { schedule(); } return null; } const buffer = await body(); const dataToSend = data.size(); data.clear(); logger.debug(`Sending report (queue ${dataToSend})`); const response = sendFromBreaker(buffer) .then(res => { logger.debug(`Report sent!`); return res; }) .catch(error => { logger.debug(`Failed to send report.`); if (sendOptions === null || sendOptions === void 0 ? void 0 : sendOptions.throwOnError) { throw error; } return null; }) .finally(() => { if (!(sendOptions === null || sendOptions === void 0 ? void 0 : sendOptions.skipSchedule)) { schedule(); } }); return response; } async function dispose() { logger.debug('Disposing'); if (timeoutID) { clearTimeout(timeoutID); } if (inProgressCaptures.length) { await Promise.all(inProgressCaptures); } await send({ skipSchedule: true, throwOnError: false, }); } let breaker; let loadCircuitBreakerPromise = null; const breakerLogger = (0, utils_js_1.createHiveLogger)(logger, '[circuit breaker]'); function noopBreaker() { return { getSignal() { return undefined; }, fire: sendHTTPCall, }; } if (options.circuitBreaker) { /** * We support Cloudflare, which does not has the `events` module. * So we lazy load opossum which has `events` as a dependency. */ breakerLogger.info('initialize circuit breaker'); loadCircuitBreakerPromise = (0, utils_js_1.loadCircuitBreaker)(CircuitBreaker => { breakerLogger.info('started'); const realBreaker = new CircuitBreaker(sendHTTPCall, Object.assign(Object.assign({}, options.circuitBreaker), { timeout: false, autoRenewAbortController: true })); realBreaker.on('open', () => breakerLogger.error('circuit opened - backend seems unreachable.')); realBreaker.on('halfOpen', () => breakerLogger.info('circuit half open - testing backend connectivity')); realBreaker.on('close', () => breakerLogger.info('circuit closed - backend recovered ')); // @ts-expect-error missing definition in typedefs for `opposum` breaker = realBreaker; }, () => { breakerLogger.info('circuit breaker not supported on platform'); breaker = noopBreaker(); }); } else { breaker = noopBreaker(); } async function sendFromBreaker(...args) { if (!breaker) { await loadCircuitBreakerPromise; } try { return await breaker.fire(...args); } catch (err) { if (err instanceof Error && 'code' in err && err.code === 'EOPENBREAKER') { breakerLogger.info('circuit open - sending report skipped'); return null; } throw err; } } return { capture, sendImmediately, dispose, }; }