@nestjs/common#OnApplicationShutdown TypeScript Examples

The following examples show how to use @nestjs/common#OnApplicationShutdown. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: app.module.ts    From amplication with Apache License 2.0 5 votes vote down vote up
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: ['.env.local', '.env']
    }),
    SendGridModule.forRootAsync({
      imports: [ConfigModule, GoogleSecretsManagerModule],
      inject: [ConfigService, GoogleSecretsManagerService],
      useClass: SendgridConfigService
    }),
    ServeStaticModule.forRoot({
      rootPath: path.join(
        __dirname,
        '..',
        '..',
        '..',
        'amplication-client',
        'build'
      ),
      exclude: ['/graphql']
    }),

    RootWinstonModule,

    GraphQLModule.forRootAsync({
      useFactory: async (configService: ConfigService) => ({
        autoSchemaFile:
          configService.get('GRAPHQL_SCHEMA_DEST') || './src/schema.graphql',
        debug: configService.get('GRAPHQL_DEBUG') === '1',
        playground: configService.get('PLAYGROUND_ENABLE') === '1',
        context: ({ req }: { req: Request }) => ({
          req
        })
      }),
      inject: [ConfigService]
    }),

    RootStorageModule,

    MorganModule,
    SegmentAnalyticsModule.registerAsync({
      useClass: SegmentAnalyticsOptionsService
    }),
    CoreModule
  ],
  controllers: [],
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: InjectContextInterceptor
    }
  ]
})
export class AppModule implements OnApplicationShutdown {
  onApplicationShutdown(signal: string) {
    console.trace(`Application shut down (signal: ${signal})`);
  }
}
Example #2
Source File: bull.providers.ts    From nestjs-bullmq with MIT License 5 votes vote down vote up
function buildQueue(option: BullModuleOptions): Queue {
  const queue: Queue = new Queue(option.name ? option.name : 'default', option);
  const workers: (Worker | QueueScheduler)[] = [];

  /**
   * TODO: Add top level option to allow for creation of scheduler. This current
   * implementation currently would cause multiple schedulers to be created if
   * we were scaling horizontally.
   * @url https://docs.bullmq.io/guide/queuescheduler
   */
  /*
  if (!option.disableScheduler) {
    workers.push(new QueueScheduler(queue.name, option))
  }
  */

  if (option.processors) {
    option.processors.forEach((processor: BullQueueProcessor) => {
      if (isAdvancedProcessor(processor)) {
        workers.push(
          new Worker(queue.name, processor.callback, {
            concurrency: processor.concurrency,
            connection: option.connection,
          }),
        );
      } else if (isAdvancedSeparateProcessor(processor)) {
        workers.push(
          new Worker(queue.name, processor.path, {
            concurrency: processor.concurrency,
            connection: option.connection,
          }),
        );
      } else if (isSeparateProcessor(processor)) {
        workers.push(
          new Worker(queue.name, processor, {
            connection: option.connection,
          }),
        );
      } else if (isProcessorCallback(processor)) {
        workers.push(
          new Worker(queue.name, processor, {
            connection: option.connection,
          }),
        );
      }
    });
  }
  (queue as unknown as OnApplicationShutdown).onApplicationShutdown = function (
    this: Queue,
  ) {
    return Promise.all([...workers.map((w) => w.close()), this.close()]);
  };
  return queue;
}
Example #3
Source File: app.module.ts    From nestjs-starter with MIT License 5 votes vote down vote up
@Module({
  imports: [
    ConfigModule.forRoot({
      load: [config],
    }),
    WinstonModule.forRoot(loggerConfig),
    TypeOrmModule.forRootAsync(typeOrmConfig),
    EventsModule,
  ],
  controllers: [AppController],
  providers: [
    {
      provide: 'configService',
      useFactory: () => new ConfigService(),
    },
  ],
})
@Injectable()
export class AppModule implements OnApplicationShutdown {
  private readonly logger = new Logger(AppModule.name);
  private readonly shutdownListener$: Subject<void> = new Subject();

  constructor(private readonly connection: Connection) {}

  closeDatabaseConnection = async (): Promise<void> => {
    try {
      await this.connection.close();
      this.logger.log('Database connection is closed');
    } catch (error) {
      this.logger.error(error.message);
    }
  };

  onApplicationShutdown = async (signal: string): Promise<void> => {
    if (!signal) return;
    this.logger.log(`Detected signal: ${signal}`);

    this.shutdownListener$.next();
    return this.closeDatabaseConnection();
  };

