Merge pull request #15705 from mag123c/fix/configurable-module-async-extras

fix(core): resolve extras in configurable module builder async methods
This commit is contained in:
Kamil Mysliwiec
2025-10-21 10:29:02 +02:00
committed by GitHub
3 changed files with 64 additions and 2 deletions

View File

@@ -4,6 +4,7 @@ import { Logger } from '../services/logger.service';
import { randomStringGenerator } from '../utils/random-string-generator.util';
import {
ASYNC_METHOD_SUFFIX,
ASYNC_OPTIONS_METADATA_KEYS,
CONFIGURABLE_MODULE_ID,
DEFAULT_FACTORY_CLASS_METHOD_KEY,
DEFAULT_METHOD_KEY,
@@ -254,7 +255,7 @@ export class ConfigurableModuleBuilder<
},
{
...self.extras,
...options,
...this.extractExtrasFromAsyncOptions(options, self.extras),
},
);
}
@@ -277,8 +278,28 @@ export class ConfigurableModuleBuilder<
return moduleOptions as ModuleOptions;
}
private static extractExtrasFromAsyncOptions(
input: ConfigurableModuleAsyncOptions<ModuleOptions> &
ExtraModuleDefinitionOptions,
extras: ExtraModuleDefinitionOptions | undefined,
): Partial<ExtraModuleDefinitionOptions> {
if (!extras) {
return {};
}
const extrasOptions = {};
Object.keys(input as object)
.filter(key => !ASYNC_OPTIONS_METADATA_KEYS.includes(key as any))
.forEach(key => {
extrasOptions[key] = input[key];
});
return extrasOptions;
}
private static createAsyncProviders(
options: ConfigurableModuleAsyncOptions<ModuleOptions>,
options: ConfigurableModuleAsyncOptions<ModuleOptions> &
ExtraModuleDefinitionOptions,
): Provider[] {
if (options.useExisting || options.useFactory) {
if (options.inject && options.provideInjectionTokensFrom) {

View File

@@ -3,3 +3,16 @@ export const DEFAULT_FACTORY_CLASS_METHOD_KEY = 'create';
export const ASYNC_METHOD_SUFFIX = 'Async';
export const CONFIGURABLE_MODULE_ID = 'CONFIGURABLE_MODULE_ID';
/**
* List of keys that are specific to ConfigurableModuleAsyncOptions
* and should be excluded when extracting user-defined extras.
*/
export const ASYNC_OPTIONS_METADATA_KEYS = [
'useFactory',
'useClass',
'useExisting',
'inject',
'imports',
'provideInjectionTokensFrom',
] as const;

View File

@@ -24,6 +24,34 @@ describe('ConfigurableModuleBuilder', () => {
global: true,
});
});
it('should preserve extras in registerAsync transformation', () => {
let capturedExtras: any;
const { ConfigurableModuleClass } = new ConfigurableModuleBuilder()
.setExtras(
{ folder: 'default' },
(definition, extras: { folder: string }) => {
capturedExtras = extras;
return {
...definition,
customProperty: `folder: ${extras.folder}`,
};
},
)
.build();
const asyncResult = ConfigurableModuleClass.registerAsync({
useFactory: () => ({}),
folder: 'forRootAsync',
});
expect(capturedExtras).to.deep.equal({ folder: 'forRootAsync' });
expect(asyncResult).to.have.property(
'customProperty',
'folder: forRootAsync',
);
});
});
describe('setClassMethodName', () => {
it('should set static class method name and return typed builder', () => {