UNPKG

loopback-graphql

Version:

Add Apollo Server or GraphQL queries on your Loopback server

374 lines (344 loc) 9.83 kB
import * as _ from 'lodash'; import { connectionTypeName, singularModelName, pluralModelName, methodName, edgeTypeName, sharedRelations, idToCursor, } from './utils'; import { findRelated, findAll, findOne, resolveConnection } from './execution'; import { IProperty, ITypesHash } from './interfaces'; /*** Loopback Types - GraphQL types any - JSON Array - [JSON] Boolean = boolean Buffer - not supported Date - Date (custom scalar) GeoPoint - not supported null - not supported Number = float Object = JSON (custom scalar) String - string ***/ let types: ITypesHash = {}; const exchangeTypes = { 'any': 'JSON', 'Any': 'JSON', 'Number': 'Int', 'number': 'Int', 'Object': 'JSON', 'object': 'JSON', }; const SCALARS = { any: 'JSON', number: 'Float', string: 'String', boolean: 'Boolean', objectid: 'ID', date: 'Date', object: 'JSON', now: 'Date', guid: 'ID', uuid: 'ID', uuidv4: 'ID', geopoint: 'GeoPoint', }; const PAGINATION = 'where: JSON, after: String, first: Int, before: String, last: Int'; const IDPARAMS = 'id: ID!'; function getScalar(type: string) { return SCALARS[type.toLowerCase().trim()]; } function toTypes(union: string[]) { return _.map(union, type => { return getScalar(type) ? getScalar(type) : type; }); } function mapProperty(model: any, property: any, modelName: string, propertyName: string) { if (property.deprecated) { return; } types[modelName].fields[propertyName] = { required: property.required, hidden: model.definition.settings.hidden && model.definition.settings.hidden.indexOf(propertyName) !== -1, }; let currentProperty = types[modelName].fields[propertyName]; let typeName = `${modelName}_${propertyName}`; let propertyType = property.type; if (propertyType.name === 'Array') { // JSON Array currentProperty.list = true; currentProperty.gqlType = 'JSON'; currentProperty.scalar = true; return; } if (_.isArray(property.type)) { currentProperty.list = true; propertyType = property.type[0]; } let scalar = getScalar(propertyType.name); if (property.defaultFn) { scalar = getScalar(property.defaultFn); } if (scalar) { currentProperty.scalar = true; currentProperty.gqlType = scalar; if (property.enum) { // enum has a dedicated type but no input type is required types[typeName] = { values: property.enum, category: 'ENUM', }; currentProperty.gqlType = typeName; } } if (propertyType.name === 'ModelConstructor' && property.defaultFn !== 'now') { currentProperty.gqlType = propertyType.modelName; let union = propertyType.modelName.split('|'); //type is a union if (union.length > 1) { // union type types[typeName] = { // creating a new union type category: 'UNION', values: toTypes(union), }; } else if (propertyType.settings && propertyType.settings.anonymous && propertyType.definition) { currentProperty.gqlType = typeName; types[typeName] = { category: 'TYPE', input: true, fields: {}, }; // creating a new type _.forEach(propertyType.definition.properties, (p, key) => { mapProperty(propertyType, p, typeName, key); }); } } } function mapRelation(rel: any, modelName: string, relName: string) { types[modelName].fields[relName] = { relation: true, embed: rel.embed, gqlType: connectionTypeName(rel.modelTo), args: PAGINATION, resolver: (obj, args, context) => { return findRelated(rel, obj, args, context); }, }; } /* function generateReturns(name, props) { if (_.isObject(props)) { props = [props]; } let args; args = _.map(props, prop => { if (_.isArray(prop.type)) { return `${prop.arg}: [${toType(prop.type[0])}]${prop.required ? '!' : ''}`; } else if (toType(prop.type)) { return `${prop.arg}: ${toType(prop.type)}${prop.required ? '!' : ''}`; } return ''; }).join(' \n '); return args ? `{${args}}` : ''; } function generateAccepts(name, props) { let ret = _.map(props, prop => { let propType = prop.type; if (_.isArray(prop.type)) { propType = prop.type[0]; } return propType ? `${prop.arg}: [${toType(prop.type[0])}]${prop.required ? '!' : ''}` : ''; }).join(' \n '); return ret ? `(${ret})` : ''; } */ function addRemoteHooks(model: any) { _.map(model.sharedClass._methods, (method: any) => { if (method.accessType !== 'READ' && method.http.path) { let acceptingParams = '', returnType = 'JSON'; method.accepts.map(function (param) { let paramType = ''; if (typeof param.type === 'object') { paramType = 'JSON'; } else { if (!SCALARS[param.type.toLowerCase()]) { paramType = `${param.type}Input`; } else { paramType = _.upperFirst(param.type); } } if (param.arg) { acceptingParams += `${param.arg}: ${exchangeTypes[paramType] || paramType} `; } }); if (method.returns && method.returns[0]) { if (!SCALARS[method.returns[0].type] && typeof method.returns[0].type !== 'object') { returnType = `${method.returns[0].type}`; } else { returnType = `${_.upperFirst(method.returns[0].type)}`; if (typeof method.returns[0].type === 'object') { returnType = 'JSON'; } } } types.Mutation.fields[`${methodName(method, model)}`] = { relation: true, args: acceptingParams, gqlType: `${exchangeTypes[returnType] || returnType}`, }; } }); } function mapRoot(model) { types.Query.fields[singularModelName(model)] = { relation: true, args: IDPARAMS, root: true, gqlType: singularModelName(model), resolver: (obj, args, context) => { findOne(model, obj, args, context); }, }; types.Query.fields[pluralModelName(model)] = { relation: true, root: true, args: PAGINATION, gqlType: connectionTypeName(model), resolver: (obj, args, context) => { findAll(model, obj, args, context); }, }; types.Mutation.fields[`save${singularModelName(model)}`] = { relation: true, args: `obj: ${singularModelName(model)}Input!`, gqlType: singularModelName(model), resolver: (context, args) => model.upsert(args.obj), }; types.Mutation.fields[`delete${singularModelName(model)}`] = { relation: true, args: IDPARAMS, gqlType: ` ${singularModelName(model)}`, resolver: (context, args) => { return model.findById(args.id) .then(instance => instance.destroy()); }, }; // _.each(model.sharedClass.methods, method => { // if (method.accessType !== 'READ' && method.http.path) { // let methodName = methodName(method, model); // types.Mutation.fields[methodName] = { // gqlType: `${generateReturns(method.name, method.returns)}`, // args: `${generateAccepts(method.name, method.accepts)}` // } // return `${methodName(method)} // ${generateAccepts(method.name, method.accepts)} // : JSON`; // } else { // return undefined; // } // }); addRemoteHooks(model); } function mapConnection(model) { types[connectionTypeName(model)] = { connection: true, category: 'TYPE', fields: { pageInfo: { required: true, gqlType: 'pageInfo', }, edges: { list: true, gqlType: edgeTypeName(model), resolver: (obj, args, context) => { return _.map(obj.list, node => { return { cursor: idToCursor(node[model.getIdName()]), node: node, }; }); }, }, totalCount: { gqlType: 'Int', scalar: true, resolver: (obj, args, context) => { return obj.count; }, }, [model.pluralModelName]: { gqlType: singularModelName(model), list: true, resolver: (obj, args, context) => { return obj.list; }, }, }, resolver: (obj, args, context) => { return resolveConnection(model); }, }; types[edgeTypeName(model)] = { category: 'TYPE', fields: { node: { gqlType: singularModelName(model), required: true, }, cursor: { gqlType: 'String', required: true, }, }, }; } export function abstractTypes(models: any[]): ITypesHash { //building all models types & relationships types.pageInfo = { category: 'TYPE', fields: { hasNextPage: { gqlType: 'Boolean', required: true, }, hasPreviousPage: { gqlType: 'Boolean', required: true, }, startCursor: { gqlType: 'String', }, endCursor: { gqlType: 'String', }, }, }; types.Query = { category: 'TYPE', fields: {}, }; types.Mutation = { category: 'TYPE', fields: {}, }; _.forEach(models, model => { if (model.shared) { mapRoot(model); } types[singularModelName(model)] = { category: 'TYPE', input: true, fields: {}, }; _.forEach(model.definition.properties, (property, key) => { mapProperty(model, property, singularModelName(model), key); }); mapConnection(model); _.forEach(sharedRelations(model), rel => { mapRelation(rel, singularModelName(model), rel.name); mapConnection(rel.modelTo); }); }); return types; }