Repository: nestjs/nest
Pull request #10521 (2022-11-05 15:10:23)

feat(core): expose each option to get and resolve methods
## PR Checklist
Please check if your PR fulfills the following requirements:

- [x] The commit message follows our guidelines: https://github.com/nestjs/nest/blob/master/CONTRIBUTING.md
- [x] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features)


## PR Type
What kind of change does this PR introduce?

<!-- Please check the one that applies to this PR using "x". -->
- [ ] Bugfix
- [x] Feature
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Other... Please describe:

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->

Issue Number: N/A


## What is the new behavior?


## Does this PR introduce a breaking change?
- [ ] Yes
- [x] No

<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->


## Other information


Patch:
diff --git a/integration/injector/src/multiple-providers/a.module.ts b/integration/injector/src/multiple-providers/a.module.ts
new file mode 100644
index 000000000..95ae5eea1
--- /dev/null
+++ b/integration/injector/src/multiple-providers/a.module.ts
@@ -0,0 +1,16 @@
+import { Module, Scope } from '@nestjs/common';
+
+@Module({
+  providers: [
+    {
+      provide: 'MULTI_PROVIDER',
+      useValue: 'A',
+    },
+    {
+      provide: 'REQ_SCOPED_MULTI_PROVIDER',
+      useFactory: () => 'A',
+      scope: Scope.REQUEST,
+    },
+  ],
+})
+export class AModule {}
diff --git a/integration/injector/src/multiple-providers/b.module.ts b/integration/injector/src/multiple-providers/b.module.ts
new file mode 100644
index 000000000..1ed8ac423
--- /dev/null
+++ b/integration/injector/src/multiple-providers/b.module.ts
@@ -0,0 +1,16 @@
+import { Module, Scope } from '@nestjs/common';
+
+@Module({
+  providers: [
+    {
+      provide: 'MULTI_PROVIDER',
+      useValue: 'B',
+    },
+    {
+      provide: 'REQ_SCOPED_MULTI_PROVIDER',
+      useFactory: () => 'B',
+      scope: Scope.REQUEST,
+    },
+  ],
+})
+export class BModule {}
diff --git a/integration/injector/src/multiple-providers/c.module.ts b/integration/injector/src/multiple-providers/c.module.ts
new file mode 100644
index 000000000..4def2a71a
--- /dev/null
+++ b/integration/injector/src/multiple-providers/c.module.ts
@@ -0,0 +1,16 @@
+import { Module, Scope } from '@nestjs/common';
+
+@Module({
+  providers: [
+    {
+      provide: 'MULTI_PROVIDER',
+      useValue: 'C',
+    },
+    {
+      provide: 'REQ_SCOPED_MULTI_PROVIDER',
+      useFactory: () => 'C',
+      scope: Scope.REQUEST,
+    },
+  ],
+})
+export class CModule {}
diff --git a/integration/injector/src/multiple-providers/multiple-providers.module.ts b/integration/injector/src/multiple-providers/multiple-providers.module.ts
new file mode 100644
index 000000000..1bb2345ee
--- /dev/null
+++ b/integration/injector/src/multiple-providers/multiple-providers.module.ts
@@ -0,0 +1,9 @@
+import { Module } from '@nestjs/common';
+import { AModule } from './a.module';
+import { BModule } from './b.module';
+import { CModule } from './c.module';
+
+@Module({
+  imports: [AModule, BModule, CModule],
+})
+export class MultipleProvidersModule {}
diff --git a/packages/common/interfaces/nest-application-context.interface.ts b/packages/common/interfaces/nest-application-context.interface.ts
index e10dfc306..554fa1d1b 100644
--- a/packages/common/interfaces/nest-application-context.interface.ts
+++ b/packages/common/interfaces/nest-application-context.interface.ts
@@ -3,6 +3,20 @@ import { LoggerService, LogLevel } from '../services/logger.service';
 import { DynamicModule } from './modules';
 import { Type } from './type.interface';
 
+export interface GetOrResolveOptions {
+  /**
+   * If enabled, lookup will only be performed in the host module.
+   * @default false
+   */
+  strict?: boolean;
+  /**
+   * If enabled, instead of returning a first instance registered under a given token,
+   * a list of instances will be returned.
+   * @default false
+   */
+  each?: boolean;
+}
+
 /**
  * Interface defining NestApplicationContext.
  *
@@ -21,18 +35,74 @@ export interface INestApplicationContext {
    */
   get<TInput = any, TResult = TInput>(
     typeOrToken: Type<TInput> | Function | string | symbol,
-    options?: { strict: boolean },
   ): TResult;
