golevelup / nestjs

A collection of badass modules and utilities to help you level up your NestJS applications 🚀
MIT License
2.23k stars 259 forks source link

Creating a decorator and use it within the application from external provider #654

Closed tonydspaniard closed 11 months ago

tonydspaniard commented 11 months ago

First of all, I would like to apologize if this questions seems too newbie in the advanced concepts of NestJs but I am going a bit nuts trying to figure out what is going on.

I have created a decorator:

export const InjectS3Client = makeInjectableDecorator(AWS_S3_CLIENT_TOKEN);

I have added the declaration of it into a dynamic root module:

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { awsConfig } from './config';
import { AWS_S3_CLIENT_TOKEN } from './constants';
import * as ClientS3 from '@aws-sdk/client-s3';
import { S3 } from '@aws-sdk/client-s3';

@Module({
  imports: [
    ConfigModule.forFeature(awsConfig),
  ],
  providers: [
    {
      provide: AWS_S3_CLIENT_TOKEN,
      useFactory: (config: ConfigService) => {
        return new S3(<ClientS3.S3ClientConfig>{
          credentials: config.get('aws.credentials'),
          region: config.get('aws.region'),
          forcePathStyle: true,
          signatureVersion: 'v4',
        });
      },
      inject: [ConfigService],
    },
  ],
  exports: [AWS_S3_CLIENT_TOKEN],
})
export class AwsProviderModule{}

When I run the tests, all is working:

image

But then, when I plug the module into my app.module and I want to use the decorator in a service:

@Injectable()
export class AwsS3Service {
  private readonly bucket: string | undefined;
  private readonly logger: Logger = new Logger(AwsS3Service.name);

  constructor(@InjectS3Client() private readonly s3: S3, private readonly configService: ConfigService) {
    this.bucket = this.configService.get('aws.s3.bucket');
  }
 [Nest] 96441  - 10/13/2023, 11:44:12 AM   ERROR [ExceptionHandler] Nest can't resolve dependencies of the AwsS3Service (?, ConfigService). Please make sure that the argument Symbol(AWS_S3_CLIENT_TOKEN) at index [0] is available in the UploaderProviderModule context.

Potential solutions:
- Is UploaderProviderModule a valid NestJS module?
- If Symbol(AWS_S3_CLIENT_TOKEN) is a provider, is it part of the current UploaderProviderModule?
- If Symbol(AWS_S3_CLIENT_TOKEN) is exported from a separate @Module, is that module imported within UploaderProviderModule?
  @Module({
    imports: [ /* the Module containing Symbol(AWS_S3_CLIENT_TOKEN) */ ]
  })

Error: Nest can't resolve dependencies of the AwsS3Service (?, ConfigService). Please make sure that the argument Symbol(AWS_S3_CLIENT_TOKEN) at index [0] is available in the UploaderProviderModule context.

Potential solutions:
- Is UploaderProviderModule a valid NestJS module?
- If Symbol(AWS_S3_CLIENT_TOKEN) is a provider, is it part of the current UploaderProviderModule?
- If Symbol(AWS_S3_CLIENT_TOKEN) is exported from a separate @Module, is that module imported within UploaderProviderModule?
  @Module({
    imports: [ /* the Module containing Symbol(AWS_S3_CLIENT_TOKEN) */ ]
  })

I tried the above solutions, I looked into how you created the stripe client, and is exactly the same. What am I missing?

tonydspaniard commented 11 months ago

Looks like this https://github.com/golevelup/nestjs/issues/499 issue, but I also tried creating a dynamic root module:

import { Module } from '@nestjs/common';
import { AWS_MODULE_CONFIG_TOKEN, AWS_S3_CLIENT_TOKEN } from './constants';
import * as ClientS3 from '@aws-sdk/client-s3';
import { S3 } from '@aws-sdk/client-s3';
import { createConfigurableDynamicRootModule } from '@golevelup/nestjs-modules';
import { AwsModuleConfig } from './aws-vendor.interfaces';
import { AwsS3Service } from './aws-vendor-s3.service';

@Module({})
export class AwsVendorProviderModule extends createConfigurableDynamicRootModule<AwsVendorProviderModule, AwsModuleConfig>(
  AWS_MODULE_CONFIG_TOKEN,
  {
    providers: [
      {
        provide: AWS_S3_CLIENT_TOKEN,
        useFactory: ({ s3 }: AwsModuleConfig): S3 => {
          return new S3(<ClientS3.S3ClientConfig>s3);
        },
        inject: [AWS_MODULE_CONFIG_TOKEN]
      },
      AwsS3Service
    ],
    exports: [AWS_MODULE_CONFIG_TOKEN, AWS_S3_CLIENT_TOKEN, AwsS3Service]
  }
) {
}

Added to the App.Module, but when I try to use the AwsS3Service that contains the decorator, it fails even if I add AwsVendorProviderModule.externallyConfigured(AwsVendorProviderModule, 0), to the module that contains the issue. It gotta be something very simple, but I cannot figured it out...

tonydspaniard commented 11 months ago

I solved doing the following, that looks to me like a hack:

Currently works, but I feel this is a hack. If you have a better solution I am all ears. Thank you!

tonydspaniard commented 11 months ago

Found the problem, the issue was to make use of path aliases within the structure of the application, changing them to relative paths everything works.