UNPKG

@nyteshade/lattice-legacy

Version:

OO Underpinnings for ease of GraphQL Implementation

303 lines (258 loc) 9.83 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.SchemaUtils = undefined; var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); var _keys = require('babel-runtime/core-js/object/keys'); var _keys2 = _interopRequireDefault(_keys); var _path = require('path'); var _path2 = _interopRequireDefault(_path); var _SyntaxTree = require('./SyntaxTree'); var _GQLBase = require('./GQLBase'); var _GQLEnum = require('./GQLEnum'); var _GQLInterface = require('./GQLInterface'); var _GQLScalar = require('./GQLScalar'); var _neTypes = require('ne-types'); var _utils = require('./utils'); var _lodash = require('lodash'); var _events = require('events'); var _events2 = _interopRequireDefault(_events); var _graphql = require('graphql'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * The SchemaUtils is used by tools such as GQLExpressMiddleware in order to * apply GraphQL Lattice specifics to the build schema. * * @class SchemaUtils */ let SchemaUtils = exports.SchemaUtils = class SchemaUtils extends _events2.default { /** * Calls all the Lattice post-schema creation routines on a given Schema * using data from a supplied array of classes. * * @param {GraphQLSchema} schema the schema to post-process * @param {Array<GQLBase>} Classes the Classes from which to drive post * processing data from */ static injectAll(schema, Classes) { SchemaUtils.injectInterfaceResolvers(schema, Classes); SchemaUtils.injectEnums(schema, Classes); SchemaUtils.injectScalars(schema, Classes); SchemaUtils.injectComments(schema, Classes); } /** * Until such time as I can get the reference Facebook GraphQL AST parser to * read and apply descriptions or until such time as I employ the Apollo * AST parser, providing a `static get apiDocs()` getter is the way to get * your descriptions into the proper fields, post schema creation. * * This method walks the types in the registered classes and the supplied * schema type. It then injects the written comments such that they can * be exposed in graphiql and to applications or code that read the meta * fields of a built schema * * @memberof SchemaUtils * @method ⌾⠀injectComments * @static * @since 2.7.0 * * @param {Object} schema a built GraphQLSchema object created via buildSchema * or some other alternative but compatible manner * @param {Function[]} Classes these are GQLBase extended classes used to * manipulate the schema with. */ static injectComments(schema, Classes) { const { DOC_CLASS, DOC_FIELDS, DOC_QUERIES, DOC_MUTATORS, DOC_SUBSCRIPTIONS, DOC_QUERY, DOC_MUTATION, DOC_SUBSCRIPTION } = _GQLBase.GQLBase; for (let Class of Classes) { const docs = Class.apiDocs(); const query = schema._typeMap.Query; const mutation = schema._typeMap.Mutation; const subscription = schema._typeMap.Subscription; let type; if (type = schema._typeMap[Class.name]) { let fields = type._fields; let values = type._values; if (docs[DOC_CLASS]) { type.description = docs[DOC_CLASS]; } for (let field of (0, _keys2.default)(docs[DOC_FIELDS] || {})) { if (fields && field in fields) { fields[field].description = docs[DOC_FIELDS][field]; } if (values) { for (let value of values) { if (value.name === field) { value.description = docs[DOC_FIELDS][field]; } } } } } for (let [_type, _CONST, _topCONST] of [[query, DOC_QUERIES, DOC_QUERY], [mutation, DOC_MUTATORS, DOC_MUTATION], [subscription, DOC_SUBSCRIPTIONS, DOC_SUBSCRIPTION]]) { if (_type && ((0, _keys2.default)(docs[_CONST] || {}).length || docs[_topCONST] && docs[_topCONST].length)) { let fields = _type._fields; if (docs[_topCONST]) { _type.description = docs[_topCONST]; } for (let field of (0, _keys2.default)(docs[_CONST])) { if (field in fields) { fields[field].description = docs[_CONST][field]; } } } } } } /** * Somewhat like `injectComments` and other similar methods, the * `injectInterfaceResolvers` method walks the registered classes and * finds `GQLInterface` types and applies their `resolveType()` * implementations. * * @memberof SchemaUtils * @method ⌾⠀injectInterfaceResolvers * @static * * @param {Object} schema a built GraphQLSchema object created via buildSchema * or some other alternative but compatible manner * @param {Function[]} Classes these are GQLBase extended classes used to * manipulate the schema with. */ static injectInterfaceResolvers(schema, Classes) { for (let Class of Classes) { if (Class.GQL_TYPE === _graphql.GraphQLInterfaceType) { schema._typeMap[Class.name].resolveType = schema._typeMap[Class.name]._typeConfig.resolveType = Class.resolveType; } } } /** * Somewhat like `injectComments` and other similar methods, the * `injectInterfaceResolvers` method walks the registered classes and * finds `GQLInterface` types and applies their `resolveType()` * implementations. * * @memberof SchemaUtils * @method ⌾⠀injectEnums * @static * * @param {Object} schema a built GraphQLSchema object created via buildSchema * or some other alternative but compatible manner * @param {Function[]} Classes these are GQLBase extended classes used to * manipulate the schema with. */ static injectEnums(schema, Classes) { for (let Class of Classes) { if (Class.GQL_TYPE === _graphql.GraphQLEnumType) { const __enum = schema._typeMap[Class.name]; const values = Class.values; for (let value of __enum._values) { if (value.name in values) { (0, _lodash.merge)(value, values[value.name]); } } } } } /** * GQLScalar types must define three methods to have a valid implementation. * They are serialize, parseValue and parseLiteral. See their docs for more * info on how to do so. * * This code finds each scalar and adds their implementation details to the * generated schema type config. * * @memberof SchemaUtils * @method ⌾⠀injectScalars * @static * * @param {Object} schema a built GraphQLSchema object created via buildSchema * or some other alternative but compatible manner * @param {Function[]} Classes these are GQLBase extended classes used to * manipulate the schema with. */ static injectScalars(schema, Classes) { for (let Class of Classes) { if (Class.GQL_TYPE === _graphql.GraphQLScalarType) { // @ComputedType const type = schema._typeMap[Class.name]; // @ComputedType const { serialize, parseValue, parseLiteral } = Class; if (!serialize || !parseValue || !parseLiteral) { // @ComputedType _utils.LatticeLogs.error(`Scalar type ${Class.name} has invaild impl.`); continue; } (0, _lodash.merge)(type._scalarConfig, { serialize, parseValue, parseLiteral }); } } } /** * A function that combines the IDL schemas of all the supplied classes and * returns that value to the middleware getter. * * @static * @memberof GQLExpressMiddleware * @method ⌾⠀generateSchemaSDL * * @return {string} a dynamically generated GraphQL IDL schema string */ static generateSchemaSDL(Classes, logOutput = true) { let schema = _SyntaxTree.SyntaxTree.EmptyDocument(); let log = (...args) => { if (logOutput) { console.log(...args); } }; for (let Class of Classes) { let classSchema = Class.SCHEMA; if ((0, _neTypes.typeOf)(classSchema) === 'Symbol') { let handler = Class.handler; let filename = _path2.default.basename(Class.handler.path); classSchema = handler.getSchema(); log(`\nRead schema (%s)\n%s\n%s\n`, filename, '-'.repeat(14 + filename.length), classSchema.replace(/^/gm, ' ')); } schema.appendDefinitions(classSchema); } log('\nGenerated GraphQL Schema\n----------------\n%s', schema); return schema.toString(); } /** * An asynchronous function used to parse the supplied classes for each * ones resolvers and mutators. These are all combined into a single root * object passed to express-graphql. * * @static * @memberof SchemaUtils * @method ⌾⠀createMergedRoot * * @param {Array<GQLBase>} Classes the GQLBase extended class objects or * functions from which to merge the RESOLVERS and MUTATORS functions. * @param {Object} requestData for Express apss, this will be an object * containing { req, res, gql } where those are the Express request and * response object as well as the GraphQL parameters for the request. * @return {Promise<Object>} a Promise resolving to an Object containing all * the functions described in both Query and Mutation types. */ static createMergedRoot(Classes, requestData, separateByType = false) { return (0, _asyncToGenerator3.default)(function* () { const root = {}; for (let Class of Classes) { (0, _lodash.merge)(root, ( // $FlowFixMe yield Class.getMergedRoot(requestData, separateByType))); } return root; })(); } }; exports.default = SchemaUtils; //# sourceMappingURL=SchemaUtils.js.map