@platformatic/kafka 1.23.0 → 1.24.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/dist/apis/admin/alter-client-quotas-v1.d.ts +4 -4
- package/dist/apis/admin/alter-configs-v2.d.ts +3 -2
- package/dist/apis/admin/alter-partition-reassignments-v0.js +1 -1
- package/dist/apis/admin/create-acls-v3.d.ts +3 -11
- package/dist/apis/admin/create-partitions-v3.d.ts +2 -2
- package/dist/apis/admin/delete-acls-v3.d.ts +4 -19
- package/dist/apis/admin/delete-acls-v3.js +6 -6
- package/dist/apis/admin/describe-acls-v3.d.ts +5 -13
- package/dist/apis/admin/describe-acls-v3.js +9 -9
- package/dist/apis/admin/describe-client-quotas-v0.d.ts +5 -5
- package/dist/apis/admin/describe-configs-v4.d.ts +7 -6
- package/dist/apis/admin/describe-configs-v4.js +1 -1
- package/dist/apis/admin/incremental-alter-configs-v1.d.ts +12 -5
- package/dist/apis/admin/incremental-alter-configs-v1.js +3 -1
- package/dist/apis/admin/list-groups-v4.d.ts +2 -2
- package/dist/apis/admin/list-groups-v5.d.ts +2 -2
- package/dist/apis/admin/offset-delete-v0.js +3 -3
- package/dist/apis/callbacks.d.ts +3 -1
- package/dist/apis/consumer/offset-commit-v9.d.ts +2 -2
- package/dist/apis/consumer/offset-fetch-v8.d.ts +2 -2
- package/dist/apis/consumer/offset-fetch-v9.d.ts +2 -2
- package/dist/apis/definitions.d.ts +2 -2
- package/dist/apis/enumerations.d.ts +134 -42
- package/dist/apis/enumerations.js +42 -6
- package/dist/apis/index.d.ts +1 -0
- package/dist/apis/index.js +1 -0
- package/dist/apis/producer/add-offsets-to-txn-v3.d.ts +10 -0
- package/dist/apis/producer/add-offsets-to-txn-v3.js +34 -0
- package/dist/apis/producer/add-partitions-to-txn-v3.d.ts +34 -0
- package/dist/apis/producer/add-partitions-to-txn-v3.js +67 -0
- package/dist/apis/producer/add-partitions-to-txn-v4.d.ts +34 -0
- package/dist/apis/producer/add-partitions-to-txn-v4.js +79 -0
- package/dist/apis/producer/end-txn-v3.d.ts +10 -0
- package/dist/apis/producer/end-txn-v3.js +34 -0
- package/dist/apis/producer/index.d.ts +5 -0
- package/dist/apis/producer/index.js +5 -0
- package/dist/apis/producer/txn-offset-commit-v3.d.ts +29 -0
- package/dist/apis/producer/txn-offset-commit-v3.js +74 -0
- package/dist/apis/types.d.ts +16 -0
- package/dist/apis/types.js +1 -0
- package/dist/clients/admin/admin.d.ts +31 -1
- package/dist/clients/admin/admin.js +732 -50
- package/dist/clients/admin/options.d.ts +577 -0
- package/dist/clients/admin/options.js +359 -2
- package/dist/clients/admin/types.d.ts +105 -3
- package/dist/clients/base/base.js +29 -26
- package/dist/clients/base/options.d.ts +2 -5
- package/dist/clients/base/options.js +0 -1
- package/dist/clients/base/types.d.ts +8 -2
- package/dist/clients/consumer/consumer.d.ts +58 -2
- package/dist/clients/consumer/consumer.js +116 -47
- package/dist/clients/consumer/messages-stream.d.ts +1 -0
- package/dist/clients/consumer/messages-stream.js +48 -17
- package/dist/clients/consumer/options.d.ts +6 -6
- package/dist/clients/consumer/options.js +3 -3
- package/dist/clients/consumer/types.d.ts +26 -3
- package/dist/clients/index.d.ts +1 -1
- package/dist/clients/index.js +1 -1
- package/dist/clients/producer/options.d.ts +12 -3
- package/dist/clients/producer/options.js +1 -0
- package/dist/clients/producer/producer.d.ts +19 -2
- package/dist/clients/producer/producer.js +335 -37
- package/dist/clients/producer/transaction.d.ts +30 -0
- package/dist/clients/producer/transaction.js +172 -0
- package/dist/clients/producer/types.d.ts +2 -0
- package/dist/diagnostic.d.ts +5 -0
- package/dist/diagnostic.js +5 -0
- package/dist/errors.js +1 -0
- package/dist/network/connection-pool.js +4 -4
- package/dist/network/connection.js +3 -2
- package/dist/protocol/records.d.ts +12 -0
- package/dist/protocol/records.js +6 -4
- package/dist/protocol/sasl/oauth-bearer.js +4 -4
- package/dist/protocol/sasl/plain.js +2 -2
- package/dist/protocol/sasl/scram-sha.js +8 -8
- package/dist/protocol/sasl/utils.js +5 -5
- package/dist/symbols.d.ts +8 -0
- package/dist/symbols.js +8 -0
- package/dist/typescript-4/dist/apis/admin/alter-client-quotas-v1.d.ts +4 -4
- package/dist/typescript-4/dist/apis/admin/alter-configs-v2.d.ts +3 -2
- package/dist/typescript-4/dist/apis/admin/create-acls-v3.d.ts +3 -11
- package/dist/typescript-4/dist/apis/admin/create-partitions-v3.d.ts +2 -2
- package/dist/typescript-4/dist/apis/admin/delete-acls-v3.d.ts +4 -19
- package/dist/typescript-4/dist/apis/admin/describe-acls-v3.d.ts +5 -13
- package/dist/typescript-4/dist/apis/admin/describe-client-quotas-v0.d.ts +5 -5
- package/dist/typescript-4/dist/apis/admin/describe-configs-v4.d.ts +7 -6
- package/dist/typescript-4/dist/apis/admin/incremental-alter-configs-v1.d.ts +13 -6
- package/dist/typescript-4/dist/apis/admin/list-groups-v4.d.ts +2 -2
- package/dist/typescript-4/dist/apis/admin/list-groups-v5.d.ts +2 -2
- package/dist/typescript-4/dist/apis/callbacks.d.ts +3 -1
- package/dist/typescript-4/dist/apis/consumer/offset-commit-v9.d.ts +2 -2
- package/dist/typescript-4/dist/apis/consumer/offset-fetch-v8.d.ts +2 -2
- package/dist/typescript-4/dist/apis/consumer/offset-fetch-v9.d.ts +2 -2
- package/dist/typescript-4/dist/apis/definitions.d.ts +2 -2
- package/dist/typescript-4/dist/apis/enumerations.d.ts +134 -42
- package/dist/typescript-4/dist/apis/index.d.ts +1 -0
- package/dist/typescript-4/dist/apis/producer/add-offsets-to-txn-v3.d.ts +10 -0
- package/dist/typescript-4/dist/apis/producer/add-partitions-to-txn-v3.d.ts +34 -0
- package/dist/typescript-4/dist/apis/producer/add-partitions-to-txn-v4.d.ts +34 -0
- package/dist/typescript-4/dist/apis/producer/end-txn-v3.d.ts +10 -0
- package/dist/typescript-4/dist/apis/producer/index.d.ts +5 -0
- package/dist/typescript-4/dist/apis/producer/txn-offset-commit-v3.d.ts +29 -0
- package/dist/typescript-4/dist/apis/types.d.ts +14 -0
- package/dist/typescript-4/dist/clients/admin/admin.d.ts +31 -1
- package/dist/typescript-4/dist/clients/admin/options.d.ts +577 -0
- package/dist/typescript-4/dist/clients/admin/types.d.ts +105 -3
- package/dist/typescript-4/dist/clients/base/options.d.ts +2 -5
- package/dist/typescript-4/dist/clients/base/types.d.ts +8 -2
- package/dist/typescript-4/dist/clients/consumer/consumer.d.ts +58 -2
- package/dist/typescript-4/dist/clients/consumer/messages-stream.d.ts +1 -0
- package/dist/typescript-4/dist/clients/consumer/options.d.ts +6 -6
- package/dist/typescript-4/dist/clients/consumer/types.d.ts +27 -4
- package/dist/typescript-4/dist/clients/index.d.ts +1 -1
- package/dist/typescript-4/dist/clients/producer/options.d.ts +12 -3
- package/dist/typescript-4/dist/clients/producer/producer.d.ts +19 -2
- package/dist/typescript-4/dist/clients/producer/transaction.d.ts +30 -0
- package/dist/typescript-4/dist/clients/producer/types.d.ts +2 -0
- package/dist/typescript-4/dist/diagnostic.d.ts +5 -0
- package/dist/typescript-4/dist/protocol/records.d.ts +12 -0
- package/dist/typescript-4/dist/symbols.d.ts +9 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
1
2
|
import { createPromisifiedCallback, kCallbackPromise, runConcurrentCallbacks } from "../../apis/callbacks.js";
|
|
2
|
-
import { ProduceAcks } from "../../apis/enumerations.js";
|
|
3
|
-
import { createDiagnosticContext, producerInitIdempotentChannel, producerSendsChannel } from "../../diagnostic.js";
|
|
3
|
+
import { FindCoordinatorKeyTypes, ProduceAcks } from "../../apis/enumerations.js";
|
|
4
|
+
import { createDiagnosticContext, producerInitIdempotentChannel, producerSendsChannel, producerTransactionsChannel } from "../../diagnostic.js";
|
|
4
5
|
import { UserError } from "../../errors.js";
|
|
5
6
|
import { murmur2 } from "../../protocol/murmur2.js";
|
|
7
|
+
import { kInstance, kTransaction, kTransactionAddOffsets, kTransactionAddPartitions, kTransactionCancel, kTransactionCommitOffset, kTransactionEnd, kTransactionFindCoordinator, kTransactionPrepare } from "../../symbols.js";
|
|
6
8
|
import { NumericMap } from "../../utils.js";
|
|
7
9
|
import { Base, kAfterCreate, kCheckNotClosed, kClosed, kGetApi, kGetBootstrapConnection, kGetConnection, kMetadata, kOptions, kPerformDeduplicated, kPerformWithRetry, kPrometheus, kValidateOptions } from "../base/base.js";
|
|
8
10
|
import { ensureMetric } from "../metrics.js";
|
|
9
11
|
import { produceOptionsValidator, producerOptionsValidator, sendOptionsValidator } from "./options.js";
|
|
12
|
+
import { Transaction } from "./transaction.js";
|
|
10
13
|
// Don't move this function as being in the same file will enable V8 to remove.
|
|
11
14
|
// For futher info, ask Matteo.
|
|
12
15
|
export function noopSerializer(data) {
|
|
@@ -23,6 +26,8 @@ export class Producer extends Base {
|
|
|
23
26
|
#headerKeySerializer;
|
|
24
27
|
#headerValueSerializer;
|
|
25
28
|
#metricsProducedMessages;
|
|
29
|
+
#coordinatorId;
|
|
30
|
+
#transaction;
|
|
26
31
|
constructor(options) {
|
|
27
32
|
if (options.idempotent) {
|
|
28
33
|
options.maxInflights = 1;
|
|
@@ -40,6 +45,7 @@ export class Producer extends Base {
|
|
|
40
45
|
this.#valueSerializer = options.serializers?.value ?? noopSerializer;
|
|
41
46
|
this.#headerKeySerializer = options.serializers?.headerKey ?? noopSerializer;
|
|
42
47
|
this.#headerValueSerializer = options.serializers?.headerValue ?? noopSerializer;
|
|
48
|
+
this[kOptions].transactionalId ??= randomUUID();
|
|
43
49
|
this[kValidateOptions](options, producerOptionsValidator, '/options');
|
|
44
50
|
if (this[kPrometheus]) {
|
|
45
51
|
ensureMetric(this[kPrometheus], 'Gauge', 'kafka_producers', 'Number of active Kafka producers').inc();
|
|
@@ -53,6 +59,12 @@ export class Producer extends Base {
|
|
|
53
59
|
get producerEpoch() {
|
|
54
60
|
return this.#producerInfo?.producerEpoch;
|
|
55
61
|
}
|
|
62
|
+
get transaction() {
|
|
63
|
+
return this.#transaction;
|
|
64
|
+
}
|
|
65
|
+
get coordinatorId() {
|
|
66
|
+
return this.#coordinatorId;
|
|
67
|
+
}
|
|
56
68
|
close(callback) {
|
|
57
69
|
if (!callback) {
|
|
58
70
|
callback = createPromisifiedCallback();
|
|
@@ -84,7 +96,7 @@ export class Producer extends Base {
|
|
|
84
96
|
}
|
|
85
97
|
const validationError = this[kValidateOptions](options, produceOptionsValidator, '/options', false);
|
|
86
98
|
if (validationError) {
|
|
87
|
-
callback(validationError
|
|
99
|
+
callback(validationError);
|
|
88
100
|
return callback[kCallbackPromise];
|
|
89
101
|
}
|
|
90
102
|
producerInitIdempotentChannel.traceCallback(this.#initIdempotentProducer, 1, createDiagnosticContext({ client: this, operation: 'initIdempotentProducer', options }), this, options, callback);
|
|
@@ -97,36 +109,284 @@ export class Producer extends Base {
|
|
|
97
109
|
if (this[kCheckNotClosed](callback)) {
|
|
98
110
|
return callback[kCallbackPromise];
|
|
99
111
|
}
|
|
112
|
+
if (this.#transaction && options[kTransaction] !== this.#transaction[kInstance]) {
|
|
113
|
+
callback(new UserError(options[kTransaction]
|
|
114
|
+
? 'The producer is in use by another transaction.'
|
|
115
|
+
: 'The producer is in use by a transaction.'));
|
|
116
|
+
return callback[kCallbackPromise];
|
|
117
|
+
}
|
|
100
118
|
const validationError = this[kValidateOptions](options, sendOptionsValidator, '/options', false);
|
|
101
119
|
if (validationError) {
|
|
102
|
-
callback(validationError
|
|
120
|
+
callback(validationError);
|
|
103
121
|
return callback[kCallbackPromise];
|
|
104
122
|
}
|
|
123
|
+
const idempotent = options.idempotent ?? this[kOptions].idempotent;
|
|
124
|
+
if (idempotent) {
|
|
125
|
+
if (typeof options.producerId !== 'undefined' || typeof options.producerEpoch !== 'undefined') {
|
|
126
|
+
callback(new UserError('Cannot specify producerId or producerEpoch when using idempotent producer.'));
|
|
127
|
+
return callback[kCallbackPromise];
|
|
128
|
+
}
|
|
129
|
+
if (typeof options.acks !== 'undefined' && options.acks !== ProduceAcks.ALL) {
|
|
130
|
+
callback(new UserError('Idempotent producer requires acks to be ALL (-1).'));
|
|
131
|
+
return callback[kCallbackPromise];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
options.acks ??= idempotent ? ProduceAcks.ALL : ProduceAcks.LEADER;
|
|
105
135
|
producerSendsChannel.traceCallback(this.#send, 1, createDiagnosticContext({ client: this, operation: 'send', options }), this, options, callback);
|
|
106
136
|
return callback[kCallbackPromise];
|
|
107
137
|
}
|
|
138
|
+
beginTransaction(options, callback) {
|
|
139
|
+
if (!callback) {
|
|
140
|
+
callback = createPromisifiedCallback();
|
|
141
|
+
}
|
|
142
|
+
if (this[kCheckNotClosed](callback)) {
|
|
143
|
+
return callback[kCallbackPromise];
|
|
144
|
+
}
|
|
145
|
+
if (!options) {
|
|
146
|
+
options = {};
|
|
147
|
+
}
|
|
148
|
+
const validationError = this[kValidateOptions](options, produceOptionsValidator, '/options', false);
|
|
149
|
+
if (validationError) {
|
|
150
|
+
callback(validationError);
|
|
151
|
+
return callback[kCallbackPromise];
|
|
152
|
+
}
|
|
153
|
+
if (!this[kOptions].idempotent) {
|
|
154
|
+
callback(new UserError('Cannot begin a transaction on a non-idempotent producer.'));
|
|
155
|
+
return callback[kCallbackPromise];
|
|
156
|
+
}
|
|
157
|
+
if (this.#transaction) {
|
|
158
|
+
callback(new UserError('There is already an active transaction.'));
|
|
159
|
+
return callback[kCallbackPromise];
|
|
160
|
+
}
|
|
161
|
+
this.#transaction = new Transaction(this);
|
|
162
|
+
producerTransactionsChannel.traceCallback(this.#transaction[kTransactionPrepare], 2, createDiagnosticContext({ client: this, operation: 'begin' }), this.#transaction, this.#producerInfo?.transactionalId, options, callback);
|
|
163
|
+
return callback[kCallbackPromise];
|
|
164
|
+
}
|
|
165
|
+
[kTransactionFindCoordinator](callback) {
|
|
166
|
+
if (this.#coordinatorId !== undefined) {
|
|
167
|
+
callback(null);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const transactionalId = this.#transaction.id;
|
|
171
|
+
this[kPerformDeduplicated]('findCoordinator', deduplicateCallback => {
|
|
172
|
+
this[kPerformWithRetry]('findCoordinator', retryCallback => {
|
|
173
|
+
this[kGetBootstrapConnection]((error, connection) => {
|
|
174
|
+
if (error) {
|
|
175
|
+
retryCallback(error);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
this[kGetApi]('FindCoordinator', (error, api) => {
|
|
179
|
+
if (error) {
|
|
180
|
+
retryCallback(error);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
api(connection, FindCoordinatorKeyTypes.TRANSACTION, [transactionalId], retryCallback);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}, (error, response) => {
|
|
187
|
+
if (error) {
|
|
188
|
+
deduplicateCallback(error);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const groupInfo = response.coordinators.find(coordinator => coordinator.key === transactionalId);
|
|
192
|
+
this.#coordinatorId = groupInfo.nodeId;
|
|
193
|
+
deduplicateCallback(null);
|
|
194
|
+
}, 0);
|
|
195
|
+
}, callback);
|
|
196
|
+
}
|
|
197
|
+
[kTransactionAddPartitions](transactionId, topicsPartitions, callback) {
|
|
198
|
+
if (transactionId !== this.#transaction?.[kInstance]) {
|
|
199
|
+
callback(new UserError('The producer is in use by another transaction.'));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const transactions = [];
|
|
203
|
+
for (const [topic, partitionsSet] of topicsPartitions) {
|
|
204
|
+
transactions.push({
|
|
205
|
+
name: topic,
|
|
206
|
+
partitions: Array.from(partitionsSet)
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
this[kPerformDeduplicated]('addPartitionsToTransaction', deduplicateCallback => {
|
|
210
|
+
this[kPerformWithRetry]('addPartitionsToTransaction', retryCallback => {
|
|
211
|
+
this.#getCoordinatorConnection((error, connection) => {
|
|
212
|
+
if (error) {
|
|
213
|
+
retryCallback(error);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this[kGetApi]('AddPartitionsToTxn', (error, api) => {
|
|
217
|
+
if (error) {
|
|
218
|
+
retryCallback(error);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
api(connection, [
|
|
222
|
+
{
|
|
223
|
+
transactionalId: this.#transaction.id,
|
|
224
|
+
producerId: this.#producerInfo.producerId,
|
|
225
|
+
producerEpoch: this.#producerInfo.producerEpoch,
|
|
226
|
+
topics: transactions,
|
|
227
|
+
verifyOnly: false
|
|
228
|
+
}
|
|
229
|
+
], error => {
|
|
230
|
+
if (error) {
|
|
231
|
+
retryCallback(error);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
retryCallback(null);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
}, deduplicateCallback);
|
|
239
|
+
}, callback);
|
|
240
|
+
}
|
|
241
|
+
[kTransactionAddOffsets](transactionId, groupId, callback) {
|
|
242
|
+
if (transactionId !== this.#transaction?.[kInstance]) {
|
|
243
|
+
callback(new UserError('The producer is in use by another transaction.'));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
this[kPerformDeduplicated]('addOffsetsToTransaction', deduplicateCallback => {
|
|
247
|
+
this[kPerformWithRetry]('addOffsetsToTransaction', retryCallback => {
|
|
248
|
+
this.#getCoordinatorConnection((error, connection) => {
|
|
249
|
+
if (error) {
|
|
250
|
+
retryCallback(error);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
this[kGetApi]('AddOffsetsToTxn', (error, api) => {
|
|
254
|
+
if (error) {
|
|
255
|
+
retryCallback(error);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
api(connection, this.#transaction.id, this.#producerInfo.producerId, this.#producerInfo.producerEpoch, groupId, error => {
|
|
259
|
+
if (error) {
|
|
260
|
+
retryCallback(error);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
retryCallback(null);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
}, deduplicateCallback);
|
|
268
|
+
}, callback);
|
|
269
|
+
}
|
|
270
|
+
[kTransactionCommitOffset](transactionId, message, callback) {
|
|
271
|
+
if (transactionId !== this.#transaction?.[kInstance]) {
|
|
272
|
+
callback(new UserError('The producer is in use by another transaction.'));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
this[kPerformDeduplicated]('commit', deduplicateCallback => {
|
|
276
|
+
this[kPerformWithRetry]('commit', retryCallback => {
|
|
277
|
+
this[kMetadata]({ topics: [message.topic] }, (error, metadata) => {
|
|
278
|
+
if (error) {
|
|
279
|
+
retryCallback(error);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const { groupId, generationId, memberId, coordinatorId } = message.metadata
|
|
283
|
+
.consumer;
|
|
284
|
+
this[kGetConnection](metadata.brokers.get(coordinatorId), (error, connection) => {
|
|
285
|
+
if (error) {
|
|
286
|
+
retryCallback(error);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
this[kGetApi]('TxnOffsetCommit', (error, api) => {
|
|
290
|
+
if (error) {
|
|
291
|
+
retryCallback(error);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const { topic, partition } = message;
|
|
295
|
+
api(connection, this.#transaction.id, groupId, this.#producerInfo.producerId, this.#producerInfo.producerEpoch, generationId, memberId, null, [
|
|
296
|
+
{
|
|
297
|
+
name: topic,
|
|
298
|
+
partitions: [
|
|
299
|
+
{
|
|
300
|
+
partitionIndex: message.partition,
|
|
301
|
+
committedOffset: message.offset + 1n,
|
|
302
|
+
committedLeaderEpoch: metadata.topics.get(topic).partitions[partition].leaderEpoch
|
|
303
|
+
}
|
|
304
|
+
]
|
|
305
|
+
}
|
|
306
|
+
], error => {
|
|
307
|
+
if (error) {
|
|
308
|
+
retryCallback(error);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
retryCallback(null);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
}, deduplicateCallback);
|
|
317
|
+
}, callback);
|
|
318
|
+
}
|
|
319
|
+
[kTransactionEnd](transactionId, commit, callback) {
|
|
320
|
+
if (transactionId !== this.#transaction?.[kInstance]) {
|
|
321
|
+
callback(new UserError('The producer is in use by another transaction.'));
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
this[kPerformDeduplicated]('endTransaction', deduplicateCallback => {
|
|
325
|
+
this[kPerformWithRetry]('endTransaction', retryCallback => {
|
|
326
|
+
this.#getCoordinatorConnection((error, connection) => {
|
|
327
|
+
if (error) {
|
|
328
|
+
retryCallback(error);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
this[kGetApi]('EndTxn', (error, api) => {
|
|
332
|
+
if (error) {
|
|
333
|
+
retryCallback(error);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
api(connection, this.#transaction.id, this.#producerInfo.producerId, this.#producerInfo.producerEpoch, commit, error => {
|
|
337
|
+
if (error) {
|
|
338
|
+
this.#handleFencingError(error);
|
|
339
|
+
retryCallback(error);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
this.#transaction = undefined;
|
|
343
|
+
retryCallback(null);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
}, deduplicateCallback);
|
|
348
|
+
}, callback);
|
|
349
|
+
}
|
|
350
|
+
[kTransactionCancel](transactionId, callback) {
|
|
351
|
+
if (transactionId !== this.#transaction?.[kInstance]) {
|
|
352
|
+
callback(new UserError('The producer is in use by another transaction.'));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
this.#transaction = undefined;
|
|
356
|
+
callback(null);
|
|
357
|
+
}
|
|
108
358
|
#initIdempotentProducer(options, callback) {
|
|
359
|
+
const transactionalId = this.#transaction?.id;
|
|
109
360
|
this[kPerformDeduplicated]('initProducerId', deduplicateCallback => {
|
|
110
361
|
this[kPerformWithRetry]('initProducerId', retryCallback => {
|
|
111
|
-
|
|
362
|
+
const connector = transactionalId
|
|
363
|
+
? this.#getCoordinatorConnection.bind(this)
|
|
364
|
+
: this[kGetBootstrapConnection].bind(this);
|
|
365
|
+
connector((error, connection) => {
|
|
112
366
|
if (error) {
|
|
113
|
-
retryCallback(error
|
|
367
|
+
retryCallback(error);
|
|
114
368
|
return;
|
|
115
369
|
}
|
|
116
370
|
this[kGetApi]('InitProducerId', (error, api) => {
|
|
117
371
|
if (error) {
|
|
118
|
-
retryCallback(error
|
|
372
|
+
retryCallback(error);
|
|
119
373
|
return;
|
|
120
374
|
}
|
|
121
|
-
api(connection,
|
|
375
|
+
api(connection, transactionalId, this[kOptions].timeout, options.producerId ?? this[kOptions].producerId ?? 0n, options.producerEpoch ?? this[kOptions].producerEpoch ?? 0, retryCallback);
|
|
122
376
|
});
|
|
123
377
|
});
|
|
124
378
|
}, (error, response) => {
|
|
125
379
|
if (error) {
|
|
126
|
-
|
|
380
|
+
this.#handleFencingError(error);
|
|
381
|
+
deduplicateCallback(error);
|
|
127
382
|
return;
|
|
128
383
|
}
|
|
129
|
-
this.#producerInfo = {
|
|
384
|
+
this.#producerInfo = {
|
|
385
|
+
producerId: response.producerId,
|
|
386
|
+
producerEpoch: response.producerEpoch,
|
|
387
|
+
transactionalId
|
|
388
|
+
};
|
|
389
|
+
this.#sequences.clear();
|
|
130
390
|
deduplicateCallback(null, this.#producerInfo);
|
|
131
391
|
}, 0);
|
|
132
392
|
}, callback);
|
|
@@ -136,39 +396,34 @@ export class Producer extends Base {
|
|
|
136
396
|
options.repeatOnStaleMetadata ??= this[kOptions].repeatOnStaleMetadata;
|
|
137
397
|
options.partitioner ??= this[kOptions].partitioner;
|
|
138
398
|
const { idempotent, partitioner } = options;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
399
|
+
let producerId = options.producerId;
|
|
400
|
+
let producerEpoch = options.producerEpoch;
|
|
401
|
+
// If the producer was transactional and is not anymore, reset
|
|
402
|
+
if (!this.#transaction && this.#producerInfo?.transactionalId) {
|
|
403
|
+
this.#producerInfo = undefined;
|
|
144
404
|
}
|
|
145
|
-
//
|
|
146
|
-
if (idempotent) {
|
|
405
|
+
// Initialize the idempotent producer if required
|
|
406
|
+
if (options.idempotent) {
|
|
147
407
|
if (!this.#producerInfo) {
|
|
148
408
|
const { messages, ...initOptions } = options;
|
|
149
409
|
this.initIdempotentProducer(initOptions, error => {
|
|
150
410
|
if (error) {
|
|
151
|
-
callback(error
|
|
411
|
+
callback(error);
|
|
152
412
|
return;
|
|
153
413
|
}
|
|
154
414
|
this.#send(options, callback);
|
|
155
415
|
});
|
|
156
416
|
return;
|
|
157
417
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
if (options.acks !== ProduceAcks.ALL) {
|
|
163
|
-
callback(new UserError('Idempotent producer requires acks to be ALL (-1).'), undefined);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
418
|
+
producerId = this.#producerInfo.producerId;
|
|
419
|
+
producerEpoch = this.#producerInfo.producerEpoch;
|
|
166
420
|
}
|
|
167
421
|
const produceOptions = {
|
|
168
422
|
compression: options.compression ?? this[kOptions].compression,
|
|
169
|
-
producerId
|
|
170
|
-
producerEpoch
|
|
171
|
-
sequences: idempotent ? this.#sequences : undefined
|
|
423
|
+
producerId,
|
|
424
|
+
producerEpoch,
|
|
425
|
+
sequences: idempotent ? this.#sequences : undefined,
|
|
426
|
+
transactionalId: this.#transaction?.id
|
|
172
427
|
};
|
|
173
428
|
// Build messages records out of messages
|
|
174
429
|
const topics = new Set();
|
|
@@ -214,13 +469,32 @@ export class Producer extends Base {
|
|
|
214
469
|
timestamp: message.timestamp
|
|
215
470
|
});
|
|
216
471
|
}
|
|
217
|
-
|
|
472
|
+
const topicsArray = Array.from(topics);
|
|
473
|
+
if (this.#transaction) {
|
|
474
|
+
// Get the metadata for topics, we need to normalize the partitions
|
|
475
|
+
this[kMetadata]({ topics: topicsArray, autocreateTopics: options.autocreateTopics }, (error, metadata) => {
|
|
476
|
+
if (error) {
|
|
477
|
+
callback(error);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
this.#transaction?.addPartitions(metadata, messages, error => {
|
|
481
|
+
if (error) {
|
|
482
|
+
callback(error);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
this.#performSend(topicsArray, messages, options, produceOptions, callback);
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
this.#performSend(topicsArray, messages, options, produceOptions, callback);
|
|
491
|
+
}
|
|
218
492
|
}
|
|
219
493
|
#performSend(topics, messages, sendOptions, produceOptions, callback) {
|
|
220
494
|
// Get the metadata with the topic/partitions informations
|
|
221
495
|
this[kMetadata]({ topics, autocreateTopics: sendOptions.autocreateTopics }, (error, metadata) => {
|
|
222
496
|
if (error) {
|
|
223
|
-
callback(error
|
|
497
|
+
callback(error);
|
|
224
498
|
return;
|
|
225
499
|
}
|
|
226
500
|
const messagesByDestination = new Map();
|
|
@@ -247,7 +521,7 @@ export class Producer extends Base {
|
|
|
247
521
|
this.#performSingleDestinationSend(topics, destinationMessages, this[kOptions].timeout, sendOptions.acks, sendOptions.autocreateTopics, sendOptions.repeatOnStaleMetadata, produceOptions, concurrentCallback);
|
|
248
522
|
}, (error, apiResults) => {
|
|
249
523
|
if (error) {
|
|
250
|
-
callback(error
|
|
524
|
+
callback(error);
|
|
251
525
|
return;
|
|
252
526
|
}
|
|
253
527
|
this.#metricsProducedMessages?.inc(messages.length);
|
|
@@ -286,7 +560,7 @@ export class Producer extends Base {
|
|
|
286
560
|
// Get the metadata with the topic/partitions informations
|
|
287
561
|
this[kMetadata]({ topics, autocreateTopics }, (error, metadata) => {
|
|
288
562
|
if (error) {
|
|
289
|
-
callback(error
|
|
563
|
+
callback(error);
|
|
290
564
|
return;
|
|
291
565
|
}
|
|
292
566
|
const { topic, partition } = messages[0];
|
|
@@ -294,12 +568,12 @@ export class Producer extends Base {
|
|
|
294
568
|
this[kPerformWithRetry]('produce', retryCallback => {
|
|
295
569
|
this[kGetConnection](metadata.brokers.get(leader), (error, connection) => {
|
|
296
570
|
if (error) {
|
|
297
|
-
retryCallback(error
|
|
571
|
+
retryCallback(error);
|
|
298
572
|
return;
|
|
299
573
|
}
|
|
300
574
|
this[kGetApi]('Produce', (error, api) => {
|
|
301
575
|
if (error) {
|
|
302
|
-
retryCallback(error
|
|
576
|
+
retryCallback(error);
|
|
303
577
|
return;
|
|
304
578
|
}
|
|
305
579
|
api(connection, acks, timeout, messages, produceOptions, retryCallback);
|
|
@@ -315,13 +589,37 @@ export class Producer extends Base {
|
|
|
315
589
|
this.#performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, false, produceOptions, callback);
|
|
316
590
|
return;
|
|
317
591
|
}
|
|
318
|
-
callback(error
|
|
592
|
+
callback(error);
|
|
319
593
|
return;
|
|
320
594
|
}
|
|
321
595
|
callback(error, results);
|
|
322
|
-
}, 0, [],
|
|
596
|
+
}, 0, [], error => {
|
|
323
597
|
return repeatOnStaleMetadata && !!error.findBy('hasStaleMetadata', true);
|
|
324
598
|
});
|
|
325
599
|
});
|
|
326
600
|
}
|
|
601
|
+
#getCoordinatorConnection(callback) {
|
|
602
|
+
// Get a connection to the coordinator
|
|
603
|
+
this[kMetadata]({}, (error, metadata) => {
|
|
604
|
+
if (error) {
|
|
605
|
+
callback(error);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
this[kPerformWithRetry]('getCoordinatorConnection', retryCallback => {
|
|
609
|
+
this[kGetConnection](metadata.brokers.get(this.#coordinatorId), (error, connection) => {
|
|
610
|
+
if (error) {
|
|
611
|
+
retryCallback(error);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
retryCallback(null, connection);
|
|
615
|
+
});
|
|
616
|
+
}, callback);
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
#handleFencingError(error) {
|
|
620
|
+
if (error.findBy?.('producerFenced', true)) {
|
|
621
|
+
this.#producerInfo = undefined;
|
|
622
|
+
this.#transaction = undefined;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
327
625
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type CallbackWithPromise } from '../../apis/callbacks.ts';
|
|
2
|
+
import { type Callback } from '../../apis/definitions.ts';
|
|
3
|
+
import { type Message, type MessageRecord } from '../../protocol/records.ts';
|
|
4
|
+
import { kInstance, kTransactionPrepare } from '../../symbols.ts';
|
|
5
|
+
import { type ClusterMetadata } from '../base/types.ts';
|
|
6
|
+
import { type Consumer } from '../consumer/consumer.ts';
|
|
7
|
+
import { type Producer } from './producer.ts';
|
|
8
|
+
import { type ProduceOptions, type ProduceResult, type SendOptions } from './types.ts';
|
|
9
|
+
export declare class Transaction<Key = Buffer, Value = Buffer, HeaderKey = Buffer, HeaderValue = Buffer> {
|
|
10
|
+
#private;
|
|
11
|
+
id: string;
|
|
12
|
+
[kInstance]: number;
|
|
13
|
+
constructor(producer: Producer<Key, Value, HeaderKey, HeaderValue>);
|
|
14
|
+
get completed(): boolean;
|
|
15
|
+
[kTransactionPrepare](transactionalId: string | undefined, options: ProduceOptions<Key, Value, HeaderKey, HeaderValue>, callback: Callback<Transaction<Key, Value, HeaderKey, HeaderValue>>): void;
|
|
16
|
+
send(options: SendOptions<Key, Value, HeaderKey, HeaderValue>, callback: CallbackWithPromise<ProduceResult>): void;
|
|
17
|
+
send(options: SendOptions<Key, Value, HeaderKey, HeaderValue>): Promise<ProduceResult>;
|
|
18
|
+
addPartitions(metadata: ClusterMetadata, messages: MessageRecord[], callback: CallbackWithPromise<void>): void;
|
|
19
|
+
addPartitions(metadata: ClusterMetadata, messages: MessageRecord[]): Promise<void>;
|
|
20
|
+
addConsumer<MessageKey = Buffer, MessageValue = Buffer, MessageHeaderKey = Buffer, MessageHeaderValue = Buffer>(consumer: Consumer<MessageKey, MessageValue, MessageHeaderKey, MessageHeaderValue>, callback: CallbackWithPromise<void>): void;
|
|
21
|
+
addConsumer<MessageKey = Buffer, MessageValue = Buffer, MessageHeaderKey = Buffer, MessageHeaderValue = Buffer>(consumer: Consumer<MessageKey, MessageValue, MessageHeaderKey, MessageHeaderValue>): Promise<void>;
|
|
22
|
+
addOffset<MessageKey = Buffer, MessageValue = Buffer, MessageHeaderKey = Buffer, MessageHeaderValue = Buffer>(message: Message<MessageKey, MessageValue, MessageHeaderKey, MessageHeaderValue>, callback: CallbackWithPromise<void>): void;
|
|
23
|
+
addOffset<MessageKey = Buffer, MessageValue = Buffer, MessageHeaderKey = Buffer, MessageHeaderValue = Buffer>(message: Message<MessageKey, MessageValue, MessageHeaderKey, MessageHeaderValue>): Promise<void>;
|
|
24
|
+
commit(callback: CallbackWithPromise<void>): void;
|
|
25
|
+
commit(): Promise<void>;
|
|
26
|
+
abort(callback: CallbackWithPromise<void>): void;
|
|
27
|
+
abort(): Promise<void>;
|
|
28
|
+
cancel(callback: CallbackWithPromise<void>): void;
|
|
29
|
+
cancel(): Promise<void>;
|
|
30
|
+
}
|