@solidxai/core 0.1.0 → 0.1.2

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.
Files changed (131) hide show
  1. package/dist/dtos/create-saved-filters.dto.d.ts.map +1 -1
  2. package/dist/dtos/create-saved-filters.dto.js.map +1 -1
  3. package/dist/entities/chatter-message.entity.d.ts.map +1 -1
  4. package/dist/entities/chatter-message.entity.js +2 -0
  5. package/dist/entities/chatter-message.entity.js.map +1 -1
  6. package/dist/helpers/schematic.service.d.ts.map +1 -1
  7. package/dist/helpers/schematic.service.js +6 -2
  8. package/dist/helpers/schematic.service.js.map +1 -1
  9. package/dist/jobs/api-email-queue-options.d.ts.map +1 -1
  10. package/dist/jobs/api-email-queue-options.js +2 -2
  11. package/dist/jobs/api-email-queue-options.js.map +1 -1
  12. package/dist/jobs/chatter-queue-options.js +2 -2
  13. package/dist/jobs/chatter-queue-options.js.map +1 -1
  14. package/dist/jobs/computed-field-evaluation-queue-options.js +2 -2
  15. package/dist/jobs/computed-field-evaluation-queue-options.js.map +1 -1
  16. package/dist/jobs/database/api-email-queue-options-database.js +2 -2
  17. package/dist/jobs/database/api-email-queue-options-database.js.map +1 -1
  18. package/dist/jobs/database/computed-field-evaluation-queue-options-database.js +2 -2
  19. package/dist/jobs/database/computed-field-evaluation-queue-options-database.js.map +1 -1
  20. package/dist/jobs/database/generate-code-queue-options-database.js +2 -2
  21. package/dist/jobs/database/generate-code-queue-options-database.js.map +1 -1
  22. package/dist/jobs/database/msg91-sms-queue-database-options.d.ts.map +1 -1
  23. package/dist/jobs/database/msg91-sms-queue-database-options.js +2 -2
  24. package/dist/jobs/database/msg91-sms-queue-database-options.js.map +1 -1
  25. package/dist/jobs/database/msg91-whatsapp-queue-options-database.js +2 -2
  26. package/dist/jobs/database/msg91-whatsapp-queue-options-database.js.map +1 -1
  27. package/dist/jobs/database/otp-queue-options-database.d.ts.map +1 -1
  28. package/dist/jobs/database/otp-queue-options-database.js +2 -2
  29. package/dist/jobs/database/otp-queue-options-database.js.map +1 -1
  30. package/dist/jobs/database/smtp-email-queue-options-database.js +1 -1
  31. package/dist/jobs/database/smtp-email-queue-options-database.js.map +1 -1
  32. package/dist/jobs/database/test-queue-options-database.js +2 -2
  33. package/dist/jobs/database/test-queue-options-database.js.map +1 -1
  34. package/dist/jobs/database/three60-whatsapp-queue-options-database.js +2 -2
  35. package/dist/jobs/database/three60-whatsapp-queue-options-database.js.map +1 -1
  36. package/dist/jobs/database/trigger-mcp-client-queue-options.js +2 -2
  37. package/dist/jobs/database/trigger-mcp-client-queue-options.js.map +1 -1
  38. package/dist/jobs/database/twilio-sms-queue-database-options.js +2 -2
  39. package/dist/jobs/database/twilio-sms-queue-database-options.js.map +1 -1
  40. package/dist/jobs/generate-code-queue-options.js +2 -2
  41. package/dist/jobs/generate-code-queue-options.js.map +1 -1
  42. package/dist/jobs/msg91-otp-queue-options.d.ts.map +1 -1
  43. package/dist/jobs/msg91-otp-queue-options.js +2 -2
  44. package/dist/jobs/msg91-otp-queue-options.js.map +1 -1
  45. package/dist/jobs/msg91-sms-queue-options.d.ts.map +1 -1
  46. package/dist/jobs/msg91-sms-queue-options.js +2 -2
  47. package/dist/jobs/msg91-sms-queue-options.js.map +1 -1
  48. package/dist/jobs/msg91-whatsapp-queue-options.d.ts.map +1 -1
  49. package/dist/jobs/msg91-whatsapp-queue-options.js +2 -2
  50. package/dist/jobs/msg91-whatsapp-queue-options.js.map +1 -1
  51. package/dist/jobs/smtp-email-queue-options.d.ts.map +1 -1
  52. package/dist/jobs/smtp-email-queue-options.js +1 -1
  53. package/dist/jobs/smtp-email-queue-options.js.map +1 -1
  54. package/dist/jobs/test-queue-options.d.ts.map +1 -1
  55. package/dist/jobs/test-queue-options.js +2 -2
  56. package/dist/jobs/test-queue-options.js.map +1 -1
  57. package/dist/jobs/three60-whatsapp-queue-options.d.ts.map +1 -1
  58. package/dist/jobs/three60-whatsapp-queue-options.js +2 -2
  59. package/dist/jobs/three60-whatsapp-queue-options.js.map +1 -1
  60. package/dist/jobs/three60-whatsapp-subscriber.service.js +2 -2
  61. package/dist/jobs/three60-whatsapp-subscriber.service.js.map +1 -1
  62. package/dist/jobs/trigger-mcp-client-queue-options.js +2 -2
  63. package/dist/jobs/trigger-mcp-client-queue-options.js.map +1 -1
  64. package/dist/jobs/twilio-sms-queue-options.js +2 -2
  65. package/dist/jobs/twilio-sms-queue-options.js.map +1 -1
  66. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  67. package/dist/seeders/module-metadata-seeder.service.js +2 -1
  68. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  69. package/dist/seeders/seed-data/solid-core-metadata.json +34 -9
  70. package/dist/services/chatter-message.service.d.ts.map +1 -1
  71. package/dist/services/chatter-message.service.js +8 -1
  72. package/dist/services/chatter-message.service.js.map +1 -1
  73. package/dist/services/crud-helper.service.js.map +1 -1
  74. package/dist/services/model-metadata.service.d.ts.map +1 -1
  75. package/dist/services/model-metadata.service.js +2 -1
  76. package/dist/services/model-metadata.service.js.map +1 -1
  77. package/dist/services/module-metadata.service.d.ts.map +1 -1
  78. package/dist/services/module-metadata.service.js +2 -1
  79. package/dist/services/module-metadata.service.js.map +1 -1
  80. package/dist/services/queues/database-publisher.service.js +0 -1
  81. package/dist/services/queues/database-publisher.service.js.map +1 -1
  82. package/dist/services/queues/database-subscriber.service.d.ts.map +1 -1
  83. package/dist/services/queues/database-subscriber.service.js +14 -1
  84. package/dist/services/queues/database-subscriber.service.js.map +1 -1
  85. package/dist/services/queues/rabbitmq-subscriber.service.d.ts +14 -1
  86. package/dist/services/queues/rabbitmq-subscriber.service.d.ts.map +1 -1
  87. package/dist/services/queues/rabbitmq-subscriber.service.js +209 -66
  88. package/dist/services/queues/rabbitmq-subscriber.service.js.map +1 -1
  89. package/dist/services/scheduled-jobs/scheduler.service.d.ts +1 -0
  90. package/dist/services/scheduled-jobs/scheduler.service.d.ts.map +1 -1
  91. package/dist/services/scheduled-jobs/scheduler.service.js +26 -10
  92. package/dist/services/scheduled-jobs/scheduler.service.js.map +1 -1
  93. package/dist/tsconfig.tsbuildinfo +1 -1
  94. package/package.json +1 -1
  95. package/src/dtos/create-saved-filters.dto.ts +10 -1
  96. package/src/entities/chatter-message.entity.ts +11 -1
  97. package/src/helpers/schematic.service.ts +6 -2
  98. package/src/jobs/api-email-queue-options.ts +2 -6
  99. package/src/jobs/chatter-queue-options.ts +2 -2
  100. package/src/jobs/computed-field-evaluation-queue-options.ts +2 -2
  101. package/src/jobs/database/api-email-queue-options-database.ts +2 -2
  102. package/src/jobs/database/computed-field-evaluation-queue-options-database.ts +2 -2
  103. package/src/jobs/database/generate-code-queue-options-database.ts +2 -2
  104. package/src/jobs/database/msg91-sms-queue-database-options.ts +3 -2
  105. package/src/jobs/database/msg91-whatsapp-queue-options-database.ts +2 -2
  106. package/src/jobs/database/otp-queue-options-database.ts +3 -2
  107. package/src/jobs/database/smtp-email-queue-options-database.ts +1 -1
  108. package/src/jobs/database/test-queue-options-database.ts +2 -2
  109. package/src/jobs/database/three60-whatsapp-queue-options-database.ts +2 -2
  110. package/src/jobs/database/trigger-mcp-client-queue-options.ts +2 -2
  111. package/src/jobs/database/twilio-sms-queue-database-options.ts +2 -2
  112. package/src/jobs/generate-code-queue-options.ts +2 -2
  113. package/src/jobs/msg91-otp-queue-options.ts +3 -8
  114. package/src/jobs/msg91-sms-queue-options.ts +3 -6
  115. package/src/jobs/msg91-whatsapp-queue-options.ts +4 -7
  116. package/src/jobs/smtp-email-queue-options.ts +1 -6
  117. package/src/jobs/test-queue-options.ts +2 -6
  118. package/src/jobs/three60-whatsapp-queue-options.ts +4 -7
  119. package/src/jobs/three60-whatsapp-subscriber.service.ts +1 -1
  120. package/src/jobs/trigger-mcp-client-queue-options.ts +2 -2
  121. package/src/jobs/twilio-sms-queue-options.ts +3 -3
  122. package/src/seeders/module-metadata-seeder.service.ts +2 -1
  123. package/src/seeders/seed-data/solid-core-metadata.json +34 -9
  124. package/src/services/chatter-message.service.ts +32 -26
  125. package/src/services/crud-helper.service.ts +1 -1
  126. package/src/services/model-metadata.service.ts +2 -1
  127. package/src/services/module-metadata.service.ts +2 -1
  128. package/src/services/queues/database-publisher.service.ts +1 -1
  129. package/src/services/queues/database-subscriber.service.ts +17 -28
  130. package/src/services/queues/rabbitmq-subscriber.service.ts +250 -110
  131. package/src/services/scheduled-jobs/scheduler.service.ts +31 -14
