Skip to content

Commit

Permalink
feat: implement disabling of cron/interval/timout discovery
Browse files Browse the repository at this point in the history
Signed-off-by: Dusan <dusanuveric@protonmail.com>
  • Loading branch information
uvera committed Jun 21, 2024
1 parent 808e121 commit a3f0be4
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 24 deletions.
5 changes: 5 additions & 0 deletions lib/interfaces/schedule-module-options.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface ScheduleModuleOptions {
disableCronJobDiscovery?: boolean;
disableIntervalDiscovery?: boolean;
disableTimeoutDiscovery?: boolean;
}
1 change: 1 addition & 0 deletions lib/schedule.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
40 changes: 30 additions & 10 deletions lib/schedule.explorer.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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(
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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.`,
);
Expand Down
13 changes: 11 additions & 2 deletions lib/schedule.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
};
}
Expand Down
79 changes: 79 additions & 0 deletions tests/e2e/disabled-discovery.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
37 changes: 25 additions & 12 deletions tests/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
};
}
Expand Down

0 comments on commit a3f0be4

Please # to comment.