@eventferry/kafka 2.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,97 @@
1
+ // src/kafkajs-classifier.ts
2
+ function classifyKafkajsError(err) {
3
+ if (!err || typeof err !== "object") return "retriable";
4
+ const e = err;
5
+ if (e.name === "KafkaJSConnectionError") return "retriable";
6
+ if (e.name === "KafkaJSRequestTimeoutError") return "retriable";
7
+ if (e.name === "KafkaJSNonRetriableError") return "fatal";
8
+ const type = typeof e.type === "string" ? e.type : void 0;
9
+ if (type) {
10
+ if (RETRIABLE_TYPES.has(type)) return "retriable";
11
+ if (POISON_TYPES.has(type)) return "poison";
12
+ if (FATAL_TYPES.has(type)) return "fatal";
13
+ }
14
+ if (typeof e.code === "number") {
15
+ const k = CODE_TO_KIND.get(e.code);
16
+ if (k) return k;
17
+ }
18
+ return "retriable";
19
+ }
20
+ var RETRIABLE_TYPES = /* @__PURE__ */ new Set([
21
+ "NOT_LEADER_FOR_PARTITION",
22
+ "LEADER_NOT_AVAILABLE",
23
+ "UNKNOWN_TOPIC_OR_PARTITION",
24
+ "NETWORK_EXCEPTION",
25
+ "REQUEST_TIMED_OUT",
26
+ "REPLICA_NOT_AVAILABLE",
27
+ "NOT_ENOUGH_REPLICAS",
28
+ "NOT_ENOUGH_REPLICAS_AFTER_APPEND",
29
+ "FENCED_LEADER_EPOCH",
30
+ "UNKNOWN_LEADER_EPOCH",
31
+ "BROKER_NOT_AVAILABLE",
32
+ "COORDINATOR_LOAD_IN_PROGRESS",
33
+ "COORDINATOR_NOT_AVAILABLE"
34
+ ]);
35
+ var POISON_TYPES = /* @__PURE__ */ new Set([
36
+ "CORRUPT_MESSAGE",
37
+ "MESSAGE_TOO_LARGE",
38
+ "INVALID_RECORD",
39
+ "UNSUPPORTED_COMPRESSION_TYPE",
40
+ "INVALID_REQUIRED_ACKS",
41
+ "INVALID_PARTITIONS"
42
+ ]);
43
+ var FATAL_TYPES = /* @__PURE__ */ new Set([
44
+ "INVALID_PRODUCER_EPOCH",
45
+ "PRODUCER_FENCED",
46
+ "TOPIC_AUTHORIZATION_FAILED",
47
+ "CLUSTER_AUTHORIZATION_FAILED",
48
+ "TRANSACTIONAL_ID_AUTHORIZATION_FAILED",
49
+ "SASL_AUTHENTICATION_FAILED",
50
+ "INVALID_TRANSACTION_STATE",
51
+ "UNSUPPORTED_VERSION"
52
+ ]);
53
+ var CODE_TO_KIND = /* @__PURE__ */ new Map([
54
+ [2, "poison"],
55
+ // CORRUPT_MESSAGE
56
+ [3, "retriable"],
57
+ // UNKNOWN_TOPIC_OR_PARTITION
58
+ [5, "retriable"],
59
+ // LEADER_NOT_AVAILABLE
60
+ [6, "retriable"],
61
+ // NOT_LEADER_FOR_PARTITION
62
+ [7, "retriable"],
63
+ // REQUEST_TIMED_OUT
64
+ [9, "retriable"],
65
+ // REPLICA_NOT_AVAILABLE
66
+ [10, "poison"],
67
+ // MESSAGE_TOO_LARGE
68
+ [13, "retriable"],
69
+ // NETWORK_EXCEPTION
70
+ [19, "retriable"],
71
+ // NOT_ENOUGH_REPLICAS
72
+ [29, "fatal"],
73
+ // TOPIC_AUTHORIZATION_FAILED
74
+ [31, "fatal"],
75
+ // CLUSTER_AUTHORIZATION_FAILED
76
+ [47, "fatal"],
77
+ // INVALID_PRODUCER_EPOCH
78
+ [58, "fatal"],
79
+ // SASL_AUTHENTICATION_FAILED
80
+ [74, "retriable"],
81
+ // FENCED_LEADER_EPOCH
82
+ [76, "poison"],
83
+ // UNSUPPORTED_COMPRESSION_TYPE
84
+ [87, "poison"]
85
+ // INVALID_RECORD
86
+ ]);
87
+
1
88
  // src/kafkajs-driver.ts
89
+ var UNSUPPORTED_BY_KAFKAJS = [
90
+ "lingerMs",
91
+ "batchSize",
92
+ "deliveryTimeoutMs",
93
+ "maxRequestSize"
94
+ ];
2
95
  var KafkaJsDriver = class {
3
96
  transactional;
4
97
  producer = null;
@@ -11,6 +104,7 @@ var KafkaJsDriver = class {
11
104
  "KafkaJsDriver: transactionalId is required when transactional=true"
12
105
  );
13
106
  }
107
+ warnUnsupportedKafkajsOptions(opts);
14
108
  }
15
109
  async connect() {
16
110
  this.producer = await this.createProducer();
@@ -25,13 +119,36 @@ var KafkaJsDriver = class {
25
119
  const kafka = new mod.Kafka({
26
120
  clientId: this.opts.clientId ?? "eventferry",
27
121
  brokers: this.opts.brokers,
122
+ // kafkajs accepts `ssl: tls.ConnectionOptions` directly — Buffer + PEM
123
+ // string both supported. Our TlsConfig is a structural subset of that
124
+ // (`rejectUnauthorized` intentionally omitted; the cluster CA goes via
125
+ // `ca`). No translation needed.
28
126
  ssl: this.opts.ssl,
127
+ // SASL: PLAIN / SCRAM-SHA-256 / SCRAM-SHA-512 / OAUTHBEARER. kafkajs's
128
+ // shape matches ours; for OAUTHBEARER kafkajs reads only `value` from
129
+ // the provider's returned token (other fields are ignored).
29
130
  sasl: this.opts.sasl
30
131
  });
132
+ const createPartitioner = resolveCreatePartitioner(
133
+ mod.Partitioners,
134
+ this.opts.partitioner,
135
+ this.transactional
136
+ );
31
137
  return kafka.producer({
32
138
  idempotent: this.opts.idempotent ?? true,
33
- maxInFlightRequests: this.transactional ? 1 : void 0,
34
- transactionalId: this.transactional ? this.opts.transactionalId : void 0
139
+ // Idempotent / transactional producers cap maxInFlight at 5. When the
140
+ // user picks transactional we force 1 to keep strict ordering across
141
+ // retries on classic (non-idempotent) clusters that haven't migrated
142
+ // to the broker-side fence.
143
+ maxInFlightRequests: this.transactional ? 1 : this.opts.maxInFlightRequests,
144
+ transactionalId: this.transactional ? this.opts.transactionalId : void 0,
145
+ // kafkajs accepts these directly when set; undefined falls through to
146
+ // the kafkajs default.
147
+ requestTimeout: this.opts.requestTimeoutMs,
148
+ transactionTimeout: this.opts.transactionTimeoutMs,
149
+ // Setting any partitioner choice silences kafkajs's
150
+ // KafkaJSPartitionerNotSpecified warning.
151
+ createPartitioner
35
152
  });
36
153
  }
37
154
  async disconnect() {
@@ -49,19 +166,27 @@ var KafkaJsDriver = class {
49
166
  return messages.map((m) => ({ recordId: m.recordId, ok: true }));
50
167
  } catch (err) {
51
168
  await txn.abort().catch(() => void 0);
52
- const error = err instanceof Error ? err : new Error(String(err));
53
- return messages.map((m) => ({ recordId: m.recordId, ok: false, error }));
169
+ return failedResults(messages, err);
54
170
  }
55
171
  }
56
172
  try {
57
173
  await this.producer.sendBatch({ topicMessages, acks: this.opts.acks ?? -1 });
58
174
  return messages.map((m) => ({ recordId: m.recordId, ok: true }));
59
175
  } catch (err) {
60
- const error = err instanceof Error ? err : new Error(String(err));
61
- return messages.map((m) => ({ recordId: m.recordId, ok: false, error }));
176
+ return failedResults(messages, err);
62
177
  }
63
178
  }
64
179
  };
180
+ function failedResults(messages, err) {
181
+ const error = err instanceof Error ? err : new Error(String(err));
182
+ const errorKind = classifyKafkajsError(err);
183
+ return messages.map((m) => ({
184
+ recordId: m.recordId,
185
+ ok: false,
186
+ error,
187
+ errorKind
188
+ }));
189
+ }
65
190
  function groupByTopic(messages, compression) {
66
191
  const byTopic = /* @__PURE__ */ new Map();
67
192
  for (const m of messages) {
@@ -69,7 +194,12 @@ function groupByTopic(messages, compression) {
69
194
  arr.push({
70
195
  key: m.key,
71
196
  value: m.value,
72
- headers: m.headers
197
+ headers: m.headers,
198
+ // Per-message partition override. When set, kafkajs routes the record
199
+ // to this exact partition; when undefined, the configured partitioner
200
+ // chooses. We keep the key here too because compacted topics need it
201
+ // even when partition is pinned.
202
+ ...m.partition !== void 0 ? { partition: m.partition } : {}
73
203
  });
74
204
  byTopic.set(m.topic, arr);
75
205
  }
@@ -79,6 +209,32 @@ function groupByTopic(messages, compression) {
79
209
  ...compression && compression !== "none" ? { compression } : {}
80
210
  }));
81
211
  }
