@znight/messaging 0.0.0-stage → 0.1.1

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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +622 -2
  3. package/dist/async-CFh_bvex.mjs +216 -0
  4. package/dist/async-CFh_bvex.mjs.map +1 -0
  5. package/dist/codec-Dku-NXXU.mjs +83 -0
  6. package/dist/codec-Dku-NXXU.mjs.map +1 -0
  7. package/dist/compiler.d.mts +75 -0
  8. package/dist/compiler.mjs +1023 -0
  9. package/dist/compiler.mjs.map +1 -0
  10. package/dist/driver-ZxLqy_Ap.d.mts +216 -0
  11. package/dist/index.d.mts +459 -0
  12. package/dist/index.mjs +870 -0
  13. package/dist/index.mjs.map +1 -0
  14. package/dist/kafka.d.mts +51 -0
  15. package/dist/kafka.mjs +517 -0
  16. package/dist/kafka.mjs.map +1 -0
  17. package/dist/memory.d.mts +124 -0
  18. package/dist/memory.mjs +429 -0
  19. package/dist/memory.mjs.map +1 -0
  20. package/dist/rabbitmq.d.mts +65 -0
  21. package/dist/rabbitmq.mjs +686 -0
  22. package/dist/rabbitmq.mjs.map +1 -0
  23. package/dist/shared-BJBpLS8V.mjs +52 -0
  24. package/dist/shared-BJBpLS8V.mjs.map +1 -0
  25. package/dist/sqs.d.mts +38 -0
  26. package/dist/sqs.mjs +421 -0
  27. package/dist/sqs.mjs.map +1 -0
  28. package/package.json +123 -4
  29. package/src/broker.ts +156 -0
  30. package/src/codec.ts +98 -0
  31. package/src/compiler/analyze.ts +800 -0
  32. package/src/compiler/constants.ts +73 -0
  33. package/src/compiler/data.ts +75 -0
  34. package/src/compiler/generate.ts +131 -0
  35. package/src/compiler/index.ts +52 -0
  36. package/src/compiler/patterns.ts +54 -0
  37. package/src/compiler/retry.ts +136 -0
  38. package/src/compiler/validate.ts +57 -0
  39. package/src/compiler/values.ts +76 -0
  40. package/src/consumers.ts +260 -0
  41. package/src/decorators.ts +131 -0
  42. package/src/driver.ts +142 -0
  43. package/src/drivers/kafka.ts +765 -0
  44. package/src/drivers/memory.ts +609 -0
  45. package/src/drivers/rabbitmq.ts +944 -0
  46. package/src/drivers/shared.ts +81 -0
  47. package/src/drivers/sqs.ts +583 -0
  48. package/src/errors.ts +95 -0
  49. package/src/headers.ts +70 -0
  50. package/src/hooks.ts +2 -0
  51. package/src/index.ts +89 -0
  52. package/src/internal/async.ts +115 -0
  53. package/src/message.ts +96 -0
  54. package/src/metadata.ts +65 -0
  55. package/src/plugin.ts +236 -0
  56. package/src/retry.ts +129 -0
  57. package/src/runtime.ts +424 -0
  58. package/src/standard-schema.ts +68 -0
  59. package/src/tokens.ts +14 -0
  60. package/src/validation.ts +61 -0