+  /**
+   * Retrieves an instance of either injectable or controller, otherwise, throws exception.
+   * @returns {TResult}
+   */
+  get<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    options: { strict?: boolean; each?: undefined | false },
+  ): TResult;
+  /**
+   * Retrieves a list of instances of either injectables or controllers, otherwise, throws exception.
+   * @returns {Array<TResult>}
+   */
+  get<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    options: { strict?: boolean; each: true },
+  ): Array<TResult>;
+  /**
+   * Retrieves an instance (or a list of instances) of either injectable or controller, otherwise, throws exception.
+   * @returns {TResult | Array<TResult>}
+   */
+  get<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    options?: GetOrResolveOptions,
+  ): TResult | Array<TResult>;
 
   /**
    * Resolves transient or request-scoped instance of either injectable or controller, otherwise, throws exception.
-   * @returns {Promise<TResult>}
+   * @returns {Array<TResult>}
+   */
+  resolve<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+  ): Promise<TResult>;
+  /**
+   * Resolves transient or request-scoped instance of either injectable or controller, otherwise, throws exception.
+   * @returns {Array<TResult>}
+   */
+  resolve<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    contextId?: { id: number },
+  ): Promise<TResult>;
+  /**
+   * Resolves transient or request-scoped instance of either injectable or controller, otherwise, throws exception.
+   * @returns {Array<TResult>}
    */
   resolve<TInput = any, TResult = TInput>(
     typeOrToken: Type<TInput> | Function | string | symbol,
     contextId?: { id: number },
-    options?: { strict: boolean },
+    options?: { strict?: boolean; each?: undefined | false },
   ): Promise<TResult>;
