@veloxts/scheduler 0.7.0 → 0.7.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/dist/manager.js +21 -27
- package/package.json +3 -3
package/dist/manager.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Core scheduler that manages and executes scheduled tasks.
|
|
5
5
|
*/
|
|
6
|
+
import { createLogger } from '@veloxts/core';
|
|
6
7
|
import { CronJob } from 'cron';
|
|
7
8
|
/**
|
|
8
9
|
* Maximum execution history entries per task.
|
|
@@ -38,7 +39,7 @@ const DEFAULT_MAX_LOCK_MINUTES = 1440;
|
|
|
38
39
|
* ```
|
|
39
40
|
*/
|
|
40
41
|
export function createScheduler(tasks, options = {}) {
|
|
41
|
-
const { timezone: defaultTimezone = 'UTC'
|
|
42
|
+
const { timezone: defaultTimezone = 'UTC' } = options;
|
|
42
43
|
// Job states
|
|
43
44
|
const jobs = new Map();
|
|
44
45
|
// Execution history - use per-task arrays for O(1) trimming instead of O(n²)
|
|
@@ -56,14 +57,7 @@ export function createScheduler(tasks, options = {}) {
|
|
|
56
57
|
let running = false;
|
|
57
58
|
// Track running task promises for graceful shutdown
|
|
58
59
|
const runningTasks = new Map();
|
|
59
|
-
|
|
60
|
-
* Log debug message.
|
|
61
|
-
*/
|
|
62
|
-
function log(message) {
|
|
63
|
-
if (debug) {
|
|
64
|
-
console.log(`[scheduler] ${message}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
60
|
+
const log = createLogger('scheduler');
|
|
67
61
|
/**
|
|
68
62
|
* Add execution to history.
|
|
69
63
|
* O(1) amortized - uses per-task arrays with simple shift for trimming.
|
|
@@ -107,7 +101,7 @@ export function createScheduler(tasks, options = {}) {
|
|
|
107
101
|
const lockExpired = jobState.runningStartedAt && Date.now() - jobState.runningStartedAt.getTime() > maxLockMs;
|
|
108
102
|
if (lockExpired) {
|
|
109
103
|
// Lock expired - force release and allow new execution
|
|
110
|
-
log(`Lock expired for ${task.name} after ${task.maxLockMinutes ?? DEFAULT_MAX_LOCK_MINUTES} minutes, forcing new execution`);
|
|
104
|
+
log.debug(`Lock expired for ${task.name} after ${task.maxLockMinutes ?? DEFAULT_MAX_LOCK_MINUTES} minutes, forcing new execution`);
|
|
111
105
|
jobState.isRunning = false;
|
|
112
106
|
jobState.runningStartedAt = undefined;
|
|
113
107
|
}
|
|
@@ -117,7 +111,7 @@ export function createScheduler(tasks, options = {}) {
|
|
|
117
111
|
execution.status = 'skipped';
|
|
118
112
|
execution.completedAt = new Date();
|
|
119
113
|
execution.duration = 0;
|
|
120
|
-
log(`Skipping ${task.name}: ${skipReason}`);
|
|
114
|
+
log.debug(`Skipping ${task.name}: ${skipReason}`);
|
|
121
115
|
if (task.onSkip) {
|
|
122
116
|
try {
|
|
123
117
|
await task.onSkip(ctx, skipReason);
|
|
@@ -147,7 +141,7 @@ export function createScheduler(tasks, options = {}) {
|
|
|
147
141
|
execution.status = 'skipped';
|
|
148
142
|
execution.completedAt = new Date();
|
|
149
143
|
execution.duration = 0;
|
|
150
|
-
log(`Skipping ${task.name}: ${skipReason}`);
|
|
144
|
+
log.debug(`Skipping ${task.name}: ${skipReason}`);
|
|
151
145
|
if (task.onSkip) {
|
|
152
146
|
try {
|
|
153
147
|
await task.onSkip(ctx, skipReason);
|
|
@@ -173,7 +167,7 @@ export function createScheduler(tasks, options = {}) {
|
|
|
173
167
|
execution.status = 'skipped';
|
|
174
168
|
execution.completedAt = new Date();
|
|
175
169
|
execution.duration = 0;
|
|
176
|
-
log(`Skipping ${task.name}: ${skipReason}`);
|
|
170
|
+
log.debug(`Skipping ${task.name}: ${skipReason}`);
|
|
177
171
|
addToHistory(execution);
|
|
178
172
|
return execution;
|
|
179
173
|
}
|
|
@@ -181,7 +175,7 @@ export function createScheduler(tasks, options = {}) {
|
|
|
181
175
|
// Mark as running
|
|
182
176
|
jobState.isRunning = true;
|
|
183
177
|
jobState.runningStartedAt = new Date();
|
|
184
|
-
log(`Starting ${task.name}`);
|
|
178
|
+
log.debug(`Starting ${task.name}`);
|
|
185
179
|
if (options.onTaskStart) {
|
|
186
180
|
try {
|
|
187
181
|
await options.onTaskStart(task, ctx);
|
|
@@ -216,7 +210,7 @@ export function createScheduler(tasks, options = {}) {
|
|
|
216
210
|
execution.completedAt = completedAt;
|
|
217
211
|
execution.duration = duration;
|
|
218
212
|
jobState.lastRunAt = completedAt;
|
|
219
|
-
log(`Completed ${task.name} in ${duration}ms`);
|
|
213
|
+
log.debug(`Completed ${task.name} in ${duration}ms`);
|
|
220
214
|
if (task.onSuccess) {
|
|
221
215
|
try {
|
|
222
216
|
await task.onSuccess(ctx, duration);
|
|
@@ -242,7 +236,7 @@ export function createScheduler(tasks, options = {}) {
|
|
|
242
236
|
execution.completedAt = completedAt;
|
|
243
237
|
execution.duration = duration;
|
|
244
238
|
execution.error = errorObj.message;
|
|
245
|
-
log(`Failed ${task.name}: ${errorObj.message}`);
|
|
239
|
+
log.debug(`Failed ${task.name}: ${errorObj.message}`);
|
|
246
240
|
if (task.onFailure) {
|
|
247
241
|
try {
|
|
248
242
|
await task.onFailure(ctx, errorObj, duration);
|
|
@@ -273,7 +267,7 @@ export function createScheduler(tasks, options = {}) {
|
|
|
273
267
|
function initializeJobs() {
|
|
274
268
|
for (const task of tasks) {
|
|
275
269
|
if (!task.enabled) {
|
|
276
|
-
log(`Skipping disabled task: ${task.name}`);
|
|
270
|
+
log.debug(`Skipping disabled task: ${task.name}`);
|
|
277
271
|
continue;
|
|
278
272
|
}
|
|
279
273
|
const taskTimezone = task.timezone || defaultTimezone;
|
|
@@ -283,7 +277,7 @@ export function createScheduler(tasks, options = {}) {
|
|
|
283
277
|
// Track the running promise for graceful shutdown
|
|
284
278
|
const taskPromise = executeTask(jobState)
|
|
285
279
|
.catch((err) => {
|
|
286
|
-
|
|
280
|
+
log.error(`Unhandled error in ${task.name}:`, err);
|
|
287
281
|
// Return a failed execution to satisfy the type
|
|
288
282
|
return {
|
|
289
283
|
taskName: task.name,
|
|
@@ -308,7 +302,7 @@ export function createScheduler(tasks, options = {}) {
|
|
|
308
302
|
isRunning: false,
|
|
309
303
|
};
|
|
310
304
|
jobs.set(task.name, jobState);
|
|
311
|
-
log(`Registered task: ${task.name} (${task.cronExpression})`);
|
|
305
|
+
log.debug(`Registered task: ${task.name} (${task.cronExpression})`);
|
|
312
306
|
}
|
|
313
307
|
}
|
|
314
308
|
// Initialize jobs
|
|
@@ -316,21 +310,21 @@ export function createScheduler(tasks, options = {}) {
|
|
|
316
310
|
const manager = {
|
|
317
311
|
start() {
|
|
318
312
|
if (running) {
|
|
319
|
-
log('Scheduler already running');
|
|
313
|
+
log.debug('Scheduler already running');
|
|
320
314
|
return;
|
|
321
315
|
}
|
|
322
316
|
running = true;
|
|
323
|
-
log('Starting scheduler');
|
|
317
|
+
log.debug('Starting scheduler');
|
|
324
318
|
for (const jobState of jobs.values()) {
|
|
325
319
|
jobState.cronJob.start();
|
|
326
320
|
}
|
|
327
321
|
},
|
|
328
322
|
async stop() {
|
|
329
323
|
if (!running) {
|
|
330
|
-
log('Scheduler not running');
|
|
324
|
+
log.debug('Scheduler not running');
|
|
331
325
|
return;
|
|
332
326
|
}
|
|
333
|
-
log('Stopping scheduler');
|
|
327
|
+
log.debug('Stopping scheduler');
|
|
334
328
|
// Stop all cron jobs (prevents new executions)
|
|
335
329
|
for (const jobState of jobs.values()) {
|
|
336
330
|
jobState.cronJob.stop();
|
|
@@ -340,10 +334,10 @@ export function createScheduler(tasks, options = {}) {
|
|
|
340
334
|
const maxWait = 30000; // 30 seconds
|
|
341
335
|
const runningPromises = Array.from(runningTasks.values());
|
|
342
336
|
if (runningPromises.length > 0) {
|
|
343
|
-
log(`Waiting for ${runningPromises.length} running task(s) to complete...`);
|
|
337
|
+
log.debug(`Waiting for ${runningPromises.length} running task(s) to complete...`);
|
|
344
338
|
const timeoutPromise = new Promise((resolve) => {
|
|
345
339
|
setTimeout(() => {
|
|
346
|
-
log('Graceful shutdown timeout reached, forcing stop');
|
|
340
|
+
log.debug('Graceful shutdown timeout reached, forcing stop');
|
|
347
341
|
resolve();
|
|
348
342
|
}, maxWait);
|
|
349
343
|
});
|
|
@@ -352,7 +346,7 @@ export function createScheduler(tasks, options = {}) {
|
|
|
352
346
|
}
|
|
353
347
|
running = false;
|
|
354
348
|
runningTasks.clear();
|
|
355
|
-
log('Scheduler stopped');
|
|
349
|
+
log.debug('Scheduler stopped');
|
|
356
350
|
},
|
|
357
351
|
isRunning() {
|
|
358
352
|
return running;
|
|
@@ -376,7 +370,7 @@ export function createScheduler(tasks, options = {}) {
|
|
|
376
370
|
if (!jobState) {
|
|
377
371
|
throw new Error(`Task not found: ${name}`);
|
|
378
372
|
}
|
|
379
|
-
log(`Manually running task: ${name}`);
|
|
373
|
+
log.debug(`Manually running task: ${name}`);
|
|
380
374
|
return executeTask(jobState);
|
|
381
375
|
},
|
|
382
376
|
getNextRun(name) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veloxts/scheduler",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Task scheduling for VeloxTS framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"cron": "4.4.0",
|
|
22
22
|
"fastify-plugin": "5.1.0",
|
|
23
|
-
"@veloxts/core": "0.7.
|
|
23
|
+
"@veloxts/core": "0.7.1"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"fastify": "^5.7.4"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@types/node": "25.
|
|
29
|
+
"@types/node": "25.2.3",
|
|
30
30
|
"@vitest/coverage-v8": "4.0.18",
|
|
31
31
|
"fastify": "5.7.4",
|
|
32
32
|
"typescript": "5.9.3",
|