@hazeljs/cron 0.2.0-beta.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/README.md +441 -0
- package/dist/cron.decorator.d.ts +20 -0
- package/dist/cron.decorator.d.ts.map +1 -0
- package/dist/cron.decorator.js +38 -0
- package/dist/cron.module.d.ts +38 -0
- package/dist/cron.module.d.ts.map +1 -0
- package/dist/cron.module.js +78 -0
- package/dist/cron.service.d.ts +56 -0
- package/dist/cron.service.d.ts.map +1 -0
- package/dist/cron.service.js +273 -0
- package/dist/cron.types.d.ts +174 -0
- package/dist/cron.types.d.ts.map +1 -0
- package/dist/cron.types.js +79 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# @hazeljs/cron
|
|
2
|
+
|
|
3
|
+
**Cron Job Scheduling Module for HazelJS**
|
|
4
|
+
|
|
5
|
+
Schedule and manage recurring tasks with cron expressions and decorator-based API.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@hazeljs/cron)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- ⏰ **Cron Expressions** - Standard cron syntax support
|
|
13
|
+
- 🎨 **Decorator-Based API** - `@Cron`, `@Interval`, `@Timeout`
|
|
14
|
+
- 🔄 **Job Management** - Start, stop, and manage scheduled jobs
|
|
15
|
+
- 📊 **Job Monitoring** - Track execution history and status
|
|
16
|
+
- 🛡️ **Error Handling** - Automatic retry and error recovery
|
|
17
|
+
- 🎯 **Timezone Support** - Schedule jobs in specific timezones
|
|
18
|
+
- 🔒 **Overlap Prevention** - Prevent concurrent executions
|
|
19
|
+
- 📝 **Logging** - Built-in execution logging
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @hazeljs/cron
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Import CronModule
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { HazelModule } from '@hazeljs/core';
|
|
33
|
+
import { CronModule } from '@hazeljs/cron';
|
|
34
|
+
|
|
35
|
+
@HazelModule({
|
|
36
|
+
imports: [CronModule.forRoot()],
|
|
37
|
+
})
|
|
38
|
+
export class AppModule {}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Create Scheduled Tasks
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { Injectable } from '@hazeljs/core';
|
|
45
|
+
import { Cron, CronExpression } from '@hazeljs/cron';
|
|
46
|
+
|
|
47
|
+
@Injectable()
|
|
48
|
+
export class TasksService {
|
|
49
|
+
@Cron(CronExpression.EVERY_MINUTE)
|
|
50
|
+
handleEveryMinute() {
|
|
51
|
+
console.log('This runs every minute');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@Cron('0 0 * * *') // Every day at midnight
|
|
55
|
+
handleMidnight() {
|
|
56
|
+
console.log('This runs at midnight every day');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Cron('0 9 * * 1-5') // Weekdays at 9 AM
|
|
60
|
+
handleWeekdayMorning() {
|
|
61
|
+
console.log('This runs Monday-Friday at 9 AM');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Cron Expressions
|
|
67
|
+
|
|
68
|
+
### Predefined Expressions
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { CronExpression } from '@hazeljs/cron';
|
|
72
|
+
|
|
73
|
+
@Cron(CronExpression.EVERY_SECOND) // Every second
|
|
74
|
+
@Cron(CronExpression.EVERY_5_SECONDS) // Every 5 seconds
|
|
75
|
+
@Cron(CronExpression.EVERY_10_SECONDS) // Every 10 seconds
|
|
76
|
+
@Cron(CronExpression.EVERY_30_SECONDS) // Every 30 seconds
|
|
77
|
+
@Cron(CronExpression.EVERY_MINUTE) // Every minute
|
|
78
|
+
@Cron(CronExpression.EVERY_5_MINUTES) // Every 5 minutes
|
|
79
|
+
@Cron(CronExpression.EVERY_10_MINUTES) // Every 10 minutes
|
|
80
|
+
@Cron(CronExpression.EVERY_30_MINUTES) // Every 30 minutes
|
|
81
|
+
@Cron(CronExpression.EVERY_HOUR) // Every hour
|
|
82
|
+
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT) // Daily at 00:00
|
|
83
|
+
@Cron(CronExpression.EVERY_DAY_AT_NOON) // Daily at 12:00
|
|
84
|
+
@Cron(CronExpression.EVERY_WEEK) // Every Sunday at 00:00
|
|
85
|
+
@Cron(CronExpression.EVERY_MONTH) // First day of month at 00:00
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Custom Expressions
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// Format: second minute hour day month weekday
|
|
92
|
+
// * * * * * *
|
|
93
|
+
|
|
94
|
+
@Cron('0 */15 * * * *') // Every 15 minutes
|
|
95
|
+
@Cron('0 0 12 * * *') // Every day at noon
|
|
96
|
+
@Cron('0 0 0 1 * *') // First day of every month
|
|
97
|
+
@Cron('0 0 9 * * 1-5') // Weekdays at 9 AM
|
|
98
|
+
@Cron('0 30 11 * * 1,3,5') // Mon, Wed, Fri at 11:30 AM
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Decorators
|
|
102
|
+
|
|
103
|
+
### @Cron()
|
|
104
|
+
|
|
105
|
+
Schedule recurring tasks:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
@Injectable()
|
|
109
|
+
export class TasksService {
|
|
110
|
+
@Cron('0 0 * * *', {
|
|
111
|
+
name: 'daily-cleanup',
|
|
112
|
+
timezone: 'America/New_York',
|
|
113
|
+
})
|
|
114
|
+
async dailyCleanup() {
|
|
115
|
+
console.log('Running daily cleanup');
|
|
116
|
+
await this.cleanupOldData();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### @Interval()
|
|
122
|
+
|
|
123
|
+
Run tasks at fixed intervals:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
@Injectable()
|
|
127
|
+
export class MonitoringService {
|
|
128
|
+
@Interval(5000) // Every 5 seconds
|
|
129
|
+
checkHealth() {
|
|
130
|
+
console.log('Health check');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@Interval(60000, { name: 'metrics-collector' })
|
|
134
|
+
collectMetrics() {
|
|
135
|
+
console.log('Collecting metrics');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### @Timeout()
|
|
141
|
+
|
|
142
|
+
Run tasks once after a delay:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
@Injectable()
|
|
146
|
+
export class StartupService {
|
|
147
|
+
@Timeout(5000) // After 5 seconds
|
|
148
|
+
async warmupCache() {
|
|
149
|
+
console.log('Warming up cache');
|
|
150
|
+
await this.cacheService.warmup();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Job Management
|
|
156
|
+
|
|
157
|
+
### Manual Job Control
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { Injectable } from '@hazeljs/core';
|
|
161
|
+
import { CronService, SchedulerRegistry } from '@hazeljs/cron';
|
|
162
|
+
|
|
163
|
+
@Injectable()
|
|
164
|
+
export class JobManager {
|
|
165
|
+
constructor(
|
|
166
|
+
private schedulerRegistry: SchedulerRegistry,
|
|
167
|
+
private cronService: CronService
|
|
168
|
+
) {}
|
|
169
|
+
|
|
170
|
+
stopJob(name: string) {
|
|
171
|
+
const job = this.schedulerRegistry.getCronJob(name);
|
|
172
|
+
job.stop();
|
|
173
|
+
console.log(`Job ${name} stopped`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
startJob(name: string) {
|
|
177
|
+
const job = this.schedulerRegistry.getCronJob(name);
|
|
178
|
+
job.start();
|
|
179
|
+
console.log(`Job ${name} started`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
deleteJob(name: string) {
|
|
183
|
+
this.schedulerRegistry.deleteCronJob(name);
|
|
184
|
+
console.log(`Job ${name} deleted`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
getAllJobs() {
|
|
188
|
+
return this.schedulerRegistry.getCronJobs();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Dynamic Job Creation
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
@Injectable()
|
|
197
|
+
export class DynamicJobService {
|
|
198
|
+
constructor(private schedulerRegistry: SchedulerRegistry) {}
|
|
199
|
+
|
|
200
|
+
addJob(name: string, cronExpression: string, callback: () => void) {
|
|
201
|
+
const job = new CronJob(cronExpression, callback);
|
|
202
|
+
this.schedulerRegistry.addCronJob(name, job);
|
|
203
|
+
job.start();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
removeJob(name: string) {
|
|
207
|
+
this.schedulerRegistry.deleteCronJob(name);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Configuration
|
|
213
|
+
|
|
214
|
+
### Module Configuration
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
CronModule.forRoot({
|
|
218
|
+
// Enable/disable all cron jobs
|
|
219
|
+
enabled: true,
|
|
220
|
+
|
|
221
|
+
// Default timezone for all jobs
|
|
222
|
+
timezone: 'UTC',
|
|
223
|
+
|
|
224
|
+
// Prevent overlapping executions
|
|
225
|
+
preventOverlap: true,
|
|
226
|
+
|
|
227
|
+
// Retry failed jobs
|
|
228
|
+
retry: {
|
|
229
|
+
attempts: 3,
|
|
230
|
+
delay: 1000,
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
// Logging
|
|
234
|
+
logging: {
|
|
235
|
+
enabled: true,
|
|
236
|
+
logSuccess: true,
|
|
237
|
+
logErrors: true,
|
|
238
|
+
},
|
|
239
|
+
})
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Job-Level Configuration
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
@Cron('0 0 * * *', {
|
|
246
|
+
name: 'backup-job',
|
|
247
|
+
timezone: 'America/New_York',
|
|
248
|
+
runOnInit: false,
|
|
249
|
+
preventOverlap: true,
|
|
250
|
+
retryAttempts: 3,
|
|
251
|
+
retryDelay: 5000,
|
|
252
|
+
})
|
|
253
|
+
async backupDatabase() {
|
|
254
|
+
await this.databaseService.backup();
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Error Handling
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
@Injectable()
|
|
262
|
+
export class TasksService {
|
|
263
|
+
@Cron('0 0 * * *', {
|
|
264
|
+
name: 'risky-job',
|
|
265
|
+
retryAttempts: 3,
|
|
266
|
+
retryDelay: 5000,
|
|
267
|
+
})
|
|
268
|
+
async riskyJob() {
|
|
269
|
+
try {
|
|
270
|
+
await this.performRiskyOperation();
|
|
271
|
+
} catch (error) {
|
|
272
|
+
console.error('Job failed:', error);
|
|
273
|
+
// Error will be retried automatically
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Use Cases
|
|
281
|
+
|
|
282
|
+
### Database Cleanup
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
@Injectable()
|
|
286
|
+
export class DatabaseCleanupService {
|
|
287
|
+
@Cron('0 2 * * *') // Every day at 2 AM
|
|
288
|
+
async cleanupOldRecords() {
|
|
289
|
+
const thirtyDaysAgo = new Date();
|
|
290
|
+
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
291
|
+
|
|
292
|
+
await this.db.logs.deleteMany({
|
|
293
|
+
where: { createdAt: { lt: thirtyDaysAgo } },
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
console.log('Old records cleaned up');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Report Generation
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
@Injectable()
|
|
305
|
+
export class ReportService {
|
|
306
|
+
@Cron('0 0 9 * * 1') // Every Monday at 9 AM
|
|
307
|
+
async generateWeeklyReport() {
|
|
308
|
+
const report = await this.analytics.generateWeeklyReport();
|
|
309
|
+
await this.email.send({
|
|
310
|
+
to: 'admin@example.com',
|
|
311
|
+
subject: 'Weekly Report',
|
|
312
|
+
body: report,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Cache Warming
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
@Injectable()
|
|
322
|
+
export class CacheService {
|
|
323
|
+
@Cron('0 */30 * * * *') // Every 30 minutes
|
|
324
|
+
async warmupCache() {
|
|
325
|
+
const popularProducts = await this.db.products.findMany({
|
|
326
|
+
where: { popular: true },
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
for (const product of popularProducts) {
|
|
330
|
+
await this.cache.set(`product:${product.id}`, product, 3600);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Health Monitoring
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
@Injectable()
|
|
340
|
+
export class HealthMonitor {
|
|
341
|
+
@Interval(30000) // Every 30 seconds
|
|
342
|
+
async checkServices() {
|
|
343
|
+
const services = ['database', 'redis', 'api'];
|
|
344
|
+
|
|
345
|
+
for (const service of services) {
|
|
346
|
+
const isHealthy = await this.checkServiceHealth(service);
|
|
347
|
+
if (!isHealthy) {
|
|
348
|
+
await this.alertService.sendAlert(`${service} is down!`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Data Synchronization
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
@Injectable()
|
|
359
|
+
export class SyncService {
|
|
360
|
+
@Cron('0 */5 * * * *') // Every 5 minutes
|
|
361
|
+
async syncData() {
|
|
362
|
+
const localData = await this.getLocalData();
|
|
363
|
+
const remoteData = await this.getRemoteData();
|
|
364
|
+
|
|
365
|
+
const diff = this.calculateDiff(localData, remoteData);
|
|
366
|
+
await this.applyChanges(diff);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Best Practices
|
|
372
|
+
|
|
373
|
+
1. **Use Named Jobs** - Always provide a name for easier management
|
|
374
|
+
2. **Handle Errors** - Implement proper error handling and logging
|
|
375
|
+
3. **Prevent Overlaps** - Enable `preventOverlap` for long-running jobs
|
|
376
|
+
4. **Set Appropriate Timezones** - Specify timezone for time-sensitive jobs
|
|
377
|
+
5. **Monitor Execution** - Track job execution and failures
|
|
378
|
+
6. **Test Thoroughly** - Test cron expressions before deploying
|
|
379
|
+
7. **Use Intervals for Simple Tasks** - Use `@Interval` for fixed-interval tasks
|
|
380
|
+
8. **Cleanup Resources** - Properly cleanup resources in job handlers
|
|
381
|
+
|
|
382
|
+
## API Reference
|
|
383
|
+
|
|
384
|
+
### Decorators
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
@Cron(expression: string, options?: CronOptions)
|
|
388
|
+
@Interval(milliseconds: number, options?: IntervalOptions)
|
|
389
|
+
@Timeout(milliseconds: number, options?: TimeoutOptions)
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### CronService
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
class CronService {
|
|
396
|
+
addCronJob(name: string, expression: string, callback: () => void): void;
|
|
397
|
+
getCronJob(name: string): CronJob;
|
|
398
|
+
deleteCronJob(name: string): void;
|
|
399
|
+
getCronJobs(): Map<string, CronJob>;
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### SchedulerRegistry
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
class SchedulerRegistry {
|
|
407
|
+
addCronJob(name: string, job: CronJob): void;
|
|
408
|
+
getCronJob(name: string): CronJob;
|
|
409
|
+
deleteCronJob(name: string): void;
|
|
410
|
+
getCronJobs(): Map<string, CronJob>;
|
|
411
|
+
addInterval(name: string, intervalId: NodeJS.Timeout): void;
|
|
412
|
+
deleteInterval(name: string): void;
|
|
413
|
+
addTimeout(name: string, timeoutId: NodeJS.Timeout): void;
|
|
414
|
+
deleteTimeout(name: string): void;
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Examples
|
|
419
|
+
|
|
420
|
+
See the [examples](../../example/src/cron) directory for complete working examples.
|
|
421
|
+
|
|
422
|
+
## Testing
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
npm test
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
## Contributing
|
|
429
|
+
|
|
430
|
+
Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
|
|
431
|
+
|
|
432
|
+
## License
|
|
433
|
+
|
|
434
|
+
MIT © [HazelJS](https://hazeljs.com)
|
|
435
|
+
|
|
436
|
+
## Links
|
|
437
|
+
|
|
438
|
+
- [Documentation](https://hazeljs.com/docs/packages/cron)
|
|
439
|
+
- [GitHub](https://github.com/hazel-js/hazeljs)
|
|
440
|
+
- [Issues](https://github.com/hazeljs/hazel-js/issues)
|
|
441
|
+
- [Discord](https://discord.gg/hazeljs)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { CronOptions } from './cron.types';
|
|
3
|
+
/**
|
|
4
|
+
* Metadata key for cron jobs
|
|
5
|
+
*/
|
|
6
|
+
export declare const CRON_METADATA_KEY: unique symbol;
|
|
7
|
+
/**
|
|
8
|
+
* Decorator to mark a method as a cron job
|
|
9
|
+
* @param options - Cron job options
|
|
10
|
+
*/
|
|
11
|
+
export declare function Cron(options: CronOptions): MethodDecorator;
|
|
12
|
+
/**
|
|
13
|
+
* Get cron job metadata from a class
|
|
14
|
+
*/
|
|
15
|
+
export declare function getCronMetadata(target: object): Array<{
|
|
16
|
+
target: object;
|
|
17
|
+
methodName: string;
|
|
18
|
+
options: CronOptions;
|
|
19
|
+
}>;
|
|
20
|
+
//# sourceMappingURL=cron.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron.decorator.d.ts","sourceRoot":"","sources":["../src/cron.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C;;GAEG;AACH,eAAO,MAAM,iBAAiB,eAAsB,CAAC;AAErD;;;GAGG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,eAAe,CAoB1D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,WAAW,CAAC;CACtB,CAAC,CAED"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CRON_METADATA_KEY = void 0;
|
|
4
|
+
exports.Cron = Cron;
|
|
5
|
+
exports.getCronMetadata = getCronMetadata;
|
|
6
|
+
require("reflect-metadata");
|
|
7
|
+
/**
|
|
8
|
+
* Metadata key for cron jobs
|
|
9
|
+
*/
|
|
10
|
+
exports.CRON_METADATA_KEY = Symbol('cron:jobs');
|
|
11
|
+
/**
|
|
12
|
+
* Decorator to mark a method as a cron job
|
|
13
|
+
* @param options - Cron job options
|
|
14
|
+
*/
|
|
15
|
+
function Cron(options) {
|
|
16
|
+
return (target, propertyKey, _descriptor) => {
|
|
17
|
+
// Get existing cron jobs or initialize empty array
|
|
18
|
+
const existingJobs = Reflect.getMetadata(exports.CRON_METADATA_KEY, target.constructor) || [];
|
|
19
|
+
// Add this job to the list
|
|
20
|
+
const jobMetadata = {
|
|
21
|
+
target,
|
|
22
|
+
methodName: propertyKey.toString(),
|
|
23
|
+
options: {
|
|
24
|
+
name: options.name || `${target.constructor.name}.${propertyKey.toString()}`,
|
|
25
|
+
...options,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
existingJobs.push(jobMetadata);
|
|
29
|
+
// Store metadata
|
|
30
|
+
Reflect.defineMetadata(exports.CRON_METADATA_KEY, existingJobs, target.constructor);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get cron job metadata from a class
|
|
35
|
+
*/
|
|
36
|
+
function getCronMetadata(target) {
|
|
37
|
+
return Reflect.getMetadata(exports.CRON_METADATA_KEY, target.constructor) || [];
|
|
38
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { CronService } from './cron.service';
|
|
2
|
+
/**
|
|
3
|
+
* Cron module options
|
|
4
|
+
*/
|
|
5
|
+
export interface CronModuleOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Whether this is a global module
|
|
8
|
+
* @default true
|
|
9
|
+
*/
|
|
10
|
+
isGlobal?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Cron module for HazelJS
|
|
14
|
+
* Provides scheduled job execution using cron expressions
|
|
15
|
+
*/
|
|
16
|
+
export declare class CronModule {
|
|
17
|
+
/**
|
|
18
|
+
* Configure cron module
|
|
19
|
+
*/
|
|
20
|
+
static forRoot(options?: CronModuleOptions): {
|
|
21
|
+
module: typeof CronModule;
|
|
22
|
+
providers: Array<typeof CronService>;
|
|
23
|
+
exports: Array<typeof CronService>;
|
|
24
|
+
global: boolean;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Manually register cron jobs from a provider instance
|
|
28
|
+
* Call this method after the provider has been instantiated
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const taskService = container.resolve(TaskService);
|
|
33
|
+
* CronModule.registerJobsFromProvider(taskService);
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
static registerJobsFromProvider(provider: object): void;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=cron.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron.module.d.ts","sourceRoot":"","sources":["../src/cron.module.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAK7C;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;GAGG;AACH,qBAIa,UAAU;IACrB;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,iBAAsB,GAAG;QAC/C,MAAM,EAAE,OAAO,UAAU,CAAC;QAC1B,SAAS,EAAE,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;QACrC,OAAO,EAAE,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;QACnC,MAAM,EAAE,OAAO,CAAC;KACjB;IAaD;;;;;;;;;OASG;IACH,MAAM,CAAC,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;CAgCxD"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
var CronModule_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.CronModule = void 0;
|
|
14
|
+
const core_1 = require("@hazeljs/core");
|
|
15
|
+
const cron_service_1 = require("./cron.service");
|
|
16
|
+
const cron_decorator_1 = require("./cron.decorator");
|
|
17
|
+
const core_2 = require("@hazeljs/core");
|
|
18
|
+
const core_3 = __importDefault(require("@hazeljs/core"));
|
|
19
|
+
/**
|
|
20
|
+
* Cron module for HazelJS
|
|
21
|
+
* Provides scheduled job execution using cron expressions
|
|
22
|
+
*/
|
|
23
|
+
let CronModule = CronModule_1 = class CronModule {
|
|
24
|
+
/**
|
|
25
|
+
* Configure cron module
|
|
26
|
+
*/
|
|
27
|
+
static forRoot(options = {}) {
|
|
28
|
+
const { isGlobal = true } = options;
|
|
29
|
+
core_3.default.info('Configuring cron module...');
|
|
30
|
+
return {
|
|
31
|
+
module: CronModule_1,
|
|
32
|
+
providers: [cron_service_1.CronService],
|
|
33
|
+
exports: [cron_service_1.CronService],
|
|
34
|
+
global: isGlobal,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Manually register cron jobs from a provider instance
|
|
39
|
+
* Call this method after the provider has been instantiated
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const taskService = container.resolve(TaskService);
|
|
44
|
+
* CronModule.registerJobsFromProvider(taskService);
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
static registerJobsFromProvider(provider) {
|
|
48
|
+
try {
|
|
49
|
+
const container = core_2.Container.getInstance();
|
|
50
|
+
const cronService = container.resolve(cron_service_1.CronService);
|
|
51
|
+
if (!cronService) {
|
|
52
|
+
core_3.default.warn('CronService not found in DI container');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const metadata = (0, cron_decorator_1.getCronMetadata)(provider);
|
|
56
|
+
if (metadata && metadata.length > 0) {
|
|
57
|
+
for (const job of metadata) {
|
|
58
|
+
const instance = provider;
|
|
59
|
+
const callback = instance[job.methodName];
|
|
60
|
+
if (typeof callback === 'function') {
|
|
61
|
+
cronService.registerJob(job.options.name || job.methodName, job.options.cronTime, callback.bind(instance), job.options);
|
|
62
|
+
core_3.default.info(`Registered cron job: ${job.options.name || job.methodName}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
core_3.default.error('Error registering cron jobs from provider:', error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
exports.CronModule = CronModule;
|
|
73
|
+
exports.CronModule = CronModule = CronModule_1 = __decorate([
|
|
74
|
+
(0, core_1.HazelModule)({
|
|
75
|
+
providers: [cron_service_1.CronService],
|
|
76
|
+
exports: [cron_service_1.CronService],
|
|
77
|
+
})
|
|
78
|
+
], CronModule);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CronOptions, CronJobStatus } from './cron.types';
|
|
2
|
+
/**
|
|
3
|
+
* Cron service for managing scheduled jobs
|
|
4
|
+
*/
|
|
5
|
+
export declare class CronService {
|
|
6
|
+
private jobs;
|
|
7
|
+
/**
|
|
8
|
+
* Register a new cron job
|
|
9
|
+
*/
|
|
10
|
+
registerJob(name: string, cronExpression: string, callback: () => void | Promise<void>, options?: CronOptions): void;
|
|
11
|
+
/**
|
|
12
|
+
* Delete a cron job
|
|
13
|
+
*/
|
|
14
|
+
deleteJob(name: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Start a specific job
|
|
17
|
+
*/
|
|
18
|
+
startJob(name: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Stop a specific job
|
|
21
|
+
*/
|
|
22
|
+
stopJob(name: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Enable a job
|
|
25
|
+
*/
|
|
26
|
+
enableJob(name: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Disable a job
|
|
29
|
+
*/
|
|
30
|
+
disableJob(name: string): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Get status of a specific job
|
|
33
|
+
*/
|
|
34
|
+
getJobStatus(name: string): CronJobStatus | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Get status of all jobs
|
|
37
|
+
*/
|
|
38
|
+
getAllJobStatuses(): CronJobStatus[];
|
|
39
|
+
/**
|
|
40
|
+
* Stop all jobs
|
|
41
|
+
*/
|
|
42
|
+
stopAll(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Start all jobs
|
|
45
|
+
*/
|
|
46
|
+
startAll(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Clear all jobs
|
|
49
|
+
*/
|
|
50
|
+
clearAll(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Get number of registered jobs
|
|
53
|
+
*/
|
|
54
|
+
getJobCount(): number;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=cron.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron.service.d.ts","sourceRoot":"","sources":["../src/cron.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA4J1D;;GAEG;AACH,qBACa,WAAW;IACtB,OAAO,CAAC,IAAI,CAA8B;IAE1C;;OAEG;IACH,WAAW,CACT,IAAI,EAAE,MAAM,EACZ,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACpC,OAAO,GAAE,WAA0C,GAClD,IAAI;IAeP;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAWhC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAU/B;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAU9B;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAUhC;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAUjC;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAKrD;;OAEG;IACH,iBAAiB,IAAI,aAAa,EAAE;IAIpC;;OAEG;IACH,OAAO,IAAI,IAAI;IAKf;;OAEG;IACH,QAAQ,IAAI,IAAI;IAKhB;;OAEG;IACH,QAAQ,IAAI,IAAI;IAMhB;;OAEG;IACH,WAAW,IAAI,MAAM;CAGtB"}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.CronService = void 0;
|
|
13
|
+
const core_1 = require("@hazeljs/core");
|
|
14
|
+
const core_2 = __importDefault(require("@hazeljs/core"));
|
|
15
|
+
const node_cron_1 = __importDefault(require("node-cron"));
|
|
16
|
+
/**
|
|
17
|
+
* Represents a scheduled cron job
|
|
18
|
+
* Uses node-cron for proper cron expression parsing and scheduling
|
|
19
|
+
*/
|
|
20
|
+
class CronJob {
|
|
21
|
+
constructor(name, cronExpression, callback, options) {
|
|
22
|
+
this.name = name;
|
|
23
|
+
this.cronExpression = cronExpression;
|
|
24
|
+
this.callback = callback;
|
|
25
|
+
this.options = options;
|
|
26
|
+
this.task = null;
|
|
27
|
+
this._isRunning = false;
|
|
28
|
+
this._runCount = 0;
|
|
29
|
+
this._enabled = true;
|
|
30
|
+
this._enabled = options.enabled !== false;
|
|
31
|
+
// node-cron uses 5-field (minute-level) or 6-field (second-level) expressions
|
|
32
|
+
// Validate the expression upfront
|
|
33
|
+
const expr = this.normalizeExpression(this.cronExpression);
|
|
34
|
+
if (!node_cron_1.default.validate(expr)) {
|
|
35
|
+
throw new Error(`Invalid cron expression: ${this.cronExpression}. ` +
|
|
36
|
+
`Format: second minute hour day-of-month month day-of-week`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Normalize expression for node-cron compatibility
|
|
41
|
+
* node-cron supports both 5-field (no seconds) and 6-field (with seconds)
|
|
42
|
+
*/
|
|
43
|
+
normalizeExpression(expression) {
|
|
44
|
+
return expression;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Start the cron job
|
|
48
|
+
*/
|
|
49
|
+
start() {
|
|
50
|
+
if (this.task) {
|
|
51
|
+
core_2.default.warn(`Cron job "${this.name}" is already running`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!this._enabled) {
|
|
55
|
+
core_2.default.warn(`Cron job "${this.name}" is disabled`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const expr = this.normalizeExpression(this.cronExpression);
|
|
59
|
+
this.task = node_cron_1.default.schedule(expr, async () => {
|
|
60
|
+
await this.execute();
|
|
61
|
+
}, {
|
|
62
|
+
scheduled: true,
|
|
63
|
+
timezone: this.options.timeZone,
|
|
64
|
+
});
|
|
65
|
+
// Run on init if specified
|
|
66
|
+
if (this.options.runOnInit) {
|
|
67
|
+
this.execute();
|
|
68
|
+
}
|
|
69
|
+
core_2.default.info(`Cron job "${this.name}" started with expression: ${this.cronExpression}`);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Stop the cron job
|
|
73
|
+
*/
|
|
74
|
+
stop() {
|
|
75
|
+
if (this.task) {
|
|
76
|
+
this.task.stop();
|
|
77
|
+
this.task = null;
|
|
78
|
+
this._nextExecution = undefined;
|
|
79
|
+
core_2.default.info(`Cron job "${this.name}" stopped`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Execute the job
|
|
84
|
+
*/
|
|
85
|
+
async execute() {
|
|
86
|
+
if (this._isRunning) {
|
|
87
|
+
core_2.default.warn(`Cron job "${this.name}" is already executing, skipping this run`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Check max runs
|
|
91
|
+
if (this.options.maxRuns && this._runCount >= this.options.maxRuns) {
|
|
92
|
+
core_2.default.info(`Cron job "${this.name}" reached max runs (${this.options.maxRuns}), stopping`);
|
|
93
|
+
this.stop();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this._isRunning = true;
|
|
97
|
+
this._lastExecution = new Date();
|
|
98
|
+
this._runCount++;
|
|
99
|
+
try {
|
|
100
|
+
core_2.default.debug(`Executing cron job "${this.name}" (run #${this._runCount})`);
|
|
101
|
+
await this.callback();
|
|
102
|
+
if (this.options.onComplete) {
|
|
103
|
+
this.options.onComplete();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
core_2.default.error(`Error executing cron job "${this.name}":`, error);
|
|
108
|
+
if (this.options.onError) {
|
|
109
|
+
this.options.onError(error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
this._isRunning = false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Enable the job
|
|
118
|
+
*/
|
|
119
|
+
enable() {
|
|
120
|
+
this._enabled = true;
|
|
121
|
+
core_2.default.info(`Cron job "${this.name}" enabled`);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Disable the job
|
|
125
|
+
*/
|
|
126
|
+
disable() {
|
|
127
|
+
this._enabled = false;
|
|
128
|
+
this.stop();
|
|
129
|
+
core_2.default.info(`Cron job "${this.name}" disabled`);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get job status
|
|
133
|
+
*/
|
|
134
|
+
getStatus() {
|
|
135
|
+
return {
|
|
136
|
+
name: this.name,
|
|
137
|
+
isRunning: this._isRunning,
|
|
138
|
+
lastExecution: this._lastExecution,
|
|
139
|
+
nextExecution: this._nextExecution,
|
|
140
|
+
runCount: this._runCount,
|
|
141
|
+
enabled: this._enabled,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Cron service for managing scheduled jobs
|
|
147
|
+
*/
|
|
148
|
+
let CronService = class CronService {
|
|
149
|
+
constructor() {
|
|
150
|
+
this.jobs = new Map();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Register a new cron job
|
|
154
|
+
*/
|
|
155
|
+
registerJob(name, cronExpression, callback, options = { cronTime: cronExpression }) {
|
|
156
|
+
if (this.jobs.has(name)) {
|
|
157
|
+
core_2.default.warn(`Cron job "${name}" already exists, replacing it`);
|
|
158
|
+
this.deleteJob(name);
|
|
159
|
+
}
|
|
160
|
+
const job = new CronJob(name, cronExpression, callback, options);
|
|
161
|
+
this.jobs.set(name, job);
|
|
162
|
+
// Auto-start if not explicitly disabled
|
|
163
|
+
if (options.enabled !== false) {
|
|
164
|
+
job.start();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Delete a cron job
|
|
169
|
+
*/
|
|
170
|
+
deleteJob(name) {
|
|
171
|
+
const job = this.jobs.get(name);
|
|
172
|
+
if (job) {
|
|
173
|
+
job.stop();
|
|
174
|
+
this.jobs.delete(name);
|
|
175
|
+
core_2.default.info(`Cron job "${name}" deleted`);
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Start a specific job
|
|
182
|
+
*/
|
|
183
|
+
startJob(name) {
|
|
184
|
+
const job = this.jobs.get(name);
|
|
185
|
+
if (job) {
|
|
186
|
+
job.start();
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
core_2.default.warn(`Cron job "${name}" not found`);
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Stop a specific job
|
|
194
|
+
*/
|
|
195
|
+
stopJob(name) {
|
|
196
|
+
const job = this.jobs.get(name);
|
|
197
|
+
if (job) {
|
|
198
|
+
job.stop();
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
core_2.default.warn(`Cron job "${name}" not found`);
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Enable a job
|
|
206
|
+
*/
|
|
207
|
+
enableJob(name) {
|
|
208
|
+
const job = this.jobs.get(name);
|
|
209
|
+
if (job) {
|
|
210
|
+
job.enable();
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
core_2.default.warn(`Cron job "${name}" not found`);
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Disable a job
|
|
218
|
+
*/
|
|
219
|
+
disableJob(name) {
|
|
220
|
+
const job = this.jobs.get(name);
|
|
221
|
+
if (job) {
|
|
222
|
+
job.disable();
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
core_2.default.warn(`Cron job "${name}" not found`);
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get status of a specific job
|
|
230
|
+
*/
|
|
231
|
+
getJobStatus(name) {
|
|
232
|
+
const job = this.jobs.get(name);
|
|
233
|
+
return job?.getStatus();
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Get status of all jobs
|
|
237
|
+
*/
|
|
238
|
+
getAllJobStatuses() {
|
|
239
|
+
return Array.from(this.jobs.values()).map((job) => job.getStatus());
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Stop all jobs
|
|
243
|
+
*/
|
|
244
|
+
stopAll() {
|
|
245
|
+
this.jobs.forEach((job) => job.stop());
|
|
246
|
+
core_2.default.info('All cron jobs stopped');
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Start all jobs
|
|
250
|
+
*/
|
|
251
|
+
startAll() {
|
|
252
|
+
this.jobs.forEach((job) => job.start());
|
|
253
|
+
core_2.default.info('All cron jobs started');
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Clear all jobs
|
|
257
|
+
*/
|
|
258
|
+
clearAll() {
|
|
259
|
+
this.stopAll();
|
|
260
|
+
this.jobs.clear();
|
|
261
|
+
core_2.default.info('All cron jobs cleared');
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Get number of registered jobs
|
|
265
|
+
*/
|
|
266
|
+
getJobCount() {
|
|
267
|
+
return this.jobs.size;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
exports.CronService = CronService;
|
|
271
|
+
exports.CronService = CronService = __decorate([
|
|
272
|
+
(0, core_1.Injectable)()
|
|
273
|
+
], CronService);
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron job types and interfaces
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Cron expression type
|
|
6
|
+
* Format: second minute hour day-of-month month day-of-week
|
|
7
|
+
* Examples:
|
|
8
|
+
* - '* * * * * *' - Every second
|
|
9
|
+
* - '0 * * * * *' - Every minute
|
|
10
|
+
* - '0 0 * * * *' - Every hour
|
|
11
|
+
* - '0 0 0 * * *' - Every day at midnight
|
|
12
|
+
* - '0 0 9 * * 1-5' - Every weekday at 9 AM
|
|
13
|
+
*/
|
|
14
|
+
export type CronExpression = string;
|
|
15
|
+
/**
|
|
16
|
+
* Cron job options
|
|
17
|
+
*/
|
|
18
|
+
export interface CronOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Name of the cron job (for logging and management)
|
|
21
|
+
*/
|
|
22
|
+
name?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Cron expression defining when the job should run
|
|
25
|
+
*/
|
|
26
|
+
cronTime: CronExpression;
|
|
27
|
+
/**
|
|
28
|
+
* Timezone for the cron job
|
|
29
|
+
* @default 'UTC'
|
|
30
|
+
*/
|
|
31
|
+
timeZone?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Whether to start the job immediately
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
runOnInit?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Whether the job is enabled
|
|
39
|
+
* @default true
|
|
40
|
+
*/
|
|
41
|
+
enabled?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Maximum number of times the job can run
|
|
44
|
+
*/
|
|
45
|
+
maxRuns?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Error handler for the job
|
|
48
|
+
*/
|
|
49
|
+
onError?: (error: Error) => void;
|
|
50
|
+
/**
|
|
51
|
+
* Callback when job completes
|
|
52
|
+
*/
|
|
53
|
+
onComplete?: () => void;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Cron job metadata
|
|
57
|
+
*/
|
|
58
|
+
export interface CronJobMetadata {
|
|
59
|
+
/**
|
|
60
|
+
* Target class
|
|
61
|
+
*/
|
|
62
|
+
target: object;
|
|
63
|
+
/**
|
|
64
|
+
* Method name
|
|
65
|
+
*/
|
|
66
|
+
methodName: string;
|
|
67
|
+
/**
|
|
68
|
+
* Cron options
|
|
69
|
+
*/
|
|
70
|
+
options: CronOptions;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Cron job status
|
|
74
|
+
*/
|
|
75
|
+
export interface CronJobStatus {
|
|
76
|
+
/**
|
|
77
|
+
* Job name
|
|
78
|
+
*/
|
|
79
|
+
name: string;
|
|
80
|
+
/**
|
|
81
|
+
* Whether the job is running
|
|
82
|
+
*/
|
|
83
|
+
isRunning: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Last execution time
|
|
86
|
+
*/
|
|
87
|
+
lastExecution?: Date;
|
|
88
|
+
/**
|
|
89
|
+
* Next execution time
|
|
90
|
+
*/
|
|
91
|
+
nextExecution?: Date;
|
|
92
|
+
/**
|
|
93
|
+
* Number of times the job has run
|
|
94
|
+
*/
|
|
95
|
+
runCount: number;
|
|
96
|
+
/**
|
|
97
|
+
* Whether the job is enabled
|
|
98
|
+
*/
|
|
99
|
+
enabled: boolean;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Predefined cron expressions
|
|
103
|
+
*/
|
|
104
|
+
export declare const CronExpression: {
|
|
105
|
+
/**
|
|
106
|
+
* Every second
|
|
107
|
+
*/
|
|
108
|
+
readonly EVERY_SECOND: "* * * * * *";
|
|
109
|
+
/**
|
|
110
|
+
* Every 5 seconds
|
|
111
|
+
*/
|
|
112
|
+
readonly EVERY_5_SECONDS: "*/5 * * * * *";
|
|
113
|
+
/**
|
|
114
|
+
* Every 10 seconds
|
|
115
|
+
*/
|
|
116
|
+
readonly EVERY_10_SECONDS: "*/10 * * * * *";
|
|
117
|
+
/**
|
|
118
|
+
* Every 30 seconds
|
|
119
|
+
*/
|
|
120
|
+
readonly EVERY_30_SECONDS: "*/30 * * * * *";
|
|
121
|
+
/**
|
|
122
|
+
* Every minute
|
|
123
|
+
*/
|
|
124
|
+
readonly EVERY_MINUTE: "0 * * * * *";
|
|
125
|
+
/**
|
|
126
|
+
* Every 5 minutes
|
|
127
|
+
*/
|
|
128
|
+
readonly EVERY_5_MINUTES: "0 */5 * * * *";
|
|
129
|
+
/**
|
|
130
|
+
* Every 10 minutes
|
|
131
|
+
*/
|
|
132
|
+
readonly EVERY_10_MINUTES: "0 */10 * * * *";
|
|
133
|
+
/**
|
|
134
|
+
* Every 30 minutes
|
|
135
|
+
*/
|
|
136
|
+
readonly EVERY_30_MINUTES: "0 */30 * * * *";
|
|
137
|
+
/**
|
|
138
|
+
* Every hour
|
|
139
|
+
*/
|
|
140
|
+
readonly EVERY_HOUR: "0 0 * * * *";
|
|
141
|
+
/**
|
|
142
|
+
* Every day at midnight
|
|
143
|
+
*/
|
|
144
|
+
readonly EVERY_DAY_AT_MIDNIGHT: "0 0 0 * * *";
|
|
145
|
+
/**
|
|
146
|
+
* Every day at noon
|
|
147
|
+
*/
|
|
148
|
+
readonly EVERY_DAY_AT_NOON: "0 0 12 * * *";
|
|
149
|
+
/**
|
|
150
|
+
* Every week (Sunday at midnight)
|
|
151
|
+
*/
|
|
152
|
+
readonly EVERY_WEEK: "0 0 0 * * 0";
|
|
153
|
+
/**
|
|
154
|
+
* Every weekday (Monday-Friday at midnight)
|
|
155
|
+
*/
|
|
156
|
+
readonly EVERY_WEEKDAY: "0 0 0 * * 1-5";
|
|
157
|
+
/**
|
|
158
|
+
* Every weekend (Saturday and Sunday at midnight)
|
|
159
|
+
*/
|
|
160
|
+
readonly EVERY_WEEKEND: "0 0 0 * * 0,6";
|
|
161
|
+
/**
|
|
162
|
+
* Every month (1st day at midnight)
|
|
163
|
+
*/
|
|
164
|
+
readonly EVERY_MONTH: "0 0 0 1 * *";
|
|
165
|
+
/**
|
|
166
|
+
* Every quarter (1st day of Jan, Apr, Jul, Oct at midnight)
|
|
167
|
+
*/
|
|
168
|
+
readonly EVERY_QUARTER: "0 0 0 1 */3 *";
|
|
169
|
+
/**
|
|
170
|
+
* Every year (Jan 1st at midnight)
|
|
171
|
+
*/
|
|
172
|
+
readonly EVERY_YEAR: "0 0 0 1 1 *";
|
|
173
|
+
};
|
|
174
|
+
//# sourceMappingURL=cron.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron.types.d.ts","sourceRoot":"","sources":["../src/cron.types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,QAAQ,EAAE,cAAc,CAAC;IAEzB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,OAAO,EAAE,WAAW,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,aAAa,CAAC,EAAE,IAAI,CAAC;IAErB;;OAEG;IACH,aAAa,CAAC,EAAE,IAAI,CAAC;IAErB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,eAAO,MAAM,cAAc;IACzB;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;CAEK,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cron job types and interfaces
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CronExpression = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Predefined cron expressions
|
|
9
|
+
*/
|
|
10
|
+
exports.CronExpression = {
|
|
11
|
+
/**
|
|
12
|
+
* Every second
|
|
13
|
+
*/
|
|
14
|
+
EVERY_SECOND: '* * * * * *',
|
|
15
|
+
/**
|
|
16
|
+
* Every 5 seconds
|
|
17
|
+
*/
|
|
18
|
+
EVERY_5_SECONDS: '*/5 * * * * *',
|
|
19
|
+
/**
|
|
20
|
+
* Every 10 seconds
|
|
21
|
+
*/
|
|
22
|
+
EVERY_10_SECONDS: '*/10 * * * * *',
|
|
23
|
+
/**
|
|
24
|
+
* Every 30 seconds
|
|
25
|
+
*/
|
|
26
|
+
EVERY_30_SECONDS: '*/30 * * * * *',
|
|
27
|
+
/**
|
|
28
|
+
* Every minute
|
|
29
|
+
*/
|
|
30
|
+
EVERY_MINUTE: '0 * * * * *',
|
|
31
|
+
/**
|
|
32
|
+
* Every 5 minutes
|
|
33
|
+
*/
|
|
34
|
+
EVERY_5_MINUTES: '0 */5 * * * *',
|
|
35
|
+
/**
|
|
36
|
+
* Every 10 minutes
|
|
37
|
+
*/
|
|
38
|
+
EVERY_10_MINUTES: '0 */10 * * * *',
|
|
39
|
+
/**
|
|
40
|
+
* Every 30 minutes
|
|
41
|
+
*/
|
|
42
|
+
EVERY_30_MINUTES: '0 */30 * * * *',
|
|
43
|
+
/**
|
|
44
|
+
* Every hour
|
|
45
|
+
*/
|
|
46
|
+
EVERY_HOUR: '0 0 * * * *',
|
|
47
|
+
/**
|
|
48
|
+
* Every day at midnight
|
|
49
|
+
*/
|
|
50
|
+
EVERY_DAY_AT_MIDNIGHT: '0 0 0 * * *',
|
|
51
|
+
/**
|
|
52
|
+
* Every day at noon
|
|
53
|
+
*/
|
|
54
|
+
EVERY_DAY_AT_NOON: '0 0 12 * * *',
|
|
55
|
+
/**
|
|
56
|
+
* Every week (Sunday at midnight)
|
|
57
|
+
*/
|
|
58
|
+
EVERY_WEEK: '0 0 0 * * 0',
|
|
59
|
+
/**
|
|
60
|
+
* Every weekday (Monday-Friday at midnight)
|
|
61
|
+
*/
|
|
62
|
+
EVERY_WEEKDAY: '0 0 0 * * 1-5',
|
|
63
|
+
/**
|
|
64
|
+
* Every weekend (Saturday and Sunday at midnight)
|
|
65
|
+
*/
|
|
66
|
+
EVERY_WEEKEND: '0 0 0 * * 0,6',
|
|
67
|
+
/**
|
|
68
|
+
* Every month (1st day at midnight)
|
|
69
|
+
*/
|
|
70
|
+
EVERY_MONTH: '0 0 0 1 * *',
|
|
71
|
+
/**
|
|
72
|
+
* Every quarter (1st day of Jan, Apr, Jul, Oct at midnight)
|
|
73
|
+
*/
|
|
74
|
+
EVERY_QUARTER: '0 0 0 1 */3 *',
|
|
75
|
+
/**
|
|
76
|
+
* Every year (Jan 1st at midnight)
|
|
77
|
+
*/
|
|
78
|
+
EVERY_YEAR: '0 0 0 1 1 *',
|
|
79
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hazeljs/cron - Cron job scheduling module for HazelJS
|
|
3
|
+
*/
|
|
4
|
+
export { CronModule, type CronModuleOptions } from './cron.module';
|
|
5
|
+
export { CronService } from './cron.service';
|
|
6
|
+
export { Cron, getCronMetadata } from './cron.decorator';
|
|
7
|
+
export type { CronOptions, CronJobMetadata, CronJobStatus } from './cron.types';
|
|
8
|
+
export { CronExpression } from './cron.types';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @hazeljs/cron - Cron job scheduling module for HazelJS
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CronExpression = exports.getCronMetadata = exports.Cron = exports.CronService = exports.CronModule = void 0;
|
|
7
|
+
var cron_module_1 = require("./cron.module");
|
|
8
|
+
Object.defineProperty(exports, "CronModule", { enumerable: true, get: function () { return cron_module_1.CronModule; } });
|
|
9
|
+
var cron_service_1 = require("./cron.service");
|
|
10
|
+
Object.defineProperty(exports, "CronService", { enumerable: true, get: function () { return cron_service_1.CronService; } });
|
|
11
|
+
var cron_decorator_1 = require("./cron.decorator");
|
|
12
|
+
Object.defineProperty(exports, "Cron", { enumerable: true, get: function () { return cron_decorator_1.Cron; } });
|
|
13
|
+
Object.defineProperty(exports, "getCronMetadata", { enumerable: true, get: function () { return cron_decorator_1.getCronMetadata; } });
|
|
14
|
+
var cron_types_1 = require("./cron.types");
|
|
15
|
+
Object.defineProperty(exports, "CronExpression", { enumerable: true, get: function () { return cron_types_1.CronExpression; } });
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hazeljs/cron",
|
|
3
|
+
"version": "0.2.0-beta.1",
|
|
4
|
+
"description": "Cron job scheduling module for HazelJS framework",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "jest --coverage --passWithNoTests",
|
|
13
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
14
|
+
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
|
15
|
+
"clean": "rm -rf dist"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@hazeljs/core": "file:../core",
|
|
19
|
+
"node-cron": "^3.0.3"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^20.17.50",
|
|
23
|
+
"@types/node-cron": "^3.0.11",
|
|
24
|
+
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
|
25
|
+
"@typescript-eslint/parser": "^8.18.2",
|
|
26
|
+
"eslint": "^8.56.0",
|
|
27
|
+
"jest": "^29.7.0",
|
|
28
|
+
"ts-jest": "^29.1.2",
|
|
29
|
+
"typescript": "^5.3.3"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/hazel-js/hazeljs.git",
|
|
37
|
+
"directory": "packages/cron"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"hazeljs",
|
|
41
|
+
"cron",
|
|
42
|
+
"scheduler",
|
|
43
|
+
"jobs"
|
|
44
|
+
],
|
|
45
|
+
"author": "Muhammad Arslan <marslan@hazeljs.com>",
|
|
46
|
+
"license": "MIT"
|
|
47
|
+
}
|