@drarzter/kafka-client 0.9.4 → 0.11.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 (180) hide show
  1. package/README.md +693 -8
  2. package/dist/chunk-OR7TPAAE.mjs +4760 -0
  3. package/dist/chunk-OR7TPAAE.mjs.map +1 -0
  4. package/dist/chunk-PQVBRDNV.mjs +149 -0
  5. package/dist/chunk-PQVBRDNV.mjs.map +1 -0
  6. package/dist/cli/dlq.d.ts +119 -0
  7. package/dist/cli/dlq.d.ts.map +1 -0
  8. package/dist/cli/index.d.ts +3 -0
  9. package/dist/cli/index.d.ts.map +1 -0
  10. package/dist/{chunk-SM4FZKAZ.mjs → cli/index.js} +1073 -309
  11. package/dist/cli/index.js.map +1 -0
  12. package/dist/cli/index.mjs +356 -0
  13. package/dist/cli/index.mjs.map +1 -0
  14. package/dist/client/config/from-env.d.ts +188 -0
  15. package/dist/client/config/from-env.d.ts.map +1 -0
  16. package/dist/client/config/index.d.ts +2 -0
  17. package/dist/client/config/index.d.ts.map +1 -0
  18. package/dist/client/errors.d.ts +67 -0
  19. package/dist/client/errors.d.ts.map +1 -0
  20. package/dist/client/kafka.client/admin/ops.d.ts +114 -0
  21. package/dist/client/kafka.client/admin/ops.d.ts.map +1 -0
  22. package/dist/client/kafka.client/consumer/features/delayed.d.ts +24 -0
  23. package/dist/client/kafka.client/consumer/features/delayed.d.ts.map +1 -0
  24. package/dist/client/kafka.client/consumer/features/dlq-replay.d.ts +52 -0
  25. package/dist/client/kafka.client/consumer/features/dlq-replay.d.ts.map +1 -0
  26. package/dist/client/kafka.client/consumer/features/routed.d.ts +4 -0
  27. package/dist/client/kafka.client/consumer/features/routed.d.ts.map +1 -0
  28. package/dist/client/kafka.client/consumer/features/snapshot.d.ts +10 -0
  29. package/dist/client/kafka.client/consumer/features/snapshot.d.ts.map +1 -0
  30. package/dist/client/kafka.client/consumer/features/window.d.ts +5 -0
  31. package/dist/client/kafka.client/consumer/features/window.d.ts.map +1 -0
  32. package/dist/client/kafka.client/consumer/handler.d.ts +163 -0
  33. package/dist/client/kafka.client/consumer/handler.d.ts.map +1 -0
  34. package/dist/client/kafka.client/consumer/ops.d.ts +64 -0
  35. package/dist/client/kafka.client/consumer/ops.d.ts.map +1 -0
  36. package/dist/client/kafka.client/consumer/pipeline.d.ts +168 -0
  37. package/dist/client/kafka.client/consumer/pipeline.d.ts.map +1 -0
  38. package/dist/client/kafka.client/consumer/queue.d.ts +37 -0
  39. package/dist/client/kafka.client/consumer/queue.d.ts.map +1 -0
  40. package/dist/client/kafka.client/consumer/retry-topic.d.ts +68 -0
  41. package/dist/client/kafka.client/consumer/retry-topic.d.ts.map +1 -0
  42. package/dist/client/kafka.client/consumer/setup.d.ts +66 -0
  43. package/dist/client/kafka.client/consumer/setup.d.ts.map +1 -0
  44. package/dist/client/kafka.client/consumer/start.d.ts +7 -0
  45. package/dist/client/kafka.client/consumer/start.d.ts.map +1 -0
  46. package/dist/client/kafka.client/consumer/stop.d.ts +19 -0
  47. package/dist/client/kafka.client/consumer/stop.d.ts.map +1 -0
  48. package/dist/client/kafka.client/consumer/subscribe-retry.d.ts +4 -0
  49. package/dist/client/kafka.client/consumer/subscribe-retry.d.ts.map +1 -0
  50. package/dist/client/kafka.client/context.d.ts +75 -0
  51. package/dist/client/kafka.client/context.d.ts.map +1 -0
  52. package/dist/client/kafka.client/index.d.ts +155 -0
  53. package/dist/client/kafka.client/index.d.ts.map +1 -0
  54. package/dist/client/kafka.client/infra/circuit-breaker.manager.d.ts +61 -0
  55. package/dist/client/kafka.client/infra/circuit-breaker.manager.d.ts.map +1 -0
  56. package/dist/client/kafka.client/infra/dedup.store.d.ts +28 -0
  57. package/dist/client/kafka.client/infra/dedup.store.d.ts.map +1 -0
  58. package/dist/client/kafka.client/infra/inflight.tracker.d.ts +22 -0
  59. package/dist/client/kafka.client/infra/inflight.tracker.d.ts.map +1 -0
  60. package/dist/client/kafka.client/infra/metrics.manager.d.ts +67 -0
  61. package/dist/client/kafka.client/infra/metrics.manager.d.ts.map +1 -0
  62. package/dist/client/kafka.client/producer/lifecycle.d.ts +41 -0
  63. package/dist/client/kafka.client/producer/lifecycle.d.ts.map +1 -0
  64. package/dist/client/kafka.client/producer/ops.d.ts +79 -0
  65. package/dist/client/kafka.client/producer/ops.d.ts.map +1 -0
  66. package/dist/client/kafka.client/producer/send.d.ts +21 -0
  67. package/dist/client/kafka.client/producer/send.d.ts.map +1 -0
  68. package/dist/client/kafka.client/validate-options.d.ts +11 -0
  69. package/dist/client/kafka.client/validate-options.d.ts.map +1 -0
  70. package/dist/client/message/envelope.d.ts +105 -0
  71. package/dist/client/message/envelope.d.ts.map +1 -0
  72. package/dist/client/message/schema-registry.d.ts +124 -0
  73. package/dist/client/message/schema-registry.d.ts.map +1 -0
  74. package/dist/client/message/serde.d.ts +68 -0
  75. package/dist/client/message/serde.d.ts.map +1 -0
  76. package/dist/client/message/topic.d.ts +159 -0
  77. package/dist/client/message/topic.d.ts.map +1 -0
  78. package/dist/client/message/versioned-schema.d.ts +53 -0
  79. package/dist/client/message/versioned-schema.d.ts.map +1 -0
  80. package/dist/client/outbox/index.d.ts +4 -0
  81. package/dist/client/outbox/index.d.ts.map +1 -0
  82. package/dist/client/outbox/outbox.relay.d.ts +90 -0
  83. package/dist/client/outbox/outbox.relay.d.ts.map +1 -0
  84. package/dist/client/outbox/outbox.store.d.ts +42 -0
  85. package/dist/client/outbox/outbox.store.d.ts.map +1 -0
  86. package/dist/client/outbox/outbox.types.d.ts +144 -0
  87. package/dist/client/outbox/outbox.types.d.ts.map +1 -0
  88. package/dist/client/security/acl.d.ts +108 -0
  89. package/dist/client/security/acl.d.ts.map +1 -0
  90. package/dist/client/security/index.d.ts +5 -0
  91. package/dist/client/security/index.d.ts.map +1 -0
  92. package/dist/client/security/providers.d.ts +88 -0
  93. package/dist/client/security/providers.d.ts.map +1 -0
  94. package/dist/client/security/resolve-security.d.ts +19 -0
  95. package/dist/client/security/resolve-security.d.ts.map +1 -0
  96. package/dist/client/security/security.types.d.ts +76 -0
  97. package/dist/client/security/security.types.d.ts.map +1 -0
  98. package/dist/client/transport/confluent.transport.d.ts +32 -0
  99. package/dist/client/transport/confluent.transport.d.ts.map +1 -0
  100. package/dist/client/transport/transport.interface.d.ts +221 -0
  101. package/dist/client/transport/transport.interface.d.ts.map +1 -0
  102. package/dist/client/types/admin.interface.d.ts +174 -0
  103. package/dist/client/types/admin.interface.d.ts.map +1 -0
  104. package/dist/client/types/admin.types.d.ts +140 -0
  105. package/dist/client/types/admin.types.d.ts.map +1 -0
  106. package/dist/client/types/client.d.ts +21 -0
  107. package/dist/client/types/client.d.ts.map +1 -0
  108. package/dist/client/types/common.d.ts +84 -0
  109. package/dist/client/types/common.d.ts.map +1 -0
  110. package/dist/client/types/config.types.d.ts +167 -0
  111. package/dist/client/types/config.types.d.ts.map +1 -0
  112. package/dist/client/types/consumer.interface.d.ts +115 -0
  113. package/dist/client/types/consumer.interface.d.ts.map +1 -0
  114. package/dist/{consumer.types-fFCag3VJ.d.mts → client/types/consumer.types.d.ts} +62 -383
  115. package/dist/client/types/consumer.types.d.ts.map +1 -0
  116. package/dist/client/types/dedup.types.d.ts +50 -0
  117. package/dist/client/types/dedup.types.d.ts.map +1 -0
  118. package/dist/client/types/lifecycle.interface.d.ts +72 -0
  119. package/dist/client/types/lifecycle.interface.d.ts.map +1 -0
  120. package/dist/client/types/producer.interface.d.ts +52 -0
  121. package/dist/client/types/producer.interface.d.ts.map +1 -0
  122. package/dist/client/types/producer.types.d.ts +90 -0
  123. package/dist/client/types/producer.types.d.ts.map +1 -0
  124. package/dist/client/types.d.ts +8 -0
  125. package/dist/client/types.d.ts.map +1 -0
  126. package/dist/core.d.ts +13 -314
  127. package/dist/core.d.ts.map +1 -0
  128. package/dist/core.js +1466 -123
  129. package/dist/core.js.map +1 -1
  130. package/dist/core.mjs +45 -3
  131. package/dist/index.d.ts +7 -128
  132. package/dist/index.d.ts.map +1 -0
  133. package/dist/index.js +1483 -123
  134. package/dist/index.js.map +1 -1
  135. package/dist/index.mjs +62 -3
  136. package/dist/index.mjs.map +1 -1
  137. package/dist/nest/kafka.constants.d.ts +5 -0
  138. package/dist/nest/kafka.constants.d.ts.map +1 -0
  139. package/dist/nest/kafka.decorator.d.ts +49 -0
  140. package/dist/nest/kafka.decorator.d.ts.map +1 -0
  141. package/dist/nest/kafka.explorer.d.ts +17 -0
  142. package/dist/nest/kafka.explorer.d.ts.map +1 -0
  143. package/dist/nest/kafka.health.d.ts +7 -0
  144. package/dist/nest/kafka.health.d.ts.map +1 -0
  145. package/dist/nest/kafka.module.d.ts +61 -0
  146. package/dist/nest/kafka.module.d.ts.map +1 -0
  147. package/dist/otel.d.ts +83 -5
  148. package/dist/otel.d.ts.map +1 -0
  149. package/dist/otel.js +100 -6
  150. package/dist/otel.js.map +1 -1
  151. package/dist/otel.mjs +98 -5
  152. package/dist/otel.mjs.map +1 -1
  153. package/dist/serde.d.ts +157 -0
  154. package/dist/serde.d.ts.map +1 -0
  155. package/dist/serde.js +308 -0
  156. package/dist/serde.js.map +1 -0
  157. package/dist/serde.mjs +158 -0
  158. package/dist/serde.mjs.map +1 -0
  159. package/dist/testing/client.mock.d.ts +47 -0
  160. package/dist/testing/client.mock.d.ts.map +1 -0
  161. package/dist/testing/index.d.ts +4 -0
  162. package/dist/testing/index.d.ts.map +1 -0
  163. package/dist/testing/test.container.d.ts +63 -0
  164. package/dist/testing/test.container.d.ts.map +1 -0
  165. package/dist/{testing.d.mts → testing/transport.fake.d.ts} +7 -111
  166. package/dist/testing/transport.fake.d.ts.map +1 -0
  167. package/dist/testing.d.ts +2 -318
  168. package/dist/testing.d.ts.map +1 -0
  169. package/dist/testing.js +26 -0
  170. package/dist/testing.js.map +1 -1
  171. package/dist/testing.mjs +26 -0
  172. package/dist/testing.mjs.map +1 -1
  173. package/package.json +40 -8
  174. package/dist/chunk-SM4FZKAZ.mjs.map +0 -1
  175. package/dist/client-1irhGEu0.d.mts +0 -751
  176. package/dist/client-BpFjkHhr.d.ts +0 -751
  177. package/dist/consumer.types-fFCag3VJ.d.ts +0 -958
  178. package/dist/core.d.mts +0 -314
  179. package/dist/index.d.mts +0 -128
  180. package/dist/otel.d.mts +0 -27
package/dist/core.js CHANGED
@@ -20,32 +20,53 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/core.ts
21
21
  var core_exports = {};
22
22
  __export(core_exports, {
23
+ ConfluentTransport: () => ConfluentTransport,
23
24
  HEADER_CORRELATION_ID: () => HEADER_CORRELATION_ID,
25
+ HEADER_DELAYED_TARGET: () => HEADER_DELAYED_TARGET,
26
+ HEADER_DELAYED_UNTIL: () => HEADER_DELAYED_UNTIL,
24
27
  HEADER_EVENT_ID: () => HEADER_EVENT_ID,
25
28
  HEADER_LAMPORT_CLOCK: () => HEADER_LAMPORT_CLOCK,
26
29
  HEADER_SCHEMA_VERSION: () => HEADER_SCHEMA_VERSION,
27
30
  HEADER_TIMESTAMP: () => HEADER_TIMESTAMP,
28
31
  HEADER_TRACEPARENT: () => HEADER_TRACEPARENT,
32
+ InMemoryDedupStore: () => InMemoryDedupStore,
33
+ InMemoryOutboxStore: () => InMemoryOutboxStore,
34
+ JsonSerde: () => JsonSerde,
29
35
  KafkaClient: () => KafkaClient,
30
36
  KafkaProcessingError: () => KafkaProcessingError,
31
37
  KafkaRetryExhaustedError: () => KafkaRetryExhaustedError,
32
38
  KafkaValidationError: () => KafkaValidationError,
39
+ SchemaRegistryClient: () => SchemaRegistryClient,
40
+ awsMskIamProvider: () => awsMskIamProvider,
33
41
  buildEnvelopeHeaders: () => buildEnvelopeHeaders,
42
+ consumerOptionsFromEnv: () => consumerOptionsFromEnv,
34
43
  decodeHeaders: () => decodeHeaders,
44
+ describeRequiredAcls: () => describeRequiredAcls,
35
45
  extractEnvelope: () => extractEnvelope,
46
+ gcpAccessTokenProvider: () => gcpAccessTokenProvider,
36
47
  getEnvelopeContext: () => getEnvelopeContext,
48
+ kafkaClientConfigFromEnv: () => kafkaClientConfigFromEnv,
49
+ mergeConsumerOptions: () => mergeConsumerOptions,
50
+ registrySchema: () => registrySchema,
51
+ resolveSecurityOptions: () => resolveSecurityOptions,
37
52
  runWithEnvelopeContext: () => runWithEnvelopeContext,
38
- topic: () => topic
53
+ startOutboxRelay: () => startOutboxRelay,
54
+ toError: () => toError,
55
+ toKafkaAclCommands: () => toKafkaAclCommands,
56
+ toMskIamPolicy: () => toMskIamPolicy,
57
+ topic: () => topic,
58
+ versionedSchema: () => versionedSchema
39
59
  });
40
60
  module.exports = __toCommonJS(core_exports);
41
61
 
42
- // src/client/kafka.client/confluent-transport.ts
62
+ // src/client/transport/confluent.transport.ts
43
63
  var import_kafka_javascript = require("@confluentinc/kafka-javascript");
44
64
  var { Kafka: KafkaClass, logLevel: KafkaLogLevel, PartitionAssigners } = import_kafka_javascript.KafkaJS;
