@amqp-contract/core 0.23.1 → 0.25.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.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { Future, Result } from "@swan-io/boxed";
1
+ import { Result, ResultAsync } from "neverthrow";
2
2
  import { AmqpConnectionManager, AmqpConnectionManagerOptions, ConnectionUrl, CreateChannelOpts } from "amqp-connection-manager";
3
3
  import { ContractDefinition } from "@amqp-contract/contract";
4
4
  import { Attributes, Counter, Histogram, Span, Tracer } from "@opentelemetry/api";
@@ -35,7 +35,7 @@ declare class MessageValidationError extends Error {
35
35
  * Default time `waitForConnect` will wait for the broker before erroring out.
36
36
  * Defaulting to a finite value (rather than waiting forever) means a fail-fast
37
37
  * developer experience: a misconfigured URL, a down broker, or wrong
38
- * credentials surface as a Result.Error within 30 seconds. Pass `null`
38
+ * credentials surface as an `err` within 30 seconds. Pass `null`
39
39
  * explicitly to disable the timeout — `Infinity` and other non-finite values
40
40
  * are also coerced to "no timeout" because Node's `setTimeout` clamps large
41
41
  * delays to ~24.8 days and silently fires near-immediately on `Infinity`.
@@ -63,16 +63,29 @@ type AmqpClientOptions = {
63
63
  */
64
64
  type ConsumeCallback = (msg: ConsumeMessage | null) => void | Promise<void>;
65
65
  /**
66
- * Publish options that extend amqplib's Options.Publish with optional timeout support.
66
+ * Publish options for `AmqpClient.publish` / `AmqpClient.sendToQueue`.
67
+ *
68
+ * Currently a re-export of amqplib's `Options.Publish`. A previous version of
69
+ * this type also exposed a `timeout` field, but that field never had a
70
+ * meaningful AMQP-level effect in this codebase and has been removed to avoid
71
+ * suggesting behaviour we do not provide. (`amqp-connection-manager`'s own
72
+ * `publishTimeout` channel option is unrelated and is configured at channel
73
+ * creation, not per-publish.)
67
74
  */
68
- type PublishOptions = Options.Publish & {
69
- /** Message will be rejected after timeout ms */timeout?: number;
70
- };
75
+ type PublishOptions = Options.Publish;
71
76
  /**
72
- * Consume options that extend amqplib's Options.Consume with optional prefetch support.
77
+ * Consume options that extend amqplib's `Options.Consume` with an optional
78
+ * per-consumer prefetch count.
79
+ *
80
+ * `prefetch` is intercepted by {@link AmqpClient.consume}: it is stripped from
81
+ * the options handed to the underlying `channelWrapper.consume(...)` call
82
+ * (since amqplib's `Options.Consume` does not include it) and applied via
83
+ * `channel.prefetch(count, false)` registered through `addSetup` *before* the
84
+ * consume so the value is in effect when the consumer starts and is reapplied
85
+ * automatically on channel reconnect.
73
86
  */
74
87
  type ConsumerOptions = Options.Consume & {
75
- /** Number of messages to prefetch */prefetch?: number;
88
+ /** Per-consumer prefetch count. Applied before `channel.consume(...)`. */prefetch?: number;
76
89
  };
77
90
  /**
78
91
  * AMQP client that manages connections and channels with automatic topology setup.
@@ -83,7 +96,7 @@ type ConsumerOptions = Options.Consume & {
83
96
  * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
84
97
  * - Channel creation with JSON serialization enabled by default
85
98
  *
86
- * All operations return `Future<Result<T, TechnicalError>>` for consistent error handling.
99
+ * All operations return `ResultAsync<T, TechnicalError>` for consistent error handling.
87
100
  *
88
101
  * @example
89
102
  * ```typescript
@@ -92,14 +105,14 @@ type ConsumerOptions = Options.Consume & {
92
105
  * connectionOptions: { heartbeatIntervalInSeconds: 30 }
93
106
  * });
94
107
  *
95
- * // Wait for connection
96
- * await client.waitForConnect().resultToPromise();
108
+ * // Wait for connection (ResultAsync is thenable)
109
+ * await client.waitForConnect();
97
110
  *
98
111
  * // Publish a message
99
- * const result = await client.publish('exchange', 'routingKey', { data: 'value' }).resultToPromise();
112
+ * const result = await client.publish('exchange', 'routingKey', { data: 'value' });
100
113
  *
101
114
  * // Close when done
102
- * await client.close().resultToPromise();
115
+ * await client.close();
103
116
  * ```
104
117
  */
105
118
  declare class AmqpClient {
@@ -110,6 +123,15 @@ declare class AmqpClient {
110
123
  private readonly connectionOptions?;
111
124
  /** Resolved timeout in ms; `null` means "wait forever". */
112
125
  private readonly connectTimeoutMs;
126
+ /**
127
+ * Per-consumer prefetch setup functions registered via `addSetup` so they
128
+ * can be removed in {@link cancel} once the consumer is gone — otherwise
129
+ * the channel wrapper would replay the cancelled consumer's QoS on every
130
+ * reconnect and silently apply it to subsequent consumers.
131
+ *
132
+ * @internal
133
+ */
134
+ private readonly prefetchSetups;
113
135
  /**
114
136
  * Create a new AMQP client instance.
115
137
  *
@@ -136,7 +158,7 @@ declare class AmqpClient {
136
158
  * Wait for the channel to be connected and ready.
137
159
  *
138
160
  * If `connectTimeoutMs` was provided in the constructor options, the returned
139
- * Future resolves to `Result.Error<TechnicalError>` once the timeout elapses.
161
+ * ResultAsync resolves to `err(TechnicalError)` once the timeout elapses.
140
162
  * Without a timeout, this waits forever — amqp-connection-manager retries
141
163
  * connections indefinitely and never errors on its own.
142
164
  *
@@ -145,46 +167,42 @@ declare class AmqpClient {
145
167
  * connection's reference count. Callers must invoke `close()` on the error
146
168
  * path to release the connection — `waitForConnect` does not do this
147
169
  * automatically. The typed factories handle this cleanup for you.
148
- *
149
- * @returns A Future resolving to `Result.Ok(void)` on connect, or
150
- * `Result.Error(TechnicalError)` on timeout / connection failure.
151
170
  */
152
- waitForConnect(): Future<Result<void, TechnicalError>>;
171
+ waitForConnect(): ResultAsync<void, TechnicalError>;
153
172
  /**
154
173
  * Publish a message to an exchange.
155
174
  *
156
- * @param exchange - The exchange name
157
- * @param routingKey - The routing key
158
- * @param content - The message content (will be JSON serialized if json: true)
159
- * @param options - Optional publish options
160
- * @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full
175
+ * @returns ResultAsync resolving to `true` if the message was sent, `false` if the channel buffer is full.
161
176
  */
162
- publish(exchange: string, routingKey: string, content: Buffer | unknown, options?: PublishOptions): Future<Result<boolean, TechnicalError>>;
177
+ publish(exchange: string, routingKey: string, content: Buffer | unknown, options?: PublishOptions): ResultAsync<boolean, TechnicalError>;
163
178
  /**
164
179
  * Publish a message directly to a queue.
165
180
  *
166
- * @param queue - The queue name
167
- * @param content - The message content (will be JSON serialized if json: true)
168
- * @param options - Optional publish options
169
- * @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full
181
+ * @returns ResultAsync resolving to `true` if the message was sent, `false` if the channel buffer is full.
170
182
  */
171
- sendToQueue(queue: string, content: Buffer | unknown, options?: PublishOptions): Future<Result<boolean, TechnicalError>>;
183
+ sendToQueue(queue: string, content: Buffer | unknown, options?: PublishOptions): ResultAsync<boolean, TechnicalError>;
172
184
  /**
173
185
  * Start consuming messages from a queue.
174
186
  *
175
- * @param queue - The queue name
176
- * @param callback - The callback to invoke for each message
177
- * @param options - Optional consume options
178
- * @returns A Future with `Result<string>` - the consumer tag
187
+ * If `options.prefetch` is set, a per-consumer prefetch count is applied via
188
+ * `channel.prefetch(count, false)` registered as a setup function on the
189
+ * channel wrapper *before* the underlying `consume` call. Registering it via
190
+ * `addSetup` ensures the prefetch is reapplied automatically on channel
191
+ * reconnect; using `global=false` scopes it to subsequent consumers on the
192
+ * channel (RabbitMQ semantics — opposite of intuition: `false` is per-
193
+ * consumer, `true` is channel-wide).
194
+ *
195
+ * `prefetch` is stripped from the options handed to `channelWrapper.consume`
196
+ * because it is not a valid `amqplib` `Options.Consume` field — leaving it
197
+ * in would just travel as a no-op key-value pair on the consume frame.
198
+ *
199
+ * @returns ResultAsync resolving to the consumer tag.
179
200
  */
180
- consume(queue: string, callback: ConsumeCallback, options?: ConsumerOptions): Future<Result<string, TechnicalError>>;
201
+ consume(queue: string, callback: ConsumeCallback, options?: ConsumerOptions): ResultAsync<string, TechnicalError>;
181
202
  /**
182
203
  * Cancel a consumer by its consumer tag.
183
- *
184
- * @param consumerTag - The consumer tag to cancel
185
- * @returns A Future that resolves when the consumer is cancelled
186
204
  */
187
- cancel(consumerTag: string): Future<Result<void, TechnicalError>>;
205
+ cancel(consumerTag: string): ResultAsync<void, TechnicalError>;
188
206
  /**
189
207
  * Acknowledge a message.
190
208
  *
@@ -228,9 +246,10 @@ declare class AmqpClient {
228
246
  * - Decrease the reference count on the shared connection
229
247
  * - Close the connection if this was the last client using it
230
248
  *
231
- * @returns A Future that resolves when the channel and connection are closed
249
+ * Both steps run regardless of each other's outcome; if both fail, the
250
+ * errors are wrapped in an AggregateError.
232
251
  */
233
- close(): Future<Result<void, TechnicalError>>;
252
+ close(): ResultAsync<void, TechnicalError>;
234
253
  /**
235
254
  * Reset connection singleton cache (for testing only)
236
255
  * @internal
@@ -311,6 +330,30 @@ type Logger = {
311
330
  error(message: string, context?: LoggerContext): void;
312
331
  };
313
332
  //#endregion
333
+ //#region src/parsing.d.ts
334
+ /**
335
+ * Parse a `Buffer` as JSON, mapping any `JSON.parse` exception to the
336
+ * caller-supplied error type.
337
+ *
338
+ * Use this in consume / reply paths where a parse failure is a typed value,
339
+ * not a thrown exception — the caller decides how to translate the raw error
340
+ * into a domain-level error (e.g. {@link TechnicalError}).
341
+ *
342
+ * @typeParam E - The error type produced by `errorFn`.
343
+ * @param buffer - The raw message body to parse.
344
+ * @param errorFn - Callback invoked with the underlying `JSON.parse` error.
345
+ * @returns A `Result` containing the parsed `unknown` value or the mapped error.
346
+ *
347
+ * @example
348
+ * ```typescript
349
+ * const parsed = safeJsonParse(
350
+ * msg.content,
351
+ * (error) => new TechnicalError("Failed to parse JSON", error),
352
+ * );
353
+ * ```
354
+ */
355
+ declare function safeJsonParse<E>(buffer: Buffer, errorFn: (raw: unknown) => E): Result<unknown, E>;
356
+ //#endregion
314
357
  //#region src/setup.d.ts
315
358
  /**
316
359
  * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition.
@@ -438,5 +481,5 @@ declare function recordLateRpcReply(provider: TelemetryProvider, reason: "unknow
438
481
  */
439
482
  declare function _resetTelemetryCacheForTesting(): void;
440
483
  //#endregion
441
- export { AmqpClient, type AmqpClientOptions, type ConsumeCallback, type ConsumerOptions, DEFAULT_CONNECT_TIMEOUT_MS, type Logger, type LoggerContext, MessageValidationError, MessagingSemanticConventions, type PublishOptions, TechnicalError, type TelemetryProvider, _getConnectionCountForTesting, _resetConnectionsForTesting, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordLateRpcReply, recordPublishMetric, setupAmqpTopology, startConsumeSpan, startPublishSpan };
484
+ export { AmqpClient, type AmqpClientOptions, type ConsumeCallback, type ConsumerOptions, DEFAULT_CONNECT_TIMEOUT_MS, type Logger, type LoggerContext, MessageValidationError, MessagingSemanticConventions, type PublishOptions, TechnicalError, type TelemetryProvider, _getConnectionCountForTesting, _resetConnectionsForTesting, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordLateRpcReply, recordPublishMetric, safeJsonParse, setupAmqpTopology, startConsumeSpan, startPublishSpan };
442
485
  //# sourceMappingURL=index.d.mts.map
@@ -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/parsing.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, Result } from "@swan-io/boxed";
2
+ import { Result, ResultAsync, err, errAsync, ok } from "neverthrow";
3
3
  import amqp from "amqp-connection-manager";
4
4
  import { extractQueue } from "@amqp-contract/contract";
5
5
  //#region \0rolldown/runtime.js
@@ -300,7 +300,7 @@ function callSetupFunc(setup, channel) {
300
300
  * Default time `waitForConnect` will wait for the broker before erroring out.
301
301
  * Defaulting to a finite value (rather than waiting forever) means a fail-fast
302
302
  * developer experience: a misconfigured URL, a down broker, or wrong
303
- * credentials surface as a Result.Error within 30 seconds. Pass `null`
303
+ * credentials surface as an `err` within 30 seconds. Pass `null`
304
304
  * explicitly to disable the timeout — `Infinity` and other non-finite values
305
305
  * are also coerced to "no timeout" because Node's `setTimeout` clamps large
306
306
  * delays to ~24.8 days and silently fires near-immediately on `Infinity`.
@@ -327,7 +327,7 @@ function resolveConnectTimeoutMs(input) {
327
327
  * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
328
328
  * - Channel creation with JSON serialization enabled by default
329
329
  *
330
- * All operations return `Future<Result<T, TechnicalError>>` for consistent error handling.
330
+ * All operations return `ResultAsync<T, TechnicalError>` for consistent error handling.
331
331
  *
332
332
  * @example
333
333
  * ```typescript
@@ -336,14 +336,14 @@ function resolveConnectTimeoutMs(input) {
336
336
  * connectionOptions: { heartbeatIntervalInSeconds: 30 }
337
337
  * });
338
338
  *
339
- * // Wait for connection
340
- * await client.waitForConnect().resultToPromise();
339
+ * // Wait for connection (ResultAsync is thenable)
340
+ * await client.waitForConnect();
341
341
  *
342
342
  * // Publish a message
343
- * const result = await client.publish('exchange', 'routingKey', { data: 'value' }).resultToPromise();
343
+ * const result = await client.publish('exchange', 'routingKey', { data: 'value' });
344
344
  *
345
345
  * // Close when done
346
- * await client.close().resultToPromise();
346
+ * await client.close();
347
347
  * ```
348
348
  */
349
349
  var AmqpClient = class {
@@ -354,6 +354,15 @@ var AmqpClient = class {
354
354
  /** Resolved timeout in ms; `null` means "wait forever". */
355
355
  connectTimeoutMs;
356
356
  /**
357
+ * Per-consumer prefetch setup functions registered via `addSetup` so they
358
+ * can be removed in {@link cancel} once the consumer is gone — otherwise
359
+ * the channel wrapper would replay the cancelled consumer's QoS on every
360
+ * reconnect and silently apply it to subsequent consumers.
361
+ *
362
+ * @internal
363
+ */
364
+ prefetchSetups = /* @__PURE__ */ new Map();
365
+ /**
357
366
  * Create a new AMQP client instance.
358
367
  *
359
368
  * The client will automatically:
@@ -401,7 +410,7 @@ var AmqpClient = class {
401
410
  * Wait for the channel to be connected and ready.
402
411
  *
403
412
  * If `connectTimeoutMs` was provided in the constructor options, the returned
404
- * Future resolves to `Result.Error<TechnicalError>` once the timeout elapses.
413
+ * ResultAsync resolves to `err(TechnicalError)` once the timeout elapses.
405
414
  * Without a timeout, this waits forever — amqp-connection-manager retries
406
415
  * connections indefinitely and never errors on its own.
407
416
  *
@@ -410,9 +419,6 @@ var AmqpClient = class {
410
419
  * connection's reference count. Callers must invoke `close()` on the error
411
420
  * path to release the connection — `waitForConnect` does not do this
412
421
  * automatically. The typed factories handle this cleanup for you.
413
- *
414
- * @returns A Future resolving to `Result.Ok(void)` on connect, or
415
- * `Result.Error(TechnicalError)` on timeout / connection failure.
416
422
  */
417
423
  waitForConnect() {
418
424
  const connectPromise = this.channelWrapper.waitForConnect();
@@ -429,50 +435,76 @@ var AmqpClient = class {
429
435
  reject(error);
430
436
  });
431
437
  });
432
- return Future.fromPromise(racedPromise).mapError((error) => new TechnicalError("Failed to connect to AMQP broker", error));
438
+ return ResultAsync.fromPromise(racedPromise, (error) => new TechnicalError("Failed to connect to AMQP broker", error));
433
439
  }
434
440
  /**
435
441
  * Publish a message to an exchange.
436
442
  *
437
- * @param exchange - The exchange name
438
- * @param routingKey - The routing key
439
- * @param content - The message content (will be JSON serialized if json: true)
440
- * @param options - Optional publish options
441
- * @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full
443
+ * @returns ResultAsync resolving to `true` if the message was sent, `false` if the channel buffer is full.
442
444
  */
443
445
  publish(exchange, routingKey, content, options) {
444
- return Future.fromPromise(this.channelWrapper.publish(exchange, routingKey, content, options)).mapError((error) => new TechnicalError("Failed to publish message", error));
446
+ return ResultAsync.fromPromise(this.channelWrapper.publish(exchange, routingKey, content, options), (error) => new TechnicalError("Failed to publish message", error));
445
447
  }
446
448
  /**
447
449
  * Publish a message directly to a queue.
448
450
  *
449
- * @param queue - The queue name
450
- * @param content - The message content (will be JSON serialized if json: true)
451
- * @param options - Optional publish options
452
- * @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full
451
+ * @returns ResultAsync resolving to `true` if the message was sent, `false` if the channel buffer is full.
453
452
  */
454
453
  sendToQueue(queue, content, options) {
455
- return Future.fromPromise(this.channelWrapper.sendToQueue(queue, content, options)).mapError((error) => new TechnicalError("Failed to publish message to queue", error));
454
+ return ResultAsync.fromPromise(this.channelWrapper.sendToQueue(queue, content, options), (error) => new TechnicalError("Failed to publish message to queue", error));
456
455
  }
457
456
  /**
458
457
  * Start consuming messages from a queue.
459
458
  *
460
- * @param queue - The queue name
461
- * @param callback - The callback to invoke for each message
462
- * @param options - Optional consume options
463
- * @returns A Future with `Result<string>` - the consumer tag
459
+ * If `options.prefetch` is set, a per-consumer prefetch count is applied via
460
+ * `channel.prefetch(count, false)` registered as a setup function on the
461
+ * channel wrapper *before* the underlying `consume` call. Registering it via
462
+ * `addSetup` ensures the prefetch is reapplied automatically on channel
463
+ * reconnect; using `global=false` scopes it to subsequent consumers on the
464
+ * channel (RabbitMQ semantics — opposite of intuition: `false` is per-
465
+ * consumer, `true` is channel-wide).
466
+ *
467
+ * `prefetch` is stripped from the options handed to `channelWrapper.consume`
468
+ * because it is not a valid `amqplib` `Options.Consume` field — leaving it
469
+ * in would just travel as a no-op key-value pair on the consume frame.
470
+ *
471
+ * @returns ResultAsync resolving to the consumer tag.
464
472
  */
465
473
  consume(queue, callback, options) {
466
- return Future.fromPromise(this.channelWrapper.consume(queue, callback, options)).mapError((error) => new TechnicalError("Failed to start consuming messages", error)).mapOk((reply) => reply.consumerTag);
474
+ const { prefetch, ...consumeOptions } = options ?? {};
475
+ if (prefetch !== void 0) {
476
+ if (!Number.isInteger(prefetch) || prefetch < 0 || prefetch > 65535) return errAsync(new TechnicalError(`Invalid prefetch: expected a non-negative integer ≤ 65535, got ${String(prefetch)}`));
477
+ }
478
+ const prefetchSetup = typeof prefetch === "number" ? async (channel) => {
479
+ await channel.prefetch(prefetch, false);
480
+ } : void 0;
481
+ const consumePromise = (async () => {
482
+ if (prefetchSetup) await this.channelWrapper.addSetup(prefetchSetup);
483
+ let reply;
484
+ try {
485
+ reply = await this.channelWrapper.consume(queue, callback, consumeOptions);
486
+ } catch (error) {
487
+ if (prefetchSetup) await this.channelWrapper.removeSetup(prefetchSetup).catch(() => {});
488
+ throw error;
489
+ }
490
+ if (prefetchSetup) this.prefetchSetups.set(reply.consumerTag, prefetchSetup);
491
+ return reply;
492
+ })();
493
+ return ResultAsync.fromPromise(consumePromise, (error) => new TechnicalError("Failed to start consuming messages", error)).map((reply) => reply.consumerTag);
467
494
  }
468
495
  /**
469
496
  * Cancel a consumer by its consumer tag.
470
- *
471
- * @param consumerTag - The consumer tag to cancel
472
- * @returns A Future that resolves when the consumer is cancelled
473
497
  */
474
498
  cancel(consumerTag) {
475
- return Future.fromPromise(this.channelWrapper.cancel(consumerTag)).mapError((error) => new TechnicalError("Failed to cancel consumer", error)).mapOk(() => void 0);
499
+ return ResultAsync.fromPromise((async () => {
500
+ const setup = this.prefetchSetups.get(consumerTag);
501
+ this.prefetchSetups.delete(consumerTag);
502
+ try {
503
+ await this.channelWrapper.cancel(consumerTag);
504
+ } finally {
505
+ if (setup !== void 0) await this.channelWrapper.removeSetup(setup).catch(() => {});
506
+ }
507
+ })(), (error) => new TechnicalError("Failed to cancel consumer", error)).map(() => void 0);
476
508
  }
477
509
  /**
478
510
  * Acknowledge a message.
@@ -525,13 +557,18 @@ var AmqpClient = class {
525
557
  * - Decrease the reference count on the shared connection
526
558
  * - Close the connection if this was the last client using it
527
559
  *
528
- * @returns A Future that resolves when the channel and connection are closed
560
+ * Both steps run regardless of each other's outcome; if both fail, the
561
+ * errors are wrapped in an AggregateError.
529
562
  */
530
563
  close() {
531
- 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) => {
532
- 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")));
533
- return channelResult.isError() ? channelResult : releaseResult;
534
- }));
564
+ return new ResultAsync((async () => {
565
+ const channelResult = await ResultAsync.fromPromise(this.channelWrapper.close(), (error) => new TechnicalError("Failed to close channel", error));
566
+ const releaseResult = await ResultAsync.fromPromise(ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions), (error) => new TechnicalError("Failed to release connection", error));
567
+ if (channelResult.isErr() && releaseResult.isErr()) return err(new TechnicalError("Failed to close channel and release connection", new AggregateError([channelResult.error, releaseResult.error], "Failed to close channel and release connection")));
568
+ if (channelResult.isErr()) return channelResult;
569
+ if (releaseResult.isErr()) return releaseResult;
570
+ return ok(void 0);
571
+ })());
535
572
  }
