feat: alphabetical graphql api, fix internal reassign, enhanced Graphql schema cache system (#7344)
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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' });
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 } = (
|
||||||
|
|||||||
Reference in New Issue
Block a user