@autofleet/kafka 0.0.1
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 +579 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +84 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
# @autofleet/kafka
|
|
2
|
+
|
|
3
|
+
Internal wrapper for Apache Kafka producer using [@platformatic/kafka](https://www.npmjs.com/package/@platformatic/kafka), providing a production-ready interface for publishing messages to Kafka topics.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Features](#features)
|
|
9
|
+
- [Quick Start](#quick-start)
|
|
10
|
+
- [Usage](#usage)
|
|
11
|
+
- [Publishing Messages](#publishing-messages)
|
|
12
|
+
- [Batch Publishing](#batch-publishing)
|
|
13
|
+
- [Managing Connection](#managing-connection)
|
|
14
|
+
- [Configuration](#configuration)
|
|
15
|
+
- [Advanced Features](#advanced-features)
|
|
16
|
+
- [Message Keys for Partitioning](#message-keys-for-partitioning)
|
|
17
|
+
- [Custom Headers](#custom-headers)
|
|
18
|
+
- [Partition Control](#partition-control)
|
|
19
|
+
- [API Reference](#api-reference)
|
|
20
|
+
- [Best Practices](#best-practices)
|
|
21
|
+
- [Testing](#testing)
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm add @autofleet/kafka
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- **Simple Producer API** - Easy-to-use interface for publishing messages
|
|
32
|
+
- **Automatic Connection Management** - Handles connection lifecycle automatically
|
|
33
|
+
- **Batch Publishing** - Efficient batch message sending
|
|
34
|
+
- **Message Partitioning** - Control message distribution across partitions
|
|
35
|
+
- **Custom Headers** - Attach metadata to messages
|
|
36
|
+
- **Graceful Shutdown** - Automatic cleanup on process termination
|
|
37
|
+
- **Type Safety** - Full TypeScript support
|
|
38
|
+
- **Production Ready** - Built with reliability and performance in mind
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import AfKafka from '@autofleet/kafka';
|
|
44
|
+
|
|
45
|
+
// Initialize the producer
|
|
46
|
+
const kafka = new AfKafka({
|
|
47
|
+
brokers: ['localhost:9092'],
|
|
48
|
+
clientId: 'my-service',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Publish a message
|
|
52
|
+
await kafka.publish('user-events', {
|
|
53
|
+
userId: '123',
|
|
54
|
+
action: 'user.created',
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Publish multiple messages in a batch
|
|
59
|
+
await kafka.publishBatch({
|
|
60
|
+
topic: 'user-events',
|
|
61
|
+
messages: [
|
|
62
|
+
{ value: { userId: '123', action: 'login' } },
|
|
63
|
+
{ value: { userId: '456', action: 'logout' } },
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Disconnect when done
|
|
68
|
+
await kafka.disconnect();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
### Publishing Messages
|
|
74
|
+
|
|
75
|
+
#### Basic Publishing
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const kafka = new AfKafka({
|
|
79
|
+
brokers: ['localhost:9092', 'localhost:9093'],
|
|
80
|
+
clientId: 'user-service',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Publish a simple message
|
|
84
|
+
await kafka.publish('user-events', {
|
|
85
|
+
userId: '123',
|
|
86
|
+
event: 'profile_updated',
|
|
87
|
+
data: {
|
|
88
|
+
name: 'John Doe',
|
|
89
|
+
email: 'john@example.com',
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### Publishing with Message Key
|
|
95
|
+
|
|
96
|
+
Message keys determine which partition a message goes to, ensuring messages with the same key are always sent to the same partition (maintaining order).
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
await kafka.publish(
|
|
100
|
+
'user-events',
|
|
101
|
+
{ userId: '123', action: 'update' },
|
|
102
|
+
{
|
|
103
|
+
key: 'user-123', // All messages with this key go to the same partition
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### Publishing with Custom Headers
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
await kafka.publish(
|
|
112
|
+
'user-events',
|
|
113
|
+
{ userId: '123', action: 'login' },
|
|
114
|
+
{
|
|
115
|
+
headers: {
|
|
116
|
+
'correlation-id': requestId,
|
|
117
|
+
'source': 'api-gateway',
|
|
118
|
+
'version': '1.0.0',
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### Publishing to Specific Partition
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
await kafka.publish(
|
|
128
|
+
'user-events',
|
|
129
|
+
{ userId: '123', action: 'update' },
|
|
130
|
+
{
|
|
131
|
+
partition: 2, // Send directly to partition 2
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Batch Publishing
|
|
137
|
+
|
|
138
|
+
Batch publishing is more efficient for sending multiple messages:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
await kafka.publishBatch({
|
|
142
|
+
topic: 'user-events',
|
|
143
|
+
messages: [
|
|
144
|
+
{
|
|
145
|
+
value: { userId: '123', action: 'login' },
|
|
146
|
+
key: 'user-123',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
value: { userId: '456', action: 'logout' },
|
|
150
|
+
key: 'user-456',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
value: { userId: '789', action: 'update' },
|
|
154
|
+
key: 'user-789',
|
|
155
|
+
headers: { priority: 'high' },
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Managing Connection
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// The producer connects automatically on first publish
|
|
165
|
+
await kafka.publish('my-topic', { data: 'value' });
|
|
166
|
+
|
|
167
|
+
// Disconnect when you're done
|
|
168
|
+
await kafka.disconnect();
|
|
169
|
+
|
|
170
|
+
// Graceful shutdown is automatic on SIGTERM/SIGINT
|
|
171
|
+
// unless dontGracefulShutdown is set to true
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Configuration
|
|
175
|
+
|
|
176
|
+
### KafkaOptions
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
interface KafkaOptions {
|
|
180
|
+
// Array of Kafka broker addresses (required)
|
|
181
|
+
brokers: string[];
|
|
182
|
+
|
|
183
|
+
// Client ID for this producer (optional)
|
|
184
|
+
clientId?: string;
|
|
185
|
+
|
|
186
|
+
// Custom logger instance (optional)
|
|
187
|
+
logger?: LoggerInstanceManager;
|
|
188
|
+
|
|
189
|
+
// Skip automatic graceful shutdown (default: false)
|
|
190
|
+
dontGracefulShutdown?: boolean;
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The package uses [@platformatic/kafka](https://www.npmjs.com/package/@platformatic/kafka) with `jsonSerializer` for automatic JSON serialization of message values.
|
|
195
|
+
|
|
196
|
+
## Advanced Features
|
|
197
|
+
|
|
198
|
+
### Message Keys for Partitioning
|
|
199
|
+
|
|
200
|
+
Use message keys to control partitioning and maintain order:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
// All messages for the same user go to the same partition
|
|
204
|
+
await kafka.publish('user-events', event, {
|
|
205
|
+
key: `user-${userId}`,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// All messages for the same order go to the same partition
|
|
209
|
+
await kafka.publish('order-events', event, {
|
|
210
|
+
key: `order-${orderId}`,
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Why use keys?**
|
|
215
|
+
- Ensures messages with the same key are ordered
|
|
216
|
+
- Enables partition-level parallelism
|
|
217
|
+
- Supports stateful stream processing
|
|
218
|
+
|
|
219
|
+
### Custom Headers
|
|
220
|
+
|
|
221
|
+
Headers are useful for metadata and message routing:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
await kafka.publish('events', data, {
|
|
225
|
+
headers: {
|
|
226
|
+
// Correlation ID for distributed tracing
|
|
227
|
+
'correlation-id': correlationId,
|
|
228
|
+
|
|
229
|
+
// Source service
|
|
230
|
+
'source': 'user-service',
|
|
231
|
+
|
|
232
|
+
// Message schema version
|
|
233
|
+
'schema-version': '2.0',
|
|
234
|
+
|
|
235
|
+
// Content encoding
|
|
236
|
+
'encoding': 'json',
|
|
237
|
+
|
|
238
|
+
// Custom application headers
|
|
239
|
+
'tenant-id': 'tenant-123',
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Partition Control
|
|
245
|
+
|
|
246
|
+
Direct partition control for advanced use cases:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// Send to specific partition
|
|
250
|
+
await kafka.publish('events', data, {
|
|
251
|
+
partition: 0, // Always send to partition 0
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Balance across partitions using keys
|
|
255
|
+
const partitionKey = `${customerId % 10}`;
|
|
256
|
+
await kafka.publish('events', data, {
|
|
257
|
+
key: partitionKey,
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## API Reference
|
|
262
|
+
|
|
263
|
+
### `new AfKafka(options)`
|
|
264
|
+
|
|
265
|
+
Creates a new Kafka producer instance.
|
|
266
|
+
|
|
267
|
+
**Parameters:**
|
|
268
|
+
- `options` - Configuration options (see [Configuration](#configuration))
|
|
269
|
+
|
|
270
|
+
**Example:**
|
|
271
|
+
```typescript
|
|
272
|
+
const kafka = new AfKafka({
|
|
273
|
+
brokers: ['localhost:9092'],
|
|
274
|
+
clientId: 'my-service',
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### `publish<T>(topic, value, options?): Promise<RecordMetadata[]>`
|
|
279
|
+
|
|
280
|
+
Publishes a single message to a topic.
|
|
281
|
+
|
|
282
|
+
**Parameters:**
|
|
283
|
+
- `topic` - Topic name
|
|
284
|
+
- `value` - Message value (will be JSON stringified)
|
|
285
|
+
- `options` (optional) - Publish options
|
|
286
|
+
|
|
287
|
+
**Returns:** Array of record metadata (partition, offset, etc.)
|
|
288
|
+
|
|
289
|
+
**Example:**
|
|
290
|
+
```typescript
|
|
291
|
+
const metadata = await kafka.publish('events', { id: 1, data: 'test' }, {
|
|
292
|
+
key: 'key-1',
|
|
293
|
+
headers: { type: 'create' },
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
console.log(`Published to partition ${metadata[0].partition} at offset ${metadata[0].offset}`);
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### `publishBatch(options): Promise<RecordMetadata[]>`
|
|
300
|
+
|
|
301
|
+
Publishes multiple messages in a batch to a topic.
|
|
302
|
+
|
|
303
|
+
**Parameters:**
|
|
304
|
+
- `options.topic` - Topic name
|
|
305
|
+
- `options.messages` - Array of messages with values, keys, headers, etc.
|
|
306
|
+
|
|
307
|
+
**Returns:** Array of record metadata for each message
|
|
308
|
+
|
|
309
|
+
**Example:**
|
|
310
|
+
```typescript
|
|
311
|
+
const metadata = await kafka.publishBatch({
|
|
312
|
+
topic: 'events',
|
|
313
|
+
messages: [
|
|
314
|
+
{ value: { id: 1 }, key: 'key-1' },
|
|
315
|
+
{ value: { id: 2 }, key: 'key-2' },
|
|
316
|
+
],
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### `disconnect(): Promise<void>`
|
|
321
|
+
|
|
322
|
+
Disconnects the producer and cleans up resources.
|
|
323
|
+
|
|
324
|
+
**Example:**
|
|
325
|
+
```typescript
|
|
326
|
+
await kafka.disconnect();
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Best Practices
|
|
330
|
+
|
|
331
|
+
### 1. Use Centralized Topic Definitions
|
|
332
|
+
|
|
333
|
+
Define your topics in `src/topics.ts`:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
export const TOPICS = {
|
|
337
|
+
USER_EVENTS: 'user-events',
|
|
338
|
+
ORDER_EVENTS: 'order-events',
|
|
339
|
+
PAYMENT_EVENTS: 'payment-events',
|
|
340
|
+
} as const;
|
|
341
|
+
|
|
342
|
+
export type TopicName = typeof TOPICS[keyof typeof TOPICS];
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Usage:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { TOPICS } from './topics';
|
|
349
|
+
|
|
350
|
+
await kafka.publish(TOPICS.USER_EVENTS, data);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### 2. Use Message Keys for Ordering
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// Ensure all events for a user are processed in order
|
|
357
|
+
await kafka.publish('user-events', event, {
|
|
358
|
+
key: `user-${userId}`,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Ensure all events for a session are processed in order
|
|
362
|
+
await kafka.publish('session-events', event, {
|
|
363
|
+
key: `session-${sessionId}`,
|
|
364
|
+
});
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### 3. Add Metadata with Headers
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
await kafka.publish('events', data, {
|
|
371
|
+
headers: {
|
|
372
|
+
'correlation-id': requestId,
|
|
373
|
+
'source-service': serviceName,
|
|
374
|
+
'event-type': eventType,
|
|
375
|
+
'schema-version': '1.0',
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### 4. Use Batch Publishing for High Throughput
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
// Instead of multiple publish calls
|
|
384
|
+
for (const event of events) {
|
|
385
|
+
await kafka.publish('events', event); // ❌ Slow
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Use batch publishing
|
|
389
|
+
await kafka.publishBatch({
|
|
390
|
+
topic: 'events',
|
|
391
|
+
messages: events.map(e => ({ value: e })), // ✅ Fast
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### 5. Handle Errors Gracefully
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
try {
|
|
399
|
+
await kafka.publish('events', data);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
logger.error('Failed to publish to Kafka', { error, data });
|
|
402
|
+
// Consider:
|
|
403
|
+
// - Retry logic
|
|
404
|
+
// - Fallback to queue
|
|
405
|
+
// - Alert monitoring
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### 6. Use Multiple Brokers for High Availability
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
const kafka = new AfKafka({
|
|
413
|
+
brokers: ['kafka1:9092', 'kafka2:9092', 'kafka3:9092'],
|
|
414
|
+
clientId: 'my-service',
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### 7. Monitor Production Metrics
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
const metadata = await kafka.publish('events', data);
|
|
422
|
+
|
|
423
|
+
// Log metrics
|
|
424
|
+
logger.info('Message published', {
|
|
425
|
+
topic: 'events',
|
|
426
|
+
partition: metadata[0].partition,
|
|
427
|
+
offset: metadata[0].offset,
|
|
428
|
+
latency: Date.now() - startTime,
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
## Testing
|
|
433
|
+
|
|
434
|
+
Run the test suite:
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
pnpm test
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Run tests with coverage:
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
pnpm run coverage
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Example Test
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
450
|
+
import AfKafka from '@autofleet/kafka';
|
|
451
|
+
|
|
452
|
+
describe('MyKafkaService', () => {
|
|
453
|
+
let kafka: AfKafka;
|
|
454
|
+
|
|
455
|
+
beforeEach(() => {
|
|
456
|
+
kafka = new AfKafka({
|
|
457
|
+
brokers: ['localhost:9092'],
|
|
458
|
+
clientId: 'test-client',
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('should publish event successfully', async () => {
|
|
463
|
+
const result = await kafka.publish('test-topic', {
|
|
464
|
+
userId: '123',
|
|
465
|
+
action: 'test',
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
expect(result).toBeDefined();
|
|
469
|
+
expect(result[0]).toHaveProperty('partition');
|
|
470
|
+
expect(result[0]).toHaveProperty('offset');
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## Environment Variables
|
|
476
|
+
|
|
477
|
+
Common environment variable patterns:
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
# Kafka brokers
|
|
481
|
+
KAFKA_BROKERS=kafka1:9092,kafka2:9092,kafka3:9092
|
|
482
|
+
|
|
483
|
+
# Client configuration
|
|
484
|
+
KAFKA_CLIENT_ID=my-service
|
|
485
|
+
KAFKA_SASL_USERNAME=user
|
|
486
|
+
KAFKA_SASL_PASSWORD=pass
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Usage:
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
const kafka = new AfKafka({
|
|
493
|
+
brokers: process.env.KAFKA_BROKERS?.split(',') || ['localhost:9092'],
|
|
494
|
+
clientId: process.env.KAFKA_CLIENT_ID || 'default-client',
|
|
495
|
+
});
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
## Common Patterns
|
|
499
|
+
|
|
500
|
+
### Event Sourcing
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
await kafka.publish('user-events', {
|
|
504
|
+
eventType: 'UserCreated',
|
|
505
|
+
aggregateId: userId,
|
|
506
|
+
timestamp: Date.now(),
|
|
507
|
+
data: userData,
|
|
508
|
+
}, {
|
|
509
|
+
key: userId,
|
|
510
|
+
headers: {
|
|
511
|
+
'event-type': 'UserCreated',
|
|
512
|
+
'aggregate-type': 'User',
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Change Data Capture (CDC)
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
await kafka.publish('database-changes', {
|
|
521
|
+
operation: 'INSERT',
|
|
522
|
+
table: 'users',
|
|
523
|
+
before: null,
|
|
524
|
+
after: newUserRecord,
|
|
525
|
+
}, {
|
|
526
|
+
key: newUserRecord.id,
|
|
527
|
+
headers: {
|
|
528
|
+
'schema': 'public',
|
|
529
|
+
'table': 'users',
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Dead Letter Queue Pattern
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
try {
|
|
538
|
+
await processMessage(message);
|
|
539
|
+
} catch (error) {
|
|
540
|
+
// Send to DLQ for manual review
|
|
541
|
+
await kafka.publish('events-dlq', {
|
|
542
|
+
originalTopic: 'events',
|
|
543
|
+
error: error.message,
|
|
544
|
+
message: message,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
## Troubleshooting
|
|
550
|
+
|
|
551
|
+
### Connection Issues
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
// Use multiple brokers for redundancy
|
|
555
|
+
const kafka = new AfKafka({
|
|
556
|
+
brokers: ['kafka1:9092', 'kafka2:9092', 'kafka3:9092'],
|
|
557
|
+
clientId: 'my-service',
|
|
558
|
+
});
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Performance Tuning
|
|
562
|
+
|
|
563
|
+
For high-throughput scenarios, use batch publishing:
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
// Batch multiple messages together
|
|
567
|
+
await kafka.publishBatch({
|
|
568
|
+
topic: 'events',
|
|
569
|
+
messages: events.map(e => ({ value: e })),
|
|
570
|
+
});
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
## License
|
|
574
|
+
|
|
575
|
+
Proprietary - Autofleet
|
|
576
|
+
|
|
577
|
+
## Support
|
|
578
|
+
|
|
579
|
+
For issues or questions, please contact the platform team or create an issue in the repository.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Object.defineProperty(exports,`__esModule`,{value:!0});let e=require(`@platformatic/kafka`);const t={info:(...e)=>console.log(`[INFO]`,...e),error:(...e)=>console.error(`[ERROR]`,...e),warn:(...e)=>console.warn(`[WARN]`,...e),debug:(...e)=>console.debug(`[DEBUG]`,...e)};var n=t,r=class extends Error{constructor(e){super(e),this.name=`KafkaError`}};const i=`x-timestamp`,a=`autofleet-kafka-producer`;var o=class{constructor(t){if(this.isDisconnected=!1,this.isVerified=!1,this.gracefulShutdownStarted=!1,!t.brokers||t.brokers.length===0)throw new r(`At least one broker is required`);this.logger=t.logger??n,this.producer=new e.Producer({bootstrapBrokers:t.brokers,clientId:t.clientId||a,serializers:e.stringSerializers}),this.logger.info(`Kafka: Initializing Kafka producer`,{clientId:t.clientId||a,brokers:t.brokers}),t.dontGracefulShutdown||this.setupGracefulShutdown()}setupGracefulShutdown(){this.logger.info(`Kafka: [graceful-shutdown] adding graceful shutdown for process.pid ${process.pid}`),process.on(`SIGTERM`,async()=>{await this.gracefulShutdown(`SIGTERM`)}),process.on(`SIGINT`,async()=>{await this.gracefulShutdown(`SIGINT`)})}async gracefulShutdown(e){if(!this.gracefulShutdownStarted){this.gracefulShutdownStarted=!0,this.logger.info(`Kafka: [graceful-shutdown] received ${e}! Disconnecting producer...`);try{await this.disconnect(),this.logger.info(`Kafka: [graceful-shutdown] producer disconnected successfully`)}catch(e){throw this.logger.error(`Kafka: [graceful-shutdown] error during shutdown`,{error:e}),e}}}get isConnected(){return this.producer.isConnected()}async ping(){if(!this.isVerified)try{let e=await this.producer.metadata({topics:[]});this.isVerified=!0,this.logger.info(`Kafka: Successfully connected to cluster`,{clusterId:e.id,brokers:e.brokers.size})}catch(e){throw this.logger.error(`Kafka: Failed to connect to brokers`,{error:e}),new r(`Failed to connect to Kafka brokers: ${e.message}`)}}async publish(e,t,n){if(!e)throw new r(`Topic name is required`);await this.ping();try{let r={[i]:Date.now().toString(),...n?.headers},a=await this.producer.send({messages:[{topic:e,value:JSON.stringify(t),key:n?.key,partition:n?.partition,headers:r}]});return this.logger.debug(`Kafka: Published message to topic ${e}`,{topic:e}),[a]}catch(n){let i=n;throw this.logger.error(`Kafka: Error publishing to topic ${e}`,{error:n,value:t}),new r(`Failed to publish to topic ${e}: ${i.message||`Unknown error`}`)}}async publishBatch(e){if(!e.topic)throw new r(`Topic name is required`);if(!e.messages||e.messages.length===0)throw new r(`At least one message is required`);await this.ping();try{let t=e.messages.map(t=>({topic:e.topic,value:JSON.stringify(t.value),key:t.key,partition:t.partition,headers:{[i]:Date.now().toString(),...t.headers}})),n=await this.producer.send({messages:t});return this.logger.debug(`Kafka: Published ${t.length} messages to topic ${e.topic}`,{topic:e.topic,count:t.length}),[n]}catch(t){let n=t;throw this.logger.error(`Kafka: Error publishing batch to topic ${e.topic}`,{error:t}),new r(`Failed to publish batch to topic ${e.topic}: ${n.message||`Unknown error`}`)}}async disconnect(){if(this.isDisconnected){this.logger.debug(`Kafka: Producer already disconnected`);return}this.logger.info(`Kafka: Disconnecting producer`);try{await this.producer.close(),this.isDisconnected=!0,this.logger.info(`Kafka: Producer disconnected successfully`)}catch(e){throw this.logger.error(`Kafka: Error disconnecting producer`,{error:e}),new r(`Failed to disconnect producer: ${e.message}`)}}},s=o;exports.KafkaError=r,exports.default=s;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["fallbackLogger: LoggerInstanceManager","fallbackLogger","Producer","stringSerializers","headers: Record<string, string>"],"sources":["../src/logger.ts","../src/kafkaError.ts","../src/consts.ts","../src/index.ts"],"sourcesContent":["import type { LoggerInstanceManager } from '@autofleet/logger';\n\n/* eslint-disable no-console */\nconst fallbackLogger: LoggerInstanceManager = {\n info: (...args: unknown[]) => console.log('[INFO]', ...args),\n error: (...args: unknown[]) => console.error('[ERROR]', ...args),\n warn: (...args: unknown[]) => console.warn('[WARN]', ...args),\n debug: (...args: unknown[]) => console.debug('[DEBUG]', ...args),\n} as LoggerInstanceManager;\n/* eslint-enable no-console */\n\nexport default fallbackLogger;\n","export default class KafkaError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'KafkaError';\n }\n}\n","export const TIMESTAMP_HEADER = 'x-timestamp';\nexport const CORRELATION_ID_HEADER = 'x-correlation-id';\nexport const SOURCE_HEADER = 'x-source';\n\nexport const DEFAULT_CLIENT_ID = 'autofleet-kafka-producer';\nexport const DEFAULT_TIMEOUT = 30000; // 30 seconds\nexport const DEFAULT_RETRY = {\n retries: 5,\n initialRetryTime: 300,\n maxRetryTime: 30000,\n};\n","import { Producer, type ProduceResult, stringSerializers } from '@platformatic/kafka';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport fallbackLogger from './logger';\nimport KafkaError from './kafkaError';\nimport {\n DEFAULT_CLIENT_ID,\n TIMESTAMP_HEADER,\n} from './consts';\nimport type {\n KafkaOptions,\n PublishOptions,\n PublishBatchOptions,\n RecordMetadata,\n} from './types';\n\nexport interface IAfKafka {\n ping(): Promise<void>;\n publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<ProduceResult[]>;\n publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]>;\n disconnect(): Promise<void>;\n}\n\nclass AfKafka implements IAfKafka {\n private readonly producer: Producer<string, string, string, string>;\n private readonly logger: LoggerInstanceManager;\n private isDisconnected = false;\n private isVerified = false;\n private gracefulShutdownStarted = false;\n\n constructor(options: KafkaOptions) {\n if (!options.brokers || options.brokers.length === 0) {\n throw new KafkaError('At least one broker is required');\n }\n\n this.logger = options.logger ?? fallbackLogger;\n\n this.producer = new Producer({\n bootstrapBrokers: options.brokers,\n clientId: options.clientId || DEFAULT_CLIENT_ID,\n serializers: stringSerializers,\n });\n\n this.logger.info('Kafka: Initializing Kafka producer', {\n clientId: options.clientId || DEFAULT_CLIENT_ID,\n brokers: options.brokers,\n });\n\n if (!options.dontGracefulShutdown) {\n this.setupGracefulShutdown();\n }\n }\n\n private setupGracefulShutdown(): void {\n this.logger.info(`Kafka: [graceful-shutdown] adding graceful shutdown for process.pid ${process.pid}`);\n\n process.on('SIGTERM', async () => {\n await this.gracefulShutdown('SIGTERM');\n });\n\n process.on('SIGINT', async () => {\n await this.gracefulShutdown('SIGINT');\n });\n }\n\n private async gracefulShutdown(signal: string): Promise<void> {\n if (this.gracefulShutdownStarted) {\n return;\n }\n\n this.gracefulShutdownStarted = true;\n\n this.logger.info(`Kafka: [graceful-shutdown] received ${signal}! Disconnecting producer...`);\n\n try {\n await this.disconnect();\n this.logger.info('Kafka: [graceful-shutdown] producer disconnected successfully');\n } catch (error) {\n this.logger.error('Kafka: [graceful-shutdown] error during shutdown', { error });\n throw error;\n }\n }\n\n get isConnected(): boolean {\n return this.producer.isConnected();\n }\n\n /**\n * Ping Kafka brokers to verify connectivity\n */\n async ping(): Promise<void> {\n if (this.isVerified) {\n return;\n }\n\n try {\n // Fetch metadata to verify connection\n const metadata = await this.producer.metadata({ topics: [] });\n this.isVerified = true;\n\n this.logger.info('Kafka: Successfully connected to cluster', {\n clusterId: metadata.id,\n brokers: metadata.brokers.size,\n });\n } catch (error) {\n this.logger.error('Kafka: Failed to connect to brokers', { error });\n throw new KafkaError(`Failed to connect to Kafka brokers: ${(error as Error).message}`);\n }\n }\n\n /**\n * Publish a single message to a topic\n */\n async publish<T = unknown>(\n topic: string,\n value: T,\n options?: PublishOptions,\n ): Promise<ProduceResult[]> {\n if (!topic) {\n throw new KafkaError('Topic name is required');\n }\n\n // Verify connection on first publish\n await this.ping();\n\n try {\n const headers: Record<string, string> = {\n [TIMESTAMP_HEADER]: Date.now().toString(),\n ...options?.headers,\n };\n\n const res = await this.producer.send({\n messages: [{\n topic,\n value: JSON.stringify(value),\n key: options?.key,\n partition: options?.partition,\n headers,\n }],\n });\n\n this.logger.debug(`Kafka: Published message to topic ${topic}`, {\n topic,\n });\n\n return [res];\n } catch (error) {\n const err = error as { message?: string; };\n this.logger.error(`Kafka: Error publishing to topic ${topic}`, { error, value });\n throw new KafkaError(`Failed to publish to topic ${topic}: ${err.message || 'Unknown error'}`);\n }\n }\n\n /**\n * Publish multiple messages in a batch\n */\n async publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]> {\n if (!options.topic) {\n throw new KafkaError('Topic name is required');\n }\n\n if (!options.messages || options.messages.length === 0) {\n throw new KafkaError('At least one message is required');\n }\n\n // Verify connection on first publish\n await this.ping();\n\n try {\n const messages = options.messages.map(msg => ({\n topic: options.topic,\n value: JSON.stringify(msg.value),\n key: msg.key,\n partition: msg.partition,\n headers: {\n [TIMESTAMP_HEADER]: Date.now().toString(),\n ...msg.headers,\n },\n }));\n\n const res = await this.producer.send({ messages });\n\n this.logger.debug(`Kafka: Published ${messages.length} messages to topic ${options.topic}`, {\n topic: options.topic,\n count: messages.length,\n });\n\n // Return mock metadata since @platformatic/kafka send() returns void\n return [res];\n } catch (error) {\n const err = error as { message?: string; };\n this.logger.error(`Kafka: Error publishing batch to topic ${options.topic}`, { error });\n throw new KafkaError(`Failed to publish batch to topic ${options.topic}: ${err.message || 'Unknown error'}`);\n }\n }\n\n /**\n * Disconnect the producer and clean up resources\n */\n async disconnect(): Promise<void> {\n if (this.isDisconnected) {\n this.logger.debug('Kafka: Producer already disconnected');\n return;\n }\n\n this.logger.info('Kafka: Disconnecting producer');\n\n try {\n await this.producer.close();\n this.isDisconnected = true;\n this.logger.info('Kafka: Producer disconnected successfully');\n } catch (error) {\n this.logger.error('Kafka: Error disconnecting producer', { error });\n throw new KafkaError(`Failed to disconnect producer: ${(error as Error).message}`);\n }\n }\n}\n\nexport default AfKafka;\nexport { KafkaError };\nexport type {\n KafkaOptions,\n PublishOptions,\n PublishBatchOptions,\n RecordMetadata,\n};\n"],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ProduceResult } from "@platformatic/kafka";
|
|
2
|
+
import { LoggerInstanceManager } from "@autofleet/logger";
|
|
3
|
+
|
|
4
|
+
//#region src/kafkaError.d.ts
|
|
5
|
+
declare class KafkaError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/types.d.ts
|
|
10
|
+
interface KafkaOptions {
|
|
11
|
+
/** Kafka broker addresses */
|
|
12
|
+
brokers: string[];
|
|
13
|
+
/** Client ID for this producer */
|
|
14
|
+
clientId?: string;
|
|
15
|
+
/** Custom logger instance */
|
|
16
|
+
logger?: LoggerInstanceManager;
|
|
17
|
+
/**
|
|
18
|
+
* When you want your own graceful shutdown, set this to true.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
dontGracefulShutdown?: boolean;
|
|
22
|
+
}
|
|
23
|
+
interface PublishOptions {
|
|
24
|
+
/** Custom headers to attach to the message */
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
/** Message key for partitioning */
|
|
27
|
+
key?: string;
|
|
28
|
+
/** Specific partition to send to */
|
|
29
|
+
partition?: number;
|
|
30
|
+
}
|
|
31
|
+
interface PublishBatchOptions {
|
|
32
|
+
/** Topic to publish to */
|
|
33
|
+
topic: string;
|
|
34
|
+
/** Messages to publish */
|
|
35
|
+
messages: {
|
|
36
|
+
value: unknown;
|
|
37
|
+
key?: string;
|
|
38
|
+
headers?: Record<string, string>;
|
|
39
|
+
partition?: number;
|
|
40
|
+
}[];
|
|
41
|
+
}
|
|
42
|
+
interface RecordMetadata {
|
|
43
|
+
topic: string;
|
|
44
|
+
partition: number;
|
|
45
|
+
offset: string;
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/index.d.ts
|
|
49
|
+
interface IAfKafka {
|
|
50
|
+
ping(): Promise<void>;
|
|
51
|
+
publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<ProduceResult[]>;
|
|
52
|
+
publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]>;
|
|
53
|
+
disconnect(): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
declare class AfKafka implements IAfKafka {
|
|
56
|
+
private readonly producer;
|
|
57
|
+
private readonly logger;
|
|
58
|
+
private isDisconnected;
|
|
59
|
+
private isVerified;
|
|
60
|
+
private gracefulShutdownStarted;
|
|
61
|
+
constructor(options: KafkaOptions);
|
|
62
|
+
private setupGracefulShutdown;
|
|
63
|
+
private gracefulShutdown;
|
|
64
|
+
get isConnected(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Ping Kafka brokers to verify connectivity
|
|
67
|
+
*/
|
|
68
|
+
ping(): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Publish a single message to a topic
|
|
71
|
+
*/
|
|
72
|
+
publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<ProduceResult[]>;
|
|
73
|
+
/**
|
|
74
|
+
* Publish multiple messages in a batch
|
|
75
|
+
*/
|
|
76
|
+
publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Disconnect the producer and clean up resources
|
|
79
|
+
*/
|
|
80
|
+
disconnect(): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { IAfKafka, KafkaError, type KafkaOptions, type PublishBatchOptions, type PublishOptions, type RecordMetadata, AfKafka as default };
|
|
84
|
+
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ProduceResult } from "@platformatic/kafka";
|
|
2
|
+
import { LoggerInstanceManager } from "@autofleet/logger";
|
|
3
|
+
|
|
4
|
+
//#region src/kafkaError.d.ts
|
|
5
|
+
declare class KafkaError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/types.d.ts
|
|
10
|
+
interface KafkaOptions {
|
|
11
|
+
/** Kafka broker addresses */
|
|
12
|
+
brokers: string[];
|
|
13
|
+
/** Client ID for this producer */
|
|
14
|
+
clientId?: string;
|
|
15
|
+
/** Custom logger instance */
|
|
16
|
+
logger?: LoggerInstanceManager;
|
|
17
|
+
/**
|
|
18
|
+
* When you want your own graceful shutdown, set this to true.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
dontGracefulShutdown?: boolean;
|
|
22
|
+
}
|
|
23
|
+
interface PublishOptions {
|
|
24
|
+
/** Custom headers to attach to the message */
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
/** Message key for partitioning */
|
|
27
|
+
key?: string;
|
|
28
|
+
/** Specific partition to send to */
|
|
29
|
+
partition?: number;
|
|
30
|
+
}
|
|
31
|
+
interface PublishBatchOptions {
|
|
32
|
+
/** Topic to publish to */
|
|
33
|
+
topic: string;
|
|
34
|
+
/** Messages to publish */
|
|
35
|
+
messages: {
|
|
36
|
+
value: unknown;
|
|
37
|
+
key?: string;
|
|
38
|
+
headers?: Record<string, string>;
|
|
39
|
+
partition?: number;
|
|
40
|
+
}[];
|
|
41
|
+
}
|
|
42
|
+
interface RecordMetadata {
|
|
43
|
+
topic: string;
|
|
44
|
+
partition: number;
|
|
45
|
+
offset: string;
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/index.d.ts
|
|
49
|
+
interface IAfKafka {
|
|
50
|
+
ping(): Promise<void>;
|
|
51
|
+
publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<ProduceResult[]>;
|
|
52
|
+
publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]>;
|
|
53
|
+
disconnect(): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
declare class AfKafka implements IAfKafka {
|
|
56
|
+
private readonly producer;
|
|
57
|
+
private readonly logger;
|
|
58
|
+
private isDisconnected;
|
|
59
|
+
private isVerified;
|
|
60
|
+
private gracefulShutdownStarted;
|
|
61
|
+
constructor(options: KafkaOptions);
|
|
62
|
+
private setupGracefulShutdown;
|
|
63
|
+
private gracefulShutdown;
|
|
64
|
+
get isConnected(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Ping Kafka brokers to verify connectivity
|
|
67
|
+
*/
|
|
68
|
+
ping(): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Publish a single message to a topic
|
|
71
|
+
*/
|
|
72
|
+
publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<ProduceResult[]>;
|
|
73
|
+
/**
|
|
74
|
+
* Publish multiple messages in a batch
|
|
75
|
+
*/
|
|
76
|
+
publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Disconnect the producer and clean up resources
|
|
79
|
+
*/
|
|
80
|
+
disconnect(): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { IAfKafka, KafkaError, type KafkaOptions, type PublishBatchOptions, type PublishOptions, type RecordMetadata, AfKafka as default };
|
|
84
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{Producer as e,stringSerializers as t}from"@platformatic/kafka";var n={info:(...e)=>console.log(`[INFO]`,...e),error:(...e)=>console.error(`[ERROR]`,...e),warn:(...e)=>console.warn(`[WARN]`,...e),debug:(...e)=>console.debug(`[DEBUG]`,...e)},r=class extends Error{constructor(e){super(e),this.name=`KafkaError`}};const i=`x-timestamp`,a=`autofleet-kafka-producer`;var o=class{constructor(i){if(this.isDisconnected=!1,this.isVerified=!1,this.gracefulShutdownStarted=!1,!i.brokers||i.brokers.length===0)throw new r(`At least one broker is required`);this.logger=i.logger??n,this.producer=new e({bootstrapBrokers:i.brokers,clientId:i.clientId||a,serializers:t}),this.logger.info(`Kafka: Initializing Kafka producer`,{clientId:i.clientId||a,brokers:i.brokers}),i.dontGracefulShutdown||this.setupGracefulShutdown()}setupGracefulShutdown(){this.logger.info(`Kafka: [graceful-shutdown] adding graceful shutdown for process.pid ${process.pid}`),process.on(`SIGTERM`,async()=>{await this.gracefulShutdown(`SIGTERM`)}),process.on(`SIGINT`,async()=>{await this.gracefulShutdown(`SIGINT`)})}async gracefulShutdown(e){if(!this.gracefulShutdownStarted){this.gracefulShutdownStarted=!0,this.logger.info(`Kafka: [graceful-shutdown] received ${e}! Disconnecting producer...`);try{await this.disconnect(),this.logger.info(`Kafka: [graceful-shutdown] producer disconnected successfully`)}catch(e){throw this.logger.error(`Kafka: [graceful-shutdown] error during shutdown`,{error:e}),e}}}get isConnected(){return this.producer.isConnected()}async ping(){if(!this.isVerified)try{let e=await this.producer.metadata({topics:[]});this.isVerified=!0,this.logger.info(`Kafka: Successfully connected to cluster`,{clusterId:e.id,brokers:e.brokers.size})}catch(e){throw this.logger.error(`Kafka: Failed to connect to brokers`,{error:e}),new r(`Failed to connect to Kafka brokers: ${e.message}`)}}async publish(e,t,n){if(!e)throw new r(`Topic name is required`);await this.ping();try{let r={[i]:Date.now().toString(),...n?.headers},a=await this.producer.send({messages:[{topic:e,value:JSON.stringify(t),key:n?.key,partition:n?.partition,headers:r}]});return this.logger.debug(`Kafka: Published message to topic ${e}`,{topic:e}),[a]}catch(n){let i=n;throw this.logger.error(`Kafka: Error publishing to topic ${e}`,{error:n,value:t}),new r(`Failed to publish to topic ${e}: ${i.message||`Unknown error`}`)}}async publishBatch(e){if(!e.topic)throw new r(`Topic name is required`);if(!e.messages||e.messages.length===0)throw new r(`At least one message is required`);await this.ping();try{let t=e.messages.map(t=>({topic:e.topic,value:JSON.stringify(t.value),key:t.key,partition:t.partition,headers:{[i]:Date.now().toString(),...t.headers}})),n=await this.producer.send({messages:t});return this.logger.debug(`Kafka: Published ${t.length} messages to topic ${e.topic}`,{topic:e.topic,count:t.length}),[n]}catch(t){let n=t;throw this.logger.error(`Kafka: Error publishing batch to topic ${e.topic}`,{error:t}),new r(`Failed to publish batch to topic ${e.topic}: ${n.message||`Unknown error`}`)}}async disconnect(){if(this.isDisconnected){this.logger.debug(`Kafka: Producer already disconnected`);return}this.logger.info(`Kafka: Disconnecting producer`);try{await this.producer.close(),this.isDisconnected=!0,this.logger.info(`Kafka: Producer disconnected successfully`)}catch(e){throw this.logger.error(`Kafka: Error disconnecting producer`,{error:e}),new r(`Failed to disconnect producer: ${e.message}`)}}};export{r as KafkaError,o as default};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["fallbackLogger: LoggerInstanceManager","fallbackLogger","headers: Record<string, string>"],"sources":["../src/logger.ts","../src/kafkaError.ts","../src/consts.ts","../src/index.ts"],"sourcesContent":["import type { LoggerInstanceManager } from '@autofleet/logger';\n\n/* eslint-disable no-console */\nconst fallbackLogger: LoggerInstanceManager = {\n info: (...args: unknown[]) => console.log('[INFO]', ...args),\n error: (...args: unknown[]) => console.error('[ERROR]', ...args),\n warn: (...args: unknown[]) => console.warn('[WARN]', ...args),\n debug: (...args: unknown[]) => console.debug('[DEBUG]', ...args),\n} as LoggerInstanceManager;\n/* eslint-enable no-console */\n\nexport default fallbackLogger;\n","export default class KafkaError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'KafkaError';\n }\n}\n","export const TIMESTAMP_HEADER = 'x-timestamp';\nexport const CORRELATION_ID_HEADER = 'x-correlation-id';\nexport const SOURCE_HEADER = 'x-source';\n\nexport const DEFAULT_CLIENT_ID = 'autofleet-kafka-producer';\nexport const DEFAULT_TIMEOUT = 30000; // 30 seconds\nexport const DEFAULT_RETRY = {\n retries: 5,\n initialRetryTime: 300,\n maxRetryTime: 30000,\n};\n","import { Producer, type ProduceResult, stringSerializers } from '@platformatic/kafka';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport fallbackLogger from './logger';\nimport KafkaError from './kafkaError';\nimport {\n DEFAULT_CLIENT_ID,\n TIMESTAMP_HEADER,\n} from './consts';\nimport type {\n KafkaOptions,\n PublishOptions,\n PublishBatchOptions,\n RecordMetadata,\n} from './types';\n\nexport interface IAfKafka {\n ping(): Promise<void>;\n publish<T = unknown>(topic: string, value: T, options?: PublishOptions): Promise<ProduceResult[]>;\n publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]>;\n disconnect(): Promise<void>;\n}\n\nclass AfKafka implements IAfKafka {\n private readonly producer: Producer<string, string, string, string>;\n private readonly logger: LoggerInstanceManager;\n private isDisconnected = false;\n private isVerified = false;\n private gracefulShutdownStarted = false;\n\n constructor(options: KafkaOptions) {\n if (!options.brokers || options.brokers.length === 0) {\n throw new KafkaError('At least one broker is required');\n }\n\n this.logger = options.logger ?? fallbackLogger;\n\n this.producer = new Producer({\n bootstrapBrokers: options.brokers,\n clientId: options.clientId || DEFAULT_CLIENT_ID,\n serializers: stringSerializers,\n });\n\n this.logger.info('Kafka: Initializing Kafka producer', {\n clientId: options.clientId || DEFAULT_CLIENT_ID,\n brokers: options.brokers,\n });\n\n if (!options.dontGracefulShutdown) {\n this.setupGracefulShutdown();\n }\n }\n\n private setupGracefulShutdown(): void {\n this.logger.info(`Kafka: [graceful-shutdown] adding graceful shutdown for process.pid ${process.pid}`);\n\n process.on('SIGTERM', async () => {\n await this.gracefulShutdown('SIGTERM');\n });\n\n process.on('SIGINT', async () => {\n await this.gracefulShutdown('SIGINT');\n });\n }\n\n private async gracefulShutdown(signal: string): Promise<void> {\n if (this.gracefulShutdownStarted) {\n return;\n }\n\n this.gracefulShutdownStarted = true;\n\n this.logger.info(`Kafka: [graceful-shutdown] received ${signal}! Disconnecting producer...`);\n\n try {\n await this.disconnect();\n this.logger.info('Kafka: [graceful-shutdown] producer disconnected successfully');\n } catch (error) {\n this.logger.error('Kafka: [graceful-shutdown] error during shutdown', { error });\n throw error;\n }\n }\n\n get isConnected(): boolean {\n return this.producer.isConnected();\n }\n\n /**\n * Ping Kafka brokers to verify connectivity\n */\n async ping(): Promise<void> {\n if (this.isVerified) {\n return;\n }\n\n try {\n // Fetch metadata to verify connection\n const metadata = await this.producer.metadata({ topics: [] });\n this.isVerified = true;\n\n this.logger.info('Kafka: Successfully connected to cluster', {\n clusterId: metadata.id,\n brokers: metadata.brokers.size,\n });\n } catch (error) {\n this.logger.error('Kafka: Failed to connect to brokers', { error });\n throw new KafkaError(`Failed to connect to Kafka brokers: ${(error as Error).message}`);\n }\n }\n\n /**\n * Publish a single message to a topic\n */\n async publish<T = unknown>(\n topic: string,\n value: T,\n options?: PublishOptions,\n ): Promise<ProduceResult[]> {\n if (!topic) {\n throw new KafkaError('Topic name is required');\n }\n\n // Verify connection on first publish\n await this.ping();\n\n try {\n const headers: Record<string, string> = {\n [TIMESTAMP_HEADER]: Date.now().toString(),\n ...options?.headers,\n };\n\n const res = await this.producer.send({\n messages: [{\n topic,\n value: JSON.stringify(value),\n key: options?.key,\n partition: options?.partition,\n headers,\n }],\n });\n\n this.logger.debug(`Kafka: Published message to topic ${topic}`, {\n topic,\n });\n\n return [res];\n } catch (error) {\n const err = error as { message?: string; };\n this.logger.error(`Kafka: Error publishing to topic ${topic}`, { error, value });\n throw new KafkaError(`Failed to publish to topic ${topic}: ${err.message || 'Unknown error'}`);\n }\n }\n\n /**\n * Publish multiple messages in a batch\n */\n async publishBatch(options: PublishBatchOptions): Promise<ProduceResult[]> {\n if (!options.topic) {\n throw new KafkaError('Topic name is required');\n }\n\n if (!options.messages || options.messages.length === 0) {\n throw new KafkaError('At least one message is required');\n }\n\n // Verify connection on first publish\n await this.ping();\n\n try {\n const messages = options.messages.map(msg => ({\n topic: options.topic,\n value: JSON.stringify(msg.value),\n key: msg.key,\n partition: msg.partition,\n headers: {\n [TIMESTAMP_HEADER]: Date.now().toString(),\n ...msg.headers,\n },\n }));\n\n const res = await this.producer.send({ messages });\n\n this.logger.debug(`Kafka: Published ${messages.length} messages to topic ${options.topic}`, {\n topic: options.topic,\n count: messages.length,\n });\n\n // Return mock metadata since @platformatic/kafka send() returns void\n return [res];\n } catch (error) {\n const err = error as { message?: string; };\n this.logger.error(`Kafka: Error publishing batch to topic ${options.topic}`, { error });\n throw new KafkaError(`Failed to publish batch to topic ${options.topic}: ${err.message || 'Unknown error'}`);\n }\n }\n\n /**\n * Disconnect the producer and clean up resources\n */\n async disconnect(): Promise<void> {\n if (this.isDisconnected) {\n this.logger.debug('Kafka: Producer already disconnected');\n return;\n }\n\n this.logger.info('Kafka: Disconnecting producer');\n\n try {\n await this.producer.close();\n this.isDisconnected = true;\n this.logger.info('Kafka: Producer disconnected successfully');\n } catch (error) {\n this.logger.error('Kafka: Error disconnecting producer', { error });\n throw new KafkaError(`Failed to disconnect producer: ${(error as Error).message}`);\n }\n }\n}\n\nexport default AfKafka;\nexport { KafkaError };\nexport type {\n KafkaOptions,\n PublishOptions,\n PublishBatchOptions,\n RecordMetadata,\n};\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@autofleet/kafka",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Internal wrapper for Apache Kafka producer",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.cts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18.0.0"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsdown",
|
|
28
|
+
"test": "vitest",
|
|
29
|
+
"coverage": "vitest --coverage"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/Autofleet/autorepo.git"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/Autofleet/autorepo/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/Autofleet/autorepo/tree/master/packages/kafka#readme",
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@platformatic/kafka": "^1.21.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@autofleet/logger": "*"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@autofleet/logger": "workspace:^",
|
|
47
|
+
"@types/node": "^20.14.11"
|
|
48
|
+
},
|
|
49
|
+
"keywords": ["kafka", "producer", "messaging"],
|
|
50
|
+
"author": "",
|
|
51
|
+
"license": "Proprietary",
|
|
52
|
+
"packageManager": "pnpm@10.24.0"
|
|
53
|
+
}
|