From 288269913c39a9a9f6af418d512766986c25acdb Mon Sep 17 00:00:00 2001 From: jkalberer Date: Wed, 19 Nov 2025 17:04:29 -0800 Subject: [PATCH] feat: Add async loading of options --- .prettierrc | 5 ++-- README.md | 19 ++++++++++++ src/datadog-trace.module.spec.ts | 48 ++++++++++++++++++++++++++++++ src/datadog-trace.module.ts | 51 ++++++++++++++++++++++++-------- 4 files changed, 109 insertions(+), 14 deletions(-) create mode 100644 src/datadog-trace.module.spec.ts diff --git a/.prettierrc b/.prettierrc index dcb7279..ec1ea75 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,5 @@ { "singleQuote": true, - "trailingComma": "all" -} \ No newline at end of file + "trailingComma": "all", + "tabWidth": 2 +} diff --git a/README.md b/README.md index 0510e15..418077f 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,25 @@ import { DatadogTraceModule } from 'nestjs-ddtrace'; export class AppModule {} ``` +## Async loading of options +```ts +import { DatadogTraceModule } from 'nestjs-ddtrace'; + +@Module({ + imports: [ + DatadogTraceModule.forRootAsync({ + imports: [CustomModule], + injects: [CustonService], + useFactory: async (customService: CustomService) => { + return await customService.getOptions(); + } + }) + ], +}) + +export class AppModule {} +``` + ## Miscellaneous Inspired by the [nestjs-otel](https://github.com/pragmaticivan/nestjs-otel) and [nestjs-opentelemetry](https://github.com/MetinSeylan/Nestjs-OpenTelemetry#readme) repository. diff --git a/src/datadog-trace.module.spec.ts b/src/datadog-trace.module.spec.ts new file mode 100644 index 0000000..e210f67 --- /dev/null +++ b/src/datadog-trace.module.spec.ts @@ -0,0 +1,48 @@ +import { Test } from '@nestjs/testing'; +import { DatadogTraceModule } from './datadog-trace.module'; +import { TraceService } from './trace.service'; +import type { DatadogTraceModuleOptions } from './datadog-trace-module-options.interface'; +import { Injectable, Module } from '@nestjs/common'; + +describe('DatadogTraceModule', () => { + it('forRoot should register TraceService, DecoratorInjector and TRACE_INJECTORS provider', async () => { + const options: DatadogTraceModuleOptions = {}; + + const moduleRef = await Test.createTestingModule({ + imports: [DatadogTraceModule.forRoot(options)], + }).compile(); + + // exports + expect(moduleRef.get(TraceService)).toBeInstanceOf(TraceService); + }); + + + it('forRootAsync should resolve options via factory and export TraceService', async () => { + @Injectable() + class CustomService { + getOptions(): DatadogTraceModuleOptions { + return { providers: true }; + } + } + + @Module({ + providers: [CustomService], + exports: [CustomService], + }) + class CustomModule { } + + const moduleRef = await Test.createTestingModule({ + imports: [ + DatadogTraceModule.forRootAsync({ + imports: [CustomModule], + inject: [CustomService], + useFactory: async (customService: CustomService) => { + return await customService.getOptions() + }, + }), + ], + }).compile(); + + expect(moduleRef.get(TraceService)).toBeInstanceOf(TraceService); + }); +}); diff --git a/src/datadog-trace.module.ts b/src/datadog-trace.module.ts index 3b07fab..0dbd1ab 100644 --- a/src/datadog-trace.module.ts +++ b/src/datadog-trace.module.ts @@ -1,4 +1,4 @@ -import { DynamicModule, Module } from '@nestjs/common'; +import { DynamicModule, Module, type ModuleMetadata } from '@nestjs/common'; import { FactoryProvider } from '@nestjs/common/interfaces/modules/provider.interface'; import { TraceService } from './trace.service'; import { DecoratorInjector } from './decorator.injector'; @@ -6,7 +6,13 @@ import { Injector } from 'src/injector.interface'; import { Constants } from './constants'; import { DatadogTraceModuleOptions } from './datadog-trace-module-options.interface'; -@Module({}) +interface DatadogTraceAsyncModuleOptions extends Pick { + useFactory: (...args: any[]) => Promise | DatadogTraceModuleOptions; + inject: any[]; +} +const DATADOG_TRACE_MODULE_PARAMS = Symbol('DATADOG_TRACE_MODULE_PARAMS'); + + export class DatadogTraceModule { static forRoot(options: DatadogTraceModuleOptions = {}): DynamicModule { return { @@ -15,23 +21,44 @@ export class DatadogTraceModule { providers: [ TraceService, DecoratorInjector, - this.buildInjectors(options), + { + provide: Constants.TRACE_INJECTORS, + useFactory: async (...injectors: Injector[]) => { + for await (const injector of injectors) { + if (injector.inject) await injector.inject(options); + } + }, + inject: [DecoratorInjector], + } ], exports: [TraceService], }; } - private static buildInjectors( - options: DatadogTraceModuleOptions, - ): FactoryProvider { + static forRootAsync(options: DatadogTraceAsyncModuleOptions): DynamicModule { return { - provide: Constants.TRACE_INJECTORS, - useFactory: async (...injectors: Injector[]) => { - for await (const injector of injectors) { - if (injector.inject) await injector.inject(options); + global: true, + module: DatadogTraceModule, + imports: options.imports ?? [], + providers: [ + TraceService, + DecoratorInjector, + { + provide: DATADOG_TRACE_MODULE_PARAMS, + inject: options.inject ?? [], + useFactory: options.useFactory, + }, + { + provide: Constants.TRACE_INJECTORS, + inject: [DATADOG_TRACE_MODULE_PARAMS, DecoratorInjector], + useFactory: async (moduleOptions: DatadogTraceModuleOptions, ...injectors: Injector[]) => { + for await (const injector of injectors) { + if (injector.inject) await injector.inject(moduleOptions); + } + }, } - }, - inject: [DecoratorInjector], + ], + exports: [TraceService], }; } }