  subscribeToShutdown = (shutdownFn: () => void): void => {
    this.shutdownListener$.subscribe(() => {
      this.logger.log('App is closed');
      shutdownFn();
    });
  };
}
Example #4
Source File: mikro-orm-core.module.ts    From nestjs with MIT License 4 votes vote down vote up
@Global()
@Module({})
export class MikroOrmCoreModule implements OnApplicationShutdown {

  constructor(@Inject(MIKRO_ORM_MODULE_OPTIONS)
              private readonly options: MikroOrmModuleOptions,
              private readonly moduleRef: ModuleRef) { }

  static async forRoot(options?: MikroOrmModuleSyncOptions): Promise<DynamicModule> {
    const contextName = this.setContextName(options?.contextName);
    const knex = await tryRequire(() => import('@mikro-orm/knex'));
    const mongo = await tryRequire(() => import('@mikro-orm/mongodb'));

    return {
      module: MikroOrmCoreModule,
      providers: [
        { provide: MIKRO_ORM_MODULE_OPTIONS, useValue: options || {} },
        createMikroOrmProvider(contextName),
        createEntityManagerProvider(options?.scope, EntityManager, contextName),
        ...(knex ? [createEntityManagerProvider(options?.scope, knex.SqlEntityManager, contextName)] : []),
        ...(mongo ? [createEntityManagerProvider(options?.scope, mongo.MongoEntityManager, contextName)] : []),
      ],
      exports: [
        contextName ? getMikroORMToken(contextName) : MikroORM,
        contextName ? getEntityManagerToken(contextName) : EntityManager,
        ...(knex ? (contextName ? [] : [knex.SqlEntityManager as any]) : []),
        ...(mongo ? (contextName ? [] : [mongo.MongoEntityManager as any]) : []),
      ],
    };
  }

  static async forRootAsync(options: MikroOrmModuleAsyncOptions): Promise<DynamicModule> {
    const contextName = this.setContextName(options?.contextName);
    const knex = await tryRequire(() => import('@mikro-orm/knex'));
    const mongo = await tryRequire(() => import('@mikro-orm/mongodb'));

    return {
      module: MikroOrmCoreModule,
      imports: options.imports || [],
      providers: [
        ...(options.providers || []),
        ...createAsyncProviders({ ...options, contextName: options.contextName }),
        createMikroOrmProvider(contextName),
        createEntityManagerProvider(options.scope, EntityManager, contextName),
        ...(knex ? [createEntityManagerProvider(options?.scope, knex.SqlEntityManager, contextName)] : []),
        ...(mongo ? [createEntityManagerProvider(options?.scope, mongo.MongoEntityManager, contextName)] : []),
      ],
      exports: [
        contextName ? getMikroORMToken(contextName) : MikroORM,
        contextName ? getEntityManagerToken(contextName) : EntityManager,
        ...(knex ? (contextName ? [] : [knex.SqlEntityManager as any]) : []),
        ...(mongo ? (contextName ? [] : [mongo.MongoEntityManager as any]) : []),
      ],
    };
  }

  async onApplicationShutdown() {
    const token = this.options.contextName ? getMikroORMToken(this.options.contextName) : MikroORM;
    const orm = this.moduleRef.get(token);

    if (orm) {
      await orm.close();
    }

    CONTEXT_NAMES.length = 0;
  }

  configure(consumer: MiddlewareConsumer): void {
    if (this.options.registerRequestContext === false) {
      return;
    }

    consumer
      .apply(MikroOrmMiddleware) // register request context automatically
      .forRoutes({ path: forRoutesPath(this.options, consumer), method: RequestMethod.ALL });
  }

  private static setContextName(contextName?: string) {
    if (!contextName) {
      return;
    }

    if (CONTEXT_NAMES.includes(contextName)) {
      throw new Error(`ContextName '${contextName}' already registered`);
    }

    CONTEXT_NAMES.push(contextName);

    return contextName;
  }

}
Example #5
Source File: tenancy-core.module.ts    From nestjs-tenancy with MIT License 4 votes vote down vote up
@Global()
@Module({})
export class TenancyCoreModule implements OnApplicationShutdown {
    constructor(
        private readonly moduleRef: ModuleRef,
    ) { }

