nest icon indicating copy to clipboard operation
nest copied to clipboard

[9.x.x] Duplicate Provider names don't get shown in REPL-debug() function

Open BrunnerLivio opened this issue 1 year ago • 15 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Current behavior

Given the following Diagram:

graph TD;
  AppModule((AppModule))-->CatModule((CatModule))
  AppModule-->AppService_A>AppService]
  AppModule-->foo_A>provide: 'Foo', useValue: 'bar']

  CatModule-->AppService_B>AppService]
  CatModule-->foo_B>provide: 'Foo', useValue: 'barbaz']
  CatModule-->CatService>CatService]

Both the AppModule and CatModule have providers with the same name but different implementations/instances. When using the NestJS repl() method and print a debug() statement in order to list the application context, the AppService & Foo provider does not get listed within the CatModule.

[Nest] 5340  - 07/08/2022, 11:18:42 AM     LOG [NestFactory] Starting Nest application...
[Nest] 5340  - 07/08/2022, 11:18:42 AM     LOG [InstanceLoader] CatModule dependencies initialized
[Nest] 5340  - 07/08/2022, 11:18:42 AM     LOG [InstanceLoader] AppModule dependencies initialized
[Nest] 5340  - 07/08/2022, 11:18:42 AM     LOG REPL initialized
> debug()

AppModule:
 - controllers:
  â—» AppController
 - providers:
  â—» AppService
  â—» Foo
CatModule:
 - providers:
  â—» CatService

Minimum reproduction code

https://github.com/BrunnerLivio/nestjs-repro-duplicate-provider-repl

Steps to reproduce

  1. npm ci
  2. npm run start:dev
  3. Write debug() within REPL console

Expected behavior

Should list all providers

[Nest] 5340  - 07/08/2022, 11:18:42 AM     LOG [NestFactory] Starting Nest application...
[Nest] 5340  - 07/08/2022, 11:18:42 AM     LOG [InstanceLoader] CatModule dependencies initialized
[Nest] 5340  - 07/08/2022, 11:18:42 AM     LOG [InstanceLoader] AppModule dependencies initialized
[Nest] 5340  - 07/08/2022, 11:18:42 AM     LOG REPL initialized
$ debug()

AppModule:
 - controllers:
  â—» AppController
 - providers:
  â—» AppService
  â—» Foo
CatModule:
 - providers:
  â—» CatService
+ â—» AppService
+ â—» Foo

Package

  • [ ] I don't know. Or some 3rd-party package
  • [ ] @nestjs/common
  • [X] @nestjs/core
  • [ ] @nestjs/microservices
  • [ ] @nestjs/platform-express
  • [ ] @nestjs/platform-fastify
  • [ ] @nestjs/platform-socket.io
  • [ ] @nestjs/platform-ws
  • [ ] @nestjs/testing
  • [ ] @nestjs/websockets
  • [ ] Other (see below)

Other package

No response

NestJS version

9.0.0

Packages versions

platform-express version : 9.0.0
schematics version       : 9.0.1
testing version          : 9.0.0
common version           : 9.0.0
core version             : 9.0.0
cli version              : 9.0.0

Node.js version

16.15.0

In which operating systems have you tested?

  • [ ] macOS
  • [X] Windows
  • [ ] Linux

Other

No response

BrunnerLivio avatar Jul 08 '22 09:07 BrunnerLivio

Sorry for this issue. You just released an AWESOME Nest V9 and now I already have to report an edge case :P Low prio obviously (in case you consider this as unexpected behavior), I am just playing dumb user haha

BrunnerLivio avatar Jul 08 '22 09:07 BrunnerLivio

I was expecting a lot of edge cases for the REPL tbh

I'll investigate this issue tomorrow

micalevisk avatar Jul 08 '22 11:07 micalevisk

Should be easy to fix, luckily. Thank you @BrunnerLivio!

kamilmysliwiec avatar Jul 08 '22 12:07 kamilmysliwiec

One thing to keep in mind; the REPL seems to automatically load in all the providers & modules into the REPL-context. Though since there are two providers with the same name the following is ambigous (given the example above):

// main.ts
await repl(AppModule);
...

// REPL
[Nest] 5922  - 07/08/2022, 2:17:14 PM     LOG REPL initialized
$ methods(AppService)

Methods:
+ â—» getHello

If the CatModule is loaded instead:

// main.ts
await repl(CatModule);
...

// REPL
[Nest] 5922  - 07/08/2022, 2:17:14 PM     LOG REPL initialized
$ methods(AppService)

Methods:
+ â—» getDog

