@kaapi/kafka-messaging 0.0.40 → 0.0.41

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/lib/index.d.ts CHANGED
@@ -1,18 +1,92 @@
1
1
  import { Admin, AdminConfig, Consumer, ConsumerConfig, ITopicConfig, Kafka, KafkaConfig, Producer, ProducerConfig } from 'kafkajs';
2
2
  import { ILogger, IMessaging, IMessagingContext } from '@kaapi/kaapi';
3
+ /**
4
+ * Extended messaging context with Kafka-specific metadata.
5
+ */
3
6
  export interface KafkaMessagingContext extends IMessagingContext {
7
+ /** The Kafka message offset */
4
8
  offset?: string;
9
+ address?: string;
5
10
  }
11
+ /**
12
+ * Configuration options for KafkaMessaging.
13
+ * Extends KafkaJS's KafkaConfig with additional Kaapi-specific options.
14
+ */
6
15
  export interface KafkaMessagingConfig extends KafkaConfig {
16
+ /** Optional logger implementing Kaapi's ILogger interface */
7
17
  logger?: ILogger;
18
+ /** Optional unique service address for routing and identification */
8
19
  address?: string;
20
+ /** Optional human-readable name for service tracking/monitoring */
9
21
  name?: string;
22
+ /** Optional default KafkaJS producer configuration */
10
23
  producer?: ProducerConfig;
11
24
  }
25
+ /**
26
+ * Configuration options for subscribing to a Kafka topic.
27
+ * Extends KafkaJS's ConsumerConfig with additional options.
28
+ */
12
29
  export interface KafkaMessagingSubscribeConfig extends Partial<ConsumerConfig> {
30
+ /** Whether to start consuming from the beginning of the topic */
13
31
  fromBeginning?: boolean;
32
+ /** Callback invoked when the consumer is ready */
14
33
  onReady?(consumer: Consumer): void;
34
+ /**
35
+ * Custom consumer group ID. If not provided, defaults to `{name}.{topic}`
36
+ * where `name` is the service name from KafkaMessagingConfig.
37
+ */
38
+ groupId?: string;
39
+ /**
40
+ * Prefix for the auto-generated group ID. Only used when `groupId` is not provided.
41
+ * Defaults to the service `name` or 'group' if name is not set.
42
+ */
43
+ groupIdPrefix?: string;
44
+ /**
45
+ * Whether to log partition offsets on subscribe.
46
+ * Requires an admin client connection, adding some overhead.
47
+ * @default false
48
+ */
49
+ logOffsets?: boolean;
50
+ /**
51
+ * Called when a message handler throws an error.
52
+ * Allows custom error handling (e.g., alerting, metrics, logging to external service).
53
+ *
54
+ * @param error - The error thrown by the handler
55
+ * @param message - The parsed message that failed
56
+ * @param context - The message context (offset, headers, etc.)
57
+ */
58
+ onError?(error: unknown, message: unknown, context: KafkaMessagingContext): void | Promise<void>;
15
59
  }
