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

nestjs / nest / 4afa0176-b936-482c-ad88-b635a9db93b4

14 Jul 2025 11:37AM UTC coverage: 88.866% (-0.02%) from 88.886%
4afa0176-b936-482c-ad88-b635a9db93b4

Pull #15386

circleci

kamilmysliwiec
style: address linter warnings
Pull Request #15386: feat: enhance introspection capabilities

2714 of 3431 branches covered (79.1%)

101 of 118 new or added lines in 15 files covered. (85.59%)

12 existing lines in 1 file now uncovered.

7239 of 8146 relevant lines covered (88.87%)

16.53 hits per line

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

85.23
/packages/core/injector/module.ts
1
import {
1✔
2
  EnhancerSubtype,
3
  ENTRY_PROVIDER_WATERMARK,
4
} from '@nestjs/common/constants';
5
import {
1✔
6
  ClassProvider,
7
  Controller,
8
  DynamicModule,
9
  ExistingProvider,
10
  FactoryProvider,
11
  Injectable,
12
  InjectionToken,
13
  NestModule,
14
  Provider,
15
  Scope,
16
  Type,
17
  ValueProvider,
18
} from '@nestjs/common/interfaces';
19
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
1✔
20
import {
1✔
21
  isFunction,
22
  isNil,
23
  isObject,
24
  isString,
25
  isSymbol,
26
  isUndefined,
27
} from '@nestjs/common/utils/shared.utils';
28
import { iterate } from 'iterare';
1✔
29
import { ApplicationConfig } from '../application-config';
1✔
30
import {
1✔
31
  InvalidClassException,
32
  RuntimeException,
33
  UnknownExportException,
34
} from '../errors/exceptions';
35
import { createContextId } from '../helpers/context-id-factory';
1✔
36
import { getClassScope } from '../helpers/get-class-scope';
1✔
37
import { isDurable } from '../helpers/is-durable';
1✔
38
import { UuidFactory } from '../inspector/uuid-factory';
1✔
39
import { CONTROLLER_ID_KEY } from './constants';
1✔
40
import { NestContainer } from './container';
41
import { ContextId, InstanceWrapper } from './instance-wrapper';
1✔
42
import { ModuleRef, ModuleRefGetOrResolveOpts } from './module-ref';
1✔
43

