@amqp-contract/worker 0.1.4 → 0.2.1
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 +14 -54
- package/dist/index.cjs +257 -89
- package/dist/index.d.cts +294 -45
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +294 -45
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +256 -90
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +719 -0
- package/package.json +14 -9
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# @amqp-contract/worker
|
|
2
2
|
|
|
3
|
-
Type-safe AMQP worker for consuming messages using amqp-contract with standard async/await error handling
|
|
3
|
+
**Type-safe AMQP worker for consuming messages using amqp-contract with standard async/await error handling.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/btravers/amqp-contract/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/@amqp-contract/worker)
|
|
7
|
+
[](https://www.npmjs.com/package/@amqp-contract/worker)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
10
|
|
|
5
11
|
📖 **[Full documentation →](https://btravers.github.io/amqp-contract/api/worker)**
|
|
6
12
|
|
|
@@ -39,6 +45,10 @@ const worker = await TypedAmqpWorker.create({
|
|
|
39
45
|
// await worker.close();
|
|
40
46
|
```
|
|
41
47
|
|
|
48
|
+
## Defining Handlers Externally
|
|
49
|
+
|
|
50
|
+
You can define handlers outside of the worker creation using `defineHandler` and `defineHandlers` for better code organization. See the [Worker API documentation](https://btravers.github.io/amqp-contract/api/worker) for details.
|
|
51
|
+
|
|
42
52
|
## Error Handling
|
|
43
53
|
|
|
44
54
|
Worker handlers use standard Promise-based async/await pattern:
|
|
@@ -70,61 +80,11 @@ These errors are logged but **handlers don't need to use them** - just throw sta
|
|
|
70
80
|
|
|
71
81
|
## API
|
|
72
82
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
Create a type-safe AMQP worker from a contract with message handlers. Automatically connects and starts consuming all messages.
|
|
76
|
-
|
|
77
|
-
**Parameters:**
|
|
78
|
-
|
|
79
|
-
- `options.contract` - Contract definition
|
|
80
|
-
- `options.handlers` - Object with async handler functions for each consumer
|
|
81
|
-
- `options.connection` - AMQP connection URL (string) or connection options (Options.Connect)
|
|
82
|
-
|
|
83
|
-
**Returns:** `Promise<TypedAmqpWorker>`
|
|
84
|
-
|
|
85
|
-
**Example:**
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
const worker = await TypedAmqpWorker.create({
|
|
89
|
-
contract,
|
|
90
|
-
handlers: {
|
|
91
|
-
// Each handler receives type-checked message
|
|
92
|
-
processOrder: async (message) => {
|
|
93
|
-
// message.orderId is type-checked
|
|
94
|
-
console.log(message.orderId);
|
|
95
|
-
},
|
|
96
|
-
processPayment: async (message) => {
|
|
97
|
-
// Different message type for this consumer
|
|
98
|
-
await handlePayment(message);
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
connection: {
|
|
102
|
-
hostname: 'localhost',
|
|
103
|
-
port: 5672,
|
|
104
|
-
username: 'guest',
|
|
105
|
-
password: 'guest',
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Handler Signature
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
type Handler<T> = (message: T) => Promise<void>
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Handlers are simple async functions that:
|
|
117
|
-
|
|
118
|
-
- Receive type-checked message as parameter
|
|
119
|
-
- Return `Promise<void>`
|
|
120
|
-
- Can throw exceptions (message will be requeued)
|
|
121
|
-
- Message is acknowledged automatically on success
|
|
122
|
-
|
|
123
|
-
### `TypedAmqpWorker.close()`
|
|
83
|
+
See the [Worker API documentation](https://btravers.github.io/amqp-contract/api/worker) for complete API reference.
|
|
124
84
|
|
|
125
|
-
|
|
85
|
+
## Documentation
|
|
126
86
|
|
|
127
|
-
**
|
|
87
|
+
📖 **[Read the full documentation →](https://btravers.github.io/amqp-contract)**
|
|
128
88
|
|
|
129
89
|
## License
|
|
130
90
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
let
|
|
1
|
+
let _amqp_contract_core = require("@amqp-contract/core");
|
|
2
2
|
let _swan_io_boxed = require("@swan-io/boxed");
|
|
3
3
|
|
|
4
4
|
//#region src/errors.ts
|
|
@@ -39,128 +39,296 @@ var MessageValidationError = class extends WorkerError {
|
|
|
39
39
|
//#endregion
|
|
40
40
|
//#region src/worker.ts
|
|
41
41
|
/**
|
|
42
|
-
* Type-safe AMQP worker for consuming messages
|
|
42
|
+
* Type-safe AMQP worker for consuming messages from RabbitMQ.
|
|
43
|
+
*
|
|
44
|
+
* This class provides automatic message validation, connection management,
|
|
45
|
+
* and error handling for consuming messages based on a contract definition.
|
|
46
|
+
*
|
|
47
|
+
* @typeParam TContract - The contract definition type
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* import { TypedAmqpWorker } from '@amqp-contract/worker';
|
|
52
|
+
* import { z } from 'zod';
|
|
53
|
+
*
|
|
54
|
+
* const contract = defineContract({
|
|
55
|
+
* queues: {
|
|
56
|
+
* orderProcessing: defineQueue('order-processing', { durable: true })
|
|
57
|
+
* },
|
|
58
|
+
* consumers: {
|
|
59
|
+
* processOrder: defineConsumer('order-processing', z.object({
|
|
60
|
+
* orderId: z.string(),
|
|
61
|
+
* amount: z.number()
|
|
62
|
+
* }))
|
|
63
|
+
* }
|
|
64
|
+
* });
|
|
65
|
+
*
|
|
66
|
+
* const worker = await TypedAmqpWorker.create({
|
|
67
|
+
* contract,
|
|
68
|
+
* handlers: {
|
|
69
|
+
* processOrder: async (message) => {
|
|
70
|
+
* console.log('Processing order', message.orderId);
|
|
71
|
+
* // Process the order...
|
|
72
|
+
* }
|
|
73
|
+
* },
|
|
74
|
+
* urls: ['amqp://localhost']
|
|
75
|
+
* }).resultToPromise();
|
|
76
|
+
*
|
|
77
|
+
* // Close when done
|
|
78
|
+
* await worker.close().resultToPromise();
|
|
79
|
+
* ```
|
|
43
80
|
*/
|
|
44
81
|
var TypedAmqpWorker = class TypedAmqpWorker {
|
|
45
|
-
|
|
46
|
-
connection = null;
|
|
47
|
-
consumerTags = [];
|
|
48
|
-
constructor(contract, handlers, connectionOptions) {
|
|
82
|
+
constructor(contract, amqpClient, handlers) {
|
|
49
83
|
this.contract = contract;
|
|
84
|
+
this.amqpClient = amqpClient;
|
|
50
85
|
this.handlers = handlers;
|
|
51
|
-
this.connectionOptions = connectionOptions;
|
|
52
86
|
}
|
|
53
87
|
/**
|
|
54
|
-
* Create a type-safe AMQP worker from a contract
|
|
55
|
-
*
|
|
88
|
+
* Create a type-safe AMQP worker from a contract.
|
|
89
|
+
*
|
|
90
|
+
* Connection management (including automatic reconnection) is handled internally
|
|
91
|
+
* by amqp-connection-manager via the {@link AmqpClient}. The worker will set up
|
|
92
|
+
* consumers for all contract-defined handlers asynchronously in the background
|
|
93
|
+
* once the underlying connection and channels are ready.
|
|
94
|
+
*
|
|
95
|
+
* @param options - Configuration options for the worker
|
|
96
|
+
* @returns A Future that resolves to a Result containing the worker or an error
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* const workerResult = await TypedAmqpWorker.create({
|
|
101
|
+
* contract: myContract,
|
|
102
|
+
* handlers: {
|
|
103
|
+
* processOrder: async (msg) => console.log('Order:', msg.orderId)
|
|
104
|
+
* },
|
|
105
|
+
* urls: ['amqp://localhost']
|
|
106
|
+
* }).resultToPromise();
|
|
107
|
+
*
|
|
108
|
+
* if (workerResult.isError()) {
|
|
109
|
+
* console.error('Failed to create worker:', workerResult.error);
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
56
112
|
*/
|
|
57
|
-
static
|
|
58
|
-
const worker = new TypedAmqpWorker(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
113
|
+
static create({ contract, handlers, urls, connectionOptions }) {
|
|
114
|
+
const worker = new TypedAmqpWorker(contract, new _amqp_contract_core.AmqpClient(contract, {
|
|
115
|
+
urls,
|
|
116
|
+
connectionOptions
|
|
117
|
+
}), handlers);
|
|
118
|
+
return worker.consumeAll().mapOk(() => worker);
|
|
62
119
|
}
|
|
63
120
|
/**
|
|
64
|
-
* Close the connection
|
|
121
|
+
* Close the AMQP channel and connection.
|
|
122
|
+
*
|
|
123
|
+
* This gracefully closes the connection to the AMQP broker,
|
|
124
|
+
* stopping all message consumption and cleaning up resources.
|
|
125
|
+
*
|
|
126
|
+
* @returns A Future that resolves to a Result indicating success or failure
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* const closeResult = await worker.close().resultToPromise();
|
|
131
|
+
* if (closeResult.isOk()) {
|
|
132
|
+
* console.log('Worker closed successfully');
|
|
133
|
+
* }
|
|
134
|
+
* ```
|
|
65
135
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (this.channel) {
|
|
69
|
-
await this.channel.close();
|
|
70
|
-
this.channel = null;
|
|
71
|
-
}
|
|
72
|
-
if (this.connection) {
|
|
73
|
-
await this.connection.close();
|
|
74
|
-
this.connection = null;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Connect to AMQP broker
|
|
79
|
-
*/
|
|
80
|
-
async init() {
|
|
81
|
-
this.connection = await (0, amqplib.connect)(this.connectionOptions);
|
|
82
|
-
this.channel = await this.connection.createChannel();
|
|
83
|
-
if (this.contract.exchanges) for (const exchange of Object.values(this.contract.exchanges)) await this.channel.assertExchange(exchange.name, exchange.type, {
|
|
84
|
-
durable: exchange.durable,
|
|
85
|
-
autoDelete: exchange.autoDelete,
|
|
86
|
-
internal: exchange.internal,
|
|
87
|
-
arguments: exchange.arguments
|
|
88
|
-
});
|
|
89
|
-
if (this.contract.queues) for (const queue of Object.values(this.contract.queues)) await this.channel.assertQueue(queue.name, {
|
|
90
|
-
durable: queue.durable,
|
|
91
|
-
exclusive: queue.exclusive,
|
|
92
|
-
autoDelete: queue.autoDelete,
|
|
93
|
-
arguments: queue.arguments
|
|
94
|
-
});
|
|
95
|
-
if (this.contract.bindings) {
|
|
96
|
-
for (const binding of Object.values(this.contract.bindings)) if (binding.type === "queue") await this.channel.bindQueue(binding.queue, binding.exchange, binding.routingKey ?? "", binding.arguments);
|
|
97
|
-
else if (binding.type === "exchange") await this.channel.bindExchange(binding.destination, binding.source, binding.routingKey ?? "", binding.arguments);
|
|
98
|
-
}
|
|
136
|
+
close() {
|
|
137
|
+
return _swan_io_boxed.Future.fromPromise(this.amqpClient.close()).mapError((error) => new TechnicalError("Failed to close AMQP connection", error)).mapOk(() => void 0);
|
|
99
138
|
}
|
|
100
139
|
/**
|
|
101
140
|
* Start consuming messages for all consumers
|
|
102
141
|
*/
|
|
103
|
-
|
|
104
|
-
if (!this.contract.consumers)
|
|
142
|
+
consumeAll() {
|
|
143
|
+
if (!this.contract.consumers) return _swan_io_boxed.Future.value(_swan_io_boxed.Result.Error(new TechnicalError("No consumers defined in contract")));
|
|
105
144
|
const consumerNames = Object.keys(this.contract.consumers);
|
|
106
|
-
|
|
145
|
+
return _swan_io_boxed.Future.all(consumerNames.map((consumerName) => this.consume(consumerName))).map(_swan_io_boxed.Result.all).mapOk(() => void 0);
|
|
107
146
|
}
|
|
108
147
|
/**
|
|
109
148
|
* Start consuming messages for a specific consumer
|
|
110
149
|
*/
|
|
111
|
-
|
|
112
|
-
if (!this.channel) throw new Error("Worker not initialized. Use TypedAmqpWorker.create() to obtain an initialized worker instance.");
|
|
150
|
+
consume(consumerName) {
|
|
113
151
|
const consumers = this.contract.consumers;
|
|
114
|
-
if (!consumers)
|
|
152
|
+
if (!consumers) return _swan_io_boxed.Future.value(_swan_io_boxed.Result.Error(new TechnicalError("No consumers defined in contract")));
|
|
115
153
|
const consumer = consumers[consumerName];
|
|
116
|
-
if (!consumer
|
|
154
|
+
if (!consumer) {
|
|
117
155
|
const availableConsumers = Object.keys(consumers);
|
|
118
156
|
const available = availableConsumers.length > 0 ? availableConsumers.join(", ") : "none";
|
|
119
|
-
|
|
157
|
+
return _swan_io_boxed.Future.value(_swan_io_boxed.Result.Error(new TechnicalError(`Consumer not found: "${String(consumerName)}". Available consumers: ${available}`)));
|
|
120
158
|
}
|
|
121
|
-
const consumerDef = consumer;
|
|
122
159
|
const handler = this.handlers[consumerName];
|
|
123
|
-
if (!handler)
|
|
124
|
-
|
|
125
|
-
const result = await this.channel.consume(consumerDef.queue, async (msg) => {
|
|
126
|
-
if (!msg) return;
|
|
160
|
+
if (!handler) return _swan_io_boxed.Future.value(_swan_io_boxed.Result.Error(new TechnicalError(`Handler for "${String(consumerName)}" not provided`)));
|
|
161
|
+
return _swan_io_boxed.Future.fromPromise(this.amqpClient.channel.consume(consumer.queue.name, async (msg) => {
|
|
127
162
|
const parseResult = _swan_io_boxed.Result.fromExecution(() => JSON.parse(msg.content.toString()));
|
|
128
163
|
if (parseResult.isError()) {
|
|
129
164
|
console.error(new TechnicalError(`Error parsing message for consumer "${String(consumerName)}"`, parseResult.error));
|
|
130
|
-
this.channel
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const content = parseResult.value;
|
|
134
|
-
const rawValidation = consumerDef.message["~standard"].validate(content);
|
|
135
|
-
const resolvedValidation = rawValidation instanceof Promise ? await rawValidation : rawValidation;
|
|
136
|
-
const validationResult = typeof resolvedValidation === "object" && resolvedValidation !== null && "issues" in resolvedValidation && resolvedValidation.issues ? _swan_io_boxed.Result.Error(new MessageValidationError(String(consumerName), resolvedValidation.issues)) : _swan_io_boxed.Result.Ok(typeof resolvedValidation === "object" && resolvedValidation !== null && "value" in resolvedValidation ? resolvedValidation.value : content);
|
|
137
|
-
if (validationResult.isError()) {
|
|
138
|
-
console.error(validationResult.error);
|
|
139
|
-
this.channel?.nack(msg, false, false);
|
|
165
|
+
this.amqpClient.channel.nack(msg, false, false);
|
|
140
166
|
return;
|
|
141
167
|
}
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
168
|
+
const rawValidation = consumer.message.payload["~standard"].validate(parseResult.value);
|
|
169
|
+
await _swan_io_boxed.Future.fromPromise(rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation)).mapOkToResult((validationResult) => {
|
|
170
|
+
if (validationResult.issues) return _swan_io_boxed.Result.Error(new MessageValidationError(String(consumerName), validationResult.issues));
|
|
171
|
+
return _swan_io_boxed.Result.Ok(validationResult.value);
|
|
172
|
+
}).tapError((error) => {
|
|
173
|
+
console.error(error);
|
|
174
|
+
this.amqpClient.channel.nack(msg, false, false);
|
|
175
|
+
}).flatMapOk((validatedMessage) => _swan_io_boxed.Future.fromPromise(handler(validatedMessage)).tapError((error) => {
|
|
147
176
|
console.error(new TechnicalError(`Error processing message for consumer "${String(consumerName)}"`, error));
|
|
148
|
-
this.channel
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Stop consuming messages
|
|
155
|
-
*/
|
|
156
|
-
async stopConsuming() {
|
|
157
|
-
if (!this.channel) return;
|
|
158
|
-
for (const tag of this.consumerTags) await this.channel.cancel(tag);
|
|
159
|
-
this.consumerTags = [];
|
|
177
|
+
this.amqpClient.channel.nack(msg, false, true);
|
|
178
|
+
})).tapOk(() => {
|
|
179
|
+
this.amqpClient.channel.ack(msg);
|
|
180
|
+
}).toPromise();
|
|
181
|
+
})).mapError((error) => new TechnicalError(`Failed to start consuming for "${String(consumerName)}"`, error)).mapOk(() => void 0);
|
|
160
182
|
}
|
|
161
183
|
};
|
|
162
184
|
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/handlers.ts
|
|
187
|
+
/**
|
|
188
|
+
* Define a type-safe handler for a specific consumer in a contract.
|
|
189
|
+
*
|
|
190
|
+
* This utility allows you to define handlers outside of the worker creation,
|
|
191
|
+
* providing better code organization and reusability.
|
|
192
|
+
*
|
|
193
|
+
* @template TContract - The contract definition type
|
|
194
|
+
* @template TName - The consumer name from the contract
|
|
195
|
+
* @param contract - The contract definition containing the consumer
|
|
196
|
+
* @param consumerName - The name of the consumer from the contract
|
|
197
|
+
* @param handler - The async handler function that processes messages
|
|
198
|
+
* @returns A type-safe handler that can be used with TypedAmqpWorker
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* import { defineHandler } from '@amqp-contract/worker';
|
|
203
|
+
* import { orderContract } from './contract';
|
|
204
|
+
*
|
|
205
|
+
* // Define handler outside of worker creation
|
|
206
|
+
* const processOrderHandler = defineHandler(
|
|
207
|
+
* orderContract,
|
|
208
|
+
* 'processOrder',
|
|
209
|
+
* async (message) => {
|
|
210
|
+
* // message is fully typed based on the contract
|
|
211
|
+
* console.log('Processing order:', message.orderId);
|
|
212
|
+
* await processPayment(message);
|
|
213
|
+
* }
|
|
214
|
+
* );
|
|
215
|
+
*
|
|
216
|
+
* // Use the handler in worker
|
|
217
|
+
* const worker = await TypedAmqpWorker.create({
|
|
218
|
+
* contract: orderContract,
|
|
219
|
+
* handlers: {
|
|
220
|
+
* processOrder: processOrderHandler,
|
|
221
|
+
* },
|
|
222
|
+
* connection: 'amqp://localhost',
|
|
223
|
+
* });
|
|
224
|
+
* ```
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```typescript
|
|
228
|
+
* // Define multiple handlers
|
|
229
|
+
* const processOrderHandler = defineHandler(
|
|
230
|
+
* orderContract,
|
|
231
|
+
* 'processOrder',
|
|
232
|
+
* async (message) => {
|
|
233
|
+
* await processOrder(message);
|
|
234
|
+
* }
|
|
235
|
+
* );
|
|
236
|
+
*
|
|
237
|
+
* const notifyOrderHandler = defineHandler(
|
|
238
|
+
* orderContract,
|
|
239
|
+
* 'notifyOrder',
|
|
240
|
+
* async (message) => {
|
|
241
|
+
* await sendNotification(message);
|
|
242
|
+
* }
|
|
243
|
+
* );
|
|
244
|
+
*
|
|
245
|
+
* // Compose handlers
|
|
246
|
+
* const worker = await TypedAmqpWorker.create({
|
|
247
|
+
* contract: orderContract,
|
|
248
|
+
* handlers: {
|
|
249
|
+
* processOrder: processOrderHandler,
|
|
250
|
+
* notifyOrder: notifyOrderHandler,
|
|
251
|
+
* },
|
|
252
|
+
* connection: 'amqp://localhost',
|
|
253
|
+
* });
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
function defineHandler(contract, consumerName, handler) {
|
|
257
|
+
const consumers = contract.consumers;
|
|
258
|
+
if (!consumers || !(consumerName in consumers)) {
|
|
259
|
+
const availableConsumers = consumers ? Object.keys(consumers) : [];
|
|
260
|
+
const available = availableConsumers.length > 0 ? availableConsumers.join(", ") : "none";
|
|
261
|
+
throw new Error(`Consumer "${String(consumerName)}" not found in contract. Available consumers: ${available}`);
|
|
262
|
+
}
|
|
263
|
+
return handler;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Define multiple type-safe handlers for consumers in a contract.
|
|
267
|
+
*
|
|
268
|
+
* This utility allows you to define all handlers at once outside of the worker creation,
|
|
269
|
+
* ensuring type safety and providing better code organization.
|
|
270
|
+
*
|
|
271
|
+
* @template TContract - The contract definition type
|
|
272
|
+
* @param contract - The contract definition containing the consumers
|
|
273
|
+
* @param handlers - An object with async handler functions for each consumer
|
|
274
|
+
* @returns A type-safe handlers object that can be used with TypedAmqpWorker
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```typescript
|
|
278
|
+
* import { defineHandlers } from '@amqp-contract/worker';
|
|
279
|
+
* import { orderContract } from './contract';
|
|
280
|
+
*
|
|
281
|
+
* // Define all handlers at once
|
|
282
|
+
* const handlers = defineHandlers(orderContract, {
|
|
283
|
+
* processOrder: async (message) => {
|
|
284
|
+
* // message is fully typed based on the contract
|
|
285
|
+
* console.log('Processing order:', message.orderId);
|
|
286
|
+
* await processPayment(message);
|
|
287
|
+
* },
|
|
288
|
+
* notifyOrder: async (message) => {
|
|
289
|
+
* await sendNotification(message);
|
|
290
|
+
* },
|
|
291
|
+
* shipOrder: async (message) => {
|
|
292
|
+
* await prepareShipment(message);
|
|
293
|
+
* },
|
|
294
|
+
* });
|
|
295
|
+
*
|
|
296
|
+
* // Use the handlers in worker
|
|
297
|
+
* const worker = await TypedAmqpWorker.create({
|
|
298
|
+
* contract: orderContract,
|
|
299
|
+
* handlers,
|
|
300
|
+
* connection: 'amqp://localhost',
|
|
301
|
+
* });
|
|
302
|
+
* ```
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* ```typescript
|
|
306
|
+
* // Separate handler definitions for better organization
|
|
307
|
+
* async function handleProcessOrder(message: WorkerInferConsumerInput<typeof orderContract, 'processOrder'>) {
|
|
308
|
+
* await processOrder(message);
|
|
309
|
+
* }
|
|
310
|
+
*
|
|
311
|
+
* async function handleNotifyOrder(message: WorkerInferConsumerInput<typeof orderContract, 'notifyOrder'>) {
|
|
312
|
+
* await sendNotification(message);
|
|
313
|
+
* }
|
|
314
|
+
*
|
|
315
|
+
* const handlers = defineHandlers(orderContract, {
|
|
316
|
+
* processOrder: handleProcessOrder,
|
|
317
|
+
* notifyOrder: handleNotifyOrder,
|
|
318
|
+
* });
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
function defineHandlers(contract, handlers) {
|
|
322
|
+
const consumers = contract.consumers;
|
|
323
|
+
const availableConsumers = Object.keys(consumers ?? {});
|
|
324
|
+
const availableConsumerNames = availableConsumers.length > 0 ? availableConsumers.join(", ") : "none";
|
|
325
|
+
for (const handlerName of Object.keys(handlers)) if (!consumers || !(handlerName in consumers)) throw new Error(`Consumer "${handlerName}" not found in contract. Available consumers: ${availableConsumerNames}`);
|
|
326
|
+
return handlers;
|
|
327
|
+
}
|
|
328
|
+
|
|
163
329
|
//#endregion
|
|
164
330
|
exports.MessageValidationError = MessageValidationError;
|
|
165
331
|
exports.TechnicalError = TechnicalError;
|
|
166
|
-
exports.TypedAmqpWorker = TypedAmqpWorker;
|
|
332
|
+
exports.TypedAmqpWorker = TypedAmqpWorker;
|
|
333
|
+
exports.defineHandler = defineHandler;
|
|
334
|
+
exports.defineHandlers = defineHandlers;
|