Skip to content

Commit 4124b61

Browse files
IvanGoncharovmjmahone
authored andcommitted
Fix memory leak in buildSchema/extendSchema (#1417)
1 parent 508760e commit 4124b61

File tree

1 file changed

+71
-69
lines changed

1 file changed

+71
-69
lines changed

src/type/definition.js

+71-69
Original file line numberDiff line numberDiff line change
@@ -639,17 +639,17 @@ export class GraphQLObjectType {
639639
extensionASTNodes: ?$ReadOnlyArray<ObjectTypeExtensionNode>;
640640
isTypeOf: ?GraphQLIsTypeOfFn<*, *>;
641641

642-
_typeConfig: GraphQLObjectTypeConfig<*, *>;
643-
_fields: GraphQLFieldMap<*, *>;
644-
_interfaces: Array<GraphQLInterfaceType>;
642+
_fields: Thunk<GraphQLFieldMap<*, *>>;
643+
_interfaces: Thunk<Array<GraphQLInterfaceType>>;
645644

646645
constructor(config: GraphQLObjectTypeConfig<*, *>): void {
647646
this.name = config.name;
648647
this.description = config.description;
649648
this.astNode = config.astNode;
650649
this.extensionASTNodes = config.extensionASTNodes;
651650
this.isTypeOf = config.isTypeOf;
652-
this._typeConfig = config;
651+
this._fields = defineFieldMap.bind(undefined, config);
652+
this._interfaces = defineInterfaces.bind(undefined, config);
653653
invariant(typeof config.name === 'string', 'Must provide name.');
654654
if (config.isTypeOf) {
655655
invariant(
@@ -660,17 +660,17 @@ export class GraphQLObjectType {
660660
}
661661

662662
getFields(): GraphQLFieldMap<*, *> {
663-
return (
664-
this._fields ||
665-
(this._fields = defineFieldMap(this, this._typeConfig.fields))
666-
);
663+
if (typeof this._fields === 'function') {
664+
this._fields = this._fields();
665+
}
666+
return this._fields;
667667
}
668668

669669
getInterfaces(): Array<GraphQLInterfaceType> {
670-
return (
671-
this._interfaces ||
672-
(this._interfaces = defineInterfaces(this, this._typeConfig.interfaces))
673-
);
670+
if (typeof this._interfaces === 'function') {
671+
this._interfaces = this._interfaces();
672+
}
673+
return this._interfaces;
674674
}
675675

676676
toString(): string {
@@ -683,26 +683,26 @@ defineToStringTag(GraphQLObjectType);
683683
defineToJSON(GraphQLObjectType);
684684

685685
function defineInterfaces(
686-
type: GraphQLObjectType,
687-
interfacesThunk: Thunk<?Array<GraphQLInterfaceType>>,
686+
config: GraphQLObjectTypeConfig<*, *>,
688687
): Array<GraphQLInterfaceType> {
689-
const interfaces = resolveThunk(interfacesThunk) || [];
688+
const interfaces = resolveThunk(config.interfaces) || [];
690689
invariant(
691690
Array.isArray(interfaces),
692-
`${type.name} interfaces must be an Array or a function which returns ` +
691+
`${config.name} interfaces must be an Array or a function which returns ` +
693692
'an Array.',
694693
);
695694
return interfaces;
696695
}
697696

698697
function defineFieldMap<TSource, TContext>(
699-
type: GraphQLNamedType,
700-
fieldsThunk: Thunk<GraphQLFieldConfigMap<TSource, TContext>>,
698+
config:
699+
| GraphQLObjectTypeConfig<TSource, TContext>
700+
| GraphQLInterfaceTypeConfig<TSource, TContext>,
701701
): GraphQLFieldMap<TSource, TContext> {
702-
const fieldMap = resolveThunk(fieldsThunk) || {};
702+
const fieldMap = resolveThunk(config.fields) || {};
703703
invariant(
704704
isPlainObj(fieldMap),
705-
`${type.name} fields must be an object with field names as keys or a ` +
705+
`${config.name} fields must be an object with field names as keys or a ` +
706706
'function which returns such an object.',
707707
);
708708

@@ -711,12 +711,12 @@ function defineFieldMap<TSource, TContext>(
711711
const fieldConfig = fieldMap[fieldName];
712712
invariant(
713713
isPlainObj(fieldConfig),
714-
`${type.name}.${fieldName} field config must be an object`,
714+
`${config.name}.${fieldName} field config must be an object`,
715715
);
716716
invariant(
717717
!fieldConfig.hasOwnProperty('isDeprecated'),
718-
`${type.name}.${fieldName} should provide "deprecationReason" instead ` +
719-
'of "isDeprecated".',
718+
`${config.name}.${fieldName} should provide "deprecationReason" ` +
719+
'instead of "isDeprecated".',
720720
);
721721
const field = {
722722
...fieldConfig,
@@ -725,7 +725,7 @@ function defineFieldMap<TSource, TContext>(
725725
};
726726
invariant(
727727
isValidResolver(field.resolve),
728-
`${type.name}.${fieldName} field resolver must be a function if ` +
728+
`${config.name}.${fieldName} field resolver must be a function if ` +
729729
`provided, but got: ${inspect(field.resolve)}.`,
730730
);
731731
const argsConfig = fieldConfig.args;
@@ -734,7 +734,7 @@ function defineFieldMap<TSource, TContext>(
734734
} else {
735735
invariant(
736736
isPlainObj(argsConfig),
737-
`${type.name}.${fieldName} args must be an object with argument ` +
737+
`${config.name}.${fieldName} args must be an object with argument ` +
738738
'names as keys.',
739739
);
740740
field.args = Object.keys(argsConfig).map(argName => {
@@ -893,16 +893,15 @@ export class GraphQLInterfaceType {
893893
extensionASTNodes: ?$ReadOnlyArray<InterfaceTypeExtensionNode>;
894894
resolveType: ?GraphQLTypeResolver<*, *>;
895895

896-
_typeConfig: GraphQLInterfaceTypeConfig<*, *>;
897-
_fields: GraphQLFieldMap<*, *>;
896+
_fields: Thunk<GraphQLFieldMap<*, *>>;
898897

899898
constructor(config: GraphQLInterfaceTypeConfig<*, *>): void {
900899
this.name = config.name;
901900
this.description = config.description;
902901
this.astNode = config.astNode;
903902
this.extensionASTNodes = config.extensionASTNodes;
904903
this.resolveType = config.resolveType;
905-
this._typeConfig = config;
904+
this._fields = defineFieldMap.bind(undefined, config);
906905
invariant(typeof config.name === 'string', 'Must provide name.');
907906
if (config.resolveType) {
908907
invariant(
@@ -913,10 +912,10 @@ export class GraphQLInterfaceType {
913912
}
914913

915914
getFields(): GraphQLFieldMap<*, *> {
916-
return (
917-
this._fields ||
918-
(this._fields = defineFieldMap(this, this._typeConfig.fields))
919-
);
915+
if (typeof this._fields === 'function') {
916+
this._fields = this._fields();
917+
}
918+
return this._fields;
920919
}
921920

922921
toString(): string {
@@ -972,16 +971,15 @@ export class GraphQLUnionType {
972971
extensionASTNodes: ?$ReadOnlyArray<UnionTypeExtensionNode>;
973972
resolveType: ?GraphQLTypeResolver<*, *>;
974973

975-
_typeConfig: GraphQLUnionTypeConfig<*, *>;
976-
_types: Array<GraphQLObjectType>;
974+
_types: Thunk<Array<GraphQLObjectType>>;
977975

978976
constructor(config: GraphQLUnionTypeConfig<*, *>): void {
979977
this.name = config.name;
980978
this.description = config.description;
981979
this.astNode = config.astNode;
982980
this.extensionASTNodes = config.extensionASTNodes;
983981
this.resolveType = config.resolveType;
984-
this._typeConfig = config;
982+
this._types = defineTypes.bind(undefined, config);
985983
invariant(typeof config.name === 'string', 'Must provide name.');
986984
if (config.resolveType) {
987985
invariant(
@@ -992,9 +990,10 @@ export class GraphQLUnionType {
992990
}
993991

994992
getTypes(): Array<GraphQLObjectType> {
995-
return (
996-
this._types || (this._types = defineTypes(this, this._typeConfig.types))
997-
);
993+
if (typeof this._types === 'function') {
994+
this._types = this._types();
995+
}
996+
return this._types;
998997
}
999998

1000999
toString(): string {
@@ -1007,14 +1006,13 @@ defineToStringTag(GraphQLUnionType);
10071006
defineToJSON(GraphQLUnionType);
10081007

10091008
function defineTypes(
1010-
unionType: GraphQLUnionType,
1011-
typesThunk: Thunk<Array<GraphQLObjectType>>,
1009+
config: GraphQLUnionTypeConfig<*, *>,
10121010
): Array<GraphQLObjectType> {
1013-
const types = resolveThunk(typesThunk) || [];
1011+
const types = resolveThunk(config.types) || [];
10141012
invariant(
10151013
Array.isArray(types),
10161014
'Must provide Array of types or a function which returns ' +
1017-
`such an array for Union ${unionType.name}.`,
1015+
`such an array for Union ${config.name}.`,
10181016
);
10191017
return types;
10201018
}
@@ -1206,43 +1204,22 @@ export class GraphQLInputObjectType {
12061204
astNode: ?InputObjectTypeDefinitionNode;
12071205
extensionASTNodes: ?$ReadOnlyArray<InputObjectTypeExtensionNode>;
12081206

1209-
_typeConfig: GraphQLInputObjectTypeConfig;
1210-
_fields: GraphQLInputFieldMap;
1207+
_fields: Thunk<GraphQLInputFieldMap>;
12111208

12121209
constructor(config: GraphQLInputObjectTypeConfig): void {
12131210
this.name = config.name;
12141211
this.description = config.description;
12151212
this.astNode = config.astNode;
12161213
this.extensionASTNodes = config.extensionASTNodes;
1217-
this._typeConfig = config;
1214+
this._fields = defineInputFieldMap.bind(undefined, config);
12181215
invariant(typeof config.name === 'string', 'Must provide name.');
12191216
}
12201217

12211218
getFields(): GraphQLInputFieldMap {
1222-
return this._fields || (this._fields = this._defineFieldMap());
1223-
}
1224-
1225-
_defineFieldMap(): GraphQLInputFieldMap {
1226-
const fieldMap: any = resolveThunk(this._typeConfig.fields) || {};
1227-
invariant(
1228-
isPlainObj(fieldMap),
1229-
`${this.name} fields must be an object with field names as keys or a ` +
1230-
'function which returns such an object.',
1231-
);
1232-
const resultFieldMap = Object.create(null);
1233-
Object.keys(fieldMap).forEach(fieldName => {
1234-
const field = {
1235-
...fieldMap[fieldName],
1236-
name: fieldName,
1237-
};
1238-
invariant(
1239-
!field.hasOwnProperty('resolve'),
1240-
`${this.name}.${fieldName} field type has a resolve property, but ` +
1241-
'Input Types cannot define resolvers.',
1242-
);
1243-
resultFieldMap[fieldName] = field;
1244-
});
1245-
return resultFieldMap;
1219+
if (typeof this._fields === 'function') {
1220+
this._fields = this._fields();
1221+
}
1222+
return this._fields;
12461223
}
12471224

12481225
toString(): string {
@@ -1254,6 +1231,31 @@ export class GraphQLInputObjectType {
12541231
defineToStringTag(GraphQLInputObjectType);
12551232
defineToJSON(GraphQLInputObjectType);
12561233

1234+
function defineInputFieldMap(
1235+
config: GraphQLInputObjectTypeConfig,
1236+
): GraphQLInputFieldMap {
1237+
const fieldMap: any = resolveThunk(config.fields) || {};
1238+
invariant(
1239+
isPlainObj(fieldMap),
1240+
`${config.name} fields must be an object with field names as keys or a ` +
1241+
'function which returns such an object.',
1242+
);
1243+
const resultFieldMap = Object.create(null);
1244+
Object.keys(fieldMap).forEach(fieldName => {
1245+
const field = {
1246+
...fieldMap[fieldName],
1247+
name: fieldName,
1248+
};
1249+
invariant(
1250+
!field.hasOwnProperty('resolve'),
1251+
`${config.name}.${fieldName} field type has a resolve property, but ` +
1252+
'Input Types cannot define resolvers.',
1253+
);
1254+
resultFieldMap[fieldName] = field;
1255+
});
1256+
return resultFieldMap;
1257+
}
1258+
12571259
export type GraphQLInputObjectTypeConfig = {|
12581260
name: string,
12591261
fields: Thunk<GraphQLInputFieldConfigMap>,

0 commit comments

Comments
 (0)