@graphql-tools/delegate
Version:
A set of utils for faster development of GraphQL tools
149 lines (148 loc) • 6.47 kB
JavaScript
import { validate, } from 'graphql';
import { getBatchingExecutor } from '@graphql-tools/batch-execute';
import { normalizedExecutor } from '@graphql-tools/executor';
import { getDefinedRootType, getOperationASTFromRequest, isAsyncIterable, isPromise, mapAsyncIterator, memoize1, } from '@graphql-tools/utils';
import { applySchemaTransforms } from './applySchemaTransforms.js';
import { createRequest, getDelegatingOperation } from './createRequest.js';
import { Subschema } from './Subschema.js';
import { isSubschemaConfig } from './subschemaConfig.js';
import { Transformer } from './Transformer.js';
export function delegateToSchema(options) {
const { info, schema, rootValue = schema.rootValue ?? info.rootValue, operationName = info.operation.name?.value, operation = getDelegatingOperation(info.parentType, info.schema), fieldName = info.fieldName, selectionSet, fieldNodes = info.fieldNodes, context, } = options;
const request = createRequest({
sourceSchema: info.schema,
sourceParentType: info.parentType,
sourceFieldName: info.fieldName,
fragments: info.fragments,
variableDefinitions: info.operation.variableDefinitions,
variableValues: info.variableValues,
targetRootValue: rootValue,
targetOperationName: operationName,
targetOperation: operation,
targetFieldName: fieldName,
selectionSet,
fieldNodes,
context,
info,
});
return delegateRequest({
...options,
request,
});
}
function getDelegationReturnType(targetSchema, operation, fieldName) {
const rootType = getDefinedRootType(targetSchema, operation);
const rootFieldType = rootType.getFields()[fieldName];
if (!rootFieldType) {
throw new Error(`Unable to find field '${fieldName}' in type '${rootType}'.`);
}
return rootFieldType.type;
}
export function delegateRequest(options) {
const delegationContext = getDelegationContext(options);
const transformer = new Transformer(delegationContext);
const processedRequest = transformer.transformRequest(options.request);
if (options.validateRequest) {
validateRequest(delegationContext, processedRequest.document);
}
const executor = getExecutor(delegationContext);
const result$ = executor(processedRequest);
function handleExecutorResult(executorResult) {
if (isAsyncIterable(executorResult)) {
const iterator = executorResult[Symbol.asyncIterator]();
// "subscribe" to the subscription result and map the result through the transforms
return mapAsyncIterator(iterator, result => transformer.transformResult(result));
}
return transformer.transformResult(executorResult);
}
if (isPromise(result$)) {
return result$.then(handleExecutorResult);
}
return handleExecutorResult(result$);
}
function getDelegationContext({ request, schema, fieldName, returnType, args, info, transforms = [], transformedSchema, skipTypeMerging = false, }) {
const operationDefinition = getOperationASTFromRequest(request);
let targetFieldName;
if (fieldName == null) {
targetFieldName = operationDefinition.selectionSet.selections[0].name.value;
}
else {
targetFieldName = fieldName;
}
const stitchingInfo = info?.schema.extensions?.['stitchingInfo'];
const subschemaOrSubschemaConfig = stitchingInfo?.subschemaMap.get(schema) ?? schema;
const operation = operationDefinition.operation;
if (isSubschemaConfig(subschemaOrSubschemaConfig)) {
const targetSchema = subschemaOrSubschemaConfig.schema;
return {
subschema: schema,
subschemaConfig: subschemaOrSubschemaConfig,
targetSchema,
operation,
fieldName: targetFieldName,
args,
context: request.context,
info,
returnType: returnType ??
info?.returnType ??
getDelegationReturnType(targetSchema, operation, targetFieldName),
transforms: subschemaOrSubschemaConfig.transforms != null
? subschemaOrSubschemaConfig.transforms.concat(transforms)
: transforms,
transformedSchema: transformedSchema ??
(subschemaOrSubschemaConfig instanceof Subschema
? subschemaOrSubschemaConfig.transformedSchema
: applySchemaTransforms(targetSchema, subschemaOrSubschemaConfig)),
skipTypeMerging,
};
}
return {
subschema: schema,
subschemaConfig: undefined,
targetSchema: subschemaOrSubschemaConfig,
operation,
fieldName: targetFieldName,
args,
context: request.context,
info,
returnType: returnType ??
info?.returnType ??
getDelegationReturnType(subschemaOrSubschemaConfig, operation, targetFieldName),
transforms,
transformedSchema: transformedSchema ?? subschemaOrSubschemaConfig,
skipTypeMerging,
};
}
function validateRequest(delegationContext, document) {
const errors = validate(delegationContext.targetSchema, document);
if (errors.length > 0) {
if (errors.length > 1) {
const combinedError = new AggregateError(errors, errors.map(error => error.message).join(', \n'));
throw combinedError;
}
const error = errors[0];
throw error.originalError || error;
}
}
const GLOBAL_CONTEXT = {};
function getExecutor(delegationContext) {
const { subschemaConfig, targetSchema, context } = delegationContext;
let executor = subschemaConfig?.executor || createDefaultExecutor(targetSchema);
if (subschemaConfig?.batch) {
const batchingOptions = subschemaConfig?.batchingOptions;
executor = getBatchingExecutor(context ?? GLOBAL_CONTEXT, executor, batchingOptions?.dataLoaderOptions, batchingOptions?.extensionsReducer);
}
return executor;
}
export const createDefaultExecutor = memoize1(function createDefaultExecutor(schema) {
return function defaultExecutor(request) {
return normalizedExecutor({
schema,
document: request.document,
rootValue: request.rootValue,
contextValue: request.context,
variableValues: request.variables,
operationName: request.operationName,
});
};
});