@drarzter/kafka-client 0.9.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +625 -8
  2. package/dist/chunk-CMO7SMVK.mjs +4814 -0
  3. package/dist/chunk-CMO7SMVK.mjs.map +1 -0
  4. package/dist/cli/dlq.d.ts +119 -0
  5. package/dist/cli/dlq.d.ts.map +1 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/{chunk-TPIP5VV7.mjs → cli/index.js} +965 -265
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/index.mjs +355 -0
  11. package/dist/cli/index.mjs.map +1 -0
  12. package/dist/client/config/from-env.d.ts +188 -0
  13. package/dist/client/config/from-env.d.ts.map +1 -0
  14. package/dist/client/config/index.d.ts +2 -0
  15. package/dist/client/config/index.d.ts.map +1 -0
  16. package/dist/client/errors.d.ts +67 -0
  17. package/dist/client/errors.d.ts.map +1 -0
  18. package/dist/client/kafka.client/admin/ops.d.ts +114 -0
  19. package/dist/client/kafka.client/admin/ops.d.ts.map +1 -0
  20. package/dist/client/kafka.client/consumer/features/delayed.d.ts +24 -0
  21. package/dist/client/kafka.client/consumer/features/delayed.d.ts.map +1 -0
  22. package/dist/client/kafka.client/consumer/features/dlq-replay.d.ts +52 -0
  23. package/dist/client/kafka.client/consumer/features/dlq-replay.d.ts.map +1 -0
  24. package/dist/client/kafka.client/consumer/features/routed.d.ts +4 -0
  25. package/dist/client/kafka.client/consumer/features/routed.d.ts.map +1 -0
  26. package/dist/client/kafka.client/consumer/features/snapshot.d.ts +10 -0
  27. package/dist/client/kafka.client/consumer/features/snapshot.d.ts.map +1 -0
  28. package/dist/client/kafka.client/consumer/features/window.d.ts +5 -0
  29. package/dist/client/kafka.client/consumer/features/window.d.ts.map +1 -0
  30. package/dist/client/kafka.client/consumer/handler.d.ts +149 -0
  31. package/dist/client/kafka.client/consumer/handler.d.ts.map +1 -0
  32. package/dist/client/kafka.client/consumer/ops.d.ts +51 -0
  33. package/dist/client/kafka.client/consumer/ops.d.ts.map +1 -0
  34. package/dist/client/kafka.client/consumer/pipeline.d.ts +167 -0
  35. package/dist/client/kafka.client/consumer/pipeline.d.ts.map +1 -0
  36. package/dist/client/kafka.client/consumer/queue.d.ts +37 -0
  37. package/dist/client/kafka.client/consumer/queue.d.ts.map +1 -0
  38. package/dist/client/kafka.client/consumer/retry-topic.d.ts +65 -0
  39. package/dist/client/kafka.client/consumer/retry-topic.d.ts.map +1 -0
  40. package/dist/client/kafka.client/consumer/setup.d.ts +63 -0
  41. package/dist/client/kafka.client/consumer/setup.d.ts.map +1 -0
  42. package/dist/client/kafka.client/consumer/start.d.ts +7 -0
  43. package/dist/client/kafka.client/consumer/start.d.ts.map +1 -0
  44. package/dist/client/kafka.client/consumer/stop.d.ts +19 -0
  45. package/dist/client/kafka.client/consumer/stop.d.ts.map +1 -0
  46. package/dist/client/kafka.client/consumer/subscribe-retry.d.ts +4 -0
  47. package/dist/client/kafka.client/consumer/subscribe-retry.d.ts.map +1 -0
  48. package/dist/client/kafka.client/context.d.ts +72 -0
  49. package/dist/client/kafka.client/context.d.ts.map +1 -0
  50. package/dist/client/kafka.client/index.d.ts +155 -0
  51. package/dist/client/kafka.client/index.d.ts.map +1 -0
  52. package/dist/client/kafka.client/infra/circuit-breaker.manager.d.ts +61 -0
  53. package/dist/client/kafka.client/infra/circuit-breaker.manager.d.ts.map +1 -0
  54. package/dist/client/kafka.client/infra/dedup.store.d.ts +28 -0
  55. package/dist/client/kafka.client/infra/dedup.store.d.ts.map +1 -0
  56. package/dist/client/kafka.client/infra/inflight.tracker.d.ts +22 -0
  57. package/dist/client/kafka.client/infra/inflight.tracker.d.ts.map +1 -0
  58. package/dist/client/kafka.client/infra/metrics.manager.d.ts +67 -0
  59. package/dist/client/kafka.client/infra/metrics.manager.d.ts.map +1 -0
  60. package/dist/client/kafka.client/producer/lifecycle.d.ts +41 -0
  61. package/dist/client/kafka.client/producer/lifecycle.d.ts.map +1 -0
  62. package/dist/client/kafka.client/producer/ops.d.ts +70 -0
  63. package/dist/client/kafka.client/producer/ops.d.ts.map +1 -0
  64. package/dist/client/kafka.client/producer/send.d.ts +21 -0
  65. package/dist/client/kafka.client/producer/send.d.ts.map +1 -0
  66. package/dist/client/kafka.client/validate-options.d.ts +11 -0
  67. package/dist/client/kafka.client/validate-options.d.ts.map +1 -0
  68. package/dist/client/message/envelope.d.ts +105 -0
  69. package/dist/client/message/envelope.d.ts.map +1 -0
  70. package/dist/client/message/schema-registry.d.ts +105 -0
  71. package/dist/client/message/schema-registry.d.ts.map +1 -0
  72. package/dist/client/message/topic.d.ts +138 -0
  73. package/dist/client/message/topic.d.ts.map +1 -0
  74. package/dist/client/message/versioned-schema.d.ts +53 -0
  75. package/dist/client/message/versioned-schema.d.ts.map +1 -0
  76. package/dist/client/outbox/index.d.ts +4 -0
  77. package/dist/client/outbox/index.d.ts.map +1 -0
  78. package/dist/client/outbox/outbox.relay.d.ts +90 -0
  79. package/dist/client/outbox/outbox.relay.d.ts.map +1 -0
  80. package/dist/client/outbox/outbox.store.d.ts +42 -0
  81. package/dist/client/outbox/outbox.store.d.ts.map +1 -0
  82. package/dist/client/outbox/outbox.types.d.ts +144 -0
  83. package/dist/client/outbox/outbox.types.d.ts.map +1 -0
  84. package/dist/client/security/acl.d.ts +108 -0
  85. package/dist/client/security/acl.d.ts.map +1 -0
  86. package/dist/client/security/index.d.ts +5 -0
  87. package/dist/client/security/index.d.ts.map +1 -0
  88. package/dist/client/security/providers.d.ts +88 -0
  89. package/dist/client/security/providers.d.ts.map +1 -0
  90. package/dist/client/security/resolve-security.d.ts +19 -0
  91. package/dist/client/security/resolve-security.d.ts.map +1 -0
  92. package/dist/client/security/security.types.d.ts +76 -0
  93. package/dist/client/security/security.types.d.ts.map +1 -0
  94. package/dist/client/transport/confluent.transport.d.ts +32 -0
  95. package/dist/client/transport/confluent.transport.d.ts.map +1 -0
  96. package/dist/client/transport/transport.interface.d.ts +216 -0
  97. package/dist/client/transport/transport.interface.d.ts.map +1 -0
  98. package/dist/client/types/admin.interface.d.ts +174 -0
  99. package/dist/client/types/admin.interface.d.ts.map +1 -0
  100. package/dist/client/types/admin.types.d.ts +140 -0
  101. package/dist/client/types/admin.types.d.ts.map +1 -0
  102. package/dist/client/types/client.d.ts +21 -0
  103. package/dist/client/types/client.d.ts.map +1 -0
  104. package/dist/client/types/common.d.ts +84 -0
  105. package/dist/client/types/common.d.ts.map +1 -0
  106. package/dist/client/types/config.types.d.ts +150 -0
  107. package/dist/client/types/config.types.d.ts.map +1 -0
  108. package/dist/client/types/consumer.interface.d.ts +115 -0
  109. package/dist/client/types/consumer.interface.d.ts.map +1 -0
  110. package/dist/{consumer.types-fFCag3VJ.d.mts → client/types/consumer.types.d.ts} +62 -383
  111. package/dist/client/types/consumer.types.d.ts.map +1 -0
  112. package/dist/client/types/dedup.types.d.ts +50 -0
  113. package/dist/client/types/dedup.types.d.ts.map +1 -0
  114. package/dist/client/types/lifecycle.interface.d.ts +72 -0
  115. package/dist/client/types/lifecycle.interface.d.ts.map +1 -0
  116. package/dist/client/types/producer.interface.d.ts +52 -0
  117. package/dist/client/types/producer.interface.d.ts.map +1 -0
  118. package/dist/client/types/producer.types.d.ts +90 -0
  119. package/dist/client/types/producer.types.d.ts.map +1 -0
  120. package/dist/client/types.d.ts +8 -0
  121. package/dist/client/types.d.ts.map +1 -0
  122. package/dist/core.d.ts +10 -314
  123. package/dist/core.d.ts.map +1 -0
  124. package/dist/core.js +1326 -74
  125. package/dist/core.js.map +1 -1
  126. package/dist/core.mjs +39 -3
  127. package/dist/index.d.ts +7 -128
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +1343 -74
  130. package/dist/index.js.map +1 -1
  131. package/dist/index.mjs +56 -3
  132. package/dist/index.mjs.map +1 -1
  133. package/dist/nest/kafka.constants.d.ts +5 -0
  134. package/dist/nest/kafka.constants.d.ts.map +1 -0
  135. package/dist/nest/kafka.decorator.d.ts +49 -0
  136. package/dist/nest/kafka.decorator.d.ts.map +1 -0
  137. package/dist/nest/kafka.explorer.d.ts +17 -0
  138. package/dist/nest/kafka.explorer.d.ts.map +1 -0
  139. package/dist/nest/kafka.health.d.ts +7 -0
  140. package/dist/nest/kafka.health.d.ts.map +1 -0
  141. package/dist/nest/kafka.module.d.ts +61 -0
  142. package/dist/nest/kafka.module.d.ts.map +1 -0
  143. package/dist/otel.d.ts +83 -5
  144. package/dist/otel.d.ts.map +1 -0
  145. package/dist/otel.js +100 -6
  146. package/dist/otel.js.map +1 -1
  147. package/dist/otel.mjs +98 -5
  148. package/dist/otel.mjs.map +1 -1
  149. package/dist/testing/client.mock.d.ts +47 -0
  150. package/dist/testing/client.mock.d.ts.map +1 -0
  151. package/dist/testing/index.d.ts +4 -0
  152. package/dist/testing/index.d.ts.map +1 -0
  153. package/dist/testing/test.container.d.ts +63 -0
  154. package/dist/testing/test.container.d.ts.map +1 -0
  155. package/dist/{testing.d.mts → testing/transport.fake.d.ts} +7 -111
  156. package/dist/testing/transport.fake.d.ts.map +1 -0
  157. package/dist/testing.d.ts +2 -318
  158. package/dist/testing.d.ts.map +1 -0
  159. package/dist/testing.js +28 -2
  160. package/dist/testing.js.map +1 -1
  161. package/dist/testing.mjs +28 -2
  162. package/dist/testing.mjs.map +1 -1
  163. package/package.json +22 -9
  164. package/dist/chunk-TPIP5VV7.mjs.map +0 -1
  165. package/dist/client-CBBUDDtu.d.ts +0 -751
  166. package/dist/client-D-SxYV2b.d.mts +0 -751
  167. package/dist/consumer.types-fFCag3VJ.d.ts +0 -958
  168. package/dist/core.d.mts +0 -314
  169. package/dist/index.d.mts +0 -128
  170. package/dist/otel.d.mts +0 -27
