• 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

16.46
/packages/core/injector/instance-wrapper.ts
1
import { Logger, LoggerService, Provider, Scope, Type } from '@nestjs/common';
1✔
2
import { EnhancerSubtype } from '@nestjs/common/constants';
3
import { FactoryProvider, InjectionToken } from '@nestjs/common/interfaces';
4
import { clc } from '@nestjs/common/utils/cli-colors.util';
1✔
5
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
1✔
6
import {
1✔
7
  isNil,
8
  isString,
9
  isUndefined,
10
} from '@nestjs/common/utils/shared.utils';
11
import { iterate } from 'iterare';
1✔
12
import { UuidFactory } from '../inspector/uuid-factory';
1✔
13
import { STATIC_CONTEXT } from './constants';
1✔
14
import {
1✔
15
  isClassProvider,
16
  isFactoryProvider,
17
  isValueProvider,
18
} from './helpers/provider-classifier';
19
import { Module } from './module';
20
import { SettlementSignal } from './settlement-signal';
21

22
export const INSTANCE_METADATA_SYMBOL = Symbol.for('instance_metadata:cache');
1✔
23
export const INSTANCE_ID_SYMBOL = Symbol.for('instance_metadata:id');
1✔
24

25
export interface HostComponentInfo {
26
  /**
27
   * Injection token (or class reference)
28
   */
29
  token: InjectionToken;
30
  /**
31
   * Flag that indicates whether DI subtree is durable
32
   */
33
  isTreeDurable: boolean;
34
}
35

36
export interface ContextId {
37
  readonly id: number;
38
  payload?: unknown;
39
  getParent?(info: HostComponentInfo): ContextId;
40
}
41

42
export interface InstancePerContext<T> {
43
  instance: T;
44
  isResolved?: boolean;
45
  isPending?: boolean;
46
  donePromise?: Promise<unknown>;
47
}
48

49
export interface PropertyMetadata {
50
  key: symbol | string;
51
  wrapper: InstanceWrapper;
52
}
53

54
interface InstanceMetadataStore {
55
  dependencies?: InstanceWrapper[];
56
  properties?: PropertyMetadata[];
57
  enhancers?: InstanceWrapper[];
58
}
59