    /**
     * Register for synchornous modules
     *
     * @static
     * @param {TenancyModuleOptions} options
     * @returns {DynamicModule}
     * @memberof TenancyCoreModule
     */
    static register(options: TenancyModuleOptions): DynamicModule {

        /* Module options */
        const tenancyModuleOptionsProvider = {
            provide: TENANT_MODULE_OPTIONS,
            useValue: { ...options },
        };

        /* Connection Map */
        const connectionMapProvider = this.createConnectionMapProvider();

        /* Model Definition Map */
        const modelDefinitionMapProvider = this.createModelDefinitionMapProvider();

        /* Tenant Context */
        const tenantContextProvider = this.createTenantContextProvider();

        /* Http Adaptor */
        const httpAdapterHost = this.createHttpAdapterProvider();

        /* Tenant Connection */
        const tenantConnectionProvider = {
            provide: TENANT_CONNECTION,
            useFactory: async (
                tenantId: string,
                moduleOptions: TenancyModuleOptions,
                connMap: ConnectionMap,
                modelDefMap: ModelDefinitionMap,
            ): Promise<Connection> => {
                return await this.getConnection(tenantId, moduleOptions, connMap, modelDefMap);
            },
            inject: [
                TENANT_CONTEXT,
                TENANT_MODULE_OPTIONS,
                CONNECTION_MAP,
                MODEL_DEFINITION_MAP,
            ],
        };

        const providers = [
            tenancyModuleOptionsProvider,
            tenantContextProvider,
            connectionMapProvider,
            modelDefinitionMapProvider,
            tenantConnectionProvider,
            httpAdapterHost,
        ];

        return {
            module: TenancyCoreModule,
            providers,
            exports: providers,
        };
    }

    /**
     * Register for asynchronous modules
     *
     * @static
     * @param {TenancyModuleAsyncOptions} options
     * @returns {DynamicModule}
     * @memberof TenancyCoreModule
     */
    static registerAsync(options: TenancyModuleAsyncOptions): DynamicModule {

        /* Connection Map */
        const connectionMapProvider = this.createConnectionMapProvider();

        /* Model Definition Map */
        const modelDefinitionMapProvider = this.createModelDefinitionMapProvider();

        /* Tenant Context */
        const tenantContextProvider = this.createTenantContextProvider();

        /* Http Adaptor */
        const httpAdapterHost = this.createHttpAdapterProvider();

        /* Tenant Connection */
        const tenantConnectionProvider = {
            provide: TENANT_CONNECTION,
            useFactory: async (
                tenantId: string,
                moduleOptions: TenancyModuleOptions,
                connMap: ConnectionMap,
                modelDefMap: ModelDefinitionMap,
            ): Promise<Connection> => {
                return await this.getConnection(tenantId, moduleOptions, connMap, modelDefMap);
            },
            inject: [
                TENANT_CONTEXT,
                TENANT_MODULE_OPTIONS,
                CONNECTION_MAP,
                MODEL_DEFINITION_MAP,
            ]
        };

        /* Asyc providers */
        const asyncProviders = this.createAsyncProviders(options);

        const providers = [
            ...asyncProviders,
            tenantContextProvider,
            connectionMapProvider,
            modelDefinitionMapProvider,
            tenantConnectionProvider,
            httpAdapterHost,
        ];

        return {
            module: TenancyCoreModule,
            imports: options.imports,
            providers: providers,
            exports: providers
        };
    }

    /**
     * Override method from `OnApplicationShutdown`
     *
     * @memberof TenantCoreModule
     */
    async onApplicationShutdown() {
        // Map of all connections
        const connectionMap: ConnectionMap = this.moduleRef.get(CONNECTION_MAP);

        // Remove all stray connections
        await Promise.all(
            [...connectionMap.values()].map(connection => connection.close()),
        );
    }

    /**
     * Get Tenant id from the request
     *
     * @private
     * @static
     * @param {Request} req
     * @param {TenancyModuleOptions} moduleOptions
     * @param {HttpAdapterHost} adapterHost
     * @returns {string}
     * @memberof TenancyCoreModule
     */
    private static getTenant(
        req: Request,
        moduleOptions: TenancyModuleOptions,
        adapterHost: HttpAdapterHost,
    ): string {
        // Check if the adaptor is fastify
        const isFastifyAdaptor = this.adapterIsFastify(adapterHost);

        if (!moduleOptions) {
            throw new BadRequestException(`Tenant options are mandatory`);
        }

        // Extract the tenant idetifier
        const {
            tenantIdentifier = null,
            isTenantFromSubdomain = false,
        } = moduleOptions;

        // Pull the tenant id from the subdomain
        if (isTenantFromSubdomain) {

            return this.getTenantFromSubdomain(isFastifyAdaptor, req);

        } else {
            // Validate if tenant identifier token is present
            if (!tenantIdentifier) {
                throw new BadRequestException(`${tenantIdentifier} is mandatory`);
            }

            return this.getTenantFromRequest(isFastifyAdaptor, req, tenantIdentifier);
        }
    }

