Relay Spec (#6089)

* Install graphql-relay

* Add relayNodeInterface to ParseGraphQLSchema

* Add support to global id

* Add support to global id in other operations

* Fix sort by glboal id

* Fix where by global id

* Introduce IdWhereInput

* Add Relay object identification tests

* Client mutation id on createFile mutation

* Client mutation id on callCloudCode mutation

* Client mutation id on signUp mutation

* Client mutation id on logIn mutation

* Client mutation id on logOut mutation

* Client mutation id on createClass mutation

* Client mutation id on updateClass mutation

* Client mutation id on deleteClass mutation

* Client mutation id on create object mutation

* Improve Viewer type

* Client mutation id on update object mutation

* Client mutation id on delete object mutation

* Introducing connections

* Fix tests

* Add pagination test

* Fix file location

* Fix postgres tests

* Add comments

* Tests to calculateSkipAndLimit
This commit is contained in:
Antonio Davi Macedo Coelho de Castro
2019-12-01 21:43:08 -08:00
committed by GitHub
parent 67e3c33ffe
commit a9066e20dc
22 changed files with 4685 additions and 2816 deletions

View File

@@ -1,4 +1,5 @@
import { GraphQLNonNull } from 'graphql';
import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay';
import getFieldNames from 'graphql-list-fields';
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import {
@@ -17,13 +18,16 @@ const getOnlyRequiredFields = (
includedFieldsString,
nativeObjectFields
) => {
const includedFields = includedFieldsString.split(',');
const selectedFields = selectedFieldsString.split(',');
const includedFields = includedFieldsString
? includedFieldsString.split(',')
: [];
const selectedFields = selectedFieldsString
? selectedFieldsString.split(',')
: [];
const missingFields = selectedFields
.filter(
field =>
!nativeObjectFields.includes(field) ||
includedFields.includes(field)
!nativeObjectFields.includes(field) || includedFields.includes(field)
)
.join(',');
if (!missingFields.length) {
@@ -40,6 +44,8 @@ const load = function(
) {
const className = parseClass.className;
const graphQLClassName = transformClassNameToGraphQL(className);
const getGraphQLQueryName =
graphQLClassName.charAt(0).toLowerCase() + graphQLClassName.slice(1);
const {
create: isCreateEnabled = true,
@@ -55,18 +61,25 @@ const load = function(
if (isCreateEnabled) {
const createGraphQLMutationName = `create${graphQLClassName}`;
parseGraphQLSchema.addGraphQLMutation(createGraphQLMutationName, {
const createGraphQLMutation = mutationWithClientMutationId({
name: `Create${graphQLClassName}`,
description: `The ${createGraphQLMutationName} mutation can be used to create a new object of the ${graphQLClassName} class.`,
args: {
inputFields: {
fields: {
description: 'These are the fields used to create the object.',
description:
'These are the fields that will be used to create the new object.',
type: classGraphQLCreateType || defaultGraphQLTypes.OBJECT,
},
},
type: new GraphQLNonNull(
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
),
async resolve(_source, args, context, mutationInfo) {
outputFields: {
[getGraphQLQueryName]: {
description: 'This is the created object.',
type: new GraphQLNonNull(
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
),
},
},
mutateAndGetPayload: async (args, context, mutationInfo) => {
try {
let { fields } = args;
if (!fields) fields = {};
@@ -85,13 +98,15 @@ const load = function(
auth,
info
);
const selectedFields = getFieldNames(mutationInfo);
const selectedFields = getFieldNames(mutationInfo)
.filter(field => field.startsWith(`${getGraphQLQueryName}.`))
.map(field => field.replace(`${getGraphQLQueryName}.`, ''));
const { keys, include } = extractKeysAndInclude(selectedFields);
const { keys: requiredKeys, needGet } = getOnlyRequiredFields(
fields,
keys,
include,
['id', 'createdAt', 'updatedAt']
['id', 'objectId', 'createdAt', 'updatedAt']
);
let optimizedObject = {};
if (needGet) {
@@ -108,37 +123,65 @@ const load = function(
);
}
return {
...createdObject,
updatedAt: createdObject.createdAt,
...parseFields,
...optimizedObject,
[getGraphQLQueryName]: {
...createdObject,
updatedAt: createdObject.createdAt,
...parseFields,
...optimizedObject,
},
};
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
});
if (
parseGraphQLSchema.addGraphQLType(
createGraphQLMutation.args.input.type.ofType
) &&
parseGraphQLSchema.addGraphQLType(createGraphQLMutation.type)
) {
parseGraphQLSchema.addGraphQLMutation(
createGraphQLMutationName,
createGraphQLMutation
);
}
}
if (isUpdateEnabled) {
const updateGraphQLMutationName = `update${graphQLClassName}`;
parseGraphQLSchema.addGraphQLMutation(updateGraphQLMutationName, {
const updateGraphQLMutation = mutationWithClientMutationId({
name: `Update${graphQLClassName}`,
description: `The ${updateGraphQLMutationName} mutation can be used to update an object of the ${graphQLClassName} class.`,
args: {
id: defaultGraphQLTypes.OBJECT_ID_ATT,
inputFields: {
id: defaultGraphQLTypes.GLOBAL_OR_OBJECT_ID_ATT,
fields: {
description: 'These are the fields used to update the object.',
description:
'These are the fields that will be used to update the object.',
type: classGraphQLUpdateType || defaultGraphQLTypes.OBJECT,
},
},
type: new GraphQLNonNull(
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
),
async resolve(_source, args, context, mutationInfo) {
outputFields: {
[getGraphQLQueryName]: {
description: 'This is the updated object.',
type: new GraphQLNonNull(
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
),
},
},
mutateAndGetPayload: async (args, context, mutationInfo) => {
try {
const { id, fields } = args;
let { id, fields } = args;
if (!fields) fields = {};
const { config, auth, info } = context;
const globalIdObject = fromGlobalId(id);
if (globalIdObject.type === className) {
id = globalIdObject.id;
}
const parseFields = await transformTypes('update', fields, {
className,
parseGraphQLSchema,
@@ -154,15 +197,17 @@ const load = function(
info
);
const selectedFields = getFieldNames(mutationInfo);
const selectedFields = getFieldNames(mutationInfo)
.filter(field => field.startsWith(`${getGraphQLQueryName}.`))
.map(field => field.replace(`${getGraphQLQueryName}.`, ''));
const { keys, include } = extractKeysAndInclude(selectedFields);
const { keys: requiredKeys, needGet } = getOnlyRequiredFields(
fields,
keys,
include,
['id', 'updatedAt']
['id', 'objectId', 'updatedAt']
);
let optimizedObject = {};
if (needGet) {
optimizedObject = await objectsQueries.getObject(
@@ -178,38 +223,69 @@ const load = function(
);
}
return {
id,
...updatedObject,
...parseFields,
...optimizedObject,
[getGraphQLQueryName]: {
objectId: id,
...updatedObject,
...parseFields,
...optimizedObject,
},
};
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
});
if (
parseGraphQLSchema.addGraphQLType(
updateGraphQLMutation.args.input.type.ofType
) &&
parseGraphQLSchema.addGraphQLType(updateGraphQLMutation.type)
) {
parseGraphQLSchema.addGraphQLMutation(
updateGraphQLMutationName,
updateGraphQLMutation
);
}
}
if (isDestroyEnabled) {
const deleteGraphQLMutationName = `delete${graphQLClassName}`;
parseGraphQLSchema.addGraphQLMutation(deleteGraphQLMutationName, {
const deleteGraphQLMutation = mutationWithClientMutationId({
name: `Delete${graphQLClassName}`,
description: `The ${deleteGraphQLMutationName} mutation can be used to delete an object of the ${graphQLClassName} class.`,
args: {
id: defaultGraphQLTypes.OBJECT_ID_ATT,
inputFields: {
id: defaultGraphQLTypes.GLOBAL_OR_OBJECT_ID_ATT,
},
type: new GraphQLNonNull(
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
),
async resolve(_source, args, context, mutationInfo) {
outputFields: {
[getGraphQLQueryName]: {
description: 'This is the deleted object.',
type: new GraphQLNonNull(
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
),
},
},
mutateAndGetPayload: async (args, context, mutationInfo) => {
try {
const { id } = args;
let { id } = args;
const { config, auth, info } = context;
const selectedFields = getFieldNames(mutationInfo);
const { keys, include } = extractKeysAndInclude(selectedFields);
const globalIdObject = fromGlobalId(id);
if (globalIdObject.type === className) {
id = globalIdObject.id;
}
const selectedFields = getFieldNames(mutationInfo)
.filter(field => field.startsWith(`${getGraphQLQueryName}.`))
.map(field => field.replace(`${getGraphQLQueryName}.`, ''));
const { keys, include } = extractKeysAndInclude(selectedFields);
let optimizedObject = {};
const splitedKeys = keys.split(',');
if (splitedKeys.length > 1 || splitedKeys[0] !== 'id') {
if (
keys &&
keys.split(',').filter(key => !['id', 'objectId'].includes(key))
.length > 0
) {
optimizedObject = await objectsQueries.getObject(
className,
id,
@@ -229,12 +305,29 @@ const load = function(
auth,
info
);
return { id, ...optimizedObject };
return {
[getGraphQLQueryName]: {
objectId: id,
...optimizedObject,
},
};
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
});
if (
parseGraphQLSchema.addGraphQLType(
deleteGraphQLMutation.args.input.type.ofType
) &&
parseGraphQLSchema.addGraphQLType(deleteGraphQLMutation.type)
) {
parseGraphQLSchema.addGraphQLMutation(
deleteGraphQLMutationName,
deleteGraphQLMutation
);
}
}
};