@amqp-contract/contract 0.11.0 → 0.13.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,5 @@
1
1
 
2
- //#region src/builder.ts
2
+ //#region src/builder/exchange.ts
3
3
  /**
4
4
  * Define an AMQP exchange.
5
5
  *
@@ -20,6 +20,263 @@ function defineExchange(name, type, options) {
20
20
  ...options
21
21
  };
22
22
  }
23
+
24
+ //#endregion
25
+ //#region src/builder/message.ts
26
+ /**
27
+ * Define a message definition with payload and optional headers/metadata.
28
+ *
29
+ * A message definition specifies the schema for message payloads and headers using
30
+ * Standard Schema v1 compatible libraries (Zod, Valibot, ArkType, etc.).
31
+ * The schemas are used for automatic validation when publishing or consuming messages.
32
+ *
33
+ * @param payload - The payload schema (must be Standard Schema v1 compatible)
34
+ * @param options - Optional message metadata
35
+ * @param options.headers - Optional header schema for message headers
36
+ * @param options.summary - Brief description for documentation (used in AsyncAPI generation)
37
+ * @param options.description - Detailed description for documentation (used in AsyncAPI generation)
38
+ * @returns A message definition with inferred types
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * import { z } from 'zod';
43
+ *
44
+ * const orderMessage = defineMessage(
45
+ * z.object({
46
+ * orderId: z.string().uuid(),
47
+ * customerId: z.string().uuid(),
48
+ * amount: z.number().positive(),
49
+ * items: z.array(z.object({
50
+ * productId: z.string(),
51
+ * quantity: z.number().int().positive(),
52
+ * })),
53
+ * }),
54
+ * {
55
+ * summary: 'Order created event',
56
+ * description: 'Emitted when a new order is created in the system'
57
+ * }
58
+ * );
59
+ * ```
60
+ */
61
+ function defineMessage(payload, options) {
62
+ return {
63
+ payload,
64
+ ...options
65
+ };
66
+ }
67
+
68
+ //#endregion
69
+ //#region src/builder/binding.ts
70
+ /**
71
+ * Type guard to check if a queue entry is a QueueWithTtlBackoffInfrastructure.
72
+ * Duplicated here to avoid circular dependency with queue.ts.
73
+ * @internal
74
+ */
75
+ function isQueueWithTtlBackoffInfrastructure$1(entry) {
76
+ return typeof entry === "object" && entry !== null && "__brand" in entry && entry.__brand === "QueueWithTtlBackoffInfrastructure";
77
+ }
78
+ /**
79
+ * Extract the plain QueueDefinition from a QueueEntry.
80
+ * Duplicated here to avoid circular dependency with queue.ts.
81
+ * @internal
82
+ */
83
+ function extractQueueInternal(entry) {
84
+ if (isQueueWithTtlBackoffInfrastructure$1(entry)) return entry.queue;
85
+ return entry;
86
+ }
87
+ /**
88
+ * Define a binding between a queue and an exchange.
89
+ *
90
+ * This is the implementation function - use the type-specific overloads for better type safety.
91
+ *
92
+ * @param queue - The queue definition or queue with infrastructure to bind
93
+ * @param exchange - The exchange definition
94
+ * @param options - Optional binding configuration
95
+ * @returns A queue binding definition
96
+ * @internal
97
+ */
98
+ function defineQueueBinding(queue, exchange, options) {
99
+ const queueDef = extractQueueInternal(queue);
100
+ if (exchange.type === "fanout") return {
101
+ type: "queue",
102
+ queue: queueDef,
103
+ exchange,
104
+ ...options?.arguments && { arguments: options.arguments }
105
+ };
106
+ return {
107
+ type: "queue",
108
+ queue: queueDef,
109
+ exchange,
110
+ routingKey: options?.routingKey,
111
+ ...options?.arguments && { arguments: options.arguments }
112
+ };
113
+ }
114
+ /**
115
+ * Internal helper to call defineQueueBinding with proper type handling.
116
+ * Used by queue.ts to avoid circular dependency.
117
+ * @internal
118
+ */
119
+ function defineQueueBindingInternal(queue, exchange, options) {
120
+ if (exchange.type === "fanout") return defineQueueBinding(queue, exchange, options);
121
+ return defineQueueBinding(queue, exchange, options);
122
+ }
123
+ /**
124
+ * Define a binding between two exchanges (exchange-to-exchange routing).
125
+ *
126
+ * This is the implementation function - use the type-specific overloads for better type safety.
127
+ *
128
+ * @param destination - The destination exchange definition
129
+ * @param source - The source exchange definition
130
+ * @param options - Optional binding configuration
131
+ * @returns An exchange binding definition
132
+ * @internal
133
+ */
134
+ function defineExchangeBinding(destination, source, options) {
135
+ if (source.type === "fanout") return {
136
+ type: "exchange",
137
+ source,
138
+ destination,
139
+ ...options?.arguments && { arguments: options.arguments }
140
+ };
141
+ return {
142
+ type: "exchange",
143
+ source,
144
+ destination,
145
+ routingKey: options?.routingKey ?? "",
146
+ ...options?.arguments && { arguments: options.arguments }
147
+ };
148
+ }
149
+
150
+ //#endregion
151
+ //#region src/builder/queue.ts
152
+ /**
153
+ * Resolve TTL-backoff retry options with defaults applied.
154
+ * @internal
155
+ */
156
+ function resolveTtlBackoffOptions(options) {
157
+ return {
158
+ mode: "ttl-backoff",
159
+ maxRetries: options?.maxRetries ?? 3,
160
+ initialDelayMs: options?.initialDelayMs ?? 1e3,
161
+ maxDelayMs: options?.maxDelayMs ?? 3e4,
162
+ backoffMultiplier: options?.backoffMultiplier ?? 2,
163
+ jitter: options?.jitter ?? true
164
+ };
165
+ }
166
+ /**
167
+ * Type guard to check if a queue entry is a QueueWithTtlBackoffInfrastructure.
168
+ *
169
+ * When you configure a queue with TTL-backoff retry and a dead letter exchange,
170
+ * `defineQueue` returns a `QueueWithTtlBackoffInfrastructure` instead of a plain
171
+ * `QueueDefinition`. This type guard helps you distinguish between the two.
172
+ *
173
+ * **When to use:**
174
+ * - When you need to check the type of a queue entry at runtime
175
+ * - When writing generic code that handles both plain queues and infrastructure wrappers
176
+ *
177
+ * **Related functions:**
178
+ * - `extractQueue()` - Use this to get the underlying queue definition from either type
179
+ *
180
+ * @param entry - The queue entry to check
181
+ * @returns True if the entry is a QueueWithTtlBackoffInfrastructure, false otherwise
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * const queue = defineQueue('orders', {
186
+ * deadLetter: { exchange: dlx },
187
+ * retry: { mode: 'ttl-backoff' },
188
+ * });
189
+ *
190
+ * if (isQueueWithTtlBackoffInfrastructure(queue)) {
191
+ * // queue has .queue, .waitQueue, .waitQueueBinding, .mainQueueRetryBinding
192
+ * console.log('Wait queue:', queue.waitQueue.name);
193
+ * } else {
194
+ * // queue is a plain QueueDefinition
195
+ * console.log('Queue:', queue.name);
196
+ * }
197
+ * ```
198
+ */
199
+ function isQueueWithTtlBackoffInfrastructure(entry) {
200
+ return typeof entry === "object" && entry !== null && "__brand" in entry && entry.__brand === "QueueWithTtlBackoffInfrastructure";
201
+ }
202
+ /**
203
+ * Extract the plain QueueDefinition from a QueueEntry.
204
+ *
205
+ * **Why this function exists:**
206
+ * When you configure a queue with TTL-backoff retry and a dead letter exchange,
207
+ * `defineQueue` (or `defineTtlBackoffQueue`) returns a wrapper object that includes
208
+ * the main queue, wait queue, and bindings. This function extracts the underlying
209
+ * queue definition so you can access properties like `name`, `type`, etc.
210
+ *
211
+ * **When to use:**
212
+ * - When you need to access queue properties (name, type, deadLetter, etc.)
213
+ * - When passing a queue to functions that expect a plain QueueDefinition
214
+ * - Works safely on both plain queues and infrastructure wrappers
215
+ *
216
+ * **How it works:**
217
+ * - If the entry is a `QueueWithTtlBackoffInfrastructure`, returns `entry.queue`
218
+ * - Otherwise, returns the entry as-is (it's already a plain QueueDefinition)
219
+ *
220
+ * @param entry - The queue entry (either plain QueueDefinition or QueueWithTtlBackoffInfrastructure)
221
+ * @returns The plain QueueDefinition
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * import { defineQueue, defineTtlBackoffQueue, extractQueue } from '@amqp-contract/contract';
226
+ *
227
+ * // TTL-backoff queue returns a wrapper
228
+ * const orderQueue = defineTtlBackoffQueue('orders', {
229
+ * deadLetterExchange: dlx,
230
+ * maxRetries: 3,
231
+ * });
232
+ *
233
+ * // Use extractQueue to access the queue name
234
+ * const queueName = extractQueue(orderQueue).name; // 'orders'
235
+ *
236
+ * // Also works safely on plain queues
237
+ * const plainQueue = defineQueue('simple', { type: 'quorum', retry: { mode: 'quorum-native' } });
238
+ * const plainName = extractQueue(plainQueue).name; // 'simple'
239
+ *
240
+ * // Access other properties
241
+ * const queueDef = extractQueue(orderQueue);
242
+ * console.log(queueDef.name); // 'orders'
243
+ * console.log(queueDef.type); // 'quorum'
244
+ * console.log(queueDef.deadLetter); // { exchange: dlx, ... }
245
+ * ```
246
+ *
247
+ * @see isQueueWithTtlBackoffInfrastructure - Type guard to check if extraction is needed
248
+ * @see defineTtlBackoffQueue - Creates queues with TTL-backoff infrastructure
249
+ */
250
+ function extractQueue(entry) {
251
+ if (isQueueWithTtlBackoffInfrastructure(entry)) return entry.queue;
252
+ return entry;
253
+ }
254
+ /**
255
+ * Wrap a queue definition with TTL-backoff retry infrastructure.
256
+ * @internal
257
+ */
258
+ function wrapWithTtlBackoffInfrastructure(queue) {
259
+ 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.`);
260
+ const dlx = queue.deadLetter.exchange;
261
+ const waitQueueName = `${queue.name}-wait`;
262
+ const waitQueue = {
263
+ name: waitQueueName,
264
+ type: "quorum",
265
+ durable: queue.durable ?? true,
266
+ deadLetter: {
267
+ exchange: dlx,
268
+ routingKey: queue.name
269
+ },
270
+ retry: resolveTtlBackoffOptions(void 0)
271
+ };
272
+ return {
273
+ __brand: "QueueWithTtlBackoffInfrastructure",
274
+ queue,
275
+ waitQueue,
276
+ waitQueueBinding: defineQueueBindingInternal(waitQueue, dlx, { routingKey: waitQueueName }),
277
+ mainQueueRetryBinding: defineQueueBindingInternal(queue, dlx, { routingKey: queue.name })
278
+ };
279
+ }
23
280
  /**
24
281
  * Define an AMQP queue.
25
282
  *
@@ -92,22 +349,30 @@ function defineQueue(name, options) {
92
349
  if (opts.arguments !== void 0) baseProps.arguments = opts.arguments;
93
350
  if (type === "quorum") {
94
351
  const quorumOpts = opts;
352
+ const inputRetry = quorumOpts.retry ?? { mode: "ttl-backoff" };
353
+ if (inputRetry.mode === "quorum-native") {
354
+ 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.`);
355
+ }
356
+ const retry$1 = inputRetry.mode === "quorum-native" ? inputRetry : resolveTtlBackoffOptions(inputRetry);
95
357
  const queueDefinition$1 = {
96
358
  ...baseProps,
97
- type: "quorum"
359
+ type: "quorum",
360
+ retry: retry$1
98
361
  };