    /**
     * Get the Tenant information from the request object
     *
     * @private
     * @static
     * @param {boolean} isFastifyAdaptor
     * @param {Request} req
     * @param {string} tenantIdentifier
     * @returns
     * @memberof TenancyCoreModule
     */
    private static getTenantFromRequest(isFastifyAdaptor: boolean, req: Request, tenantIdentifier: string) {
        let tenantId = '';

        if (isFastifyAdaptor) { // For Fastify
            // Get the tenant id from the header
            tenantId = req.headers[`${tenantIdentifier || ''}`.toLowerCase()]?.toString() || '';
        } else { // For Express - Default
            // Get the tenant id from the request
            tenantId = req.get(`${tenantIdentifier}`) || '';
        }

        // Validate if tenant id is present
        if (this.isEmpty(tenantId)) {
            throw new BadRequestException(`${tenantIdentifier} is not supplied`);
        }

        return tenantId;
    }

    /**
     * Get the Tenant information from the request header
     *
     * @private
     * @static
     * @param {boolean} isFastifyAdaptor
     * @param {Request} req
     * @returns
     * @memberof TenancyCoreModule
     */
    private static getTenantFromSubdomain(isFastifyAdaptor: boolean, req: Request) {
        let tenantId = '';

        if (isFastifyAdaptor) { // For Fastify
            const subdomains = this.getSubdomainsForFastify(req);

            if (subdomains instanceof Array && subdomains.length > 0) {
                tenantId = subdomains[subdomains.length - 1];
            }
        } else { // For Express - Default
            // Check for multi-level subdomains and return only the first name
            if (req.subdomains instanceof Array && req.subdomains.length > 0) {
                tenantId = req.subdomains[req.subdomains.length - 1];
            }
        }

        // Validate if tenant identifier token is present
        if (this.isEmpty(tenantId)) {
            throw new BadRequestException(`Tenant ID is mandatory`);
        }

        return tenantId;
    }

    /**
     * Get the connection for the tenant
     *
     * @private
     * @static
     * @param {String} tenantId
     * @param {TenancyModuleOptions} moduleOptions
     * @param {ConnectionMap} connMap
     * @param {ModelDefinitionMap} modelDefMap
     * @returns {Promise<Connection>}
     * @memberof TenancyCoreModule
     */
    private static async getConnection(
        tenantId: string,
        moduleOptions: TenancyModuleOptions,
        connMap: ConnectionMap,
        modelDefMap: ModelDefinitionMap,
    ): Promise<Connection> {
        // Check if validator is set, if so call the `validate` method on it
        if (moduleOptions.validator) {
            await moduleOptions.validator(tenantId).validate();
        }

        // Check if tenantId exist in the connection map
        const exists = connMap.has(tenantId);

        // Return the connection if exist
        if (exists) {
            const connection = connMap.get(tenantId) as Connection;

            if (moduleOptions.forceCreateCollections) {
                // For transactional support the Models/Collections has exist in the
                // tenant database, otherwise it will throw error
                await Promise.all(
                    Object.entries(connection.models).map(([k, m]) => m.createCollection())
                );
            }

            return connection;
        }

        // Otherwise create a new connection
        const uri = await Promise.resolve(moduleOptions.uri(tenantId))
        const connection = createConnection(uri, {
            useNewUrlParser: true,
            useUnifiedTopology: true,
            ...moduleOptions.options(),
        });

        // Attach connection to the models passed in the map
        modelDefMap.forEach(async (definition: any) => {
            const { name, schema, collection } = definition;
            const modelCreated = connection.model(name, schema, collection);

            if (moduleOptions.forceCreateCollections) {
                // For transactional support the Models/Collections has exist in the
                // tenant database, otherwise it will throw error
                await modelCreated.createCollection();
            }
        });

        // Add the new connection to the map
        connMap.set(tenantId, connection);

        return connection;
    }

    /**
     * Create connection map provider
     *
     * @private
     * @static
     * @returns {Provider}
     * @memberof TenancyCoreModule
     */
    private static createConnectionMapProvider(): Provider {
        return {
            provide: CONNECTION_MAP,
            useFactory: (): ConnectionMap => new Map(),
        }
    }

