Skip to content

Commit adc7e15

Browse files
feat: enhance introspection capabilities
1 parent 84d0ff3 commit adc7e15

26 files changed

+535
-143
lines changed

packages/common/interfaces/nest-application-context-options.interface.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,18 @@ export class NestApplicationContextOptions {
5353
* @default 'reference'
5454
*/
5555
moduleIdGeneratorAlgorithm?: 'deep-hash' | 'reference';
56+
57+
/**
58+
* Instrument the application context.
59+
* This option allows you to add custom instrumentation to the application context.
60+
*/
61+
instrument?: {
62+
/**
63+
* Function that decorates each instance created by the application context.
64+
* This function can be used to add custom properties or methods to the instance.
65+
* @param instance The instance to decorate.
66+
* @returns The decorated instance.
67+
*/
68+
instanceDecorator: (instance: unknown) => unknown;
69+
};
5670
}

packages/common/services/console-logger.service.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,23 @@ export class ConsoleLogger implements LoggerService {
346346
writeStreamType?: 'stdout' | 'stderr';
347347
errorStack?: unknown;
348348
},
349+
) {
350+
const logObject = this.getJsonLogObject(message, options);
351+
const formattedMessage =
352+
!this.options.colors && this.inspectOptions.compact === true
353+
? JSON.stringify(logObject, this.stringifyReplacer)
354+
: inspect(logObject, this.inspectOptions);
355+
process[options.writeStreamType ?? 'stdout'].write(`${formattedMessage}\n`);
356+
}
357+
358+
protected getJsonLogObject(
359+
message: unknown,
360+
options: {
361+
context: string;
362+
logLevel: LogLevel;
363+
writeStreamType?: 'stdout' | 'stderr';
364+
errorStack?: unknown;
365+
},
349366
) {
350367
type JsonLogObject = {
351368
level: LogLevel;
@@ -370,12 +387,7 @@ export class ConsoleLogger implements LoggerService {
370387
if (options.errorStack) {
371388
logObject.stack = options.errorStack;
372389
}
373-
374-
const formattedMessage =
375-
!this.options.colors && this.inspectOptions.compact === true
376-
? JSON.stringify(logObject, this.stringifyReplacer)
377-
: inspect(logObject, this.inspectOptions);
378-
process[options.writeStreamType ?? 'stdout'].write(`${formattedMessage}\n`);
390+
return logObject;
379391
}
380392

381393
protected formatPid(pid: number) {

packages/core/adapters/http-adapter.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ export abstract class AbstractHttpAdapter<
1212
> implements HttpServer<TRequest, TResponse>
1313
{
1414
protected httpServer: TServer;
15+
protected onRouteTriggered:
16+
| ((requestMethod: RequestMethod, path: string) => void)
17+
| undefined;
1518

1619
constructor(protected instance?: any) {}
1720

@@ -143,6 +146,16 @@ export abstract class AbstractHttpAdapter<
143146
return path;
144147
}
145148

149+
public setOnRouteTriggered(
150+
onRouteTriggered: (requestMethod: RequestMethod, path: string) => void,
151+
) {
152+
this.onRouteTriggered = onRouteTriggered;
153+
}
154+
155+
public getOnRouteTriggered() {
156+
return this.onRouteTriggered;
157+
}
158+
146159
abstract close();
147160
abstract initHttpServer(options: NestApplicationOptions);
148161
abstract useStaticAssets(...args: any[]);
@@ -174,4 +187,6 @@ export abstract class AbstractHttpAdapter<
174187
version: VersionValue,
175188
versioningOptions: VersioningOptions,
176189
): (req: TRequest, res: TResponse, next: () => void) => Function;
190+
abstract setOnRequestHook?(onRequestHook: Function): void;
191+
abstract setOnResponseHook?(onResponseHook: Function): void;
177192
}

packages/core/helpers/http-adapter-host.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Observable, Subject } from 'rxjs';
1+
import { Observable, ReplaySubject, Subject } from 'rxjs';
22
import { AbstractHttpAdapter } from '../adapters/http-adapter';
33

44
/**
@@ -18,6 +18,7 @@ export class HttpAdapterHost<
1818
> {
1919
private _httpAdapter?: T;
2020
private _listen$ = new Subject<void>();
21+
private _init$ = new ReplaySubject<void>();
2122
private isListening = false;
2223

2324
/**
@@ -27,6 +28,9 @@ export class HttpAdapterHost<
2728
*/
2829
set httpAdapter(httpAdapter: T) {
2930
this._httpAdapter = httpAdapter;
31+
32+
this._init$.next();
33+
this._init$.complete();
3034
}
3135

3236
/**
@@ -47,6 +51,14 @@ export class HttpAdapterHost<
4751
return this._listen$.asObservable();
4852
}
4953

54+
/**
55+
* Observable that allows to subscribe to the `init` event.
56+
* This event is emitted when the HTTP application is initialized.
57+
*/
58+
get init$(): Observable<void> {
59+
return this._init$.asObservable();
60+
}
61+
5062
/**
5163
* Sets the listening state of the application.
5264
*/

packages/core/injector/container.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ export class NestContainer {
6767
return this._applicationConfig;
6868
}
6969

