Skip to content

Commit 67162e5

Browse files
committed
Fix memory leak in buildSchema/extendSchema
1 parent d3020b1 commit 67162e5

File tree

2 files changed

+72
-70
lines changed

2 files changed

+72
-70
lines changed

.eslintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"rules": {
4343
"prettier/prettier": 2,
4444

45-
"flowtype/space-after-type-colon": [2, "always"],
45+
"flowtype/space-after-type-colon": 0,
4646
"flowtype/space-before-type-colon": [2, "never"],
4747
"flowtype/space-before-generic-bracket": [2, "never"],
4848
"flowtype/union-intersection-spacing": [2, "always"],

src/type/definition.js

Lines changed: 71 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -649,17 +649,17 @@ export class GraphQLObjectType {
649649
extensionASTNodes: ?$ReadOnlyArray<ObjectTypeExtensionNode>;
650650
isTypeOf: ?GraphQLIsTypeOfFn<*, *>;
651651

652-
_typeConfig: GraphQLObjectTypeConfig<*, *>;
653-
_fields: GraphQLFieldMap<*, *>;
654-
_interfaces: Array<GraphQLInterfaceType>;
652+
_fields: Thunk<GraphQLFieldMap<*, *>>;
653+
_interfaces: Thunk<Array<GraphQLInterfaceType>>;
655654

656655
constructor(config: GraphQLObjectTypeConfig<*, *>): void {
657656
this.name = config.name;
658657
this.description = config.description;
659658
this.astNode = config.astNode;
660659
this.extensionASTNodes = config.extensionASTNodes;
661660
this.isTypeOf = config.isTypeOf;
662-
this._typeConfig = config;
661+
this._fields = defineFieldMap.bind(undefined, config);
662+
this._interfaces = defineInterfaces.bind(undefined, config);
663663
invariant(typeof config.name === 'string', 'Must provide name.');
664664
if (config.isTypeOf) {
665665
invariant(
@@ -670,17 +670,17 @@ export class GraphQLObjectType {
670670
}
671671

672672
getFields(): GraphQLFieldMap<*, *> {
673-
return (
674-
this._fields ||
675-
(this._fields = defineFieldMap(this, this._typeConfig.fields))
676-
);
673+
if (typeof this._fields === 'function') {
674+
this._fields = this._fields();
675+
}
676+
return this._fields;
677677
}
678678

679679
getInterfaces(): Array<GraphQLInterfaceType> {
680-
return (
681-
this._interfaces ||
682-
(this._interfaces = defineInterfaces(this, this._typeConfig.interfaces))
683-
);
680+
if (typeof this._interfaces === 'function') {
681+
this._interfaces = this._interfaces();
682+
}
683+
return this._interfaces;
684684
}
685685

686686
toString(): string {
@@ -693,26 +693,26 @@ defineToStringTag(GraphQLObjectType);
693693
defineToJSON(GraphQLObjectType);
694694

695695
function defineInterfaces(
696-
type: GraphQLObjectType,
697-
interfacesThunk: Thunk<?Array<GraphQLInterfaceType>>,
696+
config: GraphQLObjectTypeConfig<*, *>,
698697
): Array<GraphQLInterfaceType> {
699-
const interfaces = resolveThunk(interfacesThunk) || [];
698+
const interfaces = resolveThunk(config.interfaces) || [];
700699
invariant(
701700
Array.isArray(interfaces),
702-
`${type.name} interfaces must be an Array or a function which returns ` +
701+
`${config.name} interfaces must be an Array or a function which returns ` +
703702
'an Array.',
704703
);
705704
return interfaces;
706705
}
707706

708707
function defineFieldMap<TSource, TContext>(
709-
type: GraphQLNamedType,
710-
fieldsThunk: Thunk<GraphQLFieldConfigMap<TSource, TContext>>,
708+
config:
709+
| GraphQLObjectTypeConfig<TSource, TContext>
710+
| GraphQLInterfaceTypeConfig<TSource, TContext>,
711711
): GraphQLFieldMap<TSource, TContext> {
712-
const fieldMap = resolveThunk(fieldsThunk) || {};
712+
const fieldMap = resolveThunk(config.fields) || {};
713713
invariant(
714714
isPlainObj(fieldMap),
715-
`${type.name} fields must be an object with field names as keys or a ` +
715+
`${config.name} fields must be an object with field names as keys or a ` +
716716
'function which returns such an object.',
717717
);
718718

@@ -721,12 +721,12 @@ function defineFieldMap<TSource, TContext>(
721721
const fieldConfig = fieldMap[fieldName];
722722
invariant(
723723
isPlainObj(fieldConfig),
724-
`${type.name}.${fieldName} field config must be an object`,
724+
`${config.name}.${fieldName} field config must be an object`,
725725
);
726726
invariant(
727727
!fieldConfig.hasOwnProperty('isDeprecated'),
728-
`${type.name}.${fieldName} should provide "deprecationReason" instead ` +
729-
'of "isDeprecated".',
728+
`${config.name}.${fieldName} should provide "deprecationReason" ` +
729+
'instead of "isDeprecated".',
730730
);
731731
const field = {
732732
...fieldConfig,
@@ -735,7 +735,7 @@ function defineFieldMap<TSource, TContext>(
735735
};
736736
invariant(
737737
isValidResolver(field.resolve),
738-
`${type.name}.${fieldName} field resolver must be a function if ` +
738+
`${config.name}.${fieldName} field resolver must be a function if ` +
739739
`provided, but got: ${inspect(field.resolve)}.`,
740740
);
741741
const argsConfig = fieldConfig.args;
@@ -744,7 +744,7 @@ function defineFieldMap<TSource, TContext>(
744744
} else {
745745
invariant(
746746
isPlainObj(argsConfig),
747-
`${type.name}.${fieldName} args must be an object with argument ` +
747+
`${config.name}.${fieldName} args must be an object with argument ` +
748748
'names as keys.',
749749
);
750750
field.args = Object.keys(argsConfig).map(argName => {
@@ -903,16 +903,15 @@ export class GraphQLInterfaceType {
903903
extensionASTNodes: ?$ReadOnlyArray<InterfaceTypeExtensionNode>;
904904
resolveType: ?GraphQLTypeResolver<*, *>;
905905

906-
_typeConfig: GraphQLInterfaceTypeConfig<*, *>;
907-
_fields: GraphQLFieldMap<*, *>;
906+
_fields: Thunk<GraphQLFieldMap<*, *>>;
908907

909908
constructor(config: GraphQLInterfaceTypeConfig<*, *>): void {
910909
this.name = config.name;
911910
this.description = config.description;
912911
this.astNode = config.astNode;
913912
this.extensionASTNodes = config.extensionASTNodes;
914913
this.resolveType = config.resolveType;
915-
this._typeConfig = config;
914+
this._fields = defineFieldMap.bind(undefined, config);
916915
invariant(typeof config.name === 'string', 'Must provide name.');
917916
if (config.resolveType) {
918917
invariant(
@@ -923,10 +922,10 @@ export class GraphQLInterfaceType {
923922
}
924923

925924
getFields(): GraphQLFieldMap<*, *> {
926-
return (
927-
this._fields ||
928-
(this._fields = defineFieldMap(this, this._typeConfig.fields))
929-
);
925+
if (typeof this._fields === 'function') {
926+
this._fields = this._fields();
927+
}
928+
return this._fields;
930929
}
931930

932931
toString(): string {
@@ -982,16 +981,15 @@ export class GraphQLUnionType {
982981
extensionASTNodes: ?$ReadOnlyArray<UnionTypeExtensionNode>;
983982
resolveType: ?GraphQLTypeResolver<*, *>;
984983

985-
_typeConfig: GraphQLUnionTypeConfig<*, *>;
986-
_types: Array<GraphQLObjectType>;
984+
_types: Thunk<Array<GraphQLObjectType>>;
987985

988986
constructor(config: GraphQLUnionTypeConfig<*, *>): void {
989987
this.name = config.name;
990988
this.description = config.description;
991989
this.astNode = config.astNode;
992990
this.extensionASTNodes = config.extensionASTNodes;
993991
this.resolveType = config.resolveType;
994-
this._typeConfig = config;
992+
this._types = defineTypes.bind(undefined, config);
995993
invariant(typeof config.name === 'string', 'Must provide name.');
996994
if (config.resolveType) {
997995
invariant(
@@ -1002,9 +1000,10 @@ export class GraphQLUnionType {
10021000
}
10031001

10041002
getTypes(): Array<GraphQLObjectType> {
1005-
return (
1006-
this._types || (this._types = defineTypes(this, this._typeConfig.types))
1007-
);
1003+
if (typeof this._types === 'function') {
1004+
this._types = this._types();
1005+
}
1006+
return this._types;
10081007
}
10091008

10101009
toString(): string {
@@ -1017,14 +1016,13 @@ defineToStringTag(GraphQLUnionType);
10171016
defineToJSON(GraphQLUnionType);
10181017

10191018
function defineTypes(
1020-
unionType: GraphQLUnionType,
1021-
typesThunk: Thunk<Array<GraphQLObjectType>>,
1019+
config: GraphQLUnionTypeConfig<*, *>,
10221020
): Array<GraphQLObjectType> {
1023-
const types = resolveThunk(typesThunk) || [];
1021+
const types = resolveThunk(config.types) || [];
10241022
invariant(
10251023
Array.isArray(types),
10261024
'Must provide Array of types or a function which returns ' +
1027-
`such an array for Union ${unionType.name}.`,
1025+
`such an array for Union ${config.name}.`,
10281026
);
10291027
return types;
10301028
}
@@ -1216,43 +1214,22 @@ export class GraphQLInputObjectType {
12161214
astNode: ?InputObjectTypeDefinitionNode;
12171215
extensionASTNodes: ?$ReadOnlyArray<InputObjectTypeExtensionNode>;
12181216

1219-
_typeConfig: GraphQLInputObjectTypeConfig;
1220-
_fields: GraphQLInputFieldMap;
1217+
_fields: Thunk<GraphQLInputFieldMap>;
12211218

12221219
constructor(config: GraphQLInputObjectTypeConfig): void {
12231220
this.name = config.name;
12241221
this.description = config.description;
12251222
this.astNode = config.astNode;
12261223
this.extensionASTNodes = config.extensionASTNodes;
1227-
this._typeConfig = config;
1224+
this._fields = defineInputFieldMap.bind(undefined, config);
12281225
invariant(typeof config.name === 'string', 'Must provide name.');
12291226
}
12301227

12311228
getFields(): GraphQLInputFieldMap {
1232-
return this._fields || (this._fields = this._defineFieldMap());
1233-
}
1234-
1235-
_defineFieldMap(): GraphQLInputFieldMap {
1236-
const fieldMap: any = resolveThunk(this._typeConfig.fields) || {};
1237-
invariant(
1238-
isPlainObj(fieldMap),
1239-
`${this.name} fields must be an object with field names as keys or a ` +
1240-
'function which returns such an object.',
1241-
);
1242-
const resultFieldMap = Object.create(null);
1243-
Object.keys(fieldMap).forEach(fieldName => {
1244-
const field = {
1245-
...fieldMap[fieldName],
1246-
name: fieldName,
1247-
};
1248-
invariant(
1249-
!field.hasOwnProperty('resolve'),
1250-
`${this.name}.${fieldName} field type has a resolve property, but ` +
1251-
'Input Types cannot define resolvers.',
1252-
);
1253-
resultFieldMap[fieldName] = field;
1254-
});
1255-
return resultFieldMap;
1229+
if (typeof this._fields === 'function') {
1230+
this._fields = this._fields();
1231+
}
1232+
return this._fields;
12561233
}
12571234

12581235
toString(): string {
@@ -1264,6 +1241,31 @@ export class GraphQLInputObjectType {
12641241
defineToStringTag(GraphQLInputObjectType);
12651242
defineToJSON(GraphQLInputObjectType);
12661243

1244+
function defineInputFieldMap(
1245+
config: GraphQLInputObjectTypeConfig,
1246+
): GraphQLInputFieldMap {
1247+
const fieldMap: any = resolveThunk(config.fields) || {};
1248+
invariant(
1249+
isPlainObj(fieldMap),
1250+
`${config.name} fields must be an object with field names as keys or a ` +
1251+
'function which returns such an object.',
1252+
);
1253+
const resultFieldMap = Object.create(null);
1254+
Object.keys(fieldMap).forEach(fieldName => {
1255+
const field = {
1256+
...fieldMap[fieldName],
1257+
name: fieldName,
1258+
};
1259+
invariant(
1260+
!field.hasOwnProperty('resolve'),
1261+
`${config.name}.${fieldName} field type has a resolve property, but ` +
1262+
'Input Types cannot define resolvers.',
1263+
);
1264+
resultFieldMap[fieldName] = field;
1265+
});
1266+
return resultFieldMap;
1267+
}
1268+
12671269
export type GraphQLInputObjectTypeConfig = {|
12681270
name: string,
12691271
fields: Thunk<GraphQLInputFieldConfigMap>,

0 commit comments

Comments
 (0)