@platformatic/kafka 1.33.0 → 1.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apis/admin/alter-partition-reassignments-v1.js +1 -0
- package/dist/clients/admin/admin.d.ts +1 -1
- package/dist/clients/admin/admin.js +2 -1
- package/dist/clients/base/base.d.ts +1 -0
- package/dist/clients/base/base.js +14 -4
- package/dist/clients/consumer/consumer.js +11 -1
- package/dist/clients/consumer/messages-stream.d.ts +1 -0
- package/dist/clients/consumer/messages-stream.js +24 -9
- package/dist/clients/producer/partitioners.js +2 -0
- package/dist/clients/producer/producer.d.ts +6 -0
- package/dist/clients/producer/producer.js +125 -24
- package/dist/errors.js +1 -1
- package/dist/network/connection-pool.d.ts +3 -0
- package/dist/network/connection-pool.js +9 -0
- package/dist/network/connection.js +1 -0
- package/dist/typescript-4/dist/clients/admin/admin.d.ts +1 -1
- package/dist/typescript-4/dist/clients/base/base.d.ts +1 -0
- package/dist/typescript-4/dist/clients/consumer/messages-stream.d.ts +1 -0
- package/dist/typescript-4/dist/clients/producer/producer.d.ts +6 -0
- package/dist/typescript-4/dist/network/connection-pool.d.ts +3 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -39,6 +39,7 @@ export function parseResponse(_correlationId, apiKey, apiVersion, reader) {
|
|
|
39
39
|
const throttleTimeMs = reader.readInt32();
|
|
40
40
|
const errorCode = reader.readInt16();
|
|
41
41
|
const errorMessage = reader.readNullableString();
|
|
42
|
+
/* c8 ignore next 3 - Hard to test */
|
|
42
43
|
if (errorCode !== 0) {
|
|
43
44
|
errors.push(['', [errorCode, errorMessage]]);
|
|
44
45
|
}
|
|
@@ -5,7 +5,7 @@ import { type CallbackWithPromise } from '../../apis/callbacks.ts';
|
|
|
5
5
|
import { type Callback } from '../../apis/definitions.ts';
|
|
6
6
|
import { type Acl } from '../../apis/types.ts';
|
|
7
7
|
import { Base } from '../base/base.ts';
|
|
8
|
-
import { type AdminListOffsetsOptions, type AdminOptions, type AlterClientQuotasOptions, type AlterConfigsOptions, type AlterConsumerGroupOffsetsOptions, type BrokerLogDirDescription, type ConfigDescription, type CreateAclsOptions, type CreatedTopic, type CreatePartitionsOptions, type CreateTopicsOptions, type DeleteAclsOptions, type DeleteConsumerGroupOffsetsOptions, type
|
|
8
|
+
import { type AdminListOffsetsOptions, type AdminOptions, type AlterClientQuotasOptions, type AlterConfigsOptions, type AlterConsumerGroupOffsetsOptions, type BrokerLogDirDescription, type ConfigDescription, type CreateAclsOptions, type CreatedTopic, type CreatePartitionsOptions, type CreateTopicsOptions, type DeleteAclsOptions, type DeleteConsumerGroupOffsetsOptions, type DeletedRecordsTopic, type DeleteGroupsOptions, type DeleteRecordsOptions, type DeleteTopicsOptions, type DescribeAclsOptions, type DescribeClientQuotasOptions, type DescribeConfigsOptions, type DescribeGroupsOptions, type DescribeLogDirsOptions, type FindCoordinatorOptions, type FindCoordinatorResult, type Group, type GroupBase, type IncrementalAlterConfigsOptions, type ListConsumerGroupOffsetsGroup, type ListConsumerGroupOffsetsOptions, type ListedOffsetsTopic, type ListGroupsOptions, type ListTopicsOptions, type RemoveMembersFromConsumerGroupOptions } from './types.ts';
|
|
9
9
|
export declare class Admin extends Base<AdminOptions> {
|
|
10
10
|
#private;
|
|
11
11
|
constructor(options: AdminOptions);
|
|
@@ -4,7 +4,7 @@ import { adminAclsChannel, adminClientQuotasChannel, adminConfigsChannel, adminC
|
|
|
4
4
|
import { MultipleErrors, 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
|
-
import { adminListOffsetsOptionsValidator, alterClientQuotasOptionsValidator, alterConfigsOptionsValidator, alterConsumerGroupOffsetsOptionsValidator, createAclsOptionsValidator, createPartitionsOptionsValidator, createTopicsOptionsValidator, deleteAclsOptionsValidator, deleteConsumerGroupOffsetsOptionsValidator,
|
|
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";
|
|
8
8
|
export class Admin extends Base {
|
|
9
9
|
#controller = null;
|
|
10
10
|
constructor(options) {
|
|
@@ -1391,6 +1391,7 @@ export class Admin extends Base {
|
|
|
1391
1391
|
// topics.get(topic.name) must be defined as the metadata request was successful
|
|
1392
1392
|
const topicData = topics.get(topic.name);
|
|
1393
1393
|
const targetPartitionData = topicData.partitions[partition.partitionIndex];
|
|
1394
|
+
/* c8 ignore next 4 - Hard to test */
|
|
1394
1395
|
if (!targetPartitionData) {
|
|
1395
1396
|
callback(new UserError(`Unknown partition ${partition.partitionIndex} for topic ${topic.name}.`));
|
|
1396
1397
|
return;
|
|
@@ -66,6 +66,7 @@ export declare class Base<OptionsType extends BaseOptions = BaseOptions, EventsT
|
|
|
66
66
|
get closed(): boolean;
|
|
67
67
|
get type(): ClientType;
|
|
68
68
|
get context(): unknown;
|
|
69
|
+
get connections(): ConnectionPool;
|
|
69
70
|
emitWithDebug(section: string | null, name: string, ...args: any[]): boolean;
|
|
70
71
|
close(callback: CallbackWithPromise<void>): void;
|
|
71
72
|
close(): Promise<void>;
|
|
@@ -90,6 +90,9 @@ export class Base extends TypedEventEmitter {
|
|
|
90
90
|
get context() {
|
|
91
91
|
return this[kContext];
|
|
92
92
|
}
|
|
93
|
+
get connections() {
|
|
94
|
+
return this[kConnections];
|
|
95
|
+
}
|
|
93
96
|
emitWithDebug(section, name, ...args) {
|
|
94
97
|
if (!section) {
|
|
95
98
|
return this.emit(name, ...args);
|
|
@@ -254,14 +257,19 @@ export class Base extends TypedEventEmitter {
|
|
|
254
257
|
this.once('client:close', onClose);
|
|
255
258
|
}
|
|
256
259
|
else {
|
|
257
|
-
if (attempt
|
|
258
|
-
|
|
259
|
-
return;
|
|
260
|
+
if (attempt > 0) {
|
|
261
|
+
error = new MultipleErrors(`${operationId} failed ${attempt + 1} times.`, errors);
|
|
260
262
|
}
|
|
261
|
-
|
|
263
|
+
this.emitWithDebug('client', 'performWithRetry:failed', operationId, retries, errors);
|
|
264
|
+
callback(error);
|
|
265
|
+
return;
|
|
262
266
|
}
|
|
263
267
|
return;
|
|
264
268
|
}
|
|
269
|
+
// Make sure all previous errors are deleted
|
|
270
|
+
if (errors.length > 0) {
|
|
271
|
+
errors.splice(0, errors.length);
|
|
272
|
+
}
|
|
265
273
|
callback(null, result);
|
|
266
274
|
});
|
|
267
275
|
return callback[kCallbackPromise];
|
|
@@ -398,6 +406,7 @@ export class Base extends TypedEventEmitter {
|
|
|
398
406
|
const unknownTopicError = error.findBy('apiCode', 3);
|
|
399
407
|
if (unknownTopicError) {
|
|
400
408
|
const topicIndexMatch = unknownTopicError.path?.match(/\/topics\/(\d+)/);
|
|
409
|
+
/* c8 ignore next 3 - Hard to test */
|
|
401
410
|
const topicIndex = topicIndexMatch ? parseInt(topicIndexMatch[1]) : -1;
|
|
402
411
|
const topicName = topicIndex >= 0 && topicIndex < topicsToFetch.length ? topicsToFetch[topicIndex] : 'unknown';
|
|
403
412
|
deduplicateCallback(new UserError(`Unknown topic ${topicName}.`));
|
|
@@ -405,6 +414,7 @@ export class Base extends TypedEventEmitter {
|
|
|
405
414
|
}
|
|
406
415
|
const hasStaleMetadata = error.findBy('hasStaleMetadata', true);
|
|
407
416
|
// Stale metadata, we need to fetch everything again
|
|
417
|
+
/* c8 ignore next 4 - Hard to test */
|
|
408
418
|
if (hasStaleMetadata) {
|
|
409
419
|
this.clearMetadata();
|
|
410
420
|
topicsToFetch = topics;
|
|
@@ -483,7 +483,7 @@ export class Consumer extends Base {
|
|
|
483
483
|
callback(error, response);
|
|
484
484
|
}, 0);
|
|
485
485
|
}
|
|
486
|
-
#commit(options, callback) {
|
|
486
|
+
#commit(options, callback, rejoinAttempts = 0) {
|
|
487
487
|
this.#performGroupOperation('commit', (connection, groupCallback) => {
|
|
488
488
|
const topics = new Map();
|
|
489
489
|
for (const { topic, partition, offset, leaderEpoch } of options.offsets) {
|
|
@@ -507,6 +507,16 @@ export class Consumer extends Base {
|
|
|
507
507
|
api(connection, this.groupId, this.#useConsumerGroupProtocol ? this.#memberEpoch : this.generationId, this.memberId, null, Array.from(topics.values()), groupCallback);
|
|
508
508
|
});
|
|
509
509
|
}, error => {
|
|
510
|
+
if (error && rejoinAttempts < this[kOptions].retries && this.#getRejoinError(error)) {
|
|
511
|
+
this.joinGroup({}, joinError => {
|
|
512
|
+
if (joinError) {
|
|
513
|
+
callback(joinError);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
this.#commit(options, callback, rejoinAttempts + 1);
|
|
517
|
+
});
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
510
520
|
callback(error);
|
|
511
521
|
});
|
|
512
522
|
}
|
|
@@ -19,6 +19,7 @@ export declare class MessagesStream<Key, Value, HeaderKey, HeaderValue> extends
|
|
|
19
19
|
get offsetsToCommit(): Map<string, CommitOptionsPartition>;
|
|
20
20
|
get offsetsCommitted(): Map<string, bigint>;
|
|
21
21
|
get committedOffsets(): Map<string, bigint>;
|
|
22
|
+
get connections(): ConnectionPool;
|
|
22
23
|
close(callback: CallbackWithPromise<void>): void;
|
|
23
24
|
close(): Promise<void>;
|
|
24
25
|
isActive(): boolean;
|
|
@@ -61,6 +61,8 @@ export class MessagesStream extends Readable {
|
|
|
61
61
|
#metricsConsumedMessages;
|
|
62
62
|
#corruptedMessageHandler;
|
|
63
63
|
#context;
|
|
64
|
+
#onConsumerGroupJoin;
|
|
65
|
+
#onBrokerDisconnect;
|
|
64
66
|
#pushRecordsOperation;
|
|
65
67
|
[kInstance];
|
|
66
68
|
/*
|
|
@@ -122,6 +124,14 @@ export class MessagesStream extends Readable {
|
|
|
122
124
|
this.#closeCallbacks = [];
|
|
123
125
|
this.#corruptedMessageHandler = onCorruptedMessage ?? defaultCorruptedMessageHandler;
|
|
124
126
|
this.#context = context;
|
|
127
|
+
this.#onConsumerGroupJoin = () => {
|
|
128
|
+
this.#offsetsCommitted.clear();
|
|
129
|
+
this.#partitionsEpochs.clear();
|
|
130
|
+
this.#scheduleRefreshOffsetsAndFetch();
|
|
131
|
+
};
|
|
132
|
+
this.#onBrokerDisconnect = () => {
|
|
133
|
+
this.#partitionsEpochs.clear();
|
|
134
|
+
};
|
|
125
135
|
if (registry) {
|
|
126
136
|
this.#pushRecordsOperation = this.#beforeDeserialization.bind(this, registry.getBeforeDeserializationHook());
|
|
127
137
|
}
|
|
@@ -150,18 +160,12 @@ export class MessagesStream extends Readable {
|
|
|
150
160
|
// When the consumer joins a group, we need to fetch again as the assignments
|
|
151
161
|
// will have changed so we may have gone from last with no assignments to
|
|
152
162
|
// having some.
|
|
153
|
-
this.#consumer.on('consumer:group:join',
|
|
154
|
-
this.#offsetsCommitted.clear();
|
|
155
|
-
this.#partitionsEpochs.clear();
|
|
156
|
-
this.#scheduleRefreshOffsetsAndFetch();
|
|
157
|
-
});
|
|
163
|
+
this.#consumer.on('consumer:group:join', this.#onConsumerGroupJoin);
|
|
158
164
|
if (consumer[kPrometheus]) {
|
|
159
165
|
this.#metricsConsumedMessages = ensureMetric(consumer[kPrometheus], 'Counter', 'kafka_consumed_messages', 'Number of consumed Kafka messages');
|
|
160
166
|
}
|
|
161
167
|
// Whenever the consumer loses a connection, reset all the partitions epochs
|
|
162
|
-
consumer.on('client:broker:disconnect',
|
|
163
|
-
this.#partitionsEpochs.clear();
|
|
164
|
-
});
|
|
168
|
+
consumer.on('client:broker:disconnect', this.#onBrokerDisconnect);
|
|
165
169
|
notifyCreation('messages-stream', this);
|
|
166
170
|
}
|
|
167
171
|
/* c8 ignore next 3 - Simple getter */
|
|
@@ -188,6 +192,10 @@ export class MessagesStream extends Readable {
|
|
|
188
192
|
get committedOffsets() {
|
|
189
193
|
return this.#offsetsCommitted;
|
|
190
194
|
}
|
|
195
|
+
/* c8 ignore next 3 - Simple getter */
|
|
196
|
+
get connections() {
|
|
197
|
+
return this[kConnections];
|
|
198
|
+
}
|
|
191
199
|
close(callback) {
|
|
192
200
|
if (!callback) {
|
|
193
201
|
callback = createPromisifiedCallback();
|
|
@@ -304,6 +312,8 @@ export class MessagesStream extends Readable {
|
|
|
304
312
|
if (this.#autocommitInterval) {
|
|
305
313
|
clearInterval(this.#autocommitInterval);
|
|
306
314
|
}
|
|
315
|
+
this.#consumer.removeListener('consumer:group:join', this.#onConsumerGroupJoin);
|
|
316
|
+
this.#consumer.removeListener('client:broker:disconnect', this.#onBrokerDisconnect);
|
|
307
317
|
this[kConnections].close(closeError => {
|
|
308
318
|
callback(closeError ?? error);
|
|
309
319
|
});
|
|
@@ -589,7 +599,12 @@ export class MessagesStream extends Readable {
|
|
|
589
599
|
this.#autocommitInflight = false;
|
|
590
600
|
if (error) {
|
|
591
601
|
this.emit('autocommit', error);
|
|
592
|
-
|
|
602
|
+
// Only destroy the stream when the broker requires a group rejoin
|
|
603
|
+
// (ILLEGAL_GENERATION, UNKNOWN_MEMBER_ID, REBALANCE_IN_PROGRESS).
|
|
604
|
+
// Transient coordinator errors must not tear down the consumption loop.
|
|
605
|
+
if (error.findBy?.('needsRejoin', true)) {
|
|
606
|
+
this.destroy(error);
|
|
607
|
+
}
|
|
593
608
|
return;
|
|
594
609
|
}
|
|
595
610
|
for (const { topic, partition, offset } of offsets) {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { murmur2 } from "../../protocol/murmur2.js";
|
|
2
2
|
const compatibilityMurmur2Mask = 0x7fffffff;
|
|
3
3
|
export function defaultPartitioner(_, key) {
|
|
4
|
+
/* c8 ignore next - Hard to test */
|
|
4
5
|
return Buffer.isBuffer(key) ? murmur2(key) >>> 0 : 0;
|
|
5
6
|
}
|
|
6
7
|
export function compatibilityPartitioner(_, key) {
|
|
8
|
+
/* c8 ignore next - Hard to test */
|
|
7
9
|
return Buffer.isBuffer(key) ? murmur2(key) & compatibilityMurmur2Mask : 0;
|
|
8
10
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type CallbackWithPromise } from '../../apis/callbacks.ts';
|
|
2
|
+
import { type Broker, type Connection } from '../../network/connection.ts';
|
|
2
3
|
import { type Message } from '../../protocol/records.ts';
|
|
3
4
|
import { kTransaction, kTransactionAddOffsets, kTransactionAddPartitions, kTransactionCancel, kTransactionCommitOffset, kTransactionEnd, kTransactionFindCoordinator } from '../../symbols.ts';
|
|
4
5
|
import { Base } from '../base/base.ts';
|
|
@@ -27,6 +28,11 @@ export declare class Producer<Key = Buffer, Value = Buffer, HeaderKey = Buffer,
|
|
|
27
28
|
asStream(options?: ProducerStreamOptions<Key, Value, HeaderKey, HeaderValue>): ProducerStream<Key, Value, HeaderKey, HeaderValue>;
|
|
28
29
|
beginTransaction(options: ProduceOptions<Key, Value, HeaderKey, HeaderValue>, callback: CallbackWithPromise<Transaction<Key, Value, HeaderKey, HeaderValue>>): void;
|
|
29
30
|
beginTransaction(options?: ProduceOptions<Key, Value, HeaderKey, HeaderValue>): Promise<Transaction<Key, Value, HeaderKey, HeaderValue>>;
|
|
31
|
+
getSendTopicPartitions(options: SendOptions<Key, Value, HeaderKey, HeaderValue>): Map<string, Set<number>>;
|
|
32
|
+
getSendBrokers(options: SendOptions<Key, Value, HeaderKey, HeaderValue>, callback: CallbackWithPromise<Record<string, Broker>>): void;
|
|
33
|
+
getSendBrokers(options: SendOptions<Key, Value, HeaderKey, HeaderValue>): Promise<Record<string, Broker>>;
|
|
34
|
+
getSendConnections(options: SendOptions<Key, Value, HeaderKey, HeaderValue>, callback: CallbackWithPromise<Record<string, Connection>>): void;
|
|
35
|
+
getSendConnections(options: SendOptions<Key, Value, HeaderKey, HeaderValue>): Promise<Record<string, Connection>>;
|
|
30
36
|
[kTransactionFindCoordinator](callback: CallbackWithPromise<void>): void;
|
|
31
37
|
[kTransactionAddPartitions](transactionId: number, topicsPartitions: Map<string, Set<number>>, callback: CallbackWithPromise<void>): void;
|
|
32
38
|
[kTransactionAddOffsets](transactionId: number, groupId: string, callback: CallbackWithPromise<void>): void;
|
|
@@ -243,6 +243,98 @@ export class Producer extends Base {
|
|
|
243
243
|
producerTransactionsChannel.traceCallback(this.#transaction[kTransactionPrepare], 2, createDiagnosticContext({ client: this, operation: 'begin' }), this.#transaction, this.#producerInfo?.transactionalId, options, callback);
|
|
244
244
|
return callback[kCallbackPromise];
|
|
245
245
|
}
|
|
246
|
+
getSendTopicPartitions(options) {
|
|
247
|
+
const topicsPartitions = new Map();
|
|
248
|
+
const partitioner = options.partitioner ?? this[kOptions].partitioner;
|
|
249
|
+
for (const message of options.messages) {
|
|
250
|
+
const topic = message.topic;
|
|
251
|
+
let key;
|
|
252
|
+
let headers = new Map();
|
|
253
|
+
try {
|
|
254
|
+
if (message.headers) {
|
|
255
|
+
headers =
|
|
256
|
+
message.headers instanceof Map
|
|
257
|
+
? message.headers
|
|
258
|
+
: new Map(Object.entries(message.headers));
|
|
259
|
+
}
|
|
260
|
+
key = this.#keySerializer(message.key, headers, message);
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
throw new UserError('Failed to serialize a message.', { cause: error });
|
|
264
|
+
}
|
|
265
|
+
const partition = this.#assignPartition(message, partitioner, key, topic);
|
|
266
|
+
let partitions = topicsPartitions.get(topic);
|
|
267
|
+
if (!partitions) {
|
|
268
|
+
partitions = new Set();
|
|
269
|
+
topicsPartitions.set(topic, partitions);
|
|
270
|
+
}
|
|
271
|
+
partitions.add(partition);
|
|
272
|
+
}
|
|
273
|
+
return topicsPartitions;
|
|
274
|
+
}
|
|
275
|
+
getSendBrokers(options, callback) {
|
|
276
|
+
if (!callback) {
|
|
277
|
+
callback = createPromisifiedCallback();
|
|
278
|
+
}
|
|
279
|
+
if (this[kCheckNotClosed](callback)) {
|
|
280
|
+
return callback[kCallbackPromise];
|
|
281
|
+
}
|
|
282
|
+
let topicsPartitions;
|
|
283
|
+
try {
|
|
284
|
+
topicsPartitions = this.getSendTopicPartitions(options);
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
callback(error);
|
|
288
|
+
return callback[kCallbackPromise];
|
|
289
|
+
}
|
|
290
|
+
this[kMetadata]({ topics: Array.from(topicsPartitions.keys()), autocreateTopics: options.autocreateTopics }, (error, metadata) => {
|
|
291
|
+
if (error) {
|
|
292
|
+
callback(error);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const brokers = {};
|
|
296
|
+
for (const [topic, partitions] of topicsPartitions) {
|
|
297
|
+
for (const rawPartition of partitions) {
|
|
298
|
+
const partition = rawPartition & metadata.topics.get(topic).partitionsCount;
|
|
299
|
+
const leader = metadata.topics.get(topic).partitions[partition].leader;
|
|
300
|
+
const broker = metadata.brokers.get(leader);
|
|
301
|
+
brokers[`${topic}:${partition}`] = broker;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
callback(null, brokers);
|
|
305
|
+
});
|
|
306
|
+
return callback[kCallbackPromise];
|
|
307
|
+
}
|
|
308
|
+
getSendConnections(options, callback) {
|
|
309
|
+
if (!callback) {
|
|
310
|
+
callback = createPromisifiedCallback();
|
|
311
|
+
}
|
|
312
|
+
if (this[kCheckNotClosed](callback)) {
|
|
313
|
+
return callback[kCallbackPromise];
|
|
314
|
+
}
|
|
315
|
+
this.getSendBrokers(options, (error, brokers) => {
|
|
316
|
+
if (error) {
|
|
317
|
+
callback(error);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
runConcurrentCallbacks('Preparing producer connections failed.', Object.entries(brokers), ([topicPartition, broker], concurrentCallback) => {
|
|
321
|
+
this[kGetConnection](broker, (error, connection) => {
|
|
322
|
+
if (error) {
|
|
323
|
+
concurrentCallback(error);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
concurrentCallback(null, [topicPartition, connection]);
|
|
327
|
+
});
|
|
328
|
+
}, (error, results) => {
|
|
329
|
+
if (error) {
|
|
330
|
+
callback(error);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
callback(null, Object.fromEntries(results));
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
return callback[kCallbackPromise];
|
|
337
|
+
}
|
|
246
338
|
[kTransactionFindCoordinator](callback) {
|
|
247
339
|
if (this.#coordinatorId !== undefined) {
|
|
248
340
|
callback(null);
|
|
@@ -533,22 +625,7 @@ export class Producer extends Base {
|
|
|
533
625
|
callback(new UserError('Failed to serialize a message.', { cause: error }));
|
|
534
626
|
return;
|
|
535
627
|
}
|
|
536
|
-
|
|
537
|
-
if (typeof message.partition !== 'number') {
|
|
538
|
-
if (partitioner) {
|
|
539
|
-
partition = partitioner(message, key);
|
|
540
|
-
}
|
|
541
|
-
else if (key) {
|
|
542
|
-
partition = defaultPartitioner(message, key);
|
|
543
|
-
}
|
|
544
|
-
else {
|
|
545
|
-
// Use the roundrobin
|
|
546
|
-
partition = this.#partitionsRoundRobin.postIncrement(topic, 1, 0);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
else {
|
|
550
|
-
partition = message.partition;
|
|
551
|
-
}
|
|
628
|
+
const partition = this.#assignPartition(message, partitioner, key, topic);
|
|
552
629
|
topics.add(topic);
|
|
553
630
|
messages.push({
|
|
554
631
|
key,
|
|
@@ -610,16 +687,12 @@ export class Producer extends Base {
|
|
|
610
687
|
nodes.push(destination);
|
|
611
688
|
this.#performSingleDestinationSend(topics, destinationMessages, this[kOptions].timeout, sendOptions.acks, sendOptions.autocreateTopics, sendOptions.repeatOnStaleMetadata, produceOptions, concurrentCallback);
|
|
612
689
|
}, (error, apiResults) => {
|
|
613
|
-
if (error) {
|
|
614
|
-
callback(error);
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
690
|
this.#metricsProducedMessages?.inc(messages.length);
|
|
618
691
|
const results = {};
|
|
619
692
|
if (sendOptions.acks === ProduceAcks.NO_RESPONSE) {
|
|
620
693
|
const unwritableNodes = [];
|
|
621
694
|
for (let i = 0; i < apiResults.length; i++) {
|
|
622
|
-
if (apiResults[i] === false) {
|
|
695
|
+
if (typeof apiResults[i] === 'undefined' || apiResults[i] === false) {
|
|
623
696
|
unwritableNodes.push(nodes[i]);
|
|
624
697
|
}
|
|
625
698
|
}
|
|
@@ -628,7 +701,7 @@ export class Producer extends Base {
|
|
|
628
701
|
else {
|
|
629
702
|
const topics = [];
|
|
630
703
|
for (const result of apiResults) {
|
|
631
|
-
for (const { name, partitionResponses } of result
|
|
704
|
+
for (const { name, partitionResponses } of result?.responses ?? []) {
|
|
632
705
|
for (const partitionResponse of partitionResponses) {
|
|
633
706
|
topics.push({
|
|
634
707
|
topic: name,
|
|
@@ -642,7 +715,14 @@ export class Producer extends Base {
|
|
|
642
715
|
results.offsets = topics;
|
|
643
716
|
}
|
|
644
717
|
}
|
|
645
|
-
|
|
718
|
+
if (error) {
|
|
719
|
+
;
|
|
720
|
+
error.produced = results;
|
|
721
|
+
callback(error);
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
callback(null, results);
|
|
725
|
+
}
|
|
646
726
|
});
|
|
647
727
|
});
|
|
648
728
|
}
|
|
@@ -673,6 +753,7 @@ export class Producer extends Base {
|
|
|
673
753
|
if (error) {
|
|
674
754
|
// If the last error was due to stale metadata, we retry the operation with this set of messages
|
|
675
755
|
// since the partition is already set, it should attempt on the new destination
|
|
756
|
+
/* c8 ignore next 3 - Hard to test */
|
|
676
757
|
const kafkaError = GenericError.isGenericError(error)
|
|
677
758
|
? error
|
|
678
759
|
: null;
|
|
@@ -682,11 +763,12 @@ export class Producer extends Base {
|
|
|
682
763
|
this.#performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, false, produceOptions, callback);
|
|
683
764
|
return;
|
|
684
765
|
}
|
|
685
|
-
callback(error);
|
|
766
|
+
callback(error, results);
|
|
686
767
|
return;
|
|
687
768
|
}
|
|
688
769
|
callback(error, results);
|
|
689
770
|
}, 0, [], error => {
|
|
771
|
+
/* c8 ignore next 3 - Hard to test */
|
|
690
772
|
if (!repeatOnStaleMetadata || !GenericError.isGenericError(error)) {
|
|
691
773
|
return false;
|
|
692
774
|
}
|
|
@@ -752,4 +834,23 @@ export class Producer extends Base {
|
|
|
752
834
|
this.#send(options, callback);
|
|
753
835
|
});
|
|
754
836
|
}
|
|
837
|
+
#assignPartition(message, partitioner, key, topic) {
|
|
838
|
+
let partition = 0;
|
|
839
|
+
if (typeof message.partition !== 'number') {
|
|
840
|
+
if (partitioner) {
|
|
841
|
+
partition = partitioner(message, key);
|
|
842
|
+
}
|
|
843
|
+
else if (key) {
|
|
844
|
+
partition = defaultPartitioner(message, key);
|
|
845
|
+
}
|
|
846
|
+
else {
|
|
847
|
+
// Use the roundrobin
|
|
848
|
+
partition = this.#partitionsRoundRobin.postIncrement(topic, 1, 0);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
else {
|
|
852
|
+
partition = message.partition;
|
|
853
|
+
}
|
|
854
|
+
return partition;
|
|
855
|
+
}
|
|
755
856
|
}
|
package/dist/errors.js
CHANGED
|
@@ -120,7 +120,7 @@ export class ProtocolError extends GenericError {
|
|
|
120
120
|
'NOT_LEADER_OR_FOLLOWER',
|
|
121
121
|
'FENCED_LEADER_EPOCH'
|
|
122
122
|
].includes(id),
|
|
123
|
-
needsRejoin: ['MEMBER_ID_REQUIRED', 'UNKNOWN_MEMBER_ID', 'REBALANCE_IN_PROGRESS'].includes(id),
|
|
123
|
+
needsRejoin: ['MEMBER_ID_REQUIRED', 'UNKNOWN_MEMBER_ID', 'REBALANCE_IN_PROGRESS', 'ILLEGAL_GENERATION'].includes(id),
|
|
124
124
|
producerFenced: id === 'INVALID_PRODUCER_EPOCH',
|
|
125
125
|
rebalanceInProgress: id === 'REBALANCE_IN_PROGRESS',
|
|
126
126
|
unknownMemberId: id === 'UNKNOWN_MEMBER_ID',
|
|
@@ -27,12 +27,15 @@ export declare class ConnectionPool extends TypedEventEmitter<ConnectionPoolEven
|
|
|
27
27
|
get instanceId(): number;
|
|
28
28
|
get ownerId(): number | undefined;
|
|
29
29
|
get context(): unknown;
|
|
30
|
+
[Symbol.iterator](): MapIterator<[string, Connection]>;
|
|
30
31
|
get(broker: Broker, callback: CallbackWithPromise<Connection>): void;
|
|
31
32
|
get(broker: Broker): Promise<Connection>;
|
|
32
33
|
getFirstAvailable(brokers: Broker[], callback: CallbackWithPromise<Connection>): void;
|
|
33
34
|
getFirstAvailable(brokers: Broker[]): Promise<Connection>;
|
|
35
|
+
getEstablishedConnection(broker: Broker): Connection | undefined;
|
|
34
36
|
close(callback: CallbackWithPromise<void>): void;
|
|
35
37
|
close(): Promise<void>;
|
|
36
38
|
isActive(): boolean;
|
|
37
39
|
isConnected(): boolean;
|
|
40
|
+
has(broker: Broker): boolean;
|
|
38
41
|
}
|
|
@@ -34,6 +34,9 @@ export class ConnectionPool extends TypedEventEmitter {
|
|
|
34
34
|
get context() {
|
|
35
35
|
return this.#context;
|
|
36
36
|
}
|
|
37
|
+
[Symbol.iterator]() {
|
|
38
|
+
return this.#connections[Symbol.iterator]();
|
|
39
|
+
}
|
|
37
40
|
get(broker, callback) {
|
|
38
41
|
if (!callback) {
|
|
39
42
|
callback = createPromisifiedCallback();
|
|
@@ -48,6 +51,9 @@ export class ConnectionPool extends TypedEventEmitter {
|
|
|
48
51
|
connectionsPoolGetsChannel.traceCallback(this.#getFirstAvailable, 3, createDiagnosticContext({ connectionPool: this, brokers, operation: 'getFirstAvailable' }), this, brokers, 0, [], callback);
|
|
49
52
|
return callback[kCallbackPromise];
|
|
50
53
|
}
|
|
54
|
+
getEstablishedConnection(broker) {
|
|
55
|
+
return this.#connections.get(`${broker.host}:${broker.port}`);
|
|
56
|
+
}
|
|
51
57
|
close(callback) {
|
|
52
58
|
if (!callback) {
|
|
53
59
|
callback = createPromisifiedCallback();
|
|
@@ -81,6 +87,9 @@ export class ConnectionPool extends TypedEventEmitter {
|
|
|
81
87
|
}
|
|
82
88
|
return true;
|
|
83
89
|
}
|
|
90
|
+
has(broker) {
|
|
91
|
+
return this.#connections.has(`${broker.host}:${broker.port}`);
|
|
92
|
+
}
|
|
84
93
|
#get(broker, callback) {
|
|
85
94
|
if (this.#closed) {
|
|
86
95
|
callback(new Error('Connection pool is closed.'));
|
|
@@ -122,6 +122,7 @@ export class Connection extends TypedEventEmitter {
|
|
|
122
122
|
connectionOptions.servername =
|
|
123
123
|
typeof this.#options.tlsServerName === 'string' ? this.#options.tlsServerName : host;
|
|
124
124
|
}
|
|
125
|
+
/* c8 ignore next 13 - Hard to test */
|
|
125
126
|
const connectingSocketTimeoutHandler = () => {
|
|
126
127
|
const error = new TimeoutError(`Connection to ${host}:${port} timed out.`);
|
|
127
128
|
diagnosticContext.error = error;
|
|
@@ -5,7 +5,7 @@ import { type CallbackWithPromise } from "../../apis/callbacks";
|
|
|
5
5
|
import { type Callback } from "../../apis/definitions";
|
|
6
6
|
import { type Acl } from "../../apis/types";
|
|
7
7
|
import { Base } from "../base/base";
|
|
8
|
-
import { type AdminListOffsetsOptions, type AdminOptions, type AlterClientQuotasOptions, type AlterConfigsOptions, type AlterConsumerGroupOffsetsOptions, type BrokerLogDirDescription, type ConfigDescription, type CreateAclsOptions, type CreatedTopic, type CreatePartitionsOptions, type CreateTopicsOptions, type DeleteAclsOptions, type DeleteConsumerGroupOffsetsOptions, type
|
|
8
|
+
import { type AdminListOffsetsOptions, type AdminOptions, type AlterClientQuotasOptions, type AlterConfigsOptions, type AlterConsumerGroupOffsetsOptions, type BrokerLogDirDescription, type ConfigDescription, type CreateAclsOptions, type CreatedTopic, type CreatePartitionsOptions, type CreateTopicsOptions, type DeleteAclsOptions, type DeleteConsumerGroupOffsetsOptions, type DeletedRecordsTopic, type DeleteGroupsOptions, type DeleteRecordsOptions, type DeleteTopicsOptions, type DescribeAclsOptions, type DescribeClientQuotasOptions, type DescribeConfigsOptions, type DescribeGroupsOptions, type DescribeLogDirsOptions, type FindCoordinatorOptions, type FindCoordinatorResult, type Group, type GroupBase, type IncrementalAlterConfigsOptions, type ListConsumerGroupOffsetsGroup, type ListConsumerGroupOffsetsOptions, type ListedOffsetsTopic, type ListGroupsOptions, type ListTopicsOptions, type RemoveMembersFromConsumerGroupOptions } from "./types";
|
|
9
9
|
export declare class Admin extends Base<AdminOptions> {
|
|
10
10
|
#private;
|
|
11
11
|
constructor(options: AdminOptions);
|
|
@@ -66,6 +66,7 @@ export declare class Base<OptionsType extends BaseOptions = BaseOptions, EventsT
|
|
|
66
66
|
get closed(): boolean;
|
|
67
67
|
get type(): ClientType;
|
|
68
68
|
get context(): unknown;
|
|
69
|
+
get connections(): ConnectionPool;
|
|
69
70
|
emitWithDebug(section: string | null, name: string, ...args: any[]): boolean;
|
|
70
71
|
close(callback: CallbackWithPromise<void>): void;
|
|
71
72
|
close(): Promise<void>;
|
|
@@ -19,6 +19,7 @@ export declare class MessagesStream<Key, Value, HeaderKey, HeaderValue> extends
|
|
|
19
19
|
get offsetsToCommit(): Map<string, CommitOptionsPartition>;
|
|
20
20
|
get offsetsCommitted(): Map<string, bigint>;
|
|
21
21
|
get committedOffsets(): Map<string, bigint>;
|
|
22
|
+
get connections(): ConnectionPool;
|
|
22
23
|
close(callback: CallbackWithPromise<void>): void;
|
|
23
24
|
close(): Promise<void>;
|
|
24
25
|
isActive(): boolean;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type CallbackWithPromise } from "../../apis/callbacks";
|
|
2
|
+
import { type Broker, type Connection } from "../../network/connection";
|
|
2
3
|
import { type Message } from "../../protocol/records";
|
|
3
4
|
import { kTransaction, kTransactionAddOffsets, kTransactionAddPartitions, kTransactionCancel, kTransactionCommitOffset, kTransactionEnd, kTransactionFindCoordinator } from "../../symbols";
|
|
4
5
|
import { Base } from "../base/base";
|
|
@@ -27,6 +28,11 @@ export declare class Producer<Key = Buffer, Value = Buffer, HeaderKey = Buffer,
|
|
|
27
28
|
asStream(options?: ProducerStreamOptions<Key, Value, HeaderKey, HeaderValue>): ProducerStream<Key, Value, HeaderKey, HeaderValue>;
|
|
28
29
|
beginTransaction(options: ProduceOptions<Key, Value, HeaderKey, HeaderValue>, callback: CallbackWithPromise<Transaction<Key, Value, HeaderKey, HeaderValue>>): void;
|
|
29
30
|
beginTransaction(options?: ProduceOptions<Key, Value, HeaderKey, HeaderValue>): Promise<Transaction<Key, Value, HeaderKey, HeaderValue>>;
|
|
31
|
+
getSendTopicPartitions(options: SendOptions<Key, Value, HeaderKey, HeaderValue>): Map<string, Set<number>>;
|
|
32
|
+
getSendBrokers(options: SendOptions<Key, Value, HeaderKey, HeaderValue>, callback: CallbackWithPromise<Record<string, Broker>>): void;
|
|
33
|
+
getSendBrokers(options: SendOptions<Key, Value, HeaderKey, HeaderValue>): Promise<Record<string, Broker>>;
|
|
34
|
+
getSendConnections(options: SendOptions<Key, Value, HeaderKey, HeaderValue>, callback: CallbackWithPromise<Record<string, Connection>>): void;
|
|
35
|
+
getSendConnections(options: SendOptions<Key, Value, HeaderKey, HeaderValue>): Promise<Record<string, Connection>>;
|
|
30
36
|
[kTransactionFindCoordinator](callback: CallbackWithPromise<void>): void;
|
|
31
37
|
[kTransactionAddPartitions](transactionId: number, topicsPartitions: Map<string, Set<number>>, callback: CallbackWithPromise<void>): void;
|
|
32
38
|
[kTransactionAddOffsets](transactionId: number, groupId: string, callback: CallbackWithPromise<void>): void;
|
|
@@ -27,12 +27,15 @@ export declare class ConnectionPool extends TypedEventEmitter<ConnectionPoolEven
|
|
|
27
27
|
get instanceId(): number;
|
|
28
28
|
get ownerId(): number | undefined;
|
|
29
29
|
get context(): unknown;
|
|
30
|
+
[Symbol.iterator](): MapIterator<[string, Connection]>;
|
|
30
31
|
get(broker: Broker, callback: CallbackWithPromise<Connection>): void;
|
|
31
32
|
get(broker: Broker): Promise<Connection>;
|
|
32
33
|
getFirstAvailable(brokers: Broker[], callback: CallbackWithPromise<Connection>): void;
|
|
33
34
|
getFirstAvailable(brokers: Broker[]): Promise<Connection>;
|
|
35
|
+
getEstablishedConnection(broker: Broker): Connection | undefined;
|
|
34
36
|
close(callback: CallbackWithPromise<void>): void;
|
|
35
37
|
close(): Promise<void>;
|
|
36
38
|
isActive(): boolean;
|
|
37
39
|
isConnected(): boolean;
|
|
40
|
+
has(broker: Broker): boolean;
|
|
38
41
|
}
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const name = "@platformatic/kafka";
|
|
2
|
-
export const version = "1.
|
|
2
|
+
export const version = "1.34.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/kafka",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.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)",
|