@amqp-contract/worker 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.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { extractConsumer, extractQueue, isQueueWithTtlBackoffInfrastructure } from "@amqp-contract/contract";
2
- import { AmqpClient, MessageValidationError, TechnicalError, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, startConsumeSpan } from "@amqp-contract/core";
3
- import { Result, ResultAsync, err, errAsync, ok, okAsync } from "neverthrow";
2
+ import { AmqpClient, MessageValidationError, TechnicalError, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, safeJsonParse, startConsumeSpan } from "@amqp-contract/core";
3
+ import { ResultAsync, err, errAsync, ok, okAsync } from "neverthrow";
4
4
  import { gunzip, inflate } from "node:zlib";
5
5
  import { promisify } from "node:util";
6
6
  //#region src/decompression.ts
@@ -37,36 +37,39 @@ function decompressBuffer(buffer, contentEncoding) {
37
37
  //#endregion
38
38
  //#region src/errors.ts
39
39
  /**
40
- * Retryable errors - transient failures that may succeed on retry
41
- * Examples: network timeouts, rate limiting, temporary service unavailability
40
+ * Abstract base class for all handler-signalled errors.
42
41
  *
43
- * Use this error type when the operation might succeed if retried.
44
- * The worker will apply exponential backoff and retry the message.
42
+ * Concrete subclasses (`RetryableError`, `NonRetryableError`) discriminate on
43
+ * the `name` property so exhaustive narrowing in user code keeps working.
44
+ * `error instanceof HandlerError` is true for any handler error.
45
45
  */
46
- var RetryableError = class extends Error {
46
+ var HandlerError = class extends Error {
47
47
  constructor(message, cause) {
48
48
  super(message);
49
49
  this.cause = cause;
50
- this.name = "RetryableError";
51
50
  const ErrorConstructor = Error;
52
51
  if (typeof ErrorConstructor.captureStackTrace === "function") ErrorConstructor.captureStackTrace(this, this.constructor);
53
52
  }
54
53
  };
55
54
  /**
55
+ * Retryable errors - transient failures that may succeed on retry
56
+ * Examples: network timeouts, rate limiting, temporary service unavailability
57
+ *
58
+ * Use this error type when the operation might succeed if retried.
59
+ * The worker will apply exponential backoff and retry the message.
60
+ */
61
+ var RetryableError = class extends HandlerError {
62
+ name = "RetryableError";
63
+ };
64
+ /**
56
65
  * Non-retryable errors - permanent failures that should not be retried
57
66
  * Examples: invalid data, business rule violations, permanent external failures
58
67
  *
59
68
  * Use this error type when retrying would not help - the message will be
60
69
  * immediately sent to the dead letter queue (DLQ) if configured.
61
70
  */
62
- var NonRetryableError = class extends Error {
63
- constructor(message, cause) {
64
- super(message);
65
- this.cause = cause;
66
- this.name = "NonRetryableError";
67
- const ErrorConstructor = Error;
68
- if (typeof ErrorConstructor.captureStackTrace === "function") ErrorConstructor.captureStackTrace(this, this.constructor);
69
- }
71
+ var NonRetryableError = class extends HandlerError {
72
+ name = "NonRetryableError";
70
73
  };
71
74
  /**
72
75
  * Type guard to check if an error is a RetryableError.
@@ -137,7 +140,7 @@ function isNonRetryableError(error) {
137
140
  * ```
138
141
  */
139
142
  function isHandlerError(error) {
140
- return isRetryableError(error) || isNonRetryableError(error);
143
+ return error instanceof HandlerError;
141
144
  }
142
145
  /**
143
146
  * Create a RetryableError with less verbosity.
@@ -218,20 +221,15 @@ function nonRetryable(message, cause) {
218
221
  */
219
222
  function handleError(ctx, error, msg, consumerName, consumer) {
220
223
  if (error instanceof NonRetryableError) {
221
- ctx.logger?.error("Non-retryable error, sending to DLQ immediately", {
222
- consumerName,
223
- errorType: error.name,
224
- error: error.message
225
- });
226
224
  sendToDLQ(ctx, msg, consumer);
227
225
  return okAsync(void 0);
228
226
  }
229
227
  const config = extractQueue(consumer.queue).retry;
230
228
  if (config.mode === "immediate-requeue") return handleErrorImmediateRequeue(ctx, error, msg, consumerName, consumer, config);
231
229
  if (config.mode === "ttl-backoff") return handleErrorTtlBackoff(ctx, error, msg, consumerName, consumer, config);
232
- ctx.logger?.warn("Retry disabled (none mode), sending to DLQ", {
230
+ ctx.logger?.info("Retry disabled (none mode), sending to DLQ", {
233
231
  consumerName,
234
- error: error.message
232
+ queueName: extractQueue(consumer.queue).name
235
233
  });
236
234
  sendToDLQ(ctx, msg, consumer);
237
235
  return okAsync(void 0);
@@ -250,22 +248,20 @@ function handleErrorImmediateRequeue(ctx, error, msg, consumerName, consumer, co
250
248
  const queueName = queue.name;
251
249
  const retryCount = queue.type === "quorum" ? msg.properties.headers?.["x-delivery-count"] ?? 0 : msg.properties.headers?.["x-retry-count"] ?? 0;
252
250
  if (retryCount >= config.maxRetries) {
253
- ctx.logger?.error("Max retries exceeded, sending to DLQ (immediate-requeue mode)", {
251
+ ctx.logger?.info("Max retries exceeded, sending to DLQ (immediate-requeue mode)", {
254
252
  consumerName,
255
253
  queueName,
256
254
  retryCount,
257
- maxRetries: config.maxRetries,
258
- error: error.message
255
+ maxRetries: config.maxRetries
259
256
  });
260
257
  sendToDLQ(ctx, msg, consumer);
261
258
  return okAsync(void 0);
262
259
  }
263
- ctx.logger?.warn("Retrying message (immediate-requeue mode)", {
260
+ ctx.logger?.info("Retrying message (immediate-requeue mode)", {
264
261
  consumerName,
265
262
  queueName,
266
263
  retryCount,
267
- maxRetries: config.maxRetries,
268
- error: error.message
264
+ maxRetries: config.maxRetries
269
265
  });
270
266
  if (queue.type === "quorum") {
271
267
  ctx.amqpClient.nack(msg, false, true);
@@ -316,24 +312,22 @@ function handleErrorTtlBackoff(ctx, error, msg, consumerName, consumer, config)
316
312
  const queueName = extractQueue(queueEntry).name;
317
313
  const retryCount = msg.properties.headers?.["x-retry-count"] ?? 0;
318
314
  if (retryCount >= config.maxRetries) {
319
- ctx.logger?.error("Max retries exceeded, sending to DLQ (ttl-backoff mode)", {
315
+ ctx.logger?.info("Max retries exceeded, sending to DLQ (ttl-backoff mode)", {
320
316
  consumerName,
321
317
  queueName,
322
318
  retryCount,
323
- maxRetries: config.maxRetries,
324
- error: error.message
319
+ maxRetries: config.maxRetries
325
320
  });
326
321
  sendToDLQ(ctx, msg, consumer);
327
322
  return okAsync(void 0);
328
323
  }
329
324
  const delayMs = calculateRetryDelay(retryCount, config);
330
- ctx.logger?.warn("Retrying message (ttl-backoff mode)", {
325
+ ctx.logger?.info("Retrying message (ttl-backoff mode)", {
331
326
  consumerName,
332
327
  queueName,
333
328
  retryCount: retryCount + 1,
334
329
  maxRetries: config.maxRetries,
335
- delayMs,
336
- error: error.message
330
+ delayMs
337
331
  });
338
332
  return publishForRetry(ctx, {
339
333
  msg,
@@ -350,9 +344,9 @@ function handleErrorTtlBackoff(ctx, error, msg, consumerName, consumer, config)
350
344
  */
351
345
  function calculateRetryDelay(retryCount, config) {
352
346
  const { initialDelayMs, maxDelayMs, backoffMultiplier, jitter } = config;
353
- let delay = Math.min(initialDelayMs * Math.pow(backoffMultiplier, retryCount), maxDelayMs);
354
- if (jitter) delay = delay * (.5 + Math.random() * .5);
355
- return Math.floor(delay);
347
+ let delay = initialDelayMs * Math.pow(backoffMultiplier, retryCount);
348
+ if (jitter) delay = delay * (.5 + Math.random());
349
+ return Math.floor(Math.min(delay, maxDelayMs));
356
350
  }
357
351
  /**
358
352
  * Parse message content for republishing.
@@ -384,7 +378,6 @@ function parseMessageContentForRetry(ctx, msg, queueName) {
384
378
  */
385
379
  function publishForRetry(ctx, { msg, exchange, routingKey, queueName, waitQueueName, delayMs, error }) {
386
380
  const newRetryCount = (msg.properties.headers?.["x-retry-count"] ?? 0) + 1;
387
- ctx.amqpClient.ack(msg);
388
381
  const content = parseMessageContentForRetry(ctx, msg, queueName);
389
382
  return ctx.amqpClient.publish(exchange, routingKey, content, {
390
383
  ...msg.properties,
@@ -408,12 +401,21 @@ function publishForRetry(ctx, { msg, exchange, routingKey, queueName, waitQueueN
408
401
  });
409
402
  return err(new TechnicalError("Failed to publish message for retry (write buffer full)"));
410
403
  }
404
+ ctx.amqpClient.ack(msg);
411
405
  ctx.logger?.info("Message published for retry", {
412
406
  queueName,
413
407
  retryCount: newRetryCount,
414
408
  ...delayMs !== void 0 ? { delayMs } : {}
415
409
  });
416
410
  return ok(void 0);
411
+ }).orElse((publishError) => {
412
+ ctx.logger?.error("Publish for retry failed; leaving original un-ack'd for redelivery", {
413
+ queueName,
414
+ retryCount: newRetryCount,
415
+ ...delayMs !== void 0 ? { delayMs } : {},
416
+ error: publishError
417
+ });
418
+ return err(publishError);
417
419
  });
418
420
  }
419
421
  /**
@@ -652,7 +654,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
652
654
  */
653
655
  parseAndValidateMessage(msg, consumer, consumerName) {
654
656
  const context = { consumerName: String(consumerName) };
655
- const parsePayload = decompressBuffer(msg.content, msg.properties.contentEncoding).andThen((buffer) => Result.fromThrowable(() => JSON.parse(buffer.toString()), (error) => new TechnicalError("Failed to parse JSON", error))()).andThen((parsed) => this.validateSchema(consumer.message.payload, parsed, {
657
+ const parsePayload = decompressBuffer(msg.content, msg.properties.contentEncoding).andThen((buffer) => safeJsonParse(buffer, (error) => new TechnicalError("Failed to parse JSON", error))).andThen((parsed) => this.validateSchema(consumer.message.payload, parsed, {
656
658
  ...context,
657
659
  field: "payload"
658
660
  }));
@@ -718,66 +720,110 @@ var TypedAmqpWorker = class TypedAmqpWorker {
718
720
  }).mapErr((error) => new NonRetryableError("Failed to publish RPC response", error)).andThen((published) => published ? ok(void 0) : err(new NonRetryableError("Failed to publish RPC response: channel buffer full"))));
719
721
  }
720
722
  /**
723
+ * Parse and validate the message; on failure, nack(requeue=false) so the
724
+ * queue's DLX (if configured) receives the poison message and bypass the
725
+ * retry pipeline — a malformed payload is deterministic and retrying it
726
+ * would burn the queue's retry budget on a guaranteed failure.
727
+ */
728
+ parseAndValidateOrNack(msg, consumer, name) {
729
+ return this.parseAndValidateMessage(msg, consumer, name).orElse((parseError) => {
730
+ this.amqpClient.nack(msg, false, false);
731
+ return errAsync(parseError);
732
+ });
733
+ }
734
+ /**
735
+ * Invoke the handler and ack the message on success. Returns the handler's
736
+ * response (RPC) or `undefined` (regular consumer). Errors propagate as
737
+ * `HandlerError` for downstream RPC reply publishing or routing via
738
+ * {@link handleError}.
739
+ */
740
+ runHandler(handler, validatedMessage, msg) {
741
+ return handler(validatedMessage, msg);
742
+ }
743
+ /**
744
+ * For RPC handlers, validate and publish the reply on the caller's
745
+ * `replyTo` / `correlationId`. For non-RPC consumers, this is a no-op that
746
+ * resolves to `okAsync(undefined)`.
747
+ */
748
+ publishReplyIfRpc(msg, view, name, handlerResponse) {
749
+ if (!view.isRpc || !view.responseSchema) return okAsync(void 0);
750
+ const queueName = extractQueue(view.consumer.queue).name;
751
+ return this.publishRpcResponse(msg, queueName, name, view.responseSchema, handlerResponse);
752
+ }
753
+ /**
721
754
  * Process a single consumed message: validate, invoke handler, optionally
722
- * publish the RPC response, record telemetry, and handle errors.
755
+ * publish the RPC response, record telemetry, and route errors.
756
+ *
757
+ * The caller-supplied `state` is mutated as the message is ack'd/nack'd so
758
+ * the consume callback's catch-all guard can tell whether a defensive nack
759
+ * is still needed (see {@link consumeSingle}).
760
+ *
761
+ * Success-vs-failure telemetry is data-driven: the chain resolves to
762
+ * `ok(undefined)` only on handler success (and reply-publish success for
763
+ * RPC). Handler failures — even when {@link handleError} routes them
764
+ * successfully to retry/DLQ — are classified as failures for metrics by
765
+ * re-failing the chain with a `TechnicalError` whose `cause` is the
766
+ * original `HandlerError`. The terminal `orTee` unwraps the cause before
767
+ * recording the span exception so traces keep the original
768
+ * `RetryableError` / `NonRetryableError` class as the exception type.
723
769
  */
724
- processMessage(msg, view, name, handler) {
725
- const { consumer, isRpc, responseSchema } = view;
770
+ processMessage(msg, view, name, handler, state) {
771
+ const { consumer } = view;
726
772
  const queueName = extractQueue(consumer.queue).name;
727
773
  const startTime = Date.now();
728
774
  const span = startConsumeSpan(this.telemetry, queueName, String(name), { "messaging.rabbitmq.message.delivery_tag": msg.fields.deliveryTag });
729
- let messageHandled = false;
730
- let firstError;
731
- const inner = this.parseAndValidateMessage(msg, consumer, name).orElse((parseError) => {
732
- firstError = parseError;
775
+ return this.parseAndValidateOrNack(msg, consumer, name).orTee((parseError) => {
733
776
  this.logger?.error("Failed to parse/validate message; sending to DLQ", {
734
777
  consumerName: String(name),
735
778
  queueName,
736
779
  error: parseError
737
780
  });
738
- this.amqpClient.nack(msg, false, false);
739
- return errAsync(parseError);
740
- }).andThen((validatedMessage) => handler(validatedMessage, msg).andThen((handlerResponse) => {
741
- if (isRpc && responseSchema) return this.publishRpcResponse(msg, queueName, name, responseSchema, handlerResponse).map(() => {
742
- this.logger?.info("Message consumed successfully", {
743
- consumerName: String(name),
744
- queueName
745
- });
746
- this.amqpClient.ack(msg);
747
- messageHandled = true;
748
- });
781
+ state.messageHandled = true;
782
+ }).andThen((validatedMessage) => this.runHandler(handler, validatedMessage, msg).andThen((handlerResponse) => this.publishReplyIfRpc(msg, view, name, handlerResponse).andTee(() => {
749
783
  this.logger?.info("Message consumed successfully", {
750
784
  consumerName: String(name),
751
785
  queueName
752
786
  });
753
787
  this.amqpClient.ack(msg);
754
- messageHandled = true;
755
- return okAsync(void 0);
756
- }).orElse((handlerError) => {
788
+ state.messageHandled = true;
789
+ })).orElse((handlerError) => {
757
790
  this.logger?.error("Error processing message", {
758
791
  consumerName: String(name),
759
792
  queueName,
760
793
  errorType: handlerError.name,
794
+ retryCount: msg.properties.headers?.["x-delivery-count"] ?? msg.properties.headers?.["x-retry-count"] ?? 0,
761
795
  error: handlerError.message
762
796
  });
763
- firstError = handlerError;
764
797
  return handleError({
765
798
  amqpClient: this.amqpClient,
766
799
  logger: this.logger
767
- }, handlerError, msg, String(name), consumer);
768
- }));
769
- return new ResultAsync((async () => {
770
- const result = await inner;
771
- const durationMs = Date.now() - startTime;
772
- if (messageHandled) {
800
+ }, handlerError, msg, String(name), consumer).andTee(() => {
801
+ state.messageHandled = true;
802
+ }).andThen(() => errAsync(new TechnicalError(`Handler "${String(name)}" failed: ${handlerError.message}`, handlerError)));
803
+ })).andTee(() => {
804
+ try {
773
805
  endSpanSuccess(span);
774
- recordConsumeMetric(this.telemetry, queueName, String(name), true, durationMs);
775
- } else {
776
- endSpanError(span, result.isErr() ? result.error : firstError ?? /* @__PURE__ */ new Error("Unknown error"));
777
- recordConsumeMetric(this.telemetry, queueName, String(name), false, durationMs);
806
+ recordConsumeMetric(this.telemetry, queueName, String(name), true, Date.now() - startTime);
807
+ } catch (telemetryError) {
808
+ this.logger?.warn("Telemetry recording threw; ignoring", {
809
+ consumerName: String(name),
810
+ queueName,
811
+ error: telemetryError
812
+ });
778
813
  }
779
- return result;
780
- })());
814
+ }).orTee((error) => {
815
+ const reportedError = error.cause instanceof Error ? error.cause : error;
816
+ try {
817
+ endSpanError(span, reportedError);
818
+ recordConsumeMetric(this.telemetry, queueName, String(name), false, Date.now() - startTime);
819
+ } catch (telemetryError) {
820
+ this.logger?.warn("Telemetry recording threw; ignoring", {
821
+ consumerName: String(name),
822
+ queueName,
823
+ error: telemetryError
824
+ });
825
+ }
826
+ });
781
827
  }
782
828
  /**
783
829
  * Consume messages one at a time.
@@ -792,9 +838,18 @@ var TypedAmqpWorker = class TypedAmqpWorker {
792
838
  });
793
839
  return;
794
840
  }
841
+ const state = { messageHandled: false };
795
842
  try {
796
- await this.processMessage(msg, view, name, handler);
843
+ await this.processMessage(msg, view, name, handler, state);
797
844
  } catch (error) {
845
+ if (state.messageHandled) {
846
+ this.logger?.error("Uncaught error in consume callback after message was already handled; not nacking", {
847
+ consumerName: String(name),
848
+ queueName,
849
+ error
850
+ });
851
+ return;
852
+ }
798
853
  this.logger?.error("Uncaught error in consume callback; nacking message", {
799
854
  consumerName: String(name),
800
855
  queueName,
@@ -810,46 +865,61 @@ var TypedAmqpWorker = class TypedAmqpWorker {
810
865
  //#endregion
811
866
  //#region src/handlers.ts
812
867
  /**
813
- * Validate that a consumer exists in the contract
868
+ * Build the list of available handler-target names — every key under
869
+ * `contract.consumers` plus every key under `contract.rpcs`.
814
870
  */
815
- function validateConsumerExists(contract, consumerName) {
871
+ function availableHandlerNames(contract) {
872
+ const consumers = contract.consumers ? Object.keys(contract.consumers) : [];
873
+ const rpcs = contract.rpcs ? Object.keys(contract.rpcs) : [];
874
+ return [...consumers, ...rpcs];
875
+ }
876
+ function formatAvailable(names) {
877
+ return names.length > 0 ? names.join(", ") : "none";
878
+ }
879
+ /**
880
+ * Validate that a name maps to a contract entry — either a `consumers` key
881
+ * or an `rpcs` key. The two name spaces are disjoint by contract definition.
882
+ */
883
+ function validateHandlerTargetExists(contract, name) {
816
884
  const consumers = contract.consumers;
817
- if (!consumers || !(consumerName in consumers)) {
818
- const availableConsumers = consumers ? Object.keys(consumers) : [];
819
- const available = availableConsumers.length > 0 ? availableConsumers.join(", ") : "none";
820
- throw new Error(`Consumer "${consumerName}" not found in contract. Available consumers: ${available}`);
885
+ const rpcs = contract.rpcs;
886
+ if (!(!!consumers && Object.hasOwn(consumers, name)) && !(!!rpcs && Object.hasOwn(rpcs, name))) {
887
+ const available = formatAvailable(availableHandlerNames(contract));
888
+ throw new Error(`Handler target "${name}" not found in contract. Available consumers and RPCs: ${available}`);
821
889
  }
822
890
  }
823
891
  /**
824
- * Validate that all handlers reference valid consumers
892
+ * Validate that every key in `handlers` maps to a contract entry —
893
+ * either a `consumers` key or an `rpcs` key.
825
894
  */
826
895
  function validateHandlers(contract, handlers) {
827
- const consumers = contract.consumers;
828
- const availableConsumers = Object.keys(consumers ?? {});
829
- const availableConsumerNames = availableConsumers.length > 0 ? availableConsumers.join(", ") : "none";
830
- for (const handlerName of Object.keys(handlers)) if (!consumers || !(handlerName in consumers)) throw new Error(`Consumer "${handlerName}" not found in contract. Available consumers: ${availableConsumerNames}`);
896
+ for (const handlerName of Object.keys(handlers)) validateHandlerTargetExists(contract, handlerName);
831
897
  }
832
- function defineHandler(contract, consumerName, handler, options) {
833
- validateConsumerExists(contract, String(consumerName));
898
+ function defineHandler(contract, name, handler, options) {
899
+ validateHandlerTargetExists(contract, String(name));
834
900
  if (options) return [handler, options];
835
901
  return handler;
836
902
  }
837
903
  /**
838
- * Define multiple type-safe handlers for consumers in a contract.
904
+ * Define multiple type-safe handlers for consumers and RPCs in a contract.
839
905
  *
840
- * **Recommended:** This function creates handlers that return `ResultAsync<void, HandlerError>`,
841
- * providing explicit error handling and better control over retry behavior.
906
+ * **Recommended:** This function creates handlers that return
907
+ * `ResultAsync<void, HandlerError>` (consumers) or
908
+ * `ResultAsync<TResponse, HandlerError>` (RPCs), providing explicit error
909
+ * handling and better control over retry behavior.
910
+ *
911
+ * The handlers object must contain exactly one entry per `consumers` and
912
+ * `rpcs` key in the contract — see {@link WorkerInferHandlers}.
842
913
  *
843
914
  * @template TContract - The contract definition type
844
- * @param contract - The contract definition containing the consumers
845
- * @param handlers - An object with handler functions for each consumer
915
+ * @param contract - The contract definition containing the consumers and RPCs
916
+ * @param handlers - An object with handler functions for each consumer and RPC
846
917
  * @returns A type-safe handlers object that can be used with TypedAmqpWorker
847
918
  *
848
919
  * @example
849
920
  * ```typescript
850
921
  * import { defineHandlers, RetryableError } from '@amqp-contract/worker';
851
- * import { ResultAsync } from 'neverthrow';
852
- * import { orderContract } from './contract';
922
+ * import { okAsync, ResultAsync } from 'neverthrow';
853
923
  *
854
924
  * const handlers = defineHandlers(orderContract, {
855
925
  * processOrder: ({ payload }) =>
@@ -857,11 +927,7 @@ function defineHandler(contract, consumerName, handler, options) {
857
927
  * processPayment(payload),
858
928
  * (error) => new RetryableError('Payment failed', error),
859
929
  * ).map(() => undefined),
860
- * notifyOrder: ({ payload }) =>
861
- * ResultAsync.fromPromise(
862
- * sendNotification(payload),
863
- * (error) => new RetryableError('Notification failed', error),
864
- * ).map(() => undefined),
930
+ * calculate: ({ payload }) => okAsync({ sum: payload.a + payload.b }),
865
931
  * });
866
932
  * ```
867
933
  */
@@ -870,6 +936,6 @@ function defineHandlers(contract, handlers) {
870
936
  return handlers;
871
937
  }
872
938
  //#endregion
873
- export { MessageValidationError, NonRetryableError, RetryableError, TypedAmqpWorker, defineHandler, defineHandlers, isHandlerError, isNonRetryableError, isRetryableError, nonRetryable, retryable };
939
+ export { HandlerError, MessageValidationError, NonRetryableError, RetryableError, TypedAmqpWorker, defineHandler, defineHandlers, isHandlerError, isNonRetryableError, isRetryableError, nonRetryable, retryable };
874
940
 
875
941
  //# sourceMappingURL=index.mjs.map