diff --git a/lib/interfaces/schedule-module-options.interface.ts b/lib/interfaces/schedule-module-options.interface.ts new file mode 100644 index 00000000..590d0516 --- /dev/null +++ b/lib/interfaces/schedule-module-options.interface.ts @@ -0,0 +1,5 @@ +export interface ScheduleModuleOptions { + disableCronJobDiscovery?: boolean; + disableIntervalDiscovery?: boolean; + disableTimeoutDiscovery?: boolean; +} diff --git a/lib/schedule.constants.ts b/lib/schedule.constants.ts index af486f55..77961435 100644 --- a/lib/schedule.constants.ts +++ b/lib/schedule.constants.ts @@ -4,3 +4,4 @@ export const SCHEDULER_TYPE = 'SCHEDULER_TYPE'; export const SCHEDULE_CRON_OPTIONS = 'SCHEDULE_CRON_OPTIONS'; export const SCHEDULE_INTERVAL_OPTIONS = 'SCHEDULE_INTERVAL_OPTIONS'; export const SCHEDULE_TIMEOUT_OPTIONS = 'SCHEDULE_TIMEOUT_OPTIONS'; +export const SCHEDULE_MODULE_OPTIONS = 'SCHEDULE_MODULE_OPTIONS'; diff --git a/lib/schedule.explorer.ts b/lib/schedule.explorer.ts index 7749640e..afd6f480 100644 --- a/lib/schedule.explorer.ts +++ b/lib/schedule.explorer.ts @@ -1,15 +1,19 @@ -import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { DiscoveryService, MetadataScanner } from '@nestjs/core'; import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; import { SchedulerType } from './enums/scheduler-type.enum'; import { SchedulerMetadataAccessor } from './schedule-metadata.accessor'; import { SchedulerOrchestrator } from './scheduler.orchestrator'; +import { ScheduleModuleOptions } from './interfaces/schedule-module-options.interface'; +import { SCHEDULE_MODULE_OPTIONS } from './schedule.constants'; @Injectable() export class ScheduleExplorer implements OnModuleInit { private readonly logger = new Logger('Scheduler'); constructor( + @Inject(SCHEDULE_MODULE_OPTIONS) + private readonly moduleOptions: ScheduleModuleOptions, private readonly schedulerOrchestrator: SchedulerOrchestrator, private readonly discoveryService: DiscoveryService, private readonly metadataAccessor: SchedulerMetadataAccessor, @@ -27,16 +31,16 @@ export class ScheduleExplorer implements OnModuleInit { ]; instanceWrappers.forEach((wrapper: InstanceWrapper) => { const { instance } = wrapper; - + if (!instance || !Object.getPrototypeOf(instance)) { return; } - const processMethod = (name: string) => + const processMethod = (name: string) => wrapper.isDependencyTreeStatic() ? this.lookupSchedulers(instance, name) : this.warnForNonStaticProviders(wrapper, instance, name); - + // TODO(v4): remove this after dropping support for nestjs v9.3.2 if (!Reflect.has(this.metadataScanner, 'getAllMethodNames')) { this.metadataScanner.scanFromPrototype( @@ -60,15 +64,20 @@ export class ScheduleExplorer implements OnModuleInit { switch (metadata) { case SchedulerType.CRON: { + if (this.moduleOptions.disableCronJobDiscovery) { + return; + } const cronMetadata = this.metadataAccessor.getCronMetadata(methodRef); const cronFn = this.wrapFunctionInTryCatchBlocks(methodRef, instance); return this.schedulerOrchestrator.addCron(cronFn, cronMetadata!); } case SchedulerType.TIMEOUT: { - const timeoutMetadata = this.metadataAccessor.getTimeoutMetadata( - methodRef, - ); + if (this.moduleOptions.disableTimeoutDiscovery) { + return; + } + const timeoutMetadata = + this.metadataAccessor.getTimeoutMetadata(methodRef); const name = this.metadataAccessor.getSchedulerName(methodRef); const timeoutFn = this.wrapFunctionInTryCatchBlocks( methodRef, @@ -82,9 +91,11 @@ export class ScheduleExplorer implements OnModuleInit { ); } case SchedulerType.INTERVAL: { - const intervalMetadata = this.metadataAccessor.getIntervalMetadata( - methodRef, - ); + if (this.moduleOptions.disableIntervalDiscovery) { + return; + } + const intervalMetadata = + this.metadataAccessor.getIntervalMetadata(methodRef); const name = this.metadataAccessor.getSchedulerName(methodRef); const intervalFn = this.wrapFunctionInTryCatchBlocks( methodRef, @@ -110,18 +121,27 @@ export class ScheduleExplorer implements OnModuleInit { switch (metadata) { case SchedulerType.CRON: { + if (this.moduleOptions.disableCronJobDiscovery) { + return; + } this.logger.warn( `Cannot register cron job "${wrapper.name}@${key}" because it is defined in a non static provider.`, ); break; } case SchedulerType.TIMEOUT: { + if (this.moduleOptions.disableTimeoutDiscovery) { + return; + } this.logger.warn( `Cannot register timeout "${wrapper.name}@${key}" because it is defined in a non static provider.`, ); break; } case SchedulerType.INTERVAL: { + if (this.moduleOptions.disableIntervalDiscovery) { + return; + } this.logger.warn( `Cannot register interval "${wrapper.name}@${key}" because it is defined in a non static provider.`, ); diff --git a/lib/schedule.module.ts b/lib/schedule.module.ts index 332d3420..21301ab2 100644 --- a/lib/schedule.module.ts +++ b/lib/schedule.module.ts @@ -4,17 +4,26 @@ import { SchedulerMetadataAccessor } from './schedule-metadata.accessor'; import { ScheduleExplorer } from './schedule.explorer'; import { SchedulerOrchestrator } from './scheduler.orchestrator'; import { SchedulerRegistry } from './scheduler.registry'; +import { ScheduleModuleOptions } from './interfaces/schedule-module-options.interface'; +import { SCHEDULE_MODULE_OPTIONS } from './schedule.constants'; @Module({ imports: [DiscoveryModule], providers: [SchedulerMetadataAccessor, SchedulerOrchestrator], }) export class ScheduleModule { - static forRoot(): DynamicModule { + static forRoot(options: ScheduleModuleOptions = {}): DynamicModule { return { global: true, module: ScheduleModule, - providers: [ScheduleExplorer, SchedulerRegistry], + providers: [ + ScheduleExplorer, + SchedulerRegistry, + { + provide: SCHEDULE_MODULE_OPTIONS, + useValue: options, + }, + ], exports: [SchedulerRegistry], }; } diff --git a/tests/e2e/disabled-discovery.spec.ts b/tests/e2e/disabled-discovery.spec.ts new file mode 100644 index 00000000..d4664702 --- /dev/null +++ b/tests/e2e/disabled-discovery.spec.ts @@ -0,0 +1,79 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { SchedulerRegistry } from '../../lib/scheduler.registry'; +import { AppModule } from '../src/app.module'; + +describe('Cron', () => { + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [ + AppModule.registerCron({ disableCronJobDiscovery: true }), + ], + }).compile(); + + app = module.createNestApplication(); + }); + + it('should not register cron', async () => { + await app.init(); + const registry = app.get(SchedulerRegistry); + const count = Array.from(registry.getCronJobs().keys()).length; + expect(count).toBe(0); + }); + + afterEach(async () => { + await app.close(); + }); +}); + +describe('Interval', () => { + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [ + AppModule.registerInterval({ disableIntervalDiscovery: true }), + ], + }).compile(); + + app = module.createNestApplication(); + }); + + it('should not register interval', async () => { + await app.init(); + const registry = app.get(SchedulerRegistry); + const count = registry.getIntervals().length; + expect(count).toBe(0); + }); + + afterEach(async () => { + await app.close(); + }); +}); + +describe('Timeout', () => { + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [ + AppModule.registerTimeout({ disableTimeoutDiscovery: true }), + ], + }).compile(); + + app = module.createNestApplication(); + }); + + it('should not register timeout', async () => { + await app.init(); + const registry = app.get(SchedulerRegistry); + const count = registry.getTimeouts().length; + expect(count).toBe(0); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/tests/src/app.module.ts b/tests/src/app.module.ts index 7ea26b32..a5d931f4 100644 --- a/tests/src/app.module.ts +++ b/tests/src/app.module.ts @@ -6,53 +6,66 @@ import { RequestScopedCronService } from './request-scoped-cron.service'; import { RequestScopedIntervalService } from './request-scoped-interval.service'; import { RequestScopedTimeoutService } from './request-scoped-timeout.service'; import { TimeoutService } from './timeout.service'; +import { ScheduleModuleOptions } from '../../lib/interfaces/schedule-module-options.interface'; @Module({}) export class AppModule { - static registerTimeout(): DynamicModule { + static registerTimeout( + scheduleModuleOptions: ScheduleModuleOptions = {}, + ): DynamicModule { return { module: AppModule, - imports: [ScheduleModule.forRoot()], + imports: [ScheduleModule.forRoot(scheduleModuleOptions)], providers: [TimeoutService], }; } - static registerRequestScopedTimeout(): DynamicModule { + static registerRequestScopedTimeout( + scheduleModuleOptions: ScheduleModuleOptions = {}, + ): DynamicModule { return { module: AppModule, - imports: [ScheduleModule.forRoot()], + imports: [ScheduleModule.forRoot(scheduleModuleOptions)], providers: [RequestScopedTimeoutService], }; } - static registerInterval(): DynamicModule { + static registerInterval( + scheduleModuleOptions: ScheduleModuleOptions = {}, + ): DynamicModule { return { module: AppModule, - imports: [ScheduleModule.forRoot()], + imports: [ScheduleModule.forRoot(scheduleModuleOptions)], providers: [IntervalService], }; } - static registerRequestScopedInterval(): DynamicModule { + static registerRequestScopedInterval( + scheduleModuleOptions: ScheduleModuleOptions = {}, + ): DynamicModule { return { module: AppModule, - imports: [ScheduleModule.forRoot()], + imports: [ScheduleModule.forRoot(scheduleModuleOptions)], providers: [RequestScopedIntervalService], }; } - static registerCron(): DynamicModule { + static registerCron( + scheduleModuleOptions: ScheduleModuleOptions = {}, + ): DynamicModule { return { module: AppModule, - imports: [ScheduleModule.forRoot()], + imports: [ScheduleModule.forRoot(scheduleModuleOptions)], providers: [CronService], }; } - static registerRequestScopedCron(): DynamicModule { + static registerRequestScopedCron( + scheduleModuleOptions: ScheduleModuleOptions = {}, + ): DynamicModule { return { module: AppModule, - imports: [ScheduleModule.forRoot()], + imports: [ScheduleModule.forRoot(scheduleModuleOptions)], providers: [RequestScopedCronService], }; }