UNPKG

graphql-compose

Version:

GraphQL schema builder from different data sources with middleware extensions.

1,262 lines (1,016 loc) 40 kB
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } 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; } /* eslint-disable no-use-before-define */ import { GraphQLObjectType, GraphQLInputObjectType, GraphQLInterfaceType } from './graphql'; import { InputTypeComposer } from './InputTypeComposer'; import { InterfaceTypeComposer } from './InterfaceTypeComposer'; import { Resolver } from './Resolver'; import { SchemaComposer } from './SchemaComposer'; import { ListComposer } from './ListComposer'; import { NonNullComposer } from './NonNullComposer'; import { ThunkComposer } from './ThunkComposer'; import { TypeMapper } from './TypeMapper'; import { resolveMaybeThunk, upperFirst, inspect, mapEachKey } from './utils/misc'; import { isObject, isFunction, isString } from './utils/is'; import { defineFieldMap, convertObjectFieldMapToConfig, convertInterfaceArrayAsThunk } from './utils/configToDefine'; import { toInputObjectType } from './utils/toInputObjectType'; import { typeByPath } from './utils/typeByPath'; import { getComposeTypeName, unwrapOutputTC, unwrapInputTC } from './utils/typeHelpers'; import { graphqlVersion } from './utils/graphqlVersion'; import { createThunkedObjectProxy } from './utils/createThunkedObjectProxy'; export class ObjectTypeComposer { static create(typeDef, schemaComposer) { if (!(schemaComposer instanceof SchemaComposer)) { throw new Error('You must provide SchemaComposer instance as a second argument for `ObjectTypeComposer.create(typeDef, schemaComposer)`'); } if (schemaComposer.hasInstance(typeDef, ObjectTypeComposer)) { return schemaComposer.getOTC(typeDef); } const tc = this.createTemp(typeDef, schemaComposer); const typeName = tc.getTypeName(); if (typeName !== 'Query' && typeName !== 'Mutation' && typeName !== 'Subscription') { schemaComposer.add(tc); } return tc; } static createTemp(typeDef, schemaComposer) { const sc = schemaComposer || new SchemaComposer(); let TC; if (isString(typeDef)) { const typeName = typeDef; if (TypeMapper.isTypeNameString(typeName)) { TC = new ObjectTypeComposer(new GraphQLObjectType({ name: typeName, fields: () => ({}) }), sc); } else { TC = sc.typeMapper.convertSDLTypeDefinition(typeName); if (!(TC instanceof ObjectTypeComposer)) { throw new Error('You should provide correct GraphQLObjectType type definition. ' + 'Eg. `type MyType { name: String }`'); } } } else if (typeDef instanceof GraphQLObjectType) { TC = new ObjectTypeComposer(typeDef, sc); } else if (typeDef instanceof ObjectTypeComposer) { return typeDef; } else if (isObject(typeDef)) { const type = new GraphQLObjectType(_objectSpread({}, typeDef, { fields: () => ({}) })); TC = new ObjectTypeComposer(type, sc); const fields = typeDef.fields; if (isFunction(fields)) { // `convertOutputFieldMapToConfig` helps to solve hoisting problems // rewrap fields `() => { f1: { type: A } }` -> `{ f1: { type: () => A } }` TC.addFields(convertObjectFieldMapToConfig(fields, sc)); } else if (isObject(fields)) { TC.addFields(fields); } const interfaces = typeDef.interfaces; if (Array.isArray(interfaces)) TC.setInterfaces(interfaces);else if (isFunction(interfaces)) { // rewrap interfaces `() => [i1, i2]` -> `[()=>i1, ()=>i2]` // helps to solve hoisting problems TC.setInterfaces(convertInterfaceArrayAsThunk(interfaces, sc)); } TC._gqcExtensions = typeDef.extensions || {}; } else { throw new Error(`You should provide GraphQLObjectTypeConfig or string with type name to ObjectTypeComposer.create(opts). Provided:\n${inspect(typeDef)}`); } return TC; } constructor(graphqlType, schemaComposer) { if (!(schemaComposer instanceof SchemaComposer)) { throw new Error('You must provide SchemaComposer instance as a second argument for `new ObjectTypeComposer(GraphQLObjectType, SchemaComposer)`'); } if (!(graphqlType instanceof GraphQLObjectType)) { throw new Error('ObjectTypeComposer accept only GraphQLObjectType in constructor'); } this.schemaComposer = schemaComposer; this._gqType = graphqlType; // add itself to TypeStorage on create // it avoids recursive type use errors this.schemaComposer.set(graphqlType, this); if (graphqlVersion >= 14) { this._gqcFields = convertObjectFieldMapToConfig(this._gqType._fields, this.schemaComposer); this._gqcInterfaces = convertInterfaceArrayAsThunk(this._gqType._interfaces, this.schemaComposer); } else { const fields = this._gqType._typeConfig.fields; this._gqcFields = this.schemaComposer.typeMapper.convertOutputFieldConfigMap(resolveMaybeThunk(fields) || {}, this.getTypeName()); this._gqcInterfaces = convertInterfaceArrayAsThunk(this._gqType._interfaces || this._gqType._typeConfig.interfaces, this.schemaComposer); } // alive proper Flow type casting in autosuggestions for class with Generics /* :: return this; */ } // ----------------------------------------------- // Field methods // ----------------------------------------------- getFields() { return this._gqcFields; } getFieldNames() { return Object.keys(this._gqcFields); } getField(fieldName) { const field = this._gqcFields[fieldName]; if (!field) { throw new Error(`Cannot get field '${fieldName}' from type '${this.getTypeName()}'. Field does not exist.`); } return field; } hasField(fieldName) { return !!this._gqcFields[fieldName]; } setFields(fields) { this._gqcFields = this.schemaComposer.typeMapper.convertOutputFieldConfigMap(fields); return this; } setField(fieldName, fieldConfig) { this._gqcFields[fieldName] = this.schemaComposer.typeMapper.convertOutputFieldConfig(fieldConfig); return this; } /** * Add new fields or replace existed in a GraphQL type */ addFields(newFields) { this._gqcFields = _objectSpread({}, this._gqcFields, {}, this.schemaComposer.typeMapper.convertOutputFieldConfigMap(newFields)); return this; } /** * Add new fields or replace existed (where field name may have dots) */ addNestedFields(newFields) { Object.keys(newFields).forEach(fieldName => { const fc = newFields[fieldName]; const names = fieldName.split('.'); const name = names.shift(); if (names.length === 0) { // single field this.setField(name, fc); } else { // nested field let childTC; if (!this.hasField(name)) { childTC = ObjectTypeComposer.create(`${this.getTypeName()}${upperFirst(name)}`, this.schemaComposer); this.setField(name, { type: childTC, resolve: () => ({}) }); } else { childTC = this.getFieldTC(name); } if (childTC instanceof ObjectTypeComposer) { childTC.addNestedFields({ [names.join('.')]: fc }); } } }); return this; } removeField(fieldNameOrArray) { const fieldNames = Array.isArray(fieldNameOrArray) ? fieldNameOrArray : [fieldNameOrArray]; fieldNames.forEach(fieldName => delete this._gqcFields[fieldName]); return this; } removeOtherFields(fieldNameOrArray) { const keepFieldNames = Array.isArray(fieldNameOrArray) ? fieldNameOrArray : [fieldNameOrArray]; Object.keys(this._gqcFields).forEach(fieldName => { if (keepFieldNames.indexOf(fieldName) === -1) { delete this._gqcFields[fieldName]; } }); return this; } reorderFields(names) { const orderedFields = {}; const fields = this._gqcFields; names.forEach(name => { if (fields[name]) { orderedFields[name] = fields[name]; delete fields[name]; } }); this._gqcFields = _objectSpread({}, orderedFields, {}, fields); return this; } extendField(fieldName, partialFieldConfig) { let prevFieldConfig; try { prevFieldConfig = this.getField(fieldName); } catch (e) { throw new Error(`Cannot extend field '${fieldName}' from type '${this.getTypeName()}'. Field does not exist.`); } this.setField(fieldName, _objectSpread({}, prevFieldConfig, {}, partialFieldConfig, { extensions: _objectSpread({}, prevFieldConfig.extensions || {}, {}, partialFieldConfig.extensions || {}) })); return this; } getFieldConfig(fieldName) { const _this$getField = this.getField(fieldName), { type, args } = _this$getField, rest = _objectWithoutProperties(_this$getField, ["type", "args"]); return _objectSpread({ type: type.getType(), args: args && mapEachKey(args, ac => _objectSpread({}, ac, { type: ac.type.getType() })) }, rest); } getFieldType(fieldName) { return this.getField(fieldName).type.getType(); } getFieldTypeName(fieldName) { return this.getField(fieldName).type.getTypeName(); } /** * Automatically unwrap from List, NonNull, ThunkComposer * It's important! Cause greatly helps to modify fields types in a real code * without manual unwrap writing. * * If you need to work with wrappers, you may use the following code: * - `TC.getField().type` // returns real wrapped TypeComposer * - `TC.isFieldNonNull()` // checks is field NonNull or not * - `TC.makeFieldNonNull()` // for wrapping in NonNullComposer * - `TC.makeFieldNullable()` // for unwrapping from NonNullComposer * - `TC.isFieldPlural()` // checks is field wrapped in ListComposer or not * - `TC.makeFieldPlural()` // for wrapping in ListComposer * - `TC.makeFieldNonPlural()` // for unwrapping from ListComposer */ getFieldTC(fieldName) { const anyTC = this.getField(fieldName).type; return unwrapOutputTC(anyTC); } /** * Alias for `getFieldTC()` but returns statically checked ObjectTypeComposer. * If field have other type then error will be thrown. */ getFieldOTC(fieldName) { const tc = this.getFieldTC(fieldName); if (!(tc instanceof ObjectTypeComposer)) { throw new Error(`${this.getTypeName()}.getFieldOTC('${fieldName}') must be ObjectTypeComposer, but recieved ${tc.constructor.name}. Maybe you need to use 'getFieldTC()' method which returns any type composer?`); } return tc; } isFieldNonNull(fieldName) { return this.getField(fieldName).type instanceof NonNullComposer; } makeFieldNonNull(fieldNameOrArray) { const fieldNames = Array.isArray(fieldNameOrArray) ? fieldNameOrArray : [fieldNameOrArray]; fieldNames.forEach(fieldName => { const fc = this._gqcFields[fieldName]; if (fc && !(fc.type instanceof NonNullComposer)) { fc.type = new NonNullComposer(fc.type); } }); return this; } makeFieldNullable(fieldNameOrArray) { const fieldNames = Array.isArray(fieldNameOrArray) ? fieldNameOrArray : [fieldNameOrArray]; fieldNames.forEach(fieldName => { const fc = this._gqcFields[fieldName]; if (fc && fc.type instanceof NonNullComposer) { fc.type = fc.type.ofType; } }); return this; } isFieldPlural(fieldName) { const type = this.getField(fieldName).type; return type instanceof ListComposer || type instanceof NonNullComposer && type.ofType instanceof ListComposer; } makeFieldPlural(fieldNameOrArray) { const fieldNames = Array.isArray(fieldNameOrArray) ? fieldNameOrArray : [fieldNameOrArray]; fieldNames.forEach(fieldName => { const fc = this._gqcFields[fieldName]; if (fc && !(fc.type instanceof ListComposer)) { fc.type = new ListComposer(fc.type); } }); return this; } makeFieldNonPlural(fieldNameOrArray) { const fieldNames = Array.isArray(fieldNameOrArray) ? fieldNameOrArray : [fieldNameOrArray]; fieldNames.forEach(fieldName => { const fc = this._gqcFields[fieldName]; if (fc) { if (fc.type instanceof ListComposer) { fc.type = fc.type.ofType; } else if (fc.type instanceof NonNullComposer && fc.type.ofType instanceof ListComposer) { fc.type = fc.type.ofType.ofType instanceof NonNullComposer ? fc.type.ofType.ofType : new NonNullComposer(fc.type.ofType.ofType); } } }); return this; } deprecateFields(fields) { const existedFieldNames = this.getFieldNames(); if (typeof fields === 'string') { if (existedFieldNames.indexOf(fields) === -1) { throw new Error(`Cannot deprecate unexisted field '${fields}' from type '${this.getTypeName()}'`); } this.extendField(fields, { deprecationReason: 'deprecated' }); } else if (Array.isArray(fields)) { fields.forEach(field => { if (existedFieldNames.indexOf(field) === -1) { throw new Error(`Cannot deprecate unexisted field '${field}' from type '${this.getTypeName()}'`); } this.extendField(field, { deprecationReason: 'deprecated' }); }); } else { const fieldMap = fields; Object.keys(fieldMap).forEach(field => { if (existedFieldNames.indexOf(field) === -1) { throw new Error(`Cannot deprecate unexisted field '${field}' from type '${this.getTypeName()}'`); } const deprecationReason = fieldMap[field]; this.extendField(field, { deprecationReason }); }); } return this; } /** * ----------------------------------------------- * Field Args methods * ----------------------------------------------- */ getFieldArgs(fieldName) { try { const fc = this.getField(fieldName); return fc.args || {}; } catch (e) { throw new Error(`Cannot get args from '${this.getTypeName()}.${fieldName}'. Field does not exist.`); } } getFieldArgNames(fieldName) { return Object.keys(this.getFieldArgs(fieldName)); } hasFieldArg(fieldName, argName) { try { const fieldArgs = this.getFieldArgs(fieldName); return !!fieldArgs[argName]; } catch (e) { return false; } } getFieldArg(fieldName, argName) { const fieldArgs = this.getFieldArgs(fieldName); const arg = fieldArgs[argName]; if (!arg) { throw new Error(`Cannot get '${this.getTypeName()}.${fieldName}@${argName}'. Argument does not exist.`); } return arg; } getFieldArgType(fieldName, argName) { const ac = this.getFieldArg(fieldName, argName); return ac.type.getType(); } getFieldArgTypeName(fieldName, argName) { const ac = this.getFieldArg(fieldName, argName); return ac.type.getTypeName(); } /** * Automatically unwrap from List, NonNull, ThunkComposer * It's important! Cause greatly helps to modify args types in a real code * without manual unwrap writing. * * If you need to work with wrappers, you may use the following code: * `isFieldArgPlural()` – checks is arg wrapped in ListComposer or not * `makeFieldArgPlural()` – for arg wrapping in ListComposer * `makeFieldArgNonPlural()` – for arg unwrapping from ListComposer * `isFieldArgNonNull()` – checks is arg wrapped in NonNullComposer or not * `makeFieldArgNonNull()` – for arg wrapping in NonNullComposer * `makeFieldArgNullable()` – for arg unwrapping from NonNullComposer */ getFieldArgTC(fieldName, argName) { const anyTC = this.getFieldArg(fieldName, argName).type; // Unwrap from List, NonNull and ThunkComposer return unwrapInputTC(anyTC); } /** * Alias for `getFieldArgTC()` but returns statically checked InputTypeComposer. * If field have other type then error will be thrown. */ getFieldArgITC(fieldName, argName) { const tc = this.getFieldArgTC(fieldName, argName); if (!(tc instanceof InputTypeComposer)) { throw new Error(`${this.getTypeName()}.getFieldArgITC('${fieldName}', '${argName}') must be InputTypeComposer, but recieved ${tc.constructor.name}. Maybe you need to use 'getFieldArgTC()' method which returns any type composer?`); } return tc; } setFieldArgs(fieldName, args) { const fc = this.getField(fieldName); fc.args = this.schemaComposer.typeMapper.convertArgConfigMap(args, fieldName, this.getTypeName()); return this; } addFieldArgs(fieldName, newArgs) { const fc = this.getField(fieldName); fc.args = _objectSpread({}, fc.args, {}, this.schemaComposer.typeMapper.convertArgConfigMap(newArgs, fieldName, this.getTypeName())); return this; } setFieldArg(fieldName, argName, argConfig) { const fc = this.getField(fieldName); fc.args = fc.args || {}; fc.args[argName] = this.schemaComposer.typeMapper.convertArgConfig(argConfig, argName, fieldName, this.getTypeName()); return this; } removeFieldArg(fieldName, argNameOrArray) { const argNames = Array.isArray(argNameOrArray) ? argNameOrArray : [argNameOrArray]; const args = this._gqcFields[fieldName] && this._gqcFields[fieldName].args; if (args) { argNames.forEach(argName => delete args[argName]); } return this; } removeFieldOtherArgs(fieldName, argNameOrArray) { const keepArgNames = Array.isArray(argNameOrArray) ? argNameOrArray : [argNameOrArray]; const args = this._gqcFields[fieldName] && this._gqcFields[fieldName].args; if (args) { Object.keys(args).forEach(argName => { if (keepArgNames.indexOf(argName) === -1) { delete args[argName]; } }); } return this; } isFieldArgPlural(fieldName, argName) { const type = this.getFieldArg(fieldName, argName).type; return type instanceof ListComposer || type instanceof NonNullComposer && type.ofType instanceof ListComposer; } makeFieldArgPlural(fieldName, argNameOrArray) { const args = this.getField(fieldName).args; if (!args) return this; const argNames = Array.isArray(argNameOrArray) ? argNameOrArray : [argNameOrArray]; argNames.forEach(argName => { const ac = args[argName]; if (ac && !(ac.type instanceof ListComposer)) { ac.type = new ListComposer(ac.type); } }); return this; } makeFieldArgNonPlural(fieldName, argNameOrArray) { const args = this.getField(fieldName).args; if (!args) return this; const argNames = Array.isArray(argNameOrArray) ? argNameOrArray : [argNameOrArray]; argNames.forEach(argName => { const ac = args[argName]; if (ac) { if (ac.type instanceof ListComposer) { ac.type = ac.type.ofType; } else if (ac.type instanceof NonNullComposer && ac.type.ofType instanceof ListComposer) { ac.type = ac.type.ofType.ofType instanceof NonNullComposer ? ac.type.ofType.ofType : new NonNullComposer(ac.type.ofType.ofType); } } }); return this; } isFieldArgNonNull(fieldName, argName) { const type = this.getFieldArg(fieldName, argName).type; return type instanceof NonNullComposer; } makeFieldArgNonNull(fieldName, argNameOrArray) { const args = this.getField(fieldName).args; if (!args) return this; const argNames = Array.isArray(argNameOrArray) ? argNameOrArray : [argNameOrArray]; argNames.forEach(argName => { const ac = args[argName]; if (ac && !(ac.type instanceof NonNullComposer)) { ac.type = new NonNullComposer(ac.type); } }); return this; } makeFieldArgNullable(fieldName, argNameOrArray) { const args = this.getField(fieldName).args; if (!args) return this; const argNames = Array.isArray(argNameOrArray) ? argNameOrArray : [argNameOrArray]; argNames.forEach(argName => { const ac = args[argName]; if (ac && ac.type instanceof NonNullComposer) { ac.type = ac.type.ofType; } }); return this; } // ----------------------------------------------- // Type methods // ----------------------------------------------- getType() { if (graphqlVersion >= 14) { this._gqType._fields = () => defineFieldMap(this._gqType, mapEachKey(this._gqcFields, (fc, name) => this.getFieldConfig(name))); this._gqType._interfaces = () => this._gqcInterfaces.map(i => i.getType()); } else { this._gqType._typeConfig.fields = () => { return mapEachKey(this._gqcFields, (fc, name) => this.getFieldConfig(name)); }; this._gqType._typeConfig.interfaces = () => this._gqcInterfaces.map(i => i.getType()); delete this._gqType._fields; // clear builded fields in type delete this._gqType._interfaces; } return this._gqType; } getTypePlural() { return new ListComposer(this); } getTypeNonNull() { return new NonNullComposer(this); } getTypeName() { return this._gqType.name; } setTypeName(name) { this._gqType.name = name; this.schemaComposer.add(this); return this; } getDescription() { return this._gqType.description || ''; } setDescription(description) { this._gqType.description = description; return this; } /** * You may clone this type with a new provided name as string. * Or you may provide a new TypeComposer which will get all clonned * settings from this type. */ clone(newTypeNameOrTC) { if (!newTypeNameOrTC) { throw new Error('You should provide newTypeName:string for ObjectTypeComposer.clone()'); } const cloned = newTypeNameOrTC instanceof ObjectTypeComposer ? newTypeNameOrTC : ObjectTypeComposer.create(newTypeNameOrTC, this.schemaComposer); cloned._gqcFields = mapEachKey(this._gqcFields, fieldConfig => _objectSpread({}, fieldConfig, { args: mapEachKey(fieldConfig.args, argConfig => _objectSpread({}, argConfig, { extensions: _objectSpread({}, argConfig.extensions) })), extensions: _objectSpread({}, fieldConfig.extensions) })); cloned._gqcInterfaces = [...this._gqcInterfaces]; cloned._gqcExtensions = _objectSpread({}, this._gqcExtensions); cloned._gqcGetRecordIdFn = this._gqcGetRecordIdFn; cloned.setDescription(this.getDescription()); this.getResolvers().forEach(resolver => { const newResolver = resolver.clone(); cloned.addResolver(newResolver); }); return cloned; } getIsTypeOf() { return this._gqType.isTypeOf; } setIsTypeOf(fn) { this._gqType.isTypeOf = fn; return this; } merge(type) { if (type instanceof GraphQLObjectType) { const tmp = ObjectTypeComposer.createTemp(type, this.schemaComposer); this.addFields(tmp.getFields()); this.addInterfaces(tmp.getInterfaces()); } else if (type instanceof GraphQLInterfaceType) { const tmp = InterfaceTypeComposer.createTemp(type, this.schemaComposer); this.addFields(tmp.getFields()); } else if (type instanceof ObjectTypeComposer) { this.addFields(type.getFields()); this.addInterfaces(type.getInterfaces()); // Feel free to add other properties for merging two TypeComposers. // For simplicity it just merge fields and interfaces. } else if (type instanceof InterfaceTypeComposer) { this.addFields(type.getFields()); } else { throw new Error(`Cannot merge ${inspect(type)} with ObjectType(${this.getTypeName()}). Provided type should be GraphQLObjectType or ObjectTypeComposer.`); } return this; } // ----------------------------------------------- // InputType methods // ----------------------------------------------- getInputType() { return this.getInputTypeComposer().getType(); } hasInputTypeComposer() { return !!this._gqcInputTypeComposer; } setInputTypeComposer(itc) { this._gqcInputTypeComposer = itc; return this; } getInputTypeComposer() { if (!this._gqcInputTypeComposer) { this._gqcInputTypeComposer = toInputObjectType(this); } return this._gqcInputTypeComposer; } // Alias for getInputTypeComposer() getITC() { return this.getInputTypeComposer(); } removeInputTypeComposer() { this._gqcInputTypeComposer = undefined; return this; } // ----------------------------------------------- // Resolver methods // ----------------------------------------------- getResolvers() { if (!this._gqcResolvers) { this._gqcResolvers = new Map(); } return this._gqcResolvers; } hasResolver(name) { if (!this._gqcResolvers) { return false; } return this._gqcResolvers.has(name); } getResolver(name, middlewares) { if (!this.hasResolver(name)) { throw new Error(`Type ${this.getTypeName()} does not have resolver with name '${name}'`); } const resolverMap = this._gqcResolvers; const resolver = resolverMap.get(name); if (Array.isArray(middlewares)) { return resolver.withMiddlewares(middlewares); } return resolver; } setResolver(name, resolver) { if (!this._gqcResolvers) { this._gqcResolvers = new Map(); } if (!(resolver instanceof Resolver)) { throw new Error('setResolver() accept only Resolver instance'); } this._gqcResolvers.set(name, resolver); resolver.setDisplayName(`${this.getTypeName()}.${resolver.name}`); return this; } addResolver(opts) { if (!opts) { throw new Error('addResolver called with empty Resolver'); } let resolver; if (!(opts instanceof Resolver)) { const resolverOpts = _objectSpread({}, opts); // add resolve method, otherwise added resolver will not return any data by graphql-js if (!resolverOpts.hasOwnProperty('resolve')) { resolverOpts.resolve = () => ({}); } resolver = new Resolver(resolverOpts, this.schemaComposer); } else { resolver = opts; } if (!resolver.name) { throw new Error('resolver should have non-empty `name` property'); } this.setResolver(resolver.name, resolver); return this; } removeResolver(resolverName) { if (resolverName) { this.getResolvers().delete(resolverName); } return this; } wrapResolver(resolverName, cbResolver) { const resolver = this.getResolver(resolverName); const newResolver = resolver.wrap(cbResolver); this.setResolver(resolverName, newResolver); return this; } wrapResolverAs(resolverName, fromResolverName, cbResolver) { const resolver = this.getResolver(fromResolverName); const newResolver = resolver.wrap(cbResolver); this.setResolver(resolverName, newResolver); return this; } wrapResolverResolve(resolverName, cbNextRp) { const resolver = this.getResolver(resolverName); this.setResolver(resolverName, resolver.wrapResolve(cbNextRp)); return this; } // ----------------------------------------------- // Interface methods // ----------------------------------------------- getInterfaces() { return this._gqcInterfaces; } setInterfaces(interfaces) { this._gqcInterfaces = convertInterfaceArrayAsThunk(interfaces, this.schemaComposer); return this; } hasInterface(iface) { const typeName = getComposeTypeName(iface); return !!this._gqcInterfaces.find(i => i.getTypeName() === typeName); } addInterface(iface) { if (!this.hasInterface(iface)) { this._gqcInterfaces.push(this.schemaComposer.typeMapper.convertInterfaceTypeDefinition(iface)); } return this; } addInterfaces(ifaces) { if (!Array.isArray(ifaces)) { throw new Error(`ObjectTypeComposer[${this.getTypeName()}].addInterfaces() accepts only array`); } ifaces.forEach(iface => this.addInterface(iface)); return this; } removeInterface(iface) { const typeName = getComposeTypeName(iface); this._gqcInterfaces = this._gqcInterfaces.filter(i => i.getTypeName() !== typeName); return this; } // ----------------------------------------------- // Extensions methods // ----------------------------------------------- getExtensions() { if (!this._gqcExtensions) { return {}; } else { return this._gqcExtensions; } } setExtensions(extensions) { this._gqcExtensions = extensions; return this; } extendExtensions(extensions) { const current = this.getExtensions(); this.setExtensions(_objectSpread({}, current, {}, extensions)); return this; } clearExtensions() { this.setExtensions({}); return this; } getExtension(extensionName) { const extensions = this.getExtensions(); return extensions[extensionName]; } hasExtension(extensionName) { const extensions = this.getExtensions(); return extensionName in extensions; } setExtension(extensionName, value) { this.extendExtensions({ [extensionName]: value }); return this; } removeExtension(extensionName) { const extensions = _objectSpread({}, this.getExtensions()); delete extensions[extensionName]; this.setExtensions(extensions); return this; } getFieldExtensions(fieldName) { const field = this.getField(fieldName); return field.extensions || {}; } setFieldExtensions(fieldName, extensions) { const field = this.getField(fieldName); this.setField(fieldName, _objectSpread({}, field, { extensions })); return this; } extendFieldExtensions(fieldName, extensions) { const current = this.getFieldExtensions(fieldName); this.setFieldExtensions(fieldName, _objectSpread({}, current, {}, extensions)); return this; } clearFieldExtensions(fieldName) { this.setFieldExtensions(fieldName, {}); return this; } getFieldExtension(fieldName, extensionName) { const extensions = this.getFieldExtensions(fieldName); return extensions[extensionName]; } hasFieldExtension(fieldName, extensionName) { const extensions = this.getFieldExtensions(fieldName); return extensionName in extensions; } setFieldExtension(fieldName, extensionName, value) { this.extendFieldExtensions(fieldName, { [extensionName]: value }); return this; } removeFieldExtension(fieldName, extensionName) { const extensions = _objectSpread({}, this.getFieldExtensions(fieldName)); delete extensions[extensionName]; this.setFieldExtensions(fieldName, extensions); return this; } getFieldArgExtensions(fieldName, argName) { const ac = this.getFieldArg(fieldName, argName); return ac.extensions || {}; } setFieldArgExtensions(fieldName, argName, extensions) { const ac = this.getFieldArg(fieldName, argName); this.setFieldArg(fieldName, argName, _objectSpread({}, ac, { extensions })); return this; } extendFieldArgExtensions(fieldName, argName, extensions) { const current = this.getFieldArgExtensions(fieldName, argName); this.setFieldArgExtensions(fieldName, argName, _objectSpread({}, current, {}, extensions)); return this; } clearFieldArgExtensions(fieldName, argName) { this.setFieldArgExtensions(fieldName, argName, {}); return this; } getFieldArgExtension(fieldName, argName, extensionName) { const extensions = this.getFieldArgExtensions(fieldName, argName); return extensions[extensionName]; } hasFieldArgExtension(fieldName, argName, extensionName) { const extensions = this.getFieldArgExtensions(fieldName, argName); return extensionName in extensions; } setFieldArgExtension(fieldName, argName, extensionName, value) { this.extendFieldArgExtensions(fieldName, argName, { [extensionName]: value }); return this; } removeFieldArgExtension(fieldName, argName, extensionName) { const extensions = _objectSpread({}, this.getFieldArgExtensions(fieldName, argName)); delete extensions[extensionName]; this.setFieldArgExtensions(fieldName, argName, extensions); return this; } // ----------------------------------------------- // Directive methods // ----------------------------------------------- getDirectives() { const directives = this.getExtension('directives'); if (Array.isArray(directives)) { return directives; } return []; } getDirectiveNames() { return this.getDirectives().map(d => d.name); } getDirectiveByName(directiveName) { const directive = this.getDirectives().find(d => d.name === directiveName); if (!directive) return undefined; return directive.args; } getDirectiveById(idx) { const directive = this.getDirectives()[idx]; if (!directive) return undefined; return directive.args; } getFieldDirectives(fieldName) { const directives = this.getFieldExtension(fieldName, 'directives'); if (Array.isArray(directives)) { return directives; } return []; } getFieldDirectiveNames(fieldName) { return this.getFieldDirectives(fieldName).map(d => d.name); } getFieldDirectiveByName(fieldName, directiveName) { const directive = this.getFieldDirectives(fieldName).find(d => d.name === directiveName); if (!directive) return undefined; return directive.args; } getFieldDirectiveById(fieldName, idx) { const directive = this.getFieldDirectives(fieldName)[idx]; if (!directive) return undefined; return directive.args; } getFieldArgDirectives(fieldName, argName) { const directives = this.getFieldArgExtension(fieldName, argName, 'directives'); if (Array.isArray(directives)) { return directives; } return []; } getFieldArgDirectiveNames(fieldName, argName) { return this.getFieldArgDirectives(fieldName, argName).map(d => d.name); } getFieldArgDirectiveByName(fieldName, argName, directiveName) { const directive = this.getFieldArgDirectives(fieldName, argName).find(d => d.name === directiveName); if (!directive) return undefined; return directive.args; } getFieldArgDirectiveById(fieldName, argName, idx) { const directive = this.getFieldArgDirectives(fieldName, argName)[idx]; if (!directive) return undefined; return directive.args; } // ----------------------------------------------- // Misc methods // ----------------------------------------------- addRelation(fieldName, opts) { if (!this._gqcRelations) { this._gqcRelations = {}; } this._gqcRelations[fieldName] = opts; if (opts.hasOwnProperty('resolver')) { if (isFunction(opts.resolver)) { this._gqcFields[fieldName] = createThunkedObjectProxy(() => this._relationWithResolverToFC(opts, fieldName)); } else { this._gqcFields[fieldName] = this._relationWithResolverToFC(opts, fieldName); } } else if (opts.hasOwnProperty('type')) { const fc = opts; this.setField(fieldName, fc); } return this; } getRelations() { if (!this._gqcRelations) { this._gqcRelations = {}; } return this._gqcRelations; } _relationWithResolverToFC(opts, fieldName = '') { const resolver = isFunction(opts.resolver) ? opts.resolver() : opts.resolver; if (!(resolver instanceof Resolver)) { throw new Error('You should provide correct Resolver object for relation ' + `${this.getTypeName()}.${fieldName}`); } if (opts.type) { throw new Error('You can not use `resolver` and `type` properties simultaneously for relation ' + `${this.getTypeName()}.${fieldName}`); } if (opts.resolve) { throw new Error('You can not use `resolver` and `resolve` properties simultaneously for relation ' + `${this.getTypeName()}.${fieldName}`); } const argsConfig = _objectSpread({}, resolver.args); const argsProto = {}; const 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 const optsArgs = opts.prepareArgs || {}; Object.keys(optsArgs).forEach(argName => { const argMapVal = optsArgs[argName]; if (argMapVal !== undefined) { delete argsConfig[argName]; if (isFunction(argMapVal)) { argsRuntime.push([argName, argMapVal]); } else if (argMapVal !== null) { argsProto[argName] = argMapVal; } } }); // if opts.catchErrors is undefined then set true, otherwise take it value const { catchErrors = true } = opts; const fieldConfig = resolver.getFieldConfig(); const resolve = (source, args, context, info) => { const newArgs = _objectSpread({}, args, {}, argsProto); argsRuntime.forEach(([argName, argFn]) => { newArgs[argName] = argFn(source, args, context, info); }); const payload = fieldConfig.resolve ? fieldConfig.resolve(source, newArgs, context, info) : null; return catchErrors ? Promise.resolve(payload).catch(e => { // eslint-disable-next-line console.log(`GQC ERROR: relation for ${this.getTypeName()}.${fieldName} throws error:`); console.log(e); // eslint-disable-line return null; }) : payload; }; return { type: resolver.type, description: opts.description || resolver.description, deprecationReason: opts.deprecationReason, args: argsConfig, resolve, projection: opts.projection }; } setRecordIdFn(fn) { this._gqcGetRecordIdFn = fn; return this; } hasRecordIdFn() { return !!this._gqcGetRecordIdFn; } getRecordIdFn() { if (!this._gqcGetRecordIdFn) { throw new Error(`Type ${this.getTypeName()} does not have RecordIdFn`); } return this._gqcGetRecordIdFn; } /** * Get function that returns record id, from provided object. */ getRecordId(source, args, context) { return this.getRecordIdFn()(source, args, context); } get(path) { return typeByPath(this, path); } }