@autofleet/kafka 0.1.1 → 0.2.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 +649 -17
- package/dist/index.cjs +3 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +176 -4
- package/dist/index.d.ts +176 -4
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,11 @@ Internal wrapper for Apache Kafka producer using [@platformatic/kafka](https://w
|
|
|
7
7
|
- [Installation](#installation)
|
|
8
8
|
- [Features](#features)
|
|
9
9
|
- [Quick Start](#quick-start)
|
|
10
|
+
- [Initialization & Bootstrap](#initialization--bootstrap)
|
|
11
|
+
- [Lifecycle Overview](#lifecycle-overview)
|
|
12
|
+
- [Production Bootstrap Pattern](#production-bootstrap-pattern)
|
|
13
|
+
- [Development Bootstrap Pattern](#development-bootstrap-pattern)
|
|
14
|
+
- [Bootstrap Options](#bootstrap-options)
|
|
10
15
|
- [Usage](#usage)
|
|
11
16
|
- [Setup with Multiple Producers](#setup-with-multiple-producers)
|
|
12
17
|
- [Publishing Messages](#publishing-messages)
|
|
@@ -20,6 +25,7 @@ Internal wrapper for Apache Kafka producer using [@platformatic/kafka](https://w
|
|
|
20
25
|
- [Partition Control](#partition-control)
|
|
21
26
|
- [API Reference](#api-reference)
|
|
22
27
|
- [Best Practices](#best-practices)
|
|
28
|
+
- [Troubleshooting](#troubleshooting)
|
|
23
29
|
- [Testing](#testing)
|
|
24
30
|
|
|
25
31
|
## Installation
|
|
@@ -91,6 +97,177 @@ app.get('/health/ready', async (req, res) => {
|
|
|
91
97
|
});
|
|
92
98
|
```
|
|
93
99
|
|
|
100
|
+
## Initialization & Bootstrap
|
|
101
|
+
|
|
102
|
+
### Lifecycle Overview
|
|
103
|
+
|
|
104
|
+
The `@autofleet/kafka` package follows a clear, predictable lifecycle:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
┌─────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐
|
|
108
|
+
│ create() │────▶│ bootstrap() │────▶│ publish/use │────▶│disconnect()│
|
|
109
|
+
│ (sync) │ │ (async) │ │ (async) │ │ (async) │
|
|
110
|
+
└─────────────┘ └──────────────┘ └──────────────┘ └────────────┘
|
|
111
|
+
Instant Validates Runtime Cleanup
|
|
112
|
+
construction connectivity operations
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**1. `create()` - Synchronous Construction**
|
|
116
|
+
- Creates the KafkaManager instance
|
|
117
|
+
- Sets up configuration
|
|
118
|
+
- Does NOT connect to Kafka yet
|
|
119
|
+
- Returns immediately
|
|
120
|
+
|
|
121
|
+
**2. `bootstrap()` - Async Initialization**
|
|
122
|
+
- Explicitly connects all producers to Kafka
|
|
123
|
+
- Validates broker connectivity
|
|
124
|
+
- Fetches cluster metadata
|
|
125
|
+
- Fails fast with detailed diagnostics
|
|
126
|
+
- **Recommended before serving traffic**
|
|
127
|
+
|
|
128
|
+
**3. Runtime Operations**
|
|
129
|
+
- `publish()`, `publishBatch()` work normally
|
|
130
|
+
- Lazy initialization also supported (backwards compatible)
|
|
131
|
+
- Health checks available
|
|
132
|
+
|
|
133
|
+
**4. `disconnect()` - Cleanup**
|
|
134
|
+
- Gracefully closes all connections
|
|
135
|
+
- Automatic on SIGTERM/SIGINT
|
|
136
|
+
|
|
137
|
+
### Production Bootstrap Pattern
|
|
138
|
+
|
|
139
|
+
**Recommended production service initialization:**
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { KafkaManager } from '@autofleet/kafka';
|
|
143
|
+
import logger from './logger';
|
|
144
|
+
|
|
145
|
+
// 1. Synchronous construction
|
|
146
|
+
export const kafka = KafkaManager.create({
|
|
147
|
+
enabled: process.env.ENABLE_KAFKA === 'true',
|
|
148
|
+
logger,
|
|
149
|
+
bootstrapTimeoutMs: 15000, // 15s bootstrap timeout
|
|
150
|
+
healthCheckCacheMs: 2000, // Cache health for 2s
|
|
151
|
+
strictBootstrap: true, // Fail if ANY producer fails
|
|
152
|
+
producers: {
|
|
153
|
+
main: {
|
|
154
|
+
brokers: process.env.KAFKA_BROKERS?.split(',') || [],
|
|
155
|
+
clientId: 'my-service-main',
|
|
156
|
+
},
|
|
157
|
+
analytics: {
|
|
158
|
+
brokers: process.env.ANALYTICS_BROKERS?.split(',') || [],
|
|
159
|
+
clientId: 'my-service-analytics',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// 2. Bootstrap before serving traffic
|
|
165
|
+
async function startServer() {
|
|
166
|
+
logger.info('Bootstrapping Kafka...');
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const result = await kafka.bootstrap();
|
|
170
|
+
|
|
171
|
+
logger.info('Kafka bootstrap successful', {
|
|
172
|
+
duration: result.duration,
|
|
173
|
+
producers: Object.keys(result.results),
|
|
174
|
+
});
|
|
175
|
+
} catch (error) {
|
|
176
|
+
logger.error('Fatal: Kafka bootstrap failed', { error });
|
|
177
|
+
process.exit(1); // Fail fast in production
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 3. Start HTTP server only after Kafka is ready
|
|
181
|
+
app.listen(3000, () => {
|
|
182
|
+
logger.info('Server ready - Kafka connected');
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
startServer();
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Development Bootstrap Pattern
|
|
190
|
+
|
|
191
|
+
**For development/local environments with optional Kafka:**
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
async function startServer() {
|
|
195
|
+
// Try to bootstrap, but don't fail if Kafka unavailable
|
|
196
|
+
try {
|
|
197
|
+
await kafka.bootstrap({
|
|
198
|
+
timeoutMs: 5000, // Shorter timeout for dev
|
|
199
|
+
strict: false, // Allow partial failures
|
|
200
|
+
});
|
|
201
|
+
logger.info('Kafka connected');
|
|
202
|
+
} catch (error) {
|
|
203
|
+
logger.warn('Kafka unavailable - using lazy initialization', { error });
|
|
204
|
+
// Server still starts, producers will retry on first publish
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
app.listen(3000, () => {
|
|
208
|
+
logger.info('Server ready');
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Bootstrap Options
|
|
214
|
+
|
|
215
|
+
#### Strict Mode (Default)
|
|
216
|
+
|
|
217
|
+
Bootstrap fails if **any** producer fails to connect:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
await kafka.bootstrap(); // strict: true by default
|
|
221
|
+
|
|
222
|
+
// Throws if any producer fails
|
|
223
|
+
// Perfect for production where all clusters must be available
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### Non-Strict Mode
|
|
227
|
+
|
|
228
|
+
Bootstrap succeeds if **at least one** producer connects:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
const result = await kafka.bootstrap({
|
|
232
|
+
strict: false,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
if (!result.success) {
|
|
236
|
+
logger.warn('Some producers failed:', result.results);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Check which ones failed
|
|
240
|
+
const failed = Object.entries(result.results)
|
|
241
|
+
.filter(([_, r]) => !r.success)
|
|
242
|
+
.map(([name]) => name);
|
|
243
|
+
|
|
244
|
+
logger.warn('Failed producers:', failed);
|
|
245
|
+
// Continue - failed producers use lazy initialization
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### Selective Bootstrap
|
|
249
|
+
|
|
250
|
+
Bootstrap specific producers only:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// Only bootstrap critical producer
|
|
254
|
+
await kafka.bootstrap({
|
|
255
|
+
producers: ['main'],
|
|
256
|
+
timeoutMs: 10000,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Analytics producer will use lazy initialization
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### Custom Timeouts
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
await kafka.bootstrap({
|
|
266
|
+
timeoutMs: 20000, // 20 second timeout
|
|
267
|
+
strict: true,
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
94
271
|
## Usage
|
|
95
272
|
|
|
96
273
|
### Setup with Multiple Producers
|
|
@@ -202,30 +379,148 @@ await kafka.publish('main', 'topic', { data: 'test' });
|
|
|
202
379
|
|
|
203
380
|
### Health Checks & Readiness Probes
|
|
204
381
|
|
|
205
|
-
|
|
382
|
+
The package provides comprehensive health check APIs suitable for Kubernetes probes:
|
|
383
|
+
|
|
384
|
+
#### Readiness Probe
|
|
385
|
+
|
|
386
|
+
Checks if Kafka is ready to handle traffic (connects to brokers):
|
|
206
387
|
|
|
207
388
|
```typescript
|
|
208
389
|
import { kafka } from './kafka';
|
|
209
390
|
|
|
210
|
-
// Express/Fastify/etc
|
|
211
391
|
app.get('/health/ready', async (req, res) => {
|
|
212
392
|
const ready = await kafka.isReady();
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
393
|
+
|
|
394
|
+
if (!ready) {
|
|
395
|
+
const health = kafka.getHealth();
|
|
396
|
+
return res.status(503).json({
|
|
397
|
+
ready: false,
|
|
398
|
+
kafka: health,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
res.json({ ready: true });
|
|
217
403
|
});
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Readiness check features:**
|
|
407
|
+
- Revalidates connectivity if cache is stale
|
|
408
|
+
- Configurable cache duration (default: 1 second)
|
|
409
|
+
- Force revalidation with `isReady({ force: true })`
|
|
410
|
+
- Respects `healthCheckTimeoutMs` configuration
|
|
411
|
+
|
|
412
|
+
#### Liveness Probe
|
|
413
|
+
|
|
414
|
+
Lightweight check for process health (does NOT connect to Kafka):
|
|
218
415
|
|
|
219
|
-
|
|
220
|
-
app.get('/health/
|
|
221
|
-
const
|
|
222
|
-
|
|
416
|
+
```typescript
|
|
417
|
+
app.get('/health/live', (req, res) => {
|
|
418
|
+
const live = kafka.isLive();
|
|
419
|
+
res.status(live ? 200 : 503).json({ live });
|
|
420
|
+
});
|
|
421
|
+
```
|
|
223
422
|
|
|
224
|
-
|
|
225
|
-
|
|
423
|
+
**Liveness check:**
|
|
424
|
+
- Returns `false` if manager is in a fatal state
|
|
425
|
+
- Returns `false` if graceful shutdown has started
|
|
426
|
+
- Does NOT perform Kafka operations (very fast)
|
|
427
|
+
- Perfect for Kubernetes liveness probes
|
|
428
|
+
|
|
429
|
+
#### Detailed Health Snapshot
|
|
430
|
+
|
|
431
|
+
Get comprehensive health information for all producers:
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
app.get('/status/kafka', (req, res) => {
|
|
435
|
+
const health = kafka.getHealth();
|
|
436
|
+
|
|
437
|
+
res.json({
|
|
438
|
+
producers: health,
|
|
226
439
|
enabled: kafka.isEnabled,
|
|
440
|
+
live: kafka.isLive(),
|
|
227
441
|
});
|
|
228
442
|
});
|
|
443
|
+
|
|
444
|
+
// Example response:
|
|
445
|
+
{
|
|
446
|
+
"producers": {
|
|
447
|
+
"main": {
|
|
448
|
+
"name": "main",
|
|
449
|
+
"enabled": true,
|
|
450
|
+
"isConnected": true,
|
|
451
|
+
"lastPingAt": 1701234567890,
|
|
452
|
+
"lastPingSucceededAt": 1701234567890,
|
|
453
|
+
"lastError": null,
|
|
454
|
+
"clusterId": "prod-kafka-main-01",
|
|
455
|
+
"brokerCount": 3,
|
|
456
|
+
"brokers": ["kafka1:9092", "kafka2:9092", "kafka3:9092"]
|
|
457
|
+
},
|
|
458
|
+
"analytics": {
|
|
459
|
+
"name": "analytics",
|
|
460
|
+
"enabled": true,
|
|
461
|
+
"isConnected": false,
|
|
462
|
+
"lastPingAt": 1701234560000,
|
|
463
|
+
"lastPingSucceededAt": 1701234500000,
|
|
464
|
+
"lastError": {
|
|
465
|
+
"message": "Connection refused",
|
|
466
|
+
"timestamp": 1701234560000
|
|
467
|
+
},
|
|
468
|
+
"clusterId": null,
|
|
469
|
+
"brokerCount": 0,
|
|
470
|
+
"brokers": ["analytics-kafka:9092"]
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
"enabled": true,
|
|
474
|
+
"live": true
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
#### Connection Status
|
|
479
|
+
|
|
480
|
+
Get quick connection status summary:
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
const status = kafka.getConnectionStatus();
|
|
484
|
+
|
|
485
|
+
// Returns:
|
|
486
|
+
{
|
|
487
|
+
"main": {
|
|
488
|
+
"connected": true,
|
|
489
|
+
"lastSuccessAt": 1701234567890,
|
|
490
|
+
"lastError": null
|
|
491
|
+
},
|
|
492
|
+
"analytics": {
|
|
493
|
+
"connected": false,
|
|
494
|
+
"lastSuccessAt": 1701234500000,
|
|
495
|
+
"lastError": "Connection refused"
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
#### Kubernetes Example
|
|
501
|
+
|
|
502
|
+
```yaml
|
|
503
|
+
apiVersion: v1
|
|
504
|
+
kind: Pod
|
|
505
|
+
metadata:
|
|
506
|
+
name: my-service
|
|
507
|
+
spec:
|
|
508
|
+
containers:
|
|
509
|
+
- name: app
|
|
510
|
+
image: my-service:latest
|
|
511
|
+
livenessProbe:
|
|
512
|
+
httpGet:
|
|
513
|
+
path: /health/live
|
|
514
|
+
port: 3000
|
|
515
|
+
initialDelaySeconds: 10
|
|
516
|
+
periodSeconds: 10
|
|
517
|
+
readinessProbe:
|
|
518
|
+
httpGet:
|
|
519
|
+
path: /health/ready
|
|
520
|
+
port: 3000
|
|
521
|
+
initialDelaySeconds: 15
|
|
522
|
+
periodSeconds: 5
|
|
523
|
+
timeoutSeconds: 3
|
|
229
524
|
```
|
|
230
525
|
|
|
231
526
|
### Migration from getKafka() Pattern
|
|
@@ -303,16 +598,41 @@ async function publishEvent() {
|
|
|
303
598
|
```typescript
|
|
304
599
|
interface KafkaManagerOptions {
|
|
305
600
|
// Enable/disable Kafka - when false, returns mock implementations
|
|
601
|
+
// @default true
|
|
306
602
|
enabled?: boolean;
|
|
307
603
|
|
|
308
604
|
// Custom logger instance
|
|
309
605
|
logger?: LoggerInstanceManager;
|
|
310
606
|
|
|
311
|
-
// Skip automatic graceful shutdown
|
|
607
|
+
// Skip automatic graceful shutdown
|
|
608
|
+
// @default false
|
|
312
609
|
dontGracefulShutdown?: boolean;
|
|
313
610
|
|
|
314
|
-
// Named producers configuration
|
|
611
|
+
// Named producers configuration (required)
|
|
315
612
|
producers: Record<string, ProducerConfig>;
|
|
613
|
+
|
|
614
|
+
// ===== NEW: Health & Bootstrap Options =====
|
|
615
|
+
|
|
616
|
+
// Global timeout for health checks in ms
|
|
617
|
+
// Used by isReady() and ping operations
|
|
618
|
+
// @default 5000 (5 seconds)
|
|
619
|
+
healthCheckTimeoutMs?: number;
|
|
620
|
+
|
|
621
|
+
// How long to cache health check results in ms
|
|
622
|
+
// Set to 0 to always revalidate
|
|
623
|
+
// @default 1000 (1 second)
|
|
624
|
+
healthCheckCacheMs?: number;
|
|
625
|
+
|
|
626
|
+
// Default timeout for bootstrap operations in ms
|
|
627
|
+
// Can be overridden per bootstrap() call
|
|
628
|
+
// @default 30000 (30 seconds)
|
|
629
|
+
bootstrapTimeoutMs?: number;
|
|
630
|
+
|
|
631
|
+
// Default strict mode for bootstrap
|
|
632
|
+
// If true, all producers must succeed
|
|
633
|
+
// If false, at least one producer must succeed
|
|
634
|
+
// @default true
|
|
635
|
+
strictBootstrap?: boolean;
|
|
316
636
|
}
|
|
317
637
|
```
|
|
318
638
|
|
|
@@ -402,9 +722,11 @@ await kafka.publish('main', 'events', data, {
|
|
|
402
722
|
|
|
403
723
|
## API Reference
|
|
404
724
|
|
|
405
|
-
###
|
|
725
|
+
### Core Lifecycle Methods
|
|
406
726
|
|
|
407
|
-
|
|
727
|
+
#### `KafkaManager.create(options): KafkaManager<ProducerNames>`
|
|
728
|
+
|
|
729
|
+
Creates a new KafkaManager instance with multiple named producers. **Producer names are type-safe** - TypeScript will autocomplete and validate them based on your configuration.
|
|
408
730
|
|
|
409
731
|
**Parameters:**
|
|
410
732
|
- `options` - Configuration options (see [Configuration](#configuration))
|
|
@@ -437,7 +759,123 @@ await kafka.publish('analytics', 'metrics', { value: 1 }); // ✅ Valid
|
|
|
437
759
|
// await kafka.publish('wrong', 'topic', {}); // ❌ TypeScript error!
|
|
438
760
|
```
|
|
439
761
|
|
|
440
|
-
|
|
762
|
+
#### `bootstrap(options?): Promise<BootstrapResult>`
|
|
763
|
+
|
|
764
|
+
**NEW:** Explicitly bootstrap all (or specific) producers. Connects to brokers, validates connectivity, and returns detailed results. **Recommended before serving traffic in production.**
|
|
765
|
+
|
|
766
|
+
**Parameters:**
|
|
767
|
+
- `options.timeoutMs` (optional) - Maximum time to wait for all producers (default: `bootstrapTimeoutMs` config)
|
|
768
|
+
- `options.strict` (optional) - Fail if ANY producer fails (default: `strictBootstrap` config)
|
|
769
|
+
- `options.producers` (optional) - Array of specific producer names to bootstrap (default: all)
|
|
770
|
+
|
|
771
|
+
**Returns:** `BootstrapResult` with success status, duration, and per-producer results
|
|
772
|
+
|
|
773
|
+
**Examples:**
|
|
774
|
+
```typescript
|
|
775
|
+
// Bootstrap all producers with defaults
|
|
776
|
+
await kafka.bootstrap();
|
|
777
|
+
|
|
778
|
+
// Non-strict mode - continue if some fail
|
|
779
|
+
const result = await kafka.bootstrap({
|
|
780
|
+
timeoutMs: 10000,
|
|
781
|
+
strict: false,
|
|
782
|
+
});
|
|
783
|
+
if (!result.success) {
|
|
784
|
+
console.error('Some producers failed:', result.results);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Bootstrap specific producers only
|
|
788
|
+
await kafka.bootstrap({ producers: ['main'] });
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### Health & Monitoring Methods
|
|
792
|
+
|
|
793
|
+
#### `getHealth(): Record<ProducerNames, ProducerHealth>`
|
|
794
|
+
|
|
795
|
+
Get detailed health snapshot for all producers. Returns comprehensive metadata including connection state, timestamps, errors, and cluster info.
|
|
796
|
+
|
|
797
|
+
**Returns:** Object mapping producer names to `ProducerHealth` objects
|
|
798
|
+
|
|
799
|
+
**Example:**
|
|
800
|
+
```typescript
|
|
801
|
+
const health = kafka.getHealth();
|
|
802
|
+
console.log(health.main.clusterId); // 'prod-kafka-01'
|
|
803
|
+
console.log(health.main.lastPingSucceededAt);// 1701234567890
|
|
804
|
+
console.log(health.main.lastError); // null or error object
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
#### `getProducerHealth(name): ProducerHealth`
|
|
808
|
+
|
|
809
|
+
Get detailed health for a specific producer.
|
|
810
|
+
|
|
811
|
+
**Parameters:**
|
|
812
|
+
- `name` - Producer name (type-safe)
|
|
813
|
+
|
|
814
|
+
**Returns:** `ProducerHealth` object
|
|
815
|
+
|
|
816
|
+
**Example:**
|
|
817
|
+
```typescript
|
|
818
|
+
const health = kafka.getProducerHealth('main');
|
|
819
|
+
console.log('Cluster:', health.clusterId);
|
|
820
|
+
console.log('Brokers:', health.brokerCount);
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
#### `isReady(options?): Promise<boolean>`
|
|
824
|
+
|
|
825
|
+
Check if Kafka is ready to handle traffic. Revalidates connectivity if cache is stale. **Use for Kubernetes readiness probes.**
|
|
826
|
+
|
|
827
|
+
**Parameters:**
|
|
828
|
+
- `options.timeout` (optional) - Override default health check timeout
|
|
829
|
+
- `options.force` (optional) - Force revalidation, ignore cache
|
|
830
|
+
|
|
831
|
+
**Returns:** `true` if all producers are connected, `false` otherwise
|
|
832
|
+
|
|
833
|
+
**Examples:**
|
|
834
|
+
```typescript
|
|
835
|
+
// Use cached result if fresh
|
|
836
|
+
const ready = await kafka.isReady();
|
|
837
|
+
|
|
838
|
+
// Force immediate revalidation
|
|
839
|
+
const ready = await kafka.isReady({ force: true });
|
|
840
|
+
|
|
841
|
+
// Custom timeout
|
|
842
|
+
const ready = await kafka.isReady({ timeout: 10000 });
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
#### `isLive(): boolean`
|
|
846
|
+
|
|
847
|
+
Lightweight liveness check - does NOT perform Kafka operations. Only checks internal state for fatal errors. **Perfect for Kubernetes liveness probes.**
|
|
848
|
+
|
|
849
|
+
**Returns:** `false` if manager is in fatal state or graceful shutdown has started
|
|
850
|
+
|
|
851
|
+
**Example:**
|
|
852
|
+
```typescript
|
|
853
|
+
app.get('/health/live', (req, res) => {
|
|
854
|
+
res.status(kafka.isLive() ? 200 : 503).json({ live: kafka.isLive() });
|
|
855
|
+
});
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
#### `getConnectionStatus(): Record<ProducerNames, ConnectionStatus>`
|
|
859
|
+
|
|
860
|
+
Get connection status for all producers with timestamps and errors.
|
|
861
|
+
|
|
862
|
+
**Returns:** Object mapping producer names to connection status
|
|
863
|
+
|
|
864
|
+
**Example:**
|
|
865
|
+
```typescript
|
|
866
|
+
const status = kafka.getConnectionStatus();
|
|
867
|
+
// {
|
|
868
|
+
// main: {
|
|
869
|
+
// connected: true,
|
|
870
|
+
// lastSuccessAt: 1701234567890,
|
|
871
|
+
// lastError: null
|
|
872
|
+
// }
|
|
873
|
+
// }
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
### Publishing Methods
|
|
877
|
+
|
|
878
|
+
#### `publish<T>(producerName, topic, value, options?): Promise<RecordMetadata[]>`
|
|
441
879
|
|
|
442
880
|
Publishes a single message to a topic using a named producer. Producer name is **type-safe** - must match one of the configured producers.
|
|
443
881
|
|
|
@@ -657,6 +1095,200 @@ await kafka.publish('main', 'orders', orderData);
|
|
|
657
1095
|
await kafka.publish('analytics', 'metrics', metricsData);
|
|
658
1096
|
```
|
|
659
1097
|
|
|
1098
|
+
## Troubleshooting
|
|
1099
|
+
|
|
1100
|
+
### Common Issues and Solutions
|
|
1101
|
+
|
|
1102
|
+
#### Bootstrap Fails with Connection Timeout
|
|
1103
|
+
|
|
1104
|
+
**Problem:**
|
|
1105
|
+
```
|
|
1106
|
+
[main] Failed to connect to Kafka brokers: Connection timeout after 5000ms
|
|
1107
|
+
|
|
1108
|
+
Possible causes:
|
|
1109
|
+
1. Brokers are unreachable: kafka:9092
|
|
1110
|
+
2. Network connectivity issues
|
|
1111
|
+
3. Firewall blocking ports
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
**Solutions:**
|
|
1115
|
+
1. **Verify brokers are running:**
|
|
1116
|
+
```bash
|
|
1117
|
+
kubectl get pods -l app=kafka
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
2. **Test connectivity:**
|
|
1121
|
+
```bash
|
|
1122
|
+
nc -zv kafka-broker 9092
|
|
1123
|
+
```
|
|
1124
|
+
|
|
1125
|
+
3. **Check network policies:**
|
|
1126
|
+
- Ensure service can reach Kafka namespace
|
|
1127
|
+
- Verify firewall rules allow traffic on port 9092
|
|
1128
|
+
|
|
1129
|
+
4. **Increase timeout:**
|
|
1130
|
+
```typescript
|
|
1131
|
+
await kafka.bootstrap({ timeoutMs: 30000 }); // 30 seconds
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
#### SASL Authentication Failed
|
|
1135
|
+
|
|
1136
|
+
**Problem:**
|
|
1137
|
+
```
|
|
1138
|
+
[main] Failed to connect to Kafka brokers: SASL authentication failed
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
**Solutions:**
|
|
1142
|
+
1. **Verify credentials:**
|
|
1143
|
+
```typescript
|
|
1144
|
+
producers: {
|
|
1145
|
+
main: {
|
|
1146
|
+
brokers: ['kafka:9092'],
|
|
1147
|
+
sasl: {
|
|
1148
|
+
mechanism: 'scram-sha-256',
|
|
1149
|
+
username: process.env.KAFKA_USERNAME, // Check this
|
|
1150
|
+
password: process.env.KAFKA_PASSWORD, // And this
|
|
1151
|
+
},
|
|
1152
|
+
},
|
|
1153
|
+
}
|
|
1154
|
+
```
|
|
1155
|
+
|
|
1156
|
+
2. **Check mechanism:**
|
|
1157
|
+
- Verify broker supports the SASL mechanism
|
|
1158
|
+
- Common mechanisms: `'plain'`, `'scram-sha-256'`, `'scram-sha-512'`
|
|
1159
|
+
|
|
1160
|
+
3. **Inspect secrets:**
|
|
1161
|
+
```bash
|
|
1162
|
+
kubectl get secret kafka-credentials -o yaml
|
|
1163
|
+
```
|
|
1164
|
+
|
|
1165
|
+
#### Producer Not Found Error
|
|
1166
|
+
|
|
1167
|
+
**Problem:**
|
|
1168
|
+
```
|
|
1169
|
+
Producer 'analytics' not found. Available producers: main
|
|
1170
|
+
```
|
|
1171
|
+
|
|
1172
|
+
**Solution:**
|
|
1173
|
+
You're trying to use a producer that wasn't configured:
|
|
1174
|
+
|
|
1175
|
+
```typescript
|
|
1176
|
+
// Add the missing producer
|
|
1177
|
+
const kafka = KafkaManager.create({
|
|
1178
|
+
producers: {
|
|
1179
|
+
main: { brokers: ['kafka:9092'] },
|
|
1180
|
+
analytics: { brokers: ['kafka-analytics:9092'] }, // Add this
|
|
1181
|
+
},
|
|
1182
|
+
});
|
|
1183
|
+
```
|
|
1184
|
+
|
|
1185
|
+
#### Readiness Check Always Fails
|
|
1186
|
+
|
|
1187
|
+
**Problem:**
|
|
1188
|
+
Health checks keep failing even though Kafka is up.
|
|
1189
|
+
|
|
1190
|
+
**Solutions:**
|
|
1191
|
+
1. **Check timeout is sufficient:**
|
|
1192
|
+
```typescript
|
|
1193
|
+
const kafka = KafkaManager.create({
|
|
1194
|
+
healthCheckTimeoutMs: 5000, // Increase if needed
|
|
1195
|
+
});
|
|
1196
|
+
```
|
|
1197
|
+
|
|
1198
|
+
2. **Force revalidation:**
|
|
1199
|
+
```typescript
|
|
1200
|
+
const ready = await kafka.isReady({ force: true });
|
|
1201
|
+
```
|
|
1202
|
+
|
|
1203
|
+
3. **Inspect detailed health:**
|
|
1204
|
+
```typescript
|
|
1205
|
+
const health = kafka.getHealth();
|
|
1206
|
+
console.log(health); // Check lastError for details
|
|
1207
|
+
```
|
|
1208
|
+
|
|
1209
|
+
#### Bootstrap Succeeds but Publish Fails
|
|
1210
|
+
|
|
1211
|
+
**Problem:**
|
|
1212
|
+
Bootstrap passes, but publish throws "topic auto-create disabled".
|
|
1213
|
+
|
|
1214
|
+
**Solution:**
|
|
1215
|
+
Enable topic auto-creation or create topics manually:
|
|
1216
|
+
|
|
1217
|
+
```typescript
|
|
1218
|
+
producers: {
|
|
1219
|
+
main: {
|
|
1220
|
+
brokers: ['kafka:9092'],
|
|
1221
|
+
autoCreateTopics: true, // Enable for dev/test
|
|
1222
|
+
},
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// Or create topics manually:
|
|
1226
|
+
// kafka-topics --create --topic my-topic --bootstrap-server kafka:9092
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
#### Graceful Shutdown Not Working
|
|
1230
|
+
|
|
1231
|
+
**Problem:**
|
|
1232
|
+
Service doesn't close Kafka connections on shutdown.
|
|
1233
|
+
|
|
1234
|
+
**Solutions:**
|
|
1235
|
+
1. **Ensure graceful shutdown is enabled:**
|
|
1236
|
+
```typescript
|
|
1237
|
+
const kafka = KafkaManager.create({
|
|
1238
|
+
dontGracefulShutdown: false, // Default
|
|
1239
|
+
producers: { /* ... */ },
|
|
1240
|
+
});
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
2. **Manual disconnect if needed:**
|
|
1244
|
+
```typescript
|
|
1245
|
+
process.on('SIGTERM', async () => {
|
|
1246
|
+
await kafka.disconnect();
|
|
1247
|
+
process.exit(0);
|
|
1248
|
+
});
|
|
1249
|
+
```
|
|
1250
|
+
|
|
1251
|
+
### Debugging Tips
|
|
1252
|
+
|
|
1253
|
+
#### Enable Debug Logging
|
|
1254
|
+
|
|
1255
|
+
```typescript
|
|
1256
|
+
import { KafkaManager } from '@autofleet/kafka';
|
|
1257
|
+
import logger from './logger';
|
|
1258
|
+
|
|
1259
|
+
const kafka = KafkaManager.create({
|
|
1260
|
+
logger, // Ensure logger has debug level enabled
|
|
1261
|
+
producers: { /* ... */ },
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
// Check health frequently
|
|
1265
|
+
setInterval(() => {
|
|
1266
|
+
const health = kafka.getHealth();
|
|
1267
|
+
logger.debug('Kafka health', { health });
|
|
1268
|
+
}, 10000);
|
|
1269
|
+
```
|
|
1270
|
+
|
|
1271
|
+
#### Inspect Cluster Metadata
|
|
1272
|
+
|
|
1273
|
+
```typescript
|
|
1274
|
+
const health = kafka.getProducerHealth('main');
|
|
1275
|
+
console.log('Cluster ID:', health.clusterId);
|
|
1276
|
+
console.log('Brokers:', health.brokerCount);
|
|
1277
|
+
console.log('Last success:', new Date(health.lastPingSucceededAt));
|
|
1278
|
+
console.log('Last error:', health.lastError);
|
|
1279
|
+
```
|
|
1280
|
+
|
|
1281
|
+
#### Test Connectivity Manually
|
|
1282
|
+
|
|
1283
|
+
```typescript
|
|
1284
|
+
try {
|
|
1285
|
+
await kafka.pingProducer('main');
|
|
1286
|
+
console.log('✓ Connection successful');
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
console.error('✗ Connection failed:', error.message);
|
|
1289
|
+
}
|
|
1290
|
+
```
|
|
1291
|
+
|
|
660
1292
|
## Testing
|
|
661
1293
|
|
|
662
1294
|
Run the test suite:
|