@drarzter/kafka-client 0.9.4 → 0.10.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 (170) hide show
  1. package/README.md +625 -8
  2. package/dist/chunk-CMO7SMVK.mjs +4814 -0
  3. package/dist/chunk-CMO7SMVK.mjs.map +1 -0
  4. package/dist/cli/dlq.d.ts +119 -0
  5. package/dist/cli/dlq.d.ts.map +1 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/{chunk-SM4FZKAZ.mjs → cli/index.js} +964 -264
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/index.mjs +355 -0
  11. package/dist/cli/index.mjs.map +1 -0
  12. package/dist/client/config/from-env.d.ts +188 -0
  13. package/dist/client/config/from-env.d.ts.map +1 -0
  14. package/dist/client/config/index.d.ts +2 -0
  15. package/dist/client/config/index.d.ts.map +1 -0
  16. package/dist/client/errors.d.ts +67 -0
  17. package/dist/client/errors.d.ts.map +1 -0
  18. package/dist/client/kafka.client/admin/ops.d.ts +114 -0
  19. package/dist/client/kafka.client/admin/ops.d.ts.map +1 -0
  20. package/dist/client/kafka.client/consumer/features/delayed.d.ts +24 -0
  21. package/dist/client/kafka.client/consumer/features/delayed.d.ts.map +1 -0
  22. package/dist/client/kafka.client/consumer/features/dlq-replay.d.ts +52 -0
  23. package/dist/client/kafka.client/consumer/features/dlq-replay.d.ts.map +1 -0
  24. package/dist/client/kafka.client/consumer/features/routed.d.ts +4 -0
  25. package/dist/client/kafka.client/consumer/features/routed.d.ts.map +1 -0
  26. package/dist/client/kafka.client/consumer/features/snapshot.d.ts +10 -0
  27. package/dist/client/kafka.client/consumer/features/snapshot.d.ts.map +1 -0
  28. package/dist/client/kafka.client/consumer/features/window.d.ts +5 -0
  29. package/dist/client/kafka.client/consumer/features/window.d.ts.map +1 -0
  30. package/dist/client/kafka.client/consumer/handler.d.ts +149 -0
  31. package/dist/client/kafka.client/consumer/handler.d.ts.map +1 -0
  32. package/dist/client/kafka.client/consumer/ops.d.ts +51 -0
  33. package/dist/client/kafka.client/consumer/ops.d.ts.map +1 -0
  34. package/dist/client/kafka.client/consumer/pipeline.d.ts +167 -0
  35. package/dist/client/kafka.client/consumer/pipeline.d.ts.map +1 -0
  36. package/dist/client/kafka.client/consumer/queue.d.ts +37 -0
  37. package/dist/client/kafka.client/consumer/queue.d.ts.map +1 -0
  38. package/dist/client/kafka.client/consumer/retry-topic.d.ts +65 -0
  39. package/dist/client/kafka.client/consumer/retry-topic.d.ts.map +1 -0
  40. package/dist/client/kafka.client/consumer/setup.d.ts +63 -0
  41. package/dist/client/kafka.client/consumer/setup.d.ts.map +1 -0
  42. package/dist/client/kafka.client/consumer/start.d.ts +7 -0
  43. package/dist/client/kafka.client/consumer/start.d.ts.map +1 -0
  44. package/dist/client/kafka.client/consumer/stop.d.ts +19 -0
  45. package/dist/client/kafka.client/consumer/stop.d.ts.map +1 -0
  46. package/dist/client/kafka.client/consumer/subscribe-retry.d.ts +4 -0
  47. package/dist/client/kafka.client/consumer/subscribe-retry.d.ts.map +1 -0
  48. package/dist/client/kafka.client/context.d.ts +72 -0
  49. package/dist/client/kafka.client/context.d.ts.map +1 -0
  50. package/dist/client/kafka.client/index.d.ts +155 -0
  51. package/dist/client/kafka.client/index.d.ts.map +1 -0
  52. package/dist/client/kafka.client/infra/circuit-breaker.manager.d.ts +61 -0
  53. package/dist/client/kafka.client/infra/circuit-breaker.manager.d.ts.map +1 -0
  54. package/dist/client/kafka.client/infra/dedup.store.d.ts +28 -0
  55. package/dist/client/kafka.client/infra/dedup.store.d.ts.map +1 -0
  56. package/dist/client/kafka.client/infra/inflight.tracker.d.ts +22 -0
  57. package/dist/client/kafka.client/infra/inflight.tracker.d.ts.map +1 -0
  58. package/dist/client/kafka.client/infra/metrics.manager.d.ts +67 -0
  59. package/dist/client/kafka.client/infra/metrics.manager.d.ts.map +1 -0
  60. package/dist/client/kafka.client/producer/lifecycle.d.ts +41 -0
  61. package/dist/client/kafka.client/producer/lifecycle.d.ts.map +1 -0
  62. package/dist/client/kafka.client/producer/ops.d.ts +70 -0
  63. package/dist/client/kafka.client/producer/ops.d.ts.map +1 -0
  64. package/dist/client/kafka.client/producer/send.d.ts +21 -0
  65. package/dist/client/kafka.client/producer/send.d.ts.map +1 -0
  66. package/dist/client/kafka.client/validate-options.d.ts +11 -0
  67. package/dist/client/kafka.client/validate-options.d.ts.map +1 -0
  68. package/dist/client/message/envelope.d.ts +105 -0
  69. package/dist/client/message/envelope.d.ts.map +1 -0
  70. package/dist/client/message/schema-registry.d.ts +105 -0
  71. package/dist/client/message/schema-registry.d.ts.map +1 -0
  72. package/dist/client/message/topic.d.ts +138 -0
  73. package/dist/client/message/topic.d.ts.map +1 -0
  74. package/dist/client/message/versioned-schema.d.ts +53 -0
  75. package/dist/client/message/versioned-schema.d.ts.map +1 -0
  76. package/dist/client/outbox/index.d.ts +4 -0
  77. package/dist/client/outbox/index.d.ts.map +1 -0
  78. package/dist/client/outbox/outbox.relay.d.ts +90 -0
  79. package/dist/client/outbox/outbox.relay.d.ts.map +1 -0
  80. package/dist/client/outbox/outbox.store.d.ts +42 -0
  81. package/dist/client/outbox/outbox.store.d.ts.map +1 -0
  82. package/dist/client/outbox/outbox.types.d.ts +144 -0
  83. package/dist/client/outbox/outbox.types.d.ts.map +1 -0
  84. package/dist/client/security/acl.d.ts +108 -0
  85. package/dist/client/security/acl.d.ts.map +1 -0
  86. package/dist/client/security/index.d.ts +5 -0
  87. package/dist/client/security/index.d.ts.map +1 -0
  88. package/dist/client/security/providers.d.ts +88 -0
  89. package/dist/client/security/providers.d.ts.map +1 -0
  90. package/dist/client/security/resolve-security.d.ts +19 -0
  91. package/dist/client/security/resolve-security.d.ts.map +1 -0
  92. package/dist/client/security/security.types.d.ts +76 -0
  93. package/dist/client/security/security.types.d.ts.map +1 -0
  94. package/dist/client/transport/confluent.transport.d.ts +32 -0
  95. package/dist/client/transport/confluent.transport.d.ts.map +1 -0
  96. package/dist/client/transport/transport.interface.d.ts +216 -0
  97. package/dist/client/transport/transport.interface.d.ts.map +1 -0
  98. package/dist/client/types/admin.interface.d.ts +174 -0
  99. package/dist/client/types/admin.interface.d.ts.map +1 -0
  100. package/dist/client/types/admin.types.d.ts +140 -0
  101. package/dist/client/types/admin.types.d.ts.map +1 -0
  102. package/dist/client/types/client.d.ts +21 -0
  103. package/dist/client/types/client.d.ts.map +1 -0
  104. package/dist/client/types/common.d.ts +84 -0
  105. package/dist/client/types/common.d.ts.map +1 -0
  106. package/dist/client/types/config.types.d.ts +150 -0
  107. package/dist/client/types/config.types.d.ts.map +1 -0
  108. package/dist/client/types/consumer.interface.d.ts +115 -0
  109. package/dist/client/types/consumer.interface.d.ts.map +1 -0
  110. package/dist/{consumer.types-fFCag3VJ.d.mts → client/types/consumer.types.d.ts} +62 -383
  111. package/dist/client/types/consumer.types.d.ts.map +1 -0
  112. package/dist/client/types/dedup.types.d.ts +50 -0
  113. package/dist/client/types/dedup.types.d.ts.map +1 -0
  114. package/dist/client/types/lifecycle.interface.d.ts +72 -0
  115. package/dist/client/types/lifecycle.interface.d.ts.map +1 -0
  116. package/dist/client/types/producer.interface.d.ts +52 -0
  117. package/dist/client/types/producer.interface.d.ts.map +1 -0
  118. package/dist/client/types/producer.types.d.ts +90 -0
  119. package/dist/client/types/producer.types.d.ts.map +1 -0
  120. package/dist/client/types.d.ts +8 -0
  121. package/dist/client/types.d.ts.map +1 -0
  122. package/dist/core.d.ts +10 -314
  123. package/dist/core.d.ts.map +1 -0
  124. package/dist/core.js +1325 -73
  125. package/dist/core.js.map +1 -1
  126. package/dist/core.mjs +39 -3
  127. package/dist/index.d.ts +7 -128
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +1342 -73
  130. package/dist/index.js.map +1 -1
  131. package/dist/index.mjs +56 -3
  132. package/dist/index.mjs.map +1 -1
  133. package/dist/nest/kafka.constants.d.ts +5 -0
  134. package/dist/nest/kafka.constants.d.ts.map +1 -0
  135. package/dist/nest/kafka.decorator.d.ts +49 -0
  136. package/dist/nest/kafka.decorator.d.ts.map +1 -0
  137. package/dist/nest/kafka.explorer.d.ts +17 -0
  138. package/dist/nest/kafka.explorer.d.ts.map +1 -0
  139. package/dist/nest/kafka.health.d.ts +7 -0
  140. package/dist/nest/kafka.health.d.ts.map +1 -0
  141. package/dist/nest/kafka.module.d.ts +61 -0
  142. package/dist/nest/kafka.module.d.ts.map +1 -0
  143. package/dist/otel.d.ts +83 -5
  144. package/dist/otel.d.ts.map +1 -0
  145. package/dist/otel.js +100 -6
  146. package/dist/otel.js.map +1 -1
  147. package/dist/otel.mjs +98 -5
  148. package/dist/otel.mjs.map +1 -1
  149. package/dist/testing/client.mock.d.ts +47 -0
  150. package/dist/testing/client.mock.d.ts.map +1 -0
  151. package/dist/testing/index.d.ts +4 -0
  152. package/dist/testing/index.d.ts.map +1 -0
  153. package/dist/testing/test.container.d.ts +63 -0
  154. package/dist/testing/test.container.d.ts.map +1 -0
  155. package/dist/{testing.d.mts → testing/transport.fake.d.ts} +7 -111
  156. package/dist/testing/transport.fake.d.ts.map +1 -0
  157. package/dist/testing.d.ts +2 -318
  158. package/dist/testing.d.ts.map +1 -0
  159. package/dist/testing.js +26 -0
  160. package/dist/testing.js.map +1 -1
  161. package/dist/testing.mjs +26 -0
  162. package/dist/testing.mjs.map +1 -1
  163. package/package.json +21 -8
  164. package/dist/chunk-SM4FZKAZ.mjs.map +0 -1
  165. package/dist/client-1irhGEu0.d.mts +0 -751
  166. package/dist/client-BpFjkHhr.d.ts +0 -751
  167. package/dist/consumer.types-fFCag3VJ.d.ts +0 -958
  168. package/dist/core.d.mts +0 -314
  169. package/dist/index.d.mts +0 -128
  170. package/dist/otel.d.mts +0 -27
