@graphql-hive/gateway-plugin-console-sdk
Version:
Hive Console + Hive Gateway
145 lines (144 loc) • 7.26 kB
JavaScript
import { OperationTypeNode, responsePathAsArray, } from 'graphql';
import { lru } from 'tiny-lru';
import { addHiveTypenames, createHive as createHiveClient, getDefinedRootType, hideInjectedTypenames, isAsyncIterable, isHiveClient, } from '@graphql-hive/core';
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();
const fieldLevelMetricsEnabled = isHiveClient(clientOrOptions)
? false
: (typeof clientOrOptions.usage === 'object' &&
((_a = clientOrOptions.usage) === null || _a === void 0 ? void 0 : _a.fieldLevelMetricsEnabled)) ||
false;
/** stores the original query SDL to avoid having to print */
const operationCache = fieldLevelMetricsEnabled
? lru(isHiveClient(clientOrOptions) ? 10000 : ((_b = clientOrOptions.cache) !== null && _b !== void 0 ? _b : 10000))
: null;
let latestSchema = null;
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, _c, _d;
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('.')]
: (_d = getDefinedRootType(subgraphSchema, (_c = executionRequest.operationType) !== null && _c !== void 0 ? _c : OperationTypeNode.QUERY)) === null || _d === void 0 ? void 0 : _d.name,
});
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 });
latestSchema = schema;
operationCache === null || operationCache === void 0 ? void 0 : operationCache.clear();
},
onParse(parseCtx) {
return ctx => {
if (ctx.result.kind === 'Document' && fieldLevelMetricsEnabled && operationCache) {
// 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 = parseCtx.params.source;
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) {
parseCtx.setParsedDocument(cachedDocument);
}
}
else if (latestSchema) {
const modifiedDocument = addHiveTypenames(ctx.result, latestSchema);
operationCache.set(query, ctx.result === modifiedDocument || modifiedDocument);
if (ctx.result !== modifiedDocument) {
parseCtx.setParsedDocument(modifiedDocument);
}
}
}
};
},
onExecute({ args }) {
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;
}
return {
onExecuteDone({ result }) {
if (!isAsyncIterable(result)) {
if (result.data && fieldLevelMetricsEnabled) {
hideInjectedTypenames(result.data);
}
void collection.finish(args, result);
return;
}
const errors = [];
return {
onNext({ result }) {
if (result.data && fieldLevelMetricsEnabled) {
hideInjectedTypenames(result.data);
}
if (result.errors) {
errors.push(...result.errors);
}
},
onEnd() {
void collection.finish(args, errors.length ? { errors } : {});
},
};
},
};
},
onSubscribe({ args }) {
hive.collectSubscriptionUsage({ args });
},
onDispose: async () => {
await hive.dispose();
},
};
}