@jetit/publisher 1.0.9 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetit/publisher",
3
- "version": "1.0.9",
3
+ "version": "1.1.1",
4
4
  "type": "commonjs",
5
5
  "dependencies": {
6
6
  "@jetit/id": "0.0.11",
@@ -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,15 +44,25 @@ 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(() => {
46
- this.clearDuplicationCheckKeys();
47
- this.eventsListened.forEach((eventName) => {
48
- this.cleanupAcknowledgedMessages(eventName, cleanUpInterval);
49
- this.republishUnprocessedEvents(eventName);
50
- });
50
+ this.runClear(cleanUpInterval);
51
51
  }, cleanUpInterval);
52
52
  }
53
+ runClear(cleanUpInterval) {
54
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
55
+ console.log('Running Publisher Clearance');
56
+ this.clearDuplicationCheckKeys();
57
+ for (const eventName of this.eventsListened) {
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
+ }));
63
+ }
64
+ });
65
+ }
53
66
  createConsumerGroup(eventName) {
54
67
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
55
68
  try {
@@ -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) {
@@ -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();
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
  }));
@@ -259,16 +276,16 @@ class Streams {
259
276
  if (result) {
260
277
  const [, streamMessages] = result[0];
261
278
  console.log(`Unprocessed events: ${streamMessages.length}`);
279
+ const transaction = this.redisGroups.multi();
262
280
  for (const [id, data] of streamMessages) {
263
281
  const eventData = JSON.parse(data[1]);
264
- const transaction = this.redisGroups.multi();
265
282
  // Republishing the events
266
283
  transaction.xadd(streamName, '*', 'data', JSON.stringify(eventData));
267
284
  transaction.publish(eventName, '');
268
285
  transaction.xack(streamName, this.consumerGroupName, id);
269
- yield transaction.exec();
270
286
  console.log(`Event ${eventName} with ID: ${id} published`);
271
287
  }
288
+ yield transaction.exec();
272
289
  }
273
290
  });
274
291
  }
@@ -286,13 +303,16 @@ class Streams {
286
303
  * // Attempt to recover messages from the "order.created" stream with an idle timeout of 10 seconds
287
304
  * await streams.recoverCrashedConsumerMessages('order.created', 10000);
288
305
  */
289
- recoverCrashedConsumerMessages(eventName, idleTimeout) {
306
+ recoverCrashedConsumerMessages(eventName, idleTimeout = 30000) {
290
307
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
291
308
  const streamName = `${eventName}:${this.consumerGroupName}`;
292
309
  const pendingMessages = (yield this.redisGroups.xpending(streamName, this.consumerGroupName));
293
310
  if (!pendingMessages)
294
311
  return;
295
312
  const [, minId, maxId, consumers] = pendingMessages;
313
+ if (!consumers || consumers.length === 0)
314
+ return;
315
+ const transaction = this.redisGroups.multi();
296
316
  for (const [consumer, pendingCount] of consumers) {
297
317
  if (parseInt(pendingCount) > 0) {
298
318
  const pending = (yield this.redisGroups.xpending(streamName, this.consumerGroupName, minId, maxId, Number(pendingCount), consumer));
@@ -301,15 +321,14 @@ class Streams {
301
321
  if (claimedMessage) {
302
322
  const [, data] = claimedMessage[0];
303
323
  const eventData = JSON.parse(data[1]);
304
- const transaction = this.redisGroups.multi();
305
324
  transaction.xadd(streamName, '*', 'data', JSON.stringify(eventData));
306
325
  transaction.publish(eventName, '');
307
326
  transaction.xack(streamName, this.consumerGroupName, messageId);
308
- yield transaction.exec();
309
327
  }
310
328
  }
311
329
  }
312
330
  }
331
+ yield transaction.exec();
313
332
  });
314
333
  }
315
334
  /**