@amqp-contract/core 0.21.0 → 0.22.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 +77 -12
- package/dist/index.d.cts +23 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +23 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +78 -13
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +83 -70
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -139,6 +139,14 @@ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
|
|
|
139
139
|
return value;
|
|
140
140
|
}
|
|
141
141
|
/**
|
|
142
|
+
* Get the number of active pooled connections.
|
|
143
|
+
*
|
|
144
|
+
* @internal
|
|
145
|
+
*/
|
|
146
|
+
_getConnectionCountForTesting() {
|
|
147
|
+
return this.connections.size;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
142
150
|
* Reset all cached connections (for testing purposes)
|
|
143
151
|
* @internal
|
|
144
152
|
*/
|
|
@@ -208,13 +216,20 @@ var MessageValidationError = class extends Error {
|
|
|
208
216
|
* ```
|
|
209
217
|
*/
|
|
210
218
|
async function setupAmqpTopology(channel, contract) {
|
|
211
|
-
const
|
|
219
|
+
const exchanges = Object.values(contract.exchanges ?? {}).filter((e) => e.name !== "");
|
|
220
|
+
const exchangeErrors = (await Promise.allSettled(exchanges.map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
|
|
212
221
|
durable: exchange.durable,
|
|
213
222
|
autoDelete: exchange.autoDelete,
|
|
214
223
|
internal: exchange.internal,
|
|
215
224
|
arguments: exchange.arguments
|
|
216
|
-
})))).
|
|
217
|
-
|
|
225
|
+
})))).map((result, i) => ({
|
|
226
|
+
result,
|
|
227
|
+
name: exchanges[i].name
|
|
228
|
+
})).filter((entry) => entry.result.status === "rejected");
|
|
229
|
+
if (exchangeErrors.length > 0) {
|
|
230
|
+
const names = exchangeErrors.map((e) => e.name).join(", ");
|
|
231
|
+
throw new AggregateError(exchangeErrors.map(({ result }) => result.reason), `Failed to setup exchanges: ${names}`);
|
|
232
|
+
}
|
|
218
233
|
for (const queueEntry of Object.values(contract.queues ?? {})) {
|
|
219
234
|
const queue = (0, _amqp_contract_contract.extractQueue)(queueEntry);
|
|
220
235
|
if (queue.deadLetter) {
|
|
@@ -222,7 +237,8 @@ async function setupAmqpTopology(channel, contract) {
|
|
|
222
237
|
if (!Object.values(contract.exchanges ?? {}).some((exchange) => exchange.name === dlxName)) throw new TechnicalError(`Queue "${queue.name}" references dead letter exchange "${dlxName}" which is not declared in the contract. Add the exchange to contract.exchanges to ensure it is created before the queue.`);
|
|
223
238
|
}
|
|
224
239
|
}
|
|
225
|
-
const
|
|
240
|
+
const queueEntries = Object.values(contract.queues ?? {});
|
|
241
|
+
const queueErrors = (await Promise.allSettled(queueEntries.map((queueEntry) => {
|
|
226
242
|
const queue = (0, _amqp_contract_contract.extractQueue)(queueEntry);
|
|
227
243
|
const queueArguments = { ...queue.arguments };
|
|
228
244
|
queueArguments["x-queue-type"] = queue.type;
|
|
@@ -241,13 +257,29 @@ async function setupAmqpTopology(channel, contract) {
|
|
|
241
257
|
autoDelete: queue.autoDelete,
|
|
242
258
|
arguments: queueArguments
|
|
243
259
|
});
|
|
244
|
-
}))).
|
|
245
|
-
|
|
246
|
-
|
|
260
|
+
}))).map((result, i) => ({
|
|
261
|
+
result,
|
|
262
|
+
name: (0, _amqp_contract_contract.extractQueue)(queueEntries[i]).name
|
|
263
|
+
})).filter((entry) => entry.result.status === "rejected");
|
|
264
|
+
if (queueErrors.length > 0) {
|
|
265
|
+
const names = queueErrors.map((e) => e.name).join(", ");
|
|
266
|
+
throw new AggregateError(queueErrors.map(({ result }) => result.reason), `Failed to setup queues: ${names}`);
|
|
267
|
+
}
|
|
268
|
+
const bindings = Object.values(contract.bindings ?? {});
|
|
269
|
+
const bindingErrors = (await Promise.allSettled(bindings.map((binding) => {
|
|
247
270
|
if (binding.type === "queue") return channel.bindQueue(binding.queue.name, binding.exchange.name, binding.routingKey ?? "", binding.arguments);
|
|
248
271
|
return channel.bindExchange(binding.destination.name, binding.source.name, binding.routingKey ?? "", binding.arguments);
|
|
249
|
-
}))).
|
|
250
|
-
|
|
272
|
+
}))).map((result, i) => {
|
|
273
|
+
const binding = bindings[i];
|
|
274
|
+
return {
|
|
275
|
+
result,
|
|
276
|
+
name: binding.type === "queue" ? `${binding.exchange.name} -> ${binding.queue.name}` : `${binding.source.name} -> ${binding.destination.name}`
|
|
277
|
+
};
|
|
278
|
+
}).filter((entry) => entry.result.status === "rejected");
|
|
279
|
+
if (bindingErrors.length > 0) {
|
|
280
|
+
const names = bindingErrors.map((e) => e.name).join(", ");
|
|
281
|
+
throw new AggregateError(bindingErrors.map(({ result }) => result.reason), `Failed to setup bindings: ${names}`);
|
|
282
|
+
}
|
|
251
283
|
}
|
|
252
284
|
//#endregion
|
|
253
285
|
//#region src/amqp-client.ts
|
|
@@ -298,6 +330,7 @@ var AmqpClient = class {
|
|
|
298
330
|
channelWrapper;
|
|
299
331
|
urls;
|
|
300
332
|
connectionOptions;
|
|
333
|
+
connectTimeoutMs;
|
|
301
334
|
/**
|
|
302
335
|
* Create a new AMQP client instance.
|
|
303
336
|
*
|
|
@@ -313,6 +346,7 @@ var AmqpClient = class {
|
|
|
313
346
|
this.contract = contract;
|
|
314
347
|
this.urls = options.urls;
|
|
315
348
|
if (options.connectionOptions !== void 0) this.connectionOptions = options.connectionOptions;
|
|
349
|
+
if (options.connectTimeoutMs !== void 0) this.connectTimeoutMs = options.connectTimeoutMs;
|
|
316
350
|
const singleton = ConnectionManagerSingleton.getInstance();
|
|
317
351
|
this.connection = singleton.getConnection(options.urls, options.connectionOptions);
|
|
318
352
|
const defaultSetup = (channel) => setupAmqpTopology(channel, this.contract);
|
|
@@ -344,10 +378,36 @@ var AmqpClient = class {
|
|
|
344
378
|
/**
|
|
345
379
|
* Wait for the channel to be connected and ready.
|
|
346
380
|
*
|
|
347
|
-
*
|
|
381
|
+
* If `connectTimeoutMs` was provided in the constructor options, the returned
|
|
382
|
+
* Future resolves to `Result.Error<TechnicalError>` once the timeout elapses.
|
|
383
|
+
* Without a timeout, this waits forever — amqp-connection-manager retries
|
|
384
|
+
* connections indefinitely and never errors on its own.
|
|
385
|
+
*
|
|
386
|
+
* NOTE: When using `AmqpClient` directly (not via `TypedAmqpClient` /
|
|
387
|
+
* `TypedAmqpWorker`), the constructor has already incremented the pooled
|
|
388
|
+
* connection's reference count. Callers must invoke `close()` on the error
|
|
389
|
+
* path to release the connection — `waitForConnect` does not do this
|
|
390
|
+
* automatically. The typed factories handle this cleanup for you.
|
|
391
|
+
*
|
|
392
|
+
* @returns A Future resolving to `Result.Ok(void)` on connect, or
|
|
393
|
+
* `Result.Error(TechnicalError)` on timeout / connection failure.
|
|
348
394
|
*/
|
|
349
395
|
waitForConnect() {
|
|
350
|
-
|
|
396
|
+
const connectPromise = this.channelWrapper.waitForConnect();
|
|
397
|
+
const racedPromise = this.connectTimeoutMs === void 0 ? connectPromise : new Promise((resolve, reject) => {
|
|
398
|
+
const timeoutMs = this.connectTimeoutMs;
|
|
399
|
+
const handle = setTimeout(() => {
|
|
400
|
+
reject(/* @__PURE__ */ new Error(`Timed out waiting for AMQP connection after ${timeoutMs}ms`));
|
|
401
|
+
}, timeoutMs);
|
|
402
|
+
connectPromise.then(() => {
|
|
403
|
+
clearTimeout(handle);
|
|
404
|
+
resolve();
|
|
405
|
+
}, (error) => {
|
|
406
|
+
clearTimeout(handle);
|
|
407
|
+
reject(error);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
return _swan_io_boxed.Future.fromPromise(racedPromise).mapError((error) => new TechnicalError("Failed to connect to AMQP broker", error));
|
|
351
411
|
}
|
|
352
412
|
/**
|
|
353
413
|
* Publish a message to an exchange.
|
|
@@ -446,7 +506,10 @@ var AmqpClient = class {
|
|
|
446
506
|
* @returns A Future that resolves when the channel and connection are closed
|
|
447
507
|
*/
|
|
448
508
|
close() {
|
|
449
|
-
return _swan_io_boxed.Future.fromPromise(this.channelWrapper.close()).mapError((error) => new TechnicalError("Failed to close channel", error)).
|
|
509
|
+
return _swan_io_boxed.Future.fromPromise(this.channelWrapper.close()).mapError((error) => new TechnicalError("Failed to close channel", error)).flatMap((channelResult) => _swan_io_boxed.Future.fromPromise(ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions)).mapError((error) => new TechnicalError("Failed to release connection", error)).map((releaseResult) => {
|
|
510
|
+
if (channelResult.isError() && releaseResult.isError()) return _swan_io_boxed.Result.Error(new TechnicalError("Failed to close channel and release connection", new AggregateError([channelResult.error, releaseResult.error], "Failed to close channel and release connection")));
|
|
511
|
+
return channelResult.isError() ? channelResult : releaseResult;
|
|
512
|
+
}));
|
|
450
513
|
}
|
|
451
514
|
/**
|
|
452
515
|
* Reset connection singleton cache (for testing only)
|
|
@@ -464,7 +527,9 @@ var AmqpClient = class {
|
|
|
464
527
|
* @see https://opentelemetry.io/docs/specs/otel/trace/api/#spankind
|
|
465
528
|
*/
|
|
466
529
|
const SpanKind = {
|
|
530
|
+
/** Producer span represents a message producer */
|
|
467
531
|
PRODUCER: 3,
|
|
532
|
+
/** Consumer span represents a message consumer */
|
|
468
533
|
CONSUMER: 4
|
|
469
534
|
};
|
|
470
535
|
/**
|
package/dist/index.d.cts
CHANGED
|
@@ -37,11 +37,14 @@ declare class MessageValidationError extends Error {
|
|
|
37
37
|
* @property urls - AMQP broker URL(s). Multiple URLs provide failover support.
|
|
38
38
|
* @property connectionOptions - Optional connection configuration (heartbeat, reconnect settings, etc.).
|
|
39
39
|
* @property channelOptions - Optional channel configuration options.
|
|
40
|
+
* @property connectTimeoutMs - Maximum time in ms to wait for the channel to become ready
|
|
41
|
+
* in `waitForConnect`. If unset, waits forever (amqp-connection-manager retries indefinitely).
|
|
40
42
|
*/
|
|
41
43
|
type AmqpClientOptions = {
|
|
42
44
|
urls: ConnectionUrl[];
|
|
43
45
|
connectionOptions?: AmqpConnectionManagerOptions | undefined;
|
|
44
46
|
channelOptions?: Partial<CreateChannelOpts> | undefined;
|
|
47
|
+
connectTimeoutMs?: number | undefined;
|
|
45
48
|
};
|
|
46
49
|
/**
|
|
47
50
|
* Callback type for consuming messages.
|
|
@@ -93,6 +96,7 @@ declare class AmqpClient {
|
|
|
93
96
|
private readonly channelWrapper;
|
|
94
97
|
private readonly urls;
|
|
95
98
|
private readonly connectionOptions?;
|
|
99
|
+
private readonly connectTimeoutMs?;
|
|
96
100
|
/**
|
|
97
101
|
* Create a new AMQP client instance.
|
|
98
102
|
*
|
|
@@ -118,7 +122,19 @@ declare class AmqpClient {
|
|
|
118
122
|
/**
|
|
119
123
|
* Wait for the channel to be connected and ready.
|
|
120
124
|
*
|
|
121
|
-
*
|
|
125
|
+
* If `connectTimeoutMs` was provided in the constructor options, the returned
|
|
126
|
+
* Future resolves to `Result.Error<TechnicalError>` once the timeout elapses.
|
|
127
|
+
* Without a timeout, this waits forever — amqp-connection-manager retries
|
|
128
|
+
* connections indefinitely and never errors on its own.
|
|
129
|
+
*
|
|
130
|
+
* NOTE: When using `AmqpClient` directly (not via `TypedAmqpClient` /
|
|
131
|
+
* `TypedAmqpWorker`), the constructor has already incremented the pooled
|
|
132
|
+
* connection's reference count. Callers must invoke `close()` on the error
|
|
133
|
+
* path to release the connection — `waitForConnect` does not do this
|
|
134
|
+
* automatically. The typed factories handle this cleanup for you.
|
|
135
|
+
*
|
|
136
|
+
* @returns A Future resolving to `Result.Ok(void)` on connect, or
|
|
137
|
+
* `Result.Error(TechnicalError)` on timeout / connection failure.
|
|
122
138
|
*/
|
|
123
139
|
waitForConnect(): Future<Result<void, TechnicalError>>;
|
|
124
140
|
/**
|
|
@@ -284,6 +300,12 @@ declare class ConnectionManagerSingleton {
|
|
|
284
300
|
* @returns The value with all object keys sorted alphabetically
|
|
285
301
|
*/
|
|
286
302
|
private deepSort;
|
|
303
|
+
/**
|
|
304
|
+
* Get the number of active pooled connections.
|
|
305
|
+
*
|
|
306
|
+
* @internal
|
|
307
|
+
*/
|
|
308
|
+
_getConnectionCountForTesting(): number;
|
|
287
309
|
/**
|
|
288
310
|
* Reset all cached connections (for testing purposes)
|
|
289
311
|
* @internal
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/errors.ts","../src/amqp-client.ts","../src/connection-manager.ts","../src/logger.ts","../src/setup.ts","../src/telemetry.ts"],"mappings":";;;;;;;;;;;;;cAMa,cAAA,SAAuB,KAAA;EAAA,SAGP,KAAA;cADzB,OAAA,UACyB,KAAA;AAAA;;;;;;;;;AAuB7B;cAAa,sBAAA,SAA+B,KAAA;EAAA,SAExB,MAAA;EAAA,SACA,MAAA;cADA,MAAA,UACA,MAAA;AAAA;;;;;AA7BpB
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/errors.ts","../src/amqp-client.ts","../src/connection-manager.ts","../src/logger.ts","../src/setup.ts","../src/telemetry.ts"],"mappings":";;;;;;;;;;;;;cAMa,cAAA,SAAuB,KAAA;EAAA,SAGP,KAAA;cADzB,OAAA,UACyB,KAAA;AAAA;;;;;;;;;AAuB7B;cAAa,sBAAA,SAA+B,KAAA;EAAA,SAExB,MAAA;EAAA,SACA,MAAA;cADA,MAAA,UACA,MAAA;AAAA;;;;;AA7BpB;;;;;;;KCwCY,iBAAA;EACV,IAAA,EAAM,aAAA;EACN,iBAAA,GAAoB,4BAAA;EACpB,cAAA,GAAiB,OAAA,CAAQ,iBAAA;EACzB,gBAAA;AAAA;;;;KAMU,eAAA,IAAmB,GAAA,EAAK,cAAA,mBAAiC,OAAA;;;;KAKzD,cAAA,GAAiB,OAAA,CAAQ,OAAA;ED1BF,gDC4BjC,OAAA;AAAA;;;AAjBF;KAuBY,eAAA,GAAkB,OAAA,CAAQ,OAAA;uCAEpC,QAAA;AAAA;;;;;;;;;;;;;;;AAfF;;;;;;;;;AAKA;;;;;cAyCa,UAAA;EAAA,iBAmBQ,QAAA;EAAA,iBAlBF,UAAA;EAAA,iBACA,cAAA;EAAA,iBACA,IAAA;EAAA,iBACA,iBAAA;EAAA,iBACA,gBAAA;EAtC0B;;;;;;AAiC7C;;;;;cAmBqB,QAAA,EAAU,kBAAA,EAC3B,OAAA,EAAS,iBAAA;EAsE2B;;;;;;;;;EArBtC,aAAA,CAAA,GAAiB,qBAAA;EAiFS;;;;;;;;;;;;;;;;;EA5D1B,cAAA,CAAA,GAAkB,MAAA,CAAO,MAAA,OAAa,cAAA;EA0LU;;;;;;;;;EArJhD,OAAA,CACE,QAAA,UACA,UAAA,UACA,OAAA,EAAS,MAAA,YACT,OAAA,GAAU,cAAA,GACT,MAAA,CAAO,MAAA,UAAgB,cAAA;EAjHG;;;;;;;;EA+H7B,WAAA,CACE,KAAA,UACA,OAAA,EAAS,MAAA,YACT,OAAA,GAAU,cAAA,GACT,MAAA,CAAO,MAAA,UAAgB,cAAA;EA5DY;;;;;;;;EA0EtC,OAAA,CACE,KAAA,UACA,QAAA,EAAU,eAAA,EACV,OAAA,GAAU,eAAA,GACT,MAAA,CAAO,MAAA,SAAe,cAAA;EApCf;;;;;;EAgDV,MAAA,CAAO,WAAA,WAAsB,MAAA,CAAO,MAAA,OAAa,cAAA;EA/B/C;;;;;;EA2CF,GAAA,CAAI,GAAA,EAAK,cAAA,EAAgB,OAAA;EA1BvB;;;;;;;EAqCF,IAAA,CAAK,GAAA,EAAK,cAAA,EAAgB,OAAA,YAAiB,OAAA;EAvBd;;;;;;;EAkC7B,QAAA,CAAS,KAAA,GAAQ,OAAA,EAAS,OAAA,YAAmB,OAAA;EAXnC;;;;;;;;;;;EA0BV,EAAA,CAAG,KAAA,UAAe,QAAA,MAAc,IAAA;EAAd;;;;;;;;;;EAclB,KAAA,CAAA,GAAS,MAAA,CAAO,MAAA,OAAa,cAAA;;ACnU/B;;;SDoWe,+BAAA,CAAA,GAAmC,OAAA;AAAA;;;;;;;;;ADpXlD;;;;;;;;;;cEgBa,0BAAA;EAAA,eACI,QAAA;EAAA,QACP,WAAA;EAAA,QACA,SAAA;EAAA,QAED,WAAA,CAAA;EFKmC;;;;;EAAA,OEEnC,WAAA,CAAA,GAAe,0BAAA;EFCW;;;;;ACWnC;;;;;ECKE,aAAA,CACE,IAAA,EAAM,aAAA,IACN,iBAAA,GAAoB,4BAAA,GACnB,qBAAA;EDLc;;;;;;;;;;EC+BX,iBAAA,CACJ,IAAA,EAAM,aAAA,IACN,iBAAA,GAAoB,4BAAA,GACnB,OAAA;EDjCa;;AAMlB;;;;;;;;EANkB,QC6DR,mBAAA;EDlDE;;;;;;EAAA,QCoEF,gBAAA;EDlED;;AAMT;;;;EANS,QC8EC,QAAA;EDxE4B;;;;AAiCtC;ECgEE,6BAAA,CAAA;;;;;EAQM,gBAAA,CAAA,GAAoB,OAAA;AAAA;;;;;;;;;;AFxK5B;KGEY,aAAA,GAAgB,MAAA;EAC1B,KAAA;AAAA;;;;;;;;AHuBF;;;;;;;;;;KGHY,MAAA;EHMuB;;;;ACWnC;EEXE,KAAA,CAAM,OAAA,UAAiB,OAAA,GAAU,aAAA;;;;;;EAOjC,IAAA,CAAK,OAAA,UAAiB,OAAA,GAAU,aAAA;EFOR;;;;;EEAxB,IAAA,CAAK,OAAA,UAAiB,OAAA,GAAU,aAAA;EFAf;;;;;EEOjB,KAAA,CAAM,OAAA,UAAiB,OAAA,GAAU,aAAA;AAAA;;;;;;;;AHlDnC;;;;;;;;;;;AA0BA;;;;iBIPsB,iBAAA,CACpB,OAAA,EAAS,OAAA,EACT,QAAA,EAAU,kBAAA,GACT,OAAA;;;;;;;cCJU,4BAAA;EAAA;;;;;;;;;;;;;;;;;;;KA4BD,iBAAA;ELlBQ;;;;EKuBlB,SAAA,QAAiB,MAAA;;;AJXnB;;EIiBE,iBAAA,QAAyB,OAAA;EJhBnB;;;;EIsBN,iBAAA,QAAyB,OAAA;EJpBD;;;;EI0BxB,0BAAA,QAAkC,SAAA;EJ1BlC;;;;EIgCA,0BAAA,QAAkC,SAAA;AAAA;AJzBpC;;;AAAA,cIyIa,wBAAA,EAA0B,iBAAA;;;;;iBAYvB,gBAAA,CACd,QAAA,EAAU,iBAAA,EACV,YAAA,UACA,UAAA,sBACA,UAAA,GAAa,UAAA,GACZ,IAAA;AJrJH;;;;AAAA,iBImLgB,gBAAA,CACd,QAAA,EAAU,iBAAA,EACV,SAAA,UACA,YAAA,UACA,UAAA,GAAa,UAAA,GACZ,IAAA;;;;iBA2Ba,cAAA,CAAe,IAAA,EAAM,IAAA;AJ3MrC;;;AAAA,iBI0NgB,YAAA,CAAa,IAAA,EAAM,IAAA,cAAkB,KAAA,EAAO,KAAA;;;;iBAiB5C,mBAAA,CACd,QAAA,EAAU,iBAAA,EACV,YAAA,UACA,UAAA,sBACA,OAAA,WACA,UAAA;;AJ/MF;;iBIqOgB,mBAAA,CACd,QAAA,EAAU,iBAAA,EACV,SAAA,UACA,YAAA,UACA,OAAA,WACA,UAAA;;;;;;iBAsBc,8BAAA,CAAA"}
|
package/dist/index.d.mts
CHANGED
|
@@ -37,11 +37,14 @@ declare class MessageValidationError extends Error {
|
|
|
37
37
|
* @property urls - AMQP broker URL(s). Multiple URLs provide failover support.
|
|
38
38
|
* @property connectionOptions - Optional connection configuration (heartbeat, reconnect settings, etc.).
|
|
39
39
|
* @property channelOptions - Optional channel configuration options.
|
|
40
|
+
* @property connectTimeoutMs - Maximum time in ms to wait for the channel to become ready
|
|
41
|
+
* in `waitForConnect`. If unset, waits forever (amqp-connection-manager retries indefinitely).
|
|
40
42
|
*/
|
|
41
43
|
type AmqpClientOptions = {
|
|
42
44
|
urls: ConnectionUrl[];
|
|
43
45
|
connectionOptions?: AmqpConnectionManagerOptions | undefined;
|
|
44
46
|
channelOptions?: Partial<CreateChannelOpts> | undefined;
|
|
47
|
+
connectTimeoutMs?: number | undefined;
|
|
45
48
|
};
|
|
46
49
|
/**
|
|
47
50
|
* Callback type for consuming messages.
|
|
@@ -93,6 +96,7 @@ declare class AmqpClient {
|
|
|
93
96
|
private readonly channelWrapper;
|
|
94
97
|
private readonly urls;
|
|
95
98
|
private readonly connectionOptions?;
|
|
99
|
+
private readonly connectTimeoutMs?;
|
|
96
100
|
/**
|
|
97
101
|
* Create a new AMQP client instance.
|
|
98
102
|
*
|
|
@@ -118,7 +122,19 @@ declare class AmqpClient {
|
|
|
118
122
|
/**
|
|
119
123
|
* Wait for the channel to be connected and ready.
|
|
120
124
|
*
|
|
121
|
-
*
|
|
125
|
+
* If `connectTimeoutMs` was provided in the constructor options, the returned
|
|
126
|
+
* Future resolves to `Result.Error<TechnicalError>` once the timeout elapses.
|
|
127
|
+
* Without a timeout, this waits forever — amqp-connection-manager retries
|
|
128
|
+
* connections indefinitely and never errors on its own.
|
|
129
|
+
*
|
|
130
|
+
* NOTE: When using `AmqpClient` directly (not via `TypedAmqpClient` /
|
|
131
|
+
* `TypedAmqpWorker`), the constructor has already incremented the pooled
|
|
132
|
+
* connection's reference count. Callers must invoke `close()` on the error
|
|
133
|
+
* path to release the connection — `waitForConnect` does not do this
|
|
134
|
+
* automatically. The typed factories handle this cleanup for you.
|
|
135
|
+
*
|
|
136
|
+
* @returns A Future resolving to `Result.Ok(void)` on connect, or
|
|
137
|
+
* `Result.Error(TechnicalError)` on timeout / connection failure.
|
|
122
138
|
*/
|
|
123
139
|
waitForConnect(): Future<Result<void, TechnicalError>>;
|
|
124
140
|
/**
|
|
@@ -284,6 +300,12 @@ declare class ConnectionManagerSingleton {
|
|
|
284
300
|
* @returns The value with all object keys sorted alphabetically
|
|
285
301
|
*/
|
|
286
302
|
private deepSort;
|
|
303
|
+
/**
|
|
304
|
+
* Get the number of active pooled connections.
|
|
305
|
+
*
|
|
306
|
+
* @internal
|
|
307
|
+
*/
|
|
308
|
+
_getConnectionCountForTesting(): number;
|
|
287
309
|
/**
|
|
288
310
|
* Reset all cached connections (for testing purposes)
|
|
289
311
|
* @internal
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/errors.ts","../src/amqp-client.ts","../src/connection-manager.ts","../src/logger.ts","../src/setup.ts","../src/telemetry.ts"],"mappings":";;;;;;;;;;;;;cAMa,cAAA,SAAuB,KAAA;EAAA,SAGP,KAAA;cADzB,OAAA,UACyB,KAAA;AAAA;;;;;;;;;AAuB7B;cAAa,sBAAA,SAA+B,KAAA;EAAA,SAExB,MAAA;EAAA,SACA,MAAA;cADA,MAAA,UACA,MAAA;AAAA;;;;;AA7BpB
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/errors.ts","../src/amqp-client.ts","../src/connection-manager.ts","../src/logger.ts","../src/setup.ts","../src/telemetry.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { Future } from "@swan-io/boxed";
|
|
2
|
+
import { Future, Result } from "@swan-io/boxed";
|
|
3
3
|
import amqp from "amqp-connection-manager";
|
|
4
4
|
import { extractQueue } from "@amqp-contract/contract";
|
|
5
5
|
//#region \0rolldown/runtime.js
|
|
@@ -119,6 +119,14 @@ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
|
|
|
119
119
|
return value;
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
|
+
* Get the number of active pooled connections.
|
|
123
|
+
*
|
|
124
|
+
* @internal
|
|
125
|
+
*/
|
|
126
|
+
_getConnectionCountForTesting() {
|
|
127
|
+
return this.connections.size;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
122
130
|
* Reset all cached connections (for testing purposes)
|
|
123
131
|
* @internal
|
|
124
132
|
*/
|
|
@@ -188,13 +196,20 @@ var MessageValidationError = class extends Error {
|
|
|
188
196
|
* ```
|
|
189
197
|
*/
|
|
190
198
|
async function setupAmqpTopology(channel, contract) {
|
|
191
|
-
const
|
|
199
|
+
const exchanges = Object.values(contract.exchanges ?? {}).filter((e) => e.name !== "");
|
|
200
|
+
const exchangeErrors = (await Promise.allSettled(exchanges.map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
|
|
192
201
|
durable: exchange.durable,
|
|
193
202
|
autoDelete: exchange.autoDelete,
|
|
194
203
|
internal: exchange.internal,
|
|
195
204
|
arguments: exchange.arguments
|
|
196
|
-
})))).
|
|
197
|
-
|
|
205
|
+
})))).map((result, i) => ({
|
|
206
|
+
result,
|
|
207
|
+
name: exchanges[i].name
|
|
208
|
+
})).filter((entry) => entry.result.status === "rejected");
|
|
209
|
+
if (exchangeErrors.length > 0) {
|
|
210
|
+
const names = exchangeErrors.map((e) => e.name).join(", ");
|
|
211
|
+
throw new AggregateError(exchangeErrors.map(({ result }) => result.reason), `Failed to setup exchanges: ${names}`);
|
|
212
|
+
}
|
|
198
213
|
for (const queueEntry of Object.values(contract.queues ?? {})) {
|
|
199
214
|
const queue = extractQueue(queueEntry);
|
|
200
215
|
if (queue.deadLetter) {
|
|
@@ -202,7 +217,8 @@ async function setupAmqpTopology(channel, contract) {
|
|
|
202
217
|
if (!Object.values(contract.exchanges ?? {}).some((exchange) => exchange.name === dlxName)) throw new TechnicalError(`Queue "${queue.name}" references dead letter exchange "${dlxName}" which is not declared in the contract. Add the exchange to contract.exchanges to ensure it is created before the queue.`);
|
|
203
218
|
}
|
|
204
219
|
}
|
|
205
|
-
const
|
|
220
|
+
const queueEntries = Object.values(contract.queues ?? {});
|
|
221
|
+
const queueErrors = (await Promise.allSettled(queueEntries.map((queueEntry) => {
|
|
206
222
|
const queue = extractQueue(queueEntry);
|
|
207
223
|
const queueArguments = { ...queue.arguments };
|
|
208
224
|
queueArguments["x-queue-type"] = queue.type;
|
|
@@ -221,13 +237,29 @@ async function setupAmqpTopology(channel, contract) {
|
|
|
221
237
|
autoDelete: queue.autoDelete,
|
|
222
238
|
arguments: queueArguments
|
|
223
239
|
});
|
|
224
|
-
}))).
|
|
225
|
-
|
|
226
|
-
|
|
240
|
+
}))).map((result, i) => ({
|
|
241
|
+
result,
|
|
242
|
+
name: extractQueue(queueEntries[i]).name
|
|
243
|
+
})).filter((entry) => entry.result.status === "rejected");
|
|
244
|
+
if (queueErrors.length > 0) {
|
|
245
|
+
const names = queueErrors.map((e) => e.name).join(", ");
|
|
246
|
+
throw new AggregateError(queueErrors.map(({ result }) => result.reason), `Failed to setup queues: ${names}`);
|
|
247
|
+
}
|
|
248
|
+
const bindings = Object.values(contract.bindings ?? {});
|
|
249
|
+
const bindingErrors = (await Promise.allSettled(bindings.map((binding) => {
|
|
227
250
|
if (binding.type === "queue") return channel.bindQueue(binding.queue.name, binding.exchange.name, binding.routingKey ?? "", binding.arguments);
|
|
228
251
|
return channel.bindExchange(binding.destination.name, binding.source.name, binding.routingKey ?? "", binding.arguments);
|
|
229
|
-
}))).
|
|
230
|
-
|
|
252
|
+
}))).map((result, i) => {
|
|
253
|
+
const binding = bindings[i];
|
|
254
|
+
return {
|
|
255
|
+
result,
|
|
256
|
+
name: binding.type === "queue" ? `${binding.exchange.name} -> ${binding.queue.name}` : `${binding.source.name} -> ${binding.destination.name}`
|
|
257
|
+
};
|
|
258
|
+
}).filter((entry) => entry.result.status === "rejected");
|
|
259
|
+
if (bindingErrors.length > 0) {
|
|
260
|
+
const names = bindingErrors.map((e) => e.name).join(", ");
|
|
261
|
+
throw new AggregateError(bindingErrors.map(({ result }) => result.reason), `Failed to setup bindings: ${names}`);
|
|
262
|
+
}
|
|
231
263
|
}
|
|
232
264
|
//#endregion
|
|
233
265
|
//#region src/amqp-client.ts
|
|
@@ -278,6 +310,7 @@ var AmqpClient = class {
|
|
|
278
310
|
channelWrapper;
|
|
279
311
|
urls;
|
|
280
312
|
connectionOptions;
|
|
313
|
+
connectTimeoutMs;
|
|
281
314
|
/**
|
|
282
315
|
* Create a new AMQP client instance.
|
|
283
316
|
*
|
|
@@ -293,6 +326,7 @@ var AmqpClient = class {
|
|
|
293
326
|
this.contract = contract;
|
|
294
327
|
this.urls = options.urls;
|
|
295
328
|
if (options.connectionOptions !== void 0) this.connectionOptions = options.connectionOptions;
|
|
329
|
+
if (options.connectTimeoutMs !== void 0) this.connectTimeoutMs = options.connectTimeoutMs;
|
|
296
330
|
const singleton = ConnectionManagerSingleton.getInstance();
|
|
297
331
|
this.connection = singleton.getConnection(options.urls, options.connectionOptions);
|
|
298
332
|
const defaultSetup = (channel) => setupAmqpTopology(channel, this.contract);
|
|
@@ -324,10 +358,36 @@ var AmqpClient = class {
|
|
|
324
358
|
/**
|
|
325
359
|
* Wait for the channel to be connected and ready.
|
|
326
360
|
*
|
|
327
|
-
*
|
|
361
|
+
* If `connectTimeoutMs` was provided in the constructor options, the returned
|
|
362
|
+
* Future resolves to `Result.Error<TechnicalError>` once the timeout elapses.
|
|
363
|
+
* Without a timeout, this waits forever — amqp-connection-manager retries
|
|
364
|
+
* connections indefinitely and never errors on its own.
|
|
365
|
+
*
|
|
366
|
+
* NOTE: When using `AmqpClient` directly (not via `TypedAmqpClient` /
|
|
367
|
+
* `TypedAmqpWorker`), the constructor has already incremented the pooled
|
|
368
|
+
* connection's reference count. Callers must invoke `close()` on the error
|
|
369
|
+
* path to release the connection — `waitForConnect` does not do this
|
|
370
|
+
* automatically. The typed factories handle this cleanup for you.
|
|
371
|
+
*
|
|
372
|
+
* @returns A Future resolving to `Result.Ok(void)` on connect, or
|
|
373
|
+
* `Result.Error(TechnicalError)` on timeout / connection failure.
|
|
328
374
|
*/
|
|
329
375
|
waitForConnect() {
|
|
330
|
-
|
|
376
|
+
const connectPromise = this.channelWrapper.waitForConnect();
|
|
377
|
+
const racedPromise = this.connectTimeoutMs === void 0 ? connectPromise : new Promise((resolve, reject) => {
|
|
378
|
+
const timeoutMs = this.connectTimeoutMs;
|
|
379
|
+
const handle = setTimeout(() => {
|
|
380
|
+
reject(/* @__PURE__ */ new Error(`Timed out waiting for AMQP connection after ${timeoutMs}ms`));
|
|
381
|
+
}, timeoutMs);
|
|
382
|
+
connectPromise.then(() => {
|
|
383
|
+
clearTimeout(handle);
|
|
384
|
+
resolve();
|
|
385
|
+
}, (error) => {
|
|
386
|
+
clearTimeout(handle);
|
|
387
|
+
reject(error);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
return Future.fromPromise(racedPromise).mapError((error) => new TechnicalError("Failed to connect to AMQP broker", error));
|
|
331
391
|
}
|
|
332
392
|
/**
|
|
333
393
|
* Publish a message to an exchange.
|
|
@@ -426,7 +486,10 @@ var AmqpClient = class {
|
|
|
426
486
|
* @returns A Future that resolves when the channel and connection are closed
|
|
427
487
|
*/
|
|
428
488
|
close() {
|
|
429
|
-
return Future.fromPromise(this.channelWrapper.close()).mapError((error) => new TechnicalError("Failed to close channel", error)).
|
|
489
|
+
return Future.fromPromise(this.channelWrapper.close()).mapError((error) => new TechnicalError("Failed to close channel", error)).flatMap((channelResult) => Future.fromPromise(ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions)).mapError((error) => new TechnicalError("Failed to release connection", error)).map((releaseResult) => {
|
|
490
|
+
if (channelResult.isError() && releaseResult.isError()) return Result.Error(new TechnicalError("Failed to close channel and release connection", new AggregateError([channelResult.error, releaseResult.error], "Failed to close channel and release connection")));
|
|
491
|
+
return channelResult.isError() ? channelResult : releaseResult;
|
|
492
|
+
}));
|
|
430
493
|
}
|
|
431
494
|
/**
|
|
432
495
|
* Reset connection singleton cache (for testing only)
|
|
@@ -444,7 +507,9 @@ var AmqpClient = class {
|
|
|
444
507
|
* @see https://opentelemetry.io/docs/specs/otel/trace/api/#spankind
|
|
445
508
|
*/
|
|
446
509
|
const SpanKind = {
|
|
510
|
+
/** Producer span represents a message producer */
|
|
447
511
|
PRODUCER: 3,
|
|
512
|
+
/** Consumer span represents a message consumer */
|
|
448
513
|
CONSUMER: 4
|
|
449
514
|
};
|
|
450
515
|
/**
|