sentinel-kafka-manager 1.0.3 → 1.0.5

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.
Files changed (2) hide show
  1. package/README.md +555 -551
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -27,6 +27,36 @@ In most services, Kafka setup gets repeated:
27
27
 
28
28
  `sentinel-kafka-manager` wraps those common tasks in one small class: `KafkaManager`.
29
29
 
30
+ ## Enterprise Features
31
+
32
+ This version now includes a stronger production baseline:
33
+
34
+ - config validation for client id, brokers, and broker discovery inputs
35
+ - shared producer, admin, and consumer lifecycle
36
+ - logger and metrics hooks
37
+ - standard message envelope publishing
38
+ - managed consumer runner with dead-letter topic support
39
+ - health snapshot reporting
40
+ - safer startup behavior for concurrent connections
41
+ - stable error codes and structured exception details
42
+
43
+ ## Recommended Usage Level
44
+
45
+ This package is now suitable as a shared internal Kafka module for SaaS microservices.
46
+
47
+ Recommended use:
48
+
49
+ - internal platform package for Node.js services
50
+ - event publishing with standard envelopes
51
+ - managed consumers with DLQ handling
52
+ - consistent service-layer success and error responses
53
+
54
+ Still recommended outside the package:
55
+
56
+ - automated unit and integration tests in the consuming service or platform repo
57
+ - schema validation for business payloads when required by your organization
58
+ - organization-specific tracing, alerting, and compliance rules
59
+
30
60
  ## Installation
31
61
 
32
62
  Install from npm:
@@ -106,281 +136,300 @@ process.on('SIGTERM', async () => {
106
136
  })
107
137
  ```
108
138
 
109
- ## Create A Kafka Server With This Repo
110
-
111
- This repository already includes a full local Kafka server setup in [docker-compose.yml](/var/www/html/matrix-project/sentinel-kafka-manager/docker-compose.yml) and sample environment values in [.env.local.example](/var/www/html/matrix-project/sentinel-kafka-manager/.env.local.example).
112
-
113
- The stack includes:
114
-
115
- - 3 KRaft controllers
116
- - 3 Kafka brokers
117
- - 1 Kafka UI instance
118
-
119
- ### 1. Create your `.env`
139
+ ## Basic Usage
120
140
 
121
- Copy the example file:
141
+ ### 1. Import the package
122
142
 
123
- ```bash
124
- cp .env.local.example .env
143
+ ```ts
144
+ import { KafkaManager } from 'sentinel-kafka-manager'
125
145
  ```
126
146
 
127
- If you want a standard localhost setup, the example values are already suitable.
128
-
129
- ### 2. Start the Kafka cluster
147
+ If you want to catch package-specific errors explicitly:
130
148
 
131
- ```bash
132
- docker compose --env-file .env up -d
149
+ ```ts
150
+ import {
151
+ KafkaErrorCode,
152
+ KafkaManager,
153
+ KafkaManagerError,
154
+ OperationResponse,
155
+ } from 'sentinel-kafka-manager'
133
156
  ```
134
157
 
135
- This creates:
136
-
137
- - controller quorum on port `9093` inside Docker
138
- - internal broker traffic on port `9092` inside Docker
139
- - host-accessible broker ports `29092`, `39092`, and `49092`
140
- - Kafka UI on `127.0.0.1:5001`
158
+ ### 2. Create a manager
141
159
 
142
- ### 3. Stop the Kafka cluster
160
+ You can create it in two ways:
143
161
 
144
- ```bash
145
- docker compose --env-file .env down
146
- ```
162
+ 1. pass brokers directly
163
+ 2. build brokers from environment variables
147
164
 
148
- To also remove persisted Kafka data volumes:
165
+ ### Direct broker example
149
166
 
150
- ```bash
151
- docker compose --env-file .env down -v
167
+ ```ts
168
+ const kafkaManager = new KafkaManager({
169
+ clientId: 'claims-service',
170
+ brokers: ['localhost:29092', 'localhost:39092', 'localhost:49092'],
171
+ })
152
172
  ```
153
173
 
154
- ### 4. Check container status
174
+ ### Environment-based example
155
175
 
156
- ```bash
157
- docker compose --env-file .env ps
176
+ ```ts
177
+ const kafkaManager = KafkaManager.fromEnv({
178
+ clientId: 'claims-service',
179
+ mode: 'external',
180
+ })
158
181
  ```
159
182
 
160
- ### 5. Current broker endpoints
183
+ ## Recommended Project Pattern
161
184
 
162
- For applications running on your host machine:
185
+ In a real service, create one shared manager instance and reuse it.
163
186
 
164
- ```text
165
- localhost:29092
166
- localhost:39092
167
- localhost:49092
168
- ```
187
+ Example:
169
188
 
170
- For applications running inside Docker on the same Compose network:
189
+ ```ts
190
+ import { KafkaManager } from 'sentinel-kafka-manager'
171
191
 
172
- ```text
173
- broker-1:9092
174
- broker-2:9092
175
- broker-3:9092
192
+ export const kafkaManager = KafkaManager.fromEnv({
193
+ clientId: 'claims-service',
194
+ mode: 'external',
195
+ })
176
196
  ```
177
197
 
178
- Important:
179
-
180
- - application clients must connect to brokers, not controllers
181
- - controllers are internal cluster metadata nodes only
182
- - `mode: 'external'` is for host-based apps
183
- - `mode: 'internal'` is for Dockerized apps on the same network
184
-
185
- ## Kafka UI Usage
186
-
187
- The Docker stack also starts Kafka UI for cluster inspection.
198
+ Then import that shared instance anywhere you need Kafka access.
188
199
 
189
- ### Open the UI
200
+ You can also attach enterprise hooks:
190
201
 
191
- Visit:
202
+ ```ts
203
+ import { KafkaManager } from 'sentinel-kafka-manager'
192
204
 
193
- ```text
194
- http://127.0.0.1:5001
205
+ export const kafkaManager = KafkaManager.fromEnv({
206
+ clientId: 'claims-service',
207
+ mode: 'external',
208
+ logger: {
209
+ debug: (message, meta) => console.debug(message, meta),
210
+ info: (message, meta) => console.info(message, meta),
211
+ warn: (message, meta) => console.warn(message, meta),
212
+ error: (message, meta) => console.error(message, meta),
213
+ },
214
+ metrics: {
215
+ emit: (event) => {
216
+ console.log('METRIC', event)
217
+ },
218
+ },
219
+ })
195
220
  ```
196
221
 
197
- ### Login credentials
222
+ ## How To Use In Your Service
198
223
 
199
- By default, the UI uses the credentials from `.env`:
224
+ ### Publish a message
200
225
 
201
- ```env
202
- KAFKA_UI_USERNAME=admin
203
- KAFKA_UI_PASSWORD=Admin@007
226
+ ```ts
227
+ await kafkaManager.publish({
228
+ topic: 'claims.created',
229
+ key: 'claim-1001',
230
+ payload: {
231
+ claimId: 'claim-1001',
232
+ status: 'created',
233
+ source: 'claims-service',
234
+ },
235
+ })
204
236
  ```
