@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/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
- isConsumerRunning = false;
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
- /** Send a single typed message to a topic. */
46
- async sendMessage(topic, message, options = {}) {
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
- /** Send multiple typed messages to a topic in one call. */
60
- async sendBatch(topic, messages) {
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 (topic, message, options = {}) => {
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 (topic, messages) => {
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
- await this.consumer.connect();
127
- await this.consumer.subscribe({
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 ${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 ${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
- const maxAttempts = retry ? retry.maxRetries + 1 : 1;
154
- const backoffMs = retry?.backoffMs ?? 1e3;
155
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
156
- try {
157
- for (const interceptor of interceptors) {
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.isConsumerRunning = false;
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
- async sendToDlq(topic, rawMessage) {
219
- const dlqTopic = `${topic}.dlq`;
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 topicsArray = Array.isArray(topics) ? topics : [topics];
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, topic) => {
304
- await handler(message, topic);
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
@@ -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.6",
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",