Skip to content

Commit 6a1f23e

Browse files
committed
Add GraphQLSchema types field
This is a rebased and updated version of @tgriesser's #199. I further extended it to completely remove the side-effectful mutation of Interface types rather than just deferring that mutation to schema creation time. This introduces a *breaking* change to the Type System API. Now, any individual Interface type does not have the required information to answer `getPossibleTypes` or `isPossibleType` without knowing the other types in the Schema. These methods were moved to the Schema API, accepting the abstract type as the first parameter. This also introduces a *breaking* change to the type comparator functions: `isTypeSubTypeOf` and `doTypesOverlap` which now require a Schema as a first argument.
1 parent a781b55 commit 6a1f23e

23 files changed

+297
-137
lines changed

src/__tests__/starWarsIntrospectionTests.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ describe('Star Wars Introspection Tests', () => {
4040
name: 'Character'
4141
},
4242
{
43-
name: 'Human'
43+
name: 'String'
4444
},
4545
{
46-
name: 'String'
46+
name: 'Human'
4747
},
4848
{
4949
name: 'Droid'

src/__tests__/starWarsSchema.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,5 +270,6 @@ const queryType = new GraphQLObjectType({
270270
* type we defined above) and export it.
271271
*/
272272
export const StarWarsSchema = new GraphQLSchema({
273-
query: queryType
273+
query: queryType,
274+
types: [ humanType, droidType ]
274275
});

src/execution/__tests__/abstract.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,6 @@ describe('Execute: Handles execution of abstract types', () => {
5151
}
5252
});
5353

54-
// Added to interface type when defined
55-
/* eslint-disable no-unused-vars */
56-
5754
const DogType = new GraphQLObjectType({
5855
name: 'Dog',
5956
interfaces: [ PetType ],
@@ -74,8 +71,6 @@ describe('Execute: Handles execution of abstract types', () => {
7471
}
7572
});
7673

77-
/* eslint-enable no-unused-vars */
78-
7974
const schema = new GraphQLSchema({
8075
query: new GraphQLObjectType({
8176
name: 'Query',
@@ -87,7 +82,8 @@ describe('Execute: Handles execution of abstract types', () => {
8782
}
8883
}
8984
}
90-
})
85+
}),
86+
types: [ CatType, DogType ]
9187
});
9288

9389
const query = `{
@@ -231,7 +227,8 @@ describe('Execute: Handles execution of abstract types', () => {
231227
}
232228
}
233229
}
234-
})
230+
}),
231+
types: [ CatType, DogType ]
235232
});
236233

237234
const query = `{

src/execution/__tests__/union-interface.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ const PersonType = new GraphQLObjectType({
9797
});
9898

9999
const schema = new GraphQLSchema({
100-
query: PersonType
100+
query: PersonType,
101+
types: [ PetType ]
101102
});
102103

103104
const garfield = new Cat('Garfield', false);
@@ -143,9 +144,9 @@ describe('Execute: Union and intersection types', () => {
143144
],
144145
interfaces: null,
145146
possibleTypes: [
147+
{ name: 'Person' },
146148
{ name: 'Dog' },
147149
{ name: 'Cat' },
148-
{ name: 'Person' }
149150
],
150151
enumValues: null,
151152
inputFields: null

src/execution/execute.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,8 @@ function doesFragmentConditionMatch(
468468
return true;
469469
}
470470
if (isAbstractType(conditionalType)) {
471-
return ((conditionalType: any): GraphQLAbstractType).isPossibleType(type);
471+
const abstractType = ((conditionalType: any): GraphQLAbstractType);
472+
return exeContext.schema.isPossibleType(abstractType, type);
472473
}
473474
return false;
474475
}
@@ -806,7 +807,8 @@ function completeAbstractValue(
806807
return null;
807808
}
808809

809-
if (runtimeType && !returnType.isPossibleType(runtimeType)) {
810+
const schema = exeContext.schema;
811+
if (runtimeType && !schema.isPossibleType(returnType, runtimeType)) {
810812
throw new GraphQLError(
811813
`Runtime Object type "${runtimeType}" is not a possible type ` +
812814
`for "${returnType}".`,
@@ -872,7 +874,7 @@ function defaultResolveTypeFn(
872874
info: GraphQLResolveInfo,
873875
abstractType: GraphQLAbstractType
874876
): ?GraphQLObjectType {
875-
const possibleTypes = abstractType.getPossibleTypes();
877+
const possibleTypes = info.schema.getPossibleTypes(abstractType);
876878
for (let i = 0; i < possibleTypes.length; i++) {
877879
const type = possibleTypes[i];
878880
if (typeof type.isTypeOf === 'function' && type.isTypeOf(value, info)) {

src/type/__tests__/definition.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@ describe('Type System: Example', () => {
227227
fields: {
228228
iface: { type: SomeInterface }
229229
}
230-
})
230+
}),
231+
types: [ SomeSubtype ]
231232
});
232233

233234
expect(schema.getTypeMap().SomeSubtype).to.equal(SomeSubtype);
@@ -256,7 +257,8 @@ describe('Type System: Example', () => {
256257
fields: {
257258
iface: { type: SomeInterface }
258259
}
259-
})
260+
}),
261+
types: [ SomeSubtype ]
260262
});
261263