205
237
 
206
- ### What the UI connects to
238
+ `publish()` automatically:
207
239
 
208
- Kafka UI is preconfigured in `docker-compose.yml` to use all three brokers:
240
+ - gets a shared producer
241
+ - connects it once
242
+ - serializes `payload` with `JSON.stringify()`
209
243
 
210
- ```text
211
- broker-1:9092,broker-2:9092,broker-3:9092
212
- ```
244
+ ### Publish an enterprise event envelope
213
245
 
214
- The cluster name shown in the UI comes from:
246
+ Use this when you want traceable event metadata such as `eventType`, `version`, `traceId`, or `tenantId`.
215
247
 
216
- ```env
217
- KAFKA_UI_CLUSTER_NAME=Project Omni Enterprise Kafka
248
+ ```ts
249
+ await kafkaManager.publishEnvelope({
250
+ topic: 'claims.created',
251
+ key: 'claim-1001',
252
+ envelope: {
253
+ eventId: 'evt-claim-1001',
254
+ eventType: 'claims.created',
255
+ version: '1.0.0',
256
+ timestamp: new Date().toISOString(),
257
+ source: 'claims-service',
258
+ traceId: 'trace-123',
259
+ tenantId: 'tenant-abc',
260
+ correlationId: 'corr-001',
261
+ payload: {
262
+ claimId: 'claim-1001',
263
+ status: 'created',
264
+ },
265
+ },
266
+ })
218
267
  ```
219
268
 
220
- ### What you can do in the UI
221
-
222
- - view brokers and cluster health
223
- - inspect topics and partitions
224
- - browse messages
225
- - inspect consumer groups
226
- - check offsets and lag
269
+ ### Create or reuse a producer manually
227
270
 
228
- ### Read-only mode
271
+ If you need direct producer access:
229
272
 
230
- The example `.env` sets:
273
+ ```ts
274
+ const producer = await kafkaManager.getProducer()
231
275
 
232
- ```env
233
- KAFKA_UI_READONLY=true
276
+ await producer.send({
277
+ topic: 'claims.created',
278
+ messages: [
279
+ {
280
+ key: 'claim-1001',
281
+ value: JSON.stringify({ claimId: 'claim-1001' }),
282
+ },
283
+ ],
284
+ })
234
285
  ```
235
286
 
236
- That means the UI is intended for safe inspection only.
287
+ ### Create or reuse a consumer
237
288
 
238
- If you want to create topics or make changes from the UI, change it to:
289
+ ```ts
290
+ const consumer = await kafkaManager.getConsumer('claims-service-group')
239
291
 
240
- ```env
241
- KAFKA_UI_READONLY=false
242
- ```
292
+ await consumer.subscribe({
293
+ topic: 'claims.created',
294
+ fromBeginning: false,
295
+ })
243
296
 
244
- Then restart the stack:
297
+ await consumer.run({
298
+ eachMessage: async ({ topic, partition, message }) => {
299
+ const rawValue = message.value?.toString() ?? '{}'
300
+ const payload = JSON.parse(rawValue)
245
301
 
246
- ```bash
247
- docker compose --env-file .env up -d
302
+ console.log({
303
+ topic,
304
+ partition,
305
+ key: message.key?.toString(),
306
+ payload,
307
+ })
308
+ },
309
+ })
248
310
  ```
249
311
 
250
- ### Typical UI flow
312
+ `getConsumer(groupId)` returns one shared connected consumer per group id.
251
313
 
252
- 1. Start the Docker stack.
253
- 2. Open `http://127.0.0.1:5001`.
254
- 3. Log in with `KAFKA_UI_USERNAME` and `KAFKA_UI_PASSWORD`.
255
- 4. Open the configured cluster.
256
- 5. Go to Topics, Consumer Groups, or Brokers as needed.
314
+ ### Run a managed consumer with DLQ support
257
315
 
258
- ## Docker Compose And `.env` Reference
316
+ For SaaS-style services, this is the recommended consumer pattern.
259
317
 
260
- This project does not use a separate `Dockerfile` for Kafka. The Kafka server is created from `docker-compose.yml` and environment values from `.env`.
261
-
262
- ### Core Kafka image and cluster identity
263
-
264
- ```env
265
- KAFKA_IMAGE=confluentinc/cp-kafka:7.6.6
266
- KAFKA_CLUSTER_ID=MkU3OEVBNTcwNTJENDM2Qk
267
- KAFKA_RESTART_POLICY=unless-stopped
268
- KAFKA_STOP_GRACE_PERIOD=60s
269
- KAFKA_NETWORK_NAME=kafka-network
318
+ ```ts
319
+ await kafkaManager.runConsumer({
320
+ groupId: 'claims-service-group',
321
+ topic: 'claims.created',
322
+ dlqTopic: 'claims.created.dlq',
323
+ onMessage: async ({ message, headers, key }) => {
324
+ console.log('Processing:', {
325
+ key,
326
+ traceId: headers['x-trace-id'],
327
+ payload: message,
328
+ })
329
+ },
330
+ onError: async (error, context) => {
331
+ console.error('Consumer error:', {
332
+ error: error.message,
333
+ topic: context.topic,
334
+ offset: context.offset,
335
+ })
336
+ },
337
+ })
270
338
  ```
271
339
 
272
- - `KAFKA_IMAGE`: Kafka container image used by controllers and brokers
273
- - `KAFKA_CLUSTER_ID`: shared KRaft cluster id for all nodes
274
- - `KAFKA_NETWORK_NAME`: Docker network name used by the full stack
275
-
276
- ### Controller quorum settings
340
+ If processing fails:
277
341
 
278
- ```env
279
- KAFKA_CONTROLLER_PORT=9093
280
- KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER
281
- KAFKA_CONTROLLER_QUORUM_VOTERS=1@controller-1:9093,2@controller-2:9093,3@controller-3:9093
282
- ```
342
+ - the error is logged
343
+ - a metric hook can receive the failure event
344
+ - the message can be published to a dead-letter topic when `dlqTopic` is set
283
345
 
284
- - controllers run only inside Docker
285
- - apps should never use these controller addresses as Kafka client brokers
346
+ ### Catch structured errors
286
347
 
287
- ### Broker listener settings
348
+ All package-thrown operational errors now use `KafkaManagerError`.
288
349
 
289
- ```env
290
- KAFKA_BROKER_BIND_ADDRESS=0.0.0.0
291
- KAFKA_EXTERNAL_HOST=localhost
292
- KAFKA_BROKER_INTERNAL_PORT=9092
293
- KAFKA_BROKER_EXTERNAL_CONTAINER_PORT=19092
294
- KAFKA_BROKER_1_EXTERNAL_PORT=29092
295
- KAFKA_BROKER_2_EXTERNAL_PORT=39092
296
- KAFKA_BROKER_3_EXTERNAL_PORT=49092
297
- KAFKA_INTER_BROKER_LISTENER_NAME=INTERNAL
298
- KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
299
- ```
350
+ ```ts
351
+ import {
352
+ KafkaErrorCode,
353
+ KafkaManagerError,
354
+ } from 'sentinel-kafka-manager'
300
355
 
