• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nestjs / nest / e1a120dc-6519-4c6a-a074-de38a9ffd346

14 Feb 2025 09:25PM UTC coverage: 27.513% (-61.8%) from 89.294%
e1a120dc-6519-4c6a-a074-de38a9ffd346

Pull #14640

circleci

luddwichr
fix(platform-express) respect existing parser middlewares when using Express 5

Express 5 made the router public API again and renamed the field from app._router to app.router.
This broke the detection mechanism whether a middleware named "jsonParser" or "urlencodedParser"
is already registered or not.
Unfortunately, https://github.com/nestjs/nest/pull/14574/ only fixed the issue partially.
This commit now uses app.router everywhere.
To avoid future regressions a test was added to verify the expected behavior.
Pull Request #14640: fix(platform-express) respect custom parser middlewares in Express 5

250 of 3354 branches covered (7.45%)

2201 of 8000 relevant lines covered (27.51%)

0.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

8.2
/packages/common/module-utils/configurable-module.builder.ts
1
/* eslint-disable @typescript-eslint/no-empty-object-type */
2
import { DynamicModule, Provider } from '../interfaces';
3
import { Logger } from '../services/logger.service';
1✔
4
import { randomStringGenerator } from '../utils/random-string-generator.util';
1✔
5
import {
1✔
6
  ASYNC_METHOD_SUFFIX,
7
  CONFIGURABLE_MODULE_ID,
8
  DEFAULT_FACTORY_CLASS_METHOD_KEY,
9
  DEFAULT_METHOD_KEY,
10
} from './constants';
11
import {
12
  ConfigurableModuleAsyncOptions,
13
  ConfigurableModuleCls,
14
  ConfigurableModuleHost,
15
  ConfigurableModuleOptionsFactory,
16
} from './interfaces';
17
import { generateOptionsInjectionToken, getInjectionProviders } from './utils';
1✔
18

19
/**
20
 * @publicApi
21
 */
22
export interface ConfigurableModuleBuilderOptions {
23
  /**
24
   * Specifies what injection token should be used for the module options provider.
25
   * By default, an auto-generated UUID will be used.
26
   */
27
  optionsInjectionToken?: string | symbol;
28
  /**
29
   * By default, an UUID will be used as a module options provider token.
30
   * Explicitly specifying the "moduleName" will instruct the "ConfigurableModuleBuilder"
31
   * to use a more descriptive provider token.
32
   *
33
   * For example, `moduleName: "Cache"` will auto-generate the provider token: "CACHE_MODULE_OPTIONS".
34
   */
35
  moduleName?: string;
36
  /**
37
   * Indicates whether module should always be "transient" - meaning,
38
   * every time you call the static method to construct a dynamic module,
39
   * regardless of what arguments you pass in, a new "unique" module will be created.
40
   *
41
   * @default false
42
   */
43
  alwaysTransient?: boolean;
44
}
45

46
/**
47
 * Factory that lets you create configurable modules and
48
 * provides a way to reduce the majority of dynamic module boilerplate.
49
 *
50
 * @publicApi
51
 */
52
export class ConfigurableModuleBuilder<
1✔
53
  ModuleOptions,
54
  StaticMethodKey extends string = typeof DEFAULT_METHOD_KEY,
55
  FactoryClassMethodKey extends
56
    string = typeof DEFAULT_FACTORY_CLASS_METHOD_KEY,
57
  ExtraModuleDefinitionOptions = {},
58
> {
59
  protected staticMethodKey: StaticMethodKey;
60
  protected factoryClassMethodKey: FactoryClassMethodKey;
61
  protected extras: ExtraModuleDefinitionOptions;
62
  protected transformModuleDefinition: (
63
    definition: DynamicModule,
64
    extraOptions: ExtraModuleDefinitionOptions,
65
  ) => DynamicModule;
66

67
  protected readonly logger = new Logger(ConfigurableModuleBuilder.name);
×
68

69
  constructor(
70
    protected readonly options: ConfigurableModuleBuilderOptions = {},
×
71
    parentBuilder?: ConfigurableModuleBuilder<ModuleOptions>,
72
  ) {
73
    if (parentBuilder) {
×
74
      this.staticMethodKey = parentBuilder.staticMethodKey as StaticMethodKey;
×
75
      this.factoryClassMethodKey =
×
76
        parentBuilder.factoryClassMethodKey as FactoryClassMethodKey;
77
      this.transformModuleDefinition =
×
78
        parentBuilder.transformModuleDefinition as (
79
          definition: DynamicModule,
80
          extraOptions: ExtraModuleDefinitionOptions,
81
        ) => DynamicModule;
82
      this.extras = parentBuilder.extras as ExtraModuleDefinitionOptions;
×
83
    }
84
  }
85

86
  /**
87
   * Registers the "extras" object (a set of extra options that can be used to modify the dynamic module definition).
88
   * Values you specify within the "extras" object will be used as default values (that can be overridden by module consumers).
89
   *
90
   * This method also applies the so-called "module definition transform function" that takes the auto-generated
91
   * dynamic module object ("DynamicModule") and the actual consumer "extras" object as input parameters.
92
   * The "extras" object consists of values explicitly specified by module consumers and default values.
93
   *
94
   * @example
95
   * ```typescript
96
   * .setExtras<{ isGlobal?: boolean }>({ isGlobal: false }, (definition, extras) =>
97
   *    ({ ...definition, global: extras.isGlobal })
98
   * )
99
   * ```
100
   */
101
  setExtras<ExtraModuleDefinitionOptions>(
102
    extras: ExtraModuleDefinitionOptions,
103
    transformDefinition: (
104
      definition: DynamicModule,
105
      extras: ExtraModuleDefinitionOptions,
106
    ) => DynamicModule = def => def,
×
107
  ) {
108
    const builder = new ConfigurableModuleBuilder<
×
109
      ModuleOptions,
110
      StaticMethodKey,
111
      FactoryClassMethodKey,
112
      ExtraModuleDefinitionOptions
113
    >(this.options, this as any);
114
    builder.extras = extras;
×
115
    builder.transformModuleDefinition = transformDefinition;
×
116
    return builder;
×
117
  }
118

119
  /**
120
   * Dynamic modules must expose public static methods that let you pass in
121
   * configuration parameters (control the module's behavior from the outside).
122
   * Some frequently used names that you may have seen in other modules are:
123
   * "forRoot", "forFeature", "register", "configure".
124
   *
125
   * This method "setClassMethodName" lets you specify the name of the
126
   * method that will be auto-generated.
127
   *
128
   * @param key name of the method
129
   */
130
  setClassMethodName<StaticMethodKey extends string>(key: StaticMethodKey) {
131
    const builder = new ConfigurableModuleBuilder<
×
132
      ModuleOptions,
133
      StaticMethodKey,
134
      FactoryClassMethodKey,
135
      ExtraModuleDefinitionOptions
136
    >(this.options, this as any);
137
    builder.staticMethodKey = key;
×
138
    return builder;
×
139
  }
140

141
  /**
142
   * Asynchronously configured modules (that rely on other modules, i.e. "ConfigModule")
143
   * let you pass the configuration factory class that will be registered and instantiated as a provider.
144
   * This provider then will be used to retrieve the module's configuration. To provide the configuration,
145
   * the corresponding factory method must be implemented.
146
   *
147
   * This method ("setFactoryMethodName") lets you control what method name will have to be
148
   * implemented by the config factory (default is "create").
149
   *
150
   * @param key name of the method
151
   */
152
  setFactoryMethodName<FactoryClassMethodKey extends string>(
153
    key: FactoryClassMethodKey,
154
  ) {
155
    const builder = new ConfigurableModuleBuilder<
×
156
      ModuleOptions,
157
      StaticMethodKey,
158
      FactoryClassMethodKey,
159
      ExtraModuleDefinitionOptions
160
    >(this.options, this as any);
161
    builder.factoryClassMethodKey = key;
×
162
    return builder;
×
163
  }
164

165
  /**
166
   * Returns an object consisting of multiple properties that lets you
167
   * easily construct dynamic configurable modules. See "ConfigurableModuleHost" interface for more details.
168
   */
169
  build(): ConfigurableModuleHost<
170
    ModuleOptions,
171
    StaticMethodKey,
172
    FactoryClassMethodKey,
173
    ExtraModuleDefinitionOptions
174
  > {
175
    this.staticMethodKey ??= DEFAULT_METHOD_KEY as StaticMethodKey;
×
176
    this.factoryClassMethodKey ??=
×
177
      DEFAULT_FACTORY_CLASS_METHOD_KEY as FactoryClassMethodKey;
178
    this.options.optionsInjectionToken ??= this.options.moduleName
×
179
      ? this.constructInjectionTokenString()
×
180
      : generateOptionsInjectionToken();
181
    this.transformModuleDefinition ??= definition => definition;
×
182

183
    return {
×
184
      ConfigurableModuleClass:
185
        this.createConfigurableModuleCls<ModuleOptions>(),
186
      MODULE_OPTIONS_TOKEN: this.options.optionsInjectionToken,
187
      ASYNC_OPTIONS_TYPE: this.createTypeProxy('ASYNC_OPTIONS_TYPE'),
188
      OPTIONS_TYPE: this.createTypeProxy('OPTIONS_TYPE'),
189
    };
190
  }
191

192
  private constructInjectionTokenString(): string {
193
    const moduleNameInSnakeCase = this.options
×
194
      .moduleName!.trim()
195
      .split(/(?=[A-Z])/)
196
      .join('_')
197
      .toUpperCase();
198
    return `${moduleNameInSnakeCase}_MODULE_OPTIONS`;
×
199
  }
200

201
  private createConfigurableModuleCls<ModuleOptions>(): ConfigurableModuleCls<
202
    ModuleOptions,
203
    StaticMethodKey,
204
    FactoryClassMethodKey
205
  > {
206
    // eslint-disable-next-line @typescript-eslint/no-this-alias
207
    const self = this;
×
208
    const asyncMethodKey = this.staticMethodKey + ASYNC_METHOD_SUFFIX;
×
209

210
    class InternalModuleClass {
211
      static [self.staticMethodKey](
212
        options: ModuleOptions & ExtraModuleDefinitionOptions,
213
      ): DynamicModule {
214
        const providers: Array<Provider> = [
×
215
          {
216
            provide: self.options.optionsInjectionToken!,
217
            useValue: this.omitExtras(options, self.extras),
218
          },
219
        ];
220
        if (self.options.alwaysTransient) {
×
221
          providers.push({
×
222
            provide: CONFIGURABLE_MODULE_ID,
223
            useValue: randomStringGenerator(),
224
          });
225
        }
226
        return self.transformModuleDefinition(
×
227
          {
228
            module: this,
229
            providers,
230
          },
231
          {
232
            ...self.extras,
233
            ...options,
234
          },
235
        );
236
      }
237

238
      static [asyncMethodKey](
239
        options: ConfigurableModuleAsyncOptions<ModuleOptions> &
240
          ExtraModuleDefinitionOptions,
241
      ): DynamicModule {
242
        const providers = this.createAsyncProviders(options);
×
243
        if (self.options.alwaysTransient) {
×
244
          providers.push({
×
245
            provide: CONFIGURABLE_MODULE_ID,
246
            useValue: randomStringGenerator(),
247
          });
248
        }
249
        return self.transformModuleDefinition(
×
250
          {
251
            module: this,
252
            imports: options.imports || [],
×
253
            providers,
254
          },
255
          {
256
            ...self.extras,
257
            ...options,
258
          },
259
        );
260
      }
261

262
      private static omitExtras(
263
        input: ModuleOptions & ExtraModuleDefinitionOptions,
264
        extras: ExtraModuleDefinitionOptions | undefined,
265
      ): ModuleOptions {
266
        if (!extras) {
×
267
          return input;
×
268
        }
269
        const moduleOptions = {};
×
270
        const extrasKeys = Object.keys(extras);
×
271

272
        Object.keys(input as object)
×
273
          .filter(key => !extrasKeys.includes(key))
×
274
          .forEach(key => {
275
            moduleOptions[key] = input[key];
×
276
          });
277
        return moduleOptions as ModuleOptions;
×
278
      }
279

280
      private static createAsyncProviders(
281
        options: ConfigurableModuleAsyncOptions<ModuleOptions>,
282
      ): Provider[] {
283
        if (options.useExisting || options.useFactory) {
×
284
          if (options.inject && options.provideInjectionTokensFrom) {
×
285
            return [
×
286
              this.createAsyncOptionsProvider(options),
287
              ...getInjectionProviders(
288
                options.provideInjectionTokensFrom,
289
                options.inject,
290
              ),
291
            ];
292
          }
293
          return [this.createAsyncOptionsProvider(options)];
×
294
        }
295
        return [
×
296
          this.createAsyncOptionsProvider(options),
297
          {
298
            provide: options.useClass!,
299
            useClass: options.useClass!,
300
          },
301
        ];
302
      }
303

304
      private static createAsyncOptionsProvider(
305
        options: ConfigurableModuleAsyncOptions<ModuleOptions>,
306
      ): Provider {
307
        if (options.useFactory) {
×
308
          return {
×
309
            provide: self.options.optionsInjectionToken!,
310
            useFactory: options.useFactory,
311
            inject: options.inject || [],
×
312
          };
313
        }
314
        return {
×
315
          provide: self.options.optionsInjectionToken!,
316
          useFactory: async (
317
            optionsFactory: ConfigurableModuleOptionsFactory<
318
              ModuleOptions,
319
              FactoryClassMethodKey
320
            >,
321
          ) =>
322
            await optionsFactory[
×
323
              self.factoryClassMethodKey as keyof typeof optionsFactory
324
            ](),
325
          inject: [options.useExisting || options.useClass!],
×
326
        };
327
      }
328
    }
329
    return InternalModuleClass as unknown as ConfigurableModuleCls<
×
330
      ModuleOptions,
331
      StaticMethodKey,
332
      FactoryClassMethodKey
333
    >;
334
  }
335

336
  private createTypeProxy(
337
    typeName: 'OPTIONS_TYPE' | 'ASYNC_OPTIONS_TYPE' | 'OptionsFactoryInterface',
338
  ) {
339
    const proxy = new Proxy(
×
340
      {},
341
      {
342
        get: () => {
343
          throw new Error(
×
344
            `"${typeName}" is not supposed to be used as a value.`,
345
          );
346
        },
347
      },
348
    );
349
    return proxy as any;
×
350
  }
351
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc