@cryptexlabs/codex-nodejs-common 0.2.5 → 0.4.0

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 (122) hide show
  1. package/lib/package.json +6 -6
  2. package/lib/src/auth/index.js +5 -1
  3. package/lib/src/auth/index.js.map +1 -1
  4. package/lib/src/client/index.js +5 -1
  5. package/lib/src/client/index.js.map +1 -1
  6. package/lib/src/config/default-config.js +5 -5
  7. package/lib/src/config/default-config.js.map +1 -1
  8. package/lib/src/config/index.js +5 -1
  9. package/lib/src/config/index.js.map +1 -1
  10. package/lib/src/config/kafka-config.d.ts +2 -1
  11. package/lib/src/config/kafka-config.interface.d.ts +1 -0
  12. package/lib/src/config/kafka-config.js +2 -1
  13. package/lib/src/config/kafka-config.js.map +1 -1
  14. package/lib/src/context/context.builder.js +2 -2
  15. package/lib/src/context/context.builder.js.map +1 -1
  16. package/lib/src/context/index.js +5 -1
  17. package/lib/src/context/index.js.map +1 -1
  18. package/lib/src/decorator/api-meta-headers.js +12 -12
  19. package/lib/src/decorator/api-meta-headers.js.map +1 -1
  20. package/lib/src/decorator/api-pagination.js +3 -3
  21. package/lib/src/decorator/api-pagination.js.map +1 -1
  22. package/lib/src/decorator/index.js +5 -1
  23. package/lib/src/decorator/index.js.map +1 -1
  24. package/lib/src/event/event-handler.d.ts +1 -1
  25. package/lib/src/event/event-handler.js +4 -1
  26. package/lib/src/event/event-handler.js.map +1 -1
  27. package/lib/src/event/index.js +5 -1
  28. package/lib/src/event/index.js.map +1 -1
  29. package/lib/src/event/websocket-event.handler.js +2 -2
  30. package/lib/src/event/websocket-event.handler.js.map +1 -1
  31. package/lib/src/exception/index.js +5 -1
  32. package/lib/src/exception/index.js.map +1 -1
  33. package/lib/src/filter/app-http-exception-filter.js +2 -2
  34. package/lib/src/filter/app-http-exception-filter.js.map +1 -1
  35. package/lib/src/filter/http-status.interceptor.js +2 -2
  36. package/lib/src/filter/http-status.interceptor.js.map +1 -1
  37. package/lib/src/filter/index.js +5 -1
  38. package/lib/src/filter/index.js.map +1 -1
  39. package/lib/src/index.js +5 -1
  40. package/lib/src/index.js.map +1 -1
  41. package/lib/src/logger/context.logger.js +1 -1
  42. package/lib/src/logger/context.logger.js.map +1 -1
  43. package/lib/src/logger/custom.logger.js +1 -1
  44. package/lib/src/logger/custom.logger.js.map +1 -1
  45. package/lib/src/logger/index.js +5 -1
  46. package/lib/src/logger/index.js.map +1 -1
  47. package/lib/src/message/index.js +5 -1
  48. package/lib/src/message/index.js.map +1 -1
  49. package/lib/src/message/message-meta.js +1 -1
  50. package/lib/src/message/message-meta.js.map +1 -1
  51. package/lib/src/pipe/country-code.pipe.js +1 -1
  52. package/lib/src/pipe/country-code.pipe.js.map +1 -1
  53. package/lib/src/pipe/csv.pipe.js +1 -1
  54. package/lib/src/pipe/csv.pipe.js.map +1 -1
  55. package/lib/src/pipe/index.js +5 -1
  56. package/lib/src/pipe/index.js.map +1 -1
  57. package/lib/src/pipe/joi-http-validation.pipe.js +1 -1
  58. package/lib/src/pipe/joi-http-validation.pipe.js.map +1 -1
  59. package/lib/src/pipe/language-code.pipe.js +1 -1
  60. package/lib/src/pipe/language-code.pipe.js.map +1 -1
  61. package/lib/src/pipe/not-empty.pipe.js +1 -1
  62. package/lib/src/pipe/not-empty.pipe.js.map +1 -1
  63. package/lib/src/pipe/uuidv4.array.validation.pipe.js +1 -1
  64. package/lib/src/pipe/uuidv4.array.validation.pipe.js.map +1 -1
  65. package/lib/src/pipe/uuidv4.validation.pipe.js +1 -1
  66. package/lib/src/pipe/uuidv4.validation.pipe.js.map +1 -1
  67. package/lib/src/response/healthz-response.js +1 -1
  68. package/lib/src/response/healthz-response.js.map +1 -1
  69. package/lib/src/response/index.js +5 -1
  70. package/lib/src/response/index.js.map +1 -1
  71. package/lib/src/result/index.js +5 -1
  72. package/lib/src/result/index.js.map +1 -1
  73. package/lib/src/service/consumer/consumer-service-delegate.interface.d.ts +2 -0
  74. package/lib/src/service/consumer/consumer.service.d.ts +1 -0
  75. package/lib/src/service/consumer/consumer.service.js +24 -10
  76. package/lib/src/service/consumer/consumer.service.js.map +1 -1
  77. package/lib/src/service/consumer/index.js +5 -1
  78. package/lib/src/service/consumer/index.js.map +1 -1
  79. package/lib/src/service/consumer/websocket-consumer.service.js +5 -5
  80. package/lib/src/service/consumer/websocket-consumer.service.js.map +1 -1
  81. package/lib/src/service/elasticsearch/elasticsearch-healthz.service.js +22 -28
  82. package/lib/src/service/elasticsearch/elasticsearch-healthz.service.js.map +1 -1
  83. package/lib/src/service/elasticsearch/elasticsearch.initializer.js +4 -7
  84. package/lib/src/service/elasticsearch/elasticsearch.initializer.js.map +1 -1
  85. package/lib/src/service/elasticsearch/index.js +5 -1
  86. package/lib/src/service/elasticsearch/index.js.map +1 -1
  87. package/lib/src/service/healthz/healthz.service.js +3 -3
  88. package/lib/src/service/healthz/healthz.service.js.map +1 -1
  89. package/lib/src/service/healthz/index.js +5 -1
  90. package/lib/src/service/healthz/index.js.map +1 -1
  91. package/lib/src/service/index.js +5 -1
  92. package/lib/src/service/index.js.map +1 -1
  93. package/lib/src/service/kafka/index.d.ts +1 -0
  94. package/lib/src/service/kafka/index.js +6 -1
  95. package/lib/src/service/kafka/index.js.map +1 -1
  96. package/lib/src/service/kafka/kafka.replay.messages.d.ts +10 -0
  97. package/lib/src/service/kafka/kafka.replay.messages.js +42 -0
  98. package/lib/src/service/kafka/kafka.replay.messages.js.map +1 -0
  99. package/lib/src/service/kafka/kafka.service.d.ts +2 -0
  100. package/lib/src/service/kafka/kafka.service.js +83 -3
  101. package/lib/src/service/kafka/kafka.service.js.map +1 -1
  102. package/lib/src/service/kafka/kafka.stub.service.js +1 -1
  103. package/lib/src/service/kafka/kafka.stub.service.js.map +1 -1
  104. package/lib/src/service/socket/index.js +5 -1
  105. package/lib/src/service/socket/index.js.map +1 -1
  106. package/lib/src/service/socket/websocket.gateway.js +8 -8
  107. package/lib/src/service/socket/websocket.gateway.js.map +1 -1
  108. package/lib/src/util/index.js +5 -1
  109. package/lib/src/util/index.js.map +1 -1
  110. package/package.json +6 -6
  111. package/src/config/default-config.ts +2 -1
  112. package/src/config/kafka-config.interface.ts +1 -0
  113. package/src/config/kafka-config.ts +4 -1
  114. package/src/event/event-handler.ts +5 -2
  115. package/src/service/consumer/consumer-service-delegate.interface.ts +2 -0
  116. package/src/service/consumer/consumer.service.ts +27 -3
  117. package/src/service/elasticsearch/elasticsearch-healthz.service.ts +20 -25
  118. package/src/service/elasticsearch/elasticsearch.initializer.ts +4 -6
  119. package/src/service/healthz/healthz.service.spec.ts +5 -5
  120. package/src/service/kafka/index.ts +1 -0
  121. package/src/service/kafka/kafka.replay.messages.ts +33 -0
  122. package/src/service/kafka/kafka.service.ts +145 -0
