319 lines
9.5 KiB
JavaScript

'use strict';
const { prop } = require('lodash/fp');
const { makeSchema, unionType } = require('nexus');
const createBuilders = require('../builders');
const { utils, scalars, internals } = 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 => {
contentTypes.forEach(contentType => {
const { attributes = {}, modelName, plugin } = contentType;
const morphAttributes = Object.keys(attributes).filter(attributeName =>
utils.isMorphRelation(attributes[attributeName])
);
for (const attributeName of morphAttributes) {
const name = utils.getMorphRelationTypeName(contentType, attributeName);
/**
* Filter definitions from the types registry and return definitions
* which have a relation attribute to the current content type
*
* @param {object} config
* @param {string} config.kind
* @param {object} config.contentType
* @return {boolean}
*/
const backlinksPredicate = ({ config }) => {
return (
// Only search for links in base types & components
['types', 'components'].includes(config.kind) &&
// Keep any of the content type where some of its
// attributes have a relation to the current content type
Object.values(config.contentType.attributes)
.filter(utils.isRelation)
.some(attr => {
return (
(attr.model || attr.collection) === modelName &&
attr.plugin === plugin &&
attr.via === attributeName
);
})
);
};
const backLinks = registry.where(backlinksPredicate);
// Don't register the morph type if there is no
// relation pointing to the current polymorphic attribute
if (backLinks.length === 0) {
continue;
}
registry.register(
name,
unionType({
name,
resolveType(obj) {
return obj.__typename;
},
definition(t) {
t.members(...backLinks.map(prop('definition')));
},
}),
{ 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
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'),
// },
});
};
};