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

nestjs / nest / 8a9ca339-ab74-4090-839f-edf407cfaff7

05 Aug 2025 01:54PM UTC coverage: 88.764% (-0.1%) from 88.913%
8a9ca339-ab74-4090-839f-edf407cfaff7

Pull #15504

circleci

hajekjiri
fix(core): fix race condition in class dependency resolution

Fix race condition in class dependency resolution, which could otherwise
lead to undefined or null injection. This is a followup to #15405 that
fixes another case of this bug that was not taken care of in the
previous PR. In this case specifically, the staticity of the dependency
tree could be checked before all dependencies are loaded and the param /
property Barrier is passed, resulting in a potentially wrong evaluation
of the `isTreeStatic` property of the `InstanceWrapper`.

Closes #4873
Pull Request #15504: fix(core): fix race condition in class dependency resolution from imported modules

2725 of 3451 branches covered (78.96%)

7268 of 8188 relevant lines covered (88.76%)

16.68 hits per line

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

79.25
/packages/core/injector/injector.ts
1
import {
1✔
2
  InjectionToken,
3
  Logger,
4
  LoggerService,
5
  OptionalFactoryDependency,
6
} from '@nestjs/common';
7
import {
1✔
8
  OPTIONAL_DEPS_METADATA,
9
  OPTIONAL_PROPERTY_DEPS_METADATA,
10
  PARAMTYPES_METADATA,
11
  PROPERTY_DEPS_METADATA,
12
  SELF_DECLARED_DEPS_METADATA,
13
} from '@nestjs/common/constants';
14
import {
15
  Controller,
16
  ForwardReference,
17
  Injectable,
18
  Type,
19
} from '@nestjs/common/interfaces';
20
import { clc } from '@nestjs/common/utils/cli-colors.util';
1✔
21
import {
1✔
22
  isFunction,
23
  isNil,
24
  isObject,
25
  isString,
26
  isSymbol,
27
  isUndefined,
28
} from '@nestjs/common/utils/shared.utils';
29
import { iterate } from 'iterare';
1✔
30
import { performance } from 'perf_hooks';
1✔
31
import { CircularDependencyException } from '../errors/exceptions';
1✔
32
import { RuntimeException } from '../errors/exceptions/runtime.exception';
1✔
33
import { UndefinedDependencyException } from '../errors/exceptions/undefined-dependency.exception';
1✔
34
import { UnknownDependenciesException } from '../errors/exceptions/unknown-dependencies.exception';
1✔
35
import { STATIC_CONTEXT } from './constants';
1✔
36
import { INQUIRER } from './inquirer';
1✔
37
import {
1✔
38
  ContextId,
39
  InstancePerContext,
40
  InstanceWrapper,
41
  PropertyMetadata,
42
} from './instance-wrapper';
43
import { Module } from './module';
44
import { SettlementSignal } from './settlement-signal';
1✔
45
import { Barrier } from '../helpers/barrier';
1✔
46

47
/**
48
 * The type of an injectable dependency
49
 */
50
export type InjectorDependency = InjectionToken;
51

52
/**
53
 * The property-based dependency
54
 */
55
export interface PropertyDependency {
56
  key: symbol | string;
57
  name: InjectorDependency;
58
  isOptional?: boolean;
59
  instance?: any;
60
}
61

62
/**
63
 * Context of a dependency which gets injected by
64
 * the injector
65
 */
66
export interface InjectorDependencyContext {
67
  /**
68
   * The name of the property key (property-based injection)
69
   */
70
  key?: string | symbol;
71
  /**
72
   * The function itself, the name of the function, or injection token.
73
   */
74
  name?: Function | string | symbol;
75
  /**
76
   * The index of the dependency which gets injected
77
   * from the dependencies array
78
   */
79
  index?: number;
80
  /**
81
   * The dependency array which gets injected
82
   */
83
  dependencies?: InjectorDependency[];
84
}
85

