feat: implement Dynamic Modules support (sync and async)
Allows modules to be configured at runtime via static methods returning DynamicModule objects, similar to NestJS forRoot/register patterns.
This commit is contained in:
@@ -2,8 +2,10 @@ import "reflect-metadata";
|
||||
import { CircularDependencyError, ProviderNotFoundError } from "../errors";
|
||||
import { ContextualModuleRef, ModuleRef } from "../module/ModuleRef";
|
||||
import type { Constructor } from "../types";
|
||||
import type { ForwardRefFn } from "./forwardRef";
|
||||
import {
|
||||
type BaseProvider,
|
||||
type DynamicModule,
|
||||
INJECT_METADATA_KEY,
|
||||
INJECTABLE_METADATA_KEY,
|
||||
MODULE_METADATA_KEY,
|
||||
@@ -40,34 +42,95 @@ export class Container {
|
||||
this.ensureScope(this.globalContext);
|
||||
}
|
||||
|
||||
public registerRootModule(moduleClass: Constructor): void {
|
||||
this.rootModuleName = moduleClass.name;
|
||||
this.registerModule(moduleClass);
|
||||
public async registerRootModule(
|
||||
module: Constructor | DynamicModule,
|
||||
): Promise<void> {
|
||||
const moduleClass = (module as DynamicModule).module || module;
|
||||
this.rootModuleName = (moduleClass as Constructor).name;
|
||||
await this.registerModule(module);
|
||||
}
|
||||
|
||||
public getRootModuleName(): string | undefined {
|
||||
return this.rootModuleName;
|
||||
}
|
||||
|
||||
public registerModule(moduleClass: Constructor): void {
|
||||
const options: ModuleOptions | undefined = Reflect.getMetadata(
|
||||
public async registerModule(
|
||||
module:
|
||||
| Constructor
|
||||
| DynamicModule
|
||||
| Promise<DynamicModule>
|
||||
| ForwardRefFn<Constructor>,
|
||||
): Promise<void> {
|
||||
const unwrappedModule = (await (typeof module === "object" &&
|
||||
module !== null &&
|
||||
"forwardRef" in (module as object)
|
||||
? (
|
||||
module as { forwardRef: () => Constructor | DynamicModule }
|
||||
).forwardRef()
|
||||
: module)) as Constructor | DynamicModule;
|
||||
|
||||
const isDynamic =
|
||||
typeof unwrappedModule === "object" && "module" in unwrappedModule;
|
||||
const moduleClass = isDynamic
|
||||
? (unwrappedModule as DynamicModule).module
|
||||
: (unwrappedModule as Constructor);
|
||||
const dynamicOptions: ModuleOptions = isDynamic
|
||||
? (unwrappedModule as DynamicModule)
|
||||
: {};
|
||||
|
||||
const metadataOptions: ModuleOptions | undefined = Reflect.getMetadata(
|
||||
MODULE_METADATA_KEY,
|
||||
moduleClass,
|
||||
);
|
||||
if (!options) return;
|
||||
|
||||
if (!metadataOptions && !isDynamic) return;
|
||||
|
||||
const moduleName = moduleClass.name;
|
||||
if (this.moduleOptions.has(moduleName)) return;
|
||||
|
||||
const rawImports = [
|
||||
...(metadataOptions?.imports || []),
|
||||
...(dynamicOptions?.imports || []),
|
||||
];
|
||||
|
||||
const resolvedImports = await Promise.all(
|
||||
rawImports.map(async (imp) => {
|
||||
return await (typeof imp === "object" &&
|
||||
imp !== null &&
|
||||
"forwardRef" in (imp as object)
|
||||
? (
|
||||
imp as {
|
||||
forwardRef: () => Constructor | DynamicModule;
|
||||
}
|
||||
).forwardRef()
|
||||
: imp);
|
||||
}),
|
||||
);
|
||||
|
||||
const options: ModuleOptions = {
|
||||
...metadataOptions,
|
||||
...dynamicOptions,
|
||||
imports: resolvedImports,
|
||||
providers: [
|
||||
...(metadataOptions?.providers || []),
|
||||
...(dynamicOptions?.providers || []),
|
||||
],
|
||||
exports: [
|
||||
...(metadataOptions?.exports || []),
|
||||
...(dynamicOptions?.exports || []),
|
||||
],
|
||||
};
|
||||
|
||||
this.moduleOptions.set(moduleName, options);
|
||||
this.ensureScope(moduleName);
|
||||
|
||||
if (options.imports) {
|
||||
for (const imported of options.imports) {
|
||||
this.registerModule(
|
||||
this.unwrapToken(imported as ProviderToken<Constructor>),
|
||||
);
|
||||
}
|
||||
for (const imported of resolvedImports) {
|
||||
await this.registerModule(
|
||||
imported as
|
||||
| Constructor
|
||||
| DynamicModule
|
||||
| Promise<DynamicModule>,
|
||||
);
|
||||
}
|
||||
|
||||
if (options.providers) {
|
||||
@@ -292,15 +355,16 @@ export class Container {
|
||||
const options = this.moduleOptions.get(context);
|
||||
if (options?.imports) {
|
||||
for (const imported of options.imports) {
|
||||
const unwrappedImport = this.unwrapToken(
|
||||
imported as ProviderToken<Constructor>,
|
||||
);
|
||||
const importedProviders = this.providers.get(
|
||||
unwrappedImport.name,
|
||||
);
|
||||
const importedOptions = this.moduleOptions.get(
|
||||
unwrappedImport.name,
|
||||
);
|
||||
const importedModule =
|
||||
(imported as DynamicModule).module ||
|
||||
(imported as Constructor);
|
||||
|
||||
if (typeof importedModule !== "function") continue;
|
||||
|
||||
const moduleName = importedModule.name;
|
||||
const importedProviders = this.providers.get(moduleName);
|
||||
const importedOptions = this.moduleOptions.get(moduleName);
|
||||
|
||||
if (
|
||||
importedProviders?.has(unwrappedToken) &&
|
||||
importedOptions?.exports?.some(
|
||||
@@ -310,7 +374,7 @@ export class Container {
|
||||
) {
|
||||
return importedProviders.get(unwrappedToken);
|
||||
}
|
||||
if (unwrappedToken === unwrappedImport) {
|
||||
if (unwrappedToken === importedModule) {
|
||||
return importedProviders?.get(unwrappedToken);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user