536
573
  /**
537
574
  * Reset connection singleton cache (for testing only)
@@ -542,6 +579,32 @@ var AmqpClient = class {
542
579
  }
543
580
  };
544
581
  //#endregion
582
+ //#region src/parsing.ts
583
+ /**
584
+ * Parse a `Buffer` as JSON, mapping any `JSON.parse` exception to the
585
+ * caller-supplied error type.
586
+ *
587
+ * Use this in consume / reply paths where a parse failure is a typed value,
588
+ * not a thrown exception — the caller decides how to translate the raw error
589
+ * into a domain-level error (e.g. {@link TechnicalError}).
590
+ *
591
+ * @typeParam E - The error type produced by `errorFn`.
592
+ * @param buffer - The raw message body to parse.
593
+ * @param errorFn - Callback invoked with the underlying `JSON.parse` error.
594
+ * @returns A `Result` containing the parsed `unknown` value or the mapped error.
595
+ *
596
+ * @example
597
+ * ```typescript
598
+ * const parsed = safeJsonParse(
599
+ * msg.content,
600
+ * (error) => new TechnicalError("Failed to parse JSON", error),
601
+ * );
602
+ * ```
603
+ */
604
+ function safeJsonParse(buffer, errorFn) {
605
+ return Result.fromThrowable(() => JSON.parse(buffer.toString()), errorFn)();
606
+ }
607
+ //#endregion
545
608
  //#region src/telemetry.ts
546
609
  /**
547
610
  * SpanKind values from OpenTelemetry.
@@ -806,6 +869,6 @@ function _resetTelemetryCacheForTesting() {
806
869
  cachedLateRpcReplyCounter = void 0;
807
870
  }
808
871
  //#endregion
809
- export { AmqpClient, DEFAULT_CONNECT_TIMEOUT_MS, MessageValidationError, MessagingSemanticConventions, TechnicalError, _getConnectionCountForTesting, _resetConnectionsForTesting, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordLateRpcReply, recordPublishMetric, setupAmqpTopology, startConsumeSpan, startPublishSpan };
872
+ export { AmqpClient, DEFAULT_CONNECT_TIMEOUT_MS, MessageValidationError, MessagingSemanticConventions, TechnicalError, _getConnectionCountForTesting, _resetConnectionsForTesting, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordLateRpcReply, recordPublishMetric, safeJsonParse, setupAmqpTopology, startConsumeSpan, startPublishSpan };
810
873
 
811
874
  //# sourceMappingURL=index.mjs.map