Home > Back-end >  Typing is not checked for classes returned by factory function in typescript
Typing is not checked for classes returned by factory function in typescript

Time:09-27

I've created a module factory function with returns a class annotated by @Module({}). My problem is, the class depends on function arguments providerToken and strategy so I cannot move it outside the function. When I run the code, it works perfectly fine but the value for forRoot and forRootAsync is not properly checked for types. Infact typescript doesn't throw any error about that. Also what should be my return value of the function. I've put it any to avoid errors for now.

This is how I'm using the function to create a module

const TwitterAuthModule =
  createHybridAuthModule<TwitterAuthModuleOptions>(
    TWITTER_HYBRID_AUTH_OPTIONS,
    TwitterAuthStrategy
  );

Module creator factory

export function createHybridAuthModule<T>(
  providerToken: string,
  strategy: any
): any {
  @Module({})
  class NestHybridAuthModule {
    static forRoot(options: T): DynamicModule {
      return {
        module: NestHybridAuthModule,
        providers: [
          {
            provide: providerToken,
            useValue: options,
          },
          strategy,
        ],
      };
    }

    static forRootAsync(
      options: ModuleAsyncOptions<ModuleOptionsFactory<T>, T>
    ): DynamicModule {
      return {
        module: NestHybridAuthModule,
        providers: [...this.createAsyncProviders(options), strategy],
      };
    }

    private static createAsyncProviders(
      options: ModuleAsyncOptions<ModuleOptionsFactory<T>, T>
    ): Provider[] {
      if (options.useExisting || options.useFactory) {
        return [this.createAsyncOptionsProvider(options)];
      }
      const useClass = options.useClass as Type<ModuleOptionsFactory<T>>;
      return [
        this.createAsyncOptionsProvider(options),
        {
          provide: useClass,
          useClass,
        },
      ];
    }

    private static createAsyncOptionsProvider(
      options: ModuleAsyncOptions<ModuleOptionsFactory<T>, T>
    ): Provider {
      if (options.useFactory) {
        return {
          provide: providerToken,
          useFactory: options.useFactory,
          inject: options.inject || [],
        };
      }

      const inject = [
        (options.useClass || options.useExisting) as Type<
          ModuleOptionsFactory<T>
        >,
      ];

      return {
        provide: providerToken,
        useFactory: async (optionsFactory: ModuleOptionsFactory<T>) =>
          await optionsFactory.createModuleOptions(),
        inject,
      };
    }
  }

  return NestHybridAuthModule;
}

Usage of the created module. The value of forRoot is never checked for types

@Module({
  imports: [
    TwitterAuthModule.forRoot({
      consumerKey: '********',
      consumerSecret: '******',
      callbackURL: '*******',
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

CodePudding user response:

Reason why typescript not check your types is that it treat NestHybridAuthModule as any because createHybridAuthModule function return type is any. Remove it and allow typescript infer return type.

In case there is --noImplicitReturns used you can use generic interface

simplified example:

interface INestHybridAuthModule<T> {
    forRoot(options: T): DynamicModule
}
export function createHybridAuthModule<T>(
    providerToken: string,
    strategy: any
): new (...args: any[]) => INestHybridAuthModule<T> {
    class NestHybridAuthModule {
        forRoot(options: T): DynamicModule {
            // logic here
        }
    }
    return NestHybridAuthModule
}

CodePudding user response:

I finally found my answer at https://stackoverflow.com/a/43674389/1029506 and implemented it using a customer decorator:

export interface INestHybridAuthModule<T> {
  forRoot(options: T): DynamicModule;
}

function staticImplements<T>() {
  return <U extends T>(constructor: U) => {
    constructor;
  };
}

function createHybridAuthModule<T>(
    providerToken: string,
    strategy: any
  ): INestHybridAuthModule<T> {
    @Module({})
    @staticImplements<INestHybridAuthModule<T>>()
    class NestHybridAuthModule {
      static forRoot(options: T): DynamicModule {
        // code here
      }
  
      static forRootAsync(
        options: ModuleAsyncOptions<ModuleOptionsFactory<T>, T>
      ): DynamicModule {
        // Code here
      }
  
      private static createAsyncProviders(
        options: ModuleAsyncOptions<ModuleOptionsFactory<T>, T>
      ): Provider[] {
        // Code here
      }
  
      private static createAsyncOptionsProvider(
        options: ModuleAsyncOptions<ModuleOptionsFactory<T>, T>
      ): Provider {
        // Code here
      }
    }
  
    return NestHybridAuthModule;
  }

And Finally

const TwitterAuthModule: INestHybridAuthModule<TwitterAuthModuleOptions> = createHybridAuthModule<
  TwitterAuthModuleOptions
>(TWITTER_HYBRID_AUTH_OPTIONS, TwitterAuthStrategy);
  • Related