UNPKG

@nestjs-mod/common

Version:

A collection of utilities for unifying NestJS applications and modules

860 lines (859 loc) 84.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getWrapModuleMetadataMethods = getWrapModuleMetadataMethods; exports.getNestModuleInternalUtils = getNestModuleInternalUtils; exports.getNestModuleDecorators = getNestModuleDecorators; exports.createNestModule = createNestModule; exports.collectRootNestModules = collectRootNestModules; const tslib_1 = require("tslib"); /* eslint-disable @typescript-eslint/no-explicit-any */ const common_1 = require("@nestjs/common"); const crypto_1 = require("crypto"); const rxjs_1 = require("rxjs"); const utils_1 = require("../config-model/utils"); const utils_2 = require("../env-model/utils"); const default_context_name_1 = require("../utils/default-context-name"); const detect_provider_name_1 = require("../utils/detect-provider-name"); const is_infrastructure_1 = require("../utils/is-infrastructure"); const name_it_class_1 = require("../utils/name-it-class"); const errors_1 = require("./errors"); const types_1 = require("./types"); // ============= Token Generation Utilities ============= function getWrapModuleMetadataMethods() { const nestModuleMetadataMethods = ['preWrapApplication', 'wrapApplication', 'postWrapApplication']; return nestModuleMetadataMethods; } function getNestModuleInternalUtils({ moduleName }) { function getFeatureEnvironmentsToken(contextName) { return `${moduleName}_${(0, default_context_name_1.defaultContextName)(contextName)}_feature_environments`; } function getAllFeatureEnvironmentsToken() { return `${moduleName}_all_feature_environments`; } function getFeatureConfigurationsToken(contextName) { return `${moduleName}_${(0, default_context_name_1.defaultContextName)(contextName)}_feature_configurations`; } function getAllFeatureConfigurationsToken() { return `${moduleName}_all_feature_configurations`; } function getConfigurationLoaderToken(contextName) { return `${moduleName}_${(0, default_context_name_1.defaultContextName)(contextName)}_configuration_loader`; } function getStaticConfigurationLoaderToken(contextName) { return `${moduleName}_${(0, default_context_name_1.defaultContextName)(contextName)}_static_configuration_loader`; } function getEnvironmentsLoaderToken(contextName) { return `${moduleName}_${(0, default_context_name_1.defaultContextName)(contextName)}_environments_loader`; } function getStaticEnvironmentsLoaderToken(contextName) { return `${moduleName}_${(0, default_context_name_1.defaultContextName)(contextName)}_static_environments_loader`; } function getAsyncConfigurationToken(contextName) { return `${moduleName}_${(0, default_context_name_1.defaultContextName)(contextName)}_options_async_loader`; } function getServiceToken(targetName, contextName) { return `${moduleName}_${(0, default_context_name_1.defaultContextName)(contextName)}_service_${targetName}`; } function getAllModuleSettingsToken() { return `${moduleName}_all_module_settings`; } function getModuleSettingsToken(contextName) { return `${moduleName}_${(0, default_context_name_1.defaultContextName)(contextName)}_module_settings`; } function createUniqProvider() { const uniqValue = (0, crypto_1.randomUUID)(); return { provide: `${uniqValue}_uniq_provider`, useValue: uniqValue, }; } return { defaultContextName: default_context_name_1.defaultContextName, createUniqProvider, getEnvironmentsLoaderToken, getStaticEnvironmentsLoaderToken, getStaticConfigurationLoaderToken, getConfigurationLoaderToken, getServiceToken, getFeatureConfigurationsToken, getAsyncConfigurationToken, getAllFeatureConfigurationsToken, getFeatureEnvironmentsToken, getAllFeatureEnvironmentsToken, getModuleSettingsToken, getAllModuleSettingsToken, }; } // ============= Decorator Factories ============= function getNestModuleDecorators({ moduleName }) { const { getFeatureConfigurationsToken, getServiceToken, getAllFeatureConfigurationsToken, getFeatureEnvironmentsToken, getAllFeatureEnvironmentsToken, getModuleSettingsToken, getAllModuleSettingsToken, } = getNestModuleInternalUtils({ moduleName, }); const InjectAllModuleSettings = () => (0, common_1.Inject)(getAllModuleSettingsToken()); const InjectModuleSettings = (contextName) => (0, common_1.Inject)(getModuleSettingsToken(contextName)); const InjectAllFeatureEnvironments = () => (0, common_1.Inject)(getAllFeatureEnvironmentsToken()); const InjectFeatureEnvironments = (contextName) => (0, common_1.Inject)(getFeatureEnvironmentsToken(contextName)); const InjectAllFeatures = () => (0, common_1.Inject)(getAllFeatureConfigurationsToken()); const InjectFeatures = (contextName) => (0, common_1.Inject)(getFeatureConfigurationsToken(contextName)); const InjectService = (service, contextName) => (target, key, index) => { const token = getServiceToken((0, detect_provider_name_1.detectProviderName)(service), contextName); return (0, common_1.Inject)(token)(target, key, index); }; return { InjectFeatures, InjectService, InjectAllFeatures, InjectFeatureEnvironments, InjectAllFeatureEnvironments, InjectModuleSettings, InjectAllModuleSettings, }; } // ============= Helper Classes ============= class DummyClass { } // ============= Validation Options Helpers ============= function buildValidationOptions({ globalOptions, metadataOptions, asyncOptions, contextName, optionType, }) { const { name: globalName, ...globalRest } = globalOptions || {}; const asyncContextName = asyncOptions?.contextName; const specificOptions = optionType === 'configuration' ? asyncOptions?.configurationOptions : asyncOptions?.environmentsOptions; const metadataSpecificOptions = optionType === 'configuration' ? metadataOptions?.configurationOptions : metadataOptions?.environmentsOptions; const merged = { ...specificOptions, ...metadataSpecificOptions, ...globalRest, }; const nameArr = []; if (globalName && globalName !== (0, default_context_name_1.defaultContextName)()) { nameArr.push(globalName); } if (asyncContextName && asyncContextName !== (0, default_context_name_1.defaultContextName)()) { nameArr.push(asyncContextName); } else if (contextName && contextName !== (0, default_context_name_1.defaultContextName)()) { nameArr.push(contextName); } if (merged.name && merged.name !== (0, default_context_name_1.defaultContextName)()) { nameArr.push(merged.name); } return { ...merged, name: [...new Set(nameArr)].join('_') }; } function getRootConfigurationValidationOptions({ nestModuleMetadata, asyncModuleOptions, contextName, }) { return buildValidationOptions({ globalOptions: nestModuleMetadata.globalConfigurationOptions, metadataOptions: nestModuleMetadata, asyncOptions: asyncModuleOptions, contextName, optionType: 'configuration', }); } function getRootEnvironmentsValidationOptions({ nestModuleMetadata, asyncModuleOptions, contextName, }) { return buildValidationOptions({ globalOptions: nestModuleMetadata.globalEnvironmentsOptions, metadataOptions: nestModuleMetadata, asyncOptions: asyncModuleOptions, contextName, optionType: 'environments', }); } // ============= Module Collection Utility ============= function createNestModule(nestModuleMetadata) { var InternalNestModule_1; // Handle dummy class workaround for staticEnvironmentsModel without staticConfigurationModel if (nestModuleMetadata.staticEnvironmentsModel && !nestModuleMetadata.staticConfigurationModel) { nestModuleMetadata.staticConfigurationModel = DummyClass; } // Initialize logger if not provided if (!nestModuleMetadata.logger) { nestModuleMetadata.logger = new common_1.ConsoleLogger('createNestModule'); } // Initialize method names const forRootMethodName = nestModuleMetadata.forRootMethodName || types_1.DEFAULT_FOR_ROOT_METHOD_NAME; const forRootAsyncMethodName = nestModuleMetadata.forRootAsyncMethodName || types_1.DEFAULT_FOR_ROOT_ASYNC_METHOD_NAME; const forFeatureMethodName = nestModuleMetadata.forFeatureMethodName || types_1.DEFAULT_FOR_FEATURE_METHOD_NAME; const forFeatureAsyncMethodName = nestModuleMetadata.forFeatureAsyncMethodName || types_1.DEFAULT_FOR_FEATURE_ASYNC_METHOD_NAME; // Get internal utilities const { defaultContextName, createUniqProvider, getEnvironmentsLoaderToken, getStaticEnvironmentsLoaderToken, getStaticConfigurationLoaderToken, getConfigurationLoaderToken, getServiceToken, getFeatureConfigurationsToken, getFeatureEnvironmentsToken, getAllFeatureConfigurationsToken, getAllFeatureEnvironmentsToken, getAsyncConfigurationToken, getModuleSettingsToken, getAllModuleSettingsToken, } = getNestModuleInternalUtils({ moduleName: nestModuleMetadata.moduleName, }); // ============= State Management ============= const forFeatureModules = []; const moduleSettings = {}; const modulesByContextName = {}; const featuresConfigurationByContextName = {}; const featuresEnvironmentsByContextName = {}; const settingsModulesByContextName = {}; // ============= Feature Providers ============= const getFeatureProviders = (contextName) => { contextName = defaultContextName(contextName); if (featuresConfigurationByContextName[contextName] === undefined) { featuresConfigurationByContextName[contextName] = []; } if (featuresEnvironmentsByContextName[contextName] === undefined) { featuresEnvironmentsByContextName[contextName] = []; } if (moduleSettings[contextName] === undefined) { moduleSettings[contextName] = {}; } return [ { provide: getAllFeatureConfigurationsToken(), useValue: new Proxy(featuresConfigurationByContextName, { get(target, prop) { if (prop !== undefined) { return featuresConfigurationByContextName[prop]; } else { return featuresConfigurationByContextName; } }, }), }, { provide: getFeatureConfigurationsToken(contextName), useValue: new Proxy(featuresConfigurationByContextName[contextName], { get(target, prop) { contextName = defaultContextName(contextName); if (prop === 'length') { return featuresConfigurationByContextName[contextName].length; } if (prop !== undefined) { return featuresConfigurationByContextName[contextName][prop]; } return target[prop]; }, }), }, // { provide: getAllFeatureEnvironmentsToken(), useValue: new Proxy(featuresEnvironmentsByContextName, { get(target, prop) { if (prop !== undefined) { return featuresEnvironmentsByContextName[prop]; } else { return featuresEnvironmentsByContextName; } }, }), }, { provide: getFeatureEnvironmentsToken(contextName), useValue: new Proxy(featuresEnvironmentsByContextName[contextName], { get(target, prop) { contextName = defaultContextName(contextName); if (prop === 'length') { return featuresEnvironmentsByContextName[contextName].length; } if (prop !== undefined) { return featuresEnvironmentsByContextName[contextName][prop]; } return target[prop]; }, }), }, // { provide: getAllModuleSettingsToken(), useValue: new Proxy(moduleSettings, { get(target, prop) { if (prop !== undefined) { return moduleSettings[prop]; } else { return moduleSettings; } }, }), }, { provide: getModuleSettingsToken(contextName), useValue: new Proxy(moduleSettings[contextName], { get(target, prop) { contextName = defaultContextName(contextName); if (prop !== undefined) { return moduleSettings[contextName]?.[prop]; } else { return moduleSettings[contextName]; } }, }), }, ]; }; // ============= Settings Module ============= const getSettingsModule = (contextName) => { let SettingsModule = class SettingsModule { }; SettingsModule = tslib_1.__decorate([ (0, common_1.Module)({ providers: [ ...(nestModuleMetadata.configurationModel ? [nestModuleMetadata.configurationModel] : []), ...(nestModuleMetadata.staticConfigurationModel ? [nestModuleMetadata.staticConfigurationModel] : []), ...getFeatureProviders(contextName), ...getFeatureProviders(), ...(nestModuleMetadata.environmentsModel ? [nestModuleMetadata.environmentsModel] : []), ...(nestModuleMetadata.staticEnvironmentsModel ? [nestModuleMetadata.staticEnvironmentsModel] : []), ], exports: [ ...(nestModuleMetadata.configurationModel ? [nestModuleMetadata.configurationModel] : []), ...(nestModuleMetadata.staticConfigurationModel ? [nestModuleMetadata.staticConfigurationModel] : []), getAllFeatureConfigurationsToken(), getAllFeatureEnvironmentsToken(), getFeatureConfigurationsToken(contextName), getFeatureConfigurationsToken(), getFeatureEnvironmentsToken(contextName), getFeatureEnvironmentsToken(), getAllModuleSettingsToken(), getModuleSettingsToken(contextName), ...(nestModuleMetadata.environmentsModel ? [nestModuleMetadata.environmentsModel] : []), ...(nestModuleMetadata.staticEnvironmentsModel ? [nestModuleMetadata.staticEnvironmentsModel] : []), ], }) ], SettingsModule); return (0, name_it_class_1.nameItClass)(`${nestModuleMetadata.moduleName}Settings`, SettingsModule); }; const getOrCreateSettingsModule = (contextName) => { contextName = defaultContextName(contextName); if (settingsModulesByContextName[contextName] === undefined) { settingsModulesByContextName[contextName] = getSettingsModule(contextName); } return { contextName, module: settingsModulesByContextName[contextName] }; }; let sharedStaticConfiguration; let sharedStaticEnvironments; // ============= Shared Module ============= const getSharedModule = (contextName, staticConfiguration, staticEnvironments) => { if (sharedStaticConfiguration === undefined) { sharedStaticConfiguration = staticConfiguration; } if (sharedStaticEnvironments === undefined) { sharedStaticEnvironments = staticEnvironments; } const { module: settingsModule } = getOrCreateSettingsModule(contextName); const sharedProvidersArr = !nestModuleMetadata?.sharedProviders || Array.isArray(nestModuleMetadata.sharedProviders) ? nestModuleMetadata?.sharedProviders : nestModuleMetadata.sharedProviders({ project: nestModuleMetadata.project, contextName, staticConfiguration, staticEnvironments, globalConfigurationOptions: getRootConfigurationValidationOptions({ nestModuleMetadata, contextName, }), globalEnvironmentsOptions: getRootEnvironmentsValidationOptions({ nestModuleMetadata, contextName, }), }); const sharedProviders = ((nestModuleMetadata.sharedProviders === undefined ? [] : sharedProvidersArr) || []); const sharedImportsArr = nestModuleMetadata?.sharedImports === undefined || Array.isArray(nestModuleMetadata.sharedImports) ? nestModuleMetadata?.sharedImports : nestModuleMetadata.sharedImports({ project: nestModuleMetadata.project, contextName, settingsModule, staticConfiguration, staticEnvironments, globalConfigurationOptions: getRootConfigurationValidationOptions({ nestModuleMetadata, contextName, }), globalEnvironmentsOptions: getRootEnvironmentsValidationOptions({ nestModuleMetadata, contextName, }), }); const sharedImports = (nestModuleMetadata.sharedImports === undefined ? [] : sharedImportsArr) || []; const exports = []; const providers = sharedProviders .map((sharedService) => { try { const detectedProviderName = (0, detect_provider_name_1.detectProviderName)(sharedService); if (typeof sharedService !== 'string') { if ('provide' in sharedService) { const providers = [ sharedService, { provide: getServiceToken(detectedProviderName, contextName), useExisting: sharedService.provide, }, ]; exports.push(providers[0].provide); exports.push(getServiceToken(detectedProviderName, contextName)); return providers; } if ('name' in sharedService) { const providers = [ sharedService, { provide: getServiceToken(detectedProviderName, contextName), useExisting: sharedService, }, ]; exports.push(sharedService); exports.push(getServiceToken(detectedProviderName, contextName)); return providers; } } exports.push(sharedService); exports.push(getServiceToken(detectedProviderName, contextName)); return [sharedService]; } catch (err) { throw new errors_1.NestModuleError(err.message); } }) .filter(Boolean) .flat(); let SharedModule = class SharedModule { }; SharedModule = tslib_1.__decorate([ (0, common_1.Module)({ imports: [settingsModule, ...sharedImports], providers, exports, }) ], SharedModule); return (0, name_it_class_1.nameItClass)(`${nestModuleMetadata.moduleName}Shared`, SharedModule); }; // ============= Feature Module ============= const getFeatureModule = ({ contextName, staticConfiguration, staticEnvironments, }) => { contextName = defaultContextName(contextName); if (modulesByContextName[contextName] === undefined) { modulesByContextName[contextName] = getSharedModule(contextName, staticConfiguration, staticEnvironments); } if (moduleSettings[contextName] === undefined) { moduleSettings[contextName] = {}; } return { contextName, module: modulesByContextName[contextName], featureConfiguration: undefined, featureEnvironments: undefined, }; }; const getOrCreateFeatureModule = async ({ contextName, featureModuleName, featureConfiguration, featureEnvironments, featureConfigurationOptions, featureEnvironmentsOptions, }) => { contextName = defaultContextName(contextName); if (modulesByContextName[contextName] === undefined) { modulesByContextName[contextName] = getSharedModule(contextName); } if (moduleSettings[contextName] === undefined) { moduleSettings[contextName] = {}; } if (featureConfiguration) { if (featuresConfigurationByContextName[contextName] === undefined) { featuresConfigurationByContextName[contextName] = []; } if (nestModuleMetadata.featureConfigurationModel) { const obj = await (0, utils_1.configTransform)({ model: nestModuleMetadata.featureConfigurationModel, data: featureConfiguration, rootOptions: { ...featureConfigurationOptions, ...getRootConfigurationValidationOptions({ nestModuleMetadata, contextName }), }, }); if (moduleSettings[contextName].featureModuleConfigurations === undefined) { moduleSettings[contextName].featureModuleConfigurations = {}; } if (moduleSettings[contextName].featureModuleConfigurations && moduleSettings[contextName].featureModuleConfigurations[featureModuleName] === undefined) { moduleSettings[contextName].featureModuleConfigurations[featureModuleName] = []; } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion moduleSettings[contextName].featureModuleConfigurations[featureModuleName].push(obj.info); featuresConfigurationByContextName[contextName].push({ featureModuleName, featureConfiguration: obj.data, }); featureConfiguration = obj.data; } else { featuresConfigurationByContextName[contextName].push({ featureModuleName, featureConfiguration }); } } if (featuresEnvironmentsByContextName[contextName] === undefined) { featuresEnvironmentsByContextName[contextName] = []; } if (nestModuleMetadata.featureEnvironmentsModel) { if (featureEnvironments === undefined) { featureEnvironments = {}; } const obj = await (0, utils_2.envTransform)({ model: nestModuleMetadata.featureEnvironmentsModel, data: featureEnvironments, rootOptions: { ...featureEnvironmentsOptions, ...getRootEnvironmentsValidationOptions({ nestModuleMetadata, contextName: defaultContextName() !== contextName ? `${contextName}_${featureModuleName}` : featureModuleName, }), }, }); if (moduleSettings[contextName].featureModuleEnvironments === undefined) { moduleSettings[contextName].featureModuleEnvironments = {}; } if (moduleSettings[contextName].featureModuleEnvironments && moduleSettings[contextName].featureModuleEnvironments[featureModuleName] === undefined) { moduleSettings[contextName].featureModuleEnvironments[featureModuleName] = []; } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion moduleSettings[contextName].featureModuleEnvironments[featureModuleName].push(obj.info); featuresEnvironmentsByContextName[contextName].push({ featureModuleName, featureEnvironments: obj.data, }); featureEnvironments = obj.data; } return { contextName, module: modulesByContextName[contextName], featureConfiguration, featureEnvironments }; }; // ============= Internal Nest Module ============= let InternalNestModule = InternalNestModule_1 = class InternalNestModule { static [forFeatureAsyncMethodName](asyncModuleOptions) { if (!asyncModuleOptions) { asyncModuleOptions = {}; } if (nestModuleMetadata.wrapForFeatureAsync) { const { asyncModuleOptions: newAsyncModuleOptions, module: wrapForFeatureAsyncModule } = nestModuleMetadata.wrapForFeatureAsync(asyncModuleOptions); if (wrapForFeatureAsyncModule) { return wrapForFeatureAsyncModule; } if (asyncModuleOptions && newAsyncModuleOptions) { Object.assign(asyncModuleOptions, newAsyncModuleOptions); } } const contextName = defaultContextName(asyncModuleOptions?.contextName); let { featureConfiguration, featureEnvironments } = asyncModuleOptions || {}; let { featureConfigurationOptions, featureEnvironmentsOptions } = asyncModuleOptions || {}; if (featureConfigurationOptions === undefined) { featureConfigurationOptions = {}; } if (featureEnvironmentsOptions === undefined) { featureEnvironmentsOptions = {}; } async function getModule() { // console.log('getModule:feature', nestModuleMetadata.moduleName); const { module: settingsModule } = getOrCreateSettingsModule(contextName); // Build providers for async feature configuration // These providers handle dynamic configuration loading through different strategies: // - featureConfigurationClass: Uses class with DI support (useClass) // - featureConfigurationFactory: Creates config via factory function with DI // - featureConfigurationStream: Updates config from RxJS Observable stream const featureProviders = []; // Handle async configuration options let resolvedFeatureConfiguration = featureConfiguration; if (asyncModuleOptions) { // Handle class-based configuration with DI support // The class will be instantiated by NestJS DI container with injected dependencies if ('featureConfigurationClass' in asyncModuleOptions && asyncModuleOptions.featureConfigurationClass) { const configClass = asyncModuleOptions.featureConfigurationClass; // Use factory to instantiate class with DI // This allows NestJS to inject dependencies into the config class constructor featureProviders.push({ provide: getFeatureConfigurationsToken(contextName), useFactory: async (...args) => { // Instantiate the class with injected dependencies const instance = new configClass(...args); const config = instance; // Transform and validate if model exists if (config && nestModuleMetadata.featureConfigurationModel) { const obj = await (0, utils_1.configTransform)({ model: nestModuleMetadata.featureConfigurationModel, data: config, rootOptions: { ...featureConfigurationOptions, ...getRootConfigurationValidationOptions({ nestModuleMetadata, contextName }), }, }); // Update module settings for documentation/infrastructure if (moduleSettings[contextName].featureModuleConfigurations === undefined) { moduleSettings[contextName].featureModuleConfigurations = {}; } if (moduleSettings[contextName].featureModuleConfigurations && Array.isArray(moduleSettings[contextName].featureModuleConfigurations)) { moduleSettings[contextName].featureModuleConfigurations.push({ featureModuleName: asyncModuleOptions.featureModuleName, featureConfiguration: obj.data, }); } // Track for runtime access via InjectFeatures decorator if (featuresConfigurationByContextName[contextName] === undefined) { featuresConfigurationByContextName[contextName] = []; } featuresConfigurationByContextName[contextName].push({ featureModuleName: asyncModuleOptions.featureModuleName, featureConfiguration: obj.data, }); return obj.data; } return config; }, // Inject tokens from options - these will be passed to the class constructor inject: 'inject' in asyncModuleOptions ? asyncModuleOptions.inject : [], }); } if ('featureConfigurationExisting' in asyncModuleOptions && asyncModuleOptions.featureConfigurationExisting) { // Use existing instance resolvedFeatureConfiguration = asyncModuleOptions.featureConfigurationExisting; } } const { module: featureModule } = await getOrCreateFeatureModule({ contextName, featureModuleName: asyncModuleOptions.featureModuleName, featureConfiguration: resolvedFeatureConfiguration, featureEnvironments, featureConfigurationOptions, featureEnvironmentsOptions, }); const importsArr = asyncModuleOptions?.imports === undefined || Array.isArray(asyncModuleOptions.imports) ? asyncModuleOptions?.imports : asyncModuleOptions.imports({ project: nestModuleMetadata.project, contextName, settingsModule, featureModule, featureConfiguration, featureEnvironments, globalConfigurationOptions: getRootConfigurationValidationOptions({ nestModuleMetadata, asyncModuleOptions: { configurationOptions: featureConfigurationOptions }, contextName, }), globalEnvironmentsOptions: getRootEnvironmentsValidationOptions({ nestModuleMetadata, asyncModuleOptions: { environmentsOptions: featureEnvironmentsOptions }, contextName, }), }); const imports = (nestModuleMetadata.imports === undefined ? [] : importsArr) || []; const exportsArr = nestModuleMetadata.exports === undefined || Array.isArray(nestModuleMetadata.exports) ? nestModuleMetadata.exports : nestModuleMetadata.exports({ project: nestModuleMetadata.project, contextName, settingsModule, featureModule, featureConfiguration, featureEnvironments, globalConfigurationOptions: getRootConfigurationValidationOptions({ nestModuleMetadata, contextName, }), globalEnvironmentsOptions: getRootEnvironmentsValidationOptions({ nestModuleMetadata, contextName, }), }); const exports = (nestModuleMetadata.exports === undefined ? [] : exportsArr) || []; if (asyncModuleOptions) { // Handle factory-based configuration if ('featureConfigurationFactory' in asyncModuleOptions && asyncModuleOptions.featureConfigurationFactory) { featureProviders.push({ provide: getFeatureConfigurationsToken(contextName), useFactory: async (...args) => { // Call factory to get configuration data const config = await asyncModuleOptions.featureConfigurationFactory(...args); // Transform and validate if model exists if (config && nestModuleMetadata.featureConfigurationModel) { const obj = await (0, utils_1.configTransform)({ model: nestModuleMetadata.featureConfigurationModel, data: config, rootOptions: { ...featureConfigurationOptions, ...getRootConfigurationValidationOptions({ nestModuleMetadata, contextName }), }, }); // Update module settings for documentation/infrastructure if (moduleSettings[contextName].featureModuleConfigurations === undefined) { moduleSettings[contextName].featureModuleConfigurations = {}; } if (moduleSettings[contextName].featureModuleConfigurations && moduleSettings[contextName].featureModuleConfigurations[asyncModuleOptions.featureModuleName] === undefined) { moduleSettings[contextName].featureModuleConfigurations[asyncModuleOptions.featureModuleName] = []; } moduleSettings[contextName].featureModuleConfigurations[asyncModuleOptions.featureModuleName].push(obj.info); // Add to feature configurations array for runtime access if (featuresConfigurationByContextName[contextName] === undefined) { featuresConfigurationByContextName[contextName] = []; } featuresConfigurationByContextName[contextName].push({ featureModuleName: asyncModuleOptions.featureModuleName, featureConfiguration: obj.data, }); return obj.data; } return config; }, inject: 'inject' in asyncModuleOptions ? asyncModuleOptions.inject : [], }); } // Handle stream-based configuration for real-time updates if ('featureConfigurationStream' in asyncModuleOptions && asyncModuleOptions.featureConfigurationStream) { // Create lifecycle manager to cleanup stream subscription on module destroy let OnModuleDestroyService = class OnModuleDestroyService { constructor() { this.unsubscribe$ = new rxjs_1.Subject(); } onModuleDestroy() { this.unsubscribe$.next(true); this.unsubscribe$.complete(); } }; OnModuleDestroyService = tslib_1.__decorate([ (0, common_1.Injectable)() ], OnModuleDestroyService); featureProviders.push(OnModuleDestroyService, { provide: getFeatureConfigurationsToken(contextName), useFactory: async (...args) => { if (asyncModuleOptions?.featureConfigurationStream) { const stream = asyncModuleOptions.featureConfigurationStream(args); if ((0, rxjs_1.isObservable)(stream)) { // Find lifecycle manager from injected args const destroyService = args.find((arg) => arg instanceof OnModuleDestroyService); let initialValue = undefined; if (destroyService) { // Subscribe to stream with automatic cleanup on module destroy // Wait for first value to ensure initial config is set await new Promise((resolve) => { stream .pipe((0, rxjs_1.takeUntil)(destroyService.unsubscribe$)) .subscribe({ next: async (config) => { if (config && nestModuleMetadata.featureConfigurationModel) { try { // Store first value to return from factory if (initialValue === undefined) { initialValue = config; } // Transform and validate each stream emission const obj = await (0, utils_1.configTransform)({ model: nestModuleMetadata.featureConfigurationModel, data: config, rootOptions: { ...featureConfigurationOptions, ...getRootConfigurationValidationOptions({ nestModuleMetadata, contextName }), }, }); // Update module settings for documentation if (moduleSettings[contextName].featureModuleConfigurations === undefined) { moduleSettings[contextName].featureModuleConfigurations = {}; } if (moduleSettings[contextName].featureModuleConfigurations && moduleSettings[contextName].featureModuleConfigurations[asyncModuleOptions.featureModuleName] === undefined) { moduleSettings[contextName].featureModuleConfigurations[asyncModuleOptions.featureModuleName] = []; } moduleSettings[contextName].featureModuleConfigurations[asyncModuleOptions.featureModuleName].push(obj.info); // Update runtime feature configurations if (featuresConfigurationByContextName[contextName] === undefined) { featuresConfigurationByContextName[contextName] = []; } featuresConfigurationByContextName[contextName].push({ featureModuleName: asyncModuleOptions.featureModuleName, featureConfiguration: obj.data, }); // Resolve promise after first value processed if (initialValue === config) { resolve(); } } catch (error) { // Log validation errors but keep stream alive nestModuleMetadata.logger?.error?.(`Feature configuration stream error: ${error}`); if (initialValue === config) { resolve(); } } } }, error: (error) => { // Handle stream errors without breaking nestModuleMetadata.logger?.error?.(`Feature configuration stream error: ${error}`); resolve(); }, }); }); } return initialValue; } } return undefined; }, inject: [ ...('inject' in asyncModuleOptions ? asyncModuleOptions.inject : []), OnModuleDestroyService, ], }); } } return { module: (0, name_it_class_1.nameItClass)(nestModuleMetadata.moduleName, InternalNestModule_1), providers: [createUniqProvider(), ...featureProviders], imports: [settingsModule, featureModule, ...imports], exports: [...exports, settingsModule, featureModule], }; } // hack for disable auto resolving promise in nodejs // console.log('getModule:feature:before', nestModuleMetadata.moduleName); const result = Promise.resolve().then(() => getModule()); // console.log('getModule:feature:after', nestModuleMetadata.moduleName); const nestModuleMetadataMethods = getWrapModuleMetadataMethods(); Object.assign(result, { getNestModuleMetadata: () => { return { ...nestModuleMetadata, project: nestModuleMetadata.project || {}, ...nestModuleMetadataMethods .map((method) => ({ [method]: undefined })) .reduce((all, cur) => ({ ...all, ...cur }), {}), }; }, moduleSettings, // need to set global options for configurations and environments // todo: try remove it pathNestModuleMetadata: (newNestModuleMetadata) => { const keys = Object.keys(newNestModuleMetadata); const globalConfigurationOptions = newNestModuleMetadata.globalConfigurationOptions; const globalEnvironmentsOptions = newNestModuleMetadata.globalEnvironmentsOptions; const logger = newNestModuleMetadata.logger; if (featureConfigurationOptions && nestModuleMetadata?.globalConfigurationOptions) { Object.assign(featureConfigurationOptions, nestModuleMetadata.globalConfigurationOptions || {}); } if (featureEnvironmentsOptions && nestModuleMetadata?.globalEnvironmentsOptions) { Object.assign(featureEnvironmentsOptions, nestModuleMetadata.globalEnvironmentsOptions || {}); } for (const key of keys) { if (newNestModuleMetadata[key].constructor !== Object) { Object.setPrototypeOf(nestModuleMetadata[key], newNestModuleMetadata[key]); } if (nestModuleMetadata[key]) { Object.assign(nestModuleMetadata[key], newNestModuleMetadata[key] || {}); } else { nestModuleMetadata[key] = newNestModuleMetadata[key]; } } const imports = nestModuleMetadata?.imports || []; if (Array.isArray(imports)) { for (const imp of imports) { if ('pathNestModuleMetadata' in imp && imp.pathNestModuleMetadata) { imp.pathNestModuleMetadata({ ...(newNestModuleMetadata.project ? { project: newNestModuleMetadata.project } : {}), ...(globalConfigurationOptions ? { globalConfigurationOptions } : {}), ...(globalEnvironmentsOptions ? { globalEnvironmentsOptions } : {}), ...(logger ? { logger } : {}), }); } } } const sharedImports = nestModuleMetadata?.sharedImports || []; if (Array.isArray(sharedImports)) { for (const imp of sharedImports) { if ('pathNestModuleMetadata' in imp && imp.pathNestModuleMetadata) { imp.pathNestModuleMetadata({ ...(newNestModuleMetadata.project ? { project: newNestModuleMetadata.project } : {}), ...(globalConfigurationOptions ? { globalConfigurationOptions } : {}),