@stonyx/cron 0.2.0 → 0.2.1-beta.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.
@@ -0,0 +1,771 @@
1
+ # stonyx-cron Project Structure
2
+
3
+ ## 1. Project Overview
4
+
5
+ **stonyx-cron** is a lightweight async job scheduler for the Stonyx framework that uses a min-heap priority queue for efficient job scheduling.
6
+
7
+ **Core Purpose:**
8
+ - Schedule and execute async jobs at specified intervals
9
+ - O(log n) scheduling efficiency via min-heap priority queue
10
+ - Robust error handling that never crashes the scheduler
11
+ - Configuration-driven logging aligned with Stonyx patterns
12
+
13
+ **Technology Stack:**
14
+ - **Module System:** ES Modules (ESM)
15
+ - **Testing:** QUnit with Sinon for spies/stubs/fake timers
16
+ - **Dependencies:** Stonyx framework, @stonyx/utils/date
17
+ - **Node Version:** Specified in `.nvmrc`
18
+
19
+ ---
20
+
21
+ ## 2. Architecture & Design Decisions
22
+
23
+ ### Singleton Pattern
24
+ The `Cron` class uses a singleton pattern to ensure only one scheduler instance exists across the entire application. The constructor returns the existing instance if one has already been created.
25
+
26
+ ```javascript
27
+ constructor() {
28
+ if (Cron.instance) return Cron.instance;
29
+ Cron.instance = this;
30
+ }
31
+ ```
32
+
33
+ **Rationale:** Prevents multiple competing schedulers and ensures consistent job management.
34
+
35
+ ### Min-Heap Priority Queue
36
+ Jobs are stored in a min-heap ordered by `nextTrigger` timestamp, allowing O(log n) insertion and O(1) peek of the next job to run.
37
+
38
+ **Heap Property:** Parent nodes have earlier `nextTrigger` values than their children, so the root is always the next job to execute.
39
+
40
+ ### Async Job Execution Strategy
41
+ - Jobs are executed with `await job.callback()` to handle async operations
42
+ - Errors are caught and logged but never crash the scheduler
43
+ - After execution (success or failure), jobs are rescheduled and re-inserted into the heap
44
+ - The scheduler reschedules itself after processing all due jobs
45
+
46
+ ### Configuration-Driven Logging
47
+ Logging follows Stonyx patterns:
48
+ - Check `config.debug` before debug logs
49
+ - Check `config.cron?.log` before cron-specific logs
50
+ - Use `log.cron()` for cron messages, `log.error()` for errors
51
+
52
+ ---
53
+
54
+ ## 3. File Structure
55
+
56
+ ```
57
+ stonyx-cron/
58
+ ├── .claude/
59
+ │ └── project-structure.md - This document
60
+ ├── .github/
61
+ │ └── workflows/ - CI/CD configuration
62
+ ├── config/
63
+ │ └── environment.js - Cron module configuration
64
+ ├── src/
65
+ │ ├── main.js - Cron class (singleton scheduler)
66
+ │ └── min-heap.js - MinHeap priority queue implementation
67
+ ├── test/
68
+ │ └── unit/
69
+ │ ├── cron-test.js - Cron class unit tests
70
+ │ └── min-heap-test.js - MinHeap unit tests
71
+ ├── package.json - Package metadata and exports
72
+ ├── stonyx-bootstrap.cjs - QUnit test bootstrap
73
+ ├── README.md - Project documentation
74
+ ├── LICENSE.md - Apache 2.0 license
75
+ ├── .nvmrc - Node version specification
76
+ └── .gitignore - Git ignore rules
77
+ ```
78
+
79
+ ---
80
+
81
+ ## 4. Core Components Deep Dive
82
+
83
+ ### Cron Class (`stonyx-cron/src/main.js`)
84
+
85
+ **Properties:**
86
+ ```javascript
87
+ jobs = {}; // Object mapping job keys to job objects
88
+ heap = new MinHeap(); // Priority queue of jobs
89
+ timer = null; // setTimeout handle for next scheduled run
90
+ ```
91
+
92
+ **Job Object Schema:**
93
+ ```javascript
94
+ {
95
+ key: string, // Unique identifier for the job
96
+ callback: Function, // Async function to execute
97
+ interval: number, // Interval in seconds
98
+ nextTrigger: number // Unix timestamp in seconds when job should run
99
+ }
100
+ ```
101
+
102
+ **Public Methods:**
103
+
104
+ **`register(key, callback, interval, runOnInit=false)`**
105
+ - Registers a new recurring job
106
+ - `key` (string): Unique job identifier
107
+ - `callback` (Function): Async function to execute on each trigger
108
+ - `interval` (number): Time in seconds between executions
109
+ - `runOnInit` (boolean): Whether to run callback immediately
110
+
111
+ **`unregister(key)`**
112
+ - Removes a job from the scheduler
113
+ - Deletes from `jobs` object and removes from heap
114
+ - Reschedules next run after removal
115
+
116
+ **`scheduleNextRun()`**
117
+ - Clears existing timer
118
+ - Peeks at next job in heap
119
+ - Calculates delay: `(nextTrigger - now) * 1000` (converts seconds to milliseconds)
120
+ - Sets setTimeout for next job execution
121
+
122
+ **`runDueJobs()`**
123
+ - Processes all jobs with `nextTrigger <= now`
124
+ - Executes job callbacks with error handling
125
+ - Reschedules each job after execution
126
+ - Calls `scheduleNextRun()` when done
127
+
128
+ **`setNextTrigger(job)`**
129
+ - Updates job's `nextTrigger` to `now + interval`
130
+ - Uses `getTimestamp()` which returns **seconds**, not milliseconds
131
+
132
+ **`log(text, key=null)`**
133
+ - Conditional logging based on `config.cron?.log`
134
+ - Formats messages as `Cron::${key} - ${text}:` or `Cron - ${text}:`
135
+
136
+ ### MinHeap Class (`stonyx-cron/src/min-heap.js`)
137
+
138
+ **Properties:**
139
+ ```javascript
140
+ items = []; // Array backing the heap
141
+ ```
142
+
143
+ **Public Methods:**
144
+
145
+ **`push(job)`**
146
+ - Adds job to end of array
147
+ - Bubbles up to maintain heap property
148
+
149
+ **`pop()`**
150
+ - Removes and returns root (minimum `nextTrigger`)
151
+ - Replaces root with last item
152
+ - Bubbles down to restore heap property
153
+
154
+ **`peek()`**
155
+ - Returns root without removing it
156
+ - O(1) access to next job to run
157
+
158
+ **`remove(job)`**
159
+ - Finds job by reference equality (`indexOf`)
160
+ - Replaces with last item
161
+ - Bubbles both up and down to restore heap property
162
+
163
+ **`isEmpty()`**
164
+ - Returns `true` if `items.length === 0`
165
+
166
+ **Internal Methods:**
167
+
168
+ **`bubbleUp()`**
169
+ - Moves last item up tree until heap property is satisfied
170
+ - Compares `nextTrigger` values with parent
171
+
172
+ **`bubbleDown()`**
173
+ - Moves root down tree, swapping with smallest child
174
+ - Maintains min-heap property
175
+
176
+ ---
177
+
178
+ ## 5. Dependencies & Integration
179
+
180
+ ### Stonyx Framework Integration
181
+ ```javascript
182
+ import config from 'stonyx/config'; // Configuration system
183
+ import log from 'stonyx/log'; // Logging system
184
+ import { setupIntegrationTests } from "stonyx/test-helpers"; // Test utilities
185
+ ```
186
+
187
+ ### External Dependencies
188
+ ```javascript
189
+ import { getTimestamp } from "@stonyx/utils/date"; // Time utilities
190
+ import QUnit from 'qunit'; // Test framework
191
+ import sinon from 'sinon'; // Test spies/stubs/fake timers
192
+ ```
193
+
194
+ ### Critical Time Handling Detail
195
+ **`getTimestamp()` returns Unix timestamps in SECONDS, not milliseconds.**
196
+
197
+ This affects:
198
+ - Job `interval` values (specified in seconds)
199
+ - Job `nextTrigger` values (stored in seconds)
200
+ - Delay calculation in `scheduleNextRun()`: must multiply by 1000 for `setTimeout`
201
+
202
+ ```javascript
203
+ // CORRECT: Convert seconds to milliseconds for setTimeout
204
+ const delay = Math.max(0, nextJob.nextTrigger - getTimestamp()) * 1000;
205
+ this.timer = setTimeout(() => this.runDueJobs(), delay);
206
+ ```
207
+
208
+ ---
209
+
210
+ ## 6. Code Patterns & Conventions
211
+
212
+ ### Module System
213
+ All modules use ES Module syntax with default exports:
214
+ ```javascript
215
+ export default class Cron { ... }
216
+ export default class MinHeap { ... }
217
+ export default { log: true, logColor: '#888' }; // config
218
+ ```
219
+
220
+ Imports use full package paths:
221
+ ```javascript
222
+ import Cron from '@stonyx/cron';
223
+ import MinHeap from '@stonyx/cron/min-heap';
224
+ ```
225
+
226
+ ### Logging Patterns
227
+ **Always check config before logging:**
228
+ ```javascript
229
+ if (config.debug) this.log('job has been triggered', job.key);
230
+ if (config.cron?.log) log.cron(`${tag} - ${text}:`);
231
+ ```
232
+
233
+ **Use appropriate log methods:**
234
+ - `log.cron()` for informational cron messages
235
+ - `log.error()` for error conditions
236
+
237
+ ### Error Handling
238
+ **Never let errors crash the scheduler:**
239
+ ```javascript
240
+ try {
241
+ await job.callback();
242
+ } catch (err) {
243
+ log.error(`Cron job "${job.key}" failed:`, err);
244
+ }
245
+ // Always reschedule job, even after error
246
+ this.setNextTrigger(job);
247
+ heap.push(job);
248
+ ```
249
+
250
+ ### Time Handling
251
+ **Always use `getTimestamp()` for current time:**
252
+ ```javascript
253
+ // CORRECT
254
+ const now = getTimestamp();
255
+ job.nextTrigger = getTimestamp() + parseInt(job.interval, 10);
256
+
257
+ // WRONG - don't use Date.now() or other time sources
258
+ const now = Date.now(); // WRONG: milliseconds instead of seconds
259
+ ```
260
+
261
+ ---
262
+
263
+ ## 7. Testing Guidelines
264
+
265
+ ### Test Structure
266
+ Tests are located in `stonyx-cron/test/unit/` and use QUnit modules:
267
+
268
+ ```javascript
269
+ import QUnit from 'qunit';
270
+ import sinon from 'sinon';
271
+ import { setupIntegrationTests } from "stonyx/test-helpers";
272
+
273
+ const { module, test } = QUnit;
274
+
275
+ module('[Unit] Cron', function (hooks) {
276
+ setupIntegrationTests(hooks);
277
+
278
+ let cron, clock;
279
+
280
+ hooks.beforeEach(function () {
281
+ clock = sinon.useFakeTimers({ shouldAdvanceTime: false });
282
+ cron = new Cron();
283
+ });
284
+
285
+ hooks.afterEach(function () {
286
+ sinon.restore();
287
+ });
288
+
289
+ test('test description', async function (assert) {
290
+ // Test implementation
291
+ });
292
+ });
293
+ ```
294
+
295
+ ### Fake Timers Pattern (CRITICAL)
296
+ **Always use fake timers for time-based tests:**
297
+
298
+ ```javascript
299
+ // Setup in beforeEach
300
+ clock = sinon.useFakeTimers({ shouldAdvanceTime: false });
301
+
302
+ // Advance time synchronously
303
+ clock.tick(5000); // Advance 5 seconds
304
+
305
+ // For async operations, use tickAsync
306
+ clock.tick(5000);
307
+ await clock.tickAsync(0); // Process async callbacks
308
+
309
+ // Cleanup in afterEach
310
+ sinon.restore();
311
+ ```
312
+
313
+ **Why `shouldAdvanceTime: false`?**
314
+ Prevents real time from interfering with fake time, ensuring deterministic tests.
315
+
316
+ ### Spies & Stubs Patterns
317
+ ```javascript
318
+ // Spy on function calls
319
+ const cb = sinon.spy();
320
+ cron.register('job1', cb, 5);
321
+ assert.ok(cb.calledOnce, 'Callback executed once');
322
+
323
+ // Stub methods
324
+ const stub = sinon.stub().rejects(new Error('boom'));
325
+ cron.register('jobErr', stub, 1);
326
+
327
+ // Spy on existing methods
328
+ const logSpy = sinon.spy(log, 'cron');
329
+ cron.log('test message');
330
+ assert.ok(logSpy.calledOnce, 'Log called');
331
+ ```
332
+
333
+ ### Test Coverage Expectations
334
+ Tests should cover:
335
+ - Job registration and execution
336
+ - Rescheduling behavior
337
+ - Unregistration
338
+ - Error handling
339
+ - Configuration-driven logging
340
+ - Edge cases (empty heap, multiple jobs, etc.)
341
+
342
+ ### Running Tests
343
+ ```bash
344
+ npm test # Runs: qunit --require ./stonyx-bootstrap.cjs
345
+ ```
346
+
347
+ ---
348
+
349
+ ## 8. Extension Points
350
+
351
+ This section provides specific guidance for common feature additions.
352
+
353
+ ### Adding Job Metadata
354
+
355
+ **Where:** `stonyx-cron/src/main.js`
356
+
357
+ **Pattern:**
358
+ ```javascript
359
+ // In register method, extend job object:
360
+ register(key, callback, interval, runOnInit=false, metadata={}) {
361
+ const job = {
362
+ callback,
363
+ interval,
364
+ key,
365
+ metadata // Add metadata field
366
+ };
367
+ this.jobs[key] = job;
368
+ // ... rest of method
369
+ }
370
+
371
+ // Access metadata in runDueJobs or other methods:
372
+ const { metadata } = job;
373
+ if (metadata.priority === 'high') {
374
+ // Handle high priority jobs differently
375
+ }
376
+ ```
377
+
378
+ **Tests to add:** Verify metadata is stored and accessible in job object.
379
+
380
+ ### Adding Priority Levels
381
+
382
+ **Where:**
383
+ - `stonyx-cron/src/main.js` (Cron class)
384
+ - `stonyx-cron/src/min-heap.js` (MinHeap comparison)
385
+
386
+ **Pattern:**
387
+ ```javascript
388
+ // Option 1: Secondary sort on priority
389
+ // In min-heap.js bubbleUp/bubbleDown, change comparison:
390
+ if (this.items[idx].nextTrigger < this.items[parentIdx].nextTrigger ||
391
+ (this.items[idx].nextTrigger === this.items[parentIdx].nextTrigger &&
392
+ this.items[idx].priority > this.items[parentIdx].priority)) {
393
+ // Swap
394
+ }
395
+
396
+ // Option 2: Offset nextTrigger by priority
397
+ // In main.js setNextTrigger:
398
+ setNextTrigger(job) {
399
+ const priorityOffset = job.priority || 0;
400
+ job.nextTrigger = getTimestamp() + parseInt(job.interval, 10) - priorityOffset;
401
+ }
402
+ ```
403
+
404
+ **Considerations:**
405
+ - Option 1 maintains exact timing but adds complexity to heap
406
+ - Option 2 is simpler but slightly alters execution timing
407
+ - Add `priority` field to job object schema
408
+
409
+ **Tests to add:** Jobs with higher priority run before lower priority when due at same time.
410
+
411
+ ### Adding Job Statistics
412
+
413
+ **Where:** `stonyx-cron/src/main.js`
414
+
415
+ **Pattern:**
416
+ ```javascript
417
+ // Extend job object with stats:
418
+ register(key, callback, interval, runOnInit=false) {
419
+ const job = {
420
+ callback,
421
+ interval,
422
+ key,
423
+ stats: {
424
+ runCount: 0,
425
+ lastRun: null,
426
+ lastError: null,
427
+ errorCount: 0
428
+ }
429
+ };
430
+ // ...
431
+ }
432
+
433
+ // Update stats in runDueJobs:
434
+ async runDueJobs() {
435
+ // ...
436
+ try {
437
+ await job.callback();
438
+ job.stats.runCount++;
439
+ job.stats.lastRun = getTimestamp();
440
+ } catch (err) {
441
+ job.stats.errorCount++;
442
+ job.stats.lastError = err.message;
443
+ log.error(`Cron job "${job.key}" failed:`, err);
444
+ }
445
+ // ...
446
+ }
447
+
448
+ // Add method to retrieve stats:
449
+ getJobStats(key) {
450
+ return this.jobs[key]?.stats;
451
+ }
452
+ ```
453
+
454
+ **Tests to add:** Verify stats increment correctly on success/error.
455
+
456
+ ### Adding Persistence
457
+
458
+ **Where:** `stonyx-cron/src/main.js`
459
+
460
+ **Pattern:**
461
+ ```javascript
462
+ // Add save/load methods:
463
+ async saveJobs() {
464
+ const jobData = Object.values(this.jobs).map(job => ({
465
+ key: job.key,
466
+ interval: job.interval,
467
+ // Don't save callback - must be re-registered
468
+ }));
469
+ // Write to file/database
470
+ }
471
+
472
+ async loadJobs(callbacks) {
473
+ const jobData = // Read from file/database
474
+ jobData.forEach(({ key, interval }) => {
475
+ if (callbacks[key]) {
476
+ this.register(key, callbacks[key], interval);
477
+ }
478
+ });
479
+ }
480
+ ```
481
+
482
+ **Considerations:**
483
+ - Cannot serialize callbacks - must be re-registered on load
484
+ - Save job metadata, intervals, and keys
485
+ - Consider saving `nextTrigger` to maintain schedule across restarts
486
+
487
+ **Tests to add:** Verify jobs can be saved and restored with same intervals.
488
+
489
+ ### Adding One-Time Jobs
490
+
491
+ **Where:** `stonyx-cron/src/main.js`
492
+
493
+ **Pattern:**
494
+ ```javascript
495
+ // Add oneTime flag to job schema:
496
+ register(key, callback, interval, runOnInit=false, oneTime=false) {
497
+ const job = { callback, interval, key, oneTime };
498
+ // ...
499
+ }
500
+
501
+ // Modify runDueJobs to unregister one-time jobs:
502
+ async runDueJobs() {
503
+ const now = getTimestamp();
504
+ const { heap } = this;
505
+
506
+ while (!heap.isEmpty() && heap.peek().nextTrigger <= now) {
507
+ const job = heap.pop();
508
+
509
+ try {
510
+ await job.callback();
511
+ } catch (err) {
512
+ log.error(`Cron job "${job.key}" failed:`, err);
513
+ }
514
+
515
+ if (job.oneTime) {
516
+ delete this.jobs[job.key]; // Don't reschedule
517
+ if (config.debug) this.log('one-time job completed', job.key);
518
+ } else {
519
+ this.setNextTrigger(job);
520
+ heap.push(job);
521
+ }
522
+ }
523
+
524
+ this.scheduleNextRun();
525
+ }
526
+ ```
527
+
528
+ **Tests to add:** One-time jobs execute once and are not rescheduled.
529
+
530
+ ---
531
+
532
+ ## 9. Configuration Reference
533
+
534
+ ### Module Configuration
535
+ **File:** `stonyx-cron/config/environment.js`
536
+
537
+ ```javascript
538
+ const { CRON_LOG } = process;
539
+
540
+ export default {
541
+ log: CRON_LOG ?? true, // Enable/disable cron logging
542
+ logColor: '#888', // Color for cron logs
543
+ }
544
+ ```
545
+
546
+ **Environment Variable:**
547
+ - `CRON_LOG`: Set to `false` to disable cron logging
548
+
549
+ ### Using Configuration in Code
550
+ ```javascript
551
+ import config from 'stonyx/config';
552
+
553
+ // Access cron config
554
+ if (config.cron?.log) {
555
+ log.cron('message');
556
+ }
557
+
558
+ // Access debug flag (from main Stonyx config)
559
+ if (config.debug) {
560
+ this.log('debug message');
561
+ }
562
+ ```
563
+
564
+ ---
565
+
566
+ ## 10. Package Exports
567
+
568
+ **File:** `stonyx-cron/package.json`
569
+
570
+ ```json
571
+ {
572
+ "exports": {
573
+ ".": "./src/main.js",
574
+ "./min-heap": "./src/min-heap.js"
575
+ }
576
+ }
577
+ ```
578
+
579
+ **Usage:**
580
+ ```javascript
581
+ // Import Cron class (default export)
582
+ import Cron from '@stonyx/cron';
583
+
584
+ // Import MinHeap class (for advanced usage)
585
+ import MinHeap from '@stonyx/cron/min-heap';
586
+ ```
587
+
588
+ ---
589
+
590
+ ## 11. Development Workflow
591
+
592
+ ### Local Development
593
+ ```bash
594
+ # Install dependencies
595
+ npm install
596
+
597
+ # Run tests
598
+ npm test
599
+
600
+ # Run tests with specific Node version
601
+ nvm use
602
+ npm test
603
+ ```
604
+
605
+ ### Test-Driven Development
606
+ 1. Write failing test in `stonyx-cron/test/unit/`
607
+ 2. Implement feature in `stonyx-cron/src/`
608
+ 3. Run `npm test` to verify
609
+ 4. Refactor if needed
610
+
611
+ ### CI/CD
612
+ - GitHub Actions workflows in `.github/workflows/`
613
+ - Runs tests on push/PR
614
+ - Publishes to npm on version tags
615
+
616
+ ### Publishing
617
+ ```bash
618
+ # Bump version
619
+ npm version patch|minor|major
620
+
621
+ # Publish to npm (if you have access)
622
+ npm publish
623
+ ```
624
+
625
+ ---
626
+
627
+ ## 12. Common Pitfalls & Gotchas
628
+
629
+ ### Time Units Confusion
630
+ **Pitfall:** Mixing seconds and milliseconds
631
+
632
+ **Correct:**
633
+ ```javascript
634
+ // getTimestamp() returns SECONDS
635
+ const now = getTimestamp();
636
+ job.interval = 5; // 5 seconds
637
+ job.nextTrigger = now + 5; // 5 seconds from now
638
+
639
+ // setTimeout expects MILLISECONDS
640
+ const delay = (job.nextTrigger - now) * 1000;
641
+ setTimeout(callback, delay);
642
+ ```
643
+
644
+ **Wrong:**
645
+ ```javascript
646
+ // DON'T DO THIS
647
+ const delay = job.nextTrigger - getTimestamp(); // Missing * 1000
648
+ setTimeout(callback, delay); // Will run almost immediately!
649
+ ```
650
+
651
+ ### Singleton Behavior
652
+ **Pitfall:** Creating multiple Cron instances unexpectedly
653
+
654
+ **Behavior:**
655
+ ```javascript
656
+ const cron1 = new Cron();
657
+ const cron2 = new Cron();
658
+ console.log(cron1 === cron2); // true - same instance!
659
+ ```
660
+
661
+ **Implication:** Registering jobs on any instance affects the same scheduler.
662
+
663
+ ### Job Callback Async Handling
664
+ **Pitfall:** Not awaiting async callbacks
665
+
666
+ **Correct:**
667
+ ```javascript
668
+ cron.register('job', async () => {
669
+ await someAsyncOperation();
670
+ }, 10);
671
+ ```
672
+
673
+ The scheduler awaits the callback, so errors are caught properly.
674
+
675
+ **Wrong:**
676
+ ```javascript
677
+ cron.register('job', () => {
678
+ someAsyncOperation(); // Not awaited - errors won't be caught!
679
+ }, 10);
680
+ ```
681
+
682
+ ### Heap Reference Equality
683
+ **Pitfall:** Modifying job objects outside the scheduler
684
+
685
+ **Issue:**
686
+ ```javascript
687
+ const job = cron.jobs['myJob'];
688
+ job.interval = 20; // This doesn't update the heap!
689
+ ```
690
+
691
+ **Solution:** Always use `unregister` then `register` to update job properties.
692
+
693
+ ### Test Isolation
694
+ **Pitfall:** Tests interfering with each other due to singleton
695
+
696
+ **Solution:**
697
+ ```javascript
698
+ hooks.beforeEach(function () {
699
+ clock = sinon.useFakeTimers({ shouldAdvanceTime: false });
700
+ cron = new Cron(); // Gets singleton
701
+
702
+ // Clear previous test's jobs
703
+ Object.keys(cron.jobs).forEach(key => cron.unregister(key));
704
+ });
705
+
706
+ hooks.afterEach(function () {
707
+ sinon.restore(); // Restores real timers
708
+ });
709
+ ```
710
+
711
+ ---
712
+
713
+ ## 13. Future Enhancement Opportunities
714
+
715
+ Ideas aligned with current architecture:
716
+
717
+ 1. **Job Prioritization** - Add priority levels for jobs due at same time
718
+ 2. **Persistence Layer** - Save/restore jobs across restarts
719
+ 3. **Job Statistics** - Track run counts, errors, execution time
720
+ 4. **Job Dependencies** - Wait for other jobs before running
721
+ 5. **Cron Expression Support** - Use cron syntax instead of intervals
722
+ 6. **Job Timeout** - Cancel jobs that run too long
723
+ 7. **Pause/Resume** - Temporarily stop/start the scheduler
724
+ 8. **Job Groups** - Batch operations on related jobs
725
+ 9. **Event Emitters** - Emit events on job lifecycle (start, complete, error)
726
+ 10. **Rate Limiting** - Limit concurrent job execution
727
+
728
+ All enhancements should maintain:
729
+ - Zero-crash guarantee (catch all errors)
730
+ - O(log n) scheduling efficiency
731
+ - Singleton pattern
732
+ - Configuration-driven logging
733
+
734
+ ---
735
+
736
+ ## 14. Related Resources
737
+
738
+ ### Stonyx Framework
739
+ - Main repository: https://github.com/abofs/stonyx
740
+ - Configuration patterns: See `stonyx/config` documentation
741
+ - Logging patterns: See `stonyx/log` documentation
742
+ - Test helpers: See `stonyx/test-helpers` documentation
743
+
744
+ ### Testing
745
+ - QUnit documentation: https://qunitjs.com/
746
+ - Sinon documentation: https://sinonjs.org/
747
+ - Fake timers: https://sinonjs.org/releases/latest/fake-timers/
748
+
749
+ ### Data Structures
750
+ - Min-heap algorithm: https://en.wikipedia.org/wiki/Binary_heap
751
+ - Priority queue patterns: See MinHeap implementation
752
+
753
+ ### Project Repository
754
+ - GitHub: https://github.com/abofs/stonyx-cron
755
+ - Issues: https://github.com/abofs/stonyx-cron/issues
756
+ - License: Apache 2.0
757
+
758
+ ---
759
+
760
+ ## Document Maintenance
761
+
762
+ **Last Updated:** 2026-01-31
763
+ **Version:** 1.0.0
764
+ **Maintainer:** Project owner (update when structure changes significantly)
765
+
766
+ **When to Update:**
767
+ - Major architectural changes
768
+ - New core components added
769
+ - Extension patterns change
770
+ - New configuration options
771
+ - Breaking changes to API
package/.git/config CHANGED
@@ -3,22 +3,16 @@
3
3
  filemode = true