60
export class InstanceWrapper<T = any> {
1✔
61
  public readonly name: any;
62
  public readonly token: InjectionToken;
63
  public readonly async?: boolean;
64
  public readonly host?: Module;
65
  public readonly isAlias: boolean = false;
4✔
66
  public readonly subtype?: EnhancerSubtype;
67
  public scope?: Scope = Scope.DEFAULT;
4✔
68
  public metatype: Type<T> | Function | null;
69
  public inject?: FactoryProvider['inject'] | null;
70
  public forwardRef?: boolean;
71
  public durable?: boolean;
72
  public initTime?: number;
73
  public settlementSignal?: SettlementSignal;
74

75
  private static logger: LoggerService = new Logger(InstanceWrapper.name);
1✔
76

77
  private readonly values = new WeakMap<ContextId, InstancePerContext<T>>();
4✔
78
  private readonly [INSTANCE_METADATA_SYMBOL]: InstanceMetadataStore = {};
4✔
79
  private readonly [INSTANCE_ID_SYMBOL]: string;
80
  private transientMap?:
81
    | Map<string, WeakMap<ContextId, InstancePerContext<T>>>
82
    | undefined;
83
  private isTreeStatic: boolean | undefined;
84
  private isTreeDurable: boolean | undefined;
85

86
  constructor(
87
    metadata: Partial<InstanceWrapper<T>> & Partial<InstancePerContext<T>> = {},
×
88
  ) {
89
    this.initialize(metadata);
4✔
90
    this[INSTANCE_ID_SYMBOL] =
4✔
91
      metadata[INSTANCE_ID_SYMBOL] ?? this.generateUuid();
8✔
92
  }
93

94
  get id(): string {
95
    return this[INSTANCE_ID_SYMBOL];
×
96
  }
97

98
  set instance(value: T) {
99
    this.values.set(STATIC_CONTEXT, { instance: value });
×
100
  }
101

102
  get instance(): T {
103
    const instancePerContext = this.getInstanceByContextId(STATIC_CONTEXT);
×
104
    return instancePerContext.instance;
×
105
  }
106

107
  get isNotMetatype(): boolean {
108
    return !this.metatype || this.isFactory;
×
109
  }
110

111
  get isFactory(): boolean {
112
    return !!this.metatype && !isNil(this.inject);
×
113
  }
114

115
  get isTransient(): boolean {
116
    return this.scope === Scope.TRANSIENT;
×
117
  }
118

119
  public getInstanceByContextId(
120
    contextId: ContextId,
121
    inquirerId?: string,
122
  ): InstancePerContext<T> {
123
    if (this.scope === Scope.TRANSIENT && inquirerId) {
×
124
      return this.getInstanceByInquirerId(contextId, inquirerId);
×
125
    }
126
    const instancePerContext = this.values.get(contextId);
×
127
    return instancePerContext
×
128
      ? instancePerContext
×
129
      : this.cloneStaticInstance(contextId);
130
  }
131

132
  public getInstanceByInquirerId(
133
    contextId: ContextId,
134
    inquirerId: string,
135
  ): InstancePerContext<T> {
136
    let collectionPerContext = this.transientMap!.get(inquirerId);
×
137
    if (!collectionPerContext) {
×
138
      collectionPerContext = new WeakMap();
×
139
      this.transientMap!.set(inquirerId, collectionPerContext);
×
140
    }
141
    const instancePerContext = collectionPerContext.get(contextId);
×
142
    return instancePerContext
×
143
      ? instancePerContext
×
144
      : this.cloneTransientInstance(contextId, inquirerId);
145
  }
146

147
  public setInstanceByContextId(
148
    contextId: ContextId,
149
    value: InstancePerContext<T>,
150
    inquirerId?: string,
151
  ) {
152
    if (this.scope === Scope.TRANSIENT && inquirerId) {
4!
153
      return this.setInstanceByInquirerId(contextId, inquirerId, value);
×
154
    }
155
    this.values.set(contextId, value);
4✔
156
  }
157

158
  public setInstanceByInquirerId(
159
    contextId: ContextId,
160
    inquirerId: string,
161
    value: InstancePerContext<T>,
162
  ) {
163
    let collection = this.transientMap!.get(inquirerId);
×
164
    if (!collection) {
×
165
      collection = new WeakMap();
×
166
      this.transientMap!.set(inquirerId, collection);
×
167
    }
168
    collection.set(contextId, value);
×
169
  }
170

171
  public removeInstanceByContextId(contextId: ContextId, inquirerId?: string) {
172
    if (this.scope === Scope.TRANSIENT && inquirerId) {
×
173
      return this.removeInstanceByInquirerId(contextId, inquirerId);
×
174
    }
175
    this.values.delete(contextId);
×
176
  }
177

178
  public removeInstanceByInquirerId(contextId: ContextId, inquirerId: string) {
179
    const collection = this.transientMap!.get(inquirerId);
×
180
    if (!collection) {
×
181
      return;
×
182
    }
183
    collection.delete(contextId);
×
184
  }
185

186
  public addCtorMetadata(index: number, wrapper: InstanceWrapper) {
187
    if (!this[INSTANCE_METADATA_SYMBOL].dependencies) {
×
188
      this[INSTANCE_METADATA_SYMBOL].dependencies = [];
×
189
    }
190
    this[INSTANCE_METADATA_SYMBOL].dependencies[index] = wrapper;
×
191
  }
192

193
  public getCtorMetadata(): InstanceWrapper[] {
194
    return this[INSTANCE_METADATA_SYMBOL].dependencies!;
×
195
  }
196

197
  public addPropertiesMetadata(key: symbol | string, wrapper: InstanceWrapper) {
198
    if (!this[INSTANCE_METADATA_SYMBOL].properties) {
×
199
      this[INSTANCE_METADATA_SYMBOL].properties = [];
×
200
    }
201
    this[INSTANCE_METADATA_SYMBOL].properties.push({
×
202
      key,
203
      wrapper,
204
    });
205
  }
206

207
  public getPropertiesMetadata(): PropertyMetadata[] {
208
    return this[INSTANCE_METADATA_SYMBOL].properties!;
×
209
  }
210

211
  public addEnhancerMetadata(wrapper: InstanceWrapper) {
212
    if (!this[INSTANCE_METADATA_SYMBOL].enhancers) {
×
213
      this[INSTANCE_METADATA_SYMBOL].enhancers = [];
×
214
    }
215
    this[INSTANCE_METADATA_SYMBOL].enhancers.push(wrapper);
×
216
  }
217

218
  public getEnhancersMetadata(): InstanceWrapper[] {
219
    return this[INSTANCE_METADATA_SYMBOL].enhancers!;
×
220
  }
221

222
  public isDependencyTreeDurable(lookupRegistry: string[] = []): boolean {
×
223
    if (!isUndefined(this.isTreeDurable)) {
×
224
      return this.isTreeDurable;
×
225
    }
226
    if (this.scope === Scope.REQUEST) {
×
227
      this.isTreeDurable = this.durable === undefined ? false : this.durable;
×
228
      if (this.isTreeDurable) {
×
229
        this.printIntrospectedAsDurable();
×
230
      }
231
      return this.isTreeDurable;
×
232
    }
233
    const isStatic = this.isDependencyTreeStatic();
×
234
    if (isStatic) {
×
235
      return false;
×
236
    }
237

238
    const isTreeNonDurable = this.introspectDepsAttribute(
×
239
      (collection, registry) =>
240
        collection.some(
×
241
          (item: InstanceWrapper) =>
242
            !item.isDependencyTreeStatic() &&
×
243
            !item.isDependencyTreeDurable(registry),
244
        ),
245
      lookupRegistry,
246
    );
247
    this.isTreeDurable = !isTreeNonDurable;
×
248
    if (this.isTreeDurable) {
×
249
      this.printIntrospectedAsDurable();
×
250
    }
251
    return this.isTreeDurable;
×
252
  }
253

254
  public introspectDepsAttribute(
255
    callback: (
256
      collection: InstanceWrapper[],
257
      lookupRegistry: string[],
258
    ) => boolean,
259
    lookupRegistry: string[] = [],
×
260
  ): boolean {
261
    if (lookupRegistry.includes(this[INSTANCE_ID_SYMBOL])) {
×
262
      return false;
×
263
    }
264
    lookupRegistry = lookupRegistry.concat(this[INSTANCE_ID_SYMBOL]);
×
265

266
    const { dependencies, properties, enhancers } =
267
      this[INSTANCE_METADATA_SYMBOL];
×
268

269
    let introspectionResult = dependencies
×
270
      ? callback(dependencies, lookupRegistry)
×
271
      : false;
272

273
    if (introspectionResult || !(properties || enhancers)) {
×
274
      return introspectionResult;
×
275
    }
276
    introspectionResult = properties
×
277
      ? callback(
×
278
          properties.map(item => item.wrapper),
×
279
          lookupRegistry,
280
        )
281
      : false;
282
    if (introspectionResult || !enhancers) {
×
283
      return introspectionResult;
×
284
    }
285
    return enhancers ? callback(enhancers, lookupRegistry) : false;
×
286
  }
287

288
  public isDependencyTreeStatic(lookupRegistry: string[] = []): boolean {
×
289
    if (!isUndefined(this.isTreeStatic)) {
×
290
      return this.isTreeStatic;
×
291
    }
292
    if (this.scope === Scope.REQUEST) {
×
293
      this.isTreeStatic = false;
×
294
      this.printIntrospectedAsRequestScoped();
×
295
      return this.isTreeStatic;
×
296
    }
297
    this.isTreeStatic = !this.introspectDepsAttribute(
×
298
      (collection, registry) =>
299
        collection.some(
×
300
          (item: InstanceWrapper) => !item.isDependencyTreeStatic(registry),
×
301
        ),
302
      lookupRegistry,
303
    );
304
    if (!this.isTreeStatic) {
×
305
      this.printIntrospectedAsRequestScoped();
×
306
    }
307
    return this.isTreeStatic;
×
308
  }
309

310
  public cloneStaticInstance(contextId: ContextId): InstancePerContext<T> {
311
    const staticInstance = this.getInstanceByContextId(STATIC_CONTEXT);
×
312
    if (this.isDependencyTreeStatic()) {
×
313
      return staticInstance;
×
314
    }
315
    const instancePerContext: InstancePerContext<T> = {
×
316
      ...staticInstance,
317
      instance: undefined!,
318
      isResolved: false,
319
      isPending: false,
320
    };
321
    if (this.isNewable()) {
×
322
      instancePerContext.instance = Object.create(this.metatype!.prototype);
×
323
    }
324
    this.setInstanceByContextId(contextId, instancePerContext);
×
325
    return instancePerContext;
×
326
  }
327

328
  public cloneTransientInstance(
329
    contextId: ContextId,
330
    inquirerId: string,
331
  ): InstancePerContext<T> {
332
    const staticInstance = this.getInstanceByContextId(STATIC_CONTEXT);
×
333
    const instancePerContext: InstancePerContext<T> = {
×
334
      ...staticInstance,
335
      instance: undefined!,
336
      isResolved: false,
337
      isPending: false,
338
    };
339
    if (this.isNewable()) {
×
340
      instancePerContext.instance = Object.create(this.metatype!.prototype);
×
341
    }
342
    this.setInstanceByInquirerId(contextId, inquirerId, instancePerContext);
×
343
    return instancePerContext;
×
344
  }
345

346
  public createPrototype(contextId: ContextId) {
347
    const host = this.getInstanceByContextId(contextId);
×
348
    if (!this.isNewable() || host.isResolved) {
×
349
      return;
×
350
    }
351
    return Object.create(this.metatype!.prototype);
×
352
  }
353

354
  public isInRequestScope(
355
    contextId: ContextId,
356
    inquirer?: InstanceWrapper,
357
  ): boolean {
358
    const isDependencyTreeStatic = this.isDependencyTreeStatic();
×
359

360
    return (
×
361
      !isDependencyTreeStatic &&
×
362
      contextId !== STATIC_CONTEXT &&
363
      (!this.isTransient || (this.isTransient && !!inquirer))
364
    );
365
  }
366

367
  public isLazyTransient(
368
    contextId: ContextId,
369
    inquirer: InstanceWrapper | undefined,
370
  ): boolean {
371
    const isInquirerRequestScoped = !!(
×
372
      inquirer && !inquirer.isDependencyTreeStatic()
×
373
    );
374

375
    return (
×
376
      this.isDependencyTreeStatic() &&
×
377
      contextId !== STATIC_CONTEXT &&
378
      this.isTransient &&
379
      isInquirerRequestScoped
380
    );
381
  }
382

383
  public isExplicitlyRequested(
384
    contextId: ContextId,
385
    inquirer?: InstanceWrapper,
386
  ): boolean {
387
    const isSelfRequested = inquirer === this;
×
388
    return (
×
389
      this.isDependencyTreeStatic() &&
×
390
      contextId !== STATIC_CONTEXT &&
391
      (isSelfRequested || !!(inquirer && inquirer.scope === Scope.TRANSIENT))
×
392
    );
393
  }
394

395
  public isStatic(
396
    contextId: ContextId,
397
    inquirer: InstanceWrapper | undefined,
398
  ): boolean {
399
    const isInquirerRequestScoped =
400
      inquirer && !inquirer.isDependencyTreeStatic();
×
401
    const isStaticTransient = this.isTransient && !isInquirerRequestScoped;
×
402

403
    return (
×
404
      this.isDependencyTreeStatic() &&
×
405
      contextId === STATIC_CONTEXT &&
406
      (!this.isTransient ||
407
        (isStaticTransient && !!inquirer && !inquirer.isTransient))
408
    );
409
  }
410

411
  public getStaticTransientInstances() {
412
    if (!this.transientMap) {
×
413
      return [];
×
414
    }
415
    const instances = [...this.transientMap.values()];
×
416
    return iterate(instances)
×
417
      .map(item => item.get(STATIC_CONTEXT))
×
418
      .filter(item => !!item)
×
419
      .toArray();
420
  }
421

422
  public mergeWith(provider: Provider) {
423
    if (isValueProvider(provider)) {
×
424
      this.metatype = null;
×
425
      this.inject = null;
×
426

427
      this.scope = Scope.DEFAULT;
×
428

429
      this.setInstanceByContextId(STATIC_CONTEXT, {
×
430
        instance: provider.useValue,
431
        isResolved: true,
432
        isPending: false,
433
      });
434
    } else if (isClassProvider(provider)) {
×
435
      this.inject = null;
×
436
      this.metatype = provider.useClass;
×
437
    } else if (isFactoryProvider(provider)) {
×
438
      this.metatype = provider.useFactory;
×
439
      this.inject = provider.inject || [];
×
440
    }
441
  }
442

443
  private isNewable(): boolean {
444
    return isNil(this.inject) && this.metatype && this.metatype.prototype;
×
445
  }
446

447
  private initialize(
448
    metadata: Partial<InstanceWrapper<T>> & Partial<InstancePerContext<T>>,
449
  ) {
450
    const { instance, isResolved, ...wrapperPartial } = metadata;
4✔
451
    Object.assign(this, wrapperPartial);
4✔
452

453
    this.setInstanceByContextId(STATIC_CONTEXT, {
4✔
454
      instance: instance as T,
455
      isResolved,
456
    });
457
    this.scope === Scope.TRANSIENT && (this.transientMap = new Map());
4!
458
  }
459

460
  private printIntrospectedAsRequestScoped() {
461
    if (!this.isDebugMode() || this.name === 'REQUEST') {
×
462
      return;
×
463
    }
464
    if (isString(this.name)) {
×
465
      InstanceWrapper.logger.log(
×
466
        `${clc.cyanBright(this.name)}${clc.green(
467
          ' introspected as ',
468
        )}${clc.magentaBright('request-scoped')}`,
469
      );
470
    }
471
  }
472

473
  private printIntrospectedAsDurable() {
474
    if (!this.isDebugMode()) {
×
475
      return;
×
476
    }
477
    if (isString(this.name)) {
×
478
      InstanceWrapper.logger.log(
×
479
        `${clc.cyanBright(this.name)}${clc.green(
480
          ' introspected as ',
481
        )}${clc.magentaBright('durable')}`,
482
      );
483
    }
484
  }
485

486
  private isDebugMode(): boolean {
487
    return !!process.env.NEST_DEBUG;
×
488
  }
489

490
  private generateUuid(): string {
491
    let key = this.name?.toString() ?? this.token?.toString();
4✔
492
    key += this.host?.name ?? '';
4✔
493

494
    return key ? UuidFactory.get(key) : randomStringGenerator();
4!
495
  }
496
}
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