@platformatic/kafka 1.27.0 → 1.29.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 (54) hide show
  1. package/README.md +14 -7
  2. package/dist/apis/admin/describe-cluster-v0.d.ts +2 -2
  3. package/dist/apis/admin/describe-cluster-v0.js +1 -1
  4. package/dist/apis/admin/list-groups-v4.d.ts +2 -2
  5. package/dist/apis/admin/list-groups-v4.js +1 -1
  6. package/dist/apis/admin/list-transactions-v0.d.ts +2 -2
  7. package/dist/apis/admin/list-transactions-v0.js +1 -1
  8. package/dist/apis/consumer/join-group-v6.d.ts +2 -2
  9. package/dist/apis/consumer/join-group-v6.js +1 -1
  10. package/dist/apis/consumer/join-group-v7.d.ts +2 -2
  11. package/dist/apis/consumer/join-group-v7.js +1 -1
  12. package/dist/apis/consumer/offset-commit-v8.d.ts +2 -2
  13. package/dist/apis/consumer/sync-group-v4.d.ts +2 -2
  14. package/dist/apis/consumer/sync-group-v4.js +1 -1
  15. package/dist/clients/admin/admin.d.ts +3 -1
  16. package/dist/clients/admin/admin.js +88 -7
  17. package/dist/clients/admin/options.d.ts +43 -0
  18. package/dist/clients/admin/options.js +33 -0
  19. package/dist/clients/admin/types.d.ts +19 -0
  20. package/dist/clients/base/base.js +6 -1
  21. package/dist/clients/consumer/messages-stream.js +41 -12
  22. package/dist/clients/producer/index.d.ts +1 -0
  23. package/dist/clients/producer/index.js +1 -0
  24. package/dist/clients/producer/options.d.ts +63 -0
  25. package/dist/clients/producer/options.js +18 -0
  26. package/dist/clients/producer/producer-stream.d.ts +19 -0
  27. package/dist/clients/producer/producer-stream.js +187 -0
  28. package/dist/clients/producer/producer.d.ts +6 -3
  29. package/dist/clients/producer/producer.js +55 -3
  30. package/dist/clients/producer/types.d.ts +24 -0
  31. package/dist/clients/producer/types.js +6 -1
  32. package/dist/diagnostic.d.ts +2 -2
  33. package/dist/network/connection.js +13 -2
  34. package/dist/protocol/sasl/scram-sha.d.ts +2 -2
  35. package/dist/protocol/sasl/scram-sha.js +35 -27
  36. package/dist/typescript-4/dist/apis/admin/describe-cluster-v0.d.ts +2 -2
  37. package/dist/typescript-4/dist/apis/admin/list-groups-v4.d.ts +2 -2
  38. package/dist/typescript-4/dist/apis/admin/list-transactions-v0.d.ts +2 -2
  39. package/dist/typescript-4/dist/apis/consumer/join-group-v6.d.ts +2 -2
  40. package/dist/typescript-4/dist/apis/consumer/join-group-v7.d.ts +2 -2
  41. package/dist/typescript-4/dist/apis/consumer/offset-commit-v8.d.ts +2 -2
  42. package/dist/typescript-4/dist/apis/consumer/sync-group-v4.d.ts +2 -2
  43. package/dist/typescript-4/dist/clients/admin/admin.d.ts +3 -1
  44. package/dist/typescript-4/dist/clients/admin/options.d.ts +43 -0
  45. package/dist/typescript-4/dist/clients/admin/types.d.ts +19 -0
  46. package/dist/typescript-4/dist/clients/producer/index.d.ts +1 -0
  47. package/dist/typescript-4/dist/clients/producer/options.d.ts +64 -1
  48. package/dist/typescript-4/dist/clients/producer/producer-stream.d.ts +19 -0
  49. package/dist/typescript-4/dist/clients/producer/producer.d.ts +6 -3
  50. package/dist/typescript-4/dist/clients/producer/types.d.ts +25 -1
  51. package/dist/typescript-4/dist/diagnostic.d.ts +2 -2
  52. package/dist/typescript-4/dist/protocol/sasl/scram-sha.d.ts +2 -2
  53. package/dist/version.js +1 -1
  54. package/package.json +3 -3
@@ -3,6 +3,7 @@ import { allowedCompressionsAlgorithms, compressionsAlgorithms } from "../../pro
3
3
  import { messageSchema } from "../../protocol/records.js";
4
4
  import { ajv, enumErrorMessage } from "../../utils.js";
5
5
  import { serdeProperties } from "../serde.js";