86
export class Injector {
1✔
87
  private logger: LoggerService = new Logger('InjectorLogger');
304✔
88
  private readonly instanceDecorator: (target: unknown) => unknown = (
304✔
89
    target: unknown,
90
  ) => target;
61✔
91

92
  constructor(
93
    private readonly options?: {
304✔
94
      /**
95
       * Whether to enable preview mode.
96
       */
97
      preview: boolean;
98
      /**
99
       * Function to decorate a freshly created instance.
100
       */
101
      instanceDecorator?: (target: unknown) => unknown;
102
    },
103
  ) {
104
    if (options?.instanceDecorator) {
304!
105
      this.instanceDecorator = options.instanceDecorator;
×
106
    }
107
  }
108

109
  public loadPrototype<T>(
110
    { token }: InstanceWrapper<T>,
111
    collection: Map<InjectionToken, InstanceWrapper<T>>,
112
    contextId = STATIC_CONTEXT,
124✔
113
  ) {
114
    if (!collection) {
124✔
115
      return;
1✔
116
    }
117
    const target = collection.get(token)!;
123✔
118
    const instance = target.createPrototype(contextId);
123✔
119
    if (instance) {
123✔
120
      const wrapper = new InstanceWrapper({
71✔
121
        ...target,
122
        instance,
123
      });
124
      collection.set(token, wrapper);
71✔
125
    }
126
  }
127

128
  public async loadInstance<T>(
129
    wrapper: InstanceWrapper<T>,
130
    collection: Map<InjectionToken, InstanceWrapper>,
131
    moduleRef: Module,
132
    contextId = STATIC_CONTEXT,
6✔
133
    inquirer?: InstanceWrapper,
134
  ) {
135
    const inquirerId = this.getInquirerId(inquirer);
143✔
136
    const instanceHost = wrapper.getInstanceByContextId(
143✔
137
      this.getContextId(contextId, wrapper),
138
      inquirerId,
139
    );
140

141
    if (instanceHost.isPending) {
142✔
142
      const settlementSignal = wrapper.settlementSignal;
11✔
143
      if (inquirer && settlementSignal?.isCycle(inquirer.id)) {
11!
144
        throw new CircularDependencyException(`"${wrapper.name}"`);
×
145
      }
146

147
      return instanceHost.donePromise!.then((err?: unknown) => {
11✔
148
        if (err) {
11✔
149
          throw err;
1✔
150
        }
151
      });
152
    }
153

154
    const settlementSignal = this.applySettlementSignal(instanceHost, wrapper);
131✔
155
    const token = wrapper.token || wrapper.name;
131✔
156

157
    const { inject } = wrapper;
131✔
158
    const targetWrapper = collection.get(token);
131✔
159
    if (isUndefined(targetWrapper)) {
131!
160
      throw new RuntimeException();
×
161
    }
162
    if (instanceHost.isResolved) {
131✔
163
      return settlementSignal.complete();
48✔
164
    }
165
    try {
83✔
166
      const t0 = this.getNowTimestamp();
83✔
167
      const callback = async (instances: unknown[]) => {
83✔
168
        const properties = await this.resolveProperties(
83✔
169
          wrapper,
170
          moduleRef,
171
          inject as InjectionToken[],
172
          contextId,
173
          wrapper,
174
          inquirer,
175
        );
176
        const instance = await this.instantiateClass(
83✔
177
          instances,
178
          wrapper,
179
          targetWrapper,
180
          contextId,
181
          inquirer,
182
        );
183
        this.applyProperties(instance, properties);
83✔
184
        wrapper.initTime = this.getNowTimestamp() - t0;
83✔
185
        settlementSignal.complete();
83✔
186
      };
187
      await this.resolveConstructorParams<T>(
83✔
188
        wrapper,
189
        moduleRef,
190
        inject as InjectionToken[],
191
        callback,
192
        contextId,
193
        wrapper,
194
        inquirer,
195
      );
196
    } catch (err) {
197
      wrapper.removeInstanceByContextId(
×
198
        this.getContextId(contextId, wrapper),
199
        inquirerId,
200
      );
201

202
      settlementSignal.error(err);
×
203
      throw err;
×
204
    }
205
  }
206

207
  public async loadMiddleware(
208
    wrapper: InstanceWrapper,
209
    collection: Map<InjectionToken, InstanceWrapper>,
210
    moduleRef: Module,
211
    contextId = STATIC_CONTEXT,
2✔
212
    inquirer?: InstanceWrapper,
213
  ) {
214
    const { metatype, token } = wrapper;
2✔
215
    const targetWrapper = collection.get(token)!;
2✔
216
    if (!isUndefined(targetWrapper.instance)) {
2✔
217
      return;
1✔
218
    }
219
    targetWrapper.instance = Object.create(metatype!.prototype);
1✔
220
    await this.loadInstance(
1✔
221
      wrapper,
222
      collection,
223
      moduleRef,
224
      contextId,
225
      inquirer || wrapper,
2✔
226
    );
227
  }
228

229
  public async loadController(
230
    wrapper: InstanceWrapper<Controller>,
231
    moduleRef: Module,
232
    contextId = STATIC_CONTEXT,
1✔
233
  ) {
234
    const controllers = moduleRef.controllers;
1✔
235
    await this.loadInstance<Controller>(
1✔
236
      wrapper,
237
      controllers,
238
      moduleRef,
239
      contextId,
240
      wrapper,
241
    );
242
    await this.loadEnhancersPerContext(wrapper, contextId, wrapper);
1✔
243
  }
244

245
  public async loadInjectable<T = any>(
246
    wrapper: InstanceWrapper<T>,
247
    moduleRef: Module,
248
    contextId = STATIC_CONTEXT,
21✔
249
    inquirer?: InstanceWrapper,
250
  ) {
251
    const injectables = moduleRef.injectables;
21✔
252
    await this.loadInstance<T>(
21✔
253
      wrapper,
254
      injectables,
255
      moduleRef,
256
      contextId,
257
      inquirer,
258
    );
259
  }
260

261
  public async loadProvider(
262
    wrapper: InstanceWrapper<Injectable>,
263
    moduleRef: Module,
264
    contextId = STATIC_CONTEXT,
94✔
265
    inquirer?: InstanceWrapper,
266
  ) {
267
    const providers = moduleRef.providers;
98✔
268
    await this.loadInstance<Injectable>(
98✔
269
      wrapper,
270
      providers,
271
      moduleRef,
272
      contextId,
273
      inquirer,
274
    );
275
    await this.loadEnhancersPerContext(wrapper, contextId, wrapper);
98✔
276
  }
277

278
  public applySettlementSignal<T>(
279
    instancePerContext: InstancePerContext<T>,
280
    host: InstanceWrapper<T>,
281
  ) {
282
    const settlementSignal = new SettlementSignal();
131✔
283
    instancePerContext.donePromise = settlementSignal.asPromise();
131✔
284
    instancePerContext.isPending = true;
131✔
285
    host.settlementSignal = settlementSignal;
131✔
286

287
    return settlementSignal;
131✔
288
  }
289

290
  public async resolveConstructorParams<T>(
291
    wrapper: InstanceWrapper<T>,
292
    moduleRef: Module,
293
    inject: InjectorDependency[] | undefined,
294
    callback: (args: unknown[]) => void | Promise<void>,
295
    contextId = STATIC_CONTEXT,
×
296
    inquirer?: InstanceWrapper,
297
    parentInquirer?: InstanceWrapper,
298
  ) {
299
    let inquirerId = this.getInquirerId(inquirer);
84✔
300
    const metadata = wrapper.getCtorMetadata();
84✔
301

302
    if (metadata && contextId !== STATIC_CONTEXT) {
84✔
303
      const deps = await this.loadCtorMetadata(
1✔
304
        metadata,
305
        contextId,
306
        inquirer,
307
        parentInquirer,
308
      );
309
      return callback(deps);
1✔
310
    }
311

312
    const isFactoryProvider = !isNil(inject);
83✔
313
    const [dependencies, optionalDependenciesIds] = isFactoryProvider
83✔
314
      ? this.getFactoryProviderDependencies(wrapper)
83!
315
      : this.getClassDependencies(wrapper);
316

317
    const paramBarrier = new Barrier(dependencies.length);
83✔
318
    let isResolved = true;
83✔
319
    const resolveParam = async (param: unknown, index: number) => {
83✔
320
      try {
4✔
321
        if (this.isInquirer(param, parentInquirer)) {
4!
322
          /*
323
           * Signal the barrier to make sure other dependencies do not get stuck waiting forever.
324
           */
325
          paramBarrier.signal();
×
326

327
          return parentInquirer && parentInquirer.instance;
×
328
        }
329
        if (inquirer?.isTransient && parentInquirer) {
4!
330
          inquirer = parentInquirer;
×
331
          inquirerId = this.getInquirerId(parentInquirer);
×
332
        }
333
        const paramWrapper = await this.resolveSingleParam<T>(
4✔
334
          wrapper,
335
          param as Type | string | symbol,
336
          { index, dependencies },
337
          moduleRef,
338
          contextId,
339
          inquirer,
340
          index,
341
        );
342

343
        /*
344
         * Ensure that all instance wrappers are resolved at this point before we continue.
345
         * Otherwise the staticity of `wrapper`'s dependency tree may be evaluated incorrectly
346
         * and result in undefined / null injection.
347
         */
348
        await paramBarrier.signalAndWait();
4✔
349

350
        const paramWrapperWithInstance = await this.resolveComponentHost(
4✔
351
          moduleRef,
352
          paramWrapper,
353
          contextId,
354
          inquirer,
355
        );
356
        const instanceHost = paramWrapperWithInstance.getInstanceByContextId(
4✔
357
          this.getContextId(contextId, paramWrapperWithInstance),
358
          inquirerId,
359
        );
360
        if (!instanceHost.isResolved && !paramWrapperWithInstance.forwardRef) {
4!
361
          isResolved = false;
×
362
        }
363
        return instanceHost?.instance;
4✔
364
      } catch (err) {
365
        /*
366
         * Signal the barrier to make sure other dependencies do not get stuck waiting forever. We
367
         * do not care if this occurs after `Barrier.signalAndWait()` is called in the `try` block
368
         * because the barrier will always have been resolved by then.
369
         */
370
        paramBarrier.signal();
×
371

372
        const isOptional = optionalDependenciesIds.includes(index);
×
373
        if (!isOptional) {
×
374
          throw err;
×
375
        }
376
        return undefined;
×
377
      }
378
    };
379
    const instances = await Promise.all(dependencies.map(resolveParam));
83✔
380
    isResolved && (await callback(instances));
83✔
381
  }
382

383
  public getClassDependencies<T>(
384
    wrapper: InstanceWrapper<T>,
385
  ): [InjectorDependency[], number[]] {
386
    const ctorRef = wrapper.metatype as Type<any>;
85✔
387
    return [
85✔
388
      this.reflectConstructorParams(ctorRef),
389
      this.reflectOptionalParams(ctorRef),
390
    ];
391
  }
392

393
  public getFactoryProviderDependencies<T>(
394
    wrapper: InstanceWrapper<T>,
395
  ): [InjectorDependency[], number[]] {
396
    const optionalDependenciesIds: number[] = [];
1✔
397

398
    /**
399
     * Same as the internal utility function `isOptionalFactoryDependency` from `@nestjs/common`.
400
     * We are duplicating it here because that one is not supposed to be exported.
401
     */
402
    function isOptionalFactoryDependency(
403
      value: InjectionToken | OptionalFactoryDependency,
404
    ): value is OptionalFactoryDependency {
405
      return (
3✔
406
        !isUndefined((value as OptionalFactoryDependency).token) &&
7✔
407
        !isUndefined((value as OptionalFactoryDependency).optional) &&
408
        !(value as any).prototype
409
      );
410
    }
411

412
    const mapFactoryProviderInjectArray = (
1✔
413
      item: InjectionToken | OptionalFactoryDependency,
414
      index: number,
415
    ): InjectionToken => {
416
      if (typeof item !== 'object') {
4✔
417
        return item;
1✔
418
      }
419
      if (isOptionalFactoryDependency(item)) {
3✔
420
        if (item.optional) {
2✔
421
          optionalDependenciesIds.push(index);
1✔
422
        }
423
        return item?.token;
2✔
424
      }
425
      return item;
1✔
426
    };
427
    return [
1✔
428
      wrapper.inject?.map?.(mapFactoryProviderInjectArray) as any[],
429
      optionalDependenciesIds,
430
    ];
431
  }
432

433
  public reflectConstructorParams<T>(type: Type<T>): any[] {
434
    const paramtypes = [
85✔
435
      ...(Reflect.getMetadata(PARAMTYPES_METADATA, type) || []),
166✔
436
    ];
437
    const selfParams = this.reflectSelfParams<T>(type);
85✔
438

439
    selfParams.forEach(({ index, param }) => (paramtypes[index] = param));
85✔
440
    return paramtypes;
85✔
441
  }
442

443
  public reflectOptionalParams<T>(type: Type<T>): any[] {
444
    return Reflect.getMetadata(OPTIONAL_DEPS_METADATA, type) || [];
85✔
445
  }
446

447
  public reflectSelfParams<T>(type: Type<T>): any[] {
448
    return Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, type) || [];
85✔
449
  }
