@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.js
CHANGED
|
@@ -36,8 +36,11 @@ __export(index_exports, {
|
|
|
36
36
|
KafkaExplorer: () => KafkaExplorer,
|
|
37
37
|
KafkaHealthIndicator: () => KafkaHealthIndicator,
|
|
38
38
|
KafkaModule: () => KafkaModule,
|
|
39
|
+
KafkaProcessingError: () => KafkaProcessingError,
|
|
40
|
+
KafkaRetryExhaustedError: () => KafkaRetryExhaustedError,
|
|
39
41
|
SubscribeTo: () => SubscribeTo,
|
|
40
|
-
getKafkaClientToken: () => getKafkaClientToken
|
|
42
|
+
getKafkaClientToken: () => getKafkaClientToken,
|
|
43
|
+
topic: () => topic
|
|
41
44
|
});
|
|
42
45
|
module.exports = __toCommonJS(index_exports);
|
|
43
46
|
|
|
@@ -48,18 +51,44 @@ var import_core2 = require("@nestjs/core");
|
|
|
48
51
|
// src/client/kafka.client.ts
|
|
49
52
|
var import_kafkajs = require("kafkajs");
|
|
50
53
|
var import_common = require("@nestjs/common");
|
|
54
|
+
|
|
55
|
+
// src/client/errors.ts
|
|
56
|
+
var KafkaProcessingError = class extends Error {
|
|
57
|
+
constructor(message, topic2, originalMessage, options) {
|
|
58
|
+
super(message, options);
|
|
59
|
+
this.topic = topic2;
|
|
60
|
+
this.originalMessage = originalMessage;
|
|
61
|
+
this.name = "KafkaProcessingError";
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var KafkaRetryExhaustedError = class extends KafkaProcessingError {
|
|
65
|
+
constructor(topic2, originalMessage, attempts, options) {
|
|
66
|
+
super(
|
|
67
|
+
`Message processing failed after ${attempts} attempts on topic "${topic2}"`,
|
|
68
|
+
topic2,
|
|
69
|
+
originalMessage,
|
|
70
|
+
options
|
|
71
|
+
);
|
|
72
|
+
this.attempts = attempts;
|
|
73
|
+
this.name = "KafkaRetryExhaustedError";
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// src/client/kafka.client.ts
|
|
51
78
|
var KafkaClient = class {
|
|
52
79
|
kafka;
|
|
53
80
|
producer;
|
|
54
81
|
consumer;
|
|
55
82
|
admin;
|
|
56
83
|
logger;
|
|
57
|
-
|
|
84
|
+
autoCreateTopicsEnabled;
|
|
85
|
+
ensuredTopics = /* @__PURE__ */ new Set();
|
|
58
86
|
isAdminConnected = false;
|
|
59
87
|
clientId;
|
|
60
|
-
constructor(clientId, groupId, brokers) {
|
|
88
|
+
constructor(clientId, groupId, brokers, options) {
|
|
61
89
|
this.clientId = clientId;
|
|
62
90
|
this.logger = new import_common.Logger(`KafkaClient:${clientId}`);
|
|
91
|
+
this.autoCreateTopicsEnabled = options?.autoCreateTopics ?? false;
|
|
63
92
|
this.kafka = new import_kafkajs.Kafka({
|
|
64
93
|
clientId: this.clientId,
|
|
65
94
|
brokers
|
|
@@ -73,10 +102,29 @@ var KafkaClient = class {
|
|
|
73
102
|
this.consumer = this.kafka.consumer({ groupId });
|
|
74
103
|
this.admin = this.kafka.admin();
|
|
75
104
|
}
|
|
76
|
-
|
|
77
|
-
|
|
105
|
+
resolveTopicName(topicOrDescriptor) {
|
|
106
|
+
if (typeof topicOrDescriptor === "string") return topicOrDescriptor;
|
|
107
|
+
if (topicOrDescriptor && typeof topicOrDescriptor === "object" && "__topic" in topicOrDescriptor) {
|
|
108
|
+
return topicOrDescriptor.__topic;
|
|
109
|
+
}
|
|
110
|
+
return String(topicOrDescriptor);
|
|
111
|
+
}
|
|
112
|
+
async ensureTopic(topic2) {
|
|
113
|
+
if (!this.autoCreateTopicsEnabled || this.ensuredTopics.has(topic2)) return;
|
|
114
|
+
if (!this.isAdminConnected) {
|
|
115
|
+
await this.admin.connect();
|
|
116
|
+
this.isAdminConnected = true;
|
|
117
|
+
}
|
|
118
|
+
await this.admin.createTopics({
|
|
119
|
+
topics: [{ topic: topic2, numPartitions: 1 }]
|
|
120
|
+
});
|
|
121
|
+
this.ensuredTopics.add(topic2);
|
|
122
|
+
}
|
|
123
|
+
async sendMessage(topicOrDesc, message, options = {}) {
|
|
124
|
+
const topic2 = this.resolveTopicName(topicOrDesc);
|
|
125
|
+
await this.ensureTopic(topic2);
|
|
78
126
|
await this.producer.send({
|
|
79
|
-
topic,
|
|
127
|
+
topic: topic2,
|
|
80
128
|
messages: [
|
|
81
129
|
{
|
|
82
130
|
value: JSON.stringify(message),
|
|
@@ -87,10 +135,11 @@ var KafkaClient = class {
|
|
|
87
135
|
acks: -1
|
|
88
136
|
});
|
|
89
137
|
}
|
|
90
|
-
|
|
91
|
-
|
|
138
|
+
async sendBatch(topicOrDesc, messages) {
|
|
139
|
+
const topic2 = this.resolveTopicName(topicOrDesc);
|
|
140
|
+
await this.ensureTopic(topic2);
|
|
92
141
|
await this.producer.send({
|
|
93
|
-
topic,
|
|
142
|
+
topic: topic2,
|
|
94
143
|
messages: messages.map((m) => ({
|
|
95
144
|
value: JSON.stringify(m.value),
|
|
96
145
|
key: m.key ?? null,
|
|
@@ -104,9 +153,11 @@ var KafkaClient = class {
|
|
|
104
153
|
const tx = await this.producer.transaction();
|
|
105
154
|
try {
|
|
106
155
|
const ctx = {
|
|
107
|
-
send: async (
|
|
156
|
+
send: async (topicOrDesc, message, options = {}) => {
|
|
157
|
+
const topic2 = this.resolveTopicName(topicOrDesc);
|
|
158
|
+
await this.ensureTopic(topic2);
|
|
108
159
|
await tx.send({
|
|
109
|
-
topic,
|
|
160
|
+
topic: topic2,
|
|
110
161
|
messages: [
|
|
111
162
|
{
|
|
112
163
|
value: JSON.stringify(message),
|
|
@@ -117,9 +168,11 @@ var KafkaClient = class {
|
|
|
117
168
|
acks: -1
|
|
118
169
|
});
|
|
119
170
|
},
|
|
120
|
-
sendBatch: async (
|
|
171
|
+
sendBatch: async (topicOrDesc, messages) => {
|
|
172
|
+
const topic2 = this.resolveTopicName(topicOrDesc);
|
|
173
|
+
await this.ensureTopic(topic2);
|
|
121
174
|
await tx.send({
|
|
122
|
-
topic,
|
|
175
|
+
topic: topic2,
|
|
123
176
|
messages: messages.map((m) => ({
|
|
124
177
|
value: JSON.stringify(m.value),
|
|
125
178
|
key: m.key ?? null,
|
|
@@ -154,20 +207,20 @@ var KafkaClient = class {
|
|
|
154
207
|
dlq = false,
|
|
155
208
|
interceptors = []
|
|
156
209
|
} = options;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
topics,
|
|
160
|
-
fromBeginning
|
|
161
|
-
});
|
|
162
|
-
this.isConsumerRunning = true;
|
|
163
|
-
this.logger.log(
|
|
164
|
-
`Consumer subscribed to topics: ${topics.join(", ")}`
|
|
210
|
+
const topicNames = topics.map(
|
|
211
|
+
(t) => this.resolveTopicName(t)
|
|
165
212
|
);
|
|
213
|
+
await this.consumer.connect();
|
|
214
|
+
for (const t of topicNames) {
|
|
215
|
+
await this.ensureTopic(t);
|
|
216
|
+
}
|
|
217
|
+
await this.consumer.subscribe({ topics: topicNames, fromBeginning });
|
|
218
|
+
this.logger.log(`Consumer subscribed to topics: ${topicNames.join(", ")}`);
|
|
166
219
|
await this.consumer.run({
|
|
167
220
|
autoCommit,
|
|
168
|
-
eachMessage: async ({ topic, message }) => {
|
|
221
|
+
eachMessage: async ({ topic: topic2, message }) => {
|
|
169
222
|
if (!message.value) {
|
|
170
|
-
this.logger.warn(`Received empty message from topic ${
|
|
223
|
+
this.logger.warn(`Received empty message from topic ${topic2}`);
|
|
171
224
|
return;
|
|
172
225
|
}
|
|
173
226
|
const raw = message.value.toString();
|
|
@@ -176,47 +229,20 @@ var KafkaClient = class {
|
|
|
176
229
|
parsedMessage = JSON.parse(raw);
|
|
177
230
|
} catch (error) {
|
|
178
231
|
this.logger.error(
|
|
179
|
-
`Failed to parse message from topic ${
|
|
232
|
+
`Failed to parse message from topic ${topic2}:`,
|
|
180
233
|
error instanceof Error ? error.stack : String(error)
|
|
181
234
|
);
|
|
182
235
|
return;
|
|
183
236
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
await interceptor.before?.(parsedMessage, topic);
|
|
190
|
-
}
|
|
191
|
-
await handleMessage(parsedMessage, topic);
|
|
192
|
-
for (const interceptor of interceptors) {
|
|
193
|
-
await interceptor.after?.(parsedMessage, topic);
|
|
194
|
-
}
|
|
195
|
-
return;
|
|
196
|
-
} catch (error) {
|
|
197
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
198
|
-
for (const interceptor of interceptors) {
|
|
199
|
-
await interceptor.onError?.(parsedMessage, topic, err);
|
|
200
|
-
}
|
|
201
|
-
const isLastAttempt = attempt === maxAttempts;
|
|
202
|
-
this.logger.error(
|
|
203
|
-
`Error processing message from topic ${topic} (attempt ${attempt}/${maxAttempts}):`,
|
|
204
|
-
err.stack
|
|
205
|
-
);
|
|
206
|
-
if (isLastAttempt) {
|
|
207
|
-
if (dlq) {
|
|
208
|
-
await this.sendToDlq(topic, raw);
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
await this.sleep(backoffMs * attempt);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
237
|
+
await this.processMessage(parsedMessage, raw, topic2, handleMessage, {
|
|
238
|
+
retry,
|
|
239
|
+
dlq,
|
|
240
|
+
interceptors
|
|
241
|
+
});
|
|
215
242
|
}
|
|
216
243
|
});
|
|
217
244
|
}
|
|
218
245
|
async stopConsumer() {
|
|
219
|
-
this.isConsumerRunning = false;
|
|
220
246
|
await this.consumer.disconnect();
|
|
221
247
|
this.logger.log("Consumer disconnected");
|
|
222
248
|
}
|
|
@@ -234,11 +260,7 @@ var KafkaClient = class {
|
|
|
234
260
|
}
|
|
235
261
|
/** Gracefully disconnect producer, consumer, and admin. */
|
|
236
262
|
async disconnect() {
|
|
237
|
-
this.
|
|
238
|
-
const tasks = [
|
|
239
|
-
this.producer.disconnect(),
|
|
240
|
-
this.consumer.disconnect()
|
|
241
|
-
];
|
|
263
|
+
const tasks = [this.producer.disconnect(), this.consumer.disconnect()];
|
|
242
264
|
if (this.isAdminConnected) {
|
|
243
265
|
tasks.push(this.admin.disconnect());
|
|
244
266
|
this.isAdminConnected = false;
|
|
@@ -246,8 +268,53 @@ var KafkaClient = class {
|
|
|
246
268
|
await Promise.allSettled(tasks);
|
|
247
269
|
this.logger.log("All connections closed");
|
|
248
270
|
}
|
|
249
|
-
|
|
250
|
-
|
|
271
|
+
// --- Private helpers ---
|
|
272
|
+
async processMessage(parsedMessage, raw, topic2, handleMessage, opts) {
|
|
273
|
+
const { retry, dlq = false, interceptors = [] } = opts;
|
|
274
|
+
const maxAttempts = retry ? retry.maxRetries + 1 : 1;
|
|
275
|
+
const backoffMs = retry?.backoffMs ?? 1e3;
|
|
276
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
277
|
+
try {
|
|
278
|
+
for (const interceptor of interceptors) {
|
|
279
|
+
await interceptor.before?.(parsedMessage, topic2);
|
|
280
|
+
}
|
|
281
|
+
await handleMessage(parsedMessage, topic2);
|
|
282
|
+
for (const interceptor of interceptors) {
|
|
283
|
+
await interceptor.after?.(parsedMessage, topic2);
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
} catch (error) {
|
|
287
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
288
|
+
const isLastAttempt = attempt === maxAttempts;
|
|
289
|
+
if (isLastAttempt && maxAttempts > 1) {
|
|
290
|
+
const exhaustedError = new KafkaRetryExhaustedError(
|
|
291
|
+
topic2,
|
|
292
|
+
parsedMessage,
|
|
293
|
+
maxAttempts,
|
|
294
|
+
{ cause: err }
|
|
295
|
+
);
|
|
296
|
+
for (const interceptor of interceptors) {
|
|
297
|
+
await interceptor.onError?.(parsedMessage, topic2, exhaustedError);
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
for (const interceptor of interceptors) {
|
|
301
|
+
await interceptor.onError?.(parsedMessage, topic2, err);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
this.logger.error(
|
|
305
|
+
`Error processing message from topic ${topic2} (attempt ${attempt}/${maxAttempts}):`,
|
|
306
|
+
err.stack
|
|
307
|
+
);
|
|
308
|
+
if (isLastAttempt) {
|
|
309
|
+
if (dlq) await this.sendToDlq(topic2, raw);
|
|
310
|
+
} else {
|
|
311
|
+
await this.sleep(backoffMs * attempt);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async sendToDlq(topic2, rawMessage) {
|
|
317
|
+
const dlqTopic = `${topic2}.dlq`;
|
|
251
318
|
try {
|
|
252
319
|
await this.producer.send({
|
|
253
320
|
topic: dlqTopic,
|
|
@@ -280,7 +347,10 @@ var import_common2 = require("@nestjs/common");
|
|
|
280
347
|
var KAFKA_SUBSCRIBER_METADATA = "KAFKA_SUBSCRIBER_METADATA";
|
|
281
348
|
var InjectKafkaClient = (name) => (0, import_common2.Inject)(getKafkaClientToken(name));
|
|
282
349
|
var SubscribeTo = (topics, options) => {
|
|
283
|
-
const
|
|
350
|
+
const arr = Array.isArray(topics) ? topics : [topics];
|
|
351
|
+
const topicsArray = arr.map(
|
|
352
|
+
(t) => typeof t === "string" ? t : t.__topic
|
|
353
|
+
);
|
|
284
354
|
const { clientName, ...consumerOptions } = options || {};
|
|
285
355
|
return (target, propertyKey, _descriptor) => {
|
|
286
356
|
const existing = Reflect.getMetadata(KAFKA_SUBSCRIBER_METADATA, target.constructor) || [];
|
|
@@ -331,8 +401,8 @@ var KafkaExplorer = class {
|
|
|
331
401
|
const handler = instance[entry.methodName].bind(instance);
|
|
332
402
|
await client.startConsumer(
|
|
333
403
|
entry.topics,
|
|
334
|
-
async (message,
|
|
335
|
-
await handler(message,
|
|
404
|
+
async (message, topic2) => {
|
|
405
|
+
await handler(message, topic2);
|
|
336
406
|
},
|
|
337
407
|
entry.options
|
|
338
408
|
);
|
|
@@ -360,7 +430,8 @@ var KafkaModule = class {
|
|
|
360
430
|
const client = new KafkaClient(
|
|
361
431
|
options.clientId,
|
|
362
432
|
options.groupId,
|
|
363
|
-
options.brokers
|
|
433
|
+
options.brokers,
|
|
434
|
+
{ autoCreateTopics: options.autoCreateTopics }
|
|
364
435
|
);
|
|
365
436
|
await client.connectProducer();
|
|
366
437
|
return client;
|
|
@@ -391,7 +462,8 @@ var KafkaModule = class {
|
|
|
391
462
|
const client = new KafkaClient(
|
|
392
463
|
options.clientId,
|
|
393
464
|
options.groupId,
|
|
394
|
-
options.brokers
|
|
465
|
+
options.brokers,
|
|
466
|
+
{ autoCreateTopics: options.autoCreateTopics }
|
|
395
467
|
);
|
|
396
468
|
await client.connectProducer();
|
|
397
469
|
return client;
|
|
@@ -418,6 +490,14 @@ KafkaModule = __decorateClass([
|
|
|
418
490
|
(0, import_common4.Module)({})
|
|
419
491
|
], KafkaModule);
|
|
420
492
|
|
|
493
|
+
// src/client/topic.ts
|
|
494
|
+
function topic(name) {
|
|
495
|
+
return () => ({
|
|
496
|
+
__topic: name,
|
|
497
|
+
__type: void 0
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
421
501
|
// src/health/kafka.health.ts
|
|
422
502
|
var import_common5 = require("@nestjs/common");
|
|
423
503
|
var KafkaHealthIndicator = class {
|
|
@@ -450,7 +530,10 @@ KafkaHealthIndicator = __decorateClass([
|
|
|
450
530
|
KafkaExplorer,
|
|
451
531
|
KafkaHealthIndicator,
|
|
452
532
|
KafkaModule,
|
|
533
|
+
KafkaProcessingError,
|
|
534
|
+
KafkaRetryExhaustedError,
|
|
453
535
|
SubscribeTo,
|
|
454
|
-
getKafkaClientToken
|
|
536
|
+
getKafkaClientToken,
|
|
537
|
+
topic
|
|
455
538
|
});
|
|
456
539
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../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":["export * from \"./module/kafka.module\";\nexport * from \"./client/kafka.client\";\nexport * from \"./module/kafka.constants\";\nexport * from \"./decorators/kafka.decorator\";\nexport * from \"./module/kafka.explorer\";\nexport * from \"./health/kafka.health\";\n","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":["import_common","import_core","import_common","import_common","import_common"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../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":["export * from \"./module/kafka.module\";\nexport * from \"./client/kafka.client\";\nexport * from \"./client/topic\";\nexport * from \"./client/errors\";\nexport * from \"./module/kafka.constants\";\nexport * from \"./decorators/kafka.decorator\";\nexport * from \"./module/kafka.explorer\";\nexport * from \"./health/kafka.health\";\n","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":["import_common","import_core","topic","topic","import_common","import_common","topic","import_common"]}
|