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