4
4
  bare = false
5
5
  logallrefupdates = true
6
- ignorecase = true
7
- precomposeunicode = true
8
6
  [remote "origin"]
9
- url = git@github.com:abofs/stonyx-cron.git
7
+ url = https://github.com/abofs/stonyx-cron
10
8
  fetch = +refs/heads/*:refs/remotes/origin/*
11
- [branch "dev"]
12
- remote = origin
13
- merge = refs/heads/dev
14
- vscode-merge-base = origin/dev
15
- [branch "stone/cron-updates"]
16
- remote = origin
17
- merge = refs/heads/stone/cron-updates
18
- vscode-merge-base = origin/stone/cron-updates
19
- gk-last-accessed = 2026-01-26T20:25:14.543Z
20
- gk-last-modified = 2026-01-26T20:25:14.543Z
9
+ [gc]
10
+ auto = 0
11
+ [http "https://github.com/"]
12
+ extraheader = AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2hzX3ZlU0VYM1FDZkhtbWl4bU1kRDlBRVRBTjBDUW5HQzBPVUxScg==
21
13
  [branch "main"]
22
14
  remote = origin
23
15
  merge = refs/heads/main
24
- vscode-merge-base = origin/main
16
+ [user]
17
+ name = github-actions[bot]
18
+ email = github-actions[bot]@users.noreply.github.com
@@ -2,35 +2,15 @@ name: CI
2
2
 
3
3
  on:
4
4
  pull_request:
5
- branches:
6
- - dev
7
- - main
5
+ branches: [dev, main]
8
6
 
9
7
  concurrency:
10
8
  group: ci-${{ github.head_ref || github.ref }}
11
9
  cancel-in-progress: true
12
10
 
11
+ permissions:
12
+ contents: read
13
+
13
14
  jobs:
14
15
  test:
15
- runs-on: ubuntu-latest
16
-
17
- steps:
18
- - name: Checkout code
19
- uses: actions/checkout@v3
20
-
21
- - name: Setup pnpm
22
- uses: pnpm/action-setup@v4
23
- with:
24
- version: 9
25
-
26
- - name: Set up Node.js
27
- uses: actions/setup-node@v3
28
- with:
29
- node-version: 22.18.0
30
- cache: 'pnpm'
31
-
32
- - name: Install dependencies
33
- run: pnpm install --frozen-lockfile
34
-
35
- - name: Run tests
36
- run: pnpm test
16
+ uses: abofs/stonyx-workflows/.github/workflows/ci.yml@main
@@ -0,0 +1,35 @@
1
+ name: Publish to NPM
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ version-type:
7
+ description: 'Version type'
8
+ required: true
9
+ type: choice
10
+ options:
11
+ - patch
12
+ - minor
13
+ - major
14
+ custom-version:
15
+ description: 'Custom version (optional, overrides version-type)'
16
+ required: false
17
+ type: string
18
+ pull_request:
19
+ types: [opened, synchronize, reopened]
20
+ branches: [main, dev]
21
+ push:
22
+ branches: [main]
23
+
24
+ permissions:
25
+ contents: write
26
+ id-token: write
27
+ pull-requests: write
28
+
29
+ jobs:
30
+ publish:
31
+ uses: abofs/stonyx-workflows/.github/workflows/npm-publish.yml@main
32
+ with:
33
+ version-type: ${{ github.event.inputs.version-type }}
34
+ custom-version: ${{ github.event.inputs.custom-version }}
35
+ secrets: inherit
package/.gitignore CHANGED
@@ -11,3 +11,6 @@ config/environment.js
11
11
  .vscode/*
12
12
  *.vsix%
13
13
  *DS_Store*
14
+
15
+ # npm auth (never commit tokens)
16
+ .npmrc
package/README.md CHANGED
@@ -20,19 +20,18 @@ cron.register('exampleJob', async () => {
20
20
 
21
21
  ## How it works
22
22
 
23
- `stonyx-cron` uses a min-heap internally to efficiently track the next job to run. Each job has a scheduled trigger time, and the heap ensures the job with the earliest trigger is always at the top.
23
+ `stonyx-cron` uses a min-heap internally to efficiently track the next job to run. Each job has a scheduled trigger time, and the heap ensures the job with the earliest trigger is always at the top.
24
24
 
25
25
  When a job is executed, its next trigger time is updated, and it is re-inserted into the heap. This allows `Cron` to always know which job should run next without scanning all jobs, keeping scheduling efficient even with many jobs.
26
26
 
27
-
28
- ### Public Methods
27
+ ## Public Methods
29
28
 
30
29
  | Method | Parameters | Description |
31
30
  | :----------: | :----------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------- |
32
31
  | `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. |
33
32
  | `unregister` | `key: string` | Remove a previously registered job. |
34
33
 
35
- > All other methods and classes (like `MinHeap`) are used internally by `Cron` and are not intended for direct use.
34
+ > All other methods and classes (like `MinHeap`) are used internally and are not intended for direct use.
36
35
 
37
36
  ## Configuration
38
37
 
package/logs/error.log CHANGED
@@ -1 +1,2 @@
1
- [12/31/1969, 7:00:01 PM] Cron job "jobErr" failed:
1
+ [1/1/1970, 12:00:01 AM] Cron job "jobErr" failed:
2
+ [1/1/1970, 12:00:01 AM] Cron job "jobErr" failed:
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "keywords": [
4
4
  "stonyx-module"
5
5
  ],
6
- "version": "0.2.0",
6
+ "version": "0.2.1-beta.0",
7
7
  "description": "",
8
8
  "main": "src/main.js",
9
9
  "type": "module",
@@ -14,6 +14,10 @@
14
14
  ".": "./src/main.js",
15
15
  "./min-heap": "./src/min-heap.js"
16
16
  },
17
+ "publishConfig": {
18
+ "access": "public",
19
+ "provenance": true
20
+ },
17
21
  "repository": {
18
22
  "type": "git",
19
23
  "url": "git+https://github.com/abofs/stonyx-cron.git"
@@ -1,15 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(node --version:*)",
5
- "Bash(source ~/.nvm/nvm.sh)",
6
- "Bash(nvm use)",
7
- "Bash(pnpm install:*)",
8
- "Bash(pnpm store:*)",
9
- "Bash(npm view:*)",
10
- "Bash(npm publish:*)",
11
- "Bash(npm version:*)",
12
- "Bash(curl:*)"
13
- ]
14
- }
15
- }