@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 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 exchangeErrors = (await Promise.allSettled(Object.values(contract.exchanges ?? {}).map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
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
- })))).filter((result) => result.status === "rejected");
217
- if (exchangeErrors.length > 0) throw new AggregateError(exchangeErrors.map(({ reason }) => reason), "Failed to setup exchanges");
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 queueErrors = (await Promise.allSettled(Object.values(contract.queues ?? {}).map((queueEntry) => {
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
- }))).filter((result) => result.status === "rejected");
245
- if (queueErrors.length > 0) throw new AggregateError(queueErrors.map(({ reason }) => reason), "Failed to setup queues");
246
- const bindingErrors = (await Promise.allSettled(Object.values(contract.bindings ?? {}).map((binding) => {
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
- }))).filter((result) => result.status === "rejected");
250
- if (bindingErrors.length > 0) throw new AggregateError(bindingErrors.map(({ reason }) => reason), "Failed to setup bindings");
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
- * @returns A Future that resolves when the channel is connected
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
- return _swan_io_boxed.Future.fromPromise(this.channelWrapper.waitForConnect()).mapError((error) => new TechnicalError("Failed to connect to AMQP broker", error));
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)).flatMapOk(() => _swan_io_boxed.Future.fromPromise(ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions)).mapError((error) => new TechnicalError("Failed to release connection", error))).mapOk(() => void 0);
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
- * @returns A Future that resolves when the channel is connected
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
@@ -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":"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"}
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":"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"}
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
- * @returns A Future that resolves when the channel is connected
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
@@ -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":"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"}
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 exchangeErrors = (await Promise.allSettled(Object.values(contract.exchanges ?? {}).map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
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
- })))).filter((result) => result.status === "rejected");
197
- if (exchangeErrors.length > 0) throw new AggregateError(exchangeErrors.map(({ reason }) => reason), "Failed to setup exchanges");
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 queueErrors = (await Promise.allSettled(Object.values(contract.queues ?? {}).map((queueEntry) => {
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
- }))).filter((result) => result.status === "rejected");
225
- if (queueErrors.length > 0) throw new AggregateError(queueErrors.map(({ reason }) => reason), "Failed to setup queues");
226
- const bindingErrors = (await Promise.allSettled(Object.values(contract.bindings ?? {}).map((binding) => {
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
- }))).filter((result) => result.status === "rejected");
230
- if (bindingErrors.length > 0) throw new AggregateError(bindingErrors.map(({ reason }) => reason), "Failed to setup bindings");
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
- * @returns A Future that resolves when the channel is connected
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
- return Future.fromPromise(this.channelWrapper.waitForConnect()).mapError((error) => new TechnicalError("Failed to connect to AMQP broker", error));
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)).flatMapOk(() => Future.fromPromise(ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions)).mapError((error) => new TechnicalError("Failed to release connection", error))).mapOk(() => void 0);
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
  /**