mirror of
https://github.com/strapi/strapi.git
synced 2025-09-07 15:49:24 +00:00
319 lines
9.5 KiB
JavaScript
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'),
|
|
// },
|
|
});
|
|
};
|
|
};
|