@graphql-mesh/runtime
Version:
170 lines (169 loc) • 8 kB
JavaScript
import { BREAK, execute, visit } from 'graphql';
import { compileQuery, isCompiledQuery } from 'graphql-jit';
import { mapAsyncIterator } from '@envelop/core';
import { applyRequestTransforms, applyResultTransforms } from '@graphql-mesh/utils';
import { applySchemaTransforms, createDefaultExecutor, } from '@graphql-tools/delegate';
import { getDefinedRootType, getOperationASTFromRequest, isAsyncIterable, isPromise, memoize1, printSchemaWithDirectives, } from '@graphql-tools/utils';
var IntrospectionQueryType;
(function (IntrospectionQueryType) {
IntrospectionQueryType["FEDERATION"] = "FEDERATION";
IntrospectionQueryType["REGULAR"] = "REGULAR";
IntrospectionQueryType["STREAM"] = "STREAM";
})(IntrospectionQueryType || (IntrospectionQueryType = {}));
const getIntrospectionOperationType = memoize1(function getIntrospectionOperationType(operationAST) {
let introspectionQueryType = null;
if (operationAST.operation === 'query' && operationAST.selectionSet.selections.length === 1) {
visit(operationAST, {
Field: (node) => {
if (node.name.value === '__schema' || node.name.value === '__type') {
introspectionQueryType = IntrospectionQueryType.REGULAR;
return BREAK;
}
if (node.name.value === '_service') {
introspectionQueryType = IntrospectionQueryType.FEDERATION;
return BREAK;
}
if (node.directives?.some(d => d.name.value === 'stream')) {
introspectionQueryType = IntrospectionQueryType.STREAM;
return BREAK;
}
},
});
}
return introspectionQueryType;
});
function getExecuteFn(subschema) {
const compiledQueryCache = new WeakMap();
const transformedDocumentNodeCache = new WeakMap();
return function subschemaExecute(args) {
const originalRequest = {
document: args.document,
variables: args.variableValues,
operationName: args.operationName ?? undefined,
rootValue: args.rootValue,
context: args.contextValue,
};
const operationAST = getOperationASTFromRequest(originalRequest);
// TODO: We need more elegant solution
const introspectionQueryType = getIntrospectionOperationType(operationAST);
if (introspectionQueryType === IntrospectionQueryType.FEDERATION) {
const executionResult = {
data: {
_service: {
sdl: printSchemaWithDirectives(args.schema),
},
},
};
return executionResult;
}
else if (introspectionQueryType === IntrospectionQueryType.REGULAR) {
return execute(args);
}
const isStream = introspectionQueryType === IntrospectionQueryType.STREAM;
const delegationContext = {
subschema,
subschemaConfig: subschema,
targetSchema: args.schema,
operation: operationAST.operation,
fieldName: '', // Might not work
context: args.contextValue,
rootValue: args.rootValue,
transforms: subschema.transforms,
transformedSchema: subschema.transformedSchema,
skipTypeMerging: true,
returnType: getDefinedRootType(args.schema, operationAST.operation),
};
let executor = subschema.executor;
if (executor == null) {
if (isStream || operationAST.operation === 'subscription') {
executor = createDefaultExecutor(subschema.schema);
}
else {
executor = function subschemaExecutor(request) {
let compiledQuery = compiledQueryCache.get(request.document);
if (!compiledQuery) {
const compilationResult = compileQuery(subschema.schema, request.document, request.operationName, {
// TODO: Disable for now
customJSONSerializer: false,
disableLeafSerialization: true,
});
if (!isCompiledQuery(compilationResult)) {
return compilationResult;
}
compiledQuery = compilationResult;
compiledQueryCache.set(request.document, compiledQuery);
}
if (operationAST.operation === 'subscription') {
const result$ = compiledQuery.subscribe(request.rootValue, request.context, request.variables);
if (isPromise(result$)) {
return result$.then(result => {
result.stringify = compiledQuery.stringify;
return result;
});
}
result$.stringify = compiledQuery.stringify;
return result$;
}
const result$ = compiledQuery.query(request.rootValue, request.context, request.variables);
if (isPromise(result$)) {
return result$.then(result => {
result.stringify = compiledQuery.stringify;
return result;
});
}
result$.stringify = compiledQuery.stringify;
return result$;
};
}
}
/*
if (subschema.batch) {
executor = createBatchingExecutor(executor);
}
*/
const transformationContext = {};
const transformedRequest = applyRequestTransforms(originalRequest, delegationContext, transformationContext, subschema.transforms);
const cachedTransfomedDocumentNode = transformedDocumentNodeCache.get(originalRequest.document);
if (cachedTransfomedDocumentNode) {
transformedRequest.document = cachedTransfomedDocumentNode;
}
else {
transformedDocumentNodeCache.set(originalRequest.document, transformedRequest.document);
}
function handleResult(originalResult) {
if (isAsyncIterable(originalResult)) {
return mapAsyncIterator(originalResult, singleResult => applyResultTransforms(singleResult, delegationContext, transformationContext, subschema.transforms));
}
const transformedResult = applyResultTransforms(originalResult, delegationContext, transformationContext, subschema.transforms);
return transformedResult;
}
const originalResult$ = executor(transformedRequest);
if (isPromise(originalResult$)) {
return originalResult$.then(handleResult);
}
return handleResult(originalResult$);
};
}
// Creates an envelop plugin to execute a subschema inside Envelop
export function useSubschema(subschema) {
const executeFn = getExecuteFn(subschema);
const plugin = {
onPluginInit({ setSchema }) {
// To prevent unwanted warnings from stitching
if (!('_transformedSchema' in subschema)) {
subschema.transformedSchema = applySchemaTransforms(subschema.schema, subschema);
}
subschema.transformedSchema.extensions =
subschema.transformedSchema.extensions || subschema.schema.extensions || {};
Object.assign(subschema.transformedSchema.extensions, subschema.schema.extensions);
setSchema(subschema.transformedSchema);
},
onExecute({ setExecuteFn }) {
setExecuteFn(executeFn);
},
onSubscribe({ setSubscribeFn }) {
setSubscribeFn(executeFn);
},
};
return plugin;
}