301
- - `KAFKA_EXTERNAL_HOST=localhost` exposes brokers to your host machine
302
- - `KAFKA_BROKER_1_EXTERNAL_PORT`, `KAFKA_BROKER_2_EXTERNAL_PORT`, and `KAFKA_BROKER_3_EXTERNAL_PORT` are the ports your local apps should use
303
- - `KAFKA_BROKER_INTERNAL_PORT=9092` is used by containers on the Docker network
356
+ try {
357
+ await kafkaManager.publish({
358
+ topic: 'claims.created',
359
+ payload: { claimId: 'claim-1001' },
360
+ })
361
+ } catch (error) {
362
+ if (error instanceof KafkaManagerError) {
363
+ console.error('Kafka error', {
364
+ code: error.code,
365
+ message: error.message,
366
+ details: error.details,
367
+ })
304
368
 
305
- ### Replication and durability defaults
369
+ if (error.code === KafkaErrorCode.MESSAGE_PUBLISH_FAILED) {
370
+ // retry, alert, or degrade gracefully
371
+ }
372
+ }
306
373
 
307
- ```env
308
- KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=3
309
- KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=3
310
- KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=2
311
- KAFKA_DEFAULT_REPLICATION_FACTOR=3
312
- KAFKA_MIN_INSYNC_REPLICAS=2
313
- KAFKA_NUM_PARTITIONS=6
374
+ throw error
375
+ }
314
376
  ```
315
377
 
316
- These values are designed for the included three-broker cluster.
317
-
318
- ### Broker behavior
319
-
320
- ```env
321
- KAFKA_AUTO_CREATE_TOPICS_ENABLE=false
322
- KAFKA_DELETE_TOPIC_ENABLE=true
323
- KAFKA_LOG_RETENTION_HOURS=168
324
- KAFKA_LOG_SEGMENT_BYTES=1073741824
325
- KAFKA_MESSAGE_MAX_BYTES=10485880
326
- KAFKA_REPLICA_FETCH_MAX_BYTES=10485880
327
- KAFKA_SOCKET_REQUEST_MAX_BYTES=104857600
328
- ```
378
+ ### Use common success and error response types
329
379
 
330
- - topics are not auto-created by default
331
- - deleting topics is allowed
332
- - retention is 7 days by default
380
+ If your service wraps Kafka operations in API or service-layer responses, you can use the shared response contracts from this package.
333
381
 
334
- ### Storage and safety limits
382
+ ```ts
383
+ import {
384
+ ErrorResponse,
385
+ OperationResponse,
386
+ SuccessResponse,
387
+ } from 'sentinel-kafka-manager'
335
388
 
336
- ```env
337
- KAFKA_LOG_DIRS=/var/lib/kafka/data
338
- KAFKA_ULIMIT_NOFILE_SOFT=65536
339
- KAFKA_ULIMIT_NOFILE_HARD=65536
340
- KAFKA_LOG_MAX_SIZE=100m
341
- KAFKA_LOG_MAX_FILE=5
389
+ type PublishClaimCreatedResponse = OperationResponse<{
390
+ topic: string
391
+ key: string
392
+ }>
342
393
  ```
343
394
 
344
- ### Kafka UI settings
395
+ Success example:
345
396
 
346
- ```env
347
- KAFKA_UI_IMAGE=provectuslabs/kafka-ui:latest
348
- KAFKA_UI_CONTAINER_NAME=kafka-ui
349
- KAFKA_UI_HOSTNAME=kafka-ui
350
- KAFKA_UI_RESTART_POLICY=unless-stopped
351
- KAFKA_UI_BIND_ADDRESS=127.0.0.1
352
- KAFKA_UI_PORT=5001
353
- KAFKA_UI_DYNAMIC_CONFIG_ENABLED=false
354
- KAFKA_UI_CLUSTER_NAME=Project Omni Enterprise Kafka
355
- KAFKA_UI_READONLY=true
356
- KAFKA_UI_AUTH_TYPE=LOGIN_FORM
357
- KAFKA_UI_USERNAME=admin
358
- KAFKA_UI_PASSWORD=Admin@007
397
+ ```ts
398
+ const response: SuccessResponse<{ topic: string; key: string }> = {
399
+ success: true,
400
+ message: 'Kafka message published successfully.',
401
+ data: {
402
+ topic: 'claims.created',
403
+ key: 'claim-1001',
404
+ },
405
+ meta: {
406
+ timestamp: new Date().toISOString(),
407
+ traceId: 'trace-123',
408
+ },
409
+ }
359
410
  ```
360
411
 
361
- - `KAFKA_UI_BIND_ADDRESS=127.0.0.1` keeps the UI local-only
362
- - `KAFKA_UI_PORT=5001` publishes the UI on your machine
363
- - `KAFKA_UI_READONLY=true` prevents write actions from the UI
364
-
365
- ### Healthcheck settings
412
+ Error example:
366
413
 
367
- ```env
368
- KAFKA_HEALTHCHECK_INTERVAL=10s
369
- KAFKA_HEALTHCHECK_TIMEOUT=5s
370
- KAFKA_HEALTHCHECK_RETRIES=15
371
- KAFKA_HEALTHCHECK_START_PERIOD=30s
414
+ ```ts
415
+ const response: ErrorResponse = {
416
+ success: false,
417
+ message: 'Failed to publish Kafka message.',
418
+ error: {
419
+ code: KafkaErrorCode.MESSAGE_PUBLISH_FAILED,
420
+ details: {
421
+ topic: 'claims.created',
422
+ key: 'claim-1001',
423
+ },
424
+ },
425
+ meta: {
426
+ timestamp: new Date().toISOString(),
427
+ retryable: true,
428
+ },
429
+ }
372
430
  ```
373
431
 
374
- ## Creating Topics
375
-
376
- Because `KAFKA_AUTO_CREATE_TOPICS_ENABLE=false`, topics should be created deliberately.
377
-
378
- You have two good options:
379
-
380
- - create topics from your service with `kafkaManager.provisionTopics()`
381
- - allow UI-based creation by setting `KAFKA_UI_READONLY=false`
382
-
383
- Example with this package:
432
+ ### Provision topics
384
433
 
385
434
  ```ts
386
435
  await kafkaManager.provisionTopics([
@@ -389,505 +438,458 @@ await kafkaManager.provisionTopics([
389
438
  numPartitions: 6,
390
439
  replicationFactor: 3,
391
440
  },
441
+ {
442
+ topic: 'claims.updated',
443
+ numPartitions: 6,
444
+ replicationFactor: 3,
445
+ },
392
446
  ])
393
447
  ```
394
448
 
395
- ## Enterprise Features
449
+ This is useful during service startup when your topics should exist before producers or consumers begin work.
396
450
 
397
- This version now includes a stronger production baseline:
451
+ ### Disconnect on shutdown
398
452
 
399
- - config validation for client id, brokers, and broker discovery inputs
400
- - shared producer, admin, and consumer lifecycle
401
- - logger and metrics hooks
402
- - standard message envelope publishing
403
- - managed consumer runner with dead-letter topic support
404
- - health snapshot reporting
405
- - safer startup behavior for concurrent connections
406
- - stable error codes and structured exception details
453
+ Always close Kafka clients during application shutdown.
407
454
 
408
- ## Recommended Usage Level
455
+ ```ts
456
+ process.on('SIGINT', async () => {
457
+ await kafkaManager.disconnect()
458
+ process.exit(0)
459
+ })
409
460
 
410
- This package is now suitable as a shared internal Kafka module for SaaS microservices.
461
+ process.on('SIGTERM', async () => {
462
+ await kafkaManager.disconnect()
463
+ process.exit(0)
464
+ })
465
+ ```
411
466
 
412
- Recommended use:
467
+ ### Inspect health
413
468
 
414
- - internal platform package for Node.js services
415
- - event publishing with standard envelopes
416
- - managed consumers with DLQ handling
417
- - consistent service-layer success and error responses
469
+ ```ts
470
+ const health = kafkaManager.getHealthSnapshot()
418
471
 
419
- Still recommended outside the package:
472
+ console.log(health)
473
+ ```
420
474
 
421
- - automated unit and integration tests in the consuming service or platform repo
422
- - schema validation for business payloads when required by your organization
423
- - organization-specific tracing, alerting, and compliance rules
475
+ ## Environment Setup
424
476
 
425
- ## Basic Usage
477
+ This package supports two environment styles:
426
478
 
427
- ### 1. Import the package
479
+ 1. one explicit broker list with `KAFKA_BROKERS`
480
+ 2. derived brokers from your existing Kafka runtime variables
428
481
 
429
- ```ts
430
- import { KafkaManager } from 'sentinel-kafka-manager'
482
+ ### Option 1: Use `KAFKA_BROKERS`
483
+
484
+ This is the simplest option.
485
+
486
+ ### `.env`
487
+
488
+ ```env
489
+ KAFKA_BROKERS=localhost:29092,localhost:39092,localhost:49092
431
490
  ```
432
491
 
433
- If you want to catch package-specific errors explicitly:
492
+ ### Code
434
493
 
435
494
  ```ts
436
- import {
437
- KafkaErrorCode,
438
- KafkaManager,
439
- KafkaManagerError,
440
- OperationResponse,
441
- } from 'sentinel-kafka-manager'
495
+ const kafkaManager = KafkaManager.fromEnv({
496
+ clientId: 'claims-service',
497
+ })
442
498
  ```
443
499
 
444
- ### 2. Create a manager
500
+ ### Option 2: Use your current Kafka cluster variables
445
501
 
446
- You can create it in two ways:
502
+ This matches the Kafka server setup you shared.
447
503
 
448
- 1. pass brokers directly
449
- 2. build brokers from environment variables
504
+ ### For host machine apps
450
505
 
451
- ### Direct broker example
506
+ Use this when your Node.js service runs on the host machine.
507
+
508
+ ### `.env`
509
+
510
+ ```env
511
+ KAFKA_EXTERNAL_HOST=localhost
512
+ KAFKA_BROKER_1_EXTERNAL_PORT=29092
513
+ KAFKA_BROKER_2_EXTERNAL_PORT=39092
514
+ KAFKA_BROKER_3_EXTERNAL_PORT=49092
515
+ KAFKA_BROKER_INTERNAL_PORT=9092
516
+ ```
517
+
518
+ ### Code
452
519
 
453
520
  ```ts
454
- const kafkaManager = new KafkaManager({
521
+ const kafkaManager = KafkaManager.fromEnv({
455
522
  clientId: 'claims-service',
456
- brokers: ['localhost:29092', 'localhost:39092', 'localhost:49092'],
523
+ mode: 'external',
457
524
  })
458
525
  ```
459
526
 
460
- ### Environment-based example
527
+ This resolves to:
528
+
529
+ ```text
530
+ localhost:29092
531
+ localhost:39092
532
+ localhost:49092
533
+ ```
534
+
535
+ ### For Dockerized apps on the Kafka network
536
+
537
+ Use this when your Node.js service runs inside Docker on the same Kafka network.
538
+
539
+ ### `.env`
540
+
541
+ ```env
542
+ KAFKA_BROKER_INTERNAL_PORT=9092
543
+ ```
544
+
545
+ ### Code
461
546
 
462
547
  ```ts
463
548
  const kafkaManager = KafkaManager.fromEnv({
464
549
  clientId: 'claims-service',
465
- mode: 'external',
550
+ mode: 'internal',
466
551
  })
467
552
  ```
468
553
 
469
- ## Recommended Project Pattern
554
+ This resolves to:
555
+
556
+ ```text
557
+ broker-1:9092
558
+ broker-2:9092
559
+ broker-3:9092
560
+ ```
561
+
562
+ ### Which Mode Should You Use?
470
563
 
471
- In a real service, create one shared manager instance and reuse it.
564
+ - Use `mode: 'external'` when your service uses `localhost` broker ports like `29092`, `39092`, and `49092`
565
+ - Use `mode: 'internal'` when your service is inside Docker and should reach brokers like `broker-1:9092`
566
+ - Use `KAFKA_BROKERS` when you want the simplest and most explicit configuration
472
567
 
473
- Example:
568
+ ### Your Current Running Kafka Cluster
474
569
 
475
- ```ts
476
- import { KafkaManager } from 'sentinel-kafka-manager'
570
+ Based on your running Docker containers, your Kafka cluster is exposed like this:
477
571
 
478
- export const kafkaManager = KafkaManager.fromEnv({
479
- clientId: 'claims-service',
480
- mode: 'external',
481
- })
482
- ```
572
+ - `broker-1` -> host port `29092`
573
+ - `broker-2` -> host port `39092`
574
+ - `broker-3` -> host port `49092`
575
+ - `controller-1`, `controller-2`, and `controller-3` are controller-only nodes and should not be used by application clients
483
576
 
484
- Then import that shared instance anywhere you need Kafka access.
577
+ If your Node.js service runs on the host machine, use:
485
578
 
486
- You can also attach enterprise hooks:
579
+ ```env
580
+ KAFKA_EXTERNAL_HOST=localhost
581
+ KAFKA_BROKER_1_EXTERNAL_PORT=29092
582
+ KAFKA_BROKER_2_EXTERNAL_PORT=39092
583
+ KAFKA_BROKER_3_EXTERNAL_PORT=49092
584
+ KAFKA_BROKER_INTERNAL_PORT=9092
585
+ ```
487
586
 
488
587
  ```ts
489
- import { KafkaManager } from 'sentinel-kafka-manager'
490
-
491
- export const kafkaManager = KafkaManager.fromEnv({
588
+ const kafkaManager = KafkaManager.fromEnv({
492
589
  clientId: 'claims-service',
493
590
  mode: 'external',
494
- logger: {
495
- debug: (message, meta) => console.debug(message, meta),
496
- info: (message, meta) => console.info(message, meta),
497
- warn: (message, meta) => console.warn(message, meta),
498
- error: (message, meta) => console.error(message, meta),
499
- },
500
- metrics: {
501
- emit: (event) => {
502
- console.log('METRIC', event)
503
- },
504
- },
505
591
  })
506
592
  ```
507
593
 
508
- ## How To Use In Your Service
509
-
510
- ### Publish a message
594
+ If your Node.js service runs inside Docker on the same Kafka network, use:
511
595
 
512
596
  ```ts
513
- await kafkaManager.publish({
514
- topic: 'claims.created',
515
- key: 'claim-1001',
516
- payload: {
517
- claimId: 'claim-1001',
518
- status: 'created',
519
- source: 'claims-service',
520
- },
597
+ const kafkaManager = KafkaManager.fromEnv({
598
+ clientId: 'claims-service',
599
+ mode: 'internal',
521
600
  })
522
601
  ```
523
602
 
524
- `publish()` automatically:
603
+ This package is designed to work with that exact broker layout.
525
604
 
526
- - gets a shared producer
527
- - connects it once
528
- - serializes `payload` with `JSON.stringify()`
605
+ ## Local Infrastructure And Operations
529
606
 
530
- ### Publish an enterprise event envelope
607
+ ### Create A Kafka Server With This Repo
531
608
 
532
- Use this when you want traceable event metadata such as `eventType`, `version`, `traceId`, or `tenantId`.
609
+ This repository already includes a full local Kafka server setup in [docker-compose.yml](/var/www/html/matrix-project/sentinel-kafka-manager/docker-compose.yml) and sample environment values in [.env.local.example](/var/www/html/matrix-project/sentinel-kafka-manager/.env.local.example).
533
610
 
534
- ```ts
535
- await kafkaManager.publishEnvelope({
536
- topic: 'claims.created',
537
- key: 'claim-1001',
538
- envelope: {
539
- eventId: 'evt-claim-1001',
540
- eventType: 'claims.created',
541
- version: '1.0.0',
542
- timestamp: new Date().toISOString(),
543
- source: 'claims-service',
544
- traceId: 'trace-123',
545
- tenantId: 'tenant-abc',
546
- correlationId: 'corr-001',
547
- payload: {
548
- claimId: 'claim-1001',
549
- status: 'created',
550
- },
551
- },
552
- })
553
- ```
611
+ The stack includes:
554
612
 
555
- ### Create or reuse a producer manually
613
+ - 3 KRaft controllers
614
+ - 3 Kafka brokers
615
+ - 1 Kafka UI instance
556
616
 
557
- If you need direct producer access:
617
+ ### 1. Create your `.env`
558
618
 
559
- ```ts
560
- const producer = await kafkaManager.getProducer()
619
+ Copy the example file:
561
620
 
562
- await producer.send({
563
- topic: 'claims.created',
564
- messages: [
565
- {
566
- key: 'claim-1001',
567
- value: JSON.stringify({ claimId: 'claim-1001' }),
568
- },
569
- ],
570
- })
621
+ ```bash
622
+ cp .env.local.example .env
571
623
  ```
572
624
 
573
- ### Create or reuse a consumer
625
+ If you want a standard localhost setup, the example values are already suitable.
574
626
 
575
- ```ts
576
- const consumer = await kafkaManager.getConsumer('claims-service-group')
627
+ ### 2. Start the Kafka cluster
577
628
 
578
- await consumer.subscribe({
579
- topic: 'claims.created',
580
- fromBeginning: false,
581
- })
629
+ ```bash
630
+ docker compose --env-file .env up -d
631
+ ```
582
632
 
583
- await consumer.run({
584
- eachMessage: async ({ topic, partition, message }) => {
585
- const rawValue = message.value?.toString() ?? '{}'
586
- const payload = JSON.parse(rawValue)
633
+ This creates:
587
634
 
588
- console.log({
589
- topic,
590
- partition,
591
- key: message.key?.toString(),
592
- payload,
593
- })
594
- },
595
- })
635
+ - controller quorum on port `9093` inside Docker
636
+ - internal broker traffic on port `9092` inside Docker
637
+ - host-accessible broker ports `29092`, `39092`, and `49092`
638
+ - Kafka UI on `127.0.0.1:5001`
639
+
640
+ ### 3. Stop the Kafka cluster
641
+
642
+ ```bash
643
+ docker compose --env-file .env down
596
644
  ```
597
645
 
598
- `getConsumer(groupId)` returns one shared connected consumer per group id.
646
+ To also remove persisted Kafka data volumes:
599
647
 
600
- ### Run a managed consumer with DLQ support
648
+ ```bash
649
+ docker compose --env-file .env down -v
650
+ ```
601
651
 
602
- For SaaS-style services, this is the recommended consumer pattern.
652
+ ### 4. Check container status
603
653
 
604
- ```ts
605
- await kafkaManager.runConsumer({
606
- groupId: 'claims-service-group',
607
- topic: 'claims.created',
608
- dlqTopic: 'claims.created.dlq',
609
- onMessage: async ({ message, headers, key }) => {
610
- console.log('Processing:', {
611
- key,
612
- traceId: headers['x-trace-id'],
613
- payload: message,
614
- })
615
- },
616
- onError: async (error, context) => {
617
- console.error('Consumer error:', {
618
- error: error.message,
619
- topic: context.topic,
620
- offset: context.offset,
621
- })
622
- },
623
- })
654
+ ```bash
655
+ docker compose --env-file .env ps
624
656
  ```
625
657
 
626
- If processing fails:
658
+ ### 5. Current broker endpoints
627
659
 
628
- - the error is logged
629
- - a metric hook can receive the failure event
630
- - the message can be published to a dead-letter topic when `dlqTopic` is set
660
+ For applications running on your host machine:
631
661
 
632
- ### Catch structured errors
662
+ ```text
663
+ localhost:29092
664
+ localhost:39092
665
+ localhost:49092
666
+ ```
633
667
 
634
- All package-thrown operational errors now use `KafkaManagerError`.
668
+ For applications running inside Docker on the same Compose network:
635
669
 
636
- ```ts
637
- import {
638
- KafkaErrorCode,
639
- KafkaManagerError,
640
- } from 'sentinel-kafka-manager'
670
+ ```text
671
+ broker-1:9092
672
+ broker-2:9092
673
+ broker-3:9092
674
+ ```
641
675
 
642
- try {
643
- await kafkaManager.publish({
644
- topic: 'claims.created',
645
- payload: { claimId: 'claim-1001' },
646
- })
647
- } catch (error) {
648
- if (error instanceof KafkaManagerError) {
649
- console.error('Kafka error', {
650
- code: error.code,
651
- message: error.message,
652
- details: error.details,
653
- })
676
+ Important:
654
677
 
655
- if (error.code === KafkaErrorCode.MESSAGE_PUBLISH_FAILED) {
656
- // retry, alert, or degrade gracefully
657
- }
658
- }
678
+ - application clients must connect to brokers, not controllers
679
+ - controllers are internal cluster metadata nodes only
680
+ - `mode: 'external'` is for host-based apps
681
+ - `mode: 'internal'` is for Dockerized apps on the same network
659
682
 
660
- throw error
661
- }
662
- ```
683
+ ### Kafka UI Usage
663
684
 
664
- ### Use common success and error response types
685
+ The Docker stack also starts Kafka UI for cluster inspection.
665
686
 
666
- If your service wraps Kafka operations in API or service-layer responses, you can use the shared response contracts from this package.
687
+ ### Open the UI
667
688
 
668
- ```ts
669
- import {
670
- ErrorResponse,
671
- OperationResponse,
672
- SuccessResponse,
673
- } from 'sentinel-kafka-manager'
689
+ Visit:
674
690
 
675
- type PublishClaimCreatedResponse = OperationResponse<{
676
- topic: string
677
- key: string
678
- }>
691
+ ```text
692
+ http://127.0.0.1:5001
679
693
  ```
680
694
 
681
- Success example:
695
+ ### Login credentials
682
696
 
683
- ```ts
684
- const response: SuccessResponse<{ topic: string; key: string }> = {
685
- success: true,
686
- message: 'Kafka message published successfully.',
687
- data: {
688
- topic: 'claims.created',
689
- key: 'claim-1001',
690
- },
691
- meta: {
692
- timestamp: new Date().toISOString(),
693
- traceId: 'trace-123',
694
- },
695
- }
697
+ By default, the UI uses the credentials from `.env`:
698
+
699
+ ```env
700
+ KAFKA_UI_USERNAME=admin
701
+ KAFKA_UI_PASSWORD=Admin@007
696
702
  ```
697
703
 
698
- Error example:
704
+ ### What the UI connects to
699
705
 
700
- ```ts
701
- const response: ErrorResponse = {
702
- success: false,
703
- message: 'Failed to publish Kafka message.',
704
- error: {
705
- code: KafkaErrorCode.MESSAGE_PUBLISH_FAILED,
706
- details: {
707
- topic: 'claims.created',
708
- key: 'claim-1001',
709
- },
710
- },
711
- meta: {
712
- timestamp: new Date().toISOString(),
713
- retryable: true,
714
- },
715
- }
706
+ Kafka UI is preconfigured in `docker-compose.yml` to use all three brokers:
707
+
708
+ ```text
709
+ broker-1:9092,broker-2:9092,broker-3:9092
716
710
  ```
717
711
 
718
- ### Provision topics
712
+ The cluster name shown in the UI comes from:
719
713
 
720
- ```ts
721
- await kafkaManager.provisionTopics([
722
- {
723
- topic: 'claims.created',
724
- numPartitions: 6,
725
- replicationFactor: 3,
726
- },
727
- {
728
- topic: 'claims.updated',
729
- numPartitions: 6,
730
- replicationFactor: 3,
731
- },
732
- ])
714
+ ```env
715
+ KAFKA_UI_CLUSTER_NAME=Project Omni Enterprise Kafka
733
716
  ```
734
717
 
735
- This is useful during service startup when your topics should exist before producers or consumers begin work.
718
+ ### What you can do in the UI
736
719
 
737
- ### Disconnect on shutdown
720
+ - view brokers and cluster health
721
+ - inspect topics and partitions
722
+ - browse messages
723
+ - inspect consumer groups
724
+ - check offsets and lag
738
725
 
739
- Always close Kafka clients during application shutdown.
726
+ ### Read-only mode
740
727
 
741
- ```ts
742
- process.on('SIGINT', async () => {
743
- await kafkaManager.disconnect()
744
- process.exit(0)
745
- })
728
+ The example `.env` sets:
746
729
 
747
- process.on('SIGTERM', async () => {
748
- await kafkaManager.disconnect()
749
- process.exit(0)
750
- })
730
+ ```env
731
+ KAFKA_UI_READONLY=true
751
732
  ```
752
733
 
753
- ### Inspect health
734
+ That means the UI is intended for safe inspection only.
754
735
 
755
- ```ts
756
- const health = kafkaManager.getHealthSnapshot()
736
+ If you want to create topics or make changes from the UI, change it to:
757
737
 
758
- console.log(health)
738
+ ```env
739
+ KAFKA_UI_READONLY=false
759
740
  ```
760
741
 
761
- ## Environment Setup
762
-
763
- This package supports two environment styles:
742
+ Then restart the stack:
764
743
 
765
- 1. one explicit broker list with `KAFKA_BROKERS`
766
- 2. derived brokers from your existing Kafka runtime variables
744
+ ```bash
745
+ docker compose --env-file .env up -d
746
+ ```
767
747
 
768
- ## Option 1: Use `KAFKA_BROKERS`
748
+ ### Typical UI flow
769
749
 
770
- This is the simplest option.
750
+ 1. Start the Docker stack.
751
+ 2. Open `http://127.0.0.1:5001`.
752
+ 3. Log in with `KAFKA_UI_USERNAME` and `KAFKA_UI_PASSWORD`.
753
+ 4. Open the configured cluster.
754
+ 5. Go to Topics, Consumer Groups, or Brokers as needed.
771
755
 
772
- ### `.env`
756
+ ### Docker Compose And `.env` Reference
773
757
 
774
- ```env
775
- KAFKA_BROKERS=localhost:29092,localhost:39092,localhost:49092
776
- ```
758
+ This project does not use a separate `Dockerfile` for Kafka. The Kafka server is created from `docker-compose.yml` and environment values from `.env`.
777
759
 
778
- ### Code
760
+ ### Core Kafka image and cluster identity
779
761
 
780
- ```ts
781
- const kafkaManager = KafkaManager.fromEnv({
782
- clientId: 'claims-service',
783
- })
762
+ ```env
763
+ KAFKA_IMAGE=confluentinc/cp-kafka:7.6.6
764
+ KAFKA_CLUSTER_ID=MkU3OEVBNTcwNTJENDM2Qk
765
+ KAFKA_RESTART_POLICY=unless-stopped
766
+ KAFKA_STOP_GRACE_PERIOD=60s
767
+ KAFKA_NETWORK_NAME=kafka-network
784
768
  ```
785
769
 
786
- ## Option 2: Use your current Kafka cluster variables
770
+ - `KAFKA_IMAGE`: Kafka container image used by controllers and brokers
771
+ - `KAFKA_CLUSTER_ID`: shared KRaft cluster id for all nodes
772
+ - `KAFKA_NETWORK_NAME`: Docker network name used by the full stack
787
773
 
788
- This matches the Kafka server setup you shared.
774
+ ### Controller quorum settings
789
775
 
790
- ### For host machine apps
776
+ ```env
777
+ KAFKA_CONTROLLER_PORT=9093
778
+ KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER
779
+ KAFKA_CONTROLLER_QUORUM_VOTERS=1@controller-1:9093,2@controller-2:9093,3@controller-3:9093
780
+ ```
791
781
 
792
- Use this when your Node.js service runs on the host machine.
782
+ - controllers run only inside Docker
783
+ - apps should never use these controller addresses as Kafka client brokers
793
784
 
794
- ### `.env`
785
+ ### Broker listener settings
795
786
 
796
787
  ```env
788
+ KAFKA_BROKER_BIND_ADDRESS=0.0.0.0
797
789
  KAFKA_EXTERNAL_HOST=localhost
790
+ KAFKA_BROKER_INTERNAL_PORT=9092
791
+ KAFKA_BROKER_EXTERNAL_CONTAINER_PORT=19092
798
792
  KAFKA_BROKER_1_EXTERNAL_PORT=29092
799
793
  KAFKA_BROKER_2_EXTERNAL_PORT=39092
800
794
  KAFKA_BROKER_3_EXTERNAL_PORT=49092
801
- KAFKA_BROKER_INTERNAL_PORT=9092
795
+ KAFKA_INTER_BROKER_LISTENER_NAME=INTERNAL
796
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
802
797
  ```
803
798
 
804
- ### Code
805
-
806
- ```ts
807
- const kafkaManager = KafkaManager.fromEnv({
808
- clientId: 'claims-service',
809
- mode: 'external',
810
- })
811
- ```
799
+ - `KAFKA_EXTERNAL_HOST=localhost` exposes brokers to your host machine
800
+ - `KAFKA_BROKER_1_EXTERNAL_PORT`, `KAFKA_BROKER_2_EXTERNAL_PORT`, and `KAFKA_BROKER_3_EXTERNAL_PORT` are the ports your local apps should use
801
+ - `KAFKA_BROKER_INTERNAL_PORT=9092` is used by containers on the Docker network
812
802
 
813
- This resolves to:
803
+ ### Replication and durability defaults
814
804
 
815
- ```text
816
- localhost:29092
817
- localhost:39092
818
- localhost:49092
805
+ ```env
806
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=3
807
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=3
808
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=2
809
+ KAFKA_DEFAULT_REPLICATION_FACTOR=3
810
+ KAFKA_MIN_INSYNC_REPLICAS=2
811
+ KAFKA_NUM_PARTITIONS=6
819
812
  ```
820
813
 
821
- ### For Dockerized apps on the Kafka network
822
-
823
- Use this when your Node.js service runs inside Docker on the same Kafka network.
814
+ These values are designed for the included three-broker cluster.
824
815
 
825
- ### `.env`
816
+ ### Broker behavior
826
817
 
827
818
  ```env
828
- KAFKA_BROKER_INTERNAL_PORT=9092
819
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE=false
820
+ KAFKA_DELETE_TOPIC_ENABLE=true
821
+ KAFKA_LOG_RETENTION_HOURS=168
822
+ KAFKA_LOG_SEGMENT_BYTES=1073741824
823
+ KAFKA_MESSAGE_MAX_BYTES=10485880
824
+ KAFKA_REPLICA_FETCH_MAX_BYTES=10485880
825
+ KAFKA_SOCKET_REQUEST_MAX_BYTES=104857600
829
826
  ```
830
827
 
831
- ### Code
828
+ - topics are not auto-created by default
829
+ - deleting topics is allowed
830
+ - retention is 7 days by default
832
831
 
833
- ```ts
834
- const kafkaManager = KafkaManager.fromEnv({
835
- clientId: 'claims-service',
836
- mode: 'internal',
837
- })
832
+ ### Storage and safety limits
833
+
834
+ ```env
835
+ KAFKA_LOG_DIRS=/var/lib/kafka/data
836
+ KAFKA_ULIMIT_NOFILE_SOFT=65536
837
+ KAFKA_ULIMIT_NOFILE_HARD=65536
838
+ KAFKA_LOG_MAX_SIZE=100m
839
+ KAFKA_LOG_MAX_FILE=5
838
840
  ```
839
841
 
840
- This resolves to:
842
+ ### Kafka UI settings
841
843
 
842
- ```text
843
- broker-1:9092
844
- broker-2:9092
845
- broker-3:9092
844
+ ```env
845
+ KAFKA_UI_IMAGE=provectuslabs/kafka-ui:latest
846
+ KAFKA_UI_CONTAINER_NAME=kafka-ui
847
+ KAFKA_UI_HOSTNAME=kafka-ui
848
+ KAFKA_UI_RESTART_POLICY=unless-stopped
849
+ KAFKA_UI_BIND_ADDRESS=127.0.0.1
850
+ KAFKA_UI_PORT=5001
851
+ KAFKA_UI_DYNAMIC_CONFIG_ENABLED=false
852
+ KAFKA_UI_CLUSTER_NAME=Project Omni Enterprise Kafka
853
+ KAFKA_UI_READONLY=true
854
+ KAFKA_UI_AUTH_TYPE=LOGIN_FORM
855
+ KAFKA_UI_USERNAME=admin
856
+ KAFKA_UI_PASSWORD=Admin@007
846
857
  ```
847
858
 
848
- ## Which Mode Should You Use?
849
-
850
- - Use `mode: 'external'` when your service uses `localhost` broker ports like `29092`, `39092`, and `49092`
851
- - Use `mode: 'internal'` when your service is inside Docker and should reach brokers like `broker-1:9092`
852
- - Use `KAFKA_BROKERS` when you want the simplest and most explicit configuration
859
+ - `KAFKA_UI_BIND_ADDRESS=127.0.0.1` keeps the UI local-only
860
+ - `KAFKA_UI_PORT=5001` publishes the UI on your machine
861
+ - `KAFKA_UI_READONLY=true` prevents write actions from the UI
853
862
 
854
- ## Your Current Running Kafka Cluster
863
+ ### Healthcheck settings
855
864
 
856
- Based on your running Docker containers, your Kafka cluster is exposed like this:
865
+ ```env
866
+ KAFKA_HEALTHCHECK_INTERVAL=10s
867
+ KAFKA_HEALTHCHECK_TIMEOUT=5s
868
+ KAFKA_HEALTHCHECK_RETRIES=15
869
+ KAFKA_HEALTHCHECK_START_PERIOD=30s
870
+ ```
857
871
 
858
- - `broker-1` -> host port `29092`
859
- - `broker-2` -> host port `39092`
860
- - `broker-3` -> host port `49092`
861
- - `controller-1`, `controller-2`, and `controller-3` are controller-only nodes and should not be used by application clients
872
+ ### Creating Topics
862
873
 
863
- If your Node.js service runs on the host machine, use:
874
+ Because `KAFKA_AUTO_CREATE_TOPICS_ENABLE=false`, topics should be created deliberately.
864
875
 
865
- ```env
866
- KAFKA_EXTERNAL_HOST=localhost
867
- KAFKA_BROKER_1_EXTERNAL_PORT=29092
868
- KAFKA_BROKER_2_EXTERNAL_PORT=39092
869
- KAFKA_BROKER_3_EXTERNAL_PORT=49092
870
- KAFKA_BROKER_INTERNAL_PORT=9092
871
- ```
876
+ You have two good options:
872
877
 
873
- ```ts
874
- const kafkaManager = KafkaManager.fromEnv({
875
- clientId: 'claims-service',
876
- mode: 'external',
877
- })
878
- ```
878
+ - create topics from your service with `kafkaManager.provisionTopics()`
879
+ - allow UI-based creation by setting `KAFKA_UI_READONLY=false`
879
880
 
880
- If your Node.js service runs inside Docker on the same Kafka network, use:
881
+ Example with this package:
881
882
 
882
883
  ```ts
883
- const kafkaManager = KafkaManager.fromEnv({
884
- clientId: 'claims-service',
885
- mode: 'internal',
886
- })
884
+ await kafkaManager.provisionTopics([
885
+ {
886
+ topic: 'claims.created',
887
+ numPartitions: 6,
888
+ replicationFactor: 3,
889
+ },
890
+ ])
887
891
  ```
888
892
 
889
- This package is designed to work with that exact broker layout.
890
-
891
893
  ## API Summary
892
894
 
893
895
  ### `new KafkaManager(options)`
@@ -1138,7 +1140,9 @@ const kafkaManager = new KafkaManager({
1138
1140
  })
1139
1141
  ```
1140
1142
 
1141
- ## Common Mistakes
1143
+ ## Operational Guidance
1144
+
1145
+ ### Common Mistakes
1142
1146
 
1143
1147
  - Do not connect application clients to KRaft controller nodes. Connect only to brokers.
1144
1148
  - Do not use `mode: 'external'` inside Docker unless you truly want host-published ports.
@@ -1149,7 +1153,7 @@ const kafkaManager = new KafkaManager({
1149
1153
  - Do not publish business-critical events without metadata. Prefer `publishEnvelope()` for traceability.
1150
1154
  - Do not depend on raw driver error strings in application logic. Use `KafkaManagerError.code`.
1151
1155
 
1152
- ## Recommended SaaS Pattern
1156
+ ### Recommended SaaS Pattern
1153
1157
 
1154
1158
  For most SaaS services, the cleanest pattern is:
1155
1159
 
@@ -1160,13 +1164,13 @@ For most SaaS services, the cleanest pattern is:
1160
1164
  5. Catch `KafkaManagerError` and branch on `error.code`.
1161
1165
  6. Wrap service outcomes in `OperationResponse<T>` where useful.
1162
1166
 
1163
- ## Notes
1167
+ ### Notes
1164
1168
 
1165
1169
  - Your current Kafka cluster works fine with PLAINTEXT, so SSL and SASL are optional unless your environment changes
1166
1170
  - This package is designed for both host-based services and Dockerized services
1167
1171
  - If you want the least surprise, use `KAFKA_BROKERS`
1168
1172
 
1169
- ## Links
1173
+ ## Additional Documentation
1170
1174
 
1171
1175
  - npm deployment guide: [NPM_DEPLOYMENT.md](/var/www/html/matrix-project/sentinel-kafka-manager/NPM_DEPLOYMENT.md)
1172
1176
  - Example folder: [examples/service-module-usage/README.md](/var/www/html/matrix-project/sentinel-kafka-manager/examples/service-module-usage/README.md)