Skip to content

Commit f0799d9

Browse files
authored
Merge pull request #280 from KillerCodeMonkey/dev
feat: generic context typing
2 parents ff150fa + 3792d45 commit f0799d9

File tree

4 files changed

+86
-66
lines changed

4 files changed

+86
-66
lines changed

src/croner.ts

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,14 @@ const maxDelay: number = 30 * 1000;
5353
/**
5454
* An array containing all named cron jobs.
5555
*/
56-
const scheduledJobs: Cron[] = [];
56+
// deno-lint-ignore no-explicit-any
57+
const scheduledJobs: Cron<any>[] = [];
5758

5859
/**
5960
* Encapsulate all internal states in the Cron instance.
6061
* Duplicate all options that can change to internal states, for example maxRuns and paused.
6162
*/
62-
type CronState = {
63+
type CronState<T = undefined> = {
6364
kill: boolean;
6465
blocking: boolean;
6566
/**
@@ -68,15 +69,15 @@ type CronState = {
6869
* Stored to use as the actual previous run, even while a new trigger
6970
* is started. Used by the public funtion `.previousRun()`
7071
*/
71-
previousRun: CronDate | undefined;
72+
previousRun: CronDate<T> | undefined;
7273
/**
7374
* Start time of current trigger, this is updated just before triggering
7475
*
7576
* This is used internally as "previous run", as we mostly want to know
7677
* when the previous run _started_
7778
*/
78-
currentRun: CronDate | undefined;
79-
once: CronDate | undefined;
79+
currentRun: CronDate<T> | undefined;
80+
once: CronDate<T> | undefined;
8081
//@ts-ignore Cross Runtime
8182
currentTimeout: NodeJS.Timer | number | undefined;
8283
maxRuns: number | undefined;
@@ -92,9 +93,9 @@ type CronState = {
9293
*
9394
* @returns void or Promise<void> for async callbacks
9495
*/
95-
export type CronCallback = (
96-
self: InstanceType<typeof Cron>,
97-
context: unknown,
96+
export type CronCallback<T = undefined> = (
97+
self: InstanceType<typeof Cron<T>>,
98+
context: T,
9899
) => void | Promise<void>;
99100

100101
/**
@@ -105,41 +106,42 @@ export type CronCallback = (
105106
* @param [fnOrOptions1] - Options or function to be run each iteration of pattern
106107
* @param [fnOrOptions2] - Options or function to be run each iteration of pattern
107108
*/
108-
class Cron {
109+
class Cron<T = undefined> {
109110
name: string | undefined;
110-
options: CronOptions;
111-
private _states: CronState;
112-
private fn?: CronCallback;
111+
options: CronOptions<T>;
112+
private _states: CronState<T>;
113+
private fn?: CronCallback<T>;
113114
constructor(
114115
pattern: string | Date,
115-
fnOrOptions1?: CronOptions | CronCallback,
116-
fnOrOptions2?: CronOptions | CronCallback,
116+
fnOrOptions1?: CronOptions<T> | CronCallback<T>,
117+
fnOrOptions2?: CronOptions<T> | CronCallback<T>,
117118
) {
118119
// Make options and func optional and interchangable
119-
let options, func;
120+
let options: CronOptions<T> | undefined;
121+
let func: CronCallback<T> | undefined;
120122

121123
if (isFunction(fnOrOptions1)) {
122-
func = fnOrOptions1;
124+
func = fnOrOptions1 as CronCallback<T>;
123125
} else if (typeof fnOrOptions1 === "object") {
124-
options = fnOrOptions1;
126+
options = fnOrOptions1 as CronOptions<T>;
125127
} else if (fnOrOptions1 !== void 0) {
126128
throw new Error(
127129
"Cron: Invalid argument passed for optionsIn. Should be one of function, or object (options).",
128130
);
129131
}
130132

131133
if (isFunction(fnOrOptions2)) {
132-
func = fnOrOptions2;
134+
func = fnOrOptions2 as CronCallback<T>;
133135
} else if (typeof fnOrOptions2 === "object") {
134-
options = fnOrOptions2;
136+
options = fnOrOptions2 as CronOptions<T>;
135137
} else if (fnOrOptions2 !== void 0) {
136138
throw new Error(
137139
"Cron: Invalid argument passed for funcIn. Should be one of function, or object (options).",
138140
);
139141
}
140142

141143
this.name = options?.name;
142-
this.options = CronOptionsHandler(options);
144+
this.options = CronOptionsHandler<T>(options);
143145

144146
this._states = {
145147
kill: false,
@@ -159,7 +161,7 @@ class Cron {
159161
pattern &&
160162
(pattern instanceof Date || ((typeof pattern === "string") && pattern.indexOf(":") > 0))
161163
) {
162-
this._states.once = new CronDate(pattern, this.options.timezone || this.options.utcOffset);
164+
this._states.once = new CronDate<T>(pattern, this.options.timezone || this.options.utcOffset);
163165
} else {
164166
this._states.pattern = new CronPattern(pattern as string, this.options.timezone);
165167
}
@@ -172,7 +174,7 @@ class Cron {
172174
"Cron: Tried to initialize new named job '" + this.name + "', but name already taken.",
173175
);
174176
} else {
175-
scheduledJobs.push(this);
177+
scheduledJobs.push(this as Cron<unknown>);
176178
}
177179
}
178180

@@ -191,7 +193,7 @@ class Cron {
191193
* @param prev - Optional. Date to start from. Can be a CronDate, Date object, or a string representing a date.
192194
* @returns The next run time as a Date object, or null if there is no next run.
193195
*/
194-
public nextRun(prev?: CronDate | Date | string | null): Date | null {
196+
public nextRun(prev?: CronDate<T> | Date | string | null): Date | null {
195197
const next = this._next(prev);
196198
return next ? next.getDate(false) : null;
197199
}
@@ -208,7 +210,8 @@ class Cron {
208210
n = this._states.maxRuns;
209211
}
210212
const enumeration: Date[] = [];
211-
let prev: CronDate | Date | string | undefined | null = previous || this._states.currentRun ||
213+
let prev: CronDate<T> | Date | string | undefined | null = previous ||
214+
this._states.currentRun ||
212215
undefined;
213216
while (n-- && (prev = this.nextRun(prev))) {
214217
enumeration.push(prev);
@@ -283,15 +286,15 @@ class Cron {
283286
*
284287
* @param prev Starting date, defaults to now - minimum interval
285288
*/
286-
public msToNext(prev?: CronDate | Date | string): number | null {
289+
public msToNext(prev?: CronDate<T> | Date | string): number | null {
287290
// Get next run time
288291
const next = this._next(prev);
289292

290293
if (next) {
291294
if (prev instanceof CronDate || prev instanceof Date) {
292295
return (next.getTime() - prev.getTime());
293296
} else {
294-
return (next.getTime() - new CronDate(prev).getTime());
297+
return (next.getTime() - new CronDate<T>(prev).getTime());
295298
}
296299
} else {
297300
return null;
@@ -317,7 +320,7 @@ class Cron {
317320

318321
// Remove job from the scheduledJobs array to free up the name, and allow the job to be
319322
// garbage collected
320-
const jobIndex = scheduledJobs.indexOf(this);
323+
const jobIndex = scheduledJobs.indexOf(this as Cron<unknown>);
321324
if (jobIndex >= 0) {
322325
scheduledJobs.splice(jobIndex, 1);
323326
}
@@ -350,7 +353,7 @@ class Cron {
350353
*
351354
* @param func - Function to be run each iteration of pattern
352355
*/
353-
public schedule(func?: CronCallback): Cron {
356+
public schedule(func?: CronCallback<T>): Cron<T> {
354357
// If a function is already scheduled, bail out
355358
if (func && this.fn) {
356359
throw new Error(
@@ -395,15 +398,15 @@ class Cron {
395398
private async _trigger(initiationDate?: Date) {
396399
this._states.blocking = true;
397400

398-
this._states.currentRun = new CronDate(
401+
this._states.currentRun = new CronDate<T>(
399402
void 0, // We should use initiationDate, but that does not play well with fake timers in third party tests. In real world there is not much difference though */
400403
this.options.timezone || this.options.utcOffset,
401404
);
402405

403406
if (this.options.catch) {
404407
try {
405408
if (this.fn !== undefined) {
406-
await this.fn(this, this.options.context);
409+
await this.fn(this, this.options.context as T);
407410
}
408411
} catch (_e) {
409412
if (isFunction(this.options.catch)) {
@@ -413,11 +416,11 @@ class Cron {
413416
} else {
414417
// Trigger the function without catching
415418
if (this.fn !== undefined) {
416-
await this.fn(this, this.options.context);
419+
await this.fn(this, this.options.context as T);
417420
}
418421
}
419422

420-
this._states.previousRun = new CronDate(
423+
this._states.previousRun = new CronDate<T>(
421424
initiationDate,
422425
this.options.timezone || this.options.utcOffset,
423426
);
@@ -470,7 +473,7 @@ class Cron {
470473
/**
471474
* Internal version of next. Cron needs millseconds internally, hence _next.
472475
*/
473-
private _next(previousRun?: CronDate | Date | string | null) {
476+
private _next(previousRun?: CronDate<T> | Date | string | null) {
474477
let hasPreviousRun = (previousRun || this._states.currentRun) ? true : false;
475478

476479
// If no previous run, and startAt and interval is set, calculate when the last run should have been
@@ -481,19 +484,19 @@ class Cron {
481484
}
482485

483486
// Ensure previous run is a CronDate
484-
previousRun = new CronDate(previousRun, this.options.timezone || this.options.utcOffset);
487+
previousRun = new CronDate<T>(previousRun, this.options.timezone || this.options.utcOffset);
485488

486489
// Previous run should never be before startAt
487490
if (
488491
this.options.startAt && previousRun &&
489-
previousRun.getTime() < (this.options.startAt as CronDate).getTime()
492+
previousRun.getTime() < (this.options.startAt as CronDate<T>).getTime()
490493
) {
491494
previousRun = this.options.startAt;
492495
}
493496

494497
// Calculate next run according to pattern or one-off timestamp, pass actual previous run to increment
495-
let nextRun: CronDate | null = this._states.once ||
496-
new CronDate(previousRun, this.options.timezone || this.options.utcOffset);
498+
let nextRun: CronDate<T> | null = this._states.once ||
499+
new CronDate<T>(previousRun, this.options.timezone || this.options.utcOffset);
497500

498501
// if the startAt is in the future and the interval is set, then the prev is already set to the startAt, so there is no need to increment it
499502
if (!startAtInFutureWithInterval && nextRun !== this._states.once) {
@@ -504,13 +507,15 @@ class Cron {
504507
);
505508
}
506509

507-
if (this._states.once && this._states.once.getTime() <= (previousRun as CronDate).getTime()) {
510+
if (
511+
this._states.once && this._states.once.getTime() <= (previousRun as CronDate<T>).getTime()
512+
) {
508513
return null;
509514
} else if (
510515
(nextRun === null) ||
511516
(this._states.maxRuns !== undefined && this._states.maxRuns <= 0) ||
512517
(this._states.kill) ||
513-
(this.options.stopAt && nextRun.getTime() >= (this.options.stopAt as CronDate).getTime())
518+
(this.options.stopAt && nextRun.getTime() >= (this.options.stopAt as CronDate<T>).getTime())
514519
) {
515520
return null;
516521
} else {
@@ -524,21 +529,22 @@ class Cron {
524529
* Should only be called from the _next function.
525530
*/
526531
private _calculatePreviousRun(
527-
prev: CronDate | Date | string | undefined | null,
532+
prev: CronDate<T> | Date | string | undefined | null,
528533
hasPreviousRun: boolean,
529-
): [CronDate | undefined, boolean] {
530-
const now = new CronDate(undefined, this.options.timezone || this.options.utcOffset);
531-
let newPrev: CronDate | undefined | null = prev as CronDate;
532-
if ((this.options.startAt as CronDate).getTime() <= now.getTime()) {
533-
newPrev = this.options.startAt as CronDate;
534-
let prevTimePlusInterval = (newPrev as CronDate).getTime() + this.options.interval! * 1000;
534+
): [CronDate<T> | undefined, boolean] {
535+
const now = new CronDate<T>(undefined, this.options.timezone || this.options.utcOffset);
536+
let newPrev: CronDate<T> | undefined | null = prev as CronDate<T>;
537+
if ((this.options.startAt as CronDate<T>).getTime() <= now.getTime()) {
538+
newPrev = this.options.startAt as CronDate<T>;
539+
let prevTimePlusInterval = (newPrev as CronDate<T>).getTime() + this.options.interval! * 1000;
535540
while (prevTimePlusInterval <= now.getTime()) {
536-
newPrev = new CronDate(newPrev, this.options.timezone || this.options.utcOffset).increment(
537-
this._states.pattern,
538-
this.options,
539-
true,
540-
);
541-
prevTimePlusInterval = (newPrev as CronDate).getTime() + this.options.interval! * 1000;
541+
newPrev = new CronDate<T>(newPrev, this.options.timezone || this.options.utcOffset)
542+
.increment(
543+
this._states.pattern,
544+
this.options,
545+
true,
546+
);
547+
prevTimePlusInterval = (newPrev as CronDate<T>).getTime() + this.options.interval! * 1000;
542548
}
543549
hasPreviousRun = true;
544550
}

src/date.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const RecursionSteps: RecursionStep[] = [
4747
* @param d Input date, if using string representation ISO 8001 (2015-11-24T19:40:00) local timezone is expected
4848
* @param tz String representation of target timezone in Europe/Stockholm format, or a number representing offset in minutes.
4949
*/
50-
class CronDate {
50+
class CronDate<T = undefined> {
5151
tz: string | number | undefined;
5252

5353
/**
@@ -90,7 +90,7 @@ class CronDate {
9090
*/
9191
year!: number;
9292

93-
constructor(d?: CronDate | Date | string | null, tz?: string | number) {
93+
constructor(d?: CronDate<T> | Date | string | null, tz?: string | number) {
9494
/**
9595
* TimeZone
9696
* @type {string|number|undefined}
@@ -241,7 +241,7 @@ class CronDate {
241241
*
242242
* @param {CronDate} d - Input date
243243
*/
244-
private fromCronDate(d: CronDate) {
244+
private fromCronDate(d: CronDate<T>) {
245245
this.tz = d.tz;
246246
this.year = d.year;
247247
this.month = d.month;
@@ -303,7 +303,7 @@ class CronDate {
303303
* Find next match of current part
304304
*/
305305
private findNext(
306-
options: CronOptions,
306+
options: CronOptions<T>,
307307
target: RecursionTarget,
308308
pattern: CronPattern,
309309
offset: number,
@@ -416,7 +416,11 @@ class CronDate {
416416
*
417417
* @private
418418
*/
419-
private recurse(pattern: CronPattern, options: CronOptions, doing: number): CronDate | null {
419+
private recurse(
420+
pattern: CronPattern,
421+
options: CronOptions<T>,
422+
doing: number,
423+
): CronDate<T> | null {
420424
// Find next month (or whichever part we're at)
421425
const res = this.findNext(options, RecursionSteps[doing][0], pattern, RecursionSteps[doing][2]);
422426

@@ -469,9 +473,9 @@ class CronDate {
469473
*/
470474
public increment(
471475
pattern: CronPattern,
472-
options: CronOptions,
476+
options: CronOptions<T>,
473477
hasPreviousRun: boolean,
474-
): CronDate | null {
478+
): CronDate<T> | null {
475479
// Move to next second, or increment according to minimum interval indicated by option `interval: x`
476480
// Do not increment a full interval if this is the very first run
477481
this.second += (options.interval !== undefined && options.interval > 1 && hasPreviousRun)

src/options.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ type ProtectCallbackFn = (job: Cron) => void;
99
*
1010
* @interface
1111
*/
12-
interface CronOptions {
12+
interface CronOptions<T = undefined> {
1313
/**
1414
* The name of the cron job. If provided, the job will be added to the
1515
* `scheduledJobs` array, allowing it to be accessed by name.
@@ -64,12 +64,12 @@ interface CronOptions {
6464
/**
6565
* The date and time at which the job should start running.
6666
*/
67-
startAt?: string | Date | CronDate;
67+
startAt?: string | Date | CronDate<T>;
6868

6969
/**
7070
* The date and time at which the job should stop running.
7171
*/
72-
stopAt?: string | Date | CronDate;
72+
stopAt?: string | Date | CronDate<T>;
7373

7474
/**
7575
* The timezone for the cron job.
@@ -90,7 +90,7 @@ interface CronOptions {
9090
/**
9191
* An optional context object that will be passed to the job function.
9292
*/
93-
context?: unknown;
93+
context?: T;
9494
}
9595

9696
/**
@@ -100,7 +100,7 @@ interface CronOptions {
100100
* @returns The processed and validated cron options.
101101
* @throws {Error} If any of the options are invalid.
102102
*/
103-
function CronOptionsHandler(options?: CronOptions): CronOptions {
103+
function CronOptionsHandler<T = undefined>(options?: CronOptions<T>): CronOptions<T> {
104104
if (options === void 0) {
105105
options = {};
106106
}

0 commit comments

Comments
 (0)