@@ -152,18 +152,100 @@ class RateLimiterCache {
152152// Global cache instance
153153const rateLimiterCache = new RateLimiterCache ( ) ;
154154
155+ // di.container.ts
156+ type Constructor < T = any > = new ( ...args : any [ ] ) => T ;
157+
158+ export class DIContainer {
159+ private instances = new Map < Constructor , any > ( ) ;
160+ private constructing = new Map < Constructor , any > ( ) ;
161+
162+ register < T > ( target : Constructor < T > ) {
163+ // Mark as injectable, but do not instantiate yet
164+ if ( ! this . instances . has ( target ) && ! this . constructing . has ( target ) ) {
165+ // No-op: instantiation is deferred until get()
166+ }
167+ }
168+
169+ get < T > ( target : Constructor < T > ) : T {
170+ // Return existing instance if available
171+ if ( this . instances . has ( target ) ) {
172+ return this . instances . get ( target ) ;
173+ }
174+
175+ // If currently constructing, return the proxy (for circular refs)
176+ if ( this . constructing . has ( target ) ) {
177+ return this . constructing . get ( target ) ;
178+ }
179+
180+ // Mark as constructing (for circular dependency support)
181+ let proxy : any = { } ;
182+ this . constructing . set ( target , proxy ) ;
183+
184+ // Resolve constructor dependencies
185+ const paramTypes : Constructor [ ] = Reflect . getMetadata ( 'design:paramtypes' , target as object ) || [ ] ;
186+ const dependencies = paramTypes . map ( dep => this . get ( dep ) ) ;
187+ const instance = new target ( ...dependencies ) ;
188+
189+ // Copy instance properties to proxy (for circular refs)
190+ Object . assign ( proxy , instance ) ;
191+
192+ // Replace proxy with real instance
193+ this . instances . set ( target , proxy ) ;
194+ this . constructing . delete ( target ) ;
195+
196+ // Copy prototype (for instanceof checks)
197+ Object . setPrototypeOf ( proxy , target . prototype ) ;
198+
199+ return proxy ;
200+ }
201+
202+ clear ( ) {
203+ this . instances . clear ( ) ;
204+ this . constructing . clear ( ) ;
205+ }
206+ }
207+
208+ const diContainer = new DIContainer ( ) ;
209+
155210function normalizeHeaderValue ( value : unknown ) : string | string [ ] | undefined {
156211 if ( typeof value === 'undefined' ) return undefined ;
157-
158212 if ( typeof value === 'string' ) return value ;
159-
160213 if ( Array . isArray ( value ) && value . every ( item => typeof item === 'string' ) ) {
161214 return value ;
162215 }
163-
164216 return String ( value ) ;
165217}
166218
219+ /**
220+ * Injectable decorator for marking classes as injectable.
221+ *
222+ * @returns Class decorator that marks a class as injectable and registers it with the DI container.
223+ */
224+ export function Injectable ( ) : ClassDecorator {
225+ return target => {
226+ Reflect . defineMetadata ( 'injectable' , true , target ) ;
227+ diContainer . register ( target as any ) ;
228+ } ;
229+ }
230+
231+ /**
232+ * Inject decorator for injecting dependencies into class properties.
233+ *
234+ * @param targetClass - The class to inject
235+ * @returns Property decorator that injects the specified class into the property
236+ */
237+ export function Inject < T > ( targetClass : new ( ...args : any [ ] ) => T ) : PropertyDecorator {
238+ return ( target , propertyKey ) => {
239+ Object . defineProperty ( target , propertyKey , {
240+ get : function ( ) {
241+ return diContainer . get ( targetClass ) ;
242+ } ,
243+ enumerable : true ,
244+ configurable : true
245+ } ) ;
246+ } ;
247+ }
248+
167249/**
168250 * Factory function that creates HTTP method decorators.
169251 *
@@ -173,13 +255,13 @@ function normalizeHeaderValue(value: unknown): string | string[] | undefined {
173255function createRouteDecorator ( method : HttpMethod ) {
174256 return ( path : string ) : MethodDecorator => {
175257 return ( target , propertyKey , descriptor ) => {
176- const routes : RouteDefinition [ ] = Reflect . getMetadata ( ROUTES_KEY , target . constructor ) || [ ] ;
258+ const routes : RouteDefinition [ ] = Reflect . getMetadata ( ROUTES_KEY , ( target as object ) . constructor ) || [ ] ;
177259 routes . push ( {
178260 path,
179261 method,
180262 handlerName : propertyKey as string
181263 } ) ;
182- Reflect . defineMetadata ( ROUTES_KEY , routes , target . constructor ) ;
264+ Reflect . defineMetadata ( ROUTES_KEY , routes , ( target as object ) . constructor ) ;
183265 } ;
184266 } ;
185267}
@@ -311,8 +393,9 @@ export function Controller(basePath: string): ClassDecorator {
311393 */
312394export function Use ( ...middlewares : RequestHandler [ ] ) : MethodDecorator {
313395 return ( target , propertyKey , _descriptor ) => {
314- const existing : RequestHandler [ ] = Reflect . getMetadata ( MIDDLEWARE_KEY , target , propertyKey as string ) || [ ] ;
315- Reflect . defineMetadata ( MIDDLEWARE_KEY , [ ...existing , ...middlewares ] , target , propertyKey as string ) ;
396+ const existing : RequestHandler [ ] =
397+ Reflect . getMetadata ( MIDDLEWARE_KEY , target as object , propertyKey as string ) || [ ] ;
398+ Reflect . defineMetadata ( MIDDLEWARE_KEY , [ ...existing , ...middlewares ] , target as object , propertyKey as string ) ;
316399 } ;
317400}
318401
@@ -326,11 +409,11 @@ export function Use(...middlewares: RequestHandler[]): MethodDecorator {
326409function createParamDecorator ( type : ParamDefinition [ 'type' ] , key ?: string ) {
327410 return ( paramKey ?: string ) : ParameterDecorator => {
328411 return ( target , propertyKey , parameterIndex ) => {
329- const params : ParamDefinition [ ] = Reflect . getMetadata ( PARAMS_KEY , target , propertyKey as string ) || [ ] ;
412+ const params : ParamDefinition [ ] = Reflect . getMetadata ( PARAMS_KEY , target as object , propertyKey as string ) || [ ] ;
330413 // Ensure parameters are ordered by index
331414 params . push ( { index : parameterIndex , type, key : paramKey || key } ) ;
332415 params . sort ( ( a , b ) => a . index - b . index ) ;
333- Reflect . defineMetadata ( PARAMS_KEY , params , target , propertyKey as string ) ;
416+ Reflect . defineMetadata ( PARAMS_KEY , params , target as object , propertyKey as string ) ;
334417 } ;
335418 } ;
336419}
@@ -495,14 +578,14 @@ export function Headers(headers: Record<string, string> | string, value?: string
495578 return ( target : any , propertyKey ?: string | symbol ) => {
496579 if ( typeof propertyKey === 'undefined' ) {
497580 // Class decorator
498- const existing : Record < string , string > = Reflect . getMetadata ( HEADER_KEY , target ) || { } ;
581+ const existing : Record < string , string > = Reflect . getMetadata ( HEADER_KEY , target as object ) || { } ;
499582 const newHeaders = typeof headers === 'string' ? { [ headers ] : value ! } : headers ;
500- Reflect . defineMetadata ( HEADER_KEY , { ...existing , ...newHeaders } , target ) ;
583+ Reflect . defineMetadata ( HEADER_KEY , { ...existing , ...newHeaders } , target as object ) ;
501584 } else {
502585 // Method decorator
503- const existing : Record < string , string > = Reflect . getMetadata ( HEADER_KEY , target , propertyKey ) || { } ;
586+ const existing : Record < string , string > = Reflect . getMetadata ( HEADER_KEY , target as object , propertyKey ) || { } ;
504587 const newHeaders = typeof headers === 'string' ? { [ headers ] : value ! } : headers ;
505- Reflect . defineMetadata ( HEADER_KEY , { ...existing , ...newHeaders } , target , propertyKey ) ;
588+ Reflect . defineMetadata ( HEADER_KEY , { ...existing , ...newHeaders } , target as object , propertyKey ) ;
506589 }
507590 } ;
508591}
@@ -525,9 +608,9 @@ export function Headers(headers: Record<string, string> | string, value?: string
525608 */
526609export function Before ( fn : Function ) : MethodDecorator {
527610 return ( target , propertyKey , _descriptor ) => {
528- const hooks : Function [ ] = Reflect . getMetadata ( BEFORE_KEY , target , propertyKey as string ) || [ ] ;
611+ const hooks : Function [ ] = Reflect . getMetadata ( BEFORE_KEY , target as object , propertyKey as string ) || [ ] ;
529612 hooks . push ( fn ) ;
530- Reflect . defineMetadata ( BEFORE_KEY , hooks , target , propertyKey as string ) ;
613+ Reflect . defineMetadata ( BEFORE_KEY , hooks , target as object , propertyKey as string ) ;
531614 } ;
532615}
533616
@@ -550,9 +633,9 @@ export function Before(fn: Function): MethodDecorator {
550633 */
551634export function After ( fn : Function ) : MethodDecorator {
552635 return ( target , propertyKey , _descriptor ) => {
553- const hooks : Function [ ] = Reflect . getMetadata ( AFTER_KEY , target , propertyKey as string ) || [ ] ;
636+ const hooks : Function [ ] = Reflect . getMetadata ( AFTER_KEY , target as object , propertyKey as string ) || [ ] ;
554637 hooks . push ( fn ) ;
555- Reflect . defineMetadata ( AFTER_KEY , hooks , target , propertyKey as string ) ;
638+ Reflect . defineMetadata ( AFTER_KEY , hooks , target as object , propertyKey as string ) ;
556639 } ;
557640}
558641
@@ -579,10 +662,10 @@ export function Roles(...roles: string[]): MethodDecorator & ClassDecorator {
579662 return ( target : any , propertyKey ?: string | symbol , _descriptor ?: PropertyDescriptor ) => {
580663 if ( typeof propertyKey === 'undefined' ) {
581664 // Class decorator
582- Reflect . defineMetadata ( ROLES_KEY , roles , target ) ;
665+ Reflect . defineMetadata ( ROLES_KEY , roles , target as object ) ;
583666 } else {
584667 // Method decorator
585- Reflect . defineMetadata ( ROLES_KEY , roles , target , propertyKey as string ) ;
668+ Reflect . defineMetadata ( ROLES_KEY , roles , target as object , propertyKey as string ) ;
586669 }
587670 } ;
588671}
@@ -637,10 +720,10 @@ export function Cache(ttlSeconds: number): MethodDecorator & ClassDecorator {
637720 return ( target : any , propertyKey ?: string | symbol , _descriptor ?: PropertyDescriptor ) => {
638721 if ( typeof propertyKey === 'undefined' ) {
639722 // Class decorator
640- Reflect . defineMetadata ( CACHE_KEY , { ttlSeconds } , target ) ;
723+ Reflect . defineMetadata ( CACHE_KEY , { ttlSeconds } , target as object ) ;
641724 } else {
642725 // Method decorator
643- Reflect . defineMetadata ( CACHE_KEY , { ttlSeconds } , target , propertyKey as string ) ;
726+ Reflect . defineMetadata ( CACHE_KEY , { ttlSeconds } , target as object , propertyKey as string ) ;
644727 }
645728 } ;
646729}
@@ -680,10 +763,10 @@ export function RateLimit(options: {
680763 return ( target : any , propertyKey ?: string | symbol , _descriptor ?: PropertyDescriptor ) => {
681764 if ( typeof propertyKey === 'undefined' ) {
682765 // Class decorator
683- Reflect . defineMetadata ( RATE_LIMIT_KEY , opts , target ) ;
766+ Reflect . defineMetadata ( RATE_LIMIT_KEY , opts , target as object ) ;
684767 } else {
685768 // Method decorator
686- Reflect . defineMetadata ( RATE_LIMIT_KEY , opts , target , propertyKey as string ) ;
769+ Reflect . defineMetadata ( RATE_LIMIT_KEY , opts , target as object , propertyKey as string ) ;
687770 }
688771 } ;
689772}
@@ -752,10 +835,10 @@ export function Version(
752835 return ( target : any , propertyKey ?: string | symbol , _descriptor ?: PropertyDescriptor ) => {
753836 if ( typeof propertyKey === 'undefined' ) {
754837 // Class decorator
755- Reflect . defineMetadata ( VERSION_KEY , { version, options : opts } , target ) ;
838+ Reflect . defineMetadata ( VERSION_KEY , { version, options : opts } , target as object ) ;
756839 } else {
757840 // Method decorator
758- Reflect . defineMetadata ( VERSION_KEY , { version, options : opts } , target , propertyKey as string ) ;
841+ Reflect . defineMetadata ( VERSION_KEY , { version, options : opts } , target as object , propertyKey as string ) ;
759842 }
760843 } ;
761844}
@@ -779,10 +862,10 @@ export function Timeout(ms: number): MethodDecorator & ClassDecorator {
779862 return ( target : any , propertyKey ?: string | symbol , _descriptor ?: PropertyDescriptor ) => {
780863 if ( typeof propertyKey === 'undefined' ) {
781864 // Class decorator
782- Reflect . defineMetadata ( TIMEOUT_KEY , { ms } , target ) ;
865+ Reflect . defineMetadata ( TIMEOUT_KEY , { ms } , target as object ) ;
783866 } else {
784867 // Method decorator
785- Reflect . defineMetadata ( TIMEOUT_KEY , { ms } , target , propertyKey as string ) ;
868+ Reflect . defineMetadata ( TIMEOUT_KEY , { ms } , target as object , propertyKey as string ) ;
786869 }
787870 } ;
788871}
@@ -833,10 +916,10 @@ export function Log(options?: {
833916 } ;
834917 if ( typeof propertyKey === 'undefined' ) {
835918 // Class decorator
836- Reflect . defineMetadata ( LOG_KEY , config , target ) ;
919+ Reflect . defineMetadata ( LOG_KEY , config , target as object ) ;
837920 } else {
838921 // Method decorator
839- Reflect . defineMetadata ( LOG_KEY , config , target , propertyKey as string ) ;
922+ Reflect . defineMetadata ( LOG_KEY , config , target as object , propertyKey as string ) ;
840923 }
841924 } ;
842925}
@@ -850,50 +933,56 @@ export function Log(options?: {
850933 */
851934export function registerControllers ( router : Router , controllers : any [ ] ) {
852935 controllers . forEach ( ControllerClass => {
853- const instance = new ControllerClass ( ) ;
854- const basePath : string = Reflect . getMetadata ( 'basePath' , ControllerClass ) || '' ;
855- const routes : RouteDefinition [ ] = Reflect . getMetadata ( ROUTES_KEY , ControllerClass ) || [ ] ;
936+ // Use DI container to resolve controller (constructor injection + property injection)
937+ const instance = diContainer . get ( ControllerClass ) ;
938+ const basePath : string = Reflect . getMetadata ( 'basePath' , ControllerClass as object ) || '' ;
939+ const routes : RouteDefinition [ ] = Reflect . getMetadata ( ROUTES_KEY , ControllerClass as object ) || [ ] ;
856940
857941 // Get controller-level decorators (fallback values)
858- const controllerRateLimit = Reflect . getMetadata ( RATE_LIMIT_KEY , ControllerClass ) ;
859- const controllerCache = Reflect . getMetadata ( CACHE_KEY , ControllerClass ) ;
860- const controllerTimeout = Reflect . getMetadata ( TIMEOUT_KEY , ControllerClass ) ;
861- const controllerVersion = Reflect . getMetadata ( VERSION_KEY , ControllerClass ) ;
862- const controllerRoles = Reflect . getMetadata ( ROLES_KEY , ControllerClass ) ;
863- const controllerLogConfig = Reflect . getMetadata ( LOG_KEY , ControllerClass ) ;
864- const controllerHeaders = Reflect . getMetadata ( HEADER_KEY , ControllerClass ) || { } ;
942+ const controllerRateLimit = Reflect . getMetadata ( RATE_LIMIT_KEY , ControllerClass as object ) ;
943+ const controllerCache = Reflect . getMetadata ( CACHE_KEY , ControllerClass as object ) ;
944+ const controllerTimeout = Reflect . getMetadata ( TIMEOUT_KEY , ControllerClass as object ) ;
945+ const controllerVersion = Reflect . getMetadata ( VERSION_KEY , ControllerClass as object ) ;
946+ const controllerRoles = Reflect . getMetadata ( ROLES_KEY , ControllerClass as object ) ;
947+ const controllerLogConfig = Reflect . getMetadata ( LOG_KEY , ControllerClass as object ) ;
948+ const controllerHeaders = Reflect . getMetadata ( HEADER_KEY , ControllerClass as object ) || { } ;
865949
866950 routes . forEach ( ( { path, method, handlerName } ) => {
867- const middlewares : RequestHandler [ ] = Reflect . getMetadata ( MIDDLEWARE_KEY , instance , handlerName ) || [ ] ;
868- const params : ParamDefinition [ ] = Reflect . getMetadata ( PARAMS_KEY , instance , handlerName ) || [ ] ;
951+ const middlewares : RequestHandler [ ] = Reflect . getMetadata ( MIDDLEWARE_KEY , instance as object , handlerName ) || [ ] ;
952+ const params : ParamDefinition [ ] = Reflect . getMetadata ( PARAMS_KEY , instance as object , handlerName ) || [ ] ;
869953
870- const httpCode : number | undefined = Reflect . getMetadata ( HTTP_CODE_KEY , instance , handlerName ) ;
954+ const httpCode : number | undefined = Reflect . getMetadata ( HTTP_CODE_KEY , instance as object , handlerName ) ;
871955
872956 // Merge controller-level and method-level headers
873- const methodHeaders : Record < string , string > = Reflect . getMetadata ( HEADER_KEY , instance , handlerName ) || { } ;
957+ const methodHeaders : Record < string , string > =
958+ Reflect . getMetadata ( HEADER_KEY , instance as object , handlerName ) || { } ;
874959 const headers = { ...controllerHeaders , ...methodHeaders } ;
875960
876- const beforeHooks : Function [ ] = Reflect . getMetadata ( BEFORE_KEY , instance , handlerName ) || [ ] ;
877- const afterHooks : Function [ ] = Reflect . getMetadata ( AFTER_KEY , instance , handlerName ) || [ ] ;
961+ const beforeHooks : Function [ ] = Reflect . getMetadata ( BEFORE_KEY , instance as object , handlerName ) || [ ] ;
962+ const afterHooks : Function [ ] = Reflect . getMetadata ( AFTER_KEY , instance as object , handlerName ) || [ ] ;
878963 const redirect : { url ?: string ; statusCode : number } | undefined = Reflect . getMetadata (
879964 REDIRECT_KEY ,
880- instance ,
965+ instance as object ,
966+ handlerName
967+ ) ;
968+ const contentType : { type : string } | undefined = Reflect . getMetadata (
969+ CONTENT_TYPE_KEY ,
970+ instance as object ,
881971 handlerName
882972 ) ;
883- const contentType : { type : string } | undefined = Reflect . getMetadata ( CONTENT_TYPE_KEY , instance , handlerName ) ;
884973
885974 // Use method-level decorators if present, otherwise fall back to controller-level
886- const roles : string [ ] = Reflect . getMetadata ( ROLES_KEY , instance , handlerName ) || controllerRoles || [ ] ;
975+ const roles : string [ ] = Reflect . getMetadata ( ROLES_KEY , instance as object , handlerName ) || controllerRoles || [ ] ;
887976 const cache : { ttlSeconds : number } | undefined =
888- Reflect . getMetadata ( CACHE_KEY , instance , handlerName ) || controllerCache ;
977+ Reflect . getMetadata ( CACHE_KEY , instance as object , handlerName ) || controllerCache ;
889978 const rateLimitOptions :
890979 | { max : number ; windowMs : number ; standardHeaders : boolean ; legacyHeaders : boolean }
891- | undefined = Reflect . getMetadata ( RATE_LIMIT_KEY , instance , handlerName ) || controllerRateLimit ;
980+ | undefined = Reflect . getMetadata ( RATE_LIMIT_KEY , instance as object , handlerName ) || controllerRateLimit ;
892981 const version :
893982 | { version : string ; options : { addPrefix : boolean ; addHeader : boolean ; headerName : string } }
894- | undefined = Reflect . getMetadata ( VERSION_KEY , instance , handlerName ) || controllerVersion ;
983+ | undefined = Reflect . getMetadata ( VERSION_KEY , instance as object , handlerName ) || controllerVersion ;
895984 const timeout : { ms : number } | undefined =
896- Reflect . getMetadata ( TIMEOUT_KEY , instance , handlerName ) || controllerTimeout ;
985+ Reflect . getMetadata ( TIMEOUT_KEY , instance as object , handlerName ) || controllerTimeout ;
897986 const logConfig :
898987 | {
899988 logEntry ?: boolean ;
@@ -902,7 +991,7 @@ export function registerControllers(router: Router, controllers: any[]) {
902991 logParams ?: boolean ;
903992 logResponse ?: boolean ;
904993 }
905- | undefined = Reflect . getMetadata ( LOG_KEY , instance , handlerName ) || controllerLogConfig ;
994+ | undefined = Reflect . getMetadata ( LOG_KEY , instance as object , handlerName ) || controllerLogConfig ;
906995
907996 // Create rate limiter for this specific route if needed
908997 let rateLimiter : any = null ;
0 commit comments