@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.
Files changed (122) hide show
  1. package/dist/apis/admin/alter-client-quotas-v1.d.ts +4 -4
  2. package/dist/apis/admin/alter-configs-v2.d.ts +3 -2
  3. package/dist/apis/admin/alter-partition-reassignments-v0.js +1 -1
  4. package/dist/apis/admin/create-acls-v3.d.ts +3 -11
  5. package/dist/apis/admin/create-partitions-v3.d.ts +2 -2
  6. package/dist/apis/admin/delete-acls-v3.d.ts +4 -19
  7. package/dist/apis/admin/delete-acls-v3.js +6 -6
  8. package/dist/apis/admin/describe-acls-v3.d.ts +5 -13
  9. package/dist/apis/admin/describe-acls-v3.js +9 -9
  10. package/dist/apis/admin/describe-client-quotas-v0.d.ts +5 -5
  11. package/dist/apis/admin/describe-configs-v4.d.ts +7 -6
  12. package/dist/apis/admin/describe-configs-v4.js +1 -1
  13. package/dist/apis/admin/incremental-alter-configs-v1.d.ts +12 -5
  14. package/dist/apis/admin/incremental-alter-configs-v1.js +3 -1
  15. package/dist/apis/admin/list-groups-v4.d.ts +2 -2
  16. package/dist/apis/admin/list-groups-v5.d.ts +2 -2
  17. package/dist/apis/admin/offset-delete-v0.js +3 -3
  18. package/dist/apis/callbacks.d.ts +3 -1
  19. package/dist/apis/consumer/offset-commit-v9.d.ts +2 -2
  20. package/dist/apis/consumer/offset-fetch-v8.d.ts +2 -2
  21. package/dist/apis/consumer/offset-fetch-v9.d.ts +2 -2
  22. package/dist/apis/definitions.d.ts +2 -2
  23. package/dist/apis/enumerations.d.ts +134 -42
  24. package/dist/apis/enumerations.js +42 -6
  25. package/dist/apis/index.d.ts +1 -0
  26. package/dist/apis/index.js +1 -0
  27. package/dist/apis/producer/add-offsets-to-txn-v3.d.ts +10 -0
  28. package/dist/apis/producer/add-offsets-to-txn-v3.js +34 -0
  29. package/dist/apis/producer/add-partitions-to-txn-v3.d.ts +34 -0
  30. package/dist/apis/producer/add-partitions-to-txn-v3.js +67 -0
  31. package/dist/apis/producer/add-partitions-to-txn-v4.d.ts +34 -0
  32. package/dist/apis/producer/add-partitions-to-txn-v4.js +79 -0
  33. package/dist/apis/producer/end-txn-v3.d.ts +10 -0
  34. package/dist/apis/producer/end-txn-v3.js +34 -0
  35. package/dist/apis/producer/index.d.ts +5 -0
  36. package/dist/apis/producer/index.js +5 -0
  37. package/dist/apis/producer/txn-offset-commit-v3.d.ts +29 -0
  38. package/dist/apis/producer/txn-offset-commit-v3.js +74 -0
  39. package/dist/apis/types.d.ts +16 -0
  40. package/dist/apis/types.js +1 -0
  41. package/dist/clients/admin/admin.d.ts +31 -1
  42. package/dist/clients/admin/admin.js +732 -50
  43. package/dist/clients/admin/options.d.ts +577 -0
  44. package/dist/clients/admin/options.js +359 -2
  45. package/dist/clients/admin/types.d.ts +105 -3
  46. package/dist/clients/base/base.js +29 -26
  47. package/dist/clients/base/options.d.ts +2 -5
  48. package/dist/clients/base/options.js +0 -1
  49. package/dist/clients/base/types.d.ts +8 -2
  50. package/dist/clients/consumer/consumer.d.ts +58 -2
  51. package/dist/clients/consumer/consumer.js +116 -47
  52. package/dist/clients/consumer/messages-stream.d.ts +1 -0
  53. package/dist/clients/consumer/messages-stream.js +48 -17
  54. package/dist/clients/consumer/options.d.ts +6 -6
  55. package/dist/clients/consumer/options.js +3 -3
  56. package/dist/clients/consumer/types.d.ts +26 -3
  57. package/dist/clients/index.d.ts +1 -1
  58. package/dist/clients/index.js +1 -1
  59. package/dist/clients/producer/options.d.ts +12 -3
  60. package/dist/clients/producer/options.js +1 -0
  61. package/dist/clients/producer/producer.d.ts +19 -2
  62. package/dist/clients/producer/producer.js +335 -37
  63. package/dist/clients/producer/transaction.d.ts +30 -0
  64. package/dist/clients/producer/transaction.js +172 -0
  65. package/dist/clients/producer/types.d.ts +2 -0
  66. package/dist/diagnostic.d.ts +5 -0
  67. package/dist/diagnostic.js +5 -0
  68. package/dist/errors.js +1 -0
  69. package/dist/network/connection-pool.js +4 -4
  70. package/dist/network/connection.js +3 -2
  71. package/dist/protocol/records.d.ts +12 -0
  72. package/dist/protocol/records.js +6 -4
  73. package/dist/protocol/sasl/oauth-bearer.js +4 -4
  74. package/dist/protocol/sasl/plain.js +2 -2
  75. package/dist/protocol/sasl/scram-sha.js +8 -8
  76. package/dist/protocol/sasl/utils.js +5 -5
  77. package/dist/symbols.d.ts +8 -0
  78. package/dist/symbols.js +8 -0
  79. package/dist/typescript-4/dist/apis/admin/alter-client-quotas-v1.d.ts +4 -4
  80. package/dist/typescript-4/dist/apis/admin/alter-configs-v2.d.ts +3 -2
  81. package/dist/typescript-4/dist/apis/admin/create-acls-v3.d.ts +3 -11
  82. package/dist/typescript-4/dist/apis/admin/create-partitions-v3.d.ts +2 -2
  83. package/dist/typescript-4/dist/apis/admin/delete-acls-v3.d.ts +4 -19
  84. package/dist/typescript-4/dist/apis/admin/describe-acls-v3.d.ts +5 -13
  85. package/dist/typescript-4/dist/apis/admin/describe-client-quotas-v0.d.ts +5 -5
  86. package/dist/typescript-4/dist/apis/admin/describe-configs-v4.d.ts +7 -6
  87. package/dist/typescript-4/dist/apis/admin/incremental-alter-configs-v1.d.ts +13 -6
  88. package/dist/typescript-4/dist/apis/admin/list-groups-v4.d.ts +2 -2
  89. package/dist/typescript-4/dist/apis/admin/list-groups-v5.d.ts +2 -2
  90. package/dist/typescript-4/dist/apis/callbacks.d.ts +3 -1
  91. package/dist/typescript-4/dist/apis/consumer/offset-commit-v9.d.ts +2 -2
  92. package/dist/typescript-4/dist/apis/consumer/offset-fetch-v8.d.ts +2 -2
  93. package/dist/typescript-4/dist/apis/consumer/offset-fetch-v9.d.ts +2 -2
  94. package/dist/typescript-4/dist/apis/definitions.d.ts +2 -2
  95. package/dist/typescript-4/dist/apis/enumerations.d.ts +134 -42
  96. package/dist/typescript-4/dist/apis/index.d.ts +1 -0
  97. package/dist/typescript-4/dist/apis/producer/add-offsets-to-txn-v3.d.ts +10 -0
  98. package/dist/typescript-4/dist/apis/producer/add-partitions-to-txn-v3.d.ts +34 -0
  99. package/dist/typescript-4/dist/apis/producer/add-partitions-to-txn-v4.d.ts +34 -0
  100. package/dist/typescript-4/dist/apis/producer/end-txn-v3.d.ts +10 -0
  101. package/dist/typescript-4/dist/apis/producer/index.d.ts +5 -0
  102. package/dist/typescript-4/dist/apis/producer/txn-offset-commit-v3.d.ts +29 -0
  103. package/dist/typescript-4/dist/apis/types.d.ts +14 -0
  104. package/dist/typescript-4/dist/clients/admin/admin.d.ts +31 -1
  105. package/dist/typescript-4/dist/clients/admin/options.d.ts +577 -0
  106. package/dist/typescript-4/dist/clients/admin/types.d.ts +105 -3
  107. package/dist/typescript-4/dist/clients/base/options.d.ts +2 -5
  108. package/dist/typescript-4/dist/clients/base/types.d.ts +8 -2
  109. package/dist/typescript-4/dist/clients/consumer/consumer.d.ts +58 -2
  110. package/dist/typescript-4/dist/clients/consumer/messages-stream.d.ts +1 -0
  111. package/dist/typescript-4/dist/clients/consumer/options.d.ts +6 -6
  112. package/dist/typescript-4/dist/clients/consumer/types.d.ts +27 -4
  113. package/dist/typescript-4/dist/clients/index.d.ts +1 -1
  114. package/dist/typescript-4/dist/clients/producer/options.d.ts +12 -3
  115. package/dist/typescript-4/dist/clients/producer/producer.d.ts +19 -2
  116. package/dist/typescript-4/dist/clients/producer/transaction.d.ts +30 -0
  117. package/dist/typescript-4/dist/clients/producer/types.d.ts +2 -0
  118. package/dist/typescript-4/dist/diagnostic.d.ts +5 -0
  119. package/dist/typescript-4/dist/protocol/records.d.ts +12 -0
  120. package/dist/typescript-4/dist/symbols.d.ts +9 -1
  121. package/dist/version.js +1 -1
  122. 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, undefined);
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, undefined);
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
- this[kGetBootstrapConnection]((error, connection) => {
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, undefined);
367
+ retryCallback(error);
114
368
  return;
115
369
  }
