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

nestjs / nest / d7ad88dc-04c6-4461-9072-cd830ab444bd

12 May 2026 06:55PM UTC coverage: 89.929% (-0.1%) from 90.028%
d7ad88dc-04c6-4461-9072-cd830ab444bd

Pull #16939

circleci

kamilmysliwiec
test: add .js to mocha require
Pull Request #16939: fix(core): fix deeply nested transient providers resolution

2369 of 2921 branches covered (81.1%)

56 of 57 new or added lines in 2 files covered. (98.25%)

13 existing lines in 2 files now uncovered.

7697 of 8559 relevant lines covered (89.93%)

56.52 hits per line

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

96.09
/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
  isConstructorCalled?: boolean;
48
}
49

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

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

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

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

78
  private readonly values = new WeakMap<ContextId, InstancePerContext<T>>();
1,091✔
79
  private readonly [INSTANCE_METADATA_SYMBOL]: InstanceMetadataStore = {};
1,091✔
80
  private readonly [INSTANCE_ID_SYMBOL]: string;
81
  private transientMap?:
82
    | Map<string, WeakMap<ContextId, InstancePerContext<T>>>
83
    | undefined;
84
  private isTreeStatic: boolean | undefined;
85
  private isTreeDurable: boolean | undefined;
86
  /**
87
   * The root inquirer reference. Present only if child instance wrapper
88
   * is transient and has a parent inquirer.
89
   */
90
  private rootInquirer: InstanceWrapper | undefined;
91

92
  constructor(
93
    metadata: Partial<InstanceWrapper<T>> & Partial<InstancePerContext<T>> = {},
128✔
94
  ) {
95
    this.initialize(metadata);
1,091✔
96
    this[INSTANCE_ID_SYMBOL] =
1,091✔
97
      metadata[INSTANCE_ID_SYMBOL] ?? this.generateUuid();
2,075✔
98
  }
99

100
  get id(): string {
101
    return this[INSTANCE_ID_SYMBOL];
406✔
102
  }
103

104
  set instance(value: T) {
105
    this.values.set(STATIC_CONTEXT, { instance: value });
6✔
106
  }
107

108
  get instance(): T {
109
    const instancePerContext = this.getInstanceByContextId(STATIC_CONTEXT);
274✔
110
    return instancePerContext.instance;
274✔
111
  }
112

113
  get isNotMetatype(): boolean {
114
    return !this.metatype || this.isFactory;
2✔
115
  }
116

117
  get isFactory(): boolean {
118
    return !!this.metatype && !isNil(this.inject);
1✔
119
  }
120

121
  get isTransient(): boolean {
122
    return this.scope === Scope.TRANSIENT;
1,241✔
123
  }
124

125
  public getInstanceByContextId(
126
    contextId: ContextId,
127
    inquirerId?: string,
128
  ): InstancePerContext<T> {
129
    if (this.scope === Scope.TRANSIENT && inquirerId) {
1,150✔
130
      return this.getInstanceByInquirerId(contextId, inquirerId);
153✔
131
    }
132
    const instancePerContext = this.values.get(contextId);
997✔
133
    return instancePerContext
997✔
134
      ? instancePerContext
135
      : contextId !== STATIC_CONTEXT
21!
136
        ? this.cloneStaticInstance(contextId)
137
        : {
138
            instance: null as T,
139
            isResolved: true,
140
            isPending: false,
141
          };
142
  }
143

144
  public getInstanceByInquirerId(
145
    contextId: ContextId,
146
    inquirerId: string,
147
  ): InstancePerContext<T> {
148
    let collectionPerContext = this.transientMap!.get(inquirerId);
153✔
149
    if (!collectionPerContext) {
153✔
150
      collectionPerContext = new WeakMap();
35✔
151
      this.transientMap!.set(inquirerId, collectionPerContext);
35✔
152
    }
153
    const instancePerContext = collectionPerContext.get(contextId);
153✔
154
    return instancePerContext
153✔
155
      ? instancePerContext
156
      : this.cloneTransientInstance(contextId, inquirerId);
157
  }
