@drarzter/kafka-client 0.9.2 → 0.9.4
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/README.md +32 -7
- package/dist/{chunk-Z2DOJQRI.mjs → chunk-SM4FZKAZ.mjs} +6 -6
- package/dist/chunk-SM4FZKAZ.mjs.map +1 -0
- package/dist/client-1irhGEu0.d.mts +751 -0
- package/dist/client-BpFjkHhr.d.ts +751 -0
- package/dist/consumer.types-fFCag3VJ.d.mts +958 -0
- package/dist/consumer.types-fFCag3VJ.d.ts +958 -0
- package/dist/core.d.mts +126 -3
- package/dist/core.d.ts +126 -3
- package/dist/core.js +5 -5
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +1 -1
- package/dist/index.d.mts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/otel.d.mts +1 -1
- package/dist/otel.d.ts +1 -1
- package/dist/testing.d.mts +2 -1
- package/dist/testing.d.ts +2 -1
- package/dist/testing.js +5 -5
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +5 -5
- package/dist/testing.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-Z2DOJQRI.mjs.map +0 -1
- package/dist/types-4XNxkici.d.mts +0 -1952
- package/dist/types-4XNxkici.d.ts +0 -1952
|
@@ -1,1952 +0,0 @@
|
|
|
1
|
-
/** A topic-partition pair. */
|
|
2
|
-
type ITopicPartition = {
|
|
3
|
-
topic: string;
|
|
4
|
-
partition: number;
|
|
5
|
-
};
|
|
6
|
-
/** A topic-partition pair with an absolute offset string. */
|
|
7
|
-
type ITopicPartitionOffset = {
|
|
8
|
-
topic: string;
|
|
9
|
-
partition: number;
|
|
10
|
-
offset: string;
|
|
11
|
-
};
|
|
12
|
-
/** Pause / resume assignment shape: one topic + its partition list. */
|
|
13
|
-
type ITopicPartitions = {
|
|
14
|
-
topic: string;
|
|
15
|
-
partitions: number[];
|
|
16
|
-
};
|
|
17
|
-
/** A single message in a produce request. */
|
|
18
|
-
type IProducerMessage = {
|
|
19
|
-
value: string | null;
|
|
20
|
-
key?: string;
|
|
21
|
-
headers?: Record<string, string | Buffer | string[]>;
|
|
22
|
-
};
|
|
23
|
-
/** Produce request payload for one topic. */
|
|
24
|
-
type IProducerRecord = {
|
|
25
|
-
topic: string;
|
|
26
|
-
messages: IProducerMessage[];
|
|
27
|
-
};
|
|
28
|
-
/** Options for creating a producer. */
|
|
29
|
-
type IProducerCreationOptions = {
|
|
30
|
-
/** When set, the producer uses idempotent + exactly-once semantics. */
|
|
31
|
-
transactionalId?: string;
|
|
32
|
-
/** Enable idempotent writes (required for `transactionalId`). */
|
|
33
|
-
idempotent?: boolean;
|
|
34
|
-
};
|
|
35
|
-
/** An open Kafka transaction. */
|
|
36
|
-
interface ITransaction {
|
|
37
|
-
send(record: IProducerRecord): Promise<void>;
|
|
38
|
-
/**
|
|
39
|
-
* Atomically commit offsets for `consumer` as part of this transaction.
|
|
40
|
-
* The `consumer` parameter must be the `IConsumer` whose offsets are being committed.
|
|
41
|
-
*/
|
|
42
|
-
sendOffsets(options: {
|
|
43
|
-
consumer: IConsumer;
|
|
44
|
-
topics: Array<{
|
|
45
|
-
topic: string;
|
|
46
|
-
partitions: Array<{
|
|
47
|
-
partition: number;
|
|
48
|
-
offset: string;
|
|
49
|
-
}>;
|
|
50
|
-
}>;
|
|
51
|
-
}): Promise<void>;
|
|
52
|
-
commit(): Promise<void>;
|
|
53
|
-
abort(): Promise<void>;
|
|
54
|
-
}
|
|
55
|
-
/** A Kafka producer. */
|
|
56
|
-
interface IProducer {
|
|
57
|
-
connect(): Promise<void>;
|
|
58
|
-
disconnect(): Promise<void>;
|
|
59
|
-
send(record: IProducerRecord): Promise<void>;
|
|
60
|
-
transaction(): Promise<ITransaction>;
|
|
61
|
-
}
|
|
62
|
-
/** A single message in an `eachMessage` callback. */
|
|
63
|
-
type IMessage = {
|
|
64
|
-
value: Buffer | null;
|
|
65
|
-
/** Header map as returned by librdkafka — values may be arrays. */
|
|
66
|
-
headers: Record<string, any>;
|
|
67
|
-
offset: string;
|
|
68
|
-
key?: Buffer | null;
|
|
69
|
-
};
|
|
70
|
-
/** Payload passed to the `eachMessage` handler. */
|
|
71
|
-
type IEachMessagePayload = {
|
|
72
|
-
topic: string;
|
|
73
|
-
partition: number;
|
|
74
|
-
message: IMessage;
|
|
75
|
-
};
|
|
76
|
-
/** A batch of messages from one topic-partition. */
|
|
77
|
-
type IMessageBatch = {
|
|
78
|
-
topic: string;
|
|
79
|
-
partition: number;
|
|
80
|
-
messages: IMessage[];
|
|
81
|
-
highWatermark?: string;
|
|
82
|
-
};
|
|
83
|
-
/** Payload passed to the `eachBatch` handler. */
|
|
84
|
-
type IEachBatchPayload = {
|
|
85
|
-
batch: IMessageBatch;
|
|
86
|
-
/** Send a heartbeat to the broker to prevent session timeout. */
|
|
87
|
-
heartbeat: () => Promise<void>;
|
|
88
|
-
/** Mark `offset` as processed (without committing). */
|
|
89
|
-
resolveOffset: (offset: string) => void;
|
|
90
|
-
/** Commit if the auto-commit threshold has been reached. */
|
|
91
|
-
commitOffsetsIfNecessary: () => Promise<void>;
|
|
92
|
-
};
|
|
93
|
-
/** Configuration passed to `IConsumer.run()`. */
|
|
94
|
-
type IConsumerRunConfig = {
|
|
95
|
-
eachMessage?: (payload: IEachMessagePayload) => Promise<void>;
|
|
96
|
-
eachBatch?: (payload: IEachBatchPayload) => Promise<void>;
|
|
97
|
-
};
|
|
98
|
-
/** Options for creating a consumer. */
|
|
99
|
-
type IConsumerCreationOptions = {
|
|
100
|
-
groupId: string;
|
|
101
|
-
fromBeginning?: boolean;
|
|
102
|
-
autoCommit?: boolean;
|
|
103
|
-
partitionAssigner?: "cooperative-sticky" | "roundrobin" | "range";
|
|
104
|
-
/** Fired on every partition assign/revoke. */
|
|
105
|
-
onRebalance?: (type: "assign" | "revoke", assignments: ITopicPartition[]) => void;
|
|
106
|
-
};
|
|
107
|
-
/** A Kafka consumer. */
|
|
108
|
-
interface IConsumer {
|
|
109
|
-
connect(): Promise<void>;
|
|
110
|
-
disconnect(): Promise<void>;
|
|
111
|
-
subscribe(options: {
|
|
112
|
-
topics: (string | RegExp)[];
|
|
113
|
-
}): Promise<void>;
|
|
114
|
-
run(config: IConsumerRunConfig): Promise<void>;
|
|
115
|
-
pause(assignments: ITopicPartitions[]): void;
|
|
116
|
-
resume(assignments: ITopicPartitions[]): void;
|
|
117
|
-
/** Seek a partition to an explicit offset. */
|
|
118
|
-
seek(options: ITopicPartitionOffset): void;
|
|
119
|
-
/** Current partition assignment for this consumer. */
|
|
120
|
-
assignment(): ITopicPartition[];
|
|
121
|
-
commitOffsets(offsets: ITopicPartitionOffset[]): Promise<void>;
|
|
122
|
-
/** Stop processing (alias for disconnect in some usages). */
|
|
123
|
-
stop(): Promise<void>;
|
|
124
|
-
}
|
|
125
|
-
/** Low/current/high watermark offsets for one partition. */
|
|
126
|
-
type IPartitionWatermarks = {
|
|
127
|
-
partition: number;
|
|
128
|
-
low: string;
|
|
129
|
-
high: string;
|
|
130
|
-
};
|
|
131
|
-
/** A partition → offset pair. */
|
|
132
|
-
type IPartitionOffset = {
|
|
133
|
-
partition: number;
|
|
134
|
-
offset: string;
|
|
135
|
-
};
|
|
136
|
-
/** Committed offsets for a group's topic. */
|
|
137
|
-
type IGroupTopicOffsets = {
|
|
138
|
-
topic: string;
|
|
139
|
-
partitions: IPartitionOffset[];
|
|
140
|
-
};
|
|
141
|
-
/** A consumer group descriptor. */
|
|
142
|
-
type IGroupDescription = {
|
|
143
|
-
groupId: string;
|
|
144
|
-
state?: string;
|
|
145
|
-
};
|
|
146
|
-
/** Partition metadata. */
|
|
147
|
-
type IPartitionMetadata = {
|
|
148
|
-
partitionId?: number;
|
|
149
|
-
partition?: number;
|
|
150
|
-
leader?: number;
|
|
151
|
-
replicas?: (number | {
|
|
152
|
-
nodeId: number;
|
|
153
|
-
})[];
|
|
154
|
-
isr?: (number | {
|
|
155
|
-
nodeId: number;
|
|
156
|
-
})[];
|
|
157
|
-
};
|
|
158
|
-
/** Topic metadata. */
|
|
159
|
-
type ITopicMetadata = {
|
|
160
|
-
name: string;
|
|
161
|
-
partitions: IPartitionMetadata[];
|
|
162
|
-
};
|
|
163
|
-
/** A Kafka admin client. */
|
|
164
|
-
interface IAdmin {
|
|
165
|
-
connect(): Promise<void>;
|
|
166
|
-
disconnect(): Promise<void>;
|
|
167
|
-
createTopics(options: {
|
|
168
|
-
topics: Array<{
|
|
169
|
-
topic: string;
|
|
170
|
-
numPartitions: number;
|
|
171
|
-
}>;
|
|
172
|
-
}): Promise<void>;
|
|
173
|
-
fetchTopicOffsets(topic: string): Promise<IPartitionWatermarks[]>;
|
|
174
|
-
fetchTopicOffsetsByTimestamp(topic: string, timestamp: number): Promise<IPartitionOffset[]>;
|
|
175
|
-
fetchOffsets(options: {
|
|
176
|
-
groupId: string;
|
|
177
|
-
}): Promise<IGroupTopicOffsets[]>;
|
|
178
|
-
setOffsets(options: {
|
|
179
|
-
groupId: string;
|
|
180
|
-
topic: string;
|
|
181
|
-
partitions: IPartitionOffset[];
|
|
182
|
-
}): Promise<void>;
|
|
183
|
-
listTopics(): Promise<string[]>;
|
|
184
|
-
listGroups(): Promise<{
|
|
185
|
-
groups: IGroupDescription[];
|
|
186
|
-
}>;
|
|
187
|
-
fetchTopicMetadata(options?: {
|
|
188
|
-
topics?: string[];
|
|
189
|
-
}): Promise<{
|
|
190
|
-
topics: ITopicMetadata[];
|
|
191
|
-
}>;
|
|
192
|
-
deleteGroups(groupIds: string[]): Promise<void>;
|
|
193
|
-
deleteTopicRecords(options: {
|
|
194
|
-
topic: string;
|
|
195
|
-
partitions: IPartitionOffset[];
|
|
196
|
-
}): Promise<void>;
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Factory that creates connected Kafka primitives.
|
|
200
|
-
* The default implementation wraps `@confluentinc/kafka-javascript` via
|
|
201
|
-
* `ConfluentTransport`. Inject a custom transport (e.g. a fake) via
|
|
202
|
-
* `KafkaClientOptions.transport` for testing or alternative broker support.
|
|
203
|
-
*/
|
|
204
|
-
interface KafkaTransport {
|
|
205
|
-
producer(options?: IProducerCreationOptions): IProducer;
|
|
206
|
-
consumer(options: IConsumerCreationOptions): IConsumer;
|
|
207
|
-
admin(): IAdmin;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Context passed as the second argument to `SchemaLike.parse()`.
|
|
212
|
-
* Enables schema-registry adapters, version-aware migration, and
|
|
213
|
-
* header-driven parsing without coupling validators to Kafka internals.
|
|
214
|
-
*
|
|
215
|
-
* All fields are optional-friendly — validators that don't need the context
|
|
216
|
-
* can simply ignore the second argument.
|
|
217
|
-
*/
|
|
218
|
-
interface SchemaParseContext {
|
|
219
|
-
/** Topic the message was produced to / consumed from. */
|
|
220
|
-
topic: string;
|
|
221
|
-
/** Decoded message headers (envelope headers included). */
|
|
222
|
-
headers: MessageHeaders;
|
|
223
|
-
/** Value of the `x-schema-version` header, defaults to `1`. */
|
|
224
|
-
version: number;
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Any validation library with a `.parse()` method.
|
|
228
|
-
* Works with Zod, Valibot, ArkType, or any custom validator.
|
|
229
|
-
*
|
|
230
|
-
* The optional `ctx` argument carries topic/header/version metadata so
|
|
231
|
-
* validators can perform schema-registry lookups or version-aware migrations.
|
|
232
|
-
* Existing validators that only use the first argument continue to work
|
|
233
|
-
* unchanged — the second argument is silently ignored.
|
|
234
|
-
*
|
|
235
|
-
* @example
|
|
236
|
-
* ```ts
|
|
237
|
-
* import { z } from 'zod';
|
|
238
|
-
* const schema: SchemaLike<{ id: string }> = z.object({ id: z.string() });
|
|
239
|
-
*
|
|
240
|
-
* // Context-aware validator:
|
|
241
|
-
* const schema: SchemaLike<MyType> = {
|
|
242
|
-
* parse(data, ctx) {
|
|
243
|
-
* const version = ctx?.version ?? 1;
|
|
244
|
-
* return version >= 2 ? migrateV1toV2(data) : validateV1(data);
|
|
245
|
-
* }
|
|
246
|
-
* };
|
|
247
|
-
* ```
|
|
248
|
-
*/
|
|
249
|
-
interface SchemaLike<T = any> {
|
|
250
|
-
parse(data: unknown, ctx?: SchemaParseContext): T | Promise<T>;
|
|
251
|
-
}
|
|
252
|
-
/** Infer the output type from a SchemaLike. */
|
|
253
|
-
type InferSchema<S extends SchemaLike> = S extends SchemaLike<infer T> ? T : never;
|
|
254
|
-
/**
|
|
255
|
-
* A typed topic descriptor that pairs a topic name with its message type.
|
|
256
|
-
* Created via the `topic()` factory function.
|
|
257
|
-
*
|
|
258
|
-
* @typeParam N - The literal topic name string.
|
|
259
|
-
* @typeParam M - The message payload type for this topic.
|
|
260
|
-
*/
|
|
261
|
-
interface TopicDescriptor<N extends string = string, M extends Record<string, any> = Record<string, any>> {
|
|
262
|
-
readonly __topic: N;
|
|
263
|
-
/** @internal Phantom type — never has a real value at runtime. */
|
|
264
|
-
readonly __type: M;
|
|
265
|
-
/** Runtime schema validator. Present only when created via `topic().schema()`. */
|
|
266
|
-
readonly __schema?: SchemaLike<M>;
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Define a typed topic descriptor.
|
|
270
|
-
*
|
|
271
|
-
* @example
|
|
272
|
-
* ```ts
|
|
273
|
-
* // Without schema — explicit type via .type<T>():
|
|
274
|
-
* const OrderCreated = topic('order.created').type<{ orderId: string; amount: number }>();
|
|
275
|
-
*
|
|
276
|
-
* // With schema — type inferred from schema:
|
|
277
|
-
* const OrderCreated = topic('order.created').schema(z.object({
|
|
278
|
-
* orderId: z.string(),
|
|
279
|
-
* amount: z.number(),
|
|
280
|
-
* }));
|
|
281
|
-
*
|
|
282
|
-
* // Use with KafkaClient:
|
|
283
|
-
* await kafka.sendMessage(OrderCreated, { orderId: '123', amount: 100 });
|
|
284
|
-
*
|
|
285
|
-
* // Use with @SubscribeTo:
|
|
286
|
-
* @SubscribeTo(OrderCreated)
|
|
287
|
-
* async handleOrder(msg) { ... }
|
|
288
|
-
* ```
|
|
289
|
-
*/
|
|
290
|
-
declare function topic<N extends string>(name: N): {
|
|
291
|
-
/** Provide an explicit message type without a runtime schema. */
|
|
292
|
-
type: <M extends Record<string, any>>() => TopicDescriptor<N, M>;
|
|
293
|
-
schema: <S extends SchemaLike<Record<string, any>>>(schema: S) => TopicDescriptor<N, InferSchema<S>>;
|
|
294
|
-
};
|
|
295
|
-
/**
|
|
296
|
-
* Build a topic-message map type from a union of TopicDescriptors.
|
|
297
|
-
*
|
|
298
|
-
* @example
|
|
299
|
-
* ```ts
|
|
300
|
-
* const OrderCreated = topic('order.created').type<{ orderId: string }>();
|
|
301
|
-
* const OrderCompleted = topic('order.completed').type<{ completedAt: string }>();
|
|
302
|
-
*
|
|
303
|
-
* type MyTopics = TopicsFrom<typeof OrderCreated | typeof OrderCompleted>;
|
|
304
|
-
* // { 'order.created': { orderId: string }; 'order.completed': { completedAt: string } }
|
|
305
|
-
* ```
|
|
306
|
-
*/
|
|
307
|
-
type TopicsFrom<D extends TopicDescriptor<any, any>> = {
|
|
308
|
-
[K in D as K["__topic"]]: K["__type"];
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
declare const HEADER_EVENT_ID = "x-event-id";
|
|
312
|
-
declare const HEADER_CORRELATION_ID = "x-correlation-id";
|
|
313
|
-
declare const HEADER_TIMESTAMP = "x-timestamp";
|
|
314
|
-
declare const HEADER_SCHEMA_VERSION = "x-schema-version";
|
|
315
|
-
declare const HEADER_TRACEPARENT = "traceparent";
|
|
316
|
-
/** Monotonically increasing logical clock stamped by the producer for deduplication. */
|
|
317
|
-
declare const HEADER_LAMPORT_CLOCK = "x-lamport-clock";
|
|
318
|
-
/**
|
|
319
|
-
* Typed wrapper combining a parsed message payload with Kafka metadata
|
|
320
|
-
* and envelope headers.
|
|
321
|
-
*
|
|
322
|
-
* On **send**, the library auto-generates envelope headers
|
|
323
|
-
* (`x-event-id`, `x-correlation-id`, `x-timestamp`, `x-schema-version`).
|
|
324
|
-
*
|
|
325
|
-
* On **consume**, the library extracts those headers and assembles
|
|
326
|
-
* an `EventEnvelope` that is passed to the handler.
|
|
327
|
-
*
|
|
328
|
-
* @example
|
|
329
|
-
* ```ts
|
|
330
|
-
* await kafka.startConsumer(['orders'], async (envelope: EventEnvelope<Order>) => {
|
|
331
|
-
* console.log(envelope.payload.orderId); // typed payload
|
|
332
|
-
* console.log(envelope.correlationId); // auto-propagated
|
|
333
|
-
* console.log(envelope.eventId); // unique message ID
|
|
334
|
-
* });
|
|
335
|
-
* ```
|
|
336
|
-
*/
|
|
337
|
-
interface EventEnvelope<T> {
|
|
338
|
-
/** Deserialized + validated message body. */
|
|
339
|
-
payload: T;
|
|
340
|
-
/** Topic the message was produced to / consumed from. */
|
|
341
|
-
topic: string;
|
|
342
|
-
/** Kafka partition (consume-side only, `-1` on send). */
|
|
343
|
-
partition: number;
|
|
344
|
-
/** Kafka offset (consume-side only, empty string on send). */
|
|
345
|
-
offset: string;
|
|
346
|
-
/** ISO-8601 timestamp set by the producer. */
|
|
347
|
-
timestamp: string;
|
|
348
|
-
/** Unique ID for this event (UUID v4). */
|
|
349
|
-
eventId: string;
|
|
350
|
-
/** Correlation ID — auto-propagated via AsyncLocalStorage. */
|
|
351
|
-
correlationId: string;
|
|
352
|
-
/** Schema version of the payload. */
|
|
353
|
-
schemaVersion: number;
|
|
354
|
-
/** W3C Trace Context `traceparent` header (set by OTel instrumentation). */
|
|
355
|
-
traceparent?: string;
|
|
356
|
-
/** All decoded Kafka headers for extensibility. */
|
|
357
|
-
headers: MessageHeaders;
|
|
358
|
-
}
|
|
359
|
-
interface EnvelopeCtx {
|
|
360
|
-
correlationId: string;
|
|
361
|
-
traceparent?: string;
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Read the current envelope context (correlationId / traceparent) from ALS.
|
|
365
|
-
* Returns `undefined` outside of a Kafka consumer handler.
|
|
366
|
-
* @example
|
|
367
|
-
* ```ts
|
|
368
|
-
* const ctx = getEnvelopeContext();
|
|
369
|
-
* if (ctx) console.log('correlationId:', ctx.correlationId);
|
|
370
|
-
* ```
|
|
371
|
-
*/
|
|
372
|
-
declare function getEnvelopeContext(): EnvelopeCtx | undefined;
|
|
373
|
-
/**
|
|
374
|
-
* Execute `fn` inside an envelope context so nested sends inherit correlationId.
|
|
375
|
-
* Automatically called by the consumer pipeline — use this in tests or manual flows.
|
|
376
|
-
* @example
|
|
377
|
-
* ```ts
|
|
378
|
-
* await runWithEnvelopeContext({ correlationId: 'abc-123' }, async () => {
|
|
379
|
-
* await kafka.sendMessage('orders.created', payload); // inherits correlationId
|
|
380
|
-
* });
|
|
381
|
-
* ```
|
|
382
|
-
*/
|
|
383
|
-
declare function runWithEnvelopeContext<R>(ctx: EnvelopeCtx, fn: () => R): R;
|
|
384
|
-
/** Options accepted by `buildEnvelopeHeaders`. */
|
|
385
|
-
interface EnvelopeHeaderOptions {
|
|
386
|
-
correlationId?: string;
|
|
387
|
-
schemaVersion?: number;
|
|
388
|
-
eventId?: string;
|
|
389
|
-
headers?: MessageHeaders;
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Generate envelope headers for the send path.
|
|
393
|
-
*
|
|
394
|
-
* Priority for `correlationId`:
|
|
395
|
-
* explicit option → ALS context → new UUID.
|
|
396
|
-
*/
|
|
397
|
-
declare function buildEnvelopeHeaders(options?: EnvelopeHeaderOptions): MessageHeaders;
|
|
398
|
-
/**
|
|
399
|
-
* Decode kafkajs headers (`Record<string, Buffer | string | undefined>`)
|
|
400
|
-
* into plain `Record<string, string>`.
|
|
401
|
-
*/
|
|
402
|
-
declare function decodeHeaders(raw: Record<string, Buffer | string | (Buffer | string)[] | undefined> | undefined): MessageHeaders;
|
|
403
|
-
/**
|
|
404
|
-
* Build an `EventEnvelope` from a consumed kafkajs message.
|
|
405
|
-
* Tolerates missing envelope headers — generates defaults so messages
|
|
406
|
-
* from non-envelope producers still work.
|
|
407
|
-
*/
|
|
408
|
-
declare function extractEnvelope<T>(payload: T, headers: MessageHeaders, topic: string, partition: number, offset: string): EventEnvelope<T>;
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Mapping of topic names to their message types.
|
|
412
|
-
* Define this interface to get type-safe publish/subscribe across your app.
|
|
413
|
-
*
|
|
414
|
-
* @example
|
|
415
|
-
* ```ts
|
|
416
|
-
* // with explicit extends (IDE hints for values)
|
|
417
|
-
* interface MyTopics extends TTopicMessageMap {
|
|
418
|
-
* "orders.created": { orderId: string; amount: number };
|
|
419
|
-
* "users.updated": { userId: string; name: string };
|
|
420
|
-
* }
|
|
421
|
-
*
|
|
422
|
-
* // or plain interface / type — works the same
|
|
423
|
-
* interface MyTopics {
|
|
424
|
-
* "orders.created": { orderId: string; amount: number };
|
|
425
|
-
* }
|
|
426
|
-
* ```
|
|
427
|
-
*/
|
|
428
|
-
type TTopicMessageMap = {
|
|
429
|
-
[topic: string]: Record<string, any>;
|
|
430
|
-
};
|
|
431
|
-
/**
|
|
432
|
-
* Generic constraint for topic-message maps.
|
|
433
|
-
* Works with both `type` aliases and `interface` declarations.
|
|
434
|
-
*/
|
|
435
|
-
type TopicMapConstraint<T> = {
|
|
436
|
-
[K in keyof T]: Record<string, any>;
|
|
437
|
-
};
|
|
438
|
-
type ClientId = string;
|
|
439
|
-
type GroupId = string;
|
|
440
|
-
type MessageHeaders = Record<string, string>;
|
|
441
|
-
/**
|
|
442
|
-
* Message compression codec.
|
|
443
|
-
* Maps directly to the underlying librdkafka codec values.
|
|
444
|
-
* - `'none'` — no compression (default)
|
|
445
|
-
* - `'gzip'` — widely supported, moderate compression ratio
|
|
446
|
-
* - `'snappy'` — fast, moderate compression ratio
|
|
447
|
-
* - `'lz4'` — fastest, slightly lower ratio
|
|
448
|
-
* - `'zstd'` — best ratio, slightly slower
|
|
449
|
-
*/
|
|
450
|
-
type CompressionType = "none" | "gzip" | "snappy" | "lz4" | "zstd";
|
|
451
|
-
/**
|
|
452
|
-
* Options for sending a single message.
|
|
453
|
-
*
|
|
454
|
-
* @example
|
|
455
|
-
* ```ts
|
|
456
|
-
* await kafka.sendMessage('orders.created', { orderId: '123', amount: 99 }, {
|
|
457
|
-
* key: 'order-123',
|
|
458
|
-
* headers: { 'x-source': 'checkout-service' },
|
|
459
|
-
* compression: 'snappy',
|
|
460
|
-
* });
|
|
461
|
-
* ```
|
|
462
|
-
*/
|
|
463
|
-
interface SendOptions {
|
|
464
|
-
/** Partition key for message routing. */
|
|
465
|
-
key?: string;
|
|
466
|
-
/** Custom headers attached to the message (merged with auto-generated envelope headers). */
|
|
467
|
-
headers?: MessageHeaders;
|
|
468
|
-
/** Override the auto-propagated correlation ID (default: inherited from ALS context or new UUID). */
|
|
469
|
-
correlationId?: string;
|
|
470
|
-
/** Schema version for the payload. Default: `1`. */
|
|
471
|
-
schemaVersion?: number;
|
|
472
|
-
/** Override the auto-generated event ID (UUID v4). */
|
|
473
|
-
eventId?: string;
|
|
474
|
-
/**
|
|
475
|
-
* Compression codec for this message.
|
|
476
|
-
* Applied at the producer record level — all messages in a single `send` call share the same codec.
|
|
477
|
-
* Default: `'none'`.
|
|
478
|
-
*/
|
|
479
|
-
compression?: CompressionType;
|
|
480
|
-
}
|
|
481
|
-
/**
|
|
482
|
-
* Shape of each item in a `sendBatch` call.
|
|
483
|
-
*
|
|
484
|
-
* @example
|
|
485
|
-
* ```ts
|
|
486
|
-
* await kafka.sendBatch('orders.created', [
|
|
487
|
-
* { value: { orderId: '1', amount: 10 }, key: 'order-1' },
|
|
488
|
-
* { value: { orderId: '2', amount: 20 }, key: 'order-2', headers: { 'x-priority': 'high' } },
|
|
489
|
-
* ]);
|
|
490
|
-
* ```
|
|
491
|
-
*/
|
|
492
|
-
interface BatchMessageItem<V> {
|
|
493
|
-
value: V;
|
|
494
|
-
/**
|
|
495
|
-
* Kafka partition key for this message.
|
|
496
|
-
* Kafka hashes the key to deterministically route the message to a partition.
|
|
497
|
-
* Messages with the same key always land on the same partition — use this to
|
|
498
|
-
* guarantee ordering per entity (e.g. `userId`, `orderId`).
|
|
499
|
-
*/
|
|
500
|
-
key?: string;
|
|
501
|
-
headers?: MessageHeaders;
|
|
502
|
-
correlationId?: string;
|
|
503
|
-
schemaVersion?: number;
|
|
504
|
-
eventId?: string;
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* Options for a `sendBatch` call (applies to all messages in the batch).
|
|
508
|
-
*
|
|
509
|
-
* @example
|
|
510
|
-
* ```ts
|
|
511
|
-
* await kafka.sendBatch('metrics', messages, { compression: 'zstd' });
|
|
512
|
-
* ```
|
|
513
|
-
*/
|
|
514
|
-
interface BatchSendOptions {
|
|
515
|
-
/**
|
|
516
|
-
* Compression codec for this batch.
|
|
517
|
-
* Applied at the producer record level — all messages in the batch share the same codec.
|
|
518
|
-
* Default: `'none'`.
|
|
519
|
-
*/
|
|
520
|
-
compression?: CompressionType;
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* Metadata exposed to batch consumer handlers.
|
|
524
|
-
*
|
|
525
|
-
* @example
|
|
526
|
-
* ```ts
|
|
527
|
-
* await kafka.startBatchConsumer(['events'], async (envelopes, meta) => {
|
|
528
|
-
* console.log(`Partition ${meta.partition}, HWM ${meta.highWatermark}`);
|
|
529
|
-
* await db.insertMany(envelopes.map(e => e.payload));
|
|
530
|
-
* meta.resolveOffset(envelopes.at(-1)!.offset);
|
|
531
|
-
* await meta.commitOffsetsIfNecessary();
|
|
532
|
-
* }, { autoCommit: false });
|
|
533
|
-
* ```
|
|
534
|
-
*/
|
|
535
|
-
interface BatchMeta {
|
|
536
|
-
/** Partition number for this batch. */
|
|
537
|
-
partition: number;
|
|
538
|
-
/**
|
|
539
|
-
* Highest offset available on the broker for this partition.
|
|
540
|
-
* `null` when the message is being replayed via a retry topic consumer —
|
|
541
|
-
* in that path the broker high-watermark is not accessible without an admin
|
|
542
|
-
* call. Do not use this for lag calculation in the retry path.
|
|
543
|
-
*/
|
|
544
|
-
highWatermark: string | null;
|
|
545
|
-
/** Send a heartbeat to the broker to prevent session timeout. */
|
|
546
|
-
heartbeat(): Promise<void>;
|
|
547
|
-
/** Mark an offset as processed (for manual offset management). */
|
|
548
|
-
resolveOffset(offset: string): void;
|
|
549
|
-
/** Commit offsets if the auto-commit threshold has been reached. */
|
|
550
|
-
commitOffsetsIfNecessary(): Promise<void>;
|
|
551
|
-
}
|
|
552
|
-
/**
|
|
553
|
-
* Options for Lamport Clock-based message deduplication.
|
|
554
|
-
*
|
|
555
|
-
* @example
|
|
556
|
-
* ```ts
|
|
557
|
-
* await kafka.startConsumer(['payments'], handler, {
|
|
558
|
-
* deduplication: { strategy: 'dlq' },
|
|
559
|
-
* dlq: true,
|
|
560
|
-
* });
|
|
561
|
-
* ```
|
|
562
|
-
*
|
|
563
|
-
* The producer stamps every outgoing message with a monotonically increasing
|
|
564
|
-
* `x-lamport-clock` header. The consumer tracks the last processed value per
|
|
565
|
-
* `topic:partition` and skips any message whose clock is not strictly greater
|
|
566
|
-
* than that value.
|
|
567
|
-
*
|
|
568
|
-
* Messages that arrive without the `x-lamport-clock` header are passed through
|
|
569
|
-
* unchanged (backwards-compatible with producers that don't use this library).
|
|
570
|
-
*
|
|
571
|
-
* **In-session limitation**: deduplication state lives in memory and is reset
|
|
572
|
-
* whenever the process restarts or `stopConsumer` is called. After a restart,
|
|
573
|
-
* previously processed messages with `clock ≤ N` will be re-processed until
|
|
574
|
-
* their offsets catch up to the high-watermark again. The same applies after a
|
|
575
|
-
* rebalance: the instance that receives the partition begins with empty state.
|
|
576
|
-
* This is a fundamental limitation of the in-memory Lamport clock approach —
|
|
577
|
-
* it provides deduplication only within a single process session.
|
|
578
|
-
*/
|
|
579
|
-
interface DeduplicationOptions {
|
|
580
|
-
/**
|
|
581
|
-
* What to do with detected duplicate messages:
|
|
582
|
-
* - `'drop'` — silently discard. No routing, no callback. **(default)**
|
|
583
|
-
* - `'dlq'` — forward to `<topic>.dlq` with reason metadata headers.
|
|
584
|
-
* Requires `dlq: true` on the consumer options.
|
|
585
|
-
* - `'topic'` — forward to `<topic>.duplicates` (or `duplicatesTopic` if set).
|
|
586
|
-
*/
|
|
587
|
-
strategy?: "dlq" | "topic" | "drop";
|
|
588
|
-
/**
|
|
589
|
-
* Custom destination topic for `strategy: 'topic'`.
|
|
590
|
-
* Defaults to `<consumedTopic>.duplicates`.
|
|
591
|
-
*/
|
|
592
|
-
duplicatesTopic?: string;
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* Options for the per-partition circuit breaker.
|
|
596
|
-
*
|
|
597
|
-
* @example
|
|
598
|
-
* ```ts
|
|
599
|
-
* await kafka.startConsumer(['payments'], handler, {
|
|
600
|
-
* circuitBreaker: {
|
|
601
|
-
* threshold: 5,
|
|
602
|
-
* recoveryMs: 30_000,
|
|
603
|
-
* windowSize: 20,
|
|
604
|
-
* halfOpenSuccesses: 2,
|
|
605
|
-
* },
|
|
606
|
-
* dlq: true,
|
|
607
|
-
* });
|
|
608
|
-
* ```
|
|
609
|
-
*
|
|
610
|
-
* The circuit breaker tracks recent message outcomes in a **sliding window** and
|
|
611
|
-
* opens (pauses the partition) when too many failures accumulate:
|
|
612
|
-
*
|
|
613
|
-
* - **CLOSED** — normal operation. Each DLQ route or successful delivery is recorded
|
|
614
|
-
* in the window. When `failuresInWindow >= threshold` the circuit opens.
|
|
615
|
-
* - **OPEN** — the partition is paused. After `recoveryMs` the circuit moves to HALF-OPEN.
|
|
616
|
-
* - **HALF-OPEN** — the partition is resumed. If the next `halfOpenSuccesses` messages
|
|
617
|
-
* succeed the circuit closes; a new failure immediately re-opens it.
|
|
618
|
-
*/
|
|
619
|
-
interface CircuitBreakerOptions {
|
|
620
|
-
/**
|
|
621
|
-
* Number of failures within the sliding window required to open the circuit.
|
|
622
|
-
* A failure is any message that ends up in the DLQ.
|
|
623
|
-
* Default: `5`.
|
|
624
|
-
*/
|
|
625
|
-
threshold?: number;
|
|
626
|
-
/**
|
|
627
|
-
* Time (ms) to keep the circuit OPEN before attempting recovery (HALF_OPEN).
|
|
628
|
-
* Default: `30_000` (30 s).
|
|
629
|
-
*/
|
|
630
|
-
recoveryMs?: number;
|
|
631
|
-
/**
|
|
632
|
-
* Number of outcomes (successes + failures) to keep in the sliding window.
|
|
633
|
-
* Default: `threshold * 2` (minimum `10`).
|
|
634
|
-
*/
|
|
635
|
-
windowSize?: number;
|
|
636
|
-
/**
|
|
637
|
-
* Number of consecutive successes in HALF-OPEN state required to close the
|
|
638
|
-
* circuit. Default: `1`.
|
|
639
|
-
*/
|
|
640
|
-
halfOpenSuccesses?: number;
|
|
641
|
-
}
|
|
642
|
-
/**
|
|
643
|
-
* Options for configuring a Kafka consumer.
|
|
644
|
-
*
|
|
645
|
-
* @example
|
|
646
|
-
* ```ts
|
|
647
|
-
* await kafka.startConsumer(['orders.created'], handler, {
|
|
648
|
-
* groupId: 'billing-service',
|
|
649
|
-
* retry: { maxRetries: 5, backoffMs: 1000 },
|
|
650
|
-
* dlq: true,
|
|
651
|
-
* deduplication: { strategy: 'drop' },
|
|
652
|
-
* circuitBreaker: { threshold: 3, recoveryMs: 60_000 },
|
|
653
|
-
* interceptors: [loggingInterceptor],
|
|
654
|
-
* });
|
|
655
|
-
* ```
|
|
656
|
-
*/
|
|
657
|
-
interface ConsumerOptions<T extends TopicMapConstraint<T> = TTopicMessageMap> {
|
|
658
|
-
/** Override the default consumer group ID from the constructor. */
|
|
659
|
-
groupId?: string;
|
|
660
|
-
/** Start reading from earliest offset. Default: `false`. */
|
|
661
|
-
fromBeginning?: boolean;
|
|
662
|
-
/** Automatically commit offsets. Default: `true`. */
|
|
663
|
-
autoCommit?: boolean;
|
|
664
|
-
/** Retry policy for failed message processing. */
|
|
665
|
-
retry?: RetryOptions;
|
|
666
|
-
/** Send failed messages to a Dead Letter Queue (`<topic>.dlq`). */
|
|
667
|
-
dlq?: boolean;
|
|
668
|
-
/** Interceptors called before/after each message. */
|
|
669
|
-
interceptors?: ConsumerInterceptor<T>[];
|
|
670
|
-
/** @internal Schema map populated by @SubscribeTo when descriptors have schemas. */
|
|
671
|
-
schemas?: Map<string, SchemaLike>;
|
|
672
|
-
/** Retry config for `consumer.subscribe()` when the topic doesn't exist yet. */
|
|
673
|
-
subscribeRetry?: SubscribeRetryOptions;
|
|
674
|
-
/**
|
|
675
|
-
* Route failed messages through a Kafka retry topic (`<topic>.retry`) instead of sleeping
|
|
676
|
-
* in-process between retry attempts. Requires `retry` to be set.
|
|
677
|
-
*
|
|
678
|
-
* Benefits over in-process retry:
|
|
679
|
-
* - Retry messages survive consumer restarts (durable)
|
|
680
|
-
* - Original consumer is not blocked during retry delay
|
|
681
|
-
*
|
|
682
|
-
* A companion consumer on `<topic>.retry` is auto-started with group `<groupId>-retry`.
|
|
683
|
-
* On exhaustion, messages go to `<topic>.dlq` (if `dlq: true`) or `onMessageLost`.
|
|
684
|
-
*/
|
|
685
|
-
retryTopics?: boolean;
|
|
686
|
-
/**
|
|
687
|
-
* Timeout (ms) for waiting until each retry level consumer receives partition
|
|
688
|
-
* assignments after `startConsumer` connects. Default: `10000`.
|
|
689
|
-
* Increase this when the broker is slow to rebalance.
|
|
690
|
-
*/
|
|
691
|
-
retryTopicAssignmentTimeoutMs?: number;
|
|
692
|
-
/**
|
|
693
|
-
* Log a warning if the message handler has not resolved within this window (ms).
|
|
694
|
-
* The handler is not cancelled — this is a diagnostic aid to surface stuck handlers
|
|
695
|
-
* before they starve a partition.
|
|
696
|
-
*/
|
|
697
|
-
handlerTimeoutMs?: number;
|
|
698
|
-
/**
|
|
699
|
-
* Enable Lamport Clock deduplication.
|
|
700
|
-
* Requires the producer to stamp messages with `x-lamport-clock` headers
|
|
701
|
-
* (done automatically when using this library's send methods).
|
|
702
|
-
* Messages without the header are passed through unchanged.
|
|
703
|
-
*/
|
|
704
|
-
deduplication?: DeduplicationOptions;
|
|
705
|
-
/**
|
|
706
|
-
* Drop messages older than this threshold, measured in milliseconds from
|
|
707
|
-
* the `x-timestamp` header set by the producer.
|
|
708
|
-
*
|
|
709
|
-
* Expired messages are routed to `{topic}.dlq` when `dlq: true`, otherwise
|
|
710
|
-
* `onTtlExpired` is called. The handler is never invoked for an expired message.
|
|
711
|
-
*/
|
|
712
|
-
messageTtlMs?: number;
|
|
713
|
-
/**
|
|
714
|
-
* Automatically pause a partition when it accumulates too many consecutive
|
|
715
|
-
* failures, then resume after a recovery window.
|
|
716
|
-
*
|
|
717
|
-
* See `CircuitBreakerOptions` for the sliding-window semantics.
|
|
718
|
-
*/
|
|
719
|
-
circuitBreaker?: CircuitBreakerOptions;
|
|
720
|
-
/**
|
|
721
|
-
* Max number of messages buffered in the `consume()` iterator queue before
|
|
722
|
-
* the partition is paused. The partition resumes when the queue drains below 50%.
|
|
723
|
-
* Only applies to `consume()`. Default: unbounded.
|
|
724
|
-
*/
|
|
725
|
-
queueHighWaterMark?: number;
|
|
726
|
-
/**
|
|
727
|
-
* Kafka partition assignment strategy for this consumer group.
|
|
728
|
-
* - `'cooperative-sticky'` — **(default)** partitions move as little as possible on rebalance.
|
|
729
|
-
* Best for horizontal scaling: only the partitions that need to be reassigned are moved.
|
|
730
|
-
* - `'roundrobin'` — distributes partitions as evenly as possible across consumers.
|
|
731
|
-
* - `'range'` — assigns contiguous partition ranges; can cause uneven distribution with multiple topics.
|
|
732
|
-
*/
|
|
733
|
-
partitionAssigner?: "roundrobin" | "range" | "cooperative-sticky";
|
|
734
|
-
/**
|
|
735
|
-
* Called when a message is dropped due to TTL expiration (`messageTtlMs`).
|
|
736
|
-
* Fires instead of `onMessageLost` for expired messages when `dlq` is not enabled.
|
|
737
|
-
* When `dlq: true`, expired messages go to the DLQ and this callback is NOT called.
|
|
738
|
-
*
|
|
739
|
-
* **Per-consumer override**: takes precedence over `KafkaClientOptions.onTtlExpired`
|
|
740
|
-
* when both are set. Use this to handle TTL expiry differently per consumer group.
|
|
741
|
-
*/
|
|
742
|
-
onTtlExpired?: (ctx: TtlExpiredContext) => void | Promise<void>;
|
|
743
|
-
/**
|
|
744
|
-
* Called when a message is silently dropped after all retries are exhausted
|
|
745
|
-
* and `dlq` is not enabled.
|
|
746
|
-
*
|
|
747
|
-
* **Per-consumer override**: takes precedence over `KafkaClientOptions.onMessageLost`
|
|
748
|
-
* when both are set. Use this for consumer-specific alerting or dead-message logging.
|
|
749
|
-
*/
|
|
750
|
-
onMessageLost?: (ctx: MessageLostContext) => void | Promise<void>;
|
|
751
|
-
/**
|
|
752
|
-
* Called before each retry attempt (both in-process and retry-topic paths).
|
|
753
|
-
*
|
|
754
|
-
* **Per-consumer override**: fires in addition to any global instrumentation hooks.
|
|
755
|
-
* Use this for per-consumer retry metrics or structured logging.
|
|
756
|
-
*/
|
|
757
|
-
onRetry?: (envelope: EventEnvelope<any>, attempt: number, maxRetries: number) => void | Promise<void>;
|
|
758
|
-
}
|
|
759
|
-
/**
|
|
760
|
-
* Configuration for consumer retry behavior.
|
|
761
|
-
*
|
|
762
|
-
* @example
|
|
763
|
-
* ```ts
|
|
764
|
-
* await kafka.startConsumer(['orders'], handler, {
|
|
765
|
-
* retry: { maxRetries: 5, backoffMs: 500, maxBackoffMs: 10_000 },
|
|
766
|
-
* });
|
|
767
|
-
* ```
|
|
768
|
-
*/
|
|
769
|
-
interface RetryOptions {
|
|
770
|
-
/** Maximum number of retry attempts before giving up. */
|
|
771
|
-
maxRetries: number;
|
|
772
|
-
/** Base delay for exponential backoff in ms. Default: `1000`. */
|
|
773
|
-
backoffMs?: number;
|
|
774
|
-
/** Maximum delay cap for exponential backoff in ms. Default: `30000`. */
|
|
775
|
-
maxBackoffMs?: number;
|
|
776
|
-
}
|
|
777
|
-
/**
|
|
778
|
-
* Interceptor hooks for consumer message processing.
|
|
779
|
-
* All methods are optional — implement only what you need.
|
|
780
|
-
*
|
|
781
|
-
* Interceptors are per-consumer. For client-wide hooks (e.g. OTel),
|
|
782
|
-
* use `KafkaInstrumentation` instead.
|
|
783
|
-
*
|
|
784
|
-
* @example
|
|
785
|
-
* ```ts
|
|
786
|
-
* const logger: ConsumerInterceptor = {
|
|
787
|
-
* async before(envelope) { console.log('Processing', envelope.eventId); },
|
|
788
|
-
* async after(envelope) { console.log('Done', envelope.eventId); },
|
|
789
|
-
* async onError(envelope, err) { console.error('Failed', err.message); },
|
|
790
|
-
* };
|
|
791
|
-
*
|
|
792
|
-
* await kafka.startConsumer(['orders'], handler, { interceptors: [logger] });
|
|
793
|
-
* ```
|
|
794
|
-
*/
|
|
795
|
-
interface ConsumerInterceptor<T extends TopicMapConstraint<T> = TTopicMessageMap> {
|
|
796
|
-
/** Called before the message handler. */
|
|
797
|
-
before?(envelope: EventEnvelope<T[keyof T]>): Promise<void> | void;
|
|
798
|
-
/** Called after the message handler succeeds. */
|
|
799
|
-
after?(envelope: EventEnvelope<T[keyof T]>): Promise<void> | void;
|
|
800
|
-
/** Called when the message handler throws. */
|
|
801
|
-
onError?(envelope: EventEnvelope<T[keyof T]>, error: Error): Promise<void> | void;
|
|
802
|
-
}
|
|
803
|
-
/**
|
|
804
|
-
* Return value of `KafkaInstrumentation.beforeConsume`.
|
|
805
|
-
*
|
|
806
|
-
* - `() => void` — legacy form: a cleanup function called after the handler.
|
|
807
|
-
* - Object form:
|
|
808
|
-
* - `cleanup?()` — called after the handler (same as the legacy function form).
|
|
809
|
-
* - `wrap?(fn)` — wraps the handler execution; call `fn()` inside the desired
|
|
810
|
-
* async context (e.g. `context.with(spanCtx, fn)` for OpenTelemetry). Multiple
|
|
811
|
-
* wraps from different instrumentations are composed in declaration order,
|
|
812
|
-
* so the first instrumentation's wrap is the outermost.
|
|
813
|
-
*
|
|
814
|
-
* @example
|
|
815
|
-
* ```ts
|
|
816
|
-
* // Cleanup-only (legacy form):
|
|
817
|
-
* beforeConsume(envelope) {
|
|
818
|
-
* const timer = startTimer();
|
|
819
|
-
* return () => timer.end();
|
|
820
|
-
* }
|
|
821
|
-
*
|
|
822
|
-
* // Wrap form — run handler inside an OTel span context:
|
|
823
|
-
* beforeConsume(envelope) {
|
|
824
|
-
* const ctx = propagator.extract(ROOT_CONTEXT, envelope.headers);
|
|
825
|
-
* const span = tracer.startSpan(envelope.topic, {}, ctx);
|
|
826
|
-
* return {
|
|
827
|
-
* wrap: (fn) => context.with(trace.setSpan(ctx, span), fn),
|
|
828
|
-
* cleanup: () => span.end(),
|
|
829
|
-
* };
|
|
830
|
-
* }
|
|
831
|
-
* ```
|
|
832
|
-
*/
|
|
833
|
-
type BeforeConsumeResult = (() => void) | {
|
|
834
|
-
cleanup?(): void;
|
|
835
|
-
wrap?(fn: () => Promise<void>): Promise<void>;
|
|
836
|
-
};
|
|
837
|
-
/**
|
|
838
|
-
* Reason a message was sent to the DLQ.
|
|
839
|
-
* - `'handler-error'` — the consumer handler threw after all retry attempts.
|
|
840
|
-
* - `'validation-error'` — schema validation failed before the handler ran.
|
|
841
|
-
* - `'lamport-clock-duplicate'` — message was identified as a Lamport-clock duplicate
|
|
842
|
-
* and `deduplication.strategy` is `'dlq'`.
|
|
843
|
-
* - `'ttl-expired'` — message age exceeded `messageTtlMs` before the handler ran.
|
|
844
|
-
*/
|
|
845
|
-
type DlqReason = "handler-error" | "validation-error" | "lamport-clock-duplicate" | "ttl-expired";
|
|
846
|
-
/**
|
|
847
|
-
* Options for `readSnapshot`.
|
|
848
|
-
*
|
|
849
|
-
* @example
|
|
850
|
-
* ```ts
|
|
851
|
-
* const snapshot = await kafka.readSnapshot('users.state', {
|
|
852
|
-
* schema: UserSchema,
|
|
853
|
-
* onTombstone: (key) => console.log(`Key ${key} was compacted away`),
|
|
854
|
-
* });
|
|
855
|
-
* ```
|
|
856
|
-
*/
|
|
857
|
-
interface ReadSnapshotOptions {
|
|
858
|
-
/**
|
|
859
|
-
* Schema to validate each message payload against (Zod, Valibot, ArkType, or any `.parse()` shape).
|
|
860
|
-
* Messages that fail validation are skipped with a warning log — they do not throw.
|
|
861
|
-
*/
|
|
862
|
-
schema?: SchemaLike;
|
|
863
|
-
/**
|
|
864
|
-
* Called when a tombstone record (null-value message) is encountered.
|
|
865
|
-
* The corresponding key is removed from the snapshot automatically.
|
|
866
|
-
* Use this for auditing or logging which keys were compacted away.
|
|
867
|
-
*/
|
|
868
|
-
onTombstone?: (key: string) => void;
|
|
869
|
-
}
|
|
870
|
-
/** A single partition offset entry stored in a checkpoint record. */
|
|
871
|
-
interface CheckpointEntry {
|
|
872
|
-
topic: string;
|
|
873
|
-
partition: number;
|
|
874
|
-
offset: string;
|
|
875
|
-
}
|
|
876
|
-
/** Result returned by a successful `checkpointOffsets` call. */
|
|
877
|
-
interface CheckpointResult {
|
|
878
|
-
/** Consumer group whose offsets were saved. */
|
|
879
|
-
groupId: string;
|
|
880
|
-
/** Topics included in the checkpoint. */
|
|
881
|
-
topics: string[];
|
|
882
|
-
/** Total number of topic-partition pairs saved. */
|
|
883
|
-
partitionCount: number;
|
|
884
|
-
/** Unix timestamp (ms) when the checkpoint was created. */
|
|
885
|
-
savedAt: number;
|
|
886
|
-
}
|
|
887
|
-
/** Options for `restoreFromCheckpoint`. */
|
|
888
|
-
interface RestoreCheckpointOptions {
|
|
889
|
-
/**
|
|
890
|
-
* Target Unix timestamp (ms). The newest checkpoint whose `savedAt` is **≤ this value**
|
|
891
|
-
* is selected. Defaults to the latest available checkpoint when omitted.
|
|
892
|
-
*/
|
|
893
|
-
timestamp?: number;
|
|
894
|
-
}
|
|
895
|
-
/** Result returned by a successful `restoreFromCheckpoint` call. */
|
|
896
|
-
interface CheckpointRestoreResult {
|
|
897
|
-
/** Consumer group that was repositioned. */
|
|
898
|
-
groupId: string;
|
|
899
|
-
/** The committed offsets restored from the checkpoint. */
|
|
900
|
-
offsets: CheckpointEntry[];
|
|
901
|
-
/** Unix timestamp (ms) recorded when the checkpoint was originally saved. */
|
|
902
|
-
restoredAt: number;
|
|
903
|
-
/** Age of the restored checkpoint in milliseconds (now − `restoredAt`). */
|
|
904
|
-
checkpointAge: number;
|
|
905
|
-
}
|
|
906
|
-
/**
|
|
907
|
-
* Options for `replayDlq`.
|
|
908
|
-
*
|
|
909
|
-
* @example
|
|
910
|
-
* ```ts
|
|
911
|
-
* await kafka.replayDlq('orders.created', {
|
|
912
|
-
* targetTopic: 'orders.retry-manual',
|
|
913
|
-
* dryRun: false,
|
|
914
|
-
* filter: (headers, value) =>
|
|
915
|
-
* headers['x-dlq-reason'] === 'handler-error' &&
|
|
916
|
-
* JSON.parse(value).amount > 0,
|
|
917
|
-
* });
|
|
918
|
-
* ```
|
|
919
|
-
*/
|
|
920
|
-
interface DlqReplayOptions {
|
|
921
|
-
/**
|
|
922
|
-
* Override the target topic to re-publish to.
|
|
923
|
-
* Default: reads the `x-dlq-original-topic` header from each DLQ message.
|
|
924
|
-
*/
|
|
925
|
-
targetTopic?: string;
|
|
926
|
-
/**
|
|
927
|
-
* Dry-run mode — log what would be replayed without actually sending.
|
|
928
|
-
* Increments the `replayed` counter so you can see what would happen.
|
|
929
|
-
*/
|
|
930
|
-
dryRun?: boolean;
|
|
931
|
-
/**
|
|
932
|
-
* Optional filter — return `false` to skip a message.
|
|
933
|
-
* @param headers All headers on the DLQ message (including `x-dlq-*` metadata).
|
|
934
|
-
* @param value Raw message value (JSON string).
|
|
935
|
-
*/
|
|
936
|
-
filter?: (headers: MessageHeaders, value: string) => boolean;
|
|
937
|
-
/**
|
|
938
|
-
* Seek to the earliest available offset before consuming, regardless of any
|
|
939
|
-
* previously committed offsets for the replay consumer group.
|
|
940
|
-
* Default: `true` — full replay of all DLQ messages on every call.
|
|
941
|
-
* Set to `false` to replay only messages added since the previous `replayDlq` call.
|
|
942
|
-
*/
|
|
943
|
-
fromBeginning?: boolean;
|
|
944
|
-
}
|
|
945
|
-
/**
|
|
946
|
-
* Snapshot of internal event counters accumulated since client creation
|
|
947
|
-
* (or since the last `resetMetrics()` call).
|
|
948
|
-
*/
|
|
949
|
-
interface KafkaMetrics {
|
|
950
|
-
/** Total messages successfully processed by the consumer handler. */
|
|
951
|
-
processedCount: number;
|
|
952
|
-
/** Total retry attempts routed — covers both in-process retries and retry-topic hops. */
|
|
953
|
-
retryCount: number;
|
|
954
|
-
/** Total messages sent to a DLQ topic. */
|
|
955
|
-
dlqCount: number;
|
|
956
|
-
/** Total duplicate messages detected by the Lamport clock. */
|
|
957
|
-
dedupCount: number;
|
|
958
|
-
}
|
|
959
|
-
/**
|
|
960
|
-
* Client-wide instrumentation hooks for both send and consume paths.
|
|
961
|
-
* Use this for cross-cutting concerns like tracing and metrics.
|
|
962
|
-
*
|
|
963
|
-
* @see `otelInstrumentation()` from `@drarzter/kafka-client/otel`
|
|
964
|
-
*
|
|
965
|
-
* @example
|
|
966
|
-
* ```ts
|
|
967
|
-
* const tracing: KafkaInstrumentation = {
|
|
968
|
-
* beforeSend(topic, headers) {
|
|
969
|
-
* headers['traceparent'] = getCurrentTraceId();
|
|
970
|
-
* },
|
|
971
|
-
* beforeConsume(envelope) {
|
|
972
|
-
* const span = tracer.startSpan(envelope.topic);
|
|
973
|
-
* return { cleanup: () => span.end() };
|
|
974
|
-
* },
|
|
975
|
-
* onDlq(envelope, reason) {
|
|
976
|
-
* metrics.increment('kafka.dlq', { topic: envelope.topic, reason });
|
|
977
|
-
* },
|
|
978
|
-
* };
|
|
979
|
-
*
|
|
980
|
-
* const kafka = new KafkaClient(config, groupId, { instrumentation: [tracing] });
|
|
981
|
-
* ```
|
|
982
|
-
*/
|
|
983
|
-
interface KafkaInstrumentation {
|
|
984
|
-
/** Called before sending — can mutate `headers` (e.g. inject `traceparent`). */
|
|
985
|
-
beforeSend?(topic: string, headers: MessageHeaders): void;
|
|
986
|
-
/** Called after a successful send. */
|
|
987
|
-
afterSend?(topic: string): void;
|
|
988
|
-
/**
|
|
989
|
-
* Called before the consumer handler.
|
|
990
|
-
* Return a cleanup function (legacy) or a `BeforeConsumeResult` object with
|
|
991
|
-
* optional `cleanup` and `wrap`. Use `wrap` to run the handler inside a
|
|
992
|
-
* specific async context (e.g. an active OpenTelemetry span).
|
|
993
|
-
*/
|
|
994
|
-
beforeConsume?(envelope: EventEnvelope<any>): BeforeConsumeResult | void;
|
|
995
|
-
/** Called when the consumer handler throws. */
|
|
996
|
-
onConsumeError?(envelope: EventEnvelope<any>, error: Error): void;
|
|
997
|
-
/**
|
|
998
|
-
* Called when a message is queued for retry.
|
|
999
|
-
* Fires for both in-process retries (before the backoff sleep) and
|
|
1000
|
-
* retry-topic routing (EOS and non-EOS paths).
|
|
1001
|
-
*/
|
|
1002
|
-
onRetry?(envelope: EventEnvelope<any>, attempt: number, maxRetries: number): void;
|
|
1003
|
-
/** Called when a message is routed to a DLQ topic. */
|
|
1004
|
-
onDlq?(envelope: EventEnvelope<any>, reason: DlqReason): void;
|
|
1005
|
-
/**
|
|
1006
|
-
* Called when a duplicate message is detected via the Lamport clock.
|
|
1007
|
-
* Fires regardless of the configured `deduplication.strategy`.
|
|
1008
|
-
*/
|
|
1009
|
-
onDuplicate?(envelope: EventEnvelope<any>, strategy: "drop" | "dlq" | "topic"): void;
|
|
1010
|
-
/**
|
|
1011
|
-
* Called after the consumer handler successfully processes a message.
|
|
1012
|
-
* Use this as a success counter for error-rate calculations.
|
|
1013
|
-
* Fires for both single-message and batch consumers (once per envelope).
|
|
1014
|
-
*/
|
|
1015
|
-
onMessage?(envelope: EventEnvelope<any>): void;
|
|
1016
|
-
/** Called when a partition circuit opens (consumer paused due to DLQ failures). */
|
|
1017
|
-
onCircuitOpen?(topic: string, partition: number): void;
|
|
1018
|
-
/** Called when the circuit moves to half-open (partition resumed for a probe). */
|
|
1019
|
-
onCircuitHalfOpen?(topic: string, partition: number): void;
|
|
1020
|
-
/** Called when the circuit closes (normal operation restored). */
|
|
1021
|
-
onCircuitClose?(topic: string, partition: number): void;
|
|
1022
|
-
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Context passed to the `transaction()` callback with type-safe send methods.
|
|
1025
|
-
*
|
|
1026
|
-
* @example
|
|
1027
|
-
* ```ts
|
|
1028
|
-
* await kafka.transaction(async (tx) => {
|
|
1029
|
-
* await tx.send('inventory.reserved', { itemId: 'a', qty: 1 });
|
|
1030
|
-
* await tx.sendBatch('audit.log', [
|
|
1031
|
-
* { value: { action: 'reserve', itemId: 'a' } },
|
|
1032
|
-
* ]);
|
|
1033
|
-
* });
|
|
1034
|
-
* ```
|
|
1035
|
-
*/
|
|
1036
|
-
interface TransactionContext<T extends TopicMapConstraint<T>> {
|
|
1037
|
-
send<K extends keyof T>(topic: K, message: T[K], options?: SendOptions): Promise<void>;
|
|
1038
|
-
send<D extends TopicDescriptor<string & keyof T, T[string & keyof T]>>(descriptor: D, message: D["__type"], options?: SendOptions): Promise<void>;
|
|
1039
|
-
sendBatch<K extends keyof T>(topic: K, messages: Array<BatchMessageItem<T[K]>>, options?: BatchSendOptions): Promise<void>;
|
|
1040
|
-
sendBatch<D extends TopicDescriptor<string & keyof T, T[string & keyof T]>>(descriptor: D, messages: Array<BatchMessageItem<D["__type"]>>, options?: BatchSendOptions): Promise<void>;
|
|
1041
|
-
}
|
|
1042
|
-
/**
|
|
1043
|
-
* Transactional context passed to each `startTransactionalConsumer` handler invocation.
|
|
1044
|
-
*
|
|
1045
|
-
* Every call to `send` or `sendBatch` on this object is part of the same Kafka
|
|
1046
|
-
* transaction as the source message offset commit. Either all sends and the offset
|
|
1047
|
-
* commit succeed atomically, or the transaction is aborted and the source message
|
|
1048
|
-
* is redelivered.
|
|
1049
|
-
*
|
|
1050
|
-
* @example
|
|
1051
|
-
* ```ts
|
|
1052
|
-
* await kafka.startTransactionalConsumer(['orders.created'], async (envelope, tx) => {
|
|
1053
|
-
* await tx.send('inventory.reserved', { orderId: envelope.payload.orderId, qty: 1 });
|
|
1054
|
-
* await tx.sendBatch('audit.log', [{ value: { action: 'reserve', orderId: envelope.payload.orderId } }]);
|
|
1055
|
-
* });
|
|
1056
|
-
* ```
|
|
1057
|
-
*/
|
|
1058
|
-
interface TransactionalHandlerContext<T extends TopicMapConstraint<T>> {
|
|
1059
|
-
/**
|
|
1060
|
-
* Send a message as part of this message's transaction.
|
|
1061
|
-
* The send is staged — it only becomes visible to consumers after the transaction commits.
|
|
1062
|
-
*/
|
|
1063
|
-
send<K extends keyof T>(topic: K, message: T[K], options?: SendOptions): Promise<void>;
|
|
1064
|
-
/**
|
|
1065
|
-
* Send multiple messages as part of this message's transaction.
|
|
1066
|
-
* All messages are staged together and become visible only after commit.
|
|
1067
|
-
*/
|
|
1068
|
-
sendBatch<K extends keyof T>(topic: K, messages: Array<BatchMessageItem<T[K]>>, options?: BatchSendOptions): Promise<void>;
|
|
1069
|
-
}
|
|
1070
|
-
/**
|
|
1071
|
-
* Routing configuration for `startRoutedConsumer`.
|
|
1072
|
-
*
|
|
1073
|
-
* Messages are dispatched to the handler whose key matches the value of `header`.
|
|
1074
|
-
* Unmatched messages are forwarded to `fallback` if provided, or silently skipped.
|
|
1075
|
-
*
|
|
1076
|
-
* @example
|
|
1077
|
-
* ```ts
|
|
1078
|
-
* await kafka.startRoutedConsumer(['events'], {
|
|
1079
|
-
* header: 'x-event-type',
|
|
1080
|
-
* routes: {
|
|
1081
|
-
* 'order.created': async (e) => handleOrderCreated(e.payload),
|
|
1082
|
-
* 'order.cancelled': async (e) => handleOrderCancelled(e.payload),
|
|
1083
|
-
* },
|
|
1084
|
-
* fallback: async (e) => logger.warn('Unknown event type', e.headers),
|
|
1085
|
-
* });
|
|
1086
|
-
* ```
|
|
1087
|
-
*/
|
|
1088
|
-
interface RoutingOptions<E> {
|
|
1089
|
-
/** Header whose value determines which handler is invoked. */
|
|
1090
|
-
header: string;
|
|
1091
|
-
/**
|
|
1092
|
-
* Map of header value → handler.
|
|
1093
|
-
* The handler with the matching key is called for each message.
|
|
1094
|
-
*/
|
|
1095
|
-
routes: Record<string, (envelope: EventEnvelope<E>) => Promise<void>>;
|
|
1096
|
-
/**
|
|
1097
|
-
* Called when no route matches the header value (or the header is absent).
|
|
1098
|
-
* If omitted, unmatched messages are silently skipped.
|
|
1099
|
-
*/
|
|
1100
|
-
fallback?: (envelope: EventEnvelope<E>) => Promise<void>;
|
|
1101
|
-
}
|
|
1102
|
-
/**
|
|
1103
|
-
* Metadata passed to the `startWindowConsumer` handler on each flush.
|
|
1104
|
-
*
|
|
1105
|
-
* @example
|
|
1106
|
-
* ```ts
|
|
1107
|
-
* await kafka.startWindowConsumer('events', async (batch, meta) => {
|
|
1108
|
-
* console.log(`Flush: ${batch.length} events, trigger=${meta.trigger}`);
|
|
1109
|
-
* console.log(`Window: ${meta.windowEnd - meta.windowStart} ms`);
|
|
1110
|
-
* await db.insertMany(batch.map(e => e.payload));
|
|
1111
|
-
* }, { maxMessages: 100, maxMs: 5_000 });
|
|
1112
|
-
* ```
|
|
1113
|
-
*/
|
|
1114
|
-
interface WindowMeta {
|
|
1115
|
-
/** What triggered this flush: accumulated `maxMessages` messages, or the `maxMs` timer. */
|
|
1116
|
-
trigger: "size" | "time";
|
|
1117
|
-
/** Unix timestamp (ms) of the first message that entered the current window. */
|
|
1118
|
-
windowStart: number;
|
|
1119
|
-
/** Unix timestamp (ms) when the flush was initiated. */
|
|
1120
|
-
windowEnd: number;
|
|
1121
|
-
}
|
|
1122
|
-
/**
|
|
1123
|
-
* Options for `startWindowConsumer`.
|
|
1124
|
-
*
|
|
1125
|
-
* Extends `ConsumerOptions` — all standard consumer settings apply.
|
|
1126
|
-
* `retryTopics`, `retryTopicAssignmentTimeoutMs`, and `queueHighWaterMark`
|
|
1127
|
-
* are excluded: they are incompatible with windowed accumulation.
|
|
1128
|
-
*/
|
|
1129
|
-
interface WindowConsumerOptions<T extends TopicMapConstraint<T> = TTopicMessageMap> extends Omit<ConsumerOptions<T>, "retryTopics" | "retryTopicAssignmentTimeoutMs" | "queueHighWaterMark"> {
|
|
1130
|
-
/**
|
|
1131
|
-
* Maximum number of messages to accumulate before flushing.
|
|
1132
|
-
* When the buffer reaches this size the handler is called immediately,
|
|
1133
|
-
* regardless of how much time has elapsed since the first message.
|
|
1134
|
-
*/
|
|
1135
|
-
maxMessages: number;
|
|
1136
|
-
/**
|
|
1137
|
-
* Maximum time (ms) to wait after the first message before flushing.
|
|
1138
|
-
* When this timer expires the handler is called with whatever messages
|
|
1139
|
-
* have accumulated so far — even if the buffer is not full.
|
|
1140
|
-
*/
|
|
1141
|
-
maxMs: number;
|
|
1142
|
-
}
|
|
1143
|
-
/**
|
|
1144
|
-
* Handle returned by `startConsumer` / `startBatchConsumer`.
|
|
1145
|
-
*
|
|
1146
|
-
* @example
|
|
1147
|
-
* ```ts
|
|
1148
|
-
* const handle = await kafka.startConsumer(['orders'], handler);
|
|
1149
|
-
* // later, on shutdown:
|
|
1150
|
-
* await handle.stop();
|
|
1151
|
-
* ```
|
|
1152
|
-
*/
|
|
1153
|
-
interface ConsumerHandle {
|
|
1154
|
-
/** The consumer group ID this consumer is running under. */
|
|
1155
|
-
groupId: string;
|
|
1156
|
-
/** Stop this consumer. Equivalent to calling `client.stopConsumer(groupId)`. */
|
|
1157
|
-
stop(): Promise<void>;
|
|
1158
|
-
/**
|
|
1159
|
-
* Resolves once the consumer has received its first partition assignment from
|
|
1160
|
-
* the broker (i.e. it has joined the group and is ready to receive messages).
|
|
1161
|
-
*
|
|
1162
|
-
* Use this in tests and startup probes instead of a fixed `setTimeout` delay:
|
|
1163
|
-
*
|
|
1164
|
-
* @example
|
|
1165
|
-
* ```ts
|
|
1166
|
-
* const handle = await kafka.startConsumer(['orders'], handler);
|
|
1167
|
-
* await handle.ready(); // wait for partition assignment — no fixed sleep needed
|
|
1168
|
-
* await kafka.sendMessage('orders', payload);
|
|
1169
|
-
* ```
|
|
1170
|
-
*/
|
|
1171
|
-
ready(): Promise<void>;
|
|
1172
|
-
}
|
|
1173
|
-
/** Result returned by `KafkaClient.checkStatus()`. */
|
|
1174
|
-
type KafkaHealthResult = {
|
|
1175
|
-
status: "up";
|
|
1176
|
-
clientId: string;
|
|
1177
|
-
topics: string[];
|
|
1178
|
-
} | {
|
|
1179
|
-
status: "down";
|
|
1180
|
-
clientId: string;
|
|
1181
|
-
error: string;
|
|
1182
|
-
};
|
|
1183
|
-
/** Summary of a consumer group returned by `listConsumerGroups`. */
|
|
1184
|
-
interface ConsumerGroupSummary {
|
|
1185
|
-
/** Consumer group ID. */
|
|
1186
|
-
groupId: string;
|
|
1187
|
-
/**
|
|
1188
|
-
* Current broker-reported state of the group.
|
|
1189
|
-
* Common values: `'Empty'`, `'Stable'`, `'PreparingRebalance'`, `'CompletingRebalance'`, `'Dead'`.
|
|
1190
|
-
*/
|
|
1191
|
-
state: string;
|
|
1192
|
-
}
|
|
1193
|
-
/** Partition-level metadata for a topic. */
|
|
1194
|
-
interface TopicPartitionInfo {
|
|
1195
|
-
/** Partition index (0-based). */
|
|
1196
|
-
partition: number;
|
|
1197
|
-
/** Node ID of the partition leader broker. */
|
|
1198
|
-
leader: number;
|
|
1199
|
-
/** Node IDs of all replica brokers. */
|
|
1200
|
-
replicas: number[];
|
|
1201
|
-
/** Node IDs of in-sync replicas. */
|
|
1202
|
-
isr: number[];
|
|
1203
|
-
}
|
|
1204
|
-
/** Topic metadata returned by `describeTopics`. */
|
|
1205
|
-
interface TopicDescription {
|
|
1206
|
-
/** Topic name. */
|
|
1207
|
-
name: string;
|
|
1208
|
-
/** Per-partition metadata. */
|
|
1209
|
-
partitions: TopicPartitionInfo[];
|
|
1210
|
-
}
|
|
1211
|
-
/** Interface describing all public methods of the Kafka client. */
|
|
1212
|
-
interface IKafkaClient<T extends TopicMapConstraint<T>> {
|
|
1213
|
-
/**
|
|
1214
|
-
* @example
|
|
1215
|
-
* ```ts
|
|
1216
|
-
* const status = await kafka.checkStatus();
|
|
1217
|
-
* if (status.status === 'down') console.error(status.error);
|
|
1218
|
-
* ```
|
|
1219
|
-
*/
|
|
1220
|
-
checkStatus(): Promise<KafkaHealthResult>;
|
|
1221
|
-
/**
|
|
1222
|
-
* List all consumer groups known to the broker.
|
|
1223
|
-
* @returns Array of `{ groupId, state }` summaries.
|
|
1224
|
-
*
|
|
1225
|
-
* @example
|
|
1226
|
-
* ```ts
|
|
1227
|
-
* const groups = await kafka.listConsumerGroups();
|
|
1228
|
-
* console.log(groups.map(g => `${g.groupId}: ${g.state}`));
|
|
1229
|
-
* ```
|
|
1230
|
-
*/
|
|
1231
|
-
listConsumerGroups(): Promise<ConsumerGroupSummary[]>;
|
|
1232
|
-
/**
|
|
1233
|
-
* Describe topics — returns partition layout, leader, replicas, and ISR for each topic.
|
|
1234
|
-
* @param topics Topic names to describe. Omit to describe all topics visible to this client.
|
|
1235
|
-
*
|
|
1236
|
-
* @example
|
|
1237
|
-
* ```ts
|
|
1238
|
-
* const [desc] = await kafka.describeTopics(['orders.created']);
|
|
1239
|
-
* console.log(desc.partitions.length); // number of partitions
|
|
1240
|
-
* ```
|
|
1241
|
-
*/
|
|
1242
|
-
describeTopics(topics?: string[]): Promise<TopicDescription[]>;
|
|
1243
|
-
/**
|
|
1244
|
-
* Delete records from a topic up to (but not including) the specified offsets.
|
|
1245
|
-
* All messages with offsets **before** the given offset are deleted.
|
|
1246
|
-
* Useful for purging expired or sensitive data.
|
|
1247
|
-
*
|
|
1248
|
-
* @param topic Topic name.
|
|
1249
|
-
* @param partitions Array of `{ partition, offset }` — records before each offset are deleted.
|
|
1250
|
-
*
|
|
1251
|
-
* @example
|
|
1252
|
-
* ```ts
|
|
1253
|
-
* await kafka.deleteRecords('orders.created', [
|
|
1254
|
-
* { partition: 0, offset: '1000' },
|
|
1255
|
-
* { partition: 1, offset: '500' },
|
|
1256
|
-
* ]);
|
|
1257
|
-
* ```
|
|
1258
|
-
*/
|
|
1259
|
-
deleteRecords(topic: string, partitions: Array<{
|
|
1260
|
-
partition: number;
|
|
1261
|
-
offset: string;
|
|
1262
|
-
}>): Promise<void>;
|
|
1263
|
-
/**
|
|
1264
|
-
* Query the consumer group lag per partition using the admin API.
|
|
1265
|
-
* Lag = (broker high-watermark offset) − (last committed offset).
|
|
1266
|
-
* - A committed offset of `-1` (no offset committed yet) counts as full lag.
|
|
1267
|
-
* - Defaults to the client's default `groupId` when none is provided.
|
|
1268
|
-
*
|
|
1269
|
-
* @example
|
|
1270
|
-
* ```ts
|
|
1271
|
-
* const lag = await kafka.getConsumerLag();
|
|
1272
|
-
* const total = lag.reduce((sum, p) => sum + p.lag, 0);
|
|
1273
|
-
* console.log(`Total lag: ${total} messages`);
|
|
1274
|
-
* ```
|
|
1275
|
-
*/
|
|
1276
|
-
getConsumerLag(groupId?: string): Promise<Array<{
|
|
1277
|
-
topic: string;
|
|
1278
|
-
partition: number;
|
|
1279
|
-
lag: number;
|
|
1280
|
-
}>>;
|
|
1281
|
-
/**
|
|
1282
|
-
* @example
|
|
1283
|
-
* ```ts
|
|
1284
|
-
* const handle = await kafka.startConsumer(['orders.created'], async (envelope) => {
|
|
1285
|
-
* await processOrder(envelope.payload);
|
|
1286
|
-
* }, { retry: { maxRetries: 3 }, dlq: true });
|
|
1287
|
-
*
|
|
1288
|
-
* // on shutdown:
|
|
1289
|
-
* await handle.stop();
|
|
1290
|
-
* ```
|
|
1291
|
-
*/
|
|
1292
|
-
startConsumer<K extends Array<keyof T>>(topics: K, handleMessage: (envelope: EventEnvelope<T[K[number]]>) => Promise<void>, options?: ConsumerOptions<T>): Promise<ConsumerHandle>;
|
|
1293
|
-
startConsumer<D extends TopicDescriptor<string & keyof T, T[string & keyof T]>>(topics: D[], handleMessage: (envelope: EventEnvelope<D["__type"]>) => Promise<void>, options?: ConsumerOptions<T>): Promise<ConsumerHandle>;
|
|
1294
|
-
/**
|
|
1295
|
-
* Subscribe using regex topic patterns (or a mix of strings and patterns).
|
|
1296
|
-
* Note: type-safety is reduced to the union of all topic payloads when using regex.
|
|
1297
|
-
* Incompatible with `retryTopics: true`.
|
|
1298
|
-
*/
|
|
1299
|
-
startConsumer(topics: (string | RegExp)[], handleMessage: (envelope: EventEnvelope<T[keyof T]>) => Promise<void>, options?: ConsumerOptions<T>): Promise<ConsumerHandle>;
|
|
1300
|
-
/**
|
|
1301
|
-
* @example
|
|
1302
|
-
* ```ts
|
|
1303
|
-
* await kafka.startBatchConsumer(['metrics'], async (envelopes, meta) => {
|
|
1304
|
-
* await db.insertMany(envelopes.map(e => e.payload));
|
|
1305
|
-
* meta.resolveOffset(envelopes.at(-1)!.offset);
|
|
1306
|
-
* }, { autoCommit: false });
|
|
1307
|
-
* ```
|
|
1308
|
-
*/
|
|
1309
|
-
startBatchConsumer<K extends Array<keyof T>>(topics: K, handleBatch: (envelopes: EventEnvelope<T[K[number]]>[], meta: BatchMeta) => Promise<void>, options?: ConsumerOptions<T>): Promise<ConsumerHandle>;
|
|
1310
|
-
startBatchConsumer<D extends TopicDescriptor<string & keyof T, T[string & keyof T]>>(topics: D[], handleBatch: (envelopes: EventEnvelope<D["__type"]>[], meta: BatchMeta) => Promise<void>, options?: ConsumerOptions<T>): Promise<ConsumerHandle>;
|
|
1311
|
-
/**
|
|
1312
|
-
* Subscribe using regex topic patterns (or a mix of strings and patterns).
|
|
1313
|
-
* Note: type-safety is reduced to the union of all topic payloads when using regex.
|
|
1314
|
-
* Incompatible with `retryTopics: true`.
|
|
1315
|
-
*/
|
|
1316
|
-
startBatchConsumer(topics: (string | RegExp)[], handleBatch: (envelopes: EventEnvelope<T[keyof T]>[], meta: BatchMeta) => Promise<void>, options?: ConsumerOptions<T>): Promise<ConsumerHandle>;
|
|
1317
|
-
/**
|
|
1318
|
-
* Subscribe to a topic and accumulate messages into a window, flushing the handler
|
|
1319
|
-
* when **either** `maxMessages` messages have accumulated **or** `maxMs` milliseconds
|
|
1320
|
-
* have elapsed since the first message in the current window — whichever fires first.
|
|
1321
|
-
*
|
|
1322
|
-
* This is semantically different from `startBatchConsumer`, which delivers
|
|
1323
|
-
* broker-sized batches of unpredictable size. A window consumer gives you control
|
|
1324
|
-
* over both the size and the latency of each batch — useful for micro-batching
|
|
1325
|
-
* writes to a database, aggregating events before processing, or rate-limiting a
|
|
1326
|
-
* downstream API.
|
|
1327
|
-
*
|
|
1328
|
-
* On `handle.stop()`, any messages remaining in the buffer are flushed before the
|
|
1329
|
-
* consumer disconnects, so no messages are lost on clean shutdown.
|
|
1330
|
-
*
|
|
1331
|
-
* @param topic Topic to consume.
|
|
1332
|
-
* @param handler Called with each flushed window and a `WindowMeta` describing the trigger.
|
|
1333
|
-
* @param options Window size/timeout and standard consumer options.
|
|
1334
|
-
*
|
|
1335
|
-
* @example
|
|
1336
|
-
* ```ts
|
|
1337
|
-
* await kafka.startWindowConsumer('events', async (batch, meta) => {
|
|
1338
|
-
* console.log(`Flushing ${batch.length} events (trigger: ${meta.trigger})`);
|
|
1339
|
-
* await db.insertMany(batch.map(e => e.payload));
|
|
1340
|
-
* }, { maxMessages: 100, maxMs: 5_000 });
|
|
1341
|
-
* ```
|
|
1342
|
-
*/
|
|
1343
|
-
startWindowConsumer<K extends keyof T & string>(topic: K, handler: (envelopes: EventEnvelope<T[K]>[], meta: WindowMeta) => Promise<void>, options: WindowConsumerOptions<T>): Promise<ConsumerHandle>;
|
|
1344
|
-
/**
|
|
1345
|
-
* Subscribe to one or more topics and dispatch each message to a handler based
|
|
1346
|
-
* on the value of a specific Kafka header. Eliminates boilerplate `if/switch`
|
|
1347
|
-
* statements inside a catch-all handler when one topic carries multiple event types.
|
|
1348
|
-
*
|
|
1349
|
-
* Messages whose header value does not match any route key are forwarded to `fallback`
|
|
1350
|
-
* if provided, or silently skipped otherwise.
|
|
1351
|
-
*
|
|
1352
|
-
* Accepts the same `ConsumerOptions` as `startConsumer` — retry, DLQ, deduplication,
|
|
1353
|
-
* circuit breaker, interceptors, etc. all apply to every route.
|
|
1354
|
-
*
|
|
1355
|
-
* @param topics Array of topic keys or `RegExp` patterns.
|
|
1356
|
-
* @param routing Header name, route map, and optional fallback handler.
|
|
1357
|
-
* @param options Standard consumer options.
|
|
1358
|
-
* @returns A handle with `{ groupId, stop() }`.
|
|
1359
|
-
*
|
|
1360
|
-
* @example
|
|
1361
|
-
* ```ts
|
|
1362
|
-
* await kafka.startRoutedConsumer(['domain.events'], {
|
|
1363
|
-
* header: 'x-event-type',
|
|
1364
|
-
* routes: {
|
|
1365
|
-
* 'order.created': async (e) => handleOrderCreated(e.payload),
|
|
1366
|
-
* 'order.cancelled': async (e) => handleOrderCancelled(e.payload),
|
|
1367
|
-
* },
|
|
1368
|
-
* fallback: async (e) => logger.warn('Unknown event type', e.headers),
|
|
1369
|
-
* });
|
|
1370
|
-
* ```
|
|
1371
|
-
*/
|
|
1372
|
-
startRoutedConsumer<K extends Array<keyof T>>(topics: K, routing: RoutingOptions<T[K[number]]>, options?: ConsumerOptions<T>): Promise<ConsumerHandle>;
|
|
1373
|
-
/**
|
|
1374
|
-
* Subscribe to topics and consume messages with **exactly-once semantics** for
|
|
1375
|
-
* read-process-write pipelines.
|
|
1376
|
-
*
|
|
1377
|
-
* Each message is processed inside a Kafka transaction: the handler receives a
|
|
1378
|
-
* `TransactionalHandlerContext` with `send` / `sendBatch` methods that stage
|
|
1379
|
-
* outgoing messages inside the transaction. When the handler resolves, the source
|
|
1380
|
-
* offset commit and all staged sends are committed atomically. A handler error
|
|
1381
|
-
* aborts the transaction and the source message is redelivered — no sends become
|
|
1382
|
-
* visible to downstream consumers.
|
|
1383
|
-
*
|
|
1384
|
-
* Incompatible with `retryTopics: true` — throws at startup if set.
|
|
1385
|
-
* `autoCommit` is always `false` (managed by the transaction).
|
|
1386
|
-
*
|
|
1387
|
-
* @param topics Array of topic keys.
|
|
1388
|
-
* @param handler Called for each message with the decoded envelope and a transaction context.
|
|
1389
|
-
* @param options Standard consumer options (`retry`, `dlq`, `deduplication`, etc.).
|
|
1390
|
-
* @returns A handle with `{ groupId, stop() }`.
|
|
1391
|
-
*
|
|
1392
|
-
* @example
|
|
1393
|
-
* ```ts
|
|
1394
|
-
* await kafka.startTransactionalConsumer(['orders.created'], async (envelope, tx) => {
|
|
1395
|
-
* await tx.send('inventory.reserved', { orderId: envelope.payload.orderId, qty: 1 });
|
|
1396
|
-
* });
|
|
1397
|
-
* ```
|
|
1398
|
-
*/
|
|
1399
|
-
startTransactionalConsumer<K extends Array<keyof T>>(topics: K, handler: (envelope: EventEnvelope<T[K[number]]>, tx: TransactionalHandlerContext<T>) => Promise<void>, options?: ConsumerOptions<T>): Promise<ConsumerHandle>;
|
|
1400
|
-
/**
|
|
1401
|
-
* Stop consumer(s).
|
|
1402
|
-
* - `stopConsumer(groupId)` — disconnect and remove the consumer for a specific group.
|
|
1403
|
-
* - `stopConsumer()` — disconnect and remove all consumers.
|
|
1404
|
-
*/
|
|
1405
|
-
stopConsumer(groupId?: string): Promise<void>;
|
|
1406
|
-
/**
|
|
1407
|
-
* @example
|
|
1408
|
-
* ```ts
|
|
1409
|
-
* await kafka.sendMessage('orders.created', { orderId: '123', amount: 99 });
|
|
1410
|
-
* ```
|
|
1411
|
-
*/
|
|
1412
|
-
sendMessage<K extends keyof T>(topic: K, message: T[K], options?: SendOptions): Promise<void>;
|
|
1413
|
-
/**
|
|
1414
|
-
* Send a null-value (tombstone) message to a topic.
|
|
1415
|
-
* Tombstones are used with log-compacted topics to signal that a key's record
|
|
1416
|
-
* should be removed during the next compaction cycle.
|
|
1417
|
-
*
|
|
1418
|
-
* Unlike `sendMessage`, tombstones carry no payload, no envelope headers, and
|
|
1419
|
-
* skip schema validation. Only the partition `key` and optional custom `headers`
|
|
1420
|
-
* are forwarded to Kafka.
|
|
1421
|
-
*
|
|
1422
|
-
* @param topic Topic name or descriptor.
|
|
1423
|
-
* @param key Partition key identifying the record to tombstone.
|
|
1424
|
-
* @param headers Optional custom Kafka headers.
|
|
1425
|
-
*
|
|
1426
|
-
* @example
|
|
1427
|
-
* ```ts
|
|
1428
|
-
* await kafka.sendTombstone('users.state', 'user-42');
|
|
1429
|
-
* ```
|
|
1430
|
-
*/
|
|
1431
|
-
sendTombstone(topic: string, key: string, headers?: MessageHeaders): Promise<void>;
|
|
1432
|
-
/**
|
|
1433
|
-
* @example
|
|
1434
|
-
* ```ts
|
|
1435
|
-
* await kafka.sendBatch('orders.created', [
|
|
1436
|
-
* { value: { orderId: '1', amount: 10 }, key: 'order-1' },
|
|
1437
|
-
* { value: { orderId: '2', amount: 20 }, key: 'order-2' },
|
|
1438
|
-
* ]);
|
|
1439
|
-
* ```
|
|
1440
|
-
*/
|
|
1441
|
-
sendBatch<K extends keyof T>(topic: K, messages: Array<BatchMessageItem<T[K]>>, options?: BatchSendOptions): Promise<void>;
|
|
1442
|
-
/**
|
|
1443
|
-
* @example
|
|
1444
|
-
* ```ts
|
|
1445
|
-
* await kafka.transaction(async (tx) => {
|
|
1446
|
-
* await tx.send('orders.created', { orderId: '123', amount: 99 });
|
|
1447
|
-
* await tx.send('inventory.reserved', { itemId: 'a', qty: 1 });
|
|
1448
|
-
* });
|
|
1449
|
-
* ```
|
|
1450
|
-
*/
|
|
1451
|
-
transaction(fn: (ctx: TransactionContext<T>) => Promise<void>): Promise<void>;
|
|
1452
|
-
/**
|
|
1453
|
-
* @example
|
|
1454
|
-
* ```ts
|
|
1455
|
-
* const id = kafka.getClientId(); // e.g. 'my-service'
|
|
1456
|
-
* ```
|
|
1457
|
-
*/
|
|
1458
|
-
getClientId(): ClientId;
|
|
1459
|
-
/**
|
|
1460
|
-
* Return a snapshot of internal event counters (retry / DLQ / dedup).
|
|
1461
|
-
* - `getMetrics()` — aggregate across all topics.
|
|
1462
|
-
* - `getMetrics(topic)` — counters for a specific topic only; returns all-zero
|
|
1463
|
-
* if no events have been observed for that topic yet.
|
|
1464
|
-
*
|
|
1465
|
-
* Counters accumulate since client creation or the last `resetMetrics()` call.
|
|
1466
|
-
*
|
|
1467
|
-
* @example
|
|
1468
|
-
* ```ts
|
|
1469
|
-
* const { processedCount, dlqCount, retryCount } = kafka.getMetrics();
|
|
1470
|
-
* console.log(`Processed: ${processedCount}, DLQ: ${dlqCount}`);
|
|
1471
|
-
*
|
|
1472
|
-
* const topicMetrics = kafka.getMetrics('orders.created');
|
|
1473
|
-
* ```
|
|
1474
|
-
*/
|
|
1475
|
-
getMetrics(topic?: string): Readonly<KafkaMetrics>;
|
|
1476
|
-
/**
|
|
1477
|
-
* Reset internal event counters to zero.
|
|
1478
|
-
* - `resetMetrics()` — reset all topics.
|
|
1479
|
-
* - `resetMetrics(topic)` — reset a single topic only.
|
|
1480
|
-
*
|
|
1481
|
-
* @example
|
|
1482
|
-
* ```ts
|
|
1483
|
-
* kafka.resetMetrics(); // reset all
|
|
1484
|
-
* kafka.resetMetrics('orders.created'); // reset one topic
|
|
1485
|
-
* ```
|
|
1486
|
-
*/
|
|
1487
|
-
resetMetrics(topic?: string): void;
|
|
1488
|
-
/**
|
|
1489
|
-
* Consume all messages currently in `{topic}.dlq`, strip the `x-dlq-*` metadata
|
|
1490
|
-
* headers, and re-publish each message to its original topic (or `options.targetTopic`).
|
|
1491
|
-
*
|
|
1492
|
-
* A temporary consumer group is created and torn down automatically. The DLQ topic
|
|
1493
|
-
* itself is not modified — messages remain there after replay.
|
|
1494
|
-
*
|
|
1495
|
-
* @returns `{ replayed, skipped }` — counts of re-published vs skipped messages.
|
|
1496
|
-
*
|
|
1497
|
-
* @example
|
|
1498
|
-
* ```ts
|
|
1499
|
-
* const { replayed, skipped } = await kafka.replayDlq('orders.created');
|
|
1500
|
-
* console.log(`Replayed ${replayed}, skipped ${skipped}`);
|
|
1501
|
-
*
|
|
1502
|
-
* // dry-run with filter:
|
|
1503
|
-
* await kafka.replayDlq('orders.created', {
|
|
1504
|
-
* dryRun: true,
|
|
1505
|
-
* filter: (headers) => headers['x-dlq-reason'] === 'handler-error',
|
|
1506
|
-
* });
|
|
1507
|
-
* ```
|
|
1508
|
-
*/
|
|
1509
|
-
replayDlq(topic: string, options?: DlqReplayOptions): Promise<{
|
|
1510
|
-
replayed: number;
|
|
1511
|
-
skipped: number;
|
|
1512
|
-
}>;
|
|
1513
|
-
/**
|
|
1514
|
-
* Reset committed offsets for a consumer group to the earliest or latest position.
|
|
1515
|
-
*
|
|
1516
|
-
* The consumer group must be inactive (no running consumers) — Kafka does not
|
|
1517
|
-
* allow offset resets while members are actively consuming. Call
|
|
1518
|
-
* `stopConsumer(groupId)` first.
|
|
1519
|
-
*
|
|
1520
|
-
* @param groupId Consumer group to reset. Defaults to the client's default groupId.
|
|
1521
|
-
* @param topic Topic to reset.
|
|
1522
|
-
* @param position `'earliest'` seeks to the first available offset; `'latest'`
|
|
1523
|
-
* seeks past the last message (consumer will only see new messages).
|
|
1524
|
-
*
|
|
1525
|
-
* @example
|
|
1526
|
-
* ```ts
|
|
1527
|
-
* await kafka.stopConsumer('billing-service');
|
|
1528
|
-
* await kafka.resetOffsets('billing-service', 'orders.created', 'earliest');
|
|
1529
|
-
* ```
|
|
1530
|
-
*/
|
|
1531
|
-
resetOffsets(groupId: string | undefined, topic: string, position: "earliest" | "latest"): Promise<void>;
|
|
1532
|
-
/**
|
|
1533
|
-
* Seek specific partitions to explicit offsets.
|
|
1534
|
-
* More granular than `resetOffsets` — each partition can target a different offset.
|
|
1535
|
-
*
|
|
1536
|
-
* The consumer group must be inactive. Assignments for different topics are batched
|
|
1537
|
-
* into one admin call per topic.
|
|
1538
|
-
*
|
|
1539
|
-
* @param groupId Consumer group to seek. Defaults to the client's default groupId.
|
|
1540
|
-
* @param assignments Array of `{ topic, partition, offset }` tuples.
|
|
1541
|
-
*
|
|
1542
|
-
* @example
|
|
1543
|
-
* ```ts
|
|
1544
|
-
* await kafka.seekToOffset('billing-service', [
|
|
1545
|
-
* { topic: 'orders.created', partition: 0, offset: '1000' },
|
|
1546
|
-
* { topic: 'orders.created', partition: 1, offset: '500' },
|
|
1547
|
-
* ]);
|
|
1548
|
-
* ```
|
|
1549
|
-
*/
|
|
1550
|
-
seekToOffset(groupId: string | undefined, assignments: Array<{
|
|
1551
|
-
topic: string;
|
|
1552
|
-
partition: number;
|
|
1553
|
-
offset: string;
|
|
1554
|
-
}>): Promise<void>;
|
|
1555
|
-
/**
|
|
1556
|
-
* Seek specific partitions to the offset nearest to a given Unix timestamp (ms).
|
|
1557
|
-
* Uses `admin.fetchTopicOffsetsByTime` under the hood. Falls back to `-1` (end of
|
|
1558
|
-
* topic) when no offset exists at the requested timestamp (e.g. empty partition or
|
|
1559
|
-
* future timestamp).
|
|
1560
|
-
*
|
|
1561
|
-
* The consumer group must be inactive. Call `stopConsumer(groupId)` first.
|
|
1562
|
-
*
|
|
1563
|
-
* @param groupId Consumer group to seek. Defaults to the client's default groupId.
|
|
1564
|
-
* @param assignments Array of `{ topic, partition, timestamp }` tuples (Unix ms).
|
|
1565
|
-
*
|
|
1566
|
-
* @example
|
|
1567
|
-
* ```ts
|
|
1568
|
-
* const midnight = new Date('2025-01-01').getTime();
|
|
1569
|
-
* await kafka.seekToTimestamp('billing-service', [
|
|
1570
|
-
* { topic: 'orders.created', partition: 0, timestamp: midnight },
|
|
1571
|
-
* ]);
|
|
1572
|
-
* ```
|
|
1573
|
-
*/
|
|
1574
|
-
seekToTimestamp(groupId: string | undefined, assignments: Array<{
|
|
1575
|
-
topic: string;
|
|
1576
|
-
partition: number;
|
|
1577
|
-
timestamp: number;
|
|
1578
|
-
}>): Promise<void>;
|
|
1579
|
-
/**
|
|
1580
|
-
* Returns the current circuit breaker state for a specific topic partition.
|
|
1581
|
-
* Returns `undefined` when no circuit state exists — either `circuitBreaker` is not
|
|
1582
|
-
* configured for the group, or the circuit has never been tripped.
|
|
1583
|
-
*
|
|
1584
|
-
* @param topic Topic name.
|
|
1585
|
-
* @param partition Partition index.
|
|
1586
|
-
* @param groupId Consumer group. Defaults to the client's default groupId.
|
|
1587
|
-
*
|
|
1588
|
-
* @example
|
|
1589
|
-
* ```ts
|
|
1590
|
-
* const state = kafka.getCircuitState('orders.created', 0);
|
|
1591
|
-
* if (state?.status === 'open') console.warn('Circuit open!', state.failures);
|
|
1592
|
-
* ```
|
|
1593
|
-
*/
|
|
1594
|
-
getCircuitState(topic: string, partition: number, groupId?: string): {
|
|
1595
|
-
status: "closed" | "open" | "half-open";
|
|
1596
|
-
failures: number;
|
|
1597
|
-
windowSize: number;
|
|
1598
|
-
} | undefined;
|
|
1599
|
-
/**
|
|
1600
|
-
* Read a compacted topic from the beginning to its current high-watermark and
|
|
1601
|
-
* return a `Map<key, EventEnvelope<T>>` with the **latest** value per key.
|
|
1602
|
-
*
|
|
1603
|
-
* Tombstone records (null-value messages) remove the key from the map —
|
|
1604
|
-
* consistent with log-compaction semantics.
|
|
1605
|
-
*
|
|
1606
|
-
* Useful for bootstrapping in-memory state at service startup without an external cache:
|
|
1607
|
-
* ```ts
|
|
1608
|
-
* const orders = await kafka.readSnapshot('orders.state');
|
|
1609
|
-
* // orders.get('order-123') → EventEnvelope with the latest payload for that key
|
|
1610
|
-
* ```
|
|
1611
|
-
*
|
|
1612
|
-
* @param topic Topic to read. Must be a log-compacted topic (or any topic you want a key → latest-value index for).
|
|
1613
|
-
* @param options Optional schema validation and tombstone callback.
|
|
1614
|
-
* @returns Map keyed by the Kafka message key string; value is the last seen `EventEnvelope`.
|
|
1615
|
-
*/
|
|
1616
|
-
readSnapshot<K extends keyof T & string>(topic: K, options?: ReadSnapshotOptions): Promise<Map<string, EventEnvelope<T[K]>>>;
|
|
1617
|
-
/**
|
|
1618
|
-
* Snapshot the current committed offsets of a consumer group into a Kafka topic.
|
|
1619
|
-
*
|
|
1620
|
-
* Each call appends a new checkpoint record keyed by `groupId`. The checkpoint topic
|
|
1621
|
-
* acts as an append-only audit log — use a non-compacted topic to retain history.
|
|
1622
|
-
* Use `restoreFromCheckpoint` to rewind the group to any saved point in time.
|
|
1623
|
-
*
|
|
1624
|
-
* Requires `connectProducer()` to have been called.
|
|
1625
|
-
*
|
|
1626
|
-
* @param groupId Consumer group whose offsets to checkpoint. Defaults to the client's default group.
|
|
1627
|
-
* @param checkpointTopic Topic where checkpoint records are written.
|
|
1628
|
-
* @returns Summary of the saved checkpoint.
|
|
1629
|
-
*
|
|
1630
|
-
* @example
|
|
1631
|
-
* ```ts
|
|
1632
|
-
* const result = await kafka.checkpointOffsets(undefined, 'checkpoints');
|
|
1633
|
-
* console.log(`Saved ${result.partitionCount} offsets at ${result.savedAt}`);
|
|
1634
|
-
* ```
|
|
1635
|
-
*/
|
|
1636
|
-
checkpointOffsets(groupId: string | undefined, checkpointTopic: string): Promise<CheckpointResult>;
|
|
1637
|
-
/**
|
|
1638
|
-
* Restore a consumer group's committed offsets from the nearest checkpoint stored in `checkpointTopic`.
|
|
1639
|
-
*
|
|
1640
|
-
* Reads all checkpoint records for the group, selects the newest checkpoint whose `savedAt`
|
|
1641
|
-
* timestamp is ≤ `options.timestamp` (or the latest checkpoint if no timestamp is given),
|
|
1642
|
-
* then calls `admin.setOffsets` for every topic-partition in that checkpoint.
|
|
1643
|
-
*
|
|
1644
|
-
* **The consumer group must be stopped before calling this method** — throws if any consumer
|
|
1645
|
-
* in the group is currently running.
|
|
1646
|
-
*
|
|
1647
|
-
* @param groupId Consumer group to reposition. Defaults to the client's default group.
|
|
1648
|
-
* @param checkpointTopic Topic where checkpoints were written by `checkpointOffsets`.
|
|
1649
|
-
* @param options.timestamp Target Unix ms. Omit to restore the latest checkpoint.
|
|
1650
|
-
* @throws If no checkpoint exists for the group, or if the group is still running.
|
|
1651
|
-
*
|
|
1652
|
-
* @example
|
|
1653
|
-
* ```ts
|
|
1654
|
-
* await kafka.stopConsumer('billing-service');
|
|
1655
|
-
* const result = await kafka.restoreFromCheckpoint(undefined, 'checkpoints');
|
|
1656
|
-
* console.log(`Restored from checkpoint saved ${result.checkpointAge}ms ago`);
|
|
1657
|
-
*
|
|
1658
|
-
* // restore to the state before a specific deployment:
|
|
1659
|
-
* await kafka.restoreFromCheckpoint(undefined, 'checkpoints', {
|
|
1660
|
-
* timestamp: new Date('2025-06-01T00:00:00Z').getTime(),
|
|
1661
|
-
* });
|
|
1662
|
-
* ```
|
|
1663
|
-
*/
|
|
1664
|
-
restoreFromCheckpoint(groupId: string | undefined, checkpointTopic: string, options?: RestoreCheckpointOptions): Promise<CheckpointRestoreResult>;
|
|
1665
|
-
/**
|
|
1666
|
-
* Consume messages as an async iterator. Useful for scripts, migrations, and
|
|
1667
|
-
* one-off processing where the full `startConsumer` lifecycle is unnecessary.
|
|
1668
|
-
*
|
|
1669
|
-
* Breaking out of the loop (or calling `return()` on the iterator) stops the
|
|
1670
|
-
* underlying consumer automatically.
|
|
1671
|
-
*
|
|
1672
|
-
* @remarks
|
|
1673
|
-
* **Limitations vs `startConsumer`:**
|
|
1674
|
-
* - Does **not** support `retryTopics: true` (EOS retry chains). Use `startConsumer` for that.
|
|
1675
|
-
* - Does not support regex topic patterns.
|
|
1676
|
-
*
|
|
1677
|
-
* @example
|
|
1678
|
-
* ```ts
|
|
1679
|
-
* for await (const envelope of kafka.consume('orders')) {
|
|
1680
|
-
* await process(envelope);
|
|
1681
|
-
* }
|
|
1682
|
-
* ```
|
|
1683
|
-
*/
|
|
1684
|
-
consume<K extends keyof T & string>(topic: K, options?: ConsumerOptions<T>): AsyncIterableIterator<EventEnvelope<T[K]>>;
|
|
1685
|
-
/**
|
|
1686
|
-
* Pause message delivery for specific topic-partitions on a consumer group.
|
|
1687
|
-
* The consumer remains connected and its committed offsets are preserved —
|
|
1688
|
-
* only polling is suspended. Call `resumeConsumer` to restart delivery.
|
|
1689
|
-
*
|
|
1690
|
-
* @param groupId Consumer group to pause. Defaults to the client's default groupId.
|
|
1691
|
-
* @param assignments Topic-partition pairs to pause.
|
|
1692
|
-
*
|
|
1693
|
-
* @example
|
|
1694
|
-
* ```ts
|
|
1695
|
-
* kafka.pauseConsumer(undefined, [{ topic: 'orders.created', partitions: [0, 1] }]);
|
|
1696
|
-
* // ... do some backpressure work ...
|
|
1697
|
-
* kafka.resumeConsumer(undefined, [{ topic: 'orders.created', partitions: [0, 1] }]);
|
|
1698
|
-
* ```
|
|
1699
|
-
*/
|
|
1700
|
-
pauseConsumer(groupId: string | undefined, assignments: Array<{
|
|
1701
|
-
topic: string;
|
|
1702
|
-
partitions: number[];
|
|
1703
|
-
}>): void;
|
|
1704
|
-
/**
|
|
1705
|
-
* Resume message delivery for previously paused topic-partitions.
|
|
1706
|
-
*
|
|
1707
|
-
* @param groupId Consumer group to resume. Defaults to the client's default groupId.
|
|
1708
|
-
* @param assignments Topic-partition pairs to resume.
|
|
1709
|
-
*
|
|
1710
|
-
* @example
|
|
1711
|
-
* ```ts
|
|
1712
|
-
* kafka.resumeConsumer(undefined, [{ topic: 'orders.created', partitions: [0, 1] }]);
|
|
1713
|
-
* ```
|
|
1714
|
-
*/
|
|
1715
|
-
resumeConsumer(groupId: string | undefined, assignments: Array<{
|
|
1716
|
-
topic: string;
|
|
1717
|
-
partitions: number[];
|
|
1718
|
-
}>): void;
|
|
1719
|
-
/**
|
|
1720
|
-
* Drain in-flight handlers, then disconnect all producers, consumers, and admin.
|
|
1721
|
-
* @param drainTimeoutMs Max ms to wait for in-flight handlers (default 30 000).
|
|
1722
|
-
*
|
|
1723
|
-
* @example
|
|
1724
|
-
* ```ts
|
|
1725
|
-
* await kafka.disconnect();
|
|
1726
|
-
* ```
|
|
1727
|
-
*/
|
|
1728
|
-
disconnect(drainTimeoutMs?: number): Promise<void>;
|
|
1729
|
-
/**
|
|
1730
|
-
* Register SIGTERM / SIGINT signal handlers that drain in-flight messages before
|
|
1731
|
-
* disconnecting. Call once after constructing the client in non-NestJS apps.
|
|
1732
|
-
* NestJS apps get drain automatically via `onModuleDestroy` → `disconnect()`.
|
|
1733
|
-
*
|
|
1734
|
-
* @example
|
|
1735
|
-
* ```ts
|
|
1736
|
-
* kafka.enableGracefulShutdown(['SIGTERM', 'SIGINT'], 30_000);
|
|
1737
|
-
* ```
|
|
1738
|
-
*/
|
|
1739
|
-
enableGracefulShutdown(signals?: NodeJS.Signals[], drainTimeoutMs?: number): void;
|
|
1740
|
-
}
|
|
1741
|
-
/**
|
|
1742
|
-
* Logger interface for KafkaClient.
|
|
1743
|
-
* Compatible with NestJS Logger, console, winston, pino, or any custom logger.
|
|
1744
|
-
*
|
|
1745
|
-
* `debug` is optional — omit it to suppress debug output in production.
|
|
1746
|
-
*
|
|
1747
|
-
* @example
|
|
1748
|
-
* ```ts
|
|
1749
|
-
* // Pass a NestJS logger:
|
|
1750
|
-
* const kafka = new KafkaClient(config, groupId, { logger: this.logger });
|
|
1751
|
-
*
|
|
1752
|
-
* // Or a minimal pino wrapper:
|
|
1753
|
-
* const kafka = new KafkaClient(config, groupId, {
|
|
1754
|
-
* logger: {
|
|
1755
|
-
* log: (msg) => pino.info(msg),
|
|
1756
|
-
* warn: (msg, ...a) => pino.warn(msg, ...a),
|
|
1757
|
-
* error: (msg, ...a) => pino.error(msg, ...a),
|
|
1758
|
-
* debug: (msg, ...a) => pino.debug(msg, ...a),
|
|
1759
|
-
* },
|
|
1760
|
-
* });
|
|
1761
|
-
* ```
|
|
1762
|
-
*/
|
|
1763
|
-
interface KafkaLogger {
|
|
1764
|
-
log(message: string): void;
|
|
1765
|
-
warn(message: string, ...args: any[]): void;
|
|
1766
|
-
error(message: string, ...args: any[]): void;
|
|
1767
|
-
debug?(message: string, ...args: any[]): void;
|
|
1768
|
-
}
|
|
1769
|
-
/**
|
|
1770
|
-
* Context passed to `onTtlExpired` when a message is dropped because it
|
|
1771
|
-
* exceeded `messageTtlMs` and `dlq` is not enabled.
|
|
1772
|
-
*
|
|
1773
|
-
* @example
|
|
1774
|
-
* ```ts
|
|
1775
|
-
* const kafka = new KafkaClient(config, groupId, {
|
|
1776
|
-
* onTtlExpired: ({ topic, ageMs, messageTtlMs }) => {
|
|
1777
|
-
* console.warn(`Message on ${topic} expired: age=${ageMs}ms, ttl=${messageTtlMs}ms`);
|
|
1778
|
-
* },
|
|
1779
|
-
* });
|
|
1780
|
-
* ```
|
|
1781
|
-
*/
|
|
1782
|
-
interface TtlExpiredContext {
|
|
1783
|
-
/** Topic the message was consumed from. */
|
|
1784
|
-
topic: string;
|
|
1785
|
-
/** Actual age of the message in ms at the time it was dropped. */
|
|
1786
|
-
ageMs: number;
|
|
1787
|
-
/** The configured TTL threshold (`messageTtlMs`). */
|
|
1788
|
-
messageTtlMs: number;
|
|
1789
|
-
/** Original Kafka message headers (correlationId, traceparent, etc.). */
|
|
1790
|
-
headers: MessageHeaders;
|
|
1791
|
-
}
|
|
1792
|
-
/**
|
|
1793
|
-
* Context passed to `onMessageLost` when a message is silently dropped
|
|
1794
|
-
* (handler threw and `dlq` is not enabled).
|
|
1795
|
-
*
|
|
1796
|
-
* @example
|
|
1797
|
-
* ```ts
|
|
1798
|
-
* const kafka = new KafkaClient(config, groupId, {
|
|
1799
|
-
* onMessageLost: ({ topic, error, attempt }) => {
|
|
1800
|
-
* alerting.fire('kafka.message-lost', { topic, error: error.message, attempt });
|
|
1801
|
-
* },
|
|
1802
|
-
* });
|
|
1803
|
-
* ```
|
|
1804
|
-
*/
|
|
1805
|
-
interface MessageLostContext {
|
|
1806
|
-
/** Topic the message was consumed from. */
|
|
1807
|
-
topic: string;
|
|
1808
|
-
/** Error that caused the message to be dropped. */
|
|
1809
|
-
error: Error;
|
|
1810
|
-
/** Number of processing attempts (0 = validation failure, before handler ran). */
|
|
1811
|
-
attempt: number;
|
|
1812
|
-
/** Original Kafka message headers (correlationId, traceparent, etc.). */
|
|
1813
|
-
headers: MessageHeaders;
|
|
1814
|
-
}
|
|
1815
|
-
/**
|
|
1816
|
-
* Options for `KafkaClient` constructor.
|
|
1817
|
-
*
|
|
1818
|
-
* @example
|
|
1819
|
-
* ```ts
|
|
1820
|
-
* const kafka = new KafkaClient(kafkaConfig, 'my-service', {
|
|
1821
|
-
* transactionalId: `my-service-tx-${replicaIndex}`,
|
|
1822
|
-
* lagThrottle: { maxLag: 10_000, pollIntervalMs: 3_000 },
|
|
1823
|
-
* clockRecovery: { topics: ['orders.created'] },
|
|
1824
|
-
* onMessageLost: (ctx) => alerting.fire('kafka.message-lost', ctx),
|
|
1825
|
-
* instrumentation: [otelInstrumentation()],
|
|
1826
|
-
* });
|
|
1827
|
-
* ```
|
|
1828
|
-
*/
|
|
1829
|
-
interface KafkaClientOptions {
|
|
1830
|
-
/** Auto-create topics via admin before the first `sendMessage`, `sendBatch`, or `transaction` for each topic. Useful for development — not recommended in production. */
|
|
1831
|
-
autoCreateTopics?: boolean;
|
|
1832
|
-
/** When `true`, string topic keys are validated against any schema previously registered via a TopicDescriptor. Default: `true`. */
|
|
1833
|
-
strictSchemas?: boolean;
|
|
1834
|
-
/** Custom logger. Defaults to console with `[KafkaClient:<clientId>]` prefix. */
|
|
1835
|
-
logger?: KafkaLogger;
|
|
1836
|
-
/** Number of partitions for auto-created topics. Default: `1`. */
|
|
1837
|
-
numPartitions?: number;
|
|
1838
|
-
/** Client-wide instrumentation hooks (e.g. OTel). Applied to both send and consume paths. */
|
|
1839
|
-
instrumentation?: KafkaInstrumentation[];
|
|
1840
|
-
/**
|
|
1841
|
-
* Override the transactional producer ID used by `transaction()`.
|
|
1842
|
-
* Defaults to `${clientId}-tx`.
|
|
1843
|
-
*
|
|
1844
|
-
* The transactional ID must be **unique per producer instance** across the
|
|
1845
|
-
* entire Kafka cluster. Two `KafkaClient` instances with the same ID will
|
|
1846
|
-
* cause Kafka to fence one of the producers — the fenced producer will fail
|
|
1847
|
-
* on the next `transaction()` call. Set a distinct value per replica when
|
|
1848
|
-
* running multiple instances of the same service.
|
|
1849
|
-
*/
|
|
1850
|
-
transactionalId?: string;
|
|
1851
|
-
/**
|
|
1852
|
-
* Called when a message is dropped without being sent to a DLQ.
|
|
1853
|
-
* Fires when the handler throws after all retries, or schema validation fails — and `dlq` is not enabled.
|
|
1854
|
-
* Use this to alert, log to external systems, or trigger fallback logic.
|
|
1855
|
-
*/
|
|
1856
|
-
onMessageLost?: (ctx: MessageLostContext) => void | Promise<void>;
|
|
1857
|
-
/**
|
|
1858
|
-
* Called when a message is dropped due to TTL expiration (`messageTtlMs`).
|
|
1859
|
-
* Fires instead of `onMessageLost` for expired messages when `dlq` is not enabled.
|
|
1860
|
-
* When `dlq: true`, expired messages go to the DLQ and this callback is NOT called.
|
|
1861
|
-
*
|
|
1862
|
-
* **Client-wide fallback**: if `ConsumerOptions.onTtlExpired` is set on the consumer,
|
|
1863
|
-
* it takes precedence over this client-level callback.
|
|
1864
|
-
*/
|
|
1865
|
-
onTtlExpired?: (ctx: TtlExpiredContext) => void | Promise<void>;
|
|
1866
|
-
/**
|
|
1867
|
-
* Called whenever a consumer group rebalance occurs.
|
|
1868
|
-
* - `'assign'` — new partitions were granted to this instance.
|
|
1869
|
-
* - `'revoke'` — partitions were taken away (e.g. another consumer joined).
|
|
1870
|
-
*
|
|
1871
|
-
* Applied to every consumer created by this client. If you need per-consumer
|
|
1872
|
-
* rebalance handling, use separate `KafkaClient` instances.
|
|
1873
|
-
*/
|
|
1874
|
-
onRebalance?: (type: "assign" | "revoke", partitions: Array<{
|
|
1875
|
-
topic: string;
|
|
1876
|
-
partition: number;
|
|
1877
|
-
}>) => void;
|
|
1878
|
-
/**
|
|
1879
|
-
* Recover the Lamport clock from the last message in the given topics on `connectProducer()`.
|
|
1880
|
-
*
|
|
1881
|
-
* On startup the producer creates a short-lived consumer, seeks each partition to its
|
|
1882
|
-
* last message (`highWatermark − 1`), reads the `x-lamport-clock` header, then
|
|
1883
|
-
* initialises `_lamportClock` to the maximum value found. This guarantees monotonic
|
|
1884
|
-
* clock values across restarts without an external store.
|
|
1885
|
-
*
|
|
1886
|
-
* Topics that do not exist or are empty are silently skipped.
|
|
1887
|
-
*/
|
|
1888
|
-
clockRecovery?: {
|
|
1889
|
-
/** Topic names to scan for the highest Lamport clock. */
|
|
1890
|
-
topics: string[];
|
|
1891
|
-
};
|
|
1892
|
-
/**
|
|
1893
|
-
* Delay `sendMessage` / `sendBatch` / `sendTombstone` when the observed lag of a
|
|
1894
|
-
* consumer group exceeds `maxLag`. Resumes immediately when lag drops below the threshold.
|
|
1895
|
-
*
|
|
1896
|
-
* Lag is polled via `getConsumerLag()` every `pollIntervalMs` in the background;
|
|
1897
|
-
* no admin call is made on each individual send.
|
|
1898
|
-
*
|
|
1899
|
-
* When `maxWaitMs` is exceeded the send is unblocked with a warning — this is
|
|
1900
|
-
* best-effort throttling, not hard back-pressure.
|
|
1901
|
-
*
|
|
1902
|
-
* Requires `connectProducer()` to have been called to start the polling loop.
|
|
1903
|
-
*/
|
|
1904
|
-
lagThrottle?: {
|
|
1905
|
-
/** Consumer group whose lag is monitored. Defaults to the client's default group. */
|
|
1906
|
-
groupId?: string;
|
|
1907
|
-
/** Lag threshold (number of messages) above which sends are delayed. */
|
|
1908
|
-
maxLag: number;
|
|
1909
|
-
/** How often to poll `getConsumerLag()`. Default: `5000` ms. */
|
|
1910
|
-
pollIntervalMs?: number;
|
|
1911
|
-
/**
|
|
1912
|
-
* Maximum time (ms) a send will wait while throttled before proceeding anyway.
|
|
1913
|
-
* Default: `30_000` ms.
|
|
1914
|
-
*/
|
|
1915
|
-
maxWaitMs?: number;
|
|
1916
|
-
};
|
|
1917
|
-
/**
|
|
1918
|
-
* Custom transport implementation.
|
|
1919
|
-
*
|
|
1920
|
-
* By default `KafkaClient` uses `ConfluentTransport` which wraps
|
|
1921
|
-
* `@confluentinc/kafka-javascript` (librdkafka). Inject a different
|
|
1922
|
-
* `KafkaTransport` to target an alternative broker library, or to supply
|
|
1923
|
-
* a deterministic fake in unit tests without mocking the confluentinc module.
|
|
1924
|
-
*
|
|
1925
|
-
* @example
|
|
1926
|
-
* ```ts
|
|
1927
|
-
* // In tests — no jest.mock() needed
|
|
1928
|
-
* const kafka = new KafkaClient('svc', 'grp', [], {
|
|
1929
|
-
* transport: new FakeTransport(),
|
|
1930
|
-
* });
|
|
1931
|
-
* ```
|
|
1932
|
-
*/
|
|
1933
|
-
transport?: KafkaTransport;
|
|
1934
|
-
}
|
|
1935
|
-
/**
|
|
1936
|
-
* Options for consumer subscribe retry when topic doesn't exist yet.
|
|
1937
|
-
*
|
|
1938
|
-
* @example
|
|
1939
|
-
* ```ts
|
|
1940
|
-
* await kafka.startConsumer(['orders.created'], handler, {
|
|
1941
|
-
* subscribeRetry: { retries: 10, backoffMs: 2_000 },
|
|
1942
|
-
* });
|
|
1943
|
-
* ```
|
|
1944
|
-
*/
|
|
1945
|
-
interface SubscribeRetryOptions {
|
|
1946
|
-
/** Maximum number of subscribe attempts. Default: `5`. */
|
|
1947
|
-
retries?: number;
|
|
1948
|
-
/** Delay between retries in ms. Default: `5000`. */
|
|
1949
|
-
backoffMs?: number;
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
export { type KafkaMetrics as $, type CheckpointRestoreResult as A, type BatchMessageItem as B, type ClientId as C, type CheckpointResult as D, type CircuitBreakerOptions as E, type CompressionType as F, type GroupId as G, type ConsumerGroupSummary as H, type IKafkaClient as I, type ConsumerHandle as J, type KafkaInstrumentation as K, type ConsumerInterceptor as L, type DeduplicationOptions as M, type DlqReason as N, type DlqReplayOptions as O, type EnvelopeHeaderOptions as P, type EventEnvelope as Q, HEADER_CORRELATION_ID as R, type SchemaLike as S, type TopicMapConstraint as T, HEADER_EVENT_ID as U, HEADER_LAMPORT_CLOCK as V, HEADER_SCHEMA_VERSION as W, HEADER_TIMESTAMP as X, HEADER_TRACEPARENT as Y, type InferSchema as Z, type KafkaLogger as _, type IAdmin as a, type MessageHeaders as a0, type MessageLostContext as a1, type ReadSnapshotOptions as a2, type RestoreCheckpointOptions as a3, type RetryOptions as a4, type RoutingOptions as a5, type SchemaParseContext as a6, type SendOptions as a7, type SubscribeRetryOptions as a8, type TTopicMessageMap as a9, type TopicDescription as aa, type TopicPartitionInfo as ab, type TopicsFrom as ac, type TransactionContext as ad, type TransactionalHandlerContext as ae, type TtlExpiredContext as af, type WindowConsumerOptions as ag, type WindowMeta as ah, buildEnvelopeHeaders as ai, decodeHeaders as aj, extractEnvelope as ak, getEnvelopeContext as al, runWithEnvelopeContext as am, topic as an, type IPartitionWatermarks as b, type IGroupTopicOffsets as c, type IPartitionOffset as d, type IGroupDescription as e, type ITopicMetadata as f, type IConsumer as g, type IConsumerCreationOptions as h, type IConsumerRunConfig as i, type ITopicPartitions as j, type ITopicPartitionOffset as k, type ITopicPartition as l, type IMessage as m, type IProducer as n, type IProducerRecord as o, type ITransaction as p, type IProducerCreationOptions as q, type KafkaTransport as r, type KafkaClientOptions as s, type ConsumerOptions as t, type TopicDescriptor as u, type KafkaHealthResult as v, type BatchMeta as w, type BatchSendOptions as x, type BeforeConsumeResult as y, type CheckpointEntry as z };
|