450

451
  public async resolveSingleParam<T>(
452
    wrapper: InstanceWrapper<T>,
453
    param: Type<any> | string | symbol,
454
    dependencyContext: InjectorDependencyContext,
455
    moduleRef: Module,
456
    contextId = STATIC_CONTEXT,
1✔
457
    inquirer?: InstanceWrapper,
458
    keyOrIndex?: symbol | string | number,
459
  ) {
460
    if (isUndefined(param)) {
7✔
461
      this.logger.log(
1✔
462
        'Nest encountered an undefined dependency. This may be due to a circular import or a missing dependency declaration.',
463
      );
464
      throw new UndefinedDependencyException(
1✔
465
        wrapper.name,
466
        dependencyContext,
467
        moduleRef,
468
      );
469
    }
470
    const token = this.resolveParamToken(wrapper, param);
6✔
471
    return this.resolveComponentWrapper(
6✔
472
      moduleRef,
473
      token,
474
      dependencyContext,
475
      wrapper,
476
      contextId,
477
      inquirer,
478
      keyOrIndex,
479
    );
480
  }
481

482
  public resolveParamToken<T>(
483
    wrapper: InstanceWrapper<T>,
484
    param: Type<any> | string | symbol | ForwardReference,
485
  ) {
486
    if (typeof param === 'object' && 'forwardRef' in param) {
10✔
487
      wrapper.forwardRef = true;
2✔
488
      return param.forwardRef();
2✔
489
    }
490
    return param;
8✔
491
  }