package/dist/testing.d.ts CHANGED
@@ -1,318 +1,2 @@
1
- import { T as TopicMapConstraint } from './consumer.types-fFCag3VJ.js';
2
- import { I as IKafkaClient, a as IAdmin, b as IPartitionWatermarks, c as IGroupTopicOffsets, d as IPartitionOffset, e as IGroupDescription, f as ITopicMetadata, g as IConsumer, h as IConsumerCreationOptions, i as IConsumerRunConfig, j as ITopicPartitions, k as ITopicPartitionOffset, l as ITopicPartition, m as IMessage, n as IProducer, o as IProducerRecord, p as ITransaction, q as IProducerCreationOptions, K as KafkaTransport } from './client-CBBUDDtu.js';
3
-
4
- /**
5
- * Fully typed mock of `IKafkaClient<T>` where every method is a mock function.
6
- * Compatible with Jest, Vitest, or any framework whose `fn()` returns
7
- * an object with `.mock`, `.mockResolvedValue`, etc.
8
- */
9
- type MockKafkaClient<T extends TopicMapConstraint<T>> = {
10
- [K in keyof IKafkaClient<T>]: IKafkaClient<T>[K] & Record<string, any>;
11
- };
12
- /** Factory that creates a no-op mock function (e.g. `() => jest.fn()`). */
13
- type MockFactory = () => (...args: any[]) => any;
14
- /**
15
- * Create a fully typed mock implementing every `IKafkaClient<T>` method.
16
- * Useful for unit-testing services that depend on `KafkaClient` without
17
- * touching a real broker.
18
- *
19
- * Auto-detects Jest (`jest.fn()`) or Vitest (`vi.fn()`). Pass a custom
20
- * `mockFactory` for other frameworks.
21
- *
22
- * All methods resolve to sensible defaults:
23
- * - `checkStatus()` → `{ status: 'up', clientId: 'mock-client', topics: [] }`
24
- * - `getClientId()` → `"mock-client"`
25
- * - void methods → `undefined`
26
- *
27
- * @example
28
- * ```ts
29
- * const kafka = createMockKafkaClient<MyTopics>();
30
- *
31
- * const service = new OrdersService(kafka);
32
- * await service.createOrder();
33
- *
34
- * expect(kafka.sendMessage).toHaveBeenCalledWith(
35
- * 'order.created',
36
- * expect.objectContaining({ orderId: '123' }),
37
- * );
38
- * ```
39
- */
40
- declare function createMockKafkaClient<T extends TopicMapConstraint<T>>(mockFactory?: MockFactory): MockKafkaClient<T>;
41
-
42
- /** Options for `KafkaTestContainer`. */
43
- interface KafkaTestContainerOptions {
44
- /** Docker image. Default: `"confluentinc/cp-kafka:7.7.0"`. */
45
- image?: string;
46
- /** Warm up the transactional coordinator on start. Default: `true`. */
47
- transactionWarmup?: boolean;
48
- /** Topics to pre-create. Each entry can be a string (1 partition) or `{ topic, numPartitions }`. */
49
- topics?: Array<string | {
50
- topic: string;
51
- numPartitions?: number;
52
- }>;
53
- }
54
- /**
55
- * Thin wrapper around `@testcontainers/kafka` that starts a single-node
56
- * KRaft Kafka container and exposes `brokers` for use with `KafkaClient`.
57
- *
58
- * Handles common setup pain points:
59
- * - Transaction coordinator warmup (avoids transactional producer hangs)
60
- * - Topic pre-creation (avoids race conditions)
61
- *
62
- * @example
63
- * ```ts
64
- * const container = new KafkaTestContainer({ topics: ['orders', 'payments'] });
65
- * const brokers = await container.start();
66
- *
67
- * const kafka = new KafkaClient('test', 'test-group', brokers);
68
- * // ... run tests ...
69
- *
70
- * await container.stop();
71
- * ```
72
- *
73
- * @example Jest lifecycle
74
- * ```ts
75
- * let container: KafkaTestContainer;
76
- * let brokers: string[];
77
- *
78
- * beforeAll(async () => {
79
- * container = new KafkaTestContainer({ topics: ['orders'] });
80
- * brokers = await container.start();
81
- * }, 120_000);
82
- *
83
- * afterAll(() => container.stop());
84
- * ```
85
- */
86
- declare class KafkaTestContainer {
87
- private container;
88
- private readonly image;
89
- private readonly transactionWarmup;
90
- private readonly topics;
91
- constructor(options?: KafkaTestContainerOptions);
92
- /**
93
- * Start the Kafka container, pre-create topics, and optionally warm up
94
- * the transaction coordinator.
95
- *
96
- * @returns Broker connection strings, e.g. `["localhost:55123"]`.
97
- */
98
- start(): Promise<string[]>;
99
- /** Stop and remove the container. */
100
- stop(): Promise<void>;
101
- /** Broker connection strings. Throws if container is not started. */
102
- get brokers(): string[];
103
- }
104
-
105
- /**
106
- * An in-memory Kafka transaction.
107
- * Staged sends are visible in `staged`; committed sends are flushed
108
- * to the owning `FakeProducer.sent` on `commit()`.
109
- */
110
- declare class FakeTransaction implements ITransaction {
111
- private readonly producer;
112
- /** Records staged within this transaction (not yet committed). */
113
- readonly staged: IProducerRecord[];
114
- /** True after `commit()` was called. */
115
- committed: boolean;
116
- /** True after `abort()` was called. */
117
- aborted: boolean;
118
- /** sendOffsets calls (for EOS assertions). */
119
- readonly offsetsCommitted: Array<{
120
- consumer: IConsumer;
121
- topics: Array<{
122
- topic: string;
123
- partitions: IPartitionOffset[];
124
- }>;
125
- }>;
126
- constructor(producer: FakeProducer);
127
- send(record: IProducerRecord): Promise<void>;
128
- sendOffsets(options: {
129
- consumer: IConsumer;
130
- topics: Array<{
131
- topic: string;
132
- partitions: Array<{
133
- partition: number;
134
- offset: string;
135
- }>;
136
- }>;
137
- }): Promise<void>;
138
- commit(): Promise<void>;
139
- abort(): Promise<void>;
140
- }
141
- /**
142
- * In-memory producer. All `send()` calls are captured in `sent`.
143
- * Transactions are backed by `FakeTransaction`.
144
- */
145
- declare class FakeProducer implements IProducer {
146
- /** All records delivered via `send()` (direct + committed transactions). */
147
- readonly sent: IProducerRecord[];
148
- /** All transactions opened via `transaction()`. */
149
- readonly transactions: FakeTransaction[];
150
- readonly options: IProducerCreationOptions | undefined;
151
- connected: boolean;
152
- constructor(options?: IProducerCreationOptions);
153
- connect(): Promise<void>;
154
- disconnect(): Promise<void>;
155
- send(record: IProducerRecord): Promise<void>;
156
- transaction(): Promise<ITransaction>;
157
- /** Return the last committed transaction, or throw if none exist. */
158
- get lastTransaction(): FakeTransaction;
159
- /** All topic names that received at least one message. */
160
- sentTopics(): string[];
161
- /** All messages sent to a specific topic. */
162
- sentTo(topic: string): IProducerRecord["messages"];
163
- }
164
- /**
165
- * In-memory consumer.
166
- * Call `deliver(topic, message)` from your test to push messages through
167
- * the `eachMessage` handler without a real broker.
168
- */
169
- declare class FakeConsumer implements IConsumer {
170
- readonly groupId: string;
171
- readonly fromBeginning: boolean;
172
- /** Topics subscribed via `subscribe()`. */
173
- readonly subscribed: string[];
174
- private _runConfig;
175
- private _assignments;
176
- readonly pausedTopics: Set<string>;
177
- connected: boolean;
178
- private readonly onRebalance;
179
- constructor(options: IConsumerCreationOptions);
180
- connect(): Promise<void>;
181
- disconnect(): Promise<void>;
182
- subscribe(options: {
183
- topics: (string | RegExp)[];
184
- }): Promise<void>;
185
- run(config: IConsumerRunConfig): Promise<void>;
186
- pause(assignments: ITopicPartitions[]): void;
187
- resume(assignments: ITopicPartitions[]): void;
188
- seek(_options: ITopicPartitionOffset): void;
189
- assignment(): ITopicPartition[];
190
- commitOffsets(_offsets: ITopicPartitionOffset[]): Promise<void>;
191
- stop(): Promise<void>;
192
- /**
193
- * Push a message through the `eachMessage` handler.
194
- * Throws if `run()` has not been called yet.
195
- */
196
- deliver(topic: string, message: Partial<IMessage> & {
197
- value: Buffer | null;
198
- }, partition?: number, offset?: string): Promise<void>;
199
- /**
200
- * Simulate a partition-assign rebalance event.
201
- * Useful for testing onRebalance callbacks.
202
- */
203
- triggerRebalance(type: "assign" | "revoke", assignments: ITopicPartition[]): void;
204
- /** Whether `run()` has been called (consumer is active). */
205
- get isRunning(): boolean;
206
- }
207
- /**
208
- * In-memory admin client.
209
- * Pre-populate `topicOffsets`, `groupOffsets`, and `existingTopics`
210
- * to control what admin queries return.
211
- */
212
- declare class FakeAdmin implements IAdmin {
213
- /** Topics returned by `listTopics()`. Add to this from your test. */
214
- readonly existingTopics: string[];
215
- /** Per-topic partition watermarks returned by `fetchTopicOffsets()`. */
216
- readonly topicOffsets: Map<string, IPartitionWatermarks[]>;
217
- /** Per-groupId committed offsets returned by `fetchOffsets()`. */
218
- readonly groupOffsets: Map<string, IGroupTopicOffsets[]>;
219
- /** Calls captured by `setOffsets()` — inspect in tests. */
220
- readonly setOffsetsCalls: Array<{
221
- groupId: string;
222
- topic: string;
223
- partitions: IPartitionOffset[];
224
- }>;
225
- /** Group IDs deleted via `deleteGroups()`. */
226
- readonly deletedGroups: string[];
227
- /** Records deleted via `deleteTopicRecords()`. */
228
- readonly deletedRecords: Array<{
229
- topic: string;
230
- partitions: IPartitionOffset[];
231
- }>;
232
- connected: boolean;
233
- connect(): Promise<void>;
234
- disconnect(): Promise<void>;
235
- createTopics(options: {
236
- topics: Array<{
237
- topic: string;
238
- numPartitions: number;
239
- }>;
240
- }): Promise<void>;
241
- fetchTopicOffsets(topic: string): Promise<IPartitionWatermarks[]>;
242
- fetchTopicOffsetsByTimestamp(_topic: string, _timestamp: number): Promise<IPartitionOffset[]>;
243
- fetchOffsets(options: {
244
- groupId: string;
245
- }): Promise<IGroupTopicOffsets[]>;
246
- setOffsets(options: {
247
- groupId: string;
248
- topic: string;
249
- partitions: IPartitionOffset[];
250
- }): Promise<void>;
251
- listTopics(): Promise<string[]>;
252
- listGroups(): Promise<{
253
- groups: IGroupDescription[];
254
- }>;
255
- fetchTopicMetadata(_options?: {
256
- topics?: string[];
257
- }): Promise<{
258
- topics: ITopicMetadata[];
259
- }>;
260
- deleteGroups(groupIds: string[]): Promise<void>;
261
- deleteTopicRecords(options: {
262
- topic: string;
263
- partitions: IPartitionOffset[];
264
- }): Promise<void>;
265
- }
266
- /**
267
- * In-memory `KafkaTransport` for unit testing.
268
- *
269
- * Inject into `KafkaClient` via `KafkaClientOptions.transport` to test
270
- * producer/consumer logic without `jest.mock('@confluentinc/kafka-javascript')`.
271
- *
272
- * @example
273
- * ```ts
274
- * const transport = new FakeTransport();
275
- * const client = new KafkaClient('svc', 'grp', [], { transport });
276
- *
277
- * await client.connectProducer();
278
- * await client.sendMessage('orders', { id: '1' });
279
- *
280
- * expect(transport.mainProducer.sentTo('orders')).toHaveLength(1);
281
- * ```
282
- */
283
- declare class FakeTransport implements KafkaTransport {
284
- private readonly _producers;
285
- private readonly _consumers;
286
- private readonly _admin;
287
- producer(options?: IProducerCreationOptions): IProducer;
288
- consumer(options: IConsumerCreationOptions): IConsumer;
289
- admin(): IAdmin;
290
- /** The admin client shared across all admin() calls. */
291
- get fakeAdmin(): FakeAdmin;
292
- /**
293
- * The first (default) producer — the non-transactional producer
294
- * created during `KafkaClient` construction.
295
- */
296
- get mainProducer(): FakeProducer;
297
- /** All producers created so far (main + transactional). */
298
- get producers(): readonly FakeProducer[];
299
- /** All consumers created so far. */
300
- get consumers(): readonly FakeConsumer[];
301
- /**
302
- * Find the consumer for a given group ID.
303
- * Throws if no consumer with that group exists.
304
- */
305
- consumerFor(groupId: string): FakeConsumer;
306
- /**
307
- * Deliver a JSON-serialized message to the first consumer subscribed to `topic`.
308
- * Simulates a broker dispatching a message to the consumer handler.
309
- */
310
- deliver<T>(topic: string, payload: T, options?: {
311
- key?: string;
312
- headers?: Record<string, string>;
313
- partition?: number;
314
- offset?: string;
315
- }): Promise<void>;
316
- }
317
-
318
- export { FakeAdmin, FakeConsumer, FakeProducer, FakeTransaction, FakeTransport, KafkaTestContainer, type KafkaTestContainerOptions, type MockKafkaClient, createMockKafkaClient };
1
+ export * from "./testing/index";
2
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC"}
package/dist/testing.js CHANGED
@@ -63,6 +63,7 @@ function createMockKafkaClient(mockFactory) {
63
63
  getClientId: returning("mock-client"),
64
64
  sendMessage: resolved(void 0),
65
65
  sendBatch: resolved(void 0),
66
+ sendTombstone: resolved(void 0),
66
67
  transaction: mock().mockImplementation(
67
68
  async (cb) => {
68
69
  const ctx = {
@@ -82,6 +83,21 @@ function createMockKafkaClient(mockFactory) {
82
83
  stop: mock().mockResolvedValue(void 0),
83
84
  ready: mock().mockResolvedValue(void 0)
84
85
  }),
86
+ startWindowConsumer: resolved({
87
+ groupId: "mock-group",
88
+ stop: mock().mockResolvedValue(void 0),
89
+ ready: mock().mockResolvedValue(void 0)
90
+ }),
91
+ startRoutedConsumer: resolved({
92
+ groupId: "mock-group",
93
+ stop: mock().mockResolvedValue(void 0),
94
+ ready: mock().mockResolvedValue(void 0)
95
+ }),
96
+ startTransactionalConsumer: resolved({
97
+ groupId: "mock-group",
98
+ stop: mock().mockResolvedValue(void 0),
99
+ ready: mock().mockResolvedValue(void 0)
100
+ }),
85
101
  stopConsumer: resolved(void 0),
86
102
  consume: returning(
87
103
  (function* () {
@@ -101,6 +117,15 @@ function createMockKafkaClient(mockFactory) {
101
117
  dedupCount: 0
102
118
  }),
103
119
  resetMetrics: mock(),
120
+ listConsumerGroups: resolved([]),
121
+ describeTopics: resolved([]),
122
+ deleteRecords: resolved(void 0),
123
+ readSnapshot: resolved(/* @__PURE__ */ new Map()),
124
+ checkpointOffsets: resolved({ groupId: "mock-group", checkpoints: [] }),
125
+ restoreFromCheckpoint: resolved(void 0),
126
+ connectProducer: resolved(void 0),
127
+ disconnectProducer: resolved(void 0),
128
+ onModuleDestroy: resolved(void 0),
104
129
  disconnect: resolved(void 0),
105
130
  enableGracefulShutdown: mock()
106
131
  };
@@ -194,6 +219,7 @@ var FakeTransaction = class {
194
219
  constructor(producer) {
195
220
  this.producer = producer;
196
221
  }
222
+ producer;
197
223
  /** Records staged within this transaction (not yet committed). */
198
224
  staged = [];
199
225
  /** True after `commit()` was called. */
@@ -325,7 +351,7 @@ var FakeConsumer = class {
325
351
  value: message.value,
326
352
  headers: message.headers ?? {},
327
353
  offset,
328
- key: message.key
354
+ key: message.key ?? null
329
355
  }
330
356
  });
331
357
  }
@@ -465,7 +491,7 @@ var FakeTransport = class {
465
491
  headers: options.headers ? Object.fromEntries(
466
492
  Object.entries(options.headers).map(([k, v]) => [k, [v]])
467
493
  ) : {},
468
- key: options.key !== void 0 ? Buffer.from(options.key) : void 0
494
+ key: options.key !== void 0 ? Buffer.from(options.key) : null
469
495
  },
470
496
  options.partition ?? 0,
471
497
  options.offset ?? "0"
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/testing.ts","../src/testing/client.mock.ts","../src/testing/test.container.ts","../src/testing/transport.fake.ts"],"sourcesContent":["export * from \"./testing/index\";\n","import type { IKafkaClient, TopicMapConstraint } from \"../client/types\";\n\n/**\n * Fully typed mock of `IKafkaClient<T>` where every method is a mock function.\n * Compatible with Jest, Vitest, or any framework whose `fn()` returns\n * an object with `.mock`, `.mockResolvedValue`, etc.\n */\nexport type MockKafkaClient<T extends TopicMapConstraint<T>> = {\n [K in keyof IKafkaClient<T>]: IKafkaClient<T>[K] & Record<string, any>;\n};\n\n/** Factory that creates a no-op mock function (e.g. `() => jest.fn()`). */\nexport type MockFactory = () => (...args: any[]) => any;\n\nfunction detectMockFactory(): MockFactory {\n // Jest and Vitest inject their globals (`jest` / `vi`) as module-scope\n // bindings, not as properties of `globalThis`. The only reliable way to\n // detect them without a hard import is via `eval`, which evaluates in the\n // current module scope where those bindings are available.\n try {\n if (eval(\"typeof jest === 'object' && typeof jest.fn === 'function'\")) {\n return () => eval(\"jest.fn()\");\n }\n } catch {\n /* not available */\n }\n try {\n if (eval(\"typeof vi === 'object' && typeof vi.fn === 'function'\")) {\n return () => eval(\"vi.fn()\");\n }\n } catch {\n /* not available */\n }\n throw new Error(\n \"createMockKafkaClient: no mock framework detected (jest/vitest). \" +\n \"Pass a custom mockFactory.\",\n );\n}\n\n/**\n * Create a fully typed mock implementing every `IKafkaClient<T>` method.\n * Useful for unit-testing services that depend on `KafkaClient` without\n * touching a real broker.\n *\n * Auto-detects Jest (`jest.fn()`) or Vitest (`vi.fn()`). Pass a custom\n * `mockFactory` for other frameworks.\n *\n * All methods resolve to sensible defaults:\n * - `checkStatus()` → `{ status: 'up', clientId: 'mock-client', topics: [] }`\n * - `getClientId()` → `\"mock-client\"`\n * - void methods → `undefined`\n *\n * @example\n * ```ts\n * const kafka = createMockKafkaClient<MyTopics>();\n *\n * const service = new OrdersService(kafka);\n * await service.createOrder();\n *\n * expect(kafka.sendMessage).toHaveBeenCalledWith(\n * 'order.created',\n * expect.objectContaining({ orderId: '123' }),\n * );\n * ```\n */\nexport function createMockKafkaClient<T extends TopicMapConstraint<T>>(\n mockFactory?: MockFactory,\n): MockKafkaClient<T> {\n const fn = mockFactory ?? detectMockFactory();\n\n const mock = () => fn() as any;\n const resolved = (value: unknown) => mock().mockResolvedValue(value);\n const returning = (value: unknown) => mock().mockReturnValue(value);\n\n return {\n checkStatus: resolved({\n status: \"up\",\n clientId: \"mock-client\",\n topics: [],\n }),\n getConsumerLag: resolved([]),\n getClientId: returning(\"mock-client\"),\n sendMessage: resolved(undefined),\n sendBatch: resolved(undefined),\n transaction: mock().mockImplementation(\n async (cb: (ctx: Record<string, unknown>) => Promise<void>) => {\n const ctx = {\n send: resolved(undefined),\n sendBatch: resolved(undefined),\n };\n await cb(ctx);\n },\n ),\n startConsumer: resolved({\n groupId: \"mock-group\",\n stop: mock().mockResolvedValue(undefined),\n ready: mock().mockResolvedValue(undefined),\n }),\n startBatchConsumer: resolved({\n groupId: \"mock-group\",\n stop: mock().mockResolvedValue(undefined),\n ready: mock().mockResolvedValue(undefined),\n }),\n stopConsumer: resolved(undefined),\n consume: returning(\n (function* () {})() as unknown as AsyncIterableIterator<any>,\n ),\n replayDlq: resolved({ replayed: 0, skipped: 0 }),\n resetOffsets: resolved(undefined),\n seekToOffset: resolved(undefined),\n seekToTimestamp: resolved(undefined),\n getCircuitState: returning(undefined),\n pauseConsumer: mock(),\n resumeConsumer: mock(),\n getMetrics: returning({\n processedCount: 0,\n retryCount: 0,\n dlqCount: 0,\n dedupCount: 0,\n }),\n resetMetrics: mock(),\n disconnect: resolved(undefined),\n enableGracefulShutdown: mock(),\n } as unknown as MockKafkaClient<T>;\n}\n","import {\n KafkaContainer,\n type StartedKafkaContainer,\n} from \"@testcontainers/kafka\";\nimport { KafkaJS } from \"@confluentinc/kafka-javascript\";\nconst { Kafka, logLevel: KafkaLogLevel } = KafkaJS;\n\n/** Options for `KafkaTestContainer`. */\nexport interface KafkaTestContainerOptions {\n /** Docker image. Default: `\"confluentinc/cp-kafka:7.7.0\"`. */\n image?: string;\n /** Warm up the transactional coordinator on start. Default: `true`. */\n transactionWarmup?: boolean;\n /** Topics to pre-create. Each entry can be a string (1 partition) or `{ topic, numPartitions }`. */\n topics?: Array<string | { topic: string; numPartitions?: number }>;\n}\n\n/**\n * Thin wrapper around `@testcontainers/kafka` that starts a single-node\n * KRaft Kafka container and exposes `brokers` for use with `KafkaClient`.\n *\n * Handles common setup pain points:\n * - Transaction coordinator warmup (avoids transactional producer hangs)\n * - Topic pre-creation (avoids race conditions)\n *\n * @example\n * ```ts\n * const container = new KafkaTestContainer({ topics: ['orders', 'payments'] });\n * const brokers = await container.start();\n *\n * const kafka = new KafkaClient('test', 'test-group', brokers);\n * // ... run tests ...\n *\n * await container.stop();\n * ```\n *\n * @example Jest lifecycle\n * ```ts\n * let container: KafkaTestContainer;\n * let brokers: string[];\n *\n * beforeAll(async () => {\n * container = new KafkaTestContainer({ topics: ['orders'] });\n * brokers = await container.start();\n * }, 120_000);\n *\n * afterAll(() => container.stop());\n * ```\n */\nexport class KafkaTestContainer {\n private container: StartedKafkaContainer | undefined;\n private readonly image: string;\n private readonly transactionWarmup: boolean;\n private readonly topics: Array<\n string | { topic: string; numPartitions?: number }\n >;\n\n constructor(options?: KafkaTestContainerOptions) {\n this.image = options?.image ?? \"confluentinc/cp-kafka:7.7.0\";\n this.transactionWarmup = options?.transactionWarmup ?? true;\n this.topics = options?.topics ?? [];\n }\n\n /**\n * Start the Kafka container, pre-create topics, and optionally warm up\n * the transaction coordinator.\n *\n * @returns Broker connection strings, e.g. `[\"localhost:55123\"]`.\n */\n async start(): Promise<string[]> {\n this.container = await new KafkaContainer(this.image)\n .withKraft()\n .withExposedPorts(9093)\n .withEnvironment({\n KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: \"1\",\n KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: \"1\",\n })\n .start();\n\n const host = this.container.getHost();\n const port = this.container.getMappedPort(9093);\n const brokers = [`${host}:${port}`];\n\n const kafka = new Kafka({\n kafkaJS: {\n clientId: \"test-container-setup\",\n brokers,\n logLevel: KafkaLogLevel.NOTHING,\n },\n });\n\n if (this.topics.length > 0) {\n const admin = kafka.admin();\n await admin.connect();\n await admin.createTopics({\n topics: this.topics.map((t) =>\n typeof t === \"string\"\n ? { topic: t, numPartitions: 1 }\n : { topic: t.topic, numPartitions: t.numPartitions ?? 1 },\n ),\n });\n await admin.disconnect();\n }\n\n if (this.transactionWarmup) {\n const warmupKafka = new Kafka({\n kafkaJS: {\n clientId: \"test-container-warmup\",\n brokers,\n logLevel: KafkaLogLevel.NOTHING,\n },\n });\n const txProducer = warmupKafka.producer({\n kafkaJS: {\n transactionalId: \"test-container-warmup-tx\",\n idempotent: true,\n maxInFlightRequests: 1,\n },\n });\n await txProducer.connect();\n const tx = await txProducer.transaction();\n await tx.abort();\n await txProducer.disconnect();\n }\n\n return brokers;\n }\n\n /** Stop and remove the container. */\n async stop(): Promise<void> {\n await this.container?.stop();\n this.container = undefined;\n }\n\n /** Broker connection strings. Throws if container is not started. */\n get brokers(): string[] {\n if (!this.container) {\n throw new Error(\"KafkaTestContainer is not started. Call start() first.\");\n }\n const host = this.container.getHost();\n const port = this.container.getMappedPort(9093);\n return [`${host}:${port}`];\n }\n}\n","import type {\n KafkaTransport,\n IProducer,\n IConsumer,\n IAdmin,\n ITransaction,\n IProducerRecord,\n IProducerCreationOptions,\n IConsumerCreationOptions,\n IConsumerRunConfig,\n ITopicPartition,\n ITopicPartitions,\n ITopicPartitionOffset,\n IPartitionWatermarks,\n IPartitionOffset,\n IGroupTopicOffsets,\n IGroupDescription,\n ITopicMetadata,\n IMessage,\n} from \"../client/transport.interface\";\n\n// ── FakeTransaction ───────────────────────────────────────────────────────────\n\n/**\n * An in-memory Kafka transaction.\n * Staged sends are visible in `staged`; committed sends are flushed\n * to the owning `FakeProducer.sent` on `commit()`.\n */\nexport class FakeTransaction implements ITransaction {\n /** Records staged within this transaction (not yet committed). */\n readonly staged: IProducerRecord[] = [];\n /** True after `commit()` was called. */\n committed = false;\n /** True after `abort()` was called. */\n aborted = false;\n /** sendOffsets calls (for EOS assertions). */\n readonly offsetsCommitted: Array<{\n consumer: IConsumer;\n topics: Array<{ topic: string; partitions: IPartitionOffset[] }>;\n }> = [];\n\n constructor(private readonly producer: FakeProducer) {}\n\n async send(record: IProducerRecord): Promise<void> {\n this.staged.push(record);\n }\n\n async sendOffsets(options: {\n consumer: IConsumer;\n topics: Array<{ topic: string; partitions: Array<{ partition: number; offset: string }> }>;\n }): Promise<void> {\n this.offsetsCommitted.push(options);\n }\n\n async commit(): Promise<void> {\n if (this.aborted) throw new Error(\"FakeTransaction: already aborted\");\n this.committed = true;\n for (const record of this.staged) {\n this.producer.sent.push(record);\n }\n }\n\n async abort(): Promise<void> {\n if (this.committed) throw new Error(\"FakeTransaction: already committed\");\n this.aborted = true;\n this.staged.length = 0;\n }\n}\n\n// ── FakeProducer ──────────────────────────────────────────────────────────────\n\n/**\n * In-memory producer. All `send()` calls are captured in `sent`.\n * Transactions are backed by `FakeTransaction`.\n */\nexport class FakeProducer implements IProducer {\n /** All records delivered via `send()` (direct + committed transactions). */\n readonly sent: IProducerRecord[] = [];\n /** All transactions opened via `transaction()`. */\n readonly transactions: FakeTransaction[] = [];\n\n readonly options: IProducerCreationOptions | undefined;\n connected = false;\n\n constructor(options?: IProducerCreationOptions) {\n this.options = options;\n }\n\n async connect(): Promise<void> {\n this.connected = true;\n }\n\n async disconnect(): Promise<void> {\n this.connected = false;\n }\n\n async send(record: IProducerRecord): Promise<void> {\n this.sent.push(record);\n }\n\n async transaction(): Promise<ITransaction> {\n const tx = new FakeTransaction(this);\n this.transactions.push(tx);\n return tx;\n }\n\n /** Return the last committed transaction, or throw if none exist. */\n get lastTransaction(): FakeTransaction {\n const tx = this.transactions.at(-1);\n if (!tx) throw new Error(\"FakeProducer: no transactions opened yet\");\n return tx;\n }\n\n /** All topic names that received at least one message. */\n sentTopics(): string[] {\n return [...new Set(this.sent.map((r) => r.topic))];\n }\n\n /** All messages sent to a specific topic. */\n sentTo(topic: string): IProducerRecord[\"messages\"] {\n return this.sent.filter((r) => r.topic === topic).flatMap((r) => r.messages);\n }\n}\n\n// ── FakeConsumer ──────────────────────────────────────────────────────────────\n\n/**\n * In-memory consumer.\n * Call `deliver(topic, message)` from your test to push messages through\n * the `eachMessage` handler without a real broker.\n */\nexport class FakeConsumer implements IConsumer {\n readonly groupId: string;\n readonly fromBeginning: boolean;\n\n /** Topics subscribed via `subscribe()`. */\n readonly subscribed: string[] = [];\n\n private _runConfig: IConsumerRunConfig | undefined;\n private _assignments: ITopicPartition[] = [];\n readonly pausedTopics = new Set<string>();\n connected = false;\n\n private readonly onRebalance: IConsumerCreationOptions[\"onRebalance\"];\n\n constructor(options: IConsumerCreationOptions) {\n this.groupId = options.groupId;\n this.fromBeginning = options.fromBeginning ?? false;\n this.onRebalance = options.onRebalance;\n }\n\n async connect(): Promise<void> {\n this.connected = true;\n }\n\n async disconnect(): Promise<void> {\n this.connected = false;\n }\n\n async subscribe(options: { topics: (string | RegExp)[] }): Promise<void> {\n for (const t of options.topics) {\n if (typeof t === \"string\") this.subscribed.push(t);\n }\n // Auto-assign one partition per subscribed topic and immediately fire the\n // rebalance callback so that handle.ready() resolves without a real broker.\n this._assignments = this.subscribed.map((topic) => ({ topic, partition: 0 }));\n this.onRebalance?.(\"assign\", this._assignments);\n }\n\n async run(config: IConsumerRunConfig): Promise<void> {\n this._runConfig = config;\n }\n\n pause(assignments: ITopicPartitions[]): void {\n for (const { topic } of assignments) this.pausedTopics.add(topic);\n }\n\n resume(assignments: ITopicPartitions[]): void {\n for (const { topic } of assignments) this.pausedTopics.delete(topic);\n }\n\n seek(_options: ITopicPartitionOffset): void {}\n\n assignment(): ITopicPartition[] {\n return this._assignments;\n }\n\n async commitOffsets(_offsets: ITopicPartitionOffset[]): Promise<void> {}\n\n async stop(): Promise<void> {\n this.connected = false;\n }\n\n // ── Test helpers ─────────────────────────────────────────────────────\n\n /**\n * Push a message through the `eachMessage` handler.\n * Throws if `run()` has not been called yet.\n */\n async deliver(\n topic: string,\n message: Partial<IMessage> & { value: Buffer | null },\n partition = 0,\n offset = \"0\",\n ): Promise<void> {\n if (!this._runConfig?.eachMessage) {\n throw new Error(\n `FakeConsumer(${this.groupId}): run() with eachMessage not called yet`,\n );\n }\n await this._runConfig.eachMessage({\n topic,\n partition,\n message: {\n value: message.value,\n headers: message.headers ?? {},\n offset,\n key: message.key,\n },\n });\n }\n\n /**\n * Simulate a partition-assign rebalance event.\n * Useful for testing onRebalance callbacks.\n */\n triggerRebalance(\n type: \"assign\" | \"revoke\",\n assignments: ITopicPartition[],\n ): void {\n this.onRebalance?.(type, assignments);\n }\n\n /** Whether `run()` has been called (consumer is active). */\n get isRunning(): boolean {\n return this._runConfig !== undefined;\n }\n}\n\n// ── FakeAdmin ─────────────────────────────────────────────────────────────────\n\n/**\n * In-memory admin client.\n * Pre-populate `topicOffsets`, `groupOffsets`, and `existingTopics`\n * to control what admin queries return.\n */\nexport class FakeAdmin implements IAdmin {\n /** Topics returned by `listTopics()`. Add to this from your test. */\n readonly existingTopics: string[] = [];\n\n /** Per-topic partition watermarks returned by `fetchTopicOffsets()`. */\n readonly topicOffsets = new Map<string, IPartitionWatermarks[]>();\n\n /** Per-groupId committed offsets returned by `fetchOffsets()`. */\n readonly groupOffsets = new Map<string, IGroupTopicOffsets[]>();\n\n /** Calls captured by `setOffsets()` — inspect in tests. */\n readonly setOffsetsCalls: Array<{\n groupId: string;\n topic: string;\n partitions: IPartitionOffset[];\n }> = [];\n\n /** Group IDs deleted via `deleteGroups()`. */\n readonly deletedGroups: string[] = [];\n\n /** Records deleted via `deleteTopicRecords()`. */\n readonly deletedRecords: Array<{\n topic: string;\n partitions: IPartitionOffset[];\n }> = [];\n\n connected = false;\n\n async connect(): Promise<void> {\n this.connected = true;\n }\n\n async disconnect(): Promise<void> {\n this.connected = false;\n }\n\n async createTopics(options: {\n topics: Array<{ topic: string; numPartitions: number }>;\n }): Promise<void> {\n for (const { topic } of options.topics) {\n if (!this.existingTopics.includes(topic)) this.existingTopics.push(topic);\n }\n }\n\n async fetchTopicOffsets(topic: string): Promise<IPartitionWatermarks[]> {\n return this.topicOffsets.get(topic) ?? [{ partition: 0, low: \"0\", high: \"0\" }];\n }\n\n async fetchTopicOffsetsByTimestamp(\n _topic: string,\n _timestamp: number,\n ): Promise<IPartitionOffset[]> {\n return [];\n }\n\n async fetchOffsets(options: {\n groupId: string;\n }): Promise<IGroupTopicOffsets[]> {\n return this.groupOffsets.get(options.groupId) ?? [];\n }\n\n async setOffsets(options: {\n groupId: string;\n topic: string;\n partitions: IPartitionOffset[];\n }): Promise<void> {\n this.setOffsetsCalls.push(options);\n }\n\n async listTopics(): Promise<string[]> {\n return this.existingTopics;\n }\n\n async listGroups(): Promise<{ groups: IGroupDescription[] }> {\n return { groups: [] };\n }\n\n async fetchTopicMetadata(_options?: {\n topics?: string[];\n }): Promise<{ topics: ITopicMetadata[] }> {\n return { topics: [] };\n }\n\n async deleteGroups(groupIds: string[]): Promise<void> {\n this.deletedGroups.push(...groupIds);\n }\n\n async deleteTopicRecords(options: {\n topic: string;\n partitions: IPartitionOffset[];\n }): Promise<void> {\n this.deletedRecords.push(options);\n }\n}\n\n// ── FakeTransport ─────────────────────────────────────────────────────────────\n\n/**\n * In-memory `KafkaTransport` for unit testing.\n *\n * Inject into `KafkaClient` via `KafkaClientOptions.transport` to test\n * producer/consumer logic without `jest.mock('@confluentinc/kafka-javascript')`.\n *\n * @example\n * ```ts\n * const transport = new FakeTransport();\n * const client = new KafkaClient('svc', 'grp', [], { transport });\n *\n * await client.connectProducer();\n * await client.sendMessage('orders', { id: '1' });\n *\n * expect(transport.mainProducer.sentTo('orders')).toHaveLength(1);\n * ```\n */\nexport class FakeTransport implements KafkaTransport {\n private readonly _producers: FakeProducer[] = [];\n private readonly _consumers: FakeConsumer[] = [];\n private readonly _admin = new FakeAdmin();\n\n producer(options?: IProducerCreationOptions): IProducer {\n const p = new FakeProducer(options);\n this._producers.push(p);\n return p;\n }\n\n consumer(options: IConsumerCreationOptions): IConsumer {\n const c = new FakeConsumer(options);\n this._consumers.push(c);\n return c;\n }\n\n admin(): IAdmin {\n return this._admin;\n }\n\n // ── Convenience accessors ─────────────────────────────────────────\n\n /** The admin client shared across all admin() calls. */\n get fakeAdmin(): FakeAdmin {\n return this._admin;\n }\n\n /**\n * The first (default) producer — the non-transactional producer\n * created during `KafkaClient` construction.\n */\n get mainProducer(): FakeProducer {\n const p = this._producers[0];\n if (!p) throw new Error(\"FakeTransport: no producers created yet\");\n return p;\n }\n\n /** All producers created so far (main + transactional). */\n get producers(): readonly FakeProducer[] {\n return this._producers;\n }\n\n /** All consumers created so far. */\n get consumers(): readonly FakeConsumer[] {\n return this._consumers;\n }\n\n /**\n * Find the consumer for a given group ID.\n * Throws if no consumer with that group exists.\n */\n consumerFor(groupId: string): FakeConsumer {\n const c = this._consumers.find(\n (c) => c.groupId === groupId || c.groupId.startsWith(groupId),\n );\n if (!c)\n throw new Error(\n `FakeTransport: no consumer for group \"${groupId}\". ` +\n `Available: ${this._consumers.map((c) => c.groupId).join(\", \") || \"(none)\"}`,\n );\n return c;\n }\n\n /**\n * Deliver a JSON-serialized message to the first consumer subscribed to `topic`.\n * Simulates a broker dispatching a message to the consumer handler.\n */\n async deliver<T>(\n topic: string,\n payload: T,\n options: {\n key?: string;\n headers?: Record<string, string>;\n partition?: number;\n offset?: string;\n } = {},\n ): Promise<void> {\n const consumer = this._consumers.find((c) => c.subscribed.includes(topic));\n if (!consumer) {\n throw new Error(\n `FakeTransport: no consumer subscribed to \"${topic}\". ` +\n `Subscribed topics: ${this._consumers.flatMap((c) => c.subscribed).join(\", \") || \"(none)\"}`,\n );\n }\n await consumer.deliver(\n topic,\n {\n value: Buffer.from(JSON.stringify(payload)),\n headers: options.headers\n ? Object.fromEntries(\n Object.entries(options.headers).map(([k, v]) => [k, [v]]),\n )\n : {},\n key: options.key !== undefined ? Buffer.from(options.key) : undefined,\n },\n options.partition ?? 0,\n options.offset ?? \"0\",\n );\n }\n}\n"],"mappings":"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","names":["c"]}
1
+ {"version":3,"sources":["../src/testing.ts","../src/testing/client.mock.ts","../src/testing/test.container.ts","../src/testing/transport.fake.ts"],"sourcesContent":["export * from \"./testing/index\";\n","import type { IKafkaClient, TopicMapConstraint } from \"../client/types\";\n\n/**\n * Fully typed mock of `IKafkaClient<T>` where every method is a mock function.\n * Compatible with Jest, Vitest, or any framework whose `fn()` returns\n * an object with `.mock`, `.mockResolvedValue`, etc.\n */\nexport type MockKafkaClient<T extends TopicMapConstraint<T>> = {\n [K in keyof IKafkaClient<T>]: IKafkaClient<T>[K] & Record<string, any>;\n} & {\n /**\n * Present on the concrete `KafkaClient` but not part of `IKafkaClient` —\n * included so services typed against the class can still use the mock.\n */\n disconnectProducer: (() => Promise<void>) & Record<string, any>;\n /** NestJS lifecycle hook on the concrete `KafkaClient`. */\n onModuleDestroy: (() => Promise<void>) & Record<string, any>;\n};\n\n/** Factory that creates a no-op mock function (e.g. `() => jest.fn()`). */\nexport type MockFactory = () => (...args: any[]) => any;\n\nfunction detectMockFactory(): MockFactory {\n // Jest and Vitest inject their globals (`jest` / `vi`) as module-scope\n // bindings, not as properties of `globalThis`. The only reliable way to\n // detect them without a hard import is via `eval`, which evaluates in the\n // current module scope where those bindings are available.\n try {\n if (eval(\"typeof jest === 'object' && typeof jest.fn === 'function'\")) {\n return () => eval(\"jest.fn()\");\n }\n } catch {\n /* not available */\n }\n try {\n if (eval(\"typeof vi === 'object' && typeof vi.fn === 'function'\")) {\n return () => eval(\"vi.fn()\");\n }\n } catch {\n /* not available */\n }\n throw new Error(\n \"createMockKafkaClient: no mock framework detected (jest/vitest). \" +\n \"Pass a custom mockFactory.\",\n );\n}\n\n/**\n * Create a fully typed mock implementing every `IKafkaClient<T>` method.\n * Useful for unit-testing services that depend on `KafkaClient` without\n * touching a real broker.\n *\n * Auto-detects Jest (`jest.fn()`) or Vitest (`vi.fn()`). Pass a custom\n * `mockFactory` for other frameworks.\n *\n * All methods resolve to sensible defaults:\n * - `checkStatus()` → `{ status: 'up', clientId: 'mock-client', topics: [] }`\n * - `getClientId()` → `\"mock-client\"`\n * - void methods → `undefined`\n *\n * @example\n * ```ts\n * const kafka = createMockKafkaClient<MyTopics>();\n *\n * const service = new OrdersService(kafka);\n * await service.createOrder();\n *\n * expect(kafka.sendMessage).toHaveBeenCalledWith(\n * 'order.created',\n * expect.objectContaining({ orderId: '123' }),\n * );\n * ```\n */\nexport function createMockKafkaClient<T extends TopicMapConstraint<T>>(\n mockFactory?: MockFactory,\n): MockKafkaClient<T> {\n const fn = mockFactory ?? detectMockFactory();\n\n const mock = () => fn() as any;\n const resolved = (value: unknown) => mock().mockResolvedValue(value);\n const returning = (value: unknown) => mock().mockReturnValue(value);\n\n return {\n checkStatus: resolved({\n status: \"up\",\n clientId: \"mock-client\",\n topics: [],\n }),\n getConsumerLag: resolved([]),\n getClientId: returning(\"mock-client\"),\n sendMessage: resolved(undefined),\n sendBatch: resolved(undefined),\n sendTombstone: resolved(undefined),\n transaction: mock().mockImplementation(\n async (cb: (ctx: Record<string, unknown>) => Promise<void>) => {\n const ctx = {\n send: resolved(undefined),\n sendBatch: resolved(undefined),\n };\n await cb(ctx);\n },\n ),\n startConsumer: resolved({\n groupId: \"mock-group\",\n stop: mock().mockResolvedValue(undefined),\n ready: mock().mockResolvedValue(undefined),\n }),\n startBatchConsumer: resolved({\n groupId: \"mock-group\",\n stop: mock().mockResolvedValue(undefined),\n ready: mock().mockResolvedValue(undefined),\n }),\n startWindowConsumer: resolved({\n groupId: \"mock-group\",\n stop: mock().mockResolvedValue(undefined),\n ready: mock().mockResolvedValue(undefined),\n }),\n startRoutedConsumer: resolved({\n groupId: \"mock-group\",\n stop: mock().mockResolvedValue(undefined),\n ready: mock().mockResolvedValue(undefined),\n }),\n startTransactionalConsumer: resolved({\n groupId: \"mock-group\",\n stop: mock().mockResolvedValue(undefined),\n ready: mock().mockResolvedValue(undefined),\n }),\n stopConsumer: resolved(undefined),\n consume: returning(\n (function* () {})() as unknown as AsyncIterableIterator<any>,\n ),\n replayDlq: resolved({ replayed: 0, skipped: 0 }),\n resetOffsets: resolved(undefined),\n seekToOffset: resolved(undefined),\n seekToTimestamp: resolved(undefined),\n getCircuitState: returning(undefined),\n pauseConsumer: mock(),\n resumeConsumer: mock(),\n getMetrics: returning({\n processedCount: 0,\n retryCount: 0,\n dlqCount: 0,\n dedupCount: 0,\n }),\n resetMetrics: mock(),\n listConsumerGroups: resolved([]),\n describeTopics: resolved([]),\n deleteRecords: resolved(undefined),\n readSnapshot: resolved(new Map()),\n checkpointOffsets: resolved({ groupId: \"mock-group\", checkpoints: [] }),\n restoreFromCheckpoint: resolved(undefined),\n connectProducer: resolved(undefined),\n disconnectProducer: resolved(undefined),\n onModuleDestroy: resolved(undefined),\n disconnect: resolved(undefined),\n enableGracefulShutdown: mock(),\n } as unknown as MockKafkaClient<T>;\n}\n","import {\n KafkaContainer,\n type StartedKafkaContainer,\n} from \"@testcontainers/kafka\";\nimport { KafkaJS } from \"@confluentinc/kafka-javascript\";\nconst { Kafka, logLevel: KafkaLogLevel } = KafkaJS;\n\n/** Options for `KafkaTestContainer`. */\nexport interface KafkaTestContainerOptions {\n /** Docker image. Default: `\"confluentinc/cp-kafka:7.7.0\"`. */\n image?: string;\n /** Warm up the transactional coordinator on start. Default: `true`. */\n transactionWarmup?: boolean;\n /** Topics to pre-create. Each entry can be a string (1 partition) or `{ topic, numPartitions }`. */\n topics?: Array<string | { topic: string; numPartitions?: number }>;\n}\n\n/**\n * Thin wrapper around `@testcontainers/kafka` that starts a single-node\n * KRaft Kafka container and exposes `brokers` for use with `KafkaClient`.\n *\n * Handles common setup pain points:\n * - Transaction coordinator warmup (avoids transactional producer hangs)\n * - Topic pre-creation (avoids race conditions)\n *\n * @example\n * ```ts\n * const container = new KafkaTestContainer({ topics: ['orders', 'payments'] });\n * const brokers = await container.start();\n *\n * const kafka = new KafkaClient('test', 'test-group', brokers);\n * // ... run tests ...\n *\n * await container.stop();\n * ```\n *\n * @example Jest lifecycle\n * ```ts\n * let container: KafkaTestContainer;\n * let brokers: string[];\n *\n * beforeAll(async () => {\n * container = new KafkaTestContainer({ topics: ['orders'] });\n * brokers = await container.start();\n * }, 120_000);\n *\n * afterAll(() => container.stop());\n * ```\n */\nexport class KafkaTestContainer {\n private container: StartedKafkaContainer | undefined;\n private readonly image: string;\n private readonly transactionWarmup: boolean;\n private readonly topics: Array<\n string | { topic: string; numPartitions?: number }\n >;\n\n constructor(options?: KafkaTestContainerOptions) {\n this.image = options?.image ?? \"confluentinc/cp-kafka:7.7.0\";\n this.transactionWarmup = options?.transactionWarmup ?? true;\n this.topics = options?.topics ?? [];\n }\n\n /**\n * Start the Kafka container, pre-create topics, and optionally warm up\n * the transaction coordinator.\n *\n * @returns Broker connection strings, e.g. `[\"localhost:55123\"]`.\n */\n async start(): Promise<string[]> {\n this.container = await new KafkaContainer(this.image)\n .withKraft()\n .withExposedPorts(9093)\n .withEnvironment({\n KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: \"1\",\n KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: \"1\",\n })\n .start();\n\n const host = this.container.getHost();\n const port = this.container.getMappedPort(9093);\n const brokers = [`${host}:${port}`];\n\n const kafka = new Kafka({\n kafkaJS: {\n clientId: \"test-container-setup\",\n brokers,\n logLevel: KafkaLogLevel.NOTHING,\n },\n });\n\n if (this.topics.length > 0) {\n const admin = kafka.admin();\n await admin.connect();\n await admin.createTopics({\n topics: this.topics.map((t) =>\n typeof t === \"string\"\n ? { topic: t, numPartitions: 1 }\n : { topic: t.topic, numPartitions: t.numPartitions ?? 1 },\n ),\n });\n await admin.disconnect();\n }\n\n if (this.transactionWarmup) {\n const warmupKafka = new Kafka({\n kafkaJS: {\n clientId: \"test-container-warmup\",\n brokers,\n logLevel: KafkaLogLevel.NOTHING,\n },\n });\n const txProducer = warmupKafka.producer({\n kafkaJS: {\n transactionalId: \"test-container-warmup-tx\",\n idempotent: true,\n maxInFlightRequests: 1,\n },\n });\n await txProducer.connect();\n const tx = await txProducer.transaction();\n await tx.abort();\n await txProducer.disconnect();\n }\n\n return brokers;\n }\n\n /** Stop and remove the container. */\n async stop(): Promise<void> {\n await this.container?.stop();\n this.container = undefined;\n }\n\n /** Broker connection strings. Throws if container is not started. */\n get brokers(): string[] {\n if (!this.container) {\n throw new Error(\"KafkaTestContainer is not started. Call start() first.\");\n }\n const host = this.container.getHost();\n const port = this.container.getMappedPort(9093);\n return [`${host}:${port}`];\n }\n}\n","import type {\n KafkaTransport,\n IProducer,\n IConsumer,\n IAdmin,\n ITransaction,\n IProducerRecord,\n IProducerCreationOptions,\n IConsumerCreationOptions,\n IConsumerRunConfig,\n ITopicPartition,\n ITopicPartitions,\n ITopicPartitionOffset,\n IPartitionWatermarks,\n IPartitionOffset,\n IGroupTopicOffsets,\n IGroupDescription,\n ITopicMetadata,\n IMessage,\n} from \"../client/transport/transport.interface\";\n\n// ── FakeTransaction ───────────────────────────────────────────────────────────\n\n/**\n * An in-memory Kafka transaction.\n * Staged sends are visible in `staged`; committed sends are flushed\n * to the owning `FakeProducer.sent` on `commit()`.\n */\nexport class FakeTransaction implements ITransaction {\n /** Records staged within this transaction (not yet committed). */\n readonly staged: IProducerRecord[] = [];\n /** True after `commit()` was called. */\n committed = false;\n /** True after `abort()` was called. */\n aborted = false;\n /** sendOffsets calls (for EOS assertions). */\n readonly offsetsCommitted: Array<{\n consumer: IConsumer;\n topics: Array<{ topic: string; partitions: IPartitionOffset[] }>;\n }> = [];\n\n constructor(private readonly producer: FakeProducer) {}\n\n async send(record: IProducerRecord): Promise<void> {\n this.staged.push(record);\n }\n\n async sendOffsets(options: {\n consumer: IConsumer;\n topics: Array<{ topic: string; partitions: Array<{ partition: number; offset: string }> }>;\n }): Promise<void> {\n this.offsetsCommitted.push(options);\n }\n\n async commit(): Promise<void> {\n if (this.aborted) throw new Error(\"FakeTransaction: already aborted\");\n this.committed = true;\n for (const record of this.staged) {\n this.producer.sent.push(record);\n }\n }\n\n async abort(): Promise<void> {\n if (this.committed) throw new Error(\"FakeTransaction: already committed\");\n this.aborted = true;\n this.staged.length = 0;\n }\n}\n\n// ── FakeProducer ──────────────────────────────────────────────────────────────\n\n/**\n * In-memory producer. All `send()` calls are captured in `sent`.\n * Transactions are backed by `FakeTransaction`.\n */\nexport class FakeProducer implements IProducer {\n /** All records delivered via `send()` (direct + committed transactions). */\n readonly sent: IProducerRecord[] = [];\n /** All transactions opened via `transaction()`. */\n readonly transactions: FakeTransaction[] = [];\n\n readonly options: IProducerCreationOptions | undefined;\n connected = false;\n\n constructor(options?: IProducerCreationOptions) {\n this.options = options;\n }\n\n async connect(): Promise<void> {\n this.connected = true;\n }\n\n async disconnect(): Promise<void> {\n this.connected = false;\n }\n\n async send(record: IProducerRecord): Promise<void> {\n this.sent.push(record);\n }\n\n async transaction(): Promise<ITransaction> {\n const tx = new FakeTransaction(this);\n this.transactions.push(tx);\n return tx;\n }\n\n /** Return the last committed transaction, or throw if none exist. */\n get lastTransaction(): FakeTransaction {\n const tx = this.transactions.at(-1);\n if (!tx) throw new Error(\"FakeProducer: no transactions opened yet\");\n return tx;\n }\n\n /** All topic names that received at least one message. */\n sentTopics(): string[] {\n return [...new Set(this.sent.map((r) => r.topic))];\n }\n\n /** All messages sent to a specific topic. */\n sentTo(topic: string): IProducerRecord[\"messages\"] {\n return this.sent.filter((r) => r.topic === topic).flatMap((r) => r.messages);\n }\n}\n\n// ── FakeConsumer ──────────────────────────────────────────────────────────────\n\n/**\n * In-memory consumer.\n * Call `deliver(topic, message)` from your test to push messages through\n * the `eachMessage` handler without a real broker.\n */\nexport class FakeConsumer implements IConsumer {\n readonly groupId: string;\n readonly fromBeginning: boolean;\n\n /** Topics subscribed via `subscribe()`. */\n readonly subscribed: string[] = [];\n\n private _runConfig: IConsumerRunConfig | undefined;\n private _assignments: ITopicPartition[] = [];\n readonly pausedTopics = new Set<string>();\n connected = false;\n\n private readonly onRebalance: IConsumerCreationOptions[\"onRebalance\"];\n\n constructor(options: IConsumerCreationOptions) {\n this.groupId = options.groupId;\n this.fromBeginning = options.fromBeginning ?? false;\n this.onRebalance = options.onRebalance;\n }\n\n async connect(): Promise<void> {\n this.connected = true;\n }\n\n async disconnect(): Promise<void> {\n this.connected = false;\n }\n\n async subscribe(options: { topics: (string | RegExp)[] }): Promise<void> {\n for (const t of options.topics) {\n if (typeof t === \"string\") this.subscribed.push(t);\n }\n // Auto-assign one partition per subscribed topic and immediately fire the\n // rebalance callback so that handle.ready() resolves without a real broker.\n this._assignments = this.subscribed.map((topic) => ({ topic, partition: 0 }));\n this.onRebalance?.(\"assign\", this._assignments);\n }\n\n async run(config: IConsumerRunConfig): Promise<void> {\n this._runConfig = config;\n }\n\n pause(assignments: ITopicPartitions[]): void {\n for (const { topic } of assignments) this.pausedTopics.add(topic);\n }\n\n resume(assignments: ITopicPartitions[]): void {\n for (const { topic } of assignments) this.pausedTopics.delete(topic);\n }\n\n seek(_options: ITopicPartitionOffset): void {}\n\n assignment(): ITopicPartition[] {\n return this._assignments;\n }\n\n async commitOffsets(_offsets: ITopicPartitionOffset[]): Promise<void> {}\n\n async stop(): Promise<void> {\n this.connected = false;\n }\n\n // ── Test helpers ─────────────────────────────────────────────────────\n\n /**\n * Push a message through the `eachMessage` handler.\n * Throws if `run()` has not been called yet.\n */\n async deliver(\n topic: string,\n message: Partial<IMessage> & { value: Buffer | null },\n partition = 0,\n offset = \"0\",\n ): Promise<void> {\n if (!this._runConfig?.eachMessage) {\n throw new Error(\n `FakeConsumer(${this.groupId}): run() with eachMessage not called yet`,\n );\n }\n await this._runConfig.eachMessage({\n topic,\n partition,\n message: {\n value: message.value,\n headers: message.headers ?? {},\n offset,\n key: message.key ?? null,\n },\n });\n }\n\n /**\n * Simulate a partition-assign rebalance event.\n * Useful for testing onRebalance callbacks.\n */\n triggerRebalance(\n type: \"assign\" | \"revoke\",\n assignments: ITopicPartition[],\n ): void {\n this.onRebalance?.(type, assignments);\n }\n\n /** Whether `run()` has been called (consumer is active). */\n get isRunning(): boolean {\n return this._runConfig !== undefined;\n }\n}\n\n// ── FakeAdmin ─────────────────────────────────────────────────────────────────\n\n/**\n * In-memory admin client.\n * Pre-populate `topicOffsets`, `groupOffsets`, and `existingTopics`\n * to control what admin queries return.\n */\nexport class FakeAdmin implements IAdmin {\n /** Topics returned by `listTopics()`. Add to this from your test. */\n readonly existingTopics: string[] = [];\n\n /** Per-topic partition watermarks returned by `fetchTopicOffsets()`. */\n readonly topicOffsets = new Map<string, IPartitionWatermarks[]>();\n\n /** Per-groupId committed offsets returned by `fetchOffsets()`. */\n readonly groupOffsets = new Map<string, IGroupTopicOffsets[]>();\n\n /** Calls captured by `setOffsets()` — inspect in tests. */\n readonly setOffsetsCalls: Array<{\n groupId: string;\n topic: string;\n partitions: IPartitionOffset[];\n }> = [];\n\n /** Group IDs deleted via `deleteGroups()`. */\n readonly deletedGroups: string[] = [];\n\n /** Records deleted via `deleteTopicRecords()`. */\n readonly deletedRecords: Array<{\n topic: string;\n partitions: IPartitionOffset[];\n }> = [];\n\n connected = false;\n\n async connect(): Promise<void> {\n this.connected = true;\n }\n\n async disconnect(): Promise<void> {\n this.connected = false;\n }\n\n async createTopics(options: {\n topics: Array<{ topic: string; numPartitions: number }>;\n }): Promise<void> {\n for (const { topic } of options.topics) {\n if (!this.existingTopics.includes(topic)) this.existingTopics.push(topic);\n }\n }\n\n async fetchTopicOffsets(topic: string): Promise<IPartitionWatermarks[]> {\n return this.topicOffsets.get(topic) ?? [{ partition: 0, low: \"0\", high: \"0\" }];\n }\n\n async fetchTopicOffsetsByTimestamp(\n _topic: string,\n _timestamp: number,\n ): Promise<IPartitionOffset[]> {\n return [];\n }\n\n async fetchOffsets(options: {\n groupId: string;\n }): Promise<IGroupTopicOffsets[]> {\n return this.groupOffsets.get(options.groupId) ?? [];\n }\n\n async setOffsets(options: {\n groupId: string;\n topic: string;\n partitions: IPartitionOffset[];\n }): Promise<void> {\n this.setOffsetsCalls.push(options);\n }\n\n async listTopics(): Promise<string[]> {\n return this.existingTopics;\n }\n\n async listGroups(): Promise<{ groups: IGroupDescription[] }> {\n return { groups: [] };\n }\n\n async fetchTopicMetadata(_options?: {\n topics?: string[];\n }): Promise<{ topics: ITopicMetadata[] }> {\n return { topics: [] };\n }\n\n async deleteGroups(groupIds: string[]): Promise<void> {\n this.deletedGroups.push(...groupIds);\n }\n\n async deleteTopicRecords(options: {\n topic: string;\n partitions: IPartitionOffset[];\n }): Promise<void> {\n this.deletedRecords.push(options);\n }\n}\n\n// ── FakeTransport ─────────────────────────────────────────────────────────────\n\n/**\n * In-memory `KafkaTransport` for unit testing.\n *\n * Inject into `KafkaClient` via `KafkaClientOptions.transport` to test\n * producer/consumer logic without `jest.mock('@confluentinc/kafka-javascript')`.\n *\n * @example\n * ```ts\n * const transport = new FakeTransport();\n * const client = new KafkaClient('svc', 'grp', [], { transport });\n *\n * await client.connectProducer();\n * await client.sendMessage('orders', { id: '1' });\n *\n * expect(transport.mainProducer.sentTo('orders')).toHaveLength(1);\n * ```\n */\nexport class FakeTransport implements KafkaTransport {\n private readonly _producers: FakeProducer[] = [];\n private readonly _consumers: FakeConsumer[] = [];\n private readonly _admin = new FakeAdmin();\n\n producer(options?: IProducerCreationOptions): IProducer {\n const p = new FakeProducer(options);\n this._producers.push(p);\n return p;\n }\n\n consumer(options: IConsumerCreationOptions): IConsumer {\n const c = new FakeConsumer(options);\n this._consumers.push(c);\n return c;\n }\n\n admin(): IAdmin {\n return this._admin;\n }\n\n // ── Convenience accessors ─────────────────────────────────────────\n\n /** The admin client shared across all admin() calls. */\n get fakeAdmin(): FakeAdmin {\n return this._admin;\n }\n\n /**\n * The first (default) producer — the non-transactional producer\n * created during `KafkaClient` construction.\n */\n get mainProducer(): FakeProducer {\n const p = this._producers[0];\n if (!p) throw new Error(\"FakeTransport: no producers created yet\");\n return p;\n }\n\n /** All producers created so far (main + transactional). */\n get producers(): readonly FakeProducer[] {\n return this._producers;\n }\n\n /** All consumers created so far. */\n get consumers(): readonly FakeConsumer[] {\n return this._consumers;\n }\n\n /**\n * Find the consumer for a given group ID.\n * Throws if no consumer with that group exists.\n */\n consumerFor(groupId: string): FakeConsumer {\n const c = this._consumers.find(\n (c) => c.groupId === groupId || c.groupId.startsWith(groupId),\n );\n if (!c)\n throw new Error(\n `FakeTransport: no consumer for group \"${groupId}\". ` +\n `Available: ${this._consumers.map((c) => c.groupId).join(\", \") || \"(none)\"}`,\n );\n return c;\n }\n\n /**\n * Deliver a JSON-serialized message to the first consumer subscribed to `topic`.\n * Simulates a broker dispatching a message to the consumer handler.\n */\n async deliver<T>(\n topic: string,\n payload: T,\n options: {\n key?: string;\n headers?: Record<string, string>;\n partition?: number;\n offset?: string;\n } = {},\n ): Promise<void> {\n const consumer = this._consumers.find((c) => c.subscribed.includes(topic));\n if (!consumer) {\n throw new Error(\n `FakeTransport: no consumer subscribed to \"${topic}\". ` +\n `Subscribed topics: ${this._consumers.flatMap((c) => c.subscribed).join(\", \") || \"(none)\"}`,\n );\n }\n await consumer.deliver(\n topic,\n {\n value: Buffer.from(JSON.stringify(payload)),\n headers: options.headers\n ? Object.fromEntries(\n Object.entries(options.headers).map(([k, v]) => [k, [v]]),\n )\n : {},\n key: options.key !== undefined ? Buffer.from(options.key) : null,\n },\n options.partition ?? 0,\n options.offset ?? \"0\",\n );\n }\n}\n"],"mappings":"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","names":["c"]}
package/dist/testing.mjs CHANGED
@@ -33,6 +33,7 @@ function createMockKafkaClient(mockFactory) {
33
33
  getClientId: returning("mock-client"),
34
34
  sendMessage: resolved(void 0),
35
35
  sendBatch: resolved(void 0),
36
+ sendTombstone: resolved(void 0),
36
37
  transaction: mock().mockImplementation(
37
38
  async (cb) => {
38
39
  const ctx = {
@@ -52,6 +53,21 @@ function createMockKafkaClient(mockFactory) {
52
53
  stop: mock().mockResolvedValue(void 0),
53
54
  ready: mock().mockResolvedValue(void 0)
54
55
  }),
56
+ startWindowConsumer: resolved({
57
+ groupId: "mock-group",
58
+ stop: mock().mockResolvedValue(void 0),
59
+ ready: mock().mockResolvedValue(void 0)
60
+ }),
61
+ startRoutedConsumer: resolved({
62
+ groupId: "mock-group",
63
+ stop: mock().mockResolvedValue(void 0),
64
+ ready: mock().mockResolvedValue(void 0)
65
+ }),
66
+ startTransactionalConsumer: resolved({
67
+ groupId: "mock-group",
68
+ stop: mock().mockResolvedValue(void 0),
69
+ ready: mock().mockResolvedValue(void 0)
70
+ }),
55
71
  stopConsumer: resolved(void 0),
56
72
  consume: returning(
57
73
  (function* () {
@@ -71,6 +87,15 @@ function createMockKafkaClient(mockFactory) {
71
87
  dedupCount: 0
72
88
  }),
73
89
  resetMetrics: mock(),
90
+ listConsumerGroups: resolved([]),
91
+ describeTopics: resolved([]),
92
+ deleteRecords: resolved(void 0),
93
+ readSnapshot: resolved(/* @__PURE__ */ new Map()),
94
+ checkpointOffsets: resolved({ groupId: "mock-group", checkpoints: [] }),
95
+ restoreFromCheckpoint: resolved(void 0),
96
+ connectProducer: resolved(void 0),
97
+ disconnectProducer: resolved(void 0),
98
+ onModuleDestroy: resolved(void 0),
74
99
  disconnect: resolved(void 0),
75
100
  enableGracefulShutdown: mock()
76
101
  };
@@ -166,6 +191,7 @@ var FakeTransaction = class {
166
191
  constructor(producer) {
167
192
  this.producer = producer;
168
193
  }
194
+ producer;
169
195
  /** Records staged within this transaction (not yet committed). */
170
196
  staged = [];
171
197
  /** True after `commit()` was called. */
@@ -297,7 +323,7 @@ var FakeConsumer = class {
297
323
  value: message.value,
298
324
  headers: message.headers ?? {},
299
325
  offset,
300
- key: message.key
326
+ key: message.key ?? null
301
327
  }
302
328
  });
303
329
  }
@@ -437,7 +463,7 @@ var FakeTransport = class {
437
463
  headers: options.headers ? Object.fromEntries(
438
464
  Object.entries(options.headers).map(([k, v]) => [k, [v]])
439
465
  ) : {},
440
- key: options.key !== void 0 ? Buffer.from(options.key) : void 0
466
+ key: options.key !== void 0 ? Buffer.from(options.key) : null
441
467
  },
442
468
  options.partition ?? 0,
443
469
  options.offset ?? "0"