@amqp-contract/core 0.24.0 → 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.cjs CHANGED
@@ -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:
@@ -468,16 +477,55 @@ var AmqpClient = class {
468
477
  /**
469
478
  * Start consuming messages from a queue.
470
479
  *
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
+ *
471
492
  * @returns ResultAsync resolving to the consumer tag.
472
493
  */
473
494
  consume(queue, callback, options) {
474
- return neverthrow.ResultAsync.fromPromise(this.channelWrapper.consume(queue, callback, options), (error) => new TechnicalError("Failed to start consuming messages", error)).map((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);
475
515
  }
476
516
  /**
477
517
  * Cancel a consumer by its consumer tag.
478
518
  */
479
519
  cancel(consumerTag) {
480
- return neverthrow.ResultAsync.fromPromise(this.channelWrapper.cancel(consumerTag), (error) => new TechnicalError("Failed to cancel consumer", error)).map(() => 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);
481
529
  }
482
530
  /**
483
531
  * Acknowledge a message.
@@ -552,6 +600,32 @@ var AmqpClient = class {
552
600
  }
553
601
  };
554
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
555
629
  //#region src/telemetry.ts
556
630
  /**
557
631
  * SpanKind values from OpenTelemetry.
@@ -830,6 +904,7 @@ exports.endSpanSuccess = endSpanSuccess;
830
904
  exports.recordConsumeMetric = recordConsumeMetric;
831
905
  exports.recordLateRpcReply = recordLateRpcReply;
832
906
  exports.recordPublishMetric = recordPublishMetric;
907
+ exports.safeJsonParse = safeJsonParse;
833
908
  exports.setupAmqpTopology = setupAmqpTopology;
834
909
  exports.startConsumeSpan = startConsumeSpan;
835
910
  exports.startPublishSpan = startPublishSpan;
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ContractDefinition } from "@amqp-contract/contract";
2
2
  import { AmqpConnectionManager, AmqpConnectionManagerOptions, ConnectionUrl, CreateChannelOpts } from "amqp-connection-manager";
3
3
  import { Channel, ConsumeMessage, Options } from "amqplib";
4
- import { ResultAsync } from "neverthrow";
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
@@ -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.
@@ -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
  *
@@ -162,6 +184,18 @@ declare class AmqpClient {
162
184
  /**
163
185
  * Start consuming messages from a queue.
164
186
  *
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
+ *
165
199
  * @returns ResultAsync resolving to the consumer tag.
166
200
  */
167
201
  consume(queue: string, callback: ConsumeCallback, options?: ConsumerOptions): ResultAsync<string, TechnicalError>;
@@ -296,6 +330,30 @@ type Logger = {
296
330
  error(message: string, context?: LoggerContext): void;
297
331
  };
298
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
299
357
  //#region src/setup.d.ts
300
358
  /**
301
359
  * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition.
@@ -423,5 +481,5 @@ declare function recordLateRpcReply(provider: TelemetryProvider, reason: "unknow
423
481
  */
424
482
  declare function _resetTelemetryCacheForTesting(): void;
425
483
  //#endregion
426
- 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 };
427
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":"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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/parsing.ts","../src/setup.ts","../src/telemetry.ts"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { ResultAsync } from "neverthrow";
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";
@@ -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.
@@ -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
  *
@@ -162,6 +184,18 @@ declare class AmqpClient {
162
184
  /**
163
185
  * Start consuming messages from a queue.
164
186
  *
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
+ *
165
199
  * @returns ResultAsync resolving to the consumer tag.
166
200
  */
167
201
  consume(queue: string, callback: ConsumeCallback, options?: ConsumerOptions): ResultAsync<string, TechnicalError>;
@@ -296,6 +330,30 @@ type Logger = {
296
330
  error(message: string, context?: LoggerContext): void;
297
331
  };
298
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
299
357
  //#region src/setup.d.ts
300
358
  /**
301
359
  * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition.
@@ -423,5 +481,5 @@ declare function recordLateRpcReply(provider: TelemetryProvider, reason: "unknow
423
481
  */
424
482
  declare function _resetTelemetryCacheForTesting(): void;
425
483
  //#endregion
426
- 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 };
427
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 { ResultAsync, err, ok } from "neverthrow";
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
@@ -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:
@@ -447,16 +456,55 @@ var AmqpClient = class {
447
456
  /**
448
457
  * Start consuming messages from a queue.
449
458
  *
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
+ *
450
471
  * @returns ResultAsync resolving to the consumer tag.
451
472
  */
452
473
  consume(queue, callback, options) {
453
- return ResultAsync.fromPromise(this.channelWrapper.consume(queue, callback, options), (error) => new TechnicalError("Failed to start consuming messages", error)).map((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);
454
494
  }
455
495
  /**
456
496
  * Cancel a consumer by its consumer tag.
457
497
  */
458
498
  cancel(consumerTag) {
459
- return ResultAsync.fromPromise(this.channelWrapper.cancel(consumerTag), (error) => new TechnicalError("Failed to cancel consumer", error)).map(() => 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);
460
508
  }
461
509
  /**
462
510
  * Acknowledge a message.
@@ -531,6 +579,32 @@ var AmqpClient = class {
531
579
  }
532
580
  };
533
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
534
608
  //#region src/telemetry.ts
535
609
  /**
536
610
  * SpanKind values from OpenTelemetry.
@@ -795,6 +869,6 @@ function _resetTelemetryCacheForTesting() {
795
869
  cachedLateRpcReplyCounter = void 0;
796
870
  }
797
871
  //#endregion
798
- 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 };
799
873
 
800
874
  //# sourceMappingURL=index.mjs.map