@restorecommerce/kafka-client 1.2.13 → 1.2.15
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/CHANGELOG.md +22 -0
- package/eslint.config.mjs +22 -0
- package/lib/events/index.d.ts +1 -0
- package/lib/events/index.d.ts.map +1 -0
- package/lib/events/index.js +32 -19
- package/lib/events/index.js.map +1 -1
- package/lib/events/provider/kafka/index.d.ts +3 -2
- package/lib/events/provider/kafka/index.d.ts.map +1 -0
- package/lib/events/provider/kafka/index.js +358 -365
- package/lib/events/provider/kafka/index.js.map +1 -1
- package/lib/events/provider/local/index.d.ts +2 -1
- package/lib/events/provider/local/index.d.ts.map +1 -0
- package/lib/events/provider/local/index.js +92 -87
- package/lib/events/provider/local/index.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/protos.d.ts +3 -2
- package/lib/protos.d.ts.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +15 -23
- package/tsconfig-base.json +13 -0
- package/tsconfig.json +2 -16
- package/tsconfig.test.json +3 -5
- package/vitest.config.ts +2 -0
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
21
24
|
};
|
|
22
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
26
|
exports.Name = exports.Kafka = exports.Topic = void 0;
|
|
24
|
-
const retry = require("retry");
|
|
25
|
-
const _ = require("lodash");
|
|
27
|
+
const retry = __importStar(require("retry"));
|
|
28
|
+
const _ = __importStar(require("lodash"));
|
|
26
29
|
const events_1 = require("events");
|
|
27
|
-
const async = require("async");
|
|
30
|
+
const async = __importStar(require("async"));
|
|
28
31
|
const kafkajs_1 = require("kafkajs");
|
|
29
32
|
const protos_1 = require("../../../protos");
|
|
30
33
|
const makeProtoResolver = (protoFilePath, protoRoot) => {
|
|
@@ -41,6 +44,20 @@ const makeProtoResolver = (protoFilePath, protoRoot) => {
|
|
|
41
44
|
* A Kafka topic.
|
|
42
45
|
*/
|
|
43
46
|
class Topic {
|
|
47
|
+
name;
|
|
48
|
+
emitter;
|
|
49
|
+
provider;
|
|
50
|
+
subscribed;
|
|
51
|
+
waitQueue;
|
|
52
|
+
currentOffset;
|
|
53
|
+
consumer;
|
|
54
|
+
config;
|
|
55
|
+
// message sync throttling attributes
|
|
56
|
+
asyncQueue;
|
|
57
|
+
drainEvent;
|
|
58
|
+
// default process one message at at time
|
|
59
|
+
asyncLimit = 1;
|
|
60
|
+
manualOffsetCommit;
|
|
44
61
|
/**
|
|
45
62
|
* Kafka topic.
|
|
46
63
|
* When the listener count for all events are zero, the consumer unsubscribes
|
|
@@ -53,8 +70,6 @@ class Topic {
|
|
|
53
70
|
* @param config
|
|
54
71
|
*/
|
|
55
72
|
constructor(name, provider, config, manualOffsetCommit = false) {
|
|
56
|
-
// default process one message at at time
|
|
57
|
-
this.asyncLimit = 1;
|
|
58
73
|
this.name = name;
|
|
59
74
|
this.emitter = new events_1.EventEmitter();
|
|
60
75
|
this.provider = provider;
|
|
@@ -64,33 +79,31 @@ class Topic {
|
|
|
64
79
|
this.config = config;
|
|
65
80
|
this.manualOffsetCommit = manualOffsetCommit;
|
|
66
81
|
}
|
|
67
|
-
createIfNotExists() {
|
|
68
|
-
return
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
});
|
|
82
|
+
async createIfNotExists() {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
this.provider.admin.listTopics().then((topics) => {
|
|
85
|
+
if (topics.indexOf(this.name) < 0) {
|
|
86
|
+
const operation = retry.operation();
|
|
87
|
+
operation.attempt(async () => {
|
|
88
|
+
this.provider.admin.createTopics({
|
|
89
|
+
topics: [{
|
|
90
|
+
topic: this.name
|
|
91
|
+
}],
|
|
92
|
+
}).then(() => {
|
|
93
|
+
this.provider.logger.info(`Topic ${this.name} created successfully`);
|
|
94
|
+
resolve();
|
|
95
|
+
}).catch(err => {
|
|
96
|
+
this.provider.logger.error(`Cannot create topic ${this.name}:`, { code: err.code, message: err.message, stack: err.stack });
|
|
97
|
+
operation.retry(err);
|
|
98
|
+
const attemptNo = operation.attempts();
|
|
99
|
+
this.provider.logger.info(`Retry creating the Topic, attempt no: ${attemptNo}`);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
this.provider.logger.warn(`Topic ${this.name} already exists`);
|
|
105
|
+
resolve();
|
|
106
|
+
}
|
|
94
107
|
});
|
|
95
108
|
});
|
|
96
109
|
}
|
|
@@ -128,14 +141,12 @@ class Topic {
|
|
|
128
141
|
* @param {string} eventName Name of the event
|
|
129
142
|
* @param {function|generator} listener Event listener
|
|
130
143
|
*/
|
|
131
|
-
removeListener(eventName, listener) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
});
|
|
144
|
+
async removeListener(eventName, listener) {
|
|
145
|
+
this.provider.logger.verbose(`Removing listener from event ${eventName}`);
|
|
146
|
+
this.emitter.removeListener(eventName, listener);
|
|
147
|
+
if (this.listenerCount(eventName) === 0) {
|
|
148
|
+
await this.$unsubscribe(eventName);
|
|
149
|
+
}
|
|
139
150
|
}
|
|
140
151
|
/**
|
|
141
152
|
* Remove all listeners from given event.
|
|
@@ -143,14 +154,12 @@ class Topic {
|
|
|
143
154
|
*
|
|
144
155
|
* @param {string} eventName Name of the event
|
|
145
156
|
*/
|
|
146
|
-
removeAllListeners(eventName) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
});
|
|
157
|
+
async removeAllListeners(eventName) {
|
|
158
|
+
this.provider.logger.verbose(`Removing all listeners from event ${eventName}`);
|
|
159
|
+
this.emitter.removeAllListeners(eventName);
|
|
160
|
+
if (this.listenerCount(eventName) === 0) {
|
|
161
|
+
await this.$unsubscribe(eventName);
|
|
162
|
+
}
|
|
154
163
|
}
|
|
155
164
|
/**
|
|
156
165
|
* Return the offset number of this topic.
|
|
@@ -158,23 +167,21 @@ class Topic {
|
|
|
158
167
|
* @param {number} time Use -1 for latest and 0 for earliest.
|
|
159
168
|
* @return {number} offset number
|
|
160
169
|
*/
|
|
161
|
-
$offset() {
|
|
162
|
-
return
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return this.provider.admin.fetchTopicOffsets(this.name).then(data => {
|
|
166
|
-
resolve(parseInt(data[0].offset, 10));
|
|
167
|
-
}).catch(err => {
|
|
168
|
-
this.provider.logger.error('Error occurred retrieving topic offset:', { code: err.code, message: err.message, stack: err.stack });
|
|
169
|
-
reject(err);
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
return this.provider.admin.fetchTopicOffsetsByTimestamp(this.name, time).then(data => {
|
|
170
|
+
async $offset(time = -1) {
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
if (time < 0) {
|
|
173
|
+
return this.provider.admin.fetchTopicOffsets(this.name).then(data => {
|
|
173
174
|
resolve(parseInt(data[0].offset, 10));
|
|
174
175
|
}).catch(err => {
|
|
175
176
|
this.provider.logger.error('Error occurred retrieving topic offset:', { code: err.code, message: err.message, stack: err.stack });
|
|
176
177
|
reject(err);
|
|
177
178
|
});
|
|
179
|
+
}
|
|
180
|
+
return this.provider.admin.fetchTopicOffsetsByTimestamp(this.name, time).then(data => {
|
|
181
|
+
resolve(parseInt(data[0].offset, 10));
|
|
182
|
+
}).catch(err => {
|
|
183
|
+
this.provider.logger.error('Error occurred retrieving topic offset:', { code: err.code, message: err.message, stack: err.stack });
|
|
184
|
+
reject(err);
|
|
178
185
|
});
|
|
179
186
|
});
|
|
180
187
|
}
|
|
@@ -184,18 +191,16 @@ class Topic {
|
|
|
184
191
|
* @return {Promise} Thunk will be resolved when a message is received
|
|
185
192
|
* with the corresponding offset.
|
|
186
193
|
*/
|
|
187
|
-
$wait(offset) {
|
|
188
|
-
return
|
|
189
|
-
return
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
})());
|
|
198
|
-
});
|
|
194
|
+
async $wait(offset) {
|
|
195
|
+
return new Promise((() => {
|
|
196
|
+
return (cb) => {
|
|
197
|
+
if (this.currentOffset >= offset) {
|
|
198
|
+
cb(null);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
this.waitQueue.push({ offset, cb });
|
|
202
|
+
};
|
|
203
|
+
})());
|
|
199
204
|
}
|
|
200
205
|
/**
|
|
201
206
|
* Reset consumer, unsubscribes all the events on the topic and then
|
|
@@ -204,28 +209,26 @@ class Topic {
|
|
|
204
209
|
* @param {string[]} eventNames list of event names
|
|
205
210
|
* @param {number} offset The offset at which to restart from.
|
|
206
211
|
*/
|
|
207
|
-
$resetConsumer(eventNames, offset) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
this.provider.logger.info(`Unsubscribed event ${eventName}`);
|
|
218
|
-
}
|
|
219
|
-
// subscribe all events on consumer
|
|
220
|
-
for (let eventName of eventNamesList) {
|
|
221
|
-
yield this.$subscribe(eventName, offset);
|
|
222
|
-
this.provider.logger.info(`Subscribed event ${eventName}`);
|
|
223
|
-
}
|
|
212
|
+
async $resetConsumer(eventNames, offset) {
|
|
213
|
+
this.provider.logger.info('Event Names for consumer reset', eventNames);
|
|
214
|
+
if (eventNames && eventNames.length > 0) {
|
|
215
|
+
// since the consumer is set to undefined only when there is no more subscription
|
|
216
|
+
// need to unsubcribe all eventNames and then resubcribe at once
|
|
217
|
+
const eventNamesList = _.clone(eventNames);
|
|
218
|
+
// unsubscribe all events on consumer
|
|
219
|
+
for (const eventName of eventNamesList) {
|
|
220
|
+
await this.$unsubscribe(eventName);
|
|
221
|
+
this.provider.logger.info(`Unsubscribed event ${eventName}`);
|
|
224
222
|
}
|
|
225
|
-
|
|
226
|
-
|
|
223
|
+
// subscribe all events on consumer
|
|
224
|
+
for (const eventName of eventNamesList) {
|
|
225
|
+
await this.$subscribe(eventName, offset);
|
|
226
|
+
this.provider.logger.info(`Subscribed event ${eventName}`);
|
|
227
227
|
}
|
|
228
|
-
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
this.provider.logger.warn('Event names empty for consumer reset');
|
|
231
|
+
}
|
|
229
232
|
}
|
|
230
233
|
/**
|
|
231
234
|
* Force a committed offset reset.
|
|
@@ -233,34 +236,30 @@ class Topic {
|
|
|
233
236
|
* @param {string} eventName
|
|
234
237
|
* @param {number} offset The offset at which to restart from.
|
|
235
238
|
*/
|
|
236
|
-
$reset(eventName, offset) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
yield this.$subscribe(eventName, offset);
|
|
242
|
-
});
|
|
239
|
+
async $reset(eventName, offset) {
|
|
240
|
+
if (this.subscribed.indexOf(eventName) > -1) {
|
|
241
|
+
await this.$unsubscribe(eventName);
|
|
242
|
+
}
|
|
243
|
+
await this.$subscribe(eventName, offset);
|
|
243
244
|
}
|
|
244
245
|
/**
|
|
245
246
|
* Unsubscribe from Kafka topic. Does not remove any listeners.
|
|
246
247
|
*/
|
|
247
|
-
$unsubscribe(eventName) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
});
|
|
248
|
+
async $unsubscribe(eventName) {
|
|
249
|
+
if (!_.includes(this.subscribed, eventName)) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const index = this.subscribed.indexOf(eventName);
|
|
253
|
+
this.subscribed.splice(index, 1);
|
|
254
|
+
if (this.subscribed.length == 0) {
|
|
255
|
+
this.provider.logger.info(`Closing consumer from topic ${this.name}`);
|
|
256
|
+
await this.consumer.stop().then(() => this.consumer.disconnect()).then(() => {
|
|
257
|
+
this.provider.logger.info(`Consumer disconnected from topic ${this.name}`);
|
|
258
|
+
this.consumer = undefined;
|
|
259
|
+
}).catch((err) => {
|
|
260
|
+
this.provider.logger.error(`Error occurred unsubscribing ${eventName} on topic ${this.name}`, { code: err.code, message: err.message, stack: err.stack });
|
|
261
|
+
});
|
|
262
|
+
}
|
|
264
263
|
}
|
|
265
264
|
/**
|
|
266
265
|
* Filters subscribed messages among all received Kafka messages and then
|
|
@@ -305,49 +304,47 @@ class Topic {
|
|
|
305
304
|
* @param offsetValue
|
|
306
305
|
* @param queue
|
|
307
306
|
**/
|
|
308
|
-
$subscribe(eventName, offsetValue, queue) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
this.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
this.drain();
|
|
330
|
-
}
|
|
331
|
-
yield this.consumer.run({
|
|
332
|
-
eachMessage: (payload) => __awaiter(this, void 0, void 0, function* () {
|
|
333
|
-
if (queue) {
|
|
334
|
-
this.onMessageForQueue(payload);
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
this.makeDataHandler(payload);
|
|
338
|
-
}
|
|
339
|
-
})
|
|
340
|
-
}).catch(err => {
|
|
341
|
-
this.provider.logger.error(`Consumer for topic '${this.name}' failed to run`, { code: err.code, message: err.message, stack: err.stack });
|
|
342
|
-
});
|
|
343
|
-
this.consumer.seek({
|
|
344
|
-
topic: this.name,
|
|
345
|
-
partition: 0,
|
|
346
|
-
offset: offsetValue.toString(10)
|
|
347
|
-
});
|
|
307
|
+
async $subscribe(eventName, offsetValue, queue) {
|
|
308
|
+
if (!this.consumer) {
|
|
309
|
+
this.consumer = this.provider.client.consumer({
|
|
310
|
+
groupId: this.provider.config.groupId + '_' + this.name
|
|
311
|
+
});
|
|
312
|
+
await this.consumer.connect().then(() => {
|
|
313
|
+
this.provider.logger.info(`Consumer for topic '${this.name}' connected`);
|
|
314
|
+
}).catch(err => {
|
|
315
|
+
this.provider.logger.error(`Consumer for topic '${this.name}' connection error`, { code: err.code, message: err.message, stack: err.stack });
|
|
316
|
+
});
|
|
317
|
+
await this.consumer.subscribe({
|
|
318
|
+
topic: this.name
|
|
319
|
+
}).then(() => {
|
|
320
|
+
this.provider.logger.info(`Consumer for topic '${this.name}' subscribed`);
|
|
321
|
+
}).catch(err => {
|
|
322
|
+
this.provider.logger.error(`Consumer for topic '${this.name}' subscriber error`, { code: err.code, message: err.message, stack: err.stack });
|
|
323
|
+
});
|
|
324
|
+
// On receiving the message on Kafka consumer put the message to async Queue.
|
|
325
|
+
if (queue) {
|
|
326
|
+
// start drain process
|
|
327
|
+
this.drain();
|
|
348
328
|
}
|
|
349
|
-
this.
|
|
350
|
-
|
|
329
|
+
await this.consumer.run({
|
|
330
|
+
eachMessage: async (payload) => {
|
|
331
|
+
if (queue) {
|
|
332
|
+
this.onMessageForQueue(payload);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
this.makeDataHandler(payload);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}).catch(err => {
|
|
339
|
+
this.provider.logger.error(`Consumer for topic '${this.name}' failed to run`, { code: err.code, message: err.message, stack: err.stack });
|
|
340
|
+
});
|
|
341
|
+
this.consumer.seek({
|
|
342
|
+
topic: this.name,
|
|
343
|
+
partition: 0,
|
|
344
|
+
offset: offsetValue.toString(10)
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
this.subscribed.push(eventName);
|
|
351
348
|
}
|
|
352
349
|
onMessageForQueue(context) {
|
|
353
350
|
if (_.includes(this.subscribed, context.message.key.toString())) {
|
|
@@ -407,37 +404,33 @@ class Topic {
|
|
|
407
404
|
});
|
|
408
405
|
}
|
|
409
406
|
}
|
|
410
|
-
commitCurrentOffsets() {
|
|
411
|
-
return
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
reject(err);
|
|
422
|
-
});
|
|
407
|
+
async commitCurrentOffsets() {
|
|
408
|
+
return new Promise((resolve, reject) => {
|
|
409
|
+
this.consumer.commitOffsets([
|
|
410
|
+
{
|
|
411
|
+
topic: this.name,
|
|
412
|
+
offset: this.currentOffset.toString(10),
|
|
413
|
+
partition: 0 // ?
|
|
414
|
+
}
|
|
415
|
+
]).then(resolve).catch(err => {
|
|
416
|
+
this.provider.logger.error('Error committing offset', { code: err.code, message: err.message, stack: err.stack });
|
|
417
|
+
reject(err);
|
|
423
418
|
});
|
|
424
419
|
});
|
|
425
420
|
}
|
|
426
421
|
/**
|
|
427
422
|
* Manually commit the current offset.
|
|
428
423
|
*/
|
|
429
|
-
commitOffset() {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
440
|
-
});
|
|
424
|
+
async commitOffset() {
|
|
425
|
+
try {
|
|
426
|
+
// Commit the current offset
|
|
427
|
+
await this.commitCurrentOffsets();
|
|
428
|
+
this.provider.logger.verbose('Offset committed manually');
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
this.provider.logger.error('Failed to commit offset manually', { code: error.code, message: error.message, stack: error.stack });
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
441
434
|
}
|
|
442
435
|
/**
|
|
443
436
|
* Internal function for receiving event messages from Kafka and
|
|
@@ -469,19 +462,18 @@ class Topic {
|
|
|
469
462
|
* @param {function|generator} listener Listener
|
|
470
463
|
* @param opts
|
|
471
464
|
*/
|
|
472
|
-
on(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
yield this.$subscribe(eventName, startingOffset, queue);
|
|
465
|
+
async on(eventName, listener, opts = {}) {
|
|
466
|
+
let { startingOffset } = opts;
|
|
467
|
+
const { queue, forceOffset } = opts;
|
|
468
|
+
if (!(this.subscribed.indexOf(eventName) > -1)) {
|
|
469
|
+
if (_.isNil(startingOffset) || (this.config.latestOffset && !forceOffset)) {
|
|
470
|
+
// override the startingOffset with the latestOffset from Kafka
|
|
471
|
+
// if above config is set
|
|
472
|
+
startingOffset = await this.$offset(-1);
|
|
482
473
|
}
|
|
483
|
-
this
|
|
484
|
-
}
|
|
474
|
+
await this.$subscribe(eventName, startingOffset, queue);
|
|
475
|
+
}
|
|
476
|
+
this.emitter.on(eventName, listener);
|
|
485
477
|
}
|
|
486
478
|
/**
|
|
487
479
|
* Send event messages.
|
|
@@ -489,14 +481,12 @@ class Topic {
|
|
|
489
481
|
* @param {string} eventName Event name
|
|
490
482
|
* @param {Object} message Message
|
|
491
483
|
*/
|
|
492
|
-
emit(eventName, message) {
|
|
493
|
-
|
|
494
|
-
yield this.provider.$send(this.name, eventName, message);
|
|
495
|
-
});
|
|
484
|
+
async emit(eventName, message) {
|
|
485
|
+
await this.provider.$send(this.name, eventName, message);
|
|
496
486
|
}
|
|
497
487
|
}
|
|
498
488
|
exports.Topic = Topic;
|
|
499
|
-
const toWinstonLogLevel = level => {
|
|
489
|
+
const toWinstonLogLevel = (level) => {
|
|
500
490
|
switch (level) {
|
|
501
491
|
case kafkajs_1.logLevel.ERROR:
|
|
502
492
|
case kafkajs_1.logLevel.NOTHING:
|
|
@@ -513,6 +503,14 @@ const toWinstonLogLevel = level => {
|
|
|
513
503
|
* Events provider.
|
|
514
504
|
*/
|
|
515
505
|
class Kafka {
|
|
506
|
+
config;
|
|
507
|
+
topics;
|
|
508
|
+
logger;
|
|
509
|
+
client;
|
|
510
|
+
producer;
|
|
511
|
+
admin;
|
|
512
|
+
producerConnected;
|
|
513
|
+
adminConnected;
|
|
516
514
|
/**
|
|
517
515
|
* Kafka is a provider for Events.
|
|
518
516
|
*
|
|
@@ -530,67 +528,67 @@ class Kafka {
|
|
|
530
528
|
* Start connects to kafka with a producer.
|
|
531
529
|
* Suspends the calling function until the producer is connected.
|
|
532
530
|
*/
|
|
533
|
-
start() {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
})
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
})
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}
|
|
593
|
-
}
|
|
531
|
+
async start() {
|
|
532
|
+
const operation = retry.operation({
|
|
533
|
+
forever: true,
|
|
534
|
+
maxTimeout: this.config?.timeout ?? 60000,
|
|
535
|
+
});
|
|
536
|
+
return new Promise((resolveRetry) => {
|
|
537
|
+
operation.attempt(async () => {
|
|
538
|
+
try {
|
|
539
|
+
this.client = new kafkajs_1.Kafka({
|
|
540
|
+
retry: {
|
|
541
|
+
initialRetryTime: 1000,
|
|
542
|
+
maxRetryTime: 10000,
|
|
543
|
+
retries: 100,
|
|
544
|
+
},
|
|
545
|
+
...this.config.kafka,
|
|
546
|
+
logCreator: () => {
|
|
547
|
+
return ({ level, log }) => {
|
|
548
|
+
const { message, ...extra } = log;
|
|
549
|
+
this.logger.log(toWinstonLogLevel(level), '[kafka-client] ' + message, extra);
|
|
550
|
+
};
|
|
551
|
+
},
|
|
552
|
+
});
|
|
553
|
+
this.producer = this.client.producer();
|
|
554
|
+
this.admin = this.client.admin();
|
|
555
|
+
// waiting for producer to be ready
|
|
556
|
+
await new Promise((resolveProducer, rejectProducer) => {
|
|
557
|
+
this.producer.on('producer.connect', () => {
|
|
558
|
+
this.producerConnected = true;
|
|
559
|
+
this.logger.info('The Producer is ready.');
|
|
560
|
+
resolveProducer(true);
|
|
561
|
+
});
|
|
562
|
+
this.producer.on('producer.disconnect', (err) => {
|
|
563
|
+
this.producerConnected = false;
|
|
564
|
+
this.logger.warn('The Producer has disconnected:', err);
|
|
565
|
+
rejectProducer(err);
|
|
566
|
+
});
|
|
567
|
+
this.producer.on('producer.network.request_timeout', (err) => {
|
|
568
|
+
this.logger.warn('The Producer timed out:', err);
|
|
569
|
+
rejectProducer(err);
|
|
570
|
+
});
|
|
571
|
+
this.producer.connect().catch(err => {
|
|
572
|
+
this.logger.warn('Producer connection error:', err);
|
|
573
|
+
});
|
|
574
|
+
}).then(async () => {
|
|
575
|
+
this.admin.on('admin.connect', () => {
|
|
576
|
+
this.adminConnected = true;
|
|
577
|
+
resolveRetry();
|
|
578
|
+
});
|
|
579
|
+
this.admin.on('admin.disconnect', () => this.adminConnected = false);
|
|
580
|
+
await this.admin.connect().catch(err => {
|
|
581
|
+
this.logger.warn('Admin connection error:', err);
|
|
582
|
+
throw err;
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
catch (err) {
|
|
587
|
+
operation.retry(err);
|
|
588
|
+
const attemptNo = operation.attempts();
|
|
589
|
+
this.producer?.disconnect();
|
|
590
|
+
this.logger.info(`Retry initialize the Producer, attempt no: ${attemptNo}`);
|
|
591
|
+
}
|
|
594
592
|
});
|
|
595
593
|
});
|
|
596
594
|
}
|
|
@@ -626,53 +624,52 @@ class Kafka {
|
|
|
626
624
|
* @param {string} eventName
|
|
627
625
|
* @param {Object|Object[]} message
|
|
628
626
|
*/
|
|
629
|
-
$send(topicName, eventName, message) {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
627
|
+
async $send(topicName, eventName, message) {
|
|
628
|
+
let messages = message;
|
|
629
|
+
const config = this.config;
|
|
630
|
+
const messageObject = config[eventName].messageObject;
|
|
631
|
+
if (!_.isArray(message)) {
|
|
632
|
+
messages = [message];
|
|
633
|
+
}
|
|
634
|
+
try {
|
|
635
|
+
const values = [];
|
|
636
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
637
|
+
// get the binary representation of the message using serializeBinary()
|
|
638
|
+
// and build a Buffer from it.
|
|
639
|
+
const msg = messages[i];
|
|
640
|
+
const bufferObj = Kafka.encodeObject(msg, messageObject);
|
|
641
|
+
values.push({
|
|
642
|
+
key: eventName,
|
|
643
|
+
value: Buffer.from(bufferObj),
|
|
644
|
+
partition: 0
|
|
645
|
+
});
|
|
635
646
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
// and build a Buffer from it.
|
|
641
|
-
const msg = messages[i];
|
|
642
|
-
const bufferObj = Kafka.encodeObject(msg, messageObject);
|
|
643
|
-
values.push({
|
|
644
|
-
key: eventName,
|
|
645
|
-
value: Buffer.from(bufferObj),
|
|
646
|
-
partition: 0
|
|
647
|
-
});
|
|
647
|
+
for (const msg of messages) {
|
|
648
|
+
if (config && config[eventName].omittedFields) {
|
|
649
|
+
const keys = config[eventName].omittedFields;
|
|
650
|
+
this.omitFields(keys, msg, config[eventName].enableLogging);
|
|
648
651
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
652
|
+
}
|
|
653
|
+
this.logger.debug(`Sending event ${eventName} to topic ${topicName}`, { messages });
|
|
654
|
+
return new Promise((resolve, reject) => {
|
|
655
|
+
this.producer.send({
|
|
656
|
+
topic: topicName,
|
|
657
|
+
messages: values
|
|
658
|
+
}).then((data) => {
|
|
659
|
+
for (const msg of messages) {
|
|
660
|
+
this.logger.debug(`Sent event ${eventName} to topic ${topicName}`, msg);
|
|
653
661
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
topic: topicName,
|
|
659
|
-
messages: values
|
|
660
|
-
}).then((data) => {
|
|
661
|
-
for (let msg of messages) {
|
|
662
|
-
this.logger.debug(`Sent event ${eventName} to topic ${topicName}`, msg);
|
|
663
|
-
}
|
|
664
|
-
resolve(data);
|
|
665
|
-
}).catch((err) => {
|
|
666
|
-
this.logger.error(`error sending event ${eventName} to topic ${topicName}`, { code: err.code, message: err.message, stack: err.stack });
|
|
667
|
-
reject(err);
|
|
668
|
-
});
|
|
662
|
+
resolve(data);
|
|
663
|
+
}).catch((err) => {
|
|
664
|
+
this.logger.error(`error sending event ${eventName} to topic ${topicName}`, { code: err.code, message: err.message, stack: err.stack });
|
|
665
|
+
reject(err);
|
|
669
666
|
});
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
catch (err) {
|
|
670
|
+
this.logger.error(`error on sending event ${eventName} to topic ${topicName}`, { code: err.code, message: err.message, stack: err.stack });
|
|
671
|
+
throw err;
|
|
672
|
+
}
|
|
676
673
|
}
|
|
677
674
|
omitFields(keys, msg, enableLogging) {
|
|
678
675
|
let msgs;
|
|
@@ -682,14 +679,14 @@ class Kafka {
|
|
|
682
679
|
else {
|
|
683
680
|
msgs = msg;
|
|
684
681
|
}
|
|
685
|
-
for (
|
|
686
|
-
for (
|
|
682
|
+
for (const key of keys) {
|
|
683
|
+
for (const msg of msgs) {
|
|
687
684
|
if (typeof key === 'string') {
|
|
688
685
|
if (enableLogging && msg[key] && msg[key].value) {
|
|
689
686
|
msg[key] = msg[key].value.toString();
|
|
690
687
|
}
|
|
691
688
|
else if (enableLogging && msg[key] && _.isArray(msg[key])) {
|
|
692
|
-
for (
|
|
689
|
+
for (const eachMsg of msg[key]) {
|
|
693
690
|
msg[key] = eachMsg.value.toString();
|
|
694
691
|
}
|
|
695
692
|
}
|
|
@@ -711,53 +708,49 @@ class Kafka {
|
|
|
711
708
|
* @param config
|
|
712
709
|
* @return {Topic} Kafka topic
|
|
713
710
|
*/
|
|
714
|
-
topic(topicName, config) {
|
|
715
|
-
|
|
716
|
-
if (this.topics[topicName]) {
|
|
717
|
-
return this.topics[topicName];
|
|
718
|
-
}
|
|
719
|
-
this.topics[topicName] = new Topic(topicName, this, config);
|
|
720
|
-
yield this.topics[topicName].createIfNotExists();
|
|
711
|
+
async topic(topicName, config) {
|
|
712
|
+
if (this.topics[topicName]) {
|
|
721
713
|
return this.topics[topicName];
|
|
722
|
-
}
|
|
714
|
+
}
|
|
715
|
+
this.topics[topicName] = new Topic(topicName, this, config);
|
|
716
|
+
await this.topics[topicName].createIfNotExists();
|
|
717
|
+
return this.topics[topicName];
|
|
723
718
|
}
|
|
724
719
|
/**
|
|
725
720
|
* stops the connection to kafka.
|
|
726
721
|
* The calling function is suspended until the producer and
|
|
727
722
|
* all consumers from topics are disconnected.
|
|
728
723
|
*/
|
|
729
|
-
stop() {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
yield topic.removeAllListeners(eventName);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
if (errors.length > 0) {
|
|
757
|
-
this.logger.error('Errors when stopping Kafka client:', errors);
|
|
758
|
-
throw errors;
|
|
724
|
+
async stop() {
|
|
725
|
+
this.logger.warn('Stopping Kafka. Ignore any following connection errors');
|
|
726
|
+
const errors = [];
|
|
727
|
+
if (this.producerConnected) {
|
|
728
|
+
await this.producer.disconnect().catch(err => {
|
|
729
|
+
this.logger.warn('Error occurred stopping Kafka producer:', err);
|
|
730
|
+
errors.push(err);
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
if (this.adminConnected) {
|
|
734
|
+
await this.admin.disconnect().catch(err => {
|
|
735
|
+
this.logger.warn('Error occurred stopping Kafka admin:', err);
|
|
736
|
+
errors.push(err);
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
const topicNames = _.keys(this.topics);
|
|
740
|
+
for (let i = 0; i < topicNames.length; i += 1) {
|
|
741
|
+
const topic = this.topics[topicNames[i]];
|
|
742
|
+
const eventNames = topic.subscribed;
|
|
743
|
+
for (let j = eventNames.length - 1; j >= 0; j -= 1) {
|
|
744
|
+
const eventName = eventNames[j];
|
|
745
|
+
// This closes both producer and consumer objects
|
|
746
|
+
// via unsubscribe()
|
|
747
|
+
await topic.removeAllListeners(eventName);
|
|
759
748
|
}
|
|
760
|
-
}
|
|
749
|
+
}
|
|
750
|
+
if (errors.length > 0) {
|
|
751
|
+
this.logger.error('Errors when stopping Kafka client:', errors);
|
|
752
|
+
throw errors;
|
|
753
|
+
}
|
|
761
754
|
}
|
|
762
755
|
}
|
|
763
756
|
exports.Kafka = Kafka;
|