diff --git a/.changeset/beige-teams-spend.md b/.changeset/beige-teams-spend.md new file mode 100644 index 00000000000..5dab7888bdf --- /dev/null +++ b/.changeset/beige-teams-spend.md @@ -0,0 +1,38 @@ +--- +'@graphql-hive/envelop': minor +'@graphql-hive/apollo': minor +'@graphql-hive/core': minor +'@graphql-hive/yoga': minor +--- + +Support circuit breaking for usage reporting. + +Circuit breaking is a fault-tolerance pattern that prevents a system from repeatedly calling a failing service. When errors or timeouts exceed a set threshold, the circuit “opens,” blocking further requests until the service recovers. + +This ensures that during a network issue or outage, the service using the Hive SDK remains healthy and is not overwhelmed by failed usage reports or repeated retries. + +```ts +import { createClient } from "@graphql-hive/core" + +const client = createClient({ + agent: { + circuitBreaker: { + /** + * Count of requests before starting evaluating. + * Default: 5 + */ + volumeThreshold: 5, + /** + * Percentage of requests failing before the circuit breaker kicks in. + * Default: 50 + */ + errorThresholdPercentage: 1, + /** + * After what time the circuit breaker is attempting to retry sending requests in milliseconds + * Default: 30_000 + */ + resetTimeout: 10_000, + }, + } +}) +``` diff --git a/packages/libraries/core/package.json b/packages/libraries/core/package.json index bfd3cc6d438..91204035344 100644 --- a/packages/libraries/core/package.json +++ b/packages/libraries/core/package.json @@ -45,11 +45,13 @@ "graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" }, "dependencies": { + "@graphql-hive/signal": "^2.0.0", "@graphql-tools/utils": "^10.0.0", "@whatwg-node/fetch": "^0.10.6", "async-retry": "^1.3.3", "js-md5": "0.8.3", "lodash.sortby": "^4.7.0", + "opossum": "^9.0.0", "tiny-lru": "^8.0.2" }, "devDependencies": { @@ -58,6 +60,7 @@ "@types/async-retry": "1.4.8", "@types/js-md5": "0.8.0", "@types/lodash.sortby": "4.7.9", + "@types/opossum": "8.1.9", "graphql": "16.9.0", "nock": "14.0.10", "tslib": "2.8.1", diff --git a/packages/libraries/core/playground/agent-circuit-breaker.ts b/packages/libraries/core/playground/agent-circuit-breaker.ts new file mode 100644 index 00000000000..c2661a82326 --- /dev/null +++ b/packages/libraries/core/playground/agent-circuit-breaker.ts @@ -0,0 +1,52 @@ +/** + * + * Just a small playground to play around with different scenarios arounf the agent. + * You can run it like this: `bun run --watch packages/libraries/core/playground/agent-circuit-breaker.ts` + */ + +import { createAgent } from '../src/client/agent.js'; + +let data: Array<{}> = []; + +const agent = createAgent<{}>( + { + debug: true, + endpoint: 'http://127.0.0.1', + token: 'noop', + async fetch(_url, _opts) { + // throw new Error('FAIL FAIL'); + console.log('SENDING!'); + return new Response('ok', { + status: 200, + }); + }, + circuitBreaker: { + errorThresholdPercentage: 1, + resetTimeout: 10_000, + volumeThreshold: 0, + }, + maxSize: 1, + maxRetries: 0, + }, + { + body() { + data = []; + return String(data); + }, + data: { + clear() { + data = []; + }, + size() { + return data.length; + }, + set(d) { + data.push(d); + }, + }, + }, +); + +setInterval(() => { + agent.capture({}); +}, 1_000); diff --git a/packages/libraries/core/src/client/agent.ts b/packages/libraries/core/src/client/agent.ts index 2ccc0d205b2..459939289e3 100644 --- a/packages/libraries/core/src/client/agent.ts +++ b/packages/libraries/core/src/client/agent.ts @@ -1,9 +1,35 @@ +import { fetch as defaultFetch } from '@whatwg-node/fetch'; import { version } from '../version.js'; import { http } from './http-client.js'; import type { Logger } from './types.js'; +import { CircuitBreakerInterface, createHiveLogger, loadCircuitBreaker } from './utils.js'; type ReadOnlyResponse = Pick; +export type AgentCircuitBreakerConfiguration = { + /** + * Percentage after what the circuit breaker should kick in. + * Default: 50 + */ + errorThresholdPercentage: number; + /** + * Count of requests before starting evaluating. + * Default: 5 + */ + volumeThreshold: number; + /** + * After what time the circuit breaker is attempting to retry sending requests in milliseconds + * Default: 30_000 + */ + resetTimeout: number; +}; + +const defaultCircuitBreakerConfiguration: AgentCircuitBreakerConfiguration = { + errorThresholdPercentage: 50, + volumeThreshold: 10, + resetTimeout: 30_000, +}; + export interface AgentOptions { enabled?: boolean; name?: string; @@ -48,7 +74,14 @@ export interface AgentOptions { * WHATWG Compatible fetch implementation * used by the agent to send reports */ - fetch?: typeof fetch; + fetch?: typeof defaultFetch; + /** + * Circuit Breaker Configuration. + * true -> Use default configuration + * false -> Disable + * object -> use custom configuration see {AgentCircuitBreakerConfiguration} + */ + circuitBreaker?: boolean | AgentCircuitBreakerConfiguration; } export function createAgent( @@ -67,7 +100,9 @@ export function createAgent( headers?(): Record; }, ) { - const options: Required> = { + const options: Required> & { + circuitBreaker: null | AgentCircuitBreakerConfiguration; + } = { timeout: 30_000, debug: false, enabled: true, @@ -75,15 +110,21 @@ export function createAgent( maxRetries: 3, sendInterval: 10_000, maxSize: 25, - logger: console, name: 'hive-client', version, ...pluginOptions, + circuitBreaker: + pluginOptions.circuitBreaker == null || pluginOptions.circuitBreaker === true + ? defaultCircuitBreakerConfiguration + : pluginOptions.circuitBreaker === false + ? null + : pluginOptions.circuitBreaker, + logger: createHiveLogger(pluginOptions.logger ?? console, '[agent]'), }; const enabled = options.enabled !== false; - let timeoutID: any = null; + let timeoutID: ReturnType | null = null; function schedule() { if (timeoutID) { @@ -143,6 +184,27 @@ export function createAgent( return send({ throwOnError: true, skipSchedule: true }); } + async function sendHTTPCall(buffer: string | Buffer): Promise { + const signal = breaker.getSignal(); + return await http.post(options.endpoint, buffer, { + headers: { + 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: options.logger, + fetchImplementation: pluginOptions.fetch, + signal, + }); + } + async function send(sendOptions?: { throwOnError?: boolean; skipSchedule: boolean; @@ -160,23 +222,7 @@ export function createAgent( data.clear(); debugLog(`Sending report (queue ${dataToSend})`); - const response = await http - .post(options.endpoint, buffer, { - headers: { - 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: options.logger, - fetchImplementation: pluginOptions.fetch, - }) + const response = sendFromBreaker(buffer) .then(res => { debugLog(`Report sent!`); return res; @@ -215,6 +261,74 @@ export function createAgent( }); } + let breaker: CircuitBreakerInterface< + Parameters, + ReturnType + >; + let loadCircuitBreakerPromise: Promise | null = null; + const breakerLogger = createHiveLogger(options.logger, '[circuit breaker]'); + + function noopBreaker(): typeof breaker { + 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 = loadCircuitBreaker( + CircuitBreaker => { + breakerLogger.info('started'); + const realBreaker = new CircuitBreaker(sendHTTPCall, { + ...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: Parameters) { + if (!breaker) { + await loadCircuitBreakerPromise; + } + + try { + return await breaker.fire(...args); + } catch (err: unknown) { + if (err instanceof Error && 'code' in err && err.code === 'EOPENBREAKER') { + breakerLogger.info('circuit open - sending report skipped'); + return null; + } + + throw err; + } + } + return { capture, sendImmediately, diff --git a/packages/libraries/core/src/client/http-client.ts b/packages/libraries/core/src/client/http-client.ts index 24908656042..503bdaa5d07 100644 --- a/packages/libraries/core/src/client/http-client.ts +++ b/packages/libraries/core/src/client/http-client.ts @@ -1,4 +1,5 @@ import asyncRetry from 'async-retry'; +import { abortSignalAny } from '@graphql-hive/signal'; import { crypto, fetch, URL } from '@whatwg-node/fetch'; import type { Logger } from './types.js'; @@ -21,6 +22,8 @@ interface SharedConfig { * @default {response => response.ok} **/ isRequestOk?: ResponseAssertFunction; + /** Optional abort signal */ + signal?: AbortSignal; } /** @@ -78,6 +81,8 @@ export async function makeFetchCall( * @default {response => response.ok} **/ isRequestOk?: ResponseAssertFunction; + /** Optional abort signal */ + signal?: AbortSignal; }, ): Promise { const logger = config.logger; @@ -87,6 +92,9 @@ export async function makeFetchCall( let maxTimeout = 2000; let factor = 1.2; + const actionHeader = + config.method === 'POST' ? { 'x-client-action-id': crypto.randomUUID() } : undefined; + if (config.retry !== false) { retries = config.retry?.retries ?? 5; minTimeout = config.retry?.minTimeout ?? 200; @@ -104,13 +112,15 @@ export async function makeFetchCall( ); const getDuration = measureTime(); - const signal = AbortSignal.timeout(config.timeout ?? 20_000); + const timeoutSignal = AbortSignal.timeout(config.timeout ?? 20_000); + const signal = config.signal ? abortSignalAny([config.signal, timeoutSignal]) : timeoutSignal; const response = await (config.fetchImplementation ?? fetch)(endpoint, { method: config.method, body: config.body, headers: { 'x-request-id': requestId, + ...actionHeader, ...config.headers, }, signal, @@ -135,6 +145,12 @@ export async function makeFetchCall( throw new Error(`Unexpected HTTP error. (x-request-id=${requestId})`, { cause: error }); }); + if (config.signal?.aborted === true) { + const error = config.signal.reason ?? new Error('Request aborted externally.'); + bail(error); + throw error; + } + if (isRequestOk(response)) { logger?.info( `${config.method} ${endpoint} (x-request-id=${requestId}) succeeded with status ${response.status} ${getDuration()}.`, diff --git a/packages/libraries/core/src/client/utils.ts b/packages/libraries/core/src/client/utils.ts index d67e31b6f06..a1d8ffe4ca7 100644 --- a/packages/libraries/core/src/client/utils.ts +++ b/packages/libraries/core/src/client/utils.ts @@ -1,3 +1,4 @@ +import type CircuitBreaker from 'opossum'; import { crypto, TextEncoder } from '@whatwg-node/fetch'; import { hiveClientSymbol } from './client.js'; import type { HiveClient, HivePluginOptions, Logger } from './types.js'; @@ -226,3 +227,21 @@ export function isLegacyAccessToken(accessToken: string): boolean { return false; } + +export async function loadCircuitBreaker( + success: (breaker: typeof CircuitBreaker) => void, + error: () => void, +): Promise { + const packageName = 'opossum'; + try { + const module = await import(packageName); + success(module.default); + } catch (err) { + error(); + } +} + +export type CircuitBreakerInterface = { + fire(...args: TI): TR; + getSignal(): AbortSignal | undefined; +}; diff --git a/packages/libraries/core/tests/test-utils.ts b/packages/libraries/core/tests/test-utils.ts index 8b4639d5231..62ab7300bf0 100644 --- a/packages/libraries/core/tests/test-utils.ts +++ b/packages/libraries/core/tests/test-utils.ts @@ -6,24 +6,30 @@ export function waitFor(ms: number) { /** helper function to get log lines and replace milliseconds with static value. */ function getLogLines(calls: Array>) { - return calls.map(log => { - let msg: string; - if (typeof log[1] === 'string') { - msg = maskRequestId( - log[1] - // Replace milliseconds with static value - .replace(/\(\d{1,4}ms\)/, '(666ms)') - // Replace stack trace line numbers with static value - .replace(/\(node:net:\d+:\d+\)/, '(node:net:666:666)') - .replace(/\(node:dns:\d+:\d+\)/, '(node:dns:666:666)'), - // request UUIDsu - ); - } else { - msg = String(log[1]); - } + return calls + .map(log => { + let msg: string; + if (typeof log[1] === 'string') { + if (log[1].includes('[circuit breaker]')) { + return null; + } - return '[' + log[0] + ']' + ' ' + msg; - }); + msg = maskRequestId( + log[1] + // Replace milliseconds with static value + .replace(/\(\d{1,4}ms\)/, '(666ms)') + // Replace stack trace line numbers with static value + .replace(/\(node:net:\d+:\d+\)/, '(node:net:666:666)') + .replace(/\(node:dns:\d+:\d+\)/, '(node:dns:666:666)'), + // request UUIDsu + ); + } else { + msg = String(log[1]); + } + + return '[' + log[0] + ']' + ' ' + msg; + }) + .filter(line => !!line); } export function createHiveTestingLogger() { diff --git a/packages/libraries/core/tests/usage.spec.ts b/packages/libraries/core/tests/usage.spec.ts index 3b11d786aa7..d44942a6dce 100644 --- a/packages/libraries/core/tests/usage.spec.ts +++ b/packages/libraries/core/tests/usage.spec.ts @@ -165,11 +165,11 @@ test('should send data to Hive', async () => { http.done(); expect(logger.getLogs()).toMatchInlineSnapshot(` - [INF] [hive][usage] Disposing - [INF] [hive][usage] Sending report (queue 1) - [INF] [hive][usage] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) - [INF] [hive][usage] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms). - [INF] [hive][usage] Report sent! + [INF] [hive][usage][agent] Disposing + [INF] [hive][usage][agent] Sending report (queue 1) + [INF] [hive][usage][agent] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) + [INF] [hive][usage][agent] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms). + [INF] [hive][usage][agent] Report sent! `); // Map @@ -275,11 +275,11 @@ test('should send data to Hive (deprecated endpoint)', async () => { http.done(); expect(logger.getLogs()).toMatchInlineSnapshot(` - [INF] [hive][usage] Disposing - [INF] [hive][usage] Sending report (queue 1) - [INF] [hive][usage] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) - [INF] [hive][usage] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms). - [INF] [hive][usage] Report sent! + [INF] [hive][usage][agent] Disposing + [INF] [hive][usage][agent] Sending report (queue 1) + [INF] [hive][usage][agent] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) + [INF] [hive][usage][agent] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms). + [INF] [hive][usage][agent] Report sent! `); // Map @@ -366,11 +366,11 @@ test('should not leak the exception', { retry: 3 }, async () => { await hive.dispose(); expect(logger.getLogs()).toMatchInlineSnapshot(` - [INF] [hive][usage] Sending report (queue 1) - [INF] [hive][usage] POST http://404.localhost.noop (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) Attempt (1/2) - [ERR] [hive][usage] Error: getaddrinfo ENOTFOUND 404.localhost.noop - [ERR] [hive][usage] POST http://404.localhost.noop (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) failed (666ms). getaddrinfo ENOTFOUND 404.localhost.noop - [INF] [hive][usage] Disposing + [INF] [hive][usage][agent] Sending report (queue 1) + [INF] [hive][usage][agent] POST http://404.localhost.noop (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) Attempt (1/2) + [ERR] [hive][usage][agent] Error: getaddrinfo ENOTFOUND 404.localhost.noop + [ERR] [hive][usage][agent] POST http://404.localhost.noop (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) failed (666ms). getaddrinfo ENOTFOUND 404.localhost.noop + [INF] [hive][usage][agent] Disposing `); }); @@ -536,11 +536,11 @@ test('should send data to Hive at least once when using atLeastOnceSampler', asy http.done(); expect(logger.getLogs()).toMatchInlineSnapshot(` - [INF] [hive][usage] Disposing - [INF] [hive][usage] Sending report (queue 2) - [INF] [hive][usage] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) - [INF] [hive][usage] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms). - [INF] [hive][usage] Report sent! + [INF] [hive][usage][agent] Disposing + [INF] [hive][usage][agent] Sending report (queue 2) + [INF] [hive][usage][agent] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) + [INF] [hive][usage][agent] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms). + [INF] [hive][usage][agent] Report sent! `); // Map @@ -640,11 +640,11 @@ test('should not send excluded operation name data to Hive', async () => { http.done(); expect(logger.getLogs()).toMatchInlineSnapshot(` - [INF] [hive][usage] Disposing - [INF] [hive][usage] Sending report (queue 2) - [INF] [hive][usage] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) - [INF] [hive][usage] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms). - [INF] [hive][usage] Report sent! + [INF] [hive][usage][agent] Disposing + [INF] [hive][usage][agent] Sending report (queue 2) + [INF] [hive][usage][agent] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) + [INF] [hive][usage][agent] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms). + [INF] [hive][usage][agent] Report sent! `); // Map @@ -741,10 +741,10 @@ test('retry on non-200', async () => { await hive.dispose(); expect(logger.getLogs()).toMatchInlineSnapshot(` - [INF] [hive][usage] Sending report (queue 1) - [INF] [hive][usage] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) Attempt (1/2) - [ERR] [hive][usage] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) failed with status 500 (666ms): No no no - [INF] [hive][usage] Disposing + [INF] [hive][usage][agent] Sending report (queue 1) + [INF] [hive][usage][agent] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) Attempt (1/2) + [ERR] [hive][usage][agent] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) failed with status 500 (666ms): No no no + [INF] [hive][usage][agent] Disposing `); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df9c802e539..02a526edd30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -507,6 +507,9 @@ importers: packages/libraries/core: dependencies: + '@graphql-hive/signal': + specifier: ^2.0.0 + version: 2.0.0 '@graphql-tools/utils': specifier: ^10.0.0 version: 10.5.6(graphql@16.9.0) @@ -522,6 +525,9 @@ importers: lodash.sortby: specifier: ^4.7.0 version: 4.7.0 + opossum: + specifier: ^9.0.0 + version: 9.0.0 tiny-lru: specifier: ^8.0.2 version: 8.0.2 @@ -541,6 +547,9 @@ importers: '@types/lodash.sortby': specifier: 4.7.9 version: 4.7.9 + '@types/opossum': + specifier: 8.1.9 + version: 8.1.9 graphql: specifier: 16.9.0 version: 16.9.0 @@ -1436,7 +1445,7 @@ importers: devDependencies: '@graphql-inspector/core': specifier: 6.4.1 - version: 6.4.1(graphql@16.9.0) + version: 6.4.1(graphql@16.11.0) '@hive/service-common': specifier: workspace:* version: link:../service-common @@ -3789,6 +3798,7 @@ packages: '@fastify/vite@6.0.7': resolution: {integrity: sha512-+dRo9KUkvmbqdmBskG02SwigWl06Mwkw8SBDK1zTNH6vd4DyXbRvI7RmJEmBkLouSU81KTzy1+OzwHSffqSD6w==} + bundledDependencies: [] '@floating-ui/core@1.2.6': resolution: {integrity: sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==} @@ -3973,6 +3983,10 @@ packages: resolution: {integrity: sha512-RiwLMc89lTjvyLEivZ/qxAC5nBHoS2CtsWFSOsN35sxG9zoo5Z+JsFHM8MlvmO9yt+MJNIyC5MLE1rsbOphlag==} engines: {node: '>=18.0.0'} + '@graphql-hive/signal@2.0.0': + resolution: {integrity: sha512-Pz8wB3K0iU6ae9S1fWfsmJX24CcGeTo6hE7T44ucmV/ALKRj+bxClmqrYcDT7v3f0d12Rh4FAXBb6gon+WkDpQ==} + engines: {node: '>=20.0.0'} + '@graphql-inspector/audit-command@4.0.3': resolution: {integrity: sha512-cm4EtieIp9PUSDBze+Sn5HHF80jDF9V7sYyXqFa7+Vtw4Jlet98Ig48dFVtoLuFCPtCv2eZ22I8JOkBKL5WgVA==} engines: {node: '>=16.0.0'} @@ -8892,6 +8906,9 @@ packages: '@types/object-hash@3.0.6': resolution: {integrity: sha512-fOBV8C1FIu2ELinoILQ+ApxcUKz4ngq+IWUYrxSGjXzzjUALijilampwkMgEtJ+h2njAW3pi853QpzNVCHB73w==} + '@types/opossum@8.1.9': + resolution: {integrity: sha512-Jm/tYxuJFefiwRYs+/EOsUP3ktk0c8siMgAHPLnA4PXF4wKghzcjqf88dY+Xii5jId5Txw4JV0FMKTpjbd7KJA==} + '@types/oracledb@6.5.2': resolution: {integrity: sha512-kK1eBS/Adeyis+3OlBDMeQQuasIDLUYXsi2T15ccNJ0iyUpQ4xDF7svFu3+bGVrI0CMBUclPciz+lsQR3JX3TQ==} @@ -14270,6 +14287,10 @@ packages: peerDependencies: '@opentelemetry/api': ^1.6.0 + opossum@9.0.0: + resolution: {integrity: sha512-K76U0QkxOfUZamneQuzz+AP0fyfTJcCplZ2oZL93nxeupuJbN4s6uFNbmVCt4eWqqGqRnnowdFuBicJ1fLMVxw==} + engines: {node: ^24 || ^22 || ^20} + optionator@0.9.3: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} @@ -20556,6 +20577,8 @@ snapshots: '@graphql-hive/signal@1.0.0': {} + '@graphql-hive/signal@2.0.0': {} + '@graphql-inspector/audit-command@4.0.3(@graphql-inspector/config@4.0.2(graphql@16.9.0))(@graphql-inspector/loaders@4.0.3(@babel/core@7.22.9)(@graphql-inspector/config@4.0.2(graphql@16.9.0))(graphql@16.9.0))(graphql@16.9.0)(yargs@17.7.2)': dependencies: '@graphql-inspector/commands': 4.0.3(@graphql-inspector/config@4.0.2(graphql@16.9.0))(@graphql-inspector/loaders@4.0.3(@babel/core@7.22.9)(@graphql-inspector/config@4.0.2(graphql@16.9.0))(graphql@16.9.0))(graphql@16.9.0)(yargs@17.7.2) @@ -20629,6 +20652,13 @@ snapshots: object-inspect: 1.12.3 tslib: 2.6.2 + '@graphql-inspector/core@6.4.1(graphql@16.11.0)': + dependencies: + dependency-graph: 1.0.0 + graphql: 16.11.0 + object-inspect: 1.13.2 + tslib: 2.6.2 + '@graphql-inspector/core@6.4.1(graphql@16.9.0)': dependencies: dependency-graph: 1.0.0 @@ -26735,8 +26765,8 @@ snapshots: '@typescript-eslint/parser': 7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3) eslint: 8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0) eslint-config-prettier: 9.1.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-plugin-import@2.29.1)(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) eslint-plugin-jsonc: 2.11.1(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) eslint-plugin-mdx: 3.0.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) @@ -27217,6 +27247,10 @@ snapshots: '@types/object-hash@3.0.6': {} + '@types/opossum@8.1.9': + dependencies: + '@types/node': 22.10.5 + '@types/oracledb@6.5.2': dependencies: '@types/node': 22.10.5 @@ -29669,13 +29703,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-plugin-import@2.29.1)(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)): dependencies: debug: 4.4.1(supports-color@8.1.1) enhanced-resolve: 5.17.1 eslint: 8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0) - eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-core-module: 2.13.1 @@ -29706,14 +29740,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)): + eslint-module-utils@2.8.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: '@typescript-eslint/parser': 7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3) eslint: 8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-plugin-import@2.29.1)(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) transitivePeerDependencies: - supports-color @@ -29729,7 +29763,7 @@ snapshots: eslint: 8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0) eslint-compat-utils: 0.1.2(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)): dependencies: array-includes: 3.1.7 array.prototype.findlastindex: 1.2.3 @@ -29739,7 +29773,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)))(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1(patch_hash=08d9d41d21638cb74d0f9f34877a8839601a4e5a8263066ff23e7032addbcba0)) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -33893,6 +33927,8 @@ snapshots: transitivePeerDependencies: - supports-color + opossum@9.0.0: {} + optionator@0.9.3: dependencies: '@aashutoshrathi/word-wrap': 1.2.6