diff --git a/example/apollo-server/movies.js b/example/apollo-server/movies.js index 48a71c3d..e4caa261 100644 --- a/example/apollo-server/movies.js +++ b/example/apollo-server/movies.js @@ -1,6 +1,6 @@ import { augmentTypeDefs, augmentSchema } from '../../src/index'; import { ApolloServer, gql, makeExecutableSchema } from 'apollo-server'; -import { v1 as neo4j } from 'neo4j-driver'; +import neo4j from 'neo4j-driver'; import { typeDefs, resolvers } from './movies-schema'; const schema = makeExecutableSchema({ diff --git a/src/translate.js b/src/translate.js index c2801396..f06e6595 100644 --- a/src/translate.js +++ b/src/translate.js @@ -379,7 +379,11 @@ export const nodeTypeFieldOnRelationType = ({ return { selection: relationTypeMutationPayloadField({ ...fieldInfo, - parentSelectionInfo + schemaType, + isInlineFragment, + parentSelectionInfo, + innerSchemaType, + resolveInfo }), subSelection: fieldInfo.subSelection }; @@ -405,17 +409,27 @@ const relationTypeMutationPayloadField = ({ initial, fieldName, variableName, + nestedVariable, subSelection, skipLimit, commaIfTail, tailParams, - parentSelectionInfo + parentSelectionInfo, + isInlineFragment, + resolveInfo, + innerSchemaType }) => { const safeVariableName = safeVar(variableName); + const fragmentTypeParams = isInlineFragment + ? derivedTypesParams(resolveInfo.schema, innerSchemaType.name) + : {}; + subSelection[1] = { ...subSelection[1], ...fragmentTypeParams }; return { initial: `${initial}${fieldName}: ${safeVariableName} {${ - subSelection[0] - }}${skipLimit} ${commaIfTail}`, + isInlineFragment + ? `${fragmentType(nestedVariable, innerSchemaType.name)},` + : '' + }${subSelection[0]}}${skipLimit} ${commaIfTail}`, ...tailParams, variableName: fieldName === 'from' ? parentSelectionInfo.to : parentSelectionInfo.from diff --git a/test/helpers/cypherTestHelpers.js b/test/helpers/cypherTestHelpers.js index f0049b83..c2012434 100644 --- a/test/helpers/cypherTestHelpers.js +++ b/test/helpers/cypherTestHelpers.js @@ -208,6 +208,9 @@ export function augmentedSchemaCypherTestRunner( MergeUserFriends: checkCypherMutation, UpdateUserFriends: checkCypherMutation, RemoveUserFriends: checkCypherMutation, + AddActorKnows: checkCypherMutation, + MergeActorKnows: checkCypherMutation, + RemoveActorKnows: checkCypherMutation, currentUserId: checkCypherMutation, computedObjectWithCypherParams: checkCypherMutation, computedStringList: checkCypherMutation, diff --git a/test/helpers/testSchema.js b/test/helpers/testSchema.js index 61309836..34509426 100644 --- a/test/helpers/testSchema.js +++ b/test/helpers/testSchema.js @@ -87,6 +87,7 @@ export const testSchema = /* GraphQL */ ` userId: ID! name: String movies: [Movie] @relation(name: "ACTED_IN", direction: "OUT") + knows: [Person] @relation(name: "KNOWS", direction: "OUT") } type User implements Person { diff --git a/test/unit/augmentSchemaTest.test.js b/test/unit/augmentSchemaTest.test.js index 697830ce..664662d7 100644 --- a/test/unit/augmentSchemaTest.test.js +++ b/test/unit/augmentSchemaTest.test.js @@ -427,6 +427,14 @@ test.cb('Test augmented schema', t => { movies_none: _MovieFilter movies_single: _MovieFilter movies_every: _MovieFilter + knows: _PersonFilter + knows_not: _PersonFilter + knows_in: [_PersonFilter!] + knows_not_in: [_PersonFilter!] + knows_some: _PersonFilter + knows_none: _PersonFilter + knows_single: _PersonFilter + knows_every: _PersonFilter } input _StateFilter { @@ -866,6 +874,12 @@ test.cb('Test augmented schema', t => { orderBy: [_MovieOrdering] filter: _MovieFilter ): [Movie] @relation(name: "ACTED_IN", direction: "OUT") + knows( + first: Int + offset: Int + orderBy: [_PersonOrdering] + filter: _PersonFilter + ): [Person] @relation(name: "KNOWS", direction: "OUT") _id: String } @@ -1503,6 +1517,24 @@ test.cb('Test augmented schema', t => { ): _MergeActorMoviesPayload @MutationMeta(relationship: "ACTED_IN", from: "Actor", to: "Movie") @hasScope(scopes: ["Actor: Merge", "Movie: Merge"]) + AddActorKnows( + from: _ActorInput! + to: _PersonInput! + ): _AddActorKnowsPayload + @MutationMeta(relationship: "KNOWS", from: "Actor", to: "Person") + @hasScope(scopes: ["Actor: Create", "Person: Create"]) + RemoveActorKnows( + from: _ActorInput! + to: _PersonInput! + ): _RemoveActorKnowsPayload + @MutationMeta(relationship: "KNOWS", from: "Actor", to: "Person") + @hasScope(scopes: ["Actor: Delete", "Person: Delete"]) + MergeActorKnows( + from: _ActorInput! + to: _PersonInput! + ): _MergeActorKnowsPayload + @MutationMeta(relationship: "KNOWS", from: "Actor", to: "Person") + @hasScope(scopes: ["Actor: Merge", "Person: Merge"]) CreateActor(userId: ID, name: String): Actor @hasScope(scopes: ["Actor: Create"]) UpdateActor(userId: ID!, name: String): Actor @@ -1901,6 +1933,24 @@ test.cb('Test augmented schema', t => { to: Movie } + type _AddActorKnowsPayload + @relation(name: "KNOWS", from: "Actor", to: "Person") { + from: Actor + to: Person + } + + type _MergeActorKnowsPayload + @relation(name: "KNOWS", from: "Actor", to: "Person") { + from: Actor + to: Person + } + + type _RemoveActorKnowsPayload + @relation(name: "KNOWS", from: "Actor", to: "Person") { + from: Actor + to: Person + } + type _AddUserRatedPayload @relation(name: "RATED", from: "User", to: "Movie") { from: User diff --git a/test/unit/cypherTest.test.js b/test/unit/cypherTest.test.js index ae26e741..16a8eef2 100644 --- a/test/unit/cypherTest.test.js +++ b/test/unit/cypherTest.test.js @@ -1343,6 +1343,122 @@ test('Update reflexive relationship mutation with relationship property', t => { ); }); +test('Add interfaced relationship mutation', t => { + const graphQLQuery = `mutation someMutation { + AddActorKnows( + from: { userId: "123" }, + to: { userId: "456" } + ) { + from { + userId + name + } + to { + userId + name + } + } + }`, + expectedCypherQuery = ` + MATCH (\`actor_from\`:\`Actor\` {userId: $from.userId}) + MATCH (\`person_to\`:\`Person\` {userId: $to.userId}) + CREATE (\`actor_from\`)-[\`knows_relation\`:\`KNOWS\`]->(\`person_to\`) + RETURN \`knows_relation\` { from: \`actor_from\` { .userId , .name } ,to: \`person_to\` {FRAGMENT_TYPE: head( [ label IN labels(person_to) WHERE label IN $Person_derivedTypes ] ), .userId , .name } } AS \`_AddActorKnowsPayload\`; + `; + + t.plan(1); + return augmentedSchemaCypherTestRunner( + t, + graphQLQuery, + { + from: { userId: '123' }, + to: { userId: '456' }, + first: -1, + offset: 0 + }, + expectedCypherQuery, + {} + ); +}); + +test('Merge interfaced relationship mutation', t => { + const graphQLQuery = `mutation someMutation { + MergeActorKnows( + from: { userId: "123" }, + to: { userId: "456" } + ) { + from { + userId + name + } + to { + userId + name + } + } + }`, + expectedCypherQuery = ` + MATCH (\`actor_from\`:\`Actor\` {userId: $from.userId}) + MATCH (\`person_to\`:\`Person\` {userId: $to.userId}) + MERGE (\`actor_from\`)-[\`knows_relation\`:\`KNOWS\`]->(\`person_to\`) + RETURN \`knows_relation\` { from: \`actor_from\` { .userId , .name } ,to: \`person_to\` {FRAGMENT_TYPE: head( [ label IN labels(person_to) WHERE label IN $Person_derivedTypes ] ), .userId , .name } } AS \`_MergeActorKnowsPayload\`; + `; + + t.plan(1); + return augmentedSchemaCypherTestRunner( + t, + graphQLQuery, + { + from: { userId: '123' }, + to: { userId: '456' }, + first: -1, + offset: 0 + }, + expectedCypherQuery, + {} + ); +}); + +test('Remove interfaced relationship mutation', t => { + const graphQLQuery = `mutation someMutation { + RemoveActorKnows( + from: { userId: "123" }, + to: { userId: "456" } + ) { + from { + userId + name + } + to { + userId + name + } + } + }`, + expectedCypherQuery = ` + MATCH (\`actor_from\`:\`Actor\` {userId: $from.userId}) + MATCH (\`person_to\`:\`Person\` {userId: $to.userId}) + OPTIONAL MATCH (\`actor_from\`)-[\`actor_fromperson_to\`:\`KNOWS\`]->(\`person_to\`) + DELETE \`actor_fromperson_to\` + WITH COUNT(*) AS scope, \`actor_from\` AS \`_actor_from\`, \`person_to\` AS \`_person_to\` + RETURN {from: \`_actor_from\` { .userId , .name } ,to: \`_person_to\` {FRAGMENT_TYPE: head( [ label IN labels(_person_to) WHERE label IN $Person_derivedTypes ] ), .userId , .name } } AS \`_RemoveActorKnowsPayload\`; + `; + + t.plan(1); + return augmentedSchemaCypherTestRunner( + t, + graphQLQuery, + { + from: { userId: '123' }, + to: { userId: '456' }, + first: -1, + offset: 0 + }, + expectedCypherQuery, + {} + ); +}); + test('Remove relationship mutation', t => { const graphQLQuery = `mutation someMutation { RemoveMovieGenres(