492

493
  public async resolveComponentWrapper<T>(
494
    moduleRef: Module,
495
    token: InjectionToken,
496
    dependencyContext: InjectorDependencyContext,
497
    wrapper: InstanceWrapper<T>,
498
    contextId = STATIC_CONTEXT,
×
499
    inquirer?: InstanceWrapper,
500
    keyOrIndex?: symbol | string | number,
501
  ): Promise<InstanceWrapper> {
502
    this.printResolvingDependenciesLog(token, inquirer);
6✔
503
    this.printLookingForProviderLog(token, moduleRef);
6✔
504
    const providers = moduleRef.providers;
6✔
505
    return this.lookupComponent(
6✔
506
      providers,
507
      moduleRef,
508
      { ...dependencyContext, name: token },
509
      wrapper,
510
      contextId,
511
      inquirer,
512
      keyOrIndex,
513
    );
514
  }
515

516
  public async resolveComponentHost<T>(
517
    moduleRef: Module,
518
    instanceWrapper: InstanceWrapper<T | Promise<T>>,
519
    contextId = STATIC_CONTEXT,
4✔
520
    inquirer?: InstanceWrapper,
521
  ): Promise<InstanceWrapper> {
522
    const inquirerId = this.getInquirerId(inquirer);
10✔
523
    const instanceHost = instanceWrapper.getInstanceByContextId(
10✔
524
      this.getContextId(contextId, instanceWrapper),
525
      inquirerId,
526
    );
527
    if (!instanceHost.isResolved && !instanceWrapper.forwardRef) {
10✔
528
      inquirer?.settlementSignal?.insertRef(instanceWrapper.id);
5✔
529

530
      await this.loadProvider(
5✔
531
        instanceWrapper,
532
        instanceWrapper.host ?? moduleRef,
10✔
533
        contextId,
534
        inquirer,
535
      );
536
    } else if (
5!
537
      !instanceHost.isResolved &&
11✔
538
      instanceWrapper.forwardRef &&
539
      (contextId !== STATIC_CONTEXT || !!inquirerId)
540
    ) {
541
      /**
542
       * When circular dependency has been detected between
543
       * either request/transient providers, we have to asynchronously
544
       * resolve instance host for a specific contextId or inquirer, to ensure
545
       * that eventual lazily created instance will be merged with the prototype
546
       * instantiated beforehand.
547
       */
548
      instanceHost.donePromise &&
×
549
        void instanceHost.donePromise.then(() =>
550
          this.loadProvider(instanceWrapper, moduleRef, contextId, inquirer),
×
551
        );
552
    }
553
    if (instanceWrapper.async) {
10✔
554
      const host = instanceWrapper.getInstanceByContextId(
1✔
555
        this.getContextId(contextId, instanceWrapper),
556
        inquirerId,
557
      );
558
      host.instance = await host.instance;
1✔
559
      instanceWrapper.setInstanceByContextId(contextId, host, inquirerId);
1✔
560
    }
561
    return instanceWrapper;
10✔
562
  }