158

159
  public setInstanceByContextId(
160
    contextId: ContextId,
161
    value: InstancePerContext<T>,
162
    inquirerId?: string,
163
  ) {
164
    if (this.scope === Scope.TRANSIENT && inquirerId) {
1,116✔
165
      return this.setInstanceByInquirerId(contextId, inquirerId, value);
2✔
166
    }
167
    this.values.set(contextId, value);
1,114✔
168
  }
169

170
  public setInstanceByInquirerId(
171
    contextId: ContextId,
172
    inquirerId: string,
173
    value: InstancePerContext<T>,
174
  ) {
175
    let collection = this.transientMap!.get(inquirerId);
49✔
176
    if (!collection) {
49✔
177
      collection = new WeakMap();
5✔
178
      this.transientMap!.set(inquirerId, collection);
5✔
179
    }
180
    collection.set(contextId, value);
49✔
181
  }
182

183
  public removeInstanceByContextId(contextId: ContextId, inquirerId?: string) {
184
    if (this.scope === Scope.TRANSIENT && inquirerId) {
2✔
185
      return this.removeInstanceByInquirerId(contextId, inquirerId);
1✔
186
    }
187
    this.values.delete(contextId);
1✔
188
  }
189

190
  public removeInstanceByInquirerId(contextId: ContextId, inquirerId: string) {
191
    const collection = this.transientMap!.get(inquirerId);
1✔
192
    if (!collection) {
1!
193
      return;
×
194
    }
195
    collection.delete(contextId);
1✔
196
  }
197

198
  public addCtorMetadata(index: number, wrapper: InstanceWrapper) {
199
    if (!this[INSTANCE_METADATA_SYMBOL].dependencies) {
99✔
200
      this[INSTANCE_METADATA_SYMBOL].dependencies = [];
67✔
201
    }
202
    this[INSTANCE_METADATA_SYMBOL].dependencies[index] = wrapper;
99✔
203
  }
204

205
  public getCtorMetadata(): InstanceWrapper[] {
206
    return this[INSTANCE_METADATA_SYMBOL].dependencies!;
318✔
207
  }
208

209
  public addPropertiesMetadata(key: symbol | string, wrapper: InstanceWrapper) {
210
    if (!this[INSTANCE_METADATA_SYMBOL].properties) {
33✔
211
      this[INSTANCE_METADATA_SYMBOL].properties = [];
23✔
212
    }
213
    this[INSTANCE_METADATA_SYMBOL].properties.push({
33✔
214
      key,
215
      wrapper,
216
    });
217
  }
218

219
  public getPropertiesMetadata(): PropertyMetadata[] {
220
    return this[INSTANCE_METADATA_SYMBOL].properties!;
299✔
221
  }
222

223
  public addEnhancerMetadata(wrapper: InstanceWrapper) {
224
    if (!this[INSTANCE_METADATA_SYMBOL].enhancers) {
35✔
225
      this[INSTANCE_METADATA_SYMBOL].enhancers = [];
26✔
226
    }
227
    this[INSTANCE_METADATA_SYMBOL].enhancers.push(wrapper);
35✔
228
  }
229

230
  public getEnhancersMetadata(): InstanceWrapper[] {
231
    return this[INSTANCE_METADATA_SYMBOL].enhancers!;
35✔
232
  }
233