@@ -20,7 +20,7 @@ import { HealthzService } from "./healthz.service";
20
20
  import { DefaultConfig } from "../../config";
21
21
  import { instance, mock, when } from "ts-mockito";
22
22
  import { unlink, writeFile } from "fs";
23
- import { mocked } from "ts-jest/utils";
23
+ import { mocked } from "jest-mock";
24
24
 
25
25
  describe("HealthzService", () => {
26
26
  let service: HealthzService;
@@ -61,7 +61,7 @@ describe("HealthzService", () => {
61
61
 
62
62
  it("Should delete healthz file when made unhealthy and file exists", async () => {
63
63
  await service.makeHealthy(HealthzComponentEnum.DATABASE_ELASTICSEARCH);
64
- const mockedUnlink = mocked(unlink, true);
64
+ const mockedUnlink = await mocked(unlink, true);
65
65
  when(MockedConfig.healthzFilePath).thenReturn("/tmp/noexist");
66
66
  expect(mockedUnlink.mock.calls.length).toBe(0);
67
67
  await service.makeUnhealthy(HealthzComponentEnum.DATABASE_ELASTICSEARCH);
@@ -70,14 +70,14 @@ describe("HealthzService", () => {
70
70
 
71
71
  it("Should delete healthz file when made unhealthy and file does not exist", async () => {
72
72
  await service.makeHealthy(HealthzComponentEnum.DATABASE_ELASTICSEARCH);
73
- const mockedUnlink = mocked(unlink, true);
73
+ const mockedUnlink = await mocked(unlink, true);
74
74
  expect(mockedUnlink.mock.calls.length).toBe(0);
75
75
  await service.makeUnhealthy(HealthzComponentEnum.DATABASE_ELASTICSEARCH);
76
76
  expect(mockedUnlink.mock.calls.length).toBe(0);
77
77
  });
78
78
 
79
79
  it("Should create healthz file when all components are made healthy", async () => {
80
- const mockedWriteFile = mocked(writeFile, true);
80
+ const mockedWriteFile = await mocked(writeFile, true);
81
81
 
82
82
  // Make both components unhealthy
83
83
  await service.makeUnhealthy(HealthzComponentEnum.DATABASE_ELASTICSEARCH);
@@ -95,7 +95,7 @@ describe("HealthzService", () => {
95
95
  });
96
96
 
97
97
  it("Should not create healthz file when file already is created", async () => {
98
- const mockedWriteFile = mocked(writeFile, true);
98
+ const mockedWriteFile = await mocked(writeFile, true);
99
99
 
100
100
  await service.makeHealthy(HealthzComponentEnum.DATABASE_ELASTICSEARCH);
101
101
 
@@ -1,3 +1,4 @@
1
1
  export * from "./kafka.service";
2
2
  export * from "./kafka.stub.service";
3
3
  export * from "./kafka-service.interface";
4
+ export * from "./kafka.replay.messages";
@@ -0,0 +1,33 @@
1
+ import { Inject, Injectable, LoggerService } from "@nestjs/common";
2
+ import { KafkaService } from "./kafka.service";
3
+ import { DefaultConfig } from "../../config";
4
+
5
+ @Injectable()
6
+ export class ReplayKafkaMessages {
7
+ constructor(
8
+ @Inject("CONFIG") private readonly config: DefaultConfig,
9
+ @Inject("LOGGER") private readonly logger: LoggerService,
10
+ private readonly kafkaService: KafkaService
11
+ ) {}
12
+
13
+ async process(
14
+ groupId: string,
15
+ inTopic: string,
16
+ outTopic: string
17
+ ): Promise<void> {
18
+ await this.kafkaService.initializeProducer();
19
+ await this.kafkaService.initializeConsumer(groupId);
20
+ await this.kafkaService.connect();
21
+
22
+ await this.kafkaService.startManualConsumer(
23
+ [inTopic],
24
+ async (_, message) => {
25
+ await this.kafkaService.publish(
26
+ outTopic,
27
+ JSON.parse(message.value.toString())
28
+ );
29
+ }
30
+ );
31
+ await this.kafkaService.disconnect();
32
+ }
33
+ }
@@ -2,6 +2,7 @@ import { Inject, Injectable, LoggerService } from "@nestjs/common";
2
2
  import {
3
3
  Admin,
4
4
  Consumer,
5
+ EachMessagePayload,
5
6
  ITopicConfig,
6
7
  Kafka,
7
8
  Message,
@@ -115,6 +116,39 @@ export class KafkaService implements ConsumerServiceDelegateInterface {
115
116
  });
116
117
  }
117
118
 
119
+ public async startManualConsumer(
120
+ topics: (string | RegExp)[],
121
+ callback: (topic: string, message: Message) => Promise<void>
122
+ ): Promise<void> {
123
+ await this._setManualConsume();
124
+
125
+ const promises = [];
126
+ for (const topic of topics) {
127
+ promises.push(
128
+ this._kafkaConsumer.subscribe({ topic, fromBeginning: true })
129
+ );
130
+ }
131
+ try {
132
+ await Promise.all(promises);
133
+
134
+ await this._ensureTopicsExist(topics);
135
+
136
+ const results = [
137
+ new Promise((resolve) => {
138
+ this._kafkaConsumer.run({
139
+ eachMessage: async (messagePayload: EachMessagePayload) => {
140
+ const { topic, partition, message } = messagePayload;
141
+ await callback(topic, message);
142
+ },
143
+ });
144
+ }),
145
+ ];
146
+ await Promise.all(results);
147
+ } catch (e) {
148
+ this.logger.error(e.message, e.trace);
149
+ }
150
+ }
151
+
118
152
  private async _ensureTopicsExist(topics: (string | RegExp)[]): Promise<void> {
119
153
  const stringTopics = [];
120
154
  for (const topic of topics) {
@@ -165,4 +199,115 @@ export class KafkaService implements ConsumerServiceDelegateInterface {
165
199
  messages,
166
200
  });
167
201
  }
202
+
203
+ private async _setManualConsume(): Promise<void> {
204
+ /* Based on solution provided here:
205
+ * https://github.com/tulios/kafkajs/issues/825#issuecomment-674106799
206
+ *
207
+ * 1. We need to know which partitions we are assigned.
208
+ * 2. Which partitions have we consumed the last offset for
209
+ * 3. If all partitions have 0 lag, we exit.
210
+ */
211
+
212
+ /*
213
+ * `consumedTopicPartitions` will be an object of all topic-partitions
214
+ * and a boolean indicating whether or not we have consumed all
215
+ * messages in that topic-partition. For example:
216
+ *
217
+ * {
218
+ * "topic-test-0": false,
219
+ * "topic-test-1": false,
220
+ * "topic-test-2": false
221
+ * }
222
+ */
223
+ let consumedTopicPartitions = {};
224
+ this._kafkaConsumer.on(
225
+ this._kafkaConsumer.events.GROUP_JOIN,
226
+ async ({ payload }) => {
227
+ const { memberAssignment } = payload;
228
+
229
+ consumedTopicPartitions = Object.entries(memberAssignment).reduce(
230
+ (topics, [topic, partitions]) => {
231
+ for (const partition in partitions) {
232
+ this.logger.debug("topic-partition: ", `${topic}-${partition}`);
233
+ topics[`${topic}-${partition}`] = false;
234
+ }
235
+ return topics;
236
+ },
237
+ {}
238
+ );
239
+ }
240
+ );
241
+
242
+ /*
243
+ * This is extremely unergonomic, but if we are currently caught up to the head
244
+ * of all topic-partitions, we won't actually get any batches, which means we'll
245
+ * never find out that we are actually caught up. So as a workaround, what we can do
246
+ * is to check in `FETCH_START` if we have previously made a fetch without
247
+ * processing any batches in between. If so, it means that we received empty
248
+ * fetch responses, meaning there was no more data to fetch.
249
+ *
250
+ * We need to initially set this to true, or we would immediately exit.
251
+ */
252
+ let processedBatch = true;
253
+ this._kafkaConsumer.on(this._kafkaConsumer.events.FETCH_START, async () => {
254
+ if (processedBatch === false) {
255
+ await this._kafkaConsumer.disconnect();
256
+ process.exit(0);
257
+ }
258
+
259
+ processedBatch = false;
260
+ });
261
+
262
+ /*
263
+ * Now whenever we have finished processing a batch, we'll update `consumedTopicPartitions`
264
+ * and exit if all topic-partitions have been consumed,
265
+ */
266
+ this._kafkaConsumer.on(
267
+ this._kafkaConsumer.events.END_BATCH_PROCESS,
268
+ async ({ payload }) => {
269
+ const { topic, partition, offsetLag } = payload;
270
+
271
+ consumedTopicPartitions[`${topic}-${partition}`] = offsetLag === "0";
272
+
273
+ if (
274
+ Object.values(consumedTopicPartitions).every((consumed) =>
275
+ Boolean(consumed)
276
+ )
277
+ ) {
278
+ await this._kafkaConsumer.disconnect();
279
+ process.exit(0);
280
+ }
281
+
282
+ processedBatch = true;
283
+ }
284
+ );
285
+
286
+ // Graceful shutdown
287
+ const errorTypes = ["unhandledRejection", "uncaughtException"];
288
+ const signalTraps: NodeJS.Signals[] = ["SIGTERM", "SIGINT", "SIGUSR2"];
289
+
290
+ errorTypes.map((type) => {
291
+ process.on(type, async (e) => {
292
+ try {
293
+ this.logger.debug(`process.on ${type}`);
294
+ this.logger.error(e);
295
+ await this.disconnect();
296
+ process.exit(0);
297
+ } catch (_) {
298
+ process.exit(1);
299
+ }
300
+ });
301
+ });
302
+
303
+ signalTraps.map((type) => {
304
+ process.once(type, async () => {
305
+ try {
306
+ await this.disconnect();
307
+ } finally {
308
+ process.kill(process.pid, type);
309
+ }
310
+ });
311
+ });
312
+ }
168
313
  }