Skip to content

Commit 7f2d2b8

Browse files
committed
fix: handle registry missing instance
1 parent 348f3b6 commit 7f2d2b8

File tree

3 files changed

+91
-54
lines changed

3 files changed

+91
-54
lines changed

src/frameworks/shared/controllers/rpc-extended-controller.decorator.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
import { UseFilters, UseInterceptors, applyDecorators } from '@nestjs/common';
1+
import {
2+
Controller,
3+
UseFilters,
4+
UseInterceptors,
5+
applyDecorators,
6+
} from '@nestjs/common';
27
import { RpcExtendedExceptionFilter } from '../filters';
38
import { RpcContextInterceptor, RpcLoggingInterceptor } from '../interceptors';
49
import { RegisterControls } from '../decorators/register-controls.decorator';
10+
import { log } from '../utils';
511

612
/**
713
* Extended RPC controller decorator that applies:
@@ -11,15 +17,21 @@ import { RegisterControls } from '../decorators/register-controls.decorator';
1117
* @param prefix Controller prefix (optional)
1218
*/
1319
export function RpcExtendedController(handlerType?: string) {
20+
log.debug(`Creating RpcExtendedController with handler type: ${handlerType}`);
21+
1422
const decorators = [
15-
// Only add Controller with prefix if prefix is provided
23+
// Add standard Controller decorator
24+
Controller(),
1625
UseFilters(RpcExtendedExceptionFilter),
1726
UseInterceptors(RpcLoggingInterceptor),
1827
UseInterceptors(RpcContextInterceptor),
1928
];
2029

2130
// Add RegisterControls decorator if handlerType is provided
2231
if (handlerType) {
32+
log.debug(
33+
`Adding RegisterControls decorator with handler type: ${handlerType}`,
34+
);
2335
decorators.push(RegisterControls(handlerType));
2436
}
2537

src/frameworks/shared/decorators/register-controls.decorator.ts

Lines changed: 73 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,17 @@ export const Control = (description: string) =>
2020
/**
2121
* Decorator for registering a handler class with all its control methods.
2222
*
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.
2628
*
2729
* Usage:
2830
* ```typescript
2931
* @Injectable()
3032
* @RegisterControls('MyHandlerType')
3133
* export class MyHandler {
32-
* constructor() {
33-
* // No need to inject HandlerRegistryService
34-
* }
35-
*
3634
* @Control('Does something useful')
3735
* async myMethod() {
3836
* // Implementation
@@ -59,72 +57,95 @@ export function RegisterControls(handlerType: string) {
5957
);
6058
});
6159

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+
);
6363

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 }[] = [];
6666

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+
});
7280
}
81+
});
7382

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);
7789

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;
8492

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);
9395

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(() => {
9699
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;
99104

100-
if (metadata) {
105+
if (handlerRegistry instanceof HandlerRegistryService) {
101106
log.debug(
102-
`Registering control: ${handlerType}.${methodName} - ${metadata.description}`,
107+
`Registering ${controlMethods.length} controls for handler ${handlerType}`,
103108
);
104109

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}`,
109121
);
110122
}
111123
} catch (err: any) {
112124
log.error(
113-
`Error registering control ${methodName}: ${err.message}`,
125+
`Error registering controls for ${target.name}: ${err.message}`,
114126
);
115127
}
116-
}
128+
}, 100);
117129

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;
125131
}
126-
};
127132

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
128149
return target;
129150
};
130151
}

src/modules/messaging/domain/usecases/services/handler-registry.service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ export class HandlerRegistryService {
2222

2323
constructor() {
2424
log.debug('HandlerRegistryService created');
25+
26+
// Store a reference to this instance globally for fallback mechanism
27+
// This helps with decorators that may not have access to the DI container
28+
(global as any).handlerRegistryService = this;
2529
}
2630

2731
// Getter to allow debugging of the controls map

0 commit comments

Comments
 (0)