    /**
     * Create model definition map provider
     *
     * @private
     * @static
     * @returns {Provider}
     * @memberof TenancyCoreModule
     */
    private static createModelDefinitionMapProvider(): Provider {
        return {
            provide: MODEL_DEFINITION_MAP,
            useFactory: (): ModelDefinitionMap => new Map(),
        }
    }

    /**
     * Create tenant context provider
     *
     * @private
     * @static
     * @returns {Provider}
     * @memberof TenancyCoreModule
     */
    private static createTenantContextProvider(): Provider {
        return {
            provide: TENANT_CONTEXT,
            scope: Scope.REQUEST,
            useFactory: (
                req: Request,
                moduleOptions: TenancyModuleOptions,
                adapterHost: HttpAdapterHost,
            ) => this.getTenant(req, moduleOptions, adapterHost),
            inject: [
                REQUEST,
                TENANT_MODULE_OPTIONS,
                DEFAULT_HTTP_ADAPTER_HOST,
            ]
        }
    }

    /**
     * Create options providers
     *
     * @private
     * @static
     * @param {TenancyModuleAsyncOptions} options
     * @returns {Provider[]}
     * @memberof TenancyCoreModule
     */
    private static createAsyncProviders(
        options: TenancyModuleAsyncOptions,
    ): Provider[] {
        if (options.useExisting || options.useFactory) {
            return [this.createAsyncOptionsProvider(options)];
        }

        const useClass = options.useClass as Type<TenancyOptionsFactory>;

        return [
            this.createAsyncOptionsProvider(options),
            {
                provide: useClass,
                useClass,
            },
        ];
    }

    /**
     * Create options provider
     *
     * @private
     * @static
     * @param {TenancyModuleAsyncOptions} options
     * @returns {Provider}
     * @memberof TenancyCoreModule
     */
    private static createAsyncOptionsProvider(
        options: TenancyModuleAsyncOptions,
    ): Provider {
        if (options.useFactory) {
            return {
                provide: TENANT_MODULE_OPTIONS,
                useFactory: options.useFactory,
                inject: options.inject || [],
            };
        }

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

        return {
            provide: TENANT_MODULE_OPTIONS,
            useFactory: async (optionsFactory: TenancyOptionsFactory) =>
                await optionsFactory.createTenancyOptions(),
            inject,
        };
    }

    /**
     * Create Http Adapter provider
     *
     * @private
     * @static
     * @returns {Provider}
     * @memberof TenancyCoreModule
     */
    private static createHttpAdapterProvider(): Provider {
        return {
            provide: DEFAULT_HTTP_ADAPTER_HOST,
            useFactory: (adapterHost: HttpAdapterHost) => adapterHost,
            inject: [
                HttpAdapterHost
            ],
        };
    }

    /**
     * Check if the object is empty or not
     *
     * @private
     * @param {*} obj
     * @returns
     * @memberof TenancyCoreModule
     */
    private static isEmpty(obj: any) {
        return !obj || !Object.keys(obj).some(x => obj[x] !== void 0);
    }

    /**
     * Check if the adapter is a fastify instance or not
     *
     * @private
     * @static
     * @param {HttpAdapterHost} adapterHost
     * @returns {boolean}
     * @memberof TenancyCoreModule
     */
    private static adapterIsFastify(adapterHost: HttpAdapterHost): boolean {
        return adapterHost.httpAdapter.getType() === 'fastify';
    }

