From c31f655dad1b4b0c09622542eb03ffca7c916788 Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Thu, 23 Jan 2020 14:58:09 +0100 Subject: [PATCH 1/8] Allow real GraphQL Schema via ParseServer.start --- src/ParseServer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ParseServer.js b/src/ParseServer.js index 5611c50491..1fd279b4c7 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -262,10 +262,12 @@ class ParseServer { if (options.mountGraphQL === true || options.mountPlayground === true) { let graphQLCustomTypeDefs = undefined; - if (options.graphQLSchema) { + if (typeof options.graphQLSchema === 'string') { graphQLCustomTypeDefs = parse( fs.readFileSync(options.graphQLSchema, 'utf8') ); + } else if (typeof options.graphQLSchema === 'object') { + graphQLCustomTypeDefs = options.graphQLSchema; } const parseGraphQLServer = new ParseGraphQLServer(this, { From ee49f11da89a6d559f6964710978f81ec2e6fc2c Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Thu, 23 Jan 2020 18:38:24 +0100 Subject: [PATCH 2/8] wip --- src/GraphQL/ParseGraphQLSchema.js | 46 +++++++++++++++++---- src/GraphQL/helpers/objectsQueries.js | 12 ++++-- src/GraphQL/loaders/defaultRelaySchema.js | 3 +- src/GraphQL/loaders/parseClassMutations.js | 48 +++++++++++++++++++--- src/GraphQL/loaders/parseClassQueries.js | 11 ++--- 5 files changed, 95 insertions(+), 25 deletions(-) diff --git a/src/GraphQL/ParseGraphQLSchema.js b/src/GraphQL/ParseGraphQLSchema.js index d01f5eee0a..012da4011a 100644 --- a/src/GraphQL/ParseGraphQLSchema.js +++ b/src/GraphQL/ParseGraphQLSchema.js @@ -197,19 +197,47 @@ class ParseGraphQLSchema { if (this.graphQLCustomTypeDefs) { schemaDirectives.load(this); - this.graphQLSchema = mergeSchemas({ - schemas: [ - this.graphQLSchemaDirectivesDefinitions, - this.graphQLAutoSchema, - this.graphQLCustomTypeDefs, - ], - mergeDirectives: true, - }); + if (typeof this.graphQLCustomTypeDefs.getTypeMap === 'function') { + const customGraphQLSchemaTypeMap = this.graphQLCustomTypeDefs.getTypeMap(); + Object.values(customGraphQLSchemaTypeMap).forEach( + customGraphQLSchemaType => { + const autoGraphQLSchemaType = this.graphQLAutoSchema.getType( + customGraphQLSchemaType.name + ); + if (autoGraphQLSchemaType) { + autoGraphQLSchemaType._fields = { + ...autoGraphQLSchemaType._fields, + ...customGraphQLSchemaType._fields, + }; + } + } + ); + this.graphQLSchema = mergeSchemas({ + schemas: [ + this.graphQLSchemaDirectivesDefinitions, + this.graphQLCustomTypeDefs, + this.graphQLAutoSchema, + ], + mergeDirectives: true, + }); + } else { + this.graphQLSchema = mergeSchemas({ + schemas: [ + this.graphQLSchemaDirectivesDefinitions, + this.graphQLAutoSchema, + this.graphQLCustomTypeDefs, + ], + mergeDirectives: true, + }); + } const graphQLSchemaTypeMap = this.graphQLSchema.getTypeMap(); Object.keys(graphQLSchemaTypeMap).forEach(graphQLSchemaTypeName => { const graphQLSchemaType = graphQLSchemaTypeMap[graphQLSchemaTypeName]; - if (typeof graphQLSchemaType.getFields === 'function') { + if ( + typeof graphQLSchemaType.getFields === 'function' && + this.graphQLCustomTypeDefs.definitions + ) { const graphQLCustomTypeDef = this.graphQLCustomTypeDefs.definitions.find( definition => definition.name.value === graphQLSchemaTypeName ); diff --git a/src/GraphQL/helpers/objectsQueries.js b/src/GraphQL/helpers/objectsQueries.js index 3e918c98b6..3195d485c1 100644 --- a/src/GraphQL/helpers/objectsQueries.js +++ b/src/GraphQL/helpers/objectsQueries.js @@ -3,6 +3,9 @@ import { offsetToCursor, cursorToOffset } from 'graphql-relay'; import rest from '../../rest'; import { transformQueryInputToParse } from '../transformers/query'; +const hasCustomField = (fields, keys) => + !!keys.find(keyName => !fields[keyName.split('.')[0]]); + const getObject = async ( className, objectId, @@ -12,10 +15,11 @@ const getObject = async ( includeReadPreference, config, auth, - info + info, + parseClass ) => { const options = {}; - if (keys) { + if (keys && !hasCustomField(parseClass.fields, keys)) { options.keys = keys; } if (include) { @@ -133,7 +137,7 @@ const findObjects = async ( // Silently replace the limit on the query with the max configured options.limit = config.maxLimit; } - if (keys) { + if (keys && !hasCustomField(parseClasses[className].fields, keys)) { options.keys = keys; } if (includeAll === true) { @@ -313,4 +317,4 @@ const calculateSkipAndLimit = ( }; }; -export { getObject, findObjects, calculateSkipAndLimit }; +export { getObject, findObjects, calculateSkipAndLimit, hasCustomField }; diff --git a/src/GraphQL/loaders/defaultRelaySchema.js b/src/GraphQL/loaders/defaultRelaySchema.js index 3837bd5b9f..fbc1f0cb6f 100644 --- a/src/GraphQL/loaders/defaultRelaySchema.js +++ b/src/GraphQL/loaders/defaultRelaySchema.js @@ -30,7 +30,8 @@ const load = parseGraphQLSchema => { undefined, config, auth, - info + info, + parseGraphQLSchema.parseClasses[type] )), }; } catch (e) { diff --git a/src/GraphQL/loaders/parseClassMutations.js b/src/GraphQL/loaders/parseClassMutations.js index 3ca333a0db..f5ee4a6228 100644 --- a/src/GraphQL/loaders/parseClassMutations.js +++ b/src/GraphQL/loaders/parseClassMutations.js @@ -112,8 +112,12 @@ const load = function( include, ['id', 'objectId', 'createdAt', 'updatedAt'] ); + const hasCustomField = objectsQueries.hasCustomField( + parseClass.fields, + keys + ); let optimizedObject = {}; - if (needGet) { + if (needGet && !hasCustomField) { optimizedObject = await objectsQueries.getObject( className, createdObject.objectId, @@ -123,7 +127,21 @@ const load = function( undefined, config, auth, - info + info, + parseClass + ); + } else if (hasCustomField) { + optimizedObject = await objectsQueries.getObject( + className, + createdObject.objectId, + undefined, + include, + undefined, + undefined, + config, + auth, + info, + parseClass ); } return { @@ -212,9 +230,12 @@ const load = function( include, ['id', 'objectId', 'updatedAt'] ); - + const hasCustomField = objectsQueries.hasCustomField( + parseClass.fields, + keys + ); let optimizedObject = {}; - if (needGet) { + if (needGet && !hasCustomField) { optimizedObject = await objectsQueries.getObject( className, id, @@ -224,7 +245,21 @@ const load = function( undefined, config, auth, - info + info, + parseClass + ); + } else if (hasCustomField) { + optimizedObject = await objectsQueries.getObject( + className, + id, + undefined, + include, + undefined, + undefined, + config, + auth, + info, + parseClass ); } return { @@ -301,7 +336,8 @@ const load = function( undefined, config, auth, - info + info, + parseClass ); } await objectsMutations.deleteObject( diff --git a/src/GraphQL/loaders/parseClassQueries.js b/src/GraphQL/loaders/parseClassQueries.js index 80667836d0..cd1e71d922 100644 --- a/src/GraphQL/loaders/parseClassQueries.js +++ b/src/GraphQL/loaders/parseClassQueries.js @@ -14,7 +14,7 @@ const getParseClassQueryConfig = function( return (parseClassConfig && parseClassConfig.query) || {}; }; -const getQuery = async (className, _source, args, context, queryInfo) => { +const getQuery = async (parseClass, _source, args, context, queryInfo) => { let { id } = args; const { options } = args; const { readPreference, includeReadPreference } = options || {}; @@ -23,14 +23,14 @@ const getQuery = async (className, _source, args, context, queryInfo) => { const globalIdObject = fromGlobalId(id); - if (globalIdObject.type === className) { + if (globalIdObject.type === parseClass.className) { id = globalIdObject.id; } const { keys, include } = extractKeysAndInclude(selectedFields); return await objectsQueries.getObject( - className, + parseClass.className, id, keys, include, @@ -38,7 +38,8 @@ const getQuery = async (className, _source, args, context, queryInfo) => { includeReadPreference, config, auth, - info + info, + parseClass ); }; @@ -79,7 +80,7 @@ const load = function( ), async resolve(_source, args, context, queryInfo) { try { - return await getQuery(className, _source, args, context, queryInfo); + return await getQuery(parseClass, _source, args, context, queryInfo); } catch (e) { parseGraphQLSchema.handleError(e); } From fff17c7454c6c4b9ca49dde98e04ee0d12e365ef Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Thu, 23 Jan 2020 19:11:36 +0100 Subject: [PATCH 3/8] working --- src/GraphQL/helpers/objectsQueries.js | 14 ++++++++++++-- src/GraphQL/loaders/defaultRelaySchema.js | 4 +++- src/GraphQL/loaders/parseClassTypes.js | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/GraphQL/helpers/objectsQueries.js b/src/GraphQL/helpers/objectsQueries.js index 3195d485c1..88621f845b 100644 --- a/src/GraphQL/helpers/objectsQueries.js +++ b/src/GraphQL/helpers/objectsQueries.js @@ -4,7 +4,9 @@ import rest from '../../rest'; import { transformQueryInputToParse } from '../transformers/query'; const hasCustomField = (fields, keys) => - !!keys.find(keyName => !fields[keyName.split('.')[0]]); + keys + ? !!keys.split(',').find(keyName => !fields[keyName.split('.')[0]]) + : true; const getObject = async ( className, @@ -137,7 +139,15 @@ const findObjects = async ( // Silently replace the limit on the query with the max configured options.limit = config.maxLimit; } - if (keys && !hasCustomField(parseClasses[className].fields, keys)) { + if ( + keys && + !hasCustomField( + parseClasses.find( + ({ className: parseClassName }) => className === parseClassName + ).fields, + keys + ) + ) { options.keys = keys; } if (includeAll === true) { diff --git a/src/GraphQL/loaders/defaultRelaySchema.js b/src/GraphQL/loaders/defaultRelaySchema.js index fbc1f0cb6f..c3c2d9ccce 100644 --- a/src/GraphQL/loaders/defaultRelaySchema.js +++ b/src/GraphQL/loaders/defaultRelaySchema.js @@ -31,7 +31,9 @@ const load = parseGraphQLSchema => { config, auth, info, - parseGraphQLSchema.parseClasses[type] + parseGraphQLSchema.parseClasses.find( + ({ className }) => type === className + ) )), }; } catch (e) { diff --git a/src/GraphQL/loaders/parseClassTypes.js b/src/GraphQL/loaders/parseClassTypes.js index 4909c3e992..b0272c4de6 100644 --- a/src/GraphQL/loaders/parseClassTypes.js +++ b/src/GraphQL/loaders/parseClassTypes.js @@ -436,7 +436,7 @@ const load = ( ); const parseOrder = order && order.join(','); - return await objectsQueries.findObjects( + return objectsQueries.findObjects( source[field].className, { $relatedTo: { From 32d23d613ffdc8df04157c6be033753cf2690085 Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Thu, 23 Jan 2020 20:35:06 +0100 Subject: [PATCH 4/8] tests ok --- spec/ParseGraphQLServer.spec.js | 301 ++++++++++++++++++++---------- src/GraphQL/ParseGraphQLSchema.js | 7 + 2 files changed, 206 insertions(+), 102 deletions(-) diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index 83c40b9fc4..5447393baa 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -14,6 +14,13 @@ const { SubscriptionClient } = require('subscriptions-transport-ws'); const { WebSocketLink } = require('apollo-link-ws'); const ApolloClient = require('apollo-client').default; const gql = require('graphql-tag'); +const { + GraphQLObjectType, + GraphQLString, + GraphQLNonNull, + GraphQLEnumType, + GraphQLSchema, +} = require('graphql'); const { ParseServer } = require('../'); const { ParseGraphQLServer } = require('../lib/GraphQL/ParseGraphQLServer'); const ReadPreference = require('mongodb').ReadPreference; @@ -10279,130 +10286,220 @@ describe('ParseGraphQLServer', () => { }); describe('Custom API', () => { - let httpServer; - const headers = { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Javascript-Key': 'test', - }; - let apolloClient; - - beforeAll(async () => { - const expressApp = express(); - httpServer = http.createServer(expressApp); - parseGraphQLServer = new ParseGraphQLServer(parseServer, { - graphQLPath: '/graphql', - graphQLCustomTypeDefs: gql` - extend type Query { - hello: String @resolve - hello2: String @resolve(to: "hello") - userEcho(user: CreateUserFieldsInput!): User! @resolve - hello3: String! @mock(with: "Hello world!") - hello4: User! @mock(with: { username: "somefolk" }) - } - `, - }); - parseGraphQLServer.applyGraphQL(expressApp); - await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve)); - const httpLink = createUploadLink({ - uri: 'http://localhost:13377/graphql', - fetch, - headers, - }); - apolloClient = new ApolloClient({ - link: httpLink, - cache: new InMemoryCache(), - defaultOptions: { - query: { - fetchPolicy: 'no-cache', + describe('GraphQL Schema Based', () => { + let httpServer; + const headers = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Javascript-Key': 'test', + }; + let apolloClient; + beforeAll(async () => { + const expressApp = express(); + httpServer = http.createServer(expressApp); + parseGraphQLServer = new ParseGraphQLServer(parseServer, { + graphQLPath: '/graphql', + graphQLCustomTypeDefs: gql` + extend type Query { + hello: String @resolve + hello2: String @resolve(to: "hello") + userEcho(user: CreateUserFieldsInput!): User! @resolve + hello3: String! @mock(with: "Hello world!") + hello4: User! @mock(with: { username: "somefolk" }) + } + `, + }); + parseGraphQLServer.applyGraphQL(expressApp); + await new Promise(resolve => + httpServer.listen({ port: 13377 }, resolve) + ); + const httpLink = createUploadLink({ + uri: 'http://localhost:13377/graphql', + fetch, + headers, + }); + apolloClient = new ApolloClient({ + link: httpLink, + cache: new InMemoryCache(), + defaultOptions: { + query: { + fetchPolicy: 'no-cache', + }, }, - }, + }); }); - }); - afterAll(async () => { - await httpServer.close(); - }); - - it('can resolve a custom query using default function name', async () => { - Parse.Cloud.define('hello', async () => { - return 'Hello world!'; + afterAll(async () => { + await httpServer.close(); }); - const result = await apolloClient.query({ - query: gql` - query Hello { - hello - } - `, - }); + it('can resolve a custom query using default function name', async () => { + Parse.Cloud.define('hello', async () => { + return 'Hello world!'; + }); - expect(result.data.hello).toEqual('Hello world!'); - }); + const result = await apolloClient.query({ + query: gql` + query Hello { + hello + } + `, + }); - it('can resolve a custom query using function name set by "to" argument', async () => { - Parse.Cloud.define('hello', async () => { - return 'Hello world!'; + expect(result.data.hello).toEqual('Hello world!'); }); - const result = await apolloClient.query({ - query: gql` - query Hello { - hello2 - } - `, - }); + it('can resolve a custom query using function name set by "to" argument', async () => { + Parse.Cloud.define('hello', async () => { + return 'Hello world!'; + }); - expect(result.data.hello2).toEqual('Hello world!'); - }); + const result = await apolloClient.query({ + query: gql` + query Hello { + hello2 + } + `, + }); - it('should resolve auto types', async () => { - Parse.Cloud.define('userEcho', async req => { - return req.params.user; + expect(result.data.hello2).toEqual('Hello world!'); }); + }); - const result = await apolloClient.query({ - query: gql` - query UserEcho($user: CreateUserFieldsInput!) { - userEcho(user: $user) { - username - } - } - `, - variables: { - user: { - username: 'somefolk', - password: 'somepassword', + describe('SDL Based', () => { + let httpServer; + const headers = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Javascript-Key': 'test', + }; + let apolloClient; + + beforeAll(async () => { + const expressApp = express(); + httpServer = http.createServer(expressApp); + parseGraphQLServer = new ParseGraphQLServer(parseServer, { + graphQLPath: '/graphql', + graphQLCustomTypeDefs: new GraphQLSchema({ + query: new GraphQLObjectType({ + name: 'Query', + fields: { + customQuery: { + type: new GraphQLNonNull(GraphQLString), + args: { + message: { type: new GraphQLNonNull(GraphQLString) }, + }, + resolve: (p, { message }) => message, + }, + }, + }), + types: [ + new GraphQLObjectType({ + name: 'SomeClass', + fields: { + nameUpperCase: { + type: new GraphQLNonNull(GraphQLString), + resolve: p => p.name.toUpperCase(), + }, + language: { + type: new GraphQLEnumType({ + name: 'LanguageEnum', + values: { + fr: { value: 'fr' }, + en: { value: 'en' }, + }, + }), + resolve: () => 'fr', + }, + }, + }), + ], + }), + }); + + parseGraphQLServer.applyGraphQL(expressApp); + await new Promise(resolve => + httpServer.listen({ port: 13377 }, resolve) + ); + const httpLink = createUploadLink({ + uri: 'http://localhost:13377/graphql', + fetch, + headers, + }); + apolloClient = new ApolloClient({ + link: httpLink, + cache: new InMemoryCache(), + defaultOptions: { + query: { + fetchPolicy: 'no-cache', + }, }, - }, + }); }); - expect(result.data.userEcho.username).toEqual('somefolk'); - }); + afterAll(async () => { + await httpServer.close(); + }); - it('can mock a custom query with string', async () => { - const result = await apolloClient.query({ - query: gql` - query Hello { - hello3 - } - `, + it('can resolve a custom query', async () => { + const result = await apolloClient.query({ + variables: { message: 'hello' }, + query: gql` + query CustomQuery($message: String!) { + customQuery(message: $message) + } + `, + }); + expect(result.data.customQuery).toEqual('hello'); }); - expect(result.data.hello3).toEqual('Hello world!'); - }); + it('can resolve a custom extend type', async () => { + const obj = new Parse.Object('SomeClass'); + await obj.save({ name: 'aname' }); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const result = await apolloClient.query({ + variables: { id: obj.id }, + query: gql` + query someClass($id: ID!) { + someClass(id: $id) { + nameUpperCase + language + } + } + `, + }); + + expect(result.data.someClass.nameUpperCase).toEqual('ANAME'); + expect(result.data.someClass.language).toEqual('fr'); - it('can mock a custom query with auto type', async () => { - const result = await apolloClient.query({ - query: gql` - query Hello { - hello4 { - username + const result2 = await apolloClient.query({ + variables: { id: obj.id }, + query: gql` + query someClass($id: ID!) { + someClass(id: $id) { + name + language + } } - } - `, - }); + `, + }); + expect(result2.data.someClass.name).toEqual('aname'); + expect(result.data.someClass.language).toEqual('fr'); - expect(result.data.hello4.username).toEqual('somefolk'); + const result3 = await apolloClient.mutate({ + variables: { id: obj.id, name: 'anewname' }, + mutation: gql` + mutation someClass($id: ID!, $name: String!) { + updateSomeClass(input: { id: $id, fields: { name: $name } }) { + someClass { + nameUpperCase + } + } + } + `, + }); + expect(result3.data.updateSomeClass.someClass.nameUpperCase).toEqual( + 'ANEWNAME' + ); + }); }); }); }); diff --git a/src/GraphQL/ParseGraphQLSchema.js b/src/GraphQL/ParseGraphQLSchema.js index 012da4011a..0421e566d7 100644 --- a/src/GraphQL/ParseGraphQLSchema.js +++ b/src/GraphQL/ParseGraphQLSchema.js @@ -201,6 +201,13 @@ class ParseGraphQLSchema { const customGraphQLSchemaTypeMap = this.graphQLCustomTypeDefs.getTypeMap(); Object.values(customGraphQLSchemaTypeMap).forEach( customGraphQLSchemaType => { + if ( + !customGraphQLSchemaType || + !customGraphQLSchemaType.name || + customGraphQLSchemaType.name.startsWith('__') + ) { + return; + } const autoGraphQLSchemaType = this.graphQLAutoSchema.getType( customGraphQLSchemaType.name ); From 441c69c784dcdd84980b44553c8f951c832654b2 Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Mon, 27 Jan 2020 12:44:24 +0100 Subject: [PATCH 5/8] add tests about enum/input use case --- spec/ParseGraphQLServer.spec.js | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index 5447393baa..212c099ef8 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -19,6 +19,7 @@ const { GraphQLString, GraphQLNonNull, GraphQLEnumType, + GraphQLInputObjectType, GraphQLSchema, } = require('graphql'); const { ParseServer } = require('../'); @@ -10376,6 +10377,13 @@ describe('ParseGraphQLServer', () => { beforeAll(async () => { const expressApp = express(); httpServer = http.createServer(expressApp); + const TypeEnum = new GraphQLEnumType({ + name: 'TypeEnum', + values: { + human: { value: 'human' }, + robot: { value: 'robot' }, + }, + }); parseGraphQLServer = new ParseGraphQLServer(parseServer, { graphQLPath: '/graphql', graphQLCustomTypeDefs: new GraphQLSchema({ @@ -10392,6 +10400,18 @@ describe('ParseGraphQLServer', () => { }, }), types: [ + new GraphQLInputObjectType({ + name: 'CreateSomeClassFieldsInput', + fields: { + type: { type: TypeEnum }, + }, + }), + new GraphQLInputObjectType({ + name: 'UpdateSomeClassFieldsInput', + fields: { + type: { type: TypeEnum }, + }, + }), new GraphQLObjectType({ name: 'SomeClass', fields: { @@ -10399,6 +10419,7 @@ describe('ParseGraphQLServer', () => { type: new GraphQLNonNull(GraphQLString), resolve: p => p.name.toUpperCase(), }, + type: { type: TypeEnum }, language: { type: new GraphQLEnumType({ name: 'LanguageEnum', @@ -10453,7 +10474,7 @@ describe('ParseGraphQLServer', () => { it('can resolve a custom extend type', async () => { const obj = new Parse.Object('SomeClass'); - await obj.save({ name: 'aname' }); + await obj.save({ name: 'aname', type: 'robot' }); await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); const result = await apolloClient.query({ variables: { id: obj.id }, @@ -10462,13 +10483,14 @@ describe('ParseGraphQLServer', () => { someClass(id: $id) { nameUpperCase language + type } } `, }); - expect(result.data.someClass.nameUpperCase).toEqual('ANAME'); expect(result.data.someClass.language).toEqual('fr'); + expect(result.data.someClass.type).toEqual('robot'); const result2 = await apolloClient.query({ variables: { id: obj.id }, @@ -10483,14 +10505,16 @@ describe('ParseGraphQLServer', () => { }); expect(result2.data.someClass.name).toEqual('aname'); expect(result.data.someClass.language).toEqual('fr'); - const result3 = await apolloClient.mutate({ variables: { id: obj.id, name: 'anewname' }, mutation: gql` mutation someClass($id: ID!, $name: String!) { - updateSomeClass(input: { id: $id, fields: { name: $name } }) { + updateSomeClass( + input: { id: $id, fields: { name: $name, type: human } } + ) { someClass { nameUpperCase + type } } } @@ -10499,6 +10523,7 @@ describe('ParseGraphQLServer', () => { expect(result3.data.updateSomeClass.someClass.nameUpperCase).toEqual( 'ANEWNAME' ); + expect(result3.data.updateSomeClass.someClass.type).toEqual('human'); }); }); }); From 519cebf22e44369e886481bc727ce2c9fcb72ae1 Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Thu, 20 Feb 2020 18:02:24 +0100 Subject: [PATCH 6/8] Add async function based merge --- spec/ParseGraphQLServer.spec.js | 52 +++++++++++++++++++++++++++++++ src/GraphQL/ParseGraphQLSchema.js | 6 ++++ 2 files changed, 58 insertions(+) diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index 212c099ef8..abdba1a9b6 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -10526,5 +10526,57 @@ describe('ParseGraphQLServer', () => { expect(result3.data.updateSomeClass.someClass.type).toEqual('human'); }); }); + describe('Async Function Based Merge', () => { + let httpServer; + const headers = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Javascript-Key': 'test', + }; + let apolloClient; + + beforeAll(async () => { + const expressApp = express(); + httpServer = http.createServer(expressApp); + parseGraphQLServer = new ParseGraphQLServer(parseServer, { + graphQLPath: '/graphql', + graphQLCustomTypeDefs: ({ autoSchema, mergeSchemas }) => + mergeSchemas({ schemas: [autoSchema] }), + }); + + parseGraphQLServer.applyGraphQL(expressApp); + await new Promise(resolve => + httpServer.listen({ port: 13377 }, resolve) + ); + const httpLink = createUploadLink({ + uri: 'http://localhost:13377/graphql', + fetch, + headers, + }); + apolloClient = new ApolloClient({ + link: httpLink, + cache: new InMemoryCache(), + defaultOptions: { + query: { + fetchPolicy: 'no-cache', + }, + }, + }); + }); + + afterAll(async () => { + await httpServer.close(); + }); + + it('can resolve a query', async () => { + const result = await apolloClient.query({ + query: gql` + query Health { + health + } + `, + }); + expect(result.data.health).toEqual(true); + }); + }); }); }); diff --git a/src/GraphQL/ParseGraphQLSchema.js b/src/GraphQL/ParseGraphQLSchema.js index 0421e566d7..d596eae5b7 100644 --- a/src/GraphQL/ParseGraphQLSchema.js +++ b/src/GraphQL/ParseGraphQLSchema.js @@ -227,6 +227,12 @@ class ParseGraphQLSchema { ], mergeDirectives: true, }); + } else if (typeof this.graphQLCustomTypeDefs === 'function') { + this.graphQLSchema = await this.graphQLCustomTypeDefs({ + directivesDefinitionsSchema: this.graphQLSchemaDirectivesDefinitions, + autoSchema: this.graphQLAutoSchema, + mergeSchemas, + }); } else { this.graphQLSchema = mergeSchemas({ schemas: [ From 636ced83574a5ed68cf05017c9cc9fe97ed3e6a4 Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Thu, 20 Feb 2020 18:12:08 +0100 Subject: [PATCH 7/8] Better naming --- src/GraphQL/helpers/objectsQueries.js | 8 ++++---- src/GraphQL/loaders/parseClassMutations.js | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/GraphQL/helpers/objectsQueries.js b/src/GraphQL/helpers/objectsQueries.js index 88621f845b..9e8c0d4329 100644 --- a/src/GraphQL/helpers/objectsQueries.js +++ b/src/GraphQL/helpers/objectsQueries.js @@ -3,7 +3,7 @@ import { offsetToCursor, cursorToOffset } from 'graphql-relay'; import rest from '../../rest'; import { transformQueryInputToParse } from '../transformers/query'; -const hasCustomField = (fields, keys) => +const needToGetAllKeys = (fields, keys) => keys ? !!keys.split(',').find(keyName => !fields[keyName.split('.')[0]]) : true; @@ -21,7 +21,7 @@ const getObject = async ( parseClass ) => { const options = {}; - if (keys && !hasCustomField(parseClass.fields, keys)) { + if (keys && !needToGetAllKeys(parseClass.fields, keys)) { options.keys = keys; } if (include) { @@ -141,7 +141,7 @@ const findObjects = async ( } if ( keys && - !hasCustomField( + !needToGetAllKeys( parseClasses.find( ({ className: parseClassName }) => className === parseClassName ).fields, @@ -327,4 +327,4 @@ const calculateSkipAndLimit = ( }; }; -export { getObject, findObjects, calculateSkipAndLimit, hasCustomField }; +export { getObject, findObjects, calculateSkipAndLimit, needToGetAllKeys }; diff --git a/src/GraphQL/loaders/parseClassMutations.js b/src/GraphQL/loaders/parseClassMutations.js index f5ee4a6228..f41cccf5ed 100644 --- a/src/GraphQL/loaders/parseClassMutations.js +++ b/src/GraphQL/loaders/parseClassMutations.js @@ -112,12 +112,12 @@ const load = function( include, ['id', 'objectId', 'createdAt', 'updatedAt'] ); - const hasCustomField = objectsQueries.hasCustomField( + const needToGetAllKeys = objectsQueries.needToGetAllKeys( parseClass.fields, keys ); let optimizedObject = {}; - if (needGet && !hasCustomField) { + if (needGet && !needToGetAllKeys) { optimizedObject = await objectsQueries.getObject( className, createdObject.objectId, @@ -130,7 +130,7 @@ const load = function( info, parseClass ); - } else if (hasCustomField) { + } else if (needToGetAllKeys) { optimizedObject = await objectsQueries.getObject( className, createdObject.objectId, @@ -230,12 +230,12 @@ const load = function( include, ['id', 'objectId', 'updatedAt'] ); - const hasCustomField = objectsQueries.hasCustomField( + const needToGetAllKeys = objectsQueries.needToGetAllKeys( parseClass.fields, keys ); let optimizedObject = {}; - if (needGet && !hasCustomField) { + if (needGet && !needToGetAllKeys) { optimizedObject = await objectsQueries.getObject( className, id, @@ -248,7 +248,7 @@ const load = function( info, parseClass ); - } else if (hasCustomField) { + } else if (needToGetAllKeys) { optimizedObject = await objectsQueries.getObject( className, id, From b658b46236d8e902c483c081cecc037dea8fff6e Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Thu, 20 Feb 2020 18:14:17 +0100 Subject: [PATCH 8/8] remove useless condition --- src/GraphQL/helpers/objectsQueries.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/GraphQL/helpers/objectsQueries.js b/src/GraphQL/helpers/objectsQueries.js index 9e8c0d4329..c1237deaea 100644 --- a/src/GraphQL/helpers/objectsQueries.js +++ b/src/GraphQL/helpers/objectsQueries.js @@ -21,7 +21,7 @@ const getObject = async ( parseClass ) => { const options = {}; - if (keys && !needToGetAllKeys(parseClass.fields, keys)) { + if (!needToGetAllKeys(parseClass.fields, keys)) { options.keys = keys; } if (include) { @@ -140,7 +140,6 @@ const findObjects = async ( options.limit = config.maxLimit; } if ( - keys && !needToGetAllKeys( parseClasses.find( ({ className: parseClassName }) => className === parseClassName