70+
get contextOptions(): NestApplicationContextOptions | undefined {
71+
return this._contextOptions;
72+
}
73+
7074
public setHttpAdapter(httpAdapter: any) {
7175
this.internalProvidersStorage.httpAdapter = httpAdapter;
7276

packages/core/injector/injector.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,26 @@ export interface InjectorDependencyContext {
8484

8585
export class Injector {
8686
private logger: LoggerService = new Logger('InjectorLogger');
87+
private readonly instanceDecorator: (target: unknown) => unknown = (
88+
target: unknown,
89+
) => target;
8790

88-
constructor(private readonly options?: { preview: boolean }) {}
91+
constructor(
92+
private readonly options?: {
93+
/**
94+
* Whether to enable preview mode.
95+
*/
96+
preview: boolean;
97+
/**
98+
* Function to decorate a freshly created instance.
99+
*/
100+
instanceDecorator?: (target: unknown) => unknown;
101+
},
102+
) {
103+
if (options?.instanceDecorator) {
104+
this.instanceDecorator = options.instanceDecorator;
105+
}
106+
}
89107

90108
public loadPrototype<T>(
91109
{ token }: InstanceWrapper<T>,
@@ -768,11 +786,14 @@ export class Injector {
768786
new (metatype as Type<any>)(...instances),
769787
)
770788
: new (metatype as Type<any>)(...instances);
789+
790+
instanceHost.instance = this.instanceDecorator(instanceHost.instance);
771791
} else if (isInContext) {
772792
const factoryReturnValue = (targetMetatype.metatype as any as Function)(
773793
...instances,
774794
);
775795
instanceHost.instance = await factoryReturnValue;
796+
instanceHost.instance = this.instanceDecorator(instanceHost.instance);
776797
}
777798
instanceHost.isResolved = true;
778799
return instanceHost.instance;

packages/core/injector/internal-core-module/internal-core-module-factory.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ export class InternalCoreModuleFactory {
2727
const logger = new Logger(LazyModuleLoader.name, {
2828
timestamp: false,
2929
});
30-
const injector = new Injector();
30+
const injector = new Injector({
31+
preview: container.contextOptions?.preview!,
32+
instanceDecorator:
33+
container.contextOptions?.instrument?.instanceDecorator,
34+
});
3135
const instanceLoader = new InstanceLoader(
3236
container,
3337
injector,

packages/core/injector/module-ref.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export interface ModuleRefGetOrResolveOpts {
2323
}
2424

2525
export abstract class ModuleRef extends AbstractInstanceResolver {
26-
protected readonly injector = new Injector();
26+
protected readonly injector: Injector;
2727
private _instanceLinksHost: InstanceLinksHost;
2828

2929
protected get instanceLinksHost() {
@@ -35,6 +35,12 @@ export abstract class ModuleRef extends AbstractInstanceResolver {
3535

3636
constructor(protected readonly container: NestContainer) {
3737
super();
38+
39+
this.injector = new Injector({
40+
preview: container.contextOptions?.preview!,
41+
instanceDecorator:
42+
container.contextOptions?.instrument?.instanceDecorator,
43+
});
3844
}
3945

4046
/**

packages/core/injector/module.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,13 +384,16 @@ export class Module {
384384
enhancerSubtype?: EnhancerSubtype,
385385
) {
386386
const { useValue: value, provide: providerToken } = provider;
387+
388+
const instanceDecorator =
389+
this.container.contextOptions?.instrument?.instanceDecorator;
387390
collection.set(
388391
providerToken,
389392
new InstanceWrapper({
390393
token: providerToken,
391394
name: (providerToken as Function)?.name || providerToken,
392395
metatype: null!,
393-
instance: value,
396+
instance: instanceDecorator ? instanceDecorator(value) : value,
394397
isResolved: true,
395398
async: value instanceof Promise,
396399
host: this,
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,41 @@
1+
import { Observable, ReplaySubject } from 'rxjs';
12
import { uid } from 'uid';
23
import { Module } from './module';
34

45
export class ModulesContainer extends Map<string, Module> {
56
private readonly _applicationId = uid(21);
7+
private readonly _rpcTargetRegistry$ = new ReplaySubject<any>();
68

9+
/**
10+
* Unique identifier of the application instance.
11+
*/
712
get applicationId(): string {
813
return this._applicationId;
914
}
1015

16+
/**
17+
* Retrieves a module by its identifier.
18+
* @param id The identifier of the module to retrieve.
19+
* @returns The module instance if found, otherwise undefined.
20+
*/
1121
public getById(id: string): Module | undefined {
1222
return Array.from(this.values()).find(moduleRef => moduleRef.id === id);
1323
}
24+
25+
/**
26+
* Returns the RPC target registry as an observable.
27+
* This registry contains all RPC targets registered in the application.
28+
* @returns An observable that emits the RPC target registry.
29+
*/
30+
public getRpcTargetRegistry<T>(): Observable<T> {
31+
return this._rpcTargetRegistry$.asObservable();
32+
}
33+
34+
/**
35+
* Adds an RPC target to the registry.
36+
* @param target The RPC target to add.
37+
*/
38+
public addRpcTarget<T>(target: T): void {
39+
this._rpcTargetRegistry$.next(target);
40+
}
1441
}

0 commit comments

Comments
 (0)