glide-mq 0.3.0 → 0.4.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/demo/index.ts ADDED
@@ -0,0 +1,502 @@
1
+ #!/usr/bin/env node
2
+ import { Queue, Worker, FlowProducer, QueueEvents, chain, group } from 'glide-mq';
3
+ import type { Job } from 'glide-mq';
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import Table from 'cli-table3';
7
+ import { setTimeout } from 'timers/promises';
8
+
9
+ // Connection config
10
+ const connection = {
11
+ addresses: [{ host: 'localhost', port: 6379 }],
12
+ clusterMode: false,
13
+ };
14
+
15
+ // Color-coded console logging
16
+ const log = {
17
+ info: (msg: string) => console.log(chalk.cyan('[INFO]'), msg),
18
+ success: (msg: string) => console.log(chalk.green('[SUCCESS]'), msg),
19
+ error: (msg: string) => console.log(chalk.red('[ERROR]'), msg),
20
+ warn: (msg: string) => console.log(chalk.yellow('[WARN]'), msg),
21
+ job: (msg: string) => console.log(chalk.magenta('[JOB]'), msg),
22
+ flow: (msg: string) => console.log(chalk.blue('[FLOW]'), msg),
23
+ };
24
+
25
+ // Queue definitions with different features
26
+ const queues = {
27
+ orders: new Queue('orders', { connection, compression: 'gzip' }),
28
+ payments: new Queue('payments', { connection }),
29
+ inventory: new Queue('inventory', { connection }),
30
+ shipping: new Queue('shipping', { connection }),
31
+ notifications: new Queue('notifications', { connection }),
32
+ analytics: new Queue('analytics', { connection }),
33
+ recommendations: new Queue('recommendations', { connection }),
34
+ reports: new Queue('reports', { connection }),
35
+ deadLetter: new Queue('dead-letter', { connection }),
36
+ priority: new Queue('priority-tasks', { connection }),
37
+ };
38
+
39
+ // Flow producer for complex workflows
40
+ const flowProducer = new FlowProducer({ connection });
41
+
42
+ // Event listeners for all queues
43
+ const setupQueueEvents = () => {
44
+ Object.entries(queues).forEach(([name, queue]) => {
45
+ const events = new QueueEvents(queue.name, { connection });
46
+
47
+ events.on('added', ({ jobId }) => {
48
+ log.job(`${name}: Job ${jobId} added`);
49
+ });
50
+
51
+ events.on('completed', ({ jobId, returnvalue }) => {
52
+ log.success(`${name}: Job ${jobId} completed with result: ${JSON.stringify(returnvalue)}`);
53
+ });
54
+
55
+ events.on('failed', ({ jobId, failedReason }) => {
56
+ log.error(`${name}: Job ${jobId} failed - ${failedReason}`);
57
+ });
58
+
59
+ events.on('progress', ({ jobId, data }) => {
60
+ log.info(`${name}: Job ${jobId} progress: ${data}%`);
61
+ });
62
+
63
+ events.on('stalled', ({ jobId }) => {
64
+ log.warn(`${name}: Job ${jobId} stalled`);
65
+ });
66
+ });
67
+ };
68
+
69
+ // Worker implementations
70
+ const setupWorkers = () => {
71
+ // Order processing worker with progress tracking
72
+ const orderWorker = new Worker('orders', async (job: Job) => {
73
+ await job.log(`Processing order ${job.data.orderId}`);
74
+ await job.updateProgress(25);
75
+
76
+ // Simulate order validation
77
+ await setTimeout(500);
78
+ await job.updateProgress(50);
79
+
80
+ // Simulate inventory check
81
+ await setTimeout(500);
82
+ await job.updateProgress(75);
83
+
84
+ // Complete order
85
+ await setTimeout(500);
86
+ await job.updateProgress(100);
87
+
88
+ return {
89
+ orderId: job.data.orderId,
90
+ status: 'processed',
91
+ items: job.data.items,
92
+ total: job.data.total
93
+ };
94
+ }, {
95
+ connection,
96
+ concurrency: 5,
97
+ stalledInterval: 30000,
98
+ deadLetterQueue: { name: 'dead-letter' },
99
+ });
100
+
101
+ // Payment processor with retries
102
+ const paymentWorker = new Worker('payments', async (job: Job) => {
103
+ await job.log(`Processing payment for order ${job.data.orderId}`);
104
+
105
+ // Simulate payment gateway call
106
+ await setTimeout(1000);
107
+
108
+ // Random failure for retry demonstration
109
+ if (Math.random() < 0.2 && job.attemptsMade < 2) {
110
+ throw new Error('Payment gateway timeout');
111
+ }
112
+
113
+ return {
114
+ orderId: job.data.orderId,
115
+ transactionId: `TXN-${Date.now()}`,
116
+ amount: job.data.amount,
117
+ status: 'completed'
118
+ };
119
+ }, {
120
+ connection,
121
+ concurrency: 3,
122
+ backoffStrategies: {
123
+ exponential: (attemptsMade) => 2 ** attemptsMade * 1000,
124
+ },
125
+ });
126
+
127
+ // Inventory worker with rate limiting
128
+ const inventoryWorker = new Worker('inventory', async (job: Job) => {
129
+ await job.log(`Updating inventory for SKUs: ${job.data.skus.join(', ')}`);
130
+ await setTimeout(300);
131
+
132
+ return {
133
+ updated: job.data.skus.length,
134
+ timestamp: new Date().toISOString()
135
+ };
136
+ }, {
137
+ connection,
138
+ concurrency: 10,
139
+ limiter: { max: 50, duration: 60000 }, // 50 jobs per minute
140
+ });
141
+
142
+ // Shipping worker
143
+ const shippingWorker = new Worker('shipping', async (job: Job) => {
144
+ await job.log(`Creating shipping label for order ${job.data.orderId}`);
145
+ await setTimeout(800);
146
+
147
+ return {
148
+ orderId: job.data.orderId,
149
+ trackingNumber: `TRACK-${Date.now()}`,
150
+ carrier: ['FedEx', 'UPS', 'USPS'][Math.floor(Math.random() * 3)],
151
+ estimatedDelivery: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString()
152
+ };
153
+ }, {
154
+ connection,
155
+ concurrency: 5,
156
+ });
157
+
158
+ // Notification worker with multiple channels
159
+ const notificationWorker = new Worker('notifications', async (job: Job) => {
160
+ const { type, recipient, message } = job.data;
161
+ await job.log(`Sending ${type} notification to ${recipient}`);
162
+
163
+ // Simulate different notification channels
164
+ const delay = type === 'email' ? 500 : type === 'sms' ? 300 : 100;
165
+ await setTimeout(delay);
166
+
167
+ return {
168
+ sent: true,
169
+ channel: type,
170
+ recipient,
171
+ timestamp: new Date().toISOString()
172
+ };
173
+ }, {
174
+ connection,
175
+ concurrency: 20,
176
+ });
177
+
178
+ // Analytics worker for aggregation
179
+ const analyticsWorker = new Worker('analytics', async (job: Job) => {
180
+ await job.log(`Processing analytics event: ${job.data.event}`);
181
+ await setTimeout(200);
182
+
183
+ return {
184
+ event: job.data.event,
185
+ processed: true,
186
+ metrics: {
187
+ views: Math.floor(Math.random() * 1000),
188
+ clicks: Math.floor(Math.random() * 100),
189
+ conversions: Math.floor(Math.random() * 10)
190
+ }
191
+ };
192
+ }, {
193
+ connection,
194
+ concurrency: 15,
195
+ });
196
+
197
+ // Recommendation engine worker
198
+ const recommendationWorker = new Worker('recommendations', async (job: Job) => {
199
+ await job.log(`Generating recommendations for user ${job.data.userId}`);
200
+ await setTimeout(1500);
201
+
202
+ return {
203
+ userId: job.data.userId,
204
+ recommendations: [
205
+ { productId: 'PROD-001', score: 0.95 },
206
+ { productId: 'PROD-002', score: 0.87 },
207
+ { productId: 'PROD-003', score: 0.82 },
208
+ ]
209
+ };
210
+ }, {
211
+ connection,
212
+ concurrency: 2,
213
+ });
214
+
215
+ // Report generator with long-running tasks
216
+ const reportWorker = new Worker('reports', async (job: Job) => {
217
+ await job.log(`Generating ${job.data.type} report`);
218
+
219
+ // Simulate long-running report generation
220
+ for (let i = 0; i <= 100; i += 10) {
221
+ await setTimeout(500);
222
+ await job.updateProgress(i);
223
+ }
224
+
225
+ return {
226
+ reportId: `REPORT-${Date.now()}`,
227
+ type: job.data.type,
228
+ url: `https://reports.example.com/${Date.now()}.pdf`,
229
+ pages: Math.floor(Math.random() * 50) + 10
230
+ };
231
+ }, {
232
+ connection,
233
+ concurrency: 1,
234
+ });
235
+
236
+ // Priority task worker
237
+ const priorityWorker = new Worker('priority-tasks', async (job: Job) => {
238
+ await job.log(`Processing priority task: ${job.data.task}`);
239
+ await setTimeout(100);
240
+
241
+ return {
242
+ task: job.data.task,
243
+ priority: job.opts.priority,
244
+ completed: new Date().toISOString()
245
+ };
246
+ }, {
247
+ connection,
248
+ concurrency: 10,
249
+ });
250
+
251
+ // Dead letter queue worker for investigation
252
+ const deadLetterWorker = new Worker('dead-letter', async (job: Job) => {
253
+ await job.log(`Investigating failed job from ${job.data.originalQueue}`);
254
+ // Here you would typically log to external system or alert
255
+ return { investigated: true };
256
+ }, {
257
+ connection,
258
+ concurrency: 1,
259
+ });
260
+
261
+ return {
262
+ orderWorker,
263
+ paymentWorker,
264
+ inventoryWorker,
265
+ shippingWorker,
266
+ notificationWorker,
267
+ analyticsWorker,
268
+ recommendationWorker,
269
+ reportWorker,
270
+ priorityWorker,
271
+ deadLetterWorker,
272
+ };
273
+ };
274
+
275
+ // Demo scenarios
276
+ async function runDemoScenarios() {
277
+ const spinner = ora('Starting demo scenarios...').start();
278
+
279
+ // Scenario 1: Simple job with progress
280
+ spinner.text = 'Scenario 1: Simple order processing';
281
+ await queues.orders.add('process-order', {
282
+ orderId: 'ORD-001',
283
+ items: ['SKU-123', 'SKU-456'],
284
+ total: 299.99
285
+ });
286
+ await setTimeout(2000);
287
+
288
+ // Scenario 2: Bulk operations
289
+ spinner.text = 'Scenario 2: Bulk inventory update';
290
+ const bulkJobs = Array.from({ length: 20 }, (_, i) => ({
291
+ name: 'update-inventory',
292
+ data: { skus: [`SKU-${i}00`, `SKU-${i}01`, `SKU-${i}02`] }
293
+ }));
294
+ await queues.inventory.addBulk(bulkJobs);
295
+ await setTimeout(1000);
296
+
297
+ // Scenario 3: Priority jobs
298
+ spinner.text = 'Scenario 3: Priority tasks';
299
+ await queues.priority.add('urgent-task',
300
+ { task: 'Critical system update' },
301
+ { priority: 1 }
302
+ );
303
+ await queues.priority.add('normal-task',
304
+ { task: 'Regular maintenance' },
305
+ { priority: 5 }
306
+ );
307
+ await queues.priority.add('low-task',
308
+ { task: 'Cleanup operation' },
309
+ { priority: 10 }
310
+ );
311
+ await setTimeout(1000);
312
+
313
+ // Scenario 4: Delayed jobs
314
+ spinner.text = 'Scenario 4: Scheduled notifications';
315
+ await queues.notifications.add('reminder',
316
+ { type: 'email', recipient: 'user@example.com', message: 'Order shipped!' },
317
+ { delay: 5000 }
318
+ );
319
+ await setTimeout(1000);
320
+
321
+ // Scenario 5: Job with retries
322
+ spinner.text = 'Scenario 5: Payment processing with retries';
323
+ await queues.payments.add('process-payment',
324
+ { orderId: 'ORD-002', amount: 199.99 },
325
+ { attempts: 3, backoff: { type: 'exponential', delay: 1000 } }
326
+ );
327
+ await setTimeout(2000);
328
+
329
+ // Scenario 6: Deduplication
330
+ spinner.text = 'Scenario 6: Deduplicated analytics events';
331
+ for (let i = 0; i < 5; i++) {
332
+ await queues.analytics.add('track-event',
333
+ { event: 'page_view', userId: 'USER-123', page: '/home' },
334
+ { deduplication: { id: 'pageview-home-123', mode: 'simple' } }
335
+ );
336
+ }
337
+ await setTimeout(1000);
338
+
339
+ // Scenario 7: Complex workflow with FlowProducer
340
+ spinner.text = 'Scenario 7: E-commerce order workflow';
341
+ await flowProducer.add({
342
+ name: 'complete-order',
343
+ queueName: 'orders',
344
+ data: { orderId: 'ORD-003', items: ['PROD-A', 'PROD-B'], total: 499.99 },
345
+ children: [
346
+ {
347
+ name: 'process-payment',
348
+ queueName: 'payments',
349
+ data: { orderId: 'ORD-003', amount: 499.99 },
350
+ children: [
351
+ {
352
+ name: 'update-inventory',
353
+ queueName: 'inventory',
354
+ data: { skus: ['PROD-A', 'PROD-B'] }
355
+ },
356
+ {
357
+ name: 'create-shipping',
358
+ queueName: 'shipping',
359
+ data: { orderId: 'ORD-003', address: '123 Main St' }
360
+ }
361
+ ]
362
+ },
363
+ {
364
+ name: 'send-confirmation',
365
+ queueName: 'notifications',
366
+ data: { type: 'email', recipient: 'customer@example.com', message: 'Order confirmed!' }
367
+ }
368
+ ]
369
+ });
370
+ await setTimeout(3000);
371
+
372
+ // Scenario 8: Chain pattern - Sequential pipeline
373
+ spinner.text = 'Scenario 8: Sequential data pipeline';
374
+ await chain('analytics', [
375
+ { name: 'collect-data', data: { source: 'api', endpoint: '/metrics' } },
376
+ { name: 'transform-data', data: { format: 'json' } },
377
+ { name: 'aggregate-data', data: { window: '1h' } },
378
+ { name: 'store-data', data: { destination: 'warehouse' } }
379
+ ], connection);
380
+ await setTimeout(2000);
381
+
382
+ // Scenario 9: Group pattern - Parallel execution
383
+ spinner.text = 'Scenario 9: Parallel notification broadcast';
384
+ await group('notifications', [
385
+ { name: 'send-email', data: { type: 'email', recipient: 'user1@example.com', message: 'New feature!' } },
386
+ { name: 'send-sms', data: { type: 'sms', recipient: '+1234567890', message: 'New feature!' } },
387
+ { name: 'send-push', data: { type: 'push', recipient: 'device-token-123', message: 'New feature!' } }
388
+ ], connection);
389
+ await setTimeout(2000);
390
+
391
+ // Scenario 10: Large payload with compression
392
+ spinner.text = 'Scenario 10: Large report generation with compression';
393
+ const largeData = {
394
+ reportType: 'annual',
395
+ data: Array.from({ length: 1000 }, (_, i) => ({
396
+ id: i,
397
+ timestamp: new Date().toISOString(),
398
+ metrics: {
399
+ revenue: Math.random() * 10000,
400
+ orders: Math.floor(Math.random() * 100),
401
+ customers: Math.floor(Math.random() * 50)
402
+ }
403
+ }))
404
+ };
405
+ await queues.reports.add('generate-annual-report', largeData);
406
+ await setTimeout(3000);
407
+
408
+ // Scenario 11: Rate-limited batch processing
409
+ spinner.text = 'Scenario 11: Rate-limited recommendation generation';
410
+ const userIds = Array.from({ length: 10 }, (_, i) => `USER-${i}`);
411
+ for (const userId of userIds) {
412
+ await queues.recommendations.add('generate-recommendations', { userId });
413
+ }
414
+ await setTimeout(5000);
415
+
416
+ // Scenario 12: Job with timeout demonstration
417
+ spinner.text = 'Scenario 12: Job timeout handling';
418
+ await queues.reports.add('slow-report',
419
+ { type: 'detailed-analysis' },
420
+ { timeout: 3000 } // Will timeout if takes > 3s
421
+ );
422
+ await setTimeout(2000);
423
+
424
+ spinner.succeed('All demo scenarios launched!');
425
+ }
426
+
427
+ // Display metrics
428
+ async function displayMetrics() {
429
+ console.log('\n' + chalk.bold.cyan('Queue Metrics:'));
430
+
431
+ const table = new Table({
432
+ head: ['Queue', 'Waiting', 'Active', 'Delayed', 'Completed', 'Failed'],
433
+ colWidths: [20, 10, 10, 10, 12, 10],
434
+ style: { head: ['cyan'] }
435
+ });
436
+
437
+ for (const [name, queue] of Object.entries(queues)) {
438
+ const counts = await queue.getJobCounts();
439
+ table.push([
440
+ name,
441
+ counts.waiting || 0,
442
+ counts.active || 0,
443
+ counts.delayed || 0,
444
+ counts.completed || 0,
445
+ counts.failed || 0
446
+ ]);
447
+ }
448
+
449
+ console.log(table.toString());
450
+ }
451
+
452
+ // Main execution
453
+ async function main() {
454
+ console.log(chalk.bold.green('\n[OK] Starting glide-mq Comprehensive Demo\n'));
455
+ console.log(chalk.gray('This demo showcases all major features of glide-mq:\n'));
456
+ console.log(chalk.gray('- Job processing with progress tracking'));
457
+ console.log(chalk.gray('- Bulk operations and compression'));
458
+ console.log(chalk.gray('- Priority queues and delayed jobs'));
459
+ console.log(chalk.gray('- Retries with backoff strategies'));
460
+ console.log(chalk.gray('- Deduplication and rate limiting'));
461
+ console.log(chalk.gray('- Complex workflows and patterns'));
462
+ console.log(chalk.gray('- Dead letter queue handling'));
463
+ console.log(chalk.gray('- And much more...\n'));
464
+
465
+ // Setup
466
+ setupQueueEvents();
467
+ const workers = setupWorkers();
468
+
469
+ // Run demo scenarios
470
+ await runDemoScenarios();
471
+
472
+ // Display metrics periodically
473
+ const metricsInterval = setInterval(displayMetrics, 5000);
474
+ await displayMetrics();
475
+
476
+ // Keep running
477
+ console.log(chalk.green('\n[OK] Demo is running. Press Ctrl+C to stop.\n'));
478
+
479
+ // Graceful shutdown
480
+ process.on('SIGINT', async () => {
481
+ console.log(chalk.yellow('\n[WARN] Shutting down gracefully...'));
482
+ clearInterval(metricsInterval);
483
+
484
+ // Close all workers
485
+ await Promise.all(Object.values(workers).map(w => w.close()));
486
+
487
+ // Close all queues
488
+ await Promise.all(Object.values(queues).map(q => q.close()));
489
+
490
+ console.log(chalk.green('[OK] Demo stopped.'));
491
+ process.exit(0);
492
+ });
493
+ }
494
+
495
+ // Error handling
496
+ process.on('unhandledRejection', (err) => {
497
+ log.error(`Unhandled rejection: ${err}`);
498
+ console.error(err);
499
+ });
500
+
501
+ // Start the demo
502
+ main().catch(console.error);