nodejs-task-scheduler 1.0.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.
- package/.claude/settings.local.json +20 -0
- package/.github/workflows/ci.yml +266 -0
- package/.github/workflows/release.yml +117 -0
- package/CHANGELOG.md +43 -0
- package/README.md +653 -0
- package/dist/__tests__/setup.d.ts +2 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +24 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/decorators/index.d.ts +80 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +171 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/metadata.d.ts +59 -0
- package/dist/decorators/metadata.d.ts.map +1 -0
- package/dist/decorators/metadata.js +68 -0
- package/dist/decorators/metadata.js.map +1 -0
- package/dist/decorators/registry.d.ts +42 -0
- package/dist/decorators/registry.d.ts.map +1 -0
- package/dist/decorators/registry.js +182 -0
- package/dist/decorators/registry.js.map +1 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +198 -0
- package/dist/index.js.map +1 -0
- package/dist/queue/index.d.ts +19 -0
- package/dist/queue/index.d.ts.map +1 -0
- package/dist/queue/index.js +89 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/scheduler/index.d.ts +13 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +102 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/types/index.d.ts +63 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +12 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/load-balancer.d.ts +36 -0
- package/dist/utils/load-balancer.d.ts.map +1 -0
- package/dist/utils/load-balancer.js +158 -0
- package/dist/utils/load-balancer.js.map +1 -0
- package/dist/utils/rabbitmq.d.ts +18 -0
- package/dist/utils/rabbitmq.d.ts.map +1 -0
- package/dist/utils/rabbitmq.js +114 -0
- package/dist/utils/rabbitmq.js.map +1 -0
- package/dist/worker/index.d.ts +20 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +138 -0
- package/dist/worker/index.js.map +1 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
# Node.js Task Scheduler
|
|
2
|
+
|
|
3
|
+
[](https://github.com/your-username/nodejs-task-scheduler/actions/workflows/ci.yml)
|
|
4
|
+
[](https://badge.fury.io/js/nodejs-task-scheduler)
|
|
5
|
+
|
|
6
|
+
A distributed task scheduler for Node.js using RabbitMQ with support for cron jobs, direct job execution, and load balancing across multiple nodes.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Distributed Job Processing**: Jobs are distributed across multiple worker nodes using RabbitMQ
|
|
11
|
+
- **Cron Job Support**: Schedule recurring jobs using cron expressions
|
|
12
|
+
- **Direct Job Execution**: Execute jobs immediately on available nodes
|
|
13
|
+
- **Load Balancing**: Automatically selects the least loaded available node
|
|
14
|
+
- **Singleton & Multi-instance Workers**: Support for both single-threaded and concurrent job processing
|
|
15
|
+
- **Retry Logic**: Configurable retry attempts with exponential backoff
|
|
16
|
+
- **Dead Letter Queue**: Failed jobs are moved to a dead letter queue for inspection
|
|
17
|
+
- **TypeScript Support**: Full TypeScript support with type definitions
|
|
18
|
+
- **Auto-reconnection**: Automatic reconnection to RabbitMQ on connection loss
|
|
19
|
+
- **Health Monitoring**: Built-in heartbeat system for node health monitoring
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
### From npm (when published)
|
|
24
|
+
```bash
|
|
25
|
+
npm install nodejs-task-scheduler
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### From GitHub Packages
|
|
29
|
+
```bash
|
|
30
|
+
npm install @theduchruben/nodejs-task-scheduler
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Development Setup
|
|
34
|
+
```bash
|
|
35
|
+
git clone https://github.com/your-username/nodejs-task-scheduler.git
|
|
36
|
+
cd nodejs-task-scheduler
|
|
37
|
+
npm install
|
|
38
|
+
npm run build
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Prerequisites
|
|
42
|
+
|
|
43
|
+
- **Node.js**: Version 16 or higher
|
|
44
|
+
- **RabbitMQ**: Running RabbitMQ server (local or remote)
|
|
45
|
+
- **TypeScript**: For development (automatically handled by npm scripts)
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { TaskScheduler } from './src';
|
|
51
|
+
|
|
52
|
+
// Initialize the scheduler
|
|
53
|
+
const scheduler = new TaskScheduler({
|
|
54
|
+
url: 'amqp://localhost:5672'
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await scheduler.initialize();
|
|
58
|
+
|
|
59
|
+
// Create a worker
|
|
60
|
+
await scheduler.createWorker({
|
|
61
|
+
name: 'email-worker',
|
|
62
|
+
concurrency: 1, // Singleton worker
|
|
63
|
+
queues: ['email-jobs'],
|
|
64
|
+
handlers: {
|
|
65
|
+
'send-email': async (data) => {
|
|
66
|
+
console.log('Sending email to:', data.email);
|
|
67
|
+
// Email sending logic here
|
|
68
|
+
return { success: true };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Schedule a direct job
|
|
74
|
+
await scheduler.scheduleJob({
|
|
75
|
+
id: 'job-1',
|
|
76
|
+
name: 'Send Welcome Email',
|
|
77
|
+
handler: 'send-email',
|
|
78
|
+
data: { email: 'user@example.com' }
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Schedule a cron job
|
|
82
|
+
await scheduler.scheduleCronJob({
|
|
83
|
+
id: 'daily-report',
|
|
84
|
+
name: 'Daily Report',
|
|
85
|
+
handler: 'generate-report',
|
|
86
|
+
schedule: '0 9 * * *', // Every day at 9 AM
|
|
87
|
+
data: { reportType: 'daily' }
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Configuration
|
|
92
|
+
|
|
93
|
+
### Connection Configuration
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
const connectionConfig = {
|
|
97
|
+
url: 'amqp://localhost:5672',
|
|
98
|
+
options: {
|
|
99
|
+
heartbeat: 60,
|
|
100
|
+
// Other amqplib connection options
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Worker Configuration
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const workerConfig = {
|
|
109
|
+
name: 'my-worker',
|
|
110
|
+
concurrency: 5, // Max concurrent jobs (1 for singleton)
|
|
111
|
+
queues: ['queue1', 'queue2'],
|
|
112
|
+
handlers: {
|
|
113
|
+
'job-type-1': async (data) => {
|
|
114
|
+
// Job handler logic
|
|
115
|
+
return { success: true, data: result };
|
|
116
|
+
},
|
|
117
|
+
'job-type-2': async (data) => {
|
|
118
|
+
// Another job handler
|
|
119
|
+
return { success: false, error: 'Something went wrong' };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Job Configuration
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// Direct job
|
|
129
|
+
const jobConfig = {
|
|
130
|
+
id: 'unique-job-id',
|
|
131
|
+
name: 'Job Name',
|
|
132
|
+
handler: 'job-type-1',
|
|
133
|
+
data: { key: 'value' },
|
|
134
|
+
priority: 5, // Higher number = higher priority
|
|
135
|
+
attempts: 3, // Max retry attempts
|
|
136
|
+
backoff: {
|
|
137
|
+
type: 'exponential',
|
|
138
|
+
delay: 1000 // Initial delay in ms
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Cron job
|
|
143
|
+
const cronJobConfig = {
|
|
144
|
+
...jobConfig,
|
|
145
|
+
schedule: '0 */6 * * *', // Every 6 hours
|
|
146
|
+
timezone: 'America/New_York'
|
|
147
|
+
};
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## API Reference
|
|
151
|
+
|
|
152
|
+
### TaskScheduler
|
|
153
|
+
|
|
154
|
+
#### Methods
|
|
155
|
+
|
|
156
|
+
- `initialize()`: Initialize the scheduler and connect to RabbitMQ
|
|
157
|
+
- `shutdown()`: Gracefully shutdown all workers and connections
|
|
158
|
+
- `createWorker(config)`: Create and start a new worker
|
|
159
|
+
- `scheduleJob(config)`: Schedule a job for immediate execution
|
|
160
|
+
- `scheduleCronJob(config)`: Schedule a recurring cron job
|
|
161
|
+
- `cancelCronJob(jobId)`: Cancel a scheduled cron job
|
|
162
|
+
- `register(instance)`: Register a class instance with decorators
|
|
163
|
+
- `executeJobMethod(className, methodName, data)`: Execute a job method from registered class
|
|
164
|
+
- `getNodeInfo()`: Get information about the current node and active nodes
|
|
165
|
+
- `getWorkerStatus(workerName)`: Get status of a specific worker
|
|
166
|
+
- `getCronJobs()`: Get list of active cron jobs
|
|
167
|
+
- `getRegisteredClasses()`: Get information about registered decorator classes
|
|
168
|
+
|
|
169
|
+
### Decorators
|
|
170
|
+
|
|
171
|
+
#### Job Decorators
|
|
172
|
+
- `@Job(options?)`: Mark a method as a job handler
|
|
173
|
+
- `@CronJob(options)`: Mark a method as a cron job handler
|
|
174
|
+
- `@SingletonJob(options?)`: Mark a method as singleton job (concurrency: 1)
|
|
175
|
+
- `@HighPriorityJob(options?)`: Mark a method as high priority job
|
|
176
|
+
- `@LowPriorityJob(options?)`: Mark a method as low priority job
|
|
177
|
+
- `@Retry(attempts, type, delay)`: Add retry configuration to a job
|
|
178
|
+
|
|
179
|
+
#### Class Decorators
|
|
180
|
+
- `@Worker(options?)`: Define worker configuration for a class
|
|
181
|
+
- `@Queue(options)`: Define queue configuration for a class
|
|
182
|
+
|
|
183
|
+
### Job Handlers
|
|
184
|
+
|
|
185
|
+
Job handlers are async functions that process job data:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
const handler = async (data: any): Promise<JobResult> => {
|
|
189
|
+
try {
|
|
190
|
+
// Process job data
|
|
191
|
+
const result = await processData(data);
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
success: true,
|
|
195
|
+
data: result
|
|
196
|
+
};
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return {
|
|
199
|
+
success: false,
|
|
200
|
+
error: error.message
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Load Balancing
|
|
207
|
+
|
|
208
|
+
The scheduler automatically distributes jobs to the least loaded available nodes. Nodes communicate via heartbeat messages to track their status and current load.
|
|
209
|
+
|
|
210
|
+
## Error Handling
|
|
211
|
+
|
|
212
|
+
- Failed jobs are automatically retried based on the `attempts` configuration
|
|
213
|
+
- Backoff strategies include fixed delay and exponential backoff
|
|
214
|
+
- Jobs that exceed max attempts are moved to a dead letter queue
|
|
215
|
+
- Connection failures trigger automatic reconnection attempts
|
|
216
|
+
|
|
217
|
+
## Docker Setup
|
|
218
|
+
|
|
219
|
+
### Using Docker Compose (Recommended for Testing)
|
|
220
|
+
|
|
221
|
+
Create a `docker-compose.yml` file:
|
|
222
|
+
|
|
223
|
+
```yaml
|
|
224
|
+
version: '3.8'
|
|
225
|
+
services:
|
|
226
|
+
rabbitmq:
|
|
227
|
+
image: rabbitmq:3-management
|
|
228
|
+
container_name: rabbitmq
|
|
229
|
+
ports:
|
|
230
|
+
- "5672:5672"
|
|
231
|
+
- "15672:15672"
|
|
232
|
+
environment:
|
|
233
|
+
- RABBITMQ_DEFAULT_USER=admin
|
|
234
|
+
- RABBITMQ_DEFAULT_PASS=password
|
|
235
|
+
volumes:
|
|
236
|
+
- rabbitmq_data:/var/lib/rabbitmq
|
|
237
|
+
|
|
238
|
+
scheduler-node-1:
|
|
239
|
+
build: .
|
|
240
|
+
depends_on:
|
|
241
|
+
- rabbitmq
|
|
242
|
+
environment:
|
|
243
|
+
- RABBITMQ_URL=amqp://admin:password@rabbitmq:5672
|
|
244
|
+
- NODE_NAME=scheduler-1
|
|
245
|
+
volumes:
|
|
246
|
+
- ./examples:/app/examples
|
|
247
|
+
|
|
248
|
+
scheduler-node-2:
|
|
249
|
+
build: .
|
|
250
|
+
depends_on:
|
|
251
|
+
- rabbitmq
|
|
252
|
+
environment:
|
|
253
|
+
- RABBITMQ_URL=amqp://admin:password@rabbitmq:5672
|
|
254
|
+
- NODE_NAME=scheduler-2
|
|
255
|
+
volumes:
|
|
256
|
+
- ./examples:/app/examples
|
|
257
|
+
|
|
258
|
+
volumes:
|
|
259
|
+
rabbitmq_data:
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Start the services:
|
|
263
|
+
```bash
|
|
264
|
+
docker-compose up -d
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### RabbitMQ Management UI
|
|
268
|
+
Access the RabbitMQ management interface at http://localhost:15672
|
|
269
|
+
- Username: admin
|
|
270
|
+
- Password: password
|
|
271
|
+
|
|
272
|
+
## Complete Usage Examples
|
|
273
|
+
|
|
274
|
+
### Basic Email Service Example
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { TaskScheduler } from 'nodejs-task-scheduler';
|
|
278
|
+
|
|
279
|
+
const scheduler = new TaskScheduler({
|
|
280
|
+
url: process.env.RABBITMQ_URL || 'amqp://localhost:5672'
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
async function setupEmailService() {
|
|
284
|
+
await scheduler.initialize();
|
|
285
|
+
|
|
286
|
+
// Create email worker with singleton processing
|
|
287
|
+
await scheduler.createWorker({
|
|
288
|
+
name: 'email-worker',
|
|
289
|
+
concurrency: 1, // Process one email at a time
|
|
290
|
+
queues: ['email-jobs'],
|
|
291
|
+
handlers: {
|
|
292
|
+
'send-welcome-email': async (data) => {
|
|
293
|
+
console.log(`Sending welcome email to: ${data.email}`);
|
|
294
|
+
|
|
295
|
+
// Simulate email sending
|
|
296
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
297
|
+
|
|
298
|
+
if (Math.random() > 0.9) {
|
|
299
|
+
return { success: false, error: 'Email service temporarily unavailable' };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
success: true,
|
|
304
|
+
data: { messageId: `msg_${Date.now()}` }
|
|
305
|
+
};
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
'send-notification': async (data) => {
|
|
309
|
+
console.log(`Sending notification: ${data.message}`);
|
|
310
|
+
return { success: true };
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Schedule immediate jobs
|
|
316
|
+
await scheduler.scheduleJob({
|
|
317
|
+
id: 'welcome-123',
|
|
318
|
+
name: 'Welcome Email',
|
|
319
|
+
handler: 'send-welcome-email',
|
|
320
|
+
data: {
|
|
321
|
+
email: 'user@example.com',
|
|
322
|
+
name: 'John Doe'
|
|
323
|
+
},
|
|
324
|
+
attempts: 3,
|
|
325
|
+
backoff: {
|
|
326
|
+
type: 'exponential',
|
|
327
|
+
delay: 2000
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Schedule daily email digest
|
|
332
|
+
await scheduler.scheduleCronJob({
|
|
333
|
+
id: 'daily-digest',
|
|
334
|
+
name: 'Daily Email Digest',
|
|
335
|
+
handler: 'send-notification',
|
|
336
|
+
schedule: '0 9 * * *', // 9 AM every day
|
|
337
|
+
timezone: 'America/New_York',
|
|
338
|
+
data: {
|
|
339
|
+
message: 'Your daily digest is ready!'
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
setupEmailService().catch(console.error);
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Decorator-Based Architecture (Recommended)
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
import 'reflect-metadata';
|
|
351
|
+
import {
|
|
352
|
+
TaskScheduler,
|
|
353
|
+
Job,
|
|
354
|
+
CronJob,
|
|
355
|
+
Worker,
|
|
356
|
+
Queue,
|
|
357
|
+
SingletonJob,
|
|
358
|
+
HighPriorityJob,
|
|
359
|
+
Retry
|
|
360
|
+
} from 'nodejs-task-scheduler';
|
|
361
|
+
|
|
362
|
+
@Worker({ name: 'email-service', concurrency: 1 })
|
|
363
|
+
@Queue({ name: 'email-queue', durable: true })
|
|
364
|
+
class EmailService {
|
|
365
|
+
|
|
366
|
+
@SingletonJob({ name: 'send-email' })
|
|
367
|
+
@Retry(3, 'exponential', 2000)
|
|
368
|
+
async sendEmail(data: { to: string; subject: string; body: string }) {
|
|
369
|
+
console.log(`📧 Sending email to: ${data.to}`);
|
|
370
|
+
|
|
371
|
+
// Simulate email sending
|
|
372
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
373
|
+
|
|
374
|
+
return { messageId: `msg_${Date.now()}`, sentAt: new Date() };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
@CronJob({
|
|
378
|
+
schedule: '0 9 * * *', // 9 AM every day
|
|
379
|
+
name: 'daily-digest',
|
|
380
|
+
timezone: 'America/New_York'
|
|
381
|
+
})
|
|
382
|
+
async sendDailyDigest() {
|
|
383
|
+
console.log('📰 Sending daily digest emails...');
|
|
384
|
+
// Implementation here
|
|
385
|
+
return { digestSent: true };
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
@Worker({ name: 'data-processor', concurrency: 4 })
|
|
390
|
+
class DataProcessingService {
|
|
391
|
+
|
|
392
|
+
@Job({ name: 'process-user-data', priority: 6 })
|
|
393
|
+
@Retry(2, 'exponential', 1000)
|
|
394
|
+
async processUserData(data: { userId: string; records: any[] }) {
|
|
395
|
+
console.log(`🔄 Processing data for user: ${data.userId}`);
|
|
396
|
+
// Processing logic here
|
|
397
|
+
return { processed: true, recordCount: data.records.length };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
@HighPriorityJob({ name: 'process-urgent-data' })
|
|
401
|
+
async processUrgentData(data: { alertId: string }) {
|
|
402
|
+
console.log(`🚨 Processing urgent data: ${data.alertId}`);
|
|
403
|
+
// Urgent processing logic
|
|
404
|
+
return { processed: true, urgent: true };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
@CronJob({ schedule: '0 2 * * *', name: 'daily-cleanup' })
|
|
408
|
+
async dailyCleanup() {
|
|
409
|
+
console.log('🌙 Running daily cleanup...');
|
|
410
|
+
return { cleanedUp: true };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Usage
|
|
415
|
+
async function main() {
|
|
416
|
+
const scheduler = new TaskScheduler({
|
|
417
|
+
url: process.env.RABBITMQ_URL || 'amqp://localhost:5672'
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
await scheduler.initialize();
|
|
421
|
+
|
|
422
|
+
// Register services - workers and cron jobs are automatically created
|
|
423
|
+
await scheduler.register(new EmailService());
|
|
424
|
+
await scheduler.register(new DataProcessingService());
|
|
425
|
+
|
|
426
|
+
// Execute jobs directly
|
|
427
|
+
await scheduler.executeJobMethod('EmailService', 'sendEmail', {
|
|
428
|
+
to: 'user@example.com',
|
|
429
|
+
subject: 'Welcome!',
|
|
430
|
+
body: 'Thanks for joining!'
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
await scheduler.executeJobMethod('DataProcessingService', 'processUserData', {
|
|
434
|
+
userId: 'user123',
|
|
435
|
+
records: [{ id: 1, data: 'sample' }]
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Multi-Service Architecture Example (Traditional)
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
import { TaskScheduler } from 'nodejs-task-scheduler';
|
|
444
|
+
|
|
445
|
+
class DataProcessingService {
|
|
446
|
+
private scheduler: TaskScheduler;
|
|
447
|
+
|
|
448
|
+
constructor() {
|
|
449
|
+
this.scheduler = new TaskScheduler({
|
|
450
|
+
url: process.env.RABBITMQ_URL || 'amqp://localhost:5672'
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async start() {
|
|
455
|
+
await this.scheduler.initialize();
|
|
456
|
+
|
|
457
|
+
// High-throughput data processing worker
|
|
458
|
+
await this.scheduler.createWorker({
|
|
459
|
+
name: 'data-processor',
|
|
460
|
+
concurrency: 5, // Process 5 jobs concurrently
|
|
461
|
+
queues: ['data-processing', 'analytics'],
|
|
462
|
+
handlers: {
|
|
463
|
+
'process-user-data': this.processUserData.bind(this),
|
|
464
|
+
'generate-analytics': this.generateAnalytics.bind(this),
|
|
465
|
+
'cleanup-old-data': this.cleanupOldData.bind(this)
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Critical operations worker (singleton)
|
|
470
|
+
await this.scheduler.createWorker({
|
|
471
|
+
name: 'critical-ops',
|
|
472
|
+
concurrency: 1,
|
|
473
|
+
queues: ['critical-operations'],
|
|
474
|
+
handlers: {
|
|
475
|
+
'backup-database': this.backupDatabase.bind(this),
|
|
476
|
+
'update-system-config': this.updateSystemConfig.bind(this)
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Schedule recurring maintenance tasks
|
|
481
|
+
await this.scheduleMaintenance();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
private async processUserData(data: any) {
|
|
485
|
+
console.log(`Processing data for user: ${data.userId}`);
|
|
486
|
+
|
|
487
|
+
// Simulate data processing
|
|
488
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
success: true,
|
|
492
|
+
data: { processedRecords: data.records?.length || 0 }
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
private async generateAnalytics(data: any) {
|
|
497
|
+
console.log(`Generating analytics report: ${data.reportType}`);
|
|
498
|
+
|
|
499
|
+
// Simulate analytics generation
|
|
500
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
success: true,
|
|
504
|
+
data: { reportUrl: `https://reports.example.com/${data.reportType}` }
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
private async cleanupOldData(data: any) {
|
|
509
|
+
console.log(`Cleaning up data older than: ${data.days} days`);
|
|
510
|
+
|
|
511
|
+
// Simulate cleanup
|
|
512
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
513
|
+
|
|
514
|
+
return {
|
|
515
|
+
success: true,
|
|
516
|
+
data: { deletedRecords: Math.floor(Math.random() * 1000) }
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private async backupDatabase(data: any) {
|
|
521
|
+
console.log('Starting database backup...');
|
|
522
|
+
|
|
523
|
+
// Simulate backup
|
|
524
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
525
|
+
|
|
526
|
+
return {
|
|
527
|
+
success: true,
|
|
528
|
+
data: { backupSize: '2.5GB', location: '/backups/2024-01-01.sql' }
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
private async updateSystemConfig(data: any) {
|
|
533
|
+
console.log(`Updating system config: ${data.configKey}`);
|
|
534
|
+
|
|
535
|
+
return { success: true };
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
private async scheduleMaintenance() {
|
|
539
|
+
// Daily cleanup at 2 AM
|
|
540
|
+
await this.scheduler.scheduleCronJob({
|
|
541
|
+
id: 'daily-cleanup',
|
|
542
|
+
name: 'Daily Data Cleanup',
|
|
543
|
+
handler: 'cleanup-old-data',
|
|
544
|
+
schedule: '0 2 * * *',
|
|
545
|
+
data: { days: 30 }
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
// Weekly database backup on Sundays at 3 AM
|
|
549
|
+
await this.scheduler.scheduleCronJob({
|
|
550
|
+
id: 'weekly-backup',
|
|
551
|
+
name: 'Weekly Database Backup',
|
|
552
|
+
handler: 'backup-database',
|
|
553
|
+
schedule: '0 3 * * 0',
|
|
554
|
+
data: { type: 'full' }
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// Generate daily analytics report at 6 AM
|
|
558
|
+
await this.scheduler.scheduleCronJob({
|
|
559
|
+
id: 'daily-analytics',
|
|
560
|
+
name: 'Daily Analytics Report',
|
|
561
|
+
handler: 'generate-analytics',
|
|
562
|
+
schedule: '0 6 * * *',
|
|
563
|
+
data: { reportType: 'daily' }
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async processUserSignup(userId: string, userData: any) {
|
|
568
|
+
// Schedule user data processing
|
|
569
|
+
return await this.scheduler.scheduleJob({
|
|
570
|
+
id: `user-signup-${userId}`,
|
|
571
|
+
name: 'Process User Signup',
|
|
572
|
+
handler: 'process-user-data',
|
|
573
|
+
data: { userId, records: userData },
|
|
574
|
+
priority: 5
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async shutdown() {
|
|
579
|
+
await this.scheduler.shutdown();
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Usage
|
|
584
|
+
const service = new DataProcessingService();
|
|
585
|
+
service.start().then(() => {
|
|
586
|
+
console.log('Data processing service started');
|
|
587
|
+
|
|
588
|
+
// Example: Process a new user signup
|
|
589
|
+
service.processUserSignup('user123', { name: 'John', email: 'john@example.com' });
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// Graceful shutdown
|
|
593
|
+
process.on('SIGINT', async () => {
|
|
594
|
+
console.log('Shutting down gracefully...');
|
|
595
|
+
await service.shutdown();
|
|
596
|
+
process.exit(0);
|
|
597
|
+
});
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
## Examples
|
|
601
|
+
|
|
602
|
+
```typescript
|
|
603
|
+
// Node 1 - Scheduler + Worker
|
|
604
|
+
const scheduler1 = new TaskScheduler({ url: 'amqp://localhost:5672' });
|
|
605
|
+
await scheduler1.initialize();
|
|
606
|
+
|
|
607
|
+
await scheduler1.createWorker({
|
|
608
|
+
name: 'worker-1',
|
|
609
|
+
concurrency: 3,
|
|
610
|
+
queues: ['tasks'],
|
|
611
|
+
handlers: { 'process': processHandler }
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// Node 2 - Worker Only
|
|
615
|
+
const scheduler2 = new TaskScheduler({ url: 'amqp://localhost:5672' });
|
|
616
|
+
await scheduler2.initialize();
|
|
617
|
+
|
|
618
|
+
await scheduler2.createWorker({
|
|
619
|
+
name: 'worker-2',
|
|
620
|
+
concurrency: 5,
|
|
621
|
+
queues: ['tasks'],
|
|
622
|
+
handlers: { 'process': processHandler }
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
// Jobs scheduled on Node 1 will be distributed between both workers
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### Singleton Worker
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
// Ensures only one job runs at a time
|
|
632
|
+
await scheduler.createWorker({
|
|
633
|
+
name: 'singleton-worker',
|
|
634
|
+
concurrency: 1,
|
|
635
|
+
queues: ['critical-tasks'],
|
|
636
|
+
handlers: {
|
|
637
|
+
'critical-job': async (data) => {
|
|
638
|
+
// Only one instance of this job runs at a time
|
|
639
|
+
return { success: true };
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
## Requirements
|
|
646
|
+
|
|
647
|
+
- Node.js 16+
|
|
648
|
+
- RabbitMQ server
|
|
649
|
+
- TypeScript (for development)
|
|
650
|
+
|
|
651
|
+
## License
|
|
652
|
+
|
|
653
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/__tests__/setup.ts"],"names":[],"mappings":"AACA,OAAO,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// Test setup file
|
|
4
|
+
require("reflect-metadata");
|
|
5
|
+
// Suppress console logs during tests unless debugging
|
|
6
|
+
if (!process.env.DEBUG_TESTS) {
|
|
7
|
+
global.console = {
|
|
8
|
+
...console,
|
|
9
|
+
log: jest.fn(),
|
|
10
|
+
warn: jest.fn(),
|
|
11
|
+
error: jest.fn(),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
// Global test timeout
|
|
15
|
+
jest.setTimeout(10000);
|
|
16
|
+
// Handle unhandled promise rejections in tests
|
|
17
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
18
|
+
// Ignore unhandled rejections during tests
|
|
19
|
+
});
|
|
20
|
+
// Clean up any lingering timers after each test
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
jest.clearAllTimers();
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/__tests__/setup.ts"],"names":[],"mappings":";;AAAA,kBAAkB;AAClB,4BAA0B;AAE1B,sDAAsD;AACtD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;IAC7B,MAAM,CAAC,OAAO,GAAG;QACf,GAAG,OAAO;QACV,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE;QACd,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE;QACf,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE;KACjB,CAAC;AACJ,CAAC;AAED,sBAAsB;AACtB,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;AAEvB,+CAA+C;AAC/C,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;IACnD,2CAA2C;AAC7C,CAAC,CAAC,CAAC;AAEH,gDAAgD;AAChD,SAAS,CAAC,GAAG,EAAE;IACb,IAAI,CAAC,cAAc,EAAE,CAAC;AACxB,CAAC,CAAC,CAAC"}
|