@namnguyen.repl.it/nn-scheduler 1.0.0
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 +58 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +18 -0
- package/dist/scheduler.d.ts +55 -0
- package/dist/scheduler.js +318 -0
- package/dist/types/SchedulerType.d.ts +34 -0
- package/dist/types/SchedulerType.js +2 -0
- package/package.json +41 -0
package/Readme.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
|
|
2
|
+
## Example usage with timeout handling in actions
|
|
3
|
+
class MyAction {
|
|
4
|
+
static longRunningAction() {
|
|
5
|
+
return async () => {
|
|
6
|
+
try {
|
|
7
|
+
//console.log('Long running action started');
|
|
8
|
+
await new Promise((resolve, reject) => {
|
|
9
|
+
const timeout = setTimeout(() => reject(new Error('Action timed out')), 90 * 1000);
|
|
10
|
+
setTimeout(() => {
|
|
11
|
+
clearTimeout(timeout);
|
|
12
|
+
resolve();
|
|
13
|
+
}, 120 * 1000);
|
|
14
|
+
});
|
|
15
|
+
//console.log('Long running action completed');
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error(`Long running action failed: ${error}`);
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static failingAction() {
|
|
24
|
+
return async () => {
|
|
25
|
+
try {
|
|
26
|
+
//console.log('Failing action started');
|
|
27
|
+
await new Promise((_, reject) => setTimeout(() => reject(new Error('Action failed')), 1000));
|
|
28
|
+
//console.log('Failing action completed');
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error(`Failing action failed: ${error}`);
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const scheduler = new Scheduler({
|
|
38
|
+
logHandler: (msg) => //console.log(msg)
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
scheduler.schedule([
|
|
42
|
+
{ time: '07:15', action: MyAction.longRunningAction(), weekDays: ['*'] },
|
|
43
|
+
{ time: '09:15', action: MyAction.failingAction(), weekDays: [6, 1, 2, 3] },
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
scheduler.interval([
|
|
47
|
+
{ interval: 60 * 1000, action: MyAction.longRunningAction(), weekDays: ['*'] },
|
|
48
|
+
{ interval: 60 * 1000, action: MyAction.failingAction(), weekDays: [2, 3, 4] },
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
Using PM2 to manage tasks:
|
|
53
|
+
pm2 start scheduler.js --name scheduler
|
|
54
|
+
pm2 list
|
|
55
|
+
pm2 logs scheduler
|
|
56
|
+
pm2 stop scheduler
|
|
57
|
+
pm2 restart scheduler
|
|
58
|
+
pm2 delete scheduler
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types/SchedulerType"), exports);
|
|
18
|
+
__exportStar(require("./scheduler"), exports);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { IntervalTask, ScheduledTask, SchedulerConfig } from './types/SchedulerType';
|
|
2
|
+
/**
|
|
3
|
+
* Scheduler class for managing background tasks in Node.js, designed to run under PM2.
|
|
4
|
+
* Supports two types of tasks:
|
|
5
|
+
* 1. Scheduled tasks that run at specific times on specified weekdays or matchPattern.
|
|
6
|
+
* 2. Interval tasks that run every n milliseconds if weekdays or matchPattern conditions are met.
|
|
7
|
+
* Runs indefinitely until stopped via PM2 commands (e.g., `pm2 stop scheduler`).
|
|
8
|
+
* Long-running or failing tasks are handled by the actions themselves.
|
|
9
|
+
*/
|
|
10
|
+
export declare class Scheduler {
|
|
11
|
+
private logHandler;
|
|
12
|
+
private cronJobs;
|
|
13
|
+
private intervalIds;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new Scheduler instance
|
|
16
|
+
* @param config Configuration object with optional logHandler
|
|
17
|
+
*/
|
|
18
|
+
constructor(config?: SchedulerConfig);
|
|
19
|
+
/**
|
|
20
|
+
* Converts a time string and weekdays into a cron expression
|
|
21
|
+
* @param time Time in "HH:MM" format
|
|
22
|
+
* @param weekDays Array of weekdays (0-6 or '*')
|
|
23
|
+
* @returns Cron expression string (e.g., "15 7 * * *")
|
|
24
|
+
* @throws Error if time format is invalid
|
|
25
|
+
*/
|
|
26
|
+
private timeToCron;
|
|
27
|
+
private patternToCron;
|
|
28
|
+
/**
|
|
29
|
+
* Checks if the current day matches the specified weekdays
|
|
30
|
+
* @param weekDays Array of weekdays (0-6 or '*')
|
|
31
|
+
* @returns True if today is in the weekday list or '*' is included
|
|
32
|
+
*/
|
|
33
|
+
private isMatchingWeekDay;
|
|
34
|
+
private isMatchingPattern;
|
|
35
|
+
/**
|
|
36
|
+
* Schedules tasks to run at specific times on specified weekdays or matchPattern
|
|
37
|
+
* @param tasks Array of scheduled tasks to execute
|
|
38
|
+
*/
|
|
39
|
+
schedule(tasks: ScheduledTask[]): void;
|
|
40
|
+
/**
|
|
41
|
+
* Schedules tasks to run every n milliseconds if weekdays or matchPattern conditions are met
|
|
42
|
+
* @param tasks Array of interval tasks to execute
|
|
43
|
+
*/
|
|
44
|
+
interval(tasks: IntervalTask[]): void;
|
|
45
|
+
/**
|
|
46
|
+
* Stops all scheduled and interval tasks
|
|
47
|
+
* Called when PM2 sends a shutdown signal
|
|
48
|
+
*/
|
|
49
|
+
stop(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Sets up integration with PM2 by handling shutdown signals
|
|
52
|
+
* PM2 sends SIGINT when stopping the process
|
|
53
|
+
*/
|
|
54
|
+
private setupPM2;
|
|
55
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.Scheduler = void 0;
|
|
46
|
+
const cron = __importStar(require("node-cron"));
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
/** Writes log messages to a file named by date (e.g., "2025-03-24.log") in the current directory */
|
|
50
|
+
const defaultLogHandler = (message) => {
|
|
51
|
+
const date = new Date().toISOString().split('T')[0];
|
|
52
|
+
const logFile = path.join(process.cwd(), `${date}.log`);
|
|
53
|
+
const logMessage = `${new Date().toISOString()} - ${message}\n`;
|
|
54
|
+
fs.appendFileSync(logFile, logMessage, { encoding: 'utf8' });
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Scheduler class for managing background tasks in Node.js, designed to run under PM2.
|
|
58
|
+
* Supports two types of tasks:
|
|
59
|
+
* 1. Scheduled tasks that run at specific times on specified weekdays or matchPattern.
|
|
60
|
+
* 2. Interval tasks that run every n milliseconds if weekdays or matchPattern conditions are met.
|
|
61
|
+
* Runs indefinitely until stopped via PM2 commands (e.g., `pm2 stop scheduler`).
|
|
62
|
+
* Long-running or failing tasks are handled by the actions themselves.
|
|
63
|
+
*/
|
|
64
|
+
class Scheduler {
|
|
65
|
+
/**
|
|
66
|
+
* Creates a new Scheduler instance
|
|
67
|
+
* @param config Configuration object with optional logHandler
|
|
68
|
+
*/
|
|
69
|
+
constructor(config = {}) {
|
|
70
|
+
this.cronJobs = [];
|
|
71
|
+
this.intervalIds = [];
|
|
72
|
+
this.logHandler = config.logHandler || defaultLogHandler;
|
|
73
|
+
this.setupPM2();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Converts a time string and weekdays into a cron expression
|
|
77
|
+
* @param time Time in "HH:MM" format
|
|
78
|
+
* @param weekDays Array of weekdays (0-6 or '*')
|
|
79
|
+
* @returns Cron expression string (e.g., "15 7 * * *")
|
|
80
|
+
* @throws Error if time format is invalid
|
|
81
|
+
*/
|
|
82
|
+
timeToCron(time, weekDays) {
|
|
83
|
+
const [hours, minutes] = time.split(':').map(Number);
|
|
84
|
+
if (isNaN(hours) || isNaN(minutes) || hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
|
|
85
|
+
throw new Error(`Invalid time format: ${time}`);
|
|
86
|
+
}
|
|
87
|
+
const weekDayStr = weekDays.includes('*') ? '*' : weekDays.join(',');
|
|
88
|
+
return `${minutes} ${hours} * * ${weekDayStr}`;
|
|
89
|
+
}
|
|
90
|
+
// Converts a time string and matchPattern into a cron expression
|
|
91
|
+
// @param time Time in "HH:MM" format
|
|
92
|
+
// @param matchPattern Pattern (e.g., '2025/**/**', '2025/06/01')
|
|
93
|
+
// @returns Cron expression string
|
|
94
|
+
// @throws Error if time or pattern is invalid
|
|
95
|
+
patternToCron(time, matchPattern) {
|
|
96
|
+
const [hours, minutes] = time.split(':').map(Number);
|
|
97
|
+
if (isNaN(hours) || isNaN(minutes) || hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
|
|
98
|
+
throw new Error(`Invalid time format: ${time}`);
|
|
99
|
+
}
|
|
100
|
+
// Validate pattern format (yyyy/mm/dd with ** wildcards)
|
|
101
|
+
if (!matchPattern.match(/^\d{4}\/(\d{2}|\*\*)\/(\d{2}|\*\*)$/)) {
|
|
102
|
+
throw new Error(`Invalid pattern format: ${matchPattern}`);
|
|
103
|
+
}
|
|
104
|
+
const [year, month, day] = matchPattern.split('/');
|
|
105
|
+
const yearNum = parseInt(year, 10);
|
|
106
|
+
const monthNum = month === '**' ? '*' : parseInt(month, 10);
|
|
107
|
+
const dayNum = day === '**' ? '*' : parseInt(day, 10);
|
|
108
|
+
// Validate year (basic range check, assuming 1970-2099 for cron compatibility)
|
|
109
|
+
if (isNaN(yearNum) || yearNum < new Date().getFullYear() || yearNum > 3000) {
|
|
110
|
+
throw new Error(`Invalid year in pattern: ${matchPattern}`);
|
|
111
|
+
}
|
|
112
|
+
// Validate month//
|
|
113
|
+
//@ts-ignore
|
|
114
|
+
if (month !== '**' && (isNaN(monthNum) || monthNum < 1 || monthNum > 12)) {
|
|
115
|
+
throw new Error(`Invalid month in pattern: ${matchPattern}`);
|
|
116
|
+
}
|
|
117
|
+
// Validate day
|
|
118
|
+
//@ts-ignore
|
|
119
|
+
if (day !== '**' && (isNaN(dayNum) || dayNum < 1 || dayNum > 31)) {
|
|
120
|
+
throw new Error(`Invalid day in pattern: ${matchPattern}`);
|
|
121
|
+
}
|
|
122
|
+
// Note: Cron doesn't support year directly, so we rely on pattern matching in schedule
|
|
123
|
+
// For simplicity, we generate a cron expression for day/month, assuming year is handled externally
|
|
124
|
+
return `${minutes} ${hours} ${dayNum} ${monthNum} *`;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Checks if the current day matches the specified weekdays
|
|
128
|
+
* @param weekDays Array of weekdays (0-6 or '*')
|
|
129
|
+
* @returns True if today is in the weekday list or '*' is included
|
|
130
|
+
*/
|
|
131
|
+
isMatchingWeekDay(weekDays) {
|
|
132
|
+
const currentDay = new Date().getDay();
|
|
133
|
+
return weekDays.includes('*') || weekDays.includes(currentDay);
|
|
134
|
+
}
|
|
135
|
+
//Checks if the current date matches the specified match signalling
|
|
136
|
+
//@param matchPattern Pattern (e.g., '2025/**/** ', '2025 /06 /01')
|
|
137
|
+
//@returns True if today matches the pattern
|
|
138
|
+
isMatchingPattern(matchPattern) {
|
|
139
|
+
const today = new Date();
|
|
140
|
+
const day = today.getDate();
|
|
141
|
+
const month = today.getMonth() + 1; // getMonth() is 0-based
|
|
142
|
+
const year = today.getFullYear();
|
|
143
|
+
// Validate pattern format (yyyy/mm/dd with ** wildcards)
|
|
144
|
+
if (!matchPattern.match(/^\d{4}\/(\d{2}|\*\*)\/(\d{2}|\*\*)$/)) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const [patternYear, patternMonth, patternDay] = matchPattern.split('/');
|
|
148
|
+
const yearNum = parseInt(patternYear, 10);
|
|
149
|
+
const monthNum = patternMonth === '**' ? month : parseInt(patternMonth, 10);
|
|
150
|
+
const dayNum = patternDay === '**' ? day : parseInt(patternDay, 10);
|
|
151
|
+
// Validate parsed values
|
|
152
|
+
if (isNaN(yearNum) || (patternMonth !== '**' && isNaN(monthNum)) || (patternDay !== '**' && isNaN(dayNum))) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
// Check year match
|
|
156
|
+
if (yearNum !== year) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
// Check month match
|
|
160
|
+
if (patternMonth !== '**' && monthNum !== month) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
// Check day match
|
|
164
|
+
if (patternDay !== '**' && dayNum !== day) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
// Validate date (e.g., avoid Feb 30)
|
|
168
|
+
if (patternMonth !== '**' && patternDay !== '**') {
|
|
169
|
+
const date = new Date(yearNum, monthNum - 1, dayNum);
|
|
170
|
+
if (date.getFullYear() !== yearNum || date.getMonth() + 1 !== monthNum || date.getDate() !== dayNum) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Schedules tasks to run at specific times on specified weekdays or matchPattern
|
|
178
|
+
* @param tasks Array of scheduled tasks to execute
|
|
179
|
+
*/
|
|
180
|
+
schedule(tasks) {
|
|
181
|
+
for (const task of tasks) {
|
|
182
|
+
if (!task.time) {
|
|
183
|
+
this.logHandler('No valid time provided for scheduled task, skipping');
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
// Weekdays trigger
|
|
187
|
+
try {
|
|
188
|
+
const cronExpression = this.timeToCron(task.time, task.weekDays);
|
|
189
|
+
if (!cron.validate(cronExpression)) {
|
|
190
|
+
this.logHandler(`Invalid cron expression for task at ${task.time}: ${cronExpression}`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const cronJob = cron.schedule(cronExpression, () => __awaiter(this, void 0, void 0, function* () {
|
|
194
|
+
try {
|
|
195
|
+
this.logHandler(`Executing scheduled task at ${task.time} (weekdays trigger)`);
|
|
196
|
+
yield task.action();
|
|
197
|
+
this.logHandler(`Scheduled task at ${task.time} (weekdays trigger) completed`);
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
this.logHandler(`Error in scheduled task at ${task.time} (weekdays trigger): ${error}`);
|
|
201
|
+
}
|
|
202
|
+
}));
|
|
203
|
+
this.cronJobs.push(cronJob);
|
|
204
|
+
this.logHandler(`Scheduled task at ${task.time} with weekdays ${task.weekDays.join(',')}`);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
this.logHandler(`Error processing task at ${task.time} for weekdays: ${error}`);
|
|
208
|
+
}
|
|
209
|
+
// MatchPattern trigger
|
|
210
|
+
if (task.matchPattern) {
|
|
211
|
+
for (const pattern of task.matchPattern) {
|
|
212
|
+
try {
|
|
213
|
+
const cronExpression = this.patternToCron(task.time, pattern);
|
|
214
|
+
if (!cron.validate(cronExpression)) {
|
|
215
|
+
this.logHandler(`Invalid cron expression for task at ${task.time}, pattern ${pattern}: ${cronExpression}`);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
const cronJob = cron.schedule(cronExpression, () => __awaiter(this, void 0, void 0, function* () {
|
|
219
|
+
try {
|
|
220
|
+
this.logHandler(`Executing scheduled task at ${task.time} (pattern ${pattern} trigger)`);
|
|
221
|
+
yield task.action();
|
|
222
|
+
this.logHandler(`Scheduled task at ${task.time} (pattern ${pattern} trigger) completed`);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
this.logHandler(`Error in scheduled task at ${task.time} (pattern ${pattern} trigger): ${error}`);
|
|
226
|
+
}
|
|
227
|
+
}));
|
|
228
|
+
this.cronJobs.push(cronJob);
|
|
229
|
+
this.logHandler(`Scheduled task at ${task.time} with pattern ${pattern}`);
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
this.logHandler(`Error processing task at ${task.time}, pattern ${pattern}: ${error}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Schedules tasks to run every n milliseconds if weekdays or matchPattern conditions are met
|
|
240
|
+
* @param tasks Array of interval tasks to execute
|
|
241
|
+
*/
|
|
242
|
+
interval(tasks) {
|
|
243
|
+
for (const task of tasks) {
|
|
244
|
+
if (task.interval <= 0 || !Number.isInteger(task.interval)) {
|
|
245
|
+
this.logHandler(`Invalid interval: ${task.interval}`);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
249
|
+
let triggered = false;
|
|
250
|
+
// Check weekdays
|
|
251
|
+
if (this.isMatchingWeekDay(task.weekDays)) {
|
|
252
|
+
triggered = true;
|
|
253
|
+
try {
|
|
254
|
+
this.logHandler(`Executing interval task every ${task.interval / 1000} seconds (weekdays trigger)`);
|
|
255
|
+
yield task.action();
|
|
256
|
+
this.logHandler(`Interval task every ${task.interval / 1000} seconds (weekdays trigger) completed`);
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
this.logHandler(`Error in interval task every ${task.interval / 1000} seconds (weekdays trigger): ${error}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Check matchPattern
|
|
263
|
+
if (task.matchPattern) {
|
|
264
|
+
for (const pattern of task.matchPattern) {
|
|
265
|
+
if (this.isMatchingPattern(pattern)) {
|
|
266
|
+
triggered = true;
|
|
267
|
+
try {
|
|
268
|
+
this.logHandler(`Executing interval task every ${task.interval / 1000} seconds (pattern ${pattern} trigger)`);
|
|
269
|
+
yield task.action();
|
|
270
|
+
this.logHandler(`Interval task every ${task.interval / 1000} seconds (pattern ${pattern} trigger) completed`);
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
this.logHandler(`Error in interval task every ${task.interval / 1000} seconds (pattern ${pattern} trigger): ${error}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (!triggered) {
|
|
279
|
+
this.logHandler(`Interval task every ${task.interval / 1000} seconds skipped (no matching weekdays or patterns)`);
|
|
280
|
+
}
|
|
281
|
+
}), task.interval);
|
|
282
|
+
this.intervalIds.push(intervalId);
|
|
283
|
+
this.logHandler(`Started interval task every ${task.interval / 1000} seconds with weekdays ${task.weekDays.join(',')} and patterns ${task.matchPattern ? task.matchPattern.join(',') : 'none'}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Stops all scheduled and interval tasks
|
|
288
|
+
* Called when PM2 sends a shutdown signal
|
|
289
|
+
*/
|
|
290
|
+
stop() {
|
|
291
|
+
this.cronJobs.forEach(job => job.stop());
|
|
292
|
+
this.cronJobs = [];
|
|
293
|
+
this.logHandler('All scheduled tasks stopped');
|
|
294
|
+
this.intervalIds.forEach(id => clearInterval(id));
|
|
295
|
+
this.intervalIds = [];
|
|
296
|
+
this.logHandler('All interval tasks stopped');
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Sets up integration with PM2 by handling shutdown signals
|
|
300
|
+
* PM2 sends SIGINT when stopping the process
|
|
301
|
+
*/
|
|
302
|
+
setupPM2() {
|
|
303
|
+
process.on('SIGINT', () => {
|
|
304
|
+
this.logHandler('Received SIGINT from PM2, stopping scheduler...');
|
|
305
|
+
this.stop();
|
|
306
|
+
process.exit(0);
|
|
307
|
+
});
|
|
308
|
+
process.on('message', (msg) => {
|
|
309
|
+
if (msg === 'shutdown') {
|
|
310
|
+
this.logHandler('Received shutdown message from PM2, stopping scheduler...');
|
|
311
|
+
this.stop();
|
|
312
|
+
process.exit(0);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
this.logHandler(`Scheduler started with PID ${process.pid}. Manage with PM2 commands (e.g., 'pm2 stop scheduler').`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
exports.Scheduler = Scheduler;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** A task is a function that can be executed synchronously or asynchronously */
|
|
2
|
+
export type Task = () => void | Promise<void>;
|
|
3
|
+
/** A log handler is a function that processes log messages */
|
|
4
|
+
export type LogHandler = (message: string) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Configuration options for the Scheduler
|
|
7
|
+
*/
|
|
8
|
+
export interface SchedulerConfig {
|
|
9
|
+
/** Optional custom log handler. If not provided, logs are written to daily files (e.g., "2025-03-24.log") */
|
|
10
|
+
logHandler?: LogHandler;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Definition of a scheduled task with time, matchPattern, and weekday constraints
|
|
14
|
+
*/
|
|
15
|
+
export interface ScheduledTask {
|
|
16
|
+
/** Time to run the task in "HH:MM" format (24-hour, e.g., "07:15") */
|
|
17
|
+
time: string;
|
|
18
|
+
matchPattern?: string[];
|
|
19
|
+
/** The task to execute */
|
|
20
|
+
action: Task;
|
|
21
|
+
/** Array of weekdays to run on (0-6 for Sun-Sat, or '*' for all days) */
|
|
22
|
+
weekDays: Array<number | '*'>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Definition of an interval task with time, matchPattern, and weekday constraints
|
|
26
|
+
*/
|
|
27
|
+
export interface IntervalTask {
|
|
28
|
+
interval: number;
|
|
29
|
+
matchPattern?: string[];
|
|
30
|
+
/** The task to execute */
|
|
31
|
+
action: Task;
|
|
32
|
+
/** Array of weekdays to run on (0-6 for Sun-Sat, or '*' for all days) */
|
|
33
|
+
weekDays: Array<number | '*'>;
|
|
34
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@namnguyen.repl.it/nn-scheduler",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A scheduler module for NodeJS",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepublishOnly": "npm run build",
|
|
10
|
+
"test": "jest"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [],
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"node-cron": "^3.0.3"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@babel/core": "^7.26.10",
|
|
18
|
+
"@babel/preset-env": "^7.26.9",
|
|
19
|
+
"@babel/preset-typescript": "^7.26.0",
|
|
20
|
+
"@types/jest": "^29.5.14",
|
|
21
|
+
"@types/node": "^20.17.30",
|
|
22
|
+
"@types/node-cron": "^3.0.11",
|
|
23
|
+
"babel-jest": "^29.7.0",
|
|
24
|
+
"jest": "^29.7.0",
|
|
25
|
+
"ts-jest": "^29.2.6",
|
|
26
|
+
"ts-node": "^10.9.2",
|
|
27
|
+
"typescript": "^5.8.2"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist/**/*"
|
|
31
|
+
],
|
|
32
|
+
"author": "namnguyen.repl.it@gmail.com",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/NamReplIT/nn-scheduler/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/NamReplIT/nn-scheduler#readme",
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
}
|
|
41
|
+
}
|