@@ -41,6 +41,12 @@ class RabbitMqSubscriber {
41
41
  this.mqMessageService = mqMessageService;
42
42
  this.mqMessageQueueService = mqMessageQueueService;
43
43
  this.logger = new common_1.Logger(RabbitMqSubscriber.name);
44
+ this.connection = null;
45
+ this.channel = null;
46
+ this.consumerTag = null;
47
+ this.reconnectPromise = null;
48
+ this.reconnectAttempt = 0;
49
+ this.stopping = false;
44
50
  this.url = process.env.QUEUES_RABBIT_MQ_URL;
45
51
  this.serviceRole = process.env.QUEUES_SERVICE_ROLE;
46
52
  if (!this.url) {
@@ -59,91 +65,228 @@ class RabbitMqSubscriber {
59
65
  username: url.username,
60
66
  password: decodeURIComponent(url.password),
61
67
  frameMax: 131072,
68
+ heartbeat: 30,
62
69
  });
63
70
  return connection;
64
71
  }
65
72
  async onModuleInit() {
73
+ const defaultBroker = process.env.QUEUES_DEFAULT_BROKER || 'rabbitmq';
66
74
  const solidCliRunning = process.env.SOLID_CLI_RUNNING || "false";
67
- if (this.url && ['both', 'subscriber'].includes(this.serviceRole) && solidCliRunning === "false") {
68
- let connection;
69
- try {
70
- connection = await this.establishConnection();
71
- }
72
- catch (err) {
73
- this.logger.error(`Failed to connect to RabbitMQ: ${err.message}`, err.stack);
74
- throw err;
75
- }
76
- const channel = await connection.createChannel();
75
+ const queueNameRegex = (process.env.QUEUES_QUEUE_NAME_REGEX_TO_ENABLE || '').trim();
76
+ if (this.url && ['both', 'subscriber'].includes(this.serviceRole) && solidCliRunning === "false" && defaultBroker === 'rabbitmq') {
77
77
  const options = this.options();
78
78
  const queueName = options.queueName;
79
- const exchangeName = `${queueName}.exchange`;
80
- const routingKey = `${queueName}.routing-key`;
81
- await channel.assertExchange(exchangeName, 'direct', {});
82
- const queue = await channel.assertQueue(queueName, {});
83
- await channel.bindQueue(queue.queue, exchangeName, routingKey);
84
- channel.consume(queue.queue, async (rawMessage) => {
85
- if (rawMessage) {
86
- const messageContentString = rawMessage.content.toString();
87
- let message = null;
88
- try {
89
- message = JSON.parse(messageContentString);
90
- if (!message.retryCount)
91
- message.retryCount = 0;
92
- if (!message.retryInterval)
93
- message.retryInterval = 1000;
94
- if (!message.currentRetry)
95
- message.currentRetry = 0;
96
- await this.processMessage(message, rawMessage, channel);
97
- }
98
- catch (error) {
99
- this.logger.error(`Error processing message: ${error.message}`);
100
- if (message) {
101
- if (message.currentRetry < message.retryCount) {
102
- await this.updateStatusInDatabase('retrying', message);
103
- message.currentRetry++;
104
- this.logger.warn(`Retrying message (${message.currentRetry}/${message.retryCount}) after ${message.retryInterval}ms`);
105
- setTimeout(() => {
106
- this.retryMessage(message, rawMessage, channel);
107
- }, message.retryInterval);
108
- }
109
- else {
110
- await this.updateStatusInDatabase('failed', message, error.message, '');
111
- this.logger.error(`Message failed after ${message.retryCount} attempts: ${error.message}`);
112
- channel.ack(rawMessage);
113
- }
114
- }
79
+ if (queueNameRegex && queueNameRegex !== "all") {
80
+ try {
81
+ const regex = new RegExp(queueNameRegex);
82
+ if (!regex.test(queueName)) {
83
+ this.logger.log(`RabbitMqSubscriber for queue ${queueName} is disabled because it does not match QUEUES_QUEUE_NAME_REGEX_TO_ENABLE=${queueNameRegex}`);
84
+ return;
115
85
  }
116
86
  }
117
- }, {});
87
+ catch (error) {
88
+ this.logger.error(`Invalid QUEUES_QUEUE_NAME_REGEX_TO_ENABLE regex "${queueNameRegex}". Subscriber for queue ${queueName} will not start.`);
89
+ return;
90
+ }
91
+ }
92
+ try {
93
+ await this.connectAndConsume(queueName);
94
+ }
95
+ catch (err) {
96
+ this.logger.error(`Failed to connect to RabbitMQ for queue ${queueName}: ${err.message}`, err.stack);
97
+ this.triggerReconnect(queueName, 'initial connection failure');
98
+ }
118
99
  this.logger.log(`RabbitMqSubscriber ready to consume messages: ${JSON.stringify(this.options())} and url: ${this.url}`);
119
100
  }
120
101
  }
121
- async processMessage(message, rawMessage, channel) {
122
- await this.updateStatusInDatabase('started', message);
123
- const result = await this.subscribe(message);
102
+ async connectAndConsume(queueName) {
103
+ await this.cleanup();
104
+ let connection;
105
+ try {
106
+ connection = await this.establishConnection();
107
+ }
108
+ catch (err) {
109
+ this.logger.error(`Failed to connect to RabbitMQ for queue ${queueName}: ${err.message}`, err.stack);
110
+ throw err;
111
+ }
112
+ this.connection = connection;
113
+ connection.on('error', (err) => {
114
+ if (connection !== this.connection)
115
+ return;
116
+ this.logger.error(`RabbitMqSubscriber connection error for queue ${queueName}: ${err.message}`);
117
+ });
118
+ connection.on('close', () => {
119
+ if (connection !== this.connection)
120
+ return;
121
+ this.logger.warn(`RabbitMqSubscriber connection closed for queue ${queueName}`);
122
+ this.triggerReconnect(queueName, 'connection closed');
123
+ });
124
+ const channel = await connection.createChannel();
125
+ this.channel = channel;
126
+ channel.on('error', (err) => {
127
+ if (channel !== this.channel)
128
+ return;
129
+ this.logger.error(`RabbitMqSubscriber channel error for queue ${queueName}: ${err.message}`);
130
+ });
131
+ channel.on('close', () => {
132
+ if (channel !== this.channel)
133
+ return;
134
+ this.logger.warn(`RabbitMqSubscriber channel closed for queue ${queueName}`);
135
+ this.triggerReconnect(queueName, 'channel closed');
136
+ });
137
+ await channel.prefetch(1);
138
+ const exchangeName = `${queueName}.exchange`;
139
+ const routingKey = `${queueName}.routing-key`;
140
+ const retryQueue = `${queueName}.retry`;
141
+ const failedQueue = `${queueName}.failed`;
142
+ await channel.assertExchange(exchangeName, 'direct', {});
143
+ await channel.assertQueue(queueName, {});
144
+ await channel.bindQueue(queueName, exchangeName, routingKey);
145
+ await channel.assertQueue(retryQueue, {
146
+ arguments: {
147
+ 'x-dead-letter-exchange': exchangeName,
148
+ 'x-dead-letter-routing-key': routingKey,
149
+ }
150
+ });
151
+ await channel.assertQueue(failedQueue, {});
152
+ const consumeResult = await channel.consume(queueName, async (rawMessage) => {
153
+ if (!rawMessage) {
154
+ return;
155
+ }
156
+ const messageContentString = rawMessage.content.toString();
157
+ let message = null;
158
+ try {
159
+ message = JSON.parse(messageContentString);
160
+ }
161
+ catch (error) {
162
+ this.logger.error(`Invalid JSON message on queue ${queueName}: ${error.message}`);
163
+ await this.publishToFailedQueue(queueName, rawMessage.content, channel, error);
164
+ channel.ack(rawMessage);
165
+ return;
166
+ }
167
+ if (!message.retryCount)
168
+ message.retryCount = 0;
169
+ if (!message.retryInterval)
170
+ message.retryInterval = 1000;
171
+ if (!message.currentRetry)
172
+ message.currentRetry = 0;
173
+ try {
174
+ await this.processMessage(message, rawMessage, channel);
175
+ }
176
+ catch (error) {
177
+ await this.handleProcessingError(message, rawMessage, channel, error, queueName);
178
+ }
179
+ }, { noAck: false });
180
+ this.consumerTag = consumeResult.consumerTag;
181
+ }
182
+ async handleProcessingError(message, rawMessage, channel, error, queueName) {
183
+ const errorMessage = error?.message || String(error);
184
+ this.logger.error(`Error processing message on queue ${queueName}: ${errorMessage}`);
185
+ if (message.currentRetry < message.retryCount) {
186
+ await this.updateStatusInDatabase('retrying', message);
187
+ message.currentRetry++;
188
+ const retryQueue = `${queueName}.retry`;
189
+ const payload = Buffer.from(JSON.stringify(message));
190
+ channel.sendToQueue(retryQueue, payload, {
191
+ expiration: String(message.retryInterval || 1000),
192
+ headers: {
193
+ 'x-error': errorMessage,
194
+ }
195
+ });
196
+ channel.ack(rawMessage);
197
+ this.logger.warn(`Retrying message (${message.currentRetry}/${message.retryCount}) after ${message.retryInterval}ms on queue ${queueName}`);
198
+ return;
199
+ }
200
+ await this.updateStatusInDatabase('failed', message, errorMessage, '');
124
201
  channel.ack(rawMessage);
125
- await this.updateStatusInDatabase('succeeded', message, '', result ? JSON.stringify(result, null, 2) : '');
202
+ await this.publishToFailedQueue(queueName, Buffer.from(JSON.stringify(message)), channel, error);
203
+ this.logger.error(`Message failed after ${message.retryCount} attempts on queue ${queueName}: ${errorMessage}`);
126
204
  }
127
- async retryMessage(message, rawMessage, channel) {
205
+ async publishToFailedQueue(queueName, payload, channel, error) {
206
+ const failedQueue = `${queueName}.failed`;
207
+ const body = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
208
+ const errorMessage = error?.message || String(error || '');
128
209
  try {
129
- await this.processMessage(message, rawMessage, channel);
210
+ channel.sendToQueue(failedQueue, body, errorMessage ? {
211
+ headers: { 'x-error': errorMessage }
212
+ } : undefined);
130
213
  }
131
- catch (error) {
132
- if (message.currentRetry < message.retryCount) {
133
- await this.updateStatusInDatabase('retrying', message);
134
- message.currentRetry++;
135
- this.logger.warn(`Retrying message (${message.currentRetry}/${message.retryCount}) after ${message.retryInterval}ms: ${error.message}`);
136
- setTimeout(() => {
137
- this.retryMessage(message, rawMessage, channel);
138
- }, message.retryInterval);
139
- }
140
- else {
141
- this.logger.error(`Message failed after ${message.retryCount} attempts: ${error.message}`);
142
- channel.ack(rawMessage);
143
- await this.updateStatusInDatabase('failed', message, error.message, '');
214
+ catch (err) {
215
+ this.logger.error(`Failed to publish to failed queue ${failedQueue}: ${err.message}`);
216
+ }
217
+ }
218
+ triggerReconnect(queueName, reason) {
219
+ if (this.stopping)
220
+ return;
221
+ if (this.reconnectPromise)
222
+ return;
223
+ this.reconnectPromise = this.reconnectLoop(queueName, reason)
224
+ .finally(() => {
225
+ this.reconnectPromise = null;
226
+ });
227
+ }
228
+ async reconnectLoop(queueName, reason) {
229
+ this.logger.warn(`RabbitMqSubscriber reconnecting for queue ${queueName}: ${reason}`);
230
+ while (!this.stopping) {
231
+ try {
232
+ await this.connectAndConsume(queueName);
233
+ this.reconnectAttempt = 0;
234
+ this.logger.log(`RabbitMqSubscriber reconnected for queue ${queueName}`);
235
+ return;
236
+ }
237
+ catch (err) {
238
+ this.reconnectAttempt += 1;
239
+ const delay = this.backoff();
240
+ this.logger.warn(`RabbitMqSubscriber reconnect failed for queue ${queueName}; retrying in ${delay}ms`);
241
+ await this.sleep(delay);
144
242
  }
145
243
  }
146
244
  }
245
+ async cleanup() {
246
+ const channel = this.channel;
247
+ const connection = this.connection;
248
+ const consumerTag = this.consumerTag;
249
+ this.channel = null;
250
+ this.connection = null;
251
+ this.consumerTag = null;
252
+ if (channel) {
253
+ try {
254
+ if (consumerTag) {
255
+ await channel.cancel(consumerTag);
256
+ }
257
+ }
258
+ catch (_) {
259
+ }
260
+ try {
261
+ await channel.close();
262
+ }
263
+ catch (_) {
264
+ }
265
+ }
266
+ if (connection) {
267
+ try {
268
+ await connection.close();
269
+ }
270
+ catch (_) {
271
+ }
272
+ }
273
+ }
274
+ sleep(ms) {
275
+ return new Promise(resolve => setTimeout(resolve, ms));
276
+ }
277
+ backoff() {
278
+ const baseMs = 1000;
279
+ const maxMs = 30_000;
280
+ const exp = Math.min(maxMs, baseMs * Math.pow(2, this.reconnectAttempt));
281
+ const jitter = Math.floor(Math.random() * (exp * 0.2));
282
+ return Math.min(maxMs, exp + jitter);
283
+ }
284
+ async processMessage(message, rawMessage, channel) {
285
+ await this.updateStatusInDatabase('started', message);
286
+ const result = await this.subscribe(message);
287
+ channel.ack(rawMessage);
288
+ await this.updateStatusInDatabase('succeeded', message, '', result ? JSON.stringify(result, null, 2) : '');
289
+ }
147
290
  async updateStatusInDatabase(stage, message, error = '', result = '') {
148
291
  try {
149
292
  const mqMessage = await this.mqMessageService.repo.findOne({
@@ -1 +1 @@
1
- {"version":3,"file":"rabbitmq-subscriber.service.js","sourceRoot":"","sources":["../../../src/services/queues/rabbitmq-subscriber.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Logger, OnModuleInit } from '@nestjs/common';\nimport * as amqp from 'amqplib';\nimport { QueuesModuleOptions } from \"../../interfaces\";\nimport { QueueMessage, QueueSubscriber } from '../../interfaces/mq';\nimport { MqMessageQueueService } from '../mq-message-queue.service';\nimport { MqMessageService } from '../mq-message.service';\n\n\nexport abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscriber<T> { // TODO This can be made a generic type for better type visibility\n private readonly logger = new Logger(RabbitMqSubscriber.name);\n private readonly url: string;\n private readonly serviceRole: string;\n\n constructor(\n protected readonly mqMessageService: MqMessageService,\n protected readonly mqMessageQueueService: MqMessageQueueService,\n ) {\n this.url = process.env.QUEUES_RABBIT_MQ_URL;\n this.serviceRole = process.env.QUEUES_SERVICE_ROLE;\n if (!this.url) {\n this.logger.debug('RabbitMqPublisher url is not defined in the environment variables');\n }\n if (!this.serviceRole) {\n this.logger.debug('Queue service Role is not defined in the environment variables');\n }\n // this.logger.debug(`RabbitMqSubscriber instance created with options: ${JSON.stringify(this.options())} and url: ${this.url}`);\n }\n\n abstract subscribe(message: QueueMessage<T>);\n\n abstract options(): QueuesModuleOptions;\n\n async establishConnection(): Promise<amqp.Connection> {\n\n const url = new URL(this.url);\n\n // this.logger.debug(`user: ${url.username}`);\n // // just for local debug, don’t log in prod\n // this.logger.debug(`pass: ${url.password}`);\n // this.logger.debug(`path (vhost): ${url.pathname}`);\n\n const connection = await amqp.connect({\n protocol: url.protocol.replace(':', ''),\n hostname: url.hostname,\n port: parseInt(url.port),\n username: url.username,\n password: decodeURIComponent(url.password),\n frameMax: 131072,\n });\n\n return connection\n }\n\n async onModuleInit(): Promise<void> {\n const solidCliRunning = process.env.SOLID_CLI_RUNNING || \"false\";\n\n // we will start subscriber only if the current service role is subscriber. \n if (this.url && ['both', 'subscriber'].includes(this.serviceRole) && solidCliRunning === \"false\") {\n\n // this.logger.debug(`RabbitMqSubscriber instance created with options: ${JSON.stringify(this.options())} and url: ${this.url}`);\n // const connection = await amqp.connect(this.url);\n\n let connection;\n try {\n connection = await this.establishConnection();\n // this.logger.debug(`RabbitMqSubscriber connection established: ${JSON.stringify(this.options())} and url: ${this.url}`);\n }\n catch (err) {\n this.logger.error(`Failed to connect to RabbitMQ: ${(err as Error).message}`, (err as Error).stack);\n throw err;\n }\n\n const channel = await connection.createChannel();\n // this.logger.debug(`RabbitMqSubscriber channel created: ${JSON.stringify(this.options())} and url: ${url}`);\n\n const options = this.options();\n\n const queueName = options.queueName;\n const exchangeName = `${queueName}.exchange`;\n const routingKey = `${queueName}.routing-key`;\n\n await channel.assertExchange(exchangeName, 'direct', {});\n // this.logger.debug(`RabbitMqSubscriber channel asserted: ${JSON.stringify(this.options())} and url: ${url}`);\n\n const queue = await channel.assertQueue(queueName, {});\n // this.logger.debug(`RabbitMqSubscriber queue asserted: ${JSON.stringify(this.options())} and url: ${url}`);\n\n await channel.bindQueue(queue.queue, exchangeName, routingKey);\n // this.logger.debug(`RabbitMqSubscriber queue bound: ${JSON.stringify(this.options())} and url: ${url}`);\n\n // Consume messages from the queue\n channel.consume(\n queue.queue,\n async (rawMessage) => {\n if (rawMessage) {\n const messageContentString = rawMessage.content.toString();\n // this.logger.debug(`RabbitMqSubscriber Received raw message: ${messageContentString}`);\n\n let message: QueueMessage<T> = null;\n\n try {\n message = JSON.parse(messageContentString) as QueueMessage<T>;\n\n // this is the first time we are receiving the message so we set the currentRetry to 0\n if (!message.retryCount) message.retryCount = 0;\n if (!message.retryInterval) message.retryInterval = 1000;\n if (!message.currentRetry) message.currentRetry = 0;\n\n await this.processMessage(message, rawMessage, channel);\n }\n catch (error) {\n this.logger.error(`Error processing message: ${error.message}`);\n\n // if an error occurs then if retryCount is set we start retrying. \n if (message) {\n if (message.currentRetry < message.retryCount) {\n await this.updateStatusInDatabase('retrying', message);\n\n message.currentRetry++;\n this.logger.warn(`Retrying message (${message.currentRetry}/${message.retryCount}) after ${message.retryInterval}ms`);\n setTimeout(() => {\n this.retryMessage(message, rawMessage, channel);\n }, message.retryInterval);\n } else {\n await this.updateStatusInDatabase('failed', message, error.message, '');\n\n this.logger.error(`Message failed after ${message.retryCount} attempts: ${error.message}`);\n channel.ack(rawMessage); // Discard the message after max retries\n }\n }\n\n }\n }\n },\n // { noAck: true },\n {},\n );\n\n this.logger.log(`RabbitMqSubscriber ready to consume messages: ${JSON.stringify(this.options())} and url: ${this.url}`);\n }\n }\n\n /**\n * Abstract method for message processing logic.\n */\n protected async processMessage(message: QueueMessage<T>, rawMessage, channel): Promise<void> {\n await this.updateStatusInDatabase('started', message);\n\n // Capture the results of handling the task.\n const result = await this.subscribe(message);\n\n // Ack the message. \n channel.ack(rawMessage);\n\n // TODO: Update the database to indicate that the task is finished.\n await this.updateStatusInDatabase('succeeded', message, '', result ? JSON.stringify(result, null, 2) : '');\n\n }\n\n /**\n * Retry the message by invoking the processing logic again.\n */\n private async retryMessage(message: QueueMessage<T>, rawMessage, channel) {\n try {\n await this.processMessage(message, rawMessage, channel);\n } catch (error) {\n if (message.currentRetry < message.retryCount) {\n await this.updateStatusInDatabase('retrying', message);\n\n message.currentRetry++;\n this.logger.warn(`Retrying message (${message.currentRetry}/${message.retryCount}) after ${message.retryInterval}ms: ${error.message}`);\n setTimeout(() => {\n this.retryMessage(message, rawMessage, channel);\n }, message.retryInterval);\n } else {\n\n this.logger.error(`Message failed after ${message.retryCount} attempts: ${error.message}`);\n\n // Discard the message after max retries\n channel.ack(rawMessage);\n\n // TODO: Store the error in the database and update the status accordingly.\n await this.updateStatusInDatabase('failed', message, error.message, '');\n\n }\n }\n }\n\n private async updateStatusInDatabase(stage: string, message: QueueMessage<T>, error: string = '', result: string = '') {\n\n // TODO: make an entry in the relevant database table, generate a unique id earlier.\n try {\n // 1. resolve the queue first\n const mqMessage = await this.mqMessageService.repo.findOne({\n where: {\n messageId: message.messageId,\n }\n });\n\n if (mqMessage) {\n const updatedFields = {\n stage: stage\n };\n if (stage === 'failed' || stage === 'succeeded') {\n updatedFields['finishedAt'] = new Date();\n updatedFields['elapsedMillis'] = updatedFields['finishedAt'].getTime() - mqMessage.startedAt.getTime();\n }\n if (stage === 'succeeded') {\n updatedFields['output'] = result;\n }\n if (stage === 'failed') {\n updatedFields['error'] = error;\n }\n await this.mqMessageService.repo.update(mqMessage.id, updatedFields);\n }\n }\n catch (error) {\n this.logger.error(error.message, error.stack);\n }\n\n }\n\n}"]}
1
+ {"version":3,"file":"rabbitmq-subscriber.service.js","sourceRoot":"","sources":["../../../src/services/queues/rabbitmq-subscriber.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Logger, OnModuleInit } from '@nestjs/common';\nimport * as amqp from 'amqplib';\nimport { QueuesModuleOptions } from \"../../interfaces\";\nimport { QueueMessage, QueueSubscriber } from '../../interfaces/mq';\nimport { MqMessageQueueService } from '../mq-message-queue.service';\nimport { MqMessageService } from '../mq-message.service';\n\n\nexport abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscriber<T> { // TODO This can be made a generic type for better type visibility\n private readonly logger = new Logger(RabbitMqSubscriber.name);\n private readonly url: string;\n private readonly serviceRole: string;\n private connection: amqp.Connection | null = null;\n private channel: amqp.Channel | null = null;\n private consumerTag: string | null = null;\n private reconnectPromise: Promise<void> | null = null;\n private reconnectAttempt = 0;\n private stopping = false;\n\n constructor(protected readonly mqMessageService: MqMessageService, protected readonly mqMessageQueueService: MqMessageQueueService) {\n this.url = process.env.QUEUES_RABBIT_MQ_URL;\n this.serviceRole = process.env.QUEUES_SERVICE_ROLE;\n if (!this.url) {\n this.logger.debug('RabbitMqPublisher url is not defined in the environment variables');\n }\n if (!this.serviceRole) {\n this.logger.debug('Queue service Role is not defined in the environment variables');\n }\n // this.logger.debug(`RabbitMqSubscriber instance created with options: ${JSON.stringify(this.options())} and url: ${this.url}`);\n }\n\n abstract subscribe(message: QueueMessage<T>);\n\n abstract options(): QueuesModuleOptions;\n\n async establishConnection(): Promise<amqp.Connection> {\n\n const url = new URL(this.url);\n\n // this.logger.debug(`user: ${url.username}`);\n // // just for local debug, don’t log in prod\n // this.logger.debug(`pass: ${url.password}`);\n // this.logger.debug(`path (vhost): ${url.pathname}`);\n\n const connection = await amqp.connect({\n protocol: url.protocol.replace(':', ''),\n hostname: url.hostname,\n port: parseInt(url.port),\n username: url.username,\n password: decodeURIComponent(url.password),\n frameMax: 131072,\n heartbeat: 30,\n });\n\n return connection\n }\n\n async onModuleInit(): Promise<void> {\n // Not using SettingService here as that will necessitate all implementors of RabbitMqSubscriber to also inject SettingService which is not ideal. \n // Instead we directly read the environment variables here.\n const defaultBroker = process.env.QUEUES_DEFAULT_BROKER || 'rabbitmq';\n const solidCliRunning = process.env.SOLID_CLI_RUNNING || \"false\";\n const queueNameRegex = (process.env.QUEUES_QUEUE_NAME_REGEX_TO_ENABLE || '').trim();\n\n // we will start subscriber only if the current service role is subscriber. \n if (this.url && ['both', 'subscriber'].includes(this.serviceRole) && solidCliRunning === \"false\" && defaultBroker === 'rabbitmq') {\n const options = this.options();\n const queueName = options.queueName;\n\n if (queueNameRegex && queueNameRegex !== \"all\") {\n try {\n const regex = new RegExp(queueNameRegex);\n if (!regex.test(queueName)) {\n this.logger.log(`RabbitMqSubscriber for queue ${queueName} is disabled because it does not match QUEUES_QUEUE_NAME_REGEX_TO_ENABLE=${queueNameRegex}`);\n return;\n }\n } catch (error) {\n this.logger.error(`Invalid QUEUES_QUEUE_NAME_REGEX_TO_ENABLE regex \"${queueNameRegex}\". Subscriber for queue ${queueName} will not start.`);\n return;\n }\n }\n\n try {\n await this.connectAndConsume(queueName);\n } catch (err) {\n this.logger.error(`Failed to connect to RabbitMQ for queue ${queueName}: ${(err as Error).message}`, (err as Error).stack);\n this.triggerReconnect(queueName, 'initial connection failure');\n }\n\n this.logger.log(`RabbitMqSubscriber ready to consume messages: ${JSON.stringify(this.options())} and url: ${this.url}`);\n }\n }\n\n private async connectAndConsume(queueName: string): Promise<void> {\n await this.cleanup();\n\n let connection: amqp.Connection;\n try {\n connection = await this.establishConnection();\n } catch (err) {\n this.logger.error(`Failed to connect to RabbitMQ for queue ${queueName}: ${(err as Error).message}`, (err as Error).stack);\n throw err;\n }\n\n this.connection = connection;\n\n connection.on('error', (err) => {\n if (connection !== this.connection) return;\n this.logger.error(`RabbitMqSubscriber connection error for queue ${queueName}: ${(err as Error).message}`);\n });\n\n connection.on('close', () => {\n if (connection !== this.connection) return;\n this.logger.warn(`RabbitMqSubscriber connection closed for queue ${queueName}`);\n this.triggerReconnect(queueName, 'connection closed');\n });\n\n const channel = await connection.createChannel();\n this.channel = channel;\n\n channel.on('error', (err) => {\n if (channel !== this.channel) return;\n this.logger.error(`RabbitMqSubscriber channel error for queue ${queueName}: ${(err as Error).message}`);\n });\n\n channel.on('close', () => {\n if (channel !== this.channel) return;\n this.logger.warn(`RabbitMqSubscriber channel closed for queue ${queueName}`);\n this.triggerReconnect(queueName, 'channel closed');\n });\n\n // Process one message at a time per consumer to avoid parallel work on the same subscriber instance.\n await channel.prefetch(1);\n\n // Use a direct exchange with a stable routing key so retry DLX can route back to the main queue.\n const exchangeName = `${queueName}.exchange`;\n const routingKey = `${queueName}.routing-key`;\n const retryQueue = `${queueName}.retry`;\n const failedQueue = `${queueName}.failed`;\n\n await channel.assertExchange(exchangeName, 'direct', {});\n await channel.assertQueue(queueName, {});\n await channel.bindQueue(queueName, exchangeName, routingKey);\n\n // Retry queue uses DLX to route expired messages back to the main exchange/routing key.\n await channel.assertQueue(retryQueue, {\n arguments: {\n 'x-dead-letter-exchange': exchangeName,\n 'x-dead-letter-routing-key': routingKey,\n }\n });\n\n await channel.assertQueue(failedQueue, {});\n\n const consumeResult = await channel.consume(\n queueName,\n async (rawMessage) => {\n if (!rawMessage) {\n return;\n }\n\n const messageContentString = rawMessage.content.toString();\n let message: QueueMessage<T> = null;\n\n try {\n message = JSON.parse(messageContentString) as QueueMessage<T>;\n } catch (error) {\n this.logger.error(`Invalid JSON message on queue ${queueName}: ${(error as Error).message}`);\n await this.publishToFailedQueue(queueName, rawMessage.content, channel, error);\n channel.ack(rawMessage);\n return;\n }\n\n if (!message.retryCount) message.retryCount = 0;\n if (!message.retryInterval) message.retryInterval = 1000;\n if (!message.currentRetry) message.currentRetry = 0;\n\n try {\n await this.processMessage(message, rawMessage, channel);\n } catch (error) {\n await this.handleProcessingError(message, rawMessage, channel, error, queueName);\n }\n },\n // Explicit ack enables reliable processing and retry routing.\n { noAck: false },\n );\n\n this.consumerTag = consumeResult.consumerTag;\n }\n\n // Retry flow: update DB -> increment retry -> send to retry queue with per-message expiration -> ack original.\n private async handleProcessingError(message: QueueMessage<T>, rawMessage: amqp.ConsumeMessage, channel: amqp.Channel, error: any, queueName: string): Promise<void> {\n const errorMessage = (error as Error)?.message || String(error);\n this.logger.error(`Error processing message on queue ${queueName}: ${errorMessage}`);\n\n if (message.currentRetry < message.retryCount) {\n await this.updateStatusInDatabase('retrying', message);\n\n message.currentRetry++;\n const retryQueue = `${queueName}.retry`;\n const payload = Buffer.from(JSON.stringify(message));\n\n // Per-message expiration keeps the message in the retry queue until TTL, then DLX routes it back.\n channel.sendToQueue(retryQueue, payload, {\n expiration: String(message.retryInterval || 1000),\n headers: {\n 'x-error': errorMessage,\n }\n });\n\n channel.ack(rawMessage);\n this.logger.warn(`Retrying message (${message.currentRetry}/${message.retryCount}) after ${message.retryInterval}ms on queue ${queueName}`);\n return;\n }\n\n await this.updateStatusInDatabase('failed', message, errorMessage, '');\n channel.ack(rawMessage);\n await this.publishToFailedQueue(queueName, Buffer.from(JSON.stringify(message)), channel, error);\n this.logger.error(`Message failed after ${message.retryCount} attempts on queue ${queueName}: ${errorMessage}`);\n }\n\n private async publishToFailedQueue(queueName: string, payload: Buffer | string, channel: amqp.Channel, error?: any): Promise<void> {\n const failedQueue = `${queueName}.failed`;\n const body = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);\n const errorMessage = (error as Error)?.message || String(error || '');\n\n try {\n channel.sendToQueue(failedQueue, body, errorMessage ? {\n headers: { 'x-error': errorMessage }\n } : undefined);\n } catch (err) {\n this.logger.error(`Failed to publish to failed queue ${failedQueue}: ${(err as Error).message}`);\n }\n }\n\n private triggerReconnect(queueName: string, reason: string) {\n if (this.stopping) return;\n if (this.reconnectPromise) return;\n\n this.reconnectPromise = this.reconnectLoop(queueName, reason)\n .finally(() => {\n this.reconnectPromise = null;\n });\n }\n\n // Reconnect with backoff to avoid hammering the broker during outages.\n private async reconnectLoop(queueName: string, reason: string): Promise<void> {\n this.logger.warn(`RabbitMqSubscriber reconnecting for queue ${queueName}: ${reason}`);\n\n while (!this.stopping) {\n try {\n await this.connectAndConsume(queueName);\n this.reconnectAttempt = 0;\n this.logger.log(`RabbitMqSubscriber reconnected for queue ${queueName}`);\n return;\n } catch (err) {\n this.reconnectAttempt += 1;\n const delay = this.backoff();\n this.logger.warn(`RabbitMqSubscriber reconnect failed for queue ${queueName}; retrying in ${delay}ms`);\n await this.sleep(delay);\n }\n }\n }\n\n private async cleanup(): Promise<void> {\n const channel = this.channel;\n const connection = this.connection;\n const consumerTag = this.consumerTag;\n\n this.channel = null;\n this.connection = null;\n this.consumerTag = null;\n\n if (channel) {\n try {\n if (consumerTag) {\n await channel.cancel(consumerTag);\n }\n } catch (_) {\n // ignore\n }\n\n try {\n await channel.close();\n } catch (_) {\n // ignore\n }\n }\n\n if (connection) {\n try {\n await connection.close();\n } catch (_) {\n // ignore\n }\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n\n // Exponential backoff with jitter, capped to 30s.\n private backoff(): number {\n const baseMs = 1000;\n const maxMs = 30_000;\n const exp = Math.min(maxMs, baseMs * Math.pow(2, this.reconnectAttempt));\n const jitter = Math.floor(Math.random() * (exp * 0.2));\n return Math.min(maxMs, exp + jitter);\n }\n\n /**\n * Abstract method for message processing logic.\n */\n protected async processMessage(message: QueueMessage<T>, rawMessage, channel): Promise<void> {\n await this.updateStatusInDatabase('started', message);\n\n // Capture the results of handling the task.\n const result = await this.subscribe(message);\n\n // Ack the message. \n channel.ack(rawMessage);\n\n // Persist success output and timing.\n await this.updateStatusInDatabase('succeeded', message, '', result ? JSON.stringify(result, null, 2) : '');\n\n }\n\n private async updateStatusInDatabase(stage: string, message: QueueMessage<T>, error: string = '', result: string = '') {\n\n // Update the existing message record by messageId; creation happens upstream.\n try {\n // 1. resolve the queue first\n const mqMessage = await this.mqMessageService.repo.findOne({\n where: {\n messageId: message.messageId,\n }\n });\n\n if (mqMessage) {\n const updatedFields = {\n stage: stage\n };\n if (stage === 'failed' || stage === 'succeeded') {\n updatedFields['finishedAt'] = new Date();\n updatedFields['elapsedMillis'] = updatedFields['finishedAt'].getTime() - mqMessage.startedAt.getTime();\n }\n if (stage === 'succeeded') {\n updatedFields['output'] = result;\n }\n if (stage === 'failed') {\n updatedFields['error'] = error;\n }\n await this.mqMessageService.repo.update(mqMessage.id, updatedFields);\n }\n }\n catch (error) {\n this.logger.error(error.message, error.stack);\n }\n\n }\n\n}\n"]}
@@ -5,6 +5,7 @@ export declare class SchedulerServiceImpl implements ISchedulerService {
5
5
  private readonly scheduledJobRepo;
6
6
  private readonly solidRegistry;
7
7
  private readonly logger;
8
+ private readonly runningJobs;
8
9
  constructor(scheduledJobRepo: ScheduledJobRepository, solidRegistry: SolidRegistry);
9
10
  runScheduledJobs(): Promise<void>;
10
11
  private shouldRunNow;
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.service.d.ts","sourceRoot":"","sources":["../../../src/services/scheduled-jobs/scheduler.service.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG1D,qBACa,oBAAqB,YAAW,iBAAiB;IAMtD,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa;IANlC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;gBAK3C,gBAAgB,EAAE,sBAAsB,EACxC,aAAa,EAAE,aAAa;IAI3C,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAwDvC,OAAO,CAAC,YAAY;IA0CpB,OAAO,CAAC,2BAA2B;IA8BnC,OAAO,CAAC,gBAAgB;CAwB3B"}
1
+ {"version":3,"file":"scheduler.service.d.ts","sourceRoot":"","sources":["../../../src/services/scheduled-jobs/scheduler.service.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG1D,qBACa,oBAAqB,YAAW,iBAAiB;IAOtD,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAPlC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;IAChE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;gBAK5B,gBAAgB,EAAE,sBAAsB,EACxC,aAAa,EAAE,aAAa;IAI3C,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAwEvC,OAAO,CAAC,YAAY;IA0CpB,OAAO,CAAC,2BAA2B;IA8BnC,OAAO,CAAC,gBAAgB;CAwB3B"}
@@ -22,8 +22,13 @@ let SchedulerServiceImpl = SchedulerServiceImpl_1 = class SchedulerServiceImpl {
22
22
  this.scheduledJobRepo = scheduledJobRepo;
23
23
  this.solidRegistry = solidRegistry;
24
24
  this.logger = new common_1.Logger(SchedulerServiceImpl_1.name);
25
+ this.runningJobs = new Set();
25
26
  }
26
27
  async runScheduledJobs() {
28
+ const solidSchedulerEnabled = process.env.SOLID_SCHEDULER_ENABLED || "true";
29
+ if (solidSchedulerEnabled.toLowerCase() !== "true") {
30
+ return;
31
+ }
27
32
  const solidCliRunning = process.env.SOLID_CLI_RUNNING || "false";
28
33
  if (solidCliRunning === "true") {
29
34
  return;
@@ -42,20 +47,28 @@ let SchedulerServiceImpl = SchedulerServiceImpl_1 = class SchedulerServiceImpl {
42
47
  ],
43
48
  });
44
49
  for (const job of dueJobs) {
50
+ const jobKey = String(job.id ?? job.scheduleName ?? job.job);
51
+ if (this.runningJobs.has(jobKey)) {
52
+ this.logger.log(`[${now.getTime()}]: scheduler service skipping job ${job.job} because a run is already in progress`);
53
+ continue;
54
+ }
55
+ if (!this.shouldRunNow(job, now)) {
56
+ this.logger.log(`[${now.getTime()}]: scheduler service skipping job ${job.job}`);
57
+ continue;
58
+ }
59
+ const handler = this.solidRegistry.getScheduledJobProviderInstance(job.job);
60
+ if (!handler) {
61
+ this.logger.warn(`[${now.getTime()}]: scheduler service skipping because job handler not found: ${job.job}`);
62
+ continue;
63
+ }
64
+ this.runningJobs.add(jobKey);
45
65
  this.logger.log(`[${now.getTime()}]: scheduler service attempting to run job ${job.job}`);
46
66
  try {
47
- if (!this.shouldRunNow(job, now)) {
48
- this.logger.log(`[${now.getTime()}]: scheduler service skipping job ${job.job}`);
49
- continue;
50
- }
51
- const handler = this.solidRegistry.getScheduledJobProviderInstance(job.job);
52
- if (!handler) {
53
- continue;
54
- }
55
67
  await handler.execute(job);
56
68
  job.isActive = true;
57
- job.lastRunAt = now;
58
- job.nextRunAt = this.computeNextRunAt(job, now);
69
+ const finishedAt = new Date();
70
+ job.lastRunAt = finishedAt;
71
+ job.nextRunAt = this.computeNextRunAt(job, finishedAt);
59
72
  this.logger.log(`[${now.getTime()}]: scheduler service coomputed next run for ${job.job} as ${job.nextRunAt}`);
60
73
  await this.scheduledJobRepo.save(job);
61
74
  this.logger.log(`[${now.getTime()}]: scheduler service finished running job: ${job.job}`);
@@ -63,6 +76,9 @@ let SchedulerServiceImpl = SchedulerServiceImpl_1 = class SchedulerServiceImpl {
63
76
  catch (err) {
64
77
  this.logger.error(`[${now.getTime()}]: scheduler service failed to run job ${job.job}`, err.stack);
65
78
  }
79
+ finally {
80
+ this.runningJobs.delete(jobKey);
81
+ }
66
82
  }
67
83
  }
68
84
  shouldRunNow(job, now) {
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.service.js","sourceRoot":"","sources":["../../../src/services/scheduled-jobs/scheduler.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAAoD;AACpD,qCAAkD;AAElD,+CAAwD;AAExD,iEAA2D;AAC3D,wFAAiF;AAEjF,6CAAmD;AAG5C,IAAM,oBAAoB,4BAA1B,MAAM,oBAAoB;IAG7B,YAGqB,gBAAwC,EACxC,aAA4B;QAD5B,qBAAgB,GAAhB,gBAAgB,CAAwB;QACxC,kBAAa,GAAb,aAAa,CAAe;QANhC,WAAM,GAAG,IAAI,eAAM,CAAC,sBAAoB,CAAC,IAAI,CAAC,CAAC;IAO5D,CAAC;IAGC,AAAN,KAAK,CAAC,gBAAgB;QAClB,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,OAAO,CAAC;QACjE,IAAI,eAAe,KAAK,MAAM,EAAE,CAAC;YAC7B,OAAO;QACX,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QAGvB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC;YAC7C,KAAK,EAAE;gBACH;oBACI,QAAQ,EAAE,IAAI;oBACd,SAAS,EAAE,IAAA,yBAAe,EAAC,GAAG,CAAC;iBAClC;gBAED;oBACI,QAAQ,EAAE,IAAI;oBACd,SAAS,EAAE,IAAA,gBAAM,GAAE;iBACtB;aACJ;SACJ,CAAC,CAAC;QAIH,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,8CAA8C,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;YAC1F,IAAI,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;oBAC/B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,qCAAqC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;oBACjF,SAAS;gBACb,CAAC;gBAED,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,+BAA+B,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC5E,IAAI,CAAC,OAAO,EAAE,CAAC;oBAEX,SAAS;gBACb,CAAC;gBAGD,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBAG3B,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACpB,GAAG,CAAC,SAAS,GAAG,GAAG,CAAC;gBACpB,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBAChD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,+CAA+C,GAAG,CAAC,GAAG,OAAO,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;gBAE/G,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACtC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,8CAA8C,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;YAC9F,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACX,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,0CAA0C,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;YACvG,CAAC;QACL,CAAC;IACL,CAAC;IAEO,YAAY,CAAC,GAAiB,EAAE,GAAS;QAC7C,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAG/C,IAAI,GAAG,CAAC,SAAS,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO,KAAK,CAAC;QAC7E,IAAI,GAAG,CAAC,OAAO,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,KAAK,CAAC;QAGzE,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAChB,MAAM,QAAQ,GAAG,GAAG,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1D,IAAI,OAAO,GAAG,QAAQ;gBAAE,OAAO,KAAK,CAAC;QACzC,CAAC;QACD,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;YACd,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACtD,IAAI,OAAO,GAAG,MAAM;gBAAE,OAAO,KAAK,CAAC;QACvC,CAAC;QAGD,IAAI,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,QAAQ,EAAE,CAAC;YAG3C,OAAO,IAAI,CAAC;QAChB,CAAC;QAGD,IAAI,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,QAAQ,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAC5D,MAAM,SAAS,GAAG,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YAEnE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAa,CAAC;YACnD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAAE,OAAO,KAAK,CAAC;QAChD,CAAC;QAGD,IAAI,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,SAAS,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;YAC9D,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;YAC1B,IAAI,GAAG,KAAK,GAAG,CAAC,UAAU;gBAAE,OAAO,KAAK,CAAC;QAC7C,CAAC;QAED,OAAO,IAAI,CAAC;IAChB,CAAC;IAEO,2BAA2B,CAAC,GAAiB,EAAE,IAAU;QAC7D,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;QAE5B,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;YACtB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,oDAAoD,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;YAE1F,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1D,CAAC;QAED,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,kCAAoB,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,EAAE;gBAC5D,WAAW,EAAE,IAAI;gBACjB,EAAE,EAAE,KAAK;aACZ,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;YAGzC,IAAI,OAAO,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;gBAC7C,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;YAC1E,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,gBAAgB,GAAG,CAAC,cAAc,eAAe,OAAO,EAAE,CAAC,CAAC;YAC5E,OAAO,OAAO,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,GAAG,CAAC,YAAY,KAAK,GAAG,CAAC,cAAc,EAAE,EAAE,KAAK,CAAC,CAAC;YAEvG,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1D,CAAC;IACL,CAAC;IAEO,gBAAgB,CAAC,GAAiB,EAAE,IAAU;QAClD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;QAE5B,QAAQ,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC;YAGlC,KAAK,cAAc;gBACf,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YACpD,KAAK,QAAQ;gBACT,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YACrD,KAAK,OAAO;gBACR,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YAC1D,KAAK,QAAQ;gBACT,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YAC9D,KAAK,SAAS;gBACV,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC5B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;gBACnC,OAAO,IAAI,CAAC;YAChB,KAAK,QAAQ;gBACT,OAAO,IAAI,CAAC,2BAA2B,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACvD;gBACI,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC9D,CAAC;IACL,CAAC;CACJ,CAAA;AAnKY,oDAAoB;AAWvB;IADL,IAAA,eAAI,EAAC,yBAAc,CAAC,YAAY,CAAC;;;;4DAuDjC;+BAjEQ,oBAAoB;IADhC,IAAA,mBAAU,GAAE;qCAO8B,iDAAsB;QACzB,8BAAa;GAPxC,oBAAoB,CAmKhC","sourcesContent":["import { Injectable, Logger } from '@nestjs/common';\nimport { IsNull, LessThanOrEqual } from 'typeorm';\n\nimport { Cron, CronExpression } from '@nestjs/schedule';\nimport { ScheduledJob } from 'src/entities/scheduled-job.entity';\nimport { SolidRegistry } from 'src/helpers/solid-registry';\nimport { ScheduledJobRepository } from 'src/repository/scheduled-job.repository';\nimport { ISchedulerService } from './scheduler.interface';\nimport { CronExpressionParser } from 'cron-parser';\n\n@Injectable()\nexport class SchedulerServiceImpl implements ISchedulerService {\n private readonly logger = new Logger(SchedulerServiceImpl.name);\n\n constructor(\n // @InjectRepository(ScheduledJob)\n // private readonly scheduledJobRepo: Repository<ScheduledJob>,\n private readonly scheduledJobRepo: ScheduledJobRepository,\n private readonly solidRegistry: SolidRegistry,\n ) { }\n\n @Cron(CronExpression.EVERY_MINUTE)\n async runScheduledJobs(): Promise<void> {\n const solidCliRunning = process.env.SOLID_CLI_RUNNING || \"false\";\n if (solidCliRunning === \"true\") {\n return;\n }\n\n const now = new Date();\n\n // this.logger.log(`[${now.getTime()}]: scheduler service started run...`);\n const dueJobs = await this.scheduledJobRepo.find({\n where: [\n {\n isActive: true,\n nextRunAt: LessThanOrEqual(now),\n },\n // Newly created jobs are also picked for examination \n {\n isActive: true,\n nextRunAt: IsNull(),\n },\n ],\n });\n\n // this.logger.log(`[${now.getTime()}]: scheduler service identified ${dueJobs.length} jobs to run...`);\n\n for (const job of dueJobs) {\n this.logger.log(`[${now.getTime()}]: scheduler service attempting to run job ${job.job}`);\n try {\n if (!this.shouldRunNow(job, now)) {\n this.logger.log(`[${now.getTime()}]: scheduler service skipping job ${job.job}`);\n continue;\n }\n\n const handler = this.solidRegistry.getScheduledJobProviderInstance(job.job);\n if (!handler) {\n // this.logger.warn(`[${now.getTime()}]: scheduler service skipping because job handler not found: ${job.job}`);\n continue;\n }\n\n // this.logger.log(`[${now.getTime()}]: scheduler service about to run job ${job.job}`);\n await handler.execute(job);\n // this.logger.log(`[${now.getTime()}]: scheduler service finished running job ${job.job}`);\n\n job.isActive = true;\n job.lastRunAt = now;\n job.nextRunAt = this.computeNextRunAt(job, now);\n this.logger.log(`[${now.getTime()}]: scheduler service coomputed next run for ${job.job} as ${job.nextRunAt}`);\n\n await this.scheduledJobRepo.save(job);\n this.logger.log(`[${now.getTime()}]: scheduler service finished running job: ${job.job}`);\n } catch (err) {\n this.logger.error(`[${now.getTime()}]: scheduler service failed to run job ${job.job}`, err.stack);\n }\n }\n }\n\n private shouldRunNow(job: ScheduledJob, now: Date): boolean {\n const today = now.toISOString().split('T')[0]; // yyyy-mm-dd\n const timeNow = now.toTimeString().slice(0, 5); // hh:mm\n\n // 1. Check startDate / endDate\n if (job.startDate && new Date(today) < new Date(job.startDate)) return false;\n if (job.endDate && new Date(today) > new Date(job.endDate)) return false;\n\n // 2. Check startTime / endTime\n if (job.startTime) {\n const jobStart = job.startTime.toTimeString().slice(0, 5);\n if (timeNow < jobStart) return false;\n }\n if (job.endTime) {\n const jobEnd = job.endTime.toTimeString().slice(0, 5);\n if (timeNow > jobEnd) return false;\n }\n\n // 3. Check custom frequency\n if (job.frequency.toLowerCase() === 'custom') {\n // Custom cron expressions handle their own scheduling logic\n // Just check if nextRunAt is due, which was already checked in the query\n return true;\n }\n\n // 3. Check dayOfWeek (for weekly)\n if (job.frequency.toLowerCase() === 'weekly' && job.dayOfWeek) {\n const todayName = now.toLocaleString('en-US', { weekday: 'long' }); // e.g., \"Monday\"\n // const days = job.dayOfWeek.split(',').map(d => d.trim());\n const days = JSON.parse(job.dayOfWeek) as string[];\n if (!days.includes(todayName)) return false;\n }\n\n // 4. Check dayOfMonth (for monthly)\n if (job.frequency.toLowerCase() === 'monthly' && job.dayOfMonth) {\n const dom = now.getDate();\n if (dom !== job.dayOfMonth) return false;\n }\n\n return true;\n }\n\n private computeNextRunForCustomCron(job: ScheduledJob, from: Date): Date {\n const base = new Date(from);\n\n if (!job.cronExpression) {\n this.logger.error(`Custom frequency requires cronExpression for job ${job.scheduleName}`);\n // Fallback to daily if cron expression is missing\n return new Date(base.getTime() + 24 * 60 * 60 * 1000);\n }\n\n try {\n const interval = CronExpressionParser.parse(job.cronExpression, {\n currentDate: from,\n tz: 'UTC'\n });\n const nextRun = interval.next().toDate();\n\n // Validate minimum 1 minute interval\n if (nextRun.getTime() - from.getTime() < 60000) {\n throw new Error('Cron expression interval must be at least 1 minute');\n }\n \n this.logger.log(`Custom cron '${job.cronExpression}' next run: ${nextRun}`);\n return nextRun;\n } catch (error) {\n this.logger.error(`Invalid cron expression for job ${job.scheduleName}: ${job.cronExpression}`, error);\n // Fallback to daily if cron parsing fails\n return new Date(base.getTime() + 24 * 60 * 60 * 1000);\n }\n }\n\n private computeNextRunAt(job: ScheduledJob, from: Date): Date {\n const base = new Date(from);\n\n switch (job.frequency.toLowerCase()) {\n // case 'once':\n // return null; // don't reschedule\n case 'every minute':\n return new Date(base.getTime() + 1 * 60 * 1000);\n case 'hourly':\n return new Date(base.getTime() + 60 * 60 * 1000);\n case 'daily':\n return new Date(base.getTime() + 24 * 60 * 60 * 1000);\n case 'weekly':\n return new Date(base.getTime() + 7 * 24 * 60 * 60 * 1000);\n case 'monthly':\n const next = new Date(base);\n next.setMonth(base.getMonth() + 1);\n return next;\n case 'custom':\n return this.computeNextRunForCustomCron(job, from);\n default:\n return new Date(base.getTime() + 24 * 60 * 60 * 1000);\n }\n }\n}"]}
1
+ {"version":3,"file":"scheduler.service.js","sourceRoot":"","sources":["../../../src/services/scheduled-jobs/scheduler.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAAoD;AACpD,qCAAkD;AAElD,+CAAwD;AAExD,iEAA2D;AAC3D,wFAAiF;AAEjF,6CAAmD;AAG5C,IAAM,oBAAoB,4BAA1B,MAAM,oBAAoB;IAI7B,YAGqB,gBAAwC,EACxC,aAA4B;QAD5B,qBAAgB,GAAhB,gBAAgB,CAAwB;QACxC,kBAAa,GAAb,aAAa,CAAe;QAPhC,WAAM,GAAG,IAAI,eAAM,CAAC,sBAAoB,CAAC,IAAI,CAAC,CAAC;QAC/C,gBAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IAO7C,CAAC;IAGC,AAAN,KAAK,CAAC,gBAAgB;QAClB,MAAM,qBAAqB,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,MAAM,CAAC;QAC5E,IAAI,qBAAqB,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;YAEjD,OAAO;QACX,CAAC;QACD,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,OAAO,CAAC;QACjE,IAAI,eAAe,KAAK,MAAM,EAAE,CAAC;YAC7B,OAAO;QACX,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QAGvB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC;YAC7C,KAAK,EAAE;gBACH;oBACI,QAAQ,EAAE,IAAI;oBACd,SAAS,EAAE,IAAA,yBAAe,EAAC,GAAG,CAAC;iBAClC;gBAED;oBACI,QAAQ,EAAE,IAAI;oBACd,SAAS,EAAE,IAAA,gBAAM,GAAE;iBACtB;aACJ;SACJ,CAAC,CAAC;QAIH,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;YAE7D,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC/B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,qCAAqC,GAAG,CAAC,GAAG,uCAAuC,CAAC,CAAC;gBACtH,SAAS;YACb,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;gBAC/B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,qCAAqC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;gBACjF,SAAS;YACb,CAAC;YAED,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,+BAA+B,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC5E,IAAI,CAAC,OAAO,EAAE,CAAC;gBACX,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,gEAAgE,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;gBAC7G,SAAS;YACb,CAAC;YAED,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAC7B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,8CAA8C,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;YAC1F,IAAI,CAAC;gBAED,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBAG3B,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACpB,MAAM,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC;gBAC9B,GAAG,CAAC,SAAS,GAAG,UAAU,CAAC;gBAC3B,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;gBACvD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,+CAA+C,GAAG,CAAC,GAAG,OAAO,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;gBAE/G,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACtC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,8CAA8C,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;YAC9F,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACX,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,0CAA0C,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;YACvG,CAAC;oBAAS,CAAC;gBACP,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACpC,CAAC;QACL,CAAC;IACL,CAAC;IAEO,YAAY,CAAC,GAAiB,EAAE,GAAS;QAC7C,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAG/C,IAAI,GAAG,CAAC,SAAS,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO,KAAK,CAAC;QAC7E,IAAI,GAAG,CAAC,OAAO,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,KAAK,CAAC;QAGzE,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAChB,MAAM,QAAQ,GAAG,GAAG,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1D,IAAI,OAAO,GAAG,QAAQ;gBAAE,OAAO,KAAK,CAAC;QACzC,CAAC;QACD,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;YACd,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACtD,IAAI,OAAO,GAAG,MAAM;gBAAE,OAAO,KAAK,CAAC;QACvC,CAAC;QAGD,IAAI,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,QAAQ,EAAE,CAAC;YAG3C,OAAO,IAAI,CAAC;QAChB,CAAC;QAGD,IAAI,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,QAAQ,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAC5D,MAAM,SAAS,GAAG,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YAEnE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAa,CAAC;YACnD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAAE,OAAO,KAAK,CAAC;QAChD,CAAC;QAGD,IAAI,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,SAAS,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;YAC9D,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;YAC1B,IAAI,GAAG,KAAK,GAAG,CAAC,UAAU;gBAAE,OAAO,KAAK,CAAC;QAC7C,CAAC;QAED,OAAO,IAAI,CAAC;IAChB,CAAC;IAEO,2BAA2B,CAAC,GAAiB,EAAE,IAAU;QAC7D,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;QAE5B,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;YACtB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,oDAAoD,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;YAE1F,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1D,CAAC;QAED,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,kCAAoB,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,EAAE;gBAC5D,WAAW,EAAE,IAAI;gBACjB,EAAE,EAAE,KAAK;aACZ,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;YAGzC,IAAI,OAAO,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;gBAC7C,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;YAC1E,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,gBAAgB,GAAG,CAAC,cAAc,eAAe,OAAO,EAAE,CAAC,CAAC;YAC5E,OAAO,OAAO,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,GAAG,CAAC,YAAY,KAAK,GAAG,CAAC,cAAc,EAAE,EAAE,KAAK,CAAC,CAAC;YAEvG,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1D,CAAC;IACL,CAAC;IAEO,gBAAgB,CAAC,GAAiB,EAAE,IAAU;QAClD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;QAE5B,QAAQ,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC;YAGlC,KAAK,cAAc;gBACf,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YACpD,KAAK,QAAQ;gBACT,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YACrD,KAAK,OAAO;gBACR,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YAC1D,KAAK,QAAQ;gBACT,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YAC9D,KAAK,SAAS;gBACV,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC5B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;gBACnC,OAAO,IAAI,CAAC;YAChB,KAAK,QAAQ;gBACT,OAAO,IAAI,CAAC,2BAA2B,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACvD;gBACI,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC9D,CAAC;IACL,CAAC;CACJ,CAAA;AApLY,oDAAoB;AAYvB;IADL,IAAA,eAAI,EAAC,yBAAc,CAAC,YAAY,CAAC;;;;4DAuEjC;+BAlFQ,oBAAoB;IADhC,IAAA,mBAAU,GAAE;qCAQ8B,iDAAsB;QACzB,8BAAa;GARxC,oBAAoB,CAoLhC","sourcesContent":["import { Injectable, Logger } from '@nestjs/common';\nimport { IsNull, LessThanOrEqual } from 'typeorm';\n\nimport { Cron, CronExpression } from '@nestjs/schedule';\nimport { ScheduledJob } from 'src/entities/scheduled-job.entity';\nimport { SolidRegistry } from 'src/helpers/solid-registry';\nimport { ScheduledJobRepository } from 'src/repository/scheduled-job.repository';\nimport { ISchedulerService } from './scheduler.interface';\nimport { CronExpressionParser } from 'cron-parser';\n\n@Injectable()\nexport class SchedulerServiceImpl implements ISchedulerService {\n private readonly logger = new Logger(SchedulerServiceImpl.name);\n private readonly runningJobs = new Set<string>();\n\n constructor(\n // @InjectRepository(ScheduledJob)\n // private readonly scheduledJobRepo: Repository<ScheduledJob>,\n private readonly scheduledJobRepo: ScheduledJobRepository,\n private readonly solidRegistry: SolidRegistry,\n ) { }\n\n @Cron(CronExpression.EVERY_MINUTE)\n async runScheduledJobs(): Promise<void> {\n const solidSchedulerEnabled = process.env.SOLID_SCHEDULER_ENABLED || \"true\";\n if (solidSchedulerEnabled.toLowerCase() !== \"true\") {\n // this.logger.debug('Solid scheduler is disabled via environment variable');\n return;\n }\n const solidCliRunning = process.env.SOLID_CLI_RUNNING || \"false\";\n if (solidCliRunning === \"true\") {\n return;\n }\n\n const now = new Date();\n\n // this.logger.log(`[${now.getTime()}]: scheduler service started run...`);\n const dueJobs = await this.scheduledJobRepo.find({\n where: [\n {\n isActive: true,\n nextRunAt: LessThanOrEqual(now),\n },\n // Newly created jobs are also picked for examination \n {\n isActive: true,\n nextRunAt: IsNull(),\n },\n ],\n });\n\n // this.logger.log(`[${now.getTime()}]: scheduler service identified ${dueJobs.length} jobs to run...`);\n\n for (const job of dueJobs) {\n const jobKey = String(job.id ?? job.scheduleName ?? job.job);\n\n if (this.runningJobs.has(jobKey)) {\n this.logger.log(`[${now.getTime()}]: scheduler service skipping job ${job.job} because a run is already in progress`);\n continue;\n }\n\n if (!this.shouldRunNow(job, now)) {\n this.logger.log(`[${now.getTime()}]: scheduler service skipping job ${job.job}`);\n continue;\n }\n\n const handler = this.solidRegistry.getScheduledJobProviderInstance(job.job);\n if (!handler) {\n this.logger.warn(`[${now.getTime()}]: scheduler service skipping because job handler not found: ${job.job}`);\n continue;\n }\n\n this.runningJobs.add(jobKey);\n this.logger.log(`[${now.getTime()}]: scheduler service attempting to run job ${job.job}`);\n try {\n // this.logger.log(`[${now.getTime()}]: scheduler service about to run job ${job.job}`);\n await handler.execute(job);\n // this.logger.log(`[${now.getTime()}]: scheduler service finished running job ${job.job}`);\n\n job.isActive = true;\n const finishedAt = new Date();\n job.lastRunAt = finishedAt;\n job.nextRunAt = this.computeNextRunAt(job, finishedAt);\n this.logger.log(`[${now.getTime()}]: scheduler service coomputed next run for ${job.job} as ${job.nextRunAt}`);\n\n await this.scheduledJobRepo.save(job);\n this.logger.log(`[${now.getTime()}]: scheduler service finished running job: ${job.job}`);\n } catch (err) {\n this.logger.error(`[${now.getTime()}]: scheduler service failed to run job ${job.job}`, err.stack);\n } finally {\n this.runningJobs.delete(jobKey);\n }\n }\n }\n\n private shouldRunNow(job: ScheduledJob, now: Date): boolean {\n const today = now.toISOString().split('T')[0]; // yyyy-mm-dd\n const timeNow = now.toTimeString().slice(0, 5); // hh:mm\n\n // 1. Check startDate / endDate\n if (job.startDate && new Date(today) < new Date(job.startDate)) return false;\n if (job.endDate && new Date(today) > new Date(job.endDate)) return false;\n\n // 2. Check startTime / endTime\n if (job.startTime) {\n const jobStart = job.startTime.toTimeString().slice(0, 5);\n if (timeNow < jobStart) return false;\n }\n if (job.endTime) {\n const jobEnd = job.endTime.toTimeString().slice(0, 5);\n if (timeNow > jobEnd) return false;\n }\n\n // 3. Check custom frequency\n if (job.frequency.toLowerCase() === 'custom') {\n // Custom cron expressions handle their own scheduling logic\n // Just check if nextRunAt is due, which was already checked in the query\n return true;\n }\n\n // 3. Check dayOfWeek (for weekly)\n if (job.frequency.toLowerCase() === 'weekly' && job.dayOfWeek) {\n const todayName = now.toLocaleString('en-US', { weekday: 'long' }); // e.g., \"Monday\"\n // const days = job.dayOfWeek.split(',').map(d => d.trim());\n const days = JSON.parse(job.dayOfWeek) as string[];\n if (!days.includes(todayName)) return false;\n }\n\n // 4. Check dayOfMonth (for monthly)\n if (job.frequency.toLowerCase() === 'monthly' && job.dayOfMonth) {\n const dom = now.getDate();\n if (dom !== job.dayOfMonth) return false;\n }\n\n return true;\n }\n\n private computeNextRunForCustomCron(job: ScheduledJob, from: Date): Date {\n const base = new Date(from);\n\n if (!job.cronExpression) {\n this.logger.error(`Custom frequency requires cronExpression for job ${job.scheduleName}`);\n // Fallback to daily if cron expression is missing\n return new Date(base.getTime() + 24 * 60 * 60 * 1000);\n }\n\n try {\n const interval = CronExpressionParser.parse(job.cronExpression, {\n currentDate: from,\n tz: 'UTC'\n });\n const nextRun = interval.next().toDate();\n\n // Validate minimum 1 minute interval\n if (nextRun.getTime() - from.getTime() < 60000) {\n throw new Error('Cron expression interval must be at least 1 minute');\n }\n \n this.logger.log(`Custom cron '${job.cronExpression}' next run: ${nextRun}`);\n return nextRun;\n } catch (error) {\n this.logger.error(`Invalid cron expression for job ${job.scheduleName}: ${job.cronExpression}`, error);\n // Fallback to daily if cron parsing fails\n return new Date(base.getTime() + 24 * 60 * 60 * 1000);\n }\n }\n\n private computeNextRunAt(job: ScheduledJob, from: Date): Date {\n const base = new Date(from);\n\n switch (job.frequency.toLowerCase()) {\n // case 'once':\n // return null; // don't reschedule\n case 'every minute':\n return new Date(base.getTime() + 1 * 60 * 1000);\n case 'hourly':\n return new Date(base.getTime() + 60 * 60 * 1000);\n case 'daily':\n return new Date(base.getTime() + 24 * 60 * 60 * 1000);\n case 'weekly':\n return new Date(base.getTime() + 7 * 24 * 60 * 60 * 1000);\n case 'monthly':\n const next = new Date(base);\n next.setMonth(base.getMonth() + 1);\n return next;\n case 'custom':\n return this.computeNextRunForCustomCron(job, from);\n default:\n return new Date(base.getTime() + 24 * 60 * 60 * 1000);\n }\n }\n}\n"]}