6
+ import { allowedProducerStreamReportModes, ProducerStreamReportModes } from "./types.js";
6
7
  export const produceOptionsProperties = {
7
8
  producerId: { bigint: true },
8
9
  producerEpoch: { type: 'number' },
@@ -52,3 +53,20 @@ export const sendOptionsSchema = {
52
53
  additionalProperties: false
53
54
  };
54
55
  export const sendOptionsValidator = ajv.compile(sendOptionsSchema);
56
+ export const defaultProducerStreamOptions = {
57
+ batchSize: 100,
58
+ batchTime: 10,
59
+ reportMode: ProducerStreamReportModes.NONE
60
+ };
61
+ export const producerStreamOptionsSchema = {
62
+ type: 'object',
63
+ properties: {
64
+ highWaterMark: { type: 'integer', minimum: 1 },
65
+ batchSize: { type: 'integer', minimum: 1 },
66
+ batchTime: { type: 'integer', minimum: 0 },
67
+ reportMode: { type: 'string', enum: allowedProducerStreamReportModes },
68
+ ...produceOptionsProperties
69
+ },
70
+ additionalProperties: false
71
+ };
72
+ export const producerStreamOptionsValidator = ajv.compile(producerStreamOptionsSchema);
@@ -0,0 +1,19 @@
1
+ import { Writable } from 'node:stream';
2
+ import { type CallbackWithPromise } from '../../apis/callbacks.ts';
3
+ import type { MessageToProduce } from '../../protocol/records.ts';
4
+ import type { Producer } from './producer.ts';
5
+ import { type ProducerStreamOptions } from './types.ts';
6
+ export declare class ProducerStream<Key = Buffer, Value = Buffer, HeaderKey = Buffer, HeaderValue = Buffer> extends Writable {
7
+ #private;
8
+ instance: number;
9
+ constructor(producer: Producer<Key, Value, HeaderKey, HeaderValue>, options: ProducerStreamOptions<Key, Value, HeaderKey, HeaderValue>);
10
+ get producer(): Producer<Key, Value, HeaderKey, HeaderValue>;
11
+ close(callback: CallbackWithPromise<void>): void;
12
+ close(): Promise<void>;
13
+ _write(message: MessageToProduce<Key, Value, HeaderKey, HeaderValue>, _: BufferEncoding, callback: (error?: Error | null) => void): void;
14
+ _writev(chunks: Array<{
15
+ chunk: MessageToProduce<Key, Value, HeaderKey, HeaderValue>;
16
+ }>, callback: (error?: Error | null) => void): void;
17
+ _final(callback: (error?: Error | null) => void): void;
18
+ _destroy(error: Error | null, callback: (error?: Error | null) => void): void;
19
+ }
@@ -0,0 +1,187 @@
1
+ import { Writable } from 'node:stream';
2
+ import { createPromisifiedCallback, kCallbackPromise } from "../../apis/callbacks.js";
3
+ import { notifyCreation } from "../../diagnostic.js";
4
+ import { defaultProducerStreamOptions } from "./options.js";
5
+ import { ProducerStreamReportModes } from "./types.js";
6
+ let currentInstance = 0;
7
+ export class ProducerStream extends Writable {
8
+ #producer;
9
+ #batchSize;
10
+ #batchTime;
11
+ #reportMode;
12
+ #produceOptions;
13
+ #buffer;
14
+ #timer;
15
+ #flushing;
16
+ #batchId;
17
+ #pendingWriteCallback;
18
+ instance;
19
+ constructor(producer, options) {
20
+ super({
21
+ objectMode: true,
22
+ highWaterMark: options.highWaterMark
23
+ });
24
+ const { batchSize, batchTime, reportMode, highWaterMark: _highWaterMark, ...sendOptions } = options;
25
+ this.#producer = producer;
26
+ this.#batchSize = batchSize ?? defaultProducerStreamOptions.batchSize;
27
+ this.#batchTime = batchTime ?? defaultProducerStreamOptions.batchTime;
28
+ this.#reportMode = reportMode ?? defaultProducerStreamOptions.reportMode;
29
+ this.#produceOptions = sendOptions;
30
+ this.#buffer = [];
31
+ this.#timer = null;
32
+ this.#flushing = false;
33
+ this.#batchId = 0;
34
+ this.#pendingWriteCallback = null;
35
+ this.instance = currentInstance++;
36
+ notifyCreation('producer-stream', this);
37
+ }
38
+ get producer() {
39
+ return this.#producer;
40
+ }
41
+ close(callback) {
42
+ if (!callback) {
43
+ callback = createPromisifiedCallback();
44
+ }
45
+ this.end((error) => callback(error ?? null));
46
+ return callback[kCallbackPromise];
47
+ }
48
+ _write(message, _, callback) {
49
+ this.#buffer.push(message);
50
+ const finalizeAfterFlush = this.#buffer.length >= this.writableHighWaterMark;
51
+ if (this.#buffer.length >= this.#batchSize) {
52
+ this.#clearTimer();
53
+ this.#flush();
54
+ }
55
+ else {
56
+ this.#scheduleFlush();
57
+ }
58
+ if (finalizeAfterFlush) {
59
+ this.#pendingWriteCallback = callback;
60
+ return;
61
+ }
62
+ callback(null);
63
+ }
64
+ _writev(chunks, callback) {
65
+ for (const { chunk } of chunks) {
66
+ this.#buffer.push(chunk);
67
+ }
68
+ const finalizeAfterFlush = this.#buffer.length >= this.writableHighWaterMark;
69
+ if (this.#buffer.length >= this.#batchSize) {
70
+ this.#clearTimer();
71
+ this.#flush();
72
+ }
73
+ else {
74
+ this.#scheduleFlush();
75
+ }
76
+ if (finalizeAfterFlush) {
77
+ this.#pendingWriteCallback = callback;
78
+ return;
79
+ }
80
+ callback(null);
81
+ }
82
+ _final(callback) {
83
+ this.#clearTimer();
84
+ if (!this.#buffer.length && !this.#flushing) {
85
+ callback(null);
86
+ return;
87
+ }
88
+ const self = this;
89
+ function onError(error) {
90
+ self.removeListener('error', onError);
91
+ self.removeListener('flush', onFlush);
92
+ callback(error);
93
+ }
94
+ function onFlush() {
95
+ if (self.#buffer.length || self.#flushing) {
96
+ return;
97
+ }
98
+ self.removeListener('error', onError);
99
+ self.removeListener('flush', onFlush);
100
+ callback(null);
101
+ }
102
+ this.on('error', onError);
103
+ this.on('flush', onFlush);
104
+ this.#flush();
105
+ }
106
+ _destroy(error, callback) {
107
+ this.#clearTimer();
108
+ this.#buffer = [];
109
+ this.#flushWriteCallbacks(error);
110
+ callback(error);
111
+ }
112
+ #scheduleFlush() {
113
+ if (this.#timer || this.#batchTime < 0) {
114
+ return;
115
+ }
116
+ this.#timer = setTimeout(() => {
117
+ this.#timer = null;
118
+ this.#flush();
119
+ }, this.#batchTime);
120
+ }
121
+ #clearTimer() {
122
+ if (this.#timer) {
123
+ clearTimeout(this.#timer);
124
+ this.#timer = null;
125
+ }
126
+ }
127
+ #flush() {
128
+ if (this.#flushing || this.destroyed) {
129
+ return;
130
+ }
131
+ /* c8 ignore next 3 - Hard to test */
132
+ if (this.#buffer.length === 0) {
133
+ return;
134
+ }
135
+ this.#flushing = true;
136
+ const pending = this.#buffer.splice(0, this.#batchSize);
137
+ const batchId = this.#batchId++;
138
+ const startedAt = Date.now();
139
+ this.#producer.send({ messages: pending, ...this.#produceOptions }, (error, result) => {
140
+ this.#flushing = false;
141
+ if (error) {
142
+ this.#flushWriteCallbacks(error);
143
+ this.destroy(error);
144
+ return;
145
+ }
146
+ this.#emitReports(this.#reportMode, batchId, pending, result);
147
+ this.emit('flush', { batchId, count: pending.length, duration: Date.now() - startedAt, result: result });
148
+ if (this.#buffer.length >= this.#batchSize) {
149
+ this.#flush();
150
+ }
151
+ else if (this.#buffer.length > 0) {
152
+ this.#scheduleFlush();
153
+ }
154
+ this.#flushWriteCallbacks();
155
+ });
156
+ }
157
+ #flushWriteCallbacks(error) {
158
+ if (!this.#pendingWriteCallback || this.#buffer.length >= this.writableHighWaterMark) {
159
+ return;
160
+ }
161
+ const callback = this.#pendingWriteCallback;
162
+ this.#pendingWriteCallback = null;
163
+ callback(error ?? null);
164
+ }
165
+ #emitReports(mode, batchId, pending, result) {
166
+ if (mode === ProducerStreamReportModes.NONE) {
167
+ return;
168
+ }
169
+ if (mode === ProducerStreamReportModes.BATCH) {
170
+ const report = {
171
+ batchId,
172
+ count: pending.length,
173
+ result
174
+ };
175
+ this.emit('delivery-report', report);
176
+ return;
177
+ }
178
+ for (let i = 0; i < pending.length; i++) {
179
+ const report = {
180
+ batchId,
181
+ index: i,
182
+ message: pending[i]
183
+ };
184
+ this.emit('delivery-report', report);
185
+ }
186
+ }
187
+ }
@@ -2,8 +2,9 @@ import { type CallbackWithPromise } from '../../apis/callbacks.ts';
2
2
  import { type Message } from '../../protocol/records.ts';