    /**
     * Get the subdomains for fastify adaptor
     *
     * @private
     * @static
     * @param {Request} req
     * @returns {string[]}
     * @memberof TenancyCoreModule
     */
    private static getSubdomainsForFastify(req: Request): string[] {
        let host = req?.headers?.host || '';

        host = host.split(':')[0];
        host = host.trim();

        return host.split('.').reverse();
    }
}
Example #6
Source File: puppeteer-core.module.ts    From nest-puppeteer with MIT License 4 votes vote down vote up
@Global()
@Module({})
export class PuppeteerCoreModule
  implements OnApplicationShutdown, OnModuleDestroy {
  constructor(
    @Inject(PUPPETEER_INSTANCE_NAME) private readonly instanceName: string,
    private readonly moduleRef: ModuleRef,
  ) {}
  onApplicationShutdown() {
    return this.onModuleDestroy();
  }

  static forRoot(
    launchOptions: LaunchOptions = DEFAULT_CHROME_LAUNCH_OPTIONS,
    instanceName: string = DEFAULT_PUPPETEER_INSTANCE_NAME,
  ): DynamicModule {
    const instanceNameProvider = {
      provide: PUPPETEER_INSTANCE_NAME,
      useValue: instanceName,
    };

    const browserProvider = {
      provide: getBrowserToken(instanceName),
      async useFactory() {
        return await launch(launchOptions);
      },
    };

    const contextProvider = {
      provide: getContextToken(instanceName),
      async useFactory(browser: Browser) {
        return browser.createIncognitoBrowserContext();
      },
      inject: [getBrowserToken(instanceName)],
    };

    const pageProvider = {
      provide: getPageToken(instanceName),
      async useFactory(context: BrowserContext) {
        return await context.newPage();
      },
      inject: [getContextToken(instanceName)],
    };

    return {
      module: PuppeteerCoreModule,
      providers: [
        instanceNameProvider,
        browserProvider,
        contextProvider,
        pageProvider,
      ],
      exports: [browserProvider, contextProvider, pageProvider],
    };
  }

  static forRootAsync(options: PuppeteerModuleAsyncOptions): DynamicModule {
    const puppeteerInstanceName =
      options.instanceName ?? DEFAULT_PUPPETEER_INSTANCE_NAME;

    const instanceNameProvider = {
      provide: PUPPETEER_INSTANCE_NAME,
      useValue: puppeteerInstanceName,
    };

    const browserProvider = {
      provide: getBrowserToken(puppeteerInstanceName),
      async useFactory(puppeteerModuleOptions: PuppeteerModuleOptions) {
        return await launch(
          puppeteerModuleOptions.launchOptions ?? DEFAULT_CHROME_LAUNCH_OPTIONS,
        );
      },
      inject: [PUPPETEER_MODULE_OPTIONS],
    };

    const contextProvider = {
      provide: getContextToken(puppeteerInstanceName),
      async useFactory(browser: Browser) {
        return await browser.createIncognitoBrowserContext();
      },
      inject: [
        PUPPETEER_MODULE_OPTIONS,
        getBrowserToken(puppeteerInstanceName),
      ],
    };

    const pageProvider = {
      provide: getPageToken(puppeteerInstanceName),
      async useFactory(context: BrowserContext) {
        return await context.newPage();
      },
      inject: [
        PUPPETEER_MODULE_OPTIONS,
        getContextToken(puppeteerInstanceName),
      ],
    };

    const asyncProviders = this.createAsyncProviders(options);

    return {
      module: PuppeteerCoreModule,
      imports: options.imports,
      providers: [
        ...asyncProviders,
        browserProvider,
        contextProvider,
        pageProvider,
        instanceNameProvider,
      ],
      exports: [browserProvider, contextProvider, pageProvider],
    };
  }

  async onModuleDestroy() {
    const browser: Browser = this.moduleRef.get(
      getBrowserToken(this.instanceName),
    );

    if (browser?.isConnected()) await browser.close();
  }

  private static createAsyncProviders(
    options: PuppeteerModuleAsyncOptions,
  ): Provider[] {
    if (options.useExisting || options.useFactory) {
      return [this.createAsyncOptionsProvider(options)];
    } else if (options.useClass) {
      return [
        this.createAsyncOptionsProvider(options),
        {
          provide: options.useClass,
          useClass: options.useClass,
        },
      ];
    } else {
      return [];
    }
  }

  private static createAsyncOptionsProvider(
    options: PuppeteerModuleAsyncOptions,
  ): Provider {
    if (options.useFactory) {
      return {
        provide: PUPPETEER_MODULE_OPTIONS,
        useFactory: options.useFactory,
        inject: options.inject ?? [],
      };
    } else if (options.useExisting) {
      return {
        provide: PUPPETEER_MODULE_OPTIONS,
        async useFactory(optionsFactory: PuppeteerOptionsFactory) {
          return optionsFactory.createPuppeteerOptions();
        },
        inject: [options.useExisting],
      };
    } else if (options.useClass) {
      return {
        provide: PUPPETEER_MODULE_OPTIONS,
        async useFactory(optionsFactory: PuppeteerOptionsFactory) {
          return optionsFactory.createPuppeteerOptions();
        },
        inject: [options.useClass],
      };
    } else {
      throw new Error('Invalid PuppeteerModule options');
    }
  }
}