feat: alphabetical graphql api, fix internal reassign, enhanced Graphql schema cache system (#7344)

This commit is contained in:
Antoine Cormouls
2021-10-11 14:51:28 +02:00
committed by GitHub
parent 90b18bcccf
commit 85ef7217b0
10 changed files with 221 additions and 79 deletions

View File

@@ -108,6 +108,7 @@ ___
- ci: add node engine version check (Manuel Trezza) [#7574](https://github.com/parse-community/parse-server/pull/7574) - ci: add node engine version check (Manuel Trezza) [#7574](https://github.com/parse-community/parse-server/pull/7574)
### Notable Changes ### 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) - 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 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) - 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)

View File

@@ -57,7 +57,7 @@ describe('ParseGraphQLSchema', () => {
it('should load a brand new GraphQL Schema if Parse Schema changes', async () => { it('should load a brand new GraphQL Schema if Parse Schema changes', async () => {
await parseGraphQLSchema.load(); await parseGraphQLSchema.load();
const parseClasses = parseGraphQLSchema.parseClasses; const parseClasses = parseGraphQLSchema.parseClasses;
const parseClassesString = parseGraphQLSchema.parseClassesString; const parseCachedClasses = parseGraphQLSchema.parseCachedClasses;
const parseClassTypes = parseGraphQLSchema.parseClassTypes; const parseClassTypes = parseGraphQLSchema.parseClassTypes;
const graphQLSchema = parseGraphQLSchema.graphQLSchema; const graphQLSchema = parseGraphQLSchema.graphQLSchema;
const graphQLTypes = parseGraphQLSchema.graphQLTypes; const graphQLTypes = parseGraphQLSchema.graphQLTypes;
@@ -70,7 +70,7 @@ describe('ParseGraphQLSchema', () => {
await new Promise(resolve => setTimeout(resolve, 200)); await new Promise(resolve => setTimeout(resolve, 200));
await parseGraphQLSchema.load(); await parseGraphQLSchema.load();
expect(parseClasses).not.toBe(parseGraphQLSchema.parseClasses); 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(parseClassTypes).not.toBe(parseGraphQLSchema.parseClassTypes);
expect(graphQLSchema).not.toBe(parseGraphQLSchema.graphQLSchema); expect(graphQLSchema).not.toBe(parseGraphQLSchema.graphQLSchema);
expect(graphQLTypes).not.toBe(parseGraphQLSchema.graphQLTypes); expect(graphQLTypes).not.toBe(parseGraphQLSchema.graphQLTypes);
@@ -94,7 +94,7 @@ describe('ParseGraphQLSchema', () => {
}); });
await parseGraphQLSchema.load(); await parseGraphQLSchema.load();
const parseClasses = parseGraphQLSchema.parseClasses; const parseClasses = parseGraphQLSchema.parseClasses;
const parseClassesString = parseGraphQLSchema.parseClassesString; const parseCachedClasses = parseGraphQLSchema.parseCachedClasses;
const parseClassTypes = parseGraphQLSchema.parseClassTypes; const parseClassTypes = parseGraphQLSchema.parseClassTypes;
const graphQLSchema = parseGraphQLSchema.graphQLSchema; const graphQLSchema = parseGraphQLSchema.graphQLSchema;
const graphQLTypes = parseGraphQLSchema.graphQLTypes; const graphQLTypes = parseGraphQLSchema.graphQLTypes;
@@ -109,7 +109,7 @@ describe('ParseGraphQLSchema', () => {
await new Promise(resolve => setTimeout(resolve, 200)); await new Promise(resolve => setTimeout(resolve, 200));
await parseGraphQLSchema.load(); await parseGraphQLSchema.load();
expect(parseClasses).not.toBe(parseGraphQLSchema.parseClasses); 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(parseClassTypes).not.toBe(parseGraphQLSchema.parseClassTypes);
expect(graphQLSchema).not.toBe(parseGraphQLSchema.graphQLSchema); expect(graphQLSchema).not.toBe(parseGraphQLSchema.graphQLSchema);
expect(graphQLTypes).not.toBe(parseGraphQLSchema.graphQLTypes); expect(graphQLTypes).not.toBe(parseGraphQLSchema.graphQLTypes);

View File

@@ -25,6 +25,7 @@ const {
GraphQLEnumType, GraphQLEnumType,
GraphQLInputObjectType, GraphQLInputObjectType,
GraphQLSchema, GraphQLSchema,
GraphQLList,
} = require('graphql'); } = require('graphql');
const { ParseServer } = require('../'); const { ParseServer } = require('../');
const { ParseGraphQLServer } = require('../lib/GraphQL/ParseGraphQLServer'); const { ParseGraphQLServer } = require('../lib/GraphQL/ParseGraphQLServer');
@@ -10341,6 +10342,18 @@ describe('ParseGraphQLServer', () => {
robot: { value: 'robot' }, 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({ const SomeClassType = new GraphQLObjectType({
name: 'SomeClass', name: 'SomeClass',
fields: { fields: {
@@ -10386,6 +10399,18 @@ describe('ParseGraphQLServer', () => {
return obj.toJSON(); 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: [ types: [
@@ -10401,7 +10426,17 @@ describe('ParseGraphQLServer', () => {
type: { type: TypeEnum }, type: { type: TypeEnum },
}, },
}), }),
// Enhanced where input with a extended enum
new GraphQLInputObjectType({
name: 'SomeClassWhereInput',
fields: {
type: {
type: TypeEnumWhereInput,
},
},
}),
SomeClassType, SomeClassType,
SomeClass2WhereInput,
], ],
}), }),
}); });
@@ -10463,6 +10498,65 @@ describe('ParseGraphQLServer', () => {
expect(result.data.customQueryWithAutoTypeReturn.type).toEqual('robot'); 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 () => { it('can resolve a custom extend type', async () => {
const obj = new Parse.Object('SomeClass'); const obj = new Parse.Object('SomeClass');
await obj.save({ name: 'aname', type: 'robot' }); await obj.save({ name: 'aname', type: 'robot' });

View File

@@ -1,6 +1,7 @@
import Parse from 'parse/node'; import Parse from 'parse/node';
import { GraphQLSchema, GraphQLObjectType, DocumentNode, GraphQLNamedType } from 'graphql'; import { GraphQLSchema, GraphQLObjectType, DocumentNode, GraphQLNamedType } from 'graphql';
import { stitchSchemas } from '@graphql-tools/stitch'; import { stitchSchemas } from '@graphql-tools/stitch';
import { isDeepStrictEqual } from 'util';
import { SchemaDirectiveVisitor } from '@graphql-tools/utils'; import { SchemaDirectiveVisitor } from '@graphql-tools/utils';
import requiredParameter from '../requiredParameter'; import requiredParameter from '../requiredParameter';
import * as defaultGraphQLTypes from './loaders/defaultGraphQLTypes'; import * as defaultGraphQLTypes from './loaders/defaultGraphQLTypes';
@@ -93,15 +94,12 @@ class ParseGraphQLSchema {
async load() { async load() {
const { parseGraphQLConfig } = await this._initializeSchemaAndConfig(); const { parseGraphQLConfig } = await this._initializeSchemaAndConfig();
const parseClasses = await this._getClassesForSchema(parseGraphQLConfig); const parseClasses = await this._getClassesForSchema(parseGraphQLConfig);
const parseClassesString = JSON.stringify(parseClasses);
const functionNames = await this._getFunctionNames(); const functionNames = await this._getFunctionNames();
const functionNamesString = JSON.stringify(functionNames); const functionNamesString = JSON.stringify(functionNames);
if ( if (
this.graphQLSchema &&
!this._hasSchemaInputChanged({ !this._hasSchemaInputChanged({
parseClasses, parseClasses,
parseClassesString,
parseGraphQLConfig, parseGraphQLConfig,
functionNamesString, functionNamesString,
}) })
@@ -110,7 +108,6 @@ class ParseGraphQLSchema {
} }
this.parseClasses = parseClasses; this.parseClasses = parseClasses;
this.parseClassesString = parseClassesString;
this.parseGraphQLConfig = parseGraphQLConfig; this.parseGraphQLConfig = parseGraphQLConfig;
this.functionNames = functionNames; this.functionNames = functionNames;
this.functionNamesString = functionNamesString; this.functionNamesString = functionNamesString;
@@ -132,6 +129,26 @@ class ParseGraphQLSchema {
this._getParseClassesWithConfig(parseClasses, parseGraphQLConfig).forEach( this._getParseClassesWithConfig(parseClasses, parseGraphQLConfig).forEach(
([parseClass, parseClassConfig]) => { ([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); parseClassTypes.load(this, parseClass, parseClassConfig);
parseClassQueries.load(this, parseClass, parseClassConfig); parseClassQueries.load(this, parseClass, parseClassConfig);
parseClassMutations.load(this, parseClass, parseClassConfig); parseClassMutations.load(this, parseClass, parseClassConfig);
@@ -183,16 +200,17 @@ class ParseGraphQLSchema {
schemaDirectives.load(this); schemaDirectives.load(this);
if (typeof this.graphQLCustomTypeDefs.getTypeMap === 'function') { 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) => { const findAndReplaceLastType = (parent, key) => {
if (parent[key].name) { if (parent[key].name) {
if ( if (
this.graphQLAutoSchema.getType(parent[key].name) && this.graphQLAutoSchema._typeMap[parent[key].name] &&
this.graphQLAutoSchema.getType(parent[key].name) !== parent[key] this.graphQLAutoSchema._typeMap[parent[key].name] !== parent[key]
) { ) {
// To avoid unresolved field on overloaded schema // To avoid unresolved field on overloaded schema
// replace the final type with the auto schema one // 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 { } else {
if (parent[key].ofType) { if (parent[key].ofType) {
@@ -200,47 +218,59 @@ class ParseGraphQLSchema {
} }
} }
}; };
Object.values(customGraphQLSchemaTypeMap).forEach(customGraphQLSchemaType => { // Add non shared types from custom schema to auto schema
if ( // note: some non shared types can use some shared types
!customGraphQLSchemaType || // so this code need to be ran before the shared types addition
!customGraphQLSchemaType.name || // we use sort to ensure schema consistency over restarts
customGraphQLSchemaType.name.startsWith('__') Object.keys(customGraphQLSchemaTypeMap)
) { .sort()
return; .forEach(customGraphQLSchemaTypeKey => {
} const customGraphQLSchemaType = customGraphQLSchemaTypeMap[customGraphQLSchemaTypeKey];
const autoGraphQLSchemaType = this.graphQLAutoSchema.getType( if (
customGraphQLSchemaType.name !customGraphQLSchemaType ||
); !customGraphQLSchemaType.name ||
if (!autoGraphQLSchemaType) { customGraphQLSchemaType.name.startsWith('__')
this.graphQLAutoSchema._typeMap[customGraphQLSchemaType.name] = customGraphQLSchemaType; ) {
} return;
}); }
Object.values(customGraphQLSchemaTypeMap).forEach(customGraphQLSchemaType => { const autoGraphQLSchemaType = this.graphQLAutoSchema._typeMap[
if ( customGraphQLSchemaType.name
!customGraphQLSchemaType || ];
!customGraphQLSchemaType.name || if (!autoGraphQLSchemaType) {
customGraphQLSchemaType.name.startsWith('__') this.graphQLAutoSchema._typeMap[
) { customGraphQLSchemaType.name
return; ] = customGraphQLSchemaType;
} }
const autoGraphQLSchemaType = this.graphQLAutoSchema.getType( });
customGraphQLSchemaType.name // 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') { if (autoGraphQLSchemaType && typeof customGraphQLSchemaType.getFields === 'function') {
Object.values(customGraphQLSchemaType.getFields()).forEach(field => { Object.keys(customGraphQLSchemaType._fields)
findAndReplaceLastType(field, 'type'); .sort()
}); .forEach(fieldKey => {
autoGraphQLSchemaType._fields = { const field = customGraphQLSchemaType._fields[fieldKey];
...autoGraphQLSchemaType.getFields(), findAndReplaceLastType(field, 'type');
...customGraphQLSchemaType.getFields(), autoGraphQLSchemaType._fields[field.name] = field;
}; });
} }
}); });
this.graphQLSchema = stitchSchemas({ this.graphQLSchema = this.graphQLAutoSchema;
schemas: [this.graphQLSchemaDirectivesDefinitions, this.graphQLAutoSchema],
mergeDirectives: true,
});
} else if (typeof this.graphQLCustomTypeDefs === 'function') { } else if (typeof this.graphQLCustomTypeDefs === 'function') {
this.graphQLSchema = await this.graphQLCustomTypeDefs({ this.graphQLSchema = await this.graphQLCustomTypeDefs({
directivesDefinitionsSchema: this.graphQLSchemaDirectivesDefinitions, directivesDefinitionsSchema: this.graphQLSchemaDirectivesDefinitions,
@@ -258,6 +288,7 @@ class ParseGraphQLSchema {
}); });
} }
// Only merge directive when string schema provided
const graphQLSchemaTypeMap = this.graphQLSchema.getTypeMap(); const graphQLSchemaTypeMap = this.graphQLSchema.getTypeMap();
Object.keys(graphQLSchemaTypeMap).forEach(graphQLSchemaTypeName => { Object.keys(graphQLSchemaTypeMap).forEach(graphQLSchemaTypeName => {
const graphQLSchemaType = graphQLSchemaTypeMap[graphQLSchemaTypeName]; const graphQLSchemaType = graphQLSchemaTypeMap[graphQLSchemaTypeName];
@@ -463,26 +494,35 @@ class ParseGraphQLSchema {
*/ */
_hasSchemaInputChanged(params: { _hasSchemaInputChanged(params: {
parseClasses: any, parseClasses: any,
parseClassesString: string,
parseGraphQLConfig: ?ParseGraphQLConfig, parseGraphQLConfig: ?ParseGraphQLConfig,
functionNamesString: string, functionNamesString: string,
}): boolean { }): boolean {
const { parseClasses, parseClassesString, parseGraphQLConfig, functionNamesString } = params; const { parseClasses, parseGraphQLConfig, functionNamesString } = params;
if ( // First init
JSON.stringify(this.parseGraphQLConfig) === JSON.stringify(parseGraphQLConfig) && if (!this.parseCachedClasses || !this.graphQLSchema) {
this.functionNamesString === functionNamesString const thisParseClassesObj = parseClasses.reduce((acc, clzz) => {
) { acc[clzz.className] = clzz;
if (this.parseClasses === parseClasses) { return acc;
return false; }, {});
} this.parseCachedClasses = thisParseClassesObj;
return true;
if (this.parseClassesString === parseClassesString) {
this.parseClasses = parseClasses;
return false;
}
} }
const newParseCachedClasses = parseClasses.reduce((acc, clzz) => {
acc[clzz.className] = clzz;
return acc;
}, {});
if (
isDeepStrictEqual(this.parseGraphQLConfig, parseGraphQLConfig) &&
this.functionNamesString === functionNamesString &&
isDeepStrictEqual(this.parseCachedClasses, newParseCachedClasses)
) {
return false;
}
this.parseCachedClasses = newParseCachedClasses;
return true; return true;
} }
} }

View File

@@ -1,4 +1,5 @@
import { GraphQLNonNull, GraphQLEnumType } from 'graphql'; import { GraphQLNonNull, GraphQLEnumType } from 'graphql';
import deepcopy from 'deepcopy';
import { mutationWithClientMutationId } from 'graphql-relay'; import { mutationWithClientMutationId } from 'graphql-relay';
import { FunctionsRouter } from '../../Routers/FunctionsRouter'; import { FunctionsRouter } from '../../Routers/FunctionsRouter';
import * as defaultGraphQLTypes from './defaultGraphQLTypes'; import * as defaultGraphQLTypes from './defaultGraphQLTypes';
@@ -43,7 +44,7 @@ const load = parseGraphQLSchema => {
}, },
mutateAndGetPayload: async (args, context) => { mutateAndGetPayload: async (args, context) => {
try { try {
const { functionName, params } = args; const { functionName, params } = deepcopy(args);
const { config, auth, info } = context; const { config, auth, info } = context;
return { return {

View File

@@ -1,6 +1,7 @@
import { GraphQLNonNull } from 'graphql'; import { GraphQLNonNull } from 'graphql';
import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay'; import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay';
import getFieldNames from 'graphql-list-fields'; import getFieldNames from 'graphql-list-fields';
import deepcopy from 'deepcopy';
import * as defaultGraphQLTypes from './defaultGraphQLTypes'; import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import { extractKeysAndInclude, getParseClassMutationConfig } from '../parseGraphQLUtils'; import { extractKeysAndInclude, getParseClassMutationConfig } from '../parseGraphQLUtils';
import * as objectsMutations from '../helpers/objectsMutations'; import * as objectsMutations from '../helpers/objectsMutations';
@@ -66,7 +67,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG
}, },
mutateAndGetPayload: async (args, context, mutationInfo) => { mutateAndGetPayload: async (args, context, mutationInfo) => {
try { try {
let { fields } = args; let { fields } = deepcopy(args);
if (!fields) fields = {}; if (!fields) fields = {};
const { config, auth, info } = context; const { config, auth, info } = context;
@@ -168,7 +169,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG
}, },
mutateAndGetPayload: async (args, context, mutationInfo) => { mutateAndGetPayload: async (args, context, mutationInfo) => {
try { try {
let { id, fields } = args; let { id, fields } = deepcopy(args);
if (!fields) fields = {}; if (!fields) fields = {};
const { config, auth, info } = context; const { config, auth, info } = context;
@@ -273,7 +274,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG
}, },
mutateAndGetPayload: async (args, context, mutationInfo) => { mutateAndGetPayload: async (args, context, mutationInfo) => {
try { try {
let { id } = args; let { id } = deepcopy(args);
const { config, auth, info } = context; const { config, auth, info } = context;
const globalIdObject = fromGlobalId(id); const globalIdObject = fromGlobalId(id);

View File

@@ -1,6 +1,7 @@
import { GraphQLNonNull } from 'graphql'; import { GraphQLNonNull } from 'graphql';
import { fromGlobalId } from 'graphql-relay'; import { fromGlobalId } from 'graphql-relay';
import getFieldNames from 'graphql-list-fields'; import getFieldNames from 'graphql-list-fields';
import deepcopy from 'deepcopy';
import pluralize from 'pluralize'; import pluralize from 'pluralize';
import * as defaultGraphQLTypes from './defaultGraphQLTypes'; import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import * as objectsQueries from '../helpers/objectsQueries'; import * as objectsQueries from '../helpers/objectsQueries';
@@ -74,7 +75,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG
return await getQuery( return await getQuery(
parseClass, parseClass,
_source, _source,
args, deepcopy(args),
context, context,
queryInfo, queryInfo,
parseGraphQLSchema.parseClasses parseGraphQLSchema.parseClasses
@@ -97,7 +98,8 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG
type: new GraphQLNonNull(classGraphQLFindResultType || defaultGraphQLTypes.OBJECT), type: new GraphQLNonNull(classGraphQLFindResultType || defaultGraphQLTypes.OBJECT),
async resolve(_source, args, context, queryInfo) { async resolve(_source, args, context, queryInfo) {
try { 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 { readPreference, includeReadPreference, subqueryReadPreference } = options || {};
const { config, auth, info } = context; const { config, auth, info } = context;
const selectedFields = getFieldNames(queryInfo); const selectedFields = getFieldNames(queryInfo);

View File

@@ -1,5 +1,6 @@
import Parse from 'parse/node'; import Parse from 'parse/node';
import { GraphQLNonNull } from 'graphql'; import { GraphQLNonNull } from 'graphql';
import deepcopy from 'deepcopy';
import { mutationWithClientMutationId } from 'graphql-relay'; import { mutationWithClientMutationId } from 'graphql-relay';
import * as schemaTypes from './schemaTypes'; import * as schemaTypes from './schemaTypes';
import { transformToParse, transformToGraphQL } from '../transformers/schemaFields'; import { transformToParse, transformToGraphQL } from '../transformers/schemaFields';
@@ -26,7 +27,7 @@ const load = parseGraphQLSchema => {
}, },
mutateAndGetPayload: async (args, context) => { mutateAndGetPayload: async (args, context) => {
try { try {
const { name, schemaFields } = args; const { name, schemaFields } = deepcopy(args);
const { config, auth } = context; const { config, auth } = context;
enforceMasterKeyAccess(auth); enforceMasterKeyAccess(auth);
@@ -75,7 +76,7 @@ const load = parseGraphQLSchema => {
}, },
mutateAndGetPayload: async (args, context) => { mutateAndGetPayload: async (args, context) => {
try { try {
const { name, schemaFields } = args; const { name, schemaFields } = deepcopy(args);
const { config, auth } = context; const { config, auth } = context;
enforceMasterKeyAccess(auth); enforceMasterKeyAccess(auth);
@@ -126,7 +127,7 @@ const load = parseGraphQLSchema => {
}, },
mutateAndGetPayload: async (args, context) => { mutateAndGetPayload: async (args, context) => {
try { try {
const { name } = args; const { name } = deepcopy(args);
const { config, auth } = context; const { config, auth } = context;
enforceMasterKeyAccess(auth); enforceMasterKeyAccess(auth);

View File

@@ -1,4 +1,5 @@
import Parse from 'parse/node'; import Parse from 'parse/node';
import deepcopy from 'deepcopy';
import { GraphQLNonNull, GraphQLList } from 'graphql'; import { GraphQLNonNull, GraphQLList } from 'graphql';
import { transformToGraphQL } from '../transformers/schemaFields'; import { transformToGraphQL } from '../transformers/schemaFields';
import * as schemaTypes from './schemaTypes'; import * as schemaTypes from './schemaTypes';
@@ -27,7 +28,7 @@ const load = parseGraphQLSchema => {
type: new GraphQLNonNull(schemaTypes.CLASS), type: new GraphQLNonNull(schemaTypes.CLASS),
resolve: async (_source, args, context) => { resolve: async (_source, args, context) => {
try { try {
const { name } = args; const { name } = deepcopy(args);
const { config, auth } = context; const { config, auth } = context;
enforceMasterKeyAccess(auth); enforceMasterKeyAccess(auth);

View File

@@ -1,5 +1,6 @@
import { GraphQLNonNull, GraphQLString, GraphQLBoolean, GraphQLInputObjectType } from 'graphql'; import { GraphQLNonNull, GraphQLString, GraphQLBoolean, GraphQLInputObjectType } from 'graphql';
import { mutationWithClientMutationId } from 'graphql-relay'; import { mutationWithClientMutationId } from 'graphql-relay';
import deepcopy from 'deepcopy';
import UsersRouter from '../../Routers/UsersRouter'; import UsersRouter from '../../Routers/UsersRouter';
import * as objectsMutations from '../helpers/objectsMutations'; import * as objectsMutations from '../helpers/objectsMutations';
import { OBJECT } from './defaultGraphQLTypes'; import { OBJECT } from './defaultGraphQLTypes';
@@ -31,7 +32,7 @@ const load = parseGraphQLSchema => {
}, },
mutateAndGetPayload: async (args, context, mutationInfo) => { mutateAndGetPayload: async (args, context, mutationInfo) => {
try { try {
const { fields } = args; const { fields } = deepcopy(args);
const { config, auth, info } = context; const { config, auth, info } = context;
const parseFields = await transformTypes('create', fields, { const parseFields = await transformTypes('create', fields, {
@@ -101,7 +102,7 @@ const load = parseGraphQLSchema => {
}, },
mutateAndGetPayload: async (args, context, mutationInfo) => { mutateAndGetPayload: async (args, context, mutationInfo) => {
try { try {
const { fields, authData } = args; const { fields, authData } = deepcopy(args);
const { config, auth, info } = context; const { config, auth, info } = context;
const parseFields = await transformTypes('create', fields, { const parseFields = await transformTypes('create', fields, {
@@ -154,7 +155,7 @@ const load = parseGraphQLSchema => {
}, },
mutateAndGetPayload: async (args, context, mutationInfo) => { mutateAndGetPayload: async (args, context, mutationInfo) => {
try { try {
const { username, password } = args; const { username, password } = deepcopy(args);
const { config, auth, info } = context; const { config, auth, info } = context;
const { sessionToken, objectId } = ( const { sessionToken, objectId } = (