@stonyx/cron 0.2.1-beta.7 → 0.2.1-beta.71
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 +4 -0
- package/dist/cron-parser.d.ts +30 -0
- package/dist/cron-parser.js +200 -0
- package/dist/job.d.ts +72 -0
- package/dist/job.js +172 -0
- package/dist/locked.d.ts +13 -0
- package/dist/locked.js +27 -0
- package/dist/main.d.ts +21 -0
- package/dist/main.js +107 -0
- package/dist/min-heap.d.ts +13 -0
- package/dist/min-heap.js +67 -0
- package/dist/normalize.d.ts +49 -0
- package/dist/normalize.js +148 -0
- package/dist/run-log.d.ts +44 -0
- package/dist/run-log.js +60 -0
- package/dist/schedule.d.ts +23 -0
- package/dist/schedule.js +65 -0
- package/dist/service.d.ts +85 -0
- package/dist/service.js +271 -0
- package/package.json +53 -9
- package/.claude/architecture.md +0 -215
- package/.claude/extension-guide.md +0 -291
- package/.claude/improvements.md +0 -53
- package/.claude/project-structure.md +0 -139
- package/.claude/testing.md +0 -85
- package/.git/config +0 -18
- package/.github/workflows/ci.yml +0 -16
- package/.github/workflows/publish.yml +0 -51
- package/.gitignore +0 -16
- package/.npmignore +0 -5
- package/logs/error.log +0 -2
- package/pnpm-lock.yaml +0 -370
- package/src/main.js +0 -112
- package/src/min-heap.js +0 -73
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
# Extension Guide
|
|
2
|
-
|
|
3
|
-
## Extension Points
|
|
4
|
-
|
|
5
|
-
This section provides specific guidance for common feature additions.
|
|
6
|
-
|
|
7
|
-
### Adding Job Metadata
|
|
8
|
-
|
|
9
|
-
**Where:** `stonyx-cron/src/main.js`
|
|
10
|
-
|
|
11
|
-
**Pattern:**
|
|
12
|
-
```javascript
|
|
13
|
-
// In register method, extend job object:
|
|
14
|
-
register(key, callback, interval, runOnInit=false, metadata={}) {
|
|
15
|
-
const job = {
|
|
16
|
-
callback,
|
|
17
|
-
interval,
|
|
18
|
-
key,
|
|
19
|
-
metadata // Add metadata field
|
|
20
|
-
};
|
|
21
|
-
this.jobs[key] = job;
|
|
22
|
-
// ... rest of method
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Access metadata in runDueJobs or other methods:
|
|
26
|
-
const { metadata } = job;
|
|
27
|
-
if (metadata.priority === 'high') {
|
|
28
|
-
// Handle high priority jobs differently
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
**Tests to add:** Verify metadata is stored and accessible in job object.
|
|
33
|
-
|
|
34
|
-
### Adding Priority Levels
|
|
35
|
-
|
|
36
|
-
**Where:**
|
|
37
|
-
- `stonyx-cron/src/main.js` (Cron class)
|
|
38
|
-
- `stonyx-cron/src/min-heap.js` (MinHeap comparison)
|
|
39
|
-
|
|
40
|
-
**Pattern:**
|
|
41
|
-
```javascript
|
|
42
|
-
// Option 1: Secondary sort on priority
|
|
43
|
-
// In min-heap.js bubbleUp/bubbleDown, change comparison:
|
|
44
|
-
if (this.items[idx].nextTrigger < this.items[parentIdx].nextTrigger ||
|
|
45
|
-
(this.items[idx].nextTrigger === this.items[parentIdx].nextTrigger &&
|
|
46
|
-
this.items[idx].priority > this.items[parentIdx].priority)) {
|
|
47
|
-
// Swap
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Option 2: Offset nextTrigger by priority
|
|
51
|
-
// In main.js setNextTrigger:
|
|
52
|
-
setNextTrigger(job) {
|
|
53
|
-
const priorityOffset = job.priority || 0;
|
|
54
|
-
job.nextTrigger = getTimestamp() + parseInt(job.interval, 10) - priorityOffset;
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
**Considerations:**
|
|
59
|
-
- Option 1 maintains exact timing but adds complexity to heap
|
|
60
|
-
- Option 2 is simpler but slightly alters execution timing
|
|
61
|
-
- Add `priority` field to job object schema
|
|
62
|
-
|
|
63
|
-
**Tests to add:** Jobs with higher priority run before lower priority when due at same time.
|
|
64
|
-
|
|
65
|
-
### Adding Job Statistics
|
|
66
|
-
|
|
67
|
-
**Where:** `stonyx-cron/src/main.js`
|
|
68
|
-
|
|
69
|
-
**Pattern:**
|
|
70
|
-
```javascript
|
|
71
|
-
// Extend job object with stats:
|
|
72
|
-
register(key, callback, interval, runOnInit=false) {
|
|
73
|
-
const job = {
|
|
74
|
-
callback,
|
|
75
|
-
interval,
|
|
76
|
-
key,
|
|
77
|
-
stats: {
|
|
78
|
-
runCount: 0,
|
|
79
|
-
lastRun: null,
|
|
80
|
-
lastError: null,
|
|
81
|
-
errorCount: 0
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
// ...
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Update stats in runDueJobs:
|
|
88
|
-
async runDueJobs() {
|
|
89
|
-
// ...
|
|
90
|
-
try {
|
|
91
|
-
await job.callback();
|
|
92
|
-
job.stats.runCount++;
|
|
93
|
-
job.stats.lastRun = getTimestamp();
|
|
94
|
-
} catch (err) {
|
|
95
|
-
job.stats.errorCount++;
|
|
96
|
-
job.stats.lastError = err.message;
|
|
97
|
-
log.error(`Cron job "${job.key}" failed:`, err);
|
|
98
|
-
}
|
|
99
|
-
// ...
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Add method to retrieve stats:
|
|
103
|
-
getJobStats(key) {
|
|
104
|
-
return this.jobs[key]?.stats;
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
**Tests to add:** Verify stats increment correctly on success/error.
|
|
109
|
-
|
|
110
|
-
### Adding Persistence
|
|
111
|
-
|
|
112
|
-
**Where:** `stonyx-cron/src/main.js`
|
|
113
|
-
|
|
114
|
-
**Pattern:**
|
|
115
|
-
```javascript
|
|
116
|
-
// Add save/load methods:
|
|
117
|
-
async saveJobs() {
|
|
118
|
-
const jobData = Object.values(this.jobs).map(job => ({
|
|
119
|
-
key: job.key,
|
|
120
|
-
interval: job.interval,
|
|
121
|
-
// Don't save callback - must be re-registered
|
|
122
|
-
}));
|
|
123
|
-
// Write to file/database
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async loadJobs(callbacks) {
|
|
127
|
-
const jobData = // Read from file/database
|
|
128
|
-
jobData.forEach(({ key, interval }) => {
|
|
129
|
-
if (callbacks[key]) {
|
|
130
|
-
this.register(key, callbacks[key], interval);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
**Considerations:**
|
|
137
|
-
- Cannot serialize callbacks - must be re-registered on load
|
|
138
|
-
- Save job metadata, intervals, and keys
|
|
139
|
-
- Consider saving `nextTrigger` to maintain schedule across restarts
|
|
140
|
-
|
|
141
|
-
**Tests to add:** Verify jobs can be saved and restored with same intervals.
|
|
142
|
-
|
|
143
|
-
### Adding One-Time Jobs
|
|
144
|
-
|
|
145
|
-
**Where:** `stonyx-cron/src/main.js`
|
|
146
|
-
|
|
147
|
-
**Pattern:**
|
|
148
|
-
```javascript
|
|
149
|
-
// Add oneTime flag to job schema:
|
|
150
|
-
register(key, callback, interval, runOnInit=false, oneTime=false) {
|
|
151
|
-
const job = { callback, interval, key, oneTime };
|
|
152
|
-
// ...
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Modify runDueJobs to unregister one-time jobs:
|
|
156
|
-
async runDueJobs() {
|
|
157
|
-
const now = getTimestamp();
|
|
158
|
-
const { heap } = this;
|
|
159
|
-
|
|
160
|
-
while (!heap.isEmpty() && heap.peek().nextTrigger <= now) {
|
|
161
|
-
const job = heap.pop();
|
|
162
|
-
|
|
163
|
-
try {
|
|
164
|
-
await job.callback();
|
|
165
|
-
} catch (err) {
|
|
166
|
-
log.error(`Cron job "${job.key}" failed:`, err);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (job.oneTime) {
|
|
170
|
-
delete this.jobs[job.key]; // Don't reschedule
|
|
171
|
-
if (config.debug) this.log('one-time job completed', job.key);
|
|
172
|
-
} else {
|
|
173
|
-
this.setNextTrigger(job);
|
|
174
|
-
heap.push(job);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
this.scheduleNextRun();
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
**Tests to add:** One-time jobs execute once and are not rescheduled.
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
|
-
## Common Pitfalls & Gotchas
|
|
187
|
-
|
|
188
|
-
### Time Units Confusion
|
|
189
|
-
**Pitfall:** Mixing seconds and milliseconds
|
|
190
|
-
|
|
191
|
-
**Correct:**
|
|
192
|
-
```javascript
|
|
193
|
-
// getTimestamp() returns SECONDS
|
|
194
|
-
const now = getTimestamp();
|
|
195
|
-
job.interval = 5; // 5 seconds
|
|
196
|
-
job.nextTrigger = now + 5; // 5 seconds from now
|
|
197
|
-
|
|
198
|
-
// setTimeout expects MILLISECONDS
|
|
199
|
-
const delay = (job.nextTrigger - now) * 1000;
|
|
200
|
-
setTimeout(callback, delay);
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
**Wrong:**
|
|
204
|
-
```javascript
|
|
205
|
-
// DON'T DO THIS
|
|
206
|
-
const delay = job.nextTrigger - getTimestamp(); // Missing * 1000
|
|
207
|
-
setTimeout(callback, delay); // Will run almost immediately!
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### Singleton Behavior
|
|
211
|
-
**Pitfall:** Creating multiple Cron instances unexpectedly
|
|
212
|
-
|
|
213
|
-
**Behavior:**
|
|
214
|
-
```javascript
|
|
215
|
-
const cron1 = new Cron();
|
|
216
|
-
const cron2 = new Cron();
|
|
217
|
-
console.log(cron1 === cron2); // true - same instance!
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
**Implication:** Registering jobs on any instance affects the same scheduler.
|
|
221
|
-
|
|
222
|
-
### Job Callback Async Handling
|
|
223
|
-
**Pitfall:** Not awaiting async callbacks
|
|
224
|
-
|
|
225
|
-
**Correct:**
|
|
226
|
-
```javascript
|
|
227
|
-
cron.register('job', async () => {
|
|
228
|
-
await someAsyncOperation();
|
|
229
|
-
}, 10);
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
The scheduler awaits the callback, so errors are caught properly.
|
|
233
|
-
|
|
234
|
-
**Wrong:**
|
|
235
|
-
```javascript
|
|
236
|
-
cron.register('job', () => {
|
|
237
|
-
someAsyncOperation(); // Not awaited - errors won't be caught!
|
|
238
|
-
}, 10);
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### Heap Reference Equality
|
|
242
|
-
**Pitfall:** Modifying job objects outside the scheduler
|
|
243
|
-
|
|
244
|
-
**Issue:**
|
|
245
|
-
```javascript
|
|
246
|
-
const job = cron.jobs['myJob'];
|
|
247
|
-
job.interval = 20; // This doesn't update the heap!
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
**Solution:** Always use `unregister` then `register` to update job properties.
|
|
251
|
-
|
|
252
|
-
### Test Isolation
|
|
253
|
-
**Pitfall:** Tests interfering with each other due to singleton
|
|
254
|
-
|
|
255
|
-
**Solution:**
|
|
256
|
-
```javascript
|
|
257
|
-
hooks.beforeEach(function () {
|
|
258
|
-
clock = sinon.useFakeTimers({ shouldAdvanceTime: false });
|
|
259
|
-
cron = new Cron(); // Gets singleton
|
|
260
|
-
|
|
261
|
-
// Clear previous test's jobs
|
|
262
|
-
Object.keys(cron.jobs).forEach(key => cron.unregister(key));
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
hooks.afterEach(function () {
|
|
266
|
-
sinon.restore(); // Restores real timers
|
|
267
|
-
});
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
---
|
|
271
|
-
|
|
272
|
-
## Future Enhancement Opportunities
|
|
273
|
-
|
|
274
|
-
Ideas aligned with current architecture:
|
|
275
|
-
|
|
276
|
-
1. **Job Prioritization** - Add priority levels for jobs due at same time
|
|
277
|
-
2. **Persistence Layer** - Save/restore jobs across restarts
|
|
278
|
-
3. **Job Statistics** - Track run counts, errors, execution time
|
|
279
|
-
4. **Job Dependencies** - Wait for other jobs before running
|
|
280
|
-
5. **Cron Expression Support** - Use cron syntax instead of intervals
|
|
281
|
-
6. **Job Timeout** - Cancel jobs that run too long
|
|
282
|
-
7. **Pause/Resume** - Temporarily stop/start the scheduler
|
|
283
|
-
8. **Job Groups** - Batch operations on related jobs
|
|
284
|
-
9. **Event Emitters** - Emit events on job lifecycle (start, complete, error)
|
|
285
|
-
10. **Rate Limiting** - Limit concurrent job execution
|
|
286
|
-
|
|
287
|
-
All enhancements should maintain:
|
|
288
|
-
- Zero-crash guarantee (catch all errors)
|
|
289
|
-
- O(log n) scheduling efficiency
|
|
290
|
-
- Singleton pattern
|
|
291
|
-
- Configuration-driven logging
|
package/.claude/improvements.md
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# Improvement Opportunities
|
|
2
|
-
|
|
3
|
-
## `register()` does not validate duplicate keys
|
|
4
|
-
|
|
5
|
-
**File:** `src/main.js`, `register()` method (line 67)
|
|
6
|
-
|
|
7
|
-
When `register()` is called with a key that already exists, it overwrites the entry in `this.jobs[key]` but never removes the old job object from the heap. The old entry remains orphaned in the heap and will still trigger when its `nextTrigger` time arrives, even though it is no longer tracked in `this.jobs`.
|
|
8
|
-
|
|
9
|
-
```javascript
|
|
10
|
-
register(key, callback, interval, runOnInit=false) {
|
|
11
|
-
const job = { callback, interval, key };
|
|
12
|
-
this.jobs[key] = job; // overwrites old reference
|
|
13
|
-
this.setNextTrigger(job);
|
|
14
|
-
this.heap.push(job); // pushes new entry, old entry still in heap
|
|
15
|
-
// ...
|
|
16
|
-
}
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
**Impact:** The orphaned heap entry will fire its old callback on the old schedule. Since it is no longer in `this.jobs`, it cannot be unregistered.
|
|
20
|
-
|
|
21
|
-
**Suggested fix:** Check for an existing key and call `unregister(key)` before registering, or throw an error if the key is already registered.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## `runOnInit` callback is not awaited
|
|
26
|
-
|
|
27
|
-
**File:** `src/main.js`, `register()` method (line 79)
|
|
28
|
-
|
|
29
|
-
When `runOnInit` is `true`, the callback is invoked synchronously without `await`:
|
|
30
|
-
|
|
31
|
-
```javascript
|
|
32
|
-
if (runOnInit) {
|
|
33
|
-
try {
|
|
34
|
-
callback(); // not awaited
|
|
35
|
-
} catch (err) {
|
|
36
|
-
log.error(`Cron job "${key}" failed on init:`, err);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
This is inconsistent with `runDueJobs()`, which does await the callback:
|
|
42
|
-
|
|
43
|
-
```javascript
|
|
44
|
-
try {
|
|
45
|
-
await job.callback(); // awaited
|
|
46
|
-
} catch (err) {
|
|
47
|
-
log.error(`Cron job "${job.key}" failed:`, err);
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
**Impact:** If the callback is async and throws, the rejection will not be caught by the `try/catch` block in `register()`. The error becomes an unhandled promise rejection instead of being logged.
|
|
52
|
-
|
|
53
|
-
**Suggested fix:** Add `await` to the `callback()` call in the `runOnInit` branch and make `register()` async, or wrap in a `.catch()` handler.
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
# stonyx-cron Project Structure
|
|
2
|
-
|
|
3
|
-
## Detailed Guides
|
|
4
|
-
|
|
5
|
-
- [Architecture & Core Components](./architecture.md) — Deep dive into Cron and MinHeap classes, dependencies, code patterns, and configuration reference
|
|
6
|
-
- [Testing Guidelines](./testing.md) — Test structure, fake timers, spies/stubs patterns, and running tests
|
|
7
|
-
- [Extension Guide](./extension-guide.md) — Extension points, common pitfalls, and future enhancement opportunities
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## 1. Project Overview
|
|
12
|
-
|
|
13
|
-
**stonyx-cron** is a lightweight async job scheduler for the Stonyx framework that uses a min-heap priority queue for efficient job scheduling.
|
|
14
|
-
|
|
15
|
-
**Core Purpose:**
|
|
16
|
-
- Schedule and execute async jobs at specified intervals
|
|
17
|
-
- O(log n) scheduling efficiency via min-heap priority queue
|
|
18
|
-
- Robust error handling that never crashes the scheduler
|
|
19
|
-
- Configuration-driven logging aligned with Stonyx patterns
|
|
20
|
-
|
|
21
|
-
**Technology Stack:**
|
|
22
|
-
- **Module System:** ES Modules (ESM)
|
|
23
|
-
- **Testing:** QUnit with Sinon for spies/stubs/fake timers
|
|
24
|
-
- **Dependencies:** Stonyx framework, @stonyx/utils/date
|
|
25
|
-
- **Node Version:** Specified in `.nvmrc`
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## 2. Architecture & Design Decisions
|
|
30
|
-
|
|
31
|
-
### Singleton Pattern
|
|
32
|
-
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.
|
|
33
|
-
|
|
34
|
-
```javascript
|
|
35
|
-
constructor() {
|
|
36
|
-
if (Cron.instance) return Cron.instance;
|
|
37
|
-
Cron.instance = this;
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
**Rationale:** Prevents multiple competing schedulers and ensures consistent job management.
|
|
42
|
-
|
|
43
|
-
### Min-Heap Priority Queue
|
|
44
|
-
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.
|
|
45
|
-
|
|
46
|
-
**Heap Property:** Parent nodes have earlier `nextTrigger` values than their children, so the root is always the next job to execute.
|
|
47
|
-
|
|
48
|
-
### Async Job Execution Strategy
|
|
49
|
-
- Jobs are executed with `await job.callback()` to handle async operations
|
|
50
|
-
- Errors are caught and logged but never crash the scheduler
|
|
51
|
-
- After execution (success or failure), jobs are rescheduled and re-inserted into the heap
|
|
52
|
-
- The scheduler reschedules itself after processing all due jobs
|
|
53
|
-
|
|
54
|
-
### Configuration-Driven Logging
|
|
55
|
-
Logging follows Stonyx patterns:
|
|
56
|
-
- Check `config.debug` before debug logs
|
|
57
|
-
- Check `config.cron?.log` before cron-specific logs
|
|
58
|
-
- Use `log.cron()` for cron messages, `log.error()` for errors
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## 3. File Structure
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
stonyx-cron/
|
|
66
|
-
├── .claude/
|
|
67
|
-
│ ├── project-structure.md - Project overview & structure
|
|
68
|
-
│ ├── architecture.md - Core components & code patterns
|
|
69
|
-
│ ├── testing.md - Testing guidelines
|
|
70
|
-
│ ├── extension-guide.md - Extension points & pitfalls
|
|
71
|
-
│ └── improvements.md - Known improvement opportunities
|
|
72
|
-
├── .github/
|
|
73
|
-
│ └── workflows/
|
|
74
|
-
│ ├── ci.yml - CI pipeline (PR checks)
|
|
75
|
-
│ └── publish.yml - NPM publish workflow
|
|
76
|
-
├── config/
|
|
77
|
-
│ └── environment.js - Cron module configuration
|
|
78
|
-
├── src/
|
|
79
|
-
│ ├── main.js - Cron class (singleton scheduler)
|
|
80
|
-
│ └── min-heap.js - MinHeap priority queue implementation
|
|
81
|
-
├── test/
|
|
82
|
-
│ └── unit/
|
|
83
|
-
│ ├── cron-test.js - Cron class unit tests
|
|
84
|
-
│ └── min-heap-test.js - MinHeap unit tests
|
|
85
|
-
├── package.json - Package metadata and exports
|
|
86
|
-
├── README.md - Project documentation
|
|
87
|
-
├── LICENSE.md - Apache 2.0 license
|
|
88
|
-
├── .npmignore - Files excluded from npm publish
|
|
89
|
-
├── .nvmrc - Node version specification
|
|
90
|
-
└── .gitignore - Git ignore rules
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
## 4. Package Exports
|
|
96
|
-
|
|
97
|
-
**File:** `stonyx-cron/package.json`
|
|
98
|
-
|
|
99
|
-
```json
|
|
100
|
-
{
|
|
101
|
-
"exports": {
|
|
102
|
-
".": "./src/main.js",
|
|
103
|
-
"./min-heap": "./src/min-heap.js"
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
**Usage:**
|
|
109
|
-
```javascript
|
|
110
|
-
// Import Cron class (default export)
|
|
111
|
-
import Cron from '@stonyx/cron';
|
|
112
|
-
|
|
113
|
-
// Import MinHeap class (for advanced usage)
|
|
114
|
-
import MinHeap from '@stonyx/cron/min-heap';
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## 5. Related Resources
|
|
120
|
-
|
|
121
|
-
### Stonyx Framework
|
|
122
|
-
- Main repository: https://github.com/abofs/stonyx
|
|
123
|
-
- Configuration patterns: See `stonyx/config` documentation
|
|
124
|
-
- Logging patterns: See `stonyx/log` documentation
|
|
125
|
-
- Test helpers: See `stonyx/test-helpers` documentation
|
|
126
|
-
|
|
127
|
-
### Testing
|
|
128
|
-
- QUnit documentation: https://qunitjs.com/
|
|
129
|
-
- Sinon documentation: https://sinonjs.org/
|
|
130
|
-
- Fake timers: https://sinonjs.org/releases/latest/fake-timers/
|
|
131
|
-
|
|
132
|
-
### Data Structures
|
|
133
|
-
- Min-heap algorithm: https://en.wikipedia.org/wiki/Binary_heap
|
|
134
|
-
- Priority queue patterns: See MinHeap implementation
|
|
135
|
-
|
|
136
|
-
### Project Repository
|
|
137
|
-
- GitHub: https://github.com/abofs/stonyx-cron
|
|
138
|
-
- Issues: https://github.com/abofs/stonyx-cron/issues
|
|
139
|
-
- License: Apache 2.0
|
package/.claude/testing.md
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# Testing Guidelines
|
|
2
|
-
|
|
3
|
-
## Testing Guidelines
|
|
4
|
-
|
|
5
|
-
### Test Structure
|
|
6
|
-
Tests are located in `stonyx-cron/test/unit/` and use QUnit modules:
|
|
7
|
-
|
|
8
|
-
```javascript
|
|
9
|
-
import QUnit from 'qunit';
|
|
10
|
-
import sinon from 'sinon';
|
|
11
|
-
import { setupIntegrationTests } from "stonyx/test-helpers";
|
|
12
|
-
|
|
13
|
-
const { module, test } = QUnit;
|
|
14
|
-
|
|
15
|
-
module('[Unit] Cron', function (hooks) {
|
|
16
|
-
setupIntegrationTests(hooks);
|
|
17
|
-
|
|
18
|
-
let cron, clock;
|
|
19
|
-
|
|
20
|
-
hooks.beforeEach(function () {
|
|
21
|
-
clock = sinon.useFakeTimers({ shouldAdvanceTime: false });
|
|
22
|
-
cron = new Cron();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
hooks.afterEach(function () {
|
|
26
|
-
sinon.restore();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('test description', async function (assert) {
|
|
30
|
-
// Test implementation
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Fake Timers Pattern (CRITICAL)
|
|
36
|
-
**Always use fake timers for time-based tests:**
|
|
37
|
-
|
|
38
|
-
```javascript
|
|
39
|
-
// Setup in beforeEach
|
|
40
|
-
clock = sinon.useFakeTimers({ shouldAdvanceTime: false });
|
|
41
|
-
|
|
42
|
-
// Advance time synchronously
|
|
43
|
-
clock.tick(5000); // Advance 5 seconds
|
|
44
|
-
|
|
45
|
-
// For async operations, use tickAsync
|
|
46
|
-
clock.tick(5000);
|
|
47
|
-
await clock.tickAsync(0); // Process async callbacks
|
|
48
|
-
|
|
49
|
-
// Cleanup in afterEach
|
|
50
|
-
sinon.restore();
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
**Why `shouldAdvanceTime: false`?**
|
|
54
|
-
Prevents real time from interfering with fake time, ensuring deterministic tests.
|
|
55
|
-
|
|
56
|
-
### Spies & Stubs Patterns
|
|
57
|
-
```javascript
|
|
58
|
-
// Spy on function calls
|
|
59
|
-
const cb = sinon.spy();
|
|
60
|
-
cron.register('job1', cb, 5);
|
|
61
|
-
assert.ok(cb.calledOnce, 'Callback executed once');
|
|
62
|
-
|
|
63
|
-
// Stub methods
|
|
64
|
-
const stub = sinon.stub().rejects(new Error('boom'));
|
|
65
|
-
cron.register('jobErr', stub, 1);
|
|
66
|
-
|
|
67
|
-
// Spy on existing methods
|
|
68
|
-
const logSpy = sinon.spy(log, 'cron');
|
|
69
|
-
cron.log('test message');
|
|
70
|
-
assert.ok(logSpy.calledOnce, 'Log called');
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Test Coverage Expectations
|
|
74
|
-
Tests should cover:
|
|
75
|
-
- Job registration and execution
|
|
76
|
-
- Rescheduling behavior
|
|
77
|
-
- Unregistration
|
|
78
|
-
- Error handling
|
|
79
|
-
- Configuration-driven logging
|
|
80
|
-
- Edge cases (empty heap, multiple jobs, etc.)
|
|
81
|
-
|
|
82
|
-
### Running Tests
|
|
83
|
-
```bash
|
|
84
|
-
pnpm test # Runs: stonyx test
|
|
85
|
-
```
|
package/.git/config
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
[core]
|
|
2
|
-
repositoryformatversion = 0
|
|
3
|
-
filemode = true
|
|
4
|
-
bare = false
|
|
5
|
-
logallrefupdates = true
|
|
6
|
-
[remote "origin"]
|
|
7
|
-
url = https://github.com/abofs/stonyx-cron
|
|
8
|
-
fetch = +refs/heads/*:refs/remotes/origin/*
|
|
9
|
-
[gc]
|
|
10
|
-
auto = 0
|
|
11
|
-
[http "https://github.com/"]
|
|
12
|
-
extraheader = AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2hwX2hBdU5WbnJNcml2bktkZTlmaU80WW9lZk9QenZtdTFiUWtQNA==
|
|
13
|
-
[branch "main"]
|
|
14
|
-
remote = origin
|
|
15
|
-
merge = refs/heads/main
|
|
16
|
-
[user]
|
|
17
|
-
name = github-actions[bot]
|
|
18
|
-
email = github-actions[bot]@users.noreply.github.com
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
branches: [dev, main]
|
|
6
|
-
|
|
7
|
-
concurrency:
|
|
8
|
-
group: ci-${{ github.head_ref || github.ref }}
|
|
9
|
-
cancel-in-progress: true
|
|
10
|
-
|
|
11
|
-
permissions:
|
|
12
|
-
contents: read
|
|
13
|
-
|
|
14
|
-
jobs:
|
|
15
|
-
test:
|
|
16
|
-
uses: abofs/stonyx-workflows/.github/workflows/ci.yml@main
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
name: Publish to NPM
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
repository_dispatch:
|
|
5
|
-
types: [cascade-publish]
|
|
6
|
-
workflow_dispatch:
|
|
7
|
-
inputs:
|
|
8
|
-
version-type:
|
|
9
|
-
description: 'Version type'
|
|
10
|
-
required: true
|
|
11
|
-
type: choice
|
|
12
|
-
options:
|
|
13
|
-
- patch
|
|
14
|
-
- minor
|
|
15
|
-
- major
|
|
16
|
-
custom-version:
|
|
17
|
-
description: 'Custom version (optional, overrides version-type)'
|
|
18
|
-
required: false
|
|
19
|
-
type: string
|
|
20
|
-
pull_request:
|
|
21
|
-
types: [opened, synchronize, reopened]
|
|
22
|
-
branches: [main]
|
|
23
|
-
push:
|
|
24
|
-
branches: [main]
|
|
25
|
-
|
|
26
|
-
concurrency:
|
|
27
|
-
group: ${{ github.event_name == 'repository_dispatch' && 'cascade-update' || format('publish-{0}', github.ref) }}
|
|
28
|
-
cancel-in-progress: false
|
|
29
|
-
|
|
30
|
-
permissions:
|
|
31
|
-
contents: write
|
|
32
|
-
id-token: write
|
|
33
|
-
pull-requests: write
|
|
34
|
-
|
|
35
|
-
jobs:
|
|
36
|
-
publish:
|
|
37
|
-
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
|
38
|
-
uses: abofs/stonyx-workflows/.github/workflows/npm-publish.yml@main
|
|
39
|
-
with:
|
|
40
|
-
version-type: ${{ github.event.inputs.version-type }}
|
|
41
|
-
custom-version: ${{ github.event.inputs.custom-version }}
|
|
42
|
-
cascade-source: ${{ github.event.client_payload.source_package || '' }}
|
|
43
|
-
secrets: inherit
|
|
44
|
-
|
|
45
|
-
cascade:
|
|
46
|
-
needs: publish
|
|
47
|
-
uses: abofs/stonyx-workflows/.github/workflows/cascade.yml@main
|
|
48
|
-
with:
|
|
49
|
-
package-name: ${{ needs.publish.outputs.package-name }}
|
|
50
|
-
published-version: ${{ needs.publish.outputs.published-version }}
|
|
51
|
-
secrets: inherit
|
package/.gitignore
DELETED
package/logs/error.log
DELETED