kafka-crab-js 4.1.2 → 5.0.0
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 +317 -1073
- package/dist/diagnostics/channels.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/kafka-client.cjs +92 -31
- package/dist/kafka-client.cjs.map +1 -1
- package/dist/kafka-client.d.cts +20 -8
- package/dist/kafka-client.d.ts +20 -8
- package/dist/kafka-client.js +92 -32
- package/dist/kafka-client.js.map +1 -1
- package/dist/send-failure-DMmbzTJn.d.cts +18 -0
- package/dist/send-failure-DMmbzTJn.d.ts +18 -0
- package/dist/send-failure.cjs +30 -0
- package/dist/send-failure.cjs.map +1 -0
- package/dist/send-failure.d.cts +2 -0
- package/dist/send-failure.d.ts +2 -0
- package/dist/send-failure.js +28 -0
- package/dist/send-failure.js.map +1 -0
- package/dist/streams/base-kafka-stream-readable.cjs +5 -1
- package/dist/streams/base-kafka-stream-readable.cjs.map +1 -1
- package/dist/streams/base-kafka-stream-readable.js +5 -1
- package/dist/streams/base-kafka-stream-readable.js.map +1 -1
- package/dist/streams/kafka-batch-stream-readable.cjs +1 -1
- package/dist/streams/kafka-batch-stream-readable.cjs.map +1 -1
- package/dist/streams/kafka-batch-stream-readable.d.cts +2 -3
- package/dist/streams/kafka-batch-stream-readable.d.ts +2 -3
- package/dist/streams/kafka-batch-stream-readable.js +1 -1
- package/dist/streams/kafka-batch-stream-readable.js.map +1 -1
- package/dist/streams/kafka-stream-readable.d.cts +1 -2
- package/dist/streams/kafka-stream-readable.d.ts +1 -2
- package/js-binding.cjs +81 -82
- package/js-binding.d.ts +28 -15
- package/js-binding.js +81 -83
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -1,1131 +1,375 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
delta and peak heap than both. Treat benchmark numbers as workload-specific; the full methodology and memory/GC
|
|
47
|
-
breakdown are documented in [BENCHMARKS.md](../../BENCHMARKS.md) and [Performance Benchmarks](#performance-benchmarks).
|
|
48
|
-
|
|
49
|
-
## Highlights
|
|
50
|
-
|
|
51
|
-
- Native librdkafka client exposed through a TypeScript-friendly API.
|
|
52
|
-
- Producer, direct consumer, Node.js `Readable` stream, and v4 native Web Stream consumer APIs.
|
|
53
|
-
- High-throughput batch receive APIs for workloads that can process more than one message at a time.
|
|
54
|
-
- Compact batch streaming and serial prefetching to reduce native boundary crossings.
|
|
55
|
-
- Manual commit helpers that commit `message.offset + 1` correctly.
|
|
56
|
-
- Optional diagnostics-channel instrumentation for OpenTelemetry through `kafka-crab-js-otel`.
|
|
57
|
-
- Advanced librdkafka settings are forwarded through `configuration` without forcing a custom allowlist.
|
|
58
|
-
|
|
59
|
-
## Requirements
|
|
60
|
-
|
|
61
|
-
- Node.js `>= 22`.
|
|
62
|
-
- A Kafka broker reachable from the Node.js process.
|
|
63
|
-
- No separate librdkafka install is required for the published binaries.
|
|
64
|
-
|
|
65
|
-
## Table Of Contents
|
|
66
|
-
|
|
67
|
-
1. [Version 4](#version-4)
|
|
68
|
-
2. [Installation](#installation)
|
|
69
|
-
3. [Module Usage](#module-usage)
|
|
70
|
-
4. [Quick Start](#quick-start)
|
|
71
|
-
5. [Message Model](#message-model)
|
|
72
|
-
6. [Choosing A Consumer API](#choosing-a-consumer-api)
|
|
73
|
-
7. [Producer API](#producer-api)
|
|
74
|
-
8. [Consumer API](#consumer-api)
|
|
75
|
-
9. [Stream Consumers](#stream-consumers)
|
|
76
|
-
10. [Batching, Backpressure, And Tuning](#batching-backpressure-and-tuning)
|
|
77
|
-
11. [Configuration](#configuration)
|
|
78
|
-
12. [OpenTelemetry](#opentelemetry)
|
|
79
|
-
13. [Performance Benchmarks](#performance-benchmarks)
|
|
80
|
-
14. [Migration Notes](#migration-notes)
|
|
81
|
-
15. [Troubleshooting](#troubleshooting)
|
|
82
|
-
16. [Development](#development)
|
|
83
|
-
17. [License](#license)
|
|
84
|
-
|
|
85
|
-
## Installation
|
|
86
|
-
|
|
87
|
-
```bash
|
|
1
|
+
# kafka-crab-js
|
|
2
|
+
|
|
3
|
+
**Native Kafka performance. A JavaScript API.**
|
|
4
|
+
|
|
5
|
+
Build Kafka services in JavaScript and TypeScript with Rust, NAPI-RS, and librdkafka
|
|
6
|
+
handling the native transport. Publish with delivery confirmations, consume through
|
|
7
|
+
Web Streams or Node.js streams, and process batches without writing native code.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/kafka-crab-js)
|
|
10
|
+
[](https://nodejs.org/)
|
|
11
|
+
[](https://opensource.org/licenses/MIT)
|
|
12
|
+
|
|
13
|
+
[Quick start](#quick-start) · [Benchmarks](#benchmarks) · [Choose your API](#choose-your-api) ·
|
|
14
|
+
[API reference](docs/api.md) · [Examples](../../examples/kafka/README.md) ·
|
|
15
|
+
[Production recommendations](#production-recommendations)
|
|
16
|
+
|
|
17
|
+
## Why kafka-crab-js
|
|
18
|
+
|
|
19
|
+
Kafka should move your events without consuming your entire JavaScript heap.
|
|
20
|
+
`kafka-crab-js` puts Kafka transport and batch collection in native code while
|
|
21
|
+
keeping application logic in familiar async JavaScript.
|
|
22
|
+
|
|
23
|
+
- **Batch throughput that makes a difference.** The current development snapshot
|
|
24
|
+
delivered **1.66 million messages/s**, **1.98× KafkaJS `eachBatch`**, in the
|
|
25
|
+
[reproducible consumer comparison](../../BENCHMARKS.md).
|
|
26
|
+
- **More room in the JavaScript heap.** That batch scenario peaked at **29.0 MiB
|
|
27
|
+
of JS heap versus KafkaJS's 71.7 MiB**. Native memory still contributes to RSS;
|
|
28
|
+
the comparison reports both.
|
|
29
|
+
- **An API for each processing style.** Use `Message[]` Web Stream batches for
|
|
30
|
+
bulk writes, individual messages for sequential handlers, Node streams for
|
|
31
|
+
pipelines, or `recvBatch()` for your own receive loop.
|
|
32
|
+
- **Explicit delivery and offset control.** Await producer results, commit after
|
|
33
|
+
successful processing, and preserve keys, headers, and tombstones across batching.
|
|
34
|
+
- **librdkafka configuration when you need it.** Configure TLS/SASL, compression,
|
|
35
|
+
idempotent production, fetch behavior, and prefetch through the native client.
|
|
36
|
+
- **Observability without bundling an OTEL SDK into the core.** Diagnostic channels
|
|
37
|
+
integrate with the optional [`kafka-crab-js-otel`](../kafka-crab-js-otel/README.md) package.
|
|
38
|
+
|
|
39
|
+
The measurements above describe this source tree's development build, not the
|
|
40
|
+
currently installed npm release. The
|
|
41
|
+
[artifact fingerprints and methodology](../../BENCHMARKS.md#methodology) identify the tested build.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```sh
|
|
88
46
|
npm install kafka-crab-js
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
```bash
|
|
47
|
+
# or
|
|
92
48
|
pnpm add kafka-crab-js
|
|
93
49
|
```
|
|
94
50
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
## Module Usage
|
|
100
|
-
|
|
101
|
-
The package publishes both ESM and CommonJS entry points:
|
|
102
|
-
|
|
103
|
-
```ts
|
|
104
|
-
import { KafkaClient } from 'kafka-crab-js'
|
|
105
|
-
import type { Message, ProducerRecord } from 'kafka-crab-js'
|
|
106
|
-
```
|
|
51
|
+
Use **Node.js 24**. Prebuilt binaries cover macOS x64/arm64 and Linux x64/arm64
|
|
52
|
+
with glibc or musl. Published binaries include librdkafka; supported installations
|
|
53
|
+
do not need a separate Rust toolchain or librdkafka installation. ESM, CommonJS,
|
|
54
|
+
and TypeScript declarations are included.
|
|
107
55
|
|
|
108
56
|
```js
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
The public TypeScript types are generated from the native NAPI contract and the JavaScript wrapper. Runtime enum-like
|
|
113
|
-
values such as commit modes and security protocols are string literals, not runtime objects:
|
|
114
|
-
|
|
115
|
-
```ts
|
|
116
|
-
await consumer.commitMessage(message, 'Sync')
|
|
117
|
-
|
|
118
|
-
const client = new KafkaClient({
|
|
119
|
-
brokers: 'localhost:9092',
|
|
120
|
-
securityProtocol: 'Plaintext',
|
|
121
|
-
})
|
|
57
|
+
import { KafkaClient } from "kafka-crab-js";
|
|
58
|
+
// CommonJS: const { KafkaClient } = require('kafka-crab-js')
|
|
122
59
|
```
|
|
123
60
|
|
|
124
|
-
|
|
61
|
+
This is a native Node.js client. Web Streams support refers to Node's Web Stream
|
|
62
|
+
API; browser, WASM, and Windows builds are not provided. Documentation on the main
|
|
63
|
+
branch describes the current source; use a matching release tag for older packages.
|
|
125
64
|
|
|
126
|
-
###
|
|
65
|
+
### Upgrading to 5.0
|
|
127
66
|
|
|
128
|
-
|
|
129
|
-
|
|
67
|
+
Version 5.0 requires Node.js 24. The previous release supported Node.js 22 and
|
|
68
|
+
newer; update service runtimes and container images before upgrading.
|
|
130
69
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
70
|
+
Producer and consumer entrypoints remain available. Review the stricter
|
|
71
|
+
[commit and shutdown contracts](docs/api.md#commits-and-processing-order): manual
|
|
72
|
+
Async commits require a live event listener, mixed group subscriptions and manual
|
|
73
|
+
assignments are rejected, and Node.js consumer streams require object mode.
|
|
74
|
+
Failed sends expose per-call delivery details through `SendFailureError`; use
|
|
75
|
+
those details when deciding retries after partial delivery.
|
|
136
76
|
|
|
137
|
-
|
|
77
|
+
## Quick start
|
|
138
78
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
{
|
|
143
|
-
key: Buffer.from('order-123'),
|
|
144
|
-
payload: Buffer.from(JSON.stringify({ id: 'order-123', status: 'created' })),
|
|
145
|
-
headers: {
|
|
146
|
-
'content-type': Buffer.from('application/json'),
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
],
|
|
150
|
-
})
|
|
79
|
+
Run a Kafka broker and create an `orders` topic before starting these examples.
|
|
80
|
+
Use `KAFKA_BROKERS` for a comma-separated broker list; the examples default to
|
|
81
|
+
`localhost:9092`. Save the following files as `.mjs` and run with Node.js 24.
|
|
151
82
|
|
|
152
|
-
|
|
153
|
-
```
|
|
83
|
+
### Publish with confirmation
|
|
154
84
|
|
|
155
|
-
|
|
85
|
+
`producer.mjs`:
|
|
156
86
|
|
|
157
|
-
```
|
|
158
|
-
import { KafkaClient } from
|
|
87
|
+
```js
|
|
88
|
+
import { KafkaClient } from "kafka-crab-js";
|
|
159
89
|
|
|
160
90
|
const client = new KafkaClient({
|
|
161
|
-
brokers:
|
|
162
|
-
clientId:
|
|
163
|
-
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
const consumer = client.createConsumer({
|
|
167
|
-
groupId: 'orders-worker',
|
|
168
|
-
enableAutoCommit: false,
|
|
169
|
-
configuration: {
|
|
170
|
-
'auto.offset.reset': 'earliest',
|
|
171
|
-
},
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
await consumer.subscribe('orders')
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
while (true) {
|
|
178
|
-
const message = await consumer.recv()
|
|
179
|
-
if (!message) {
|
|
180
|
-
break
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const value = JSON.parse(message.payload.toString('utf8'))
|
|
184
|
-
console.log({ value, topic: message.topic, partition: message.partition, offset: message.offset })
|
|
185
|
-
|
|
186
|
-
await consumer.commitMessage(message, 'Sync')
|
|
187
|
-
}
|
|
188
|
-
} finally {
|
|
189
|
-
await consumer.disconnect()
|
|
190
|
-
}
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
### Consume In Batches
|
|
194
|
-
|
|
195
|
-
```ts
|
|
196
|
-
const consumer = client.createConsumer({
|
|
197
|
-
groupId: 'orders-batch-worker',
|
|
198
|
-
enableAutoCommit: false,
|
|
199
|
-
configuration: {
|
|
200
|
-
'auto.offset.reset': 'earliest',
|
|
201
|
-
},
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
await consumer.subscribe('orders')
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
while (true) {
|
|
208
|
-
const batch = await consumer.recvBatch(500, 50)
|
|
209
|
-
if (batch.length === 0) {
|
|
210
|
-
continue
|
|
211
|
-
}
|
|
91
|
+
brokers: process.env.KAFKA_BROKERS ?? "localhost:9092",
|
|
92
|
+
clientId: "orders-producer",
|
|
93
|
+
});
|
|
212
94
|
|
|
213
|
-
for (const message of batch) {
|
|
214
|
-
await processOrder(message.payload)
|
|
215
|
-
await consumer.commitMessage(message, 'Async')
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
} finally {
|
|
219
|
-
await consumer.disconnect()
|
|
220
|
-
}
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
## Message Model
|
|
224
|
-
|
|
225
|
-
Messages use `Buffer` values because the Kafka protocol treats keys, payloads, and headers as bytes. Decode them at the
|
|
226
|
-
edge of your application instead of assuming text:
|
|
227
|
-
|
|
228
|
-
```ts
|
|
229
|
-
import type { Message, MessageProducer, RecordMetadata } from 'kafka-crab-js'
|
|
230
|
-
|
|
231
|
-
const outgoing: MessageProducer = {
|
|
232
|
-
key: Buffer.from('order-123'),
|
|
233
|
-
payload: Buffer.from(JSON.stringify({ id: 'order-123' })),
|
|
234
|
-
headers: {
|
|
235
|
-
'content-type': Buffer.from('application/json'),
|
|
236
|
-
source: Buffer.from('orders-api'),
|
|
237
|
-
},
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function decodeJsonMessage(message: Message) {
|
|
241
|
-
return JSON.parse(message.payload.toString('utf8')) as unknown
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function logDelivery(records: RecordMetadata[]) {
|
|
245
|
-
for (const record of records) {
|
|
246
|
-
console.log(`${record.topic}[${record.partition}]@${record.offset}`)
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
`Message` values contain the consumed Kafka offset. When committing manually, commit the next offset. Prefer
|
|
252
|
-
`commitMessage()` unless you intentionally need to calculate offsets yourself:
|
|
253
|
-
|
|
254
|
-
```ts
|
|
255
|
-
await consumer.commitMessage(message, 'Sync')
|
|
256
|
-
await consumer.commit(message.topic, message.partition, message.offset + 1, 'Async')
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
Keys are optional. An omitted key keeps Kafka producer partitioning semantics for keyless records. An empty
|
|
260
|
-
`Buffer.alloc(0)` is still a present key.
|
|
261
|
-
|
|
262
|
-
## Choosing A Consumer API
|
|
263
|
-
|
|
264
|
-
| API | Emits | Best For |
|
|
265
|
-
| ---------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------ |
|
|
266
|
-
| `consumer.recv()` | `Message \| null` | Simple sequential workers and explicit control. |
|
|
267
|
-
| `consumer.recvBatch()` | `Message[]` | High-throughput workers that can process chunks. |
|
|
268
|
-
| `consumer.recvStream()` | Web `ReadableStream<Message>` | Pull-based Web Stream workflows. |
|
|
269
|
-
| `consumer.recvBatchStream()` | Web `ReadableStream<Message[]>` | Native batch streaming with explicit consumer control. |
|
|
270
|
-
| `client.createStreamConsumer()` | Node.js `Readable` emitting `Message` | Existing Node stream pipelines. |
|
|
271
|
-
| `client.createWebStreamConsumer()` | Web `ReadableStream<Message>` or `ReadableStream<Message[]>` | v4 high-throughput stream consumers. |
|
|
272
|
-
|
|
273
|
-
Two details matter for choosing correctly:
|
|
274
|
-
|
|
275
|
-
- `createStreamConsumer()` is a Node.js `Readable` compatibility API. Even when `batchSize > 1`, it emits individual
|
|
276
|
-
`Message` objects and uses batching internally to reduce native boundary crossings.
|
|
277
|
-
- `createWebStreamConsumer()` returns a discriminated object. In serial mode it emits `Message`; in batch mode it emits
|
|
278
|
-
`Message[]`. Use this API when your code can process whole batches directly.
|
|
279
|
-
|
|
280
|
-
Recommended starting points:
|
|
281
|
-
|
|
282
|
-
- Use `consumer.recv()` for straightforward workers where one handler processes one message and then commits.
|
|
283
|
-
- Use `consumer.recvBatch()` when the handler can process chunks, flush work in groups, or commit after a batch.
|
|
284
|
-
- Use `createStreamConsumer()` when existing code already expects a Node.js `Readable`.
|
|
285
|
-
- Use `createWebStreamConsumer()` for new v4 stream code, especially when you can keep batch chunks intact.
|
|
286
|
-
|
|
287
|
-
## Producer API
|
|
288
|
-
|
|
289
|
-
Create a producer from a `KafkaClient`:
|
|
290
|
-
|
|
291
|
-
```ts
|
|
292
95
|
const producer = client.createProducer({
|
|
293
|
-
autoFlush: true,
|
|
294
|
-
queueTimeout: 5000,
|
|
295
96
|
configuration: {
|
|
296
|
-
|
|
297
|
-
acks:
|
|
97
|
+
"enable.idempotence": true,
|
|
98
|
+
acks: "all",
|
|
99
|
+
"compression.type": "lz4",
|
|
298
100
|
},
|
|
299
|
-
})
|
|
300
|
-
```
|
|
101
|
+
});
|
|
301
102
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
```ts
|
|
305
|
-
const records = await producer.send({
|
|
306
|
-
topic: 'orders',
|
|
103
|
+
const deliveries = await producer.send({
|
|
104
|
+
topic: "orders",
|
|
307
105
|
messages: [
|
|
308
|
-
{
|
|
309
|
-
|
|
106
|
+
{
|
|
107
|
+
key: Buffer.from("order-42"),
|
|
108
|
+
payload: Buffer.from(
|
|
109
|
+
JSON.stringify({ orderId: "order-42", total: 129.9 }),
|
|
110
|
+
),
|
|
111
|
+
headers: { "content-type": Buffer.from("application/json") },
|
|
112
|
+
},
|
|
310
113
|
],
|
|
311
|
-
})
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
When `autoFlush` is enabled, `send()` waits for delivery confirmation and returns `RecordMetadata[]`. When `autoFlush`
|
|
315
|
-
is disabled, `send()` buffers messages and `flush()` sends pending messages:
|
|
114
|
+
});
|
|
316
115
|
|
|
317
|
-
|
|
318
|
-
const producer = client.createProducer({ autoFlush: false })
|
|
319
|
-
|
|
320
|
-
await producer.send({
|
|
321
|
-
topic: 'orders',
|
|
322
|
-
messages: [{ payload: Buffer.from('buffered') }],
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
const records = await producer.flush()
|
|
326
|
-
console.log(records)
|
|
116
|
+
console.log("Delivered:", deliveries);
|
|
327
117
|
```
|
|
328
118
|
|
|
329
|
-
`
|
|
330
|
-
|
|
331
|
-
|
|
119
|
+
By default, `send()` waits for delivery results and rejects on failure. For
|
|
120
|
+
high-volume publishing, send multiple records together and bound concurrent sends.
|
|
121
|
+
If you opt into `autoFlush: false`, call `flush()` regularly and before shutdown.
|
|
122
|
+
Records can be transmitted before a manual flush; it collects confirmations.
|
|
123
|
+
See [producer delivery semantics](docs/api.md#delivery-and-partial-failures).
|
|
332
124
|
|
|
333
|
-
|
|
334
|
-
resolving. Delivery metadata can still contain a per-record `error`, so production code should inspect it when failed
|
|
335
|
-
records must be retried or reported:
|
|
336
|
-
|
|
337
|
-
```ts
|
|
338
|
-
const records = await producer.send({
|
|
339
|
-
topic: 'orders',
|
|
340
|
-
messages: [{ payload: Buffer.from('created') }],
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
for (const record of records) {
|
|
344
|
-
if (record.error) {
|
|
345
|
-
throw new Error(`Kafka delivery failed: ${record.error.message}`)
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
```
|
|
125
|
+
### Consume batches and commit after processing
|
|
349
126
|
|
|
350
|
-
|
|
351
|
-
This can improve throughput for bursty producers, but shutdown code must flush before the process exits:
|
|
127
|
+
`consumer.mjs`:
|
|
352
128
|
|
|
353
|
-
```
|
|
354
|
-
import
|
|
355
|
-
|
|
356
|
-
const producer = client.createProducer({ autoFlush: false })
|
|
357
|
-
|
|
358
|
-
function assertAllDelivered(records: RecordMetadata[]) {
|
|
359
|
-
for (const record of records) {
|
|
360
|
-
if (record.error) {
|
|
361
|
-
throw new Error(`Kafka delivery failed: ${record.error.message}`)
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
try {
|
|
367
|
-
await producer.send({ topic: 'orders', messages })
|
|
368
|
-
const records = await producer.flush()
|
|
369
|
-
assertAllDelivered(records)
|
|
370
|
-
} finally {
|
|
371
|
-
const remaining = await producer.flush()
|
|
372
|
-
assertAllDelivered(remaining)
|
|
373
|
-
}
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
## Consumer API
|
|
377
|
-
|
|
378
|
-
### Subscribe
|
|
379
|
-
|
|
380
|
-
The simplest subscription accepts a topic string:
|
|
381
|
-
|
|
382
|
-
```ts
|
|
383
|
-
await consumer.subscribe('orders')
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
Use `TopicPartitionConfig[]` when you need offsets, partition-specific offsets, or topic creation:
|
|
387
|
-
|
|
388
|
-
```ts
|
|
389
|
-
await consumer.subscribe([
|
|
390
|
-
{
|
|
391
|
-
topic: 'orders',
|
|
392
|
-
createTopic: true,
|
|
393
|
-
numPartitions: 3,
|
|
394
|
-
replicas: 1,
|
|
395
|
-
allOffsets: { position: 'Beginning' },
|
|
396
|
-
},
|
|
397
|
-
])
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
For partition-specific offsets:
|
|
401
|
-
|
|
402
|
-
```ts
|
|
403
|
-
await consumer.subscribe([
|
|
404
|
-
{
|
|
405
|
-
topic: 'orders',
|
|
406
|
-
partitionOffset: [
|
|
407
|
-
{
|
|
408
|
-
partition: 0,
|
|
409
|
-
offset: { offset: 42 },
|
|
410
|
-
},
|
|
411
|
-
],
|
|
412
|
-
},
|
|
413
|
-
])
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
### Receive
|
|
417
|
-
|
|
418
|
-
`recv()` waits for one message and returns `null` when the consumer is disconnected:
|
|
419
|
-
|
|
420
|
-
```ts
|
|
421
|
-
const message = await consumer.recv()
|
|
422
|
-
if (message) {
|
|
423
|
-
await handleMessage(message)
|
|
424
|
-
}
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
`recvBatch(size, timeoutMs)` returns up to `size` messages:
|
|
428
|
-
|
|
429
|
-
```ts
|
|
430
|
-
const messages = await consumer.recvBatch(1000, 100)
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
An empty array means the timeout elapsed without a full batch. It does not mean the consumer is closed.
|
|
434
|
-
|
|
435
|
-
### Commit
|
|
436
|
-
|
|
437
|
-
Use `commitMessage()` for normal message processing. It commits `message.offset + 1`, which is the offset Kafka expects:
|
|
438
|
-
|
|
439
|
-
```ts
|
|
440
|
-
await consumer.commitMessage(message, 'Sync')
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
Use `commit()` when you already calculated the offset:
|
|
444
|
-
|
|
445
|
-
```ts
|
|
446
|
-
await consumer.commit(message.topic, message.partition, message.offset + 1, 'Async')
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
Commit modes are string literal values:
|
|
450
|
-
|
|
451
|
-
```ts
|
|
452
|
-
type CommitMode = 'Sync' | 'Async'
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
For at-least-once processing, disable auto commit, process the message successfully, then commit. If processing fails
|
|
456
|
-
before the commit, Kafka can redeliver that message after restart or rebalance:
|
|
457
|
-
|
|
458
|
-
```ts
|
|
459
|
-
const consumer = client.createConsumer({
|
|
460
|
-
groupId: 'orders-worker',
|
|
461
|
-
enableAutoCommit: false,
|
|
462
|
-
})
|
|
463
|
-
|
|
464
|
-
const message = await consumer.recv()
|
|
465
|
-
if (message) {
|
|
466
|
-
await processOrder(message)
|
|
467
|
-
await consumer.commitMessage(message, 'Sync')
|
|
468
|
-
}
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
`'Sync'` waits for the broker commit response. `'Async'` schedules the commit through librdkafka and returns sooner,
|
|
472
|
-
which can be useful in high-throughput workers that tolerate the usual async commit tradeoff.
|
|
473
|
-
|
|
474
|
-
### Pause, Resume, Seek, And Assignment
|
|
475
|
-
|
|
476
|
-
```ts
|
|
477
|
-
consumer.pause()
|
|
478
|
-
consumer.resume()
|
|
479
|
-
|
|
480
|
-
consumer.seek('orders', 0, { position: 'Beginning' })
|
|
481
|
-
consumer.seek('orders', 0, { offset: 42 })
|
|
482
|
-
|
|
483
|
-
const assignment = consumer.assignment()
|
|
484
|
-
const subscription = consumer.getSubscription()
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
Consumer events are available through `onEvents()`:
|
|
488
|
-
|
|
489
|
-
```ts
|
|
490
|
-
consumer.onEvents((error, event) => {
|
|
491
|
-
if (error) {
|
|
492
|
-
console.error(error)
|
|
493
|
-
return
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
console.log(event.name, event.payload)
|
|
497
|
-
})
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
Event names are currently `PreRebalance`, `PostRebalance`, and `CommitCallback`. The payload includes the topic-partition
|
|
501
|
-
list associated with the event and may include an `error` string for commit or rebalance failures.
|
|
502
|
-
|
|
503
|
-
### Cleanup
|
|
504
|
-
|
|
505
|
-
Always disconnect direct consumers:
|
|
506
|
-
|
|
507
|
-
```ts
|
|
508
|
-
try {
|
|
509
|
-
await consumer.subscribe('orders')
|
|
510
|
-
// consume...
|
|
511
|
-
} finally {
|
|
512
|
-
await consumer.disconnect()
|
|
513
|
-
}
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
## Stream Consumers
|
|
517
|
-
|
|
518
|
-
### Node.js Readable Stream
|
|
129
|
+
```js
|
|
130
|
+
import { KafkaClient } from "kafka-crab-js";
|
|
519
131
|
|
|
520
|
-
|
|
132
|
+
const client = new KafkaClient({
|
|
133
|
+
brokers: process.env.KAFKA_BROKERS ?? "localhost:9092",
|
|
134
|
+
clientId: "orders-consumer",
|
|
135
|
+
});
|
|
521
136
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
groupId: 'orders-stream',
|
|
137
|
+
const { consumer, stream } = client.createWebStreamConsumer({
|
|
138
|
+
groupId: "orders-workers",
|
|
525
139
|
enableAutoCommit: false,
|
|
526
|
-
batchSize:
|
|
527
|
-
batchTimeout:
|
|
140
|
+
batchSize: 64,
|
|
141
|
+
batchTimeout: 5,
|
|
528
142
|
configuration: {
|
|
529
|
-
|
|
143
|
+
"enable.auto.offset.store": false,
|
|
144
|
+
"auto.offset.reset": "earliest",
|
|
530
145
|
},
|
|
531
|
-
})
|
|
532
|
-
|
|
533
|
-
await stream.subscribe('orders')
|
|
534
|
-
|
|
535
|
-
stream.on('data', async (message) => {
|
|
536
|
-
try {
|
|
537
|
-
await processOrder(message.payload)
|
|
538
|
-
await stream.commitMessage(message, 'Async')
|
|
539
|
-
} catch (error) {
|
|
540
|
-
stream.destroy(error instanceof Error ? error : new Error(String(error)))
|
|
541
|
-
}
|
|
542
|
-
})
|
|
543
|
-
|
|
544
|
-
stream.on('error', (error) => {
|
|
545
|
-
console.error('stream error', error)
|
|
546
|
-
})
|
|
547
|
-
|
|
548
|
-
stream.on('close', () => {
|
|
549
|
-
console.log('stream closed')
|
|
550
|
-
})
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
Destroy stream consumers instead of calling `disconnect()` directly. The stream cleanup path cancels the source reader,
|
|
554
|
-
unsubscribes, and disconnects the native consumer:
|
|
555
|
-
|
|
556
|
-
```ts
|
|
557
|
-
stream.destroy()
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
### Native Web Stream
|
|
561
|
-
|
|
562
|
-
Use `createWebStreamConsumer()` when you want a Web Stream and clear serial/batch typing.
|
|
563
|
-
|
|
564
|
-
Serial mode is selected when `batchSize` is omitted, `0`, or `1`:
|
|
565
|
-
|
|
566
|
-
```ts
|
|
567
|
-
const webConsumer = client.createWebStreamConsumer({
|
|
568
|
-
groupId: 'orders-web-serial',
|
|
569
|
-
serialPrefetchSize: 64,
|
|
570
|
-
serialPrefetchTimeout: 1,
|
|
571
|
-
configuration: {
|
|
572
|
-
'auto.offset.reset': 'earliest',
|
|
573
|
-
},
|
|
574
|
-
})
|
|
575
|
-
|
|
576
|
-
await webConsumer.consumer.subscribe('orders')
|
|
146
|
+
});
|
|
577
147
|
|
|
578
|
-
const reader =
|
|
148
|
+
const reader = stream.getReader();
|
|
149
|
+
const stop = () => {
|
|
150
|
+
void reader.cancel("shutdown").catch(console.error);
|
|
151
|
+
};
|
|
152
|
+
process.once("SIGINT", stop);
|
|
153
|
+
process.once("SIGTERM", stop);
|
|
579
154
|
|
|
580
155
|
try {
|
|
581
|
-
|
|
582
|
-
if (!done && message) {
|
|
583
|
-
await processOrder(message.payload)
|
|
584
|
-
await webConsumer.consumer.commitMessage(message, 'Async')
|
|
585
|
-
}
|
|
586
|
-
} finally {
|
|
587
|
-
await reader.cancel()
|
|
588
|
-
await webConsumer.consumer.disconnect()
|
|
589
|
-
}
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
Batch mode is selected with `batchSize > 1` and emits `Message[]` chunks:
|
|
156
|
+
await consumer.subscribe("orders");
|
|
593
157
|
|
|
594
|
-
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
await webConsumer.consumer.subscribe('orders')
|
|
606
|
-
const reader = webConsumer.stream.getReader()
|
|
607
|
-
|
|
608
|
-
try {
|
|
609
|
-
const { value: batch, done } = await reader.read()
|
|
610
|
-
if (!done && batch) {
|
|
611
|
-
for (const message of batch) {
|
|
612
|
-
await processOrder(message.payload)
|
|
158
|
+
while (true) {
|
|
159
|
+
const { value: messages, done } = await reader.read();
|
|
160
|
+
if (done) break;
|
|
161
|
+
|
|
162
|
+
const processed = new Map();
|
|
163
|
+
for (const message of messages) {
|
|
164
|
+
// Replace this with an awaited, idempotent database write or handler.
|
|
165
|
+
if (message.isTombstone) {
|
|
166
|
+
console.log("Delete:", message.key?.toString());
|
|
167
|
+
} else {
|
|
168
|
+
console.log("Order:", JSON.parse(message.payload.toString()));
|
|
613
169
|
}
|
|
170
|
+
processed.set(
|
|
171
|
+
JSON.stringify([message.topic, message.partition]),
|
|
172
|
+
message,
|
|
173
|
+
);
|
|
614
174
|
}
|
|
615
|
-
} finally {
|
|
616
|
-
await reader.cancel()
|
|
617
|
-
await webConsumer.consumer.disconnect()
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
```
|
|
621
|
-
|
|
622
|
-
## Batching, Backpressure, And Tuning
|
|
623
|
-
|
|
624
|
-
The main throughput lever is how many messages cross the native-to-JavaScript boundary per call.
|
|
625
|
-
|
|
626
|
-
| Knob | Applies To | Effect |
|
|
627
|
-
| ----------------------- | ----------------------------------------------- | ---------------------------------------------------------------------- |
|
|
628
|
-
| `recvBatch(size, ms)` | Direct consumer | Pulls up to `size` messages, waiting up to `ms` for data. |
|
|
629
|
-
| `batchSize` | Node stream and Web Stream batch modes | Sets the maximum native batch size used by the stream. |
|
|
630
|
-
| `batchTimeout` | Node stream and Web Stream batch modes | Bounds how long a partially filled batch waits before being emitted. |
|
|
631
|
-
| `serialPrefetchSize` | `createWebStreamConsumer()` serial mode | Pulls small native batches and flattens them into individual messages. |
|
|
632
|
-
| `serialPrefetchTimeout` | `createWebStreamConsumer()` serial mode | Timeout for the serial-mode prefetch batch. |
|
|
633
|
-
| `streamOptions` | `createStreamConsumer()` Node.js `Readable` API | Lets Node stream `highWaterMark` participate in backpressure. |
|
|
634
|
-
|
|
635
|
-
Use larger batches when throughput matters and your handler can process arrays efficiently. Use smaller batches and
|
|
636
|
-
shorter timeouts when tail latency matters more than total throughput. Very large batches can increase RSS because more
|
|
637
|
-
payloads, keys, headers, and metadata must be retained at once.
|
|
638
|
-
|
|
639
|
-
The v4 Web Stream serial path defaults to `serialPrefetchSize: 64` and `serialPrefetchTimeout: 1`. This keeps the public
|
|
640
|
-
serial API message-by-message while reducing native boundary crossings. Batch mode defaults `batchTimeout` to `1000`
|
|
641
|
-
milliseconds when it is omitted.
|
|
642
|
-
|
|
643
|
-
For broker fetch tuning, pass librdkafka settings through `configuration`. Keep these values aligned with your expected
|
|
644
|
-
message size:
|
|
645
|
-
|
|
646
|
-
```ts
|
|
647
|
-
const consumer = client.createConsumer({
|
|
648
|
-
groupId: 'orders-worker',
|
|
649
|
-
configuration: {
|
|
650
|
-
'fetch.min.bytes': 1,
|
|
651
|
-
'fetch.wait.max.ms': 10,
|
|
652
|
-
'fetch.max.bytes': 1_048_576,
|
|
653
|
-
'max.partition.fetch.bytes': 1_048_576,
|
|
654
|
-
'message.max.bytes': 1_000_000,
|
|
655
|
-
},
|
|
656
|
-
})
|
|
657
|
-
```
|
|
658
|
-
|
|
659
|
-
`fetch.max.bytes` must be greater than or equal to `message.max.bytes`. If this invariant is broken, librdkafka rejects
|
|
660
|
-
the consumer configuration before the benchmark or application starts.
|
|
661
|
-
|
|
662
|
-
## Configuration
|
|
663
|
-
|
|
664
|
-
### KafkaClient
|
|
665
|
-
|
|
666
|
-
```ts
|
|
667
|
-
const client = new KafkaClient({
|
|
668
|
-
brokers: 'localhost:9092',
|
|
669
|
-
clientId: 'orders-service',
|
|
670
|
-
securityProtocol: 'Plaintext',
|
|
671
|
-
logLevel: 'info',
|
|
672
|
-
brokerAddressFamily: 'v4',
|
|
673
|
-
diagnostics: true,
|
|
674
|
-
configuration: {
|
|
675
|
-
'socket.keepalive.enable': true,
|
|
676
|
-
},
|
|
677
|
-
})
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
| Option | Type | Default | Description |
|
|
681
|
-
| --------------------- | ------------------------------------------------------ | --------- | ------------------------------------------------------------------------- |
|
|
682
|
-
| `brokers` | `string` | required | Comma-separated broker list, for example `localhost:9092,localhost:9093`. |
|
|
683
|
-
| `clientId` | `string` | `rdkafka` | Client identifier sent to Kafka. |
|
|
684
|
-
| `securityProtocol` | `'Plaintext' \| 'Ssl' \| 'SaslPlaintext' \| 'SaslSsl'` | | Security protocol. |
|
|
685
|
-
| `logLevel` | `string` | `error` | librdkafka log level. |
|
|
686
|
-
| `brokerAddressFamily` | `string` | `v4` | Address family hint such as `v4` or `any`. |
|
|
687
|
-
| `diagnostics` | `boolean` | `true` | Enables diagnostic-channel events used by `kafka-crab-js-otel`. |
|
|
688
|
-
| `configuration` | `Record<string, any>` | | Additional librdkafka client settings. |
|
|
689
|
-
|
|
690
|
-
`configuration` is passed through to librdkafka after values are converted to strings. Use the original librdkafka
|
|
691
|
-
property names, for example `sasl.mechanism`, `queued.min.messages`, or `fetch.wait.max.ms`.
|
|
692
|
-
|
|
693
|
-
Common connection examples:
|
|
694
|
-
|
|
695
|
-
```ts
|
|
696
|
-
const localClient = new KafkaClient({
|
|
697
|
-
brokers: 'localhost:9092',
|
|
698
|
-
clientId: 'orders-local',
|
|
699
|
-
securityProtocol: 'Plaintext',
|
|
700
|
-
brokerAddressFamily: 'v4',
|
|
701
|
-
})
|
|
702
|
-
```
|
|
703
|
-
|
|
704
|
-
```ts
|
|
705
|
-
const saslClient = new KafkaClient({
|
|
706
|
-
brokers: process.env.KAFKA_BROKERS!,
|
|
707
|
-
clientId: 'orders-worker',
|
|
708
|
-
securityProtocol: 'SaslSsl',
|
|
709
|
-
configuration: {
|
|
710
|
-
'sasl.mechanism': 'PLAIN',
|
|
711
|
-
'sasl.username': process.env.KAFKA_USERNAME!,
|
|
712
|
-
'sasl.password': process.env.KAFKA_PASSWORD!,
|
|
713
|
-
},
|
|
714
|
-
})
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
### ConsumerConfiguration
|
|
718
|
-
|
|
719
|
-
```ts
|
|
720
|
-
const consumer = client.createConsumer({
|
|
721
|
-
groupId: 'orders-worker',
|
|
722
|
-
enableAutoCommit: false,
|
|
723
|
-
fetchMetadataTimeout: 5000,
|
|
724
|
-
configuration: {
|
|
725
|
-
'auto.offset.reset': 'earliest',
|
|
726
|
-
'enable.auto.commit': false,
|
|
727
|
-
'fetch.min.bytes': 1,
|
|
728
|
-
},
|
|
729
|
-
})
|
|
730
|
-
```
|
|
731
|
-
|
|
732
|
-
| Option | Type | Description |
|
|
733
|
-
| ---------------------- | --------------------- | ------------------------------------------ |
|
|
734
|
-
| `groupId` | `string` | Kafka consumer group id. |
|
|
735
|
-
| `enableAutoCommit` | `boolean` | Convenience flag for auto commit behavior. |
|
|
736
|
-
| `fetchMetadataTimeout` | `number` | Metadata fetch timeout in milliseconds. |
|
|
737
|
-
| `configuration` | `Record<string, any>` | Additional librdkafka consumer settings. |
|
|
738
|
-
|
|
739
|
-
`enableAutoCommit` is a convenience option that sets librdkafka `enable.auto.commit`. You can also pass
|
|
740
|
-
`'enable.auto.commit'` in `configuration`, but using both with different values makes the intent hard to read.
|
|
741
|
-
|
|
742
|
-
For new services that need explicit processing guarantees, start with manual commits:
|
|
743
|
-
|
|
744
|
-
```ts
|
|
745
|
-
const consumer = client.createConsumer({
|
|
746
|
-
groupId: 'orders-worker',
|
|
747
|
-
enableAutoCommit: false,
|
|
748
|
-
configuration: {
|
|
749
|
-
'auto.offset.reset': 'earliest',
|
|
750
|
-
'enable.partition.eof': false,
|
|
751
|
-
},
|
|
752
|
-
})
|
|
753
|
-
```
|
|
754
|
-
|
|
755
|
-
### ProducerConfiguration
|
|
756
|
-
|
|
757
|
-
```ts
|
|
758
|
-
const producer = client.createProducer({
|
|
759
|
-
autoFlush: true,
|
|
760
|
-
queueTimeout: 5000,
|
|
761
|
-
configuration: {
|
|
762
|
-
'compression.type': 'lz4',
|
|
763
|
-
},
|
|
764
|
-
})
|
|
765
|
-
```
|
|
766
|
-
|
|
767
|
-
| Option | Type | Description |
|
|
768
|
-
| --------------- | --------------------- | ------------------------------------------------------- |
|
|
769
|
-
| `autoFlush` | `boolean` | When enabled, `send()` waits for delivery confirmation. |
|
|
770
|
-
| `queueTimeout` | `number` | Queue timeout in milliseconds. |
|
|
771
|
-
| `configuration` | `Record<string, any>` | Additional librdkafka producer settings. |
|
|
772
|
-
|
|
773
|
-
Producer settings are workload-specific. A low-latency producer might prefer smaller buffering windows, while a
|
|
774
|
-
throughput-oriented producer can allow librdkafka to coalesce more work:
|
|
775
|
-
|
|
776
|
-
```ts
|
|
777
|
-
const producer = client.createProducer({
|
|
778
|
-
autoFlush: false,
|
|
779
|
-
queueTimeout: 5000,
|
|
780
|
-
configuration: {
|
|
781
|
-
acks: 'all',
|
|
782
|
-
'compression.type': 'lz4',
|
|
783
|
-
'queue.buffering.max.ms': 10,
|
|
784
|
-
'queue.buffering.max.messages': 100000,
|
|
785
|
-
},
|
|
786
|
-
})
|
|
787
|
-
```
|
|
788
|
-
|
|
789
|
-
### TopicPartitionConfig
|
|
790
|
-
|
|
791
|
-
| Option | Type | Description |
|
|
792
|
-
| ----------------- | ------------------- | ------------------------------------------------ |
|
|
793
|
-
| `topic` | `string` | Topic name. |
|
|
794
|
-
| `allOffsets` | `OffsetModel` | Offset model applied to all assigned partitions. |
|
|
795
|
-
| `partitionOffset` | `PartitionOffset[]` | Partition-specific offset model. |
|
|
796
|
-
| `createTopic` | `boolean` | Create the topic before subscribing. |
|
|
797
|
-
| `numPartitions` | `number` | Partition count when creating a topic. |
|
|
798
|
-
| `replicas` | `number` | Replica count when creating a topic. |
|
|
799
|
-
|
|
800
|
-
Offset models:
|
|
801
|
-
|
|
802
|
-
```ts
|
|
803
|
-
{
|
|
804
|
-
position: 'Beginning'
|
|
805
|
-
}
|
|
806
|
-
{
|
|
807
|
-
position: 'End'
|
|
808
|
-
}
|
|
809
|
-
{
|
|
810
|
-
position: 'Stored'
|
|
811
|
-
}
|
|
812
|
-
{
|
|
813
|
-
position: 'Invalid'
|
|
814
|
-
}
|
|
815
|
-
{
|
|
816
|
-
offset: 42
|
|
817
|
-
}
|
|
818
|
-
```
|
|
819
|
-
|
|
820
|
-
### Type-Only Literals
|
|
821
|
-
|
|
822
|
-
In v4, these names are TypeScript-only exports:
|
|
823
|
-
|
|
824
|
-
```ts
|
|
825
|
-
import type { CommitMode, KafkaEventName, PartitionPosition, SecurityProtocol } from 'kafka-crab-js'
|
|
826
|
-
```
|
|
827
|
-
|
|
828
|
-
Use literal values at runtime:
|
|
829
|
-
|
|
830
|
-
```ts
|
|
831
|
-
const commitMode: CommitMode = 'Sync'
|
|
832
|
-
const securityProtocol: SecurityProtocol = 'Plaintext'
|
|
833
|
-
```
|
|
834
|
-
|
|
835
|
-
## OpenTelemetry
|
|
836
|
-
|
|
837
|
-
OpenTelemetry support lives in the separate `kafka-crab-js-otel` package. The core package emits diagnostic-channel
|
|
838
|
-
events when `diagnostics !== false`; the OTEL package subscribes to those events.
|
|
839
|
-
|
|
840
|
-
Install the instrumentation package and OpenTelemetry dependencies:
|
|
841
|
-
|
|
842
|
-
```bash
|
|
843
|
-
npm install kafka-crab-js-otel @opentelemetry/api @opentelemetry/sdk-node
|
|
844
|
-
```
|
|
845
|
-
|
|
846
|
-
Enable instrumentation before creating `KafkaClient`:
|
|
847
|
-
|
|
848
|
-
```ts
|
|
849
|
-
import { KafkaClient } from 'kafka-crab-js'
|
|
850
|
-
import { enableOtelInstrumentation, endSpan } from 'kafka-crab-js-otel'
|
|
851
|
-
|
|
852
|
-
enableOtelInstrumentation({
|
|
853
|
-
metrics: { enabled: true },
|
|
854
|
-
})
|
|
855
|
-
|
|
856
|
-
const client = new KafkaClient({
|
|
857
|
-
brokers: 'localhost:9092',
|
|
858
|
-
clientId: 'orders-worker',
|
|
859
|
-
diagnostics: true,
|
|
860
|
-
})
|
|
861
175
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
176
|
+
// Commit once per topic/partition, only after the entire batch succeeds.
|
|
177
|
+
for (const message of processed.values()) {
|
|
178
|
+
await consumer.commitMessage(message, "Sync");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} finally {
|
|
182
|
+
process.off("SIGINT", stop);
|
|
183
|
+
process.off("SIGTERM", stop);
|
|
867
184
|
try {
|
|
868
|
-
await
|
|
185
|
+
await reader.cancel();
|
|
869
186
|
} finally {
|
|
870
|
-
|
|
187
|
+
reader.releaseLock();
|
|
188
|
+
await consumer.disconnect();
|
|
871
189
|
}
|
|
872
190
|
}
|
|
873
191
|
```
|
|
874
192
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
|
916
|
-
|
|
|
917
|
-
|
|
|
918
|
-
|
|
|
919
|
-
|
|
|
920
|
-
|
|
|
921
|
-
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
import { CommitMode, SecurityProtocol } from 'kafka-crab-js'
|
|
994
|
-
```
|
|
995
|
-
|
|
996
|
-
After:
|
|
997
|
-
|
|
998
|
-
```ts
|
|
999
|
-
import type { CommitMode, SecurityProtocol } from 'kafka-crab-js'
|
|
1000
|
-
|
|
1001
|
-
const commitMode: CommitMode = 'Sync'
|
|
1002
|
-
const securityProtocol: SecurityProtocol = 'Plaintext'
|
|
1003
|
-
```
|
|
1004
|
-
|
|
1005
|
-
### v3 OpenTelemetry Split
|
|
1006
|
-
|
|
1007
|
-
OpenTelemetry instrumentation moved from the core package to `kafka-crab-js-otel` in v3. This keeps the core package
|
|
1008
|
-
small and makes tracing/metrics opt-in.
|
|
193
|
+
Run `node consumer.mjs` and then `node producer.mjs` in another terminal. Consumers
|
|
194
|
+
sharing a group split assigned partitions; use another group for an independent
|
|
195
|
+
subscription. `earliest` applies when there is no valid committed offset.
|
|
196
|
+
|
|
197
|
+
A failed handler prevents that batch's commits, so records can be replayed. Make
|
|
198
|
+
side effects idempotent and never commit past unfinished work in the same partition.
|
|
199
|
+
The signal handler cancels future reads while the already handed-out batch finishes;
|
|
200
|
+
cancellation can abandon prefetched work. See [shutdown semantics](docs/api.md#position-flow-control-and-shutdown)
|
|
201
|
+
when the application needs to drain collected work instead.
|
|
202
|
+
|
|
203
|
+
## Choose your API
|
|
204
|
+
|
|
205
|
+
| Your workload | Start with | Delivered to your code |
|
|
206
|
+
| ------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------- |
|
|
207
|
+
| Bulk database writes, ingestion, analytics | `createWebStreamConsumer({ batchSize: 64, batchTimeout: 5, ... })` | `Message[]` per read |
|
|
208
|
+
| Sequential business logic, message-level handling | `createWebStreamConsumer({ groupId, ... })` | One `Message` per read, with internal prefetch |
|
|
209
|
+
| Existing Node.js pipelines | `createStreamConsumer({ groupId, batchSize: 64, ... })` | Individual `Message` objects in object mode |
|
|
210
|
+
| A custom polling or scheduling loop | `createConsumer()` + `recvBatch(size, timeoutMs)` | Up to `size` messages per call |
|
|
211
|
+
| One direct blocking receive | `createConsumer()` + `recv()` | One message, or `null` after disconnect |
|
|
212
|
+
|
|
213
|
+
Start with a batch size of **64 and a 5 ms timeout** for evaluation, then measure
|
|
214
|
+
with your payload sizes and handler. Larger batches amortize per-delivery work;
|
|
215
|
+
smaller batches reduce collection delay and the amount of work held by the handler.
|
|
216
|
+
The throughput comparison uses 4,096/2 ms for its small-message scenario, which is
|
|
217
|
+
not a universal production preset.
|
|
218
|
+
|
|
219
|
+
For serial Web Streams, `serialPrefetchSize` and `serialPrefetchTimeout` control
|
|
220
|
+
internal collection without changing the one-message public interface. A Node
|
|
221
|
+
batch stream still emits individual messages; choose the Web batch API when your
|
|
222
|
+
application needs arrays. Await handlers with a reader loop, async iteration, or
|
|
223
|
+
a pipeline sink so downstream processing applies backpressure.
|
|
224
|
+
|
|
225
|
+
## Benchmarks
|
|
226
|
+
|
|
227
|
+
**September 12, 2026 · Apple M4 · Node 24.20.0 · local Kafka · 120 runs per scenario.**
|
|
228
|
+
Two full runs used opposite crab pair orders, fresh processes per block, explicit
|
|
229
|
+
warmup, and all samples. Throughput is total measured messages / total measured
|
|
230
|
+
time. Heap and RSS are maximum sampled lifecycle values across four processes.
|
|
231
|
+
|
|
232
|
+
| Consumer | Interface | Messages/s | Peak JS heap | Peak RSS |
|
|
233
|
+
| ----------------------------- | ---------------------------- | ------------: | -----------: | ------------: |
|
|
234
|
+
| **kafka-crab-js development** | **Web Stream batch** | **1,663,382** | **29.0 MiB** | **266.3 MiB** |
|
|
235
|
+
| @platformatic/kafka 2.11.0 | Message stream | 998,122 | 87.0 MiB | 256.7 MiB |
|
|
236
|
+
| **kafka-crab-js development** | **Web Stream serial** | **915,664** | **21.9 MiB** | **178.9 MiB** |
|
|
237
|
+
| KafkaJS 2.2.4 | `eachBatch` | 841,194 | 71.7 MiB | 238.9 MiB |
|
|
238
|
+
| KafkaJS 2.2.4 | `eachMessage` | 655,365 | 77.8 MiB | 245.6 MiB |
|
|
239
|
+
| KafkaJS 2.2.4 | `eachMessage`, concurrency 3 | 646,613 | 83.2 MiB | 246.7 MiB |
|
|
240
|
+
|
|
241
|
+
For this workload, current batch throughput was **1.98× KafkaJS `eachBatch`**;
|
|
242
|
+
current serial was **1.40× KafkaJS `eachMessage`**. Platformatic's message stream
|
|
243
|
+
was faster than crab serial. Crab batch used less JS heap than the measured JS clients
|
|
244
|
+
but more total RSS; native allocations are outside the JS heap.
|
|
245
|
+
|
|
246
|
+
These are consumer delivery rates for small, already published messages, without
|
|
247
|
+
application processing, manual commit costs, or end-to-end producer latency.
|
|
248
|
+
They are not a universal ranking. The topic has three partitions, two nonempty;
|
|
249
|
+
KafkaJS concurrency 3 therefore does not imply three active message partitions.
|
|
250
|
+
Crab uses its default fetch queue backoff of 20 ms. Cluster observations and other
|
|
251
|
+
measurement limits are recorded in the full report.
|
|
252
|
+
|
|
253
|
+
[Full results, percentiles, configuration, and reproduction](../../BENCHMARKS.md) ·
|
|
254
|
+
[Comparison with the previous release](../../BENCHMARKS.md#comparison-with-the-previous-release) ·
|
|
255
|
+
[Portable evidence: all 960 measurements](docs/rfc/review/evidence/consumer-comparison-2026-09-12.json)
|
|
256
|
+
|
|
257
|
+
## How it compares
|
|
258
|
+
|
|
259
|
+
Choose the client that matches your processing model and deployment constraints.
|
|
260
|
+
|
|
261
|
+
| Decision | kafka-crab-js | KafkaJS | @platformatic/kafka |
|
|
262
|
+
| -------------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------- |
|
|
263
|
+
| Implementation | Rust + NAPI-RS + librdkafka | JavaScript | TypeScript/JavaScript |
|
|
264
|
+
| Consumer style | Direct receive, Node streams, serial/batch Web Streams | `eachMessage` and `eachBatch` callbacks | Node streams and events |
|
|
265
|
+
| Best reason to evaluate it | Native batch throughput, explicit delivery/offset control, librdkafka tuning | JavaScript-only deployment and callback-oriented processing | JavaScript-only deployment and stream-oriented processing |
|
|
266
|
+
| Configuration model | Typed wrapper plus librdkafka properties | KafkaJS options | Platformatic options |
|
|
267
|
+
| Installation consideration | Prebuilt native targets, Node 24 | No native addon | No native addon |
|
|
268
|
+
|
|
269
|
+
Competitor API descriptions come from the official
|
|
270
|
+
[KafkaJS documentation](https://kafka.js.org/docs/consuming) and
|
|
271
|
+
[Platformatic repository](https://github.com/platformatic/kafka). The comparison
|
|
272
|
+
covers these interfaces, not every feature or every client in the Kafka ecosystem.
|
|
273
|
+
|
|
274
|
+
`kafka-crab-js` is a strong fit for ingestion workers, event-driven services, and
|
|
275
|
+
bulk processing where you can benefit from native batch delivery. If you need Kafka transactions, a built-in schema registry client, or other
|
|
276
|
+
administrative operations beyond record deletion, account for the current public
|
|
277
|
+
API's scope before migrating.
|
|
278
|
+
|
|
279
|
+
### Coming from KafkaJS
|
|
280
|
+
|
|
281
|
+
| KafkaJS concept | kafka-crab-js equivalent |
|
|
282
|
+
| ------------------------------------------ | -------------------------------------------------------------- |
|
|
283
|
+
| `new Kafka({ brokers: [...] })` | `new KafkaClient({ brokers: 'host:9092,...' })` |
|
|
284
|
+
| `kafka.producer()` | `client.createProducer()` |
|
|
285
|
+
| `producer.send({ messages: [{ value }] })` | `producer.send({ messages: [{ payload: Buffer.from(...) }] })` |
|
|
286
|
+
| `eachMessage` | Serial Web Stream + an awaited message handler |
|
|
287
|
+
| `eachBatch` | Batch Web Stream + an awaited batch handler |
|
|
288
|
+
| Commit after successful work | `consumer.commitMessage(message, 'Sync')` |
|
|
289
|
+
|
|
290
|
+
This is an API migration, not an import-only replacement. Review message encoding,
|
|
291
|
+
partition ordering, commit policy, retries, and shutdown. Start by replaying a
|
|
292
|
+
representative topic into an independent group and compare throughput, latency,
|
|
293
|
+
RSS, and verified processing outcomes. The [API reference](docs/api.md) describes
|
|
294
|
+
individual contracts and partial-failure behavior.
|
|
295
|
+
|
|
296
|
+
## Production recommendations
|
|
297
|
+
|
|
298
|
+
**Commit after the work succeeds.** Disable automatic commits/offset storage when
|
|
299
|
+
application completion defines delivery. Commit the last successfully processed
|
|
300
|
+
message per topic/partition. Keep side effects idempotent so replay is safe. For
|
|
301
|
+
Async commits, observe `CommitCallback`; resolving the scheduling promise is not
|
|
302
|
+
broker confirmation.
|
|
303
|
+
|
|
304
|
+
**Budget memory across the entire process.** The current native batch queue's
|
|
305
|
+
32 MiB accounted-byte budget includes headers and keys, with an oversized-batch
|
|
306
|
+
exception. It does not cap RSS or librdkafka's separate prefetch queues. Bound
|
|
307
|
+
application concurrency and retained messages, and monitor RSS alongside JS heap.
|
|
308
|
+
|
|
309
|
+
**Use a smaller prefetch profile for large headers and reader stalls.** Evaluate
|
|
310
|
+
these consumer properties against your own workload:
|
|
1009
311
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
await
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
```ts
|
|
1057
|
-
const client = new KafkaClient({
|
|
1058
|
-
brokers: 'localhost:9092',
|
|
1059
|
-
brokerAddressFamily: 'v4',
|
|
1060
|
-
})
|
|
1061
|
-
```
|
|
1062
|
-
|
|
1063
|
-
### Need Advanced Kafka Settings
|
|
1064
|
-
|
|
1065
|
-
Pass librdkafka settings through `configuration`:
|
|
1066
|
-
|
|
1067
|
-
```ts
|
|
1068
|
-
const client = new KafkaClient({
|
|
1069
|
-
brokers: 'localhost:9092',
|
|
1070
|
-
configuration: {
|
|
1071
|
-
'socket.keepalive.enable': true,
|
|
1072
|
-
'metadata.max.age.ms': 300000,
|
|
1073
|
-
},
|
|
1074
|
-
})
|
|
1075
|
-
```
|
|
1076
|
-
|
|
1077
|
-
Consumer and producer configuration objects also accept their own `configuration` maps.
|
|
1078
|
-
|
|
1079
|
-
### `fetch.max.bytes` Must Be At Least `message.max.bytes`
|
|
1080
|
-
|
|
1081
|
-
librdkafka validates fetch limits at consumer creation time. If you tune fetch sizes for benchmarks or production and
|
|
1082
|
-
set `message.max.bytes` higher than `fetch.max.bytes`, the consumer will fail to start with an error similar to:
|
|
1083
|
-
|
|
1084
|
-
```text
|
|
1085
|
-
`fetch.max.bytes` must be >= `message.max.bytes`
|
|
1086
|
-
```
|
|
1087
|
-
|
|
1088
|
-
Keep the fetch caps aligned:
|
|
1089
|
-
|
|
1090
|
-
```ts
|
|
1091
|
-
const consumer = client.createConsumer({
|
|
1092
|
-
groupId: 'orders-worker',
|
|
1093
|
-
configuration: {
|
|
1094
|
-
'message.max.bytes': 1_000_000,
|
|
1095
|
-
'fetch.max.bytes': 1_048_576,
|
|
1096
|
-
'max.partition.fetch.bytes': 1_048_576,
|
|
1097
|
-
},
|
|
1098
|
-
})
|
|
1099
|
-
```
|
|
1100
|
-
|
|
1101
|
-
### Benchmark Numbers Move Between Runs
|
|
1102
|
-
|
|
1103
|
-
Kafka benchmarks are sensitive to CPU frequency, power mode, broker state, topic data already in page cache, and V8 heap
|
|
1104
|
-
history. The benchmark package defaults to isolated child processes for memory mode to reduce cross-scenario
|
|
1105
|
-
contamination, but you should still compare multiple runs and focus on large, repeatable gaps.
|
|
1106
|
-
|
|
1107
|
-
## Development
|
|
312
|
+
```js
|
|
313
|
+
const configuration = {
|
|
314
|
+
"queued.max.messages.kbytes": 65536,
|
|
315
|
+
"queued.min.messages": 256,
|
|
316
|
+
"fetch.max.bytes": 8 * 1024 * 1024,
|
|
317
|
+
"max.partition.fetch.bytes": 1024 * 1024,
|
|
318
|
+
"fetch.queue.backoff.ms": 20,
|
|
319
|
+
};
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
In the [concurrent workload](docs/rfc/review/performance.md#concurrent-workload-with-natural-gc--2026-09-12),
|
|
323
|
+
640,000 new records totaling 12.736 GiB passed byte/order checks and broker-verified
|
|
324
|
+
commits with zero final backlog. Using this profile reduced the worst observed
|
|
325
|
+
consumer RSS by **50.1% serial / 54.3% batch** versus the 100,000-message threshold.
|
|
326
|
+
Normal p95 stayed around 60 ms; p99 and recovery time increased. Each consumer ran
|
|
327
|
+
for two minutes on macOS with natural GC. This validates a specific profile, not
|
|
328
|
+
long-term stability, a hard memory ceiling, or production failure recovery.
|
|
329
|
+
|
|
330
|
+
**Tune for your application.** Larger fetches can help small-message throughput;
|
|
331
|
+
large batches can raise memory and latency. Use the
|
|
332
|
+
[benchmark harness](../../benchmarks/kafka/README.md) and your own handler to find
|
|
333
|
+
the useful tradeoff. Keep confirmed send/commit costs in end-to-end measurements.
|
|
334
|
+
|
|
335
|
+
**Connect and shut down deliberately.** Use TLS/SASL appropriate to your cluster,
|
|
336
|
+
keep credentials in a secret store, and retain certificate validation. Finish
|
|
337
|
+
accepted work before committing/closing. Await sends and manual flushes; cancel
|
|
338
|
+
and release Web readers or destroy Node streams and await close. Details and
|
|
339
|
+
examples are in the [API reference](docs/api.md).
|
|
340
|
+
|
|
341
|
+
## Observability
|
|
342
|
+
|
|
343
|
+
Install `kafka-crab-js-otel` when you need OpenTelemetry tracing and metrics.
|
|
344
|
+
Configure your SDK/exporter, enable instrumentation before creating clients, and
|
|
345
|
+
keep client diagnostics enabled. Consumer processing spans should end after the
|
|
346
|
+
handler completes.
|
|
347
|
+
|
|
348
|
+
[OTEL setup](../kafka-crab-js-otel/README.md) ·
|
|
349
|
+
[Runnable tracing and metrics examples](../../examples/kafka/README.md)
|
|
350
|
+
|
|
351
|
+
## Documentation and development
|
|
352
|
+
|
|
353
|
+
- [API reference](docs/api.md): configuration, producer, consumer, streams, commits, and shutdown.
|
|
354
|
+
- [Examples](../../examples/kafka/README.md): direct APIs, streams, retries, and observability.
|
|
355
|
+
- [Benchmarks](../../BENCHMARKS.md): measured comparisons and reproducible commands.
|
|
356
|
+
- [Engineering docs](docs/README.md): RFCs, validation evidence, and remaining release checks.
|
|
357
|
+
- [Issues and feature requests](https://github.com/flash-tecnologia/crab-js/issues): share reproductions and proposals.
|
|
1108
358
|
|
|
1109
359
|
From the repository root:
|
|
1110
360
|
|
|
1111
|
-
```
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
vp check
|
|
1117
|
-
```
|
|
1118
|
-
|
|
1119
|
-
Useful package-local commands:
|
|
1120
|
-
|
|
1121
|
-
```bash
|
|
1122
|
-
vp run --filter kafka-crab-js build
|
|
1123
|
-
vp run --filter kafka-crab-js test
|
|
1124
|
-
vp run --filter kafka-crab-js test:integration
|
|
361
|
+
```sh
|
|
362
|
+
pnpm install
|
|
363
|
+
pnpm --filter kafka-crab-js build
|
|
364
|
+
pnpm --filter kafka-crab-js test
|
|
365
|
+
RUN_KAFKA_INTEGRATION=true pnpm --filter kafka-crab-js test:integration
|
|
1125
366
|
```
|
|
1126
367
|
|
|
1127
|
-
|
|
368
|
+
Building from source requires Rust and the native build prerequisites. Kafka must
|
|
369
|
+
be running for integration tests. See [test infrastructure](js-tests/integration/README.md)
|
|
370
|
+
for the Compose setup and broker requirements. The [release review](docs/rfc/review/README.md) tracks
|
|
371
|
+
validation separately from the performance claims above.
|
|
1128
372
|
|
|
1129
373
|
## License
|
|
1130
374
|
|
|
1131
|
-
MIT
|
|
375
|
+
[MIT](https://opensource.org/licenses/MIT)
|