diff --git a/src/stitching/addTypenameToAbstract.ts b/src/stitching/addTypenameToAbstract.ts new file mode 100644 index 00000000000..706574b1b32 --- /dev/null +++ b/src/stitching/addTypenameToAbstract.ts @@ -0,0 +1,56 @@ +import { + GraphQLType, + DocumentNode, + TypeInfo, + visit, + visitWithTypeInfo, + SelectionSetNode, + Kind, + FieldNode, + GraphQLInterfaceType, + GraphQLUnionType, +} from 'graphql'; +import { GraphQLSchemaWithTransforms } from '../Interfaces'; + +export function addTypenameToAbstract( + targetSchema: GraphQLSchemaWithTransforms, + document: DocumentNode, +): DocumentNode { + const typeInfo = new TypeInfo(targetSchema); + return visit( + document, + visitWithTypeInfo(typeInfo, { + [Kind.SELECTION_SET]( + node: SelectionSetNode, + ): SelectionSetNode | null | undefined { + const parentType: GraphQLType = typeInfo.getParentType(); + let selections = node.selections; + if ( + parentType && + (parentType instanceof GraphQLInterfaceType || + parentType instanceof GraphQLUnionType) && + !selections.find( + _ => + (_ as FieldNode).kind === Kind.FIELD && + (_ as FieldNode).name.value === '__typename', + ) + ) { + selections = selections.concat({ + kind: Kind.FIELD, + name: { + kind: Kind.NAME, + value: '__typename', + }, + }); + } + + if (selections !== node.selections) { + return { + ...node, + selections, + }; + } + }, + }), + ); +} diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index b4ac0506443..77f6031a5c5 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -8,10 +8,12 @@ import { buildSchema, Kind, GraphQLResolveInfo, - BuildSchemaOptions + BuildSchemaOptions, + DocumentNode, } from 'graphql'; import linkToFetcher, { execute } from './linkToFetcher'; import { Fetcher, Operation } from '../Interfaces'; +import { addTypenameToAbstract } from './addTypenameToAbstract'; import { checkResultAndHandleErrors } from './checkResultAndHandleErrors'; import { observableToAsyncIterable } from './observableToAsyncIterable'; import mapAsyncIterator from './mapAsyncIterator'; @@ -79,12 +81,15 @@ export default function makeRemoteExecutableSchema({ export function createResolver(fetcher: Fetcher): GraphQLFieldResolver { return async (root, args, context, info) => { const fragments = Object.keys(info.fragments).map(fragment => info.fragments[fragment]); - const document = { + let query: DocumentNode = { kind: Kind.DOCUMENT, definitions: [info.operation, ...fragments] }; + + query = addTypenameToAbstract(info.schema, query); + const result = await fetcher({ - query: document, + query, variables: info.variableValues, context: { graphqlContext: context } }); @@ -95,13 +100,15 @@ export function createResolver(fetcher: Fetcher): GraphQLFieldResolver function createSubscriptionResolver(link: ApolloLink): ResolverFn { return (root, args, context, info) => { const fragments = Object.keys(info.fragments).map(fragment => info.fragments[fragment]); - const document = { + let query: DocumentNode = { kind: Kind.DOCUMENT, definitions: [info.operation, ...fragments] }; + query = addTypenameToAbstract(info.schema, query); + const operation = { - query: document, + query, variables: info.variableValues, context: { graphqlContext: context } }; diff --git a/src/test/testMakeRemoteExecutableSchema.ts b/src/test/testMakeRemoteExecutableSchema.ts index e17ad9c0b92..0e30c64172a 100644 --- a/src/test/testMakeRemoteExecutableSchema.ts +++ b/src/test/testMakeRemoteExecutableSchema.ts @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { forAwaitEach } from 'iterall'; -import { GraphQLSchema, ExecutionResult, subscribe, parse } from 'graphql'; +import { GraphQLSchema, ExecutionResult, subscribe, parse, graphql } from 'graphql'; import { + propertySchema, subscriptionSchema, subscriptionPubSubTrigger, subscriptionPubSub, @@ -11,6 +12,47 @@ import { } from '../test/testingSchemas'; import { makeRemoteExecutableSchema } from '../stitching'; +describe('remote queries', () => { + let schema: GraphQLSchema; + before(async () => { + const remoteSchemaExecConfig = await makeSchemaRemoteFromLink(propertySchema); + schema = makeRemoteExecutableSchema({ + schema: remoteSchemaExecConfig.schema, + link: remoteSchemaExecConfig.link + }); + }); + + it('should work', async () => { + const query = ` + { + interfaceTest(kind: ONE) { + kind + testString + ...on TestImpl1 { + foo + } + ...on TestImpl2 { + bar + } + } + } + `; + + const expected = { + data: { + interfaceTest: { + foo: 'foo', + kind: 'ONE', + testString: 'test', + }, + }, + }; + + const result = await graphql(schema, query); + expect(result).to.deep.equal(expected); + }); +}); + describe('remote subscriptions', () => { let schema: GraphQLSchema; before(async () => { diff --git a/src/transforms/AddTypenameToAbstract.ts b/src/transforms/AddTypenameToAbstract.ts index ef790062219..be773a65b6a 100644 --- a/src/transforms/AddTypenameToAbstract.ts +++ b/src/transforms/AddTypenameToAbstract.ts @@ -1,18 +1,7 @@ -import { - DocumentNode, - FieldNode, - GraphQLInterfaceType, - GraphQLSchema, - GraphQLType, - GraphQLUnionType, - Kind, - SelectionSetNode, - TypeInfo, - visit, - visitWithTypeInfo, -} from 'graphql'; +import { GraphQLSchema } from 'graphql'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; +import { addTypenameToAbstract } from '../stitching/addTypenameToAbstract'; export default class AddTypenameToAbstract implements Transform { private targetSchema: GraphQLSchema; @@ -32,46 +21,3 @@ export default class AddTypenameToAbstract implements Transform { }; } } - -function addTypenameToAbstract( - targetSchema: GraphQLSchema, - document: DocumentNode, -): DocumentNode { - const typeInfo = new TypeInfo(targetSchema); - return visit( - document, - visitWithTypeInfo(typeInfo, { - [Kind.SELECTION_SET]( - node: SelectionSetNode, - ): SelectionSetNode | null | undefined { - const parentType: GraphQLType = typeInfo.getParentType(); - let selections = node.selections; - if ( - parentType && - (parentType instanceof GraphQLInterfaceType || - parentType instanceof GraphQLUnionType) && - !selections.find( - _ => - (_ as FieldNode).kind === Kind.FIELD && - (_ as FieldNode).name.value === '__typename', - ) - ) { - selections = selections.concat({ - kind: Kind.FIELD, - name: { - kind: Kind.NAME, - value: '__typename', - }, - }); - } - - if (selections !== node.selections) { - return { - ...node, - selections, - }; - } - }, - }), - ); -}