@amqp-contract/contract 0.13.0 → 0.15.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/README.md +9 -13
- package/dist/index.cjs +169 -141
- package/dist/index.d.cts +288 -157
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +288 -157
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +168 -142
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +522 -265
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -36,7 +36,6 @@ import {
|
|
|
36
36
|
defineContract,
|
|
37
37
|
defineExchange,
|
|
38
38
|
defineQueue,
|
|
39
|
-
definePublisher,
|
|
40
39
|
defineMessage,
|
|
41
40
|
} from "@amqp-contract/contract";
|
|
42
41
|
import { z } from "zod";
|
|
@@ -57,26 +56,23 @@ const orderCreatedEvent = defineEventPublisher(ordersExchange, orderMessage, {
|
|
|
57
56
|
|
|
58
57
|
// Multiple queues can consume the same event
|
|
59
58
|
const orderQueue = defineQueue("order-processing", { durable: true });
|
|
60
|
-
const { consumer, binding } = defineEventConsumer(orderCreatedEvent, orderQueue);
|
|
61
|
-
|
|
62
|
-
// For topic exchanges, consumers can override with their own pattern
|
|
63
59
|
const analyticsQueue = defineQueue("analytics", { durable: true });
|
|
64
|
-
const { consumer: analyticsConsumer, binding: analyticsBinding } = defineEventConsumer(
|
|
65
|
-
orderCreatedEvent,
|
|
66
|
-
analyticsQueue,
|
|
67
|
-
{ routingKey: "order.*" }, // Subscribe to all order events
|
|
68
|
-
);
|
|
69
60
|
|
|
61
|
+
// Compose contract - configs go directly, bindings auto-generated
|
|
70
62
|
const contract = defineContract({
|
|
71
63
|
exchanges: { orders: ordersExchange },
|
|
72
64
|
queues: { orderQueue, analyticsQueue },
|
|
73
|
-
bindings: { orderBinding: binding, analyticsBinding },
|
|
74
65
|
publishers: {
|
|
75
|
-
|
|
66
|
+
// EventPublisherConfig → auto-extracted to publisher
|
|
67
|
+
orderCreated: orderCreatedEvent,
|
|
76
68
|
},
|
|
77
69
|
consumers: {
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
// EventConsumerResult → auto-extracted to consumer + binding
|
|
71
|
+
processOrder: defineEventConsumer(orderCreatedEvent, orderQueue),
|
|
72
|
+
// For topic exchanges, consumers can override with their own pattern
|
|
73
|
+
trackOrders: defineEventConsumer(orderCreatedEvent, analyticsQueue, {
|
|
74
|
+
routingKey: "order.*", // Subscribe to all order events
|
|
75
|
+
}),
|
|
80
76
|
},
|
|
81
77
|
});
|
|
82
78
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
1
2
|
|
|
2
3
|
//#region src/builder/exchange.ts
|
|
3
4
|
/**
|
|
@@ -269,76 +270,17 @@ function wrapWithTtlBackoffInfrastructure(queue) {
|
|
|
269
270
|
},
|
|
270
271
|
retry: resolveTtlBackoffOptions(void 0)
|
|
271
272
|
};
|
|
273
|
+
const waitQueueBinding = defineQueueBindingInternal(waitQueue, dlx, { routingKey: waitQueueName });
|
|
274
|
+
const mainQueueRetryBinding = defineQueueBindingInternal(queue, dlx, { routingKey: queue.name });
|
|
272
275
|
return {
|
|
273
276
|
__brand: "QueueWithTtlBackoffInfrastructure",
|
|
274
277
|
queue,
|
|
278
|
+
deadLetter: queue.deadLetter,
|
|
275
279
|
waitQueue,
|
|
276
|
-
waitQueueBinding
|
|
277
|
-
mainQueueRetryBinding
|
|
280
|
+
waitQueueBinding,
|
|
281
|
+
mainQueueRetryBinding
|
|
278
282
|
};
|
|
279
283
|
}
|
|
280
|
-
/**
|
|
281
|
-
* Define an AMQP queue.
|
|
282
|
-
*
|
|
283
|
-
* A queue stores messages until they are consumed by workers. Queues can be bound to exchanges
|
|
284
|
-
* to receive messages based on routing rules.
|
|
285
|
-
*
|
|
286
|
-
* By default, queues are created as quorum queues which provide better durability and
|
|
287
|
-
* high-availability. Use `type: 'classic'` for special cases like non-durable queues
|
|
288
|
-
* or priority queues.
|
|
289
|
-
*
|
|
290
|
-
* @param name - The name of the queue
|
|
291
|
-
* @param options - Optional queue configuration
|
|
292
|
-
* @param options.type - Queue type: 'quorum' (default, recommended) or 'classic'
|
|
293
|
-
* @param options.durable - If true, the queue survives broker restarts. Quorum queues are always durable.
|
|
294
|
-
* @param options.exclusive - If true, the queue can only be used by the declaring connection. Only supported with classic queues.
|
|
295
|
-
* @param options.autoDelete - If true, the queue is deleted when the last consumer unsubscribes (default: false)
|
|
296
|
-
* @param options.deadLetter - Dead letter configuration for handling failed messages
|
|
297
|
-
* @param options.maxPriority - Maximum priority level for priority queue (1-255, recommended: 1-10). Only supported with classic queues.
|
|
298
|
-
* @param options.arguments - Additional AMQP arguments (e.g., x-message-ttl)
|
|
299
|
-
* @returns A queue definition
|
|
300
|
-
*
|
|
301
|
-
* @example
|
|
302
|
-
* ```typescript
|
|
303
|
-
* // Quorum queue (default, recommended for production)
|
|
304
|
-
* const orderQueue = defineQueue('order-processing');
|
|
305
|
-
*
|
|
306
|
-
* // Explicit quorum queue with dead letter exchange
|
|
307
|
-
* const dlx = defineExchange('orders-dlx', 'topic', { durable: true });
|
|
308
|
-
* const orderQueueWithDLX = defineQueue('order-processing', {
|
|
309
|
-
* type: 'quorum',
|
|
310
|
-
* deadLetter: {
|
|
311
|
-
* exchange: dlx,
|
|
312
|
-
* routingKey: 'order.failed'
|
|
313
|
-
* },
|
|
314
|
-
* arguments: {
|
|
315
|
-
* 'x-message-ttl': 86400000, // 24 hours
|
|
316
|
-
* }
|
|
317
|
-
* });
|
|
318
|
-
*
|
|
319
|
-
* // Classic queue (for special cases)
|
|
320
|
-
* const tempQueue = defineQueue('temp-queue', {
|
|
321
|
-
* type: 'classic',
|
|
322
|
-
* durable: false,
|
|
323
|
-
* autoDelete: true,
|
|
324
|
-
* });
|
|
325
|
-
*
|
|
326
|
-
* // Priority queue (requires classic type)
|
|
327
|
-
* const taskQueue = defineQueue('urgent-tasks', {
|
|
328
|
-
* type: 'classic',
|
|
329
|
-
* durable: true,
|
|
330
|
-
* maxPriority: 10,
|
|
331
|
-
* });
|
|
332
|
-
*
|
|
333
|
-
* // Queue with TTL-backoff retry (returns infrastructure automatically)
|
|
334
|
-
* const dlx = defineExchange('orders-dlx', 'direct', { durable: true });
|
|
335
|
-
* const orderQueue = defineQueue('order-processing', {
|
|
336
|
-
* deadLetter: { exchange: dlx },
|
|
337
|
-
* retry: { mode: 'ttl-backoff', maxRetries: 5 },
|
|
338
|
-
* });
|
|
339
|
-
* // orderQueue is QueueWithTtlBackoffInfrastructure, pass directly to defineContract
|
|
340
|
-
* ```
|
|
341
|
-
*/
|
|
342
284
|
function defineQueue(name, options) {
|
|
343
285
|
const opts = options ?? {};
|
|
344
286
|
const type = opts.type ?? "quorum";
|
|
@@ -353,18 +295,18 @@ function defineQueue(name, options) {
|
|
|
353
295
|
if (inputRetry.mode === "quorum-native") {
|
|
354
296
|
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
297
|
}
|
|
356
|
-
const retry
|
|
357
|
-
const queueDefinition
|
|
298
|
+
const retry = inputRetry.mode === "quorum-native" ? inputRetry : resolveTtlBackoffOptions(inputRetry);
|
|
299
|
+
const queueDefinition = {
|
|
358
300
|
...baseProps,
|
|
359
301
|
type: "quorum",
|
|
360
|
-
retry
|
|
302
|
+
retry
|
|
361
303
|
};
|
|
362
304
|
if (quorumOpts.deliveryLimit !== void 0) {
|
|
363
305
|
if (quorumOpts.deliveryLimit < 1 || !Number.isInteger(quorumOpts.deliveryLimit)) throw new Error(`Invalid deliveryLimit: ${quorumOpts.deliveryLimit}. Must be a positive integer.`);
|
|
364
|
-
queueDefinition
|
|
306
|
+
queueDefinition.deliveryLimit = quorumOpts.deliveryLimit;
|
|
365
307
|
}
|
|
366
|
-
if (retry
|
|
367
|
-
return queueDefinition
|
|
308
|
+
if (retry.mode === "ttl-backoff" && queueDefinition.deadLetter) return wrapWithTtlBackoffInfrastructure(queueDefinition);
|
|
309
|
+
return queueDefinition;
|
|
368
310
|
}
|
|
369
311
|
const classicOpts = opts;
|
|
370
312
|
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").`);
|
|
@@ -411,10 +353,10 @@ function defineQueue(name, options) {
|
|
|
411
353
|
* deliveryLimit: 3, // Retry up to 3 times
|
|
412
354
|
* });
|
|
413
355
|
*
|
|
356
|
+
* // Use in a contract — exchanges, queues, and bindings are auto-extracted
|
|
414
357
|
* const contract = defineContract({
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
* // ...
|
|
358
|
+
* publishers: { ... },
|
|
359
|
+
* consumers: { processOrder: defineEventConsumer(event, orderQueue) },
|
|
418
360
|
* });
|
|
419
361
|
* ```
|
|
420
362
|
*
|
|
@@ -469,10 +411,10 @@ function defineQuorumQueue(name, options) {
|
|
|
469
411
|
* maxDelayMs: 30000, // Cap at 30s
|
|
470
412
|
* });
|
|
471
413
|
*
|
|
414
|
+
* // Use in a contract — wait queue, bindings, and DLX are auto-extracted
|
|
472
415
|
* const contract = defineContract({
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
* // ... bindings auto-generated
|
|
416
|
+
* publishers: { ... },
|
|
417
|
+
* consumers: { processOrder: defineEventConsumer(event, extractQueue(orderQueue)) },
|
|
476
418
|
* });
|
|
477
419
|
*
|
|
478
420
|
* // To access the underlying queue definition (e.g., for the queue name):
|
|
@@ -543,6 +485,52 @@ function definePublisherInternal(exchange, message, options) {
|
|
|
543
485
|
//#endregion
|
|
544
486
|
//#region src/builder/consumer.ts
|
|
545
487
|
/**
|
|
488
|
+
* Type guard to check if an entry is an EventConsumerResult.
|
|
489
|
+
*/
|
|
490
|
+
function isEventConsumerResultEntry(entry) {
|
|
491
|
+
return "__brand" in entry && entry.__brand === "EventConsumerResult";
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Type guard to check if an entry is a CommandConsumerConfig.
|
|
495
|
+
*/
|
|
496
|
+
function isCommandConsumerConfigEntry(entry) {
|
|
497
|
+
return "__brand" in entry && entry.__brand === "CommandConsumerConfig";
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Extract the ConsumerDefinition from any ConsumerEntry type.
|
|
501
|
+
*
|
|
502
|
+
* Handles the following entry types:
|
|
503
|
+
* - ConsumerDefinition: returned as-is
|
|
504
|
+
* - EventConsumerResult: returns the nested `.consumer` property
|
|
505
|
+
* - CommandConsumerConfig: returns the nested `.consumer` property
|
|
506
|
+
*
|
|
507
|
+
* Use this function when you need to access the underlying ConsumerDefinition
|
|
508
|
+
* from a consumer entry that may have been created with defineEventConsumer
|
|
509
|
+
* or defineCommandConsumer.
|
|
510
|
+
*
|
|
511
|
+
* @param entry - The consumer entry to extract from
|
|
512
|
+
* @returns The underlying ConsumerDefinition
|
|
513
|
+
*
|
|
514
|
+
* @example
|
|
515
|
+
* ```typescript
|
|
516
|
+
* // Works with plain ConsumerDefinition
|
|
517
|
+
* const consumer1 = defineConsumer(queue, message);
|
|
518
|
+
* extractConsumer(consumer1).queue.name; // "my-queue"
|
|
519
|
+
*
|
|
520
|
+
* // Works with EventConsumerResult
|
|
521
|
+
* const consumer2 = defineEventConsumer(eventPublisher, queue);
|
|
522
|
+
* extractConsumer(consumer2).queue.name; // "my-queue"
|
|
523
|
+
*
|
|
524
|
+
* // Works with CommandConsumerConfig
|
|
525
|
+
* const consumer3 = defineCommandConsumer(queue, exchange, message, { routingKey: "cmd" });
|
|
526
|
+
* extractConsumer(consumer3).queue.name; // "my-queue"
|
|
527
|
+
* ```
|
|
528
|
+
*/
|
|
529
|
+
function extractConsumer(entry) {
|
|
530
|
+
if (isEventConsumerResultEntry(entry) || isCommandConsumerConfigEntry(entry)) return entry.consumer;
|
|
531
|
+
return entry;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
546
534
|
* Define a message consumer.
|
|
547
535
|
*
|
|
548
536
|
* A consumer receives and processes messages from a queue. The message schema is validated
|
|
@@ -636,10 +624,14 @@ function defineEventConsumer(eventPublisher, queue, options) {
|
|
|
636
624
|
const bindingArguments = options?.arguments ?? eventPublisher.arguments;
|
|
637
625
|
if (bindingArguments !== void 0) bindingOptions.arguments = bindingArguments;
|
|
638
626
|
const binding = defineQueueBindingInternal(queue, exchange, bindingOptions);
|
|
627
|
+
const consumer = defineConsumer(queue, message);
|
|
639
628
|
return {
|
|
640
629
|
__brand: "EventConsumerResult",
|
|
641
|
-
consumer
|
|
642
|
-
binding
|
|
630
|
+
consumer,
|
|
631
|
+
binding,
|
|
632
|
+
exchange,
|
|
633
|
+
queue: consumer.queue,
|
|
634
|
+
deadLetterExchange: consumer.queue.deadLetter?.exchange
|
|
643
635
|
};
|
|
644
636
|
}
|
|
645
637
|
/**
|
|
@@ -668,11 +660,14 @@ function isEventConsumerResult(value) {
|
|
|
668
660
|
* @internal
|
|
669
661
|
*/
|
|
670
662
|
function defineCommandConsumer(queue, exchange, message, options) {
|
|
663
|
+
const consumer = defineConsumer(queue, message);
|
|
671
664
|
return {
|
|
672
665
|
__brand: "CommandConsumerConfig",
|
|
673
|
-
consumer
|
|
666
|
+
consumer,
|
|
674
667
|
binding: defineQueueBindingInternal(queue, exchange, options),
|
|
675
668
|
exchange,
|
|
669
|
+
queue: consumer.queue,
|
|
670
|
+
deadLetterExchange: consumer.queue.deadLetter?.exchange,
|
|
676
671
|
message,
|
|
677
672
|
routingKey: options?.routingKey
|
|
678
673
|
};
|
|
@@ -704,19 +699,17 @@ function isCommandConsumerConfig(value) {
|
|
|
704
699
|
* Define an AMQP contract.
|
|
705
700
|
*
|
|
706
701
|
* A contract is the central definition of your AMQP messaging topology. It brings together
|
|
707
|
-
*
|
|
702
|
+
* publishers and consumers in a single, type-safe definition. Exchanges, queues, and bindings
|
|
703
|
+
* are automatically extracted from publishers and consumers.
|
|
708
704
|
*
|
|
709
705
|
* The contract is used by both clients (for publishing) and workers (for consuming) to ensure
|
|
710
706
|
* type safety throughout your messaging infrastructure. TypeScript will infer all message types
|
|
711
707
|
* and publisher/consumer names from the contract.
|
|
712
708
|
*
|
|
713
|
-
* @param definition - The contract definition containing
|
|
714
|
-
* @param definition.exchanges - Named exchange definitions
|
|
715
|
-
* @param definition.queues - Named queue definitions
|
|
716
|
-
* @param definition.bindings - Named binding definitions (queue-to-exchange or exchange-to-exchange)
|
|
709
|
+
* @param definition - The contract definition containing publishers and consumers
|
|
717
710
|
* @param definition.publishers - Named publisher definitions for sending messages
|
|
718
711
|
* @param definition.consumers - Named consumer definitions for receiving messages
|
|
719
|
-
* @returns The
|
|
712
|
+
* @returns The contract definition with fully inferred exchanges, queues, bindings, publishers, and consumers
|
|
720
713
|
*
|
|
721
714
|
* @example
|
|
722
715
|
* ```typescript
|
|
@@ -724,16 +717,20 @@ function isCommandConsumerConfig(value) {
|
|
|
724
717
|
* defineContract,
|
|
725
718
|
* defineExchange,
|
|
726
719
|
* defineQueue,
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
* defineConsumer,
|
|
720
|
+
* defineEventPublisher,
|
|
721
|
+
* defineEventConsumer,
|
|
730
722
|
* defineMessage,
|
|
731
723
|
* } from '@amqp-contract/contract';
|
|
732
724
|
* import { z } from 'zod';
|
|
733
725
|
*
|
|
734
726
|
* // Define resources
|
|
735
727
|
* const ordersExchange = defineExchange('orders', 'topic', { durable: true });
|
|
736
|
-
* const
|
|
728
|
+
* const dlx = defineExchange('orders-dlx', 'direct', { durable: true });
|
|
729
|
+
* const orderQueue = defineQueue('order-processing', {
|
|
730
|
+
* deadLetter: { exchange: dlx },
|
|
731
|
+
* retry: { mode: 'quorum-native' },
|
|
732
|
+
* deliveryLimit: 3,
|
|
733
|
+
* });
|
|
737
734
|
* const orderMessage = defineMessage(
|
|
738
735
|
* z.object({
|
|
739
736
|
* orderId: z.string(),
|
|
@@ -741,76 +738,114 @@ function isCommandConsumerConfig(value) {
|
|
|
741
738
|
* })
|
|
742
739
|
* );
|
|
743
740
|
*
|
|
744
|
-
* //
|
|
741
|
+
* // Define event publisher
|
|
742
|
+
* const orderCreatedEvent = defineEventPublisher(ordersExchange, orderMessage, {
|
|
743
|
+
* routingKey: 'order.created',
|
|
744
|
+
* });
|
|
745
|
+
*
|
|
746
|
+
* // Compose contract - exchanges, queues, bindings are auto-extracted
|
|
745
747
|
* export const contract = defineContract({
|
|
746
|
-
* exchanges: {
|
|
747
|
-
* orders: ordersExchange,
|
|
748
|
-
* },
|
|
749
|
-
* queues: {
|
|
750
|
-
* orderProcessing: orderQueue,
|
|
751
|
-
* },
|
|
752
|
-
* bindings: {
|
|
753
|
-
* orderBinding: defineQueueBinding(orderQueue, ordersExchange, {
|
|
754
|
-
* routingKey: 'order.created',
|
|
755
|
-
* }),
|
|
756
|
-
* },
|
|
757
748
|
* publishers: {
|
|
758
|
-
* orderCreated:
|
|
759
|
-
* routingKey: 'order.created',
|
|
760
|
-
* }),
|
|
749
|
+
* orderCreated: orderCreatedEvent,
|
|
761
750
|
* },
|
|
762
751
|
* consumers: {
|
|
763
|
-
* processOrder:
|
|
752
|
+
* processOrder: defineEventConsumer(orderCreatedEvent, orderQueue),
|
|
764
753
|
* },
|
|
765
754
|
* });
|
|
766
755
|
*
|
|
767
756
|
* // TypeScript now knows:
|
|
757
|
+
* // - contract.exchanges.orders, contract.exchanges['orders-dlx']
|
|
758
|
+
* // - contract.queues['order-processing']
|
|
759
|
+
* // - contract.bindings.processOrderBinding
|
|
768
760
|
* // - client.publish('orderCreated', { orderId: string, amount: number })
|
|
769
|
-
* // - handler:
|
|
761
|
+
* // - handler: (message: { orderId: string, amount: number }) => Future<Result<void, HandlerError>>
|
|
770
762
|
* ```
|
|
771
763
|
*/
|
|
772
764
|
function defineContract(definition) {
|
|
773
|
-
const { publishers: inputPublishers, consumers: inputConsumers
|
|
774
|
-
const result =
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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
|
|
788
|
-
};
|
|
789
|
-
}
|
|
765
|
+
const { publishers: inputPublishers, consumers: inputConsumers } = definition;
|
|
766
|
+
const result = {
|
|
767
|
+
exchanges: {},
|
|
768
|
+
queues: {},
|
|
769
|
+
bindings: {},
|
|
770
|
+
publishers: {},
|
|
771
|
+
consumers: {}
|
|
772
|
+
};
|
|
790
773
|
if (inputPublishers && Object.keys(inputPublishers).length > 0) {
|
|
791
774
|
const processedPublishers = {};
|
|
775
|
+
const exchanges = {};
|
|
792
776
|
for (const [name, entry] of Object.entries(inputPublishers)) if (isEventPublisherConfig(entry)) {
|
|
777
|
+
exchanges[entry.exchange.name] = entry.exchange;
|
|
793
778
|
const publisherOptions = {};
|
|
794
779
|
if (entry.routingKey !== void 0) publisherOptions.routingKey = entry.routingKey;
|
|
795
780
|
processedPublishers[name] = definePublisherInternal(entry.exchange, entry.message, publisherOptions);
|
|
796
|
-
} else
|
|
781
|
+
} else {
|
|
782
|
+
const publisher = entry;
|
|
783
|
+
exchanges[publisher.exchange.name] = publisher.exchange;
|
|
784
|
+
processedPublishers[name] = publisher;
|
|
785
|
+
}
|
|
797
786
|
result.publishers = processedPublishers;
|
|
787
|
+
result.exchanges = {
|
|
788
|
+
...result.exchanges,
|
|
789
|
+
...exchanges
|
|
790
|
+
};
|
|
798
791
|
}
|
|
799
792
|
if (inputConsumers && Object.keys(inputConsumers).length > 0) {
|
|
800
793
|
const processedConsumers = {};
|
|
801
794
|
const consumerBindings = {};
|
|
795
|
+
const queues = {};
|
|
796
|
+
const exchanges = {};
|
|
802
797
|
for (const [name, entry] of Object.entries(inputConsumers)) if (isEventConsumerResult(entry)) {
|
|
803
798
|
processedConsumers[name] = entry.consumer;
|
|
804
799
|
consumerBindings[`${name}Binding`] = entry.binding;
|
|
800
|
+
const queueEntry = entry.consumer.queue;
|
|
801
|
+
queues[queueEntry.name] = queueEntry;
|
|
802
|
+
exchanges[entry.binding.exchange.name] = entry.binding.exchange;
|
|
803
|
+
if (queueEntry.deadLetter?.exchange) exchanges[queueEntry.deadLetter.exchange.name] = queueEntry.deadLetter.exchange;
|
|
805
804
|
} else if (isCommandConsumerConfig(entry)) {
|
|
806
805
|
processedConsumers[name] = entry.consumer;
|
|
807
806
|
consumerBindings[`${name}Binding`] = entry.binding;
|
|
808
|
-
|
|
807
|
+
const queueEntry = entry.consumer.queue;
|
|
808
|
+
queues[queueEntry.name] = queueEntry;
|
|
809
|
+
exchanges[entry.exchange.name] = entry.exchange;
|
|
810
|
+
if (queueEntry.deadLetter?.exchange) exchanges[queueEntry.deadLetter.exchange.name] = queueEntry.deadLetter.exchange;
|
|
811
|
+
} else {
|
|
812
|
+
const consumer = entry;
|
|
813
|
+
processedConsumers[name] = consumer;
|
|
814
|
+
const queueEntry = consumer.queue;
|
|
815
|
+
queues[queueEntry.name] = queueEntry;
|
|
816
|
+
if (queueEntry.deadLetter?.exchange) exchanges[queueEntry.deadLetter.exchange.name] = queueEntry.deadLetter.exchange;
|
|
817
|
+
}
|
|
818
|
+
for (const queue of Object.values(queues)) if (queue.retry?.mode === "ttl-backoff" && queue.deadLetter) {
|
|
819
|
+
const dlx = queue.deadLetter.exchange;
|
|
820
|
+
const waitQueueName = `${queue.name}-wait`;
|
|
821
|
+
const waitQueue = {
|
|
822
|
+
name: waitQueueName,
|
|
823
|
+
type: "quorum",
|
|
824
|
+
durable: queue.durable ?? true,
|
|
825
|
+
deadLetter: {
|
|
826
|
+
exchange: dlx,
|
|
827
|
+
routingKey: queue.name
|
|
828
|
+
},
|
|
829
|
+
retry: resolveTtlBackoffOptions(void 0)
|
|
830
|
+
};
|
|
831
|
+
queues[waitQueueName] = waitQueue;
|
|
832
|
+
consumerBindings[`${queue.name}WaitBinding`] = defineQueueBindingInternal(waitQueue, dlx, { routingKey: waitQueueName });
|
|
833
|
+
consumerBindings[`${queue.name}RetryBinding`] = defineQueueBindingInternal(queue, dlx, { routingKey: queue.name });
|
|
834
|
+
exchanges[dlx.name] = dlx;
|
|
835
|
+
}
|
|
809
836
|
result.consumers = processedConsumers;
|
|
810
|
-
|
|
837
|
+
result.bindings = {
|
|
811
838
|
...result.bindings,
|
|
812
839
|
...consumerBindings
|
|
813
840
|
};
|
|
841
|
+
result.queues = {
|
|
842
|
+
...result.queues,
|
|
843
|
+
...queues
|
|
844
|
+
};
|
|
845
|
+
result.exchanges = {
|
|
846
|
+
...result.exchanges,
|
|
847
|
+
...exchanges
|
|
848
|
+
};
|
|
814
849
|
}
|
|
815
850
|
return result;
|
|
816
851
|
}
|
|
@@ -848,23 +883,15 @@ function defineContract(definition) {
|
|
|
848
883
|
* },
|
|
849
884
|
* });
|
|
850
885
|
*
|
|
851
|
-
* //
|
|
852
|
-
* const retryInfra = defineTtlBackoffRetryInfrastructure(orderQueue);
|
|
853
|
-
*
|
|
854
|
-
* // Spread into contract
|
|
886
|
+
* // Infrastructure is auto-extracted when using defineContract:
|
|
855
887
|
* const contract = defineContract({
|
|
856
|
-
*
|
|
857
|
-
*
|
|
858
|
-
* orderProcessing: orderQueue,
|
|
859
|
-
* orderProcessingWait: retryInfra.waitQueue,
|
|
860
|
-
* },
|
|
861
|
-
* bindings: {
|
|
862
|
-
* ...// your other bindings
|
|
863
|
-
* orderWaitBinding: retryInfra.waitQueueBinding,
|
|
864
|
-
* orderRetryBinding: retryInfra.mainQueueRetryBinding,
|
|
865
|
-
* },
|
|
866
|
-
* // ... publishers and consumers
|
|
888
|
+
* publishers: { ... },
|
|
889
|
+
* consumers: { processOrder: defineEventConsumer(event, extractQueue(orderQueue)) },
|
|
867
890
|
* });
|
|
891
|
+
* // contract.queues includes the wait queue, contract.bindings includes retry bindings
|
|
892
|
+
*
|
|
893
|
+
* // Or generate manually for advanced use cases:
|
|
894
|
+
* const retryInfra = defineTtlBackoffRetryInfrastructure(orderQueue);
|
|
868
895
|
* ```
|
|
869
896
|
*/
|
|
870
897
|
function defineTtlBackoffRetryInfrastructure(queueEntry, options) {
|
|
@@ -903,6 +930,7 @@ exports.defineQueueBinding = defineQueueBinding;
|
|
|
903
930
|
exports.defineQuorumQueue = defineQuorumQueue;
|
|
904
931
|
exports.defineTtlBackoffQueue = defineTtlBackoffQueue;
|
|
905
932
|
exports.defineTtlBackoffRetryInfrastructure = defineTtlBackoffRetryInfrastructure;
|
|
933
|
+
exports.extractConsumer = extractConsumer;
|
|
906
934
|
exports.extractQueue = extractQueue;
|
|
907
935
|
exports.isCommandConsumerConfig = isCommandConsumerConfig;
|
|
908
936
|
exports.isEventConsumerResult = isEventConsumerResult;
|