@platformatic/kafka 2.9.0 → 2.10.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/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/consumer/consumer.d.ts +2 -1
- package/dist/clients/consumer/consumer.js +94 -4
- package/dist/clients/consumer/messages-stream.js +49 -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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createPromisifiedCallback, kCallbackPromise, runConcurrentCallbacks } from "../../apis/callbacks.js";
|
|
2
2
|
import { ConfigResourceTypes, FetchIsolationLevels, FindCoordinatorKeyTypes } from "../../apis/enumerations.js";
|
|
3
3
|
import { adminAclsChannel, adminClientQuotasChannel, adminConfigsChannel, adminConsumerGroupOffsetsChannel, adminGroupsChannel, adminLogDirsChannel, adminOffsetsChannel, adminTopicsChannel, createDiagnosticContext } from "../../diagnostic.js";
|
|
4
|
-
import { findErrorBy, MultipleErrors, UserError } from "../../errors.js";
|
|
4
|
+
import { findErrorBy, MultipleErrors, NetworkError, UserError } from "../../errors.js";
|
|
5
5
|
import { Reader } from "../../protocol/reader.js";
|
|
6
6
|
import { Base, kAfterCreate, kCheckNotClosed, kConnections, kGetApi, kGetBootstrapConnection, kGetConnection, kMetadata, kOptions, kPerformDeduplicated, kPerformWithRetry, kValidateOptions } from "../base/base.js";
|
|
7
7
|
import { adminListOffsetsOptionsValidator, alterClientQuotasOptionsValidator, alterConfigsOptionsValidator, alterConsumerGroupOffsetsOptionsValidator, createAclsOptionsValidator, createPartitionsOptionsValidator, createTopicsOptionsValidator, deleteAclsOptionsValidator, deleteConsumerGroupOffsetsOptionsValidator, deleteGroupsOptionsValidator, deleteRecordsOptionsValidator, deleteTopicsOptionsValidator, describeAclsOptionsValidator, describeClientQuotasOptionsValidator, describeConfigsOptionsValidator, describeGroupsOptionsValidator, describeLogDirsOptionsValidator, findCoordinatorOptionsValidator, incrementalAlterConfigsOptionsValidator, listConsumerGroupOffsetsOptionsValidator, listGroupsOptionsValidator, listTopicsOptionsValidator, removeMembersFromConsumerGroupOptionsValidator } from "./options.js";
|
|
@@ -366,7 +366,12 @@ export class Admin extends Base {
|
|
|
366
366
|
}
|
|
367
367
|
#getControllerConnection(callback) {
|
|
368
368
|
if (this.#controller) {
|
|
369
|
-
this[kConnections].get(this.#controller,
|
|
369
|
+
this[kConnections].get(this.#controller, (error, connection) => {
|
|
370
|
+
if (NetworkError.isRetryable(error)) {
|
|
371
|
+
this.#controller = null;
|
|
372
|
+
}
|
|
373
|
+
callback(error, connection);
|
|
374
|
+
});
|
|
370
375
|
}
|
|
371
376
|
else {
|
|
372
377
|
this[kGetBootstrapConnection](callback);
|
|
@@ -422,26 +427,34 @@ export class Admin extends Base {
|
|
|
422
427
|
}, 0);
|
|
423
428
|
}, callback);
|
|
424
429
|
}
|
|
430
|
+
#mapTopicAssignments(assignments) {
|
|
431
|
+
const mapped = [];
|
|
432
|
+
for (const { partition, brokers } of assignments ?? []) {
|
|
433
|
+
mapped.push({ partitionIndex: partition, brokerIds: brokers });
|
|
434
|
+
}
|
|
435
|
+
return mapped;
|
|
436
|
+
}
|
|
425
437
|
#createTopics(options, callback) {
|
|
426
438
|
// -1 is required if manual assignments are used. If no manual assignments are used, -1 will default to broker settings.
|
|
427
439
|
const numPartitions = options.partitions ?? -1;
|
|
428
440
|
const replicationFactor = options.replicas ?? -1;
|
|
429
|
-
const assignments =
|
|
441
|
+
const assignments = this.#mapTopicAssignments(options.assignments);
|
|
430
442
|
const configs = options.configs ?? [];
|
|
431
|
-
for (const { partition, brokers } of options.assignments ?? []) {
|
|
432
|
-
assignments.push({ partitionIndex: partition, brokerIds: brokers });
|
|
433
|
-
}
|
|
434
443
|
const requests = [];
|
|
435
444
|
for (const topic of options.topics) {
|
|
445
|
+
if (typeof topic === 'string') {
|
|
446
|
+
requests.push({ name: topic, numPartitions, replicationFactor, assignments, configs });
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
436
449
|
requests.push({
|
|
437
|
-
name: topic,
|
|
438
|
-
numPartitions,
|
|
439
|
-
replicationFactor,
|
|
440
|
-
assignments,
|
|
450
|
+
name: topic.topic,
|
|
451
|
+
numPartitions: topic.partitions ?? numPartitions,
|
|
452
|
+
replicationFactor: topic.replicas ?? replicationFactor,
|
|
453
|
+
assignments: topic.assignments ? this.#mapTopicAssignments(topic.assignments) : assignments,
|
|
441
454
|
configs
|
|
442
455
|
});
|
|
443
456
|
}
|
|
444
|
-
this[kPerformDeduplicated](`createTopics-${options.topics.join(',')}`, deduplicateCallback => {
|
|
457
|
+
this[kPerformDeduplicated](`createTopics-${options.topics.map(topic => (typeof topic === 'string' ? topic : topic.topic)).join(',')}`, deduplicateCallback => {
|
|
445
458
|
this[kPerformWithRetry]('createTopics', retryCallback => {
|
|
446
459
|
this.#getControllerConnection((error, connection) => {
|
|
447
460
|
if (error) {
|
|
@@ -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;
|
|
@@ -8,24 +8,55 @@ export const groupsProperties = {
|
|
|
8
8
|
minItems: 1
|
|
9
9
|
}
|
|
10
10
|
};
|
|
11
|
+
const createTopicAssignmentsSchema = {
|
|
12
|
+
type: 'array',
|
|
13
|
+
items: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
partition: { type: 'number', minimum: 0 },
|
|
17
|
+
brokers: { type: 'array', items: { type: 'number' }, minItems: 1 }
|
|
18
|
+
},
|
|
19
|
+
required: ['partition', 'brokers'],
|
|
20
|
+
additionalProperties: false
|
|
21
|
+
},
|
|
22
|
+
minItems: 1
|
|
23
|
+
};
|
|
11
24
|
export const createTopicOptionsSchema = {
|
|
12
25
|
type: 'object',
|
|
13
26
|
properties: {
|
|
14
|
-
topics: {
|
|
27
|
+
topics: {
|
|
28
|
+
type: 'array',
|
|
29
|
+
items: {
|
|
30
|
+
oneOf: [
|
|
31
|
+
idProperty,
|
|
32
|
+
{
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
topic: idProperty,
|
|
36
|
+
partitions: { type: 'number' },
|
|
37
|
+
replicas: { type: 'number' },
|
|
38
|
+
assignments: createTopicAssignmentsSchema
|
|
39
|
+
},
|
|
40
|
+
required: ['topic'],
|
|
41
|
+
additionalProperties: false
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
},
|
|
15
46
|
partitions: { type: 'number' },
|
|
16
47
|
replicas: { type: 'number' },
|
|
17
|
-
assignments:
|
|
48
|
+
assignments: createTopicAssignmentsSchema,
|
|
49
|
+
configs: {
|
|
18
50
|
type: 'array',
|
|
19
51
|
items: {
|
|
20
52
|
type: 'object',
|
|
21
53
|
properties: {
|
|
22
|
-
|
|
23
|
-
|
|
54
|
+
name: { type: 'string' },
|
|
55
|
+
value: { type: ['string', 'null'] }
|
|
24
56
|
},
|
|
25
|
-
required: ['
|
|
57
|
+
required: ['name'],
|
|
26
58
|
additionalProperties: false
|
|
27
|
-
}
|
|
28
|
-
minItems: 1
|
|
59
|
+
}
|
|
29
60
|
}
|
|
30
61
|
},
|
|
31
62
|
required: ['topics'],
|
|
@@ -43,8 +43,14 @@ export interface Group extends Omit<GroupBase, 'groupType'> {
|
|
|
43
43
|
}
|
|
44
44
|
export interface AdminOptions extends BaseOptions {
|
|
45
45
|
}
|
|
46
|
+
export interface CreateTopicsTopicOptions {
|
|
47
|
+
topic: string;
|
|
48
|
+
partitions?: number;
|
|
49
|
+
replicas?: number;
|
|
50
|
+
assignments?: BrokerAssignment[];
|
|
51
|
+
}
|
|
46
52
|
export interface CreateTopicsOptions {
|
|
47
|
-
topics: string[];
|
|
53
|
+
topics: (string | CreateTopicsTopicOptions)[];
|
|
48
54
|
partitions?: number;
|
|
49
55
|
replicas?: number;
|
|
50
56
|
assignments?: BrokerAssignment[];
|
|
@@ -6,7 +6,7 @@ import { Base, type BaseEvents, kCreateConnectionPool } from '../base/base.ts';
|
|
|
6
6
|
import { type ClusterMetadata } from '../base/types.ts';
|
|
7
7
|
import { MessagesStream } from './messages-stream.ts';
|
|
8
8
|
import { TopicsMap } from './topics-map.ts';
|
|
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.ts';
|
|
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.ts';
|
|
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
|
}
|
|
@@ -32,6 +32,10 @@ export class Consumer extends Base {
|
|
|
32
32
|
#heartbeatInterval;
|
|
33
33
|
#lastHeartbeatIntervalMs;
|
|
34
34
|
#lastHeartbeat;
|
|
35
|
+
#heartbeatStallTimer;
|
|
36
|
+
#heartbeatStallStart;
|
|
37
|
+
#heartbeatStalled;
|
|
38
|
+
#heartbeatStallError;
|
|
35
39
|
#useConsumerGroupProtocol;
|
|
36
40
|
#memberEpoch;
|
|
37
41
|
#groupRemoteAssignor;
|
|
@@ -77,6 +81,10 @@ export class Consumer extends Base {
|
|
|
77
81
|
this.#heartbeatInterval = null;
|
|
78
82
|
this.#lastHeartbeatIntervalMs = 0;
|
|
79
83
|
this.#lastHeartbeat = null;
|
|
84
|
+
this.#heartbeatStallTimer = null;
|
|
85
|
+
this.#heartbeatStallStart = null;
|
|
86
|
+
this.#heartbeatStalled = false;
|
|
87
|
+
this.#heartbeatStallError = undefined;
|
|
80
88
|
this.#streams = new Set();
|
|
81
89
|
this.#lagMonitoring = null;
|
|
82
90
|
this.#memberEpoch = 0;
|
|
@@ -121,6 +129,7 @@ export class Consumer extends Base {
|
|
|
121
129
|
}
|
|
122
130
|
this[kClosed] = true;
|
|
123
131
|
clearTimeout(this.#lagMonitoring);
|
|
132
|
+
this.#resetHeartbeatStall();
|
|
124
133
|
let closer;
|
|
125
134
|
if (this.#useConsumerGroupProtocol) {
|
|
126
135
|
closer = this.#leaveGroupConsumerProtocol.bind(this);
|
|
@@ -424,6 +433,7 @@ export class Consumer extends Base {
|
|
|
424
433
|
return callback[kCallbackPromise];
|
|
425
434
|
}
|
|
426
435
|
this.#membershipActive = false;
|
|
436
|
+
this.#resetHeartbeatStall();
|
|
427
437
|
this.#leaveGroupClassicProtocol(force, error => {
|
|
428
438
|
if (error) {
|
|
429
439
|
this.#membershipActive = true;
|
|
@@ -486,12 +496,15 @@ export class Consumer extends Base {
|
|
|
486
496
|
}
|
|
487
497
|
#fetch(options, callback) {
|
|
488
498
|
const isolationLevel = options.isolationLevel ?? this[kOptions].isolationLevel;
|
|
499
|
+
let refreshMetadata = false;
|
|
489
500
|
this[kPerformWithRetry]('fetch', retryCallback => {
|
|
490
|
-
this[kMetadata]({ topics: this.topics.current }, (error, metadata) => {
|
|
501
|
+
this[kMetadata]({ topics: this.topics.current, forceUpdate: refreshMetadata }, (error, metadata) => {
|
|
491
502
|
if (error) {
|
|
503
|
+
refreshMetadata = NetworkError.isRetryable(error);
|
|
492
504
|
retryCallback(error);
|
|
493
505
|
return;
|
|
494
506
|
}
|
|
507
|
+
refreshMetadata = false;
|
|
495
508
|
const topicIds = this.#topicIdsById(metadata);
|
|
496
509
|
const node = this.#fetchNodeForRequest(metadata, options.node, options.topics, topicIds, Date.now());
|
|
497
510
|
const broker = metadata.brokers.get(node);
|
|
@@ -502,6 +515,7 @@ export class Consumer extends Base {
|
|
|
502
515
|
const pool = options.connectionPool ?? this[kConnections];
|
|
503
516
|
pool.get(broker, (error, connection) => {
|
|
504
517
|
if (error) {
|
|
518
|
+
refreshMetadata = NetworkError.isRetryable(error);
|
|
505
519
|
this.#clearPreferredReadReplicas(options.topics, topicIds);
|
|
506
520
|
this.#fetchSessions.delete(node);
|
|
507
521
|
// When a connection was not available (either interrupted or not available) we
|
|
@@ -517,6 +531,7 @@ export class Consumer extends Base {
|
|
|
517
531
|
}
|
|
518
532
|
this[kGetApi]('Fetch', (error, api) => {
|
|
519
533
|
if (error) {
|
|
534
|
+
refreshMetadata = NetworkError.isRetryable(error);
|
|
520
535
|
retryCallback(error);
|
|
521
536
|
return;
|
|
522
537
|
}
|
|
@@ -579,6 +594,7 @@ export class Consumer extends Base {
|
|
|
579
594
|
}
|
|
580
595
|
api(connection, options.maxWaitTime ?? this[kOptions].maxWaitTime, options.minBytes ?? this[kOptions].minBytes, options.maxBytes ?? this[kOptions].maxBytes, isolationLevel, sessionId, sessionEpoch, requestTopics, forgottenTopicsData, this.#clientRack, (error, result) => {
|
|
581
596
|
if (error) {
|
|
597
|
+
refreshMetadata = NetworkError.isRetryable(error);
|
|
582
598
|
this.#clearPreferredReadReplicas(options.topics, topicIds);
|
|
583
599
|
if (findErrorBy(error, 'apiId', 'FETCH_SESSION_ID_NOT_FOUND') ||
|
|
584
600
|
findErrorBy(error, 'apiId', 'INVALID_FETCH_SESSION_EPOCH') ||
|
|
@@ -668,12 +684,12 @@ export class Consumer extends Base {
|
|
|
668
684
|
callback(error);
|
|
669
685
|
});
|
|
670
686
|
}
|
|
671
|
-
#listOffsets(withTimestamps, options, callback) {
|
|
687
|
+
#listOffsets(withTimestamps, options, callback, forceUpdateMetadata = false) {
|
|
672
688
|
let topics = options.topics;
|
|
673
689
|
if (!topics || topics.length === 0) {
|
|
674
690
|
topics = this.topics.current;
|
|
675
691
|
}
|
|
676
|
-
this[kMetadata]({ topics }, (error, metadata) => {
|
|
692
|
+
this[kMetadata]({ topics, forceUpdate: forceUpdateMetadata }, (error, metadata) => {
|
|
677
693
|
if (error) {
|
|
678
694
|
callback(error);
|
|
679
695
|
return;
|
|
@@ -728,6 +744,11 @@ export class Consumer extends Base {
|
|
|
728
744
|
}, concurrentCallback, 0);
|
|
729
745
|
}, (error, responses) => {
|
|
730
746
|
if (error) {
|
|
747
|
+
if (!forceUpdateMetadata && NetworkError.isRetryable(error)) {
|
|
748
|
+
this.clearMetadata();
|
|
749
|
+
this.#listOffsets(withTimestamps, options, callback, true);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
731
752
|
callback(this.#handleError(error));
|
|
732
753
|
return;
|
|
733
754
|
}
|
|
@@ -842,6 +863,7 @@ export class Consumer extends Base {
|
|
|
842
863
|
return;
|
|
843
864
|
}
|
|
844
865
|
this.emitWithDebug('consumer:heartbeat', 'start', eventPayload);
|
|
866
|
+
this.#trackHeartbeatStall(options);
|
|
845
867
|
this[kGetApi]('Heartbeat', (error, api) => {
|
|
846
868
|
if (error) {
|
|
847
869
|
groupCallback(error);
|
|
@@ -858,21 +880,43 @@ export class Consumer extends Base {
|
|
|
858
880
|
if (error) {
|
|
859
881
|
this.#cancelHeartbeat();
|
|
860
882
|
if (this.#getRejoinError(error)) {
|
|
883
|
+
// A rejoin can legitimately block up to rebalanceTimeout:
|
|
884
|
+
// - extend the stall window by that bound rather than pausing it
|
|
885
|
+
// - a rejoin that never completes still surfaces as a stall
|
|
886
|
+
this.#resetHeartbeatStall();
|
|
887
|
+
this.#trackHeartbeatStall(options, error, options.rebalanceTimeout + this.#heartbeatStallTimeoutFor(options));
|
|
861
888
|
this[kPerformWithRetry]('rejoinGroup', retryCallback => {
|
|
862
889
|
this.#joinGroup(options, retryCallback);
|
|
863
890
|
}, error => {
|
|
864
891
|
if (error) {
|
|
865
892
|
this.emitWithDebug(null, 'error', error);
|
|
893
|
+
if (!this.#heartbeatStalled) {
|
|
894
|
+
// No more heartbeats will be scheduled: restart the stall window without
|
|
895
|
+
// the rejoin grace, keeping the triggering heartbeat error as lastError.
|
|
896
|
+
const heartbeatError = this.#heartbeatStallError;
|
|
897
|
+
this.#resetHeartbeatStall();
|
|
898
|
+
this.#trackHeartbeatStall(options, heartbeatError);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
// Rejoin succeeded: drop the extended window; the next heartbeat opens the normal one.
|
|
903
|
+
this.#resetHeartbeatStall();
|
|
866
904
|
}
|
|
867
905
|
this.emitWithDebug('consumer', 'rejoin');
|
|
868
906
|
}, 0);
|
|
869
907
|
return;
|
|
870
908
|
}
|
|
909
|
+
// The error was not group related, so we schedule another heartbeat.
|
|
910
|
+
// Do this before notifying listeners so they can still cancel it.
|
|
911
|
+
this.#heartbeatInterval = setTimeout(() => {
|
|
912
|
+
this.#heartbeat(options);
|
|
913
|
+
}, options.heartbeatInterval);
|
|
871
914
|
this.emitWithDebug('consumer:heartbeat', 'error', { ...eventPayload, error });
|
|
872
|
-
|
|
915
|
+
this.#trackHeartbeatStall(options, error);
|
|
873
916
|
}
|
|
874
917
|
else {
|
|
875
918
|
this.#lastHeartbeat = new Date();
|
|
919
|
+
this.#resetHeartbeatStall();
|
|
876
920
|
this.emitWithDebug('consumer:heartbeat', 'end', eventPayload);
|
|
877
921
|
}
|
|
878
922
|
this.#heartbeatInterval?.refresh();
|
|
@@ -882,6 +926,47 @@ export class Consumer extends Base {
|
|
|
882
926
|
clearTimeout(this.#heartbeatInterval);
|
|
883
927
|
this.#heartbeatInterval = null;
|
|
884
928
|
}
|
|
929
|
+
#heartbeatStallTimeoutFor(options) {
|
|
930
|
+
return (options.heartbeatStallTimeout ??
|
|
931
|
+
this[kOptions].heartbeatStallTimeout ??
|
|
932
|
+
options.rebalanceTimeout ??
|
|
933
|
+
this[kOptions].rebalanceTimeout ??
|
|
934
|
+
defaultConsumerOptions.rebalanceTimeout);
|
|
935
|
+
}
|
|
936
|
+
// Opens the stall window on the first heartbeat attempt after the last success.
|
|
937
|
+
// The window survives retries and only #resetHeartbeatStall closes it.
|
|
938
|
+
#trackHeartbeatStall(options, error, timeout) {
|
|
939
|
+
if (error) {
|
|
940
|
+
this.#heartbeatStallError = error;
|
|
941
|
+
}
|
|
942
|
+
if (this.#heartbeatStalled || this.#heartbeatStallTimer) {
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
timeout ??= this.#heartbeatStallTimeoutFor(options);
|
|
946
|
+
this.#heartbeatStallStart = Date.now();
|
|
947
|
+
this.#heartbeatStallTimer = setTimeout(() => {
|
|
948
|
+
this.#heartbeatStallTimer = null;
|
|
949
|
+
// Emit once per stall episode. The next successful heartbeat re-arms the tracking.
|
|
950
|
+
this.#heartbeatStalled = true;
|
|
951
|
+
if (this[kClosed] || !this.#membershipActive) {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
this.emitWithDebug('consumer:heartbeat', 'stalled', {
|
|
955
|
+
lastError: this.#heartbeatStallError,
|
|
956
|
+
lastHeartbeat: this.#lastHeartbeat,
|
|
957
|
+
stalledFor: Date.now() - this.#heartbeatStallStart
|
|
958
|
+
});
|
|
959
|
+
}, timeout);
|
|
960
|
+
}
|
|
961
|
+
#resetHeartbeatStall() {
|
|
962
|
+
if (this.#heartbeatStallTimer) {
|
|
963
|
+
clearTimeout(this.#heartbeatStallTimer);
|
|
964
|
+
this.#heartbeatStallTimer = null;
|
|
965
|
+
}
|
|
966
|
+
this.#heartbeatStallStart = null;
|
|
967
|
+
this.#heartbeatStalled = false;
|
|
968
|
+
this.#heartbeatStallError = undefined;
|
|
969
|
+
}
|
|
885
970
|
#consumerGroupHeartbeat(options, callback) {
|
|
886
971
|
options.rebalanceTimeout ??= this[kOptions].rebalanceTimeout;
|
|
887
972
|
consumerHeartbeatChannel.traceCallback(this.#performConsumerGroupHeartbeat, 1, createDiagnosticContext({ client: this, operation: 'consumerGroupHeartbeat' }), this, options, callback);
|
|
@@ -889,6 +974,7 @@ export class Consumer extends Base {
|
|
|
889
974
|
#performConsumerGroupHeartbeat(options, callback) {
|
|
890
975
|
this.#performGroupOperation('consumerGroupHeartbeat', (connection, groupCallback) => {
|
|
891
976
|
this.emitWithDebug('consumer:heartbeat', 'start');
|
|
977
|
+
this.#trackHeartbeatStall(options);
|
|
892
978
|
this[kGetApi]('ConsumerGroupHeartbeat', (error, api) => {
|
|
893
979
|
if (error) {
|
|
894
980
|
groupCallback(error);
|
|
@@ -899,6 +985,7 @@ export class Consumer extends Base {
|
|
|
899
985
|
});
|
|
900
986
|
}, (error, response) => {
|
|
901
987
|
if (this[kClosed]) {
|
|
988
|
+
this.#resetHeartbeatStall();
|
|
902
989
|
this.emitWithDebug('consumer:heartbeat', 'end');
|
|
903
990
|
callback(null);
|
|
904
991
|
return;
|
|
@@ -906,6 +993,7 @@ export class Consumer extends Base {
|
|
|
906
993
|
if (error) {
|
|
907
994
|
this.#cancelHeartbeat();
|
|
908
995
|
this.emitWithDebug('consumer:heartbeat', 'error', { error });
|
|
996
|
+
this.#trackHeartbeatStall(options, error);
|
|
909
997
|
const fenced = error.response?.errorCode === protocolErrors.FENCED_MEMBER_EPOCH.code;
|
|
910
998
|
if (fenced) {
|
|
911
999
|
this.#assignments = [];
|
|
@@ -923,6 +1011,7 @@ export class Consumer extends Base {
|
|
|
923
1011
|
return;
|
|
924
1012
|
}
|
|
925
1013
|
this.#lastHeartbeat = new Date();
|
|
1014
|
+
this.#resetHeartbeatStall();
|
|
926
1015
|
this.#memberEpoch = response.memberEpoch;
|
|
927
1016
|
if (response.memberId) {
|
|
928
1017
|
const changed = this.memberId !== response.memberId;
|
|
@@ -972,6 +1061,7 @@ export class Consumer extends Base {
|
|
|
972
1061
|
if (toRevoke.length === 0) {
|
|
973
1062
|
return;
|
|
974
1063
|
}
|
|
1064
|
+
this.emitWithDebug('consumer', 'group:rebalance', { groupId: this.groupId });
|
|
975
1065
|
for (const stream of this.#streams) {
|
|
976
1066
|
stream.pause();
|
|
977
1067
|
stream[kAutocommit]();
|
|
@@ -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
|
|
@@ -831,7 +854,10 @@ export class MessagesStream extends Readable {
|
|
|
831
854
|
topics.push(assignment);
|
|
832
855
|
}
|
|
833
856
|
if (!topics.length) {
|
|
834
|
-
|
|
857
|
+
// No assignment yet: do not seed fallback offsets for the whole topic.
|
|
858
|
+
// consumer:group:join will refresh once partitions are assigned.
|
|
859
|
+
this.emit('offsets');
|
|
860
|
+
callback(null);
|
|
835
861
|
return;
|
|
836
862
|
}
|
|
837
863
|
this.#consumer.listCommittedOffsets({ topics }, (error, commits) => {
|
|
@@ -858,6 +884,7 @@ export class MessagesStream extends Readable {
|
|
|
858
884
|
this.#refreshOffsetsInflight = true;
|
|
859
885
|
this.#refreshOffsets(error => {
|
|
860
886
|
const shouldDestroyOnError = this.#refreshOffsetsDestroyOnError;
|
|
887
|
+
const wasPending = this.#refreshOffsetsPending;
|
|
861
888
|
this.#refreshOffsetsInflight = false;
|
|
862
889
|
this.#refreshOffsetsDestroyOnError = false;
|
|
863
890
|
this.#refreshOffsetsPending = false;
|
|
@@ -869,8 +896,7 @@ export class MessagesStream extends Readable {
|
|
|
869
896
|
return;
|
|
870
897
|
}
|
|
871
898
|
// A new one was scheduled while the previous one was inflight, we need to run it immediately
|
|
872
|
-
|
|
873
|
-
if (this.#refreshOffsetsPending) {
|
|
899
|
+
if (wasPending) {
|
|
874
900
|
this.#scheduleRefreshOffsetsAndFetch(shouldDestroyOnError);
|
|
875
901
|
return;
|
|
876
902
|
}
|
|
@@ -972,9 +998,23 @@ export class MessagesStream extends Readable {
|
|
|
972
998
|
}
|
|
973
999
|
runAsyncSeries((request, cb) => {
|
|
974
1000
|
const [data, type, message] = request;
|
|
975
|
-
|
|
1001
|
+
/*
|
|
1002
|
+
Without an error handler a failing hook destroys the stream, as it always did. With one,
|
|
1003
|
+
the failure is recorded on the message it belongs to and reported to the handler in
|
|
1004
|
+
#pushRecords, together with everything else which can fail while deserializing, so that
|
|
1005
|
+
the caller can skip or degrade that single message instead of losing the stream.
|
|
1006
|
+
*/
|
|
1007
|
+
const onHookSettled = error => {
|
|
1008
|
+
if (error && this.#deserializationErrorHandler) {
|
|
1009
|
+
message[kDeserializationError] ??= { error, payloadType: type };
|
|
1010
|
+
cb(null);
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
cb(error);
|
|
1014
|
+
};
|
|
1015
|
+
const result = hook(data, type, message, onHookSettled);
|
|
976
1016
|
if (typeof result?.then === 'function') {
|
|
977
|
-
result.then(() =>
|
|
1017
|
+
result.then(() => onHookSettled(null), onHookSettled);
|
|
978
1018
|
}
|
|
979
1019
|
}, requests, 0, error => {
|
|
980
1020
|
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";
|
|
@@ -744,65 +744,71 @@ export class Producer extends Base {
|
|
|
744
744
|
});
|
|
745
745
|
}
|
|
746
746
|
#performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, repeatOnStaleMetadata, produceOptions, recoverOnProducerStateLoss, callback) {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
747
|
+
const { topic, partition } = messages[0];
|
|
748
|
+
let refreshMetadata = false;
|
|
749
|
+
this[kPerformWithRetry]('produce', retryCallback => {
|
|
750
|
+
this[kMetadata]({ topics, autocreateTopics, forceUpdate: refreshMetadata }, (error, metadata) => {
|
|
751
|
+
if (error) {
|
|
752
|
+
refreshMetadata = NetworkError.isRetryable(error);
|
|
753
|
+
retryCallback(error);
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
refreshMetadata = false;
|
|
757
|
+
const leader = metadata.topics.get(topic).partitions[partition].leader;
|
|
756
758
|
this[kGetConnection](metadata.brokers.get(leader), (error, connection) => {
|
|
757
759
|
if (error) {
|
|
760
|
+
refreshMetadata = NetworkError.isRetryable(error);
|
|
758
761
|
retryCallback(error);
|
|
759
762
|
return;
|
|
760
763
|
}
|
|
761
764
|
this[kGetApi]('Produce', (error, api) => {
|
|
762
765
|
if (error) {
|
|
766
|
+
refreshMetadata = NetworkError.isRetryable(error);
|
|
763
767
|
retryCallback(error);
|
|
764
768
|
return;
|
|
765
769
|
}
|
|
766
|
-
api(connection, acks, timeout, messages, produceOptions,
|
|
770
|
+
api(connection, acks, timeout, messages, produceOptions, (error, response) => {
|
|
771
|
+
refreshMetadata = NetworkError.isRetryable(error);
|
|
772
|
+
retryCallback(error, response);
|
|
773
|
+
});
|
|
767
774
|
});
|
|
768
775
|
});
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}
|
|
780
|
-
if (recoverOnProducerStateLoss && !this.#transaction && this.#isProducerStateLost(error)) {
|
|
781
|
-
this.#recoverIdempotentProducer(messages, produceOptions, (error, recoveredProduceOptions) => {
|
|
782
|
-
if (error) {
|
|
783
|
-
callback(error);
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
this.#performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, repeatOnStaleMetadata, recoveredProduceOptions, false, callback);
|
|
787
|
-
});
|
|
788
|
-
return;
|
|
789
|
-
}
|
|
790
|
-
callback(error, results);
|
|
776
|
+
});
|
|
777
|
+
}, (error, results) => {
|
|
778
|
+
if (error) {
|
|
779
|
+
// If the last error was due to stale metadata, we retry the operation with this set of messages
|
|
780
|
+
// since the partition is already set, it should attempt on the new destination
|
|
781
|
+
/* c8 ignore next - Hard to test */
|
|
782
|
+
const hasStaleMetadata = findErrorBy(error, 'hasStaleMetadata', true);
|
|
783
|
+
if (hasStaleMetadata && repeatOnStaleMetadata) {
|
|
784
|
+
this.clearMetadata();
|
|
785
|
+
this.#performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, false, produceOptions, recoverOnProducerStateLoss, callback);
|
|
791
786
|
return;
|
|
792
787
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
788
|
+
if (recoverOnProducerStateLoss && !this.#transaction && this.#isProducerStateLost(error)) {
|
|
789
|
+
this.#recoverIdempotentProducer(messages, produceOptions, (error, recoveredProduceOptions) => {
|
|
790
|
+
if (error) {
|
|
791
|
+
callback(error);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
this.#performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, repeatOnStaleMetadata, recoveredProduceOptions, false, callback);
|
|
795
|
+
});
|
|
796
|
+
return;
|
|
798
797
|
}
|
|
799
|
-
|
|
800
|
-
|
|
798
|
+
callback(error, results);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
callback(error, results);
|
|
802
|
+
}, 0, [], error => {
|
|
803
|
+
/* c8 ignore next 3 - Hard to test */
|
|
804
|
+
if (!repeatOnStaleMetadata) {
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
return !!findErrorBy(error, 'hasStaleMetadata', true);
|
|
801
808
|
});
|
|
802
809
|
}
|
|
803
810
|
#isProducerStateLost(error) {
|
|
804
|
-
return !!(findErrorBy(error, 'apiId', 'UNKNOWN_PRODUCER_ID') ||
|
|
805
|
-
findErrorBy(error, 'apiId', 'OUT_OF_ORDER_SEQUENCE_NUMBER'));
|
|
811
|
+
return !!(findErrorBy(error, 'apiId', 'UNKNOWN_PRODUCER_ID') || findErrorBy(error, 'apiId', 'OUT_OF_ORDER_SEQUENCE_NUMBER'));
|
|
806
812
|
}
|
|
807
813
|
#recoverIdempotentProducer(messages, produceOptions, callback) {
|
|
808
814
|
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
|
}
|
|
@@ -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")[];
|
|
@@ -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> {
|
|
@@ -168,4 +170,9 @@ export interface ConsumerHeartbeatPayload {
|
|
|
168
170
|
}
|
|
169
171
|
export interface ConsumerHeartbeatErrorPayload extends ConsumerHeartbeatPayload {
|
|
170
172
|
error: Error;
|
|
173
|
+
}
|
|
174
|
+
export interface ConsumerHeartbeatStalledPayload {
|
|
175
|
+
lastError?: Error;
|
|
176
|
+
lastHeartbeat: Date | null;
|
|
177
|
+
stalledFor: number;
|
|
171
178
|
}
|
|
@@ -42,6 +42,7 @@ export declare class AuthenticationError extends GenericError {
|
|
|
42
42
|
}
|
|
43
43
|
export declare class NetworkError extends GenericError {
|
|
44
44
|
static code: ErrorCode;
|
|
45
|
+
static isRetryable(error: Error | null | undefined): boolean;
|
|
45
46
|
constructor(message: string, properties?: ErrorProperties);
|
|
46
47
|
}
|
|
47
48
|
export declare class ProtocolError extends GenericError {
|
|
@@ -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>;
|
|
@@ -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/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const name = "@platformatic/kafka";
|
|
2
|
-
export const version = "2.
|
|
2
|
+
export const version = "2.10.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/kafka",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "Modern and performant client for Apache Kafka",
|
|
5
5
|
"homepage": "https://github.com/platformatic/kafka",
|
|
6
6
|
"author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",
|