Skip to content

Commit

Permalink
feat: improve context, onTick & onComplete typings (#705)
Browse files Browse the repository at this point in the history
  • Loading branch information
sheerlox committed Oct 9, 2023
1 parent cc4e62f commit 82c78d7
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 127 deletions.
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ export { CronJob } from './job';
export { CronTime } from './time';

export {
CronCallback,
CronCommand,
CronContext,
CronJobParams,
CronOnCompleteCallback,
CronOnCompleteCommand,
Ranges,
TimeUnit
} from './types/cron.types';
Expand Down
109 changes: 64 additions & 45 deletions src/job.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,64 @@
import { spawn } from 'child_process';
import { ExclusiveParametersError } from './errors';
import { CronTime } from './time';
import { CronCommand, CronJobParams } from './types/cron.types';

export class CronJob {
import {
CronCallback,
CronCommand,
CronContext,
CronJobParams,
CronOnCompleteCallback,
CronOnCompleteCommand,
WithOnComplete
} from './types/cron.types';

export class CronJob<OC extends CronOnCompleteCommand<C> | null, C = null> {
cronTime: CronTime;
running = false;
unrefTimeout = false;
lastExecution: Date | null = null;
runOnce = false;
context: unknown;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onComplete?: (...args: any) => void;
context: CronContext<C>;
onComplete?: WithOnComplete<OC> extends true
? CronOnCompleteCallback<C>
: undefined;

private _timeout?: NodeJS.Timeout;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _callbacks: ((...args: any) => void)[] = [];
private _callbacks: CronCallback<C, WithOnComplete<OC>>[] = [];

constructor(
cronTime: CronJobParams['cronTime'],
onTick: CronJobParams['onTick'],
onComplete?: CronJobParams['onComplete'],
start?: CronJobParams['start'],
timeZone?: CronJobParams['timeZone'],
context?: CronJobParams['context'],
runOnInit?: CronJobParams['runOnInit'],
cronTime: CronJobParams<OC, C>['cronTime'],
onTick: CronJobParams<OC, C>['onTick'],
onComplete?: CronJobParams<OC, C>['onComplete'],
start?: CronJobParams<OC, C>['start'],
timeZone?: CronJobParams<OC, C>['timeZone'],
context?: CronJobParams<OC, C>['context'],
runOnInit?: CronJobParams<OC, C>['runOnInit'],
utcOffset?: null,
unrefTimeout?: CronJobParams['unrefTimeout']
unrefTimeout?: CronJobParams<OC, C>['unrefTimeout']
);
constructor(
cronTime: CronJobParams['cronTime'],
onTick: CronJobParams['onTick'],
onComplete?: CronJobParams['onComplete'],
start?: CronJobParams['start'],
cronTime: CronJobParams<OC, C>['cronTime'],
onTick: CronJobParams<OC, C>['onTick'],
onComplete?: CronJobParams<OC, C>['onComplete'],
start?: CronJobParams<OC, C>['start'],
timeZone?: null,
context?: CronJobParams['context'],
runOnInit?: CronJobParams['runOnInit'],
utcOffset?: CronJobParams['utcOffset'],
unrefTimeout?: CronJobParams['unrefTimeout']
context?: CronJobParams<OC, C>['context'],
runOnInit?: CronJobParams<OC, C>['runOnInit'],
utcOffset?: CronJobParams<OC, C>['utcOffset'],
unrefTimeout?: CronJobParams<OC, C>['unrefTimeout']
);
constructor(
cronTime: CronJobParams['cronTime'],
onTick: CronJobParams['onTick'],
onComplete?: CronJobParams['onComplete'],
start?: CronJobParams['start'],
timeZone?: CronJobParams['timeZone'],
context?: CronJobParams['context'],
runOnInit?: CronJobParams['runOnInit'],
utcOffset?: CronJobParams['utcOffset'],
unrefTimeout?: CronJobParams['unrefTimeout']
cronTime: CronJobParams<OC, C>['cronTime'],
onTick: CronJobParams<OC, C>['onTick'],
onComplete?: CronJobParams<OC, C>['onComplete'],
start?: CronJobParams<OC, C>['start'],
timeZone?: CronJobParams<OC, C>['timeZone'],
context?: CronJobParams<OC, C>['context'],
runOnInit?: CronJobParams<OC, C>['runOnInit'],
utcOffset?: CronJobParams<OC, C>['utcOffset'],
unrefTimeout?: CronJobParams<OC, C>['unrefTimeout']
) {
this.context = context || this;
this.context = (context ?? this) as CronContext<C>;

// runtime check for JS users
if (timeZone != null && utcOffset != null) {
Expand All @@ -70,7 +78,12 @@ export class CronJob {
}

if (onComplete != null) {
this.onComplete = this._fnWrap(onComplete);
// casting to the correct type since we just made sure that WithOnComplete<OC> = true
this.onComplete = this._fnWrap(
onComplete
) as WithOnComplete<OC> extends true
? CronOnCompleteCallback<C>
: undefined;
}

if (this.cronTime.realDate) {
Expand All @@ -87,15 +100,17 @@ export class CronJob {
if (start) this.start();
}

static from(params: CronJobParams) {
static from<C = null, OC extends CronOnCompleteCommand<C> | null = null>(
params: CronJobParams<OC, C>
) {
// runtime check for JS users
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (params.timeZone != null && params.utcOffset != null) {
throw new ExclusiveParametersError('timeZone', 'utcOffset');
}

if (params.timeZone != null) {
return new CronJob(
return new CronJob<OC, C>(
params.cronTime,
params.onTick,
params.onComplete,
Expand All @@ -107,7 +122,7 @@ export class CronJob {
params.unrefTimeout
);
} else if (params.utcOffset != null) {
return new CronJob(
return new CronJob<OC, C>(
params.cronTime,
params.onTick,
params.onComplete,
Expand All @@ -119,7 +134,7 @@ export class CronJob {
params.unrefTimeout
);
} else {
return new CronJob(
return new CronJob<OC, C>(
params.cronTime,
params.onTick,
params.onComplete,
Expand All @@ -133,7 +148,7 @@ export class CronJob {
}
}

private _fnWrap(cmd: CronCommand | string) {
private _fnWrap(cmd: CronCommand<C, boolean>): CronCallback<C, boolean> {
switch (typeof cmd) {
case 'function': {
return cmd;
Expand All @@ -142,7 +157,7 @@ export class CronJob {
case 'string': {
const [command, ...args] = cmd.split(' ');

return spawn.bind(undefined, command ?? cmd, args);
return spawn.bind(undefined, command ?? cmd, args, {});
}

case 'object': {
Expand All @@ -156,8 +171,7 @@ export class CronJob {
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
addCallback(callback: (...args: any) => void) {
addCallback(callback: CronCallback<C, WithOnComplete<OC>>) {
if (typeof callback === 'function') {
this._callbacks.push(callback);
}
Expand All @@ -179,7 +193,12 @@ export class CronJob {

fireOnTick() {
for (const callback of this._callbacks) {
callback.call(this.context, this.onComplete);
callback.call(
this.context,
this.onComplete as WithOnComplete<OC> extends true
? CronOnCompleteCallback<C>
: never
);
}
}

Expand Down Expand Up @@ -276,7 +295,7 @@ export class CronJob {
if (this._timeout) clearTimeout(this._timeout);
this.running = false;
if (typeof this.onComplete === 'function') {
this.onComplete();
this.onComplete.call(this.context);
}
}
}
14 changes: 7 additions & 7 deletions src/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,19 @@ export class CronTime {
private dayOfWeek: TimeUnitField<'dayOfWeek'> = {};

constructor(
source: CronJobParams['cronTime'],
timeZone?: CronJobParams['timeZone'],
source: CronJobParams<null>['cronTime'],
timeZone?: CronJobParams<null>['timeZone'],
utcOffset?: null
);
constructor(
source: CronJobParams['cronTime'],
source: CronJobParams<null>['cronTime'],
timeZone?: null,
utcOffset?: CronJobParams['utcOffset']
utcOffset?: CronJobParams<null>['utcOffset']
);
constructor(
source: CronJobParams['cronTime'],
timeZone?: CronJobParams['timeZone'],
utcOffset?: CronJobParams['utcOffset']
source: CronJobParams<null>['cronTime'],
timeZone?: CronJobParams<null>['timeZone'],
utcOffset?: CronJobParams<null>['utcOffset']
) {
// runtime check for JS users
if (timeZone != null && utcOffset != null) {
Expand Down
48 changes: 34 additions & 14 deletions src/types/cron.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,24 @@ import { CONSTRAINTS, TIME_UNITS_MAP } from '../constants';
import { CronJob } from '../job';
import { IntRange } from './utils';

interface BaseCronJobParams {
interface BaseCronJobParams<
OC extends CronOnCompleteCommand<C> | null,
C = null
> {
cronTime: string | Date | DateTime;
onTick: CronCommand;
onComplete?: CronCommand | null;
onTick: CronCommand<C, WithOnComplete<OC>>;
onComplete?: OC;
start?: boolean | null;
context?: unknown | null;
context?: C;
runOnInit?: boolean | null;
unrefTimeout?: boolean | null;
}

export type CronJobParams =
| BaseCronJobParams &
export type CronJobParams<
OC extends CronOnCompleteCommand<C> | null,
C = null
> =
| BaseCronJobParams<OC, C> &
(
| {
timeZone?: string | null;
Expand All @@ -27,21 +33,35 @@ export type CronJobParams =
}
);

export type CronCommand =
/**
* TODO: find out how to type the context correctly, based on
* if the "context" was provided to the CronJob constructor
* leaving "any" for now...
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
| ((this: CronJob | any) => void)
export type CronContext<C> = C extends null ? CronJob<null> : NonNullable<C>;

export type CronCallback<C, WithOnCompleteBool extends boolean = false> = (
this: CronContext<C>,
onComplete: WithOnCompleteBool extends true
? OmitThisParameter<CronOnCompleteCallback<C>>
: never
) => void;

export type CronOnCompleteCallback<C> = (this: CronContext<C>) => void;

export type CronSystemCommand =
| string
| {
command: string;
args?: readonly string[] | null;
options?: SpawnOptions | null;
};

export type CronCommand<C, WithOnCompleteBool extends boolean = false> =
| CronCallback<C, WithOnCompleteBool>
| CronSystemCommand;

export type CronOnCompleteCommand<C> =
| OmitThisParameter<CronOnCompleteCallback<C>>
| CronSystemCommand;

export type WithOnComplete<OC> = OC extends null ? false : true;

export type TimeUnit = (typeof TIME_UNITS_MAP)[keyof typeof TIME_UNITS_MAP];

export type TimeUnitField<T extends TimeUnit> = Partial<
Expand Down
Loading

0 comments on commit 82c78d7

Please sign in to comment.