UNPKG

@dillonkearns/elm-graphql

Version:

<img src="https://cdn.jsdelivr.net/gh/martimatix/logo-graphqelm/logo.svg" alt="dillonearns/elm-graphql logo" width="40%" align="right">

557 lines (486 loc) 19.7 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.extendSchema = extendSchema; var _invariant = require('../jsutils/invariant'); var _invariant2 = _interopRequireDefault(_invariant); var _keyMap = require('../jsutils/keyMap'); var _keyMap2 = _interopRequireDefault(_keyMap); var _keyValMap = require('../jsutils/keyValMap'); var _keyValMap2 = _interopRequireDefault(_keyValMap); var _buildASTSchema = require('./buildASTSchema'); var _valueFromAST = require('./valueFromAST'); var _GraphQLError = require('../error/GraphQLError'); var _schema = require('../type/schema'); var _definition = require('../type/definition'); var _directives = require('../type/directives'); var _introspection = require('../type/introspection'); var _scalars = require('../type/scalars'); var _kinds = require('../language/kinds'); var Kind = _interopRequireWildcard(_kinds); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Produces a new schema given an existing schema and a document which may * contain GraphQL type extensions and definitions. The original schema will * remain unaltered. * * Because a schema represents a graph of references, a schema cannot be * extended without effectively making an entire copy. We do not know until it's * too late if subgraphs remain unchanged. * * This algorithm copies the provided schema, applying extensions while * producing the copy. The original schema remains unaltered. */ /** * 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. * * */ function extendSchema(schema, documentAST) { !(schema instanceof _schema.GraphQLSchema) ? (0, _invariant2.default)(0, 'Must provide valid GraphQLSchema') : void 0; !(documentAST && documentAST.kind === Kind.DOCUMENT) ? (0, _invariant2.default)(0, 'Must provide valid Document AST') : void 0; // Collect the type definitions and extensions found in the document. var typeDefinitionMap = Object.create(null); var typeExtensionsMap = Object.create(null); // New directives and types are separate because a directives and types can // have the same name. For example, a type named "skip". var directiveDefinitions = []; for (var i = 0; i < documentAST.definitions.length; i++) { var def = documentAST.definitions[i]; switch (def.kind) { case Kind.OBJECT_TYPE_DEFINITION: case Kind.INTERFACE_TYPE_DEFINITION: case Kind.ENUM_TYPE_DEFINITION: case Kind.UNION_TYPE_DEFINITION: case Kind.SCALAR_TYPE_DEFINITION: case Kind.INPUT_OBJECT_TYPE_DEFINITION: // Sanity check that none of the defined types conflict with the // schema's existing types. var typeName = def.name.value; if (schema.getType(typeName)) { throw new _GraphQLError.GraphQLError('Type "' + typeName + '" already exists in the schema. It cannot also ' + 'be defined in this type definition.', [def]); } typeDefinitionMap[typeName] = def; break; case Kind.TYPE_EXTENSION_DEFINITION: // Sanity check that this type extension exists within the // schema's existing types. var extendedTypeName = def.definition.name.value; var existingType = schema.getType(extendedTypeName); if (!existingType) { throw new _GraphQLError.GraphQLError('Cannot extend type "' + extendedTypeName + '" because it does not ' + 'exist in the existing schema.', [def.definition]); } if (!(existingType instanceof _definition.GraphQLObjectType)) { throw new _GraphQLError.GraphQLError('Cannot extend non-object type "' + extendedTypeName + '".', [def.definition]); } var extensions = typeExtensionsMap[extendedTypeName]; if (extensions) { extensions.push(def); } else { extensions = [def]; } typeExtensionsMap[extendedTypeName] = extensions; break; case Kind.DIRECTIVE_DEFINITION: var directiveName = def.name.value; var existingDirective = schema.getDirective(directiveName); if (existingDirective) { throw new _GraphQLError.GraphQLError('Directive "' + directiveName + '" already exists in the schema. It ' + 'cannot be redefined.', [def]); } directiveDefinitions.push(def); break; } } // If this document contains no new types, extensions, or directives then // return the same unmodified GraphQLSchema instance. if (Object.keys(typeExtensionsMap).length === 0 && Object.keys(typeDefinitionMap).length === 0 && directiveDefinitions.length === 0) { return schema; } // A cache to use to store the actual GraphQLType definition objects by name. // Initialize to the GraphQL built in scalars and introspection types. All // functions below are inline so that this type def cache is within the scope // of the closure. var typeDefCache = { String: _scalars.GraphQLString, Int: _scalars.GraphQLInt, Float: _scalars.GraphQLFloat, Boolean: _scalars.GraphQLBoolean, ID: _scalars.GraphQLID, __Schema: _introspection.__Schema, __Directive: _introspection.__Directive, __DirectiveLocation: _introspection.__DirectiveLocation, __Type: _introspection.__Type, __Field: _introspection.__Field, __InputValue: _introspection.__InputValue, __EnumValue: _introspection.__EnumValue, __TypeKind: _introspection.__TypeKind }; // Get the root Query, Mutation, and Subscription object types. var queryType = getTypeFromDef(schema.getQueryType()); var existingMutationType = schema.getMutationType(); var mutationType = existingMutationType ? getTypeFromDef(existingMutationType) : null; var existingSubscriptionType = schema.getSubscriptionType(); var subscriptionType = existingSubscriptionType ? getTypeFromDef(existingSubscriptionType) : null; // Iterate through all types, getting the type definition for each, ensuring // that any type not directly referenced by a field will get created. var typeMap = schema.getTypeMap(); var types = Object.keys(typeMap).map(function (typeName) { return getTypeFromDef(typeMap[typeName]); }); // Do the same with new types, appending to the list of defined types. Object.keys(typeDefinitionMap).forEach(function (typeName) { types.push(getTypeFromAST(typeDefinitionMap[typeName])); }); // Then produce and return a Schema with these types. return new _schema.GraphQLSchema({ query: queryType, mutation: mutationType, subscription: subscriptionType, types: types, directives: getMergedDirectives(), astNode: schema.astNode }); // 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() { var existingDirectives = schema.getDirectives(); !existingDirectives ? (0, _invariant2.default)(0, 'schema must have default directives') : void 0; var newDirectives = directiveDefinitions.map(function (directiveNode) { return getDirective(directiveNode); }); return existingDirectives.concat(newDirectives); } function getTypeFromDef(typeDef) { var type = _getNamedType(typeDef.name); !type ? (0, _invariant2.default)(0, 'Missing type from schema') : void 0; return type; } function getTypeFromAST(node) { var type = _getNamedType(node.name.value); if (!type) { throw new _GraphQLError.GraphQLError('Unknown type: "' + node.name.value + '". Ensure that this type exists ' + 'either in the original schema, or is added in a type definition.', [node]); } return type; } function getObjectTypeFromAST(node) { var type = getTypeFromAST(node); !(type instanceof _definition.GraphQLObjectType) ? (0, _invariant2.default)(0, 'Must be Object type.') : void 0; return type; } function getInterfaceTypeFromAST(node) { var type = getTypeFromAST(node); !(type instanceof _definition.GraphQLInterfaceType) ? (0, _invariant2.default)(0, 'Must be Interface type.') : void 0; return type; } function getInputTypeFromAST(node) { return (0, _definition.assertInputType)(getTypeFromAST(node)); } function getOutputTypeFromAST(node) { return (0, _definition.assertOutputType)(getTypeFromAST(node)); } // Given a name, returns a type from either the existing schema or an // added type. function _getNamedType(typeName) { var cachedTypeDef = typeDefCache[typeName]; if (cachedTypeDef) { return cachedTypeDef; } var existingType = schema.getType(typeName); if (existingType) { var typeDef = extendType(existingType); typeDefCache[typeName] = typeDef; return typeDef; } var typeNode = typeDefinitionMap[typeName]; if (typeNode) { var _typeDef = buildType(typeNode); typeDefCache[typeName] = _typeDef; return _typeDef; } } // Given a type's introspection result, construct the correct // GraphQLType instance. function extendType(type) { if (type instanceof _definition.GraphQLObjectType) { return extendObjectType(type); } if (type instanceof _definition.GraphQLInterfaceType) { return extendInterfaceType(type); } if (type instanceof _definition.GraphQLUnionType) { return extendUnionType(type); } return type; } function extendObjectType(type) { var name = type.name; var extensionASTNodes = type.extensionASTNodes; if (typeExtensionsMap[name]) { extensionASTNodes = extensionASTNodes.concat(typeExtensionsMap[name]); } return new _definition.GraphQLObjectType({ name: name, description: type.description, interfaces: function interfaces() { return extendImplementedInterfaces(type); }, fields: function fields() { return extendFieldMap(type); }, astNode: type.astNode, extensionASTNodes: extensionASTNodes, isTypeOf: type.isTypeOf }); } function extendInterfaceType(type) { return new _definition.GraphQLInterfaceType({ name: type.name, description: type.description, fields: function fields() { return extendFieldMap(type); }, astNode: type.astNode, resolveType: type.resolveType }); } function extendUnionType(type) { return new _definition.GraphQLUnionType({ name: type.name, description: type.description, types: type.getTypes().map(getTypeFromDef), astNode: type.astNode, resolveType: type.resolveType }); } function extendImplementedInterfaces(type) { var interfaces = type.getInterfaces().map(getTypeFromDef); // If there are any extensions to the interfaces, apply those here. var extensions = typeExtensionsMap[type.name]; if (extensions) { extensions.forEach(function (extension) { extension.definition.interfaces.forEach(function (namedType) { var interfaceName = namedType.name.value; if (interfaces.some(function (def) { return def.name === interfaceName; })) { throw new _GraphQLError.GraphQLError('Type "' + type.name + '" already implements "' + interfaceName + '". ' + 'It cannot also be implemented in this type extension.', [namedType]); } interfaces.push(getInterfaceTypeFromAST(namedType)); }); }); } return interfaces; } function extendFieldMap(type) { var newFieldMap = Object.create(null); var oldFieldMap = type.getFields(); Object.keys(oldFieldMap).forEach(function (fieldName) { var field = oldFieldMap[fieldName]; newFieldMap[fieldName] = { description: field.description, deprecationReason: field.deprecationReason, type: extendFieldType(field.type), args: (0, _keyMap2.default)(field.args, function (arg) { return arg.name; }), astNode: field.astNode, resolve: field.resolve }; }); // If there are any extensions to the fields, apply those here. var extensions = typeExtensionsMap[type.name]; if (extensions) { extensions.forEach(function (extension) { extension.definition.fields.forEach(function (field) { var fieldName = field.name.value; if (oldFieldMap[fieldName]) { throw new _GraphQLError.GraphQLError('Field "' + type.name + '.' + fieldName + '" already exists in the ' + 'schema. It cannot also be defined in this type extension.', [field]); } newFieldMap[fieldName] = { description: (0, _buildASTSchema.getDescription)(field), type: buildOutputFieldType(field.type), args: buildInputValues(field.arguments), deprecationReason: (0, _buildASTSchema.getDeprecationReason)(field), astNode: field }; }); }); } return newFieldMap; } function extendFieldType(typeDef) { if (typeDef instanceof _definition.GraphQLList) { return new _definition.GraphQLList(extendFieldType(typeDef.ofType)); } if (typeDef instanceof _definition.GraphQLNonNull) { return new _definition.GraphQLNonNull(extendFieldType(typeDef.ofType)); } return getTypeFromDef(typeDef); } function buildType(typeNode) { switch (typeNode.kind) { case Kind.OBJECT_TYPE_DEFINITION: return buildObjectType(typeNode); case Kind.INTERFACE_TYPE_DEFINITION: return buildInterfaceType(typeNode); case Kind.UNION_TYPE_DEFINITION: return buildUnionType(typeNode); case Kind.SCALAR_TYPE_DEFINITION: return buildScalarType(typeNode); case Kind.ENUM_TYPE_DEFINITION: return buildEnumType(typeNode); case Kind.INPUT_OBJECT_TYPE_DEFINITION: return buildInputObjectType(typeNode); } throw new TypeError('Unknown type kind ' + typeNode.kind); } function buildObjectType(typeNode) { return new _definition.GraphQLObjectType({ name: typeNode.name.value, description: (0, _buildASTSchema.getDescription)(typeNode), interfaces: function interfaces() { return buildImplementedInterfaces(typeNode); }, fields: function fields() { return buildFieldMap(typeNode); }, astNode: typeNode }); } function buildInterfaceType(typeNode) { return new _definition.GraphQLInterfaceType({ name: typeNode.name.value, description: (0, _buildASTSchema.getDescription)(typeNode), fields: function fields() { return buildFieldMap(typeNode); }, astNode: typeNode, resolveType: cannotExecuteExtendedSchema }); } function buildUnionType(typeNode) { return new _definition.GraphQLUnionType({ name: typeNode.name.value, description: (0, _buildASTSchema.getDescription)(typeNode), types: typeNode.types.map(getObjectTypeFromAST), astNode: typeNode, resolveType: cannotExecuteExtendedSchema }); } function buildScalarType(typeNode) { return new _definition.GraphQLScalarType({ name: typeNode.name.value, description: (0, _buildASTSchema.getDescription)(typeNode), astNode: typeNode, serialize: function serialize(id) { return id; }, // Note: validation calls the parse functions to determine if a // literal value is correct. Returning null would cause use of custom // scalars to always fail validation. Returning false causes them to // always pass validation. parseValue: function parseValue() { return false; }, parseLiteral: function parseLiteral() { return false; } }); } function buildEnumType(typeNode) { return new _definition.GraphQLEnumType({ name: typeNode.name.value, description: (0, _buildASTSchema.getDescription)(typeNode), values: (0, _keyValMap2.default)(typeNode.values, function (enumValue) { return enumValue.name.value; }, function (enumValue) { return { description: (0, _buildASTSchema.getDescription)(enumValue), deprecationReason: (0, _buildASTSchema.getDeprecationReason)(enumValue), astNode: enumValue }; }), astNode: typeNode }); } function buildInputObjectType(typeNode) { return new _definition.GraphQLInputObjectType({ name: typeNode.name.value, description: (0, _buildASTSchema.getDescription)(typeNode), fields: function fields() { return buildInputValues(typeNode.fields); }, astNode: typeNode }); } function getDirective(directiveNode) { return new _directives.GraphQLDirective({ name: directiveNode.name.value, description: (0, _buildASTSchema.getDescription)(directiveNode), locations: directiveNode.locations.map(function (node) { return node.value; }), args: directiveNode.arguments && buildInputValues(directiveNode.arguments), astNode: directiveNode }); } function buildImplementedInterfaces(typeNode) { return typeNode.interfaces && typeNode.interfaces.map(getInterfaceTypeFromAST); } function buildFieldMap(typeNode) { return (0, _keyValMap2.default)(typeNode.fields, function (field) { return field.name.value; }, function (field) { return { type: buildOutputFieldType(field.type), description: (0, _buildASTSchema.getDescription)(field), args: buildInputValues(field.arguments), deprecationReason: (0, _buildASTSchema.getDeprecationReason)(field), astNode: field }; }); } function buildInputValues(values) { return (0, _keyValMap2.default)(values, function (value) { return value.name.value; }, function (value) { var type = buildInputFieldType(value.type); return { type: type, description: (0, _buildASTSchema.getDescription)(value), defaultValue: (0, _valueFromAST.valueFromAST)(value.defaultValue, type), astNode: value }; }); } function buildInputFieldType(typeNode) { if (typeNode.kind === Kind.LIST_TYPE) { return new _definition.GraphQLList(buildInputFieldType(typeNode.type)); } if (typeNode.kind === Kind.NON_NULL_TYPE) { var nullableType = buildInputFieldType(typeNode.type); !!(nullableType instanceof _definition.GraphQLNonNull) ? (0, _invariant2.default)(0, 'Must be nullable') : void 0; return new _definition.GraphQLNonNull(nullableType); } return getInputTypeFromAST(typeNode); } function buildOutputFieldType(typeNode) { if (typeNode.kind === Kind.LIST_TYPE) { return new _definition.GraphQLList(buildOutputFieldType(typeNode.type)); } if (typeNode.kind === Kind.NON_NULL_TYPE) { var nullableType = buildOutputFieldType(typeNode.type); !!(nullableType instanceof _definition.GraphQLNonNull) ? (0, _invariant2.default)(0, 'Must be nullable') : void 0; return new _definition.GraphQLNonNull(nullableType); } return getOutputTypeFromAST(typeNode); } } function cannotExecuteExtendedSchema() { throw new Error('Extended Schema cannot use Interface or Union types for execution.'); }