563

564
  public async lookupComponent<T = any>(
565
    providers: Map<Function | string | symbol, InstanceWrapper>,
566
    moduleRef: Module,
567
    dependencyContext: InjectorDependencyContext,
568
    wrapper: InstanceWrapper<T>,
569
    contextId = STATIC_CONTEXT,
5✔
570
    inquirer?: InstanceWrapper,
571
    keyOrIndex?: symbol | string | number,
572
  ): Promise<InstanceWrapper<T>> {
573
    const token = wrapper.token || wrapper.name;
11✔
574
    const { name } = dependencyContext;
11✔
575
    if (wrapper && token === name) {
11✔
576
      throw new UnknownDependenciesException(
1✔
577
        wrapper.name,
578
        dependencyContext,
579
        moduleRef,
580
        { id: wrapper.id },
581
      );
582
    }
583
    if (name && providers.has(name)) {
10✔
584
      const instanceWrapper = providers.get(name)!;
7✔
585
      this.printFoundInModuleLog(name, moduleRef);
7✔
586
      this.addDependencyMetadata(keyOrIndex!, wrapper, instanceWrapper);
7✔
587
      return instanceWrapper;
7✔
588
    }
589
    return this.lookupComponentInParentModules(
3✔
590
      dependencyContext,
591
      moduleRef,
592
      wrapper,
593
      contextId,
594
      inquirer,
595
      keyOrIndex,
596
    );
597
  }
598

599
  public async lookupComponentInParentModules<T = any>(
600
    dependencyContext: InjectorDependencyContext,
601
    moduleRef: Module,
602
    wrapper: InstanceWrapper<T>,
603
    contextId = STATIC_CONTEXT,
×
604
    inquirer?: InstanceWrapper,
605
    keyOrIndex?: symbol | string | number,
606
  ) {
607
    const instanceWrapper = await this.lookupComponentInImports(
3✔
608
      moduleRef,
609
      dependencyContext.name!,
610
      wrapper,
611
      [],
612
      contextId,
613
      inquirer,
614
      keyOrIndex,
615
    );
616
    if (isNil(instanceWrapper)) {
3✔
617
      throw new UnknownDependenciesException(
1✔
618
        wrapper.name,
619
        dependencyContext,
620
        moduleRef,
621
        { id: wrapper.id },
622
      );
623
    }
624
    return instanceWrapper;
2✔
625
  }
626

627
  public async lookupComponentInImports(
628
    moduleRef: Module,
629
    name: InjectionToken,
630
    wrapper: InstanceWrapper,
631
    moduleRegistry: any[] = [],
3✔
632
    contextId = STATIC_CONTEXT,
3✔
633
    inquirer?: InstanceWrapper,
634
    keyOrIndex?: symbol | string | number,
635
    isTraversing?: boolean,
636
  ): Promise<any> {
637
    let instanceWrapperRef: InstanceWrapper | null = null;
3✔
638
    const imports = moduleRef.imports || new Set<Module>();
3✔
639
    const identity = (item: any) => item;
3✔
640

641
    let children = [...imports.values()].filter(identity);
3✔
642
    if (isTraversing) {
3!
643
      const contextModuleExports = moduleRef.exports;
×
644
      children = children.filter(child =>
×
645
        contextModuleExports.has(child.metatype),
×
646
      );
647
    }
648
    for (const relatedModule of children) {
3✔
649
      if (moduleRegistry.includes(relatedModule.id)) {
×
650
        continue;
×
651
      }
652
      this.printLookingForProviderLog(name, relatedModule);
×
653
      moduleRegistry.push(relatedModule.id);
×
654

655
      const { providers, exports } = relatedModule;
×
656
      if (!exports.has(name) || !providers.has(name)) {
×
657
        const instanceRef = await this.lookupComponentInImports(
×
658
          relatedModule,
659
          name,
660
          wrapper,
661
          moduleRegistry,
662
          contextId,
663
          inquirer,
664
          keyOrIndex,
665
          true,
666
        );
667
        if (instanceRef) {
×
668
          this.addDependencyMetadata(keyOrIndex!, wrapper, instanceRef);
×
669
          return instanceRef;
×
670
        }
671
        continue;
×
672
      }
673
      this.printFoundInModuleLog(name, relatedModule);
×
674
      instanceWrapperRef = providers.get(name)!;
×
675
      this.addDependencyMetadata(keyOrIndex!, wrapper, instanceWrapperRef);
×
676

677
      const inquirerId = this.getInquirerId(inquirer);
×
678
      const instanceHost = instanceWrapperRef.getInstanceByContextId(
×
679
        this.getContextId(contextId, instanceWrapperRef),
680
        inquirerId,
681
      );
682
      if (!instanceHost.isResolved && !instanceWrapperRef.forwardRef) {
×
683
        /*
684
         * Provider will be loaded shortly in resolveComponentHost() once we pass the current
685
         * Barrier. We cannot load it here because doing so could incorrectly evaluate the
686
         * staticity of the dependency tree and lead to undefined / null injection.
687
         */
688
        break;
×
689
      }
690
    }
691
    return instanceWrapperRef;
3✔
692
  }
