You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This RFC proposes a refactor of the DynamicModule system in NexusDI to provide a type-safe, ergonomic, and unified API for module configuration. The goal is to eliminate boilerplate for module authors, support both static and DI-injected factory config, and ensure type safety for application developers.
Motivation
Reduce boilerplate: Module authors should only declare the config token and expected config type, not write custom static methods.
Type safety: The config passed to .config() or .configAsync() should be type-checked against the module’s expected config type.
Support DI-injected factories: Allow config to be provided via a factory function with dependencies (like useFactory + deps in providers).
Async support: Support async config factories and enforce await before registration.
Consistency: All modules use the same config pattern, making documentation and onboarding easier.
Optional global flag: Allow modules/providers to be marked as global for cross-cutting concerns.
Proposed Features
Generic, type-safe config API: DynamicModule<TConfig> enforces the config type for .config() and .configAsync().
Unified config method:
The base class provides a static config() method that supports:
Static config objects
Factory config with DI (useFactory + deps)
Async factories
No custom static methods:
Module authors only declare the config token and expected config type.
Global module/provider support:
Optionally, allow modules/providers to be marked as global.
Container integration:
The container accepts both plain module classes and module config objects, and enforces await for async config.
Example Usage
Module Author
exportinterfaceITypeOrmConfig{type: string;host: string;port: number;// ...other options}exportconstDB_CONFIG=Symbol('DB_CONFIG');
@Module({providers: [TypeOrmService],})exportclassTypeOrmModuleextendsDynamicModule<ITypeOrmConfig>{staticconfigToken=DB_CONFIG;// No need to write a static config() method!}
Application Developer
import{ConfigService}from'@nexusdi/config';
@Module({imports: [// Static config (type-checked)TypeOrmModule.config({type: 'postgres',host: 'localhost',port: 5432,}),// Factory config with DI (type-checked)TypeOrmModule.config({useFactory: (config: ConfigService)=>({type: 'postgres',host: config.get('db.host'),port: config.get('db.port'),}),deps: [ConfigService]}),// Async factory config with DI (type-checked)awaitTypeOrmModule.configAsync({useFactory: async(config: ConfigService)=>{consthost=awaitconfig.getAsync('db.host');return{type: 'postgres',
host,port: 5432,};},deps: [ConfigService]}),]})exportclassAppModule{}
Under the Hood
The base DynamicModule provides a static, generic config() and configAsync() method.
These methods accept:
A static config object (TConfig)
An object with useFactory and deps (dependencies to inject)
The config value is registered under the module’s configToken.
The container enforces that async configs are awaited before registration.
API Sketch
exportabstractclassDynamicModule<TConfig=any>{staticconfigToken: TokenType<TConfig>;staticconfig(config:
|TConfig|ProviderConfigObject<TConfig>): ModuleConfig{// Implementation: register provider for configToken// with useValue or useFactory/inject}staticasyncconfigAsync(configProvider: ProviderConfigObject<Promise<TConfig>>): Promise<ModuleConfig>{// Implementation: resolve deps, call useFactory, return ModuleConfig}}
Complexity & Implementation Review
What’s Already Supported (Low Complexity to Keep/Refine)
Type-safe config via generics: Already present; just needs to be enforced and documented.
Static config objects: Already supported via .config(config: TConfig).
Async config factories: Already supported via .configAsync(() => Promise<TConfig>), though not with DI.
What Would Add Some Complexity (But Is Reasonable)
Factory Config with DI (useFactory + deps):
Requires the container to resolve dependencies before calling the factory.
Pattern is well-established (used in NestJS, Angular, etc.).
Adds minimal bloat if you already have a DI system.
Global Module/Provider Support:
Only complex if you want true encapsulation; a simple flag is low bloat.
Validation Hooks:
Low complexity if optional and opt-in.
What Would Add Significant Bloat/Complexity (Not Recommended)
Registering Promises Directly in the Container:
Would complicate the container’s internal state and error handling.
Recommendation: Do not support; enforce await before set().
Live/Observable Config Injection:
Would require proxies, subscriptions, and possibly re-injection or re-binding.
Not needed for most use cases; can be opt-in via a separate utility.
help wantedExtra attention is neededquestionFurther information is requested
1 participant
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Overview
This RFC proposes a refactor of the
DynamicModulesystem in NexusDI to provide a type-safe, ergonomic, and unified API for module configuration. The goal is to eliminate boilerplate for module authors, support both static and DI-injected factory config, and ensure type safety for application developers.Motivation
.config()or.configAsync()should be type-checked against the module’s expected config type.useFactory+depsin providers).awaitbefore registration.Proposed Features
DynamicModule<TConfig>enforces the config type for.config()and.configAsync().The base class provides a static
config()method that supports:useFactory+deps)Module authors only declare the config token and expected config type.
Optionally, allow modules/providers to be marked as global.
The container accepts both plain module classes and module config objects, and enforces
awaitfor async config.Example Usage
Module Author
Application Developer
Under the Hood
DynamicModuleprovides a static, genericconfig()andconfigAsync()method.TConfig)useFactoryanddeps(dependencies to inject)configToken.API Sketch
Complexity & Implementation Review
What’s Already Supported (Low Complexity to Keep/Refine)
.config(config: TConfig)..configAsync(() => Promise<TConfig>), though not with DI.What Would Add Some Complexity (But Is Reasonable)
useFactory+deps):What Would Add Significant Bloat/Complexity (Not Recommended)
awaitbeforeset().Summary Table
useFactory)Migration Notes
config()/configAsync()methods.config()/configAsync()API.awaitthe result ofconfigAsync()before passing tocontainer.set().Open Questions
.config()or require.configAsync()for async?await?Call for Feedback
Beta Was this translation helpful? Give feedback.
All reactions