UNPKG

@graphql-tools/delegate

Version:

A set of utils for faster development of GraphQL tools

149 lines (148 loc) 6.47 kB
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, }); }; });