116
370
  this[kGetApi]('InitProducerId', (error, api) => {
117
371
  if (error) {
118
- retryCallback(error, undefined);
372
+ retryCallback(error);
119
373
  return;
120
374
  }
121
- api(connection, null, this[kOptions].timeout, options.producerId ?? this[kOptions].producerId ?? 0n, options.producerEpoch ?? this[kOptions].producerEpoch ?? 0, retryCallback);
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
- deduplicateCallback(error, undefined);
380
+ this.#handleFencingError(error);
381
+ deduplicateCallback(error);
127
382
  return;
128
383
  }
129
- this.#producerInfo = { producerId: response.producerId, producerEpoch: response.producerEpoch };
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
- if (idempotent) {
140
- options.acks ??= ProduceAcks.ALL;
141
- }
142
- else {
143
- options.acks ??= ProduceAcks.LEADER;
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
- // We still need to initialize the producerId
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, undefined);
411
+ callback(error);
152
412
  return;
153
413
  }
154
414
  this.#send(options, callback);
155
415
  });
156
416
  return;
157
417
  }
158
- if (typeof options.producerId !== 'undefined' || typeof options.producerEpoch !== 'undefined') {
159
- callback(new UserError('Cannot specify producerId or producerEpoch when using idempotent producer.'), undefined);
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: idempotent ? this.#producerInfo.producerId : options.producerId,
170
- producerEpoch: idempotent ? this.#producerInfo.producerEpoch : options.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
- this.#performSend(Array.from(topics), messages, options, produceOptions, callback);
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, undefined);
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, undefined);
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, undefined);
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, undefined);
571
+ retryCallback(error);
298
572
  return;
299
573
  }
300
574
  this[kGetApi]('Produce', (error, api) => {
301
575
  if (error) {
302
- retryCallback(error, undefined);
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, undefined);
592
+ callback(error);
319
593
  return;
320
594
  }
321
595
  callback(error, results);
322
- }, 0, [], (error) => {
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
+ }