@jetit/publisher 1.0.0 → 1.0.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,7 +1,7 @@
1
1
  {
2
2
  "name": "@jetit/publisher",
3
- "version": "1.0.0",
4
- "type": "module",
3
+ "version": "1.0.2",
4
+ "type": "commonjs",
5
5
  "dependencies": {
6
6
  "@jetit/id": "0.0.6",
7
7
  "ioredis": "5.3.1",
package/src/index.js CHANGED
@@ -1 +1,4 @@
1
- export * from './lib/publisher';
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./lib/publisher"), exports);
@@ -1,3 +1,9 @@
1
- export { Streams as Publisher } from './redis/streams';
2
- export { setRedisConnectionSettings as setRedisConfig } from './redis/registry';
3
- export { ScheduledProcessor as __SCHEDULER_INTERNALS__ } from './redis/scheduler';
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.__SCHEDULER_INTERNALS__ = exports.setRedisConfig = exports.Publisher = void 0;
4
+ var streams_1 = require("./redis/streams");
5
+ Object.defineProperty(exports, "Publisher", { enumerable: true, get: function () { return streams_1.Streams; } });
6
+ var registry_1 = require("./redis/registry");
7
+ Object.defineProperty(exports, "setRedisConfig", { enumerable: true, get: function () { return registry_1.setRedisConnectionSettings; } });
8
+ var scheduler_1 = require("./redis/scheduler");
9
+ Object.defineProperty(exports, "__SCHEDULER_INTERNALS__", { enumerable: true, get: function () { return scheduler_1.ScheduledProcessor; } });
@@ -0,0 +1,2 @@
1
+ import { RedisType } from './registry';
2
+ export declare function getAllConsumerGroups(eventName: string, redisConnection: RedisType): Promise<string[]>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAllConsumerGroups = void 0;
4
+ const tslib_1 = require("tslib");
5
+ function getAllConsumerGroups(eventName, redisConnection) {
6
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
7
+ const consumerGroups = yield redisConnection.smembers(`consumerGroups:${eventName}`);
8
+ return consumerGroups;
9
+ });
10
+ }
11
+ exports.getAllConsumerGroups = getAllConsumerGroups;
@@ -1,14 +1,17 @@
1
+ "use strict";
1
2
  var _a, _b;
