@platformatic/kafka 2.12.0 → 2.13.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 +27 -22
- package/dist/clients/admin/types.d.ts +1 -0
- package/dist/clients/base/options.d.ts +13 -0
- package/dist/clients/base/options.js +3 -0
- package/dist/clients/consumer/consumer-protocol.js +42 -29
- package/dist/clients/consumer/consumer.d.ts +1 -1
- package/dist/clients/consumer/consumer.js +41 -20
- package/dist/clients/consumer/messages-stream.js +43 -10
- package/dist/clients/consumer/options.d.ts +15 -0
- package/dist/clients/consumer/options.js +3 -2
- package/dist/clients/consumer/types.d.ts +3 -1
- package/dist/clients/producer/producer.js +1 -1
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +28 -21
- package/dist/network/connection.d.ts +3 -0
- package/dist/network/connection.js +35 -3
- package/dist/typescript-4/dist/clients/admin/types.d.ts +1 -0
- package/dist/typescript-4/dist/clients/base/options.d.ts +13 -0
- package/dist/typescript-4/dist/clients/consumer/consumer.d.ts +1 -1
- package/dist/typescript-4/dist/clients/consumer/options.d.ts +15 -0
- package/dist/typescript-4/dist/clients/consumer/types.d.ts +3 -1
- package/dist/typescript-4/dist/errors.d.ts +1 -0
- package/dist/typescript-4/dist/network/connection.d.ts +3 -0
- package/dist/version.js +1 -1
- package/package.json +3 -2
|
@@ -648,29 +648,34 @@ export class Admin extends Base {
|
|
|
648
648
|
members: new Map(),
|
|
649
649
|
authorizedOperations: raw.authorizedOperations
|
|
650
650
|
};
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
651
|
+
try {
|
|
652
|
+
for (const member of raw.members) {
|
|
653
|
+
let memberMetadata;
|
|
654
|
+
let memberAssignments;
|
|
655
|
+
if (raw.protocolType === 'consumer' && member.memberMetadata.length > 0) {
|
|
656
|
+
const subscription = decodeConsumerProtocolSubscription(member.memberMetadata);
|
|
657
|
+
memberMetadata = {
|
|
658
|
+
version: subscription.version,
|
|
659
|
+
topics: subscription.topics,
|
|
660
|
+
metadata: subscription.userData,
|
|
661
|
+
ownedPartitions: subscription.ownedPartitions,
|
|
662
|
+
generationId: subscription.generationId,
|
|
663
|
+
rackId: subscription.rackId
|
|
664
|
+
};
|
|
665
|
+
memberAssignments = new Map(decodeConsumerProtocolAssignment(member.memberAssignment).assignedPartitions.map(assignment => [assignment.topic, assignment]));
|
|
666
|
+
}
|
|
667
|
+
group.members.set(member.memberId, {
|
|
668
|
+
id: member.memberId,
|
|
669
|
+
groupInstanceId: member.groupInstanceId,
|
|
670
|
+
clientId: member.clientId,
|
|
671
|
+
clientHost: member.clientHost,
|
|
672
|
+
metadata: memberMetadata,
|
|
673
|
+
assignments: memberAssignments
|
|
674
|
+
});
|
|
665
675
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
clientId: member.clientId,
|
|
670
|
-
clientHost: member.clientHost,
|
|
671
|
-
metadata: memberMetadata,
|
|
672
|
-
assignments: memberAssignments
|
|
673
|
-
});
|
|
676
|
+
}
|
|
677
|
+
catch (e) {
|
|
678
|
+
group.error = e;
|
|
674
679
|
}
|
|
675
680
|
groups.set(group.id, group);
|
|
676
681
|
}
|
|
@@ -108,6 +108,19 @@ export declare const baseOptionsSchema: {
|
|
|
108
108
|
type: string;
|
|
109
109
|
enum: ("PLAIN" | "SCRAM-SHA-256" | "SCRAM-SHA-512" | "OAUTHBEARER" | "GSSAPI")[];
|
|
110
110
|
};
|
|
111
|
+
reauthFraction: {
|
|
112
|
+
type: string;
|
|
113
|
+
exclusiveMinimum: number;
|
|
114
|
+
maximum: number;
|
|
115
|
+
default: number;
|
|
116
|
+
};
|
|
117
|
+
reauthLeadTime: {
|
|
118
|
+
type: string;
|
|
119
|
+
minimum: number;
|
|
120
|
+
};
|
|
121
|
+
lazyReauthentication: {
|
|
122
|
+
type: string;
|
|
123
|
+
};
|
|
111
124
|
username: {
|
|
112
125
|
oneOf: ({
|
|
113
126
|
type: string;
|
|
@@ -40,6 +40,9 @@ export const baseOptionsSchema = {
|
|
|
40
40
|
type: 'object',
|
|
41
41
|
properties: {
|
|
42
42
|
mechanism: { type: 'string', enum: allowedSASLMechanisms },
|
|
43
|
+
reauthFraction: { type: 'number', exclusiveMinimum: 0, maximum: 1, default: 0.8 },
|
|
44
|
+
reauthLeadTime: { type: 'number', minimum: 0 },
|
|
45
|
+
lazyReauthentication: { type: 'boolean' },
|
|
43
46
|
username: { oneOf: [{ type: 'string' }, { function: true }] },
|
|
44
47
|
password: { oneOf: [{ type: 'string' }, { function: true }] },
|
|
45
48
|
token: { oneOf: [{ type: 'string' }, { function: true }] },
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UnsupportedFormatError } from "../../errors.js";
|
|
1
2
|
import { Reader } from "../../protocol/reader.js";
|
|
2
3
|
import { Writer } from "../../protocol/writer.js";
|
|
3
4
|
export const CONSUMER_PROTOCOL_LOWEST_VERSION = 0;
|
|
@@ -48,26 +49,33 @@ export function encodeConsumerProtocolSubscription(data) {
|
|
|
48
49
|
}
|
|
49
50
|
export function decodeConsumerProtocolSubscription(buffer) {
|
|
50
51
|
const reader = Reader.from(buffer);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
try {
|
|
53
|
+
const encodedVersion = reader.readInt16();
|
|
54
|
+
const version = supportedVersion(encodedVersion);
|
|
55
|
+
const subscription = {
|
|
56
|
+
version: encodedVersion,
|
|
57
|
+
topics: reader.readArray(r => r.readString(false), false, false),
|
|
58
|
+
userData: reader.readBytes(false),
|
|
59
|
+
ownedPartitions: [],
|
|
60
|
+
generationId: -1,
|
|
61
|
+
rackId: null
|
|
62
|
+
};
|
|
63
|
+
// KafkaJS and Platformatic <= 2.11 may advertise newer versions with a v0 body.
|
|
64
|
+
// Default absent trailing fields, but still reject partially encoded fields.
|
|
65
|
+
if (version >= 1 && reader.remaining > 0) {
|
|
66
|
+
subscription.ownedPartitions = readTopicPartitions(reader);
|
|
67
|
+
}
|
|
68
|
+
if (version >= 2 && reader.remaining > 0) {
|
|
69
|
+
subscription.generationId = reader.readInt32();
|
|
70
|
+
}
|
|
71
|
+
if (version >= 3 && reader.remaining > 0) {
|
|
72
|
+
subscription.rackId = reader.readNullableString(false);
|
|
73
|
+
}
|
|
74
|
+
return subscription;
|
|
63
75
|
}
|
|
64
|
-
|
|
65
|
-
subscription
|
|
76
|
+
catch (e) {
|
|
77
|
+
throw new UnsupportedFormatError('Malformed consumer protocol subscription', { cause: e });
|
|
66
78
|
}
|
|
67
|
-
if (version >= 3) {
|
|
68
|
-
subscription.rackId = reader.readNullableString(false);
|
|
69
|
-
}
|
|
70
|
-
return subscription;
|
|
71
79
|
}
|
|
72
80
|
export function decodeCooperativeStickyGeneration(userData) {
|
|
73
81
|
if (userData.length < 4) {
|
|
@@ -96,19 +104,24 @@ export function decodeConsumerProtocolAssignment(buffer) {
|
|
|
96
104
|
};
|
|
97
105
|
}
|
|
98
106
|
const reader = Reader.from(buffer);
|
|
99
|
-
|
|
100
|
-
|
|
107
|
+
try {
|
|
108
|
+
const encodedVersion = reader.readInt16();
|
|
109
|
+
if (reader.remaining === 0) {
|
|
110
|
+
return {
|
|
111
|
+
version: encodedVersion,
|
|
112
|
+
assignedPartitions: [],
|
|
113
|
+
userData: Buffer.alloc(0)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const assignedPartitions = readTopicPartitions(reader);
|
|
117
|
+
const userData = reader.remaining > 0 ? reader.readBytes(false) : Buffer.alloc(0);
|
|
101
118
|
return {
|
|
102
119
|
version: encodedVersion,
|
|
103
|
-
assignedPartitions
|
|
104
|
-
userData
|
|
120
|
+
assignedPartitions,
|
|
121
|
+
userData
|
|
105
122
|
};
|
|
106
123
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
version: encodedVersion,
|
|
111
|
-
assignedPartitions,
|
|
112
|
-
userData
|
|
113
|
-
};
|
|
124
|
+
catch (e) {
|
|
125
|
+
throw new UnsupportedFormatError('Malformed consumer protocol assignment', { cause: e });
|
|
126
|
+
}
|
|
114
127
|
}
|
|
@@ -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
|
|
9
|
+
import { type CommitOptions, type ConsumeOptions, type ConsumerGroupAutocommitErrorPayload, 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;
|
|
@@ -9,8 +9,8 @@ import { kAutocommit, kGetFetchNode, kRefreshOffsetsAndFetch, kUpdateCommittedOf
|
|
|
9
9
|
import { emitExperimentalApiWarning } from "../../utils.js";
|
|
10
10
|
import { Base, kAfterCreate, kCheckNotClosed, kClosed, kConnections, kCreateConnectionPool, kFormatValidationErrors, kGetApi, kGetBootstrapConnection, kGetConnection, kMetadata, kOptions, kPerformDeduplicated, kPerformWithRetry, kPrometheus, kValidateOptions } from "../base/base.js";
|
|
11
11
|
import { ensureMetric } from "../metrics.js";
|
|
12
|
+
import { CONSUMER_PROTOCOL_HIGHEST_VERSION, decodeConsumerProtocolAssignment, decodeConsumerProtocolSubscription, decodeCooperativeStickyGeneration, encodeConsumerProtocolAssignment, encodeConsumerProtocolSubscription } from "./consumer-protocol.js";
|
|
12
13
|
import { MessagesStream } from "./messages-stream.js";
|
|
13
|
-
import { CONSUMER_PROTOCOL_HIGHEST_VERSION, decodeCooperativeStickyGeneration, decodeConsumerProtocolAssignment, decodeConsumerProtocolSubscription, encodeConsumerProtocolAssignment, encodeConsumerProtocolSubscription } from "./consumer-protocol.js";
|
|
14
14
|
import { commitOptionsValidator, consumeOptionsValidator, consumerOptionsValidator, defaultConsumerOptions, fetchOptionsValidator, getLagOptionsValidator, groupIdAndOptionsValidator, groupOptionsValidator, listCommitsOptionsValidator, listOffsetsOptionsValidator } from "./options.js";
|
|
15
15
|
import { COOPERATIVE_STICKY_ASSIGNOR, cooperativeStickyAssigner, roundRobinAssigner } from "./partitions-assigners.js";
|
|
16
16
|
import { TopicsMap } from "./topics-map.js";
|
|
@@ -418,6 +418,12 @@ export class Consumer extends Base {
|
|
|
418
418
|
if (!options.protocolsMetadata && protocolsMetadata) {
|
|
419
419
|
options.protocolsMetadata = protocolsMetadata;
|
|
420
420
|
}
|
|
421
|
+
if (!options.partitionAssigner && this[kOptions].partitionAssigner) {
|
|
422
|
+
options.partitionAssigner = this[kOptions].partitionAssigner;
|
|
423
|
+
}
|
|
424
|
+
if (!options.partitionAssignerTopicsSelector && this[kOptions].partitionAssignerTopicsSelector) {
|
|
425
|
+
options.partitionAssignerTopicsSelector = this[kOptions].partitionAssignerTopicsSelector;
|
|
426
|
+
}
|
|
421
427
|
this.#validateGroupOptions(options);
|
|
422
428
|
this.#membershipActive = true;
|
|
423
429
|
this.#joinGroup(options, callback);
|
|
@@ -922,8 +928,8 @@ export class Consumer extends Base {
|
|
|
922
928
|
#leaveGroupClassicProtocol(force, callback) {
|
|
923
929
|
consumerGroupChannel.traceCallback(this.#performLeaveGroup, 1, createDiagnosticContext({ client: this, operation: 'leaveGroup', force }), this, force, callback);
|
|
924
930
|
}
|
|
925
|
-
#syncGroup(partitionsAssigner, callback) {
|
|
926
|
-
consumerGroupChannel.traceCallback(this.#performSyncGroup,
|
|
931
|
+
#syncGroup(partitionsAssigner, partitionAssignerTopicsSelector, callback) {
|
|
932
|
+
consumerGroupChannel.traceCallback(this.#performSyncGroup, 3, createDiagnosticContext({ client: this, operation: 'syncGroup' }), this, partitionsAssigner, partitionAssignerTopicsSelector, null, callback);
|
|
927
933
|
}
|
|
928
934
|
#heartbeat(options) {
|
|
929
935
|
const eventPayload = { groupId: this.groupId, memberId: this.memberId, generationId: this.generationId };
|
|
@@ -1425,7 +1431,7 @@ export class Consumer extends Base {
|
|
|
1425
1431
|
this.#members.set(member.memberId, this.#decodeProtocolSubscriptionMetadata(member.memberId, member.metadata));
|
|
1426
1432
|
}
|
|
1427
1433
|
// Send a syncGroup request
|
|
1428
|
-
this.#syncGroup(options.partitionAssigner, (error, response) => {
|
|
1434
|
+
this.#syncGroup(options.partitionAssigner, options.partitionAssignerTopicsSelector, (error, response) => {
|
|
1429
1435
|
if (!this.#membershipActive) {
|
|
1430
1436
|
callback(null);
|
|
1431
1437
|
return;
|
|
@@ -1619,26 +1625,43 @@ export class Consumer extends Base {
|
|
|
1619
1625
|
}
|
|
1620
1626
|
this.#performDeduplicateGroupOperaton('leaveGroup', operation, onLeave);
|
|
1621
1627
|
}
|
|
1622
|
-
#performSyncGroup(partitionsAssigner, assignments, callback) {
|
|
1628
|
+
#performSyncGroup(partitionsAssigner, partitionAssignerTopicsSelector, assignments, callback) {
|
|
1623
1629
|
if (!this.#membershipActive) {
|
|
1624
1630
|
callback(null, []);
|
|
1625
1631
|
return;
|
|
1626
1632
|
}
|
|
1627
1633
|
if (!Array.isArray(assignments)) {
|
|
1628
1634
|
if (this.#isLeader) {
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1635
|
+
const customTopics = !!(partitionsAssigner && partitionAssignerTopicsSelector);
|
|
1636
|
+
let subscribedTopics;
|
|
1637
|
+
if (customTopics) {
|
|
1638
|
+
try {
|
|
1639
|
+
subscribedTopics = partitionAssignerTopicsSelector(this.#members.get(this.memberId), this.#members);
|
|
1640
|
+
}
|
|
1641
|
+
catch (error) {
|
|
1642
|
+
callback(this.#handleError(new UserError('partitionAssignerTopicsSelector failed.', { cause: error })));
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
if (!Array.isArray(subscribedTopics) || subscribedTopics.some(topic => typeof topic !== 'string')) {
|
|
1646
|
+
callback(this.#handleError(new UserError('partitionAssignerTopicsSelector must return an array of topic names.')));
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
else {
|
|
1651
|
+
// Get all the metadata for the topics the consumers are listening to, then compute the assignments.
|
|
1652
|
+
const topicsSubscriptions = new Map();
|
|
1653
|
+
for (const subscription of this.#members.values()) {
|
|
1654
|
+
for (const topic of subscription.topics) {
|
|
1655
|
+
let topicSubscriptions = topicsSubscriptions.get(topic);
|
|
1656
|
+
if (!topicSubscriptions) {
|
|
1657
|
+
topicSubscriptions = [];
|
|
1658
|
+
topicsSubscriptions.set(topic, topicSubscriptions);
|
|
1659
|
+
}
|
|
1660
|
+
topicSubscriptions.push(subscription);
|
|
1637
1661
|
}
|
|
1638
|
-
topicSubscriptions.push(subscription);
|
|
1639
1662
|
}
|
|
1663
|
+
subscribedTopics = Array.from(topicsSubscriptions.keys());
|
|
1640
1664
|
}
|
|
1641
|
-
const subscribedTopics = Array.from(topicsSubscriptions.keys());
|
|
1642
1665
|
this[kMetadata]({ topics: subscribedTopics }, (error, metadata) => {
|
|
1643
1666
|
if (error) {
|
|
1644
1667
|
callback(this.#handleError(error));
|
|
@@ -1649,7 +1672,7 @@ export class Consumer extends Base {
|
|
|
1649
1672
|
callback(this.#handleError(new NetworkError(`Cannot compute group assignments: metadata for subscribed topic "${missingTopic}" is not available in the current cluster metadata.`, { canRetry: true, hasStaleMetadata: true })));
|
|
1650
1673
|
return;
|
|
1651
1674
|
}
|
|
1652
|
-
this.#performSyncGroup(partitionsAssigner, this.#createAssignments(partitionsAssigner, metadata), callback);
|
|
1675
|
+
this.#performSyncGroup(partitionsAssigner, partitionAssignerTopicsSelector, this.#createAssignments(partitionsAssigner, metadata), callback);
|
|
1653
1676
|
});
|
|
1654
1677
|
return;
|
|
1655
1678
|
}
|
|
@@ -1781,9 +1804,7 @@ export class Consumer extends Base {
|
|
|
1781
1804
|
const ownedPartitions = cooperative ? this.#ownedPartitionsForSubscription(topics) : [];
|
|
1782
1805
|
let userData = typeof metadata.metadata === 'string' ? Buffer.from(metadata.metadata) : metadata.metadata;
|
|
1783
1806
|
if (cooperative && !userData && metadata.version < 2) {
|
|
1784
|
-
userData = Writer.create()
|
|
1785
|
-
.appendInt32(this.memberId && ownedPartitions.length > 0 ? this.generationId : -1)
|
|
1786
|
-
.buffer;
|
|
1807
|
+
userData = Writer.create().appendInt32(this.memberId && ownedPartitions.length > 0 ? this.generationId : -1).buffer;
|
|
1787
1808
|
}
|
|
1788
1809
|
return encodeConsumerProtocolSubscription({
|
|
1789
1810
|
version: metadata.version,
|
|
@@ -1832,7 +1853,7 @@ export class Consumer extends Base {
|
|
|
1832
1853
|
}
|
|
1833
1854
|
// We are the only member of the group, assign all partitions to us
|
|
1834
1855
|
const membersSize = this.#members.size;
|
|
1835
|
-
if (membersSize === 1) {
|
|
1856
|
+
if (membersSize === 1 && !partitionsAssigner) {
|
|
1836
1857
|
const assignments = [];
|
|
1837
1858
|
for (const topic of this.topics.current) {
|
|
1838
1859
|
const partitionsCount = metadata.topics.get(topic).partitionsCount;
|
|
@@ -42,6 +42,7 @@ export class MessagesStream extends Readable {
|
|
|
42
42
|
#refreshOffsetsInflight;
|
|
43
43
|
#refreshOffsetsPending;
|
|
44
44
|
#refreshOffsetsDestroyOnError;
|
|
45
|
+
#offsetsEpoch;
|
|
45
46
|
#fetches;
|
|
46
47
|
#maxFetches;
|
|
47
48
|
#options;
|
|
@@ -88,7 +89,7 @@ export class MessagesStream extends Readable {
|
|
|
88
89
|
constructor(consumer, options) {
|
|
89
90
|
const { autocommit, mode, fallbackMode, maxFetches, offsets, context, deserializers, onCorruptedMessage, onDeserializationError, registry, beforeDeserialization,
|
|
90
91
|
// The options below are only destructured to avoid being part of structuredClone below
|
|
91
|
-
partitionAssigner: _partitionAssigner, protocolsMetadata: _protocolsMetadata, ...otherOptions } = options;
|
|
92
|
+
partitionAssigner: _partitionAssigner, partitionAssignerTopicsSelector: _partitionAssignerTopicsSelector, protocolsMetadata: _protocolsMetadata, ...otherOptions } = options;
|
|
92
93
|
if (onCorruptedMessage && onDeserializationError) {
|
|
93
94
|
throw new UserError('Cannot specify both onCorruptedMessage and onDeserializationError.');
|
|
94
95
|
}
|
|
@@ -116,6 +117,7 @@ export class MessagesStream extends Readable {
|
|
|
116
117
|
this.#refreshOffsetsInflight = false;
|
|
117
118
|
this.#refreshOffsetsPending = false;
|
|
118
119
|
this.#refreshOffsetsDestroyOnError = false;
|
|
120
|
+
this.#offsetsEpoch = 0;
|
|
119
121
|
this.#fetches = 0;
|
|
120
122
|
this.#maxFetches = maxFetches ?? 0;
|
|
121
123
|
this.#topics = structuredClone(options.topics);
|
|
@@ -352,7 +354,11 @@ export class MessagesStream extends Readable {
|
|
|
352
354
|
if (this.readableFlowing === null || this.#paused || this.#refreshOffsetsInflight || this.#refreshOffsetsPending) {
|
|
353
355
|
return;
|
|
354
356
|
}
|
|
357
|
+
const offsetsEpoch = this.#offsetsEpoch;
|
|
355
358
|
this.#consumer.metadata({ topics: this.#consumer.topics.current }, (error, metadata) => {
|
|
359
|
+
if (offsetsEpoch !== this.#offsetsEpoch) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
356
362
|
if (error) {
|
|
357
363
|
this.emit('fetch');
|
|
358
364
|
// The stream has been closed, ignore any error
|
|
@@ -373,8 +379,8 @@ export class MessagesStream extends Readable {
|
|
|
373
379
|
// Remove stale inflight entries that have been pending for too long.
|
|
374
380
|
// This prevents permanent partition starvation if a fetch callback is never invoked.
|
|
375
381
|
const now = Date.now();
|
|
376
|
-
for (const [node,
|
|
377
|
-
if (now -
|
|
382
|
+
for (const [node, inflight] of this.#inflightNodes) {
|
|
383
|
+
if (now - inflight.startedAt > 120_000) {
|
|
378
384
|
this.#inflightNodes.delete(node);
|
|
379
385
|
}
|
|
380
386
|
}
|
|
@@ -423,10 +429,29 @@ export class MessagesStream extends Readable {
|
|
|
423
429
|
return;
|
|
424
430
|
}
|
|
425
431
|
for (const [node, nodeRequests] of requests) {
|
|
426
|
-
|
|
432
|
+
const inflight = { startedAt: Date.now() };
|
|
433
|
+
this.#inflightNodes.set(node, inflight);
|
|
427
434
|
this.#consumer.fetch({ ...this.#options, node, topics: nodeRequests, connectionPool: this[kConnections] }, (error, response) => {
|
|
428
|
-
this.#inflightNodes.
|
|
435
|
+
const ownsNode = this.#inflightNodes.get(node) === inflight;
|
|
436
|
+
if (ownsNode) {
|
|
437
|
+
this.#inflightNodes.delete(node);
|
|
438
|
+
}
|
|
429
439
|
this.emit('fetch');
|
|
440
|
+
// A refresh (including a rejoin) invalidates requests issued before it.
|
|
441
|
+
// Never let an old response or error undo the restored offsets.
|
|
442
|
+
if (offsetsEpoch !== this.#offsetsEpoch || !ownsNode) {
|
|
443
|
+
if (this.#closed || this.closed || this.destroyed) {
|
|
444
|
+
if (this.#inflightNodes.size === 0) {
|
|
445
|
+
this.push(null);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else if (ownsNode) {
|
|
449
|
+
// The refresh may have finished while this node was still inflight.
|
|
450
|
+
// Now that it is free, resume fetching from the restored position.
|
|
451
|
+
process.nextTick(() => this.#fetch());
|
|
452
|
+
}
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
430
455
|
if (error) {
|
|
431
456
|
// The stream has been closed, ignore the error
|
|
432
457
|
/* c8 ignore next 4 - Hard to test */
|
|
@@ -464,7 +489,7 @@ export class MessagesStream extends Readable {
|
|
|
464
489
|
}
|
|
465
490
|
return;
|
|
466
491
|
}
|
|
467
|
-
this.#pushRecordsOperation(metadata, topicIds, response, requestedOffsets);
|
|
492
|
+
this.#pushRecordsOperation(metadata, topicIds, response, requestedOffsets, offsetsEpoch);
|
|
468
493
|
});
|
|
469
494
|
}
|
|
470
495
|
});
|
|
@@ -553,7 +578,10 @@ export class MessagesStream extends Readable {
|
|
|
553
578
|
}
|
|
554
579
|
return recoveredOffsets.length > 0;
|
|
555
580
|
}
|
|
556
|
-
#pushRecords(metadata, topicIds, response, requestedOffsets) {
|
|
581
|
+
#pushRecords(metadata, topicIds, response, requestedOffsets, offsetsEpoch) {
|
|
582
|
+
if (offsetsEpoch !== this.#offsetsEpoch) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
557
585
|
const autocommit = this.#autocommitEnabled;
|
|
558
586
|
const keyDeserializer = this.#keyDeserializer;
|
|
559
587
|
const valueDeserializer = this.#valueDeserializer;
|
|
@@ -995,6 +1023,8 @@ export class MessagesStream extends Readable {
|
|
|
995
1023
|
});
|
|
996
1024
|
}
|
|
997
1025
|
#scheduleRefreshOffsetsAndFetch(destroyOnError = true) {
|
|
1026
|
+
// Invalidate outstanding fetches immediately, not only after restoration completes.
|
|
1027
|
+
this.#offsetsEpoch++;
|
|
998
1028
|
this.#refreshOffsetsDestroyOnError ||= destroyOnError;
|
|
999
1029
|
/* c8 ignore next 4 - Hard to test */
|
|
1000
1030
|
if (this.#refreshOffsetsInflight) {
|
|
@@ -1078,7 +1108,7 @@ export class MessagesStream extends Readable {
|
|
|
1078
1108
|
[kInspect](...args) {
|
|
1079
1109
|
this.#consumer[kInspect](...args);
|
|
1080
1110
|
}
|
|
1081
|
-
#beforeDeserialization(hook, metadata, topicIds, response, requestedOffsets) {
|
|
1111
|
+
#beforeDeserialization(hook, metadata, topicIds, response, requestedOffsets, offsetsEpoch) {
|
|
1082
1112
|
const requests = [];
|
|
1083
1113
|
// Create the pre-deserialization requests
|
|
1084
1114
|
for (const topicResponse of response.responses) {
|
|
@@ -1113,7 +1143,7 @@ export class MessagesStream extends Readable {
|
|
|
1113
1143
|
}
|
|
1114
1144
|
}
|
|
1115
1145
|
if (requests.length === 0) {
|
|
1116
|
-
this.#pushRecords(metadata, topicIds, response, requestedOffsets);
|
|
1146
|
+
this.#pushRecords(metadata, topicIds, response, requestedOffsets, offsetsEpoch);
|
|
1117
1147
|
return;
|
|
1118
1148
|
}
|
|
1119
1149
|
runAsyncSeries((request, cb) => {
|
|
@@ -1137,11 +1167,14 @@ export class MessagesStream extends Readable {
|
|
|
1137
1167
|
result.then(() => onHookSettled(null), onHookSettled);
|
|
1138
1168
|
}
|
|
1139
1169
|
}, requests, 0, error => {
|
|
1170
|
+
if (offsetsEpoch !== this.#offsetsEpoch) {
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1140
1173
|
if (error) {
|
|
1141
1174
|
this.destroy(error);
|
|
1142
1175
|
return;
|
|
1143
1176
|
}
|
|
1144
|
-
this.#pushRecords(metadata, topicIds, response, requestedOffsets);
|
|
1177
|
+
this.#pushRecords(metadata, topicIds, response, requestedOffsets, offsetsEpoch);
|
|
1145
1178
|
});
|
|
1146
1179
|
}
|
|
1147
1180
|
}
|
|
@@ -63,6 +63,9 @@ export declare const groupOptionsProperties: {
|
|
|
63
63
|
partitionAssigner: {
|
|
64
64
|
function: boolean;
|
|
65
65
|
};
|
|
66
|
+
partitionAssignerTopicsSelector: {
|
|
67
|
+
function: boolean;
|
|
68
|
+
};
|
|
66
69
|
};
|
|
67
70
|
export declare const groupOptionsAdditionalValidations: {
|
|
68
71
|
rebalanceTimeout: {
|
|
@@ -212,6 +215,9 @@ export declare const groupOptionsSchema: {
|
|
|
212
215
|
partitionAssigner: {
|
|
213
216
|
function: boolean;
|
|
214
217
|
};
|
|
218
|
+
partitionAssignerTopicsSelector: {
|
|
219
|
+
function: boolean;
|
|
220
|
+
};
|
|
215
221
|
};
|
|
216
222
|
additionalProperties: boolean;
|
|
217
223
|
};
|
|
@@ -339,6 +345,9 @@ export declare const consumeOptionsSchema: {
|
|
|
339
345
|
partitionAssigner: {
|
|
340
346
|
function: boolean;
|
|
341
347
|
};
|
|
348
|
+
partitionAssignerTopicsSelector: {
|
|
349
|
+
function: boolean;
|
|
350
|
+
};
|
|
342
351
|
topics: {
|
|
343
352
|
type: string;
|
|
344
353
|
items: {
|
|
@@ -515,6 +524,9 @@ export declare const consumerOptionsSchema: {
|
|
|
515
524
|
partitionAssigner: {
|
|
516
525
|
function: boolean;
|
|
517
526
|
};
|
|
527
|
+
partitionAssignerTopicsSelector: {
|
|
528
|
+
function: boolean;
|
|
529
|
+
};
|
|
518
530
|
groupId: {
|
|
519
531
|
type: string;
|
|
520
532
|
pattern: string;
|
|
@@ -649,6 +661,9 @@ export declare const fetchOptionsSchema: {
|
|
|
649
661
|
partitionAssigner: {
|
|
650
662
|
function: boolean;
|
|
651
663
|
};
|
|
664
|
+
partitionAssignerTopicsSelector: {
|
|
665
|
+
function: boolean;
|
|
666
|
+
};
|
|
652
667
|
node: {
|
|
653
668
|
type: string;
|
|
654
669
|
minimum: number;
|
|
@@ -27,7 +27,8 @@ export const groupOptionsProperties = {
|
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
protocolsMetadata: { function: true },
|
|
30
|
-
partitionAssigner: { function: true }
|
|
30
|
+
partitionAssigner: { function: true },
|
|
31
|
+
partitionAssignerTopicsSelector: { function: true }
|
|
31
32
|
};
|
|
32
33
|
export const groupOptionsAdditionalValidations = {
|
|
33
34
|
rebalanceTimeout: {
|
|
@@ -200,7 +201,7 @@ export const listOffsetsOptionsSchema = {
|
|
|
200
201
|
items: { type: 'number', minimum: 0 }
|
|
201
202
|
}
|
|
202
203
|
},
|
|
203
|
-
isolationLevel: { type: '
|
|
204
|
+
isolationLevel: { type: 'number', enum: allowedFetchIsolationLevels },
|
|
204
205
|
timestamp: { bigint: true }
|
|
205
206
|
},
|
|
206
207
|
required: ['topics'],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Callback } from '../../apis/definitions.ts';
|
|
2
1
|
import { type FetchRequestTopic } from '../../apis/consumer/fetch-v17.ts';
|
|
2
|
+
import { type Callback } from '../../apis/definitions.ts';
|
|
3
3
|
import { type GroupProtocols } from '../../apis/enumerations.ts';
|
|
4
4
|
import { type ConnectionPool } from '../../network/connection-pool.ts';
|
|
5
5
|
import { type KafkaRecord, type Message } from '../../protocol/records.ts';
|
|
@@ -65,6 +65,7 @@ export type DeserializationErrorHandler = (context: DeserializationErrorContext)
|
|
|
65
65
|
*/
|
|
66
66
|
export type CorruptedMessageHandler = (record: KafkaRecord, topic: string, partition: number, firstTimestamp: bigint, firstOffset: bigint, commit: Message['commit'], error: unknown) => boolean;
|
|
67
67
|
export type GroupPartitionsAssigner = (current: string, members: Map<string, ExtendedGroupProtocolSubscription>, topics: Set<string>, metadata: ClusterMetadata) => GroupPartitionsAssignments[];
|
|
68
|
+
export type GroupPartitionsAssignerTopicsSelector = (leader: ExtendedGroupProtocolSubscription, members: Map<string, ExtendedGroupProtocolSubscription>) => string[];
|
|
68
69
|
export type GroupProtocolsMetadataCallback = (protocols: GroupProtocolSubscription[], topics: string[], metadata: ClusterMetadata, callback: Callback<Buffer>) => Buffer | Promise<Buffer> | undefined;
|
|
69
70
|
export declare const MessagesStreamModes: {
|
|
70
71
|
readonly LATEST: "latest";
|
|
@@ -93,6 +94,7 @@ export interface GroupOptions {
|
|
|
93
94
|
protocols?: GroupProtocolSubscription[];
|
|
94
95
|
protocolsMetadata?: GroupProtocolsMetadataCallback;
|
|
95
96
|
partitionAssigner?: GroupPartitionsAssigner;
|
|
97
|
+
partitionAssignerTopicsSelector?: GroupPartitionsAssignerTopicsSelector;
|
|
96
98
|
assignmentUserData?: Buffer;
|
|
97
99
|
}
|
|
98
100
|
export interface ConsumerGroupOptions {
|
|
@@ -295,7 +295,7 @@ export class Producer extends Base {
|
|
|
295
295
|
const brokers = {};
|
|
296
296
|
for (const [topic, partitions] of topicsPartitions) {
|
|
297
297
|
for (const rawPartition of partitions) {
|
|
298
|
-
const partition = rawPartition
|
|
298
|
+
const partition = rawPartition % metadata.topics.get(topic).partitionsCount;
|
|
299
299
|
const leader = metadata.topics.get(topic).partitions[partition].leader;
|
|
300
300
|
const broker = metadata.brokers.get(leader);
|
|
301
301
|
brokers[`${topic}:${partition}`] = broker;
|
package/dist/errors.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type ErrorCode = (typeof errorCodes)[number];
|
|
|
7
7
|
export type ErrorProperties = {
|
|
8
8
|
cause?: Error;
|
|
9
9
|
} & Record<string, any>;
|
|
10
|
+
export declare function findNestedErrorBy(error: Error, property: string, value: unknown, visited: Set<Error>): Error | null;
|
|
10
11
|
export declare class GenericError extends Error {
|
|
11
12
|
code: string;
|
|
12
13
|
[index: string]: any;
|
package/dist/errors.js
CHANGED
|
@@ -19,6 +19,30 @@ export const errorCodes = [
|
|
|
19
19
|
'PLT_KFK_UNSUPPORTED',
|
|
20
20
|
'PLT_KFK_USER'
|
|
21
21
|
];
|
|
22
|
+
export function findNestedErrorBy(error, property, value, visited) {
|
|
23
|
+
if (!error || visited.has(error)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
visited.add(error);
|
|
27
|
+
if (error[property] === value) {
|
|
28
|
+
return error;
|
|
29
|
+
}
|
|
30
|
+
// Visit aggregate entries before the cause, preserving the existing search order.
|
|
31
|
+
if (MultipleErrors.isMultipleErrors(error)) {
|
|
32
|
+
for (const nested of error.errors) {
|
|
33
|
+
if (nested && typeof nested === 'object') {
|
|
34
|
+
const found = findNestedErrorBy(nested, property, value, visited);
|
|
35
|
+
if (found) {
|
|
36
|
+
return found;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (error.cause && typeof error.cause === 'object') {
|
|
42
|
+
return findNestedErrorBy(error.cause, property, value, visited);
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
22
46
|
export class GenericError extends Error {
|
|
23
47
|
code;
|
|
24
48
|
[kGenericError];
|
|
@@ -40,10 +64,7 @@ export class GenericError extends Error {
|
|
|
40
64
|
Reflect.defineProperty(this, kGenericError, { value: true, enumerable: false });
|
|
41
65
|
}
|
|
42
66
|
findBy(property, value) {
|
|
43
|
-
|
|
44
|
-
return this;
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
67
|
+
return findNestedErrorBy(this, property, value, new Set());
|
|
47
68
|
}
|
|
48
69
|
toString() {
|
|
49
70
|
return `Error ${this.code}: ${this.message}`;
|
|
@@ -77,22 +98,7 @@ export class MultipleErrors extends AggregateError {
|
|
|
77
98
|
Reflect.defineProperty(this, kMultipleErrors, { value: true, enumerable: false });
|
|
78
99
|
}
|
|
79
100
|
findBy(property, value) {
|
|
80
|
-
|
|
81
|
-
return this;
|
|
82
|
-
}
|
|
83
|
-
for (const error of this.errors) {
|
|
84
|
-
if (!error) {
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (error[property] === value) {
|
|
88
|
-
return error;
|
|
89
|
-
}
|
|
90
|
-
const found = error[kGenericError] ? error.findBy(property, value) : undefined;
|
|
91
|
-
if (found) {
|
|
92
|
-
return found;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
101
|
+
return findNestedErrorBy(this, property, value, new Set());
|
|
96
102
|
}
|
|
97
103
|
toString() {
|
|
98
104
|
return `Error ${this.code}: ${this.message} (caused by: ${this.errors.map(error => error.toString()).join(', ')})`;
|
|
@@ -121,7 +127,8 @@ export class NetworkError extends GenericError {
|
|
|
121
127
|
static code = 'PLT_KFK_NETWORK';
|
|
122
128
|
static isRetryable(error) {
|
|
123
129
|
const networkError = findErrorBy(error, 'code', NetworkError.code);
|
|
124
|
-
|
|
130
|
+
// Require a retryable network error, but reject any non-retriable error in the whole tree.
|
|
131
|
+
return networkError?.canRetry === true && !findErrorBy(error, 'canRetry', false);
|
|
125
132
|
}
|
|
126
133
|
constructor(message, properties = {}) {
|
|
127
134
|
super(NetworkError.code, message, { canRetry: true, ...properties });
|
|
@@ -35,6 +35,9 @@ export interface SASLOptions {
|
|
|
35
35
|
oauthBearerExtensions?: Record<string, string> | CredentialProvider<Record<string, string>>;
|
|
36
36
|
authenticate?: SASLCustomAuthenticator;
|
|
37
37
|
authBytesValidator?: (authBytes: Buffer, callback: CallbackWithPromise<Buffer>) => void;
|
|
38
|
+
reauthFraction?: number;
|
|
39
|
+
reauthLeadTime?: number;
|
|
40
|
+
lazyReauthentication?: boolean;
|
|
38
41
|
}
|
|
39
42
|
export interface ConnectionOptions {
|
|
40
43
|
connectTimeout?: number;
|
|
@@ -4,8 +4,8 @@ import { createConnection } from 'node:net';
|
|
|
4
4
|
import { connect as createTLSConnection } from 'node:tls';
|
|
5
5
|
import { createPromisifiedCallback, kCallbackPromise } from "../apis/callbacks.js";
|
|
6
6
|
import { allowedSASLMechanisms, SASLMechanisms } from "../apis/enumerations.js";
|
|
7
|
-
import { clientSoftwareName, clientSoftwareVersion } from "../clients/base/options.js";
|
|
8
7
|
import { apiVersionsV1, saslAuthenticateV0, saslAuthenticateV1, saslAuthenticateV2, saslHandshakeV1 } from "../apis/index.js";
|
|
8
|
+
import { clientSoftwareName, clientSoftwareVersion } from "../clients/base/options.js";
|
|
9
9
|
import { connectionsApiChannel, connectionsConnectsChannel, createDiagnosticContext, notifyCreation } from "../diagnostic.js";
|
|
10
10
|
import { AuthenticationError, NetworkError, TimeoutError, UnexpectedCorrelationIdError, UserError } from "../errors.js";
|
|
11
11
|
import { TypedEventEmitter } from "../events.js";
|
|
@@ -54,6 +54,7 @@ export class Connection extends TypedEventEmitter {
|
|
|
54
54
|
#socketMustBeDrained;
|
|
55
55
|
#detectMissingTLS;
|
|
56
56
|
#reauthenticationTimeout;
|
|
57
|
+
#reauthenticationDue = false;
|
|
57
58
|
// Negotiated once per connection: reauthentication runs the same path again.
|
|
58
59
|
#saslAuthenticateApi;
|
|
59
60
|
constructor(clientId, options = {}) {
|
|
@@ -239,7 +240,7 @@ export class Connection extends TypedEventEmitter {
|
|
|
239
240
|
if (!callback) {
|
|
240
241
|
callback = createPromisifiedCallback();
|
|
241
242
|
}
|
|
242
|
-
|
|
243
|
+
clearTimeout(this.#reauthenticationTimeout);
|
|
243
244
|
if (this.#status === ConnectionStatuses.CLOSED ||
|
|
244
245
|
this.#status === ConnectionStatuses.ERROR ||
|
|
245
246
|
this.#status === ConnectionStatuses.NONE) {
|
|
@@ -266,6 +267,25 @@ export class Connection extends TypedEventEmitter {
|
|
|
266
267
|
return callback[kCallbackPromise];
|
|
267
268
|
}
|
|
268
269
|
send(apiKey, apiVersion, createPayload, responseParser, hasRequestHeaderTaggedFields, hasResponseHeaderTaggedFields, callback) {
|
|
270
|
+
if ((this.#status === ConnectionStatuses.REAUTHENTICATING ||
|
|
271
|
+
(this.#status === ConnectionStatuses.CONNECTED && this.#reauthenticationDue)) &&
|
|
272
|
+
// SASL requests must still pass through send() while reauthentication is in progress.
|
|
273
|
+
apiKey !== apiVersionsV1.api.key &&
|
|
274
|
+
apiKey !== saslHandshakeV1.api.key &&
|
|
275
|
+
apiKey !== saslAuthenticateV2.api.key) {
|
|
276
|
+
this.ready(error => {
|
|
277
|
+
if (error) {
|
|
278
|
+
callback(error);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
this.send(apiKey, apiVersion, createPayload, responseParser, hasRequestHeaderTaggedFields, hasResponseHeaderTaggedFields, callback);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
if (this.#reauthenticationDue) {
|
|
285
|
+
this.reauthenticate();
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
269
289
|
// Correlation ID is a 32-bit integer in the protocol, so we need to wrap around after 2^31 - 1
|
|
270
290
|
const correlationId = (this.#correlationId + 1) & 0x7fffffff;
|
|
271
291
|
this.#correlationId = correlationId;
|
|
@@ -358,6 +378,7 @@ export class Connection extends TypedEventEmitter {
|
|
|
358
378
|
const host = this.#host;
|
|
359
379
|
const port = this.#port;
|
|
360
380
|
const diagnosticContext = createDiagnosticContext({ connection: this, operation: 'reauthenticate', host, port });
|
|
381
|
+
this.#reauthenticationDue = false;
|
|
361
382
|
this.#status = ConnectionStatuses.REAUTHENTICATING;
|
|
362
383
|
clearTimeout(this.#reauthenticationTimeout);
|
|
363
384
|
this.#authenticate(host, port, diagnosticContext);
|
|
@@ -537,7 +558,18 @@ export class Connection extends TypedEventEmitter {
|
|
|
537
558
|
return;
|
|
538
559
|
}
|
|
539
560
|
if (sessionLifetimeMs > 0) {
|
|
540
|
-
|
|
561
|
+
const lifetime = Number(sessionLifetimeMs);
|
|
562
|
+
const leadTime = lifetime - (this.#options.sasl?.reauthLeadTime ?? 0);
|
|
563
|
+
const fractionTime = lifetime * (this.#options.sasl?.reauthFraction ?? 0.8);
|
|
564
|
+
// Ignore a non-positive lead-time delay to avoid an immediate reauthentication loop.
|
|
565
|
+
this.#reauthenticationTimeout = setTimeout(() => {
|
|
566
|
+
if (this.#options.sasl?.lazyReauthentication) {
|
|
567
|
+
this.#reauthenticationDue = true;
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
this.reauthenticate();
|
|
571
|
+
}
|
|
572
|
+
}, Math.min(fractionTime, leadTime > 0 ? leadTime : fractionTime));
|
|
541
573
|
}
|
|
542
574
|
const isReauthenticating = this.#status === ConnectionStatuses.REAUTHENTICATING;
|
|
543
575
|
if (this.#status === ConnectionStatuses.CONNECTED || isReauthenticating) {
|
|
@@ -40,6 +40,7 @@ export interface Group extends Omit<GroupBase, 'groupType'> {
|
|
|
40
40
|
protocol: string;
|
|
41
41
|
members: Map<string, GroupMember>;
|
|
42
42
|
authorizedOperations: number;
|
|
43
|
+
error?: Error;
|
|
43
44
|
}
|
|
44
45
|
export interface AdminOptions extends BaseOptions {}
|
|
45
46
|
export interface CreateTopicsTopicOptions {
|
|
@@ -108,6 +108,19 @@ export declare const baseOptionsSchema: {
|
|
|
108
108
|
type: string;
|
|
109
109
|
enum: ("PLAIN" | "SCRAM-SHA-256" | "SCRAM-SHA-512" | "OAUTHBEARER" | "GSSAPI")[];
|
|
110
110
|
};
|
|
111
|
+
reauthFraction: {
|
|
112
|
+
type: string;
|
|
113
|
+
exclusiveMinimum: number;
|
|
114
|
+
maximum: number;
|
|
115
|
+
default: number;
|
|
116
|
+
};
|
|
117
|
+
reauthLeadTime: {
|
|
118
|
+
type: string;
|
|
119
|
+
minimum: number;
|
|
120
|
+
};
|
|
121
|
+
lazyReauthentication: {
|
|
122
|
+
type: string;
|
|
123
|
+
};
|
|
111
124
|
username: {
|
|
112
125
|
oneOf: ({
|
|
113
126
|
type: string;
|
|
@@ -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
|
|
9
|
+
import { type CommitOptions, type ConsumeOptions, type ConsumerGroupAutocommitErrorPayload, 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;
|
|
@@ -63,6 +63,9 @@ export declare const groupOptionsProperties: {
|
|
|
63
63
|
partitionAssigner: {
|
|
64
64
|
function: boolean;
|
|
65
65
|
};
|
|
66
|
+
partitionAssignerTopicsSelector: {
|
|
67
|
+
function: boolean;
|
|
68
|
+
};
|
|
66
69
|
};
|
|
67
70
|
export declare const groupOptionsAdditionalValidations: {
|
|
68
71
|
rebalanceTimeout: {
|
|
@@ -212,6 +215,9 @@ export declare const groupOptionsSchema: {
|
|
|
212
215
|
partitionAssigner: {
|
|
213
216
|
function: boolean;
|
|
214
217
|
};
|
|
218
|
+
partitionAssignerTopicsSelector: {
|
|
219
|
+
function: boolean;
|
|
220
|
+
};
|
|
215
221
|
};
|
|
216
222
|
additionalProperties: boolean;
|
|
217
223
|
};
|
|
@@ -339,6 +345,9 @@ export declare const consumeOptionsSchema: {
|
|
|
339
345
|
partitionAssigner: {
|
|
340
346
|
function: boolean;
|
|
341
347
|
};
|
|
348
|
+
partitionAssignerTopicsSelector: {
|
|
349
|
+
function: boolean;
|
|
350
|
+
};
|
|
342
351
|
topics: {
|
|
343
352
|
type: string;
|
|
344
353
|
items: {
|
|
@@ -515,6 +524,9 @@ export declare const consumerOptionsSchema: {
|
|
|
515
524
|
partitionAssigner: {
|
|
516
525
|
function: boolean;
|
|
517
526
|
};
|
|
527
|
+
partitionAssignerTopicsSelector: {
|
|
528
|
+
function: boolean;
|
|
529
|
+
};
|
|
518
530
|
groupId: {
|
|
519
531
|
type: string;
|
|
520
532
|
pattern: string;
|
|
@@ -649,6 +661,9 @@ export declare const fetchOptionsSchema: {
|
|
|
649
661
|
partitionAssigner: {
|
|
650
662
|
function: boolean;
|
|
651
663
|
};
|
|
664
|
+
partitionAssignerTopicsSelector: {
|
|
665
|
+
function: boolean;
|
|
666
|
+
};
|
|
652
667
|
node: {
|
|
653
668
|
type: string;
|
|
654
669
|
minimum: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Callback } from "../../apis/definitions";
|
|
2
1
|
import { type FetchRequestTopic } from "../../apis/consumer/fetch-v17";
|
|
2
|
+
import { type Callback } from "../../apis/definitions";
|
|
3
3
|
import { type GroupProtocols } from "../../apis/enumerations";
|
|
4
4
|
import { type ConnectionPool } from "../../network/connection-pool";
|
|
5
5
|
import { type KafkaRecord, type Message } from "../../protocol/records";
|
|
@@ -65,6 +65,7 @@ export type DeserializationErrorHandler = (context: DeserializationErrorContext)
|
|
|
65
65
|
*/
|
|
66
66
|
export type CorruptedMessageHandler = (record: KafkaRecord, topic: string, partition: number, firstTimestamp: bigint, firstOffset: bigint, commit: Message['commit'], error: unknown) => boolean;
|
|
67
67
|
export type GroupPartitionsAssigner = (current: string, members: Map<string, ExtendedGroupProtocolSubscription>, topics: Set<string>, metadata: ClusterMetadata) => GroupPartitionsAssignments[];
|
|
68
|
+
export type GroupPartitionsAssignerTopicsSelector = (leader: ExtendedGroupProtocolSubscription, members: Map<string, ExtendedGroupProtocolSubscription>) => string[];
|
|
68
69
|
export type GroupProtocolsMetadataCallback = (protocols: GroupProtocolSubscription[], topics: string[], metadata: ClusterMetadata, callback: Callback<Buffer>) => Buffer | Promise<Buffer> | undefined;
|
|
69
70
|
export declare const MessagesStreamModes: {
|
|
70
71
|
readonly LATEST: "latest";
|
|
@@ -93,6 +94,7 @@ export interface GroupOptions {
|
|
|
93
94
|
protocols?: GroupProtocolSubscription[];
|
|
94
95
|
protocolsMetadata?: GroupProtocolsMetadataCallback;
|
|
95
96
|
partitionAssigner?: GroupPartitionsAssigner;
|
|
97
|
+
partitionAssignerTopicsSelector?: GroupPartitionsAssignerTopicsSelector;
|
|
96
98
|
assignmentUserData?: Buffer;
|
|
97
99
|
}
|
|
98
100
|
export interface ConsumerGroupOptions {
|
|
@@ -7,6 +7,7 @@ export type ErrorCode = (typeof errorCodes)[number];
|
|
|
7
7
|
export type ErrorProperties = {
|
|
8
8
|
cause?: Error;
|
|
9
9
|
} & Record<string, any>;
|
|
10
|
+
export declare function findNestedErrorBy(error: Error, property: string, value: unknown, visited: Set<Error>): Error | null;
|
|
10
11
|
export declare class GenericError extends Error {
|
|
11
12
|
code: string;
|
|
12
13
|
[index: string]: any;
|
|
@@ -35,6 +35,9 @@ export interface SASLOptions {
|
|
|
35
35
|
oauthBearerExtensions?: Record<string, string> | CredentialProvider<Record<string, string>>;
|
|
36
36
|
authenticate?: SASLCustomAuthenticator;
|
|
37
37
|
authBytesValidator?: (authBytes: Buffer, callback: CallbackWithPromise<Buffer>) => void;
|
|
38
|
+
reauthFraction?: number;
|
|
39
|
+
reauthLeadTime?: number;
|
|
40
|
+
lazyReauthentication?: boolean;
|
|
38
41
|
}
|
|
39
42
|
export interface ConnectionOptions {
|
|
40
43
|
connectTimeout?: number;
|
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.13.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/kafka",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.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)",
|
|
@@ -95,12 +95,13 @@
|
|
|
95
95
|
"test": "c8 -c test/config/c8-local.json ./scripts/node --test --test-reporter=cleaner-spec-reporter 'test/*.test.ts' 'test/*/*.test.ts' 'test/*/*/*.test.ts'",
|
|
96
96
|
"test:ci": "./scripts/node --test --experimental-test-coverage --test-coverage-include='src/**' --test-coverage-lines=90 --test-coverage-branches=90 --test-coverage-functions=90 --test-reporter=cleaner-spec-reporter 'test/*.test.ts' 'test/*/*.test.ts' 'test/*/*/*.test.ts'",
|
|
97
97
|
"test:integrity": "./scripts/node --test --test-reporter=cleaner-spec-reporter 'regression/integrity/*.test.ts'",
|
|
98
|
-
"test:performance": "./scripts/node --test --test-reporter=cleaner-spec-reporter 'regression/performance/*.test.ts'",
|
|
98
|
+
"test:performance": "./scripts/node --test --test-concurrency=1 --test-reporter=cleaner-spec-reporter 'regression/performance/*.test.ts'",
|
|
99
99
|
"test:regression": "npm run test:integrity",
|
|
100
100
|
"test:regression:all": "npm run test:integrity && npm run test:performance",
|
|
101
101
|
"test:all": "npm test && npm run test:regression:all",
|
|
102
102
|
"test:compat": "./scripts/node --test --test-reporter=cleaner-spec-reporter 'test/integration/*.compat-test.ts'",
|
|
103
103
|
"test:memory": "NODE_OPTIONS='--expose-gc' ./scripts/node --test --test-reporter=cleaner-spec-reporter 'test/**/*.memory-test.ts'",
|
|
104
|
+
"test:e2e:upgrade": "./scripts/node --test --test-reporter=cleaner-spec-reporter 'test/e2e/client-upgrade.e2e-test.ts'",
|
|
104
105
|
"test:e2e:redpanda": "./scripts/node --test --test-reporter=cleaner-spec-reporter 'test/e2e/redpanda.e2e-test.ts'",
|
|
105
106
|
"test:e2e:eventhubs": "./scripts/node --test --test-reporter=cleaner-spec-reporter 'test/e2e/eventhubs.e2e-test.ts'",
|
|
106
107
|
"test:docker:up": "docker-compose up -d --wait",
|