99
362
  if (quorumOpts.deliveryLimit !== void 0) {
100
363
  if (quorumOpts.deliveryLimit < 1 || !Number.isInteger(quorumOpts.deliveryLimit)) throw new Error(`Invalid deliveryLimit: ${quorumOpts.deliveryLimit}. Must be a positive integer.`);
101
364
  queueDefinition$1.deliveryLimit = quorumOpts.deliveryLimit;
102
365
  }
103
- if (quorumOpts.retry !== void 0) queueDefinition$1.retry = quorumOpts.retry;
104
- if (quorumOpts.retry?.mode === "ttl-backoff" && queueDefinition$1.deadLetter) return wrapWithTtlBackoffInfrastructure(queueDefinition$1);
366
+ if (retry$1.mode === "ttl-backoff" && queueDefinition$1.deadLetter) return wrapWithTtlBackoffInfrastructure(queueDefinition$1);
105
367
  return queueDefinition$1;
106
368
  }
107
369
  const classicOpts = opts;
370
+ 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").`);
371
+ const retry = resolveTtlBackoffOptions(classicOpts.retry);
108
372
  const queueDefinition = {
109
373
  ...baseProps,
110
- type: "classic"
374
+ type: "classic",
375
+ retry
111
376
  };
112
377
  if (classicOpts.exclusive !== void 0) queueDefinition.exclusive = classicOpts.exclusive;
