Skip to content

ModuleRef cannot resolve() multiple default-scoped providers registered to the same token #15979

@hajekjiri

Description

@hajekjiri

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

Imagine a situation where there are N modules with providers MyService1, MyService2, ..., MyServiceN (one provider per module), the providers are all default-scoped and registered under the same token MY_SERVICE.

When I attempt to resolve all instances with

moduleRef.resolve(MY_SERVICE, undefined, { strict: false, each: true })

I receive an array with N items: [MyServiceN {}, MyServiceN {}, ..., MyServiceN {}] (the particular provider depends on the order of imports in AppModule).

In the production code, some of the providers are request-scoped, which is why I needed to use ModuleRef.resolve() and ModuleRef.get() would not work (although it works fine for this example with only default-scoped providers).

Minimum reproduction code

https://github.com/hajekjiri/nestjs-same-token-multiresolution

Steps to reproduce

  1. npm install
  2. npm test

Expected behavior

Given the described situation and the resolve call

moduleRef.resolve(MY_SERVICE, undefined, { strict: false, each: true })

I would expect to receive an array with all of the providers: [MyService1 {}, MyService2 {}, ..., MyServiceN {}].

NestJS version

11.1.9

Packages versions

{
  "scripts": {
    "test": "jest",
    "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --no-cache --detectOpenHandles --forceExit app.test.ts"
  },
  "dependencies": {
    "@nestjs/common": "^11.1.9",
    "@nestjs/core": "^11.1.9",
    "@nestjs/platform-express": "^11.1.9",
    "reflect-metadata": "^0.2.2",
    "rxjs": "^7.8.2"
  },
  "devDependencies": {
    "@nestjs/testing": "^11.1.9",
    "@types/jest": "^30.0.0",
    "jest": "^30.2.0",
    "ts-jest": "^29.4.5",
    "typescript": "^5.9.3"
  }
}

Node.js version

22.17.0

In which operating systems have you tested?

  • macOS
  • Windows
  • Linux

Other

The issue lies in this condition (AbstractInstanceResolver), which always passes for default-scoped providers, then leads to this line (InstanceLinksHost) and always returns the last provider in the array (always the same one).

I came up with two ideas to fix this issue, none of which breaks any existing tests. Let me know what you think and I'll gladly submit a PR.

Proposal 1: Add && !options?.each to the condition

if (wrapperRef.isDependencyTreeStatic() && !wrapperRef.isTransient && !options?.each) {
  // ...
}

This would force multiresolution to go through the other code path (intended for request-scoped and transient-scoped providers), which resolves all providers just fine.

Proposal 2: Return instance from wrapper directly

Not sure what this could affect, but in case the condition passes, we could replace the return statement

return this.get(typeOrToken, { strict: options?.strict });

with

return wrapperRef.instance;

and return the instance directly from the wrapper.

I think we might also have to extend the condition to check if the instance was successfully resolved in the wrapper.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs triageThis issue has not been looked into

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions