@drarzter/kafka-client 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -104,8 +104,23 @@ type MessageHeaders = Record<string, string>;
104
104
  interface SendOptions {
105
105
  /** Partition key for message routing. */
106
106
  key?: string;
107
- /** Custom headers attached to the message. */
107
+ /** Custom headers attached to the message (merged with auto-generated envelope headers). */
108
108
  headers?: MessageHeaders;
109
+ /** Override the auto-propagated correlation ID (default: inherited from ALS context or new UUID). */
110
+ correlationId?: string;
111
+ /** Schema version for the payload. Default: `1`. */
112
+ schemaVersion?: number;
113
+ /** Override the auto-generated event ID (UUID v4). */
114
+ eventId?: string;
115
+ }
116
+ /** Shape of each item in a `sendBatch` call. */
117
+ interface BatchMessageItem<V> {
118
+ value: V;
119
+ key?: string;
120
+ headers?: MessageHeaders;
121
+ correlationId?: string;
122
+ schemaVersion?: number;
123
+ eventId?: string;
109
124
  }
110
125
  /** Metadata exposed to batch consumer handlers. */
111
126
  interface BatchMeta {
@@ -149,46 +164,53 @@ interface RetryOptions {
149
164
  /**
150
165
  * Interceptor hooks for consumer message processing.
151
166
  * All methods are optional — implement only what you need.
167
+ *
168
+ * Interceptors are per-consumer. For client-wide hooks (e.g. OTel),
169
+ * use `KafkaInstrumentation` instead.
152
170
  */
153
171
  interface ConsumerInterceptor<T extends TopicMapConstraint<T> = TTopicMessageMap> {
154
172
  /** Called before the message handler. */
155
- before?(message: T[keyof T], topic: string): Promise<void> | void;
173
+ before?(envelope: EventEnvelope<T[keyof T]>): Promise<void> | void;
156
174
  /** Called after the message handler succeeds. */
157
- after?(message: T[keyof T], topic: string): Promise<void> | void;
175
+ after?(envelope: EventEnvelope<T[keyof T]>): Promise<void> | void;
158
176
  /** Called when the message handler throws. */
159
- onError?(message: T[keyof T], topic: string, error: Error): Promise<void> | void;
177
+ onError?(envelope: EventEnvelope<T[keyof T]>, error: Error): Promise<void> | void;
178
+ }
179
+ /**
180
+ * Client-wide instrumentation hooks for both send and consume paths.
181
+ * Use this for cross-cutting concerns like tracing and metrics.
182
+ *
183
+ * @see `otelInstrumentation()` from `@drarzter/kafka-client/otel`
184
+ */
185
+ interface KafkaInstrumentation {
186
+ /** Called before sending — can mutate `headers` (e.g. inject `traceparent`). */
187
+ beforeSend?(topic: string, headers: MessageHeaders): void;
188
+ /** Called after a successful send. */
189
+ afterSend?(topic: string): void;
190
+ /** Called before the consumer handler. Return a cleanup function called after the handler. */
191
+ beforeConsume?(envelope: EventEnvelope<any>): (() => void) | void;
192
+ /** Called when the consumer handler throws. */
193
+ onConsumeError?(envelope: EventEnvelope<any>, error: Error): void;
160
194
  }
161
195
  /** Context passed to the `transaction()` callback with type-safe send methods. */
162
196
  interface TransactionContext<T extends TopicMapConstraint<T>> {
163
197
  send<K extends keyof T>(topic: K, message: T[K], options?: SendOptions): Promise<void>;
164
198
  send<D extends TopicDescriptor<string & keyof T, T[string & keyof T]>>(descriptor: D, message: D["__type"], options?: SendOptions): Promise<void>;
165
- sendBatch<K extends keyof T>(topic: K, messages: Array<{
166
- value: T[K];
167
- key?: string;
168
- headers?: MessageHeaders;
169
- }>): Promise<void>;
170
- sendBatch<D extends TopicDescriptor<string & keyof T, T[string & keyof T]>>(descriptor: D, messages: Array<{
171
- value: D["__type"];
172
- key?: string;
173
- headers?: MessageHeaders;
174
- }>): Promise<void>;
199
+ sendBatch<K extends keyof T>(topic: K, messages: Array<BatchMessageItem<T[K]>>): Promise<void>;
200
+ sendBatch<D extends TopicDescriptor<string & keyof T, T[string & keyof T]>>(descriptor: D, messages: Array<BatchMessageItem<D["__type"]>>): Promise<void>;
175
201
  }
176
202
  /** Interface describing all public methods of the Kafka client. */
177
203
  interface IKafkaClient<T extends TopicMapConstraint<T>> {
178
204
  checkStatus(): Promise<{
179
205
  topics: string[];
180
206
  }>;
181
- startConsumer<K extends Array<keyof T>>(topics: K, handleMessage: (message: T[K[number]], topic: K[number]) => Promise<void>, options?: ConsumerOptions<T>): Promise<void>;
182
- startConsumer<D extends TopicDescriptor<string & keyof T, T[string & keyof T]>>(topics: D[], handleMessage: (message: D["__type"], topic: D["__topic"]) => Promise<void>, options?: ConsumerOptions<T>): Promise<void>;
183
- startBatchConsumer<K extends Array<keyof T>>(topics: K, handleBatch: (messages: T[K[number]][], topic: K[number], meta: BatchMeta) => Promise<void>, options?: ConsumerOptions<T>): Promise<void>;
184
- startBatchConsumer<D extends TopicDescriptor<string & keyof T, T[string & keyof T]>>(topics: D[], handleBatch: (messages: D["__type"][], topic: D["__topic"], meta: BatchMeta) => Promise<void>, options?: ConsumerOptions<T>): Promise<void>;
207
+ startConsumer<K extends Array<keyof T>>(topics: K, handleMessage: (envelope: EventEnvelope<T[K[number]]>) => Promise<void>, options?: ConsumerOptions<T>): Promise<void>;
208
+ startConsumer<D extends TopicDescriptor<string & keyof T, T[string & keyof T]>>(topics: D[], handleMessage: (envelope: EventEnvelope<D["__type"]>) => Promise<void>, options?: ConsumerOptions<T>): Promise<void>;
209
+ startBatchConsumer<K extends Array<keyof T>>(topics: K, handleBatch: (envelopes: EventEnvelope<T[K[number]]>[], meta: BatchMeta) => Promise<void>, options?: ConsumerOptions<T>): Promise<void>;
210
+ 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<void>;
185
211
  stopConsumer(): Promise<void>;
186
212
  sendMessage<K extends keyof T>(topic: K, message: T[K], options?: SendOptions): Promise<void>;
187
- sendBatch<K extends keyof T>(topic: K, messages: Array<{
188
- value: T[K];
189
- key?: string;
190
- headers?: MessageHeaders;
191
- }>): Promise<void>;
213
+ sendBatch<K extends keyof T>(topic: K, messages: Array<BatchMessageItem<T[K]>>): Promise<void>;
192
214
  transaction(fn: (ctx: TransactionContext<T>) => Promise<void>): Promise<void>;
193
215
  getClientId: () => ClientId;
194
216
  disconnect(): Promise<void>;
@@ -212,6 +234,8 @@ interface KafkaClientOptions {
212
234
  logger?: KafkaLogger;
213
235
  /** Number of partitions for auto-created topics. Default: `1`. */
214
236
  numPartitions?: number;
237
+ /** Client-wide instrumentation hooks (e.g. OTel). Applied to both send and consume paths. */
238
+ instrumentation?: KafkaInstrumentation[];
215
239
  }
216
240
  /** Options for consumer subscribe retry when topic doesn't exist yet. */
217
241
  interface SubscribeRetryOptions {
@@ -221,4 +245,75 @@ interface SubscribeRetryOptions {
221
245
  backoffMs?: number;
222
246
  }
223
247
 
224
- export { type BatchMeta as B, type ClientId as C, type GroupId as G, type IKafkaClient as I, type KafkaClientOptions as K, type MessageHeaders as M, type RetryOptions as R, type SchemaLike as S, type TopicMapConstraint as T, type ConsumerOptions as a, type TopicDescriptor as b, type ConsumerInterceptor as c, type InferSchema as d, type KafkaLogger as e, type SendOptions as f, type SubscribeRetryOptions as g, type TTopicMessageMap as h, type TopicsFrom as i, type TransactionContext as j, topic as t };
248
+ declare const HEADER_EVENT_ID = "x-event-id";
249
+ declare const HEADER_CORRELATION_ID = "x-correlation-id";
250
+ declare const HEADER_TIMESTAMP = "x-timestamp";
251
+ declare const HEADER_SCHEMA_VERSION = "x-schema-version";
252
+ declare const HEADER_TRACEPARENT = "traceparent";
253
+ /**
254
+ * Typed wrapper combining a parsed message payload with Kafka metadata
255
+ * and envelope headers.
256
+ *
257
+ * On **send**, the library auto-generates envelope headers
258
+ * (`x-event-id`, `x-correlation-id`, `x-timestamp`, `x-schema-version`).
259
+ *
260
+ * On **consume**, the library extracts those headers and assembles
261
+ * an `EventEnvelope` that is passed to the handler.
262
+ */
263
+ interface EventEnvelope<T> {
264
+ /** Deserialized + validated message body. */
265
+ payload: T;
266
+ /** Topic the message was produced to / consumed from. */
267
+ topic: string;
268
+ /** Kafka partition (consume-side only, `-1` on send). */
269
+ partition: number;
270
+ /** Kafka offset (consume-side only, empty string on send). */
271
+ offset: string;
272
+ /** ISO-8601 timestamp set by the producer. */
273
+ timestamp: string;
274
+ /** Unique ID for this event (UUID v4). */
275
+ eventId: string;
276
+ /** Correlation ID — auto-propagated via AsyncLocalStorage. */
277
+ correlationId: string;
278
+ /** Schema version of the payload. */
279
+ schemaVersion: number;
280
+ /** W3C Trace Context `traceparent` header (set by OTel instrumentation). */
281
+ traceparent?: string;
282
+ /** All decoded Kafka headers for extensibility. */
283
+ headers: MessageHeaders;
284
+ }
285
+ interface EnvelopeCtx {
286
+ correlationId: string;
287
+ traceparent?: string;
288
+ }
289
+ /** Read the current envelope context (correlationId / traceparent) from ALS. */
290
+ declare function getEnvelopeContext(): EnvelopeCtx | undefined;
291
+ /** Execute `fn` inside an envelope context so nested sends inherit correlationId. */
292
+ declare function runWithEnvelopeContext<R>(ctx: EnvelopeCtx, fn: () => R): R;
293
+ /** Options accepted by `buildEnvelopeHeaders`. */
294
+ interface EnvelopeHeaderOptions {
295
+ correlationId?: string;
296
+ schemaVersion?: number;
297
+ eventId?: string;
298
+ headers?: MessageHeaders;
299
+ }
300
+ /**
301
+ * Generate envelope headers for the send path.
302
+ *
303
+ * Priority for `correlationId`:
304
+ * explicit option → ALS context → new UUID.
305
+ */
306
+ declare function buildEnvelopeHeaders(options?: EnvelopeHeaderOptions): MessageHeaders;
307
+ /**
308
+ * Decode kafkajs headers (`Record<string, Buffer | string | undefined>`)
309
+ * into plain `Record<string, string>`.
310
+ */
311
+ declare function decodeHeaders(raw: Record<string, Buffer | string | (Buffer | string)[] | undefined> | undefined): MessageHeaders;
312
+ /**
313
+ * Build an `EventEnvelope` from a consumed kafkajs message.
314
+ * Tolerates missing envelope headers — generates defaults so messages
315
+ * from non-envelope producers still work.
316
+ */
317
+ declare function extractEnvelope<T>(payload: T, headers: MessageHeaders, topic: string, partition: number, offset: string): EventEnvelope<T>;
318
+
319
+ export { type BatchMessageItem as B, type ClientId as C, type EnvelopeHeaderOptions as E, type GroupId as G, HEADER_CORRELATION_ID as H, type IKafkaClient as I, type KafkaInstrumentation as K, type MessageHeaders as M, type RetryOptions as R, type SchemaLike as S, type TopicMapConstraint as T, type ConsumerOptions as a, type TopicDescriptor as b, type BatchMeta as c, type ConsumerInterceptor as d, type EventEnvelope as e, HEADER_EVENT_ID as f, HEADER_SCHEMA_VERSION as g, HEADER_TIMESTAMP as h, HEADER_TRACEPARENT as i, type InferSchema as j, type KafkaClientOptions as k, type KafkaLogger as l, type SendOptions as m, type SubscribeRetryOptions as n, type TTopicMessageMap as o, type TopicsFrom as p, type TransactionContext as q, buildEnvelopeHeaders as r, decodeHeaders as s, extractEnvelope as t, getEnvelopeContext as u, runWithEnvelopeContext as v, topic as w };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { KafkaClient } from './core.mjs';
2
2
  export { KafkaProcessingError, KafkaRetryExhaustedError, KafkaValidationError } from './core.mjs';
3
- import { T as TopicMapConstraint, C as ClientId, G as GroupId, S as SchemaLike, a as ConsumerOptions, b as TopicDescriptor } from './types-CtwJihJ3.mjs';
4
- export { B as BatchMeta, c as ConsumerInterceptor, I as IKafkaClient, d as InferSchema, K as KafkaClientOptions, e as KafkaLogger, M as MessageHeaders, R as RetryOptions, f as SendOptions, g as SubscribeRetryOptions, h as TTopicMessageMap, i as TopicsFrom, j as TransactionContext, t as topic } from './types-CtwJihJ3.mjs';
3
+ import { T as TopicMapConstraint, C as ClientId, G as GroupId, K as KafkaInstrumentation, S as SchemaLike, a as ConsumerOptions, b as TopicDescriptor } from './envelope-QK1trQu4.mjs';
4
+ export { B as BatchMessageItem, c as BatchMeta, d as ConsumerInterceptor, E as EnvelopeHeaderOptions, e as EventEnvelope, H as HEADER_CORRELATION_ID, f as HEADER_EVENT_ID, g as HEADER_SCHEMA_VERSION, h as HEADER_TIMESTAMP, i as HEADER_TRACEPARENT, I as IKafkaClient, j as InferSchema, k as KafkaClientOptions, l as KafkaLogger, M as MessageHeaders, R as RetryOptions, m as SendOptions, n as SubscribeRetryOptions, o as TTopicMessageMap, p as TopicsFrom, q as TransactionContext, r as buildEnvelopeHeaders, s as decodeHeaders, t as extractEnvelope, u as getEnvelopeContext, v as runWithEnvelopeContext, w as topic } from './envelope-QK1trQu4.mjs';
5
5
  import { DynamicModule, OnModuleInit } from '@nestjs/common';
6
6
  import { DiscoveryService, ModuleRef } from '@nestjs/core';
7
7
 
@@ -19,6 +19,12 @@ interface KafkaModuleOptions {
19
19
  isGlobal?: boolean;
20
20
  /** Auto-create topics via admin on first use (send/consume). Useful for development. */
21
21
  autoCreateTopics?: boolean;
22
+ /** When `true`, string topic keys are validated against any schema previously registered via a TopicDescriptor. Default: `true`. */
23
+ strictSchemas?: boolean;
24
+ /** Number of partitions for auto-created topics. Default: `1`. */
25
+ numPartitions?: number;
26
+ /** Client-wide instrumentation hooks (e.g. OTel). Applied to both send and consume paths. */
27
+ instrumentation?: KafkaInstrumentation[];
22
28
  }
23
29
  /** Async configuration for `KafkaModule.registerAsync()` with dependency injection. */
24
30
  interface KafkaModuleAsyncOptions {
@@ -88,4 +94,4 @@ declare class KafkaHealthIndicator {
88
94
  check<T extends TopicMapConstraint<T>>(client: KafkaClient<T>): Promise<KafkaHealthResult>;
89
95
  }
90
96
 
91
- export { ClientId, ConsumerOptions, GroupId, InjectKafkaClient, KAFKA_CLIENT, KAFKA_SUBSCRIBER_METADATA, KafkaClient, KafkaExplorer, KafkaHealthIndicator, type KafkaHealthResult, KafkaModule, type KafkaModuleAsyncOptions, type KafkaModuleOptions, type KafkaSubscriberMetadata, SchemaLike, SubscribeTo, TopicDescriptor, TopicMapConstraint, getKafkaClientToken };
97
+ export { ClientId, ConsumerOptions, GroupId, InjectKafkaClient, KAFKA_CLIENT, KAFKA_SUBSCRIBER_METADATA, KafkaClient, KafkaExplorer, KafkaHealthIndicator, type KafkaHealthResult, KafkaInstrumentation, KafkaModule, type KafkaModuleAsyncOptions, type KafkaModuleOptions, type KafkaSubscriberMetadata, SchemaLike, SubscribeTo, TopicDescriptor, TopicMapConstraint, getKafkaClientToken };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { KafkaClient } from './core.js';
2
2
  export { KafkaProcessingError, KafkaRetryExhaustedError, KafkaValidationError } from './core.js';
3
- import { T as TopicMapConstraint, C as ClientId, G as GroupId, S as SchemaLike, a as ConsumerOptions, b as TopicDescriptor } from './types-CtwJihJ3.js';
4
- export { B as BatchMeta, c as ConsumerInterceptor, I as IKafkaClient, d as InferSchema, K as KafkaClientOptions, e as KafkaLogger, M as MessageHeaders, R as RetryOptions, f as SendOptions, g as SubscribeRetryOptions, h as TTopicMessageMap, i as TopicsFrom, j as TransactionContext, t as topic } from './types-CtwJihJ3.js';
3
+ import { T as TopicMapConstraint, C as ClientId, G as GroupId, K as KafkaInstrumentation, S as SchemaLike, a as ConsumerOptions, b as TopicDescriptor } from './envelope-QK1trQu4.js';
4
+ export { B as BatchMessageItem, c as BatchMeta, d as ConsumerInterceptor, E as EnvelopeHeaderOptions, e as EventEnvelope, H as HEADER_CORRELATION_ID, f as HEADER_EVENT_ID, g as HEADER_SCHEMA_VERSION, h as HEADER_TIMESTAMP, i as HEADER_TRACEPARENT, I as IKafkaClient, j as InferSchema, k as KafkaClientOptions, l as KafkaLogger, M as MessageHeaders, R as RetryOptions, m as SendOptions, n as SubscribeRetryOptions, o as TTopicMessageMap, p as TopicsFrom, q as TransactionContext, r as buildEnvelopeHeaders, s as decodeHeaders, t as extractEnvelope, u as getEnvelopeContext, v as runWithEnvelopeContext, w as topic } from './envelope-QK1trQu4.js';
5
5
  import { DynamicModule, OnModuleInit } from '@nestjs/common';
6
6
  import { DiscoveryService, ModuleRef } from '@nestjs/core';
7
7
 
@@ -19,6 +19,12 @@ interface KafkaModuleOptions {
19
19
  isGlobal?: boolean;
20
20
  /** Auto-create topics via admin on first use (send/consume). Useful for development. */
21
21
  autoCreateTopics?: boolean;
22
+ /** When `true`, string topic keys are validated against any schema previously registered via a TopicDescriptor. Default: `true`. */
23
+ strictSchemas?: boolean;
24
+ /** Number of partitions for auto-created topics. Default: `1`. */
25
+ numPartitions?: number;
26
+ /** Client-wide instrumentation hooks (e.g. OTel). Applied to both send and consume paths. */
27
+ instrumentation?: KafkaInstrumentation[];
22
28
  }
23
29
  /** Async configuration for `KafkaModule.registerAsync()` with dependency injection. */
24
30
  interface KafkaModuleAsyncOptions {
@@ -88,4 +94,4 @@ declare class KafkaHealthIndicator {
88
94
  check<T extends TopicMapConstraint<T>>(client: KafkaClient<T>): Promise<KafkaHealthResult>;
89
95
  }
90
96
 
91
- export { ClientId, ConsumerOptions, GroupId, InjectKafkaClient, KAFKA_CLIENT, KAFKA_SUBSCRIBER_METADATA, KafkaClient, KafkaExplorer, KafkaHealthIndicator, type KafkaHealthResult, KafkaModule, type KafkaModuleAsyncOptions, type KafkaModuleOptions, type KafkaSubscriberMetadata, SchemaLike, SubscribeTo, TopicDescriptor, TopicMapConstraint, getKafkaClientToken };
97
+ export { ClientId, ConsumerOptions, GroupId, InjectKafkaClient, KAFKA_CLIENT, KAFKA_SUBSCRIBER_METADATA, KafkaClient, KafkaExplorer, KafkaHealthIndicator, type KafkaHealthResult, KafkaInstrumentation, KafkaModule, type KafkaModuleAsyncOptions, type KafkaModuleOptions, type KafkaSubscriberMetadata, SchemaLike, SubscribeTo, TopicDescriptor, TopicMapConstraint, getKafkaClientToken };