@amqp-contract/worker 0.23.0 → 0.24.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
2
  import { AmqpClient, MessageValidationError, TechnicalError, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, startConsumeSpan } from "@amqp-contract/core";
3
- import { Future, Result } from "@swan-io/boxed";
3
+ import { Result, 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
@@ -21,17 +21,17 @@ function isSupportedEncoding(encoding) {
21
21
  *
22
22
  * @param buffer - The buffer to decompress
23
23
  * @param contentEncoding - The content-encoding header value (e.g., 'gzip', 'deflate')
24
- * @returns A Future with the decompressed buffer or a TechnicalError
24
+ * @returns A ResultAsync resolving to the decompressed buffer or a TechnicalError
25
25
  *
26
26
  * @internal
27
27
  */
28
28
  function decompressBuffer(buffer, contentEncoding) {
29
- if (!contentEncoding) return Future.value(Result.Ok(buffer));
29
+ if (!contentEncoding) return okAsync(buffer);
30
30
  const normalizedEncoding = contentEncoding.toLowerCase();
31
- if (!isSupportedEncoding(normalizedEncoding)) return Future.value(Result.Error(new TechnicalError(`Unsupported content-encoding: "${contentEncoding}". Supported encodings are: ${SUPPORTED_ENCODINGS.join(", ")}. Please check your publisher configuration.`)));
31
+ if (!isSupportedEncoding(normalizedEncoding)) return errAsync(new TechnicalError(`Unsupported content-encoding: "${contentEncoding}". Supported encodings are: ${SUPPORTED_ENCODINGS.join(", ")}. Please check your publisher configuration.`));
32
32
  switch (normalizedEncoding) {
33
- case "gzip": return Future.fromPromise(gunzipAsync(buffer)).mapError((error) => new TechnicalError("Failed to decompress gzip", error));
34
- case "deflate": return Future.fromPromise(inflateAsync(buffer)).mapError((error) => new TechnicalError("Failed to decompress deflate", error));
33
+ case "gzip": return ResultAsync.fromPromise(gunzipAsync(buffer), (error) => new TechnicalError("Failed to decompress gzip", error));
34
+ case "deflate": return ResultAsync.fromPromise(inflateAsync(buffer), (error) => new TechnicalError("Failed to decompress deflate", error));
35
35
  }
36
36
  }
37
37
  //#endregion
@@ -152,15 +152,16 @@ function isHandlerError(error) {
152
152
  * @example
153
153
  * ```typescript
154
154
  * import { retryable } from '@amqp-contract/worker';
155
- * import { Future, Result } from '@swan-io/boxed';
155
+ * import { ResultAsync } from 'neverthrow';
156
156
  *
157
157
  * const handler = ({ payload }) =>
158
- * Future.fromPromise(processPayment(payload))
159
- * .mapOk(() => undefined)
160
- * .mapError((e) => retryable('Payment service unavailable', e));
158
+ * ResultAsync.fromPromise(
159
+ * processPayment(payload),
160
+ * (e) => retryable('Payment service unavailable', e),
161
+ * ).map(() => undefined);
161
162
  *
162
163
  * // Equivalent to:
163
- * // .mapError((e) => new RetryableError('Payment service unavailable', e));
164
+ * // ResultAsync.fromPromise(processPayment(payload), (e) => new RetryableError('...', e))
164
165
  * ```
165
166
  */
166
167
  function retryable(message, cause) {
@@ -179,17 +180,17 @@ function retryable(message, cause) {
179
180
  * @example
180
181
  * ```typescript
181
182
  * import { nonRetryable } from '@amqp-contract/worker';
182
- * import { Future, Result } from '@swan-io/boxed';
183
+ * import { errAsync, okAsync } from 'neverthrow';
183
184
  *
184
185
  * const handler = ({ payload }) => {
185
186
  * if (!isValidPayload(payload)) {
186
- * return Future.value(Result.Error(nonRetryable('Invalid payload format')));
187
+ * return errAsync(nonRetryable('Invalid payload format'));
187
188
  * }
188
- * return Future.value(Result.Ok(undefined));
189
+ * return okAsync(undefined);
189
190
  * };
190
191
  *
191
192
  * // Equivalent to:
192
- * // return Future.value(Result.Error(new NonRetryableError('Invalid payload format')));
193
+ * // return errAsync(new NonRetryableError('Invalid payload format'));
193
194
  * ```
194
195
  */
195
196
  function nonRetryable(message, cause) {
@@ -223,7 +224,7 @@ function handleError(ctx, error, msg, consumerName, consumer) {
223
224
  error: error.message
224
225
  });
225
226
  sendToDLQ(ctx, msg, consumer);
226
- return Future.value(Result.Ok(void 0));
227
+ return okAsync(void 0);
227
228
  }
228
229
  const config = extractQueue(consumer.queue).retry;
229
230
  if (config.mode === "immediate-requeue") return handleErrorImmediateRequeue(ctx, error, msg, consumerName, consumer, config);
@@ -233,7 +234,7 @@ function handleError(ctx, error, msg, consumerName, consumer) {
233
234
  error: error.message
234
235
  });
235
236
  sendToDLQ(ctx, msg, consumer);
236
- return Future.value(Result.Ok(void 0));
237
+ return okAsync(void 0);
237
238
  }
238
239
  /**
239
240
  * Handle error by requeuing immediately.
@@ -257,7 +258,7 @@ function handleErrorImmediateRequeue(ctx, error, msg, consumerName, consumer, co
257
258
  error: error.message
258
259
  });
259
260
  sendToDLQ(ctx, msg, consumer);
260
- return Future.value(Result.Ok(void 0));
261
+ return okAsync(void 0);
261
262
  }
262
263
  ctx.logger?.warn("Retrying message (immediate-requeue mode)", {
263
264
  consumerName,
@@ -268,7 +269,7 @@ function handleErrorImmediateRequeue(ctx, error, msg, consumerName, consumer, co
268
269
  });
269
270
  if (queue.type === "quorum") {
270
271
  ctx.amqpClient.nack(msg, false, true);
271
- return Future.value(Result.Ok(void 0));
272
+ return okAsync(void 0);
272
273
  } else return publishForRetry(ctx, {
273
274
  msg,
274
275
  exchange: msg.fields.exchange,
@@ -309,7 +310,7 @@ function handleErrorTtlBackoff(ctx, error, msg, consumerName, consumer, config)
309
310
  consumerName,
310
311
  queueName: consumer.queue.name
311
312
  });
312
- return Future.value(Result.Error(new TechnicalError("Queue does not have TTL-backoff infrastructure")));
313
+ return errAsync(new TechnicalError("Queue does not have TTL-backoff infrastructure"));
313
314
  }
314
315
  const queueEntry = consumer.queue;
315
316
  const queueName = extractQueue(queueEntry).name;
@@ -323,7 +324,7 @@ function handleErrorTtlBackoff(ctx, error, msg, consumerName, consumer, config)
323
324
  error: error.message
324
325
  });
325
326
  sendToDLQ(ctx, msg, consumer);
326
- return Future.value(Result.Ok(void 0));
327
+ return okAsync(void 0);
327
328
  }
328
329
  const delayMs = calculateRetryDelay(retryCount, config);
329
330
  ctx.logger?.warn("Retrying message (ttl-backoff mode)", {
@@ -370,10 +371,10 @@ function parseMessageContentForRetry(ctx, msg, queueName) {
370
371
  if (!(contentType === void 0 || contentType === "application/json" || contentType.startsWith("application/json;") || contentType.endsWith("+json"))) return msg.content;
371
372
  try {
372
373
  return JSON.parse(msg.content.toString());
373
- } catch (err) {
374
+ } catch (parseErr) {
374
375
  ctx.logger?.warn("Failed to parse JSON message for retry, using original buffer", {
375
376
  queueName,
376
- error: err
377
+ error: parseErr
377
378
  });
378
379
  return msg.content;
379
380
  }
@@ -398,21 +399,21 @@ function publishForRetry(ctx, { msg, exchange, routingKey, queueName, waitQueueN
398
399
  "x-retry-queue": queueName
399
400
  } : {}
400
401
  }
401
- }).mapOkToResult((published) => {
402
+ }).andThen((published) => {
402
403
  if (!published) {
403
404
  ctx.logger?.error("Failed to publish message for retry (write buffer full)", {
404
405
  queueName,
405
406
  retryCount: newRetryCount,
406
407
  ...delayMs !== void 0 ? { delayMs } : {}
407
408
  });
408
- return Result.Error(new TechnicalError("Failed to publish message for retry (write buffer full)"));
409
+ return err(new TechnicalError("Failed to publish message for retry (write buffer full)"));
409
410
  }
410
411
  ctx.logger?.info("Message published for retry", {
411
412
  queueName,
412
413
  retryCount: newRetryCount,
413
414
  ...delayMs !== void 0 ? { delayMs } : {}
414
415
  });
415
- return Result.Ok(void 0);
416
+ return ok(void 0);
416
417
  });
417
418
  }
418
419
  /**
@@ -449,6 +450,7 @@ function isHandlerTuple(entry) {
449
450
  * ```typescript
450
451
  * import { TypedAmqpWorker } from '@amqp-contract/worker';
451
452
  * import { defineQueue, defineMessage, defineContract, defineConsumer } from '@amqp-contract/contract';
453
+ * import { okAsync } from 'neverthrow';
452
454
  * import { z } from 'zod';
453
455
  *
454
456
  * const orderQueue = defineQueue('order-processing');
@@ -463,19 +465,22 @@ function isHandlerTuple(entry) {
463
465
  * }
464
466
  * });
465
467
  *
466
- * const worker = await TypedAmqpWorker.create({
468
+ * const result = await TypedAmqpWorker.create({
467
469
  * contract,
468
470
  * handlers: {
469
- * processOrder: async (message) => {
470
- * console.log('Processing order', message.orderId);
471
- * // Process the order...
472
- * }
471
+ * processOrder: ({ payload }) => {
472
+ * console.log('Processing order', payload.orderId);
473
+ * return okAsync(undefined);
474
+ * },
473
475
  * },
474
- * urls: ['amqp://localhost']
475
- * }).resultToPromise();
476
+ * urls: ['amqp://localhost'],
477
+ * });
478
+ *
479
+ * if (result.isErr()) throw result.error;
480
+ * const worker = result.value;
476
481
  *
477
482
  * // Close when done
478
- * await worker.close().resultToPromise();
483
+ * await worker.close();
479
484
  * ```
480
485
  */
481
486
  var TypedAmqpWorker = class TypedAmqpWorker {
@@ -551,18 +556,17 @@ var TypedAmqpWorker = class TypedAmqpWorker {
551
556
  * Connections are automatically shared across clients and workers with the same
552
557
  * URLs and connection options, following RabbitMQ best practices.
553
558
  *
554
- * @param options - Configuration options for the worker
555
- * @returns A Future that resolves to a Result containing the worker or an error
559
+ * @returns A ResultAsync that resolves to the worker or a TechnicalError.
556
560
  *
557
561
  * @example
558
562
  * ```typescript
559
- * const worker = await TypedAmqpWorker.create({
563
+ * const result = await TypedAmqpWorker.create({
560
564
  * contract: myContract,
561
565
  * handlers: {
562
- * processOrder: async ({ payload }) => console.log('Order:', payload.orderId)
566
+ * processOrder: ({ payload }) => okAsync(undefined),
563
567
  * },
564
- * urls: ['amqp://localhost']
565
- * }).resultToPromise();
568
+ * urls: ['amqp://localhost'],
569
+ * });
566
570
  * ```
567
571
  */
568
572
  static create({ contract, handlers, urls, connectionOptions, defaultConsumerOptions, logger, telemetry, connectTimeoutMs }) {
@@ -571,12 +575,14 @@ var TypedAmqpWorker = class TypedAmqpWorker {
571
575
  connectionOptions,
572
576
  connectTimeoutMs
573
577
  }), handlers, defaultConsumerOptions ?? {}, logger, telemetry);
574
- return worker.waitForConnectionReady().flatMapOk(() => worker.consumeAll()).flatMap((result) => result.match({
575
- Ok: () => Future.value(Result.Ok(worker)),
576
- Error: (error) => worker.close().tapError((closeError) => {
577
- logger?.warn("Failed to close worker after setup failure", { error: closeError });
578
- }).map(() => Result.Error(error))
579
- }));
578
+ const setup = worker.waitForConnectionReady().andThen(() => worker.consumeAll());
579
+ return new ResultAsync((async () => {
580
+ const setupResult = await setup;
581
+ if (setupResult.isOk()) return ok(worker);
582
+ const closeResult = await worker.close();
583
+ if (closeResult.isErr()) logger?.warn("Failed to close worker after setup failure", { error: closeResult.error });
584
+ return err(setupResult.error);
585
+ })());
580
586
  }
581
587
  /**
582
588
  * Close the AMQP channel and connection.
@@ -584,26 +590,25 @@ var TypedAmqpWorker = class TypedAmqpWorker {
584
590
  * This gracefully closes the connection to the AMQP broker,
585
591
  * stopping all message consumption and cleaning up resources.
586
592
  *
587
- * @returns A Future that resolves to a Result indicating success or failure
588
- *
589
593
  * @example
590
594
  * ```typescript
591
- * const closeResult = await worker.close().resultToPromise();
595
+ * const closeResult = await worker.close();
592
596
  * if (closeResult.isOk()) {
593
597
  * console.log('Worker closed successfully');
594
598
  * }
595
599
  * ```
596
600
  */
597
601
  close() {
598
- return Future.all(Array.from(this.consumerTags).map((consumerTag) => this.amqpClient.cancel(consumerTag).mapErrorToResult((error) => {
602
+ const cancellations = Array.from(this.consumerTags).map((consumerTag) => this.amqpClient.cancel(consumerTag).orElse((error) => {
599
603
  this.logger?.warn("Failed to cancel consumer during close", {
600
604
  consumerTag,
601
605
  error
602
606
  });
603
- return Result.Ok(void 0);
604
- }))).map(Result.all).tapOk(() => {
607
+ return ok(void 0);
608
+ }));
609
+ return ResultAsync.combine(cancellations).andTee(() => {
605
610
  this.consumerTags.clear();
606
- }).flatMapOk(() => this.amqpClient.close()).mapOk(() => void 0);
611
+ }).andThen(() => this.amqpClient.close()).map(() => void 0);
607
612
  }
608
613
  /**
609
614
  * Start consuming for every entry in `contract.consumers` and `contract.rpcs`.
@@ -612,7 +617,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
612
617
  const consumerNames = Object.keys(this.contract.consumers ?? {});
613
618
  const rpcNames = Object.keys(this.contract.rpcs ?? {});
614
619
  const allNames = [...consumerNames, ...rpcNames];
615
- return Future.all(allNames.map((name) => this.consume(name))).map(Result.all).mapOk(() => void 0);
620
+ return ResultAsync.combine(allNames.map((name) => this.consume(name))).map(() => void 0);
616
621
  }
617
622
  waitForConnectionReady() {
618
623
  return this.amqpClient.waitForConnect();
@@ -633,9 +638,9 @@ var TypedAmqpWorker = class TypedAmqpWorker {
633
638
  validateSchema(schema, data, context) {
634
639
  const rawValidation = schema["~standard"].validate(data);
635
640
  const validationPromise = rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation);
636
- return Future.fromPromise(validationPromise).mapError((error) => new TechnicalError(`Error validating ${context.field}`, error)).mapOkToResult((result) => {
637
- if (result.issues) return Result.Error(new TechnicalError(`${context.field} validation failed`, new MessageValidationError(context.consumerName, result.issues)));
638
- return Result.Ok(result.value);
641
+ return ResultAsync.fromPromise(validationPromise, (error) => new TechnicalError(`Error validating ${context.field}`, error)).andThen((result) => {
642
+ if (result.issues) return err(new TechnicalError(`${context.field} validation failed`, new MessageValidationError(context.consumerName, result.issues)));
643
+ return ok(result.value);
639
644
  });
640
645
  }
641
646
  /**
@@ -647,18 +652,18 @@ var TypedAmqpWorker = class TypedAmqpWorker {
647
652
  */
648
653
  parseAndValidateMessage(msg, consumer, consumerName) {
649
654
  const context = { consumerName: String(consumerName) };
650
- const parsePayload = decompressBuffer(msg.content, msg.properties.contentEncoding).mapErrorToResult((error) => Result.Error(new TechnicalError("Failed to decompress message", error))).mapOkToResult((buffer) => Result.fromExecution(() => JSON.parse(buffer.toString())).mapError((error) => new TechnicalError("Failed to parse JSON", error))).flatMapOk((parsed) => this.validateSchema(consumer.message.payload, parsed, {
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, {
651
656
  ...context,
652
657
  field: "payload"
653
658
  }));
654
659
  const parseHeaders = consumer.message.headers ? this.validateSchema(consumer.message.headers, msg.properties.headers ?? {}, {
655
660
  ...context,
656
661
  field: "headers"
657
- }) : Future.value(Result.Ok(void 0));
658
- return Future.allFromDict({
659
- payload: parsePayload,
660
- headers: parseHeaders
661
- }).map(Result.allFromDict);
662
+ }) : okAsync(void 0);
663
+ return ResultAsync.combine([parsePayload, parseHeaders]).map(([payload, headers]) => ({
664
+ payload,
665
+ headers
666
+ }));
662
667
  }
663
668
  /**
664
669
  * Validate an RPC handler's response and publish it back to the caller's reply
@@ -687,7 +692,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
687
692
  rpcName: String(rpcName),
688
693
  queueName
689
694
  });
690
- return Future.value(Result.Error(new NonRetryableError(`RPC "${String(rpcName)}" received a message without replyTo; cannot deliver response`)));
695
+ return errAsync(new NonRetryableError(`RPC "${String(rpcName)}" received a message without replyTo; cannot deliver response`));
691
696
  }
692
697
  if (typeof correlationId !== "string" || correlationId.length === 0) {
693
698
  this.logger?.error("RPC handler returned a response but the incoming message has no correlationId", {
@@ -695,22 +700,22 @@ var TypedAmqpWorker = class TypedAmqpWorker {
695
700
  queueName,
696
701
  replyTo
697
702
  });
698
- return Future.value(Result.Error(new NonRetryableError(`RPC "${String(rpcName)}" received a message without correlationId; cannot deliver response`)));
703
+ return errAsync(new NonRetryableError(`RPC "${String(rpcName)}" received a message without correlationId; cannot deliver response`));
699
704
  }
700
705
  let rawValidation;
701
706
  try {
702
707
  rawValidation = responseSchema["~standard"].validate(response);
703
708
  } catch (error) {
704
- return Future.value(Result.Error(new NonRetryableError("RPC response schema validation threw", error)));
709
+ return errAsync(new NonRetryableError("RPC response schema validation threw", error));
705
710
  }
706
711
  const validationPromise = rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation);
707
- return Future.fromPromise(validationPromise).mapError((error) => new NonRetryableError("RPC response schema validation threw", error)).mapOkToResult((validation) => {
708
- if (validation.issues) return Result.Error(new NonRetryableError(`RPC response for "${String(rpcName)}" failed schema validation`, new MessageValidationError(String(rpcName), validation.issues)));
709
- return Result.Ok(validation.value);
710
- }).flatMapOk((validatedResponse) => this.amqpClient.publish("", replyTo, validatedResponse, {
712
+ return ResultAsync.fromPromise(validationPromise, (error) => new NonRetryableError("RPC response schema validation threw", error)).andThen((validation) => {
713
+ if (validation.issues) return err(new NonRetryableError(`RPC response for "${String(rpcName)}" failed schema validation`, new MessageValidationError(String(rpcName), validation.issues)));
714
+ return ok(validation.value);
715
+ }).andThen((validatedResponse) => this.amqpClient.publish("", replyTo, validatedResponse, {
711
716
  correlationId,
712
717
  contentType: "application/json"
713
- }).mapErrorToResult((error) => Result.Error(new NonRetryableError("Failed to publish RPC response", error))).mapOkToResult((published) => published ? Result.Ok(void 0) : Result.Error(new NonRetryableError("Failed to publish RPC response: channel buffer full"))));
718
+ }).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"))));
714
719
  }
715
720
  /**
716
721
  * Process a single consumed message: validate, invoke handler, optionally
@@ -723,58 +728,56 @@ var TypedAmqpWorker = class TypedAmqpWorker {
723
728
  const span = startConsumeSpan(this.telemetry, queueName, String(name), { "messaging.rabbitmq.message.delivery_tag": msg.fields.deliveryTag });
724
729
  let messageHandled = false;
725
730
  let firstError;
726
- return this.parseAndValidateMessage(msg, consumer, name).flatMap((parseResult) => parseResult.match({
727
- Ok: (validatedMessage) => handler(validatedMessage, msg).flatMapOk((handlerResponse) => {
728
- if (isRpc && responseSchema) return this.publishRpcResponse(msg, queueName, name, responseSchema, handlerResponse).flatMapOk(() => {
729
- this.logger?.info("Message consumed successfully", {
730
- consumerName: String(name),
731
- queueName
732
- });
733
- this.amqpClient.ack(msg);
734
- messageHandled = true;
735
- return Future.value(Result.Ok(void 0));
736
- });
731
+ const inner = this.parseAndValidateMessage(msg, consumer, name).orElse((parseError) => {
732
+ firstError = parseError;
733
+ this.logger?.error("Failed to parse/validate message; sending to DLQ", {
734
+ consumerName: String(name),
735
+ queueName,
736
+ error: parseError
737
+ });
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(() => {
737
742
  this.logger?.info("Message consumed successfully", {
738
743
  consumerName: String(name),
739
744
  queueName
740
745
  });
741
746
  this.amqpClient.ack(msg);
742
747
  messageHandled = true;
743
- return Future.value(Result.Ok(void 0));
744
- }).flatMapError((handlerError) => {
745
- this.logger?.error("Error processing message", {
746
- consumerName: String(name),
747
- queueName,
748
- errorType: handlerError.name,
749
- error: handlerError.message
750
- });
751
- firstError = handlerError;
752
- return handleError({
753
- amqpClient: this.amqpClient,
754
- logger: this.logger
755
- }, handlerError, msg, String(name), consumer);
756
- }),
757
- Error: (parseError) => {
758
- firstError = parseError;
759
- this.logger?.error("Failed to parse/validate message; sending to DLQ", {
760
- consumerName: String(name),
761
- queueName,
762
- error: parseError
763
- });
764
- this.amqpClient.nack(msg, false, false);
765
- return Future.value(Result.Error(parseError));
766
- }
767
- })).map((result) => {
748
+ });
749
+ this.logger?.info("Message consumed successfully", {
750
+ consumerName: String(name),
751
+ queueName
752
+ });
753
+ this.amqpClient.ack(msg);
754
+ messageHandled = true;
755
+ return okAsync(void 0);
756
+ }).orElse((handlerError) => {
757
+ this.logger?.error("Error processing message", {
758
+ consumerName: String(name),
759
+ queueName,
760
+ errorType: handlerError.name,
761
+ error: handlerError.message
762
+ });
763
+ firstError = handlerError;
764
+ return handleError({
765
+ amqpClient: this.amqpClient,
766
+ logger: this.logger
767
+ }, handlerError, msg, String(name), consumer);
768
+ }));
769
+ return new ResultAsync((async () => {
770
+ const result = await inner;
768
771
  const durationMs = Date.now() - startTime;
769
772
  if (messageHandled) {
770
773
  endSpanSuccess(span);
771
774
  recordConsumeMetric(this.telemetry, queueName, String(name), true, durationMs);
772
775
  } else {
773
- endSpanError(span, result.isError() ? result.error : firstError ?? /* @__PURE__ */ new Error("Unknown error"));
776
+ endSpanError(span, result.isErr() ? result.error : firstError ?? /* @__PURE__ */ new Error("Unknown error"));
774
777
  recordConsumeMetric(this.telemetry, queueName, String(name), false, durationMs);
