@stacksjs/scheduler 0.58.72 → 0.59.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -127,7 +127,6 @@ For casual chit-chat with others using this package:
127
127
  Many thanks to the following core technologies & people who have contributed to this package:
128
128
 
129
129
  - [Chris Breuer](https://github.com/chrisbbreuer)
130
- - [Novu](https://novu.co/)
131
130
  - [All Contributors](../../contributors)
132
131
 
133
132
  ## 📄 License
@@ -0,0 +1,74 @@
1
+ export declare const CONSTRAINTS: Readonly<{
2
+ readonly second: readonly [0, 59];
3
+ readonly minute: readonly [0, 59];
4
+ readonly hour: readonly [0, 23];
5
+ readonly dayOfMonth: readonly [1, 31];
6
+ readonly month: readonly [1, 12];
7
+ readonly dayOfWeek: readonly [0, 7];
8
+ }>;
9
+ export declare const MONTH_CONSTRAINTS: Readonly<{
10
+ readonly 1: 31;
11
+ readonly 2: 29;
12
+ readonly 3: 31;
13
+ readonly 4: 30;
14
+ readonly 5: 31;
15
+ readonly 6: 30;
16
+ readonly 7: 31;
17
+ readonly 8: 31;
18
+ readonly 9: 30;
19
+ readonly 10: 31;
20
+ readonly 11: 30;
21
+ readonly 12: 31;
22
+ }>;
23
+ export declare const PARSE_DEFAULTS: Readonly<{
24
+ readonly second: "0";
25
+ readonly minute: "*";
26
+ readonly hour: "*";
27
+ readonly dayOfMonth: "*";
28
+ readonly month: "*";
29
+ readonly dayOfWeek: "*";
30
+ }>;
31
+ export declare const ALIASES: Readonly<{
32
+ readonly jan: 1;
33
+ readonly feb: 2;
34
+ readonly mar: 3;
35
+ readonly apr: 4;
36
+ readonly may: 5;
37
+ readonly jun: 6;
38
+ readonly jul: 7;
39
+ readonly aug: 8;
40
+ readonly sep: 9;
41
+ readonly oct: 10;
42
+ readonly nov: 11;
43
+ readonly dec: 12;
44
+ readonly sun: 0;
45
+ readonly mon: 1;
46
+ readonly tue: 2;
47
+ readonly wed: 3;
48
+ readonly thu: 4;
49
+ readonly fri: 5;
50
+ readonly sat: 6;
51
+ }>;
52
+ export declare const TIME_UNITS_MAP: Readonly<{
53
+ readonly SECOND: "second";
54
+ readonly MINUTE: "minute";
55
+ readonly HOUR: "hour";
56
+ readonly DAY_OF_MONTH: "dayOfMonth";
57
+ readonly MONTH: "month";
58
+ readonly DAY_OF_WEEK: "dayOfWeek";
59
+ }>;
60
+ export declare const TIME_UNITS: ["second", "minute", "hour", "dayOfMonth", "month", "dayOfWeek"];
61
+ export declare const TIME_UNITS_LEN: number;
62
+ export declare const PRESETS: Readonly<{
63
+ readonly '@yearly': "0 0 0 1 1 *";
64
+ readonly '@monthly': "0 0 0 1 * *";
65
+ readonly '@weekly': "0 0 0 * * 0";
66
+ readonly '@daily': "0 0 0 * * *";
67
+ readonly '@hourly': "0 0 * * * *";
68
+ readonly '@minutely': "0 * * * * *";
69
+ readonly '@secondly': "* * * * * *";
70
+ readonly '@weekdays': "0 0 0 * * 1-5";
71
+ readonly '@weekends': "0 0 0 * * 0,6";
72
+ }>;
73
+ export declare const RE_WILDCARDS: RegExp;
74
+ export declare const RE_RANGE: RegExp;
@@ -0,0 +1,5 @@
1
+ export declare class CronError extends Error {
2
+ }
3
+ export declare class ExclusiveParametersError extends CronError {
4
+ constructor(param1: string, param2: string);
5
+ }
@@ -0,0 +1,7 @@
1
+ import { Schedule } from './schedule';
2
+ export { CronJob as BunCronJob } from './job';
3
+ export { CronTime } from './time';
4
+ export type { CronCallback, CronCommand, CronContext, CronJobParams, CronOnCompleteCallback, CronOnCompleteCommand, Ranges, TimeUnit, } from './types/cron';
5
+ export * from './types/utils';
6
+ export * from './schedule';
7
+ export default Schedule;
package/dist/index.js CHANGED
@@ -573,6 +573,14 @@ class Schedule {
573
573
  new CronJob(this.cronPattern, this.task, null, true, this.timezone);
574
574
  log.info(`Scheduled task with pattern: ${this.cronPattern} in timezone: ${this.timezone}`);
575
575
  }
576
+ job(path) {
577
+ log.info(`Scheduling job: ${path}`);
578
+ return this;
579
+ }
580
+ action(path) {
581
+ log.info(`Scheduling action: ${path}`);
582
+ return this;
583
+ }
576
584
  static command(cmd) {
577
585
  this.cmd = cmd;
578
586
  return this;
package/dist/job.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Many thanks to https://github.com/kelektiv/node-cron for the inspiration
3
+ */
4
+ import { CronTime } from './time';
5
+ import type { CronCallback, CronContext, CronJobParams, CronOnCompleteCallback, CronOnCompleteCommand, WithOnComplete } from './types/cron';
6
+ export declare class CronJob<OC extends CronOnCompleteCommand | null = null, C = null> {
7
+ cronTime: CronTime;
8
+ running: boolean;
9
+ unrefTimeout: boolean;
10
+ lastExecution: Date | null;
11
+ runOnce: boolean;
12
+ context: CronContext<C>;
13
+ onComplete?: WithOnComplete<OC> extends true ? CronOnCompleteCallback : undefined;
14
+ private _timeout?;
15
+ private _callbacks;
16
+ private _errorHandler?;
17
+ constructor(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<OC, C>['unrefTimeout'], errorHandler?: (error: Error) => void);
18
+ constructor(cronTime: CronJobParams<OC, C>['cronTime'], onTick: CronJobParams<OC, C>['onTick'], onComplete?: CronJobParams<OC, C>['onComplete'], start?: CronJobParams<OC, C>['start'], timeZone?: null, context?: CronJobParams<OC, C>['context'], runOnInit?: CronJobParams<OC, C>['runOnInit'], utcOffset?: CronJobParams<OC, C>['utcOffset'], unrefTimeout?: CronJobParams<OC, C>['unrefTimeout'], errorHandler?: (error: Error) => void);
19
+ static from<OC extends CronOnCompleteCommand | null = null, C = null>(params: CronJobParams<OC, C>): CronJob<OC, C>;
20
+ private _fnWrap;
21
+ addCallback(callback: CronCallback<C, WithOnComplete<OC>>): void;
22
+ setTime(time: CronTime): void;
23
+ nextDate(): import("luxon").DateTime<boolean>;
24
+ fireOnTick(): void;
25
+ nextDates(i?: number): import("luxon").DateTime<boolean>[];
26
+ start(): void;
27
+ lastDate(): Date | null;
28
+ /**
29
+ * Stop the cronjob.
30
+ */
31
+ stop(): void;
32
+ }
@@ -0,0 +1,29 @@
1
+ import type { DateTime } from 'luxon';
2
+ export declare class Schedule {
3
+ private cronPattern;
4
+ private timezone;
5
+ private readonly task;
6
+ constructor(task: () => void);
7
+ everySecond(): this;
8
+ everyMinute(): this;
9
+ everyTwoMinutes(): this;
10
+ everyFiveMinutes(): this;
11
+ everyTenMinutes(): this;
12
+ everyThirtyMinutes(): this;
13
+ hourly(): this;
14
+ daily(): this;
15
+ weekly(): this;
16
+ monthly(): this;
17
+ yearly(): this;
18
+ onDays(days: number[]): this;
19
+ at(time: string): this;
20
+ setTimeZone(timezone: string): this;
21
+ start(): void;
22
+ job(path: string): this;
23
+ action(path: string): this;
24
+ static command(cmd: string): typeof Schedule;
25
+ }
26
+ export declare function sendAt(cronTime: string | Date | DateTime): DateTime;
27
+ export declare function timeout(cronTime: string | Date | DateTime): number;
28
+ export type Scheduler = typeof Schedule;
29
+ export default Schedule;
package/dist/time.d.ts ADDED
@@ -0,0 +1,159 @@
1
+ import type { Zone } from 'luxon';
2
+ import { DateTime } from 'luxon';
3
+ import type { CronJobParams } from './types/cron';
4
+ export declare class CronTime {
5
+ source: string | DateTime;
6
+ timeZone?: string;
7
+ utcOffset?: number;
8
+ realDate: boolean;
9
+ private second;
10
+ private minute;
11
+ private hour;
12
+ private dayOfMonth;
13
+ private month;
14
+ private dayOfWeek;
15
+ constructor(source: CronJobParams['cronTime'], timeZone?: CronJobParams['timeZone'], utcOffset?: null);
16
+ constructor(source: CronJobParams['cronTime'], timeZone?: null, utcOffset?: CronJobParams['utcOffset']);
17
+ private _getWeekDay;
18
+ /**
19
+ * Ensure that the syntax parsed correctly and correct the specified values if needed.
20
+ */
21
+ private _verifyParse;
22
+ /**
23
+ * Calculate the "next" scheduled time
24
+ */
25
+ sendAt(): DateTime;
26
+ sendAt(i: number): DateTime[];
27
+ /**
28
+ * Get the number of milliseconds in the future at which to fire our callbacks.
29
+ */
30
+ getTimeout(): number;
31
+ /**
32
+ * writes out a cron string
33
+ */
34
+ toString(): string;
35
+ /**
36
+ * Json representation of the parsed cron syntax.
37
+ */
38
+ toJSON(): string[];
39
+ /**
40
+ * Get next date matching the specified cron time.
41
+ *
42
+ * Algorithm:
43
+ * - Start with a start date and a parsed crontime.
44
+ * - Loop until 5 seconds have passed, or we found the next date.
45
+ * - Within the loop:
46
+ * - If it took longer than 5 seconds to select a date, throw an exception.
47
+ * - Find the next month to run at.
48
+ * - Find the next day of the month to run at.
49
+ * - Find the next day of the week to run at.
50
+ * - Find the next hour to run at.
51
+ * - Find the next minute to run at.
52
+ * - Find the next second to run at.
53
+ * - Check that the chosen time does not equal the current execution.
54
+ * - Return the selected date object.
55
+ */
56
+ getNextDateFrom(start: Date | DateTime, timeZone?: string | Zone): DateTime<boolean>;
57
+ /**
58
+ * Search backwards in time 1 minute at a time, to detect a DST forward jump.
59
+ * When the jump is found, the range of the jump is investigated to check for acceptable cron times.
60
+ *
61
+ * A pair is returned, whose first is a boolean representing if an acceptable time was found inside the jump,
62
+ * and whose second is a DateTime representing the first millisecond after the jump.
63
+ *
64
+ * The input date is expected to be decently close to a DST jump.
65
+ * Up to a day in the past is checked before an error is thrown.
66
+ * @param date
67
+ * @return [boolean, DateTime]
68
+ */
69
+ private _findPreviousDSTJump;
70
+ /**
71
+ * Given 2 DateTimes, which represent 1 second before and immediately after a DST forward jump,
72
+ * checks if a time in the skipped range would have been a valid CronJob time.
73
+ *
74
+ * Could technically work with just one of these values, extracting the other by adding or subtracting seconds.
75
+ * However, this couples the input DateTime to actually being tied to a DST jump,
76
+ * which would make the function harder to test.
77
+ * This way the logic just tests a range of minutes and hours, regardless if there are skipped time points underneath.
78
+ *
79
+ * Assumes the DST jump started no earlier than 0:00 and jumped forward by at least 1 minute, to at most 23:59.
80
+ * i.e. The day is assumed constant, but the jump is not assumed to be an hour long.
81
+ * Empirically, it is almost always one hour, but very, very rarely 30 minutes.
82
+ *
83
+ * Assumes dayOfWeek, dayOfMonth and month match all match, so only the hours, minutes and seconds are to be checked.
84
+ * @param {DateTime} beforeJumpingPoint
85
+ * @param {DateTime} afterJumpingPoint
86
+ * @returns {boolean} True if a valid CronJob time exists within the skipped DST range, false otherwise.
87
+ */
88
+ private _checkTimeInSkippedRange;
89
+ /**
90
+ * Component of checking if a CronJob time existed in a DateTime range skipped by DST.
91
+ * This subroutine makes a further assumption that the skipped range is fully contained in one hour,
92
+ * and that all other larger units are valid for the job.
93
+ *
94
+ * for example a jump from 02:00:00 to 02:30:00, but not from 02:00:00 to 03:00:00.
95
+ * @see _checkTimeInSkippedRange
96
+ *
97
+ * This is done by checking if any minute in startMinute - endMinute is valid, excluding endMinute.
98
+ * For endMinute, there is only a match if the 0th second is a valid time.
99
+ */
100
+ private _checkTimeInSkippedRangeSingleHour;
101
+ /**
102
+ * Component of checking if a CronJob time existed in a DateTime range skipped by DST.
103
+ * This subroutine assumes the jump touches at least 2 hours, but the jump does not necessarily fully contain these hours.
104
+ *
105
+ * @see _checkTimeInSkippedRange
106
+ *
107
+ * This is done by defining the minutes to check for the first and last hour,
108
+ * and checking all 60 minutes for any hours in between them.
109
+ *
110
+ * If any hour x minute combination is a valid time, true is returned.
111
+ * The endMinute x endHour combination is only checked with the 0th second, since the rest would be out of the range.
112
+ *
113
+ * @param startHour {number}
114
+ * @param startMinute {number}
115
+ * @param endHour {number}
116
+ * @param endMinute {number}
117
+ */
118
+ private _checkTimeInSkippedRangeMultiHour;
119
+ /**
120
+ * Given expected and actual hours and minutes, report if a DST forward jump occurred.
121
+ *
122
+ * This is the case when the expected is smaller than the acutal.
123
+ *
124
+ * It is not sufficient to check only hours, because some parts of the world apply DST by shifting in minutes.
125
+ * Better to account for it by checking minutes too, before an Australian of Lord Howe Island call us.
126
+ * @param expectedHour
127
+ * @param expectedMinute
128
+ * @param {DateTime} actualDate
129
+ */
130
+ private _forwardDSTJump;
131
+ /**
132
+ * wildcard, or all params in array (for to string)
133
+ */
134
+ private _wcOrAll;
135
+ private _hasAll;
136
+ /**
137
+ * Parse the cron syntax into something useful for selecting the next execution time.
138
+ *
139
+ * Algorithm:
140
+ * - Replace preset
141
+ * - Replace aliases in the source.
142
+ * - Trim string and split for processing.
143
+ * - Loop over split options (ms -> month):
144
+ * - Get the value (or default) in the current position.
145
+ * - Parse the value.
146
+ */
147
+ private _parse;
148
+ /**
149
+ * Parse individual field from the cron syntax provided.
150
+ *
151
+ * Algorithm:
152
+ * - Split field by commas aand check for wildcards to ensure proper user.
153
+ * - Replace wildcard values with <low>-<high> boundaries.
154
+ * - Split field by commas and then iterate over ranges inside field.
155
+ * - If range matches pattern then map over matches using replace (to parse the range by the regex pattern)
156
+ * - Starting with the lower bounds of the range iterate by step up to the upper bounds and toggle the CronTime field value flag on.
157
+ */
158
+ private _parseField;
159
+ }
@@ -0,0 +1,50 @@
1
+ /// <reference types="node" />
2
+ import type { SpawnOptions } from 'node:child_process';
3
+ import type { DateTime } from 'luxon';
4
+ import type { CONSTRAINTS, TIME_UNITS_MAP } from '../constants';
5
+ import type { CronJob } from '../job';
6
+ import type { IntRange } from './utils';
7
+ interface BaseCronJobParams<OC extends CronOnCompleteCommand | null = null, C = null> {
8
+ cronTime: string | Date | DateTime;
9
+ onTick: CronCommand<C, WithOnComplete<OC>>;
10
+ onComplete?: OC;
11
+ start?: boolean | null;
12
+ context?: C;
13
+ runOnInit?: boolean | null;
14
+ unrefTimeout?: boolean | null;
15
+ }
16
+ export type CronJobParams<OC extends CronOnCompleteCommand | null = null, C = null> = BaseCronJobParams<OC, C> & ({
17
+ timeZone?: string | null;
18
+ utcOffset?: never;
19
+ } | {
20
+ timeZone?: never;
21
+ utcOffset?: number | null;
22
+ });
23
+ export type CronContext<C> = C extends null ? CronJob : NonNullable<C>;
24
+ export type CronCallback<C, WithOnCompleteBool extends boolean = false> = (this: CronContext<C>, onComplete: WithOnCompleteBool extends true ? CronOnCompleteCallback : never) => void | Promise<void>;
25
+ export type CronOnCompleteCallback = () => void | Promise<void>;
26
+ export type CronSystemCommand = string | {
27
+ command: string;
28
+ args?: readonly string[] | null;
29
+ options?: SpawnOptions | null;
30
+ };
31
+ export type CronCommand<C, WithOnCompleteBool extends boolean = false> = CronCallback<C, WithOnCompleteBool> | CronSystemCommand;
32
+ export type CronOnCompleteCommand = CronOnCompleteCallback | CronSystemCommand;
33
+ export type WithOnComplete<OC> = OC extends null ? false : true;
34
+ export type TimeUnit = (typeof TIME_UNITS_MAP)[keyof typeof TIME_UNITS_MAP];
35
+ export type TimeUnitField<T extends TimeUnit> = Partial<Record<Ranges[T], boolean>>;
36
+ export interface Ranges {
37
+ second: SecondRange;
38
+ minute: MinuteRange;
39
+ hour: HourRange;
40
+ dayOfMonth: DayOfMonthRange;
41
+ month: MonthRange;
42
+ dayOfWeek: DayOfWeekRange;
43
+ }
44
+ export type SecondRange = IntRange<(typeof CONSTRAINTS)['second'][0], (typeof CONSTRAINTS)['second'][1]>;
45
+ export type MinuteRange = IntRange<(typeof CONSTRAINTS)['minute'][0], (typeof CONSTRAINTS)['minute'][1]>;
46
+ export type HourRange = IntRange<(typeof CONSTRAINTS)['hour'][0], (typeof CONSTRAINTS)['hour'][1]>;
47
+ export type DayOfMonthRange = IntRange<(typeof CONSTRAINTS)['dayOfMonth'][0], (typeof CONSTRAINTS)['dayOfMonth'][1]>;
48
+ export type MonthRange = IntRange<(typeof CONSTRAINTS)['month'][0], (typeof CONSTRAINTS)['month'][1]>;
49
+ export type DayOfWeekRange = IntRange<(typeof CONSTRAINTS)['dayOfWeek'][0], (typeof CONSTRAINTS)['dayOfWeek'][1]>;
50
+ export {};
@@ -0,0 +1,3 @@
1
+ export type IntRange<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F, false>>;
2
+ type Enumerate<N extends number, WithTail extends boolean = true, Acc extends number[] = []> = Acc['length'] extends N ? WithTail extends true ? [...Acc, Acc['length']][number] : Acc[number] : Enumerate<N, WithTail, [...Acc, Acc['length']]>;
3
+ export {};
@@ -0,0 +1,12 @@
1
+ import type { Ranges } from './types/cron';
2
+ export declare function getRecordKeys<K extends Ranges[keyof Ranges]>(record: Partial<Record<K, boolean>>): K[];
3
+ export declare function getTimeZoneAndOffset(timeZone?: string | null, utcOffset?: number | null): {
4
+ timeZone: string;
5
+ utcOffset: null;
6
+ } | {
7
+ timeZone: null;
8
+ utcOffset: number;
9
+ } | {
10
+ timeZone: null;
11
+ utcOffset: null;
12
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/scheduler",
3
3
  "type": "module",
4
- "version": "0.58.72",
4
+ "version": "0.59.1",
5
5
  "description": "The Stacks scheduler.",
6
6
  "author": "Chris Breuer",
7
7
  "license": "MIT",
package/src/schedule.ts CHANGED
@@ -98,6 +98,17 @@ export class Schedule {
98
98
  log.info(`Scheduled task with pattern: ${this.cronPattern} in timezone: ${this.timezone}`)
99
99
  }
100
100
 
101
+ // job and action methods need to be added and they accept a path string param
102
+ job(path: string) {
103
+ log.info(`Scheduling job: ${path}`)
104
+ return this
105
+ }
106
+
107
+ action(path: string) {
108
+ log.info(`Scheduling action: ${path}`)
109
+ return this
110
+ }
111
+
101
112
  static command(cmd: string) {
102
113
  // log.info(`Executing command: ${cmd}`)
103
114
  this.cmd = cmd
@@ -113,4 +124,6 @@ export function timeout(cronTime: string | Date | DateTime): number {
113
124
  return new CronTime(cronTime).getTimeout()
114
125
  }
115
126
 
127
+ export type Scheduler = typeof Schedule
128
+
116
129
  export default Schedule