113
378
  if (classicOpts.maxPriority !== void 0) {
@@ -117,129 +382,132 @@ function defineQueue(name, options) {
117
382
  "x-max-priority": classicOpts.maxPriority
118
383
  };
119
384
  }
120
- if (classicOpts.retry !== void 0) queueDefinition.retry = classicOpts.retry;
121
- if (classicOpts.retry?.mode === "ttl-backoff" && queueDefinition.deadLetter) return wrapWithTtlBackoffInfrastructure(queueDefinition);
385
+ if (retry.mode === "ttl-backoff" && queueDefinition.deadLetter) return wrapWithTtlBackoffInfrastructure(queueDefinition);
122
386
  return queueDefinition;
123
387
  }
124
388
  /**
125
- * Wrap a queue definition with TTL-backoff retry infrastructure.
126
- * @internal
127
- */
128
- function wrapWithTtlBackoffInfrastructure(queue) {
129
- 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.`);
130
- const dlx = queue.deadLetter.exchange;
131
- const waitQueueName = `${queue.name}-wait`;
132
- const waitQueue = {
133
- name: waitQueueName,
134
- type: "quorum",
135
- durable: queue.durable ?? true,
136
- deadLetter: {
137
- exchange: dlx,
138
- routingKey: queue.name
139
- }
140
- };
141
- return {
142
- __brand: "QueueWithTtlBackoffInfrastructure",
143
- queue,
144
- waitQueue,
145
- waitQueueBinding: callDefineQueueBinding(waitQueue, dlx, { routingKey: waitQueueName }),
146
- mainQueueRetryBinding: callDefineQueueBinding(queue, dlx, { routingKey: queue.name })
147
- };
148
- }
149
- /**
150
- * Define a message definition with payload and optional headers/metadata.
389
+ * Create a quorum queue with quorum-native retry.
151
390
  *
152
- * A message definition specifies the schema for message payloads and headers using
153
- * Standard Schema v1 compatible libraries (Zod, Valibot, ArkType, etc.).
154
- * The schemas are used for automatic validation when publishing or consuming messages.
391
+ * This is a simplified helper that enforces best practices:
392
+ * - Uses quorum queues (recommended for most use cases)
393
+ * - Requires dead letter exchange for failed message handling
394
+ * - Uses quorum-native retry mode (simpler than TTL-backoff)
155
395
  *
156
- * @param payload - The payload schema (must be Standard Schema v1 compatible)
157
- * @param options - Optional message metadata
158
- * @param options.headers - Optional header schema for message headers
159
- * @param options.summary - Brief description for documentation (used in AsyncAPI generation)
160
- * @param options.description - Detailed description for documentation (used in AsyncAPI generation)
161
- * @returns A message definition with inferred types
396
+ * **When to use:**
397
+ * - You want simple, immediate retries without exponential backoff
398
+ * - You don't need configurable delays between retries
399
+ * - You want the simplest retry configuration
400
+ *
401
+ * @param name - The queue name
402
+ * @param options - Configuration options
403
+ * @returns A quorum queue definition with quorum-native retry
162
404
  *
163
405
  * @example
164
406
  * ```typescript
165
- * import { z } from 'zod';
407
+ * const dlx = defineExchange('orders-dlx', 'direct', { durable: true });
166
408
  *
167
- * const orderMessage = defineMessage(
168
- * z.object({
169
- * orderId: z.string().uuid(),
170
- * customerId: z.string().uuid(),
171
- * amount: z.number().positive(),
172
- * items: z.array(z.object({
173
- * productId: z.string(),
174
- * quantity: z.number().int().positive(),
175
- * })),
176
- * }),
177
- * {
178
- * summary: 'Order created event',
179
- * description: 'Emitted when a new order is created in the system'
180
- * }
181
- * );
409
+ * const orderQueue = defineQuorumQueue('order-processing', {
410
+ * deadLetterExchange: dlx,
411
+ * deliveryLimit: 3, // Retry up to 3 times
412
+ * });
413
+ *
414
+ * const contract = defineContract({
415
+ * exchanges: { dlx },
416
+ * queues: { orderProcessing: orderQueue },
417
+ * // ...
418
+ * });
182
419
  * ```
420
+ *
421
+ * @see defineQueue - For full queue configuration options
422
+ * @see defineTtlBackoffQueue - For queues with exponential backoff retry
183
423
  */
184
- function defineMessage(payload, options) {
185
- return {
186
- payload,
187
- ...options
424
+ function defineQuorumQueue(name, options) {
425
+ const { deadLetterExchange, deadLetterRoutingKey, deliveryLimit, autoDelete, arguments: args } = options;
426
+ const queueOptions = {
427
+ type: "quorum",
428
+ deadLetter: deadLetterRoutingKey ? {
429
+ exchange: deadLetterExchange,
430
+ routingKey: deadLetterRoutingKey
431
+ } : { exchange: deadLetterExchange },
432
+ deliveryLimit,
433
+ retry: { mode: "quorum-native" }
188
434
  };
435
+ if (autoDelete !== void 0) queueOptions.autoDelete = autoDelete;
436
+ if (args !== void 0) queueOptions.arguments = args;
437
+ return defineQueue(name, queueOptions);
189
438
  }
190
439
  /**
191
- * Define a binding between a queue and an exchange.
440
+ * Create a queue with TTL-backoff retry (exponential backoff).
192
441
  *
193
- * This is the implementation function - use the type-specific overloads for better type safety.
442
+ * This is a simplified helper that enforces best practices:
443
+ * - Uses quorum queues (recommended for most use cases)
444
+ * - Requires dead letter exchange for retry routing
445
+ * - Uses TTL-backoff retry mode with configurable delays
446
+ * - Automatically generates wait queue and bindings
194
447
  *
195
- * @param queue - The queue definition or queue with infrastructure to bind
196
- * @param exchange - The exchange definition
197
- * @param options - Optional binding configuration
198
- * @returns A queue binding definition
199
- * @internal
200
- */
201
- function defineQueueBinding(queue, exchange, options) {
202
- const queueDef = extractQueue(queue);
203
- if (exchange.type === "fanout") return {
204
- type: "queue",
205
- queue: queueDef,
206
- exchange,
207
- ...options?.arguments && { arguments: options.arguments }
208
- };
209
- return {
210
- type: "queue",
211
- queue: queueDef,
212
- exchange,
213
- routingKey: options?.routingKey,
214
- ...options?.arguments && { arguments: options.arguments }
215
- };
216
- }
217
- /**
218
- * Define a binding between two exchanges (exchange-to-exchange routing).
448
+ * **When to use:**
449
+ * - You need exponential backoff between retries
450
+ * - You want configurable delays (initial delay, max delay, jitter)
451
+ * - You're processing messages that may need time before retry
219
452
  *
220
- * This is the implementation function - use the type-specific overloads for better type safety.
453
+ * **Returns:** A `QueueWithTtlBackoffInfrastructure` object that includes the
454
+ * main queue, wait queue, and bindings. Pass this directly to `defineContract`
455
+ * and it will be expanded automatically.
221
456
  *
222
- * @param destination - The destination exchange definition
223
- * @param source - The source exchange definition
224
- * @param options - Optional binding configuration
225
- * @returns An exchange binding definition
226
- * @internal
457
+ * @param name - The queue name
458
+ * @param options - Configuration options
459
+ * @returns A queue with TTL-backoff infrastructure
460
+ *
461
+ * @example
462
+ * ```typescript
463
+ * const dlx = defineExchange('orders-dlx', 'direct', { durable: true });
464
+ *
465
+ * const orderQueue = defineTtlBackoffQueue('order-processing', {
466
+ * deadLetterExchange: dlx,
467
+ * maxRetries: 5,
468
+ * initialDelayMs: 1000, // Start with 1s delay
469
+ * maxDelayMs: 30000, // Cap at 30s
470
+ * });
471
+ *
472
+ * const contract = defineContract({
473
+ * exchanges: { dlx },
474
+ * queues: { orderProcessing: orderQueue }, // Wait queue auto-added
475
+ * // ... bindings auto-generated
476
+ * });
477
+ *
478
+ * // To access the underlying queue definition (e.g., for the queue name):
479
+ * import { extractQueue } from '@amqp-contract/contract';
480
+ * const queueName = extractQueue(orderQueue).name;
481
+ * ```
482
+ *
483
+ * @see defineQueue - For full queue configuration options
484
+ * @see defineQuorumQueue - For queues with quorum-native retry (simpler, immediate retries)
485
+ * @see extractQueue - To access the underlying queue definition
227
486
  */
228
- function defineExchangeBinding(destination, source, options) {
229
- if (source.type === "fanout") return {
230
- type: "exchange",
231
- source,
232
- destination,
233
- ...options?.arguments && { arguments: options.arguments }
234
- };
235
- return {
236
- type: "exchange",
237
- source,
238
- destination,
239
- routingKey: options?.routingKey ?? "",
240
- ...options?.arguments && { arguments: options.arguments }
487
+ function defineTtlBackoffQueue(name, options) {
488
+ const { deadLetterExchange, deadLetterRoutingKey, maxRetries, initialDelayMs, maxDelayMs, backoffMultiplier, jitter, autoDelete, arguments: args } = options;
489
+ const deadLetter = deadLetterRoutingKey ? {
490
+ exchange: deadLetterExchange,
491
+ routingKey: deadLetterRoutingKey
492
+ } : { exchange: deadLetterExchange };
493
+ const retryOptions = { mode: "ttl-backoff" };
494
+ if (maxRetries !== void 0) retryOptions.maxRetries = maxRetries;
495
+ if (initialDelayMs !== void 0) retryOptions.initialDelayMs = initialDelayMs;
496
+ if (maxDelayMs !== void 0) retryOptions.maxDelayMs = maxDelayMs;
497
+ if (backoffMultiplier !== void 0) retryOptions.backoffMultiplier = backoffMultiplier;
498
+ if (jitter !== void 0) retryOptions.jitter = jitter;
499
+ const queueOptions = {
500
+ type: "quorum",
501
+ deadLetter,
502
+ retry: retryOptions
241
503
  };
504
+ if (autoDelete !== void 0) queueOptions.autoDelete = autoDelete;
505
+ if (args !== void 0) queueOptions.arguments = args;
506
+ return defineQueue(name, queueOptions);
242
507
  }
508
+
509
+ //#endregion
510
+ //#region src/builder/publisher.ts
243
511
  /**
244
512
  * Define a message publisher.
245
513
  *
@@ -263,6 +531,18 @@ function definePublisher(exchange, message, options) {
263
531
  };
264
532
  }
265
533
  /**
534
+ * Helper to call definePublisher with proper type handling.
535
+ * Type safety is enforced by overloaded public function signatures.
536
+ * @internal
537
+ */
538
+ function definePublisherInternal(exchange, message, options) {
539
+ if (exchange.type === "fanout") return definePublisher(exchange, message, options);
540
+ return definePublisher(exchange, message, options);
541
+ }
542
+
543
+ //#endregion
544
+ //#region src/builder/consumer.ts
545
+ /**
266
546
  * Define a message consumer.
267
547
  *
268
548
  * A consumer receives and processes messages from a queue. The message schema is validated
@@ -271,6 +551,19 @@ function definePublisher(exchange, message, options) {
271
551
  * Consumers are associated with a specific queue and message type. When you create a worker
272
552
  * with this consumer, it will process messages from the queue according to the schema.
273
553
  *
554
+ * **Which pattern to use:**
555
+ *
556
+ * | Pattern | Best for | Description |
557
+ * |---------|----------|-------------|
558
+ * | `definePublisher` + `defineConsumer` | Independent definition | Define publishers and consumers separately with manual schema consistency |
559
+ * | `defineEventPublisher` + `defineEventConsumer` | Event broadcasting | Define event publisher first, create consumers that subscribe to it |
560
+ * | `defineCommandConsumer` + `defineCommandPublisher` | Task queues | Define command consumer first, create publishers that send commands to it |
561
+ *
562
+ * Use `defineCommandConsumer` when:
563
+ * - One consumer receives from multiple publishers
564
+ * - You want automatic schema consistency between consumer and publishers
565
+ * - You're building task queue or command patterns
566
+ *
274
567
  * @param queue - The queue definition to consume from
275
568
  * @param message - The message definition with payload schema
276
569
  * @param options - Optional consumer configuration
@@ -303,6 +596,9 @@ function definePublisher(exchange, message, options) {
303
596
  * // connection
304
597
  * // });
305
598
  * ```
599
+ *
600
+ * @see defineCommandConsumer - For task queue patterns with automatic schema consistency
601
+ * @see defineEventPublisher - For event-driven patterns with automatic schema consistency
306
602
  */
307
603
  function defineConsumer(queue, message, options) {
308
604
  return {
@@ -311,6 +607,99 @@ function defineConsumer(queue, message, options) {
311
607
  ...options
312
608
  };
313
609
  }
610
+
611
+ //#endregion
612
+ //#region src/builder/event.ts
613
+ /**
614
+ * Implementation of defineEventPublisher.
615
+ * @internal
616
+ */
617
+ function defineEventPublisher(exchange, message, options) {
618
+ const config = {
619
+ __brand: "EventPublisherConfig",
620
+ exchange,
621
+ message,
622
+ routingKey: options?.routingKey
623
+ };
624
+ if (options?.arguments !== void 0) config.arguments = options.arguments;
625
+ return config;
626
+ }
627
+ /**
628
+ * Implementation of defineEventConsumer.
629
+ * @internal
630
+ */
631
+ function defineEventConsumer(eventPublisher, queue, options) {
632
+ const { exchange, message, routingKey: publisherRoutingKey } = eventPublisher;
633
+ const bindingRoutingKey = options?.routingKey ?? publisherRoutingKey;
634
+ const bindingOptions = {};
635
+ if (bindingRoutingKey !== void 0) bindingOptions.routingKey = bindingRoutingKey;
636
+ const bindingArguments = options?.arguments ?? eventPublisher.arguments;
637
+ if (bindingArguments !== void 0) bindingOptions.arguments = bindingArguments;
638
+ const binding = defineQueueBindingInternal(queue, exchange, bindingOptions);
639
+ return {
640
+ __brand: "EventConsumerResult",
641
+ consumer: defineConsumer(queue, message),
642
+ binding
643
+ };
644
+ }
645
+ /**
646
+ * Type guard to check if a value is an EventPublisherConfig.
647
+ *
648
+ * @param value - The value to check
649
+ * @returns True if the value is an EventPublisherConfig
650
+ */
651
+ function isEventPublisherConfig(value) {
652
+ return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "EventPublisherConfig";
653
+ }
654
+ /**
655
+ * Type guard to check if a value is an EventConsumerResult.
656
+ *
657
+ * @param value - The value to check
658
+ * @returns True if the value is an EventConsumerResult
659
+ */
660
+ function isEventConsumerResult(value) {
661
+ return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "EventConsumerResult";
662
+ }
663
+
664
+ //#endregion
665
+ //#region src/builder/command.ts
666
+ /**
667
+ * Implementation of defineCommandConsumer.
668
+ * @internal
669
+ */
670
+ function defineCommandConsumer(queue, exchange, message, options) {
671
+ return {
672
+ __brand: "CommandConsumerConfig",
673
+ consumer: defineConsumer(queue, message),
674
+ binding: defineQueueBindingInternal(queue, exchange, options),
675
+ exchange,
676
+ message,
677
+ routingKey: options?.routingKey
678
+ };
679
+ }
680
+ /**
681
+ * Implementation of defineCommandPublisher.
682
+ * @internal
683
+ */
684
+ function defineCommandPublisher(commandConsumer, options) {
685
+ const { exchange, message, routingKey: consumerRoutingKey } = commandConsumer;
686
+ const publisherRoutingKey = options?.routingKey ?? consumerRoutingKey;
687
+ const publisherOptions = {};
688
+ if (publisherRoutingKey !== void 0) publisherOptions.routingKey = publisherRoutingKey;
689
+ return definePublisherInternal(exchange, message, publisherOptions);
690
+ }
691
+ /**
692
+ * Type guard to check if a value is a CommandConsumerConfig.
693
+ *
694
+ * @param value - The value to check
695
+ * @returns True if the value is a CommandConsumerConfig
696
+ */
697
+ function isCommandConsumerConfig(value) {
698
+ return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "CommandConsumerConfig";
699
+ }
700
+
701
+ //#endregion
702
+ //#region src/builder/contract.ts
314
703
  /**
315
704
  * Define an AMQP contract.
316
705
  *
@@ -381,138 +770,53 @@ function defineConsumer(queue, message, options) {
381
770
  * ```
382
771
  */
383
772
  function defineContract(definition) {
384
- if (!definition.queues || Object.keys(definition.queues).length === 0) return definition;
385
- const queues = definition.queues;
386
- const expandedQueues = {};
387
- const autoBindings = {};
388
- for (const [name, entry] of Object.entries(queues)) if (isQueueWithTtlBackoffInfrastructure(entry)) {
389
- expandedQueues[name] = entry.queue;
390
- expandedQueues[`${name}Wait`] = entry.waitQueue;
391
- autoBindings[`${name}WaitBinding`] = entry.waitQueueBinding;
392
- autoBindings[`${name}RetryBinding`] = entry.mainQueueRetryBinding;
393
- } else expandedQueues[name] = entry;
394
- if (Object.keys(autoBindings).length > 0) {
395
- const mergedBindings = {
396
- ...definition.bindings,
397
- ...autoBindings
398
- };
399
- return {
400
- ...definition,
401
- queues: expandedQueues,
402
- bindings: mergedBindings
773
+ const { publishers: inputPublishers, consumers: inputConsumers, ...rest } = definition;
774
+ const result = rest;
775
+ if (definition.queues && Object.keys(definition.queues).length > 0) {
776
+ const expandedQueues = {};
777
+ const queueBindings = {};
778
+ for (const [name, entry] of Object.entries(definition.queues)) if (isQueueWithTtlBackoffInfrastructure(entry)) {
779
+ expandedQueues[name] = entry.queue;
780
+ expandedQueues[`${name}Wait`] = entry.waitQueue;
781
+ queueBindings[`${name}WaitBinding`] = entry.waitQueueBinding;
782
+ queueBindings[`${name}RetryBinding`] = entry.mainQueueRetryBinding;
783
+ } else expandedQueues[name] = entry;
784
+ result.queues = expandedQueues;
785
+ if (Object.keys(queueBindings).length > 0) result.bindings = {
786
+ ...result.bindings,
787
+ ...queueBindings
403
788
  };
404
789
  }
405
- return {
406
- ...definition,
407
- queues: expandedQueues
408
- };
409
- }
410
- /**
411
- * Type guard to check if a queue entry is a QueueWithTtlBackoffInfrastructure.
412
- * @internal
413
- */
414
- function isQueueWithTtlBackoffInfrastructure(entry) {
415
- return typeof entry === "object" && entry !== null && "__brand" in entry && entry.__brand === "QueueWithTtlBackoffInfrastructure";
416
- }
417
- /**
418
- * Extract the plain QueueDefinition from a QueueEntry.
419
- * If the entry is a QueueWithTtlBackoffInfrastructure, returns the inner queue.
420
- * Otherwise, returns the entry as-is.
421
- *
422
- * @param entry - The queue entry (either plain QueueDefinition or QueueWithTtlBackoffInfrastructure)
423
- * @returns The plain QueueDefinition
424
- *
425
- * @example
426
- * ```typescript
427
- * const queue = defineQueue('orders', { retry: { mode: 'ttl-backoff' }, deadLetter: { exchange: dlx } });
428
- * const plainQueue = extractQueue(queue); // Returns the inner QueueDefinition
429
- * ```
430
- */
431
- function extractQueue(entry) {
432
- if (isQueueWithTtlBackoffInfrastructure(entry)) return entry.queue;
433
- return entry;
434
- }
435
- /**
436
- * Helper to call definePublisher with proper type handling.
437
- * Type safety is enforced by overloaded public function signatures.
438
- * @internal
439
- */
440
- function callDefinePublisher(exchange, message, options) {
441
- if (exchange.type === "fanout") return definePublisher(exchange, message, options);
442
- return definePublisher(exchange, message, options);
443
- }
444
- /**
445
- * Helper to call defineQueueBinding with proper type handling.
446
- * Type safety is enforced by overloaded public function signatures.
447
- * @internal
448
- */
449
- function callDefineQueueBinding(queue, exchange, options) {
450
- if (exchange.type === "fanout") return defineQueueBinding(queue, exchange, options);
451
- return defineQueueBinding(queue, exchange, options);
452
- }
453
- /**
454
- * Implementation of definePublisherFirst.
455
- * @internal
456
- */
457
- function definePublisherFirst(exchange, message, options) {
458
- const publisher = callDefinePublisher(exchange, message, options);
459
- if (exchange.type === "topic") {
460
- const createConsumer$1 = (queue, routingKey) => {
461
- const binding = callDefineQueueBinding(queue, exchange, routingKey ? {
462
- ...options,
463
- routingKey
464
- } : options);
465
- return {
466
- consumer: defineConsumer(queue, message),
467
- binding
468
- };
469
- };
470
- return {
471
- publisher,
472
- createConsumer: createConsumer$1
473
- };
790
+ if (inputPublishers && Object.keys(inputPublishers).length > 0) {
791
+ const processedPublishers = {};
792
+ for (const [name, entry] of Object.entries(inputPublishers)) if (isEventPublisherConfig(entry)) {
793
+ const publisherOptions = {};
794
+ if (entry.routingKey !== void 0) publisherOptions.routingKey = entry.routingKey;
795
+ processedPublishers[name] = definePublisherInternal(entry.exchange, entry.message, publisherOptions);
796
+ } else processedPublishers[name] = entry;
797
+ result.publishers = processedPublishers;
474
798
  }
475
- const createConsumer = (queue) => {
476
- const binding = callDefineQueueBinding(queue, exchange, options);
477
- return {
478
- consumer: defineConsumer(queue, message),
479
- binding
480
- };
481
- };
482
- return {
483
- publisher,
484
- createConsumer
485
- };
486
- }
487
- /**
488
- * Implementation of defineConsumerFirst.
489
- * @internal
490
- */
491
- function defineConsumerFirst(queue, exchange, message, options) {
492
- const consumer = defineConsumer(queue, message);
493
- const binding = callDefineQueueBinding(queue, exchange, options);
494
- if (exchange.type === "topic") {
495
- const createPublisher$1 = (routingKey) => {
496
- return callDefinePublisher(exchange, message, {
497
- ...options,
498
- routingKey
499
- });
500
- };
501
- return {
502
- consumer,
503
- binding,
504
- createPublisher: createPublisher$1
799
+ if (inputConsumers && Object.keys(inputConsumers).length > 0) {
800
+ const processedConsumers = {};
801
+ const consumerBindings = {};
802
+ for (const [name, entry] of Object.entries(inputConsumers)) if (isEventConsumerResult(entry)) {
803
+ processedConsumers[name] = entry.consumer;
804
+ consumerBindings[`${name}Binding`] = entry.binding;
805
+ } else if (isCommandConsumerConfig(entry)) {
806
+ processedConsumers[name] = entry.consumer;
807
+ consumerBindings[`${name}Binding`] = entry.binding;
808
+ } else processedConsumers[name] = entry;
809
+ result.consumers = processedConsumers;
810
+ if (Object.keys(consumerBindings).length > 0) result.bindings = {
811
+ ...result.bindings,
812
+ ...consumerBindings
505
813
  };
506
814
  }
507
- const createPublisher = () => {
508
- return callDefinePublisher(exchange, message, options);
509
- };
510
- return {
511
- consumer,
512
- binding,
513
- createPublisher
514
- };
815
+ return result;
515
816
  }
817
+
818
+ //#endregion
819
+ //#region src/builder/ttl-backoff.ts
516
820
  /**
517
821
  * Create TTL-backoff retry infrastructure for a queue.
518
822
  *
@@ -578,21 +882,29 @@ function defineTtlBackoffRetryInfrastructure(queueEntry, options) {
578
882
  });
579
883
  return {
580
884
  waitQueue,
581
- waitQueueBinding: callDefineQueueBinding(waitQueue, dlx, { routingKey: waitQueueName }),
582
- mainQueueRetryBinding: callDefineQueueBinding(queue, dlx, { routingKey: queue.name })
885
+ waitQueueBinding: defineQueueBindingInternal(waitQueue, dlx, { routingKey: waitQueueName }),
886
+ mainQueueRetryBinding: defineQueueBindingInternal(queue, dlx, { routingKey: queue.name })
583
887
  };
584
888
  }
585
889
 
586
890
  //#endregion
891
+ exports.defineCommandConsumer = defineCommandConsumer;
892
+ exports.defineCommandPublisher = defineCommandPublisher;
587
893
  exports.defineConsumer = defineConsumer;
588
- exports.defineConsumerFirst = defineConsumerFirst;
589
894
  exports.defineContract = defineContract;
895
+ exports.defineEventConsumer = defineEventConsumer;
896
+ exports.defineEventPublisher = defineEventPublisher;
590
897
  exports.defineExchange = defineExchange;
591
898
  exports.defineExchangeBinding = defineExchangeBinding;
592
899
  exports.defineMessage = defineMessage;
593
900
  exports.definePublisher = definePublisher;
594
- exports.definePublisherFirst = definePublisherFirst;
595
901
  exports.defineQueue = defineQueue;
596
902
  exports.defineQueueBinding = defineQueueBinding;
903
+ exports.defineQuorumQueue = defineQuorumQueue;
904
+ exports.defineTtlBackoffQueue = defineTtlBackoffQueue;
597
905
  exports.defineTtlBackoffRetryInfrastructure = defineTtlBackoffRetryInfrastructure;
598
- exports.extractQueue = extractQueue;
906
+ exports.extractQueue = extractQueue;
907
+ exports.isCommandConsumerConfig = isCommandConsumerConfig;
908
+ exports.isEventConsumerResult = isEventConsumerResult;
909
+ exports.isEventPublisherConfig = isEventPublisherConfig;
910
+ exports.isQueueWithTtlBackoffInfrastructure = isQueueWithTtlBackoffInfrastructure;