234
  public isDependencyTreeDurable(lookupRegistry: string[] = []): boolean {
208✔
235
    if (!isUndefined(this.isTreeDurable)) {
231✔
236
      return this.isTreeDurable;
3✔
237
    }
238
    if (this.scope === Scope.REQUEST) {
228✔
239
      this.isTreeDurable = this.durable === undefined ? false : this.durable;
56✔
240
      if (this.isTreeDurable) {
56✔
241
        this.printIntrospectedAsDurable();
19✔
242
      }
243
      return this.isTreeDurable;
56✔
244
    }
245
    const isStatic = this.isDependencyTreeStatic();
172✔
246
    if (isStatic) {
172✔
247
      return false;
154✔
248
    }
249

250
    const isTreeNonDurable = this.introspectDepsAttribute(
18✔
251
      (collection, registry) =>
252
        collection.some(
20✔
253
          (item: InstanceWrapper) =>
254
            !item.isDependencyTreeStatic() &&
31✔
255
            !item.isDependencyTreeDurable(registry),
256
        ),
257
      lookupRegistry,
258
    );
259
    this.isTreeDurable = !isTreeNonDurable;
18✔
260
    if (this.isTreeDurable) {
18✔
261
      this.printIntrospectedAsDurable();
4✔
262
    }
263
    return this.isTreeDurable;
18✔
264
  }
265

266
  public introspectDepsAttribute(
267
    callback: (
268
      collection: InstanceWrapper[],
269
      lookupRegistry: string[],
270
    ) => boolean,
271
    lookupRegistry: string[] = [],
×
272
  ): boolean {
273
    if (lookupRegistry.includes(this[INSTANCE_ID_SYMBOL])) {
341✔
274
      return false;
2✔
275
    }
276
    lookupRegistry = lookupRegistry.concat(this[INSTANCE_ID_SYMBOL]);
339✔
277

278
    const { dependencies, properties, enhancers } =
279
      this[INSTANCE_METADATA_SYMBOL];
339✔
280

281
    let introspectionResult = dependencies
339✔
282
      ? callback(dependencies, lookupRegistry)
283
      : false;
284

285
    if (introspectionResult || !(properties || enhancers)) {
339✔
286
      return introspectionResult;
312✔
287
    }
288
    introspectionResult = properties
27✔
289
      ? callback(
290
          properties.map(item => item.wrapper),
26✔
291
          lookupRegistry,
292
        )
293
      : false;
294
    if (introspectionResult || !enhancers) {
27✔
295
      return introspectionResult;
11✔
296
    }
297
    return enhancers ? callback(enhancers, lookupRegistry) : false;
16!
298
  }
299

300
  public isDependencyTreeStatic(lookupRegistry: string[] = []): boolean {
990✔
301
    if (!isUndefined(this.isTreeStatic)) {
1,071✔
302
      return this.isTreeStatic;
686✔
303
    }
304
    if (this.scope === Scope.REQUEST) {
385✔
305
      this.isTreeStatic = false;
62✔
306
      this.printIntrospectedAsRequestScoped();
62✔
307
      return this.isTreeStatic;
62✔
308
    }
309
    this.isTreeStatic = !this.introspectDepsAttribute(
323✔
310
      (collection, registry) =>
311
        collection.some(
60✔
312
          (item: InstanceWrapper) => !item.isDependencyTreeStatic(registry),
81✔
313
        ),
314
      lookupRegistry,
315
    );
316
    if (!this.isTreeStatic) {
323✔
317
      this.printIntrospectedAsRequestScoped();
26✔
318
    }
319
    return this.isTreeStatic;
323✔
320
  }
321

322
  public cloneStaticInstance(contextId: ContextId): InstancePerContext<T> {
323
    const staticInstance = this.getInstanceByContextId(STATIC_CONTEXT);
23✔
324
    if (this.isDependencyTreeStatic()) {
23✔
325
      return staticInstance;
7✔
326
    }
327
    const instancePerContext: InstancePerContext<T> = {
16✔
328
      ...staticInstance,
329
      instance: undefined!,
330
      isResolved: false,
331
      isPending: false,
332
    };
333
    if (this.isNewable()) {
16✔
334
      instancePerContext.instance = Object.create(this.metatype!.prototype);
15✔
335
    }
336
    this.setInstanceByContextId(contextId, instancePerContext);
16✔
337
    return instancePerContext;
16✔
338
  }
339

