@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.
@@ -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
+ ```