@autofleet/kafka 0.1.1 → 0.3.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 +642 -79
- package/dist/index.cjs +3 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +179 -7
- package/dist/index.d.ts +179 -7
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
let e=require(`node:timers/promises`);var t=class extends Error{constructor(...e){super(...e),this.name=`KafkaError`}};const n=`x-timestamp`,r=`autofleet-kafka-producer`;var i=class{constructor(e,t,n){this.name=e,this.config=t,this.logger=n,this.producer=null,this._isConnected=!1,this.initPromise=null,this._lastPingAt=null,this._lastPingSucceededAt=null,this._lastError=null,this._clusterId=null,this._brokerCount=0}async#e(){let{Producer:e,stringSerializers:t}=await import(`@platformatic/kafka`);this.producer=new e({bootstrapBrokers:this.config.brokers,clientId:this.config.clientId||`${r}-${this.name}`,serializers:t,autocreateTopics:this.config.autoCreateTopics??!1,sasl:this.config.sasl}),this.logger.info(`Kafka: [${this.name}] Initialized producer`,{clientId:this.config.clientId||`${r}-${this.name}`,brokers:this.config.brokers}),this.initPromise=null}async initialize(){this.producer||(this.initPromise??=this.#e(),await this.initPromise)}get isConnected(){return this._isConnected}getHealth(){return{name:this.name,enabled:!0,isConnected:this._isConnected,lastPingAt:this._lastPingAt,lastPingSucceededAt:this._lastPingSucceededAt,lastError:this._lastError,clusterId:this._clusterId,brokerCount:this._brokerCount,brokers:this.config.brokers}}buildConnectionErrorMessage(e){let t=this.config.brokers.join(`, `),n=this.config.brokers[0],[i,a]=n?n.split(`:`):[``,``];return`[${this.name}] Failed to connect to Kafka brokers: ${e.message}\n\nPossible causes:\n 1. Brokers are unreachable: ${t}\n 2. Network connectivity issues\n 3. Firewall blocking ports\n 4. ${this.config.sasl?`SASL authentication failed`:`Authentication not configured but required`}\n\nTroubleshooting:\n - Verify brokers are running and accessible\n`+(i&&a?` - Test connectivity: nc -zv ${i} ${a}\n`:``)+` - Check network policies and firewall rules\n ${this.config.sasl?`- Verify SASL credentials are correct
|
|
2
|
+
`:``}\nCurrent configuration:\n Brokers: ${t}\n Client ID: ${this.config.clientId||`${r}-${this.name}`}\n SASL: ${this.config.sasl?`enabled (${this.config.sasl.mechanism})`:`disabled`}\n Auto-create topics: ${this.config.autoCreateTopics??!1}`}async ping(n){if(this._lastPingAt=Date.now(),!(this._isConnected&&!n?.force)){await this.initialize();try{let t=n?.timeout??5e3,r=Error(`Connection timeout after ${t}ms`),i=(0,e.setTimeout)(t).then(()=>{throw r}),a=await Promise.race([this.producer.metadata({topics:[]}),i]);this._isConnected=!0,this._lastPingSucceededAt=Date.now(),this._clusterId=a.id,this._brokerCount=a.brokers.size,this._lastError=null,this.logger.info(`Kafka: [${this.name}] Successfully connected to cluster`,{clusterId:a.id,brokers:a.brokers.size,duration:Date.now()-this._lastPingAt})}catch(e){let n=e;throw this._isConnected=!1,this._lastError={message:n.message,timestamp:Date.now(),stack:n.stack},this.logger.error(`Kafka: [${this.name}] Failed to connect to brokers`,{error:n.message,duration:Date.now()-this._lastPingAt}),new t(this.buildConnectionErrorMessage(n),{cause:n})}}}async publish(e,r,i){if(!e)throw new t(`[${this.name}] 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: [${this.name}] 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: [${this.name}] Error publishing to topic ${e}`,{error:n,value:r}),new t(`[${this.name}] Failed to publish to topic ${e}: ${i.message||`Unknown error`}`,{cause:n})}}async publishBatch(e){if(!e.topic)throw new t(`[${this.name}] Topic name is required`);if(!e.messages||e.messages.length===0)throw new t(`[${this.name}] 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: [${this.name}] 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: [${this.name}] Error publishing batch to topic ${e.topic}`,{error:n}),new t(`[${this.name}] Failed to publish batch to topic ${e.topic}: ${r.message||`Unknown error`}`,{cause:n})}}async disconnect(){if(!this.producer||!this._isConnected){this.logger.debug(`Kafka: [${this.name}] Producer already disconnected`);return}this.logger.info(`Kafka: [${this.name}] Disconnecting producer`);try{await this.producer.close(),this._isConnected=!1,this.logger.info(`Kafka: [${this.name}] Producer disconnected successfully`)}catch(e){throw this.logger.error(`Kafka: [${this.name}] Error disconnecting producer`,{error:e}),new t(`[${this.name}] Failed to disconnect producer: ${e.message}`,{cause:e})}}},a=class{constructor(e,t,n){this.name=e,this.brokers=t,this.logger=n,this.isConnected=!0}getHealth(){return{name:this.name,enabled:!1,isConnected:!0,lastPingAt:null,lastPingSucceededAt:null,lastError:null,clusterId:`mock-cluster`,brokerCount:0,brokers:this.brokers}}async ping(){return this.logger.debug(`Kafka: [${this.name}] Mock mode - skipping ping`),Promise.resolve()}async publish(e,t,n){return this.logger.debug(`Kafka: [${this.name}] Mock mode - skipping publish to topic: ${e}`,{value:t}),Promise.resolve([{topic:e,partition:0,offset:`0`}])}async publishBatch(e){return this.logger.debug(`Kafka: [${this.name}] Mock mode - skipping batch publish to topic: ${e.topic}`,{messageCount:e.messages.length}),Promise.resolve(e.messages.map(()=>({topic:e.topic,partition:0,offset:`0`})))}async disconnect(){return this.logger.debug(`Kafka: [${this.name}] Mock mode - skipping disconnect`),Promise.resolve()}},o=class e{constructor(e){if(this.producers=new Map,this.gracefulShutdownStarted=!1,this.fatalError=null,this.lastReadinessCheck=null,this.logger=e.logger,this.enabled=e.enabled??!0,this.healthCheckTimeoutMs=e.healthCheckTimeoutMs??5e3,this.healthCheckCacheMs=e.healthCheckCacheMs??1e3,this.bootstrapTimeoutMs=e.bootstrapTimeoutMs??3e4,this.strictBootstrap=e.strictBootstrap??!0,this.logger.info(`Kafka: Initialized KafkaManager`,{enabled:this.enabled,producerCount:Object.keys(e.producers).length,producers:Object.keys(e.producers)}),this.enabled)for(let[n,r]of Object.entries(e.producers)){if(!r.brokers||r.brokers.length===0)throw new t(`[${n}] At least one broker is required`);this.producers.set(n,new i(n,r,this.logger))}else{for(let[t,n]of Object.entries(e.producers))this.producers.set(t,new a(t,n.brokers,this.logger));this.logger.info(`Kafka: Created mock producers (Kafka disabled)`)}e.dontGracefulShutdown||this.setupGracefulShutdown()}static create(t){return new e(t)}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 all producers...`);try{await this.disconnect(),this.logger.info(`Kafka: [graceful-shutdown] all producers disconnected successfully`)}catch(e){throw this.logger.error(`Kafka: [graceful-shutdown] error during shutdown`,{error:e}),e}}}get isEnabled(){return this.enabled}get producerNames(){return Array.from(this.producers.keys())}hasProducer(e){return this.producers.has(e)}getProducer(e){let n=this.producers.get(e);if(!n)throw new t(`Producer '${e}' not found. Available producers: ${this.producerNames.join(`, `)}`);return n}async bootstrap(e){let n=Date.now(),r=e?.timeoutMs??this.bootstrapTimeoutMs,i=e?.strict??this.strictBootstrap,a=e?.producers??this.producerNames;this.logger.info(`Kafka: Starting bootstrap`,{producers:a,timeout:r,strict:i});let o={},s=[];for(let e of a){let t=this.producers.get(e);if(!t){o[e]={success:!1,duration:0,error:`Producer '${e}' not found`},s.push(`Producer '${e}' not found`);continue}let n=Date.now();try{await t.ping({timeout:r,force:!0});let i=t.getHealth();o[e]={success:!0,duration:Date.now()-n,clusterId:i.clusterId??void 0,brokerCount:i.brokerCount},this.logger.info(`Kafka: [${e}] Bootstrap successful`,{duration:Date.now()-n,clusterId:i.clusterId,brokerCount:i.brokerCount})}catch(t){let r=t;o[e]={success:!1,duration:Date.now()-n,error:r.message},s.push(`[${e}] ${r.message}`),this.logger.error(`Kafka: [${e}] Bootstrap failed`,{duration:Date.now()-n,error:r.message})}}let c=Date.now()-n,l=Object.values(o).filter(e=>e.success).length,u=Object.keys(o).length,d=i?l===u:l>0,f={success:d,duration:c,results:o};if(d)this.logger.info(`Kafka: Bootstrap completed successfully`,{duration:c,successCount:l,totalCount:u});else{let e=`Kafka bootstrap failed (${l}/${u} producers succeeded)\n\nFailures:\n${s.map(e=>` - ${e}`).join(`
|
|
3
|
+
`)}\n\nConfiguration:\n Strict mode: ${i}\n Timeout: ${r}ms\n Producers attempted: ${a.join(`, `)}`;throw this.logger.error(`Kafka: Bootstrap failed`,{duration:c,successCount:l,totalCount:u,errors:s}),new t(e)}return f}getHealth(){let e={};for(let[t,n]of this.producers.entries())e[t]=n.getHealth();return e}getProducerHealth(e){return this.getProducer(e).getHealth()}isLive(){return!this.fatalError&&!this.gracefulShutdownStarted}async ping(){let e=Array.from(this.producers.values(),e=>e.ping());await Promise.all(e)}async pingProducer(e){await this.getProducer(e).ping()}async isReady(e){if(!this.enabled)return!0;let t=Date.now();if(!(e?.force??!1)&&this.lastReadinessCheck){let e=t-this.lastReadinessCheck.timestamp;if(e<this.healthCheckCacheMs)return this.logger.debug(`Kafka: Using cached readiness result`,{result:this.lastReadinessCheck.result,age:e}),this.lastReadinessCheck.result}try{let n=e?.timeout??this.healthCheckTimeoutMs,r=Array.from(this.producers.values()).map(e=>e.ping({timeout:n,force:!0}));return await Promise.all(r),this.lastReadinessCheck={result:!0,timestamp:t},!0}catch(e){return this.lastReadinessCheck={result:!1,timestamp:t},this.logger.warn(`Kafka: Readiness check failed`,{error:e.message}),!1}}getConnectionStatus(){let e={};for(let[t,n]of this.producers.entries()){let r=n.getHealth();e[t]={connected:r.isConnected,lastSuccessAt:r.lastPingSucceededAt,lastError:r.lastError?.message??null}}return e}async publish(e,t,n,r){return this.getProducer(e).publish(t,n,r)}async publishBatch(e,t){return this.getProducer(e).publishBatch(t)}async disconnect(){let e=Array.from(this.producers.values()).map(e=>e.disconnect());await Promise.all(e),this.logger.info(`Kafka: All producers disconnected`)}async disconnectProducer(e){await this.getProducer(e).disconnect()}};exports.KafkaError=t,exports.KafkaManager=o;
|
|
2
4
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["fallbackLogger: LoggerInstanceManager","headers: Record<string, string>","fallbackLogger","status: Record<string, boolean>"],"sources":["../src/logger.ts","../src/kafkaError.ts","../src/consts.ts","../src/manager.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 KafkaManagerOptions,\n ProducerConfig,\n PublishOptions,\n PublishBatchOptions,\n RecordMetadata,\n} from './types';\n\n/**\n * Wrapper for a single Kafka producer instance with lazy initialization\n */\nclass ProducerWrapper {\n private producer: Producer<string, string, string, string> | null = null;\n private readonly logger: LoggerInstanceManager;\n private readonly name: string;\n private readonly config: ProducerConfig;\n private _isConnected = false;\n private initPromise: Promise<void> | null = null;\n\n constructor(\n name: string,\n config: ProducerConfig,\n logger: LoggerInstanceManager,\n ) {\n this.name = name;\n this.config = config;\n this.logger = logger;\n }\n\n private async initialize(): Promise<void> {\n if (this.producer) {\n return;\n }\n\n if (this.initPromise) {\n await this.initPromise;\n return;\n }\n\n this.initPromise = (async (): Promise<void> => {\n const { Producer, stringSerializers } = await import('@platformatic/kafka');\n\n this.producer = new Producer({\n bootstrapBrokers: this.config.brokers,\n clientId: this.config.clientId || `${DEFAULT_CLIENT_ID}-${this.name}`,\n serializers: stringSerializers,\n autocreateTopics: this.config.autoCreateTopics ?? false,\n sasl: this.config.sasl,\n });\n\n this.logger.info(`Kafka: [${this.name}] Initialized producer`, {\n clientId: this.config.clientId || `${DEFAULT_CLIENT_ID}-${this.name}`,\n brokers: this.config.brokers,\n });\n })();\n\n await this.initPromise;\n }\n\n get isConnected(): boolean {\n return this._isConnected;\n }\n\n async ping(): Promise<void> {\n if (this._isConnected) {\n return;\n }\n\n await this.initialize();\n\n try {\n const metadata = await this.producer!.metadata({ topics: [] });\n this._isConnected = true;\n\n this.logger.info(`Kafka: [${this.name}] Successfully connected to cluster`, {\n clusterId: metadata.id,\n brokers: metadata.brokers.size,\n });\n } catch (error) {\n this.logger.error(`Kafka: [${this.name}] Failed to connect to brokers`, { error });\n throw new KafkaError(`[${this.name}] Failed to connect to Kafka brokers: ${(error as Error).message}`);\n }\n }\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(`[${this.name}] 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: [${this.name}] Published message to topic ${topic}`, {\n topic,\n });\n\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: [${this.name}] Error publishing to topic ${topic}`, { error, value });\n throw new KafkaError(`[${this.name}] Failed to publish to topic ${topic}: ${err.message || 'Unknown error'}`);\n }\n }\n\n async publishBatch(options: PublishBatchOptions): Promise<RecordMetadata[]> {\n if (!options.topic) {\n throw new KafkaError(`[${this.name}] Topic name is required`);\n }\n\n if (!options.messages || options.messages.length === 0) {\n throw new KafkaError(`[${this.name}] 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: [${this.name}] Published ${messages.length} messages to topic ${options.topic}`, {\n topic: options.topic,\n count: messages.length,\n });\n\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: [${this.name}] Error publishing batch to topic ${options.topic}`, { error });\n throw new KafkaError(`[${this.name}] Failed to publish batch to topic ${options.topic}: ${err.message || 'Unknown error'}`);\n }\n }\n\n async disconnect(): Promise<void> {\n if (!this.producer || !this._isConnected) {\n this.logger.debug(`Kafka: [${this.name}] Producer already disconnected`);\n return;\n }\n\n this.logger.info(`Kafka: [${this.name}] Disconnecting producer`);\n\n try {\n await this.producer.close();\n this._isConnected = false;\n this.logger.info(`Kafka: [${this.name}] Producer disconnected successfully`);\n } catch (error) {\n this.logger.error(`Kafka: [${this.name}] Error disconnecting producer`, { error });\n throw new KafkaError(`[${this.name}] Failed to disconnect producer: ${(error as Error).message}`);\n }\n }\n}\n\n/**\n * Mock producer for when Kafka is disabled\n */\nclass MockProducer {\n private readonly logger: LoggerInstanceManager;\n private readonly name: string;\n\n constructor(name: string, logger: LoggerInstanceManager) {\n this.name = name;\n this.logger = logger;\n }\n\n readonly isConnected = true;\n\n async ping(): Promise<void> {\n this.logger.debug(`Kafka: [${this.name}] Mock mode - skipping ping`);\n return Promise.resolve();\n }\n\n async publish<T = unknown>(topic: string, value: T, _options?: PublishOptions): Promise<RecordMetadata[]> {\n this.logger.debug(`Kafka: [${this.name}] Mock mode - skipping publish to topic: ${topic}`, { value });\n return Promise.resolve([{\n topic,\n partition: 0,\n offset: '0',\n }]);\n }\n\n async publishBatch(options: PublishBatchOptions): Promise<RecordMetadata[]> {\n this.logger.debug(`Kafka: [${this.name}] Mock mode - skipping batch publish to topic: ${options.topic}`, {\n messageCount: options.messages.length,\n });\n return Promise.resolve(options.messages.map(() => ({\n topic: options.topic,\n partition: 0,\n offset: '0',\n })));\n }\n\n async disconnect(): Promise<void> {\n this.logger.debug(`Kafka: [${this.name}] Mock mode - skipping disconnect`);\n return Promise.resolve();\n }\n}\n\n/**\n * Manager for multiple Kafka producers\n */\nexport class KafkaManager<TProducers extends string = string> {\n private readonly producers = new Map<string, ProducerWrapper | MockProducer>();\n private readonly logger: LoggerInstanceManager;\n private readonly enabled: boolean;\n private gracefulShutdownStarted = false;\n\n private constructor(options: KafkaManagerOptions) {\n this.logger = options.logger ?? fallbackLogger;\n this.enabled = options.enabled ?? true;\n\n this.logger.info('Kafka: Initialized KafkaManager', {\n enabled: this.enabled,\n producerCount: Object.keys(options.producers).length,\n producers: Object.keys(options.producers),\n });\n\n // Create producers\n if (!this.enabled) {\n // Create mock producers\n for (const [name, _config] of Object.entries(options.producers)) {\n this.producers.set(name, new MockProducer(name, this.logger));\n }\n this.logger.info('Kafka: Created mock producers (Kafka disabled)');\n } else {\n // Create real producers (they will initialize lazily)\n for (const [name, config] of Object.entries(options.producers)) {\n if (!config.brokers || config.brokers.length === 0) {\n throw new KafkaError(`[${name}] At least one broker is required`);\n }\n\n this.producers.set(name, new ProducerWrapper(name, config, this.logger));\n }\n }\n\n if (!options.dontGracefulShutdown) {\n this.setupGracefulShutdown();\n }\n }\n\n /**\n * Create a new KafkaManager instance with multiple named producers\n * Producers are initialized lazily on first use\n * Producer names are type-safe based on the configuration\n */\n static create<T extends Record<string, ProducerConfig>>(\n options: Omit<KafkaManagerOptions, 'producers'> & { producers: T; },\n ): KafkaManager<keyof T & string> {\n return new KafkaManager(options);\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 all producers...`);\n\n try {\n await this.disconnect();\n this.logger.info('Kafka: [graceful-shutdown] all producers disconnected successfully');\n } catch (error) {\n this.logger.error('Kafka: [graceful-shutdown] error during shutdown', { error });\n throw error;\n }\n }\n\n /**\n * Check if Kafka is enabled\n */\n get isEnabled(): boolean {\n return this.enabled;\n }\n\n /**\n * Get all producer names\n */\n get producerNames(): TProducers[] {\n return Array.from(this.producers.keys()) as TProducers[];\n }\n\n /**\n * Check if a producer exists\n */\n hasProducer(name: TProducers): boolean {\n return this.producers.has(name);\n }\n\n /**\n * Get a specific producer\n */\n private getProducer(name: TProducers): ProducerWrapper | MockProducer {\n const producer = this.producers.get(name);\n if (!producer) {\n throw new KafkaError(`Producer '${name}' not found. Available producers: ${this.producerNames.join(', ')}`);\n }\n return producer;\n }\n\n /**\n * Ping all producers to verify connectivity\n */\n async ping(): Promise<void> {\n const promises = Array.from(this.producers.values()).map(p => p.ping());\n await Promise.all(promises);\n }\n\n /**\n * Ping a specific producer\n */\n async pingProducer(name: TProducers): Promise<void> {\n await this.getProducer(name).ping();\n }\n\n /**\n * Check if all producers are connected and ready\n */\n async isReady(): Promise<boolean> {\n if (!this.enabled) {\n return true;\n }\n\n try {\n await this.ping();\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Check connection status of all producers\n */\n getConnectionStatus(): Record<TProducers, boolean> {\n const status: Record<string, boolean> = {};\n for (const [name, producer] of this.producers.entries()) {\n status[name] = producer.isConnected;\n }\n return status as Record<TProducers, boolean>;\n }\n\n /**\n * Publish a message using a named producer\n */\n async publish<T = unknown>(\n producerName: TProducers,\n topic: string,\n value: T,\n options?: PublishOptions,\n ): Promise<RecordMetadata[]> {\n return this.getProducer(producerName).publish(topic, value, options);\n }\n\n /**\n * Publish a batch of messages using a named producer\n */\n async publishBatch(\n producerName: TProducers,\n options: PublishBatchOptions,\n ): Promise<RecordMetadata[]> {\n return this.getProducer(producerName).publishBatch(options);\n }\n\n /**\n * Disconnect all producers\n */\n async disconnect(): Promise<void> {\n const promises = Array.from(this.producers.values()).map(p => p.disconnect());\n await Promise.all(promises);\n this.logger.info('Kafka: All producers disconnected');\n }\n\n /**\n * Disconnect a specific producer\n */\n async disconnectProducer(name: TProducers): Promise<void> {\n await this.getProducer(name).disconnect();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["name: string","config: ProducerConfig","logger: LoggerInstanceManager","#loadKafkaAndSetup","headers: Record<string, string>","name: string","brokers: string[]","logger: LoggerInstanceManager","results: BootstrapResult['results']","errors: string[]","result: BootstrapResult","health: Record<string, ProducerHealth>","status: Record<string, ConnectionStatus>"],"sources":["../src/kafkaError.ts","../src/consts.ts","../src/producer.ts","../src/mockProducer.ts","../src/manager.ts"],"sourcesContent":["export default class KafkaError extends Error {\n name = 'KafkaError';\n}\n","export const TIMESTAMP_HEADER = 'x-timestamp';\n\nexport const DEFAULT_CLIENT_ID = 'autofleet-kafka-producer';\n","import { setTimeout } from 'node:timers/promises';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport type { Producer } from '@platformatic/kafka/dist/clients/producer/index.ts';\nimport KafkaError from './kafkaError';\nimport {\n DEFAULT_CLIENT_ID,\n TIMESTAMP_HEADER,\n} from './consts';\nimport type {\n ProducerConfig,\n PublishOptions,\n PublishBatchOptions,\n RecordMetadata,\n ProducerHealth,\n IProducer,\n} from './types';\n\n/**\n * Wrapper for a single Kafka producer instance with lazy initialization\n * Tracks health metadata including connection state, timestamps, and errors\n */\nexport class ProducerWrapper implements IProducer {\n private producer: Producer<string, string, string, string> | null = null;\n private _isConnected = false;\n private initPromise: Promise<void> | null = null;\n\n // Health tracking metadata\n private _lastPingAt: number | null = null;\n private _lastPingSucceededAt: number | null = null;\n private _lastError: { message: string; timestamp: number; stack?: string; } | null = null;\n private _clusterId: string | null = null;\n private _brokerCount = 0;\n\n constructor(\n private readonly name: string,\n private readonly config: ProducerConfig,\n private readonly logger: LoggerInstanceManager,\n ) {}\n\n async #loadKafkaAndSetup(): Promise<void> {\n const { Producer, stringSerializers } = await import('@platformatic/kafka');\n\n this.producer = new Producer({\n bootstrapBrokers: this.config.brokers,\n clientId: this.config.clientId || `${DEFAULT_CLIENT_ID}-${this.name}`,\n serializers: stringSerializers,\n autocreateTopics: this.config.autoCreateTopics ?? false,\n sasl: this.config.sasl,\n });\n\n this.logger.info(`Kafka: [${this.name}] Initialized producer`, {\n clientId: this.config.clientId || `${DEFAULT_CLIENT_ID}-${this.name}`,\n brokers: this.config.brokers,\n });\n\n // Clear promise to allow garbage collection\n this.initPromise = null;\n }\n\n private async initialize(): Promise<void> {\n if (this.producer) {\n return;\n }\n\n this.initPromise ??= this.#loadKafkaAndSetup();\n await this.initPromise;\n }\n\n get isConnected(): boolean {\n return this._isConnected;\n }\n\n /**\n * Get comprehensive health snapshot for this producer\n */\n getHealth(): ProducerHealth {\n return {\n name: this.name,\n enabled: true,\n isConnected: this._isConnected,\n lastPingAt: this._lastPingAt,\n lastPingSucceededAt: this._lastPingSucceededAt,\n lastError: this._lastError,\n clusterId: this._clusterId,\n brokerCount: this._brokerCount,\n brokers: this.config.brokers,\n };\n }\n\n /**\n * Build an actionable error message with troubleshooting guidance\n */\n private buildConnectionErrorMessage(error: Error): string {\n const brokerList = this.config.brokers.join(', ');\n const firstBroker = this.config.brokers[0];\n const [host, port] = firstBroker ? firstBroker.split(':') : ['', ''];\n\n return `[${this.name}] Failed to connect to Kafka brokers: ${error.message}\\n\\n`\n + `Possible causes:\\n`\n + ` 1. Brokers are unreachable: ${brokerList}\\n`\n + ` 2. Network connectivity issues\\n`\n + ` 3. Firewall blocking ports\\n`\n + ` 4. ${this.config.sasl ? 'SASL authentication failed' : 'Authentication not configured but required'}\\n\\n`\n + `Troubleshooting:\\n`\n + ` - Verify brokers are running and accessible\\n`\n + (host && port ? ` - Test connectivity: nc -zv ${host} ${port}\\n` : '')\n + ` - Check network policies and firewall rules\\n`\n + ` ${this.config.sasl ? '- Verify SASL credentials are correct\\n' : ''}\\n`\n + `Current configuration:\\n`\n + ` Brokers: ${brokerList}\\n`\n + ` Client ID: ${this.config.clientId || `${DEFAULT_CLIENT_ID}-${this.name}`}\\n`\n + ` SASL: ${this.config.sasl ? `enabled (${this.config.sasl.mechanism})` : 'disabled'}\\n`\n + ` Auto-create topics: ${this.config.autoCreateTopics ?? false}`;\n }\n\n /**\n * Ping the Kafka cluster to verify connectivity\n * @param options.timeout - Maximum time to wait for connection (ms)\n * @param options.force - Force reconnection even if already connected\n */\n async ping(options?: { timeout?: number; force?: boolean; }): Promise<void> {\n this._lastPingAt = Date.now();\n\n // Skip if already connected and not forcing revalidation\n if (this._isConnected && !options?.force) {\n return;\n }\n\n await this.initialize();\n\n try {\n // Create error before promise to preserve stack trace\n const timeoutMs = options?.timeout ?? 5000;\n const timeoutError = new Error(`Connection timeout after ${timeoutMs}ms`);\n const timeoutPromise = setTimeout(timeoutMs).then(() => {\n throw timeoutError;\n });\n\n // Race between metadata fetch and timeout\n const metadata = await Promise.race([\n this.producer!.metadata({ topics: [] }),\n timeoutPromise,\n ]);\n\n this._isConnected = true;\n this._lastPingSucceededAt = Date.now();\n this._clusterId = metadata.id;\n this._brokerCount = metadata.brokers.size;\n this._lastError = null; // Clear any previous errors\n\n this.logger.info(`Kafka: [${this.name}] Successfully connected to cluster`, {\n clusterId: metadata.id,\n brokers: metadata.brokers.size,\n duration: Date.now() - this._lastPingAt,\n });\n } catch (error) {\n const err = error as Error;\n this._isConnected = false;\n this._lastError = {\n message: err.message,\n timestamp: Date.now(),\n stack: err.stack,\n };\n\n this.logger.error(`Kafka: [${this.name}] Failed to connect to brokers`, {\n error: err.message,\n duration: Date.now() - this._lastPingAt,\n });\n\n throw new KafkaError(this.buildConnectionErrorMessage(err), { cause: err });\n }\n }\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(`[${this.name}] 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: [${this.name}] Published message to topic ${topic}`, {\n topic,\n });\n\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: [${this.name}] Error publishing to topic ${topic}`, { error, value });\n throw new KafkaError(`[${this.name}] Failed to publish to topic ${topic}: ${err.message || 'Unknown error'}`, { cause: error });\n }\n }\n\n async publishBatch(options: PublishBatchOptions): Promise<RecordMetadata[]> {\n if (!options.topic) {\n throw new KafkaError(`[${this.name}] Topic name is required`);\n }\n\n if (!options.messages || options.messages.length === 0) {\n throw new KafkaError(`[${this.name}] 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: [${this.name}] Published ${messages.length} messages to topic ${options.topic}`, {\n topic: options.topic,\n count: messages.length,\n });\n\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: [${this.name}] Error publishing batch to topic ${options.topic}`, { error });\n throw new KafkaError(`[${this.name}] Failed to publish batch to topic ${options.topic}: ${err.message || 'Unknown error'}`, { cause: error });\n }\n }\n\n async disconnect(): Promise<void> {\n if (!this.producer || !this._isConnected) {\n this.logger.debug(`Kafka: [${this.name}] Producer already disconnected`);\n return;\n }\n\n this.logger.info(`Kafka: [${this.name}] Disconnecting producer`);\n\n try {\n await this.producer.close();\n this._isConnected = false;\n this.logger.info(`Kafka: [${this.name}] Producer disconnected successfully`);\n } catch (error) {\n this.logger.error(`Kafka: [${this.name}] Error disconnecting producer`, { error });\n throw new KafkaError(`[${this.name}] Failed to disconnect producer: ${(error as Error).message}`, { cause: error });\n }\n }\n}\n","import type { LoggerInstanceManager } from '@autofleet/logger';\nimport type {\n PublishOptions,\n PublishBatchOptions,\n RecordMetadata,\n ProducerHealth,\n IProducer,\n} from './types';\n\n/**\n * Mock producer for when Kafka is disabled\n */\nexport class MockProducer implements IProducer {\n constructor(private readonly name: string, private readonly brokers: string[], private readonly logger: LoggerInstanceManager) {}\n\n readonly isConnected = true;\n\n /**\n * Get health snapshot for mock producer\n */\n getHealth(): ProducerHealth {\n return {\n name: this.name,\n enabled: false, // Mock mode means Kafka is disabled\n isConnected: true, // Mock is always \"connected\"\n lastPingAt: null,\n lastPingSucceededAt: null,\n lastError: null,\n clusterId: 'mock-cluster',\n brokerCount: 0,\n brokers: this.brokers,\n };\n }\n\n async ping(): Promise<void> {\n this.logger.debug(`Kafka: [${this.name}] Mock mode - skipping ping`);\n return Promise.resolve();\n }\n\n async publish<T = unknown>(topic: string, value: T, _options?: PublishOptions): Promise<RecordMetadata[]> {\n this.logger.debug(`Kafka: [${this.name}] Mock mode - skipping publish to topic: ${topic}`, { value });\n return Promise.resolve([{\n topic,\n partition: 0,\n offset: '0',\n }]);\n }\n\n async publishBatch(options: PublishBatchOptions): Promise<RecordMetadata[]> {\n this.logger.debug(`Kafka: [${this.name}] Mock mode - skipping batch publish to topic: ${options.topic}`, {\n messageCount: options.messages.length,\n });\n return Promise.resolve(options.messages.map(() => ({\n topic: options.topic,\n partition: 0,\n offset: '0',\n })));\n }\n\n async disconnect(): Promise<void> {\n this.logger.debug(`Kafka: [${this.name}] Mock mode - skipping disconnect`);\n return Promise.resolve();\n }\n}\n","import type { LoggerInstanceManager } from '@autofleet/logger';\nimport KafkaError from './kafkaError';\nimport { ProducerWrapper } from './producer';\nimport { MockProducer } from './mockProducer';\nimport type {\n KafkaManagerOptions,\n PublishOptions,\n PublishBatchOptions,\n RecordMetadata,\n ProducerHealth,\n BootstrapOptions,\n BootstrapResult,\n ReadinessOptions,\n ConnectionStatus,\n IProducer,\n ProducerConfig,\n} from './types';\n\n/**\n * Manager for multiple Kafka producers\n * Provides lifecycle management, health tracking, and operational guarantees\n */\nexport class KafkaManager<TProducers extends string = string> {\n private readonly producers = new Map<string, IProducer>();\n private readonly logger: LoggerInstanceManager;\n private readonly enabled: boolean;\n private gracefulShutdownStarted = false;\n private fatalError: Error | null = null;\n\n // Configuration options\n private readonly healthCheckTimeoutMs: number;\n private readonly healthCheckCacheMs: number;\n private readonly bootstrapTimeoutMs: number;\n private readonly strictBootstrap: boolean;\n\n // Health check cache\n private lastReadinessCheck: { result: boolean; timestamp: number; } | null = null;\n\n private constructor(options: KafkaManagerOptions) {\n this.logger = options.logger;\n this.enabled = options.enabled ?? true;\n this.healthCheckTimeoutMs = options.healthCheckTimeoutMs ?? 5000;\n this.healthCheckCacheMs = options.healthCheckCacheMs ?? 1000;\n this.bootstrapTimeoutMs = options.bootstrapTimeoutMs ?? 30_000;\n this.strictBootstrap = options.strictBootstrap ?? true;\n\n this.logger.info('Kafka: Initialized KafkaManager', {\n enabled: this.enabled,\n producerCount: Object.keys(options.producers).length,\n producers: Object.keys(options.producers),\n });\n\n // Create producers\n if (!this.enabled) {\n // Create mock producers\n for (const [name, config] of Object.entries(options.producers)) {\n this.producers.set(name, new MockProducer(name, config.brokers, this.logger));\n }\n this.logger.info('Kafka: Created mock producers (Kafka disabled)');\n } else {\n // Create real producers (they will initialize lazily)\n for (const [name, config] of Object.entries(options.producers)) {\n if (!config.brokers || config.brokers.length === 0) {\n throw new KafkaError(`[${name}] At least one broker is required`);\n }\n\n this.producers.set(name, new ProducerWrapper(name, config, this.logger));\n }\n }\n\n if (!options.dontGracefulShutdown) {\n this.setupGracefulShutdown();\n }\n }\n\n /**\n * Create a new KafkaManager instance with multiple named producers\n * Producers are initialized lazily on first use\n * Producer names are type-safe based on the configuration\n */\n static create<T extends Record<string, ProducerConfig>>(\n options: Omit<KafkaManagerOptions, 'producers'> & { producers: T; },\n ): KafkaManager<keyof T & string> {\n return new KafkaManager(options);\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 all producers...`);\n\n try {\n await this.disconnect();\n this.logger.info('Kafka: [graceful-shutdown] all producers disconnected successfully');\n } catch (error) {\n this.logger.error('Kafka: [graceful-shutdown] error during shutdown', { error });\n throw error;\n }\n }\n\n /**\n * Check if Kafka is enabled\n */\n get isEnabled(): boolean {\n return this.enabled;\n }\n\n /**\n * Get all producer names\n */\n get producerNames(): TProducers[] {\n return Array.from(this.producers.keys()) as TProducers[];\n }\n\n /**\n * Check if a producer exists\n */\n hasProducer(name: TProducers): boolean {\n return this.producers.has(name);\n }\n\n /**\n * Get a specific producer\n */\n private getProducer(name: TProducers): IProducer {\n const producer = this.producers.get(name);\n if (!producer) {\n throw new KafkaError(`Producer '${name}' not found. Available producers: ${this.producerNames.join(', ')}`);\n }\n return producer;\n }\n\n /**\n * Explicitly bootstrap all (or specific) producers\n * Connects to brokers, validates connectivity, and returns detailed results\n *\n * @example\n * // Bootstrap all producers with defaults (30s timeout, strict mode)\n * await kafka.bootstrap();\n *\n * @example\n * // Bootstrap with custom timeout and non-strict mode\n * const result = await kafka.bootstrap({ timeoutMs: 10000, strict: false });\n * if (!result.success) {\n * console.error('Some producers failed:', result.results);\n * }\n *\n * @example\n * // Bootstrap specific producers only\n * await kafka.bootstrap({ producers: ['main'] });\n */\n async bootstrap(options?: BootstrapOptions): Promise<BootstrapResult> {\n const startTime = Date.now();\n const timeoutMs = options?.timeoutMs ?? this.bootstrapTimeoutMs;\n const strict = options?.strict ?? this.strictBootstrap;\n const producersToBootstrap = options?.producers ?? this.producerNames;\n\n this.logger.info('Kafka: Starting bootstrap', {\n producers: producersToBootstrap,\n timeout: timeoutMs,\n strict,\n });\n\n const results: BootstrapResult['results'] = {};\n const errors: string[] = [];\n\n // Bootstrap each producer\n for (const name of producersToBootstrap) {\n const producer = this.producers.get(name);\n if (!producer) {\n results[name] = {\n success: false,\n duration: 0,\n error: `Producer '${name}' not found`,\n };\n errors.push(`Producer '${name}' not found`);\n continue;\n }\n\n const producerStartTime = Date.now();\n try {\n await producer.ping({ timeout: timeoutMs, force: true });\n const health = producer.getHealth();\n\n results[name] = {\n success: true,\n duration: Date.now() - producerStartTime,\n clusterId: health.clusterId ?? undefined,\n brokerCount: health.brokerCount,\n };\n\n this.logger.info(`Kafka: [${name}] Bootstrap successful`, {\n duration: Date.now() - producerStartTime,\n clusterId: health.clusterId,\n brokerCount: health.brokerCount,\n });\n } catch (error) {\n const err = error as Error;\n results[name] = {\n success: false,\n duration: Date.now() - producerStartTime,\n error: err.message,\n };\n errors.push(`[${name}] ${err.message}`);\n\n this.logger.error(`Kafka: [${name}] Bootstrap failed`, {\n duration: Date.now() - producerStartTime,\n error: err.message,\n });\n }\n }\n\n const totalDuration = Date.now() - startTime;\n const successCount = Object.values(results).filter(r => r.success).length;\n const totalCount = Object.keys(results).length;\n const success = strict ? successCount === totalCount : successCount > 0;\n\n const result: BootstrapResult = {\n success,\n duration: totalDuration,\n results,\n };\n\n if (success) {\n this.logger.info('Kafka: Bootstrap completed successfully', {\n duration: totalDuration,\n successCount,\n totalCount,\n });\n } else {\n const errorMessage = `Kafka bootstrap failed (${successCount}/${totalCount} producers succeeded)\\n\\n`\n + `Failures:\\n${errors.map(e => ` - ${e}`).join('\\n')}\\n\\n`\n + `Configuration:\\n`\n + ` Strict mode: ${strict}\\n`\n + ` Timeout: ${timeoutMs}ms\\n`\n + ` Producers attempted: ${producersToBootstrap.join(', ')}`;\n\n this.logger.error('Kafka: Bootstrap failed', {\n duration: totalDuration,\n successCount,\n totalCount,\n errors,\n });\n\n throw new KafkaError(errorMessage);\n }\n\n return result;\n }\n\n /**\n * Get detailed health snapshot for all producers\n * Returns comprehensive metadata including timestamps, errors, cluster info\n *\n * @example\n * const health = kafka.getHealth();\n * console.log(health.main.lastPingSucceededAt); // timestamp\n * console.log(health.main.clusterId); // 'prod-kafka-01'\n */\n getHealth(): Record<TProducers, ProducerHealth> {\n const health: Record<string, ProducerHealth> = {};\n for (const [name, producer] of this.producers.entries()) {\n health[name] = producer.getHealth();\n }\n return health as Record<TProducers, ProducerHealth>;\n }\n\n /**\n * Get detailed health for a specific producer\n */\n getProducerHealth(name: TProducers): ProducerHealth {\n const producer = this.getProducer(name);\n return producer.getHealth();\n }\n\n /**\n * Lightweight liveness check - does NOT perform Kafka operations\n * Only checks internal state for fatal errors\n * Suitable for Kubernetes liveness probes\n *\n * Returns false if:\n * - Manager is in a fatal state\n * - Graceful shutdown has started\n *\n * @example\n * app.get('/health/live', (req, res) => {\n * res.status(kafka.isLive() ? 200 : 503).json({ live: kafka.isLive() });\n * });\n */\n isLive(): boolean {\n return !this.fatalError && !this.gracefulShutdownStarted;\n }\n\n /**\n * Ping all producers to verify connectivity\n */\n async ping(): Promise<void> {\n const promises = Array.from(this.producers.values(), p => p.ping());\n await Promise.all(promises);\n }\n\n /**\n * Ping a specific producer\n */\n async pingProducer(name: TProducers): Promise<void> {\n await this.getProducer(name).ping();\n }\n\n /**\n * Check if Kafka is ready to handle traffic\n * Revalidates connectivity if cache is stale\n *\n * @param options.timeout - Override default health check timeout\n * @param options.force - Force revalidation, ignore cache\n *\n * @example\n * // Use cached result if fresh (within healthCheckCacheMs)\n * const ready = await kafka.isReady();\n *\n * @example\n * // Force immediate revalidation\n * const ready = await kafka.isReady({ force: true });\n */\n async isReady(options?: ReadinessOptions): Promise<boolean> {\n if (!this.enabled) {\n return true;\n }\n\n const now = Date.now();\n const force = options?.force ?? false;\n\n // Use cached result if available and fresh\n if (!force && this.lastReadinessCheck) {\n const age = now - this.lastReadinessCheck.timestamp;\n if (age < this.healthCheckCacheMs) {\n this.logger.debug('Kafka: Using cached readiness result', {\n result: this.lastReadinessCheck.result,\n age,\n });\n return this.lastReadinessCheck.result;\n }\n }\n\n // Revalidate connectivity\n try {\n const timeout = options?.timeout ?? this.healthCheckTimeoutMs;\n const promises = Array.from(this.producers.values()).map(p =>\n p.ping({ timeout, force: true }),\n );\n await Promise.all(promises);\n\n this.lastReadinessCheck = { result: true, timestamp: now };\n return true;\n } catch (error) {\n this.lastReadinessCheck = { result: false, timestamp: now };\n this.logger.warn('Kafka: Readiness check failed', {\n error: (error as Error).message,\n });\n return false;\n }\n }\n\n /**\n * Check connection status of all producers\n * Returns detailed status including last success time and errors\n */\n getConnectionStatus(): Record<TProducers, ConnectionStatus> {\n const status: Record<string, ConnectionStatus> = {};\n for (const [name, producer] of this.producers.entries()) {\n const health = producer.getHealth();\n status[name] = {\n connected: health.isConnected,\n lastSuccessAt: health.lastPingSucceededAt,\n lastError: health.lastError?.message ?? null,\n };\n }\n return status as Record<TProducers, ConnectionStatus>;\n }\n\n /**\n * Publish a message using a named producer\n */\n async publish<T = unknown>(\n producerName: TProducers,\n topic: string,\n value: T,\n options?: PublishOptions,\n ): Promise<RecordMetadata[]> {\n return this.getProducer(producerName).publish(topic, value, options);\n }\n\n /**\n * Publish a batch of messages using a named producer\n */\n async publishBatch(\n producerName: TProducers,\n options: PublishBatchOptions,\n ): Promise<RecordMetadata[]> {\n return this.getProducer(producerName).publishBatch(options);\n }\n\n /**\n * Disconnect all producers\n */\n async disconnect(): Promise<void> {\n const promises = Array.from(this.producers.values()).map(p => p.disconnect());\n await Promise.all(promises);\n this.logger.info('Kafka: All producers disconnected');\n }\n\n /**\n * Disconnect a specific producer\n */\n async disconnectProducer(name: TProducers): Promise<void> {\n await this.getProducer(name).disconnect();\n }\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -17,8 +17,8 @@ interface ProducerConfig {
|
|
|
17
17
|
interface KafkaManagerOptions {
|
|
18
18
|
/** Enable/disable Kafka - when false, returns mock implementations */
|
|
19
19
|
enabled?: boolean;
|
|
20
|
-
/**
|
|
21
|
-
logger
|
|
20
|
+
/** Logger instance */
|
|
21
|
+
logger: LoggerInstanceManager;
|
|
22
22
|
/**
|
|
23
23
|
* When you want your own graceful shutdown, set this to true.
|
|
24
24
|
* @default false
|
|
@@ -26,6 +26,27 @@ interface KafkaManagerOptions {
|
|
|
26
26
|
dontGracefulShutdown?: boolean;
|
|
27
27
|
/** Named producers configuration */
|
|
28
28
|
producers: Record<string, ProducerConfig>;
|
|
29
|
+
/**
|
|
30
|
+
* Global timeout for health checks in ms
|
|
31
|
+
* @default 5000 (5 seconds)
|
|
32
|
+
*/
|
|
33
|
+
healthCheckTimeoutMs?: number;
|
|
34
|
+
/**
|
|
35
|
+
* How long to cache health check results in ms
|
|
36
|
+
* Set to 0 to always revalidate
|
|
37
|
+
* @default 1000 (1 second)
|
|
38
|
+
*/
|
|
39
|
+
healthCheckCacheMs?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Default timeout for bootstrap in ms
|
|
42
|
+
* @default 30000 (30 seconds)
|
|
43
|
+
*/
|
|
44
|
+
bootstrapTimeoutMs?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Default strict mode for bootstrap
|
|
47
|
+
* @default true
|
|
48
|
+
*/
|
|
49
|
+
strictBootstrap?: boolean;
|
|
29
50
|
}
|
|
30
51
|
interface PublishOptions {
|
|
31
52
|
/** Custom headers to attach to the message */
|
|
@@ -51,16 +72,105 @@ interface RecordMetadata {
|
|
|
51
72
|
partition: number;
|
|
52
73
|
offset: string;
|
|
53
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Health metadata for a single producer
|
|
77
|
+
* Tracks connection state, timing, errors, and cluster information
|
|
78
|
+
*/
|
|
79
|
+
interface ProducerHealth {
|
|
80
|
+
/** Producer name */
|
|
81
|
+
name: string;
|
|
82
|
+
/** Whether this producer is enabled (or in mock mode) */
|
|
83
|
+
enabled: boolean;
|
|
84
|
+
/** Whether the producer is currently connected */
|
|
85
|
+
isConnected: boolean;
|
|
86
|
+
/** Timestamp of last ping attempt (ms since epoch) */
|
|
87
|
+
lastPingAt: number | null;
|
|
88
|
+
/** Timestamp of last successful ping (ms since epoch) */
|
|
89
|
+
lastPingSucceededAt: number | null;
|
|
90
|
+
/** Last error encountered, if any */
|
|
91
|
+
lastError: {
|
|
92
|
+
message: string;
|
|
93
|
+
timestamp: number;
|
|
94
|
+
stack?: string;
|
|
95
|
+
} | null;
|
|
96
|
+
/** Kafka cluster ID, if known */
|
|
97
|
+
clusterId: string | null;
|
|
98
|
+
/** Number of brokers in cluster */
|
|
99
|
+
brokerCount: number;
|
|
100
|
+
/** Configured broker addresses */
|
|
101
|
+
brokers: string[];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Options for bootstrap operation
|
|
105
|
+
*/
|
|
106
|
+
interface BootstrapOptions {
|
|
107
|
+
/**
|
|
108
|
+
* Maximum time in ms to wait for all producers to connect
|
|
109
|
+
* @default 30000 (30 seconds)
|
|
110
|
+
*/
|
|
111
|
+
timeoutMs?: number;
|
|
112
|
+
/**
|
|
113
|
+
* If true, bootstrap fails if ANY producer fails
|
|
114
|
+
* If false, bootstrap succeeds if at least one producer connects
|
|
115
|
+
* @default true
|
|
116
|
+
*/
|
|
117
|
+
strict?: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Specific producers to bootstrap (defaults to all)
|
|
120
|
+
*/
|
|
121
|
+
producers?: string[];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Result of a bootstrap operation
|
|
125
|
+
*/
|
|
126
|
+
interface BootstrapResult {
|
|
127
|
+
/** Whether bootstrap succeeded overall */
|
|
128
|
+
success: boolean;
|
|
129
|
+
/** Total duration in milliseconds */
|
|
130
|
+
duration: number;
|
|
131
|
+
/** Per-producer results */
|
|
132
|
+
results: Record<string, {
|
|
133
|
+
success: boolean;
|
|
134
|
+
duration: number;
|
|
135
|
+
error?: string;
|
|
136
|
+
clusterId?: string;
|
|
137
|
+
brokerCount?: number;
|
|
138
|
+
}>;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Options for readiness check
|
|
142
|
+
*/
|
|
143
|
+
interface ReadinessOptions {
|
|
144
|
+
/** Override default health check timeout */
|
|
145
|
+
timeout?: number;
|
|
146
|
+
/** Force revalidation, ignore cache */
|
|
147
|
+
force?: boolean;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Detailed connection status
|
|
151
|
+
*/
|
|
152
|
+
interface ConnectionStatus {
|
|
153
|
+
connected: boolean;
|
|
154
|
+
lastSuccessAt: number | null;
|
|
155
|
+
lastError: string | null;
|
|
156
|
+
}
|
|
54
157
|
//#endregion
|
|
55
158
|
//#region src/manager.d.ts
|
|
56
159
|
/**
|
|
57
160
|
* Manager for multiple Kafka producers
|
|
161
|
+
* Provides lifecycle management, health tracking, and operational guarantees
|
|
58
162
|
*/
|
|
59
163
|
declare class KafkaManager<TProducers extends string = string> {
|
|
60
164
|
private readonly producers;
|
|
61
165
|
private readonly logger;
|
|
62
166
|
private readonly enabled;
|
|
63
167
|
private gracefulShutdownStarted;
|
|
168
|
+
private fatalError;
|
|
169
|
+
private readonly healthCheckTimeoutMs;
|
|
170
|
+
private readonly healthCheckCacheMs;
|
|
171
|
+
private readonly bootstrapTimeoutMs;
|
|
172
|
+
private readonly strictBootstrap;
|
|
173
|
+
private lastReadinessCheck;
|
|
64
174
|
private constructor();
|
|
65
175
|
/**
|
|
66
176
|
* Create a new KafkaManager instance with multiple named producers
|
|
@@ -89,6 +199,55 @@ declare class KafkaManager<TProducers extends string = string> {
|
|
|
89
199
|
*/
|
|
90
200
|
private getProducer;
|
|
91
201
|
/**
|
|
202
|
+
* Explicitly bootstrap all (or specific) producers
|
|
203
|
+
* Connects to brokers, validates connectivity, and returns detailed results
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* // Bootstrap all producers with defaults (30s timeout, strict mode)
|
|
207
|
+
* await kafka.bootstrap();
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* // Bootstrap with custom timeout and non-strict mode
|
|
211
|
+
* const result = await kafka.bootstrap({ timeoutMs: 10000, strict: false });
|
|
212
|
+
* if (!result.success) {
|
|
213
|
+
* console.error('Some producers failed:', result.results);
|
|
214
|
+
* }
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* // Bootstrap specific producers only
|
|
218
|
+
* await kafka.bootstrap({ producers: ['main'] });
|
|
219
|
+
*/
|
|
220
|
+
bootstrap(options?: BootstrapOptions): Promise<BootstrapResult>;
|
|
221
|
+
/**
|
|
222
|
+
* Get detailed health snapshot for all producers
|
|
223
|
+
* Returns comprehensive metadata including timestamps, errors, cluster info
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* const health = kafka.getHealth();
|
|
227
|
+
* console.log(health.main.lastPingSucceededAt); // timestamp
|
|
228
|
+
* console.log(health.main.clusterId); // 'prod-kafka-01'
|
|
229
|
+
*/
|
|
230
|
+
getHealth(): Record<TProducers, ProducerHealth>;
|
|
231
|
+
/**
|
|
232
|
+
* Get detailed health for a specific producer
|
|
233
|
+
*/
|
|
234
|
+
getProducerHealth(name: TProducers): ProducerHealth;
|
|
235
|
+
/**
|
|
236
|
+
* Lightweight liveness check - does NOT perform Kafka operations
|
|
237
|
+
* Only checks internal state for fatal errors
|
|
238
|
+
* Suitable for Kubernetes liveness probes
|
|
239
|
+
*
|
|
240
|
+
* Returns false if:
|
|
241
|
+
* - Manager is in a fatal state
|
|
242
|
+
* - Graceful shutdown has started
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* app.get('/health/live', (req, res) => {
|
|
246
|
+
* res.status(kafka.isLive() ? 200 : 503).json({ live: kafka.isLive() });
|
|
247
|
+
* });
|
|
248
|
+
*/
|
|
249
|
+
isLive(): boolean;
|
|
250
|
+
/**
|
|
92
251
|
* Ping all producers to verify connectivity
|
|
93
252
|
*/
|
|
94
253
|
ping(): Promise<void>;
|
|
@@ -97,13 +256,26 @@ declare class KafkaManager<TProducers extends string = string> {
|
|
|
97
256
|
*/
|
|
98
257
|
pingProducer(name: TProducers): Promise<void>;
|
|
99
258
|
/**
|
|
100
|
-
* Check if
|
|
259
|
+
* Check if Kafka is ready to handle traffic
|
|
260
|
+
* Revalidates connectivity if cache is stale
|
|
261
|
+
*
|
|
262
|
+
* @param options.timeout - Override default health check timeout
|
|
263
|
+
* @param options.force - Force revalidation, ignore cache
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* // Use cached result if fresh (within healthCheckCacheMs)
|
|
267
|
+
* const ready = await kafka.isReady();
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* // Force immediate revalidation
|
|
271
|
+
* const ready = await kafka.isReady({ force: true });
|
|
101
272
|
*/
|
|
102
|
-
isReady(): Promise<boolean>;
|
|
273
|
+
isReady(options?: ReadinessOptions): Promise<boolean>;
|
|
103
274
|
/**
|
|
104
275
|
* Check connection status of all producers
|
|
276
|
+
* Returns detailed status including last success time and errors
|
|
105
277
|
*/
|
|
106
|
-
getConnectionStatus(): Record<TProducers,
|
|
278
|
+
getConnectionStatus(): Record<TProducers, ConnectionStatus>;
|
|
107
279
|
/**
|
|
108
280
|
* Publish a message using a named producer
|
|
109
281
|
*/
|
|
@@ -124,8 +296,8 @@ declare class KafkaManager<TProducers extends string = string> {
|
|
|
124
296
|
//#endregion
|
|
125
297
|
//#region src/kafkaError.d.ts
|
|
126
298
|
declare class KafkaError extends Error {
|
|
127
|
-
|
|
299
|
+
name: string;
|
|
128
300
|
}
|
|
129
301
|
//#endregion
|
|
130
|
-
export { KafkaError, KafkaManager, type KafkaManagerOptions, type ProducerConfig, type PublishBatchOptions, type PublishOptions, type RecordMetadata };
|
|
302
|
+
export { type BootstrapOptions, type BootstrapResult, type ConnectionStatus, KafkaError, KafkaManager, type KafkaManagerOptions, type ProducerConfig, type ProducerHealth, type PublishBatchOptions, type PublishOptions, type ReadinessOptions, type RecordMetadata };
|
|
131
303
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -17,8 +17,8 @@ interface ProducerConfig {
|
|
|
17
17
|
interface KafkaManagerOptions {
|
|
18
18
|
/** Enable/disable Kafka - when false, returns mock implementations */
|
|
19
19
|
enabled?: boolean;
|
|
20
|
-
/**
|
|
21
|
-
logger
|
|
20
|
+
/** Logger instance */
|
|
21
|
+
logger: LoggerInstanceManager;
|
|
22
22
|
/**
|
|
23
23
|
* When you want your own graceful shutdown, set this to true.
|
|
24
24
|
* @default false
|
|
@@ -26,6 +26,27 @@ interface KafkaManagerOptions {
|
|
|
26
26
|
dontGracefulShutdown?: boolean;
|
|
27
27
|
/** Named producers configuration */
|
|
28
28
|
producers: Record<string, ProducerConfig>;
|
|
29
|
+
/**
|
|
30
|
+
* Global timeout for health checks in ms
|
|
31
|
+
* @default 5000 (5 seconds)
|
|
32
|
+
*/
|
|
33
|
+
healthCheckTimeoutMs?: number;
|
|
34
|
+
/**
|
|
35
|
+
* How long to cache health check results in ms
|
|
36
|
+
* Set to 0 to always revalidate
|
|
37
|
+
* @default 1000 (1 second)
|
|
38
|
+
*/
|
|
39
|
+
healthCheckCacheMs?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Default timeout for bootstrap in ms
|
|
42
|
+
* @default 30000 (30 seconds)
|
|
43
|
+
*/
|
|
44
|
+
bootstrapTimeoutMs?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Default strict mode for bootstrap
|
|
47
|
+
* @default true
|
|
48
|
+
*/
|
|
49
|
+
strictBootstrap?: boolean;
|
|
29
50
|
}
|
|
30
51
|
interface PublishOptions {
|
|
31
52
|
/** Custom headers to attach to the message */
|
|
@@ -51,16 +72,105 @@ interface RecordMetadata {
|
|
|
51
72
|
partition: number;
|
|
52
73
|
offset: string;
|
|
53
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Health metadata for a single producer
|
|
77
|
+
* Tracks connection state, timing, errors, and cluster information
|
|
78
|
+
*/
|
|
79
|
+
interface ProducerHealth {
|
|
80
|
+
/** Producer name */
|
|
81
|
+
name: string;
|
|
82
|
+
/** Whether this producer is enabled (or in mock mode) */
|
|
83
|
+
enabled: boolean;
|
|
84
|
+
/** Whether the producer is currently connected */
|
|
85
|
+
isConnected: boolean;
|
|
86
|
+
/** Timestamp of last ping attempt (ms since epoch) */
|
|
87
|
+
lastPingAt: number | null;
|
|
88
|
+
/** Timestamp of last successful ping (ms since epoch) */
|
|
89
|
+
lastPingSucceededAt: number | null;
|
|
90
|
+
/** Last error encountered, if any */
|
|
91
|
+
lastError: {
|
|
92
|
+
message: string;
|
|
93
|
+
timestamp: number;
|
|
94
|
+
stack?: string;
|
|
95
|
+
} | null;
|
|
96
|
+
/** Kafka cluster ID, if known */
|
|
97
|
+
clusterId: string | null;
|
|
98
|
+
/** Number of brokers in cluster */
|
|
99
|
+
brokerCount: number;
|
|
100
|
+
/** Configured broker addresses */
|
|
101
|
+
brokers: string[];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Options for bootstrap operation
|
|
105
|
+
*/
|
|
106
|
+
interface BootstrapOptions {
|
|
107
|
+
/**
|
|
108
|
+
* Maximum time in ms to wait for all producers to connect
|
|
109
|
+
* @default 30000 (30 seconds)
|
|
110
|
+
*/
|
|
111
|
+
timeoutMs?: number;
|
|
112
|
+
/**
|
|
113
|
+
* If true, bootstrap fails if ANY producer fails
|
|
114
|
+
* If false, bootstrap succeeds if at least one producer connects
|
|
115
|
+
* @default true
|
|
116
|
+
*/
|
|
117
|
+
strict?: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Specific producers to bootstrap (defaults to all)
|
|
120
|
+
*/
|
|
121
|
+
producers?: string[];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Result of a bootstrap operation
|
|
125
|
+
*/
|
|
126
|
+
interface BootstrapResult {
|
|
127
|
+
/** Whether bootstrap succeeded overall */
|
|
128
|
+
success: boolean;
|
|
129
|
+
/** Total duration in milliseconds */
|
|
130
|
+
duration: number;
|
|
131
|
+
/** Per-producer results */
|
|
132
|
+
results: Record<string, {
|
|
133
|
+
success: boolean;
|
|
134
|
+
duration: number;
|
|
135
|
+
error?: string;
|
|
136
|
+
clusterId?: string;
|
|
137
|
+
brokerCount?: number;
|
|
138
|
+
}>;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Options for readiness check
|
|
142
|
+
*/
|
|
143
|
+
interface ReadinessOptions {
|
|
144
|
+
/** Override default health check timeout */
|
|
145
|
+
timeout?: number;
|
|
146
|
+
/** Force revalidation, ignore cache */
|
|
147
|
+
force?: boolean;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Detailed connection status
|
|
151
|
+
*/
|
|
152
|
+
interface ConnectionStatus {
|
|
153
|
+
connected: boolean;
|
|
154
|
+
lastSuccessAt: number | null;
|
|
155
|
+
lastError: string | null;
|
|
156
|
+
}
|
|
54
157
|
//#endregion
|
|
55
158
|
//#region src/manager.d.ts
|
|
56
159
|
/**
|
|
57
160
|
* Manager for multiple Kafka producers
|
|
161
|
+
* Provides lifecycle management, health tracking, and operational guarantees
|
|
58
162
|
*/
|
|
59
163
|
declare class KafkaManager<TProducers extends string = string> {
|
|
60
164
|
private readonly producers;
|
|
61
165
|
private readonly logger;
|
|
62
166
|
private readonly enabled;
|
|
63
167
|
private gracefulShutdownStarted;
|
|
168
|
+
private fatalError;
|
|
169
|
+
private readonly healthCheckTimeoutMs;
|
|
170
|
+
private readonly healthCheckCacheMs;
|
|
171
|
+
private readonly bootstrapTimeoutMs;
|
|
172
|
+
private readonly strictBootstrap;
|
|
173
|
+
private lastReadinessCheck;
|
|
64
174
|
private constructor();
|
|
65
175
|
/**
|
|
66
176
|
* Create a new KafkaManager instance with multiple named producers
|
|
@@ -89,6 +199,55 @@ declare class KafkaManager<TProducers extends string = string> {
|
|
|
89
199
|
*/
|
|
90
200
|
private getProducer;
|
|
91
201
|
/**
|
|
202
|
+
* Explicitly bootstrap all (or specific) producers
|
|
203
|
+
* Connects to brokers, validates connectivity, and returns detailed results
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* // Bootstrap all producers with defaults (30s timeout, strict mode)
|
|
207
|
+
* await kafka.bootstrap();
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* // Bootstrap with custom timeout and non-strict mode
|
|
211
|
+
* const result = await kafka.bootstrap({ timeoutMs: 10000, strict: false });
|
|
212
|
+
* if (!result.success) {
|
|
213
|
+
* console.error('Some producers failed:', result.results);
|
|
214
|
+
* }
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* // Bootstrap specific producers only
|
|
218
|
+
* await kafka.bootstrap({ producers: ['main'] });
|
|
219
|
+
*/
|
|
220
|
+
bootstrap(options?: BootstrapOptions): Promise<BootstrapResult>;
|
|
221
|
+
/**
|
|
222
|
+
* Get detailed health snapshot for all producers
|
|
223
|
+
* Returns comprehensive metadata including timestamps, errors, cluster info
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* const health = kafka.getHealth();
|
|
227
|
+
* console.log(health.main.lastPingSucceededAt); // timestamp
|
|
228
|
+
* console.log(health.main.clusterId); // 'prod-kafka-01'
|
|
229
|
+
*/
|
|
230
|
+
getHealth(): Record<TProducers, ProducerHealth>;
|
|
231
|
+
/**
|
|
232
|
+
* Get detailed health for a specific producer
|
|
233
|
+
*/
|
|
234
|
+
getProducerHealth(name: TProducers): ProducerHealth;
|
|
235
|
+
/**
|
|
236
|
+
* Lightweight liveness check - does NOT perform Kafka operations
|
|
237
|
+
* Only checks internal state for fatal errors
|
|
238
|
+
* Suitable for Kubernetes liveness probes
|
|
239
|
+
*
|
|
240
|
+
* Returns false if:
|
|
241
|
+
* - Manager is in a fatal state
|
|
242
|
+
* - Graceful shutdown has started
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* app.get('/health/live', (req, res) => {
|
|
246
|
+
* res.status(kafka.isLive() ? 200 : 503).json({ live: kafka.isLive() });
|
|
247
|
+
* });
|
|
248
|
+
*/
|
|
249
|
+
isLive(): boolean;
|
|
250
|
+
/**
|
|
92
251
|
* Ping all producers to verify connectivity
|
|
93
252
|
*/
|
|
94
253
|
ping(): Promise<void>;
|
|
@@ -97,13 +256,26 @@ declare class KafkaManager<TProducers extends string = string> {
|
|
|
97
256
|
*/
|
|
98
257
|
pingProducer(name: TProducers): Promise<void>;
|
|
99
258
|
/**
|
|
100
|
-
* Check if
|
|
259
|
+
* Check if Kafka is ready to handle traffic
|
|
260
|
+
* Revalidates connectivity if cache is stale
|
|
261
|
+
*
|
|
262
|
+
* @param options.timeout - Override default health check timeout
|
|
263
|
+
* @param options.force - Force revalidation, ignore cache
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* // Use cached result if fresh (within healthCheckCacheMs)
|
|
267
|
+
* const ready = await kafka.isReady();
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* // Force immediate revalidation
|
|
271
|
+
* const ready = await kafka.isReady({ force: true });
|
|
101
272
|
*/
|
|
102
|
-
isReady(): Promise<boolean>;
|
|
273
|
+
isReady(options?: ReadinessOptions): Promise<boolean>;
|
|
103
274
|
/**
|
|
104
275
|
* Check connection status of all producers
|
|
276
|
+
* Returns detailed status including last success time and errors
|
|
105
277
|
*/
|
|
106
|
-
getConnectionStatus(): Record<TProducers,
|
|
278
|
+
getConnectionStatus(): Record<TProducers, ConnectionStatus>;
|
|
107
279
|
/**
|
|
108
280
|
* Publish a message using a named producer
|
|
109
281
|
*/
|
|
@@ -124,8 +296,8 @@ declare class KafkaManager<TProducers extends string = string> {
|
|
|
124
296
|
//#endregion
|
|
125
297
|
//#region src/kafkaError.d.ts
|
|
126
298
|
declare class KafkaError extends Error {
|
|
127
|
-
|
|
299
|
+
name: string;
|
|
128
300
|
}
|
|
129
301
|
//#endregion
|
|
130
|
-
export { KafkaError, KafkaManager, type KafkaManagerOptions, type ProducerConfig, type PublishBatchOptions, type PublishOptions, type RecordMetadata };
|
|
302
|
+
export { type BootstrapOptions, type BootstrapResult, type ConnectionStatus, KafkaError, KafkaManager, type KafkaManagerOptions, type ProducerConfig, type ProducerHealth, type PublishBatchOptions, type PublishOptions, type ReadinessOptions, type RecordMetadata };
|
|
131
303
|
//# sourceMappingURL=index.d.ts.map
|