@elsikora/nestjs-crud-automator
Version:
A library for automating the creation of CRUD operations in NestJS.
252 lines (248 loc) • 16 kB
JavaScript
;
require('../../../constant/decorator/api/function.constant.js');
var propertyDescribe_constant = require('../../../constant/decorator/api/property-describe.constant.js');
var operatorRegistry_constant = require('../../../constant/filter/operator-registry.constant.js');
var metadata_constant = require('../../../constant/swagger/metadata.constant.js');
var generate_constant = require('../../../constant/utility/dto/generate.constant.js');
require('../../../enum/decorator/api/action.enum.js');
require('../../../enum/decorator/api/authentication-type.enum.js');
require('../../../enum/decorator/api/controller/get-list/query/filter/missing-behavior.enum.js');
require('../../../enum/decorator/api/controller/get-list/query/unlisted-fields.enum.js');
require('../../../enum/decorator/api/controller/relation-reference-shape.enum.js');
require('../../../enum/decorator/api/controller/request/target.enum.js');
require('../../../enum/decorator/api/controller/request/transformer-type.enum.js');
require('../../../enum/decorator/api/controller/response-target.enum.js');
var dtoType_enum = require('../../../enum/decorator/api/dto-type.enum.js');
require('../../../enum/decorator/api/function/context-storage-kind.enum.js');
require('../../../enum/decorator/api/function/subscriber-transaction-expectation.enum.js');
require('../../../enum/decorator/api/function/transaction/event-status.enum.js');
require('../../../enum/decorator/api/function/transaction/failure-stage.enum.js');
require('../../../enum/decorator/api/function/transaction/mode.enum.js');
require('../../../enum/decorator/api/function/transaction/outcome.enum.js');
require('../../../enum/decorator/api/function/transaction/owner-kind.enum.js');
require('../../../enum/decorator/api/function/transaction/trace-type.enum.js');
require('../../../enum/decorator/api/function/type.enum.js');
require('../../../enum/decorator/api/on-type.enum.js');
require('../../../enum/decorator/api/property/data-type.enum.js');
require('../../../enum/decorator/api/property/date/identifier.enum.js');
require('../../../enum/decorator/api/property/date/type.enum.js');
var desribeType_enum = require('../../../enum/decorator/api/property/desribe-type.enum.js');
require('../../../enum/decorator/api/property/number-type.enum.js');
require('../../../enum/decorator/api/property/string-type.enum.js');
require('../../../enum/decorator/api/route/subscriber-authorization-expectation.enum.js');
var type_enum = require('../../../enum/decorator/api/route/type.enum.js');
var swagger = require('@nestjs/swagger');
var camelCaseString_utility = require('../../camel-case-string.utility.js');
var pop_utility = require('../auto/context/pop.utility.js');
var push_utility = require('../auto/context/push.utility.js');
var buildDecorator_utility = require('../build-decorator.utility.js');
var cacheKey_utility = require('./cache-key.utility.js');
var dynamic_utility = require('./dynamic.utility.js');
var filterDecorator_utility = require('./filter-decorator.utility.js');
var getListQueryProperties_utility = require('./get-list-query-properties.utility.js');
var getListResponse_utility = require('./get-list-response.utility.js');
var getListQueryBaseClass_utility = require('../get/get-list-query-base-class.utility.js');
var shouldBeMarked_utility = require('../is/property/should-be-marked.utility.js');
var shouldBeGenerated_utility = require('../is/should-be-generated.utility.js');
var exception_utility = require('../../error/exception.utility.js');
var pairedCustomSuffixesFields_validator = require('../../../validator/has/paired-custom-suffixes-fields.validator.js');
var classTransformer = require('class-transformer');
var classValidator = require('class-validator');
const dtoGenerateCache = new Map();
/**
* Core utility for DTO generation that determines which properties should be included in the DTO.
* Builds decorators, handles special cases like filter queries, and generates the appropriate class
* based on entity metadata, route type, and DTO type.
* @param {ObjectLiteral} entity - The entity class or prototype
* @param {IApiEntity<E>} entityMetadata - The entity metadata containing column information
* @param {EApiRouteType} method - The API route type (CREATE, DELETE, GET, etc.)
* @param {EApiDtoType} dtoType - The type of DTO (REQUEST, RESPONSE, etc.)
* @param {IApiControllerPropertiesRouteAutoDtoConfig} [dtoConfig] - Optional configuration for automatic DTO generation
* @param {Type<IAuthGuard>} [currentGuard] - Optional authentication guard for property visibility control
* @param {IApiControllerGetListQueryPlan} [queryPlan] - Normalized route plan that narrows generated GET_LIST query fields.
* @returns {Type<unknown> | undefined} The generated DTO class or undefined if no DTO should be generated
* @throws {Error} When primary key metadata is missing
* @template E - The entity type
*/
function DtoGenerate(entity, entityMetadata, method, dtoType, dtoConfig, currentGuard, queryPlan) {
if (!shouldBeGenerated_utility.DtoIsShouldBeGenerated(method, dtoType)) {
return undefined;
}
const cacheKey = cacheKey_utility.DtoGenerateCacheKey({
controllerName: queryPlan?.controllerName,
dtoConfig,
dtoType,
entityName: String(entityMetadata.name),
guardName: currentGuard?.name,
method,
queryPlanSignature: queryPlan?.signature,
});
const cached = dtoGenerateCache.get(cacheKey);
if (cached) {
return cached;
}
if (!entityMetadata.primaryKey?.metadata?.[propertyDescribe_constant.PROPERTY_DESCRIBE_DECORATOR_API_CONSTANT.METADATA_KEY]) {
throw exception_utility.ErrorException(`Primary key for entity ${String(entityMetadata.name)} not found in metadata storage`);
}
// eslint-disable-next-line @elsikora/typescript/no-unsafe-function-type
const extraModels = [];
const markedProperties = [];
for (const column of entityMetadata.columns) {
if (column.metadata?.[propertyDescribe_constant.PROPERTY_DESCRIBE_DECORATOR_API_CONSTANT.METADATA_KEY] && shouldBeMarked_utility.DtoIsPropertyShouldBeMarked(method, dtoType, column.name, column.metadata[propertyDescribe_constant.PROPERTY_DESCRIBE_DECORATOR_API_CONSTANT.METADATA_KEY], column.isPrimary, currentGuard)) {
markedProperties.push({
isPrimary: column.isPrimary,
metadata: column.metadata[propertyDescribe_constant.PROPERTY_DESCRIBE_DECORATOR_API_CONSTANT.METADATA_KEY],
name: column.name,
});
}
}
const queryFilterProperties = method === type_enum.EApiRouteType.GET_LIST && dtoType === dtoType_enum.EApiDtoType.QUERY ? getListQueryProperties_utility.DtoGenerateGetListQueryProperties(entityMetadata, markedProperties, currentGuard, queryPlan) : [];
const BaseClass = method === type_enum.EApiRouteType.GET_LIST && dtoType === dtoType_enum.EApiDtoType.QUERY ? getListQueryBaseClass_utility.DtoGetGetListQueryBaseClass(entity, entityMetadata, method, dtoType, queryPlan) : class {
};
const isTypedFilterPlan = method === type_enum.EApiRouteType.GET_LIST && dtoType === dtoType_enum.EApiDtoType.QUERY && queryPlan !== undefined && !queryPlan.filter.isLegacy;
class GeneratedDTO extends BaseClass {
constructor() {
super();
if (method === type_enum.EApiRouteType.GET_LIST && dtoType === dtoType_enum.EApiDtoType.QUERY) {
for (const property of queryFilterProperties) {
Object.defineProperty(this, `${property.name}[value]`, {
// eslint-disable-next-line @elsikora/typescript/naming-convention
configurable: true,
// eslint-disable-next-line @elsikora/typescript/naming-convention
enumerable: true,
value: undefined,
// eslint-disable-next-line @elsikora/typescript/naming-convention
writable: true,
});
Object.defineProperty(this, `${property.name}[values]`, {
// eslint-disable-next-line @elsikora/typescript/naming-convention
configurable: true,
// eslint-disable-next-line @elsikora/typescript/naming-convention
enumerable: true,
value: undefined,
// eslint-disable-next-line @elsikora/typescript/naming-convention
writable: true,
});
Object.defineProperty(this, `${property.name}[operator]`, {
// eslint-disable-next-line @elsikora/typescript/naming-convention
configurable: true,
// eslint-disable-next-line @elsikora/typescript/naming-convention
enumerable: true,
value: undefined,
// eslint-disable-next-line @elsikora/typescript/naming-convention
writable: true,
});
}
}
else {
for (const property of markedProperties) {
Object.defineProperty(this, property.name, {
// eslint-disable-next-line @elsikora/typescript/naming-convention
configurable: true,
// eslint-disable-next-line @elsikora/typescript/naming-convention
enumerable: true,
value: undefined,
// eslint-disable-next-line @elsikora/typescript/naming-convention
writable: true,
});
}
}
}
}
Object.defineProperty(GeneratedDTO, "name", {
value: queryPlan && method === type_enum.EApiRouteType.GET_LIST && dtoType === dtoType_enum.EApiDtoType.QUERY ? queryPlan.schemaName : `${entityMetadata.name ?? "UnknownResource"}${camelCaseString_utility.CamelCaseString(method)}${camelCaseString_utility.CamelCaseString(dtoType)}DTO`,
});
push_utility.DtoAutoContextPush(GeneratedDTO.prototype, method, dtoType);
try {
if (method === type_enum.EApiRouteType.GET_LIST && dtoType === dtoType_enum.EApiDtoType.QUERY) {
for (const property of queryFilterProperties) {
const decorators = buildDecorator_utility.DtoBuildDecorator(method, property.metadata, property.entityMetadata, dtoType, property.name, currentGuard);
if (decorators) {
for (const [, decorator] of decorators.entries()) {
decorator(GeneratedDTO.prototype, `${property.name}[value]`);
}
}
filterDecorator_utility.DtoGenerateFilterDecorator(property.metadata, property.entityMetadata, property.allowedOperations, `${queryPlan?.schemaName ?? entityMetadata.name ?? "UnknownResource"}${camelCaseString_utility.CamelCaseString(property.name)}FilterOperation`)(GeneratedDTO.prototype, `${property.name}[operator]`);
const metadataArray = {
...property.metadata,
isArray: true,
isUniqueItems: false,
maxItems: generate_constant.DTO_GENERATE_CONSTANT.MAXIMUM_FILTER_PROPERTIES,
minItems: isTypedFilterPlan ? operatorRegistry_constant.FILTER_OPERATOR_REGISTRY_CONSTANT.VALUES_MINIMUM_OPERAND_COUNT : generate_constant.DTO_GENERATE_CONSTANT.MINIMUM_FILTER_PROPERTIES,
};
const decoratorsArray = buildDecorator_utility.DtoBuildDecorator(method, metadataArray, property.entityMetadata, dtoType, property.name, currentGuard);
if (isTypedFilterPlan) {
classTransformer.Transform(({ value }) => (value === undefined || Array.isArray(value) ? value : [value]), { toClassOnly: true })(GeneratedDTO.prototype, `${property.name}[values]`);
}
if (decoratorsArray) {
for (const [, decorator] of decoratorsArray.entries()) {
decorator(GeneratedDTO.prototype, `${property.name}[values]`);
}
}
}
}
else {
for (const property of markedProperties) {
const generatedDTOs = dynamic_utility.DtoGenerateDynamic(method, property.metadata, entityMetadata, dtoType, property.name, currentGuard);
const decorators = buildDecorator_utility.DtoBuildDecorator(method, property.metadata, entityMetadata, dtoType, property.name, currentGuard, generatedDTOs);
if (decorators) {
for (const [, decorator] of decorators.entries()) {
decorator(GeneratedDTO.prototype, property.name);
}
}
if (property.metadata.type === desribeType_enum.EApiPropertyDescribeType.OBJECT && Array.isArray(property.metadata.dataType)) {
// @ts-ignore
extraModels.push(...property.metadata.dataType);
}
if (generatedDTOs) {
for (const [, value] of Object.entries(generatedDTOs)) {
extraModels.push(value);
}
}
}
}
}
finally {
pop_utility.DtoAutoContextPop(GeneratedDTO.prototype);
}
if (isTypedFilterPlan) {
const hiddenSwaggerProperties = new Set();
for (const property of queryFilterProperties) {
for (const suffix of ["operator", "value", "values"]) {
const propertyName = `${property.name}[${suffix}]`;
hiddenSwaggerProperties.add(`:${propertyName}`);
Reflect.deleteMetadata(metadata_constant.SWAGGER_METADATA_CONSTANT.MODEL_PROPERTIES, GeneratedDTO.prototype, propertyName);
}
}
const swaggerProperties = Reflect.getMetadata(metadata_constant.SWAGGER_METADATA_CONSTANT.MODEL_PROPERTIES_ARRAY, GeneratedDTO.prototype) ?? [];
Reflect.defineMetadata(metadata_constant.SWAGGER_METADATA_CONSTANT.MODEL_PROPERTIES_ARRAY, swaggerProperties.filter((propertyName) => !hiddenSwaggerProperties.has(propertyName)), GeneratedDTO.prototype);
}
if (dtoConfig?.validators) {
for (const validator of dtoConfig.validators) {
classValidator.Validate(validator.constraintClass, validator.options)(GeneratedDTO.prototype, "object");
}
}
if (method === type_enum.EApiRouteType.GET_LIST && dtoType === dtoType_enum.EApiDtoType.QUERY) {
Object.defineProperty(GeneratedDTO.prototype, "object", {
// eslint-disable-next-line @elsikora/typescript/naming-convention
configurable: true,
// eslint-disable-next-line @elsikora/typescript/naming-convention
enumerable: true,
value: function () {
return this;
},
// eslint-disable-next-line @elsikora/typescript/naming-convention
writable: true,
});
classValidator.Validate(pairedCustomSuffixesFields_validator.HasPairedCustomSuffixesFieldsValidator, ["operator", ["value", "values"]])(GeneratedDTO.prototype, "object");
}
if (extraModels.length > 0) {
swagger.ApiExtraModels(...extraModels)(GeneratedDTO);
}
// @ts-ignore
const result = method === type_enum.EApiRouteType.GET_LIST && dtoType === dtoType_enum.EApiDtoType.RESPONSE ? getListResponse_utility.DtoGenerateGetListResponse(entity, GeneratedDTO, `${entityMetadata.name ?? "UnknownResource"}${camelCaseString_utility.CamelCaseString(method)}${camelCaseString_utility.CamelCaseString(dtoType)}ItemsDTO`) : GeneratedDTO;
dtoGenerateCache.set(cacheKey, result);
return result;
}
exports.DtoGenerate = DtoGenerate;
//# sourceMappingURL=core.utility.js.map