diff --git a/packages/core/injector/compiler.ts b/packages/core/injector/compiler.ts index 353e2758104..f7c0869a858 100644 --- a/packages/core/injector/compiler.ts +++ b/packages/core/injector/compiler.ts @@ -26,17 +26,17 @@ export class ModuleCompiler { metatype: Type | ForwardReference | DynamicModule, ): { type: Type; - dynamicMetadata?: Partial | undefined; + dynamicMetadata?: DynamicModule | undefined; } { if (!this.isDynamicModule(metatype)) { return { type: (metatype as ForwardReference)?.forwardRef ? (metatype as ForwardReference).forwardRef() : metatype, + dynamicMetadata: undefined, }; } - const { module: type, ...dynamicMetadata } = metatype; - return { type, dynamicMetadata }; + return { type: metatype.module, dynamicMetadata: metatype }; } public isDynamicModule( diff --git a/packages/core/injector/index.ts b/packages/core/injector/index.ts index 21e406d0661..322517c78e2 100644 --- a/packages/core/injector/index.ts +++ b/packages/core/injector/index.ts @@ -4,3 +4,4 @@ export { ContextId, HostComponentInfo } from './instance-wrapper'; export * from './lazy-module-loader/lazy-module-loader'; export * from './module-ref'; export * from './modules-container'; +export { UniqueDynamicModuleFactory } from './unique-dynamic-module-factory'; diff --git a/packages/core/injector/module-token-factory.ts b/packages/core/injector/module-token-factory.ts index 6fbebe4e707..40e1f31349b 100644 --- a/packages/core/injector/module-token-factory.ts +++ b/packages/core/injector/module-token-factory.ts @@ -2,6 +2,10 @@ import { DynamicModule, Logger } from '@nestjs/common'; import { Type } from '@nestjs/common/interfaces/type.interface'; import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util'; import { isFunction, isSymbol } from '@nestjs/common/utils/shared.utils'; +import { + getUniqueDynamicModuleId, + isUniqueDynamicModule, +} from './unique-dynamic-module-factory'; import { createHash } from 'crypto'; import stringify from 'fast-safe-stringify'; import { performance } from 'perf_hooks'; @@ -18,8 +22,12 @@ export class ModuleTokenFactory { public create( metatype: Type, - dynamicModuleMetadata?: Partial | undefined, + dynamicModuleMetadata?: DynamicModule | undefined, ): string { + if (isUniqueDynamicModule(dynamicModuleMetadata)) { + return getUniqueDynamicModuleId(dynamicModuleMetadata); + } + const moduleId = this.getModuleId(metatype); if (!dynamicModuleMetadata) { diff --git a/packages/core/injector/unique-dynamic-module-factory.ts b/packages/core/injector/unique-dynamic-module-factory.ts new file mode 100644 index 00000000000..4f087ad517a --- /dev/null +++ b/packages/core/injector/unique-dynamic-module-factory.ts @@ -0,0 +1,63 @@ +import { DynamicModule, Type } from '@nestjs/common'; +import { RuntimeException } from '@nestjs/core/errors/exceptions'; + +const kUniqueModuleId = Symbol('kUniqueModuleId'); + +export class UniqueDynamicModuleFactory { + protected static readonly uniqueMap = new Map(); + + /** + * You can use this method to avoid the hash algorithm during the DI compilation. + * + * Imagine you have a module that exports a provider, and that provider is + * imported using `MyModule.forRoot('myToken')`, and you can get the reference of this provider using + * injected using @Inject('myToken'). If you import this module twice, with different arguments on `.forRoot`, + * in order to register both modules, we need to serialize and hash the dynamic module created by `.forRoot`. + * Otherwise, it overrides the providers defined in the first import with the providers declared on the second import. + * + * This function tells the algorithm to skip the hash and directly use the `staticUniqueId`, and + *you are responsible for making it unique. + * + * @param staticUniqueId The unique ID across all modules. + * @param dynamicModule The dynamic module. + * + * @throws RuntimeException If the ID is already registered, an error will occur. + */ + static wrap(staticUniqueId: string, dynamicModule: DynamicModule) { + if (!this.uniqueMap.has(staticUniqueId)) { + dynamicModule[ + kUniqueModuleId + ] = `${dynamicModule.module.name}_${staticUniqueId}`; + this.uniqueMap.set(staticUniqueId, dynamicModule.module.name); + return dynamicModule; + } + + throw new RuntimeException( + `A module with this ID was already added before for "${ + this.uniqueMap.get(staticUniqueId).split('_')[0] + }".`, + ); + } +} + +/** + * Check if the given dynamic module was marked as unique. + * + * @param dynamicModule The dynamic module + */ +export function isUniqueDynamicModule( + dynamicModule: DynamicModule | Type | undefined, +): boolean { + return dynamicModule && dynamicModule[kUniqueModuleId] !== undefined; +} + +/** + * Get the stored unique dynamic module id. + * + * @param dynamicModule The dynamic module + */ +export function getUniqueDynamicModuleId( + dynamicModule: DynamicModule | Type, +): string | undefined { + return dynamicModule[kUniqueModuleId]; +}