@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.
- package/README.md +14 -7
- package/dist/apis/admin/describe-cluster-v0.d.ts +2 -2
- package/dist/apis/admin/describe-cluster-v0.js +1 -1
- package/dist/apis/admin/list-groups-v4.d.ts +2 -2
- package/dist/apis/admin/list-groups-v4.js +1 -1
- package/dist/apis/admin/list-transactions-v0.d.ts +2 -2
- package/dist/apis/admin/list-transactions-v0.js +1 -1
- package/dist/apis/consumer/join-group-v6.d.ts +2 -2
- package/dist/apis/consumer/join-group-v6.js +1 -1
- package/dist/apis/consumer/join-group-v7.d.ts +2 -2
- package/dist/apis/consumer/join-group-v7.js +1 -1
- package/dist/apis/consumer/offset-commit-v8.d.ts +2 -2
- package/dist/apis/consumer/sync-group-v4.d.ts +2 -2
- package/dist/apis/consumer/sync-group-v4.js +1 -1
- package/dist/clients/admin/admin.d.ts +3 -1
- package/dist/clients/admin/admin.js +88 -7
- package/dist/clients/admin/options.d.ts +43 -0
- package/dist/clients/admin/options.js +33 -0
- package/dist/clients/admin/types.d.ts +19 -0
- package/dist/clients/base/base.js +6 -1
- package/dist/clients/consumer/messages-stream.js +41 -12
- package/dist/clients/producer/index.d.ts +1 -0
- package/dist/clients/producer/index.js +1 -0
- package/dist/clients/producer/options.d.ts +63 -0
- package/dist/clients/producer/options.js +18 -0
- package/dist/clients/producer/producer-stream.d.ts +19 -0
- package/dist/clients/producer/producer-stream.js +187 -0
- package/dist/clients/producer/producer.d.ts +6 -3
- package/dist/clients/producer/producer.js +55 -3
- package/dist/clients/producer/types.d.ts +24 -0
- package/dist/clients/producer/types.js +6 -1
- package/dist/diagnostic.d.ts +2 -2
- package/dist/network/connection.js +13 -2
- package/dist/protocol/sasl/scram-sha.d.ts +2 -2
- package/dist/protocol/sasl/scram-sha.js +35 -27
- package/dist/typescript-4/dist/apis/admin/describe-cluster-v0.d.ts +2 -2
- package/dist/typescript-4/dist/apis/admin/list-groups-v4.d.ts +2 -2
- package/dist/typescript-4/dist/apis/admin/list-transactions-v0.d.ts +2 -2
- package/dist/typescript-4/dist/apis/consumer/join-group-v6.d.ts +2 -2
- package/dist/typescript-4/dist/apis/consumer/join-group-v7.d.ts +2 -2
- package/dist/typescript-4/dist/apis/consumer/offset-commit-v8.d.ts +2 -2
- package/dist/typescript-4/dist/apis/consumer/sync-group-v4.d.ts +2 -2
- package/dist/typescript-4/dist/clients/admin/admin.d.ts +3 -1
- package/dist/typescript-4/dist/clients/admin/options.d.ts +43 -0
- package/dist/typescript-4/dist/clients/admin/types.d.ts +19 -0
- package/dist/typescript-4/dist/clients/producer/index.d.ts +1 -0
- package/dist/typescript-4/dist/clients/producer/options.d.ts +64 -1
- package/dist/typescript-4/dist/clients/producer/producer-stream.d.ts +19 -0
- package/dist/typescript-4/dist/clients/producer/producer.d.ts +6 -3
- package/dist/typescript-4/dist/clients/producer/types.d.ts +25 -1
- package/dist/typescript-4/dist/diagnostic.d.ts +2 -2
- package/dist/typescript-4/dist/protocol/sasl/scram-sha.d.ts +2 -2
- package/dist/version.js +1 -1
- 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
|
-
|
|
16
|
-
close(
|
|
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
|
-
|
|
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
|
+
};
|
package/dist/diagnostic.d.ts
CHANGED
|
@@ -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' | '
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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:
|
|
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:
|
|
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>;
|