diff --git a/packages/delegate/src/createRequest.ts b/packages/delegate/src/createRequest.ts index 3b3bf655442..71a5747d926 100644 --- a/packages/delegate/src/createRequest.ts +++ b/packages/delegate/src/createRequest.ts @@ -17,8 +17,8 @@ import { DocumentNode, } from 'graphql'; -import { Request, ICreateRequest, serializeInputValue, updateArgument } from '@graphql-tools/utils'; -import { ICreateRequestFromInfo } from './types'; +import { Request, serializeInputValue, updateArgument } from '@graphql-tools/utils'; +import { ICreateRequestFromInfo, ICreateRequest } from './types'; export function getDelegatingOperation(parentType: GraphQLObjectType, schema: GraphQLSchema): OperationTypeNode { if (parentType === schema.getMutationType()) { diff --git a/packages/delegate/src/delegateToSchema.ts b/packages/delegate/src/delegateToSchema.ts index 480eded6a91..618434b2587 100644 --- a/packages/delegate/src/delegateToSchema.ts +++ b/packages/delegate/src/delegateToSchema.ts @@ -18,6 +18,7 @@ import { } from '@graphql-tools/utils'; import ExpandAbstractTypes from './transforms/ExpandAbstractTypes'; +import WrapConcreteTypes from './transforms/WrapConcreteTypes'; import FilterToSchema from './transforms/FilterToSchema'; import AddReplacementSelectionSets from './transforms/AddReplacementSelectionSets'; import AddReplacementFragments from './transforms/AddReplacementFragments'; @@ -76,6 +77,7 @@ function buildDelegationTransforms( args: Record, returnType: GraphQLOutputType, transforms: Array, + transformedSchema: GraphQLSchema, skipTypeMerging: boolean ): Array { let delegationTransforms: Array = [ @@ -89,9 +91,15 @@ function buildDelegationTransforms( ); } - delegationTransforms = delegationTransforms.concat(transforms); + const transformedTargetSchema = + info.mergeInfo == null + ? transformedSchema ?? targetSchema + : transformedSchema ?? info.mergeInfo.transformedSchemas.get(subschemaOrSubschemaConfig) ?? targetSchema; + + delegationTransforms.push(new WrapConcreteTypes(returnType, transformedTargetSchema)); + delegationTransforms.push(new ExpandAbstractTypes(info.schema, transformedTargetSchema)); - delegationTransforms.push(new ExpandAbstractTypes(info.schema, targetSchema)); + delegationTransforms = delegationTransforms.concat(transforms); if (info.mergeInfo != null) { delegationTransforms.push(new AddReplacementFragments(targetSchema, info.mergeInfo.replacementFragments)); @@ -117,6 +125,7 @@ export function delegateRequest({ returnType = info.returnType, context, transforms = [], + transformedSchema, skipValidation, skipTypeMerging, }: IDelegateRequestOptions) { @@ -147,6 +156,7 @@ export function delegateRequest({ args, returnType, requestTransforms.reverse(), + transformedSchema, skipTypeMerging ); diff --git a/packages/delegate/src/transforms/WrapConcreteTypes.ts b/packages/delegate/src/transforms/WrapConcreteTypes.ts new file mode 100644 index 00000000000..dd425193a3a --- /dev/null +++ b/packages/delegate/src/transforms/WrapConcreteTypes.ts @@ -0,0 +1,95 @@ +import { + DocumentNode, + GraphQLSchema, + Kind, + getNamedType, + GraphQLOutputType, + isAbstractType, + TypeInfo, + visit, + visitWithTypeInfo, + isObjectType, + FieldNode, +} from 'graphql'; + +import { Transform, Request } from '@graphql-tools/utils'; + +// For motivation, see https://github.com/ardatan/graphql-tools/issues/751 + +export default class WrapConcreteTypes implements Transform { + private readonly returnType: GraphQLOutputType; + private readonly targetSchema: GraphQLSchema; + + constructor(returnType: GraphQLOutputType, targetSchema: GraphQLSchema) { + this.returnType = returnType; + this.targetSchema = targetSchema; + } + + public transformRequest(originalRequest: Request): Request { + const document = wrapConcreteTypes(this.returnType, this.targetSchema, originalRequest.document); + return { + ...originalRequest, + document, + }; + } +} + +function wrapConcreteTypes( + returnType: GraphQLOutputType, + targetSchema: GraphQLSchema, + document: DocumentNode +): DocumentNode { + const namedType = getNamedType(returnType); + + if (!isObjectType(namedType)) { + return document; + } + + const queryRootType = targetSchema.getQueryType(); + const mutationRootType = targetSchema.getMutationType(); + const subscriptionRootType = targetSchema.getSubscriptionType(); + + const typeInfo = new TypeInfo(targetSchema); + const newDocument = visit( + document, + visitWithTypeInfo(typeInfo, { + [Kind.FIELD](node: FieldNode) { + const maybeType = typeInfo.getParentType(); + if (maybeType == null) { + return false; + } + + const parentType = getNamedType(maybeType); + if (parentType !== queryRootType && parentType !== mutationRootType && parentType !== subscriptionRootType) { + return false; + } + + if (!isAbstractType(getNamedType(typeInfo.getType()))) { + return false; + } + + return { + ...node, + selectionSet: { + kind: Kind.SELECTION_SET, + selections: [ + { + kind: Kind.INLINE_FRAGMENT, + typeCondition: { + kind: Kind.NAMED_TYPE, + name: { + kind: Kind.NAME, + value: namedType.name, + }, + }, + selectionSet: node.selectionSet, + }, + ], + }, + }; + }, + }) + ); + + return newDocument; +} diff --git a/packages/delegate/src/types.ts b/packages/delegate/src/types.ts index f7fb7afaf3d..2017931ab64 100644 --- a/packages/delegate/src/types.ts +++ b/packages/delegate/src/types.ts @@ -7,6 +7,9 @@ import { GraphQLResolveInfo, GraphQLFieldResolver, InlineFragmentNode, + FragmentDefinitionNode, + GraphQLObjectType, + VariableDefinitionNode, } from 'graphql'; import { Operation, Transform, Request, TypeMap, ExecutionResult } from '@graphql-tools/utils'; @@ -22,6 +25,7 @@ export interface IDelegateToSchemaOptions, TArgs info: GraphQLResolveInfo; rootValue?: Record; transforms?: Array; + transformedSchema?: GraphQLSchema; skipValidation?: boolean; skipTypeMerging?: boolean; } @@ -38,6 +42,19 @@ export interface ICreateRequestFromInfo { fieldNodes?: ReadonlyArray; } +export interface ICreateRequest { + sourceSchema?: GraphQLSchema; + sourceParentType?: GraphQLObjectType; + sourceFieldName?: string; + fragments?: Record; + variableDefinitions?: ReadonlyArray; + variableValues?: Record; + targetOperation: Operation; + targetFieldName: string; + selectionSet?: SelectionSetNode; + fieldNodes?: ReadonlyArray; +} + export interface MergedTypeInfo { subschemas: Array; selectionSet?: SelectionSetNode; @@ -70,12 +87,15 @@ export type Subscriber = , TArgs = Record ) => Promise> | ExecutionResult>; -export type CreateProxyingResolverFn = ( - schema: GraphQLSchema | SubschemaConfig, - transforms: Array, - operation: Operation, - fieldName: string -) => GraphQLFieldResolver; +export interface ICreateProxyingResolverOptions { + schema: GraphQLSchema | SubschemaConfig; + transforms?: Array; + transformedSchema?: GraphQLSchema; + operation?: Operation; + fieldName?: string; +} + +export type CreateProxyingResolverFn = (options: ICreateProxyingResolverOptions) => GraphQLFieldResolver; export interface SubschemaConfig { schema: GraphQLSchema; diff --git a/packages/stitch/src/mergeInfo.ts b/packages/stitch/src/mergeInfo.ts index db0fcd0f788..93ac6576f4d 100644 --- a/packages/stitch/src/mergeInfo.ts +++ b/packages/stitch/src/mergeInfo.ts @@ -24,10 +24,12 @@ import { delegateToSchema, isSubschemaConfig, SubschemaConfig } from '@graphql-t import { MergeTypeCandidate, MergedTypeInfo, MergeInfo, MergeTypeFilter } from './types'; export function createMergeInfo( + transformedSchemas: Map, typeCandidates: Record>, mergeTypes?: boolean | Array | MergeTypeFilter ): MergeInfo { return { + transformedSchemas, fragments: [], replacementSelectionSets: undefined, replacementFragments: undefined, diff --git a/packages/stitch/src/stitchSchemas.ts b/packages/stitch/src/stitchSchemas.ts index cdb899b2fca..e5e069b56ba 100644 --- a/packages/stitch/src/stitchSchemas.ts +++ b/packages/stitch/src/stitchSchemas.ts @@ -49,16 +49,6 @@ export function stitchSchemas({ throw new Error('Expected `resolverValidationOptions` to be an object'); } - const typeCandidates: Record> = Object.create(null); - const extensions: Array = []; - const directives: Array = []; - const schemaDefs = Object.create(null); - const operationTypeNames = { - query: 'Query', - mutation: 'Mutation', - subscription: 'Subscription', - }; - let schemaLikeObjects: Array = [...subschemas]; if (typeDefs) { schemaLikeObjects.push(buildDocumentFromTypeDefinitions(typeDefs, parseOptions)); @@ -78,8 +68,20 @@ export function stitchSchemas({ } }); + const transformedSchemas: Map = new Map(); + const typeCandidates: Record> = Object.create(null); + const extensions: Array = []; + const directives: Array = []; + const schemaDefs = Object.create(null); + const operationTypeNames = { + query: 'Query', + mutation: 'Mutation', + subscription: 'Subscription', + }; + buildTypeCandidates({ schemaLikeObjects, + transformedSchemas, typeCandidates, extensions, directives, @@ -90,7 +92,7 @@ export function stitchSchemas({ let mergeInfo: MergeInfo; - mergeInfo = createMergeInfo(typeCandidates, mergeTypes); + mergeInfo = createMergeInfo(transformedSchemas, typeCandidates, mergeTypes); const typeMap = buildTypeMap({ typeCandidates, diff --git a/packages/stitch/src/typeCandidates.ts b/packages/stitch/src/typeCandidates.ts index 5bf2ad981cd..375339762b8 100644 --- a/packages/stitch/src/typeCandidates.ts +++ b/packages/stitch/src/typeCandidates.ts @@ -43,6 +43,7 @@ function isDocumentNode(schemaLikeObject: any): schemaLikeObject is DocumentNode export function buildTypeCandidates({ schemaLikeObjects, + transformedSchemas, typeCandidates, extensions, directives, @@ -51,6 +52,7 @@ export function buildTypeCandidates({ mergeDirectives, }: { schemaLikeObjects: Array; + transformedSchemas: Map; typeCandidates: Record>; extensions: Array; directives: Array; @@ -80,6 +82,8 @@ export function buildTypeCandidates({ if (isSchema(schemaLikeObject) || isSubschemaConfig(schemaLikeObject)) { const schema = wrapSchema(schemaLikeObject); + transformedSchemas.set(schemaLikeObject, schema); + const operationTypes = { query: schema.getQueryType(), mutation: schema.getMutationType(), diff --git a/packages/stitch/src/types.ts b/packages/stitch/src/types.ts index a3dd75ec4e1..646a2a0eb9a 100644 --- a/packages/stitch/src/types.ts +++ b/packages/stitch/src/types.ts @@ -29,6 +29,7 @@ export interface MergedTypeInfo { } export interface MergeInfo { + transformedSchemas: Map; fragments: Array<{ field: string; fragment: string; diff --git a/packages/stitch/tests/example.test.ts b/packages/stitch/tests/example.test.ts new file mode 100644 index 00000000000..94c52009462 --- /dev/null +++ b/packages/stitch/tests/example.test.ts @@ -0,0 +1,261 @@ +import { graphql, GraphQLSchema } from 'graphql'; + +import { makeExecutableSchema } from '@graphql-tools/schema'; +import { delegateToSchema } from '@graphql-tools/delegate'; +import { addMocksToSchema } from '@graphql-tools/mock'; +import { stitchSchemas } from '@graphql-tools/stitch'; + +describe('basic stitching example', () => { + test('works', async () => { + const chirpSchema = makeExecutableSchema({ + typeDefs: ` + type Chirp { + id: ID! + text: String + authorId: ID! + } + + type Query { + chirpById(id: ID!): Chirp + chirpsByAuthorId(authorId: ID!): [Chirp] + } + ` + }); + + addMocksToSchema({ schema: chirpSchema }); + + // Mocked author schema + const authorSchema = makeExecutableSchema({ + typeDefs: ` + type User { + id: ID! + email: String + } + + type Query { + userById(id: ID!): User + } + ` + }); + + addMocksToSchema({ schema: authorSchema }); + + const linkTypeDefs = ` + extend type User { + chirps: [Chirp] + } + + extend type Chirp { + author: User + } + `; + + const stitchedSchema = stitchSchemas({ + subschemas: [ + { schema: chirpSchema, }, + { schema: authorSchema, }, + ], + typeDefs: linkTypeDefs, + resolvers: { + User: { + chirps: { + selectionSet: `{ id }`, + resolve: (user, _args, context, info) => delegateToSchema({ + schema: chirpSchema, + operation: 'query', + fieldName: 'chirpsByAuthorId', + args: { + authorId: user.id, + }, + context, + info, + }), + }, + }, + Chirp: { + author: { + selectionSet: `{ authorId }`, + resolve: (chirp, _args, context, info) => delegateToSchema({ + schema: authorSchema, + operation: 'query', + fieldName: 'userById', + args: { + id: chirp.authorId, + }, + context, + info, + }), + }, + }, + }, + }); + + const query = ` + query { + userById(id: 5) { + chirps { + id + textAlias: text + author { + email + } + } + } + } + `; + + const result = await graphql(stitchedSchema, query); + + expect(result.errors).toBeUndefined(); + expect(result.data.userById.chirps[1].id).not.toBe(null); + expect(result.data.userById.chirps[1].text).not.toBe(null); + expect(result.data.userById.chirps[1].author.email).not.toBe(null); + }); +}); + +describe('stitching to interfaces', () => { + let stitchedSchema: GraphQLSchema; + beforeAll(() => { + const chirpSchema = makeExecutableSchema({ + typeDefs: ` + interface Node { + id: ID! + } + + type Chirp implements Node { + id: ID! + text: String + authorId: ID! + } + + type Query { + node(id: ID!): Node + chirpsByAuthorId(authorId: ID!): [Chirp] + } + ` + }); + + addMocksToSchema({ schema: chirpSchema }); + + const authorSchema = makeExecutableSchema({ + typeDefs: ` + interface Node { + id: ID! + } + + type User implements Node { + id: ID! + email: String + } + + type Query { + node(id: ID!): Node + } + ` + }); + + addMocksToSchema({ schema: authorSchema }); + + const linkTypeDefs = ` + extend type User { + chirps: [Chirp] + } + + extend type Chirp { + author: User + } + `; + + stitchedSchema = stitchSchemas({ + subschemas: [ + { schema: chirpSchema, }, + { schema: authorSchema, }, + ], + typeDefs: linkTypeDefs, + resolvers: { + User: { + chirps: { + selectionSet: `{ id }`, + resolve: (user, _args, context, info) => delegateToSchema({ + schema: chirpSchema, + operation: 'query', + fieldName: 'chirpsByAuthorId', + args: { + authorId: user.id, + }, + context, + info, + }), + }, + }, + Chirp: { + author: { + selectionSet: `{ authorId }`, + resolve: (chirp, _args, context, info) => delegateToSchema({ + schema: authorSchema, + operation: 'query', + fieldName: 'node', + args: { + id: chirp.authorId, + }, + context, + info, + }), + }, + }, + }, + }); + }); + + test('it works with fragments', async () => { + const queryWithFragments = ` + query { + node(id: "fakeUserId") { + ... on User { + chirps { + id + textAlias: text + author { + ... on User { + email + } + } + } + } + } + } + `; + + const resultWithFragments = await graphql(stitchedSchema, queryWithFragments); + + expect(resultWithFragments.errors).toBeUndefined(); + expect(resultWithFragments.data.node.chirps[1].id).not.toBe(null); + expect(resultWithFragments.data.node.chirps[1].text).not.toBe(null); + expect(resultWithFragments.data.node.chirps[1].author.email).not.toBe(null); + }); + + test('it works without fragments', async () => { + const queryWithoutFragments = ` + query { + node(id: "fakeUserId") { + ... on User { + chirps { + id + author { + email + } + } + } + } + } + `; + + const resultWithoutFragments = await graphql(stitchedSchema, queryWithoutFragments); + + expect(resultWithoutFragments.errors).toBeUndefined(); + expect(resultWithoutFragments.data.node.chirps[1].id).not.toBe(null); + expect(resultWithoutFragments.data.node.chirps[1].text).not.toBe(null); + expect(resultWithoutFragments.data.node.chirps[1].author.email).not.toBe(null); + + }); +}); diff --git a/packages/utils/src/Interfaces.ts b/packages/utils/src/Interfaces.ts index 5b2ba6eb264..0800dc9358a 100644 --- a/packages/utils/src/Interfaces.ts +++ b/packages/utils/src/Interfaces.ts @@ -16,11 +16,9 @@ import { GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType, - SelectionSetNode, GraphQLDirective, FragmentDefinitionNode, SelectionNode, - VariableDefinitionNode, OperationDefinitionNode, GraphQLError, ExecutionResult as GraphQLExecutionResult, @@ -144,19 +142,6 @@ export type RenameTypesOptions = { renameScalars: boolean; }; -export interface ICreateRequest { - sourceSchema?: GraphQLSchema; - sourceParentType?: GraphQLObjectType; - sourceFieldName?: string; - fragments?: Record; - variableDefinitions?: ReadonlyArray; - variableValues?: Record; - targetOperation: Operation; - targetFieldName: string; - selectionSet?: SelectionSetNode; - fieldNodes?: ReadonlyArray; -} - export type IFieldResolver, TReturn = any> = ( source: TSource, args: TArgs, diff --git a/packages/wrap/src/generateProxyingResolvers.ts b/packages/wrap/src/generateProxyingResolvers.ts index da62e2ebeff..20123eb276b 100644 --- a/packages/wrap/src/generateProxyingResolvers.ts +++ b/packages/wrap/src/generateProxyingResolvers.ts @@ -1,16 +1,38 @@ import { GraphQLSchema, GraphQLFieldResolver, GraphQLObjectType } from 'graphql'; -import { Transform, Operation, getResponseKeyFromInfo, getErrors } from '@graphql-tools/utils'; -import { delegateToSchema, getSubschema, handleResult, SubschemaConfig } from '@graphql-tools/delegate'; +import { Transform, Operation, getResponseKeyFromInfo, getErrors, applySchemaTransforms } from '@graphql-tools/utils'; +import { + delegateToSchema, + getSubschema, + handleResult, + SubschemaConfig, + isSubschemaConfig, + CreateProxyingResolverFn, + ICreateProxyingResolverOptions, +} from '@graphql-tools/delegate'; -export function generateProxyingResolvers({ - subschemaConfig, - transforms, -}: { - subschemaConfig: SubschemaConfig; - transforms?: Array; -}): Record>> { - const targetSchema = subschemaConfig.schema; +export function generateProxyingResolvers( + subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, + transforms: Array +): Record>> { + let targetSchema: GraphQLSchema; + let schemaTransforms: Array = []; + let createProxyingResolver: CreateProxyingResolverFn; + + if (isSubschemaConfig(subschemaOrSubschemaConfig)) { + targetSchema = subschemaOrSubschemaConfig.schema; + createProxyingResolver = subschemaOrSubschemaConfig.createProxyingResolver ?? defaultCreateProxyingResolver; + if (subschemaOrSubschemaConfig.transforms != null) { + schemaTransforms = schemaTransforms.concat(subschemaOrSubschemaConfig.transforms); + } + } else { + targetSchema = subschemaOrSubschemaConfig; + createProxyingResolver = defaultCreateProxyingResolver; + } + + if (transforms != null) { + schemaTransforms = schemaTransforms.concat(transforms); + } const operationTypes: Record = { query: targetSchema.getQueryType(), @@ -18,11 +40,6 @@ export function generateProxyingResolvers({ subscription: targetSchema.getSubscriptionType(), }; - const createProxyingResolver = - subschemaConfig.createProxyingResolver != null - ? subschemaConfig.createProxyingResolver - : defaultCreateProxyingResolver; - const resolvers = {}; Object.keys(operationTypes).forEach((operation: Operation) => { const resolveField = operation === 'subscription' ? 'subscribe' : 'resolve'; @@ -34,9 +51,15 @@ export function generateProxyingResolvers({ resolvers[typeName] = {}; Object.keys(fields).forEach(fieldName => { - const proxyingResolver = createProxyingResolver(subschemaConfig, transforms, operation, fieldName); + const proxyingResolver = createProxyingResolver({ + schema: subschemaOrSubschemaConfig, + transforms, + transformedSchema: applySchemaTransforms(targetSchema, schemaTransforms), + operation, + fieldName, + }); - const finalResolver = createPossiblyNestedProxyingResolver(subschemaConfig, proxyingResolver); + const finalResolver = createPossiblyNestedProxyingResolver(subschemaOrSubschemaConfig, proxyingResolver); resolvers[typeName][fieldName] = { [resolveField]: finalResolver, @@ -49,7 +72,7 @@ export function generateProxyingResolvers({ } function createPossiblyNestedProxyingResolver( - subschemaConfig: SubschemaConfig, + subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, proxyingResolver: GraphQLFieldResolver ): GraphQLFieldResolver { return (parent, args, context, info) => { @@ -64,7 +87,7 @@ function createPossiblyNestedProxyingResolver( // If there is a proxied result from this subschema, return it // This can happen even for a root field when the root type ia // also nested as a field within a different type. - if (subschemaConfig === subschema) { + if (subschemaOrSubschemaConfig === subschema && parent[responseKey] !== undefined) { return handleResult(parent[responseKey], errors, subschema, context, info); } } @@ -74,15 +97,17 @@ function createPossiblyNestedProxyingResolver( }; } -export function defaultCreateProxyingResolver( - schema: GraphQLSchema | SubschemaConfig, - transforms: Array -): GraphQLFieldResolver { +export function defaultCreateProxyingResolver({ + schema, + transforms, + transformedSchema, +}: ICreateProxyingResolverOptions): GraphQLFieldResolver { return (_parent, _args, context, info) => delegateToSchema({ schema, context, info, transforms, + transformedSchema, }); } diff --git a/packages/wrap/src/makeRemoteExecutableSchema.ts b/packages/wrap/src/makeRemoteExecutableSchema.ts index 73510b23917..70ae51cb690 100644 --- a/packages/wrap/src/makeRemoteExecutableSchema.ts +++ b/packages/wrap/src/makeRemoteExecutableSchema.ts @@ -17,7 +17,7 @@ export function makeRemoteExecutableSchema({ return wrapSchema({ schema: targetSchema, - createProxyingResolver: (_schema, _transforms, _operation) => createResolver(executor, subscriber), + createProxyingResolver: () => createResolver(executor, subscriber), }); } diff --git a/packages/wrap/src/wrapSchema.ts b/packages/wrap/src/wrapSchema.ts index 88c0683911e..44b25af78b4 100644 --- a/packages/wrap/src/wrapSchema.ts +++ b/packages/wrap/src/wrapSchema.ts @@ -15,25 +15,25 @@ export function wrapSchema( subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, transforms?: Array ): GraphQLSchema { - const subschemaConfig: SubschemaConfig = isSubschemaConfig(subschemaOrSubschemaConfig) - ? subschemaOrSubschemaConfig - : { schema: subschemaOrSubschemaConfig }; - - const proxyingResolvers = generateProxyingResolvers({ - subschemaConfig, - transforms, - }); - - const schema = createWrappingSchema(subschemaConfig.schema, proxyingResolvers); - + let targetSchema: GraphQLSchema; let schemaTransforms: Array = []; - if (subschemaConfig.transforms != null) { - schemaTransforms = schemaTransforms.concat(subschemaConfig.transforms); + + if (isSubschemaConfig(subschemaOrSubschemaConfig)) { + targetSchema = subschemaOrSubschemaConfig.schema; + if (subschemaOrSubschemaConfig.transforms != null) { + schemaTransforms = schemaTransforms.concat(subschemaOrSubschemaConfig.transforms); + } + } else { + targetSchema = subschemaOrSubschemaConfig; } + if (transforms != null) { schemaTransforms = schemaTransforms.concat(transforms); } + const proxyingResolvers = generateProxyingResolvers(subschemaOrSubschemaConfig, transforms); + const schema = createWrappingSchema(targetSchema, proxyingResolvers); + return applySchemaTransforms(schema, schemaTransforms); }