@hemia/common 0.0.11 → 0.0.13
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.
- package/dist/hemia-common.esm.js +272 -27
- package/dist/hemia-common.js +283 -26
- package/dist/types/decorators/core/index.d.ts +2 -0
- package/dist/types/decorators/core/use-guards.decorator.d.ts +1 -1
- package/dist/types/decorators/core/use-interceptors.decorator.d.ts +1 -1
- package/dist/types/decorators/core/use-pipes.decorator.d.ts +7 -0
- package/dist/types/decorators/http/param.decorators.d.ts +21 -20
- package/dist/types/decorators/index.d.ts +1 -0
- package/dist/types/decorators/metadata.d.ts +1 -0
- package/dist/types/decorators/pipes/default-value.pipe.d.ts +9 -0
- package/dist/types/decorators/pipes/index.d.ts +10 -0
- package/dist/types/decorators/pipes/parse-array.pipe.d.ts +15 -0
- package/dist/types/decorators/pipes/parse-bool.pipe.d.ts +8 -0
- package/dist/types/decorators/pipes/parse-date.pipe.d.ts +8 -0
- package/dist/types/decorators/pipes/parse-enum.pipe.d.ts +9 -0
- package/dist/types/decorators/pipes/parse-file.pipe.d.ts +15 -0
- package/dist/types/decorators/pipes/parse-float.pipe.d.ts +8 -0
- package/dist/types/decorators/pipes/parse-int.pipe.d.ts +8 -0
- package/dist/types/decorators/pipes/parse-uuid.pipe.d.ts +11 -0
- package/dist/types/decorators/pipes/validation.pipe.d.ts +8 -0
- package/dist/types/interfaces/index.d.ts +1 -0
- package/dist/types/interfaces/param.interface.d.ts +2 -0
- package/dist/types/interfaces/pipe.interface.d.ts +34 -0
- package/package.json +3 -2
package/dist/hemia-common.esm.js
CHANGED
|
@@ -10,6 +10,7 @@ const METADATA_KEYS = {
|
|
|
10
10
|
COOKIES: 'cookies',
|
|
11
11
|
GUARDS: 'hemia:guards',
|
|
12
12
|
INTERCEPTORS: 'hemia:interceptors',
|
|
13
|
+
PIPES: 'hemia:pipes',
|
|
13
14
|
CUSTOM: 'hemia:custom',
|
|
14
15
|
SERIALIZE: 'hemia:serialize',
|
|
15
16
|
ROLES: 'hemia:roles',
|
|
@@ -73,10 +74,15 @@ const Options = createMethodDecorator(HttpMethod.OPTIONS);
|
|
|
73
74
|
const Head = createMethodDecorator(HttpMethod.HEAD);
|
|
74
75
|
|
|
75
76
|
const createParamDecorator = (type) => {
|
|
76
|
-
return (data) => {
|
|
77
|
+
return (data, ...pipes) => {
|
|
77
78
|
return (target, propertyKey, parameterIndex) => {
|
|
78
79
|
const existingParams = Reflect.getMetadata(METADATA_KEYS.PARAMS, target, propertyKey || '') || [];
|
|
79
|
-
existingParams.push({
|
|
80
|
+
existingParams.push({
|
|
81
|
+
index: parameterIndex,
|
|
82
|
+
type,
|
|
83
|
+
data,
|
|
84
|
+
pipes: pipes.length > 0 ? pipes : undefined
|
|
85
|
+
});
|
|
80
86
|
Reflect.defineMetadata(METADATA_KEYS.PARAMS, existingParams, target, propertyKey || '');
|
|
81
87
|
};
|
|
82
88
|
};
|
|
@@ -230,13 +236,12 @@ function extendArrayMetadata(key, metadata, target) {
|
|
|
230
236
|
*/
|
|
231
237
|
function UseGuards(...guards) {
|
|
232
238
|
return (target, key, descriptor) => {
|
|
233
|
-
guards.filter(guard => guard && (typeof guard === 'function' || typeof guard === 'object'));
|
|
239
|
+
const validGuards = guards.filter(guard => guard && (typeof guard === 'function' || typeof guard === 'object'));
|
|
234
240
|
if (descriptor) {
|
|
235
|
-
extendArrayMetadata(METADATA_KEYS.GUARDS,
|
|
241
|
+
extendArrayMetadata(METADATA_KEYS.GUARDS, validGuards, descriptor.value);
|
|
236
242
|
return descriptor;
|
|
237
243
|
}
|
|
238
|
-
extendArrayMetadata(METADATA_KEYS.GUARDS,
|
|
239
|
-
return target;
|
|
244
|
+
extendArrayMetadata(METADATA_KEYS.GUARDS, validGuards, target);
|
|
240
245
|
};
|
|
241
246
|
}
|
|
242
247
|
|
|
@@ -252,7 +257,21 @@ function UseInterceptors(...interceptors) {
|
|
|
252
257
|
return descriptor;
|
|
253
258
|
}
|
|
254
259
|
extendArrayMetadata(METADATA_KEYS.INTERCEPTORS, validInterceptors, target);
|
|
255
|
-
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Decorador que vincula Pipes al controlador o método.
|
|
265
|
+
* @param pipes Una lista de clases Pipe o instancias de Pipe.
|
|
266
|
+
*/
|
|
267
|
+
function UsePipes(...pipes) {
|
|
268
|
+
return (target, key, descriptor) => {
|
|
269
|
+
const validPipes = pipes.filter(pipe => pipe && (typeof pipe === 'function' || typeof pipe === 'object'));
|
|
270
|
+
if (descriptor) {
|
|
271
|
+
extendArrayMetadata(METADATA_KEYS.PIPES, validPipes, descriptor.value);
|
|
272
|
+
return descriptor;
|
|
273
|
+
}
|
|
274
|
+
extendArrayMetadata(METADATA_KEYS.PIPES, validPipes, target);
|
|
256
275
|
};
|
|
257
276
|
}
|
|
258
277
|
|
|
@@ -324,6 +343,12 @@ const Controller = (basePath = '/') => {
|
|
|
324
343
|
};
|
|
325
344
|
};
|
|
326
345
|
|
|
346
|
+
const ManualRegister = () => {
|
|
347
|
+
return (target) => {
|
|
348
|
+
Reflect.defineMetadata(METADATA_KEYS.MANUAL_REGISTER, true, target);
|
|
349
|
+
};
|
|
350
|
+
};
|
|
351
|
+
|
|
327
352
|
/**
|
|
328
353
|
* Decorador para extraer datos personalizados del request.
|
|
329
354
|
*
|
|
@@ -378,25 +403,6 @@ function Transform(transformer) {
|
|
|
378
403
|
};
|
|
379
404
|
}
|
|
380
405
|
|
|
381
|
-
class ApiResponse {
|
|
382
|
-
static success(data, message = 'OK', status = 200) {
|
|
383
|
-
return {
|
|
384
|
-
success: true,
|
|
385
|
-
message,
|
|
386
|
-
data,
|
|
387
|
-
status,
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
static error(message, error = null, status = 500) {
|
|
391
|
-
return {
|
|
392
|
-
success: false,
|
|
393
|
-
message,
|
|
394
|
-
error,
|
|
395
|
-
status,
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
406
|
class HttpError extends Error {
|
|
401
407
|
constructor(message, statusCode, error) {
|
|
402
408
|
super(message);
|
|
@@ -693,4 +699,243 @@ class InfraDataDeserializationError extends InfrastructureError {
|
|
|
693
699
|
}
|
|
694
700
|
}
|
|
695
701
|
|
|
696
|
-
|
|
702
|
+
/**
|
|
703
|
+
* Pipe de validación genérico que valida objetos contra un esquema de validación.
|
|
704
|
+
* Este es un ejemplo básico - en producción se usaría con librerías como class-validator o zod.
|
|
705
|
+
*/
|
|
706
|
+
class ValidationPipe {
|
|
707
|
+
async transform(value, metadata) {
|
|
708
|
+
if (value === undefined || value === null) {
|
|
709
|
+
throw new HttpError('Validation failed: value is required', 400);
|
|
710
|
+
}
|
|
711
|
+
// Aquí iría la lógica de validación real (ej: con class-validator)
|
|
712
|
+
// Por ahora solo verifica que el valor exista
|
|
713
|
+
return value;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Pipe que convierte un valor a número entero.
|
|
719
|
+
* Lanza una excepción si el valor no puede ser convertido.
|
|
720
|
+
*/
|
|
721
|
+
class ParseIntPipe {
|
|
722
|
+
transform(value, metadata) {
|
|
723
|
+
const val = parseInt(value, 10);
|
|
724
|
+
if (isNaN(val)) {
|
|
725
|
+
throw new HttpError(`Validation failed: "${value}" is not an integer`, 400);
|
|
726
|
+
}
|
|
727
|
+
return val;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Pipe que convierte un valor a número de punto flotante.
|
|
733
|
+
* Lanza una excepción si el valor no puede ser convertido.
|
|
734
|
+
*/
|
|
735
|
+
class ParseFloatPipe {
|
|
736
|
+
transform(value, metadata) {
|
|
737
|
+
const val = parseFloat(value);
|
|
738
|
+
if (isNaN(val)) {
|
|
739
|
+
throw new HttpError(`Validation failed: "${value}" is not a valid number`, 400);
|
|
740
|
+
}
|
|
741
|
+
return val;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Pipe que convierte un valor a booleano.
|
|
747
|
+
* Acepta: true, false, 1, 0, 'true', 'false', '1', '0'
|
|
748
|
+
*/
|
|
749
|
+
class ParseBoolPipe {
|
|
750
|
+
transform(value, metadata) {
|
|
751
|
+
if (typeof value === 'boolean') {
|
|
752
|
+
return value;
|
|
753
|
+
}
|
|
754
|
+
const normalizedValue = String(value).toLowerCase();
|
|
755
|
+
if (normalizedValue === 'true' || normalizedValue === '1') {
|
|
756
|
+
return true;
|
|
757
|
+
}
|
|
758
|
+
if (normalizedValue === 'false' || normalizedValue === '0') {
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
761
|
+
throw new HttpError(`Validation failed: "${value}" is not a boolean value`, 400);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Pipe que convierte un valor a array.
|
|
767
|
+
* Puede parsear strings separados por comas o validar que el valor ya sea un array.
|
|
768
|
+
*/
|
|
769
|
+
class ParseArrayPipe {
|
|
770
|
+
constructor(options) {
|
|
771
|
+
this.options = options;
|
|
772
|
+
}
|
|
773
|
+
async transform(value, metadata) {
|
|
774
|
+
if (!value) {
|
|
775
|
+
if (this.options?.optional) {
|
|
776
|
+
return [];
|
|
777
|
+
}
|
|
778
|
+
throw new HttpError('Validation failed: array is required', 400);
|
|
779
|
+
}
|
|
780
|
+
let array;
|
|
781
|
+
if (Array.isArray(value)) {
|
|
782
|
+
array = value;
|
|
783
|
+
}
|
|
784
|
+
else if (typeof value === 'string') {
|
|
785
|
+
const separator = this.options?.separator || ',';
|
|
786
|
+
array = value.split(separator).map(item => item.trim());
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
throw new HttpError(`Validation failed: "${value}" is not an array`, 400);
|
|
790
|
+
}
|
|
791
|
+
// Si hay un pipe para los items, aplicarlo a cada elemento
|
|
792
|
+
if (this.options?.items) {
|
|
793
|
+
const transformed = [];
|
|
794
|
+
for (const item of array) {
|
|
795
|
+
transformed.push(await this.options.items.transform(item, metadata));
|
|
796
|
+
}
|
|
797
|
+
return transformed;
|
|
798
|
+
}
|
|
799
|
+
return array;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Pipe que valida que un valor sea un UUID válido.
|
|
805
|
+
* Soporta versiones 3, 4 y 5 de UUID.
|
|
806
|
+
*/
|
|
807
|
+
class ParseUUIDPipe {
|
|
808
|
+
constructor(version) {
|
|
809
|
+
this.version = version;
|
|
810
|
+
this.uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
811
|
+
}
|
|
812
|
+
transform(value, metadata) {
|
|
813
|
+
if (!value || typeof value !== 'string') {
|
|
814
|
+
throw new HttpError('Validation failed: UUID is required', 400);
|
|
815
|
+
}
|
|
816
|
+
if (!this.uuidRegex.test(value)) {
|
|
817
|
+
throw new HttpError(`Validation failed: "${value}" is not a valid UUID`, 400);
|
|
818
|
+
}
|
|
819
|
+
if (this.version) {
|
|
820
|
+
const uuidVersion = value.charAt(14);
|
|
821
|
+
if (uuidVersion !== this.version) {
|
|
822
|
+
throw new HttpError(`Validation failed: expected UUID v${this.version}, got v${uuidVersion}`, 400);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return value;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Pipe que valida que un valor pertenezca a un enum específico.
|
|
831
|
+
*/
|
|
832
|
+
class ParseEnumPipe {
|
|
833
|
+
constructor(enumType) {
|
|
834
|
+
this.enumType = enumType;
|
|
835
|
+
}
|
|
836
|
+
transform(value, metadata) {
|
|
837
|
+
const enumValues = Object.values(this.enumType);
|
|
838
|
+
if (!enumValues.includes(value)) {
|
|
839
|
+
const validValues = enumValues.join(', ');
|
|
840
|
+
throw new HttpError(`Validation failed: "${value}" is not a valid enum value. Valid values: ${validValues}`, 400);
|
|
841
|
+
}
|
|
842
|
+
return value;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Pipe que proporciona un valor por defecto si el valor de entrada es undefined o null.
|
|
848
|
+
*/
|
|
849
|
+
class DefaultValuePipe {
|
|
850
|
+
constructor(defaultValue) {
|
|
851
|
+
this.defaultValue = defaultValue;
|
|
852
|
+
}
|
|
853
|
+
transform(value, metadata) {
|
|
854
|
+
if (value === undefined || value === null || value === '') {
|
|
855
|
+
return this.defaultValue;
|
|
856
|
+
}
|
|
857
|
+
return value;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Pipe que valida archivos subidos.
|
|
863
|
+
* Puede validar tipo MIME, tamaño máximo y campos requeridos.
|
|
864
|
+
*/
|
|
865
|
+
class ParseFilePipe {
|
|
866
|
+
constructor(options) {
|
|
867
|
+
this.options = options;
|
|
868
|
+
}
|
|
869
|
+
transform(value, metadata) {
|
|
870
|
+
if (!value) {
|
|
871
|
+
throw new HttpError('Validation failed: file is required', 400);
|
|
872
|
+
}
|
|
873
|
+
// Validar tamaño
|
|
874
|
+
if (this.options?.maxSize && value.size > this.options.maxSize) {
|
|
875
|
+
const maxSizeMB = (this.options.maxSize / (1024 * 1024)).toFixed(2);
|
|
876
|
+
throw new HttpError(`Validation failed: file size exceeds maximum of ${maxSizeMB}MB`, 400);
|
|
877
|
+
}
|
|
878
|
+
// Validar tipo MIME
|
|
879
|
+
if (this.options?.allowedMimeTypes && value.mimetype) {
|
|
880
|
+
if (!this.options.allowedMimeTypes.includes(value.mimetype)) {
|
|
881
|
+
throw new HttpError(`Validation failed: file type "${value.mimetype}" is not allowed. Allowed types: ${this.options.allowedMimeTypes.join(', ')}`, 400);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
// Validar campos requeridos
|
|
885
|
+
if (this.options?.requiredFields) {
|
|
886
|
+
for (const field of this.options.requiredFields) {
|
|
887
|
+
if (!value[field]) {
|
|
888
|
+
throw new HttpError(`Validation failed: file is missing required field "${field}"`, 400);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return value;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* Pipe que convierte un valor a Date.
|
|
898
|
+
* Acepta timestamps, strings de fecha ISO, o cualquier formato parseable por Date.
|
|
899
|
+
*/
|
|
900
|
+
class ParseDatePipe {
|
|
901
|
+
transform(value, metadata) {
|
|
902
|
+
if (!value) {
|
|
903
|
+
throw new HttpError('Validation failed: date is required', 400);
|
|
904
|
+
}
|
|
905
|
+
let date;
|
|
906
|
+
if (typeof value === 'number') {
|
|
907
|
+
date = new Date(value);
|
|
908
|
+
}
|
|
909
|
+
else if (typeof value === 'string') {
|
|
910
|
+
date = new Date(value);
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
throw new HttpError(`Validation failed: "${value}" cannot be converted to Date`, 400);
|
|
914
|
+
}
|
|
915
|
+
if (isNaN(date.getTime())) {
|
|
916
|
+
throw new HttpError(`Validation failed: "${value}" is not a valid date`, 400);
|
|
917
|
+
}
|
|
918
|
+
return date;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
class ApiResponse {
|
|
923
|
+
static success(data, message = 'OK', status = 200) {
|
|
924
|
+
return {
|
|
925
|
+
success: true,
|
|
926
|
+
message,
|
|
927
|
+
data,
|
|
928
|
+
status,
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
static error(message, error = null, status = 500) {
|
|
932
|
+
return {
|
|
933
|
+
success: false,
|
|
934
|
+
message,
|
|
935
|
+
error,
|
|
936
|
+
status,
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
export { ApiResponse, BackupError, BadRequestError, Body, BusinessRuleViolationError, ConfigurationError, ConflictError, ConnectionError, Controller, ControllerRegistry, Cookies, Custom, CustomHttpError, DataConflictError, DataIntegrityError, DataMigrationError, DataNotFoundError, DataValidationError, DefaultValuePipe, Delete, DependencyError, DomainError, DuplicateEntityError, EntityNotFoundError, 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, Locale, METADATA_KEYS, ManualRegister, Module, Next, NotFoundError, OperationNotAllowedError, Options, Param, ParamType, ParseArrayPipe, ParseBoolPipe, ParseDatePipe, ParseEnumPipe, ParseFilePipe, ParseFloatPipe, ParseIntPipe, ParseUUIDPipe, Patch, PersistenceError, Post, Put, Query, QueryExecutionError, Redirect, Repository, Req, ReqAuth, ReqContext, ReqPermissions, ReqUser, Request, Res, ResourceLimitError, ResourceLimitExceededError, Response, RestoreError, SchemaMismatchError, Serialize, Service, ServiceUnavailableError, Session, SetMetadata, TimeoutError, TransactionError, Transform, UnauthorizedError, UnprocessableEntityError, UseGuards, UseInterceptors, UsePipes, Validate, ValidationError, ValidationPipe, isRedirectResponse };
|
package/dist/hemia-common.js
CHANGED
|
@@ -12,6 +12,7 @@ const METADATA_KEYS = {
|
|
|
12
12
|
COOKIES: 'cookies',
|
|
13
13
|
GUARDS: 'hemia:guards',
|
|
14
14
|
INTERCEPTORS: 'hemia:interceptors',
|
|
15
|
+
PIPES: 'hemia:pipes',
|
|
15
16
|
CUSTOM: 'hemia:custom',
|
|
16
17
|
SERIALIZE: 'hemia:serialize',
|
|
17
18
|
ROLES: 'hemia:roles',
|
|
@@ -75,10 +76,15 @@ const Options = createMethodDecorator(exports.HttpMethod.OPTIONS);
|
|
|
75
76
|
const Head = createMethodDecorator(exports.HttpMethod.HEAD);
|
|
76
77
|
|
|
77
78
|
const createParamDecorator = (type) => {
|
|
78
|
-
return (data) => {
|
|
79
|
+
return (data, ...pipes) => {
|
|
79
80
|
return (target, propertyKey, parameterIndex) => {
|
|
80
81
|
const existingParams = Reflect.getMetadata(METADATA_KEYS.PARAMS, target, propertyKey || '') || [];
|
|
81
|
-
existingParams.push({
|
|
82
|
+
existingParams.push({
|
|
83
|
+
index: parameterIndex,
|
|
84
|
+
type,
|
|
85
|
+
data,
|
|
86
|
+
pipes: pipes.length > 0 ? pipes : undefined
|
|
87
|
+
});
|
|
82
88
|
Reflect.defineMetadata(METADATA_KEYS.PARAMS, existingParams, target, propertyKey || '');
|
|
83
89
|
};
|
|
84
90
|
};
|
|
@@ -232,13 +238,12 @@ function extendArrayMetadata(key, metadata, target) {
|
|
|
232
238
|
*/
|
|
233
239
|
function UseGuards(...guards) {
|
|
234
240
|
return (target, key, descriptor) => {
|
|
235
|
-
guards.filter(guard => guard && (typeof guard === 'function' || typeof guard === 'object'));
|
|
241
|
+
const validGuards = guards.filter(guard => guard && (typeof guard === 'function' || typeof guard === 'object'));
|
|
236
242
|
if (descriptor) {
|
|
237
|
-
extendArrayMetadata(METADATA_KEYS.GUARDS,
|
|
243
|
+
extendArrayMetadata(METADATA_KEYS.GUARDS, validGuards, descriptor.value);
|
|
238
244
|
return descriptor;
|
|
239
245
|
}
|
|
240
|
-
extendArrayMetadata(METADATA_KEYS.GUARDS,
|
|
241
|
-
return target;
|
|
246
|
+
extendArrayMetadata(METADATA_KEYS.GUARDS, validGuards, target);
|
|
242
247
|
};
|
|
243
248
|
}
|
|
244
249
|
|
|
@@ -254,7 +259,21 @@ function UseInterceptors(...interceptors) {
|
|
|
254
259
|
return descriptor;
|
|
255
260
|
}
|
|
256
261
|
extendArrayMetadata(METADATA_KEYS.INTERCEPTORS, validInterceptors, target);
|
|
257
|
-
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Decorador que vincula Pipes al controlador o método.
|
|
267
|
+
* @param pipes Una lista de clases Pipe o instancias de Pipe.
|
|
268
|
+
*/
|
|
269
|
+
function UsePipes(...pipes) {
|
|
270
|
+
return (target, key, descriptor) => {
|
|
271
|
+
const validPipes = pipes.filter(pipe => pipe && (typeof pipe === 'function' || typeof pipe === 'object'));
|
|
272
|
+
if (descriptor) {
|
|
273
|
+
extendArrayMetadata(METADATA_KEYS.PIPES, validPipes, descriptor.value);
|
|
274
|
+
return descriptor;
|
|
275
|
+
}
|
|
276
|
+
extendArrayMetadata(METADATA_KEYS.PIPES, validPipes, target);
|
|
258
277
|
};
|
|
259
278
|
}
|
|
260
279
|
|
|
@@ -326,6 +345,12 @@ const Controller = (basePath = '/') => {
|
|
|
326
345
|
};
|
|
327
346
|
};
|
|
328
347
|
|
|
348
|
+
const ManualRegister = () => {
|
|
349
|
+
return (target) => {
|
|
350
|
+
Reflect.defineMetadata(METADATA_KEYS.MANUAL_REGISTER, true, target);
|
|
351
|
+
};
|
|
352
|
+
};
|
|
353
|
+
|
|
329
354
|
/**
|
|
330
355
|
* Decorador para extraer datos personalizados del request.
|
|
331
356
|
*
|
|
@@ -380,25 +405,6 @@ function Transform(transformer) {
|
|
|
380
405
|
};
|
|
381
406
|
}
|
|
382
407
|
|
|
383
|
-
class ApiResponse {
|
|
384
|
-
static success(data, message = 'OK', status = 200) {
|
|
385
|
-
return {
|
|
386
|
-
success: true,
|
|
387
|
-
message,
|
|
388
|
-
data,
|
|
389
|
-
status,
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
static error(message, error = null, status = 500) {
|
|
393
|
-
return {
|
|
394
|
-
success: false,
|
|
395
|
-
message,
|
|
396
|
-
error,
|
|
397
|
-
status,
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
408
|
class HttpError extends Error {
|
|
403
409
|
constructor(message, statusCode, error) {
|
|
404
410
|
super(message);
|
|
@@ -695,6 +701,245 @@ class InfraDataDeserializationError extends InfrastructureError {
|
|
|
695
701
|
}
|
|
696
702
|
}
|
|
697
703
|
|
|
704
|
+
/**
|
|
705
|
+
* Pipe de validación genérico que valida objetos contra un esquema de validación.
|
|
706
|
+
* Este es un ejemplo básico - en producción se usaría con librerías como class-validator o zod.
|
|
707
|
+
*/
|
|
708
|
+
class ValidationPipe {
|
|
709
|
+
async transform(value, metadata) {
|
|
710
|
+
if (value === undefined || value === null) {
|
|
711
|
+
throw new HttpError('Validation failed: value is required', 400);
|
|
712
|
+
}
|
|
713
|
+
// Aquí iría la lógica de validación real (ej: con class-validator)
|
|
714
|
+
// Por ahora solo verifica que el valor exista
|
|
715
|
+
return value;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Pipe que convierte un valor a número entero.
|
|
721
|
+
* Lanza una excepción si el valor no puede ser convertido.
|
|
722
|
+
*/
|
|
723
|
+
class ParseIntPipe {
|
|
724
|
+
transform(value, metadata) {
|
|
725
|
+
const val = parseInt(value, 10);
|
|
726
|
+
if (isNaN(val)) {
|
|
727
|
+
throw new HttpError(`Validation failed: "${value}" is not an integer`, 400);
|
|
728
|
+
}
|
|
729
|
+
return val;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Pipe que convierte un valor a número de punto flotante.
|
|
735
|
+
* Lanza una excepción si el valor no puede ser convertido.
|
|
736
|
+
*/
|
|
737
|
+
class ParseFloatPipe {
|
|
738
|
+
transform(value, metadata) {
|
|
739
|
+
const val = parseFloat(value);
|
|
740
|
+
if (isNaN(val)) {
|
|
741
|
+
throw new HttpError(`Validation failed: "${value}" is not a valid number`, 400);
|
|
742
|
+
}
|
|
743
|
+
return val;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Pipe que convierte un valor a booleano.
|
|
749
|
+
* Acepta: true, false, 1, 0, 'true', 'false', '1', '0'
|
|
750
|
+
*/
|
|
751
|
+
class ParseBoolPipe {
|
|
752
|
+
transform(value, metadata) {
|
|
753
|
+
if (typeof value === 'boolean') {
|
|
754
|
+
return value;
|
|
755
|
+
}
|
|
756
|
+
const normalizedValue = String(value).toLowerCase();
|
|
757
|
+
if (normalizedValue === 'true' || normalizedValue === '1') {
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
if (normalizedValue === 'false' || normalizedValue === '0') {
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
throw new HttpError(`Validation failed: "${value}" is not a boolean value`, 400);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Pipe que convierte un valor a array.
|
|
769
|
+
* Puede parsear strings separados por comas o validar que el valor ya sea un array.
|
|
770
|
+
*/
|
|
771
|
+
class ParseArrayPipe {
|
|
772
|
+
constructor(options) {
|
|
773
|
+
this.options = options;
|
|
774
|
+
}
|
|
775
|
+
async transform(value, metadata) {
|
|
776
|
+
if (!value) {
|
|
777
|
+
if (this.options?.optional) {
|
|
778
|
+
return [];
|
|
779
|
+
}
|
|
780
|
+
throw new HttpError('Validation failed: array is required', 400);
|
|
781
|
+
}
|
|
782
|
+
let array;
|
|
783
|
+
if (Array.isArray(value)) {
|
|
784
|
+
array = value;
|
|
785
|
+
}
|
|
786
|
+
else if (typeof value === 'string') {
|
|
787
|
+
const separator = this.options?.separator || ',';
|
|
788
|
+
array = value.split(separator).map(item => item.trim());
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
throw new HttpError(`Validation failed: "${value}" is not an array`, 400);
|
|
792
|
+
}
|
|
793
|
+
// Si hay un pipe para los items, aplicarlo a cada elemento
|
|
794
|
+
if (this.options?.items) {
|
|
795
|
+
const transformed = [];
|
|
796
|
+
for (const item of array) {
|
|
797
|
+
transformed.push(await this.options.items.transform(item, metadata));
|
|
798
|
+
}
|
|
799
|
+
return transformed;
|
|
800
|
+
}
|
|
801
|
+
return array;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Pipe que valida que un valor sea un UUID válido.
|
|
807
|
+
* Soporta versiones 3, 4 y 5 de UUID.
|
|
808
|
+
*/
|
|
809
|
+
class ParseUUIDPipe {
|
|
810
|
+
constructor(version) {
|
|
811
|
+
this.version = version;
|
|
812
|
+
this.uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
813
|
+
}
|
|
814
|
+
transform(value, metadata) {
|
|
815
|
+
if (!value || typeof value !== 'string') {
|
|
816
|
+
throw new HttpError('Validation failed: UUID is required', 400);
|
|
817
|
+
}
|
|
818
|
+
if (!this.uuidRegex.test(value)) {
|
|
819
|
+
throw new HttpError(`Validation failed: "${value}" is not a valid UUID`, 400);
|
|
820
|
+
}
|
|
821
|
+
if (this.version) {
|
|
822
|
+
const uuidVersion = value.charAt(14);
|
|
823
|
+
if (uuidVersion !== this.version) {
|
|
824
|
+
throw new HttpError(`Validation failed: expected UUID v${this.version}, got v${uuidVersion}`, 400);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return value;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Pipe que valida que un valor pertenezca a un enum específico.
|
|
833
|
+
*/
|
|
834
|
+
class ParseEnumPipe {
|
|
835
|
+
constructor(enumType) {
|
|
836
|
+
this.enumType = enumType;
|
|
837
|
+
}
|
|
838
|
+
transform(value, metadata) {
|
|
839
|
+
const enumValues = Object.values(this.enumType);
|
|
840
|
+
if (!enumValues.includes(value)) {
|
|
841
|
+
const validValues = enumValues.join(', ');
|
|
842
|
+
throw new HttpError(`Validation failed: "${value}" is not a valid enum value. Valid values: ${validValues}`, 400);
|
|
843
|
+
}
|
|
844
|
+
return value;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Pipe que proporciona un valor por defecto si el valor de entrada es undefined o null.
|
|
850
|
+
*/
|
|
851
|
+
class DefaultValuePipe {
|
|
852
|
+
constructor(defaultValue) {
|
|
853
|
+
this.defaultValue = defaultValue;
|
|
854
|
+
}
|
|
855
|
+
transform(value, metadata) {
|
|
856
|
+
if (value === undefined || value === null || value === '') {
|
|
857
|
+
return this.defaultValue;
|
|
858
|
+
}
|
|
859
|
+
return value;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Pipe que valida archivos subidos.
|
|
865
|
+
* Puede validar tipo MIME, tamaño máximo y campos requeridos.
|
|
866
|
+
*/
|
|
867
|
+
class ParseFilePipe {
|
|
868
|
+
constructor(options) {
|
|
869
|
+
this.options = options;
|
|
870
|
+
}
|
|
871
|
+
transform(value, metadata) {
|
|
872
|
+
if (!value) {
|
|
873
|
+
throw new HttpError('Validation failed: file is required', 400);
|
|
874
|
+
}
|
|
875
|
+
// Validar tamaño
|
|
876
|
+
if (this.options?.maxSize && value.size > this.options.maxSize) {
|
|
877
|
+
const maxSizeMB = (this.options.maxSize / (1024 * 1024)).toFixed(2);
|
|
878
|
+
throw new HttpError(`Validation failed: file size exceeds maximum of ${maxSizeMB}MB`, 400);
|
|
879
|
+
}
|
|
880
|
+
// Validar tipo MIME
|
|
881
|
+
if (this.options?.allowedMimeTypes && value.mimetype) {
|
|
882
|
+
if (!this.options.allowedMimeTypes.includes(value.mimetype)) {
|
|
883
|
+
throw new HttpError(`Validation failed: file type "${value.mimetype}" is not allowed. Allowed types: ${this.options.allowedMimeTypes.join(', ')}`, 400);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
// Validar campos requeridos
|
|
887
|
+
if (this.options?.requiredFields) {
|
|
888
|
+
for (const field of this.options.requiredFields) {
|
|
889
|
+
if (!value[field]) {
|
|
890
|
+
throw new HttpError(`Validation failed: file is missing required field "${field}"`, 400);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return value;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Pipe que convierte un valor a Date.
|
|
900
|
+
* Acepta timestamps, strings de fecha ISO, o cualquier formato parseable por Date.
|
|
901
|
+
*/
|
|
902
|
+
class ParseDatePipe {
|
|
903
|
+
transform(value, metadata) {
|
|
904
|
+
if (!value) {
|
|
905
|
+
throw new HttpError('Validation failed: date is required', 400);
|
|
906
|
+
}
|
|
907
|
+
let date;
|
|
908
|
+
if (typeof value === 'number') {
|
|
909
|
+
date = new Date(value);
|
|
910
|
+
}
|
|
911
|
+
else if (typeof value === 'string') {
|
|
912
|
+
date = new Date(value);
|
|
913
|
+
}
|
|
914
|
+
else {
|
|
915
|
+
throw new HttpError(`Validation failed: "${value}" cannot be converted to Date`, 400);
|
|
916
|
+
}
|
|
917
|
+
if (isNaN(date.getTime())) {
|
|
918
|
+
throw new HttpError(`Validation failed: "${value}" is not a valid date`, 400);
|
|
919
|
+
}
|
|
920
|
+
return date;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
class ApiResponse {
|
|
925
|
+
static success(data, message = 'OK', status = 200) {
|
|
926
|
+
return {
|
|
927
|
+
success: true,
|
|
928
|
+
message,
|
|
929
|
+
data,
|
|
930
|
+
status,
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
static error(message, error = null, status = 500) {
|
|
934
|
+
return {
|
|
935
|
+
success: false,
|
|
936
|
+
message,
|
|
937
|
+
error,
|
|
938
|
+
status,
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
698
943
|
exports.ApiResponse = ApiResponse;
|
|
699
944
|
exports.BackupError = BackupError;
|
|
700
945
|
exports.BadRequestError = BadRequestError;
|
|
@@ -713,6 +958,7 @@ exports.DataIntegrityError = DataIntegrityError;
|
|
|
713
958
|
exports.DataMigrationError = DataMigrationError;
|
|
714
959
|
exports.DataNotFoundError = DataNotFoundError;
|
|
715
960
|
exports.DataValidationError = DataValidationError;
|
|
961
|
+
exports.DefaultValuePipe = DefaultValuePipe;
|
|
716
962
|
exports.Delete = Delete;
|
|
717
963
|
exports.DependencyError = DependencyError;
|
|
718
964
|
exports.DomainError = DomainError;
|
|
@@ -747,12 +993,21 @@ exports.InternalServerError = InternalServerError;
|
|
|
747
993
|
exports.Ip = Ip;
|
|
748
994
|
exports.Locale = Locale;
|
|
749
995
|
exports.METADATA_KEYS = METADATA_KEYS;
|
|
996
|
+
exports.ManualRegister = ManualRegister;
|
|
750
997
|
exports.Module = Module;
|
|
751
998
|
exports.Next = Next;
|
|
752
999
|
exports.NotFoundError = NotFoundError;
|
|
753
1000
|
exports.OperationNotAllowedError = OperationNotAllowedError;
|
|
754
1001
|
exports.Options = Options;
|
|
755
1002
|
exports.Param = Param;
|
|
1003
|
+
exports.ParseArrayPipe = ParseArrayPipe;
|
|
1004
|
+
exports.ParseBoolPipe = ParseBoolPipe;
|
|
1005
|
+
exports.ParseDatePipe = ParseDatePipe;
|
|
1006
|
+
exports.ParseEnumPipe = ParseEnumPipe;
|
|
1007
|
+
exports.ParseFilePipe = ParseFilePipe;
|
|
1008
|
+
exports.ParseFloatPipe = ParseFloatPipe;
|
|
1009
|
+
exports.ParseIntPipe = ParseIntPipe;
|
|
1010
|
+
exports.ParseUUIDPipe = ParseUUIDPipe;
|
|
756
1011
|
exports.Patch = Patch;
|
|
757
1012
|
exports.PersistenceError = PersistenceError;
|
|
758
1013
|
exports.Post = Post;
|
|
@@ -785,6 +1040,8 @@ exports.UnauthorizedError = UnauthorizedError;
|
|
|
785
1040
|
exports.UnprocessableEntityError = UnprocessableEntityError;
|
|
786
1041
|
exports.UseGuards = UseGuards;
|
|
787
1042
|
exports.UseInterceptors = UseInterceptors;
|
|
1043
|
+
exports.UsePipes = UsePipes;
|
|
788
1044
|
exports.Validate = Validate;
|
|
789
1045
|
exports.ValidationError = ValidationError;
|
|
1046
|
+
exports.ValidationPipe = ValidationPipe;
|
|
790
1047
|
exports.isRedirectResponse = isRedirectResponse;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export * from "./use-guards.decorator";
|
|
2
2
|
export * from "./use-interceptors.decorator";
|
|
3
|
+
export * from "./use-pipes.decorator";
|
|
3
4
|
export * from "./set-metadata.decorator";
|
|
4
5
|
export * from "./serialize.decorator";
|
|
5
6
|
export * from "./module.decorator";
|
|
6
7
|
export * from "./service.decorator";
|
|
7
8
|
export * from "./controller.decorator";
|
|
9
|
+
export * from "./manual-controller.decorator";
|
|
@@ -4,4 +4,4 @@ import { CanActivate, Type } from '../../interfaces';
|
|
|
4
4
|
* Decorador que vincula Guards al controlador o método.
|
|
5
5
|
* @param guards Una lista de clases Guard o instancias de Guard.
|
|
6
6
|
*/
|
|
7
|
-
export declare function UseGuards(...guards: (Type<CanActivate> | CanActivate | Function)[]): (target: object | Function, key?: string | symbol, descriptor?: PropertyDescriptor) =>
|
|
7
|
+
export declare function UseGuards(...guards: (Type<CanActivate> | CanActivate | Function)[]): (target: object | Function, key?: string | symbol, descriptor?: PropertyDescriptor) => PropertyDescriptor | undefined;
|
|
@@ -4,4 +4,4 @@ import { HemiaInterceptor, Type } from '../../interfaces';
|
|
|
4
4
|
* Decorador que vincula Interceptores al controlador o método.
|
|
5
5
|
* @param interceptors Una lista de clases Interceptor o instancias.
|
|
6
6
|
*/
|
|
7
|
-
export declare function UseInterceptors(...interceptors: (Type<HemiaInterceptor> | HemiaInterceptor | Function)[]): (target: object | Function, key?: string | symbol, descriptor?: PropertyDescriptor) =>
|
|
7
|
+
export declare function UseInterceptors(...interceptors: (Type<HemiaInterceptor> | HemiaInterceptor | Function)[]): (target: object | Function, key?: string | symbol, descriptor?: PropertyDescriptor) => PropertyDescriptor | undefined;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { PipeTransform, Type } from '../../interfaces';
|
|
3
|
+
/**
|
|
4
|
+
* Decorador que vincula Pipes al controlador o método.
|
|
5
|
+
* @param pipes Una lista de clases Pipe o instancias de Pipe.
|
|
6
|
+
*/
|
|
7
|
+
export declare function UsePipes(...pipes: (Type<PipeTransform> | PipeTransform | Function)[]): (target: object | Function, key?: string | symbol, descriptor?: PropertyDescriptor) => PropertyDescriptor | undefined;
|
|
@@ -1,121 +1,122 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
+
import { PipeTransform, PType } from '../../interfaces';
|
|
2
3
|
/**
|
|
3
4
|
* Inyecta el objeto Request completo.
|
|
4
5
|
* @example
|
|
5
6
|
* async handler(@Request() req) {}
|
|
6
7
|
*/
|
|
7
|
-
export declare const Request: (data?: string) => ParameterDecorator;
|
|
8
|
+
export declare const Request: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
8
9
|
/**
|
|
9
10
|
* Alias para Request.
|
|
10
11
|
* @example
|
|
11
12
|
* async handler(@Req() req) {}
|
|
12
13
|
*/
|
|
13
|
-
export declare const Req: (data?: string) => ParameterDecorator;
|
|
14
|
+
export declare const Req: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
14
15
|
/**
|
|
15
16
|
* Inyecta el objeto Response.
|
|
16
17
|
* @example
|
|
17
18
|
* async handler(@Response() res) {}
|
|
18
19
|
*/
|
|
19
|
-
export declare const Response: (data?: string) => ParameterDecorator;
|
|
20
|
+
export declare const Response: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
20
21
|
/**
|
|
21
22
|
* Alias para Response.
|
|
22
23
|
* @example
|
|
23
24
|
* async handler(@Res() res) {}
|
|
24
25
|
*/
|
|
25
|
-
export declare const Res: (data?: string) => ParameterDecorator;
|
|
26
|
+
export declare const Res: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
26
27
|
/**
|
|
27
28
|
* Inyecta el objeto Next para middlewares.
|
|
28
29
|
* @example
|
|
29
30
|
* async handler(@Next() next) {}
|
|
30
31
|
*/
|
|
31
|
-
export declare const Next: (data?: string) => ParameterDecorator;
|
|
32
|
+
export declare const Next: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
32
33
|
/**
|
|
33
34
|
* Inyecta la sesión del usuario.
|
|
34
35
|
* @example
|
|
35
36
|
* async handler(@Session() session) {}
|
|
36
37
|
*/
|
|
37
|
-
export declare const Session: (data?: string) => ParameterDecorator;
|
|
38
|
+
export declare const Session: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
38
39
|
/**
|
|
39
40
|
* Inyecta la IP del cliente.
|
|
40
41
|
* @example
|
|
41
42
|
* async handler(@Ip() ip) {}
|
|
42
43
|
*/
|
|
43
|
-
export declare const Ip: (data?: string) => ParameterDecorator;
|
|
44
|
+
export declare const Ip: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
44
45
|
/**
|
|
45
46
|
* Inyecta el host de la solicitud.
|
|
46
47
|
* @example
|
|
47
48
|
* async handler(@Host() host) {}
|
|
48
49
|
*/
|
|
49
|
-
export declare const Host: (data?: string) => ParameterDecorator;
|
|
50
|
+
export declare const Host: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
50
51
|
/**
|
|
51
52
|
* Inyecta el cuerpo de la solicitud.
|
|
52
53
|
* @example
|
|
53
54
|
* async handler(@Body() body) {}
|
|
54
55
|
*/
|
|
55
|
-
export declare const Body: (data?: string) => ParameterDecorator;
|
|
56
|
+
export declare const Body: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
56
57
|
/**
|
|
57
58
|
* Inyecta los parámetros de consulta (query).
|
|
58
59
|
* @example
|
|
59
60
|
* async handler(@Query('search') search) {}
|
|
60
61
|
*/
|
|
61
|
-
export declare const Query: (data?: string) => ParameterDecorator;
|
|
62
|
+
export declare const Query: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
62
63
|
/**
|
|
63
64
|
* Inyecta los parámetros de la URL.
|
|
64
65
|
* @example
|
|
65
66
|
* async handler(@Param('id') id) {}
|
|
66
67
|
*/
|
|
67
|
-
export declare const Param: (data?: string) => ParameterDecorator;
|
|
68
|
+
export declare const Param: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
68
69
|
/**
|
|
69
70
|
* Inyecta los encabezados de la solicitud.
|
|
70
71
|
* @example
|
|
71
72
|
* async handler(@Headers('authorization') auth) {}
|
|
72
73
|
*/
|
|
73
|
-
export declare const Headers: (data?: string) => ParameterDecorator;
|
|
74
|
+
export declare const Headers: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
74
75
|
/**
|
|
75
76
|
* Inyecta los cookies de la solicitud.
|
|
76
77
|
* @example
|
|
77
78
|
* async handler(@Cookies('token') token) {}
|
|
78
79
|
*/
|
|
79
|
-
export declare const Cookies: (data?: string) => ParameterDecorator;
|
|
80
|
+
export declare const Cookies: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
80
81
|
/**
|
|
81
82
|
* Inyecta todos los archivos enviados en la solicitud.
|
|
82
83
|
* @example
|
|
83
84
|
* async handler(@Files() files) {}
|
|
84
85
|
*/
|
|
85
|
-
export declare const Files: (data?: string) => ParameterDecorator;
|
|
86
|
+
export declare const Files: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
86
87
|
/**
|
|
87
88
|
* Inyecta un archivo específico de la solicitud.
|
|
88
89
|
* @example
|
|
89
90
|
* async handler(@File('avatar') avatar) {}
|
|
90
91
|
*/
|
|
91
|
-
export declare const File: (data?: string) => ParameterDecorator;
|
|
92
|
+
export declare const File: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
92
93
|
/**
|
|
93
94
|
* Inyecta el locale del usuario.
|
|
94
95
|
* @example
|
|
95
96
|
* async handler(@Locale() locale) {}
|
|
96
97
|
*/
|
|
97
|
-
export declare const Locale: (data?: string) => ParameterDecorator;
|
|
98
|
+
export declare const Locale: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
98
99
|
/**
|
|
99
100
|
* Inyecta datos de autenticación.
|
|
100
101
|
* @example
|
|
101
102
|
* async handler(@ReqAuth() auth) {}
|
|
102
103
|
*/
|
|
103
|
-
export declare const ReqAuth: (data?: string) => ParameterDecorator;
|
|
104
|
+
export declare const ReqAuth: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
104
105
|
/**
|
|
105
106
|
* Inyecta el usuario autenticado.
|
|
106
107
|
* @example
|
|
107
108
|
* async handler(@ReqUser() user) {}
|
|
108
109
|
*/
|
|
109
|
-
export declare const ReqUser: (data?: string) => ParameterDecorator;
|
|
110
|
+
export declare const ReqUser: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
110
111
|
/**
|
|
111
112
|
* Inyecta los permisos del usuario.
|
|
112
113
|
* @example
|
|
113
114
|
* async handler(@ReqPermissions() permissions) {}
|
|
114
115
|
*/
|
|
115
|
-
export declare const ReqPermissions: (data?: string) => ParameterDecorator;
|
|
116
|
+
export declare const ReqPermissions: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
116
117
|
/**
|
|
117
118
|
* Inyecta el contexto de la solicitud.
|
|
118
119
|
* @example
|
|
119
120
|
* async handler(@ReqContext() context) {}
|
|
120
121
|
*/
|
|
121
|
-
export declare const ReqContext: (data?: string) => ParameterDecorator;
|
|
122
|
+
export declare const ReqContext: (data?: string, ...pipes: (PType<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
|
|
@@ -7,6 +7,7 @@ export declare const METADATA_KEYS: {
|
|
|
7
7
|
readonly COOKIES: "cookies";
|
|
8
8
|
readonly GUARDS: "hemia:guards";
|
|
9
9
|
readonly INTERCEPTORS: "hemia:interceptors";
|
|
10
|
+
readonly PIPES: "hemia:pipes";
|
|
10
11
|
readonly CUSTOM: "hemia:custom";
|
|
11
12
|
readonly SERIALIZE: "hemia:serialize";
|
|
12
13
|
readonly ROLES: "hemia:roles";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PipeTransform, ArgumentMetadata } from '../../interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* Pipe que proporciona un valor por defecto si el valor de entrada es undefined o null.
|
|
4
|
+
*/
|
|
5
|
+
export declare class DefaultValuePipe<T = any> implements PipeTransform<T, T> {
|
|
6
|
+
private readonly defaultValue;
|
|
7
|
+
constructor(defaultValue: T);
|
|
8
|
+
transform(value: T, metadata?: ArgumentMetadata): T;
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './validation.pipe';
|
|
2
|
+
export * from './parse-int.pipe';
|
|
3
|
+
export * from './parse-float.pipe';
|
|
4
|
+
export * from './parse-bool.pipe';
|
|
5
|
+
export * from './parse-array.pipe';
|
|
6
|
+
export * from './parse-uuid.pipe';
|
|
7
|
+
export * from './parse-enum.pipe';
|
|
8
|
+
export * from './default-value.pipe';
|
|
9
|
+
export * from './parse-file.pipe';
|
|
10
|
+
export * from './parse-date.pipe';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PipeTransform, ArgumentMetadata } from '../../interfaces';
|
|
2
|
+
export interface ParseArrayOptions {
|
|
3
|
+
items?: PipeTransform;
|
|
4
|
+
separator?: string;
|
|
5
|
+
optional?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Pipe que convierte un valor a array.
|
|
9
|
+
* Puede parsear strings separados por comas o validar que el valor ya sea un array.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ParseArrayPipe implements PipeTransform {
|
|
12
|
+
private readonly options?;
|
|
13
|
+
constructor(options?: ParseArrayOptions | undefined);
|
|
14
|
+
transform(value: any, metadata?: ArgumentMetadata): Promise<any[]>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PipeTransform, ArgumentMetadata } from '../../interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* Pipe que convierte un valor a booleano.
|
|
4
|
+
* Acepta: true, false, 1, 0, 'true', 'false', '1', '0'
|
|
5
|
+
*/
|
|
6
|
+
export declare class ParseBoolPipe implements PipeTransform<string | boolean, boolean> {
|
|
7
|
+
transform(value: string | boolean, metadata?: ArgumentMetadata): boolean;
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PipeTransform, ArgumentMetadata } from '../../interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* Pipe que convierte un valor a Date.
|
|
4
|
+
* Acepta timestamps, strings de fecha ISO, o cualquier formato parseable por Date.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ParseDatePipe implements PipeTransform<string | number, Date> {
|
|
7
|
+
transform(value: string | number, metadata?: ArgumentMetadata): Date;
|
|
8
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PipeTransform, ArgumentMetadata } from '../../interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* Pipe que valida que un valor pertenezca a un enum específico.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ParseEnumPipe<T extends Record<string, any>> implements PipeTransform<string, T[keyof T]> {
|
|
6
|
+
private readonly enumType;
|
|
7
|
+
constructor(enumType: T);
|
|
8
|
+
transform(value: string, metadata?: ArgumentMetadata): T[keyof T];
|
|
9
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PipeTransform, ArgumentMetadata } from '../../interfaces';
|
|
2
|
+
export interface ParseFileOptions {
|
|
3
|
+
maxSize?: number;
|
|
4
|
+
allowedMimeTypes?: string[];
|
|
5
|
+
requiredFields?: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Pipe que valida archivos subidos.
|
|
9
|
+
* Puede validar tipo MIME, tamaño máximo y campos requeridos.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ParseFilePipe implements PipeTransform {
|
|
12
|
+
private readonly options?;
|
|
13
|
+
constructor(options?: ParseFileOptions | undefined);
|
|
14
|
+
transform(value: any, metadata?: ArgumentMetadata): any;
|
|
15
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PipeTransform, ArgumentMetadata } from '../../interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* Pipe que convierte un valor a número de punto flotante.
|
|
4
|
+
* Lanza una excepción si el valor no puede ser convertido.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ParseFloatPipe implements PipeTransform<string, number> {
|
|
7
|
+
transform(value: string, metadata?: ArgumentMetadata): number;
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PipeTransform, ArgumentMetadata } from '../../interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* Pipe que convierte un valor a número entero.
|
|
4
|
+
* Lanza una excepción si el valor no puede ser convertido.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ParseIntPipe implements PipeTransform<string, number> {
|
|
7
|
+
transform(value: string, metadata?: ArgumentMetadata): number;
|
|
8
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PipeTransform, ArgumentMetadata } from '../../interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* Pipe que valida que un valor sea un UUID válido.
|
|
4
|
+
* Soporta versiones 3, 4 y 5 de UUID.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ParseUUIDPipe implements PipeTransform<string, string> {
|
|
7
|
+
private readonly version?;
|
|
8
|
+
private readonly uuidRegex;
|
|
9
|
+
constructor(version?: "3" | "4" | "5" | undefined);
|
|
10
|
+
transform(value: string, metadata?: ArgumentMetadata): string;
|
|
11
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PipeTransform, ArgumentMetadata } from '../../interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* Pipe de validación genérico que valida objetos contra un esquema de validación.
|
|
4
|
+
* Este es un ejemplo básico - en producción se usaría con librerías como class-validator o zod.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ValidationPipe implements PipeTransform {
|
|
7
|
+
transform(value: any, metadata?: ArgumentMetadata): Promise<any>;
|
|
8
|
+
}
|
|
@@ -7,6 +7,7 @@ export * from "./features/arguments-host.interface";
|
|
|
7
7
|
export * from './type.interface';
|
|
8
8
|
export * from './handler.interface';
|
|
9
9
|
export * from './interceptor.interface';
|
|
10
|
+
export * from './pipe.interface';
|
|
10
11
|
export * from './configuration/redis-configuration.interface';
|
|
11
12
|
export * from './configuration/mongo-db-configuration.interface';
|
|
12
13
|
export * from './configuration/aws-s3-configuration.interface';
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { ParamType } from "../enums/param-type.enum";
|
|
2
|
+
import { PipeTransform, PType } from "./pipe.interface";
|
|
2
3
|
export interface ParamMetadata {
|
|
3
4
|
index: number;
|
|
4
5
|
type: ParamType;
|
|
5
6
|
data?: string;
|
|
7
|
+
pipes?: (PType<PipeTransform> | PipeTransform)[];
|
|
6
8
|
}
|
|
7
9
|
export interface HeaderMetadata {
|
|
8
10
|
name: string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface defining the contract for pipes.
|
|
3
|
+
* Pipes have two typical use cases:
|
|
4
|
+
* - transformation: transform input data to the desired form
|
|
5
|
+
* - validation: evaluate input data and if valid, pass it through unchanged; otherwise, throw an exception
|
|
6
|
+
*/
|
|
7
|
+
export interface PipeTransform<T = any, R = any> {
|
|
8
|
+
/**
|
|
9
|
+
* Method to implement a custom pipe. Called with two parameters
|
|
10
|
+
* @param value the currently processed input argument
|
|
11
|
+
* @param metadata optional metadata about the current argument
|
|
12
|
+
*/
|
|
13
|
+
transform(value: T, metadata?: ArgumentMetadata): R | Promise<R>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Metadata about the argument being processed by the pipe
|
|
17
|
+
*/
|
|
18
|
+
export interface ArgumentMetadata {
|
|
19
|
+
/**
|
|
20
|
+
* Indicates whether argument is a body, query, param, or custom parameter
|
|
21
|
+
*/
|
|
22
|
+
type?: 'body' | 'query' | 'param' | 'custom';
|
|
23
|
+
/**
|
|
24
|
+
* Metatype of the argument (e.g., String, Number)
|
|
25
|
+
*/
|
|
26
|
+
metatype?: PType<any>;
|
|
27
|
+
/**
|
|
28
|
+
* String value passed to the decorator (e.g., for @Param('id'), data would be 'id')
|
|
29
|
+
*/
|
|
30
|
+
data?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface PType<T = any> {
|
|
33
|
+
new (...args: any[]): T;
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hemia/common",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
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",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"build": "npm run clean && npm run tscBuild",
|
|
12
12
|
"test": "jest --detectOpenHandles",
|
|
13
13
|
"test:coverage": "jest --coverage",
|
|
14
|
-
"test:watch": "jest --watch"
|
|
14
|
+
"test:watch": "jest --watch",
|
|
15
|
+
"prepublish": "npm run build"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
18
|
"@rollup/plugin-commonjs": "^26.0.1",
|