693

694
  public async resolveProperties<T>(
695
    wrapper: InstanceWrapper<T>,
696
    moduleRef: Module,
697
    inject?: InjectorDependency[],
698
    contextId = STATIC_CONTEXT,
×
699
    inquirer?: InstanceWrapper,
700
    parentInquirer?: InstanceWrapper,
701
  ): Promise<PropertyDependency[]> {
702
    if (!isNil(inject)) {
84!
703
      return [];
×
704
    }
705
    const metadata = wrapper.getPropertiesMetadata();
84✔
706
    if (metadata && contextId !== STATIC_CONTEXT) {
84✔
707
      return this.loadPropertiesMetadata(metadata, contextId, inquirer);
1✔
708
    }
709
    const properties = this.reflectProperties(wrapper.metatype as Type<any>);
83✔
710
    const propertyBarrier = new Barrier(properties.length);
83✔
711
    const instances = await Promise.all(
83✔
712
      properties.map(async (item: PropertyDependency) => {
713
        try {
2✔
714
          const dependencyContext = {
2✔
715
            key: item.key,
716
            name: item.name as Function | string | symbol,
717
          };
718
          if (this.isInquirer(item.name, parentInquirer)) {
2!
719
            /*
720
             * Signal the barrier to make sure other dependencies do not get stuck waiting forever.
721
             */
722
            propertyBarrier.signal();
×
723

724
            return parentInquirer && parentInquirer.instance;
×
725
          }
726
          const paramWrapper = await this.resolveSingleParam<T>(
2✔
727
            wrapper,
728
            item.name as string,
729
            dependencyContext,
730
            moduleRef,
731
            contextId,
732
            inquirer,
733
            item.key,
734
          );
735

736
          /*
737
           * Ensure that all instance wrappers are resolved at this point before we continue.
738
           * Otherwise the staticity of `wrapper`'s dependency tree may be evaluated incorrectly
739
           * and result in undefined / null injection.
740
           */
741
          await propertyBarrier.signalAndWait();
2✔
742

743
          const paramWrapperWithInstance = await this.resolveComponentHost(
2✔
744
            moduleRef,
745
            paramWrapper,
746
            contextId,
747
            inquirer,
748
          );
749
          if (!paramWrapperWithInstance) {
2!
750
            return undefined;
×
751
          }
752
          const inquirerId = this.getInquirerId(inquirer);
2✔
753
          const instanceHost = paramWrapperWithInstance.getInstanceByContextId(
2✔
754
            this.getContextId(contextId, paramWrapperWithInstance),
755
            inquirerId,
756
          );
757
          return instanceHost.instance;
2✔
758
        } catch (err) {
759
          /*
760
           * Signal the barrier to make sure other dependencies do not get stuck waiting forever. We
761
           * do not care if this occurs after `Barrier.signalAndWait()` is called in the `try` block
762
           * because the barrier will always have been resolved by then.
763
           */
764
          propertyBarrier.signal();
×
765

766
          if (!item.isOptional) {
×
767
            throw err;
×
768
          }
769
          return undefined;
×
770
        }
771
      }),
772
    );
773
    return properties.map((item: PropertyDependency, index: number) => ({
83✔
774
      ...item,
775
      instance: instances[index],
776
    }));
777
  }
778

779
  public reflectProperties<T>(type: Type<T>): PropertyDependency[] {
780
    const properties = Reflect.getMetadata(PROPERTY_DEPS_METADATA, type) || [];
83✔
781
    const optionalKeys: string[] =
782
      Reflect.getMetadata(OPTIONAL_PROPERTY_DEPS_METADATA, type) || [];
83✔
783

784
    return properties.map((item: any) => ({
83✔
785
      ...item,
786
      name: item.type,
787
      isOptional: optionalKeys.includes(item.key),
788
    }));
789
  }
790