45
65
  var ConfluentTransaction = class {
46
66
  constructor(tx) {
47
67
  this.tx = tx;
48
68
  }
69
+ tx;
49
70
  async send(record) {
50
71
  await this.tx.send(record);
51
72
  }
@@ -67,10 +88,17 @@ var ConfluentProducer = class {
67
88
  constructor(producer) {
68
89
  this.producer = producer;
69
90
  }
91
+ producer;
92
+ connectPromise;
70
93
  async connect() {
71
- await this.producer.connect();
94
+ this.connectPromise ??= this.producer.connect().catch((err) => {
95
+ this.connectPromise = void 0;
96
+ throw err;
97
+ });
98
+ return this.connectPromise;
72
99
  }
73
100
  async disconnect() {
101
+ this.connectPromise = void 0;
74
102
  await this.producer.disconnect();
75
103
  }
76
104
  async send(record) {
@@ -85,6 +113,7 @@ var ConfluentConsumer = class {
85
113
  constructor(consumer) {
86
114
  this.consumer = consumer;
87
115
  }
116
+ consumer;
88
117
  /** Returns the underlying KafkaJS.Consumer — used by ConfluentTransaction.sendOffsets. */
89
118
  getNative() {
90
119
  return this.consumer;
@@ -124,6 +153,7 @@ var ConfluentAdmin = class {
124
153
  constructor(admin) {
125
154
  this.admin = admin;
126
155
  }
156
+ admin;
127
157
  async connect() {
128
158
  await this.admin.connect();
129
159
  }
@@ -137,7 +167,7 @@ var ConfluentAdmin = class {
137
167
  return this.admin.fetchTopicOffsets(topic2);
138
168
  }
139
169
  async fetchTopicOffsetsByTimestamp(topic2, timestamp) {
140
- return this.admin.fetchTopicOffsetsByTime(topic2, timestamp);
170
+ return this.admin.fetchTopicOffsetsByTimestamp(topic2, timestamp);
141
171
  }
142
172
  async fetchOffsets(options) {
143
173
  return this.admin.fetchOffsets(options);
@@ -163,10 +193,29 @@ var ConfluentAdmin = class {
163
193
  };
164
194
  var ConfluentTransport = class {
165
195
  kafka;
166
- constructor(clientId, brokers) {
167
- this.kafka = new KafkaClass({
168
- kafkaJS: { clientId, brokers, logLevel: KafkaLogLevel.ERROR }
169
- });
196
+ constructor(clientId, brokers, security) {
197
+ const kafkaJS = { clientId, brokers, logLevel: KafkaLogLevel.ERROR };
198
+ if (security?.ssl !== void 0) kafkaJS.ssl = security.ssl;
199
+ if (security?.sasl) {
200
+ if (security.sasl.mechanism === "oauthbearer") {
201
+ const provider = security.sasl.oauthBearerProvider;
202
+ kafkaJS.sasl = {
203
+ mechanism: "oauthbearer",
204
+ oauthBearerProvider: async () => {
205
+ const token = await provider();
206
+ return {
207
+ value: token.value,
208
+ principal: token.principal ?? "kafka-client",
209
+ lifetime: token.lifetimeMs ?? Date.now() + 15 * 6e4,
210
+ ...token.extensions && { extensions: token.extensions }
211
+ };
212
+ }
213
+ };
214
+ } else {
215
+ kafkaJS.sasl = security.sasl;
216
+ }
217
+ }
218
+ this.kafka = new KafkaClass({ kafkaJS });
170
219
  }
171
220
  producer(options) {
172
221
  const native = this.kafka.producer({
@@ -193,6 +242,9 @@ var ConfluentTransport = class {
193
242
  partitionAssigners: [assigner]
194
243
  }
195
244
  };
245
+ if (options.groupInstanceId) {
246
+ config["group.instance.id"] = options.groupInstanceId;
247
+ }
196
248
  if (options.onRebalance) {
197
249
  const cb = options.onRebalance;
198
250
  config.rebalance_cb = (err, assignment) => {
@@ -210,6 +262,37 @@ var ConfluentTransport = class {
210
262
  }
211
263
  };
212
264
 
265
+ // src/client/message/serde.ts
266
+ var JsonSerde = class {
267
+ /** JSON-stringify the validated payload. Returns a UTF-8 string. */
268
+ serialize(value) {
269
+ return JSON.stringify(value);
270
+ }
271
+ /** JSON-parse UTF-8 wire bytes into an object. */
272
+ deserialize(data) {
273
+ return JSON.parse(data.toString("utf8"));
274
+ }
275
+ };
276
+
277
+ // src/client/kafka.client/infra/dedup.store.ts
278
+ var InMemoryDedupStore = class {
279
+ constructor(states) {
280
+ this.states = states;
281
+ }
282
+ states;
283
+ getLastClock(groupId, topicPartition) {
284
+ return this.states.get(groupId)?.get(topicPartition);
285
+ }
286
+ setLastClock(groupId, topicPartition, clock) {
287
+ let group = this.states.get(groupId);
288
+ if (!group) {
289
+ group = /* @__PURE__ */ new Map();
290
+ this.states.set(groupId, group);
291
+ }
292
+ group.set(topicPartition, clock);
293
+ }
294
+ };
295
+
213
296
  // src/client/message/envelope.ts
214
297
  var import_node_async_hooks = require("async_hooks");
215
298
  var import_node_crypto = require("crypto");
@@ -219,6 +302,8 @@ var HEADER_TIMESTAMP = "x-timestamp";
219
302
  var HEADER_SCHEMA_VERSION = "x-schema-version";
220
303
  var HEADER_TRACEPARENT = "traceparent";
221
304
  var HEADER_LAMPORT_CLOCK = "x-lamport-clock";
305
+ var HEADER_DELAYED_UNTIL = "x-delayed-until";
306
+ var HEADER_DELAYED_TARGET = "x-delayed-target";
222
307
  var envelopeStorage = new import_node_async_hooks.AsyncLocalStorage();
223
308
  function getEnvelopeContext() {
224
309
  return envelopeStorage.getStore();
@@ -273,6 +358,9 @@ function extractEnvelope(payload, headers, topic2, partition, offset) {
273
358
  }
274
359
 
275
360
  // src/client/errors.ts
361
+ function toError(error) {
362
+ return error instanceof Error ? error : new Error(String(error));
363
+ }
276
364
  var KafkaProcessingError = class extends Error {
277
365
  constructor(message, topic2, originalMessage, options) {
278
366
  super(message, options);
@@ -281,6 +369,8 @@ var KafkaProcessingError = class extends Error {
281
369
  this.name = "KafkaProcessingError";
282
370
  if (options?.cause) this.cause = options.cause;
283
371
  }
372
+ topic;
373
+ originalMessage;
284
374
  };
285
375
  var KafkaValidationError = class extends Error {
286
376
  constructor(topic2, originalMessage, options) {
@@ -290,6 +380,8 @@ var KafkaValidationError = class extends Error {
290
380
  this.name = "KafkaValidationError";
291
381
  if (options?.cause) this.cause = options.cause;
292
382
  }
383
+ topic;
384
+ originalMessage;
293
385
  };
294
386
  var KafkaRetryExhaustedError = class extends KafkaProcessingError {
295
387
  constructor(topic2, originalMessage, attempts, options) {
@@ -302,9 +394,13 @@ var KafkaRetryExhaustedError = class extends KafkaProcessingError {
302
394
  this.attempts = attempts;
303
395
  this.name = "KafkaRetryExhaustedError";
304
396
  }
397
+ attempts;
305
398
  };
306
399
 
307
400
  // src/client/kafka.client/producer/ops.ts
401
+ function resolveSerde(topicOrDesc, clientSerde) {
402
+ return topicOrDesc?.__serde ?? clientSerde;
403
+ }
308
404
  function resolveTopicName(topicOrDescriptor) {
309
405
  if (typeof topicOrDescriptor === "string") return topicOrDescriptor;
310
406
  if (topicOrDescriptor && typeof topicOrDescriptor === "object" && "__topic" in topicOrDescriptor) {
@@ -351,6 +447,7 @@ async function validateMessage(topicOrDesc, message, deps, ctx) {
351
447
  }
352
448
  async function buildSendPayload(topicOrDesc, messages, deps, compression) {
353
449
  const topic2 = resolveTopicName(topicOrDesc);
450
+ const serde = resolveSerde(topicOrDesc, deps.serde);
354
451
  const builtMessages = await Promise.all(
355
452
  messages.map(async (m) => {
356
453
  const envelopeHeaders = buildEnvelopeHeaders({
@@ -370,11 +467,16 @@ async function buildSendPayload(topicOrDesc, messages, deps, compression) {
370
467
  headers: envelopeHeaders,
371
468
  version: m.schemaVersion ?? 1
372
469
  };
470
+ const validated = await validateMessage(topicOrDesc, m.value, deps, sendCtx);
373
471
  return {
374
- value: JSON.stringify(
375
- await validateMessage(topicOrDesc, m.value, deps, sendCtx)
376
- ),
377
- key: m.key ?? null,
472
+ value: await serde.serialize(validated, {
473
+ topic: topic2,
474
+ headers: envelopeHeaders,
475
+ isKey: false
476
+ }),
477
+ // Explicit key wins; otherwise fall back to the descriptor's .key()
478
+ // extractor (runs on the original, pre-validation payload).
479
+ key: m.key ?? topicOrDesc?.__key?.(m.value) ?? null,
378
480
  headers: envelopeHeaders
379
481
  };
380
482
  })
@@ -383,7 +485,7 @@ async function buildSendPayload(topicOrDesc, messages, deps, compression) {
383
485
  }
384
486
 
385
487
  // src/client/kafka.client/consumer/ops.ts
386
- function getOrCreateConsumer(groupId, fromBeginning, autoCommit, deps, partitionAssigner, onFirstAssignment) {
488
+ function getOrCreateConsumer(groupId, fromBeginning, autoCommit, deps, partitionAssigner, onFirstAssignment, groupInstanceId) {
387
489
  const { consumers, consumerCreationOptions, transport, onRebalance, logger } = deps;
388
490
  if (consumers.has(groupId)) {
389
491
  const prev = consumerCreationOptions.get(groupId);
@@ -416,6 +518,7 @@ function getOrCreateConsumer(groupId, fromBeginning, autoCommit, deps, partition
416
518
  fromBeginning,
417
519
  autoCommit,
418
520
  partitionAssigner: partitionAssigner ?? "cooperative-sticky",
521
+ groupInstanceId,
419
522
  onRebalance: (type, assignments) => {
420
523
  if (type === "assign") fireOnAssignment();
421
524
  else if (type === "revoke") scheduleSettle();
@@ -455,12 +558,22 @@ function buildSchemaMap(topics, schemaRegistry, optionSchemas, logger) {
455
558
  }
456
559
  return schemaMap;
457
560
  }
561
+ function buildSerdeMap(topics) {
562
+ let serdeMap;
563
+ for (const t of topics) {
564
+ if (t?.__serde) {
565
+ (serdeMap ??= /* @__PURE__ */ new Map()).set(resolveTopicName(t), t.__serde);
566
+ }
567
+ }
568
+ return serdeMap;
569
+ }
458
570
 
459
571
  // src/client/kafka.client/admin/ops.ts
460
572
  var AdminOps = class {
461
573
  constructor(deps) {
462
574
  this.deps = deps;
463
575
  }
576
+ deps;
464
577
  isConnected = false;
465
578
  /** Underlying admin client — used by index.ts for topic validation. */
466
579
  get admin() {
@@ -567,7 +680,10 @@ var AdminOps = class {
567
680
  const found = results.find(
568
681
  (r) => r.partition === partition
569
682
  );
570
- return { partition, offset: found?.offset ?? "-1" };
683
+ if (found) return { partition, offset: found.offset };
684
+ const topicOffsets = await this.deps.admin.fetchTopicOffsets(topic2);
685
+ const po = topicOffsets.find((o) => o.partition === partition);
686
+ return { partition, offset: po?.high ?? "0" };
571
687
  })
572
688
  );
573
689
  await this.deps.admin.setOffsets({ groupId: gid, topic: topic2, partitions: offsets });
@@ -648,7 +764,8 @@ var AdminOps = class {
648
764
  name: t.name,
649
765
  partitions: t.partitions.map((p) => ({
650
766
  partition: p.partitionId ?? p.partition ?? 0,
651
- leader: p.leader ?? 0,
767
+ // -1 is Kafka's own "no leader" sentinel; 0 is a valid broker id
768
+ leader: p.leader ?? -1,
652
769
  replicas: (p.replicas ?? []).map(
653
770
  (r) => typeof r === "number" ? r : r.nodeId
654
771
  ),
@@ -732,23 +849,9 @@ var AdminOps = class {
732
849
  };
733
850
 
734
851
  // src/client/kafka.client/consumer/pipeline.ts
735
- function toError(error) {
736
- return error instanceof Error ? error : new Error(String(error));
737
- }
738
852
  function sleep(ms) {
739
853
  return new Promise((resolve) => setTimeout(resolve, ms));
740
854
  }
741
- function parseJsonMessage(raw, topic2, logger) {
742
- try {
743
- return JSON.parse(raw);
744
- } catch (error) {
745
- logger.error(
746
- `Failed to parse message from topic ${topic2}:`,
747
- toError(error).stack
748
- );
749
- return null;
750
- }
751
- }
752
855
  async function validateWithSchema(message, raw, topic2, schemaMap, interceptors, dlq, deps) {
753
856
  const schema = schemaMap.get(topic2);
754
857
  if (!schema) return message;
@@ -1018,6 +1121,7 @@ async function executeWithRetry(fn, ctx, deps) {
1018
1121
  for (const env of envelopes) deps.onMessage?.(env);
1019
1122
  return;
1020
1123
  }
1124
+ deps.onFailure?.(envelopes[0]);
1021
1125
  const isLastAttempt = attempt === maxAttempts;
1022
1126
  const reportedError = isLastAttempt && maxAttempts > 1 ? new KafkaRetryExhaustedError(
1023
1127
  topic2,
@@ -1102,8 +1206,13 @@ async function subscribeWithRetry(consumer, topics, logger, retryOpts) {
1102
1206
  }
1103
1207
  }
1104
1208
 
1105
- // src/client/kafka.client/consumer/dlq-replay.ts
1209
+ // src/client/kafka.client/consumer/features/dlq-replay.ts
1106
1210
  async function replayDlqTopic(topic2, deps, options = {}) {
1211
+ if (topic2.endsWith(".dlq")) {
1212
+ throw new Error(
1213
+ `replayDlq: pass the ORIGINAL topic name \u2014 "${topic2}" already ends in ".dlq" (the ".dlq" suffix is appended internally, so this would read "${topic2}.dlq")`
1214
+ );
1215
+ }
1107
1216
  const dlqTopic = `${topic2}.dlq`;
1108
1217
  const partitionOffsets = await deps.fetchTopicOffsets(dlqTopic);
1109
1218
  const activePartitions = partitionOffsets.filter(
@@ -1135,15 +1244,15 @@ async function replayDlqTopic(topic2, deps, options = {}) {
1135
1244
  const originalHeaders = Object.fromEntries(
1136
1245
  Object.entries(headers).filter(([k]) => !deps.dlqHeaderKeys.has(k))
1137
1246
  );
1138
- const value = message.value.toString();
1139
- const shouldProcess = !options.filter || options.filter(headers, value);
1247
+ const bytes = message.value;
1248
+ const shouldProcess = !options.filter || options.filter(headers, bytes.toString("utf8"));
1140
1249
  if (!targetTopic || !shouldProcess) {
1141
1250
  skipped++;
1142
1251
  } else if (options.dryRun) {
1143
1252
  deps.logger.log(`[DLQ replay dry-run] Would replay to "${targetTopic}"`);
1144
1253
  replayed++;
1145
1254
  } else {
1146
- await deps.send(targetTopic, [{ value, headers: originalHeaders }]);
1255
+ await deps.send(targetTopic, [{ value: bytes, headers: originalHeaders }]);
1147
1256
  replayed++;
1148
1257
  }
1149
1258
  const allDone = Array.from(highWatermarks.entries()).every(
@@ -1169,6 +1278,7 @@ var MetricsManager = class {
1169
1278
  constructor(deps) {
1170
1279
  this.deps = deps;
1171
1280
  }
1281
+ deps;
1172
1282
  topicMetrics = /* @__PURE__ */ new Map();
1173
1283
  metricsFor(topic2) {
1174
1284
  let m = this.topicMetrics.get(topic2);
@@ -1194,16 +1304,25 @@ var MetricsManager = class {
1194
1304
  for (const inst of this.deps.instrumentation) inst.onRetry?.(envelope, attempt, maxRetries);
1195
1305
  }
1196
1306
  /**
1197
- * Increment the DLQ counter for the envelope's topic, fire all `onDlq` instrumentation hooks,
1198
- * and notify the circuit breaker of a failure (when `gid` is provided).
1307
+ * Increment the DLQ counter for the envelope's topic and fire all `onDlq` instrumentation hooks.
1308
+ * Circuit breaker failures are recorded separately via `notifyFailure` at the
1309
+ * handler-error boundary — dead-lettering itself is not a circuit event.
1199
1310
  * @param envelope The message envelope being sent to the DLQ.
1200
1311
  * @param reason The reason the message is being dead-lettered.
1201
- * @param gid Consumer group ID — used to drive circuit breaker state.
1202
1312
  */
1203
- notifyDlq(envelope, reason, gid) {
1313
+ notifyDlq(envelope, reason) {
1204
1314
  this.metricsFor(envelope.topic).dlqCount++;
1205
1315
  for (const inst of this.deps.instrumentation) inst.onDlq?.(envelope, reason);
1206
- if (gid) this.deps.onCircuitFailure(envelope, gid);
1316
+ }
1317
+ /**
1318
+ * Notify the circuit breaker of a handler failure. Fired on every failed
1319
+ * handler attempt (in-process retries and retry-topic levels included),
1320
+ * independent of whether the message is ultimately dead-lettered.
1321
+ * @param envelope The message envelope whose handler failed.
1322
+ * @param gid Consumer group ID — used to drive circuit breaker state.
1323
+ */
1324
+ notifyFailure(envelope, gid) {
1325
+ this.deps.onCircuitFailure(envelope, gid);
1207
1326
  }
1208
1327
  /**
1209
1328
  * Increment the deduplication counter for the envelope's topic and fire all `onDuplicate` hooks.
@@ -1262,6 +1381,7 @@ var InFlightTracker = class {
1262
1381
  constructor(warn) {
1263
1382
  this.warn = warn;
1264
1383
  }
1384
+ warn;
1265
1385
  inFlightTotal = 0;
1266
1386
  drainResolvers = [];
1267
1387
  /**
@@ -1272,10 +1392,16 @@ var InFlightTracker = class {
1272
1392
  */
1273
1393
  track(fn) {
1274
1394
  this.inFlightTotal++;
1275
- return fn().finally(() => {
1395
+ const done = () => {
1276
1396
  this.inFlightTotal--;
1277
1397
  if (this.inFlightTotal === 0) this.drainResolvers.splice(0).forEach((r) => r());
1278
- });
1398
+ };
1399
+ try {
1400
+ return fn().finally(done);
1401
+ } catch (err) {
1402
+ done();
1403
+ throw err;
1404
+ }
1279
1405
  }
1280
1406
  /**
1281
1407
  * Resolve when all tracked handlers have completed, or after `timeoutMs` elapses.
@@ -1309,6 +1435,7 @@ var CircuitBreakerManager = class {
1309
1435
  constructor(deps) {
1310
1436
  this.deps = deps;
1311
1437
  }
1438
+ deps;
1312
1439
  states = /* @__PURE__ */ new Map();
1313
1440
  configs = /* @__PURE__ */ new Map();
1314
1441
  /**
@@ -1453,6 +1580,9 @@ var AsyncQueue = class {
1453
1580
  this.onFull = onFull;
1454
1581
  this.onDrained = onDrained;
1455
1582
  }
1583
+ highWaterMark;
1584
+ onFull;
1585
+ onDrained;
1456
1586
  items = [];
1457
1587
  waiting = [];
1458
1588
  closed = false;
@@ -1464,6 +1594,7 @@ var AsyncQueue = class {
1464
1594
  * @param item The value to enqueue.
1465
1595
  */
1466
1596
  push(item) {
1597
+ if (this.closed) return;
1467
1598
  if (this.waiting.length > 0) {
1468
1599
  this.waiting.shift().resolve({ value: item, done: false });
1469
1600
  } else {
@@ -1514,6 +1645,101 @@ var AsyncQueue = class {
1514
1645
  }
1515
1646
  };
1516
1647
 
1648
+ // src/client/kafka.client/validate-options.ts
1649
+ function validateClientOptions(clientId, groupId, brokers, options) {
1650
+ const problems = [];
1651
+ if (typeof clientId !== "string" || clientId.trim() === "") {
1652
+ problems.push("clientId must be a non-empty string");
1653
+ }
1654
+ if (typeof groupId !== "string" || groupId.trim() === "") {
1655
+ problems.push("groupId must be a non-empty string");
1656
+ }
1657
+ if (!Array.isArray(brokers) || brokers.length === 0 && !options?.transport) {
1658
+ problems.push("brokers must be a non-empty array of broker addresses");
1659
+ } else if (brokers.some((b) => typeof b !== "string" || b.trim() === "")) {
1660
+ problems.push("brokers must not contain empty entries");
1661
+ }
1662
+ if (options) {
1663
+ const {
1664
+ numPartitions,
1665
+ transactionalId,
1666
+ clockRecovery,
1667
+ lagThrottle
1668
+ } = options;
1669
+ if (numPartitions !== void 0 && (!Number.isInteger(numPartitions) || numPartitions < 1)) {
1670
+ problems.push(
1671
+ `numPartitions must be a positive integer (got ${numPartitions})`
1672
+ );
1673
+ }
1674
+ if (transactionalId !== void 0 && transactionalId.trim() === "") {
1675
+ problems.push("transactionalId must be a non-empty string when set");
1676
+ }
1677
+ if (clockRecovery) {
1678
+ if (!Array.isArray(clockRecovery.topics)) {
1679
+ problems.push("clockRecovery.topics must be an array of topic names");
1680
+ }
1681
+ if (clockRecovery.timeoutMs !== void 0 && !(clockRecovery.timeoutMs > 0)) {
1682
+ problems.push(
1683
+ `clockRecovery.timeoutMs must be > 0 (got ${clockRecovery.timeoutMs})`
1684
+ );
1685
+ }
1686
+ }
1687
+ if (lagThrottle) {
1688
+ if (!(lagThrottle.maxLag >= 0)) {
1689
+ problems.push(`lagThrottle.maxLag must be >= 0 (got ${lagThrottle.maxLag})`);
1690
+ }
1691
+ if (lagThrottle.pollIntervalMs !== void 0 && !(lagThrottle.pollIntervalMs > 0)) {
1692
+ problems.push(
1693
+ `lagThrottle.pollIntervalMs must be > 0 (got ${lagThrottle.pollIntervalMs})`
1694
+ );
1695
+ }
1696
+ if (lagThrottle.maxWaitMs !== void 0 && !(lagThrottle.maxWaitMs >= 0)) {
1697
+ problems.push(
1698
+ `lagThrottle.maxWaitMs must be >= 0 (got ${lagThrottle.maxWaitMs})`
1699
+ );
1700
+ }
1701
+ }
1702
+ }
1703
+ if (problems.length > 0) {
1704
+ throw new Error(
1705
+ `KafkaClient: invalid configuration:
1706
+ - ${problems.join("\n- ")}`
1707
+ );
1708
+ }
1709
+ }
1710
+
1711
+ // src/client/security/resolve-security.ts
1712
+ var LOCAL_HOST_PATTERNS = [
1713
+ /^localhost(:\d+)?$/i,
1714
+ /^127\.\d+\.\d+\.\d+(:\d+)?$/,
1715
+ /^\[?::1\]?(:\d+)?$/,
1716
+ /^0\.0\.0\.0(:\d+)?$/,
1717
+ /^host\.docker\.internal(:\d+)?$/i
1718
+ ];
1719
+ function isLocalBroker(broker) {
1720
+ return LOCAL_HOST_PATTERNS.some((re) => re.test(broker.trim()));
1721
+ }
1722
+ function resolveSecurityOptions(security, brokers, logger) {
1723
+ const hasRemoteBroker = brokers.some((b) => !isLocalBroker(b));
1724
+ if (!security?.sasl && security?.ssl !== true) {
1725
+ if (hasRemoteBroker && !security?.allowInsecure) {
1726
+ logger.warn(
1727
+ "Connecting to non-local brokers without TLS or SASL \u2014 traffic and payloads travel in plaintext. Configure `security: { ssl, sasl }` for production clusters, or set `security: { allowInsecure: true }` to acknowledge an intentionally insecure setup."
1728
+ );
1729
+ }
1730
+ return security;
1731
+ }
1732
+ if (security.sasl && security.ssl === void 0) {
1733
+ return { ...security, ssl: true };
1734
+ }
1735
+ if (security.sasl && security.ssl === false) {
1736
+ logger.warn(
1737
+ "SASL credentials are configured with `ssl: false` \u2014 credentials will be sent over plaintext. This is only safe on fully trusted networks."
1738
+ );
1739
+ }
1740
+ return security;
1741
+ }
1742
+
1517
1743
  // src/client/kafka.client/producer/lifecycle.ts
1518
1744
  var _activeTransactionalIds = /* @__PURE__ */ new Set();
1519
1745
  async function ensureTopic(ctx, topic2) {
@@ -1647,6 +1873,7 @@ async function recoverLamportClockImpl(ctx, topics) {
1647
1873
  const remaining = new Set(
1648
1874
  partitionsToRead.map((p) => `${p.topic}:${p.partition}`)
1649
1875
  );
1876
+ let settled = false;
1650
1877
  const cleanup = () => {
1651
1878
  consumer.disconnect().catch(() => {
1652
1879
  }).finally(() => {
@@ -1654,6 +1881,16 @@ async function recoverLamportClockImpl(ctx, topics) {
1654
1881
  });
1655
1882
  });
1656
1883
  };
1884
+ const timeoutTimer = setTimeout(() => {
1885
+ if (settled) return;
1886
+ settled = true;
1887
+ ctx.logger.warn(
1888
+ `Clock recovery: timed out after ${ctx.clockRecoveryTimeoutMs} ms with ${remaining.size} partition(s) unread \u2014 proceeding with partial result`
1889
+ );
1890
+ cleanup();
1891
+ resolve();
1892
+ }, ctx.clockRecoveryTimeoutMs);
1893
+ timeoutTimer.unref?.();
1657
1894
  consumer.connect().then(async () => {
1658
1895
  const uniqueTopics = [
1659
1896
  ...new Set(partitionsToRead.map((p) => p.topic))
@@ -1674,13 +1911,18 @@ async function recoverLamportClockImpl(ctx, topics) {
1674
1911
  const clock = Number(raw);
1675
1912
  if (!Number.isNaN(clock) && clock > maxClock) maxClock = clock;
1676
1913
  }
1677
- if (remaining.size === 0) {
1914
+ if (remaining.size === 0 && !settled) {
1915
+ settled = true;
1916
+ clearTimeout(timeoutTimer);
1678
1917
  cleanup();
1679
1918
  resolve();
1680
1919
  }
1681
1920
  }
1682
1921
  })
1683
1922
  ).catch((err) => {
1923
+ if (settled) return;
1924
+ settled = true;
1925
+ clearTimeout(timeoutTimer);
1684
1926
  cleanup();
1685
1927
  reject(err);
1686
1928
  });
@@ -1721,6 +1963,15 @@ async function preparePayload(ctx, topicOrDesc, messages, compression) {
1721
1963
  await ensureTopic(ctx, payload.topic);
1722
1964
  return payload;
1723
1965
  }
1966
+ async function redirectToDelayed(ctx, payload, deliverAfterMs) {
1967
+ const until = String(Date.now() + deliverAfterMs);
1968
+ for (const m of payload.messages) {
1969
+ m.headers[HEADER_DELAYED_UNTIL] = until;
1970
+ m.headers[HEADER_DELAYED_TARGET] = payload.topic;
1971
+ }
1972
+ payload.topic = `${payload.topic}.delayed`;
1973
+ await ensureTopic(ctx, payload.topic);
1974
+ }
1724
1975
  async function sendMessageImpl(ctx, topicOrDesc, message, options = {}) {
1725
1976
  await waitIfThrottled(ctx);
1726
1977
  const payload = await preparePayload(
@@ -1738,6 +1989,9 @@ async function sendMessageImpl(ctx, topicOrDesc, message, options = {}) {
1738
1989
  ],
1739
1990
  options.compression
1740
1991
  );
1992
+ if (options.deliverAfterMs && options.deliverAfterMs > 0) {
1993
+ await redirectToDelayed(ctx, payload, options.deliverAfterMs);
1994
+ }
1741
1995
  await ctx.producer.send(payload);
1742
1996
  ctx.metrics.notifyAfterSend(payload.topic, payload.messages.length);
1743
1997
  }
@@ -1749,6 +2003,9 @@ async function sendBatchImpl(ctx, topicOrDesc, messages, options) {
1749
2003
  messages,
1750
2004
  options?.compression
1751
2005
  );
2006
+ if (options?.deliverAfterMs && options.deliverAfterMs > 0) {
2007
+ await redirectToDelayed(ctx, payload, options.deliverAfterMs);
2008
+ }
1752
2009
  await ctx.producer.send(payload);
1753
2010
  ctx.metrics.notifyAfterSend(payload.topic, payload.messages.length);
1754
2011
  }
@@ -1785,6 +2042,17 @@ async function transactionImpl(ctx, fn) {
1785
2042
  });
1786
2043
  }
1787
2044
  ctx.txProducer = await ctx.txProducerInitPromise;
2045
+ const prev = ctx._txChain;
2046
+ let release;
2047
+ ctx._txChain = new Promise((r) => release = r);
2048
+ await prev;
2049
+ try {
2050
+ await runTransaction(ctx, fn);
2051
+ } finally {
2052
+ release();
2053
+ }
2054
+ }
2055
+ async function runTransaction(ctx, fn) {
1788
2056
  const tx = await ctx.txProducer.transaction();
1789
2057
  try {
1790
2058
  const txCtx = {
@@ -1858,7 +2126,7 @@ async function waitForPartitionAssignment(consumer, topics, logger, timeoutMs =
1858
2126
  `Retry consumer did not receive partition assignments for [${topics.join(", ")}] within ${timeoutMs}ms`
1859
2127
  );
1860
2128
  }
1861
- async function startLevelConsumer(level, levelTopics, levelGroupId, originalTopics, handleMessage, retry, dlq, interceptors, schemaMap, deps, assignmentTimeoutMs) {
2129
+ async function startLevelConsumer(level, levelTopics, levelGroupId, originalTopics, handleMessage, retry, dlq, interceptors, schemaMap, deps, assignmentTimeoutMs, serdeMap) {
1862
2130
  const {
1863
2131
  logger,
1864
2132
  producer,
@@ -1904,20 +2172,35 @@ async function startLevelConsumer(level, levelTopics, levelGroupId, originalTopi
1904
2172
  await sleep(remaining);
1905
2173
  consumer.resume([{ topic: levelTopic, partitions: [partition] }]);
1906
2174
  }
1907
- const raw = message.value.toString();
1908
- const parsed = parseJsonMessage(raw, levelTopic, logger);
1909
- if (parsed === null) {
1910
- await consumer.commitOffsets([nextOffset]);
1911
- return;
1912
- }
2175
+ const rawBytes = message.value;
1913
2176
  const currentMaxRetries = parseInt(
1914
2177
  headers[RETRY_HEADER_MAX_RETRIES] ?? String(retry.maxRetries),
1915
2178
  10
1916
2179
  );
1917
2180
  const originalTopic = headers[RETRY_HEADER_ORIGINAL_TOPIC] ?? levelTopic.replace(/\.retry\.\d+$/, "");
2181
+ const serde = serdeMap?.get(originalTopic) ?? deps.serde;
2182
+ let parsed;
2183
+ try {
2184
+ parsed = await serde.deserialize(rawBytes, {
2185
+ topic: originalTopic,
2186
+ headers,
2187
+ isKey: false
2188
+ });
2189
+ } catch (err) {
2190
+ logger.error(
2191
+ `Failed to deserialize retry message from topic ${levelTopic}:`,
2192
+ toError(err).stack
2193
+ );
2194
+ await consumer.commitOffsets([nextOffset]);
2195
+ return;
2196
+ }
2197
+ if (parsed === null) {
2198
+ await consumer.commitOffsets([nextOffset]);
2199
+ return;
2200
+ }
1918
2201
  const validated = await validateWithSchema(
1919
2202
  parsed,
1920
- raw,
2203
+ rawBytes,
1921
2204
  originalTopic,
1922
2205
  schemaMap,
1923
2206
  interceptors,
@@ -1952,6 +2235,7 @@ async function startLevelConsumer(level, levelTopics, levelGroupId, originalTopi
1952
2235
  await consumer.commitOffsets([nextOffset]);
1953
2236
  return;
1954
2237
  }
2238
+ deps.onFailure?.(envelope);
1955
2239
  const exhausted = level >= currentMaxRetries;
1956
2240
  const reportedError = exhausted && currentMaxRetries > 1 ? new KafkaRetryExhaustedError(
1957
2241
  originalTopic,
@@ -1970,7 +2254,7 @@ async function startLevelConsumer(level, levelTopics, levelGroupId, originalTopi
1970
2254
  const delay = Math.floor(Math.random() * cap);
1971
2255
  const { topic: rtTopic, messages: rtMsgs } = buildRetryTopicPayload(
1972
2256
  originalTopic,
1973
- [raw],
2257
+ [rawBytes],
1974
2258
  nextLevel,
1975
2259
  currentMaxRetries,
1976
2260
  delay,
@@ -2012,7 +2296,7 @@ async function startLevelConsumer(level, levelTopics, levelGroupId, originalTopi
2012
2296
  } else if (dlq) {
2013
2297
  const { topic: dTopic, messages: dMsgs } = buildDlqPayload(
2014
2298
  originalTopic,
2015
- raw,
2299
+ rawBytes,
2016
2300
  {
2017
2301
  error,
2018
2302
  // +1 to account for the main consumer's initial attempt before routing.
@@ -2074,7 +2358,7 @@ async function startLevelConsumer(level, levelTopics, levelGroupId, originalTopi
2074
2358
  `Retry level ${level}/${retry.maxRetries} consumer started for: ${originalTopics.join(", ")} (group: ${levelGroupId})`
2075
2359
  );
2076
2360
  }
2077
- async function startRetryTopicConsumers(originalTopics, originalGroupId, handleMessage, retry, dlq, interceptors, schemaMap, deps, assignmentTimeoutMs) {
2361
+ async function startRetryTopicConsumers(originalTopics, originalGroupId, handleMessage, retry, dlq, interceptors, schemaMap, deps, assignmentTimeoutMs, serdeMap) {
2078
2362
  const levelGroupIds = new Array(retry.maxRetries);
2079
2363
  await Promise.all(
2080
2364
  Array.from({ length: retry.maxRetries }, async (_, i) => {
@@ -2092,7 +2376,8 @@ async function startRetryTopicConsumers(originalTopics, originalGroupId, handleM
2092
2376
  interceptors,
2093
2377
  schemaMap,
2094
2378
  deps,
2095
- assignmentTimeoutMs
2379
+ assignmentTimeoutMs,
2380
+ serdeMap
2096
2381
  );
2097
2382
  levelGroupIds[i] = levelGroupId;
2098
2383
  })
@@ -2167,7 +2452,8 @@ async function setupConsumer(ctx, topics, mode, options) {
2167
2452
  options.autoCommit ?? true,
2168
2453
  ctx.consumerOpsDeps,
2169
2454
  options.partitionAssigner,
2170
- resolveReady
2455
+ resolveReady,
2456
+ options.groupInstanceId
2171
2457
  );
2172
2458
  const schemaMap = buildSchemaMap(
2173
2459
  stringTopics,
@@ -2175,10 +2461,14 @@ async function setupConsumer(ctx, topics, mode, options) {
2175
2461
  optionSchemas,
2176
2462
  ctx.logger
2177
2463
  );
2464
+ const serdeMap = buildSerdeMap(stringTopics);
2178
2465
  const topicNames = stringTopics.map((t) => resolveTopicName(t));
2179
2466
  const subscribeTopics = [...topicNames, ...regexTopics];
2180
2467
  await ensureConsumerTopics(ctx, topicNames, dlq, options.deduplication);
2181
2468
  await consumer.connect();
2469
+ if (dlq || options.retryTopics || options.deduplication) {
2470
+ await ctx.producer.connect();
2471
+ }
2182
2472
  await subscribeWithRetry(
2183
2473
  consumer,
2184
2474
  subscribeTopics,
@@ -2189,19 +2479,19 @@ async function setupConsumer(ctx, topics, mode, options) {
2189
2479
  ctx.logger.log(
2190
2480
  `${mode === "eachBatch" ? "Batch consumer" : "Consumer"} subscribed to topics: ${displayTopics}`
2191
2481
  );
2192
- return { consumer, schemaMap, topicNames, gid, dlq, interceptors, retry, hasRegex, readyPromise };
2482
+ return { consumer, schemaMap, serdeMap, topicNames, gid, dlq, interceptors, retry, hasRegex, readyPromise };
2193
2483
  }
2194
2484
  function resolveDeduplicationContext(ctx, groupId, options) {
2195
2485
  if (!options) return void 0;
2196
- if (!ctx.dedupStates.has(groupId))
2197
- ctx.dedupStates.set(groupId, /* @__PURE__ */ new Map());
2198
- return { options, state: ctx.dedupStates.get(groupId) };
2486
+ const store = options.store ?? new InMemoryDedupStore(ctx.dedupStates);
2487
+ return { options, store, groupId };
2199
2488
  }
2200
2489
  function messageDepsFor(ctx, gid, options) {
2201
2490
  const notifyRetry = ctx.metrics.notifyRetry.bind(ctx.metrics);
2202
2491
  return {
2203
2492
  logger: ctx.logger,
2204
2493
  producer: ctx.producer,
2494
+ serde: ctx.serde,
2205
2495
  instrumentation: ctx.instrumentation,
2206
2496
  onMessageLost: options?.onMessageLost ?? ctx.onMessageLost,
2207
2497
  onTtlExpired: ctx.onTtlExpired,
@@ -2209,15 +2499,17 @@ function messageDepsFor(ctx, gid, options) {
2209
2499
  notifyRetry(envelope, attempt, max);
2210
2500
  return options.onRetry(envelope, attempt, max);
2211
2501
  } : notifyRetry,
2212
- onDlq: (envelope, reason) => ctx.metrics.notifyDlq(envelope, reason, gid),
2502
+ onDlq: (envelope, reason) => ctx.metrics.notifyDlq(envelope, reason),
2213
2503
  onDuplicate: ctx.metrics.notifyDuplicate.bind(ctx.metrics),
2214
- onMessage: (envelope) => ctx.metrics.notifyMessage(envelope, gid)
2504
+ onMessage: (envelope) => ctx.metrics.notifyMessage(envelope, gid),
2505
+ onFailure: (envelope) => ctx.metrics.notifyFailure(envelope, gid)
2215
2506
  };
2216
2507
  }
2217
2508
  function buildRetryTopicDeps(ctx) {
2218
2509
  return {
2219
2510
  logger: ctx.logger,
2220
2511
  producer: ctx.producer,
2512
+ serde: ctx.serde,
2221
2513
  instrumentation: ctx.instrumentation,
2222
2514
  onMessageLost: ctx.onMessageLost,
2223
2515
  onRetry: ctx.metrics.notifyRetry.bind(ctx.metrics),
@@ -2235,7 +2527,7 @@ async function makeEosMainContext(ctx, gid, consumer, options) {
2235
2527
  return { txProducer, consumer };
2236
2528
  }
2237
2529
  async function launchRetryChain(ctx, gid, topicNames, handleMessage, opts) {
2238
- const { retry, dlq, interceptors, schemaMap, assignmentTimeoutMs } = opts;
2530
+ const { retry, dlq, interceptors, schemaMap, serdeMap, assignmentTimeoutMs } = opts;
2239
2531
  if (!ctx.autoCreateTopicsEnabled) {
2240
2532
  await ctx.adminOps.validateRetryTopicsExist(topicNames, retry.maxRetries);
2241
2533
  }
@@ -2250,11 +2542,17 @@ async function launchRetryChain(ctx, gid, topicNames, handleMessage, opts) {
2250
2542
  schemaMap,
2251
2543
  {
2252
2544
  ...ctx.retryTopicDeps,
2545
+ // Bind circuit breaker events to the MAIN consumer group so failures and
2546
+ // successes inside the retry chain drive the same breaker as the main
2547
+ // consumer (the retry chain has no breaker config of its own).
2548
+ onFailure: (envelope) => ctx.metrics.notifyFailure(envelope, gid),
2549
+ onMessage: (envelope) => ctx.metrics.notifyMessage(envelope, gid),
2253
2550
  onLevelStarted: (levelGroupId) => {
2254
2551
  ctx.companionGroupIds.get(gid).push(levelGroupId);
2255
2552
  }
2256
2553
  },
2257
- assignmentTimeoutMs
2554
+ assignmentTimeoutMs,
2555
+ serdeMap
2258
2556
  );
2259
2557
  }
2260
2558
 
@@ -2265,7 +2563,15 @@ async function applyDeduplication(envelope, raw, dedup, dlq, deps) {
2265
2563
  const incomingClock = Number(clockRaw);
2266
2564
  if (Number.isNaN(incomingClock)) return false;
2267
2565
  const stateKey = `${envelope.topic}:${envelope.partition}`;
2268
- const lastProcessedClock = dedup.state.get(stateKey) ?? -1;
2566
+ let lastProcessedClock;
2567
+ try {
2568
+ lastProcessedClock = await dedup.store.getLastClock(dedup.groupId, stateKey) ?? -1;
2569
+ } catch (err) {
2570
+ deps.logger.error(
2571
+ `Dedup store getLastClock failed on ${envelope.topic}[${envelope.partition}] \u2014 treating message as not a duplicate (fail-open): ${err.message}`
2572
+ );
2573
+ return false;
2574
+ }
2269
2575
  if (incomingClock <= lastProcessedClock) {
2270
2576
  const meta = {
2271
2577
  incomingClock,
@@ -2295,21 +2601,38 @@ async function applyDeduplication(envelope, raw, dedup, dlq, deps) {
2295
2601
  }
2296
2602
  return true;
2297
2603
  }
2298
- dedup.state.set(stateKey, incomingClock);
2604
+ try {
2605
+ await dedup.store.setLastClock(dedup.groupId, stateKey, incomingClock);
2606
+ } catch (err) {
2607
+ deps.logger.error(
2608
+ `Dedup store setLastClock failed on ${envelope.topic}[${envelope.partition}] \u2014 processing message anyway (fail-open): ${err.message}`
2609
+ );
2610
+ }
2299
2611
  return false;
2300
2612
  }
2301
- async function parseSingleMessage(message, topic2, partition, schemaMap, interceptors, dlq, deps) {
2613
+ async function parseSingleMessage(message, topic2, partition, schemaMap, interceptors, dlq, deps, serdeMap) {
2302
2614
  if (!message.value) {
2303
2615
  deps.logger.warn(`Received empty message from topic ${topic2}`);
2304
2616
  return null;
2305
2617
  }
2306
- const raw = message.value.toString();
2307
- const parsed = parseJsonMessage(raw, topic2, deps.logger);
2308
- if (parsed === null) return null;
2618
+ const bytes = message.value;
2309
2619
  const headers = decodeHeaders(message.headers);
2620
+ const serde = serdeMap?.get(topic2) ?? deps.serde;
2621
+ let parsed;
2622
+ try {
2623
+ parsed = await serde.deserialize(bytes, { topic: topic2, headers, isKey: false });
2624
+ } catch (error) {
2625
+ deps.logger.error(
2626
+ `Failed to deserialize message from topic ${topic2}:`,
2627
+ toError(error).stack
2628
+ );
2629
+ return null;
2630
+ }
2631
+ if (parsed === null) return null;
2310
2632
  const validated = await validateWithSchema(
2311
2633
  parsed,
2312
- raw,
2634
+ // Forward the ORIGINAL bytes to DLQ on validation failure (binary-safe).
2635
+ bytes,
2313
2636
  topic2,
2314
2637
  schemaMap,
2315
2638
  interceptors,
@@ -2323,6 +2646,7 @@ async function handleEachMessage(payload, opts, deps) {
2323
2646
  const { topic: topic2, partition, message } = payload;
2324
2647
  const {
2325
2648
  schemaMap,
2649
+ serdeMap,
2326
2650
  handleMessage,
2327
2651
  interceptors,
2328
2652
  dlq,
@@ -2331,6 +2655,7 @@ async function handleEachMessage(payload, opts, deps) {
2331
2655
  timeoutMs,
2332
2656
  wrapWithTimeout
2333
2657
  } = opts;
2658
+ const rawBytes = message.value;
2334
2659
  const eos = opts.eosMainContext;
2335
2660
  const nextOffsetStr = (parseInt(message.offset, 10) + 1).toString();
2336
2661
  const commitOffset = eos ? async () => {
@@ -2375,7 +2700,8 @@ async function handleEachMessage(payload, opts, deps) {
2375
2700
  schemaMap,
2376
2701
  interceptors,
2377
2702
  dlq,
2378
- deps
2703
+ deps,
2704
+ serdeMap
2379
2705
  );
2380
2706
  if (envelope === null) {
2381
2707
  await commitOffset?.();
@@ -2384,7 +2710,7 @@ async function handleEachMessage(payload, opts, deps) {
2384
2710
  if (opts.deduplication) {
2385
2711
  const isDuplicate = await applyDeduplication(
2386
2712
  envelope,
2387
- message.value.toString(),
2713
+ rawBytes,
2388
2714
  opts.deduplication,
2389
2715
  dlq,
2390
2716
  deps
@@ -2401,7 +2727,7 @@ async function handleEachMessage(payload, opts, deps) {
2401
2727
  `[KafkaClient] TTL expired on ${topic2}: age ${ageMs}ms > ${opts.messageTtlMs}ms`
2402
2728
  );
2403
2729
  if (dlq) {
2404
- await sendToDlq(topic2, message.value.toString(), deps, {
2730
+ await sendToDlq(topic2, rawBytes, deps, {
2405
2731
  error: new Error(`Message TTL expired: age ${ageMs}ms`),
2406
2732
  attempt: 0,
2407
2733
  originalHeaders: envelope.headers
@@ -2433,7 +2759,7 @@ async function handleEachMessage(payload, opts, deps) {
2433
2759
  },
2434
2760
  {
2435
2761
  envelope,
2436
- rawMessages: [message.value.toString()],
2762
+ rawMessages: [rawBytes],
2437
2763
  interceptors,
2438
2764
  dlq,
2439
2765
  retry,
@@ -2446,6 +2772,7 @@ async function handleEachBatch(payload, opts, deps) {
2446
2772
  const { batch, heartbeat, resolveOffset, commitOffsetsIfNecessary } = payload;
2447
2773
  const {
2448
2774
  schemaMap,
2775
+ serdeMap,
2449
2776
  handleBatch,
2450
2777
  interceptors,
2451
2778
  dlq,
@@ -2501,6 +2828,7 @@ async function handleEachBatch(payload, opts, deps) {
2501
2828
  const envelopes = [];
2502
2829
  const rawMessages = [];
2503
2830
  for (const message of batch.messages) {
2831
+ const rawBytes = message.value;
2504
2832
  const envelope = await parseSingleMessage(
2505
2833
  message,
2506
2834
  batch.topic,
@@ -2508,14 +2836,14 @@ async function handleEachBatch(payload, opts, deps) {
2508
2836
  schemaMap,
2509
2837
  interceptors,
2510
2838
  dlq,
2511
- deps
2839
+ deps,
2840
+ serdeMap
2512
2841
  );
2513
2842
  if (envelope === null) continue;
2514
2843
  if (opts.deduplication) {
2515
- const raw = message.value.toString();
2516
2844
  const isDuplicate = await applyDeduplication(
2517
2845
  envelope,
2518
- raw,
2846
+ rawBytes,
2519
2847
  opts.deduplication,
2520
2848
  dlq,
2521
2849
  deps
@@ -2529,7 +2857,7 @@ async function handleEachBatch(payload, opts, deps) {
2529
2857
  `[KafkaClient] TTL expired on ${batch.topic}: age ${ageMs}ms > ${opts.messageTtlMs}ms`
2530
2858
  );
2531
2859
  if (dlq) {
2532
- await sendToDlq(batch.topic, message.value.toString(), deps, {
2860
+ await sendToDlq(batch.topic, rawBytes, deps, {
2533
2861
  error: new Error(`Message TTL expired: age ${ageMs}ms`),
2534
2862
  attempt: 0,
2535
2863
  originalHeaders: envelope.headers
@@ -2548,7 +2876,7 @@ async function handleEachBatch(payload, opts, deps) {
2548
2876
  }
2549
2877
  }
2550
2878
  envelopes.push(envelope);
2551
- rawMessages.push(message.value.toString());
2879
+ rawMessages.push(rawBytes);
2552
2880
  }
2553
2881
  if (envelopes.length === 0) {
2554
2882
  await commitBatchOffset?.();
@@ -2711,7 +3039,7 @@ function resumeTopicAllPartitions(ctx, gid, topic2) {
2711
3039
  async function startConsumerImpl(ctx, topics, handleMessage, options = {}) {
2712
3040
  validateTopicConsumerOpts(topics, options);
2713
3041
  const setupOptions = options.retryTopics ? { ...options, autoCommit: false } : options;
2714
- const { consumer, schemaMap, topicNames, gid, dlq, interceptors, retry, readyPromise } = await setupConsumer(ctx, topics, "eachMessage", setupOptions);
3042
+ const { consumer, schemaMap, serdeMap, topicNames, gid, dlq, interceptors, retry, readyPromise } = await setupConsumer(ctx, topics, "eachMessage", setupOptions);
2715
3043
  if (options.circuitBreaker)
2716
3044
  ctx.circuitBreaker.setConfig(gid, options.circuitBreaker);
2717
3045
  const deps = messageDepsFor(ctx, gid, options);
@@ -2722,6 +3050,7 @@ async function startConsumerImpl(ctx, topics, handleMessage, options = {}) {
2722
3050
  payload,
2723
3051
  {
2724
3052
  schemaMap,
3053
+ serdeMap,
2725
3054
  handleMessage,
2726
3055
  interceptors,
2727
3056
  dlq,
@@ -2749,6 +3078,7 @@ async function startConsumerImpl(ctx, topics, handleMessage, options = {}) {
2749
3078
  dlq,
2750
3079
  interceptors,
2751
3080
  schemaMap,
3081
+ serdeMap,
2752
3082
  assignmentTimeoutMs: options.retryTopicAssignmentTimeoutMs
2753
3083
  });
2754
3084
  }
@@ -2762,7 +3092,7 @@ async function startBatchConsumerImpl(ctx, topics, handleBatch, options = {}) {
2762
3092
  );
2763
3093
  }
2764
3094
  const setupOptions = options.retryTopics ? { ...options, autoCommit: false } : options;
2765
- const { consumer, schemaMap, topicNames, gid, dlq, interceptors, retry, readyPromise } = await setupConsumer(ctx, topics, "eachBatch", setupOptions);
3095
+ const { consumer, schemaMap, serdeMap, topicNames, gid, dlq, interceptors, retry, readyPromise } = await setupConsumer(ctx, topics, "eachBatch", setupOptions);
2766
3096
  if (options.circuitBreaker)
2767
3097
  ctx.circuitBreaker.setConfig(gid, options.circuitBreaker);
2768
3098
  const deps = messageDepsFor(ctx, gid, options);
@@ -2773,6 +3103,7 @@ async function startBatchConsumerImpl(ctx, topics, handleBatch, options = {}) {
2773
3103
  payload,
2774
3104
  {
2775
3105
  schemaMap,
3106
+ serdeMap,
2776
3107
  handleBatch,
2777
3108
  interceptors,
2778
3109
  dlq,
@@ -2810,6 +3141,7 @@ async function startBatchConsumerImpl(ctx, topics, handleBatch, options = {}) {
2810
3141
  dlq,
2811
3142
  interceptors,
2812
3143
  schemaMap,
3144
+ serdeMap,
2813
3145
  assignmentTimeoutMs: options.retryTopicAssignmentTimeoutMs
2814
3146
  });
2815
3147
  }
@@ -2822,7 +3154,7 @@ async function startTransactionalConsumerImpl(ctx, topics, handler, options = {}
2822
3154
  );
2823
3155
  }
2824
3156
  const setupOptions = { ...options, autoCommit: false };
2825
- const { consumer, schemaMap, gid, readyPromise } = await setupConsumer(
3157
+ const { consumer, schemaMap, serdeMap, gid, readyPromise } = await setupConsumer(
2826
3158
  ctx,
2827
3159
  topics,
2828
3160
  "eachMessage",
@@ -2839,7 +3171,8 @@ async function startTransactionalConsumerImpl(ctx, topics, handler, options = {}
2839
3171
  schemaMap,
2840
3172
  options.interceptors ?? [],
2841
3173
  false,
2842
- deps
3174
+ deps,
3175
+ serdeMap
2843
3176
  );
2844
3177
  const nextOffset = String(Number.parseInt(message.offset, 10) + 1);
2845
3178
  if (envelope === null) {
@@ -2908,7 +3241,7 @@ function stopConsumerByGid(ctx, gid) {
2908
3241
  return stopConsumerImpl(ctx, gid);
2909
3242
  }
2910
3243
 
2911
- // src/client/kafka.client/consumer/window.ts
3244
+ // src/client/kafka.client/consumer/features/window.ts
2912
3245
  async function startWindowConsumerImpl(ctx, topic2, handler, options) {
2913
3246
  const { maxMessages, maxMs, ...consumerOptions } = options;
2914
3247
  if (maxMessages <= 0)
@@ -2922,6 +3255,7 @@ async function startWindowConsumerImpl(ctx, topic2, handler, options) {
2922
3255
  const buffer = [];
2923
3256
  let flushTimer = null;
2924
3257
  let windowStart = 0;
3258
+ const onLost = consumerOptions.onMessageLost ?? ctx.onMessageLost;
2925
3259
  const flush = async (trigger) => {
2926
3260
  if (flushTimer !== null) {
2927
3261
  clearTimeout(flushTimer);
@@ -2929,17 +3263,32 @@ async function startWindowConsumerImpl(ctx, topic2, handler, options) {
2929
3263
  }
2930
3264
  if (buffer.length === 0) return;
2931
3265
  const envelopes = buffer.splice(0);
2932
- await handler(envelopes, { trigger, windowStart, windowEnd: Date.now() });
3266
+ try {
3267
+ await handler(envelopes, { trigger, windowStart, windowEnd: Date.now() });
3268
+ } catch (err) {
3269
+ const error = toError(err);
3270
+ ctx.logger.error(
3271
+ `startWindowConsumer: ${trigger}-triggered flush failed \u2014 window of ${envelopes.length} message(s) lost:`,
3272
+ error.stack
3273
+ );
3274
+ for (const envelope of envelopes) {
3275
+ await Promise.resolve(
3276
+ onLost?.({
3277
+ topic: envelope.topic,
3278
+ error,
3279
+ attempt: 0,
3280
+ headers: envelope.headers
3281
+ })
3282
+ ).catch(() => {
3283
+ });
3284
+ }
3285
+ }
2933
3286
  };
2934
3287
  const scheduleFlush = () => {
2935
3288
  if (flushTimer !== null) return;
2936
3289
  flushTimer = setTimeout(() => {
2937
3290
  flushTimer = null;
2938
- flush("time").catch((err) => {
2939
- ctx.logger.warn(
2940
- `startWindowConsumer: time-triggered flush error \u2014 ${toError(err).message}`
2941
- );
2942
- });
3291
+ void flush("time");
2943
3292
  }, maxMs);
2944
3293
  };
2945
3294
  const handle = await startConsumerImpl(
@@ -2955,40 +3304,13 @@ async function startWindowConsumerImpl(ctx, topic2, handler, options) {
2955
3304
  );
2956
3305
  const originalStop = handle.stop.bind(handle);
2957
3306
  handle.stop = async () => {
2958
- if (flushTimer !== null) {
2959
- clearTimeout(flushTimer);
2960
- flushTimer = null;
2961
- }
2962
- if (buffer.length > 0) {
2963
- const envelopes = buffer.splice(0);
2964
- await handler(envelopes, {
2965
- trigger: "time",
2966
- windowStart,
2967
- windowEnd: Date.now()
2968
- }).catch(async (err) => {
2969
- const error = toError(err);
2970
- ctx.logger.warn(
2971
- `startWindowConsumer: shutdown flush error \u2014 ${error.message}`
2972
- );
2973
- for (const envelope of envelopes) {
2974
- await Promise.resolve(
2975
- ctx.onMessageLost?.({
2976
- topic: envelope.topic,
2977
- error,
2978
- attempt: 0,
2979
- headers: envelope.headers
2980
- })
2981
- ).catch(() => {
2982
- });
2983
- }
2984
- });
2985
- }
3307
+ await flush("time");
2986
3308
  return originalStop();
2987
3309
  };
2988
3310
  return handle;
2989
3311
  }
2990
3312
 
2991
- // src/client/kafka.client/consumer/routed.ts
3313
+ // src/client/kafka.client/consumer/features/routed.ts
2992
3314
  async function startRoutedConsumerImpl(ctx, topics, routing, options) {
2993
3315
  const { header, routes, fallback } = routing;
2994
3316
  const handleMessage = async (envelope) => {
@@ -3003,7 +3325,120 @@ async function startRoutedConsumerImpl(ctx, topics, routing, options) {
3003
3325
  return startConsumerImpl(ctx, topics, handleMessage, options);
3004
3326
  }
3005
3327
 
3006
- // src/client/kafka.client/consumer/snapshot.ts
3328
+ // src/client/kafka.client/consumer/features/delayed.ts
3329
+ function delayedTopicName(topic2) {
3330
+ return `${topic2}.delayed`;
3331
+ }
3332
+ async function startDelayedRelayImpl(ctx, topics, options) {
3333
+ if (topics.length === 0) {
3334
+ throw new Error("startDelayedRelay: at least one topic is required");
3335
+ }
3336
+ const gid = options?.groupId ?? `${ctx.defaultGroupId}-delayed-relay`;
3337
+ if (ctx.runningConsumers.has(gid)) {
3338
+ throw new Error(
3339
+ `startDelayedRelay("${gid}") called twice \u2014 this group is already consuming. Call stopConsumer("${gid}") first or pass a different groupId.`
3340
+ );
3341
+ }
3342
+ const delayedTopics = topics.map(delayedTopicName);
3343
+ for (const t of delayedTopics) await ensureTopic(ctx, t);
3344
+ const txProducer = await createRetryTxProducer(ctx, `${gid}-tx`);
3345
+ let resolveReady;
3346
+ const readyPromise = new Promise((resolve) => {
3347
+ resolveReady = resolve;
3348
+ });
3349
+ const consumer = getOrCreateConsumer(
3350
+ gid,
3351
+ false,
3352
+ false,
3353
+ ctx.consumerOpsDeps,
3354
+ void 0,
3355
+ resolveReady
3356
+ );
3357
+ await consumer.connect();
3358
+ await subscribeWithRetry(consumer, delayedTopics, ctx.logger);
3359
+ await consumer.run({
3360
+ eachMessage: async ({ topic: stagingTopic, partition, message }) => {
3361
+ const nextOffset = {
3362
+ topic: stagingTopic,
3363
+ partition,
3364
+ offset: (parseInt(message.offset, 10) + 1).toString()
3365
+ };
3366
+ if (!message.value) {
3367
+ await consumer.commitOffsets([nextOffset]);
3368
+ return;
3369
+ }
3370
+ const headers = decodeHeaders(message.headers);
3371
+ const target = headers[HEADER_DELAYED_TARGET] ?? stagingTopic.replace(/\.delayed$/, "");
3372
+ const until = parseInt(
3373
+ headers[HEADER_DELAYED_UNTIL] ?? "0",
3374
+ 10
3375
+ );
3376
+ const remaining = until - Date.now();
3377
+ if (remaining > 0) {
3378
+ consumer.pause([{ topic: stagingTopic, partitions: [partition] }]);
3379
+ await sleep(remaining);
3380
+ consumer.resume([{ topic: stagingTopic, partitions: [partition] }]);
3381
+ }
3382
+ const forwardHeaders = Object.fromEntries(
3383
+ Object.entries(headers).filter(
3384
+ ([k]) => k !== HEADER_DELAYED_UNTIL && k !== HEADER_DELAYED_TARGET
3385
+ )
3386
+ );
3387
+ const tx = await txProducer.transaction();
3388
+ try {
3389
+ await tx.send({
3390
+ topic: target,
3391
+ messages: [
3392
+ {
3393
+ // Forward the ORIGINAL wire bytes unchanged — no re-serialization,
3394
+ // so binary payloads (Avro/Protobuf) are relayed losslessly.
3395
+ value: message.value,
3396
+ key: message.key ? message.key.toString() : null,
3397
+ headers: forwardHeaders
3398
+ }
3399
+ ]
3400
+ });
3401
+ await tx.sendOffsets({
3402
+ consumer,
3403
+ topics: [
3404
+ {
3405
+ topic: nextOffset.topic,
3406
+ partitions: [
3407
+ { partition: nextOffset.partition, offset: nextOffset.offset }
3408
+ ]
3409
+ }
3410
+ ]
3411
+ });
3412
+ await tx.commit();
3413
+ ctx.logger.debug?.(
3414
+ `Delayed message relayed to "${target}" (deadline ${new Date(until).toISOString()})`
3415
+ );
3416
+ } catch (txErr) {
3417
+ try {
3418
+ await tx.abort();
3419
+ } catch {
3420
+ }
3421
+ ctx.logger.error(
3422
+ `Delayed relay to "${target}" failed \u2014 message will be redelivered:`,
3423
+ toError(txErr).stack
3424
+ );
3425
+ }
3426
+ }
3427
+ });
3428
+ ctx.runningConsumers.set(gid, "eachMessage");
3429
+ ctx.logger.log(
3430
+ `Delayed relay started for: ${delayedTopics.join(", ")} (group: ${gid})`
3431
+ );
3432
+ return {
3433
+ groupId: gid,
3434
+ ready: () => readyPromise,
3435
+ stop: async () => {
3436
+ await stopConsumerImpl(ctx, gid);
3437
+ }
3438
+ };
3439
+ }
3440
+
3441
+ // src/client/kafka.client/consumer/features/snapshot.ts
3007
3442
  async function readSnapshotImpl(ctx, topic2, options = {}) {
3008
3443
  await ctx.adminOps.ensureConnected();
3009
3444
  let offsets;
@@ -3269,6 +3704,7 @@ var KafkaClient = class {
3269
3704
  * ```
3270
3705
  */
3271
3706
  constructor(clientId, groupId, brokers, options) {
3707
+ validateClientOptions(clientId, groupId, brokers, options);
3272
3708
  this.clientId = clientId;
3273
3709
  const logger = options?.logger ?? {
3274
3710
  log: (msg) => console.log(`[KafkaClient:${clientId}] ${msg}`),
@@ -3276,12 +3712,14 @@ var KafkaClient = class {
3276
3712
  error: (msg, ...args) => console.error(`[KafkaClient:${clientId}] ${msg}`, ...args),
3277
3713
  debug: (msg, ...args) => console.debug(`[KafkaClient:${clientId}] ${msg}`, ...args)
3278
3714
  };
3279
- const transport = options?.transport ?? new ConfluentTransport(clientId, brokers);
3715
+ const security = resolveSecurityOptions(options?.security, brokers, logger);
3716
+ const transport = options?.transport ?? new ConfluentTransport(clientId, brokers, security);
3280
3717
  const producer = transport.producer();
3281
3718
  const runningConsumers = /* @__PURE__ */ new Map();
3282
3719
  const consumers = /* @__PURE__ */ new Map();
3283
3720
  const consumerCreationOptions = /* @__PURE__ */ new Map();
3284
3721
  const schemaRegistry = /* @__PURE__ */ new Map();
3722
+ const serde = options?.serde ?? new JsonSerde();
3285
3723
  const adminOps = new AdminOps({
3286
3724
  admin: transport.admin(),
3287
3725
  logger,
@@ -3308,8 +3746,10 @@ var KafkaClient = class {
3308
3746
  autoCreateTopicsEnabled: options?.autoCreateTopics ?? false,
3309
3747
  strictSchemasEnabled: options?.strictSchemas ?? true,
3310
3748
  numPartitions: options?.numPartitions ?? 1,
3749
+ serde,
3311
3750
  txId: options?.transactionalId ?? `${clientId}-tx`,
3312
3751
  clockRecoveryTopics: options?.clockRecovery?.topics ?? [],
3752
+ clockRecoveryTimeoutMs: options?.clockRecovery?.timeoutMs ?? 3e4,
3313
3753
  lagThrottleOpts: options?.lagThrottle,
3314
3754
  instrumentation: options?.instrumentation ?? [],
3315
3755
  onMessageLost: options?.onMessageLost,
@@ -3319,6 +3759,7 @@ var KafkaClient = class {
3319
3759
  producer,
3320
3760
  txProducer: void 0,
3321
3761
  txProducerInitPromise: void 0,
3762
+ _txChain: Promise.resolve(),
3322
3763
  retryTxProducers: /* @__PURE__ */ new Map(),
3323
3764
  consumers,
3324
3765
  runningConsumers,
@@ -3340,6 +3781,7 @@ var KafkaClient = class {
3340
3781
  strictSchemasEnabled: options?.strictSchemas ?? true,
3341
3782
  instrumentation: options?.instrumentation ?? [],
3342
3783
  logger,
3784
+ serde,
3343
3785
  nextLamportClock: () => 0
3344
3786
  // patched below
3345
3787
  },
@@ -3358,6 +3800,7 @@ var KafkaClient = class {
3358
3800
  strictSchemasEnabled: options?.strictSchemas ?? true,
3359
3801
  instrumentation: options?.instrumentation ?? [],
3360
3802
  logger,
3803
+ serde,
3361
3804
  nextLamportClock: () => ++ctx._lamportClock
3362
3805
  };
3363
3806
  ctx.retryTopicDeps = buildRetryTopicDeps(ctx);
@@ -3442,6 +3885,31 @@ var KafkaClient = class {
3442
3885
  startRoutedConsumer(topics, routing, options) {
3443
3886
  return startRoutedConsumerImpl(this.ctx, topics, routing, options);
3444
3887
  }
3888
+ // ── Consumer: delayed delivery relay ──────────────────────────────
3889
+ /**
3890
+ * Start a relay that delivers messages produced with
3891
+ * `SendOptions.deliverAfterMs` from `<topic>.delayed` to their target topic
3892
+ * once their deadline passes.
3893
+ *
3894
+ * Forwarding is transactional (produce + source-offset commit are atomic),
3895
+ * so no duplicates are relayed even if the relay crashes mid-forward.
3896
+ * Delivery time is a lower bound — the relay must be running for delayed
3897
+ * messages to be delivered at all.
3898
+ *
3899
+ * @param topics Target topic name(s) whose `<topic>.delayed` staging topics to relay.
3900
+ * @param options Optional `groupId` override (default: `<defaultGroupId>-delayed-relay`).
3901
+ *
3902
+ * @example
3903
+ * ```ts
3904
+ * await kafka.startDelayedRelay(['orders.reminder']);
3905
+ * await kafka.sendMessage('orders.reminder', payload, { deliverAfterMs: 60_000 });
3906
+ * // → delivered to orders.reminder ~60 s later
3907
+ * ```
3908
+ */
3909
+ async startDelayedRelay(topics, options) {
3910
+ const list = Array.isArray(topics) ? topics : [topics];
3911
+ return startDelayedRelayImpl(this.ctx, list, options);
3912
+ }
3445
3913
  // ── Consumer: transactional EOS ───────────────────────────────────
3446
3914
  /** @inheritDoc */
3447
3915
  async startTransactionalConsumer(topics, handler, options = {}) {
@@ -3587,34 +4055,909 @@ var KafkaClient = class {
3587
4055
  function topic(name) {
3588
4056
  return {
3589
4057
  /** Provide an explicit message type without a runtime schema. */
3590
- type: () => ({
4058
+ type: () => keyable({
3591
4059
  __topic: name,
3592
4060
  __type: void 0
3593
4061
  }),
3594
- schema: (schema) => ({
4062
+ schema: (schema) => keyable({
3595
4063
  __topic: name,
3596
4064
  __type: void 0,
3597
4065
  __schema: schema
3598
4066
  })
3599
4067
  };
3600
4068
  }
4069
+ function keyable(desc) {
4070
+ return {
4071
+ ...desc,
4072
+ key: (extractor) => keyable({ ...desc, __key: extractor }),
4073
+ serde: (serde) => keyable({ ...desc, __serde: serde })
4074
+ };
4075
+ }
4076
+
4077
+ // src/client/message/versioned-schema.ts
4078
+ function versionedSchema(versions, options) {
4079
+ const registered = Object.keys(versions).map(Number).filter((v) => Number.isInteger(v) && v > 0).sort((a, b) => a - b);
4080
+ if (registered.length === 0) {
4081
+ throw new Error(
4082
+ "versionedSchema: at least one schema version must be registered (keys must be positive integers)"
4083
+ );
4084
+ }
4085
+ const latestVersion = registered[registered.length - 1];
4086
+ return {
4087
+ async parse(data, ctx) {
4088
+ const version = ctx?.version ?? latestVersion;
4089
+ const schema = versions[version];
4090
+ if (!schema) {
4091
+ throw new Error(
4092
+ `versionedSchema: no schema registered for version ${version}${ctx?.topic ? ` (topic "${ctx.topic}")` : ""} \u2014 registered versions: ${registered.join(", ")}`
4093
+ );
4094
+ }
4095
+ const parsed = await schema.parse(data, ctx);
4096
+ if (version < latestVersion && options?.migrate) {
4097
+ return options.migrate(parsed, version, latestVersion);
4098
+ }
4099
+ return parsed;
4100
+ }
4101
+ };
4102
+ }
4103
+
4104
+ // src/client/message/schema-registry.ts
4105
+ var SchemaRegistryClient = class {
4106
+ constructor(options) {
4107
+ this.options = options;
4108
+ if (!options.baseUrl) {
4109
+ throw new Error("SchemaRegistryClient: baseUrl is required");
4110
+ }
4111
+ this.fetchFn = options.fetchFn ?? fetch;
4112
+ this.cacheTtlMs = options.cacheTtlMs ?? 3e5;
4113
+ }
4114
+ options;
4115
+ fetchFn;
4116
+ cacheTtlMs;
4117
+ latestCache = /* @__PURE__ */ new Map();
4118
+ /**
4119
+ * `id → schema` cache. Schema ids are immutable in a Confluent-compatible
4120
+ * registry (a given id always maps to the same schema string), so entries
4121
+ * are cached for the lifetime of the client with no TTL.
4122
+ */
4123
+ byIdCache = /* @__PURE__ */ new Map();
4124
+ headers() {
4125
+ const h = {
4126
+ "Content-Type": "application/vnd.schemaregistry.v1+json"
4127
+ };
4128
+ if (this.options.auth) {
4129
+ const { username, password } = this.options.auth;
4130
+ h["Authorization"] = "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
4131
+ }
4132
+ return h;
4133
+ }
4134
+ async request(method, path, body) {
4135
+ const url = `${this.options.baseUrl.replace(/\/$/, "")}${path}`;
4136
+ const res = await this.fetchFn(url, {
4137
+ method,
4138
+ headers: this.headers(),
4139
+ ...body !== void 0 && { body: JSON.stringify(body) }
4140
+ });
4141
+ if (!res.ok) {
4142
+ const text = await res.text().catch(() => "");
4143
+ throw new Error(
4144
+ `SchemaRegistry ${method} ${path} failed: ${res.status} ${res.statusText}${text ? ` \u2014 ${text}` : ""}`
4145
+ );
4146
+ }
4147
+ return await res.json();
4148
+ }
4149
+ /** Fetch the latest schema registered under `subject`. Cached for `cacheTtlMs`. */
4150
+ async getLatestSchema(subject) {
4151
+ const cached = this.latestCache.get(subject);
4152
+ if (cached && cached.expiresAt > Date.now()) return cached.value;
4153
+ const raw = await this.request("GET", `/subjects/${encodeURIComponent(subject)}/versions/latest`);
4154
+ const value = {
4155
+ id: raw.id,
4156
+ version: raw.version,
4157
+ schema: raw.schema
4158
+ };
4159
+ this.latestCache.set(subject, {
4160
+ value,
4161
+ expiresAt: Date.now() + this.cacheTtlMs
4162
+ });
4163
+ return value;
4164
+ }
4165
+ /**
4166
+ * Fetch a schema by its globally unique registry id (`GET /schemas/ids/{id}`).
4167
+ *
4168
+ * Used by the Avro/Protobuf serdes on the deserialize path: the writer schema
4169
+ * id is read from the Confluent wire-format prefix, then resolved here. Results
4170
+ * are cached forever (schema ids are immutable), so a given id triggers exactly
4171
+ * one registry round-trip regardless of how many messages reference it.
4172
+ */
4173
+ async getSchemaById(id) {
4174
+ const cached = this.byIdCache.get(id);
4175
+ if (cached) return cached;
4176
+ const raw = await this.request(
4177
+ "GET",
4178
+ `/schemas/ids/${id}`
4179
+ );
4180
+ const value = { id, schema: raw.schema, schemaType: raw.schemaType };
4181
+ this.byIdCache.set(id, value);
4182
+ return value;
4183
+ }
4184
+ /** Fetch a specific schema version of a subject. */
4185
+ async getSchemaVersion(subject, version) {
4186
+ const raw = await this.request(
4187
+ "GET",
4188
+ `/subjects/${encodeURIComponent(subject)}/versions/${version}`
4189
+ );
4190
+ return { id: raw.id, version: raw.version, schema: raw.schema };
4191
+ }
4192
+ /**
4193
+ * Register a schema under `subject` (idempotent — re-registering the same
4194
+ * schema returns the existing id). Returns the registry-assigned schema id.
4195
+ */
4196
+ async registerSchema(subject, schema, schemaType = "JSON") {
4197
+ this.latestCache.delete(subject);
4198
+ return this.request(
4199
+ "POST",
4200
+ `/subjects/${encodeURIComponent(subject)}/versions`,
4201
+ { schema, schemaType }
4202
+ );
4203
+ }
4204
+ /**
4205
+ * Test `schema` against the subject's compatibility policy without registering.
4206
+ * Returns `true` when the registry reports the schema as compatible.
4207
+ */
4208
+ async checkCompatibility(subject, schema, schemaType = "JSON") {
4209
+ const res = await this.request(
4210
+ "POST",
4211
+ `/compatibility/subjects/${encodeURIComponent(subject)}/versions/latest`,
4212
+ { schema, schemaType }
4213
+ );
4214
+ return res.is_compatible;
4215
+ }
4216
+ };
4217
+ function registrySchema(client, subject, options) {
4218
+ const enforceVersion = options?.enforceVersion ?? true;
4219
+ return {
4220
+ async parse(data, ctx) {
4221
+ const latest = await client.getLatestSchema(subject);
4222
+ if (enforceVersion && ctx?.version !== void 0 && ctx.version > latest.version) {
4223
+ throw new Error(
4224
+ `registrySchema: message version ${ctx.version} for subject "${subject}" is newer than the latest registered version ${latest.version} \u2014 register the schema before producing with it`
4225
+ );
4226
+ }
4227
+ if (options?.validator) {
4228
+ return options.validator.parse(data, ctx);
4229
+ }
4230
+ return data;
4231
+ }
4232
+ };
4233
+ }
4234
+
4235
+ // src/client/outbox/outbox.store.ts
4236
+ var InMemoryOutboxStore = class {
4237
+ /** Insertion-ordered rows. `published` flips to true after `markPublished`. */
4238
+ rows = [];
4239
+ /**
4240
+ * Append a message to the outbox. In a real store this INSERT would run inside
4241
+ * the same DB transaction as the corresponding business write.
4242
+ */
4243
+ add(message) {
4244
+ this.rows.push({ message, published: false });
4245
+ }
4246
+ async fetchUnpublished(limit) {
4247
+ const out = [];
4248
+ for (const row of this.rows) {
4249
+ if (row.published) continue;
4250
+ out.push(row.message);
4251
+ if (out.length >= limit) break;
4252
+ }
4253
+ return out;
4254
+ }
4255
+ async markPublished(ids) {
4256
+ const idSet = new Set(ids);
4257
+ for (const row of this.rows) {
4258
+ if (idSet.has(row.message.id)) row.published = true;
4259
+ }
4260
+ }
4261
+ /** Test helper: count of rows not yet marked published. */
4262
+ get pendingCount() {
4263
+ return this.rows.filter((r) => !r.published).length;
4264
+ }
4265
+ /** Test helper: count of rows marked published. */
4266
+ get publishedCount() {
4267
+ return this.rows.filter((r) => r.published).length;
4268
+ }
4269
+ };
4270
+
4271
+ // src/client/outbox/outbox.relay.ts
4272
+ function toError2(e) {
4273
+ return e instanceof Error ? e : new Error(String(e));
4274
+ }
4275
+ function startOutboxRelay(kafka, store, options = {}) {
4276
+ const pollIntervalMs = options.pollIntervalMs ?? 1e3;
4277
+ const batchSize = options.batchSize ?? 100;
4278
+ const onError = options.onError ?? ((error, batch) => {
4279
+ console.error(
4280
+ `[outbox] batch of ${batch.length} message(s) failed \u2014 will retry:`,
4281
+ error
4282
+ );
4283
+ });
4284
+ const onPublished = options.onPublished;
4285
+ let stopped = false;
4286
+ let running = false;
4287
+ let inFlight = Promise.resolve();
4288
+ const iterate = async () => {
4289
+ let batch = [];
4290
+ try {
4291
+ batch = await store.fetchUnpublished(batchSize);
4292
+ if (batch.length === 0) return;
4293
+ await kafka.transaction(async (tx) => {
4294
+ for (const msg of batch) {
4295
+ await tx.send(msg.topic, msg.payload, {
4296
+ key: msg.key,
4297
+ headers: msg.headers,
4298
+ correlationId: msg.correlationId,
4299
+ eventId: msg.eventId
4300
+ });
4301
+ }
4302
+ });
4303
+ await store.markPublished(batch.map((m) => m.id));
4304
+ onPublished?.(batch.length);
4305
+ } catch (err) {
4306
+ onError(toError2(err), batch);
4307
+ }
4308
+ };
4309
+ const tick = () => {
4310
+ if (stopped || running) return;
4311
+ running = true;
4312
+ inFlight = iterate().finally(() => {
4313
+ running = false;
4314
+ });
4315
+ };
4316
+ const timer = setInterval(tick, pollIntervalMs);
4317
+ timer.unref?.();
4318
+ return {
4319
+ stop: async () => {
4320
+ stopped = true;
4321
+ clearInterval(timer);
4322
+ await inFlight;
4323
+ }
4324
+ };
4325
+ }
4326
+
4327
+ // src/client/security/providers.ts
4328
+ var defaultImport = (specifier) => import(specifier);
4329
+ function awsMskIamProvider(options) {
4330
+ const importFn = options.importFn ?? defaultImport;
4331
+ return async () => {
4332
+ let signer;
4333
+ try {
4334
+ signer = await importFn("aws-msk-iam-sasl-signer-js");
4335
+ } catch {
4336
+ throw new Error(
4337
+ "awsMskIamProvider: package 'aws-msk-iam-sasl-signer-js' is not installed. Run `npm install aws-msk-iam-sasl-signer-js` to enable MSK IAM authentication."
4338
+ );
4339
+ }
4340
+ const { token, expiryTime } = await signer.generateAuthToken({
4341
+ region: options.region
4342
+ });
4343
+ return {
4344
+ value: token,
4345
+ principal: "msk-iam",
4346
+ // expiryTime is epoch ms per the signer's contract
4347
+ lifetimeMs: expiryTime
4348
+ };
4349
+ };
4350
+ }
4351
+ function gcpAccessTokenProvider(options = {}) {
4352
+ const importFn = options.importFn ?? defaultImport;
4353
+ const ttlMs = options.tokenTtlMs ?? 50 * 6e4;
4354
+ return async () => {
4355
+ let lib;
4356
+ try {
4357
+ lib = await importFn("google-auth-library");
4358
+ } catch {
4359
+ throw new Error(
4360
+ "gcpAccessTokenProvider: package 'google-auth-library' is not installed. Run `npm install google-auth-library` to enable GCP authentication."
4361
+ );
4362
+ }
4363
+ const auth = new lib.GoogleAuth({
4364
+ scopes: options.scopes ?? ["https://www.googleapis.com/auth/cloud-platform"]
4365
+ });
4366
+ const token = await auth.getAccessToken();
4367
+ if (!token) {
4368
+ throw new Error(
4369
+ "gcpAccessTokenProvider: google-auth-library returned no access token \u2014 check Application Default Credentials."
4370
+ );
4371
+ }
4372
+ return {
4373
+ value: token,
4374
+ principal: options.principal ?? "gcp",
4375
+ lifetimeMs: Date.now() + ttlMs
4376
+ };
4377
+ };
4378
+ }
4379
+
4380
+ // src/client/security/acl.ts
4381
+ function addResource(out, r) {
4382
+ const key = `${r.resourceType}:${r.patternType}:${r.name}`;
4383
+ const existing = out.get(key);
4384
+ if (existing) {
4385
+ for (const op of r.operations)
4386
+ if (!existing.operations.includes(op)) existing.operations.push(op);
4387
+ if (!existing.reason.includes(r.reason))
4388
+ existing.reason += `; ${r.reason}`;
4389
+ } else {
4390
+ out.set(key, { ...r, operations: [...r.operations] });
4391
+ }
4392
+ }
4393
+ function describeRequiredAcls(input) {
4394
+ const out = /* @__PURE__ */ new Map();
4395
+ const f = input.features ?? {};
4396
+ const produce = input.produceTopics ?? [];
4397
+ const consume = input.consumeTopics ?? [];
4398
+ const groups = input.groupIds ?? [];
4399
+ for (const t of produce) {
4400
+ addResource(out, {
4401
+ resourceType: "topic",
4402
+ patternType: "literal",
4403
+ name: t,
4404
+ operations: ["WRITE", "DESCRIBE"],
4405
+ reason: "sendMessage/sendBatch"
4406
+ });
4407
+ }
4408
+ for (const t of consume) {
4409
+ addResource(out, {
4410
+ resourceType: "topic",
4411
+ patternType: "literal",
4412
+ name: t,
4413
+ operations: ["READ", "DESCRIBE"],
4414
+ reason: "startConsumer"
4415
+ });
4416
+ }
4417
+ for (const g of groups) {
4418
+ addResource(out, {
4419
+ resourceType: "group",
4420
+ patternType: "literal",
4421
+ name: g,
4422
+ operations: ["READ", "DESCRIBE"],
4423
+ reason: "consumer group membership + offset commits"
4424
+ });
4425
+ }
4426
+ if (f.dlq) {
4427
+ for (const t of consume) {
4428
+ addResource(out, {
4429
+ resourceType: "topic",
4430
+ patternType: "literal",
4431
+ name: `${t}.dlq`,
4432
+ operations: ["WRITE", "DESCRIBE"],
4433
+ reason: "dlq: true \u2014 failed messages routed to DLQ"
4434
+ });
4435
+ }
4436
+ }
4437
+ if (f.retryTopics) {
4438
+ for (const t of consume) {
4439
+ for (let level = 1; level <= f.retryTopics.maxRetries; level++) {
4440
+ addResource(out, {
4441
+ resourceType: "topic",
4442
+ patternType: "literal",
4443
+ name: `${t}.retry.${level}`,
4444
+ operations: ["READ", "WRITE", "DESCRIBE"],
4445
+ reason: "retryTopics \u2014 retry chain produce + companion consume"
4446
+ });
4447
+ }
4448
+ }
4449
+ for (const g of groups) {
4450
+ addResource(out, {
4451
+ resourceType: "group",
4452
+ patternType: "prefixed",
4453
+ name: `${g}-retry.`,
4454
+ operations: ["READ", "DESCRIBE"],
4455
+ reason: "retryTopics \u2014 companion retry-level consumer groups"
4456
+ });
4457
+ addResource(out, {
4458
+ resourceType: "transactional-id",
4459
+ patternType: "prefixed",
4460
+ name: `${g}-`,
4461
+ operations: ["WRITE", "DESCRIBE"],
4462
+ reason: "retryTopics \u2014 EOS routing transactions per retry level"
4463
+ });
4464
+ }
4465
+ }
4466
+ if (f.delayedDelivery) {
4467
+ for (const t of [.../* @__PURE__ */ new Set([...produce, ...consume])]) {
4468
+ addResource(out, {
4469
+ resourceType: "topic",
4470
+ patternType: "literal",
4471
+ name: `${t}.delayed`,
4472
+ operations: ["READ", "WRITE", "DESCRIBE"],
4473
+ reason: "deliverAfterMs staging + startDelayedRelay consume"
4474
+ });
4475
+ }
4476
+ for (const g of groups) {
4477
+ addResource(out, {
4478
+ resourceType: "group",
4479
+ patternType: "literal",
4480
+ name: `${g}-delayed-relay`,
4481
+ operations: ["READ", "DESCRIBE"],
4482
+ reason: "startDelayedRelay consumer group"
4483
+ });
4484
+ addResource(out, {
4485
+ resourceType: "transactional-id",
4486
+ patternType: "literal",
4487
+ name: `${g}-delayed-relay-tx`,
4488
+ operations: ["WRITE", "DESCRIBE"],
4489
+ reason: "startDelayedRelay transactional forwarding"
4490
+ });
4491
+ }
4492
+ }
4493
+ if (f.duplicatesTopic) {
4494
+ if (typeof f.duplicatesTopic === "string") {
4495
+ addResource(out, {
4496
+ resourceType: "topic",
4497
+ patternType: "literal",
4498
+ name: f.duplicatesTopic,
4499
+ operations: ["WRITE", "DESCRIBE"],
4500
+ reason: "deduplication.strategy 'topic' \u2014 custom duplicates topic"
4501
+ });
4502
+ } else {
4503
+ for (const t of consume) {
4504
+ addResource(out, {
4505
+ resourceType: "topic",
4506
+ patternType: "literal",
4507
+ name: `${t}.duplicates`,
4508
+ operations: ["WRITE", "DESCRIBE"],
4509
+ reason: "deduplication.strategy 'topic'"
4510
+ });
4511
+ }
4512
+ }
4513
+ }
4514
+ if (f.dlqReplay) {
4515
+ for (const t of consume) {
4516
+ addResource(out, {
4517
+ resourceType: "group",
4518
+ patternType: "prefixed",
4519
+ name: `${t}.dlq-replay`,
4520
+ operations: ["READ", "DESCRIBE", "DELETE"],
4521
+ reason: "replayDlq \u2014 ephemeral/stable replay groups (deleted after use)"
4522
+ });
4523
+ addResource(out, {
4524
+ resourceType: "topic",
4525
+ patternType: "literal",
4526
+ name: `${t}.dlq`,
4527
+ operations: ["READ", "DESCRIBE"],
4528
+ reason: "replayDlq \u2014 reads the DLQ"
4529
+ });
4530
+ }
4531
+ }
4532
+ if (f.snapshots) {
4533
+ addResource(out, {
4534
+ resourceType: "group",
4535
+ patternType: "prefixed",
4536
+ name: `${input.clientId}-snapshot-`,
4537
+ operations: ["READ", "DESCRIBE", "DELETE"],
4538
+ reason: "readSnapshot \u2014 timestamped ephemeral groups (deleted after use)"
4539
+ });
4540
+ }
4541
+ if (f.clockRecovery) {
4542
+ addResource(out, {
4543
+ resourceType: "group",
4544
+ patternType: "prefixed",
4545
+ name: `${input.clientId}-clock-recovery-`,
4546
+ operations: ["READ", "DESCRIBE", "DELETE"],
4547
+ reason: "clockRecovery \u2014 timestamped ephemeral groups (deleted after use)"
4548
+ });
4549
+ }
4550
+ if (f.transactions) {
4551
+ addResource(out, {
4552
+ resourceType: "transactional-id",
4553
+ patternType: "literal",
4554
+ name: `${input.clientId}-tx`,
4555
+ operations: ["WRITE", "DESCRIBE"],
4556
+ reason: "transaction() \u2014 default transactionalId (override-aware: adjust if you set one)"
4557
+ });
4558
+ }
4559
+ if (f.autoCreateTopics) {
4560
+ addResource(out, {
4561
+ resourceType: "cluster",
4562
+ patternType: "literal",
4563
+ name: "kafka-cluster",
4564
+ operations: ["CREATE"],
4565
+ reason: "autoCreateTopics: true \u2014 not recommended in production"
4566
+ });
4567
+ }
4568
+ return [...out.values()];
4569
+ }
4570
+ function toKafkaAclCommands(resources, principal, bootstrapServer = "<bootstrap-server>") {
4571
+ return resources.map((r) => {
4572
+ const ops = r.operations.map((o) => `--operation ${o}`).join(" ");
4573
+ const resourceFlag = r.resourceType === "topic" ? `--topic '${r.name}'` : r.resourceType === "group" ? `--group '${r.name}'` : r.resourceType === "transactional-id" ? `--transactional-id '${r.name}'` : "--cluster";
4574
+ const pattern = r.patternType === "prefixed" ? " --resource-pattern-type prefixed" : "";
4575
+ return `kafka-acls.sh --bootstrap-server ${bootstrapServer} --add --allow-principal '${principal}' ${ops} ${resourceFlag}${pattern} # ${r.reason}`;
4576
+ });
4577
+ }
4578
+ var MSK_TOPIC_ACTIONS = {
4579
+ READ: ["kafka-cluster:ReadData", "kafka-cluster:DescribeTopic"],
4580
+ WRITE: ["kafka-cluster:WriteData", "kafka-cluster:DescribeTopic"],
4581
+ DESCRIBE: ["kafka-cluster:DescribeTopic"],
4582
+ CREATE: ["kafka-cluster:CreateTopic"],
4583
+ DELETE: ["kafka-cluster:DeleteTopic"]
4584
+ };
4585
+ var MSK_GROUP_ACTIONS = {
4586
+ READ: ["kafka-cluster:AlterGroup", "kafka-cluster:DescribeGroup"],
4587
+ DESCRIBE: ["kafka-cluster:DescribeGroup"],
4588
+ DELETE: ["kafka-cluster:DeleteGroup"]
4589
+ };
4590
+ var MSK_TX_ACTIONS = {
4591
+ WRITE: [
4592
+ "kafka-cluster:AlterTransactionalId",
4593
+ "kafka-cluster:DescribeTransactionalId"
4594
+ ],
4595
+ DESCRIBE: ["kafka-cluster:DescribeTransactionalId"]
4596
+ };
4597
+ function toMskIamPolicy(resources, cluster) {
4598
+ const { region, accountId, clusterName, clusterUuid } = cluster;
4599
+ const arn = (type, name) => `arn:aws:kafka:${region}:${accountId}:${type}/${clusterName}/${clusterUuid}/${name}`;
4600
+ const statements = [
4601
+ {
4602
+ Sid: "Connect",
4603
+ Effect: "Allow",
4604
+ Action: ["kafka-cluster:Connect"],
4605
+ Resource: [
4606
+ `arn:aws:kafka:${region}:${accountId}:cluster/${clusterName}/${clusterUuid}`
4607
+ ]
4608
+ }
4609
+ ];
4610
+ let sid = 0;
4611
+ for (const r of resources) {
4612
+ const suffix = r.patternType === "prefixed" ? `${r.name}*` : r.name;
4613
+ let actions = [];
4614
+ let resource;
4615
+ if (r.resourceType === "topic") {
4616
+ actions = [...new Set(r.operations.flatMap((o) => MSK_TOPIC_ACTIONS[o] ?? []))];
4617
+ resource = arn("topic", suffix);
4618
+ } else if (r.resourceType === "group") {
4619
+ actions = [...new Set(r.operations.flatMap((o) => MSK_GROUP_ACTIONS[o] ?? []))];
4620
+ resource = arn("group", suffix);
4621
+ } else if (r.resourceType === "transactional-id") {
4622
+ actions = [...new Set(r.operations.flatMap((o) => MSK_TX_ACTIONS[o] ?? []))];
4623
+ resource = arn("transactional-id", suffix);
4624
+ } else {
4625
+ actions = ["kafka-cluster:CreateTopic"];
4626
+ resource = `arn:aws:kafka:${region}:${accountId}:topic/${clusterName}/${clusterUuid}/*`;
4627
+ }
4628
+ if (actions.length === 0 || !resource) continue;
4629
+ statements.push({
4630
+ Sid: `Acl${sid++}`,
4631
+ Effect: "Allow",
4632
+ Action: actions,
4633
+ Resource: [resource]
4634
+ });
4635
+ }
4636
+ return { Version: "2012-10-17", Statement: statements };
4637
+ }
4638
+
4639
+ // src/client/config/from-env.ts
4640
+ var TRUE_VALUES = /* @__PURE__ */ new Set(["true", "1", "yes"]);
4641
+ var FALSE_VALUES = /* @__PURE__ */ new Set(["false", "0", "no"]);
4642
+ function parseBool(name, raw) {
4643
+ const normalized = raw.trim().toLowerCase();
4644
+ if (TRUE_VALUES.has(normalized)) return true;
4645
+ if (FALSE_VALUES.has(normalized)) return false;
4646
+ throw new Error(
4647
+ `Invalid boolean for ${name}: "${raw}". Use one of true/false, 1/0, yes/no (case-insensitive).`
4648
+ );
4649
+ }
4650
+ function parseNum(name, raw) {
4651
+ const value = Number(raw.trim());
4652
+ if (Number.isNaN(value)) {
4653
+ throw new Error(`Invalid number for ${name}: "${raw}".`);
4654
+ }
4655
+ return value;
4656
+ }
4657
+ function parseList(raw) {
4658
+ return raw.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
4659
+ }
4660
+ function parseEnum(name, raw, allowed) {
4661
+ const value = raw.trim();
4662
+ if (!allowed.includes(value)) {
4663
+ throw new Error(
4664
+ `Invalid value for ${name}: "${raw}". Allowed: ${allowed.join(", ")}.`
4665
+ );
4666
+ }
4667
+ return value;
4668
+ }
4669
+ function readVar(env, key, apply) {
4670
+ const raw = env[key];
4671
+ if (raw === void 0 || raw.trim() === "") return;
4672
+ apply(raw);
4673
+ }
4674
+ function kafkaClientConfigFromEnv(env = process.env, prefix = "KAFKA_") {
4675
+ const options = {};
4676
+ const result = { options };
4677
+ readVar(env, `${prefix}CLIENT_ID`, (raw) => {
4678
+ result.clientId = raw.trim();
4679
+ });
4680
+ readVar(env, `${prefix}GROUP_ID`, (raw) => {
4681
+ result.groupId = raw.trim();
4682
+ });
4683
+ readVar(env, `${prefix}BROKERS`, (raw) => {
4684
+ result.brokers = parseList(raw);
4685
+ });
4686
+ readVar(env, `${prefix}AUTO_CREATE_TOPICS`, (raw) => {
4687
+ options.autoCreateTopics = parseBool(`${prefix}AUTO_CREATE_TOPICS`, raw);
4688
+ });
4689
+ readVar(env, `${prefix}STRICT_SCHEMAS`, (raw) => {
4690
+ options.strictSchemas = parseBool(`${prefix}STRICT_SCHEMAS`, raw);
4691
+ });
4692
+ readVar(env, `${prefix}NUM_PARTITIONS`, (raw) => {
4693
+ options.numPartitions = parseNum(`${prefix}NUM_PARTITIONS`, raw);
4694
+ });
4695
+ readVar(env, `${prefix}TRANSACTIONAL_ID`, (raw) => {
4696
+ options.transactionalId = raw.trim();
4697
+ });
4698
+ readVar(env, `${prefix}CLOCK_RECOVERY_TOPICS`, (raw) => {
4699
+ const topics = parseList(raw);
4700
+ if (topics.length === 0) return;
4701
+ options.clockRecovery = { topics };
4702
+ });
4703
+ readVar(env, `${prefix}CLOCK_RECOVERY_TIMEOUT_MS`, (raw) => {
4704
+ const timeoutMs = parseNum(`${prefix}CLOCK_RECOVERY_TIMEOUT_MS`, raw);
4705
+ if (options.clockRecovery) {
4706
+ options.clockRecovery.timeoutMs = timeoutMs;
4707
+ }
4708
+ });
4709
+ readVar(env, `${prefix}LAG_THROTTLE_MAX_LAG`, (raw) => {
4710
+ options.lagThrottle = {
4711
+ maxLag: parseNum(`${prefix}LAG_THROTTLE_MAX_LAG`, raw)
4712
+ };
4713
+ });
4714
+ readVar(env, `${prefix}LAG_THROTTLE_GROUP_ID`, (raw) => {
4715
+ if (options.lagThrottle) options.lagThrottle.groupId = raw.trim();
4716
+ });
4717
+ readVar(env, `${prefix}LAG_THROTTLE_POLL_INTERVAL_MS`, (raw) => {
4718
+ if (options.lagThrottle) {
4719
+ options.lagThrottle.pollIntervalMs = parseNum(
4720
+ `${prefix}LAG_THROTTLE_POLL_INTERVAL_MS`,
4721
+ raw
4722
+ );
4723
+ }
4724
+ });
4725
+ readVar(env, `${prefix}LAG_THROTTLE_MAX_WAIT_MS`, (raw) => {
4726
+ if (options.lagThrottle) {
4727
+ options.lagThrottle.maxWaitMs = parseNum(
4728
+ `${prefix}LAG_THROTTLE_MAX_WAIT_MS`,
4729
+ raw
4730
+ );
4731
+ }
4732
+ });
4733
+ const security = securityFromEnv(env, prefix);
4734
+ if (security) options.security = security;
4735
+ return result;
4736
+ }
4737
+ function securityFromEnv(env, prefix) {
4738
+ let ssl;
4739
+ let allowInsecure;
4740
+ let mechanism;
4741
+ let username;
4742
+ let password;
4743
+ readVar(env, `${prefix}SSL`, (raw) => {
4744
+ ssl = parseBool(`${prefix}SSL`, raw);
4745
+ });
4746
+ readVar(env, `${prefix}ALLOW_INSECURE`, (raw) => {
4747
+ allowInsecure = parseBool(`${prefix}ALLOW_INSECURE`, raw);
4748
+ });
4749
+ readVar(env, `${prefix}SASL_MECHANISM`, (raw) => {
4750
+ mechanism = parseEnum(`${prefix}SASL_MECHANISM`, raw, [
4751
+ "plain",
4752
+ "scram-sha-256",
4753
+ "scram-sha-512"
4754
+ ]);
4755
+ });
4756
+ readVar(env, `${prefix}SASL_USERNAME`, (raw) => {
4757
+ username = raw.trim();
4758
+ });
4759
+ readVar(env, `${prefix}SASL_PASSWORD`, (raw) => {
4760
+ password = raw;
4761
+ });
4762
+ if (ssl === void 0 && allowInsecure === void 0 && mechanism === void 0 && username === void 0 && password === void 0) {
4763
+ return void 0;
4764
+ }
4765
+ const security = {};
4766
+ if (ssl !== void 0) security.ssl = ssl;
4767
+ if (allowInsecure !== void 0) security.allowInsecure = allowInsecure;
4768
+ if (mechanism !== void 0 || username !== void 0 || password !== void 0) {
4769
+ if (mechanism === void 0 || username === void 0 || password === void 0) {
4770
+ throw new Error(
4771
+ `Incomplete SASL configuration: ${prefix}SASL_MECHANISM, ${prefix}SASL_USERNAME, and ${prefix}SASL_PASSWORD must all be set together (oauthbearer must be configured in code).`
4772
+ );
4773
+ }
4774
+ const sasl = { mechanism, username, password };
4775
+ security.sasl = sasl;
4776
+ }
4777
+ return security;
4778
+ }
4779
+ function consumerOptionsFromEnv(env = process.env, prefix = "KAFKA_CONSUMER_") {
4780
+ const options = {};
4781
+ readVar(env, `${prefix}GROUP_ID`, (raw) => {
4782
+ options.groupId = raw.trim();
4783
+ });
4784
+ readVar(env, `${prefix}FROM_BEGINNING`, (raw) => {
4785
+ options.fromBeginning = parseBool(`${prefix}FROM_BEGINNING`, raw);
4786
+ });
4787
+ readVar(env, `${prefix}AUTO_COMMIT`, (raw) => {
4788
+ options.autoCommit = parseBool(`${prefix}AUTO_COMMIT`, raw);
4789
+ });
4790
+ readVar(env, `${prefix}DLQ`, (raw) => {
4791
+ options.dlq = parseBool(`${prefix}DLQ`, raw);
4792
+ });
4793
+ readVar(env, `${prefix}RETRY_MAX_RETRIES`, (raw) => {
4794
+ const retry = {
4795
+ maxRetries: parseNum(`${prefix}RETRY_MAX_RETRIES`, raw)
4796
+ };
4797
+ options.retry = retry;
4798
+ });
4799
+ readVar(env, `${prefix}RETRY_BACKOFF_MS`, (raw) => {
4800
+ if (options.retry) {
4801
+ options.retry.backoffMs = parseNum(`${prefix}RETRY_BACKOFF_MS`, raw);
4802
+ }
4803
+ });
4804
+ readVar(env, `${prefix}RETRY_MAX_BACKOFF_MS`, (raw) => {
4805
+ if (options.retry) {
4806
+ options.retry.maxBackoffMs = parseNum(`${prefix}RETRY_MAX_BACKOFF_MS`, raw);
4807
+ }
4808
+ });
4809
+ readVar(env, `${prefix}RETRY_TOPICS`, (raw) => {
4810
+ options.retryTopics = parseBool(`${prefix}RETRY_TOPICS`, raw);
4811
+ });
4812
+ readVar(env, `${prefix}RETRY_TOPIC_ASSIGNMENT_TIMEOUT_MS`, (raw) => {
4813
+ options.retryTopicAssignmentTimeoutMs = parseNum(
4814
+ `${prefix}RETRY_TOPIC_ASSIGNMENT_TIMEOUT_MS`,
4815
+ raw
4816
+ );
4817
+ });
4818
+ readVar(env, `${prefix}HANDLER_TIMEOUT_MS`, (raw) => {
4819
+ options.handlerTimeoutMs = parseNum(`${prefix}HANDLER_TIMEOUT_MS`, raw);
4820
+ });
4821
+ readVar(env, `${prefix}MESSAGE_TTL_MS`, (raw) => {
4822
+ options.messageTtlMs = parseNum(`${prefix}MESSAGE_TTL_MS`, raw);
4823
+ });
4824
+ readVar(env, `${prefix}DEDUPLICATION_STRATEGY`, (raw) => {
4825
+ const strategy = parseEnum(`${prefix}DEDUPLICATION_STRATEGY`, raw, [
4826
+ "drop",
4827
+ "dlq",
4828
+ "topic"
4829
+ ]);
4830
+ const dedup = { strategy };
4831
+ options.deduplication = dedup;
4832
+ });
4833
+ readVar(env, `${prefix}DEDUPLICATION_TOPIC`, (raw) => {
4834
+ if (options.deduplication) {
4835
+ options.deduplication.duplicatesTopic = raw.trim();
4836
+ }
4837
+ });
4838
+ readVar(env, `${prefix}CIRCUIT_BREAKER_THRESHOLD`, (raw) => {
4839
+ const cb = {
4840
+ threshold: parseNum(`${prefix}CIRCUIT_BREAKER_THRESHOLD`, raw)
4841
+ };
4842
+ options.circuitBreaker = cb;
4843
+ });
4844
+ readVar(env, `${prefix}CIRCUIT_BREAKER_RECOVERY_MS`, (raw) => {
4845
+ if (options.circuitBreaker) {
4846
+ options.circuitBreaker.recoveryMs = parseNum(
4847
+ `${prefix}CIRCUIT_BREAKER_RECOVERY_MS`,
4848
+ raw
4849
+ );
4850
+ }
4851
+ });
4852
+ readVar(env, `${prefix}CIRCUIT_BREAKER_WINDOW_SIZE`, (raw) => {
4853
+ if (options.circuitBreaker) {
4854
+ options.circuitBreaker.windowSize = parseNum(
4855
+ `${prefix}CIRCUIT_BREAKER_WINDOW_SIZE`,
4856
+ raw
4857
+ );
4858
+ }
4859
+ });
4860
+ readVar(env, `${prefix}CIRCUIT_BREAKER_HALF_OPEN_SUCCESSES`, (raw) => {
4861
+ if (options.circuitBreaker) {
4862
+ options.circuitBreaker.halfOpenSuccesses = parseNum(
4863
+ `${prefix}CIRCUIT_BREAKER_HALF_OPEN_SUCCESSES`,
4864
+ raw
4865
+ );
4866
+ }
4867
+ });
4868
+ readVar(env, `${prefix}QUEUE_HIGH_WATER_MARK`, (raw) => {
4869
+ options.queueHighWaterMark = parseNum(`${prefix}QUEUE_HIGH_WATER_MARK`, raw);
4870
+ });
4871
+ readVar(env, `${prefix}PARTITION_ASSIGNER`, (raw) => {
4872
+ options.partitionAssigner = parseEnum(`${prefix}PARTITION_ASSIGNER`, raw, [
4873
+ "roundrobin",
4874
+ "range",
4875
+ "cooperative-sticky"
4876
+ ]);
4877
+ });
4878
+ readVar(env, `${prefix}GROUP_INSTANCE_ID`, (raw) => {
4879
+ options.groupInstanceId = raw.trim();
4880
+ });
4881
+ readVar(env, `${prefix}SUBSCRIBE_RETRY_RETRIES`, (raw) => {
4882
+ const subscribeRetry = {
4883
+ retries: parseNum(`${prefix}SUBSCRIBE_RETRY_RETRIES`, raw)
4884
+ };
4885
+ options.subscribeRetry = subscribeRetry;
4886
+ });
4887
+ readVar(env, `${prefix}SUBSCRIBE_RETRY_DELAY_MS`, (raw) => {
4888
+ if (options.subscribeRetry) {
4889
+ options.subscribeRetry.backoffMs = parseNum(
4890
+ `${prefix}SUBSCRIBE_RETRY_DELAY_MS`,
4891
+ raw
4892
+ );
4893
+ }
4894
+ });
4895
+ return options;
4896
+ }
4897
+ var NESTED_CONSUMER_KEYS = [
4898
+ "retry",
4899
+ "deduplication",
4900
+ "circuitBreaker",
4901
+ "subscribeRetry"
4902
+ ];
4903
+ function isPlainObject(value) {
4904
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4905
+ }
4906
+ function mergeConsumerOptions(...layers) {
4907
+ const result = {};
4908
+ for (const layer of layers) {
4909
+ if (!layer) continue;
4910
+ for (const [key, value] of Object.entries(layer)) {
4911
+ if (value === void 0) continue;
4912
+ if (NESTED_CONSUMER_KEYS.includes(key) && isPlainObject(value) && isPlainObject(result[key])) {
4913
+ result[key] = {
4914
+ ...result[key],
4915
+ ...value
4916
+ };
4917
+ } else {
4918
+ result[key] = value;
4919
+ }
4920
+ }
4921
+ }
4922
+ return result;
4923
+ }
3601
4924
  // Annotate the CommonJS export names for ESM import in node:
3602
4925
  0 && (module.exports = {
4926
+ ConfluentTransport,
3603
4927
  HEADER_CORRELATION_ID,
4928
+ HEADER_DELAYED_TARGET,
4929
+ HEADER_DELAYED_UNTIL,
3604
4930
  HEADER_EVENT_ID,
3605
4931
  HEADER_LAMPORT_CLOCK,
3606
4932
  HEADER_SCHEMA_VERSION,
3607
4933
  HEADER_TIMESTAMP,
3608
4934
  HEADER_TRACEPARENT,
4935
+ InMemoryDedupStore,
4936
+ InMemoryOutboxStore,
4937
+ JsonSerde,
3609
4938
  KafkaClient,
3610
4939
  KafkaProcessingError,
3611
4940
  KafkaRetryExhaustedError,
3612
4941
  KafkaValidationError,
4942
+ SchemaRegistryClient,
4943
+ awsMskIamProvider,
3613
4944
  buildEnvelopeHeaders,
4945
+ consumerOptionsFromEnv,
3614
4946
  decodeHeaders,
4947
+ describeRequiredAcls,
3615
4948
  extractEnvelope,
4949
+ gcpAccessTokenProvider,
3616
4950
  getEnvelopeContext,
4951
+ kafkaClientConfigFromEnv,
4952
+ mergeConsumerOptions,
4953
+ registrySchema,
4954
+ resolveSecurityOptions,
3617
4955
  runWithEnvelopeContext,
3618
- topic
4956
+ startOutboxRelay,
4957
+ toError,
4958
+ toKafkaAclCommands,
4959
+ toMskIamPolicy,
4960
+ topic,
4961
+ versionedSchema
3619
4962
  });
3620
4963
  //# sourceMappingURL=core.js.map