Skip to content

Conversation

@Evanion
Copy link
Member

@Evanion Evanion commented Jun 30, 2025

This PR refactors the DynamicModule feature to simplify creating new modules with config values, and better DX when consuming modules, with better typechecking

Evanion added 2 commits June 30, 2025 12:03
- Remove static generics from DynamicModule config/configAsync
- Add createModuleConfig and createModuleConfigAsync helpers
- Update tests for strict type safety and better DX
@Evanion Evanion linked an issue Jun 30, 2025 that may be closed by this pull request
cursor[bot]

This comment was marked as outdated.

@Evanion Evanion force-pushed the 36-dynamicmodule-refactor branch from a55b684 to c705650 Compare July 1, 2025 01:00
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Module Provider Merging and Async Factory Type Issues

The createModuleConfig function introduces two issues:

  1. It fails to merge providers defined in the base @Module decorator with the dynamically configured provider. Unlike the previous implementation, it only returns the new config provider, discarding existing module providers.
  2. There is a type signature mismatch for async factory providers. When a ProviderConfigObject<Promise<T>> is passed as a factory, the function's implementation returns a synchronous ModuleConfig instead of the expected Promise<ModuleConfig>, violating its declared overload.

libs/core/src/dynamic-module.ts#L49-L131

*/
// Overload for sync configs (non-promise values)
export function createModuleConfig<T>(
moduleClass: { configToken: TokenType<T> },
config: T | ProviderConfigObject<T>
): ModuleConfig;
// Overload for async configs (promises)
export function createModuleConfig<T>(
moduleClass: { configToken: TokenType<T> },
config: Promise<T> | ProviderConfigObject<Promise<T>>
): Promise<ModuleConfig>;
// Implementation
export function createModuleConfig<T>(
moduleClass: { configToken: TokenType<T> },
config:
| T
| ProviderConfigObject<T>
| Promise<T>
| ProviderConfigObject<Promise<T>>
): ModuleConfig | Promise<ModuleConfig> {
// If config is a factory provider
if (isFactory(config)) {
// Don't execute the factory here - let the DI container handle it
// Just pass the factory configuration through with the token
return {
providers: [
{
...config,
token: moduleClass.configToken,
},
],
};
}
// If config is a provider config (but not a factory)
if (isProvider(config)) {
// If useValue is a promise
if ('useValue' in config && isPromise(config.useValue)) {
return Promise.resolve(config.useValue).then((resolved) => ({
providers: [
{
...config,
useValue: resolved,
token: moduleClass.configToken,
},
],
}));
}
// Otherwise, sync provider config
return {
providers: [
{
...config,
token: moduleClass.configToken,
},
],
};
}
// If config is a Promise
if (isPromise(config)) {
return Promise.resolve(config).then((resolved) => ({
providers: [
{
useValue: resolved,
token: moduleClass.configToken,
},
],
}));
}
// Otherwise, it's a plain config object
return {
providers: [
{
useValue: config,
token: moduleClass.configToken,
},
],
};
}

Fix in Cursor


Was this report helpful? Give feedback by reacting with 👍 or 👎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DynamicModule Refactor

2 participants