340
  public cloneTransientInstance(
341
    contextId: ContextId,
342
    inquirerId: string,
343
  ): InstancePerContext<T> {
344
    const staticInstance = this.getInstanceByContextId(STATIC_CONTEXT);
44✔
345
    const instancePerContext: InstancePerContext<T> = {
44✔
346
      ...staticInstance,
347
      instance: undefined!,
348
      isResolved: false,
349
      isPending: false,
350
    };
351
    if (this.isNewable()) {
44✔
352
      instancePerContext.instance = Object.create(this.metatype!.prototype);
42✔
353
    }
354
    this.setInstanceByInquirerId(contextId, inquirerId, instancePerContext);
44✔
355
    return instancePerContext;
44✔
356
  }
357

358
  public createPrototype(contextId: ContextId) {
359
    const host = this.getInstanceByContextId(contextId);
211✔
360
    if (!this.isNewable() || host.isResolved) {
211✔
361
      return;
104✔
362
    }
363
    return Object.create(this.metatype!.prototype);
107✔
364
  }
365

366
  public isInRequestScope(
367
    contextId: ContextId,
368
    inquirer?: InstanceWrapper,
369
  ): boolean {
370
    const isDependencyTreeStatic = this.isDependencyTreeStatic();
64✔
371

372
    return (
64✔
373
      !isDependencyTreeStatic &&
135!
374
      contextId !== STATIC_CONTEXT &&
375
      (!this.isTransient || (this.isTransient && !!inquirer))
376
    );
377
  }
378

379
  public isLazyTransient(
380
    contextId: ContextId,
381
    inquirer: InstanceWrapper | undefined,
382
  ): boolean {
383
    const isInquirerRequestScoped = !!(
35✔
384
      inquirer && !inquirer.isDependencyTreeStatic()
60✔
385
    );
386

387
    return (
35✔
388
      this.isDependencyTreeStatic() &&
105✔
389
      contextId !== STATIC_CONTEXT &&
390
      this.isTransient &&
391
      isInquirerRequestScoped
392
    );
393
  }
394

395
  public isExplicitlyRequested(
396
    contextId: ContextId,
397
    inquirer?: InstanceWrapper,
398
  ): boolean {
399
    const isSelfRequested = inquirer === this;
32✔
400
    return (
32✔
401
      this.isDependencyTreeStatic() &&
78✔
402
      contextId !== STATIC_CONTEXT &&
403
      (isSelfRequested || !!(inquirer && inquirer.scope === Scope.TRANSIENT))
2!
404
    );
405
  }
406

407
  public isStatic(
408
    contextId: ContextId,
409
    inquirer: InstanceWrapper | undefined,
410
  ): boolean {
411
    if (!this.isDependencyTreeStatic() || contextId !== STATIC_CONTEXT) {
209✔
412
      return false;
62✔
413
    }
414

415
    // Non-transient provider in static context
416
    if (!this.isTransient) {
147✔
417
      return true;
124✔
418
    }
419

420
    const isInquirerRequestScoped =
421
      inquirer && !inquirer.isDependencyTreeStatic();
23✔
422
    const isStaticTransient = this.isTransient && !isInquirerRequestScoped;
23✔
423
    const rootInquirer = inquirer?.getRootInquirer();
23✔
424

425
    // Transient provider inquired by non-transient (e.g., DEFAULT -> TRANSIENT)
426
    if (isStaticTransient && inquirer && !inquirer.isTransient) {
23!
UNCOV
427
      return true;
×
428
    }
429

430
    // Nested transient with non-transient root (e.g., DEFAULT -> TRANSIENT -> TRANSIENT)
431
    if (isStaticTransient && rootInquirer && !rootInquirer.isTransient) {
23✔
432
      return true;
13✔
433
    }
434

435
    // Nested transient during initial instantiation (rootInquirer not yet set)
436
    if (isStaticTransient && inquirer?.isTransient && !rootInquirer) {
10✔
437
      return true;
9✔
438
    }
439

440
    return false;
1✔
441
  }
