kafka-crab-js 1.0.2 → 1.0.5
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 +116 -7
- package/js-binding.d.ts +5 -4
- package/js-binding.js +14 -15
- package/kafka-client.js +1 -1
- package/kafka-stream-readable.d.ts +4 -1
- package/kafka-stream-readable.js +6 -0
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# 🦀 Kafka Crab JS 🦀
|
|
4
4
|
|
|
5
|
-
A lightweight and
|
|
5
|
+
A lightweight, flexible, and reliable Kafka client for JavaScript/TypeScript. It is built using Rust and librdkafka, providing a high-performance and feature-rich Kafka client.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/kafka-crab-js)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -38,6 +38,8 @@ A lightweight and flexible Kafka client for JavaScript/TypeScript, built with Ru
|
|
|
38
38
|
npm install kafka-crab-js
|
|
39
39
|
# or
|
|
40
40
|
yarn add kafka-crab-js
|
|
41
|
+
# or
|
|
42
|
+
pnpm add kafka-crab-js
|
|
41
43
|
```
|
|
42
44
|
|
|
43
45
|
## Quick Start
|
|
@@ -205,11 +207,123 @@ async function produceWithMetadata() {
|
|
|
205
207
|
}
|
|
206
208
|
```
|
|
207
209
|
|
|
210
|
+
### Reconnecting Kafka Consumer
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
import { KafkaClient } from 'kafka-crab-js'
|
|
214
|
+
|
|
215
|
+
const kafkaClient = new KafkaClient({
|
|
216
|
+
brokers: 'localhost:29092',
|
|
217
|
+
clientId: 'reconnect-test',
|
|
218
|
+
logLevel: 'debug',
|
|
219
|
+
brokerAddressFamily: 'v4',
|
|
220
|
+
configuration: {
|
|
221
|
+
'auto.offset.reset': 'earliest',
|
|
222
|
+
},
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Creates and configures a new Kafka stream consumer
|
|
227
|
+
*/
|
|
228
|
+
async function createConsumer() {
|
|
229
|
+
const kafkaStream = kafkaClient.createStreamConsumer({
|
|
230
|
+
groupId: 'reconnect-test',
|
|
231
|
+
enableAutoCommit: true,
|
|
232
|
+
})
|
|
233
|
+
await kafkaStream.subscribe([
|
|
234
|
+
{ topic: 'foo' },
|
|
235
|
+
{ topic: 'bar' },
|
|
236
|
+
])
|
|
237
|
+
return kafkaStream
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Starts a Kafka consumer with auto-restart capability
|
|
242
|
+
*/
|
|
243
|
+
async function startConsumer() {
|
|
244
|
+
let counter = 0
|
|
245
|
+
let retryCount = 0
|
|
246
|
+
const MAX_RETRIES = 5
|
|
247
|
+
const RETRY_DELAY = 5000 // 5 seconds
|
|
248
|
+
|
|
249
|
+
async function handleRetry() {
|
|
250
|
+
if (retryCount < MAX_RETRIES) {
|
|
251
|
+
retryCount++
|
|
252
|
+
console.log(
|
|
253
|
+
`Attempting to restart consumer (attempt ${retryCount}/${MAX_RETRIES}) in ${RETRY_DELAY / 1000} seconds...`,
|
|
254
|
+
)
|
|
255
|
+
setTimeout(setupConsumerWithRetry, RETRY_DELAY)
|
|
256
|
+
} else {
|
|
257
|
+
console.error(`Maximum retry attempts (${MAX_RETRIES}) reached. Stopping consumer.`)
|
|
258
|
+
process.exit(1)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function setupConsumerWithRetry() {
|
|
263
|
+
try {
|
|
264
|
+
const kafkaStream = await createConsumer()
|
|
265
|
+
retryCount = 0 // Reset retry count on successful connection
|
|
266
|
+
|
|
267
|
+
console.log('Starting consumer')
|
|
268
|
+
|
|
269
|
+
kafkaStream.on('data', (message) => {
|
|
270
|
+
counter++
|
|
271
|
+
console.log('>>> Message received:', {
|
|
272
|
+
counter,
|
|
273
|
+
payload: message.payload.toString(),
|
|
274
|
+
offset: message.offset,
|
|
275
|
+
partition: message.partition,
|
|
276
|
+
topic: message.topic,
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
kafkaStream.on('error', async (error) => {
|
|
281
|
+
console.error('Stream error:', error)
|
|
282
|
+
handleRetry()
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
kafkaStream.on('close', () => {
|
|
286
|
+
console.log('Stream ended')
|
|
287
|
+
try {
|
|
288
|
+
kafkaStream.unsubscribe()
|
|
289
|
+
} catch (unsubError) {
|
|
290
|
+
console.error('Error unsubscribing:', unsubError)
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error('Error setting up consumer:', error)
|
|
295
|
+
handleRetry()
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
await setupConsumerWithRetry()
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
await startConsumer()
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Examples
|
|
306
|
+
|
|
307
|
+
You can find some examples on the [example](https://github.com/mthssdrbrg/kafka-crab-js/tree/main/example) folder of this project.
|
|
308
|
+
|
|
208
309
|
## Configuration
|
|
209
310
|
|
|
210
311
|
### Configuration properties
|
|
211
312
|
|
|
212
|
-
|
|
313
|
+
### KafkaConfiguration
|
|
314
|
+
|
|
315
|
+
| Property | Type | Default | Description |
|
|
316
|
+
| --- | --- | --- | --- |
|
|
317
|
+
| `brokers` | `string` || List of brokers to connect to |
|
|
318
|
+
| `clientId` | `string` || Client id to use for the connection |
|
|
319
|
+
| `securityProtocol` | `SecurityProtocol` || Security protocol to use (PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL) |
|
|
320
|
+
| `logLevel` | `string` | `info` | Client id to use for the connection |
|
|
321
|
+
| `brokerAddressFamily` | `string` | `"v4"` | Address family to use for the connection (v4, v6) |
|
|
322
|
+
| `configuration` | `Map<string, string>` | `{}` | Additional configuration options for the client. See [librdkafka](https://docs.confluent.io/platform/current/clients/librdkafka/html/md_CONFIGURATION.html) |
|
|
323
|
+
|
|
324
|
+
You can see the available options here: [librdkafka](https://docs.confluent.io/platform/current/clients/librdkafka/html/md_CONFIGURATION.html).
|
|
325
|
+
|
|
326
|
+
|
|
213
327
|
|
|
214
328
|
## Best Practices
|
|
215
329
|
|
|
@@ -223,11 +337,6 @@ You can see the available options here: [librdkafka](https://github.com/edenhill
|
|
|
223
337
|
- Configure appropriate batch sizes and compression
|
|
224
338
|
- Monitor and tune consumer group performance
|
|
225
339
|
|
|
226
|
-
### Resource Management
|
|
227
|
-
- Implement proper shutdown procedures
|
|
228
|
-
- Clean up resources (disconnect producers/consumers)
|
|
229
|
-
- Handle process signals (SIGTERM, SIGINT)
|
|
230
|
-
|
|
231
340
|
### Message Processing
|
|
232
341
|
- Validate message formats before processing
|
|
233
342
|
- Implement proper serialization/deserialization
|
package/js-binding.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface RetryStrategy {
|
|
|
13
13
|
}
|
|
14
14
|
export enum CommitMode {
|
|
15
15
|
Sync = 0,
|
|
16
|
-
Async = 1
|
|
16
|
+
Async = 1
|
|
17
17
|
}
|
|
18
18
|
export interface ConsumerConfiguration {
|
|
19
19
|
groupId: string
|
|
@@ -25,7 +25,7 @@ export interface ConsumerConfiguration {
|
|
|
25
25
|
export enum PartitionPosition {
|
|
26
26
|
Beginning = 'Beginning',
|
|
27
27
|
End = 'End',
|
|
28
|
-
Stored = 'Stored'
|
|
28
|
+
Stored = 'Stored'
|
|
29
29
|
}
|
|
30
30
|
export interface OffsetModel {
|
|
31
31
|
offset?: number
|
|
@@ -44,7 +44,7 @@ export enum SecurityProtocol {
|
|
|
44
44
|
Plaintext = 'Plaintext',
|
|
45
45
|
Ssl = 'Ssl',
|
|
46
46
|
SaslPlaintext = 'SaslPlaintext',
|
|
47
|
-
SaslSsl = 'SaslSsl'
|
|
47
|
+
SaslSsl = 'SaslSsl'
|
|
48
48
|
}
|
|
49
49
|
export interface KafkaConfiguration {
|
|
50
50
|
brokers: string
|
|
@@ -92,8 +92,9 @@ export declare class KafkaConsumer {
|
|
|
92
92
|
pause(): void
|
|
93
93
|
resume(): void
|
|
94
94
|
unsubscribe(): void
|
|
95
|
+
shutdownConsumer(): Promise<void>
|
|
95
96
|
seek(topic: string, partition: number, offsetModel: OffsetModel, timeout?: number | undefined | null): void
|
|
96
|
-
recv(): Promise<Message>
|
|
97
|
+
recv(): Promise<Message | null>
|
|
97
98
|
commit(topic: string, partition: number, offset: number, commit: CommitMode): void
|
|
98
99
|
}
|
|
99
100
|
export declare class KafkaClientConfig {
|
package/js-binding.js
CHANGED
|
@@ -63,7 +63,7 @@ switch (platform) {
|
|
|
63
63
|
switch (arch) {
|
|
64
64
|
case 'x64':
|
|
65
65
|
localFileExisted = existsSync(
|
|
66
|
-
join(__dirname, 'kafka-crab-js.win32-x64-msvc.node')
|
|
66
|
+
join(__dirname, 'kafka-crab-js.win32-x64-msvc.node')
|
|
67
67
|
)
|
|
68
68
|
try {
|
|
69
69
|
if (localFileExisted) {
|
|
@@ -77,7 +77,7 @@ switch (platform) {
|
|
|
77
77
|
break
|
|
78
78
|
case 'ia32':
|
|
79
79
|
localFileExisted = existsSync(
|
|
80
|
-
join(__dirname, 'kafka-crab-js.win32-ia32-msvc.node')
|
|
80
|
+
join(__dirname, 'kafka-crab-js.win32-ia32-msvc.node')
|
|
81
81
|
)
|
|
82
82
|
try {
|
|
83
83
|
if (localFileExisted) {
|
|
@@ -91,7 +91,7 @@ switch (platform) {
|
|
|
91
91
|
break
|
|
92
92
|
case 'arm64':
|
|
93
93
|
localFileExisted = existsSync(
|
|
94
|
-
join(__dirname, 'kafka-crab-js.win32-arm64-msvc.node')
|
|
94
|
+
join(__dirname, 'kafka-crab-js.win32-arm64-msvc.node')
|
|
95
95
|
)
|
|
96
96
|
try {
|
|
97
97
|
if (localFileExisted) {
|
|
@@ -132,7 +132,7 @@ switch (platform) {
|
|
|
132
132
|
break
|
|
133
133
|
case 'arm64':
|
|
134
134
|
localFileExisted = existsSync(
|
|
135
|
-
join(__dirname, 'kafka-crab-js.darwin-arm64.node')
|
|
135
|
+
join(__dirname, 'kafka-crab-js.darwin-arm64.node')
|
|
136
136
|
)
|
|
137
137
|
try {
|
|
138
138
|
if (localFileExisted) {
|
|
@@ -168,7 +168,7 @@ switch (platform) {
|
|
|
168
168
|
case 'x64':
|
|
169
169
|
if (isMusl()) {
|
|
170
170
|
localFileExisted = existsSync(
|
|
171
|
-
join(__dirname, 'kafka-crab-js.linux-x64-musl.node')
|
|
171
|
+
join(__dirname, 'kafka-crab-js.linux-x64-musl.node')
|
|
172
172
|
)
|
|
173
173
|
try {
|
|
174
174
|
if (localFileExisted) {
|
|
@@ -181,7 +181,7 @@ switch (platform) {
|
|
|
181
181
|
}
|
|
182
182
|
} else {
|
|
183
183
|
localFileExisted = existsSync(
|
|
184
|
-
join(__dirname, 'kafka-crab-js.linux-x64-gnu.node')
|
|
184
|
+
join(__dirname, 'kafka-crab-js.linux-x64-gnu.node')
|
|
185
185
|
)
|
|
186
186
|
try {
|
|
187
187
|
if (localFileExisted) {
|
|
@@ -197,7 +197,7 @@ switch (platform) {
|
|
|
197
197
|
case 'arm64':
|
|
198
198
|
if (isMusl()) {
|
|
199
199
|
localFileExisted = existsSync(
|
|
200
|
-
join(__dirname, 'kafka-crab-js.linux-arm64-musl.node')
|
|
200
|
+
join(__dirname, 'kafka-crab-js.linux-arm64-musl.node')
|
|
201
201
|
)
|
|
202
202
|
try {
|
|
203
203
|
if (localFileExisted) {
|
|
@@ -210,7 +210,7 @@ switch (platform) {
|
|
|
210
210
|
}
|
|
211
211
|
} else {
|
|
212
212
|
localFileExisted = existsSync(
|
|
213
|
-
join(__dirname, 'kafka-crab-js.linux-arm64-gnu.node')
|
|
213
|
+
join(__dirname, 'kafka-crab-js.linux-arm64-gnu.node')
|
|
214
214
|
)
|
|
215
215
|
try {
|
|
216
216
|
if (localFileExisted) {
|
|
@@ -226,7 +226,7 @@ switch (platform) {
|
|
|
226
226
|
case 'arm':
|
|
227
227
|
if (isMusl()) {
|
|
228
228
|
localFileExisted = existsSync(
|
|
229
|
-
join(__dirname, 'kafka-crab-js.linux-arm-musleabihf.node')
|
|
229
|
+
join(__dirname, 'kafka-crab-js.linux-arm-musleabihf.node')
|
|
230
230
|
)
|
|
231
231
|
try {
|
|
232
232
|
if (localFileExisted) {
|
|
@@ -239,7 +239,7 @@ switch (platform) {
|
|
|
239
239
|
}
|
|
240
240
|
} else {
|
|
241
241
|
localFileExisted = existsSync(
|
|
242
|
-
join(__dirname, 'kafka-crab-js.linux-arm-gnueabihf.node')
|
|
242
|
+
join(__dirname, 'kafka-crab-js.linux-arm-gnueabihf.node')
|
|
243
243
|
)
|
|
244
244
|
try {
|
|
245
245
|
if (localFileExisted) {
|
|
@@ -255,7 +255,7 @@ switch (platform) {
|
|
|
255
255
|
case 'riscv64':
|
|
256
256
|
if (isMusl()) {
|
|
257
257
|
localFileExisted = existsSync(
|
|
258
|
-
join(__dirname, 'kafka-crab-js.linux-riscv64-musl.node')
|
|
258
|
+
join(__dirname, 'kafka-crab-js.linux-riscv64-musl.node')
|
|
259
259
|
)
|
|
260
260
|
try {
|
|
261
261
|
if (localFileExisted) {
|
|
@@ -268,7 +268,7 @@ switch (platform) {
|
|
|
268
268
|
}
|
|
269
269
|
} else {
|
|
270
270
|
localFileExisted = existsSync(
|
|
271
|
-
join(__dirname, 'kafka-crab-js.linux-riscv64-gnu.node')
|
|
271
|
+
join(__dirname, 'kafka-crab-js.linux-riscv64-gnu.node')
|
|
272
272
|
)
|
|
273
273
|
try {
|
|
274
274
|
if (localFileExisted) {
|
|
@@ -283,7 +283,7 @@ switch (platform) {
|
|
|
283
283
|
break
|
|
284
284
|
case 's390x':
|
|
285
285
|
localFileExisted = existsSync(
|
|
286
|
-
join(__dirname, 'kafka-crab-js.linux-s390x-gnu.node')
|
|
286
|
+
join(__dirname, 'kafka-crab-js.linux-s390x-gnu.node')
|
|
287
287
|
)
|
|
288
288
|
try {
|
|
289
289
|
if (localFileExisted) {
|
|
@@ -310,8 +310,7 @@ if (!nativeBinding) {
|
|
|
310
310
|
throw new Error(`Failed to load native binding`)
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
const { KafkaConsumer, CommitMode, PartitionPosition, SecurityProtocol, KafkaClientConfig, KafkaProducer } =
|
|
314
|
-
nativeBinding
|
|
313
|
+
const { KafkaConsumer, CommitMode, PartitionPosition, SecurityProtocol, KafkaClientConfig, KafkaProducer } = nativeBinding
|
|
315
314
|
|
|
316
315
|
module.exports.KafkaConsumer = KafkaConsumer
|
|
317
316
|
module.exports.CommitMode = CommitMode
|
package/kafka-client.js
CHANGED
|
@@ -13,7 +13,7 @@ class KafkaClient {
|
|
|
13
13
|
*/
|
|
14
14
|
constructor(kafkaConfiguration) {
|
|
15
15
|
this.kafkaConfiguration = kafkaConfiguration;
|
|
16
|
-
this.kafkaClientConfig = new js_binding_js_1.KafkaClientConfig(kafkaConfiguration);
|
|
16
|
+
this.kafkaClientConfig = new js_binding_js_1.KafkaClientConfig(this.kafkaConfiguration);
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Creates a KafkaProducer instance
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Readable } from 'stream';
|
|
2
|
-
import {
|
|
2
|
+
import { CommitMode } from './js-binding';
|
|
3
|
+
import { KafkaConsumer, OffsetModel, TopicPartitionConfig } from './js-binding';
|
|
3
4
|
/**
|
|
4
5
|
* KafkaStreamReadable class
|
|
5
6
|
* @extends Readable
|
|
@@ -14,6 +15,8 @@ export declare class KafkaStreamReadable extends Readable {
|
|
|
14
15
|
* Subscribes to topics
|
|
15
16
|
*/
|
|
16
17
|
subscribe(topics: string | Array<TopicPartitionConfig>): Promise<void>;
|
|
18
|
+
seek(topic: string, partition: number, offsetModel: OffsetModel, timeout?: number | undefined): void;
|
|
19
|
+
commit(topic: string, partition: number, offset: number, commit: CommitMode): void;
|
|
17
20
|
/**
|
|
18
21
|
* Unsubscribe from topics
|
|
19
22
|
*/
|
package/kafka-stream-readable.js
CHANGED
|
@@ -27,6 +27,12 @@ class KafkaStreamReadable extends stream_1.Readable {
|
|
|
27
27
|
}
|
|
28
28
|
await this.kafkaConsumer.subscribe(topics);
|
|
29
29
|
}
|
|
30
|
+
seek(topic, partition, offsetModel, timeout) {
|
|
31
|
+
this.kafkaConsumer.seek(topic, partition, offsetModel, timeout);
|
|
32
|
+
}
|
|
33
|
+
commit(topic, partition, offset, commit) {
|
|
34
|
+
this.kafkaConsumer.commit(topic, partition, offset, commit);
|
|
35
|
+
}
|
|
30
36
|
/**
|
|
31
37
|
* Unsubscribe from topics
|
|
32
38
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kafka-crab-js",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"types": "index.d.ts",
|
|
6
6
|
"module": "commonjs",
|
|
@@ -40,13 +40,13 @@
|
|
|
40
40
|
"url": "https://github.com/inaiat/kafka-crab-js.git"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@faker-js/faker": "9.
|
|
43
|
+
"@faker-js/faker": "9.3.0",
|
|
44
44
|
"@napi-rs/cli": "^2.18.4",
|
|
45
|
-
"@types/node": "22.
|
|
45
|
+
"@types/node": "22.10.1",
|
|
46
46
|
"ava": "6.2.0",
|
|
47
|
-
"dprint": "^0.
|
|
48
|
-
"nanoid": "5.0.
|
|
49
|
-
"typescript": "5.
|
|
47
|
+
"dprint": "^0.48.0",
|
|
48
|
+
"nanoid": "5.0.9",
|
|
49
|
+
"typescript": "5.7.2",
|
|
50
50
|
"copyfiles": "^2.4.1",
|
|
51
51
|
"@types/copyfiles": "^2.4.4",
|
|
52
52
|
"rimraf": "^6.0.1"
|
|
@@ -65,10 +65,10 @@
|
|
|
65
65
|
"version": "napi version",
|
|
66
66
|
"fmt": "dprint fmt"
|
|
67
67
|
},
|
|
68
|
-
"packageManager": "pnpm@9.
|
|
68
|
+
"packageManager": "pnpm@9.15.2",
|
|
69
69
|
"optionalDependencies": {
|
|
70
|
-
"kafka-crab-js-darwin-arm64": "1.0.
|
|
71
|
-
"kafka-crab-js-darwin-x64": "1.0.
|
|
72
|
-
"kafka-crab-js-linux-x64-gnu": "1.0.
|
|
70
|
+
"kafka-crab-js-darwin-arm64": "1.0.5",
|
|
71
|
+
"kafka-crab-js-darwin-x64": "1.0.5",
|
|
72
|
+
"kafka-crab-js-linux-x64-gnu": "1.0.5"
|
|
73
73
|
}
|
|
74
74
|
}
|