GraphQL: Inline Fragment on Array Fields (#5908)
* Inline Fragment Spec * Inline Fragment on Arrays * Fix Test * Only select the root field * Requested Changes * Lazy Loaded ArrayResult
This commit is contained in:
committed by
Antonio Davi Macedo Coelho de Castro
parent
45dabbbcda
commit
4bffdce047
@@ -81,7 +81,7 @@ class ParseGraphQLSchema {
|
||||
parseClassMutations.load(this, parseClass, parseClassConfig);
|
||||
}
|
||||
);
|
||||
|
||||
defaultGraphQLTypes.loadArrayResult(this, parseClasses);
|
||||
defaultGraphQLQueries.load(this);
|
||||
defaultGraphQLMutations.load(this);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
GraphQLList,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLBoolean,
|
||||
GraphQLUnionType,
|
||||
} from 'graphql';
|
||||
import { GraphQLUpload } from 'graphql-upload';
|
||||
|
||||
@@ -1020,6 +1021,55 @@ const SIGN_UP_RESULT = new GraphQLObjectType({
|
||||
},
|
||||
});
|
||||
|
||||
const ELEMENT = new GraphQLObjectType({
|
||||
name: 'Element',
|
||||
description:
|
||||
'The SignUpResult object type is used in the users sign up mutation to return the data of the recent created user.',
|
||||
fields: {
|
||||
value: {
|
||||
description: 'Return the value of the element in the array',
|
||||
type: new GraphQLNonNull(ANY),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Default static union type, we update types and resolveType function later
|
||||
let ARRAY_RESULT;
|
||||
|
||||
const loadArrayResult = (parseGraphQLSchema, parseClasses) => {
|
||||
const classTypes = parseClasses
|
||||
.filter(parseClass =>
|
||||
parseGraphQLSchema.parseClassTypes[parseClass.className]
|
||||
.classGraphQLOutputType
|
||||
? true
|
||||
: false
|
||||
)
|
||||
.map(
|
||||
parseClass =>
|
||||
parseGraphQLSchema.parseClassTypes[parseClass.className]
|
||||
.classGraphQLOutputType
|
||||
);
|
||||
ARRAY_RESULT = new GraphQLUnionType({
|
||||
name: 'ArrayResult',
|
||||
description:
|
||||
'Use Inline Fragment on Array to get results: https://graphql.org/learn/queries/#inline-fragments',
|
||||
types: () => [ELEMENT, ...classTypes],
|
||||
resolveType: value => {
|
||||
if (value.__type === 'Object' && value.className && value.objectId) {
|
||||
if (parseGraphQLSchema.parseClassTypes[value.className]) {
|
||||
return parseGraphQLSchema.parseClassTypes[value.className]
|
||||
.classGraphQLOutputType;
|
||||
} else {
|
||||
return ELEMENT;
|
||||
}
|
||||
} else {
|
||||
return ELEMENT;
|
||||
}
|
||||
},
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(ARRAY_RESULT);
|
||||
};
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
parseGraphQLSchema.graphQLTypes.push(GraphQLUpload);
|
||||
parseGraphQLSchema.graphQLTypes.push(ANY);
|
||||
@@ -1056,6 +1106,7 @@ const load = parseGraphQLSchema => {
|
||||
parseGraphQLSchema.graphQLTypes.push(POLYGON_CONSTRAINT);
|
||||
parseGraphQLSchema.graphQLTypes.push(FIND_RESULT);
|
||||
parseGraphQLSchema.graphQLTypes.push(SIGN_UP_RESULT);
|
||||
parseGraphQLSchema.graphQLTypes.push(ELEMENT);
|
||||
};
|
||||
|
||||
export {
|
||||
@@ -1140,5 +1191,8 @@ export {
|
||||
POLYGON_CONSTRAINT,
|
||||
FIND_RESULT,
|
||||
SIGN_UP_RESULT,
|
||||
ARRAY_RESULT,
|
||||
ELEMENT,
|
||||
load,
|
||||
loadArrayResult,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { GraphQLNonNull } from 'graphql';
|
||||
import getFieldNames from 'graphql-list-fields';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import * as parseClassTypes from './parseClassTypes';
|
||||
import { extractKeysAndInclude } from '../parseGraphQLUtils';
|
||||
import * as objectsMutations from './objectsMutations';
|
||||
import * as objectsQueries from './objectsQueries';
|
||||
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
|
||||
@@ -119,9 +119,7 @@ const load = function(
|
||||
info
|
||||
);
|
||||
const selectedFields = getFieldNames(mutationInfo);
|
||||
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
||||
selectedFields
|
||||
);
|
||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||
const { keys: requiredKeys, needGet } = getOnlyRequiredFields(
|
||||
fields,
|
||||
keys,
|
||||
@@ -180,9 +178,7 @@ const load = function(
|
||||
info
|
||||
);
|
||||
const selectedFields = getFieldNames(mutationInfo);
|
||||
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
||||
selectedFields
|
||||
);
|
||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||
|
||||
const { keys: requiredKeys, needGet } = getOnlyRequiredFields(
|
||||
fields,
|
||||
@@ -225,9 +221,7 @@ const load = function(
|
||||
const { objectId } = args;
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(mutationInfo);
|
||||
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
||||
selectedFields
|
||||
);
|
||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||
|
||||
let optimizedObject = {};
|
||||
const splitedKeys = keys.split(',');
|
||||
|
||||
@@ -2,8 +2,8 @@ import { GraphQLNonNull } from 'graphql';
|
||||
import getFieldNames from 'graphql-list-fields';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import * as objectsQueries from './objectsQueries';
|
||||
import * as parseClassTypes from './parseClassTypes';
|
||||
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
|
||||
import { extractKeysAndInclude } from '../parseGraphQLUtils';
|
||||
|
||||
const getParseClassQueryConfig = function(
|
||||
parseClassConfig: ?ParseGraphQLClassConfig
|
||||
@@ -11,6 +11,26 @@ const getParseClassQueryConfig = function(
|
||||
return (parseClassConfig && parseClassConfig.query) || {};
|
||||
};
|
||||
|
||||
const getQuery = async (className, _source, args, context, queryInfo) => {
|
||||
const { objectId, readPreference, includeReadPreference } = args;
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
|
||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||
|
||||
return await objectsQueries.getObject(
|
||||
className,
|
||||
objectId,
|
||||
keys,
|
||||
include,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
};
|
||||
|
||||
const load = function(
|
||||
parseGraphQLSchema,
|
||||
parseClass,
|
||||
@@ -40,25 +60,7 @@ const load = function(
|
||||
type: new GraphQLNonNull(classGraphQLOutputType),
|
||||
async resolve(_source, args, context, queryInfo) {
|
||||
try {
|
||||
const { objectId, readPreference, includeReadPreference } = args;
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
|
||||
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
||||
selectedFields
|
||||
);
|
||||
|
||||
return await objectsQueries.getObject(
|
||||
className,
|
||||
objectId,
|
||||
keys,
|
||||
include,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
return await getQuery(className, _source, args, context, queryInfo);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
@@ -86,7 +88,7 @@ const load = function(
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
|
||||
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
||||
const { keys, include } = extractKeysAndInclude(
|
||||
selectedFields
|
||||
.filter(field => field.includes('.'))
|
||||
.map(field => field.slice(field.indexOf('.') + 1))
|
||||
|
||||
@@ -14,6 +14,7 @@ import getFieldNames from 'graphql-list-fields';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import * as objectsQueries from './objectsQueries';
|
||||
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
|
||||
import { extractKeysAndInclude } from '../parseGraphQLUtils';
|
||||
|
||||
const mapInputType = (parseType, targetClass, parseClassTypes) => {
|
||||
switch (parseType) {
|
||||
@@ -65,7 +66,7 @@ const mapOutputType = (parseType, targetClass, parseClassTypes) => {
|
||||
case 'Boolean':
|
||||
return GraphQLBoolean;
|
||||
case 'Array':
|
||||
return new GraphQLList(defaultGraphQLTypes.ANY);
|
||||
return new GraphQLList(defaultGraphQLTypes.ARRAY_RESULT);
|
||||
case 'Object':
|
||||
return defaultGraphQLTypes.OBJECT;
|
||||
case 'Date':
|
||||
@@ -135,33 +136,6 @@ const mapConstraintType = (parseType, targetClass, parseClassTypes) => {
|
||||
}
|
||||
};
|
||||
|
||||
const extractKeysAndInclude = selectedFields => {
|
||||
selectedFields = selectedFields.filter(
|
||||
field => !field.includes('__typename')
|
||||
);
|
||||
let keys = undefined;
|
||||
let include = undefined;
|
||||
if (selectedFields && selectedFields.length > 0) {
|
||||
keys = selectedFields.join(',');
|
||||
include = selectedFields
|
||||
.reduce((fields, field) => {
|
||||
fields = fields.slice();
|
||||
let pointIndex = field.lastIndexOf('.');
|
||||
while (pointIndex > 0) {
|
||||
const lastField = field.slice(pointIndex + 1);
|
||||
field = field.slice(0, pointIndex);
|
||||
if (!fields.includes(field) && lastField !== 'objectId') {
|
||||
fields.push(field);
|
||||
}
|
||||
pointIndex = field.lastIndexOf('.');
|
||||
}
|
||||
return fields;
|
||||
}, [])
|
||||
.join(',');
|
||||
}
|
||||
return { keys, include };
|
||||
};
|
||||
|
||||
const getParseClassTypeConfig = function(
|
||||
parseClassConfig: ?ParseGraphQLClassConfig
|
||||
) {
|
||||
@@ -626,6 +600,27 @@ const load = (
|
||||
},
|
||||
},
|
||||
};
|
||||
} else if (parseClass.fields[field].type === 'Array') {
|
||||
return {
|
||||
...fields,
|
||||
[field]: {
|
||||
description: `Use Inline Fragment on Array to get results: https://graphql.org/learn/queries/#inline-fragments`,
|
||||
type,
|
||||
async resolve(source) {
|
||||
return source[field].map(async elem => {
|
||||
if (
|
||||
elem.className &&
|
||||
elem.objectId &&
|
||||
elem.__type === 'Object'
|
||||
) {
|
||||
return elem;
|
||||
} else {
|
||||
return { value: elem };
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
} else if (type) {
|
||||
return {
|
||||
...fields,
|
||||
|
||||
@@ -12,3 +12,30 @@ export function toGraphQLError(error) {
|
||||
}
|
||||
return new ApolloError(message, code);
|
||||
}
|
||||
|
||||
export const extractKeysAndInclude = selectedFields => {
|
||||
selectedFields = selectedFields.filter(
|
||||
field => !field.includes('__typename')
|
||||
);
|
||||
let keys = undefined;
|
||||
let include = undefined;
|
||||
if (selectedFields && selectedFields.length > 0) {
|
||||
keys = selectedFields.join(',');
|
||||
include = selectedFields
|
||||
.reduce((fields, field) => {
|
||||
fields = fields.slice();
|
||||
let pointIndex = field.lastIndexOf('.');
|
||||
while (pointIndex > 0) {
|
||||
const lastField = field.slice(pointIndex + 1);
|
||||
field = field.slice(0, pointIndex);
|
||||
if (!fields.includes(field) && lastField !== 'objectId') {
|
||||
fields.push(field);
|
||||
}
|
||||
pointIndex = field.lastIndexOf('.');
|
||||
}
|
||||
return fields;
|
||||
}, [])
|
||||
.join(',');
|
||||
}
|
||||
return { keys, include };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user