'use strict'; const { prop } = require('lodash/fp'); const { makeSchema, unionType } = require('nexus'); const createBuilders = require('../builders'); const { utils, constants, scalars, buildInternals } = require('../../types'); const { create: createTypeRegistry } = require('../../type-registry'); module.exports = strapi => { // todo[v4]: Get the service directly with something like strapi.plugin().service() const registry = createTypeRegistry(); const builders = createBuilders({ strapi, registry }); const registerAPITypes = contentTypes => { for (const contentType of contentTypes) { const { kind, modelType } = contentType; // Generate enums & dynamic zones registerEnumsDefinition(contentType); registerDynamicZonesDefinition(contentType); registerFiltersDefinition(contentType); registerInputsDefinition(contentType); // Generate & register component's definition if (modelType === 'component') { registerComponent(contentType); } // Generate & register single type's definition else if (kind === 'singleType') { registerSingleType(contentType); } // Generate & register collection type's definition else if (kind === 'collectionType') { registerCollectionType(contentType); } } }; const registerMorphTypes = contentTypes => { // Create & register a union type that includes every type or component registered const genericMorphType = builders.buildGenericMorphDefinition(); registry.register(constants.GENERIC_MORPH_TYPENAME, genericMorphType, { kind: 'morphs' }); // For every content type contentTypes.forEach(contentType => { const { attributes = {} } = contentType; // Isolate its polymorphic attributes const morphAttributes = Object.entries(attributes).filter(([, attribute]) => utils.isMorphRelation(attribute) ); // For each one of those polymorphic attribute for (const [attributeName, attribute] of morphAttributes) { const name = utils.getMorphRelationTypeName(contentType, attributeName); const { target } = attribute; // Ignore those whose target is not an array if (!Array.isArray(target)) { continue; } // Transform target UIDs into types names const members = target // Get content types definitions .map(uid => strapi.getModel(uid)) // Resolve types names .map(contentType => utils.getTypeName(contentType)); // Register the new polymorphic union type registry.register( name, unionType({ name, resolveType: prop('__typename'), definition(t) { t.members(...members); }, }), { kind: 'morphs', contentType, attributeName } ); } }); }; const registerComponent = contentType => { const name = utils.getComponentName(contentType); const definition = builders.buildTypeDefinition(contentType); registry.register(name, definition, { kind: 'components', contentType }); }; const registerSingleType = contentType => { const types = { base: utils.getTypeName(contentType), entity: utils.getEntityName(contentType), response: utils.getEntityResponseName(contentType), queries: utils.getEntityQueriesTypeName(contentType), mutations: utils.getEntityMutationsTypeName(contentType), }; const getConfig = kind => ({ kind, contentType }); // Single type's definition registry.register(types.base, builders.buildTypeDefinition(contentType), getConfig('types')); // Higher level entity definition registry.register( types.entity, builders.buildEntityDefinition(contentType), getConfig('entities') ); // Responses definition registry.register( types.response, builders.buildResponseDefinition(contentType), getConfig('entitiesResponses') ); // Queries registry.register( types.queries, builders.buildSingleTypeQueries(contentType), getConfig('queries') ); registry.register( types.mutations, builders.buildSingleTypeMutations(contentType), getConfig('mutations') ); }; const registerCollectionType = contentType => { // Types name (as string) const types = { base: utils.getTypeName(contentType), entity: utils.getEntityName(contentType), response: utils.getEntityResponseName(contentType), responseCollection: utils.getEntityResponseCollectionName(contentType), queries: utils.getEntityQueriesTypeName(contentType), mutations: utils.getEntityMutationsTypeName(contentType), }; const getConfig = kind => ({ kind, contentType }); // Type definition registry.register(types.base, builders.buildTypeDefinition(contentType), getConfig('types')); // Higher level entity definition registry.register( types.entity, builders.buildEntityDefinition(contentType), getConfig('entities') ); // Responses definition registry.register( types.response, builders.buildResponseDefinition(contentType), getConfig('entitiesResponses') ); registry.register( types.responseCollection, builders.buildResponseCollectionDefinition(contentType), getConfig('entitiesResponsesCollection') ); // Query extensions registry.register( types.queries, builders.buildCollectionTypeQueries(contentType), getConfig('queries') ); // Mutation extensions registry.register( types.mutations, builders.buildCollectionTypeMutations(contentType), getConfig('mutations') ); }; const registerEnumsDefinition = contentType => { const { attributes } = contentType; const enumAttributes = Object.keys(attributes).filter(attributeName => utils.isEnumeration(attributes[attributeName]) ); for (const attributeName of enumAttributes) { const attribute = attributes[attributeName]; const enumName = utils.getEnumName(contentType, attributeName); const enumDefinition = builders.buildEnumTypeDefinition(attribute, enumName); registry.register(enumName, enumDefinition, { kind: 'enums', contentType, attributeName, attribute, }); } }; const registerDynamicZonesDefinition = contentType => { const { attributes } = contentType; const dynamicZoneAttributes = Object.keys(attributes).filter(attributeName => utils.isDynamicZone(attributes[attributeName]) ); for (const attributeName of dynamicZoneAttributes) { const attribute = attributes[attributeName]; const dzName = utils.getDynamicZoneName(contentType, attributeName); const dzInputName = utils.getDynamicZoneInputName(contentType, attributeName); const [type, input] = builders.buildDynamicZoneDefinition(attribute, dzName, dzInputName); if (type && input) { const baseConfig = { contentType, attributeName, attribute, }; registry.register(dzName, type, { kind: 'dynamic-zones', ...baseConfig }); registry.register(dzInputName, input, { kind: 'inputs', ...baseConfig }); } } }; const registerFiltersDefinition = contentType => { const type = utils.getFiltersInputTypeName(contentType); const definition = builders.buildContentTypeFilters(contentType); registry.register(type, definition, { kind: 'filters-inputs', contentType }); }; const registerInputsDefinition = contentType => { const { modelType } = contentType; const type = (modelType === 'component' ? utils.getComponentInputName : utils.getContentTypeInputName ).call(null, contentType); const definition = builders.buildInputType(contentType); registry.register(type, definition, { kind: 'inputs', contentType }); }; return () => { const contentTypes = [ ...Object.values(strapi.components), ...Object.values(strapi.contentTypes), ]; // Register needed scalar types Object.entries(scalars).forEach(([name, definition]) => { registry.register(name, definition, { kind: 'scalar' }); }); // Register Strapi's internal types const internals = buildInternals({ strapi }); for (const [kind, definitions] of Object.entries(internals)) { registry.registerMany(Object.entries(definitions), { kind }); } // Generate and register definitions for every content type registerAPITypes(contentTypes); // Generate and register polymorphic types' definitions registerMorphTypes(contentTypes); // Return a brand new Nexus schema return makeSchema({ // Exhaustive list of the content-api's types definitions types: registry.definitions, // Plugins plugins: [], // Auto-gen tools configuration (.graphql, .ts) // shouldGenerateArtifacts: process.env.NODE_ENV === 'development', // outputs: { // typegen: join(__dirname, '..', 'nexus-typegen.ts'), // schema: join(__dirname, '../../../../../..', 'schema.graphql'), }, }); }; };