'use strict'; /** * GraphQL.js service * * @description: A set of functions similar to controller's actions to avoid code duplication. */ const { gql, makeExecutableSchema } = require('apollo-server-koa'); const _ = require('lodash'); const graphql = require('graphql'); const Types = require('./type-builder'); const { buildModels } = require('./type-definitions'); const { mergeSchemas, createDefaultSchema, diffResolvers } = require('./utils'); const { toSDL } = require('./schema-definitions'); const { buildQuery, buildMutation } = require('./resolvers-builder'); /** * Generate GraphQL schema. * * @return Schema */ const generateSchema = () => { const shadowCRUDEnabled = strapi.plugins.graphql.config.shadowCRUD !== false; // Generate type definition and query/mutation for models. const shadowCRUD = shadowCRUDEnabled ? buildModelsShadowCRUD() : createDefaultSchema(); const _schema = strapi.plugins.graphql.config._schema.graphql; // Extract custom definition, query or resolver. const { definition, query, mutation, resolver = {} } = _schema; // Polymorphic. const polymorphicSchema = Types.addPolymorphicUnionType(definition + shadowCRUD.definition); const builtResolvers = _.merge({}, shadowCRUD.resolvers, polymorphicSchema.resolvers); const extraResolvers = diffResolvers(_schema.resolver, builtResolvers); const resolvers = _.merge({}, builtResolvers, buildResolvers(extraResolvers)); // Return empty schema when there is no model. if (_.isEmpty(shadowCRUD.definition) && _.isEmpty(definition)) { return {}; } const queryFields = shadowCRUD.query && toSDL(shadowCRUD.query, resolver.Query, null, 'query'); const mutationFields = shadowCRUD.mutation && toSDL(shadowCRUD.mutation, resolver.Mutation, null, 'mutation'); const scalars = Types.getScalars(); Object.assign(resolvers, scalars); const scalarDef = Object.keys(scalars) .map(key => `scalar ${key}`) .join('\n'); // Concatenate. let typeDefs = ` ${definition} ${shadowCRUD.definition} ${polymorphicSchema.definition} ${Types.addInput()} type AdminUser { id: ID! username: String } type Query { ${queryFields} ${query} } type Mutation { ${mutationFields} ${mutation} } ${scalarDef} `; // // Build schema. if (strapi.config.environment !== 'production') { // Write schema. const schema = makeExecutableSchema({ typeDefs, resolvers, }); writeGenerateSchema(graphql.printSchema(schema)); } // Remove custom scalar (like Upload), if not using Federation // Federation requires scalar Upload defined in typeDefs to use // buildFederatedSchema() // (https://www.apollographql.com/docs/apollo-server/federation/implementing-services/) const isFederated = _.get(strapi.plugins.graphql, 'config.federation', false); if (!isFederated) { typeDefs = Types.removeCustomScalar(typeDefs, resolvers); } return { typeDefs: gql(typeDefs), resolvers, }; }; /** * Save into a file the readable GraphQL schema. * * @return void */ const writeGenerateSchema = schema => { return strapi.fs.writeAppFile('exports/graphql/schema.graphql', schema); }; const buildModelsShadowCRUD = () => { const models = Object.values(strapi.models).filter(model => model.internal !== true); const pluginModels = Object.values(strapi.plugins) .map(plugin => Object.values(plugin.models) || []) .reduce((acc, arr) => acc.concat(arr), []); const components = Object.values(strapi.components); return mergeSchemas( createDefaultSchema(), ...buildModels([...models, ...pluginModels, ...components]) ); }; const buildResolvers = resolvers => { // Transform object to only contain function. return Object.keys(resolvers).reduce((acc, type) => { if (graphql.isScalarType(resolvers[type])) { return acc; } return Object.keys(resolvers[type]).reduce((acc, resolverName) => { const resolverObj = resolvers[type][resolverName]; // Disabled this query. if (resolverObj === false) return acc; if (_.isFunction(resolverObj)) { return _.set(acc, [type, resolverName], resolverObj); } switch (type) { case 'Mutation': { _.set(acc, [type, resolverName], buildMutation(resolverName, resolverObj)); break; } default: { _.set(acc, [type, resolverName], buildQuery(resolverName, resolverObj)); break; } } return acc; }, acc); }, {}); }; module.exports = { generateSchema, };