Skip to content

Commit 8158a7a

Browse files
committed
refactor(decorators): enhance decorators to support both class and method usage
1 parent 242318a commit 8158a7a

1 file changed

Lines changed: 70 additions & 24 deletions

File tree

src/utils/decorators.utils.ts

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ export function HttpCode(status: number): MethodDecorator {
458458
* return { data: '...' };
459459
* }
460460
*/
461-
export function Header(name: string, value: string): MethodDecorator {
461+
export function Header(name: string, value: string): MethodDecorator & ClassDecorator {
462462
return Headers(name, value);
463463
}
464464

@@ -491,11 +491,19 @@ export function Header(name: string, value: string): MethodDecorator {
491491
*
492492
* ```
493493
*/
494-
export function Headers(headers: Record<string, string> | string, value?: string): MethodDecorator {
495-
return (target, propertyKey) => {
496-
const existing: Record<string, string> = Reflect.getMetadata(HEADER_KEY, target, propertyKey) || {};
497-
const newHeaders = typeof headers === 'string' ? { [headers]: value! } : headers;
498-
Reflect.defineMetadata(HEADER_KEY, { ...existing, ...newHeaders }, target, propertyKey);
494+
export function Headers(headers: Record<string, string> | string, value?: string): MethodDecorator & ClassDecorator {
495+
return (target: any, propertyKey?: string | symbol) => {
496+
if (typeof propertyKey === 'undefined') {
497+
// Class decorator
498+
const existing: Record<string, string> = Reflect.getMetadata(HEADER_KEY, target) || {};
499+
const newHeaders = typeof headers === 'string' ? { [headers]: value! } : headers;
500+
Reflect.defineMetadata(HEADER_KEY, { ...existing, ...newHeaders }, target);
501+
} else {
502+
// Method decorator
503+
const existing: Record<string, string> = Reflect.getMetadata(HEADER_KEY, target, propertyKey) || {};
504+
const newHeaders = typeof headers === 'string' ? { [headers]: value! } : headers;
505+
Reflect.defineMetadata(HEADER_KEY, { ...existing, ...newHeaders }, target, propertyKey);
506+
}
499507
};
500508
}
501509

@@ -552,6 +560,8 @@ export function After(fn: Function): MethodDecorator {
552560
* Decorator that requires specific roles for accessing a route.
553561
* Must be used with authentication middleware.
554562
*
563+
* Check req.user.roles for user roles[].
564+
*
555565
* @param roles - List of roles that can access this route
556566
* @returns Method decorator
557567
*
@@ -565,9 +575,15 @@ export function After(fn: Function): MethodDecorator {
565575
* }
566576
* ```
567577
*/
568-
export function Roles(...roles: string[]): MethodDecorator {
569-
return (target, propertyKey, _descriptor) => {
570-
Reflect.defineMetadata(ROLES_KEY, roles, target, propertyKey as string);
578+
export function Roles(...roles: string[]): MethodDecorator & ClassDecorator {
579+
return (target: any, propertyKey?: string | symbol, _descriptor?: PropertyDescriptor) => {
580+
if (typeof propertyKey === 'undefined') {
581+
// Class decorator
582+
Reflect.defineMetadata(ROLES_KEY, roles, target);
583+
} else {
584+
// Method decorator
585+
Reflect.defineMetadata(ROLES_KEY, roles, target, propertyKey as string);
586+
}
571587
};
572588
}
573589

@@ -617,9 +633,15 @@ export function Redirect(url?: string, statusCode: number = 302): MethodDecorato
617633
* }
618634
* ```
619635
*/
620-
export function Cache(ttlSeconds: number): MethodDecorator {
621-
return (target, propertyKey, _descriptor) => {
622-
Reflect.defineMetadata(CACHE_KEY, { ttlSeconds }, target, propertyKey as string);
636+
export function Cache(ttlSeconds: number): MethodDecorator & ClassDecorator {
637+
return (target: any, propertyKey?: string | symbol, _descriptor?: PropertyDescriptor) => {
638+
if (typeof propertyKey === 'undefined') {
639+
// Class decorator
640+
Reflect.defineMetadata(CACHE_KEY, { ttlSeconds }, target);
641+
} else {
642+
// Method decorator
643+
Reflect.defineMetadata(CACHE_KEY, { ttlSeconds }, target, propertyKey as string);
644+
}
623645
};
624646
}
625647

@@ -649,14 +671,20 @@ export function RateLimit(options: {
649671
windowMs: number;
650672
standardHeaders?: boolean;
651673
legacyHeaders?: boolean;
652-
}): MethodDecorator {
674+
}): MethodDecorator & ClassDecorator {
653675
const opts = {
654676
standardHeaders: true,
655677
legacyHeaders: false,
656678
...options
657679
};
658-
return (target, propertyKey, _descriptor) => {
659-
Reflect.defineMetadata(RATE_LIMIT_KEY, opts, target, propertyKey as string);
680+
return (target: any, propertyKey?: string | symbol, _descriptor?: PropertyDescriptor) => {
681+
if (typeof propertyKey === 'undefined') {
682+
// Class decorator
683+
Reflect.defineMetadata(RATE_LIMIT_KEY, opts, target);
684+
} else {
685+
// Method decorator
686+
Reflect.defineMetadata(RATE_LIMIT_KEY, opts, target, propertyKey as string);
687+
}
660688
};
661689
}
662690

@@ -713,16 +741,22 @@ export function ContentType(type: string): MethodDecorator {
713741
export function Version(
714742
version: string,
715743
options?: { addPrefix?: boolean; addHeader?: boolean; headerName?: string }
716-
): MethodDecorator {
744+
): MethodDecorator & ClassDecorator {
717745
const opts = {
718746
addPrefix: true,
719747
addHeader: true,
720748
headerName: 'X-API-Version',
721749
...options
722750
};
723751

724-
return (target, propertyKey, _descriptor) => {
725-
Reflect.defineMetadata(VERSION_KEY, { version, options: opts }, target, propertyKey as string);
752+
return (target: any, propertyKey?: string | symbol, _descriptor?: PropertyDescriptor) => {
753+
if (typeof propertyKey === 'undefined') {
754+
// Class decorator
755+
Reflect.defineMetadata(VERSION_KEY, { version, options: opts }, target);
756+
} else {
757+
// Method decorator
758+
Reflect.defineMetadata(VERSION_KEY, { version, options: opts }, target, propertyKey as string);
759+
}
726760
};
727761
}
728762

@@ -741,9 +775,15 @@ export function Version(
741775
* }
742776
* ```
743777
*/
744-
export function Timeout(ms: number): MethodDecorator {
745-
return (target, propertyKey, _descriptor) => {
746-
Reflect.defineMetadata(TIMEOUT_KEY, { ms }, target, propertyKey as string);
778+
export function Timeout(ms: number): MethodDecorator & ClassDecorator {
779+
return (target: any, propertyKey?: string | symbol, _descriptor?: PropertyDescriptor) => {
780+
if (typeof propertyKey === 'undefined') {
781+
// Class decorator
782+
Reflect.defineMetadata(TIMEOUT_KEY, { ms }, target);
783+
} else {
784+
// Method decorator
785+
Reflect.defineMetadata(TIMEOUT_KEY, { ms }, target, propertyKey as string);
786+
}
747787
};
748788
}
749789

@@ -781,8 +821,8 @@ export function Log(options?: {
781821
logBody?: boolean;
782822
logParams?: boolean;
783823
logResponse?: boolean;
784-
}): MethodDecorator {
785-
return (target, propertyKey, _descriptor) => {
824+
}): MethodDecorator & ClassDecorator {
825+
return (target: any, propertyKey?: string | symbol, _descriptor?: PropertyDescriptor) => {
786826
const config = {
787827
logEntry: true,
788828
logExit: true,
@@ -791,7 +831,13 @@ export function Log(options?: {
791831
logResponse: false,
792832
...options
793833
};
794-
Reflect.defineMetadata(LOG_KEY, config, target, propertyKey as string);
834+
if (typeof propertyKey === 'undefined') {
835+
// Class decorator
836+
Reflect.defineMetadata(LOG_KEY, config, target);
837+
} else {
838+
// Method decorator
839+
Reflect.defineMetadata(LOG_KEY, config, target, propertyKey as string);
840+
}
795841
};
796842
}
797843

0 commit comments

Comments
 (0)