@graphql-hive/gateway-plugin-console-sdk
Version:
GraphQL Hive + GraphQL Hive Gateway
136 lines (135 loc) • 6.47 kB
JavaScript
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 });
},
};
}