UNPKG

mgs-graphql

Version:

The simple way to generates GraphQL schemas and Sequelize models from your models definition,microservice supported

228 lines (205 loc) 7.13 kB
// @flow const { isOutputType, GraphQLNonNull, GraphQLUnionType, GraphQLInterfaceType, GraphQLObjectType, GraphQLList } = require('graphql') const _ = require('lodash') const { mergeSchemas } = require('graphql-tools') const { SchemaVisitor, visitSchema, healSchema } = require('graphql-tools/dist/schemaVisitor') const invariant = require('../utils/invariant') const helper = require('../utils/helper') let otherTypes = {} class SchemaRemoteVisitor extends SchemaVisitor { static visitTheSchema (schema, context = Object.create(null)) { function visitorSelector (type, methodName) { const visitors = [] if (methodName !== 'visitFieldDefinition') { return visitors } const isStub = (type) => { return (type instanceof GraphQLObjectType) && type.name.startsWith(context.prefix) && !_.isEmpty(type.description) && type.description.startsWith('{') && type.description.endsWith('}') } let remoteType = null let visitedType = null if (type.type instanceof GraphQLList) { const element = type.type.ofType if (isStub(element)) { visitedType = type remoteType = element } } else if (type.type instanceof GraphQLNonNull) { if (isStub(type.type.ofType)) { visitedType = type remoteType = type.type.ofType } } else if (isStub(type.type)) { visitedType = type remoteType = type.type } if (remoteType != null && visitedType != null) { try { const info = JSON.parse(remoteType.description) if (!_.isEmpty(info)) { // console.log(`visitorSelector got it:${visitedType.name},${remoteType.name},${methodName}:`, remoteType.description) visitors.push(new RemoteDirective({ name: 'remote', args: {...info}, visitedType: visitedType, schema, context })) return visitors } } catch (err) { console.warn('visitorSelector:', err) } } return visitors } visitSchema(schema, visitorSelector) healSchema(schema) return schema } constructor (config) { super() this.name = config.name this.args = config.args this.visitedType = config.visitedType this.schema = config.schema this.context = config.context } } class RemoteDirective extends SchemaRemoteVisitor { visitFieldDefinition (field) { invariant(!_.isEmpty(this.args), 'Must provide args') const getTargetSchema = (modeName, srcSchemas) => { if (_.isEmpty(srcSchemas)) { return } let found = null _.forOwn(srcSchemas, (value, key) => { if (!value) { return } let type = value.getType(modeName) if (type) { if (found && found.obj) { if (helper.calcRemoteLevels(found.obj.description) > helper.calcRemoteLevels(type.description)) { found = { obj: type, schemaName: key } } } else { found = { obj: type, schemaName: key } } } }) return found } const addMergedObject = (schemaName, obj) => { if (obj instanceof GraphQLList) { addMergedObject(schemaName, obj.ofType) } else if (obj instanceof GraphQLNonNull) { addMergedObject(schemaName, obj.ofType) } else if (obj instanceof GraphQLObjectType || obj instanceof GraphQLInterfaceType) { if (!otherTypes[schemaName][obj.name]) { // console.log('addObj1:',schemaName,obj.name,obj.description) // invariant(!obj.description || !obj.description.startsWith('__'), // `graph object ${obj.name} in ${schemaName}'s description invalid:${obj.description ? obj.description : ''}`) otherTypes[schemaName][obj.name] = obj obj.description = '__' + (obj.description ? obj.description : '') const fields = obj.getFields() _.forOwn(fields, (value, key) => { const args = value.args for (let i = 0; i < args.length; ++i) { addMergedObject(schemaName, args[i].type) } addMergedObject(schemaName, value.type) }) } } else if (obj instanceof GraphQLUnionType) { const types = obj.getTypes() for (let i = 0; i < types.length; ++i) { addMergedObject(schemaName, types[i]) } } else { if (!otherTypes[schemaName][obj.name]) { // console.log('addObj2:',schemaName,obj.name,obj.description) // invariant(!obj.description || !obj.description.startsWith('__'), // `graph object ${obj.name} in ${schemaName}'s description invalid:${obj.description ? obj.description : ''}`) otherTypes[schemaName][obj.name] = obj obj.description = '__' + (obj.description ? obj.description : '') } } } const gqlObj = getTargetSchema(this.args.target, this.context.srcSchema) invariant(!gqlObj || isOutputType(gqlObj.obj), `invalid remote link ${field.name} => ${this.args.target}: not output type(maybe null)`) if (gqlObj && gqlObj.obj) { // console.log('match:',gqlObj.schemaName,gqlObj.obj.name) addMergedObject(gqlObj.schemaName, gqlObj.obj) invariant(otherTypes[gqlObj.schemaName][gqlObj.obj.name] === gqlObj.obj, `Must same output graphql object:${gqlObj.obj.name}`) if (field.type instanceof GraphQLList) { field.type = new GraphQLList(gqlObj.obj) } else { field.type = gqlObj.obj } } } } function mergeAllSchemas (schema, schemaMerged, resolvers, prefix) { if (_.isEmpty(schemaMerged)) { return schema } otherTypes = _.mapValues(schemaMerged, (value) => { return {} }) SchemaRemoteVisitor.visitTheSchema(schema, { prefix, srcSchema: schemaMerged }) // update by yy on 2009/1/31, 因为只作为API调用,不引用Schema时,是找不到有关联scehma的。 // _.forOwn(otherTypes, (value, key) => { // if (_.isEmpty(value)) { // throw new Warn(`merged schema ${key}:none of schema is merging`) // } // }) let schemas = [schema] _.forOwn(otherTypes, (objs, schemaName) => { let curSchema = [] _.forOwn(objs, (obj, name) => { curSchema.push(obj) }) schemas.push(curSchema) }) return mergeSchemas({ schemas: schemas, resolvers, onTypeConflict: (left, right) => { if (!left.description || !left.description.startsWith('__')) { return left } if (!right.description || !right.description.startsWith('__')) { return right } return left } }) } module.exports = { mergeAllSchemas }