@autofleet/kafka 0.0.2 → 0.0.4

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 CHANGED
@@ -35,6 +35,7 @@ pnpm add @autofleet/kafka
35
35
  - **Custom Headers** - Attach metadata to messages
36
36
  - **Graceful Shutdown** - Automatic cleanup on process termination
37
37
  - **Type Safety** - Full TypeScript support
38
+ - **ESM & CommonJS Compatible** - Works in both ESM and CommonJS projects via dynamic imports
38
39
  - **Production Ready** - Built with reliability and performance in mind
39
40
 
40
41
  ## Quick Start
@@ -42,8 +43,8 @@ pnpm add @autofleet/kafka
42
43
  ```typescript
43
44
  import AfKafka from '@autofleet/kafka';
44
45
 
45
- // Initialize the producer
46
- const kafka = new AfKafka({
46
+ // Initialize the producer (async factory)
47
+ const kafka = await AfKafka.create({
47
48
  brokers: ['localhost:9092'],
48
49
  clientId: 'my-service',
49
50
  });
@@ -75,7 +76,7 @@ await kafka.disconnect();
75
76
  #### Basic Publishing
76
77
 
77
78
  ```typescript
78
- const kafka = new AfKafka({
79
+ const kafka = await AfKafka.create({
79
80
  brokers: ['localhost:9092', 'localhost:9093'],
80
81
  clientId: 'user-service',
81
82
  });
@@ -191,7 +192,7 @@ interface KafkaOptions {
191
192
  }
192
193
  ```
193
194
 
194
- The package uses [@platformatic/kafka](https://www.npmjs.com/package/@platformatic/kafka) with `jsonSerializer` for automatic JSON serialization of message values.
195
+ The package uses [@platformatic/kafka](https://www.npmjs.com/package/@platformatic/kafka) with `stringSerializers` for automatic serialization. The ESM-only `@platformatic/kafka` library is loaded via dynamic imports, ensuring compatibility with both ESM and CommonJS environments.
195
196
 
196
197
  ## Advanced Features
197
198
 
@@ -260,16 +261,18 @@ await kafka.publish('events', data, {
260
261
 
261
262
  ## API Reference
262
263
 
263
- ### `new AfKafka(options)`
264
+ ### `AfKafka.create(options): Promise<AfKafka>`
264
265
 
265
- Creates a new Kafka producer instance.
266
+ Creates a new Kafka producer instance using an async factory pattern. This method dynamically imports the ESM-only `@platformatic/kafka` library, making it compatible with both ESM and CommonJS environments.
266
267
 
267
268
  **Parameters:**
268
269
  - `options` - Configuration options (see [Configuration](#configuration))
269
270
 
271
+ **Returns:** A Promise that resolves to an `AfKafka` instance
272
+
270
273
  **Example:**
271
274
  ```typescript
272
- const kafka = new AfKafka({
275
+ const kafka = await AfKafka.create({
273
276
  brokers: ['localhost:9092'],
274
277
  clientId: 'my-service',
275
278
  });
@@ -409,7 +412,7 @@ try {
409
412
  ### 6. Use Multiple Brokers for High Availability
410
413
 
411
414
  ```typescript
412
- const kafka = new AfKafka({
415
+ const kafka = await AfKafka.create({
413
416
  brokers: ['kafka1:9092', 'kafka2:9092', 'kafka3:9092'],
414
417
  clientId: 'my-service',
415
418
  });
@@ -452,8 +455,8 @@ import AfKafka from '@autofleet/kafka';
452
455
  describe('MyKafkaService', () => {
453
456
  let kafka: AfKafka;
454
457
 
455
- beforeEach(() => {
456
- kafka = new AfKafka({
458
+ beforeEach(async () => {
459
+ kafka = await AfKafka.create({
457
460
  brokers: ['localhost:9092'],
458
461
  clientId: 'test-client',
459
462
  });
@@ -489,7 +492,7 @@ KAFKA_SASL_PASSWORD=pass
489
492
  Usage:
490
493
 
491
494
  ```typescript
492
- const kafka = new AfKafka({
495
+ const kafka = await AfKafka.create({
493
496
  brokers: process.env.KAFKA_BROKERS?.split(',') || ['localhost:9092'],
494
497
  clientId: process.env.KAFKA_CLIENT_ID || 'default-client',
495
498
  });
@@ -552,7 +555,7 @@ try {
552
555
 
553
556
  ```typescript
554
557
  // Use multiple brokers for redundancy
555
- const kafka = new AfKafka({
558
+ const kafka = await AfKafka.create({
556
559
  brokers: ['kafka1:9092', 'kafka2:9092', 'kafka3:9092'],
557
560
  clientId: 'my-service',
558
561
  });
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,`__esModule`,{value:!0});const e={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 t=e,n=class extends Error{constructor(e){super(e),this.name=`KafkaError`}};const r=`x-timestamp`,i=`autofleet-kafka-producer`;var a=class e{constructor(e,n){this._isConnected=!1,this.gracefulShutdownStarted=!1,this.logger=e.logger??t,this.producer=n,this.logger.info(`Kafka: Initialized Kafka producer`,{clientId:e.clientId||i,brokers:e.brokers}),e.dontGracefulShutdown||this.setupGracefulShutdown()}static async create(t){if(!t.brokers||t.brokers.length===0)throw new n(`At least one broker is required`);let{Producer:r,stringSerializers:a}=await import(`@platformatic/kafka`);return new e(t,new r({bootstrapBrokers:t.brokers,clientId:t.clientId||i,serializers:a}))}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._isConnected}async ping(){if(!this._isConnected)try{let e=await this.producer.metadata({topics:[]});this._isConnected=!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 n(`Failed to connect to Kafka brokers: ${e.message}`)}}async publish(e,t,i){if(!e)throw new n(`Topic name is required`);await this.ping();try{let n={[r]:Date.now().toString(),...i?.headers},a=await this.producer.send({messages:[{topic:e,value:JSON.stringify(t),key:i?.key,partition:i?.partition,headers:n}]});return this.logger.debug(`Kafka: Published message to topic ${e}`,{topic:e}),[{topic:e,partition:i?.partition??0,offset:a?.offsets?.[0]?.offset?.toString()??`0`}]}catch(r){let i=r;throw this.logger.error(`Kafka: Error publishing to topic ${e}`,{error:r,value:t}),new n(`Failed to publish to topic ${e}: ${i.message||`Unknown error`}`)}}async publishBatch(e){if(!e.topic)throw new n(`Topic name is required`);if(!e.messages||e.messages.length===0)throw new n(`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:{[r]: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}),t.map((t,r)=>({topic:e.topic,partition:t.partition??0,offset:n?.offsets?.[r]?.offset?.toString()??r.toString()}))}catch(t){let r=t;throw this.logger.error(`Kafka: Error publishing batch to topic ${e.topic}`,{error:t}),new n(`Failed to publish batch to topic ${e.topic}: ${r.message||`Unknown error`}`)}}async disconnect(){if(!this._isConnected){this.logger.debug(`Kafka: Producer already disconnected`);return}this.logger.info(`Kafka: Disconnecting producer`);try{await this.producer.close(),this._isConnected=!1,this.logger.info(`Kafka: Producer disconnected successfully`)}catch(e){throw this.logger.error(`Kafka: Error disconnecting producer`,{error:e}),new n(`Failed to disconnect producer: ${e.message}`)}}},o=a;exports.KafkaError=n,exports.default=o;
1
+ Object.defineProperty(exports,`__esModule`,{value:!0});const e={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 t=e,n=class extends Error{constructor(e){super(e),this.name=`KafkaError`}};const r=`x-timestamp`,i=`autofleet-kafka-producer`;var a=class e{constructor(e,n){this._isConnected=!1,this.gracefulShutdownStarted=!1,this.logger=e.logger??t,this.producer=n,this.logger.info(`Kafka: Initialized Kafka producer`,{clientId:e.clientId||i,brokers:e.brokers}),e.dontGracefulShutdown||this.setupGracefulShutdown()}static async create(t){if(!t.brokers||t.brokers.length===0)throw new n(`At least one broker is required`);let{Producer:r,stringSerializers:a}=await import(`@platformatic/kafka`);return new e(t,new r({bootstrapBrokers:t.brokers,clientId:t.clientId||i,serializers:a,autocreateTopics:!1,sasl:t.sasl}))}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._isConnected}async ping(){if(!this._isConnected)try{let e=await this.producer.metadata({topics:[]});this._isConnected=!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 n(`Failed to connect to Kafka brokers: ${e.message}`)}}async publish(e,t,i){if(!e)throw new n(`Topic name is required`);await this.ping();try{let n={[r]:Date.now().toString(),...i?.headers},a=await this.producer.send({messages:[{topic:e,value:JSON.stringify(t),key:i?.key,partition:i?.partition,headers:n}]});return this.logger.debug(`Kafka: Published message to topic ${e}`,{topic:e}),[{topic:e,partition:i?.partition??0,offset:a?.offsets?.[0]?.offset?.toString()??`0`}]}catch(r){let i=r;throw this.logger.error(`Kafka: Error publishing to topic ${e}`,{error:r,value:t}),new n(`Failed to publish to topic ${e}: ${i.message||`Unknown error`}`)}}async publishBatch(e){if(!e.topic)throw new n(`Topic name is required`);if(!e.messages||e.messages.length===0)throw new n(`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:{[r]: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}),t.map((t,r)=>({topic:e.topic,partition:t.partition??0,offset:n?.offsets?.[r]?.offset?.toString()??r.toString()}))}catch(t){let r=t;throw this.logger.error(`Kafka: Error publishing batch to topic ${e.topic}`,{error:t}),new n(`Failed to publish batch to topic ${e.topic}: ${r.message||`Unknown error`}`)}}async disconnect(){if(!this._isConnected){this.logger.debug(`Kafka: Producer already disconnected`);return}this.logger.info(`Kafka: Disconnecting producer`);try{await this.producer.close(),this._isConnected=!1,this.logger.info(`Kafka: Producer disconnected successfully`)}catch(e){throw this.logger.error(`Kafka: Error disconnecting producer`,{error:e}),new n(`Failed to disconnect producer: ${e.message}`)}}},o=a;exports.KafkaError=n,exports.default=o;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["fallbackLogger: LoggerInstanceManager","fallbackLogger","headers: Record<string, string>","result: ProduceResult"],"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 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\ntype Producer = any; // Will be typed at runtime from dynamic import\ntype ProduceResult = any;\n\nexport interface IAfKafka {\n isConnected: boolean;\n ping(): Promise<void>;\n publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<RecordMetadata[]>;\n publishBatch(options: PublishBatchOptions): Promise<RecordMetadata[]>;\n disconnect(): Promise<void>;\n}\n\nclass AfKafka implements IAfKafka {\n private producer!: Producer;\n private readonly logger: LoggerInstanceManager;\n private _isConnected = false;\n private gracefulShutdownStarted = false;\n\n private constructor(options: KafkaOptions, producer: Producer) {\n this.logger = options.logger ?? fallbackLogger;\n this.producer = producer;\n\n this.logger.info('Kafka: Initialized 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 /**\n * Create a new Kafka producer instance\n */\n static async create(options: KafkaOptions): Promise<AfKafka> {\n if (!options.brokers || options.brokers.length === 0) {\n throw new KafkaError('At least one broker is required');\n }\n\n // Dynamic import of ESM-only package\n const { Producer, stringSerializers } = await import('@platformatic/kafka');\n\n const producer = new Producer({\n bootstrapBrokers: options.brokers,\n clientId: options.clientId || DEFAULT_CLIENT_ID,\n serializers: stringSerializers,\n });\n\n return new AfKafka(options, producer);\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._isConnected;\n }\n\n /**\n * Ping Kafka brokers to verify connectivity\n */\n async ping(): Promise<void> {\n if (this._isConnected) {\n return;\n }\n\n try {\n // Fetch metadata to verify connection\n const metadata = await this.producer.metadata({ topics: [] });\n this._isConnected = 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<RecordMetadata[]> {\n if (!topic) {\n throw new KafkaError('Topic name is required');\n }\n\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 result: ProduceResult = 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 // Map ProduceResult to RecordMetadata\n return [{\n topic,\n partition: options?.partition ?? 0,\n offset: result?.offsets?.[0]?.offset?.toString() ?? '0',\n }];\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<RecordMetadata[]> {\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 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 result: ProduceResult = 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 // Map ProduceResult to RecordMetadata array\n return messages.map((msg, idx) => ({\n topic: options.topic,\n partition: msg.partition ?? 0,\n offset: result?.offsets?.[idx]?.offset?.toString() ?? idx.toString(),\n }));\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._isConnected) {\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._isConnected = false;\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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"}
1
+ {"version":3,"file":"index.cjs","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 type { LoggerInstanceManager } from '@autofleet/logger';\nimport type { Producer } from '@platformatic/kafka/dist/clients/producer/index.ts';\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 isConnected: boolean;\n ping(): Promise<void>;\n publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<RecordMetadata[]>;\n publishBatch(options: PublishBatchOptions): Promise<RecordMetadata[]>;\n disconnect(): Promise<void>;\n}\n\nclass AfKafka implements IAfKafka {\n private producer!: Producer<string, string, string, string>;\n private readonly logger: LoggerInstanceManager;\n private _isConnected = false;\n private gracefulShutdownStarted = false;\n\n private constructor(options: KafkaOptions, producer: Producer<string, string, string, string>) {\n this.logger = options.logger ?? fallbackLogger;\n this.producer = producer;\n\n this.logger.info('Kafka: Initialized 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 /**\n * Create a new Kafka producer instance\n */\n static async create(options: KafkaOptions): Promise<AfKafka> {\n if (!options.brokers || options.brokers.length === 0) {\n throw new KafkaError('At least one broker is required');\n }\n\n // Dynamic import of ESM-only package\n const { Producer, stringSerializers } = await import('@platformatic/kafka');\n\n const producer = new Producer({\n bootstrapBrokers: options.brokers,\n clientId: options.clientId || DEFAULT_CLIENT_ID,\n serializers: stringSerializers,\n autocreateTopics: false,\n sasl: options.sasl,\n });\n\n return new AfKafka(options, producer);\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._isConnected;\n }\n\n /**\n * Ping Kafka brokers to verify connectivity\n */\n async ping(): Promise<void> {\n if (this._isConnected) {\n return;\n }\n\n try {\n // Fetch metadata to verify connection\n const metadata = await this.producer.metadata({ topics: [] });\n this._isConnected = 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<RecordMetadata[]> {\n if (!topic) {\n throw new KafkaError('Topic name is required');\n }\n\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 result = 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 // Map ProduceResult to RecordMetadata\n return [{\n topic,\n partition: options?.partition ?? 0,\n offset: result?.offsets?.[0]?.offset?.toString() ?? '0',\n }];\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<RecordMetadata[]> {\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 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 result = 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 // Map ProduceResult to RecordMetadata array\n return messages.map((msg, idx) => ({\n topic: options.topic,\n partition: msg.partition ?? 0,\n offset: result?.offsets?.[idx]?.offset?.toString() ?? idx.toString(),\n }));\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._isConnected) {\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._isConnected = false;\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/dist/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { LoggerInstanceManager } from "@autofleet/logger";
2
+ import { ConnectionOptions } from "@platformatic/kafka";
2
3
 
3
4
  //#region src/kafkaError.d.ts
4
5
  declare class KafkaError extends Error {
@@ -18,6 +19,8 @@ interface KafkaOptions {
18
19
  * @default false
19
20
  */
20
21
  dontGracefulShutdown?: boolean;
22
+ /** SASL authentication options */
23
+ sasl?: ConnectionOptions["sasl"];
21
24
  }
22
25
  interface PublishOptions {
23
26
  /** Custom headers to attach to the message */
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ConnectionOptions } from "@platformatic/kafka";
1
2
  import { LoggerInstanceManager } from "@autofleet/logger";
2
3
 
3
4
  //#region src/kafkaError.d.ts
@@ -18,6 +19,8 @@ interface KafkaOptions {
18
19
  * @default false
19
20
  */
20
21
  dontGracefulShutdown?: boolean;
22
+ /** SASL authentication options */
23
+ sasl?: ConnectionOptions["sasl"];
21
24
  }
22
25
  interface PublishOptions {
23
26
  /** Custom headers to attach to the message */
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var e={info:(...e)=>console.log(`[INFO]`,...e),error:(...e)=>console.error(`[ERROR]`,...e),warn:(...e)=>console.warn(`[WARN]`,...e),debug:(...e)=>console.debug(`[DEBUG]`,...e)},t=class extends Error{constructor(e){super(e),this.name=`KafkaError`}};const n=`x-timestamp`,r=`autofleet-kafka-producer`;var i=class i{constructor(t,n){this._isConnected=!1,this.gracefulShutdownStarted=!1,this.logger=t.logger??e,this.producer=n,this.logger.info(`Kafka: Initialized Kafka producer`,{clientId:t.clientId||r,brokers:t.brokers}),t.dontGracefulShutdown||this.setupGracefulShutdown()}static async create(e){if(!e.brokers||e.brokers.length===0)throw new t(`At least one broker is required`);let{Producer:n,stringSerializers:a}=await import(`@platformatic/kafka`);return new i(e,new n({bootstrapBrokers:e.brokers,clientId:e.clientId||r,serializers:a}))}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._isConnected}async ping(){if(!this._isConnected)try{let e=await this.producer.metadata({topics:[]});this._isConnected=!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 t(`Failed to connect to Kafka brokers: ${e.message}`)}}async publish(e,r,i){if(!e)throw new t(`Topic name is required`);await this.ping();try{let t={[n]:Date.now().toString(),...i?.headers},a=await this.producer.send({messages:[{topic:e,value:JSON.stringify(r),key:i?.key,partition:i?.partition,headers:t}]});return this.logger.debug(`Kafka: Published message to topic ${e}`,{topic:e}),[{topic:e,partition:i?.partition??0,offset:a?.offsets?.[0]?.offset?.toString()??`0`}]}catch(n){let i=n;throw this.logger.error(`Kafka: Error publishing to topic ${e}`,{error:n,value:r}),new t(`Failed to publish to topic ${e}: ${i.message||`Unknown error`}`)}}async publishBatch(e){if(!e.topic)throw new t(`Topic name is required`);if(!e.messages||e.messages.length===0)throw new t(`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:{[n]:Date.now().toString(),...t.headers}})),r=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}),t.map((t,n)=>({topic:e.topic,partition:t.partition??0,offset:r?.offsets?.[n]?.offset?.toString()??n.toString()}))}catch(n){let r=n;throw this.logger.error(`Kafka: Error publishing batch to topic ${e.topic}`,{error:n}),new t(`Failed to publish batch to topic ${e.topic}: ${r.message||`Unknown error`}`)}}async disconnect(){if(!this._isConnected){this.logger.debug(`Kafka: Producer already disconnected`);return}this.logger.info(`Kafka: Disconnecting producer`);try{await this.producer.close(),this._isConnected=!1,this.logger.info(`Kafka: Producer disconnected successfully`)}catch(e){throw this.logger.error(`Kafka: Error disconnecting producer`,{error:e}),new t(`Failed to disconnect producer: ${e.message}`)}}};export{t as KafkaError,i as default};
1
+ var e={info:(...e)=>console.log(`[INFO]`,...e),error:(...e)=>console.error(`[ERROR]`,...e),warn:(...e)=>console.warn(`[WARN]`,...e),debug:(...e)=>console.debug(`[DEBUG]`,...e)},t=class extends Error{constructor(e){super(e),this.name=`KafkaError`}};const n=`x-timestamp`,r=`autofleet-kafka-producer`;var i=class i{constructor(t,n){this._isConnected=!1,this.gracefulShutdownStarted=!1,this.logger=t.logger??e,this.producer=n,this.logger.info(`Kafka: Initialized Kafka producer`,{clientId:t.clientId||r,brokers:t.brokers}),t.dontGracefulShutdown||this.setupGracefulShutdown()}static async create(e){if(!e.brokers||e.brokers.length===0)throw new t(`At least one broker is required`);let{Producer:n,stringSerializers:a}=await import(`@platformatic/kafka`);return new i(e,new n({bootstrapBrokers:e.brokers,clientId:e.clientId||r,serializers:a,autocreateTopics:!1,sasl:e.sasl}))}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._isConnected}async ping(){if(!this._isConnected)try{let e=await this.producer.metadata({topics:[]});this._isConnected=!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 t(`Failed to connect to Kafka brokers: ${e.message}`)}}async publish(e,r,i){if(!e)throw new t(`Topic name is required`);await this.ping();try{let t={[n]:Date.now().toString(),...i?.headers},a=await this.producer.send({messages:[{topic:e,value:JSON.stringify(r),key:i?.key,partition:i?.partition,headers:t}]});return this.logger.debug(`Kafka: Published message to topic ${e}`,{topic:e}),[{topic:e,partition:i?.partition??0,offset:a?.offsets?.[0]?.offset?.toString()??`0`}]}catch(n){let i=n;throw this.logger.error(`Kafka: Error publishing to topic ${e}`,{error:n,value:r}),new t(`Failed to publish to topic ${e}: ${i.message||`Unknown error`}`)}}async publishBatch(e){if(!e.topic)throw new t(`Topic name is required`);if(!e.messages||e.messages.length===0)throw new t(`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:{[n]:Date.now().toString(),...t.headers}})),r=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}),t.map((t,n)=>({topic:e.topic,partition:t.partition??0,offset:r?.offsets?.[n]?.offset?.toString()??n.toString()}))}catch(n){let r=n;throw this.logger.error(`Kafka: Error publishing batch to topic ${e.topic}`,{error:n}),new t(`Failed to publish batch to topic ${e.topic}: ${r.message||`Unknown error`}`)}}async disconnect(){if(!this._isConnected){this.logger.debug(`Kafka: Producer already disconnected`);return}this.logger.info(`Kafka: Disconnecting producer`);try{await this.producer.close(),this._isConnected=!1,this.logger.info(`Kafka: Producer disconnected successfully`)}catch(e){throw this.logger.error(`Kafka: Error disconnecting producer`,{error:e}),new t(`Failed to disconnect producer: ${e.message}`)}}};export{t as KafkaError,i as default};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["fallbackLogger: LoggerInstanceManager","fallbackLogger","headers: Record<string, string>","result: ProduceResult"],"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 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\ntype Producer = any; // Will be typed at runtime from dynamic import\ntype ProduceResult = any;\n\nexport interface IAfKafka {\n isConnected: boolean;\n ping(): Promise<void>;\n publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<RecordMetadata[]>;\n publishBatch(options: PublishBatchOptions): Promise<RecordMetadata[]>;\n disconnect(): Promise<void>;\n}\n\nclass AfKafka implements IAfKafka {\n private producer!: Producer;\n private readonly logger: LoggerInstanceManager;\n private _isConnected = false;\n private gracefulShutdownStarted = false;\n\n private constructor(options: KafkaOptions, producer: Producer) {\n this.logger = options.logger ?? fallbackLogger;\n this.producer = producer;\n\n this.logger.info('Kafka: Initialized 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 /**\n * Create a new Kafka producer instance\n */\n static async create(options: KafkaOptions): Promise<AfKafka> {\n if (!options.brokers || options.brokers.length === 0) {\n throw new KafkaError('At least one broker is required');\n }\n\n // Dynamic import of ESM-only package\n const { Producer, stringSerializers } = await import('@platformatic/kafka');\n\n const producer = new Producer({\n bootstrapBrokers: options.brokers,\n clientId: options.clientId || DEFAULT_CLIENT_ID,\n serializers: stringSerializers,\n });\n\n return new AfKafka(options, producer);\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._isConnected;\n }\n\n /**\n * Ping Kafka brokers to verify connectivity\n */\n async ping(): Promise<void> {\n if (this._isConnected) {\n return;\n }\n\n try {\n // Fetch metadata to verify connection\n const metadata = await this.producer.metadata({ topics: [] });\n this._isConnected = 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<RecordMetadata[]> {\n if (!topic) {\n throw new KafkaError('Topic name is required');\n }\n\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 result: ProduceResult = 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 // Map ProduceResult to RecordMetadata\n return [{\n topic,\n partition: options?.partition ?? 0,\n offset: result?.offsets?.[0]?.offset?.toString() ?? '0',\n }];\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<RecordMetadata[]> {\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 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 result: ProduceResult = 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 // Map ProduceResult to RecordMetadata array\n return messages.map((msg, idx) => ({\n topic: options.topic,\n partition: msg.partition ?? 0,\n offset: result?.offsets?.[idx]?.offset?.toString() ?? idx.toString(),\n }));\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._isConnected) {\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._isConnected = false;\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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"}
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 type { LoggerInstanceManager } from '@autofleet/logger';\nimport type { Producer } from '@platformatic/kafka/dist/clients/producer/index.ts';\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 isConnected: boolean;\n ping(): Promise<void>;\n publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<RecordMetadata[]>;\n publishBatch(options: PublishBatchOptions): Promise<RecordMetadata[]>;\n disconnect(): Promise<void>;\n}\n\nclass AfKafka implements IAfKafka {\n private producer!: Producer<string, string, string, string>;\n private readonly logger: LoggerInstanceManager;\n private _isConnected = false;\n private gracefulShutdownStarted = false;\n\n private constructor(options: KafkaOptions, producer: Producer<string, string, string, string>) {\n this.logger = options.logger ?? fallbackLogger;\n this.producer = producer;\n\n this.logger.info('Kafka: Initialized 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 /**\n * Create a new Kafka producer instance\n */\n static async create(options: KafkaOptions): Promise<AfKafka> {\n if (!options.brokers || options.brokers.length === 0) {\n throw new KafkaError('At least one broker is required');\n }\n\n // Dynamic import of ESM-only package\n const { Producer, stringSerializers } = await import('@platformatic/kafka');\n\n const producer = new Producer({\n bootstrapBrokers: options.brokers,\n clientId: options.clientId || DEFAULT_CLIENT_ID,\n serializers: stringSerializers,\n autocreateTopics: false,\n sasl: options.sasl,\n });\n\n return new AfKafka(options, producer);\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._isConnected;\n }\n\n /**\n * Ping Kafka brokers to verify connectivity\n */\n async ping(): Promise<void> {\n if (this._isConnected) {\n return;\n }\n\n try {\n // Fetch metadata to verify connection\n const metadata = await this.producer.metadata({ topics: [] });\n this._isConnected = 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<RecordMetadata[]> {\n if (!topic) {\n throw new KafkaError('Topic name is required');\n }\n\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 result = 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 // Map ProduceResult to RecordMetadata\n return [{\n topic,\n partition: options?.partition ?? 0,\n offset: result?.offsets?.[0]?.offset?.toString() ?? '0',\n }];\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<RecordMetadata[]> {\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 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 result = 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 // Map ProduceResult to RecordMetadata array\n return messages.map((msg, idx) => ({\n topic: options.topic,\n partition: msg.partition ?? 0,\n offset: result?.offsets?.[idx]?.offset?.toString() ?? idx.toString(),\n }));\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._isConnected) {\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._isConnected = false;\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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/kafka",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Internal wrapper for Apache Kafka producer",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",