From 475ea920b9e9a1e7dab6fc9e2d0aa4713367c252 Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Sat, 9 Oct 2021 17:41:52 +0200 Subject: [PATCH] feat: alphabetical graphql api, fix internal reassign, enhanced Graphql schema cache system --- CHANGELOG.md | 1 + spec/ParseGraphQLSchema.spec.js | 8 +- spec/ParseGraphQLServer.spec.js | 94 ++++++++++++ src/GraphQL/ParseGraphQLSchema.js | 162 +++++++++++++-------- src/GraphQL/loaders/functionsMutations.js | 3 +- src/GraphQL/loaders/parseClassMutations.js | 7 +- src/GraphQL/loaders/parseClassQueries.js | 6 +- src/GraphQL/loaders/schemaMutations.js | 7 +- src/GraphQL/loaders/schemaQueries.js | 3 +- src/GraphQL/loaders/usersMutations.js | 7 +- 10 files changed, 220 insertions(+), 78 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 587ed9453b..43feb28fe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,7 @@ ___ - ci: add node engine version check (Manuel Trezza) [#7574](https://github.com/parse-community/parse-server/pull/7574) ### Notable Changes +- Alphabetical ordered GraphQL API, improved GraphQL Schema cache system and fix GraphQL input reassign issue (Moumouls) [#7344](https://github.com/parse-community/parse-server/issues/7344) - Added Parse Server Security Check to report weak security settings (Manuel Trezza, dblythy) [#7247](https://github.com/parse-community/parse-server/issues/7247) - EXPERIMENTAL: Added new page router with placeholder rendering and localization of custom and feature pages such as password reset and email verification (Manuel Trezza) [#7128](https://github.com/parse-community/parse-server/pull/7128) - EXPERIMENTAL: Added custom routes to easily customize flows for password reset, email verification or build entirely new flows (Manuel Trezza) [#7231](https://github.com/parse-community/parse-server/pull/7231) diff --git a/spec/ParseGraphQLSchema.spec.js b/spec/ParseGraphQLSchema.spec.js index 67472d6e91..a06a230f6a 100644 --- a/spec/ParseGraphQLSchema.spec.js +++ b/spec/ParseGraphQLSchema.spec.js @@ -57,7 +57,7 @@ describe('ParseGraphQLSchema', () => { it('should load a brand new GraphQL Schema if Parse Schema changes', async () => { await parseGraphQLSchema.load(); const parseClasses = parseGraphQLSchema.parseClasses; - const parseClassesString = parseGraphQLSchema.parseClassesString; + const parseCachedClasses = parseGraphQLSchema.parseCachedClasses; const parseClassTypes = parseGraphQLSchema.parseClassTypes; const graphQLSchema = parseGraphQLSchema.graphQLSchema; const graphQLTypes = parseGraphQLSchema.graphQLTypes; @@ -70,7 +70,7 @@ describe('ParseGraphQLSchema', () => { await new Promise(resolve => setTimeout(resolve, 200)); await parseGraphQLSchema.load(); expect(parseClasses).not.toBe(parseGraphQLSchema.parseClasses); - expect(parseClassesString).not.toBe(parseGraphQLSchema.parseClassesString); + expect(parseCachedClasses).not.toBe(parseGraphQLSchema.parseCachedClasses); expect(parseClassTypes).not.toBe(parseGraphQLSchema.parseClassTypes); expect(graphQLSchema).not.toBe(parseGraphQLSchema.graphQLSchema); expect(graphQLTypes).not.toBe(parseGraphQLSchema.graphQLTypes); @@ -94,7 +94,7 @@ describe('ParseGraphQLSchema', () => { }); await parseGraphQLSchema.load(); const parseClasses = parseGraphQLSchema.parseClasses; - const parseClassesString = parseGraphQLSchema.parseClassesString; + const parseCachedClasses = parseGraphQLSchema.parseCachedClasses; const parseClassTypes = parseGraphQLSchema.parseClassTypes; const graphQLSchema = parseGraphQLSchema.graphQLSchema; const graphQLTypes = parseGraphQLSchema.graphQLTypes; @@ -109,7 +109,7 @@ describe('ParseGraphQLSchema', () => { await new Promise(resolve => setTimeout(resolve, 200)); await parseGraphQLSchema.load(); expect(parseClasses).not.toBe(parseGraphQLSchema.parseClasses); - expect(parseClassesString).not.toBe(parseGraphQLSchema.parseClassesString); + expect(parseCachedClasses).not.toBe(parseGraphQLSchema.parseCachedClasses); expect(parseClassTypes).not.toBe(parseGraphQLSchema.parseClassTypes); expect(graphQLSchema).not.toBe(parseGraphQLSchema.graphQLSchema); expect(graphQLTypes).not.toBe(parseGraphQLSchema.graphQLTypes); diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index b8673cbe22..6d401cb3c4 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -25,6 +25,7 @@ const { GraphQLEnumType, GraphQLInputObjectType, GraphQLSchema, + GraphQLList, } = require('graphql'); const { ParseServer } = require('../'); const { ParseGraphQLServer } = require('../lib/GraphQL/ParseGraphQLServer'); @@ -10341,6 +10342,18 @@ describe('ParseGraphQLServer', () => { robot: { value: 'robot' }, }, }); + const TypeEnumWhereInput = new GraphQLInputObjectType({ + name: 'TypeEnumWhereInput', + fields: { + equalTo: { type: TypeEnum }, + }, + }); + const SomeClass2WhereInput = new GraphQLInputObjectType({ + name: 'SomeClass2WhereInput', + fields: { + type: { type: TypeEnumWhereInput }, + }, + }); const SomeClassType = new GraphQLObjectType({ name: 'SomeClass', fields: { @@ -10386,6 +10399,18 @@ describe('ParseGraphQLServer', () => { return obj.toJSON(); }, }, + customQueryWithAutoTypeReturnList: { + type: new GraphQLList(SomeClassType), + args: { + id: { type: new GraphQLNonNull(GraphQLString) }, + }, + resolve: async (p, { id }) => { + const obj = new Parse.Object('SomeClass'); + obj.id = id; + await obj.fetch(); + return [obj.toJSON(), obj.toJSON(), obj.toJSON()]; + }, + }, }, }), types: [ @@ -10401,7 +10426,17 @@ describe('ParseGraphQLServer', () => { type: { type: TypeEnum }, }, }), + // Enhanced where input with a extended enum + new GraphQLInputObjectType({ + name: 'SomeClassWhereInput', + fields: { + type: { + type: TypeEnumWhereInput, + }, + }, + }), SomeClassType, + SomeClass2WhereInput, ], }), }); @@ -10463,6 +10498,65 @@ describe('ParseGraphQLServer', () => { expect(result.data.customQueryWithAutoTypeReturn.type).toEqual('robot'); }); + it('can resolve a custom query with auto type list return', async () => { + const obj = new Parse.Object('SomeClass'); + await obj.save({ name: 'aname', type: 'robot' }); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + const result = await apolloClient.query({ + variables: { id: obj.id }, + query: gql` + query CustomQuery($id: String!) { + customQueryWithAutoTypeReturnList(id: $id) { + id + objectId + nameUpperCase + name + type + } + } + `, + }); + result.data.customQueryWithAutoTypeReturnList.forEach(rObj => { + expect(rObj.objectId).toBeDefined(); + expect(rObj.objectId).toEqual(obj.id); + expect(rObj.name).toEqual('aname'); + expect(rObj.nameUpperCase).toEqual('ANAME'); + expect(rObj.type).toEqual('robot'); + }); + }); + + it('can resolve a stacked query with same where variables on overloaded where input', async () => { + const objPointer = new Parse.Object('SomeClass2'); + await objPointer.save({ name: 'aname', type: 'robot' }); + const obj = new Parse.Object('SomeClass'); + await obj.save({ name: 'aname', type: 'robot', pointer: objPointer }); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + const result = await apolloClient.query({ + variables: { where: { OR: [{ pointer: { have: { objectId: { exists: true } } } }] } }, + query: gql` + query someQuery($where: SomeClassWhereInput!) { + q1: someClasses(where: $where) { + edges { + node { + id + } + } + } + q2: someClasses(where: $where) { + edges { + node { + id + } + } + } + } + `, + }); + expect(result.data.q1.edges.length).toEqual(1); + expect(result.data.q2.edges.length).toEqual(1); + expect(result.data.q1.edges[0].node.id).toEqual(result.data.q2.edges[0].node.id); + }); + it('can resolve a custom extend type', async () => { const obj = new Parse.Object('SomeClass'); await obj.save({ name: 'aname', type: 'robot' }); diff --git a/src/GraphQL/ParseGraphQLSchema.js b/src/GraphQL/ParseGraphQLSchema.js index d194a40ce5..011905ca2d 100644 --- a/src/GraphQL/ParseGraphQLSchema.js +++ b/src/GraphQL/ParseGraphQLSchema.js @@ -1,6 +1,7 @@ import Parse from 'parse/node'; import { GraphQLSchema, GraphQLObjectType, DocumentNode, GraphQLNamedType } from 'graphql'; import { stitchSchemas } from '@graphql-tools/stitch'; +import { isDeepStrictEqual } from 'util'; import { SchemaDirectiveVisitor } from '@graphql-tools/utils'; import requiredParameter from '../requiredParameter'; import * as defaultGraphQLTypes from './loaders/defaultGraphQLTypes'; @@ -93,15 +94,12 @@ class ParseGraphQLSchema { async load() { const { parseGraphQLConfig } = await this._initializeSchemaAndConfig(); const parseClasses = await this._getClassesForSchema(parseGraphQLConfig); - const parseClassesString = JSON.stringify(parseClasses); const functionNames = await this._getFunctionNames(); const functionNamesString = JSON.stringify(functionNames); if ( - this.graphQLSchema && !this._hasSchemaInputChanged({ parseClasses, - parseClassesString, parseGraphQLConfig, functionNamesString, }) @@ -110,7 +108,6 @@ class ParseGraphQLSchema { } this.parseClasses = parseClasses; - this.parseClassesString = parseClassesString; this.parseGraphQLConfig = parseGraphQLConfig; this.functionNames = functionNames; this.functionNamesString = functionNamesString; @@ -132,6 +129,26 @@ class ParseGraphQLSchema { this._getParseClassesWithConfig(parseClasses, parseGraphQLConfig).forEach( ([parseClass, parseClassConfig]) => { + // Some times schema return the _auth_data_ field + // it will lead to unstable graphql generation order + if (parseClass.className === '_User') { + Object.keys(parseClass.fields).forEach(fieldName => { + if (fieldName.startsWith('_auth_data_')) { + delete parseClass.fields[fieldName]; + } + }); + } + + // Fields order inside the schema seems to not be consistent across + // restart so we need to ensure an alphabetical order + // also it's better for the playground documentation + const orderedFields = {}; + Object.keys(parseClass.fields) + .sort() + .forEach(fieldName => { + orderedFields[fieldName] = parseClass.fields[fieldName]; + }); + parseClass.fields = orderedFields; parseClassTypes.load(this, parseClass, parseClassConfig); parseClassQueries.load(this, parseClass, parseClassConfig); parseClassMutations.load(this, parseClass, parseClassConfig); @@ -183,16 +200,17 @@ class ParseGraphQLSchema { schemaDirectives.load(this); if (typeof this.graphQLCustomTypeDefs.getTypeMap === 'function') { - const customGraphQLSchemaTypeMap = this.graphQLCustomTypeDefs.getTypeMap(); + // In following code we use underscore attr to avoid js var un ref + const customGraphQLSchemaTypeMap = this.graphQLCustomTypeDefs._typeMap; const findAndReplaceLastType = (parent, key) => { if (parent[key].name) { if ( - this.graphQLAutoSchema.getType(parent[key].name) && - this.graphQLAutoSchema.getType(parent[key].name) !== parent[key] + this.graphQLAutoSchema._typeMap[parent[key].name] && + this.graphQLAutoSchema._typeMap[parent[key].name] !== parent[key] ) { // To avoid unresolved field on overloaded schema // replace the final type with the auto schema one - parent[key] = this.graphQLAutoSchema.getType(parent[key].name); + parent[key] = this.graphQLAutoSchema._typeMap[parent[key].name]; } } else { if (parent[key].ofType) { @@ -200,47 +218,59 @@ class ParseGraphQLSchema { } } }; - Object.values(customGraphQLSchemaTypeMap).forEach(customGraphQLSchemaType => { - if ( - !customGraphQLSchemaType || - !customGraphQLSchemaType.name || - customGraphQLSchemaType.name.startsWith('__') - ) { - return; - } - const autoGraphQLSchemaType = this.graphQLAutoSchema.getType( - customGraphQLSchemaType.name - ); - if (!autoGraphQLSchemaType) { - this.graphQLAutoSchema._typeMap[customGraphQLSchemaType.name] = customGraphQLSchemaType; - } - }); - Object.values(customGraphQLSchemaTypeMap).forEach(customGraphQLSchemaType => { - if ( - !customGraphQLSchemaType || - !customGraphQLSchemaType.name || - customGraphQLSchemaType.name.startsWith('__') - ) { - return; - } - const autoGraphQLSchemaType = this.graphQLAutoSchema.getType( - customGraphQLSchemaType.name - ); - - if (autoGraphQLSchemaType && typeof customGraphQLSchemaType.getFields === 'function') { - Object.values(customGraphQLSchemaType.getFields()).forEach(field => { - findAndReplaceLastType(field, 'type'); - }); - autoGraphQLSchemaType._fields = { - ...autoGraphQLSchemaType.getFields(), - ...customGraphQLSchemaType.getFields(), - }; - } - }); - this.graphQLSchema = stitchSchemas({ - schemas: [this.graphQLSchemaDirectivesDefinitions, this.graphQLAutoSchema], - mergeDirectives: true, - }); + // Add non shared types from custom schema to auto schema + // note: some non shared types can use some shared types + // so this code need to be ran before the shared types addition + // we use sort to ensure schema consistency over restarts + Object.keys(customGraphQLSchemaTypeMap) + .sort() + .forEach(customGraphQLSchemaTypeKey => { + const customGraphQLSchemaType = customGraphQLSchemaTypeMap[customGraphQLSchemaTypeKey]; + if ( + !customGraphQLSchemaType || + !customGraphQLSchemaType.name || + customGraphQLSchemaType.name.startsWith('__') + ) { + return; + } + const autoGraphQLSchemaType = this.graphQLAutoSchema._typeMap[ + customGraphQLSchemaType.name + ]; + if (!autoGraphQLSchemaType) { + this.graphQLAutoSchema._typeMap[ + customGraphQLSchemaType.name + ] = customGraphQLSchemaType; + } + }); + // Handle shared types + // We pass through each type and ensure that all sub field types are replaced + // we use sort to ensure schema consistency over restarts + Object.keys(customGraphQLSchemaTypeMap) + .sort() + .forEach(customGraphQLSchemaTypeKey => { + const customGraphQLSchemaType = customGraphQLSchemaTypeMap[customGraphQLSchemaTypeKey]; + if ( + !customGraphQLSchemaType || + !customGraphQLSchemaType.name || + customGraphQLSchemaType.name.startsWith('__') + ) { + return; + } + const autoGraphQLSchemaType = this.graphQLAutoSchema._typeMap[ + customGraphQLSchemaType.name + ]; + + if (autoGraphQLSchemaType && typeof customGraphQLSchemaType.getFields === 'function') { + Object.keys(customGraphQLSchemaType._fields) + .sort() + .forEach(fieldKey => { + const field = customGraphQLSchemaType._fields[fieldKey]; + findAndReplaceLastType(field, 'type'); + autoGraphQLSchemaType._fields[field.name] = field; + }); + } + }); + this.graphQLSchema = this.graphQLAutoSchema; } else if (typeof this.graphQLCustomTypeDefs === 'function') { this.graphQLSchema = await this.graphQLCustomTypeDefs({ directivesDefinitionsSchema: this.graphQLSchemaDirectivesDefinitions, @@ -258,6 +288,7 @@ class ParseGraphQLSchema { }); } + // Only merge directive when string schema provided const graphQLSchemaTypeMap = this.graphQLSchema.getTypeMap(); Object.keys(graphQLSchemaTypeMap).forEach(graphQLSchemaTypeName => { const graphQLSchemaType = graphQLSchemaTypeMap[graphQLSchemaTypeName]; @@ -463,26 +494,35 @@ class ParseGraphQLSchema { */ _hasSchemaInputChanged(params: { parseClasses: any, - parseClassesString: string, parseGraphQLConfig: ?ParseGraphQLConfig, functionNamesString: string, }): boolean { - const { parseClasses, parseClassesString, parseGraphQLConfig, functionNamesString } = params; + const { parseClasses, parseGraphQLConfig, functionNamesString } = params; + + // First init + if (!this.parseCachedClasses || !this.graphQLSchema) { + const thisParseClassesObj = parseClasses.reduce((acc, clzz) => { + acc[clzz.className] = clzz; + return acc; + }, {}); + this.parseCachedClasses = thisParseClassesObj; + return true; + } + + const newParseCachedClasses = parseClasses.reduce((acc, clzz) => { + acc[clzz.className] = clzz; + return acc; + }, {}); if ( - JSON.stringify(this.parseGraphQLConfig) === JSON.stringify(parseGraphQLConfig) && - this.functionNamesString === functionNamesString + isDeepStrictEqual(this.parseGraphQLConfig, parseGraphQLConfig) && + this.functionNamesString === functionNamesString && + isDeepStrictEqual(this.parseCachedClasses, newParseCachedClasses) ) { - if (this.parseClasses === parseClasses) { - return false; - } - - if (this.parseClassesString === parseClassesString) { - this.parseClasses = parseClasses; - return false; - } + return false; } + this.parseCachedClasses = newParseCachedClasses; return true; } } diff --git a/src/GraphQL/loaders/functionsMutations.js b/src/GraphQL/loaders/functionsMutations.js index 0722ca9378..8eae5b2072 100644 --- a/src/GraphQL/loaders/functionsMutations.js +++ b/src/GraphQL/loaders/functionsMutations.js @@ -1,4 +1,5 @@ import { GraphQLNonNull, GraphQLEnumType } from 'graphql'; +import deepcopy from 'deepcopy'; import { mutationWithClientMutationId } from 'graphql-relay'; import { FunctionsRouter } from '../../Routers/FunctionsRouter'; import * as defaultGraphQLTypes from './defaultGraphQLTypes'; @@ -43,7 +44,7 @@ const load = parseGraphQLSchema => { }, mutateAndGetPayload: async (args, context) => { try { - const { functionName, params } = args; + const { functionName, params } = deepcopy(args); const { config, auth, info } = context; return { diff --git a/src/GraphQL/loaders/parseClassMutations.js b/src/GraphQL/loaders/parseClassMutations.js index 2ef41eccbd..0ebca5b077 100644 --- a/src/GraphQL/loaders/parseClassMutations.js +++ b/src/GraphQL/loaders/parseClassMutations.js @@ -1,6 +1,7 @@ import { GraphQLNonNull } from 'graphql'; import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay'; import getFieldNames from 'graphql-list-fields'; +import deepcopy from 'deepcopy'; import * as defaultGraphQLTypes from './defaultGraphQLTypes'; import { extractKeysAndInclude, getParseClassMutationConfig } from '../parseGraphQLUtils'; import * as objectsMutations from '../helpers/objectsMutations'; @@ -66,7 +67,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG }, mutateAndGetPayload: async (args, context, mutationInfo) => { try { - let { fields } = args; + let { fields } = deepcopy(args); if (!fields) fields = {}; const { config, auth, info } = context; @@ -168,7 +169,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG }, mutateAndGetPayload: async (args, context, mutationInfo) => { try { - let { id, fields } = args; + let { id, fields } = deepcopy(args); if (!fields) fields = {}; const { config, auth, info } = context; @@ -273,7 +274,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG }, mutateAndGetPayload: async (args, context, mutationInfo) => { try { - let { id } = args; + let { id } = deepcopy(args); const { config, auth, info } = context; const globalIdObject = fromGlobalId(id); diff --git a/src/GraphQL/loaders/parseClassQueries.js b/src/GraphQL/loaders/parseClassQueries.js index f0a855103d..edf210ace3 100644 --- a/src/GraphQL/loaders/parseClassQueries.js +++ b/src/GraphQL/loaders/parseClassQueries.js @@ -1,6 +1,7 @@ import { GraphQLNonNull } from 'graphql'; import { fromGlobalId } from 'graphql-relay'; import getFieldNames from 'graphql-list-fields'; +import deepcopy from 'deepcopy'; import pluralize from 'pluralize'; import * as defaultGraphQLTypes from './defaultGraphQLTypes'; import * as objectsQueries from '../helpers/objectsQueries'; @@ -74,7 +75,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG return await getQuery( parseClass, _source, - args, + deepcopy(args), context, queryInfo, parseGraphQLSchema.parseClasses @@ -97,7 +98,8 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG type: new GraphQLNonNull(classGraphQLFindResultType || defaultGraphQLTypes.OBJECT), async resolve(_source, args, context, queryInfo) { try { - const { where, order, skip, first, after, last, before, options } = args; + // Deep copy args to avoid internal re assign issue + const { where, order, skip, first, after, last, before, options } = deepcopy(args); const { readPreference, includeReadPreference, subqueryReadPreference } = options || {}; const { config, auth, info } = context; const selectedFields = getFieldNames(queryInfo); diff --git a/src/GraphQL/loaders/schemaMutations.js b/src/GraphQL/loaders/schemaMutations.js index 89798f9784..ffb4d6523b 100644 --- a/src/GraphQL/loaders/schemaMutations.js +++ b/src/GraphQL/loaders/schemaMutations.js @@ -1,5 +1,6 @@ import Parse from 'parse/node'; import { GraphQLNonNull } from 'graphql'; +import deepcopy from 'deepcopy'; import { mutationWithClientMutationId } from 'graphql-relay'; import * as schemaTypes from './schemaTypes'; import { transformToParse, transformToGraphQL } from '../transformers/schemaFields'; @@ -26,7 +27,7 @@ const load = parseGraphQLSchema => { }, mutateAndGetPayload: async (args, context) => { try { - const { name, schemaFields } = args; + const { name, schemaFields } = deepcopy(args); const { config, auth } = context; enforceMasterKeyAccess(auth); @@ -75,7 +76,7 @@ const load = parseGraphQLSchema => { }, mutateAndGetPayload: async (args, context) => { try { - const { name, schemaFields } = args; + const { name, schemaFields } = deepcopy(args); const { config, auth } = context; enforceMasterKeyAccess(auth); @@ -126,7 +127,7 @@ const load = parseGraphQLSchema => { }, mutateAndGetPayload: async (args, context) => { try { - const { name } = args; + const { name } = deepcopy(args); const { config, auth } = context; enforceMasterKeyAccess(auth); diff --git a/src/GraphQL/loaders/schemaQueries.js b/src/GraphQL/loaders/schemaQueries.js index cd049ce017..25bc071919 100644 --- a/src/GraphQL/loaders/schemaQueries.js +++ b/src/GraphQL/loaders/schemaQueries.js @@ -1,4 +1,5 @@ import Parse from 'parse/node'; +import deepcopy from 'deepcopy'; import { GraphQLNonNull, GraphQLList } from 'graphql'; import { transformToGraphQL } from '../transformers/schemaFields'; import * as schemaTypes from './schemaTypes'; @@ -27,7 +28,7 @@ const load = parseGraphQLSchema => { type: new GraphQLNonNull(schemaTypes.CLASS), resolve: async (_source, args, context) => { try { - const { name } = args; + const { name } = deepcopy(args); const { config, auth } = context; enforceMasterKeyAccess(auth); diff --git a/src/GraphQL/loaders/usersMutations.js b/src/GraphQL/loaders/usersMutations.js index 50cb241b68..c38905cd90 100644 --- a/src/GraphQL/loaders/usersMutations.js +++ b/src/GraphQL/loaders/usersMutations.js @@ -1,5 +1,6 @@ import { GraphQLNonNull, GraphQLString, GraphQLBoolean, GraphQLInputObjectType } from 'graphql'; import { mutationWithClientMutationId } from 'graphql-relay'; +import deepcopy from 'deepcopy'; import UsersRouter from '../../Routers/UsersRouter'; import * as objectsMutations from '../helpers/objectsMutations'; import { OBJECT } from './defaultGraphQLTypes'; @@ -31,7 +32,7 @@ const load = parseGraphQLSchema => { }, mutateAndGetPayload: async (args, context, mutationInfo) => { try { - const { fields } = args; + const { fields } = deepcopy(args); const { config, auth, info } = context; const parseFields = await transformTypes('create', fields, { @@ -101,7 +102,7 @@ const load = parseGraphQLSchema => { }, mutateAndGetPayload: async (args, context, mutationInfo) => { try { - const { fields, authData } = args; + const { fields, authData } = deepcopy(args); const { config, auth, info } = context; const parseFields = await transformTypes('create', fields, { @@ -154,7 +155,7 @@ const load = parseGraphQLSchema => { }, mutateAndGetPayload: async (args, context, mutationInfo) => { try { - const { username, password } = args; + const { username, password } = deepcopy(args); const { config, auth, info } = context; const { sessionToken, objectId } = (