kafka-crab-js 0.0.0 → 0.0.3
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/Cargo.toml +0 -3
- package/README.md +12 -1
- package/example/sample.mjs +62 -43
- package/index.d.ts +23 -15
- package/package.json +13 -9
- package/src/kafka/kafka_admin.rs +1 -1
- package/src/kafka/kafka_client.rs +31 -16
- package/src/kafka/kafka_consumer.rs +253 -46
- package/src/kafka/kafka_producer.rs +73 -23
- package/src/kafka/kafka_util.rs +48 -22
- package/src/kafka/model.rs +7 -10
package/Cargo.toml
CHANGED
|
@@ -16,9 +16,6 @@ anyhow = { version= "1.0", features = ["backtrace"]}
|
|
|
16
16
|
rdkafka = { version = "0.32", features = ["libz-static", "cmake-build", "tokio", "ssl-vendored"] }
|
|
17
17
|
tokio = { version = "1", features = ["full"] }
|
|
18
18
|
|
|
19
|
-
# log = "0.4"
|
|
20
|
-
# env_logger = "0.10.0"
|
|
21
|
-
|
|
22
19
|
tracing = "0.1"
|
|
23
20
|
tracing-subscriber = "0.3"
|
|
24
21
|
|
package/README.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
Kafka Crab Js
|
|
2
2
|
-------------
|
|
3
3
|
|
|
4
|
-
Blazingly Fast Kafka Client for NodeJs
|
|
4
|
+
**Blazingly Fast Kafka Client for NodeJs**
|
|
5
5
|
|
|
6
|
+
Kafka-Crab-JS is a powerful Node.js library that allows developers to interact with Apache Kafka using Rust programming language, seamlessly integrated with Node.js applications. This documentation provides an overview of the Kafka-Crab-JS library, including installation instructions, usage guidelines, and examples.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Installation
|
|
10
|
+
|
|
11
|
+
To use Kafka-Crab-JS in your Node.js project, follow the steps below:
|
|
12
|
+
|
|
13
|
+
todo
|
|
14
|
+
|
|
15
|
+
# Examples
|
|
16
|
+
todo
|
package/example/sample.mjs
CHANGED
|
@@ -1,50 +1,69 @@
|
|
|
1
|
-
|
|
2
|
-
import timersPromises from 'node:timers/promises'
|
|
3
|
-
import {
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
import timersPromises from 'node:timers/promises';
|
|
3
|
+
import {Buffer} from 'node:buffer';
|
|
4
|
+
import {ConsumerResult, KafkaClient, KafkaCommitMode, PartitionPosition} from '../index.js';
|
|
4
5
|
|
|
5
|
-
const kafkaClient = new KafkaClient(
|
|
6
|
-
const topic =
|
|
6
|
+
const kafkaClient = new KafkaClient('localhost:29092', 'my-id');
|
|
7
|
+
const topic = 'my-js-topic';
|
|
7
8
|
|
|
8
|
-
let counter =
|
|
9
|
+
let counter = 0;
|
|
9
10
|
|
|
10
11
|
async function process(message) {
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
await timersPromises.setTimeout(1);
|
|
13
|
+
return 'message received: ' + message;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
const consumer = kafkaClient.createConsumer({
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
17
|
+
topic,
|
|
18
|
+
groupId: 'my-js-group',
|
|
19
|
+
commitMode: KafkaCommitMode.Sync,
|
|
20
|
+
offset: {position: PartitionPosition.Stored},
|
|
21
|
+
retryStrategy: {
|
|
22
|
+
retries: 3,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
console.time('consumer');
|
|
27
|
+
|
|
28
|
+
consumer.startConsumer(async (error, value) => {
|
|
29
|
+
if (error) {
|
|
30
|
+
console.error('Consumer error', error);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const message = JSON.parse(value.toString());
|
|
35
|
+
const content = await process(message);
|
|
36
|
+
counter++;
|
|
37
|
+
if (counter >= 0) {
|
|
38
|
+
console.timeEnd('consumer');
|
|
39
|
+
console.log('Counter:', counter, 'Content:', JSON.stringify(content));
|
|
40
|
+
console.time('consumer');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (message._id === '3') {
|
|
44
|
+
console.error('Error on process message, let\'s retry', message);
|
|
45
|
+
return ConsumerResult.Retry;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return ConsumerResult.Ok;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const producer = kafkaClient.createProducer({topic, configuration: {'message.timeout.ms': '2'}});
|
|
52
|
+
console.log('Sending message');
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < 5; i++) {
|
|
55
|
+
try {
|
|
56
|
+
const result = await producer.send(
|
|
57
|
+
{
|
|
58
|
+
topic,
|
|
59
|
+
key: Buffer.from('abc'),
|
|
60
|
+
value: Buffer.from(`{"_id":"${i}","name":"Elizeu Drummond Sample js","phone":"555"}`),
|
|
61
|
+
headers: {key: Buffer.from('value1')},
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
console.log(result);
|
|
65
|
+
await timersPromises.setTimeout(1200);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('Error on send', error);
|
|
68
|
+
}
|
|
69
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -9,11 +9,22 @@ export const enum SecurityProtocol {
|
|
|
9
9
|
SaslPlaintext = 'SaslPlaintext',
|
|
10
10
|
SaslSsl = 'SaslSsl'
|
|
11
11
|
}
|
|
12
|
-
export interface
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
export interface ConsumerConfiguration {
|
|
13
|
+
topic: string
|
|
14
|
+
groupId: string
|
|
15
|
+
retryStrategy?: RetryStrategy
|
|
16
|
+
offset?: OffsetModel
|
|
17
|
+
createTopic?: boolean
|
|
18
|
+
commitMode?: KafkaCommitMode
|
|
19
|
+
enableAutoCommit?: boolean
|
|
20
|
+
configuration?: Record<string, string>
|
|
21
|
+
}
|
|
22
|
+
export interface ProducerConfiguration {
|
|
23
|
+
queueTimeout?: number
|
|
24
|
+
configuration?: Record<string, string>
|
|
15
25
|
}
|
|
16
26
|
export interface MessageModel {
|
|
27
|
+
topic: string
|
|
17
28
|
key: Buffer
|
|
18
29
|
value: Buffer
|
|
19
30
|
headers?: Record<string, Buffer>
|
|
@@ -36,13 +47,9 @@ export const enum KafkaCommitMode {
|
|
|
36
47
|
Sync = 'Sync',
|
|
37
48
|
Async = 'Async'
|
|
38
49
|
}
|
|
39
|
-
export interface
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
retryStrategy?: RetryStrategy
|
|
43
|
-
offset?: OffsetModel
|
|
44
|
-
createTopic?: boolean
|
|
45
|
-
commitMode?: KafkaCommitMode
|
|
50
|
+
export interface OwnedDelivery {
|
|
51
|
+
partition: number
|
|
52
|
+
offset: number
|
|
46
53
|
}
|
|
47
54
|
export const enum PartitionPosition {
|
|
48
55
|
Beginning = 0,
|
|
@@ -55,7 +62,8 @@ export interface OffsetModel {
|
|
|
55
62
|
}
|
|
56
63
|
export interface RetryStrategy {
|
|
57
64
|
retries: number
|
|
58
|
-
|
|
65
|
+
retryTopic?: string
|
|
66
|
+
dqlTopic?: string
|
|
59
67
|
pauseConsumerDuration?: number
|
|
60
68
|
}
|
|
61
69
|
export const enum ConsumerResult {
|
|
@@ -66,17 +74,17 @@ export class KafkaConfiguration {
|
|
|
66
74
|
brokers: string
|
|
67
75
|
clientId: string
|
|
68
76
|
securityProtocol?: SecurityProtocol
|
|
69
|
-
|
|
77
|
+
configuration?: Record<string, string>
|
|
70
78
|
}
|
|
71
79
|
export class KafkaClient {
|
|
72
80
|
readonly kafkaConfiguration: KafkaConfiguration
|
|
73
|
-
constructor(brokers: string, clientId: string)
|
|
74
|
-
createProducer(): KafkaProducer
|
|
81
|
+
constructor(brokers: string, clientId: string, securityProtocol?: SecurityProtocol | undefined | null, configuration?: Record<string, string> | undefined | null)
|
|
82
|
+
createProducer(producerConfiguration: ProducerConfiguration): KafkaProducer
|
|
75
83
|
createConsumer(consumerConfiguration: ConsumerConfiguration): KafkaConsumer
|
|
76
84
|
}
|
|
77
85
|
export class KafkaConsumer {
|
|
78
86
|
startConsumer(callback: (err: Error | null, result: Buffer) => Promise<ConsumerResult>): Promise<void>
|
|
79
87
|
}
|
|
80
88
|
export class KafkaProducer {
|
|
81
|
-
send(
|
|
89
|
+
send(message: MessageModel): Promise<OwnedDelivery>
|
|
82
90
|
}
|
package/package.json
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kafka-crab-js",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"types": "index.d.ts",
|
|
6
6
|
"napi": {
|
|
7
7
|
"name": "kafka-crab-js",
|
|
8
8
|
"triples": {
|
|
9
|
+
"defaults": false,
|
|
9
10
|
"additional": [
|
|
10
11
|
"aarch64-apple-darwin",
|
|
11
|
-
"x86_64-
|
|
12
|
+
"x86_64-apple-darwin",
|
|
13
|
+
"x86_64-unknown-linux-gnu"
|
|
12
14
|
]
|
|
13
15
|
}
|
|
14
16
|
},
|
|
15
17
|
"license": "MIT",
|
|
16
18
|
"devDependencies": {
|
|
17
19
|
"@napi-rs/cli": "^2.16.1",
|
|
18
|
-
"ava": "^5.
|
|
19
|
-
"nanoid": "^4.0.2"
|
|
20
|
+
"ava": "^5.3.1",
|
|
21
|
+
"nanoid": "^4.0.2",
|
|
22
|
+
"xo": "^0.54.2"
|
|
20
23
|
},
|
|
21
24
|
"ava": {
|
|
22
25
|
"timeout": "3m"
|
|
@@ -24,6 +27,9 @@
|
|
|
24
27
|
"engines": {
|
|
25
28
|
"node": ">= 10"
|
|
26
29
|
},
|
|
30
|
+
"xo": {
|
|
31
|
+
"space": true
|
|
32
|
+
},
|
|
27
33
|
"scripts": {
|
|
28
34
|
"artifacts": "napi artifacts",
|
|
29
35
|
"build": "napi build --platform --release",
|
|
@@ -35,10 +41,8 @@
|
|
|
35
41
|
},
|
|
36
42
|
"packageManager": "yarn@3.6.0",
|
|
37
43
|
"optionalDependencies": {
|
|
38
|
-
"kafka-crab-js-
|
|
39
|
-
"kafka-crab-js-darwin-x64": "0.0.
|
|
40
|
-
"kafka-crab-js-linux-x64-gnu": "0.0.
|
|
41
|
-
"kafka-crab-js-darwin-arm64": "0.0.0",
|
|
42
|
-
"kafka-crab-js-linux-x64-musl": "0.0.0"
|
|
44
|
+
"kafka-crab-js-darwin-arm64": "0.0.3",
|
|
45
|
+
"kafka-crab-js-darwin-x64": "0.0.3",
|
|
46
|
+
"kafka-crab-js-linux-x64-gnu": "0.0.3"
|
|
43
47
|
}
|
|
44
48
|
}
|
package/src/kafka/kafka_admin.rs
CHANGED
|
@@ -65,7 +65,7 @@ impl<'a> KafkaAdmin<'a> {
|
|
|
65
65
|
pub async fn create_topic(&self, topic_name: &str) -> anyhow::Result<()> {
|
|
66
66
|
let broker_properties = self.fetch_config_resource().await?.clone();
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
// If you want print all properties => dbg!(broker_properties.clone());
|
|
69
69
|
|
|
70
70
|
self
|
|
71
71
|
.admin_client
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
use std::fmt;
|
|
1
|
+
use std::{collections::HashMap, fmt};
|
|
2
2
|
|
|
3
|
-
use napi::bindgen_prelude
|
|
3
|
+
use napi::{bindgen_prelude::*, Result};
|
|
4
4
|
use rdkafka::{
|
|
5
5
|
client::ClientContext,
|
|
6
6
|
config::{ClientConfig, RDKafkaLogLevel},
|
|
@@ -8,10 +8,12 @@ use rdkafka::{
|
|
|
8
8
|
error::KafkaResult,
|
|
9
9
|
topic_partition_list::TopicPartitionList,
|
|
10
10
|
};
|
|
11
|
+
|
|
11
12
|
use tracing::info;
|
|
12
13
|
|
|
13
14
|
use super::{
|
|
14
|
-
kafka_consumer::
|
|
15
|
+
kafka_consumer::{ConsumerConfiguration, KafkaConsumer},
|
|
16
|
+
kafka_producer::{KafkaProducer, ProducerConfiguration},
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
struct CustomContext;
|
|
@@ -58,7 +60,7 @@ pub struct KafkaConfiguration {
|
|
|
58
60
|
pub brokers: String,
|
|
59
61
|
pub client_id: String,
|
|
60
62
|
pub security_protocol: Option<SecurityProtocol>,
|
|
61
|
-
pub
|
|
63
|
+
pub configuration: Option<HashMap<String, String>>,
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
#[derive(Clone, Debug)]
|
|
@@ -72,7 +74,12 @@ pub struct KafkaClient {
|
|
|
72
74
|
#[napi]
|
|
73
75
|
impl KafkaClient {
|
|
74
76
|
#[napi(constructor)]
|
|
75
|
-
pub fn new(
|
|
77
|
+
pub fn new(
|
|
78
|
+
brokers: String,
|
|
79
|
+
client_id: String,
|
|
80
|
+
security_protocol: Option<SecurityProtocol>,
|
|
81
|
+
configuration: Option<HashMap<String, String>>,
|
|
82
|
+
) -> Self {
|
|
76
83
|
match tracing_subscriber::fmt::try_init() {
|
|
77
84
|
Ok(_) => {}
|
|
78
85
|
Err(e) => println!("Failed to initialize tracing: {:?}", e),
|
|
@@ -81,8 +88,8 @@ impl KafkaClient {
|
|
|
81
88
|
KafkaClient::with_kafka_configuration(KafkaConfiguration {
|
|
82
89
|
brokers,
|
|
83
90
|
client_id,
|
|
84
|
-
security_protocol
|
|
85
|
-
|
|
91
|
+
security_protocol,
|
|
92
|
+
configuration,
|
|
86
93
|
})
|
|
87
94
|
}
|
|
88
95
|
|
|
@@ -95,7 +102,7 @@ impl KafkaClient {
|
|
|
95
102
|
brokers,
|
|
96
103
|
security_protocol,
|
|
97
104
|
client_id,
|
|
98
|
-
|
|
105
|
+
configuration,
|
|
99
106
|
} = kafka_configuration.clone();
|
|
100
107
|
|
|
101
108
|
let mut rdkafka_client_config = ClientConfig::new();
|
|
@@ -106,11 +113,10 @@ impl KafkaClient {
|
|
|
106
113
|
if let Some(security_protocol) = security_protocol {
|
|
107
114
|
rdkafka_client_config.set("security.protocol", security_protocol.to_string());
|
|
108
115
|
}
|
|
109
|
-
if let Some(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
);
|
|
116
|
+
if let Some(config) = configuration {
|
|
117
|
+
for (key, value) in config {
|
|
118
|
+
rdkafka_client_config.set(key, value);
|
|
119
|
+
}
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
KafkaClient {
|
|
@@ -120,12 +126,21 @@ impl KafkaClient {
|
|
|
120
126
|
}
|
|
121
127
|
|
|
122
128
|
#[napi]
|
|
123
|
-
pub fn create_producer(
|
|
124
|
-
|
|
129
|
+
pub fn create_producer(
|
|
130
|
+
&self,
|
|
131
|
+
producer_configuration: ProducerConfiguration,
|
|
132
|
+
) -> Result<KafkaProducer> {
|
|
133
|
+
match KafkaProducer::new(self.rdkafka_client_config.clone(), producer_configuration) {
|
|
134
|
+
Ok(producer) => Ok(producer),
|
|
135
|
+
Err(e) => Err(Error::new(Status::GenericFailure, e.to_string())),
|
|
136
|
+
}
|
|
125
137
|
}
|
|
126
138
|
|
|
127
139
|
#[napi]
|
|
128
|
-
pub fn create_consumer(
|
|
140
|
+
pub fn create_consumer(
|
|
141
|
+
&self,
|
|
142
|
+
consumer_configuration: ConsumerConfiguration,
|
|
143
|
+
) -> Result<KafkaConsumer> {
|
|
129
144
|
KafkaConsumer::new(self.clone(), consumer_configuration)
|
|
130
145
|
}
|
|
131
146
|
}
|
|
@@ -8,20 +8,28 @@ use rdkafka::{
|
|
|
8
8
|
config::RDKafkaLogLevel,
|
|
9
9
|
consumer::{stream_consumer::StreamConsumer, CommitMode, Consumer, ConsumerContext, Rebalance},
|
|
10
10
|
error::KafkaResult,
|
|
11
|
-
message::BorrowedMessage,
|
|
11
|
+
message::{BorrowedMessage, Header, OwnedHeaders},
|
|
12
|
+
producer::FutureRecord,
|
|
12
13
|
topic_partition_list::TopicPartitionList,
|
|
13
|
-
|
|
14
|
+
ClientConfig, Message,
|
|
14
15
|
};
|
|
15
16
|
|
|
16
|
-
use std::{time::Duration, vec};
|
|
17
|
+
use std::{collections::HashMap, time::Duration, vec};
|
|
17
18
|
use tracing::{debug, error, info};
|
|
18
19
|
|
|
20
|
+
use crate::kafka::kafka_util::{
|
|
21
|
+
convert_to_rdkafka_offset, kakfa_headers_to_hashmap, ExtractValueOnKafkaHashMap,
|
|
22
|
+
};
|
|
23
|
+
|
|
19
24
|
use super::{
|
|
20
25
|
kafka_admin::KafkaAdmin,
|
|
21
26
|
kafka_client::KafkaClient,
|
|
22
|
-
|
|
27
|
+
kafka_producer::{KafkaProducer, ProducerConfiguration},
|
|
28
|
+
model::{ConsumerResult, KafkaCommitMode, OffsetModel, RetryStrategy},
|
|
23
29
|
};
|
|
24
30
|
|
|
31
|
+
const RETRY_COUNTER_NAME: &str = "kafka-crab-js-retry-counter";
|
|
32
|
+
|
|
25
33
|
type LoggingConsumer = StreamConsumer<CustomContext>;
|
|
26
34
|
|
|
27
35
|
pub struct CustomContext;
|
|
@@ -42,25 +50,56 @@ impl ConsumerContext for CustomContext {
|
|
|
42
50
|
}
|
|
43
51
|
}
|
|
44
52
|
|
|
53
|
+
#[napi(object)]
|
|
45
54
|
#[derive(Clone, Debug)]
|
|
55
|
+
pub struct ConsumerConfiguration {
|
|
56
|
+
pub topic: String,
|
|
57
|
+
pub group_id: String,
|
|
58
|
+
pub retry_strategy: Option<RetryStrategy>,
|
|
59
|
+
pub offset: Option<OffsetModel>,
|
|
60
|
+
pub create_topic: Option<bool>,
|
|
61
|
+
pub commit_mode: Option<KafkaCommitMode>,
|
|
62
|
+
pub enable_auto_commit: Option<bool>,
|
|
63
|
+
pub configuration: Option<HashMap<String, String>>,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#[derive(Clone)]
|
|
46
67
|
#[napi]
|
|
47
68
|
pub struct KafkaConsumer {
|
|
48
69
|
kafka_client: KafkaClient,
|
|
49
70
|
consumer_configuration: ConsumerConfiguration,
|
|
71
|
+
producer: KafkaProducer,
|
|
50
72
|
}
|
|
51
73
|
|
|
52
74
|
#[napi]
|
|
53
75
|
impl KafkaConsumer {
|
|
54
|
-
pub fn new(
|
|
55
|
-
|
|
76
|
+
pub fn new(
|
|
77
|
+
kafka_client: KafkaClient,
|
|
78
|
+
consumer_configuration: ConsumerConfiguration,
|
|
79
|
+
) -> Result<Self> {
|
|
80
|
+
let producer = KafkaProducer::new(
|
|
81
|
+
kafka_client.get_client_config().clone(),
|
|
82
|
+
ProducerConfiguration {
|
|
83
|
+
configuration: None,
|
|
84
|
+
queue_timeout: None,
|
|
85
|
+
},
|
|
86
|
+
)?;
|
|
87
|
+
|
|
88
|
+
Ok(KafkaConsumer {
|
|
56
89
|
kafka_client,
|
|
57
90
|
consumer_configuration,
|
|
58
|
-
|
|
91
|
+
producer,
|
|
92
|
+
})
|
|
59
93
|
}
|
|
60
94
|
|
|
61
95
|
#[napi(ts_args_type = "callback: (err: Error | null, result: Buffer) => Promise<ConsumerResult>")]
|
|
62
96
|
pub async fn start_consumer(&self, func: ThreadsafeFunction<Buffer>) -> Result<()> {
|
|
63
|
-
let ConsumerConfiguration {
|
|
97
|
+
let ConsumerConfiguration {
|
|
98
|
+
commit_mode,
|
|
99
|
+
retry_strategy,
|
|
100
|
+
topic,
|
|
101
|
+
..
|
|
102
|
+
} = self.consumer_configuration.clone();
|
|
64
103
|
|
|
65
104
|
let commit_mode = match commit_mode {
|
|
66
105
|
None | Some(KafkaCommitMode::AutoCommit) => None, //Default AutoCommit
|
|
@@ -68,40 +107,85 @@ impl KafkaConsumer {
|
|
|
68
107
|
Some(KafkaCommitMode::Async) => Some(CommitMode::Async),
|
|
69
108
|
};
|
|
70
109
|
|
|
110
|
+
let producer = self.producer.clone();
|
|
111
|
+
|
|
112
|
+
if let Some(strategy) = retry_strategy.clone() {
|
|
113
|
+
let retry_topic = strategy
|
|
114
|
+
.retry_topic
|
|
115
|
+
.unwrap_or(format!("{}-retry", topic.clone()));
|
|
116
|
+
let stream_consumer_retry: StreamConsumer<CustomContext> = self
|
|
117
|
+
.setup_consumer(retry_topic.clone())
|
|
118
|
+
.await
|
|
119
|
+
.expect("Consumer creation failed");
|
|
120
|
+
debug!(
|
|
121
|
+
"Stream consumer configured for retry topic: {}",
|
|
122
|
+
retry_topic
|
|
123
|
+
);
|
|
124
|
+
let (retry_topic, retry_func, retry_producer) =
|
|
125
|
+
(topic.clone(), func.clone(), producer.clone());
|
|
126
|
+
tokio::spawn(async move {
|
|
127
|
+
start_consumer_loop(
|
|
128
|
+
stream_consumer_retry,
|
|
129
|
+
retry_func,
|
|
130
|
+
commit_mode,
|
|
131
|
+
strategy.retries,
|
|
132
|
+
&strategy.dql_topic.unwrap_or(format!("{}-dql", retry_topic)),
|
|
133
|
+
retry_producer,
|
|
134
|
+
)
|
|
135
|
+
.await
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
|
|
71
139
|
let stream_consumer: StreamConsumer<CustomContext> = self
|
|
72
|
-
.setup_consumer()
|
|
140
|
+
.setup_consumer(topic.clone())
|
|
73
141
|
.await
|
|
74
142
|
.expect("Consumer creation failed");
|
|
143
|
+
debug!("Stream consumer configured for topic: {}", topic);
|
|
144
|
+
let next_topic_on_fail = setup_next_topic(
|
|
145
|
+
self.consumer_configuration.topic.clone(),
|
|
146
|
+
retry_strategy.clone(),
|
|
147
|
+
);
|
|
75
148
|
|
|
76
149
|
tokio::spawn(async move {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
150
|
+
start_consumer_loop(
|
|
151
|
+
stream_consumer,
|
|
152
|
+
func,
|
|
153
|
+
commit_mode,
|
|
154
|
+
0,
|
|
155
|
+
&next_topic_on_fail,
|
|
156
|
+
producer,
|
|
157
|
+
)
|
|
158
|
+
.await
|
|
83
159
|
});
|
|
84
160
|
|
|
85
161
|
Ok(())
|
|
86
162
|
}
|
|
87
163
|
|
|
88
|
-
async fn setup_consumer(&self) -> anyhow::Result<StreamConsumer<CustomContext>> {
|
|
164
|
+
async fn setup_consumer(&self, topic: String) -> anyhow::Result<StreamConsumer<CustomContext>> {
|
|
89
165
|
let context = CustomContext;
|
|
90
166
|
|
|
91
167
|
let ConsumerConfiguration {
|
|
92
|
-
topic,
|
|
93
168
|
offset,
|
|
94
169
|
create_topic,
|
|
95
170
|
group_id,
|
|
171
|
+
configuration,
|
|
172
|
+
enable_auto_commit,
|
|
96
173
|
..
|
|
97
174
|
} = self.consumer_configuration.clone();
|
|
98
175
|
|
|
99
|
-
let
|
|
100
|
-
|
|
101
|
-
|
|
176
|
+
let mut consumer_config: ClientConfig = self.kafka_client.get_client_config().clone();
|
|
177
|
+
|
|
178
|
+
if let Some(config) = configuration {
|
|
179
|
+
consumer_config.extend(config);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let consumer: LoggingConsumer = consumer_config
|
|
102
183
|
.clone()
|
|
103
184
|
.set("group.id", group_id.clone())
|
|
104
|
-
.set(
|
|
185
|
+
.set(
|
|
186
|
+
"enable.auto.commit",
|
|
187
|
+
enable_auto_commit.unwrap_or(true).to_string(),
|
|
188
|
+
)
|
|
105
189
|
.set_log_level(RDKafkaLogLevel::Debug)
|
|
106
190
|
.create_with_context(context)
|
|
107
191
|
.expect("Consumer creation failed");
|
|
@@ -140,49 +224,172 @@ impl KafkaConsumer {
|
|
|
140
224
|
}
|
|
141
225
|
}
|
|
142
226
|
|
|
143
|
-
fn
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
227
|
+
async fn start_consumer_loop(
|
|
228
|
+
stream_consumer: StreamConsumer<CustomContext>,
|
|
229
|
+
func: ThreadsafeFunction<Buffer>,
|
|
230
|
+
commit_mode: Option<CommitMode>,
|
|
231
|
+
retries: i32,
|
|
232
|
+
next_topic_on_fail: &str,
|
|
233
|
+
producer: KafkaProducer,
|
|
234
|
+
) {
|
|
235
|
+
loop {
|
|
236
|
+
match stream_consumer.recv().await {
|
|
237
|
+
Err(e) => error!("Error while receiving from stream consumer: {:?}", e),
|
|
238
|
+
Ok(message) => {
|
|
239
|
+
let message_result = process_message(&commit_mode, &stream_consumer, &func, &message).await;
|
|
240
|
+
match handle_message_result(
|
|
241
|
+
retries,
|
|
242
|
+
next_topic_on_fail,
|
|
243
|
+
&producer,
|
|
244
|
+
&message,
|
|
245
|
+
message_result,
|
|
246
|
+
)
|
|
247
|
+
.await
|
|
248
|
+
{
|
|
249
|
+
Ok(_) => {
|
|
250
|
+
debug!(
|
|
251
|
+
"Message consumed successfully. Partition: {}, Offset: {}",
|
|
252
|
+
message.partition(),
|
|
253
|
+
message.offset()
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
Err(_) => {
|
|
257
|
+
error!("Message processing failed")
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async fn handle_message_result(
|
|
266
|
+
retries: i32,
|
|
267
|
+
next_topic_on_fail: &str,
|
|
268
|
+
producer: &KafkaProducer,
|
|
269
|
+
message: &BorrowedMessage<'_>,
|
|
270
|
+
message_result: anyhow::Result<Option<ConsumerResult>>,
|
|
271
|
+
) -> anyhow::Result<()> {
|
|
272
|
+
match message_result {
|
|
273
|
+
Ok(consumer_result) => match consumer_result {
|
|
274
|
+
Some(result) => match result {
|
|
275
|
+
ConsumerResult::Ok => Ok(()),
|
|
276
|
+
ConsumerResult::Retry => {
|
|
277
|
+
match send_to_retry_strategy(producer, message, retries, next_topic_on_fail).await {
|
|
278
|
+
Ok(_) => {
|
|
279
|
+
debug!("Message sent to retry strategy");
|
|
280
|
+
Ok(())
|
|
281
|
+
}
|
|
282
|
+
Err(e) => {
|
|
283
|
+
error!("Error while sending message to retry strategy: {:?}", e);
|
|
284
|
+
Err(e)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
152
288
|
},
|
|
153
|
-
|
|
154
|
-
|
|
289
|
+
None => Ok(()),
|
|
290
|
+
},
|
|
291
|
+
Err(err) => {
|
|
292
|
+
error!("Error while processing message: {:?}", err);
|
|
293
|
+
Err(anyhow::Error::msg("Producer not found"))
|
|
294
|
+
}
|
|
155
295
|
}
|
|
156
296
|
}
|
|
157
297
|
|
|
158
298
|
async fn process_message(
|
|
159
|
-
commit_mode: Option<CommitMode>,
|
|
299
|
+
commit_mode: &Option<CommitMode>,
|
|
160
300
|
stream_consumer: &StreamConsumer<CustomContext>,
|
|
161
301
|
func: &ThreadsafeFunction<Buffer>,
|
|
162
302
|
message: &BorrowedMessage<'_>,
|
|
163
|
-
) {
|
|
303
|
+
) -> anyhow::Result<Option<ConsumerResult>> {
|
|
164
304
|
match message.payload_view::<[u8]>() {
|
|
165
|
-
None =>
|
|
305
|
+
None => Ok(None),
|
|
166
306
|
Some(Ok(payload)) => match func
|
|
167
307
|
.call_async::<Promise<ConsumerResult>>(Ok(payload.into()))
|
|
168
308
|
.await
|
|
169
309
|
{
|
|
170
310
|
Ok(js_result) => match js_result.await {
|
|
171
|
-
Ok(value) => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
311
|
+
Ok(value) => match value {
|
|
312
|
+
ConsumerResult::Ok => {
|
|
313
|
+
if let Some(commit_mode) = commit_mode {
|
|
314
|
+
match stream_consumer.commit_message(message, *commit_mode) {
|
|
315
|
+
Ok(_) => Ok(Some(value)),
|
|
316
|
+
Err(e) => Err(anyhow::Error::new(e)),
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
Ok(Some(value))
|
|
177
320
|
}
|
|
178
321
|
}
|
|
179
|
-
|
|
180
|
-
|
|
322
|
+
ConsumerResult::Retry => Ok(Some(value)),
|
|
323
|
+
},
|
|
324
|
+
Err(e) => Err(anyhow::Error::new(e)),
|
|
181
325
|
},
|
|
182
|
-
Err(js_error) =>
|
|
326
|
+
Err(js_error) => Err(anyhow::Error::new(js_error)),
|
|
183
327
|
},
|
|
184
|
-
Some(
|
|
185
|
-
|
|
328
|
+
Some(_) => Err(anyhow::Error::msg(
|
|
329
|
+
"Error while deserializing message payload",
|
|
330
|
+
)),
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async fn send_to_retry_strategy(
|
|
335
|
+
producer: &KafkaProducer,
|
|
336
|
+
message: &BorrowedMessage<'_>,
|
|
337
|
+
retries: i32,
|
|
338
|
+
next_topic_on_fail: &str,
|
|
339
|
+
) -> anyhow::Result<()> {
|
|
340
|
+
let retry_counter: usize = {
|
|
341
|
+
let headers_map = kakfa_headers_to_hashmap(message.headers());
|
|
342
|
+
let mut counter = headers_map.get_value(RETRY_COUNTER_NAME).unwrap_or(1);
|
|
343
|
+
if retries > 0 {
|
|
344
|
+
counter += 1;
|
|
345
|
+
}
|
|
346
|
+
counter
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
let next_topic = if retries > 0 && retry_counter <= retries.try_into().unwrap() {
|
|
350
|
+
debug!(
|
|
351
|
+
"Send message to same topic. Retry counter: {}",
|
|
352
|
+
retry_counter
|
|
353
|
+
);
|
|
354
|
+
message.topic()
|
|
355
|
+
} else {
|
|
356
|
+
debug!(
|
|
357
|
+
"Send message to next topic: {}. Retry counter {}",
|
|
358
|
+
next_topic_on_fail, retry_counter
|
|
359
|
+
);
|
|
360
|
+
next_topic_on_fail
|
|
361
|
+
};
|
|
362
|
+
let new_headers = message
|
|
363
|
+
.headers()
|
|
364
|
+
.unwrap_or(OwnedHeaders::new().as_borrowed())
|
|
365
|
+
.detach()
|
|
366
|
+
.insert(Header {
|
|
367
|
+
key: RETRY_COUNTER_NAME,
|
|
368
|
+
value: Some(retry_counter.to_string().as_str()),
|
|
369
|
+
});
|
|
370
|
+
let key = message.key().unwrap_or(&[]);
|
|
371
|
+
|
|
372
|
+
let record = FutureRecord::to(next_topic)
|
|
373
|
+
.key(key)
|
|
374
|
+
.headers(new_headers)
|
|
375
|
+
.payload(message.payload().unwrap_or(&[]));
|
|
376
|
+
|
|
377
|
+
let _result = producer.raw_send(record).await;
|
|
378
|
+
info!("Message sent to topic: {:?}", next_topic);
|
|
379
|
+
Ok(())
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
fn setup_next_topic(main_topic: String, retry_strategy: Option<RetryStrategy>) -> String {
|
|
383
|
+
match retry_strategy {
|
|
384
|
+
Some(strategy) => {
|
|
385
|
+
if (strategy.retries) > 0 {
|
|
386
|
+
strategy
|
|
387
|
+
.retry_topic
|
|
388
|
+
.unwrap_or(format!("{}-retry", main_topic))
|
|
389
|
+
} else {
|
|
390
|
+
strategy.dql_topic.unwrap_or(format!("{}-dlq", main_topic))
|
|
391
|
+
}
|
|
186
392
|
}
|
|
393
|
+
None => format!("{}-dlq", main_topic),
|
|
187
394
|
}
|
|
188
395
|
}
|
|
@@ -1,57 +1,107 @@
|
|
|
1
|
-
use std::time::Duration;
|
|
1
|
+
use std::{collections::HashMap, time::Duration};
|
|
2
2
|
|
|
3
3
|
use napi::{Error, Result, Status};
|
|
4
4
|
use rdkafka::{
|
|
5
|
-
message::ToBytes,
|
|
5
|
+
message::{OwnedHeaders, ToBytes},
|
|
6
6
|
producer::{FutureProducer, FutureRecord},
|
|
7
7
|
ClientConfig,
|
|
8
8
|
};
|
|
9
|
+
use tracing::{debug, warn};
|
|
9
10
|
|
|
10
|
-
use super::
|
|
11
|
+
use super::{
|
|
12
|
+
kafka_util::hashmap_to_kafka_headers,
|
|
13
|
+
model::{MessageModel, OwnedDelivery},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const DEFAULT_QUEUE_TIMEOUT: i64 = 5000;
|
|
17
|
+
|
|
18
|
+
#[napi(object)]
|
|
19
|
+
#[derive(Clone, Debug)]
|
|
20
|
+
pub struct ProducerConfiguration {
|
|
21
|
+
pub queue_timeout: Option<i64>,
|
|
22
|
+
pub configuration: Option<HashMap<String, String>>,
|
|
23
|
+
}
|
|
11
24
|
|
|
12
25
|
#[derive(Clone)]
|
|
13
26
|
#[napi]
|
|
14
27
|
pub struct KafkaProducer {
|
|
15
28
|
future_producer: FutureProducer,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
#[napi(object)]
|
|
19
|
-
pub struct OwnedDelivery {
|
|
20
|
-
pub partition: i32,
|
|
21
|
-
pub offset: i64,
|
|
29
|
+
queue_timeout: Duration,
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
#[napi]
|
|
25
33
|
impl KafkaProducer {
|
|
26
|
-
pub fn new(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
pub fn new(
|
|
35
|
+
client_config: ClientConfig,
|
|
36
|
+
producer_configuration: ProducerConfiguration,
|
|
37
|
+
) -> Result<Self> {
|
|
38
|
+
let mut future_producer_config = client_config;
|
|
39
|
+
|
|
40
|
+
if let Some(config) = producer_configuration.configuration {
|
|
41
|
+
future_producer_config.extend(config);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let future_producer = future_producer_config
|
|
30
45
|
.create::<FutureProducer>()
|
|
31
|
-
.
|
|
46
|
+
.map_err(|e| {
|
|
47
|
+
warn!("Failed to create Kafka producer. Error: {:?}", e);
|
|
48
|
+
Error::new(Status::GenericFailure, e)
|
|
49
|
+
})?;
|
|
50
|
+
|
|
51
|
+
let queue_timeout = Duration::from_millis(
|
|
52
|
+
producer_configuration
|
|
53
|
+
.queue_timeout
|
|
54
|
+
.unwrap_or(DEFAULT_QUEUE_TIMEOUT)
|
|
55
|
+
.try_into()
|
|
56
|
+
.map_err(|e| Error::new(Status::GenericFailure, e))?,
|
|
57
|
+
);
|
|
32
58
|
|
|
33
|
-
KafkaProducer {
|
|
59
|
+
Ok(KafkaProducer {
|
|
60
|
+
future_producer,
|
|
61
|
+
queue_timeout,
|
|
62
|
+
})
|
|
34
63
|
}
|
|
35
64
|
|
|
36
65
|
#[napi]
|
|
37
|
-
pub async fn send(&self,
|
|
66
|
+
pub async fn send(&self, message: MessageModel) -> Result<OwnedDelivery> {
|
|
38
67
|
let MessageModel {
|
|
68
|
+
topic,
|
|
39
69
|
key,
|
|
40
70
|
value,
|
|
41
|
-
headers
|
|
71
|
+
headers,
|
|
42
72
|
} = message;
|
|
43
73
|
|
|
74
|
+
let kafka_headers = match headers {
|
|
75
|
+
Some(v) => hashmap_to_kafka_headers(&v),
|
|
76
|
+
None => OwnedHeaders::new(),
|
|
77
|
+
};
|
|
78
|
+
|
|
44
79
|
let record = FutureRecord::to(&topic)
|
|
45
80
|
.key(key.to_bytes())
|
|
81
|
+
.headers(kafka_headers)
|
|
46
82
|
.payload(value.to_bytes());
|
|
47
83
|
|
|
48
|
-
match self
|
|
49
|
-
.future_producer
|
|
50
|
-
.send(record, Duration::from_secs(5000))
|
|
51
|
-
.await
|
|
52
|
-
{
|
|
84
|
+
match self.raw_send(record).await {
|
|
53
85
|
Ok((partition, offset)) => Ok(OwnedDelivery { partition, offset }),
|
|
54
|
-
Err(
|
|
86
|
+
Err(e) => Err(Error::new(Status::GenericFailure, e)),
|
|
55
87
|
}
|
|
56
88
|
}
|
|
89
|
+
|
|
90
|
+
pub async fn raw_send<'a, K: ToBytes + ?Sized, P: ToBytes + ?Sized>(
|
|
91
|
+
&self,
|
|
92
|
+
record: FutureRecord<'a, K, P>,
|
|
93
|
+
) -> anyhow::Result<(i32, i64)> {
|
|
94
|
+
debug!("Trying to produce message. Topic {}", record.topic);
|
|
95
|
+
let producer: &FutureProducer = &self.future_producer;
|
|
96
|
+
|
|
97
|
+
producer
|
|
98
|
+
.send(record, self.queue_timeout)
|
|
99
|
+
.await
|
|
100
|
+
.map_err(|(k, m)| {
|
|
101
|
+
anyhow::Error::msg(format!(
|
|
102
|
+
"Failed to send message to Kafka. Error: {:?}. Message: {:?}",
|
|
103
|
+
k, m
|
|
104
|
+
))
|
|
105
|
+
})
|
|
106
|
+
}
|
|
57
107
|
}
|
package/src/kafka/kafka_util.rs
CHANGED
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
use std::collections::HashMap;
|
|
2
2
|
|
|
3
|
-
use
|
|
3
|
+
use napi::bindgen_prelude::Buffer;
|
|
4
|
+
use rdkafka::{
|
|
5
|
+
message::{BorrowedHeaders, Header, Headers, OwnedHeaders},
|
|
6
|
+
Offset,
|
|
7
|
+
};
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
use super::model::{OffsetModel, PartitionPosition};
|
|
10
|
+
|
|
11
|
+
pub fn convert_to_rdkafka_offset(offset_model: Option<OffsetModel>) -> Option<Offset> {
|
|
12
|
+
offset_model.map(|model| match model.position {
|
|
13
|
+
Some(PartitionPosition::Beginning) => Offset::Beginning,
|
|
14
|
+
Some(PartitionPosition::End) => Offset::End,
|
|
15
|
+
Some(PartitionPosition::Stored) => Offset::Stored,
|
|
16
|
+
None => match model.offset {
|
|
17
|
+
Some(value) => Offset::Offset(value),
|
|
18
|
+
None => Offset::Stored, // Default to stored
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub fn hashmap_to_kafka_headers(map: &HashMap<String, Buffer>) -> OwnedHeaders {
|
|
24
|
+
map.iter().fold(OwnedHeaders::new(), |acc, (key, value)| {
|
|
25
|
+
let value: &[u8] = value.as_ref();
|
|
26
|
+
acc.insert(Header {
|
|
27
|
+
key,
|
|
28
|
+
value: Some(value),
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub fn kakfa_headers_to_hashmap(headers: Option<&BorrowedHeaders>) -> HashMap<&str, &[u8]> {
|
|
6
34
|
match headers {
|
|
7
35
|
Some(value) => value
|
|
8
36
|
.iter()
|
|
@@ -39,29 +67,27 @@ impl ExtractValueOnKafkaHashMap<usize> for HashMap<&str, &[u8]> {
|
|
|
39
67
|
mod tests {
|
|
40
68
|
use std::collections::HashMap;
|
|
41
69
|
|
|
42
|
-
use
|
|
70
|
+
use napi::bindgen_prelude::Buffer;
|
|
71
|
+
use rdkafka::message::Headers;
|
|
43
72
|
|
|
44
|
-
use crate::kafka::kafka_util::{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Header { key, value }
|
|
48
|
-
}
|
|
73
|
+
use crate::kafka::kafka_util::{
|
|
74
|
+
hashmap_to_kafka_headers, kakfa_headers_to_hashmap, ExtractValueOnKafkaHashMap,
|
|
75
|
+
};
|
|
49
76
|
|
|
50
77
|
#[test]
|
|
51
|
-
fn
|
|
52
|
-
let
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
let
|
|
63
|
-
|
|
64
|
-
assert_eq!(hash_map.len(), 2);
|
|
78
|
+
fn headers_test() {
|
|
79
|
+
let hash_map: HashMap<String, Buffer> = HashMap::from([
|
|
80
|
+
("key_a".to_owned(), "A".as_bytes().into()),
|
|
81
|
+
("key_b".to_owned(), "B".as_bytes().into()),
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
let rd_headers = hashmap_to_kafka_headers(&hash_map);
|
|
85
|
+
|
|
86
|
+
dbg!(rd_headers.get(0));
|
|
87
|
+
dbg!(rd_headers.get(1));
|
|
88
|
+
|
|
89
|
+
let result = kakfa_headers_to_hashmap(Some(rd_headers.as_borrowed()));
|
|
90
|
+
assert_eq!(result.len(), 2);
|
|
65
91
|
}
|
|
66
92
|
|
|
67
93
|
#[test]
|
package/src/kafka/model.rs
CHANGED
|
@@ -7,6 +7,7 @@ use napi::bindgen_prelude::Buffer;
|
|
|
7
7
|
#[napi(object)]
|
|
8
8
|
#[derive(Clone)]
|
|
9
9
|
pub struct MessageModel {
|
|
10
|
+
pub topic: String,
|
|
10
11
|
pub key: Buffer,
|
|
11
12
|
pub value: Buffer,
|
|
12
13
|
pub headers: Option<HashMap<String, Buffer>>,
|
|
@@ -38,7 +39,7 @@ impl fmt::Display for AutoOffsetReset {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
#[napi(string_enum)]
|
|
41
|
-
#[derive(Debug)]
|
|
42
|
+
#[derive(Debug, PartialEq)]
|
|
42
43
|
pub enum KafkaCommitMode {
|
|
43
44
|
AutoCommit,
|
|
44
45
|
Sync,
|
|
@@ -46,14 +47,9 @@ pub enum KafkaCommitMode {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
#[napi(object)]
|
|
49
|
-
|
|
50
|
-
pub
|
|
51
|
-
pub
|
|
52
|
-
pub group_id: String,
|
|
53
|
-
pub retry_strategy: Option<RetryStrategy>,
|
|
54
|
-
pub offset: Option<OffsetModel>,
|
|
55
|
-
pub create_topic: Option<bool>,
|
|
56
|
-
pub commit_mode: Option<KafkaCommitMode>,
|
|
50
|
+
pub struct OwnedDelivery {
|
|
51
|
+
pub partition: i32,
|
|
52
|
+
pub offset: i64,
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
#[napi(object)]
|
|
@@ -74,7 +70,8 @@ pub struct OffsetModel {
|
|
|
74
70
|
#[derive(Clone, Debug)]
|
|
75
71
|
pub struct RetryStrategy {
|
|
76
72
|
pub retries: i32,
|
|
77
|
-
pub
|
|
73
|
+
pub retry_topic: Option<String>,
|
|
74
|
+
pub dql_topic: Option<String>,
|
|
78
75
|
pub pause_consumer_duration: Option<i64>,
|
|
79
76
|
}
|
|
80
77
|
|