@hemia/common 0.0.16 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,7 @@
1
1
  import 'reflect-metadata';
2
2
  import { decorate, injectable } from 'inversify';
3
+ import { validate } from 'class-validator';
4
+ import { plainToInstance } from 'class-transformer';
3
5
 
4
6
  const METADATA_KEYS = {
5
7
  BASE_PATH: 'base_path',
@@ -378,22 +380,90 @@ function Custom(key) {
378
380
  }
379
381
 
380
382
  /**
381
- * Decorador para validar datos de entrada en un controlador.
383
+ * Decorador para validar datos de entrada usando class-validator.
382
384
  *
383
385
  * @example
384
- * // Valida el parámetro 'body' usando la función validateUser
385
- * @Validate(validateUser)
386
- * async create(@Body() body: any) {}
386
+ * // Valida el body usando un DTO
387
+ * async create(@Validate(CreateUserDto) @Body() body: CreateUserDto) {}
388
+ *
389
+ * @example
390
+ * // Valida con función personalizada
391
+ * @Validate((value) => value.age > 18)
392
+ * async create(@Body('age') age: number) {}
387
393
  *
388
- * @param validator Función de validación que recibe el valor y retorna booleano o lanza error.
394
+ * @param validatorOrDto Clase DTO o función de validación.
389
395
  */
390
- function Validate(validator) {
396
+ function Validate(validatorOrDto) {
391
397
  return (target, propertyKey, parameterIndex) => {
392
398
  const existingValidators = Reflect.getMetadata(METADATA_KEYS.VALIDATORS, target, propertyKey || '') || [];
393
- existingValidators.push({ index: parameterIndex, validator });
399
+ // Determinar si es una clase DTO o función
400
+ const isClassValidator = typeof validatorOrDto === 'function' && validatorOrDto.prototype;
401
+ const validator = isClassValidator
402
+ ? async (value) => {
403
+ const instance = plainToInstance(validatorOrDto, value);
404
+ const errors = await validate(instance, { whitelist: true, forbidNonWhitelisted: true });
405
+ if (errors.length > 0) {
406
+ throw new ValidationException(errors);
407
+ }
408
+ return true;
409
+ }
410
+ : validatorOrDto;
411
+ existingValidators.push({ index: parameterIndex, validator, dtoClass: isClassValidator ? validatorOrDto : null });
394
412
  Reflect.defineMetadata(METADATA_KEYS.VALIDATORS, existingValidators, target, propertyKey || '');
395
413
  };
396
414
  }
415
+ /**
416
+ * Decorador de método para validar todo el body con un DTO
417
+ * @example
418
+ * @Post()
419
+ * @ValidateDto(CreateUserDto)
420
+ * async create(@Body() body: CreateUserDto) {}
421
+ */
422
+ function ValidateDto(dtoClass) {
423
+ return (target, propertyKey, descriptor) => {
424
+ const existingValidators = Reflect.getMetadata(METADATA_KEYS.VALIDATORS, target, propertyKey) || [];
425
+ const validator = async (value) => {
426
+ const instance = plainToInstance(dtoClass, value);
427
+ const errors = await validate(instance, {
428
+ whitelist: true,
429
+ forbidNonWhitelisted: true
430
+ });
431
+ if (errors.length > 0) {
432
+ throw new ValidationException(errors);
433
+ }
434
+ return true;
435
+ };
436
+ const paramsMetadata = Reflect.getMetadata(METADATA_KEYS.PARAMS, target, propertyKey) || [];
437
+ const bodyParam = paramsMetadata.find((p) => p.type === ParamType.BODY);
438
+ if (bodyParam) {
439
+ existingValidators.push({
440
+ index: bodyParam.index,
441
+ validator,
442
+ dtoClass
443
+ });
444
+ Reflect.defineMetadata(METADATA_KEYS.VALIDATORS, existingValidators, target, propertyKey);
445
+ }
446
+ return descriptor;
447
+ };
448
+ }
449
+ /**
450
+ * Excepción personalizada para errores de validación
451
+ */
452
+ class ValidationException extends Error {
453
+ constructor(errors) {
454
+ super('Validation failed');
455
+ this.statusCode = 400;
456
+ this.errors = errors;
457
+ this.name = 'ValidationException';
458
+ }
459
+ toJSON() {
460
+ return this.errors.map(error => ({
461
+ property: error.property,
462
+ value: error.value,
463
+ constraints: error.constraints
464
+ }));
465
+ }
466
+ }
397
467
 
398
468
  /**
399
469
  * Decorador para transformar datos de entrada en un controlador.
@@ -1102,4 +1172,4 @@ class ApiResponse {
1102
1172
  }
1103
1173
  }
1104
1174
 
1105
- export { AllowAny, ApiKey, ApiResponse, BackupError, BadGatewayError, BadRequestError, Body, BusinessRuleViolationError, ConfigurationError, ConflictError, ConnectionError, Controller, ControllerRegistry, Cookies, Custom, CustomHttpError, DataConflictError, DataIntegrityError, DataMigrationError, DataNotFoundError, DataValidationError, DefaultValuePipe, Delete, DependencyError, DomainError, DuplicateEntityError, EntityNotFoundError, FeatureFlag, File, Files, ForbiddenError, GatewayTimeoutError, Get, Head, Header, Headers, Host, HttpError, HttpErrorWithDetails, HttpMethod, IndexingError, InfraAuthenticationError, InfraAuthorizationError, InfraCacheConnectionError, InfraConfigurationError, InfraDataDeserializationError, InfraDataSerializationError, InfraDatabaseConnectionError, InfraExternalServiceError, InfraMessageQueueError, InfraNetworkError, InfraServiceUnavailableError, InfraTimeoutError, InfrastructureError, InternalServerError, Ip, IpWhitelist, Locale, METADATA_KEYS, ManualRegister, MethodNotAllowedError, Module, Next, NotAcceptableError, NotFoundError, NotImplementedError, OperationNotAllowedError, Options, Owner, Param, ParamType, ParseArrayPipe, ParseBoolPipe, ParseDatePipe, ParseEnumPipe, ParseFilePipe, ParseFloatPipe, ParseIntPipe, ParseUUIDPipe, Patch, PayloadTooLargeError, Permissions, PersistenceError, PolicyBased, Post, PreconditionFailedError, ProxyAuthenticationRequiredError, Public, Put, Query, QueryExecutionError, RateLimit, Redirect, Repository, Req, ReqAuth, ReqContext, ReqPermissions, ReqUser, Request, RequestTimeoutError, Res, ResourceLimitError, ResourceLimitExceededError, Response, RestoreError, Roles, SchemaMismatchError, Scopes, Serialize, Service, ServiceUnavailableError, Session, SetMetadata, Throttle, TimeoutError, TooManyRequestsError, TransactionError, Transform, URITooLongError, UnauthorizedError, UnprocessableEntityError, UnsupportedMediaTypeError, UseGuards, UseInterceptors, UsePipes, Validate, ValidationError, ValidationPipe, isRedirectResponse };
1175
+ export { AllowAny, ApiKey, ApiResponse, BackupError, BadGatewayError, BadRequestError, Body, BusinessRuleViolationError, ConfigurationError, ConflictError, ConnectionError, Controller, ControllerRegistry, Cookies, Custom, CustomHttpError, DataConflictError, DataIntegrityError, DataMigrationError, DataNotFoundError, DataValidationError, DefaultValuePipe, Delete, DependencyError, DomainError, DuplicateEntityError, EntityNotFoundError, FeatureFlag, File, Files, ForbiddenError, GatewayTimeoutError, Get, Head, Header, Headers, Host, HttpError, HttpErrorWithDetails, HttpMethod, IndexingError, InfraAuthenticationError, InfraAuthorizationError, InfraCacheConnectionError, InfraConfigurationError, InfraDataDeserializationError, InfraDataSerializationError, InfraDatabaseConnectionError, InfraExternalServiceError, InfraMessageQueueError, InfraNetworkError, InfraServiceUnavailableError, InfraTimeoutError, InfrastructureError, InternalServerError, Ip, IpWhitelist, Locale, METADATA_KEYS, ManualRegister, MethodNotAllowedError, Module, Next, NotAcceptableError, NotFoundError, NotImplementedError, OperationNotAllowedError, Options, Owner, Param, ParamType, ParseArrayPipe, ParseBoolPipe, ParseDatePipe, ParseEnumPipe, ParseFilePipe, ParseFloatPipe, ParseIntPipe, ParseUUIDPipe, Patch, PayloadTooLargeError, Permissions, PersistenceError, PolicyBased, Post, PreconditionFailedError, ProxyAuthenticationRequiredError, Public, Put, Query, QueryExecutionError, RateLimit, Redirect, Repository, Req, ReqAuth, ReqContext, ReqPermissions, ReqUser, Request, RequestTimeoutError, Res, ResourceLimitError, ResourceLimitExceededError, Response, RestoreError, Roles, SchemaMismatchError, Scopes, Serialize, Service, ServiceUnavailableError, Session, SetMetadata, Throttle, TimeoutError, TooManyRequestsError, TransactionError, Transform, URITooLongError, UnauthorizedError, UnprocessableEntityError, UnsupportedMediaTypeError, UseGuards, UseInterceptors, UsePipes, Validate, ValidateDto, ValidationError, ValidationException, ValidationPipe, isRedirectResponse };
@@ -2,6 +2,8 @@
2
2
 
3
3
  require('reflect-metadata');
4
4
  var inversify = require('inversify');
5
+ var classValidator = require('class-validator');
6
+ var classTransformer = require('class-transformer');
5
7
 
6
8
  const METADATA_KEYS = {
7
9
  BASE_PATH: 'base_path',
@@ -380,22 +382,90 @@ function Custom(key) {
380
382
  }
381
383
 
382
384
  /**
383
- * Decorador para validar datos de entrada en un controlador.
385
+ * Decorador para validar datos de entrada usando class-validator.
384
386
  *
385
387
  * @example
386
- * // Valida el parámetro 'body' usando la función validateUser
387
- * @Validate(validateUser)
388
- * async create(@Body() body: any) {}
388
+ * // Valida el body usando un DTO
389
+ * async create(@Validate(CreateUserDto) @Body() body: CreateUserDto) {}
390
+ *
391
+ * @example
392
+ * // Valida con función personalizada
393
+ * @Validate((value) => value.age > 18)
394
+ * async create(@Body('age') age: number) {}
389
395
  *
390
- * @param validator Función de validación que recibe el valor y retorna booleano o lanza error.
396
+ * @param validatorOrDto Clase DTO o función de validación.
391
397
  */
392
- function Validate(validator) {
398
+ function Validate(validatorOrDto) {
393
399
  return (target, propertyKey, parameterIndex) => {
394
400
  const existingValidators = Reflect.getMetadata(METADATA_KEYS.VALIDATORS, target, propertyKey || '') || [];
395
- existingValidators.push({ index: parameterIndex, validator });
401
+ // Determinar si es una clase DTO o función
402
+ const isClassValidator = typeof validatorOrDto === 'function' && validatorOrDto.prototype;
403
+ const validator = isClassValidator
404
+ ? async (value) => {
405
+ const instance = classTransformer.plainToInstance(validatorOrDto, value);
406
+ const errors = await classValidator.validate(instance, { whitelist: true, forbidNonWhitelisted: true });
407
+ if (errors.length > 0) {
408
+ throw new ValidationException(errors);
409
+ }
410
+ return true;
411
+ }
412
+ : validatorOrDto;
413
+ existingValidators.push({ index: parameterIndex, validator, dtoClass: isClassValidator ? validatorOrDto : null });
396
414
  Reflect.defineMetadata(METADATA_KEYS.VALIDATORS, existingValidators, target, propertyKey || '');
397
415
  };
398
416
  }
417
+ /**
418
+ * Decorador de método para validar todo el body con un DTO
419
+ * @example
420
+ * @Post()
421
+ * @ValidateDto(CreateUserDto)
422
+ * async create(@Body() body: CreateUserDto) {}
423
+ */
424
+ function ValidateDto(dtoClass) {
425
+ return (target, propertyKey, descriptor) => {
426
+ const existingValidators = Reflect.getMetadata(METADATA_KEYS.VALIDATORS, target, propertyKey) || [];
427
+ const validator = async (value) => {
428
+ const instance = classTransformer.plainToInstance(dtoClass, value);
429
+ const errors = await classValidator.validate(instance, {
430
+ whitelist: true,
431
+ forbidNonWhitelisted: true
432
+ });
433
+ if (errors.length > 0) {
434
+ throw new ValidationException(errors);
435
+ }
436
+ return true;
437
+ };
438
+ const paramsMetadata = Reflect.getMetadata(METADATA_KEYS.PARAMS, target, propertyKey) || [];
439
+ const bodyParam = paramsMetadata.find((p) => p.type === exports.ParamType.BODY);
440
+ if (bodyParam) {
441
+ existingValidators.push({
442
+ index: bodyParam.index,
443
+ validator,
444
+ dtoClass
445
+ });
446
+ Reflect.defineMetadata(METADATA_KEYS.VALIDATORS, existingValidators, target, propertyKey);
447
+ }
448
+ return descriptor;
449
+ };
450
+ }
451
+ /**
452
+ * Excepción personalizada para errores de validación
453
+ */
454
+ class ValidationException extends Error {
455
+ constructor(errors) {
456
+ super('Validation failed');
457
+ this.statusCode = 400;
458
+ this.errors = errors;
459
+ this.name = 'ValidationException';
460
+ }
461
+ toJSON() {
462
+ return this.errors.map(error => ({
463
+ property: error.property,
464
+ value: error.value,
465
+ constraints: error.constraints
466
+ }));
467
+ }
468
+ }
399
469
 
400
470
  /**
401
471
  * Decorador para transformar datos de entrada en un controlador.
@@ -1229,6 +1299,8 @@ exports.UseGuards = UseGuards;
1229
1299
  exports.UseInterceptors = UseInterceptors;
1230
1300
  exports.UsePipes = UsePipes;
1231
1301
  exports.Validate = Validate;
1302
+ exports.ValidateDto = ValidateDto;
1232
1303
  exports.ValidationError = ValidationError;
1304
+ exports.ValidationException = ValidationException;
1233
1305
  exports.ValidationPipe = ValidationPipe;
1234
1306
  exports.isRedirectResponse = isRedirectResponse;
@@ -1,12 +1,40 @@
1
1
  import 'reflect-metadata';
2
+ import { ValidationError } from 'class-validator';
2
3
  /**
3
- * Decorador para validar datos de entrada en un controlador.
4
+ * Decorador para validar datos de entrada usando class-validator.
4
5
  *
5
6
  * @example
6
- * // Valida el parámetro 'body' usando la función validateUser
7
- * @Validate(validateUser)
8
- * async create(@Body() body: any) {}
7
+ * // Valida el body usando un DTO
8
+ * async create(@Validate(CreateUserDto) @Body() body: CreateUserDto) {}
9
9
  *
10
- * @param validator Función de validación que recibe el valor y retorna booleano o lanza error.
10
+ * @example
11
+ * // Valida con función personalizada
12
+ * @Validate((value) => value.age > 18)
13
+ * async create(@Body('age') age: number) {}
14
+ *
15
+ * @param validatorOrDto Clase DTO o función de validación.
16
+ */
17
+ export declare function Validate(validatorOrDto: any): ParameterDecorator;
18
+ /**
19
+ * Decorador de método para validar todo el body con un DTO
20
+ * @example
21
+ * @Post()
22
+ * @ValidateDto(CreateUserDto)
23
+ * async create(@Body() body: CreateUserDto) {}
24
+ */
25
+ export declare function ValidateDto(dtoClass: any): MethodDecorator;
26
+ /**
27
+ * Excepción personalizada para errores de validación
11
28
  */
12
- export declare function Validate(validator: (value: any) => boolean | void): ParameterDecorator;
29
+ export declare class ValidationException extends Error {
30
+ errors: ValidationError[];
31
+ statusCode: number;
32
+ constructor(errors: ValidationError[]);
33
+ toJSON(): {
34
+ property: string;
35
+ value: any;
36
+ constraints: {
37
+ [type: string]: string;
38
+ } | undefined;
39
+ }[];
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hemia/common",
3
- "version": "0.0.16",
3
+ "version": "0.0.17",
4
4
  "description": "Paquete común para proyectos de Hemia",
5
5
  "main": "dist/hemia-common.js",
6
6
  "module": "dist/hemia-common.esm.js",
@@ -30,11 +30,17 @@
30
30
  "ts-jest": "^29.2.5",
31
31
  "ts-node": "^8.9.0",
32
32
  "typescript": "^5.5.4",
33
- "inversify": "^7.11.0"
33
+ "inversify": "^7.11.0",
34
+ "class-transformer": "^0.5.1",
35
+ "class-validator": "^0.15.1"
34
36
  },
35
37
  "peerDependencies": {
36
38
  "reflect-metadata": "^0.2.2"
37
39
  },
40
+ "optionalDependencies": {
41
+ "class-transformer": "^0.5.1",
42
+ "class-validator": "^0.15.1"
43
+ },
38
44
  "author": "Hemia Team",
39
45
  "license": "ISC",
40
46
  "files": [