@amqp-contract/worker 0.1.4 → 0.2.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 +5 -55
- package/dist/index.cjs +153 -19
- package/dist/index.d.cts +131 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +131 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +151 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -39,6 +39,10 @@ const worker = await TypedAmqpWorker.create({
|
|
|
39
39
|
// await worker.close();
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
## Defining Handlers Externally
|
|
43
|
+
|
|
44
|
+
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.
|
|
45
|
+
|
|
42
46
|
## Error Handling
|
|
43
47
|
|
|
44
48
|
Worker handlers use standard Promise-based async/await pattern:
|
|
@@ -70,61 +74,7 @@ These errors are logged but **handlers don't need to use them** - just throw sta
|
|
|
70
74
|
|
|
71
75
|
## API
|
|
72
76
|
|
|
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()`
|
|
124
|
-
|
|
125
|
-
Stop consuming and close the channel and connection.
|
|
126
|
-
|
|
127
|
-
**Returns:** `Promise<void>`
|
|
77
|
+
See the [Worker API documentation](https://btravers.github.io/amqp-contract/api/worker) for complete API reference.
|
|
128
78
|
|
|
129
79
|
## License
|
|
130
80
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
let amqplib = require("amqplib");
|
|
2
|
+
let _amqp_contract_core = require("@amqp-contract/core");
|
|
2
3
|
let _swan_io_boxed = require("@swan-io/boxed");
|
|
3
4
|
|
|
4
5
|
//#region src/errors.ts
|
|
@@ -80,22 +81,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
80
81
|
async init() {
|
|
81
82
|
this.connection = await (0, amqplib.connect)(this.connectionOptions);
|
|
82
83
|
this.channel = await this.connection.createChannel();
|
|
83
|
-
|
|
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
|
-
}
|
|
84
|
+
await (0, _amqp_contract_core.setupInfra)(this.channel, this.contract);
|
|
99
85
|
}
|
|
100
86
|
/**
|
|
101
87
|
* Start consuming messages for all consumers
|
|
@@ -122,7 +108,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
122
108
|
const handler = this.handlers[consumerName];
|
|
123
109
|
if (!handler) throw new Error(`Handler for "${String(consumerName)}" not provided`);
|
|
124
110
|
if (consumerDef.prefetch !== void 0) await this.channel.prefetch(consumerDef.prefetch);
|
|
125
|
-
const result = await this.channel.consume(consumerDef.queue, async (msg) => {
|
|
111
|
+
const result = await this.channel.consume(consumerDef.queue.name, async (msg) => {
|
|
126
112
|
if (!msg) return;
|
|
127
113
|
const parseResult = _swan_io_boxed.Result.fromExecution(() => JSON.parse(msg.content.toString()));
|
|
128
114
|
if (parseResult.isError()) {
|
|
@@ -131,7 +117,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
131
117
|
return;
|
|
132
118
|
}
|
|
133
119
|
const content = parseResult.value;
|
|
134
|
-
const rawValidation = consumerDef.message["~standard"].validate(content);
|
|
120
|
+
const rawValidation = consumerDef.message.payload["~standard"].validate(content);
|
|
135
121
|
const resolvedValidation = rawValidation instanceof Promise ? await rawValidation : rawValidation;
|
|
136
122
|
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
123
|
if (validationResult.isError()) {
|
|
@@ -160,7 +146,155 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
160
146
|
}
|
|
161
147
|
};
|
|
162
148
|
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/handlers.ts
|
|
151
|
+
/**
|
|
152
|
+
* Define a type-safe handler for a specific consumer in a contract.
|
|
153
|
+
*
|
|
154
|
+
* This utility allows you to define handlers outside of the worker creation,
|
|
155
|
+
* providing better code organization and reusability.
|
|
156
|
+
*
|
|
157
|
+
* @template TContract - The contract definition type
|
|
158
|
+
* @template TName - The consumer name from the contract
|
|
159
|
+
* @param contract - The contract definition containing the consumer
|
|
160
|
+
* @param consumerName - The name of the consumer from the contract
|
|
161
|
+
* @param handler - The async handler function that processes messages
|
|
162
|
+
* @returns A type-safe handler that can be used with TypedAmqpWorker
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* import { defineHandler } from '@amqp-contract/worker';
|
|
167
|
+
* import { orderContract } from './contract';
|
|
168
|
+
*
|
|
169
|
+
* // Define handler outside of worker creation
|
|
170
|
+
* const processOrderHandler = defineHandler(
|
|
171
|
+
* orderContract,
|
|
172
|
+
* 'processOrder',
|
|
173
|
+
* async (message) => {
|
|
174
|
+
* // message is fully typed based on the contract
|
|
175
|
+
* console.log('Processing order:', message.orderId);
|
|
176
|
+
* await processPayment(message);
|
|
177
|
+
* }
|
|
178
|
+
* );
|
|
179
|
+
*
|
|
180
|
+
* // Use the handler in worker
|
|
181
|
+
* const worker = await TypedAmqpWorker.create({
|
|
182
|
+
* contract: orderContract,
|
|
183
|
+
* handlers: {
|
|
184
|
+
* processOrder: processOrderHandler,
|
|
185
|
+
* },
|
|
186
|
+
* connection: 'amqp://localhost',
|
|
187
|
+
* });
|
|
188
|
+
* ```
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```typescript
|
|
192
|
+
* // Define multiple handlers
|
|
193
|
+
* const processOrderHandler = defineHandler(
|
|
194
|
+
* orderContract,
|
|
195
|
+
* 'processOrder',
|
|
196
|
+
* async (message) => {
|
|
197
|
+
* await processOrder(message);
|
|
198
|
+
* }
|
|
199
|
+
* );
|
|
200
|
+
*
|
|
201
|
+
* const notifyOrderHandler = defineHandler(
|
|
202
|
+
* orderContract,
|
|
203
|
+
* 'notifyOrder',
|
|
204
|
+
* async (message) => {
|
|
205
|
+
* await sendNotification(message);
|
|
206
|
+
* }
|
|
207
|
+
* );
|
|
208
|
+
*
|
|
209
|
+
* // Compose handlers
|
|
210
|
+
* const worker = await TypedAmqpWorker.create({
|
|
211
|
+
* contract: orderContract,
|
|
212
|
+
* handlers: {
|
|
213
|
+
* processOrder: processOrderHandler,
|
|
214
|
+
* notifyOrder: notifyOrderHandler,
|
|
215
|
+
* },
|
|
216
|
+
* connection: 'amqp://localhost',
|
|
217
|
+
* });
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
function defineHandler(contract, consumerName, handler) {
|
|
221
|
+
const consumers = contract.consumers;
|
|
222
|
+
if (!consumers || !(consumerName in consumers)) {
|
|
223
|
+
const availableConsumers = consumers ? Object.keys(consumers) : [];
|
|
224
|
+
const available = availableConsumers.length > 0 ? availableConsumers.join(", ") : "none";
|
|
225
|
+
throw new Error(`Consumer "${String(consumerName)}" not found in contract. Available consumers: ${available}`);
|
|
226
|
+
}
|
|
227
|
+
return handler;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Define multiple type-safe handlers for consumers in a contract.
|
|
231
|
+
*
|
|
232
|
+
* This utility allows you to define all handlers at once outside of the worker creation,
|
|
233
|
+
* ensuring type safety and providing better code organization.
|
|
234
|
+
*
|
|
235
|
+
* @template TContract - The contract definition type
|
|
236
|
+
* @param contract - The contract definition containing the consumers
|
|
237
|
+
* @param handlers - An object with async handler functions for each consumer
|
|
238
|
+
* @returns A type-safe handlers object that can be used with TypedAmqpWorker
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* import { defineHandlers } from '@amqp-contract/worker';
|
|
243
|
+
* import { orderContract } from './contract';
|
|
244
|
+
*
|
|
245
|
+
* // Define all handlers at once
|
|
246
|
+
* const handlers = defineHandlers(orderContract, {
|
|
247
|
+
* processOrder: async (message) => {
|
|
248
|
+
* // message is fully typed based on the contract
|
|
249
|
+
* console.log('Processing order:', message.orderId);
|
|
250
|
+
* await processPayment(message);
|
|
251
|
+
* },
|
|
252
|
+
* notifyOrder: async (message) => {
|
|
253
|
+
* await sendNotification(message);
|
|
254
|
+
* },
|
|
255
|
+
* shipOrder: async (message) => {
|
|
256
|
+
* await prepareShipment(message);
|
|
257
|
+
* },
|
|
258
|
+
* });
|
|
259
|
+
*
|
|
260
|
+
* // Use the handlers in worker
|
|
261
|
+
* const worker = await TypedAmqpWorker.create({
|
|
262
|
+
* contract: orderContract,
|
|
263
|
+
* handlers,
|
|
264
|
+
* connection: 'amqp://localhost',
|
|
265
|
+
* });
|
|
266
|
+
* ```
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```typescript
|
|
270
|
+
* // Separate handler definitions for better organization
|
|
271
|
+
* async function handleProcessOrder(message: WorkerInferConsumerInput<typeof orderContract, 'processOrder'>) {
|
|
272
|
+
* await processOrder(message);
|
|
273
|
+
* }
|
|
274
|
+
*
|
|
275
|
+
* async function handleNotifyOrder(message: WorkerInferConsumerInput<typeof orderContract, 'notifyOrder'>) {
|
|
276
|
+
* await sendNotification(message);
|
|
277
|
+
* }
|
|
278
|
+
*
|
|
279
|
+
* const handlers = defineHandlers(orderContract, {
|
|
280
|
+
* processOrder: handleProcessOrder,
|
|
281
|
+
* notifyOrder: handleNotifyOrder,
|
|
282
|
+
* });
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
function defineHandlers(contract, handlers) {
|
|
286
|
+
const consumers = contract.consumers;
|
|
287
|
+
const availableConsumers = consumers ? Object.keys(consumers) : [];
|
|
288
|
+
for (const handlerName of Object.keys(handlers)) if (!consumers || !(handlerName in consumers)) {
|
|
289
|
+
const available = availableConsumers.length > 0 ? availableConsumers.join(", ") : "none";
|
|
290
|
+
throw new Error(`Consumer "${handlerName}" not found in contract. Available consumers: ${available}`);
|
|
291
|
+
}
|
|
292
|
+
return handlers;
|
|
293
|
+
}
|
|
294
|
+
|
|
163
295
|
//#endregion
|
|
164
296
|
exports.MessageValidationError = MessageValidationError;
|
|
165
297
|
exports.TechnicalError = TechnicalError;
|
|
166
|
-
exports.TypedAmqpWorker = TypedAmqpWorker;
|
|
298
|
+
exports.TypedAmqpWorker = TypedAmqpWorker;
|
|
299
|
+
exports.defineHandler = defineHandler;
|
|
300
|
+
exports.defineHandlers = defineHandlers;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Options } from "amqplib";
|
|
2
|
-
import { ContractDefinition, WorkerInferConsumerHandlers } from "@amqp-contract/contract";
|
|
2
|
+
import { ContractDefinition, InferConsumerNames, WorkerInferConsumerHandler, WorkerInferConsumerHandlers } from "@amqp-contract/contract";
|
|
3
3
|
|
|
4
4
|
//#region src/worker.d.ts
|
|
5
5
|
|
|
@@ -73,5 +73,134 @@ declare class MessageValidationError extends WorkerError {
|
|
|
73
73
|
constructor(consumerName: string, issues: unknown);
|
|
74
74
|
}
|
|
75
75
|
//#endregion
|
|
76
|
-
|
|
76
|
+
//#region src/handlers.d.ts
|
|
77
|
+
/**
|
|
78
|
+
* Define a type-safe handler for a specific consumer in a contract.
|
|
79
|
+
*
|
|
80
|
+
* This utility allows you to define handlers outside of the worker creation,
|
|
81
|
+
* providing better code organization and reusability.
|
|
82
|
+
*
|
|
83
|
+
* @template TContract - The contract definition type
|
|
84
|
+
* @template TName - The consumer name from the contract
|
|
85
|
+
* @param contract - The contract definition containing the consumer
|
|
86
|
+
* @param consumerName - The name of the consumer from the contract
|
|
87
|
+
* @param handler - The async handler function that processes messages
|
|
88
|
+
* @returns A type-safe handler that can be used with TypedAmqpWorker
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* import { defineHandler } from '@amqp-contract/worker';
|
|
93
|
+
* import { orderContract } from './contract';
|
|
94
|
+
*
|
|
95
|
+
* // Define handler outside of worker creation
|
|
96
|
+
* const processOrderHandler = defineHandler(
|
|
97
|
+
* orderContract,
|
|
98
|
+
* 'processOrder',
|
|
99
|
+
* async (message) => {
|
|
100
|
+
* // message is fully typed based on the contract
|
|
101
|
+
* console.log('Processing order:', message.orderId);
|
|
102
|
+
* await processPayment(message);
|
|
103
|
+
* }
|
|
104
|
+
* );
|
|
105
|
+
*
|
|
106
|
+
* // Use the handler in worker
|
|
107
|
+
* const worker = await TypedAmqpWorker.create({
|
|
108
|
+
* contract: orderContract,
|
|
109
|
+
* handlers: {
|
|
110
|
+
* processOrder: processOrderHandler,
|
|
111
|
+
* },
|
|
112
|
+
* connection: 'amqp://localhost',
|
|
113
|
+
* });
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* // Define multiple handlers
|
|
119
|
+
* const processOrderHandler = defineHandler(
|
|
120
|
+
* orderContract,
|
|
121
|
+
* 'processOrder',
|
|
122
|
+
* async (message) => {
|
|
123
|
+
* await processOrder(message);
|
|
124
|
+
* }
|
|
125
|
+
* );
|
|
126
|
+
*
|
|
127
|
+
* const notifyOrderHandler = defineHandler(
|
|
128
|
+
* orderContract,
|
|
129
|
+
* 'notifyOrder',
|
|
130
|
+
* async (message) => {
|
|
131
|
+
* await sendNotification(message);
|
|
132
|
+
* }
|
|
133
|
+
* );
|
|
134
|
+
*
|
|
135
|
+
* // Compose handlers
|
|
136
|
+
* const worker = await TypedAmqpWorker.create({
|
|
137
|
+
* contract: orderContract,
|
|
138
|
+
* handlers: {
|
|
139
|
+
* processOrder: processOrderHandler,
|
|
140
|
+
* notifyOrder: notifyOrderHandler,
|
|
141
|
+
* },
|
|
142
|
+
* connection: 'amqp://localhost',
|
|
143
|
+
* });
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
declare function defineHandler<TContract extends ContractDefinition, TName extends InferConsumerNames<TContract>>(contract: TContract, consumerName: TName, handler: WorkerInferConsumerHandler<TContract, TName>): WorkerInferConsumerHandler<TContract, TName>;
|
|
147
|
+
/**
|
|
148
|
+
* Define multiple type-safe handlers for consumers in a contract.
|
|
149
|
+
*
|
|
150
|
+
* This utility allows you to define all handlers at once outside of the worker creation,
|
|
151
|
+
* ensuring type safety and providing better code organization.
|
|
152
|
+
*
|
|
153
|
+
* @template TContract - The contract definition type
|
|
154
|
+
* @param contract - The contract definition containing the consumers
|
|
155
|
+
* @param handlers - An object with async handler functions for each consumer
|
|
156
|
+
* @returns A type-safe handlers object that can be used with TypedAmqpWorker
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* import { defineHandlers } from '@amqp-contract/worker';
|
|
161
|
+
* import { orderContract } from './contract';
|
|
162
|
+
*
|
|
163
|
+
* // Define all handlers at once
|
|
164
|
+
* const handlers = defineHandlers(orderContract, {
|
|
165
|
+
* processOrder: async (message) => {
|
|
166
|
+
* // message is fully typed based on the contract
|
|
167
|
+
* console.log('Processing order:', message.orderId);
|
|
168
|
+
* await processPayment(message);
|
|
169
|
+
* },
|
|
170
|
+
* notifyOrder: async (message) => {
|
|
171
|
+
* await sendNotification(message);
|
|
172
|
+
* },
|
|
173
|
+
* shipOrder: async (message) => {
|
|
174
|
+
* await prepareShipment(message);
|
|
175
|
+
* },
|
|
176
|
+
* });
|
|
177
|
+
*
|
|
178
|
+
* // Use the handlers in worker
|
|
179
|
+
* const worker = await TypedAmqpWorker.create({
|
|
180
|
+
* contract: orderContract,
|
|
181
|
+
* handlers,
|
|
182
|
+
* connection: 'amqp://localhost',
|
|
183
|
+
* });
|
|
184
|
+
* ```
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```typescript
|
|
188
|
+
* // Separate handler definitions for better organization
|
|
189
|
+
* async function handleProcessOrder(message: WorkerInferConsumerInput<typeof orderContract, 'processOrder'>) {
|
|
190
|
+
* await processOrder(message);
|
|
191
|
+
* }
|
|
192
|
+
*
|
|
193
|
+
* async function handleNotifyOrder(message: WorkerInferConsumerInput<typeof orderContract, 'notifyOrder'>) {
|
|
194
|
+
* await sendNotification(message);
|
|
195
|
+
* }
|
|
196
|
+
*
|
|
197
|
+
* const handlers = defineHandlers(orderContract, {
|
|
198
|
+
* processOrder: handleProcessOrder,
|
|
199
|
+
* notifyOrder: handleNotifyOrder,
|
|
200
|
+
* });
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
declare function defineHandlers<TContract extends ContractDefinition>(contract: TContract, handlers: WorkerInferConsumerHandlers<TContract>): WorkerInferConsumerHandlers<TContract>;
|
|
204
|
+
//#endregion
|
|
205
|
+
export { type CreateWorkerOptions, MessageValidationError, TechnicalError, TypedAmqpWorker, defineHandler, defineHandlers };
|
|
77
206
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/worker.ts","../src/errors.ts"],"sourcesContent":[],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/worker.ts","../src/errors.ts","../src/handlers.ts"],"sourcesContent":[],"mappings":";;;;;;;AAeA;AAAuD,UAAtC,mBAAsC,CAAA,kBAAA,kBAAA,CAAA,CAAA;EAC3C,QAAA,EAAA,SAAA;EAC4B,QAAA,EAA5B,2BAA4B,CAAA,SAAA,CAAA;EAA5B,UAAA,EAAA,MAAA,GACW,OAAA,CAAQ,OADnB;;;AAOZ;;AAewC,cAf3B,eAe2B,CAAA,kBAfO,kBAeP,CAAA,CAAA;EACP,iBAAA,QAAA;EAApB,iBAAA,QAAA;EACgB,iBAAA,iBAAA;EAAhB,QAAA,OAAA;EAAR,QAAA,UAAA;EAUY,QAAA,YAAA;EAAO,QAAA,WAAA,CAAA;;;;;EC9BX,OAAA,MAAA,CAAA,kBDkB2B,kBClBO,CAAA,CAAA,OAAA,EDmBlC,mBCnBkC,CDmBd,SCnBc,CAAA,CAAA,EDoB1C,OCpB0C,CDoBlC,eCpBkC,CDoBlB,SCpBkB,CAAA,CAAA;EAalC;;;WDiBI;EEyBD;;;EAEA,QAAA,IAAA;EAEJ;;;EAEqC,QAAA,UAAA;EAAtC;;;EACR,QAAA,OAAA;EAA0B;AAuE7B;;EACY,QAAA,aAAA;;;;;;;uBDxJG,WAAA,SAAoB,KAAA;EDYlB,UAAA,WAAA,CAAmB,OAAA,EAAA,MAAA;;;;;;AAGE,cCGzB,cAAA,SAAuB,WAAA,CDHE;EAMzB,SAAA,KAAA,CAAA,EAAA,OAAe,GAAA,SAAA;EAAmB,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,OAAA,GAAA,SAAA;;;;;AAiBlC,cCPA,sBAAA,SAA+B,WAAA,CDO/B;EAAR,SAAA,YAAA,EAAA,MAAA;EAUY,SAAA,MAAA,EAAA,OAAA;EAAO,WAAA,CAAA,YAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA;;;;;;;AApCxB;;;;;;;AASA;;;;;;;;;;;;;;ACHA;AAaA;;;;AC0CA;;;;;;;;;;;;;AA8EA;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA9EgB,gCACI,kCACJ,mBAAmB,sBAEvB,yBACI,gBACL,2BAA2B,WAAW,SAC9C,2BAA2B,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuEzB,iCAAiC,8BACrC,qBACA,4BAA4B,aACrC,4BAA4B"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Options } from "amqplib";
|
|
2
|
-
import { ContractDefinition, WorkerInferConsumerHandlers } from "@amqp-contract/contract";
|
|
2
|
+
import { ContractDefinition, InferConsumerNames, WorkerInferConsumerHandler, WorkerInferConsumerHandlers } from "@amqp-contract/contract";
|
|
3
3
|
|
|
4
4
|
//#region src/worker.d.ts
|
|
5
5
|
|
|
@@ -73,5 +73,134 @@ declare class MessageValidationError extends WorkerError {
|
|
|
73
73
|
constructor(consumerName: string, issues: unknown);
|
|
74
74
|
}
|
|
75
75
|
//#endregion
|
|
76
|
-
|
|
76
|
+
//#region src/handlers.d.ts
|
|
77
|
+
/**
|
|
78
|
+
* Define a type-safe handler for a specific consumer in a contract.
|
|
79
|
+
*
|
|
80
|
+
* This utility allows you to define handlers outside of the worker creation,
|
|
81
|
+
* providing better code organization and reusability.
|
|
82
|
+
*
|
|
83
|
+
* @template TContract - The contract definition type
|
|
84
|
+
* @template TName - The consumer name from the contract
|
|
85
|
+
* @param contract - The contract definition containing the consumer
|
|
86
|
+
* @param consumerName - The name of the consumer from the contract
|
|
87
|
+
* @param handler - The async handler function that processes messages
|
|
88
|
+
* @returns A type-safe handler that can be used with TypedAmqpWorker
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* import { defineHandler } from '@amqp-contract/worker';
|
|
93
|
+
* import { orderContract } from './contract';
|
|
94
|
+
*
|
|
95
|
+
* // Define handler outside of worker creation
|
|
96
|
+
* const processOrderHandler = defineHandler(
|
|
97
|
+
* orderContract,
|
|
98
|
+
* 'processOrder',
|
|
99
|
+
* async (message) => {
|
|
100
|
+
* // message is fully typed based on the contract
|
|
101
|
+
* console.log('Processing order:', message.orderId);
|
|
102
|
+
* await processPayment(message);
|
|
103
|
+
* }
|
|
104
|
+
* );
|
|
105
|
+
*
|
|
106
|
+
* // Use the handler in worker
|
|
107
|
+
* const worker = await TypedAmqpWorker.create({
|
|
108
|
+
* contract: orderContract,
|
|
109
|
+
* handlers: {
|
|
110
|
+
* processOrder: processOrderHandler,
|
|
111
|
+
* },
|
|
112
|
+
* connection: 'amqp://localhost',
|
|
113
|
+
* });
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* // Define multiple handlers
|
|
119
|
+
* const processOrderHandler = defineHandler(
|
|
120
|
+
* orderContract,
|
|
121
|
+
* 'processOrder',
|
|
122
|
+
* async (message) => {
|
|
123
|
+
* await processOrder(message);
|
|
124
|
+
* }
|
|
125
|
+
* );
|
|
126
|
+
*
|
|
127
|
+
* const notifyOrderHandler = defineHandler(
|
|
128
|
+
* orderContract,
|
|
129
|
+
* 'notifyOrder',
|
|
130
|
+
* async (message) => {
|
|
131
|
+
* await sendNotification(message);
|
|
132
|
+
* }
|
|
133
|
+
* );
|
|
134
|
+
*
|
|
135
|
+
* // Compose handlers
|
|
136
|
+
* const worker = await TypedAmqpWorker.create({
|
|
137
|
+
* contract: orderContract,
|
|
138
|
+
* handlers: {
|
|
139
|
+
* processOrder: processOrderHandler,
|
|
140
|
+
* notifyOrder: notifyOrderHandler,
|
|
141
|
+
* },
|
|
142
|
+
* connection: 'amqp://localhost',
|
|
143
|
+
* });
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
declare function defineHandler<TContract extends ContractDefinition, TName extends InferConsumerNames<TContract>>(contract: TContract, consumerName: TName, handler: WorkerInferConsumerHandler<TContract, TName>): WorkerInferConsumerHandler<TContract, TName>;
|
|
147
|
+
/**
|
|
148
|
+
* Define multiple type-safe handlers for consumers in a contract.
|
|
149
|
+
*
|
|
150
|
+
* This utility allows you to define all handlers at once outside of the worker creation,
|
|
151
|
+
* ensuring type safety and providing better code organization.
|
|
152
|
+
*
|
|
153
|
+
* @template TContract - The contract definition type
|
|
154
|
+
* @param contract - The contract definition containing the consumers
|
|
155
|
+
* @param handlers - An object with async handler functions for each consumer
|
|
156
|
+
* @returns A type-safe handlers object that can be used with TypedAmqpWorker
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* import { defineHandlers } from '@amqp-contract/worker';
|
|
161
|
+
* import { orderContract } from './contract';
|
|
162
|
+
*
|
|
163
|
+
* // Define all handlers at once
|
|
164
|
+
* const handlers = defineHandlers(orderContract, {
|
|
165
|
+
* processOrder: async (message) => {
|
|
166
|
+
* // message is fully typed based on the contract
|
|
167
|
+
* console.log('Processing order:', message.orderId);
|
|
168
|
+
* await processPayment(message);
|
|
169
|
+
* },
|
|
170
|
+
* notifyOrder: async (message) => {
|
|
171
|
+
* await sendNotification(message);
|
|
172
|
+
* },
|
|
173
|
+
* shipOrder: async (message) => {
|
|
174
|
+
* await prepareShipment(message);
|
|
175
|
+
* },
|
|
176
|
+
* });
|
|
177
|
+
*
|
|
178
|
+
* // Use the handlers in worker
|
|
179
|
+
* const worker = await TypedAmqpWorker.create({
|
|
180
|
+
* contract: orderContract,
|
|
181
|
+
* handlers,
|
|
182
|
+
* connection: 'amqp://localhost',
|
|
183
|
+
* });
|
|
184
|
+
* ```
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```typescript
|
|
188
|
+
* // Separate handler definitions for better organization
|
|
189
|
+
* async function handleProcessOrder(message: WorkerInferConsumerInput<typeof orderContract, 'processOrder'>) {
|
|
190
|
+
* await processOrder(message);
|
|
191
|
+
* }
|
|
192
|
+
*
|
|
193
|
+
* async function handleNotifyOrder(message: WorkerInferConsumerInput<typeof orderContract, 'notifyOrder'>) {
|
|
194
|
+
* await sendNotification(message);
|
|
195
|
+
* }
|
|
196
|
+
*
|
|
197
|
+
* const handlers = defineHandlers(orderContract, {
|
|
198
|
+
* processOrder: handleProcessOrder,
|
|
199
|
+
* notifyOrder: handleNotifyOrder,
|
|
200
|
+
* });
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
declare function defineHandlers<TContract extends ContractDefinition>(contract: TContract, handlers: WorkerInferConsumerHandlers<TContract>): WorkerInferConsumerHandlers<TContract>;
|
|
204
|
+
//#endregion
|
|
205
|
+
export { type CreateWorkerOptions, MessageValidationError, TechnicalError, TypedAmqpWorker, defineHandler, defineHandlers };
|
|
77
206
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/worker.ts","../src/errors.ts"],"sourcesContent":[],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/worker.ts","../src/errors.ts","../src/handlers.ts"],"sourcesContent":[],"mappings":";;;;;;;AAeA;AAAuD,UAAtC,mBAAsC,CAAA,kBAAA,kBAAA,CAAA,CAAA;EAC3C,QAAA,EAAA,SAAA;EAC4B,QAAA,EAA5B,2BAA4B,CAAA,SAAA,CAAA;EAA5B,UAAA,EAAA,MAAA,GACW,OAAA,CAAQ,OADnB;;;AAOZ;;AAewC,cAf3B,eAe2B,CAAA,kBAfO,kBAeP,CAAA,CAAA;EACP,iBAAA,QAAA;EAApB,iBAAA,QAAA;EACgB,iBAAA,iBAAA;EAAhB,QAAA,OAAA;EAAR,QAAA,UAAA;EAUY,QAAA,YAAA;EAAO,QAAA,WAAA,CAAA;;;;;EC9BX,OAAA,MAAA,CAAA,kBDkB2B,kBClBO,CAAA,CAAA,OAAA,EDmBlC,mBCnBkC,CDmBd,SCnBc,CAAA,CAAA,EDoB1C,OCpB0C,CDoBlC,eCpBkC,CDoBlB,SCpBkB,CAAA,CAAA;EAalC;;;WDiBI;EEyBD;;;EAEA,QAAA,IAAA;EAEJ;;;EAEqC,QAAA,UAAA;EAAtC;;;EACR,QAAA,OAAA;EAA0B;AAuE7B;;EACY,QAAA,aAAA;;;;;;;uBDxJG,WAAA,SAAoB,KAAA;EDYlB,UAAA,WAAA,CAAmB,OAAA,EAAA,MAAA;;;;;;AAGE,cCGzB,cAAA,SAAuB,WAAA,CDHE;EAMzB,SAAA,KAAA,CAAA,EAAA,OAAe,GAAA,SAAA;EAAmB,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,OAAA,GAAA,SAAA;;;;;AAiBlC,cCPA,sBAAA,SAA+B,WAAA,CDO/B;EAAR,SAAA,YAAA,EAAA,MAAA;EAUY,SAAA,MAAA,EAAA,OAAA;EAAO,WAAA,CAAA,YAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA;;;;;;;AApCxB;;;;;;;AASA;;;;;;;;;;;;;;ACHA;AAaA;;;;AC0CA;;;;;;;;;;;;;AA8EA;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA9EgB,gCACI,kCACJ,mBAAmB,sBAEvB,yBACI,gBACL,2BAA2B,WAAW,SAC9C,2BAA2B,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuEzB,iCAAiC,8BACrC,qBACA,4BAA4B,aACrC,4BAA4B"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { connect } from "amqplib";
|
|
2
|
+
import { setupInfra } from "@amqp-contract/core";
|
|
2
3
|
import { Result } from "@swan-io/boxed";
|
|
3
4
|
|
|
4
5
|
//#region src/errors.ts
|
|
@@ -80,22 +81,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
80
81
|
async init() {
|
|
81
82
|
this.connection = await connect(this.connectionOptions);
|
|
82
83
|
this.channel = await this.connection.createChannel();
|
|
83
|
-
|
|
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
|
-
}
|
|
84
|
+
await setupInfra(this.channel, this.contract);
|
|
99
85
|
}
|
|
100
86
|
/**
|
|
101
87
|
* Start consuming messages for all consumers
|
|
@@ -122,7 +108,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
122
108
|
const handler = this.handlers[consumerName];
|
|
123
109
|
if (!handler) throw new Error(`Handler for "${String(consumerName)}" not provided`);
|
|
124
110
|
if (consumerDef.prefetch !== void 0) await this.channel.prefetch(consumerDef.prefetch);
|
|
125
|
-
const result = await this.channel.consume(consumerDef.queue, async (msg) => {
|
|
111
|
+
const result = await this.channel.consume(consumerDef.queue.name, async (msg) => {
|
|
126
112
|
if (!msg) return;
|
|
127
113
|
const parseResult = Result.fromExecution(() => JSON.parse(msg.content.toString()));
|
|
128
114
|
if (parseResult.isError()) {
|
|
@@ -131,7 +117,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
131
117
|
return;
|
|
132
118
|
}
|
|
133
119
|
const content = parseResult.value;
|
|
134
|
-
const rawValidation = consumerDef.message["~standard"].validate(content);
|
|
120
|
+
const rawValidation = consumerDef.message.payload["~standard"].validate(content);
|
|
135
121
|
const resolvedValidation = rawValidation instanceof Promise ? await rawValidation : rawValidation;
|
|
136
122
|
const validationResult = typeof resolvedValidation === "object" && resolvedValidation !== null && "issues" in resolvedValidation && resolvedValidation.issues ? Result.Error(new MessageValidationError(String(consumerName), resolvedValidation.issues)) : Result.Ok(typeof resolvedValidation === "object" && resolvedValidation !== null && "value" in resolvedValidation ? resolvedValidation.value : content);
|
|
137
123
|
if (validationResult.isError()) {
|
|
@@ -161,5 +147,151 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
161
147
|
};
|
|
162
148
|
|
|
163
149
|
//#endregion
|
|
164
|
-
|
|
150
|
+
//#region src/handlers.ts
|
|
151
|
+
/**
|
|
152
|
+
* Define a type-safe handler for a specific consumer in a contract.
|
|
153
|
+
*
|
|
154
|
+
* This utility allows you to define handlers outside of the worker creation,
|
|
155
|
+
* providing better code organization and reusability.
|
|
156
|
+
*
|
|
157
|
+
* @template TContract - The contract definition type
|
|
158
|
+
* @template TName - The consumer name from the contract
|
|
159
|
+
* @param contract - The contract definition containing the consumer
|
|
160
|
+
* @param consumerName - The name of the consumer from the contract
|
|
161
|
+
* @param handler - The async handler function that processes messages
|
|
162
|
+
* @returns A type-safe handler that can be used with TypedAmqpWorker
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* import { defineHandler } from '@amqp-contract/worker';
|
|
167
|
+
* import { orderContract } from './contract';
|
|
168
|
+
*
|
|
169
|
+
* // Define handler outside of worker creation
|
|
170
|
+
* const processOrderHandler = defineHandler(
|
|
171
|
+
* orderContract,
|
|
172
|
+
* 'processOrder',
|
|
173
|
+
* async (message) => {
|
|
174
|
+
* // message is fully typed based on the contract
|
|
175
|
+
* console.log('Processing order:', message.orderId);
|
|
176
|
+
* await processPayment(message);
|
|
177
|
+
* }
|
|
178
|
+
* );
|
|
179
|
+
*
|
|
180
|
+
* // Use the handler in worker
|
|
181
|
+
* const worker = await TypedAmqpWorker.create({
|
|
182
|
+
* contract: orderContract,
|
|
183
|
+
* handlers: {
|
|
184
|
+
* processOrder: processOrderHandler,
|
|
185
|
+
* },
|
|
186
|
+
* connection: 'amqp://localhost',
|
|
187
|
+
* });
|
|
188
|
+
* ```
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```typescript
|
|
192
|
+
* // Define multiple handlers
|
|
193
|
+
* const processOrderHandler = defineHandler(
|
|
194
|
+
* orderContract,
|
|
195
|
+
* 'processOrder',
|
|
196
|
+
* async (message) => {
|
|
197
|
+
* await processOrder(message);
|
|
198
|
+
* }
|
|
199
|
+
* );
|
|
200
|
+
*
|
|
201
|
+
* const notifyOrderHandler = defineHandler(
|
|
202
|
+
* orderContract,
|
|
203
|
+
* 'notifyOrder',
|
|
204
|
+
* async (message) => {
|
|
205
|
+
* await sendNotification(message);
|
|
206
|
+
* }
|
|
207
|
+
* );
|
|
208
|
+
*
|
|
209
|
+
* // Compose handlers
|
|
210
|
+
* const worker = await TypedAmqpWorker.create({
|
|
211
|
+
* contract: orderContract,
|
|
212
|
+
* handlers: {
|
|
213
|
+
* processOrder: processOrderHandler,
|
|
214
|
+
* notifyOrder: notifyOrderHandler,
|
|
215
|
+
* },
|
|
216
|
+
* connection: 'amqp://localhost',
|
|
217
|
+
* });
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
function defineHandler(contract, consumerName, handler) {
|
|
221
|
+
const consumers = contract.consumers;
|
|
222
|
+
if (!consumers || !(consumerName in consumers)) {
|
|
223
|
+
const availableConsumers = consumers ? Object.keys(consumers) : [];
|
|
224
|
+
const available = availableConsumers.length > 0 ? availableConsumers.join(", ") : "none";
|
|
225
|
+
throw new Error(`Consumer "${String(consumerName)}" not found in contract. Available consumers: ${available}`);
|
|
226
|
+
}
|
|
227
|
+
return handler;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Define multiple type-safe handlers for consumers in a contract.
|
|
231
|
+
*
|
|
232
|
+
* This utility allows you to define all handlers at once outside of the worker creation,
|
|
233
|
+
* ensuring type safety and providing better code organization.
|
|
234
|
+
*
|
|
235
|
+
* @template TContract - The contract definition type
|
|
236
|
+
* @param contract - The contract definition containing the consumers
|
|
237
|
+
* @param handlers - An object with async handler functions for each consumer
|
|
238
|
+
* @returns A type-safe handlers object that can be used with TypedAmqpWorker
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* import { defineHandlers } from '@amqp-contract/worker';
|
|
243
|
+
* import { orderContract } from './contract';
|
|
244
|
+
*
|
|
245
|
+
* // Define all handlers at once
|
|
246
|
+
* const handlers = defineHandlers(orderContract, {
|
|
247
|
+
* processOrder: async (message) => {
|
|
248
|
+
* // message is fully typed based on the contract
|
|
249
|
+
* console.log('Processing order:', message.orderId);
|
|
250
|
+
* await processPayment(message);
|
|
251
|
+
* },
|
|
252
|
+
* notifyOrder: async (message) => {
|
|
253
|
+
* await sendNotification(message);
|
|
254
|
+
* },
|
|
255
|
+
* shipOrder: async (message) => {
|
|
256
|
+
* await prepareShipment(message);
|
|
257
|
+
* },
|
|
258
|
+
* });
|
|
259
|
+
*
|
|
260
|
+
* // Use the handlers in worker
|
|
261
|
+
* const worker = await TypedAmqpWorker.create({
|
|
262
|
+
* contract: orderContract,
|
|
263
|
+
* handlers,
|
|
264
|
+
* connection: 'amqp://localhost',
|
|
265
|
+
* });
|
|
266
|
+
* ```
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```typescript
|
|
270
|
+
* // Separate handler definitions for better organization
|
|
271
|
+
* async function handleProcessOrder(message: WorkerInferConsumerInput<typeof orderContract, 'processOrder'>) {
|
|
272
|
+
* await processOrder(message);
|
|
273
|
+
* }
|
|
274
|
+
*
|
|
275
|
+
* async function handleNotifyOrder(message: WorkerInferConsumerInput<typeof orderContract, 'notifyOrder'>) {
|
|
276
|
+
* await sendNotification(message);
|
|
277
|
+
* }
|
|
278
|
+
*
|
|
279
|
+
* const handlers = defineHandlers(orderContract, {
|
|
280
|
+
* processOrder: handleProcessOrder,
|
|
281
|
+
* notifyOrder: handleNotifyOrder,
|
|
282
|
+
* });
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
function defineHandlers(contract, handlers) {
|
|
286
|
+
const consumers = contract.consumers;
|
|
287
|
+
const availableConsumers = consumers ? Object.keys(consumers) : [];
|
|
288
|
+
for (const handlerName of Object.keys(handlers)) if (!consumers || !(handlerName in consumers)) {
|
|
289
|
+
const available = availableConsumers.length > 0 ? availableConsumers.join(", ") : "none";
|
|
290
|
+
throw new Error(`Consumer "${handlerName}" not found in contract. Available consumers: ${available}`);
|
|
291
|
+
}
|
|
292
|
+
return handlers;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
//#endregion
|
|
296
|
+
export { MessageValidationError, TechnicalError, TypedAmqpWorker, defineHandler, defineHandlers };
|
|
165
297
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["cause?: unknown","consumerName: string","issues: unknown","contract: TContract","handlers: WorkerInferConsumerHandlers<TContract>","connectionOptions: string | Options.Connect","validationResult: Result<unknown, MessageValidationError>"],"sources":["../src/errors.ts","../src/worker.ts"],"sourcesContent":["/**\n * Base error class for worker errors\n */\nabstract class WorkerError extends Error {\n protected constructor(message: string) {\n super(message);\n this.name = \"WorkerError\";\n // Node.js specific stack trace capture\n const ErrorConstructor = Error as unknown as {\n captureStackTrace?: (target: object, constructor: Function) => void;\n };\n if (typeof ErrorConstructor.captureStackTrace === \"function\") {\n ErrorConstructor.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * Error for technical/runtime failures in worker operations\n * This includes validation failures, parsing failures, and processing failures\n */\nexport class TechnicalError extends WorkerError {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"TechnicalError\";\n }\n}\n\n/**\n * Error thrown when message validation fails\n */\nexport class MessageValidationError extends WorkerError {\n constructor(\n public readonly consumerName: string,\n public readonly issues: unknown,\n ) {\n super(`Message validation failed for consumer \"${consumerName}\"`);\n this.name = \"MessageValidationError\";\n }\n}\n","import { connect } from \"amqplib\";\nimport type { Channel, ChannelModel, ConsumeMessage, Options } from \"amqplib\";\nimport type {\n ContractDefinition,\n InferConsumerNames,\n WorkerInferConsumerHandlers,\n WorkerInferConsumerInput,\n} from \"@amqp-contract/contract\";\nimport { Result } from \"@swan-io/boxed\";\nimport { MessageValidationError, TechnicalError } from \"./errors.js\";\n\n/**\n * Options for creating a worker\n */\nexport interface CreateWorkerOptions<TContract extends ContractDefinition> {\n contract: TContract;\n handlers: WorkerInferConsumerHandlers<TContract>;\n connection: string | Options.Connect;\n}\n\n/**\n * Type-safe AMQP worker for consuming messages\n */\nexport class TypedAmqpWorker<TContract extends ContractDefinition> {\n private channel: Channel | null = null;\n private connection: ChannelModel | null = null;\n private consumerTags: string[] = [];\n\n private constructor(\n private readonly contract: TContract,\n private readonly handlers: WorkerInferConsumerHandlers<TContract>,\n private readonly connectionOptions: string | Options.Connect,\n ) {}\n\n /**\n * Create a type-safe AMQP worker from a contract\n * The worker will automatically connect and start consuming all messages\n */\n static async create<TContract extends ContractDefinition>(\n options: CreateWorkerOptions<TContract>,\n ): Promise<TypedAmqpWorker<TContract>> {\n const worker = new TypedAmqpWorker(options.contract, options.handlers, options.connection);\n await worker.init();\n await worker.consumeAll();\n return worker;\n }\n\n /**\n * Close the connection\n */\n async close(): Promise<void> {\n await this.stopConsuming();\n\n if (this.channel) {\n await this.channel.close();\n this.channel = null;\n }\n\n if (this.connection) {\n await this.connection.close();\n this.connection = null;\n }\n }\n\n /**\n * Connect to AMQP broker\n */\n private async init(): Promise<void> {\n this.connection = await connect(this.connectionOptions);\n this.channel = await this.connection.createChannel();\n\n // Setup exchanges\n if (this.contract.exchanges) {\n for (const exchange of Object.values(this.contract.exchanges)) {\n await this.channel.assertExchange(exchange.name, exchange.type, {\n durable: exchange.durable,\n autoDelete: exchange.autoDelete,\n internal: exchange.internal,\n arguments: exchange.arguments,\n });\n }\n }\n\n // Setup queues\n if (this.contract.queues) {\n for (const queue of Object.values(this.contract.queues)) {\n await this.channel.assertQueue(queue.name, {\n durable: queue.durable,\n exclusive: queue.exclusive,\n autoDelete: queue.autoDelete,\n arguments: queue.arguments,\n });\n }\n }\n\n // Setup bindings\n if (this.contract.bindings) {\n for (const binding of Object.values(this.contract.bindings)) {\n if (binding.type === \"queue\") {\n await this.channel.bindQueue(\n binding.queue,\n binding.exchange,\n binding.routingKey ?? \"\",\n binding.arguments,\n );\n } else if (binding.type === \"exchange\") {\n await this.channel.bindExchange(\n binding.destination,\n binding.source,\n binding.routingKey ?? \"\",\n binding.arguments,\n );\n }\n }\n }\n }\n\n /**\n * Start consuming messages for all consumers\n */\n private async consumeAll(): Promise<void> {\n if (!this.contract.consumers) {\n throw new Error(\"No consumers defined in contract\");\n }\n\n const consumerNames = Object.keys(this.contract.consumers) as InferConsumerNames<TContract>[];\n\n for (const consumerName of consumerNames) {\n await this.consume(consumerName);\n }\n }\n\n /**\n * Start consuming messages for a specific consumer\n */\n private async consume<TName extends InferConsumerNames<TContract>>(\n consumerName: TName,\n ): Promise<void> {\n if (!this.channel) {\n throw new Error(\n \"Worker not initialized. Use TypedAmqpWorker.create() to obtain an initialized worker instance.\",\n );\n }\n\n const consumers = this.contract.consumers as Record<string, unknown>;\n if (!consumers) {\n throw new Error(\"No consumers defined in contract\");\n }\n\n const consumer = consumers[consumerName as string];\n if (!consumer || typeof consumer !== \"object\") {\n const availableConsumers = Object.keys(consumers);\n const available = availableConsumers.length > 0 ? availableConsumers.join(\", \") : \"none\";\n throw new Error(\n `Consumer not found: \"${String(consumerName)}\". Available consumers: ${available}`,\n );\n }\n\n const consumerDef = consumer as {\n queue: string;\n message: { \"~standard\": { validate: (value: unknown) => unknown } };\n prefetch?: number;\n noAck?: boolean;\n };\n\n const handler = this.handlers[consumerName];\n if (!handler) {\n throw new Error(`Handler for \"${String(consumerName)}\" not provided`);\n }\n\n // Set prefetch if specified\n if (consumerDef.prefetch !== undefined) {\n await this.channel.prefetch(consumerDef.prefetch);\n }\n\n // Start consuming\n const result = await this.channel.consume(\n consumerDef.queue,\n async (msg: ConsumeMessage | null) => {\n if (!msg) {\n return;\n }\n\n // Parse message\n const parseResult = Result.fromExecution(() => JSON.parse(msg.content.toString()));\n\n if (parseResult.isError()) {\n console.error(\n new TechnicalError(\n `Error parsing message for consumer \"${String(consumerName)}\"`,\n parseResult.error,\n ),\n );\n // Reject message with no requeue (malformed JSON)\n this.channel?.nack(msg, false, false);\n return;\n }\n\n const content = parseResult.value;\n\n // Validate message using schema (supports sync and async validators)\n const rawValidation = consumerDef.message[\"~standard\"].validate(content);\n const resolvedValidation =\n rawValidation instanceof Promise ? await rawValidation : rawValidation;\n const validationResult: Result<unknown, MessageValidationError> =\n typeof resolvedValidation === \"object\" &&\n resolvedValidation !== null &&\n \"issues\" in resolvedValidation &&\n resolvedValidation.issues\n ? Result.Error(\n new MessageValidationError(String(consumerName), resolvedValidation.issues),\n )\n : Result.Ok(\n typeof resolvedValidation === \"object\" &&\n resolvedValidation !== null &&\n \"value\" in resolvedValidation\n ? resolvedValidation.value\n : content,\n );\n\n if (validationResult.isError()) {\n console.error(validationResult.error);\n // Reject message with no requeue (validation failed)\n this.channel?.nack(msg, false, false);\n return;\n }\n\n const validatedMessage = validationResult.value as WorkerInferConsumerInput<\n TContract,\n TName\n >;\n\n // Call handler and wait for Promise to resolve\n try {\n await handler(validatedMessage);\n\n // Acknowledge message if not in noAck mode\n if (!consumerDef.noAck) {\n this.channel?.ack(msg);\n }\n } catch (error) {\n console.error(\n new TechnicalError(\n `Error processing message for consumer \"${String(consumerName)}\"`,\n error,\n ),\n );\n // Reject message and requeue (handler failed)\n this.channel?.nack(msg, false, true);\n }\n },\n {\n noAck: consumerDef.noAck ?? false,\n },\n );\n\n this.consumerTags.push(result.consumerTag);\n }\n\n /**\n * Stop consuming messages\n */\n private async stopConsuming(): Promise<void> {\n if (!this.channel) {\n return;\n }\n\n for (const tag of this.consumerTags) {\n await this.channel.cancel(tag);\n }\n\n this.consumerTags = [];\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["cause?: unknown","consumerName: string","issues: unknown","contract: TContract","handlers: WorkerInferConsumerHandlers<TContract>","connectionOptions: string | Options.Connect","validationResult: Result<unknown, MessageValidationError>"],"sources":["../src/errors.ts","../src/worker.ts","../src/handlers.ts"],"sourcesContent":["/**\n * Base error class for worker errors\n */\nabstract class WorkerError extends Error {\n protected constructor(message: string) {\n super(message);\n this.name = \"WorkerError\";\n // Node.js specific stack trace capture\n const ErrorConstructor = Error as unknown as {\n captureStackTrace?: (target: object, constructor: Function) => void;\n };\n if (typeof ErrorConstructor.captureStackTrace === \"function\") {\n ErrorConstructor.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * Error for technical/runtime failures in worker operations\n * This includes validation failures, parsing failures, and processing failures\n */\nexport class TechnicalError extends WorkerError {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"TechnicalError\";\n }\n}\n\n/**\n * Error thrown when message validation fails\n */\nexport class MessageValidationError extends WorkerError {\n constructor(\n public readonly consumerName: string,\n public readonly issues: unknown,\n ) {\n super(`Message validation failed for consumer \"${consumerName}\"`);\n this.name = \"MessageValidationError\";\n }\n}\n","import { connect } from \"amqplib\";\nimport type { Channel, ChannelModel, ConsumeMessage, Options } from \"amqplib\";\nimport type {\n ContractDefinition,\n InferConsumerNames,\n WorkerInferConsumerHandlers,\n WorkerInferConsumerInput,\n} from \"@amqp-contract/contract\";\nimport { setupInfra } from \"@amqp-contract/core\";\nimport { Result } from \"@swan-io/boxed\";\nimport { MessageValidationError, TechnicalError } from \"./errors.js\";\n\n/**\n * Options for creating a worker\n */\nexport interface CreateWorkerOptions<TContract extends ContractDefinition> {\n contract: TContract;\n handlers: WorkerInferConsumerHandlers<TContract>;\n connection: string | Options.Connect;\n}\n\n/**\n * Type-safe AMQP worker for consuming messages\n */\nexport class TypedAmqpWorker<TContract extends ContractDefinition> {\n private channel: Channel | null = null;\n private connection: ChannelModel | null = null;\n private consumerTags: string[] = [];\n\n private constructor(\n private readonly contract: TContract,\n private readonly handlers: WorkerInferConsumerHandlers<TContract>,\n private readonly connectionOptions: string | Options.Connect,\n ) {}\n\n /**\n * Create a type-safe AMQP worker from a contract\n * The worker will automatically connect and start consuming all messages\n */\n static async create<TContract extends ContractDefinition>(\n options: CreateWorkerOptions<TContract>,\n ): Promise<TypedAmqpWorker<TContract>> {\n const worker = new TypedAmqpWorker(options.contract, options.handlers, options.connection);\n await worker.init();\n await worker.consumeAll();\n return worker;\n }\n\n /**\n * Close the connection\n */\n async close(): Promise<void> {\n await this.stopConsuming();\n\n if (this.channel) {\n await this.channel.close();\n this.channel = null;\n }\n\n if (this.connection) {\n await this.connection.close();\n this.connection = null;\n }\n }\n\n /**\n * Connect to AMQP broker\n */\n private async init(): Promise<void> {\n this.connection = await connect(this.connectionOptions);\n this.channel = await this.connection.createChannel();\n\n // Setup exchanges, queues, and bindings\n await setupInfra(this.channel, this.contract);\n }\n\n /**\n * Start consuming messages for all consumers\n */\n private async consumeAll(): Promise<void> {\n if (!this.contract.consumers) {\n throw new Error(\"No consumers defined in contract\");\n }\n\n const consumerNames = Object.keys(this.contract.consumers) as InferConsumerNames<TContract>[];\n\n for (const consumerName of consumerNames) {\n await this.consume(consumerName);\n }\n }\n\n /**\n * Start consuming messages for a specific consumer\n */\n private async consume<TName extends InferConsumerNames<TContract>>(\n consumerName: TName,\n ): Promise<void> {\n if (!this.channel) {\n throw new Error(\n \"Worker not initialized. Use TypedAmqpWorker.create() to obtain an initialized worker instance.\",\n );\n }\n\n const consumers = this.contract.consumers as Record<string, unknown>;\n if (!consumers) {\n throw new Error(\"No consumers defined in contract\");\n }\n\n const consumer = consumers[consumerName as string];\n if (!consumer || typeof consumer !== \"object\") {\n const availableConsumers = Object.keys(consumers);\n const available = availableConsumers.length > 0 ? availableConsumers.join(\", \") : \"none\";\n throw new Error(\n `Consumer not found: \"${String(consumerName)}\". Available consumers: ${available}`,\n );\n }\n\n const consumerDef = consumer as {\n queue: { name: string };\n message: { payload: { \"~standard\": { validate: (value: unknown) => unknown } } };\n prefetch?: number;\n noAck?: boolean;\n };\n\n const handler = this.handlers[consumerName];\n if (!handler) {\n throw new Error(`Handler for \"${String(consumerName)}\" not provided`);\n }\n\n // Set prefetch if specified\n if (consumerDef.prefetch !== undefined) {\n await this.channel.prefetch(consumerDef.prefetch);\n }\n\n // Start consuming\n const result = await this.channel.consume(\n consumerDef.queue.name,\n async (msg: ConsumeMessage | null) => {\n if (!msg) {\n return;\n }\n\n // Parse message\n const parseResult = Result.fromExecution(() => JSON.parse(msg.content.toString()));\n\n if (parseResult.isError()) {\n console.error(\n new TechnicalError(\n `Error parsing message for consumer \"${String(consumerName)}\"`,\n parseResult.error,\n ),\n );\n // Reject message with no requeue (malformed JSON)\n this.channel?.nack(msg, false, false);\n return;\n }\n\n const content = parseResult.value;\n\n // Validate message using schema (supports sync and async validators)\n const rawValidation = consumerDef.message.payload[\"~standard\"].validate(content);\n const resolvedValidation =\n rawValidation instanceof Promise ? await rawValidation : rawValidation;\n const validationResult: Result<unknown, MessageValidationError> =\n typeof resolvedValidation === \"object\" &&\n resolvedValidation !== null &&\n \"issues\" in resolvedValidation &&\n resolvedValidation.issues\n ? Result.Error(\n new MessageValidationError(String(consumerName), resolvedValidation.issues),\n )\n : Result.Ok(\n typeof resolvedValidation === \"object\" &&\n resolvedValidation !== null &&\n \"value\" in resolvedValidation\n ? resolvedValidation.value\n : content,\n );\n\n if (validationResult.isError()) {\n console.error(validationResult.error);\n // Reject message with no requeue (validation failed)\n this.channel?.nack(msg, false, false);\n return;\n }\n\n const validatedMessage = validationResult.value as WorkerInferConsumerInput<\n TContract,\n TName\n >;\n\n // Call handler and wait for Promise to resolve\n try {\n await handler(validatedMessage);\n\n // Acknowledge message if not in noAck mode\n if (!consumerDef.noAck) {\n this.channel?.ack(msg);\n }\n } catch (error) {\n console.error(\n new TechnicalError(\n `Error processing message for consumer \"${String(consumerName)}\"`,\n error,\n ),\n );\n // Reject message and requeue (handler failed)\n this.channel?.nack(msg, false, true);\n }\n },\n {\n noAck: consumerDef.noAck ?? false,\n },\n );\n\n this.consumerTags.push(result.consumerTag);\n }\n\n /**\n * Stop consuming messages\n */\n private async stopConsuming(): Promise<void> {\n if (!this.channel) {\n return;\n }\n\n for (const tag of this.consumerTags) {\n await this.channel.cancel(tag);\n }\n\n this.consumerTags = [];\n }\n}\n","import type {\n ContractDefinition,\n InferConsumerNames,\n WorkerInferConsumerHandler,\n WorkerInferConsumerHandlers,\n} from \"@amqp-contract/contract\";\n\n/**\n * Define a type-safe handler for a specific consumer in a contract.\n *\n * This utility allows you to define handlers outside of the worker creation,\n * providing better code organization and reusability.\n *\n * @template TContract - The contract definition type\n * @template TName - The consumer name from the contract\n * @param contract - The contract definition containing the consumer\n * @param consumerName - The name of the consumer from the contract\n * @param handler - The async handler function that processes messages\n * @returns A type-safe handler that can be used with TypedAmqpWorker\n *\n * @example\n * ```typescript\n * import { defineHandler } from '@amqp-contract/worker';\n * import { orderContract } from './contract';\n *\n * // Define handler outside of worker creation\n * const processOrderHandler = defineHandler(\n * orderContract,\n * 'processOrder',\n * async (message) => {\n * // message is fully typed based on the contract\n * console.log('Processing order:', message.orderId);\n * await processPayment(message);\n * }\n * );\n *\n * // Use the handler in worker\n * const worker = await TypedAmqpWorker.create({\n * contract: orderContract,\n * handlers: {\n * processOrder: processOrderHandler,\n * },\n * connection: 'amqp://localhost',\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Define multiple handlers\n * const processOrderHandler = defineHandler(\n * orderContract,\n * 'processOrder',\n * async (message) => {\n * await processOrder(message);\n * }\n * );\n *\n * const notifyOrderHandler = defineHandler(\n * orderContract,\n * 'notifyOrder',\n * async (message) => {\n * await sendNotification(message);\n * }\n * );\n *\n * // Compose handlers\n * const worker = await TypedAmqpWorker.create({\n * contract: orderContract,\n * handlers: {\n * processOrder: processOrderHandler,\n * notifyOrder: notifyOrderHandler,\n * },\n * connection: 'amqp://localhost',\n * });\n * ```\n */\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: WorkerInferConsumerHandler<TContract, TName>,\n): WorkerInferConsumerHandler<TContract, TName> {\n // Validate that the consumer exists in the contract\n const consumers = contract.consumers as Record<string, unknown> | undefined;\n if (!consumers || !((consumerName as string) in consumers)) {\n const availableConsumers = consumers ? Object.keys(consumers) : [];\n const available = availableConsumers.length > 0 ? availableConsumers.join(\", \") : \"none\";\n throw new Error(\n `Consumer \"${String(consumerName)}\" not found in contract. Available consumers: ${available}`,\n );\n }\n\n // Return the handler as-is, with type checking enforced\n return handler;\n}\n\n/**\n * Define multiple type-safe handlers for consumers in a contract.\n *\n * This utility allows you to define all handlers at once outside of the worker creation,\n * ensuring type safety and providing better code organization.\n *\n * @template TContract - The contract definition type\n * @param contract - The contract definition containing the consumers\n * @param handlers - An object with async handler functions for each consumer\n * @returns A type-safe handlers object that can be used with TypedAmqpWorker\n *\n * @example\n * ```typescript\n * import { defineHandlers } from '@amqp-contract/worker';\n * import { orderContract } from './contract';\n *\n * // Define all handlers at once\n * const handlers = defineHandlers(orderContract, {\n * processOrder: async (message) => {\n * // message is fully typed based on the contract\n * console.log('Processing order:', message.orderId);\n * await processPayment(message);\n * },\n * notifyOrder: async (message) => {\n * await sendNotification(message);\n * },\n * shipOrder: async (message) => {\n * await prepareShipment(message);\n * },\n * });\n *\n * // Use the handlers in worker\n * const worker = await TypedAmqpWorker.create({\n * contract: orderContract,\n * handlers,\n * connection: 'amqp://localhost',\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Separate handler definitions for better organization\n * async function handleProcessOrder(message: WorkerInferConsumerInput<typeof orderContract, 'processOrder'>) {\n * await processOrder(message);\n * }\n *\n * async function handleNotifyOrder(message: WorkerInferConsumerInput<typeof orderContract, 'notifyOrder'>) {\n * await sendNotification(message);\n * }\n *\n * const handlers = defineHandlers(orderContract, {\n * processOrder: handleProcessOrder,\n * notifyOrder: handleNotifyOrder,\n * });\n * ```\n */\nexport function defineHandlers<TContract extends ContractDefinition>(\n contract: TContract,\n handlers: WorkerInferConsumerHandlers<TContract>,\n): WorkerInferConsumerHandlers<TContract> {\n // Validate that all consumers in handlers exist in the contract\n const consumers = contract.consumers as Record<string, unknown> | undefined;\n const availableConsumers = consumers ? Object.keys(consumers) : [];\n\n for (const handlerName of Object.keys(handlers)) {\n if (!consumers || !(handlerName in consumers)) {\n const available = availableConsumers.length > 0 ? availableConsumers.join(\", \") : \"none\";\n throw new Error(\n `Consumer \"${handlerName}\" not found in contract. Available consumers: ${available}`,\n );\n }\n }\n\n // Return the handlers as-is, with type checking enforced\n return handlers;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amqp-contract/worker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Worker utilities for consuming messages using amqp-contract",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"amqp",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@swan-io/boxed": "3.2.1",
|
|
45
|
-
"@amqp-contract/contract": "0.
|
|
45
|
+
"@amqp-contract/contract": "0.2.0",
|
|
46
|
+
"@amqp-contract/core": "0.2.0"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@types/amqplib": "0.10.8",
|
|
@@ -53,8 +54,8 @@
|
|
|
53
54
|
"typescript": "5.9.3",
|
|
54
55
|
"vitest": "4.0.16",
|
|
55
56
|
"zod": "4.2.1",
|
|
56
|
-
"@amqp-contract/client": "0.
|
|
57
|
-
"@amqp-contract/testing": "0.
|
|
57
|
+
"@amqp-contract/client": "0.2.0",
|
|
58
|
+
"@amqp-contract/testing": "0.2.0",
|
|
58
59
|
"@amqp-contract/tsconfig": "0.0.0"
|
|
59
60
|
},
|
|
60
61
|
"peerDependencies": {
|