@amqp-contract/worker 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,9 +29,9 @@ pnpm add @amqp-contract/worker
29
29
  ### Basic Usage
30
30
 
31
31
  ```typescript
32
- import { TypedAmqpWorker } from '@amqp-contract/worker';
33
- import type { Logger } from '@amqp-contract/core';
34
- import { contract } from './contract';
32
+ import { TypedAmqpWorker } from "@amqp-contract/worker";
33
+ import type { Logger } from "@amqp-contract/core";
34
+ import { contract } from "./contract";
35
35
 
36
36
  // Optional: Create a logger implementation
37
37
  const logger: Logger = {
@@ -46,7 +46,7 @@ const worker = await TypedAmqpWorker.create({
46
46
  contract,
47
47
  handlers: {
48
48
  processOrder: async (message) => {
49
- console.log('Processing order:', message.orderId);
49
+ console.log("Processing order:", message.orderId);
50
50
 
51
51
  // Your business logic here
52
52
  await processPayment(message);
@@ -55,7 +55,7 @@ const worker = await TypedAmqpWorker.create({
55
55
  // If an exception is thrown, the message is automatically requeued
56
56
  },
57
57
  },
58
- urls: ['amqp://localhost'],
58
+ urls: ["amqp://localhost"],
59
59
  logger, // Optional: logs message consumption and errors
60
60
  });
61
61
 
@@ -89,7 +89,7 @@ handlers: {
89
89
  // Message is requeued for retry
90
90
  throw error;
91
91
  }
92
- }
92
+ };
93
93
  }
94
94
  ```
95
95
 
package/dist/index.cjs CHANGED
@@ -1,5 +1,7 @@
1
1
  let _amqp_contract_core = require("@amqp-contract/core");
2
2
  let _swan_io_boxed = require("@swan-io/boxed");
3
+ let node_zlib = require("node:zlib");
4
+ let node_util = require("node:util");
3
5
 
4
6
  //#region src/errors.ts
5
7
  /**
@@ -36,6 +38,29 @@ var MessageValidationError = class extends WorkerError {
36
38
  }
37
39
  };
38
40
 
41
+ //#endregion
42
+ //#region src/decompression.ts
43
+ const gunzipAsync = (0, node_util.promisify)(node_zlib.gunzip);
44
+ const inflateAsync = (0, node_util.promisify)(node_zlib.inflate);
45
+ /**
46
+ * Decompress a buffer based on the content-encoding header.
47
+ *
48
+ * @param buffer - The buffer to decompress
49
+ * @param contentEncoding - The content-encoding header value (e.g., 'gzip', 'deflate')
50
+ * @returns A promise that resolves to the decompressed buffer
51
+ * @throws Error if decompression fails or if the encoding is unsupported
52
+ *
53
+ * @internal
54
+ */
55
+ async function decompressBuffer(buffer, contentEncoding) {
56
+ if (!contentEncoding) return buffer;
57
+ switch (contentEncoding.toLowerCase()) {
58
+ case "gzip": return gunzipAsync(buffer);
59
+ case "deflate": return inflateAsync(buffer);
60
+ default: throw new Error(`Unsupported content-encoding: ${contentEncoding}`);
61
+ }
62
+ }
63
+
39
64
  //#endregion
40
65
  //#region src/worker.ts
41
66
  /**
@@ -82,6 +107,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
82
107
  actualHandlers;
83
108
  consumerOptions;
84
109
  batchTimers = /* @__PURE__ */ new Map();
110
+ consumerTags = /* @__PURE__ */ new Set();
85
111
  constructor(contract, amqpClient, handlers, logger) {
86
112
  this.contract = contract;
87
113
  this.amqpClient = amqpClient;
@@ -151,7 +177,15 @@ var TypedAmqpWorker = class TypedAmqpWorker {
151
177
  close() {
152
178
  for (const timer of this.batchTimers.values()) clearTimeout(timer);
153
179
  this.batchTimers.clear();
154
- return _swan_io_boxed.Future.fromPromise(this.amqpClient.close()).mapError((error) => new TechnicalError("Failed to close AMQP connection", error)).mapOk(() => void 0);
180
+ return _swan_io_boxed.Future.all(Array.from(this.consumerTags).map((consumerTag) => _swan_io_boxed.Future.fromPromise(this.amqpClient.channel.cancel(consumerTag)).mapErrorToResult((error) => {
181
+ this.logger?.warn("Failed to cancel consumer during close", {
182
+ consumerTag,
183
+ error
184
+ });
185
+ return _swan_io_boxed.Result.Ok(void 0);
186
+ }))).map(_swan_io_boxed.Result.all).tapOk(() => {
187
+ this.consumerTags.clear();
188
+ }).flatMapOk(() => _swan_io_boxed.Future.fromPromise(this.amqpClient.close())).mapError((error) => new TechnicalError("Failed to close AMQP connection", error)).mapOk(() => void 0);
155
189
  }
156
190
  /**
157
191
  * Start consuming messages for all consumers
@@ -208,30 +242,45 @@ var TypedAmqpWorker = class TypedAmqpWorker {
208
242
  * @returns Future<Result<validated message, void>> - Ok with validated message, or Error (already handled with nack)
209
243
  */
210
244
  parseAndValidateMessage(msg, consumer, consumerName) {
211
- const parseResult = _swan_io_boxed.Result.fromExecution(() => JSON.parse(msg.content.toString()));
212
- if (parseResult.isError()) {
213
- this.logger?.error("Error parsing message", {
245
+ const decompressMessage = _swan_io_boxed.Future.fromPromise(decompressBuffer(msg.content, msg.properties.contentEncoding)).mapError((error) => {
246
+ this.logger?.error("Error decompressing message", {
214
247
  consumerName: String(consumerName),
215
248
  queueName: consumer.queue.name,
216
- error: parseResult.error
249
+ contentEncoding: msg.properties.contentEncoding,
250
+ error
217
251
  });
218
252
  this.amqpClient.channel.nack(msg, false, false);
219
- return _swan_io_boxed.Future.value(_swan_io_boxed.Result.Error(void 0));
220
- }
221
- const rawValidation = consumer.message.payload["~standard"].validate(parseResult.value);
222
- return _swan_io_boxed.Future.fromPromise(rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation)).mapOkToResult((validationResult) => {
223
- if (validationResult.issues) {
224
- const error = new MessageValidationError(String(consumerName), validationResult.issues);
225
- this.logger?.error("Message validation failed", {
253
+ });
254
+ const parseMessage = (buffer) => {
255
+ const parseResult = _swan_io_boxed.Result.fromExecution(() => JSON.parse(buffer.toString()));
256
+ if (parseResult.isError()) {
257
+ this.logger?.error("Error parsing message", {
226
258
  consumerName: String(consumerName),
227
259
  queueName: consumer.queue.name,
228
- error
260
+ error: parseResult.error
229
261
  });
230
262
  this.amqpClient.channel.nack(msg, false, false);
231
- return _swan_io_boxed.Result.Error(void 0);
263
+ return _swan_io_boxed.Future.value(_swan_io_boxed.Result.Error(void 0));
232
264
  }
233
- return _swan_io_boxed.Result.Ok(validationResult.value);
234
- });
265
+ return _swan_io_boxed.Future.value(_swan_io_boxed.Result.Ok(parseResult.value));
266
+ };
267
+ const validateMessage = (parsedMessage) => {
268
+ const rawValidation = consumer.message.payload["~standard"].validate(parsedMessage);
269
+ return _swan_io_boxed.Future.fromPromise(rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation)).mapOkToResult((validationResult) => {
270
+ if (validationResult.issues) {
271
+ const error = new MessageValidationError(String(consumerName), validationResult.issues);
272
+ this.logger?.error("Message validation failed", {
273
+ consumerName: String(consumerName),
274
+ queueName: consumer.queue.name,
275
+ error
276
+ });
277
+ this.amqpClient.channel.nack(msg, false, false);
278
+ return _swan_io_boxed.Result.Error(void 0);
279
+ }
280
+ return _swan_io_boxed.Result.Ok(validationResult.value);
281
+ });
282
+ };
283
+ return decompressMessage.flatMapOk(parseMessage).flatMapOk(validateMessage);
235
284
  }
236
285
  /**
237
286
  * Consume messages one at a time
@@ -259,7 +308,9 @@ var TypedAmqpWorker = class TypedAmqpWorker {
259
308
  });
260
309
  this.amqpClient.channel.ack(msg);
261
310
  }).toPromise();
262
- })).mapError((error) => new TechnicalError(`Failed to start consuming for "${String(consumerName)}"`, error)).mapOk(() => void 0);
311
+ })).tapOk((reply) => {
312
+ this.consumerTags.add(reply.consumerTag);
313
+ }).mapError((error) => new TechnicalError(`Failed to start consuming for "${String(consumerName)}"`, error)).mapOk(() => void 0);
263
314
  }
264
315
  /**
265
316
  * Consume messages in batches
@@ -339,7 +390,9 @@ var TypedAmqpWorker = class TypedAmqpWorker {
339
390
  await processBatch();
340
391
  if (batch.length > 0 && !this.batchTimers.has(timerKey)) scheduleBatchProcessing();
341
392
  } else if (!this.batchTimers.has(timerKey)) scheduleBatchProcessing();
342
- })).mapError((error) => new TechnicalError(`Failed to start consuming for "${String(consumerName)}"`, error)).mapOk(() => void 0);
393
+ })).tapOk((reply) => {
394
+ this.consumerTags.add(reply.consumerTag);
395
+ }).mapError((error) => new TechnicalError(`Failed to start consuming for "${String(consumerName)}"`, error)).mapOk(() => void 0);
343
396
  }
344
397
  };
345
398
 
package/dist/index.d.cts CHANGED
@@ -180,6 +180,7 @@ declare class TypedAmqpWorker<TContract extends ContractDefinition> {
180
180
  private readonly actualHandlers;
181
181
  private readonly consumerOptions;
182
182
  private readonly batchTimers;
183
+ private readonly consumerTags;
183
184
  private constructor();
184
185
  /**
185
186
  * Create a type-safe AMQP worker from a contract.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/errors.ts","../src/types.ts","../src/worker.ts","../src/handlers.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/errors.ts","../src/types.ts","../src/worker.ts","../src/handlers.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -180,6 +180,7 @@ declare class TypedAmqpWorker<TContract extends ContractDefinition> {
180
180
  private readonly actualHandlers;
181
181
  private readonly consumerOptions;
182
182
  private readonly batchTimers;
183
+ private readonly consumerTags;
183
184
  private constructor();
184
185
  /**
185
186
  * Create a type-safe AMQP worker from a contract.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/errors.ts","../src/types.ts","../src/worker.ts","../src/handlers.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/errors.ts","../src/types.ts","../src/worker.ts","../src/handlers.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,7 @@
1
1
  import { AmqpClient } from "@amqp-contract/core";
2
2
  import { Future, Result } from "@swan-io/boxed";
3
+ import { gunzip, inflate } from "node:zlib";
4
+ import { promisify } from "node:util";
3
5
 
4
6
  //#region src/errors.ts
5
7
  /**
@@ -36,6 +38,29 @@ var MessageValidationError = class extends WorkerError {
36
38
  }
37
39
  };
38
40
 
41
+ //#endregion
42
+ //#region src/decompression.ts
43
+ const gunzipAsync = promisify(gunzip);
44
+ const inflateAsync = promisify(inflate);
45
+ /**
46
+ * Decompress a buffer based on the content-encoding header.
47
+ *
48
+ * @param buffer - The buffer to decompress
49
+ * @param contentEncoding - The content-encoding header value (e.g., 'gzip', 'deflate')
50
+ * @returns A promise that resolves to the decompressed buffer
51
+ * @throws Error if decompression fails or if the encoding is unsupported
52
+ *
53
+ * @internal
54
+ */
55
+ async function decompressBuffer(buffer, contentEncoding) {
56
+ if (!contentEncoding) return buffer;
57
+ switch (contentEncoding.toLowerCase()) {
58
+ case "gzip": return gunzipAsync(buffer);
59
+ case "deflate": return inflateAsync(buffer);
60
+ default: throw new Error(`Unsupported content-encoding: ${contentEncoding}`);
61
+ }
62
+ }
63
+
39
64
  //#endregion
40
65
  //#region src/worker.ts
41
66
  /**
@@ -82,6 +107,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
82
107
  actualHandlers;
83
108
  consumerOptions;
84
109
  batchTimers = /* @__PURE__ */ new Map();
110
+ consumerTags = /* @__PURE__ */ new Set();
85
111
  constructor(contract, amqpClient, handlers, logger) {
86
112
  this.contract = contract;
87
113
  this.amqpClient = amqpClient;
@@ -151,7 +177,15 @@ var TypedAmqpWorker = class TypedAmqpWorker {
151
177
  close() {
152
178
  for (const timer of this.batchTimers.values()) clearTimeout(timer);
153
179
  this.batchTimers.clear();
154
- return Future.fromPromise(this.amqpClient.close()).mapError((error) => new TechnicalError("Failed to close AMQP connection", error)).mapOk(() => void 0);
180
+ return Future.all(Array.from(this.consumerTags).map((consumerTag) => Future.fromPromise(this.amqpClient.channel.cancel(consumerTag)).mapErrorToResult((error) => {
181
+ this.logger?.warn("Failed to cancel consumer during close", {
182
+ consumerTag,
183
+ error
184
+ });
185
+ return Result.Ok(void 0);
186
+ }))).map(Result.all).tapOk(() => {
187
+ this.consumerTags.clear();
188
+ }).flatMapOk(() => Future.fromPromise(this.amqpClient.close())).mapError((error) => new TechnicalError("Failed to close AMQP connection", error)).mapOk(() => void 0);
155
189
  }
156
190
  /**
157
191
  * Start consuming messages for all consumers
@@ -208,30 +242,45 @@ var TypedAmqpWorker = class TypedAmqpWorker {
208
242
  * @returns Future<Result<validated message, void>> - Ok with validated message, or Error (already handled with nack)
209
243
  */
210
244
  parseAndValidateMessage(msg, consumer, consumerName) {
211
- const parseResult = Result.fromExecution(() => JSON.parse(msg.content.toString()));
212
- if (parseResult.isError()) {
213
- this.logger?.error("Error parsing message", {
245
+ const decompressMessage = Future.fromPromise(decompressBuffer(msg.content, msg.properties.contentEncoding)).mapError((error) => {
246
+ this.logger?.error("Error decompressing message", {
214
247
  consumerName: String(consumerName),
215
248
  queueName: consumer.queue.name,
216
- error: parseResult.error
249
+ contentEncoding: msg.properties.contentEncoding,
250
+ error
217
251
  });
218
252
  this.amqpClient.channel.nack(msg, false, false);
219
- return Future.value(Result.Error(void 0));
220
- }
221
- const rawValidation = consumer.message.payload["~standard"].validate(parseResult.value);
222
- return Future.fromPromise(rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation)).mapOkToResult((validationResult) => {
223
- if (validationResult.issues) {
224
- const error = new MessageValidationError(String(consumerName), validationResult.issues);
225
- this.logger?.error("Message validation failed", {
253
+ });
254
+ const parseMessage = (buffer) => {
255
+ const parseResult = Result.fromExecution(() => JSON.parse(buffer.toString()));
256
+ if (parseResult.isError()) {
257
+ this.logger?.error("Error parsing message", {
226
258
  consumerName: String(consumerName),
227
259
  queueName: consumer.queue.name,
228
- error
260
+ error: parseResult.error
229
261
  });
230
262
  this.amqpClient.channel.nack(msg, false, false);
231
- return Result.Error(void 0);
263
+ return Future.value(Result.Error(void 0));
232
264
  }
233
- return Result.Ok(validationResult.value);
234
- });
265
+ return Future.value(Result.Ok(parseResult.value));
266
+ };
267
+ const validateMessage = (parsedMessage) => {
268
+ const rawValidation = consumer.message.payload["~standard"].validate(parsedMessage);
269
+ return Future.fromPromise(rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation)).mapOkToResult((validationResult) => {
270
+ if (validationResult.issues) {
271
+ const error = new MessageValidationError(String(consumerName), validationResult.issues);
272
+ this.logger?.error("Message validation failed", {
273
+ consumerName: String(consumerName),
274
+ queueName: consumer.queue.name,
275
+ error
276
+ });
277
+ this.amqpClient.channel.nack(msg, false, false);
278
+ return Result.Error(void 0);
279
+ }
280
+ return Result.Ok(validationResult.value);
281
+ });
282
+ };
283
+ return decompressMessage.flatMapOk(parseMessage).flatMapOk(validateMessage);
235
284
  }
236
285
  /**
237
286
  * Consume messages one at a time
@@ -259,7 +308,9 @@ var TypedAmqpWorker = class TypedAmqpWorker {
259
308
  });
260
309
  this.amqpClient.channel.ack(msg);
261
310
  }).toPromise();
262
- })).mapError((error) => new TechnicalError(`Failed to start consuming for "${String(consumerName)}"`, error)).mapOk(() => void 0);
311
+ })).tapOk((reply) => {
312
+ this.consumerTags.add(reply.consumerTag);
313
+ }).mapError((error) => new TechnicalError(`Failed to start consuming for "${String(consumerName)}"`, error)).mapOk(() => void 0);
263
314
  }
264
315
  /**
265
316
  * Consume messages in batches
@@ -339,7 +390,9 @@ var TypedAmqpWorker = class TypedAmqpWorker {
339
390
  await processBatch();
340
391
  if (batch.length > 0 && !this.batchTimers.has(timerKey)) scheduleBatchProcessing();
341
392
  } else if (!this.batchTimers.has(timerKey)) scheduleBatchProcessing();
342
- })).mapError((error) => new TechnicalError(`Failed to start consuming for "${String(consumerName)}"`, error)).mapOk(() => void 0);
393
+ })).tapOk((reply) => {
394
+ this.consumerTags.add(reply.consumerTag);
395
+ }).mapError((error) => new TechnicalError(`Failed to start consuming for "${String(consumerName)}"`, error)).mapOk(() => void 0);
343
396
  }
344
397
  };
345
398
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["cause?: unknown","consumerName: string","issues: unknown","contract: TContract","amqpClient: AmqpClient","logger?: Logger","batch: BatchItem[]"],"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 { AmqpClient, type Logger } from \"@amqp-contract/core\";\nimport type { AmqpConnectionManagerOptions, ConnectionUrl } from \"amqp-connection-manager\";\nimport type { Channel, Message } from \"amqplib\";\nimport type {\n ConsumerDefinition,\n ContractDefinition,\n InferConsumerNames,\n} from \"@amqp-contract/contract\";\nimport { Future, Result } from \"@swan-io/boxed\";\nimport { MessageValidationError, TechnicalError } from \"./errors.js\";\nimport type {\n WorkerInferConsumerBatchHandler,\n WorkerInferConsumerHandler,\n WorkerInferConsumerHandlers,\n WorkerInferConsumerInput,\n} from \"./types.js\";\n\n/**\n * Internal type for consumer options extracted from handler tuples.\n * Not exported - options are specified inline in the handler tuple types.\n * Uses discriminated union to enforce mutual exclusivity:\n * - Prefetch-only mode: Cannot have batchSize or batchTimeout\n * - Batch mode: Requires batchSize, allows batchTimeout and prefetch\n */\ntype ConsumerOptions =\n | {\n /** Prefetch-based processing (no batching) */\n prefetch?: number;\n batchSize?: never;\n batchTimeout?: never;\n }\n | {\n /** Batch-based processing */\n prefetch?: number;\n batchSize: number;\n batchTimeout?: number;\n };\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 * // Simple handler\n * processOrder: async (message) => {\n * console.log('Processing order:', message.orderId);\n * },\n * // Handler with options (prefetch)\n * processPayment: [\n * async (message) => {\n * console.log('Processing payment:', message.paymentId);\n * },\n * { prefetch: 10 }\n * ],\n * // Handler with batch processing\n * processNotifications: [\n * async (messages) => {\n * console.log('Processing batch:', messages.length);\n * },\n * { batchSize: 5, batchTimeout: 1000 }\n * ]\n * },\n * urls: ['amqp://localhost'],\n * connectionOptions: {\n * heartbeatIntervalInSeconds: 30\n * },\n * logger: myLogger\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. Can be a function or a tuple of [handler, options] */\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 /** Optional logger for logging message consumption and errors */\n logger?: Logger | 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 readonly actualHandlers: Partial<\n Record<\n InferConsumerNames<TContract>,\n | WorkerInferConsumerHandler<TContract, InferConsumerNames<TContract>>\n | WorkerInferConsumerBatchHandler<TContract, InferConsumerNames<TContract>>\n >\n >;\n private readonly consumerOptions: Partial<Record<InferConsumerNames<TContract>, ConsumerOptions>>;\n private readonly batchTimers: Map<string, NodeJS.Timeout> = new Map();\n\n private constructor(\n private readonly contract: TContract,\n private readonly amqpClient: AmqpClient,\n handlers: WorkerInferConsumerHandlers<TContract>,\n private readonly logger?: Logger,\n ) {\n // Extract handlers and options from the handlers object\n this.actualHandlers = {};\n this.consumerOptions = {};\n\n for (const consumerName of Object.keys(handlers) as InferConsumerNames<TContract>[]) {\n const handlerEntry = handlers[consumerName];\n\n if (Array.isArray(handlerEntry)) {\n // Tuple format: [handler, options]\n // Type assertion is safe: The discriminated union in WorkerInferConsumerHandlerEntry\n // ensures the handler matches the options (single-message or batch handler).\n // TypeScript loses this type relationship during runtime extraction, but it's\n // guaranteed by the type system at compile time.\n this.actualHandlers[consumerName] = handlerEntry[0] as unknown as\n | WorkerInferConsumerHandler<TContract, InferConsumerNames<TContract>>\n | WorkerInferConsumerBatchHandler<TContract, InferConsumerNames<TContract>>;\n this.consumerOptions[consumerName] = handlerEntry[1];\n } else {\n // Direct function format\n // Type assertion is safe: handlerEntry is guaranteed to be a function type\n // by the discriminated union, but TypeScript needs help with the union narrowing.\n this.actualHandlers[consumerName] = handlerEntry as unknown as\n | WorkerInferConsumerHandler<TContract, InferConsumerNames<TContract>>\n | WorkerInferConsumerBatchHandler<TContract, InferConsumerNames<TContract>>;\n }\n }\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 * Connections are automatically shared across clients and workers with the same\n * URLs and connection options, following RabbitMQ best practices.\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 logger,\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 logger,\n );\n\n return worker\n .waitForConnectionReady()\n .flatMapOk(() => worker.consumeAll())\n .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 // Clear all pending batch timers\n for (const timer of this.batchTimers.values()) {\n clearTimeout(timer);\n }\n this.batchTimers.clear();\n\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 // Calculate the maximum prefetch value among all consumers\n // Since prefetch is per-channel in AMQP 0.9.1, we use the maximum value\n const consumerNames = Object.keys(this.contract.consumers) as InferConsumerNames<TContract>[];\n let maxPrefetch = 0;\n\n for (const consumerName of consumerNames) {\n const options = this.consumerOptions[consumerName];\n if (options?.prefetch !== undefined) {\n if (options.prefetch <= 0 || !Number.isInteger(options.prefetch)) {\n return Future.value(\n Result.Error(\n new TechnicalError(\n `Invalid prefetch value for \"${String(consumerName)}\": must be a positive integer`,\n ),\n ),\n );\n }\n maxPrefetch = Math.max(maxPrefetch, options.prefetch);\n }\n if (options?.batchSize !== undefined) {\n // Batch consumers need prefetch at least equal to batch size\n const effectivePrefetch = options.prefetch ?? options.batchSize;\n maxPrefetch = Math.max(maxPrefetch, effectivePrefetch);\n }\n }\n\n // Apply the maximum prefetch if any consumer specified it\n if (maxPrefetch > 0) {\n this.amqpClient.channel.addSetup(async (channel: Channel) => {\n await channel.prefetch(maxPrefetch);\n });\n }\n\n return Future.all(consumerNames.map((consumerName) => this.consume(consumerName)))\n .map(Result.all)\n .mapOk(() => undefined);\n }\n\n private waitForConnectionReady(): Future<Result<void, TechnicalError>> {\n return Future.fromPromise(this.amqpClient.channel.waitForConnect()).mapError(\n (error) => new TechnicalError(\"Failed to wait for connection ready\", error),\n );\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.actualHandlers[consumerName];\n if (!handler) {\n return Future.value(\n Result.Error(new TechnicalError(`Handler for \"${String(consumerName)}\" not provided`)),\n );\n }\n\n // Get consumer-specific options\n const options = this.consumerOptions[consumerName] ?? {};\n\n // Validate batch size if specified\n if (options.batchSize !== undefined) {\n if (options.batchSize <= 0 || !Number.isInteger(options.batchSize)) {\n return Future.value(\n Result.Error(\n new TechnicalError(\n `Invalid batchSize for \"${String(consumerName)}\": must be a positive integer`,\n ),\n ),\n );\n }\n }\n\n // Validate batch timeout if specified\n if (options.batchTimeout !== undefined) {\n if (\n typeof options.batchTimeout !== \"number\" ||\n !Number.isFinite(options.batchTimeout) ||\n options.batchTimeout <= 0\n ) {\n return Future.value(\n Result.Error(\n new TechnicalError(\n `Invalid batchTimeout for \"${String(consumerName)}\": must be a positive number`,\n ),\n ),\n );\n }\n }\n\n // Check if this is a batch consumer\n const isBatchConsumer = options.batchSize !== undefined && options.batchSize > 0;\n\n if (isBatchConsumer) {\n return this.consumeBatch(\n consumerName,\n consumer,\n options,\n handler as unknown as (\n messages: Array<WorkerInferConsumerInput<TContract, TName>>,\n ) => Promise<void>,\n );\n } else {\n return this.consumeSingle(\n consumerName,\n consumer,\n handler as unknown as (\n message: WorkerInferConsumerInput<TContract, TName>,\n ) => Promise<void>,\n );\n }\n }\n\n /**\n * Parse and validate a message from AMQP\n * @returns Future<Result<validated message, void>> - Ok with validated message, or Error (already handled with nack)\n */\n private parseAndValidateMessage<TName extends InferConsumerNames<TContract>>(\n msg: Message,\n consumer: ConsumerDefinition,\n consumerName: TName,\n ): Future<Result<WorkerInferConsumerInput<TContract, TName>, void>> {\n // Parse message\n const parseResult = Result.fromExecution(() => JSON.parse(msg.content.toString()));\n if (parseResult.isError()) {\n this.logger?.error(\"Error parsing message\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n error: parseResult.error,\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 Future.value(Result.Error(undefined));\n }\n\n const rawValidation = consumer.message.payload[\"~standard\"].validate(parseResult.value);\n return Future.fromPromise(\n rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation),\n ).mapOkToResult((validationResult) => {\n if (validationResult.issues) {\n const error = new MessageValidationError(String(consumerName), validationResult.issues);\n this.logger?.error(\"Message validation failed\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n error,\n });\n\n // fixme proper error handling strategy\n // Reject message with no requeue (validation failed)\n this.amqpClient.channel.nack(msg, false, false);\n return Result.Error(undefined) as Result<WorkerInferConsumerInput<TContract, TName>, void>;\n }\n\n return Result.Ok(validationResult.value as WorkerInferConsumerInput<TContract, TName>);\n }) as Future<Result<WorkerInferConsumerInput<TContract, TName>, void>>;\n }\n\n /**\n * Consume messages one at a time\n */\n private consumeSingle<TName extends InferConsumerNames<TContract>>(\n consumerName: TName,\n consumer: ConsumerDefinition,\n handler: (message: WorkerInferConsumerInput<TContract, TName>) => Promise<void>,\n ): Future<Result<void, TechnicalError>> {\n // Start consuming\n return Future.fromPromise(\n this.amqpClient.channel.consume(consumer.queue.name, async (msg) => {\n // Handle null messages (consumer cancellation)\n if (msg === null) {\n this.logger?.warn(\"Consumer cancelled by server\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n });\n return;\n }\n\n // Parse and validate message\n await this.parseAndValidateMessage(msg, consumer, consumerName)\n .flatMapOk((validatedMessage) =>\n Future.fromPromise(handler(validatedMessage)).tapError((error) => {\n this.logger?.error(\"Error processing message\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n error,\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 this.logger?.info(\"Message consumed successfully\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n });\n\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 /**\n * Consume messages in batches\n */\n private consumeBatch<TName extends InferConsumerNames<TContract>>(\n consumerName: TName,\n consumer: ConsumerDefinition,\n options: ConsumerOptions,\n handler: (messages: Array<WorkerInferConsumerInput<TContract, TName>>) => Promise<void>,\n ): Future<Result<void, TechnicalError>> {\n const batchSize = options.batchSize!;\n const batchTimeout = options.batchTimeout ?? 1000;\n const timerKey = String(consumerName);\n\n // Note: Prefetch is handled globally in consumeAll()\n // Batch accumulation state\n type BatchItem = {\n message: WorkerInferConsumerInput<TContract, TName>;\n amqpMessage: Message;\n };\n let batch: BatchItem[] = [];\n // Track if batch processing is currently in progress to avoid race conditions\n let isProcessing = false;\n\n const processBatch = async () => {\n // Prevent concurrent batch processing\n if (isProcessing || batch.length === 0) {\n return;\n }\n\n isProcessing = true;\n\n const currentBatch = batch;\n batch = [];\n\n // Clear timer from tracking\n const timer = this.batchTimers.get(timerKey);\n if (timer) {\n clearTimeout(timer);\n this.batchTimers.delete(timerKey);\n }\n\n const messages = currentBatch.map((item) => item.message);\n\n this.logger?.info(\"Processing batch\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n batchSize: currentBatch.length,\n });\n\n try {\n await handler(messages);\n\n // Acknowledge all messages in the batch\n for (const item of currentBatch) {\n this.amqpClient.channel.ack(item.amqpMessage);\n }\n\n this.logger?.info(\"Batch processed successfully\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n batchSize: currentBatch.length,\n });\n } catch (error) {\n this.logger?.error(\"Error processing batch\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n batchSize: currentBatch.length,\n error,\n });\n\n // fixme proper error handling strategy\n // Reject all messages and requeue (handler failed)\n for (const item of currentBatch) {\n this.amqpClient.channel.nack(item.amqpMessage, false, true);\n }\n } finally {\n isProcessing = false;\n }\n };\n\n const scheduleBatchProcessing = () => {\n // Don't schedule if batch is currently being processed\n if (isProcessing) {\n return;\n }\n\n // Clear existing timer\n const existingTimer = this.batchTimers.get(timerKey);\n if (existingTimer) {\n clearTimeout(existingTimer);\n }\n\n // Schedule new timer and track it\n const timer = setTimeout(() => {\n processBatch().catch((error) => {\n this.logger?.error(\"Unexpected error in batch processing\", {\n consumerName: String(consumerName),\n error,\n });\n });\n }, batchTimeout);\n\n this.batchTimers.set(timerKey, timer);\n };\n\n // Start consuming\n return Future.fromPromise(\n this.amqpClient.channel.consume(consumer.queue.name, async (msg) => {\n // Handle null messages (consumer cancellation)\n if (msg === null) {\n this.logger?.warn(\"Consumer cancelled by server\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n });\n // Process any remaining messages in the batch\n await processBatch();\n return;\n }\n\n // Parse and validate message\n const validationResult = await this.parseAndValidateMessage(\n msg,\n consumer,\n consumerName,\n ).toPromise();\n\n if (validationResult.isError()) {\n // Error already handled in parseAndValidateMessage (nacked)\n return;\n }\n\n // Add to batch\n batch.push({\n message: validationResult.value,\n amqpMessage: msg,\n });\n\n // Process batch if full\n if (batch.length >= batchSize) {\n await processBatch();\n // After processing a full batch, schedule timer for any subsequent messages\n // This ensures that if more messages arrive at a slow rate, they won't be held indefinitely\n if (batch.length > 0 && !this.batchTimers.has(timerKey)) {\n scheduleBatchProcessing();\n }\n } else {\n // Schedule batch processing if not already scheduled\n if (!this.batchTimers.has(timerKey)) {\n scheduleBatchProcessing();\n }\n }\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 {\n WorkerInferConsumerBatchHandler,\n WorkerInferConsumerHandler,\n WorkerInferConsumerHandlerEntry,\n WorkerInferConsumerHandlers,\n} 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 * Supports three patterns:\n * 1. Simple handler: just the function (single message handler)\n * 2. Handler with prefetch: [handler, { prefetch: 10 }] (single message handler with config)\n * 3. Batch handler: [batchHandler, { batchSize: 5, batchTimeout: 1000 }] (REQUIRES batchSize config)\n *\n * **Important**: Batch handlers (handlers that accept an array of messages) MUST include\n * batchSize configuration. You cannot create a batch handler without specifying batchSize.\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 (single or batch)\n * @param options - Optional consumer options (prefetch, batchSize, batchTimeout)\n * - For single-message handlers: { prefetch?: number } is optional\n * - For batch handlers: { batchSize: number, batchTimeout?: number } is REQUIRED\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 * // Simple single-message handler without options\n * const processOrderHandler = defineHandler(\n * orderContract,\n * 'processOrder',\n * async (message) => {\n * console.log('Processing order:', message.orderId);\n * await processPayment(message);\n * }\n * );\n *\n * // Single-message handler with prefetch\n * const processOrderWithPrefetch = defineHandler(\n * orderContract,\n * 'processOrder',\n * async (message) => {\n * await processOrder(message);\n * },\n * { prefetch: 10 }\n * );\n *\n * // Batch handler - MUST include batchSize\n * const processBatchOrders = defineHandler(\n * orderContract,\n * 'processOrders',\n * async (messages) => {\n * // messages is an array - batchSize configuration is REQUIRED\n * await db.insertMany(messages);\n * },\n * { batchSize: 5, batchTimeout: 1000 }\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): WorkerInferConsumerHandlerEntry<TContract, TName>;\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 options: { prefetch?: number; batchSize?: never; batchTimeout?: never },\n): WorkerInferConsumerHandlerEntry<TContract, TName>;\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: WorkerInferConsumerBatchHandler<TContract, TName>,\n options: { prefetch?: number; batchSize: number; batchTimeout?: number },\n): WorkerInferConsumerHandlerEntry<TContract, TName>;\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler:\n | WorkerInferConsumerHandler<TContract, TName>\n | WorkerInferConsumerBatchHandler<TContract, TName>,\n options?: { prefetch?: number; batchSize?: number; batchTimeout?: number },\n): WorkerInferConsumerHandlerEntry<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 with options if provided, otherwise just the handler\n if (options) {\n return [handler, options] as WorkerInferConsumerHandlerEntry<TContract, TName>;\n }\n return handler as WorkerInferConsumerHandlerEntry<TContract, TName>;\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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"}
1
+ {"version":3,"file":"index.mjs","names":["cause?: unknown","consumerName: string","issues: unknown","contract: TContract","amqpClient: AmqpClient","logger?: Logger","batch: BatchItem[]"],"sources":["../src/errors.ts","../src/decompression.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 { gunzip, inflate } from \"node:zlib\";\nimport { promisify } from \"node:util\";\n\nconst gunzipAsync = promisify(gunzip);\nconst inflateAsync = promisify(inflate);\n\n/**\n * Decompress a buffer based on the content-encoding header.\n *\n * @param buffer - The buffer to decompress\n * @param contentEncoding - The content-encoding header value (e.g., 'gzip', 'deflate')\n * @returns A promise that resolves to the decompressed buffer\n * @throws Error if decompression fails or if the encoding is unsupported\n *\n * @internal\n */\nexport async function decompressBuffer(\n buffer: Buffer,\n contentEncoding: string | undefined,\n): Promise<Buffer> {\n if (!contentEncoding) {\n return buffer; // No compression\n }\n\n switch (contentEncoding.toLowerCase()) {\n case \"gzip\":\n return gunzipAsync(buffer);\n case \"deflate\":\n return inflateAsync(buffer);\n default:\n throw new Error(`Unsupported content-encoding: ${contentEncoding}`);\n }\n}\n","import { AmqpClient, type Logger } from \"@amqp-contract/core\";\nimport type { AmqpConnectionManagerOptions, ConnectionUrl } from \"amqp-connection-manager\";\nimport type { Channel, Message } from \"amqplib\";\nimport type {\n ConsumerDefinition,\n ContractDefinition,\n InferConsumerNames,\n} from \"@amqp-contract/contract\";\nimport { Future, Result } from \"@swan-io/boxed\";\nimport { MessageValidationError, TechnicalError } from \"./errors.js\";\nimport type {\n WorkerInferConsumerBatchHandler,\n WorkerInferConsumerHandler,\n WorkerInferConsumerHandlers,\n WorkerInferConsumerInput,\n} from \"./types.js\";\nimport { decompressBuffer } from \"./decompression.js\";\n\n/**\n * Internal type for consumer options extracted from handler tuples.\n * Not exported - options are specified inline in the handler tuple types.\n * Uses discriminated union to enforce mutual exclusivity:\n * - Prefetch-only mode: Cannot have batchSize or batchTimeout\n * - Batch mode: Requires batchSize, allows batchTimeout and prefetch\n */\ntype ConsumerOptions =\n | {\n /** Prefetch-based processing (no batching) */\n prefetch?: number;\n batchSize?: never;\n batchTimeout?: never;\n }\n | {\n /** Batch-based processing */\n prefetch?: number;\n batchSize: number;\n batchTimeout?: number;\n };\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 * // Simple handler\n * processOrder: async (message) => {\n * console.log('Processing order:', message.orderId);\n * },\n * // Handler with options (prefetch)\n * processPayment: [\n * async (message) => {\n * console.log('Processing payment:', message.paymentId);\n * },\n * { prefetch: 10 }\n * ],\n * // Handler with batch processing\n * processNotifications: [\n * async (messages) => {\n * console.log('Processing batch:', messages.length);\n * },\n * { batchSize: 5, batchTimeout: 1000 }\n * ]\n * },\n * urls: ['amqp://localhost'],\n * connectionOptions: {\n * heartbeatIntervalInSeconds: 30\n * },\n * logger: myLogger\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. Can be a function or a tuple of [handler, options] */\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 /** Optional logger for logging message consumption and errors */\n logger?: Logger | 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 readonly actualHandlers: Partial<\n Record<\n InferConsumerNames<TContract>,\n | WorkerInferConsumerHandler<TContract, InferConsumerNames<TContract>>\n | WorkerInferConsumerBatchHandler<TContract, InferConsumerNames<TContract>>\n >\n >;\n private readonly consumerOptions: Partial<Record<InferConsumerNames<TContract>, ConsumerOptions>>;\n private readonly batchTimers: Map<string, NodeJS.Timeout> = new Map();\n private readonly consumerTags: Set<string> = new Set();\n\n private constructor(\n private readonly contract: TContract,\n private readonly amqpClient: AmqpClient,\n handlers: WorkerInferConsumerHandlers<TContract>,\n private readonly logger?: Logger,\n ) {\n // Extract handlers and options from the handlers object\n this.actualHandlers = {};\n this.consumerOptions = {};\n\n for (const consumerName of Object.keys(handlers) as InferConsumerNames<TContract>[]) {\n const handlerEntry = handlers[consumerName];\n\n if (Array.isArray(handlerEntry)) {\n // Tuple format: [handler, options]\n // Type assertion is safe: The discriminated union in WorkerInferConsumerHandlerEntry\n // ensures the handler matches the options (single-message or batch handler).\n // TypeScript loses this type relationship during runtime extraction, but it's\n // guaranteed by the type system at compile time.\n this.actualHandlers[consumerName] = handlerEntry[0] as unknown as\n | WorkerInferConsumerHandler<TContract, InferConsumerNames<TContract>>\n | WorkerInferConsumerBatchHandler<TContract, InferConsumerNames<TContract>>;\n this.consumerOptions[consumerName] = handlerEntry[1];\n } else {\n // Direct function format\n // Type assertion is safe: handlerEntry is guaranteed to be a function type\n // by the discriminated union, but TypeScript needs help with the union narrowing.\n this.actualHandlers[consumerName] = handlerEntry as unknown as\n | WorkerInferConsumerHandler<TContract, InferConsumerNames<TContract>>\n | WorkerInferConsumerBatchHandler<TContract, InferConsumerNames<TContract>>;\n }\n }\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 * Connections are automatically shared across clients and workers with the same\n * URLs and connection options, following RabbitMQ best practices.\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 logger,\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 logger,\n );\n\n return worker\n .waitForConnectionReady()\n .flatMapOk(() => worker.consumeAll())\n .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 // Clear all pending batch timers\n for (const timer of this.batchTimers.values()) {\n clearTimeout(timer);\n }\n this.batchTimers.clear();\n\n return Future.all(\n Array.from(this.consumerTags).map((consumerTag) =>\n Future.fromPromise(this.amqpClient.channel.cancel(consumerTag)).mapErrorToResult(\n (error) => {\n this.logger?.warn(\"Failed to cancel consumer during close\", { consumerTag, error });\n return Result.Ok(undefined);\n },\n ),\n ),\n )\n .map(Result.all)\n .tapOk(() => {\n // Clear consumer tags after successful cancellation\n this.consumerTags.clear();\n })\n .flatMapOk(() => 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 // Calculate the maximum prefetch value among all consumers\n // Since prefetch is per-channel in AMQP 0.9.1, we use the maximum value\n const consumerNames = Object.keys(this.contract.consumers) as InferConsumerNames<TContract>[];\n let maxPrefetch = 0;\n\n for (const consumerName of consumerNames) {\n const options = this.consumerOptions[consumerName];\n if (options?.prefetch !== undefined) {\n if (options.prefetch <= 0 || !Number.isInteger(options.prefetch)) {\n return Future.value(\n Result.Error(\n new TechnicalError(\n `Invalid prefetch value for \"${String(consumerName)}\": must be a positive integer`,\n ),\n ),\n );\n }\n maxPrefetch = Math.max(maxPrefetch, options.prefetch);\n }\n if (options?.batchSize !== undefined) {\n // Batch consumers need prefetch at least equal to batch size\n const effectivePrefetch = options.prefetch ?? options.batchSize;\n maxPrefetch = Math.max(maxPrefetch, effectivePrefetch);\n }\n }\n\n // Apply the maximum prefetch if any consumer specified it\n if (maxPrefetch > 0) {\n this.amqpClient.channel.addSetup(async (channel: Channel) => {\n await channel.prefetch(maxPrefetch);\n });\n }\n\n return Future.all(consumerNames.map((consumerName) => this.consume(consumerName)))\n .map(Result.all)\n .mapOk(() => undefined);\n }\n\n private waitForConnectionReady(): Future<Result<void, TechnicalError>> {\n return Future.fromPromise(this.amqpClient.channel.waitForConnect()).mapError(\n (error) => new TechnicalError(\"Failed to wait for connection ready\", error),\n );\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.actualHandlers[consumerName];\n if (!handler) {\n return Future.value(\n Result.Error(new TechnicalError(`Handler for \"${String(consumerName)}\" not provided`)),\n );\n }\n\n // Get consumer-specific options\n const options = this.consumerOptions[consumerName] ?? {};\n\n // Validate batch size if specified\n if (options.batchSize !== undefined) {\n if (options.batchSize <= 0 || !Number.isInteger(options.batchSize)) {\n return Future.value(\n Result.Error(\n new TechnicalError(\n `Invalid batchSize for \"${String(consumerName)}\": must be a positive integer`,\n ),\n ),\n );\n }\n }\n\n // Validate batch timeout if specified\n if (options.batchTimeout !== undefined) {\n if (\n typeof options.batchTimeout !== \"number\" ||\n !Number.isFinite(options.batchTimeout) ||\n options.batchTimeout <= 0\n ) {\n return Future.value(\n Result.Error(\n new TechnicalError(\n `Invalid batchTimeout for \"${String(consumerName)}\": must be a positive number`,\n ),\n ),\n );\n }\n }\n\n // Check if this is a batch consumer\n const isBatchConsumer = options.batchSize !== undefined && options.batchSize > 0;\n\n if (isBatchConsumer) {\n return this.consumeBatch(\n consumerName,\n consumer,\n options,\n handler as unknown as (\n messages: Array<WorkerInferConsumerInput<TContract, TName>>,\n ) => Promise<void>,\n );\n } else {\n return this.consumeSingle(\n consumerName,\n consumer,\n handler as unknown as (\n message: WorkerInferConsumerInput<TContract, TName>,\n ) => Promise<void>,\n );\n }\n }\n\n /**\n * Parse and validate a message from AMQP\n * @returns Future<Result<validated message, void>> - Ok with validated message, or Error (already handled with nack)\n */\n private parseAndValidateMessage<TName extends InferConsumerNames<TContract>>(\n msg: Message,\n consumer: ConsumerDefinition,\n consumerName: TName,\n ): Future<Result<WorkerInferConsumerInput<TContract, TName>, void>> {\n // Decompress message if needed\n const decompressMessage = Future.fromPromise(\n decompressBuffer(msg.content, msg.properties.contentEncoding),\n ).mapError((error) => {\n this.logger?.error(\"Error decompressing message\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n contentEncoding: msg.properties.contentEncoding,\n error,\n });\n\n // Reject message with no requeue (decompression failed)\n this.amqpClient.channel.nack(msg, false, false);\n return undefined;\n });\n\n // Parse message\n const parseMessage = (buffer: Buffer) => {\n const parseResult = Result.fromExecution(() => JSON.parse(buffer.toString()));\n if (parseResult.isError()) {\n this.logger?.error(\"Error parsing message\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n error: parseResult.error,\n });\n\n // Reject message with no requeue (malformed JSON)\n this.amqpClient.channel.nack(msg, false, false);\n return Future.value(Result.Error(undefined));\n }\n return Future.value(Result.Ok(parseResult.value));\n };\n\n // Validate message\n const validateMessage = (parsedMessage: unknown) => {\n const rawValidation = consumer.message.payload[\"~standard\"].validate(parsedMessage);\n return Future.fromPromise(\n rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation),\n ).mapOkToResult((validationResult) => {\n if (validationResult.issues) {\n const error = new MessageValidationError(String(consumerName), validationResult.issues);\n this.logger?.error(\"Message validation failed\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n error,\n });\n\n // Reject message with no requeue (validation failed)\n this.amqpClient.channel.nack(msg, false, false);\n return Result.Error(undefined) as Result<\n WorkerInferConsumerInput<TContract, TName>,\n void\n >;\n }\n\n return Result.Ok(validationResult.value as WorkerInferConsumerInput<TContract, TName>);\n }) as Future<Result<WorkerInferConsumerInput<TContract, TName>, void>>;\n };\n\n return decompressMessage.flatMapOk(parseMessage).flatMapOk(validateMessage) as Future<\n Result<WorkerInferConsumerInput<TContract, TName>, void>\n >;\n }\n\n /**\n * Consume messages one at a time\n */\n private consumeSingle<TName extends InferConsumerNames<TContract>>(\n consumerName: TName,\n consumer: ConsumerDefinition,\n handler: (message: WorkerInferConsumerInput<TContract, TName>) => Promise<void>,\n ): Future<Result<void, TechnicalError>> {\n // Start consuming\n return Future.fromPromise(\n this.amqpClient.channel.consume(consumer.queue.name, async (msg) => {\n // Handle null messages (consumer cancellation)\n if (msg === null) {\n this.logger?.warn(\"Consumer cancelled by server\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n });\n return;\n }\n\n // Parse and validate message\n await this.parseAndValidateMessage(msg, consumer, consumerName)\n .flatMapOk((validatedMessage) =>\n Future.fromPromise(handler(validatedMessage)).tapError((error) => {\n this.logger?.error(\"Error processing message\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n error,\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 this.logger?.info(\"Message consumed successfully\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n });\n\n // Acknowledge message\n this.amqpClient.channel.ack(msg);\n })\n .toPromise();\n }),\n )\n .tapOk((reply) => {\n // Store consumer tag for later cancellation\n this.consumerTags.add(reply.consumerTag);\n })\n .mapError(\n (error) =>\n new TechnicalError(`Failed to start consuming for \"${String(consumerName)}\"`, error),\n )\n .mapOk(() => undefined);\n }\n\n /**\n * Consume messages in batches\n */\n private consumeBatch<TName extends InferConsumerNames<TContract>>(\n consumerName: TName,\n consumer: ConsumerDefinition,\n options: ConsumerOptions,\n handler: (messages: Array<WorkerInferConsumerInput<TContract, TName>>) => Promise<void>,\n ): Future<Result<void, TechnicalError>> {\n const batchSize = options.batchSize!;\n const batchTimeout = options.batchTimeout ?? 1000;\n const timerKey = String(consumerName);\n\n // Note: Prefetch is handled globally in consumeAll()\n // Batch accumulation state\n type BatchItem = {\n message: WorkerInferConsumerInput<TContract, TName>;\n amqpMessage: Message;\n };\n let batch: BatchItem[] = [];\n // Track if batch processing is currently in progress to avoid race conditions\n let isProcessing = false;\n\n const processBatch = async () => {\n // Prevent concurrent batch processing\n if (isProcessing || batch.length === 0) {\n return;\n }\n\n isProcessing = true;\n\n const currentBatch = batch;\n batch = [];\n\n // Clear timer from tracking\n const timer = this.batchTimers.get(timerKey);\n if (timer) {\n clearTimeout(timer);\n this.batchTimers.delete(timerKey);\n }\n\n const messages = currentBatch.map((item) => item.message);\n\n this.logger?.info(\"Processing batch\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n batchSize: currentBatch.length,\n });\n\n try {\n await handler(messages);\n\n // Acknowledge all messages in the batch\n for (const item of currentBatch) {\n this.amqpClient.channel.ack(item.amqpMessage);\n }\n\n this.logger?.info(\"Batch processed successfully\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n batchSize: currentBatch.length,\n });\n } catch (error) {\n this.logger?.error(\"Error processing batch\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n batchSize: currentBatch.length,\n error,\n });\n\n // fixme proper error handling strategy\n // Reject all messages and requeue (handler failed)\n for (const item of currentBatch) {\n this.amqpClient.channel.nack(item.amqpMessage, false, true);\n }\n } finally {\n isProcessing = false;\n }\n };\n\n const scheduleBatchProcessing = () => {\n // Don't schedule if batch is currently being processed\n if (isProcessing) {\n return;\n }\n\n // Clear existing timer\n const existingTimer = this.batchTimers.get(timerKey);\n if (existingTimer) {\n clearTimeout(existingTimer);\n }\n\n // Schedule new timer and track it\n const timer = setTimeout(() => {\n processBatch().catch((error) => {\n this.logger?.error(\"Unexpected error in batch processing\", {\n consumerName: String(consumerName),\n error,\n });\n });\n }, batchTimeout);\n\n this.batchTimers.set(timerKey, timer);\n };\n\n // Start consuming\n return Future.fromPromise(\n this.amqpClient.channel.consume(consumer.queue.name, async (msg) => {\n // Handle null messages (consumer cancellation)\n if (msg === null) {\n this.logger?.warn(\"Consumer cancelled by server\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n });\n // Process any remaining messages in the batch\n await processBatch();\n return;\n }\n\n // Parse and validate message\n const validationResult = await this.parseAndValidateMessage(\n msg,\n consumer,\n consumerName,\n ).toPromise();\n\n if (validationResult.isError()) {\n // Error already handled in parseAndValidateMessage (nacked)\n return;\n }\n\n // Add to batch\n batch.push({\n message: validationResult.value,\n amqpMessage: msg,\n });\n\n // Process batch if full\n if (batch.length >= batchSize) {\n await processBatch();\n // After processing a full batch, schedule timer for any subsequent messages\n // This ensures that if more messages arrive at a slow rate, they won't be held indefinitely\n if (batch.length > 0 && !this.batchTimers.has(timerKey)) {\n scheduleBatchProcessing();\n }\n } else {\n // Schedule batch processing if not already scheduled\n if (!this.batchTimers.has(timerKey)) {\n scheduleBatchProcessing();\n }\n }\n }),\n )\n .tapOk((reply) => {\n // Store consumer tag for later cancellation\n this.consumerTags.add(reply.consumerTag);\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 {\n WorkerInferConsumerBatchHandler,\n WorkerInferConsumerHandler,\n WorkerInferConsumerHandlerEntry,\n WorkerInferConsumerHandlers,\n} 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 * Supports three patterns:\n * 1. Simple handler: just the function (single message handler)\n * 2. Handler with prefetch: [handler, { prefetch: 10 }] (single message handler with config)\n * 3. Batch handler: [batchHandler, { batchSize: 5, batchTimeout: 1000 }] (REQUIRES batchSize config)\n *\n * **Important**: Batch handlers (handlers that accept an array of messages) MUST include\n * batchSize configuration. You cannot create a batch handler without specifying batchSize.\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 (single or batch)\n * @param options - Optional consumer options (prefetch, batchSize, batchTimeout)\n * - For single-message handlers: { prefetch?: number } is optional\n * - For batch handlers: { batchSize: number, batchTimeout?: number } is REQUIRED\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 * // Simple single-message handler without options\n * const processOrderHandler = defineHandler(\n * orderContract,\n * 'processOrder',\n * async (message) => {\n * console.log('Processing order:', message.orderId);\n * await processPayment(message);\n * }\n * );\n *\n * // Single-message handler with prefetch\n * const processOrderWithPrefetch = defineHandler(\n * orderContract,\n * 'processOrder',\n * async (message) => {\n * await processOrder(message);\n * },\n * { prefetch: 10 }\n * );\n *\n * // Batch handler - MUST include batchSize\n * const processBatchOrders = defineHandler(\n * orderContract,\n * 'processOrders',\n * async (messages) => {\n * // messages is an array - batchSize configuration is REQUIRED\n * await db.insertMany(messages);\n * },\n * { batchSize: 5, batchTimeout: 1000 }\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): WorkerInferConsumerHandlerEntry<TContract, TName>;\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 options: { prefetch?: number; batchSize?: never; batchTimeout?: never },\n): WorkerInferConsumerHandlerEntry<TContract, TName>;\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: WorkerInferConsumerBatchHandler<TContract, TName>,\n options: { prefetch?: number; batchSize: number; batchTimeout?: number },\n): WorkerInferConsumerHandlerEntry<TContract, TName>;\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler:\n | WorkerInferConsumerHandler<TContract, TName>\n | WorkerInferConsumerBatchHandler<TContract, TName>,\n options?: { prefetch?: number; batchSize?: number; batchTimeout?: number },\n): WorkerInferConsumerHandlerEntry<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 with options if provided, otherwise just the handler\n if (options) {\n return [handler, options] as WorkerInferConsumerHandlerEntry<TContract, TName>;\n }\n return handler as WorkerInferConsumerHandlerEntry<TContract, TName>;\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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"}
package/docs/index.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ### MessageValidationError
10
10
 
11
- Defined in: [packages/worker/src/errors.ts:35](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/errors.ts#L35)
11
+ Defined in: [packages/worker/src/errors.ts:35](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/errors.ts#L35)
12
12
 
13
13
  Error thrown when message validation fails
14
14
 
@@ -24,7 +24,7 @@ Error thrown when message validation fails
24
24
  new MessageValidationError(consumerName, issues): MessageValidationError;
25
25
  ```
26
26
 
27
- Defined in: [packages/worker/src/errors.ts:36](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/errors.ts#L36)
27
+ Defined in: [packages/worker/src/errors.ts:36](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/errors.ts#L36)
28
28
 
29
29
  ###### Parameters
30
30
 
@@ -48,8 +48,8 @@ WorkerError.constructor
48
48
  | Property | Modifier | Type | Description | Inherited from | Defined in |
49
49
  | ------ | ------ | ------ | ------ | ------ | ------ |
50
50
  | <a id="cause"></a> `cause?` | `public` | `unknown` | - | `WorkerError.cause` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:26 |
51
- | <a id="consumername"></a> `consumerName` | `readonly` | `string` | - | - | [packages/worker/src/errors.ts:37](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/errors.ts#L37) |
52
- | <a id="issues"></a> `issues` | `readonly` | `unknown` | - | - | [packages/worker/src/errors.ts:38](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/errors.ts#L38) |
51
+ | <a id="consumername"></a> `consumerName` | `readonly` | `string` | - | - | [packages/worker/src/errors.ts:37](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/errors.ts#L37) |
52
+ | <a id="issues"></a> `issues` | `readonly` | `unknown` | - | - | [packages/worker/src/errors.ts:38](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/errors.ts#L38) |
53
53
  | <a id="message"></a> `message` | `public` | `string` | - | `WorkerError.message` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 |
54
54
  | <a id="name"></a> `name` | `public` | `string` | - | `WorkerError.name` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 |
55
55
  | <a id="stack"></a> `stack?` | `public` | `string` | - | `WorkerError.stack` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 |
@@ -159,7 +159,7 @@ WorkerError.prepareStackTrace
159
159
 
160
160
  ### TechnicalError
161
161
 
162
- Defined in: [packages/worker/src/errors.ts:22](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/errors.ts#L22)
162
+ Defined in: [packages/worker/src/errors.ts:22](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/errors.ts#L22)
163
163
 
164
164
  Error for technical/runtime failures in worker operations
165
165
  This includes validation failures, parsing failures, and processing failures
@@ -176,7 +176,7 @@ This includes validation failures, parsing failures, and processing failures
176
176
  new TechnicalError(message, cause?): TechnicalError;
177
177
  ```
178
178
 
179
- Defined in: [packages/worker/src/errors.ts:23](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/errors.ts#L23)
179
+ Defined in: [packages/worker/src/errors.ts:23](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/errors.ts#L23)
180
180
 
181
181
  ###### Parameters
182
182
 
@@ -199,7 +199,7 @@ WorkerError.constructor
199
199
 
200
200
  | Property | Modifier | Type | Description | Inherited from | Defined in |
201
201
  | ------ | ------ | ------ | ------ | ------ | ------ |
202
- | <a id="cause-1"></a> `cause?` | `readonly` | `unknown` | - | `WorkerError.cause` | [packages/worker/src/errors.ts:25](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/errors.ts#L25) |
202
+ | <a id="cause-1"></a> `cause?` | `readonly` | `unknown` | - | `WorkerError.cause` | [packages/worker/src/errors.ts:25](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/errors.ts#L25) |
203
203
  | <a id="message-1"></a> `message` | `public` | `string` | - | `WorkerError.message` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 |
204
204
  | <a id="name-1"></a> `name` | `public` | `string` | - | `WorkerError.name` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 |
205
205
  | <a id="stack-1"></a> `stack?` | `public` | `string` | - | `WorkerError.stack` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 |
@@ -309,7 +309,7 @@ WorkerError.prepareStackTrace
309
309
 
310
310
  ### TypedAmqpWorker
311
311
 
312
- Defined in: [packages/worker/src/worker.ts:129](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/worker.ts#L129)
312
+ Defined in: [packages/worker/src/worker.ts:130](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/worker.ts#L130)
313
313
 
314
314
  Type-safe AMQP worker for consuming messages from RabbitMQ.
315
315
 
@@ -363,7 +363,7 @@ await worker.close().resultToPromise();
363
363
  close(): Future<Result<void, TechnicalError>>;
364
364
  ```
365
365
 
366
- Defined in: [packages/worker/src/worker.ts:242](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/worker.ts#L242)
366
+ Defined in: [packages/worker/src/worker.ts:244](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/worker.ts#L244)
367
367
 
368
368
  Close the AMQP channel and connection.
369
369
 
@@ -391,7 +391,7 @@ if (closeResult.isOk()) {
391
391
  static create<TContract>(options): Future<Result<TypedAmqpWorker<TContract>, TechnicalError>>;
392
392
  ```
393
393
 
394
- Defined in: [packages/worker/src/worker.ts:203](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/worker.ts#L203)
394
+ Defined in: [packages/worker/src/worker.ts:205](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/worker.ts#L205)
395
395
 
396
396
  Create a type-safe AMQP worker from a contract.
397
397
 
@@ -445,7 +445,7 @@ if (workerResult.isError()) {
445
445
  type CreateWorkerOptions<TContract> = object;
446
446
  ```
447
447
 
448
- Defined in: [packages/worker/src/worker.ts:76](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/worker.ts#L76)
448
+ Defined in: [packages/worker/src/worker.ts:77](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/worker.ts#L77)
449
449
 
450
450
  Options for creating a type-safe AMQP worker.
451
451
 
@@ -492,11 +492,11 @@ const options: CreateWorkerOptions<typeof contract> = {
492
492
 
493
493
  | Property | Type | Description | Defined in |
494
494
  | ------ | ------ | ------ | ------ |
495
- | <a id="connectionoptions"></a> `connectionOptions?` | `AmqpConnectionManagerOptions` | Optional connection configuration (heartbeat, reconnect settings, etc.) | [packages/worker/src/worker.ts:84](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/worker.ts#L84) |
496
- | <a id="contract"></a> `contract` | `TContract` | The AMQP contract definition specifying consumers and their message schemas | [packages/worker/src/worker.ts:78](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/worker.ts#L78) |
497
- | <a id="handlers"></a> `handlers` | [`WorkerInferConsumerHandlers`](#workerinferconsumerhandlers)\<`TContract`\> | Handlers for each consumer defined in the contract. Can be a function or a tuple of [handler, options] | [packages/worker/src/worker.ts:80](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/worker.ts#L80) |
498
- | <a id="logger"></a> `logger?` | `Logger` | Optional logger for logging message consumption and errors | [packages/worker/src/worker.ts:86](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/worker.ts#L86) |
499
- | <a id="urls"></a> `urls` | `ConnectionUrl`[] | AMQP broker URL(s). Multiple URLs provide failover support | [packages/worker/src/worker.ts:82](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/worker.ts#L82) |
495
+ | <a id="connectionoptions"></a> `connectionOptions?` | `AmqpConnectionManagerOptions` | Optional connection configuration (heartbeat, reconnect settings, etc.) | [packages/worker/src/worker.ts:85](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/worker.ts#L85) |
496
+ | <a id="contract"></a> `contract` | `TContract` | The AMQP contract definition specifying consumers and their message schemas | [packages/worker/src/worker.ts:79](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/worker.ts#L79) |
497
+ | <a id="handlers"></a> `handlers` | [`WorkerInferConsumerHandlers`](#workerinferconsumerhandlers)\<`TContract`\> | Handlers for each consumer defined in the contract. Can be a function or a tuple of [handler, options] | [packages/worker/src/worker.ts:81](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/worker.ts#L81) |
498
+ | <a id="logger"></a> `logger?` | `Logger` | Optional logger for logging message consumption and errors | [packages/worker/src/worker.ts:87](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/worker.ts#L87) |
499
+ | <a id="urls"></a> `urls` | `ConnectionUrl`[] | AMQP broker URL(s). Multiple URLs provide failover support | [packages/worker/src/worker.ts:83](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/worker.ts#L83) |
500
500
 
501
501
  ***
502
502
 
@@ -506,7 +506,7 @@ const options: CreateWorkerOptions<typeof contract> = {
506
506
  type WorkerInferConsumerBatchHandler<TContract, TName> = (messages) => Promise<void>;
507
507
  ```
508
508
 
509
- Defined in: [packages/worker/src/types.ts:56](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/types.ts#L56)
509
+ Defined in: [packages/worker/src/types.ts:56](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/types.ts#L56)
510
510
 
511
511
  Infer consumer handler type for batch processing.
512
512
  Batch handlers receive an array of messages.
@@ -536,7 +536,7 @@ Batch handlers receive an array of messages.
536
536
  type WorkerInferConsumerHandler<TContract, TName> = (message) => Promise<void>;
537
537
  ```
538
538
 
539
- Defined in: [packages/worker/src/types.ts:47](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/types.ts#L47)
539
+ Defined in: [packages/worker/src/types.ts:47](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/types.ts#L47)
540
540
 
541
541
  Infer consumer handler type for a specific consumer.
542
542
  Handlers always receive a single message by default.
@@ -578,7 +578,7 @@ type WorkerInferConsumerHandlerEntry<TContract, TName> =
578
578
  }];
579
579
  ```
580
580
 
581
- Defined in: [packages/worker/src/types.ts:69](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/types.ts#L69)
581
+ Defined in: [packages/worker/src/types.ts:69](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/types.ts#L69)
582
582
 
583
583
  Infer handler entry for a consumer - either a function or a tuple of [handler, options].
584
584
 
@@ -602,7 +602,7 @@ Three patterns are supported:
602
602
  type WorkerInferConsumerHandlers<TContract> = { [K in InferConsumerNames<TContract>]: WorkerInferConsumerHandlerEntry<TContract, K> };
603
603
  ```
604
604
 
605
- Defined in: [packages/worker/src/types.ts:87](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/types.ts#L87)
605
+ Defined in: [packages/worker/src/types.ts:87](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/types.ts#L87)
606
606
 
607
607
  Infer all consumer handlers for a contract.
608
608
  Handlers can be either single-message handlers, batch handlers, or a tuple of [handler, options].
@@ -621,7 +621,7 @@ Handlers can be either single-message handlers, batch handlers, or a tuple of [h
621
621
  type WorkerInferConsumerInput<TContract, TName> = ConsumerInferInput<InferConsumer<TContract, TName>>;
622
622
  ```
623
623
 
624
- Defined in: [packages/worker/src/types.ts:37](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/types.ts#L37)
624
+ Defined in: [packages/worker/src/types.ts:37](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/types.ts#L37)
625
625
 
626
626
  Worker perspective types - for consuming messages
627
627
 
@@ -645,7 +645,7 @@ function defineHandler<TContract, TName>(
645
645
  handler): WorkerInferConsumerHandlerEntry<TContract, TName>;
646
646
  ```
647
647
 
648
- Defined in: [packages/worker/src/handlers.ts:70](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/handlers.ts#L70)
648
+ Defined in: [packages/worker/src/handlers.ts:70](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/handlers.ts#L70)
649
649
 
650
650
  Define a type-safe handler for a specific consumer in a contract.
651
651
 
@@ -729,7 +729,7 @@ function defineHandler<TContract, TName>(
729
729
  options): WorkerInferConsumerHandlerEntry<TContract, TName>;
730
730
  ```
731
731
 
732
- Defined in: [packages/worker/src/handlers.ts:78](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/handlers.ts#L78)
732
+ Defined in: [packages/worker/src/handlers.ts:78](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/handlers.ts#L78)
733
733
 
734
734
  Define a type-safe handler for a specific consumer in a contract.
735
735
 
@@ -817,7 +817,7 @@ function defineHandler<TContract, TName>(
817
817
  options): WorkerInferConsumerHandlerEntry<TContract, TName>;
818
818
  ```
819
819
 
820
- Defined in: [packages/worker/src/handlers.ts:87](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/handlers.ts#L87)
820
+ Defined in: [packages/worker/src/handlers.ts:87](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/handlers.ts#L87)
821
821
 
822
822
  Define a type-safe handler for a specific consumer in a contract.
823
823
 
@@ -903,7 +903,7 @@ const processBatchOrders = defineHandler(
903
903
  function defineHandlers<TContract>(contract, handlers): WorkerInferConsumerHandlers<TContract>;
904
904
  ```
905
905
 
906
- Defined in: [packages/worker/src/handlers.ts:181](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/worker/src/handlers.ts#L181)
906
+ Defined in: [packages/worker/src/handlers.ts:181](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/worker/src/handlers.ts#L181)
907
907
 
908
908
  Define multiple type-safe handlers for consumers in a contract.
909
909
 
package/package.json CHANGED
@@ -1,13 +1,21 @@
1
1
  {
2
2
  "name": "@amqp-contract/worker",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Worker utilities for consuming messages using amqp-contract",
5
5
  "keywords": [
6
6
  "amqp",
7
7
  "contract",
8
8
  "rabbitmq",
9
9
  "typescript",
10
- "worker"
10
+ "worker",
11
+ "nodejs",
12
+ "messaging",
13
+ "type-safe",
14
+ "schema",
15
+ "validation",
16
+ "message-broker",
17
+ "message-queue",
18
+ "consumer"
11
19
  ],
12
20
  "homepage": "https://github.com/btravers/amqp-contract#readme",
13
21
  "bugs": {
@@ -44,8 +52,8 @@
44
52
  "dependencies": {
45
53
  "@standard-schema/spec": "1.1.0",
46
54
  "@swan-io/boxed": "3.2.1",
47
- "@amqp-contract/contract": "0.6.0",
48
- "@amqp-contract/core": "0.6.0"
55
+ "@amqp-contract/contract": "0.7.0",
56
+ "@amqp-contract/core": "0.7.0"
49
57
  },
50
58
  "devDependencies": {
51
59
  "@types/amqplib": "0.10.8",
@@ -53,13 +61,13 @@
53
61
  "@vitest/coverage-v8": "4.0.16",
54
62
  "amqp-connection-manager": "5.0.0",
55
63
  "amqplib": "0.10.9",
56
- "tsdown": "0.18.3",
64
+ "tsdown": "0.18.4",
57
65
  "typedoc": "0.28.15",
58
66
  "typedoc-plugin-markdown": "4.9.0",
59
67
  "typescript": "5.9.3",
60
68
  "vitest": "4.0.16",
61
- "zod": "4.2.1",
62
- "@amqp-contract/testing": "0.6.0",
69
+ "zod": "4.3.5",
70
+ "@amqp-contract/testing": "0.7.0",
63
71
  "@amqp-contract/tsconfig": "0.0.0",
64
72
  "@amqp-contract/typedoc": "0.0.1"
65
73
  },
@@ -67,9 +75,8 @@
67
75
  "build": "tsdown src/index.ts --format cjs,esm --dts --clean",
68
76
  "build:docs": "typedoc",
69
77
  "dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
70
- "test": "vitest run --project integration",
78
+ "test": "vitest run --project unit",
71
79
  "test:integration": "vitest run --project integration",
72
- "test:watch": "vitest --project integration",
73
80
  "typecheck": "tsc --noEmit"
74
81
  }
75
82
  }