@jetit/publisher 1.1.0 → 1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetit/publisher",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "type": "commonjs",
5
5
  "dependencies": {
6
6
  "@jetit/id": "0.0.11",
@@ -51,7 +51,7 @@ class ScheduledProcessor {
51
51
  * Instead of using the publish method directly, the entire logic is
52
52
  * copy pasted to reduce the case of failure.
53
53
  */
54
- const transaction = this.redisPublisher.multi();
54
+ const transaction = this.redisPublisher.multi({ pipeline: true });
55
55
  eventData.eventId = (0, id_1.generateID)('HEX', 'FF');
56
56
  transaction.zrem('se', eventString);
57
57
  const consumerGroups = yield (0, groups_1.getAllConsumerGroups)(eventData.eventName, this.redisPublisher);
@@ -27,6 +27,7 @@ export declare class Streams {
27
27
  * const streams = new Streams('POS');
28
28
  */
29
29
  constructor(serviceName: string);
30
+ private runClear;
30
31
  private createConsumerGroup;
31
32
  private isDuplicateMessage;
32
33
  private clearDuplicationCheckKeys;
@@ -124,7 +125,7 @@ export declare class Streams {
124
125
  * // Attempt to recover messages from the "order.created" stream with an idle timeout of 10 seconds
125
126
  * await streams.recoverCrashedConsumerMessages('order.created', 10000);
126
127
  */
127
- recoverCrashedConsumerMessages(eventName: string, idleTimeout: number): Promise<void>;
128
+ recoverCrashedConsumerMessages(eventName: string, idleTimeout?: number): Promise<void>;
128
129
  /**
129
130
  * This method allows the possibility of a graceful shutdown by cleaning up the
130
131
  * redis connections.
@@ -6,6 +6,9 @@ const registry_1 = require("./registry");
6
6
  const rxjs_1 = require("rxjs");
7
7
  const id_1 = require("@jetit/id");
8
8
  const groups_1 = require("./groups");
9
+ function publisherErrorHandler(error) {
10
+ console.error('Publisher Error: ', error);
11
+ }
9
12
  class Streams {
10
13
  get redisPublisher() {
11
14
  if (!this._redisPublisher)
@@ -41,14 +44,24 @@ class Streams {
41
44
  this.eventsListened = [];
42
45
  this.instanceId = `${serviceName}:${(0, id_1.generateID)('HEX', 'FE')}`;
43
46
  this.consumerGroupName = `cg-${serviceName}`;
44
- const cleanUpInterval = (_a = parseInt(process.env['CLEANUP_INTERVAL'] || '1000 * 60 * 60', 10)) !== null && _a !== void 0 ? _a : 1000 * 60 * 60;
47
+ const cleanUpInterval = (_a = parseInt(process.env['CLEANUP_INTERVAL'] || `${1000 * 60 * 60}`, 10)) !== null && _a !== void 0 ? _a : 1000 * 60 * 60;
48
+ setTimeout(() => this.runClear(cleanUpInterval), 3000);
45
49
  this.cleanUpTimer = setInterval(() => {
50
+ this.runClear(cleanUpInterval);
51
+ }, cleanUpInterval);
52
+ }
53
+ runClear(cleanUpInterval) {
54
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
55
+ console.log('Running Publisher Clearance');
46
56
  this.clearDuplicationCheckKeys();
47
57
  for (const eventName of this.eventsListened) {
48
- this.cleanupAcknowledgedMessages(eventName, cleanUpInterval);
49
- this.republishUnprocessedEvents(eventName);
58
+ process.nextTick(() => tslib_1.__awaiter(this, void 0, void 0, function* () {
59
+ yield this.cleanupAcknowledgedMessages(eventName, cleanUpInterval).catch(publisherErrorHandler);
60
+ yield this.republishUnprocessedEvents(eventName).catch(publisherErrorHandler);
61
+ yield this.recoverCrashedConsumerMessages(eventName).catch(publisherErrorHandler);
62
+ }));
50
63
  }
51
- }, cleanUpInterval);
64
+ });
52
65
  }
53
66
  createConsumerGroup(eventName) {
54
67
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
@@ -78,14 +91,16 @@ class Streams {
78
91
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
79
92
  const processedMessagesKeyPattern = `pm:${this.consumerGroupName}:*`;
80
93
  let cursor = '0';
94
+ const transaction = this.redisGroups.pipeline();
81
95
  do {
82
96
  const [nextCursor, keys] = yield this.redisGroups.scan(cursor, 'MATCH', processedMessagesKeyPattern);
83
97
  cursor = nextCursor;
84
98
  for (const key of keys) {
85
99
  const oneHourAgo = Date.now() - 60 * 60 * 1000;
86
- yield this.redisGroups.zremrangebyscore(key, '-inf', oneHourAgo);
100
+ transaction.zremrangebyscore(key, '-inf', oneHourAgo);
87
101
  }
88
102
  } while (cursor !== '0');
103
+ yield transaction.exec();
89
104
  });
90
105
  }
91
106
  publish(data) {
@@ -95,7 +110,7 @@ class Streams {
95
110
  data.eventId = (0, id_1.generateID)('HEX', 'FF');
96
111
  if (!data.createdAt)
97
112
  data.createdAt = Date.now();
98
- const transaction = this.redisPublisher.multi();
113
+ const transaction = this.redisPublisher.multi({ pipeline: true });
99
114
  const consumerGroups = yield (0, groups_1.getAllConsumerGroups)(data.eventName, this.redisPublisher);
100
115
  if (consumerGroups.length > 0) {
101
116
  console.log(`Publishing event ${data.eventName} to consumer groups: ${consumerGroups.join(', ')}`);
@@ -203,35 +218,37 @@ class Streams {
203
218
  const observable = bs.asObservable().pipe((0, rxjs_1.skip)(1));
204
219
  const streamName = `${eventName}:${this.consumerGroupName}`;
205
220
  this.redisSubscriber.subscribe(eventName);
206
- const processMessage = () => tslib_1.__awaiter(this, void 0, void 0, function* () {
221
+ const processMessage = () => {
207
222
  try {
208
- const result = yield this.redisGroups.xreadgroup('GROUP', this.consumerGroupName, this.instanceId, 'COUNT', 1, 'BLOCK', 0, 'STREAMS', streamName, '>');
209
- if (result) {
210
- const [, streamMessages] = result[0];
211
- for (const [id, data] of streamMessages) {
212
- const eventData = JSON.parse(data[1]);
213
- const messageId = eventData.eventId;
214
- const isDuplicate = yield this.isDuplicateMessage(streamName, messageId);
215
- if (isDuplicate) {
216
- console.warn(`Duplicate message detected: ${messageId}`);
217
- yield this.redisGroups.xack(streamName, this.consumerGroupName, id);
218
- continue;
223
+ process.nextTick(() => tslib_1.__awaiter(this, void 0, void 0, function* () {
224
+ const result = yield this.redisGroups.xreadgroup('GROUP', this.consumerGroupName, this.instanceId, 'COUNT', 1, 'BLOCK', 0, 'STREAMS', streamName, '>');
225
+ if (result) {
226
+ const [, streamMessages] = result[0];
227
+ for (const [id, data] of streamMessages) {
228
+ const eventData = JSON.parse(data[1]);
229
+ const messageId = eventData.eventId;
230
+ const isDuplicate = yield this.isDuplicateMessage(streamName, messageId);
231
+ if (isDuplicate) {
232
+ console.warn(`Duplicate message detected: ${messageId}`);
233
+ yield this.redisGroups.xack(streamName, this.consumerGroupName, id);
234
+ continue;
235
+ }
236
+ bs.next(eventData);
237
+ const pmKey = `pm:${this.consumerGroupName}:${streamName}`;
238
+ const currentTime = Date.now();
239
+ const transaction = this.redisGroups.multi({ pipeline: true });
240
+ transaction.zadd(pmKey, currentTime, messageId);
241
+ transaction.xack(streamName, this.consumerGroupName, id);
242
+ transaction.zadd(`ack:${streamName}`, Date.now(), id);
243
+ yield transaction.exec();
219
244
  }
220
- bs.next(eventData);
221
- const pmKey = `pm:${this.consumerGroupName}:${streamName}`;
222
- const currentTime = Date.now();
223
- const transaction = this.redisGroups.multi();
224
- transaction.zadd(pmKey, currentTime, messageId);
225
- transaction.xack(streamName, this.consumerGroupName, id);
226
- transaction.zadd(`ack:${streamName}`, Date.now(), id);
227
- yield transaction.exec();
228
245
  }
229
- }
246
+ }));
230
247
  }
231
248
  catch (e) {
232
249
  console.error(JSON.stringify(e));
233
250
  }
234
- });
251
+ };
235
252
  this.redisSubscriber.on('message', () => tslib_1.__awaiter(this, void 0, void 0, function* () {
236
253
  yield processMessage();
237
254
  }));
@@ -258,17 +275,19 @@ class Streams {
258
275
  const result = yield this.redisGroups.xreadgroup('GROUP', this.consumerGroupName, this.instanceId, 'STREAMS', streamName, '>');
259
276
  if (result) {
260
277
  const [, streamMessages] = result[0];
278
+ if (!streamMessages)
279
+ return;
261
280
  console.log(`Unprocessed events: ${streamMessages.length}`);
281
+ const transaction = this.redisGroups.multi({ pipeline: true });
262
282
  for (const [id, data] of streamMessages) {
263
283
  const eventData = JSON.parse(data[1]);
264
- const transaction = this.redisGroups.multi();
265
284
  // Republishing the events
266
285
  transaction.xadd(streamName, '*', 'data', JSON.stringify(eventData));
267
286
  transaction.publish(eventName, '');
268
287
  transaction.xack(streamName, this.consumerGroupName, id);
269
- yield transaction.exec();
270
288
  console.log(`Event ${eventName} with ID: ${id} published`);
271
289
  }
290
+ yield transaction.exec();
272
291
  }
273
292
  });
274
293
  }
@@ -286,13 +305,16 @@ class Streams {
286
305
  * // Attempt to recover messages from the "order.created" stream with an idle timeout of 10 seconds
287
306
  * await streams.recoverCrashedConsumerMessages('order.created', 10000);
288
307
  */
289
- recoverCrashedConsumerMessages(eventName, idleTimeout) {
308
+ recoverCrashedConsumerMessages(eventName, idleTimeout = 30000) {
290
309
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
291
310
  const streamName = `${eventName}:${this.consumerGroupName}`;
292
311
  const pendingMessages = (yield this.redisGroups.xpending(streamName, this.consumerGroupName));
293
312
  if (!pendingMessages)
294
313
  return;
295
314
  const [, minId, maxId, consumers] = pendingMessages;
315
+ if (!consumers || consumers.length === 0)
316
+ return;
317
+ const transaction = this.redisGroups.multi({ pipeline: true });
296
318
  for (const [consumer, pendingCount] of consumers) {
297
319
  if (parseInt(pendingCount) > 0) {
298
320
  const pending = (yield this.redisGroups.xpending(streamName, this.consumerGroupName, minId, maxId, Number(pendingCount), consumer));
@@ -301,15 +323,14 @@ class Streams {
301
323
  if (claimedMessage) {
302
324
  const [, data] = claimedMessage[0];
303
325
  const eventData = JSON.parse(data[1]);
304
- const transaction = this.redisGroups.multi();
305
326
  transaction.xadd(streamName, '*', 'data', JSON.stringify(eventData));
306
327
  transaction.publish(eventName, '');
307
328
  transaction.xack(streamName, this.consumerGroupName, messageId);
308
- yield transaction.exec();
309
329
  }
310
330
  }
311
331
  }
312
332
  }
333
+ yield transaction.exec();
313
334
  });
314
335
  }
315
336
  /**
@@ -368,7 +389,7 @@ class Streams {
368
389
  const cleanupThreshold = Date.now() - interval;
369
390
  const acknowledgedMessages = yield this.redisGroups.zrangebyscore(`ack:${streamName}`, '-inf', cleanupThreshold);
370
391
  if (acknowledgedMessages && acknowledgedMessages.length > 0) {
371
- const transaction = this.redisGroups.multi();
392
+ const transaction = this.redisGroups.multi({ pipeline: true });
372
393
  // Remove acknowledged messages from the stream
373
394
  for (const messageId of acknowledgedMessages) {
374
395
  transaction.xdel(streamName, messageId);