2
- import Redis, { Cluster } from 'ioredis';
3
- export class RedisRegistry {
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.setRedisConnectionSettings = exports.RedisRegistry = void 0;
5
+ const ioredis_1 = require("ioredis");
6
+ class RedisRegistry {
4
7
  static attemptConnection(connectionKey, storeRef = 0) {
5
8
  let ref;
6
9
  if (RedisRegistry.options.cluster) {
7
- ref = new Cluster(RedisRegistry.options.cluster.nodes, Object.assign(Object.assign({}, RedisRegistry.options.cluster.options), { redisOptions: {
10
+ ref = new ioredis_1.Cluster(RedisRegistry.options.cluster.nodes, Object.assign(Object.assign({}, RedisRegistry.options.cluster.options), { redisOptions: {
8
11
  db: storeRef,
9
12
  } }));
10
13
  }
11
- ref = new Redis(Object.assign(Object.assign({}, RedisRegistry.options.redis), { db: storeRef }));
14
+ ref = new ioredis_1.default(Object.assign(Object.assign({}, RedisRegistry.options.redis), { db: storeRef }));
12
15
  RedisRegistry.registry.set(connectionKey, ref);
13
16
  return ref;
14
17
  }
@@ -17,11 +20,11 @@ export class RedisRegistry {
17
20
  let ref = this.registry.get(connectionKey);
18
21
  if (!ref) {
19
22
  if (RedisRegistry.options.cluster) {
20
- ref = new Cluster(RedisRegistry.options.cluster.nodes, Object.assign(Object.assign({}, RedisRegistry.options.cluster.options), { redisOptions: {
23
+ ref = new ioredis_1.Cluster(RedisRegistry.options.cluster.nodes, Object.assign(Object.assign({}, RedisRegistry.options.cluster.options), { redisOptions: {
21
24
  db: storeRef,
22
25
  } }));
23
26
  }
24
- ref = new Redis(Object.assign(Object.assign({}, RedisRegistry.options.redis), { db: storeRef }));
27
+ ref = new ioredis_1.default(Object.assign(Object.assign({}, RedisRegistry.options.redis), { db: storeRef }));
25
28
  }
26
29
  return ref;
27
30
  }
@@ -39,6 +42,7 @@ RedisRegistry.options = {
39
42
  host: (_b = process.env['REDIS_HOST']) !== null && _b !== void 0 ? _b : 'localhost',
40
43
  },
41
44
  };
45
+ exports.RedisRegistry = RedisRegistry;
42
46
  /**
43
47
  * This function is used to set Redis Connection options per instance. If no
44
48
  * options are provided, then the service connects as to a single instance
@@ -47,6 +51,7 @@ RedisRegistry.options = {
47
51
  *
48
52
  * @param options
49
53
  */
50
- export function setRedisConnectionSettings(options) {
54
+ function setRedisConnectionSettings(options) {
51
55
  RedisRegistry.setOptions(options);
52
56
  }
57
+ exports.setRedisConnectionSettings = setRedisConnectionSettings;
@@ -6,6 +6,7 @@ import { RedisType } from './registry';
6
6
  export declare class ScheduledProcessor {
7
7
  private scheduledMessagesTimer;
8
8
  private _redisPublisher?;
9
+ private previousTaskCompleted;
9
10
  get redisPublisher(): RedisType;
10
11
  constructor(duration?: number);
11
12
  private processScheduledEvents;
@@ -1,28 +1,46 @@
1
- import { __awaiter } from "tslib";
2
- import { generateID } from '@jetit/id';
3
- import { interval } from 'rxjs';
4
- import { RedisRegistry } from './registry';
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ScheduledProcessor = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const id_1 = require("@jetit/id");
6
+ const rxjs_1 = require("rxjs");
7
+ const registry_1 = require("./registry");
8
+ const groups_1 = require("./groups");
5
9
  /**
6
10
  * DO NOT USE THIS CLASS IF YOU DON'T KNOW WHAT YOU ARE DOING. This class is
7
11
  * meant to be used internally by the scheduler application
8
12
  */
9
- export class ScheduledProcessor {
13
+ class ScheduledProcessor {
10
14
  get redisPublisher() {
11
15
  if (!this._redisPublisher)
12
- this._redisPublisher = RedisRegistry.getConnection('publish');
16
+ this._redisPublisher = registry_1.RedisRegistry.getConnection('publish');
13
17
  return this._redisPublisher;
14
18
  }
15
19
  constructor(duration = 1000) {
16
- this.scheduledMessagesTimer = interval(duration).subscribe(() => {
17
- this.processScheduledEvents().catch((error) => {
18
- console.error('Error while processing scheduled events:', error);
19
- });
20
+ this.previousTaskCompleted = true;
21
+ this.scheduledMessagesTimer = (0, rxjs_1.interval)(duration).subscribe(() => {
22
+ console.log('Checking Streams messages at ', new Date().toISOString(), '...');
23
+ /** Do not run scheduler if the previous run is not completed */
24
+ if (this.previousTaskCompleted) {
25
+ this.previousTaskCompleted = false;
26
+ this.processScheduledEvents()
27
+ .catch((error) => {
28
+ console.error('Error while processing scheduled events:', error);
29
+ })
30
+ .then(() => {
31
+ this.previousTaskCompleted = true;
32
+ });
33
+ }
34
+ else {
35
+ console.log('Skipping current scheduler run because previous run is in progress');
36
+ }
20
37
  });
21
38
  }
22
39
  processScheduledEvents() {
23
- return __awaiter(this, void 0, void 0, function* () {
40
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
24
41
  const currentTime = new Date().getTime();
25
42
  const events = yield this.redisPublisher.zrangebyscore('se', 0, currentTime);
43
+ console.log('Events to process:', events.length);
26
44
  for (const eventString of events) {
27
45
  const eventData = JSON.parse(eventString);
28
46
  /**
@@ -34,9 +52,15 @@ export class ScheduledProcessor {
34
52
  * copy pasted to reduce the case of failure.
35
53
  */
36
54
  const transaction = this.redisPublisher.multi();
37
- eventData.eventId = generateID('HEX', 'FF');
55
+ eventData.eventId = (0, id_1.generateID)('HEX', 'FF');
38
56
  transaction.zrem('se', eventString);
39
- transaction.xadd(eventData.eventName, '*', 'data', JSON.stringify(eventData));
57
+ const consumerGroups = yield (0, groups_1.getAllConsumerGroups)(eventData.eventName, this.redisPublisher);
58
+ console.log('Scheduled Publishing to consumer groups: ', consumerGroups, 'with id ', eventData.eventId, '...');
59
+ for (const consumerGroup of consumerGroups) {
60
+ // Publish the event to each consumer group's stream
61
+ const streamName = `${eventData.eventName}:${consumerGroup}`;
62
+ transaction.xadd(streamName, '*', 'data', JSON.stringify(eventData));
63
+ }
40
64
  transaction.publish(eventData.eventName, '');
41
65
  yield transaction.exec();
42
66
  }
@@ -46,10 +70,11 @@ export class ScheduledProcessor {
46
70
  return this.redisPublisher.zrange('se', 0, -1);
47
71
  }
48
72
  close() {
49
- return __awaiter(this, void 0, void 0, function* () {
73
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
50
74
  if (this.scheduledMessagesTimer) {
51
75
  this.scheduledMessagesTimer.unsubscribe();
52
76
  }
53
77
  });
54
78
  }
55
79
  }
80
+ exports.ScheduledProcessor = ScheduledProcessor;
@@ -27,7 +27,7 @@ export declare class Streams {
27
27
  * const streams = new Streams('POS');
28
28
  */
29
29
  constructor(serviceName: string);
30
- private createStream;
30
+ private createConsumerGroup;
31
31
  private isDuplicateMessage;
32
32
  private clearDuplicationCheckKeys;
33
33
  /**
@@ -148,6 +148,5 @@ export declare class Streams {
148
148
  close(): Promise<void>;
149
149
  private clearSubscribedEvents;
150
150
  private registerConsumerGroup;
151
- private getAllConsumerGroups;
152
151
  private cleanupAcknowledgedMessages;
153
152
  }
@@ -1,21 +1,25 @@
1
- import { __awaiter } from "tslib";
2
- import { RedisRegistry } from './registry';
3
- import { BehaviorSubject, skip, timer, interval, catchError, retry, throwError } from 'rxjs';
4
- import { generateID } from '@jetit/id';
5
- export class Streams {
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Streams = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const registry_1 = require("./registry");
6
+ const rxjs_1 = require("rxjs");
7
+ const id_1 = require("@jetit/id");
8
+ const groups_1 = require("./groups");
9
+ class Streams {
6
10
  get redisPublisher() {
7
11
  if (!this._redisPublisher)
8
- this._redisPublisher = RedisRegistry.getConnection('publish');
12
+ this._redisPublisher = registry_1.RedisRegistry.getConnection('publish');
9
13
  return this._redisPublisher;
10
14
  }
11
15
  get redisSubscriber() {
12
16
  if (!this._redisSubscriber)
13
- this._redisSubscriber = RedisRegistry.getConnection('subscriber');
17
+ this._redisSubscriber = registry_1.RedisRegistry.getConnection('subscriber');
14
18
  return this._redisSubscriber;
15
19
  }
16
20
  get redisGroups() {
17
21
  if (!this._redisGroups)
18
- this._redisGroups = RedisRegistry.getConnection('groups');
22
+ this._redisGroups = registry_1.RedisRegistry.getConnection('groups');
19
23
  return this._redisGroups;
20
24
  }
21
25
  /**
@@ -35,16 +39,16 @@ export class Streams {
35
39
  constructor(serviceName) {
36
40
  var _a;
37
41
  this.eventsListened = [];
38
- this.instanceId = `${serviceName}:${generateID('HEX', 'FE')}`;
42
+ this.instanceId = `${serviceName}:${(0, id_1.generateID)('HEX', 'FE')}`;
39
43
  this.consumerGroupName = `cg-${serviceName}`;
40
44
  const cleanUpInterval = (_a = parseInt(process.env['CLEANUP_INTERVAL'] || '1000 * 60 * 60', 10)) !== null && _a !== void 0 ? _a : 1000 * 60 * 60;
41
- this.cleanUpTimer = interval(cleanUpInterval).subscribe(() => {
45
+ this.cleanUpTimer = (0, rxjs_1.interval)(cleanUpInterval).subscribe(() => {
42
46
  this.clearDuplicationCheckKeys();
43
47
  this.eventsListened.forEach((eventName) => this.cleanupAcknowledgedMessages(eventName, cleanUpInterval));
44
48
  });
45
49
  }
46
- createStream(eventName) {
47
- return __awaiter(this, void 0, void 0, function* () {
50
+ createConsumerGroup(eventName) {
51
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
48
52
  try {
49
53
  const streamName = `${eventName}:${this.consumerGroupName}`;
50
54
  yield this.redisGroups.xgroup('CREATE', streamName, this.consumerGroupName, '0', 'MKSTREAM');
@@ -57,7 +61,7 @@ export class Streams {
57
61
  });
58
62
  }
59
63
  isDuplicateMessage(streamName, messageId) {
60
- return __awaiter(this, void 0, void 0, function* () {
64
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
61
65
  const processedMessagesKey = `pm:${this.consumerGroupName}:${streamName}`;
62
66
  const temp = yield Promise.race([
63
67
  this.redisGroups.zscore(processedMessagesKey, messageId),
@@ -68,7 +72,7 @@ export class Streams {
68
72
  });
69
73
  }
70
74
  clearDuplicationCheckKeys() {
71
- return __awaiter(this, void 0, void 0, function* () {
75
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
72
76
  const processedMessagesKeyPattern = `pm:${this.consumerGroupName}:*`;
73
77
  let cursor = '0';
74
78
  do {
@@ -99,10 +103,10 @@ export class Streams {
99
103
  * await streams.publish(eventData);
100
104
  */
101
105
  publish(data) {
102
- return __awaiter(this, void 0, void 0, function* () {
103
- data.eventId = generateID('HEX', 'FF'); // Added a unique Id to handle Message deduplication
106
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
107
+ data.eventId = (0, id_1.generateID)('HEX', 'FF'); // Added a unique Id to handle Message deduplication
104
108
  const transaction = this.redisPublisher.multi();
105
- const consumerGroups = yield this.getAllConsumerGroups(data.eventName);
109
+ const consumerGroups = yield (0, groups_1.getAllConsumerGroups)(data.eventName, this.redisGroups);
106
110
  if (consumerGroups.length > 0) {
107
111
  console.log(`Publishing event ${data.eventName} to consumer groups: ${consumerGroups.join(', ')}`);
108
112
  for (const consumerGroup of consumerGroups) {
@@ -140,7 +144,7 @@ export class Streams {
140
144
  * await streams.scheduledPublish(futureTime, eventData);
141
145
  */
142
146
  scheduledPublish(scheduledTime, eventData, uniquePerInstance = false) {
143
- return __awaiter(this, void 0, void 0, function* () {
147
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
144
148
  const currentTime = new Date();
145
149
  if (scheduledTime < currentTime) {
146
150
  throw new Error('Cannot schedule an event in the past');
@@ -190,26 +194,26 @@ export class Streams {
190
194
  listen(eventName, maxRetries = 5, initialDelay = 1000) {
191
195
  this.registerConsumerGroup(eventName); // Registers the consumer group for listening to the message
192
196
  this.eventsListened.push(eventName);
193
- return this.listenInternals(eventName).pipe(retry({
197
+ return this.listenInternals(eventName).pipe((0, rxjs_1.retry)({
194
198
  count: maxRetries,
195
199
  delay: (error, retryAttempt) => {
196
200
  const delay = initialDelay * Math.pow(2, retryAttempt);
197
201
  console.error(`Error in listen: ${error.message}. Retrying in ${delay}ms (attempt ${retryAttempt + 1})`);
198
- return timer(delay);
202
+ return (0, rxjs_1.timer)(delay);
199
203
  },
200
- }), catchError((error) => {
204
+ }), (0, rxjs_1.catchError)((error) => {
201
205
  console.error(`Error in listen after ${maxRetries} retries: ${error.message}`);
202
- return throwError(() => new Error(error.message));
206
+ return (0, rxjs_1.throwError)(() => new Error(error.message));
203
207
  }));
204
208
  }
205
209
  listenInternals(eventName) {
206
210
  try {
207
- this.createStream(eventName);
208
- const bs = new BehaviorSubject(null);
209
- const observable = bs.asObservable().pipe(skip(1));
211
+ this.createConsumerGroup(eventName);
212
+ const bs = new rxjs_1.BehaviorSubject(null);
213
+ const observable = bs.asObservable().pipe((0, rxjs_1.skip)(1));
210
214
  const streamName = `${eventName}:${this.consumerGroupName}`;
211
215
  this.redisSubscriber.subscribe(eventName);
212
- const processMessage = () => __awaiter(this, void 0, void 0, function* () {
216
+ const processMessage = () => tslib_1.__awaiter(this, void 0, void 0, function* () {
213
217
  try {
214
218
  const result = yield this.redisGroups.xreadgroup('GROUP', this.consumerGroupName, this.instanceId, 'COUNT', 1, 'BLOCK', 0, 'STREAMS', streamName, '>');
215
219
  if (result) {
@@ -238,8 +242,8 @@ export class Streams {
238
242
  console.error(JSON.stringify(e));
239
243
  }
240
244
  });
241
- this.redisSubscriber.on('message', () => __awaiter(this, void 0, void 0, function* () {
242
- processMessage();
245
+ this.redisSubscriber.on('message', () => tslib_1.__awaiter(this, void 0, void 0, function* () {
246
+ yield processMessage();
243
247
  }));
244
248
  return observable;
245
249
  }
@@ -259,7 +263,7 @@ export class Streams {
259
263
  * @param streamName
260
264
  */
261
265
  republishUnprocessedEvents(eventName) {
262
- return __awaiter(this, void 0, void 0, function* () {
266
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
263
267
  const streamName = `${eventName}:${this.consumerGroupName}`;
264
268
  const result = yield this.redisGroups.xreadgroup('GROUP', this.consumerGroupName, this.instanceId, 'STREAMS', streamName, '>');
265
269
  if (result) {
@@ -292,7 +296,7 @@ export class Streams {
292
296
  * await streams.recoverCrashedConsumerMessages('order.created', 10000);
293
297
  */
294
298
  recoverCrashedConsumerMessages(eventName, idleTimeout) {
295
- return __awaiter(this, void 0, void 0, function* () {
299
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
296
300
  const streamName = `${eventName}:${this.consumerGroupName}`;
297
301
  const pendingMessages = (yield this.redisGroups.xpending(streamName, this.consumerGroupName));
298
302
  if (!pendingMessages)
@@ -338,7 +342,7 @@ export class Streams {
338
342
  * }
339
343
  */
340
344
  close() {
341
- return __awaiter(this, void 0, void 0, function* () {
345
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
342
346
  this.clearSubscribedEvents();
343
347
  if (this.redisPublisher) {
344
348
  yield this.redisPublisher.quit();
@@ -355,7 +359,7 @@ export class Streams {
355
359
  });
356
360
  }
357
361
  clearSubscribedEvents() {
358
- return __awaiter(this, void 0, void 0, function* () {
362
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
359
363
  console.log(`${this.eventsListened.length} events to be cleared`);
360
364
  for (const eventName of this.eventsListened) {
361
365
  yield this.redisGroups.srem(`consumerGroups:${eventName}`, this.consumerGroupName);
@@ -363,18 +367,12 @@ export class Streams {
363
367
  });
364
368
  }
365
369
  registerConsumerGroup(eventName) {
366
- return __awaiter(this, void 0, void 0, function* () {
370
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
367
371
  yield this.redisGroups.sadd(`consumerGroups:${eventName}`, this.consumerGroupName);
368
372
  });
369
373
  }
370
- getAllConsumerGroups(eventName) {
371
- return __awaiter(this, void 0, void 0, function* () {
372
- const consumerGroups = yield this.redisGroups.smembers(`consumerGroups:${eventName}`);
373
- return consumerGroups;
374
- });
375
- }
376
374
  cleanupAcknowledgedMessages(eventName, interval = 60 * 60 * 1000) {
377
- return __awaiter(this, void 0, void 0, function* () {
375
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
378
376
  const streamName = `${eventName}:${this.consumerGroupName}`;
379
377
  const cleanupThreshold = Date.now() - interval;
380
378
  const acknowledgedMessages = yield this.redisGroups.zrangebyscore(`ack:${streamName}`, '-inf', cleanupThreshold);
@@ -391,3 +389,4 @@ export class Streams {
391
389
  });
392
390
  }
393
391
  }
392
+ exports.Streams = Streams;
@@ -1 +1,2 @@
1
- export {};
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });