@platformatic/kafka 1.25.0 → 1.26.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.
@@ -17,7 +17,6 @@ export declare const kGetConnection: unique symbol;
17
17
  export declare const kGetBootstrapConnection: unique symbol;
18
18
  export declare const kOptions: unique symbol;
19
19
  export declare const kConnections: unique symbol;
20
- export declare const kFetchConnections: unique symbol;
21
20
  export declare const kCreateConnectionPool: unique symbol;
22
21
  export declare const kClosed: unique symbol;
23
22
  export declare const kListApis: unique symbol;
@@ -17,7 +17,6 @@ export const kGetConnection = Symbol('plt.kafka.base.getConnection');
17
17
  export const kGetBootstrapConnection = Symbol('plt.kafka.base.getBootstrapConnection');
18
18
  export const kOptions = Symbol('plt.kafka.base.options');
19
19
  export const kConnections = Symbol('plt.kafka.base.connections');
20
- export const kFetchConnections = Symbol('plt.kafka.base.fetchCnnections');
21
20
  export const kCreateConnectionPool = Symbol('plt.kafka.base.createConnectionPool');
22
21
  export const kClosed = Symbol('plt.kafka.base.closed');
23
22
  export const kListApis = Symbol('plt.kafka.base.listApis');
@@ -82,6 +82,10 @@ export declare const baseOptionsSchema: {
82
82
  type: string;
83
83
  additionalProperties: boolean;
84
84
  };
85
+ ssl: {
86
+ type: string;
87
+ additionalProperties: boolean;
88
+ };
85
89
  tlsServerName: {
86
90
  oneOf: {
87
91
  type: string;
@@ -33,6 +33,7 @@ export const baseOptionsSchema = {
33
33
  maxInflights: { type: 'number', minimum: 0 },
34
34
  handleBackPressure: { type: 'boolean', default: false },
35
35
  tls: { type: 'object', additionalProperties: true }, // No validation as they come from Node.js
36
+ ssl: { type: 'object', additionalProperties: true }, // Alias for tls, no validation as they come from Node.js
36
37
  tlsServerName: { oneOf: [{ type: 'boolean' }, { type: 'string' }] },
37
38
  sasl: {
38
39
  type: 'object',
@@ -1,7 +1,6 @@
1
1
  import { type CallbackWithPromise } from '../../apis/callbacks.ts';
2
2
  import { type FetchResponse } from '../../apis/consumer/fetch-v17.ts';
3
- import { type ConnectionPool } from '../../network/connection-pool.ts';
4
- import { Base, type BaseEvents, kFetchConnections } from '../base/base.ts';
3
+ import { Base, type BaseEvents } from '../base/base.ts';
5
4
  import { MessagesStream } from './messages-stream.ts';
6
5
  import { TopicsMap } from './topics-map.ts';
7
6
  import { type CommitOptions, type ConsumeOptions, type ConsumerGroupJoinPayload, type ConsumerGroupLeavePayload, type ConsumerGroupRebalancePayload, type ConsumerHeartbeatErrorPayload, type ConsumerHeartbeatPayload, type ConsumerOptions, type FetchOptions, type GetLagOptions, type GroupAssignment, type GroupOptions, type ListCommitsOptions, type ListOffsetsOptions, type Offsets, type OffsetsWithTimestamps } from './types.ts';
@@ -20,11 +19,11 @@ export interface ConsumerEvents extends BaseEvents {
20
19
  export declare class Consumer<Key = Buffer, Value = Buffer, HeaderKey = Buffer, HeaderValue = Buffer> extends Base<ConsumerOptions<Key, Value, HeaderKey, HeaderValue>, ConsumerEvents> {
21
20
  #private;
22
21
  groupId: string;
22
+ groupInstanceId: string | null;
23
23
  generationId: number;
24
24
  memberId: string | null;
25
25
  topics: TopicsMap;
26
26
  assignments: GroupAssignment[] | null;
27
- [kFetchConnections]: ConnectionPool;
28
27
  constructor(options: ConsumerOptions<Key, Value, HeaderKey, HeaderValue>);
29
28
  get streamsCount(): number;
30
29
  get lastHeartbeat(): Date | null;
@@ -7,7 +7,7 @@ import { Reader } from "../../protocol/reader.js";
7
7
  import { IS_CONTROL } from "../../protocol/records.js";
8
8
  import { Writer } from "../../protocol/writer.js";
9
9
  import { kAutocommit, kRefreshOffsetsAndFetch } from "../../symbols.js";
10
- import { Base, kAfterCreate, kCheckNotClosed, kClosed, kCreateConnectionPool, kFetchConnections, kFormatValidationErrors, kGetApi, kGetBootstrapConnection, kGetConnection, kMetadata, kOptions, kPerformDeduplicated, kPerformWithRetry, kPrometheus, kValidateOptions } from "../base/base.js";
10
+ import { Base, kAfterCreate, kCheckNotClosed, kClosed, kConnections, kFormatValidationErrors, kGetApi, kGetBootstrapConnection, kGetConnection, kMetadata, kOptions, kPerformDeduplicated, kPerformWithRetry, kPrometheus, kValidateOptions } from "../base/base.js";
11
11
  import { ensureMetric } from "../metrics.js";
12
12
  import { MessagesStream } from "./messages-stream.js";
13
13
  import { commitOptionsValidator, consumeOptionsValidator, consumerOptionsValidator, defaultConsumerOptions, fetchOptionsValidator, getLagOptionsValidator, groupIdAndOptionsValidator, groupOptionsValidator, listCommitsOptionsValidator, listOffsetsOptionsValidator } from "./options.js";
@@ -15,6 +15,7 @@ import { roundRobinAssigner } from "./partitions-assigners.js";
15
15
  import { TopicsMap } from "./topics-map.js";
16
16
  export class Consumer extends Base {
17
17
  groupId;
18
+ groupInstanceId;
18
19
  generationId;
19
20
  memberId;
20
21
  topics;
@@ -33,21 +34,6 @@ export class Consumer extends Base {
33
34
  #groupRemoteAssignor;
34
35
  #streams;
35
36
  #lagMonitoring;
36
- /*
37
- The following requests are blocking in Kafka:
38
-
39
- FetchRequest (soprattutto con maxWaitMs)
40
- JoinGroupRequest
41
- SyncGroupRequest
42
- OffsetCommitRequest
43
- ProduceRequest
44
- ListOffsetsRequest
45
- ListGroupsRequest
46
- DescribeGroupsRequest
47
-
48
- In order to avoid consumer group problems, we separate FetchRequest only on a separate connection.
49
- */
50
- [kFetchConnections];
51
37
  // Metrics
52
38
  #metricActiveStreams;
53
39
  #metricLags;
@@ -55,6 +41,7 @@ export class Consumer extends Base {
55
41
  super({ ...defaultConsumerOptions, ...options });
56
42
  this[kValidateOptions](options, consumerOptionsValidator, '/options');
57
43
  this.groupId = options.groupId;
44
+ this.groupInstanceId = options.groupInstanceId ?? null;
58
45
  this.generationId = 0;
59
46
  this.memberId = null;
60
47
  this.topics = new TopicsMap();
@@ -74,8 +61,6 @@ export class Consumer extends Base {
74
61
  this.#useConsumerGroupProtocol = this[kOptions].groupProtocol === 'consumer';
75
62
  this.#groupRemoteAssignor = this[kOptions].groupRemoteAssignor ?? null;
76
63
  this.#validateGroupOptions(this[kOptions], groupIdAndOptionsValidator);
77
- // Initialize connection pool
78
- this[kFetchConnections] = this[kCreateConnectionPool]();
79
64
  if (this[kPrometheus]) {
80
65
  ensureMetric(this[kPrometheus], 'Gauge', 'kafka_consumers', 'Number of active Kafka consumers').inc();
81
66
  this.#metricActiveStreams = ensureMetric(this[kPrometheus], 'Gauge', 'kafka_consumers_streams', 'Number of active Kafka consumers streams');
@@ -125,24 +110,17 @@ export class Consumer extends Base {
125
110
  callback(error);
126
111
  return;
127
112
  }
128
- this[kFetchConnections].close(error => {
113
+ super.close(error => {
129
114
  if (error) {
130
115
  this[kClosed] = false;
131
116
  callback(error);
132
117
  return;
133
118
  }
134
- super.close(error => {
135
- if (error) {
136
- this[kClosed] = false;
137
- callback(error);
138
- return;
139
- }
140
- this.topics.clear();
141
- if (this[kPrometheus]) {
142
- ensureMetric(this[kPrometheus], 'Gauge', 'kafka_consumers', 'Number of active Kafka consumers').dec();
143
- }
144
- callback(null);
145
- });
119
+ this.topics.clear();
120
+ if (this[kPrometheus]) {
121
+ ensureMetric(this[kPrometheus], 'Gauge', 'kafka_consumers', 'Number of active Kafka consumers').dec();
122
+ }
123
+ callback(null);
146
124
  });
147
125
  });
148
126
  return callback[kCallbackPromise];
@@ -410,7 +388,8 @@ export class Consumer extends Base {
410
388
  retryCallback(new UserError(`Cannot find broker with node id ${options.node}`));
411
389
  return;
412
390
  }
413
- this[kFetchConnections].get(broker, (error, connection) => {
391
+ const pool = options.connectionPool ?? this[kConnections];
392
+ pool.get(broker, (error, connection) => {
414
393
  if (error) {
415
394
  // When a connection was not available (either interrupted or not available) we
416
395
  // reset the leader epoch in the options so that when connection is re-established again we can continue
@@ -638,7 +617,7 @@ export class Consumer extends Base {
638
617
  groupCallback(error);
639
618
  return;
640
619
  }
641
- api(connection, this.groupId, this.generationId, this.memberId, null, groupCallback);
620
+ api(connection, this.groupId, this.generationId, this.memberId, this.groupInstanceId, groupCallback);
642
621
  });
643
622
  }, error => {
644
623
  // The heartbeat has been aborted elsewhere, ignore the response
@@ -685,8 +664,7 @@ export class Consumer extends Base {
685
664
  groupCallback(error);
686
665
  return;
687
666
  }
688
- api(connection, this.groupId, this.memberId || '', this.#memberEpoch, null, // instanceId
689
- null, // rackId
667
+ api(connection, this.groupId, this.memberId || '', this.#memberEpoch, this.groupInstanceId, null, // rackId
690
668
  options.rebalanceTimeout, this.topics.current, this.#groupRemoteAssignor, this.#assignments, groupCallback);
691
669
  });
692
670
  }, (error, response) => {
@@ -836,8 +814,7 @@ export class Consumer extends Base {
836
814
  return;
837
815
  }
838
816
  api(connection, this.groupId, this.memberId, -1, // memberEpoch = -1 signals leave
839
- null, // instanceId
840
- null, // rackId
817
+ this.groupInstanceId, null, // rackId
841
818
  0, // rebalanceTimeout
842
819
  [], // subscribedTopicNames
843
820
  this.#groupRemoteAssignor, [], // topicPartitions
@@ -941,7 +918,7 @@ export class Consumer extends Base {
941
918
  groupCallback(error);
942
919
  return;
943
920
  }
944
- api(connection, this.groupId, options.sessionTimeout, options.rebalanceTimeout, this.memberId ?? '', null, 'consumer', protocols, '', groupCallback);
921
+ api(connection, this.groupId, options.sessionTimeout, options.rebalanceTimeout, this.memberId ?? '', this.groupInstanceId, 'consumer', protocols, '', groupCallback);
945
922
  });
946
923
  }, (error, response) => {
947
924
  if (!this.#membershipActive) {
@@ -1091,7 +1068,7 @@ export class Consumer extends Base {
1091
1068
  groupCallback(error);
1092
1069
  return;
1093
1070
  }
1094
- api(connection, this.groupId, this.generationId, this.memberId, null, 'consumer', this.#protocol, assignments, groupCallback);
1071
+ api(connection, this.groupId, this.generationId, this.memberId, this.groupInstanceId, 'consumer', this.#protocol, assignments, groupCallback);
1095
1072
  });
1096
1073
  }, (error, response) => {
1097
1074
  if (!this.#membershipActive) {
@@ -1,8 +1,9 @@
1
1
  import { Readable } from 'node:stream';
2
2
  import { type CallbackWithPromise } from '../../apis/callbacks.ts';
3
+ import { type ConnectionPool } from '../../network/connection-pool.ts';
3
4
  import { type Message } from '../../protocol/records.ts';
4
5
  import { kAutocommit, kInstance, kRefreshOffsetsAndFetch } from '../../symbols.ts';
5
- import { kInspect } from '../base/base.ts';
6
+ import { kConnections, kInspect } from '../base/base.ts';
6
7
  import { type Consumer } from './consumer.ts';
7
8
  import { type CommitOptionsPartition, type ConsumeOptions } from './types.ts';
8
9
  export declare function noopDeserializer(data?: Buffer): Buffer | undefined;
@@ -10,6 +11,7 @@ export declare function defaultCorruptedMessageHandler(): boolean;
10
11
  export declare class MessagesStream<Key, Value, HeaderKey, HeaderValue> extends Readable {
11
12
  #private;
12
13
  [kInstance]: number;
14
+ [kConnections]: ConnectionPool;
13
15
  constructor(consumer: Consumer<Key, Value, HeaderKey, HeaderValue>, options: ConsumeOptions<Key, Value, HeaderKey, HeaderValue>);
14
16
  get consumer(): Consumer<Key, Value, HeaderKey, HeaderValue>;
15
17
  get offsetsToFetch(): Map<string, bigint>;
@@ -5,7 +5,7 @@ import { consumerReceivesChannel, createDiagnosticContext, notifyCreation } from
5
5
  import { UserError } from "../../errors.js";
6
6
  import { IS_CONTROL } from "../../protocol/records.js";
7
7
  import { kAutocommit, kInstance, kRefreshOffsetsAndFetch } from "../../symbols.js";
8
- import { kInspect, kPrometheus } from "../base/base.js";
8
+ import { kConnections, kCreateConnectionPool, kInspect, kPrometheus } from "../base/base.js";
9
9
  import { ensureMetric } from "../metrics.js";
10
10
  import { defaultConsumerOptions } from "./options.js";
11
11
  import { MessagesStreamFallbackModes, MessagesStreamModes } from "./types.js";
@@ -18,6 +18,18 @@ export function noopDeserializer(data) {
18
18
  export function defaultCorruptedMessageHandler() {
19
19
  return true;
20
20
  }
21
+ function messageToJSON() {
22
+ return {
23
+ key: this.key,
24
+ value: this.value,
25
+ headers: Array.from(this.headers.entries()),
26
+ topic: this.topic,
27
+ partition: this.partition,
28
+ timestamp: this.timestamp.toString(),
29
+ offset: this.offset.toString(),
30
+ metadata: this.metadata
31
+ };
32
+ }
21
33
  let currentInstance = 0;
22
34
  export class MessagesStream extends Readable {
23
35
  #consumer;
@@ -40,11 +52,27 @@ export class MessagesStream extends Readable {
40
52
  #autocommitEnabled;
41
53
  #autocommitInterval;
42
54
  #autocommitInflight;
43
- #shouldClose;
55
+ #closed;
44
56
  #closeCallbacks;
45
57
  #metricsConsumedMessages;
46
58
  #corruptedMessageHandler;
47
59
  [kInstance];
60
+ /*
61
+ The following requests are blocking in Kafka:
62
+
63
+ FetchRequest (soprattutto con maxWaitMs)
64
+ JoinGroupRequest
65
+ SyncGroupRequest
66
+ OffsetCommitRequest
67
+ ProduceRequest
68
+ ListOffsetsRequest
69
+ ListGroupsRequest
70
+ DescribeGroupsRequest
71
+
72
+ In order to avoid consumer group problems, we separate FetchRequest only on a separate connection.
73
+ Also, to avoid head-of-line blocking between streams, we use a pool of connections for each stream.
74
+ */
75
+ [kConnections];
48
76
  constructor(consumer, options) {
49
77
  const { autocommit, mode, fallbackMode, maxFetches, offsets, deserializers, onCorruptedMessage,
50
78
  // The options below are only destructured to avoid being part of structuredClone below
@@ -61,6 +89,7 @@ export class MessagesStream extends Readable {
61
89
  highWaterMark: maxFetches ?? options.highWaterMark ?? defaultConsumerOptions.highWaterMark
62
90
  });
63
91
  this[kInstance] = currentInstance++;
92
+ this[kConnections] = consumer[kCreateConnectionPool]();
64
93
  this.#consumer = consumer;
65
94
  this.#mode = mode ?? MessagesStreamModes.LATEST;
66
95
  this.#fallbackMode = fallbackMode ?? MessagesStreamFallbackModes.LATEST;
@@ -78,7 +107,7 @@ export class MessagesStream extends Readable {
78
107
  this.#headerValueDeserializer = deserializers?.headerValue ?? noopDeserializer;
79
108
  this.#autocommitEnabled = !!options.autocommit;
80
109
  this.#autocommitInflight = false;
81
- this.#shouldClose = false;
110
+ this.#closed = false;
82
111
  this.#closeCallbacks = [];
83
112
  this.#corruptedMessageHandler = onCorruptedMessage ?? defaultCorruptedMessageHandler;
84
113
  // Restore offsets
@@ -150,11 +179,11 @@ export class MessagesStream extends Readable {
150
179
  return callback[kCallbackPromise];
151
180
  }
152
181
  this.#closeCallbacks.push(callback);
153
- if (this.#shouldClose) {
154
- this.#invokeCloseCallbacks(null);
182
+ if (this.#closed) {
183
+ this.#afterClose(null);
155
184
  return callback[kCallbackPromise];
156
185
  }
157
- this.#shouldClose = true;
186
+ this.#closed = true;
158
187
  this.push(null);
159
188
  if (this.#autocommitInterval) {
160
189
  clearInterval(this.#autocommitInterval);
@@ -166,32 +195,33 @@ export class MessagesStream extends Readable {
166
195
  }
167
196
  /* c8 ignore next 3 - Hard to test */
168
197
  this.once('error', error => {
169
- callback(error);
198
+ this.#afterClose(error);
170
199
  });
171
200
  this.once('close', () => {
172
201
  // We have offsets that were enqueued to be committed. Perform the operation
202
+ /* c8 ignore next 3 - Hard to test */
173
203
  if (this.#offsetsToCommit.size > 0) {
174
204
  this[kAutocommit]();
175
205
  }
176
206
  // We have offsets that are being committed. These are awaited despite of the force parameters
177
207
  if (this.#autocommitInflight) {
178
208
  this.once('autocommit', error => {
179
- this.#invokeCloseCallbacks(error);
209
+ this.#afterClose(error);
180
210
  });
181
211
  return;
182
212
  }
183
- this.#invokeCloseCallbacks(null);
213
+ this.#afterClose(null);
184
214
  });
185
215
  return callback[kCallbackPromise];
186
216
  }
187
217
  isActive() {
188
- if (this.#shouldClose || this.closed || this.destroyed) {
218
+ if (this.#closed || this.closed || this.destroyed) {
189
219
  return false;
190
220
  }
191
221
  return this.#consumer.isActive();
192
222
  }
193
223
  isConnected() {
194
- if (this.#shouldClose || this.closed || this.destroyed) {
224
+ if (this.#closed || this.closed || this.destroyed) {
195
225
  return false;
196
226
  }
197
227
  return this.#consumer.isConnected();
@@ -243,7 +273,7 @@ export class MessagesStream extends Readable {
243
273
  }
244
274
  #fetch() {
245
275
  /* c8 ignore next 4 - Hard to test */
246
- if (this.#shouldClose || this.closed || this.destroyed) {
276
+ if (this.#closed || this.closed || this.destroyed) {
247
277
  this.push(null);
248
278
  return;
249
279
  }
@@ -256,7 +286,7 @@ export class MessagesStream extends Readable {
256
286
  this.emit('fetch');
257
287
  // The stream has been closed, ignore any error
258
288
  /* c8 ignore next 4 - Hard to test */
259
- if (this.#shouldClose || this.closed || this.destroyed) {
289
+ if (this.#closed || this.closed || this.destroyed) {
260
290
  this.push(null);
261
291
  return;
262
292
  }
@@ -264,7 +294,7 @@ export class MessagesStream extends Readable {
264
294
  return;
265
295
  }
266
296
  /* c8 ignore next 5 - Hard to test */
267
- if (this.#shouldClose || this.closed || this.destroyed) {
297
+ if (this.#closed || this.closed || this.destroyed) {
268
298
  this.emit('fetch');
269
299
  this.push(null);
270
300
  return;
@@ -317,14 +347,14 @@ export class MessagesStream extends Readable {
317
347
  if (error) {
318
348
  // The stream has been closed, ignore the error
319
349
  /* c8 ignore next 4 - Hard to test */
320
- if (this.#shouldClose || this.closed || this.destroyed) {
350
+ if (this.#closed || this.closed || this.destroyed) {
321
351
  this.push(null);
322
352
  return;
323
353
  }
324
354
  this.destroy(error);
325
355
  return;
326
356
  }
327
- if (this.#shouldClose || this.closed || this.destroyed) {
357
+ if (this.#closed || this.closed || this.destroyed) {
328
358
  // When it's the last inflight, we finally close the stream.
329
359
  // This is done to avoid the user exiting from consmuming metrics like for-await and still see the process up.
330
360
  if (this.#inflightNodes.size === 0) {
@@ -421,7 +451,8 @@ export class MessagesStream extends Readable {
421
451
  timestamp: firstTimestamp + record.timestampDelta,
422
452
  offset,
423
453
  commit,
424
- metadata: messageMetadata
454
+ metadata: messageMetadata,
455
+ toJSON: messageToJSON
425
456
  };
426
457
  diagnosticContext.result = message;
427
458
  consumerReceivesChannel.asyncStart.publish(diagnosticContext);
@@ -512,7 +543,7 @@ export class MessagesStream extends Readable {
512
543
  }, (error, offsets) => {
513
544
  if (error) {
514
545
  /* c8 ignore next 4 - Hard to test */
515
- if (this.#shouldClose || this.closed || this.destroyed) {
546
+ if (this.#closed || this.closed || this.destroyed) {
516
547
  callback(null);
517
548
  return;
518
549
  }
@@ -539,7 +570,7 @@ export class MessagesStream extends Readable {
539
570
  this.#consumer.listCommittedOffsets({ topics }, (error, commits) => {
540
571
  if (error) {
541
572
  /* c8 ignore next 4 - Hard to test */
542
- if (this.#shouldClose || this.closed || this.destroyed) {
573
+ if (this.#closed || this.closed || this.destroyed) {
543
574
  callback(null);
544
575
  return;
545
576
  }
@@ -594,11 +625,13 @@ export class MessagesStream extends Readable {
594
625
  #assignmentsForTopic(topic) {
595
626
  return this.#consumer.assignments?.find(assignment => assignment.topic === topic);
596
627
  }
597
- #invokeCloseCallbacks(error) {
598
- for (const callback of this.#closeCallbacks) {
599
- callback(error);
600
- }
601
- this.#closeCallbacks = [];
628
+ #afterClose(error) {
629
+ this[kConnections].close(closeError => {
630
+ for (const callback of this.#closeCallbacks) {
631
+ callback(error ?? closeError);
632
+ }
633
+ this.#closeCallbacks = [];
634
+ });
602
635
  }
603
636
  /* c8 ignore next 3 - This is a private API used to debug during development */
604
637
  [kInspect](...args) {
@@ -1,4 +1,8 @@
1
1
  export declare const groupOptionsProperties: {
2
+ groupInstanceId: {
3
+ type: string;
4
+ pattern: string;
5
+ };
2
6
  sessionTimeout: {
3
7
  type: string;
4
8
  minimum: number;
@@ -127,6 +131,10 @@ export declare const consumeOptionsProperties: {
127
131
  export declare const groupOptionsSchema: {
128
132
  type: string;
129
133
  properties: {
134
+ groupInstanceId: {
135
+ type: string;
136
+ pattern: string;
137
+ };
130
138
  sessionTimeout: {
131
139
  type: string;
132
140
  minimum: number;
@@ -233,6 +241,10 @@ export declare const consumeOptionsSchema: {
233
241
  type: string;
234
242
  minimum: number;
235
243
  };
244
+ groupInstanceId: {
245
+ type: string;
246
+ pattern: string;
247
+ };
236
248
  sessionTimeout: {
237
249
  type: string;
238
250
  minimum: number;
@@ -384,6 +396,10 @@ export declare const consumerOptionsSchema: {
384
396
  type: string;
385
397
  minimum: number;
386
398
  };
399
+ groupInstanceId: {
400
+ type: string;
401
+ pattern: string;
402
+ };
387
403
  sessionTimeout: {
388
404
  type: string;
389
405
  minimum: number;
@@ -495,6 +511,10 @@ export declare const fetchOptionsSchema: {
495
511
  type: string;
496
512
  minimum: number;
497
513
  };
514
+ groupInstanceId: {
515
+ type: string;
516
+ pattern: string;
517
+ };
498
518
  sessionTimeout: {
499
519
  type: string;
500
520
  minimum: number;
@@ -588,6 +608,9 @@ export declare const fetchOptionsSchema: {
588
608
  required: string[];
589
609
  };
590
610
  };
611
+ connectionPool: {
612
+ type: string;
613
+ };
591
614
  };
592
615
  required: string[];
593
616
  additionalProperties: boolean;
@@ -4,6 +4,7 @@ import { idProperty, topicWithPartitionAndOffsetProperties } from "../base/optio
4
4
  import { serdeProperties } from "../serde.js";
5
5
  import { allowedMessagesStreamFallbackModes, allowedMessagesStreamModes } from "./types.js";
6
6
  export const groupOptionsProperties = {
7
+ groupInstanceId: { type: 'string', pattern: '^\\S+$' },
7
8
  sessionTimeout: { type: 'number', minimum: 0 },
8
9
  rebalanceTimeout: { type: 'number', minimum: 0 },
9
10
  heartbeatInterval: { type: 'number', minimum: 0 },
@@ -128,6 +129,7 @@ export const fetchOptionsSchema = {
128
129
  required: ['topicId', 'partitions']
129
130
  }
130
131
  },
132
+ connectionPool: { type: 'object' },
131
133
  ...groupOptionsProperties,
132
134
  ...consumeOptionsProperties
133
135
  },
@@ -1,5 +1,6 @@
1
1
  import { type FetchRequestTopic } from '../../apis/consumer/fetch-v17.ts';
2
2
  import { type GroupProtocols } from '../../apis/enumerations.ts';
3
+ import { type ConnectionPool } from '../../network/connection-pool.ts';
3
4
  import { type KafkaRecord, type Message } from '../../protocol/records.ts';
4
5
  import { type BaseOptions, type ClusterMetadata, type TopicWithPartitionAndOffset } from '../base/types.ts';
5
6
  import { type Deserializers } from '../serde.ts';
@@ -46,6 +47,7 @@ export type MessagesStreamFallbackMode = keyof typeof MessagesStreamFallbackMode
46
47
  export type MessagesStreamFallbackModeValue = (typeof MessagesStreamFallbackModes)[keyof typeof MessagesStreamFallbackModes];
47
48
  export interface GroupOptions {
48
49
  groupProtocol?: typeof GroupProtocols.CLASSIC;
50
+ groupInstanceId?: string;
49
51
  sessionTimeout?: number;
50
52
  rebalanceTimeout?: number;
51
53
  heartbeatInterval?: number;
@@ -54,6 +56,7 @@ export interface GroupOptions {
54
56
  }
55
57
  export interface ConsumerGroupOptions {
56
58
  groupProtocol: typeof GroupProtocols.CONSUMER;
59
+ groupInstanceId?: string;
57
60
  groupRemoteAssignor?: string;
58
61
  rebalanceTimeout?: number;
59
62
  }
@@ -81,6 +84,7 @@ export type ConsumerOptions<Key, Value, HeaderKey, HeaderValue> = BaseOptions &
81
84
  export type FetchOptions<Key, Value, HeaderKey, HeaderValue> = Pick<ConsumeBaseOptions<Key, Value, HeaderKey, HeaderValue>, 'minBytes' | 'maxBytes' | 'maxWaitTime' | 'isolationLevel'> & {
82
85
  node: number;
83
86
  topics: FetchRequestTopic[];
87
+ connectionPool?: ConnectionPool;
84
88
  };
85
89
  export interface CommitOptionsPartition extends TopicWithPartitionAndOffset {
86
90
  leaderEpoch: number;
@@ -4,8 +4,8 @@ import { type CallbackWithPromise } from '../apis/callbacks.ts';
4
4
  import { type Callback, type ResponseParser } from '../apis/definitions.ts';
5
5
  import { type SASLMechanismValue } from '../apis/enumerations.ts';
6
6
  import { type SaslAuthenticateResponse, type SASLAuthenticationAPI } from '../apis/security/sasl-authenticate-v2.ts';
7
- import { TypedEventEmitter, type TypedEvents } from '../events.ts';
8
7
  import { TimeoutError } from '../errors.ts';
8
+ import { TypedEventEmitter, type TypedEvents } from '../events.ts';
9
9
  import { Writer } from '../protocol/writer.ts';
10
10
  export interface ConnectionEvents extends TypedEvents {
11
11
  connecting: () => void;
@@ -40,6 +40,7 @@ export interface ConnectionOptions {
40
40
  requestTimeout?: number;
41
41
  maxInflights?: number;
42
42
  tls?: TLSConnectionOptions;
43
+ ssl?: TLSConnectionOptions;
43
44
  tlsServerName?: string | boolean;
44
45
  sasl?: SASLOptions;
45
46
  ownerId?: number;
@@ -62,6 +63,7 @@ export declare const ConnectionStatuses: {
62
63
  readonly NONE: "none";
63
64
  readonly CONNECTING: "connecting";
64
65
  readonly AUTHENTICATING: "authenticating";
66
+ readonly REAUTHENTICATING: "reauthenticating";
65
67
  readonly CONNECTED: "connected";
66
68
  readonly CLOSED: "closed";
67
69
  readonly CLOSING: "closing";
@@ -89,4 +91,5 @@ export declare class Connection extends TypedEventEmitter<ConnectionEvents> {
89
91
  close(callback: CallbackWithPromise<void>): void;
90
92
  close(): Promise<void>;
91
93
  send<ReturnType>(apiKey: number, apiVersion: number, createPayload: () => Writer, responseParser: ResponseParser<ReturnType>, hasRequestHeaderTaggedFields: boolean, hasResponseHeaderTaggedFields: boolean, callback: Callback<ReturnType>): void;
94
+ reauthenticate(): void;
92
95
  }
@@ -5,8 +5,8 @@ import { createPromisifiedCallback, kCallbackPromise } from "../apis/callbacks.j
5
5
  import { allowedSASLMechanisms, SASLMechanisms } from "../apis/enumerations.js";
6
6
  import { saslAuthenticateV2, saslHandshakeV1 } from "../apis/index.js";
7
7
  import { connectionsApiChannel, connectionsConnectsChannel, createDiagnosticContext, notifyCreation } from "../diagnostic.js";
8
- import { TypedEventEmitter } from "../events.js";
9
8
  import { AuthenticationError, NetworkError, TimeoutError, UnexpectedCorrelationIdError, UserError } from "../errors.js";
9
+ import { TypedEventEmitter } from "../events.js";
10
10
  import { protocolAPIsById } from "../protocol/apis.js";
11
11
  import { EMPTY_OR_SINGLE_COMPACT_LENGTH_SIZE, INT32_SIZE } from "../protocol/definitions.js";
12
12
  import { DynamicBuffer } from "../protocol/dynamic-buffer.js";
@@ -19,6 +19,7 @@ export const ConnectionStatuses = {
19
19
  NONE: 'none',
20
20
  CONNECTING: 'connecting',
21
21
  AUTHENTICATING: 'authenticating',
22
+ REAUTHENTICATING: 'reauthenticating',
22
23
  CONNECTED: 'connected',
23
24
  CLOSED: 'closed',
24
25
  CLOSING: 'closing',
@@ -55,6 +56,7 @@ export class Connection extends TypedEventEmitter {
55
56
  this.setMaxListeners(0);
56
57
  this.#instanceId = currentInstance++;
57
58
  this.#options = Object.assign({}, defaultOptions, options);
59
+ this.#options.tls ??= this.#options.ssl;
58
60
  this.#status = ConnectionStatuses.NONE;
59
61
  this.#clientId = clientId;
60
62
  this.#ownerId = options.ownerId;
@@ -261,6 +263,17 @@ export class Connection extends TypedEventEmitter {
261
263
  return this.#sendRequest(request);
262
264
  }, this.#onResponse.bind(this, request, callback));
263
265
  }
266
+ reauthenticate() {
267
+ if (!this.#options.sasl) {
268
+ return;
269
+ }
270
+ const host = this.#host;
271
+ const port = this.#port;
272
+ const diagnosticContext = createDiagnosticContext({ connection: this, operation: 'reauthenticate', host, port });
273
+ this.#status = ConnectionStatuses.REAUTHENTICATING;
274
+ clearTimeout(this.#reauthenticationTimeout);
275
+ this.#authenticate(host, port, diagnosticContext);
276
+ }
264
277
  #onResponse(request, callback, error, payload) {
265
278
  clearTimeout(request.timeoutHandle);
266
279
  request.timeoutHandle = null;
@@ -314,7 +327,9 @@ export class Connection extends TypedEventEmitter {
314
327
  #sendRequest(request) {
315
328
  connectionsApiChannel.start.publish(request.diagnostic);
316
329
  try {
317
- if (this.#status !== ConnectionStatuses.CONNECTED && this.#status !== ConnectionStatuses.AUTHENTICATING) {
330
+ if (this.#status !== ConnectionStatuses.CONNECTED &&
331
+ this.#status !== ConnectionStatuses.AUTHENTICATING &&
332
+ this.#status !== ConnectionStatuses.REAUTHENTICATING) {
318
333
  request.callback(new NetworkError('Connection closed'), undefined);
319
334
  return false;
320
335
  }
@@ -387,17 +402,10 @@ export class Connection extends TypedEventEmitter {
387
402
  return;
388
403
  }
389
404
  if (sessionLifetimeMs > 0) {
390
- this.#reauthenticationTimeout = setTimeout(() => {
391
- const diagnosticContext = createDiagnosticContext({
392
- connection: this,
393
- operation: 'reauthenticate',
394
- host,
395
- port
396
- });
397
- this.#authenticate(host, port, diagnosticContext);
398
- }, Number(sessionLifetimeMs) * 0.8);
405
+ this.#reauthenticationTimeout = setTimeout(this.reauthenticate.bind(this), Number(sessionLifetimeMs) * 0.8);
399
406
  }
400
- if (this.#status === ConnectionStatuses.CONNECTED) {
407
+ if (this.#status === ConnectionStatuses.CONNECTED || this.#status === ConnectionStatuses.REAUTHENTICATING) {
408
+ this.#status = ConnectionStatuses.CONNECTED;
401
409
  this.emit('sasl:authentication:extended', authBytes);
402
410
  }
403
411
  else {
@@ -24,11 +24,22 @@ export interface MessageConsumerMetadata {
24
24
  generationId: number;
25
25
  memberId: string;
26
26
  }
27
+ export interface MessageJSON<Key = unknown, Value = unknown, HeaderKey = unknown, HeaderValue = unknown> {
28
+ key: Key;
29
+ value: Value;
30
+ headers: Array<[HeaderKey, HeaderValue]>;
31
+ topic: string;
32
+ partition: number;
33
+ timestamp: string;
34
+ offset: string;
35
+ metadata: Record<string, unknown>;
36
+ }
27
37
  export interface Message<Key = Buffer, Value = Buffer, HeaderKey = Buffer, HeaderValue = Buffer> extends Required<MessageBase<Key, Value>> {
28
38
  headers: Map<HeaderKey, HeaderValue>;
29
39
  offset: bigint;
30
40
  metadata: Record<string, unknown>;
31
41
  commit(callback?: (error?: Error) => void): void | Promise<void>;
42
+ toJSON(): MessageJSON<Key, Value, HeaderKey, HeaderValue>;
32
43
  }
33
44
  export interface MessageRecord {
34
45
  key?: Buffer;
@@ -17,7 +17,6 @@ export declare const kGetConnection: unique symbol;
17
17
  export declare const kGetBootstrapConnection: unique symbol;
18
18
  export declare const kOptions: unique symbol;
19
19
  export declare const kConnections: unique symbol;
20
- export declare const kFetchConnections: unique symbol;
21
20
  export declare const kCreateConnectionPool: unique symbol;
22
21
  export declare const kClosed: unique symbol;
23
22
  export declare const kListApis: unique symbol;
@@ -82,6 +82,10 @@ export declare const baseOptionsSchema: {
82
82
  type: string;
83
83
  additionalProperties: boolean;
84
84
  };
85
+ ssl: {
86
+ type: string;
87
+ additionalProperties: boolean;
88
+ };
85
89
  tlsServerName: {
86
90
  oneOf: {
87
91
  type: string;
@@ -1,7 +1,6 @@
1
1
  import { type CallbackWithPromise } from "../../apis/callbacks";
2
2
  import { type FetchResponse } from "../../apis/consumer/fetch-v17";
3
- import { type ConnectionPool } from "../../network/connection-pool";
4
- import { Base, type BaseEvents, kFetchConnections } from "../base/base";
3
+ import { Base, type BaseEvents } from "../base/base";
5
4
  import { MessagesStream } from "./messages-stream";
6
5
  import { TopicsMap } from "./topics-map";
7
6
  import { type CommitOptions, type ConsumeOptions, type ConsumerGroupJoinPayload, type ConsumerGroupLeavePayload, type ConsumerGroupRebalancePayload, type ConsumerHeartbeatErrorPayload, type ConsumerHeartbeatPayload, type ConsumerOptions, type FetchOptions, type GetLagOptions, type GroupAssignment, type GroupOptions, type ListCommitsOptions, type ListOffsetsOptions, type Offsets, type OffsetsWithTimestamps } from "./types";
@@ -20,11 +19,11 @@ export interface ConsumerEvents extends BaseEvents {
20
19
  export declare class Consumer<Key = Buffer, Value = Buffer, HeaderKey = Buffer, HeaderValue = Buffer> extends Base<ConsumerOptions<Key, Value, HeaderKey, HeaderValue>, ConsumerEvents> {
21
20
  #private;
22
21
  groupId: string;
22
+ groupInstanceId: string | null;
23
23
  generationId: number;
24
24
  memberId: string | null;
25
25
  topics: TopicsMap;
26
26
  assignments: GroupAssignment[] | null;
27
- [kFetchConnections]: ConnectionPool;
28
27
  constructor(options: ConsumerOptions<Key, Value, HeaderKey, HeaderValue>);
29
28
  get streamsCount(): number;
30
29
  get lastHeartbeat(): Date | null;
@@ -1,8 +1,9 @@
1
1
  import { Readable } from 'node:stream';
2
2
  import { type CallbackWithPromise } from "../../apis/callbacks";
3
+ import { type ConnectionPool } from "../../network/connection-pool";
3
4
  import { type Message } from "../../protocol/records";
4
5
  import { kAutocommit, kInstance, kRefreshOffsetsAndFetch } from "../../symbols";
5
- import { kInspect } from "../base/base";
6
+ import { kConnections, kInspect } from "../base/base";
6
7
  import { type Consumer } from "./consumer";
7
8
  import { type CommitOptionsPartition, type ConsumeOptions } from "./types";
8
9
  export declare function noopDeserializer(data?: Buffer): Buffer | undefined;
@@ -10,6 +11,7 @@ export declare function defaultCorruptedMessageHandler(): boolean;
10
11
  export declare class MessagesStream<Key, Value, HeaderKey, HeaderValue> extends Readable {
11
12
  #private;
12
13
  [kInstance]: number;
14
+ [kConnections]: ConnectionPool;
13
15
  constructor(consumer: Consumer<Key, Value, HeaderKey, HeaderValue>, options: ConsumeOptions<Key, Value, HeaderKey, HeaderValue>);
14
16
  get consumer(): Consumer<Key, Value, HeaderKey, HeaderValue>;
15
17
  get offsetsToFetch(): Map<string, bigint>;
@@ -1,4 +1,8 @@
1
1
  export declare const groupOptionsProperties: {
2
+ groupInstanceId: {
3
+ type: string;
4
+ pattern: string;
5
+ };
2
6
  sessionTimeout: {
3
7
  type: string;
4
8
  minimum: number;
@@ -127,6 +131,10 @@ export declare const consumeOptionsProperties: {
127
131
  export declare const groupOptionsSchema: {
128
132
  type: string;
129
133
  properties: {
134
+ groupInstanceId: {
135
+ type: string;
136
+ pattern: string;
137
+ };
130
138
  sessionTimeout: {
131
139
  type: string;
132
140
  minimum: number;
@@ -233,6 +241,10 @@ export declare const consumeOptionsSchema: {
233
241
  type: string;
234
242
  minimum: number;
235
243
  };
244
+ groupInstanceId: {
245
+ type: string;
246
+ pattern: string;
247
+ };
236
248
  sessionTimeout: {
237
249
  type: string;
238
250
  minimum: number;
@@ -384,6 +396,10 @@ export declare const consumerOptionsSchema: {
384
396
  type: string;
385
397
  minimum: number;
386
398
  };
399
+ groupInstanceId: {
400
+ type: string;
401
+ pattern: string;
402
+ };
387
403
  sessionTimeout: {
388
404
  type: string;
389
405
  minimum: number;
@@ -495,6 +511,10 @@ export declare const fetchOptionsSchema: {
495
511
  type: string;
496
512
  minimum: number;
497
513
  };
514
+ groupInstanceId: {
515
+ type: string;
516
+ pattern: string;
517
+ };
498
518
  sessionTimeout: {
499
519
  type: string;
500
520
  minimum: number;
@@ -588,6 +608,9 @@ export declare const fetchOptionsSchema: {
588
608
  required: string[];
589
609
  };
590
610
  };
611
+ connectionPool: {
612
+ type: string;
613
+ };
591
614
  };
592
615
  required: string[];
593
616
  additionalProperties: boolean;
@@ -1,5 +1,6 @@
1
1
  import { type FetchRequestTopic } from "../../apis/consumer/fetch-v17";
2
2
  import { type GroupProtocols } from "../../apis/enumerations";
3
+ import { type ConnectionPool } from "../../network/connection-pool";
3
4
  import { type KafkaRecord, type Message } from "../../protocol/records";
4
5
  import { type BaseOptions, type ClusterMetadata, type TopicWithPartitionAndOffset } from "../base/types";
5
6
  import { type Deserializers } from "../serde";
@@ -46,6 +47,7 @@ export type MessagesStreamFallbackMode = keyof typeof MessagesStreamFallbackMode
46
47
  export type MessagesStreamFallbackModeValue = (typeof MessagesStreamFallbackModes)[keyof typeof MessagesStreamFallbackModes];
47
48
  export interface GroupOptions {
48
49
  groupProtocol?: typeof GroupProtocols.CLASSIC;
50
+ groupInstanceId?: string;
49
51
  sessionTimeout?: number;
50
52
  rebalanceTimeout?: number;
51
53
  heartbeatInterval?: number;
@@ -54,6 +56,7 @@ export interface GroupOptions {
54
56
  }
55
57
  export interface ConsumerGroupOptions {
56
58
  groupProtocol: typeof GroupProtocols.CONSUMER;
59
+ groupInstanceId?: string;
57
60
  groupRemoteAssignor?: string;
58
61
  rebalanceTimeout?: number;
59
62
  }
@@ -81,6 +84,7 @@ export type ConsumerOptions<Key, Value, HeaderKey, HeaderValue> = BaseOptions &
81
84
  export type FetchOptions<Key, Value, HeaderKey, HeaderValue> = Pick<ConsumeBaseOptions<Key, Value, HeaderKey, HeaderValue>, 'minBytes' | 'maxBytes' | 'maxWaitTime' | 'isolationLevel'> & {
82
85
  node: number;
83
86
  topics: FetchRequestTopic[];
87
+ connectionPool?: ConnectionPool;
84
88
  };
85
89
  export interface CommitOptionsPartition extends TopicWithPartitionAndOffset {
86
90
  leaderEpoch: number;
@@ -4,8 +4,8 @@ import { type CallbackWithPromise } from "../apis/callbacks";
4
4
  import { type Callback, type ResponseParser } from "../apis/definitions";
5
5
  import { type SASLMechanismValue } from "../apis/enumerations";
6
6
  import { type SaslAuthenticateResponse, type SASLAuthenticationAPI } from "../apis/security/sasl-authenticate-v2";
7
- import { TypedEventEmitter, type TypedEvents } from "../events";
8
7
  import { TimeoutError } from "../errors";
8
+ import { TypedEventEmitter, type TypedEvents } from "../events";
9
9
  import { Writer } from "../protocol/writer";
10
10
  export interface ConnectionEvents extends TypedEvents {
11
11
  connecting: () => void;
@@ -40,6 +40,7 @@ export interface ConnectionOptions {
40
40
  requestTimeout?: number;
41
41
  maxInflights?: number;
42
42
  tls?: TLSConnectionOptions;
43
+ ssl?: TLSConnectionOptions;
43
44
  tlsServerName?: string | boolean;
44
45
  sasl?: SASLOptions;
45
46
  ownerId?: number;
@@ -62,6 +63,7 @@ export declare const ConnectionStatuses: {
62
63
  readonly NONE: "none";
63
64
  readonly CONNECTING: "connecting";
64
65
  readonly AUTHENTICATING: "authenticating";
66
+ readonly REAUTHENTICATING: "reauthenticating";
65
67
  readonly CONNECTED: "connected";
66
68
  readonly CLOSED: "closed";
67
69
  readonly CLOSING: "closing";
@@ -89,4 +91,5 @@ export declare class Connection extends TypedEventEmitter<ConnectionEvents> {
89
91
  close(callback: CallbackWithPromise<void>): void;
90
92
  close(): Promise<void>;
91
93
  send<ReturnType>(apiKey: number, apiVersion: number, createPayload: () => Writer, responseParser: ResponseParser<ReturnType>, hasRequestHeaderTaggedFields: boolean, hasResponseHeaderTaggedFields: boolean, callback: Callback<ReturnType>): void;
94
+ reauthenticate(): void;
92
95
  }
@@ -24,11 +24,22 @@ export interface MessageConsumerMetadata {
24
24
  generationId: number;
25
25
  memberId: string;
26
26
  }
27
+ export interface MessageJSON<Key = unknown, Value = unknown, HeaderKey = unknown, HeaderValue = unknown> {
28
+ key: Key;
29
+ value: Value;
30
+ headers: Array<[HeaderKey, HeaderValue]>;
31
+ topic: string;
32
+ partition: number;
33
+ timestamp: string;
34
+ offset: string;
35
+ metadata: Record<string, unknown>;
36
+ }
27
37
  export interface Message<Key = Buffer, Value = Buffer, HeaderKey = Buffer, HeaderValue = Buffer> extends Required<MessageBase<Key, Value>> {
28
38
  headers: Map<HeaderKey, HeaderValue>;
29
39
  offset: bigint;
30
40
  metadata: Record<string, unknown>;
31
41
  commit(callback?: (error?: Error) => void): void | Promise<void>;
42
+ toJSON(): MessageJSON<Key, Value, HeaderKey, HeaderValue>;
32
43
  }
33
44
  export interface MessageRecord {
34
45
  key?: Buffer;
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export const name = "@platformatic/kafka";
2
- export const version = "1.25.0";
2
+ export const version = "1.26.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/kafka",
3
- "version": "1.25.0",
3
+ "version": "1.26.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)",