791
  public applyProperties<T = any>(
792
    instance: T,
793
    properties: PropertyDependency[],
794
  ): void {
795
    if (!isObject(instance)) {
85✔
796
      return undefined;
1✔
797
    }
798
    iterate(properties)
84✔
799
      .filter(item => !isNil(item.instance))
5✔
800
      .forEach(item => (instance[item.key] = item.instance));
4✔
801
  }
802

803
  public async instantiateClass<T = any>(
804
    instances: any[],
805
    wrapper: InstanceWrapper,
806
    targetMetatype: InstanceWrapper,
807
    contextId = STATIC_CONTEXT,
×
808
    inquirer?: InstanceWrapper,
809
  ): Promise<T> {
810
    const { metatype, inject } = wrapper;
87✔
811
    const inquirerId = this.getInquirerId(inquirer);
87✔
812
    const instanceHost = targetMetatype.getInstanceByContextId(
87✔
813
      this.getContextId(contextId, targetMetatype),
814
      inquirerId,
815
    );
816
    const isInContext =
817
      wrapper.isStatic(contextId, inquirer) ||
87✔
818
      wrapper.isInRequestScope(contextId, inquirer) ||
819
      wrapper.isLazyTransient(contextId, inquirer) ||
820
      wrapper.isExplicitlyRequested(contextId, inquirer);
821

822
    if (this.options?.preview && !wrapper.host?.initOnPreview) {
87!
823
      instanceHost.isResolved = true;
×
824
      return instanceHost.instance;
×
825
    }
826

827
    if (isNil(inject) && isInContext) {
87✔
828
      instanceHost.instance = wrapper.forwardRef
60✔
829
        ? Object.assign(
60!
830
            instanceHost.instance,
831
            new (metatype as Type<any>)(...instances),
832
          )
833
        : new (metatype as Type<any>)(...instances);
834

835
      instanceHost.instance = this.instanceDecorator(instanceHost.instance);
60✔
836
    } else if (isInContext) {
27✔
837
      const factoryReturnValue = (targetMetatype.metatype as any as Function)(
1✔
838
        ...instances,
839
      );
840
      instanceHost.instance = await factoryReturnValue;
1✔
841
      instanceHost.instance = this.instanceDecorator(instanceHost.instance);
1✔
842
    }
843
    instanceHost.isResolved = true;
87✔
844
    return instanceHost.instance;
87✔
845
  }
846

847
  public async loadPerContext<T = any>(
848
    instance: T,
849
    moduleRef: Module,
850
    collection: Map<InjectionToken, InstanceWrapper>,
851
    ctx: ContextId,
852
    wrapper?: InstanceWrapper,
853
  ): Promise<T> {
854
    if (!wrapper) {
19✔
855
      const injectionToken = (instance as any).constructor!;
1✔
856
      wrapper = collection.get(injectionToken);
1✔
857
    }
858
    await this.loadInstance(wrapper!, collection, moduleRef, ctx, wrapper);
19✔
859
    await this.loadEnhancersPerContext(wrapper!, ctx, wrapper);
19✔
860

861
    const host = wrapper!.getInstanceByContextId(
19✔
862
      this.getContextId(ctx, wrapper!),
863
      wrapper!.id,
864
    );
865
    return host && (host.instance as T);
19✔
866
  }
867

868
  public async loadEnhancersPerContext(
869
    wrapper: InstanceWrapper,
870
    ctx: ContextId,
871
    inquirer?: InstanceWrapper,
872
  ) {
873
    const enhancers = wrapper.getEnhancersMetadata() || [];
119✔
874
    const loadEnhancer = (item: InstanceWrapper) => {
119✔
875
      const hostModule = item.host!;
2✔
876
      return this.loadInstance(
2✔
877
        item,
878
        hostModule.injectables,
879
        hostModule,
880
        ctx,
881
        inquirer,
882
      );
883
    };
884
    await Promise.all(enhancers.map(loadEnhancer));
119✔
885
  }
886

887
  public async loadCtorMetadata(
888
    metadata: InstanceWrapper<any>[],
889
    contextId: ContextId,
890
    inquirer?: InstanceWrapper,
891
    parentInquirer?: InstanceWrapper,
892
  ): Promise<any[]> {
893
    const hosts: Array<InstanceWrapper<any> | undefined> = await Promise.all(
2✔
894
      metadata.map(async item =>
895
        this.resolveScopedComponentHost(
2✔
896
          item,
897
          contextId,
898
          inquirer,
899
          parentInquirer,
900
        ),
901
      ),
902
    );
903
    const inquirerId = this.getInquirerId(inquirer);
2✔
904
    return hosts.map(
2✔
905
      item =>
906
        item?.getInstanceByContextId(
2✔
907
          this.getContextId(contextId, item),
908
          inquirerId,
909
        ).instance,
910
    );
911
  }
912

