@stonyx/cron 0.2.1-alpha.10 → 0.2.1-alpha.11

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
@@ -35,6 +35,8 @@ When a job is executed, its next trigger time is updated, and it is re-inserted
35
35
  | `register` | `key: string, callback: Function, interval: number, runOnInit?: boolean` | Register a new job with a given interval in seconds. If `runOnInit` is true, the job runs immediately upon registration. |
36
36
  | `unregister` | `key: string` | Remove a previously registered job. |
37
37
 
38
+ > Callbacks are invoked fire-and-forget: `Cron` never waits for one to settle. Two *different* jobs that fall due on the same tick may therefore overlap, and a job that is still running when it next falls due is skipped for that tick (a warning is logged). Both synchronous throws and asynchronous rejections are caught and logged; neither can stop the scheduler.
39
+
38
40
  > `MinHeap` is also exported as a public subpath (`@stonyx/cron/min-heap`) and can be imported directly for advanced usage.
39
41
 
40
42
  ## Configuration
package/dist/main.d.ts CHANGED
@@ -9,10 +9,21 @@ export default class Cron {
9
9
  jobs: Record<string, CronJob>;
10
10
  heap: MinHeap<CronJob>;
11
11
  timer: ReturnType<typeof setTimeout> | null;
12
+ /** Keys whose previous invocation has not settled yet. */
13
+ inFlight: Set<string>;
12
14
  constructor();
13
15
  init(): Promise<void>;
14
16
  scheduleNextRun(): void;
15
17
  runDueJobs(): Promise<void>;
18
+ /**
19
+ * The one safe way this class invokes a consumer callback.
20
+ *
21
+ * Never blocks the caller, catches synchronous throws and asynchronous
22
+ * rejections alike, and skips the invocation entirely when the job's previous
23
+ * invocation has not settled yet (fire-and-forget would otherwise let a slow
24
+ * job stack invocations on itself).
25
+ */
26
+ safeInvoke(job: CronJob, runOnInit?: boolean): void;
16
27
  register(key: string, callback: () => void | Promise<void>, interval: string, runOnInit?: boolean): void;
17
28
  unregister(key: string): void;
18
29
  setNextTrigger(job: CronJob): void;
package/dist/main.js CHANGED
@@ -22,6 +22,8 @@ export default class Cron {
22
22
  jobs = {};
23
23
  heap = new MinHeap();
24
24
  timer = null;
25
+ /** Keys whose previous invocation has not settled yet. */
26
+ inFlight = new Set();
25
27
  constructor() {
26
28
  if (Cron.instance)
27
29
  return Cron.instance;
@@ -55,17 +57,47 @@ export default class Cron {
55
57
  const job = heap.pop();
56
58
  if (config.debug)
57
59
  this.log('job has been triggered', job.key);
58
- try {
59
- await job.callback();
60
- }
61
- catch (err) {
62
- log.error(`Cron job "${job.key}" failed:`, err);
63
- }
60
+ // Reschedule *before* invoking. The callback's result is not used by this
61
+ // class (`runDueJobs` returns void), so awaiting it bought nothing and
62
+ // cost the scheduler: a callback that never settled left the job absent
63
+ // from the heap and stopped the timer from ever re-arming.
64
64
  this.setNextTrigger(job);
65
65
  heap.push(job);
66
+ this.safeInvoke(job);
66
67
  }
67
68
  this.scheduleNextRun();
68
69
  }
70
+ /**
71
+ * The one safe way this class invokes a consumer callback.
72
+ *
73
+ * Never blocks the caller, catches synchronous throws and asynchronous
74
+ * rejections alike, and skips the invocation entirely when the job's previous
75
+ * invocation has not settled yet (fire-and-forget would otherwise let a slow
76
+ * job stack invocations on itself).
77
+ */
78
+ safeInvoke(job, runOnInit = false) {
79
+ const { key } = job;
80
+ const context = runOnInit ? 'failed on init:' : 'failed:';
81
+ if (this.inFlight.has(key)) {
82
+ log.warn(`Cron job "${key}" is still running; skipping this tick`);
83
+ return;
84
+ }
85
+ this.inFlight.add(key);
86
+ try {
87
+ const result = job.callback();
88
+ if (result && typeof result.then === 'function') {
89
+ Promise.resolve(result)
90
+ .catch(err => log.error(`Cron job "${key}" ${context}`, err))
91
+ .finally(() => this.inFlight.delete(key));
92
+ return;
93
+ }
94
+ this.inFlight.delete(key);
95
+ }
96
+ catch (err) {
97
+ this.inFlight.delete(key);
98
+ log.error(`Cron job "${key}" ${context}`, err);
99
+ }
100
+ }
69
101
  register(key, callback, interval, runOnInit = false) {
70
102
  const job = { callback, interval, key, nextTrigger: 0 };
71
103
  this.jobs[key] = job;
@@ -74,14 +106,8 @@ export default class Cron {
74
106
  if (config.debug) {
75
107
  this.log(`job has been registered with interval: ${interval}`, key);
76
108
  }
77
- if (runOnInit) {
78
- try {
79
- callback();
80
- }
81
- catch (err) {
82
- log.error(`Cron job "${key}" failed on init:`, err);
83
- }
84
- }
109
+ if (runOnInit)
110
+ this.safeInvoke(job, true);
85
111
  this.scheduleNextRun();
86
112
  }
87
113
  unregister(key) {
@@ -91,6 +117,7 @@ export default class Cron {
91
117
  return;
92
118
  delete jobs[key];
93
119
  heap.remove(job);
120
+ this.inFlight.delete(key);
94
121
  if (config.debug)
95
122
  this.log('job has been unregistered', key);
96
123
  this.scheduleNextRun();
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "keywords": [
4
4
  "stonyx-module"
5
5
  ],
6
- "version": "0.2.1-alpha.10",
6
+ "version": "0.2.1-alpha.11",
7
7
  "description": "Cron/job scheduler for Stonyx framework",
8
8
  "main": "dist/main.js",
9
9
  "types": "dist/main.d.ts",
@@ -69,7 +69,7 @@
69
69
  },
70
70
  "homepage": "https://github.com/abofs/stonyx-cron#readme",
71
71
  "devDependencies": {
72
- "@stonyx/utils": "0.2.3-beta.23",
72
+ "@stonyx/utils": "0.2.3-beta.26",
73
73
  "@types/node": "^25.5.2",
74
74
  "@types/qunit": "^2.19.13",
75
75
  "@types/sinon": "^21.0.1",
@@ -79,7 +79,7 @@
79
79
  "typescript": "^5.8.3"
80
80
  },
81
81
  "dependencies": {
82
- "stonyx": "0.2.3-beta.63"
82
+ "stonyx": "0.2.3-beta.76"
83
83
  },
84
84
  "scripts": {
85
85
  "build": "tsc",