package/dist/core.js CHANGED
@@ -21,31 +21,50 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var core_exports = {};
22
22
  __export(core_exports, {
23
23
  HEADER_CORRELATION_ID: () => HEADER_CORRELATION_ID,
24
+ HEADER_DELAYED_TARGET: () => HEADER_DELAYED_TARGET,
25
+ HEADER_DELAYED_UNTIL: () => HEADER_DELAYED_UNTIL,
24
26
  HEADER_EVENT_ID: () => HEADER_EVENT_ID,
25
27
  HEADER_LAMPORT_CLOCK: () => HEADER_LAMPORT_CLOCK,
26
28
  HEADER_SCHEMA_VERSION: () => HEADER_SCHEMA_VERSION,
27
29
  HEADER_TIMESTAMP: () => HEADER_TIMESTAMP,
28
30
  HEADER_TRACEPARENT: () => HEADER_TRACEPARENT,
31
+ InMemoryDedupStore: () => InMemoryDedupStore,
32
+ InMemoryOutboxStore: () => InMemoryOutboxStore,
29
33
  KafkaClient: () => KafkaClient,
30
34
  KafkaProcessingError: () => KafkaProcessingError,
31
35
  KafkaRetryExhaustedError: () => KafkaRetryExhaustedError,
32
36
  KafkaValidationError: () => KafkaValidationError,
37
+ SchemaRegistryClient: () => SchemaRegistryClient,
38
+ awsMskIamProvider: () => awsMskIamProvider,
33
39
  buildEnvelopeHeaders: () => buildEnvelopeHeaders,
40
+ consumerOptionsFromEnv: () => consumerOptionsFromEnv,
34
41
  decodeHeaders: () => decodeHeaders,
42
+ describeRequiredAcls: () => describeRequiredAcls,
35
43
  extractEnvelope: () => extractEnvelope,
44
+ gcpAccessTokenProvider: () => gcpAccessTokenProvider,
36
45
  getEnvelopeContext: () => getEnvelopeContext,
46
+ kafkaClientConfigFromEnv: () => kafkaClientConfigFromEnv,
47
+ mergeConsumerOptions: () => mergeConsumerOptions,
48
+ registrySchema: () => registrySchema,
49
+ resolveSecurityOptions: () => resolveSecurityOptions,
37
50
  runWithEnvelopeContext: () => runWithEnvelopeContext,
38
- topic: () => topic
51
+ startOutboxRelay: () => startOutboxRelay,
52
+ toError: () => toError,
53
+ toKafkaAclCommands: () => toKafkaAclCommands,
54
+ toMskIamPolicy: () => toMskIamPolicy,
55
+ topic: () => topic,
56
+ versionedSchema: () => versionedSchema
39
57
  });
40
58
  module.exports = __toCommonJS(core_exports);
41
59
 
42
- // src/client/kafka.client/confluent-transport.ts
60
+ // src/client/transport/confluent.transport.ts
43
61
  var import_kafka_javascript = require("@confluentinc/kafka-javascript");
44
62
  var { Kafka: KafkaClass, logLevel: KafkaLogLevel, PartitionAssigners } = import_kafka_javascript.KafkaJS;
