From cc4e16f3d0905e13f8e733ce4c38034a0f62a699 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 16 Jan 2020 00:09:49 -0500 Subject: [PATCH] feat(stitching): export createDelegatingRequest and delegateRequest methods. May be useful for use with dataloaders or memoization. See https://github.com/apollographql/graphql-tools/pull/724 --- src/Interfaces.ts | 25 ++++- src/stitching/delegateToSchema.ts | 148 ++++++++++++++++++++---------- src/stitching/index.ts | 4 +- 3 files changed, 129 insertions(+), 48 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 73446f5d737..3e001bfdd11 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -112,19 +112,42 @@ export type SchemaLikeObject = export function isSubschemaConfig(value: SchemaLikeObject): value is SubschemaConfig { return !!(value as SubschemaConfig).schema; } + export interface IDelegateToSchemaOptions { schema: GraphQLSchema | SubschemaConfig; operation: Operation; fieldName: string; returnType?: GraphQLOutputType; args?: { [key: string]: any }; - context: TContext; + context?: TContext; info: IGraphQLToolsResolveInfo; rootValue?: Record; transforms?: Array; skipValidation?: boolean; } +export interface ICreateDelegatingRequestOptions { + schema: GraphQLSchema | SubschemaConfig; + info: IGraphQLToolsResolveInfo; + operation: Operation; + fieldName: string; + args?: { [key: string]: any }; + transforms?: Array; + skipValidation?: boolean; +} + +export interface IDelegateRequestOptions { + request: Request; + schema: GraphQLSchema | SubschemaConfig; + rootValue?: Record; + info: IGraphQLToolsResolveInfo; + operation: Operation; + fieldName: string; + returnType?: GraphQLOutputType; + context?: TContext; + transforms?: Array; +} + export type Delegator = ({ document, context, variables }: { document: DocumentNode; context?: { [key: string]: any }; diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 44c83eb6b1e..d3f2c99b95d 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -1,6 +1,5 @@ import { ArgumentNode, - DocumentNode, FieldNode, FragmentDefinitionNode, Kind, @@ -10,20 +9,21 @@ import { subscribe, execute, validate, - VariableDefinitionNode, GraphQLSchema, ExecutionResult, - NameNode, } from 'graphql'; import { IDelegateToSchemaOptions, + ICreateDelegatingRequestOptions, + IDelegateRequestOptions, Operation, Request, Fetcher, Delegator, SubschemaConfig, isSubschemaConfig, + IGraphQLToolsResolveInfo, } from '../Interfaces'; import { @@ -48,7 +48,6 @@ import { isAsyncIterable } from 'iterall'; export default function delegateToSchema( options: IDelegateToSchemaOptions | GraphQLSchema, - ...args: any[] ): any { if (options instanceof GraphQLSchema) { throw new Error( @@ -56,53 +55,66 @@ export default function delegateToSchema( 'Please pass named parameters instead.', ); } - return delegateToSchemaImplementation(options); + + const { + schema: subschema, + rootValue, + info, + operation = info.operation.operation, + fieldName, + returnType = info.returnType, + args, + context, + transforms = [], + skipValidation, + } = options; + + const request = createDelegatingRequest({ + schema: subschema, + info, + operation, + fieldName, + args, + transforms, + skipValidation, + }); + + return delegateRequest({ + request, + schema: subschema, + rootValue, + info, + operation, + fieldName, + returnType, + context, + transforms, + }); } -function delegateToSchemaImplementation({ +export function createDelegatingRequest({ schema: subschema, - rootValue, info, operation = info.operation.operation, fieldName, - returnType = info.returnType, args, - context, transforms = [], skipValidation, -}: IDelegateToSchemaOptions, -): any { +}: ICreateDelegatingRequestOptions): any { let targetSchema: GraphQLSchema; let subschemaConfig: SubschemaConfig; if (isSubschemaConfig(subschema)) { subschemaConfig = subschema; targetSchema = subschemaConfig.schema; - rootValue = rootValue || subschemaConfig.rootValue || info.rootValue; transforms = transforms.concat((subschemaConfig.transforms || []).slice().reverse()); } else { targetSchema = subschema; - rootValue = rootValue || info.rootValue; } - const rawDocument: DocumentNode = createDocument( - fieldName, - operation, - info.fieldNodes, - Object.keys(info.fragments).map( - fragmentName => info.fragments[fragmentName], - ), - info.operation.variableDefinitions, - info.operation.name, - ); - - const rawRequest: Request = { - document: rawDocument, - variables: info.variableValues as Record, - }; + const initialRequest = createInitialRequest(fieldName, operation, info); transforms = [ - new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType), ...transforms, new ExpandAbstractTypes(info.schema, targetSchema), ]; @@ -124,27 +136,60 @@ function delegateToSchemaImplementation({ ); } - transforms = transforms.concat([ + transforms.push( new FilterToSchema(targetSchema), new AddTypenameToAbstract(targetSchema), - ]); + ); - const processedRequest = applyRequestTransforms(rawRequest, transforms); + const delegatingRequest = applyRequestTransforms(initialRequest, transforms); if (!skipValidation) { - const errors = validate(targetSchema, processedRequest.document); + const errors = validate(targetSchema, delegatingRequest.document); if (errors.length > 0) { throw errors; } } + return delegatingRequest; +} + +export function delegateRequest({ + request, + schema: subschema, + rootValue, + info, + operation = info.operation.operation, + fieldName, + returnType = info.returnType, + context, + transforms = [], +}: IDelegateRequestOptions): any { + let targetSchema: GraphQLSchema; + let subschemaConfig: SubschemaConfig; + + if (isSubschemaConfig(subschema)) { + subschemaConfig = subschema; + targetSchema = subschemaConfig.schema; + rootValue = rootValue || subschemaConfig.rootValue || info.rootValue; + transforms = transforms.concat((subschemaConfig.transforms || []).slice().reverse()); + } else { + targetSchema = subschema; + rootValue = rootValue || info.rootValue; + } + + transforms = [ + new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType), + ...transforms, + ]; + if (operation === 'query' || operation === 'mutation') { + const executor = createExecutor(targetSchema, rootValue, subschemaConfig); const executionResult: ExecutionResult | Promise = executor({ - document: processedRequest.document, + document: request.document, context, - variables: processedRequest.variables + variables: request.variables }); if (executionResult instanceof Promise) { @@ -152,13 +197,15 @@ function delegateToSchemaImplementation({ } else { return applyResultTransforms(executionResult, transforms); } + } else if (operation === 'subscription') { + const subscriber = createSubscriber(targetSchema, rootValue, subschemaConfig); return subscriber({ - document: processedRequest.document, + document: request.document, context, - variables: processedRequest.variables, + variables: request.variables, }).then((subscriptionResult: AsyncIterableIterator | ExecutionResult) => { if (isAsyncIterable(subscriptionResult)) { // "subscribe" to the subscription result and map the result through the transforms @@ -175,20 +222,19 @@ function delegateToSchemaImplementation({ return applyResultTransforms(subscriptionResult, transforms); } }); + } } -function createDocument( +function createInitialRequest( targetField: string, targetOperation: Operation, - originalSelections: ReadonlyArray, - fragments: Array, - variables: ReadonlyArray, - operationName: NameNode, -): DocumentNode { + info: IGraphQLToolsResolveInfo, +): Request { let selections: Array = []; let args: Array = []; + const originalSelections: ReadonlyArray = info.fieldNodes; originalSelections.forEach((field: FieldNode) => { const fieldSelections = field.selectionSet ? field.selectionSet.selections @@ -215,6 +261,7 @@ function createDocument( value: targetField, }, }; + const rootSelectionSet: SelectionSetNode = { kind: Kind.SELECTION_SET, selections: [rootField], @@ -223,15 +270,24 @@ function createDocument( const operationDefinition: OperationDefinitionNode = { kind: Kind.OPERATION_DEFINITION, operation: targetOperation, - variableDefinitions: variables, + variableDefinitions: info.operation.variableDefinitions, selectionSet: rootSelectionSet, - name: operationName, + name: info.operation.name, }; - return { + const fragments: Array = Object.keys(info.fragments).map( + fragmentName => info.fragments[fragmentName], + ); + + const document = { kind: Kind.DOCUMENT, definitions: [operationDefinition, ...fragments], }; + + return { + document, + variables: info.variableValues, + }; } function createExecutor( diff --git a/src/stitching/index.ts b/src/stitching/index.ts index 66e9851258a..02440022bcc 100644 --- a/src/stitching/index.ts +++ b/src/stitching/index.ts @@ -1,7 +1,7 @@ import makeRemoteExecutableSchema, { createResolver as defaultCreateRemoteResolver } from './makeRemoteExecutableSchema'; import introspectSchema from './introspectSchema'; import mergeSchemas from './mergeSchemas'; -import delegateToSchema from './delegateToSchema'; +import { default as delegateToSchema, createDelegatingRequest, delegateRequest } from './delegateToSchema'; import defaultMergedResolver from './defaultMergedResolver'; import { createMergedResolver } from './createMergedResolver'; import { dehoistResult, unwrapResult } from './proxiedResult'; @@ -14,6 +14,8 @@ export { // These are currently undocumented and not part of official API, // but exposed for the community use delegateToSchema, + createDelegatingRequest, + delegateRequest, defaultCreateRemoteResolver, defaultMergedResolver, createMergedResolver,