UNPKG

@graphql-hive/gateway-plugin-console-sdk

Version:

GraphQL Hive + GraphQL Hive Gateway

136 lines (135 loc) • 6.47 kB
import { print, responsePathAsArray } from 'graphql'; import { lru } from 'tiny-lru'; import { autoDisposeSymbol, createHive as createHiveClient, isAsyncIterable, isHiveClient, } from '@graphql-hive/core'; import { addTypenames } from './add-typenames.js'; import { isEntityRequest } from './is-entity-request.js'; import { version } from './version.js'; export function createHive(clientOrOptions) { return createHiveClient(Object.assign(Object.assign({}, clientOrOptions), { agent: Object.assign({ name: 'hive-client-gateway-sdk', version }, clientOrOptions.agent) })); } export function useHive(clientOrOptions) { var _a, _b; const hive = isHiveClient(clientOrOptions) ? clientOrOptions : createHive(Object.assign(Object.assign({}, clientOrOptions), { agent: Object.assign({ name: 'hive-client-gateway-sdk' }, clientOrOptions.agent) })); void hive.info(); /** stores the resulting status from fetches */ const statusMap = new WeakMap(); /** stores the original query SDL to avoid having to print */ const operationCache = lru(isHiveClient(clientOrOptions) ? 10000 : ((_a = clientOrOptions.cache) !== null && _a !== void 0 ? _a : 10000)); if (hive[autoDisposeSymbol]) { if (global.process) { const signals = Array.isArray(hive[autoDisposeSymbol]) ? hive[autoDisposeSymbol] : ['SIGINT', 'SIGTERM']; for (const signal of signals) { process.once(signal, () => hive.dispose()); } } else { console.error('It seems that GraphQL Hive is not being executed in Node.js. ' + 'Please attempt manual client disposal and use autoDispose: false option.'); } } const fieldLevelMetricsEnabled = isHiveClient(clientOrOptions) ? false : ((_b = clientOrOptions.fieldLevelMetricsEnabled) !== null && _b !== void 0 ? _b : false); return { onFetch({ executionRequest }) { /** Only if the execution request is set, then this is a subgraph execution. */ if (!executionRequest) { return; } return function onFetchDone({ response }) { statusMap.set(executionRequest, response.status); }; }, onSubgraphExecute({ executionRequest, subgraphName, subgraph: subgraphSchema }) { var _a, _b; if (!fieldLevelMetricsEnabled) { // short circuit the entire hook to avoid processing this data. return; } const collection = (_a = executionRequest.context) === null || _a === void 0 ? void 0 : _a.__hiveUsageCollection; if (!collection) { // This is set onExecute so this should exist... but just to be safe return; } /** * Note that we need __typename on every abstract type in the subgraph call. * This is added in the "onExecute" hook to the entire document. So subgraph * calls should also include this field. */ const finishSubRequest = collection.subrequest({ subgraph: subgraphName, type: isEntityRequest(executionRequest.document) ? 'ENTITY' : 'ROOT', /** @NOTE this field's format supports batched requests, but onSubgraphExecute does not. */ paths: ((_b = executionRequest.info) === null || _b === void 0 ? void 0 : _b.path) ? [responsePathAsArray(executionRequest.info.path).join('.')] : [], }); return function onSubgraphExecuteDone({ result }) { var _a; if (!isAsyncIterable(result)) { finishSubRequest({ status: (_a = statusMap.get(executionRequest)) !== null && _a !== void 0 ? _a : 200, subgraphSchema, result, document: executionRequest.document, }); } }; }, onSchemaChange({ schema }) { hive.reportSchema({ schema }); }, onExecute({ args }) { var _a; const collection = hive.collectUsage(); // Inject the collection object into the GraphQL context // so it can be accessed downstream by subgraph executions. if (args.contextValue) { args.contextValue.__hiveUsageCollection = collection; } // We need __typename on every object in the subgraph result so we can // resolve abstract types (unions/interfaces) to concrete type coordinates // when recording field-level metrics downstream. // This is done here for more performant caching of the result. const query = (_a = args.contextValue.params.query) !== null && _a !== void 0 ? _a : print(args.document); const cachedDocument = operationCache.get(query); if (cachedDocument) { // If "true" is cached, then this operation doesn't need stored because it's identical to the original. // Else, the document hash been modified and cached if (cachedDocument !== true) { args.document = cachedDocument; } } else { const modifiedDocument = addTypenames(args.document, args.schema); operationCache.set(query, args.document === modifiedDocument || args.document); } return { onExecuteDone({ result }) { if (!isAsyncIterable(result)) { void collection.finish(args, result); return; } const errors = []; return { onNext(ctx) { if (ctx.result.errors) { errors.push(...ctx.result.errors); } }, onEnd() { void collection.finish(args, errors.length ? { errors } : {}); }, }; }, }; }, onSubscribe({ args }) { hive.collectSubscriptionUsage({ args }); }, }; }