442

443
  public attachRootInquirer(inquirer: InstanceWrapper) {
444
    if (!this.isTransient) {
16!
445
      // Only attach root inquirer if the instance wrapper is transient
446
      return;
×
447
    }
448
    this.rootInquirer = inquirer.getRootInquirer() ?? inquirer;
16✔
449
  }
450

451
  getRootInquirer(): InstanceWrapper | undefined {
452
    return this.rootInquirer;
42✔
453
  }
454

455
  public getStaticTransientInstances() {
456
    if (!this.transientMap) {
73✔
457
      return [];
70✔
458
    }
459
    const instances = [...this.transientMap.values()];
3✔
460
    return iterate(instances)
3✔
461
      .map(item => item.get(STATIC_CONTEXT))
3✔
462
      .filter(item => {
463
        // Only return items where constructor has been actually called
464
        // This prevents calling lifecycle hooks on non-instantiated transient services
465
        return !!(item && item.isConstructorCalled);
3✔
466
      })
467
      .toArray();
468
  }
469

470
  public mergeWith(provider: Provider) {
471
    if (isValueProvider(provider)) {
4✔
472
      this.metatype = null;
1✔
473
      this.inject = null;
1✔
474

475
      this.scope = Scope.DEFAULT;
1✔
476

477
      this.setInstanceByContextId(STATIC_CONTEXT, {
1✔
478
        instance: provider.useValue,
479
        isResolved: true,
480
        isPending: false,
481
      });
482
    } else if (isClassProvider(provider)) {
3✔
483
      this.inject = null;
1✔
484
      this.metatype = provider.useClass;
1✔
485
    } else if (isFactoryProvider(provider)) {
2✔
486
      this.metatype = provider.useFactory;
2✔
487
      this.inject = provider.inject || [];
2✔
488
    }
489
  }
490

491
  private isNewable(): boolean {
492
    return isNil(this.inject) && this.metatype && this.metatype.prototype;
271✔
493
  }
494

495
  private initialize(
496
    metadata: Partial<InstanceWrapper<T>> & Partial<InstancePerContext<T>>,
497
  ) {
498
    const { instance, isResolved, ...wrapperPartial } = metadata;
1,091✔
499
    Object.assign(this, wrapperPartial);
1,091✔
500

501
    this.setInstanceByContextId(STATIC_CONTEXT, {
1,091✔
502
      instance: instance as T,
503
      isResolved,
504
    });
505
    this.scope === Scope.TRANSIENT && (this.transientMap = new Map());
1,091✔
506
  }
507

508
  private printIntrospectedAsRequestScoped() {
509
    if (!this.isDebugMode() || this.name === 'REQUEST') {
88!
510
      return;
88✔
511
    }
512
    if (isString(this.name)) {
×
513
      InstanceWrapper.logger.log(
×
514
        `${clc.cyanBright(this.name)}${clc.green(
515
          ' introspected as ',
516
        )}${clc.magentaBright('request-scoped')}`,
517
      );
518
    }
519
  }
520

521
  private printIntrospectedAsDurable() {
522
    if (!this.isDebugMode()) {
23✔
523
      return;
23✔
524
    }
525
    if (isString(this.name)) {
×
526
      InstanceWrapper.logger.log(
×
527
        `${clc.cyanBright(this.name)}${clc.green(
528
          ' introspected as ',
529
        )}${clc.magentaBright('durable')}`,
530
      );
531
    }
532
  }
533

534
  private isDebugMode(): boolean {
535
    return !!process.env.NEST_DEBUG;
111✔
536
  }
537

538
  private generateUuid(): string {
539
    let key = this.name?.toString() ?? this.token?.toString();
984✔
540
    key += this.host?.name ?? '';
984✔
541

542
    return key ? UuidFactory.get(key) : randomStringGenerator();
984✔
543
  }
544
}
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