262264
expect(schema.getTypeMap().SomeSubtype).to.equal(SomeSubtype);

src/type/__tests__/validation.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ function schemaWithFieldType(type) {
108108
query: new GraphQLObjectType({
109109
name: 'Query',
110110
fields: { f: { type } }
111-
})
111+
}),
112+
types: [ type ],
112113
});
113114
}
114115

@@ -261,32 +262,29 @@ describe('Type System: A Schema must contain uniquely named types', () => {
261262
fields: { f: { type: GraphQLString } },
262263
});
263264

264-
/* eslint-disable no-unused-vars */
265-
266-
// Automatically included in Interface
267265
const FirstBadObject = new GraphQLObjectType({
268266
name: 'BadObject',
269267
interfaces: [ AnotherInterface ],
270268
fields: { f: { type: GraphQLString } },
271269
});
272270

273-
// Automatically included in Interface
274271
const SecondBadObject = new GraphQLObjectType({
275272
name: 'BadObject',
276273
interfaces: [ AnotherInterface ],
277274
fields: { f: { type: GraphQLString } },
278275
});
279276

280-
/* eslint-enable no-unused-vars */
281-
282277
const QueryType = new GraphQLObjectType({
283278
name: 'Query',
284279
fields: {
285280
iface: { type: AnotherInterface },
286281
}
287282
});
288283

289-
return new GraphQLSchema({ query: QueryType });
284+
return new GraphQLSchema({
285+
query: QueryType,
286+
types: [ FirstBadObject, SecondBadObject ]
287+
});
290288
}).to.throw(
291289
'Schema must contain unique named types but contains multiple types ' +
292290
'named "BadObject".'
@@ -1114,7 +1112,8 @@ describe('Type System: Objects can only implement interfaces', () => {
11141112
fields: {
11151113
f: { type: BadObjectType }
11161114
}
1117-
})
1115+
}),
1116+
types: [ BadObjectType ]
11181117
});
11191118
}
11201119

src/type/definition.js

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
import invariant from '../jsutils/invariant';
1212
import isNullish from '../jsutils/isNullish';
13-
import keyMap from '../jsutils/keyMap';
1413
import { ENUM } from '../language/kinds';
1514
import { assertValidName } from '../utilities/assertValidName';
1615
import type {
@@ -321,7 +320,6 @@ export class GraphQLObjectType {
321320
}
322321
this.isTypeOf = config.isTypeOf;
323322
this._typeConfig = config;
324-
addImplementationToInterfaces(this);
325323
}
326324