212
+ function resolveCreatePartitioner(partitioners, choice, transactional) {
213
+ if (!partitioners) return void 0;
214
+ const effective = choice ?? (transactional ? "default" : "java-compatible");
215
+ switch (effective) {
216
+ case "java-compatible":
217
+ return partitioners.JavaCompatiblePartitioner;
218
+ case "legacy":
219
+ return partitioners.LegacyPartitioner;
220
+ case "default":
221
+ return partitioners.DefaultPartitioner;
222
+ }
223
+ }
224
+ var warnedKafkajsKeys = /* @__PURE__ */ new Set();
225
+ function warnUnsupportedKafkajsOptions(opts) {
226
+ for (const key of UNSUPPORTED_BY_KAFKAJS) {
227
+ if (opts[key] === void 0) continue;
228
+ if (warnedKafkajsKeys.has(key)) continue;
229
+ warnedKafkajsKeys.add(key);
230
+ console.warn(
231
+ `[@eventferry/kafka] '${key}' is not configurable on the kafkajs driver and was ignored. Switch to the confluent driver (driver: "confluent") for fine-grained tuning, or remove the option to silence this warning.`
232
+ );
233
+ }
234
+ }
235
+ function _resetKafkajsWarnDedup() {
236
+ warnedKafkajsKeys.clear();
237
+ }
82
238
  async function importKafkaJs() {
83
239
  try {
84
240
  return await import("kafkajs");
@@ -89,6 +245,169 @@ async function importKafkaJs() {
89
245
  }
90
246
  }
91
247
 
248
+ // src/confluent-classifier.ts
249
+ function classifyConfluentError(err) {
250
+ if (!err || typeof err !== "object") return "retriable";
251
+ const e = err;
252
+ if (typeof e.code === "number") {
253
+ const k = CODE_TO_KIND2.get(e.code);
254
+ if (k) return k;
255
+ }
256
+ if (typeof e.name === "string") {
257
+ const k = NAME_TO_KIND.get(e.name);
258
+ if (k) return k;
259
+ }
260
+ return "retriable";
261
+ }
262
+ var CODE_TO_KIND2 = /* @__PURE__ */ new Map([
263
+ // Library-internal (negative codes)
264
+ [-184, "backpressure"],
265
+ // ERR__QUEUE_FULL — our outbound buffer is full
266
+ [-185, "retriable"],
267
+ // ERR__TIMED_OUT
268
+ [-187, "retriable"],
269
+ // ERR__ALL_BROKERS_DOWN
270
+ [-188, "poison"],
271
+ // ERR__UNKNOWN_TOPIC — topic doesn't exist on broker
272
+ [-190, "poison"],
273
+ // ERR__UNKNOWN_PARTITION
274
+ [-192, "retriable"],
275
+ // ERR__MSG_TIMED_OUT
276
+ [-195, "retriable"],
277
+ // ERR__TRANSPORT
278
+ [-198, "poison"],
279
+ // ERR__BAD_COMPRESSION
280
+ [-144, "fatal"],
281
+ // ERR__FENCED — producer fenced by another with same txn id
282
+ [-150, "fatal"],
283
+ // ERR__FATAL — unrecoverable librdkafka error
284
+ [-169, "fatal"],
285
+ // ERR__AUTHENTICATION
286
+ [-181, "fatal"],
287
+ // ERR__SSL
288
+ [-196, "retriable"],
289
+ // ERR__FAIL — catch-all, safe-default to retriable
290
+ // Wire-protocol (non-negative codes — Kafka error-code registry)
291
+ [2, "poison"],
292
+ // CORRUPT_MESSAGE
293
+ [3, "retriable"],
294
+ // UNKNOWN_TOPIC_OR_PARTITION
295
+ [5, "retriable"],
296
+ // LEADER_NOT_AVAILABLE
297
+ [6, "retriable"],
298
+ // NOT_LEADER_FOR_PARTITION
299
+ [7, "retriable"],
300
+ // REQUEST_TIMED_OUT
301
+ [9, "retriable"],
302
+ // REPLICA_NOT_AVAILABLE
303
+ [10, "poison"],
304
+ // MESSAGE_TOO_LARGE
305
+ [13, "retriable"],
306
+ // NETWORK_EXCEPTION
307
+ [19, "retriable"],
308
+ // NOT_ENOUGH_REPLICAS
309
+ [29, "fatal"],
310
+ // TOPIC_AUTHORIZATION_FAILED
311
+ [31, "fatal"],
312
+ // CLUSTER_AUTHORIZATION_FAILED
313
+ [47, "fatal"],
314
+ // INVALID_PRODUCER_EPOCH
315
+ [58, "fatal"],
316
+ // SASL_AUTHENTICATION_FAILED
317
+ [74, "retriable"],
318
+ // FENCED_LEADER_EPOCH
319
+ [76, "poison"],
320
+ // UNSUPPORTED_COMPRESSION_TYPE
321
+ [87, "poison"],
322
+ // INVALID_RECORD
323
+ [89, "quota"]
324
+ // THROTTLING_QUOTA_EXCEEDED
325
+ ]);
326
+ var NAME_TO_KIND = /* @__PURE__ */ new Map([
327
+ ["ERR__QUEUE_FULL", "backpressure"],
328
+ ["ERR__FENCED", "fatal"],
329
+ ["ERR__FATAL", "fatal"],
330
+ ["ERR__AUTHENTICATION", "fatal"],
331
+ ["ERR__SSL", "fatal"],
332
+ ["ERR__UNKNOWN_TOPIC", "poison"],
333
+ ["ERR__UNKNOWN_PARTITION", "poison"],
334
+ ["ERR__BAD_COMPRESSION", "poison"],
335
+ ["ERR_TOPIC_AUTHORIZATION_FAILED", "fatal"],
336
+ ["ERR_CLUSTER_AUTHORIZATION_FAILED", "fatal"],
337
+ ["ERR_INVALID_PRODUCER_EPOCH", "fatal"],
338
+ ["ERR_SASL_AUTHENTICATION_FAILED", "fatal"],
339
+ ["ERR_CORRUPT_MESSAGE", "poison"],
340
+ ["ERR_MSG_SIZE_TOO_LARGE", "poison"],
341
+ ["ERR_INVALID_RECORD", "poison"],
342
+ ["ERR_UNSUPPORTED_COMPRESSION_TYPE", "poison"],
343
+ ["ERR_THROTTLING_QUOTA_EXCEEDED", "quota"]
344
+ ]);
345
+
346
+ // src/confluent-config.ts
347
+ function buildConfluentClientConfig(opts) {
348
+ const kafkaJS = {
349
+ clientId: opts.clientId ?? "eventferry",
350
+ brokers: opts.brokers
351
+ };
352
+ const librdkafka = {};
353
+ if (opts.lingerMs !== void 0) librdkafka["linger.ms"] = opts.lingerMs;
354
+ if (opts.batchSize !== void 0) librdkafka["batch.size"] = opts.batchSize;
355
+ if (opts.maxInFlightRequests !== void 0) {
356
+ librdkafka["max.in.flight.requests.per.connection"] = opts.maxInFlightRequests;
357
+ }
358
+ if (opts.requestTimeoutMs !== void 0) {
359
+ librdkafka["request.timeout.ms"] = opts.requestTimeoutMs;
360
+ }
361
+ if (opts.deliveryTimeoutMs !== void 0) {
362
+ librdkafka["delivery.timeout.ms"] = opts.deliveryTimeoutMs;
363
+ }
364
+ if (opts.maxRequestSize !== void 0) {
365
+ librdkafka["message.max.bytes"] = opts.maxRequestSize;
366
+ }
367
+ if (opts.transactionTimeoutMs !== void 0) {
368
+ librdkafka["transaction.timeout.ms"] = opts.transactionTimeoutMs;
369
+ }
370
+ const tlsRequested = opts.ssl === true || isTlsConfig(opts.ssl);
371
+ const saslRequested = !!opts.sasl;
372
+ if (saslRequested && tlsRequested) {
373
+ librdkafka["security.protocol"] = "sasl_ssl";
374
+ } else if (tlsRequested) {
375
+ librdkafka["security.protocol"] = "ssl";
376
+ } else if (saslRequested) {
377
+ librdkafka["security.protocol"] = "sasl_plaintext";
378
+ }
379
+ if (isTlsConfig(opts.ssl)) {
380
+ const tls = opts.ssl;
381
+ if (tls.ca !== void 0) {
382
+ librdkafka["ssl.ca.pem"] = stringifyPem(tls.ca);
383
+ }
384
+ if (tls.cert !== void 0) {
385
+ librdkafka["ssl.certificate.pem"] = stringifyPem(tls.cert);
386
+ }
387
+ if (tls.key !== void 0) {
388
+ librdkafka["ssl.key.pem"] = stringifyPem(tls.key);
389
+ }
390
+ if (tls.passphrase !== void 0) {
391
+ librdkafka["ssl.key.password"] = tls.passphrase;
392
+ }
393
+ } else if (opts.ssl === true) {
394
+ kafkaJS["ssl"] = true;
395
+ }
396
+ if (opts.sasl) {
397
+ kafkaJS["sasl"] = opts.sasl;
398
+ }
399
+ return { kafkaJS, librdkafka };
400
+ }
401
+ function isTlsConfig(v) {
402
+ return typeof v === "object" && v !== null;
403
+ }
404
+ function stringifyPem(input) {
405
+ if (Array.isArray(input)) {
406
+ return input.map((x) => typeof x === "string" ? x : x.toString("utf8")).join("\n");
407
+ }
408
+ return typeof input === "string" ? input : input.toString("utf8");
409
+ }
410
+
92
411
  // src/confluent-driver.ts
93
412
  var ConfluentDriver = class {
94
413
  transactional;
@@ -113,13 +432,10 @@ var ConfluentDriver = class {
113
432
  */
114
433
  async createProducer() {
115
434
  const mod = await importConfluent();
435
+ const { kafkaJS, librdkafka } = buildConfluentClientConfig(this.opts);
116
436
  const kafka = new mod.KafkaJS.Kafka({
117
- kafkaJS: {
118
- clientId: this.opts.clientId ?? "eventferry",
119
- brokers: this.opts.brokers,
120
- ssl: this.opts.ssl,
121
- sasl: this.opts.sasl
122
- }
437
+ kafkaJS,
438
+ ...librdkafka
123
439
  });
124
440
  return kafka.producer({
125
441
  kafkaJS: {
@@ -155,24 +471,39 @@ var ConfluentDriver = class {
155
471
  return messages.map((m) => ({ recordId: m.recordId, ok: true }));
156
472
  } catch (err) {
157
473
  await txn.abort().catch(() => void 0);
158
- const error = err instanceof Error ? err : new Error(String(err));
159
- return messages.map((m) => ({ recordId: m.recordId, ok: false, error }));
474
+ return failedResults2(messages, err);
160
475
  }
161
476
  }
162
477
  try {
163
478
  await doSends(this.producer);
164
479
  return messages.map((m) => ({ recordId: m.recordId, ok: true }));
165
480
  } catch (err) {
166
- const error = err instanceof Error ? err : new Error(String(err));
167
- return messages.map((m) => ({ recordId: m.recordId, ok: false, error }));
481
+ return failedResults2(messages, err);
168
482
  }
169
483
  }
170
484
  };
485
+ function failedResults2(messages, err) {
486
+ const error = err instanceof Error ? err : new Error(String(err));
487
+ const errorKind = classifyConfluentError(err);
488
+ return messages.map((m) => ({
489
+ recordId: m.recordId,
490
+ ok: false,
491
+ error,
492
+ errorKind
493
+ }));
494
+ }
171
495
  function groupByTopic2(messages) {
172
496
  const byTopic = /* @__PURE__ */ new Map();
173
497
  for (const m of messages) {
174
498
  const arr = byTopic.get(m.topic) ?? [];
175
- arr.push({ key: m.key, value: m.value, headers: m.headers });
499
+ arr.push({
500
+ key: m.key,
501
+ value: m.value,
502
+ headers: m.headers,
503
+ // Per-message partition override. librdkafka honors an explicit
504
+ // partition value; undefined leaves the default partitioner in charge.
505
+ ...m.partition !== void 0 ? { partition: m.partition } : {}
506
+ });
176
507
  byTopic.set(m.topic, arr);
177
508
  }
178
509
  return [...byTopic.entries()].map(([topic, msgs]) => ({
@@ -243,6 +574,10 @@ function selectDriver(opts) {
243
574
  export {
244
575
  ConfluentDriver,
245
576
  KafkaJsDriver,
246
- KafkaPublisher
577
+ KafkaPublisher,
578
+ _resetKafkajsWarnDedup,
579
+ buildConfluentClientConfig,
580
+ classifyConfluentError,
581
+ classifyKafkajsError
247
582
  };
248
583
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/kafkajs-driver.ts","../src/confluent-driver.ts","../src/publisher.ts"],"sourcesContent":["import type { PublishableMessage, PublishResult } from \"@eventferry/core\";\nimport type {\n KafkaConnectionConfig,\n KafkaDriver,\n ProducerBehaviorConfig,\n} from \"./driver.js\";\n\n// Loosely-typed structural references to the kafkajs API so this file\n// compiles without kafkajs installed (it's an optional peer dep).\ninterface KjsProducer {\n connect(): Promise<void>;\n disconnect(): Promise<void>;\n // kafkajs: sendBatch takes { topicMessages }, send takes a single { topic, messages }.\n sendBatch(args: unknown): Promise<unknown>;\n transaction(): Promise<KjsTransaction>;\n}\ninterface KjsTransaction {\n sendBatch(args: unknown): Promise<unknown>;\n commit(): Promise<void>;\n abort(): Promise<void>;\n}\ninterface KjsKafka {\n producer(args?: unknown): KjsProducer;\n}\n\nexport interface KafkaJsDriverOptions\n extends KafkaConnectionConfig,\n ProducerBehaviorConfig {}\n\n/**\n * Driver backed by the pure-JS `kafkajs` client. Simple, zero native deps.\n */\nexport class KafkaJsDriver implements KafkaDriver {\n readonly transactional: boolean;\n private producer: KjsProducer | null = null;\n private readonly opts: KafkaJsDriverOptions;\n\n constructor(opts: KafkaJsDriverOptions) {\n this.opts = opts;\n this.transactional = opts.transactional ?? false;\n if (this.transactional && !opts.transactionalId) {\n throw new Error(\n \"KafkaJsDriver: transactionalId is required when transactional=true\",\n );\n }\n }\n\n async connect(): Promise<void> {\n this.producer = await this.createProducer();\n await this.producer.connect();\n }\n\n /**\n * Construct the underlying kafkajs producer. Overridable as a test seam so\n * the send/transaction logic can be exercised without a real broker.\n */\n protected async createProducer(): Promise<KjsProducer> {\n const mod = await importKafkaJs();\n const kafka: KjsKafka = new mod.Kafka({\n clientId: this.opts.clientId ?? \"eventferry\",\n brokers: this.opts.brokers,\n ssl: this.opts.ssl,\n sasl: this.opts.sasl,\n });\n return kafka.producer({\n idempotent: this.opts.idempotent ?? true,\n maxInFlightRequests: this.transactional ? 1 : undefined,\n transactionalId: this.transactional ? this.opts.transactionalId : undefined,\n });\n }\n\n async disconnect(): Promise<void> {\n await this.producer?.disconnect();\n this.producer = null;\n }\n\n async sendBatch(messages: PublishableMessage[]): Promise<PublishResult[]> {\n if (!this.producer) throw new Error(\"KafkaJsDriver not connected\");\n const topicMessages = groupByTopic(messages, this.opts.compression);\n\n if (this.transactional) {\n const txn = await this.producer.transaction();\n try {\n await txn.sendBatch({ topicMessages, acks: this.opts.acks ?? -1 });\n await txn.commit();\n return messages.map((m) => ({ recordId: m.recordId, ok: true }));\n } catch (err) {\n await txn.abort().catch(() => undefined);\n const error = err instanceof Error ? err : new Error(String(err));\n return messages.map((m) => ({ recordId: m.recordId, ok: false, error }));\n }\n }\n\n try {\n await this.producer.sendBatch({ topicMessages, acks: this.opts.acks ?? -1 });\n return messages.map((m) => ({ recordId: m.recordId, ok: true }));\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return messages.map((m) => ({ recordId: m.recordId, ok: false, error }));\n }\n }\n}\n\nfunction groupByTopic(messages: PublishableMessage[], compression?: string) {\n const byTopic = new Map<string, unknown[]>();\n for (const m of messages) {\n const arr = byTopic.get(m.topic) ?? [];\n arr.push({\n key: m.key,\n value: m.value,\n headers: m.headers,\n });\n byTopic.set(m.topic, arr);\n }\n return [...byTopic.entries()].map(([topic, msgs]) => ({\n topic,\n messages: msgs,\n ...(compression && compression !== \"none\" ? { compression } : {}),\n }));\n}\n\nasync function importKafkaJs(): Promise<{ Kafka: new (cfg: unknown) => KjsKafka }> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (await import(\"kafkajs\")) as any;\n } catch {\n throw new Error(\n 'Driver \"kafkajs\" selected but the \"kafkajs\" package is not installed. Run: npm i kafkajs',\n );\n }\n}\n","import type { PublishableMessage, PublishResult } from \"@eventferry/core\";\nimport type {\n KafkaConnectionConfig,\n KafkaDriver,\n ProducerBehaviorConfig,\n} from \"./driver.js\";\n\n// Structural typing of the confluent KafkaJS-compatible API surface so this\n// file compiles without the optional native dep installed.\ninterface CkProducer {\n connect(): Promise<void>;\n disconnect(): Promise<void>;\n send(args: unknown): Promise<unknown>;\n transaction(): Promise<CkTransaction>;\n}\ninterface CkTransaction {\n send(args: unknown): Promise<unknown>;\n commit(): Promise<void>;\n abort(): Promise<void>;\n}\ninterface CkKafka {\n producer(args?: unknown): CkProducer;\n}\n\nexport interface ConfluentDriverOptions\n extends KafkaConnectionConfig,\n ProducerBehaviorConfig {}\n\n/**\n * Driver backed by `@confluentinc/kafka-javascript` (librdkafka wrapper).\n * Higher throughput; uses the KafkaJS-compatible promisified API so the\n * adapter mirrors the kafkajs driver closely.\n */\nexport class ConfluentDriver implements KafkaDriver {\n readonly transactional: boolean;\n private producer: CkProducer | null = null;\n private readonly opts: ConfluentDriverOptions;\n\n constructor(opts: ConfluentDriverOptions) {\n this.opts = opts;\n this.transactional = opts.transactional ?? false;\n if (this.transactional && !opts.transactionalId) {\n throw new Error(\n \"ConfluentDriver: transactionalId is required when transactional=true\",\n );\n }\n }\n\n async connect(): Promise<void> {\n this.producer = await this.createProducer();\n await this.producer.connect();\n }\n\n /**\n * Construct the underlying confluent producer. Overridable as a test seam so\n * the send/transaction logic can be exercised without a real broker.\n */\n protected async createProducer(): Promise<CkProducer> {\n const mod = await importConfluent();\n const kafka: CkKafka = new mod.KafkaJS.Kafka({\n kafkaJS: {\n clientId: this.opts.clientId ?? \"eventferry\",\n brokers: this.opts.brokers,\n ssl: this.opts.ssl,\n sasl: this.opts.sasl,\n },\n });\n return kafka.producer({\n kafkaJS: {\n idempotent: this.opts.idempotent ?? true,\n ...(this.transactional\n ? { transactionalId: this.opts.transactionalId }\n : {}),\n },\n });\n }\n\n async disconnect(): Promise<void> {\n await this.producer?.disconnect();\n this.producer = null;\n }\n\n async sendBatch(messages: PublishableMessage[]): Promise<PublishResult[]> {\n if (!this.producer) throw new Error(\"ConfluentDriver not connected\");\n const topicMessages = groupByTopic(messages);\n const acks = this.opts.acks ?? -1;\n const compression = this.opts.compression;\n\n const doSends = async (target: CkProducer | CkTransaction) => {\n for (const tm of topicMessages) {\n await target.send({\n topic: tm.topic,\n messages: tm.messages,\n acks,\n ...(compression && compression !== \"none\" ? { compression } : {}),\n });\n }\n };\n\n if (this.transactional) {\n const txn = await this.producer.transaction();\n try {\n await doSends(txn);\n await txn.commit();\n return messages.map((m) => ({ recordId: m.recordId, ok: true }));\n } catch (err) {\n await txn.abort().catch(() => undefined);\n const error = err instanceof Error ? err : new Error(String(err));\n return messages.map((m) => ({ recordId: m.recordId, ok: false, error }));\n }\n }\n\n try {\n await doSends(this.producer);\n return messages.map((m) => ({ recordId: m.recordId, ok: true }));\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return messages.map((m) => ({ recordId: m.recordId, ok: false, error }));\n }\n }\n}\n\nfunction groupByTopic(messages: PublishableMessage[]) {\n const byTopic = new Map<string, unknown[]>();\n for (const m of messages) {\n const arr = byTopic.get(m.topic) ?? [];\n arr.push({ key: m.key, value: m.value, headers: m.headers });\n byTopic.set(m.topic, arr);\n }\n return [...byTopic.entries()].map(([topic, msgs]) => ({\n topic,\n messages: msgs,\n }));\n}\n\nasync function importConfluent(): Promise<{\n KafkaJS: { Kafka: new (cfg: unknown) => CkKafka };\n}> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (await import(\"@confluentinc/kafka-javascript\")) as any;\n } catch {\n throw new Error(\n 'Driver \"confluent\" selected but \"@confluentinc/kafka-javascript\" is not installed. Run: npm i @confluentinc/kafka-javascript',\n );\n }\n}\n","import type {\n PublishableMessage,\n Publisher,\n PublishResult,\n} from \"@eventferry/core\";\nimport { ConfluentDriver } from \"./confluent-driver.js\";\nimport type {\n DriverKind,\n KafkaConnectionConfig,\n KafkaDriver,\n ProducerBehaviorConfig,\n} from \"./driver.js\";\nimport { KafkaJsDriver } from \"./kafkajs-driver.js\";\n\nexport interface KafkaPublisherOptions\n extends KafkaConnectionConfig,\n ProducerBehaviorConfig {\n /** Which underlying client to use. Default \"kafkajs\". */\n driver?: DriverKind;\n /**\n * Provide a fully custom driver instance instead of the built-ins.\n * Useful for testing or unsupported clients.\n */\n customDriver?: KafkaDriver;\n}\n\n/**\n * The Publisher the Relay talks to. Wraps a pluggable KafkaDriver and\n * adds dead-letter routing. Works against Kafka and Redpanda identically\n * (Redpanda is Kafka-API compatible).\n */\nexport class KafkaPublisher implements Publisher {\n private readonly driver: KafkaDriver;\n\n constructor(opts: KafkaPublisherOptions) {\n this.driver = opts.customDriver ?? selectDriver(opts);\n }\n\n connect(): Promise<void> {\n return this.driver.connect();\n }\n\n disconnect(): Promise<void> {\n return this.driver.disconnect();\n }\n\n publish(messages: PublishableMessage[]): Promise<PublishResult[]> {\n return this.driver.sendBatch(messages);\n }\n\n /**\n * Send a single dead-lettered message. The message already carries the\n * DLQ topic (the Relay rewrites it), plus the failure reason as a header.\n */\n async publishToDlq(message: PublishableMessage, error: Error): Promise<void> {\n const dlqMessage: PublishableMessage = {\n ...message,\n headers: {\n ...message.headers,\n \"dlq-reason\": error.message,\n \"dlq-original-topic\": message.headers[\"original-topic\"] ?? \"\",\n \"dlq-failed-at\": new Date().toISOString(),\n },\n };\n const [result] = await this.driver.sendBatch([dlqMessage]);\n if (result && !result.ok) {\n throw result.error ?? new Error(\"DLQ publish failed\");\n }\n }\n\n /** Whether the configured driver provides atomic (EOS) batch sends. */\n get transactional(): boolean {\n return this.driver.transactional;\n }\n}\n\nfunction selectDriver(opts: KafkaPublisherOptions): KafkaDriver {\n const kind = opts.driver ?? \"kafkajs\";\n switch (kind) {\n case \"kafkajs\":\n return new KafkaJsDriver(opts);\n case \"confluent\":\n return new ConfluentDriver(opts);\n default:\n throw new Error(`Unknown driver \"${kind}\"`);\n }\n}\n"],"mappings":"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","names":["groupByTopic"]}
1
+ {"version":3,"sources":["../src/kafkajs-classifier.ts","../src/kafkajs-driver.ts","../src/confluent-classifier.ts","../src/confluent-config.ts","../src/confluent-driver.ts","../src/publisher.ts"],"sourcesContent":["import type { PublishErrorKind } from \"@eventferry/core\";\n\n/**\n * Classify a kafkajs producer error into a {@link PublishErrorKind} so the\n * core relay can decide whether to retry, short-circuit to the DLQ, or pause\n * polling.\n *\n * Mapping verified against `kafkajs/src/errors.js` (v2.x). Protocol error\n * codes match the Kafka Protocol error-code registry. Library-specific\n * subclasses (`KafkaJSRequestTimeoutError`, `KafkaJSConnectionError`,\n * `KafkaJSNonRetriableError`) are matched by the `name` property kafkajs\n * sets on its Error subclasses.\n *\n * Unknown errors fall back to `\"retriable\"` — the safe bias. At worst we\n * retry an error that should have been skipped; in practice we'd rather\n * over-retry than mis-classify a transient blip as terminal.\n */\nexport function classifyKafkajsError(err: unknown): PublishErrorKind {\n if (!err || typeof err !== \"object\") return \"retriable\";\n const e = err as { name?: string; type?: string; code?: number };\n\n // Class-based first — these don't carry a protocol error code.\n if (e.name === \"KafkaJSConnectionError\") return \"retriable\";\n if (e.name === \"KafkaJSRequestTimeoutError\") return \"retriable\";\n if (e.name === \"KafkaJSNonRetriableError\") return \"fatal\";\n\n // Protocol error type (string) — kafkajs's KafkaJSProtocolError exposes\n // both `type` (uppercase string) and `code` (number). Use `type` first\n // for readability and fall back to `code` for codes that lack a stable\n // string label.\n const type = typeof e.type === \"string\" ? e.type : undefined;\n if (type) {\n if (RETRIABLE_TYPES.has(type)) return \"retriable\";\n if (POISON_TYPES.has(type)) return \"poison\";\n if (FATAL_TYPES.has(type)) return \"fatal\";\n }\n\n if (typeof e.code === \"number\") {\n const k = CODE_TO_KIND.get(e.code);\n if (k) return k;\n }\n\n return \"retriable\";\n}\n\nconst RETRIABLE_TYPES = new Set<string>([\n \"NOT_LEADER_FOR_PARTITION\",\n \"LEADER_NOT_AVAILABLE\",\n \"UNKNOWN_TOPIC_OR_PARTITION\",\n \"NETWORK_EXCEPTION\",\n \"REQUEST_TIMED_OUT\",\n \"REPLICA_NOT_AVAILABLE\",\n \"NOT_ENOUGH_REPLICAS\",\n \"NOT_ENOUGH_REPLICAS_AFTER_APPEND\",\n \"FENCED_LEADER_EPOCH\",\n \"UNKNOWN_LEADER_EPOCH\",\n \"BROKER_NOT_AVAILABLE\",\n \"COORDINATOR_LOAD_IN_PROGRESS\",\n \"COORDINATOR_NOT_AVAILABLE\",\n]);\n\nconst POISON_TYPES = new Set<string>([\n \"CORRUPT_MESSAGE\",\n \"MESSAGE_TOO_LARGE\",\n \"INVALID_RECORD\",\n \"UNSUPPORTED_COMPRESSION_TYPE\",\n \"INVALID_REQUIRED_ACKS\",\n \"INVALID_PARTITIONS\",\n]);\n\nconst FATAL_TYPES = new Set<string>([\n \"INVALID_PRODUCER_EPOCH\",\n \"PRODUCER_FENCED\",\n \"TOPIC_AUTHORIZATION_FAILED\",\n \"CLUSTER_AUTHORIZATION_FAILED\",\n \"TRANSACTIONAL_ID_AUTHORIZATION_FAILED\",\n \"SASL_AUTHENTICATION_FAILED\",\n \"INVALID_TRANSACTION_STATE\",\n \"UNSUPPORTED_VERSION\",\n]);\n\n/** Numeric fallback for clusters that only return the wire code. */\nconst CODE_TO_KIND: ReadonlyMap<number, PublishErrorKind> = new Map([\n [2, \"poison\"], // CORRUPT_MESSAGE\n [3, \"retriable\"], // UNKNOWN_TOPIC_OR_PARTITION\n [5, \"retriable\"], // LEADER_NOT_AVAILABLE\n [6, \"retriable\"], // NOT_LEADER_FOR_PARTITION\n [7, \"retriable\"], // REQUEST_TIMED_OUT\n [9, \"retriable\"], // REPLICA_NOT_AVAILABLE\n [10, \"poison\"], // MESSAGE_TOO_LARGE\n [13, \"retriable\"], // NETWORK_EXCEPTION\n [19, \"retriable\"], // NOT_ENOUGH_REPLICAS\n [29, \"fatal\"], // TOPIC_AUTHORIZATION_FAILED\n [31, \"fatal\"], // CLUSTER_AUTHORIZATION_FAILED\n [47, \"fatal\"], // INVALID_PRODUCER_EPOCH\n [58, \"fatal\"], // SASL_AUTHENTICATION_FAILED\n [74, \"retriable\"], // FENCED_LEADER_EPOCH\n [76, \"poison\"], // UNSUPPORTED_COMPRESSION_TYPE\n [87, \"poison\"], // INVALID_RECORD\n]);\n","import type { PublishableMessage, PublishResult } from \"@eventferry/core\";\nimport { classifyKafkajsError } from \"./kafkajs-classifier.js\";\nimport type {\n KafkaConnectionConfig,\n KafkaDriver,\n KafkaJsPartitionerChoice,\n ProducerBehaviorConfig,\n} from \"./driver.js\";\n\n// Loosely-typed structural references to the kafkajs API so this file\n// compiles without kafkajs installed (it's an optional peer dep).\ninterface KjsProducer {\n connect(): Promise<void>;\n disconnect(): Promise<void>;\n // kafkajs: sendBatch takes { topicMessages }, send takes a single { topic, messages }.\n sendBatch(args: unknown): Promise<unknown>;\n transaction(): Promise<KjsTransaction>;\n}\ninterface KjsTransaction {\n sendBatch(args: unknown): Promise<unknown>;\n commit(): Promise<void>;\n abort(): Promise<void>;\n}\ninterface KjsKafka {\n producer(args?: unknown): KjsProducer;\n}\n// kafkajs's `Partitioners` namespace: three factory functions; we pluck them\n// at runtime rather than depending on the kafkajs types.\ninterface KjsPartitionersNamespace {\n DefaultPartitioner: () => unknown;\n LegacyPartitioner: () => unknown;\n JavaCompatiblePartitioner: () => unknown;\n}\n\nexport interface KafkaJsDriverOptions\n extends KafkaConnectionConfig,\n ProducerBehaviorConfig {}\n\n/**\n * kafkajs producer-level knobs we expose on the typed API that kafkajs does\n * NOT actually support. On this driver these are warn-and-ignore; users\n * who need them should switch to the confluent driver.\n */\nconst UNSUPPORTED_BY_KAFKAJS = [\n \"lingerMs\",\n \"batchSize\",\n \"deliveryTimeoutMs\",\n \"maxRequestSize\",\n] as const;\n\n/**\n * Driver backed by the pure-JS `kafkajs` client. Simple, zero native deps.\n */\nexport class KafkaJsDriver implements KafkaDriver {\n readonly transactional: boolean;\n private producer: KjsProducer | null = null;\n private readonly opts: KafkaJsDriverOptions;\n\n constructor(opts: KafkaJsDriverOptions) {\n this.opts = opts;\n this.transactional = opts.transactional ?? false;\n if (this.transactional && !opts.transactionalId) {\n throw new Error(\n \"KafkaJsDriver: transactionalId is required when transactional=true\",\n );\n }\n warnUnsupportedKafkajsOptions(opts);\n }\n\n async connect(): Promise<void> {\n this.producer = await this.createProducer();\n await this.producer.connect();\n }\n\n /**\n * Construct the underlying kafkajs producer. Overridable as a test seam so\n * the send/transaction logic can be exercised without a real broker.\n */\n protected async createProducer(): Promise<KjsProducer> {\n const mod = await importKafkaJs();\n const kafka: KjsKafka = new mod.Kafka({\n clientId: this.opts.clientId ?? \"eventferry\",\n brokers: this.opts.brokers,\n // kafkajs accepts `ssl: tls.ConnectionOptions` directly — Buffer + PEM\n // string both supported. Our TlsConfig is a structural subset of that\n // (`rejectUnauthorized` intentionally omitted; the cluster CA goes via\n // `ca`). No translation needed.\n ssl: this.opts.ssl,\n // SASL: PLAIN / SCRAM-SHA-256 / SCRAM-SHA-512 / OAUTHBEARER. kafkajs's\n // shape matches ours; for OAUTHBEARER kafkajs reads only `value` from\n // the provider's returned token (other fields are ignored).\n sasl: this.opts.sasl,\n });\n const createPartitioner = resolveCreatePartitioner(\n mod.Partitioners,\n this.opts.partitioner,\n this.transactional,\n );\n return kafka.producer({\n idempotent: this.opts.idempotent ?? true,\n // Idempotent / transactional producers cap maxInFlight at 5. When the\n // user picks transactional we force 1 to keep strict ordering across\n // retries on classic (non-idempotent) clusters that haven't migrated\n // to the broker-side fence.\n maxInFlightRequests: this.transactional\n ? 1\n : this.opts.maxInFlightRequests,\n transactionalId: this.transactional\n ? this.opts.transactionalId\n : undefined,\n // kafkajs accepts these directly when set; undefined falls through to\n // the kafkajs default.\n requestTimeout: this.opts.requestTimeoutMs,\n transactionTimeout: this.opts.transactionTimeoutMs,\n // Setting any partitioner choice silences kafkajs's\n // KafkaJSPartitionerNotSpecified warning.\n createPartitioner,\n });\n }\n\n async disconnect(): Promise<void> {\n await this.producer?.disconnect();\n this.producer = null;\n }\n\n async sendBatch(messages: PublishableMessage[]): Promise<PublishResult[]> {\n if (!this.producer) throw new Error(\"KafkaJsDriver not connected\");\n const topicMessages = groupByTopic(messages, this.opts.compression);\n\n if (this.transactional) {\n const txn = await this.producer.transaction();\n try {\n await txn.sendBatch({ topicMessages, acks: this.opts.acks ?? -1 });\n await txn.commit();\n return messages.map((m) => ({ recordId: m.recordId, ok: true }));\n } catch (err) {\n await txn.abort().catch(() => undefined);\n return failedResults(messages, err);\n }\n }\n\n try {\n await this.producer.sendBatch({ topicMessages, acks: this.opts.acks ?? -1 });\n return messages.map((m) => ({ recordId: m.recordId, ok: true }));\n } catch (err) {\n return failedResults(messages, err);\n }\n }\n}\n\nfunction failedResults(\n messages: PublishableMessage[],\n err: unknown,\n): PublishResult[] {\n const error = err instanceof Error ? err : new Error(String(err));\n const errorKind = classifyKafkajsError(err);\n return messages.map((m) => ({\n recordId: m.recordId,\n ok: false,\n error,\n errorKind,\n }));\n}\n\nfunction groupByTopic(messages: PublishableMessage[], compression?: string) {\n const byTopic = new Map<string, unknown[]>();\n for (const m of messages) {\n const arr = byTopic.get(m.topic) ?? [];\n arr.push({\n key: m.key,\n value: m.value,\n headers: m.headers,\n // Per-message partition override. When set, kafkajs routes the record\n // to this exact partition; when undefined, the configured partitioner\n // chooses. We keep the key here too because compacted topics need it\n // even when partition is pinned.\n ...(m.partition !== undefined ? { partition: m.partition } : {}),\n });\n byTopic.set(m.topic, arr);\n }\n return [...byTopic.entries()].map(([topic, msgs]) => ({\n topic,\n messages: msgs,\n ...(compression && compression !== \"none\" ? { compression } : {}),\n }));\n}\n\n/**\n * Resolve the `createPartitioner` factory kafkajs expects on\n * `producer({...})`. Returns `undefined` to fall through to the kafkajs\n * default when no choice is made AND the producer is non-transactional\n * (transactional producers don't trigger the no-partitioner warning).\n */\nfunction resolveCreatePartitioner(\n partitioners: KjsPartitionersNamespace | undefined,\n choice: KafkaJsPartitionerChoice | undefined,\n transactional: boolean,\n): (() => unknown) | undefined {\n if (!partitioners) return undefined;\n // Default to the java-compatible partitioner when the caller didn't pick.\n // It matches the Java client (murmur2) and silences the noisy warning;\n // for transactional producers we leave the kafkajs default alone since\n // EOS ordering is partitioner-agnostic and the warning doesn't fire there.\n const effective: KafkaJsPartitionerChoice =\n choice ?? (transactional ? \"default\" : \"java-compatible\");\n switch (effective) {\n case \"java-compatible\":\n return partitioners.JavaCompatiblePartitioner;\n case \"legacy\":\n return partitioners.LegacyPartitioner;\n case \"default\":\n return partitioners.DefaultPartitioner;\n }\n}\n\n/** Process-wide dedup so we never warn for the same option twice. */\nconst warnedKafkajsKeys = new Set<string>();\n\nfunction warnUnsupportedKafkajsOptions(opts: KafkaJsDriverOptions): void {\n for (const key of UNSUPPORTED_BY_KAFKAJS) {\n if (opts[key] === undefined) continue;\n if (warnedKafkajsKeys.has(key)) continue;\n warnedKafkajsKeys.add(key);\n console.warn(\n `[@eventferry/kafka] '${key}' is not configurable on the kafkajs driver and was ignored. ` +\n `Switch to the confluent driver (driver: \"confluent\") for fine-grained tuning, ` +\n `or remove the option to silence this warning.`,\n );\n }\n}\n\n/** Internal — used by tests. Resets the dedup so warnings can be observed in isolation. */\nexport function _resetKafkajsWarnDedup(): void {\n warnedKafkajsKeys.clear();\n}\n\nasync function importKafkaJs(): Promise<{\n Kafka: new (cfg: unknown) => KjsKafka;\n Partitioners: KjsPartitionersNamespace;\n}> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (await import(\"kafkajs\")) as any;\n } catch {\n throw new Error(\n 'Driver \"kafkajs\" selected but the \"kafkajs\" package is not installed. Run: npm i kafkajs',\n );\n }\n}\n","import type { PublishErrorKind } from \"@eventferry/core\";\n\n/**\n * Classify a `@confluentinc/kafka-javascript` (librdkafka) producer error\n * into a {@link PublishErrorKind} so the core relay can decide whether to\n * retry, short-circuit to the DLQ, or pause polling.\n *\n * librdkafka exposes errors as numeric `RD_KAFKA_RESP_ERR_*` codes — negative\n * codes are library-internal (transport, queue-full, ssl), non-negative\n * codes are wire-protocol errors that match the Kafka protocol's error-code\n * registry. The confluent driver surfaces these on `err.code` (alongside\n * an `err.name` for the symbolic form).\n *\n * Unknown errors fall back to `\"retriable\"` — the safe bias.\n */\nexport function classifyConfluentError(err: unknown): PublishErrorKind {\n if (!err || typeof err !== \"object\") return \"retriable\";\n const e = err as { code?: number; name?: string };\n\n if (typeof e.code === \"number\") {\n const k = CODE_TO_KIND.get(e.code);\n if (k) return k;\n }\n\n if (typeof e.name === \"string\") {\n const k = NAME_TO_KIND.get(e.name);\n if (k) return k;\n }\n\n return \"retriable\";\n}\n\n/**\n * Authoritative mapping for the most-common librdkafka producer error codes.\n * Sources: `librdkafka/src/rdkafka.h` (`RD_KAFKA_RESP_ERR_*` enum) and the\n * Kafka Protocol error-code registry. Adding a code here is a one-line\n * change — start narrow, broaden as production exposes new codes.\n */\nconst CODE_TO_KIND: ReadonlyMap<number, PublishErrorKind> = new Map([\n // Library-internal (negative codes)\n [-184, \"backpressure\"], // ERR__QUEUE_FULL — our outbound buffer is full\n [-185, \"retriable\"], // ERR__TIMED_OUT\n [-187, \"retriable\"], // ERR__ALL_BROKERS_DOWN\n [-188, \"poison\"], // ERR__UNKNOWN_TOPIC — topic doesn't exist on broker\n [-190, \"poison\"], // ERR__UNKNOWN_PARTITION\n [-192, \"retriable\"], // ERR__MSG_TIMED_OUT\n [-195, \"retriable\"], // ERR__TRANSPORT\n [-198, \"poison\"], // ERR__BAD_COMPRESSION\n [-144, \"fatal\"], // ERR__FENCED — producer fenced by another with same txn id\n [-150, \"fatal\"], // ERR__FATAL — unrecoverable librdkafka error\n [-169, \"fatal\"], // ERR__AUTHENTICATION\n [-181, \"fatal\"], // ERR__SSL\n [-196, \"retriable\"], // ERR__FAIL — catch-all, safe-default to retriable\n\n // Wire-protocol (non-negative codes — Kafka error-code registry)\n [2, \"poison\"], // CORRUPT_MESSAGE\n [3, \"retriable\"], // UNKNOWN_TOPIC_OR_PARTITION\n [5, \"retriable\"], // LEADER_NOT_AVAILABLE\n [6, \"retriable\"], // NOT_LEADER_FOR_PARTITION\n [7, \"retriable\"], // REQUEST_TIMED_OUT\n [9, \"retriable\"], // REPLICA_NOT_AVAILABLE\n [10, \"poison\"], // MESSAGE_TOO_LARGE\n [13, \"retriable\"], // NETWORK_EXCEPTION\n [19, \"retriable\"], // NOT_ENOUGH_REPLICAS\n [29, \"fatal\"], // TOPIC_AUTHORIZATION_FAILED\n [31, \"fatal\"], // CLUSTER_AUTHORIZATION_FAILED\n [47, \"fatal\"], // INVALID_PRODUCER_EPOCH\n [58, \"fatal\"], // SASL_AUTHENTICATION_FAILED\n [74, \"retriable\"], // FENCED_LEADER_EPOCH\n [76, \"poison\"], // UNSUPPORTED_COMPRESSION_TYPE\n [87, \"poison\"], // INVALID_RECORD\n [89, \"quota\"], // THROTTLING_QUOTA_EXCEEDED\n]);\n\n/** Symbolic name fallback for clients that surface `err.name` only. */\nconst NAME_TO_KIND: ReadonlyMap<string, PublishErrorKind> = new Map([\n [\"ERR__QUEUE_FULL\", \"backpressure\"],\n [\"ERR__FENCED\", \"fatal\"],\n [\"ERR__FATAL\", \"fatal\"],\n [\"ERR__AUTHENTICATION\", \"fatal\"],\n [\"ERR__SSL\", \"fatal\"],\n [\"ERR__UNKNOWN_TOPIC\", \"poison\"],\n [\"ERR__UNKNOWN_PARTITION\", \"poison\"],\n [\"ERR__BAD_COMPRESSION\", \"poison\"],\n [\"ERR_TOPIC_AUTHORIZATION_FAILED\", \"fatal\"],\n [\"ERR_CLUSTER_AUTHORIZATION_FAILED\", \"fatal\"],\n [\"ERR_INVALID_PRODUCER_EPOCH\", \"fatal\"],\n [\"ERR_SASL_AUTHENTICATION_FAILED\", \"fatal\"],\n [\"ERR_CORRUPT_MESSAGE\", \"poison\"],\n [\"ERR_MSG_SIZE_TOO_LARGE\", \"poison\"],\n [\"ERR_INVALID_RECORD\", \"poison\"],\n [\"ERR_UNSUPPORTED_COMPRESSION_TYPE\", \"poison\"],\n [\"ERR_THROTTLING_QUOTA_EXCEEDED\", \"quota\"],\n]);\n","import type {\n KafkaConnectionConfig,\n ProducerBehaviorConfig,\n TlsConfig,\n} from \"./driver.js\";\n\n/**\n * Translate eventferry's normalized `KafkaConnectionConfig` into the shape\n * expected by `@confluentinc/kafka-javascript`'s `KafkaJS.Kafka` constructor.\n *\n * Returns an object with two parts:\n * - `kafkaJS`: the kafkajs-compatible config layer (clientId, brokers, and\n * simple ssl/sasl when no advanced TLS is needed).\n * - top-level keys: librdkafka-style config (e.g. `ssl.ca.pem`,\n * `security.protocol`) used when the user supplies a {@link TlsConfig}.\n *\n * Why a separate translator: the kafkajs-compat layer accepts the simple\n * `ssl: true` boolean but the verified path for mTLS (CA + cert + key) is\n * librdkafka's `ssl.*.pem` keys. The translator picks the right surface\n * based on what the caller supplied. Buffer inputs are coerced to strings —\n * librdkafka accepts PEM strings, NOT Buffers.\n */\nexport interface ConfluentClientConfig {\n kafkaJS: Record<string, unknown>;\n // librdkafka top-level keys; kept as a Record so we can spread them.\n librdkafka: Record<string, unknown>;\n}\n\nexport function buildConfluentClientConfig(\n opts: KafkaConnectionConfig & ProducerBehaviorConfig,\n): ConfluentClientConfig {\n const kafkaJS: Record<string, unknown> = {\n clientId: opts.clientId ?? \"eventferry\",\n brokers: opts.brokers,\n };\n const librdkafka: Record<string, unknown> = {};\n\n // ── Producer tuning passthrough (librdkafka config keys) ─────────────\n if (opts.lingerMs !== undefined) librdkafka[\"linger.ms\"] = opts.lingerMs;\n if (opts.batchSize !== undefined) librdkafka[\"batch.size\"] = opts.batchSize;\n if (opts.maxInFlightRequests !== undefined) {\n librdkafka[\"max.in.flight.requests.per.connection\"] =\n opts.maxInFlightRequests;\n }\n if (opts.requestTimeoutMs !== undefined) {\n librdkafka[\"request.timeout.ms\"] = opts.requestTimeoutMs;\n }\n if (opts.deliveryTimeoutMs !== undefined) {\n librdkafka[\"delivery.timeout.ms\"] = opts.deliveryTimeoutMs;\n }\n if (opts.maxRequestSize !== undefined) {\n librdkafka[\"message.max.bytes\"] = opts.maxRequestSize;\n }\n if (opts.transactionTimeoutMs !== undefined) {\n librdkafka[\"transaction.timeout.ms\"] = opts.transactionTimeoutMs;\n }\n\n const tlsRequested = opts.ssl === true || isTlsConfig(opts.ssl);\n const saslRequested = !!opts.sasl;\n\n if (saslRequested && tlsRequested) {\n librdkafka[\"security.protocol\"] = \"sasl_ssl\";\n } else if (tlsRequested) {\n librdkafka[\"security.protocol\"] = \"ssl\";\n } else if (saslRequested) {\n librdkafka[\"security.protocol\"] = \"sasl_plaintext\";\n } // else: leave as default (plaintext)\n\n if (isTlsConfig(opts.ssl)) {\n // Custom TLS — explicit librdkafka PEM keys. Buffers are coerced to\n // strings (librdkafka does not accept Buffer).\n const tls = opts.ssl;\n if (tls.ca !== undefined) {\n librdkafka[\"ssl.ca.pem\"] = stringifyPem(tls.ca);\n }\n if (tls.cert !== undefined) {\n librdkafka[\"ssl.certificate.pem\"] = stringifyPem(tls.cert);\n }\n if (tls.key !== undefined) {\n librdkafka[\"ssl.key.pem\"] = stringifyPem(tls.key);\n }\n if (tls.passphrase !== undefined) {\n librdkafka[\"ssl.key.password\"] = tls.passphrase;\n }\n // servername (SNI) — librdkafka derives SNI from `ssl.endpoint.identification.algorithm`;\n // explicit SNI override is not documented in the v1.x kafkaJS-compat surface, so we\n // honor it as a no-op for now and document the limitation in the gap analysis.\n } else if (opts.ssl === true) {\n // Simple TLS — kafkajs-compat boolean is sufficient.\n kafkaJS[\"ssl\"] = true;\n }\n\n if (opts.sasl) {\n // SASL — kafkajs-compat shape works for both password mechanisms and\n // OAUTHBEARER (the confluent client implements the provider callback).\n kafkaJS[\"sasl\"] = opts.sasl;\n }\n\n return { kafkaJS, librdkafka };\n}\n\nfunction isTlsConfig(v: unknown): v is TlsConfig {\n return typeof v === \"object\" && v !== null;\n}\n\nfunction stringifyPem(input: string | Buffer | Array<string | Buffer>): string {\n if (Array.isArray(input)) {\n return input.map((x) => (typeof x === \"string\" ? x : x.toString(\"utf8\"))).join(\"\\n\");\n }\n return typeof input === \"string\" ? input : input.toString(\"utf8\");\n}\n","import type { PublishableMessage, PublishResult } from \"@eventferry/core\";\nimport { classifyConfluentError } from \"./confluent-classifier.js\";\nimport { buildConfluentClientConfig } from \"./confluent-config.js\";\nimport type {\n KafkaConnectionConfig,\n KafkaDriver,\n ProducerBehaviorConfig,\n} from \"./driver.js\";\n\n// Structural typing of the confluent KafkaJS-compatible API surface so this\n// file compiles without the optional native dep installed.\ninterface CkProducer {\n connect(): Promise<void>;\n disconnect(): Promise<void>;\n send(args: unknown): Promise<unknown>;\n transaction(): Promise<CkTransaction>;\n}\ninterface CkTransaction {\n send(args: unknown): Promise<unknown>;\n commit(): Promise<void>;\n abort(): Promise<void>;\n}\ninterface CkKafka {\n producer(args?: unknown): CkProducer;\n}\n\nexport interface ConfluentDriverOptions\n extends KafkaConnectionConfig,\n ProducerBehaviorConfig {}\n\n/**\n * Driver backed by `@confluentinc/kafka-javascript` (librdkafka wrapper).\n * Higher throughput; uses the KafkaJS-compatible promisified API so the\n * adapter mirrors the kafkajs driver closely.\n */\nexport class ConfluentDriver implements KafkaDriver {\n readonly transactional: boolean;\n private producer: CkProducer | null = null;\n private readonly opts: ConfluentDriverOptions;\n\n constructor(opts: ConfluentDriverOptions) {\n this.opts = opts;\n this.transactional = opts.transactional ?? false;\n if (this.transactional && !opts.transactionalId) {\n throw new Error(\n \"ConfluentDriver: transactionalId is required when transactional=true\",\n );\n }\n }\n\n async connect(): Promise<void> {\n this.producer = await this.createProducer();\n await this.producer.connect();\n }\n\n /**\n * Construct the underlying confluent producer. Overridable as a test seam so\n * the send/transaction logic can be exercised without a real broker.\n */\n protected async createProducer(): Promise<CkProducer> {\n const mod = await importConfluent();\n const { kafkaJS, librdkafka } = buildConfluentClientConfig(this.opts);\n const kafka: CkKafka = new mod.KafkaJS.Kafka({\n kafkaJS,\n ...librdkafka,\n });\n return kafka.producer({\n kafkaJS: {\n idempotent: this.opts.idempotent ?? true,\n ...(this.transactional\n ? { transactionalId: this.opts.transactionalId }\n : {}),\n },\n });\n }\n\n async disconnect(): Promise<void> {\n await this.producer?.disconnect();\n this.producer = null;\n }\n\n async sendBatch(messages: PublishableMessage[]): Promise<PublishResult[]> {\n if (!this.producer) throw new Error(\"ConfluentDriver not connected\");\n const topicMessages = groupByTopic(messages);\n const acks = this.opts.acks ?? -1;\n const compression = this.opts.compression;\n\n const doSends = async (target: CkProducer | CkTransaction) => {\n for (const tm of topicMessages) {\n await target.send({\n topic: tm.topic,\n messages: tm.messages,\n acks,\n ...(compression && compression !== \"none\" ? { compression } : {}),\n });\n }\n };\n\n if (this.transactional) {\n const txn = await this.producer.transaction();\n try {\n await doSends(txn);\n await txn.commit();\n return messages.map((m) => ({ recordId: m.recordId, ok: true }));\n } catch (err) {\n await txn.abort().catch(() => undefined);\n return failedResults(messages, err);\n }\n }\n\n try {\n await doSends(this.producer);\n return messages.map((m) => ({ recordId: m.recordId, ok: true }));\n } catch (err) {\n return failedResults(messages, err);\n }\n }\n}\n\nfunction failedResults(\n messages: PublishableMessage[],\n err: unknown,\n): PublishResult[] {\n const error = err instanceof Error ? err : new Error(String(err));\n const errorKind = classifyConfluentError(err);\n return messages.map((m) => ({\n recordId: m.recordId,\n ok: false,\n error,\n errorKind,\n }));\n}\n\nfunction groupByTopic(messages: PublishableMessage[]) {\n const byTopic = new Map<string, unknown[]>();\n for (const m of messages) {\n const arr = byTopic.get(m.topic) ?? [];\n arr.push({\n key: m.key,\n value: m.value,\n headers: m.headers,\n // Per-message partition override. librdkafka honors an explicit\n // partition value; undefined leaves the default partitioner in charge.\n ...(m.partition !== undefined ? { partition: m.partition } : {}),\n });\n byTopic.set(m.topic, arr);\n }\n return [...byTopic.entries()].map(([topic, msgs]) => ({\n topic,\n messages: msgs,\n }));\n}\n\nasync function importConfluent(): Promise<{\n KafkaJS: { Kafka: new (cfg: unknown) => CkKafka };\n}> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (await import(\"@confluentinc/kafka-javascript\")) as any;\n } catch {\n throw new Error(\n 'Driver \"confluent\" selected but \"@confluentinc/kafka-javascript\" is not installed. Run: npm i @confluentinc/kafka-javascript',\n );\n }\n}\n","import type {\n PublishableMessage,\n Publisher,\n PublishResult,\n} from \"@eventferry/core\";\nimport { ConfluentDriver } from \"./confluent-driver.js\";\nimport type {\n DriverKind,\n KafkaConnectionConfig,\n KafkaDriver,\n ProducerBehaviorConfig,\n} from \"./driver.js\";\nimport { KafkaJsDriver } from \"./kafkajs-driver.js\";\n\nexport interface KafkaPublisherOptions\n extends KafkaConnectionConfig,\n ProducerBehaviorConfig {\n /** Which underlying client to use. Default \"kafkajs\". */\n driver?: DriverKind;\n /**\n * Provide a fully custom driver instance instead of the built-ins.\n * Useful for testing or unsupported clients.\n */\n customDriver?: KafkaDriver;\n}\n\n/**\n * The Publisher the Relay talks to. Wraps a pluggable KafkaDriver and\n * adds dead-letter routing. Works against Kafka and Redpanda identically\n * (Redpanda is Kafka-API compatible).\n */\nexport class KafkaPublisher implements Publisher {\n private readonly driver: KafkaDriver;\n\n constructor(opts: KafkaPublisherOptions) {\n this.driver = opts.customDriver ?? selectDriver(opts);\n }\n\n connect(): Promise<void> {\n return this.driver.connect();\n }\n\n disconnect(): Promise<void> {\n return this.driver.disconnect();\n }\n\n publish(messages: PublishableMessage[]): Promise<PublishResult[]> {\n return this.driver.sendBatch(messages);\n }\n\n /**\n * Send a single dead-lettered message. The message already carries the\n * DLQ topic (the Relay rewrites it), plus the failure reason as a header.\n */\n async publishToDlq(message: PublishableMessage, error: Error): Promise<void> {\n const dlqMessage: PublishableMessage = {\n ...message,\n headers: {\n ...message.headers,\n \"dlq-reason\": error.message,\n \"dlq-original-topic\": message.headers[\"original-topic\"] ?? \"\",\n \"dlq-failed-at\": new Date().toISOString(),\n },\n };\n const [result] = await this.driver.sendBatch([dlqMessage]);\n if (result && !result.ok) {\n throw result.error ?? new Error(\"DLQ publish failed\");\n }\n }\n\n /** Whether the configured driver provides atomic (EOS) batch sends. */\n get transactional(): boolean {\n return this.driver.transactional;\n }\n}\n\nfunction selectDriver(opts: KafkaPublisherOptions): KafkaDriver {\n const kind = opts.driver ?? \"kafkajs\";\n switch (kind) {\n case \"kafkajs\":\n return new KafkaJsDriver(opts);\n case \"confluent\":\n return new ConfluentDriver(opts);\n default:\n throw new Error(`Unknown driver \"${kind}\"`);\n }\n}\n"],"mappings":";AAiBO,SAAS,qBAAqB,KAAgC;AACnE,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,IAAI;AAGV,MAAI,EAAE,SAAS,yBAA0B,QAAO;AAChD,MAAI,EAAE,SAAS,6BAA8B,QAAO;AACpD,MAAI,EAAE,SAAS,2BAA4B,QAAO;AAMlD,QAAM,OAAO,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;AACnD,MAAI,MAAM;AACR,QAAI,gBAAgB,IAAI,IAAI,EAAG,QAAO;AACtC,QAAI,aAAa,IAAI,IAAI,EAAG,QAAO;AACnC,QAAI,YAAY,IAAI,IAAI,EAAG,QAAO;AAAA,EACpC;AAEA,MAAI,OAAO,EAAE,SAAS,UAAU;AAC9B,UAAM,IAAI,aAAa,IAAI,EAAE,IAAI;AACjC,QAAI,EAAG,QAAO;AAAA,EAChB;AAEA,SAAO;AACT;AAEA,IAAM,kBAAkB,oBAAI,IAAY;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,eAAe,oBAAI,IAAY;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,cAAc,oBAAI,IAAY;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,IAAM,eAAsD,oBAAI,IAAI;AAAA,EAClE,CAAC,GAAG,QAAQ;AAAA;AAAA,EACZ,CAAC,GAAG,WAAW;AAAA;AAAA,EACf,CAAC,GAAG,WAAW;AAAA;AAAA,EACf,CAAC,GAAG,WAAW;AAAA;AAAA,EACf,CAAC,GAAG,WAAW;AAAA;AAAA,EACf,CAAC,GAAG,WAAW;AAAA;AAAA,EACf,CAAC,IAAI,QAAQ;AAAA;AAAA,EACb,CAAC,IAAI,WAAW;AAAA;AAAA,EAChB,CAAC,IAAI,WAAW;AAAA;AAAA,EAChB,CAAC,IAAI,OAAO;AAAA;AAAA,EACZ,CAAC,IAAI,OAAO;AAAA;AAAA,EACZ,CAAC,IAAI,OAAO;AAAA;AAAA,EACZ,CAAC,IAAI,OAAO;AAAA;AAAA,EACZ,CAAC,IAAI,WAAW;AAAA;AAAA,EAChB,CAAC,IAAI,QAAQ;AAAA;AAAA,EACb,CAAC,IAAI,QAAQ;AAAA;AACf,CAAC;;;ACxDD,IAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKO,IAAM,gBAAN,MAA2C;AAAA,EACvC;AAAA,EACD,WAA+B;AAAA,EACtB;AAAA,EAEjB,YAAY,MAA4B;AACtC,SAAK,OAAO;AACZ,SAAK,gBAAgB,KAAK,iBAAiB;AAC3C,QAAI,KAAK,iBAAiB,CAAC,KAAK,iBAAiB;AAC/C,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,kCAA8B,IAAI;AAAA,EACpC;AAAA,EAEA,MAAM,UAAyB;AAC7B,SAAK,WAAW,MAAM,KAAK,eAAe;AAC1C,UAAM,KAAK,SAAS,QAAQ;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAgB,iBAAuC;AACrD,UAAM,MAAM,MAAM,cAAc;AAChC,UAAM,QAAkB,IAAI,IAAI,MAAM;AAAA,MACpC,UAAU,KAAK,KAAK,YAAY;AAAA,MAChC,SAAS,KAAK,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,MAKnB,KAAK,KAAK,KAAK;AAAA;AAAA;AAAA;AAAA,MAIf,MAAM,KAAK,KAAK;AAAA,IAClB,CAAC;AACD,UAAM,oBAAoB;AAAA,MACxB,IAAI;AAAA,MACJ,KAAK,KAAK;AAAA,MACV,KAAK;AAAA,IACP;AACA,WAAO,MAAM,SAAS;AAAA,MACpB,YAAY,KAAK,KAAK,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA,MAKpC,qBAAqB,KAAK,gBACtB,IACA,KAAK,KAAK;AAAA,MACd,iBAAiB,KAAK,gBAClB,KAAK,KAAK,kBACV;AAAA;AAAA;AAAA,MAGJ,gBAAgB,KAAK,KAAK;AAAA,MAC1B,oBAAoB,KAAK,KAAK;AAAA;AAAA;AAAA,MAG9B;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAA4B;AAChC,UAAM,KAAK,UAAU,WAAW;AAChC,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,UAAU,UAA0D;AACxE,QAAI,CAAC,KAAK,SAAU,OAAM,IAAI,MAAM,6BAA6B;AACjE,UAAM,gBAAgB,aAAa,UAAU,KAAK,KAAK,WAAW;AAElE,QAAI,KAAK,eAAe;AACtB,YAAM,MAAM,MAAM,KAAK,SAAS,YAAY;AAC5C,UAAI;AACF,cAAM,IAAI,UAAU,EAAE,eAAe,MAAM,KAAK,KAAK,QAAQ,GAAG,CAAC;AACjE,cAAM,IAAI,OAAO;AACjB,eAAO,SAAS,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,IAAI,KAAK,EAAE;AAAA,MACjE,SAAS,KAAK;AACZ,cAAM,IAAI,MAAM,EAAE,MAAM,MAAM,MAAS;AACvC,eAAO,cAAc,UAAU,GAAG;AAAA,MACpC;AAAA,IACF;AAEA,QAAI;AACF,YAAM,KAAK,SAAS,UAAU,EAAE,eAAe,MAAM,KAAK,KAAK,QAAQ,GAAG,CAAC;AAC3E,aAAO,SAAS,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,IAAI,KAAK,EAAE;AAAA,IACjE,SAAS,KAAK;AACZ,aAAO,cAAc,UAAU,GAAG;AAAA,IACpC;AAAA,EACF;AACF;AAEA,SAAS,cACP,UACA,KACiB;AACjB,QAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,QAAM,YAAY,qBAAqB,GAAG;AAC1C,SAAO,SAAS,IAAI,CAAC,OAAO;AAAA,IAC1B,UAAU,EAAE;AAAA,IACZ,IAAI;AAAA,IACJ;AAAA,IACA;AAAA,EACF,EAAE;AACJ;AAEA,SAAS,aAAa,UAAgC,aAAsB;AAC1E,QAAM,UAAU,oBAAI,IAAuB;AAC3C,aAAW,KAAK,UAAU;AACxB,UAAM,MAAM,QAAQ,IAAI,EAAE,KAAK,KAAK,CAAC;AACrC,QAAI,KAAK;AAAA,MACP,KAAK,EAAE;AAAA,MACP,OAAO,EAAE;AAAA,MACT,SAAS,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA,MAKX,GAAI,EAAE,cAAc,SAAY,EAAE,WAAW,EAAE,UAAU,IAAI,CAAC;AAAA,IAChE,CAAC;AACD,YAAQ,IAAI,EAAE,OAAO,GAAG;AAAA,EAC1B;AACA,SAAO,CAAC,GAAG,QAAQ,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,IAAI,OAAO;AAAA,IACpD;AAAA,IACA,UAAU;AAAA,IACV,GAAI,eAAe,gBAAgB,SAAS,EAAE,YAAY,IAAI,CAAC;AAAA,EACjE,EAAE;AACJ;AAQA,SAAS,yBACP,cACA,QACA,eAC6B;AAC7B,MAAI,CAAC,aAAc,QAAO;AAK1B,QAAM,YACJ,WAAW,gBAAgB,YAAY;AACzC,UAAQ,WAAW;AAAA,IACjB,KAAK;AACH,aAAO,aAAa;AAAA,IACtB,KAAK;AACH,aAAO,aAAa;AAAA,IACtB,KAAK;AACH,aAAO,aAAa;AAAA,EACxB;AACF;AAGA,IAAM,oBAAoB,oBAAI,IAAY;AAE1C,SAAS,8BAA8B,MAAkC;AACvE,aAAW,OAAO,wBAAwB;AACxC,QAAI,KAAK,GAAG,MAAM,OAAW;AAC7B,QAAI,kBAAkB,IAAI,GAAG,EAAG;AAChC,sBAAkB,IAAI,GAAG;AACzB,YAAQ;AAAA,MACN,wBAAwB,GAAG;AAAA,IAG7B;AAAA,EACF;AACF;AAGO,SAAS,yBAA+B;AAC7C,oBAAkB,MAAM;AAC1B;AAEA,eAAe,gBAGZ;AACD,MAAI;AAEF,WAAQ,MAAM,OAAO,SAAS;AAAA,EAChC,QAAQ;AACN,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;;;ACzOO,SAAS,uBAAuB,KAAgC;AACrE,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,IAAI;AAEV,MAAI,OAAO,EAAE,SAAS,UAAU;AAC9B,UAAM,IAAIA,cAAa,IAAI,EAAE,IAAI;AACjC,QAAI,EAAG,QAAO;AAAA,EAChB;AAEA,MAAI,OAAO,EAAE,SAAS,UAAU;AAC9B,UAAM,IAAI,aAAa,IAAI,EAAE,IAAI;AACjC,QAAI,EAAG,QAAO;AAAA,EAChB;AAEA,SAAO;AACT;AAQA,IAAMA,gBAAsD,oBAAI,IAAI;AAAA;AAAA,EAElE,CAAC,MAAM,cAAc;AAAA;AAAA,EACrB,CAAC,MAAM,WAAW;AAAA;AAAA,EAClB,CAAC,MAAM,WAAW;AAAA;AAAA,EAClB,CAAC,MAAM,QAAQ;AAAA;AAAA,EACf,CAAC,MAAM,QAAQ;AAAA;AAAA,EACf,CAAC,MAAM,WAAW;AAAA;AAAA,EAClB,CAAC,MAAM,WAAW;AAAA;AAAA,EAClB,CAAC,MAAM,QAAQ;AAAA;AAAA,EACf,CAAC,MAAM,OAAO;AAAA;AAAA,EACd,CAAC,MAAM,OAAO;AAAA;AAAA,EACd,CAAC,MAAM,OAAO;AAAA;AAAA,EACd,CAAC,MAAM,OAAO;AAAA;AAAA,EACd,CAAC,MAAM,WAAW;AAAA;AAAA;AAAA,EAGlB,CAAC,GAAG,QAAQ;AAAA;AAAA,EACZ,CAAC,GAAG,WAAW;AAAA;AAAA,EACf,CAAC,GAAG,WAAW;AAAA;AAAA,EACf,CAAC,GAAG,WAAW;AAAA;AAAA,EACf,CAAC,GAAG,WAAW;AAAA;AAAA,EACf,CAAC,GAAG,WAAW;AAAA;AAAA,EACf,CAAC,IAAI,QAAQ;AAAA;AAAA,EACb,CAAC,IAAI,WAAW;AAAA;AAAA,EAChB,CAAC,IAAI,WAAW;AAAA;AAAA,EAChB,CAAC,IAAI,OAAO;AAAA;AAAA,EACZ,CAAC,IAAI,OAAO;AAAA;AAAA,EACZ,CAAC,IAAI,OAAO;AAAA;AAAA,EACZ,CAAC,IAAI,OAAO;AAAA;AAAA,EACZ,CAAC,IAAI,WAAW;AAAA;AAAA,EAChB,CAAC,IAAI,QAAQ;AAAA;AAAA,EACb,CAAC,IAAI,QAAQ;AAAA;AAAA,EACb,CAAC,IAAI,OAAO;AAAA;AACd,CAAC;AAGD,IAAM,eAAsD,oBAAI,IAAI;AAAA,EAClE,CAAC,mBAAmB,cAAc;AAAA,EAClC,CAAC,eAAe,OAAO;AAAA,EACvB,CAAC,cAAc,OAAO;AAAA,EACtB,CAAC,uBAAuB,OAAO;AAAA,EAC/B,CAAC,YAAY,OAAO;AAAA,EACpB,CAAC,sBAAsB,QAAQ;AAAA,EAC/B,CAAC,0BAA0B,QAAQ;AAAA,EACnC,CAAC,wBAAwB,QAAQ;AAAA,EACjC,CAAC,kCAAkC,OAAO;AAAA,EAC1C,CAAC,oCAAoC,OAAO;AAAA,EAC5C,CAAC,8BAA8B,OAAO;AAAA,EACtC,CAAC,kCAAkC,OAAO;AAAA,EAC1C,CAAC,uBAAuB,QAAQ;AAAA,EAChC,CAAC,0BAA0B,QAAQ;AAAA,EACnC,CAAC,sBAAsB,QAAQ;AAAA,EAC/B,CAAC,oCAAoC,QAAQ;AAAA,EAC7C,CAAC,iCAAiC,OAAO;AAC3C,CAAC;;;ACjEM,SAAS,2BACd,MACuB;AACvB,QAAM,UAAmC;AAAA,IACvC,UAAU,KAAK,YAAY;AAAA,IAC3B,SAAS,KAAK;AAAA,EAChB;AACA,QAAM,aAAsC,CAAC;AAG7C,MAAI,KAAK,aAAa,OAAW,YAAW,WAAW,IAAI,KAAK;AAChE,MAAI,KAAK,cAAc,OAAW,YAAW,YAAY,IAAI,KAAK;AAClE,MAAI,KAAK,wBAAwB,QAAW;AAC1C,eAAW,uCAAuC,IAChD,KAAK;AAAA,EACT;AACA,MAAI,KAAK,qBAAqB,QAAW;AACvC,eAAW,oBAAoB,IAAI,KAAK;AAAA,EAC1C;AACA,MAAI,KAAK,sBAAsB,QAAW;AACxC,eAAW,qBAAqB,IAAI,KAAK;AAAA,EAC3C;AACA,MAAI,KAAK,mBAAmB,QAAW;AACrC,eAAW,mBAAmB,IAAI,KAAK;AAAA,EACzC;AACA,MAAI,KAAK,yBAAyB,QAAW;AAC3C,eAAW,wBAAwB,IAAI,KAAK;AAAA,EAC9C;AAEA,QAAM,eAAe,KAAK,QAAQ,QAAQ,YAAY,KAAK,GAAG;AAC9D,QAAM,gBAAgB,CAAC,CAAC,KAAK;AAE7B,MAAI,iBAAiB,cAAc;AACjC,eAAW,mBAAmB,IAAI;AAAA,EACpC,WAAW,cAAc;AACvB,eAAW,mBAAmB,IAAI;AAAA,EACpC,WAAW,eAAe;AACxB,eAAW,mBAAmB,IAAI;AAAA,EACpC;AAEA,MAAI,YAAY,KAAK,GAAG,GAAG;AAGzB,UAAM,MAAM,KAAK;AACjB,QAAI,IAAI,OAAO,QAAW;AACxB,iBAAW,YAAY,IAAI,aAAa,IAAI,EAAE;AAAA,IAChD;AACA,QAAI,IAAI,SAAS,QAAW;AAC1B,iBAAW,qBAAqB,IAAI,aAAa,IAAI,IAAI;AAAA,IAC3D;AACA,QAAI,IAAI,QAAQ,QAAW;AACzB,iBAAW,aAAa,IAAI,aAAa,IAAI,GAAG;AAAA,IAClD;AACA,QAAI,IAAI,eAAe,QAAW;AAChC,iBAAW,kBAAkB,IAAI,IAAI;AAAA,IACvC;AAAA,EAIF,WAAW,KAAK,QAAQ,MAAM;AAE5B,YAAQ,KAAK,IAAI;AAAA,EACnB;AAEA,MAAI,KAAK,MAAM;AAGb,YAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AAEA,SAAO,EAAE,SAAS,WAAW;AAC/B;AAEA,SAAS,YAAY,GAA4B;AAC/C,SAAO,OAAO,MAAM,YAAY,MAAM;AACxC;AAEA,SAAS,aAAa,OAAyD;AAC7E,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,CAAC,MAAO,OAAO,MAAM,WAAW,IAAI,EAAE,SAAS,MAAM,CAAE,EAAE,KAAK,IAAI;AAAA,EACrF;AACA,SAAO,OAAO,UAAU,WAAW,QAAQ,MAAM,SAAS,MAAM;AAClE;;;AC3EO,IAAM,kBAAN,MAA6C;AAAA,EACzC;AAAA,EACD,WAA8B;AAAA,EACrB;AAAA,EAEjB,YAAY,MAA8B;AACxC,SAAK,OAAO;AACZ,SAAK,gBAAgB,KAAK,iBAAiB;AAC3C,QAAI,KAAK,iBAAiB,CAAC,KAAK,iBAAiB;AAC/C,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAC7B,SAAK,WAAW,MAAM,KAAK,eAAe;AAC1C,UAAM,KAAK,SAAS,QAAQ;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAgB,iBAAsC;AACpD,UAAM,MAAM,MAAM,gBAAgB;AAClC,UAAM,EAAE,SAAS,WAAW,IAAI,2BAA2B,KAAK,IAAI;AACpE,UAAM,QAAiB,IAAI,IAAI,QAAQ,MAAM;AAAA,MAC3C;AAAA,MACA,GAAG;AAAA,IACL,CAAC;AACD,WAAO,MAAM,SAAS;AAAA,MACpB,SAAS;AAAA,QACP,YAAY,KAAK,KAAK,cAAc;AAAA,QACpC,GAAI,KAAK,gBACL,EAAE,iBAAiB,KAAK,KAAK,gBAAgB,IAC7C,CAAC;AAAA,MACP;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAA4B;AAChC,UAAM,KAAK,UAAU,WAAW;AAChC,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,UAAU,UAA0D;AACxE,QAAI,CAAC,KAAK,SAAU,OAAM,IAAI,MAAM,+BAA+B;AACnE,UAAM,gBAAgBC,cAAa,QAAQ;AAC3C,UAAM,OAAO,KAAK,KAAK,QAAQ;AAC/B,UAAM,cAAc,KAAK,KAAK;AAE9B,UAAM,UAAU,OAAO,WAAuC;AAC5D,iBAAW,MAAM,eAAe;AAC9B,cAAM,OAAO,KAAK;AAAA,UAChB,OAAO,GAAG;AAAA,UACV,UAAU,GAAG;AAAA,UACb;AAAA,UACA,GAAI,eAAe,gBAAgB,SAAS,EAAE,YAAY,IAAI,CAAC;AAAA,QACjE,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI,KAAK,eAAe;AACtB,YAAM,MAAM,MAAM,KAAK,SAAS,YAAY;AAC5C,UAAI;AACF,cAAM,QAAQ,GAAG;AACjB,cAAM,IAAI,OAAO;AACjB,eAAO,SAAS,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,IAAI,KAAK,EAAE;AAAA,MACjE,SAAS,KAAK;AACZ,cAAM,IAAI,MAAM,EAAE,MAAM,MAAM,MAAS;AACvC,eAAOC,eAAc,UAAU,GAAG;AAAA,MACpC;AAAA,IACF;AAEA,QAAI;AACF,YAAM,QAAQ,KAAK,QAAQ;AAC3B,aAAO,SAAS,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,IAAI,KAAK,EAAE;AAAA,IACjE,SAAS,KAAK;AACZ,aAAOA,eAAc,UAAU,GAAG;AAAA,IACpC;AAAA,EACF;AACF;AAEA,SAASA,eACP,UACA,KACiB;AACjB,QAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,QAAM,YAAY,uBAAuB,GAAG;AAC5C,SAAO,SAAS,IAAI,CAAC,OAAO;AAAA,IAC1B,UAAU,EAAE;AAAA,IACZ,IAAI;AAAA,IACJ;AAAA,IACA;AAAA,EACF,EAAE;AACJ;AAEA,SAASD,cAAa,UAAgC;AACpD,QAAM,UAAU,oBAAI,IAAuB;AAC3C,aAAW,KAAK,UAAU;AACxB,UAAM,MAAM,QAAQ,IAAI,EAAE,KAAK,KAAK,CAAC;AACrC,QAAI,KAAK;AAAA,MACP,KAAK,EAAE;AAAA,MACP,OAAO,EAAE;AAAA,MACT,SAAS,EAAE;AAAA;AAAA;AAAA,MAGX,GAAI,EAAE,cAAc,SAAY,EAAE,WAAW,EAAE,UAAU,IAAI,CAAC;AAAA,IAChE,CAAC;AACD,YAAQ,IAAI,EAAE,OAAO,GAAG;AAAA,EAC1B;AACA,SAAO,CAAC,GAAG,QAAQ,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,IAAI,OAAO;AAAA,IACpD;AAAA,IACA,UAAU;AAAA,EACZ,EAAE;AACJ;AAEA,eAAe,kBAEZ;AACD,MAAI;AAEF,WAAQ,MAAM,OAAO,gCAAgC;AAAA,EACvD,QAAQ;AACN,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;;;ACrIO,IAAM,iBAAN,MAA0C;AAAA,EAC9B;AAAA,EAEjB,YAAY,MAA6B;AACvC,SAAK,SAAS,KAAK,gBAAgB,aAAa,IAAI;AAAA,EACtD;AAAA,EAEA,UAAyB;AACvB,WAAO,KAAK,OAAO,QAAQ;AAAA,EAC7B;AAAA,EAEA,aAA4B;AAC1B,WAAO,KAAK,OAAO,WAAW;AAAA,EAChC;AAAA,EAEA,QAAQ,UAA0D;AAChE,WAAO,KAAK,OAAO,UAAU,QAAQ;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,aAAa,SAA6B,OAA6B;AAC3E,UAAM,aAAiC;AAAA,MACrC,GAAG;AAAA,MACH,SAAS;AAAA,QACP,GAAG,QAAQ;AAAA,QACX,cAAc,MAAM;AAAA,QACpB,sBAAsB,QAAQ,QAAQ,gBAAgB,KAAK;AAAA,QAC3D,kBAAiB,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC1C;AAAA,IACF;AACA,UAAM,CAAC,MAAM,IAAI,MAAM,KAAK,OAAO,UAAU,CAAC,UAAU,CAAC;AACzD,QAAI,UAAU,CAAC,OAAO,IAAI;AACxB,YAAM,OAAO,SAAS,IAAI,MAAM,oBAAoB;AAAA,IACtD;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,gBAAyB;AAC3B,WAAO,KAAK,OAAO;AAAA,EACrB;AACF;AAEA,SAAS,aAAa,MAA0C;AAC9D,QAAM,OAAO,KAAK,UAAU;AAC5B,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,IAAI,cAAc,IAAI;AAAA,IAC/B,KAAK;AACH,aAAO,IAAI,gBAAgB,IAAI;AAAA,IACjC;AACE,YAAM,IAAI,MAAM,mBAAmB,IAAI,GAAG;AAAA,EAC9C;AACF;","names":["CODE_TO_KIND","groupByTopic","failedResults"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventferry/kafka",
3
- "version": "2.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Kafka/Redpanda publisher for @eventferry (kafkajs + confluent drivers)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -49,7 +49,7 @@
49
49
  "peerDependencies": {
50
50
  "kafkajs": "^2.0.0",
51
51
  "@confluentinc/kafka-javascript": "^1.0.0",
52
- "@eventferry/core": "2.0.0"
52
+ "@eventferry/core": "3.1.0"
53
53
  },
54
54
  "peerDependenciesMeta": {
55
55
  "kafkajs": {
@@ -64,7 +64,7 @@
64
64
  "tsup": "^8.3.5",
65
65
  "typescript": "^5.7.2",
66
66
  "vitest": "^2.1.8",
67
- "@eventferry/core": "2.0.0"
67
+ "@eventferry/core": "3.1.0"
68
68
  },
69
69
  "scripts": {
70
70
  "build": "tsup",