Skip to content

Commit f2f7720

Browse files
authored
Allow to resolve automatically Parse Type fields from Custom Schema (#6562)
* add package * Allow real GraphQL Schema via ParseServer.start * Allow resolve fields from auto graphQL Schema * Simple merge * fix + improve * Add tests
1 parent ad027c2 commit f2f7720

File tree

3 files changed

+121
-73
lines changed

3 files changed

+121
-73
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
"url": "https://opencollective.com/parse-server",
123123
"logo": "https://opencollective.com/parse-server/logo.txt?reverse=true&variant=binary"
124124
},
125+
"publishConfig": { "registry": "https://npm.pkg.github.com/" },
125126
"funding": {
126127
"type": "opencollective",
127128
"url": "https://opencollective.com/parse-server"

spec/ParseGraphQLServer.spec.js

+83-43
Original file line numberDiff line numberDiff line change
@@ -10769,57 +10769,70 @@ describe('ParseGraphQLServer', () => {
1076910769
robot: { value: 'robot' },
1077010770
},
1077110771
});
10772-
parseGraphQLServer = new ParseGraphQLServer(parseServer, {
10773-
graphQLPath: '/graphql',
10774-
graphQLCustomTypeDefs: new GraphQLSchema({
10775-
query: new GraphQLObjectType({
10776-
name: 'Query',
10777-
fields: {
10778-
customQuery: {
10779-
type: new GraphQLNonNull(GraphQLString),
10780-
args: {
10781-
message: { type: new GraphQLNonNull(GraphQLString) },
10772+
const SomeClassType = new GraphQLObjectType({
10773+
name: 'SomeClass',
10774+
fields: {
10775+
nameUpperCase: {
10776+
type: new GraphQLNonNull(GraphQLString),
10777+
resolve: (p) => p.name.toUpperCase(),
10778+
},
10779+
type: { type: TypeEnum },
10780+
language: {
10781+
type: new GraphQLEnumType({
10782+
name: 'LanguageEnum',
10783+
values: {
10784+
fr: { value: 'fr' },
10785+
en: { value: 'en' },
1078210786
},
10783-
resolve: (p, { message }) => message,
10784-
},
10787+
}),
10788+
resolve: () => 'fr',
1078510789
},
10786-
}),
10787-
types: [
10788-
new GraphQLInputObjectType({
10789-
name: 'CreateSomeClassFieldsInput',
10790-
fields: {
10791-
type: { type: TypeEnum },
10792-
},
10793-
}),
10794-
new GraphQLInputObjectType({
10795-
name: 'UpdateSomeClassFieldsInput',
10796-
fields: {
10797-
type: { type: TypeEnum },
10798-
},
10799-
}),
10800-
new GraphQLObjectType({
10801-
name: 'SomeClass',
10790+
},
10791+
}),
10792+
parseGraphQLServer = new ParseGraphQLServer(parseServer, {
10793+
graphQLPath: '/graphql',
10794+
graphQLCustomTypeDefs: new GraphQLSchema({
10795+
query: new GraphQLObjectType({
10796+
name: 'Query',
1080210797
fields: {
10803-
nameUpperCase: {
10798+
customQuery: {
1080410799
type: new GraphQLNonNull(GraphQLString),
10805-
resolve: (p) => p.name.toUpperCase(),
10800+
args: {
10801+
message: { type: new GraphQLNonNull(GraphQLString) },
10802+
},
10803+
resolve: (p, { message }) => message,
1080610804
},
10807-
type: { type: TypeEnum },
10808-
language: {
10809-
type: new GraphQLEnumType({
10810-
name: 'LanguageEnum',
10811-
values: {
10812-
fr: { value: 'fr' },
10813-
en: { value: 'en' },
10814-
},
10815-
}),
10816-
resolve: () => 'fr',
10805+
customQueryWithAutoTypeReturn: {
10806+
type: SomeClassType,
10807+
args: {
10808+
id: { type: new GraphQLNonNull(GraphQLString) },
10809+
},
10810+
resolve: async (p, { id }) => {
10811+
const obj = new Parse.Object('SomeClass');
10812+
obj.id = id;
10813+
await obj.fetch();
10814+
return obj.toJSON();
10815+
},
1081710816
},
1081810817
},
1081910818
}),
10820-
],
10821-
}),
10822-
});
10819+
types: [
10820+
new GraphQLInputObjectType({
10821+
name: 'CreateSomeClassFieldsInput',
10822+
fields: {
10823+
type: { type: TypeEnum },
10824+
},
10825+
}),
10826+
new GraphQLInputObjectType({
10827+
name: 'UpdateSomeClassFieldsInput',
10828+
fields: {
10829+
type: { type: TypeEnum },
10830+
},
10831+
}),
10832+
SomeClassType,
10833+
],
10834+
}),
10835+
});
1082310836

1082410837
parseGraphQLServer.applyGraphQL(expressApp);
1082510838
await new Promise((resolve) =>
@@ -10857,6 +10870,33 @@ describe('ParseGraphQLServer', () => {
1085710870
expect(result.data.customQuery).toEqual('hello');
1085810871
});
1085910872

10873+
it('can resolve a custom query with auto type return', async () => {
10874+
const obj = new Parse.Object('SomeClass');
10875+
await obj.save({ name: 'aname', type: 'robot' });
10876+
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
10877+
const result = await apolloClient.query({
10878+
variables: { id: obj.id },
10879+
query: gql`
10880+
query CustomQuery($id: String!) {
10881+
customQueryWithAutoTypeReturn(id: $id) {
10882+
objectId
10883+
nameUpperCase
10884+
name
10885+
type
10886+
}
10887+
}
10888+
`,
10889+
});
10890+
expect(result.data.customQueryWithAutoTypeReturn.objectId).toEqual(
10891+
obj.id
10892+
);
10893+
expect(result.data.customQueryWithAutoTypeReturn.name).toEqual('aname');
10894+
expect(result.data.customQueryWithAutoTypeReturn.nameUpperCase).toEqual(
10895+
'ANAME'
10896+
);
10897+
expect(result.data.customQueryWithAutoTypeReturn.type).toEqual('robot');
10898+
});
10899+
1086010900
it('can resolve a custom extend type', async () => {
1086110901
const obj = new Parse.Object('SomeClass');
1086210902
await obj.save({ name: 'aname', type: 'robot' });

src/GraphQL/ParseGraphQLSchema.js

+37-30
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ class ParseGraphQLSchema {
200200
if (typeof this.graphQLCustomTypeDefs.getTypeMap === 'function') {
201201
const customGraphQLSchemaTypeMap = this.graphQLCustomTypeDefs.getTypeMap();
202202
Object.values(customGraphQLSchemaTypeMap).forEach(
203-
customGraphQLSchemaType => {
203+
(customGraphQLSchemaType) => {
204204
if (
205205
!customGraphQLSchemaType ||
206206
!customGraphQLSchemaType.name ||
@@ -215,40 +215,45 @@ class ParseGraphQLSchema {
215215
autoGraphQLSchemaType &&
216216
typeof customGraphQLSchemaType.getFields === 'function'
217217
) {
218-
const findAndAddLastType = type => {
219-
if (type.name) {
220-
if (!this.graphQLAutoSchema.getType(type)) {
221-
// To avoid schema stitching (Unknow type) bug on variables
222-
// transfer the final type to the Auto Schema
223-
this.graphQLAutoSchema._typeMap[type.name] = type;
218+
const findAndReplaceLastType = (parent, key) => {
219+
if (parent[key].name) {
220+
if (
221+
this.graphQLAutoSchema.getType(parent[key].name) &&
222+
this.graphQLAutoSchema.getType(parent[key].name) !==
223+
parent[key]
224+
) {
225+
// To avoid unresolved field on overloaded schema
226+
// replace the final type with the auto schema one
227+
parent[key] = this.graphQLAutoSchema.getType(
228+
parent[key].name
229+
);
224230
}
225231
} else {
226-
if (type.ofType) {
227-
findAndAddLastType(type.ofType);
232+
if (parent[key].ofType) {
233+
findAndReplaceLastType(parent[key], 'ofType');
228234
}
229235
}
230236
};
237+
231238
Object.values(customGraphQLSchemaType.getFields()).forEach(
232-
field => {
233-
findAndAddLastType(field.type);
234-
if (field.args) {
235-
field.args.forEach(arg => {
236-
findAndAddLastType(arg.type);
237-
});
238-
}
239+
(field) => {
240+
findAndReplaceLastType(field, 'type');
239241
}
240242
);
241243
autoGraphQLSchemaType._fields = {
242-
...autoGraphQLSchemaType._fields,
243-
...customGraphQLSchemaType._fields,
244+
...autoGraphQLSchemaType.getFields(),
245+
...customGraphQLSchemaType.getFields(),
244246
};
247+
} else {
248+
this.graphQLAutoSchema._typeMap[
249+
customGraphQLSchemaType.name
250+
] = customGraphQLSchemaType;
245251
}
246252
}
247253
);
248254
this.graphQLSchema = mergeSchemas({
249255
schemas: [
250256
this.graphQLSchemaDirectivesDefinitions,
251-
this.graphQLCustomTypeDefs,
252257
this.graphQLAutoSchema,
253258
],
254259
mergeDirectives: true,
@@ -271,24 +276,24 @@ class ParseGraphQLSchema {
271276
}
272277

273278
const graphQLSchemaTypeMap = this.graphQLSchema.getTypeMap();
274-
Object.keys(graphQLSchemaTypeMap).forEach(graphQLSchemaTypeName => {
279+
Object.keys(graphQLSchemaTypeMap).forEach((graphQLSchemaTypeName) => {
275280
const graphQLSchemaType = graphQLSchemaTypeMap[graphQLSchemaTypeName];
276281
if (
277282
typeof graphQLSchemaType.getFields === 'function' &&
278283
this.graphQLCustomTypeDefs.definitions
279284
) {
280285
const graphQLCustomTypeDef = this.graphQLCustomTypeDefs.definitions.find(
281-
definition => definition.name.value === graphQLSchemaTypeName
286+
(definition) => definition.name.value === graphQLSchemaTypeName
282287
);
283288
if (graphQLCustomTypeDef) {
284289
const graphQLSchemaTypeFieldMap = graphQLSchemaType.getFields();
285290
Object.keys(graphQLSchemaTypeFieldMap).forEach(
286-
graphQLSchemaTypeFieldName => {
291+
(graphQLSchemaTypeFieldName) => {
287292
const graphQLSchemaTypeField =
288293
graphQLSchemaTypeFieldMap[graphQLSchemaTypeFieldName];
289294
if (!graphQLSchemaTypeField.astNode) {
290295
const astNode = graphQLCustomTypeDef.fields.find(
291-
field => field.name.value === graphQLSchemaTypeFieldName
296+
(field) => field.name.value === graphQLSchemaTypeFieldName
292297
);
293298
if (astNode) {
294299
graphQLSchemaTypeField.astNode = astNode;
@@ -319,7 +324,9 @@ class ParseGraphQLSchema {
319324
) {
320325
if (
321326
(!ignoreReserved && RESERVED_GRAPHQL_TYPE_NAMES.includes(type.name)) ||
322-
this.graphQLTypes.find(existingType => existingType.name === type.name) ||
327+
this.graphQLTypes.find(
328+
(existingType) => existingType.name === type.name
329+
) ||
323330
(!ignoreConnection && type.name.endsWith('Connection'))
324331
) {
325332
const message = `Type ${type.name} could not be added to the auto schema because it collided with an existing type.`;
@@ -409,20 +416,20 @@ class ParseGraphQLSchema {
409416
if (Array.isArray(enabledForClasses) || Array.isArray(disabledForClasses)) {
410417
let includedClasses = allClasses;
411418
if (enabledForClasses) {
412-
includedClasses = allClasses.filter(clazz => {
419+
includedClasses = allClasses.filter((clazz) => {
413420
return enabledForClasses.includes(clazz.className);
414421
});
415422
}
416423
if (disabledForClasses) {
417424
// Classes included in `enabledForClasses` that
418425
// are also present in `disabledForClasses` will
419426
// still be filtered out
420-
includedClasses = includedClasses.filter(clazz => {
427+
includedClasses = includedClasses.filter((clazz) => {
421428
return !disabledForClasses.includes(clazz.className);
422429
});
423430
}
424431
425-
this.isUsersClassDisabled = !includedClasses.some(clazz => {
432+
this.isUsersClassDisabled = !includedClasses.some((clazz) => {
426433
return clazz.className === '_User';
427434
});
428435
@@ -467,19 +474,19 @@ class ParseGraphQLSchema {
467474
}
468475
};
469476
470-
return parseClasses.sort(sortClasses).map(parseClass => {
477+
return parseClasses.sort(sortClasses).map((parseClass) => {
471478
let parseClassConfig;
472479
if (classConfigs) {
473480
parseClassConfig = classConfigs.find(
474-
c => c.className === parseClass.className
481+
(c) => c.className === parseClass.className
475482
);
476483
}
477484
return [parseClass, parseClassConfig];
478485
});
479486
}
480487
481488
async _getFunctionNames() {
482-
return await getFunctionNames(this.appId).filter(functionName => {
489+
return await getFunctionNames(this.appId).filter((functionName) => {
483490
if (/^[_a-zA-Z][_a-zA-Z0-9]*$/.test(functionName)) {
484491
return true;
485492
} else {

0 commit comments

Comments
 (0)