Skip to content

Commit f7c78eb

Browse files
committed
Introduce formal definition of "Thunk" to aid in fixing more issues uncovered by Flow v0.28
1 parent 3083fc5 commit f7c78eb

File tree

7 files changed

+109
-76
lines changed

7 files changed

+109
-76
lines changed

src/execution/execute.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ function resolveField(
594594
// Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField`
595595
// function. Returns the result of resolveFn or the abrupt-return Error object.
596596
function resolveOrError(
597-
resolveFn: GraphQLFieldResolveFn,
597+
resolveFn: GraphQLFieldResolveFn<*>,
598598
source: mixed,
599599
args: { [key: string]: mixed },
600600
context: mixed,

src/type/definition.js

Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,17 @@ export function getNamedType(type: ?GraphQLType): ?GraphQLNamedType {
187187
}
188188

189189

190+
/**
191+
* Used while defining GraphQL types to allow for circular references in
192+
* otherwise immutable type definitions.
193+
*/
194+
export type Thunk<T> = (() => T) | T;
195+
196+
function resolveThunk<T>(thunk: Thunk<T>): T {
197+
return typeof thunk === 'function' ? thunk() : thunk;
198+
}
199+
200+
190201
/**
191202
* Scalar Type Definition
192203
*
@@ -204,13 +215,13 @@ export function getNamedType(type: ?GraphQLType): ?GraphQLNamedType {
204215
* });
205216
*
206217
*/
207-
export class GraphQLScalarType<InternalType> {
218+
export class GraphQLScalarType<TInternal, TExternal> {
208219
name: string;
209220
description: ?string;
210221

211-
_scalarConfig: GraphQLScalarTypeConfig<InternalType>;
222+
_scalarConfig: GraphQLScalarTypeConfig<TInternal, TExternal>;
212223

213-
constructor(config: GraphQLScalarTypeConfig<InternalType>) {
224+
constructor(config: GraphQLScalarTypeConfig<TInternal, TExternal>) {
214225
invariant(config.name, 'Type must be named.');
215226
assertValidName(config.name);
216227
this.name = config.name;
@@ -232,17 +243,17 @@ export class GraphQLScalarType<InternalType> {
232243
this._scalarConfig = config;
233244
}
234245

235-
serialize(value: InternalType): mixed {
246+
serialize(value: TInternal): ?TExternal {
236247
const serializer = this._scalarConfig.serialize;
237248
return serializer(value);
238249
}
239250

240-
parseValue(value: mixed): ?InternalType {
251+
parseValue(value: TExternal): ?TInternal {
241252
const parser = this._scalarConfig.parseValue;
242253
return parser ? parser(value) : null;
243254
}
244255

245-
parseLiteral(valueAST: Value): ?InternalType {
256+
parseLiteral(valueAST: Value): ?TInternal {
246257
const parser = this._scalarConfig.parseLiteral;
247258
return parser ? parser(valueAST) : null;
248259
}
@@ -252,12 +263,12 @@ export class GraphQLScalarType<InternalType> {
252263
}
253264
}
254265

255-
export type GraphQLScalarTypeConfig<InternalType> = {
266+
export type GraphQLScalarTypeConfig<TInternal, TExternal> = {
256267
name: string;
257268
description?: ?string;
258-
serialize: (value: mixed) => ?InternalType;
259-
parseValue?: (value: mixed) => ?InternalType;
260-
parseLiteral?: (valueAST: Value) => ?InternalType;
269+
serialize: (value: TInternal) => ?TExternal;
270+
parseValue?: (value: TExternal) => ?TInternal;
271+
parseLiteral?: (valueAST: Value) => ?TInternal;
261272
}
262273

263274

@@ -304,11 +315,11 @@ export class GraphQLObjectType {
304315
description: ?string;
305316
isTypeOf: ?GraphQLIsTypeOfFn;
306317

307-
_typeConfig: GraphQLObjectTypeConfig;
318+
_typeConfig: GraphQLObjectTypeConfig<*>;
308319
_fields: GraphQLFieldDefinitionMap;
309320
_interfaces: Array<GraphQLInterfaceType>;
310321

311-
constructor(config: GraphQLObjectTypeConfig) {
322+
constructor(config: GraphQLObjectTypeConfig<*>) {
312323
invariant(config.name, 'Type must be named.');
313324
assertValidName(config.name);
314325
this.name = config.name;
@@ -340,15 +351,11 @@ export class GraphQLObjectType {
340351
}
341352
}
342353

343-
function resolveMaybeThunk<T>(thingOrThunk: T | () => T): T {
344-
return typeof thingOrThunk === 'function' ? thingOrThunk() : thingOrThunk;
345-
}
346-
347354
function defineInterfaces(
348355
type: GraphQLObjectType,
349-
interfacesOrThunk: Array<GraphQLInterfaceType> | ?GraphQLInterfacesThunk
356+
interfacesThunk: Thunk<?Array<GraphQLInterfaceType>>
350357
): Array<GraphQLInterfaceType> {
351-
const interfaces = resolveMaybeThunk(interfacesOrThunk);
358+
const interfaces = resolveThunk(interfacesThunk);
352359
if (!interfaces) {
353360
return [];
354361
}
@@ -378,9 +385,9 @@ function defineInterfaces(
378385

379386
function defineFieldMap(
380387
type: GraphQLNamedType,
381-
fields: GraphQLFieldConfigMap | GraphQLFieldConfigMapThunk
388+
fieldsThunk: Thunk<GraphQLFieldConfigMap<*>>
382389
): GraphQLFieldDefinitionMap {
383-
const fieldMap: any = resolveMaybeThunk(fields);
390+
const fieldMap = resolveThunk(fieldsThunk);
384391
invariant(
385392
isPlainObj(fieldMap),
386393
`${type.name} fields must be an object with field names as keys or a ` +
@@ -397,8 +404,9 @@ function defineFieldMap(
397404
const resultFieldMap = {};
398405
fieldNames.forEach(fieldName => {
399406
assertValidName(fieldName);
407+
const fieldConfig = fieldMap[fieldName];
400408
const field = {
401-
...fieldMap[fieldName],
409+
...fieldConfig,
402410
name: fieldName
403411
};
404412
invariant(
@@ -411,17 +419,18 @@ function defineFieldMap(
411419
`${type.name}.${fieldName} field type must be Output Type but ` +
412420
`got: ${String(field.type)}.`
413421
);
414-
if (!field.args) {
422+
const argsConfig = fieldConfig.args;
423+
if (!argsConfig) {
415424
field.args = [];
416425
} else {
417426
invariant(
418-
isPlainObj(field.args),
427+
isPlainObj(argsConfig),
419428
`${type.name}.${fieldName} args must be an object with argument ` +
420429
'names as keys.'
421430
);
422-
field.args = Object.keys(field.args).map(argName => {
431+
field.args = Object.keys(argsConfig).map(argName => {
423432
assertValidName(argName);
424-
const arg = field.args[argName];
433+
const arg = argsConfig[argName];
425434
invariant(
426435
isInputType(arg.type),
427436
`${type.name}.${fieldName}(${argName}:) argument type must be ` +
@@ -444,32 +453,28 @@ function isPlainObj(obj) {
444453
return obj && typeof obj === 'object' && !Array.isArray(obj);
445454
}
446455

447-
export type GraphQLObjectTypeConfig = {
456+
export type GraphQLObjectTypeConfig<TSource> = {
448457
name: string;
449-
interfaces?: GraphQLInterfacesThunk | Array<GraphQLInterfaceType>;
450-
fields: GraphQLFieldConfigMapThunk | GraphQLFieldConfigMap;
451-
isTypeOf?: GraphQLIsTypeOfFn;
458+
interfaces?: Thunk<?Array<GraphQLInterfaceType>>;
459+
fields: Thunk<GraphQLFieldConfigMap<TSource>>;
460+
isTypeOf?: ?GraphQLIsTypeOfFn;
452461
description?: ?string
453462
}
454463

455-
type GraphQLInterfacesThunk = () => Array<GraphQLInterfaceType>;
456-
457-
type GraphQLFieldConfigMapThunk = () => GraphQLFieldConfigMap;
458-
459464
export type GraphQLTypeResolveFn = (
460465
value: mixed,
461466
context: mixed,
462467
info: GraphQLResolveInfo
463468
) => ?GraphQLObjectType
464469

465470
export type GraphQLIsTypeOfFn = (
466-
value: mixed,
471+
source: mixed,
467472
context: mixed,
468473
info: GraphQLResolveInfo
469474
) => boolean
470475

471-
export type GraphQLFieldResolveFn = (
472-
source: mixed,
476+
export type GraphQLFieldResolveFn<TSource> = (
477+
source: TSource,
473478
args: {[argName: string]: mixed},
474479
context: mixed,
475480
info: GraphQLResolveInfo
@@ -488,10 +493,10 @@ export type GraphQLResolveInfo = {
488493
variableValues: { [variableName: string]: mixed };
489494
}
490495

491-
export type GraphQLFieldConfig = {
496+
export type GraphQLFieldConfig<TSource> = {
492497
type: GraphQLOutputType;
493498
args?: GraphQLFieldConfigArgumentMap;
494-
resolve?: GraphQLFieldResolveFn;
499+
resolve?: GraphQLFieldResolveFn<TSource>;
495500
deprecationReason?: ?string;
496501
description?: ?string;
497502
}
@@ -506,16 +511,16 @@ export type GraphQLArgumentConfig = {
506511
description?: ?string;
507512
}
508513

509-
export type GraphQLFieldConfigMap = {
510-
[fieldName: string]: GraphQLFieldConfig;
514+
export type GraphQLFieldConfigMap<TSource> = {
515+
[fieldName: string]: GraphQLFieldConfig<TSource>;
511516
};
512517

513518
export type GraphQLFieldDefinition = {
514519
name: string;
515520
description: ?string;
516521
type: GraphQLOutputType;
517522
args: Array<GraphQLArgument>;
518-
resolve?: GraphQLFieldResolveFn;
523+
resolve?: GraphQLFieldResolveFn<*>;
519524
deprecationReason?: ?string;
520525
}
521526

@@ -585,13 +590,13 @@ export class GraphQLInterfaceType {
585590

586591
export type GraphQLInterfaceTypeConfig = {
587592
name: string,
588-
fields: GraphQLFieldConfigMapThunk | GraphQLFieldConfigMap,
593+
fields: Thunk<GraphQLFieldConfigMap<mixed>>,
589594
/**
590595
* Optionally provide a custom type resolver function. If one is not provided,
591596
* the default implementation will call `isTypeOf` on each implementing
592597
* Object type.
593598
*/
594-
resolveType?: GraphQLTypeResolveFn,
599+
resolveType?: ?GraphQLTypeResolveFn,
595600
description?: ?string
596601
};
597602

@@ -880,7 +885,7 @@ export class GraphQLInputObjectType {
880885
}
881886

882887
_defineFieldMap(): InputObjectFieldMap {
883-
const fieldMap: any = resolveMaybeThunk(this._typeConfig.fields);
888+
const fieldMap: any = resolveThunk(this._typeConfig.fields);
884889
invariant(
885890
isPlainObj(fieldMap),
886891
`${this.name} fields must be an object with field names as keys or a ` +
@@ -916,12 +921,10 @@ export class GraphQLInputObjectType {
916921

917922
export type InputObjectConfig = {
918923
name: string;
919-
fields: InputObjectConfigFieldMapThunk | InputObjectConfigFieldMap;
924+
fields: Thunk<InputObjectConfigFieldMap>;
920925
description?: ?string;
921926
}
922927

923-
export type InputObjectConfigFieldMapThunk = () => InputObjectConfigFieldMap;
924-
925928
export type InputObjectFieldConfig = {
926929
type: GraphQLInputType;
927930
defaultValue?: mixed;

src/type/scalars.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@ import { Kind } from '../language';
1919
const MAX_INT = 2147483647;
2020
const MIN_INT = -2147483648;
2121

22-
function coerceInt(value) {
22+
function coerceInt(value: number): ?number {
2323
const num = Number(value);
2424
if (num === num && num <= MAX_INT && num >= MIN_INT) {
2525
return (num < 0 ? Math.ceil : Math.floor)(num);
2626
}
2727
return null;
28-
2928
}
3029

3130
export const GraphQLInt = new GraphQLScalarType({
@@ -46,7 +45,7 @@ export const GraphQLInt = new GraphQLScalarType({
4645
}
4746
});
4847

49-
function coerceFloat(value) {
48+
function coerceFloat(value: number): ?number {
5049
const num = Number(value);
5150
return num === num ? num : null;
5251
}

src/utilities/buildASTSchema.js

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,6 @@ import type {
9292
} from '../type/definition';
9393

9494

95-
type CompositeDefinition =
96-
ObjectTypeDefinition |
97-
InterfaceTypeDefinition |
98-
UnionTypeDefinition;
99-
10095
function buildWrappedType(
10196
innerType: GraphQLType,
10297
inputTypeAST: Type
@@ -259,9 +254,9 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
259254
return new GraphQLDirective({
260255
name: directiveAST.name.value,
261256
locations: directiveAST.locations.map(
262-
node => (node.value: DirectiveLocationEnum)
257+
node => ((node.value: any): DirectiveLocationEnum)
263258
),
264-
args: makeInputValues(directiveAST.arguments),
259+
args: directiveAST.arguments && makeInputValues(directiveAST.arguments),
265260
});
266261
}
267262

@@ -274,12 +269,24 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
274269
return (type: any);
275270
}
276271

277-
function produceTypeDef(typeAST: Type): GraphQLType {
272+
function produceType(typeAST: Type): GraphQLType {
278273
const typeName = getNamedTypeAST(typeAST).name.value;
279274
const typeDef = typeDefNamed(typeName);
280275
return buildWrappedType(typeDef, typeAST);
281276
}
282277

278+
function produceObjectType(typeAST: Type): GraphQLObjectType {
279+
const type = produceType(typeAST);
280+
invariant(type instanceof GraphQLObjectType, 'Expected Object type.');
281+
return type;
282+
}
283+
284+
function produceInterfaceType(typeAST: Type): GraphQLInterfaceType {
285+
const type = produceType(typeAST);
286+
invariant(type instanceof GraphQLInterfaceType, 'Expected Object type.');
287+
return type;
288+
}
289+
283290
function typeDefNamed(typeName: string): GraphQLNamedType {
284291
if (innerTypeMap[typeName]) {
285292
return innerTypeMap[typeName];
@@ -329,28 +336,31 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
329336
return new GraphQLObjectType(config);
330337
}
331338

332-
function makeFieldDefMap(def: CompositeDefinition) {
339+
function makeFieldDefMap(
340+
def: ObjectTypeDefinition | InterfaceTypeDefinition
341+
) {
333342
return keyValMap(
334343
def.fields,
335344
field => field.name.value,
336345
field => ({
337-
type: produceTypeDef(field.type),
346+
type: produceType(field.type),
338347
args: makeInputValues(field.arguments),
339348
deprecationReason: getDeprecationReason(field.directives)
340349
})
341350
);
342351
}
343352

344353
function makeImplementedInterfaces(def: ObjectTypeDefinition) {
345-
return def.interfaces.map(inter => produceTypeDef(inter));
354+
return def.interfaces &&
355+
def.interfaces.map(iface => produceInterfaceType(iface));
346356
}
347357

348358
function makeInputValues(values: Array<InputValueDefinition>) {
349359
return keyValMap(
350360
values,
351361
value => value.name.value,
352362
value => {
353-
const type = produceTypeDef(value.type);
363+
const type = produceType(value.type);
354364
return { type, defaultValue: valueFromAST(value.defaultValue, type) };
355365
}
356366
);
@@ -385,7 +395,7 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
385395
return new GraphQLUnionType({
386396
name: def.name.value,
387397
resolveType: () => null,
388-
types: def.types.map(t => produceTypeDef(t)),
398+
types: def.types.map(t => produceObjectType(t)),
389399
});
390400
}
391401

src/utilities/buildClientSchema.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ export function buildClientSchema(
222222

223223
function buildScalarDef(
224224
scalarIntrospection: IntrospectionScalarType
225-
): GraphQLScalarType<boolean> {
225+
): GraphQLScalarType<boolean, void> {
226226
return new GraphQLScalarType({
227227
name: scalarIntrospection.name,
228228
description: scalarIntrospection.description,

0 commit comments

Comments
 (0)