@@ -20,19 +20,17 @@ export const Control = (description: string) =>
20
20
/**
21
21
* Decorator for registering a handler class with all its control methods.
22
22
*
23
- * This decorator automatically detects the HandlerRegistryService from the
24
- * dependency injection container, so you don't need to manually inject it
25
- * in your handler constructor.
23
+ * This decorator will store metadata about the handler type on the class itself,
24
+ * and will scan for methods that have the @Control decorator.
25
+ *
26
+ * This version works directly with method decorators to automatically register
27
+ * control methods.
26
28
*
27
29
* Usage:
28
30
* ```typescript
29
31
* @Injectable ()
30
32
* @RegisterControls ('MyHandlerType')
31
33
* export class MyHandler {
32
- * constructor() {
33
- * // No need to inject HandlerRegistryService
34
- * }
35
- *
36
34
* @Control ('Does something useful')
37
35
* async myMethod() {
38
36
* // Implementation
@@ -59,72 +57,95 @@ export function RegisterControls(handlerType: string) {
59
57
) ;
60
58
} ) ;
61
59
62
- log . debug ( `Found ${ methodNames . length } methods in class ${ target . name } ` ) ;
60
+ log . debug (
61
+ `Found ${ methodNames . length } methods in class ${ target . name } : ${ methodNames . join ( ', ' ) } ` ,
62
+ ) ;
63
63
64
- // Original constructor
65
- const originalInit = target . prototype . onModuleInit ;
64
+ // Store info about which methods have the Control decorator
65
+ const controlMethods : { name : string ; description : string } [ ] = [ ] ;
66
66
67
- // Add onModuleInit lifecycle hook to register controls when module initializes
68
- target . prototype . onModuleInit = async function ( ) {
69
- // Call original onModuleInit if it exists
70
- if ( originalInit ) {
71
- await originalInit . call ( this ) ;
67
+ // Check each method for the Control decorator
68
+ methodNames . forEach ( ( methodName ) => {
69
+ const method = prototype [ methodName ] ;
70
+ const metadata = Reflect . getMetadata ( REGISTERED_CONTROLS , method ) ;
71
+
72
+ if ( metadata ) {
73
+ log . debug (
74
+ `Found Control decorator on ${ target . name } .${ methodName } : ${ metadata . description } ` ,
75
+ ) ;
76
+ controlMethods . push ( {
77
+ name : methodName ,
78
+ description : metadata . description ,
79
+ } ) ;
72
80
}
81
+ } ) ;
73
82
74
- try {
75
- // Find the HandlerRegistryService
76
- const handlerRegistryService = this . handlerRegistry ;
83
+ // Store the control methods on the class
84
+ if ( controlMethods . length > 0 ) {
85
+ log . debug (
86
+ `Saving ${ controlMethods . length } control methods on ${ target . name } ` ,
87
+ ) ;
88
+ Reflect . defineMetadata ( 'control_methods' , controlMethods , target ) ;
77
89
78
- if ( ! handlerRegistryService ) {
79
- log . warn (
80
- `HandlerRegistryService not injected in ${ target . name } (property 'handlerRegistry' not found).` ,
81
- ) ;
82
- return ;
83
- }
90
+ // When an instance is created, we attempt to register the controls
91
+ const originalConstructor = target ;
84
92
85
- if ( ! ( handlerRegistryService instanceof HandlerRegistryService ) ) {
86
- log . warn (
87
- `Property 'handlerRegistry' in ${ target . name } is not an instance of HandlerRegistryService.` ,
88
- ) ;
89
- return ;
90
- }
91
-
92
- log . debug ( `Found HandlerRegistryService in ${ target . name } ` ) ;
93
+ function newConstructor ( ...args : any [ ] ) {
94
+ const instance = new originalConstructor ( ...args ) ;
93
95
94
- // Register each method that has the @Control decorator
95
- for ( const methodName of methodNames ) {
96
+ // Use setTimeout to ensure this runs after construction
97
+ // This gives the DI container time to set up properties
98
+ setTimeout ( ( ) => {
96
99
try {
97
- const method = prototype [ methodName ] ;
98
- const metadata = Reflect . getMetadata ( REGISTERED_CONTROLS , method ) ;
100
+ // Try to find HandlerRegistryService
101
+ const handlerRegistry =
102
+ ( global as any ) . handlerRegistryService ||
103
+ instance . handlerRegistry ;
99
104
100
- if ( metadata ) {
105
+ if ( handlerRegistry instanceof HandlerRegistryService ) {
101
106
log . debug (
102
- `Registering control: ${ handlerType } . ${ methodName } - ${ metadata . description } ` ,
107
+ `Registering ${ controlMethods . length } controls for handler ${ handlerType } ` ,
103
108
) ;
104
109
105
- handlerRegistryService . registerControl (
106
- handlerType ,
107
- methodName ,
108
- metadata . description ,
110
+ // Register each control method
111
+ controlMethods . forEach ( ( control ) => {
112
+ handlerRegistry . registerControl (
113
+ handlerType ,
114
+ control . name ,
115
+ control . description ,
116
+ ) ;
117
+ } ) ;
118
+ } else {
119
+ log . debug (
120
+ `HandlerRegistryService not found when creating ${ target . name } ` ,
109
121
) ;
110
122
}
111
123
} catch ( err : any ) {
112
124
log . error (
113
- `Error registering control ${ methodName } : ${ err . message } ` ,
125
+ `Error registering controls for ${ target . name } : ${ err . message } ` ,
114
126
) ;
115
127
}
116
- }
128
+ } , 100 ) ;
117
129
118
- const registeredControls =
119
- handlerRegistryService . getControlsForHandler ( handlerType ) ;
120
- log . info (
121
- `Registered ${ registeredControls . length } controls for handler ${ handlerType } ` ,
122
- ) ;
123
- } catch ( err : any ) {
124
- log . error ( `Error in onModuleInit for ${ target . name } : ${ err . message } ` ) ;
130
+ return instance ;
125
131
}
126
- } ;
127
132
133
+ // Copy prototype and metadata so the new constructor works like the original
134
+ newConstructor . prototype = originalConstructor . prototype ;
135
+ Object . getOwnPropertyNames ( target ) . forEach ( ( key ) => {
136
+ if ( key !== 'prototype' ) {
137
+ Object . defineProperty (
138
+ newConstructor ,
139
+ key ,
140
+ Object . getOwnPropertyDescriptor ( target , key ) as PropertyDescriptor ,
141
+ ) ;
142
+ }
143
+ } ) ;
144
+
145
+ return newConstructor ;
146
+ }
147
+
148
+ // If no control methods found, just return the original class
128
149
return target ;
129
150
} ;
130
151
}
0 commit comments