@amqp-contract/contract 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,5 +1,4 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
-
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
2
  //#region src/builder/exchange.ts
4
3
  /**
5
4
  * Define an AMQP exchange.
@@ -9,19 +8,23 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
9
8
  * type safety.
10
9
  *
11
10
  * @param name - The name of the exchange
12
- * @param type - The type of exchange: "fanout", "direct", or "topic"
13
11
  * @param options - Optional exchange configuration
12
+ * @param options.type - Exchange type (one of "topic", "direct", "fanout", "headers") (default: "topic")
13
+ * @param options.durable - If true, the exchange survives broker restarts (default: true)
14
+ * @param options.autoDelete - If true, the exchange is deleted when no queues are bound
15
+ * @param options.internal - If true, the exchange cannot be directly published to
16
+ * @param options.arguments - Additional AMQP arguments for the exchange
14
17
  * @returns An exchange definition
15
18
  * @internal
16
19
  */
17
- function defineExchange(name, type, options) {
20
+ function defineExchange(name, options) {
18
21
  return {
19
22
  name,
20
- type,
23
+ type: options?.type ?? "topic",
24
+ durable: true,
21
25
  ...options
22
26
  };
23
27
  }
24
-
25
28
  //#endregion
26
29
  //#region src/builder/message.ts
27
30
  /**
@@ -65,25 +68,64 @@ function defineMessage(payload, options) {
65
68
  ...options
66
69
  };
67
70
  }
68
-
69
71
  //#endregion
70
72
  //#region src/builder/queue-utils.ts
71
73
  /**
72
- * Type guard to check if a queue entry is a QueueWithTtlBackoffInfrastructure.
74
+ * Extract the plain QueueDefinition from a QueueEntry.
73
75
  * @internal
74
76
  */
75
- function isQueueWithTtlBackoffInfrastructure$1(entry) {
76
- return typeof entry === "object" && entry !== null && "__brand" in entry && entry.__brand === "QueueWithTtlBackoffInfrastructure";
77
+ function extractQueueFromEntry(entry) {
78
+ if (isQueueWithTtlBackoffInfrastructure(entry)) return entry.queue;
79
+ return entry;
77
80
  }
78
81
  /**
79
82
  * Extract the plain QueueDefinition from a QueueEntry.
80
- * @internal
83
+ *
84
+ * **Why this function exists:**
85
+ * When you configure a queue with TTL-backoff retry,
86
+ * `defineQueue` returns a wrapper object that includes
87
+ * the main queue, wait queue, headers exchanges, and bindings. This function extracts the underlying
88
+ * queue definition so you can access properties like `name`, `type`, etc.
89
+ *
90
+ * **When to use:**
91
+ * - When you need to access queue properties (name, type, etc.)
92
+ * - When passing a queue to functions that expect a plain QueueDefinition
93
+ * - Works safely on both plain queues and infrastructure wrappers
94
+ *
95
+ * **How it works:**
96
+ * - If the entry is a `QueueWithTtlBackoffInfrastructure`, returns `entry.queue`
97
+ * - Otherwise, returns the entry as-is (it's already a plain QueueDefinition)
98
+ *
99
+ * @param entry - The queue entry (either plain QueueDefinition or QueueWithTtlBackoffInfrastructure)
100
+ * @returns The plain QueueDefinition
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * import { defineQueue, extractQueue } from '@amqp-contract/contract';
105
+ *
106
+ * // TTL-backoff queue returns a wrapper
107
+ * const orderQueue = defineQueue('orders', {
108
+ * retry: { mode: 'ttl-backoff', maxRetries: 3 },
109
+ * });
110
+ *
111
+ * // Use extractQueue to access the queue name
112
+ * const queueName = extractQueue(orderQueue).name; // 'orders'
113
+ *
114
+ * // Also works safely on plain queues
115
+ * const plainQueue = defineQueue('simple', { type: 'quorum', retry: { mode: 'immediate-requeue' } });
116
+ * const plainName = extractQueue(plainQueue).name; // 'simple'
117
+ *
118
+ * // Access other properties
119
+ * const queueDef = extractQueue(orderQueue);
120
+ * console.log(queueDef.name); // 'orders'
121
+ * console.log(queueDef.type); // 'quorum'
122
+ * ```
123
+ *
124
+ * @see isQueueWithTtlBackoffInfrastructure - Type guard to check if extraction is needed
81
125
  */
82
- function extractQueueFromEntry(entry) {
83
- if (isQueueWithTtlBackoffInfrastructure$1(entry)) return entry.queue;
84
- return entry;
126
+ function extractQueue(entry) {
127
+ return extractQueueFromEntry(entry);
85
128
  }
86
-
87
129
  //#endregion
88
130
  //#region src/builder/binding.ts
89
131
  /**
@@ -98,8 +140,8 @@ function extractQueueFromEntry(entry) {
98
140
  * @internal
99
141
  */
100
142
  function defineQueueBinding(queue, exchange, options) {
101
- const queueDef = extractQueueFromEntry(queue);
102
- if (exchange.type === "fanout") return {
143
+ const queueDef = extractQueue(queue);
144
+ if (exchange.type === "fanout" || exchange.type === "headers") return {
103
145
  type: "queue",
104
146
  queue: queueDef,
105
147
  exchange,
@@ -119,7 +161,7 @@ function defineQueueBinding(queue, exchange, options) {
119
161
  * @internal
120
162
  */
121
163
  function defineQueueBindingInternal(queue, exchange, options) {
122
- if (exchange.type === "fanout") return defineQueueBinding(queue, exchange, options);
164
+ if (exchange.type === "fanout" || exchange.type === "headers") return defineQueueBinding(queue, exchange, options);
123
165
  return defineQueueBinding(queue, exchange, options);
124
166
  }
125
167
  /**
@@ -134,7 +176,7 @@ function defineQueueBindingInternal(queue, exchange, options) {
134
176
  * @internal
135
177
  */
136
178
  function defineExchangeBinding(destination, source, options) {
137
- if (source.type === "fanout") return {
179
+ if (source.type === "fanout" || source.type === "headers") return {
138
180
  type: "exchange",
139
181
  source,
140
182
  destination,
@@ -148,27 +190,12 @@ function defineExchangeBinding(destination, source, options) {
148
190
  ...options?.arguments && { arguments: options.arguments }
149
191
  };
150
192
  }
151
-
152
193
  //#endregion
153
- //#region src/builder/queue.ts
154
- /**
155
- * Resolve TTL-backoff retry options with defaults applied.
156
- * @internal
157
- */
158
- function resolveTtlBackoffOptions(options) {
159
- return {
160
- mode: "ttl-backoff",
161
- maxRetries: options?.maxRetries ?? 3,
162
- initialDelayMs: options?.initialDelayMs ?? 1e3,
163
- maxDelayMs: options?.maxDelayMs ?? 3e4,
164
- backoffMultiplier: options?.backoffMultiplier ?? 2,
165
- jitter: options?.jitter ?? true
166
- };
167
- }
194
+ //#region src/builder/ttl-backoff.ts
168
195
  /**
169
196
  * Type guard to check if a queue entry is a QueueWithTtlBackoffInfrastructure.
170
197
  *
171
- * When you configure a queue with TTL-backoff retry and a dead letter exchange,
198
+ * When you configure a queue with TTL-backoff retry,
172
199
  * `defineQueue` returns a `QueueWithTtlBackoffInfrastructure` instead of a plain
173
200
  * `QueueDefinition`. This type guard helps you distinguish between the two.
174
201
  *
@@ -185,12 +212,11 @@ function resolveTtlBackoffOptions(options) {
185
212
  * @example
186
213
  * ```typescript
187
214
  * const queue = defineQueue('orders', {
188
- * deadLetter: { exchange: dlx },
189
215
  * retry: { mode: 'ttl-backoff' },
190
216
  * });
191
217
  *
192
218
  * if (isQueueWithTtlBackoffInfrastructure(queue)) {
193
- * // queue has .queue, .waitQueue, .waitQueueBinding, .mainQueueRetryBinding
219
+ * // queue has .queue, .waitQueue, .waitQueueBinding, .retryQueueBinding, .waitExchange, .retryExchange
194
220
  * console.log('Wait queue:', queue.waitQueue.name);
195
221
  * } else {
196
222
  * // queue is a plain QueueDefinition
@@ -199,256 +225,161 @@ function resolveTtlBackoffOptions(options) {
199
225
  * ```
200
226
  */
201
227
  function isQueueWithTtlBackoffInfrastructure(entry) {
202
- return isQueueWithTtlBackoffInfrastructure$1(entry);
228
+ return typeof entry === "object" && entry !== null && "__brand" in entry && entry.__brand === "QueueWithTtlBackoffInfrastructure";
203
229
  }
204
230
  /**
205
- * Extract the plain QueueDefinition from a QueueEntry.
206
- *
207
- * **Why this function exists:**
208
- * When you configure a queue with TTL-backoff retry and a dead letter exchange,
209
- * `defineQueue` (or `defineTtlBackoffQueue`) returns a wrapper object that includes
210
- * the main queue, wait queue, and bindings. This function extracts the underlying
211
- * queue definition so you can access properties like `name`, `type`, etc.
231
+ * Wrap a queue definition with TTL-backoff retry infrastructure.
232
+ */
233
+ function wrapWithTtlBackoffInfrastructure(queue) {
234
+ return {
235
+ __brand: "QueueWithTtlBackoffInfrastructure",
236
+ queue,
237
+ ...createTtlBackoffInfrastructure(queue)
238
+ };
239
+ }
240
+ /**
241
+ * Create TTL-backoff retry infrastructure for a queue.
212
242
  *
213
- * **When to use:**
214
- * - When you need to access queue properties (name, type, deadLetter, etc.)
215
- * - When passing a queue to functions that expect a plain QueueDefinition
216
- * - Works safely on both plain queues and infrastructure wrappers
243
+ * This builder helper generates the wait queue, exchanges, and bindings needed for TTL-backoff retry.
244
+ * The generated infrastructure can be spread into a contract definition.
217
245
  *
218
- * **How it works:**
219
- * - If the entry is a `QueueWithTtlBackoffInfrastructure`, returns `entry.queue`
220
- * - Otherwise, returns the entry as-is (it's already a plain QueueDefinition)
246
+ * TTL-backoff retry works by:
247
+ * 1. Failed messages are sent to the wait exchange with header `x-wait-queue` set to the wait queue name
248
+ * 2. The wait queue receives these messages and holds them for a TTL period
249
+ * 3. After TTL expires, messages are dead-lettered back to the retry exchange with header `x-retry-queue` set to the main queue name
250
+ * 4. The main queue receives the retried message via its binding to the retry exchange
221
251
  *
222
- * @param entry - The queue entry (either plain QueueDefinition or QueueWithTtlBackoffInfrastructure)
223
- * @returns The plain QueueDefinition
252
+ * @param queue - The main queue definition
253
+ * @param options - Optional configuration for the wait queue
254
+ * @returns TTL-backoff retry infrastructure containing wait queue and bindings
255
+ * @throws {Error} If the queue does not have retry mode set to `ttl-backoff`
224
256
  *
225
257
  * @example
226
258
  * ```typescript
227
- * import { defineQueue, defineTtlBackoffQueue, extractQueue } from '@amqp-contract/contract';
228
- *
229
- * // TTL-backoff queue returns a wrapper
230
- * const orderQueue = defineTtlBackoffQueue('orders', {
231
- * deadLetter: { exchange: dlx },
232
- * maxRetries: 3,
259
+ * const orderQueue = defineQueue('order-processing', {
260
+ * type: 'quorum',
261
+ * retry: {
262
+ * mode: 'ttl-backoff',
263
+ * maxRetries: 5,
264
+ * initialDelayMs: 1000,
265
+ * },
233
266
  * });
234
267
  *
235
- * // Use extractQueue to access the queue name
236
- * const queueName = extractQueue(orderQueue).name; // 'orders'
237
- *
238
- * // Also works safely on plain queues
239
- * const plainQueue = defineQueue('simple', { type: 'quorum', retry: { mode: 'quorum-native' } });
240
- * const plainName = extractQueue(plainQueue).name; // 'simple'
241
- *
242
- * // Access other properties
243
- * const queueDef = extractQueue(orderQueue);
244
- * console.log(queueDef.name); // 'orders'
245
- * console.log(queueDef.type); // 'quorum'
246
- * console.log(queueDef.deadLetter); // { exchange: dlx, ... }
268
+ * // Infrastructure is auto-extracted when using defineContract:
269
+ * const contract = defineContract({
270
+ * publishers: { ... },
271
+ * consumers: { processOrder: defineEventConsumer(event, orderQueue) },
272
+ * });
273
+ * // contract.queues includes the wait queue, contract.exchanges includes retry exchanges, contract.bindings includes retry bindings
247
274
  * ```
248
- *
249
- * @see isQueueWithTtlBackoffInfrastructure - Type guard to check if extraction is needed
250
- * @see defineTtlBackoffQueue - Creates queues with TTL-backoff infrastructure
251
275
  */
252
- function extractQueue(entry) {
253
- return extractQueueFromEntry(entry);
276
+ function createTtlBackoffInfrastructure(queue) {
277
+ if (queue.retry.mode !== "ttl-backoff") throw new Error(`Queue ${queue.name} does not have ttl-backoff retry mode. Infrastructure can only be created for queues with ttl-backoff retry.`);
278
+ const waitExchange = defineExchange(queue.retry.waitExchangeName, { type: "headers" });
279
+ const retryExchange = defineExchange(queue.retry.retryExchangeName, { type: "headers" });
280
+ const baseWaitQueue = {
281
+ name: queue.retry.waitQueueName,
282
+ deadLetter: { exchange: retryExchange },
283
+ retry: { mode: "none" }
284
+ };
285
+ const waitQueue = queue.type === "quorum" ? {
286
+ ...baseWaitQueue,
287
+ type: queue.type,
288
+ durable: true
289
+ } : {
290
+ ...baseWaitQueue,
291
+ type: queue.type,
292
+ durable: queue.durable
293
+ };
294
+ return {
295
+ waitQueue,
296
+ waitExchange,
297
+ retryExchange,
298
+ waitQueueBinding: defineQueueBindingInternal(waitQueue, waitExchange, { arguments: {
299
+ "x-match": "all",
300
+ "x-wait-queue": waitQueue.name
301
+ } }),
302
+ retryQueueBinding: defineQueueBindingInternal(queue, retryExchange, { arguments: {
303
+ "x-match": "all",
304
+ "x-retry-queue": queue.name
305
+ } })
306
+ };
254
307
  }
308
+ //#endregion
309
+ //#region src/builder/queue.ts
255
310
  /**
256
- * Create TTL-backoff retry infrastructure (wait queue + bindings) for a queue.
311
+ * Resolve immediate-requeue retry options with defaults.
257
312
  * @internal
258
313
  */
259
- function createTtlBackoffInfrastructure(queue) {
260
- if (!queue.deadLetter) throw new Error(`Queue "${queue.name}" does not have a dead letter exchange configured. TTL-backoff retry requires deadLetter to be set on the queue.`);
261
- const dlx = queue.deadLetter.exchange;
262
- const waitQueueName = `${queue.name}-wait`;
263
- const waitQueue = {
264
- name: waitQueueName,
265
- type: "quorum",
266
- durable: queue.durable ?? true,
267
- deadLetter: {
268
- exchange: dlx,
269
- routingKey: queue.name
270
- },
271
- retry: resolveTtlBackoffOptions(void 0)
272
- };
314
+ function resolveImmediateRequeueOptions(options) {
273
315
  return {
274
- waitQueue,
275
- waitQueueBinding: defineQueueBindingInternal(waitQueue, dlx, { routingKey: waitQueueName }),
276
- mainQueueRetryBinding: defineQueueBindingInternal(queue, dlx, { routingKey: queue.name })
316
+ mode: "immediate-requeue",
317
+ maxRetries: options?.maxRetries ?? 3
277
318
  };
278
319
  }
279
320
  /**
280
- * Wrap a queue definition with TTL-backoff retry infrastructure.
321
+ * Resolve TTL-backoff retry options with defaults.
281
322
  * @internal
282
323
  */
283
- function wrapWithTtlBackoffInfrastructure(queue) {
284
- const infra = createTtlBackoffInfrastructure(queue);
324
+ function resolveTtlBackoffOptions(queueName, options) {
285
325
  return {
286
- __brand: "QueueWithTtlBackoffInfrastructure",
287
- queue,
288
- deadLetter: queue.deadLetter,
289
- ...infra
326
+ mode: "ttl-backoff",
327
+ maxRetries: options?.maxRetries ?? 3,
328
+ initialDelayMs: options?.initialDelayMs ?? 1e3,
329
+ maxDelayMs: options?.maxDelayMs ?? 3e4,
330
+ backoffMultiplier: options?.backoffMultiplier ?? 2,
331
+ jitter: options?.jitter ?? true,
332
+ waitQueueName: options?.waitQueueName ?? `${queueName}-wait`,
333
+ waitExchangeName: options?.waitExchangeName ?? "wait-exchange",
334
+ retryExchangeName: options?.retryExchangeName ?? "retry-exchange"
290
335
  };
291
336
  }
292
337
  function defineQueue(name, options) {
293
338
  const opts = options ?? {};
294
339
  const type = opts.type ?? "quorum";
295
- const baseProps = { name };
296
- if (opts.durable !== void 0) baseProps.durable = opts.durable;
297
- if (opts.autoDelete !== void 0) baseProps.autoDelete = opts.autoDelete;
298
- if (opts.deadLetter !== void 0) baseProps.deadLetter = opts.deadLetter;
299
- if (opts.arguments !== void 0) baseProps.arguments = opts.arguments;
340
+ const durable = opts.durable ?? true;
341
+ const baseProps = {
342
+ name,
343
+ ...opts.deadLetter !== void 0 && { deadLetter: opts.deadLetter },
344
+ ...opts.arguments !== void 0 && { arguments: opts.arguments }
345
+ };
346
+ const classicProps = {
347
+ ...opts.exclusive !== void 0 && { exclusive: opts.exclusive },
348
+ ...opts.autoDelete !== void 0 && { autoDelete: opts.autoDelete },
349
+ ...opts.maxPriority !== void 0 && { maxPriority: opts.maxPriority }
350
+ };
300
351
  if (type === "quorum") {
301
- const quorumOpts = opts;
302
- const inputRetry = quorumOpts.retry ?? { mode: "ttl-backoff" };
303
- if (inputRetry.mode === "quorum-native") {
304
- if (quorumOpts.deliveryLimit === void 0) throw new Error(`Queue "${name}" uses quorum-native retry mode but deliveryLimit is not configured. Quorum-native retry requires deliveryLimit to be set.`);
305
- }
306
- const retry = inputRetry.mode === "quorum-native" ? inputRetry : resolveTtlBackoffOptions(inputRetry);
307
- const queueDefinition = {
308
- ...baseProps,
309
- type: "quorum",
310
- retry
311
- };
312
- if (quorumOpts.deliveryLimit !== void 0) {
313
- if (quorumOpts.deliveryLimit < 1 || !Number.isInteger(quorumOpts.deliveryLimit)) throw new Error(`Invalid deliveryLimit: ${quorumOpts.deliveryLimit}. Must be a positive integer.`);
314
- queueDefinition.deliveryLimit = quorumOpts.deliveryLimit;
352
+ if (opts.durable === false) throw new Error("Non-durable queues are not supported with quorum type.");
353
+ if (opts.exclusive !== void 0) throw new Error("Exclusive queues are not supported with quorum type.");
354
+ if (opts.autoDelete !== void 0) throw new Error("Auto-deleting queues are not supported with quorum type.");
355
+ if (opts.maxPriority !== void 0) throw new Error("Priority queues are not supported with quorum type.");
356
+ } else if (opts.maxPriority !== void 0) {
357
+ if (opts.maxPriority < 1 || opts.maxPriority > 255) throw new Error(`Invalid maxPriority: ${opts.maxPriority}. Must be between 1 and 255. Recommended range: 1-10.`);
358
+ }
359
+ const inputRetry = opts.retry ?? { mode: "none" };
360
+ if (inputRetry.mode === "immediate-requeue" || inputRetry.mode === "ttl-backoff") {
361
+ if (inputRetry.maxRetries !== void 0) {
362
+ if (inputRetry.maxRetries < 1 || !Number.isInteger(inputRetry.maxRetries)) throw new Error(`Queue "${name}" uses ${inputRetry.mode} retry mode with invalid maxRetries: ${inputRetry.maxRetries}. Must be a positive integer.`);
315
363
  }
316
- if (retry.mode === "ttl-backoff" && queueDefinition.deadLetter) return wrapWithTtlBackoffInfrastructure(queueDefinition);
317
- return queueDefinition;
318
364
  }
319
- const classicOpts = opts;
320
- if (classicOpts.retry?.mode === "quorum-native") throw new Error(`Queue "${name}" uses quorum-native retry mode but is a classic queue. Quorum-native retry requires quorum queues (type: "quorum").`);
321
- const retry = resolveTtlBackoffOptions(classicOpts.retry);
322
- const queueDefinition = {
365
+ const retry = inputRetry.mode === "immediate-requeue" ? resolveImmediateRequeueOptions(inputRetry) : inputRetry.mode === "ttl-backoff" ? resolveTtlBackoffOptions(name, inputRetry) : inputRetry;
366
+ const baseQueueDefinition = {
323
367
  ...baseProps,
324
- type: "classic",
325
368
  retry
326
369
  };
327
- if (classicOpts.exclusive !== void 0) queueDefinition.exclusive = classicOpts.exclusive;
328
- if (classicOpts.maxPriority !== void 0) {
329
- if (classicOpts.maxPriority < 1 || classicOpts.maxPriority > 255) throw new Error(`Invalid maxPriority: ${classicOpts.maxPriority}. Must be between 1 and 255. Recommended range: 1-10.`);
330
- queueDefinition.arguments = {
331
- ...queueDefinition.arguments,
332
- "x-max-priority": classicOpts.maxPriority
333
- };
334
- }
335
- if (retry.mode === "ttl-backoff" && queueDefinition.deadLetter) return wrapWithTtlBackoffInfrastructure(queueDefinition);
336
- return queueDefinition;
337
- }
338
- /**
339
- * Create a quorum queue with quorum-native retry.
340
- *
341
- * This is a simplified helper that enforces best practices:
342
- * - Uses quorum queues (recommended for most use cases)
343
- * - Requires dead letter exchange for failed message handling
344
- * - Uses quorum-native retry mode (simpler than TTL-backoff)
345
- *
346
- * **When to use:**
347
- * - You want simple, immediate retries without exponential backoff
348
- * - You don't need configurable delays between retries
349
- * - You want the simplest retry configuration
350
- *
351
- * @param name - The queue name
352
- * @param options - Configuration options
353
- * @returns A quorum queue definition with quorum-native retry
354
- *
355
- * @example
356
- * ```typescript
357
- * const dlx = defineExchange('orders-dlx', 'direct', { durable: true });
358
- *
359
- * const orderQueue = defineQuorumQueue('order-processing', {
360
- * deadLetter: { exchange: dlx },
361
- * deliveryLimit: 3, // Retry up to 3 times
362
- * });
363
- *
364
- * // Use in a contract — exchanges, queues, and bindings are auto-extracted
365
- * const contract = defineContract({
366
- * publishers: { ... },
367
- * consumers: { processOrder: defineEventConsumer(event, orderQueue) },
368
- * });
369
- * ```
370
- *
371
- * @see defineQueue - For full queue configuration options
372
- * @see defineTtlBackoffQueue - For queues with exponential backoff retry
373
- */
374
- function defineQuorumQueue(name, options) {
375
- const { deadLetter, deliveryLimit, autoDelete, arguments: args } = options;
376
- const queueOptions = {
377
- type: "quorum",
378
- deadLetter,
379
- deliveryLimit,
380
- retry: { mode: "quorum-native" }
381
- };
382
- if (autoDelete !== void 0) queueOptions.autoDelete = autoDelete;
383
- if (args !== void 0) queueOptions.arguments = args;
384
- return defineQueue(name, queueOptions);
385
- }
386
- /**
387
- * Create a queue with TTL-backoff retry (exponential backoff).
388
- *
389
- * This is a simplified helper that enforces best practices:
390
- * - Uses quorum queues (recommended for most use cases)
391
- * - Requires dead letter exchange for retry routing
392
- * - Uses TTL-backoff retry mode with configurable delays
393
- * - Automatically generates wait queue and bindings
394
- *
395
- * **When to use:**
396
- * - You need exponential backoff between retries
397
- * - You want configurable delays (initial delay, max delay, jitter)
398
- * - You're processing messages that may need time before retry
399
- *
400
- * **Returns:** A `QueueWithTtlBackoffInfrastructure` object that includes the
401
- * main queue, wait queue, and bindings. Pass this directly to `defineContract`
402
- * and it will be expanded automatically.
403
- *
404
- * @param name - The queue name
405
- * @param options - Configuration options
406
- * @returns A queue with TTL-backoff infrastructure
407
- *
408
- * @example
409
- * ```typescript
410
- * const dlx = defineExchange('orders-dlx', 'direct', { durable: true });
411
- *
412
- * const orderQueue = defineTtlBackoffQueue('order-processing', {
413
- * deadLetter: { exchange: dlx },
414
- * maxRetries: 5,
415
- * initialDelayMs: 1000, // Start with 1s delay
416
- * maxDelayMs: 30000, // Cap at 30s
417
- * });
418
- *
419
- * // Use in a contract — wait queue, bindings, and DLX are auto-extracted
420
- * const contract = defineContract({
421
- * publishers: { ... },
422
- * consumers: { processOrder: defineEventConsumer(event, extractQueue(orderQueue)) },
423
- * });
424
- *
425
- * // To access the underlying queue definition (e.g., for the queue name):
426
- * import { extractQueue } from '@amqp-contract/contract';
427
- * const queueName = extractQueue(orderQueue).name;
428
- * ```
429
- *
430
- * @see defineQueue - For full queue configuration options
431
- * @see defineQuorumQueue - For queues with quorum-native retry (simpler, immediate retries)
432
- * @see extractQueue - To access the underlying queue definition
433
- */
434
- function defineTtlBackoffQueue(name, options) {
435
- const { deadLetter, maxRetries, initialDelayMs, maxDelayMs, backoffMultiplier, jitter, autoDelete, arguments: args } = options;
436
- const retryOptions = { mode: "ttl-backoff" };
437
- if (maxRetries !== void 0) retryOptions.maxRetries = maxRetries;
438
- if (initialDelayMs !== void 0) retryOptions.initialDelayMs = initialDelayMs;
439
- if (maxDelayMs !== void 0) retryOptions.maxDelayMs = maxDelayMs;
440
- if (backoffMultiplier !== void 0) retryOptions.backoffMultiplier = backoffMultiplier;
441
- if (jitter !== void 0) retryOptions.jitter = jitter;
442
- const queueOptions = {
443
- type: "quorum",
444
- deadLetter,
445
- retry: retryOptions
370
+ const queueDefinition = type === "quorum" ? {
371
+ ...baseQueueDefinition,
372
+ type,
373
+ durable: true
374
+ } : {
375
+ ...baseQueueDefinition,
376
+ ...classicProps,
377
+ type,
378
+ durable
446
379
  };
447
- if (autoDelete !== void 0) queueOptions.autoDelete = autoDelete;
448
- if (args !== void 0) queueOptions.arguments = args;
449
- return defineQueue(name, queueOptions);
380
+ if (retry.mode === "ttl-backoff") return wrapWithTtlBackoffInfrastructure(queueDefinition);
381
+ return queueDefinition;
450
382
  }
451
-
452
383
  //#endregion
453
384
  //#region src/builder/publisher.ts
454
385
  /**
@@ -463,7 +394,7 @@ function defineTtlBackoffQueue(name, options) {
463
394
  * @internal
464
395
  */
465
396
  function definePublisher(exchange, message, options) {
466
- if (exchange.type === "fanout") return {
397
+ if (exchange.type === "fanout" || exchange.type === "headers") return {
467
398
  exchange,
468
399
  message
469
400
  };
@@ -479,10 +410,9 @@ function definePublisher(exchange, message, options) {
479
410
  * @internal
480
411
  */
481
412
  function definePublisherInternal(exchange, message, options) {
482
- if (exchange.type === "fanout") return definePublisher(exchange, message, options);
413
+ if (exchange.type === "fanout" || exchange.type === "headers") return definePublisher(exchange, message, options);
483
414
  return definePublisher(exchange, message, options);
484
415
  }
485
-
486
416
  //#endregion
487
417
  //#region src/builder/consumer.ts
488
418
  /**
@@ -562,7 +492,7 @@ function extractConsumer(entry) {
562
492
  * ```typescript
563
493
  * import { z } from 'zod';
564
494
  *
565
- * const orderQueue = defineQueue('order-processing', { durable: true });
495
+ * const orderQueue = defineQueue('order-processing');
566
496
  * const orderMessage = defineMessage(
567
497
  * z.object({
568
498
  * orderId: z.string().uuid(),
@@ -591,12 +521,72 @@ function extractConsumer(entry) {
591
521
  */
592
522
  function defineConsumer(queue, message, options) {
593
523
  return {
594
- queue: extractQueue(queue),
524
+ queue,
595
525
  message,
596
526
  ...options
597
527
  };
598
528
  }
599
-
529
+ //#endregion
530
+ //#region src/builder/command.ts
531
+ /**
532
+ * Implementation of defineCommandConsumer.
533
+ * @internal
534
+ */
535
+ function defineCommandConsumer(queue, exchange, message, options) {
536
+ return {
537
+ __brand: "CommandConsumerConfig",
538
+ consumer: defineConsumer(queue, message),
539
+ binding: defineQueueBindingInternal(queue, exchange, options),
540
+ exchange,
541
+ queue,
542
+ message,
543
+ routingKey: options?.routingKey
544
+ };
545
+ }
546
+ /**
547
+ * Implementation of defineCommandPublisher.
548
+ * @internal
549
+ */
550
+ function defineCommandPublisher(commandConsumer, options) {
551
+ const { exchange: targetExchange, message, routingKey: consumerRoutingKey } = commandConsumer;
552
+ const publisherRoutingKey = options?.routingKey ?? consumerRoutingKey;
553
+ const bridgeExchange = options?.bridgeExchange;
554
+ if (bridgeExchange) {
555
+ const publisherOptions = {};
556
+ if (publisherRoutingKey !== void 0) publisherOptions.routingKey = publisherRoutingKey;
557
+ const publisher = definePublisherInternal(bridgeExchange, message, publisherOptions);
558
+ const e2eBindingOptions = {};
559
+ if (publisherRoutingKey !== void 0) e2eBindingOptions.routingKey = publisherRoutingKey;
560
+ return {
561
+ __brand: "BridgedPublisherConfig",
562
+ publisher,
563
+ exchangeBinding: bridgeExchange.type === "fanout" || bridgeExchange.type === "headers" ? defineExchangeBinding(targetExchange, bridgeExchange) : defineExchangeBinding(targetExchange, bridgeExchange, e2eBindingOptions),
564
+ bridgeExchange,
565
+ targetExchange
566
+ };
567
+ }
568
+ const publisherOptions = {};
569
+ if (publisherRoutingKey !== void 0) publisherOptions.routingKey = publisherRoutingKey;
570
+ return definePublisherInternal(targetExchange, message, publisherOptions);
571
+ }
572
+ /**
573
+ * Type guard to check if a value is a CommandConsumerConfig.
574
+ *
575
+ * @param value - The value to check
576
+ * @returns True if the value is a CommandConsumerConfig
577
+ */
578
+ function isCommandConsumerConfig(value) {
579
+ return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "CommandConsumerConfig";
580
+ }
581
+ /**
582
+ * Type guard to check if a value is a BridgedPublisherConfig.
583
+ *
584
+ * @param value - The value to check
585
+ * @returns True if the value is a BridgedPublisherConfig
586
+ */
587
+ function isBridgedPublisherConfig(value) {
588
+ return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "BridgedPublisherConfig";
589
+ }
600
590
  //#endregion
601
591
  //#region src/builder/event.ts
602
592
  /**
@@ -630,27 +620,23 @@ function defineEventConsumer(eventPublisher, queue, options) {
630
620
  const consumer = defineConsumer(queue, message);
631
621
  const exchangeBindingOptions = {};
632
622
  if (bindingRoutingKey !== void 0) exchangeBindingOptions.routingKey = bindingRoutingKey;
633
- const e2eBinding = sourceExchange.type === "fanout" ? defineExchangeBinding(bridgeExchange, sourceExchange) : defineExchangeBinding(bridgeExchange, sourceExchange, exchangeBindingOptions);
634
623
  return {
635
624
  __brand: "EventConsumerResult",
636
625
  consumer,
637
626
  binding,
638
627
  exchange: sourceExchange,
639
- queue: consumer.queue,
640
- deadLetterExchange: consumer.queue.deadLetter?.exchange,
641
- exchangeBinding: e2eBinding,
628
+ queue,
629
+ exchangeBinding: sourceExchange.type === "fanout" || sourceExchange.type === "headers" ? defineExchangeBinding(bridgeExchange, sourceExchange) : defineExchangeBinding(bridgeExchange, sourceExchange, exchangeBindingOptions),
642
630
  bridgeExchange
643
631
  };
644
632
  }
645
633
  const binding = defineQueueBindingInternal(queue, sourceExchange, bindingOptions);
646
- const consumer = defineConsumer(queue, message);
647
634
  return {
648
635
  __brand: "EventConsumerResult",
649
- consumer,
636
+ consumer: defineConsumer(queue, message),
650
637
  binding,
651
638
  exchange: sourceExchange,
652
- queue: consumer.queue,
653
- deadLetterExchange: consumer.queue.deadLetter?.exchange,
639
+ queue,
654
640
  exchangeBinding: void 0,
655
641
  bridgeExchange: void 0
656
642
  };
@@ -673,71 +659,6 @@ function isEventPublisherConfig(value) {
673
659
  function isEventConsumerResult(value) {
674
660
  return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "EventConsumerResult";
675
661
  }
676
-
677
- //#endregion
678
- //#region src/builder/command.ts
679
- /**
680
- * Implementation of defineCommandConsumer.
681
- * @internal
682
- */
683
- function defineCommandConsumer(queue, exchange, message, options) {
684
- const consumer = defineConsumer(queue, message);
685
- return {
686
- __brand: "CommandConsumerConfig",
687
- consumer,
688
- binding: defineQueueBindingInternal(queue, exchange, options),
689
- exchange,
690
- queue: consumer.queue,
691
- deadLetterExchange: consumer.queue.deadLetter?.exchange,
692
- message,
693
- routingKey: options?.routingKey
694
- };
695
- }
696
- /**
697
- * Implementation of defineCommandPublisher.
698
- * @internal
699
- */
700
- function defineCommandPublisher(commandConsumer, options) {
701
- const { exchange: targetExchange, message, routingKey: consumerRoutingKey } = commandConsumer;
702
- const publisherRoutingKey = options?.routingKey ?? consumerRoutingKey;
703
- const bridgeExchange = options?.bridgeExchange;
704
- if (bridgeExchange) {
705
- const publisherOptions = {};
706
- if (publisherRoutingKey !== void 0) publisherOptions.routingKey = publisherRoutingKey;
707
- const publisher = definePublisherInternal(bridgeExchange, message, publisherOptions);
708
- const e2eBindingOptions = {};
709
- if (publisherRoutingKey !== void 0) e2eBindingOptions.routingKey = publisherRoutingKey;
710
- return {
711
- __brand: "BridgedPublisherConfig",
712
- publisher,
713
- exchangeBinding: bridgeExchange.type === "fanout" ? defineExchangeBinding(targetExchange, bridgeExchange) : defineExchangeBinding(targetExchange, bridgeExchange, e2eBindingOptions),
714
- bridgeExchange,
715
- targetExchange
716
- };
717
- }
718
- const publisherOptions = {};
719
- if (publisherRoutingKey !== void 0) publisherOptions.routingKey = publisherRoutingKey;
720
- return definePublisherInternal(targetExchange, message, publisherOptions);
721
- }
722
- /**
723
- * Type guard to check if a value is a CommandConsumerConfig.
724
- *
725
- * @param value - The value to check
726
- * @returns True if the value is a CommandConsumerConfig
727
- */
728
- function isCommandConsumerConfig(value) {
729
- return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "CommandConsumerConfig";
730
- }
731
- /**
732
- * Type guard to check if a value is a BridgedPublisherConfig.
733
- *
734
- * @param value - The value to check
735
- * @returns True if the value is a BridgedPublisherConfig
736
- */
737
- function isBridgedPublisherConfig(value) {
738
- return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "BridgedPublisherConfig";
739
- }
740
-
741
662
  //#endregion
742
663
  //#region src/builder/contract.ts
743
664
  /**
@@ -769,12 +690,11 @@ function isBridgedPublisherConfig(value) {
769
690
  * import { z } from 'zod';
770
691
  *
771
692
  * // Define resources
772
- * const ordersExchange = defineExchange('orders', 'topic', { durable: true });
773
- * const dlx = defineExchange('orders-dlx', 'direct', { durable: true });
693
+ * const ordersExchange = defineExchange('orders');
694
+ * const dlx = defineExchange('orders-dlx', { type: 'direct' });
774
695
  * const orderQueue = defineQueue('order-processing', {
775
696
  * deadLetter: { exchange: dlx },
776
- * retry: { mode: 'quorum-native' },
777
- * deliveryLimit: 3,
697
+ * retry: { mode: 'immediate-requeue', maxRetries: 3 },
778
698
  * });
779
699
  * const orderMessage = defineMessage(
780
700
  * z.object({
@@ -807,13 +727,18 @@ function isBridgedPublisherConfig(value) {
807
727
  * ```
808
728
  */
809
729
  function defineContract(definition) {
810
- const { publishers: inputPublishers, consumers: inputConsumers } = definition;
730
+ const { publishers: inputPublishers, consumers: inputConsumers, rpcs: inputRpcs } = definition;
731
+ if (inputConsumers && inputRpcs) {
732
+ const collisions = Object.keys(inputConsumers).filter((name) => Object.hasOwn(inputRpcs, name));
733
+ if (collisions.length > 0) throw new Error(`defineContract: name collision between consumers and rpcs — keys must be disjoint. Conflicting names: ${collisions.join(", ")}`);
734
+ }
811
735
  const result = {
812
736
  exchanges: {},
813
737
  queues: {},
814
738
  bindings: {},
815
739
  publishers: {},
816
- consumers: {}
740
+ consumers: {},
741
+ rpcs: {}
817
742
  };
818
743
  if (inputPublishers && Object.keys(inputPublishers).length > 0) {
819
744
  const processedPublishers = {};
@@ -853,9 +778,10 @@ function defineContract(definition) {
853
778
  processedConsumers[name] = entry.consumer;
854
779
  consumerBindings[`${name}Binding`] = entry.binding;
855
780
  const queueEntry = entry.consumer.queue;
856
- queues[queueEntry.name] = queueEntry;
781
+ const queueDef = extractQueue(queueEntry);
782
+ queues[queueDef.name] = queueEntry;
857
783
  exchanges[entry.binding.exchange.name] = entry.binding.exchange;
858
- if (queueEntry.deadLetter?.exchange) exchanges[queueEntry.deadLetter.exchange.name] = queueEntry.deadLetter.exchange;
784
+ if (queueDef.deadLetter?.exchange) exchanges[queueDef.deadLetter.exchange.name] = queueDef.deadLetter.exchange;
859
785
  if (entry.exchangeBinding) consumerBindings[`${name}ExchangeBinding`] = entry.exchangeBinding;
860
786
  if (entry.bridgeExchange) exchanges[entry.bridgeExchange.name] = entry.bridgeExchange;
861
787
  if (entry.exchange) exchanges[entry.exchange.name] = entry.exchange;
@@ -863,22 +789,24 @@ function defineContract(definition) {
863
789
  processedConsumers[name] = entry.consumer;
864
790
  consumerBindings[`${name}Binding`] = entry.binding;
865
791
  const queueEntry = entry.consumer.queue;
866
- queues[queueEntry.name] = queueEntry;
792
+ const queueDef = extractQueue(queueEntry);
793
+ queues[queueDef.name] = queueEntry;
867
794
  exchanges[entry.exchange.name] = entry.exchange;
868
- if (queueEntry.deadLetter?.exchange) exchanges[queueEntry.deadLetter.exchange.name] = queueEntry.deadLetter.exchange;
795
+ if (queueDef.deadLetter?.exchange) exchanges[queueDef.deadLetter.exchange.name] = queueDef.deadLetter.exchange;
869
796
  } else {
870
797
  const consumer = entry;
871
798
  processedConsumers[name] = consumer;
872
799
  const queueEntry = consumer.queue;
873
- queues[queueEntry.name] = queueEntry;
874
- if (queueEntry.deadLetter?.exchange) exchanges[queueEntry.deadLetter.exchange.name] = queueEntry.deadLetter.exchange;
800
+ const queueDef = extractQueue(queueEntry);
801
+ queues[queueDef.name] = queueEntry;
802
+ if (queueDef.deadLetter?.exchange) exchanges[queueDef.deadLetter.exchange.name] = queueDef.deadLetter.exchange;
875
803
  }
876
- for (const queue of Object.values(queues)) if (queue.retry?.mode === "ttl-backoff" && queue.deadLetter) {
877
- const infra = createTtlBackoffInfrastructure(queue);
878
- queues[infra.waitQueue.name] = infra.waitQueue;
879
- consumerBindings[`${queue.name}WaitBinding`] = infra.waitQueueBinding;
880
- consumerBindings[`${queue.name}RetryBinding`] = infra.mainQueueRetryBinding;
881
- exchanges[queue.deadLetter.exchange.name] = queue.deadLetter.exchange;
804
+ for (const queueEntry of Object.values(queues)) if (isQueueWithTtlBackoffInfrastructure(queueEntry)) {
805
+ queues[queueEntry.waitQueue.name] = queueEntry.waitQueue;
806
+ consumerBindings[`${queueEntry.queue.name}WaitBinding`] = queueEntry.waitQueueBinding;
807
+ consumerBindings[`${queueEntry.queue.name}RetryBinding`] = queueEntry.retryQueueBinding;
808
+ exchanges[queueEntry.waitExchange.name] = queueEntry.waitExchange;
809
+ exchanges[queueEntry.retryExchange.name] = queueEntry.retryExchange;
882
810
  }
883
811
  result.consumers = processedConsumers;
884
812
  result.bindings = {
@@ -894,59 +822,75 @@ function defineContract(definition) {
894
822
  ...exchanges
895
823
  };
896
824
  }
825
+ if (inputRpcs && Object.keys(inputRpcs).length > 0) {
826
+ const processedRpcs = {};
827
+ const rpcQueues = {};
828
+ const rpcExchanges = {};
829
+ for (const [name, rpc] of Object.entries(inputRpcs)) {
830
+ processedRpcs[name] = rpc;
831
+ const queueDef = extractQueue(rpc.queue);
832
+ rpcQueues[queueDef.name] = rpc.queue;
833
+ if (queueDef.deadLetter?.exchange) rpcExchanges[queueDef.deadLetter.exchange.name] = queueDef.deadLetter.exchange;
834
+ }
835
+ result.rpcs = processedRpcs;
836
+ result.queues = {
837
+ ...result.queues,
838
+ ...rpcQueues
839
+ };
840
+ result.exchanges = {
841
+ ...result.exchanges,
842
+ ...rpcExchanges
843
+ };
844
+ }
897
845
  return result;
898
846
  }
899
-
900
847
  //#endregion
901
- //#region src/builder/ttl-backoff.ts
848
+ //#region src/builder/rpc.ts
902
849
  /**
903
- * Create TTL-backoff retry infrastructure for a queue.
850
+ * Define an RPC operation: a request/response pair flowing over a request
851
+ * queue with replies routed back via RabbitMQ direct reply-to.
904
852
  *
905
- * This builder helper generates the wait queue and bindings needed for TTL-backoff retry.
906
- * The generated infrastructure can be spread into a contract definition.
853
+ * RPC is bidirectional on both ends the worker handler consumes the request
854
+ * and produces the response; `client.call(name, request, options)` publishes
855
+ * the request and awaits the typed response. Both sides share the same
856
+ * definition, so request and response schemas cannot drift between them.
907
857
  *
908
- * TTL-backoff retry works by:
909
- * 1. Failed messages are sent to the DLX with routing key `{queueName}-wait`
910
- * 2. The wait queue receives these messages and holds them for a TTL period
911
- * 3. After TTL expires, messages are dead-lettered back to the DLX with routing key `{queueName}`
912
- * 4. The main queue receives the retried message via its binding to the DLX
858
+ * Plug the result into `defineContract({ rpcs: { name: ... } })`. RPCs do not
859
+ * appear in `publishers` or `consumers`.
913
860
  *
914
- * @param queue - The main queue definition (must have deadLetter configured)
915
- * @param options - Optional configuration for the wait queue
916
- * @param options.waitQueueDurable - Whether the wait queue should be durable (default: same as main queue)
917
- * @returns TTL-backoff retry infrastructure containing wait queue and bindings
918
- * @throws {Error} If the queue does not have a dead letter exchange configured
861
+ * @param queue - The queue that receives RPC requests. The queue name is
862
+ * used as the routing key on the AMQP default direct exchange.
863
+ * @param messages.request - Schema validated against incoming request payloads
864
+ * (server side) and outgoing requests (client side).
865
+ * @param messages.response - Schema validated against handler return values
866
+ * (server side) and incoming replies (client side).
919
867
  *
920
868
  * @example
921
869
  * ```typescript
922
- * const dlx = defineExchange('orders-dlx', 'direct', { durable: true });
923
- * const orderQueue = defineQueue('order-processing', {
924
- * type: 'quorum',
925
- * deadLetter: { exchange: dlx },
926
- * retry: {
927
- * mode: 'ttl-backoff',
928
- * maxRetries: 5,
929
- * initialDelayMs: 1000,
930
- * },
931
- * });
870
+ * import { defineQueue, defineMessage, defineRpc, defineContract } from '@amqp-contract/contract';
871
+ * import { z } from 'zod';
932
872
  *
933
- * // Infrastructure is auto-extracted when using defineContract:
934
- * const contract = defineContract({
935
- * publishers: { ... },
936
- * consumers: { processOrder: defineEventConsumer(event, extractQueue(orderQueue)) },
873
+ * const calculate = defineRpc(defineQueue('rpc.calculate'), {
874
+ * request: defineMessage(z.object({ a: z.number(), b: z.number() })),
875
+ * response: defineMessage(z.object({ sum: z.number() })),
937
876
  * });
938
- * // contract.queues includes the wait queue, contract.bindings includes retry bindings
939
877
  *
940
- * // Or generate manually for advanced use cases:
941
- * const retryInfra = defineTtlBackoffRetryInfrastructure(orderQueue);
878
+ * const contract = defineContract({ rpcs: { calculate } });
879
+ *
880
+ * // Server (worker): handler returns the typed response
881
+ * // handlers: { calculate: ({ payload }) => Future.value(Result.Ok({ sum: payload.a + payload.b })) }
882
+ *
883
+ * // Client: typed call with required timeout
884
+ * // const result = await client.call('calculate', { a: 1, b: 2 }, { timeoutMs: 5_000 }).toPromise();
942
885
  * ```
943
886
  */
944
- function defineTtlBackoffRetryInfrastructure(queueEntry, options) {
945
- const infra = createTtlBackoffInfrastructure(extractQueue(queueEntry));
946
- if (options?.waitQueueDurable !== void 0) infra.waitQueue.durable = options.waitQueueDurable;
947
- return infra;
887
+ function defineRpc(queue, messages) {
888
+ return {
889
+ queue,
890
+ request: messages.request,
891
+ response: messages.response
892
+ };
948
893
  }
949
-
950
894
  //#endregion
951
895
  exports.defineCommandConsumer = defineCommandConsumer;
952
896
  exports.defineCommandPublisher = defineCommandPublisher;
@@ -960,13 +904,11 @@ exports.defineMessage = defineMessage;
960
904
  exports.definePublisher = definePublisher;
961
905
  exports.defineQueue = defineQueue;
962
906
  exports.defineQueueBinding = defineQueueBinding;
963
- exports.defineQuorumQueue = defineQuorumQueue;
964
- exports.defineTtlBackoffQueue = defineTtlBackoffQueue;
965
- exports.defineTtlBackoffRetryInfrastructure = defineTtlBackoffRetryInfrastructure;
907
+ exports.defineRpc = defineRpc;
966
908
  exports.extractConsumer = extractConsumer;
967
909
  exports.extractQueue = extractQueue;
968
910
  exports.isBridgedPublisherConfig = isBridgedPublisherConfig;
969
911
  exports.isCommandConsumerConfig = isCommandConsumerConfig;
970
912
  exports.isEventConsumerResult = isEventConsumerResult;
971
913
  exports.isEventPublisherConfig = isEventPublisherConfig;
972
- exports.isQueueWithTtlBackoffInfrastructure = isQueueWithTtlBackoffInfrastructure;
914
+ exports.isQueueWithTtlBackoffInfrastructure = isQueueWithTtlBackoffInfrastructure;