graphql-compose
Version:
GraphQL schema builder from different data sources with middleware extensions.
648 lines (581 loc) • 20.8 kB
JavaScript
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
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 _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
import { GraphQLObjectType, GraphQLList, GraphQLInputObjectType } from 'graphql';
import { resolveMaybeThunk } from './utils/misc';
import { isObject, isFunction, isString } from './utils/is';
import { resolveOutputConfigsAsThunk, keepConfigsAsThunk } from './utils/configAsThunk';
import { deprecate } from './utils/debug';
import Resolver from './resolver';
import { toInputObjectType } from './toInputObjectType';
import InputTypeComposer from './inputTypeComposer';
import TypeMapper from './typeMapper';
import { typeByPath } from './typeByPath';
var TypeComposer = function () {
_createClass(TypeComposer, null, [{
key: 'create',
value: function create(opts) {
var TC = void 0;
if (isString(opts)) {
// $FlowFixMe
var typeName = opts;
var NAME_RX = /^[_a-zA-Z][_a-zA-Z0-9]*$/;
if (NAME_RX.test(typeName)) {
TC = new TypeComposer(new GraphQLObjectType({
name: typeName,
fields: function fields() {
return {};
}
}));
} else {
var type = TypeMapper.createType(typeName);
if (!(type instanceof GraphQLObjectType)) {
throw new Error('You should provide correct GraphQLObjectType type definition.');
}
TC = new TypeComposer(type);
}
} else if (opts instanceof GraphQLObjectType) {
TC = new TypeComposer(opts);
} else if (isObject(opts)) {
// $FlowFixMe
var _type = new GraphQLObjectType(_extends({}, opts, {
fields: function fields() {
return {};
}
}));
TC = new TypeComposer(_type);
// $FlowFixMe
if (isObject(opts.fields)) {
// $FlowFixMe
TC.addFields(opts.fields);
}
} else {
throw new Error('You should provide GraphQLObjectTypeConfig or string with type name to TypeComposer.create(opts)');
}
return TC;
}
}]);
function TypeComposer(gqType) {
_classCallCheck(this, TypeComposer);
this.gqType = gqType;
}
/**
* Get fields from a GraphQL type
* WARNING: this method read an internal GraphQL instance variable.
*/
_createClass(TypeComposer, [{
key: 'getFields',
value: function getFields() {
var fields = this.gqType._typeConfig.fields;
// $FlowFixMe
var fieldMap = keepConfigsAsThunk(resolveMaybeThunk(fields));
if (isObject(fieldMap)) {
// $FlowFixMe
return Object.assign({}, fieldMap);
}
return {};
}
}, {
key: 'getFieldNames',
value: function getFieldNames() {
return Object.keys(this.getFields());
}
/**
* Completely replace all fields in GraphQL type
* WARNING: this method rewrite an internal GraphQL instance variable.
*/
}, {
key: 'setFields',
value: function setFields(fields) {
var _this = this;
var prepearedFields = TypeMapper.convertOutputFieldConfigMap(fields, this.getTypeName());
// if field has a projection option, then add it to projection mapper
Object.keys(prepearedFields).forEach(function (name) {
if (prepearedFields[name].projection) {
// $FlowFixMe
var projection = prepearedFields[name].projection;
_this.addProjectionMapper(name, projection);
}
});
this.gqType._typeConfig.fields = function () {
return resolveOutputConfigsAsThunk(prepearedFields, _this.getTypeName());
};
delete this.gqType._fields; // clear builded fields in type
return this;
}
}, {
key: 'hasField',
value: function hasField(fieldName) {
var fields = this.getFields();
return !!fields[fieldName];
}
}, {
key: 'setField',
value: function setField(fieldName, fieldConfig) {
this.addFields(_defineProperty({}, fieldName, fieldConfig));
return this;
}
/**
* @deprecated 2.0.0
*/
}, {
key: 'addField',
value: function addField(fieldName, fieldConfig) {
deprecate('Use TypeComposer.setField() or plural addFields({}) instead.');
this.addFields(_defineProperty({}, fieldName, fieldConfig));
}
/**
* Add new fields or replace existed in a GraphQL type
*/
}, {
key: 'addFields',
value: function addFields(newFields) {
this.setFields(Object.assign({}, this.getFields(), newFields));
return this;
}
/**
* Get fieldConfig by name
*/
}, {
key: 'getField',
value: function getField(fieldName) {
var fields = this.getFields();
if (fields[fieldName]) {
return fields[fieldName];
}
return undefined;
}
}, {
key: 'removeField',
value: function removeField(fieldNameOrArray) {
var fieldNames = Array.isArray(fieldNameOrArray) ? fieldNameOrArray : [fieldNameOrArray];
var fields = this.getFields();
fieldNames.forEach(function (fieldName) {
return delete fields[fieldName];
});
this.setFields(Object.assign({}, fields)); // immutability
return this;
}
}, {
key: 'removeOtherFields',
value: function removeOtherFields(fieldNameOrArray) {
var keepFieldNames = Array.isArray(fieldNameOrArray) ? fieldNameOrArray : [fieldNameOrArray];
var fields = this.getFields();
Object.keys(fields).forEach(function (fieldName) {
if (!keepFieldNames.includes(fieldName)) {
delete fields[fieldName];
}
});
this.setFields(fields);
return this;
}
}, {
key: 'extendField',
value: function extendField(name, parialFieldConfig) {
var fieldConfig = Object.assign({}, this.getField(name), parialFieldConfig);
this.setField(name, fieldConfig);
return this;
}
}, {
key: 'reorderFields',
value: function reorderFields(names) {
var orderedFields = {};
var fields = this.getFields();
names.forEach(function (name) {
if (fields[name]) {
orderedFields[name] = fields[name];
delete fields[name];
}
});
this.setFields(_extends({}, orderedFields, fields));
return this;
}
}, {
key: 'addRelation',
value: function addRelation(fieldName, relationFn) {
if (!this.gqType._gqcRelations) {
this.gqType._gqcRelations = {};
}
this.gqType._gqcRelations[fieldName] = relationFn;
return this;
}
}, {
key: 'getRelations',
value: function getRelations() {
if (!this.gqType._gqcRelations) {
this.gqType._gqcRelations = {};
}
return this.gqType._gqcRelations;
}
}, {
key: 'buildRelations',
value: function buildRelations() {
var _this2 = this;
var relationFields = {};
var names = Object.keys(this.getRelations());
names.forEach(function (fieldName) {
relationFields[fieldName] = _this2.buildRelation(fieldName);
});
return this;
}
}, {
key: 'buildRelation',
value: function buildRelation(fieldName) {
if (!this.gqType._gqcRelations || !isFunction(this.gqType._gqcRelations[fieldName])) {
throw new Error('Cannot call buildRelation() for type ' + this.getTypeName() + '. ' + ('Relation with name \'' + fieldName + '\' does not exist.'));
}
var relationFn = this.gqType._gqcRelations[fieldName];
// $FlowFixMe
var relationOpts = relationFn();
if (relationOpts.resolver) {
if (!(relationOpts.resolver instanceof Resolver)) {
throw new Error('You should provide correct Resolver object for relation ' + (this.getTypeName() + '.' + fieldName));
}
if (relationOpts.type) {
throw new Error('You can not use `resolver` and `type` properties simultaneously for relation ' + (this.getTypeName() + '.' + fieldName));
}
if (relationOpts.resolve) {
throw new Error('You can not use `resolver` and `resolve` properties simultaneously for relation ' + (this.getTypeName() + '.' + fieldName));
}
this.addRelationWithResolver(fieldName, relationOpts.resolver, relationOpts);
} else if (relationOpts.type) {
this.setField(fieldName, _extends({}, relationOpts, {
_gqcIsRelation: true
}));
}
return this;
}
}, {
key: 'addRelationWithResolver',
value: function addRelationWithResolver(fieldName, resolver, opts) {
var _this3 = this;
var resolverFieldConfig = resolver.getFieldConfig();
var argsConfig = Object.assign({}, resolverFieldConfig.args);
var argsProto = {};
var argsRuntime = [];
// remove args from config, if arg name provided in args
// if `argMapVal`
// is `undefined`, then keep arg field in config
// is `null`, then just remove arg field from config
// is `function`, then remove arg field and run it in resolve
// is any other value, then put it to args prototype for resolve
var optsArgs = opts.args || {};
Object.keys(optsArgs).forEach(function (argName) {
var argMapVal = optsArgs[argName];
if (argMapVal !== undefined) {
delete argsConfig[argName];
if (isFunction(argMapVal)) {
// $FlowFixMe
argsRuntime.push([argName, argMapVal]);
} else if (argMapVal !== null) {
argsProto[argName] = argMapVal;
}
}
});
// if opts.catchErrors is undefined then set true, otherwise take it value
var _opts$catchErrors = opts.catchErrors,
catchErrors = _opts$catchErrors === undefined ? true : _opts$catchErrors;
var resolve = function resolve(source, args, context, info) {
var newArgs = Object.assign({}, args, argsProto);
argsRuntime.forEach(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
argName = _ref2[0],
argFn = _ref2[1];
newArgs[argName] = argFn(source, args, context, info);
});
var payload = resolverFieldConfig.resolve(source, newArgs, context, info);
return catchErrors ? Promise.resolve(payload).catch(function (e) {
console.log(
// eslint-disable-line
'GQC ERROR: relation for ' + _this3.getTypeName() + '.' + fieldName + ' throws error:');
console.log(e); // eslint-disable-line
return null;
}) : payload;
};
this.setField(fieldName, {
type: resolverFieldConfig.type,
description: opts.description,
deprecationReason: opts.deprecationReason,
args: argsConfig,
resolve: resolve,
projection: opts.projection,
_gqcIsRelation: true
});
return this;
}
/**
* Get fields from a GraphQL type
* WARNING: this method read an internal GraphQL instance variable.
*/
}, {
key: 'getInterfaces',
value: function getInterfaces() {
var interfaces = this.gqType._typeConfig.interfaces;
if (typeof interfaces === 'function') {
return interfaces() || [];
}
return interfaces || [];
}
/**
* Completely replace all interfaces in GraphQL type
* WARNING: this method rewrite an internal GraphQL instance variable.
*/
}, {
key: 'setInterfaces',
value: function setInterfaces(interfaces) {
this.gqType._typeConfig.interfaces = interfaces;
delete this.gqType._interfaces; // if schema was builded, delete _interfaces
return this;
}
}, {
key: 'hasInterface',
value: function hasInterface(interfaceObj) {
return this.getInterfaces().indexOf(interfaceObj) > -1;
}
}, {
key: 'addInterface',
value: function addInterface(interfaceObj) {
if (!this.hasInterface(interfaceObj)) {
this.setInterfaces([].concat(_toConsumableArray(this.getInterfaces()), [interfaceObj]));
}
return this;
}
}, {
key: 'removeInterface',
value: function removeInterface(interfaceObj) {
var interfaces = this.getInterfaces();
var idx = interfaces.indexOf(interfaceObj);
if (idx > -1) {
interfaces.splice(idx, 1);
this.setInterfaces(interfaces);
}
return this;
}
}, {
key: 'clone',
value: function clone(newTypeName) {
if (!newTypeName) {
throw new Error('You should provide newTypeName:string for TypeComposer.clone()');
}
var fields = this.getFields();
var newFields = {};
Object.keys(fields).forEach(function (fieldName) {
newFields[fieldName] = Object.assign({}, fields[fieldName]);
});
var cloned = new TypeComposer(new GraphQLObjectType({
name: newTypeName,
fields: newFields
}));
cloned.gqType._gqcProjectionMapper = this.gqType._gqcProjectionMapper;
cloned.setDescription(this.getDescription());
try {
cloned.setRecordIdFn(this.getRecordIdFn());
} catch (e) {
// no problem, clone without resolveIdFn
}
this.getResolvers().forEach(function (resolver) {
var newResolver = resolver.clone(cloned);
cloned.addResolver(newResolver);
});
return cloned;
}
/**
* Get fieldType by name
*/
}, {
key: 'getFieldType',
value: function getFieldType(fieldName) {
var field = this.getField(fieldName);
if (field) {
return field.type;
}
return undefined;
}
}, {
key: 'getType',
value: function getType() {
return this.gqType;
}
}, {
key: 'getTypePlural',
value: function getTypePlural() {
return new GraphQLList(this.gqType);
}
}, {
key: 'getInputType',
value: function getInputType() {
return this.getInputTypeComposer().getType();
}
}, {
key: 'hasInputTypeComposer',
value: function hasInputTypeComposer() {
return !!this.gqType._gqcInputTypeComposer;
}
}, {
key: 'getInputTypeComposer',
value: function getInputTypeComposer() {
if (!this.gqType._gqcInputTypeComposer) {
this.gqType._gqcInputTypeComposer = toInputObjectType(this);
}
return this.gqType._gqcInputTypeComposer;
}
}, {
key: 'getResolvers',
value: function getResolvers() {
if (!this.gqType._gqcResolvers) {
this.gqType._gqcResolvers = new Map();
}
return this.gqType._gqcResolvers;
}
}, {
key: 'hasResolver',
value: function hasResolver(name) {
if (!this.gqType._gqcResolvers) {
return false;
}
return this.gqType._gqcResolvers.has(name);
}
}, {
key: 'getResolver',
value: function getResolver(name) {
if (this.hasResolver(name) && this.gqType._gqcResolvers) {
return this.gqType._gqcResolvers.get(name);
}
return undefined;
}
}, {
key: 'setResolver',
value: function setResolver(name, resolver) {
if (!this.gqType._gqcResolvers) {
this.gqType._gqcResolvers = new Map();
}
if (!(resolver instanceof Resolver)) {
throw new Error('setResolver() accept only Resolver instance');
}
this.gqType._gqcResolvers.set(name, resolver);
return this;
}
}, {
key: 'addResolver',
value: function addResolver(resolver) {
if (!(resolver instanceof Resolver)) {
resolver = new Resolver(resolver); // eslint-disable-line no-param-reassign
}
if (!resolver.name) {
throw new Error('resolver should have non-empty name property');
}
this.setResolver(resolver.name, resolver);
return this;
}
}, {
key: 'removeResolver',
value: function removeResolver(resolverName) {
if (resolverName) {
this.getResolvers().delete(resolverName);
}
return this;
}
}, {
key: 'getTypeName',
value: function getTypeName() {
return this.gqType.name;
}
}, {
key: 'setTypeName',
value: function setTypeName(name) {
this.gqType.name = name;
return this;
}
}, {
key: 'getDescription',
value: function getDescription() {
return this.gqType.description || '';
}
}, {
key: 'setDescription',
value: function setDescription(description) {
this.gqType.description = description;
return this;
}
}, {
key: 'setRecordIdFn',
value: function setRecordIdFn(fn) {
this.gqType._gqcGetRecordIdFn = fn;
return this;
}
}, {
key: 'hasRecordIdFn',
value: function hasRecordIdFn() {
return !!this.gqType._gqcGetRecordIdFn;
}
}, {
key: 'getRecordIdFn',
value: function getRecordIdFn() {
if (!this.gqType._gqcGetRecordIdFn) {
throw new Error('Type ' + this.getTypeName() + ' does not have RecordIdFn');
}
return this.gqType._gqcGetRecordIdFn;
}
/**
* Get function that returns record id, from provided object.
*/
}, {
key: 'getRecordId',
value: function getRecordId(source, args, context) {
return this.getRecordIdFn()(source, args, context);
}
}, {
key: 'getFieldArgs',
value: function getFieldArgs(fieldName) {
var field = this.getField(fieldName);
if (field) {
return field.args;
}
return null;
}
}, {
key: 'getFieldArg',
value: function getFieldArg(fieldName, argName) {
var fieldArgs = this.getFieldArgs(fieldName) || {};
return fieldArgs[argName] ? fieldArgs[argName] : undefined;
}
/**
* @deprecated 2.0.0
*/
}, {
key: 'getByPath',
value: function getByPath(path) {
deprecate('Use TypeComposer.get() instead.');
return this.get(path);
}
}, {
key: 'get',
value: function get(path) {
return typeByPath(this, path);
}
// Sometimes, when you create relations or some tricky fields,
// you should have a data from additional fields, that not in a query projection.
// E.g. for obtaining `friendList` you also should add `friendIds` to projection.
// or for `fullname` field you should request `firstname` and `lastname` from DB.
// this _gqcProjectionMapper used in `projection` module
}, {
key: 'addProjectionMapper',
value: function addProjectionMapper(fieldName, sourceProjection) {
if (!this.gqType._gqcProjectionMapper) {
this.gqType._gqcProjectionMapper = {};
}
this.gqType._gqcProjectionMapper[fieldName] = sourceProjection;
return this;
}
}, {
key: 'getProjectionMapper',
value: function getProjectionMapper() {
return this.gqType._gqcProjectionMapper || {};
}
}]);
return TypeComposer;
}();
export default TypeComposer;