jm2 0.1.12 → 0.1.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jm2",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Job Manager 2 - A simple yet powerful job scheduler combining cron and at functionality",
5
5
  "type": "module",
6
6
  "main": "src/cli/index.js",
@@ -133,7 +133,21 @@ export class Scheduler {
133
133
  // Calculate next run time for active jobs
134
134
  let nextRun = null;
135
135
  if (job.status === JobStatus.ACTIVE) {
136
- nextRun = this.calculateNextRun(job, new Date());
136
+ // Check if there's a stored nextRun that is in the past (missed execution)
137
+ const storedNextRun = job.nextRun || job.nextRunISO;
138
+ if (storedNextRun) {
139
+ const storedDate = new Date(storedNextRun);
140
+ const now = new Date();
141
+ // If stored nextRun is in the past and it's a cron job, keep it so tick() will detect it as overdue
142
+ if (storedDate < now && job.type === JobType.CRON) {
143
+ nextRun = storedDate;
144
+ }
145
+ }
146
+
147
+ // If nextRun wasn't set from stored value, calculate a new one
148
+ if (!nextRun) {
149
+ nextRun = this.calculateNextRun(job, new Date());
150
+ }
137
151
  }
138
152
 
139
153
  this.jobs.set(job.id, {
@@ -193,6 +207,34 @@ export class Scheduler {
193
207
  return nextRun;
194
208
  }
195
209
 
210
+ /**
211
+ * Find periodic jobs that are overdue (nextRun is in the past)
212
+ * This handles system sleep/wake scenarios where jobs should have run while asleep
213
+ * @param {Date} now - Current time
214
+ * @returns {Array} Array of overdue job IDs
215
+ */
216
+ findOverduePeriodicJobs(now) {
217
+ const overdueJobs = [];
218
+
219
+ for (const [id, job] of this.jobs) {
220
+ if (
221
+ job.status === JobStatus.ACTIVE &&
222
+ job.type === JobType.CRON &&
223
+ job.cron &&
224
+ job.nextRun
225
+ ) {
226
+ const timeSinceNextRun = now.getTime() - job.nextRun.getTime();
227
+ const isOverdue = timeSinceNextRun > this.checkIntervalMs * 2;
228
+
229
+ if (isOverdue) {
230
+ overdueJobs.push(id);
231
+ }
232
+ }
233
+ }
234
+
235
+ return overdueJobs;
236
+ }
237
+
196
238
  /**
197
239
  * Recalculate next run times for periodic jobs that have drifted into the past
198
240
  * This handles system sleep/wake scenarios where nextRun becomes stale
@@ -321,8 +363,27 @@ export class Scheduler {
321
363
 
322
364
  const nowDate = new Date();
323
365
 
324
- // Recalculate next run for periodic jobs that have drifted into the past
325
- // This handles system sleep/wake scenarios
366
+ // Check for overdue jobs BEFORE recalculating next run times
367
+ // This ensures jobs that should have run while system was asleep get executed
368
+ const overdueJobIds = this.findOverduePeriodicJobs(nowDate);
369
+ if (overdueJobIds.length > 0) {
370
+ this.logger.info(`Found ${overdueJobIds.length} overdue job(s) to execute after system wake`);
371
+ for (const jobId of overdueJobIds) {
372
+ const job = this.jobs.get(jobId);
373
+ if (job) {
374
+ this.logger.info(`Executing overdue job ${jobId} (${job.name || 'unnamed'})`);
375
+ // Execute immediately without waiting for the normal due jobs check
376
+ this.executeJob(job);
377
+ // Calculate next run from the original scheduled time to maintain cadence
378
+ const originalNextRun = job.nextRun ? new Date(job.nextRun) : new Date();
379
+ const nextRun = this.calculateNextRunAfterExecution(job, originalNextRun);
380
+ this.updateJobNextRun(jobId, nextRun);
381
+ }
382
+ }
383
+ }
384
+
385
+ // Recalculate next run for any remaining periodic jobs that have drifted into the past
386
+ // This handles edge cases and ensures nextRun times are in the future
326
387
  this.recalculateStalePeriodicJobs(nowDate);
327
388
 
328
389
  const dueJobs = this.getDueJobs();
@@ -535,6 +596,14 @@ export class Scheduler {
535
596
  ...job,
536
597
  nextRun: job.nextRun ? job.nextRun.toISOString() : null,
537
598
  }));
599
+
600
+ // Safety check: don't persist empty jobs array if we had jobs before
601
+ // This prevents accidental data loss during startup/shutdown
602
+ if (jobsArray.length === 0 && this.jobs.size > 0) {
603
+ this.logger.error('BUG: Attempted to save empty jobs array but jobs Map is not empty!');
604
+ return;
605
+ }
606
+
538
607
  saveJobs(jobsArray);
539
608
  }
540
609