@drarzter/kafka-client 0.1.3 → 0.1.5
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 +31 -0
- package/dist/index.d.mts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +23 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +21 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -158,6 +158,37 @@ KafkaModule.registerAsync<OrdersTopicMap>({
|
|
|
158
158
|
})
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
+
#### Global module
|
|
162
|
+
|
|
163
|
+
By default, `KafkaModule` is scoped — you need to import it in every module that uses `@InjectKafkaClient()`. Pass `isGlobal: true` to make the client available everywhere:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// app.module.ts — register once
|
|
167
|
+
KafkaModule.register<OrdersTopicMap>({
|
|
168
|
+
clientId: 'my-service',
|
|
169
|
+
groupId: 'my-consumer-group',
|
|
170
|
+
brokers: ['localhost:9092'],
|
|
171
|
+
isGlobal: true,
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
// any other module — no need to import KafkaModule
|
|
175
|
+
@Injectable()
|
|
176
|
+
export class SomeService {
|
|
177
|
+
constructor(@InjectKafkaClient() private readonly kafka: KafkaClient<OrdersTopicMap>) {}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Works with `registerAsync()` too:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
KafkaModule.registerAsync<OrdersTopicMap>({
|
|
185
|
+
isGlobal: true,
|
|
186
|
+
imports: [ConfigModule],
|
|
187
|
+
inject: [ConfigService],
|
|
188
|
+
useFactory: (config: ConfigService) => ({ ... }),
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
161
192
|
### 3. Inject and use
|
|
162
193
|
|
|
163
194
|
```typescript
|
package/dist/index.d.mts
CHANGED
|
@@ -110,6 +110,7 @@ declare class KafkaClient<T extends TopicMapConstraint<T>> implements IKafkaClie
|
|
|
110
110
|
private readonly admin;
|
|
111
111
|
private readonly logger;
|
|
112
112
|
private isConsumerRunning;
|
|
113
|
+
private isAdminConnected;
|
|
113
114
|
readonly clientId: ClientId;
|
|
114
115
|
constructor(clientId: ClientId, groupId: GroupId, brokers: string[]);
|
|
115
116
|
/** Send a single typed message to a topic. */
|
|
@@ -133,7 +134,7 @@ declare class KafkaClient<T extends TopicMapConstraint<T>> implements IKafkaClie
|
|
|
133
134
|
topics: string[];
|
|
134
135
|
}>;
|
|
135
136
|
getClientId(): ClientId;
|
|
136
|
-
/** Gracefully disconnect producer and
|
|
137
|
+
/** Gracefully disconnect producer, consumer, and admin. */
|
|
137
138
|
disconnect(): Promise<void>;
|
|
138
139
|
private sendToDlq;
|
|
139
140
|
private sleep;
|
|
@@ -149,10 +150,14 @@ interface KafkaModuleOptions {
|
|
|
149
150
|
groupId: GroupId;
|
|
150
151
|
/** List of Kafka broker addresses. */
|
|
151
152
|
brokers: string[];
|
|
153
|
+
/** If true, makes KAFKA_CLIENT available globally without importing KafkaModule in every feature module. */
|
|
154
|
+
isGlobal?: boolean;
|
|
152
155
|
}
|
|
153
156
|
/** Async configuration for `KafkaModule.registerAsync()` with dependency injection. */
|
|
154
157
|
interface KafkaModuleAsyncOptions {
|
|
155
158
|
name?: string;
|
|
159
|
+
/** If true, makes KAFKA_CLIENT available globally without importing KafkaModule in every feature module. */
|
|
160
|
+
isGlobal?: boolean;
|
|
156
161
|
imports?: any[];
|
|
157
162
|
useFactory: (...args: any[]) => KafkaModuleOptions | Promise<KafkaModuleOptions>;
|
|
158
163
|
inject?: any[];
|
package/dist/index.d.ts
CHANGED
|
@@ -110,6 +110,7 @@ declare class KafkaClient<T extends TopicMapConstraint<T>> implements IKafkaClie
|
|
|
110
110
|
private readonly admin;
|
|
111
111
|
private readonly logger;
|
|
112
112
|
private isConsumerRunning;
|
|
113
|
+
private isAdminConnected;
|
|
113
114
|
readonly clientId: ClientId;
|
|
114
115
|
constructor(clientId: ClientId, groupId: GroupId, brokers: string[]);
|
|
115
116
|
/** Send a single typed message to a topic. */
|
|
@@ -133,7 +134,7 @@ declare class KafkaClient<T extends TopicMapConstraint<T>> implements IKafkaClie
|
|
|
133
134
|
topics: string[];
|
|
134
135
|
}>;
|
|
135
136
|
getClientId(): ClientId;
|
|
136
|
-
/** Gracefully disconnect producer and
|
|
137
|
+
/** Gracefully disconnect producer, consumer, and admin. */
|
|
137
138
|
disconnect(): Promise<void>;
|
|
138
139
|
private sendToDlq;
|
|
139
140
|
private sleep;
|
|
@@ -149,10 +150,14 @@ interface KafkaModuleOptions {
|
|
|
149
150
|
groupId: GroupId;
|
|
150
151
|
/** List of Kafka broker addresses. */
|
|
151
152
|
brokers: string[];
|
|
153
|
+
/** If true, makes KAFKA_CLIENT available globally without importing KafkaModule in every feature module. */
|
|
154
|
+
isGlobal?: boolean;
|
|
152
155
|
}
|
|
153
156
|
/** Async configuration for `KafkaModule.registerAsync()` with dependency injection. */
|
|
154
157
|
interface KafkaModuleAsyncOptions {
|
|
155
158
|
name?: string;
|
|
159
|
+
/** If true, makes KAFKA_CLIENT available globally without importing KafkaModule in every feature module. */
|
|
160
|
+
isGlobal?: boolean;
|
|
156
161
|
imports?: any[];
|
|
157
162
|
useFactory: (...args: any[]) => KafkaModuleOptions | Promise<KafkaModuleOptions>;
|
|
158
163
|
inject?: any[];
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
24
24
|
if (kind && result) __defProp(target, key, result);
|
|
25
25
|
return result;
|
|
26
26
|
};
|
|
27
|
+
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
27
28
|
|
|
28
29
|
// src/index.ts
|
|
29
30
|
var index_exports = {};
|
|
@@ -42,7 +43,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
42
43
|
|
|
43
44
|
// src/module/kafka.module.ts
|
|
44
45
|
var import_common4 = require("@nestjs/common");
|
|
45
|
-
var
|
|
46
|
+
var import_core2 = require("@nestjs/core");
|
|
46
47
|
|
|
47
48
|
// src/client/kafka.client.ts
|
|
48
49
|
var import_kafkajs = require("kafkajs");
|
|
@@ -54,6 +55,7 @@ var KafkaClient = class {
|
|
|
54
55
|
admin;
|
|
55
56
|
logger;
|
|
56
57
|
isConsumerRunning = false;
|
|
58
|
+
isAdminConnected = false;
|
|
57
59
|
clientId;
|
|
58
60
|
constructor(clientId, groupId, brokers) {
|
|
59
61
|
this.clientId = clientId;
|
|
@@ -219,21 +221,28 @@ var KafkaClient = class {
|
|
|
219
221
|
}
|
|
220
222
|
/** Check broker connectivity and return available topics. */
|
|
221
223
|
async checkStatus() {
|
|
222
|
-
|
|
224
|
+
if (!this.isAdminConnected) {
|
|
225
|
+
await this.admin.connect();
|
|
226
|
+
this.isAdminConnected = true;
|
|
227
|
+
}
|
|
223
228
|
const topics = await this.admin.listTopics();
|
|
224
|
-
await this.admin.disconnect();
|
|
225
229
|
return { topics };
|
|
226
230
|
}
|
|
227
231
|
getClientId() {
|
|
228
232
|
return this.clientId;
|
|
229
233
|
}
|
|
230
|
-
/** Gracefully disconnect producer and
|
|
234
|
+
/** Gracefully disconnect producer, consumer, and admin. */
|
|
231
235
|
async disconnect() {
|
|
232
236
|
this.isConsumerRunning = false;
|
|
233
|
-
|
|
237
|
+
const tasks = [
|
|
234
238
|
this.producer.disconnect(),
|
|
235
239
|
this.consumer.disconnect()
|
|
236
|
-
]
|
|
240
|
+
];
|
|
241
|
+
if (this.isAdminConnected) {
|
|
242
|
+
tasks.push(this.admin.disconnect());
|
|
243
|
+
this.isAdminConnected = false;
|
|
244
|
+
}
|
|
245
|
+
await Promise.allSettled(tasks);
|
|
237
246
|
this.logger.log("All connections closed");
|
|
238
247
|
}
|
|
239
248
|
async sendToDlq(topic, rawMessage) {
|
|
@@ -263,6 +272,7 @@ var getKafkaClientToken = (name) => name ? `KAFKA_CLIENT_${name}` : KAFKA_CLIENT
|
|
|
263
272
|
|
|
264
273
|
// src/module/kafka.explorer.ts
|
|
265
274
|
var import_common3 = require("@nestjs/common");
|
|
275
|
+
var import_core = require("@nestjs/core");
|
|
266
276
|
|
|
267
277
|
// src/decorators/kafka.decorator.ts
|
|
268
278
|
var import_common2 = require("@nestjs/common");
|
|
@@ -333,7 +343,9 @@ var KafkaExplorer = class {
|
|
|
333
343
|
}
|
|
334
344
|
};
|
|
335
345
|
KafkaExplorer = __decorateClass([
|
|
336
|
-
(0, import_common3.Injectable)()
|
|
346
|
+
(0, import_common3.Injectable)(),
|
|
347
|
+
__decorateParam(0, (0, import_common3.Inject)(import_core.DiscoveryService)),
|
|
348
|
+
__decorateParam(1, (0, import_common3.Inject)(import_core.ModuleRef))
|
|
337
349
|
], KafkaExplorer);
|
|
338
350
|
|
|
339
351
|
// src/module/kafka.module.ts
|
|
@@ -361,8 +373,9 @@ var KafkaModule = class {
|
|
|
361
373
|
inject: [token]
|
|
362
374
|
};
|
|
363
375
|
return {
|
|
376
|
+
global: options.isGlobal ?? false,
|
|
364
377
|
module: KafkaModule,
|
|
365
|
-
imports: [
|
|
378
|
+
imports: [import_core2.DiscoveryModule],
|
|
366
379
|
providers: [kafkaClientProvider, destroyProvider, KafkaExplorer],
|
|
367
380
|
exports: [kafkaClientProvider]
|
|
368
381
|
};
|
|
@@ -392,8 +405,9 @@ var KafkaModule = class {
|
|
|
392
405
|
inject: [token]
|
|
393
406
|
};
|
|
394
407
|
return {
|
|
408
|
+
global: asyncOptions.isGlobal ?? false,
|
|
395
409
|
module: KafkaModule,
|
|
396
|
-
imports: [...asyncOptions.imports || [],
|
|
410
|
+
imports: [...asyncOptions.imports || [], import_core2.DiscoveryModule],
|
|
397
411
|
providers: [kafkaClientProvider, destroyProvider, KafkaExplorer],
|
|
398
412
|
exports: [kafkaClientProvider]
|
|
399
413
|
};
|
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}\n\n/** Async configuration for `KafkaModule.registerAsync()` with dependency injection. */\nexport interface KafkaModuleAsyncOptions {\n name?: string;\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 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 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 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 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 await this.admin.connect();\n const topics = await this.admin.listTopics();\n await this.admin.disconnect();\n return { topics };\n }\n\n public getClientId(): ClientId {\n return this.clientId;\n }\n\n /** Gracefully disconnect producer and consumer. */\n public async disconnect(): Promise<void> {\n this.isConsumerRunning = false;\n await Promise.allSettled([\n this.producer.disconnect(),\n this.consumer.disconnect(),\n ]);\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 { 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 private readonly discoveryService: DiscoveryService,\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_common","import_common","import_common"]}
|
|
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 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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -8,6 +8,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
8
8
|
if (kind && result) __defProp(target, key, result);
|
|
9
9
|
return result;
|
|
10
10
|
};
|
|
11
|
+
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
11
12
|
|
|
12
13
|
// src/module/kafka.module.ts
|
|
13
14
|
import { Module } from "@nestjs/common";
|
|
@@ -23,6 +24,7 @@ var KafkaClient = class {
|
|
|
23
24
|
admin;
|
|
24
25
|
logger;
|
|
25
26
|
isConsumerRunning = false;
|
|
27
|
+
isAdminConnected = false;
|
|
26
28
|
clientId;
|
|
27
29
|
constructor(clientId, groupId, brokers) {
|
|
28
30
|
this.clientId = clientId;
|
|
@@ -188,21 +190,28 @@ var KafkaClient = class {
|
|
|
188
190
|
}
|
|
189
191
|
/** Check broker connectivity and return available topics. */
|
|
190
192
|
async checkStatus() {
|
|
191
|
-
|
|
193
|
+
if (!this.isAdminConnected) {
|
|
194
|
+
await this.admin.connect();
|
|
195
|
+
this.isAdminConnected = true;
|
|
196
|
+
}
|
|
192
197
|
const topics = await this.admin.listTopics();
|
|
193
|
-
await this.admin.disconnect();
|
|
194
198
|
return { topics };
|
|
195
199
|
}
|
|
196
200
|
getClientId() {
|
|
197
201
|
return this.clientId;
|
|
198
202
|
}
|
|
199
|
-
/** Gracefully disconnect producer and
|
|
203
|
+
/** Gracefully disconnect producer, consumer, and admin. */
|
|
200
204
|
async disconnect() {
|
|
201
205
|
this.isConsumerRunning = false;
|
|
202
|
-
|
|
206
|
+
const tasks = [
|
|
203
207
|
this.producer.disconnect(),
|
|
204
208
|
this.consumer.disconnect()
|
|
205
|
-
]
|
|
209
|
+
];
|
|
210
|
+
if (this.isAdminConnected) {
|
|
211
|
+
tasks.push(this.admin.disconnect());
|
|
212
|
+
this.isAdminConnected = false;
|
|
213
|
+
}
|
|
214
|
+
await Promise.allSettled(tasks);
|
|
206
215
|
this.logger.log("All connections closed");
|
|
207
216
|
}
|
|
208
217
|
async sendToDlq(topic, rawMessage) {
|
|
@@ -231,7 +240,8 @@ var KAFKA_CLIENT = "KAFKA_CLIENT";
|
|
|
231
240
|
var getKafkaClientToken = (name) => name ? `KAFKA_CLIENT_${name}` : KAFKA_CLIENT;
|
|
232
241
|
|
|
233
242
|
// src/module/kafka.explorer.ts
|
|
234
|
-
import { Injectable, Logger as Logger2 } from "@nestjs/common";
|
|
243
|
+
import { Inject as Inject2, Injectable, Logger as Logger2 } from "@nestjs/common";
|
|
244
|
+
import { DiscoveryService, ModuleRef } from "@nestjs/core";
|
|
235
245
|
|
|
236
246
|
// src/decorators/kafka.decorator.ts
|
|
237
247
|
import { Inject } from "@nestjs/common";
|
|
@@ -302,7 +312,9 @@ var KafkaExplorer = class {
|
|
|
302
312
|
}
|
|
303
313
|
};
|
|
304
314
|
KafkaExplorer = __decorateClass([
|
|
305
|
-
Injectable()
|
|
315
|
+
Injectable(),
|
|
316
|
+
__decorateParam(0, Inject2(DiscoveryService)),
|
|
317
|
+
__decorateParam(1, Inject2(ModuleRef))
|
|
306
318
|
], KafkaExplorer);
|
|
307
319
|
|
|
308
320
|
// src/module/kafka.module.ts
|
|
@@ -330,6 +342,7 @@ var KafkaModule = class {
|
|
|
330
342
|
inject: [token]
|
|
331
343
|
};
|
|
332
344
|
return {
|
|
345
|
+
global: options.isGlobal ?? false,
|
|
333
346
|
module: KafkaModule,
|
|
334
347
|
imports: [DiscoveryModule],
|
|
335
348
|
providers: [kafkaClientProvider, destroyProvider, KafkaExplorer],
|
|
@@ -361,6 +374,7 @@ var KafkaModule = class {
|
|
|
361
374
|
inject: [token]
|
|
362
375
|
};
|
|
363
376
|
return {
|
|
377
|
+
global: asyncOptions.isGlobal ?? false,
|
|
364
378
|
module: KafkaModule,
|
|
365
379
|
imports: [...asyncOptions.imports || [], DiscoveryModule],
|
|
366
380
|
providers: [kafkaClientProvider, destroyProvider, KafkaExplorer],
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/module/kafka.module.ts","../src/client/kafka.client.ts","../src/module/kafka.constants.ts","../src/module/kafka.explorer.ts","../src/decorators/kafka.decorator.ts","../src/health/kafka.health.ts"],"sourcesContent":["import { Module, DynamicModule, Provider } from \"@nestjs/common\";\nimport { DiscoveryModule } from \"@nestjs/core\";\nimport {\n KafkaClient,\n ClientId,\n GroupId,\n TopicMapConstraint,\n} from \"../client/kafka.client\";\nimport { getKafkaClientToken } from \"./kafka.constants\";\nimport { KafkaExplorer } from \"./kafka.explorer\";\n\n/** Synchronous configuration for `KafkaModule.register()`. */\nexport interface KafkaModuleOptions {\n /** Optional name for multi-client setups. Must match `@InjectKafkaClient(name)`. */\n name?: string;\n /** Unique Kafka client identifier. */\n clientId: ClientId;\n /** Consumer group identifier. */\n groupId: GroupId;\n /** List of Kafka broker addresses. */\n brokers: string[];\n}\n\n/** Async configuration for `KafkaModule.registerAsync()` with dependency injection. */\nexport interface KafkaModuleAsyncOptions {\n name?: string;\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 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 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 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 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 await this.admin.connect();\n const topics = await this.admin.listTopics();\n await this.admin.disconnect();\n return { topics };\n }\n\n public getClientId(): ClientId {\n return this.clientId;\n }\n\n /** Gracefully disconnect producer and consumer. */\n public async disconnect(): Promise<void> {\n this.isConsumerRunning = false;\n await Promise.allSettled([\n this.producer.disconnect(),\n this.consumer.disconnect(),\n ]);\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 { 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 private readonly discoveryService: DiscoveryService,\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":["Logger","Logger","Injectable","Injectable"]}
|
|
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 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"]}
|