File size: 5,194 Bytes
e8a6607
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import IORedis from 'ioredis';
import { Queue, Job } from 'bullmq';
import dotenv from 'dotenv';

import logger from '../logger';
// import { workerRedis } from './workerRedis';

const envFile: string = process.env.NODE_ENV === 'production' ? '.env.production' : '.env.development';

dotenv.config({ path: envFile });

export class QueueInfra {
  public readonly queueRedis: IORedis;
  public readonly queue: Queue;
  private initialized = false;
  private queueName: string;

  constructor(queueRedis: IORedis, queueName: string) { 
    this.queueRedis = queueRedis;
    this.queueName = queueName;

    // Create the queue for email related tasks
    this.queue = new Queue(queueName, {
      connection: this.queueRedis,  // Pass connection object
      
      defaultJobOptions: {
        attempts: 3,
        backoff: {
          type: 'exponential',  // Options: 'fixed' | 'exponential'
          delay: 3000           // Delay in ms
        },
        removeOnComplete: {
          count: 1000,          // Keep last 1000 completed jobs
          age: 24 * 3600        // OR keep for 24 hours (optional)
        },
        removeOnFail: {
          count: 6000,          // Keep last 5000 failed jobs
          age: 72 * 3600        // OR keep for 72 hours (optional)
        },
      
        // Timeouts are handled at worker level
      }
    });

  }

  async init() {
    if (this.initialized) return;
    this.initialized = true;

    // this.setupRedisEventListeners();
    this.setupQueueEventListeners();
  }

  /*private setupRedisEventListeners(): void {
    // Add event handlers
    this.emailQueueRedis.on('connect', () => {
      logger.info('βœ… Redis connected (Email queue redis)');
    });

    this.emailQueueRedis.on('ready', () => {
      logger.info('βœ… Redis ready (Email queue redis)');
    });

    this.emailQueueRedis.on('error', (err) => {
      logger.error({ err }, `❌ Redis connection error (Email queue redis): ${err.message}`);
      // Don't crash the app on Redis errors
    });

    this.emailQueueRedis.on('close', () => {
      logger.info('⚠️  Redis connection closed (Email queue redis)');
    });

    this.emailQueueRedis.on('reconnecting', (delay: number) => {
      logger.info(`πŸ”„ Redis reconnecting in ${delay}ms (email queue redis)`);
    });

    this.emailQueueRedis.on('end', () => {
      logger.info('πŸ”΄ Redis connection ended (email queue redis)');
    });
  } */

  private setupQueueEventListeners(): void {
    // The callback receives a Job object directly, not an args object
    this.queue.on('waiting', (job: Job) => {
      logger.info(`πŸ“₯ Job ${job.id} is waiting: ${this.queueName}`);
      logger.info(`Job data: ${job.data}: ${this.queueName}`);
    });

    this.queue.on('error', (err: Error) => {
      logger.error({ err }, `❌ Queue error: ${err.message}: ${this.queueName}`);
    });

    this.queue.on('progress', (job: string, progress: any) => {
      logger.debug(`πŸ“Š Job ${job} progress: ${progress} : ${this.queueName}`);
    });

    // Other available events:
    this.queue.on('paused', () => {
      logger.info(`⏸️ Queue paused: ${this.queueName}`);
    });

    this.queue.on('resumed', () => {
      logger.info(`▢️ Queue resumed: ${this.queueName}`);
    });

    this.queue.on('cleaned', (jobs: string[], type: string) => {
      logger.debug(`🧹 Cleaned ${jobs.length} ${type} jobs: ${this.queueName}`);
    });

    this.queue.on('removed', (job: string) => {
      logger.debug(`πŸ—‘οΈ Job ${job} removed: ${this.queueName}`);
    });

    // Redis-specific events
    this.queue.on('ioredis:close', () => {
      logger.info(`πŸ”Œ Redis connection closed: ${this.queueName}`);
    });
  }
  
  // Test connection on startup
  public async testQueueRedisConnection() {
    try {
      await this.queueRedis.ping();
      logger.info(`βœ… Redis connection test passed: ${this.queueName}`);
    } catch (err: any) {
        logger.error({err}, `❌ Redis connection test failed: ${err.message}: ${this.queueName}`);
    }
  }

  public async closeQueue() {
    logger.info(`⏳ Closing ${this.queueName}...`);
    if (!this.queue) {
      logger.info(`βœ… ${this.queueName} is not active.`);
      return;
    }

    try {
      await this.queue.close();
      logger.info(`βœ… ${this.queueName} closed`);
    } catch (err: any) {
        logger.error({ err }, `❌ Failed to close ${this.queueName}`);
        throw err; // propagate to main shutdown  
    }
  }
  
  public async closeQueueRedis() {
    logger.info(`⏳ Closing Redis connection: ${this.queueName}...`);

    if(!this.queueRedis) {
      logger.info(`βœ… Redis connection: ${this.queueName} is not active`);
      return;   
    }

    try {
      await this.queueRedis.quit(); // Graceful Redis shutdown
      logger.info(`βœ… Redis connection closed: ${this.queueName} `);
    } catch (err: any) {
        logger.error({ err }, `❌ Failed to close Redis connection: ${this.queueName}`);
        throw err; // propagate to main shutdown  
    }
  }

};


/*
Never do:

try {
   everything
}

Always do:

START log
if exists
   try risky close
SUCCESS log
FAIL log

This is architecturally correct


*/