@amqp-contract/contract 0.10.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 +344 -92
- package/dist/index.d.cts +658 -123
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +658 -123
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +342 -92
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +562 -102
- package/package.json +4 -4
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,67 +20,9 @@ function defineExchange(name, type, options) {
|
|
|
20
20
|
...options
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* A queue stores messages until they are consumed by workers. Queues can be bound to exchanges
|
|
27
|
-
* to receive messages based on routing rules.
|
|
28
|
-
*
|
|
29
|
-
* @param name - The name of the queue
|
|
30
|
-
* @param options - Optional queue configuration
|
|
31
|
-
* @param options.durable - If true, the queue survives broker restarts (default: false)
|
|
32
|
-
* @param options.exclusive - If true, the queue can only be used by the declaring connection (default: false)
|
|
33
|
-
* @param options.autoDelete - If true, the queue is deleted when the last consumer unsubscribes (default: false)
|
|
34
|
-
* @param options.deadLetter - Dead letter configuration for handling failed messages
|
|
35
|
-
* @param options.maxPriority - Maximum priority level for priority queue (1-255, recommended: 1-10). Sets x-max-priority argument.
|
|
36
|
-
* @param options.arguments - Additional AMQP arguments (e.g., x-message-ttl)
|
|
37
|
-
* @returns A queue definition
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```typescript
|
|
41
|
-
* // Basic queue
|
|
42
|
-
* const orderQueue = defineQueue('order-processing', {
|
|
43
|
-
* durable: true,
|
|
44
|
-
* });
|
|
45
|
-
*
|
|
46
|
-
* // Priority queue with max priority of 10
|
|
47
|
-
* const taskQueue = defineQueue('urgent-tasks', {
|
|
48
|
-
* durable: true,
|
|
49
|
-
* maxPriority: 10,
|
|
50
|
-
* });
|
|
51
|
-
*
|
|
52
|
-
* // Queue with dead letter exchange
|
|
53
|
-
* const dlx = defineExchange('orders-dlx', 'topic', { durable: true });
|
|
54
|
-
* const orderQueueWithDLX = defineQueue('order-processing', {
|
|
55
|
-
* durable: true,
|
|
56
|
-
* deadLetter: {
|
|
57
|
-
* exchange: dlx,
|
|
58
|
-
* routingKey: 'order.failed'
|
|
59
|
-
* },
|
|
60
|
-
* arguments: {
|
|
61
|
-
* 'x-message-ttl': 86400000, // 24 hours
|
|
62
|
-
* }
|
|
63
|
-
* });
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
function defineQueue(name, options) {
|
|
67
|
-
const { maxPriority, ...queueOptions } = options ?? {};
|
|
68
|
-
if (maxPriority !== void 0) {
|
|
69
|
-
if (maxPriority < 1 || maxPriority > 255) throw new Error(`Invalid maxPriority: ${maxPriority}. Must be between 1 and 255. Recommended range: 1-10.`);
|
|
70
|
-
return {
|
|
71
|
-
name,
|
|
72
|
-
...queueOptions,
|
|
73
|
-
arguments: {
|
|
74
|
-
...queueOptions.arguments,
|
|
75
|
-
"x-max-priority": maxPriority
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
return {
|
|
80
|
-
name,
|
|
81
|
-
...queueOptions
|
|
82
|
-
};
|
|
83
|
-
}
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/builder/message.ts
|
|
84
26
|
/**
|
|
85
27
|
* Define a message definition with payload and optional headers/metadata.
|
|
86
28
|
*
|
|
@@ -122,33 +64,63 @@ function defineMessage(payload, options) {
|
|
|
122
64
|
...options
|
|
123
65
|
};
|
|
124
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
|
+
}
|
|
125
87
|
/**
|
|
126
88
|
* Define a binding between a queue and an exchange.
|
|
127
89
|
*
|
|
128
90
|
* This is the implementation function - use the type-specific overloads for better type safety.
|
|
129
91
|
*
|
|
130
|
-
* @param queue - The queue definition to bind
|
|
92
|
+
* @param queue - The queue definition or queue with infrastructure to bind
|
|
131
93
|
* @param exchange - The exchange definition
|
|
132
94
|
* @param options - Optional binding configuration
|
|
133
95
|
* @returns A queue binding definition
|
|
134
96
|
* @internal
|
|
135
97
|
*/
|
|
136
98
|
function defineQueueBinding(queue, exchange, options) {
|
|
99
|
+
const queueDef = extractQueueInternal(queue);
|
|
137
100
|
if (exchange.type === "fanout") return {
|
|
138
101
|
type: "queue",
|
|
139
|
-
queue,
|
|
102
|
+
queue: queueDef,
|
|
140
103
|
exchange,
|
|
141
104
|
...options?.arguments && { arguments: options.arguments }
|
|
142
105
|
};
|
|
143
106
|
return {
|
|
144
107
|
type: "queue",
|
|
145
|
-
queue,
|
|
108
|
+
queue: queueDef,
|
|
146
109
|
exchange,
|
|
147
110
|
routingKey: options?.routingKey,
|
|
148
111
|
...options?.arguments && { arguments: options.arguments }
|
|
149
112
|
};
|
|
150
113
|
}
|
|
151
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
|
+
/**
|
|
152
124
|
* Define a binding between two exchanges (exchange-to-exchange routing).
|
|
153
125
|
*
|
|
154
126
|
* This is the implementation function - use the type-specific overloads for better type safety.
|
|
@@ -174,6 +146,185 @@ function defineExchangeBinding(destination, source, options) {
|
|
|
174
146
|
...options?.arguments && { arguments: options.arguments }
|
|
175
147
|
};
|
|
176
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
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Define an AMQP queue.
|
|
219
|
+
*
|
|
220
|
+
* A queue stores messages until they are consumed by workers. Queues can be bound to exchanges
|
|
221
|
+
* to receive messages based on routing rules.
|
|
222
|
+
*
|
|
223
|
+
* By default, queues are created as quorum queues which provide better durability and
|
|
224
|
+
* high-availability. Use `type: 'classic'` for special cases like non-durable queues
|
|
225
|
+
* or priority queues.
|
|
226
|
+
*
|
|
227
|
+
* @param name - The name of the queue
|
|
228
|
+
* @param options - Optional queue configuration
|
|
229
|
+
* @param options.type - Queue type: 'quorum' (default, recommended) or 'classic'
|
|
230
|
+
* @param options.durable - If true, the queue survives broker restarts. Quorum queues are always durable.
|
|
231
|
+
* @param options.exclusive - If true, the queue can only be used by the declaring connection. Only supported with classic queues.
|
|
232
|
+
* @param options.autoDelete - If true, the queue is deleted when the last consumer unsubscribes (default: false)
|
|
233
|
+
* @param options.deadLetter - Dead letter configuration for handling failed messages
|
|
234
|
+
* @param options.maxPriority - Maximum priority level for priority queue (1-255, recommended: 1-10). Only supported with classic queues.
|
|
235
|
+
* @param options.arguments - Additional AMQP arguments (e.g., x-message-ttl)
|
|
236
|
+
* @returns A queue definition
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```typescript
|
|
240
|
+
* // Quorum queue (default, recommended for production)
|
|
241
|
+
* const orderQueue = defineQueue('order-processing');
|
|
242
|
+
*
|
|
243
|
+
* // Explicit quorum queue with dead letter exchange
|
|
244
|
+
* const dlx = defineExchange('orders-dlx', 'topic', { durable: true });
|
|
245
|
+
* const orderQueueWithDLX = defineQueue('order-processing', {
|
|
246
|
+
* type: 'quorum',
|
|
247
|
+
* deadLetter: {
|
|
248
|
+
* exchange: dlx,
|
|
249
|
+
* routingKey: 'order.failed'
|
|
250
|
+
* },
|
|
251
|
+
* arguments: {
|
|
252
|
+
* 'x-message-ttl': 86400000, // 24 hours
|
|
253
|
+
* }
|
|
254
|
+
* });
|
|
255
|
+
*
|
|
256
|
+
* // Classic queue (for special cases)
|
|
257
|
+
* const tempQueue = defineQueue('temp-queue', {
|
|
258
|
+
* type: 'classic',
|
|
259
|
+
* durable: false,
|
|
260
|
+
* autoDelete: true,
|
|
261
|
+
* });
|
|
262
|
+
*
|
|
263
|
+
* // Priority queue (requires classic type)
|
|
264
|
+
* const taskQueue = defineQueue('urgent-tasks', {
|
|
265
|
+
* type: 'classic',
|
|
266
|
+
* durable: true,
|
|
267
|
+
* maxPriority: 10,
|
|
268
|
+
* });
|
|
269
|
+
*
|
|
270
|
+
* // Queue with TTL-backoff retry (returns infrastructure automatically)
|
|
271
|
+
* const dlx = defineExchange('orders-dlx', 'direct', { durable: true });
|
|
272
|
+
* const orderQueue = defineQueue('order-processing', {
|
|
273
|
+
* deadLetter: { exchange: dlx },
|
|
274
|
+
* retry: { mode: 'ttl-backoff', maxRetries: 5 },
|
|
275
|
+
* });
|
|
276
|
+
* // orderQueue is QueueWithTtlBackoffInfrastructure, pass directly to defineContract
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
function defineQueue(name, options) {
|
|
280
|
+
const opts = options ?? {};
|
|
281
|
+
const type = opts.type ?? "quorum";
|
|
282
|
+
const baseProps = { name };
|
|
283
|
+
if (opts.durable !== void 0) baseProps.durable = opts.durable;
|
|
284
|
+
if (opts.autoDelete !== void 0) baseProps.autoDelete = opts.autoDelete;
|
|
285
|
+
if (opts.deadLetter !== void 0) baseProps.deadLetter = opts.deadLetter;
|
|
286
|
+
if (opts.arguments !== void 0) baseProps.arguments = opts.arguments;
|
|
287
|
+
if (type === "quorum") {
|
|
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);
|
|
294
|
+
const queueDefinition$1 = {
|
|
295
|
+
...baseProps,
|
|
296
|
+
type: "quorum",
|
|
297
|
+
retry: retry$1
|
|
298
|
+
};
|
|
299
|
+
if (quorumOpts.deliveryLimit !== void 0) {
|
|
300
|
+
if (quorumOpts.deliveryLimit < 1 || !Number.isInteger(quorumOpts.deliveryLimit)) throw new Error(`Invalid deliveryLimit: ${quorumOpts.deliveryLimit}. Must be a positive integer.`);
|
|
301
|
+
queueDefinition$1.deliveryLimit = quorumOpts.deliveryLimit;
|
|
302
|
+
}
|
|
303
|
+
if (retry$1.mode === "ttl-backoff" && queueDefinition$1.deadLetter) return wrapWithTtlBackoffInfrastructure(queueDefinition$1);
|
|
304
|
+
return queueDefinition$1;
|
|
305
|
+
}
|
|
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);
|
|
309
|
+
const queueDefinition = {
|
|
310
|
+
...baseProps,
|
|
311
|
+
type: "classic",
|
|
312
|
+
retry
|
|
313
|
+
};
|
|
314
|
+
if (classicOpts.exclusive !== void 0) queueDefinition.exclusive = classicOpts.exclusive;
|
|
315
|
+
if (classicOpts.maxPriority !== void 0) {
|
|
316
|
+
if (classicOpts.maxPriority < 1 || classicOpts.maxPriority > 255) throw new Error(`Invalid maxPriority: ${classicOpts.maxPriority}. Must be between 1 and 255. Recommended range: 1-10.`);
|
|
317
|
+
queueDefinition.arguments = {
|
|
318
|
+
...queueDefinition.arguments,
|
|
319
|
+
"x-max-priority": classicOpts.maxPriority
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
if (retry.mode === "ttl-backoff" && queueDefinition.deadLetter) return wrapWithTtlBackoffInfrastructure(queueDefinition);
|
|
323
|
+
return queueDefinition;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/builder/publisher.ts
|
|
177
328
|
/**
|
|
178
329
|
* Define a message publisher.
|
|
179
330
|
*
|
|
@@ -197,6 +348,18 @@ function definePublisher(exchange, message, options) {
|
|
|
197
348
|
};
|
|
198
349
|
}
|
|
199
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
|
+
/**
|
|
200
363
|
* Define a message consumer.
|
|
201
364
|
*
|
|
202
365
|
* A consumer receives and processes messages from a queue. The message schema is validated
|
|
@@ -240,11 +403,14 @@ function definePublisher(exchange, message, options) {
|
|
|
240
403
|
*/
|
|
241
404
|
function defineConsumer(queue, message, options) {
|
|
242
405
|
return {
|
|
243
|
-
queue,
|
|
406
|
+
queue: extractQueue(queue),
|
|
244
407
|
message,
|
|
245
408
|
...options
|
|
246
409
|
};
|
|
247
410
|
}
|
|
411
|
+
|
|
412
|
+
//#endregion
|
|
413
|
+
//#region src/builder/contract.ts
|
|
248
414
|
/**
|
|
249
415
|
* Define an AMQP contract.
|
|
250
416
|
*
|
|
@@ -315,35 +481,44 @@ function defineConsumer(queue, message, options) {
|
|
|
315
481
|
* ```
|
|
316
482
|
*/
|
|
317
483
|
function defineContract(definition) {
|
|
318
|
-
return definition;
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
484
|
+
if (!definition.queues || Object.keys(definition.queues).length === 0) return definition;
|
|
485
|
+
const queues = definition.queues;
|
|
486
|
+
const expandedQueues = {};
|
|
487
|
+
const autoBindings = {};
|
|
488
|
+
for (const [name, entry] of Object.entries(queues)) if (isQueueWithTtlBackoffInfrastructure(entry)) {
|
|
489
|
+
expandedQueues[name] = entry.queue;
|
|
490
|
+
expandedQueues[`${name}Wait`] = entry.waitQueue;
|
|
491
|
+
autoBindings[`${name}WaitBinding`] = entry.waitQueueBinding;
|
|
492
|
+
autoBindings[`${name}RetryBinding`] = entry.mainQueueRetryBinding;
|
|
493
|
+
} else expandedQueues[name] = entry;
|
|
494
|
+
if (Object.keys(autoBindings).length > 0) {
|
|
495
|
+
const mergedBindings = {
|
|
496
|
+
...definition.bindings,
|
|
497
|
+
...autoBindings
|
|
498
|
+
};
|
|
499
|
+
return {
|
|
500
|
+
...definition,
|
|
501
|
+
queues: expandedQueues,
|
|
502
|
+
bindings: mergedBindings
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
return {
|
|
506
|
+
...definition,
|
|
507
|
+
queues: expandedQueues
|
|
508
|
+
};
|
|
337
509
|
}
|
|
510
|
+
|
|
511
|
+
//#endregion
|
|
512
|
+
//#region src/builder/publisher-first.ts
|
|
338
513
|
/**
|
|
339
514
|
* Implementation of definePublisherFirst.
|
|
340
515
|
* @internal
|
|
341
516
|
*/
|
|
342
517
|
function definePublisherFirst(exchange, message, options) {
|
|
343
|
-
const publisher =
|
|
518
|
+
const publisher = definePublisherInternal(exchange, message, options);
|
|
344
519
|
if (exchange.type === "topic") {
|
|
345
520
|
const createConsumer$1 = (queue, routingKey) => {
|
|
346
|
-
const binding =
|
|
521
|
+
const binding = defineQueueBindingInternal(queue, exchange, routingKey ? {
|
|
347
522
|
...options,
|
|
348
523
|
routingKey
|
|
349
524
|
} : options);
|
|
@@ -358,7 +533,7 @@ function definePublisherFirst(exchange, message, options) {
|
|
|
358
533
|
};
|
|
359
534
|
}
|
|
360
535
|
const createConsumer = (queue) => {
|
|
361
|
-
const binding =
|
|
536
|
+
const binding = defineQueueBindingInternal(queue, exchange, options);
|
|
362
537
|
return {
|
|
363
538
|
consumer: defineConsumer(queue, message),
|
|
364
539
|
binding
|
|
@@ -369,16 +544,19 @@ function definePublisherFirst(exchange, message, options) {
|
|
|
369
544
|
createConsumer
|
|
370
545
|
};
|
|
371
546
|
}
|
|
547
|
+
|
|
548
|
+
//#endregion
|
|
549
|
+
//#region src/builder/consumer-first.ts
|
|
372
550
|
/**
|
|
373
551
|
* Implementation of defineConsumerFirst.
|
|
374
552
|
* @internal
|
|
375
553
|
*/
|
|
376
554
|
function defineConsumerFirst(queue, exchange, message, options) {
|
|
377
555
|
const consumer = defineConsumer(queue, message);
|
|
378
|
-
const binding =
|
|
556
|
+
const binding = defineQueueBindingInternal(queue, exchange, options);
|
|
379
557
|
if (exchange.type === "topic") {
|
|
380
558
|
const createPublisher$1 = (routingKey) => {
|
|
381
|
-
return
|
|
559
|
+
return definePublisherInternal(exchange, message, {
|
|
382
560
|
...options,
|
|
383
561
|
routingKey
|
|
384
562
|
});
|
|
@@ -390,7 +568,7 @@ function defineConsumerFirst(queue, exchange, message, options) {
|
|
|
390
568
|
};
|
|
391
569
|
}
|
|
392
570
|
const createPublisher = () => {
|
|
393
|
-
return
|
|
571
|
+
return definePublisherInternal(exchange, message, options);
|
|
394
572
|
};
|
|
395
573
|
return {
|
|
396
574
|
consumer,
|
|
@@ -399,6 +577,78 @@ function defineConsumerFirst(queue, exchange, message, options) {
|
|
|
399
577
|
};
|
|
400
578
|
}
|
|
401
579
|
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/builder/ttl-backoff.ts
|
|
582
|
+
/**
|
|
583
|
+
* Create TTL-backoff retry infrastructure for a queue.
|
|
584
|
+
*
|
|
585
|
+
* This builder helper generates the wait queue and bindings needed for TTL-backoff retry.
|
|
586
|
+
* The generated infrastructure can be spread into a contract definition.
|
|
587
|
+
*
|
|
588
|
+
* TTL-backoff retry works by:
|
|
589
|
+
* 1. Failed messages are sent to the DLX with routing key `{queueName}-wait`
|
|
590
|
+
* 2. The wait queue receives these messages and holds them for a TTL period
|
|
591
|
+
* 3. After TTL expires, messages are dead-lettered back to the DLX with routing key `{queueName}`
|
|
592
|
+
* 4. The main queue receives the retried message via its binding to the DLX
|
|
593
|
+
*
|
|
594
|
+
* @param queue - The main queue definition (must have deadLetter configured)
|
|
595
|
+
* @param options - Optional configuration for the wait queue
|
|
596
|
+
* @param options.waitQueueDurable - Whether the wait queue should be durable (default: same as main queue)
|
|
597
|
+
* @returns TTL-backoff retry infrastructure containing wait queue and bindings
|
|
598
|
+
* @throws {Error} If the queue does not have a dead letter exchange configured
|
|
599
|
+
*
|
|
600
|
+
* @example
|
|
601
|
+
* ```typescript
|
|
602
|
+
* const dlx = defineExchange('orders-dlx', 'direct', { durable: true });
|
|
603
|
+
* const orderQueue = defineQueue('order-processing', {
|
|
604
|
+
* type: 'quorum',
|
|
605
|
+
* deadLetter: { exchange: dlx },
|
|
606
|
+
* retry: {
|
|
607
|
+
* mode: 'ttl-backoff',
|
|
608
|
+
* maxRetries: 5,
|
|
609
|
+
* initialDelayMs: 1000,
|
|
610
|
+
* },
|
|
611
|
+
* });
|
|
612
|
+
*
|
|
613
|
+
* // Generate TTL-backoff infrastructure
|
|
614
|
+
* const retryInfra = defineTtlBackoffRetryInfrastructure(orderQueue);
|
|
615
|
+
*
|
|
616
|
+
* // Spread into contract
|
|
617
|
+
* const contract = defineContract({
|
|
618
|
+
* exchanges: { dlx },
|
|
619
|
+
* queues: {
|
|
620
|
+
* orderProcessing: orderQueue,
|
|
621
|
+
* orderProcessingWait: retryInfra.waitQueue,
|
|
622
|
+
* },
|
|
623
|
+
* bindings: {
|
|
624
|
+
* ...// your other bindings
|
|
625
|
+
* orderWaitBinding: retryInfra.waitQueueBinding,
|
|
626
|
+
* orderRetryBinding: retryInfra.mainQueueRetryBinding,
|
|
627
|
+
* },
|
|
628
|
+
* // ... publishers and consumers
|
|
629
|
+
* });
|
|
630
|
+
* ```
|
|
631
|
+
*/
|
|
632
|
+
function defineTtlBackoffRetryInfrastructure(queueEntry, options) {
|
|
633
|
+
const queue = extractQueue(queueEntry);
|
|
634
|
+
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.`);
|
|
635
|
+
const dlx = queue.deadLetter.exchange;
|
|
636
|
+
const waitQueueName = `${queue.name}-wait`;
|
|
637
|
+
const waitQueue = defineQueue(waitQueueName, {
|
|
638
|
+
type: "quorum",
|
|
639
|
+
durable: options?.waitQueueDurable ?? queue.durable ?? true,
|
|
640
|
+
deadLetter: {
|
|
641
|
+
exchange: dlx,
|
|
642
|
+
routingKey: queue.name
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
return {
|
|
646
|
+
waitQueue,
|
|
647
|
+
waitQueueBinding: defineQueueBindingInternal(waitQueue, dlx, { routingKey: waitQueueName }),
|
|
648
|
+
mainQueueRetryBinding: defineQueueBindingInternal(queue, dlx, { routingKey: queue.name })
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
402
652
|
//#endregion
|
|
403
653
|
exports.defineConsumer = defineConsumer;
|
|
404
654
|
exports.defineConsumerFirst = defineConsumerFirst;
|
|
@@ -409,4 +659,6 @@ exports.defineMessage = defineMessage;
|
|
|
409
659
|
exports.definePublisher = definePublisher;
|
|
410
660
|
exports.definePublisherFirst = definePublisherFirst;
|
|
411
661
|
exports.defineQueue = defineQueue;
|
|
412
|
-
exports.defineQueueBinding = defineQueueBinding;
|
|
662
|
+
exports.defineQueueBinding = defineQueueBinding;
|
|
663
|
+
exports.defineTtlBackoffRetryInfrastructure = defineTtlBackoffRetryInfrastructure;
|
|
664
|
+
exports.extractQueue = extractQueue;
|