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