kafka-crab-js 0.3.1 → 0.5.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/.yarnrc.yml CHANGED
@@ -1,3 +1,7 @@
1
+ compressionLevel: mixed
2
+
3
+ enableGlobalCache: false
4
+
1
5
  nodeLinker: node-modules
2
6
 
3
- yarnPath: .yarn/releases/yarn-3.6.0.cjs
7
+ yarnPath: .yarn/releases/yarn-4.1.1.cjs
package/Cargo.toml CHANGED
@@ -8,20 +8,19 @@ crate-type = ["cdylib"]
8
8
 
9
9
  [dependencies]
10
10
  # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
11
- napi = { version = "2", default-features = false, features = [
11
+ napi = { version = "2.16", default-features = false, features = [
12
12
  "napi8",
13
13
  "tokio_rt",
14
14
  "serde-json",
15
15
  ] }
16
- napi-derive = "2"
16
+ napi-derive = "2.16"
17
17
 
18
18
  anyhow = { version = "1.0", features = ["backtrace"] }
19
19
 
20
- rdkafka = { version = "0.34", features = [
20
+ rdkafka = { version = "0.36", features = [
21
21
  "libz-static",
22
22
  "cmake-build",
23
23
  "tokio",
24
- "ssl",
25
24
  "ssl-vendored",
26
25
  ] }
27
26
  tokio = { version = "1", features = ["full"] }
@@ -34,3 +33,4 @@ napi-build = "2.0.1"
34
33
 
35
34
  [profile.release]
36
35
  lto = true
36
+ strip = "symbols"
package/README.md CHANGED
@@ -72,5 +72,4 @@ consumer.startConsumer(async (error, {value, partition, offset}) => {
72
72
  });
73
73
  ```
74
74
  ## Examples
75
-
76
75
  You can find examples of using Kafka Crab JS in the [GitHub repository](https://github.com/inaiat/kafka-crab-js).
@@ -1,14 +1,17 @@
1
1
  /* eslint-disable no-await-in-loop */
2
2
 
3
- import timersPromises from 'node:timers/promises';
3
+ // import timersPromises from 'node:timers/promises';
4
4
  import { Buffer } from 'node:buffer';
5
5
  import { nanoid } from 'nanoid';
6
- import { KafkaClient, CommitMode, PartitionPosition, ConsumerResult } from '../index.js';
6
+ import {
7
+ KafkaClient, CommitMode, PartitionPosition, ConsumerResult,
8
+ } from '../index.js';
7
9
 
8
10
  const kafkaClient = new KafkaClient({
9
11
  brokers: 'localhost:29092',
10
12
  clientId: 'my-js-group',
11
13
  logLevel: 'info',
14
+ brokerAddressFamily: 'v4',
12
15
  });
13
16
  const topic = `topic-${nanoid()}`;
14
17
 
package/index.d.ts CHANGED
@@ -3,19 +3,6 @@
3
3
 
4
4
  /* auto-generated by NAPI-RS */
5
5
 
6
- export const enum SecurityProtocol {
7
- Plaintext = 'Plaintext',
8
- Ssl = 'Ssl',
9
- SaslPlaintext = 'SaslPlaintext',
10
- SaslSsl = 'SaslSsl'
11
- }
12
- export interface KafkaConfiguration {
13
- brokers: string
14
- clientId: string
15
- securityProtocol?: SecurityProtocol
16
- configuration?: Record<string, string>
17
- logLevel?: string
18
- }
19
6
  export const enum CommitMode {
20
7
  AutoCommit = 'AutoCommit',
21
8
  Sync = 'Sync',
@@ -43,6 +30,20 @@ export interface ConsumerConfiguration {
43
30
  enableAutoCommit?: boolean
44
31
  configuration?: Record<string, string>
45
32
  }
33
+ export const enum SecurityProtocol {
34
+ Plaintext = 'Plaintext',
35
+ Ssl = 'Ssl',
36
+ SaslPlaintext = 'SaslPlaintext',
37
+ SaslSsl = 'SaslSsl'
38
+ }
39
+ export interface KafkaConfiguration {
40
+ brokers: string
41
+ clientId: string
42
+ securityProtocol?: SecurityProtocol
43
+ configuration?: Record<string, string>
44
+ logLevel?: string
45
+ brokerAddressFamily?: string
46
+ }
46
47
  export interface ProducerConfiguration {
47
48
  queueTimeout?: number
48
49
  thrownOnError?: boolean
@@ -70,9 +71,9 @@ export interface Payload {
70
71
  offset: number
71
72
  }
72
73
  export const enum PartitionPosition {
73
- Beginning = 0,
74
- End = 1,
75
- Stored = 2
74
+ Beginning = 'Beginning',
75
+ End = 'End',
76
+ Stored = 'Stored'
76
77
  }
77
78
  export interface OffsetModel {
78
79
  offset?: number
@@ -93,16 +94,15 @@ export interface MessageModel {
93
94
  key?: Buffer
94
95
  headers?: Record<string, Buffer>
95
96
  }
97
+ export class KafkaConsumer {
98
+ startConsumer(callback: (error: Error | undefined, result: Payload) => Promise<ConsumerResult | undefined>): Promise<void>
99
+ }
96
100
  export class KafkaClient {
97
101
  readonly kafkaConfiguration: KafkaConfiguration
98
102
  constructor(kafkaConfiguration: KafkaConfiguration)
99
103
  createProducer(producerConfiguration: ProducerConfiguration): KafkaProducer
100
104
  createConsumer(consumerConfiguration: ConsumerConfiguration): KafkaConsumer
101
105
  }
102
- export class ProducerHelper { }
103
- export class KafkaConsumer {
104
- startConsumer(callback: (error: Error | undefined, result: Payload) => Promise<ConsumerResult | undefined>): Promise<void>
105
- }
106
106
  export class KafkaProducer {
107
107
  send(producerRecord: ProducerRecord): Promise<Array<RecordMetadata>>
108
108
  }
package/index.js CHANGED
@@ -237,6 +237,49 @@ switch (platform) {
237
237
  loadError = e
238
238
  }
239
239
  break
240
+ case 'riscv64':
241
+ if (isMusl()) {
242
+ localFileExisted = existsSync(
243
+ join(__dirname, 'kafka-crab-js.linux-riscv64-musl.node')
244
+ )
245
+ try {
246
+ if (localFileExisted) {
247
+ nativeBinding = require('./kafka-crab-js.linux-riscv64-musl.node')
248
+ } else {
249
+ nativeBinding = require('kafka-crab-js-linux-riscv64-musl')
250
+ }
251
+ } catch (e) {
252
+ loadError = e
253
+ }
254
+ } else {
255
+ localFileExisted = existsSync(
256
+ join(__dirname, 'kafka-crab-js.linux-riscv64-gnu.node')
257
+ )
258
+ try {
259
+ if (localFileExisted) {
260
+ nativeBinding = require('./kafka-crab-js.linux-riscv64-gnu.node')
261
+ } else {
262
+ nativeBinding = require('kafka-crab-js-linux-riscv64-gnu')
263
+ }
264
+ } catch (e) {
265
+ loadError = e
266
+ }
267
+ }
268
+ break
269
+ case 's390x':
270
+ localFileExisted = existsSync(
271
+ join(__dirname, 'kafka-crab-js.linux-s390x-gnu.node')
272
+ )
273
+ try {
274
+ if (localFileExisted) {
275
+ nativeBinding = require('./kafka-crab-js.linux-s390x-gnu.node')
276
+ } else {
277
+ nativeBinding = require('kafka-crab-js-linux-s390x-gnu')
278
+ }
279
+ } catch (e) {
280
+ loadError = e
281
+ }
282
+ break
240
283
  default:
241
284
  throw new Error(`Unsupported architecture on Linux: ${arch}`)
242
285
  }
@@ -252,14 +295,13 @@ if (!nativeBinding) {
252
295
  throw new Error(`Failed to load native binding`)
253
296
  }
254
297
 
255
- const { SecurityProtocol, KafkaClient, CommitMode, ConsumerResult, ProducerHelper, KafkaConsumer, KafkaProducer, AutoOffsetReset, PartitionPosition } = nativeBinding
298
+ const { CommitMode, ConsumerResult, KafkaConsumer, SecurityProtocol, KafkaClient, KafkaProducer, AutoOffsetReset, PartitionPosition } = nativeBinding
256
299
 
257
- module.exports.SecurityProtocol = SecurityProtocol
258
- module.exports.KafkaClient = KafkaClient
259
300
  module.exports.CommitMode = CommitMode
260
301
  module.exports.ConsumerResult = ConsumerResult
261
- module.exports.ProducerHelper = ProducerHelper
262
302
  module.exports.KafkaConsumer = KafkaConsumer
303
+ module.exports.SecurityProtocol = SecurityProtocol
304
+ module.exports.KafkaClient = KafkaClient
263
305
  module.exports.KafkaProducer = KafkaProducer
264
306
  module.exports.AutoOffsetReset = AutoOffsetReset
265
307
  module.exports.PartitionPosition = PartitionPosition
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kafka-crab-js",
3
- "version": "0.3.1",
3
+ "version": "0.5.4",
4
4
  "main": "index.js",
5
5
  "types": "index.d.ts",
6
6
  "napi": {
@@ -24,19 +24,20 @@
24
24
  ],
25
25
  "author": "inaiat@gmail.com",
26
26
  "homepage": "https://github.com/inaiat/kafka-crab-js.git",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/inaiat/kafka-crab-js.git"
30
+ },
27
31
  "devDependencies": {
28
- "@napi-rs/cli": "^2.16.3",
29
- "ava": "^5.3.1",
30
- "benny": "^3.7.1",
31
- "kafkajs": "^2.2.4",
32
- "nanoid": "^4.0.2",
33
- "xo": "^0.54.2"
32
+ "@napi-rs/cli": "^2.18.0",
33
+ "ava": "^6.1.2",
34
+ "xo": "^0.58.0"
34
35
  },
35
36
  "ava": {
36
37
  "timeout": "3m"
37
38
  },
38
39
  "engines": {
39
- "node": ">= 10"
40
+ "node": ">= 18"
40
41
  },
41
42
  "xo": {
42
43
  "space": true,
@@ -54,10 +55,10 @@
54
55
  "universal": "napi universal",
55
56
  "version": "napi version"
56
57
  },
57
- "packageManager": "yarn@3.6.0",
58
+ "packageManager": "yarn@4.1.1",
58
59
  "optionalDependencies": {
59
- "kafka-crab-js-darwin-arm64": "0.3.1",
60
- "kafka-crab-js-darwin-x64": "0.3.1",
61
- "kafka-crab-js-linux-x64-gnu": "0.3.1"
60
+ "kafka-crab-js-darwin-arm64": "0.5.4",
61
+ "kafka-crab-js-darwin-x64": "0.5.4",
62
+ "kafka-crab-js-linux-x64-gnu": "0.5.4"
62
63
  }
63
64
  }
@@ -0,0 +1,29 @@
1
+ use rdkafka::consumer::StreamConsumer;
2
+ use tracing::info;
3
+
4
+ use rdkafka::{
5
+ client::ClientContext,
6
+ consumer::{ConsumerContext, Rebalance},
7
+ error::KafkaResult,
8
+ topic_partition_list::TopicPartitionList,
9
+ };
10
+
11
+ pub type LoggingConsumer = StreamConsumer<CustomContext>;
12
+
13
+ pub struct CustomContext;
14
+
15
+ impl ClientContext for CustomContext {}
16
+
17
+ impl ConsumerContext for CustomContext {
18
+ fn pre_rebalance(&self, rebalance: &Rebalance) {
19
+ info!("Pre rebalance {:?}", rebalance);
20
+ }
21
+
22
+ fn post_rebalance(&self, rebalance: &Rebalance) {
23
+ info!("Post rebalance {:?}", rebalance);
24
+ }
25
+
26
+ fn commit_callback(&self, result: KafkaResult<()>, _offsets: &TopicPartitionList) {
27
+ info!("Committing offsets: {:?}. Offset: {:?}", result, _offsets);
28
+ }
29
+ }
@@ -0,0 +1,196 @@
1
+ use napi::{bindgen_prelude::*, threadsafe_function::ThreadsafeFunction};
2
+
3
+ use rdkafka::{
4
+ consumer::{stream_consumer::StreamConsumer, CommitMode as RdKfafkaCommitMode, Consumer},
5
+ message::{BorrowedMessage, Header, OwnedHeaders},
6
+ producer::FutureRecord,
7
+ Message,
8
+ };
9
+
10
+ use std::time::Duration;
11
+ use tracing::{debug, error, info, warn};
12
+
13
+ use crate::kafka::{
14
+ consumer::kafka_consumer::RETRY_COUNTER_NAME,
15
+ kafka_util::{
16
+ kakfa_headers_to_hashmap, kakfa_headers_to_hashmap_buffer, ExtractValueOnKafkaHashMap,
17
+ },
18
+ model::Payload,
19
+ };
20
+
21
+ use super::kafka_consumer::{ConsumerResult, ProducerHelper};
22
+
23
+ use super::consumer_context::CustomContext;
24
+
25
+ pub struct ConsumerThread {
26
+ pub stream_consumer: StreamConsumer<CustomContext>,
27
+ pub func: ThreadsafeFunction<Payload>,
28
+ pub commit_mode: Option<RdKfafkaCommitMode>,
29
+ pub retries: i32,
30
+ pub next_topic_on_fail: Option<String>,
31
+ pub producer: ProducerHelper,
32
+ pub pause_consumer_duration: Option<Duration>,
33
+ }
34
+
35
+ impl ConsumerThread {
36
+ pub async fn start(&self) {
37
+ loop {
38
+ if let Some(duration) = &self.pause_consumer_duration {
39
+ tracing::debug!("Pause consumer for {}", duration.as_millis());
40
+ tokio::time::sleep(*duration).await;
41
+ };
42
+ match self.stream_consumer.recv().await {
43
+ Err(e) => error!("Error while receiving from stream consumer: {:?}", e),
44
+ Ok(message) => {
45
+ let message_result = self.process_message(&message).await;
46
+ match self.handle_message_result(&message, message_result).await {
47
+ Ok(_) => {
48
+ debug!(
49
+ "Message consumed successfully. Topic: {}, Partition: {}, Offset: {}",
50
+ message.topic(),
51
+ message.partition(),
52
+ message.offset()
53
+ );
54
+ }
55
+ Err(err) => {
56
+ error!("Message processing failed. Error: {:?}", err);
57
+ }
58
+ }
59
+ }
60
+ };
61
+ }
62
+ }
63
+
64
+ async fn process_message(
65
+ &self,
66
+ message: &BorrowedMessage<'_>,
67
+ ) -> anyhow::Result<Option<ConsumerResult>> {
68
+ match message.payload_view::<[u8]>() {
69
+ None => Ok(None),
70
+ Some(Ok(payload)) => {
71
+ let payload_js = create_payload(message, payload);
72
+ match self
73
+ .func
74
+ .call_async::<Promise<Option<ConsumerResult>>>(Ok(payload_js))
75
+ .await
76
+ {
77
+ Ok(js_result) => match js_result.await {
78
+ Ok(value) => match value {
79
+ None | Some(ConsumerResult::Ok) => {
80
+ if let Some(commit_mode) = self.commit_mode {
81
+ match self.stream_consumer.commit_message(message, commit_mode) {
82
+ Ok(_) => Ok(Some(ConsumerResult::Ok)),
83
+ Err(e) => Err(anyhow::Error::new(e)),
84
+ }
85
+ } else {
86
+ Ok(Some(ConsumerResult::Ok))
87
+ }
88
+ }
89
+ Some(ConsumerResult::Retry) => Ok(Some(ConsumerResult::Retry)),
90
+ },
91
+ Err(_) => {
92
+ warn!("Error while calling return function");
93
+ Ok(Some(ConsumerResult::Ok))
94
+ }
95
+ },
96
+ Err(err) => {
97
+ warn!("Error while calling function: {:?}", err.to_string());
98
+ Ok(Some(ConsumerResult::Retry))
99
+ }
100
+ }
101
+ }
102
+ Some(_) => Err(anyhow::Error::msg(
103
+ "Error while deserializing message payload",
104
+ )),
105
+ }
106
+ }
107
+
108
+ async fn handle_message_result(
109
+ &self,
110
+ message: &BorrowedMessage<'_>,
111
+ message_result: anyhow::Result<Option<ConsumerResult>>,
112
+ ) -> anyhow::Result<()> {
113
+ match message_result {
114
+ Ok(consumer_result) => match consumer_result {
115
+ Some(result) => match result {
116
+ ConsumerResult::Ok => Ok(()),
117
+ ConsumerResult::Retry => match self.send_to_retry_strategy(message).await {
118
+ Ok(_) => {
119
+ debug!("Message sent to retry strategy");
120
+ Ok(())
121
+ }
122
+ Err(e) => Err(anyhow::Error::msg(format!(
123
+ "Error while sending message to retry strategy: {:?}",
124
+ e
125
+ ))),
126
+ },
127
+ },
128
+ None => Ok(()),
129
+ },
130
+ Err(err) => Err(err),
131
+ }
132
+ }
133
+
134
+ async fn send_to_retry_strategy(&self, message: &BorrowedMessage<'_>) -> anyhow::Result<()> {
135
+ let retry_counter: usize = {
136
+ let headers_map = kakfa_headers_to_hashmap(message.headers());
137
+ let mut counter = headers_map.get_value(RETRY_COUNTER_NAME).unwrap_or(1);
138
+ if self.retries > 0 {
139
+ counter += 1;
140
+ }
141
+ counter
142
+ };
143
+
144
+ let next_topic = if self.retries > 0 && retry_counter <= self.retries.try_into().unwrap() {
145
+ Some(message.topic().to_string())
146
+ } else {
147
+ self.next_topic_on_fail.clone()
148
+ };
149
+
150
+ match next_topic {
151
+ Some(next_topic) => {
152
+ info!("Message sent to next topic: {}", next_topic);
153
+ let new_headers = message
154
+ .headers()
155
+ .unwrap_or(OwnedHeaders::new().as_borrowed())
156
+ .detach()
157
+ .insert(Header {
158
+ key: RETRY_COUNTER_NAME,
159
+ value: Some(retry_counter.to_string().as_str()),
160
+ });
161
+ let key = message.key().unwrap_or(&[]);
162
+
163
+ let record = FutureRecord::to(&next_topic)
164
+ .key(key)
165
+ .headers(new_headers)
166
+ .payload(message.payload().unwrap_or(&[]));
167
+
168
+ let _result = self
169
+ .producer
170
+ .future_producer
171
+ .send(record, self.producer.queue_timeout)
172
+ .await
173
+ .map_err(|e| anyhow::Error::new(e.0))?;
174
+ Ok(())
175
+ }
176
+ None => {
177
+ debug!("There is no next topic. Commit message");
178
+ Ok(())
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ fn create_payload(message: &BorrowedMessage<'_>, payload: &[u8]) -> Payload {
185
+ let key: Option<Buffer> = message.key().map(|bytes| bytes.into());
186
+ let headers = Some(kakfa_headers_to_hashmap_buffer(message.headers()));
187
+ let payload_js = Payload::new(
188
+ payload.into(),
189
+ key,
190
+ headers,
191
+ message.topic().to_owned(),
192
+ message.partition(),
193
+ message.offset(),
194
+ );
195
+ payload_js
196
+ }
@@ -1,58 +1,33 @@
1
- use napi::{bindgen_prelude::*, threadsafe_function::ThreadsafeFunction, Result};
1
+ use napi::{threadsafe_function::ThreadsafeFunction, Result};
2
2
 
3
3
  use rdkafka::{
4
- client::ClientContext,
5
4
  config::RDKafkaLogLevel,
6
- consumer::{
7
- stream_consumer::StreamConsumer, CommitMode as RdKfafkaCommitMode, Consumer, ConsumerContext,
8
- Rebalance,
9
- },
10
- error::KafkaResult,
11
- message::{BorrowedMessage, Header, OwnedHeaders},
12
- producer::{FutureProducer, FutureRecord},
5
+ consumer::{stream_consumer::StreamConsumer, CommitMode as RdKfafkaCommitMode, Consumer},
6
+ producer::FutureProducer,
13
7
  topic_partition_list::TopicPartitionList,
14
- ClientConfig, Message,
8
+ ClientConfig,
15
9
  };
16
10
 
17
11
  use std::{collections::HashMap, time::Duration, vec};
18
12
  use tracing::{debug, error, info, warn};
19
13
 
20
- use crate::kafka::kafka_util::{
21
- convert_to_rdkafka_offset, kakfa_headers_to_hashmap, ExtractValueOnKafkaHashMap,
22
- };
23
-
24
- use super::{
14
+ use crate::kafka::{
25
15
  kafka_admin::KafkaAdmin,
26
16
  kafka_client::KafkaClient,
27
- kafka_util::{kakfa_headers_to_hashmap_buffer, AnyhowToNapiError},
17
+ kafka_util::{convert_to_rdkafka_offset, AnyhowToNapiError},
28
18
  model::{OffsetModel, PartitionPosition, Payload},
29
19
  };
30
20
 
31
- const RETRY_COUNTER_NAME: &str = "kafka-crab-js-retry-counter";
32
- const DEFAULT_QUEUE_TIMEOUT: u64 = 5000;
33
- const DEFAULT_PAUSE_DURATION: i64 = 1000;
34
- const DEFAULT_RETRY_TOPIC_SUFFIX: &str = "-retry";
35
- const DEFAULT_DLQ_TOPIC_SUFFIX: &str = "-dlq";
36
-
37
- type LoggingConsumer = StreamConsumer<CustomContext>;
38
-
39
- pub struct CustomContext;
40
-
41
- impl ClientContext for CustomContext {}
42
-
43
- impl ConsumerContext for CustomContext {
44
- fn pre_rebalance(&self, rebalance: &Rebalance) {
45
- info!("Pre rebalance {:?}", rebalance);
46
- }
47
-
48
- fn post_rebalance(&self, rebalance: &Rebalance) {
49
- info!("Post rebalance {:?}", rebalance);
50
- }
21
+ use super::{
22
+ consumer_context::{CustomContext, LoggingConsumer},
23
+ consumer_thread::ConsumerThread,
24
+ };
51
25
 
52
- fn commit_callback(&self, result: KafkaResult<()>, _offsets: &TopicPartitionList) {
53
- info!("Committing offsets: {:?}. Offset: {:?}", result, _offsets);
54
- }
55
- }
26
+ pub const RETRY_COUNTER_NAME: &str = "kafka-crab-js-retry-counter";
27
+ pub const DEFAULT_QUEUE_TIMEOUT: u64 = 5000;
28
+ pub const DEFAULT_PAUSE_DURATION: i64 = 1000;
29
+ pub const DEFAULT_RETRY_TOPIC_SUFFIX: &str = "-retry";
30
+ pub const DEFAULT_DLQ_TOPIC_SUFFIX: &str = "-dlq";
56
31
 
57
32
  #[napi(string_enum)]
58
33
  #[derive(Debug, PartialEq)]
@@ -81,10 +56,9 @@ pub enum ConsumerResult {
81
56
  }
82
57
 
83
58
  #[derive(Clone)]
84
- #[napi]
85
59
  pub struct ProducerHelper {
86
- future_producer: FutureProducer,
87
- queue_timeout: Duration,
60
+ pub future_producer: FutureProducer,
61
+ pub queue_timeout: Duration,
88
62
  }
89
63
 
90
64
  fn setup_future_producer(
@@ -149,6 +123,11 @@ impl KafkaConsumer {
149
123
  })
150
124
  }
151
125
 
126
+ // #[napi(ts_args_type = "callback: (error: Error) => Promise<ConsumerResult | undefined>")]
127
+ // pub async fn create_consumer(&self) -> Result<()> {
128
+ // Ok(())
129
+ // }
130
+
152
131
  #[napi(
153
132
  ts_args_type = "callback: (error: Error | undefined, result: Payload) => Promise<ConsumerResult | undefined>"
154
133
  )]
@@ -286,29 +265,46 @@ impl KafkaConsumer {
286
265
  enable_auto_commit.unwrap_or(true).to_string(),
287
266
  )
288
267
  .set_log_level(RDKafkaLogLevel::Debug)
289
- .create_with_context(context)
290
- .expect("Consumer creation failed");
268
+ .create_with_context(context)?;
291
269
 
292
270
  if create_topic.unwrap_or(true) {
271
+ info!("Creating topic: {:?}", topic);
293
272
  let admin = KafkaAdmin::new(&self.client_config);
294
- admin.create_topic(topic).await?;
273
+ let result = admin.create_topic(topic).await;
274
+ if let Err(e) = result {
275
+ warn!("Fail to create topic {:?}", e);
276
+ return Err(anyhow::Error::msg(format!("Fail to create topic: {:?}", e)));
277
+ }
278
+ info!("Topic created: {:?}", topic)
295
279
  }
296
280
 
297
281
  if let Some(offset) = convert_to_rdkafka_offset(offset) {
298
282
  debug!("Setting offset to: {:?}", offset);
299
- let metadata = consumer
300
- .fetch_metadata(Some(topic), Duration::from_millis(1500))
301
- .expect("Fail to retrive metadata from consumer");
283
+ let metadata = consumer.fetch_metadata(Some(topic), Duration::from_millis(1500))?;
302
284
 
303
285
  metadata.topics().iter().for_each(|meta_topic| {
304
286
  let mut tpl = TopicPartitionList::new();
305
287
  meta_topic.partitions().iter().for_each(|meta_partition| {
306
288
  tpl.add_partition(topic, meta_partition.id());
307
289
  });
308
- tpl.set_all_offsets(offset).expect("Fail to set offset");
309
- consumer
310
- .assign(&tpl)
311
- .expect("Assign topic partition list failed");
290
+ //TODO: Handle error
291
+ match tpl.set_all_offsets(offset) {
292
+ Ok(_) => {
293
+ debug!("Offset set to: {:?}", offset);
294
+ }
295
+ Err(e) => {
296
+ error!("Fail to set offset: {:?}", e)
297
+ }
298
+ };
299
+ //TODO: Handle error
300
+ match consumer.assign(&tpl) {
301
+ Ok(_) => {
302
+ debug!("Assigning topic: {:?}", topic);
303
+ }
304
+ Err(e) => {
305
+ error!("Fail to assign topic: {:?}", e);
306
+ }
307
+ }
312
308
  });
313
309
  }
314
310
 
@@ -328,176 +324,3 @@ impl KafkaConsumer {
328
324
  Ok(consumer)
329
325
  }
330
326
  }
331
-
332
- pub struct ConsumerThread {
333
- stream_consumer: StreamConsumer<CustomContext>,
334
- func: ThreadsafeFunction<Payload>,
335
- commit_mode: Option<RdKfafkaCommitMode>,
336
- retries: i32,
337
- next_topic_on_fail: Option<String>,
338
- producer: ProducerHelper,
339
- pause_consumer_duration: Option<Duration>,
340
- }
341
-
342
- impl ConsumerThread {
343
- pub async fn start(&self) {
344
- loop {
345
- if let Some(duration) = &self.pause_consumer_duration {
346
- tracing::debug!("Pause consumer for {}", duration.as_millis());
347
- tokio::time::sleep(*duration).await;
348
- };
349
- match self.stream_consumer.recv().await {
350
- Err(e) => error!("Error while receiving from stream consumer: {:?}", e),
351
- Ok(message) => {
352
- let message_result = self.process_message(&message).await;
353
- match self.handle_message_result(&message, message_result).await {
354
- Ok(_) => {
355
- debug!(
356
- "Message consumed successfully. Topic: {}, Partition: {}, Offset: {}",
357
- message.topic(),
358
- message.partition(),
359
- message.offset()
360
- );
361
- }
362
- Err(err) => {
363
- error!("Message processing failed. Error: {:?}", err);
364
- }
365
- }
366
- }
367
- };
368
- }
369
- }
370
-
371
- async fn process_message(
372
- &self,
373
- message: &BorrowedMessage<'_>,
374
- ) -> anyhow::Result<Option<ConsumerResult>> {
375
- match message.payload_view::<[u8]>() {
376
- None => Ok(None),
377
- Some(Ok(payload)) => {
378
- let payload_js = create_payload(message, payload);
379
- match self
380
- .func
381
- .call_async::<Promise<Option<ConsumerResult>>>(Ok(payload_js))
382
- .await
383
- {
384
- Ok(js_result) => match js_result.await {
385
- Ok(value) => match value {
386
- None | Some(ConsumerResult::Ok) => {
387
- if let Some(commit_mode) = self.commit_mode {
388
- match self.stream_consumer.commit_message(message, commit_mode) {
389
- Ok(_) => Ok(Some(ConsumerResult::Ok)),
390
- Err(e) => Err(anyhow::Error::new(e)),
391
- }
392
- } else {
393
- Ok(Some(ConsumerResult::Ok))
394
- }
395
- }
396
- Some(ConsumerResult::Retry) => Ok(Some(ConsumerResult::Retry)),
397
- },
398
- Err(_) => {
399
- warn!("Error while calling return function");
400
- Ok(Some(ConsumerResult::Ok))
401
- }
402
- },
403
- Err(err) => {
404
- warn!("Error while calling function: {:?}", err.to_string());
405
- Ok(Some(ConsumerResult::Retry))
406
- }
407
- }
408
- }
409
- Some(_) => Err(anyhow::Error::msg(
410
- "Error while deserializing message payload",
411
- )),
412
- }
413
- }
414
-
415
- async fn handle_message_result(
416
- &self,
417
- message: &BorrowedMessage<'_>,
418
- message_result: anyhow::Result<Option<ConsumerResult>>,
419
- ) -> anyhow::Result<()> {
420
- match message_result {
421
- Ok(consumer_result) => match consumer_result {
422
- Some(result) => match result {
423
- ConsumerResult::Ok => Ok(()),
424
- ConsumerResult::Retry => match self.send_to_retry_strategy(message).await {
425
- Ok(_) => {
426
- debug!("Message sent to retry strategy");
427
- Ok(())
428
- }
429
- Err(e) => Err(anyhow::Error::msg(format!(
430
- "Error while sending message to retry strategy: {:?}",
431
- e
432
- ))),
433
- },
434
- },
435
- None => Ok(()),
436
- },
437
- Err(err) => Err(err),
438
- }
439
- }
440
-
441
- async fn send_to_retry_strategy(&self, message: &BorrowedMessage<'_>) -> anyhow::Result<()> {
442
- let retry_counter: usize = {
443
- let headers_map = kakfa_headers_to_hashmap(message.headers());
444
- let mut counter = headers_map.get_value(RETRY_COUNTER_NAME).unwrap_or(1);
445
- if self.retries > 0 {
446
- counter += 1;
447
- }
448
- counter
449
- };
450
-
451
- let next_topic = if self.retries > 0 && retry_counter <= self.retries.try_into().unwrap() {
452
- Some(message.topic().to_string())
453
- } else {
454
- self.next_topic_on_fail.clone()
455
- };
456
-
457
- match next_topic {
458
- Some(next_topic) => {
459
- info!("Message sent to next topic: {}", next_topic);
460
- let new_headers = message
461
- .headers()
462
- .unwrap_or(OwnedHeaders::new().as_borrowed())
463
- .detach()
464
- .insert(Header {
465
- key: RETRY_COUNTER_NAME,
466
- value: Some(retry_counter.to_string().as_str()),
467
- });
468
- let key = message.key().unwrap_or(&[]);
469
-
470
- let record = FutureRecord::to(&next_topic)
471
- .key(key)
472
- .headers(new_headers)
473
- .payload(message.payload().unwrap_or(&[]));
474
-
475
- let _result = self
476
- .producer
477
- .future_producer
478
- .send(record, self.producer.queue_timeout)
479
- .await
480
- .map_err(|e| anyhow::Error::new(e.0))?;
481
- Ok(())
482
- }
483
- None => {
484
- debug!("There is no next topic. Commit message");
485
- Ok(())
486
- }
487
- }
488
- }
489
- }
490
-
491
- fn create_payload(message: &BorrowedMessage<'_>, payload: &[u8]) -> Payload {
492
- let key: Option<Buffer> = message.key().map(|bytes| bytes.into());
493
- let headers = Some(kakfa_headers_to_hashmap_buffer(message.headers()));
494
- let payload_js = Payload::new(
495
- payload.into(),
496
- key,
497
- headers,
498
- message.topic().to_owned(),
499
- message.partition(),
500
- message.offset(),
501
- );
502
- payload_js
503
- }
@@ -0,0 +1,3 @@
1
+ pub mod consumer_context;
2
+ pub mod consumer_thread;
3
+ pub mod kafka_consumer;
@@ -33,14 +33,9 @@ impl<'a> KafkaAdmin<'a> {
33
33
  }
34
34
 
35
35
  async fn fetch_config_resource(&self) -> Result<HashMap<String, String>, KafkaError> {
36
- let consumer: BaseConsumer = self
37
- .client_config
38
- .create()
39
- .expect("Consumer creation failed");
36
+ let consumer: BaseConsumer = self.client_config.create()?;
40
37
 
41
- let metadata = consumer
42
- .fetch_metadata(None, Duration::from_secs(5))
43
- .expect("Failed to fetch metadata");
38
+ let metadata = consumer.fetch_metadata(None, Duration::from_secs(5))?;
44
39
 
45
40
  for broker in metadata.brokers() {
46
41
  info!(
@@ -63,9 +58,9 @@ impl<'a> KafkaAdmin<'a> {
63
58
  }
64
59
 
65
60
  pub async fn create_topic(&self, topic_name: &str) -> anyhow::Result<()> {
61
+ debug!("Fetching broker properties");
66
62
  let broker_properties = self.fetch_config_resource().await?.clone();
67
-
68
- // If you want print all properties => dbg!(broker_properties.clone());
63
+ debug!("Broker properties: {:?}", broker_properties);
69
64
 
70
65
  self
71
66
  .admin_client
@@ -12,7 +12,7 @@ use rdkafka::{
12
12
  use tracing::{info, Level};
13
13
 
14
14
  use super::{
15
- kafka_consumer::{ConsumerConfiguration, KafkaConsumer},
15
+ consumer::kafka_consumer::{ConsumerConfiguration, KafkaConsumer},
16
16
  kafka_producer::{KafkaProducer, ProducerConfiguration},
17
17
  };
18
18
 
@@ -62,6 +62,7 @@ pub struct KafkaConfiguration {
62
62
  pub security_protocol: Option<SecurityProtocol>,
63
63
  pub configuration: Option<HashMap<String, String>>,
64
64
  pub log_level: Option<String>,
65
+ pub broker_address_family: Option<String>,
65
66
  }
66
67
 
67
68
  #[derive(Clone, Debug)]
@@ -103,6 +104,7 @@ impl KafkaClient {
103
104
  security_protocol,
104
105
  client_id,
105
106
  configuration,
107
+ broker_address_family,
106
108
  ..
107
109
  } = kafka_configuration.clone();
108
110
 
@@ -111,6 +113,10 @@ impl KafkaClient {
111
113
  rdkafka_client_config.set_log_level(RDKafkaLogLevel::Debug);
112
114
  rdkafka_client_config.set("bootstrap.servers", brokers);
113
115
  rdkafka_client_config.set("client.id", client_id);
116
+ rdkafka_client_config.set(
117
+ "broker.address.family",
118
+ broker_address_family.unwrap_or("v4".to_string()),
119
+ );
114
120
  if let Some(security_protocol) = security_protocol {
115
121
  rdkafka_client_config.set("security.protocol", security_protocol.to_string());
116
122
  }
package/src/kafka/mod.rs CHANGED
@@ -1,6 +1,6 @@
1
+ pub mod consumer;
1
2
  pub mod kafka_admin;
2
3
  pub mod kafka_client;
3
- pub mod kafka_consumer;
4
4
  pub mod kafka_producer;
5
5
  pub mod kafka_util;
6
6
  pub mod model;
@@ -1,5 +1,3 @@
1
- use napi::bindgen_prelude::*;
2
-
3
1
  use std::{collections::HashMap, fmt};
4
2
 
5
3
  use napi::bindgen_prelude::Buffer;
@@ -53,7 +51,7 @@ impl Payload {
53
51
  }
54
52
  }
55
53
 
56
- #[napi(object)]
54
+ #[napi(string_enum)]
57
55
  #[derive(Debug)]
58
56
  pub enum PartitionPosition {
59
57
  Beginning,