@amqp-contract/contract 0.11.0 → 0.12.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,200 @@ 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
+ * @internal
169
+ */
170
+ function isQueueWithTtlBackoffInfrastructure(entry) {
171
+ return typeof entry === "object" && entry !== null && "__brand" in entry && entry.__brand === "QueueWithTtlBackoffInfrastructure";
172
+ }
173
+ /**
174
+ * Extract the plain QueueDefinition from a QueueEntry.
175
+ * If the entry is a QueueWithTtlBackoffInfrastructure, returns the inner queue.
176
+ * Otherwise, returns the entry as-is.
177
+ *
178
+ * @param entry - The queue entry (either plain QueueDefinition or QueueWithTtlBackoffInfrastructure)
179
+ * @returns The plain QueueDefinition
180
+ *
181
+ * @example
182
+ * ```typescript
183
+ * const queue = defineQueue('orders', { retry: { mode: 'ttl-backoff' }, deadLetter: { exchange: dlx } });
184
+ * const plainQueue = extractQueue(queue); // Returns the inner QueueDefinition
185
+ * ```
186
+ */
187
+ function extractQueue(entry) {
188
+ if (isQueueWithTtlBackoffInfrastructure(entry)) return entry.queue;
189
+ return entry;
190
+ }
191
+ /**
192
+ * Wrap a queue definition with TTL-backoff retry infrastructure.
193
+ * @internal
194
+ */
195
+ function wrapWithTtlBackoffInfrastructure(queue) {
196
+ 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.`);
197
+ const dlx = queue.deadLetter.exchange;
198
+ const waitQueueName = `${queue.name}-wait`;
199
+ const waitQueue = {
200
+ name: waitQueueName,
201
+ type: "quorum",
202
+ durable: queue.durable ?? true,
203
+ deadLetter: {
204
+ exchange: dlx,
205
+ routingKey: queue.name
206
+ },
207
+ retry: resolveTtlBackoffOptions(void 0)
208
+ };
209
+ return {
210
+ __brand: "QueueWithTtlBackoffInfrastructure",
211
+ queue,
212
+ waitQueue,
213
+ waitQueueBinding: defineQueueBindingInternal(waitQueue, dlx, { routingKey: waitQueueName }),
214
+ mainQueueRetryBinding: defineQueueBindingInternal(queue, dlx, { routingKey: queue.name })
215
+ };
216
+ }
23
217
  /**
24
218
  * Define an AMQP queue.
25
219
  *
@@ -92,22 +286,30 @@ function defineQueue(name, options) {
92
286
  if (opts.arguments !== void 0) baseProps.arguments = opts.arguments;
93
287
  if (type === "quorum") {
94
288
  const quorumOpts = opts;
289
+ const inputRetry = quorumOpts.retry ?? { mode: "ttl-backoff" };
290
+ if (inputRetry.mode === "quorum-native") {
291
+ 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.`);
292
+ }
293
+ const retry$1 = inputRetry.mode === "quorum-native" ? inputRetry : resolveTtlBackoffOptions(inputRetry);
95
294
  const queueDefinition$1 = {
96
295
  ...baseProps,
97
- type: "quorum"
296
+ type: "quorum",
297
+ retry: retry$1
98
298
  };
99
299
  if (quorumOpts.deliveryLimit !== void 0) {
100
300
  if (quorumOpts.deliveryLimit < 1 || !Number.isInteger(quorumOpts.deliveryLimit)) throw new Error(`Invalid deliveryLimit: ${quorumOpts.deliveryLimit}. Must be a positive integer.`);
101
301
  queueDefinition$1.deliveryLimit = quorumOpts.deliveryLimit;
102
302
  }
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);
303
+ if (retry$1.mode === "ttl-backoff" && queueDefinition$1.deadLetter) return wrapWithTtlBackoffInfrastructure(queueDefinition$1);
105
304
  return queueDefinition$1;
106
305
  }
107
306
  const classicOpts = opts;
307
+ 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").`);
308
+ const retry = resolveTtlBackoffOptions(classicOpts.retry);
108
309
  const queueDefinition = {
109
310
  ...baseProps,
110
- type: "classic"
311
+ type: "classic",
312
+ retry
111
313
  };
112
314
  if (classicOpts.exclusive !== void 0) queueDefinition.exclusive = classicOpts.exclusive;
113
315
  if (classicOpts.maxPriority !== void 0) {
@@ -117,129 +319,12 @@ function defineQueue(name, options) {
117
319
  "x-max-priority": classicOpts.maxPriority
118
320
  };
119
321
  }
120
- if (classicOpts.retry !== void 0) queueDefinition.retry = classicOpts.retry;
121
- if (classicOpts.retry?.mode === "ttl-backoff" && queueDefinition.deadLetter) return wrapWithTtlBackoffInfrastructure(queueDefinition);
322
+ if (retry.mode === "ttl-backoff" && queueDefinition.deadLetter) return wrapWithTtlBackoffInfrastructure(queueDefinition);
122
323
  return queueDefinition;
123
324
  }