44
export class Module {
1✔
45
  private readonly _id: string;
46
  private readonly _imports = new Set<Module>();
125✔
47
  private readonly _providers = new Map<
125✔
48
    InjectionToken,
49
    InstanceWrapper<Injectable>
50
  >();
51
  private readonly _injectables = new Map<
125✔
52
    InjectionToken,
53
    InstanceWrapper<Injectable>
54
  >();
55
  private readonly _middlewares = new Map<
125✔
56
    InjectionToken,
57
    InstanceWrapper<Injectable>
58
  >();
59
  private readonly _controllers = new Map<
125✔
60
    InjectionToken,
61
    InstanceWrapper<Controller>
62
  >();
63
  private readonly _entryProviderKeys = new Set<InjectionToken>();
125✔
64
  private readonly _exports = new Set<InjectionToken>();
125✔
65

66
  private _distance = 0;
125✔
67
  private _initOnPreview = false;
125✔
68
  private _isGlobal = false;
125✔
69
  private _token: string;
70

71
  constructor(
72
    private readonly _metatype: Type<any>,
125✔
73
    private readonly container: NestContainer,
125✔
74
  ) {
75
    this.addCoreProviders();
125✔
76
    this._id = this.generateUuid();
125✔
77
  }
78

79
  get id(): string {
80
    return this._id;
217✔
81
  }
82

83
  get token(): string {
84
    return this._token;
211✔
85
  }
86

87
  set token(token: string) {
88
    this._token = token;
51✔
89
  }
90

91
  get name() {
92
    return this.metatype.name;
804✔
93
  }
94

95
  get isGlobal() {
96
    return this._isGlobal;
31✔
97
  }
98

99
  set isGlobal(global: boolean) {
100
    this._isGlobal = global;
2✔
101
  }
102

103
  get initOnPreview() {
104
    return this._initOnPreview;
×
105
  }
106

107
  set initOnPreview(initOnPreview: boolean) {
108
    this._initOnPreview = initOnPreview;
51✔
109
  }
110

111
  get providers(): Map<InjectionToken, InstanceWrapper<Injectable>> {
112
    return this._providers;
292✔
113
  }
114

115
  get middlewares(): Map<InjectionToken, InstanceWrapper<Injectable>> {
116
    return this._middlewares;
19✔
117
  }
118

119
  get imports(): Set<Module> {
120
    return this._imports;
38✔
121
  }
122

123
  get injectables(): Map<InjectionToken, InstanceWrapper<Injectable>> {
124
    return this._injectables;
138✔
125
  }
126

127
  get controllers(): Map<InjectionToken, InstanceWrapper<Controller>> {
128
    return this._controllers;
115✔
129
  }
130

131
  get entryProviders(): Array<InstanceWrapper<Injectable>> {
132
    return Array.from(this._entryProviderKeys).map(
×
133
      token => this.providers.get(token)!,
×
134
    );
135
  }
136

137
  get exports(): Set<InjectionToken> {
138
    return this._exports;
122✔
139
  }
140

141
  get instance(): NestModule {
142
    if (!this._providers.has(this._metatype)) {
2✔
143
      throw new RuntimeException();
1✔
144
    }
145
    const moduleRef = this._providers.get(this._metatype);
1✔
146
    return moduleRef!.instance as NestModule;
1✔
147
  }
148

149
  get metatype(): Type<any> {
150
    return this._metatype;
976✔
151
  }
152

153
  get distance(): number {
154
    return this._distance;
×
155
  }
156

157
  set distance(value: number) {
158
    this._distance = value;
8✔
159
  }
160

161
  public addCoreProviders() {
162
    this.addModuleAsProvider();
125✔
163
    this.addModuleRef();
125✔
164
    this.addApplicationConfig();
125✔
165
  }
166

167
  public addModuleRef() {
168
    const moduleRef = this.createModuleReferenceType();
125✔
169
    this._providers.set(
125✔
170
      ModuleRef,
171
      new InstanceWrapper({
172
        token: ModuleRef,
173
        name: ModuleRef.name,
174
        metatype: ModuleRef as any,
175
        isResolved: true,
176
        instance: new moduleRef(),
177
        host: this,
178
      }),
179
    );
180
  }
181

182
  public addModuleAsProvider() {
183
    this._providers.set(
125✔
184
      this._metatype,
185
      new InstanceWrapper({
186
        token: this._metatype,
187
        name: this._metatype.name,
188
        metatype: this._metatype,
189
        isResolved: false,
190
        instance: null,
191
        host: this,
192
      }),
193
    );
194
  }
195

196
  public addApplicationConfig() {
197
    this._providers.set(
125✔
198
      ApplicationConfig,
199
      new InstanceWrapper({
200
        token: ApplicationConfig,
201
        name: ApplicationConfig.name,
202
        isResolved: true,
203
        instance: this.container.applicationConfig,
204
        host: this,
205
      }),
206
    );
207
  }
208

209
  public addInjectable<T extends Injectable>(
210
    injectable: Provider,
211
    enhancerSubtype: EnhancerSubtype,
212
    host?: Type<T>,
213
  ) {
214
    if (this.isCustomProvider(injectable)) {
23✔
215
      return this.addCustomProvider(
21✔
216
        injectable,
217
        this._injectables,
218
        enhancerSubtype,
219
      );
220
    }
221
    let instanceWrapper = this.injectables.get(injectable);
2✔
222
    if (!instanceWrapper) {
2!
223
      instanceWrapper = new InstanceWrapper({
2✔
224
        token: injectable,
225
        name: injectable.name,
226
        metatype: injectable,
227
        instance: null,
228
        isResolved: false,
229
        scope: getClassScope(injectable),
230
        durable: isDurable(injectable),
231
        subtype: enhancerSubtype,
232
        host: this,
233
      });
234
      this._injectables.set(injectable, instanceWrapper);
2✔
235
    }
236
    if (host) {
2✔
237
      const hostWrapper =
238
        this._controllers.get(host) || this._providers.get(host);
1✔
239
      hostWrapper && hostWrapper.addEnhancerMetadata(instanceWrapper);
1✔
240
    }
241
    return instanceWrapper;
2✔
242
  }
243

244
  public addProvider(provider: Provider): InjectionToken;
245
  public addProvider(
246
    provider: Provider,
247
    enhancerSubtype: EnhancerSubtype,
248
  ): InjectionToken;
249
  public addProvider(provider: Provider, enhancerSubtype?: EnhancerSubtype) {
250
    if (this.isCustomProvider(provider)) {
52✔
251
      if (this.isEntryProvider(provider.provide)) {
35!
252
        this._entryProviderKeys.add(provider.provide);
×
253
      }
254
      return this.addCustomProvider(provider, this._providers, enhancerSubtype);
35✔
255
    }
256

257
    const isAlreadyDeclared = this._providers.has(provider);
17✔
258
    if (
17!
259
      (this.isTransientProvider(provider) ||
34!
260
        this.isRequestScopeProvider(provider)) &&
261
      isAlreadyDeclared
262
    ) {
263
      return provider;
×
264
    }
265

266
    this._providers.set(
17✔
267
      provider,
268
      new InstanceWrapper({
269
        token: provider,
270
        name: (provider as Type<Injectable>).name,
271
        metatype: provider as Type<Injectable>,
272
        instance: null,
273
        isResolved: false,
274
        scope: getClassScope(provider),
275
        durable: isDurable(provider),
276
        host: this,
277
      }),
278
    );
279

280
    if (this.isEntryProvider(provider)) {
17!
281
      this._entryProviderKeys.add(provider);
×
282
    }
283

284
    return provider as Type<Injectable>;
17✔
285
  }
286

287
  public isCustomProvider(
288
    provider: Provider,
289
  ): provider is
290
    | ClassProvider
291
    | FactoryProvider
292
    | ValueProvider
293
    | ExistingProvider {
294
    return !isNil(
79✔
295
      (
296
        provider as
297
          | ClassProvider
298
          | FactoryProvider
299
          | ValueProvider
300
          | ExistingProvider
301
      ).provide,
302
    );
303
  }
304

305
  public addCustomProvider(
306
    provider:
307
      | ClassProvider
308
      | FactoryProvider
309
      | ValueProvider
310
      | ExistingProvider,
311
    collection: Map<Function | string | symbol, any>,
312
    enhancerSubtype?: EnhancerSubtype,
313
  ) {
314
    if (this.isCustomClass(provider)) {
59✔
315
      this.addCustomClass(provider, collection, enhancerSubtype);
43✔
316
    } else if (this.isCustomValue(provider)) {
16✔
317
      this.addCustomValue(provider, collection, enhancerSubtype);
14✔
318
    } else if (this.isCustomFactory(provider)) {
2✔
319
      this.addCustomFactory(provider, collection, enhancerSubtype);
1✔
320
    } else if (this.isCustomUseExisting(provider)) {
1!
321
      this.addCustomUseExisting(provider, collection, enhancerSubtype);
×
322
    }
323
    return provider.provide;
59✔
324
  }
325

326
  public isCustomClass(provider: any): provider is ClassProvider {
327
    return !isUndefined((provider as ClassProvider).useClass);
59✔
328
  }
329

330
  public isCustomValue(provider: any): provider is ValueProvider {
331
    return (
16✔
332
      isObject(provider) &&
32✔
333
      Object.prototype.hasOwnProperty.call(provider, 'useValue')
334
    );
335
  }
336

337
  public isCustomFactory(provider: any): provider is FactoryProvider {
338
    return !isUndefined((provider as FactoryProvider).useFactory);
2✔
339
  }
340

341
  public isCustomUseExisting(provider: any): provider is ExistingProvider {
342
    return !isUndefined((provider as ExistingProvider).useExisting);
1✔
343
  }
344

345
  public isDynamicModule(exported: any): exported is DynamicModule {
346
    return exported && exported.module;
1✔
347
  }
348

349
  public addCustomClass(
350
    provider: ClassProvider,
351
    collection: Map<InjectionToken, InstanceWrapper>,
352
    enhancerSubtype?: EnhancerSubtype,
353
  ) {
354
    let { scope, durable } = provider;
43✔
355

356
    const { useClass } = provider;
43✔
357
    if (isUndefined(scope)) {
43✔
358
      scope = getClassScope(useClass);
2✔
359
    }
360
    if (isUndefined(durable)) {
43✔
361
      durable = isDurable(useClass);
42✔
362
    }
363

364
    const token = provider.provide;
43✔
365
    collection.set(
43✔
366
      token,
367
      new InstanceWrapper({
368
        token,
369
        name: useClass?.name || useClass,
43!
370
        metatype: useClass,
371
        instance: null,
372
        isResolved: false,
373
        scope,
374
        durable,
375
        host: this,
376
        subtype: enhancerSubtype,
377
      }),
378
    );
379
  }
380

381
  public addCustomValue(
382
    provider: ValueProvider,
383
    collection: Map<Function | string | symbol, InstanceWrapper>,
384
    enhancerSubtype?: EnhancerSubtype,
385
  ) {
386
    const { useValue: value, provide: providerToken } = provider;
13✔
387

388
    const instanceDecorator =
389
      this.container.contextOptions?.instrument?.instanceDecorator;
13✔
390
    collection.set(
13✔
391
      providerToken,
392
      new InstanceWrapper({
393
        token: providerToken,
394
        name: (providerToken as Function)?.name || providerToken,
15✔
395
        metatype: null!,
396
        instance: instanceDecorator ? instanceDecorator(value) : value,
13!
397
        isResolved: true,
398
        async: value instanceof Promise,
399
        host: this,
400
        subtype: enhancerSubtype,
401
      }),
402
    );
403
  }
404

405
  public addCustomFactory(
406
    provider: FactoryProvider,
407
    collection: Map<Function | string | symbol, InstanceWrapper>,
408
    enhancerSubtype?: EnhancerSubtype,
409
  ) {
410
    const {
411
      useFactory: factory,
412
      inject,
413
      scope,
414
      durable,
415
      provide: providerToken,
416
    } = provider;
1✔
417

418
    collection.set(
1✔
419
      providerToken,
420
      new InstanceWrapper({
421
        token: providerToken,
422
        name: (providerToken as Function)?.name || providerToken,
1!
423
        metatype: factory as any,
424
        instance: null,
425
        isResolved: false,
426
        inject: inject || [],
1!
427
        scope,
428
        durable,
429
        host: this,
430
        subtype: enhancerSubtype,
431
      }),
432
    );
433
  }
434

435
  public addCustomUseExisting(
436
    provider: ExistingProvider,
437
    collection: Map<Function | string | symbol, InstanceWrapper>,
438
    enhancerSubtype?: EnhancerSubtype,
439
  ) {
440
    const { useExisting, provide: providerToken } = provider;
1✔
441
    collection.set(
1✔
442
      providerToken,
443
      new InstanceWrapper({
444
        token: providerToken,
445
        name: (providerToken as Function)?.name || providerToken,
1!
446
        metatype: (instance => instance) as any,
1✔
447
        instance: null,
448
        isResolved: false,
449
        inject: [useExisting],
450
        host: this,
451
        isAlias: true,
452
        subtype: enhancerSubtype,
453
      }),
454
    );
455
  }
456

457
  public addExportedProviderOrModule(
458
    toExport: Provider | string | symbol | DynamicModule,
459
  ) {
460
    const addExportedUnit = (token: InjectionToken) =>
4✔
461
      this._exports.add(this.validateExportedProvider(token));
1✔
462

463
    if (this.isCustomProvider(toExport as any)) {
4✔
464
      return this.addCustomExportedProvider(toExport as any);
3✔
465
    } else if (isString(toExport) || isSymbol(toExport)) {
1!
466
      return addExportedUnit(toExport);
×
467
    } else if (this.isDynamicModule(toExport)) {
1!
468
      const { module: moduleClassRef } = toExport;
×
469
      return addExportedUnit(moduleClassRef);
×
470
    }
471
    addExportedUnit(toExport as Type<any>);
1✔
472
  }
473

474
  public addCustomExportedProvider(
475
    provider:
476
      | FactoryProvider
477
      | ValueProvider
478
      | ClassProvider
479
      | ExistingProvider,
480
  ) {
481
    const provide = provider.provide;
3✔
482
    if (isString(provide) || isSymbol(provide)) {
3!
483
      return this._exports.add(this.validateExportedProvider(provide));
3✔
484
    }
485
    this._exports.add(this.validateExportedProvider(provide));
×
486
  }
487

488
  public validateExportedProvider(token: InjectionToken) {
489
    if (this._providers.has(token)) {
5✔
490
      return token;
3✔
491
    }
492
    const imports = iterate(this._imports.values())
2✔
493
      .filter(item => !!item)
1✔
494
      .map(({ metatype }) => metatype)
1✔
495
      .filter(metatype => !!metatype)
1✔
496
      .toArray();
497

498
    if (!imports.includes(token as Type<unknown>)) {
2✔
499
      const { name } = this.metatype;
1✔
500
      const providerName = isFunction(token) ? (token as Function).name : token;
1!
501
      throw new UnknownExportException(providerName as string, name);
1✔
502
    }
503
    return token;
1✔
504
  }
505

506
  public addController(controller: Type<Controller>) {
507
    this._controllers.set(
10✔
508
      controller,
509
      new InstanceWrapper({
510
        token: controller,
511
        name: controller.name,
512
        metatype: controller,
513
        instance: null!,
514
        isResolved: false,
515
        scope: getClassScope(controller),
516
        durable: isDurable(controller),
517
        host: this,
518
      }),
519
    );
520

521
    this.assignControllerUniqueId(controller);
10✔
522
  }
523

524
  public assignControllerUniqueId(controller: Type<Controller>) {
525
    Object.defineProperty(controller, CONTROLLER_ID_KEY, {
10✔
526
      enumerable: false,
527
      writable: false,
528
      configurable: true,
529
      value: randomStringGenerator(),
530
    });
531
  }
532

533
  public addImport(moduleRef: Module) {
534
    this._imports.add(moduleRef);
9✔
535
  }
536

537
  public replace(toReplace: InjectionToken, options: any) {
538
    if (options.isProvider && this.hasProvider(toReplace)) {
2✔
539
      const originalProvider = this._providers.get(toReplace);
1✔
540

541
      return originalProvider!.mergeWith({ provide: toReplace, ...options });
1✔
542
    } else if (!options.isProvider && this.hasInjectable(toReplace)) {
1!
543
      const originalInjectable = this._injectables.get(toReplace);
1✔
544

545
      return originalInjectable!.mergeWith({
1✔
546
        provide: toReplace,
547
        ...options,
548
      });
549
    }
550
  }
551

552
  public hasProvider(token: InjectionToken): boolean {
553
    return this._providers.has(token);
2✔
554
  }
555

556
  public hasInjectable(token: InjectionToken): boolean {
557
    return this._injectables.has(token);
2✔
558
  }
559

560
  public getProviderByKey<T = any>(name: InjectionToken): InstanceWrapper<T> {
561
    return this._providers.get(name) as InstanceWrapper<T>;
48✔
562
  }
563

564
  public getProviderById<T = any>(id: string): InstanceWrapper<T> | undefined {
UNCOV
565
    return Array.from(this._providers.values()).find(
×
UNCOV
566
      item => item.id === id,
×
567
    ) as InstanceWrapper<T>;
568
  }
569

570
  public getControllerById<T = any>(
571
    id: string,
572
  ): InstanceWrapper<T> | undefined {
UNCOV
573
    return Array.from(this._controllers.values()).find(
×
UNCOV
574
      item => item.id === id,
×
575
    ) as InstanceWrapper<T>;
576
  }
577

578
  public getInjectableById<T = any>(
579
    id: string,
580
  ): InstanceWrapper<T> | undefined {
UNCOV
581
    return Array.from(this._injectables.values()).find(
×
UNCOV
582
      item => item.id === id,
×
583
    ) as InstanceWrapper<T>;
584
  }
585

586
  public getMiddlewareById<T = any>(
587
    id: string,
588
  ): InstanceWrapper<T> | undefined {
UNCOV
589
    return Array.from(this._middlewares.values()).find(
×
UNCOV
590
      item => item.id === id,
×
591
    ) as InstanceWrapper<T>;
592
  }
593

594
  public getNonAliasProviders(): Array<
595
    [InjectionToken, InstanceWrapper<Injectable>]
596
  > {
597
    return [...this._providers].filter(([_, wrapper]) => !wrapper.isAlias);
58✔
598
  }
599

600
  public createModuleReferenceType(): Type<ModuleRef> {
601
    // eslint-disable-next-line @typescript-eslint/no-this-alias
602
    const self = this;
127✔
603
    return class extends ModuleRef {
127✔
604
      constructor() {
605
        super(self.container);
127✔
606
      }
607

608
      public get<TInput = any, TResult = TInput>(
609
        typeOrToken: Type<TInput> | string | symbol,
610
        options: ModuleRefGetOrResolveOpts = {},
1✔
611
      ): TResult | Array<TResult> {
612
        options.strict ??= true;
2✔
613
        options.each ??= false;
2✔
614

615
        return this.find<TInput, TResult>(
2✔
616
          typeOrToken,
617
          options.strict
618
            ? {
2✔
619
                moduleId: self.id,
620
                each: options.each,
621
              }
622
            : options,
623
        );
624
      }
625

626
      public resolve<TInput = any, TResult = TInput>(
627
        typeOrToken: Type<TInput> | string | symbol,
628
        contextId = createContextId(),
×
629
        options: ModuleRefGetOrResolveOpts = {},
×
630
      ): Promise<TResult | Array<TResult>> {
UNCOV
631
        options.strict ??= true;
×
UNCOV
632
        options.each ??= false;
×
633

634
        return this.resolvePerContext<TInput, TResult>(
×
635
          typeOrToken,
636
          self,
637
          contextId,
638
          options,
639
        );
640
      }
641

642
      public async create<T = any>(
643
        type: Type<T>,
644
        contextId?: ContextId,
645
      ): Promise<T> {
UNCOV
646
        if (!(type && isFunction(type) && type.prototype)) {
×
UNCOV
647
          throw new InvalidClassException(type);
×
648
        }
649
        return this.instantiateClass<T>(type, self, contextId);
×
650
      }
651
    };
652
  }
653

654
  private isEntryProvider(metatype: InjectionToken): boolean {
655
    return typeof metatype === 'function'
52✔
656
      ? !!Reflect.getMetadata(ENTRY_PROVIDER_WATERMARK, metatype)
52✔
657
      : false;
658
  }
659

660
  private generateUuid(): string {
661
    const prefix = 'M_';
125✔
662
    const key = this.token
125✔
663
      ? this.token.includes(':')
125!
664
        ? this.token.split(':')[1]
×
665
        : this.token
666
      : this.name;
667

668
    return key ? UuidFactory.get(`${prefix}_${key}`) : randomStringGenerator();
125✔
669
  }
670

671
  private isTransientProvider(provider: Type<any>): boolean {
672
    return getClassScope(provider) === Scope.TRANSIENT;
17✔
673
  }
674

675
  private isRequestScopeProvider(provider: Type<any>): boolean {
676
    return getClassScope(provider) === Scope.REQUEST;
17✔
677
  }
678
}
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