I believe it picks the "nearest" provider first? I think if await repl(AppModule) is executed one wouldn't be able to target AppService of CatModule, right? Maybe we should consider a REPL command like this:

> module(CatModule).get(AppService).methods()

Though I leave it up to you to decide. In the end I believe this is an edge-case. Not a lot of people would fall into this behavior.

BrunnerLivio avatar Jul 08 '22 12:07 BrunnerLivio

Maybe we should consider a REPL command like this:

This is already feasible with select (instead of module())

kamilmysliwiec avatar Jul 08 '22 12:07 kamilmysliwiec

@kamilmysliwiec Yeah i saw that -- though even with select(CatModule).get(AppService) it will select the AppService of AppModule - not the one from CatModule.

Technically it's possible to get to the actual AppService of CatModule like this:

> const AppService_Cat = await import('./dist/cat/app.service.js').then(m => m.AppService)
> methods(AppService_Cat)
Methods:
 â—» getDog

> select(CatModule).get(AppService_Cat).getDog()
'Bob'

Though thinking further about this case - I think it makes sense the way it functions now (or at least I can't figure out a better way). So let's ignore that ^

BrunnerLivio avatar Jul 08 '22 13:07 BrunnerLivio

another thing on this issue:

image

it is always selecting the provider registered on CatModule

image image

micalevisk avatar Jul 09 '22 21:07 micalevisk

@micalevisk what Module is in your example being loaded via await repl(…)?

BrunnerLivio avatar Jul 09 '22 22:07 BrunnerLivio

I'm using your repro repo. It is repl(AppModule)

micalevisk avatar Jul 09 '22 22:07 micalevisk

if we drop the this.globalScope[stringifiedToken] assertion below and change that configurable to true, debug() will display the desired output

image

https://github.com/nestjs/nest/blob/34cb6001c30b3e2d2a9570bc87c6bd78a8c49524/packages/core/repl/repl-context.ts#L81-L87 https://github.com/nestjs/nest/blob/34cb6001c30b3e2d2a9570bc87c6bd78a8c49524/packages/core/repl/repl-context.ts#L91

but that didn't solve this another issue:

> $('Foo')
barbaz

it should display Bar as we called repl(AppModule). As of now we're always overwritting the last provider that has the same stringified token

micalevisk avatar Jul 11 '22 03:07 micalevisk

@BrunnerLivio

though even with select(CatModule).get(AppService) it will select the AppService of AppModule - not the one from CatModule.

we need to supply { strict: true } to the get() otherwise the last (or the first?) registered one will be taken.

I thought this was the default behavior but looks like it's not:

demo
import { Module, Injectable, Controller, Get, Query } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';

@Injectable()
class FooService {}

@Module({
  providers: [{ provide: 'qux', useValue: 'bar' }, { provide: FooService, useValue: 'bar-service' }]
})
export class BarModule {}

@Module({
  imports: [BarModule],
  providers: [FooService, { provide: 'qux', useValue: 'foo' }],
})
export class FooModule {}

async function bootstrap() {
  const app = await NestFactory.create(FooModule)
  await app.init()

  const ctxFooModule = app.select(FooModule)
  const ctxBarModule = app.select(BarModule)

  console.log(
    ctxFooModule.get('qux') // 'bar'
    ,
    ctxFooModule.get('qux', { strict: true }) // 'foo'
    ,
    ctxFooModule.get('qux') == ctxBarModule.get('qux') // true
    ,
    ctxFooModule.get('qux',{strict:true}) == ctxBarModule.get('qux',{strict:true}) // false
  )
}

bootstrap();

image

micalevisk avatar Jul 31 '22 03:07 micalevisk

due to the above described behavior, $(duplicatedToken) ends up returning the wrong provider like so:

image

but if we use strict: true below, we got a Nest could not find x element

https://github.com/nestjs/nest/blob/81df0dde0881b0680808798565bc88a0e65dc2ed/packages/core/repl/native-functions/get-relp-fn.ts#L14-L15

micalevisk avatar Jul 31 '22 13:07 micalevisk

Guys, what's the solution for this issue? I can code it but I didn't understood what needs to be done

ologbonowiwi avatar Aug 06 '22 12:08 ologbonowiwi

@ologbonowiwi another bug that I believe isn't directly related to this issue is that: when we have a provider with the same token registered in multiple modules, the latest registered one is always selected when calling get("token") even if we call it like this:
select(AppModule).get("token")
an workaround to that is select(AppModule).get("token", {strict:true})

micalevisk avatar Aug 06 '22 15:08 micalevisk

@micalevisk that's how NestApplicationContext instance behaves by default (this isn't a bug)

kamilmysliwiec avatar Aug 09 '22 12:08 kamilmysliwiec