@venizia/ignis-docs 0.0.7-1 → 0.0.7-2
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/package.json +1 -1
- package/wiki/references/helpers/kafka/admin.md +173 -0
- package/wiki/references/helpers/kafka/consumer.md +473 -0
- package/wiki/references/helpers/kafka/examples.md +234 -0
- package/wiki/references/helpers/kafka/index.md +397 -220
- package/wiki/references/helpers/kafka/producer.md +269 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Producer
|
|
2
|
+
|
|
3
|
+
The `KafkaProducerHelper` is a thin wrapper around `@platformatic/kafka`'s `Producer`. It manages creation, logging, and lifecycle.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
class KafkaProducerHelper<
|
|
7
|
+
KeyType = string,
|
|
8
|
+
ValueType = string,
|
|
9
|
+
HeaderKeyType = string,
|
|
10
|
+
HeaderValueType = string,
|
|
11
|
+
> extends BaseHelper
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Helper API
|
|
15
|
+
|
|
16
|
+
| Method | Signature | Description |
|
|
17
|
+
|--------|-----------|-------------|
|
|
18
|
+
| `newInstance(opts)` | `static newInstance<K,V,HK,HV>(opts): KafkaProducerHelper<K,V,HK,HV>` | Factory method |
|
|
19
|
+
| `getProducer()` | `(): Producer<KeyType, ValueType, HeaderKeyType, HeaderValueType>` | Access the underlying `Producer` |
|
|
20
|
+
| `close(isForce?)` | `(isForce?: boolean): Promise<void>` | Close the producer. Default: `force=false` |
|
|
21
|
+
|
|
22
|
+
## IKafkaProducerOpts
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
interface IKafkaProducerOpts<KeyType, ValueType, HeaderKeyType, HeaderValueType>
|
|
26
|
+
extends IKafkaConnectionOptions
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Option | Type | Default | Description |
|
|
30
|
+
|--------|------|---------|-------------|
|
|
31
|
+
| `identifier` | `string` | `'kafka-producer'` | Scoped logging identifier |
|
|
32
|
+
| `serializers` | `Partial<Serializers<K,V,HK,HV>>` | — | Key/value/header serializers. **Pass explicitly** |
|
|
33
|
+
| `compression` | `CompressionAlgorithmValue` | — | `'none'`, `'gzip'`, `'snappy'`, `'lz4'`, `'zstd'` |
|
|
34
|
+
| `acks` | `TKafkaAcks` | — | Acknowledgment level: `0`, `1`, or `-1` |
|
|
35
|
+
| `idempotent` | `boolean` | — | Enable idempotent producer (exactly-once within partition) |
|
|
36
|
+
| `transactionalId` | `string` | — | Transactional ID for exactly-once across partitions |
|
|
37
|
+
| `strict` | `boolean` | `true` | Strict mode — fail on unknown topics |
|
|
38
|
+
| `autocreateTopics` | `boolean` | `false` | Auto-create topics on first produce |
|
|
39
|
+
|
|
40
|
+
Plus all [Connection Options](./#connection-options).
|
|
41
|
+
|
|
42
|
+
## Basic Example
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { KafkaProducerHelper, KafkaAcks } from '@venizia/ignis-helpers/kafka';
|
|
46
|
+
import { stringSerializers } from '@platformatic/kafka';
|
|
47
|
+
|
|
48
|
+
const helper = KafkaProducerHelper.newInstance({
|
|
49
|
+
bootstrapBrokers: ['localhost:9092'],
|
|
50
|
+
clientId: 'order-producer',
|
|
51
|
+
serializers: stringSerializers,
|
|
52
|
+
acks: KafkaAcks.ALL,
|
|
53
|
+
compression: 'gzip',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const producer = helper.getProducer();
|
|
57
|
+
|
|
58
|
+
// Send a single message
|
|
59
|
+
await producer.send({
|
|
60
|
+
messages: [
|
|
61
|
+
{ topic: 'orders', key: 'order-123', value: JSON.stringify({ status: 'created' }) },
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Send multiple messages (batched in a single request)
|
|
66
|
+
await producer.send({
|
|
67
|
+
messages: [
|
|
68
|
+
{ topic: 'orders', key: 'order-124', value: JSON.stringify({ status: 'created' }) },
|
|
69
|
+
{ topic: 'orders', key: 'order-125', value: JSON.stringify({ status: 'created' }) },
|
|
70
|
+
{ topic: 'inventory', key: 'sku-001', value: JSON.stringify({ delta: -1 }) },
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await helper.close();
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Generic Types Example
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// Custom: string keys, JSON object values
|
|
81
|
+
const helper = KafkaProducerHelper.newInstance<string, MyEvent, string, string>({
|
|
82
|
+
bootstrapBrokers: ['localhost:9092'],
|
|
83
|
+
clientId: 'typed-producer',
|
|
84
|
+
serializers: { ...serializersFrom(jsonSerializer), key: stringSerializer },
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## API Reference (`@platformatic/kafka`)
|
|
91
|
+
|
|
92
|
+
After calling `helper.getProducer()`, you have full access to the `Producer` class:
|
|
93
|
+
|
|
94
|
+
### `producer.send(options)`
|
|
95
|
+
|
|
96
|
+
Send messages to one or more topics.
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
interface SendOptions<Key, Value, HeaderKey, HeaderValue> {
|
|
100
|
+
messages: MessageToProduce<Key, Value, HeaderKey, HeaderValue>[];
|
|
101
|
+
acks?: number;
|
|
102
|
+
compression?: CompressionAlgorithmValue;
|
|
103
|
+
partitioner?: Partitioner<Key, Value, HeaderKey, HeaderValue>;
|
|
104
|
+
idempotent?: boolean;
|
|
105
|
+
autocreateTopics?: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface MessageToProduce<Key, Value, HeaderKey, HeaderValue> {
|
|
109
|
+
topic: string;
|
|
110
|
+
key?: Key;
|
|
111
|
+
value?: Value;
|
|
112
|
+
partition?: number; // Explicit partition (overrides partitioner)
|
|
113
|
+
timestamp?: bigint; // Message timestamp
|
|
114
|
+
headers?: Map<HeaderKey, HeaderValue> | Record<string, HeaderValue>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Returns
|
|
118
|
+
interface ProduceResult {
|
|
119
|
+
offsets?: { topic: string; partition: number; offset: bigint }[];
|
|
120
|
+
unwritableNodes?: number[];
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Examples:**
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// Basic send
|
|
128
|
+
await producer.send({
|
|
129
|
+
messages: [{ topic: 'events', key: 'user-1', value: '{"action":"login"}' }],
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// With headers
|
|
133
|
+
await producer.send({
|
|
134
|
+
messages: [{
|
|
135
|
+
topic: 'events',
|
|
136
|
+
key: 'user-1',
|
|
137
|
+
value: '{"action":"login"}',
|
|
138
|
+
headers: { 'x-trace-id': 'abc123', 'x-source': 'auth-service' },
|
|
139
|
+
}],
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Tombstone (delete compacted key)
|
|
143
|
+
await producer.send({
|
|
144
|
+
messages: [{ topic: 'users', key: 'user-deleted-123', value: undefined }],
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Explicit partition
|
|
148
|
+
await producer.send({
|
|
149
|
+
messages: [{ topic: 'events', key: 'e1', value: 'data', partition: 2 }],
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Override compression per-send
|
|
153
|
+
await producer.send({
|
|
154
|
+
messages: [{ topic: 'logs', key: 'l1', value: largePayload }],
|
|
155
|
+
compression: 'zstd',
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `producer.asStream(options)`
|
|
160
|
+
|
|
161
|
+
Create a `Writable` stream for high-throughput producing with automatic batching.
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
interface ProducerStreamOptions<Key, Value, HeaderKey, HeaderValue> {
|
|
165
|
+
highWaterMark?: number; // Stream buffer size
|
|
166
|
+
batchSize?: number; // Messages per batch
|
|
167
|
+
batchTime?: number; // Max ms before flushing batch
|
|
168
|
+
reportMode?: 'none' | 'batch' | 'message';
|
|
169
|
+
// ... plus all SendOptions except messages
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const stream = producer.asStream({ batchSize: 100, batchTime: 1000 });
|
|
175
|
+
|
|
176
|
+
// Write messages — automatically batched
|
|
177
|
+
stream.write({ topic: 'events', key: 'e1', value: '{"type":"click"}' });
|
|
178
|
+
stream.write({ topic: 'events', key: 'e2', value: '{"type":"scroll"}' });
|
|
179
|
+
|
|
180
|
+
// Listen for batch completion
|
|
181
|
+
stream.on('data', (report) => {
|
|
182
|
+
console.log(`Batch ${report.batchId}: ${report.count} messages sent`);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Close when done
|
|
186
|
+
await stream.close();
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### `producer.beginTransaction(options?)`
|
|
190
|
+
|
|
191
|
+
Start a Kafka transaction for exactly-once semantics across multiple topics/partitions.
|
|
192
|
+
|
|
193
|
+
> [!NOTE]
|
|
194
|
+
> Requires `transactionalId` in producer options and `idempotent: true`.
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const producer = new Producer({
|
|
198
|
+
clientId: 'tx-producer',
|
|
199
|
+
bootstrapBrokers: ['localhost:9092'],
|
|
200
|
+
transactionalId: 'my-tx-id',
|
|
201
|
+
idempotent: true,
|
|
202
|
+
serializers: stringSerializers,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const tx = await producer.beginTransaction();
|
|
206
|
+
try {
|
|
207
|
+
await tx.send({
|
|
208
|
+
messages: [
|
|
209
|
+
{ topic: 'orders', key: 'o1', value: '{"status":"paid"}' },
|
|
210
|
+
{ topic: 'inventory', key: 'sku-1', value: '{"delta":-1}' },
|
|
211
|
+
],
|
|
212
|
+
});
|
|
213
|
+
await tx.commit();
|
|
214
|
+
} catch (err) {
|
|
215
|
+
await tx.abort();
|
|
216
|
+
throw err;
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### `producer.close(force?)`
|
|
221
|
+
|
|
222
|
+
Close the producer connection.
|
|
223
|
+
|
|
224
|
+
- `force=false` (default): Wait for in-flight requests to complete
|
|
225
|
+
- `force=true`: Abort immediately
|
|
226
|
+
|
|
227
|
+
### Producer Properties
|
|
228
|
+
|
|
229
|
+
| Property | Type | Description |
|
|
230
|
+
|----------|------|-------------|
|
|
231
|
+
| `producerId` | `bigint \| undefined` | Assigned producer ID (after idempotent init) |
|
|
232
|
+
| `producerEpoch` | `number \| undefined` | Producer epoch (fencing) |
|
|
233
|
+
| `transaction` | `Transaction \| undefined` | Active transaction (if any) |
|
|
234
|
+
| `coordinatorId` | `number` | Transaction coordinator broker ID |
|
|
235
|
+
| `streamsCount` | `number` | Number of active producer streams |
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Key Partitioning
|
|
240
|
+
|
|
241
|
+
By default, `@platformatic/kafka` uses **murmur2 hashing** on the message key to determine the target partition:
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
partition = murmur2(key) % numPartitions
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
- Same key → always same partition → guaranteed ordering per key
|
|
248
|
+
- `undefined` key → round-robin across partitions
|
|
249
|
+
- Explicit `partition` field → overrides the partitioner
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
// Key-based routing: all "user-123" messages go to the same partition
|
|
253
|
+
await producer.send({
|
|
254
|
+
messages: [
|
|
255
|
+
{ topic: 'events', key: 'user-123', value: '{"action":"login"}' },
|
|
256
|
+
{ topic: 'events', key: 'user-123', value: '{"action":"click"}' }, // Same partition
|
|
257
|
+
{ topic: 'events', key: 'user-456', value: '{"action":"login"}' }, // Different partition
|
|
258
|
+
],
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Custom partitioner
|
|
262
|
+
await producer.send({
|
|
263
|
+
messages: [{ topic: 'events', key: 'e1', value: 'data' }],
|
|
264
|
+
partitioner: (message) => {
|
|
265
|
+
// Route by first character of key
|
|
266
|
+
return message.key!.charCodeAt(0) % 3;
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
```
|