@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/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { connect } from "amqplib";
2
- import { Result } from "@swan-io/boxed";
1
+ import { AmqpClient } from "@amqp-contract/core";
2
+ import { Future, Result } from "@swan-io/boxed";
3
3
 
4
4
  //#region src/errors.ts
5
5
  /**
@@ -39,127 +39,293 @@ 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
- channel = null;
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
- * The worker will automatically connect and start consuming all messages
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 async create(options) {
58
- const worker = new TypedAmqpWorker(options.contract, options.handlers, options.connection);
59
- await worker.init();
60
- await worker.consumeAll();
61
- return worker;
113
+ static create({ contract, handlers, urls, connectionOptions }) {
114
+ const worker = new TypedAmqpWorker(contract, new 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
- async close() {
67
- await this.stopConsuming();
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 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 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
- async consumeAll() {
104
- if (!this.contract.consumers) throw new Error("No consumers defined in contract");
142
+ consumeAll() {
143
+ if (!this.contract.consumers) return Future.value(Result.Error(new TechnicalError("No consumers defined in contract")));
105
144
  const consumerNames = Object.keys(this.contract.consumers);
106
- for (const consumerName of consumerNames) await this.consume(consumerName);
145
+ return Future.all(consumerNames.map((consumerName) => this.consume(consumerName))).map(Result.all).mapOk(() => void 0);
107
146
  }
108
147
  /**
109
148
  * Start consuming messages for a specific consumer
110
149
  */
111
- async consume(consumerName) {
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) throw new Error("No consumers defined in contract");
152
+ if (!consumers) return Future.value(Result.Error(new TechnicalError("No consumers defined in contract")));
115
153
  const consumer = consumers[consumerName];
