@platformatic/kafka 2.10.0-alpha.1 → 2.11.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/dist/apis/metadata/api-versions-v3.js +8 -1
- package/dist/clients/admin/admin.js +24 -11
- package/dist/clients/admin/options.d.ts +58 -2
- package/dist/clients/admin/options.js +38 -7
- package/dist/clients/admin/types.d.ts +7 -1
- package/dist/clients/base/base.js +26 -8
- package/dist/clients/consumer/consumer.d.ts +2 -1
- package/dist/clients/consumer/consumer.js +107 -5
- package/dist/clients/consumer/messages-stream.js +59 -9
- package/dist/clients/consumer/options.d.ts +20 -0
- package/dist/clients/consumer/options.js +1 -0
- package/dist/clients/consumer/types.d.ts +7 -0
- package/dist/clients/producer/producer.js +48 -42
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +4 -0
- package/dist/network/utils.js +26 -8
- package/dist/protocol/records.d.ts +3 -0
- package/dist/registries/abstract.js +27 -8
- package/dist/registries/confluent-schema-registry.js +3 -2
- package/dist/symbols.d.ts +1 -0
- package/dist/symbols.js +1 -0
- package/dist/typescript-4/dist/clients/admin/options.d.ts +58 -2
- package/dist/typescript-4/dist/clients/admin/types.d.ts +7 -1
- package/dist/typescript-4/dist/clients/consumer/consumer.d.ts +2 -1
- package/dist/typescript-4/dist/clients/consumer/options.d.ts +20 -0
- package/dist/typescript-4/dist/clients/consumer/types.d.ts +7 -0
- package/dist/typescript-4/dist/errors.d.ts +1 -0
- package/dist/typescript-4/dist/protocol/records.d.ts +3 -0
- package/dist/typescript-4/dist/symbols.d.ts +1 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import { consumerReceivesChannel, createDiagnosticContext, notifyCreation } from
|
|
|
5
5
|
import { findErrorBy, protocolErrors, UserError } from "../../errors.js";
|
|
6
6
|
import { IS_CONTROL } from "../../protocol/records.js";
|
|
7
7
|
import { runAsyncSeries } from "../../registries/abstract.js";
|
|
8
|
-
import { kAutocommit, kGetFetchNode, kInstance, kRefreshOffsetsAndFetch } from "../../symbols.js";
|
|
8
|
+
import { kAutocommit, kDeserializationError, kGetFetchNode, kInstance, kRefreshOffsetsAndFetch } from "../../symbols.js";
|
|
9
9
|
import { kConnections, kCreateConnectionPool, kInspect, kPrometheus } from "../base/base.js";
|
|
10
10
|
import { ensureMetric } from "../metrics.js";
|
|
11
11
|
import { defaultConsumerOptions } from "./options.js";
|
|
@@ -24,11 +24,12 @@ function messageToJSON() {
|
|
|
24
24
|
return {
|
|
25
25
|
key: this.key,
|
|
26
26
|
value: this.value,
|
|
27
|
-
headers:
|
|
27
|
+
headers: this.headerEntries.map(([key, value]) => [key, value]),
|
|
28
28
|
topic: this.topic,
|
|
29
29
|
partition: this.partition,
|
|
30
30
|
timestamp: this.timestamp.toString(),
|
|
31
31
|
offset: this.offset.toString(),
|
|
32
|
+
leaderEpoch: this.leaderEpoch,
|
|
32
33
|
metadata: this.metadata
|
|
33
34
|
};
|
|
34
35
|
}
|
|
@@ -309,7 +310,12 @@ export class MessagesStream extends Readable {
|
|
|
309
310
|
_construct(callback) {
|
|
310
311
|
this.#refreshOffsetsInflight = true;
|
|
311
312
|
this.#refreshOffsets(error => {
|
|
313
|
+
const wasPending = this.#refreshOffsetsPending;
|
|
312
314
|
this.#refreshOffsetsInflight = false;
|
|
315
|
+
this.#refreshOffsetsPending = false;
|
|
316
|
+
if (!error && wasPending) {
|
|
317
|
+
this.#scheduleRefreshOffsetsAndFetch();
|
|
318
|
+
}
|
|
313
319
|
callback(error ?? undefined);
|
|
314
320
|
});
|
|
315
321
|
}
|
|
@@ -610,11 +616,20 @@ export class MessagesStream extends Readable {
|
|
|
610
616
|
: this.#commit.bind(this, topic, partition, offset + 1n, leaderEpoch);
|
|
611
617
|
try {
|
|
612
618
|
const headers = new Map();
|
|
619
|
+
const headerEntries = [];
|
|
613
620
|
let payloadType = 'headerKey';
|
|
614
621
|
let deserializedKey;
|
|
615
622
|
let deserializedValue;
|
|
616
623
|
let deserializationError;
|
|
624
|
+
let hookFailed = false;
|
|
617
625
|
try {
|
|
626
|
+
// A before-deserialization hook already failed for this message, report it as is
|
|
627
|
+
const hookError = messageToConsume[kDeserializationError];
|
|
628
|
+
if (hookError) {
|
|
629
|
+
hookFailed = true;
|
|
630
|
+
payloadType = hookError.payloadType;
|
|
631
|
+
throw hookError.error;
|
|
632
|
+
}
|
|
618
633
|
for (const [headerKey, headerValue] of record.headers) {
|
|
619
634
|
payloadType = 'headerKey';
|
|
620
635
|
/* c8 ignore next - Hard to test */
|
|
@@ -623,6 +638,7 @@ export class MessagesStream extends Readable {
|
|
|
623
638
|
/* c8 ignore next - Hard to test */
|
|
624
639
|
const deserializedHeaderValue = headerValueDeserializer(headerValue ?? undefined, messageToConsume);
|
|
625
640
|
headers.set(deserializedHeaderKey, deserializedHeaderValue);
|
|
641
|
+
headerEntries.push([deserializedHeaderKey, deserializedHeaderValue]);
|
|
626
642
|
}
|
|
627
643
|
payloadType = 'key';
|
|
628
644
|
/* c8 ignore next - Hard to test */
|
|
@@ -659,11 +675,14 @@ export class MessagesStream extends Readable {
|
|
|
659
675
|
*/
|
|
660
676
|
diagnosticContext.error = error;
|
|
661
677
|
consumerReceivesChannel.error.publish(diagnosticContext);
|
|
662
|
-
|
|
678
|
+
// A hook failure happens before anything is deserialized, so nothing can be kept
|
|
679
|
+
const headersFailed = hookFailed || payloadType === 'headerKey' || payloadType === 'headerValue';
|
|
663
680
|
if (headersFailed) {
|
|
664
681
|
headers.clear();
|
|
682
|
+
headerEntries.length = 0;
|
|
665
683
|
for (const [headerKey, headerValue] of record.headers) {
|
|
666
684
|
headers.set(headerKey, headerValue);
|
|
685
|
+
headerEntries.push([headerKey, headerValue]);
|
|
667
686
|
}
|
|
668
687
|
}
|
|
669
688
|
if (headersFailed || payloadType === 'key') {
|
|
@@ -678,7 +697,7 @@ export class MessagesStream extends Readable {
|
|
|
678
697
|
else {
|
|
679
698
|
diagnosticContext.error = error;
|
|
680
699
|
consumerReceivesChannel.error.publish(diagnosticContext);
|
|
681
|
-
this.destroy(new UserError('Failed to deserialize a message.', { cause: error }));
|
|
700
|
+
this.destroy(hookFailed ? error : new UserError('Failed to deserialize a message.', { cause: error }));
|
|
682
701
|
return;
|
|
683
702
|
}
|
|
684
703
|
}
|
|
@@ -687,10 +706,14 @@ export class MessagesStream extends Readable {
|
|
|
687
706
|
key: deserializedKey,
|
|
688
707
|
value: deserializedValue,
|
|
689
708
|
headers,
|
|
709
|
+
headerEntries,
|
|
690
710
|
topic,
|
|
691
711
|
partition,
|
|
692
712
|
timestamp: firstTimestamp + record.timestampDelta,
|
|
693
713
|
offset,
|
|
714
|
+
// The batch header epoch is the epoch of the record itself, which is what
|
|
715
|
+
// OffsetCommit expects as committedLeaderEpoch (KIP-320).
|
|
716
|
+
leaderEpoch: batch.partitionLeaderEpoch,
|
|
694
717
|
commit,
|
|
695
718
|
// Deserializers and hooks can enrich the metadata of the message they processed
|
|
696
719
|
metadata: deserializationError
|
|
@@ -773,6 +796,16 @@ export class MessagesStream extends Readable {
|
|
|
773
796
|
if (this.#offsetsToCommit.size === 0) {
|
|
774
797
|
return;
|
|
775
798
|
}
|
|
799
|
+
// Skip this tick while the consumer is mid-(re)join: a commit attempted here would fail
|
|
800
|
+
// with a rejoin-required error (ILLEGAL_GENERATION/UNKNOWN_MEMBER_ID/REBALANCE_IN_PROGRESS),
|
|
801
|
+
// and — since this runs on its own timer, independently of the group's own rejoin flow —
|
|
802
|
+
// each such failure calls joinGroup again. With a short autocommit interval and many
|
|
803
|
+
// consumers in the group, this can fire fresh JoinGroups fast enough that the group never
|
|
804
|
+
// completes a rebalance round. Leave the offsets queued; they are committed once the
|
|
805
|
+
// consumer becomes active again (next tick, or the final flush on close/destroy).
|
|
806
|
+
if (!this.#consumer.isActive()) {
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
776
809
|
this.#autocommitInflight = true;
|
|
777
810
|
const offsets = Array.from(this.#offsetsToCommit.values());
|
|
778
811
|
this.#offsetsToCommit.clear();
|
|
@@ -831,7 +864,10 @@ export class MessagesStream extends Readable {
|
|
|
831
864
|
topics.push(assignment);
|
|
832
865
|
}
|
|
833
866
|
if (!topics.length) {
|
|
834
|
-
|
|
867
|
+
// No assignment yet: do not seed fallback offsets for the whole topic.
|
|
868
|
+
// consumer:group:join will refresh once partitions are assigned.
|
|
869
|
+
this.emit('offsets');
|
|
870
|
+
callback(null);
|
|
835
871
|
return;
|
|
836
872
|
}
|
|
837
873
|
this.#consumer.listCommittedOffsets({ topics }, (error, commits) => {
|
|
@@ -858,6 +894,7 @@ export class MessagesStream extends Readable {
|
|
|
858
894
|
this.#refreshOffsetsInflight = true;
|
|
859
895
|
this.#refreshOffsets(error => {
|
|
860
896
|
const shouldDestroyOnError = this.#refreshOffsetsDestroyOnError;
|
|
897
|
+
const wasPending = this.#refreshOffsetsPending;
|
|
861
898
|
this.#refreshOffsetsInflight = false;
|
|
862
899
|
this.#refreshOffsetsDestroyOnError = false;
|
|
863
900
|
this.#refreshOffsetsPending = false;
|
|
@@ -869,8 +906,7 @@ export class MessagesStream extends Readable {
|
|
|
869
906
|
return;
|
|
870
907
|
}
|
|
871
908
|
// A new one was scheduled while the previous one was inflight, we need to run it immediately
|
|
872
|
-
|
|
873
|
-
if (this.#refreshOffsetsPending) {
|
|
909
|
+
if (wasPending) {
|
|
874
910
|
this.#scheduleRefreshOffsetsAndFetch(shouldDestroyOnError);
|
|
875
911
|
return;
|
|
876
912
|
}
|
|
@@ -972,9 +1008,23 @@ export class MessagesStream extends Readable {
|
|
|
972
1008
|
}
|
|
973
1009
|
runAsyncSeries((request, cb) => {
|
|
974
1010
|
const [data, type, message] = request;
|
|
975
|
-
|
|
1011
|
+
/*
|
|
1012
|
+
Without an error handler a failing hook destroys the stream, as it always did. With one,
|
|
1013
|
+
the failure is recorded on the message it belongs to and reported to the handler in
|
|
1014
|
+
#pushRecords, together with everything else which can fail while deserializing, so that
|
|
1015
|
+
the caller can skip or degrade that single message instead of losing the stream.
|
|
1016
|
+
*/
|
|
1017
|
+
const onHookSettled = error => {
|
|
1018
|
+
if (error && this.#deserializationErrorHandler) {
|
|
1019
|
+
message[kDeserializationError] ??= { error, payloadType: type };
|
|
1020
|
+
cb(null);
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
cb(error);
|
|
1024
|
+
};
|
|
1025
|
+
const result = hook(data, type, message, onHookSettled);
|
|
976
1026
|
if (typeof result?.then === 'function') {
|
|
977
|
-
result.then(() =>
|
|
1027
|
+
result.then(() => onHookSettled(null), onHookSettled);
|
|
978
1028
|
}
|
|
979
1029
|
}, requests, 0, error => {
|
|
980
1030
|
if (error) {
|
|
@@ -15,6 +15,10 @@ export declare const groupOptionsProperties: {
|
|
|
15
15
|
type: string;
|
|
16
16
|
minimum: number;
|
|
17
17
|
};
|
|
18
|
+
heartbeatStallTimeout: {
|
|
19
|
+
type: string;
|
|
20
|
+
minimum: number;
|
|
21
|
+
};
|
|
18
22
|
groupProtocol: {
|
|
19
23
|
type: string;
|
|
20
24
|
enum: ("consumer" | "classic")[];
|
|
@@ -160,6 +164,10 @@ export declare const groupOptionsSchema: {
|
|
|
160
164
|
type: string;
|
|
161
165
|
minimum: number;
|
|
162
166
|
};
|
|
167
|
+
heartbeatStallTimeout: {
|
|
168
|
+
type: string;
|
|
169
|
+
minimum: number;
|
|
170
|
+
};
|
|
163
171
|
groupProtocol: {
|
|
164
172
|
type: string;
|
|
165
173
|
enum: ("consumer" | "classic")[];
|
|
@@ -283,6 +291,10 @@ export declare const consumeOptionsSchema: {
|
|
|
283
291
|
type: string;
|
|
284
292
|
minimum: number;
|
|
285
293
|
};
|
|
294
|
+
heartbeatStallTimeout: {
|
|
295
|
+
type: string;
|
|
296
|
+
minimum: number;
|
|
297
|
+
};
|
|
286
298
|
groupProtocol: {
|
|
287
299
|
type: string;
|
|
288
300
|
enum: ("consumer" | "classic")[];
|
|
@@ -455,6 +467,10 @@ export declare const consumerOptionsSchema: {
|
|
|
455
467
|
type: string;
|
|
456
468
|
minimum: number;
|
|
457
469
|
};
|
|
470
|
+
heartbeatStallTimeout: {
|
|
471
|
+
type: string;
|
|
472
|
+
minimum: number;
|
|
473
|
+
};
|
|
458
474
|
groupProtocol: {
|
|
459
475
|
type: string;
|
|
460
476
|
enum: ("consumer" | "classic")[];
|
|
@@ -585,6 +601,10 @@ export declare const fetchOptionsSchema: {
|
|
|
585
601
|
type: string;
|
|
586
602
|
minimum: number;
|
|
587
603
|
};
|
|
604
|
+
heartbeatStallTimeout: {
|
|
605
|
+
type: string;
|
|
606
|
+
minimum: number;
|
|
607
|
+
};
|
|
588
608
|
groupProtocol: {
|
|
589
609
|
type: string;
|
|
590
610
|
enum: ("consumer" | "classic")[];
|
|
@@ -8,6 +8,7 @@ export const groupOptionsProperties = {
|
|
|
8
8
|
sessionTimeout: { type: 'number', minimum: 0 },
|
|
9
9
|
rebalanceTimeout: { type: 'number', minimum: 0 },
|
|
10
10
|
heartbeatInterval: { type: 'number', minimum: 0 },
|
|
11
|
+
heartbeatStallTimeout: { type: 'number', minimum: 0 },
|
|
11
12
|
groupProtocol: { type: 'string', enum: allowedGroupProtocols },
|
|
12
13
|
groupRemoteAssignor: { type: 'string' },
|
|
13
14
|
protocols: {
|
|
@@ -82,6 +82,7 @@ export interface GroupOptions {
|
|
|
82
82
|
sessionTimeout?: number;
|
|
83
83
|
rebalanceTimeout?: number;
|
|
84
84
|
heartbeatInterval?: number;
|
|
85
|
+
heartbeatStallTimeout?: number;
|
|
85
86
|
protocols?: GroupProtocolSubscription[];
|
|
86
87
|
protocolsMetadata?: GroupProtocolsMetadataCallback;
|
|
87
88
|
partitionAssigner?: GroupPartitionsAssigner;
|
|
@@ -92,6 +93,7 @@ export interface ConsumerGroupOptions {
|
|
|
92
93
|
groupInstanceId?: string;
|
|
93
94
|
groupRemoteAssignor?: string;
|
|
94
95
|
rebalanceTimeout?: number;
|
|
96
|
+
heartbeatStallTimeout?: number;
|
|
95
97
|
assignmentUserData?: Buffer;
|
|
96
98
|
}
|
|
97
99
|
export interface ConsumeBaseOptions<Key, Value, HeaderKey, HeaderValue> {
|
|
@@ -169,3 +171,8 @@ export interface ConsumerHeartbeatPayload {
|
|
|
169
171
|
export interface ConsumerHeartbeatErrorPayload extends ConsumerHeartbeatPayload {
|
|
170
172
|
error: Error;
|
|
171
173
|
}
|
|
174
|
+
export interface ConsumerHeartbeatStalledPayload {
|
|
175
|
+
lastError?: Error;
|
|
176
|
+
lastHeartbeat: Date | null;
|
|
177
|
+
stalledFor: number;
|
|
178
|
+
}
|
|
@@ -2,7 +2,7 @@ import { randomUUID } from 'crypto';
|
|
|
2
2
|
import { createPromisifiedCallback, kCallbackPromise, runConcurrentCallbacks } from "../../apis/callbacks.js";
|
|
3
3
|
import { FindCoordinatorKeyTypes, ProduceAcks } from "../../apis/enumerations.js";
|
|
4
4
|
import { createDiagnosticContext, producerInitIdempotentChannel, producerSendsChannel, producerTransactionsChannel } from "../../diagnostic.js";
|
|
5
|
-
import { findErrorBy, UserError } from "../../errors.js";
|
|
5
|
+
import { findErrorBy, NetworkError, UserError } from "../../errors.js";
|
|
6
6
|
import { runAsyncSeries } from "../../registries/abstract.js";
|
|
7
7
|
import { kInstance, kTransaction, kTransactionAddOffsets, kTransactionAddPartitions, kTransactionCancel, kTransactionCommitOffset, kTransactionEnd, kTransactionFindCoordinator, kTransactionPrepare } from "../../symbols.js";
|
|
8
8
|
import { emitExperimentalApiWarning, NumericMap } from "../../utils.js";
|
|
@@ -749,65 +749,71 @@ export class Producer extends Base {
|
|
|
749
749
|
});
|
|
750
750
|
}
|
|
751
751
|
#performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, repeatOnStaleMetadata, produceOptions, recoverOnProducerStateLoss, callback) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
752
|
+
const { topic, partition } = messages[0];
|
|
753
|
+
let refreshMetadata = false;
|
|
754
|
+
this[kPerformWithRetry]('produce', retryCallback => {
|
|
755
|
+
this[kMetadata]({ topics, autocreateTopics, forceUpdate: refreshMetadata }, (error, metadata) => {
|
|
756
|
+
if (error) {
|
|
757
|
+
refreshMetadata = NetworkError.isRetryable(error);
|
|
758
|
+
retryCallback(error);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
refreshMetadata = false;
|
|
762
|
+
const leader = metadata.topics.get(topic).partitions[partition].leader;
|
|
761
763
|
this[kGetConnection](metadata.brokers.get(leader), (error, connection) => {
|
|
762
764
|
if (error) {
|
|
765
|
+
refreshMetadata = NetworkError.isRetryable(error);
|
|
763
766
|
retryCallback(error);
|
|
764
767
|
return;
|
|
765
768
|
}
|
|
766
769
|
this[kGetApi]('Produce', (error, api) => {
|
|
767
770
|
if (error) {
|
|
771
|
+
refreshMetadata = NetworkError.isRetryable(error);
|
|
768
772
|
retryCallback(error);
|
|
769
773
|
return;
|
|
770
774
|
}
|
|
771
|
-
api(connection, acks, timeout, messages, produceOptions,
|
|
775
|
+
api(connection, acks, timeout, messages, produceOptions, (error, response) => {
|
|
776
|
+
refreshMetadata = NetworkError.isRetryable(error);
|
|
777
|
+
retryCallback(error, response);
|
|
778
|
+
});
|
|
772
779
|
});
|
|
773
780
|
});
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
}
|
|
785
|
-
if (recoverOnProducerStateLoss && !this.#transaction && this.#isProducerStateLost(error)) {
|
|
786
|
-
this.#recoverIdempotentProducer(messages, produceOptions, (error, recoveredProduceOptions) => {
|
|
787
|
-
if (error) {
|
|
788
|
-
callback(error);
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
this.#performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, repeatOnStaleMetadata, recoveredProduceOptions, false, callback);
|
|
792
|
-
});
|
|
793
|
-
return;
|
|
794
|
-
}
|
|
795
|
-
callback(error, results);
|
|
781
|
+
});
|
|
782
|
+
}, (error, results) => {
|
|
783
|
+
if (error) {
|
|
784
|
+
// If the last error was due to stale metadata, we retry the operation with this set of messages
|
|
785
|
+
// since the partition is already set, it should attempt on the new destination
|
|
786
|
+
/* c8 ignore next - Hard to test */
|
|
787
|
+
const hasStaleMetadata = findErrorBy(error, 'hasStaleMetadata', true);
|
|
788
|
+
if (hasStaleMetadata && repeatOnStaleMetadata) {
|
|
789
|
+
this.clearMetadata();
|
|
790
|
+
this.#performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, false, produceOptions, recoverOnProducerStateLoss, callback);
|
|
796
791
|
return;
|
|
797
792
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
793
|
+
if (recoverOnProducerStateLoss && !this.#transaction && this.#isProducerStateLost(error)) {
|
|
794
|
+
this.#recoverIdempotentProducer(messages, produceOptions, (error, recoveredProduceOptions) => {
|
|
795
|
+
if (error) {
|
|
796
|
+
callback(error);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
this.#performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, repeatOnStaleMetadata, recoveredProduceOptions, false, callback);
|
|
800
|
+
});
|
|
801
|
+
return;
|
|
803
802
|
}
|
|
804
|
-
|
|
805
|
-
|
|
803
|
+
callback(error, results);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
callback(error, results);
|
|
807
|
+
}, 0, [], error => {
|
|
808
|
+
/* c8 ignore next 3 - Hard to test */
|
|
809
|
+
if (!repeatOnStaleMetadata) {
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
return !!findErrorBy(error, 'hasStaleMetadata', true);
|
|
806
813
|
});
|
|
807
814
|
}
|
|
808
815
|
#isProducerStateLost(error) {
|
|
809
|
-
return !!(findErrorBy(error, 'apiId', 'UNKNOWN_PRODUCER_ID') ||
|
|
810
|
-
findErrorBy(error, 'apiId', 'OUT_OF_ORDER_SEQUENCE_NUMBER'));
|
|
816
|
+
return !!(findErrorBy(error, 'apiId', 'UNKNOWN_PRODUCER_ID') || findErrorBy(error, 'apiId', 'OUT_OF_ORDER_SEQUENCE_NUMBER'));
|
|
811
817
|
}
|
|
812
818
|
#recoverIdempotentProducer(messages, produceOptions, callback) {
|
|
813
819
|
this.#producerInfo = undefined;
|
package/dist/errors.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ export declare class AuthenticationError extends GenericError {
|
|
|
36
36
|
}
|
|
37
37
|
export declare class NetworkError extends GenericError {
|
|
38
38
|
static code: ErrorCode;
|
|
39
|
+
static isRetryable(error: Error | null | undefined): boolean;
|
|
39
40
|
constructor(message: string, properties?: ErrorProperties);
|
|
40
41
|
}
|
|
41
42
|
export declare class ProtocolError extends GenericError {
|
package/dist/errors.js
CHANGED
|
@@ -119,6 +119,10 @@ export class AuthenticationError extends GenericError {
|
|
|
119
119
|
}
|
|
120
120
|
export class NetworkError extends GenericError {
|
|
121
121
|
static code = 'PLT_KFK_NETWORK';
|
|
122
|
+
static isRetryable(error) {
|
|
123
|
+
const networkError = findErrorBy(error, 'code', NetworkError.code);
|
|
124
|
+
return networkError?.canRetry === true;
|
|
125
|
+
}
|
|
122
126
|
constructor(message, properties = {}) {
|
|
123
127
|
super(NetworkError.code, message, { canRetry: true, ...properties });
|
|
124
128
|
}
|
package/dist/network/utils.js
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
|
+
const BRACKETED_BROKER_RE = /^\[([^\]]+)](?::(\d*))?$/;
|
|
1
2
|
export function parseBroker(broker, defaultPort = 9092) {
|
|
2
|
-
if (typeof broker
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
return
|
|
6
|
-
}
|
|
7
|
-
else {
|
|
8
|
-
return { host: broker, port: defaultPort };
|
|
3
|
+
if (typeof broker !== 'string') {
|
|
4
|
+
const host = stripBrackets(broker.host);
|
|
5
|
+
if (host === broker.host) {
|
|
6
|
+
return broker;
|
|
9
7
|
}
|
|
8
|
+
return { ...broker, host };
|
|
9
|
+
}
|
|
10
|
+
const bracketed = BRACKETED_BROKER_RE.exec(broker);
|
|
11
|
+
if (bracketed) {
|
|
12
|
+
return { host: bracketed[1], port: parsePort(bracketed[2], defaultPort) };
|
|
13
|
+
}
|
|
14
|
+
const separator = broker.lastIndexOf(':');
|
|
15
|
+
// Bare IPv6 addresses contain multiple colons and no port. The host:port form
|
|
16
|
+
// is unambiguous only with a single colon, or with brackets around the host.
|
|
17
|
+
if (separator === -1 || broker.indexOf(':') !== separator) {
|
|
18
|
+
return { host: broker, port: defaultPort };
|
|
19
|
+
}
|
|
20
|
+
return { host: broker.slice(0, separator), port: parsePort(broker.slice(separator + 1), defaultPort) };
|
|
21
|
+
}
|
|
22
|
+
function parsePort(port, defaultPort) {
|
|
23
|
+
return port === undefined || port.length === 0 ? defaultPort : Number(port);
|
|
24
|
+
}
|
|
25
|
+
function stripBrackets(host) {
|
|
26
|
+
if (host.startsWith('[') && host.endsWith(']')) {
|
|
27
|
+
return host.slice(1, -1);
|
|
10
28
|
}
|
|
11
|
-
return
|
|
29
|
+
return host;
|
|
12
30
|
}
|
|
@@ -33,11 +33,14 @@ export interface MessageJSON<Key = unknown, Value = unknown, HeaderKey = unknown
|
|
|
33
33
|
partition: number;
|
|
34
34
|
timestamp: string;
|
|
35
35
|
offset: string;
|
|
36
|
+
leaderEpoch: number;
|
|
36
37
|
metadata: Record<string, unknown>;
|
|
37
38
|
}
|
|
38
39
|
export interface Message<Key = Buffer, Value = Buffer, HeaderKey = Buffer, HeaderValue = Buffer> extends Required<MessageBase<Key, Value>> {
|
|
39
40
|
headers: Map<HeaderKey, HeaderValue>;
|
|
41
|
+
headerEntries: Array<[HeaderKey, HeaderValue]>;
|
|
40
42
|
offset: bigint;
|
|
43
|
+
leaderEpoch: number;
|
|
41
44
|
metadata: Record<string, unknown>;
|
|
42
45
|
commit(callback?: (error?: Error) => void): void | Promise<void>;
|
|
43
46
|
toJSON(): MessageJSON<Key, Value, HeaderKey, HeaderValue>;
|
|
@@ -1,19 +1,38 @@
|
|
|
1
1
|
export function runAsyncSeries(operation, collection, index, callback) {
|
|
2
|
-
|
|
2
|
+
const length = collection.length;
|
|
3
|
+
if (length === 0 || index >= length) {
|
|
3
4
|
callback(null);
|
|
4
5
|
return;
|
|
5
6
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
while (index < length) {
|
|
8
|
+
let invocationReturned = false;
|
|
9
|
+
let cbCalled = false;
|
|
10
|
+
let cbError;
|
|
11
|
+
operation(collection[index], error => {
|
|
12
|
+
if (invocationReturned) {
|
|
13
|
+
// The callback was invoked asynchronously, so continuing recursively does not grow the current stack.
|
|
14
|
+
if (error) {
|
|
15
|
+
callback(error);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
runAsyncSeries(operation, collection, index + 1, callback);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
cbCalled = true;
|
|
22
|
+
cbError = error;
|
|
23
|
+
});
|
|
24
|
+
invocationReturned = true;
|
|
25
|
+
if (!cbCalled) {
|
|
26
|
+
// Wait for the async callback to continue the series.
|
|
9
27
|
return;
|
|
10
28
|
}
|
|
11
|
-
|
|
12
|
-
callback(
|
|
29
|
+
if (cbError) {
|
|
30
|
+
callback(cbError);
|
|
13
31
|
return;
|
|
14
32
|
}
|
|
15
|
-
|
|
16
|
-
}
|
|
33
|
+
index++;
|
|
34
|
+
}
|
|
35
|
+
callback(null);
|
|
17
36
|
}
|
|
18
37
|
/* c8 ignore start */
|
|
19
38
|
export class AbstractSchemaRegistry {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as AjvDraft04Module from 'ajv-draft-04';
|
|
2
|
+
import draft06MetaSchema from 'ajv/dist/refs/json-schema-draft-06.json' with { type: 'json' };
|
|
1
3
|
import { Ajv } from 'ajv';
|
|
2
4
|
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
3
5
|
import avro from 'avsc';
|
|
@@ -8,8 +10,7 @@ import { EMPTY_BUFFER, MultipleErrors, NetworkError, TimeoutError, UnsupportedFo
|
|
|
8
10
|
import { getCredential } from "../protocol/sasl/utils.js";
|
|
9
11
|
import { AbstractSchemaRegistry } from "./abstract.js";
|
|
10
12
|
const require = createRequire(import.meta.url);
|
|
11
|
-
const AjvDraft04 =
|
|
12
|
-
const draft06MetaSchema = require('ajv/dist/refs/json-schema-draft-06.json');
|
|
13
|
+
const AjvDraft04 = AjvDraft04Module.default;
|
|
13
14
|
const undiciGlobalDispatcherSymbol = /^undici\.globalDispatcher\.(\d+)$/;
|
|
14
15
|
export function createUndiciAgent(options) {
|
|
15
16
|
/*
|
package/dist/symbols.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export declare const kInstance: unique symbol;
|
|
|
2
2
|
export declare const kRefreshOffsetsAndFetch: unique symbol;
|
|
3
3
|
export declare const kAutocommit: unique symbol;
|
|
4
4
|
export declare const kGetFetchNode: unique symbol;
|
|
5
|
+
export declare const kDeserializationError: unique symbol;
|
|
5
6
|
export declare const kTransaction: unique symbol;
|
|
6
7
|
export declare const kTransactionPrepare: unique symbol;
|
|
7
8
|
export declare const kTransactionAddPartitions: unique symbol;
|
package/dist/symbols.js
CHANGED
|
@@ -3,6 +3,7 @@ export const kInstance = Symbol('plt.kafka.base.instance');
|
|
|
3
3
|
export const kRefreshOffsetsAndFetch = Symbol('plt.kafka.messagesStream.refreshOffsetsAndFetch');
|
|
4
4
|
export const kAutocommit = Symbol('plt.kafka.messagesStream.autocommit');
|
|
5
5
|
export const kGetFetchNode = Symbol('plt.kafka.consumer.getFetchNode');
|
|
6
|
+
export const kDeserializationError = Symbol('plt.kafka.messagesStream.deserializationError');
|
|
6
7
|
export const kTransaction = Symbol('plt.kafka.producer.transactions');
|
|
7
8
|
export const kTransactionPrepare = Symbol('plt.kafka.producer.transactions.prepare');
|
|
8
9
|
export const kTransactionAddPartitions = Symbol('plt.kafka.producer.transactions.addPartitions');
|
|
@@ -14,8 +14,48 @@ export declare const createTopicOptionsSchema: {
|
|
|
14
14
|
topics: {
|
|
15
15
|
type: string;
|
|
16
16
|
items: {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
oneOf: ({
|
|
18
|
+
type: string;
|
|
19
|
+
pattern: string;
|
|
20
|
+
} | {
|
|
21
|
+
type: string;
|
|
22
|
+
properties: {
|
|
23
|
+
topic: {
|
|
24
|
+
type: string;
|
|
25
|
+
pattern: string;
|
|
26
|
+
};
|
|
27
|
+
partitions: {
|
|
28
|
+
type: string;
|
|
29
|
+
};
|
|
30
|
+
replicas: {
|
|
31
|
+
type: string;
|
|
32
|
+
};
|
|
33
|
+
assignments: {
|
|
34
|
+
type: string;
|
|
35
|
+
items: {
|
|
36
|
+
type: string;
|
|
37
|
+
properties: {
|
|
38
|
+
partition: {
|
|
39
|
+
type: string;
|
|
40
|
+
minimum: number;
|
|
41
|
+
};
|
|
42
|
+
brokers: {
|
|
43
|
+
type: string;
|
|
44
|
+
items: {
|
|
45
|
+
type: string;
|
|
46
|
+
};
|
|
47
|
+
minItems: number;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
required: string[];
|
|
51
|
+
additionalProperties: boolean;
|
|
52
|
+
};
|
|
53
|
+
minItems: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
required: string[];
|
|
57
|
+
additionalProperties: boolean;
|
|
58
|
+
})[];
|
|
19
59
|
};
|
|
20
60
|
};
|
|
21
61
|
partitions: {
|
|
@@ -46,6 +86,22 @@ export declare const createTopicOptionsSchema: {
|
|
|
46
86
|
};
|
|
47
87
|
minItems: number;
|
|
48
88
|
};
|
|
89
|
+
configs: {
|
|
90
|
+
type: string;
|
|
91
|
+
items: {
|
|
92
|
+
type: string;
|
|
93
|
+
properties: {
|
|
94
|
+
name: {
|
|
95
|
+
type: string;
|
|
96
|
+
};
|
|
97
|
+
value: {
|
|
98
|
+
type: string[];
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
required: string[];
|
|
102
|
+
additionalProperties: boolean;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
49
105
|
};
|
|
50
106
|
required: string[];
|
|
51
107
|
additionalProperties: boolean;
|
|
@@ -42,8 +42,14 @@ export interface Group extends Omit<GroupBase, 'groupType'> {
|
|
|
42
42
|
authorizedOperations: number;
|
|
43
43
|
}
|
|
44
44
|
export interface AdminOptions extends BaseOptions {}
|
|
45
|
+
export interface CreateTopicsTopicOptions {
|
|
46
|
+
topic: string;
|
|
47
|
+
partitions?: number;
|
|
48
|
+
replicas?: number;
|
|
49
|
+
assignments?: BrokerAssignment[];
|
|
50
|
+
}
|
|
45
51
|
export interface CreateTopicsOptions {
|
|
46
|
-
topics: string[];
|
|
52
|
+
topics: (string | CreateTopicsTopicOptions)[];
|
|
47
53
|
partitions?: number;
|
|
48
54
|
replicas?: number;
|
|
49
55
|
assignments?: BrokerAssignment[];
|
|
@@ -6,7 +6,7 @@ import { Base, type BaseEvents, kCreateConnectionPool } from "../base/base";
|
|
|
6
6
|
import { type ClusterMetadata } from "../base/types";
|
|
7
7
|
import { MessagesStream } from "./messages-stream";
|
|
8
8
|
import { TopicsMap } from "./topics-map";
|
|
9
|
-
import { type CommitOptions, type ConsumeOptions, type ConsumerGroupJoinPayload, type ConsumerGroupLeavePayload, type ConsumerGroupRebalancePayload, type ConsumerHeartbeatErrorPayload, type ConsumerHeartbeatPayload, type ConsumerOptions, type FetchOptions, type GetLagOptions, type GroupAssignment, type GroupOptions, type ListCommitsOptions, type ListOffsetsOptions, type Offsets, type OffsetsWithTimestamps } from "./types";
|
|
9
|
+
import { type CommitOptions, type ConsumeOptions, type ConsumerGroupJoinPayload, type ConsumerGroupLeavePayload, type ConsumerGroupRebalancePayload, type ConsumerHeartbeatErrorPayload, type ConsumerHeartbeatPayload, type ConsumerHeartbeatStalledPayload, type ConsumerOptions, type FetchOptions, type GetLagOptions, type GroupAssignment, type GroupOptions, type ListCommitsOptions, type ListOffsetsOptions, type Offsets, type OffsetsWithTimestamps } from "./types";
|
|
10
10
|
export interface ConsumerEvents extends BaseEvents {
|
|
11
11
|
'consumer:group:join': (payload: ConsumerGroupJoinPayload) => void;
|
|
12
12
|
'consumer:group:leave': (payload: ConsumerGroupLeavePayload) => void;
|
|
@@ -16,6 +16,7 @@ export interface ConsumerEvents extends BaseEvents {
|
|
|
16
16
|
'consumer:heartbeat:cancel': (payload: ConsumerHeartbeatPayload) => void;
|
|
17
17
|
'consumer:heartbeat:end': (payload?: ConsumerHeartbeatPayload) => void;
|
|
18
18
|
'consumer:heartbeat:error': (payload: ConsumerHeartbeatErrorPayload) => void;
|
|
19
|
+
'consumer:heartbeat:stalled': (payload: ConsumerHeartbeatStalledPayload) => void;
|
|
19
20
|
'consumer:lag': (lag: Offsets) => void;
|
|
20
21
|
'consumer:lag:error': (error: Error) => void;
|
|
21
22
|
}
|