1- import { checkServerIdentity } from "node:tls" ;
2- import { CompressionTypes , Kafka , type Producer } from "kafkajs" ;
3- import { compress , decompress } from "lz4js" ;
4-
5- // CompressionCodecs is not exported properly in kafkajs. Source: https://github.com/tulios/kafkajs/issues/1391
6- import KafkaJS from "kafkajs" ;
7- const { CompressionCodecs } = KafkaJS ;
1+ import {
2+ KafkaJS ,
3+ type ProducerGlobalConfig ,
4+ } from "@confluentinc/kafka-javascript" ;
85
96/**
107 * Reference: https://kafka.js.org/docs/producing#producing-messages
@@ -33,104 +30,73 @@ export interface KafkaProducerSendOptions {
3330 * ```
3431 */
3532export class KafkaProducer {
36- private kafka : Kafka ;
37- private producer : Producer | null = null ;
38- private compression : CompressionTypes ;
33+ private producer : KafkaJS . Producer ;
34+ private isConnected = false ;
3935
40- constructor ( config : {
36+ constructor ( options : {
4137 /**
4238 * A descriptive name for your service. Example: "storage-server"
4339 */
4440 producerName : string ;
4541 /**
46- * The environment the service is running in.
47- */
48- environment : "development" | "production" ;
49- /**
50- * Whether to compress the events.
42+ * A comma-separated list of `host[:port]` Kafka servers.
5143 */
52- shouldCompress ?: boolean ;
53-
44+ kafkaServers : string ;
5445 username : string ;
5546 password : string ;
47+
48+ /**
49+ * Configuration for the Kafka producer.
50+ */
51+ config ?: ProducerGlobalConfig ;
5652 } ) {
57- const {
58- producerName,
59- environment,
60- shouldCompress = true ,
61- username,
62- password,
63- } = config ;
53+ const { producerName, kafkaServers, username, password, config } = options ;
6454
65- this . kafka = new Kafka ( {
66- clientId : `${ producerName } -${ environment } ` ,
67- brokers :
68- environment === "production"
69- ? [ "warpstream.thirdweb.xyz:9092" ]
70- : [ "warpstream-dev.thirdweb.xyz:9092" ] ,
71- ssl : {
72- checkServerIdentity ( hostname , cert ) {
73- return checkServerIdentity ( hostname . toLowerCase ( ) , cert ) ;
74- } ,
75- } ,
76- sasl : {
77- mechanism : "plain" ,
78- username,
79- password,
80- } ,
55+ this . producer = new KafkaJS . Kafka ( { } ) . producer ( {
56+ "client.id" : producerName ,
57+ "bootstrap.servers" : kafkaServers ,
58+ "security.protocol" : "sasl_ssl" ,
59+ "sasl.mechanisms" : "PLAIN" ,
60+ "sasl.username" : username ,
61+ "sasl.password" : password ,
62+ "compression.codec" : "lz4" ,
63+ "allow.auto.create.topics" : true ,
64+ // All configuration can be overridden.
65+ ...config ,
8166 } ) ;
67+ }
8268
83- if ( shouldCompress ) {
84- this . compression = CompressionTypes . LZ4 ;
85-
86- CompressionCodecs [ CompressionTypes . LZ4 ] = ( ) => ( {
87- // biome-ignore lint/style/noRestrictedGlobals: kafkajs expects a Buffer
88- compress : ( encoder : { buffer : Buffer } ) => {
89- const compressed = compress ( encoder . buffer ) ;
90- // biome-ignore lint/style/noRestrictedGlobals: kafkajs expects a Buffer
91- return Buffer . from ( compressed ) ;
92- } ,
93- // biome-ignore lint/style/noRestrictedGlobals: kafkajs expects a Buffer
94- decompress : ( buffer : Buffer ) => {
95- const decompressed = decompress ( buffer ) ;
96- // biome-ignore lint/style/noRestrictedGlobals: kafkajs expects a Buffer
97- return Buffer . from ( decompressed ) ;
98- } ,
99- } ) ;
100- } else {
101- this . compression = CompressionTypes . None ;
102- }
69+ /**
70+ * Connects the producer. Can be called explicitly at the start of your service, or will be called automatically when sending messages.
71+ */
72+ async connect ( ) {
73+ await this . producer . connect ( ) ;
74+ this . isConnected = true ;
10375 }
10476
10577 /**
10678 * Send messages to a Kafka topic.
10779 * This method may throw. To call this non-blocking:
80+ * ```ts
81+ * void kafka.send(topic, events).catch((e) => console.error(e))
82+ * ```
83+ *
10884 * @param topic
10985 * @param messages
110- * @param configOverrides
11186 */
11287 async send (
11388 topic : string ,
11489 messages : Record < string , unknown > [ ] ,
115- options ?: KafkaProducerSendOptions ,
11690 ) : Promise < void > {
117- if ( ! this . producer ) {
118- this . producer = this . kafka . producer ( {
119- allowAutoTopicCreation : options ?. allowAutoTopicCreation ?? false ,
120- maxInFlightRequests : options ?. maxInFlightRequests ?? 2000 ,
121- retry : { retries : options ?. retries ?? 5 } ,
122- } ) ;
123- await this . producer . connect ( ) ;
91+ if ( ! this . isConnected ) {
92+ await this . connect ( ) ;
12493 }
12594
12695 await this . producer . send ( {
12796 topic,
12897 messages : messages . map ( ( m ) => ( {
12998 value : JSON . stringify ( m ) ,
13099 } ) ) ,
131- compression : this . compression ,
132- acks : options ?. acks ?? - 1 , // Default: All brokers must acknowledge
133- timeout : options ?. timeout ?? 10_000 , // Default: 10 seconds
134100 } ) ;
135101 }
136102
@@ -139,9 +105,12 @@ export class KafkaProducer {
139105 * Useful when shutting down the service to flush in-flight events.
140106 */
141107 async disconnect ( ) {
142- if ( this . producer ) {
143- await this . producer . disconnect ( ) ;
144- this . producer = null ;
108+ if ( this . isConnected ) {
109+ try {
110+ await this . producer . flush ( ) ;
111+ await this . producer . disconnect ( ) ;
112+ } catch { }
113+ this . isConnected = false ;
145114 }
146115 }
147116}
0 commit comments