function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, 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 invariant from 'graphql/jsutils/invariant'; import { GraphQLUnionType, GraphQLObjectType, GraphQLList, GraphQLNonNull } from './graphql'; import { isObject, isString, isFunction } from './utils/is'; import { inspect } from './utils/misc'; import { ObjectTypeComposer } from './ObjectTypeComposer'; import { SchemaComposer } from './SchemaComposer'; import { resolveTypeArrayAsThunk } from './utils/configAsThunk'; import { getGraphQLType, getComposeTypeName } from './utils/typeHelpers'; import { graphqlVersion } from './utils/graphqlVersion'; export class UnionTypeComposer { // Also supported `GraphQLUnionType` but in such case Flowtype force developers // to explicitly write annotations in their code. But it's bad. static create(typeDef, schemaComposer) { if (!(schemaComposer instanceof SchemaComposer)) { throw new Error('You must provide SchemaComposer instance as a second argument for `UnionTypeComposer.create(typeDef, schemaComposer)`'); } const utc = this.createTemp(typeDef, schemaComposer); schemaComposer.add(utc); return utc; } static createTemp(typeDef, schemaComposer) { const sc = schemaComposer || new SchemaComposer(); let UTC; if (isString(typeDef)) { const typeName = typeDef; const NAME_RX = /^[_a-zA-Z][_a-zA-Z0-9]*$/; if (NAME_RX.test(typeName)) { UTC = new UnionTypeComposer(new GraphQLUnionType({ name: typeName, types: () => [] }), sc); } else { UTC = sc.typeMapper.createType(typeName); if (!(UTC instanceof UnionTypeComposer)) { throw new Error('You should provide correct GraphQLUnionType type definition.' + 'Eg. `union MyType = Photo | Person`'); } } } else if (typeDef instanceof GraphQLUnionType) { UTC = new UnionTypeComposer(typeDef, sc); } else if (isObject(typeDef)) { const types = typeDef.types; const type = new GraphQLUnionType(_objectSpread({}, typeDef, { types: isFunction(types) ? () => resolveTypeArrayAsThunk(sc, types(), typeDef.name) : () => [] })); UTC = new UnionTypeComposer(type, sc); if (Array.isArray(types)) UTC.setTypes(types); UTC.gqType._gqcExtensions = typeDef.extensions || {}; } else { throw new Error(`You should provide GraphQLUnionTypeConfig or string with union name or SDL definition. Provided:\n${inspect(typeDef)}`); } return UTC; } constructor(gqType, schemaComposer) { if (!(schemaComposer instanceof SchemaComposer)) { throw new Error('You must provide SchemaComposer instance as a second argument for `new UnionTypeComposer(GraphQLUnionType, SchemaComposer)`'); } this.schemaComposer = schemaComposer; if (!(gqType instanceof GraphQLUnionType)) { throw new Error('UnionTypeComposer accept only GraphQLUnionType in constructor. Try to use more flexible method `UnionTypeComposer.create()`.'); } this.gqType = gqType; // alive proper Flow type casting in autosuggestions for class with Generics /* :: return this; */ } // ----------------------------------------------- // Union Types methods // ----------------------------------------------- hasType(name) { const nameAsString = getComposeTypeName(name); return this.getTypeNames().includes(nameAsString); } _getTypeMap() { if (!this.gqType._gqcTypeMap) { const types = this.gqType.getTypes(); const m = new Map(); types.forEach(type => { m.set(getComposeTypeName(type), type); }); this.gqType._gqcTypeMap = m; if (graphqlVersion >= 14) { this.gqType._types = () => { return resolveTypeArrayAsThunk(this.schemaComposer, this.getTypes(), this.getTypeName()); }; } else { this.gqType._types = null; this.gqType._typeConfig.types = () => { return resolveTypeArrayAsThunk(this.schemaComposer, this.getTypes(), this.getTypeName()); }; } } return this.gqType._gqcTypeMap; } getTypes() { return Array.from(this._getTypeMap().values()); } getTypeNames() { return Array.from(this._getTypeMap().keys()); } clearTypes() { this._getTypeMap().clear(); return this; } setTypes(types) { this.clearTypes(); types.forEach(type => { this._getTypeMap().set(getComposeTypeName(type), type); }); return this; } addType(type) { this._getTypeMap().set(getComposeTypeName(type), type); return this; } removeType(nameOrArray) { const typeNames = Array.isArray(nameOrArray) ? nameOrArray : [nameOrArray]; typeNames.forEach(typeName => { this._getTypeMap().delete(typeName); }); return this; } removeOtherTypes(nameOrArray) { const keepTypeNames = Array.isArray(nameOrArray) ? nameOrArray : [nameOrArray]; this._getTypeMap().forEach((v, i) => { if (keepTypeNames.indexOf(i) === -1) { this._getTypeMap().delete(i); } }); return this; } // ----------------------------------------------- // Type methods // ----------------------------------------------- getType() { return this.gqType; } getTypePlural() { return new GraphQLList(this.gqType); } getTypeNonNull() { return new GraphQLNonNull(this.gqType); } 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; } clone(newTypeName) { if (!newTypeName) { throw new Error('You should provide newTypeName:string for UnionTypeComposer.clone()'); } const cloned = UnionTypeComposer.create(newTypeName, this.schemaComposer); cloned.setTypes(this.getTypes()); cloned.setDescription(this.getDescription()); return cloned; } merge(type) { if (type instanceof GraphQLUnionType) { type.getTypes().forEach(t => this.addType(t)); } else if (type instanceof UnionTypeComposer) { type.getTypes().forEach(t => this.addType(t)); } else { throw new Error(`Cannot merge ${inspect(type)} with UnionType(${this.getTypeName()}). Provided type should be GraphQLUnionType or UnionTypeComposer.`); } return this; } // ----------------------------------------------- // ResolveType methods // ----------------------------------------------- getResolveType() { return this.gqType.resolveType; } setResolveType(fn) { this.gqType.resolveType = fn; return this; } hasTypeResolver(type) { const typeResolversMap = this.getTypeResolvers(); return typeResolversMap.has(type); } getTypeResolvers() { if (!this.gqType._gqcTypeResolvers) { this.gqType._gqcTypeResolvers = new Map(); } return this.gqType._gqcTypeResolvers; } getTypeResolverCheckFn(type) { const typeResolversMap = this.getTypeResolvers(); if (!typeResolversMap.has(type)) { throw new Error(`Type resolve function in union '${this.getTypeName()}' is not defined for type ${inspect(type)}.`); } return typeResolversMap.get(type); } getTypeResolverNames() { const typeResolversMap = this.getTypeResolvers(); const names = []; typeResolversMap.forEach((resolveFn, composeType) => { if (composeType instanceof ObjectTypeComposer) { names.push(composeType.getTypeName()); } else if (composeType && typeof composeType.name === 'string') { names.push(composeType.name); } }); return names; } getTypeResolverTypes() { const typeResolversMap = this.getTypeResolvers(); const types = []; typeResolversMap.forEach((resolveFn, composeType) => { types.push(getGraphQLType(composeType)); }); return types; } setTypeResolvers(typeResolversMap) { this._isTypeResolversValid(typeResolversMap); this.gqType._gqcTypeResolvers = typeResolversMap; // extract GraphQLObjectType from ObjectTypeComposer const fastEntries = []; for (const [composeType, checkFn] of typeResolversMap.entries()) { fastEntries.push([getGraphQLType(composeType), checkFn]); this.addType(composeType); } let resolveType; const isAsyncRuntime = this._isTypeResolversAsync(typeResolversMap); if (isAsyncRuntime) { resolveType = async (value, context, info) => { for (const [gqType, checkFn] of fastEntries) { // should we run checkFn simultaniously or in serial? // Current decision is: dont SPIKE event loop - run in serial (it may be changed in future) // eslint-disable-next-line no-await-in-loop if (await checkFn(value, context, info)) return gqType; } return null; }; } else { resolveType = (value, context, info) => { for (const [gqType, checkFn] of fastEntries) { if (checkFn(value, context, info)) return gqType; } return null; }; } this.setResolveType(resolveType); return this; } _isTypeResolversValid(typeResolversMap) { if (!(typeResolversMap instanceof Map)) { throw new Error(`For union ${this.getTypeName()} you should provide Map object for type resolvers.`); } for (const [composeType, checkFn] of typeResolversMap.entries()) { // checking composeType try { const type = getGraphQLType(composeType); if (!(type instanceof GraphQLObjectType)) throw new Error('Must be GraphQLObjectType'); } catch (e) { throw new Error(`For union type resolver ${this.getTypeName()} you must provide GraphQLObjectType or ObjectTypeComposer, but provided ${inspect(composeType)}`); } // checking checkFn if (!isFunction(checkFn)) { throw new Error(`Union ${this.getTypeName()} has invalid check function for type ${inspect(composeType)}`); } } return true; } // eslint-disable-next-line class-methods-use-this _isTypeResolversAsync(typeResolversMap) { let res = false; for (const [, checkFn] of typeResolversMap.entries()) { try { const r = checkFn({}, {}, {}); if (r instanceof Promise) { r.catch(() => {}); res = true; } } catch (e) {// noop } } return res; } addTypeResolver(type, checkFn) { const typeResolversMap = this.getTypeResolvers(); typeResolversMap.set(type, checkFn); this.setTypeResolvers(typeResolversMap); return this; } removeTypeResolver(type) { const typeResolversMap = this.getTypeResolvers(); typeResolversMap.delete(type); this.setTypeResolvers(typeResolversMap); return this; } // ----------------------------------------------- // Extensions methods // ----------------------------------------------- getExtensions() { if (!this.gqType._gqcExtensions) { return {}; } else { return this.gqType._gqcExtensions; } } setExtensions(extensions) { this.gqType._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; } // ----------------------------------------------- // 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; } // ----------------------------------------------- // Misc methods // ----------------------------------------------- // get(path: string | string[]): any { // return typeByPath(this, path); // } }