@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/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![CI](https://github.com/btravers/amqp-contract/actions/workflows/ci.yml/badge.svg)](https://github.com/btravers/amqp-contract/actions/workflows/ci.yml)
6
6
  [![npm version](https://img.shields.io/npm/v/@amqp-contract/core.svg?logo=npm)](https://www.npmjs.com/package/@amqp-contract/core)
7
7
  [![npm downloads](https://img.shields.io/npm/dm/@amqp-contract/core.svg)](https://www.npmjs.com/package/@amqp-contract/core)
8
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue?logo=typescript)](https://www.typescriptlang.org/)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-6.0-blue?logo=typescript)](https://www.typescriptlang.org/)
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
10
 
11
11
  This package provides centralized functionality for establishing AMQP topology (exchanges, queues, and bindings) from contract definitions, and defines the `Logger` interface used across amqp-contract packages.
@@ -87,11 +87,13 @@ const logger: Logger = {
87
87
  // Pass the logger to client or worker
88
88
  import { TypedAmqpClient } from "@amqp-contract/client";
89
89
 
90
- const client = await TypedAmqpClient.create({
91
- contract,
92
- urls: ["amqp://localhost"],
93
- logger, // Optional: logs published messages
94
- });
90
+ const client = (
91
+ await TypedAmqpClient.create({
92
+ contract,
93
+ urls: ["amqp://localhost"],
94
+ logger, // Optional: logs published messages
95
+ })
96
+ )._unsafeUnwrap();
95
97
  ```
96
98
 
97
99
  ## API
package/dist/index.cjs CHANGED
@@ -21,7 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
- let _swan_io_boxed = require("@swan-io/boxed");
24
+ let neverthrow = require("neverthrow");
25
25
  let amqp_connection_manager = require("amqp-connection-manager");
26
26
  amqp_connection_manager = __toESM(amqp_connection_manager, 1);
27
27
  let _amqp_contract_contract = require("@amqp-contract/contract");
@@ -321,7 +321,7 @@ function callSetupFunc(setup, channel) {
321
321
  * Default time `waitForConnect` will wait for the broker before erroring out.
322
322
  * Defaulting to a finite value (rather than waiting forever) means a fail-fast
323
323
  * developer experience: a misconfigured URL, a down broker, or wrong
324
- * credentials surface as a Result.Error within 30 seconds. Pass `null`
324
+ * credentials surface as an `err` within 30 seconds. Pass `null`
325
325
  * explicitly to disable the timeout — `Infinity` and other non-finite values
326
326
  * are also coerced to "no timeout" because Node's `setTimeout` clamps large
327
327
  * delays to ~24.8 days and silently fires near-immediately on `Infinity`.
@@ -348,7 +348,7 @@ function resolveConnectTimeoutMs(input) {
348
348
  * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
349
349
  * - Channel creation with JSON serialization enabled by default
350
350
  *
351
- * All operations return `Future<Result<T, TechnicalError>>` for consistent error handling.
351
+ * All operations return `ResultAsync<T, TechnicalError>` for consistent error handling.
352
352
  *
353
353
  * @example
354
354
  * ```typescript
@@ -357,14 +357,14 @@ function resolveConnectTimeoutMs(input) {
357
357
  * connectionOptions: { heartbeatIntervalInSeconds: 30 }
358
358
  * });
359
359
  *
360
- * // Wait for connection
361
- * await client.waitForConnect().resultToPromise();
360
+ * // Wait for connection (ResultAsync is thenable)
361
+ * await client.waitForConnect();
362
362
  *
363
363
  * // Publish a message
364
- * const result = await client.publish('exchange', 'routingKey', { data: 'value' }).resultToPromise();
364
+ * const result = await client.publish('exchange', 'routingKey', { data: 'value' });
365
365
  *
366
366
  * // Close when done
367
- * await client.close().resultToPromise();
367
+ * await client.close();
368
368
  * ```
369
369
  */
370
370
  var AmqpClient = class {
@@ -375,6 +375,15 @@ var AmqpClient = class {
375
375
  /** Resolved timeout in ms; `null` means "wait forever". */
376
376
  connectTimeoutMs;
377
377
  /**
378
+ * Per-consumer prefetch setup functions registered via `addSetup` so they
379
+ * can be removed in {@link cancel} once the consumer is gone — otherwise
380
+ * the channel wrapper would replay the cancelled consumer's QoS on every
381
+ * reconnect and silently apply it to subsequent consumers.
382
+ *
383
+ * @internal
384
+ */
385
+ prefetchSetups = /* @__PURE__ */ new Map();
386
+ /**
378
387
  * Create a new AMQP client instance.
379
388
  *
380
389
  * The client will automatically:
@@ -422,7 +431,7 @@ var AmqpClient = class {
422
431
  * Wait for the channel to be connected and ready.
423
432
  *
424
433
  * If `connectTimeoutMs` was provided in the constructor options, the returned
425
- * Future resolves to `Result.Error<TechnicalError>` once the timeout elapses.
434
+ * ResultAsync resolves to `err(TechnicalError)` once the timeout elapses.
426
435
  * Without a timeout, this waits forever — amqp-connection-manager retries
427
436
  * connections indefinitely and never errors on its own.
428
437
  *
@@ -431,9 +440,6 @@ var AmqpClient = class {
431
440
  * connection's reference count. Callers must invoke `close()` on the error
432
441
  * path to release the connection — `waitForConnect` does not do this
433
442
  * automatically. The typed factories handle this cleanup for you.
434
- *
435
- * @returns A Future resolving to `Result.Ok(void)` on connect, or
436
- * `Result.Error(TechnicalError)` on timeout / connection failure.
437
443
  */
438
444
  waitForConnect() {
439
445
  const connectPromise = this.channelWrapper.waitForConnect();
@@ -450,50 +456,76 @@ var AmqpClient = class {
450
456
  reject(error);
451
457
  });
452
458
  });
453
- return _swan_io_boxed.Future.fromPromise(racedPromise).mapError((error) => new TechnicalError("Failed to connect to AMQP broker", error));
459
+ return neverthrow.ResultAsync.fromPromise(racedPromise, (error) => new TechnicalError("Failed to connect to AMQP broker", error));
454
460
  }
455
461
  /**
456
462
  * Publish a message to an exchange.
457
463
  *
458
- * @param exchange - The exchange name
459
- * @param routingKey - The routing key
460
- * @param content - The message content (will be JSON serialized if json: true)
461
- * @param options - Optional publish options
462
- * @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full
464
+ * @returns ResultAsync resolving to `true` if the message was sent, `false` if the channel buffer is full.
463
465
  */
464
466
  publish(exchange, routingKey, content, options) {
465
- return _swan_io_boxed.Future.fromPromise(this.channelWrapper.publish(exchange, routingKey, content, options)).mapError((error) => new TechnicalError("Failed to publish message", error));
467
+ return neverthrow.ResultAsync.fromPromise(this.channelWrapper.publish(exchange, routingKey, content, options), (error) => new TechnicalError("Failed to publish message", error));
466
468
  }
467
469
  /**
468
470
  * Publish a message directly to a queue.
469
471
  *
470
- * @param queue - The queue name
471
- * @param content - The message content (will be JSON serialized if json: true)
472
- * @param options - Optional publish options
473
- * @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full
472
+ * @returns ResultAsync resolving to `true` if the message was sent, `false` if the channel buffer is full.
474
473
  */
475
474
  sendToQueue(queue, content, options) {
476
- return _swan_io_boxed.Future.fromPromise(this.channelWrapper.sendToQueue(queue, content, options)).mapError((error) => new TechnicalError("Failed to publish message to queue", error));
475
+ return neverthrow.ResultAsync.fromPromise(this.channelWrapper.sendToQueue(queue, content, options), (error) => new TechnicalError("Failed to publish message to queue", error));
477
476
  }
478
477
  /**
479
478
  * Start consuming messages from a queue.
480
479
  *
481
- * @param queue - The queue name
482
- * @param callback - The callback to invoke for each message
483
- * @param options - Optional consume options
484
- * @returns A Future with `Result<string>` - the consumer tag
480
+ * If `options.prefetch` is set, a per-consumer prefetch count is applied via
481
+ * `channel.prefetch(count, false)` registered as a setup function on the
482
+ * channel wrapper *before* the underlying `consume` call. Registering it via
483
+ * `addSetup` ensures the prefetch is reapplied automatically on channel
484
+ * reconnect; using `global=false` scopes it to subsequent consumers on the
485
+ * channel (RabbitMQ semantics — opposite of intuition: `false` is per-
486
+ * consumer, `true` is channel-wide).
487
+ *
488
+ * `prefetch` is stripped from the options handed to `channelWrapper.consume`
489
+ * because it is not a valid `amqplib` `Options.Consume` field — leaving it
490
+ * in would just travel as a no-op key-value pair on the consume frame.
491
+ *
492
+ * @returns ResultAsync resolving to the consumer tag.
485
493
  */
486
494
  consume(queue, callback, options) {
487
- return _swan_io_boxed.Future.fromPromise(this.channelWrapper.consume(queue, callback, options)).mapError((error) => new TechnicalError("Failed to start consuming messages", error)).mapOk((reply) => reply.consumerTag);
495
+ const { prefetch, ...consumeOptions } = options ?? {};
496
+ if (prefetch !== void 0) {
497
+ if (!Number.isInteger(prefetch) || prefetch < 0 || prefetch > 65535) return (0, neverthrow.errAsync)(new TechnicalError(`Invalid prefetch: expected a non-negative integer ≤ 65535, got ${String(prefetch)}`));
498
+ }
499
+ const prefetchSetup = typeof prefetch === "number" ? async (channel) => {
500
+ await channel.prefetch(prefetch, false);
501
+ } : void 0;
502
+ const consumePromise = (async () => {
503
+ if (prefetchSetup) await this.channelWrapper.addSetup(prefetchSetup);
504
+ let reply;
505
+ try {
506
+ reply = await this.channelWrapper.consume(queue, callback, consumeOptions);
507
+ } catch (error) {
508
+ if (prefetchSetup) await this.channelWrapper.removeSetup(prefetchSetup).catch(() => {});
509
+ throw error;
510
+ }
511
+ if (prefetchSetup) this.prefetchSetups.set(reply.consumerTag, prefetchSetup);
512
+ return reply;
513
+ })();
514
+ return neverthrow.ResultAsync.fromPromise(consumePromise, (error) => new TechnicalError("Failed to start consuming messages", error)).map((reply) => reply.consumerTag);
488
515
  }
489
516
  /**
490
517
  * Cancel a consumer by its consumer tag.
491
- *
492
- * @param consumerTag - The consumer tag to cancel
493
- * @returns A Future that resolves when the consumer is cancelled
494
518
  */
495
519
  cancel(consumerTag) {
496
- return _swan_io_boxed.Future.fromPromise(this.channelWrapper.cancel(consumerTag)).mapError((error) => new TechnicalError("Failed to cancel consumer", error)).mapOk(() => void 0);
520
+ return neverthrow.ResultAsync.fromPromise((async () => {
521
+ const setup = this.prefetchSetups.get(consumerTag);
522
+ this.prefetchSetups.delete(consumerTag);
523
+ try {
524
+ await this.channelWrapper.cancel(consumerTag);
525
+ } finally {
526
+ if (setup !== void 0) await this.channelWrapper.removeSetup(setup).catch(() => {});
527
+ }
528
+ })(), (error) => new TechnicalError("Failed to cancel consumer", error)).map(() => void 0);
497
529
  }
498
530
  /**
499
531
  * Acknowledge a message.
@@ -546,13 +578,18 @@ var AmqpClient = class {
546
578
  * - Decrease the reference count on the shared connection
547
579
  * - Close the connection if this was the last client using it
548
580
  *
549
- * @returns A Future that resolves when the channel and connection are closed
581
+ * Both steps run regardless of each other's outcome; if both fail, the
582
+ * errors are wrapped in an AggregateError.
550
583
  */
551
584
  close() {
552
- 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) => {
553
- 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")));
554
- return channelResult.isError() ? channelResult : releaseResult;
555
- }));
585
+ return new neverthrow.ResultAsync((async () => {
586
+ const channelResult = await neverthrow.ResultAsync.fromPromise(this.channelWrapper.close(), (error) => new TechnicalError("Failed to close channel", error));
587
+ const releaseResult = await neverthrow.ResultAsync.fromPromise(ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions), (error) => new TechnicalError("Failed to release connection", error));
588
+ if (channelResult.isErr() && releaseResult.isErr()) return (0, neverthrow.err)(new TechnicalError("Failed to close channel and release connection", new AggregateError([channelResult.error, releaseResult.error], "Failed to close channel and release connection")));
589
+ if (channelResult.isErr()) return channelResult;
590
+ if (releaseResult.isErr()) return releaseResult;
591
+ return (0, neverthrow.ok)(void 0);
592
+ })());
556
593
  }
557
594
  /**
558
595
  * Reset connection singleton cache (for testing only)
@@ -563,6 +600,32 @@ var AmqpClient = class {
563
600
  }
564
601
  };
565
602
  //#endregion
603
+ //#region src/parsing.ts
604
+ /**
605
+ * Parse a `Buffer` as JSON, mapping any `JSON.parse` exception to the
606
+ * caller-supplied error type.
607
+ *
608
+ * Use this in consume / reply paths where a parse failure is a typed value,
609
+ * not a thrown exception — the caller decides how to translate the raw error
610
+ * into a domain-level error (e.g. {@link TechnicalError}).
611
+ *
612
+ * @typeParam E - The error type produced by `errorFn`.
613
+ * @param buffer - The raw message body to parse.
614
+ * @param errorFn - Callback invoked with the underlying `JSON.parse` error.
615
+ * @returns A `Result` containing the parsed `unknown` value or the mapped error.
616
+ *
617
+ * @example
618
+ * ```typescript
619
+ * const parsed = safeJsonParse(
620
+ * msg.content,
621
+ * (error) => new TechnicalError("Failed to parse JSON", error),
622
+ * );
623
+ * ```
624
+ */
625
+ function safeJsonParse(buffer, errorFn) {
626
+ return neverthrow.Result.fromThrowable(() => JSON.parse(buffer.toString()), errorFn)();
627
+ }
628
+ //#endregion
566
629
  //#region src/telemetry.ts
567
630
  /**
568
631
  * SpanKind values from OpenTelemetry.
@@ -841,6 +904,7 @@ exports.endSpanSuccess = endSpanSuccess;
841
904
  exports.recordConsumeMetric = recordConsumeMetric;
842
905
  exports.recordLateRpcReply = recordLateRpcReply;
843
906
  exports.recordPublishMetric = recordPublishMetric;
907
+ exports.safeJsonParse = safeJsonParse;
844
908
  exports.setupAmqpTopology = setupAmqpTopology;
845
909
  exports.startConsumeSpan = startConsumeSpan;
846
910
  exports.startPublishSpan = startPublishSpan;
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ContractDefinition } from "@amqp-contract/contract";
2
- import { Future, Result } from "@swan-io/boxed";
3
2
  import { AmqpConnectionManager, AmqpConnectionManagerOptions, ConnectionUrl, CreateChannelOpts } from "amqp-connection-manager";
4
3
  import { Channel, ConsumeMessage, Options } from "amqplib";
4
+ import { Result, ResultAsync } from "neverthrow";
5
5
  import { Attributes, Counter, Histogram, Span, Tracer } from "@opentelemetry/api";
6
6
 
7
7
  //#region src/errors.d.ts
@@ -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.cts.map
@@ -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;;;;;;;cCwCa,0BAAA;;;;ADdb;;;;;;;;KCwCY,iBAAA;EACV,IAAA,EAAM,aAAA;EACN,iBAAA,GAAoB,4BAAA;EACpB,cAAA,GAAiB,OAAA,CAAQ,iBAAA;EACzB,gBAAA;AAAA;;AA9BF;;KAoCY,eAAA,IAAmB,GAAA,EAAK,cAAA,mBAAiC,OAAA;;;AAVrE;KAeY,cAAA,GAAiB,OAAA,CAAQ,OAAA;kDAEnC,OAAA;AAAA;;;;KAMU,eAAA,GAAkB,OAAA,CAAQ,OAAA;EAtBpC,qCAwBA,QAAA;AAAA;;;;;;;;AAfF;;;;;;;;;AAKA;;;;;;;;;AAQA;;;cAiCa,UAAA;EAAA,iBAoBQ,QAAA;EAAA,iBAnBF,UAAA;EAAA,iBACA,cAAA;EAAA,iBACA,IAAA;EAAA,iBACA,iBAAA;EAJN;EAAA,iBAMM,gBAAA;;;;;;;;;;;;cAcE,QAAA,EAAU,kBAAA,EAC3B,OAAA,EAAS,iBAAA;EAoIA;;;;;;;;;EA/EX,aAAA,CAAA,GAAiB,qBAAA;EA+GgC;;;;;;;;;;;;;;;;;EA1FjD,cAAA,CAAA,GAAkB,MAAA,CAAO,MAAA,OAAa,cAAA;EAzFrB;;;;;;;;;EA8HjB,OAAA,CACE,QAAA,UACA,UAAA,UACA,OAAA,EAAS,MAAA,YACT,OAAA,GAAU,cAAA,GACT,MAAA,CAAO,MAAA,UAAgB,cAAA;EA1CD;;;;;;;;EAwDzB,WAAA,CACE,KAAA,UACA,OAAA,EAAS,MAAA,YACT,OAAA,GAAU,cAAA,GACT,MAAA,CAAO,MAAA,UAAgB,cAAA;EAlBvB;;;;;;;;EAgCH,OAAA,CACE,KAAA,UACA,QAAA,EAAU,eAAA,EACV,OAAA,GAAU,eAAA,GACT,MAAA,CAAO,MAAA,SAAe,cAAA;EAlBtB;;;;;;EA8BH,MAAA,CAAO,WAAA,WAAsB,MAAA,CAAO,MAAA,OAAa,cAAA;EAbrC;;;;;;EAyBZ,GAAA,CAAI,GAAA,EAAK,cAAA,EAAgB,OAAA;EAZI;;;;;;;EAuB7B,IAAA,CAAK,GAAA,EAAK,cAAA,EAAgB,OAAA,YAAiB,OAAA;EAAjC;;;;;;;EAWV,QAAA,CAAS,KAAA,GAAQ,OAAA,EAAS,OAAA,YAAmB,OAAA;EAApC;;;;;;;;;;;EAeT,EAAA,CAAG,KAAA,UAAe,QAAA,MAAc,IAAA;EA+CuB;;;;AC1NzD;;;;;AASA;EDgLE,KAAA,CAAA,GAAS,MAAA,CAAO,MAAA,OAAa,cAAA;;;;;SAiChB,+BAAA,CAAA,GAAmC,OAAA;AAAA;;;;;;;;;;;iBC1NlC,6BAAA,CAAA;;;;;;iBASA,2BAAA,CAAA,GAA+B,OAAA;;;;;;;;;;AFlM/C;KGEY,aAAA,GAAgB,MAAA;EAC1B,KAAA;AAAA;;;;;;;;AHuBF;;;;;;;;;;KGHY,MAAA;EHMuB;;;;ACWnC;EEXE,KAAA,CAAM,OAAA,UAAiB,OAAA,GAAU,aAAA;;;;AFqCnC;;EE9BE,IAAA,CAAK,OAAA,UAAiB,OAAA,GAAU,aAAA;EF+B1B;;;;;EExBN,IAAA,CAAK,OAAA,UAAiB,OAAA,GAAU,aAAA;EFwBhC;;;;;EEjBA,KAAA,CAAM,OAAA,UAAiB,OAAA,GAAU,aAAA;AAAA;;;;;;;;AHlDnC;;;;;;;;;;;AA0BA;;;;iBIPsB,iBAAA,CACpB,OAAA,EAAS,OAAA,EACT,QAAA,EAAU,kBAAA,GACT,OAAA;;;;;;;cCHU,4BAAA;EAAA;;;;;;;;;;;;;;;;;;;KA4BD,iBAAA;ELnBQ;;;;EKwBlB,SAAA,QAAiB,MAAA;;;AJZnB;;EIkBE,iBAAA,QAAyB,OAAA;EJlBY;;AA0BvC;;EIFE,iBAAA,QAAyB,OAAA;EJGnB;;;;EIGN,0BAAA,QAAkC,SAAA;EJDV;;;;EIOxB,0BAAA,QAAkC,SAAA;EJPlC;;;;;EIcA,sBAAA,QAA8B,OAAA;AAAA;;;;cA2InB,wBAAA,EAA0B,iBAAA;;;;;iBAavB,gBAAA,CACd,QAAA,EAAU,iBAAA,EACV,YAAA,UACA,UAAA,sBACA,UAAA,GAAa,UAAA,GACZ,IAAA;;;;;iBA8Ba,gBAAA,CACd,QAAA,EAAU,iBAAA,EACV,SAAA,UACA,YAAA,UACA,UAAA,GAAa,UAAA,GACZ,IAAA;;;;iBA2Ba,cAAA,CAAe,IAAA,EAAM,IAAA;;;;iBAerB,YAAA,CAAa,IAAA,EAAM,IAAA,cAAkB,KAAA,EAAO,KAAA;;;;iBAiB5C,mBAAA,CACd,QAAA,EAAU,iBAAA,EACV,YAAA,UACA,UAAA,sBACA,OAAA,WACA,UAAA;AJzNF;;;AAAA,iBI+OgB,mBAAA,CACd,QAAA,EAAU,iBAAA,EACV,SAAA,UACA,YAAA,UACA,OAAA,WACA,UAAA;;;;;;;;;iBAyBc,kBAAA,CACd,QAAA,EAAU,iBAAA,EACV,MAAA;;;;;;iBAkBc,8BAAA,CAAA"}
1
+ {"version":3,"file":"index.d.cts","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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"}