775
778
  }
776
779
  return result;
777
- });
780
+ })());
778
781
  }
779
782
  /**
780
783
  * Consume messages one at a time.
@@ -790,7 +793,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
790
793
  return;
791
794
  }
792
795
  try {
793
- await this.processMessage(msg, view, name, handler).toPromise();
796
+ await this.processMessage(msg, view, name, handler);
794
797
  } catch (error) {
795
798
  this.logger?.error("Uncaught error in consume callback; nacking message", {
796
799
  consumerName: String(name),
@@ -799,9 +802,9 @@ var TypedAmqpWorker = class TypedAmqpWorker {
799
802
  });
800
803
  this.amqpClient.nack(msg, false, false);
801
804
  }
802
- }, this.consumerOptions[name]).tapOk((consumerTag) => {
805
+ }, this.consumerOptions[name]).andTee((consumerTag) => {
803
806
  this.consumerTags.add(consumerTag);
804
- }).mapError((error) => new TechnicalError(`Failed to start consuming for "${String(name)}"`, error)).mapOk(() => void 0);
807
+ }).map(() => void 0).mapErr((error) => new TechnicalError(`Failed to start consuming for "${String(name)}"`, error));
805
808
  }
806
809
  };
807
810
  //#endregion
@@ -834,7 +837,7 @@ function defineHandler(contract, consumerName, handler, options) {
834
837
  /**
835
838
  * Define multiple type-safe handlers for consumers in a contract.
836
839
  *
837
- * **Recommended:** This function creates handlers that return `Future<Result<void, HandlerError>>`,
840
+ * **Recommended:** This function creates handlers that return `ResultAsync<void, HandlerError>`,
838
841
  * providing explicit error handling and better control over retry behavior.
839
842
  *
840
843
  * @template TContract - The contract definition type
@@ -845,18 +848,20 @@ function defineHandler(contract, consumerName, handler, options) {
845
848
  * @example
846
849
  * ```typescript
847
850
  * import { defineHandlers, RetryableError } from '@amqp-contract/worker';
848
- * import { Future } from '@swan-io/boxed';
851
+ * import { ResultAsync } from 'neverthrow';
849
852
  * import { orderContract } from './contract';
850
853
  *
851
854
  * const handlers = defineHandlers(orderContract, {
852
855
  * processOrder: ({ payload }) =>
853
- * Future.fromPromise(processPayment(payload))
854
- * .mapOk(() => undefined)
855
- * .mapError((error) => new RetryableError('Payment failed', error)),
856
+ * ResultAsync.fromPromise(
857
+ * processPayment(payload),
858
+ * (error) => new RetryableError('Payment failed', error),
859
+ * ).map(() => undefined),
856
860
  * notifyOrder: ({ payload }) =>
857
- * Future.fromPromise(sendNotification(payload))
858
- * .mapOk(() => undefined)
859
- * .mapError((error) => new RetryableError('Notification failed', error)),
861
+ * ResultAsync.fromPromise(
862
+ * sendNotification(payload),
863
+ * (error) => new RetryableError('Notification failed', error),
864
+ * ).map(() => undefined),
860
865
  * });
861
866
  * ```
862
867
  */