import type { FactoryProvider, Provider } from '@nestjs/common'; import { DynamicModule } from '@nestjs/common'; import { Constructor } from '@rsdk/common'; import { DeprecateMethod } from '@rsdk/decorators'; import type { PlatformOptions } from '../types'; import { AdditionalSourceInitializer } from './additional-source/additional-source.initializer'; import { AdditionalSourceModule } from './additional-source/additional-source.module'; import { ConfigContext } from './context/config.context'; import type { Config } from './config.abstract'; import { getPropertyToken } from './metadata'; import type { ConfigSource } from './sources'; import type { AppNameStrategyType } from './types'; export interface ConfigModuleOptions { /** * By convention every configuration key should have unique prefix * for current service. It is inferred from application name */ appName: string; /** * Additional asynchronous configuration sources. For example a source * that loads configuration from Consul or other remote system. */ sources?: Constructor[]; /** * Strategy for detect key name for get config values from environments (default: AppNameStrategyType.PLATFORM) */ appNameStrategy?: AppNameStrategyType; } export class PlatformConfigModule { /** * Use this method to imports specific configuration property * into given module * * @param alias It's not so easy to use plain property key because * of prefix and source key issues. That's why we use aliases. Specify * the same string that was used to define property in * @Property() or @ExtraEnv() decorator. * * @returns DynamicModule */ static forProperty(alias: string): DynamicModule { const token = getPropertyToken(alias); const provider = this.getPropertyProvider(token); return { module: PlatformConfigModule, providers: [provider], exports: [provider], }; } static createConfigProvider(ctor: Constructor): DynamicModule { /** * Factory providers are used because at the point of time when * forFeature() is executed Config instances don't yet exist */ const provider: FactoryProvider = { inject: [ ConfigContext, { optional: true, token: AdditionalSourceInitializer }, ], provide: ctor, useFactory: async ( configContext: ConfigContext, additionalSourceModuleInitializer?: AdditionalSourceInitializer, ) => { /** * Да, каждый раз вызываем метод `initialize` более подробно почему так - смотри в `JSDoc` метода */ await additionalSourceModuleInitializer?.initialize(); return configContext.resolve(ctor); }, }; return { global: true, module: PlatformConfigModule, providers: [provider], exports: [provider], }; } static forRoot(options: PlatformOptions): DynamicModule { const providers: Provider[] = []; const imports: (Promise | DynamicModule)[] = []; if (options.config?.sources) { imports.push(AdditionalSourceModule.forRoot(options.config?.sources)); } return { global: true, imports, module: PlatformConfigModule, providers, exports: providers, }; } private static getPropertyProvider( token: `STANDALONE_CONFIG_PROPERTY_${string}`, ): FactoryProvider { return { inject: [ ConfigContext, { optional: true, token: AdditionalSourceInitializer }, ], provide: token, useFactory: async ( configContext: ConfigContext, additionalSourceModuleInitializer?: AdditionalSourceInitializer, ): Promise => { await additionalSourceModuleInitializer?.initialize(); return configContext.resolveProperty(token); }, }; } /** * Use this method to import specified configuration section * into given module * @param ctor Pass constructor of configuration section class * @returns DynamicModule */ @DeprecateMethod( 'Do not use `PlatformConfigModule.forFeature`, from @rsdk/core 3.1.0 configs are loaded automatically', ) static forFeature(ctor: Constructor): DynamicModule { return PlatformConfigModule.createConfigProvider(ctor); } }