diff --git a/package-lock.json b/package-lock.json index e49f8b31f0..13b3c3f47a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1214,11 +1214,21 @@ "integrity": "sha512-J+YW09/vZRuK2/04SykW31xgMTtA/XTm4mSaLoJ79EW31SWstNUzfti9seu0MdWRafgdmRzUn+qCJ/MqXvQZRg==", "requires": { "@parse/node-gcm": "^1.0.0", - "apn": "github:parse-community/node-apn#semver:^v3.0.2-parse", + "apn": "github:parse-community/node-apn#3bc4fb20b68d53d3f3b7057cadf5a37ba6a45dc0", "npmlog": "^4.0.2", "parse": "^1.11.1" }, "dependencies": { + "apn": { + "version": "github:parse-community/node-apn#3bc4fb20b68d53d3f3b7057cadf5a37ba6a45dc0", + "from": "github:parse-community/node-apn#3bc4fb20b68d53d3f3b7057cadf5a37ba6a45dc0", + "requires": { + "debug": "^3.1.0", + "jsonwebtoken": "^8.1.0", + "node-forge": "^0.7.1", + "verror": "^1.10.0" + } + }, "parse": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/parse/-/parse-1.11.1.tgz", @@ -1668,16 +1678,6 @@ } } }, - "apn": { - "version": "github:parse-community/node-apn#3bc4fb20b68d53d3f3b7057cadf5a37ba6a45dc0", - "from": "github:parse-community/node-apn#semver:^v3.0.2-parse", - "requires": { - "debug": "^3.1.0", - "jsonwebtoken": "^8.1.0", - "node-forge": "^0.7.1", - "verror": "^1.10.0" - } - }, "apollo-cache": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.3.2.tgz", @@ -9075,6 +9075,11 @@ "semver-compare": "^1.0.0" } }, + "pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==" + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", diff --git a/package.json b/package.json index 66f4298042..86d1db7235 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "node-rsa": "1.0.5", "parse": "2.6.0", "pg-promise": "9.0.1", + "pluralize": "^8.0.0", "redis": "2.8.0", "semver": "6.3.0", "subscriptions-transport-ws": "0.9.16", diff --git a/spec/ParseGraphQLClassNameTransformer.spec.js b/spec/ParseGraphQLClassNameTransformer.spec.js new file mode 100644 index 0000000000..c2a89e2f60 --- /dev/null +++ b/spec/ParseGraphQLClassNameTransformer.spec.js @@ -0,0 +1,11 @@ +const { + transformClassNameToGraphQL, +} = require('../lib/GraphQL/transformers/className'); + +describe('transformClassNameToGraphQL', () => { + it('should remove starting _ and tansform first letter to upper case', () => { + expect( + ['_User', '_user', 'User', 'user'].map(transformClassNameToGraphQL) + ).toEqual(['User', 'User', 'User', 'User']); + }); +}); diff --git a/spec/ParseGraphQLSchema.spec.js b/spec/ParseGraphQLSchema.spec.js index e39e8782ec..6991c6c0f7 100644 --- a/spec/ParseGraphQLSchema.spec.js +++ b/spec/ParseGraphQLSchema.spec.js @@ -1,3 +1,4 @@ +const { GraphQLObjectType } = require('graphql'); const defaultLogger = require('../lib/logger').default; const { ParseGraphQLSchema } = require('../lib/GraphQL/ParseGraphQLSchema'); @@ -118,4 +119,428 @@ describe('ParseGraphQLSchema', () => { ); }); }); + + describe('addGraphQLType', () => { + it('should not load and warn duplicated types', async () => { + let logged = false; + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: { + warn: message => { + logged = true; + expect(message).toEqual( + 'Type SomeClass could not be added to the auto schema because it collided with an existing type.' + ); + }, + }, + }); + await parseGraphQLSchema.load(); + const type = new GraphQLObjectType({ name: 'SomeClass' }); + expect(parseGraphQLSchema.addGraphQLType(type)).toBe(type); + expect(parseGraphQLSchema.graphQLTypes).toContain(type); + expect( + parseGraphQLSchema.addGraphQLType( + new GraphQLObjectType({ name: 'SomeClass' }) + ) + ).toBeUndefined(); + expect(logged).toBeTruthy(); + }); + + it('should throw error when required', async () => { + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: { + warn: () => { + fail('Should not warn'); + }, + }, + }); + await parseGraphQLSchema.load(); + const type = new GraphQLObjectType({ name: 'SomeClass' }); + expect(parseGraphQLSchema.addGraphQLType(type, true)).toBe(type); + expect(parseGraphQLSchema.graphQLTypes).toContain(type); + expect(() => + parseGraphQLSchema.addGraphQLType( + new GraphQLObjectType({ name: 'SomeClass' }), + true + ) + ).toThrowError( + 'Type SomeClass could not be added to the auto schema because it collided with an existing type.' + ); + }); + + it('should warn reserved name collision', async () => { + let logged = false; + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: { + warn: message => { + logged = true; + expect(message).toEqual( + 'Type String could not be added to the auto schema because it collided with an existing type.' + ); + }, + }, + }); + await parseGraphQLSchema.load(); + expect( + parseGraphQLSchema.addGraphQLType( + new GraphQLObjectType({ name: 'String' }) + ) + ).toBeUndefined(); + expect(logged).toBeTruthy(); + }); + + it('should ignore collision when necessary', async () => { + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: { + warn: () => { + fail('Should not warn'); + }, + }, + }); + await parseGraphQLSchema.load(); + const type = new GraphQLObjectType({ name: 'String' }); + expect(parseGraphQLSchema.addGraphQLType(type, true, true)).toBe(type); + expect(parseGraphQLSchema.graphQLTypes).toContain(type); + }); + }); + + describe('addGraphQLObjectQuery', () => { + it('should not load and warn duplicated queries', async () => { + let logged = false; + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: { + warn: message => { + logged = true; + expect(message).toEqual( + 'Object query someClasses could not be added to the auto schema because it collided with an existing field.' + ); + }, + }, + }); + await parseGraphQLSchema.load(); + const field = {}; + expect( + parseGraphQLSchema.addGraphQLObjectQuery('someClasses', field) + ).toBe(field); + expect(parseGraphQLSchema.graphQLObjectsQueries['someClasses']).toBe( + field + ); + expect( + parseGraphQLSchema.addGraphQLObjectQuery('someClasses', {}) + ).toBeUndefined(); + expect(logged).toBeTruthy(); + }); + + it('should throw error when required', async () => { + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: { + warn: () => { + fail('Should not warn'); + }, + }, + }); + await parseGraphQLSchema.load(); + const field = {}; + expect( + parseGraphQLSchema.addGraphQLObjectQuery('someClasses', field) + ).toBe(field); + expect(parseGraphQLSchema.graphQLObjectsQueries['someClasses']).toBe( + field + ); + expect(() => + parseGraphQLSchema.addGraphQLObjectQuery('someClasses', {}, true) + ).toThrowError( + 'Object query someClasses could not be added to the auto schema because it collided with an existing field.' + ); + }); + + it('should warn reserved name collision', async () => { + let logged = false; + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: { + warn: message => { + logged = true; + expect(message).toEqual( + 'Object query get could not be added to the auto schema because it collided with an existing field.' + ); + }, + }, + }); + await parseGraphQLSchema.load(); + expect( + parseGraphQLSchema.addGraphQLObjectQuery('get', {}) + ).toBeUndefined(); + expect(logged).toBeTruthy(); + }); + + it('should ignore collision when necessary', async () => { + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: { + warn: () => { + fail('Should not warn'); + }, + }, + }); + await parseGraphQLSchema.load(); + delete parseGraphQLSchema.graphQLObjectsQueries.get; + const field = {}; + expect( + parseGraphQLSchema.addGraphQLObjectQuery('get', field, true, true) + ).toBe(field); + expect(parseGraphQLSchema.graphQLObjectsQueries['get']).toBe(field); + }); + }); + + describe('addGraphQLObjectMutation', () => { + it('should not load and warn duplicated mutations', async () => { + let logged = false; + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: { + warn: message => { + logged = true; + expect(message).toEqual( + 'Object mutation createSomeClass could not be added to the auto schema because it collided with an existing field.' + ); + }, + }, + }); + await parseGraphQLSchema.load(); + const field = {}; + expect( + parseGraphQLSchema.addGraphQLObjectMutation('createSomeClass', field) + ).toBe(field); + expect( + parseGraphQLSchema.graphQLObjectsMutations['createSomeClass'] + ).toBe(field); + expect( + parseGraphQLSchema.addGraphQLObjectMutation('createSomeClass', {}) + ).toBeUndefined(); + expect(logged).toBeTruthy(); + }); + + it('should throw error when required', async () => { + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: { + warn: () => { + fail('Should not warn'); + }, + }, + }); + await parseGraphQLSchema.load(); + const field = {}; + expect( + parseGraphQLSchema.addGraphQLObjectMutation('createSomeClass', field) + ).toBe(field); + expect( + parseGraphQLSchema.graphQLObjectsMutations['createSomeClass'] + ).toBe(field); + expect(() => + parseGraphQLSchema.addGraphQLObjectMutation('createSomeClass', {}, true) + ).toThrowError( + 'Object mutation createSomeClass could not be added to the auto schema because it collided with an existing field.' + ); + }); + + it('should warn reserved name collision', async () => { + let logged = false; + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: { + warn: message => { + logged = true; + expect(message).toEqual( + 'Object mutation create could not be added to the auto schema because it collided with an existing field.' + ); + }, + }, + }); + await parseGraphQLSchema.load(); + expect( + parseGraphQLSchema.addGraphQLObjectMutation('create', {}) + ).toBeUndefined(); + expect(logged).toBeTruthy(); + }); + + it('should ignore collision when necessary', async () => { + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: { + warn: () => { + fail('Should not warn'); + }, + }, + }); + await parseGraphQLSchema.load(); + delete parseGraphQLSchema.graphQLObjectsMutations.create; + const field = {}; + expect( + parseGraphQLSchema.addGraphQLObjectMutation('create', field, true, true) + ).toBe(field); + expect(parseGraphQLSchema.graphQLObjectsMutations['create']).toBe(field); + }); + }); + + describe('_getParseClassesWithConfig', () => { + it('should sort classes', () => { + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: { + warn: () => { + fail('Should not warn'); + }, + }, + }); + expect( + parseGraphQLSchema + ._getParseClassesWithConfig( + [ + { className: 'b' }, + { className: '_b' }, + { className: 'B' }, + { className: '_B' }, + { className: 'a' }, + { className: '_a' }, + { className: 'A' }, + { className: '_A' }, + ], + { + classConfigs: [], + } + ) + .map(item => item[0]) + ).toEqual([ + { className: '_A' }, + { className: '_B' }, + { className: '_a' }, + { className: '_b' }, + { className: 'A' }, + { className: 'B' }, + { className: 'a' }, + { className: 'b' }, + ]); + }); + }); + + describe('name collision', () => { + it('should not generate duplicate types when colliding to default classes', async () => { + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: defaultLogger, + }); + await parseGraphQLSchema.databaseController.schemaCache.clear(); + const schema1 = await parseGraphQLSchema.load(); + const types1 = parseGraphQLSchema.graphQLTypes; + const objectQueries1 = parseGraphQLSchema.graphQLObjectsQueries; + const objectMutations1 = parseGraphQLSchema.graphQLObjectsMutations; + const user = new Parse.Object('User'); + await user.save(); + await parseGraphQLSchema.databaseController.schemaCache.clear(); + const schema2 = await parseGraphQLSchema.load(); + const types2 = parseGraphQLSchema.graphQLTypes; + const objectQueries2 = parseGraphQLSchema.graphQLObjectsQueries; + const objectMutations2 = parseGraphQLSchema.graphQLObjectsMutations; + expect(schema1).not.toBe(schema2); + expect(types1).not.toBe(types2); + expect(types1.map(type => type.name).sort()).toEqual( + types2.map(type => type.name).sort() + ); + expect(objectQueries1).not.toBe(objectQueries2); + expect(Object.keys(objectQueries1).sort()).toEqual( + Object.keys(objectQueries2).sort() + ); + expect(objectMutations1).not.toBe(objectMutations2); + expect(Object.keys(objectMutations1).sort()).toEqual( + Object.keys(objectMutations2).sort() + ); + }); + + it('should not generate duplicate types when colliding the same name', async () => { + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: defaultLogger, + }); + const car1 = new Parse.Object('Car'); + await car1.save(); + await parseGraphQLSchema.databaseController.schemaCache.clear(); + const schema1 = await parseGraphQLSchema.load(); + const types1 = parseGraphQLSchema.graphQLTypes; + const objectQueries1 = parseGraphQLSchema.graphQLObjectsQueries; + const objectMutations1 = parseGraphQLSchema.graphQLObjectsMutations; + const car2 = new Parse.Object('car'); + await car2.save(); + await parseGraphQLSchema.databaseController.schemaCache.clear(); + const schema2 = await parseGraphQLSchema.load(); + const types2 = parseGraphQLSchema.graphQLTypes; + const objectQueries2 = parseGraphQLSchema.graphQLObjectsQueries; + const objectMutations2 = parseGraphQLSchema.graphQLObjectsMutations; + expect(schema1).not.toBe(schema2); + expect(types1).not.toBe(types2); + expect(types1.map(type => type.name).sort()).toEqual( + types2.map(type => type.name).sort() + ); + expect(objectQueries1).not.toBe(objectQueries2); + expect(Object.keys(objectQueries1).sort()).toEqual( + Object.keys(objectQueries2).sort() + ); + expect(objectMutations1).not.toBe(objectMutations2); + expect(Object.keys(objectMutations1).sort()).toEqual( + Object.keys(objectMutations2).sort() + ); + }); + + it('should not generate duplicate queries when query name collide', async () => { + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: defaultLogger, + }); + const car = new Parse.Object('Car'); + await car.save(); + await parseGraphQLSchema.databaseController.schemaCache.clear(); + const schema1 = await parseGraphQLSchema.load(); + const objectQueries1 = parseGraphQLSchema.graphQLObjectsQueries; + const objectMutations1 = parseGraphQLSchema.graphQLObjectsMutations; + const cars = new Parse.Object('cars'); + await cars.save(); + await parseGraphQLSchema.databaseController.schemaCache.clear(); + const schema2 = await parseGraphQLSchema.load(); + const objectQueries2 = parseGraphQLSchema.graphQLObjectsQueries; + const objectMutations2 = parseGraphQLSchema.graphQLObjectsMutations; + expect(schema1).not.toBe(schema2); + expect(objectQueries1).not.toBe(objectQueries2); + expect(Object.keys(objectQueries1).sort()).toEqual( + Object.keys(objectQueries2).sort() + ); + expect(objectMutations1).not.toBe(objectMutations2); + expect( + Object.keys(objectMutations1) + .concat('createCars', 'updateCars', 'deleteCars') + .sort() + ).toEqual(Object.keys(objectMutations2).sort()); + }); + }); }); diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index 13cb825803..be2caa8550 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -4,6 +4,7 @@ const req = require('../lib/request'); const fetch = require('node-fetch'); const FormData = require('form-data'); const ws = require('ws'); +const pluralize = require('pluralize'); const { getMainDefinition } = require('apollo-utilities'); const { ApolloLink, split } = require('apollo-link'); const { createHttpLink } = require('apollo-link-http'); @@ -710,7 +711,7 @@ describe('ParseGraphQLServer', () => { 'Class', 'CreateResult', 'Date', - 'File', + 'FileInfo', 'FilesMutation', 'FindResult', 'ObjectsMutation', @@ -742,17 +743,17 @@ describe('ParseGraphQLServer', () => { })).data['__schema'].types.map(type => type.name); const expectedTypes = [ - '_RoleClass', - '_RoleConstraints', - '_RoleCreateFields', - '_RoleUpdateFields', - '_RoleFindResult', - '_UserClass', - '_UserConstraints', - '_UserFindResult', - '_UserSignUpFields', - '_UserCreateFields', - '_UserUpdateFields', + 'Role', + 'RoleWhereInput', + 'CreateRoleFieldsInput', + 'UpdateRoleFieldsInput', + 'RoleFindResult', + 'User', + 'UserWhereInput', + 'UserFindResult', + 'SignUpFieldsInput', + 'CreateUserFieldsInput', + 'UpdateUserFieldsInput', ]; expect( expectedTypes.every(type => schemaTypes.indexOf(type) !== -1) @@ -773,8 +774,8 @@ describe('ParseGraphQLServer', () => { `, })).data['__type']; const possibleTypes = objectType.possibleTypes.map(o => o.name); - expect(possibleTypes).toContain('_UserClass'); - expect(possibleTypes).toContain('_RoleClass'); + expect(possibleTypes).toContain('User'); + expect(possibleTypes).toContain('Role'); expect(possibleTypes).toContain('Element'); }); @@ -787,7 +788,7 @@ describe('ParseGraphQLServer', () => { const userFields = (await apolloClient.query({ query: gql` query UserType { - __type(name: "_UserClass") { + __type(name: "User") { fields { name } @@ -802,7 +803,7 @@ describe('ParseGraphQLServer', () => { const userFields = (await apolloClient.query({ query: gql` query UserType { - __type(name: "_UserClass") { + __type(name: "User") { fields { name } @@ -842,12 +843,12 @@ describe('ParseGraphQLServer', () => { const { data } = await apolloClient.query({ query: gql` query UserType { - userType: __type(name: "_UserClass") { + userType: __type(name: "User") { fields { name } } - superCarType: __type(name: "SuperCarClass") { + superCarType: __type(name: "SuperCar") { fields { name } @@ -873,12 +874,12 @@ describe('ParseGraphQLServer', () => { const { data } = await apolloClient.query({ query: gql` query UserType { - userType: __type(name: "_UserClass") { + userType: __type(name: "User") { fields { name } } - superCarType: __type(name: "SuperCarClass") { + superCarType: __type(name: "SuperCar") { fields { name } @@ -900,7 +901,7 @@ describe('ParseGraphQLServer', () => { query: gql` query GetSuperCar($objectId: ID!) { objects { - getSuperCar(objectId: $objectId) { + superCar(objectId: $objectId) { objectId } } @@ -917,7 +918,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindCustomer { objects { - findCustomer { + customers { count } } @@ -952,7 +953,7 @@ describe('ParseGraphQLServer', () => { query: gql` query GetSuperCar($objectId: ID!) { objects { - getSuperCar(objectId: $objectId) { + superCar(objectId: $objectId) { objectId } } @@ -968,7 +969,7 @@ describe('ParseGraphQLServer', () => { query: gql` query GetCustomer($objectId: ID!) { objects { - getCustomer(objectId: $objectId) { + customer(objectId: $objectId) { objectId } } @@ -984,7 +985,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindSuperCar { objects { - findSuperCar { + superCars { count } } @@ -997,7 +998,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindCustomer { objects { - findCustomer { + customers { count } } @@ -1321,7 +1322,7 @@ describe('ParseGraphQLServer', () => { query: gql` query GetSuperCar($objectId: ID!) { objects { - getSuperCar(objectId: $objectId) { + superCar(objectId: $objectId) { objectId engine doors @@ -1341,7 +1342,7 @@ describe('ParseGraphQLServer', () => { query: gql` query GetSuperCar($objectId: ID!) { objects { - getSuperCar(objectId: $objectId) { + superCar(objectId: $objectId) { objectId engine doors @@ -1354,7 +1355,7 @@ describe('ParseGraphQLServer', () => { variables: { objectId: superCar.id, }, - })).data.objects.getSuperCar; + })).data.objects.superCar; expect(getSuperCar).toBeTruthy(); await parseGraphQLServer.setGraphQLConfig({ @@ -1374,7 +1375,7 @@ describe('ParseGraphQLServer', () => { query: gql` query GetSuperCar($objectId: ID!) { objects { - getSuperCar(objectId: $objectId) { + superCar(objectId: $objectId) { engine } } @@ -1389,7 +1390,7 @@ describe('ParseGraphQLServer', () => { query: gql` query GetSuperCar($objectId: ID!) { objects { - getSuperCar(objectId: $objectId) { + superCar(objectId: $objectId) { objectId } } @@ -1398,7 +1399,7 @@ describe('ParseGraphQLServer', () => { variables: { objectId: superCar.id, }, - })).data.objects.getSuperCar; + })).data.objects.superCar; expect(getSuperCar.objectId).toBe(superCar.id); }); it('should only allow the supplied constraint fields for a class', async () => { @@ -1440,7 +1441,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindSuperCar { objects { - findSuperCar( + superCars( where: { insuranceCertificate: { _eq: "private-file.pdf" } } @@ -1458,7 +1459,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindSuperCar { objects { - findSuperCar(where: { mileage: { _eq: 0 } }) { + superCars(where: { mileage: { _eq: 0 } }) { count } } @@ -1472,7 +1473,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindSuperCar { objects { - findSuperCar(where: { engine: { _eq: "petrol" } }) { + superCars(where: { engine: { _eq: "petrol" } }) { count } } @@ -1532,7 +1533,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindSuperCar { objects { - findSuperCar(order: [engine_ASC]) { + superCars(order: [engine_ASC]) { results { objectId } @@ -1547,7 +1548,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindSuperCar { objects { - findSuperCar(order: [engine_DESC]) { + superCars(order: [engine_DESC]) { results { objectId } @@ -1562,7 +1563,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindSuperCar { objects { - findSuperCar(order: [mileage_DESC]) { + superCars(order: [mileage_DESC]) { results { objectId } @@ -1578,7 +1579,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindSuperCar { objects { - findSuperCar(order: [mileage_ASC]) { + superCars(order: [mileage_ASC]) { results { objectId } @@ -1593,7 +1594,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindSuperCar { objects { - findSuperCar(order: [doors_ASC]) { + superCars(order: [doors_ASC]) { results { objectId } @@ -1608,7 +1609,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindSuperCar { objects { - findSuperCar(order: [price_DESC]) { + superCars(order: [price_DESC]) { results { objectId } @@ -1623,7 +1624,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindSuperCar { objects { - findSuperCar(order: [price_ASC, doors_DESC]) { + superCars(order: [price_ASC, doors_DESC]) { results { objectId } @@ -1673,7 +1674,7 @@ describe('ParseGraphQLServer', () => { query: gql` query GetCustomer($objectId: ID!) { objects { - getCustomer(objectId: $objectId) { + customer(objectId: $objectId) { objectId someField createdAt @@ -1685,7 +1686,7 @@ describe('ParseGraphQLServer', () => { variables: { objectId: obj.id, }, - })).data.objects.getCustomer; + })).data.objects.customer; expect(result.objectId).toEqual(obj.id); expect(result.someField).toEqual('someValue'); @@ -1718,10 +1719,10 @@ describe('ParseGraphQLServer', () => { query: gql` query GetCustomer($objectId: ID!) { objects { - getCustomer(objectId: $objectId) { + customer(objectId: $objectId) { objectId manyRelations { - ... on CustomerClass { + ... on Customer { objectId someCustomerField arrayField { @@ -1730,7 +1731,7 @@ describe('ParseGraphQLServer', () => { } } } - ... on SomeClassClass { + ... on SomeClass { objectId someClassField } @@ -1744,7 +1745,7 @@ describe('ParseGraphQLServer', () => { variables: { objectId: obj3.id, }, - })).data.objects.getCustomer; + })).data.objects.customer; expect(result.objectId).toEqual(obj3.id); expect(result.manyRelations.length).toEqual(2); @@ -1781,7 +1782,8 @@ describe('ParseGraphQLServer', () => { query: gql` query GetSomeObject($objectId: ID!) { objects { - get${className}(objectId: $objectId) { + ${className.charAt(0).toLowerCase() + + className.slice(1)}(objectId: $objectId) { objectId createdAt } @@ -2042,7 +2044,7 @@ describe('ParseGraphQLServer', () => { objectId: $objectId include: "pointerToUser" ) - getGraphQLClass(objectId: $objectId) { + graphQLClass(objectId: $objectId) { pointerToUser { username } @@ -2067,7 +2069,7 @@ describe('ParseGraphQLServer', () => { result2.data.objects.get.pointerToUser.username ).toBeDefined(); expect( - result2.data.objects.getGraphQLClass.pointerToUser.username + result2.data.objects.graphQLClass.pointerToUser.username ).toBeDefined(); }); @@ -2285,7 +2287,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindCustomer { objects { - findCustomer { + customers { results { objectId someField @@ -2298,9 +2300,9 @@ describe('ParseGraphQLServer', () => { `, }); - expect(result.data.objects.findCustomer.results.length).toEqual(2); + expect(result.data.objects.customers.results.length).toEqual(2); - result.data.objects.findCustomer.results.forEach(resultObj => { + result.data.objects.customers.results.forEach(resultObj => { const obj = resultObj.objectId === obj1.id ? obj1 : obj2; expect(resultObj.objectId).toEqual(obj.id); expect(resultObj.someField).toEqual(obj.get('someField')); @@ -2313,8 +2315,10 @@ describe('ParseGraphQLServer', () => { await prepareData(); await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); - async function findObjects(className, headers) { + const graphqlClassName = pluralize( + className.charAt(0).toLowerCase() + className.slice(1) + ); const result = await apolloClient.query({ query: gql` query FindSomeObjects($className: String!) { @@ -2322,7 +2326,7 @@ describe('ParseGraphQLServer', () => { find(className: $className) { results } - find${className} { + ${graphqlClassName} { results { objectId someField @@ -2341,7 +2345,7 @@ describe('ParseGraphQLServer', () => { const genericFindResults = result.data.objects.find.results; const specificFindResults = - result.data.objects[`find${className}`].results; + result.data.objects[graphqlClassName].results; genericFindResults.forEach(({ objectId, someField }) => { expect( specificFindResults.some( @@ -2470,9 +2474,9 @@ describe('ParseGraphQLServer', () => { const result = await apolloClient.query({ query: gql` - query FindSomeObjects($where: GraphQLClassConstraints) { + query FindSomeObjects($where: GraphQLClassWhereInput) { objects { - findGraphQLClass(where: $where) { + graphQLClasses(where: $where) { results { someField } @@ -2511,7 +2515,7 @@ describe('ParseGraphQLServer', () => { }); expect( - result.data.objects.findGraphQLClass.results + result.data.objects.graphQLClasses.results .map(object => object.someField) .sort() ).toEqual(['someValue1', 'someValue3']); @@ -2526,7 +2530,7 @@ describe('ParseGraphQLServer', () => { query: gql` query { objects { - findGraphQLClass( + graphQLClasses( where: { _or: [ { someField: { _eq: "someValue1" } } @@ -2549,7 +2553,7 @@ describe('ParseGraphQLServer', () => { }); expect( - result.data.objects.findGraphQLClass.results + result.data.objects.graphQLClasses.results .map(object => object.someField) .sort() ).toEqual(['someValue1', 'someValue2']); @@ -2572,7 +2576,7 @@ describe('ParseGraphQLServer', () => { query FindSomeObjects( $className: String! $where: Object - $whereCustom: SomeClassConstraints + $whereCustom: SomeClassWhereInput $order: String $orderCustom: [SomeClassOrder!] $skip: Int @@ -2588,7 +2592,7 @@ describe('ParseGraphQLServer', () => { ) { results } - findSomeClass( + someClasses( where: $whereCustom order: $orderCustom skip: $skip @@ -2624,9 +2628,7 @@ describe('ParseGraphQLServer', () => { result.data.objects.find.results.map(obj => obj.someField) ).toEqual(['someValue14', 'someValue17']); expect( - result.data.objects.findSomeClass.results.map( - obj => obj.someField - ) + result.data.objects.someClasses.results.map(obj => obj.someField) ).toEqual(['someValue14', 'someValue17']); }); @@ -2661,7 +2663,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindSomeObjects( $where1: Object - $where2: GraphQLClassConstraints + $where2: GraphQLClassWhereInput $limit: Int ) { objects { @@ -2673,7 +2675,7 @@ describe('ParseGraphQLServer', () => { results count } - findGraphQLClass(where: $where2, limit: $limit) { + graphQLClasses(where: $where2, limit: $limit) { results { objectId } @@ -2696,8 +2698,8 @@ describe('ParseGraphQLServer', () => { expect(result.data.objects.find.results).toEqual([]); expect(result.data.objects.find.count).toEqual(2); - expect(result.data.objects.findGraphQLClass.results).toEqual([]); - expect(result.data.objects.findGraphQLClass.count).toEqual(2); + expect(result.data.objects.graphQLClasses.results).toEqual([]); + expect(result.data.objects.graphQLClasses.count).toEqual(2); }); it('should only count', async () => { @@ -2731,13 +2733,13 @@ describe('ParseGraphQLServer', () => { query: gql` query FindSomeObjects( $where1: Object - $where2: GraphQLClassConstraints + $where2: GraphQLClassWhereInput ) { objects { find(className: "GraphQLClass", where: $where1) { count } - findGraphQLClass(where: $where2) { + graphQLClasses(where: $where2) { count } } @@ -2756,10 +2758,8 @@ describe('ParseGraphQLServer', () => { expect(result.data.objects.find.results).toBeUndefined(); expect(result.data.objects.find.count).toEqual(2); - expect( - result.data.objects.findGraphQLClass.results - ).toBeUndefined(); - expect(result.data.objects.findGraphQLClass.count).toEqual(2); + expect(result.data.objects.graphQLClasses.results).toBeUndefined(); + expect(result.data.objects.graphQLClasses.count).toEqual(2); }); it('should respect max limit', async () => { @@ -2788,7 +2788,7 @@ describe('ParseGraphQLServer', () => { results count } - findSomeClass( + someClasses( where: { objectId: { _exists: true } } limit: $limit ) { @@ -2812,10 +2812,8 @@ describe('ParseGraphQLServer', () => { expect(result.data.objects.find.results.length).toEqual(10); expect(result.data.objects.find.count).toEqual(100); - expect(result.data.objects.findSomeClass.results.length).toEqual( - 10 - ); - expect(result.data.objects.findSomeClass.count).toEqual(100); + expect(result.data.objects.someClasses.results.length).toEqual(10); + expect(result.data.objects.someClasses.count).toEqual(100); }); it('should support keys argument', async () => { @@ -2924,7 +2922,7 @@ describe('ParseGraphQLServer', () => { query: gql` query FindSomeObject( $where1: Object - $where2: GraphQLClassConstraints + $where2: GraphQLClassWhereInput ) { objects { find( @@ -2934,7 +2932,7 @@ describe('ParseGraphQLServer', () => { ) { results } - findGraphQLClass(where: $where2) { + graphQLClasses(where: $where2) { results { pointerToUser { username @@ -2954,7 +2952,6 @@ describe('ParseGraphQLServer', () => { }, }, }); - expect( result1.data.objects.find.results[0].pointerToUser.username ).toBeUndefined(); @@ -2962,7 +2959,7 @@ describe('ParseGraphQLServer', () => { result2.data.objects.find.results[0].pointerToUser.username ).toBeDefined(); expect( - result2.data.objects.findGraphQLClass.results[0].pointerToUser + result2.data.objects.graphQLClasses.results[0].pointerToUser .username ).toBeDefined(); }); @@ -3281,7 +3278,7 @@ describe('ParseGraphQLServer', () => { const result = await apolloClient.mutate({ mutation: gql` - mutation CreateCustomer($fields: CustomerCreateFields) { + mutation CreateCustomer($fields: CreateCustomerFieldsInput) { objects { createCustomer(fields: $fields) { objectId @@ -3451,7 +3448,7 @@ describe('ParseGraphQLServer', () => { mutation: gql` mutation UpdateCustomer( $objectId: ID! - $fields: CustomerUpdateFields + $fields: UpdateCustomerFieldsInput ) { objects { updateCustomer(objectId: $objectId, fields: $fields) { @@ -3675,7 +3672,7 @@ describe('ParseGraphQLServer', () => { mutation: gql` mutation UpdateSomeObject( $objectId: ID! - $fields: ${className}UpdateFields + $fields: Update${className}FieldsInput ) { objects { update${className}( @@ -4086,9 +4083,9 @@ describe('ParseGraphQLServer', () => { 'operations', JSON.stringify({ query: ` - mutation CreateFile($file: Upload!) { + mutation CreateFile($upload: Upload!) { files { - create(file: $file) { + create(upload: $upload) { name url } @@ -4096,11 +4093,11 @@ describe('ParseGraphQLServer', () => { } `, variables: { - file: null, + upload: null, }, }) ); - body.append('map', JSON.stringify({ 1: ['variables.file'] })); + body.append('map', JSON.stringify({ 1: ['variables.upload'] })); body.append('1', 'My File Content', { filename: 'myFileName.txt', contentType: 'text/plain', @@ -4148,7 +4145,7 @@ describe('ParseGraphQLServer', () => { query: gql` query GetCurrentUser { users { - me { + viewer { objectId username email @@ -4167,7 +4164,7 @@ describe('ParseGraphQLServer', () => { objectId, username: resultUserName, email: resultEmail, - } = result.data.users.me; + } = result.data.users.viewer; expect(objectId).toBeDefined(); expect(resultUserName).toEqual(userName); expect(resultEmail).toEqual(email); @@ -4195,7 +4192,7 @@ describe('ParseGraphQLServer', () => { query: gql` query GetCurrentUser { users { - me { + viewer { objectId sessionToken userFoo { @@ -4216,7 +4213,7 @@ describe('ParseGraphQLServer', () => { objectId, sessionToken, userFoo: resultFoo, - } = result.data.users.me; + } = result.data.users.viewer; expect(objectId).toEqual(user.id); expect(sessionToken).toBeDefined(); expect(resultFoo).toBeDefined(); @@ -4232,7 +4229,7 @@ describe('ParseGraphQLServer', () => { await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); const result = await apolloClient.mutate({ mutation: gql` - mutation SignUp($fields: _UserSignUpFields) { + mutation SignUp($fields: SignUpFieldsInput) { users { signUp(fields: $fields) { sessionToken @@ -4265,9 +4262,9 @@ describe('ParseGraphQLServer', () => { await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); const result = await apolloClient.mutate({ mutation: gql` - mutation LogInUser($input: _UserLoginFields) { + mutation LogInUser($fields: LogInFieldsInput) { users { - logIn(input: $input) { + logIn(fields: $fields) { sessionToken someField } @@ -4275,7 +4272,7 @@ describe('ParseGraphQLServer', () => { } `, variables: { - input: { + fields: { username: 'user1', password: 'user1', }, @@ -4296,16 +4293,16 @@ describe('ParseGraphQLServer', () => { const logIn = await apolloClient.mutate({ mutation: gql` - mutation LogInUser($input: _UserLoginFields) { + mutation LogInUser($fields: LogInFieldsInput) { users { - logIn(input: $input) { + logIn(fields: $fields) { sessionToken } } } `, variables: { - input: { + fields: { username: 'user1', password: 'user1', }, @@ -4318,7 +4315,9 @@ describe('ParseGraphQLServer', () => { mutation: gql` mutation LogOutUser { users { - logOut + logOut { + sessionToken + } } } `, @@ -4328,7 +4327,7 @@ describe('ParseGraphQLServer', () => { }, }, }); - expect(logOut.data.users.logOut).toBeTruthy(); + expect(logOut.data.users.logOut).toBeDefined(); try { await apolloClient.query({ @@ -4395,7 +4394,7 @@ describe('ParseGraphQLServer', () => { query: gql` query GetCurrentUser { users { - me { + viewer { username } } @@ -4426,12 +4425,12 @@ describe('ParseGraphQLServer', () => { query: gql` query GetCurrentUser { users { - me { + viewer { username } } objects { - findCar { + cars { results { objectId } @@ -4625,7 +4624,7 @@ describe('ParseGraphQLServer', () => { await apolloClient.mutate({ mutation: gql` - mutation CreateSomeObject($fields: SomeClassCreateFields) { + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { objects { createSomeClass(fields: $fields) { objectId @@ -4645,9 +4644,7 @@ describe('ParseGraphQLServer', () => { query GetSomeObject($objectId: ID!, $someFieldValue: String) { objects { get(className: "SomeClass", objectId: $objectId) - findSomeClass( - where: { someField: { _eq: $someFieldValue } } - ) { + someClasses(where: { someField: { _eq: $someFieldValue } }) { results { someField } @@ -4663,9 +4660,7 @@ describe('ParseGraphQLServer', () => { expect(typeof getResult.data.objects.get.someField).toEqual('string'); expect(getResult.data.objects.get.someField).toEqual(someFieldValue); - expect(getResult.data.objects.findSomeClass.results.length).toEqual( - 2 - ); + expect(getResult.data.objects.someClasses.results.length).toEqual(2); }); it('should support Int numbers', async () => { @@ -4692,7 +4687,7 @@ describe('ParseGraphQLServer', () => { await apolloClient.mutate({ mutation: gql` - mutation CreateSomeObject($fields: SomeClassCreateFields) { + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { objects { createSomeClass(fields: $fields) { objectId @@ -4715,9 +4710,7 @@ describe('ParseGraphQLServer', () => { query GetSomeObject($objectId: ID!, $someFieldValue: Float) { objects { get(className: "SomeClass", objectId: $objectId) - findSomeClass( - where: { someField: { _eq: $someFieldValue } } - ) { + someClasses(where: { someField: { _eq: $someFieldValue } }) { results { someField } @@ -4733,9 +4726,7 @@ describe('ParseGraphQLServer', () => { expect(typeof getResult.data.objects.get.someField).toEqual('number'); expect(getResult.data.objects.get.someField).toEqual(someFieldValue); - expect(getResult.data.objects.findSomeClass.results.length).toEqual( - 2 - ); + expect(getResult.data.objects.someClasses.results.length).toEqual(2); }); it('should support Float numbers', async () => { @@ -4765,7 +4756,7 @@ describe('ParseGraphQLServer', () => { await apolloClient.mutate({ mutation: gql` - mutation CreateSomeObject($fields: SomeClassCreateFields) { + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { objects { createSomeClass(fields: $fields) { objectId @@ -4785,9 +4776,7 @@ describe('ParseGraphQLServer', () => { query GetSomeObject($objectId: ID!, $someFieldValue: Float) { objects { get(className: "SomeClass", objectId: $objectId) - findSomeClass( - where: { someField: { _eq: $someFieldValue } } - ) { + someClasses(where: { someField: { _eq: $someFieldValue } }) { results { someField } @@ -4803,9 +4792,7 @@ describe('ParseGraphQLServer', () => { expect(typeof getResult.data.objects.get.someField).toEqual('number'); expect(getResult.data.objects.get.someField).toEqual(someFieldValue); - expect(getResult.data.objects.findSomeClass.results.length).toEqual( - 2 - ); + expect(getResult.data.objects.someClasses.results.length).toEqual(2); }); it('should support Boolean', async () => { @@ -4838,7 +4825,7 @@ describe('ParseGraphQLServer', () => { await apolloClient.mutate({ mutation: gql` - mutation CreateSomeObject($fields: SomeClassCreateFields) { + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { objects { createSomeClass(fields: $fields) { objectId @@ -4863,7 +4850,7 @@ describe('ParseGraphQLServer', () => { ) { objects { get(className: "SomeClass", objectId: $objectId) - findSomeClass( + someClasses( where: { someFieldTrue: { _eq: $someFieldValueTrue } someFieldFalse: { _eq: $someFieldValueFalse } @@ -4891,9 +4878,7 @@ describe('ParseGraphQLServer', () => { ); expect(getResult.data.objects.get.someFieldTrue).toEqual(true); expect(getResult.data.objects.get.someFieldFalse).toEqual(false); - expect(getResult.data.objects.findSomeClass.results.length).toEqual( - 2 - ); + expect(getResult.data.objects.someClasses.results.length).toEqual(2); }); it('should support Date', async () => { @@ -4926,7 +4911,7 @@ describe('ParseGraphQLServer', () => { await apolloClient.mutate({ mutation: gql` - mutation CreateSomeObject($fields: SomeClassCreateFields) { + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { objects { createSomeClass(fields: $fields) { objectId @@ -4946,7 +4931,7 @@ describe('ParseGraphQLServer', () => { query GetSomeObject($objectId: ID!) { objects { get(className: "SomeClass", objectId: $objectId) - findSomeClass(where: { someField: { _exists: true } }) { + someClasses(where: { someField: { _exists: true } }) { results { objectId } @@ -4961,9 +4946,7 @@ describe('ParseGraphQLServer', () => { expect(typeof getResult.data.objects.get.someField).toEqual('object'); expect(getResult.data.objects.get.someField).toEqual(someFieldValue); - expect(getResult.data.objects.findSomeClass.results.length).toEqual( - 2 - ); + expect(getResult.data.objects.someClasses.results.length).toEqual(2); }); it('should support createdAt', async () => { @@ -5057,8 +5040,8 @@ describe('ParseGraphQLServer', () => { await apolloClient.mutate({ mutation: gql` mutation CreateChildObject( - $fields1: ChildClassCreateFields - $fields2: ChildClassCreateFields + $fields1: CreateChildClassFieldsInput + $fields2: CreateChildClassFieldsInput ) { objects { createChildClass1: createChildClass(fields: $fields1) { @@ -5089,7 +5072,7 @@ describe('ParseGraphQLServer', () => { ) { objects { get(className: "ChildClass", objectId: $objectId) - findChildClass1: findChildClass( + findChildClass1: childClasses( where: { pointerField: { _eq: $pointerFieldValue1 } } ) { results { @@ -5099,7 +5082,7 @@ describe('ParseGraphQLServer', () => { } } } - findChildClass2: findChildClass( + findChildClass2: childClasses( where: { pointerField: { _eq: $pointerFieldValue2 } } ) { results { @@ -5187,7 +5170,7 @@ describe('ParseGraphQLServer', () => { await apolloClient.mutate({ mutation: gql` - mutation CreateMainObject($fields: MainClassCreateFields) { + mutation CreateMainObject($fields: CreateMainClassFieldsInput) { objects { createMainClass(fields: $fields) { objectId @@ -5223,7 +5206,7 @@ describe('ParseGraphQLServer', () => { query GetMainObject($objectId: ID!) { objects { get(className: "MainClass", objectId: $objectId) - getMainClass(objectId: $objectId) { + mainClass(objectId: $objectId) { relationField { results { objectId @@ -5248,11 +5231,11 @@ describe('ParseGraphQLServer', () => { className: 'SomeClass', }); expect( - getResult.data.objects.getMainClass.relationField.results.length - ).toEqual(2); - expect( - getResult.data.objects.getMainClass.relationField.count + getResult.data.objects.mainClass.relationField.results.length ).toEqual(2); + expect(getResult.data.objects.mainClass.relationField.count).toEqual( + 2 + ); const findResult = await apolloClient.query({ query: gql` @@ -5310,9 +5293,9 @@ describe('ParseGraphQLServer', () => { 'operations', JSON.stringify({ query: ` - mutation CreateFile($file: Upload!) { + mutation CreateFile($upload: Upload!) { files { - create(file: $file) { + create(upload: $upload) { name url } @@ -5320,11 +5303,11 @@ describe('ParseGraphQLServer', () => { } `, variables: { - file: null, + upload: null, }, }) ); - body.append('map', JSON.stringify({ 1: ['variables.file'] })); + body.append('map', JSON.stringify({ 1: ['variables.upload'] })); body.append('1', 'My File Content', { filename: 'myFileName.txt', contentType: 'text/plain', @@ -5375,8 +5358,8 @@ describe('ParseGraphQLServer', () => { await apolloClient.mutate({ mutation: gql` mutation CreateSomeObject( - $fields1: SomeClassCreateFields - $fields2: SomeClassCreateFields + $fields1: CreateSomeClassFieldsInput + $fields2: CreateSomeClassFieldsInput ) { objects { createSomeClass1: createSomeClass(fields: $fields1) { @@ -5406,7 +5389,7 @@ describe('ParseGraphQLServer', () => { query GetSomeObject($objectId: ID!) { objects { get(className: "SomeClass", objectId: $objectId) - findSomeClass1: findSomeClass( + findSomeClass1: someClasses( where: { someField: { _exists: true } } ) { results { @@ -5416,7 +5399,7 @@ describe('ParseGraphQLServer', () => { } } } - findSomeClass2: findSomeClass( + findSomeClass2: someClasses( where: { someField: { _exists: true } } ) { results { @@ -5479,7 +5462,7 @@ describe('ParseGraphQLServer', () => { await apolloClient.mutate({ mutation: gql` - mutation CreateSomeObject($fields: SomeClassCreateFields) { + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { objects { createSomeClass(fields: $fields) { objectId @@ -5506,12 +5489,12 @@ describe('ParseGraphQLServer', () => { query: gql` query GetSomeObject( $objectId: ID! - $where: SomeClassConstraints + $where: SomeClassWhereInput $genericWhere: Object ) { objects { get(className: "SomeClass", objectId: $objectId) - findSomeClass(where: $where) { + someClasses(where: $where) { results { objectId someField @@ -5532,7 +5515,7 @@ describe('ParseGraphQLServer', () => { const { get: getResult, - findSomeClass, + someClasses, find, } = queryResult.data.objects; @@ -5541,9 +5524,9 @@ describe('ParseGraphQLServer', () => { expect(someField).toEqual(someFieldValue); // Checks class query results - expect(findSomeClass.results.length).toEqual(2); - expect(findSomeClass.results[0].someField).toEqual(someFieldValue); - expect(findSomeClass.results[1].someField).toEqual(someFieldValue); + expect(someClasses.results.length).toEqual(2); + expect(someClasses.results[0].someField).toEqual(someFieldValue); + expect(someClasses.results[1].someField).toEqual(someFieldValue); // Checks generic query results expect(find.results.length).toEqual(2); @@ -5565,9 +5548,9 @@ describe('ParseGraphQLServer', () => { const createResult = await apolloClient.mutate({ mutation: gql` - mutation CreateSomeObject($fields: Object, $fields2: Object) { + mutation CreateSomeObject($fields1: Object, $fields2: Object) { objects { - create1: create(className: "SomeClass", fields: $fields) { + create1: create(className: "SomeClass", fields: $fields1) { objectId } create2: create(className: "SomeClass", fields: $fields2) { @@ -5577,7 +5560,7 @@ describe('ParseGraphQLServer', () => { } `, variables: { - fields: { + fields1: { someField: someFieldValue, }, fields2: { @@ -5619,11 +5602,11 @@ describe('ParseGraphQLServer', () => { const findResult = await apolloClient.query({ query: gql` query FindSomeObject( - $where: SomeClassConstraints + $where: SomeClassWhereInput $genericWhere: Object ) { objects { - findSomeClass(where: $where) { + someClasses(where: $where) { results { objectId someField @@ -5642,10 +5625,10 @@ describe('ParseGraphQLServer', () => { }); const { create1, create2 } = createResult.data.objects; - const { findSomeClass, find } = findResult.data.objects; + const { someClasses, find } = findResult.data.objects; // Checks class query results - const { results } = findSomeClass; + const { results } = someClasses; expect(results.length).toEqual(2); expect( results.find(result => result.objectId === create1.objectId) @@ -5696,7 +5679,7 @@ describe('ParseGraphQLServer', () => { await apolloClient.mutate({ mutation: gql` - mutation CreateSomeObject($fields: SomeClassCreateFields) { + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { objects { createSomeClass(fields: $fields) { objectId @@ -5716,7 +5699,7 @@ describe('ParseGraphQLServer', () => { query GetSomeObject($objectId: ID!) { objects { get(className: "SomeClass", objectId: $objectId) - findSomeClass(where: { someField: { _exists: true } }) { + someClasses(where: { someField: { _exists: true } }) { results { objectId someField { @@ -5737,9 +5720,7 @@ describe('ParseGraphQLServer', () => { const { someField } = getResult.data.objects.get; expect(Array.isArray(someField)).toBeTruthy(); expect(someField).toEqual(someFieldValue); - expect(getResult.data.objects.findSomeClass.results.length).toEqual( - 2 - ); + expect(getResult.data.objects.someClasses.results.length).toEqual(2); }); it('should support null values', async () => { @@ -5843,8 +5824,8 @@ describe('ParseGraphQLServer', () => { await apolloClient.mutate({ mutation: gql` mutation CreateSomeObject( - $fields1: SomeClassCreateFields - $fields2: SomeClassCreateFields + $fields1: CreateSomeClassFieldsInput + $fields2: CreateSomeClassFieldsInput ) { objects { createSomeClass1: createSomeClass(fields: $fields1) { @@ -5871,9 +5852,7 @@ describe('ParseGraphQLServer', () => { query GetSomeObject($objectId: ID!, $someFieldValue: Bytes) { objects { get(className: "SomeClass", objectId: $objectId) - findSomeClass( - where: { someField: { _eq: $someFieldValue } } - ) { + someClasses(where: { someField: { _eq: $someFieldValue } }) { results { objectId someField @@ -5890,9 +5869,7 @@ describe('ParseGraphQLServer', () => { expect(typeof getResult.data.objects.get.someField).toEqual('object'); expect(getResult.data.objects.get.someField).toEqual(someFieldValue); - expect(getResult.data.objects.findSomeClass.results.length).toEqual( - 3 - ); + expect(getResult.data.objects.someClasses.results.length).toEqual(3); }); it('should support Geo Points', async () => { @@ -5926,7 +5903,7 @@ describe('ParseGraphQLServer', () => { await apolloClient.mutate({ mutation: gql` - mutation CreateSomeObject($fields: SomeClassCreateFields) { + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { objects { createSomeClass(fields: $fields) { objectId @@ -5949,7 +5926,7 @@ describe('ParseGraphQLServer', () => { query GetSomeObject($objectId: ID!) { objects { get(className: "SomeClass", objectId: $objectId) - findSomeClass(where: { someField: { _exists: true } }) { + someClasses(where: { someField: { _exists: true } }) { results { objectId someField { @@ -5968,9 +5945,7 @@ describe('ParseGraphQLServer', () => { expect(typeof getResult.data.objects.get.someField).toEqual('object'); expect(getResult.data.objects.get.someField).toEqual(someFieldValue); - expect(getResult.data.objects.findSomeClass.results.length).toEqual( - 2 - ); + expect(getResult.data.objects.someClasses.results.length).toEqual(2); }); it('should support Polygons', async () => { @@ -6003,7 +5978,7 @@ describe('ParseGraphQLServer', () => { await apolloClient.mutate({ mutation: gql` - mutation CreateSomeObject($fields: SomeClassCreateFields) { + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { objects { createSomeClass(fields: $fields) { objectId @@ -6026,9 +6001,7 @@ describe('ParseGraphQLServer', () => { query GetSomeObject($objectId: ID!) { objects { get(className: "SomeClass", objectId: $objectId) - findSomeClass( - where: { somePolygonField: { _exists: true } } - ) { + someClasses(where: { somePolygonField: { _exists: true } }) { results { objectId somePolygonField { @@ -6051,9 +6024,7 @@ describe('ParseGraphQLServer', () => { expect(getResult.data.objects.get.somePolygonField).toEqual( someFieldValue ); - expect(getResult.data.objects.findSomeClass.results.length).toEqual( - 2 - ); + expect(getResult.data.objects.someClasses.results.length).toEqual(2); }); it('should support polygon values', async () => { @@ -6085,7 +6056,7 @@ describe('ParseGraphQLServer', () => { query: gql` query GetSomeObject($objectId: ID!) { objects { - getSomeClass(objectId: $objectId) { + someClass(objectId: $objectId) { somePolygonField { latitude longitude @@ -6102,7 +6073,7 @@ describe('ParseGraphQLServer', () => { const schema = await new Parse.Schema('SomeClass').get(); expect(schema.fields.somePolygonField.type).toEqual('Polygon'); - const { somePolygonField } = getResult.data.objects.getSomeClass; + const { somePolygonField } = getResult.data.objects.someClass; expect(Array.isArray(somePolygonField)).toBeTruthy(); somePolygonField.forEach((coord, i) => { expect(coord.latitude).toEqual(someFieldValue.coordinates[i][0]); @@ -6130,7 +6101,7 @@ describe('ParseGraphQLServer', () => { const createResult = await apolloClient.mutate({ mutation: gql` - mutation CreateSomeObject($fields: SomeClassCreateFields) { + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { objects { createSomeClass(fields: $fields) { objectId @@ -6149,7 +6120,7 @@ describe('ParseGraphQLServer', () => { query: gql` query GetSomeObject($objectId: ID!) { objects { - getSomeClass(objectId: $objectId) { + someClass(objectId: $objectId) { someField } } @@ -6160,7 +6131,7 @@ describe('ParseGraphQLServer', () => { }, }); - expect(getResult.data.objects.getSomeClass.someField).toEqual( + expect(getResult.data.objects.someClass.someField).toEqual( someFieldValue.base64 ); @@ -6173,7 +6144,7 @@ describe('ParseGraphQLServer', () => { mutation: gql` mutation UpdateSomeObject( $objectId: ID! - $fields: SomeClassUpdateFields + $fields: UpdateSomeClassFieldsInput ) { objects { updateSomeClass(objectId: $objectId, fields: $fields) { @@ -6195,9 +6166,9 @@ describe('ParseGraphQLServer', () => { const findResult = await apolloClient.query({ query: gql` - query FindSomeObject($where: SomeClassConstraints!) { + query FindSomeObject($where: SomeClassWhereInput!) { objects { - findSomeClass(where: $where) { + someClasses(where: $where) { results { objectId } @@ -6213,7 +6184,7 @@ describe('ParseGraphQLServer', () => { }, }, }); - const findResults = findResult.data.objects.findSomeClass.results; + const findResults = findResult.data.objects.someClasses.results; expect(findResults.length).toBe(1); expect(findResults[0].objectId).toBe( createResult.data.objects.createSomeClass.objectId @@ -6503,9 +6474,9 @@ describe('ParseGraphQLServer', () => { type Custom { hello: String @resolve hello2: String @resolve(to: "hello") - userEcho(user: _UserCreateFields!): _UserClass! @resolve + userEcho(user: CreateUserFieldsInput!): User! @resolve hello3: String! @mock(with: "Hello world!") - hello4: _UserClass! @mock(with: { username: "somefolk" }) + hello4: User! @mock(with: { username: "somefolk" }) } `, }); @@ -6574,7 +6545,7 @@ describe('ParseGraphQLServer', () => { const result = await apolloClient.query({ query: gql` - query UserEcho($user: _UserCreateFields!) { + query UserEcho($user: CreateUserFieldsInput!) { custom { userEcho(user: $user) { username diff --git a/src/GraphQL/ParseGraphQLSchema.js b/src/GraphQL/ParseGraphQLSchema.js index a0811b2fe1..b4ad2b8701 100644 --- a/src/GraphQL/ParseGraphQLSchema.js +++ b/src/GraphQL/ParseGraphQLSchema.js @@ -15,6 +15,29 @@ import DatabaseController from '../Controllers/DatabaseController'; import { toGraphQLError } from './parseGraphQLUtils'; import * as schemaDirectives from './loaders/schemaDirectives'; +const RESERVED_GRAPHQL_TYPE_NAMES = [ + 'String', + 'Boolean', + 'Int', + 'Float', + 'ID', + 'ArrayResult', + 'Query', + 'Mutation', + 'Subscription', + 'ObjectsQuery', + 'UsersQuery', + 'ObjectsMutation', + 'FilesMutation', + 'UsersMutation', + 'FunctionsMutation', + 'Viewer', + 'SignUpFieldsInput', + 'LogInFieldsInput', +]; +const RESERVED_GRAPHQL_OBJECT_QUERY_NAMES = ['get', 'find']; +const RESERVED_GRAPHQL_OBJECT_MUTATION_NAMES = ['create', 'update', 'delete']; + class ParseGraphQLSchema { databaseController: DatabaseController; parseGraphQLController: ParseGraphQLController; @@ -60,7 +83,7 @@ class ParseGraphQLSchema { this.parseClassesString = parseClassesString; this.parseGraphQLConfig = parseGraphQLConfig; this.parseClassTypes = {}; - this.meType = null; + this.viewerType = null; this.graphQLAutoSchema = null; this.graphQLSchema = null; this.graphQLTypes = []; @@ -92,7 +115,7 @@ class ParseGraphQLSchema { description: 'Query is the top level type for queries.', fields: this.graphQLQueries, }); - this.graphQLTypes.push(graphQLQuery); + this.addGraphQLType(graphQLQuery, true, true); } let graphQLMutation = undefined; @@ -102,7 +125,7 @@ class ParseGraphQLSchema { description: 'Mutation is the top level type for mutations.', fields: this.graphQLMutations, }); - this.graphQLTypes.push(graphQLMutation); + this.addGraphQLType(graphQLMutation, true, true); } let graphQLSubscription = undefined; @@ -112,7 +135,7 @@ class ParseGraphQLSchema { description: 'Subscription is the top level type for subscriptions.', fields: this.graphQLSubscriptions, }); - this.graphQLTypes.push(graphQLSubscription); + this.addGraphQLType(graphQLSubscription, true, true); } this.graphQLAutoSchema = new GraphQLSchema({ @@ -172,6 +195,66 @@ class ParseGraphQLSchema { return this.graphQLSchema; } + addGraphQLType(type, throwError = false, ignoreReserved = false) { + if ( + (!ignoreReserved && RESERVED_GRAPHQL_TYPE_NAMES.includes(type.name)) || + this.graphQLTypes.find(existingType => existingType.name === type.name) + ) { + const message = `Type ${type.name} could not be added to the auto schema because it collided with an existing type.`; + if (throwError) { + throw new Error(message); + } + this.log.warn(message); + return undefined; + } + this.graphQLTypes.push(type); + return type; + } + + addGraphQLObjectQuery( + fieldName, + field, + throwError = false, + ignoreReserved = false + ) { + if ( + (!ignoreReserved && + RESERVED_GRAPHQL_OBJECT_QUERY_NAMES.includes(fieldName)) || + this.graphQLObjectsQueries[fieldName] + ) { + const message = `Object query ${fieldName} could not be added to the auto schema because it collided with an existing field.`; + if (throwError) { + throw new Error(message); + } + this.log.warn(message); + return undefined; + } + this.graphQLObjectsQueries[fieldName] = field; + return field; + } + + addGraphQLObjectMutation( + fieldName, + field, + throwError = false, + ignoreReserved = false + ) { + if ( + (!ignoreReserved && + RESERVED_GRAPHQL_OBJECT_MUTATION_NAMES.includes(fieldName)) || + this.graphQLObjectsMutations[fieldName] + ) { + const message = `Object mutation ${fieldName} could not be added to the auto schema because it collided with an existing field.`; + if (throwError) { + throw new Error(message); + } + this.log.warn(message); + return undefined; + } + this.graphQLObjectsMutations[fieldName] = field; + return field; + } + handleError(error) { if (error instanceof Parse.Error) { this.log.error('Parse error: ', error); @@ -238,7 +321,32 @@ class ParseGraphQLSchema { parseGraphQLConfig: ParseGraphQLConfig ) { const { classConfigs } = parseGraphQLConfig; - return parseClasses.map(parseClass => { + + // Make sures that the default classes and classes that + // starts with capitalized letter will be generated first. + const sortClasses = (a, b) => { + a = a.className; + b = b.className; + if (a[0] === '_') { + if (b[0] !== '_') { + return -1; + } + } + if (b[0] === '_') { + if (a[0] !== '_') { + return 1; + } + } + if (a === b) { + return 0; + } else if (a < b) { + return -1; + } else { + return 1; + } + }; + + return parseClasses.sort(sortClasses).map(parseClass => { let parseClassConfig; if (classConfigs) { parseClassConfig = classConfigs.find( diff --git a/src/GraphQL/ParseGraphQLServer.js b/src/GraphQL/ParseGraphQLServer.js index 59c5fe61e1..e5381d2149 100644 --- a/src/GraphQL/ParseGraphQLServer.js +++ b/src/GraphQL/ParseGraphQLServer.js @@ -25,25 +25,31 @@ class ParseGraphQLServer { } this.config = config; this.parseGraphQLController = this.parseServer.config.parseGraphQLController; + this.log = + (this.parseServer.config && this.parseServer.config.loggerController) || + defaultLogger; this.parseGraphQLSchema = new ParseGraphQLSchema({ parseGraphQLController: this.parseGraphQLController, databaseController: this.parseServer.config.databaseController, - log: - (this.parseServer.config && this.parseServer.config.loggerController) || - defaultLogger, + log: this.log, graphQLCustomTypeDefs: this.config.graphQLCustomTypeDefs, }); } async _getGraphQLOptions(req) { - return { - schema: await this.parseGraphQLSchema.load(), - context: { - info: req.info, - config: req.config, - auth: req.auth, - }, - }; + try { + return { + schema: await this.parseGraphQLSchema.load(), + context: { + info: req.info, + config: req.config, + auth: req.auth, + }, + }; + } catch (e) { + this.log.error(e); + throw e; + } } applyGraphQL(app) { diff --git a/src/GraphQL/loaders/defaultGraphQLTypes.js b/src/GraphQL/loaders/defaultGraphQLTypes.js index 58115b9198..a7f7f78806 100644 --- a/src/GraphQL/loaders/defaultGraphQLTypes.js +++ b/src/GraphQL/loaders/defaultGraphQLTypes.js @@ -377,23 +377,23 @@ const GEO_POINT_FIELDS = { }, }; -const GEO_POINT = new GraphQLInputObjectType({ - name: 'GeoPoint', +const GEO_POINT_INPUT = new GraphQLInputObjectType({ + name: 'GeoPointInput', description: - 'The GeoPoint input type is used in operations that involve inputting fields of type geo point.', + 'The GeoPointInput type is used in operations that involve inputting fields of type geo point.', fields: GEO_POINT_FIELDS, }); -const GEO_POINT_INFO = new GraphQLObjectType({ - name: 'GeoPointInfo', +const GEO_POINT = new GraphQLObjectType({ + name: 'GeoPoint', description: - 'The GeoPointInfo object type is used to return the information about geo points.', + 'The GeoPoint object type is used to return the information about geo point fields.', fields: GEO_POINT_FIELDS, }); -const POLYGON = new GraphQLList(new GraphQLNonNull(GEO_POINT)); +const POLYGON_INPUT = new GraphQLList(new GraphQLNonNull(GEO_POINT_INPUT)); -const POLYGON_INFO = new GraphQLList(new GraphQLNonNull(GEO_POINT_INFO)); +const POLYGON = new GraphQLList(new GraphQLNonNull(GEO_POINT)); const RELATION_OP = new GraphQLEnumType({ name: 'RelationOp', @@ -542,10 +542,10 @@ const COUNT_ATT = { type: new GraphQLNonNull(GraphQLInt), }; -const SUBQUERY = new GraphQLInputObjectType({ - name: 'Subquery', +const SUBQUERY_INPUT = new GraphQLInputObjectType({ + name: 'SubqueryInput', description: - 'The Subquery input type is used to specific a different query to a different class.', + 'The SubqueryInput type is used to specific a different query to a different class.', fields: { className: CLASS_NAME_ATT, where: Object.assign({}, WHERE_ATT, { @@ -554,14 +554,14 @@ const SUBQUERY = new GraphQLInputObjectType({ }, }); -const SELECT_OPERATOR = new GraphQLInputObjectType({ - name: 'SelectOperator', +const SELECT_INPUT = new GraphQLInputObjectType({ + name: 'SelectInput', description: - 'The SelectOperator input type is used to specify a $select operation on a constraint.', + 'The SelectInput type is used to specify a $select operation on a constraint.', fields: { query: { description: 'This is the subquery to be executed.', - type: new GraphQLNonNull(SUBQUERY), + type: new GraphQLNonNull(SUBQUERY_INPUT), }, key: { description: @@ -571,10 +571,10 @@ const SELECT_OPERATOR = new GraphQLInputObjectType({ }, }); -const SEARCH_OPERATOR = new GraphQLInputObjectType({ - name: 'SearchOperator', +const SEARCH_INPUT = new GraphQLInputObjectType({ + name: 'SearchInput', description: - 'The SearchOperator input type is used to specifiy a $search operation on a full text search.', + 'The SearchInput type is used to specifiy a $search operation on a full text search.', fields: { _term: { description: 'This is the term to be searched.', @@ -598,54 +598,54 @@ const SEARCH_OPERATOR = new GraphQLInputObjectType({ }, }); -const TEXT_OPERATOR = new GraphQLInputObjectType({ - name: 'TextOperator', +const TEXT_INPUT = new GraphQLInputObjectType({ + name: 'TextInput', description: - 'The TextOperator input type is used to specify a $text operation on a constraint.', + 'The TextInput type is used to specify a $text operation on a constraint.', fields: { _search: { description: 'This is the search to be executed.', - type: new GraphQLNonNull(SEARCH_OPERATOR), + type: new GraphQLNonNull(SEARCH_INPUT), }, }, }); -const BOX_OPERATOR = new GraphQLInputObjectType({ - name: 'BoxOperator', +const BOX_INPUT = new GraphQLInputObjectType({ + name: 'BoxInput', description: - 'The BoxOperator input type is used to specifiy a $box operation on a within geo query.', + 'The BoxInput type is used to specifiy a $box operation on a within geo query.', fields: { bottomLeft: { description: 'This is the bottom left coordinates of the box.', - type: new GraphQLNonNull(GEO_POINT), + type: new GraphQLNonNull(GEO_POINT_INPUT), }, upperRight: { description: 'This is the upper right coordinates of the box.', - type: new GraphQLNonNull(GEO_POINT), + type: new GraphQLNonNull(GEO_POINT_INPUT), }, }, }); -const WITHIN_OPERATOR = new GraphQLInputObjectType({ - name: 'WithinOperator', +const WITHIN_INPUT = new GraphQLInputObjectType({ + name: 'WithinInput', description: - 'The WithinOperator input type is used to specify a $within operation on a constraint.', + 'The WithinInput type is used to specify a $within operation on a constraint.', fields: { _box: { description: 'This is the box to be specified.', - type: new GraphQLNonNull(BOX_OPERATOR), + type: new GraphQLNonNull(BOX_INPUT), }, }, }); -const CENTER_SPHERE_OPERATOR = new GraphQLInputObjectType({ - name: 'CenterSphereOperator', +const CENTER_SPHERE_INPUT = new GraphQLInputObjectType({ + name: 'CenterSphereInput', description: - 'The CenterSphereOperator input type is used to specifiy a $centerSphere operation on a geoWithin query.', + 'The CenterSphereInput type is used to specifiy a $centerSphere operation on a geoWithin query.', fields: { center: { description: 'This is the center of the sphere.', - type: new GraphQLNonNull(GEO_POINT), + type: new GraphQLNonNull(GEO_POINT_INPUT), }, distance: { description: 'This is the radius of the sphere.', @@ -654,30 +654,30 @@ const CENTER_SPHERE_OPERATOR = new GraphQLInputObjectType({ }, }); -const GEO_WITHIN_OPERATOR = new GraphQLInputObjectType({ - name: 'GeoWithinOperator', +const GEO_WITHIN_INPUT = new GraphQLInputObjectType({ + name: 'GeoWithinInput', description: - 'The GeoWithinOperator input type is used to specify a $geoWithin operation on a constraint.', + 'The GeoWithinInput type is used to specify a $geoWithin operation on a constraint.', fields: { _polygon: { description: 'This is the polygon to be specified.', - type: POLYGON, + type: POLYGON_INPUT, }, _centerSphere: { description: 'This is the sphere to be specified.', - type: CENTER_SPHERE_OPERATOR, + type: CENTER_SPHERE_INPUT, }, }, }); -const GEO_INTERSECTS = new GraphQLInputObjectType({ - name: 'GeoIntersectsOperator', +const GEO_INTERSECTS_INPUT = new GraphQLInputObjectType({ + name: 'GeoIntersectsInput', description: - 'The GeoIntersectsOperator input type is used to specify a $geoIntersects operation on a constraint.', + 'The GeoIntersectsInput type is used to specify a $geoIntersects operation on a constraint.', fields: { _point: { description: 'This is the point to be specified.', - type: GEO_POINT, + type: GEO_POINT_INPUT, }, }, }); @@ -739,13 +739,13 @@ const _exists = { const _select = { description: 'This is the $select operator to specify a constraint to select the objects where a field equals to a key in the result of a different query.', - type: SELECT_OPERATOR, + type: SELECT_INPUT, }; const _dontSelect = { description: 'This is the $dontSelect operator to specify a constraint to select the objects where a field do not equal to a key in the result of a different query.', - type: SELECT_OPERATOR, + type: SELECT_INPUT, }; const _regex = { @@ -760,10 +760,10 @@ const _options = { type: GraphQLString, }; -const STRING_CONSTRAINT = new GraphQLInputObjectType({ - name: 'StringConstraint', +const STRING_WHERE_INPUT = new GraphQLInputObjectType({ + name: 'StringWhereInput', description: - 'The StringConstraint input type is used in operations that involve filtering objects by a field of type String.', + 'The StringWhereInput input type is used in operations that involve filtering objects by a field of type String.', fields: { _eq: _eq(GraphQLString), _ne: _ne(GraphQLString), @@ -781,15 +781,15 @@ const STRING_CONSTRAINT = new GraphQLInputObjectType({ _text: { description: 'This is the $text operator to specify a full text search constraint.', - type: TEXT_OPERATOR, + type: TEXT_INPUT, }, }, }); -const NUMBER_CONSTRAINT = new GraphQLInputObjectType({ - name: 'NumberConstraint', +const NUMBER_WHERE_INPUT = new GraphQLInputObjectType({ + name: 'NumberWhereInput', description: - 'The NumberConstraint input type is used in operations that involve filtering objects by a field of type Number.', + 'The NumberWhereInput input type is used in operations that involve filtering objects by a field of type Number.', fields: { _eq: _eq(GraphQLFloat), _ne: _ne(GraphQLFloat), @@ -805,10 +805,10 @@ const NUMBER_CONSTRAINT = new GraphQLInputObjectType({ }, }); -const BOOLEAN_CONSTRAINT = new GraphQLInputObjectType({ - name: 'BooleanConstraint', +const BOOLEAN_WHERE_INPUT = new GraphQLInputObjectType({ + name: 'BooleanWhereInput', description: - 'The BooleanConstraint input type is used in operations that involve filtering objects by a field of type Boolean.', + 'The BooleanWhereInput input type is used in operations that involve filtering objects by a field of type Boolean.', fields: { _eq: _eq(GraphQLBoolean), _ne: _ne(GraphQLBoolean), @@ -818,10 +818,10 @@ const BOOLEAN_CONSTRAINT = new GraphQLInputObjectType({ }, }); -const ARRAY_CONSTRAINT = new GraphQLInputObjectType({ - name: 'ArrayConstraint', +const ARRAY_WHERE_INPUT = new GraphQLInputObjectType({ + name: 'ArrayWhereInput', description: - 'The ArrayConstraint input type is used in operations that involve filtering objects by a field of type Array.', + 'The ArrayWhereInput input type is used in operations that involve filtering objects by a field of type Array.', fields: { _eq: _eq(ANY), _ne: _ne(ANY), @@ -847,8 +847,8 @@ const ARRAY_CONSTRAINT = new GraphQLInputObjectType({ }, }); -const KEY_VALUE = new GraphQLInputObjectType({ - name: 'KeyValue', +const KEY_VALUE_INPUT = new GraphQLInputObjectType({ + name: 'KeyValueInput', description: 'An entry from an object, i.e., a pair of key and value.', fields: { _key: { @@ -862,29 +862,29 @@ const KEY_VALUE = new GraphQLInputObjectType({ }, }); -const OBJECT_CONSTRAINT = new GraphQLInputObjectType({ - name: 'ObjectConstraint', +const OBJECT_WHERE_INPUT = new GraphQLInputObjectType({ + name: 'ObjectWhereInput', description: - 'The ObjectConstraint input type is used in operations that involve filtering result by a field of type Object.', + 'The ObjectWhereInput input type is used in operations that involve filtering result by a field of type Object.', fields: { - _eq: _eq(KEY_VALUE), - _ne: _ne(KEY_VALUE), - _in: _in(KEY_VALUE), - _nin: _nin(KEY_VALUE), - _lt: _lt(KEY_VALUE), - _lte: _lte(KEY_VALUE), - _gt: _gt(KEY_VALUE), - _gte: _gte(KEY_VALUE), + _eq: _eq(KEY_VALUE_INPUT), + _ne: _ne(KEY_VALUE_INPUT), + _in: _in(KEY_VALUE_INPUT), + _nin: _nin(KEY_VALUE_INPUT), + _lt: _lt(KEY_VALUE_INPUT), + _lte: _lte(KEY_VALUE_INPUT), + _gt: _gt(KEY_VALUE_INPUT), + _gte: _gte(KEY_VALUE_INPUT), _exists, _select, _dontSelect, }, }); -const DATE_CONSTRAINT = new GraphQLInputObjectType({ - name: 'DateConstraint', +const DATE_WHERE_INPUT = new GraphQLInputObjectType({ + name: 'DateWhereInput', description: - 'The DateConstraint input type is used in operations that involve filtering objects by a field of type Date.', + 'The DateWhereInput input type is used in operations that involve filtering objects by a field of type Date.', fields: { _eq: _eq(DATE), _ne: _ne(DATE), @@ -900,10 +900,10 @@ const DATE_CONSTRAINT = new GraphQLInputObjectType({ }, }); -const BYTES_CONSTRAINT = new GraphQLInputObjectType({ - name: 'BytesConstraint', +const BYTES_WHERE_INPUT = new GraphQLInputObjectType({ + name: 'BytesWhereInput', description: - 'The BytesConstraint input type is used in operations that involve filtering objects by a field of type Bytes.', + 'The BytesWhereInput input type is used in operations that involve filtering objects by a field of type Bytes.', fields: { _eq: _eq(BYTES), _ne: _ne(BYTES), @@ -919,10 +919,10 @@ const BYTES_CONSTRAINT = new GraphQLInputObjectType({ }, }); -const FILE_CONSTRAINT = new GraphQLInputObjectType({ - name: 'FileConstraint', +const FILE_WHERE_INPUT = new GraphQLInputObjectType({ + name: 'FileWhereInput', description: - 'The FILE_CONSTRAINT input type is used in operations that involve filtering objects by a field of type File.', + 'The FileWhereInput input type is used in operations that involve filtering objects by a field of type File.', fields: { _eq: _eq(FILE), _ne: _ne(FILE), @@ -940,16 +940,16 @@ const FILE_CONSTRAINT = new GraphQLInputObjectType({ }, }); -const GEO_POINT_CONSTRAINT = new GraphQLInputObjectType({ - name: 'GeoPointConstraint', +const GEO_POINT_WHERE_INPUT = new GraphQLInputObjectType({ + name: 'GeoPointWhereInput', description: - 'The GeoPointConstraint input type is used in operations that involve filtering objects by a field of type GeoPoint.', + 'The GeoPointWhereInput input type is used in operations that involve filtering objects by a field of type GeoPoint.', fields: { _exists, _nearSphere: { description: 'This is the $nearSphere operator to specify a constraint to select the objects where the values of a geo point field is near to another geo point.', - type: GEO_POINT, + type: GEO_POINT_INPUT, }, _maxDistance: { description: @@ -974,26 +974,26 @@ const GEO_POINT_CONSTRAINT = new GraphQLInputObjectType({ _within: { description: 'This is the $within operator to specify a constraint to select the objects where the values of a geo point field is within a specified box.', - type: WITHIN_OPERATOR, + type: WITHIN_INPUT, }, _geoWithin: { description: 'This is the $geoWithin operator to specify a constraint to select the objects where the values of a geo point field is within a specified polygon or sphere.', - type: GEO_WITHIN_OPERATOR, + type: GEO_WITHIN_INPUT, }, }, }); -const POLYGON_CONSTRAINT = new GraphQLInputObjectType({ - name: 'PolygonConstraint', +const POLYGON_WHERE_INPUT = new GraphQLInputObjectType({ + name: 'PolygonWhereInput', description: - 'The PolygonConstraint input type is used in operations that involve filtering objects by a field of type Polygon.', + 'The PolygonWhereInput input type is used in operations that involve filtering objects by a field of type Polygon.', fields: { _exists, _geoIntersects: { description: 'This is the $geoIntersects operator to specify a constraint to select the objects where the values of a polygon field intersect a specified point.', - type: GEO_INTERSECTS, + type: GEO_INTERSECTS_INPUT, }, }, }); @@ -1071,42 +1071,43 @@ const loadArrayResult = (parseGraphQLSchema, parseClasses) => { }; const load = parseGraphQLSchema => { - parseGraphQLSchema.graphQLTypes.push(GraphQLUpload); - parseGraphQLSchema.graphQLTypes.push(ANY); - parseGraphQLSchema.graphQLTypes.push(OBJECT); - parseGraphQLSchema.graphQLTypes.push(DATE); - parseGraphQLSchema.graphQLTypes.push(BYTES); - parseGraphQLSchema.graphQLTypes.push(FILE); - parseGraphQLSchema.graphQLTypes.push(FILE_INFO); - parseGraphQLSchema.graphQLTypes.push(GEO_POINT); - parseGraphQLSchema.graphQLTypes.push(GEO_POINT_INFO); - parseGraphQLSchema.graphQLTypes.push(RELATION_OP); - parseGraphQLSchema.graphQLTypes.push(CREATE_RESULT); - parseGraphQLSchema.graphQLTypes.push(UPDATE_RESULT); - parseGraphQLSchema.graphQLTypes.push(CLASS); - parseGraphQLSchema.graphQLTypes.push(READ_PREFERENCE); - parseGraphQLSchema.graphQLTypes.push(SUBQUERY); - parseGraphQLSchema.graphQLTypes.push(SELECT_OPERATOR); - parseGraphQLSchema.graphQLTypes.push(SEARCH_OPERATOR); - parseGraphQLSchema.graphQLTypes.push(TEXT_OPERATOR); - parseGraphQLSchema.graphQLTypes.push(BOX_OPERATOR); - parseGraphQLSchema.graphQLTypes.push(WITHIN_OPERATOR); - parseGraphQLSchema.graphQLTypes.push(CENTER_SPHERE_OPERATOR); - parseGraphQLSchema.graphQLTypes.push(GEO_WITHIN_OPERATOR); - parseGraphQLSchema.graphQLTypes.push(GEO_INTERSECTS); - parseGraphQLSchema.graphQLTypes.push(STRING_CONSTRAINT); - parseGraphQLSchema.graphQLTypes.push(NUMBER_CONSTRAINT); - parseGraphQLSchema.graphQLTypes.push(BOOLEAN_CONSTRAINT); - parseGraphQLSchema.graphQLTypes.push(ARRAY_CONSTRAINT); - parseGraphQLSchema.graphQLTypes.push(OBJECT_CONSTRAINT); - parseGraphQLSchema.graphQLTypes.push(DATE_CONSTRAINT); - parseGraphQLSchema.graphQLTypes.push(BYTES_CONSTRAINT); - parseGraphQLSchema.graphQLTypes.push(FILE_CONSTRAINT); - parseGraphQLSchema.graphQLTypes.push(GEO_POINT_CONSTRAINT); - parseGraphQLSchema.graphQLTypes.push(POLYGON_CONSTRAINT); - parseGraphQLSchema.graphQLTypes.push(FIND_RESULT); - parseGraphQLSchema.graphQLTypes.push(SIGN_UP_RESULT); - parseGraphQLSchema.graphQLTypes.push(ELEMENT); + parseGraphQLSchema.addGraphQLType(GraphQLUpload, true); + parseGraphQLSchema.addGraphQLType(ANY, true); + parseGraphQLSchema.addGraphQLType(OBJECT, true); + parseGraphQLSchema.addGraphQLType(DATE, true); + parseGraphQLSchema.addGraphQLType(BYTES, true); + parseGraphQLSchema.addGraphQLType(FILE, true); + parseGraphQLSchema.addGraphQLType(FILE_INFO, true); + parseGraphQLSchema.addGraphQLType(GEO_POINT_INPUT, true); + parseGraphQLSchema.addGraphQLType(GEO_POINT, true); + parseGraphQLSchema.addGraphQLType(RELATION_OP, true); + parseGraphQLSchema.addGraphQLType(CREATE_RESULT, true); + parseGraphQLSchema.addGraphQLType(UPDATE_RESULT, true); + parseGraphQLSchema.addGraphQLType(CLASS, true); + parseGraphQLSchema.addGraphQLType(READ_PREFERENCE, true); + parseGraphQLSchema.addGraphQLType(SUBQUERY_INPUT, true); + parseGraphQLSchema.addGraphQLType(SELECT_INPUT, true); + parseGraphQLSchema.addGraphQLType(SEARCH_INPUT, true); + parseGraphQLSchema.addGraphQLType(TEXT_INPUT, true); + parseGraphQLSchema.addGraphQLType(BOX_INPUT, true); + parseGraphQLSchema.addGraphQLType(WITHIN_INPUT, true); + parseGraphQLSchema.addGraphQLType(CENTER_SPHERE_INPUT, true); + parseGraphQLSchema.addGraphQLType(GEO_WITHIN_INPUT, true); + parseGraphQLSchema.addGraphQLType(GEO_INTERSECTS_INPUT, true); + parseGraphQLSchema.addGraphQLType(STRING_WHERE_INPUT, true); + parseGraphQLSchema.addGraphQLType(NUMBER_WHERE_INPUT, true); + parseGraphQLSchema.addGraphQLType(BOOLEAN_WHERE_INPUT, true); + parseGraphQLSchema.addGraphQLType(ARRAY_WHERE_INPUT, true); + parseGraphQLSchema.addGraphQLType(KEY_VALUE_INPUT, true); + parseGraphQLSchema.addGraphQLType(OBJECT_WHERE_INPUT, true); + parseGraphQLSchema.addGraphQLType(DATE_WHERE_INPUT, true); + parseGraphQLSchema.addGraphQLType(BYTES_WHERE_INPUT, true); + parseGraphQLSchema.addGraphQLType(FILE_WHERE_INPUT, true); + parseGraphQLSchema.addGraphQLType(GEO_POINT_WHERE_INPUT, true); + parseGraphQLSchema.addGraphQLType(POLYGON_WHERE_INPUT, true); + parseGraphQLSchema.addGraphQLType(FIND_RESULT, true); + parseGraphQLSchema.addGraphQLType(SIGN_UP_RESULT, true); + parseGraphQLSchema.addGraphQLType(ELEMENT, true); }; export { @@ -1128,10 +1129,10 @@ export { FILE, FILE_INFO, GEO_POINT_FIELDS, + GEO_POINT_INPUT, GEO_POINT, - GEO_POINT_INFO, + POLYGON_INPUT, POLYGON, - POLYGON_INFO, RELATION_OP, CLASS_NAME_ATT, FIELDS_ATT, @@ -1157,15 +1158,15 @@ export { SKIP_ATT, LIMIT_ATT, COUNT_ATT, - SUBQUERY, - SELECT_OPERATOR, - SEARCH_OPERATOR, - TEXT_OPERATOR, - BOX_OPERATOR, - WITHIN_OPERATOR, - CENTER_SPHERE_OPERATOR, - GEO_WITHIN_OPERATOR, - GEO_INTERSECTS, + SUBQUERY_INPUT, + SELECT_INPUT, + SEARCH_INPUT, + TEXT_INPUT, + BOX_INPUT, + WITHIN_INPUT, + CENTER_SPHERE_INPUT, + GEO_WITHIN_INPUT, + GEO_INTERSECTS_INPUT, _eq, _ne, _lt, @@ -1179,16 +1180,17 @@ export { _dontSelect, _regex, _options, - STRING_CONSTRAINT, - NUMBER_CONSTRAINT, - BOOLEAN_CONSTRAINT, - ARRAY_CONSTRAINT, - OBJECT_CONSTRAINT, - DATE_CONSTRAINT, - BYTES_CONSTRAINT, - FILE_CONSTRAINT, - GEO_POINT_CONSTRAINT, - POLYGON_CONSTRAINT, + STRING_WHERE_INPUT, + NUMBER_WHERE_INPUT, + BOOLEAN_WHERE_INPUT, + ARRAY_WHERE_INPUT, + KEY_VALUE_INPUT, + OBJECT_WHERE_INPUT, + DATE_WHERE_INPUT, + BYTES_WHERE_INPUT, + FILE_WHERE_INPUT, + GEO_POINT_WHERE_INPUT, + POLYGON_WHERE_INPUT, FIND_RESULT, SIGN_UP_RESULT, ARRAY_RESULT, diff --git a/src/GraphQL/loaders/filesMutations.js b/src/GraphQL/loaders/filesMutations.js index 68ad4e8628..6dafa39995 100644 --- a/src/GraphQL/loaders/filesMutations.js +++ b/src/GraphQL/loaders/filesMutations.js @@ -11,7 +11,7 @@ const load = parseGraphQLSchema => { description: 'The create mutation can be used to create and upload a new file.', args: { - file: { + upload: { description: 'This is the new file to be created and uploaded', type: new GraphQLNonNull(GraphQLUpload), }, @@ -19,10 +19,10 @@ const load = parseGraphQLSchema => { type: new GraphQLNonNull(defaultGraphQLTypes.FILE_INFO), async resolve(_source, args, context) { try { - const { file } = args; + const { upload } = args; const { config } = context; - const { createReadStream, filename, mimetype } = await file; + const { createReadStream, filename, mimetype } = await upload; let data = null; if (createReadStream) { const stream = createReadStream(); @@ -81,7 +81,7 @@ const load = parseGraphQLSchema => { description: 'FilesMutation is the top level type for files mutations.', fields, }); - parseGraphQLSchema.graphQLTypes.push(filesMutation); + parseGraphQLSchema.addGraphQLType(filesMutation, true, true); parseGraphQLSchema.graphQLMutations.files = { description: 'This is the top level for files mutations.', diff --git a/src/GraphQL/loaders/functionsMutations.js b/src/GraphQL/loaders/functionsMutations.js index 6a7b9a3de9..ffb6b8ee06 100644 --- a/src/GraphQL/loaders/functionsMutations.js +++ b/src/GraphQL/loaders/functionsMutations.js @@ -45,7 +45,7 @@ const load = parseGraphQLSchema => { 'FunctionsMutation is the top level type for functions mutations.', fields, }); - parseGraphQLSchema.graphQLTypes.push(functionsMutation); + parseGraphQLSchema.addGraphQLType(functionsMutation, true, true); parseGraphQLSchema.graphQLMutations.functions = { description: 'This is the top level for functions mutations.', diff --git a/src/GraphQL/loaders/objectsMutations.js b/src/GraphQL/loaders/objectsMutations.js index 7623b6eeca..2a8beff525 100644 --- a/src/GraphQL/loaders/objectsMutations.js +++ b/src/GraphQL/loaders/objectsMutations.js @@ -1,33 +1,14 @@ import { GraphQLNonNull, GraphQLBoolean, GraphQLObjectType } from 'graphql'; import * as defaultGraphQLTypes from './defaultGraphQLTypes'; import rest from '../../rest'; - -const parseMap = { - _op: '__op', -}; - -const transformToParse = fields => { - if (!fields || typeof fields !== 'object') { - return; - } - Object.keys(fields).forEach(fieldName => { - const fieldValue = fields[fieldName]; - if (parseMap[fieldName]) { - delete fields[fieldName]; - fields[parseMap[fieldName]] = fieldValue; - } - if (typeof fieldValue === 'object') { - transformToParse(fieldValue); - } - }); -}; +import { transformMutationInputToParse } from '../transformers/mutation'; const createObject = async (className, fields, config, auth, info) => { if (!fields) { fields = {}; } - transformToParse(fields); + transformMutationInputToParse(fields); return (await rest.create(config, auth, className, fields, info.clientSDK)) .response; @@ -45,7 +26,7 @@ const updateObject = async ( fields = {}; } - transformToParse(fields); + transformMutationInputToParse(fields); return (await rest.update( config, @@ -63,80 +44,95 @@ const deleteObject = async (className, objectId, config, auth, info) => { }; const load = parseGraphQLSchema => { - parseGraphQLSchema.graphQLObjectsMutations.create = { - description: - 'The create mutation can be used to create a new object of a certain class.', - args: { - className: defaultGraphQLTypes.CLASS_NAME_ATT, - fields: defaultGraphQLTypes.FIELDS_ATT, - }, - type: new GraphQLNonNull(defaultGraphQLTypes.CREATE_RESULT), - async resolve(_source, args, context) { - try { - const { className, fields } = args; - const { config, auth, info } = context; - - return await createObject(className, fields, config, auth, info); - } catch (e) { - parseGraphQLSchema.handleError(e); - } + parseGraphQLSchema.addGraphQLObjectMutation( + 'create', + { + description: + 'The create mutation can be used to create a new object of a certain class.', + args: { + className: defaultGraphQLTypes.CLASS_NAME_ATT, + fields: defaultGraphQLTypes.FIELDS_ATT, + }, + type: new GraphQLNonNull(defaultGraphQLTypes.CREATE_RESULT), + async resolve(_source, args, context) { + try { + const { className, fields } = args; + const { config, auth, info } = context; + + return await createObject(className, fields, config, auth, info); + } catch (e) { + parseGraphQLSchema.handleError(e); + } + }, }, - }; - - parseGraphQLSchema.graphQLObjectsMutations.update = { - description: - 'The update mutation can be used to update an object of a certain class.', - args: { - className: defaultGraphQLTypes.CLASS_NAME_ATT, - objectId: defaultGraphQLTypes.OBJECT_ID_ATT, - fields: defaultGraphQLTypes.FIELDS_ATT, + true, + true + ); + + parseGraphQLSchema.addGraphQLObjectMutation( + 'update', + { + description: + 'The update mutation can be used to update an object of a certain class.', + args: { + className: defaultGraphQLTypes.CLASS_NAME_ATT, + objectId: defaultGraphQLTypes.OBJECT_ID_ATT, + fields: defaultGraphQLTypes.FIELDS_ATT, + }, + type: new GraphQLNonNull(defaultGraphQLTypes.UPDATE_RESULT), + async resolve(_source, args, context) { + try { + const { className, objectId, fields } = args; + const { config, auth, info } = context; + + return await updateObject( + className, + objectId, + fields, + config, + auth, + info + ); + } catch (e) { + parseGraphQLSchema.handleError(e); + } + }, }, - type: new GraphQLNonNull(defaultGraphQLTypes.UPDATE_RESULT), - async resolve(_source, args, context) { - try { - const { className, objectId, fields } = args; - const { config, auth, info } = context; - - return await updateObject( - className, - objectId, - fields, - config, - auth, - info - ); - } catch (e) { - parseGraphQLSchema.handleError(e); - } + true, + true + ); + + parseGraphQLSchema.addGraphQLObjectMutation( + 'delete', + { + description: + 'The delete mutation can be used to delete an object of a certain class.', + args: { + className: defaultGraphQLTypes.CLASS_NAME_ATT, + objectId: defaultGraphQLTypes.OBJECT_ID_ATT, + }, + type: new GraphQLNonNull(GraphQLBoolean), + async resolve(_source, args, context) { + try { + const { className, objectId } = args; + const { config, auth, info } = context; + + return await deleteObject(className, objectId, config, auth, info); + } catch (e) { + parseGraphQLSchema.handleError(e); + } + }, }, - }; - - parseGraphQLSchema.graphQLObjectsMutations.delete = { - description: - 'The delete mutation can be used to delete an object of a certain class.', - args: { - className: defaultGraphQLTypes.CLASS_NAME_ATT, - objectId: defaultGraphQLTypes.OBJECT_ID_ATT, - }, - type: new GraphQLNonNull(GraphQLBoolean), - async resolve(_source, args, context) { - try { - const { className, objectId } = args; - const { config, auth, info } = context; - - return await deleteObject(className, objectId, config, auth, info); - } catch (e) { - parseGraphQLSchema.handleError(e); - } - }, - }; + true, + true + ); const objectsMutation = new GraphQLObjectType({ name: 'ObjectsMutation', description: 'ObjectsMutation is the top level type for objects mutations.', fields: parseGraphQLSchema.graphQLObjectsMutations, }); - parseGraphQLSchema.graphQLTypes.push(objectsMutation); + parseGraphQLSchema.addGraphQLType(objectsMutation, true, true); parseGraphQLSchema.graphQLMutations.objects = { description: 'This is the top level for objects mutations.', diff --git a/src/GraphQL/loaders/objectsQueries.js b/src/GraphQL/loaders/objectsQueries.js index f7430196ad..9b761c1c5a 100644 --- a/src/GraphQL/loaders/objectsQueries.js +++ b/src/GraphQL/loaders/objectsQueries.js @@ -8,6 +8,7 @@ import getFieldNames from 'graphql-list-fields'; import Parse from 'parse/node'; import * as defaultGraphQLTypes from './defaultGraphQLTypes'; import rest from '../../rest'; +import { transformQueryInputToParse } from '../transformers/query'; const getObject = async ( className, @@ -54,155 +55,6 @@ const getObject = async ( return response.results[0]; }; -const parseMap = { - _or: '$or', - _and: '$and', - _nor: '$nor', - _relatedTo: '$relatedTo', - _eq: '$eq', - _ne: '$ne', - _lt: '$lt', - _lte: '$lte', - _gt: '$gt', - _gte: '$gte', - _in: '$in', - _nin: '$nin', - _exists: '$exists', - _select: '$select', - _dontSelect: '$dontSelect', - _inQuery: '$inQuery', - _notInQuery: '$notInQuery', - _containedBy: '$containedBy', - _all: '$all', - _regex: '$regex', - _options: '$options', - _text: '$text', - _search: '$search', - _term: '$term', - _language: '$language', - _caseSensitive: '$caseSensitive', - _diacriticSensitive: '$diacriticSensitive', - _nearSphere: '$nearSphere', - _maxDistance: '$maxDistance', - _maxDistanceInRadians: '$maxDistanceInRadians', - _maxDistanceInMiles: '$maxDistanceInMiles', - _maxDistanceInKilometers: '$maxDistanceInKilometers', - _within: '$within', - _box: '$box', - _geoWithin: '$geoWithin', - _polygon: '$polygon', - _centerSphere: '$centerSphere', - _geoIntersects: '$geoIntersects', - _point: '$point', -}; - -const transformToParse = (constraints, parentFieldName, parentConstraints) => { - if (!constraints || typeof constraints !== 'object') { - return; - } - Object.keys(constraints).forEach(fieldName => { - let fieldValue = constraints[fieldName]; - - /** - * If we have a key-value pair, we need to change the way the constraint is structured. - * - * Example: - * From: - * { - * "someField": { - * "_lt": { - * "_key":"foo.bar", - * "_value": 100 - * }, - * "_gt": { - * "_key":"foo.bar", - * "_value": 10 - * } - * } - * } - * - * To: - * { - * "someField.foo.bar": { - * "$lt": 100, - * "$gt": 10 - * } - * } - */ - if ( - fieldValue._key && - fieldValue._value && - parentConstraints && - parentFieldName - ) { - delete parentConstraints[parentFieldName]; - parentConstraints[`${parentFieldName}.${fieldValue._key}`] = { - ...parentConstraints[`${parentFieldName}.${fieldValue._key}`], - [parseMap[fieldName]]: fieldValue._value, - }; - } else if (parseMap[fieldName]) { - delete constraints[fieldName]; - fieldName = parseMap[fieldName]; - constraints[fieldName] = fieldValue; - } - switch (fieldName) { - case '$point': - case '$nearSphere': - if (typeof fieldValue === 'object' && !fieldValue.__type) { - fieldValue.__type = 'GeoPoint'; - } - break; - case '$box': - if ( - typeof fieldValue === 'object' && - fieldValue.bottomLeft && - fieldValue.upperRight - ) { - fieldValue = [ - { - __type: 'GeoPoint', - ...fieldValue.bottomLeft, - }, - { - __type: 'GeoPoint', - ...fieldValue.upperRight, - }, - ]; - constraints[fieldName] = fieldValue; - } - break; - case '$polygon': - if (fieldValue instanceof Array) { - fieldValue.forEach(geoPoint => { - if (typeof geoPoint === 'object' && !geoPoint.__type) { - geoPoint.__type = 'GeoPoint'; - } - }); - } - break; - case '$centerSphere': - if ( - typeof fieldValue === 'object' && - fieldValue.center && - fieldValue.distance - ) { - fieldValue = [ - { - __type: 'GeoPoint', - ...fieldValue.center, - }, - fieldValue.distance, - ]; - constraints[fieldName] = fieldValue; - } - break; - } - if (typeof fieldValue === 'object') { - transformToParse(fieldValue, fieldName, constraints); - } - }); -}; - const findObjects = async ( className, where, @@ -224,7 +76,7 @@ const findObjects = async ( where = {}; } - transformToParse(where); + transformQueryInputToParse(where); const options = {}; @@ -282,118 +134,131 @@ const findObjects = async ( }; const load = parseGraphQLSchema => { - parseGraphQLSchema.graphQLObjectsQueries.get = { - description: - 'The get query can be used to get an object of a certain class by its objectId.', - args: { - className: defaultGraphQLTypes.CLASS_NAME_ATT, - objectId: defaultGraphQLTypes.OBJECT_ID_ATT, - keys: defaultGraphQLTypes.KEYS_ATT, - include: defaultGraphQLTypes.INCLUDE_ATT, - readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT, - includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT, - }, - type: new GraphQLNonNull(defaultGraphQLTypes.OBJECT), - async resolve(_source, args, context) { - try { - const { - className, - objectId, - keys, - include, - readPreference, - includeReadPreference, - } = args; - const { config, auth, info } = context; + parseGraphQLSchema.addGraphQLObjectQuery( + 'get', + { + description: + 'The get query can be used to get an object of a certain class by its objectId.', + args: { + className: defaultGraphQLTypes.CLASS_NAME_ATT, + objectId: defaultGraphQLTypes.OBJECT_ID_ATT, + keys: defaultGraphQLTypes.KEYS_ATT, + include: defaultGraphQLTypes.INCLUDE_ATT, + readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT, + includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT, + }, + type: new GraphQLNonNull(defaultGraphQLTypes.OBJECT), + async resolve(_source, args, context) { + try { + const { + className, + objectId, + keys, + include, + readPreference, + includeReadPreference, + } = args; - return await getObject( - className, - objectId, - keys, - include, - readPreference, - includeReadPreference, - config, - auth, - info - ); - } catch (e) { - parseGraphQLSchema.handleError(e); - } - }, - }; + const { config, auth, info } = context; - parseGraphQLSchema.graphQLObjectsQueries.find = { - description: - 'The find query can be used to find objects of a certain class.', - args: { - className: defaultGraphQLTypes.CLASS_NAME_ATT, - where: defaultGraphQLTypes.WHERE_ATT, - order: { - description: - 'This is the order in which the objects should be returned', - type: GraphQLString, - }, - skip: defaultGraphQLTypes.SKIP_ATT, - limit: defaultGraphQLTypes.LIMIT_ATT, - keys: defaultGraphQLTypes.KEYS_ATT, - include: defaultGraphQLTypes.INCLUDE_ATT, - includeAll: { - description: 'All pointers will be returned', - type: GraphQLBoolean, + return await getObject( + className, + objectId, + keys, + include, + readPreference, + includeReadPreference, + config, + auth, + info + ); + } catch (e) { + parseGraphQLSchema.handleError(e); + } }, - readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT, - includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT, - subqueryReadPreference: defaultGraphQLTypes.SUBQUERY_READ_PREFERENCE_ATT, }, - type: new GraphQLNonNull(defaultGraphQLTypes.FIND_RESULT), - async resolve(_source, args, context, queryInfo) { - try { - const { - className, - where, - order, - skip, - limit, - keys, - include, - includeAll, - readPreference, - includeReadPreference, - subqueryReadPreference, - } = args; - const { config, auth, info } = context; - const selectedFields = getFieldNames(queryInfo); + true, + true + ); - return await findObjects( - className, - where, - order, - skip, - limit, - keys, - include, - includeAll, - readPreference, - includeReadPreference, - subqueryReadPreference, - config, - auth, - info, - selectedFields - ); - } catch (e) { - parseGraphQLSchema.handleError(e); - } + parseGraphQLSchema.addGraphQLObjectQuery( + 'find', + { + description: + 'The find query can be used to find objects of a certain class.', + args: { + className: defaultGraphQLTypes.CLASS_NAME_ATT, + where: defaultGraphQLTypes.WHERE_ATT, + order: { + description: + 'This is the order in which the objects should be returned', + type: GraphQLString, + }, + skip: defaultGraphQLTypes.SKIP_ATT, + limit: defaultGraphQLTypes.LIMIT_ATT, + keys: defaultGraphQLTypes.KEYS_ATT, + include: defaultGraphQLTypes.INCLUDE_ATT, + includeAll: { + description: 'All pointers will be returned', + type: GraphQLBoolean, + }, + readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT, + includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT, + subqueryReadPreference: + defaultGraphQLTypes.SUBQUERY_READ_PREFERENCE_ATT, + }, + type: new GraphQLNonNull(defaultGraphQLTypes.FIND_RESULT), + async resolve(_source, args, context, queryInfo) { + try { + const { + className, + where, + order, + skip, + limit, + keys, + include, + includeAll, + readPreference, + includeReadPreference, + subqueryReadPreference, + } = args; + + const { config, auth, info } = context; + const selectedFields = getFieldNames(queryInfo); + + return await findObjects( + className, + where, + order, + skip, + limit, + keys, + include, + includeAll, + readPreference, + includeReadPreference, + subqueryReadPreference, + config, + auth, + info, + selectedFields + ); + } catch (e) { + parseGraphQLSchema.handleError(e); + } + }, }, - }; + true, + true + ); const objectsQuery = new GraphQLObjectType({ name: 'ObjectsQuery', description: 'ObjectsQuery is the top level type for objects queries.', fields: parseGraphQLSchema.graphQLObjectsQueries, }); - parseGraphQLSchema.graphQLTypes.push(objectsQuery); + parseGraphQLSchema.addGraphQLType(objectsQuery, true, true); parseGraphQLSchema.graphQLQueries.objects = { description: 'This is the top level for objects queries.', diff --git a/src/GraphQL/loaders/parseClassMutations.js b/src/GraphQL/loaders/parseClassMutations.js index d493df2d5f..f6842db164 100644 --- a/src/GraphQL/loaders/parseClassMutations.js +++ b/src/GraphQL/loaders/parseClassMutations.js @@ -5,6 +5,7 @@ import { extractKeysAndInclude } from '../parseGraphQLUtils'; import * as objectsMutations from './objectsMutations'; import * as objectsQueries from './objectsQueries'; import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController'; +import { transformClassNameToGraphQL } from '../transformers/className'; const getParseClassMutationConfig = function( parseClassConfig: ?ParseGraphQLClassConfig @@ -39,7 +40,9 @@ const load = function( parseClass, parseClassConfig: ?ParseGraphQLClassConfig ) { - const { className } = parseClass; + const className = parseClass.className; + const graphQLClassName = transformClassNameToGraphQL(className); + const { create: isCreateEnabled = true, update: isUpdateEnabled = true, @@ -52,37 +55,29 @@ const load = function( classGraphQLOutputType, } = parseGraphQLSchema.parseClassTypes[className]; - const createFields = { - description: 'These are the fields used to create the object.', - type: classGraphQLCreateType, - }; - const updateFields = { - description: 'These are the fields used to update the object.', - type: classGraphQLUpdateType, - }; - - const classGraphQLCreateTypeFields = isCreateEnabled - ? classGraphQLCreateType.getFields() - : null; - const classGraphQLUpdateTypeFields = isUpdateEnabled - ? classGraphQLUpdateType.getFields() - : null; - const transformTypes = (inputType: 'create' | 'update', fields) => { if (fields) { + const classGraphQLCreateTypeFields = + isCreateEnabled && classGraphQLCreateType + ? classGraphQLCreateType.getFields() + : null; + const classGraphQLUpdateTypeFields = + isUpdateEnabled && classGraphQLUpdateType + ? classGraphQLUpdateType.getFields() + : null; Object.keys(fields).forEach(field => { let inputTypeField; - if (inputType === 'create') { + if (inputType === 'create' && classGraphQLCreateTypeFields) { inputTypeField = classGraphQLCreateTypeFields[field]; - } else { + } else if (classGraphQLUpdateTypeFields) { inputTypeField = classGraphQLUpdateTypeFields[field]; } if (inputTypeField) { switch (inputTypeField.type) { - case defaultGraphQLTypes.GEO_POINT: + case defaultGraphQLTypes.GEO_POINT_INPUT: fields[field].__type = 'GeoPoint'; break; - case defaultGraphQLTypes.POLYGON: + case defaultGraphQLTypes.POLYGON_INPUT: fields[field] = { __type: 'Polygon', coordinates: fields[field].map(geoPoint => [ @@ -98,13 +93,18 @@ const load = function( }; if (isCreateEnabled) { - const createGraphQLMutationName = `create${className}`; - parseGraphQLSchema.graphQLObjectsMutations[createGraphQLMutationName] = { - description: `The ${createGraphQLMutationName} mutation can be used to create a new object of the ${className} class.`, + const createGraphQLMutationName = `create${graphQLClassName}`; + parseGraphQLSchema.addGraphQLObjectMutation(createGraphQLMutationName, { + description: `The ${createGraphQLMutationName} mutation can be used to create a new object of the ${graphQLClassName} class.`, args: { - fields: createFields, + fields: { + description: 'These are the fields used to create the object.', + type: classGraphQLCreateType || defaultGraphQLTypes.OBJECT, + }, }, - type: new GraphQLNonNull(classGraphQLOutputType), + type: new GraphQLNonNull( + classGraphQLOutputType || defaultGraphQLTypes.OBJECT + ), async resolve(_source, args, context, mutationInfo) { try { let { fields } = args; @@ -150,18 +150,23 @@ const load = function( parseGraphQLSchema.handleError(e); } }, - }; + }); } if (isUpdateEnabled) { - const updateGraphQLMutationName = `update${className}`; - parseGraphQLSchema.graphQLObjectsMutations[updateGraphQLMutationName] = { - description: `The ${updateGraphQLMutationName} mutation can be used to update an object of the ${className} class.`, + const updateGraphQLMutationName = `update${graphQLClassName}`; + parseGraphQLSchema.addGraphQLObjectMutation(updateGraphQLMutationName, { + description: `The ${updateGraphQLMutationName} mutation can be used to update an object of the ${graphQLClassName} class.`, args: { objectId: defaultGraphQLTypes.OBJECT_ID_ATT, - fields: updateFields, + fields: { + description: 'These are the fields used to update the object.', + type: classGraphQLUpdateType || defaultGraphQLTypes.OBJECT, + }, }, - type: new GraphQLNonNull(classGraphQLOutputType), + type: new GraphQLNonNull( + classGraphQLOutputType || defaultGraphQLTypes.OBJECT + ), async resolve(_source, args, context, mutationInfo) { try { const { objectId, fields } = args; @@ -205,17 +210,19 @@ const load = function( parseGraphQLSchema.handleError(e); } }, - }; + }); } if (isDestroyEnabled) { - const deleteGraphQLMutationName = `delete${className}`; - parseGraphQLSchema.graphQLObjectsMutations[deleteGraphQLMutationName] = { - description: `The ${deleteGraphQLMutationName} mutation can be used to delete an object of the ${className} class.`, + const deleteGraphQLMutationName = `delete${graphQLClassName}`; + parseGraphQLSchema.addGraphQLObjectMutation(deleteGraphQLMutationName, { + description: `The ${deleteGraphQLMutationName} mutation can be used to delete an object of the ${graphQLClassName} class.`, args: { objectId: defaultGraphQLTypes.OBJECT_ID_ATT, }, - type: new GraphQLNonNull(classGraphQLOutputType), + type: new GraphQLNonNull( + classGraphQLOutputType || defaultGraphQLTypes.OBJECT + ), async resolve(_source, args, context, mutationInfo) { try { const { objectId } = args; @@ -250,7 +257,7 @@ const load = function( parseGraphQLSchema.handleError(e); } }, - }; + }); } }; diff --git a/src/GraphQL/loaders/parseClassQueries.js b/src/GraphQL/loaders/parseClassQueries.js index 5d0bc02015..12a3dfaf9e 100644 --- a/src/GraphQL/loaders/parseClassQueries.js +++ b/src/GraphQL/loaders/parseClassQueries.js @@ -1,8 +1,10 @@ import { GraphQLNonNull } from 'graphql'; import getFieldNames from 'graphql-list-fields'; +import pluralize from 'pluralize'; import * as defaultGraphQLTypes from './defaultGraphQLTypes'; import * as objectsQueries from './objectsQueries'; import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController'; +import { transformClassNameToGraphQL } from '../transformers/className'; import { extractKeysAndInclude } from '../parseGraphQLUtils'; const getParseClassQueryConfig = function( @@ -36,7 +38,8 @@ const load = function( parseClass, parseClassConfig: ?ParseGraphQLClassConfig ) { - const { className } = parseClass; + const className = parseClass.className; + const graphQLClassName = transformClassNameToGraphQL(className); const { get: isGetEnabled = true, find: isFindEnabled = true, @@ -49,15 +52,18 @@ const load = function( } = parseGraphQLSchema.parseClassTypes[className]; if (isGetEnabled) { - const getGraphQLQueryName = `get${className}`; - parseGraphQLSchema.graphQLObjectsQueries[getGraphQLQueryName] = { - description: `The ${getGraphQLQueryName} query can be used to get an object of the ${className} class by its id.`, + const getGraphQLQueryName = + graphQLClassName.charAt(0).toLowerCase() + graphQLClassName.slice(1); + parseGraphQLSchema.addGraphQLObjectQuery(getGraphQLQueryName, { + description: `The ${getGraphQLQueryName} query can be used to get an object of the ${graphQLClassName} class by its id.`, args: { objectId: defaultGraphQLTypes.OBJECT_ID_ATT, readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT, includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT, }, - type: new GraphQLNonNull(classGraphQLOutputType), + type: new GraphQLNonNull( + classGraphQLOutputType || defaultGraphQLTypes.OBJECT + ), async resolve(_source, args, context, queryInfo) { try { return await getQuery(className, _source, args, context, queryInfo); @@ -65,15 +71,19 @@ const load = function( parseGraphQLSchema.handleError(e); } }, - }; + }); } if (isFindEnabled) { - const findGraphQLQueryName = `find${className}`; - parseGraphQLSchema.graphQLObjectsQueries[findGraphQLQueryName] = { - description: `The ${findGraphQLQueryName} query can be used to find objects of the ${className} class.`, + const findGraphQLQueryName = pluralize( + graphQLClassName.charAt(0).toLowerCase() + graphQLClassName.slice(1) + ); + parseGraphQLSchema.addGraphQLObjectQuery(findGraphQLQueryName, { + description: `The ${findGraphQLQueryName} query can be used to find objects of the ${graphQLClassName} class.`, args: classGraphQLFindArgs, - type: new GraphQLNonNull(classGraphQLFindResultType), + type: new GraphQLNonNull( + classGraphQLFindResultType || defaultGraphQLTypes.FIND_RESULT + ), async resolve(_source, args, context, queryInfo) { try { const { @@ -116,7 +126,7 @@ const load = function( parseGraphQLSchema.handleError(e); } }, - }; + }); } }; diff --git a/src/GraphQL/loaders/parseClassTypes.js b/src/GraphQL/loaders/parseClassTypes.js index 8c23bf1222..0bbd184822 100644 --- a/src/GraphQL/loaders/parseClassTypes.js +++ b/src/GraphQL/loaders/parseClassTypes.js @@ -14,6 +14,7 @@ import getFieldNames from 'graphql-list-fields'; import * as defaultGraphQLTypes from './defaultGraphQLTypes'; import * as objectsQueries from './objectsQueries'; import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController'; +import { transformClassNameToGraphQL } from '../transformers/className'; import { extractKeysAndInclude } from '../parseGraphQLUtils'; const mapInputType = (parseType, targetClass, parseClassTypes) => { @@ -31,13 +32,19 @@ const mapInputType = (parseType, targetClass, parseClassTypes) => { case 'Date': return defaultGraphQLTypes.DATE; case 'Pointer': - if (parseClassTypes[targetClass]) { + if ( + parseClassTypes[targetClass] && + parseClassTypes[targetClass].classGraphQLScalarType + ) { return parseClassTypes[targetClass].classGraphQLScalarType; } else { return defaultGraphQLTypes.OBJECT; } case 'Relation': - if (parseClassTypes[targetClass]) { + if ( + parseClassTypes[targetClass] && + parseClassTypes[targetClass].classGraphQLRelationOpType + ) { return parseClassTypes[targetClass].classGraphQLRelationOpType; } else { return defaultGraphQLTypes.OBJECT; @@ -45,9 +52,9 @@ const mapInputType = (parseType, targetClass, parseClassTypes) => { case 'File': return defaultGraphQLTypes.FILE; case 'GeoPoint': - return defaultGraphQLTypes.GEO_POINT; + return defaultGraphQLTypes.GEO_POINT_INPUT; case 'Polygon': - return defaultGraphQLTypes.POLYGON; + return defaultGraphQLTypes.POLYGON_INPUT; case 'Bytes': return defaultGraphQLTypes.BYTES; case 'ACL': @@ -72,13 +79,19 @@ const mapOutputType = (parseType, targetClass, parseClassTypes) => { case 'Date': return defaultGraphQLTypes.DATE; case 'Pointer': - if (parseClassTypes[targetClass]) { + if ( + parseClassTypes[targetClass] && + parseClassTypes[targetClass].classGraphQLOutputType + ) { return parseClassTypes[targetClass].classGraphQLOutputType; } else { return defaultGraphQLTypes.OBJECT; } case 'Relation': - if (parseClassTypes[targetClass]) { + if ( + parseClassTypes[targetClass] && + parseClassTypes[targetClass].classGraphQLFindResultType + ) { return new GraphQLNonNull( parseClassTypes[targetClass].classGraphQLFindResultType ); @@ -88,9 +101,9 @@ const mapOutputType = (parseType, targetClass, parseClassTypes) => { case 'File': return defaultGraphQLTypes.FILE_INFO; case 'GeoPoint': - return defaultGraphQLTypes.GEO_POINT_INFO; + return defaultGraphQLTypes.GEO_POINT; case 'Polygon': - return defaultGraphQLTypes.POLYGON_INFO; + return defaultGraphQLTypes.POLYGON; case 'Bytes': return defaultGraphQLTypes.BYTES; case 'ACL': @@ -103,33 +116,36 @@ const mapOutputType = (parseType, targetClass, parseClassTypes) => { const mapConstraintType = (parseType, targetClass, parseClassTypes) => { switch (parseType) { case 'String': - return defaultGraphQLTypes.STRING_CONSTRAINT; + return defaultGraphQLTypes.STRING_WHERE_INPUT; case 'Number': - return defaultGraphQLTypes.NUMBER_CONSTRAINT; + return defaultGraphQLTypes.NUMBER_WHERE_INPUT; case 'Boolean': - return defaultGraphQLTypes.BOOLEAN_CONSTRAINT; + return defaultGraphQLTypes.BOOLEAN_WHERE_INPUT; case 'Array': - return defaultGraphQLTypes.ARRAY_CONSTRAINT; + return defaultGraphQLTypes.ARRAY_WHERE_INPUT; case 'Object': - return defaultGraphQLTypes.OBJECT_CONSTRAINT; + return defaultGraphQLTypes.OBJECT_WHERE_INPUT; case 'Date': - return defaultGraphQLTypes.DATE_CONSTRAINT; + return defaultGraphQLTypes.DATE_WHERE_INPUT; case 'Pointer': - if (parseClassTypes[targetClass]) { + if ( + parseClassTypes[targetClass] && + parseClassTypes[targetClass].classGraphQLConstraintType + ) { return parseClassTypes[targetClass].classGraphQLConstraintType; } else { return defaultGraphQLTypes.OBJECT; } case 'File': - return defaultGraphQLTypes.FILE_CONSTRAINT; + return defaultGraphQLTypes.FILE_WHERE_INPUT; case 'GeoPoint': - return defaultGraphQLTypes.GEO_POINT_CONSTRAINT; + return defaultGraphQLTypes.GEO_POINT_WHERE_INPUT; case 'Polygon': - return defaultGraphQLTypes.POLYGON_CONSTRAINT; + return defaultGraphQLTypes.POLYGON_WHERE_INPUT; case 'Bytes': - return defaultGraphQLTypes.BYTES_CONSTRAINT; + return defaultGraphQLTypes.BYTES_WHERE_INPUT; case 'ACL': - return defaultGraphQLTypes.OBJECT_CONSTRAINT; + return defaultGraphQLTypes.OBJECT_WHERE_INPUT; case 'Relation': default: return undefined; @@ -233,7 +249,8 @@ const load = ( parseClass, parseClassConfig: ?ParseGraphQLClassConfig ) => { - const { className } = parseClass; + const className = parseClass.className; + const graphQLClassName = transformClassNameToGraphQL(className); const { classCreateFields, classUpdateFields, @@ -242,12 +259,12 @@ const load = ( classSortFields, } = getInputFieldsAndConstraints(parseClass, parseClassConfig); - const classGraphQLScalarTypeName = `${className}Pointer`; + const classGraphQLScalarTypeName = `${graphQLClassName}Pointer`; const parseScalarValue = value => { if (typeof value === 'string') { return { __type: 'Pointer', - className, + className: className, objectId: value, }; } else if ( @@ -256,7 +273,7 @@ const load = ( value.className === className && typeof value.objectId === 'string' ) { - return value; + return { ...value, className }; } throw new defaultGraphQLTypes.TypeValidationError( @@ -264,9 +281,9 @@ const load = ( classGraphQLScalarTypeName ); }; - const classGraphQLScalarType = new GraphQLScalarType({ + let classGraphQLScalarType = new GraphQLScalarType({ name: classGraphQLScalarTypeName, - description: `The ${classGraphQLScalarTypeName} is used in operations that involve ${className} pointers.`, + description: `The ${classGraphQLScalarTypeName} is used in operations that involve ${graphQLClassName} pointers.`, parseValue: parseScalarValue, serialize(value) { if (typeof value === 'string') { @@ -318,12 +335,14 @@ const load = ( ); }, }); - parseGraphQLSchema.graphQLTypes.push(classGraphQLScalarType); + classGraphQLScalarType = + parseGraphQLSchema.addGraphQLType(classGraphQLScalarType) || + defaultGraphQLTypes.OBJECT; - const classGraphQLRelationOpTypeName = `${className}RelationOp`; - const classGraphQLRelationOpType = new GraphQLInputObjectType({ + const classGraphQLRelationOpTypeName = `${graphQLClassName}RelationOpInput`; + let classGraphQLRelationOpType = new GraphQLInputObjectType({ name: classGraphQLRelationOpTypeName, - description: `The ${classGraphQLRelationOpTypeName} input type is used in operations that involve relations with the ${className} class.`, + description: `The ${classGraphQLRelationOpTypeName} type is used in operations that involve relations with the ${graphQLClassName} class.`, fields: () => ({ _op: { description: 'This is the operation to be executed.', @@ -341,12 +360,14 @@ const load = ( }, }), }); - parseGraphQLSchema.graphQLTypes.push(classGraphQLRelationOpType); + classGraphQLRelationOpType = + parseGraphQLSchema.addGraphQLType(classGraphQLRelationOpType) || + defaultGraphQLTypes.OBJECT; - const classGraphQLCreateTypeName = `${className}CreateFields`; - const classGraphQLCreateType = new GraphQLInputObjectType({ + const classGraphQLCreateTypeName = `Create${graphQLClassName}FieldsInput`; + let classGraphQLCreateType = new GraphQLInputObjectType({ name: classGraphQLCreateTypeName, - description: `The ${classGraphQLCreateTypeName} input type is used in operations that involve creation of objects in the ${className} class.`, + description: `The ${classGraphQLCreateTypeName} input type is used in operations that involve creation of objects in the ${graphQLClassName} class.`, fields: () => classCreateFields.reduce( (fields, field) => { @@ -372,12 +393,14 @@ const load = ( } ), }); - parseGraphQLSchema.graphQLTypes.push(classGraphQLCreateType); + classGraphQLCreateType = parseGraphQLSchema.addGraphQLType( + classGraphQLCreateType + ); - const classGraphQLUpdateTypeName = `${className}UpdateFields`; - const classGraphQLUpdateType = new GraphQLInputObjectType({ + const classGraphQLUpdateTypeName = `Update${graphQLClassName}FieldsInput`; + let classGraphQLUpdateType = new GraphQLInputObjectType({ name: classGraphQLUpdateTypeName, - description: `The ${classGraphQLUpdateTypeName} input type is used in operations that involve creation of objects in the ${className} class.`, + description: `The ${classGraphQLUpdateTypeName} input type is used in operations that involve creation of objects in the ${graphQLClassName} class.`, fields: () => classUpdateFields.reduce( (fields, field) => { @@ -403,12 +426,14 @@ const load = ( } ), }); - parseGraphQLSchema.graphQLTypes.push(classGraphQLUpdateType); + classGraphQLUpdateType = parseGraphQLSchema.addGraphQLType( + classGraphQLUpdateType + ); - const classGraphQLConstraintTypeName = `${className}PointerConstraint`; - const classGraphQLConstraintType = new GraphQLInputObjectType({ + const classGraphQLConstraintTypeName = `${graphQLClassName}PointerWhereInput`; + let classGraphQLConstraintType = new GraphQLInputObjectType({ name: classGraphQLConstraintTypeName, - description: `The ${classGraphQLConstraintTypeName} input type is used in operations that involve filtering objects by a pointer field to ${className} class.`, + description: `The ${classGraphQLConstraintTypeName} input type is used in operations that involve filtering objects by a pointer field to ${graphQLClassName} class.`, fields: { _eq: defaultGraphQLTypes._eq(classGraphQLScalarType), _ne: defaultGraphQLTypes._ne(classGraphQLScalarType), @@ -420,21 +445,23 @@ const load = ( _inQuery: { description: 'This is the $inQuery operator to specify a constraint to select the objects where a field equals to any of the ids in the result of a different query.', - type: defaultGraphQLTypes.SUBQUERY, + type: defaultGraphQLTypes.SUBQUERY_INPUT, }, _notInQuery: { description: 'This is the $notInQuery operator to specify a constraint to select the objects where a field do not equal to any of the ids in the result of a different query.', - type: defaultGraphQLTypes.SUBQUERY, + type: defaultGraphQLTypes.SUBQUERY_INPUT, }, }, }); - parseGraphQLSchema.graphQLTypes.push(classGraphQLConstraintType); + classGraphQLConstraintType = parseGraphQLSchema.addGraphQLType( + classGraphQLConstraintType + ); - const classGraphQLConstraintsTypeName = `${className}Constraints`; - const classGraphQLConstraintsType = new GraphQLInputObjectType({ + const classGraphQLConstraintsTypeName = `${graphQLClassName}WhereInput`; + let classGraphQLConstraintsType = new GraphQLInputObjectType({ name: classGraphQLConstraintsTypeName, - description: `The ${classGraphQLConstraintsTypeName} input type is used in operations that involve filtering objects of ${className} class.`, + description: `The ${classGraphQLConstraintsTypeName} input type is used in operations that involve filtering objects of ${graphQLClassName} class.`, fields: () => ({ ...classConstraintFields.reduce((fields, field) => { const type = mapConstraintType( @@ -468,12 +495,14 @@ const load = ( }, }), }); - parseGraphQLSchema.graphQLTypes.push(classGraphQLConstraintsType); + classGraphQLConstraintsType = + parseGraphQLSchema.addGraphQLType(classGraphQLConstraintsType) || + defaultGraphQLTypes.OBJECT; - const classGraphQLOrderTypeName = `${className}Order`; - const classGraphQLOrderType = new GraphQLEnumType({ + const classGraphQLOrderTypeName = `${graphQLClassName}Order`; + let classGraphQLOrderType = new GraphQLEnumType({ name: classGraphQLOrderTypeName, - description: `The ${classGraphQLOrderTypeName} input type is used when sorting objects of the ${className} class.`, + description: `The ${classGraphQLOrderTypeName} input type is used when sorting objects of the ${graphQLClassName} class.`, values: classSortFields.reduce((sortFields, fieldConfig) => { const { field, asc, desc } = fieldConfig; const updatedSortFields = { @@ -488,7 +517,9 @@ const load = ( return updatedSortFields; }, {}), }); - parseGraphQLSchema.graphQLTypes.push(classGraphQLOrderType); + classGraphQLOrderType = parseGraphQLSchema.addGraphQLType( + classGraphQLOrderType + ); const classGraphQLFindArgs = { where: { @@ -498,7 +529,9 @@ const load = ( }, order: { description: 'The fields to be used when sorting the data fetched.', - type: new GraphQLList(new GraphQLNonNull(classGraphQLOrderType)), + type: classGraphQLOrderType + ? new GraphQLList(new GraphQLNonNull(classGraphQLOrderType)) + : GraphQLString, }, skip: defaultGraphQLTypes.SKIP_ATT, limit: defaultGraphQLTypes.LIMIT_ATT, @@ -507,7 +540,7 @@ const load = ( subqueryReadPreference: defaultGraphQLTypes.SUBQUERY_READ_PREFERENCE_ATT, }; - const classGraphQLOutputTypeName = `${className}Class`; + const classGraphQLOutputTypeName = `${graphQLClassName}`; const outputFields = () => { return classOutputFields.reduce((fields, field) => { const type = mapOutputType( @@ -548,14 +581,13 @@ const load = ( .filter(field => field.includes('.')) .map(field => field.slice(field.indexOf('.') + 1)) ); - return await objectsQueries.findObjects( source[field].className, { _relatedTo: { object: { __type: 'Pointer', - className, + className: className, objectId: source.objectId, }, key: field, @@ -634,29 +666,37 @@ const load = ( } }, defaultGraphQLTypes.CLASS_FIELDS); }; - const classGraphQLOutputType = new GraphQLObjectType({ + let classGraphQLOutputType = new GraphQLObjectType({ name: classGraphQLOutputTypeName, - description: `The ${classGraphQLOutputTypeName} object type is used in operations that involve outputting objects of ${className} class.`, + description: `The ${classGraphQLOutputTypeName} object type is used in operations that involve outputting objects of ${graphQLClassName} class.`, interfaces: [defaultGraphQLTypes.CLASS], fields: outputFields, }); - parseGraphQLSchema.graphQLTypes.push(classGraphQLOutputType); + classGraphQLOutputType = parseGraphQLSchema.addGraphQLType( + classGraphQLOutputType + ); - const classGraphQLFindResultTypeName = `${className}FindResult`; - const classGraphQLFindResultType = new GraphQLObjectType({ + const classGraphQLFindResultTypeName = `${graphQLClassName}FindResult`; + let classGraphQLFindResultType = new GraphQLObjectType({ name: classGraphQLFindResultTypeName, - description: `The ${classGraphQLFindResultTypeName} object type is used in the ${className} find query to return the data of the matched objects.`, + description: `The ${classGraphQLFindResultTypeName} object type is used in the ${graphQLClassName} find query to return the data of the matched objects.`, fields: { results: { description: 'This is the objects returned by the query', type: new GraphQLNonNull( - new GraphQLList(new GraphQLNonNull(classGraphQLOutputType)) + new GraphQLList( + new GraphQLNonNull( + classGraphQLOutputType || defaultGraphQLTypes.OBJECT + ) + ) ), }, count: defaultGraphQLTypes.COUNT_ATT, }, }); - parseGraphQLSchema.graphQLTypes.push(classGraphQLFindResultType); + classGraphQLFindResultType = parseGraphQLSchema.addGraphQLType( + classGraphQLFindResultType + ); parseGraphQLSchema.parseClassTypes[className] = { classGraphQLScalarType, @@ -671,22 +711,22 @@ const load = ( }; if (className === '_User') { - const meType = new GraphQLObjectType({ - name: 'Me', - description: `The Me object type is used in operations that involve outputting the current user data.`, + const viewerType = new GraphQLObjectType({ + name: 'Viewer', + description: `The Viewer object type is used in operations that involve outputting the current user data.`, interfaces: [defaultGraphQLTypes.CLASS], fields: () => ({ ...outputFields(), sessionToken: defaultGraphQLTypes.SESSION_TOKEN_ATT, }), }); - parseGraphQLSchema.meType = meType; - parseGraphQLSchema.graphQLTypes.push(meType); + parseGraphQLSchema.viewerType = viewerType; + parseGraphQLSchema.addGraphQLType(viewerType, true, true); - const userSignUpInputTypeName = '_UserSignUpFields'; + const userSignUpInputTypeName = 'SignUpFieldsInput'; const userSignUpInputType = new GraphQLInputObjectType({ name: userSignUpInputTypeName, - description: `The ${userSignUpInputTypeName} input type is used in operations that involve inputting objects of ${className} class when signing up.`, + description: `The ${userSignUpInputTypeName} input type is used in operations that involve inputting objects of ${graphQLClassName} class when signing up.`, fields: () => classCreateFields.reduce((fields, field) => { const type = mapInputType( @@ -710,8 +750,9 @@ const load = ( } }, {}), }); + parseGraphQLSchema.addGraphQLType(userSignUpInputType, true, true); - const userLogInInputTypeName = '_UserLoginFields'; + const userLogInInputTypeName = 'LogInFieldsInput'; const userLogInInputType = new GraphQLInputObjectType({ name: userLogInInputTypeName, description: `The ${userLogInInputTypeName} input type is used to login.`, @@ -726,13 +767,14 @@ const load = ( }, }, }); + parseGraphQLSchema.addGraphQLType(userLogInInputType, true, true); + parseGraphQLSchema.parseClassTypes[ - '_User' + className ].signUpInputType = userSignUpInputType; parseGraphQLSchema.parseClassTypes[ - '_User' + className ].logInInputType = userLogInInputType; - parseGraphQLSchema.graphQLTypes.push(userSignUpInputType); } }; diff --git a/src/GraphQL/loaders/usersMutations.js b/src/GraphQL/loaders/usersMutations.js index 57349b2a11..40579514dc 100644 --- a/src/GraphQL/loaders/usersMutations.js +++ b/src/GraphQL/loaders/usersMutations.js @@ -1,4 +1,4 @@ -import { GraphQLBoolean, GraphQLNonNull, GraphQLObjectType } from 'graphql'; +import { GraphQLNonNull, GraphQLObjectType } from 'graphql'; import UsersRouter from '../../Routers/UsersRouter'; import * as objectsMutations from './objectsMutations'; import { getUserFromSessionToken } from './usersQueries'; @@ -19,10 +19,11 @@ const load = parseGraphQLSchema => { type: parseGraphQLSchema.parseClassTypes['_User'].signUpInputType, }, }, - type: new GraphQLNonNull(parseGraphQLSchema.meType), + type: new GraphQLNonNull(parseGraphQLSchema.viewerType), async resolve(_source, args, context, mutationInfo) { try { const { fields } = args; + const { config, auth, info } = context; const { sessionToken } = await objectsMutations.createObject( @@ -45,16 +46,16 @@ const load = parseGraphQLSchema => { fields.logIn = { description: 'The logIn mutation can be used to log the user in.', args: { - input: { + fields: { description: 'This is data needed to login', type: parseGraphQLSchema.parseClassTypes['_User'].logInInputType, }, }, - type: new GraphQLNonNull(parseGraphQLSchema.meType), + type: new GraphQLNonNull(parseGraphQLSchema.viewerType), async resolve(_source, args, context) { try { const { - input: { username, password }, + fields: { username, password }, } = args; const { config, auth, info } = context; @@ -76,17 +77,24 @@ const load = parseGraphQLSchema => { fields.logOut = { description: 'The logOut mutation can be used to log the user out.', - type: new GraphQLNonNull(GraphQLBoolean), - async resolve(_source, _args, context) { + type: new GraphQLNonNull(parseGraphQLSchema.viewerType), + async resolve(_source, _args, context, mutationInfo) { try { const { config, auth, info } = context; + const viewer = await getUserFromSessionToken( + config, + info, + mutationInfo + ); + await usersRouter.handleLogOut({ config, auth, info, }); - return true; + + return viewer; } catch (e) { parseGraphQLSchema.handleError(e); } @@ -98,7 +106,7 @@ const load = parseGraphQLSchema => { description: 'UsersMutation is the top level type for files mutations.', fields, }); - parseGraphQLSchema.graphQLTypes.push(usersMutation); + parseGraphQLSchema.addGraphQLType(usersMutation, true, true); parseGraphQLSchema.graphQLMutations.users = { description: 'This is the top level for users mutations.', diff --git a/src/GraphQL/loaders/usersQueries.js b/src/GraphQL/loaders/usersQueries.js index a761035cca..bc50fee4a9 100644 --- a/src/GraphQL/loaders/usersQueries.js +++ b/src/GraphQL/loaders/usersQueries.js @@ -51,9 +51,10 @@ const load = parseGraphQLSchema => { } const fields = {}; - fields.me = { - description: 'The Me query can be used to return the current user data.', - type: new GraphQLNonNull(parseGraphQLSchema.meType), + fields.viewer = { + description: + 'The viewer query can be used to return the current user data.', + type: new GraphQLNonNull(parseGraphQLSchema.viewerType), async resolve(_source, _args, context, queryInfo) { try { const { config, info } = context; @@ -69,7 +70,7 @@ const load = parseGraphQLSchema => { description: 'UsersQuery is the top level type for users queries.', fields, }); - parseGraphQLSchema.graphQLTypes.push(usersQuery); + parseGraphQLSchema.addGraphQLType(usersQuery, true, true); parseGraphQLSchema.graphQLQueries.users = { description: 'This is the top level for users queries.', diff --git a/src/GraphQL/transformers/className.js b/src/GraphQL/transformers/className.js new file mode 100644 index 0000000000..da1f3cbb68 --- /dev/null +++ b/src/GraphQL/transformers/className.js @@ -0,0 +1,8 @@ +const transformClassNameToGraphQL = className => { + if (className[0] === '_') { + className = className.slice(1); + } + return className[0].toUpperCase() + className.slice(1); +}; + +export { transformClassNameToGraphQL }; diff --git a/src/GraphQL/transformers/mutation.js b/src/GraphQL/transformers/mutation.js new file mode 100644 index 0000000000..16b306e0f6 --- /dev/null +++ b/src/GraphQL/transformers/mutation.js @@ -0,0 +1,21 @@ +const parseMap = { + _op: '__op', +}; + +const transformMutationInputToParse = fields => { + if (!fields || typeof fields !== 'object') { + return; + } + Object.keys(fields).forEach(fieldName => { + const fieldValue = fields[fieldName]; + if (parseMap[fieldName]) { + delete fields[fieldName]; + fields[parseMap[fieldName]] = fieldValue; + } + if (typeof fieldValue === 'object') { + transformMutationInputToParse(fieldValue); + } + }); +}; + +export { transformMutationInputToParse }; diff --git a/src/GraphQL/transformers/query.js b/src/GraphQL/transformers/query.js new file mode 100644 index 0000000000..d95e3bcf66 --- /dev/null +++ b/src/GraphQL/transformers/query.js @@ -0,0 +1,154 @@ +const parseMap = { + _or: '$or', + _and: '$and', + _nor: '$nor', + _relatedTo: '$relatedTo', + _eq: '$eq', + _ne: '$ne', + _lt: '$lt', + _lte: '$lte', + _gt: '$gt', + _gte: '$gte', + _in: '$in', + _nin: '$nin', + _exists: '$exists', + _select: '$select', + _dontSelect: '$dontSelect', + _inQuery: '$inQuery', + _notInQuery: '$notInQuery', + _containedBy: '$containedBy', + _all: '$all', + _regex: '$regex', + _options: '$options', + _text: '$text', + _search: '$search', + _term: '$term', + _language: '$language', + _caseSensitive: '$caseSensitive', + _diacriticSensitive: '$diacriticSensitive', + _nearSphere: '$nearSphere', + _maxDistance: '$maxDistance', + _maxDistanceInRadians: '$maxDistanceInRadians', + _maxDistanceInMiles: '$maxDistanceInMiles', + _maxDistanceInKilometers: '$maxDistanceInKilometers', + _within: '$within', + _box: '$box', + _geoWithin: '$geoWithin', + _polygon: '$polygon', + _centerSphere: '$centerSphere', + _geoIntersects: '$geoIntersects', + _point: '$point', +}; + +const transformQueryInputToParse = ( + constraints, + parentFieldName, + parentConstraints +) => { + if (!constraints || typeof constraints !== 'object') { + return; + } + Object.keys(constraints).forEach(fieldName => { + let fieldValue = constraints[fieldName]; + + /** + * If we have a key-value pair, we need to change the way the constraint is structured. + * + * Example: + * From: + * { + * "someField": { + * "_lt": { + * "_key":"foo.bar", + * "_value": 100 + * }, + * "_gt": { + * "_key":"foo.bar", + * "_value": 10 + * } + * } + * } + * + * To: + * { + * "someField.foo.bar": { + * "$lt": 100, + * "$gt": 10 + * } + * } + */ + if ( + fieldValue._key && + fieldValue._value && + parentConstraints && + parentFieldName + ) { + delete parentConstraints[parentFieldName]; + parentConstraints[`${parentFieldName}.${fieldValue._key}`] = { + ...parentConstraints[`${parentFieldName}.${fieldValue._key}`], + [parseMap[fieldName]]: fieldValue._value, + }; + } else if (parseMap[fieldName]) { + delete constraints[fieldName]; + fieldName = parseMap[fieldName]; + constraints[fieldName] = fieldValue; + } + switch (fieldName) { + case '$point': + case '$nearSphere': + if (typeof fieldValue === 'object' && !fieldValue.__type) { + fieldValue.__type = 'GeoPoint'; + } + break; + case '$box': + if ( + typeof fieldValue === 'object' && + fieldValue.bottomLeft && + fieldValue.upperRight + ) { + fieldValue = [ + { + __type: 'GeoPoint', + ...fieldValue.bottomLeft, + }, + { + __type: 'GeoPoint', + ...fieldValue.upperRight, + }, + ]; + constraints[fieldName] = fieldValue; + } + break; + case '$polygon': + if (fieldValue instanceof Array) { + fieldValue.forEach(geoPoint => { + if (typeof geoPoint === 'object' && !geoPoint.__type) { + geoPoint.__type = 'GeoPoint'; + } + }); + } + break; + case '$centerSphere': + if ( + typeof fieldValue === 'object' && + fieldValue.center && + fieldValue.distance + ) { + fieldValue = [ + { + __type: 'GeoPoint', + ...fieldValue.center, + }, + fieldValue.distance, + ]; + constraints[fieldName] = fieldValue; + } + break; + } + if (typeof fieldValue === 'object') { + transformQueryInputToParse(fieldValue, fieldName, constraints); + } + }); +}; + +export { transformQueryInputToParse };