+  /**
+   * Resolves transient or request-scoped instances of either injectables or controllers, otherwise, throws exception.
+   * @returns {Array<TResult>}
+   */
+  resolve<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    contextId?: { id: number },
+    options?: { strict?: boolean; each: true },
+  ): Promise<Array<TResult>>;
+  /**
+   * Resolves transient or request-scoped instance (or a list of instances) of either injectable or controller, otherwise, throws exception.
+   * @returns {Promise<TResult | Array<TResult>>}
+   */
+  resolve<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    contextId?: { id: number },
+    options?: GetOrResolveOptions,
+  ): Promise<TResult | Array<TResult>>;
 
   /**
    * Registers the request/context object for a given context ID (DI container sub-tree).
diff --git a/packages/core/injector/abstract-instance-resolver.ts b/packages/core/injector/abstract-instance-resolver.ts
new file mode 100644
index 000000000..0308574d6
--- /dev/null
+++ b/packages/core/injector/abstract-instance-resolver.ts
@@ -0,0 +1,86 @@
+import { Abstract, Scope, Type } from '@nestjs/common';
+import { GetOrResolveOptions } from '@nestjs/common/interfaces';
+import {
+  InvalidClassScopeException,
+  UnknownElementException,
+} from '../errors/exceptions';
+import { Injector } from './injector';
+import { InstanceLink, InstanceLinksHost } from './instance-links-host';
+import { ContextId } from './instance-wrapper';
+import { Module } from './module';
+
+export abstract class AbstractInstanceResolver {
+  protected abstract instanceLinksHost: InstanceLinksHost;
+  protected abstract injector: Injector;
+
+  protected abstract get<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    options?: GetOrResolveOptions,
+  ): TResult | Array<TResult>;
+
+  protected find<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Abstract<TInput> | string | symbol,
+    options: { moduleId?: string; each?: boolean },
+  ): TResult | Array<TResult> {
+    const instanceLinkOrArray = this.instanceLinksHost.get<TResult>(
+      typeOrToken,
+      options,
+    );
+    const pluckInstance = ({ wrapperRef }: InstanceLink) => {
+      if (
+        wrapperRef.scope === Scope.REQUEST ||
+        wrapperRef.scope === Scope.TRANSIENT
+      ) {
+        throw new InvalidClassScopeException(typeOrToken);
+      }
+      return wrapperRef.instance;
+    };
+    if (Array.isArray(instanceLinkOrArray)) {
+      return instanceLinkOrArray.map(pluckInstance);
+    }
+    return pluckInstance(instanceLinkOrArray);
+  }
+
+  protected async resolvePerContext<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Abstract<TInput> | string | symbol,
+    contextModule: Module,
+    contextId: ContextId,
+    options?: GetOrResolveOptions,
+  ): Promise<TResult | Array<TResult>> {
+    const instanceLinkOrArray = options?.strict
+      ? this.instanceLinksHost.get(typeOrToken, {
+          moduleId: contextModule.id,
+          each: options.each,
+        })
+      : this.instanceLinksHost.get(typeOrToken, {
+          each: options.each,
+        });
+
+    const pluckInstance = async (instanceLink: InstanceLink) => {
+      const { wrapperRef, collection } = instanceLink;
+      if (wrapperRef.isDependencyTreeStatic() && !wrapperRef.isTransient) {
+        return this.get(typeOrToken, { strict: options.strict });
+      }
+
+      const ctorHost = wrapperRef.instance || { constructor: typeOrToken };
+      const instance = await this.injector.loadPerContext(
+        ctorHost,
+        wrapperRef.host,
+        collection,
+        contextId,
+        wrapperRef,
+      );
+      if (!instance) {
+        throw new UnknownElementException();
+      }
+      return instance;
+    };
+
+    if (Array.isArray(instanceLinkOrArray)) {
+      return Promise.all(
+        instanceLinkOrArray.map(instanceLink => pluckInstance(instanceLink)),
+      );
+    }
+    return pluckInstance(instanceLinkOrArray);
+  }
+}
diff --git a/packages/core/injector/container.ts b/packages/core/injector/container.ts
index 1898d31f1..6fed38280 100644
--- a/packages/core/injector/container.ts
+++ b/packages/core/injector/container.ts
@@ -11,7 +11,7 @@ import {
 import { REQUEST } from '../router/request/request-constants';
 import { ModuleCompiler } from './compiler';
 import { ContextId } from './instance-wrapper';
-import { InternalCoreModule } from './internal-core-module';
+import { InternalCoreModule } from './internal-core-module/internal-core-module';
 import { InternalProvidersStorage } from './internal-providers-storage';
 import { Module } from './module';
 import { ModuleTokenFactory } from './module-token-factory';
diff --git a/packages/core/injector/silent-logger.ts b/packages/core/injector/helpers/silent-logger.ts
similarity index 100%
rename from packages/core/injector/silent-logger.ts
rename to packages/core/injector/helpers/silent-logger.ts
diff --git a/packages/core/injector/index.ts b/packages/core/injector/index.ts
index 4abbe0bb6..21e406d06 100644
--- a/packages/core/injector/index.ts
+++ b/packages/core/injector/index.ts
@@ -1,6 +1,6 @@
 export * from './container';
 export * from './inquirer';
 export { ContextId, HostComponentInfo } from './instance-wrapper';
-export * from './lazy-module-loader';
+export * from './lazy-module-loader/lazy-module-loader';
 export * from './module-ref';
 export * from './modules-container';
diff --git a/packages/core/injector/instance-links-host.ts b/packages/core/injector/instance-links-host.ts
index a07617d28..e70f2e642 100644
--- a/packages/core/injector/instance-links-host.ts
+++ b/packages/core/injector/instance-links-host.ts
@@ -20,15 +20,30 @@ export class InstanceLinksHost {
     this.initialize();
   }
 
-  get<T = any>(token: InstanceToken, moduleId?: string): InstanceLink<T> {
-    const modulesMap = this.instanceLinks.get(token);
+  get<T = any>(token: InstanceToken): InstanceLink<T>;
+  get<T = any>(
+    token: InstanceToken,
+    options?: { moduleId?: string; each?: boolean },
+  ): InstanceLink<T> | Array<InstanceLink<T>>;
+  get<T = any>(
+    token: InstanceToken,
+    options: { moduleId?: string; each?: boolean } = {},
+  ): InstanceLink<T> | Array<InstanceLink<T>> {
+    const instanceLinksForGivenToken = this.instanceLinks.get(token);
 
-    if (!modulesMap) {
+    if (!instanceLinksForGivenToken) {
       throw new UnknownElementException(this.getInstanceNameByToken(token));
     }
-    const instanceLink = moduleId
-      ? modulesMap.find(item => item.moduleId === moduleId)
-      : modulesMap[modulesMap.length - 1];
+
+    if (options.each) {
+      return instanceLinksForGivenToken;
+    }
+
+    const instanceLink = options.moduleId
+      ? instanceLinksForGivenToken.find(
+          item => item.moduleId === options.moduleId,
+        )
+      : instanceLinksForGivenToken[instanceLinksForGivenToken.length - 1];
 
     if (!instanceLink) {
       throw new UnknownElementException(this.getInstanceNameByToken(token));
diff --git a/packages/core/injector/instance-loader.ts b/packages/core/injector/instance-loader.ts
index 0ae7687fd..1b9ecb656 100644
--- a/packages/core/injector/instance-loader.ts
+++ b/packages/core/injector/instance-loader.ts
@@ -4,7 +4,7 @@ import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
 import { MODULE_INIT_MESSAGE } from '../helpers/messages';
 import { NestContainer } from './container';
 import { Injector } from './injector';
-import { InternalCoreModule } from './internal-core-module';
+import { InternalCoreModule } from './internal-core-module/internal-core-module';
 import { Module } from './module';
 
 export class InstanceLoader {
diff --git a/packages/core/injector/internal-core-module/index.ts b/packages/core/injector/internal-core-module/index.ts
new file mode 100644
index 000000000..a863c2132
--- /dev/null
+++ b/packages/core/injector/internal-core-module/index.ts
@@ -0,0 +1,1 @@
+export * from './internal-core-module';
diff --git a/packages/core/injector/internal-core-module-factory.ts b/packages/core/injector/internal-core-module/internal-core-module-factory.ts
similarity index 71%
rename from packages/core/injector/internal-core-module-factory.ts
rename to packages/core/injector/internal-core-module/internal-core-module-factory.ts
index 99ecd589a..c1564e937 100644
--- a/packages/core/injector/internal-core-module-factory.ts
+++ b/packages/core/injector/internal-core-module/internal-core-module-factory.ts
@@ -1,13 +1,13 @@
 import { Logger } from '@nestjs/common';
-import { ExternalContextCreator } from '../helpers/external-context-creator';
-import { HttpAdapterHost } from '../helpers/http-adapter-host';
-import { DependenciesScanner } from '../scanner';
-import { ModuleCompiler } from './compiler';
-import { NestContainer } from './container';
-import { InstanceLoader } from './instance-loader';
+import { ExternalContextCreator } from '../../helpers/external-context-creator';
+import { HttpAdapterHost } from '../../helpers/http-adapter-host';
+import { DependenciesScanner } from '../../scanner';
+import { ModuleCompiler } from '../compiler';
+import { NestContainer } from '../container';
+import { InstanceLoader } from '../instance-loader';
+import { LazyModuleLoader } from '../lazy-module-loader/lazy-module-loader';
+import { ModulesContainer } from '../modules-container';
 import { InternalCoreModule } from './internal-core-module';
-import { LazyModuleLoader } from './lazy-module-loader';
-import { ModulesContainer } from './modules-container';
 
 export class InternalCoreModuleFactory {
   static create(
diff --git a/packages/core/injector/internal-core-module.ts b/packages/core/injector/internal-core-module/internal-core-module.ts
similarity index 80%
rename from packages/core/injector/internal-core-module.ts
rename to packages/core/injector/internal-core-module/internal-core-module.ts
index 3e3a6156f..178818337 100644
--- a/packages/core/injector/internal-core-module.ts
+++ b/packages/core/injector/internal-core-module/internal-core-module.ts
@@ -4,9 +4,9 @@ import {
   FactoryProvider,
   ValueProvider,
 } from '@nestjs/common/interfaces';
-import { requestProvider } from '../router/request/request-providers';
-import { Reflector } from '../services';
-import { inquirerProvider } from './inquirer/inquirer-providers';
+import { requestProvider } from '../../router/request/request-providers';
+import { Reflector } from '../../services';
+import { inquirerProvider } from '../inquirer/inquirer-providers';
 
 const ReflectorAliasProvider = {
   provide: Reflector.name,
diff --git a/packages/core/injector/lazy-module-loader-options.interface.ts b/packages/core/injector/lazy-module-loader/lazy-module-loader-options.interface.ts
similarity index 100%
rename from packages/core/injector/lazy-module-loader-options.interface.ts
rename to packages/core/injector/lazy-module-loader/lazy-module-loader-options.interface.ts
diff --git a/packages/core/injector/lazy-module-loader.ts b/packages/core/injector/lazy-module-loader/lazy-module-loader.ts
similarity index 86%
rename from packages/core/injector/lazy-module-loader.ts
rename to packages/core/injector/lazy-module-loader/lazy-module-loader.ts
index a9f6e29da..df145e05e 100644
--- a/packages/core/injector/lazy-module-loader.ts
+++ b/packages/core/injector/lazy-module-loader/lazy-module-loader.ts
@@ -1,12 +1,12 @@
 import { DynamicModule, Type } from '@nestjs/common';
-import { DependenciesScanner } from '../scanner';
-import { ModuleCompiler } from './compiler';
-import { InstanceLoader } from './instance-loader';
-import { SilentLogger } from './silent-logger';
+import { DependenciesScanner } from '../../scanner';
+import { ModuleCompiler } from '../compiler';
+import { SilentLogger } from '../helpers/silent-logger';
+import { InstanceLoader } from '../instance-loader';
+import { Module } from '../module';
+import { ModuleRef } from '../module-ref';
+import { ModulesContainer } from '../modules-container';
 import { LazyModuleLoaderLoadOptions } from './lazy-module-loader-options.interface';
-import { Module } from './module';
-import { ModuleRef } from './module-ref';
-import { ModulesContainer } from './modules-container';
 
 export class LazyModuleLoader {
   constructor(
diff --git a/packages/core/injector/module-ref.ts b/packages/core/injector/module-ref.ts
index 73d0d2a23..d512e0afc 100644
--- a/packages/core/injector/module-ref.ts
+++ b/packages/core/injector/module-ref.ts
@@ -1,38 +1,104 @@
 import { IntrospectionResult, Scope, Type } from '@nestjs/common';
-import {
-  InvalidClassScopeException,
-  UnknownElementException,
-} from '../errors/exceptions';
+import { GetOrResolveOptions } from '@nestjs/common/interfaces';
 import { getClassScope } from '../helpers/get-class-scope';
 import { isDurable } from '../helpers/is-durable';
+import { AbstractInstanceResolver } from './abstract-instance-resolver';
 import { NestContainer } from './container';
 import { Injector } from './injector';
 import { InstanceLinksHost } from './instance-links-host';
 import { ContextId, InstanceWrapper } from './instance-wrapper';
 import { Module } from './module';
 
-export abstract class ModuleRef {
-  private readonly injector = new Injector();
+export abstract class ModuleRef extends AbstractInstanceResolver {
+  protected readonly injector = new Injector();
   private _instanceLinksHost: InstanceLinksHost;
 
-  private get instanceLinksHost() {
+  protected get instanceLinksHost() {
     if (!this._instanceLinksHost) {
       this._instanceLinksHost = new InstanceLinksHost(this.container);
     }
     return this._instanceLinksHost;
   }
 
-  constructor(protected readonly container: NestContainer) {}
+  constructor(protected readonly container: NestContainer) {
+    super();
+  }
 
-  public abstract get<TInput = any, TResult = TInput>(
+  /**
+   * Retrieves an instance of either injectable or controller, otherwise, throws exception.
+   * @returns {TResult}
+   */
+  abstract get<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+  ): TResult;
+  /**
+   * Retrieves an instance of either injectable or controller, otherwise, throws exception.
+   * @returns {TResult}
+   */
+  abstract get<TInput = any, TResult = TInput>(
     typeOrToken: Type<TInput> | Function | string | symbol,
-    options?: { strict: boolean },
+    options: { strict?: boolean; each?: undefined | false },
   ): TResult;