3
3
  import { kTransaction, kTransactionAddOffsets, kTransactionAddPartitions, kTransactionCancel, kTransactionCommitOffset, kTransactionEnd, kTransactionFindCoordinator } from '../../symbols.ts';
4
4
  import { Base } from '../base/base.ts';
5
+ import { ProducerStream } from './producer-stream.ts';
5
6
  import { Transaction } from './transaction.ts';
6
- import { type ProduceOptions, type ProduceResult, type ProducerInfo, type ProducerOptions, type SendOptions } from './types.ts';
7
+ import { type ProduceOptions, type ProduceResult, type ProducerInfo, type ProducerOptions, type ProducerStreamOptions, type SendOptions } from './types.ts';
7
8
  export declare function noopSerializer(data?: Buffer): Buffer | undefined;
8
9
  export declare class Producer<Key = Buffer, Value = Buffer, HeaderKey = Buffer, HeaderValue = Buffer> extends Base<ProducerOptions<Key, Value, HeaderKey, HeaderValue>> {
9
10
  #private;
@@ -12,8 +13,9 @@ export declare class Producer<Key = Buffer, Value = Buffer, HeaderKey = Buffer,
12
13
  get producerEpoch(): number | undefined;
13
14
  get transaction(): Transaction<Key, Value, HeaderKey, HeaderValue> | undefined;
14
15
  get coordinatorId(): number;
15
- close(callback: CallbackWithPromise<void>): void;
16
- close(): Promise<void>;
16
+ get streamsCount(): number;
17
+ close(force: boolean | CallbackWithPromise<void>, callback?: CallbackWithPromise<void>): void;
18
+ close(force?: boolean): Promise<void>;
17
19
  initIdempotentProducer(options: ProduceOptions<Key, Value, HeaderKey, HeaderValue>, callback: CallbackWithPromise<ProducerInfo>): void;
18
20
  initIdempotentProducer(options: ProduceOptions<Key, Value, HeaderKey, HeaderValue>): Promise<ProducerInfo>;
19
21
  send(options: SendOptions<Key, Value, HeaderKey, HeaderValue> & {
@@ -22,6 +24,7 @@ export declare class Producer<Key = Buffer, Value = Buffer, HeaderKey = Buffer,
22
24
  send(options: SendOptions<Key, Value, HeaderKey, HeaderValue> & {
23
25
  [kTransaction]?: number;
24
26
  }): Promise<ProduceResult>;
27
+ asStream(options?: ProducerStreamOptions<Key, Value, HeaderKey, HeaderValue>): ProducerStream<Key, Value, HeaderKey, HeaderValue>;
25
28
  beginTransaction(options: ProduceOptions<Key, Value, HeaderKey, HeaderValue>, callback: CallbackWithPromise<Transaction<Key, Value, HeaderKey, HeaderValue>>): void;
26
29
  beginTransaction(options?: ProduceOptions<Key, Value, HeaderKey, HeaderValue>): Promise<Transaction<Key, Value, HeaderKey, HeaderValue>>;
27
30
  [kTransactionFindCoordinator](callback: CallbackWithPromise<void>): void;
@@ -9,7 +9,8 @@ import { kInstance, kTransaction, kTransactionAddOffsets, kTransactionAddPartiti
9
9
  import { emitExperimentalApiWarning, NumericMap } from "../../utils.js";
10
10
  import { Base, kAfterCreate, kCheckNotClosed, kClosed, kGetApi, kGetBootstrapConnection, kGetConnection, kMetadata, kOptions, kPerformDeduplicated, kPerformWithRetry, kPrometheus, kValidateOptions } from "../base/base.js";
11
11
  import { ensureMetric } from "../metrics.js";
12
- import { produceOptionsValidator, producerOptionsValidator, sendOptionsValidator } from "./options.js";
12
+ import { produceOptionsValidator, producerOptionsValidator, producerStreamOptionsValidator, sendOptionsValidator } from "./options.js";
13
+ import { ProducerStream } from "./producer-stream.js";
13
14
  import { Transaction } from "./transaction.js";
14
15
  // Don't move this function as being in the same file will enable V8 to remove.
15
16
  // For futher info, ask Matteo.
@@ -29,6 +30,7 @@ export class Producer extends Base {
29
30
  #metricsProducedMessages;
30
31
  #coordinatorId;
31
32
  #transaction;
33
+ #streams;
32
34
  #sendOperation;
33
35
  constructor(options) {
34
36
  if (options.idempotent) {
@@ -64,6 +66,7 @@ export class Producer extends Base {
64
66
  this.#valueSerializer = serializers?.value ?? noopSerializer;
65
67
  this.#headerKeySerializer = serializers?.headerKey ?? noopSerializer;
66
68
  this.#headerValueSerializer = serializers?.headerValue ?? noopSerializer;
69
+ this.#streams = new Set();
67
70
  this[kOptions].transactionalId ??= randomUUID();
68
71
  if (this[kPrometheus]) {
69
72
  ensureMetric(this[kPrometheus], 'Gauge', 'kafka_producers', 'Number of active Kafka producers').inc();
@@ -92,7 +95,14 @@ export class Producer extends Base {
92
95
  get coordinatorId() {
93
96
  return this.#coordinatorId;
94
97
  }
95
- close(callback) {
98
+ get streamsCount() {
99
+ return this.#streams.size;
100
+ }
101
+ close(force, callback) {
102
+ if (typeof force === 'function') {
103
+ callback = force;
104
+ force = false;
105
+ }
96
106
  if (!callback) {
97
107
  callback = createPromisifiedCallback();
98
108
  }
@@ -101,6 +111,36 @@ export class Producer extends Base {
101
111
  return callback[kCallbackPromise];
102
112
  }
103
113
  this[kClosed] = true;
114
+ this.#performClose(force, callback);
115
+ return callback[kCallbackPromise];
116
+ }
117
+ #performClose(force, callback) {
118
+ for (const stream of this.#streams) {
119
+ /* c8 ignore next 3 - Hard to test */
120
+ if (stream.closed || stream.destroyed) {
121
+ this.#streams.delete(stream);
122
+ }
123
+ }
124
+ if (this.#streams.size) {
125
+ if (!force) {
126
+ this[kClosed] = false;
127
+ callback(new UserError('Cannot close producer while producing messages.'));
128
+ return;
129
+ }
130
+ runConcurrentCallbacks('Closing streams failed.', this.#streams, (stream, concurrentCallback) => {
131
+ stream.close(concurrentCallback);
132
+ }, error => {
133
+ /* c8 ignore next 5 - Hard to test */
134
+ if (error) {
135
+ this[kClosed] = false;
136
+ callback(error);
137
+ return;
138
+ }
139
+ this.#streams.clear();
140
+ this.#performClose(false, callback);
141
+ });
142
+ return;
143
+ }
104
144
  super.close(error => {
105
145
  if (error) {
106
146
  this[kClosed] = false;
@@ -112,7 +152,6 @@ export class Producer extends Base {
112
152
  }
113
153
  callback(null);
114
154
  });
115
- return callback[kCallbackPromise];
116
155
  }
117
156
  initIdempotentProducer(options, callback) {
118
157
  if (!callback) {
@@ -162,6 +201,19 @@ export class Producer extends Base {
162
201
  producerSendsChannel.traceCallback(this.#sendOperation, 1, createDiagnosticContext({ client: this, operation: 'send', options }), this, options, callback);
163
202
  return callback[kCallbackPromise];
164
203
  }
204
+ asStream(options) {
205
+ options ??= {};
206
+ const validationError = this[kValidateOptions](options, producerStreamOptionsValidator, '/options', false);
207
+ if (validationError) {
208
+ throw validationError;
209
+ }
210
+ const stream = new ProducerStream(this, options);
211
+ this.#streams.add(stream);
212
+ stream.once('close', () => {
213
+ this.#streams.delete(stream);
214
+ });
215
+ return stream;
216
+ }
165
217
  beginTransaction(options, callback) {
166
218
  if (!callback) {
167
219
  callback = createPromisifiedCallback();
@@ -13,6 +13,16 @@ export interface ProduceResult {
13
13
  unwritableNodes?: number[];
14
14
  }
15
15
  export type Partitioner<Key, Value, HeaderKey, HeaderValue> = (message: MessageToProduce<Key, Value, HeaderKey, HeaderValue>) => number;
16
+ export interface ProducerStreamReport {
17
+ batchId: number;
18
+ count: number;
19
+ result: ProduceResult;
20
+ }
21
+ export interface ProducerStreamMessageReport<Key, Value, HeaderKey, HeaderValue> {
22
+ batchId: number;
23
+ index: number;
24
+ message: MessageToProduce<Key, Value, HeaderKey, HeaderValue>;
25
+ }
16
26
  export interface ProduceOptions<Key, Value, HeaderKey, HeaderValue> {
17
27
  producerId?: bigint;
18
28
  producerEpoch?: number;
@@ -32,3 +42,17 @@ export type ProducerOptions<Key, Value, HeaderKey, HeaderValue> = BaseOptions &
32
42
  export type SendOptions<Key, Value, HeaderKey, HeaderValue> = {
33
43
  messages: MessageToProduce<Key, Value, HeaderKey, HeaderValue>[];
34
44
  } & ProduceOptions<Key, Value, HeaderKey, HeaderValue>;
45
+ export declare const ProducerStreamReportModes: {
46
+ readonly NONE: "none";
47
+ readonly BATCH: "batch";
48
+ readonly MESSAGE: "message";
49
+ };
50
+ export declare const allowedProducerStreamReportModes: ProducerStreamReportModeValue[];
51
+ export type ProducerStreamReportMode = keyof typeof ProducerStreamReportModes;
52
+ export type ProducerStreamReportModeValue = (typeof ProducerStreamReportModes)[keyof typeof ProducerStreamReportModes];
53
+ export type ProducerStreamOptions<Key, Value, HeaderKey, HeaderValue> = Omit<SendOptions<Key, Value, HeaderKey, HeaderValue>, 'messages'> & {
54
+ highWaterMark?: number;
55
+ batchSize?: number;
56
+ batchTime?: number;
57
+ reportMode?: ProducerStreamReportModeValue;
58
+ };
@@ -1 +1,6 @@
1
- export {};
1
+ export const ProducerStreamReportModes = {
2
+ NONE: 'none',
3
+ BATCH: 'batch',
4
+ MESSAGE: 'message'
5
+ };
6
+ export const allowedProducerStreamReportModes = Object.values(ProducerStreamReportModes);
@@ -4,7 +4,7 @@ import { type ConnectionPool } from './network/connection-pool.ts';
4
4
  import { type Connection } from './network/connection.ts';
5
5
  export type ClientType = 'base' | 'producer' | 'consumer' | 'admin';
6
6
  export interface CreationEvent<InstanceType> {
7
- type: ClientType | 'connection' | 'connectionPool';
7
+ type: ClientType | 'connection' | 'connection-pool' | 'messages-stream' | 'producer-stream';
8
8
  instance: InstanceType;
9
9
  }
10
10
  export type ConnectionDiagnosticEvent<Attributes = Record<string, unknown>> = {
@@ -29,7 +29,7 @@ export type DiagnosticContext<BaseContext = {}> = BaseContext & {
29
29
  };
30
30
  export declare const channelsNamespace: "plt:kafka";
31
31
  export declare function createDiagnosticContext<BaseContext = {}>(context: BaseContext): DiagnosticContext<BaseContext>;
32
- export declare function notifyCreation<InstanceType>(type: ClientType | 'connection' | 'connection-pool' | 'messages-stream', instance: InstanceType): void;
32
+ export declare function notifyCreation<InstanceType>(type: ClientType | 'connection' | 'connection-pool' | 'messages-stream' | 'producer-stream', instance: InstanceType): void;
33
33
  export declare function createChannel<DiagnosticEvent extends object>(name: string): ChannelWithName<DiagnosticEvent>;
34
34
  export declare function createTracingChannel<DiagnosticEvent extends object>(name: string): TracingChannelWithName<DiagnosticEvent>;
35
35
  export declare const instancesChannel: ChannelWithName<object>;
@@ -126,7 +126,14 @@ export class Connection extends TypedEventEmitter {
126
126
  connectionsConnectsChannel.asyncEnd.publish(diagnosticContext);
127
127
  };
128
128
  const connectingSocketErrorHandler = (error) => {
129
- this.#onConnectionError(host, port, diagnosticContext, error);
129
+ let cause = error;
130
+ const tlsConnectionError = error;
131
+ if (this.#options.tls &&
132
+ tlsConnectionError.code === 'ECONNRESET' &&
133
+ tlsConnectionError.message.includes('secure TLS connection was established')) {
134
+ cause = new UserError('TLS handshake failed. Please verify the broker supports TLS.', { cause: error });
135
+ }
136
+ this.#onConnectionError(host, port, diagnosticContext, cause);
130
137
  };
131
138
  this.emit('connecting');
132
139
  this.#host = host;
@@ -404,9 +411,13 @@ export class Connection extends TypedEventEmitter {
404
411
  if (sessionLifetimeMs > 0) {
405
412
  this.#reauthenticationTimeout = setTimeout(this.reauthenticate.bind(this), Number(sessionLifetimeMs) * 0.8);
406
413
  }
407
- if (this.#status === ConnectionStatuses.CONNECTED || this.#status === ConnectionStatuses.REAUTHENTICATING) {
414
+ const isReauthenticating = this.#status === ConnectionStatuses.REAUTHENTICATING;
415
+ if (this.#status === ConnectionStatuses.CONNECTED || isReauthenticating) {
408
416
  this.#status = ConnectionStatuses.CONNECTED;
409
417
  this.emit('sasl:authentication:extended', authBytes);
418
+ if (isReauthenticating) {
419
+ this.emit('connect');
420
+ }
410
421
  }
411
422
  else {
412
423
  this.emit('sasl:authentication', authBytes);
@@ -8,7 +8,7 @@ export interface ScramAlgorithmDefinition {
8
8
  }
9
9
  export interface ScramCryptoModule {
10
10
  h: (definition: ScramAlgorithmDefinition, data: string | Buffer) => Buffer;
11
- hi: (definition: ScramAlgorithmDefinition, password: string, salt: Buffer, iterations: number) => Buffer;
11
+ hi: (definition: ScramAlgorithmDefinition, password: string, salt: Buffer, iterations: number) => Promise<Buffer>;
12
12
  hmac: (definition: ScramAlgorithmDefinition, key: Buffer, data: string | Buffer) => Buffer;
13
13
  xor: (a: Buffer, b: Buffer) => Buffer;
14
14
  }
@@ -31,7 +31,7 @@ export declare function createNonce(): string;
31
31
  export declare function sanitizeString(str: string): string;
32
32
  export declare function parseParameters(data: Buffer): Record<string, string>;
33
33
  export declare function h(definition: ScramAlgorithmDefinition, data: string | Buffer): Buffer;
34
- export declare function hi(definition: ScramAlgorithmDefinition, password: string, salt: Buffer, iterations: number): Buffer;
34
+ export declare function hi(definition: ScramAlgorithmDefinition, password: string, salt: Buffer, iterations: number): Promise<Buffer>;
35
35
  export declare function hmac(definition: ScramAlgorithmDefinition, key: Buffer, data: string | Buffer): Buffer;
36
36
  export declare function xor(a: Buffer, b: Buffer): Buffer;
37
37
  export declare const defaultCrypto: ScramCryptoModule;
@@ -1,4 +1,5 @@
1
- import { createHash, createHmac, pbkdf2Sync, randomBytes } from 'node:crypto';
1
+ import { createHash, createHmac, pbkdf2, randomBytes } from 'node:crypto';
2
+ import { promisify } from 'node:util';
2
3
  import { createPromisifiedCallback, kCallbackPromise } from "../../apis/callbacks.js";
3
4
  import { AuthenticationError } from "../../errors.js";
4
5
  import { getCredential } from "./utils.js";
@@ -38,8 +39,9 @@ export function parseParameters(data) {
38
39
  export function h(definition, data) {
39
40
  return createHash(definition.algorithm).update(data).digest();
40
41
  }
42
+ const pbkdf2Async = promisify(pbkdf2);
41
43
  export function hi(definition, password, salt, iterations) {
42
- return pbkdf2Sync(password, salt, iterations, definition.keyLength, definition.algorithm);
44
+ return pbkdf2Async(password, salt, iterations, definition.keyLength, definition.algorithm);
43
45
  }
44
46
  export function hmac(definition, key, data) {
45
47
  return createHmac(definition.algorithm, key).update(data).digest();
@@ -93,31 +95,37 @@ function performAuthentication(connection, algorithm, definition, authenticateAP
93
95
  // ClientProof := ClientKey XOR ClientSignature
94
96
  // ServerKey := HMAC(SaltedPassword, "Server Key")
95
97
  // ServerSignature := HMAC(ServerKey, AuthMessage)
96
- const saltedPassword = hi(definition, password, salt, iterations);
97
- const clientKey = hmac(definition, saltedPassword, HMAC_CLIENT_KEY);
98
- const storedKey = h(definition, clientKey);
99
- const clientFinalMessageWithoutProof = `c=${GS2_HEADER_BASE64},r=${serverNonce}`;
100
- const authMessage = `${clientFirstMessageBare},${serverFirstMessage},${clientFinalMessageWithoutProof}`;
101
- const clientSignature = hmac(definition, storedKey, authMessage);
102
- const clientProof = xor(clientKey, clientSignature);
103
- const serverKey = hmac(definition, saltedPassword, HMAC_SERVER_KEY);
104
- const serverSignature = hmac(definition, serverKey, authMessage);
105
- authenticateAPI(connection, Buffer.from(`${clientFinalMessageWithoutProof},p=${clientProof.toString('base64')}`), (error, lastResponse) => {
106
- if (error) {
107
- callback(new AuthenticationError('SASL authentication failed.', { cause: error }));
108
- return;
109
- }
110
- // Send the last message to the server
111
- const lastData = parseParameters(lastResponse.authBytes);
112
- if (lastData.e) {
113
- callback(new AuthenticationError(lastData.e));
114
- return;
115
- }
116
- else if (lastData.v !== serverSignature.toString('base64')) {
117
- callback(new AuthenticationError('Invalid server signature.'));
118
- return;
119
- }
120
- callback(null, lastResponse);
98
+ hi(definition, password, salt, iterations)
99
+ .then(saltedPassword => {
100
+ const clientKey = hmac(definition, saltedPassword, HMAC_CLIENT_KEY);
101
+ const storedKey = h(definition, clientKey);
102
+ const clientFinalMessageWithoutProof = `c=${GS2_HEADER_BASE64},r=${serverNonce}`;
103
+ const authMessage = `${clientFirstMessageBare},${serverFirstMessage},${clientFinalMessageWithoutProof}`;
104
+ const clientSignature = hmac(definition, storedKey, authMessage);
105
+ const clientProof = xor(clientKey, clientSignature);
106
+ const serverKey = hmac(definition, saltedPassword, HMAC_SERVER_KEY);
107
+ const serverSignature = hmac(definition, serverKey, authMessage);
108
+ authenticateAPI(connection, Buffer.from(`${clientFinalMessageWithoutProof},p=${clientProof.toString('base64')}`), (error, lastResponse) => {
109
+ if (error) {
110
+ callback(new AuthenticationError('SASL authentication failed.', { cause: error }));
111
+ return;
112
+ }
113
+ // Send the last message to the server
114
+ const lastData = parseParameters(lastResponse.authBytes);
115
+ if (lastData.e) {
116
+ callback(new AuthenticationError(lastData.e));
117
+ return;
118
+ }
119
+ else if (lastData.v !== serverSignature.toString('base64')) {
120
+ callback(new AuthenticationError('Invalid server signature.'));
121
+ return;
122
+ }
123
+ callback(null, lastResponse);
124
+ });
125
+ })
126
+ /* c8 ignore next 3 - Hard to test */
127
+ .catch(error => {
128
+ callback(new AuthenticationError('SASL authentication failed.', { cause: error }));
121
129
  });
122
130
  });
123
131
  }
@@ -18,6 +18,6 @@ export interface DescribeClusterResponse {
18
18
  brokers: DescribeClusterResponseBroker[];
19
19
  clusterAuthorizedOperations: number;
20
20
  }
21
- export declare function createRequest(includeClusterAuthorizedOperations: boolean): Writer;
21
+ export declare function createRequest(includeClusterAuthorizedOperations: boolean, _endpointType: number): Writer;
22
22
  export declare function parseResponse(_correlationId: number, apiKey: number, apiVersion: number, reader: Reader): DescribeClusterResponse;
23
- export declare const api: import("../definitions").API<[includeClusterAuthorizedOperations: boolean], DescribeClusterResponse>;
23
+ export declare const api: import("../definitions").API<[includeClusterAuthorizedOperations: boolean, _endpointType: number], DescribeClusterResponse>;
@@ -12,6 +12,6 @@ export interface ListGroupsResponse {
12
12
  errorCode: number;
13
13
  groups: ListGroupsResponseGroup[];
14
14
  }
15
- export declare function createRequest(statesFilter: ConsumerGroupStateValue[]): Writer;
15
+ export declare function createRequest(statesFilter: ConsumerGroupStateValue[], _typesFilter: string[]): Writer;
16
16
  export declare function parseResponse(_correlationId: number, apiKey: number, apiVersion: number, reader: Reader): ListGroupsResponse;
17
- export declare const api: import("../definitions").API<[statesFilter: ("PREPARING_REBALANCE" | "COMPLETING_REBALANCE" | "STABLE" | "DEAD" | "EMPTY")[]], ListGroupsResponse>;
17
+ export declare const api: import("../definitions").API<[statesFilter: ("PREPARING_REBALANCE" | "COMPLETING_REBALANCE" | "STABLE" | "DEAD" | "EMPTY")[], _typesFilter: string[]], ListGroupsResponse>;
@@ -13,6 +13,6 @@ export interface ListTransactionsResponse {
13
13
  unknownStateFilters: string[];
14
14
  transactionStates: ListTransactionsResponseTransactionState[];
15
15
  }
16
- export declare function createRequest(stateFilters: TransactionState[], producerIdFilters: bigint[]): Writer;
16
+ export declare function createRequest(stateFilters: TransactionState[], producerIdFilters: bigint[], _durationFilter: bigint): Writer;
17
17
  export declare function parseResponse(_correlationId: number, apiKey: number, apiVersion: number, reader: Reader): ListTransactionsResponse;
18
- export declare const api: import("../definitions").API<[stateFilters: ("EMPTY" | "ONGOING" | "PREPARE_ABORT" | "COMMITTING" | "ABORTING" | "COMPLETE_COMMIT" | "COMPLETE_ABORT")[], producerIdFilters: bigint[]], ListTransactionsResponse>;
18
+ export declare const api: import("../definitions").API<[stateFilters: ("EMPTY" | "ONGOING" | "PREPARE_ABORT" | "COMMITTING" | "ABORTING" | "COMPLETE_COMMIT" | "COMPLETE_ABORT")[], producerIdFilters: bigint[], _durationFilter: bigint], ListTransactionsResponse>;
@@ -22,6 +22,6 @@ export interface JoinGroupResponse {
22
22
  memberId: NullableString;
23
23
  members: JoinGroupResponseMember[];
24
24
  }
25
- export declare function createRequest(groupId: string, sessionTimeoutMs: number, rebalanceTimeoutMs: number, memberId: string, groupInstanceId: NullableString, protocolType: string, protocols: JoinGroupRequestProtocol[]): Writer;
25
+ export declare function createRequest(groupId: string, sessionTimeoutMs: number, rebalanceTimeoutMs: number, memberId: string, groupInstanceId: NullableString, protocolType: string, protocols: JoinGroupRequestProtocol[], _reason?: NullableString): Writer;
26
26
  export declare function parseResponse(_correlationId: number, apiKey: number, apiVersion: number, reader: Reader): JoinGroupResponse;
27
- export declare const api: import("../definitions").API<[groupId: string, sessionTimeoutMs: number, rebalanceTimeoutMs: number, memberId: string, groupInstanceId: NullableString, protocolType: string, protocols: JoinGroupRequestProtocol[]], JoinGroupResponse>;
27
+ export declare const api: import("../definitions").API<[groupId: string, sessionTimeoutMs: number, rebalanceTimeoutMs: number, memberId: string, groupInstanceId: NullableString, protocolType: string, protocols: JoinGroupRequestProtocol[], _reason?: NullableString], JoinGroupResponse>;
@@ -22,6 +22,6 @@ export interface JoinGroupResponse {
22
22
  memberId: NullableString;
23
23
  members: JoinGroupResponseMember[];
24
24
  }
25
- export declare function createRequest(groupId: string, sessionTimeoutMs: number, rebalanceTimeoutMs: number, memberId: string, groupInstanceId: NullableString, protocolType: string, protocols: JoinGroupRequestProtocol[]): Writer;
25
+ export declare function createRequest(groupId: string, sessionTimeoutMs: number, rebalanceTimeoutMs: number, memberId: string, groupInstanceId: NullableString, protocolType: string, protocols: JoinGroupRequestProtocol[], _reason?: NullableString): Writer;
26
26
  export declare function parseResponse(_correlationId: number, apiKey: number, apiVersion: number, reader: Reader): JoinGroupResponse;
27
- export declare const api: import("../definitions").API<[groupId: string, sessionTimeoutMs: number, rebalanceTimeoutMs: number, memberId: string, groupInstanceId: NullableString, protocolType: string, protocols: JoinGroupRequestProtocol[]], JoinGroupResponse>;
27
+ export declare const api: import("../definitions").API<[groupId: string, sessionTimeoutMs: number, rebalanceTimeoutMs: number, memberId: string, groupInstanceId: NullableString, protocolType: string, protocols: JoinGroupRequestProtocol[], _reason?: NullableString], JoinGroupResponse>;
@@ -24,6 +24,6 @@ export interface OffsetCommitResponse {
24
24
  throttleTimeMs: number;
25
25
  topics: OffsetCommitResponseTopic[];
26
26
  }
27
- export declare function createRequest(groupId: string, generationIdOrMemberEpoch: number, memberId: string, groupInstanceId: NullableString, topics: OffsetCommitRequestTopic[]): Writer;
27
+ export declare function createRequest(groupId: string, generationIdOrMemberEpoch: number, memberId: NullableString, groupInstanceId: NullableString, topics: OffsetCommitRequestTopic[]): Writer;
28
28
  export declare function parseResponse(_correlationId: number, apiKey: number, apiVersion: number, reader: Reader): OffsetCommitResponse;
29
- export declare const api: import("../definitions").API<[groupId: string, generationIdOrMemberEpoch: number, memberId: string, groupInstanceId: NullableString, topics: OffsetCommitRequestTopic[]], OffsetCommitResponse>;
29
+ export declare const api: import("../definitions").API<[groupId: string, generationIdOrMemberEpoch: number, memberId: NullableString, groupInstanceId: NullableString, topics: OffsetCommitRequestTopic[]], OffsetCommitResponse>;
@@ -13,6 +13,6 @@ export interface SyncGroupResponse {
13
13
  protocolName: NullableString;
14
14
  assignment: Buffer;
15
15
  }
16
- export declare function createRequest(groupId: string, generationId: number, memberId: string, groupInstanceId: NullableString, assignments: SyncGroupRequestAssignment[]): Writer;
16
+ export declare function createRequest(groupId: string, generationId: number, memberId: string, groupInstanceId: NullableString, _protocolType: NullableString, _protocolName: NullableString, assignments: SyncGroupRequestAssignment[]): Writer;
17
17
  export declare function parseResponse(_correlationId: number, apiKey: number, apiVersion: number, reader: Reader): SyncGroupResponse;
18
- export declare const api: import("../definitions").API<[groupId: string, generationId: number, memberId: string, groupInstanceId: NullableString, assignments: SyncGroupRequestAssignment[]], SyncGroupResponse>;
18
+ export declare const api: import("../definitions").API<[groupId: string, generationId: number, memberId: string, groupInstanceId: NullableString, _protocolType: NullableString, _protocolName: NullableString, assignments: SyncGroupRequestAssignment[]], SyncGroupResponse>;