@amqp-contract/worker 0.10.0 → 0.11.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
@@ -30,8 +30,9 @@ pnpm add @amqp-contract/worker
30
30
  ### Basic Usage
31
31
 
32
32
  ```typescript
33
- import { TypedAmqpWorker } from "@amqp-contract/worker";
33
+ import { TypedAmqpWorker, RetryableError } from "@amqp-contract/worker";
34
34
  import type { Logger } from "@amqp-contract/core";
35
+ import { Future } from "@swan-io/boxed";
35
36
  import { contract } from "./contract";
36
37
 
37
38
  // Optional: Create a logger implementation
@@ -46,14 +47,13 @@ const logger: Logger = {
46
47
  const worker = await TypedAmqpWorker.create({
47
48
  contract,
48
49
  handlers: {
49
- processOrder: async (message) => {
50
- console.log("Processing order:", message.orderId);
50
+ processOrder: ({ payload }) => {
51
+ console.log("Processing order:", payload.orderId);
51
52
 
52
53
  // Your business logic here
53
- await processPayment(message);
54
- await updateInventory(message);
55
-
56
- // If an exception is thrown, the message is automatically requeued
54
+ return Future.fromPromise(Promise.all([processPayment(payload), updateInventory(payload)]))
55
+ .mapOk(() => undefined)
56
+ .mapError((error) => new RetryableError("Order processing failed", error));
57
57
  },
58
58
  },
59
59
  urls: ["amqp://localhost"],
@@ -72,25 +72,43 @@ For advanced features like prefetch configuration, batch processing, and **autom
72
72
 
73
73
  #### Retry with Exponential Backoff
74
74
 
75
- Enable automatic retry for failed messages:
75
+ Retry is configured at the queue level in your contract definition. Add `retry` to your queue definition:
76
+
77
+ ```typescript
78
+ import { defineQueue, defineExchange, defineContract } from "@amqp-contract/contract";
79
+
80
+ const dlx = defineExchange("orders-dlx", "topic", { durable: true });
81
+
82
+ // Configure retry at queue level
83
+ const orderQueue = defineQueue("order-processing", {
84
+ deadLetter: { exchange: dlx },
85
+ retry: {
86
+ mode: "ttl-backoff",
87
+ maxRetries: 3, // Retry up to 3 times (default: 3)
88
+ initialDelayMs: 1000, // Start with 1 second delay (default: 1000)
89
+ maxDelayMs: 30000, // Max 30 seconds between retries (default: 30000)
90
+ backoffMultiplier: 2, // Double the delay each time (default: 2)
91
+ jitter: true, // Add randomness to prevent thundering herd (default: true)
92
+ },
93
+ });
94
+ ```
95
+
96
+ Then use `RetryableError` in your handlers:
76
97
 
77
98
  ```typescript
99
+ import { TypedAmqpWorker, RetryableError } from "@amqp-contract/worker";
100
+ import { Future } from "@swan-io/boxed";
101
+
78
102
  const worker = await TypedAmqpWorker.create({
79
103
  contract,
80
104
  handlers: {
81
- processOrder: async (message) => {
82
- // If this throws, message is automatically retried with exponential backoff
83
- await processPayment(message);
84
- },
105
+ processOrder: ({ payload }) =>
106
+ // If this fails with RetryableError, message is automatically retried
107
+ Future.fromPromise(processPayment(payload))
108
+ .mapOk(() => undefined)
109
+ .mapError((error) => new RetryableError("Payment failed", error)),
85
110
  },
86
111
  urls: ["amqp://localhost"],
87
- retry: {
88
- maxRetries: 3, // Retry up to 3 times
89
- initialDelayMs: 1000, // Start with 1 second delay
90
- maxDelayMs: 30000, // Max 30 seconds between retries
91
- backoffMultiplier: 2, // Double the delay each time
92
- jitter: true, // Add randomness to prevent thundering herd
93
- },
94
112
  });
95
113
  ```
96
114
 
@@ -102,22 +120,24 @@ You can define handlers outside of the worker creation using `defineHandler` and
102
120
 
103
121
  ## Error Handling
104
122
 
105
- Worker handlers use standard Promise-based async/await pattern:
123
+ Worker handlers return `Future<Result<void, HandlerError>>` for explicit error handling:
106
124
 
107
125
  ```typescript
126
+ import { RetryableError, NonRetryableError } from "@amqp-contract/worker";
127
+ import { Future, Result } from "@swan-io/boxed";
128
+
108
129
  handlers: {
109
- processOrder: async (message) => {
110
- // Standard async/await - no Result wrapping needed
111
- try {
112
- await process(message);
113
- // Message acknowledged automatically on success
114
- } catch (error) {
115
- // Exception automatically caught by worker
116
- // With retry configured: message is retried with exponential backoff
117
- // Without retry: message is immediately requeued
118
- throw error;
130
+ processOrder: ({ payload }) => {
131
+ // Validation errors - non-retryable
132
+ if (payload.amount <= 0) {
133
+ return Future.value(Result.Error(new NonRetryableError("Invalid amount")));
119
134
  }
120
- };
135
+
136
+ // Transient errors - retryable
137
+ return Future.fromPromise(process(payload))
138
+ .mapOk(() => undefined)
139
+ .mapError((error) => new RetryableError("Processing failed", error));
140
+ },
121
141
  }
122
142
  ```
123
143
 
@@ -127,9 +147,8 @@ Worker defines error classes:
127
147
 
128
148
  - `TechnicalError` - Runtime failures (parsing, processing)
129
149
  - `MessageValidationError` - Message fails schema validation
130
- - `RetryableError` - Optional error class for explicit retry signaling (all errors are retryable by default when retry is configured)
131
-
132
- **Handlers don't need to use these error classes** - just throw standard exceptions. The worker handles retry automatically based on your configuration.
150
+ - `RetryableError` - Signals that the error is transient and should be retried
151
+ - `NonRetryableError` - Signals permanent failure, message goes to DLQ
133
152
 
134
153
  ## API
135
154