327325
getFields(): GraphQLFieldDefinitionMap {
@@ -444,18 +442,6 @@ function isPlainObj(obj) {
444442
return obj && typeof obj === 'object' && !Array.isArray(obj);
445443
}
446444

447-
/**
448-
* Update the interfaces to know about this implementation.
449-
* This is an rare and unfortunate use of mutation in the type definition
450-
* implementations, but avoids an expensive "getPossibleTypes"
451-
* implementation for Interface types.
452-
*/
453-
function addImplementationToInterfaces(impl) {
454-
impl.getInterfaces().forEach(type => {
455-
type._implementations.push(impl);
456-
});
457-
}
458-
459445
export type GraphQLObjectTypeConfig = {
460446
name: string;
461447
interfaces?: GraphQLInterfacesThunk | Array<GraphQLInterfaceType>;
@@ -565,8 +551,6 @@ export class GraphQLInterfaceType {
565551

566552
_typeConfig: GraphQLInterfaceTypeConfig;
567553
_fields: GraphQLFieldDefinitionMap;
568-
_implementations: Array<GraphQLObjectType>;
569-
_possibleTypes: { [typeName: string]: GraphQLObjectType };
570554

571555
constructor(config: GraphQLInterfaceTypeConfig) {
572556
invariant(config.name, 'Type must be named.');
@@ -581,25 +565,13 @@ export class GraphQLInterfaceType {
581565
}
582566
this.resolveType = config.resolveType;
583567
this._typeConfig = config;
584-
this._implementations = [];
585568
}
586569

587570
getFields(): GraphQLFieldDefinitionMap {
588571
return this._fields ||
589572
(this._fields = defineFieldMap(this, this._typeConfig.fields));
590573
}
591574

592-
getPossibleTypes(): Array<GraphQLObjectType> {
593-
return this._implementations;
594-
}
595-
596-
isPossibleType(type: GraphQLObjectType): boolean {
597-
const possibleTypes = this._possibleTypes || (this._possibleTypes =
598-
keyMap(this.getPossibleTypes(), possibleType => possibleType.name)
599-
);
600-
return Boolean(possibleTypes[type.name]);
601-
}
602-
603575
toString(): string {
604576
return this.name;
605577
}
@@ -686,22 +658,10 @@ export class GraphQLUnionType {
686658
this._typeConfig = config;
687659
}
688660

689-
getPossibleTypes(): Array<GraphQLObjectType> {
661+
getTypes(): Array<GraphQLObjectType> {
690662
return this._types;
691663
}
692664

693-
isPossibleType(type: GraphQLObjectType): boolean {
694-
let possibleTypeNames = this._possibleTypeNames;
695-
if (!possibleTypeNames) {
696-
this._possibleTypeNames = possibleTypeNames =
697-
this.getPossibleTypes().reduce(
698-
(map, possibleType) => ((map[possibleType.name] = true), map),
699-
{}
700-
);
701-
}
702-
return possibleTypeNames[type.name] === true;
703-
}
704-
705665
toString(): string {
706666
return this.name;
707667
}

src/type/introspection.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const __Schema = new GraphQLObjectType({
6767
})
6868
});
6969

70-
const __Directive = new GraphQLObjectType({
70+
export const __Directive = new GraphQLObjectType({
7171
name: '__Directive',
7272
description:
7373
'A Directive provides a way to describe alternate runtime execution and ' +
@@ -115,7 +115,7 @@ const __Directive = new GraphQLObjectType({
115115
}),
116116
});
117117

118-
const __DirectiveLocation = new GraphQLEnumType({
118+
export const __DirectiveLocation = new GraphQLEnumType({
119119
name: '__DirectiveLocation',
120120
description:
121121
'A Directive can be adjacent to many parts of the GraphQL language, a ' +
@@ -152,7 +152,7 @@ const __DirectiveLocation = new GraphQLEnumType({
152152
}
153153
});
154154

155-
const __Type = new GraphQLObjectType({
155+
export const __Type = new GraphQLObjectType({
156156
name: '__Type',
157157
description:
158158
'The fundamental unit of any GraphQL Schema is the type. There are ' +
@@ -218,10 +218,10 @@ const __Type = new GraphQLObjectType({
218218
},
219219
possibleTypes: {
220220
type: new GraphQLList(new GraphQLNonNull(__Type)),
221-
resolve(type) {
221+
resolve(type, args, { schema }) {
222222
if (type instanceof GraphQLInterfaceType ||
223223
type instanceof GraphQLUnionType) {
224-
return type.getPossibleTypes();
224+
return schema.getPossibleTypes(type);
225225
}
226226
}
227227
},
@@ -253,7 +253,7 @@ const __Type = new GraphQLObjectType({
253253
})
254254
});
255255

256-
const __Field = new GraphQLObjectType({
256+
export const __Field = new GraphQLObjectType({
257257
name: '__Field',
258258
description:
259259
'Object and Interface types are described by a list of Fields, each of ' +
@@ -277,7 +277,7 @@ const __Field = new GraphQLObjectType({
277277
})
278278
});
279279

280-
const __InputValue = new GraphQLObjectType({
280+
export const __InputValue = new GraphQLObjectType({
281281
name: '__InputValue',
282282
description:
283283
'Arguments provided to Fields or Directives and the input fields of an ' +
@@ -299,7 +299,7 @@ const __InputValue = new GraphQLObjectType({
299299
})
300300
});
301301

302-
const __EnumValue = new GraphQLObjectType({
302+
export const __EnumValue = new GraphQLObjectType({
303303
name: '__EnumValue',
304304
description:
305305
'One possible value for a given Enum. Enum values are unique values, not ' +
@@ -329,7 +329,7 @@ export const TypeKind = {
329329
NON_NULL: 'NON_NULL',
330330
};
331331

332-
const __TypeKind = new GraphQLEnumType({
332+
export const __TypeKind = new GraphQLEnumType({
333333
name: '__TypeKind',
334334
description: 'An enum describing what kind of type a given `__Type` is.',
335335
values: {

0 commit comments

Comments
 (0)