45
63
  var ConfluentTransaction = class {
46
64
  constructor(tx) {
47
65
  this.tx = tx;
48
66
  }
67
+ tx;
49
68
  async send(record) {
50
69
  await this.tx.send(record);
51
70
  }
@@ -67,10 +86,17 @@ var ConfluentProducer = class {
67
86
  constructor(producer) {
68
87
  this.producer = producer;
69
88
  }
89
+ producer;
90
+ connectPromise;
70
91
  async connect() {
71
- await this.producer.connect();
92
+ this.connectPromise ??= this.producer.connect().catch((err) => {
93
+ this.connectPromise = void 0;
94
+ throw err;
95
+ });
96
+ return this.connectPromise;
72
97
  }
73
98
  async disconnect() {
99
+ this.connectPromise = void 0;
74
100
  await this.producer.disconnect();
75
101
  }
76
102
  async send(record) {
@@ -85,6 +111,7 @@ var ConfluentConsumer = class {
85
111
  constructor(consumer) {
86
112
  this.consumer = consumer;
87
113
  }
114
+ consumer;
88
115
  /** Returns the underlying KafkaJS.Consumer — used by ConfluentTransaction.sendOffsets. */
89
116
  getNative() {
90
117
  return this.consumer;
@@ -124,6 +151,7 @@ var ConfluentAdmin = class {
124
151
  constructor(admin) {
125
152
  this.admin = admin;
126
153
  }
154
+ admin;
127
155
  async connect() {
128
156
  await this.admin.connect();
129
157
  }
@@ -137,7 +165,7 @@ var ConfluentAdmin = class {
137
165
  return this.admin.fetchTopicOffsets(topic2);
138
166
  }
139
167
  async fetchTopicOffsetsByTimestamp(topic2, timestamp) {
140
- return this.admin.fetchTopicOffsetsByTime(topic2, timestamp);
168
+ return this.admin.fetchTopicOffsetsByTimestamp(topic2, timestamp);
141
169
  }
142
170
  async fetchOffsets(options) {
143
171
  return this.admin.fetchOffsets(options);
@@ -163,10 +191,29 @@ var ConfluentAdmin = class {
163
191
  };
164
192
  var ConfluentTransport = class {
165
193
  kafka;
166
- constructor(clientId, brokers) {
167
- this.kafka = new KafkaClass({
168
- kafkaJS: { clientId, brokers, logLevel: KafkaLogLevel.ERROR }
169
- });
194
+ constructor(clientId, brokers, security) {
195
+ const kafkaJS = { clientId, brokers, logLevel: KafkaLogLevel.ERROR };
196
+ if (security?.ssl !== void 0) kafkaJS.ssl = security.ssl;
197
+ if (security?.sasl) {
198
+ if (security.sasl.mechanism === "oauthbearer") {
199
+ const provider = security.sasl.oauthBearerProvider;
200
+ kafkaJS.sasl = {
201
+ mechanism: "oauthbearer",
202
+ oauthBearerProvider: async () => {
203
+ const token = await provider();
204
+ return {
205
+ value: token.value,
206
+ principal: token.principal ?? "kafka-client",
207
+ lifetime: token.lifetimeMs ?? Date.now() + 15 * 6e4,
208
+ ...token.extensions && { extensions: token.extensions }
209
+ };
210
+ }
211
+ };
212
+ } else {
213
+ kafkaJS.sasl = security.sasl;
214
+ }
215
+ }
216
+ this.kafka = new KafkaClass({ kafkaJS });
170
217
  }
171
218
  producer(options) {
172
219
  const native = this.kafka.producer({
@@ -193,6 +240,9 @@ var ConfluentTransport = class {
193
240
  partitionAssigners: [assigner]
194
241
  }
195
242
  };
243
+ if (options.groupInstanceId) {
244
+ config["group.instance.id"] = options.groupInstanceId;
245
+ }
196
246
  if (options.onRebalance) {
197
247
  const cb = options.onRebalance;
198
248
  config.rebalance_cb = (err, assignment) => {
@@ -210,6 +260,25 @@ var ConfluentTransport = class {
210
260
  }
211
261
  };
212
262
 
263
+ // src/client/kafka.client/infra/dedup.store.ts
264
+ var InMemoryDedupStore = class {
265
+ constructor(states) {
266
+ this.states = states;
267
+ }
268
+ states;
269
+ getLastClock(groupId, topicPartition) {
270
+ return this.states.get(groupId)?.get(topicPartition);
271
+ }
272
+ setLastClock(groupId, topicPartition, clock) {
273
+ let group = this.states.get(groupId);
274
+ if (!group) {
275
+ group = /* @__PURE__ */ new Map();
276
+ this.states.set(groupId, group);
277
+ }
278
+ group.set(topicPartition, clock);
279
+ }
280
+ };
281
+
213
282
  // src/client/message/envelope.ts
214
283
  var import_node_async_hooks = require("async_hooks");
215
284
  var import_node_crypto = require("crypto");
@@ -219,6 +288,8 @@ var HEADER_TIMESTAMP = "x-timestamp";
219
288
  var HEADER_SCHEMA_VERSION = "x-schema-version";
220
289
  var HEADER_TRACEPARENT = "traceparent";
221
290
  var HEADER_LAMPORT_CLOCK = "x-lamport-clock";
291
+ var HEADER_DELAYED_UNTIL = "x-delayed-until";
292
+ var HEADER_DELAYED_TARGET = "x-delayed-target";
222
293
  var envelopeStorage = new import_node_async_hooks.AsyncLocalStorage();
223
294
  function getEnvelopeContext() {
224
295
  return envelopeStorage.getStore();
@@ -273,6 +344,9 @@ function extractEnvelope(payload, headers, topic2, partition, offset) {
273
344
  }
274
345
 
275
346
  // src/client/errors.ts
347
+ function toError(error) {
348
+ return error instanceof Error ? error : new Error(String(error));
349
+ }
276
350
  var KafkaProcessingError = class extends Error {
277
351
  constructor(message, topic2, originalMessage, options) {
278
352
  super(message, options);
@@ -281,6 +355,8 @@ var KafkaProcessingError = class extends Error {
281
355
  this.name = "KafkaProcessingError";
282
356
  if (options?.cause) this.cause = options.cause;
283
357
  }
358
+ topic;
359
+ originalMessage;
284
360
  };
285
361
  var KafkaValidationError = class extends Error {
286
362
  constructor(topic2, originalMessage, options) {
@@ -290,6 +366,8 @@ var KafkaValidationError = class extends Error {
290
366
  this.name = "KafkaValidationError";
291
367
  if (options?.cause) this.cause = options.cause;
292
368
  }
369
+ topic;
370
+ originalMessage;
293
371
  };
294
372
  var KafkaRetryExhaustedError = class extends KafkaProcessingError {
295
373
  constructor(topic2, originalMessage, attempts, options) {
@@ -302,6 +380,7 @@ var KafkaRetryExhaustedError = class extends KafkaProcessingError {
302
380
  this.attempts = attempts;
303
381
  this.name = "KafkaRetryExhaustedError";
304
382
  }
383
+ attempts;
305
384
  };
306
385
 
307
386
  // src/client/kafka.client/producer/ops.ts
@@ -374,7 +453,9 @@ async function buildSendPayload(topicOrDesc, messages, deps, compression) {
374
453
  value: JSON.stringify(
375
454
  await validateMessage(topicOrDesc, m.value, deps, sendCtx)
376
455
  ),
377
- key: m.key ?? null,
456
+ // Explicit key wins; otherwise fall back to the descriptor's .key()
457
+ // extractor (runs on the original, pre-validation payload).
458
+ key: m.key ?? topicOrDesc?.__key?.(m.value) ?? null,
378
459
  headers: envelopeHeaders
379
460
  };
380
461
  })
@@ -383,7 +464,7 @@ async function buildSendPayload(topicOrDesc, messages, deps, compression) {
383
464
  }
384
465
 
385
466
  // src/client/kafka.client/consumer/ops.ts
386
- function getOrCreateConsumer(groupId, fromBeginning, autoCommit, deps, partitionAssigner, onFirstAssignment) {
467
+ function getOrCreateConsumer(groupId, fromBeginning, autoCommit, deps, partitionAssigner, onFirstAssignment, groupInstanceId) {
387
468
  const { consumers, consumerCreationOptions, transport, onRebalance, logger } = deps;
388
469
  if (consumers.has(groupId)) {
389
470
  const prev = consumerCreationOptions.get(groupId);
@@ -416,6 +497,7 @@ function getOrCreateConsumer(groupId, fromBeginning, autoCommit, deps, partition
416
497
  fromBeginning,
417
498
  autoCommit,
418
499
  partitionAssigner: partitionAssigner ?? "cooperative-sticky",
500
+ groupInstanceId,
419
501
  onRebalance: (type, assignments) => {
420
502
  if (type === "assign") fireOnAssignment();
421
503
  else if (type === "revoke") scheduleSettle();
@@ -461,6 +543,7 @@ var AdminOps = class {
461
543
  constructor(deps) {
462
544
  this.deps = deps;
463
545
  }
546
+ deps;
464
547
  isConnected = false;
465
548
  /** Underlying admin client — used by index.ts for topic validation. */
466
549
  get admin() {
@@ -567,7 +650,10 @@ var AdminOps = class {
567
650
  const found = results.find(
568
651
  (r) => r.partition === partition
569
652
  );
570
- return { partition, offset: found?.offset ?? "-1" };
653
+ if (found) return { partition, offset: found.offset };
654
+ const topicOffsets = await this.deps.admin.fetchTopicOffsets(topic2);
655
+ const po = topicOffsets.find((o) => o.partition === partition);
656
+ return { partition, offset: po?.high ?? "0" };
571
657
  })
572
658
  );
573
659
  await this.deps.admin.setOffsets({ groupId: gid, topic: topic2, partitions: offsets });
@@ -648,7 +734,8 @@ var AdminOps = class {
648
734
  name: t.name,
649
735
  partitions: t.partitions.map((p) => ({
650
736
  partition: p.partitionId ?? p.partition ?? 0,
651
- leader: p.leader ?? 0,
737
+ // -1 is Kafka's own "no leader" sentinel; 0 is a valid broker id
738
+ leader: p.leader ?? -1,
652
739
  replicas: (p.replicas ?? []).map(
653
740
  (r) => typeof r === "number" ? r : r.nodeId
654
741
  ),
@@ -732,9 +819,6 @@ var AdminOps = class {
732
819
  };
733
820
 
734
821
  // src/client/kafka.client/consumer/pipeline.ts
735
- function toError(error) {
736
- return error instanceof Error ? error : new Error(String(error));
737
- }
738
822
  function sleep(ms) {
739
823
  return new Promise((resolve) => setTimeout(resolve, ms));
740
824
  }
@@ -1018,6 +1102,7 @@ async function executeWithRetry(fn, ctx, deps) {
1018
1102
  for (const env of envelopes) deps.onMessage?.(env);
1019
1103
  return;
1020
1104
  }
1105
+ deps.onFailure?.(envelopes[0]);
1021
1106
  const isLastAttempt = attempt === maxAttempts;
1022
1107
  const reportedError = isLastAttempt && maxAttempts > 1 ? new KafkaRetryExhaustedError(
1023
1108
  topic2,
@@ -1102,8 +1187,13 @@ async function subscribeWithRetry(consumer, topics, logger, retryOpts) {
1102
1187
  }
1103
1188
  }
1104
1189
 
1105
- // src/client/kafka.client/consumer/dlq-replay.ts
1190
+ // src/client/kafka.client/consumer/features/dlq-replay.ts
1106
1191
  async function replayDlqTopic(topic2, deps, options = {}) {
1192
+ if (topic2.endsWith(".dlq")) {
1193
+ throw new Error(
1194
+ `replayDlq: pass the ORIGINAL topic name \u2014 "${topic2}" already ends in ".dlq" (the ".dlq" suffix is appended internally, so this would read "${topic2}.dlq")`
1195
+ );
1196
+ }
1107
1197
  const dlqTopic = `${topic2}.dlq`;
1108
1198
  const partitionOffsets = await deps.fetchTopicOffsets(dlqTopic);
1109
1199
  const activePartitions = partitionOffsets.filter(
@@ -1169,6 +1259,7 @@ var MetricsManager = class {
1169
1259
  constructor(deps) {
1170
1260
  this.deps = deps;
1171
1261
  }
1262
+ deps;
1172
1263
  topicMetrics = /* @__PURE__ */ new Map();
1173
1264
  metricsFor(topic2) {
1174
1265
  let m = this.topicMetrics.get(topic2);
@@ -1194,16 +1285,25 @@ var MetricsManager = class {
1194
1285
  for (const inst of this.deps.instrumentation) inst.onRetry?.(envelope, attempt, maxRetries);
1195
1286
  }
1196
1287
  /**
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).
1288
+ * Increment the DLQ counter for the envelope's topic and fire all `onDlq` instrumentation hooks.
1289
+ * Circuit breaker failures are recorded separately via `notifyFailure` at the
1290
+ * handler-error boundary — dead-lettering itself is not a circuit event.
1199
1291
  * @param envelope The message envelope being sent to the DLQ.
1200
1292
  * @param reason The reason the message is being dead-lettered.
1201
- * @param gid Consumer group ID — used to drive circuit breaker state.
1202
1293
  */
1203
- notifyDlq(envelope, reason, gid) {
1294
+ notifyDlq(envelope, reason) {
1204
1295
  this.metricsFor(envelope.topic).dlqCount++;
1205
1296
  for (const inst of this.deps.instrumentation) inst.onDlq?.(envelope, reason);
1206
- if (gid) this.deps.onCircuitFailure(envelope, gid);
1297
+ }
1298
+ /**
1299
+ * Notify the circuit breaker of a handler failure. Fired on every failed
1300
+ * handler attempt (in-process retries and retry-topic levels included),
1301
+ * independent of whether the message is ultimately dead-lettered.
1302
+ * @param envelope The message envelope whose handler failed.
1303
+ * @param gid Consumer group ID — used to drive circuit breaker state.
1304
+ */
1305
+ notifyFailure(envelope, gid) {
1306
+ this.deps.onCircuitFailure(envelope, gid);
1207
1307
  }
1208
1308
  /**
1209
1309
  * Increment the deduplication counter for the envelope's topic and fire all `onDuplicate` hooks.
@@ -1262,6 +1362,7 @@ var InFlightTracker = class {
1262
1362
  constructor(warn) {
1263
1363
  this.warn = warn;
1264
1364
  }
1365
+ warn;
1265
1366
  inFlightTotal = 0;
1266
1367
  drainResolvers = [];
1267
1368
  /**
@@ -1272,10 +1373,16 @@ var InFlightTracker = class {
1272
1373
  */
1273
1374
  track(fn) {
1274
1375
  this.inFlightTotal++;
1275
- return fn().finally(() => {
1376
+ const done = () => {
1276
1377
  this.inFlightTotal--;
1277
1378
  if (this.inFlightTotal === 0) this.drainResolvers.splice(0).forEach((r) => r());
1278
- });
1379
+ };
1380
+ try {
1381
+ return fn().finally(done);
1382
+ } catch (err) {
1383
+ done();
1384
+ throw err;
1385
+ }
1279
1386
  }
1280
1387
  /**
1281
1388
  * Resolve when all tracked handlers have completed, or after `timeoutMs` elapses.
@@ -1309,6 +1416,7 @@ var CircuitBreakerManager = class {
1309
1416
  constructor(deps) {
1310
1417
  this.deps = deps;
1311
1418
  }
1419
+ deps;
1312
1420
  states = /* @__PURE__ */ new Map();
1313
1421
  configs = /* @__PURE__ */ new Map();
1314
1422
  /**
@@ -1453,6 +1561,9 @@ var AsyncQueue = class {
1453
1561
  this.onFull = onFull;
1454
1562
  this.onDrained = onDrained;
1455
1563
  }
1564
+ highWaterMark;
1565
+ onFull;
1566
+ onDrained;
1456
1567
  items = [];
1457
1568
  waiting = [];
1458
1569
  closed = false;
@@ -1464,6 +1575,7 @@ var AsyncQueue = class {
1464
1575
  * @param item The value to enqueue.
1465
1576
  */
1466
1577
  push(item) {
1578
+ if (this.closed) return;
1467
1579
  if (this.waiting.length > 0) {
1468
1580
  this.waiting.shift().resolve({ value: item, done: false });
1469
1581
  } else {
@@ -1514,6 +1626,101 @@ var AsyncQueue = class {
1514
1626
  }
1515
1627
  };
1516
1628
 
1629
+ // src/client/kafka.client/validate-options.ts
1630
+ function validateClientOptions(clientId, groupId, brokers, options) {
1631
+ const problems = [];
1632
+ if (typeof clientId !== "string" || clientId.trim() === "") {
1633
+ problems.push("clientId must be a non-empty string");
1634
+ }
1635
+ if (typeof groupId !== "string" || groupId.trim() === "") {
1636
+ problems.push("groupId must be a non-empty string");
1637
+ }
1638
+ if (!Array.isArray(brokers) || brokers.length === 0 && !options?.transport) {
1639
+ problems.push("brokers must be a non-empty array of broker addresses");
1640
+ } else if (brokers.some((b) => typeof b !== "string" || b.trim() === "")) {
1641
+ problems.push("brokers must not contain empty entries");
1642
+ }
1643
+ if (options) {
1644
+ const {
1645
+ numPartitions,
1646
+ transactionalId,
1647
+ clockRecovery,
1648
+ lagThrottle
1649
+ } = options;
1650
+ if (numPartitions !== void 0 && (!Number.isInteger(numPartitions) || numPartitions < 1)) {
1651
+ problems.push(
1652
+ `numPartitions must be a positive integer (got ${numPartitions})`
1653
+ );
1654
+ }
1655
+ if (transactionalId !== void 0 && transactionalId.trim() === "") {
1656
+ problems.push("transactionalId must be a non-empty string when set");
1657
+ }
1658
+ if (clockRecovery) {
1659
+ if (!Array.isArray(clockRecovery.topics)) {
1660
+ problems.push("clockRecovery.topics must be an array of topic names");
1661
+ }
1662
+ if (clockRecovery.timeoutMs !== void 0 && !(clockRecovery.timeoutMs > 0)) {
1663
+ problems.push(
1664
+ `clockRecovery.timeoutMs must be > 0 (got ${clockRecovery.timeoutMs})`
1665
+ );
1666
+ }
1667
+ }
1668
+ if (lagThrottle) {
1669
+ if (!(lagThrottle.maxLag >= 0)) {
1670
+ problems.push(`lagThrottle.maxLag must be >= 0 (got ${lagThrottle.maxLag})`);
1671
+ }
1672
+ if (lagThrottle.pollIntervalMs !== void 0 && !(lagThrottle.pollIntervalMs > 0)) {
1673
+ problems.push(
1674
+ `lagThrottle.pollIntervalMs must be > 0 (got ${lagThrottle.pollIntervalMs})`
1675
+ );
1676
+ }
1677
+ if (lagThrottle.maxWaitMs !== void 0 && !(lagThrottle.maxWaitMs >= 0)) {
1678
+ problems.push(
1679
+ `lagThrottle.maxWaitMs must be >= 0 (got ${lagThrottle.maxWaitMs})`
1680
+ );
1681
+ }
1682
+ }
1683
+ }
1684
+ if (problems.length > 0) {
1685
+ throw new Error(
1686
+ `KafkaClient: invalid configuration:
1687
+ - ${problems.join("\n- ")}`
1688
+ );
1689
+ }
1690
+ }
1691
+
1692
+ // src/client/security/resolve-security.ts
1693
+ var LOCAL_HOST_PATTERNS = [
1694
+ /^localhost(:\d+)?$/i,
1695
+ /^127\.\d+\.\d+\.\d+(:\d+)?$/,
1696
+ /^\[?::1\]?(:\d+)?$/,
1697
+ /^0\.0\.0\.0(:\d+)?$/,
1698
+ /^host\.docker\.internal(:\d+)?$/i
1699
+ ];
1700
+ function isLocalBroker(broker) {
1701
+ return LOCAL_HOST_PATTERNS.some((re) => re.test(broker.trim()));
1702
+ }
1703
+ function resolveSecurityOptions(security, brokers, logger) {
1704
+ const hasRemoteBroker = brokers.some((b) => !isLocalBroker(b));
1705
+ if (!security?.sasl && security?.ssl !== true) {
1706
+ if (hasRemoteBroker && !security?.allowInsecure) {
1707
+ logger.warn(
1708
+ "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."
1709
+ );
1710
+ }
1711
+ return security;
1712
+ }
1713
+ if (security.sasl && security.ssl === void 0) {
1714
+ return { ...security, ssl: true };
1715
+ }
1716
+ if (security.sasl && security.ssl === false) {
1717
+ logger.warn(
1718
+ "SASL credentials are configured with `ssl: false` \u2014 credentials will be sent over plaintext. This is only safe on fully trusted networks."
1719
+ );
1720
+ }
1721
+ return security;
1722
+ }
1723
+
1517
1724
  // src/client/kafka.client/producer/lifecycle.ts
1518
1725
  var _activeTransactionalIds = /* @__PURE__ */ new Set();
1519
1726
  async function ensureTopic(ctx, topic2) {
@@ -1647,6 +1854,7 @@ async function recoverLamportClockImpl(ctx, topics) {
1647
1854
  const remaining = new Set(
1648
1855
  partitionsToRead.map((p) => `${p.topic}:${p.partition}`)
1649
1856
  );
1857
+ let settled = false;
1650
1858
  const cleanup = () => {
1651
1859
  consumer.disconnect().catch(() => {
1652
1860
  }).finally(() => {
@@ -1654,6 +1862,16 @@ async function recoverLamportClockImpl(ctx, topics) {
1654
1862
  });
1655
1863
  });
1656
1864
  };
1865
+ const timeoutTimer = setTimeout(() => {
1866
+ if (settled) return;
1867
+ settled = true;
1868
+ ctx.logger.warn(
1869
+ `Clock recovery: timed out after ${ctx.clockRecoveryTimeoutMs} ms with ${remaining.size} partition(s) unread \u2014 proceeding with partial result`
1870
+ );
1871
+ cleanup();
1872
+ resolve();
1873
+ }, ctx.clockRecoveryTimeoutMs);
1874
+ timeoutTimer.unref?.();
1657
1875
  consumer.connect().then(async () => {
1658
1876
  const uniqueTopics = [
1659
1877
  ...new Set(partitionsToRead.map((p) => p.topic))
@@ -1674,13 +1892,18 @@ async function recoverLamportClockImpl(ctx, topics) {
1674
1892
  const clock = Number(raw);
1675
1893
  if (!Number.isNaN(clock) && clock > maxClock) maxClock = clock;
1676
1894
  }
1677
- if (remaining.size === 0) {
1895
+ if (remaining.size === 0 && !settled) {
1896
+ settled = true;
1897
+ clearTimeout(timeoutTimer);
1678
1898
  cleanup();
1679
1899
  resolve();
1680
1900
  }
1681
1901
  }
1682
1902
  })
1683
1903
  ).catch((err) => {
1904
+ if (settled) return;
1905
+ settled = true;
1906
+ clearTimeout(timeoutTimer);
1684
1907
  cleanup();
1685
1908
  reject(err);
1686
1909
  });
@@ -1721,6 +1944,15 @@ async function preparePayload(ctx, topicOrDesc, messages, compression) {
1721
1944
  await ensureTopic(ctx, payload.topic);
1722
1945
  return payload;
1723
1946
  }
1947
+ async function redirectToDelayed(ctx, payload, deliverAfterMs) {
1948
+ const until = String(Date.now() + deliverAfterMs);
1949
+ for (const m of payload.messages) {
1950
+ m.headers[HEADER_DELAYED_UNTIL] = until;
1951
+ m.headers[HEADER_DELAYED_TARGET] = payload.topic;
1952
+ }
1953
+ payload.topic = `${payload.topic}.delayed`;
1954
+ await ensureTopic(ctx, payload.topic);
1955
+ }
1724
1956
  async function sendMessageImpl(ctx, topicOrDesc, message, options = {}) {
1725
1957
  await waitIfThrottled(ctx);
1726
1958
  const payload = await preparePayload(
@@ -1738,6 +1970,9 @@ async function sendMessageImpl(ctx, topicOrDesc, message, options = {}) {
1738
1970
  ],
1739
1971
  options.compression
1740
1972
  );
1973
+ if (options.deliverAfterMs && options.deliverAfterMs > 0) {
1974
+ await redirectToDelayed(ctx, payload, options.deliverAfterMs);
1975
+ }
1741
1976
  await ctx.producer.send(payload);
1742
1977
  ctx.metrics.notifyAfterSend(payload.topic, payload.messages.length);
1743
1978
  }
@@ -1749,6 +1984,9 @@ async function sendBatchImpl(ctx, topicOrDesc, messages, options) {
1749
1984
  messages,
1750
1985
  options?.compression
1751
1986
  );
1987
+ if (options?.deliverAfterMs && options.deliverAfterMs > 0) {
1988
+ await redirectToDelayed(ctx, payload, options.deliverAfterMs);
1989
+ }
1752
1990
  await ctx.producer.send(payload);
1753
1991
  ctx.metrics.notifyAfterSend(payload.topic, payload.messages.length);
1754
1992
  }
@@ -1785,6 +2023,17 @@ async function transactionImpl(ctx, fn) {
1785
2023
  });
1786
2024
  }
1787
2025
  ctx.txProducer = await ctx.txProducerInitPromise;
2026
+ const prev = ctx._txChain;
2027
+ let release;
2028
+ ctx._txChain = new Promise((r) => release = r);
2029
+ await prev;
2030
+ try {
2031
+ await runTransaction(ctx, fn);
2032
+ } finally {
2033
+ release();
2034
+ }
2035
+ }
2036
+ async function runTransaction(ctx, fn) {
1788
2037
  const tx = await ctx.txProducer.transaction();
1789
2038
  try {
1790
2039
  const txCtx = {
@@ -1952,6 +2201,7 @@ async function startLevelConsumer(level, levelTopics, levelGroupId, originalTopi
1952
2201
  await consumer.commitOffsets([nextOffset]);
1953
2202
  return;
1954
2203
  }
2204
+ deps.onFailure?.(envelope);
1955
2205
  const exhausted = level >= currentMaxRetries;
1956
2206
  const reportedError = exhausted && currentMaxRetries > 1 ? new KafkaRetryExhaustedError(
1957
2207
  originalTopic,
@@ -2167,7 +2417,8 @@ async function setupConsumer(ctx, topics, mode, options) {
2167
2417
  options.autoCommit ?? true,
2168
2418
  ctx.consumerOpsDeps,
2169
2419
  options.partitionAssigner,
2170
- resolveReady
2420
+ resolveReady,
2421
+ options.groupInstanceId
2171
2422
  );
2172
2423
  const schemaMap = buildSchemaMap(
2173
2424
  stringTopics,
@@ -2179,6 +2430,9 @@ async function setupConsumer(ctx, topics, mode, options) {
2179
2430
  const subscribeTopics = [...topicNames, ...regexTopics];
2180
2431
  await ensureConsumerTopics(ctx, topicNames, dlq, options.deduplication);
2181
2432
  await consumer.connect();
2433
+ if (dlq || options.retryTopics || options.deduplication) {
2434
+ await ctx.producer.connect();
2435
+ }
2182
2436
  await subscribeWithRetry(
2183
2437
  consumer,
2184
2438
  subscribeTopics,
@@ -2193,9 +2447,8 @@ async function setupConsumer(ctx, topics, mode, options) {
2193
2447
  }
2194
2448
  function resolveDeduplicationContext(ctx, groupId, options) {
2195
2449
  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) };
2450
+ const store = options.store ?? new InMemoryDedupStore(ctx.dedupStates);
2451
+ return { options, store, groupId };
2199
2452
  }
2200
2453
  function messageDepsFor(ctx, gid, options) {
2201
2454
  const notifyRetry = ctx.metrics.notifyRetry.bind(ctx.metrics);
@@ -2209,9 +2462,10 @@ function messageDepsFor(ctx, gid, options) {
2209
2462
  notifyRetry(envelope, attempt, max);
2210
2463
  return options.onRetry(envelope, attempt, max);
2211
2464
  } : notifyRetry,
2212
- onDlq: (envelope, reason) => ctx.metrics.notifyDlq(envelope, reason, gid),
2465
+ onDlq: (envelope, reason) => ctx.metrics.notifyDlq(envelope, reason),
2213
2466
  onDuplicate: ctx.metrics.notifyDuplicate.bind(ctx.metrics),
2214
- onMessage: (envelope) => ctx.metrics.notifyMessage(envelope, gid)
2467
+ onMessage: (envelope) => ctx.metrics.notifyMessage(envelope, gid),
2468
+ onFailure: (envelope) => ctx.metrics.notifyFailure(envelope, gid)
2215
2469
  };
2216
2470
  }
2217
2471
  function buildRetryTopicDeps(ctx) {
@@ -2250,6 +2504,11 @@ async function launchRetryChain(ctx, gid, topicNames, handleMessage, opts) {
2250
2504
  schemaMap,
2251
2505
  {
2252
2506
  ...ctx.retryTopicDeps,
2507
+ // Bind circuit breaker events to the MAIN consumer group so failures and
2508
+ // successes inside the retry chain drive the same breaker as the main
2509
+ // consumer (the retry chain has no breaker config of its own).
2510
+ onFailure: (envelope) => ctx.metrics.notifyFailure(envelope, gid),
2511
+ onMessage: (envelope) => ctx.metrics.notifyMessage(envelope, gid),
2253
2512
  onLevelStarted: (levelGroupId) => {
2254
2513
  ctx.companionGroupIds.get(gid).push(levelGroupId);
2255
2514
  }
@@ -2265,7 +2524,15 @@ async function applyDeduplication(envelope, raw, dedup, dlq, deps) {
2265
2524
  const incomingClock = Number(clockRaw);
2266
2525
  if (Number.isNaN(incomingClock)) return false;
2267
2526
  const stateKey = `${envelope.topic}:${envelope.partition}`;
2268
- const lastProcessedClock = dedup.state.get(stateKey) ?? -1;
2527
+ let lastProcessedClock;
2528
+ try {
2529
+ lastProcessedClock = await dedup.store.getLastClock(dedup.groupId, stateKey) ?? -1;
2530
+ } catch (err) {
2531
+ deps.logger.error(
2532
+ `Dedup store getLastClock failed on ${envelope.topic}[${envelope.partition}] \u2014 treating message as not a duplicate (fail-open): ${err.message}`
2533
+ );
2534
+ return false;
2535
+ }
2269
2536
  if (incomingClock <= lastProcessedClock) {
2270
2537
  const meta = {
2271
2538
  incomingClock,
@@ -2295,7 +2562,13 @@ async function applyDeduplication(envelope, raw, dedup, dlq, deps) {
2295
2562
  }
2296
2563
  return true;
2297
2564
  }
2298
- dedup.state.set(stateKey, incomingClock);
2565
+ try {
2566
+ await dedup.store.setLastClock(dedup.groupId, stateKey, incomingClock);
2567
+ } catch (err) {
2568
+ deps.logger.error(
2569
+ `Dedup store setLastClock failed on ${envelope.topic}[${envelope.partition}] \u2014 processing message anyway (fail-open): ${err.message}`
2570
+ );
2571
+ }
2299
2572
  return false;
2300
2573
  }
2301
2574
  async function parseSingleMessage(message, topic2, partition, schemaMap, interceptors, dlq, deps) {
@@ -2908,7 +3181,7 @@ function stopConsumerByGid(ctx, gid) {
2908
3181
  return stopConsumerImpl(ctx, gid);
2909
3182
  }
2910
3183
 
2911
- // src/client/kafka.client/consumer/window.ts
3184
+ // src/client/kafka.client/consumer/features/window.ts
2912
3185
  async function startWindowConsumerImpl(ctx, topic2, handler, options) {
2913
3186
  const { maxMessages, maxMs, ...consumerOptions } = options;
2914
3187
  if (maxMessages <= 0)
@@ -2922,6 +3195,7 @@ async function startWindowConsumerImpl(ctx, topic2, handler, options) {
2922
3195
  const buffer = [];
2923
3196
  let flushTimer = null;
2924
3197
  let windowStart = 0;
3198
+ const onLost = consumerOptions.onMessageLost ?? ctx.onMessageLost;
2925
3199
  const flush = async (trigger) => {
2926
3200
  if (flushTimer !== null) {
2927
3201
  clearTimeout(flushTimer);
@@ -2929,17 +3203,32 @@ async function startWindowConsumerImpl(ctx, topic2, handler, options) {
2929
3203
  }
2930
3204
  if (buffer.length === 0) return;
2931
3205
  const envelopes = buffer.splice(0);
2932
- await handler(envelopes, { trigger, windowStart, windowEnd: Date.now() });
3206
+ try {
3207
+ await handler(envelopes, { trigger, windowStart, windowEnd: Date.now() });
3208
+ } catch (err) {
3209
+ const error = toError(err);
3210
+ ctx.logger.error(
3211
+ `startWindowConsumer: ${trigger}-triggered flush failed \u2014 window of ${envelopes.length} message(s) lost:`,
3212
+ error.stack
3213
+ );
3214
+ for (const envelope of envelopes) {
3215
+ await Promise.resolve(
3216
+ onLost?.({
3217
+ topic: envelope.topic,
3218
+ error,
3219
+ attempt: 0,
3220
+ headers: envelope.headers
3221
+ })
3222
+ ).catch(() => {
3223
+ });
3224
+ }
3225
+ }
2933
3226
  };
2934
3227
  const scheduleFlush = () => {
2935
3228
  if (flushTimer !== null) return;
2936
3229
  flushTimer = setTimeout(() => {
2937
3230
  flushTimer = null;
2938
- flush("time").catch((err) => {
2939
- ctx.logger.warn(
2940
- `startWindowConsumer: time-triggered flush error \u2014 ${toError(err).message}`
2941
- );
2942
- });
3231
+ void flush("time");
2943
3232
  }, maxMs);
2944
3233
  };
2945
3234
  const handle = await startConsumerImpl(
@@ -2955,40 +3244,13 @@ async function startWindowConsumerImpl(ctx, topic2, handler, options) {
2955
3244
  );
2956
3245
  const originalStop = handle.stop.bind(handle);
2957
3246
  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
- }
3247
+ await flush("time");
2986
3248
  return originalStop();
2987
3249
  };
2988
3250
  return handle;
2989
3251
  }
2990
3252
 
2991
- // src/client/kafka.client/consumer/routed.ts
3253
+ // src/client/kafka.client/consumer/features/routed.ts
2992
3254
  async function startRoutedConsumerImpl(ctx, topics, routing, options) {
2993
3255
  const { header, routes, fallback } = routing;
2994
3256
  const handleMessage = async (envelope) => {
@@ -3003,7 +3265,118 @@ async function startRoutedConsumerImpl(ctx, topics, routing, options) {
3003
3265
  return startConsumerImpl(ctx, topics, handleMessage, options);
3004
3266
  }
3005
3267
 
3006
- // src/client/kafka.client/consumer/snapshot.ts
3268
+ // src/client/kafka.client/consumer/features/delayed.ts
3269
+ function delayedTopicName(topic2) {
3270
+ return `${topic2}.delayed`;
3271
+ }
3272
+ async function startDelayedRelayImpl(ctx, topics, options) {
3273
+ if (topics.length === 0) {
3274
+ throw new Error("startDelayedRelay: at least one topic is required");
3275
+ }
3276
+ const gid = options?.groupId ?? `${ctx.defaultGroupId}-delayed-relay`;
3277
+ if (ctx.runningConsumers.has(gid)) {
3278
+ throw new Error(
3279
+ `startDelayedRelay("${gid}") called twice \u2014 this group is already consuming. Call stopConsumer("${gid}") first or pass a different groupId.`
3280
+ );
3281
+ }
3282
+ const delayedTopics = topics.map(delayedTopicName);
3283
+ for (const t of delayedTopics) await ensureTopic(ctx, t);
3284
+ const txProducer = await createRetryTxProducer(ctx, `${gid}-tx`);
3285
+ let resolveReady;
3286
+ const readyPromise = new Promise((resolve) => {
3287
+ resolveReady = resolve;
3288
+ });
3289
+ const consumer = getOrCreateConsumer(
3290
+ gid,
3291
+ false,
3292
+ false,
3293
+ ctx.consumerOpsDeps,
3294
+ void 0,
3295
+ resolveReady
3296
+ );
3297
+ await consumer.connect();
3298
+ await subscribeWithRetry(consumer, delayedTopics, ctx.logger);
3299
+ await consumer.run({
3300
+ eachMessage: async ({ topic: stagingTopic, partition, message }) => {
3301
+ const nextOffset = {
3302
+ topic: stagingTopic,
3303
+ partition,
3304
+ offset: (parseInt(message.offset, 10) + 1).toString()
3305
+ };
3306
+ if (!message.value) {
3307
+ await consumer.commitOffsets([nextOffset]);
3308
+ return;
3309
+ }
3310
+ const headers = decodeHeaders(message.headers);
3311
+ const target = headers[HEADER_DELAYED_TARGET] ?? stagingTopic.replace(/\.delayed$/, "");
3312
+ const until = parseInt(
3313
+ headers[HEADER_DELAYED_UNTIL] ?? "0",
3314
+ 10
3315
+ );
3316
+ const remaining = until - Date.now();
3317
+ if (remaining > 0) {
3318
+ consumer.pause([{ topic: stagingTopic, partitions: [partition] }]);
3319
+ await sleep(remaining);
3320
+ consumer.resume([{ topic: stagingTopic, partitions: [partition] }]);
3321
+ }
3322
+ const forwardHeaders = Object.fromEntries(
3323
+ Object.entries(headers).filter(
3324
+ ([k]) => k !== HEADER_DELAYED_UNTIL && k !== HEADER_DELAYED_TARGET
3325
+ )
3326
+ );
3327
+ const tx = await txProducer.transaction();
3328
+ try {
3329
+ await tx.send({
3330
+ topic: target,
3331
+ messages: [
3332
+ {
3333
+ value: message.value.toString(),
3334
+ key: message.key ? message.key.toString() : null,
3335
+ headers: forwardHeaders
3336
+ }
3337
+ ]
3338
+ });
3339
+ await tx.sendOffsets({
3340
+ consumer,
3341
+ topics: [
3342
+ {
3343
+ topic: nextOffset.topic,
3344
+ partitions: [
3345
+ { partition: nextOffset.partition, offset: nextOffset.offset }
3346
+ ]
3347
+ }
3348
+ ]
3349
+ });
3350
+ await tx.commit();
3351
+ ctx.logger.debug?.(
3352
+ `Delayed message relayed to "${target}" (deadline ${new Date(until).toISOString()})`
3353
+ );
3354
+ } catch (txErr) {
3355
+ try {
3356
+ await tx.abort();
3357
+ } catch {
3358
+ }
3359
+ ctx.logger.error(
3360
+ `Delayed relay to "${target}" failed \u2014 message will be redelivered:`,
3361
+ toError(txErr).stack
3362
+ );
3363
+ }
3364
+ }
3365
+ });
3366
+ ctx.runningConsumers.set(gid, "eachMessage");
3367
+ ctx.logger.log(
3368
+ `Delayed relay started for: ${delayedTopics.join(", ")} (group: ${gid})`
3369
+ );
3370
+ return {
3371
+ groupId: gid,
3372
+ ready: () => readyPromise,
3373
+ stop: async () => {
3374
+ await stopConsumerImpl(ctx, gid);
3375
+ }
3376
+ };
3377
+ }
3378
+
3379
+ // src/client/kafka.client/consumer/features/snapshot.ts
3007
3380
  async function readSnapshotImpl(ctx, topic2, options = {}) {
3008
3381
  await ctx.adminOps.ensureConnected();
3009
3382
  let offsets;
@@ -3269,6 +3642,7 @@ var KafkaClient = class {
3269
3642
  * ```
3270
3643
  */
3271
3644
  constructor(clientId, groupId, brokers, options) {
3645
+ validateClientOptions(clientId, groupId, brokers, options);
3272
3646
  this.clientId = clientId;
3273
3647
  const logger = options?.logger ?? {
3274
3648
  log: (msg) => console.log(`[KafkaClient:${clientId}] ${msg}`),
@@ -3276,7 +3650,8 @@ var KafkaClient = class {
3276
3650
  error: (msg, ...args) => console.error(`[KafkaClient:${clientId}] ${msg}`, ...args),
3277
3651
  debug: (msg, ...args) => console.debug(`[KafkaClient:${clientId}] ${msg}`, ...args)
3278
3652
  };
3279
- const transport = options?.transport ?? new ConfluentTransport(clientId, brokers);
3653
+ const security = resolveSecurityOptions(options?.security, brokers, logger);
3654
+ const transport = options?.transport ?? new ConfluentTransport(clientId, brokers, security);
3280
3655
  const producer = transport.producer();
3281
3656
  const runningConsumers = /* @__PURE__ */ new Map();
3282
3657
  const consumers = /* @__PURE__ */ new Map();
@@ -3310,6 +3685,7 @@ var KafkaClient = class {
3310
3685
  numPartitions: options?.numPartitions ?? 1,
3311
3686
  txId: options?.transactionalId ?? `${clientId}-tx`,
3312
3687
  clockRecoveryTopics: options?.clockRecovery?.topics ?? [],
3688
+ clockRecoveryTimeoutMs: options?.clockRecovery?.timeoutMs ?? 3e4,
3313
3689
  lagThrottleOpts: options?.lagThrottle,
3314
3690
  instrumentation: options?.instrumentation ?? [],
3315
3691
  onMessageLost: options?.onMessageLost,
@@ -3319,6 +3695,7 @@ var KafkaClient = class {
3319
3695
  producer,
3320
3696
  txProducer: void 0,
3321
3697
  txProducerInitPromise: void 0,
3698
+ _txChain: Promise.resolve(),
3322
3699
  retryTxProducers: /* @__PURE__ */ new Map(),
3323
3700
  consumers,
3324
3701
  runningConsumers,
@@ -3442,6 +3819,31 @@ var KafkaClient = class {
3442
3819
  startRoutedConsumer(topics, routing, options) {
3443
3820
  return startRoutedConsumerImpl(this.ctx, topics, routing, options);
3444
3821
  }
3822
+ // ── Consumer: delayed delivery relay ──────────────────────────────
3823
+ /**
3824
+ * Start a relay that delivers messages produced with
3825
+ * `SendOptions.deliverAfterMs` from `<topic>.delayed` to their target topic
3826
+ * once their deadline passes.
3827
+ *
3828
+ * Forwarding is transactional (produce + source-offset commit are atomic),
3829
+ * so no duplicates are relayed even if the relay crashes mid-forward.
3830
+ * Delivery time is a lower bound — the relay must be running for delayed
3831
+ * messages to be delivered at all.
3832
+ *
3833
+ * @param topics Target topic name(s) whose `<topic>.delayed` staging topics to relay.
3834
+ * @param options Optional `groupId` override (default: `<defaultGroupId>-delayed-relay`).
3835
+ *
3836
+ * @example
3837
+ * ```ts
3838
+ * await kafka.startDelayedRelay(['orders.reminder']);
3839
+ * await kafka.sendMessage('orders.reminder', payload, { deliverAfterMs: 60_000 });
3840
+ * // → delivered to orders.reminder ~60 s later
3841
+ * ```
3842
+ */
3843
+ async startDelayedRelay(topics, options) {
3844
+ const list = Array.isArray(topics) ? topics : [topics];
3845
+ return startDelayedRelayImpl(this.ctx, list, options);
3846
+ }
3445
3847
  // ── Consumer: transactional EOS ───────────────────────────────────
3446
3848
  /** @inheritDoc */
3447
3849
  async startTransactionalConsumer(topics, handler, options = {}) {
@@ -3587,34 +3989,884 @@ var KafkaClient = class {
3587
3989
  function topic(name) {
3588
3990
  return {
3589
3991
  /** Provide an explicit message type without a runtime schema. */
3590
- type: () => ({
3992
+ type: () => keyable({
3591
3993
  __topic: name,
3592
3994
  __type: void 0
3593
3995
  }),
3594
- schema: (schema) => ({
3996
+ schema: (schema) => keyable({
3595
3997
  __topic: name,
3596
3998
  __type: void 0,
3597
3999
  __schema: schema
3598
4000
  })
3599
4001
  };
3600
4002
  }
4003
+ function keyable(desc) {
4004
+ return {
4005
+ ...desc,
4006
+ key: (extractor) => ({
4007
+ ...desc,
4008
+ __key: extractor
4009
+ })
4010
+ };
4011
+ }
4012
+
4013
+ // src/client/message/versioned-schema.ts
4014
+ function versionedSchema(versions, options) {
4015
+ const registered = Object.keys(versions).map(Number).filter((v) => Number.isInteger(v) && v > 0).sort((a, b) => a - b);
4016
+ if (registered.length === 0) {
4017
+ throw new Error(
4018
+ "versionedSchema: at least one schema version must be registered (keys must be positive integers)"
4019
+ );
4020
+ }
4021
+ const latestVersion = registered[registered.length - 1];
4022
+ return {
4023
+ async parse(data, ctx) {
4024
+ const version = ctx?.version ?? latestVersion;
4025
+ const schema = versions[version];
4026
+ if (!schema) {
4027
+ throw new Error(
4028
+ `versionedSchema: no schema registered for version ${version}${ctx?.topic ? ` (topic "${ctx.topic}")` : ""} \u2014 registered versions: ${registered.join(", ")}`
4029
+ );
4030
+ }
4031
+ const parsed = await schema.parse(data, ctx);
4032
+ if (version < latestVersion && options?.migrate) {
4033
+ return options.migrate(parsed, version, latestVersion);
4034
+ }
4035
+ return parsed;
4036
+ }
4037
+ };
4038
+ }
4039
+
4040
+ // src/client/message/schema-registry.ts
4041
+ var SchemaRegistryClient = class {
4042
+ constructor(options) {
4043
+ this.options = options;
4044
+ if (!options.baseUrl) {
4045
+ throw new Error("SchemaRegistryClient: baseUrl is required");
4046
+ }
4047
+ this.fetchFn = options.fetchFn ?? fetch;
4048
+ this.cacheTtlMs = options.cacheTtlMs ?? 3e5;
4049
+ }
4050
+ options;
4051
+ fetchFn;
4052
+ cacheTtlMs;
4053
+ latestCache = /* @__PURE__ */ new Map();
4054
+ headers() {
4055
+ const h = {
4056
+ "Content-Type": "application/vnd.schemaregistry.v1+json"
4057
+ };
4058
+ if (this.options.auth) {
4059
+ const { username, password } = this.options.auth;
4060
+ h["Authorization"] = "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
4061
+ }
4062
+ return h;
4063
+ }
4064
+ async request(method, path, body) {
4065
+ const url = `${this.options.baseUrl.replace(/\/$/, "")}${path}`;
4066
+ const res = await this.fetchFn(url, {
4067
+ method,
4068
+ headers: this.headers(),
4069
+ ...body !== void 0 && { body: JSON.stringify(body) }
4070
+ });
4071
+ if (!res.ok) {
4072
+ const text = await res.text().catch(() => "");
4073
+ throw new Error(
4074
+ `SchemaRegistry ${method} ${path} failed: ${res.status} ${res.statusText}${text ? ` \u2014 ${text}` : ""}`
4075
+ );
4076
+ }
4077
+ return await res.json();
4078
+ }
4079
+ /** Fetch the latest schema registered under `subject`. Cached for `cacheTtlMs`. */
4080
+ async getLatestSchema(subject) {
4081
+ const cached = this.latestCache.get(subject);
4082
+ if (cached && cached.expiresAt > Date.now()) return cached.value;
4083
+ const raw = await this.request("GET", `/subjects/${encodeURIComponent(subject)}/versions/latest`);
4084
+ const value = {
4085
+ id: raw.id,
4086
+ version: raw.version,
4087
+ schema: raw.schema
4088
+ };
4089
+ this.latestCache.set(subject, {
4090
+ value,
4091
+ expiresAt: Date.now() + this.cacheTtlMs
4092
+ });
4093
+ return value;
4094
+ }
4095
+ /** Fetch a specific schema version of a subject. */
4096
+ async getSchemaVersion(subject, version) {
4097
+ const raw = await this.request(
4098
+ "GET",
4099
+ `/subjects/${encodeURIComponent(subject)}/versions/${version}`
4100
+ );
4101
+ return { id: raw.id, version: raw.version, schema: raw.schema };
4102
+ }
4103
+ /**
4104
+ * Register a schema under `subject` (idempotent — re-registering the same
4105
+ * schema returns the existing id). Returns the registry-assigned schema id.
4106
+ */
4107
+ async registerSchema(subject, schema, schemaType = "JSON") {
4108
+ this.latestCache.delete(subject);
4109
+ return this.request(
4110
+ "POST",
4111
+ `/subjects/${encodeURIComponent(subject)}/versions`,
4112
+ { schema, schemaType }
4113
+ );
4114
+ }
4115
+ /**
4116
+ * Test `schema` against the subject's compatibility policy without registering.
4117
+ * Returns `true` when the registry reports the schema as compatible.
4118
+ */
4119
+ async checkCompatibility(subject, schema, schemaType = "JSON") {
4120
+ const res = await this.request(
4121
+ "POST",
4122
+ `/compatibility/subjects/${encodeURIComponent(subject)}/versions/latest`,
4123
+ { schema, schemaType }
4124
+ );
4125
+ return res.is_compatible;
4126
+ }
4127
+ };
4128
+ function registrySchema(client, subject, options) {
4129
+ const enforceVersion = options?.enforceVersion ?? true;
4130
+ return {
4131
+ async parse(data, ctx) {
4132
+ const latest = await client.getLatestSchema(subject);
4133
+ if (enforceVersion && ctx?.version !== void 0 && ctx.version > latest.version) {
4134
+ throw new Error(
4135
+ `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`
4136
+ );
4137
+ }
4138
+ if (options?.validator) {
4139
+ return options.validator.parse(data, ctx);
4140
+ }
4141
+ return data;
4142
+ }
4143
+ };
4144
+ }
4145
+
4146
+ // src/client/outbox/outbox.store.ts
4147
+ var InMemoryOutboxStore = class {
4148
+ /** Insertion-ordered rows. `published` flips to true after `markPublished`. */
4149
+ rows = [];
4150
+ /**
4151
+ * Append a message to the outbox. In a real store this INSERT would run inside
4152
+ * the same DB transaction as the corresponding business write.
4153
+ */
4154
+ add(message) {
4155
+ this.rows.push({ message, published: false });
4156
+ }
4157
+ async fetchUnpublished(limit) {
4158
+ const out = [];
4159
+ for (const row of this.rows) {
4160
+ if (row.published) continue;
4161
+ out.push(row.message);
4162
+ if (out.length >= limit) break;
4163
+ }
4164
+ return out;
4165
+ }
4166
+ async markPublished(ids) {
4167
+ const idSet = new Set(ids);
4168
+ for (const row of this.rows) {
4169
+ if (idSet.has(row.message.id)) row.published = true;
4170
+ }
4171
+ }
4172
+ /** Test helper: count of rows not yet marked published. */
4173
+ get pendingCount() {
4174
+ return this.rows.filter((r) => !r.published).length;
4175
+ }
4176
+ /** Test helper: count of rows marked published. */
4177
+ get publishedCount() {
4178
+ return this.rows.filter((r) => r.published).length;
4179
+ }
4180
+ };
4181
+
4182
+ // src/client/outbox/outbox.relay.ts
4183
+ function toError2(e) {
4184
+ return e instanceof Error ? e : new Error(String(e));
4185
+ }
4186
+ function startOutboxRelay(kafka, store, options = {}) {
4187
+ const pollIntervalMs = options.pollIntervalMs ?? 1e3;
4188
+ const batchSize = options.batchSize ?? 100;
4189
+ const onError = options.onError ?? ((error, batch) => {
4190
+ console.error(
4191
+ `[outbox] batch of ${batch.length} message(s) failed \u2014 will retry:`,
4192
+ error
4193
+ );
4194
+ });
4195
+ const onPublished = options.onPublished;
4196
+ let stopped = false;
4197
+ let running = false;
4198
+ let inFlight = Promise.resolve();
4199
+ const iterate = async () => {
4200
+ let batch = [];
4201
+ try {
4202
+ batch = await store.fetchUnpublished(batchSize);
4203
+ if (batch.length === 0) return;
4204
+ await kafka.transaction(async (tx) => {
4205
+ for (const msg of batch) {
4206
+ await tx.send(msg.topic, msg.payload, {
4207
+ key: msg.key,
4208
+ headers: msg.headers,
4209
+ correlationId: msg.correlationId,
4210
+ eventId: msg.eventId
4211
+ });
4212
+ }
4213
+ });
4214
+ await store.markPublished(batch.map((m) => m.id));
4215
+ onPublished?.(batch.length);
4216
+ } catch (err) {
4217
+ onError(toError2(err), batch);
4218
+ }
4219
+ };
4220
+ const tick = () => {
4221
+ if (stopped || running) return;
4222
+ running = true;
4223
+ inFlight = iterate().finally(() => {
4224
+ running = false;
4225
+ });
4226
+ };
4227
+ const timer = setInterval(tick, pollIntervalMs);
4228
+ timer.unref?.();
4229
+ return {
4230
+ stop: async () => {
4231
+ stopped = true;
4232
+ clearInterval(timer);
4233
+ await inFlight;
4234
+ }
4235
+ };
4236
+ }
4237
+
4238
+ // src/client/security/providers.ts
4239
+ var defaultImport = (specifier) => import(specifier);
4240
+ function awsMskIamProvider(options) {
4241
+ const importFn = options.importFn ?? defaultImport;
4242
+ return async () => {
4243
+ let signer;
4244
+ try {
4245
+ signer = await importFn("aws-msk-iam-sasl-signer-js");
4246
+ } catch {
4247
+ throw new Error(
4248
+ "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."
4249
+ );
4250
+ }
4251
+ const { token, expiryTime } = await signer.generateAuthToken({
4252
+ region: options.region
4253
+ });
4254
+ return {
4255
+ value: token,
4256
+ principal: "msk-iam",
4257
+ // expiryTime is epoch ms per the signer's contract
4258
+ lifetimeMs: expiryTime
4259
+ };
4260
+ };
4261
+ }
4262
+ function gcpAccessTokenProvider(options = {}) {
4263
+ const importFn = options.importFn ?? defaultImport;
4264
+ const ttlMs = options.tokenTtlMs ?? 50 * 6e4;
4265
+ return async () => {
4266
+ let lib;
4267
+ try {
4268
+ lib = await importFn("google-auth-library");
4269
+ } catch {
4270
+ throw new Error(
4271
+ "gcpAccessTokenProvider: package 'google-auth-library' is not installed. Run `npm install google-auth-library` to enable GCP authentication."
4272
+ );
4273
+ }
4274
+ const auth = new lib.GoogleAuth({
4275
+ scopes: options.scopes ?? ["https://www.googleapis.com/auth/cloud-platform"]
4276
+ });
4277
+ const token = await auth.getAccessToken();
4278
+ if (!token) {
4279
+ throw new Error(
4280
+ "gcpAccessTokenProvider: google-auth-library returned no access token \u2014 check Application Default Credentials."
4281
+ );
4282
+ }
4283
+ return {
4284
+ value: token,
4285
+ principal: options.principal ?? "gcp",
4286
+ lifetimeMs: Date.now() + ttlMs
4287
+ };
4288
+ };
4289
+ }
4290
+
4291
+ // src/client/security/acl.ts
4292
+ function addResource(out, r) {
4293
+ const key = `${r.resourceType}:${r.patternType}:${r.name}`;
4294
+ const existing = out.get(key);
4295
+ if (existing) {
4296
+ for (const op of r.operations)
4297
+ if (!existing.operations.includes(op)) existing.operations.push(op);
4298
+ if (!existing.reason.includes(r.reason))
4299
+ existing.reason += `; ${r.reason}`;
4300
+ } else {
4301
+ out.set(key, { ...r, operations: [...r.operations] });
4302
+ }
4303
+ }
4304
+ function describeRequiredAcls(input) {
4305
+ const out = /* @__PURE__ */ new Map();
4306
+ const f = input.features ?? {};
4307
+ const produce = input.produceTopics ?? [];
4308
+ const consume = input.consumeTopics ?? [];
4309
+ const groups = input.groupIds ?? [];
4310
+ for (const t of produce) {
4311
+ addResource(out, {
4312
+ resourceType: "topic",
4313
+ patternType: "literal",
4314
+ name: t,
4315
+ operations: ["WRITE", "DESCRIBE"],
4316
+ reason: "sendMessage/sendBatch"
4317
+ });
4318
+ }
4319
+ for (const t of consume) {
4320
+ addResource(out, {
4321
+ resourceType: "topic",
4322
+ patternType: "literal",
4323
+ name: t,
4324
+ operations: ["READ", "DESCRIBE"],
4325
+ reason: "startConsumer"
4326
+ });
4327
+ }
4328
+ for (const g of groups) {
4329
+ addResource(out, {
4330
+ resourceType: "group",
4331
+ patternType: "literal",
4332
+ name: g,
4333
+ operations: ["READ", "DESCRIBE"],
4334
+ reason: "consumer group membership + offset commits"
4335
+ });
4336
+ }
4337
+ if (f.dlq) {
4338
+ for (const t of consume) {
4339
+ addResource(out, {
4340
+ resourceType: "topic",
4341
+ patternType: "literal",
4342
+ name: `${t}.dlq`,
4343
+ operations: ["WRITE", "DESCRIBE"],
4344
+ reason: "dlq: true \u2014 failed messages routed to DLQ"
4345
+ });
4346
+ }
4347
+ }
4348
+ if (f.retryTopics) {
4349
+ for (const t of consume) {
4350
+ for (let level = 1; level <= f.retryTopics.maxRetries; level++) {
4351
+ addResource(out, {
4352
+ resourceType: "topic",
4353
+ patternType: "literal",
4354
+ name: `${t}.retry.${level}`,
4355
+ operations: ["READ", "WRITE", "DESCRIBE"],
4356
+ reason: "retryTopics \u2014 retry chain produce + companion consume"
4357
+ });
4358
+ }
4359
+ }
4360
+ for (const g of groups) {
4361
+ addResource(out, {
4362
+ resourceType: "group",
4363
+ patternType: "prefixed",
4364
+ name: `${g}-retry.`,
4365
+ operations: ["READ", "DESCRIBE"],
4366
+ reason: "retryTopics \u2014 companion retry-level consumer groups"
4367
+ });
4368
+ addResource(out, {
4369
+ resourceType: "transactional-id",
4370
+ patternType: "prefixed",
4371
+ name: `${g}-`,
4372
+ operations: ["WRITE", "DESCRIBE"],
4373
+ reason: "retryTopics \u2014 EOS routing transactions per retry level"
4374
+ });
4375
+ }
4376
+ }
4377
+ if (f.delayedDelivery) {
4378
+ for (const t of [.../* @__PURE__ */ new Set([...produce, ...consume])]) {
4379
+ addResource(out, {
4380
+ resourceType: "topic",
4381
+ patternType: "literal",
4382
+ name: `${t}.delayed`,
4383
+ operations: ["READ", "WRITE", "DESCRIBE"],
4384
+ reason: "deliverAfterMs staging + startDelayedRelay consume"
4385
+ });
4386
+ }
4387
+ for (const g of groups) {
4388
+ addResource(out, {
4389
+ resourceType: "group",
4390
+ patternType: "literal",
4391
+ name: `${g}-delayed-relay`,
4392
+ operations: ["READ", "DESCRIBE"],
4393
+ reason: "startDelayedRelay consumer group"
4394
+ });
4395
+ addResource(out, {
4396
+ resourceType: "transactional-id",
4397
+ patternType: "literal",
4398
+ name: `${g}-delayed-relay-tx`,
4399
+ operations: ["WRITE", "DESCRIBE"],
4400
+ reason: "startDelayedRelay transactional forwarding"
4401
+ });
4402
+ }
4403
+ }
4404
+ if (f.duplicatesTopic) {
4405
+ if (typeof f.duplicatesTopic === "string") {
4406
+ addResource(out, {
4407
+ resourceType: "topic",
4408
+ patternType: "literal",
4409
+ name: f.duplicatesTopic,
4410
+ operations: ["WRITE", "DESCRIBE"],
4411
+ reason: "deduplication.strategy 'topic' \u2014 custom duplicates topic"
4412
+ });
4413
+ } else {
4414
+ for (const t of consume) {
4415
+ addResource(out, {
4416
+ resourceType: "topic",
4417
+ patternType: "literal",
4418
+ name: `${t}.duplicates`,
4419
+ operations: ["WRITE", "DESCRIBE"],
4420
+ reason: "deduplication.strategy 'topic'"
4421
+ });
4422
+ }
4423
+ }
4424
+ }
4425
+ if (f.dlqReplay) {
4426
+ for (const t of consume) {
4427
+ addResource(out, {
4428
+ resourceType: "group",
4429
+ patternType: "prefixed",
4430
+ name: `${t}.dlq-replay`,
4431
+ operations: ["READ", "DESCRIBE", "DELETE"],
4432
+ reason: "replayDlq \u2014 ephemeral/stable replay groups (deleted after use)"
4433
+ });
4434
+ addResource(out, {
4435
+ resourceType: "topic",
4436
+ patternType: "literal",
4437
+ name: `${t}.dlq`,
4438
+ operations: ["READ", "DESCRIBE"],
4439
+ reason: "replayDlq \u2014 reads the DLQ"
4440
+ });
4441
+ }
4442
+ }
4443
+ if (f.snapshots) {
4444
+ addResource(out, {
4445
+ resourceType: "group",
4446
+ patternType: "prefixed",
4447
+ name: `${input.clientId}-snapshot-`,
4448
+ operations: ["READ", "DESCRIBE", "DELETE"],
4449
+ reason: "readSnapshot \u2014 timestamped ephemeral groups (deleted after use)"
4450
+ });
4451
+ }
4452
+ if (f.clockRecovery) {
4453
+ addResource(out, {
4454
+ resourceType: "group",
4455
+ patternType: "prefixed",
4456
+ name: `${input.clientId}-clock-recovery-`,
4457
+ operations: ["READ", "DESCRIBE", "DELETE"],
4458
+ reason: "clockRecovery \u2014 timestamped ephemeral groups (deleted after use)"
4459
+ });
4460
+ }
4461
+ if (f.transactions) {
4462
+ addResource(out, {
4463
+ resourceType: "transactional-id",
4464
+ patternType: "literal",
4465
+ name: `${input.clientId}-tx`,
4466
+ operations: ["WRITE", "DESCRIBE"],
4467
+ reason: "transaction() \u2014 default transactionalId (override-aware: adjust if you set one)"
4468
+ });
4469
+ }
4470
+ if (f.autoCreateTopics) {
4471
+ addResource(out, {
4472
+ resourceType: "cluster",
4473
+ patternType: "literal",
4474
+ name: "kafka-cluster",
4475
+ operations: ["CREATE"],
4476
+ reason: "autoCreateTopics: true \u2014 not recommended in production"
4477
+ });
4478
+ }
4479
+ return [...out.values()];
4480
+ }
4481
+ function toKafkaAclCommands(resources, principal, bootstrapServer = "<bootstrap-server>") {
4482
+ return resources.map((r) => {
4483
+ const ops = r.operations.map((o) => `--operation ${o}`).join(" ");
4484
+ const resourceFlag = r.resourceType === "topic" ? `--topic '${r.name}'` : r.resourceType === "group" ? `--group '${r.name}'` : r.resourceType === "transactional-id" ? `--transactional-id '${r.name}'` : "--cluster";
4485
+ const pattern = r.patternType === "prefixed" ? " --resource-pattern-type prefixed" : "";
4486
+ return `kafka-acls.sh --bootstrap-server ${bootstrapServer} --add --allow-principal '${principal}' ${ops} ${resourceFlag}${pattern} # ${r.reason}`;
4487
+ });
4488
+ }
4489
+ var MSK_TOPIC_ACTIONS = {
4490
+ READ: ["kafka-cluster:ReadData", "kafka-cluster:DescribeTopic"],
4491
+ WRITE: ["kafka-cluster:WriteData", "kafka-cluster:DescribeTopic"],
4492
+ DESCRIBE: ["kafka-cluster:DescribeTopic"],
4493
+ CREATE: ["kafka-cluster:CreateTopic"],
4494
+ DELETE: ["kafka-cluster:DeleteTopic"]
4495
+ };
4496
+ var MSK_GROUP_ACTIONS = {
4497
+ READ: ["kafka-cluster:AlterGroup", "kafka-cluster:DescribeGroup"],
4498
+ DESCRIBE: ["kafka-cluster:DescribeGroup"],
4499
+ DELETE: ["kafka-cluster:DeleteGroup"]
4500
+ };
4501
+ var MSK_TX_ACTIONS = {
4502
+ WRITE: [
4503
+ "kafka-cluster:AlterTransactionalId",
4504
+ "kafka-cluster:DescribeTransactionalId"
4505
+ ],
4506
+ DESCRIBE: ["kafka-cluster:DescribeTransactionalId"]
4507
+ };
4508
+ function toMskIamPolicy(resources, cluster) {
4509
+ const { region, accountId, clusterName, clusterUuid } = cluster;
4510
+ const arn = (type, name) => `arn:aws:kafka:${region}:${accountId}:${type}/${clusterName}/${clusterUuid}/${name}`;
4511
+ const statements = [
4512
+ {
4513
+ Sid: "Connect",
4514
+ Effect: "Allow",
4515
+ Action: ["kafka-cluster:Connect"],
4516
+ Resource: [
4517
+ `arn:aws:kafka:${region}:${accountId}:cluster/${clusterName}/${clusterUuid}`
4518
+ ]
4519
+ }
4520
+ ];
4521
+ let sid = 0;
4522
+ for (const r of resources) {
4523
+ const suffix = r.patternType === "prefixed" ? `${r.name}*` : r.name;
4524
+ let actions = [];
4525
+ let resource;
4526
+ if (r.resourceType === "topic") {
4527
+ actions = [...new Set(r.operations.flatMap((o) => MSK_TOPIC_ACTIONS[o] ?? []))];
4528
+ resource = arn("topic", suffix);
4529
+ } else if (r.resourceType === "group") {
4530
+ actions = [...new Set(r.operations.flatMap((o) => MSK_GROUP_ACTIONS[o] ?? []))];
4531
+ resource = arn("group", suffix);
4532
+ } else if (r.resourceType === "transactional-id") {
4533
+ actions = [...new Set(r.operations.flatMap((o) => MSK_TX_ACTIONS[o] ?? []))];
4534
+ resource = arn("transactional-id", suffix);
4535
+ } else {
4536
+ actions = ["kafka-cluster:CreateTopic"];
4537
+ resource = `arn:aws:kafka:${region}:${accountId}:topic/${clusterName}/${clusterUuid}/*`;
4538
+ }
4539
+ if (actions.length === 0 || !resource) continue;
4540
+ statements.push({
4541
+ Sid: `Acl${sid++}`,
4542
+ Effect: "Allow",
4543
+ Action: actions,
4544
+ Resource: [resource]
4545
+ });
4546
+ }
4547
+ return { Version: "2012-10-17", Statement: statements };
4548
+ }
4549
+
4550
+ // src/client/config/from-env.ts
4551
+ var TRUE_VALUES = /* @__PURE__ */ new Set(["true", "1", "yes"]);
4552
+ var FALSE_VALUES = /* @__PURE__ */ new Set(["false", "0", "no"]);
4553
+ function parseBool(name, raw) {
4554
+ const normalized = raw.trim().toLowerCase();
4555
+ if (TRUE_VALUES.has(normalized)) return true;
4556
+ if (FALSE_VALUES.has(normalized)) return false;
4557
+ throw new Error(
4558
+ `Invalid boolean for ${name}: "${raw}". Use one of true/false, 1/0, yes/no (case-insensitive).`
4559
+ );
4560
+ }
4561
+ function parseNum(name, raw) {
4562
+ const value = Number(raw.trim());
4563
+ if (Number.isNaN(value)) {
4564
+ throw new Error(`Invalid number for ${name}: "${raw}".`);
4565
+ }
4566
+ return value;
4567
+ }
4568
+ function parseList(raw) {
4569
+ return raw.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
4570
+ }
4571
+ function parseEnum(name, raw, allowed) {
4572
+ const value = raw.trim();
4573
+ if (!allowed.includes(value)) {
4574
+ throw new Error(
4575
+ `Invalid value for ${name}: "${raw}". Allowed: ${allowed.join(", ")}.`
4576
+ );
4577
+ }
4578
+ return value;
4579
+ }
4580
+ function readVar(env, key, apply) {
4581
+ const raw = env[key];
4582
+ if (raw === void 0 || raw.trim() === "") return;
4583
+ apply(raw);
4584
+ }
4585
+ function kafkaClientConfigFromEnv(env = process.env, prefix = "KAFKA_") {
4586
+ const options = {};
4587
+ const result = { options };
4588
+ readVar(env, `${prefix}CLIENT_ID`, (raw) => {
4589
+ result.clientId = raw.trim();
4590
+ });
4591
+ readVar(env, `${prefix}GROUP_ID`, (raw) => {
4592
+ result.groupId = raw.trim();
4593
+ });
4594
+ readVar(env, `${prefix}BROKERS`, (raw) => {
4595
+ result.brokers = parseList(raw);
4596
+ });
4597
+ readVar(env, `${prefix}AUTO_CREATE_TOPICS`, (raw) => {
4598
+ options.autoCreateTopics = parseBool(`${prefix}AUTO_CREATE_TOPICS`, raw);
4599
+ });
4600
+ readVar(env, `${prefix}STRICT_SCHEMAS`, (raw) => {
4601
+ options.strictSchemas = parseBool(`${prefix}STRICT_SCHEMAS`, raw);
4602
+ });
4603
+ readVar(env, `${prefix}NUM_PARTITIONS`, (raw) => {
4604
+ options.numPartitions = parseNum(`${prefix}NUM_PARTITIONS`, raw);
4605
+ });
4606
+ readVar(env, `${prefix}TRANSACTIONAL_ID`, (raw) => {
4607
+ options.transactionalId = raw.trim();
4608
+ });
4609
+ readVar(env, `${prefix}CLOCK_RECOVERY_TOPICS`, (raw) => {
4610
+ const topics = parseList(raw);
4611
+ if (topics.length === 0) return;
4612
+ options.clockRecovery = { topics };
4613
+ });
4614
+ readVar(env, `${prefix}CLOCK_RECOVERY_TIMEOUT_MS`, (raw) => {
4615
+ const timeoutMs = parseNum(`${prefix}CLOCK_RECOVERY_TIMEOUT_MS`, raw);
4616
+ if (options.clockRecovery) {
4617
+ options.clockRecovery.timeoutMs = timeoutMs;
4618
+ }
4619
+ });
4620
+ readVar(env, `${prefix}LAG_THROTTLE_MAX_LAG`, (raw) => {
4621
+ options.lagThrottle = {
4622
+ maxLag: parseNum(`${prefix}LAG_THROTTLE_MAX_LAG`, raw)
4623
+ };
4624
+ });
4625
+ readVar(env, `${prefix}LAG_THROTTLE_GROUP_ID`, (raw) => {
4626
+ if (options.lagThrottle) options.lagThrottle.groupId = raw.trim();
4627
+ });
4628
+ readVar(env, `${prefix}LAG_THROTTLE_POLL_INTERVAL_MS`, (raw) => {
4629
+ if (options.lagThrottle) {
4630
+ options.lagThrottle.pollIntervalMs = parseNum(
4631
+ `${prefix}LAG_THROTTLE_POLL_INTERVAL_MS`,
4632
+ raw
4633
+ );
4634
+ }
4635
+ });
4636
+ readVar(env, `${prefix}LAG_THROTTLE_MAX_WAIT_MS`, (raw) => {
4637
+ if (options.lagThrottle) {
4638
+ options.lagThrottle.maxWaitMs = parseNum(
4639
+ `${prefix}LAG_THROTTLE_MAX_WAIT_MS`,
4640
+ raw
4641
+ );
4642
+ }
4643
+ });
4644
+ const security = securityFromEnv(env, prefix);
4645
+ if (security) options.security = security;
4646
+ return result;
4647
+ }
4648
+ function securityFromEnv(env, prefix) {
4649
+ let ssl;
4650
+ let allowInsecure;
4651
+ let mechanism;
4652
+ let username;
4653
+ let password;
4654
+ readVar(env, `${prefix}SSL`, (raw) => {
4655
+ ssl = parseBool(`${prefix}SSL`, raw);
4656
+ });
4657
+ readVar(env, `${prefix}ALLOW_INSECURE`, (raw) => {
4658
+ allowInsecure = parseBool(`${prefix}ALLOW_INSECURE`, raw);
4659
+ });
4660
+ readVar(env, `${prefix}SASL_MECHANISM`, (raw) => {
4661
+ mechanism = parseEnum(`${prefix}SASL_MECHANISM`, raw, [
4662
+ "plain",
4663
+ "scram-sha-256",
4664
+ "scram-sha-512"
4665
+ ]);
4666
+ });
4667
+ readVar(env, `${prefix}SASL_USERNAME`, (raw) => {
4668
+ username = raw.trim();
4669
+ });
4670
+ readVar(env, `${prefix}SASL_PASSWORD`, (raw) => {
4671
+ password = raw;
4672
+ });
4673
+ if (ssl === void 0 && allowInsecure === void 0 && mechanism === void 0 && username === void 0 && password === void 0) {
4674
+ return void 0;
4675
+ }
4676
+ const security = {};
4677
+ if (ssl !== void 0) security.ssl = ssl;
4678
+ if (allowInsecure !== void 0) security.allowInsecure = allowInsecure;
4679
+ if (mechanism !== void 0 || username !== void 0 || password !== void 0) {
4680
+ if (mechanism === void 0 || username === void 0 || password === void 0) {
4681
+ throw new Error(
4682
+ `Incomplete SASL configuration: ${prefix}SASL_MECHANISM, ${prefix}SASL_USERNAME, and ${prefix}SASL_PASSWORD must all be set together (oauthbearer must be configured in code).`
4683
+ );
4684
+ }
4685
+ const sasl = { mechanism, username, password };
4686
+ security.sasl = sasl;
4687
+ }
4688
+ return security;
4689
+ }
4690
+ function consumerOptionsFromEnv(env = process.env, prefix = "KAFKA_CONSUMER_") {
4691
+ const options = {};
4692
+ readVar(env, `${prefix}GROUP_ID`, (raw) => {
4693
+ options.groupId = raw.trim();
4694
+ });
4695
+ readVar(env, `${prefix}FROM_BEGINNING`, (raw) => {
4696
+ options.fromBeginning = parseBool(`${prefix}FROM_BEGINNING`, raw);
4697
+ });
4698
+ readVar(env, `${prefix}AUTO_COMMIT`, (raw) => {
4699
+ options.autoCommit = parseBool(`${prefix}AUTO_COMMIT`, raw);
4700
+ });
4701
+ readVar(env, `${prefix}DLQ`, (raw) => {
4702
+ options.dlq = parseBool(`${prefix}DLQ`, raw);
4703
+ });
4704
+ readVar(env, `${prefix}RETRY_MAX_RETRIES`, (raw) => {
4705
+ const retry = {
4706
+ maxRetries: parseNum(`${prefix}RETRY_MAX_RETRIES`, raw)
4707
+ };
4708
+ options.retry = retry;
4709
+ });
4710
+ readVar(env, `${prefix}RETRY_BACKOFF_MS`, (raw) => {
4711
+ if (options.retry) {
4712
+ options.retry.backoffMs = parseNum(`${prefix}RETRY_BACKOFF_MS`, raw);
4713
+ }
4714
+ });
4715
+ readVar(env, `${prefix}RETRY_MAX_BACKOFF_MS`, (raw) => {
4716
+ if (options.retry) {
4717
+ options.retry.maxBackoffMs = parseNum(`${prefix}RETRY_MAX_BACKOFF_MS`, raw);
4718
+ }
4719
+ });
4720
+ readVar(env, `${prefix}RETRY_TOPICS`, (raw) => {
4721
+ options.retryTopics = parseBool(`${prefix}RETRY_TOPICS`, raw);
4722
+ });
4723
+ readVar(env, `${prefix}RETRY_TOPIC_ASSIGNMENT_TIMEOUT_MS`, (raw) => {
4724
+ options.retryTopicAssignmentTimeoutMs = parseNum(
4725
+ `${prefix}RETRY_TOPIC_ASSIGNMENT_TIMEOUT_MS`,
4726
+ raw
4727
+ );
4728
+ });
4729
+ readVar(env, `${prefix}HANDLER_TIMEOUT_MS`, (raw) => {
4730
+ options.handlerTimeoutMs = parseNum(`${prefix}HANDLER_TIMEOUT_MS`, raw);
4731
+ });
4732
+ readVar(env, `${prefix}MESSAGE_TTL_MS`, (raw) => {
4733
+ options.messageTtlMs = parseNum(`${prefix}MESSAGE_TTL_MS`, raw);
4734
+ });
4735
+ readVar(env, `${prefix}DEDUPLICATION_STRATEGY`, (raw) => {
4736
+ const strategy = parseEnum(`${prefix}DEDUPLICATION_STRATEGY`, raw, [
4737
+ "drop",
4738
+ "dlq",
4739
+ "topic"
4740
+ ]);
4741
+ const dedup = { strategy };
4742
+ options.deduplication = dedup;
4743
+ });
4744
+ readVar(env, `${prefix}DEDUPLICATION_TOPIC`, (raw) => {
4745
+ if (options.deduplication) {
4746
+ options.deduplication.duplicatesTopic = raw.trim();
4747
+ }
4748
+ });
4749
+ readVar(env, `${prefix}CIRCUIT_BREAKER_THRESHOLD`, (raw) => {
4750
+ const cb = {
4751
+ threshold: parseNum(`${prefix}CIRCUIT_BREAKER_THRESHOLD`, raw)
4752
+ };
4753
+ options.circuitBreaker = cb;
4754
+ });
4755
+ readVar(env, `${prefix}CIRCUIT_BREAKER_RECOVERY_MS`, (raw) => {
4756
+ if (options.circuitBreaker) {
4757
+ options.circuitBreaker.recoveryMs = parseNum(
4758
+ `${prefix}CIRCUIT_BREAKER_RECOVERY_MS`,
4759
+ raw
4760
+ );
4761
+ }
4762
+ });
4763
+ readVar(env, `${prefix}CIRCUIT_BREAKER_WINDOW_SIZE`, (raw) => {
4764
+ if (options.circuitBreaker) {
4765
+ options.circuitBreaker.windowSize = parseNum(
4766
+ `${prefix}CIRCUIT_BREAKER_WINDOW_SIZE`,
4767
+ raw
4768
+ );
4769
+ }
4770
+ });
4771
+ readVar(env, `${prefix}CIRCUIT_BREAKER_HALF_OPEN_SUCCESSES`, (raw) => {
4772
+ if (options.circuitBreaker) {
4773
+ options.circuitBreaker.halfOpenSuccesses = parseNum(
4774
+ `${prefix}CIRCUIT_BREAKER_HALF_OPEN_SUCCESSES`,
4775
+ raw
4776
+ );
4777
+ }
4778
+ });
4779
+ readVar(env, `${prefix}QUEUE_HIGH_WATER_MARK`, (raw) => {
4780
+ options.queueHighWaterMark = parseNum(`${prefix}QUEUE_HIGH_WATER_MARK`, raw);
4781
+ });
4782
+ readVar(env, `${prefix}PARTITION_ASSIGNER`, (raw) => {
4783
+ options.partitionAssigner = parseEnum(`${prefix}PARTITION_ASSIGNER`, raw, [
4784
+ "roundrobin",
4785
+ "range",
4786
+ "cooperative-sticky"
4787
+ ]);
4788
+ });
4789
+ readVar(env, `${prefix}GROUP_INSTANCE_ID`, (raw) => {
4790
+ options.groupInstanceId = raw.trim();
4791
+ });
4792
+ readVar(env, `${prefix}SUBSCRIBE_RETRY_RETRIES`, (raw) => {
4793
+ const subscribeRetry = {
4794
+ retries: parseNum(`${prefix}SUBSCRIBE_RETRY_RETRIES`, raw)
4795
+ };
4796
+ options.subscribeRetry = subscribeRetry;
4797
+ });
4798
+ readVar(env, `${prefix}SUBSCRIBE_RETRY_DELAY_MS`, (raw) => {
4799
+ if (options.subscribeRetry) {
4800
+ options.subscribeRetry.backoffMs = parseNum(
4801
+ `${prefix}SUBSCRIBE_RETRY_DELAY_MS`,
4802
+ raw
4803
+ );
4804
+ }
4805
+ });
4806
+ return options;
4807
+ }
4808
+ var NESTED_CONSUMER_KEYS = [
4809
+ "retry",
4810
+ "deduplication",
4811
+ "circuitBreaker",
4812
+ "subscribeRetry"
4813
+ ];
4814
+ function isPlainObject(value) {
4815
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4816
+ }
4817
+ function mergeConsumerOptions(...layers) {
4818
+ const result = {};
4819
+ for (const layer of layers) {
4820
+ if (!layer) continue;
4821
+ for (const [key, value] of Object.entries(layer)) {
4822
+ if (value === void 0) continue;
4823
+ if (NESTED_CONSUMER_KEYS.includes(key) && isPlainObject(value) && isPlainObject(result[key])) {
4824
+ result[key] = {
4825
+ ...result[key],
4826
+ ...value
4827
+ };
4828
+ } else {
4829
+ result[key] = value;
4830
+ }
4831
+ }
4832
+ }
4833
+ return result;
4834
+ }
3601
4835
  // Annotate the CommonJS export names for ESM import in node:
3602
4836
  0 && (module.exports = {
3603
4837
  HEADER_CORRELATION_ID,
4838
+ HEADER_DELAYED_TARGET,
4839
+ HEADER_DELAYED_UNTIL,
3604
4840
  HEADER_EVENT_ID,
3605
4841
  HEADER_LAMPORT_CLOCK,
3606
4842
  HEADER_SCHEMA_VERSION,
3607
4843
  HEADER_TIMESTAMP,
3608
4844
  HEADER_TRACEPARENT,
4845
+ InMemoryDedupStore,
4846
+ InMemoryOutboxStore,
3609
4847
  KafkaClient,
3610
4848
  KafkaProcessingError,
3611
4849
  KafkaRetryExhaustedError,
3612
4850
  KafkaValidationError,
4851
+ SchemaRegistryClient,
4852
+ awsMskIamProvider,
3613
4853
  buildEnvelopeHeaders,
4854
+ consumerOptionsFromEnv,
3614
4855
  decodeHeaders,
4856
+ describeRequiredAcls,
3615
4857
  extractEnvelope,
4858
+ gcpAccessTokenProvider,
3616
4859
  getEnvelopeContext,
4860
+ kafkaClientConfigFromEnv,
4861
+ mergeConsumerOptions,
4862
+ registrySchema,
4863
+ resolveSecurityOptions,
3617
4864
  runWithEnvelopeContext,
3618
- topic
4865
+ startOutboxRelay,
4866
+ toError,
4867
+ toKafkaAclCommands,
4868
+ toMskIamPolicy,
4869
+ topic,
4870
+ versionedSchema
3619
4871
  });
3620
4872
  //# sourceMappingURL=core.js.map