@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.
Files changed (35) hide show
  1. package/README.md +1 -2
  2. package/dist/apis/admin/alter-partition-reassignments-v1.js +1 -0
  3. package/dist/apis/callbacks.js +1 -2
  4. package/dist/clients/admin/admin.d.ts +1 -1
  5. package/dist/clients/admin/admin.js +3 -2
  6. package/dist/clients/base/base.d.ts +2 -0
  7. package/dist/clients/base/base.js +17 -4
  8. package/dist/clients/consumer/consumer.js +32 -5
  9. package/dist/clients/consumer/messages-stream.d.ts +1 -1
  10. package/dist/clients/consumer/messages-stream.js +23 -13
  11. package/dist/clients/consumer/options.d.ts +16 -0
  12. package/dist/clients/consumer/options.js +2 -1
  13. package/dist/clients/consumer/types.d.ts +3 -0
  14. package/dist/clients/producer/partitioners.js +2 -0
  15. package/dist/clients/producer/producer.d.ts +6 -0
  16. package/dist/clients/producer/producer.js +125 -24
  17. package/dist/clients/producer/types.d.ts +5 -2
  18. package/dist/errors.js +1 -1
  19. package/dist/network/connection-pool.d.ts +3 -0
  20. package/dist/network/connection-pool.js +9 -0
  21. package/dist/network/connection.js +1 -0
  22. package/dist/protocol/compression.js +23 -1
  23. package/dist/typescript-4/dist/clients/admin/admin.d.ts +1 -1
  24. package/dist/typescript-4/dist/clients/base/base.d.ts +2 -0
  25. package/dist/typescript-4/dist/clients/consumer/messages-stream.d.ts +1 -1
  26. package/dist/typescript-4/dist/clients/consumer/options.d.ts +16 -0
  27. package/dist/typescript-4/dist/clients/consumer/types.d.ts +3 -0
  28. package/dist/typescript-4/dist/clients/producer/producer.d.ts +6 -0
  29. package/dist/typescript-4/dist/clients/producer/types.d.ts +5 -2
  30. package/dist/typescript-4/dist/network/connection-pool.d.ts +3 -0
  31. package/dist/typescript-4/dist/utils.d.ts +0 -6
  32. package/dist/utils.d.ts +0 -6
  33. package/dist/utils.js +0 -14
  34. package/dist/version.js +1 -1
  35. package/package.json +2 -3
package/README.md CHANGED
@@ -353,8 +353,7 @@ Many of the methods accept the same options as the client's constructors. The co
353
353
 
354
354
  Node.js LTS versions:
355
355
 
356
- - `20.19.4` or above
357
- - `22.18.0` or above
356
+ - `22.22.0` or above
358
357
  - `24.6.0` or above
359
358
 
360
359
  ## Testing
@@ -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
  }
@@ -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 } = promiseWithResolvers();
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 DeleteRecordsOptions, type DeleteGroupsOptions, type DeleteTopicsOptions, type DeletedRecordsTopic, 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';
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, deleteRecordsOptionsValidator, deleteGroupsOptionsValidator, deleteTopicsOptionsValidator, describeAclsOptionsValidator, describeClientQuotasOptionsValidator, describeConfigsOptionsValidator, describeGroupsOptionsValidator, describeLogDirsOptionsValidator, findCoordinatorOptionsValidator, incrementalAlterConfigsOptionsValidator, listConsumerGroupOffsetsOptionsValidator, listGroupsOptionsValidator, listTopicsOptionsValidator, removeMembersFromConsumerGroupOptionsValidator } from "./options.js";
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
- reader.readBytes(); // Ignore the user data
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 === 0) {
258
- callback(error);
259
- return;
263
+ if (attempt > 0) {
264
+ error = new MultipleErrors(`${operationId} failed ${attempt + 1} times.`, errors);
260
265
  }
261
- callback(new MultipleErrors(`${operationId} failed ${attempt + 1} times.`, errors));
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
- return Writer.create()
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
- .appendInt32(0).buffer; // No user data
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 committedOffsets(): Map<string, bigint>;
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 committedOffsets() {
189
- return this.#offsetsCommitted;
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.maxBytes,
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
- this.destroy(error);
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 * 10, // 10 MB
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
- let partition = 0;
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.responses) {
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
- callback(null, results);
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 type Partitioner<Key, Value, HeaderKey, HeaderValue> = (message: MessageToProduce<Key, Value, HeaderKey, HeaderValue>, key?: Buffer | undefined) => number;
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 DeleteRecordsOptions, type DeleteGroupsOptions, type DeleteTopicsOptions, type DeletedRecordsTopic, 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";
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 committedOffsets(): Map<string, bigint>;
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 type Partitioner<Key, Value, HeaderKey, HeaderValue> = (message: MessageToProduce<Key, Value, HeaderKey, HeaderValue>, key?: Buffer | undefined) => number;
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 = "1.33.2";
2
+ export const version = "2.0.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/kafka",
3
- "version": "1.33.2",
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": ">= 20.19.4 || >= 22.18.0 || >= 24.6.0"
78
+ "node": ">= 22.22.0 || >= 24.6.0"
80
79
  },
81
80
  "typesVersions": {
82
81
  "<5.0": {