Skip to content

Commit 5e3d377

Browse files
authored
Advantage common convention for naming root types. (#470)
This is a result of a conversation I had with @lacker that we should provide a stronger opinion on what to name the root types, and then advantage that opinion. Here's the result of that: If you use the type names `Query` and `Mutation` for your root types, then printing the schema or parsing a schema IDL can omit the `schema { query: Query, mutation: Mutation }` boilerplate. If your root type names are anything other than these, then you have to include it. There doesn't seem to be a significant downsite to this except for the relative complication of understanding why sometimes a `schema {}` description is included and sometimes it isn't.
1 parent 331dc77 commit 5e3d377

File tree

4 files changed

+104
-109
lines changed

4 files changed

+104
-109
lines changed

src/utilities/__tests__/buildASTSchema-test.js

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -457,10 +457,6 @@ type Subscription {
457457

458458
it('Unreferenced type implementing referenced interface', () => {
459459
const body = `
460-
schema {
461-
query: Query
462-
}
463-
464460
type Concrete implements Iface {
465461
key: String
466462
}
@@ -479,10 +475,6 @@ type Query {
479475

480476
it('Unreferenced type implementing referenced union', () => {
481477
const body = `
482-
schema {
483-
query: Query
484-
}
485-
486478
type Concrete {
487479
key: String
488480
}
@@ -499,10 +491,6 @@ union Union = Concrete
499491

500492
it('Supports @deprecated', () => {
501493
const body = `
502-
schema {
503-
query: Query
504-
}
505-
506494
enum MyEnum {
507495
VALUE
508496
OLD_VALUE @deprecated
@@ -522,15 +510,16 @@ type Query {
522510

523511
describe('Failures', () => {
524512

525-
it('Requires a schema definition', () => {
513+
it('Requires a schema definition or Query type', () => {
526514
const body = `
527515
type Hello {
528516
bar: Bar
529517
}
530518
`;
531519
const doc = parse(body);
532-
expect(() => buildASTSchema(doc))
533-
.to.throw('Must provide a schema definition.');
520+
expect(() => buildASTSchema(doc)).to.throw(
521+
'Must provide schema definition with query type or a type named Query.'
522+
);
534523
});
535524

536525
it('Allows only a single schema definition', () => {
@@ -563,8 +552,9 @@ type Hello {
563552
}
564553
`;
565554
const doc = parse(body);
566-
expect(() => buildASTSchema(doc))
567-
.to.throw('Must provide schema definition with query type.');
555+
expect(() => buildASTSchema(doc)).to.throw(
556+
'Must provide schema definition with query type or a type named Query.'
557+
);
568558
});
569559

570560
it('Allows only a single query type', () => {

src/utilities/__tests__/extendSchema-test.js

Lines changed: 9 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,7 @@ describe('extendSchema', () => {
156156
expect(extendedSchema).to.not.equal(testSchema);
157157
expect(printSchema(testSchema)).to.equal(originalPrint);
158158
expect(printSchema(extendedSchema)).to.equal(
159-
`schema {
160-
query: Query
161-
}
162-
163-
type Bar implements SomeInterface {
159+
`type Bar implements SomeInterface {
164160
name: String
165161
some: SomeInterface
166162
foo: Foo
@@ -209,11 +205,7 @@ union SomeUnion = Foo | Biz
209205
expect(extendedSchema).to.not.equal(testSchema);
210206
expect(printSchema(testSchema)).to.equal(originalPrint);
211207
expect(printSchema(extendedSchema)).to.equal(
212-
`schema {
213-
query: Query
214-
}
215-
216-
type Bar implements SomeInterface {
208+
`type Bar implements SomeInterface {
217209
name: String
218210
some: SomeInterface
219211
foo: Foo
@@ -271,11 +263,7 @@ type Unused {
271263
expect(extendedSchema).to.not.equal(testSchema);
272264
expect(printSchema(testSchema)).to.equal(originalPrint);
273265
expect(printSchema(extendedSchema)).to.equal(
274-
`schema {
275-
query: Query
276-
}
277-
278-
type Bar implements SomeInterface {
266+
`type Bar implements SomeInterface {
279267
name: String
280268
some: SomeInterface
281269
foo: Foo
@@ -330,11 +318,7 @@ union SomeUnion = Foo | Biz
330318
expect(extendedSchema).to.not.equal(testSchema);
331319
expect(printSchema(testSchema)).to.equal(originalPrint);
332320
expect(printSchema(extendedSchema)).to.equal(
333-
`schema {
334-
query: Query
335-
}
336-
337-
type Bar implements SomeInterface {
321+
`type Bar implements SomeInterface {
338322
name: String
339323
some: SomeInterface
340324
foo: Foo
@@ -384,11 +368,7 @@ union SomeUnion = Foo | Biz
384368
expect(extendedSchema).to.not.equal(testSchema);
385369
expect(printSchema(testSchema)).to.equal(originalPrint);
386370
expect(printSchema(extendedSchema)).to.equal(
387-
`schema {
388-
query: Query
389-
}
390-
391-
type Bar implements SomeInterface {
371+
`type Bar implements SomeInterface {
392372
name: String
393373
some: SomeInterface
394374
foo: Foo
@@ -464,11 +444,7 @@ union SomeUnion = Foo | Biz
464444
expect(extendedSchema).to.not.equal(testSchema);
465445
expect(printSchema(testSchema)).to.equal(originalPrint);
466446
expect(printSchema(extendedSchema)).to.equal(
467-
`schema {
468-
query: Query
469-
}
470-
471-
type Bar implements SomeInterface {
447+
`type Bar implements SomeInterface {
472448
name: String
473449
some: SomeInterface
474450
foo: Foo
@@ -547,11 +523,7 @@ union SomeUnion = Foo | Biz
547523
expect(extendedSchema).to.not.equal(testSchema);
548524
expect(printSchema(testSchema)).to.equal(originalPrint);
549525
expect(printSchema(extendedSchema)).to.equal(
550-
`schema {
551-
query: Query
552-
}
553-
554-
type Bar implements SomeInterface {
526+
`type Bar implements SomeInterface {
555527
name: String
556528
some: SomeInterface
557529
foo: Foo
@@ -619,11 +591,7 @@ union SomeUnion = Foo | Biz
619591
expect(extendedSchema).to.not.equal(testSchema);
620592
expect(printSchema(testSchema)).to.equal(originalPrint);
621593
expect(printSchema(extendedSchema)).to.equal(
622-
`schema {
623-
query: Query
624-
}
625-
626-
type Bar implements SomeInterface {
594+
`type Bar implements SomeInterface {
627595
name: String
628596
some: SomeInterface
629597
foo: Foo
@@ -709,13 +677,7 @@ union SomeUnion = Foo | Biz
709677
expect(extendedSchema).to.not.equal(mutationSchema);
710678
expect(printSchema(mutationSchema)).to.equal(originalPrint);
711679
expect(printSchema(extendedSchema)).to.equal(
712-
`schema {
713-
query: Query
714-
mutation: Mutation
715-
subscription: Subscription
716-
}
717-
718-
type Mutation {
680+
`type Mutation {
719681
mutationField: String
720682
newMutationField: Int
721683
}

src/utilities/buildASTSchema.js

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
import find from '../jsutils/find';
1212
import invariant from '../jsutils/invariant';
13-
import keyMap from '../jsutils/keyMap';
1413
import keyValMap from '../jsutils/keyValMap';
1514
import { valueFromAST } from './valueFromAST';
1615
import { TokenKind } from '../language/lexer';
@@ -128,6 +127,9 @@ function getNamedTypeAST(typeAST: Type): NamedType {
128127
* This takes the ast of a schema document produced by the parse function in
129128
* src/language/parser.js.
130129
*
130+
* If no schema definition is provided, then it will look for types named Query
131+
* and Mutation.
132+
*
131133
* Given that AST it constructs a GraphQLSchema. As constructed
132134
* they are not particularly useful for non-introspection queries
133135
* since they have no resolve methods.
@@ -140,6 +142,7 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
140142
let schemaDef: ?SchemaDefinition;
141143

142144
const typeDefs: Array<TypeDefinition> = [];
145+
const astMap: {[name: string]: TypeDefinition} = Object.create(null);
143146
const directiveDefs: Array<DirectiveDefinition> = [];
144147
for (let i = 0; i < ast.definitions.length; i++) {
145148
const d = ast.definitions[i];
@@ -157,63 +160,67 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
157160
case UNION_TYPE_DEFINITION:
158161
case INPUT_OBJECT_TYPE_DEFINITION:
159162
typeDefs.push(d);
163+
astMap[d.name.value] = d;
160164
break;
161165
case DIRECTIVE_DEFINITION:
162166
directiveDefs.push(d);
163167
break;
164168
}
165169
}
166170

167-
if (!schemaDef) {
168-
throw new Error('Must provide a schema definition.');
169-
}
170-
171171
let queryTypeName;
172172
let mutationTypeName;
173173
let subscriptionTypeName;
174-
schemaDef.operationTypes.forEach(operationType => {
175-
const typeName = operationType.type.name.value;
176-
if (operationType.operation === 'query') {
177-
if (queryTypeName) {
178-
throw new Error('Must provide only one query type in schema.');
179-
}
180-
queryTypeName = typeName;
181-
} else if (operationType.operation === 'mutation') {
182-
if (mutationTypeName) {
183-
throw new Error('Must provide only one mutation type in schema.');
184-
}
185-
mutationTypeName = typeName;
186-
} else if (operationType.operation === 'subscription') {
187-
if (subscriptionTypeName) {
188-
throw new Error('Must provide only one subscription type in schema.');
174+
if (schemaDef) {
175+
schemaDef.operationTypes.forEach(operationType => {
176+
const typeName = operationType.type.name.value;
177+
if (operationType.operation === 'query') {
178+
if (queryTypeName) {
179+
throw new Error('Must provide only one query type in schema.');
180+
}
181+
if (!astMap[typeName]) {
182+
throw new Error(
183+
`Specified query type "${typeName}" not found in document.`
184+
);
185+
}
186+
queryTypeName = typeName;
187+
} else if (operationType.operation === 'mutation') {
188+
if (mutationTypeName) {
189+
throw new Error('Must provide only one mutation type in schema.');
190+
}
191+
if (!astMap[typeName]) {
192+
throw new Error(
193+
`Specified mutation type "${typeName}" not found in document.`
194+
);
195+
}
196+
mutationTypeName = typeName;
197+
} else if (operationType.operation === 'subscription') {
198+
if (subscriptionTypeName) {
199+
throw new Error('Must provide only one subscription type in schema.');
200+
}
201+
if (!astMap[typeName]) {
202+
throw new Error(
203+
`Specified subscription type "${typeName}" not found in document.`
204+
);
205+
}
206+
subscriptionTypeName = typeName;
189207
}
190-
subscriptionTypeName = typeName;
208+
});
209+
} else {
210+
if (astMap.Query) {
211+
queryTypeName = 'Query';
212+
}
213+
if (astMap.Mutation) {
214+
mutationTypeName = 'Mutation';
215+
}
216+
if (astMap.Subscription) {
217+
subscriptionTypeName = 'Subscription';
191218
}
192-
});
193-
194-
if (!queryTypeName) {
195-
throw new Error('Must provide schema definition with query type.');
196-
}
197-
198-
const astMap: {[name: string]: TypeDefinition} =
199-
keyMap(typeDefs, d => d.name.value);
200-
201-
if (!astMap[queryTypeName]) {
202-
throw new Error(
203-
`Specified query type "${queryTypeName}" not found in document.`
204-
);
205-
}
206-
207-
if (mutationTypeName && !astMap[mutationTypeName]) {
208-
throw new Error(
209-
`Specified mutation type "${mutationTypeName}" not found in document.`
210-
);
211219
}
212220

213-
if (subscriptionTypeName && !astMap[subscriptionTypeName]) {
221+
if (!queryTypeName) {
214222
throw new Error(
215-
`Specified subscription type "${
216-
subscriptionTypeName}" not found in document.`
223+
'Must provide schema definition with query type or a type named Query.'
217224
);
218225
}
219226

src/utilities/schemaPrinter.js

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,18 @@ function printFilteredSchema(
7272
.filter(typeFilter)
7373
.sort((name1, name2) => name1.localeCompare(name2))
7474
.map(typeName => typeMap[typeName]);
75+
7576
return [ printSchemaDefinition(schema) ].concat(
7677
directives.map(printDirective),
7778
types.map(printType)
78-
).join('\n\n') + '\n';
79+
).filter(Boolean).join('\n\n') + '\n';
7980
}
8081

81-
function printSchemaDefinition(schema: GraphQLSchema): string {
82+
function printSchemaDefinition(schema: GraphQLSchema): ?string {
83+
if (isSchemaOfCommonNames(schema)) {
84+
return;
85+
}
86+
8287
const operationTypes = [];
8388

8489
const queryType = schema.getQueryType();
@@ -99,6 +104,37 @@ function printSchemaDefinition(schema: GraphQLSchema): string {
99104
return `schema {\n${operationTypes.join('\n')}\n}`;
100105
}
101106

107+
/**
108+
* GraphQL schema define root types for each type of operation. These types are
109+
* the same as any other type and can be named in any manner, however there is
110+
* a common naming convention:
111+
*
112+
* schema {
113+
* query: Query
114+
* mutation: Mutation
115+
* }
116+
*
117+
* When using this naming convention, the schema description can be omitted.
118+
*/
119+
function isSchemaOfCommonNames(schema: GraphQLSchema): boolean {
120+
const queryType = schema.getQueryType();
121+
if (queryType && queryType.name !== 'Query') {
122+
return false;
123+
}
124+
125+
const mutationType = schema.getMutationType();
126+
if (mutationType && mutationType.name !== 'Mutation') {
127+
return false;
128+
}
129+
130+
const subscriptionType = schema.getSubscriptionType();
131+
if (subscriptionType && subscriptionType.name !== 'Subscription') {
132+
return false;
133+
}
134+
135+
return true;
136+
}
137+
102138
function printType(type: GraphQLType): string {
103139
if (type instanceof GraphQLScalarType) {
104140
return printScalar(type);

0 commit comments

Comments
 (0)