@platformatic/kafka 1.33.2 → 2.0.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/README.md +1 -2
- package/dist/apis/admin/alter-partition-reassignments-v1.js +1 -0
- package/dist/apis/callbacks.js +1 -2
- package/dist/clients/admin/admin.d.ts +1 -1
- package/dist/clients/admin/admin.js +3 -2
- package/dist/clients/base/base.d.ts +2 -0
- package/dist/clients/base/base.js +17 -4
- package/dist/clients/consumer/consumer.js +32 -5
- package/dist/clients/consumer/messages-stream.d.ts +1 -1
- package/dist/clients/consumer/messages-stream.js +23 -13
- package/dist/clients/consumer/options.d.ts +16 -0
- package/dist/clients/consumer/options.js +2 -1
- package/dist/clients/consumer/types.d.ts +3 -0
- 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/clients/producer/types.d.ts +5 -2
- 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/protocol/compression.js +23 -1
- package/dist/typescript-4/dist/clients/admin/admin.d.ts +1 -1
- package/dist/typescript-4/dist/clients/base/base.d.ts +2 -0
- package/dist/typescript-4/dist/clients/consumer/messages-stream.d.ts +1 -1
- package/dist/typescript-4/dist/clients/consumer/options.d.ts +16 -0
- package/dist/typescript-4/dist/clients/consumer/types.d.ts +3 -0
- package/dist/typescript-4/dist/clients/producer/producer.d.ts +6 -0
- package/dist/typescript-4/dist/clients/producer/types.d.ts +5 -2
- package/dist/typescript-4/dist/network/connection-pool.d.ts +3 -0
- package/dist/typescript-4/dist/utils.d.ts +0 -6
- package/dist/utils.d.ts +0 -6
- package/dist/utils.js +0 -14
- package/dist/version.js +1 -1
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -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
|
}
|
package/dist/apis/callbacks.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { MultipleErrors } from "../errors.js";
|
|
2
|
-
import { promiseWithResolvers } from "../utils.js";
|
|
3
2
|
export const kCallbackPromise = Symbol('plt.kafka.callbackPromise');
|
|
4
3
|
// This is only meaningful for testing
|
|
5
4
|
export const kNoopCallbackReturnValue = Symbol('plt.kafka.noopCallbackReturnValue');
|
|
@@ -7,7 +6,7 @@ export const noopCallback = () => {
|
|
|
7
6
|
return Promise.resolve(kNoopCallbackReturnValue);
|
|
8
7
|
};
|
|
9
8
|
export function createPromisifiedCallback() {
|
|
10
|
-
const { promise, resolve, reject } =
|
|
9
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
11
10
|
function callback(error, payload) {
|
|
12
11
|
if (error) {
|
|
13
12
|
reject(error);
|
|
@@ -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) {
|
|
@@ -641,7 +641,7 @@ export class Admin extends Base {
|
|
|
641
641
|
const topic = r.readString(false);
|
|
642
642
|
return [topic, { topic, partitions: reader.readArray(r => r.readInt32(), false, false) }];
|
|
643
643
|
}, false, false);
|
|
644
|
-
|
|
644
|
+
// Ignore the user data
|
|
645
645
|
}
|
|
646
646
|
group.members.set(member.memberId, {
|
|
647
647
|
id: member.memberId,
|
|
@@ -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,8 @@ 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 currentMetadata(): ClusterMetadata | undefined;
|
|
70
|
+
get connections(): ConnectionPool;
|
|
69
71
|
emitWithDebug(section: string | null, name: string, ...args: any[]): boolean;
|
|
70
72
|
close(callback: CallbackWithPromise<void>): void;
|
|
71
73
|
close(): Promise<void>;
|
|
@@ -90,6 +90,12 @@ export class Base extends TypedEventEmitter {
|
|
|
90
90
|
get context() {
|
|
91
91
|
return this[kContext];
|
|
92
92
|
}
|
|
93
|
+
get currentMetadata() {
|
|
94
|
+
return this.#metadata;
|
|
95
|
+
}
|
|
96
|
+
get connections() {
|
|
97
|
+
return this[kConnections];
|
|
98
|
+
}
|
|
93
99
|
emitWithDebug(section, name, ...args) {
|
|
94
100
|
if (!section) {
|
|
95
101
|
return this.emit(name, ...args);
|
|
@@ -254,14 +260,19 @@ export class Base extends TypedEventEmitter {
|
|
|
254
260
|
this.once('client:close', onClose);
|
|
255
261
|
}
|
|
256
262
|
else {
|
|
257
|
-
if (attempt
|
|
258
|
-
|
|
259
|
-
return;
|
|
263
|
+
if (attempt > 0) {
|
|
264
|
+
error = new MultipleErrors(`${operationId} failed ${attempt + 1} times.`, errors);
|
|
260
265
|
}
|
|
261
|
-
|
|
266
|
+
this.emitWithDebug('client', 'performWithRetry:failed', operationId, retries, errors);
|
|
267
|
+
callback(error);
|
|
268
|
+
return;
|
|
262
269
|
}
|
|
263
270
|
return;
|
|
264
271
|
}
|
|
272
|
+
// Make sure all previous errors are deleted
|
|
273
|
+
if (errors.length > 0) {
|
|
274
|
+
errors.splice(0, errors.length);
|
|
275
|
+
}
|
|
265
276
|
callback(null, result);
|
|
266
277
|
});
|
|
267
278
|
return callback[kCallbackPromise];
|
|
@@ -398,6 +409,7 @@ export class Base extends TypedEventEmitter {
|
|
|
398
409
|
const unknownTopicError = error.findBy('apiCode', 3);
|
|
399
410
|
if (unknownTopicError) {
|
|
400
411
|
const topicIndexMatch = unknownTopicError.path?.match(/\/topics\/(\d+)/);
|
|
412
|
+
/* c8 ignore next 3 - Hard to test */
|
|
401
413
|
const topicIndex = topicIndexMatch ? parseInt(topicIndexMatch[1]) : -1;
|
|
402
414
|
const topicName = topicIndex >= 0 && topicIndex < topicsToFetch.length ? topicsToFetch[topicIndex] : 'unknown';
|
|
403
415
|
deduplicateCallback(new UserError(`Unknown topic ${topicName}.`));
|
|
@@ -405,6 +417,7 @@ export class Base extends TypedEventEmitter {
|
|
|
405
417
|
}
|
|
406
418
|
const hasStaleMetadata = error.findBy('hasStaleMetadata', true);
|
|
407
419
|
// Stale metadata, we need to fetch everything again
|
|
420
|
+
/* c8 ignore next 4 - Hard to test */
|
|
408
421
|
if (hasStaleMetadata) {
|
|
409
422
|
this.clearMetadata();
|
|
410
423
|
topicsToFetch = topics;
|
|
@@ -172,6 +172,7 @@ export class Consumer extends Base {
|
|
|
172
172
|
}
|
|
173
173
|
options.autocommit ??= this[kOptions].autocommit ?? true;
|
|
174
174
|
options.maxBytes ??= this[kOptions].maxBytes;
|
|
175
|
+
options.maxBytesPerPartition ??= this[kOptions].maxBytesPerPartition ?? options.maxBytes;
|
|
175
176
|
options.highWaterMark ??= this[kOptions].highWaterMark;
|
|
176
177
|
options.registry ??= this[kOptions].registry;
|
|
177
178
|
options.beforeDeserialization ??= this[kOptions].beforeDeserialization;
|
|
@@ -483,7 +484,7 @@ export class Consumer extends Base {
|
|
|
483
484
|
callback(error, response);
|
|
484
485
|
}, 0);
|
|
485
486
|
}
|
|
486
|
-
#commit(options, callback) {
|
|
487
|
+
#commit(options, callback, rejoinAttempts = 0) {
|
|
487
488
|
this.#performGroupOperation('commit', (connection, groupCallback) => {
|
|
488
489
|
const topics = new Map();
|
|
489
490
|
for (const { topic, partition, offset, leaderEpoch } of options.offsets) {
|
|
@@ -507,6 +508,16 @@ export class Consumer extends Base {
|
|
|
507
508
|
api(connection, this.groupId, this.#useConsumerGroupProtocol ? this.#memberEpoch : this.generationId, this.memberId, null, Array.from(topics.values()), groupCallback);
|
|
508
509
|
});
|
|
509
510
|
}, error => {
|
|
511
|
+
if (error && rejoinAttempts < this[kOptions].retries && this.#getRejoinError(error)) {
|
|
512
|
+
this.joinGroup({}, joinError => {
|
|
513
|
+
if (joinError) {
|
|
514
|
+
callback(joinError);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
this.#commit(options, callback, rejoinAttempts + 1);
|
|
518
|
+
});
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
510
521
|
callback(error);
|
|
511
522
|
});
|
|
512
523
|
}
|
|
@@ -608,7 +619,7 @@ export class Consumer extends Base {
|
|
|
608
619
|
});
|
|
609
620
|
});
|
|
610
621
|
}
|
|
611
|
-
#listCommittedOffsets(options, callback) {
|
|
622
|
+
#listCommittedOffsets(options, callback, staleMemberEpochRetries = 0) {
|
|
612
623
|
const topics = [];
|
|
613
624
|
for (const { topic: name, partitions } of options.topics) {
|
|
614
625
|
topics.push({ name, partitionIndexes: partitions });
|
|
@@ -630,6 +641,18 @@ export class Consumer extends Base {
|
|
|
630
641
|
});
|
|
631
642
|
}, (error, response) => {
|
|
632
643
|
if (error) {
|
|
644
|
+
if (this.#useConsumerGroupProtocol &&
|
|
645
|
+
staleMemberEpochRetries < this[kOptions].retries &&
|
|
646
|
+
error.findBy?.('apiId', 'STALE_MEMBER_EPOCH')) {
|
|
647
|
+
this.#consumerGroupHeartbeat(this[kOptions], heartbeatError => {
|
|
648
|
+
if (heartbeatError) {
|
|
649
|
+
callback(this.#handleMetadataError(heartbeatError));
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
this.#listCommittedOffsets(options, callback, staleMemberEpochRetries + 1);
|
|
653
|
+
});
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
633
656
|
callback(this.#handleMetadataError(error));
|
|
634
657
|
return;
|
|
635
658
|
}
|
|
@@ -1219,12 +1242,16 @@ export class Consumer extends Base {
|
|
|
1219
1242
|
https://github.com/apache/kafka/blob/trunk/clients/src/main/resources/common/message/ConsumerProtocolAssignment.json
|
|
1220
1243
|
*/
|
|
1221
1244
|
#encodeProtocolAssignment(assignments) {
|
|
1222
|
-
|
|
1245
|
+
const userData = this[kOptions].assignmentUserData;
|
|
1246
|
+
const writer = Writer.create()
|
|
1223
1247
|
.appendInt16(0) // Version information
|
|
1224
1248
|
.appendArray(assignments, (w, { topic, partitions }) => {
|
|
1225
1249
|
w.appendString(topic, false).appendArray(partitions, (w, a) => w.appendInt32(a), false, false);
|
|
1226
|
-
}, false, false)
|
|
1227
|
-
|
|
1250
|
+
}, false, false);
|
|
1251
|
+
if (userData) {
|
|
1252
|
+
writer.append(userData);
|
|
1253
|
+
}
|
|
1254
|
+
return writer.buffer;
|
|
1228
1255
|
}
|
|
1229
1256
|
#decodeProtocolAssignment(buffer) {
|
|
1230
1257
|
const reader = Reader.from(buffer);
|
|
@@ -18,7 +18,7 @@ export declare class MessagesStream<Key, Value, HeaderKey, HeaderValue> extends
|
|
|
18
18
|
get context(): unknown;
|
|
19
19
|
get offsetsToCommit(): Map<string, CommitOptionsPartition>;
|
|
20
20
|
get offsetsCommitted(): Map<string, bigint>;
|
|
21
|
-
get
|
|
21
|
+
get connections(): ConnectionPool;
|
|
22
22
|
close(callback: CallbackWithPromise<void>): void;
|
|
23
23
|
close(): Promise<void>;
|
|
24
24
|
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 */
|
|
@@ -183,10 +187,9 @@ export class MessagesStream extends Readable {
|
|
|
183
187
|
get offsetsCommitted() {
|
|
184
188
|
return this.#offsetsCommitted;
|
|
185
189
|
}
|
|
186
|
-
// TODO: This is deprecated alias, remove in future major version
|
|
187
190
|
/* c8 ignore next 3 - Simple getter */
|
|
188
|
-
get
|
|
189
|
-
return this
|
|
191
|
+
get connections() {
|
|
192
|
+
return this[kConnections];
|
|
190
193
|
}
|
|
191
194
|
close(callback) {
|
|
192
195
|
if (!callback) {
|
|
@@ -304,6 +307,8 @@ export class MessagesStream extends Readable {
|
|
|
304
307
|
if (this.#autocommitInterval) {
|
|
305
308
|
clearInterval(this.#autocommitInterval);
|
|
306
309
|
}
|
|
310
|
+
this.#consumer.removeListener('consumer:group:join', this.#onConsumerGroupJoin);
|
|
311
|
+
this.#consumer.removeListener('client:broker:disconnect', this.#onBrokerDisconnect);
|
|
307
312
|
this[kConnections].close(closeError => {
|
|
308
313
|
callback(closeError ?? error);
|
|
309
314
|
});
|
|
@@ -379,7 +384,7 @@ export class MessagesStream extends Readable {
|
|
|
379
384
|
{
|
|
380
385
|
partition,
|
|
381
386
|
fetchOffset,
|
|
382
|
-
partitionMaxBytes: this.#options.
|
|
387
|
+
partitionMaxBytes: this.#options.maxBytesPerPartition,
|
|
383
388
|
currentLeaderEpoch: leaderEpoch,
|
|
384
389
|
lastFetchedEpoch: leaderEpoch
|
|
385
390
|
}
|
|
@@ -589,7 +594,12 @@ export class MessagesStream extends Readable {
|
|
|
589
594
|
this.#autocommitInflight = false;
|
|
590
595
|
if (error) {
|
|
591
596
|
this.emit('autocommit', error);
|
|
592
|
-
|
|
597
|
+
// Only destroy the stream when the broker requires a group rejoin
|
|
598
|
+
// (ILLEGAL_GENERATION, UNKNOWN_MEMBER_ID, REBALANCE_IN_PROGRESS).
|
|
599
|
+
// Transient coordinator errors must not tear down the consumption loop.
|
|
600
|
+
if (error.findBy?.('needsRejoin', true)) {
|
|
601
|
+
this.destroy(error);
|
|
602
|
+
}
|
|
593
603
|
return;
|
|
594
604
|
}
|
|
595
605
|
for (const { topic, partition, offset } of offsets) {
|
|
@@ -97,6 +97,10 @@ export declare const consumeOptionsProperties: {
|
|
|
97
97
|
type: string;
|
|
98
98
|
minimum: number;
|
|
99
99
|
};
|
|
100
|
+
maxBytesPerPartition: {
|
|
101
|
+
type: string;
|
|
102
|
+
minimum: number;
|
|
103
|
+
};
|
|
100
104
|
maxWaitTime: {
|
|
101
105
|
type: string;
|
|
102
106
|
minimum: number;
|
|
@@ -217,6 +221,10 @@ export declare const consumeOptionsSchema: {
|
|
|
217
221
|
type: string;
|
|
218
222
|
minimum: number;
|
|
219
223
|
};
|
|
224
|
+
maxBytesPerPartition: {
|
|
225
|
+
type: string;
|
|
226
|
+
minimum: number;
|
|
227
|
+
};
|
|
220
228
|
maxWaitTime: {
|
|
221
229
|
type: string;
|
|
222
230
|
minimum: number;
|
|
@@ -379,6 +387,10 @@ export declare const consumerOptionsSchema: {
|
|
|
379
387
|
type: string;
|
|
380
388
|
minimum: number;
|
|
381
389
|
};
|
|
390
|
+
maxBytesPerPartition: {
|
|
391
|
+
type: string;
|
|
392
|
+
minimum: number;
|
|
393
|
+
};
|
|
382
394
|
maxWaitTime: {
|
|
383
395
|
type: string;
|
|
384
396
|
minimum: number;
|
|
@@ -502,6 +514,10 @@ export declare const fetchOptionsSchema: {
|
|
|
502
514
|
type: string;
|
|
503
515
|
minimum: number;
|
|
504
516
|
};
|
|
517
|
+
maxBytesPerPartition: {
|
|
518
|
+
type: string;
|
|
519
|
+
minimum: number;
|
|
520
|
+
};
|
|
505
521
|
maxWaitTime: {
|
|
506
522
|
type: string;
|
|
507
523
|
minimum: number;
|
|
@@ -58,6 +58,7 @@ export const consumeOptionsProperties = {
|
|
|
58
58
|
autocommit: { oneOf: [{ type: 'boolean' }, { type: 'number', minimum: 100 }] },
|
|
59
59
|
minBytes: { type: 'number', minimum: 0 },
|
|
60
60
|
maxBytes: { type: 'number', minimum: 0 },
|
|
61
|
+
maxBytesPerPartition: { type: 'number', minimum: 0 },
|
|
61
62
|
maxWaitTime: { type: 'number', minimum: 0 },
|
|
62
63
|
isolationLevel: { type: 'number', enum: allowedFetchIsolationLevels },
|
|
63
64
|
deserializers: serdeProperties,
|
|
@@ -245,7 +246,7 @@ export const defaultConsumerOptions = {
|
|
|
245
246
|
heartbeatInterval: 3000,
|
|
246
247
|
protocols: [{ name: 'roundrobin', version: 1 }],
|
|
247
248
|
minBytes: 1,
|
|
248
|
-
maxBytes: 1_048_576 *
|
|
249
|
+
maxBytes: 1_048_576 * 50, // 50 MB
|
|
249
250
|
maxWaitTime: 5_000,
|
|
250
251
|
isolationLevel: FetchIsolationLevels.READ_COMMITTED,
|
|
251
252
|
highWaterMark: 1024
|
|
@@ -54,17 +54,20 @@ export interface GroupOptions {
|
|
|
54
54
|
heartbeatInterval?: number;
|
|
55
55
|
protocols?: GroupProtocolSubscription[];
|
|
56
56
|
partitionAssigner?: GroupPartitionsAssigner;
|
|
57
|
+
assignmentUserData?: Buffer;
|
|
57
58
|
}
|
|
58
59
|
export interface ConsumerGroupOptions {
|
|
59
60
|
groupProtocol: typeof GroupProtocols.CONSUMER;
|
|
60
61
|
groupInstanceId?: string;
|
|
61
62
|
groupRemoteAssignor?: string;
|
|
62
63
|
rebalanceTimeout?: number;
|
|
64
|
+
assignmentUserData?: Buffer;
|
|
63
65
|
}
|
|
64
66
|
export interface ConsumeBaseOptions<Key, Value, HeaderKey, HeaderValue> {
|
|
65
67
|
autocommit?: boolean | number;
|
|
66
68
|
minBytes?: number;
|
|
67
69
|
maxBytes?: number;
|
|
70
|
+
maxBytesPerPartition?: number;
|
|
68
71
|
maxWaitTime?: number;
|
|
69
72
|
isolationLevel?: number;
|
|
70
73
|
deserializers?: Partial<Deserializers<Key, Value, HeaderKey, HeaderValue>>;
|
|
@@ -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, { metadata: this.currentMetadata });
|
|
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
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type CompressionAlgorithmValue } from '../../protocol/compression.ts';
|
|
2
2
|
import { type MessageToProduce } from '../../protocol/records.ts';
|
|
3
3
|
import { type SchemaRegistry } from '../../registries/abstract.ts';
|
|
4
|
-
import { type BaseOptions, type TopicWithPartitionAndOffset } from '../base/types.ts';
|
|
4
|
+
import { type BaseOptions, type ClusterMetadata, type TopicWithPartitionAndOffset } from '../base/types.ts';
|
|
5
5
|
import { type BeforeSerializationHook, type Serializers } from '../serde.ts';
|
|
6
6
|
export interface ProducerInfo {
|
|
7
7
|
producerId: bigint;
|
|
@@ -12,7 +12,10 @@ export interface ProduceResult {
|
|
|
12
12
|
offsets?: TopicWithPartitionAndOffset[];
|
|
13
13
|
unwritableNodes?: number[];
|
|
14
14
|
}
|
|
15
|
-
export
|
|
15
|
+
export interface PartitionerContext {
|
|
16
|
+
metadata: ClusterMetadata | undefined;
|
|
17
|
+
}
|
|
18
|
+
export type Partitioner<Key, Value, HeaderKey, HeaderValue> = (message: MessageToProduce<Key, Value, HeaderKey, HeaderValue>, key: Buffer | undefined, context: PartitionerContext) => number;
|
|
16
19
|
export interface ProducerStreamReport {
|
|
17
20
|
batchId: number;
|
|
18
21
|
count: number;
|
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;
|
|
@@ -15,9 +15,31 @@ function ensureBuffer(data) {
|
|
|
15
15
|
return DynamicBuffer.isDynamicBuffer(data) ? data.buffer : data;
|
|
16
16
|
}
|
|
17
17
|
const snappyCompressSync = snappyCompress;
|
|
18
|
-
const snappyDecompressSync = snappyDecompress;
|
|
19
18
|
const lz4CompressFrameSync = lz4Compress;
|
|
20
19
|
const lz4DecompressFrameSync = lz4Decompress;
|
|
20
|
+
const xerialSnappyHeader = Buffer.from([0x82, 0x53, 0x4e, 0x41, 0x50, 0x50, 0x59, 0x00]);
|
|
21
|
+
function snappyDecompressSync(data) {
|
|
22
|
+
if (!data.subarray(0, xerialSnappyHeader.length).equals(xerialSnappyHeader)) {
|
|
23
|
+
return snappyDecompress(data);
|
|
24
|
+
}
|
|
25
|
+
const decompressed = new DynamicBuffer();
|
|
26
|
+
let offset = 16;
|
|
27
|
+
while (offset < data.length) {
|
|
28
|
+
/* c8 ignore next 3 - Hard to test */
|
|
29
|
+
if (offset + 4 > data.length) {
|
|
30
|
+
throw new Error('Invalid xerial snappy chunk length');
|
|
31
|
+
}
|
|
32
|
+
const chunkLength = data.readUInt32BE(offset);
|
|
33
|
+
offset += 4;
|
|
34
|
+
/* c8 ignore next 3 - Hard to test */
|
|
35
|
+
if (offset + chunkLength > data.length) {
|
|
36
|
+
throw new Error('Invalid xerial snappy chunk data');
|
|
37
|
+
}
|
|
38
|
+
decompressed.append(snappyDecompress(data.subarray(offset, offset + chunkLength)));
|
|
39
|
+
offset += chunkLength;
|
|
40
|
+
}
|
|
41
|
+
return decompressed.buffer;
|
|
42
|
+
}
|
|
21
43
|
export const compressionsAlgorithms = {
|
|
22
44
|
/* c8 ignore next 8 - 'none' is actually never used but this is to please Typescript */
|
|
23
45
|
none: {
|
|
@@ -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,8 @@ 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 currentMetadata(): ClusterMetadata | undefined;
|
|
70
|
+
get connections(): ConnectionPool;
|
|
69
71
|
emitWithDebug(section: string | null, name: string, ...args: any[]): boolean;
|
|
70
72
|
close(callback: CallbackWithPromise<void>): void;
|
|
71
73
|
close(): Promise<void>;
|
|
@@ -18,7 +18,7 @@ export declare class MessagesStream<Key, Value, HeaderKey, HeaderValue> extends
|
|
|
18
18
|
get context(): unknown;
|
|
19
19
|
get offsetsToCommit(): Map<string, CommitOptionsPartition>;
|
|
20
20
|
get offsetsCommitted(): Map<string, bigint>;
|
|
21
|
-
get
|
|
21
|
+
get connections(): ConnectionPool;
|
|
22
22
|
close(callback: CallbackWithPromise<void>): void;
|
|
23
23
|
close(): Promise<void>;
|
|
24
24
|
isActive(): boolean;
|
|
@@ -97,6 +97,10 @@ export declare const consumeOptionsProperties: {
|
|
|
97
97
|
type: string;
|
|
98
98
|
minimum: number;
|
|
99
99
|
};
|
|
100
|
+
maxBytesPerPartition: {
|
|
101
|
+
type: string;
|
|
102
|
+
minimum: number;
|
|
103
|
+
};
|
|
100
104
|
maxWaitTime: {
|
|
101
105
|
type: string;
|
|
102
106
|
minimum: number;
|
|
@@ -217,6 +221,10 @@ export declare const consumeOptionsSchema: {
|
|
|
217
221
|
type: string;
|
|
218
222
|
minimum: number;
|
|
219
223
|
};
|
|
224
|
+
maxBytesPerPartition: {
|
|
225
|
+
type: string;
|
|
226
|
+
minimum: number;
|
|
227
|
+
};
|
|
220
228
|
maxWaitTime: {
|
|
221
229
|
type: string;
|
|
222
230
|
minimum: number;
|
|
@@ -379,6 +387,10 @@ export declare const consumerOptionsSchema: {
|
|
|
379
387
|
type: string;
|
|
380
388
|
minimum: number;
|
|
381
389
|
};
|
|
390
|
+
maxBytesPerPartition: {
|
|
391
|
+
type: string;
|
|
392
|
+
minimum: number;
|
|
393
|
+
};
|
|
382
394
|
maxWaitTime: {
|
|
383
395
|
type: string;
|
|
384
396
|
minimum: number;
|
|
@@ -502,6 +514,10 @@ export declare const fetchOptionsSchema: {
|
|
|
502
514
|
type: string;
|
|
503
515
|
minimum: number;
|
|
504
516
|
};
|
|
517
|
+
maxBytesPerPartition: {
|
|
518
|
+
type: string;
|
|
519
|
+
minimum: number;
|
|
520
|
+
};
|
|
505
521
|
maxWaitTime: {
|
|
506
522
|
type: string;
|
|
507
523
|
minimum: number;
|
|
@@ -54,17 +54,20 @@ export interface GroupOptions {
|
|
|
54
54
|
heartbeatInterval?: number;
|
|
55
55
|
protocols?: GroupProtocolSubscription[];
|
|
56
56
|
partitionAssigner?: GroupPartitionsAssigner;
|
|
57
|
+
assignmentUserData?: Buffer;
|
|
57
58
|
}
|
|
58
59
|
export interface ConsumerGroupOptions {
|
|
59
60
|
groupProtocol: typeof GroupProtocols.CONSUMER;
|
|
60
61
|
groupInstanceId?: string;
|
|
61
62
|
groupRemoteAssignor?: string;
|
|
62
63
|
rebalanceTimeout?: number;
|
|
64
|
+
assignmentUserData?: Buffer;
|
|
63
65
|
}
|
|
64
66
|
export interface ConsumeBaseOptions<Key, Value, HeaderKey, HeaderValue> {
|
|
65
67
|
autocommit?: boolean | number;
|
|
66
68
|
minBytes?: number;
|
|
67
69
|
maxBytes?: number;
|
|
70
|
+
maxBytesPerPartition?: number;
|
|
68
71
|
maxWaitTime?: number;
|
|
69
72
|
isolationLevel?: number;
|
|
70
73
|
deserializers?: Partial<Deserializers<Key, Value, HeaderKey, HeaderValue>>;
|
|
@@ -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;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type CompressionAlgorithmValue } from "../../protocol/compression";
|
|
2
2
|
import { type MessageToProduce } from "../../protocol/records";
|
|
3
3
|
import { type SchemaRegistry } from "../../registries/abstract";
|
|
4
|
-
import { type BaseOptions, type TopicWithPartitionAndOffset } from "../base/types";
|
|
4
|
+
import { type BaseOptions, type ClusterMetadata, type TopicWithPartitionAndOffset } from "../base/types";
|
|
5
5
|
import { type BeforeSerializationHook, type Serializers } from "../serde";
|
|
6
6
|
export interface ProducerInfo {
|
|
7
7
|
producerId: bigint;
|
|
@@ -12,7 +12,10 @@ export interface ProduceResult {
|
|
|
12
12
|
offsets?: TopicWithPartitionAndOffset[];
|
|
13
13
|
unwritableNodes?: number[];
|
|
14
14
|
}
|
|
15
|
-
export
|
|
15
|
+
export interface PartitionerContext {
|
|
16
|
+
metadata: ClusterMetadata | undefined;
|
|
17
|
+
}
|
|
18
|
+
export type Partitioner<Key, Value, HeaderKey, HeaderValue> = (message: MessageToProduce<Key, Value, HeaderKey, HeaderValue>, key: Buffer | undefined, context: PartitionerContext) => number;
|
|
16
19
|
export interface ProducerStreamReport {
|
|
17
20
|
batchId: number;
|
|
18
21
|
count: number;
|
|
@@ -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
|
}
|
|
@@ -15,12 +15,6 @@ export interface DataValidationContext {
|
|
|
15
15
|
}
|
|
16
16
|
export type DebugDumpLogger = (...args: any[]) => void;
|
|
17
17
|
export { setTimeout as sleep } from 'node:timers/promises';
|
|
18
|
-
interface PromiseWithResolvers<T> {
|
|
19
|
-
promise: Promise<T>;
|
|
20
|
-
resolve: (value: T | PromiseLike<T>) => void;
|
|
21
|
-
reject: (reason?: any) => void;
|
|
22
|
-
}
|
|
23
|
-
export declare const promiseWithResolvers: <T>() => PromiseWithResolvers<T>;
|
|
24
18
|
export declare const ajv: Ajv2020;
|
|
25
19
|
export declare const loggers: Record<string, debug.Debugger>;
|
|
26
20
|
export declare class NumericMap extends Map<string, number> {
|
package/dist/utils.d.ts
CHANGED
|
@@ -15,12 +15,6 @@ export interface DataValidationContext {
|
|
|
15
15
|
}
|
|
16
16
|
export type DebugDumpLogger = (...args: any[]) => void;
|
|
17
17
|
export { setTimeout as sleep } from 'node:timers/promises';
|
|
18
|
-
interface PromiseWithResolvers<T> {
|
|
19
|
-
promise: Promise<T>;
|
|
20
|
-
resolve: (value: T | PromiseLike<T>) => void;
|
|
21
|
-
reject: (reason?: any) => void;
|
|
22
|
-
}
|
|
23
|
-
export declare const promiseWithResolvers: <T>() => PromiseWithResolvers<T>;
|
|
24
18
|
export declare const ajv: Ajv2020;
|
|
25
19
|
export declare const loggers: Record<string, debug.Debugger>;
|
|
26
20
|
export declare class NumericMap extends Map<string, number> {
|
package/dist/utils.js
CHANGED
|
@@ -2,20 +2,6 @@ import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
|
2
2
|
import debug from 'debug';
|
|
3
3
|
import { inspect } from 'node:util';
|
|
4
4
|
export { setTimeout as sleep } from 'node:timers/promises';
|
|
5
|
-
function promiseWithResolversPolyfill() {
|
|
6
|
-
let resolve;
|
|
7
|
-
let reject;
|
|
8
|
-
const promise = new Promise((_resolve, _reject) => {
|
|
9
|
-
resolve = _resolve;
|
|
10
|
-
reject = _reject;
|
|
11
|
-
});
|
|
12
|
-
// @ts-expect-error - resolve and reject are assigned in the promise constructor
|
|
13
|
-
return { promise, resolve, reject };
|
|
14
|
-
}
|
|
15
|
-
export const promiseWithResolvers = Promise.withResolvers
|
|
16
|
-
? Promise.withResolvers.bind(Promise)
|
|
17
|
-
: promiseWithResolversPolyfill;
|
|
18
|
-
/* c8 ignore end */
|
|
19
5
|
export const ajv = new Ajv2020({ allErrors: true, coerceTypes: false, strict: true });
|
|
20
6
|
export const loggers = {
|
|
21
7
|
protocol: debug('plt:kafka:protocol'),
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const name = "@platformatic/kafka";
|
|
2
|
-
export const version = "
|
|
2
|
+
export const version = "2.0.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/kafka",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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)",
|
|
@@ -59,7 +59,6 @@
|
|
|
59
59
|
"cronometro": "^5.3.0",
|
|
60
60
|
"eslint": "^9.35.0",
|
|
61
61
|
"fast-jwt": "^6.0.2",
|
|
62
|
-
"glob": "^13.0.0",
|
|
63
62
|
"hwp": "^0.4.1",
|
|
64
63
|
"json5": "^2.2.3",
|
|
65
64
|
"kafkajs": "^2.2.4",
|
|
@@ -76,7 +75,7 @@
|
|
|
76
75
|
"typescript": "^5.9.2"
|
|
77
76
|
},
|
|
78
77
|
"engines": {
|
|
79
|
-
"node": ">=
|
|
78
|
+
"node": ">= 22.22.0 || >= 24.6.0"
|
|
80
79
|
},
|
|
81
80
|
"typesVersions": {
|
|
82
81
|
"<5.0": {
|