UNPKG

@backland/schema

Version:

TypeScript schema declaration and validation library with static type inference

305 lines (302 loc) 9.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createGraphQLSchema = createGraphQLSchema; exports.resolverKinds = void 0; exports.resolversToTypescript = resolversToTypescript; exports.resolversTypescriptParts = resolversTypescriptParts; var _utils = require("@backland/utils"); var _graphql = require("graphql"); var _groupBy = _interopRequireDefault(require("lodash/groupBy")); var _CircularDeps = require("./CircularDeps"); var _GraphType = require("./GraphType/GraphType"); var _generateClientUtils2 = require("./GraphType/generateClientUtils"); var _getInnerGraphTypeId = require("./GraphType/getInnerGraphTypeId"); var _getQueryTemplates = require("./GraphType/getQueryTemplates"); var _ObjectType = require("./ObjectType"); var _MetaFieldField = require("./fields/MetaFieldField"); var _mockObject = require("./mockObject"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const resolverKinds = (0, _utils.tupleEnum)('mutation', 'query', 'subscription'); exports.resolverKinds = resolverKinds; function createGraphQLSchema(...args) { const { graphql: { GraphQLSchema }, GraphType } = _CircularDeps.CircularDeps; const registeredResolvers = GraphType.resolvers.entries.map(el => el[1]); let resolvers = Array.isArray(args[0]) ? args[0] : registeredResolvers; const schemaResolvers = resolvers.filter(el => el.__isResolver && !el.__isRelation); const config = Array.isArray(args[0]) ? args[1] : args[0]; const grouped = (0, _groupBy.default)(schemaResolvers, item => item.kind); function createFields(kind) { const fields = {}; if (grouped[kind]) { grouped[kind].forEach(item => { fields[item.name] = item; }); } return fields; } const usedConfig = { mutation: grouped.mutation ? new _graphql.GraphQLObjectType({ fields: createFields('mutation'), name: 'Mutation' }) : undefined, query: grouped.query ? new _graphql.GraphQLObjectType({ fields: createFields('query'), name: 'Query' }) : undefined, subscription: grouped.subscription ? new _graphql.GraphQLObjectType({ fields: createFields('subscription'), name: 'Subscription' }) : undefined, ...config }; const schema = new GraphQLSchema(usedConfig); let ts; const utils = { generateClientUtils() { return Promise.reject('not implemented'); }, grouped, print() { return (0, _graphql.printSchema)(schema); }, queryExamples(options) { return queryExamples({ grouped, schema, ...options }); }, queryTemplates() { return (0, _getQueryTemplates.getSchemaQueryTemplates)(schema); }, registeredResolvers, resolvers, async typescript(options) { return ts = ts || resolversToTypescript({ name: 'GraphQLTypes', ...options, resolvers }); }, usedConfig }; const result = Object.assign(schema, { utils }); utils.generateClientUtils = function _generateClientUtils() { return (0, _generateClientUtils2.generateClientUtils)(result); }; return result; } async function resolversTypescriptParts(params) { const { name = 'Schema' } = params; let prefix = ''; const mainResolvers = params.resolvers.filter(el => !el.__isRelation); const mainResolversConversion = mainResolvers.map(item => { return convertResolver({ allResolvers: params.resolvers, resolver: item }); }); const lines = await Promise.all(mainResolversConversion); let typesCode = ''; let interfaceCode = `export interface ${name} {`; let queryCode = `export type QueryResolvers = {`; let mutationCode = `export type MutationResolvers = {`; let subscriptionCode = `export type SubscriptionResolvers = {`; lines.forEach(el => { let { entryName, code, payloadName, inputName, resolver: { description = '', kind } } = el; typesCode += `${code}\n`; if (description) { description = `\n/** ${description} **/\n`; } const resolverCode = `${description} ${entryName}(args: ${inputName}): Promise<${payloadName}>,`; switch (kind) { case 'mutation': { mutationCode += resolverCode; break; } case 'query': { queryCode += resolverCode; break; } case 'subscription': { subscriptionCode += resolverCode; break; } } interfaceCode += `${description} ${entryName}: {input: ${inputName}, payload: ${payloadName}},`; }); let code = `${prefix}\n${typesCode}\n${interfaceCode}}\n${queryCode}}\n${mutationCode}}\n${subscriptionCode}}\n`.replace(/\n\n/gm, '\n') // remove multi line breaks .replace(/^\n/gm, ''); // remove empty lines // @ts-ignore circular code = _CircularDeps.CircularDeps.prettier.format(code, { parser: 'typescript' }); return { code, lines }; } async function resolversToTypescript(params) { const { options = {} } = params; const { format = true } = options; const { code } = await resolversTypescriptParts(params); return format ? // @ts-ignore circular _CircularDeps.CircularDeps.prettier.format(code, { parser: 'typescript', printWidth: 100 }) : code; } async function convertResolver(options) { const { resolver, allResolvers } = options; const inputName = resolver.argsType.id; const payloadName = resolver.payloadType.id; const payloadOriginName = (0, _getInnerGraphTypeId.getInnerGraphTypeId)(resolver.payloadType); const payloadDef = { // clearing ref because will be mutated to inject relations in definition ...(0, _MetaFieldField.cleanMetaField)(resolver.typeDef) }; allResolvers.filter(el => el.__isRelation).forEach(rel => { if (rel.__relatedToGraphTypeId === payloadOriginName) { const typeRelatedToFinalPayload = _GraphType.GraphType.register.get(rel.__graphTypeId); payloadDef.def[rel.name] = typeRelatedToFinalPayload.definition; } }); const [payload, args] = await Promise.all([convertType({ entryName: payloadName, kind: 'output', type: payloadDef // TODO generate type for User[] not for plain object }), convertType({ entryName: inputName, kind: 'input', type: resolver.argsType.definition })]); let code = ''; code += `${args.comments}\nexport type ${inputName} = ${args.code};`; code += `${payload.comments}\nexport type ${payloadName} = ${payload.code};`; return { args, code, entryName: resolver.name, inputName, payload, payloadName, resolver }; } async function convertType(options) { const { entryName, type, kind } = options; const parsed = (0, _ObjectType.parseFieldDefinitionConfig)(type, { deep: { omitMeta: true } }); const { description } = parsed; // @ts-ignore circular const result = await _CircularDeps.CircularDeps.objectToTypescript(entryName, { CONVERT__REPLACE: { ...type // prevents breaking the `export type...` etc, above. to improve. } }, { ...options, format: false, ignoreDefaultValues: kind !== 'input' }); let code = result.split('\n').slice(1, -2).join('\n').replace('CONVERT__REPLACE', ''); if (code.startsWith('?')) { code = `${code} | undefined`; } code = code.replace(/^\??: /, ``); const comments = description ? `\n/** ${description} **/\n` : ''; return { code, comments, description: description || '' }; } function queryExamples({ schema, grouped, randomText, randomNumber, resolver: resolverName }) { let templates = (0, _getQueryTemplates.getSchemaQueryTemplates)(schema); let examples = ''; Object.entries(grouped).forEach(([_kind, resolvers]) => { const kind = _kind; if (!(resolvers !== null && resolvers !== void 0 && resolvers.length)) return; const resolversRecord = templates.queryByResolver[kind]; Object.entries(resolversRecord).forEach(([name, parsed]) => { var _resolver$argsType$__; if (resolverName && resolverName !== name) return; const resolver = (0, _utils.notNull)(resolvers.find(el => el.name === name)); const argsDef = (_resolver$argsType$__ = resolver.argsType.__lazyGetter.objectType) === null || _resolver$argsType$__ === void 0 ? void 0 : _resolver$argsType$__.definition; const argsExamples = argsDef ? (0, _mockObject.objectMock)(argsDef, { randomNumber, randomText }) : ''; examples += `${kind} ${name}${(0, _utils.capitalize)(kind)} { ${name}`; if (parsed.argsParsed.vars.length) { examples += '('; const args = parsed.argsParsed.vars.map(val => { const example = argsExamples[val.name]; const ser = typeof example === 'string' ? `"${example}"` : example && typeof example === 'object' ? _utils.BJSON.stringify(example, { quoteKeys(str) { if (str.match(/[-.]/)) return `"${str}"`; return str; } }) : example; return `${val.name}: ${ser}`; }); examples += args.join(', '); examples += ')'; } if (parsed.query) { examples += ` {${parsed.query}} `; } examples += '}\n'; }); }); return [(0, _utils.formatGraphQL)(examples), templates.fullQuery].join('\n'); } //# sourceMappingURL=createGraphQLSchema.js.map