UNPKG

graphql-compose

Version:

GraphQL schema builder from different data sources with middleware extensions.

516 lines (443 loc) 16.9 kB
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /* eslint-disable no-use-before-define, class-methods-use-this, no-unused-vars, no-param-reassign */ import objectPath from 'object-path'; import { GraphQLInt, GraphQLFloat, GraphQLString, GraphQLBoolean, GraphQLScalarType, GraphQLID, GraphQLList, GraphQLNonNull, GraphQLEnumType, GraphQLObjectType, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLDirective, GraphQLSkipDirective, GraphQLIncludeDirective, GraphQLDeprecatedDirective, isOutputType, isInputType, valueFromAST } from 'graphql'; import { parse, parseType } from 'graphql/language/parser'; import { LIST_TYPE, NON_NULL_TYPE, DOCUMENT, SCHEMA_DEFINITION, SCALAR_TYPE_DEFINITION, OBJECT_TYPE_DEFINITION, INTERFACE_TYPE_DEFINITION, ENUM_TYPE_DEFINITION, UNION_TYPE_DEFINITION, INPUT_OBJECT_TYPE_DEFINITION, DIRECTIVE_DEFINITION, NAMED_TYPE } from 'graphql/language/kinds'; import { getDescription } from 'graphql/utilities/buildASTSchema'; import keyValMap from 'graphql/jsutils/keyValMap'; import invariant from 'graphql/jsutils/invariant'; import find from 'graphql/jsutils/find'; import { getArgumentValues } from 'graphql/execution/values'; import GraphQLJSON from './type/json'; import GraphQLDate from './type/date'; import TypeComposer from './typeComposer'; import InputTypeComposer from './inputTypeComposer'; import Resolver from './resolver'; var RegexpOutputTypeDefinition = /type\s[^{]+\{[^}]+\}/im; var RegexpInputTypeDefinition = /input\s[^{]+\{[^}]+\}/im; var RegexpEnumTypeDefinition = /enum\s[^{]+\{[^}]+\}/im; var TypeMapper = function () { function TypeMapper() { _classCallCheck(this, TypeMapper); this.map = new Map(); this.addBasicScalarTypes(); } _createClass(TypeMapper, [{ key: 'get', value: function get(name) { return this.map.get(name); } }, { key: 'set', value: function set(name, type) { this.map.set(name, type); } }, { key: 'has', value: function has(name) { return this.map.has(name); } }, { key: 'delete', value: function _delete(name) { return this.map.delete(name); } }, { key: 'keys', value: function keys() { // eslint-disable-line no-undef return this.map.keys(); } }, { key: 'addBasicScalarTypes', value: function addBasicScalarTypes() { this.set('String', GraphQLString); this.set('Float', GraphQLFloat); this.set('Int', GraphQLInt); this.set('Boolean', GraphQLBoolean); this.set('ID', GraphQLID); this.set('JSON', GraphQLJSON); this.set('Json', GraphQLJSON); this.set('Date', GraphQLDate); } }, { key: 'getWrapped', value: function getWrapped(str) { var inputTypeAST = parseType(str); return typeFromAST(inputTypeAST); } }, { key: 'createType', value: function createType(str) { var astDocument = parse(str); if (objectPath.get(astDocument, 'kind') !== 'Document') { throw new Error('You should provide correct type syntax. ' + "Eg. createType('type IntRange { min: Int, max: Int }')"); } var types = parseTypes(astDocument); var type = types[0]; if (type) { this.set(type.name, type); return type; } return undefined; } }, { key: 'convertOutputFieldConfig', value: function convertOutputFieldConfig(fieldConfig, fieldName, typeName) { var wrapWithList = false; if (Array.isArray(fieldConfig)) { fieldConfig = { type: fieldConfig }; } if (Array.isArray(fieldConfig.type)) { if (fieldConfig.type.length !== 1) { throw new Error(typeName + '.' + fieldName + ' can accept Array exact with one output type definition'); } wrapWithList = true; fieldConfig.type = fieldConfig.type[0]; } if (fieldConfig instanceof TypeComposer) { return { type: fieldConfig.getType() }; } if (fieldConfig instanceof Resolver) { return fieldConfig.getFieldConfig(); } if (fieldConfig instanceof InputTypeComposer || fieldConfig.type instanceof InputTypeComposer) { throw new Error('You cannot provide InputTypeComposer to the field \'' + typeName + '.' + fieldName + '\'. It should be OutputType.'); } // $FlowFixMe if (typeof fieldConfig === 'string' || isOutputType(fieldConfig)) { fieldConfig = { // $FlowFixMe type: fieldConfig }; } if (typeof fieldConfig.type === 'string') { var fieldTypeDef = fieldConfig.type; if (RegexpInputTypeDefinition.test(fieldTypeDef)) { throw new Error(typeName + '.' + fieldName + ' should be OutputType, but got input type definition \'' + fieldTypeDef + '\''); } var type = RegexpOutputTypeDefinition.test(fieldTypeDef) || RegexpEnumTypeDefinition.test(fieldTypeDef) ? this.createType(fieldTypeDef) : this.getWrapped(fieldTypeDef); if (!type) { throw new Error(typeName + '.' + fieldName + ' can not conver to OutputType following string: \'' + fieldTypeDef + '\''); } // $FlowFixMe fieldConfig.type = type; if (!isOutputType(type)) { throw new Error(typeName + '.' + fieldName + ' provided incorrect output type \'' + fieldTypeDef + '\''); } } else if (fieldConfig.type instanceof TypeComposer) { fieldConfig.type = fieldConfig.type.getType(); } if (fieldConfig.args) { fieldConfig.args = this.convertArgConfigMap(fieldConfig.args, fieldName, typeName); } if (wrapWithList) { // $FlowFixMe fieldConfig.type = new GraphQLList(fieldConfig.type); } return fieldConfig; } }, { key: 'convertOutputFieldConfigMap', value: function convertOutputFieldConfigMap(fields, typeName) { var _this = this; Object.keys(fields).forEach(function (name) { fields[name] = _this.convertOutputFieldConfig(fields[name], name, typeName); // eslint-disable-line }); return fields; } }, { key: 'convertArgConfig', value: function convertArgConfig(argConfig, argName, fieldName, typeName) { var wrapWithList = false; if (Array.isArray(argConfig)) { argConfig = { type: argConfig }; } // $FlowFixMe if (Array.isArray(argConfig.type)) { if (argConfig.type.length !== 1) { throw new Error(typeName + '.' + fieldName + '@' + argName + ' can accept Array exact with one input type definition'); } wrapWithList = true; // $FlowFixMe argConfig.type = argConfig.type[0]; } if (argConfig instanceof InputTypeComposer) { return { type: argConfig.getType() }; } if (argConfig instanceof TypeComposer || argConfig.type instanceof TypeComposer) { throw new Error('You cannot provide TypeComposer to the arg \'' + typeName + '.' + fieldName + '.@' + argName + '\'. It should be InputType.'); } // $FlowFixMe if (typeof argConfig === 'string' || isInputType(argConfig)) { argConfig = { // $FlowFixMe type: argConfig }; } if (typeof argConfig.type === 'string') { var argTypeDef = argConfig.type; if (RegexpOutputTypeDefinition.test(argTypeDef)) { throw new Error(typeName + '.' + fieldName + '@' + argName + ' should be InputType, but got output type definition \'' + argTypeDef + '\''); } var type = RegexpInputTypeDefinition.test(argTypeDef) || RegexpEnumTypeDefinition.test(argTypeDef) ? this.createType(argTypeDef) : this.getWrapped(argTypeDef); if (!isInputType(type)) { throw new Error(typeName + '.' + fieldName + '@' + argName + ' provided incorrect input type \'' + argTypeDef + '\''); } // $FlowFixMe argConfig.type = type; } else if (argConfig.type instanceof InputTypeComposer) { argConfig.type = argConfig.type.getType(); } if (wrapWithList) { // $FlowFixMe argConfig.type = new GraphQLList(argConfig.type); } return argConfig; } }, { key: 'convertArgConfigMap', value: function convertArgConfigMap(argsConfigMap, fieldName, typeName) { var _this2 = this; if (argsConfigMap) { Object.keys(argsConfigMap).forEach(function (argName) { argsConfigMap[argName] = _this2.convertArgConfig( // eslint-disable-line argsConfigMap[argName], argName, fieldName, typeName); }); } return argsConfigMap; } }, { key: 'convertInputFieldConfig', value: function convertInputFieldConfig(fieldConfig, fieldName, typeName) { var wrapWithList = false; if (Array.isArray(fieldConfig)) { fieldConfig = { type: fieldConfig }; } if (Array.isArray(fieldConfig.type)) { if (fieldConfig.type.length !== 1) { throw new Error(typeName + '.' + fieldName + ' can accept Array exact with one input type definition'); } wrapWithList = true; fieldConfig.type = fieldConfig.type[0]; } if (fieldConfig instanceof InputTypeComposer) { return { type: fieldConfig.getType() }; } if (fieldConfig instanceof TypeComposer || fieldConfig.type instanceof TypeComposer) { throw new Error('You cannot provide TypeComposer to the field \'' + typeName + '.' + fieldName + '\'. It should be InputType.'); } // $FlowFixMe if (typeof fieldConfig === 'string' || isInputType(fieldConfig)) { fieldConfig = { // $FlowFixMe type: fieldConfig }; } if (typeof fieldConfig.type === 'string') { var fieldTypeDef = fieldConfig.type; if (RegexpOutputTypeDefinition.test(fieldTypeDef)) { throw new Error(typeName + '.' + fieldName + ' should be InputType, but got output type definition \'' + fieldTypeDef + '\''); } var type = RegexpInputTypeDefinition.test(fieldTypeDef) || RegexpEnumTypeDefinition.test(fieldTypeDef) ? this.createType(fieldTypeDef) : this.getWrapped(fieldTypeDef); if (!isInputType(type)) { throw new Error(typeName + '.' + fieldName + ' provided incorrect input type \'' + fieldTypeDef + '\''); } // $FlowFixMe fieldConfig.type = type; } else if (fieldConfig.type instanceof InputTypeComposer) { fieldConfig.type = fieldConfig.type.getType(); } if (wrapWithList) { // $FlowFixMe fieldConfig.type = new GraphQLList(fieldConfig.type); } return fieldConfig; } }, { key: 'convertInputFieldConfigMap', value: function convertInputFieldConfigMap(fields, typeName) { var _this3 = this; Object.keys(fields).forEach(function (name) { fields[name] = _this3.convertInputFieldConfig(fields[name], name, typeName); // eslint-disable-line }); return fields; } }]); return TypeMapper; }(); var typeMapper = new TypeMapper(); export default typeMapper; function parseTypes(astDocument) { var types = []; for (var i = 0; i < astDocument.definitions.length; i++) { var def = astDocument.definitions[i]; types[i] = makeSchemaDef(def); } return types; } function typeFromAST(inputTypeAST) { var innerType = void 0; if (inputTypeAST.kind === LIST_TYPE) { innerType = typeFromAST(inputTypeAST.type); return innerType && new GraphQLList(innerType); } if (inputTypeAST.kind === NON_NULL_TYPE) { innerType = typeFromAST(inputTypeAST.type); return innerType && new GraphQLNonNull(innerType); } invariant(inputTypeAST.kind === NAMED_TYPE, 'Must be a named type.'); return typeMapper.get(inputTypeAST.name.value); } function typeDefNamed(typeName) { var type = typeMapper.get(typeName); if (type) { return type; } throw new Error('Cannot find type with name \'' + typeName + '\' in TypeMapper.'); } function makeSchemaDef(def) { if (!def) { throw new Error('def must be defined'); } switch (def.kind) { case OBJECT_TYPE_DEFINITION: return makeTypeDef(def); // case INTERFACE_TYPE_DEFINITION: // return makeInterfaceDef(def); case ENUM_TYPE_DEFINITION: return makeEnumDef(def); // case UNION_TYPE_DEFINITION: // return makeUnionDef(def); // case SCALAR_TYPE_DEFINITION: // return makeScalarDef(def); case INPUT_OBJECT_TYPE_DEFINITION: return makeInputObjectDef(def); default: throw new Error('Type kind "' + def.kind + '" not supported.'); } } function makeInputValues(values) { return keyValMap(values, function (value) { return value.name.value; }, function (value) { var type = produceInputType(value.type); return { type: type, description: getDescription(value), defaultValue: valueFromAST(value.defaultValue, type) }; }); } function makeFieldDefMap(def) { return keyValMap(def.fields, function (field) { return field.name.value; }, function (field) { return { type: produceOutputType(field.type), description: getDescription(field), args: makeInputValues(field.arguments), deprecationReason: getDeprecationReason(field.directives) }; }); } function makeEnumDef(def) { var enumType = new GraphQLEnumType({ name: def.name.value, description: getDescription(def), values: keyValMap(def.values, function (enumValue) { return enumValue.name.value; }, function (enumValue) { return { description: getDescription(enumValue), deprecationReason: getDeprecationReason(enumValue.directives) }; }) }); return enumType; } function makeInputObjectDef(def) { return new GraphQLInputObjectType({ name: def.name.value, description: getDescription(def), fields: function fields() { return makeInputValues(def.fields); } }); } function getNamedTypeAST(typeAST) { var namedType = typeAST; while (namedType.kind === LIST_TYPE || namedType.kind === NON_NULL_TYPE) { namedType = namedType.type; } return namedType; } function buildWrappedType(innerType, inputTypeAST) { if (inputTypeAST.kind === LIST_TYPE) { return new GraphQLList(buildWrappedType(innerType, inputTypeAST.type)); } if (inputTypeAST.kind === NON_NULL_TYPE) { var wrappedType = buildWrappedType(innerType, inputTypeAST.type); invariant(!(wrappedType instanceof GraphQLNonNull), 'No nesting nonnull.'); return new GraphQLNonNull(wrappedType); } return innerType; } function produceOutputType(typeAST) { var type = produceType(typeAST); invariant(isOutputType(type), 'Expected Output type.'); return type; } function produceType(typeAST) { var typeName = getNamedTypeAST(typeAST).name.value; var typeDef = typeDefNamed(typeName); return buildWrappedType(typeDef, typeAST); } function produceInputType(typeAST) { var type = produceType(typeAST); invariant(isInputType(type), 'Expected Input type.'); return type; } function produceInterfaceType(typeAST) { var type = produceType(typeAST); invariant(type instanceof GraphQLInterfaceType, 'Expected Object type.'); return type; } function makeImplementedInterfaces(def) { return def.interfaces && def.interfaces.map(function (iface) { return produceInterfaceType(iface); }); } function makeTypeDef(def) { var typeName = def.name.value; return new GraphQLObjectType({ name: typeName, description: getDescription(def), fields: function fields() { return makeFieldDefMap(def); }, interfaces: function interfaces() { return makeImplementedInterfaces(def); } }); } function getDeprecationReason(directives) { var deprecatedAST = directives && find(directives, function (directive) { return directive.name.value === GraphQLDeprecatedDirective.name; }); if (!deprecatedAST) { return; } var _getArgumentValues = getArgumentValues(GraphQLDeprecatedDirective, deprecatedAST), reason = _getArgumentValues.reason; return reason; // eslint-disable-line }