-  public abstract resolve<TInput = any, TResult = TInput>(
+  /**
+   * Retrieves a list of instances of either injectables or controllers, otherwise, throws exception.
+   * @returns {Array<TResult>}
+   */
+  abstract get<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    options: { strict?: boolean; each: true },
+  ): Array<TResult>;
+  /**
+   * Retrieves an instance (or a list of instances) of either injectable or controller, otherwise, throws exception.
+   * @returns {TResult | Array<TResult>}
+   */
+  abstract get<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    options?: GetOrResolveOptions,
+  ): TResult | Array<TResult>;
+
+  /**
+   * Resolves transient or request-scoped instance of either injectable or controller, otherwise, throws exception.
+   * @returns {Array<TResult>}
+   */
+  abstract resolve<TInput = any, TResult = TInput>(
     typeOrToken: Type<TInput> | Function | string | symbol,
-    contextId?: ContextId,
-    options?: { strict: boolean },
   ): Promise<TResult>;
+  /**
+   * Resolves transient or request-scoped instance of either injectable or controller, otherwise, throws exception.
+   * @returns {Array<TResult>}
+   */
+  abstract resolve<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    contextId?: { id: number },
+  ): Promise<TResult>;
+  /**
+   * Resolves transient or request-scoped instance of either injectable or controller, otherwise, throws exception.
+   * @returns {Array<TResult>}
+   */
+  abstract resolve<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    contextId?: { id: number },
+    options?: { strict?: boolean; each?: undefined | false },
+  ): Promise<TResult>;
+  /**
+   * Resolves transient or request-scoped instances of either injectables or controllers, otherwise, throws exception.
+   * @returns {Array<TResult>}
+   */
+  abstract resolve<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    contextId?: { id: number },
+    options?: { strict?: boolean; each: true },
+  ): Promise<Array<TResult>>;
+  /**
+   * Resolves transient or request-scoped instance (or a list of instances) of either injectable or controller, otherwise, throws exception.
+   * @returns {Promise<TResult | Array<TResult>>}
+   */
+  abstract resolve<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    contextId?: { id: number },
+    options?: GetOrResolveOptions,
+  ): Promise<TResult | Array<TResult>>;
+
   public abstract create<T = any>(type: Type<T>): Promise<T>;
 
   public introspect<T = any>(
@@ -53,54 +119,6 @@ export abstract class ModuleRef {
     this.container.registerRequestProvider(request, contextId);
   }
 
-  protected find<TInput = any, TResult = TInput>(
-    typeOrToken: Type<TInput> | string | symbol,
-    contextModule?: Module,
-  ): TResult {
-    const moduleId = contextModule && contextModule.id;
-    const { wrapperRef } = this.instanceLinksHost.get<TResult>(
-      typeOrToken,
-      moduleId,
-    );
-    if (
-      wrapperRef.scope === Scope.REQUEST ||
-      wrapperRef.scope === Scope.TRANSIENT
-    ) {
-      throw new InvalidClassScopeException(typeOrToken);
-    }
-    return wrapperRef.instance;
-  }
-
-  protected async resolvePerContext<TInput = any, TResult = TInput>(
-    typeOrToken: Type<TInput> | string | symbol,
-    contextModule: Module,
-    contextId: ContextId,
-    options?: { strict: boolean },
-  ): Promise<TResult> {
-    const isStrictModeEnabled = options && options.strict;
-    const instanceLink = isStrictModeEnabled
-      ? this.instanceLinksHost.get(typeOrToken, contextModule.id)
-      : this.instanceLinksHost.get(typeOrToken);
-
-    const { wrapperRef, collection } = instanceLink;
-    if (wrapperRef.isDependencyTreeStatic() && !wrapperRef.isTransient) {
-      return this.get(typeOrToken, options);
-    }
-
-    const ctorHost = wrapperRef.instance || { constructor: typeOrToken };
-    const instance = await this.injector.loadPerContext(
-      ctorHost,
-      wrapperRef.host,
-      collection,
-      contextId,
-      wrapperRef,
-    );
-    if (!instance) {
-      throw new UnknownElementException();
-    }
-    return instance;
-  }
-
   protected async instantiateClass<T = any>(
     type: Type<T>,
     moduleRef: Module,
diff --git a/packages/core/injector/module.ts b/packages/core/injector/module.ts
index b7c73fca9..b8f408f7f 100644
--- a/packages/core/injector/module.ts
+++ b/packages/core/injector/module.ts
@@ -4,12 +4,13 @@ import {
   DynamicModule,
   ExistingProvider,
   FactoryProvider,
+  GetOrResolveOptions,
   Injectable,
   InjectionToken,
   NestModule,
   Provider,
-  ValueProvider,
   Type,
+  ValueProvider,
 } from '@nestjs/common/interfaces';
 import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
 import {
@@ -520,19 +521,27 @@ export class Module {
 
       public get<TInput = any, TResult = TInput>(
         typeOrToken: Type<TInput> | string | symbol,
-        options: { strict: boolean } = { strict: true },
-      ): TResult {
+        options: GetOrResolveOptions = { strict: true },
+      ): TResult | Array<TResult> {
         return !(options && options.strict)
-          ? this.find<TInput, TResult>(typeOrToken)
-          : this.find<TInput, TResult>(typeOrToken, self);
+          ? this.find<TInput, TResult>(typeOrToken, options)
+          : this.find<TInput, TResult>(typeOrToken, {
+              moduleId: self.id,
+              each: options.each,
+            });
       }
 
       public resolve<TInput = any, TResult = TInput>(
         typeOrToken: Type<TInput> | string | symbol,
         contextId = createContextId(),
-        options: { strict: boolean } = { strict: true },
-      ): Promise<TResult> {
-        return this.resolvePerContext(typeOrToken, self, contextId, options);
+        options: GetOrResolveOptions = { strict: true },
+      ): Promise<TResult | Array<TResult>> {
+        return this.resolvePerContext<TInput, TResult>(
+          typeOrToken,
+          self,
+          contextId,
+          options,
+        );
       }
 
       public async create<T = any>(type: Type<T>): Promise<T> {
diff --git a/packages/core/nest-application-context.ts b/packages/core/nest-application-context.ts
index 18c8c6ca7..d050b51d0 100644
--- a/packages/core/nest-application-context.ts
+++ b/packages/core/nest-application-context.ts
@@ -8,17 +8,13 @@ import {
 import {
   Abstract,
   DynamicModule,
-  Scope,
+  GetOrResolveOptions,
   Type,
 } from '@nestjs/common/interfaces';
 import { isEmpty } from '@nestjs/common/utils/shared.utils';
 import { iterate } from 'iterare';
 import { MESSAGES } from './constants';
-import {
-  InvalidClassScopeException,
-  UnknownElementException,
-  UnknownModuleException,
-} from './errors/exceptions';
+import { UnknownModuleException } from './errors/exceptions';
 import { createContextId } from './helpers/context-id-factory';
 import {
   callAppShutdownHook,
@@ -27,6 +23,7 @@ import {
   callModuleDestroyHook,
   callModuleInitHook,
 } from './hooks';
+import { AbstractInstanceResolver } from './injector/abstract-instance-resolver';
 import { ModuleCompiler } from './injector/compiler';
 import { NestContainer } from './injector/container';
 import { Injector } from './injector/injector';
@@ -37,7 +34,10 @@ import { Module } from './injector/module';
 /**
  * @publicApi
  */
-export class NestApplicationContext implements INestApplicationContext {
+export class NestApplicationContext
+  extends AbstractInstanceResolver
+  implements INestApplicationContext
+{
   protected isInitialized = false;
   protected readonly injector = new Injector();
 
@@ -48,7 +48,7 @@ export class NestApplicationContext implements INestApplicationContext {
   private _instanceLinksHost: InstanceLinksHost;
   private _moduleRefsByDistance?: Array<Module>;
 
-  private get instanceLinksHost() {
+  protected get instanceLinksHost() {
     if (!this._instanceLinksHost) {
       this._instanceLinksHost = new InstanceLinksHost(this.container);
     }
@@ -59,13 +59,19 @@ export class NestApplicationContext implements INestApplicationContext {
     protected readonly container: NestContainer,
     private readonly scope = new Array<Type<any>>(),
     private contextModule: Module = null,
-  ) {}
+  ) {
+    super();
+  }
 
   public selectContextModule() {
     const modules = this.container.getModules().values();
     this.contextModule = modules.next().value;
   }
 
+  /**
+   * Allows navigating through the modules tree, for example, to pull out a specific instance from the selected module.
+   * @returns {INestApplicationContext}
+   */
   public select<T>(
     moduleType: Type<T> | DynamicModule,
   ): INestApplicationContext {
@@ -85,21 +91,106 @@ export class NestApplicationContext implements INestApplicationContext {
     return new NestApplicationContext(this.container, scope, selectedModule);
   }
 
+  /**
+   * Retrieves an instance of either injectable or controller, otherwise, throws exception.
+   * @returns {TResult}
+   */
+  public get<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+  ): TResult;
+  /**
+   * Retrieves an instance of either injectable or controller, otherwise, throws exception.
+   * @returns {TResult}
+   */
+  public get<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    options: {
+      strict?: boolean;
+      each?: undefined | false;
+    },
+  ): TResult;
+  /**
+   * Retrieves a list of instances of either injectables or controllers, otherwise, throws exception.
+   * @returns {Array<TResult>}
+   */
+  public get<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    options: {
+      strict?: boolean;
+      each: true;
+    },
+  ): Array<TResult>;
+  /**
+   * Retrieves an instance (or a list of instances) of either injectable or controller, otherwise, throws exception.
+   * @returns {TResult | Array<TResult>}
+   */
   public get<TInput = any, TResult = TInput>(
     typeOrToken: Type<TInput> | Abstract<TInput> | string | symbol,
-    options: { strict: boolean } = { strict: false },
-  ): TResult {
+    options: GetOrResolveOptions = { strict: false },
+  ): TResult | Array<TResult> {
     return !(options && options.strict)
-      ? this.find<TInput, TResult>(typeOrToken)
-      : this.find<TInput, TResult>(typeOrToken, this.contextModule);
+      ? this.find<TInput, TResult>(typeOrToken, options)
+      : this.find<TInput, TResult>(typeOrToken, {
+          moduleId: this.contextModule?.id,
+          each: options.each,
+        });
   }
 
+  /**
+   * Resolves transient or request-scoped instance of either injectable or controller, otherwise, throws exception.
+   * @returns {Array<TResult>}
+   */
+  public resolve<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+  ): Promise<TResult>;
+  /**
+   * Resolves transient or request-scoped instance of either injectable or controller, otherwise, throws exception.
+   * @returns {Array<TResult>}
+   */
+  public resolve<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    contextId?: {
+      id: number;
+    },
+  ): Promise<TResult>;
+  /**
+   * Resolves transient or request-scoped instance of either injectable or controller, otherwise, throws exception.
+   * @returns {Array<TResult>}
+   */
+  public resolve<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    contextId?: {
+      id: number;
+    },
+    options?: {
+      strict?: boolean;
+      each?: undefined | false;
+    },
+  ): Promise<TResult>;
+  /**
+   * Resolves transient or request-scoped instances of either injectables or controllers, otherwise, throws exception.
+   * @returns {Array<TResult>}
+   */
+  public resolve<TInput = any, TResult = TInput>(
+    typeOrToken: Type<TInput> | Function | string | symbol,
+    contextId?: {
+      id: number;
+    },
+    options?: {
+      strict?: boolean;
+      each: true;
+    },
+  ): Promise<Array<TResult>>;
+  /**
+   * Resolves transient or request-scoped instance (or a list of instances) of either injectable or controller, otherwise, throws exception.
+   * @returns {Promise<TResult | Array<TResult>>}
+   */
   public resolve<TInput = any, TResult = TInput>(
     typeOrToken: Type<TInput> | Abstract<TInput> | string | symbol,
     contextId = createContextId(),
-    options: { strict: boolean } = { strict: false },
-  ): Promise<TResult> {
-    return this.resolvePerContext(
+    options: GetOrResolveOptions = { strict: false },
+  ): Promise<TResult | Array<TResult>> {
+    return this.resolvePerContext<TInput, TResult>(
       typeOrToken,
       this.contextModule,
       contextId,
@@ -107,6 +198,10 @@ export class NestApplicationContext implements INestApplicationContext {
     );
   }
 
+  /**
+   * Registers the request/context object for a given context ID (DI container sub-tree).
+   * @returns {void}
+   */
   public registerRequestByContextId<T = any>(request: T, contextId: ContextId) {
     this.container.registerRequestProvider(request, contextId);
   }
@@ -128,6 +223,10 @@ export class NestApplicationContext implements INestApplicationContext {
     return this;
   }
 
+  /**
+   * Terminates the application
+   * @returns {Promise<void>}
+   */
   public async close(): Promise<void> {
     await this.callDestroyHook();
     await this.callBeforeShutdownHook();
@@ -136,6 +235,11 @@ export class NestApplicationContext implements INestApplicationContext {
     this.unsubscribeFromProcessSignals();
   }
 
+  /**
+   * Sets custom logger service.
+   * Flushes buffered logs if auto flush is on.
+   * @returns {void}
+   */
   public useLogger(logger: LoggerService | LogLevel[] | false) {
     Logger.overrideLogger(logger);
 
@@ -144,6 +248,10 @@ export class NestApplicationContext implements INestApplicationContext {
     }
   }
 
+  /**
+   * Prints buffered logs and detaches buffer.
+   * @returns {void}
+   */
   public flushLogs() {
     Logger.flush();
   }
@@ -290,54 +398,6 @@ export class NestApplicationContext implements INestApplicationContext {
     }
   }
 
-  protected find<TInput = any, TResult = TInput>(
-    typeOrToken: Type<TInput> | Abstract<TInput> | string | symbol,
-    contextModule?: Module,
-  ): TResult {
-    const moduleId = contextModule && contextModule.id;
-    const { wrapperRef } = this.instanceLinksHost.get<TResult>(
-      typeOrToken,
-      moduleId,
-    );
-    if (
-      wrapperRef.scope === Scope.REQUEST ||
-      wrapperRef.scope === Scope.TRANSIENT
-    ) {
-      throw new InvalidClassScopeException(typeOrToken);
-    }
-    return wrapperRef.instance;
-  }
-
-  protected async resolvePerContext<TInput = any, TResult = TInput>(
-    typeOrToken: Type<TInput> | Abstract<TInput> | string | symbol,
-    contextModule: Module,
-    contextId: ContextId,
-    options?: { strict: boolean },
-  ): Promise<TResult> {
-    const isStrictModeEnabled = options && options.strict;
-    const instanceLink = isStrictModeEnabled
-      ? this.instanceLinksHost.get(typeOrToken, contextModule.id)
-      : this.instanceLinksHost.get(typeOrToken);
-
-    const { wrapperRef, collection } = instanceLink;
-    if (wrapperRef.isDependencyTreeStatic() && !wrapperRef.isTransient) {
-      return this.get(typeOrToken, options);
-    }
-
-    const ctorHost = wrapperRef.instance || { constructor: typeOrToken };
-    const instance = await this.injector.loadPerContext(
-      ctorHost,
-      wrapperRef.host,
-      collection,
-      contextId,
-      wrapperRef,
-    );
-    if (!instance) {
-      throw new UnknownElementException();
-    }
-    return instance;
-  }
-
   private getModulesSortedByDistance(): Module[] {
     if (this._moduleRefsByDistance) {
       return this._moduleRefsByDistance;
diff --git a/packages/core/repl/repl-context.ts b/packages/core/repl/repl-context.ts
index bfc5ef234..4745b8f8e 100644
--- a/packages/core/repl/repl-context.ts
+++ b/packages/core/repl/repl-context.ts
@@ -5,7 +5,7 @@ import {
 } from '@nestjs/common';
 import { ApplicationConfig } from '../application-config';
 import { ModuleRef, NestContainer } from '../injector';
-import { InternalCoreModule } from '../injector/internal-core-module';
+import { InternalCoreModule } from '../injector/internal-core-module/internal-core-module';
 import { Module } from '../injector/module';
 import {
   DebugReplFn,
diff --git a/packages/core/scanner.ts b/packages/core/scanner.ts
index 97a78f66d..0bf28015f 100644
--- a/packages/core/scanner.ts
+++ b/packages/core/scanner.ts
@@ -46,7 +46,7 @@ import { UndefinedModuleException } from './errors/exceptions/undefined-module.e
 import { getClassScope } from './helpers/get-class-scope';
 import { NestContainer } from './injector/container';
 import { InstanceWrapper } from './injector/instance-wrapper';
-import { InternalCoreModuleFactory } from './injector/internal-core-module-factory';
+import { InternalCoreModuleFactory } from './injector/internal-core-module/internal-core-module-factory';
 import { Module } from './injector/module';
 import { MetadataScanner } from './metadata-scanner';
 


Test patch:
diff --git a/integration/injector/e2e/multiple-providers.spec.ts b/integration/injector/e2e/multiple-providers.spec.ts
new file mode 100644
index 000000000..26daa0e43
--- /dev/null
+++ b/integration/injector/e2e/multiple-providers.spec.ts
@@ -0,0 +1,47 @@
+import { Test } from '@nestjs/testing';
+import { expect } from 'chai';
+import { MultipleProvidersModule } from '../src/multiple-providers/multiple-providers.module';
+
+describe('Multiple providers under the same token ("each" feature)', () => {
+  describe('get()', () => {
+    it('should return an array of providers', async () => {
+      const builder = Test.createTestingModule({
+        imports: [MultipleProvidersModule],
+      });
+      const testingModule = await builder.compile();
+
+      const multiProviderInstances = testingModule.get<string>(
+        'MULTI_PROVIDER',
+        {
+          each: true,
+        },
+      );
+
+      // @ts-expect-error: make sure "multiProviderInstances" is string[] not string
+      multiProviderInstances.charAt;
+
+      expect(multiProviderInstances).to.be.eql(['A', 'B', 'C']);
+    });
+  });
+  describe('resolve()', () => {
+    it('should return an array of providers', async () => {
+      const builder = Test.createTestingModule({
+        imports: [MultipleProvidersModule],
+      });
+      const testingModule = await builder.compile();
+
+      const multiProviderInstances = await testingModule.resolve<string>(
+        'REQ_SCOPED_MULTI_PROVIDER',
+        undefined,
+        {
+          each: true,
+        },
+      );
+
+      // @ts-expect-error: make sure "multiProviderInstances" is string[] not string
+      multiProviderInstances.charAt;
+
+      expect(multiProviderInstances).to.be.eql(['A', 'B', 'C']);
+    });
+  });
+});
diff --git a/packages/core/test/injector/internal-core-module-factory.spec.ts b/packages/core/test/injector/internal-core-module/internal-core-module-factory.spec.ts
similarity index 66%
rename from packages/core/test/injector/internal-core-module-factory.spec.ts
rename to packages/core/test/injector/internal-core-module/internal-core-module-factory.spec.ts
index 92499cf95..6222e4d2a 100644
--- a/packages/core/test/injector/internal-core-module-factory.spec.ts
+++ b/packages/core/test/injector/internal-core-module/internal-core-module-factory.spec.ts
@@ -1,11 +1,11 @@
 import { ClassProvider, FactoryProvider } from '@nestjs/common';
 import { expect } from 'chai';
-import { ExternalContextCreator } from '../../helpers/external-context-creator';
-import { HttpAdapterHost } from '../../helpers/http-adapter-host';
-import { LazyModuleLoader, ModulesContainer } from '../../injector';
-import { NestContainer } from '../../injector/container';
-import { InternalCoreModule } from '../../injector/internal-core-module';
-import { InternalCoreModuleFactory } from '../../injector/internal-core-module-factory';
+import { ExternalContextCreator } from '../../../helpers/external-context-creator';
+import { HttpAdapterHost } from '../../../helpers/http-adapter-host';
+import { LazyModuleLoader, ModulesContainer } from '../../../injector';
+import { NestContainer } from '../../../injector/container';
+import { InternalCoreModule } from '../../../injector/internal-core-module/internal-core-module';
+import { InternalCoreModuleFactory } from '../../../injector/internal-core-module/internal-core-module-factory';
 
 describe('InternalCoreModuleFactory', () => {
   it('should return the internal core module definition', () => {
diff --git a/packages/core/test/injector/lazy-module-loader.spec.ts b/packages/core/test/injector/lazy-module-loader/lazy-module-loader.spec.ts
similarity index 89%
rename from packages/core/test/injector/lazy-module-loader.spec.ts
rename to packages/core/test/injector/lazy-module-loader/lazy-module-loader.spec.ts
index 35b0f8a45..24cb6f7af 100644
--- a/packages/core/test/injector/lazy-module-loader.spec.ts
+++ b/packages/core/test/injector/lazy-module-loader/lazy-module-loader.spec.ts
@@ -5,10 +5,10 @@ import {
   ModuleRef,
   ModulesContainer,
   NestContainer,
-} from '../../injector';
-import { InstanceLoader } from '../../injector/instance-loader';
-import { MetadataScanner } from '../../metadata-scanner';
-import { DependenciesScanner } from '../../scanner';
+} from '../../../injector';
+import { InstanceLoader } from '../../../injector/instance-loader';
+import { MetadataScanner } from '../../../metadata-scanner';
+import { DependenciesScanner } from '../../../scanner';
 
 describe('LazyModuleLoader', () => {
   let lazyModuleLoader: LazyModuleLoader;