60
+ /**
61
+ * A message to be published in a batch.
62
+ */
63
+ export interface KafkaMessagingBatchMessage<T = unknown> {
64
+ /** The message payload */
65
+ value: T;
66
+ /** Optional message key for partitioning */
67
+ key?: string;
68
+ /** Optional partition to send to */
69
+ partition?: number;
70
+ /** Optional custom headers (merged with default headers) */
71
+ headers?: Record<string, string>;
72
+ }
73
+ /**
74
+ * A lightweight wrapper around KafkaJS that integrates with the Kaapi framework
75
+ * to provide a clean and consistent message publishing and consuming interface.
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * const messaging = new KafkaMessaging({
80
+ * clientId: 'my-app',
81
+ * brokers: ['localhost:9092'],
82
+ * name: 'my-service',
83
+ * address: 'service-1'
84
+ * });
85
+ *
86
+ * await messaging.publish('my-topic', { event: 'user.created' });
87
+ * await messaging.subscribe('my-topic', (msg, ctx) => console.log(msg));
88
+ * ```
89
+ */
16
90
  export declare class KafkaMessaging implements IMessaging {
17
91
  #private;
18
92
  protected kafka?: Kafka;
@@ -21,10 +95,66 @@ export declare class KafkaMessaging implements IMessaging {
21
95
  protected currentProducerId?: string;
22
96
  get activeConsumers(): ReadonlySet<Consumer>;
23
97
  get activeProducers(): ReadonlySet<Producer>;
98
+ /**
99
+ * Creates a new KafkaMessaging instance.
100
+ *
101
+ * @param arg - Configuration options for the Kafka client
102
+ */
24
103
  constructor(arg: KafkaMessagingConfig);
25
104
  private _createInstance;
105
+ /**
106
+ * Internal method to initialize the shared admin.
107
+ * @private
108
+ */
109
+ private _initializeSharedAdmin;
110
+ /**
111
+ * Internal method to initialize the producer.
112
+ * @private
113
+ */
114
+ private _initializeProducer;
26
115
  protected getKafka(): Kafka | undefined;
116
+ /**
117
+ * Gets or creates a shared admin instance for internal operations.
118
+ * Uses lazy initialization to avoid unnecessary connections.
119
+ *
120
+ * @returns A promise that resolves to the shared admin instance
121
+ */
122
+ protected getSharedAdmin(): Promise<Admin | undefined>;
123
+ /**
124
+ * Creates and connects a Kafka admin client.
125
+ * The admin client is automatically tracked and will be disconnected during shutdown.
126
+ *
127
+ * @param config - Optional admin client configuration
128
+ * @returns A promise that resolves to the connected admin client, or undefined if Kafka is unavailable
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * const admin = await messaging.createAdmin();
133
+ * const topics = await admin?.listTopics();
134
+ * await admin?.disconnect();
135
+ * ```
136
+ */
27
137
  createAdmin(config?: AdminConfig): Promise<Admin | undefined>;
138
+ /**
139
+ * Creates a new Kafka topic with the specified configuration.
140
+ *
141
+ * @param topic - The topic configuration including name, partitions, and replication factor
142
+ * @param config - Optional creation options
143
+ * @param config.validateOnly - If true, only validates the request without creating the topic
144
+ * @param config.waitForLeaders - If true, waits for partition leaders to be elected
145
+ * @param config.timeout - Timeout in milliseconds for the operation
146
+ *
147
+ * @throws {Error} If the admin client cannot be created
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * await messaging.createTopic({
152
+ * topic: 'my-topic',
153
+ * numPartitions: 3,
154
+ * replicationFactor: 1
155
+ * }, { waitForLeaders: true });
156
+ * ```
157
+ */
28
158
  createTopic(topic: ITopicConfig, config?: {
29
159
  validateOnly?: boolean;
30
160
  waitForLeaders?: boolean;
@@ -46,32 +176,163 @@ export declare class KafkaMessaging implements IMessaging {
46
176
  */
47
177
  waitForTopicReady(topic: string, timeoutMs?: number, checkIntervalMs?: number): Promise<void>;
48
178
  /**
49
- * Create a new consumer with optional configuration overrides
50
- * @param groupId Consumer group id
51
- * @param config Consumer configuration overrides
52
- * @returns
179
+ * Fetches and logs partition offsets for a topic.
180
+ * Uses the shared admin instance to minimize connections.
181
+ *
182
+ * @param topic - The topic to fetch offsets for
183
+ * @returns The partition offset information, or undefined if unavailable
184
+ */
185
+ fetchTopicOffsets(topic: string): Promise<Array<{
186
+ partition: number;
187
+ offset: string;
188
+ high: string;
189
+ low: string;
190
+ }> | undefined>;
191
+ /**
192
+ * Creates and connects a new Kafka consumer.
193
+ * The consumer is automatically tracked and will be disconnected during shutdown.
194
+ *
195
+ * @param groupId - The consumer group ID
196
+ * @param config - Optional consumer configuration overrides
197
+ * @returns A promise that resolves to the connected consumer, or undefined if Kafka is unavailable
198
+ *
199
+ * @example
200
+ * ```ts
201
+ * const consumer = await messaging.createConsumer('my-group', {
202
+ * sessionTimeout: 30000
203
+ * });
204
+ * ```
53
205
  */
54
206
  createConsumer(groupId: string, config?: Partial<ConsumerConfig>): Promise<Consumer | undefined>;
55
207
  /**
56
- * Create a new producer with optional configuration overrides
57
- * @param config Producer configuration overrides
58
- * @returns
208
+ * Creates and connects a new Kafka producer.
209
+ * The producer is automatically tracked and will be disconnected during shutdown.
210
+ *
211
+ * @param config - Optional producer configuration overrides
212
+ * @returns A promise that resolves to the connected producer, or undefined if Kafka is unavailable
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * const producer = await messaging.createProducer({
217
+ * idempotent: true
218
+ * });
219
+ * ```
59
220
  */
60
221
  createProducer(config?: Partial<ProducerConfig>): Promise<Producer | undefined>;
61
222
  /**
62
- * Get the producer
223
+ * Gets or creates the singleton producer instance.
224
+ * Uses a promise-based lock to prevent race conditions when called concurrently.
225
+ *
226
+ * @returns A promise that resolves to the producer instance, or undefined if unavailable.
63
227
  */
64
228
  getProducer(): Promise<Producer | undefined>;
65
229
  /**
66
- * Disconnect the producer
230
+ * Disconnects the singleton producer instance.
231
+ *
232
+ * @returns A promise that resolves when the producer is disconnected
67
233
  */
68
234
  disconnectProducer(): Promise<void>;
235
+ /**
236
+ * Publishes multiple messages to a Kafka topic in a single batch.
237
+ * More efficient than multiple `publish()` calls for high-throughput scenarios.
238
+ *
239
+ * @typeParam T - The type of the message payload
240
+ * @param topic - The Kafka topic to publish to
241
+ * @param messages - Array of messages to publish
242
+ *
243
+ * @throws {Error} If the batch fails to send
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * await messaging.publishBatch('user-events', [
248
+ * { value: { event: 'user.created', userId: '1' } },
249
+ * { value: { event: 'user.created', userId: '2' } },
250
+ * { value: { event: 'user.updated', userId: '3' }, key: 'user-3' },
251
+ * ]);
252
+ * ```
253
+ */
254
+ publishBatch<T = unknown>(topic: string, messages: KafkaMessagingBatchMessage<T>[]): Promise<void>;
255
+ /**
256
+ * Publishes a message to the specified Kafka topic.
257
+ * Automatically manages the producer lifecycle and includes service metadata in headers.
258
+ *
259
+ * @typeParam T - The type of the message payload
260
+ * @param topic - The Kafka topic to publish to
261
+ * @param message - The message payload (will be JSON serialized)
262
+ *
263
+ * @throws {Error} If the message fails to send
264
+ *
265
+ * @example
266
+ * ```ts
267
+ * await messaging.publish('user-events', {
268
+ * event: 'user.created',
269
+ * userId: '123',
270
+ * timestamp: Date.now()
271
+ * });
272
+ * ```
273
+ */
69
274
  publish<T = unknown>(topic: string, message: T): Promise<void>;
70
275
  /**
71
- * Listen to a topic
276
+ * Subscribes to a Kafka topic and processes messages with the provided handler.
277
+ * Creates a new consumer for each subscription with an auto-generated group ID.
278
+ *
279
+ * @typeParam T - The expected type of incoming messages
280
+ * @param topic - The Kafka topic to subscribe to
281
+ * @param handler - Callback function invoked for each message. Can be async.
282
+ * @param config - Optional subscription configuration
283
+ * @param config.fromBeginning - Start consuming from the beginning of the topic
284
+ * @param config.onReady - Callback invoked when the consumer is ready
285
+ * @param config.groupId - Override the auto-generated consumer group ID
286
+ * @param config.groupIdPrefix - Prefix for auto-generated group ID (default: service name)
287
+ *
288
+ * @example
289
+ * ```ts
290
+ * // Using auto-generated group ID (e.g., "my-service.user-events")
291
+ * await messaging.subscribe('user-events', handler);
292
+ *
293
+ * // Using custom group ID
294
+ * await messaging.subscribe('user-events', handler, {
295
+ * groupId: 'my-custom-consumer-group'
296
+ * });
297
+ *
298
+ * // Using custom prefix (e.g., "analytics.user-events")
299
+ * await messaging.subscribe('user-events', handler, {
300
+ * groupIdPrefix: 'analytics'
301
+ * });
302
+ *
303
+ * // With offset logging enabled
304
+ * await messaging.subscribe('user-events', handler, {
305
+ * logOffsets: true
306
+ * });
307
+ * ```
72
308
  */
73
309
  subscribe<T = unknown>(topic: string, handler: (message: T, context: KafkaMessagingContext) => Promise<void> | void, config?: KafkaMessagingSubscribeConfig): Promise<void>;
310
+ /**
311
+ * Safely disconnects a Kafka client with timeout protection.
312
+ * Prevents hanging if the client fails to disconnect gracefully.
313
+ *
314
+ * @param client - The Kafka client (producer, consumer, or admin) to disconnect
315
+ * @param timeoutMs - Maximum time to wait for disconnection (default: 5000ms)
316
+ * @returns A promise that resolves when disconnected or rejects on timeout
317
+ *
318
+ * @throws {Error} If the disconnect times out
319
+ */
74
320
  safeDisconnect(client: Producer | Consumer | Admin, timeoutMs?: number): Promise<unknown>;
321
+ /**
322
+ * Gracefully shuts down all tracked Kafka clients (producers, consumers, and admins).
323
+ * Should be called during application teardown to release resources.
324
+ *
325
+ * @returns A summary of the shutdown operation including success and error counts
326
+ *
327
+ * @example
328
+ * ```ts
329
+ * process.on('SIGTERM', async () => {
330
+ * const result = await messaging.shutdown();
331
+ * console.log(`Shutdown complete: ${result.successProducers} producers, ${result.errorCount} errors`);
332
+ * process.exit(0);
333
+ * });
334
+ * ```
335
+ */
75
336
  shutdown(): Promise<{
76
337
  successProducers: number;
77
338
  successConsumers: number;
@@ -79,4 +340,12 @@ export declare class KafkaMessaging implements IMessaging {
79
340
  errorCount: number;
80
341
  }>;
81
342
  }
343
+ /**
344
+ * Safely disconnects a Kafka client with timeout protection.
345
+ * Standalone utility function.
346
+ *
347
+ * @param client - The Kafka client to disconnect
348
+ * @param timeoutMs - Maximum time to wait (default: 5000ms)
349
+ * @returns A promise that resolves when disconnected or rejects on timeout
350
+ */
82
351
  export declare function safeDisconnect(client: Producer | Consumer | Admin, timeoutMs?: number): Promise<unknown>;