@amqp-contract/contract 0.20.0 → 0.21.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 +3 -3
- package/dist/index.cjs +271 -402
- package/dist/index.d.cts +555 -621
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +555 -621
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +270 -396
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +318 -562
- package/package.json +24 -24
package/dist/index.mjs
CHANGED
|
@@ -7,19 +7,23 @@
|
|
|
7
7
|
* type safety.
|
|
8
8
|
*
|
|
9
9
|
* @param name - The name of the exchange
|
|
10
|
-
* @param type - The type of exchange: "fanout", "direct", or "topic"
|
|
11
10
|
* @param options - Optional exchange configuration
|
|
11
|
+
* @param options.type - Exchange type (one of "topic", "direct", "fanout", "headers") (default: "topic")
|
|
12
|
+
* @param options.durable - If true, the exchange survives broker restarts (default: true)
|
|
13
|
+
* @param options.autoDelete - If true, the exchange is deleted when no queues are bound
|
|
14
|
+
* @param options.internal - If true, the exchange cannot be directly published to
|
|
15
|
+
* @param options.arguments - Additional AMQP arguments for the exchange
|
|
12
16
|
* @returns An exchange definition
|
|
13
17
|
* @internal
|
|
14
18
|
*/
|
|
15
|
-
function defineExchange(name,
|
|
19
|
+
function defineExchange(name, options) {
|
|
16
20
|
return {
|
|
17
21
|
name,
|
|
18
|
-
type,
|
|
22
|
+
type: options?.type ?? "topic",
|
|
23
|
+
durable: true,
|
|
19
24
|
...options
|
|
20
25
|
};
|
|
21
26
|
}
|
|
22
|
-
|
|
23
27
|
//#endregion
|
|
24
28
|
//#region src/builder/message.ts
|
|
25
29
|
/**
|
|
@@ -63,25 +67,64 @@ function defineMessage(payload, options) {
|
|
|
63
67
|
...options
|
|
64
68
|
};
|
|
65
69
|
}
|
|
66
|
-
|
|
67
70
|
//#endregion
|
|
68
71
|
//#region src/builder/queue-utils.ts
|
|
69
72
|
/**
|
|
70
|
-
*
|
|
73
|
+
* Extract the plain QueueDefinition from a QueueEntry.
|
|
71
74
|
* @internal
|
|
72
75
|
*/
|
|
73
|
-
function
|
|
74
|
-
|
|
76
|
+
function extractQueueFromEntry(entry) {
|
|
77
|
+
if (isQueueWithTtlBackoffInfrastructure(entry)) return entry.queue;
|
|
78
|
+
return entry;
|
|
75
79
|
}
|
|
76
80
|
/**
|
|
77
81
|
* Extract the plain QueueDefinition from a QueueEntry.
|
|
78
|
-
*
|
|
82
|
+
*
|
|
83
|
+
* **Why this function exists:**
|
|
84
|
+
* When you configure a queue with TTL-backoff retry,
|
|
85
|
+
* `defineQueue` returns a wrapper object that includes
|
|
86
|
+
* the main queue, wait queue, headers exchanges, and bindings. This function extracts the underlying
|
|
87
|
+
* queue definition so you can access properties like `name`, `type`, etc.
|
|
88
|
+
*
|
|
89
|
+
* **When to use:**
|
|
90
|
+
* - When you need to access queue properties (name, type, etc.)
|
|
91
|
+
* - When passing a queue to functions that expect a plain QueueDefinition
|
|
92
|
+
* - Works safely on both plain queues and infrastructure wrappers
|
|
93
|
+
*
|
|
94
|
+
* **How it works:**
|
|
95
|
+
* - If the entry is a `QueueWithTtlBackoffInfrastructure`, returns `entry.queue`
|
|
96
|
+
* - Otherwise, returns the entry as-is (it's already a plain QueueDefinition)
|
|
97
|
+
*
|
|
98
|
+
* @param entry - The queue entry (either plain QueueDefinition or QueueWithTtlBackoffInfrastructure)
|
|
99
|
+
* @returns The plain QueueDefinition
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* import { defineQueue, extractQueue } from '@amqp-contract/contract';
|
|
104
|
+
*
|
|
105
|
+
* // TTL-backoff queue returns a wrapper
|
|
106
|
+
* const orderQueue = defineQueue('orders', {
|
|
107
|
+
* retry: { mode: 'ttl-backoff', maxRetries: 3 },
|
|
108
|
+
* });
|
|
109
|
+
*
|
|
110
|
+
* // Use extractQueue to access the queue name
|
|
111
|
+
* const queueName = extractQueue(orderQueue).name; // 'orders'
|
|
112
|
+
*
|
|
113
|
+
* // Also works safely on plain queues
|
|
114
|
+
* const plainQueue = defineQueue('simple', { type: 'quorum', retry: { mode: 'immediate-requeue' } });
|
|
115
|
+
* const plainName = extractQueue(plainQueue).name; // 'simple'
|
|
116
|
+
*
|
|
117
|
+
* // Access other properties
|
|
118
|
+
* const queueDef = extractQueue(orderQueue);
|
|
119
|
+
* console.log(queueDef.name); // 'orders'
|
|
120
|
+
* console.log(queueDef.type); // 'quorum'
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* @see isQueueWithTtlBackoffInfrastructure - Type guard to check if extraction is needed
|
|
79
124
|
*/
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
return entry;
|
|
125
|
+
function extractQueue(entry) {
|
|
126
|
+
return extractQueueFromEntry(entry);
|
|
83
127
|
}
|
|
84
|
-
|
|
85
128
|
//#endregion
|
|
86
129
|
//#region src/builder/binding.ts
|
|
87
130
|
/**
|
|
@@ -96,8 +139,8 @@ function extractQueueFromEntry(entry) {
|
|
|
96
139
|
* @internal
|
|
97
140
|
*/
|
|
98
141
|
function defineQueueBinding(queue, exchange, options) {
|
|
99
|
-
const queueDef =
|
|
100
|
-
if (exchange.type === "fanout") return {
|
|
142
|
+
const queueDef = extractQueue(queue);
|
|
143
|
+
if (exchange.type === "fanout" || exchange.type === "headers") return {
|
|
101
144
|
type: "queue",
|
|
102
145
|
queue: queueDef,
|
|
103
146
|
exchange,
|
|
@@ -117,7 +160,7 @@ function defineQueueBinding(queue, exchange, options) {
|
|
|
117
160
|
* @internal
|
|
118
161
|
*/
|
|
119
162
|
function defineQueueBindingInternal(queue, exchange, options) {
|
|
120
|
-
if (exchange.type === "fanout") return defineQueueBinding(queue, exchange, options);
|
|
163
|
+
if (exchange.type === "fanout" || exchange.type === "headers") return defineQueueBinding(queue, exchange, options);
|
|
121
164
|
return defineQueueBinding(queue, exchange, options);
|
|
122
165
|
}
|
|
123
166
|
/**
|
|
@@ -132,7 +175,7 @@ function defineQueueBindingInternal(queue, exchange, options) {
|
|
|
132
175
|
* @internal
|
|
133
176
|
*/
|
|
134
177
|
function defineExchangeBinding(destination, source, options) {
|
|
135
|
-
if (source.type === "fanout") return {
|
|
178
|
+
if (source.type === "fanout" || source.type === "headers") return {
|
|
136
179
|
type: "exchange",
|
|
137
180
|
source,
|
|
138
181
|
destination,
|
|
@@ -146,27 +189,12 @@ function defineExchangeBinding(destination, source, options) {
|
|
|
146
189
|
...options?.arguments && { arguments: options.arguments }
|
|
147
190
|
};
|
|
148
191
|
}
|
|
149
|
-
|
|
150
192
|
//#endregion
|
|
151
|
-
//#region src/builder/
|
|
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
|
-
}
|
|
193
|
+
//#region src/builder/ttl-backoff.ts
|
|
166
194
|
/**
|
|
167
195
|
* Type guard to check if a queue entry is a QueueWithTtlBackoffInfrastructure.
|
|
168
196
|
*
|
|
169
|
-
* When you configure a queue with TTL-backoff retry
|
|
197
|
+
* When you configure a queue with TTL-backoff retry,
|
|
170
198
|
* `defineQueue` returns a `QueueWithTtlBackoffInfrastructure` instead of a plain
|
|
171
199
|
* `QueueDefinition`. This type guard helps you distinguish between the two.
|
|
172
200
|
*
|
|
@@ -183,12 +211,11 @@ function resolveTtlBackoffOptions(options) {
|
|
|
183
211
|
* @example
|
|
184
212
|
* ```typescript
|
|
185
213
|
* const queue = defineQueue('orders', {
|
|
186
|
-
* deadLetter: { exchange: dlx },
|
|
187
214
|
* retry: { mode: 'ttl-backoff' },
|
|
188
215
|
* });
|
|
189
216
|
*
|
|
190
217
|
* if (isQueueWithTtlBackoffInfrastructure(queue)) {
|
|
191
|
-
* // queue has .queue, .waitQueue, .waitQueueBinding, .
|
|
218
|
+
* // queue has .queue, .waitQueue, .waitQueueBinding, .retryQueueBinding, .waitExchange, .retryExchange
|
|
192
219
|
* console.log('Wait queue:', queue.waitQueue.name);
|
|
193
220
|
* } else {
|
|
194
221
|
* // queue is a plain QueueDefinition
|
|
@@ -197,256 +224,161 @@ function resolveTtlBackoffOptions(options) {
|
|
|
197
224
|
* ```
|
|
198
225
|
*/
|
|
199
226
|
function isQueueWithTtlBackoffInfrastructure(entry) {
|
|
200
|
-
return
|
|
227
|
+
return typeof entry === "object" && entry !== null && "__brand" in entry && entry.__brand === "QueueWithTtlBackoffInfrastructure";
|
|
201
228
|
}
|
|
202
229
|
/**
|
|
203
|
-
*
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
230
|
+
* Wrap a queue definition with TTL-backoff retry infrastructure.
|
|
231
|
+
*/
|
|
232
|
+
function wrapWithTtlBackoffInfrastructure(queue) {
|
|
233
|
+
return {
|
|
234
|
+
__brand: "QueueWithTtlBackoffInfrastructure",
|
|
235
|
+
queue,
|
|
236
|
+
...createTtlBackoffInfrastructure(queue)
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Create TTL-backoff retry infrastructure for a queue.
|
|
210
241
|
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
* - When passing a queue to functions that expect a plain QueueDefinition
|
|
214
|
-
* - Works safely on both plain queues and infrastructure wrappers
|
|
242
|
+
* This builder helper generates the wait queue, exchanges, and bindings needed for TTL-backoff retry.
|
|
243
|
+
* The generated infrastructure can be spread into a contract definition.
|
|
215
244
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
245
|
+
* TTL-backoff retry works by:
|
|
246
|
+
* 1. Failed messages are sent to the wait exchange with header `x-wait-queue` set to the wait queue name
|
|
247
|
+
* 2. The wait queue receives these messages and holds them for a TTL period
|
|
248
|
+
* 3. After TTL expires, messages are dead-lettered back to the retry exchange with header `x-retry-queue` set to the main queue name
|
|
249
|
+
* 4. The main queue receives the retried message via its binding to the retry exchange
|
|
219
250
|
*
|
|
220
|
-
* @param
|
|
221
|
-
* @
|
|
251
|
+
* @param queue - The main queue definition
|
|
252
|
+
* @param options - Optional configuration for the wait queue
|
|
253
|
+
* @returns TTL-backoff retry infrastructure containing wait queue and bindings
|
|
254
|
+
* @throws {Error} If the queue does not have retry mode set to `ttl-backoff`
|
|
222
255
|
*
|
|
223
256
|
* @example
|
|
224
257
|
* ```typescript
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
258
|
+
* const orderQueue = defineQueue('order-processing', {
|
|
259
|
+
* type: 'quorum',
|
|
260
|
+
* retry: {
|
|
261
|
+
* mode: 'ttl-backoff',
|
|
262
|
+
* maxRetries: 5,
|
|
263
|
+
* initialDelayMs: 1000,
|
|
264
|
+
* },
|
|
231
265
|
* });
|
|
232
266
|
*
|
|
233
|
-
* //
|
|
234
|
-
* const
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
* // Access other properties
|
|
241
|
-
* const queueDef = extractQueue(orderQueue);
|
|
242
|
-
* console.log(queueDef.name); // 'orders'
|
|
243
|
-
* console.log(queueDef.type); // 'quorum'
|
|
244
|
-
* console.log(queueDef.deadLetter); // { exchange: dlx, ... }
|
|
267
|
+
* // Infrastructure is auto-extracted when using defineContract:
|
|
268
|
+
* const contract = defineContract({
|
|
269
|
+
* publishers: { ... },
|
|
270
|
+
* consumers: { processOrder: defineEventConsumer(event, orderQueue) },
|
|
271
|
+
* });
|
|
272
|
+
* // contract.queues includes the wait queue, contract.exchanges includes retry exchanges, contract.bindings includes retry bindings
|
|
245
273
|
* ```
|
|
246
|
-
*
|
|
247
|
-
* @see isQueueWithTtlBackoffInfrastructure - Type guard to check if extraction is needed
|
|
248
|
-
* @see defineTtlBackoffQueue - Creates queues with TTL-backoff infrastructure
|
|
249
274
|
*/
|
|
250
|
-
function
|
|
251
|
-
|
|
275
|
+
function createTtlBackoffInfrastructure(queue) {
|
|
276
|
+
if (queue.retry.mode !== "ttl-backoff") throw new Error(`Queue ${queue.name} does not have ttl-backoff retry mode. Infrastructure can only be created for queues with ttl-backoff retry.`);
|
|
277
|
+
const waitExchange = defineExchange(queue.retry.waitExchangeName, { type: "headers" });
|
|
278
|
+
const retryExchange = defineExchange(queue.retry.retryExchangeName, { type: "headers" });
|
|
279
|
+
const baseWaitQueue = {
|
|
280
|
+
name: queue.retry.waitQueueName,
|
|
281
|
+
deadLetter: { exchange: retryExchange },
|
|
282
|
+
retry: { mode: "none" }
|
|
283
|
+
};
|
|
284
|
+
const waitQueue = queue.type === "quorum" ? {
|
|
285
|
+
...baseWaitQueue,
|
|
286
|
+
type: queue.type,
|
|
287
|
+
durable: true
|
|
288
|
+
} : {
|
|
289
|
+
...baseWaitQueue,
|
|
290
|
+
type: queue.type,
|
|
291
|
+
durable: queue.durable
|
|
292
|
+
};
|
|
293
|
+
return {
|
|
294
|
+
waitQueue,
|
|
295
|
+
waitExchange,
|
|
296
|
+
retryExchange,
|
|
297
|
+
waitQueueBinding: defineQueueBindingInternal(waitQueue, waitExchange, { arguments: {
|
|
298
|
+
"x-match": "all",
|
|
299
|
+
"x-wait-queue": waitQueue.name
|
|
300
|
+
} }),
|
|
301
|
+
retryQueueBinding: defineQueueBindingInternal(queue, retryExchange, { arguments: {
|
|
302
|
+
"x-match": "all",
|
|
303
|
+
"x-retry-queue": queue.name
|
|
304
|
+
} })
|
|
305
|
+
};
|
|
252
306
|
}
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region src/builder/queue.ts
|
|
253
309
|
/**
|
|
254
|
-
*
|
|
310
|
+
* Resolve immediate-requeue retry options with defaults.
|
|
255
311
|
* @internal
|
|
256
312
|
*/
|
|
257
|
-
function
|
|
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
|
-
};
|
|
313
|
+
function resolveImmediateRequeueOptions(options) {
|
|
271
314
|
return {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
mainQueueRetryBinding: defineQueueBindingInternal(queue, dlx, { routingKey: queue.name })
|
|
315
|
+
mode: "immediate-requeue",
|
|
316
|
+
maxRetries: options?.maxRetries ?? 3
|
|
275
317
|
};
|
|
276
318
|
}
|
|
277
319
|
/**
|
|
278
|
-
*
|
|
320
|
+
* Resolve TTL-backoff retry options with defaults.
|
|
279
321
|
* @internal
|
|
280
322
|
*/
|
|
281
|
-
function
|
|
282
|
-
const infra = createTtlBackoffInfrastructure(queue);
|
|
323
|
+
function resolveTtlBackoffOptions(queueName, options) {
|
|
283
324
|
return {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
325
|
+
mode: "ttl-backoff",
|
|
326
|
+
maxRetries: options?.maxRetries ?? 3,
|
|
327
|
+
initialDelayMs: options?.initialDelayMs ?? 1e3,
|
|
328
|
+
maxDelayMs: options?.maxDelayMs ?? 3e4,
|
|
329
|
+
backoffMultiplier: options?.backoffMultiplier ?? 2,
|
|
330
|
+
jitter: options?.jitter ?? true,
|
|
331
|
+
waitQueueName: options?.waitQueueName ?? `${queueName}-wait`,
|
|
332
|
+
waitExchangeName: options?.waitExchangeName ?? "wait-exchange",
|
|
333
|
+
retryExchangeName: options?.retryExchangeName ?? "retry-exchange"
|
|
288
334
|
};
|
|
289
335
|
}
|
|
290
336
|
function defineQueue(name, options) {
|
|
291
337
|
const opts = options ?? {};
|
|
292
338
|
const type = opts.type ?? "quorum";
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
339
|
+
const durable = opts.durable ?? true;
|
|
340
|
+
const baseProps = {
|
|
341
|
+
name,
|
|
342
|
+
...opts.deadLetter !== void 0 && { deadLetter: opts.deadLetter },
|
|
343
|
+
...opts.arguments !== void 0 && { arguments: opts.arguments }
|
|
344
|
+
};
|
|
345
|
+
const classicProps = {
|
|
346
|
+
...opts.exclusive !== void 0 && { exclusive: opts.exclusive },
|
|
347
|
+
...opts.autoDelete !== void 0 && { autoDelete: opts.autoDelete },
|
|
348
|
+
...opts.maxPriority !== void 0 && { maxPriority: opts.maxPriority }
|
|
349
|
+
};
|
|
298
350
|
if (type === "quorum") {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (quorumOpts.deliveryLimit !== void 0) {
|
|
311
|
-
if (quorumOpts.deliveryLimit < 1 || !Number.isInteger(quorumOpts.deliveryLimit)) throw new Error(`Invalid deliveryLimit: ${quorumOpts.deliveryLimit}. Must be a positive integer.`);
|
|
312
|
-
queueDefinition.deliveryLimit = quorumOpts.deliveryLimit;
|
|
351
|
+
if (opts.durable === false) throw new Error("Non-durable queues are not supported with quorum type.");
|
|
352
|
+
if (opts.exclusive !== void 0) throw new Error("Exclusive queues are not supported with quorum type.");
|
|
353
|
+
if (opts.autoDelete !== void 0) throw new Error("Auto-deleting queues are not supported with quorum type.");
|
|
354
|
+
if (opts.maxPriority !== void 0) throw new Error("Priority queues are not supported with quorum type.");
|
|
355
|
+
} else if (opts.maxPriority !== void 0) {
|
|
356
|
+
if (opts.maxPriority < 1 || opts.maxPriority > 255) throw new Error(`Invalid maxPriority: ${opts.maxPriority}. Must be between 1 and 255. Recommended range: 1-10.`);
|
|
357
|
+
}
|
|
358
|
+
const inputRetry = opts.retry ?? { mode: "none" };
|
|
359
|
+
if (inputRetry.mode === "immediate-requeue" || inputRetry.mode === "ttl-backoff") {
|
|
360
|
+
if (inputRetry.maxRetries !== void 0) {
|
|
361
|
+
if (inputRetry.maxRetries < 1 || !Number.isInteger(inputRetry.maxRetries)) throw new Error(`Queue "${name}" uses ${inputRetry.mode} retry mode with invalid maxRetries: ${inputRetry.maxRetries}. Must be a positive integer.`);
|
|
313
362
|
}
|
|
314
|
-
if (retry.mode === "ttl-backoff" && queueDefinition.deadLetter) return wrapWithTtlBackoffInfrastructure(queueDefinition);
|
|
315
|
-
return queueDefinition;
|
|
316
363
|
}
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
const retry = resolveTtlBackoffOptions(classicOpts.retry);
|
|
320
|
-
const queueDefinition = {
|
|
364
|
+
const retry = inputRetry.mode === "immediate-requeue" ? resolveImmediateRequeueOptions(inputRetry) : inputRetry.mode === "ttl-backoff" ? resolveTtlBackoffOptions(name, inputRetry) : inputRetry;
|
|
365
|
+
const baseQueueDefinition = {
|
|
321
366
|
...baseProps,
|
|
322
|
-
type: "classic",
|
|
323
367
|
retry
|
|
324
368
|
};
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
return queueDefinition;
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Create a quorum queue with quorum-native retry.
|
|
338
|
-
*
|
|
339
|
-
* This is a simplified helper that enforces best practices:
|
|
340
|
-
* - Uses quorum queues (recommended for most use cases)
|
|
341
|
-
* - Requires dead letter exchange for failed message handling
|
|
342
|
-
* - Uses quorum-native retry mode (simpler than TTL-backoff)
|
|
343
|
-
*
|
|
344
|
-
* **When to use:**
|
|
345
|
-
* - You want simple, immediate retries without exponential backoff
|
|
346
|
-
* - You don't need configurable delays between retries
|
|
347
|
-
* - You want the simplest retry configuration
|
|
348
|
-
*
|
|
349
|
-
* @param name - The queue name
|
|
350
|
-
* @param options - Configuration options
|
|
351
|
-
* @returns A quorum queue definition with quorum-native retry
|
|
352
|
-
*
|
|
353
|
-
* @example
|
|
354
|
-
* ```typescript
|
|
355
|
-
* const dlx = defineExchange('orders-dlx', 'direct', { durable: true });
|
|
356
|
-
*
|
|
357
|
-
* const orderQueue = defineQuorumQueue('order-processing', {
|
|
358
|
-
* deadLetter: { exchange: dlx },
|
|
359
|
-
* deliveryLimit: 3, // Retry up to 3 times
|
|
360
|
-
* });
|
|
361
|
-
*
|
|
362
|
-
* // Use in a contract — exchanges, queues, and bindings are auto-extracted
|
|
363
|
-
* const contract = defineContract({
|
|
364
|
-
* publishers: { ... },
|
|
365
|
-
* consumers: { processOrder: defineEventConsumer(event, orderQueue) },
|
|
366
|
-
* });
|
|
367
|
-
* ```
|
|
368
|
-
*
|
|
369
|
-
* @see defineQueue - For full queue configuration options
|
|
370
|
-
* @see defineTtlBackoffQueue - For queues with exponential backoff retry
|
|
371
|
-
*/
|
|
372
|
-
function defineQuorumQueue(name, options) {
|
|
373
|
-
const { deadLetter, deliveryLimit, autoDelete, arguments: args } = options;
|
|
374
|
-
const queueOptions = {
|
|
375
|
-
type: "quorum",
|
|
376
|
-
deadLetter,
|
|
377
|
-
deliveryLimit,
|
|
378
|
-
retry: { mode: "quorum-native" }
|
|
379
|
-
};
|
|
380
|
-
if (autoDelete !== void 0) queueOptions.autoDelete = autoDelete;
|
|
381
|
-
if (args !== void 0) queueOptions.arguments = args;
|
|
382
|
-
return defineQueue(name, queueOptions);
|
|
383
|
-
}
|
|
384
|
-
/**
|
|
385
|
-
* Create a queue with TTL-backoff retry (exponential backoff).
|
|
386
|
-
*
|
|
387
|
-
* This is a simplified helper that enforces best practices:
|
|
388
|
-
* - Uses quorum queues (recommended for most use cases)
|
|
389
|
-
* - Requires dead letter exchange for retry routing
|
|
390
|
-
* - Uses TTL-backoff retry mode with configurable delays
|
|
391
|
-
* - Automatically generates wait queue and bindings
|
|
392
|
-
*
|
|
393
|
-
* **When to use:**
|
|
394
|
-
* - You need exponential backoff between retries
|
|
395
|
-
* - You want configurable delays (initial delay, max delay, jitter)
|
|
396
|
-
* - You're processing messages that may need time before retry
|
|
397
|
-
*
|
|
398
|
-
* **Returns:** A `QueueWithTtlBackoffInfrastructure` object that includes the
|
|
399
|
-
* main queue, wait queue, and bindings. Pass this directly to `defineContract`
|
|
400
|
-
* and it will be expanded automatically.
|
|
401
|
-
*
|
|
402
|
-
* @param name - The queue name
|
|
403
|
-
* @param options - Configuration options
|
|
404
|
-
* @returns A queue with TTL-backoff infrastructure
|
|
405
|
-
*
|
|
406
|
-
* @example
|
|
407
|
-
* ```typescript
|
|
408
|
-
* const dlx = defineExchange('orders-dlx', 'direct', { durable: true });
|
|
409
|
-
*
|
|
410
|
-
* const orderQueue = defineTtlBackoffQueue('order-processing', {
|
|
411
|
-
* deadLetter: { exchange: dlx },
|
|
412
|
-
* maxRetries: 5,
|
|
413
|
-
* initialDelayMs: 1000, // Start with 1s delay
|
|
414
|
-
* maxDelayMs: 30000, // Cap at 30s
|
|
415
|
-
* });
|
|
416
|
-
*
|
|
417
|
-
* // Use in a contract — wait queue, bindings, and DLX are auto-extracted
|
|
418
|
-
* const contract = defineContract({
|
|
419
|
-
* publishers: { ... },
|
|
420
|
-
* consumers: { processOrder: defineEventConsumer(event, extractQueue(orderQueue)) },
|
|
421
|
-
* });
|
|
422
|
-
*
|
|
423
|
-
* // To access the underlying queue definition (e.g., for the queue name):
|
|
424
|
-
* import { extractQueue } from '@amqp-contract/contract';
|
|
425
|
-
* const queueName = extractQueue(orderQueue).name;
|
|
426
|
-
* ```
|
|
427
|
-
*
|
|
428
|
-
* @see defineQueue - For full queue configuration options
|
|
429
|
-
* @see defineQuorumQueue - For queues with quorum-native retry (simpler, immediate retries)
|
|
430
|
-
* @see extractQueue - To access the underlying queue definition
|
|
431
|
-
*/
|
|
432
|
-
function defineTtlBackoffQueue(name, options) {
|
|
433
|
-
const { deadLetter, maxRetries, initialDelayMs, maxDelayMs, backoffMultiplier, jitter, autoDelete, arguments: args } = options;
|
|
434
|
-
const retryOptions = { mode: "ttl-backoff" };
|
|
435
|
-
if (maxRetries !== void 0) retryOptions.maxRetries = maxRetries;
|
|
436
|
-
if (initialDelayMs !== void 0) retryOptions.initialDelayMs = initialDelayMs;
|
|
437
|
-
if (maxDelayMs !== void 0) retryOptions.maxDelayMs = maxDelayMs;
|
|
438
|
-
if (backoffMultiplier !== void 0) retryOptions.backoffMultiplier = backoffMultiplier;
|
|
439
|
-
if (jitter !== void 0) retryOptions.jitter = jitter;
|
|
440
|
-
const queueOptions = {
|
|
441
|
-
type: "quorum",
|
|
442
|
-
deadLetter,
|
|
443
|
-
retry: retryOptions
|
|
369
|
+
const queueDefinition = type === "quorum" ? {
|
|
370
|
+
...baseQueueDefinition,
|
|
371
|
+
type,
|
|
372
|
+
durable: true
|
|
373
|
+
} : {
|
|
374
|
+
...baseQueueDefinition,
|
|
375
|
+
...classicProps,
|
|
376
|
+
type,
|
|
377
|
+
durable
|
|
444
378
|
};
|
|
445
|
-
if (
|
|
446
|
-
|
|
447
|
-
return defineQueue(name, queueOptions);
|
|
379
|
+
if (retry.mode === "ttl-backoff") return wrapWithTtlBackoffInfrastructure(queueDefinition);
|
|
380
|
+
return queueDefinition;
|
|
448
381
|
}
|
|
449
|
-
|
|
450
382
|
//#endregion
|
|
451
383
|
//#region src/builder/publisher.ts
|
|
452
384
|
/**
|
|
@@ -461,7 +393,7 @@ function defineTtlBackoffQueue(name, options) {
|
|
|
461
393
|
* @internal
|
|
462
394
|
*/
|
|
463
395
|
function definePublisher(exchange, message, options) {
|
|
464
|
-
if (exchange.type === "fanout") return {
|
|
396
|
+
if (exchange.type === "fanout" || exchange.type === "headers") return {
|
|
465
397
|
exchange,
|
|
466
398
|
message
|
|
467
399
|
};
|
|
@@ -477,10 +409,9 @@ function definePublisher(exchange, message, options) {
|
|
|
477
409
|
* @internal
|
|
478
410
|
*/
|
|
479
411
|
function definePublisherInternal(exchange, message, options) {
|
|
480
|
-
if (exchange.type === "fanout") return definePublisher(exchange, message, options);
|
|
412
|
+
if (exchange.type === "fanout" || exchange.type === "headers") return definePublisher(exchange, message, options);
|
|
481
413
|
return definePublisher(exchange, message, options);
|
|
482
414
|
}
|
|
483
|
-
|
|
484
415
|
//#endregion
|
|
485
416
|
//#region src/builder/consumer.ts
|
|
486
417
|
/**
|
|
@@ -560,7 +491,7 @@ function extractConsumer(entry) {
|
|
|
560
491
|
* ```typescript
|
|
561
492
|
* import { z } from 'zod';
|
|
562
493
|
*
|
|
563
|
-
* const orderQueue = defineQueue('order-processing'
|
|
494
|
+
* const orderQueue = defineQueue('order-processing');
|
|
564
495
|
* const orderMessage = defineMessage(
|
|
565
496
|
* z.object({
|
|
566
497
|
* orderId: z.string().uuid(),
|
|
@@ -589,12 +520,72 @@ function extractConsumer(entry) {
|
|
|
589
520
|
*/
|
|
590
521
|
function defineConsumer(queue, message, options) {
|
|
591
522
|
return {
|
|
592
|
-
queue
|
|
523
|
+
queue,
|
|
593
524
|
message,
|
|
594
525
|
...options
|
|
595
526
|
};
|
|
596
527
|
}
|
|
597
|
-
|
|
528
|
+
//#endregion
|
|
529
|
+
//#region src/builder/command.ts
|
|
530
|
+
/**
|
|
531
|
+
* Implementation of defineCommandConsumer.
|
|
532
|
+
* @internal
|
|
533
|
+
*/
|
|
534
|
+
function defineCommandConsumer(queue, exchange, message, options) {
|
|
535
|
+
return {
|
|
536
|
+
__brand: "CommandConsumerConfig",
|
|
537
|
+
consumer: defineConsumer(queue, message),
|
|
538
|
+
binding: defineQueueBindingInternal(queue, exchange, options),
|
|
539
|
+
exchange,
|
|
540
|
+
queue,
|
|
541
|
+
message,
|
|
542
|
+
routingKey: options?.routingKey
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Implementation of defineCommandPublisher.
|
|
547
|
+
* @internal
|
|
548
|
+
*/
|
|
549
|
+
function defineCommandPublisher(commandConsumer, options) {
|
|
550
|
+
const { exchange: targetExchange, message, routingKey: consumerRoutingKey } = commandConsumer;
|
|
551
|
+
const publisherRoutingKey = options?.routingKey ?? consumerRoutingKey;
|
|
552
|
+
const bridgeExchange = options?.bridgeExchange;
|
|
553
|
+
if (bridgeExchange) {
|
|
554
|
+
const publisherOptions = {};
|
|
555
|
+
if (publisherRoutingKey !== void 0) publisherOptions.routingKey = publisherRoutingKey;
|
|
556
|
+
const publisher = definePublisherInternal(bridgeExchange, message, publisherOptions);
|
|
557
|
+
const e2eBindingOptions = {};
|
|
558
|
+
if (publisherRoutingKey !== void 0) e2eBindingOptions.routingKey = publisherRoutingKey;
|
|
559
|
+
return {
|
|
560
|
+
__brand: "BridgedPublisherConfig",
|
|
561
|
+
publisher,
|
|
562
|
+
exchangeBinding: bridgeExchange.type === "fanout" || bridgeExchange.type === "headers" ? defineExchangeBinding(targetExchange, bridgeExchange) : defineExchangeBinding(targetExchange, bridgeExchange, e2eBindingOptions),
|
|
563
|
+
bridgeExchange,
|
|
564
|
+
targetExchange
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const publisherOptions = {};
|
|
568
|
+
if (publisherRoutingKey !== void 0) publisherOptions.routingKey = publisherRoutingKey;
|
|
569
|
+
return definePublisherInternal(targetExchange, message, publisherOptions);
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Type guard to check if a value is a CommandConsumerConfig.
|
|
573
|
+
*
|
|
574
|
+
* @param value - The value to check
|
|
575
|
+
* @returns True if the value is a CommandConsumerConfig
|
|
576
|
+
*/
|
|
577
|
+
function isCommandConsumerConfig(value) {
|
|
578
|
+
return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "CommandConsumerConfig";
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Type guard to check if a value is a BridgedPublisherConfig.
|
|
582
|
+
*
|
|
583
|
+
* @param value - The value to check
|
|
584
|
+
* @returns True if the value is a BridgedPublisherConfig
|
|
585
|
+
*/
|
|
586
|
+
function isBridgedPublisherConfig(value) {
|
|
587
|
+
return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "BridgedPublisherConfig";
|
|
588
|
+
}
|
|
598
589
|
//#endregion
|
|
599
590
|
//#region src/builder/event.ts
|
|
600
591
|
/**
|
|
@@ -628,27 +619,23 @@ function defineEventConsumer(eventPublisher, queue, options) {
|
|
|
628
619
|
const consumer = defineConsumer(queue, message);
|
|
629
620
|
const exchangeBindingOptions = {};
|
|
630
621
|
if (bindingRoutingKey !== void 0) exchangeBindingOptions.routingKey = bindingRoutingKey;
|
|
631
|
-
const e2eBinding = sourceExchange.type === "fanout" ? defineExchangeBinding(bridgeExchange, sourceExchange) : defineExchangeBinding(bridgeExchange, sourceExchange, exchangeBindingOptions);
|
|
632
622
|
return {
|
|
633
623
|
__brand: "EventConsumerResult",
|
|
634
624
|
consumer,
|
|
635
625
|
binding,
|
|
636
626
|
exchange: sourceExchange,
|
|
637
|
-
queue
|
|
638
|
-
|
|
639
|
-
exchangeBinding: e2eBinding,
|
|
627
|
+
queue,
|
|
628
|
+
exchangeBinding: sourceExchange.type === "fanout" || sourceExchange.type === "headers" ? defineExchangeBinding(bridgeExchange, sourceExchange) : defineExchangeBinding(bridgeExchange, sourceExchange, exchangeBindingOptions),
|
|
640
629
|
bridgeExchange
|
|
641
630
|
};
|
|
642
631
|
}
|
|
643
632
|
const binding = defineQueueBindingInternal(queue, sourceExchange, bindingOptions);
|
|
644
|
-
const consumer = defineConsumer(queue, message);
|
|
645
633
|
return {
|
|
646
634
|
__brand: "EventConsumerResult",
|
|
647
|
-
consumer,
|
|
635
|
+
consumer: defineConsumer(queue, message),
|
|
648
636
|
binding,
|
|
649
637
|
exchange: sourceExchange,
|
|
650
|
-
queue
|
|
651
|
-
deadLetterExchange: consumer.queue.deadLetter?.exchange,
|
|
638
|
+
queue,
|
|
652
639
|
exchangeBinding: void 0,
|
|
653
640
|
bridgeExchange: void 0
|
|
654
641
|
};
|
|
@@ -671,71 +658,6 @@ function isEventPublisherConfig(value) {
|
|
|
671
658
|
function isEventConsumerResult(value) {
|
|
672
659
|
return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "EventConsumerResult";
|
|
673
660
|
}
|
|
674
|
-
|
|
675
|
-
//#endregion
|
|
676
|
-
//#region src/builder/command.ts
|
|
677
|
-
/**
|
|
678
|
-
* Implementation of defineCommandConsumer.
|
|
679
|
-
* @internal
|
|
680
|
-
*/
|
|
681
|
-
function defineCommandConsumer(queue, exchange, message, options) {
|
|
682
|
-
const consumer = defineConsumer(queue, message);
|
|
683
|
-
return {
|
|
684
|
-
__brand: "CommandConsumerConfig",
|
|
685
|
-
consumer,
|
|
686
|
-
binding: defineQueueBindingInternal(queue, exchange, options),
|
|
687
|
-
exchange,
|
|
688
|
-
queue: consumer.queue,
|
|
689
|
-
deadLetterExchange: consumer.queue.deadLetter?.exchange,
|
|
690
|
-
message,
|
|
691
|
-
routingKey: options?.routingKey
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
/**
|
|
695
|
-
* Implementation of defineCommandPublisher.
|
|
696
|
-
* @internal
|
|
697
|
-
*/
|
|
698
|
-
function defineCommandPublisher(commandConsumer, options) {
|
|
699
|
-
const { exchange: targetExchange, message, routingKey: consumerRoutingKey } = commandConsumer;
|
|
700
|
-
const publisherRoutingKey = options?.routingKey ?? consumerRoutingKey;
|
|
701
|
-
const bridgeExchange = options?.bridgeExchange;
|
|
702
|
-
if (bridgeExchange) {
|
|
703
|
-
const publisherOptions = {};
|
|
704
|
-
if (publisherRoutingKey !== void 0) publisherOptions.routingKey = publisherRoutingKey;
|
|
705
|
-
const publisher = definePublisherInternal(bridgeExchange, message, publisherOptions);
|
|
706
|
-
const e2eBindingOptions = {};
|
|
707
|
-
if (publisherRoutingKey !== void 0) e2eBindingOptions.routingKey = publisherRoutingKey;
|
|
708
|
-
return {
|
|
709
|
-
__brand: "BridgedPublisherConfig",
|
|
710
|
-
publisher,
|
|
711
|
-
exchangeBinding: bridgeExchange.type === "fanout" ? defineExchangeBinding(targetExchange, bridgeExchange) : defineExchangeBinding(targetExchange, bridgeExchange, e2eBindingOptions),
|
|
712
|
-
bridgeExchange,
|
|
713
|
-
targetExchange
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
|
-
const publisherOptions = {};
|
|
717
|
-
if (publisherRoutingKey !== void 0) publisherOptions.routingKey = publisherRoutingKey;
|
|
718
|
-
return definePublisherInternal(targetExchange, message, publisherOptions);
|
|
719
|
-
}
|
|
720
|
-
/**
|
|
721
|
-
* Type guard to check if a value is a CommandConsumerConfig.
|
|
722
|
-
*
|
|
723
|
-
* @param value - The value to check
|
|
724
|
-
* @returns True if the value is a CommandConsumerConfig
|
|
725
|
-
*/
|
|
726
|
-
function isCommandConsumerConfig(value) {
|
|
727
|
-
return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "CommandConsumerConfig";
|
|
728
|
-
}
|
|
729
|
-
/**
|
|
730
|
-
* Type guard to check if a value is a BridgedPublisherConfig.
|
|
731
|
-
*
|
|
732
|
-
* @param value - The value to check
|
|
733
|
-
* @returns True if the value is a BridgedPublisherConfig
|
|
734
|
-
*/
|
|
735
|
-
function isBridgedPublisherConfig(value) {
|
|
736
|
-
return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "BridgedPublisherConfig";
|
|
737
|
-
}
|
|
738
|
-
|
|
739
661
|
//#endregion
|
|
740
662
|
//#region src/builder/contract.ts
|
|
741
663
|
/**
|
|
@@ -767,12 +689,11 @@ function isBridgedPublisherConfig(value) {
|
|
|
767
689
|
* import { z } from 'zod';
|
|
768
690
|
*
|
|
769
691
|
* // Define resources
|
|
770
|
-
* const ordersExchange = defineExchange('orders'
|
|
771
|
-
* const dlx = defineExchange('orders-dlx',
|
|
692
|
+
* const ordersExchange = defineExchange('orders');
|
|
693
|
+
* const dlx = defineExchange('orders-dlx', { type: 'direct' });
|
|
772
694
|
* const orderQueue = defineQueue('order-processing', {
|
|
773
695
|
* deadLetter: { exchange: dlx },
|
|
774
|
-
* retry: { mode: '
|
|
775
|
-
* deliveryLimit: 3,
|
|
696
|
+
* retry: { mode: 'immediate-requeue', maxRetries: 3 },
|
|
776
697
|
* });
|
|
777
698
|
* const orderMessage = defineMessage(
|
|
778
699
|
* z.object({
|
|
@@ -851,9 +772,10 @@ function defineContract(definition) {
|
|
|
851
772
|
processedConsumers[name] = entry.consumer;
|
|
852
773
|
consumerBindings[`${name}Binding`] = entry.binding;
|
|
853
774
|
const queueEntry = entry.consumer.queue;
|
|
854
|
-
|
|
775
|
+
const queueDef = extractQueue(queueEntry);
|
|
776
|
+
queues[queueDef.name] = queueEntry;
|
|
855
777
|
exchanges[entry.binding.exchange.name] = entry.binding.exchange;
|
|
856
|
-
if (
|
|
778
|
+
if (queueDef.deadLetter?.exchange) exchanges[queueDef.deadLetter.exchange.name] = queueDef.deadLetter.exchange;
|
|
857
779
|
if (entry.exchangeBinding) consumerBindings[`${name}ExchangeBinding`] = entry.exchangeBinding;
|
|
858
780
|
if (entry.bridgeExchange) exchanges[entry.bridgeExchange.name] = entry.bridgeExchange;
|
|
859
781
|
if (entry.exchange) exchanges[entry.exchange.name] = entry.exchange;
|
|
@@ -861,22 +783,24 @@ function defineContract(definition) {
|
|
|
861
783
|
processedConsumers[name] = entry.consumer;
|
|
862
784
|
consumerBindings[`${name}Binding`] = entry.binding;
|
|
863
785
|
const queueEntry = entry.consumer.queue;
|
|
864
|
-
|
|
786
|
+
const queueDef = extractQueue(queueEntry);
|
|
787
|
+
queues[queueDef.name] = queueEntry;
|
|
865
788
|
exchanges[entry.exchange.name] = entry.exchange;
|
|
866
|
-
if (
|
|
789
|
+
if (queueDef.deadLetter?.exchange) exchanges[queueDef.deadLetter.exchange.name] = queueDef.deadLetter.exchange;
|
|
867
790
|
} else {
|
|
868
791
|
const consumer = entry;
|
|
869
792
|
processedConsumers[name] = consumer;
|
|
870
793
|
const queueEntry = consumer.queue;
|
|
871
|
-
|
|
872
|
-
|
|
794
|
+
const queueDef = extractQueue(queueEntry);
|
|
795
|
+
queues[queueDef.name] = queueEntry;
|
|
796
|
+
if (queueDef.deadLetter?.exchange) exchanges[queueDef.deadLetter.exchange.name] = queueDef.deadLetter.exchange;
|
|
873
797
|
}
|
|
874
|
-
for (const
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
consumerBindings[`${queue.name}
|
|
878
|
-
|
|
879
|
-
exchanges[
|
|
798
|
+
for (const queueEntry of Object.values(queues)) if (isQueueWithTtlBackoffInfrastructure(queueEntry)) {
|
|
799
|
+
queues[queueEntry.waitQueue.name] = queueEntry.waitQueue;
|
|
800
|
+
consumerBindings[`${queueEntry.queue.name}WaitBinding`] = queueEntry.waitQueueBinding;
|
|
801
|
+
consumerBindings[`${queueEntry.queue.name}RetryBinding`] = queueEntry.retryQueueBinding;
|
|
802
|
+
exchanges[queueEntry.waitExchange.name] = queueEntry.waitExchange;
|
|
803
|
+
exchanges[queueEntry.retryExchange.name] = queueEntry.retryExchange;
|
|
880
804
|
}
|
|
881
805
|
result.consumers = processedConsumers;
|
|
882
806
|
result.bindings = {
|
|
@@ -894,57 +818,7 @@ function defineContract(definition) {
|
|
|
894
818
|
}
|
|
895
819
|
return result;
|
|
896
820
|
}
|
|
897
|
-
|
|
898
821
|
//#endregion
|
|
899
|
-
|
|
900
|
-
/**
|
|
901
|
-
* Create TTL-backoff retry infrastructure for a queue.
|
|
902
|
-
*
|
|
903
|
-
* This builder helper generates the wait queue and bindings needed for TTL-backoff retry.
|
|
904
|
-
* The generated infrastructure can be spread into a contract definition.
|
|
905
|
-
*
|
|
906
|
-
* TTL-backoff retry works by:
|
|
907
|
-
* 1. Failed messages are sent to the DLX with routing key `{queueName}-wait`
|
|
908
|
-
* 2. The wait queue receives these messages and holds them for a TTL period
|
|
909
|
-
* 3. After TTL expires, messages are dead-lettered back to the DLX with routing key `{queueName}`
|
|
910
|
-
* 4. The main queue receives the retried message via its binding to the DLX
|
|
911
|
-
*
|
|
912
|
-
* @param queue - The main queue definition (must have deadLetter configured)
|
|
913
|
-
* @param options - Optional configuration for the wait queue
|
|
914
|
-
* @param options.waitQueueDurable - Whether the wait queue should be durable (default: same as main queue)
|
|
915
|
-
* @returns TTL-backoff retry infrastructure containing wait queue and bindings
|
|
916
|
-
* @throws {Error} If the queue does not have a dead letter exchange configured
|
|
917
|
-
*
|
|
918
|
-
* @example
|
|
919
|
-
* ```typescript
|
|
920
|
-
* const dlx = defineExchange('orders-dlx', 'direct', { durable: true });
|
|
921
|
-
* const orderQueue = defineQueue('order-processing', {
|
|
922
|
-
* type: 'quorum',
|
|
923
|
-
* deadLetter: { exchange: dlx },
|
|
924
|
-
* retry: {
|
|
925
|
-
* mode: 'ttl-backoff',
|
|
926
|
-
* maxRetries: 5,
|
|
927
|
-
* initialDelayMs: 1000,
|
|
928
|
-
* },
|
|
929
|
-
* });
|
|
930
|
-
*
|
|
931
|
-
* // Infrastructure is auto-extracted when using defineContract:
|
|
932
|
-
* const contract = defineContract({
|
|
933
|
-
* publishers: { ... },
|
|
934
|
-
* consumers: { processOrder: defineEventConsumer(event, extractQueue(orderQueue)) },
|
|
935
|
-
* });
|
|
936
|
-
* // contract.queues includes the wait queue, contract.bindings includes retry bindings
|
|
937
|
-
*
|
|
938
|
-
* // Or generate manually for advanced use cases:
|
|
939
|
-
* const retryInfra = defineTtlBackoffRetryInfrastructure(orderQueue);
|
|
940
|
-
* ```
|
|
941
|
-
*/
|
|
942
|
-
function defineTtlBackoffRetryInfrastructure(queueEntry, options) {
|
|
943
|
-
const infra = createTtlBackoffInfrastructure(extractQueue(queueEntry));
|
|
944
|
-
if (options?.waitQueueDurable !== void 0) infra.waitQueue.durable = options.waitQueueDurable;
|
|
945
|
-
return infra;
|
|
946
|
-
}
|
|
822
|
+
export { defineCommandConsumer, defineCommandPublisher, defineConsumer, defineContract, defineEventConsumer, defineEventPublisher, defineExchange, defineExchangeBinding, defineMessage, definePublisher, defineQueue, defineQueueBinding, extractConsumer, extractQueue, isBridgedPublisherConfig, isCommandConsumerConfig, isEventConsumerResult, isEventPublisherConfig, isQueueWithTtlBackoffInfrastructure };
|
|
947
823
|
|
|
948
|
-
//#endregion
|
|
949
|
-
export { defineCommandConsumer, defineCommandPublisher, defineConsumer, defineContract, defineEventConsumer, defineEventPublisher, defineExchange, defineExchangeBinding, defineMessage, definePublisher, defineQueue, defineQueueBinding, defineQuorumQueue, defineTtlBackoffQueue, defineTtlBackoffRetryInfrastructure, extractConsumer, extractQueue, isBridgedPublisherConfig, isCommandConsumerConfig, isEventConsumerResult, isEventPublisherConfig, isQueueWithTtlBackoffInfrastructure };
|
|
950
824
|
//# sourceMappingURL=index.mjs.map
|