@drarzter/kafka-client 0.1.6 → 0.1.8
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/README.md +111 -1
- package/dist/index.d.mts +93 -7
- package/dist/index.d.ts +93 -7
- package/dist/index.js +152 -69
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +148 -68
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -1
package/dist/index.mjs
CHANGED
|
@@ -17,18 +17,44 @@ import { DiscoveryModule } from "@nestjs/core";
|
|
|
17
17
|
// src/client/kafka.client.ts
|
|
18
18
|
import { Kafka, Partitioners } from "kafkajs";
|
|
19
19
|
import { Logger } from "@nestjs/common";
|
|
20
|
+
|
|
21
|
+
// src/client/errors.ts
|
|
22
|
+
var KafkaProcessingError = class extends Error {
|
|
23
|
+
constructor(message, topic2, originalMessage, options) {
|
|
24
|
+
super(message, options);
|
|
25
|
+
this.topic = topic2;
|
|
26
|
+
this.originalMessage = originalMessage;
|
|
27
|
+
this.name = "KafkaProcessingError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var KafkaRetryExhaustedError = class extends KafkaProcessingError {
|
|
31
|
+
constructor(topic2, originalMessage, attempts, options) {
|
|
32
|
+
super(
|
|
33
|
+
`Message processing failed after ${attempts} attempts on topic "${topic2}"`,
|
|
34
|
+
topic2,
|
|
35
|
+
originalMessage,
|
|
36
|
+
options
|
|
37
|
+
);
|
|
38
|
+
this.attempts = attempts;
|
|
39
|
+
this.name = "KafkaRetryExhaustedError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/client/kafka.client.ts
|
|
20
44
|
var KafkaClient = class {
|
|
21
45
|
kafka;
|
|
22
46
|
producer;
|
|
23
47
|
consumer;
|
|
24
48
|
admin;
|
|
25
49
|
logger;
|
|
26
|
-
|
|
50
|
+
autoCreateTopicsEnabled;
|
|
51
|
+
ensuredTopics = /* @__PURE__ */ new Set();
|
|
27
52
|
isAdminConnected = false;
|
|
28
53
|
clientId;
|
|
29
|
-
constructor(clientId, groupId, brokers) {
|
|
54
|
+
constructor(clientId, groupId, brokers, options) {
|
|
30
55
|
this.clientId = clientId;
|
|
31
56
|
this.logger = new Logger(`KafkaClient:${clientId}`);
|
|
57
|
+
this.autoCreateTopicsEnabled = options?.autoCreateTopics ?? false;
|
|
32
58
|
this.kafka = new Kafka({
|
|
33
59
|
clientId: this.clientId,
|
|
34
60
|
brokers
|
|
@@ -42,10 +68,29 @@ var KafkaClient = class {
|
|
|
42
68
|
this.consumer = this.kafka.consumer({ groupId });
|
|
43
69
|
this.admin = this.kafka.admin();
|
|
44
70
|
}
|
|
45
|
-
|
|
46
|
-
|
|
71
|
+
resolveTopicName(topicOrDescriptor) {
|
|
72
|
+
if (typeof topicOrDescriptor === "string") return topicOrDescriptor;
|
|
73
|
+
if (topicOrDescriptor && typeof topicOrDescriptor === "object" && "__topic" in topicOrDescriptor) {
|
|
74
|
+
return topicOrDescriptor.__topic;
|
|
75
|
+
}
|
|
76
|
+
return String(topicOrDescriptor);
|
|
77
|
+
}
|
|
78
|
+
async ensureTopic(topic2) {
|
|
79
|
+
if (!this.autoCreateTopicsEnabled || this.ensuredTopics.has(topic2)) return;
|
|
80
|
+
if (!this.isAdminConnected) {
|
|
81
|
+
await this.admin.connect();
|
|
82
|
+
this.isAdminConnected = true;
|
|
83
|
+
}
|
|
84
|
+
await this.admin.createTopics({
|
|
85
|
+
topics: [{ topic: topic2, numPartitions: 1 }]
|
|
86
|
+
});
|
|
87
|
+
this.ensuredTopics.add(topic2);
|
|
88
|
+
}
|
|
89
|
+
async sendMessage(topicOrDesc, message, options = {}) {
|
|
90
|
+
const topic2 = this.resolveTopicName(topicOrDesc);
|
|
91
|
+
await this.ensureTopic(topic2);
|
|
47
92
|
await this.producer.send({
|
|
48
|
-
topic,
|
|
93
|
+
topic: topic2,
|
|
49
94
|
messages: [
|
|
50
95
|
{
|
|
51
96
|
value: JSON.stringify(message),
|
|
@@ -56,10 +101,11 @@ var KafkaClient = class {
|
|
|
56
101
|
acks: -1
|
|
57
102
|
});
|
|
58
103
|
}
|
|
59
|
-
|
|
60
|
-
|
|
104
|
+
async sendBatch(topicOrDesc, messages) {
|
|
105
|
+
const topic2 = this.resolveTopicName(topicOrDesc);
|
|
106
|
+
await this.ensureTopic(topic2);
|
|
61
107
|
await this.producer.send({
|
|
62
|
-
topic,
|
|
108
|
+
topic: topic2,
|
|
63
109
|
messages: messages.map((m) => ({
|
|
64
110
|
value: JSON.stringify(m.value),
|
|
65
111
|
key: m.key ?? null,
|
|
@@ -73,9 +119,11 @@ var KafkaClient = class {
|
|
|
73
119
|
const tx = await this.producer.transaction();
|
|
74
120
|
try {
|
|
75
121
|
const ctx = {
|
|
76
|
-
send: async (
|
|
122
|
+
send: async (topicOrDesc, message, options = {}) => {
|
|
123
|
+
const topic2 = this.resolveTopicName(topicOrDesc);
|
|
124
|
+
await this.ensureTopic(topic2);
|
|
77
125
|
await tx.send({
|
|
78
|
-
topic,
|
|
126
|
+
topic: topic2,
|
|
79
127
|
messages: [
|
|
80
128
|
{
|
|
81
129
|
value: JSON.stringify(message),
|
|
@@ -86,9 +134,11 @@ var KafkaClient = class {
|
|
|
86
134
|
acks: -1
|
|
87
135
|
});
|
|
88
136
|
},
|
|
89
|
-
sendBatch: async (
|
|
137
|
+
sendBatch: async (topicOrDesc, messages) => {
|
|
138
|
+
const topic2 = this.resolveTopicName(topicOrDesc);
|
|
139
|
+
await this.ensureTopic(topic2);
|
|
90
140
|
await tx.send({
|
|
91
|
-
topic,
|
|
141
|
+
topic: topic2,
|
|
92
142
|
messages: messages.map((m) => ({
|
|
93
143
|
value: JSON.stringify(m.value),
|
|
94
144
|
key: m.key ?? null,
|
|
@@ -123,20 +173,20 @@ var KafkaClient = class {
|
|
|
123
173
|
dlq = false,
|
|
124
174
|
interceptors = []
|
|
125
175
|
} = options;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
topics,
|
|
129
|
-
fromBeginning
|
|
130
|
-
});
|
|
131
|
-
this.isConsumerRunning = true;
|
|
132
|
-
this.logger.log(
|
|
133
|
-
`Consumer subscribed to topics: ${topics.join(", ")}`
|
|
176
|
+
const topicNames = topics.map(
|
|
177
|
+
(t) => this.resolveTopicName(t)
|
|
134
178
|
);
|
|
179
|
+
await this.consumer.connect();
|
|
180
|
+
for (const t of topicNames) {
|
|
181
|
+
await this.ensureTopic(t);
|
|
182
|
+
}
|
|
183
|
+
await this.consumer.subscribe({ topics: topicNames, fromBeginning });
|
|
184
|
+
this.logger.log(`Consumer subscribed to topics: ${topicNames.join(", ")}`);
|
|
135
185
|
await this.consumer.run({
|
|
136
186
|
autoCommit,
|
|
137
|
-
eachMessage: async ({ topic, message }) => {
|
|
187
|
+
eachMessage: async ({ topic: topic2, message }) => {
|
|
138
188
|
if (!message.value) {
|
|
139
|
-
this.logger.warn(`Received empty message from topic ${
|
|
189
|
+
this.logger.warn(`Received empty message from topic ${topic2}`);
|
|
140
190
|
return;
|
|
141
191
|
}
|
|
142
192
|
const raw = message.value.toString();
|
|
@@ -145,47 +195,20 @@ var KafkaClient = class {
|
|
|
145
195
|
parsedMessage = JSON.parse(raw);
|
|
146
196
|
} catch (error) {
|
|
147
197
|
this.logger.error(
|
|
148
|
-
`Failed to parse message from topic ${
|
|
198
|
+
`Failed to parse message from topic ${topic2}:`,
|
|
149
199
|
error instanceof Error ? error.stack : String(error)
|
|
150
200
|
);
|
|
151
201
|
return;
|
|
152
202
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
await interceptor.before?.(parsedMessage, topic);
|
|
159
|
-
}
|
|
160
|
-
await handleMessage(parsedMessage, topic);
|
|
161
|
-
for (const interceptor of interceptors) {
|
|
162
|
-
await interceptor.after?.(parsedMessage, topic);
|
|
163
|
-
}
|
|
164
|
-
return;
|
|
165
|
-
} catch (error) {
|
|
166
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
167
|
-
for (const interceptor of interceptors) {
|
|
168
|
-
await interceptor.onError?.(parsedMessage, topic, err);
|
|
169
|
-
}
|
|
170
|
-
const isLastAttempt = attempt === maxAttempts;
|
|
171
|
-
this.logger.error(
|
|
172
|
-
`Error processing message from topic ${topic} (attempt ${attempt}/${maxAttempts}):`,
|
|
173
|
-
err.stack
|
|
174
|
-
);
|
|
175
|
-
if (isLastAttempt) {
|
|
176
|
-
if (dlq) {
|
|
177
|
-
await this.sendToDlq(topic, raw);
|
|
178
|
-
}
|
|
179
|
-
} else {
|
|
180
|
-
await this.sleep(backoffMs * attempt);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
203
|
+
await this.processMessage(parsedMessage, raw, topic2, handleMessage, {
|
|
204
|
+
retry,
|
|
205
|
+
dlq,
|
|
206
|
+
interceptors
|
|
207
|
+
});
|
|
184
208
|
}
|
|
185
209
|
});
|
|
186
210
|
}
|
|
187
211
|
async stopConsumer() {
|
|
188
|
-
this.isConsumerRunning = false;
|
|
189
212
|
await this.consumer.disconnect();
|
|
190
213
|
this.logger.log("Consumer disconnected");
|
|
191
214
|
}
|
|
@@ -203,11 +226,7 @@ var KafkaClient = class {
|
|
|
203
226
|
}
|
|
204
227
|
/** Gracefully disconnect producer, consumer, and admin. */
|
|
205
228
|
async disconnect() {
|
|
206
|
-
this.
|
|
207
|
-
const tasks = [
|
|
208
|
-
this.producer.disconnect(),
|
|
209
|
-
this.consumer.disconnect()
|
|
210
|
-
];
|
|
229
|
+
const tasks = [this.producer.disconnect(), this.consumer.disconnect()];
|
|
211
230
|
if (this.isAdminConnected) {
|
|
212
231
|
tasks.push(this.admin.disconnect());
|
|
213
232
|
this.isAdminConnected = false;
|
|
@@ -215,8 +234,53 @@ var KafkaClient = class {
|
|
|
215
234
|
await Promise.allSettled(tasks);
|
|
216
235
|
this.logger.log("All connections closed");
|
|
217
236
|
}
|
|
218
|
-
|
|
219
|
-
|
|
237
|
+
// --- Private helpers ---
|
|
238
|
+
async processMessage(parsedMessage, raw, topic2, handleMessage, opts) {
|
|
239
|
+
const { retry, dlq = false, interceptors = [] } = opts;
|
|
240
|
+
const maxAttempts = retry ? retry.maxRetries + 1 : 1;
|
|
241
|
+
const backoffMs = retry?.backoffMs ?? 1e3;
|
|
242
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
243
|
+
try {
|
|
244
|
+
for (const interceptor of interceptors) {
|
|
245
|
+
await interceptor.before?.(parsedMessage, topic2);
|
|
246
|
+
}
|
|
247
|
+
await handleMessage(parsedMessage, topic2);
|
|
248
|
+
for (const interceptor of interceptors) {
|
|
249
|
+
await interceptor.after?.(parsedMessage, topic2);
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
254
|
+
const isLastAttempt = attempt === maxAttempts;
|
|
255
|
+
if (isLastAttempt && maxAttempts > 1) {
|
|
256
|
+
const exhaustedError = new KafkaRetryExhaustedError(
|
|
257
|
+
topic2,
|
|
258
|
+
parsedMessage,
|
|
259
|
+
maxAttempts,
|
|
260
|
+
{ cause: err }
|
|
261
|
+
);
|
|
262
|
+
for (const interceptor of interceptors) {
|
|
263
|
+
await interceptor.onError?.(parsedMessage, topic2, exhaustedError);
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
for (const interceptor of interceptors) {
|
|
267
|
+
await interceptor.onError?.(parsedMessage, topic2, err);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
this.logger.error(
|
|
271
|
+
`Error processing message from topic ${topic2} (attempt ${attempt}/${maxAttempts}):`,
|
|
272
|
+
err.stack
|
|
273
|
+
);
|
|
274
|
+
if (isLastAttempt) {
|
|
275
|
+
if (dlq) await this.sendToDlq(topic2, raw);
|
|
276
|
+
} else {
|
|
277
|
+
await this.sleep(backoffMs * attempt);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async sendToDlq(topic2, rawMessage) {
|
|
283
|
+
const dlqTopic = `${topic2}.dlq`;
|
|
220
284
|
try {
|
|
221
285
|
await this.producer.send({
|
|
222
286
|
topic: dlqTopic,
|
|
@@ -249,7 +313,10 @@ import { Inject } from "@nestjs/common";
|
|
|
249
313
|
var KAFKA_SUBSCRIBER_METADATA = "KAFKA_SUBSCRIBER_METADATA";
|
|
250
314
|
var InjectKafkaClient = (name) => Inject(getKafkaClientToken(name));
|
|
251
315
|
var SubscribeTo = (topics, options) => {
|
|
252
|
-
const
|
|
316
|
+
const arr = Array.isArray(topics) ? topics : [topics];
|
|
317
|
+
const topicsArray = arr.map(
|
|
318
|
+
(t) => typeof t === "string" ? t : t.__topic
|
|
319
|
+
);
|
|
253
320
|
const { clientName, ...consumerOptions } = options || {};
|
|
254
321
|
return (target, propertyKey, _descriptor) => {
|
|
255
322
|
const existing = Reflect.getMetadata(KAFKA_SUBSCRIBER_METADATA, target.constructor) || [];
|
|
@@ -300,8 +367,8 @@ var KafkaExplorer = class {
|
|
|
300
367
|
const handler = instance[entry.methodName].bind(instance);
|
|
301
368
|
await client.startConsumer(
|
|
302
369
|
entry.topics,
|
|
303
|
-
async (message,
|
|
304
|
-
await handler(message,
|
|
370
|
+
async (message, topic2) => {
|
|
371
|
+
await handler(message, topic2);
|
|
305
372
|
},
|
|
306
373
|
entry.options
|
|
307
374
|
);
|
|
@@ -329,7 +396,8 @@ var KafkaModule = class {
|
|
|
329
396
|
const client = new KafkaClient(
|
|
330
397
|
options.clientId,
|
|
331
398
|
options.groupId,
|
|
332
|
-
options.brokers
|
|
399
|
+
options.brokers,
|
|
400
|
+
{ autoCreateTopics: options.autoCreateTopics }
|
|
333
401
|
);
|
|
334
402
|
await client.connectProducer();
|
|
335
403
|
return client;
|
|
@@ -360,7 +428,8 @@ var KafkaModule = class {
|
|
|
360
428
|
const client = new KafkaClient(
|
|
361
429
|
options.clientId,
|
|
362
430
|
options.groupId,
|
|
363
|
-
options.brokers
|
|
431
|
+
options.brokers,
|
|
432
|
+
{ autoCreateTopics: options.autoCreateTopics }
|
|
364
433
|
);
|
|
365
434
|
await client.connectProducer();
|
|
366
435
|
return client;
|
|
@@ -387,6 +456,14 @@ KafkaModule = __decorateClass([
|
|
|
387
456
|
Module({})
|
|
388
457
|
], KafkaModule);
|
|
389
458
|
|
|
459
|
+
// src/client/topic.ts
|
|
460
|
+
function topic(name) {
|
|
461
|
+
return () => ({
|
|
462
|
+
__topic: name,
|
|
463
|
+
__type: void 0
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
390
467
|
// src/health/kafka.health.ts
|
|
391
468
|
import { Injectable as Injectable2 } from "@nestjs/common";
|
|
392
469
|
var KafkaHealthIndicator = class {
|
|
@@ -418,7 +495,10 @@ export {
|
|
|
418
495
|
KafkaExplorer,
|
|
419
496
|
KafkaHealthIndicator,
|
|
420
497
|
KafkaModule,
|
|
498
|
+
KafkaProcessingError,
|
|
499
|
+
KafkaRetryExhaustedError,
|
|
421
500
|
SubscribeTo,
|
|
422
|
-
getKafkaClientToken
|
|
501
|
+
getKafkaClientToken,
|
|
502
|
+
topic
|
|
423
503
|
};
|
|
424
504
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/module/kafka.module.ts","../src/client/kafka.client.ts","../src/module/kafka.constants.ts","../src/module/kafka.explorer.ts","../src/decorators/kafka.decorator.ts","../src/health/kafka.health.ts"],"sourcesContent":["import { Module, DynamicModule, Provider } from \"@nestjs/common\";\nimport { DiscoveryModule } from \"@nestjs/core\";\nimport {\n KafkaClient,\n ClientId,\n GroupId,\n TopicMapConstraint,\n} from \"../client/kafka.client\";\nimport { getKafkaClientToken } from \"./kafka.constants\";\nimport { KafkaExplorer } from \"./kafka.explorer\";\n\n/** Synchronous configuration for `KafkaModule.register()`. */\nexport interface KafkaModuleOptions {\n /** Optional name for multi-client setups. Must match `@InjectKafkaClient(name)`. */\n name?: string;\n /** Unique Kafka client identifier. */\n clientId: ClientId;\n /** Consumer group identifier. */\n groupId: GroupId;\n /** List of Kafka broker addresses. */\n brokers: string[];\n /** If true, makes KAFKA_CLIENT available globally without importing KafkaModule in every feature module. */\n isGlobal?: boolean;\n}\n\n/** Async configuration for `KafkaModule.registerAsync()` with dependency injection. */\nexport interface KafkaModuleAsyncOptions {\n name?: string;\n /** If true, makes KAFKA_CLIENT available globally without importing KafkaModule in every feature module. */\n isGlobal?: boolean;\n imports?: any[];\n useFactory: (\n ...args: any[]\n ) => KafkaModuleOptions | Promise<KafkaModuleOptions>;\n inject?: any[];\n}\n\n/**\n * NestJS dynamic module for registering type-safe Kafka clients.\n * Use `register()` for static config or `registerAsync()` for DI-based config.\n */\n@Module({})\nexport class KafkaModule {\n /** Register a Kafka client with static options. */\n static register<T extends TopicMapConstraint<T>>(\n options: KafkaModuleOptions,\n ): DynamicModule {\n const token = getKafkaClientToken(options.name);\n\n const kafkaClientProvider: Provider = {\n provide: token,\n useFactory: async (): Promise<KafkaClient<T>> => {\n const client = new KafkaClient<T>(\n options.clientId,\n options.groupId,\n options.brokers,\n );\n await client.connectProducer();\n return client;\n },\n };\n\n const destroyProvider: Provider = {\n provide: `${token}_DESTROY`,\n useFactory: (client: KafkaClient<T>) => ({\n onModuleDestroy: () => client.disconnect(),\n }),\n inject: [token],\n };\n\n return {\n global: options.isGlobal ?? false,\n module: KafkaModule,\n imports: [DiscoveryModule],\n providers: [kafkaClientProvider, destroyProvider, KafkaExplorer],\n exports: [kafkaClientProvider],\n };\n }\n\n /** Register a Kafka client with async/factory-based options. */\n static registerAsync<T extends TopicMapConstraint<T>>(\n asyncOptions: KafkaModuleAsyncOptions,\n ): DynamicModule {\n const token = getKafkaClientToken(asyncOptions.name);\n\n const kafkaClientProvider: Provider = {\n provide: token,\n useFactory: async (...args: any[]): Promise<KafkaClient<T>> => {\n const options = await asyncOptions.useFactory(...args);\n const client = new KafkaClient<T>(\n options.clientId,\n options.groupId,\n options.brokers,\n );\n await client.connectProducer();\n return client;\n },\n inject: asyncOptions.inject || [],\n };\n\n const destroyProvider: Provider = {\n provide: `${token}_DESTROY`,\n useFactory: (client: KafkaClient<T>) => ({\n onModuleDestroy: () => client.disconnect(),\n }),\n inject: [token],\n };\n\n return {\n global: asyncOptions.isGlobal ?? false,\n module: KafkaModule,\n imports: [...(asyncOptions.imports || []), DiscoveryModule],\n providers: [kafkaClientProvider, destroyProvider, KafkaExplorer],\n exports: [kafkaClientProvider],\n };\n }\n}\n","import { Consumer, Kafka, Partitioners, Producer, Admin } from \"kafkajs\";\nimport { Logger } from \"@nestjs/common\";\n\n/**\n * Mapping of topic names to their message types.\n * Define this interface to get type-safe publish/subscribe across your app.\n *\n * @example\n * ```ts\n * // with explicit extends (IDE hints for values)\n * interface MyTopics extends TTopicMessageMap {\n * \"orders.created\": { orderId: string; amount: number };\n * \"users.updated\": { userId: string; name: string };\n * }\n *\n * // or plain interface / type — works the same\n * interface MyTopics {\n * \"orders.created\": { orderId: string; amount: number };\n * }\n * ```\n */\nexport type TTopicMessageMap = {\n [topic: string]: Record<string, any>;\n};\n\n/**\n * Generic constraint for topic-message maps.\n * Works with both `type` aliases and `interface` declarations.\n */\nexport type TopicMapConstraint<T> = { [K in keyof T]: Record<string, any> };\n\nexport type ClientId = string;\nexport type GroupId = string;\n\nexport type MessageHeaders = Record<string, string>;\n\n/** Options for sending a single message. */\nexport interface SendOptions {\n /** Partition key for message routing. */\n key?: string;\n /** Custom headers attached to the message. */\n headers?: MessageHeaders;\n}\n\n/** Options for configuring a Kafka consumer. */\nexport interface ConsumerOptions<\n T extends TopicMapConstraint<T> = TTopicMessageMap,\n> {\n /** Start reading from earliest offset. Default: `false`. */\n fromBeginning?: boolean;\n /** Automatically commit offsets. Default: `true`. */\n autoCommit?: boolean;\n /** Retry policy for failed message processing. */\n retry?: RetryOptions;\n /** Send failed messages to a Dead Letter Queue (`<topic>.dlq`). */\n dlq?: boolean;\n /** Interceptors called before/after each message. */\n interceptors?: ConsumerInterceptor<T>[];\n}\n\n/** Configuration for consumer retry behavior. */\nexport interface RetryOptions {\n /** Maximum number of retry attempts before giving up. */\n maxRetries: number;\n /** Base delay between retries in ms (multiplied by attempt number). Default: `1000`. */\n backoffMs?: number;\n}\n\n/**\n * Interceptor hooks for consumer message processing.\n * All methods are optional — implement only what you need.\n */\nexport interface ConsumerInterceptor<\n T extends TopicMapConstraint<T> = TTopicMessageMap,\n> {\n /** Called before the message handler. */\n before?(message: T[keyof T], topic: string): Promise<void> | void;\n /** Called after the message handler succeeds. */\n after?(message: T[keyof T], topic: string): Promise<void> | void;\n /** Called when the message handler throws. */\n onError?(\n message: T[keyof T],\n topic: string,\n error: Error,\n ): Promise<void> | void;\n}\n\n/** Context passed to the `transaction()` callback with type-safe send methods. */\nexport interface TransactionContext<T extends TopicMapConstraint<T>> {\n send<K extends keyof T>(\n topic: K,\n message: T[K],\n options?: SendOptions,\n ): Promise<void>;\n sendBatch<K extends keyof T>(\n topic: K,\n messages: Array<{ value: T[K]; key?: string; headers?: MessageHeaders }>,\n ): Promise<void>;\n}\n\n/** Interface describing all public methods of the Kafka client. */\nexport interface IKafkaClient<T extends TopicMapConstraint<T>> {\n checkStatus(): Promise<{ topics: string[] }>;\n\n startConsumer<K extends Array<keyof T>>(\n topics: K,\n handleMessage: (message: T[K[number]], topic: K[number]) => Promise<void>,\n options?: ConsumerOptions<T>,\n ): Promise<void>;\n\n stopConsumer(): Promise<void>;\n\n sendMessage<K extends keyof T>(\n topic: K,\n message: T[K],\n options?: SendOptions,\n ): Promise<void>;\n\n sendBatch<K extends keyof T>(\n topic: K,\n messages: Array<{ value: T[K]; key?: string; headers?: MessageHeaders }>,\n ): Promise<void>;\n\n transaction(fn: (ctx: TransactionContext<T>) => Promise<void>): Promise<void>;\n\n getClientId: () => ClientId;\n\n disconnect(): Promise<void>;\n}\n\n/**\n * Type-safe Kafka client for NestJS.\n * Wraps kafkajs with JSON serialization, retries, DLQ, transactions, and interceptors.\n *\n * @typeParam T - Topic-to-message type mapping for compile-time safety.\n */\nexport class KafkaClient<\n T extends TopicMapConstraint<T>,\n> implements IKafkaClient<T> {\n private readonly kafka: Kafka;\n private readonly producer: Producer;\n private readonly consumer: Consumer;\n private readonly admin: Admin;\n private readonly logger: Logger;\n private isConsumerRunning = false;\n private isAdminConnected = false;\n public readonly clientId: ClientId;\n\n constructor(clientId: ClientId, groupId: GroupId, brokers: string[]) {\n this.clientId = clientId;\n this.logger = new Logger(`KafkaClient:${clientId}`);\n\n this.kafka = new Kafka({\n clientId: this.clientId,\n brokers,\n });\n this.producer = this.kafka.producer({\n createPartitioner: Partitioners.DefaultPartitioner,\n idempotent: true,\n transactionalId: `${clientId}-tx`,\n maxInFlightRequests: 1,\n });\n this.consumer = this.kafka.consumer({ groupId });\n this.admin = this.kafka.admin();\n }\n\n /** Send a single typed message to a topic. */\n public async sendMessage<K extends keyof T>(\n topic: K,\n message: T[K],\n options: SendOptions = {},\n ): Promise<void> {\n await this.producer.send({\n topic: topic as string,\n messages: [\n {\n value: JSON.stringify(message),\n key: options.key ?? null,\n headers: options.headers,\n },\n ],\n acks: -1,\n });\n }\n\n /** Send multiple typed messages to a topic in one call. */\n public async sendBatch<K extends keyof T>(\n topic: K,\n messages: Array<{ value: T[K]; key?: string; headers?: MessageHeaders }>,\n ): Promise<void> {\n await this.producer.send({\n topic: topic as string,\n messages: messages.map((m) => ({\n value: JSON.stringify(m.value),\n key: m.key ?? null,\n headers: m.headers,\n })),\n acks: -1,\n });\n }\n\n /** Execute multiple sends atomically. Commits on success, aborts on error. */\n public async transaction(\n fn: (ctx: TransactionContext<T>) => Promise<void>,\n ): Promise<void> {\n const tx = await this.producer.transaction();\n try {\n const ctx: TransactionContext<T> = {\n send: async (topic, message, options = {}) => {\n await tx.send({\n topic: topic as string,\n messages: [\n {\n value: JSON.stringify(message),\n key: options.key ?? null,\n headers: options.headers,\n },\n ],\n acks: -1,\n });\n },\n sendBatch: async (topic, messages) => {\n await tx.send({\n topic: topic as string,\n messages: messages.map((m) => ({\n value: JSON.stringify(m.value),\n key: m.key ?? null,\n headers: m.headers,\n })),\n acks: -1,\n });\n },\n };\n await fn(ctx);\n await tx.commit();\n } catch (error) {\n await tx.abort();\n throw error;\n }\n }\n\n /** Connect the idempotent producer. Called automatically by `KafkaModule.register()`. */\n public async connectProducer(): Promise<void> {\n await this.producer.connect();\n this.logger.log(\"Producer connected\");\n }\n\n public async disconnectProducer(): Promise<void> {\n await this.producer.disconnect();\n this.logger.log(\"Producer disconnected\");\n }\n\n /** Subscribe to topics and start consuming messages with the given handler. */\n public async startConsumer<K extends Array<keyof T>>(\n topics: K,\n handleMessage: (message: T[K[number]], topic: K[number]) => Promise<void>,\n options: ConsumerOptions<T> = {},\n ): Promise<void> {\n const {\n fromBeginning = false,\n autoCommit = true,\n retry,\n dlq = false,\n interceptors = [],\n } = options;\n\n await this.consumer.connect();\n await this.consumer.subscribe({\n topics: topics as string[],\n fromBeginning,\n });\n this.isConsumerRunning = true;\n this.logger.log(\n `Consumer subscribed to topics: ${(topics as string[]).join(\", \")}`,\n );\n\n await this.consumer.run({\n autoCommit,\n eachMessage: async ({ topic, message }) => {\n if (!message.value) {\n this.logger.warn(`Received empty message from topic ${topic}`);\n return;\n }\n\n const raw = message.value.toString();\n let parsedMessage: T[K[number]];\n\n try {\n parsedMessage = JSON.parse(raw) as T[K[number]];\n } catch (error) {\n this.logger.error(\n `Failed to parse message from topic ${topic}:`,\n error instanceof Error ? error.stack : String(error),\n );\n return;\n }\n\n const maxAttempts = retry ? retry.maxRetries + 1 : 1;\n const backoffMs = retry?.backoffMs ?? 1000;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n for (const interceptor of interceptors) {\n await interceptor.before?.(parsedMessage, topic);\n }\n\n await handleMessage(parsedMessage, topic as K[number]);\n\n for (const interceptor of interceptors) {\n await interceptor.after?.(parsedMessage, topic);\n }\n return;\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(String(error));\n for (const interceptor of interceptors) {\n await interceptor.onError?.(parsedMessage, topic, err);\n }\n\n const isLastAttempt = attempt === maxAttempts;\n this.logger.error(\n `Error processing message from topic ${topic} (attempt ${attempt}/${maxAttempts}):`,\n err.stack,\n );\n\n if (isLastAttempt) {\n if (dlq) {\n await this.sendToDlq(topic, raw);\n }\n } else {\n await this.sleep(backoffMs * attempt);\n }\n }\n }\n },\n });\n }\n\n public async stopConsumer(): Promise<void> {\n this.isConsumerRunning = false;\n await this.consumer.disconnect();\n this.logger.log(\"Consumer disconnected\");\n }\n\n /** Check broker connectivity and return available topics. */\n public async checkStatus(): Promise<{ topics: string[] }> {\n if (!this.isAdminConnected) {\n await this.admin.connect();\n this.isAdminConnected = true;\n }\n const topics = await this.admin.listTopics();\n return { topics };\n }\n\n public getClientId(): ClientId {\n return this.clientId;\n }\n\n /** Gracefully disconnect producer, consumer, and admin. */\n public async disconnect(): Promise<void> {\n this.isConsumerRunning = false;\n const tasks = [\n this.producer.disconnect(),\n this.consumer.disconnect(),\n ];\n if (this.isAdminConnected) {\n tasks.push(this.admin.disconnect());\n this.isAdminConnected = false;\n }\n await Promise.allSettled(tasks);\n this.logger.log(\"All connections closed\");\n }\n\n private async sendToDlq(topic: string, rawMessage: string): Promise<void> {\n const dlqTopic = `${topic}.dlq`;\n try {\n await this.producer.send({\n topic: dlqTopic,\n messages: [{ value: rawMessage }],\n acks: -1,\n });\n this.logger.warn(`Message sent to DLQ: ${dlqTopic}`);\n } catch (error) {\n this.logger.error(\n `Failed to send message to DLQ ${dlqTopic}:`,\n error instanceof Error ? error.stack : String(error),\n );\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","/** Default DI token for the Kafka client. */\nexport const KAFKA_CLIENT = \"KAFKA_CLIENT\";\n\n/** Returns the DI token for a named (or default) Kafka client instance. */\nexport const getKafkaClientToken = (name?: string): string =>\n name ? `KAFKA_CLIENT_${name}` : KAFKA_CLIENT;\n","import { Inject, Injectable, OnModuleInit, Logger } from \"@nestjs/common\";\nimport { DiscoveryService, ModuleRef } from \"@nestjs/core\";\nimport { KafkaClient } from \"../client/kafka.client\";\nimport {\n KAFKA_SUBSCRIBER_METADATA,\n KafkaSubscriberMetadata,\n} from \"../decorators/kafka.decorator\";\nimport { getKafkaClientToken } from \"./kafka.constants\";\n\ninterface SubscriberEntry extends KafkaSubscriberMetadata {\n methodName: string | symbol;\n}\n\n/** Discovers `@SubscribeTo()` decorators and wires them to their Kafka clients on startup. */\n@Injectable()\nexport class KafkaExplorer implements OnModuleInit {\n private readonly logger = new Logger(KafkaExplorer.name);\n\n constructor(\n @Inject(DiscoveryService)\n private readonly discoveryService: DiscoveryService,\n @Inject(ModuleRef)\n private readonly moduleRef: ModuleRef,\n ) {}\n\n async onModuleInit() {\n const providers = this.discoveryService.getProviders();\n\n for (const wrapper of providers) {\n const { instance } = wrapper;\n if (!instance || typeof instance !== \"object\") continue;\n\n const metadata: SubscriberEntry[] | undefined = Reflect.getMetadata(\n KAFKA_SUBSCRIBER_METADATA,\n instance.constructor,\n );\n\n if (!metadata || metadata.length === 0) continue;\n\n for (const entry of metadata) {\n const token = getKafkaClientToken(entry.clientName);\n let client: KafkaClient<any>;\n\n try {\n client = this.moduleRef.get(token, { strict: false });\n } catch {\n this.logger.error(\n `KafkaClient \"${entry.clientName || \"default\"}\" not found for @SubscribeTo on ${instance.constructor.name}.${String(entry.methodName)}`,\n );\n continue;\n }\n\n const handler = (instance as any)[entry.methodName].bind(instance);\n\n await client.startConsumer(\n entry.topics as any,\n async (message: any, topic: any) => {\n await handler(message, topic);\n },\n entry.options,\n );\n\n this.logger.log(\n `Registered @SubscribeTo(${entry.topics.join(\", \")}) on ${instance.constructor.name}.${String(entry.methodName)}`,\n );\n }\n }\n }\n}\n","import { Inject } from \"@nestjs/common\";\nimport { getKafkaClientToken } from \"../module/kafka.constants\";\nimport { ConsumerOptions } from \"../client/kafka.client\";\n\nexport const KAFKA_SUBSCRIBER_METADATA = \"KAFKA_SUBSCRIBER_METADATA\";\n\nexport interface KafkaSubscriberMetadata {\n topics: string[];\n options?: ConsumerOptions;\n clientName?: string;\n}\n\n/** Inject a `KafkaClient` instance. Pass a name to target a specific named client. */\nexport const InjectKafkaClient = (name?: string): ParameterDecorator =>\n Inject(getKafkaClientToken(name));\n\n/**\n * Decorator that auto-subscribes a method to Kafka topics on module init.\n * The decorated method receives `(message, topic)` for each consumed message.\n */\nexport const SubscribeTo = (\n topics: string | string[],\n options?: ConsumerOptions & { clientName?: string },\n): MethodDecorator => {\n const topicsArray = Array.isArray(topics) ? topics : [topics];\n const { clientName, ...consumerOptions } = options || {};\n\n return (target, propertyKey, _descriptor) => {\n const existing: KafkaSubscriberMetadata[] =\n Reflect.getMetadata(KAFKA_SUBSCRIBER_METADATA, target.constructor) || [];\n\n Reflect.defineMetadata(\n KAFKA_SUBSCRIBER_METADATA,\n [\n ...existing,\n {\n topics: topicsArray,\n options: Object.keys(consumerOptions).length\n ? consumerOptions\n : undefined,\n clientName,\n methodName: propertyKey,\n },\n ],\n target.constructor,\n );\n };\n};\n","import { Injectable } from \"@nestjs/common\";\nimport { KafkaClient, TopicMapConstraint } from \"../client/kafka.client\";\n\n/** Result returned by `KafkaHealthIndicator.check()`. */\nexport interface KafkaHealthResult {\n status: \"up\" | \"down\";\n clientId: string;\n topics?: string[];\n error?: string;\n}\n\n/** Health check service. Call `check(client)` to verify broker connectivity. */\n@Injectable()\nexport class KafkaHealthIndicator {\n async check<T extends TopicMapConstraint<T>>(\n client: KafkaClient<T>,\n ): Promise<KafkaHealthResult> {\n try {\n const { topics } = await client.checkStatus();\n return {\n status: \"up\",\n clientId: client.clientId,\n topics,\n };\n } catch (error) {\n return {\n status: \"down\",\n clientId: client.clientId,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n}\n"],"mappings":"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","names":["Inject","Logger","Logger","Inject","Injectable","Injectable"]}
|
|
1
|
+
{"version":3,"sources":["../src/module/kafka.module.ts","../src/client/kafka.client.ts","../src/client/errors.ts","../src/module/kafka.constants.ts","../src/module/kafka.explorer.ts","../src/decorators/kafka.decorator.ts","../src/client/topic.ts","../src/health/kafka.health.ts"],"sourcesContent":["import { Module, DynamicModule, Provider } from \"@nestjs/common\";\nimport { DiscoveryModule } from \"@nestjs/core\";\nimport {\n KafkaClient,\n ClientId,\n GroupId,\n TopicMapConstraint,\n} from \"../client/kafka.client\";\nimport { getKafkaClientToken } from \"./kafka.constants\";\nimport { KafkaExplorer } from \"./kafka.explorer\";\n\n/** Synchronous configuration for `KafkaModule.register()`. */\nexport interface KafkaModuleOptions {\n /** Optional name for multi-client setups. Must match `@InjectKafkaClient(name)`. */\n name?: string;\n /** Unique Kafka client identifier. */\n clientId: ClientId;\n /** Consumer group identifier. */\n groupId: GroupId;\n /** List of Kafka broker addresses. */\n brokers: string[];\n /** If true, makes KAFKA_CLIENT available globally without importing KafkaModule in every feature module. */\n isGlobal?: boolean;\n /** Auto-create topics via admin on first use (send/consume). Useful for development. */\n autoCreateTopics?: boolean;\n}\n\n/** Async configuration for `KafkaModule.registerAsync()` with dependency injection. */\nexport interface KafkaModuleAsyncOptions {\n name?: string;\n /** If true, makes KAFKA_CLIENT available globally without importing KafkaModule in every feature module. */\n isGlobal?: boolean;\n /** Auto-create topics via admin on first use (send/consume). Useful for development. */\n autoCreateTopics?: boolean;\n imports?: any[];\n useFactory: (\n ...args: any[]\n ) => KafkaModuleOptions | Promise<KafkaModuleOptions>;\n inject?: any[];\n}\n\n/**\n * NestJS dynamic module for registering type-safe Kafka clients.\n * Use `register()` for static config or `registerAsync()` for DI-based config.\n */\n@Module({})\nexport class KafkaModule {\n /** Register a Kafka client with static options. */\n static register<T extends TopicMapConstraint<T>>(\n options: KafkaModuleOptions,\n ): DynamicModule {\n const token = getKafkaClientToken(options.name);\n\n const kafkaClientProvider: Provider = {\n provide: token,\n useFactory: async (): Promise<KafkaClient<T>> => {\n const client = new KafkaClient<T>(\n options.clientId,\n options.groupId,\n options.brokers,\n { autoCreateTopics: options.autoCreateTopics },\n );\n await client.connectProducer();\n return client;\n },\n };\n\n const destroyProvider: Provider = {\n provide: `${token}_DESTROY`,\n useFactory: (client: KafkaClient<T>) => ({\n onModuleDestroy: () => client.disconnect(),\n }),\n inject: [token],\n };\n\n return {\n global: options.isGlobal ?? false,\n module: KafkaModule,\n imports: [DiscoveryModule],\n providers: [kafkaClientProvider, destroyProvider, KafkaExplorer],\n exports: [kafkaClientProvider],\n };\n }\n\n /** Register a Kafka client with async/factory-based options. */\n static registerAsync<T extends TopicMapConstraint<T>>(\n asyncOptions: KafkaModuleAsyncOptions,\n ): DynamicModule {\n const token = getKafkaClientToken(asyncOptions.name);\n\n const kafkaClientProvider: Provider = {\n provide: token,\n useFactory: async (...args: any[]): Promise<KafkaClient<T>> => {\n const options = await asyncOptions.useFactory(...args);\n const client = new KafkaClient<T>(\n options.clientId,\n options.groupId,\n options.brokers,\n { autoCreateTopics: options.autoCreateTopics },\n );\n await client.connectProducer();\n return client;\n },\n inject: asyncOptions.inject || [],\n };\n\n const destroyProvider: Provider = {\n provide: `${token}_DESTROY`,\n useFactory: (client: KafkaClient<T>) => ({\n onModuleDestroy: () => client.disconnect(),\n }),\n inject: [token],\n };\n\n return {\n global: asyncOptions.isGlobal ?? false,\n module: KafkaModule,\n imports: [...(asyncOptions.imports || []), DiscoveryModule],\n providers: [kafkaClientProvider, destroyProvider, KafkaExplorer],\n exports: [kafkaClientProvider],\n };\n }\n}\n","import { Consumer, Kafka, Partitioners, Producer, Admin } from \"kafkajs\";\nimport { Logger } from \"@nestjs/common\";\nimport { TopicDescriptor } from \"./topic\";\nimport { KafkaRetryExhaustedError } from \"./errors\";\nimport type {\n ClientId,\n GroupId,\n SendOptions,\n MessageHeaders,\n ConsumerOptions,\n TransactionContext,\n TopicMapConstraint,\n IKafkaClient,\n KafkaClientOptions,\n} from \"./types\";\n\n// Re-export all types so existing `import { ... } from './kafka.client'` keeps working\nexport * from \"./types\";\n\n/**\n * Type-safe Kafka client for NestJS.\n * Wraps kafkajs with JSON serialization, retries, DLQ, transactions, and interceptors.\n *\n * @typeParam T - Topic-to-message type mapping for compile-time safety.\n */\nexport class KafkaClient<T extends TopicMapConstraint<T>>\n implements IKafkaClient<T>\n{\n private readonly kafka: Kafka;\n private readonly producer: Producer;\n private readonly consumer: Consumer;\n private readonly admin: Admin;\n private readonly logger: Logger;\n private readonly autoCreateTopicsEnabled: boolean;\n private readonly ensuredTopics = new Set<string>();\n\n private isAdminConnected = false;\n public readonly clientId: ClientId;\n\n constructor(\n clientId: ClientId,\n groupId: GroupId,\n brokers: string[],\n options?: KafkaClientOptions,\n ) {\n this.clientId = clientId;\n this.logger = new Logger(`KafkaClient:${clientId}`);\n this.autoCreateTopicsEnabled = options?.autoCreateTopics ?? false;\n\n this.kafka = new Kafka({\n clientId: this.clientId,\n brokers,\n });\n this.producer = this.kafka.producer({\n createPartitioner: Partitioners.DefaultPartitioner,\n idempotent: true,\n transactionalId: `${clientId}-tx`,\n maxInFlightRequests: 1,\n });\n this.consumer = this.kafka.consumer({ groupId });\n this.admin = this.kafka.admin();\n }\n\n private resolveTopicName(topicOrDescriptor: unknown): string {\n if (typeof topicOrDescriptor === \"string\") return topicOrDescriptor;\n if (\n topicOrDescriptor &&\n typeof topicOrDescriptor === \"object\" &&\n \"__topic\" in topicOrDescriptor\n ) {\n return (topicOrDescriptor as TopicDescriptor).__topic;\n }\n return String(topicOrDescriptor);\n }\n\n private async ensureTopic(topic: string): Promise<void> {\n if (!this.autoCreateTopicsEnabled || this.ensuredTopics.has(topic)) return;\n if (!this.isAdminConnected) {\n await this.admin.connect();\n this.isAdminConnected = true;\n }\n await this.admin.createTopics({\n topics: [{ topic, numPartitions: 1 }],\n });\n this.ensuredTopics.add(topic);\n }\n\n /** Send a single typed message. Accepts a topic key or a TopicDescriptor. */\n public async sendMessage<\n D extends TopicDescriptor<string & keyof T, T[string & keyof T]>,\n >(descriptor: D, message: D[\"__type\"], options?: SendOptions): Promise<void>;\n public async sendMessage<K extends keyof T>(\n topic: K,\n message: T[K],\n options?: SendOptions,\n ): Promise<void>;\n public async sendMessage(\n topicOrDesc: any,\n message: any,\n options: SendOptions = {},\n ): Promise<void> {\n const topic = this.resolveTopicName(topicOrDesc);\n await this.ensureTopic(topic);\n await this.producer.send({\n topic,\n messages: [\n {\n value: JSON.stringify(message),\n key: options.key ?? null,\n headers: options.headers,\n },\n ],\n acks: -1,\n });\n }\n\n /** Send multiple typed messages in one call. Accepts a topic key or a TopicDescriptor. */\n public async sendBatch<\n D extends TopicDescriptor<string & keyof T, T[string & keyof T]>,\n >(\n descriptor: D,\n messages: Array<{\n value: D[\"__type\"];\n key?: string;\n headers?: MessageHeaders;\n }>,\n ): Promise<void>;\n public async sendBatch<K extends keyof T>(\n topic: K,\n messages: Array<{ value: T[K]; key?: string; headers?: MessageHeaders }>,\n ): Promise<void>;\n public async sendBatch(\n topicOrDesc: any,\n messages: Array<{ value: any; key?: string; headers?: MessageHeaders }>,\n ): Promise<void> {\n const topic = this.resolveTopicName(topicOrDesc);\n await this.ensureTopic(topic);\n await this.producer.send({\n topic,\n messages: messages.map((m) => ({\n value: JSON.stringify(m.value),\n key: m.key ?? null,\n headers: m.headers,\n })),\n acks: -1,\n });\n }\n\n /** Execute multiple sends atomically. Commits on success, aborts on error. */\n public async transaction(\n fn: (ctx: TransactionContext<T>) => Promise<void>,\n ): Promise<void> {\n const tx = await this.producer.transaction();\n try {\n const ctx: TransactionContext<T> = {\n send: async (\n topicOrDesc: any,\n message: any,\n options: SendOptions = {},\n ) => {\n const topic = this.resolveTopicName(topicOrDesc);\n await this.ensureTopic(topic);\n await tx.send({\n topic,\n messages: [\n {\n value: JSON.stringify(message),\n key: options.key ?? null,\n headers: options.headers,\n },\n ],\n acks: -1,\n });\n },\n sendBatch: async (topicOrDesc: any, messages: any[]) => {\n const topic = this.resolveTopicName(topicOrDesc);\n await this.ensureTopic(topic);\n await tx.send({\n topic,\n messages: messages.map((m: any) => ({\n value: JSON.stringify(m.value),\n key: m.key ?? null,\n headers: m.headers,\n })),\n acks: -1,\n });\n },\n };\n await fn(ctx);\n await tx.commit();\n } catch (error) {\n await tx.abort();\n throw error;\n }\n }\n\n /** Connect the idempotent producer. Called automatically by `KafkaModule.register()`. */\n public async connectProducer(): Promise<void> {\n await this.producer.connect();\n this.logger.log(\"Producer connected\");\n }\n\n public async disconnectProducer(): Promise<void> {\n await this.producer.disconnect();\n this.logger.log(\"Producer disconnected\");\n }\n\n /** Subscribe to topics and start consuming messages with the given handler. */\n public async startConsumer<K extends Array<keyof T>>(\n topics: K | TopicDescriptor[],\n handleMessage: (message: T[K[number]], topic: K[number]) => Promise<void>,\n options: ConsumerOptions<T> = {},\n ): Promise<void> {\n const {\n fromBeginning = false,\n autoCommit = true,\n retry,\n dlq = false,\n interceptors = [],\n } = options;\n\n const topicNames = (topics as any[]).map((t: any) =>\n this.resolveTopicName(t),\n );\n\n await this.consumer.connect();\n\n for (const t of topicNames) {\n await this.ensureTopic(t);\n }\n\n await this.consumer.subscribe({ topics: topicNames, fromBeginning });\n\n this.logger.log(`Consumer subscribed to topics: ${topicNames.join(\", \")}`);\n\n await this.consumer.run({\n autoCommit,\n eachMessage: async ({ topic, message }) => {\n if (!message.value) {\n this.logger.warn(`Received empty message from topic ${topic}`);\n return;\n }\n\n const raw = message.value.toString();\n let parsedMessage: T[K[number]];\n\n try {\n parsedMessage = JSON.parse(raw) as T[K[number]];\n } catch (error) {\n this.logger.error(\n `Failed to parse message from topic ${topic}:`,\n error instanceof Error ? error.stack : String(error),\n );\n return;\n }\n\n await this.processMessage(parsedMessage, raw, topic, handleMessage, {\n retry,\n dlq,\n interceptors,\n });\n },\n });\n }\n\n public async stopConsumer(): Promise<void> {\n\n await this.consumer.disconnect();\n this.logger.log(\"Consumer disconnected\");\n }\n\n /** Check broker connectivity and return available topics. */\n public async checkStatus(): Promise<{ topics: string[] }> {\n if (!this.isAdminConnected) {\n await this.admin.connect();\n this.isAdminConnected = true;\n }\n const topics = await this.admin.listTopics();\n return { topics };\n }\n\n public getClientId(): ClientId {\n return this.clientId;\n }\n\n /** Gracefully disconnect producer, consumer, and admin. */\n public async disconnect(): Promise<void> {\n\n const tasks = [this.producer.disconnect(), this.consumer.disconnect()];\n if (this.isAdminConnected) {\n tasks.push(this.admin.disconnect());\n this.isAdminConnected = false;\n }\n await Promise.allSettled(tasks);\n this.logger.log(\"All connections closed\");\n }\n\n // --- Private helpers ---\n\n private async processMessage<K extends Array<keyof T>>(\n parsedMessage: T[K[number]],\n raw: string,\n topic: string,\n handleMessage: (message: T[K[number]], topic: K[number]) => Promise<void>,\n opts: Pick<ConsumerOptions<T>, \"retry\" | \"dlq\" | \"interceptors\">,\n ): Promise<void> {\n const { retry, dlq = false, interceptors = [] } = opts;\n const maxAttempts = retry ? retry.maxRetries + 1 : 1;\n const backoffMs = retry?.backoffMs ?? 1000;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n for (const interceptor of interceptors) {\n await interceptor.before?.(parsedMessage, topic);\n }\n\n await handleMessage(parsedMessage, topic as K[number]);\n\n for (const interceptor of interceptors) {\n await interceptor.after?.(parsedMessage, topic);\n }\n return;\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(String(error));\n const isLastAttempt = attempt === maxAttempts;\n\n if (isLastAttempt && maxAttempts > 1) {\n const exhaustedError = new KafkaRetryExhaustedError(\n topic,\n parsedMessage,\n maxAttempts,\n { cause: err },\n );\n for (const interceptor of interceptors) {\n await interceptor.onError?.(parsedMessage, topic, exhaustedError);\n }\n } else {\n for (const interceptor of interceptors) {\n await interceptor.onError?.(parsedMessage, topic, err);\n }\n }\n\n this.logger.error(\n `Error processing message from topic ${topic} (attempt ${attempt}/${maxAttempts}):`,\n err.stack,\n );\n\n if (isLastAttempt) {\n if (dlq) await this.sendToDlq(topic, raw);\n } else {\n await this.sleep(backoffMs * attempt);\n }\n }\n }\n }\n\n private async sendToDlq(topic: string, rawMessage: string): Promise<void> {\n const dlqTopic = `${topic}.dlq`;\n try {\n await this.producer.send({\n topic: dlqTopic,\n messages: [{ value: rawMessage }],\n acks: -1,\n });\n this.logger.warn(`Message sent to DLQ: ${dlqTopic}`);\n } catch (error) {\n this.logger.error(\n `Failed to send message to DLQ ${dlqTopic}:`,\n error instanceof Error ? error.stack : String(error),\n );\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","/** Error thrown when a consumer message handler fails. */\nexport class KafkaProcessingError extends Error {\n constructor(\n message: string,\n public readonly topic: string,\n public readonly originalMessage: unknown,\n options?: { cause?: Error },\n ) {\n super(message, options);\n this.name = \"KafkaProcessingError\";\n }\n}\n\n/** Error thrown when all retry attempts are exhausted for a message. */\nexport class KafkaRetryExhaustedError extends KafkaProcessingError {\n constructor(\n topic: string,\n originalMessage: unknown,\n public readonly attempts: number,\n options?: { cause?: Error },\n ) {\n super(\n `Message processing failed after ${attempts} attempts on topic \"${topic}\"`,\n topic,\n originalMessage,\n options,\n );\n this.name = \"KafkaRetryExhaustedError\";\n }\n}\n","/** Default DI token for the Kafka client. */\nexport const KAFKA_CLIENT = \"KAFKA_CLIENT\";\n\n/** Returns the DI token for a named (or default) Kafka client instance. */\nexport const getKafkaClientToken = (name?: string): string =>\n name ? `KAFKA_CLIENT_${name}` : KAFKA_CLIENT;\n","import { Inject, Injectable, OnModuleInit, Logger } from \"@nestjs/common\";\nimport { DiscoveryService, ModuleRef } from \"@nestjs/core\";\nimport { KafkaClient } from \"../client/kafka.client\";\nimport {\n KAFKA_SUBSCRIBER_METADATA,\n KafkaSubscriberMetadata,\n} from \"../decorators/kafka.decorator\";\nimport { getKafkaClientToken } from \"./kafka.constants\";\n\ninterface SubscriberEntry extends KafkaSubscriberMetadata {\n methodName: string | symbol;\n}\n\n/** Discovers `@SubscribeTo()` decorators and wires them to their Kafka clients on startup. */\n@Injectable()\nexport class KafkaExplorer implements OnModuleInit {\n private readonly logger = new Logger(KafkaExplorer.name);\n\n constructor(\n @Inject(DiscoveryService)\n private readonly discoveryService: DiscoveryService,\n @Inject(ModuleRef)\n private readonly moduleRef: ModuleRef,\n ) {}\n\n async onModuleInit() {\n const providers = this.discoveryService.getProviders();\n\n for (const wrapper of providers) {\n const { instance } = wrapper;\n if (!instance || typeof instance !== \"object\") continue;\n\n const metadata: SubscriberEntry[] | undefined = Reflect.getMetadata(\n KAFKA_SUBSCRIBER_METADATA,\n instance.constructor,\n );\n\n if (!metadata || metadata.length === 0) continue;\n\n for (const entry of metadata) {\n const token = getKafkaClientToken(entry.clientName);\n let client: KafkaClient<any>;\n\n try {\n client = this.moduleRef.get(token, { strict: false });\n } catch {\n this.logger.error(\n `KafkaClient \"${entry.clientName || \"default\"}\" not found for @SubscribeTo on ${instance.constructor.name}.${String(entry.methodName)}`,\n );\n continue;\n }\n\n const handler = (instance as any)[entry.methodName].bind(instance);\n\n await client.startConsumer(\n entry.topics as any,\n async (message: any, topic: any) => {\n await handler(message, topic);\n },\n entry.options,\n );\n\n this.logger.log(\n `Registered @SubscribeTo(${entry.topics.join(\", \")}) on ${instance.constructor.name}.${String(entry.methodName)}`,\n );\n }\n }\n }\n}\n","import { Inject } from \"@nestjs/common\";\nimport { getKafkaClientToken } from \"../module/kafka.constants\";\nimport { ConsumerOptions } from \"../client/kafka.client\";\nimport { TopicDescriptor } from \"../client/topic\";\n\nexport const KAFKA_SUBSCRIBER_METADATA = \"KAFKA_SUBSCRIBER_METADATA\";\n\nexport interface KafkaSubscriberMetadata {\n topics: string[];\n options?: ConsumerOptions;\n clientName?: string;\n}\n\n/** Inject a `KafkaClient` instance. Pass a name to target a specific named client. */\nexport const InjectKafkaClient = (name?: string): ParameterDecorator =>\n Inject(getKafkaClientToken(name));\n\n/**\n * Decorator that auto-subscribes a method to Kafka topics on module init.\n * The decorated method receives `(message, topic)` for each consumed message.\n */\nexport const SubscribeTo = (\n topics:\n | string\n | string[]\n | TopicDescriptor\n | TopicDescriptor[]\n | (string | TopicDescriptor)[],\n options?: ConsumerOptions & { clientName?: string },\n): MethodDecorator => {\n const arr = Array.isArray(topics) ? topics : [topics];\n const topicsArray = arr.map((t) =>\n typeof t === \"string\" ? t : t.__topic,\n );\n const { clientName, ...consumerOptions } = options || {};\n\n return (target, propertyKey, _descriptor) => {\n const existing: KafkaSubscriberMetadata[] =\n Reflect.getMetadata(KAFKA_SUBSCRIBER_METADATA, target.constructor) || [];\n\n Reflect.defineMetadata(\n KAFKA_SUBSCRIBER_METADATA,\n [\n ...existing,\n {\n topics: topicsArray,\n options: Object.keys(consumerOptions).length\n ? consumerOptions\n : undefined,\n clientName,\n methodName: propertyKey,\n },\n ],\n target.constructor,\n );\n };\n};\n","/**\n * A typed topic descriptor that pairs a topic name with its message type.\n * Created via the `topic()` factory function.\n *\n * @typeParam N - The literal topic name string.\n * @typeParam M - The message payload type for this topic.\n */\nexport interface TopicDescriptor<\n N extends string = string,\n M extends Record<string, any> = Record<string, any>,\n> {\n readonly __topic: N;\n /** @internal Phantom type — never has a real value at runtime. */\n readonly __type: M;\n}\n\n/**\n * Define a typed topic descriptor.\n *\n * @example\n * ```ts\n * const OrderCreated = topic('order.created')<{ orderId: string; amount: number }>();\n *\n * // Use with KafkaClient:\n * await kafka.sendMessage(OrderCreated, { orderId: '123', amount: 100 });\n *\n * // Use with @SubscribeTo:\n * @SubscribeTo(OrderCreated)\n * async handleOrder(msg) { ... }\n * ```\n */\nexport function topic<N extends string>(name: N) {\n return <M extends Record<string, any>>(): TopicDescriptor<N, M> => ({\n __topic: name,\n __type: undefined as unknown as M,\n });\n}\n\n/**\n * Build a topic-message map type from a union of TopicDescriptors.\n *\n * @example\n * ```ts\n * const OrderCreated = topic('order.created')<{ orderId: string }>();\n * const OrderCompleted = topic('order.completed')<{ completedAt: string }>();\n *\n * type MyTopics = TopicsFrom<typeof OrderCreated | typeof OrderCompleted>;\n * // { 'order.created': { orderId: string }; 'order.completed': { completedAt: string } }\n * ```\n */\nexport type TopicsFrom<D extends TopicDescriptor<any, any>> = {\n [K in D as K[\"__topic\"]]: K[\"__type\"];\n};\n","import { Injectable } from \"@nestjs/common\";\nimport { KafkaClient, TopicMapConstraint } from \"../client/kafka.client\";\n\n/** Result returned by `KafkaHealthIndicator.check()`. */\nexport interface KafkaHealthResult {\n status: \"up\" | \"down\";\n clientId: string;\n topics?: string[];\n error?: string;\n}\n\n/** Health check service. Call `check(client)` to verify broker connectivity. */\n@Injectable()\nexport class KafkaHealthIndicator {\n async check<T extends TopicMapConstraint<T>>(\n client: KafkaClient<T>,\n ): Promise<KafkaHealthResult> {\n try {\n const { topics } = await client.checkStatus();\n return {\n status: \"up\",\n clientId: client.clientId,\n topics,\n };\n } catch (error) {\n return {\n status: \"down\",\n clientId: client.clientId,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n}\n"],"mappings":"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","names":["topic","topic","Inject","Logger","Logger","topic","Inject","Injectable","Injectable"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drarzter/kafka-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Type-safe Kafka client wrapper for NestJS with typed topic-message maps",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsup",
|
|
33
33
|
"test": "jest",
|
|
34
|
+
"test:integration": "jest --config jest.integration.config.ts --forceExit",
|
|
34
35
|
"lint": "eslint src --fix",
|
|
35
36
|
"format": "prettier --write src",
|
|
36
37
|
"prepublishOnly": "npm run build"
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@nestjs/common": "^11.1.13",
|
|
49
50
|
"@nestjs/core": "^11.1.13",
|
|
51
|
+
"@testcontainers/kafka": "^11.11.0",
|
|
50
52
|
"@types/jest": "^30.0.0",
|
|
51
53
|
"@types/node": "^25.2.2",
|
|
52
54
|
"@typescript-eslint/eslint-plugin": "^8.55.0",
|
|
@@ -57,6 +59,7 @@
|
|
|
57
59
|
"prettier": "^3.8.1",
|
|
58
60
|
"reflect-metadata": "^0.2.2",
|
|
59
61
|
"rxjs": "^7.8.2",
|
|
62
|
+
"testcontainers": "^11.11.0",
|
|
60
63
|
"ts-jest": "^29.4.6",
|
|
61
64
|
"ts-node": "^10.9.2",
|
|
62
65
|
"tsup": "^8.5.1",
|