124
- /**
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.
151
- *
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.
155
- *
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
162
- *
163
- * @example
164
- * ```typescript
165
- * import { z } from 'zod';
166
- *
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
- * );
182
- * ```
183
- */
184
- function defineMessage(payload, options) {
185
- return {
186
- payload,
187
- ...options
188
- };
189
- }
190
- /**
191
- * Define a binding between a queue and an exchange.
192
- *
193
- * This is the implementation function - use the type-specific overloads for better type safety.
194
- *
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).
219
- *
220
- * This is the implementation function - use the type-specific overloads for better type safety.
221
- *
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
227
- */
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 }
241
- };
242
- }
325
+
326
+ //#endregion
327
+ //#region src/builder/publisher.ts
243
328
  /**
244
329
  * Define a message publisher.
245
330
  *
@@ -263,6 +348,18 @@ function definePublisher(exchange, message, options) {
263
348
  };
264
349
  }
265
350
  /**
351
+ * Helper to call definePublisher with proper type handling.
352
+ * Type safety is enforced by overloaded public function signatures.
353
+ * @internal
354
+ */
355
+ function definePublisherInternal(exchange, message, options) {
356
+ if (exchange.type === "fanout") return definePublisher(exchange, message, options);
357
+ return definePublisher(exchange, message, options);
358
+ }
359
+
360
+ //#endregion
361
+ //#region src/builder/consumer.ts
362
+ /**
266
363
  * Define a message consumer.
267
364
  *
268
365
  * A consumer receives and processes messages from a queue. The message schema is validated
@@ -311,6 +408,9 @@ function defineConsumer(queue, message, options) {
311
408
  ...options
312
409
  };
313
410
  }
411
+
412
+ //#endregion
413
+ //#region src/builder/contract.ts
314
414
  /**
315
415
  * Define an AMQP contract.
316
416
  *
@@ -407,58 +507,18 @@ function defineContract(definition) {
407
507
  queues: expandedQueues
408
508
  };
409
509
  }
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
- }
510
+
511
+ //#endregion
512
+ //#region src/builder/publisher-first.ts
453
513
  /**
454
514
  * Implementation of definePublisherFirst.
455
515
  * @internal
456
516
  */
457
517
  function definePublisherFirst(exchange, message, options) {
458
- const publisher = callDefinePublisher(exchange, message, options);
518
+ const publisher = definePublisherInternal(exchange, message, options);
459
519
  if (exchange.type === "topic") {
460
520
  const createConsumer$1 = (queue, routingKey) => {
461
- const binding = callDefineQueueBinding(queue, exchange, routingKey ? {
521
+ const binding = defineQueueBindingInternal(queue, exchange, routingKey ? {
462
522
  ...options,
463
523
  routingKey
464
524
  } : options);
@@ -473,7 +533,7 @@ function definePublisherFirst(exchange, message, options) {
473
533
  };
474
534
  }
475
535
  const createConsumer = (queue) => {
476
- const binding = callDefineQueueBinding(queue, exchange, options);
536
+ const binding = defineQueueBindingInternal(queue, exchange, options);
477
537
  return {
478
538
  consumer: defineConsumer(queue, message),
479
539
  binding
@@ -484,16 +544,19 @@ function definePublisherFirst(exchange, message, options) {
484
544
  createConsumer
485
545
  };
486
546
  }
547
+
548
+ //#endregion
549
+ //#region src/builder/consumer-first.ts
487
550
  /**
488
551
  * Implementation of defineConsumerFirst.
489
552
  * @internal
490
553
  */
491
554
  function defineConsumerFirst(queue, exchange, message, options) {
492
555
  const consumer = defineConsumer(queue, message);
493
- const binding = callDefineQueueBinding(queue, exchange, options);
556
+ const binding = defineQueueBindingInternal(queue, exchange, options);
494
557
  if (exchange.type === "topic") {
495
558
  const createPublisher$1 = (routingKey) => {
496
- return callDefinePublisher(exchange, message, {
559
+ return definePublisherInternal(exchange, message, {
497
560
  ...options,
498
561
  routingKey
499
562
  });
@@ -505,7 +568,7 @@ function defineConsumerFirst(queue, exchange, message, options) {
505
568
  };
506
569
  }
507
570
  const createPublisher = () => {
508
- return callDefinePublisher(exchange, message, options);
571
+ return definePublisherInternal(exchange, message, options);
509
572
  };
510
573
  return {
511
574
  consumer,
@@ -513,6 +576,9 @@ function defineConsumerFirst(queue, exchange, message, options) {
513
576
  createPublisher
514
577
  };
515
578
  }
579
+
580
+ //#endregion
581
+ //#region src/builder/ttl-backoff.ts
516
582
  /**
517
583
  * Create TTL-backoff retry infrastructure for a queue.
518
584
  *
@@ -578,8 +644,8 @@ function defineTtlBackoffRetryInfrastructure(queueEntry, options) {
578
644
  });
579
645
  return {
580
646
  waitQueue,
581
- waitQueueBinding: callDefineQueueBinding(waitQueue, dlx, { routingKey: waitQueueName }),
582
- mainQueueRetryBinding: callDefineQueueBinding(queue, dlx, { routingKey: queue.name })
647
+ waitQueueBinding: defineQueueBindingInternal(waitQueue, dlx, { routingKey: waitQueueName }),
648
+ mainQueueRetryBinding: defineQueueBindingInternal(queue, dlx, { routingKey: queue.name })
583
649
  };
584
650
  }
585
651