package/dist/kafka.mjs ADDED
@@ -0,0 +1,517 @@
1
+ import { _ as consumerError, a as sleep, c as attemptFromHeaders, g as brokerUnavailableError, l as headerRecord, o as truncate, r as errorMessage, s as MessagingHeaders, t as MAX_TIMER_DELAY_MS } from "./async-CFh_bvex.mjs";
2
+ import { r as durationOption, t as MAX_ERROR_HEADER_LENGTH } from "./shared-BJBpLS8V.mjs";
3
+ import { Admin, Consumer, MessagesStreamFallbackModes, MessagesStreamModes, ProduceAcks, Producer, stringDeserializer, stringSerializer } from "@platformatic/kafka";
4
+ // src/drivers/kafka.ts
5
+ const DEFAULT_MAX_RETRY_DELAY_MS = 9e5;
6
+ /** Attempts of a topic creation failing with transient broker errors, and the first backoff (doubled each time). */
7
+ const CREATE_TOPICS_ATTEMPTS = 3;
8
+ const CREATE_TOPICS_BACKOFF_MS = 250;
9
+ /** Headers carrying transport fields (not exposed as message headers). */
10
+ const TRANSPORT_HEADERS = /* @__PURE__ */ new Set([
11
+ MessagingHeaders.Id,
12
+ MessagingHeaders.Type,
13
+ MessagingHeaders.ContentType,
14
+ MessagingHeaders.NotBefore
15
+ ]);
16
+ /** Creates a {@link KafkaDriver}. */
17
+ function kafka(options) {
18
+ return new KafkaDriver(options);
19
+ }
20
+ /** Kafka {@link MessagingDriver}. See the module documentation. */
21
+ var KafkaDriver = class {
22
+ name = "kafka";
23
+ capabilities;
24
+ #options;
25
+ #clientId;
26
+ #subscriptions = /* @__PURE__ */ new Set();
27
+ /** Consumer group → topic, to refuse groups shared between topics. */
28
+ #groups = /* @__PURE__ */ new Map();
29
+ #logger = {
30
+ debug() {},
31
+ info() {},
32
+ warn() {},
33
+ error() {}
34
+ };
35
+ #producer;
36
+ #closed = false;
37
+ constructor(options) {
38
+ if (options === null || typeof options !== "object" || !Array.isArray(options.bootstrapBrokers) || options.bootstrapBrokers.length === 0) throw new TypeError("[znight] kafka(): the \"bootstrapBrokers\" option is required");
39
+ this.#options = options;
40
+ this.#clientId = options.clientId ?? "znight";
41
+ this.capabilities = Object.freeze({
42
+ nativeDelay: true,
43
+ maxNativeDelayMs: durationOption(options.maxRetryDelayMs, DEFAULT_MAX_RETRY_DELAY_MS, "kafka maxRetryDelayMs"),
44
+ maxInProcessDelayMs: durationOption(options.maxInProcessDelayMs, 0, "kafka maxInProcessDelayMs"),
45
+ deliveryCount: "header",
46
+ maxPublishDelayMs: 0
47
+ });
48
+ }
49
+ async connect(context) {
50
+ this.#logger = context.logger;
51
+ if (this.#closed) throw brokerUnavailableError("Kafka: the driver is closed, create a new one");
52
+ if (this.#producer !== void 0) return;
53
+ const producer = new Producer({
54
+ ...this.#options.connection,
55
+ clientId: this.#clientId,
56
+ bootstrapBrokers: [...this.#options.bootstrapBrokers],
57
+ serializers: {
58
+ key: stringSerializer,
59
+ headerKey: stringSerializer,
60
+ headerValue: stringSerializer
61
+ }
62
+ });
63
+ try {
64
+ await producer.metadata({ topics: [] });
65
+ } catch (error) {
66
+ await producer.close().catch(() => void 0);
67
+ throw brokerUnavailableError(`Kafka: cannot reach ${this.#options.bootstrapBrokers.join(", ")}: ${errorMessage(error)}`, error);
68
+ }
69
+ this.#producer = producer;
70
+ }
71
+ async close() {
72
+ if (this.#closed) return;
73
+ this.#closed = true;
74
+ const subscriptions = [...this.#subscriptions];
75
+ this.#subscriptions.clear();
76
+ await Promise.allSettled(subscriptions.map((subscription) => subscription.close()));
77
+ const producer = this.#producer;
78
+ this.#producer = void 0;
79
+ if (producer !== void 0) await producer.close().catch(() => void 0);
80
+ }
81
+ deadLetterName(topic) {
82
+ return this.#options.deadLetterName?.(topic) ?? `${topic}.dlq`;
83
+ }
84
+ #producerOrThrow(action) {
85
+ const producer = this.#producer;
86
+ if (producer === void 0) throw brokerUnavailableError(this.#closed ? `Kafka: cannot ${action}, the driver is closed` : `Kafka: cannot ${action} before connect()`);
87
+ return producer;
88
+ }
89
+ /** Produces one record and waits for the acknowledgement of all in-sync replicas. */
90
+ async produce(topic, value, headers, key, timestamp) {
91
+ await this.#producerOrThrow(`produce to "${topic}"`).send({
92
+ messages: [{
93
+ topic,
94
+ value,
95
+ headers: { ...headers },
96
+ ...key === void 0 ? {} : { key },
97
+ ...timestamp === void 0 ? {} : { timestamp: BigInt(timestamp) }
98
+ }],
99
+ acks: ProduceAcks.ALL
100
+ });
101
+ }
102
+ async publish(topic, message) {
103
+ if (message.delayMs > 0) throw new RangeError("[znight] The kafka driver does not support delayed publishing");
104
+ const headers = headerRecord();
105
+ for (const [name, value] of Object.entries(message.headers)) if (!TRANSPORT_HEADERS.has(name)) headers[name] = value;
106
+ headers[MessagingHeaders.Id] = message.id;
107
+ if (message.type !== void 0) headers[MessagingHeaders.Type] = message.type;
108
+ headers[MessagingHeaders.ContentType] = message.contentType;
109
+ try {
110
+ await this.produce(topic, Buffer.from(message.body), headers, message.key, message.timestamp);
111
+ } catch (error) {
112
+ throw brokerUnavailableError(`Kafka: producing to "${topic}" failed: ${errorMessage(error)}`, error);
113
+ }
114
+ }
115
+ async subscribe(topic, handler, options) {
116
+ this.#producerOrThrow(`subscribe to "${topic}"`);
117
+ const configuredGroup = options.options["groupId"];
118
+ if (configuredGroup !== void 0 && (typeof configuredGroup !== "string" || configuredGroup === "")) throw consumerError(`Kafka: the groupId of ${options.consumer} must be a non-empty string`);
119
+ const group = configuredGroup ?? `${this.#options.groupId ?? "znight"}.${topic}`;
120
+ const owner = this.#groups.get(group);
121
+ if (owner !== void 0 && owner !== topic) throw consumerError(`Kafka: consumer group "${group}" is used for "${owner}" and "${topic}"`, ["Each @Consumer needs its own consumer group: remove the groupId option or make it unique"]);
122
+ const retryTopic = options.maxAttempts > 1 ? this.#options.retryTopicName?.(topic, group) ?? `${group}.retry` : void 0;
123
+ const topics = [
124
+ topic,
125
+ retryTopic,
126
+ options.deadLetterQueue
127
+ ].filter((name) => name !== void 0);
128
+ this.#groups.set(group, topic);
129
+ try {
130
+ await this.#ensureTopics(topics, options.consumer);
131
+ } catch (error) {
132
+ this.#groups.delete(group);
133
+ throw error;
134
+ }
135
+ const subscription = new KafkaSubscription({
136
+ driver: this,
137
+ topic,
138
+ group,
139
+ retryTopic,
140
+ handler,
141
+ options,
142
+ logger: this.#logger
143
+ });
144
+ try {
145
+ await subscription.start((groupId) => new Consumer({
146
+ ...this.#options.connection,
147
+ ...this.#options.consumer,
148
+ clientId: this.#clientId,
149
+ bootstrapBrokers: [...this.#options.bootstrapBrokers],
150
+ groupId,
151
+ autocommit: false,
152
+ deserializers: {
153
+ key: stringDeserializer,
154
+ headerKey: stringDeserializer,
155
+ headerValue: stringDeserializer
156
+ }
157
+ }), this.#options.fromBeginning === true);
158
+ } catch (error) {
159
+ this.#groups.delete(group);
160
+ await subscription.close();
161
+ throw brokerUnavailableError(`Kafka: cannot consume "${topic}": ${errorMessage(error)}`, error);
162
+ }
163
+ this.#subscriptions.add(subscription);
164
+ return {
165
+ cancel: async () => {
166
+ await subscription.cancel();
167
+ },
168
+ close: async () => {
169
+ this.#subscriptions.delete(subscription);
170
+ this.#groups.delete(group);
171
+ await subscription.close();
172
+ }
173
+ };
174
+ }
175
+ async #ensureTopics(topics, consumer) {
176
+ const admin = new Admin({
177
+ ...this.#options.connection,
178
+ clientId: `${this.#clientId}-admin`,
179
+ bootstrapBrokers: [...this.#options.bootstrapBrokers]
180
+ });
181
+ try {
182
+ const existing = new Set(await admin.listTopics());
183
+ const missing = topics.filter((topic) => !existing.has(topic));
184
+ if (missing.length === 0) return;
185
+ const create = this.#options.createTopics;
186
+ if (create === void 0 || create === false) throw consumerError(`Kafka: topic(s) ${missing.map((name) => `"${name}"`).join(", ")} of ${consumer} do not exist`, ["Create them, or set kafka({ createTopics: true }) to let Znight create them"]);
187
+ await createMissingTopics(admin, missing, create === true ? {} : create, consumer);
188
+ } finally {
189
+ await admin.close().catch(() => void 0);
190
+ }
191
+ }
192
+ };
193
+ /**
194
+ * Creates `topics`. A topic the broker reports as already existing counts as created: subscriptions
195
+ * sharing a topic (a consumer and the collector of its dead letters) or other instances create it
196
+ * concurrently, and the topic listing lags behind the controller, so it cannot tell on its own. The
197
+ * client retries transient errors by itself, but not in a response that also reports an existing
198
+ * topic (not retriable): then the topics still missing are retried here, with a short backoff.
199
+ */
200
+ async function createMissingTopics(admin, topics, settings, consumer) {
201
+ let pending = topics;
202
+ for (let attempt = 1;; attempt++) try {
203
+ await admin.createTopics({
204
+ topics: [...pending],
205
+ partitions: settings.partitions ?? 1,
206
+ replicas: settings.replicas ?? 1
207
+ });
208
+ return;
209
+ } catch (error) {
210
+ const all = topicCreationFailures(error);
211
+ const failures = all.filter((failure) => failure.code !== "TOPIC_ALREADY_EXISTS");
212
+ if (failures.length === 0) return;
213
+ const failed = failures.every((failure) => failure.topic !== void 0) ? new Set(failures.map((failure) => failure.topic)) : void 0;
214
+ const listed = new Set(await admin.listTopics().catch(() => []));
215
+ pending = pending.filter((topic) => (failed?.has(topic) ?? true) && !listed.has(topic));
216
+ if (pending.length === 0) return;
217
+ const clientRetried = failures.length === all.length;
218
+ if (attempt >= CREATE_TOPICS_ATTEMPTS || clientRetried || failures.some((failure) => !failure.retriable)) {
219
+ const causes = new Set(failures.map(({ topic, code, message }) => `${topic === void 0 ? "" : `"${topic}" `}${code}: ${message}`));
220
+ throw brokerUnavailableError(`Kafka: cannot create topic(s) ${pending.map((name) => `"${name}"`).join(", ")} of ${consumer} (${attempt} attempt${attempt === 1 ? "" : "s"}): ${[...causes].join("; ")}`, error);
221
+ }
222
+ await sleep(CREATE_TOPICS_BACKOFF_MS * 2 ** (attempt - 1));
223
+ }
224
+ }
225
+ /**
226
+ * The leaf errors of a failed `createTopics`: a `MultipleErrors` of the attempts of the client (only
227
+ * the last one counts), each a `ResponseError` carrying the response and one `ProtocolError` per failed
228
+ * topic (`path: /topics/<index>`), or a network error.
229
+ */
230
+ function topicCreationFailures(error, response) {
231
+ const fields = error !== null && typeof error === "object" ? error : {};
232
+ const nested = fields["errors"];
233
+ if (Array.isArray(nested) && nested.length > 0) return (fields["response"] === void 0 ? nested.slice(-1) : nested).flatMap((inner) => topicCreationFailures(inner, fields["response"] ?? response));
234
+ const index = /^\/topics\/(\d+)$/.exec(String(fields["path"]))?.[1];
235
+ const topics = response?.topics;
236
+ const name = index !== void 0 && Array.isArray(topics) ? topics[Number(index)]?.name : void 0;
237
+ return [{
238
+ topic: typeof name === "string" ? name : void 0,
239
+ code: String(fields["apiId"] ?? fields["code"] ?? (error instanceof Error ? error.name : "UNKNOWN")),
240
+ message: String(fields["serverErrorMessage"] ?? errorMessage(error)),
241
+ retriable: fields["canRetry"] !== false
242
+ }];
243
+ }
244
+ /**
245
+ * The consumers of one `@Consumer`: the main topic and, with retries, the retry topic. Records are
246
+ * queued per partition; at most `concurrency` partitions are handled at once, one record at a time.
247
+ */
248
+ var KafkaSubscription = class {
249
+ #config;
250
+ #partitions = /* @__PURE__ */ new Map();
251
+ #ready = [];
252
+ #sources = [];
253
+ #highWater;
254
+ #lowWater;
255
+ #idle = /* @__PURE__ */ new Set();
256
+ #running = 0;
257
+ #active = true;
258
+ #closed;
259
+ constructor(config) {
260
+ this.#config = config;
261
+ this.#highWater = Math.max(64, config.options.concurrency * 8);
262
+ this.#lowWater = Math.floor(this.#highWater / 2);
263
+ }
264
+ async start(createConsumer, fromBeginning) {
265
+ const { topic, group, retryTopic } = this.#config;
266
+ await this.#consume(createConsumer(group), topic, fromBeginning);
267
+ if (retryTopic !== void 0) await this.#consume(createConsumer(`${group}.retry`), retryTopic, true);
268
+ }
269
+ async #consume(consumer, topic, fromBeginning) {
270
+ const source = {
271
+ topic,
272
+ consumer,
273
+ stream: void 0,
274
+ buffered: 0,
275
+ paused: false,
276
+ assigned: void 0
277
+ };
278
+ this.#sources.push(source);
279
+ consumer.on("consumer:group:join", () => this.#onGroupJoin(source));
280
+ const stream = await consumer.consume({
281
+ topics: [topic],
282
+ autocommit: false,
283
+ mode: MessagesStreamModes.COMMITTED,
284
+ fallbackMode: fromBeginning ? MessagesStreamFallbackModes.EARLIEST : MessagesStreamFallbackModes.LATEST
285
+ });
286
+ source.stream = stream;
287
+ stream.on("data", (record) => this.#push(source, record));
288
+ stream.on("error", (error) => {
289
+ this.#config.logger.error(`Kafka: consumer stream of "${topic}" failed`, { err: error });
290
+ });
291
+ }
292
+ /** Stops dispatching: buffered records are dropped (not committed, consumed again later). */
293
+ async cancel() {
294
+ await Promise.resolve();
295
+ if (!this.#active) return;
296
+ this.#active = false;
297
+ for (const source of this.#sources) source.stream?.pause();
298
+ for (const queue of this.#partitions.values()) this.#dropBuffered(queue);
299
+ this.#ready.length = 0;
300
+ }
301
+ /** Waits for the records being handled, then leaves the consumer groups. Idempotent. */
302
+ close() {
303
+ this.#closed ??= this.#close();
304
+ return this.#closed;
305
+ }
306
+ async #close() {
307
+ await this.cancel();
308
+ if (this.#running > 0) await new Promise((resolve) => this.#idle.add(resolve));
309
+ await Promise.allSettled(this.#sources.map((source) => source.stream?.close()));
310
+ await Promise.allSettled(this.#sources.map((source) => source.consumer.close()));
311
+ }
312
+ /**
313
+ * A (re)join of the group, with its new assignment: the partitions this member lost belong to
314
+ * another one now, so their buffered records are dropped (the record being handled finishes).
315
+ */
316
+ #onGroupJoin(source) {
317
+ const assigned = /* @__PURE__ */ new Set();
318
+ for (const assignment of source.consumer.assignments ?? []) if (assignment.topic === source.topic) for (const partition of assignment.partitions) assigned.add(partition);
319
+ source.assigned = assigned;
320
+ for (const queue of this.#partitions.values()) {
321
+ if (queue.source !== source || assigned.has(queue.partition)) continue;
322
+ this.#dropBuffered(queue);
323
+ queue.next = queue.running ? queue.records[0].offset + 1n : void 0;
324
+ }
325
+ }
326
+ /** Drops the records of `queue` that are not being handled. */
327
+ #dropBuffered(queue) {
328
+ if (queue.timer !== void 0) clearTimeout(queue.timer);
329
+ queue.timer = void 0;
330
+ const keep = queue.running ? queue.records.slice(0, 1) : [];
331
+ this.#unbuffer(queue.source, queue.records.length - keep.length);
332
+ queue.records.length = 0;
333
+ queue.records.push(...keep);
334
+ }
335
+ #push(source, record) {
336
+ if (!this.#active) return;
337
+ if (source.assigned !== void 0 && !source.assigned.has(record.partition)) return;
338
+ const key = `${record.topic}:${record.partition}`;
339
+ let queue = this.#partitions.get(key);
340
+ if (queue === void 0) {
341
+ queue = {
342
+ key,
343
+ source,
344
+ partition: record.partition,
345
+ records: [],
346
+ running: false,
347
+ queued: false,
348
+ timer: void 0,
349
+ next: void 0
350
+ };
351
+ this.#partitions.set(key, queue);
352
+ }
353
+ if (queue.next !== void 0 && record.offset < queue.next) return;
354
+ queue.next = record.offset + 1n;
355
+ queue.records.push(record);
356
+ source.buffered++;
357
+ if (!queue.running && !queue.queued && queue.timer === void 0) this.#enqueue(queue);
358
+ if (source.buffered >= this.#highWater) {
359
+ source.paused = true;
360
+ source.stream?.pause();
361
+ }
362
+ this.#pump();
363
+ }
364
+ /** `count` records of `source` left the partition queues: resume it below the low-water mark. */
365
+ #unbuffer(source, count) {
366
+ source.buffered -= count;
367
+ if (source.paused && this.#active && source.buffered <= this.#lowWater) {
368
+ source.paused = false;
369
+ source.stream?.resume();
370
+ }
371
+ }
372
+ #enqueue(queue) {
373
+ queue.queued = true;
374
+ this.#ready.push(queue);
375
+ }
376
+ #pump() {
377
+ while (this.#active && this.#running < this.#config.options.concurrency && this.#ready.length > 0) {
378
+ const queue = this.#ready.shift();
379
+ queue.queued = false;
380
+ const head = queue.records[0];
381
+ if (head === void 0) continue;
382
+ const wait = notBefore(head) - Date.now();
383
+ if (wait > 0) {
384
+ queue.timer = setTimeout(() => {
385
+ queue.timer = void 0;
386
+ if (!this.#active) return;
387
+ this.#enqueue(queue);
388
+ this.#pump();
389
+ }, Math.min(wait, this.#config.driver.capabilities.maxNativeDelayMs, MAX_TIMER_DELAY_MS));
390
+ continue;
391
+ }
392
+ queue.running = true;
393
+ this.#running++;
394
+ this.#work(queue, head).then(() => void 0, () => void 0);
395
+ }
396
+ }
397
+ async #work(queue, record) {
398
+ let outcome = "stop";
399
+ try {
400
+ outcome = await this.#process(record);
401
+ } catch (error) {
402
+ this.#config.logger.error(`Kafka: unexpected failure handling a record of ${queue.key}`, { err: error });
403
+ outcome = this.#active ? "again" : "stop";
404
+ } finally {
405
+ queue.running = false;
406
+ this.#running--;
407
+ const assigned = queue.source.assigned;
408
+ if (outcome === "again" && assigned !== void 0 && !assigned.has(queue.partition)) {
409
+ outcome = "stop";
410
+ queue.next = void 0;
411
+ }
412
+ if (outcome === "next") {
413
+ queue.records.shift();
414
+ this.#unbuffer(queue.source, 1);
415
+ } else if (outcome === "stop") {
416
+ const dropped = queue.records.length;
417
+ queue.records.length = 0;
418
+ this.#unbuffer(queue.source, dropped);
419
+ }
420
+ if (this.#active && queue.records.length > 0) this.#enqueue(queue);
421
+ if (this.#running === 0) {
422
+ const waiters = [...this.#idle];
423
+ this.#idle.clear();
424
+ for (const waiter of waiters) waiter();
425
+ }
426
+ this.#pump();
427
+ }
428
+ }
429
+ /** Hands one record to the runtime; resolves once it was settled. */
430
+ async #process(record) {
431
+ const { driver, topic, retryTopic, group, logger } = this.#config;
432
+ const all = headerRecord();
433
+ for (const [name, value] of record.headers) if (value !== void 0) all[name] = value;
434
+ const headers = headerRecord();
435
+ for (const [name, value] of Object.entries(all)) if (!TRANSPORT_HEADERS.has(name)) headers[name] = value;
436
+ const id = all[MessagingHeaders.Id] ?? `${record.topic}-${record.partition}-${record.offset}`;
437
+ const value = record.value ?? Buffer.alloc(0);
438
+ let settled = false;
439
+ let released = false;
440
+ const check = (action) => {
441
+ if (settled) throw new Error(`[kafka] record ${id} was already settled (${action})`);
442
+ };
443
+ /** Commits the offset. A failure (rebalance...) is not fatal: the record may be consumed again. */
444
+ const commit = async () => {
445
+ try {
446
+ await record.commit();
447
+ } catch (error) {
448
+ logger.warn(`Kafka: committing record ${id} of ${record.topic}:${record.partition} failed, it may be consumed again`, { err: error });
449
+ }
450
+ };
451
+ const copyHeaders = (extra, drop) => {
452
+ const result = headerRecord();
453
+ for (const [name, headerValue] of Object.entries(all)) if (!drop.includes(name)) result[name] = headerValue;
454
+ return Object.assign(result, extra);
455
+ };
456
+ const delivery = {
457
+ id,
458
+ body: value,
459
+ contentType: all[MessagingHeaders.ContentType],
460
+ headers,
461
+ type: all[MessagingHeaders.Type],
462
+ key: record.key ?? void 0,
463
+ attempt: attemptFromHeaders(all),
464
+ redelivered: attemptFromHeaders(all) > 1,
465
+ timestamp: Number(record.timestamp),
466
+ raw: record,
467
+ ack: async () => {
468
+ check("ack");
469
+ settled = true;
470
+ await commit();
471
+ },
472
+ release: async () => {
473
+ await Promise.resolve();
474
+ check("release");
475
+ settled = true;
476
+ released = true;
477
+ },
478
+ retry: async (request) => {
479
+ check("retry");
480
+ if (retryTopic === void 0) throw new Error(`[kafka] ${topic} has no retry topic (single attempt)`);
481
+ const extra = { [MessagingHeaders.Attempt]: String(request.attempt) };
482
+ if (request.delayMs > 0) extra[MessagingHeaders.NotBefore] = String(Date.now() + request.delayMs);
483
+ await driver.produce(retryTopic, value, copyHeaders(extra, [MessagingHeaders.NotBefore]), record.key ?? void 0);
484
+ settled = true;
485
+ await commit();
486
+ },
487
+ deadLetter: async (request) => {
488
+ check("dead-letter");
489
+ if (request.queue !== void 0) await driver.produce(request.queue, value, copyHeaders({
490
+ [MessagingHeaders.DeadLetterReason]: request.reason,
491
+ [MessagingHeaders.Error]: truncate(errorMessage(request.error), MAX_ERROR_HEADER_LENGTH),
492
+ [MessagingHeaders.OriginalQueue]: topic,
493
+ [MessagingHeaders.Attempts]: String(request.attempt),
494
+ [MessagingHeaders.ConsumerGroup]: group
495
+ }, [MessagingHeaders.Attempt, MessagingHeaders.NotBefore]), record.key ?? void 0);
496
+ settled = true;
497
+ await commit();
498
+ }
499
+ };
500
+ try {
501
+ await this.#config.handler(delivery);
502
+ } catch (error) {
503
+ logger.error(`Kafka: unexpected failure handling record ${id}`, { err: error });
504
+ }
505
+ if (released || !settled) return this.#active ? "again" : "stop";
506
+ return "next";
507
+ }
508
+ };
509
+ /** `x-znight-not-before` of a retry record (epoch ms), 0 when absent. */
510
+ function notBefore(record) {
511
+ const value = record.headers.get(MessagingHeaders.NotBefore);
512
+ const time = value === void 0 ? 0 : Number(value);
513
+ return Number.isFinite(time) ? time : 0;
514
+ }
515
+ export { KafkaDriver, kafka };
516
+
517
+ //# sourceMappingURL=kafka.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kafka.mjs","names":[],"sources":["../src/drivers/kafka.ts"],"sourcesContent":["/**\n * `@znight/messaging/kafka`: Apache Kafka driver built on\n * [@platformatic/kafka](https://github.com/platformatic/kafka) 2.x (optional peer dependency).\n *\n * - Each `@Consumer` uses its own consumer group: `<groupId>.<topic>` (or `@Consumer({ groupId })`),\n * with manual commits: an offset is committed once its record was settled.\n * - Records of a partition are handled one after the other (ordering), different partitions\n * concurrently up to the consumer concurrency.\n * - Retries are non-blocking: the record is produced to the group's retry topic (`<group>.retry`) with\n * `x-znight-attempt` and `x-znight-not-before`, then committed. A dedicated retry consumer waits\n * until `not-before` (bounded by `maxRetryDelayMs`) without blocking the main topic (each stream has\n * its own back-pressure). Retried records lose their ordering relative to the partition.\n * - After a rebalance, records fetched again from the committed offset of a partition this member kept\n * are skipped (already buffered or handled); buffered records of the partitions it lost are dropped.\n * - Dead letters are produced to `<topic>.dlq` (acks from all in-sync replicas), then committed.\n * - On stop, unsettled records are not committed: they are consumed again after a restart or rebalance.\n * - Delayed publishing is not supported.\n */\nimport type { ConsumerOptions, Message, MessagesStream, ProducerOptions } from '@platformatic/kafka'\nimport type {\n DeadLetterRequest,\n DeliveryHandler,\n DriverCapabilities,\n DriverContext,\n DriverDelivery,\n MessagingDriver,\n RetryRequest,\n SubscribeOptions,\n Subscription,\n} from '../driver.ts'\nimport type { OutboundMessage } from '../message.ts'\n\nimport {\n Admin,\n Consumer,\n MessagesStreamFallbackModes,\n MessagesStreamModes,\n ProduceAcks,\n Producer,\n stringDeserializer,\n stringSerializer,\n} from '@platformatic/kafka'\n\nimport { brokerUnavailableError, consumerError } from '../errors.ts'\nimport { attemptFromHeaders, headerRecord, MessagingHeaders } from '../headers.ts'\nimport { errorMessage, MAX_TIMER_DELAY_MS, sleep, truncate } from '../internal/async.ts'\nimport { durationOption, MAX_ERROR_HEADER_LENGTH } from './shared.ts'\n\ntype KafkaRecord = Message<string, Buffer, string, string>\n\n/** Topic creation settings. */\nexport interface KafkaTopicOptions {\n /** Partitions of created topics. Default: 1. */\n readonly partitions?: number\n /** Replication factor of created topics. Default: 1. */\n readonly replicas?: number\n}\n\n/** Options of {@link kafka}. */\nexport interface KafkaDriverOptions {\n /** `host:port` of the bootstrap brokers. */\n readonly bootstrapBrokers: readonly string[]\n /** Client id. Default: `znight`. */\n readonly clientId?: string\n /** Prefix of the consumer groups (`<groupId>.<topic>`), typically the service name. Default: `znight`. */\n readonly groupId?: string\n /** Options shared by the producer, the consumers and the admin client (tls, sasl, timeouts...). */\n readonly connection?: Readonly<Partial<ProducerOptions<string, Buffer, string, string>>>\n /** Extra consumer options (sessionTimeout, heartbeatInterval...). */\n readonly consumer?: Readonly<Partial<ConsumerOptions<string, Buffer, string, string>>>\n /** Start new consumer groups at the beginning of the topic (`earliest`). Default: `false` (latest). */\n readonly fromBeginning?: boolean\n /** Create missing topics at subscribe time; otherwise their existence is verified. Default: `false`. */\n readonly createTopics?: boolean | KafkaTopicOptions\n /** Name of the retry topic of a consumer group. Default: `${group}.retry`. */\n readonly retryTopicName?: (topic: string, group: string) => string\n /** Default dead-letter topic. Default: `${topic}.dlq`. */\n readonly deadLetterName?: (topic: string) => string\n /** Longest retry delay waited in-process before producing the retry (blocks the partition). Default: 0. */\n readonly maxInProcessDelayMs?: number\n /** Longest delay a retry record may wait in the retry consumer. Default: 15 minutes. */\n readonly maxRetryDelayMs?: number\n}\n\nconst DEFAULT_MAX_RETRY_DELAY_MS = 15 * 60_000\n/** Attempts of a topic creation failing with transient broker errors, and the first backoff (doubled each time). */\nconst CREATE_TOPICS_ATTEMPTS = 3\nconst CREATE_TOPICS_BACKOFF_MS = 250\n/** Headers carrying transport fields (not exposed as message headers). */\nconst TRANSPORT_HEADERS = new Set<string>([\n MessagingHeaders.Id,\n MessagingHeaders.Type,\n MessagingHeaders.ContentType,\n MessagingHeaders.NotBefore,\n])\n\n/** Creates a {@link KafkaDriver}. */\nexport function kafka(options: KafkaDriverOptions): KafkaDriver {\n return new KafkaDriver(options)\n}\n\n/** Kafka {@link MessagingDriver}. See the module documentation. */\nexport class KafkaDriver implements MessagingDriver {\n readonly name = 'kafka'\n readonly capabilities: DriverCapabilities\n readonly #options: KafkaDriverOptions\n readonly #clientId: string\n readonly #subscriptions = new Set<KafkaSubscription>()\n /** Consumer group → topic, to refuse groups shared between topics. */\n readonly #groups = new Map<string, string>()\n #logger: DriverContext['logger'] = { debug() {}, info() {}, warn() {}, error() {} }\n #producer: Producer<string, Buffer, string, string> | undefined\n #closed = false\n\n constructor(options: KafkaDriverOptions) {\n if (\n options === null ||\n typeof options !== 'object' ||\n !Array.isArray(options.bootstrapBrokers) ||\n options.bootstrapBrokers.length === 0\n ) {\n throw new TypeError('[znight] kafka(): the \"bootstrapBrokers\" option is required')\n }\n this.#options = options\n this.#clientId = options.clientId ?? 'znight'\n this.capabilities = Object.freeze({\n nativeDelay: true,\n maxNativeDelayMs: durationOption(options.maxRetryDelayMs, DEFAULT_MAX_RETRY_DELAY_MS, 'kafka maxRetryDelayMs'),\n maxInProcessDelayMs: durationOption(options.maxInProcessDelayMs, 0, 'kafka maxInProcessDelayMs'),\n deliveryCount: 'header',\n maxPublishDelayMs: 0,\n })\n }\n\n async connect(context: DriverContext): Promise<void> {\n this.#logger = context.logger\n if (this.#closed) throw brokerUnavailableError('Kafka: the driver is closed, create a new one')\n if (this.#producer !== undefined) return\n const producer = new Producer<string, Buffer, string, string>({\n ...this.#options.connection,\n clientId: this.#clientId,\n bootstrapBrokers: [...this.#options.bootstrapBrokers],\n serializers: { key: stringSerializer, headerKey: stringSerializer, headerValue: stringSerializer },\n })\n try {\n await producer.metadata({ topics: [] })\n } catch (error) {\n await producer.close().catch(() => undefined)\n throw brokerUnavailableError(\n `Kafka: cannot reach ${this.#options.bootstrapBrokers.join(', ')}: ${errorMessage(error)}`,\n error,\n )\n }\n this.#producer = producer\n }\n\n async close(): Promise<void> {\n if (this.#closed) return\n this.#closed = true\n const subscriptions = [...this.#subscriptions]\n this.#subscriptions.clear()\n await Promise.allSettled(subscriptions.map(subscription => subscription.close()))\n const producer = this.#producer\n this.#producer = undefined\n if (producer !== undefined) await producer.close().catch(() => undefined)\n }\n\n deadLetterName(topic: string): string {\n return this.#options.deadLetterName?.(topic) ?? `${topic}.dlq`\n }\n\n #producerOrThrow(action: string): Producer<string, Buffer, string, string> {\n const producer = this.#producer\n if (producer === undefined) {\n throw brokerUnavailableError(\n this.#closed ? `Kafka: cannot ${action}, the driver is closed` : `Kafka: cannot ${action} before connect()`,\n )\n }\n return producer\n }\n\n /** Produces one record and waits for the acknowledgement of all in-sync replicas. */\n async produce(\n topic: string,\n value: Buffer,\n headers: Readonly<Record<string, string>>,\n key: string | undefined,\n timestamp?: number,\n ): Promise<void> {\n await this.#producerOrThrow(`produce to \"${topic}\"`).send({\n messages: [\n {\n topic,\n value,\n headers: { ...headers },\n ...(key === undefined ? {} : { key }),\n ...(timestamp === undefined ? {} : { timestamp: BigInt(timestamp) }),\n },\n ],\n acks: ProduceAcks.ALL,\n })\n }\n\n async publish(topic: string, message: OutboundMessage): Promise<void> {\n if (message.delayMs > 0) throw new RangeError('[znight] The kafka driver does not support delayed publishing')\n const headers = headerRecord()\n for (const [name, value] of Object.entries(message.headers)) if (!TRANSPORT_HEADERS.has(name)) headers[name] = value\n headers[MessagingHeaders.Id] = message.id\n if (message.type !== undefined) headers[MessagingHeaders.Type] = message.type\n headers[MessagingHeaders.ContentType] = message.contentType\n try {\n await this.produce(topic, Buffer.from(message.body), headers, message.key, message.timestamp)\n } catch (error) {\n throw brokerUnavailableError(`Kafka: producing to \"${topic}\" failed: ${errorMessage(error)}`, error)\n }\n }\n\n // -----------------------------------------------------------------------------------------------\n // Consuming\n // -----------------------------------------------------------------------------------------------\n\n async subscribe(topic: string, handler: DeliveryHandler, options: SubscribeOptions): Promise<Subscription> {\n this.#producerOrThrow(`subscribe to \"${topic}\"`)\n const configuredGroup = options.options['groupId']\n if (configuredGroup !== undefined && (typeof configuredGroup !== 'string' || configuredGroup === '')) {\n throw consumerError(`Kafka: the groupId of ${options.consumer} must be a non-empty string`)\n }\n const group = configuredGroup ?? `${this.#options.groupId ?? 'znight'}.${topic}`\n const owner = this.#groups.get(group)\n if (owner !== undefined && owner !== topic) {\n throw consumerError(`Kafka: consumer group \"${group}\" is used for \"${owner}\" and \"${topic}\"`, [\n 'Each @Consumer needs its own consumer group: remove the groupId option or make it unique',\n ])\n }\n const retryTopic =\n options.maxAttempts > 1 ? (this.#options.retryTopicName?.(topic, group) ?? `${group}.retry`) : undefined\n const topics = [topic, retryTopic, options.deadLetterQueue].filter((name): name is string => name !== undefined)\n this.#groups.set(group, topic) // reserved before any await: subscriptions may start concurrently\n try {\n await this.#ensureTopics(topics, options.consumer)\n } catch (error) {\n this.#groups.delete(group)\n throw error\n }\n\n const subscription = new KafkaSubscription({\n driver: this,\n topic,\n group,\n retryTopic,\n handler,\n options,\n logger: this.#logger,\n })\n try {\n await subscription.start(\n groupId =>\n new Consumer<string, Buffer, string, string>({\n ...this.#options.connection,\n ...this.#options.consumer,\n clientId: this.#clientId,\n bootstrapBrokers: [...this.#options.bootstrapBrokers],\n groupId,\n autocommit: false,\n deserializers: { key: stringDeserializer, headerKey: stringDeserializer, headerValue: stringDeserializer },\n } as ConsumerOptions<string, Buffer, string, string>),\n this.#options.fromBeginning === true,\n )\n } catch (error) {\n this.#groups.delete(group)\n await subscription.close()\n throw brokerUnavailableError(`Kafka: cannot consume \"${topic}\": ${errorMessage(error)}`, error)\n }\n this.#subscriptions.add(subscription)\n return {\n cancel: async (): Promise<void> => {\n await subscription.cancel()\n },\n close: async (): Promise<void> => {\n this.#subscriptions.delete(subscription)\n this.#groups.delete(group)\n await subscription.close()\n },\n }\n }\n\n async #ensureTopics(topics: readonly string[], consumer: string): Promise<void> {\n const admin = new Admin({\n ...this.#options.connection,\n clientId: `${this.#clientId}-admin`,\n bootstrapBrokers: [...this.#options.bootstrapBrokers],\n })\n try {\n const existing = new Set(await admin.listTopics())\n const missing = topics.filter(topic => !existing.has(topic))\n if (missing.length === 0) return\n const create = this.#options.createTopics\n if (create === undefined || create === false) {\n throw consumerError(\n `Kafka: topic(s) ${missing.map(name => `\"${name}\"`).join(', ')} of ${consumer} do not exist`,\n ['Create them, or set kafka({ createTopics: true }) to let Znight create them'],\n )\n }\n await createMissingTopics(admin, missing, create === true ? {} : create, consumer)\n } finally {\n await admin.close().catch(() => undefined)\n }\n }\n}\n\n/**\n * Creates `topics`. A topic the broker reports as already existing counts as created: subscriptions\n * sharing a topic (a consumer and the collector of its dead letters) or other instances create it\n * concurrently, and the topic listing lags behind the controller, so it cannot tell on its own. The\n * client retries transient errors by itself, but not in a response that also reports an existing\n * topic (not retriable): then the topics still missing are retried here, with a short backoff.\n */\nasync function createMissingTopics(\n admin: Admin,\n topics: readonly string[],\n settings: KafkaTopicOptions,\n consumer: string,\n): Promise<void> {\n let pending = topics\n for (let attempt = 1; ; attempt++) {\n try {\n await admin.createTopics({\n topics: [...pending],\n partitions: settings.partitions ?? 1,\n replicas: settings.replicas ?? 1,\n })\n return\n } catch (error) {\n const all = topicCreationFailures(error)\n const failures = all.filter(failure => failure.code !== 'TOPIC_ALREADY_EXISTS')\n if (failures.length === 0) return\n // a topic without an error was created, unless the request failed as a whole (timeout...)\n const failed = failures.every(failure => failure.topic !== undefined)\n ? new Set(failures.map(failure => failure.topic))\n : undefined\n // created in the meantime, by this request or concurrently\n const listed = new Set(await admin.listTopics().catch((): string[] => []))\n pending = pending.filter(topic => (failed?.has(topic) ?? true) && !listed.has(topic))\n if (pending.length === 0) return\n // without an existing topic in the response, the client already spent its own retries\n const clientRetried = failures.length === all.length\n if (attempt >= CREATE_TOPICS_ATTEMPTS || clientRetried || failures.some(failure => !failure.retriable)) {\n const causes = new Set(\n failures.map(({ topic, code, message }) => `${topic === undefined ? '' : `\"${topic}\" `}${code}: ${message}`),\n )\n throw brokerUnavailableError(\n `Kafka: cannot create topic(s) ${pending.map(name => `\"${name}\"`).join(', ')} of ${consumer}` +\n ` (${attempt} attempt${attempt === 1 ? '' : 's'}): ${[...causes].join('; ')}`,\n error,\n )\n }\n await sleep(CREATE_TOPICS_BACKOFF_MS * 2 ** (attempt - 1))\n }\n }\n}\n\n/** One error of a failed topic creation. */\ninterface TopicCreationFailure {\n /** The topic it concerns, when the broker answered topic by topic. */\n readonly topic: string | undefined\n /** Kafka error (`TOPIC_ALREADY_EXISTS`, `REQUEST_TIMED_OUT`...) or client error code. */\n readonly code: string\n readonly message: string\n /** Whether the client flags it as worth retrying. */\n readonly retriable: boolean\n}\n\n/**\n * The leaf errors of a failed `createTopics`: a `MultipleErrors` of the attempts of the client (only\n * the last one counts), each a `ResponseError` carrying the response and one `ProtocolError` per failed\n * topic (`path: /topics/<index>`), or a network error.\n */\nfunction topicCreationFailures(error: unknown, response?: unknown): TopicCreationFailure[] {\n const fields = (error !== null && typeof error === 'object' ? error : {}) as Record<string, unknown>\n const nested = fields['errors']\n if (Array.isArray(nested) && nested.length > 0) {\n // the errors of a response are its topics; the errors of a retry wrapper are attempts\n const inners = fields['response'] === undefined ? nested.slice(-1) : nested\n return inners.flatMap(inner => topicCreationFailures(inner, fields['response'] ?? response))\n }\n const index = /^\\/topics\\/(\\d+)$/.exec(String(fields['path']))?.[1]\n const topics = (response as { topics?: unknown } | null | undefined)?.topics\n const name =\n index !== undefined && Array.isArray(topics)\n ? (topics[Number(index)] as { name?: unknown } | undefined)?.name\n : undefined\n return [\n {\n topic: typeof name === 'string' ? name : undefined,\n code: String(fields['apiId'] ?? fields['code'] ?? (error instanceof Error ? error.name : 'UNKNOWN')),\n message: String(fields['serverErrorMessage'] ?? errorMessage(error)),\n retriable: fields['canRetry'] !== false,\n },\n ]\n}\n\n/**\n * A consumer of the subscription (main topic, or retry topic) with its stream and its back-pressure:\n * each stream is paused and resumed on its own, so that retries waiting for their `not-before` never\n * hold the main topic.\n */\ninterface StreamState {\n readonly topic: string\n readonly consumer: Consumer<string, Buffer, string, string>\n stream: MessagesStream<string, Buffer, string, string> | undefined\n /** Records of this stream in the partition queues (the ones being handled included). */\n buffered: number\n paused: boolean\n /** Partitions of `topic` assigned by the last group join (`undefined` before the first one). */\n assigned: ReadonlySet<number> | undefined\n}\n\ninterface PartitionQueue {\n readonly key: string\n readonly source: StreamState\n readonly partition: number\n readonly records: KafkaRecord[]\n running: boolean\n queued: boolean\n timer: ReturnType<typeof setTimeout> | undefined\n /**\n * Offset following the last record pushed. After a rebalance the stream fetches the partitions it\n * keeps again from their committed offset: older records were already buffered or handled.\n */\n next: bigint | undefined\n}\n\ninterface KafkaSubscriptionConfig {\n readonly driver: KafkaDriver\n readonly topic: string\n readonly group: string\n readonly retryTopic: string | undefined\n readonly handler: DeliveryHandler\n readonly options: SubscribeOptions\n readonly logger: DriverContext['logger']\n}\n\ntype Outcome = 'next' | 'again' | 'stop'\n\n/**\n * The consumers of one `@Consumer`: the main topic and, with retries, the retry topic. Records are\n * queued per partition; at most `concurrency` partitions are handled at once, one record at a time.\n */\nclass KafkaSubscription {\n readonly #config: KafkaSubscriptionConfig\n readonly #partitions = new Map<string, PartitionQueue>()\n readonly #ready: PartitionQueue[] = []\n readonly #sources: StreamState[] = []\n readonly #highWater: number\n readonly #lowWater: number\n readonly #idle = new Set<() => void>()\n #running = 0\n #active = true\n #closed: Promise<void> | undefined\n\n constructor(config: KafkaSubscriptionConfig) {\n this.#config = config\n this.#highWater = Math.max(64, config.options.concurrency * 8)\n this.#lowWater = Math.floor(this.#highWater / 2)\n }\n\n async start(\n createConsumer: (groupId: string) => Consumer<string, Buffer, string, string>,\n fromBeginning: boolean,\n ): Promise<void> {\n const { topic, group, retryTopic } = this.#config\n await this.#consume(createConsumer(group), topic, fromBeginning)\n // the retry topic only holds records of this group: always read it from the beginning\n if (retryTopic !== undefined) await this.#consume(createConsumer(`${group}.retry`), retryTopic, true)\n }\n\n async #consume(\n consumer: Consumer<string, Buffer, string, string>,\n topic: string,\n fromBeginning: boolean,\n ): Promise<void> {\n const source: StreamState = { topic, consumer, stream: undefined, buffered: 0, paused: false, assigned: undefined }\n this.#sources.push(source)\n consumer.on('consumer:group:join', () => this.#onGroupJoin(source))\n const stream = await consumer.consume({\n topics: [topic],\n autocommit: false,\n mode: MessagesStreamModes.COMMITTED,\n fallbackMode: fromBeginning ? MessagesStreamFallbackModes.EARLIEST : MessagesStreamFallbackModes.LATEST,\n })\n source.stream = stream\n stream.on('data', (record: KafkaRecord) => this.#push(source, record))\n stream.on('error', (error: Error) => {\n this.#config.logger.error(`Kafka: consumer stream of \"${topic}\" failed`, { err: error })\n })\n }\n\n /** Stops dispatching: buffered records are dropped (not committed, consumed again later). */\n async cancel(): Promise<void> {\n await Promise.resolve()\n if (!this.#active) return\n this.#active = false\n for (const source of this.#sources) source.stream?.pause()\n for (const queue of this.#partitions.values()) this.#dropBuffered(queue)\n this.#ready.length = 0\n }\n\n /** Waits for the records being handled, then leaves the consumer groups. Idempotent. */\n close(): Promise<void> {\n this.#closed ??= this.#close()\n return this.#closed\n }\n\n async #close(): Promise<void> {\n await this.cancel()\n if (this.#running > 0) await new Promise<void>(resolve => this.#idle.add(resolve))\n await Promise.allSettled(this.#sources.map(source => source.stream?.close()))\n await Promise.allSettled(this.#sources.map(source => source.consumer.close()))\n }\n\n /**\n * A (re)join of the group, with its new assignment: the partitions this member lost belong to\n * another one now, so their buffered records are dropped (the record being handled finishes).\n */\n #onGroupJoin(source: StreamState): void {\n const assigned = new Set<number>()\n for (const assignment of source.consumer.assignments ?? []) {\n if (assignment.topic === source.topic) for (const partition of assignment.partitions) assigned.add(partition)\n }\n source.assigned = assigned\n for (const queue of this.#partitions.values()) {\n if (queue.source !== source || assigned.has(queue.partition)) continue\n this.#dropBuffered(queue)\n // records older than the one being handled come back if the partition is assigned again\n queue.next = queue.running ? (queue.records[0] as KafkaRecord).offset + 1n : undefined\n }\n }\n\n /** Drops the records of `queue` that are not being handled. */\n #dropBuffered(queue: PartitionQueue): void {\n if (queue.timer !== undefined) clearTimeout(queue.timer)\n queue.timer = undefined\n const keep = queue.running ? queue.records.slice(0, 1) : []\n this.#unbuffer(queue.source, queue.records.length - keep.length)\n queue.records.length = 0\n queue.records.push(...keep)\n }\n\n #push(source: StreamState, record: KafkaRecord): void {\n if (!this.#active) return\n // a late record of a partition revoked by the last rebalance\n if (source.assigned !== undefined && !source.assigned.has(record.partition)) return\n const key = `${record.topic}:${record.partition}`\n let queue = this.#partitions.get(key)\n if (queue === undefined) {\n queue = {\n key,\n source,\n partition: record.partition,\n records: [],\n running: false,\n queued: false,\n timer: undefined,\n next: undefined,\n }\n this.#partitions.set(key, queue)\n }\n // fetched again after a rebalance (from the committed offset): already buffered or handled\n if (queue.next !== undefined && record.offset < queue.next) return\n queue.next = record.offset + 1n\n queue.records.push(record)\n source.buffered++\n if (!queue.running && !queue.queued && queue.timer === undefined) this.#enqueue(queue)\n if (source.buffered >= this.#highWater) {\n // pause again even when paused: the client resumes its streams after a rebalance\n source.paused = true\n source.stream?.pause()\n }\n this.#pump()\n }\n\n /** `count` records of `source` left the partition queues: resume it below the low-water mark. */\n #unbuffer(source: StreamState, count: number): void {\n source.buffered -= count\n if (source.paused && this.#active && source.buffered <= this.#lowWater) {\n source.paused = false\n source.stream?.resume()\n }\n }\n\n #enqueue(queue: PartitionQueue): void {\n queue.queued = true\n this.#ready.push(queue)\n }\n\n #pump(): void {\n while (this.#active && this.#running < this.#config.options.concurrency && this.#ready.length > 0) {\n const queue = this.#ready.shift() as PartitionQueue\n queue.queued = false\n const head = queue.records[0]\n if (head === undefined) continue\n const wait = notBefore(head) - Date.now()\n if (wait > 0) {\n // a retry that is not due yet: wait without holding a slot (timers cannot wait more than 2^31 - 1 ms)\n queue.timer = setTimeout(\n () => {\n queue.timer = undefined\n if (!this.#active) return\n this.#enqueue(queue)\n this.#pump()\n },\n Math.min(wait, this.#config.driver.capabilities.maxNativeDelayMs, MAX_TIMER_DELAY_MS),\n )\n continue\n }\n queue.running = true\n this.#running++\n this.#work(queue, head).then(\n () => undefined,\n () => undefined,\n )\n }\n }\n\n async #work(queue: PartitionQueue, record: KafkaRecord): Promise<void> {\n let outcome: Outcome = 'stop'\n try {\n outcome = await this.#process(record)\n } catch (error) {\n this.#config.logger.error(`Kafka: unexpected failure handling a record of ${queue.key}`, { err: error })\n outcome = this.#active ? 'again' : 'stop'\n } finally {\n queue.running = false\n this.#running--\n const assigned = queue.source.assigned\n if (outcome === 'again' && assigned !== undefined && !assigned.has(queue.partition)) {\n // revoked meanwhile: the new owner of the partition handles it\n outcome = 'stop'\n queue.next = undefined\n }\n if (outcome === 'next') {\n queue.records.shift()\n this.#unbuffer(queue.source, 1)\n } else if (outcome === 'stop') {\n const dropped = queue.records.length\n queue.records.length = 0\n this.#unbuffer(queue.source, dropped)\n }\n if (this.#active && queue.records.length > 0) this.#enqueue(queue)\n if (this.#running === 0) {\n const waiters = [...this.#idle]\n this.#idle.clear()\n for (const waiter of waiters) waiter()\n }\n this.#pump()\n }\n }\n\n /** Hands one record to the runtime; resolves once it was settled. */\n async #process(record: KafkaRecord): Promise<Outcome> {\n const { driver, topic, retryTopic, group, logger } = this.#config\n const all = headerRecord()\n for (const [name, value] of record.headers) if (value !== undefined) all[name] = value\n const headers = headerRecord()\n for (const [name, value] of Object.entries(all)) if (!TRANSPORT_HEADERS.has(name)) headers[name] = value\n const id = all[MessagingHeaders.Id] ?? `${record.topic}-${record.partition}-${record.offset}`\n const value = record.value ?? Buffer.alloc(0)\n let settled = false\n let released = false\n const check = (action: string): void => {\n if (settled) throw new Error(`[kafka] record ${id} was already settled (${action})`)\n }\n /** Commits the offset. A failure (rebalance...) is not fatal: the record may be consumed again. */\n const commit = async (): Promise<void> => {\n try {\n await record.commit()\n } catch (error) {\n logger.warn(\n `Kafka: committing record ${id} of ${record.topic}:${record.partition} failed, it may be consumed again`,\n {\n err: error,\n },\n )\n }\n }\n const copyHeaders = (extra: Record<string, string>, drop: readonly string[]): Record<string, string> => {\n const result = headerRecord()\n for (const [name, headerValue] of Object.entries(all)) if (!drop.includes(name)) result[name] = headerValue\n return Object.assign(result, extra)\n }\n const delivery: DriverDelivery = {\n id,\n body: value,\n contentType: all[MessagingHeaders.ContentType],\n headers,\n type: all[MessagingHeaders.Type],\n key: record.key ?? undefined,\n attempt: attemptFromHeaders(all),\n redelivered: attemptFromHeaders(all) > 1,\n timestamp: Number(record.timestamp),\n raw: record,\n ack: async (): Promise<void> => {\n check('ack')\n settled = true\n await commit()\n },\n release: async (): Promise<void> => {\n await Promise.resolve()\n check('release')\n settled = true\n released = true\n },\n retry: async (request: RetryRequest): Promise<void> => {\n check('retry')\n if (retryTopic === undefined) throw new Error(`[kafka] ${topic} has no retry topic (single attempt)`)\n const extra: Record<string, string> = { [MessagingHeaders.Attempt]: String(request.attempt) }\n if (request.delayMs > 0) extra[MessagingHeaders.NotBefore] = String(Date.now() + request.delayMs)\n await driver.produce(\n retryTopic,\n value,\n copyHeaders(extra, [MessagingHeaders.NotBefore]),\n record.key ?? undefined,\n )\n settled = true\n await commit()\n },\n deadLetter: async (request: DeadLetterRequest): Promise<void> => {\n check('dead-letter')\n if (request.queue !== undefined) {\n await driver.produce(\n request.queue,\n value,\n copyHeaders(\n {\n [MessagingHeaders.DeadLetterReason]: request.reason,\n [MessagingHeaders.Error]: truncate(errorMessage(request.error), MAX_ERROR_HEADER_LENGTH),\n [MessagingHeaders.OriginalQueue]: topic,\n [MessagingHeaders.Attempts]: String(request.attempt),\n [MessagingHeaders.ConsumerGroup]: group,\n },\n [MessagingHeaders.Attempt, MessagingHeaders.NotBefore],\n ),\n record.key ?? undefined,\n )\n }\n settled = true\n await commit()\n },\n }\n try {\n await this.#config.handler(delivery)\n } catch (error) {\n logger.error(`Kafka: unexpected failure handling record ${id}`, { err: error })\n }\n if (released || !settled) return this.#active ? 'again' : 'stop'\n return 'next'\n }\n}\n\n/** `x-znight-not-before` of a retry record (epoch ms), 0 when absent. */\nfunction notBefore(record: KafkaRecord): number {\n const value = record.headers.get(MessagingHeaders.NotBefore)\n const time = value === undefined ? 0 : Number(value)\n return Number.isFinite(time) ? time : 0\n}\n"],"mappings":"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"}