jmcdo29 / nest-commander

A module for using NestJS to build up CLI applications
https://nest-commander.jaymcdoniel.dev/
MIT License
428 stars 53 forks source link

How to use @RequestModule() #1000

Closed aryagelato closed 1 year ago

aryagelato commented 1 year ago

Is there an existing issue for this?

Current behavior

I was trying to import a service into the command by Dependency Injection but it gives me an error:

ERROR [CommandRunnerService] A service tried to call a property of "undefined" in the EcomProductEventsHandleCommand class. Did you use a request scoped provider without the @RequestModule() decorator?

I have checked your documentation and there is no example of it!

Minimum reproduction code

import { Command, CommandRunner, Option } from 'nest-commander';
import { Logger } from '../../common/logger/logger.service';
import { PriceRmqHandlerService } from '../services/price-rmq-handler.service';

interface CommandOptions {
  data?: string;
}

@Command({ name: 'ecom-product-events-handle', description: 'Handling product events from shared bus' })
export class EcomProductEventsHandleCommand extends CommandRunner {
  constructor(
    private readonly logger: Logger,
    private readonly priceRmqHandlerService: PriceRmqHandlerService,
    ) {
    super()
  }

  async run(inputs: string[], options?: CommandOptions): Promise<void> {
    if (options?.data) {
      this.runWithData(inputs, options?.data);
    } else {
      this.runWithNone()
    }
    process.exit(0)
  }

  @Option({
    flags: '-d, --data json',
    description: 'A JSON data parser',
  })
  parseJson(val: string): Object {
    return JSON.parse(val);
  }

  runWithData(param: string[], option: string): void {
    console.log("Running with data...")
  }

  runWithNone(): void {
    console.log("Running...")
  }
}

Expected behavior

because we are importing the main module in main-cli.ts like this:

async function bootstrap() {
  await CommandFactory.run(AppModule, ['warn', 'error']);
}

should do the DI automatically!

Package

Package version

3.11.0

Node.js version

16

In which operating systems have you tested?

Other

No response

jmcdo29 commented 1 year ago

There is documentation for it. And here's the API docs too

More than likely, one of the providers you are injecting is REQUEST scoped, which is what caused the error to trigger. If I had to guess without a reproduction, I would say it's the logger.

aryagelato commented 1 year ago

There is documentation for it. And here's the API docs too

I created the module for the command and imported it into the AppModule like this:

@RequestModule({
    imports: [SharedModule],
    providers: [
        EcomProductEventsHandleCommand,
        Logger,
        PriceRmqHandlerService
    ],
    requestObject: { headers: { Authorization: 'Bearer token' } }
  })
  export class PriceCommandModule {}

but still, it's giving me the same error

jmcdo29 commented 1 year ago

Then please provide a minimum reproduction repository (Git repository/StackBlitz/CodeSandbox project).

why reproductions are required

aryagelato commented 1 year ago

StackBlitz

Sure! Here we go. you can run it like this npm run command-nest:dev ecom-product-events-handle

https://stackblitz.com/edit/nestjs-typescript-starter-cvtmuu

jmcdo29 commented 1 year ago

Explicitly setting the scope to Scope.REQUEST messes up the possibility of using the @RequestModule() due to how Nest then treats the provider. What @RequestModule() is doing is creating a local REQUEST provider that is explicitly Scope.DEFAULT so that Nest's automatic resolution of REQUEST goes to the local definition and it becomes default scoped. But if the @Injectable({ scope: Scope.REQUEST }) is set, then that provider will always be REQUEST scoped, even if none of the providers inside of it are, which causes the error to come forth. Remove that setting in your RequestService and everything should work