Skip to content

Commit 3d9b1a2

Browse files
authored
Merge pull request #964 from TNG/never-job
feat: implement option to schedule job to run "never"
2 parents e20eefd + ba85d2f commit 3d9b1a2

12 files changed

Lines changed: 141 additions & 30 deletions

File tree

README.md

Lines changed: 10 additions & 10 deletions
Large diffs are not rendered by default.

src/job/Job.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@ import humanInterval from 'human-interval';
22
import { Result, err, ok } from 'neverthrow';
33
import CronExpressionParser from 'cron-parser';
44

5-
import { CronSchedule, Handler, IntervalSchedule, JobParameters, MomoJob, TypedMomoJob, isCronJob } from './MomoJob';
5+
import {
6+
CronSchedule,
7+
Handler,
8+
IntervalSchedule,
9+
JobParameters,
10+
MomoJob,
11+
NeverSchedule,
12+
TypedMomoJob,
13+
isCronJob,
14+
isNeverJob,
15+
} from './MomoJob';
616
import { momoError } from '../logging/error/MomoError';
717

818
export const maxNodeTimeoutDelay = 2147483647;
@@ -13,7 +23,7 @@ export interface ParsedIntervalSchedule extends Required<IntervalSchedule> {
1323
parsedFirstRunAfter: number;
1424
}
1525