913
  public async loadPropertiesMetadata(
914
    metadata: PropertyMetadata[],
915
    contextId: ContextId,
916
    inquirer?: InstanceWrapper,
917
  ): Promise<PropertyDependency[]> {
918
    const dependenciesHosts = await Promise.all(
2✔
919
      metadata.map(async ({ wrapper: item, key }) => ({
2✔
920
        key,
921
        host: await this.resolveComponentHost(
922
          item.host!,
923
          item,
924
          contextId,
925
          inquirer,
926
        ),
927
      })),
928
    );
929
    const inquirerId = this.getInquirerId(inquirer);
2✔
930
    return dependenciesHosts.map(({ key, host }) => ({
2✔
931
      key,
932
      name: key,
933
      instance: host.getInstanceByContextId(
934
        this.getContextId(contextId, host),
935
        inquirerId,
936
      ).instance,
937
    }));
938
  }
939

940
  private getInquirerId(
941
    inquirer: InstanceWrapper | undefined,
942
  ): string | undefined {
943
    return inquirer ? inquirer.id : undefined;
330✔
944
  }
945

946
  private resolveScopedComponentHost(
947
    item: InstanceWrapper,
948
    contextId: ContextId,
949
    inquirer?: InstanceWrapper,
950
    parentInquirer?: InstanceWrapper,
951
  ) {
952
    return this.isInquirerRequest(item, parentInquirer)
2✔
953
      ? parentInquirer
2!
954
      : this.resolveComponentHost(item.host!, item, contextId, inquirer);
955
  }
956

957
  private isInquirerRequest(
958
    item: InstanceWrapper,
959
    parentInquirer: InstanceWrapper | undefined,
960
  ) {
961
    return item.isTransient && item.name === INQUIRER && parentInquirer;
2!
962
  }
963

964
  private isInquirer(
965
    param: unknown,
966
    parentInquirer: InstanceWrapper | undefined,
967
  ) {
968
    return param === INQUIRER && parentInquirer;
6!
969
  }
970

971
  protected addDependencyMetadata(
972
    keyOrIndex: symbol | string | number,
973
    hostWrapper: InstanceWrapper,
974
    instanceWrapper: InstanceWrapper,
975
  ) {
976
    if (isSymbol(keyOrIndex) || isString(keyOrIndex)) {
10✔
977
      hostWrapper.addPropertiesMetadata(keyOrIndex, instanceWrapper);
4✔
978
    } else {
979
      hostWrapper.addCtorMetadata(keyOrIndex, instanceWrapper);
6✔
980
    }
981
  }
982

983
  private getTokenName(token: InjectionToken): string {
984
    return isFunction(token) ? (token as Function).name : token.toString();
×
985
  }
986

987
  private printResolvingDependenciesLog(
988
    token: InjectionToken,
989
    inquirer?: InstanceWrapper,
990
  ): void {
991
    if (!this.isDebugMode()) {
6!
992
      return;
6✔
993
    }
994
    const tokenName = this.getTokenName(token);
×
995
    const dependentName =
996
      (inquirer?.name && inquirer.name.toString?.()) ?? 'unknown';
×
997
    const isAlias = dependentName === tokenName;
×
998

999
    const messageToPrint = `Resolving dependency ${clc.cyanBright(
×
1000
      tokenName,
1001
    )}${clc.green(' in the ')}${clc.yellow(dependentName)}${clc.green(
1002
      ` provider ${isAlias ? '(alias)' : ''}`,
×
1003
    )}`;
1004

1005
    this.logger.log(messageToPrint);
×
1006
  }
1007

1008
  private printLookingForProviderLog(
1009
    token: InjectionToken,
1010
    moduleRef: Module,
1011
  ): void {
1012
    if (!this.isDebugMode()) {
6!
1013
      return;
6✔
1014
    }
1015
    const tokenName = this.getTokenName(token);
×
1016
    const moduleRefName = moduleRef?.metatype?.name ?? 'unknown';
×
1017
    this.logger.log(
×
1018
      `Looking for ${clc.cyanBright(tokenName)}${clc.green(
1019
        ' in ',
1020
      )}${clc.magentaBright(moduleRefName)}`,
1021
    );
1022
  }
1023

1024
  private printFoundInModuleLog(
1025
    token: InjectionToken,
1026
    moduleRef: Module,
1027
  ): void {
1028
    if (!this.isDebugMode()) {
7!
1029
      return;
7✔
1030
    }
1031
    const tokenName = this.getTokenName(token);
×
1032
    const moduleRefName = moduleRef?.metatype?.name ?? 'unknown';
×
1033
    this.logger.log(
×
1034
      `Found ${clc.cyanBright(tokenName)}${clc.green(
1035
        ' in ',
1036
      )}${clc.magentaBright(moduleRefName)}`,
1037
    );
1038
  }
1039

1040
  private isDebugMode(): boolean {
1041
    return !!process.env.NEST_DEBUG;
19✔
1042
  }
1043

1044
  private getContextId(
1045
    contextId: ContextId,
1046
    instanceWrapper: InstanceWrapper,
1047
  ): ContextId {
1048
    return contextId.getParent
270✔
1049
      ? contextId.getParent({
270!
1050
          token: instanceWrapper.token,
1051
          isTreeDurable: instanceWrapper.isDependencyTreeDurable(),
1052
        })
1053
      : contextId;
1054
  }
1055

1056
  private getNowTimestamp() {
1057
    return performance.now();
166✔
1058
  }
1059
}
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

© 2025 Coveralls, Inc