strapi/packages/plugins/graphql/services/schema-generator.js
2021-04-29 12:03:54 +02:00

179 lines
4.8 KiB
JavaScript

'use strict';
/**
* GraphQL.js service
*
* @description: A set of functions similar to controller's actions to avoid code duplication.
*/
const { filterSchema } = require('@graphql-tools/utils');
const { buildFederatedSchema } = require('@apollo/federation');
const { gql, makeExecutableSchema } = require('apollo-server-koa');
const _ = require('lodash');
const graphql = require('graphql');
const PublicationState = require('../types/publication-state');
const Types = require('./type-builder');
const buildShadowCrud = require('./shadow-crud');
const { createDefaultSchema, diffResolvers } = require('./utils');
const { toSDL } = require('./schema-definitions');
const { buildQuery, buildMutation } = require('./resolvers-builder');
/**
* Generate GraphQL schema.
*
* @return Schema
*/
const generateSchema = () => {
const isFederated = _.get(strapi.plugins.graphql.config, 'federation', false);
const shadowCRUDEnabled = strapi.plugins.graphql.config.shadowCRUD !== false;
const _schema = strapi.plugins.graphql.config._schema.graphql;
const ctx = {
schema: _schema,
};
// Generate type definition and query/mutation for models.
const shadowCRUD = shadowCRUDEnabled ? buildShadowCrud(ctx) : createDefaultSchema();
// 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');
Object.assign(resolvers, PublicationState.resolver);
const scalars = Types.getScalars();
Object.assign(resolvers, scalars);
const scalarDef = Object.keys(scalars)
.map(key => `scalar ${key}`)
.join('\n');
// Concatenate.
// Manually defined to avoid exposing all attributes (like password etc.)
let typeDefs = `
${definition}
${shadowCRUD.definition}
${polymorphicSchema.definition}
${Types.addInput()}
${PublicationState.definition}
type AdminUser {
id: ID!
username: String
firstname: String!
lastname: String!
}
type Query {
${queryFields}
${query}
}
type Mutation {
${mutationFields}
${mutation}
}
${scalarDef}
`;
// Build schema.
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
const generatedSchema = filterDisabledResolvers(schema, extraResolvers);
if (strapi.config.environment !== 'production') {
writeGenerateSchema(generatedSchema);
}
return isFederated ? getFederatedSchema(generatedSchema, resolvers) : generatedSchema;
};
const getFederatedSchema = (schema, resolvers) =>
buildFederatedSchema([{ typeDefs: gql(graphql.printSchema(schema)), resolvers }]);
const filterDisabledResolvers = (schema, extraResolvers) =>
filterSchema({
schema,
rootFieldFilter: (operationName, fieldName) => {
const resolver = _.get(extraResolvers[operationName], fieldName, true);
// resolvers set to false are filtered from the schema
if (resolver === false) {
return false;
}
return true;
},
});
/**
* Save into a file the readable GraphQL schema.
*
* @return void
*/
const writeGenerateSchema = schema => {
const printSchema = graphql.printSchema(schema);
return strapi.fs.writeAppFile('exports/graphql/schema.graphql', printSchema);
};
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,
};