16-
export interface JobDefinition<JobSchedule = ParsedIntervalSchedule | CronSchedule> {
26+
export interface JobDefinition<JobSchedule = ParsedIntervalSchedule | CronSchedule | NeverSchedule> {
1727
name: string;
1828
schedule: JobSchedule;
1929
concurrency: number;
@@ -22,7 +32,7 @@ export interface JobDefinition<JobSchedule = ParsedIntervalSchedule | CronSchedu
2232
parameters?: JobParameters;
2333
}
2434

25-
export interface Job<Schedule = ParsedIntervalSchedule | CronSchedule> extends JobDefinition<Schedule> {
35+
export interface Job<Schedule = ParsedIntervalSchedule | CronSchedule | NeverSchedule> extends JobDefinition<Schedule> {
2636
handler: Handler;
2737
}
2838

@@ -46,7 +56,19 @@ export function tryToJob(momoJob: MomoJob): Result<Job, Error> {
4656
return err(momoError.invalidTimeout);
4757
}
4858

49-
return isCronJob(momoJob) ? tryToCronJob(momoJob) : tryToIntervalJob(momoJob);
59+
return isNeverJob(momoJob)
60+
? tryToNeverJob(momoJob)
61+
: isCronJob(momoJob)
62+
? tryToCronJob(momoJob)
63+
: tryToIntervalJob(momoJob);
64+
}
65+
66+
export function tryToNeverJob(momoJob: TypedMomoJob<NeverSchedule>): Result<Job<NeverSchedule>, Error> {
67+
return ok({
68+
concurrency: 1,
69+
maxRunning: 0,
70+
...momoJob,
71+
});
5072
}
5173

5274
/**
@@ -111,7 +133,7 @@ export function tryToCronJob(momoJob: TypedMomoJob<CronSchedule>): Result<Job<Cr
111133
* @param job
112134
*/
113135
export function toJobDefinition<
114-
Schedule extends ParsedIntervalSchedule | CronSchedule,
136+
Schedule extends ParsedIntervalSchedule | CronSchedule | NeverSchedule,
115137
Type extends JobDefinition<Schedule>,
116138
>({ name, schedule, maxRunning, concurrency, timeout, parameters }: Type): JobDefinition<Schedule> {
117139
return { name, schedule, maxRunning, concurrency, timeout, parameters };

src/job/MomoJob.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export interface TypedMomoJob<Schedule> {
1414
parameters?: JobParameters;
1515
}
1616

17-
export type MomoJob = TypedMomoJob<IntervalSchedule> | TypedMomoJob<CronSchedule>;
17+
export type MomoJob = TypedMomoJob<IntervalSchedule> | TypedMomoJob<CronSchedule> | TypedMomoJob<NeverSchedule>;
1818

1919
export interface IntervalSchedule {
2020
interval: number | string;
@@ -25,18 +25,29 @@ export interface CronSchedule {
2525
cronSchedule: string;
2626
}
2727

28+
export interface NeverSchedule {
29+
interval: 'Never' | 'never';
30+
}
31+
2832
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2933
export function isCronSchedule(input: any): input is CronSchedule {
3034
return input.cronSchedule !== undefined && typeof input.cronSchedule === 'string';
3135
}
3236

37+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
38+
export function isNeverSchedule(input: any): input is NeverSchedule {
39+
return input.interval !== undefined && (input.interval === 'Never' || input.interval === 'never');
40+
}
41+
3342
/**
3443
* removes properties that are not part of the IntervalSchedule resp. CronSchedule interface
3544
*
3645
* @param schedule
3746
*/
38-
export function toSchedule(schedule: ParsedIntervalSchedule | CronSchedule): Required<IntervalSchedule> | CronSchedule {
39-
if (isCronSchedule(schedule)) {
47+
export function toSchedule(
48+
schedule: ParsedIntervalSchedule | CronSchedule | NeverSchedule,
49+
): Required<IntervalSchedule> | CronSchedule | NeverSchedule {
50+
if (isNeverSchedule(schedule) || isCronSchedule(schedule)) {
4051
return schedule;
4152
}
4253

@@ -48,3 +59,7 @@ export function toSchedule(schedule: ParsedIntervalSchedule | CronSchedule): Req
4859
export function isCronJob(momoJob: MomoJob): momoJob is TypedMomoJob<CronSchedule> {
4960
return isCronSchedule(momoJob.schedule);
5061
}
62+
63+
export function isNeverJob(momoJob: MomoJob): momoJob is TypedMomoJob<NeverSchedule> {
64+
return isNeverSchedule(momoJob.schedule);
65+
}

src/job/MomoJobBuilder.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ export class MomoJobBuilder {
2828

2929
// The interval is either a number in milliseconds or an interval in human-readable form (see readme)
3030
withSchedule(interval: number | string, firstRunAfter: number | string = 0): MomoIntervalJobBuilder {
31+
if (interval === 'Never' || interval === 'never') {
32+
this.momoJob.schedule = { interval };
33+
return this;
34+
}
35+
3136
this.momoJob.schedule = { firstRunAfter, interval };
3237
return this;
3338
}

src/job/MomoJobDescription.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { JobEntity } from '../repository/JobEntity';
2-
import { CronSchedule, IntervalSchedule, JobParameters, toSchedule } from './MomoJob';
2+
import { CronSchedule, IntervalSchedule, JobParameters, NeverSchedule, toSchedule } from './MomoJob';
33

44
/**
55
* information about scheduled job
@@ -14,7 +14,7 @@ export interface JobSchedulerStatus {
1414

1515
export interface MomoJobDescription {
1616
name: string;
17-
schedule: IntervalSchedule | CronSchedule;
17+
schedule: IntervalSchedule | CronSchedule | NeverSchedule;
1818
concurrency: number;
1919
maxRunning: number;
2020
parameters?: JobParameters;

src/repository/JobEntity.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import { ObjectId } from 'mongodb';
22

33
import { ExecutionInfo } from '../job/ExecutionInfo';
44
import { JobDefinition, ParsedIntervalSchedule } from '../job/Job';
5-
import { CronSchedule } from '../job/MomoJob';
5+
import { CronSchedule, NeverSchedule } from '../job/MomoJob';
66

77
export interface JobEntity<
8-
Schedule extends ParsedIntervalSchedule | CronSchedule = ParsedIntervalSchedule | CronSchedule,
8+
Schedule extends ParsedIntervalSchedule | CronSchedule | NeverSchedule =
9+
| ParsedIntervalSchedule
10+
| CronSchedule
11+
| NeverSchedule,
912
> extends JobDefinition<Schedule> {
1013
_id?: ObjectId;
1114
executionInfo?: ExecutionInfo;

src/repository/MomoJobStatus.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { JobEntity } from './JobEntity';
2-
import { CronSchedule, IntervalSchedule, toSchedule } from '../job/MomoJob';
2+
import { CronSchedule, IntervalSchedule, NeverSchedule, toSchedule } from '../job/MomoJob';
33
import { ParsedIntervalSchedule } from '../job/Job';
44

55
export interface MomoJobStatus extends Omit<JobEntity<never>, '_id' | 'schedule'> {
6-
schedule: Required<IntervalSchedule> | CronSchedule;
6+
schedule: Required<IntervalSchedule> | CronSchedule | NeverSchedule;
77
}
88

9-
export function toMomoJobStatus<Schedule extends ParsedIntervalSchedule | CronSchedule>({
9+
export function toMomoJobStatus<Schedule extends ParsedIntervalSchedule | CronSchedule | NeverSchedule>({
1010
name,
1111
schedule,
1212
concurrency,

src/scheduler/JobScheduler.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { momoError } from '../logging/error/MomoError';
1212
import { ExecutableIntervalSchedule } from './ExecutableIntervalSchedule';
1313
import { ExecutableCronSchedule } from './ExecutableCronSchedule';
1414
import { toExecutableSchedule } from './ExecutableSchedule';
15-
import { JobParameters } from '../job/MomoJob';
15+
import { JobParameters, isNeverSchedule } from '../job/MomoJob';
1616
import { setSafeTimeout } from '../timeout/safeTimeouts';
1717

1818
export class JobScheduler {
@@ -82,6 +82,13 @@ export class JobScheduler {
8282
return;
8383
}
8484

85+
if (isNeverSchedule(jobEntity.schedule)) {
86+
this.logger.debug(`do not start job specified to run 'never'`, {
87+
name: this.jobName,
88+
});
89+
return;
90+
}
91+
8592
this.executableSchedule = toExecutableSchedule(jobEntity.schedule);
8693

8794
const { nextExecution } = this.executableSchedule.execute({

test/job/MomoJobBuilder.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
import { MomoJobBuilder } from '../../src';
22

33
describe('MomoJobBuilder', () => {
4+
it('can build an interval job that never runs automatically', () => {
5+
const momoJob = new MomoJobBuilder()
6+
.withName('name')
7+
.withSchedule('never')
8+
.withConcurrency(1)
9+
.withMaxRunning(1)
10+
.withHandler(jest.fn())
11+
.withParameters({ foo: 'bar' })
12+
.withTimeout(1)
13+
.build();
14+
15+
expect(momoJob.name).toEqual('name');
16+
expect(momoJob.schedule).toEqual({ interval: 'never' });
17+
expect(momoJob.concurrency).toEqual(1);
18+
expect(momoJob.maxRunning).toEqual(1);
19+
expect(momoJob.handler.toString()).toEqual(jest.fn().toString());
20+
expect(momoJob.parameters).toEqual({ foo: 'bar' });
21+
expect(momoJob.timeout).toEqual(1);
22+
});
23+
424
it('can build an interval job with all attributes and an interval', () => {
525
const momoJob = new MomoJobBuilder()
626
.withName('name')

test/schedule/Schedule.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,18 @@ describe('Schedule', () => {
9797
expect(momoJob.handler).toHaveBeenCalledTimes(1);
9898
});
9999

100+
it('runs a "never" job once', async () => {
101+
await mongoSchedule.define({ ...momoJob, schedule: { interval: 'never' } });
102+
103+
when(jobRepository.findOne(deepEqual({ name: momoJob.name }))).thenResolve(entityWithId);
104+
when(schedulesRepository.addExecution(momoJob.name, 0)).thenResolve({ added: true, running: 0 });
105+
106+
const result = await mongoSchedule.run(momoJob.name);
107+
108+
expect(result).toEqual({ status: ExecutionStatus.finished, handlerResult: 'finished' });
109+
expect(momoJob.handler).toHaveBeenCalledTimes(1);
110+
});
111+
100112
it('does not run job once when job is not found', async () => {
101113
const result = await mongoSchedule.run('not defined');
102114

0 commit comments

Comments
 (0)