diff --git a/src/utilities/extendSchema.js b/src/utilities/extendSchema.js index 237473b35b..419365fe02 100644 --- a/src/utilities/extendSchema.js +++ b/src/utilities/extendSchema.js @@ -8,33 +8,18 @@ */ import invariant from '../jsutils/invariant'; -import keyMap from '../jsutils/keyMap'; -import objectValues from '../jsutils/objectValues'; import { ASTDefinitionBuilder } from './buildASTSchema'; +import { SchemaTransformer } from './transformSchema'; import { GraphQLError } from '../error/GraphQLError'; import { isSchema, GraphQLSchema } from '../type/schema'; -import { isIntrospectionType } from '../type/introspection'; - import type { GraphQLSchemaValidationOptions } from '../type/schema'; - import { isObjectType, isInterfaceType, - isUnionType, - isListType, - isNonNullType, GraphQLObjectType, GraphQLInterfaceType, - GraphQLUnionType, } from '../type/definition'; -import { GraphQLList, GraphQLNonNull } from '../type/wrappers'; - -import { GraphQLDirective } from '../type/directives'; - import { Kind } from '../language/kinds'; - -import type { GraphQLType, GraphQLNamedType } from '../type/definition'; - import type { DocumentNode, DirectiveDefinitionNode, @@ -195,10 +180,12 @@ export function extendSchema( typeDefinitionMap, options, typeRef => { + invariant(schemaTransformer); const typeName = typeRef.name.value; - const existingType = schema.getType(typeName); - if (existingType) { - return getExtendedType(existingType); + const type = schemaTransformer.transformType(typeName); + + if (type) { + return type; } throw new GraphQLError( @@ -209,54 +196,49 @@ export function extendSchema( }, ); - const extendTypeCache = Object.create(null); - - // Get the root Query, Mutation, and Subscription object types. - // Note: While this could make early assertions to get the correctly - // typed values below, that would throw immediately while type system - // validation with validateSchema() will produce more actionable results. - const existingQueryType = schema.getQueryType(); - const queryType = existingQueryType - ? getExtendedType(existingQueryType) - : null; - - const existingMutationType = schema.getMutationType(); - const mutationType = existingMutationType - ? getExtendedType(existingMutationType) - : null; - - const existingSubscriptionType = schema.getSubscriptionType(); - const subscriptionType = existingSubscriptionType - ? getExtendedType(existingSubscriptionType) - : null; - - const types = [ - // Iterate through all types, getting the type definition for each, ensuring - // that any type not directly referenced by a field will get created. - ...objectValues(schema.getTypeMap()).map(type => getExtendedType(type)), - // Do the same with new types. - ...objectValues(typeDefinitionMap).map(type => astBuilder.buildType(type)), - ]; - - // Support both original legacy names and extended legacy names. - const schemaAllowedLegacyNames = schema.__allowedLegacyNames; - const extendAllowedLegacyNames = options && options.allowedLegacyNames; - const allowedLegacyNames = - schemaAllowedLegacyNames && extendAllowedLegacyNames - ? schemaAllowedLegacyNames.concat(extendAllowedLegacyNames) - : schemaAllowedLegacyNames || extendAllowedLegacyNames; + const schemaTransformer = new SchemaTransformer(schema, { + Schema(config) { + const newDirectives = directiveDefinitions.map(node => + astBuilder.buildDirective(node), + ); - // Then produce and return a Schema with these types. - return new GraphQLSchema({ - query: queryType, - mutation: mutationType, - subscription: subscriptionType, - types, - directives: getMergedDirectives(), - astNode: schema.astNode, - allowedLegacyNames, + const newTypes = []; + Object.keys(typeDefinitionMap).forEach(typeName => { + const def = typeDefinitionMap[typeName]; + newTypes.push(astBuilder.buildType(def)); + }); + const extendAllowedLegacyNames = options && options.allowedLegacyNames; + + return new GraphQLSchema({ + ...config, + types: config.types.concat(newTypes), + directives: config.directives.concat(newDirectives), + allowedLegacyNames: extendAllowedLegacyNames + ? config.allowedLegacyNames.concat(extendAllowedLegacyNames) + : config.allowedLegacyNames, + }); + }, + ObjectType(config) { + const extensions = typeExtensionsMap[config.name] || []; + return new GraphQLObjectType({ + ...config, + interfaces: () => extendImplementedInterfaces(config, extensions), + fields: () => extendFieldMap(config, extensions), + extensionASTNodes: config.extensionASTNodes.concat(extensions), + }); + }, + InterfaceType(config) { + const extensions = typeExtensionsMap[config.name] || []; + return new GraphQLInterfaceType({ + ...config, + fields: () => extendFieldMap(config, extensions), + extensionASTNodes: config.extensionASTNodes.concat(extensions), + }); + }, }); + return schemaTransformer.transformSchema(); + function appendExtensionToTypeExtensions( extension: TypeExtensionNode, existingTypeExtensions: ?Array, @@ -268,155 +250,37 @@ export function extendSchema( return existingTypeExtensions; } - // Below are functions used for producing this schema that have closed over - // this scope and have access to the schema, cache, and newly defined types. - - function getMergedDirectives(): Array { - const existingDirectives = schema.getDirectives(); - invariant(existingDirectives, 'schema must have default directives'); - - return existingDirectives.concat( - directiveDefinitions.map(node => astBuilder.buildDirective(node)), - ); - } - - function getExtendedType(type: T): T { - if (!extendTypeCache[type.name]) { - extendTypeCache[type.name] = extendType(type); - } - return (extendTypeCache[type.name]: any); - } - - // To be called at most once per type. Only getExtendedType should call this. - function extendType(type) { - if (isIntrospectionType(type)) { - // Introspection types are not extended. - return type; - } - if (isObjectType(type)) { - return extendObjectType(type); - } - if (isInterfaceType(type)) { - return extendInterfaceType(type); - } - if (isUnionType(type)) { - return extendUnionType(type); - } - // This type is not yet extendable. - return type; - } - - function extendObjectType(type: GraphQLObjectType): GraphQLObjectType { - const name = type.name; - const extensionASTNodes = typeExtensionsMap[name] - ? type.extensionASTNodes - ? type.extensionASTNodes.concat(typeExtensionsMap[name]) - : typeExtensionsMap[name] - : type.extensionASTNodes; - return new GraphQLObjectType({ - name, - description: type.description, - interfaces: () => extendImplementedInterfaces(type), - fields: () => extendFieldMap(type), - astNode: type.astNode, - extensionASTNodes, - isTypeOf: type.isTypeOf, - }); - } - - function extendInterfaceType( - type: GraphQLInterfaceType, - ): GraphQLInterfaceType { - const name = type.name; - const extensionASTNodes = typeExtensionsMap[name] - ? type.extensionASTNodes - ? type.extensionASTNodes.concat(typeExtensionsMap[name]) - : typeExtensionsMap[name] - : type.extensionASTNodes; - return new GraphQLInterfaceType({ - name: type.name, - description: type.description, - fields: () => extendFieldMap(type), - astNode: type.astNode, - extensionASTNodes, - resolveType: type.resolveType, - }); - } - - function extendUnionType(type: GraphQLUnionType): GraphQLUnionType { - return new GraphQLUnionType({ - name: type.name, - description: type.description, - types: type.getTypes().map(getExtendedType), - astNode: type.astNode, - resolveType: type.resolveType, - }); - } - - function extendImplementedInterfaces( - type: GraphQLObjectType, - ): Array { - const interfaces = type.getInterfaces().map(getExtendedType); - - // If there are any extensions to the interfaces, apply those here. - const extensions = typeExtensionsMap[type.name]; - if (extensions) { - extensions.forEach(extension => { - extension.interfaces.forEach(namedType => { + function extendImplementedInterfaces(config, extensions) { + return config.interfaces().concat( + ...extensions.map(extension => + extension.interfaces.map( // Note: While this could make early assertions to get the correctly // typed values, that would throw immediately while type system // validation with validateSchema() will produce more actionable results. - interfaces.push((astBuilder.buildType(namedType): any)); - }); - }); - } - - return interfaces; + type => (astBuilder.buildType(type): any), + ), + ), + ); } - function extendFieldMap(type: GraphQLObjectType | GraphQLInterfaceType) { - const newFieldMap = Object.create(null); - const oldFieldMap = type.getFields(); - Object.keys(oldFieldMap).forEach(fieldName => { - const field = oldFieldMap[fieldName]; - newFieldMap[fieldName] = { - description: field.description, - deprecationReason: field.deprecationReason, - type: extendFieldType(field.type), - args: keyMap(field.args, arg => arg.name), - astNode: field.astNode, - resolve: field.resolve, - }; - }); + function extendFieldMap(config, extensions) { + const oldFields = config.fields(); + const fieldMap = { ...oldFields }; - // If there are any extensions to the fields, apply those here. - const extensions = typeExtensionsMap[type.name]; - if (extensions) { - extensions.forEach(extension => { - extension.fields.forEach(field => { - const fieldName = field.name.value; - if (oldFieldMap[fieldName]) { - throw new GraphQLError( - `Field "${type.name}.${fieldName}" already exists in the ` + - 'schema. It cannot also be defined in this type extension.', - [field], - ); - } - newFieldMap[fieldName] = astBuilder.buildField(field); - }); - }); - } + for (const extension of extensions) { + for (const field of extension.fields) { + const fieldName = field.name.value; - return newFieldMap; - } - - function extendFieldType(typeDef: T): T { - if (isListType(typeDef)) { - return (GraphQLList(extendFieldType(typeDef.ofType)): any); - } - if (isNonNullType(typeDef)) { - return (GraphQLNonNull(extendFieldType(typeDef.ofType)): any); + if (oldFields[fieldName]) { + throw new GraphQLError( + `Field "${config.name}.${fieldName}" already exists in the ` + + 'schema. It cannot also be defined in this type extension.', + [field], + ); + } + fieldMap[fieldName] = astBuilder.buildField(field); + } } - return getExtendedType(typeDef); + return fieldMap; } } diff --git a/src/utilities/lexicographicSortSchema.js b/src/utilities/lexicographicSortSchema.js index 8593d24d7c..1c9dc51677 100644 --- a/src/utilities/lexicographicSortSchema.js +++ b/src/utilities/lexicographicSortSchema.js @@ -8,173 +8,84 @@ */ import type { ObjMap } from '../jsutils/ObjMap'; -import keyValMap from '../jsutils/keyValMap'; -import objectValues from '../jsutils/objectValues'; import { GraphQLSchema } from '../type/schema'; import { GraphQLDirective } from '../type/directives'; -import { GraphQLList, GraphQLNonNull } from '../type/wrappers'; -import type { GraphQLNamedType } from '../type/definition'; import { + GraphQLScalarType, GraphQLObjectType, GraphQLInterfaceType, GraphQLUnionType, GraphQLEnumType, GraphQLInputObjectType, - isListType, - isNonNullType, - isScalarType, - isObjectType, - isInterfaceType, - isUnionType, - isEnumType, - isInputObjectType, } from '../type/definition'; -import { isSpecifiedScalarType } from '../type/scalars'; -import { isIntrospectionType } from '../type/introspection'; +import { SchemaTransformer } from './transformSchema'; /** * Sort GraphQLSchema. */ export function lexicographicSortSchema(schema: GraphQLSchema): GraphQLSchema { - const cache = Object.create(null); - - const sortMaybeType = maybeType => maybeType && sortNamedType(maybeType); - return new GraphQLSchema({ - types: sortTypes(objectValues(schema.getTypeMap())), - directives: sortByName(schema.getDirectives()).map(sortDirective), - query: sortMaybeType(schema.getQueryType()), - mutation: sortMaybeType(schema.getMutationType()), - subscription: sortMaybeType(schema.getSubscriptionType()), - astNode: schema.astNode, - }); - - function sortDirective(directive) { - return new GraphQLDirective({ - name: directive.name, - description: directive.description, - locations: sortBy(directive.locations, x => x), - args: sortArgs(directive.args), - astNode: directive.astNode, - }); - } - - function sortArgs(args) { - return keyValMap( - sortByName(args), - arg => arg.name, - arg => ({ - ...arg, - type: sortType(arg.type), - }), - ); - } - - function sortFields(fieldsMap) { - return sortObjMap(fieldsMap, field => ({ - type: sortType(field.type), - args: sortArgs(field.args), - resolve: field.resolve, - subscribe: field.subscribe, - deprecationReason: field.deprecationReason, - description: field.description, - astNode: field.astNode, - })); - } - - function sortInputFields(fieldsMap) { - return sortObjMap(fieldsMap, field => ({ - type: sortType(field.type), - defaultValue: field.defaultValue, - description: field.description, - astNode: field.astNode, - })); - } - - function sortType(type) { - if (isListType(type)) { - return new GraphQLList(sortType(type.ofType)); - } else if (isNonNullType(type)) { - return new GraphQLNonNull(sortType(type.ofType)); - } - return sortNamedType(type); - } - - function sortTypes(arr: Array): Array { - return sortByName(arr).map(sortNamedType); - } - - function sortNamedType(type: T): T { - if (isSpecifiedScalarType(type) || isIntrospectionType(type)) { - return type; - } - - let sortedType = cache[type.name]; - if (!sortedType) { - sortedType = sortNamedTypeImpl(type); - cache[type.name] = sortedType; - } - return ((sortedType: any): T); - } - - function sortNamedTypeImpl(type) { - if (isScalarType(type)) { - return type; - } else if (isObjectType(type)) { + const transformer = new SchemaTransformer(schema, { + Schema(config) { + return new GraphQLSchema({ + ...config, + types: sortByName(config.types), + directives: sortByName(config.directives), + }); + }, + Directive(config) { + return new GraphQLDirective({ + ...config, + locations: sortBy(config.locations, x => x), + args: sortObjMap(config.args), + }); + }, + ScalarType(config) { + return new GraphQLScalarType(config); + }, + ObjectType(config) { return new GraphQLObjectType({ - name: type.name, - interfaces: () => sortTypes(type.getInterfaces()), - fields: () => sortFields(type.getFields()), - isTypeOf: type.isTypeOf, - description: type.description, - astNode: type.astNode, - extensionASTNodes: type.extensionASTNodes, + ...config, + fields: () => sortFields(config.fields()), + interfaces: () => sortByName(config.interfaces()), }); - } else if (isInterfaceType(type)) { + }, + InterfaceType(config) { return new GraphQLInterfaceType({ - name: type.name, - fields: () => sortFields(type.getFields()), - resolveType: type.resolveType, - description: type.description, - astNode: type.astNode, - extensionASTNodes: type.extensionASTNodes, + ...config, + fields: () => sortFields(config.fields()), }); - } else if (isUnionType(type)) { + }, + UnionType(config) { return new GraphQLUnionType({ - name: type.name, - types: () => sortTypes(type.getTypes()), - resolveType: type.resolveType, - description: type.description, - astNode: type.astNode, + ...config, + types: () => sortByName(config.types()), }); - } else if (isEnumType(type)) { + }, + EnumType(config) { return new GraphQLEnumType({ - name: type.name, - values: keyValMap( - sortByName(type.getValues()), - val => val.name, - val => ({ - value: val.value, - deprecationReason: val.deprecationReason, - description: val.description, - astNode: val.astNode, - }), - ), - description: type.description, - astNode: type.astNode, + ...config, + values: sortObjMap(config.values), }); - } else if (isInputObjectType(type)) { + }, + InputObjectType(config) { return new GraphQLInputObjectType({ - name: type.name, - fields: () => sortInputFields(type.getFields()), - description: type.description, - astNode: type.astNode, + ...config, + fields: () => sortObjMap(config.fields()), }); - } - throw new Error(`Unknown type: "${type}"`); - } + }, + }); + + return transformer.transformSchema(); +} + +function sortFields(fields) { + return sortObjMap(fields, field => ({ + ...field, + args: sortObjMap(field.args), + })); } -function sortObjMap(map: ObjMap, sortValueFn?: T => R): ObjMap { +function sortObjMap(map: ObjMap, sortValueFn?: T => T): ObjMap { const sortedMap = Object.create(null); const sortedKeys = sortBy(Object.keys(map), x => x); for (const key of sortedKeys) { @@ -184,11 +95,11 @@ function sortObjMap(map: ObjMap, sortValueFn?: T => R): ObjMap { return sortedMap; } -function sortByName(array: $ReadOnlyArray): Array { +function sortByName(array: Array): Array { return sortBy(array, obj => obj.name); } -function sortBy(array: $ReadOnlyArray, mapToKey: T => string): Array { +function sortBy(array: Array, mapToKey: T => string): Array { return array.slice().sort((obj1, obj2) => { const key1 = mapToKey(obj1); const key2 = mapToKey(obj2); diff --git a/src/utilities/transformSchema.js b/src/utilities/transformSchema.js new file mode 100644 index 0000000000..8e5ca0d87f --- /dev/null +++ b/src/utilities/transformSchema.js @@ -0,0 +1,298 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type { ObjMap } from '../jsutils/ObjMap'; +import keyValMap from '../jsutils/keyValMap'; +import objectValues from '../jsutils/objectValues'; +import invariant from '../jsutils/invariant'; +import type { GraphQLSchemaConfig } from '../type/schema'; +import { GraphQLSchema } from '../type/schema'; +import { isSpecifiedScalarType } from '../type/scalars'; +import { isIntrospectionType } from '../type/introspection'; +import type { GraphQLDirectiveConfig } from '../type/directives'; +import { GraphQLDirective } from '../type/directives'; +import { GraphQLList, GraphQLNonNull } from '../type/wrappers'; +import type { + GraphQLNamedType, + GraphQLScalarTypeConfig, + GraphQLObjectTypeConfig, + GraphQLInterfaceTypeConfig, + GraphQLUnionTypeConfig, + GraphQLEnumTypeConfig, + GraphQLInputObjectTypeConfig, +} from '../type/definition'; +import { + GraphQLScalarType, + GraphQLObjectType, + GraphQLInterfaceType, + GraphQLUnionType, + GraphQLEnumType, + GraphQLInputObjectType, + isListType, + isNonNullType, + isScalarType, + isObjectType, + isInterfaceType, + isUnionType, + isEnumType, + isInputObjectType, +} from '../type/definition'; + +type ExactSchemaConfig = $Exact & { + types: Array<*>, + directives: Array<*>, + allowedLegacyNames: Array<*>, +}; + +type ExactDirectiveConfig = $Exact & { + args: ObjMap<*>, +}; + +type ExactScalarTypeConfig = $Exact>; + +type ExactObjectTypeConfig = $Exact> & { + fields: () => ObjMap<*>, + interfaces: () => Array<*>, + extensionASTNodes: $ReadOnlyArray<*>, +}; + +type ExactInterfaceTypeConfig = $Exact> & { + fields: () => ObjMap<*>, + extensionASTNodes: $ReadOnlyArray<*>, +}; + +type ExactUnionTypeConfig = $Exact> & { + types: () => Array<*>, +}; + +type ExactEnumTypeConfig = $Exact; + +type ExactInputObjectTypeConfig = $Exact & { + fields: () => ObjMap<*>, +}; + +type TypeTransformer = { + Schema?: (config: ExactSchemaConfig) => GraphQLSchema, + Directive?: (config: ExactDirectiveConfig) => GraphQLDirective, + ScalarType?: (config: ExactScalarTypeConfig) => GraphQLScalarType, + ObjectType?: (config: ExactObjectTypeConfig) => GraphQLObjectType, + InterfaceType?: (config: ExactInterfaceTypeConfig) => GraphQLInterfaceType, + UnionType?: (config: ExactUnionTypeConfig) => GraphQLUnionType, + EnumType?: (config: ExactEnumTypeConfig) => GraphQLEnumType, + InputObjectType?: ( + config: ExactInputObjectTypeConfig, + ) => GraphQLInputObjectType, +}; + +/** + * Note: This class is not a part of public API and is a subject to changes + * in the future. + */ +export class SchemaTransformer { + _schema: GraphQLSchema; + _transformer: TypeTransformer; + _cache: ObjMap; + + constructor(schema: GraphQLSchema, transformer: TypeTransformer) { + this._schema = schema; + this._transformer = transformer; + this._cache = Object.create(null); + } + + transformSchema(): GraphQLSchema { + const oldTypes = objectValues(this._schema.getTypeMap()); + const oldDirectives = this._schema.getDirectives(); + const transformMaybeType = maybeType => + maybeType && this._transformNamedType(maybeType); + const schemaConfig = { + types: oldTypes.map(type => this._transformNamedType(type)), + directives: oldDirectives.map(directive => + this._transformDirective(directive), + ), + query: transformMaybeType(this._schema.getQueryType()), + mutation: transformMaybeType(this._schema.getMutationType()), + subscription: transformMaybeType(this._schema.getSubscriptionType()), + astNode: this._schema.astNode, + allowedLegacyNames: (this._schema.__allowedLegacyNames || []).slice(), + }; + return this._transformer.Schema + ? this._transformer.Schema(schemaConfig) + : new GraphQLSchema(schemaConfig); + } + + transformType(name: string): ?GraphQLNamedType { + const type = this._schema.getTypeMap()[name]; + return type && this._transformNamedType(type); + } + + _transformDirective(directive: GraphQLDirective): GraphQLDirective { + const config = { + name: directive.name, + description: directive.description, + locations: directive.locations, + args: this._transformArgs(directive.args), + astNode: directive.astNode, + }; + return this._transformer.Directive + ? this._transformer.Directive(config) + : new GraphQLDirective(config); + } + + _transformArgs(args: *): * { + return keyValMap( + args, + arg => arg.name, + arg => ({ + ...arg, + type: this._transformType(arg.type), + }), + ); + } + + _transformFields(fieldsMap: *): * { + return () => + mapValues(fieldsMap, field => ({ + type: this._transformType(field.type), + args: this._transformArgs(field.args), + resolve: field.resolve, + subscribe: field.subscribe, + deprecationReason: field.deprecationReason, + description: field.description, + astNode: field.astNode, + })); + } + + _transformInputFields(fieldsMap: *): * { + return () => + mapValues(fieldsMap, field => ({ + type: this.transformType(field.type), + defaultValue: field.defaultValue, + description: field.description, + astNode: field.astNode, + })); + } + + _transformType(type: *): * { + if (isListType(type)) { + return new GraphQLList(this._transformType(type.ofType)); + } else if (isNonNullType(type)) { + return new GraphQLNonNull(this._transformType(type.ofType)); + } + return this._transformNamedType(type); + } + + _transformTypes(arr: Array): () => Array { + return () => arr.map(type => this._transformNamedType(type)); + } + + _transformNamedType(type: T): T { + if (isSpecifiedScalarType(type) || isIntrospectionType(type)) { + return type; + } + + let newType = this._cache[type.name]; + if (!newType) { + newType = this._transformNamedTypeImpl(type); + this._cache[type.name] = newType; + } + invariant(type.constructor === newType.constructor); + invariant(type.name === newType.name); + return ((newType: any): T); + } + + _transformNamedTypeImpl(type: *): * { + if (isScalarType(type)) { + const config = { + name: type.name, + description: type.description, + astNode: type.astNode, + serialize: type._scalarConfig.serialize, + parseValue: type._scalarConfig.parseValue, + parseLiteral: type._scalarConfig.parseLiteral, + }; + return this._transformer.ScalarType + ? this._transformer.ScalarType(config) + : new GraphQLScalarType(config); + } else if (isObjectType(type)) { + const config: ExactObjectTypeConfig = { + name: type.name, + interfaces: this._transformTypes(type.getInterfaces()), + fields: this._transformFields(type.getFields()), + isTypeOf: type.isTypeOf, + description: type.description, + astNode: type.astNode, + extensionASTNodes: type.extensionASTNodes || [], + }; + return this._transformer.ObjectType + ? this._transformer.ObjectType(config) + : new GraphQLObjectType(config); + } else if (isInterfaceType(type)) { + const config = { + name: type.name, + fields: this._transformFields(type.getFields()), + resolveType: type.resolveType, + description: type.description, + astNode: type.astNode, + extensionASTNodes: type.extensionASTNodes || [], + }; + return this._transformer.InterfaceType + ? this._transformer.InterfaceType(config) + : new GraphQLInterfaceType(config); + } else if (isUnionType(type)) { + const config = { + name: type.name, + types: this._transformTypes(type.getTypes()), + resolveType: type.resolveType, + description: type.description, + astNode: type.astNode, + }; + return this._transformer.UnionType + ? this._transformer.UnionType(config) + : new GraphQLUnionType(config); + } else if (isEnumType(type)) { + const config = { + name: type.name, + values: keyValMap( + type.getValues(), + val => val.name, + val => ({ + value: val.value, + deprecationReason: val.deprecationReason, + description: val.description, + astNode: val.astNode, + }), + ), + description: type.description, + astNode: type.astNode, + }; + return this._transformer.EnumType + ? this._transformer.EnumType(config) + : new GraphQLEnumType(config); + } else if (isInputObjectType(type)) { + const config = { + name: type.name, + fields: this._transformInputFields(type.getFields()), + description: type.description, + astNode: type.astNode, + }; + return this._transformer.InputObjectType + ? this._transformer.InputObjectType(config) + : new GraphQLInputObjectType(config); + } + throw new Error(`Unknown type: "${type}"`); + } +} + +function mapValues(obj: ObjMap, valFn: T => R): ObjMap { + const newObj = Object.create(null); + for (const name of Object.keys(obj)) { + newObj[name] = valFn(obj[name]); + } + return newObj; +}