@autofleet/kafka 0.0.1

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 ADDED
@@ -0,0 +1,579 @@
1
+ # @autofleet/kafka
2
+
3
+ Internal wrapper for Apache Kafka producer using [@platformatic/kafka](https://www.npmjs.com/package/@platformatic/kafka), providing a production-ready interface for publishing messages to Kafka topics.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Features](#features)
9
+ - [Quick Start](#quick-start)
10
+ - [Usage](#usage)
11
+ - [Publishing Messages](#publishing-messages)
12
+ - [Batch Publishing](#batch-publishing)
13
+ - [Managing Connection](#managing-connection)
14
+ - [Configuration](#configuration)
15
+ - [Advanced Features](#advanced-features)
16
+ - [Message Keys for Partitioning](#message-keys-for-partitioning)
17
+ - [Custom Headers](#custom-headers)
18
+ - [Partition Control](#partition-control)
19
+ - [API Reference](#api-reference)
20
+ - [Best Practices](#best-practices)
21
+ - [Testing](#testing)
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pnpm add @autofleet/kafka
27
+ ```
28
+
29
+ ## Features
30
+
31
+ - **Simple Producer API** - Easy-to-use interface for publishing messages
32
+ - **Automatic Connection Management** - Handles connection lifecycle automatically
33
+ - **Batch Publishing** - Efficient batch message sending
34
+ - **Message Partitioning** - Control message distribution across partitions
35
+ - **Custom Headers** - Attach metadata to messages
36
+ - **Graceful Shutdown** - Automatic cleanup on process termination
37
+ - **Type Safety** - Full TypeScript support
38
+ - **Production Ready** - Built with reliability and performance in mind
39
+
40
+ ## Quick Start
41
+
42
+ ```typescript
43
+ import AfKafka from '@autofleet/kafka';
44
+
45
+ // Initialize the producer
46
+ const kafka = new AfKafka({
47
+ brokers: ['localhost:9092'],
48
+ clientId: 'my-service',
49
+ });
50
+
51
+ // Publish a message
52
+ await kafka.publish('user-events', {
53
+ userId: '123',
54
+ action: 'user.created',
55
+ timestamp: Date.now(),
56
+ });
57
+
58
+ // Publish multiple messages in a batch
59
+ await kafka.publishBatch({
60
+ topic: 'user-events',
61
+ messages: [
62
+ { value: { userId: '123', action: 'login' } },
63
+ { value: { userId: '456', action: 'logout' } },
64
+ ],
65
+ });
66
+
67
+ // Disconnect when done
68
+ await kafka.disconnect();
69
+ ```
70
+
71
+ ## Usage
72
+
73
+ ### Publishing Messages
74
+
75
+ #### Basic Publishing
76
+
77
+ ```typescript
78
+ const kafka = new AfKafka({
79
+ brokers: ['localhost:9092', 'localhost:9093'],
80
+ clientId: 'user-service',
81
+ });
82
+
83
+ // Publish a simple message
84
+ await kafka.publish('user-events', {
85
+ userId: '123',
86
+ event: 'profile_updated',
87
+ data: {
88
+ name: 'John Doe',
89
+ email: 'john@example.com',
90
+ },
91
+ });
92
+ ```
93
+
94
+ #### Publishing with Message Key
95
+
96
+ Message keys determine which partition a message goes to, ensuring messages with the same key are always sent to the same partition (maintaining order).
97
+
98
+ ```typescript
99
+ await kafka.publish(
100
+ 'user-events',
101
+ { userId: '123', action: 'update' },
102
+ {
103
+ key: 'user-123', // All messages with this key go to the same partition
104
+ }
105
+ );
106
+ ```
107
+
108
+ #### Publishing with Custom Headers
109
+
110
+ ```typescript
111
+ await kafka.publish(
112
+ 'user-events',
113
+ { userId: '123', action: 'login' },
114
+ {
115
+ headers: {
116
+ 'correlation-id': requestId,
117
+ 'source': 'api-gateway',
118
+ 'version': '1.0.0',
119
+ },
120
+ }
121
+ );
122
+ ```
123
+
124
+ #### Publishing to Specific Partition
125
+
126
+ ```typescript
127
+ await kafka.publish(
128
+ 'user-events',
129
+ { userId: '123', action: 'update' },
130
+ {
131
+ partition: 2, // Send directly to partition 2
132
+ }
133
+ );
134
+ ```
135
+
136
+ ### Batch Publishing
137
+
138
+ Batch publishing is more efficient for sending multiple messages:
139
+
140
+ ```typescript
141
+ await kafka.publishBatch({
142
+ topic: 'user-events',
143
+ messages: [
144
+ {
145
+ value: { userId: '123', action: 'login' },
146
+ key: 'user-123',
147
+ },
148
+ {
149
+ value: { userId: '456', action: 'logout' },
150
+ key: 'user-456',
151
+ },
152
+ {
153
+ value: { userId: '789', action: 'update' },
154
+ key: 'user-789',
155
+ headers: { priority: 'high' },
156
+ },
157
+ ],
158
+ });
159
+ ```
160
+
161
+ ### Managing Connection
162
+
163
+ ```typescript
164
+ // The producer connects automatically on first publish
165
+ await kafka.publish('my-topic', { data: 'value' });
166
+
167
+ // Disconnect when you're done
168
+ await kafka.disconnect();
169
+
170
+ // Graceful shutdown is automatic on SIGTERM/SIGINT
171
+ // unless dontGracefulShutdown is set to true
172
+ ```
173
+
174
+ ## Configuration
175
+
176
+ ### KafkaOptions
177
+
178
+ ```typescript
179
+ interface KafkaOptions {
180
+ // Array of Kafka broker addresses (required)
181
+ brokers: string[];
182
+
183
+ // Client ID for this producer (optional)
184
+ clientId?: string;
185
+
186
+ // Custom logger instance (optional)
187
+ logger?: LoggerInstanceManager;
188
+
189
+ // Skip automatic graceful shutdown (default: false)
190
+ dontGracefulShutdown?: boolean;
191
+ }
192
+ ```
193
+
194
+ The package uses [@platformatic/kafka](https://www.npmjs.com/package/@platformatic/kafka) with `jsonSerializer` for automatic JSON serialization of message values.
195
+
196
+ ## Advanced Features
197
+
198
+ ### Message Keys for Partitioning
199
+
200
+ Use message keys to control partitioning and maintain order:
201
+
202
+ ```typescript
203
+ // All messages for the same user go to the same partition
204
+ await kafka.publish('user-events', event, {
205
+ key: `user-${userId}`,
206
+ });
207
+
208
+ // All messages for the same order go to the same partition
209
+ await kafka.publish('order-events', event, {
210
+ key: `order-${orderId}`,
211
+ });
212
+ ```
213
+
214
+ **Why use keys?**
215
+ - Ensures messages with the same key are ordered
216
+ - Enables partition-level parallelism
217
+ - Supports stateful stream processing
218
+
219
+ ### Custom Headers
220
+
221
+ Headers are useful for metadata and message routing:
222
+
223
+ ```typescript
224
+ await kafka.publish('events', data, {
225
+ headers: {
226
+ // Correlation ID for distributed tracing
227
+ 'correlation-id': correlationId,
228
+
229
+ // Source service
230
+ 'source': 'user-service',
231
+
232
+ // Message schema version
233
+ 'schema-version': '2.0',
234
+
235
+ // Content encoding
236
+ 'encoding': 'json',
237
+
238
+ // Custom application headers
239
+ 'tenant-id': 'tenant-123',
240
+ },
241
+ });
242
+ ```
243
+
244
+ ### Partition Control
245
+
246
+ Direct partition control for advanced use cases:
247
+
248
+ ```typescript
249
+ // Send to specific partition
250
+ await kafka.publish('events', data, {
251
+ partition: 0, // Always send to partition 0
252
+ });
253
+
254
+ // Balance across partitions using keys
255
+ const partitionKey = `${customerId % 10}`;
256
+ await kafka.publish('events', data, {
257
+ key: partitionKey,
258
+ });
259
+ ```
260
+
261
+ ## API Reference
262
+
263
+ ### `new AfKafka(options)`
264
+
265
+ Creates a new Kafka producer instance.
266
+
267
+ **Parameters:**
268
+ - `options` - Configuration options (see [Configuration](#configuration))
269
+
270
+ **Example:**
271
+ ```typescript
272
+ const kafka = new AfKafka({
273
+ brokers: ['localhost:9092'],
274
+ clientId: 'my-service',
275
+ });
276
+ ```
277
+
278
+ ### `publish<T>(topic, value, options?): Promise<RecordMetadata[]>`
279
+
280
+ Publishes a single message to a topic.
281
+
282
+ **Parameters:**
283
+ - `topic` - Topic name
284
+ - `value` - Message value (will be JSON stringified)
285
+ - `options` (optional) - Publish options
286
+
287
+ **Returns:** Array of record metadata (partition, offset, etc.)
288
+
289
+ **Example:**
290
+ ```typescript
291
+ const metadata = await kafka.publish('events', { id: 1, data: 'test' }, {
292
+ key: 'key-1',
293
+ headers: { type: 'create' },
294
+ });
295
+
296
+ console.log(`Published to partition ${metadata[0].partition} at offset ${metadata[0].offset}`);
297
+ ```
298
+
299
+ ### `publishBatch(options): Promise<RecordMetadata[]>`
300
+
301
+ Publishes multiple messages in a batch to a topic.
302
+
303
+ **Parameters:**
304
+ - `options.topic` - Topic name
305
+ - `options.messages` - Array of messages with values, keys, headers, etc.
306
+
307
+ **Returns:** Array of record metadata for each message
308
+
309
+ **Example:**
310
+ ```typescript
311
+ const metadata = await kafka.publishBatch({
312
+ topic: 'events',
313
+ messages: [
314
+ { value: { id: 1 }, key: 'key-1' },
315
+ { value: { id: 2 }, key: 'key-2' },
316
+ ],
317
+ });
318
+ ```
319
+
320
+ ### `disconnect(): Promise<void>`
321
+
322
+ Disconnects the producer and cleans up resources.
323
+
324
+ **Example:**
325
+ ```typescript
326
+ await kafka.disconnect();
327
+ ```
328
+
329
+ ## Best Practices
330
+
331
+ ### 1. Use Centralized Topic Definitions
332
+
333
+ Define your topics in `src/topics.ts`:
334
+
335
+ ```typescript
336
+ export const TOPICS = {
337
+ USER_EVENTS: 'user-events',
338
+ ORDER_EVENTS: 'order-events',
339
+ PAYMENT_EVENTS: 'payment-events',
340
+ } as const;
341
+
342
+ export type TopicName = typeof TOPICS[keyof typeof TOPICS];
343
+ ```
344
+
345
+ Usage:
346
+
347
+ ```typescript
348
+ import { TOPICS } from './topics';
349
+
350
+ await kafka.publish(TOPICS.USER_EVENTS, data);
351
+ ```
352
+
353
+ ### 2. Use Message Keys for Ordering
354
+
355
+ ```typescript
356
+ // Ensure all events for a user are processed in order
357
+ await kafka.publish('user-events', event, {
358
+ key: `user-${userId}`,
359
+ });
360
+
361
+ // Ensure all events for a session are processed in order
362
+ await kafka.publish('session-events', event, {
363
+ key: `session-${sessionId}`,
364
+ });
365
+ ```
366
+
367
+ ### 3. Add Metadata with Headers
368
+
369
+ ```typescript
370
+ await kafka.publish('events', data, {
371
+ headers: {
372
+ 'correlation-id': requestId,
373
+ 'source-service': serviceName,
374
+ 'event-type': eventType,
375
+ 'schema-version': '1.0',
376
+ },
377
+ });
378
+ ```
379
+
380
+ ### 4. Use Batch Publishing for High Throughput
381
+
382
+ ```typescript
383
+ // Instead of multiple publish calls
384
+ for (const event of events) {
385
+ await kafka.publish('events', event); // ❌ Slow
386
+ }
387
+
388
+ // Use batch publishing
389
+ await kafka.publishBatch({
390
+ topic: 'events',
391
+ messages: events.map(e => ({ value: e })), // ✅ Fast
392
+ });
393
+ ```
394
+
395
+ ### 5. Handle Errors Gracefully
396
+
397
+ ```typescript
398
+ try {
399
+ await kafka.publish('events', data);
400
+ } catch (error) {
401
+ logger.error('Failed to publish to Kafka', { error, data });
402
+ // Consider:
403
+ // - Retry logic
404
+ // - Fallback to queue
405
+ // - Alert monitoring
406
+ }
407
+ ```
408
+
409
+ ### 6. Use Multiple Brokers for High Availability
410
+
411
+ ```typescript
412
+ const kafka = new AfKafka({
413
+ brokers: ['kafka1:9092', 'kafka2:9092', 'kafka3:9092'],
414
+ clientId: 'my-service',
415
+ });
416
+ ```
417
+
418
+ ### 7. Monitor Production Metrics
419
+
420
+ ```typescript
421
+ const metadata = await kafka.publish('events', data);
422
+
423
+ // Log metrics
424
+ logger.info('Message published', {
425
+ topic: 'events',
426
+ partition: metadata[0].partition,
427
+ offset: metadata[0].offset,
428
+ latency: Date.now() - startTime,
429
+ });
430
+ ```
431
+
432
+ ## Testing
433
+
434
+ Run the test suite:
435
+
436
+ ```bash
437
+ pnpm test
438
+ ```
439
+
440
+ Run tests with coverage:
441
+
442
+ ```bash
443
+ pnpm run coverage
444
+ ```
445
+
446
+ ### Example Test
447
+
448
+ ```typescript
449
+ import { describe, it, expect, beforeEach } from 'vitest';
450
+ import AfKafka from '@autofleet/kafka';
451
+
452
+ describe('MyKafkaService', () => {
453
+ let kafka: AfKafka;
454
+
455
+ beforeEach(() => {
456
+ kafka = new AfKafka({
457
+ brokers: ['localhost:9092'],
458
+ clientId: 'test-client',
459
+ });
460
+ });
461
+
462
+ it('should publish event successfully', async () => {
463
+ const result = await kafka.publish('test-topic', {
464
+ userId: '123',
465
+ action: 'test',
466
+ });
467
+
468
+ expect(result).toBeDefined();
469
+ expect(result[0]).toHaveProperty('partition');
470
+ expect(result[0]).toHaveProperty('offset');
471
+ });
472
+ });
473
+ ```
474
+
475
+ ## Environment Variables
476
+
477
+ Common environment variable patterns:
478
+
479
+ ```bash
480
+ # Kafka brokers
481
+ KAFKA_BROKERS=kafka1:9092,kafka2:9092,kafka3:9092
482
+
483
+ # Client configuration
484
+ KAFKA_CLIENT_ID=my-service
485
+ KAFKA_SASL_USERNAME=user
486
+ KAFKA_SASL_PASSWORD=pass
487
+ ```
488
+
489
+ Usage:
490
+
491
+ ```typescript
492
+ const kafka = new AfKafka({
493
+ brokers: process.env.KAFKA_BROKERS?.split(',') || ['localhost:9092'],
494
+ clientId: process.env.KAFKA_CLIENT_ID || 'default-client',
495
+ });
496
+ ```
497
+
498
+ ## Common Patterns
499
+
500
+ ### Event Sourcing
501
+
502
+ ```typescript
503
+ await kafka.publish('user-events', {
504
+ eventType: 'UserCreated',
505
+ aggregateId: userId,
506
+ timestamp: Date.now(),
507
+ data: userData,
508
+ }, {
509
+ key: userId,
510
+ headers: {
511
+ 'event-type': 'UserCreated',
512
+ 'aggregate-type': 'User',
513
+ },
514
+ });
515
+ ```
516
+
517
+ ### Change Data Capture (CDC)
518
+
519
+ ```typescript
520
+ await kafka.publish('database-changes', {
521
+ operation: 'INSERT',
522
+ table: 'users',
523
+ before: null,
524
+ after: newUserRecord,
525
+ }, {
526
+ key: newUserRecord.id,
527
+ headers: {
528
+ 'schema': 'public',
529
+ 'table': 'users',
530
+ },
531
+ });
532
+ ```
533
+
534
+ ### Dead Letter Queue Pattern
535
+
536
+ ```typescript
537
+ try {
538
+ await processMessage(message);
539
+ } catch (error) {
540
+ // Send to DLQ for manual review
541
+ await kafka.publish('events-dlq', {
542
+ originalTopic: 'events',
543
+ error: error.message,
544
+ message: message,
545
+ });
546
+ }
547
+ ```
548
+
549
+ ## Troubleshooting
550
+
551
+ ### Connection Issues
552
+
553
+ ```typescript
554
+ // Use multiple brokers for redundancy
555
+ const kafka = new AfKafka({
556
+ brokers: ['kafka1:9092', 'kafka2:9092', 'kafka3:9092'],
557
+ clientId: 'my-service',
558
+ });
559
+ ```
560
+
561
+ ### Performance Tuning
562
+
563
+ For high-throughput scenarios, use batch publishing:
564
+
565
+ ```typescript
566
+ // Batch multiple messages together
567
+ await kafka.publishBatch({
568
+ topic: 'events',
569
+ messages: events.map(e => ({ value: e })),
570
+ });
571
+ ```
572
+
573
+ ## License
574
+
575
+ Proprietary - Autofleet
576
+
577
+ ## Support
578
+
579
+ For issues or questions, please contact the platform team or create an issue in the repository.
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,`__esModule`,{value:!0});let e=require(`@platformatic/kafka`);const t={info:(...e)=>console.log(`[INFO]`,...e),error:(...e)=>console.error(`[ERROR]`,...e),warn:(...e)=>console.warn(`[WARN]`,...e),debug:(...e)=>console.debug(`[DEBUG]`,...e)};var n=t,r=class extends Error{constructor(e){super(e),this.name=`KafkaError`}};const i=`x-timestamp`,a=`autofleet-kafka-producer`;var o=class{constructor(t){if(this.isDisconnected=!1,this.isVerified=!1,this.gracefulShutdownStarted=!1,!t.brokers||t.brokers.length===0)throw new r(`At least one broker is required`);this.logger=t.logger??n,this.producer=new e.Producer({bootstrapBrokers:t.brokers,clientId:t.clientId||a,serializers:e.stringSerializers}),this.logger.info(`Kafka: Initializing Kafka producer`,{clientId:t.clientId||a,brokers:t.brokers}),t.dontGracefulShutdown||this.setupGracefulShutdown()}setupGracefulShutdown(){this.logger.info(`Kafka: [graceful-shutdown] adding graceful shutdown for process.pid ${process.pid}`),process.on(`SIGTERM`,async()=>{await this.gracefulShutdown(`SIGTERM`)}),process.on(`SIGINT`,async()=>{await this.gracefulShutdown(`SIGINT`)})}async gracefulShutdown(e){if(!this.gracefulShutdownStarted){this.gracefulShutdownStarted=!0,this.logger.info(`Kafka: [graceful-shutdown] received ${e}! Disconnecting producer...`);try{await this.disconnect(),this.logger.info(`Kafka: [graceful-shutdown] producer disconnected successfully`)}catch(e){throw this.logger.error(`Kafka: [graceful-shutdown] error during shutdown`,{error:e}),e}}}get isConnected(){return this.producer.isConnected()}async ping(){if(!this.isVerified)try{let e=await this.producer.metadata({topics:[]});this.isVerified=!0,this.logger.info(`Kafka: Successfully connected to cluster`,{clusterId:e.id,brokers:e.brokers.size})}catch(e){throw this.logger.error(`Kafka: Failed to connect to brokers`,{error:e}),new r(`Failed to connect to Kafka brokers: ${e.message}`)}}async publish(e,t,n){if(!e)throw new r(`Topic name is required`);await this.ping();try{let r={[i]:Date.now().toString(),...n?.headers},a=await this.producer.send({messages:[{topic:e,value:JSON.stringify(t),key:n?.key,partition:n?.partition,headers:r}]});return this.logger.debug(`Kafka: Published message to topic ${e}`,{topic:e}),[a]}catch(n){let i=n;throw this.logger.error(`Kafka: Error publishing to topic ${e}`,{error:n,value:t}),new r(`Failed to publish to topic ${e}: ${i.message||`Unknown error`}`)}}async publishBatch(e){if(!e.topic)throw new r(`Topic name is required`);if(!e.messages||e.messages.length===0)throw new r(`At least one message is required`);await this.ping();try{let t=e.messages.map(t=>({topic:e.topic,value:JSON.stringify(t.value),key:t.key,partition:t.partition,headers:{[i]:Date.now().toString(),...t.headers}})),n=await this.producer.send({messages:t});return this.logger.debug(`Kafka: Published ${t.length} messages to topic ${e.topic}`,{topic:e.topic,count:t.length}),[n]}catch(t){let n=t;throw this.logger.error(`Kafka: Error publishing batch to topic ${e.topic}`,{error:t}),new r(`Failed to publish batch to topic ${e.topic}: ${n.message||`Unknown error`}`)}}async disconnect(){if(this.isDisconnected){this.logger.debug(`Kafka: Producer already disconnected`);return}this.logger.info(`Kafka: Disconnecting producer`);try{await this.producer.close(),this.isDisconnected=!0,this.logger.info(`Kafka: Producer disconnected successfully`)}catch(e){throw this.logger.error(`Kafka: Error disconnecting producer`,{error:e}),new r(`Failed to disconnect producer: ${e.message}`)}}},s=o;exports.KafkaError=r,exports.default=s;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["fallbackLogger: LoggerInstanceManager","fallbackLogger","Producer","stringSerializers","headers: Record<string, string>"],"sources":["../src/logger.ts","../src/kafkaError.ts","../src/consts.ts","../src/index.ts"],"sourcesContent":["import type { LoggerInstanceManager } from '@autofleet/logger';\n\n/* eslint-disable no-console */\nconst fallbackLogger: LoggerInstanceManager = {\n info: (...args: unknown[]) => console.log('[INFO]', ...args),\n error: (...args: unknown[]) => console.error('[ERROR]', ...args),\n warn: (...args: unknown[]) => console.warn('[WARN]', ...args),\n debug: (...args: unknown[]) => console.debug('[DEBUG]', ...args),\n} as LoggerInstanceManager;\n/* eslint-enable no-console */\n\nexport default fallbackLogger;\n","export default class KafkaError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'KafkaError';\n }\n}\n","export const TIMESTAMP_HEADER = 'x-timestamp';\nexport const CORRELATION_ID_HEADER = 'x-correlation-id';\nexport const SOURCE_HEADER = 'x-source';\n\nexport const DEFAULT_CLIENT_ID = 'autofleet-kafka-producer';\nexport const DEFAULT_TIMEOUT = 30000; // 30 seconds\nexport const DEFAULT_RETRY = {\n retries: 5,\n initialRetryTime: 300,\n maxRetryTime: 30000,\n};\n","import { Producer, type ProduceResult, stringSerializers } from '@platformatic/kafka';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport fallbackLogger from './logger';\nimport KafkaError from './kafkaError';\nimport {\n DEFAULT_CLIENT_ID,\n TIMESTAMP_HEADER,\n} from './consts';\nimport type {\n KafkaOptions,\n PublishOptions,\n PublishBatchOptions,\n RecordMetadata,\n} from './types';\n\nexport interface IAfKafka {\n ping(): Promise<void>;\n publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<ProduceResult[]>;\n publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]>;\n disconnect(): Promise<void>;\n}\n\nclass AfKafka implements IAfKafka {\n private readonly producer: Producer<string, string, string, string>;\n private readonly logger: LoggerInstanceManager;\n private isDisconnected = false;\n private isVerified = false;\n private gracefulShutdownStarted = false;\n\n constructor(options: KafkaOptions) {\n if (!options.brokers || options.brokers.length === 0) {\n throw new KafkaError('At least one broker is required');\n }\n\n this.logger = options.logger ?? fallbackLogger;\n\n this.producer = new Producer({\n bootstrapBrokers: options.brokers,\n clientId: options.clientId || DEFAULT_CLIENT_ID,\n serializers: stringSerializers,\n });\n\n this.logger.info('Kafka: Initializing Kafka producer', {\n clientId: options.clientId || DEFAULT_CLIENT_ID,\n brokers: options.brokers,\n });\n\n if (!options.dontGracefulShutdown) {\n this.setupGracefulShutdown();\n }\n }\n\n private setupGracefulShutdown(): void {\n this.logger.info(`Kafka: [graceful-shutdown] adding graceful shutdown for process.pid ${process.pid}`);\n\n process.on('SIGTERM', async () => {\n await this.gracefulShutdown('SIGTERM');\n });\n\n process.on('SIGINT', async () => {\n await this.gracefulShutdown('SIGINT');\n });\n }\n\n private async gracefulShutdown(signal: string): Promise<void> {\n if (this.gracefulShutdownStarted) {\n return;\n }\n\n this.gracefulShutdownStarted = true;\n\n this.logger.info(`Kafka: [graceful-shutdown] received ${signal}! Disconnecting producer...`);\n\n try {\n await this.disconnect();\n this.logger.info('Kafka: [graceful-shutdown] producer disconnected successfully');\n } catch (error) {\n this.logger.error('Kafka: [graceful-shutdown] error during shutdown', { error });\n throw error;\n }\n }\n\n get isConnected(): boolean {\n return this.producer.isConnected();\n }\n\n /**\n * Ping Kafka brokers to verify connectivity\n */\n async ping(): Promise<void> {\n if (this.isVerified) {\n return;\n }\n\n try {\n // Fetch metadata to verify connection\n const metadata = await this.producer.metadata({ topics: [] });\n this.isVerified = true;\n\n this.logger.info('Kafka: Successfully connected to cluster', {\n clusterId: metadata.id,\n brokers: metadata.brokers.size,\n });\n } catch (error) {\n this.logger.error('Kafka: Failed to connect to brokers', { error });\n throw new KafkaError(`Failed to connect to Kafka brokers: ${(error as Error).message}`);\n }\n }\n\n /**\n * Publish a single message to a topic\n */\n async publish<T = unknown>(\n topic: string,\n value: T,\n options?: PublishOptions,\n ): Promise<ProduceResult[]> {\n if (!topic) {\n throw new KafkaError('Topic name is required');\n }\n\n // Verify connection on first publish\n await this.ping();\n\n try {\n const headers: Record<string, string> = {\n [TIMESTAMP_HEADER]: Date.now().toString(),\n ...options?.headers,\n };\n\n const res = await this.producer.send({\n messages: [{\n topic,\n value: JSON.stringify(value),\n key: options?.key,\n partition: options?.partition,\n headers,\n }],\n });\n\n this.logger.debug(`Kafka: Published message to topic ${topic}`, {\n topic,\n });\n\n return [res];\n } catch (error) {\n const err = error as { message?: string; };\n this.logger.error(`Kafka: Error publishing to topic ${topic}`, { error, value });\n throw new KafkaError(`Failed to publish to topic ${topic}: ${err.message || 'Unknown error'}`);\n }\n }\n\n /**\n * Publish multiple messages in a batch\n */\n async publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]> {\n if (!options.topic) {\n throw new KafkaError('Topic name is required');\n }\n\n if (!options.messages || options.messages.length === 0) {\n throw new KafkaError('At least one message is required');\n }\n\n // Verify connection on first publish\n await this.ping();\n\n try {\n const messages = options.messages.map(msg => ({\n topic: options.topic,\n value: JSON.stringify(msg.value),\n key: msg.key,\n partition: msg.partition,\n headers: {\n [TIMESTAMP_HEADER]: Date.now().toString(),\n ...msg.headers,\n },\n }));\n\n const res = await this.producer.send({ messages });\n\n this.logger.debug(`Kafka: Published ${messages.length} messages to topic ${options.topic}`, {\n topic: options.topic,\n count: messages.length,\n });\n\n // Return mock metadata since @platformatic/kafka send() returns void\n return [res];\n } catch (error) {\n const err = error as { message?: string; };\n this.logger.error(`Kafka: Error publishing batch to topic ${options.topic}`, { error });\n throw new KafkaError(`Failed to publish batch to topic ${options.topic}: ${err.message || 'Unknown error'}`);\n }\n }\n\n /**\n * Disconnect the producer and clean up resources\n */\n async disconnect(): Promise<void> {\n if (this.isDisconnected) {\n this.logger.debug('Kafka: Producer already disconnected');\n return;\n }\n\n this.logger.info('Kafka: Disconnecting producer');\n\n try {\n await this.producer.close();\n this.isDisconnected = true;\n this.logger.info('Kafka: Producer disconnected successfully');\n } catch (error) {\n this.logger.error('Kafka: Error disconnecting producer', { error });\n throw new KafkaError(`Failed to disconnect producer: ${(error as Error).message}`);\n }\n }\n}\n\nexport default AfKafka;\nexport { KafkaError };\nexport type {\n KafkaOptions,\n PublishOptions,\n PublishBatchOptions,\n RecordMetadata,\n};\n"],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ import { ProduceResult } from "@platformatic/kafka";
2
+ import { LoggerInstanceManager } from "@autofleet/logger";
3
+
4
+ //#region src/kafkaError.d.ts
5
+ declare class KafkaError extends Error {
6
+ constructor(message: string);
7
+ }
8
+ //#endregion
9
+ //#region src/types.d.ts
10
+ interface KafkaOptions {
11
+ /** Kafka broker addresses */
12
+ brokers: string[];
13
+ /** Client ID for this producer */
14
+ clientId?: string;
15
+ /** Custom logger instance */
16
+ logger?: LoggerInstanceManager;
17
+ /**
18
+ * When you want your own graceful shutdown, set this to true.
19
+ * @default false
20
+ */
21
+ dontGracefulShutdown?: boolean;
22
+ }
23
+ interface PublishOptions {
24
+ /** Custom headers to attach to the message */
25
+ headers?: Record<string, string>;
26
+ /** Message key for partitioning */
27
+ key?: string;
28
+ /** Specific partition to send to */
29
+ partition?: number;
30
+ }
31
+ interface PublishBatchOptions {
32
+ /** Topic to publish to */
33
+ topic: string;
34
+ /** Messages to publish */
35
+ messages: {
36
+ value: unknown;
37
+ key?: string;
38
+ headers?: Record<string, string>;
39
+ partition?: number;
40
+ }[];
41
+ }
42
+ interface RecordMetadata {
43
+ topic: string;
44
+ partition: number;
45
+ offset: string;
46
+ }
47
+ //#endregion
48
+ //#region src/index.d.ts
49
+ interface IAfKafka {
50
+ ping(): Promise<void>;
51
+ publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<ProduceResult[]>;
52
+ publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]>;
53
+ disconnect(): Promise<void>;
54
+ }
55
+ declare class AfKafka implements IAfKafka {
56
+ private readonly producer;
57
+ private readonly logger;
58
+ private isDisconnected;
59
+ private isVerified;
60
+ private gracefulShutdownStarted;
61
+ constructor(options: KafkaOptions);
62
+ private setupGracefulShutdown;
63
+ private gracefulShutdown;
64
+ get isConnected(): boolean;
65
+ /**
66
+ * Ping Kafka brokers to verify connectivity
67
+ */
68
+ ping(): Promise<void>;
69
+ /**
70
+ * Publish a single message to a topic
71
+ */
72
+ publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<ProduceResult[]>;
73
+ /**
74
+ * Publish multiple messages in a batch
75
+ */
76
+ publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]>;
77
+ /**
78
+ * Disconnect the producer and clean up resources
79
+ */
80
+ disconnect(): Promise<void>;
81
+ }
82
+ //#endregion
83
+ export { IAfKafka, KafkaError, type KafkaOptions, type PublishBatchOptions, type PublishOptions, type RecordMetadata, AfKafka as default };
84
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1,84 @@
1
+ import { ProduceResult } from "@platformatic/kafka";
2
+ import { LoggerInstanceManager } from "@autofleet/logger";
3
+
4
+ //#region src/kafkaError.d.ts
5
+ declare class KafkaError extends Error {
6
+ constructor(message: string);
7
+ }
8
+ //#endregion
9
+ //#region src/types.d.ts
10
+ interface KafkaOptions {
11
+ /** Kafka broker addresses */
12
+ brokers: string[];
13
+ /** Client ID for this producer */
14
+ clientId?: string;
15
+ /** Custom logger instance */
16
+ logger?: LoggerInstanceManager;
17
+ /**
18
+ * When you want your own graceful shutdown, set this to true.
19
+ * @default false
20
+ */
21
+ dontGracefulShutdown?: boolean;
22
+ }
23
+ interface PublishOptions {
24
+ /** Custom headers to attach to the message */
25
+ headers?: Record<string, string>;
26
+ /** Message key for partitioning */
27
+ key?: string;
28
+ /** Specific partition to send to */
29
+ partition?: number;
30
+ }
31
+ interface PublishBatchOptions {
32
+ /** Topic to publish to */
33
+ topic: string;
34
+ /** Messages to publish */
35
+ messages: {
36
+ value: unknown;
37
+ key?: string;
38
+ headers?: Record<string, string>;
39
+ partition?: number;
40
+ }[];
41
+ }
42
+ interface RecordMetadata {
43
+ topic: string;
44
+ partition: number;
45
+ offset: string;
46
+ }
47
+ //#endregion
48
+ //#region src/index.d.ts
49
+ interface IAfKafka {
50
+ ping(): Promise<void>;
51
+ publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<ProduceResult[]>;
52
+ publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]>;
53
+ disconnect(): Promise<void>;
54
+ }
55
+ declare class AfKafka implements IAfKafka {
56
+ private readonly producer;
57
+ private readonly logger;
58
+ private isDisconnected;
59
+ private isVerified;
60
+ private gracefulShutdownStarted;
61
+ constructor(options: KafkaOptions);
62
+ private setupGracefulShutdown;
63
+ private gracefulShutdown;
64
+ get isConnected(): boolean;
65
+ /**
66
+ * Ping Kafka brokers to verify connectivity
67
+ */
68
+ ping(): Promise<void>;
69
+ /**
70
+ * Publish a single message to a topic
71
+ */
72
+ publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<ProduceResult[]>;
73
+ /**
74
+ * Publish multiple messages in a batch
75
+ */
76
+ publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]>;
77
+ /**
78
+ * Disconnect the producer and clean up resources
79
+ */
80
+ disconnect(): Promise<void>;
81
+ }
82
+ //#endregion
83
+ export { IAfKafka, KafkaError, type KafkaOptions, type PublishBatchOptions, type PublishOptions, type RecordMetadata, AfKafka as default };
84
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import{Producer as e,stringSerializers as t}from"@platformatic/kafka";var n={info:(...e)=>console.log(`[INFO]`,...e),error:(...e)=>console.error(`[ERROR]`,...e),warn:(...e)=>console.warn(`[WARN]`,...e),debug:(...e)=>console.debug(`[DEBUG]`,...e)},r=class extends Error{constructor(e){super(e),this.name=`KafkaError`}};const i=`x-timestamp`,a=`autofleet-kafka-producer`;var o=class{constructor(i){if(this.isDisconnected=!1,this.isVerified=!1,this.gracefulShutdownStarted=!1,!i.brokers||i.brokers.length===0)throw new r(`At least one broker is required`);this.logger=i.logger??n,this.producer=new e({bootstrapBrokers:i.brokers,clientId:i.clientId||a,serializers:t}),this.logger.info(`Kafka: Initializing Kafka producer`,{clientId:i.clientId||a,brokers:i.brokers}),i.dontGracefulShutdown||this.setupGracefulShutdown()}setupGracefulShutdown(){this.logger.info(`Kafka: [graceful-shutdown] adding graceful shutdown for process.pid ${process.pid}`),process.on(`SIGTERM`,async()=>{await this.gracefulShutdown(`SIGTERM`)}),process.on(`SIGINT`,async()=>{await this.gracefulShutdown(`SIGINT`)})}async gracefulShutdown(e){if(!this.gracefulShutdownStarted){this.gracefulShutdownStarted=!0,this.logger.info(`Kafka: [graceful-shutdown] received ${e}! Disconnecting producer...`);try{await this.disconnect(),this.logger.info(`Kafka: [graceful-shutdown] producer disconnected successfully`)}catch(e){throw this.logger.error(`Kafka: [graceful-shutdown] error during shutdown`,{error:e}),e}}}get isConnected(){return this.producer.isConnected()}async ping(){if(!this.isVerified)try{let e=await this.producer.metadata({topics:[]});this.isVerified=!0,this.logger.info(`Kafka: Successfully connected to cluster`,{clusterId:e.id,brokers:e.brokers.size})}catch(e){throw this.logger.error(`Kafka: Failed to connect to brokers`,{error:e}),new r(`Failed to connect to Kafka brokers: ${e.message}`)}}async publish(e,t,n){if(!e)throw new r(`Topic name is required`);await this.ping();try{let r={[i]:Date.now().toString(),...n?.headers},a=await this.producer.send({messages:[{topic:e,value:JSON.stringify(t),key:n?.key,partition:n?.partition,headers:r}]});return this.logger.debug(`Kafka: Published message to topic ${e}`,{topic:e}),[a]}catch(n){let i=n;throw this.logger.error(`Kafka: Error publishing to topic ${e}`,{error:n,value:t}),new r(`Failed to publish to topic ${e}: ${i.message||`Unknown error`}`)}}async publishBatch(e){if(!e.topic)throw new r(`Topic name is required`);if(!e.messages||e.messages.length===0)throw new r(`At least one message is required`);await this.ping();try{let t=e.messages.map(t=>({topic:e.topic,value:JSON.stringify(t.value),key:t.key,partition:t.partition,headers:{[i]:Date.now().toString(),...t.headers}})),n=await this.producer.send({messages:t});return this.logger.debug(`Kafka: Published ${t.length} messages to topic ${e.topic}`,{topic:e.topic,count:t.length}),[n]}catch(t){let n=t;throw this.logger.error(`Kafka: Error publishing batch to topic ${e.topic}`,{error:t}),new r(`Failed to publish batch to topic ${e.topic}: ${n.message||`Unknown error`}`)}}async disconnect(){if(this.isDisconnected){this.logger.debug(`Kafka: Producer already disconnected`);return}this.logger.info(`Kafka: Disconnecting producer`);try{await this.producer.close(),this.isDisconnected=!0,this.logger.info(`Kafka: Producer disconnected successfully`)}catch(e){throw this.logger.error(`Kafka: Error disconnecting producer`,{error:e}),new r(`Failed to disconnect producer: ${e.message}`)}}};export{r as KafkaError,o as default};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["fallbackLogger: LoggerInstanceManager","fallbackLogger","headers: Record<string, string>"],"sources":["../src/logger.ts","../src/kafkaError.ts","../src/consts.ts","../src/index.ts"],"sourcesContent":["import type { LoggerInstanceManager } from '@autofleet/logger';\n\n/* eslint-disable no-console */\nconst fallbackLogger: LoggerInstanceManager = {\n info: (...args: unknown[]) => console.log('[INFO]', ...args),\n error: (...args: unknown[]) => console.error('[ERROR]', ...args),\n warn: (...args: unknown[]) => console.warn('[WARN]', ...args),\n debug: (...args: unknown[]) => console.debug('[DEBUG]', ...args),\n} as LoggerInstanceManager;\n/* eslint-enable no-console */\n\nexport default fallbackLogger;\n","export default class KafkaError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'KafkaError';\n }\n}\n","export const TIMESTAMP_HEADER = 'x-timestamp';\nexport const CORRELATION_ID_HEADER = 'x-correlation-id';\nexport const SOURCE_HEADER = 'x-source';\n\nexport const DEFAULT_CLIENT_ID = 'autofleet-kafka-producer';\nexport const DEFAULT_TIMEOUT = 30000; // 30 seconds\nexport const DEFAULT_RETRY = {\n retries: 5,\n initialRetryTime: 300,\n maxRetryTime: 30000,\n};\n","import { Producer, type ProduceResult, stringSerializers } from '@platformatic/kafka';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport fallbackLogger from './logger';\nimport KafkaError from './kafkaError';\nimport {\n DEFAULT_CLIENT_ID,\n TIMESTAMP_HEADER,\n} from './consts';\nimport type {\n KafkaOptions,\n PublishOptions,\n PublishBatchOptions,\n RecordMetadata,\n} from './types';\n\nexport interface IAfKafka {\n ping(): Promise<void>;\n publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<ProduceResult[]>;\n publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]>;\n disconnect(): Promise<void>;\n}\n\nclass AfKafka implements IAfKafka {\n private readonly producer: Producer<string, string, string, string>;\n private readonly logger: LoggerInstanceManager;\n private isDisconnected = false;\n private isVerified = false;\n private gracefulShutdownStarted = false;\n\n constructor(options: KafkaOptions) {\n if (!options.brokers || options.brokers.length === 0) {\n throw new KafkaError('At least one broker is required');\n }\n\n this.logger = options.logger ?? fallbackLogger;\n\n this.producer = new Producer({\n bootstrapBrokers: options.brokers,\n clientId: options.clientId || DEFAULT_CLIENT_ID,\n serializers: stringSerializers,\n });\n\n this.logger.info('Kafka: Initializing Kafka producer', {\n clientId: options.clientId || DEFAULT_CLIENT_ID,\n brokers: options.brokers,\n });\n\n if (!options.dontGracefulShutdown) {\n this.setupGracefulShutdown();\n }\n }\n\n private setupGracefulShutdown(): void {\n this.logger.info(`Kafka: [graceful-shutdown] adding graceful shutdown for process.pid ${process.pid}`);\n\n process.on('SIGTERM', async () => {\n await this.gracefulShutdown('SIGTERM');\n });\n\n process.on('SIGINT', async () => {\n await this.gracefulShutdown('SIGINT');\n });\n }\n\n private async gracefulShutdown(signal: string): Promise<void> {\n if (this.gracefulShutdownStarted) {\n return;\n }\n\n this.gracefulShutdownStarted = true;\n\n this.logger.info(`Kafka: [graceful-shutdown] received ${signal}! Disconnecting producer...`);\n\n try {\n await this.disconnect();\n this.logger.info('Kafka: [graceful-shutdown] producer disconnected successfully');\n } catch (error) {\n this.logger.error('Kafka: [graceful-shutdown] error during shutdown', { error });\n throw error;\n }\n }\n\n get isConnected(): boolean {\n return this.producer.isConnected();\n }\n\n /**\n * Ping Kafka brokers to verify connectivity\n */\n async ping(): Promise<void> {\n if (this.isVerified) {\n return;\n }\n\n try {\n // Fetch metadata to verify connection\n const metadata = await this.producer.metadata({ topics: [] });\n this.isVerified = true;\n\n this.logger.info('Kafka: Successfully connected to cluster', {\n clusterId: metadata.id,\n brokers: metadata.brokers.size,\n });\n } catch (error) {\n this.logger.error('Kafka: Failed to connect to brokers', { error });\n throw new KafkaError(`Failed to connect to Kafka brokers: ${(error as Error).message}`);\n }\n }\n\n /**\n * Publish a single message to a topic\n */\n async publish<T = unknown>(\n topic: string,\n value: T,\n options?: PublishOptions,\n ): Promise<ProduceResult[]> {\n if (!topic) {\n throw new KafkaError('Topic name is required');\n }\n\n // Verify connection on first publish\n await this.ping();\n\n try {\n const headers: Record<string, string> = {\n [TIMESTAMP_HEADER]: Date.now().toString(),\n ...options?.headers,\n };\n\n const res = await this.producer.send({\n messages: [{\n topic,\n value: JSON.stringify(value),\n key: options?.key,\n partition: options?.partition,\n headers,\n }],\n });\n\n this.logger.debug(`Kafka: Published message to topic ${topic}`, {\n topic,\n });\n\n return [res];\n } catch (error) {\n const err = error as { message?: string; };\n this.logger.error(`Kafka: Error publishing to topic ${topic}`, { error, value });\n throw new KafkaError(`Failed to publish to topic ${topic}: ${err.message || 'Unknown error'}`);\n }\n }\n\n /**\n * Publish multiple messages in a batch\n */\n async publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]> {\n if (!options.topic) {\n throw new KafkaError('Topic name is required');\n }\n\n if (!options.messages || options.messages.length === 0) {\n throw new KafkaError('At least one message is required');\n }\n\n // Verify connection on first publish\n await this.ping();\n\n try {\n const messages = options.messages.map(msg => ({\n topic: options.topic,\n value: JSON.stringify(msg.value),\n key: msg.key,\n partition: msg.partition,\n headers: {\n [TIMESTAMP_HEADER]: Date.now().toString(),\n ...msg.headers,\n },\n }));\n\n const res = await this.producer.send({ messages });\n\n this.logger.debug(`Kafka: Published ${messages.length} messages to topic ${options.topic}`, {\n topic: options.topic,\n count: messages.length,\n });\n\n // Return mock metadata since @platformatic/kafka send() returns void\n return [res];\n } catch (error) {\n const err = error as { message?: string; };\n this.logger.error(`Kafka: Error publishing batch to topic ${options.topic}`, { error });\n throw new KafkaError(`Failed to publish batch to topic ${options.topic}: ${err.message || 'Unknown error'}`);\n }\n }\n\n /**\n * Disconnect the producer and clean up resources\n */\n async disconnect(): Promise<void> {\n if (this.isDisconnected) {\n this.logger.debug('Kafka: Producer already disconnected');\n return;\n }\n\n this.logger.info('Kafka: Disconnecting producer');\n\n try {\n await this.producer.close();\n this.isDisconnected = true;\n this.logger.info('Kafka: Producer disconnected successfully');\n } catch (error) {\n this.logger.error('Kafka: Error disconnecting producer', { error });\n throw new KafkaError(`Failed to disconnect producer: ${(error as Error).message}`);\n }\n }\n}\n\nexport default AfKafka;\nexport { KafkaError };\nexport type {\n KafkaOptions,\n PublishOptions,\n PublishBatchOptions,\n RecordMetadata,\n};\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@autofleet/kafka",
3
+ "version": "0.0.1",
4
+ "description": "Internal wrapper for Apache Kafka producer",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.cts",
16
+ "default": "./dist/index.cjs"
17
+ }
18
+ }
19
+ },
20
+ "files": [
21
+ "dist/"
22
+ ],
23
+ "engines": {
24
+ "node": ">=18.0.0"
25
+ },
26
+ "scripts": {
27
+ "build": "tsdown",
28
+ "test": "vitest",
29
+ "coverage": "vitest --coverage"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/Autofleet/autorepo.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/Autofleet/autorepo/issues"
37
+ },
38
+ "homepage": "https://github.com/Autofleet/autorepo/tree/master/packages/kafka#readme",
39
+ "dependencies": {
40
+ "@platformatic/kafka": "^1.21.0"
41
+ },
42
+ "peerDependencies": {
43
+ "@autofleet/logger": "*"
44
+ },
45
+ "devDependencies": {
46
+ "@autofleet/logger": "workspace:^",
47
+ "@types/node": "^20.14.11"
48
+ },
49
+ "keywords": ["kafka", "producer", "messaging"],
50
+ "author": "",
51
+ "license": "Proprietary",
52
+ "packageManager": "pnpm@10.24.0"
53
+ }