@drarzter/kafka-client 0.6.6 → 0.6.7
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 +43 -1
- package/dist/{chunk-KCUKXR6B.mjs → chunk-ISYOEX4W.mjs} +138 -20
- package/dist/chunk-ISYOEX4W.mjs.map +1 -0
- package/dist/core.d.mts +13 -3
- package/dist/core.d.ts +13 -3
- package/dist/core.js +137 -19
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +137 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/otel.d.mts +1 -1
- package/dist/otel.d.ts +1 -1
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{types-CTwLrJVU.d.mts → types-CqjRm-Cd.d.mts} +67 -3
- package/dist/{types-CTwLrJVU.d.ts → types-CqjRm-Cd.d.ts} +67 -3
- package/package.json +1 -1
- package/dist/chunk-KCUKXR6B.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -620,7 +620,7 @@ await kafka.startBatchConsumer(
|
|
|
620
620
|
| Property/Method | Description |
|
|
621
621
|
| --------------- | ----------- |
|
|
622
622
|
| `partition` | Partition number for this batch |
|
|
623
|
-
| `highWatermark` | Latest offset in the partition (lag
|
|
623
|
+
| `highWatermark` | Latest offset in the partition (`string`). `null` when the message is replayed via a retry topic consumer — in that path the broker high-watermark is not available. Guard against `null` before computing lag |
|
|
624
624
|
| `heartbeat()` | Send a heartbeat to keep the consumer session alive — call during long processing loops |
|
|
625
625
|
| `resolveOffset(offset)` | Mark offset as processed (required before `commitOffsetsIfNecessary`) |
|
|
626
626
|
| `commitOffsetsIfNecessary()` | Commit resolved offsets; respects `autoCommit` setting |
|
|
@@ -743,6 +743,47 @@ type BeforeConsumeResult =
|
|
|
743
743
|
|
|
744
744
|
When multiple instrumentations each provide a `wrap`, they compose in declaration order — the first instrumentation's `wrap` is the outermost.
|
|
745
745
|
|
|
746
|
+
### Lifecycle event hooks
|
|
747
|
+
|
|
748
|
+
Three additional hooks fire for specific events in the consume pipeline:
|
|
749
|
+
|
|
750
|
+
| Hook | When called | Arguments |
|
|
751
|
+
| ---- | ----------- | --------- |
|
|
752
|
+
| `onMessage` | Handler successfully processed a message | `(envelope)` — use as a success counter for error-rate calculations |
|
|
753
|
+
| `onRetry` | A message is queued for another attempt (in-process backoff or routed to a retry topic) | `(envelope, attempt, maxRetries)` |
|
|
754
|
+
| `onDlq` | A message is routed to the dead letter queue | `(envelope, reason)` — reason is `'handler-error'`, `'validation-error'`, or `'lamport-clock-duplicate'` |
|
|
755
|
+
| `onDuplicate` | A duplicate is detected via Lamport Clock | `(envelope, strategy)` — strategy is `'drop'`, `'dlq'`, or `'topic'` |
|
|
756
|
+
|
|
757
|
+
```typescript
|
|
758
|
+
const myInstrumentation: KafkaInstrumentation = {
|
|
759
|
+
onMessage(envelope) {
|
|
760
|
+
metrics.increment('kafka.processed', { topic: envelope.topic });
|
|
761
|
+
},
|
|
762
|
+
onRetry(envelope, attempt, maxRetries) {
|
|
763
|
+
console.warn(`Retrying ${envelope.topic} — attempt ${attempt}/${maxRetries}`);
|
|
764
|
+
},
|
|
765
|
+
onDlq(envelope, reason) {
|
|
766
|
+
alertingSystem.send({ topic: envelope.topic, reason });
|
|
767
|
+
},
|
|
768
|
+
onDuplicate(envelope, strategy) {
|
|
769
|
+
metrics.increment('kafka.duplicate', { topic: envelope.topic, strategy });
|
|
770
|
+
},
|
|
771
|
+
};
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### Built-in metrics
|
|
775
|
+
|
|
776
|
+
`KafkaClient` maintains lightweight in-process event counters independently of any instrumentation:
|
|
777
|
+
|
|
778
|
+
```typescript
|
|
779
|
+
const snapshot = kafka.getMetrics();
|
|
780
|
+
// { processedCount: number; retryCount: number; dlqCount: number; dedupCount: number }
|
|
781
|
+
|
|
782
|
+
kafka.resetMetrics(); // reset all counters to zero
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
Counters are incremented in the same code paths that fire the corresponding hooks — they are always active regardless of whether any instrumentation is configured.
|
|
786
|
+
|
|
746
787
|
## Options reference
|
|
747
788
|
|
|
748
789
|
### Send options
|
|
@@ -795,6 +836,7 @@ Passed to `KafkaModule.register()` or returned from `registerAsync()` factory:
|
|
|
795
836
|
| `numPartitions` | `1` | Number of partitions for auto-created topics |
|
|
796
837
|
| `strictSchemas` | `true` | Validate string topic keys against schemas registered via TopicDescriptor |
|
|
797
838
|
| `instrumentation` | `[]` | Client-wide instrumentation hooks (e.g. OTel). Applied to both send and consume paths |
|
|
839
|
+
| `transactionalId` | `${clientId}-tx` | Transactional producer ID for `transaction()` calls. Must be unique per producer instance across the cluster — two instances sharing the same ID will be fenced by Kafka. The client logs a warning when the same ID is registered twice within one process |
|
|
798
840
|
| `onMessageLost` | — | Called when a message is silently dropped without DLQ — use to alert, log to external systems, or trigger fallback logic |
|
|
799
841
|
| `onRebalance` | — | Called on every partition assign/revoke event across all consumers created by this client |
|
|
800
842
|
|
|
@@ -318,7 +318,10 @@ async function sendToDlq(topic2, rawMessage, deps, meta) {
|
|
|
318
318
|
deps.logger.warn(`Message sent to DLQ: ${payload.topic}`);
|
|
319
319
|
} catch (error) {
|
|
320
320
|
const err = toError(error);
|
|
321
|
-
deps.logger.error(
|
|
321
|
+
deps.logger.error(
|
|
322
|
+
`Failed to send message to DLQ ${payload.topic}:`,
|
|
323
|
+
err.stack
|
|
324
|
+
);
|
|
322
325
|
await deps.onMessageLost?.({
|
|
323
326
|
topic: topic2,
|
|
324
327
|
error: err,
|
|
@@ -333,14 +336,9 @@ var RETRY_HEADER_MAX_RETRIES = "x-retry-max-retries";
|
|
|
333
336
|
var RETRY_HEADER_ORIGINAL_TOPIC = "x-retry-original-topic";
|
|
334
337
|
function buildRetryTopicPayload(originalTopic, rawMessages, attempt, maxRetries, delayMs, originalHeaders) {
|
|
335
338
|
const retryTopic = `${originalTopic}.retry.${attempt}`;
|
|
339
|
+
const STRIP = /* @__PURE__ */ new Set([RETRY_HEADER_ATTEMPT, RETRY_HEADER_AFTER, RETRY_HEADER_MAX_RETRIES, RETRY_HEADER_ORIGINAL_TOPIC]);
|
|
336
340
|
function buildHeaders(hdr) {
|
|
337
|
-
const
|
|
338
|
-
[RETRY_HEADER_ATTEMPT]: _a,
|
|
339
|
-
[RETRY_HEADER_AFTER]: _b,
|
|
340
|
-
[RETRY_HEADER_MAX_RETRIES]: _c,
|
|
341
|
-
[RETRY_HEADER_ORIGINAL_TOPIC]: _d,
|
|
342
|
-
...userHeaders
|
|
343
|
-
} = hdr;
|
|
341
|
+
const userHeaders = Object.fromEntries(Object.entries(hdr).filter(([k]) => !STRIP.has(k)));
|
|
344
342
|
return {
|
|
345
343
|
...userHeaders,
|
|
346
344
|
[RETRY_HEADER_ATTEMPT]: String(attempt),
|
|
@@ -396,10 +394,18 @@ function buildDuplicateTopicPayload(sourceTopic, rawMessage, destinationTopic, m
|
|
|
396
394
|
"x-duplicate-incoming-clock": String(meta?.incomingClock ?? 0),
|
|
397
395
|
"x-duplicate-last-processed-clock": String(meta?.lastProcessedClock ?? 0)
|
|
398
396
|
};
|
|
399
|
-
return {
|
|
397
|
+
return {
|
|
398
|
+
topic: destinationTopic,
|
|
399
|
+
messages: [{ value: rawMessage, headers }]
|
|
400
|
+
};
|
|
400
401
|
}
|
|
401
402
|
async function sendToDuplicatesTopic(sourceTopic, rawMessage, destinationTopic, deps, meta) {
|
|
402
|
-
const payload = buildDuplicateTopicPayload(
|
|
403
|
+
const payload = buildDuplicateTopicPayload(
|
|
404
|
+
sourceTopic,
|
|
405
|
+
rawMessage,
|
|
406
|
+
destinationTopic,
|
|
407
|
+
meta
|
|
408
|
+
);
|
|
403
409
|
try {
|
|
404
410
|
await deps.producer.send(payload);
|
|
405
411
|
deps.logger.warn(`Duplicate message forwarded to ${destinationTopic}`);
|
|
@@ -491,7 +497,10 @@ async function executeWithRetry(fn, ctx, deps) {
|
|
|
491
497
|
interceptors,
|
|
492
498
|
deps.instrumentation
|
|
493
499
|
);
|
|
494
|
-
if (!error)
|
|
500
|
+
if (!error) {
|
|
501
|
+
for (const env of envelopes) deps.onMessage?.(env);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
495
504
|
const isLastAttempt = attempt === maxAttempts;
|
|
496
505
|
const reportedError = isLastAttempt && maxAttempts > 1 ? new KafkaRetryExhaustedError(
|
|
497
506
|
topic2,
|
|
@@ -516,6 +525,7 @@ async function executeWithRetry(fn, ctx, deps) {
|
|
|
516
525
|
isBatch ? envelopes.map((e) => e.headers) : envelopes[0]?.headers ?? {},
|
|
517
526
|
deps
|
|
518
527
|
);
|
|
528
|
+
deps.onRetry?.(envelopes[0], 1, retry.maxRetries);
|
|
519
529
|
} else if (isLastAttempt) {
|
|
520
530
|
if (dlq) {
|
|
521
531
|
for (let i = 0; i < rawMessages.length; i++) {
|
|
@@ -524,6 +534,7 @@ async function executeWithRetry(fn, ctx, deps) {
|
|
|
524
534
|
attempt,
|
|
525
535
|
originalHeaders: envelopes[i]?.headers
|
|
526
536
|
});
|
|
537
|
+
deps.onDlq?.(envelopes[i] ?? envelopes[0], "handler-error");
|
|
527
538
|
}
|
|
528
539
|
} else {
|
|
529
540
|
await deps.onMessageLost?.({
|
|
@@ -535,6 +546,7 @@ async function executeWithRetry(fn, ctx, deps) {
|
|
|
535
546
|
}
|
|
536
547
|
} else {
|
|
537
548
|
const cap = Math.min(backoffMs * 2 ** (attempt - 1), maxBackoffMs);
|
|
549
|
+
deps.onRetry?.(envelopes[0], attempt, maxAttempts - 1);
|
|
538
550
|
await sleep(Math.floor(Math.random() * cap));
|
|
539
551
|
}
|
|
540
552
|
}
|
|
@@ -558,6 +570,7 @@ async function applyDeduplication(envelope, raw, dedup, dlq, deps) {
|
|
|
558
570
|
deps.logger.warn(
|
|
559
571
|
`Duplicate message on ${envelope.topic}[${envelope.partition}]: clock=${incomingClock} <= last=${lastProcessedClock} \u2014 strategy=${strategy}`
|
|
560
572
|
);
|
|
573
|
+
deps.onDuplicate?.(envelope, strategy);
|
|
561
574
|
if (strategy === "dlq" && dlq) {
|
|
562
575
|
const augmentedHeaders = {
|
|
563
576
|
...envelope.headers,
|
|
@@ -761,6 +774,9 @@ async function startLevelConsumer(level, levelTopics, levelGroupId, originalTopi
|
|
|
761
774
|
producer,
|
|
762
775
|
instrumentation,
|
|
763
776
|
onMessageLost,
|
|
777
|
+
onRetry,
|
|
778
|
+
onDlq,
|
|
779
|
+
onMessage,
|
|
764
780
|
ensureTopic,
|
|
765
781
|
getOrCreateConsumer: getOrCreateConsumer2,
|
|
766
782
|
runningConsumers,
|
|
@@ -842,6 +858,7 @@ async function startLevelConsumer(level, levelTopics, levelGroupId, originalTopi
|
|
|
842
858
|
instrumentation
|
|
843
859
|
);
|
|
844
860
|
if (!error) {
|
|
861
|
+
onMessage?.(envelope);
|
|
845
862
|
await consumer.commitOffsets([nextOffset]);
|
|
846
863
|
return;
|
|
847
864
|
}
|
|
@@ -874,12 +891,23 @@ async function startLevelConsumer(level, levelTopics, levelGroupId, originalTopi
|
|
|
874
891
|
await tx.send({ topic: rtTopic, messages: rtMsgs });
|
|
875
892
|
await tx.sendOffsets({
|
|
876
893
|
consumer,
|
|
877
|
-
topics: [
|
|
894
|
+
topics: [
|
|
895
|
+
{
|
|
896
|
+
topic: nextOffset.topic,
|
|
897
|
+
partitions: [
|
|
898
|
+
{
|
|
899
|
+
partition: nextOffset.partition,
|
|
900
|
+
offset: nextOffset.offset
|
|
901
|
+
}
|
|
902
|
+
]
|
|
903
|
+
}
|
|
904
|
+
]
|
|
878
905
|
});
|
|
879
906
|
await tx.commit();
|
|
880
907
|
logger.warn(
|
|
881
908
|
`Message routed to ${rtTopic} (EOS, level ${nextLevel}/${currentMaxRetries})`
|
|
882
909
|
);
|
|
910
|
+
onRetry?.(envelope, nextLevel, currentMaxRetries);
|
|
883
911
|
} catch (txErr) {
|
|
884
912
|
try {
|
|
885
913
|
await tx.abort();
|
|
@@ -907,10 +935,21 @@ async function startLevelConsumer(level, levelTopics, levelGroupId, originalTopi
|
|
|
907
935
|
await tx.send({ topic: dTopic, messages: dMsgs });
|
|
908
936
|
await tx.sendOffsets({
|
|
909
937
|
consumer,
|
|
910
|
-
topics: [
|
|
938
|
+
topics: [
|
|
939
|
+
{
|
|
940
|
+
topic: nextOffset.topic,
|
|
941
|
+
partitions: [
|
|
942
|
+
{
|
|
943
|
+
partition: nextOffset.partition,
|
|
944
|
+
offset: nextOffset.offset
|
|
945
|
+
}
|
|
946
|
+
]
|
|
947
|
+
}
|
|
948
|
+
]
|
|
911
949
|
});
|
|
912
950
|
await tx.commit();
|
|
913
951
|
logger.warn(`Message sent to DLQ: ${dTopic} (EOS)`);
|
|
952
|
+
onDlq?.(envelope, "handler-error");
|
|
914
953
|
} catch (txErr) {
|
|
915
954
|
try {
|
|
916
955
|
await tx.abort();
|
|
@@ -934,7 +973,12 @@ async function startLevelConsumer(level, levelTopics, levelGroupId, originalTopi
|
|
|
934
973
|
}
|
|
935
974
|
});
|
|
936
975
|
runningConsumers.set(levelGroupId, "eachMessage");
|
|
937
|
-
await waitForPartitionAssignment(
|
|
976
|
+
await waitForPartitionAssignment(
|
|
977
|
+
consumer,
|
|
978
|
+
levelTopics,
|
|
979
|
+
logger,
|
|
980
|
+
assignmentTimeoutMs
|
|
981
|
+
);
|
|
938
982
|
logger.log(
|
|
939
983
|
`Retry level ${level}/${retry.maxRetries} consumer started for: ${originalTopics.join(", ")} (group: ${levelGroupId})`
|
|
940
984
|
);
|
|
@@ -964,6 +1008,7 @@ async function startRetryTopicConsumers(originalTopics, originalGroupId, handleM
|
|
|
964
1008
|
|
|
965
1009
|
// src/client/kafka.client/index.ts
|
|
966
1010
|
var { Kafka: KafkaClass, logLevel: KafkaLogLevel } = KafkaJS;
|
|
1011
|
+
var _activeTransactionalIds = /* @__PURE__ */ new Set();
|
|
967
1012
|
var KafkaClient = class {
|
|
968
1013
|
kafka;
|
|
969
1014
|
producer;
|
|
@@ -989,6 +1034,15 @@ var KafkaClient = class {
|
|
|
989
1034
|
instrumentation;
|
|
990
1035
|
onMessageLost;
|
|
991
1036
|
onRebalance;
|
|
1037
|
+
/** Transactional producer ID — configurable via `KafkaClientOptions.transactionalId`. */
|
|
1038
|
+
txId;
|
|
1039
|
+
/** Internal event counters exposed via `getMetrics()`. */
|
|
1040
|
+
_metrics = {
|
|
1041
|
+
processedCount: 0,
|
|
1042
|
+
retryCount: 0,
|
|
1043
|
+
dlqCount: 0,
|
|
1044
|
+
dedupCount: 0
|
|
1045
|
+
};
|
|
992
1046
|
/** Monotonically increasing Lamport clock stamped on every outgoing message. */
|
|
993
1047
|
_lamportClock = 0;
|
|
994
1048
|
/** Per-groupId deduplication state: `"topic:partition"` → last processed clock. */
|
|
@@ -1012,6 +1066,7 @@ var KafkaClient = class {
|
|
|
1012
1066
|
this.instrumentation = options?.instrumentation ?? [];
|
|
1013
1067
|
this.onMessageLost = options?.onMessageLost;
|
|
1014
1068
|
this.onRebalance = options?.onRebalance;
|
|
1069
|
+
this.txId = options?.transactionalId ?? `${clientId}-tx`;
|
|
1015
1070
|
this.kafka = new KafkaClass({
|
|
1016
1071
|
kafkaJS: {
|
|
1017
1072
|
clientId: this.clientId,
|
|
@@ -1048,16 +1103,22 @@ var KafkaClient = class {
|
|
|
1048
1103
|
/** Execute multiple sends atomically. Commits on success, aborts on error. */
|
|
1049
1104
|
async transaction(fn) {
|
|
1050
1105
|
if (!this.txProducerInitPromise) {
|
|
1106
|
+
if (_activeTransactionalIds.has(this.txId)) {
|
|
1107
|
+
this.logger.warn(
|
|
1108
|
+
`transactionalId "${this.txId}" is already in use by another KafkaClient in this process. Kafka will fence one of the producers. Set a unique \`transactionalId\` (or distinct \`clientId\`) per instance.`
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1051
1111
|
const initPromise = (async () => {
|
|
1052
1112
|
const p = this.kafka.producer({
|
|
1053
1113
|
kafkaJS: {
|
|
1054
1114
|
acks: -1,
|
|
1055
1115
|
idempotent: true,
|
|
1056
|
-
transactionalId:
|
|
1116
|
+
transactionalId: this.txId,
|
|
1057
1117
|
maxInFlightRequests: 1
|
|
1058
1118
|
}
|
|
1059
1119
|
});
|
|
1060
1120
|
await p.connect();
|
|
1121
|
+
_activeTransactionalIds.add(this.txId);
|
|
1061
1122
|
return p;
|
|
1062
1123
|
})();
|
|
1063
1124
|
this.txProducerInitPromise = initPromise.catch((err) => {
|
|
@@ -1128,7 +1189,10 @@ var KafkaClient = class {
|
|
|
1128
1189
|
const { consumer, schemaMap, topicNames, gid, dlq, interceptors, retry } = await this.setupConsumer(topics, "eachMessage", options);
|
|
1129
1190
|
const deps = this.messageDeps;
|
|
1130
1191
|
const timeoutMs = options.handlerTimeoutMs;
|
|
1131
|
-
const deduplication = this.resolveDeduplicationContext(
|
|
1192
|
+
const deduplication = this.resolveDeduplicationContext(
|
|
1193
|
+
gid,
|
|
1194
|
+
options.deduplication
|
|
1195
|
+
);
|
|
1132
1196
|
await consumer.run({
|
|
1133
1197
|
eachMessage: (payload) => this.trackInFlight(
|
|
1134
1198
|
() => handleEachMessage(
|
|
@@ -1182,7 +1246,10 @@ var KafkaClient = class {
|
|
|
1182
1246
|
const { consumer, schemaMap, topicNames, gid, dlq, interceptors, retry } = await this.setupConsumer(topics, "eachBatch", options);
|
|
1183
1247
|
const deps = this.messageDeps;
|
|
1184
1248
|
const timeoutMs = options.handlerTimeoutMs;
|
|
1185
|
-
const deduplication = this.resolveDeduplicationContext(
|
|
1249
|
+
const deduplication = this.resolveDeduplicationContext(
|
|
1250
|
+
gid,
|
|
1251
|
+
options.deduplication
|
|
1252
|
+
);
|
|
1186
1253
|
await consumer.run({
|
|
1187
1254
|
eachBatch: (payload) => this.trackInFlight(
|
|
1188
1255
|
() => handleEachBatch(
|
|
@@ -1209,7 +1276,7 @@ var KafkaClient = class {
|
|
|
1209
1276
|
}
|
|
1210
1277
|
const handleMessageForRetry = (env) => handleBatch([env], {
|
|
1211
1278
|
partition: env.partition,
|
|
1212
|
-
highWatermark:
|
|
1279
|
+
highWatermark: null,
|
|
1213
1280
|
heartbeat: async () => {
|
|
1214
1281
|
},
|
|
1215
1282
|
resolveOffset: () => {
|
|
@@ -1277,6 +1344,7 @@ var KafkaClient = class {
|
|
|
1277
1344
|
toError(e).message
|
|
1278
1345
|
)
|
|
1279
1346
|
);
|
|
1347
|
+
_activeTransactionalIds.delete(txId);
|
|
1280
1348
|
this.retryTxProducers.delete(txId);
|
|
1281
1349
|
}
|
|
1282
1350
|
}
|
|
@@ -1352,15 +1420,28 @@ var KafkaClient = class {
|
|
|
1352
1420
|
getClientId() {
|
|
1353
1421
|
return this.clientId;
|
|
1354
1422
|
}
|
|
1423
|
+
getMetrics() {
|
|
1424
|
+
return { ...this._metrics };
|
|
1425
|
+
}
|
|
1426
|
+
resetMetrics() {
|
|
1427
|
+
this._metrics.processedCount = 0;
|
|
1428
|
+
this._metrics.retryCount = 0;
|
|
1429
|
+
this._metrics.dlqCount = 0;
|
|
1430
|
+
this._metrics.dedupCount = 0;
|
|
1431
|
+
}
|
|
1355
1432
|
/** Gracefully disconnect producer, all consumers, and admin. */
|
|
1356
1433
|
async disconnect(drainTimeoutMs = 3e4) {
|
|
1357
1434
|
await this.waitForDrain(drainTimeoutMs);
|
|
1358
1435
|
const tasks = [this.producer.disconnect()];
|
|
1359
1436
|
if (this.txProducer) {
|
|
1360
1437
|
tasks.push(this.txProducer.disconnect());
|
|
1438
|
+
_activeTransactionalIds.delete(this.txId);
|
|
1361
1439
|
this.txProducer = void 0;
|
|
1362
1440
|
this.txProducerInitPromise = void 0;
|
|
1363
1441
|
}
|
|
1442
|
+
for (const txId of this.retryTxProducers.keys()) {
|
|
1443
|
+
_activeTransactionalIds.delete(txId);
|
|
1444
|
+
}
|
|
1364
1445
|
for (const p of this.retryTxProducers.values()) {
|
|
1365
1446
|
tasks.push(p.disconnect());
|
|
1366
1447
|
}
|
|
@@ -1456,6 +1537,30 @@ var KafkaClient = class {
|
|
|
1456
1537
|
}
|
|
1457
1538
|
}
|
|
1458
1539
|
}
|
|
1540
|
+
notifyRetry(envelope, attempt, maxRetries) {
|
|
1541
|
+
this._metrics.retryCount++;
|
|
1542
|
+
for (const inst of this.instrumentation) {
|
|
1543
|
+
inst.onRetry?.(envelope, attempt, maxRetries);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
notifyDlq(envelope, reason) {
|
|
1547
|
+
this._metrics.dlqCount++;
|
|
1548
|
+
for (const inst of this.instrumentation) {
|
|
1549
|
+
inst.onDlq?.(envelope, reason);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
notifyDuplicate(envelope, strategy) {
|
|
1553
|
+
this._metrics.dedupCount++;
|
|
1554
|
+
for (const inst of this.instrumentation) {
|
|
1555
|
+
inst.onDuplicate?.(envelope, strategy);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
notifyMessage(envelope) {
|
|
1559
|
+
this._metrics.processedCount++;
|
|
1560
|
+
for (const inst of this.instrumentation) {
|
|
1561
|
+
inst.onMessage?.(envelope);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1459
1564
|
/**
|
|
1460
1565
|
* Start a timer that logs a warning if `fn` hasn't resolved within `timeoutMs`.
|
|
1461
1566
|
* The handler itself is not cancelled — the warning is diagnostic only.
|
|
@@ -1545,6 +1650,11 @@ var KafkaClient = class {
|
|
|
1545
1650
|
* so Kafka can fence stale producers on restart without affecting other levels.
|
|
1546
1651
|
*/
|
|
1547
1652
|
async createRetryTxProducer(transactionalId) {
|
|
1653
|
+
if (_activeTransactionalIds.has(transactionalId)) {
|
|
1654
|
+
this.logger.warn(
|
|
1655
|
+
`transactionalId "${transactionalId}" is already in use by another KafkaClient in this process. Kafka will fence one of the producers. Set a unique \`transactionalId\` (or distinct \`clientId\`) per instance.`
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1548
1658
|
const p = this.kafka.producer({
|
|
1549
1659
|
kafkaJS: {
|
|
1550
1660
|
acks: -1,
|
|
@@ -1554,6 +1664,7 @@ var KafkaClient = class {
|
|
|
1554
1664
|
}
|
|
1555
1665
|
});
|
|
1556
1666
|
await p.connect();
|
|
1667
|
+
_activeTransactionalIds.add(transactionalId);
|
|
1557
1668
|
this.retryTxProducers.set(transactionalId, p);
|
|
1558
1669
|
return p;
|
|
1559
1670
|
}
|
|
@@ -1674,7 +1785,11 @@ var KafkaClient = class {
|
|
|
1674
1785
|
logger: this.logger,
|
|
1675
1786
|
producer: this.producer,
|
|
1676
1787
|
instrumentation: this.instrumentation,
|
|
1677
|
-
onMessageLost: this.onMessageLost
|
|
1788
|
+
onMessageLost: this.onMessageLost,
|
|
1789
|
+
onRetry: this.notifyRetry.bind(this),
|
|
1790
|
+
onDlq: this.notifyDlq.bind(this),
|
|
1791
|
+
onDuplicate: this.notifyDuplicate.bind(this),
|
|
1792
|
+
onMessage: this.notifyMessage.bind(this)
|
|
1678
1793
|
};
|
|
1679
1794
|
}
|
|
1680
1795
|
get retryTopicDeps() {
|
|
@@ -1683,6 +1798,9 @@ var KafkaClient = class {
|
|
|
1683
1798
|
producer: this.producer,
|
|
1684
1799
|
instrumentation: this.instrumentation,
|
|
1685
1800
|
onMessageLost: this.onMessageLost,
|
|
1801
|
+
onRetry: this.notifyRetry.bind(this),
|
|
1802
|
+
onDlq: this.notifyDlq.bind(this),
|
|
1803
|
+
onMessage: this.notifyMessage.bind(this),
|
|
1686
1804
|
ensureTopic: (t) => this.ensureTopic(t),
|
|
1687
1805
|
getOrCreateConsumer: (gid, fb, ac) => getOrCreateConsumer(gid, fb, ac, this.consumerOpsDeps),
|
|
1688
1806
|
runningConsumers: this.runningConsumers,
|
|
@@ -1725,4 +1843,4 @@ export {
|
|
|
1725
1843
|
KafkaClient,
|
|
1726
1844
|
topic
|
|
1727
1845
|
};
|
|
1728
|
-
//# sourceMappingURL=chunk-
|
|
1846
|
+
//# sourceMappingURL=chunk-ISYOEX4W.mjs.map
|