116
- if (!consumer || typeof consumer !== "object") {
154
+ if (!consumer) {
117
155
  const availableConsumers = Object.keys(consumers);
118
156
  const available = availableConsumers.length > 0 ? availableConsumers.join(", ") : "none";
119
- throw new Error(`Consumer not found: "${String(consumerName)}". Available consumers: ${available}`);
157
+ return Future.value(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) throw new Error(`Handler for "${String(consumerName)}" not provided`);
124
- if (consumerDef.prefetch !== void 0) await this.channel.prefetch(consumerDef.prefetch);
125
- const result = await this.channel.consume(consumerDef.queue, async (msg) => {
126
- if (!msg) return;
160
+ if (!handler) return Future.value(Result.Error(new TechnicalError(`Handler for "${String(consumerName)}" not provided`)));
161
+ return Future.fromPromise(this.amqpClient.channel.consume(consumer.queue.name, async (msg) => {
127
162
  const parseResult = 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?.nack(msg, false, false);
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 ? Result.Error(new MessageValidationError(String(consumerName), resolvedValidation.issues)) : 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 validatedMessage = validationResult.value;
143
- try {
144
- await handler(validatedMessage);
145
- if (!consumerDef.noAck) this.channel?.ack(msg);
146
- } catch (error) {
168
+ const rawValidation = consumer.message.payload["~standard"].validate(parseResult.value);
169
+ await Future.fromPromise(rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation)).mapOkToResult((validationResult) => {
170
+ if (validationResult.issues) return Result.Error(new MessageValidationError(String(consumerName), validationResult.issues));
171
+ return Result.Ok(validationResult.value);
172
+ }).tapError((error) => {
173
+ console.error(error);
174
+ this.amqpClient.channel.nack(msg, false, false);
175
+ }).flatMapOk((validatedMessage) => Future.fromPromise(handler(validatedMessage)).tapError((error) => {
147
176
  console.error(new TechnicalError(`Error processing message for consumer "${String(consumerName)}"`, error));
148
- this.channel?.nack(msg, false, true);
149
- }
150
- }, { noAck: consumerDef.noAck ?? false });
151
- this.consumerTags.push(result.consumerTag);
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
 
163
185
  //#endregion
164
- export { MessageValidationError, TechnicalError, TypedAmqpWorker };
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
+
329
+ //#endregion
330
+ export { MessageValidationError, TechnicalError, TypedAmqpWorker, defineHandler, defineHandlers };
165
331
  //# sourceMappingURL=index.mjs.map
@@ -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","amqpClient: AmqpClient","handlers: WorkerInferConsumerHandlers<TContract>"],"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 type { ContractDefinition, InferConsumerNames } from \"@amqp-contract/contract\";\nimport { AmqpClient } from \"@amqp-contract/core\";\nimport { Future, Result } from \"@swan-io/boxed\";\nimport { MessageValidationError, TechnicalError } from \"./errors.js\";\nimport type { WorkerInferConsumerHandlers, WorkerInferConsumerInput } from \"./types.js\";\nimport type { AmqpConnectionManagerOptions, ConnectionUrl } from \"amqp-connection-manager\";\n\n/**\n * Options for creating a type-safe AMQP worker.\n *\n * @typeParam TContract - The contract definition type\n *\n * @example\n * ```typescript\n * const options: CreateWorkerOptions<typeof contract> = {\n * contract: myContract,\n * handlers: {\n * processOrder: async (message) => {\n * console.log('Processing order:', message.orderId);\n * }\n * },\n * urls: ['amqp://localhost'],\n * connectionOptions: {\n * heartbeatIntervalInSeconds: 30\n * }\n * };\n * ```\n */\nexport type CreateWorkerOptions<TContract extends ContractDefinition> = {\n /** The AMQP contract definition specifying consumers and their message schemas */\n contract: TContract;\n /** Handlers for each consumer defined in the contract */\n handlers: WorkerInferConsumerHandlers<TContract>;\n /** AMQP broker URL(s). Multiple URLs provide failover support */\n urls: ConnectionUrl[];\n /** Optional connection configuration (heartbeat, reconnect settings, etc.) */\n connectionOptions?: AmqpConnectionManagerOptions | undefined;\n};\n\n/**\n * Type-safe AMQP worker for consuming messages from RabbitMQ.\n *\n * This class provides automatic message validation, connection management,\n * and error handling for consuming messages based on a contract definition.\n *\n * @typeParam TContract - The contract definition type\n *\n * @example\n * ```typescript\n * import { TypedAmqpWorker } from '@amqp-contract/worker';\n * import { z } from 'zod';\n *\n * const contract = defineContract({\n * queues: {\n * orderProcessing: defineQueue('order-processing', { durable: true })\n * },\n * consumers: {\n * processOrder: defineConsumer('order-processing', z.object({\n * orderId: z.string(),\n * amount: z.number()\n * }))\n * }\n * });\n *\n * const worker = await TypedAmqpWorker.create({\n * contract,\n * handlers: {\n * processOrder: async (message) => {\n * console.log('Processing order', message.orderId);\n * // Process the order...\n * }\n * },\n * urls: ['amqp://localhost']\n * }).resultToPromise();\n *\n * // Close when done\n * await worker.close().resultToPromise();\n * ```\n */\nexport class TypedAmqpWorker<TContract extends ContractDefinition> {\n private constructor(\n private readonly contract: TContract,\n private readonly amqpClient: AmqpClient,\n private readonly handlers: WorkerInferConsumerHandlers<TContract>,\n ) {}\n\n /**\n * Create a type-safe AMQP worker from a contract.\n *\n * Connection management (including automatic reconnection) is handled internally\n * by amqp-connection-manager via the {@link AmqpClient}. The worker will set up\n * consumers for all contract-defined handlers asynchronously in the background\n * once the underlying connection and channels are ready.\n *\n * @param options - Configuration options for the worker\n * @returns A Future that resolves to a Result containing the worker or an error\n *\n * @example\n * ```typescript\n * const workerResult = await TypedAmqpWorker.create({\n * contract: myContract,\n * handlers: {\n * processOrder: async (msg) => console.log('Order:', msg.orderId)\n * },\n * urls: ['amqp://localhost']\n * }).resultToPromise();\n *\n * if (workerResult.isError()) {\n * console.error('Failed to create worker:', workerResult.error);\n * }\n * ```\n */\n static create<TContract extends ContractDefinition>({\n contract,\n handlers,\n urls,\n connectionOptions,\n }: CreateWorkerOptions<TContract>): Future<Result<TypedAmqpWorker<TContract>, TechnicalError>> {\n const worker = new TypedAmqpWorker(\n contract,\n new AmqpClient(contract, {\n urls,\n connectionOptions,\n }),\n handlers,\n );\n return worker.consumeAll().mapOk(() => worker);\n }\n\n /**\n * Close the AMQP channel and connection.\n *\n * This gracefully closes the connection to the AMQP broker,\n * stopping all message consumption and cleaning up resources.\n *\n * @returns A Future that resolves to a Result indicating success or failure\n *\n * @example\n * ```typescript\n * const closeResult = await worker.close().resultToPromise();\n * if (closeResult.isOk()) {\n * console.log('Worker closed successfully');\n * }\n * ```\n */\n close(): Future<Result<void, TechnicalError>> {\n return Future.fromPromise(this.amqpClient.close())\n .mapError((error) => new TechnicalError(\"Failed to close AMQP connection\", error))\n .mapOk(() => undefined);\n }\n\n /**\n * Start consuming messages for all consumers\n */\n private consumeAll(): Future<Result<void, TechnicalError>> {\n if (!this.contract.consumers) {\n return Future.value(Result.Error(new TechnicalError(\"No consumers defined in contract\")));\n }\n\n const consumerNames = Object.keys(this.contract.consumers) as InferConsumerNames<TContract>[];\n\n return Future.all(consumerNames.map((consumerName) => this.consume(consumerName)))\n .map(Result.all)\n .mapOk(() => undefined);\n }\n\n /**\n * Start consuming messages for a specific consumer\n */\n private consume<TName extends InferConsumerNames<TContract>>(\n consumerName: TName,\n ): Future<Result<void, TechnicalError>> {\n const consumers = this.contract.consumers;\n if (!consumers) {\n return Future.value(Result.Error(new TechnicalError(\"No consumers defined in contract\")));\n }\n\n const consumer = consumers[consumerName as string];\n if (!consumer) {\n const availableConsumers = Object.keys(consumers);\n const available = availableConsumers.length > 0 ? availableConsumers.join(\", \") : \"none\";\n return Future.value(\n Result.Error(\n new TechnicalError(\n `Consumer not found: \"${String(consumerName)}\". Available consumers: ${available}`,\n ),\n ),\n );\n }\n\n const handler = this.handlers[consumerName];\n if (!handler) {\n return Future.value(\n Result.Error(new TechnicalError(`Handler for \"${String(consumerName)}\" not provided`)),\n );\n }\n\n // Start consuming\n return Future.fromPromise(\n this.amqpClient.channel.consume(consumer.queue.name, async (msg) => {\n // Parse message\n const parseResult = Result.fromExecution(() => JSON.parse(msg.content.toString()));\n if (parseResult.isError()) {\n // fixme: define a proper logging mechanism\n // fixme: do not log just an error, use a proper logging mechanism\n console.error(\n new TechnicalError(\n `Error parsing message for consumer \"${String(consumerName)}\"`,\n parseResult.error,\n ),\n );\n\n // fixme proper error handling strategy\n // Reject message with no requeue (malformed JSON)\n this.amqpClient.channel.nack(msg, false, false);\n return;\n }\n\n const rawValidation = consumer.message.payload[\"~standard\"].validate(parseResult.value);\n await Future.fromPromise(\n rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation),\n )\n .mapOkToResult((validationResult) => {\n if (validationResult.issues) {\n return Result.Error(\n new MessageValidationError(String(consumerName), validationResult.issues),\n );\n }\n\n return Result.Ok(validationResult.value as WorkerInferConsumerInput<TContract, TName>);\n })\n .tapError((error) => {\n // fixme: define a proper logging mechanism\n // fixme: do not log just an error, use a proper logging mechanism\n console.error(error);\n\n // fixme proper error handling strategy\n // Reject message with no requeue (validation failed)\n this.amqpClient.channel.nack(msg, false, false);\n })\n .flatMapOk((validatedMessage) =>\n Future.fromPromise(handler(validatedMessage)).tapError((error) => {\n // fixme: define a proper logging mechanism\n // fixme: do not log just an error, use a proper logging mechanism\n console.error(\n new TechnicalError(\n `Error processing message for consumer \"${String(consumerName)}\"`,\n error,\n ),\n );\n\n // fixme proper error handling strategy\n // Reject message and requeue (handler failed)\n this.amqpClient.channel.nack(msg, false, true);\n }),\n )\n .tapOk(() => {\n // Acknowledge message\n this.amqpClient.channel.ack(msg);\n })\n .toPromise();\n }),\n )\n .mapError(\n (error) =>\n new TechnicalError(`Failed to start consuming for \"${String(consumerName)}\"`, error),\n )\n .mapOk(() => undefined);\n }\n}\n","import type { ContractDefinition, InferConsumerNames } from \"@amqp-contract/contract\";\nimport type { WorkerInferConsumerHandler, WorkerInferConsumerHandlers } from \"./types.js\";\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;\n\n if (!consumers || !(consumerName 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;\n const availableConsumers = Object.keys(consumers ?? {});\n const availableConsumerNames =\n availableConsumers.length > 0 ? availableConsumers.join(\", \") : \"none\";\n\n for (const handlerName of Object.keys(handlers)) {\n if (!consumers || !(handlerName in consumers)) {\n throw new Error(\n `Consumer \"${handlerName}\" not found in contract. Available consumers: ${availableConsumerNames}`,\n );\n }\n }\n\n // Return the handlers as-is, with type checking enforced\n return handlers;\n}\n"],"mappings":"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"}