180 lines
5.9 KiB
JavaScript
Raw Normal View History

'use strict';
const {
mergeSchemas,
makeExecutableSchema,
addResolversToSchema,
} = require('@graphql-tools/schema');
const { pruneSchema } = require('@graphql-tools/utils');
const { makeSchema } = require('nexus');
const { prop, startsWith } = require('lodash/fp');
const { wrapResolvers } = require('./wrap-resolvers');
const {
registerSingleType,
registerCollectionType,
registerComponent,
registerScalars,
registerInternals,
registerPolymorphicContentType,
contentType: {
registerEnumsDefinition,
registerInputsDefinition,
registerFiltersDefinition,
registerDynamicZonesDefinition,
},
} = require('./register-functions');
module.exports = ({ strapi }) => {
const { service: getGraphQLService } = strapi.plugin('graphql');
const { config } = getGraphQLService('utils');
const { KINDS, GENERIC_MORPH_TYPENAME } = getGraphQLService('constants');
const extensionService = getGraphQLService('extension');
// Type Registry
let registry;
// Builders Instances
let builders;
const buildSchema = () => {
const isShadowCRUDEnabled = !!config.shadowCRUD;
2021-09-21 19:38:15 +02:00
// Create a new empty type registry
registry = getGraphQLService('type-registry').new();
// Reset the builders instances associated to the
// content-api, and link the new type registry
builders = getGraphQLService('builders').new('content-api', registry);
registerScalars({ registry, strapi });
registerInternals({ registry, strapi });
2021-09-21 19:38:15 +02:00
if (isShadowCRUDEnabled) {
shadowCRUD();
}
// Build a collection of schema based on the type registry (& temporary generated extension)
const schemas = buildSchemas({ registry });
// Merge every created schema into a single one
const mergedSchema = mergeSchemas({ schemas });
// Generate the extension configuration for the content API.
// This extension instance needs to be generated after the Nexus schema's
// generation, so that configurations created during types definitions
// can be registered before being used in the wrap resolvers operation
const extension = extensionService.generate({ typeRegistry: registry });
// Add the extension's resolvers to the final schema
const schema = addResolversToSchema(mergedSchema, extension.resolvers);
// Wrap resolvers if needed (auth, middlewares, policies...) as configured in the extension
const wrappedSchema = wrapResolvers({ schema, strapi, extension });
// Prune schema, remove unused types
// eg: removes registered subscriptions if they're disabled in the config)
const prunedSchema = pruneSchema(wrappedSchema);
return prunedSchema;
};
const buildSchemas = ({ registry }) => {
// Here we extract types, plugins & typeDefs from a temporary generated
// extension since there won't be any addition allowed after schemas generation
const { types, plugins, typeDefs = [] } = extensionService.generate({ typeRegistry: registry });
// Create a new Nexus schema (shadow CRUD) & add it to the schemas collection
const nexusSchema = makeSchema({
types: [
// Add the auto-generated Nexus types (shadow CRUD)
registry.definitions,
// Add every Nexus type registered using the extension service
types,
],
plugins: [
// Add every plugin registered using the extension service
...plugins,
],
});
// Build schemas based on SDL type definitions (defined in the extension)
const sdlSchemas = typeDefs.map(sdl => makeExecutableSchema({ typeDefs: sdl }));
return [nexusSchema, ...sdlSchemas];
};
2021-09-21 19:38:15 +02:00
const shadowCRUD = () => {
const extensionService = getGraphQLService('extension');
// Get every content type & component defined in Strapi
const contentTypes = [
...Object.values(strapi.components),
...Object.values(strapi.contentTypes),
];
// Disable Shadow CRUD for admin content types
contentTypes
.map(prop('uid'))
.filter(startsWith('admin::'))
.forEach(uid => extensionService.shadowCRUD(uid).disable());
const contentTypesWithShadowCRUD = contentTypes.filter(ct =>
extensionService.shadowCRUD(ct.uid).isEnabled()
);
// Generate and register definitions for every content type
registerAPITypes(contentTypesWithShadowCRUD);
// Generate and register polymorphic types' definitions
registerMorphTypes(contentTypesWithShadowCRUD);
};
/**
* Register needed GraphQL types for every content type
* @param {object[]} contentTypes
*/
const registerAPITypes = contentTypes => {
for (const contentType of contentTypes) {
const { kind, modelType } = contentType;
const registerOptions = { registry, strapi, builders };
// Generate various types associated to the content type
// (enums, dynamic-zones, filters, inputs...)
registerEnumsDefinition(contentType, registerOptions);
registerDynamicZonesDefinition(contentType, registerOptions);
registerFiltersDefinition(contentType, registerOptions);
registerInputsDefinition(contentType, registerOptions);
// Generate & register component's definition
if (modelType === 'component') {
registerComponent(contentType, registerOptions);
}
// Generate & register single type's definition
else if (kind === 'singleType') {
registerSingleType(contentType, registerOptions);
}
// Generate & register collection type's definition
else if (kind === 'collectionType') {
registerCollectionType(contentType, registerOptions);
}
}
};
const registerMorphTypes = contentTypes => {
// Create & register a union type that includes every type or component registered
const genericMorphType = builders.buildGenericMorphDefinition();
registry.register(GENERIC_MORPH_TYPENAME, genericMorphType, { kind: KINDS.morph });
for (const contentType of contentTypes) {
registerPolymorphicContentType(contentType, { registry, strapi });
}
};
return { buildSchema };
};