@amqp-contract/worker 0.9.0 → 0.10.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/dist/index.cjs CHANGED
@@ -252,6 +252,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
252
252
  deadLetterRoutingKey: queueName
253
253
  });
254
254
  await channel.bindQueue(waitQueueName, dlxName, `${queueName}-wait`);
255
+ await channel.bindQueue(queueName, dlxName, queueName);
255
256
  this.logger?.info("Wait queue created and bound", {
256
257
  consumerName: String(consumerName),
257
258
  queueName,
package/dist/index.mjs CHANGED
@@ -252,6 +252,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
252
252
  deadLetterRoutingKey: queueName
253
253
  });
254
254
  await channel.bindQueue(waitQueueName, dlxName, `${queueName}-wait`);
255
+ await channel.bindQueue(queueName, dlxName, queueName);
255
256
  this.logger?.info("Wait queue created and bound", {
256
257
  consumerName: String(consumerName),
257
258
  queueName,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["error"],"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\n/**\n * Retryable errors - transient failures that may succeed on retry\n * Examples: network timeouts, rate limiting, temporary service unavailability\n *\n * Use this error type when the operation might succeed if retried.\n * The worker will apply exponential backoff and retry the message.\n */\nexport class RetryableError extends WorkerError {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"RetryableError\";\n }\n}\n\n/**\n * Non-retryable errors - permanent failures that should not be retried\n * Examples: invalid data, business rule violations, permanent external failures\n *\n * Use this error type when retrying would not help - the message will be\n * immediately sent to the dead letter queue (DLQ) if configured.\n */\nexport class NonRetryableError extends WorkerError {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"NonRetryableError\";\n }\n}\n\n/**\n * Union type representing all handler errors.\n * Use this type when defining handlers that explicitly signal error outcomes.\n */\nexport type HandlerError = RetryableError | NonRetryableError;\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 {\n AmqpClient,\n type Logger,\n type TelemetryProvider,\n defaultTelemetryProvider,\n endSpanError,\n endSpanSuccess,\n recordConsumeMetric,\n startConsumeSpan,\n} 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, NonRetryableError, TechnicalError } from \"./errors.js\";\nimport type {\n WorkerInferConsumerInput,\n WorkerInferSafeConsumerBatchHandler,\n WorkerInferSafeConsumerHandler,\n WorkerInferSafeConsumerHandlers,\n} from \"./types.js\";\nimport type { HandlerError } from \"./errors.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 * Retry configuration options for handling failed message processing.\n *\n * When enabled, the worker will automatically retry failed messages using\n * RabbitMQ's native TTL + Dead Letter Exchange (DLX) pattern.\n */\nexport type RetryOptions = {\n /** Maximum retry attempts before sending to DLQ (default: 3) */\n maxRetries?: number;\n /** Initial delay in ms before first retry (default: 1000) */\n initialDelayMs?: number;\n /** Maximum delay in ms between retries (default: 30000) */\n maxDelayMs?: number;\n /** Exponential backoff multiplier (default: 2) */\n backoffMultiplier?: number;\n /** Add jitter to prevent thundering herd (default: true) */\n jitter?: boolean;\n};\n\n/**\n * Internal retry configuration with all values resolved.\n */\ntype ResolvedRetryConfig = {\n maxRetries: number;\n initialDelayMs: number;\n maxDelayMs: number;\n backoffMultiplier: number;\n jitter: boolean;\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 * retry: {\n * maxRetries: 3,\n * initialDelayMs: 1000,\n * maxDelayMs: 30000,\n * backoffMultiplier: 2,\n * jitter: true\n * }\n * };\n * ```\n */\nexport type CreateWorkerOptions<TContract extends ContractDefinition> = {\n /** The AMQP contract definition specifying consumers and their message schemas */\n contract: TContract;\n /**\n * Handlers for each consumer defined in the contract.\n * Handlers must return `Future<Result<void, HandlerError>>` for explicit error handling.\n * Use defineHandler() to create safe handlers, or defineUnsafeHandler() which wraps\n * Promise-based handlers into safe handlers internally.\n */\n handlers: WorkerInferSafeConsumerHandlers<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 /** Retry configuration - when undefined, uses legacy behavior (immediate requeue) */\n retry?: RetryOptions | undefined;\n /**\n * Optional telemetry provider for tracing and metrics.\n * If not provided, uses the default provider which attempts to load OpenTelemetry.\n * OpenTelemetry instrumentation is automatically enabled if @opentelemetry/api is installed.\n */\n telemetry?: TelemetryProvider | 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 /**\n * Internal handler type - always safe handlers (`Future<Result>`).\n * Unsafe handlers are wrapped into safe handlers by defineUnsafeHandler/defineUnsafeHandlers.\n */\n private readonly actualHandlers: Partial<\n Record<\n InferConsumerNames<TContract>,\n | WorkerInferSafeConsumerHandler<TContract, InferConsumerNames<TContract>>\n | WorkerInferSafeConsumerBatchHandler<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 private readonly retryConfig: ResolvedRetryConfig | null;\n private readonly telemetry: TelemetryProvider;\n\n private constructor(\n private readonly contract: TContract,\n private readonly amqpClient: AmqpClient,\n handlers: WorkerInferSafeConsumerHandlers<TContract>,\n private readonly logger?: Logger,\n retryOptions?: RetryOptions,\n telemetry?: TelemetryProvider,\n ) {\n this.telemetry = telemetry ?? defaultTelemetryProvider;\n\n // Extract handlers and options from the handlers object\n this.actualHandlers = {};\n this.consumerOptions = {};\n\n // Cast handlers to a generic record for iteration\n const handlersRecord = handlers as Record<string, unknown>;\n\n for (const consumerName of Object.keys(handlersRecord)) {\n const handlerEntry = handlersRecord[consumerName];\n const typedConsumerName = consumerName as InferConsumerNames<TContract>;\n\n if (Array.isArray(handlerEntry)) {\n // Tuple format: [handler, options]\n this.actualHandlers[typedConsumerName] = handlerEntry[0] as\n | WorkerInferSafeConsumerHandler<TContract, InferConsumerNames<TContract>>\n | WorkerInferSafeConsumerBatchHandler<TContract, InferConsumerNames<TContract>>;\n this.consumerOptions[typedConsumerName] = handlerEntry[1] as ConsumerOptions;\n } else {\n // Direct function format\n this.actualHandlers[typedConsumerName] = handlerEntry as\n | WorkerInferSafeConsumerHandler<TContract, InferConsumerNames<TContract>>\n | WorkerInferSafeConsumerBatchHandler<TContract, InferConsumerNames<TContract>>;\n }\n }\n\n // Initialize retry configuration\n if (retryOptions === undefined) {\n this.retryConfig = null; // Legacy behavior\n } else {\n // Set defaults for retry options\n this.retryConfig = {\n maxRetries: retryOptions.maxRetries ?? 3,\n initialDelayMs: retryOptions.initialDelayMs ?? 1000,\n maxDelayMs: retryOptions.maxDelayMs ?? 30000,\n backoffMultiplier: retryOptions.backoffMultiplier ?? 2,\n jitter: retryOptions.jitter ?? true,\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 worker = 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 */\n static create<TContract extends ContractDefinition>({\n contract,\n handlers,\n urls,\n connectionOptions,\n logger,\n retry,\n telemetry,\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 retry,\n telemetry,\n );\n\n return worker\n .waitForConnectionReady()\n .flatMapOk(() => worker.setupWaitQueues())\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 * Set up wait queues for retry mechanism.\n * Creates and binds wait queues for each consumer queue that has DLX configuration.\n */\n private setupWaitQueues(): Future<Result<void, TechnicalError>> {\n // Skip if retry is not configured\n if (this.retryConfig === null) {\n return Future.value(Result.Ok(undefined));\n }\n\n if (!this.contract.consumers || !this.contract.queues) {\n return Future.value(Result.Ok(undefined));\n }\n\n const setupTasks: Array<Future<Result<void, TechnicalError>>> = [];\n\n for (const consumerName of Object.keys(\n this.contract.consumers,\n ) as InferConsumerNames<TContract>[]) {\n const consumer = this.contract.consumers[consumerName as string];\n if (!consumer) continue;\n\n const queue = consumer.queue;\n const deadLetter = queue.deadLetter;\n\n // Only create wait queues for queues with DLX configuration\n if (!deadLetter) continue;\n\n const queueName = queue.name;\n const waitQueueName = `${queueName}-wait`;\n const dlxName = deadLetter.exchange.name;\n\n const setupTask = Future.fromPromise(\n this.amqpClient.channel.addSetup(async (channel: Channel) => {\n // Create wait queue with DLX pointing back to the main queue\n await channel.assertQueue(waitQueueName, {\n durable: queue.durable ?? false,\n deadLetterExchange: dlxName,\n deadLetterRoutingKey: queueName,\n });\n\n // Bind wait queue to DLX with routing key pattern\n await channel.bindQueue(waitQueueName, dlxName, `${queueName}-wait`);\n\n this.logger?.info(\"Wait queue created and bound\", {\n consumerName: String(consumerName),\n queueName,\n waitQueueName,\n dlxName,\n });\n }),\n ).mapError(\n (error) =>\n new TechnicalError(`Failed to setup wait queue for \"${String(consumerName)}\"`, error),\n );\n\n setupTasks.push(setupTask);\n }\n\n if (setupTasks.length === 0) {\n return Future.value(Result.Ok(undefined));\n }\n\n return Future.all(setupTasks)\n .map(Result.all)\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 // All handlers are now safe handlers (Future<Result>)\n handler as (\n messages: Array<WorkerInferConsumerInput<TContract, TName>>,\n ) => Future<Result<void, HandlerError>>,\n );\n } else {\n return this.consumeSingle(\n consumerName,\n consumer,\n // All handlers are now safe handlers (Future<Result>)\n handler as (\n message: WorkerInferConsumerInput<TContract, TName>,\n ) => Future<Result<void, HandlerError>>,\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 ).tapError((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 });\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: (\n message: WorkerInferConsumerInput<TContract, TName>,\n ) => Future<Result<void, HandlerError>>,\n ): Future<Result<void, TechnicalError>> {\n const queueName = consumer.queue.name;\n\n // Start consuming\n return Future.fromPromise(\n this.amqpClient.channel.consume(queueName, 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,\n });\n return;\n }\n\n const startTime = Date.now();\n const span = startConsumeSpan(this.telemetry, queueName, String(consumerName), {\n \"messaging.rabbitmq.message.delivery_tag\": msg.fields.deliveryTag,\n });\n\n // Parse and validate message\n await this.parseAndValidateMessage(msg, consumer, consumerName)\n .flatMapOk((validatedMessage) =>\n handler(validatedMessage)\n .flatMapOk(() => {\n this.logger?.info(\"Message consumed successfully\", {\n consumerName: String(consumerName),\n queueName,\n });\n // Acknowledge message on success\n this.amqpClient.channel.ack(msg);\n\n // Record telemetry success\n const durationMs = Date.now() - startTime;\n endSpanSuccess(span);\n recordConsumeMetric(\n this.telemetry,\n queueName,\n String(consumerName),\n true,\n durationMs,\n );\n\n return Future.value(Result.Ok<void, HandlerError>(undefined));\n })\n .flatMapError((handlerError: HandlerError) => {\n // Handler returned an error\n this.logger?.error(\"Error processing message\", {\n consumerName: String(consumerName),\n queueName,\n errorType: handlerError.name,\n error: handlerError.message,\n });\n\n // Record telemetry failure\n const durationMs = Date.now() - startTime;\n endSpanError(span, handlerError);\n recordConsumeMetric(\n this.telemetry,\n queueName,\n String(consumerName),\n false,\n durationMs,\n );\n\n // Handle the error using retry mechanism\n return this.handleError(handlerError, msg, String(consumerName), consumer);\n }),\n )\n .tapError(() => {\n // Record telemetry failure for validation errors\n // Note: The actual validation error is logged in parseAndValidateMessage,\n // here we just record that validation failed for telemetry purposes\n const durationMs = Date.now() - startTime;\n endSpanError(span, new Error(\"Message validation failed\"));\n recordConsumeMetric(this.telemetry, queueName, String(consumerName), false, durationMs);\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 * Handle batch processing error by applying error handling to all messages.\n */\n private handleBatchError(\n error: HandlerError,\n currentBatch: Array<{ amqpMessage: Message }>,\n consumerName: string,\n consumer: ConsumerDefinition,\n ): Future<Result<void, TechnicalError>> {\n return Future.all(\n currentBatch.map((item) => this.handleError(error, item.amqpMessage, consumerName, consumer)),\n )\n .map(Result.all)\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: (\n messages: Array<WorkerInferConsumerInput<TContract, TName>>,\n ) => Future<Result<void, HandlerError>>,\n ): Future<Result<void, TechnicalError>> {\n const batchSize = options.batchSize!;\n const batchTimeout = options.batchTimeout ?? 1000;\n const timerKey = String(consumerName);\n const queueName = consumer.queue.name;\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 = (): Future<Result<void, TechnicalError>> => {\n // Prevent concurrent batch processing\n if (isProcessing || batch.length === 0) {\n return Future.value(Result.Ok(undefined));\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 const batchCount = currentBatch.length;\n\n // Start telemetry span for batch processing\n const startTime = Date.now();\n const span = startConsumeSpan(this.telemetry, queueName, String(consumerName), {\n \"amqp.batch.size\": batchCount,\n });\n\n this.logger?.info(\"Processing batch\", {\n consumerName: String(consumerName),\n queueName,\n batchSize: batchCount,\n });\n\n return handler(messages)\n .flatMapOk(() => {\n // Acknowledge all messages in the batch on success\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,\n batchSize: batchCount,\n });\n\n // Record telemetry success\n const durationMs = Date.now() - startTime;\n endSpanSuccess(span);\n recordConsumeMetric(this.telemetry, queueName, String(consumerName), true, durationMs);\n\n return Future.value(Result.Ok<void, TechnicalError>(undefined));\n })\n .flatMapError((handlerError: HandlerError) => {\n // Handler returned an error - log it\n this.logger?.error(\"Error processing batch\", {\n consumerName: String(consumerName),\n queueName,\n batchSize: batchCount,\n errorType: handlerError.name,\n error: handlerError.message,\n });\n\n // Record telemetry failure\n const durationMs = Date.now() - startTime;\n endSpanError(span, handlerError);\n recordConsumeMetric(this.telemetry, queueName, String(consumerName), false, durationMs);\n\n // Handle error for each message in the batch\n return this.handleBatchError(handlerError, currentBatch, String(consumerName), consumer);\n })\n .tap(() => {\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().toPromise();\n }, batchTimeout);\n\n this.batchTimers.set(timerKey, timer);\n };\n\n // Start consuming\n return Future.fromPromise(\n this.amqpClient.channel.consume(queueName, 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,\n });\n // Process any remaining messages in the batch\n await processBatch().toPromise();\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().toPromise();\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 /**\n * Handle error in message processing with retry logic.\n *\n * Flow:\n * 1. If NonRetryableError -> send directly to DLQ (no retry)\n * 2. If no retry config -> legacy behavior (immediate requeue)\n * 3. If max retries exceeded -> send to DLQ\n * 4. Otherwise -> publish to wait queue with TTL for retry\n */\n private handleError(\n error: Error,\n msg: Message,\n consumerName: string,\n consumer: ConsumerDefinition,\n ): Future<Result<void, TechnicalError>> {\n // NonRetryableError -> send directly to DLQ without retrying\n if (error instanceof NonRetryableError) {\n this.logger?.error(\"Non-retryable error, sending to DLQ immediately\", {\n consumerName,\n errorType: error.name,\n error: error.message,\n });\n this.sendToDLQ(msg, consumer);\n return Future.value(Result.Ok(undefined));\n }\n\n // If no retry config, use legacy behavior\n if (this.retryConfig === null) {\n this.logger?.warn(\"Error in handler (legacy mode: immediate requeue)\", {\n consumerName,\n error: error.message,\n });\n this.amqpClient.channel.nack(msg, false, true); // Requeue immediately\n return Future.value(Result.Ok(undefined));\n }\n\n // Get retry count from headers\n const retryCount = (msg.properties.headers?.[\"x-retry-count\"] as number) ?? 0;\n\n // Max retries exceeded -> DLQ\n // retryConfig is guaranteed to be non-null at this point\n const config = this.retryConfig as ResolvedRetryConfig;\n if (retryCount >= config.maxRetries) {\n this.logger?.error(\"Max retries exceeded, sending to DLQ\", {\n consumerName,\n retryCount,\n maxRetries: config.maxRetries,\n error: error.message,\n });\n this.sendToDLQ(msg, consumer);\n return Future.value(Result.Ok(undefined));\n }\n\n // Retry with exponential backoff\n const delayMs = this.calculateRetryDelay(retryCount);\n this.logger?.warn(\"Retrying message\", {\n consumerName,\n retryCount: retryCount + 1,\n delayMs,\n error: error.message,\n });\n\n return this.publishForRetry(msg, consumer, retryCount + 1, delayMs, error);\n }\n\n /**\n * Calculate retry delay with exponential backoff and optional jitter.\n */\n private calculateRetryDelay(retryCount: number): number {\n // retryConfig is guaranteed to be non-null when this method is called\n const config = this.retryConfig as ResolvedRetryConfig;\n const { initialDelayMs, maxDelayMs, backoffMultiplier, jitter } = config;\n\n let delay = Math.min(initialDelayMs * Math.pow(backoffMultiplier, retryCount), maxDelayMs);\n\n if (jitter) {\n // Add jitter: random value between 50% and 100% of calculated delay\n delay = delay * (0.5 + Math.random() * 0.5);\n }\n\n return Math.floor(delay);\n }\n\n /**\n * Parse message content for republishing.\n * Prevents double JSON serialization by converting Buffer to object when possible.\n */\n private parseMessageContentForRetry(msg: Message, queueName: string): Buffer | unknown {\n let content: Buffer | unknown = msg.content;\n\n // If message is not compressed (no contentEncoding), parse it to get the original object\n if (!msg.properties.contentEncoding) {\n try {\n content = JSON.parse(msg.content.toString());\n } catch (err) {\n this.logger?.warn(\"Failed to parse message for retry, using original buffer\", {\n queueName,\n error: err,\n });\n }\n }\n\n return content;\n }\n\n /**\n * Publish message to wait queue for retry after TTL expires.\n *\n * ┌─────────────────────────────────────────────────────────────────┐\n * │ Retry Flow (Native RabbitMQ TTL + DLX Pattern) │\n * ├─────────────────────────────────────────────────────────────────┤\n * │ │\n * │ 1. Handler throws any Error │\n * │ ↓ │\n * │ 2. Worker publishes to DLX with routing key: {queue}-wait │\n * │ ↓ │\n * │ 3. DLX routes to wait queue: {queue}-wait │\n * │ (with expiration: calculated backoff delay) │\n * │ ↓ │\n * │ 4. Message waits in queue until TTL expires │\n * │ ↓ │\n * │ 5. Expired message dead-lettered to DLX │\n * │ (with routing key: {queue}) │\n * │ ↓ │\n * │ 6. DLX routes back to main queue → RETRY │\n * │ ↓ │\n * │ 7. If retries exhausted: nack without requeue → DLQ │\n * │ │\n * └─────────────────────────────────────────────────────────────────┘\n */\n private publishForRetry(\n msg: Message,\n consumer: ConsumerDefinition,\n newRetryCount: number,\n delayMs: number,\n error: Error,\n ): Future<Result<void, TechnicalError>> {\n const queueName = consumer.queue.name;\n const deadLetter = consumer.queue.deadLetter;\n\n if (!deadLetter) {\n this.logger?.warn(\n \"Cannot retry: queue does not have DLX configured, falling back to nack with requeue\",\n {\n queueName,\n },\n );\n this.amqpClient.channel.nack(msg, false, true);\n return Future.value(Result.Ok(undefined));\n }\n\n const dlxName = deadLetter.exchange.name;\n const waitRoutingKey = `${queueName}-wait`;\n\n // Acknowledge original message\n this.amqpClient.channel.ack(msg);\n\n const content = this.parseMessageContentForRetry(msg, queueName);\n\n // Publish to DLX with wait routing key\n return Future.fromPromise(\n this.amqpClient.channel.publish(dlxName, waitRoutingKey, content, {\n ...msg.properties,\n expiration: delayMs.toString(), // Per-message TTL\n headers: {\n ...msg.properties.headers,\n \"x-retry-count\": newRetryCount,\n \"x-last-error\": error.message,\n \"x-first-failure-timestamp\":\n msg.properties.headers?.[\"x-first-failure-timestamp\"] ?? Date.now(),\n },\n }),\n )\n .mapError((error) => new TechnicalError(\"Failed to publish message for retry\", error))\n .mapOkToResult((published) => {\n if (!published) {\n this.logger?.error(\"Failed to publish message for retry (write buffer full)\", {\n queueName,\n waitRoutingKey,\n retryCount: newRetryCount,\n });\n return Result.Error(\n new TechnicalError(\"Failed to publish message for retry (write buffer full)\"),\n );\n }\n\n this.logger?.info(\"Message published for retry\", {\n queueName,\n waitRoutingKey,\n retryCount: newRetryCount,\n delayMs,\n });\n return Result.Ok(undefined);\n });\n }\n\n /**\n * Send message to dead letter queue.\n * Nacks the message without requeue, relying on DLX configuration.\n */\n private sendToDLQ(msg: Message, consumer: ConsumerDefinition): void {\n const queueName = consumer.queue.name;\n const hasDeadLetter = consumer.queue.deadLetter !== undefined;\n\n if (!hasDeadLetter) {\n this.logger?.warn(\"Queue does not have DLX configured - message will be lost on nack\", {\n queueName,\n });\n }\n\n this.logger?.info(\"Sending message to DLQ\", {\n queueName,\n deliveryTag: msg.fields.deliveryTag,\n });\n\n // Nack without requeue - relies on DLX configuration\n this.amqpClient.channel.nack(msg, false, false);\n }\n}\n","import type { ContractDefinition, InferConsumerNames } from \"@amqp-contract/contract\";\nimport { Future, Result } from \"@swan-io/boxed\";\nimport { NonRetryableError, RetryableError } from \"./errors.js\";\nimport type {\n WorkerInferConsumerInput,\n WorkerInferSafeConsumerBatchHandler,\n WorkerInferSafeConsumerHandler,\n WorkerInferSafeConsumerHandlerEntry,\n WorkerInferSafeConsumerHandlers,\n} from \"./types.js\";\nimport type { HandlerError } from \"./errors.js\";\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Validate that a consumer exists in the contract\n */\nfunction validateConsumerExists<TContract extends ContractDefinition>(\n contract: TContract,\n consumerName: string,\n): void {\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 \"${consumerName}\" not found in contract. Available consumers: ${available}`,\n );\n }\n}\n\n/**\n * Validate that all handlers reference valid consumers\n */\nfunction validateHandlers<TContract extends ContractDefinition>(\n contract: TContract,\n handlers: Record<string, unknown>,\n): void {\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\n/**\n * Wrap a Promise-based handler into a Future-based safe handler.\n * This is used internally by defineUnsafeHandler to convert Promise handlers to Future handlers.\n */\nfunction wrapUnsafeHandler<TInput>(\n handler: (input: TInput) => Promise<void>,\n): (input: TInput) => Future<Result<void, HandlerError>> {\n return (input: TInput): Future<Result<void, HandlerError>> => {\n return Future.fromPromise(handler(input))\n .mapOkToResult(() => Result.Ok<void, HandlerError>(undefined))\n .flatMapError((error) => {\n // Check if error is already a HandlerError type\n if (error instanceof NonRetryableError || error instanceof RetryableError) {\n return Future.value(Result.Error<void, HandlerError>(error));\n }\n // Wrap other errors as RetryableError\n const retryableError = new RetryableError(\n error instanceof Error ? error.message : String(error),\n error,\n );\n return Future.value(Result.Error<void, HandlerError>(retryableError));\n });\n };\n}\n\n// =============================================================================\n// Safe Handler Definitions (Recommended)\n// =============================================================================\n\n/**\n * Define a type-safe handler for a specific consumer in a contract.\n *\n * **Recommended:** This function creates handlers that return `Future<Result<void, HandlerError>>`,\n * providing explicit error handling and better control over retry behavior.\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 * @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 handler function that returns `Future<Result<void, HandlerError>>`\n * @param options - Optional consumer options (prefetch, batchSize, batchTimeout)\n * @returns A type-safe handler that can be used with TypedAmqpWorker\n *\n * @example\n * ```typescript\n * import { defineHandler, RetryableError, NonRetryableError } from '@amqp-contract/worker';\n * import { Future, Result } from '@swan-io/boxed';\n * import { orderContract } from './contract';\n *\n * // Simple handler with explicit error handling using mapError\n * const processOrderHandler = defineHandler(\n * orderContract,\n * 'processOrder',\n * (message) =>\n * Future.fromPromise(processPayment(message))\n * .mapOk(() => undefined)\n * .mapError((error) => new RetryableError('Payment failed', error))\n * );\n *\n * // Handler with validation (non-retryable error)\n * const validateOrderHandler = defineHandler(\n * orderContract,\n * 'validateOrder',\n * (message) => {\n * if (message.amount < 1) {\n * // Won't be retried - goes directly to DLQ\n * return Future.value(Result.Error(new NonRetryableError('Invalid order amount')));\n * }\n * return Future.value(Result.Ok(undefined));\n * }\n * );\n * ```\n */\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: WorkerInferSafeConsumerHandler<TContract, TName>,\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: WorkerInferSafeConsumerHandler<TContract, TName>,\n options: { prefetch?: number; batchSize?: never; batchTimeout?: never },\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: WorkerInferSafeConsumerBatchHandler<TContract, TName>,\n options: { prefetch?: number; batchSize: number; batchTimeout?: number },\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler:\n | WorkerInferSafeConsumerHandler<TContract, TName>\n | WorkerInferSafeConsumerBatchHandler<TContract, TName>,\n options?: { prefetch?: number; batchSize?: number; batchTimeout?: number },\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName> {\n validateConsumerExists(contract, String(consumerName));\n\n if (options) {\n return [handler, options] as WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\n }\n return handler as WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\n}\n\n/**\n * Define multiple type-safe handlers for consumers in a contract.\n *\n * **Recommended:** This function creates handlers that return `Future<Result<void, HandlerError>>`,\n * providing explicit error handling and better control over retry behavior.\n *\n * @template TContract - The contract definition type\n * @param contract - The contract definition containing the consumers\n * @param handlers - An object with 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, RetryableError } from '@amqp-contract/worker';\n * import { Future } from '@swan-io/boxed';\n * import { orderContract } from './contract';\n *\n * const handlers = defineHandlers(orderContract, {\n * processOrder: (message) =>\n * Future.fromPromise(processPayment(message))\n * .mapOk(() => undefined)\n * .mapError((error) => new RetryableError('Payment failed', error)),\n * notifyOrder: (message) =>\n * Future.fromPromise(sendNotification(message))\n * .mapOk(() => undefined)\n * .mapError((error) => new RetryableError('Notification failed', error)),\n * });\n * ```\n */\nexport function defineHandlers<TContract extends ContractDefinition>(\n contract: TContract,\n handlers: WorkerInferSafeConsumerHandlers<TContract>,\n): WorkerInferSafeConsumerHandlers<TContract> {\n validateHandlers(contract, handlers as unknown as Record<string, unknown>);\n return handlers;\n}\n\n// =============================================================================\n// Unsafe Handler Definitions (Legacy)\n// =============================================================================\n\n/**\n * Unsafe handler type for single messages (internal use).\n */\ntype UnsafeHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n> = (message: WorkerInferConsumerInput<TContract, TName>) => Promise<void>;\n\n/**\n * Unsafe handler type for batch messages (internal use).\n */\ntype UnsafeBatchHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n> = (messages: Array<WorkerInferConsumerInput<TContract, TName>>) => Promise<void>;\n\n/**\n * Define an unsafe handler for a specific consumer in a contract.\n *\n * @deprecated Use `defineHandler` instead for explicit error handling with `Future<Result>`.\n *\n * **Warning:** Unsafe handlers use exception-based error handling:\n * - All thrown errors are treated as retryable by default\n * - Harder to reason about which errors should be retried\n * - May lead to unexpected retry behavior\n *\n * **Note:** Internally, this function wraps the Promise-based handler into a Future-based\n * safe handler for consistent processing in the worker.\n *\n * @template TContract - The contract definition type\n * @template TName - The consumer name from the contract\n * @param contract - The contract definition containing the consumer\n * @param consumerName - The name of the consumer from the contract\n * @param handler - The async handler function that processes messages\n * @param options - Optional consumer options (prefetch, batchSize, batchTimeout)\n * @returns A type-safe handler that can be used with TypedAmqpWorker\n *\n * @example\n * ```typescript\n * import { defineUnsafeHandler } from '@amqp-contract/worker';\n *\n * // ⚠️ Consider using defineHandler for better error handling\n * const processOrderHandler = defineUnsafeHandler(\n * orderContract,\n * 'processOrder',\n * async (message) => {\n * // Throws on error - will be retried\n * await processPayment(message);\n * }\n * );\n * ```\n */\nexport function defineUnsafeHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: UnsafeHandler<TContract, TName>,\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\nexport function defineUnsafeHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: UnsafeHandler<TContract, TName>,\n options: { prefetch?: number; batchSize?: never; batchTimeout?: never },\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\nexport function defineUnsafeHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: UnsafeBatchHandler<TContract, TName>,\n options: { prefetch?: number; batchSize: number; batchTimeout?: number },\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\nexport function defineUnsafeHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: UnsafeHandler<TContract, TName> | UnsafeBatchHandler<TContract, TName>,\n options?: { prefetch?: number; batchSize?: number; batchTimeout?: number },\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName> {\n validateConsumerExists(contract, String(consumerName));\n\n // Wrap the Promise-based handler into a Future-based handler\n const wrappedHandler = wrapUnsafeHandler(handler as (input: unknown) => Promise<void>);\n\n if (options) {\n return [wrappedHandler, options] as WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\n }\n return wrappedHandler as WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\n}\n\n/**\n * Unsafe handler entry type for internal use.\n */\ntype UnsafeHandlerEntry<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n> =\n | UnsafeHandler<TContract, TName>\n | readonly [\n UnsafeHandler<TContract, TName>,\n { prefetch?: number; batchSize?: never; batchTimeout?: never },\n ]\n | readonly [\n UnsafeBatchHandler<TContract, TName>,\n { prefetch?: number; batchSize: number; batchTimeout?: number },\n ];\n\n/**\n * Unsafe handlers object type for internal use.\n */\ntype UnsafeHandlers<TContract extends ContractDefinition> = {\n [K in InferConsumerNames<TContract>]: UnsafeHandlerEntry<TContract, K>;\n};\n\n/**\n * Define multiple unsafe handlers for consumers in a contract.\n *\n * @deprecated Use `defineHandlers` instead for explicit error handling with `Future<Result>`.\n *\n * **Warning:** Unsafe handlers use exception-based error handling.\n * Consider migrating to safe handlers for better error control.\n *\n * **Note:** Internally, this function wraps all Promise-based handlers into Future-based\n * safe handlers for consistent processing in the worker.\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 { defineUnsafeHandlers } from '@amqp-contract/worker';\n *\n * // ⚠️ Consider using defineHandlers for better error handling\n * const handlers = defineUnsafeHandlers(orderContract, {\n * processOrder: async (message) => {\n * await processPayment(message);\n * },\n * notifyOrder: async (message) => {\n * await sendNotification(message);\n * },\n * });\n * ```\n */\nexport function defineUnsafeHandlers<TContract extends ContractDefinition>(\n contract: TContract,\n handlers: UnsafeHandlers<TContract>,\n): WorkerInferSafeConsumerHandlers<TContract> {\n validateHandlers(contract, handlers as unknown as Record<string, unknown>);\n\n // Transform all handlers\n const result: Record<string, unknown> = {};\n for (const [name, entry] of Object.entries(handlers)) {\n if (Array.isArray(entry)) {\n // Tuple format: [handler, options]\n const [handler, options] = entry as [\n (input: unknown) => Promise<void>,\n { prefetch?: number; batchSize?: number; batchTimeout?: number },\n ];\n result[name] = [wrapUnsafeHandler(handler), options];\n } else {\n // Direct function format\n result[name] = wrapUnsafeHandler(entry as (input: unknown) => Promise<void>);\n }\n }\n\n return result as WorkerInferSafeConsumerHandlers<TContract>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["error"],"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\n/**\n * Retryable errors - transient failures that may succeed on retry\n * Examples: network timeouts, rate limiting, temporary service unavailability\n *\n * Use this error type when the operation might succeed if retried.\n * The worker will apply exponential backoff and retry the message.\n */\nexport class RetryableError extends WorkerError {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"RetryableError\";\n }\n}\n\n/**\n * Non-retryable errors - permanent failures that should not be retried\n * Examples: invalid data, business rule violations, permanent external failures\n *\n * Use this error type when retrying would not help - the message will be\n * immediately sent to the dead letter queue (DLQ) if configured.\n */\nexport class NonRetryableError extends WorkerError {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"NonRetryableError\";\n }\n}\n\n/**\n * Union type representing all handler errors.\n * Use this type when defining handlers that explicitly signal error outcomes.\n */\nexport type HandlerError = RetryableError | NonRetryableError;\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 {\n AmqpClient,\n type Logger,\n type TelemetryProvider,\n defaultTelemetryProvider,\n endSpanError,\n endSpanSuccess,\n recordConsumeMetric,\n startConsumeSpan,\n} 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, NonRetryableError, TechnicalError } from \"./errors.js\";\nimport type {\n WorkerInferConsumerInput,\n WorkerInferSafeConsumerBatchHandler,\n WorkerInferSafeConsumerHandler,\n WorkerInferSafeConsumerHandlers,\n} from \"./types.js\";\nimport type { HandlerError } from \"./errors.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 * Retry configuration options for handling failed message processing.\n *\n * When enabled, the worker will automatically retry failed messages using\n * RabbitMQ's native TTL + Dead Letter Exchange (DLX) pattern.\n */\nexport type RetryOptions = {\n /** Maximum retry attempts before sending to DLQ (default: 3) */\n maxRetries?: number;\n /** Initial delay in ms before first retry (default: 1000) */\n initialDelayMs?: number;\n /** Maximum delay in ms between retries (default: 30000) */\n maxDelayMs?: number;\n /** Exponential backoff multiplier (default: 2) */\n backoffMultiplier?: number;\n /** Add jitter to prevent thundering herd (default: true) */\n jitter?: boolean;\n};\n\n/**\n * Internal retry configuration with all values resolved.\n */\ntype ResolvedRetryConfig = {\n maxRetries: number;\n initialDelayMs: number;\n maxDelayMs: number;\n backoffMultiplier: number;\n jitter: boolean;\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 * retry: {\n * maxRetries: 3,\n * initialDelayMs: 1000,\n * maxDelayMs: 30000,\n * backoffMultiplier: 2,\n * jitter: true\n * }\n * };\n * ```\n */\nexport type CreateWorkerOptions<TContract extends ContractDefinition> = {\n /** The AMQP contract definition specifying consumers and their message schemas */\n contract: TContract;\n /**\n * Handlers for each consumer defined in the contract.\n * Handlers must return `Future<Result<void, HandlerError>>` for explicit error handling.\n * Use defineHandler() to create safe handlers, or defineUnsafeHandler() which wraps\n * Promise-based handlers into safe handlers internally.\n */\n handlers: WorkerInferSafeConsumerHandlers<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 /** Retry configuration - when undefined, uses legacy behavior (immediate requeue) */\n retry?: RetryOptions | undefined;\n /**\n * Optional telemetry provider for tracing and metrics.\n * If not provided, uses the default provider which attempts to load OpenTelemetry.\n * OpenTelemetry instrumentation is automatically enabled if @opentelemetry/api is installed.\n */\n telemetry?: TelemetryProvider | 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 /**\n * Internal handler type - always safe handlers (`Future<Result>`).\n * Unsafe handlers are wrapped into safe handlers by defineUnsafeHandler/defineUnsafeHandlers.\n */\n private readonly actualHandlers: Partial<\n Record<\n InferConsumerNames<TContract>,\n | WorkerInferSafeConsumerHandler<TContract, InferConsumerNames<TContract>>\n | WorkerInferSafeConsumerBatchHandler<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 private readonly retryConfig: ResolvedRetryConfig | null;\n private readonly telemetry: TelemetryProvider;\n\n private constructor(\n private readonly contract: TContract,\n private readonly amqpClient: AmqpClient,\n handlers: WorkerInferSafeConsumerHandlers<TContract>,\n private readonly logger?: Logger,\n retryOptions?: RetryOptions,\n telemetry?: TelemetryProvider,\n ) {\n this.telemetry = telemetry ?? defaultTelemetryProvider;\n\n // Extract handlers and options from the handlers object\n this.actualHandlers = {};\n this.consumerOptions = {};\n\n // Cast handlers to a generic record for iteration\n const handlersRecord = handlers as Record<string, unknown>;\n\n for (const consumerName of Object.keys(handlersRecord)) {\n const handlerEntry = handlersRecord[consumerName];\n const typedConsumerName = consumerName as InferConsumerNames<TContract>;\n\n if (Array.isArray(handlerEntry)) {\n // Tuple format: [handler, options]\n this.actualHandlers[typedConsumerName] = handlerEntry[0] as\n | WorkerInferSafeConsumerHandler<TContract, InferConsumerNames<TContract>>\n | WorkerInferSafeConsumerBatchHandler<TContract, InferConsumerNames<TContract>>;\n this.consumerOptions[typedConsumerName] = handlerEntry[1] as ConsumerOptions;\n } else {\n // Direct function format\n this.actualHandlers[typedConsumerName] = handlerEntry as\n | WorkerInferSafeConsumerHandler<TContract, InferConsumerNames<TContract>>\n | WorkerInferSafeConsumerBatchHandler<TContract, InferConsumerNames<TContract>>;\n }\n }\n\n // Initialize retry configuration\n if (retryOptions === undefined) {\n this.retryConfig = null; // Legacy behavior\n } else {\n // Set defaults for retry options\n this.retryConfig = {\n maxRetries: retryOptions.maxRetries ?? 3,\n initialDelayMs: retryOptions.initialDelayMs ?? 1000,\n maxDelayMs: retryOptions.maxDelayMs ?? 30000,\n backoffMultiplier: retryOptions.backoffMultiplier ?? 2,\n jitter: retryOptions.jitter ?? true,\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 worker = 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 */\n static create<TContract extends ContractDefinition>({\n contract,\n handlers,\n urls,\n connectionOptions,\n logger,\n retry,\n telemetry,\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 retry,\n telemetry,\n );\n\n return worker\n .waitForConnectionReady()\n .flatMapOk(() => worker.setupWaitQueues())\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 * Set up wait queues for retry mechanism.\n * Creates and binds wait queues for each consumer queue that has DLX configuration.\n */\n private setupWaitQueues(): Future<Result<void, TechnicalError>> {\n // Skip if retry is not configured\n if (this.retryConfig === null) {\n return Future.value(Result.Ok(undefined));\n }\n\n if (!this.contract.consumers || !this.contract.queues) {\n return Future.value(Result.Ok(undefined));\n }\n\n const setupTasks: Array<Future<Result<void, TechnicalError>>> = [];\n\n for (const consumerName of Object.keys(\n this.contract.consumers,\n ) as InferConsumerNames<TContract>[]) {\n const consumer = this.contract.consumers[consumerName as string];\n if (!consumer) continue;\n\n const queue = consumer.queue;\n const deadLetter = queue.deadLetter;\n\n // Only create wait queues for queues with DLX configuration\n if (!deadLetter) continue;\n\n const queueName = queue.name;\n const waitQueueName = `${queueName}-wait`;\n const dlxName = deadLetter.exchange.name;\n\n const setupTask = Future.fromPromise(\n this.amqpClient.channel.addSetup(async (channel: Channel) => {\n // Create wait queue with DLX pointing back to the main queue\n await channel.assertQueue(waitQueueName, {\n durable: queue.durable ?? false,\n deadLetterExchange: dlxName,\n deadLetterRoutingKey: queueName,\n });\n\n // Bind wait queue to DLX with routing key pattern\n await channel.bindQueue(waitQueueName, dlxName, `${queueName}-wait`);\n\n // Bind main queue to DLX for routing retried messages back\n await channel.bindQueue(queueName, dlxName, queueName);\n\n this.logger?.info(\"Wait queue created and bound\", {\n consumerName: String(consumerName),\n queueName,\n waitQueueName,\n dlxName,\n });\n }),\n ).mapError(\n (error) =>\n new TechnicalError(`Failed to setup wait queue for \"${String(consumerName)}\"`, error),\n );\n\n setupTasks.push(setupTask);\n }\n\n if (setupTasks.length === 0) {\n return Future.value(Result.Ok(undefined));\n }\n\n return Future.all(setupTasks)\n .map(Result.all)\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 // All handlers are now safe handlers (Future<Result>)\n handler as (\n messages: Array<WorkerInferConsumerInput<TContract, TName>>,\n ) => Future<Result<void, HandlerError>>,\n );\n } else {\n return this.consumeSingle(\n consumerName,\n consumer,\n // All handlers are now safe handlers (Future<Result>)\n handler as (\n message: WorkerInferConsumerInput<TContract, TName>,\n ) => Future<Result<void, HandlerError>>,\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 ).tapError((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 });\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: (\n message: WorkerInferConsumerInput<TContract, TName>,\n ) => Future<Result<void, HandlerError>>,\n ): Future<Result<void, TechnicalError>> {\n const queueName = consumer.queue.name;\n\n // Start consuming\n return Future.fromPromise(\n this.amqpClient.channel.consume(queueName, 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,\n });\n return;\n }\n\n const startTime = Date.now();\n const span = startConsumeSpan(this.telemetry, queueName, String(consumerName), {\n \"messaging.rabbitmq.message.delivery_tag\": msg.fields.deliveryTag,\n });\n\n // Parse and validate message\n await this.parseAndValidateMessage(msg, consumer, consumerName)\n .flatMapOk((validatedMessage) =>\n handler(validatedMessage)\n .flatMapOk(() => {\n this.logger?.info(\"Message consumed successfully\", {\n consumerName: String(consumerName),\n queueName,\n });\n // Acknowledge message on success\n this.amqpClient.channel.ack(msg);\n\n // Record telemetry success\n const durationMs = Date.now() - startTime;\n endSpanSuccess(span);\n recordConsumeMetric(\n this.telemetry,\n queueName,\n String(consumerName),\n true,\n durationMs,\n );\n\n return Future.value(Result.Ok<void, HandlerError>(undefined));\n })\n .flatMapError((handlerError: HandlerError) => {\n // Handler returned an error\n this.logger?.error(\"Error processing message\", {\n consumerName: String(consumerName),\n queueName,\n errorType: handlerError.name,\n error: handlerError.message,\n });\n\n // Record telemetry failure\n const durationMs = Date.now() - startTime;\n endSpanError(span, handlerError);\n recordConsumeMetric(\n this.telemetry,\n queueName,\n String(consumerName),\n false,\n durationMs,\n );\n\n // Handle the error using retry mechanism\n return this.handleError(handlerError, msg, String(consumerName), consumer);\n }),\n )\n .tapError(() => {\n // Record telemetry failure for validation errors\n // Note: The actual validation error is logged in parseAndValidateMessage,\n // here we just record that validation failed for telemetry purposes\n const durationMs = Date.now() - startTime;\n endSpanError(span, new Error(\"Message validation failed\"));\n recordConsumeMetric(this.telemetry, queueName, String(consumerName), false, durationMs);\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 * Handle batch processing error by applying error handling to all messages.\n */\n private handleBatchError(\n error: HandlerError,\n currentBatch: Array<{ amqpMessage: Message }>,\n consumerName: string,\n consumer: ConsumerDefinition,\n ): Future<Result<void, TechnicalError>> {\n return Future.all(\n currentBatch.map((item) => this.handleError(error, item.amqpMessage, consumerName, consumer)),\n )\n .map(Result.all)\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: (\n messages: Array<WorkerInferConsumerInput<TContract, TName>>,\n ) => Future<Result<void, HandlerError>>,\n ): Future<Result<void, TechnicalError>> {\n const batchSize = options.batchSize!;\n const batchTimeout = options.batchTimeout ?? 1000;\n const timerKey = String(consumerName);\n const queueName = consumer.queue.name;\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 = (): Future<Result<void, TechnicalError>> => {\n // Prevent concurrent batch processing\n if (isProcessing || batch.length === 0) {\n return Future.value(Result.Ok(undefined));\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 const batchCount = currentBatch.length;\n\n // Start telemetry span for batch processing\n const startTime = Date.now();\n const span = startConsumeSpan(this.telemetry, queueName, String(consumerName), {\n \"amqp.batch.size\": batchCount,\n });\n\n this.logger?.info(\"Processing batch\", {\n consumerName: String(consumerName),\n queueName,\n batchSize: batchCount,\n });\n\n return handler(messages)\n .flatMapOk(() => {\n // Acknowledge all messages in the batch on success\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,\n batchSize: batchCount,\n });\n\n // Record telemetry success\n const durationMs = Date.now() - startTime;\n endSpanSuccess(span);\n recordConsumeMetric(this.telemetry, queueName, String(consumerName), true, durationMs);\n\n return Future.value(Result.Ok<void, TechnicalError>(undefined));\n })\n .flatMapError((handlerError: HandlerError) => {\n // Handler returned an error - log it\n this.logger?.error(\"Error processing batch\", {\n consumerName: String(consumerName),\n queueName,\n batchSize: batchCount,\n errorType: handlerError.name,\n error: handlerError.message,\n });\n\n // Record telemetry failure\n const durationMs = Date.now() - startTime;\n endSpanError(span, handlerError);\n recordConsumeMetric(this.telemetry, queueName, String(consumerName), false, durationMs);\n\n // Handle error for each message in the batch\n return this.handleBatchError(handlerError, currentBatch, String(consumerName), consumer);\n })\n .tap(() => {\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().toPromise();\n }, batchTimeout);\n\n this.batchTimers.set(timerKey, timer);\n };\n\n // Start consuming\n return Future.fromPromise(\n this.amqpClient.channel.consume(queueName, 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,\n });\n // Process any remaining messages in the batch\n await processBatch().toPromise();\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().toPromise();\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 /**\n * Handle error in message processing with retry logic.\n *\n * Flow:\n * 1. If NonRetryableError -> send directly to DLQ (no retry)\n * 2. If no retry config -> legacy behavior (immediate requeue)\n * 3. If max retries exceeded -> send to DLQ\n * 4. Otherwise -> publish to wait queue with TTL for retry\n */\n private handleError(\n error: Error,\n msg: Message,\n consumerName: string,\n consumer: ConsumerDefinition,\n ): Future<Result<void, TechnicalError>> {\n // NonRetryableError -> send directly to DLQ without retrying\n if (error instanceof NonRetryableError) {\n this.logger?.error(\"Non-retryable error, sending to DLQ immediately\", {\n consumerName,\n errorType: error.name,\n error: error.message,\n });\n this.sendToDLQ(msg, consumer);\n return Future.value(Result.Ok(undefined));\n }\n\n // If no retry config, use legacy behavior\n if (this.retryConfig === null) {\n this.logger?.warn(\"Error in handler (legacy mode: immediate requeue)\", {\n consumerName,\n error: error.message,\n });\n this.amqpClient.channel.nack(msg, false, true); // Requeue immediately\n return Future.value(Result.Ok(undefined));\n }\n\n // Get retry count from headers\n const retryCount = (msg.properties.headers?.[\"x-retry-count\"] as number) ?? 0;\n\n // Max retries exceeded -> DLQ\n // retryConfig is guaranteed to be non-null at this point\n const config = this.retryConfig as ResolvedRetryConfig;\n if (retryCount >= config.maxRetries) {\n this.logger?.error(\"Max retries exceeded, sending to DLQ\", {\n consumerName,\n retryCount,\n maxRetries: config.maxRetries,\n error: error.message,\n });\n this.sendToDLQ(msg, consumer);\n return Future.value(Result.Ok(undefined));\n }\n\n // Retry with exponential backoff\n const delayMs = this.calculateRetryDelay(retryCount);\n this.logger?.warn(\"Retrying message\", {\n consumerName,\n retryCount: retryCount + 1,\n delayMs,\n error: error.message,\n });\n\n return this.publishForRetry(msg, consumer, retryCount + 1, delayMs, error);\n }\n\n /**\n * Calculate retry delay with exponential backoff and optional jitter.\n */\n private calculateRetryDelay(retryCount: number): number {\n // retryConfig is guaranteed to be non-null when this method is called\n const config = this.retryConfig as ResolvedRetryConfig;\n const { initialDelayMs, maxDelayMs, backoffMultiplier, jitter } = config;\n\n let delay = Math.min(initialDelayMs * Math.pow(backoffMultiplier, retryCount), maxDelayMs);\n\n if (jitter) {\n // Add jitter: random value between 50% and 100% of calculated delay\n delay = delay * (0.5 + Math.random() * 0.5);\n }\n\n return Math.floor(delay);\n }\n\n /**\n * Parse message content for republishing.\n * Prevents double JSON serialization by converting Buffer to object when possible.\n */\n private parseMessageContentForRetry(msg: Message, queueName: string): Buffer | unknown {\n let content: Buffer | unknown = msg.content;\n\n // If message is not compressed (no contentEncoding), parse it to get the original object\n if (!msg.properties.contentEncoding) {\n try {\n content = JSON.parse(msg.content.toString());\n } catch (err) {\n this.logger?.warn(\"Failed to parse message for retry, using original buffer\", {\n queueName,\n error: err,\n });\n }\n }\n\n return content;\n }\n\n /**\n * Publish message to wait queue for retry after TTL expires.\n *\n * ┌─────────────────────────────────────────────────────────────────┐\n * │ Retry Flow (Native RabbitMQ TTL + DLX Pattern) │\n * ├─────────────────────────────────────────────────────────────────┤\n * │ │\n * │ 1. Handler throws any Error │\n * │ ↓ │\n * │ 2. Worker publishes to DLX with routing key: {queue}-wait │\n * │ ↓ │\n * │ 3. DLX routes to wait queue: {queue}-wait │\n * │ (with expiration: calculated backoff delay) │\n * │ ↓ │\n * │ 4. Message waits in queue until TTL expires │\n * │ ↓ │\n * │ 5. Expired message dead-lettered to DLX │\n * │ (with routing key: {queue}) │\n * │ ↓ │\n * │ 6. DLX routes back to main queue → RETRY │\n * │ ↓ │\n * │ 7. If retries exhausted: nack without requeue → DLQ │\n * │ │\n * └─────────────────────────────────────────────────────────────────┘\n */\n private publishForRetry(\n msg: Message,\n consumer: ConsumerDefinition,\n newRetryCount: number,\n delayMs: number,\n error: Error,\n ): Future<Result<void, TechnicalError>> {\n const queueName = consumer.queue.name;\n const deadLetter = consumer.queue.deadLetter;\n\n if (!deadLetter) {\n this.logger?.warn(\n \"Cannot retry: queue does not have DLX configured, falling back to nack with requeue\",\n {\n queueName,\n },\n );\n this.amqpClient.channel.nack(msg, false, true);\n return Future.value(Result.Ok(undefined));\n }\n\n const dlxName = deadLetter.exchange.name;\n const waitRoutingKey = `${queueName}-wait`;\n\n // Acknowledge original message\n this.amqpClient.channel.ack(msg);\n\n const content = this.parseMessageContentForRetry(msg, queueName);\n\n // Publish to DLX with wait routing key\n return Future.fromPromise(\n this.amqpClient.channel.publish(dlxName, waitRoutingKey, content, {\n ...msg.properties,\n expiration: delayMs.toString(), // Per-message TTL\n headers: {\n ...msg.properties.headers,\n \"x-retry-count\": newRetryCount,\n \"x-last-error\": error.message,\n \"x-first-failure-timestamp\":\n msg.properties.headers?.[\"x-first-failure-timestamp\"] ?? Date.now(),\n },\n }),\n )\n .mapError((error) => new TechnicalError(\"Failed to publish message for retry\", error))\n .mapOkToResult((published) => {\n if (!published) {\n this.logger?.error(\"Failed to publish message for retry (write buffer full)\", {\n queueName,\n waitRoutingKey,\n retryCount: newRetryCount,\n });\n return Result.Error(\n new TechnicalError(\"Failed to publish message for retry (write buffer full)\"),\n );\n }\n\n this.logger?.info(\"Message published for retry\", {\n queueName,\n waitRoutingKey,\n retryCount: newRetryCount,\n delayMs,\n });\n return Result.Ok(undefined);\n });\n }\n\n /**\n * Send message to dead letter queue.\n * Nacks the message without requeue, relying on DLX configuration.\n */\n private sendToDLQ(msg: Message, consumer: ConsumerDefinition): void {\n const queueName = consumer.queue.name;\n const hasDeadLetter = consumer.queue.deadLetter !== undefined;\n\n if (!hasDeadLetter) {\n this.logger?.warn(\"Queue does not have DLX configured - message will be lost on nack\", {\n queueName,\n });\n }\n\n this.logger?.info(\"Sending message to DLQ\", {\n queueName,\n deliveryTag: msg.fields.deliveryTag,\n });\n\n // Nack without requeue - relies on DLX configuration\n this.amqpClient.channel.nack(msg, false, false);\n }\n}\n","import type { ContractDefinition, InferConsumerNames } from \"@amqp-contract/contract\";\nimport { Future, Result } from \"@swan-io/boxed\";\nimport { NonRetryableError, RetryableError } from \"./errors.js\";\nimport type {\n WorkerInferConsumerInput,\n WorkerInferSafeConsumerBatchHandler,\n WorkerInferSafeConsumerHandler,\n WorkerInferSafeConsumerHandlerEntry,\n WorkerInferSafeConsumerHandlers,\n} from \"./types.js\";\nimport type { HandlerError } from \"./errors.js\";\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Validate that a consumer exists in the contract\n */\nfunction validateConsumerExists<TContract extends ContractDefinition>(\n contract: TContract,\n consumerName: string,\n): void {\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 \"${consumerName}\" not found in contract. Available consumers: ${available}`,\n );\n }\n}\n\n/**\n * Validate that all handlers reference valid consumers\n */\nfunction validateHandlers<TContract extends ContractDefinition>(\n contract: TContract,\n handlers: Record<string, unknown>,\n): void {\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\n/**\n * Wrap a Promise-based handler into a Future-based safe handler.\n * This is used internally by defineUnsafeHandler to convert Promise handlers to Future handlers.\n */\nfunction wrapUnsafeHandler<TInput>(\n handler: (input: TInput) => Promise<void>,\n): (input: TInput) => Future<Result<void, HandlerError>> {\n return (input: TInput): Future<Result<void, HandlerError>> => {\n return Future.fromPromise(handler(input))\n .mapOkToResult(() => Result.Ok<void, HandlerError>(undefined))\n .flatMapError((error) => {\n // Check if error is already a HandlerError type\n if (error instanceof NonRetryableError || error instanceof RetryableError) {\n return Future.value(Result.Error<void, HandlerError>(error));\n }\n // Wrap other errors as RetryableError\n const retryableError = new RetryableError(\n error instanceof Error ? error.message : String(error),\n error,\n );\n return Future.value(Result.Error<void, HandlerError>(retryableError));\n });\n };\n}\n\n// =============================================================================\n// Safe Handler Definitions (Recommended)\n// =============================================================================\n\n/**\n * Define a type-safe handler for a specific consumer in a contract.\n *\n * **Recommended:** This function creates handlers that return `Future<Result<void, HandlerError>>`,\n * providing explicit error handling and better control over retry behavior.\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 * @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 handler function that returns `Future<Result<void, HandlerError>>`\n * @param options - Optional consumer options (prefetch, batchSize, batchTimeout)\n * @returns A type-safe handler that can be used with TypedAmqpWorker\n *\n * @example\n * ```typescript\n * import { defineHandler, RetryableError, NonRetryableError } from '@amqp-contract/worker';\n * import { Future, Result } from '@swan-io/boxed';\n * import { orderContract } from './contract';\n *\n * // Simple handler with explicit error handling using mapError\n * const processOrderHandler = defineHandler(\n * orderContract,\n * 'processOrder',\n * (message) =>\n * Future.fromPromise(processPayment(message))\n * .mapOk(() => undefined)\n * .mapError((error) => new RetryableError('Payment failed', error))\n * );\n *\n * // Handler with validation (non-retryable error)\n * const validateOrderHandler = defineHandler(\n * orderContract,\n * 'validateOrder',\n * (message) => {\n * if (message.amount < 1) {\n * // Won't be retried - goes directly to DLQ\n * return Future.value(Result.Error(new NonRetryableError('Invalid order amount')));\n * }\n * return Future.value(Result.Ok(undefined));\n * }\n * );\n * ```\n */\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: WorkerInferSafeConsumerHandler<TContract, TName>,\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: WorkerInferSafeConsumerHandler<TContract, TName>,\n options: { prefetch?: number; batchSize?: never; batchTimeout?: never },\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: WorkerInferSafeConsumerBatchHandler<TContract, TName>,\n options: { prefetch?: number; batchSize: number; batchTimeout?: number },\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler:\n | WorkerInferSafeConsumerHandler<TContract, TName>\n | WorkerInferSafeConsumerBatchHandler<TContract, TName>,\n options?: { prefetch?: number; batchSize?: number; batchTimeout?: number },\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName> {\n validateConsumerExists(contract, String(consumerName));\n\n if (options) {\n return [handler, options] as WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\n }\n return handler as WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\n}\n\n/**\n * Define multiple type-safe handlers for consumers in a contract.\n *\n * **Recommended:** This function creates handlers that return `Future<Result<void, HandlerError>>`,\n * providing explicit error handling and better control over retry behavior.\n *\n * @template TContract - The contract definition type\n * @param contract - The contract definition containing the consumers\n * @param handlers - An object with 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, RetryableError } from '@amqp-contract/worker';\n * import { Future } from '@swan-io/boxed';\n * import { orderContract } from './contract';\n *\n * const handlers = defineHandlers(orderContract, {\n * processOrder: (message) =>\n * Future.fromPromise(processPayment(message))\n * .mapOk(() => undefined)\n * .mapError((error) => new RetryableError('Payment failed', error)),\n * notifyOrder: (message) =>\n * Future.fromPromise(sendNotification(message))\n * .mapOk(() => undefined)\n * .mapError((error) => new RetryableError('Notification failed', error)),\n * });\n * ```\n */\nexport function defineHandlers<TContract extends ContractDefinition>(\n contract: TContract,\n handlers: WorkerInferSafeConsumerHandlers<TContract>,\n): WorkerInferSafeConsumerHandlers<TContract> {\n validateHandlers(contract, handlers as unknown as Record<string, unknown>);\n return handlers;\n}\n\n// =============================================================================\n// Unsafe Handler Definitions (Legacy)\n// =============================================================================\n\n/**\n * Unsafe handler type for single messages (internal use).\n */\ntype UnsafeHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n> = (message: WorkerInferConsumerInput<TContract, TName>) => Promise<void>;\n\n/**\n * Unsafe handler type for batch messages (internal use).\n */\ntype UnsafeBatchHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n> = (messages: Array<WorkerInferConsumerInput<TContract, TName>>) => Promise<void>;\n\n/**\n * Define an unsafe handler for a specific consumer in a contract.\n *\n * @deprecated Use `defineHandler` instead for explicit error handling with `Future<Result>`.\n *\n * **Warning:** Unsafe handlers use exception-based error handling:\n * - All thrown errors are treated as retryable by default\n * - Harder to reason about which errors should be retried\n * - May lead to unexpected retry behavior\n *\n * **Note:** Internally, this function wraps the Promise-based handler into a Future-based\n * safe handler for consistent processing in the worker.\n *\n * @template TContract - The contract definition type\n * @template TName - The consumer name from the contract\n * @param contract - The contract definition containing the consumer\n * @param consumerName - The name of the consumer from the contract\n * @param handler - The async handler function that processes messages\n * @param options - Optional consumer options (prefetch, batchSize, batchTimeout)\n * @returns A type-safe handler that can be used with TypedAmqpWorker\n *\n * @example\n * ```typescript\n * import { defineUnsafeHandler } from '@amqp-contract/worker';\n *\n * // ⚠️ Consider using defineHandler for better error handling\n * const processOrderHandler = defineUnsafeHandler(\n * orderContract,\n * 'processOrder',\n * async (message) => {\n * // Throws on error - will be retried\n * await processPayment(message);\n * }\n * );\n * ```\n */\nexport function defineUnsafeHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: UnsafeHandler<TContract, TName>,\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\nexport function defineUnsafeHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: UnsafeHandler<TContract, TName>,\n options: { prefetch?: number; batchSize?: never; batchTimeout?: never },\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\nexport function defineUnsafeHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: UnsafeBatchHandler<TContract, TName>,\n options: { prefetch?: number; batchSize: number; batchTimeout?: number },\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\nexport function defineUnsafeHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: UnsafeHandler<TContract, TName> | UnsafeBatchHandler<TContract, TName>,\n options?: { prefetch?: number; batchSize?: number; batchTimeout?: number },\n): WorkerInferSafeConsumerHandlerEntry<TContract, TName> {\n validateConsumerExists(contract, String(consumerName));\n\n // Wrap the Promise-based handler into a Future-based handler\n const wrappedHandler = wrapUnsafeHandler(handler as (input: unknown) => Promise<void>);\n\n if (options) {\n return [wrappedHandler, options] as WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\n }\n return wrappedHandler as WorkerInferSafeConsumerHandlerEntry<TContract, TName>;\n}\n\n/**\n * Unsafe handler entry type for internal use.\n */\ntype UnsafeHandlerEntry<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n> =\n | UnsafeHandler<TContract, TName>\n | readonly [\n UnsafeHandler<TContract, TName>,\n { prefetch?: number; batchSize?: never; batchTimeout?: never },\n ]\n | readonly [\n UnsafeBatchHandler<TContract, TName>,\n { prefetch?: number; batchSize: number; batchTimeout?: number },\n ];\n\n/**\n * Unsafe handlers object type for internal use.\n */\ntype UnsafeHandlers<TContract extends ContractDefinition> = {\n [K in InferConsumerNames<TContract>]: UnsafeHandlerEntry<TContract, K>;\n};\n\n/**\n * Define multiple unsafe handlers for consumers in a contract.\n *\n * @deprecated Use `defineHandlers` instead for explicit error handling with `Future<Result>`.\n *\n * **Warning:** Unsafe handlers use exception-based error handling.\n * Consider migrating to safe handlers for better error control.\n *\n * **Note:** Internally, this function wraps all Promise-based handlers into Future-based\n * safe handlers for consistent processing in the worker.\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 { defineUnsafeHandlers } from '@amqp-contract/worker';\n *\n * // ⚠️ Consider using defineHandlers for better error handling\n * const handlers = defineUnsafeHandlers(orderContract, {\n * processOrder: async (message) => {\n * await processPayment(message);\n * },\n * notifyOrder: async (message) => {\n * await sendNotification(message);\n * },\n * });\n * ```\n */\nexport function defineUnsafeHandlers<TContract extends ContractDefinition>(\n contract: TContract,\n handlers: UnsafeHandlers<TContract>,\n): WorkerInferSafeConsumerHandlers<TContract> {\n validateHandlers(contract, handlers as unknown as Record<string, unknown>);\n\n // Transform all handlers\n const result: Record<string, unknown> = {};\n for (const [name, entry] of Object.entries(handlers)) {\n if (Array.isArray(entry)) {\n // Tuple format: [handler, options]\n const [handler, options] = entry as [\n (input: unknown) => Promise<void>,\n { prefetch?: number; batchSize?: number; batchTimeout?: number },\n ];\n result[name] = [wrapUnsafeHandler(handler), options];\n } else {\n // Direct function format\n result[name] = wrapUnsafeHandler(entry as (input: unknown) => Promise<void>);\n }\n }\n\n return result as WorkerInferSafeConsumerHandlers<TContract>;\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/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/errors.ts#L35)
11
+ Defined in: [packages/worker/src/errors.ts:35](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/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/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/errors.ts#L36)
27
+ Defined in: [packages/worker/src/errors.ts:36](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/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/f058a24938d9644a82812e57d7995cb683cfd6b5/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/f058a24938d9644a82812e57d7995cb683cfd6b5/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/00468eb63a019d6161c43f07dab8b68232bbb383/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/00468eb63a019d6161c43f07dab8b68232bbb383/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
  ### NonRetryableError
161
161
 
162
- Defined in: [packages/worker/src/errors.ts:69](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/errors.ts#L69)
162
+ Defined in: [packages/worker/src/errors.ts:69](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/errors.ts#L69)
163
163
 
164
164
  Non-retryable errors - permanent failures that should not be retried
165
165
  Examples: invalid data, business rule violations, permanent external failures
@@ -179,7 +179,7 @@ immediately sent to the dead letter queue (DLQ) if configured.
179
179
  new NonRetryableError(message, cause?): NonRetryableError;
180
180
  ```
181
181
 
182
- Defined in: [packages/worker/src/errors.ts:70](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/errors.ts#L70)
182
+ Defined in: [packages/worker/src/errors.ts:70](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/errors.ts#L70)
183
183
 
184
184
  ###### Parameters
185
185
 
@@ -202,7 +202,7 @@ WorkerError.constructor
202
202
 
203
203
  | Property | Modifier | Type | Description | Inherited from | Defined in |
204
204
  | ------ | ------ | ------ | ------ | ------ | ------ |
205
- | <a id="cause-1"></a> `cause?` | `readonly` | `unknown` | - | `WorkerError.cause` | [packages/worker/src/errors.ts:72](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/errors.ts#L72) |
205
+ | <a id="cause-1"></a> `cause?` | `readonly` | `unknown` | - | `WorkerError.cause` | [packages/worker/src/errors.ts:72](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/errors.ts#L72) |
206
206
  | <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 |
207
207
  | <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 |
208
208
  | <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 |
@@ -312,7 +312,7 @@ WorkerError.prepareStackTrace
312
312
 
313
313
  ### RetryableError
314
314
 
315
- Defined in: [packages/worker/src/errors.ts:52](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/errors.ts#L52)
315
+ Defined in: [packages/worker/src/errors.ts:52](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/errors.ts#L52)
316
316
 
317
317
  Retryable errors - transient failures that may succeed on retry
318
318
  Examples: network timeouts, rate limiting, temporary service unavailability
@@ -332,7 +332,7 @@ The worker will apply exponential backoff and retry the message.
332
332
  new RetryableError(message, cause?): RetryableError;
333
333
  ```
334
334
 
335
- Defined in: [packages/worker/src/errors.ts:53](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/errors.ts#L53)
335
+ Defined in: [packages/worker/src/errors.ts:53](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/errors.ts#L53)
336
336
 
337
337
  ###### Parameters
338
338
 
@@ -355,7 +355,7 @@ WorkerError.constructor
355
355
 
356
356
  | Property | Modifier | Type | Description | Inherited from | Defined in |
357
357
  | ------ | ------ | ------ | ------ | ------ | ------ |
358
- | <a id="cause-2"></a> `cause?` | `readonly` | `unknown` | - | `WorkerError.cause` | [packages/worker/src/errors.ts:55](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/errors.ts#L55) |
358
+ | <a id="cause-2"></a> `cause?` | `readonly` | `unknown` | - | `WorkerError.cause` | [packages/worker/src/errors.ts:55](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/errors.ts#L55) |
359
359
  | <a id="message-2"></a> `message` | `public` | `string` | - | `WorkerError.message` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 |
360
360
  | <a id="name-2"></a> `name` | `public` | `string` | - | `WorkerError.name` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 |
361
361
  | <a id="stack-2"></a> `stack?` | `public` | `string` | - | `WorkerError.stack` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 |
@@ -465,7 +465,7 @@ WorkerError.prepareStackTrace
465
465
 
466
466
  ### TechnicalError
467
467
 
468
- Defined in: [packages/worker/src/errors.ts:22](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/errors.ts#L22)
468
+ Defined in: [packages/worker/src/errors.ts:22](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/errors.ts#L22)
469
469
 
470
470
  Error for technical/runtime failures in worker operations
471
471
  This includes validation failures, parsing failures, and processing failures
@@ -482,7 +482,7 @@ This includes validation failures, parsing failures, and processing failures
482
482
  new TechnicalError(message, cause?): TechnicalError;
483
483
  ```
484
484
 
485
- Defined in: [packages/worker/src/errors.ts:23](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/errors.ts#L23)
485
+ Defined in: [packages/worker/src/errors.ts:23](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/errors.ts#L23)
486
486
 
487
487
  ###### Parameters
488
488
 
@@ -505,7 +505,7 @@ WorkerError.constructor
505
505
 
506
506
  | Property | Modifier | Type | Description | Inherited from | Defined in |
507
507
  | ------ | ------ | ------ | ------ | ------ | ------ |
508
- | <a id="cause-3"></a> `cause?` | `readonly` | `unknown` | - | `WorkerError.cause` | [packages/worker/src/errors.ts:25](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/errors.ts#L25) |
508
+ | <a id="cause-3"></a> `cause?` | `readonly` | `unknown` | - | `WorkerError.cause` | [packages/worker/src/errors.ts:25](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/errors.ts#L25) |
509
509
  | <a id="message-3"></a> `message` | `public` | `string` | - | `WorkerError.message` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 |
510
510
  | <a id="name-3"></a> `name` | `public` | `string` | - | `WorkerError.name` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 |
511
511
  | <a id="stack-3"></a> `stack?` | `public` | `string` | - | `WorkerError.stack` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 |
@@ -615,7 +615,7 @@ WorkerError.prepareStackTrace
615
615
 
616
616
  ### TypedAmqpWorker
617
617
 
618
- Defined in: [packages/worker/src/worker.ts:190](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L190)
618
+ Defined in: [packages/worker/src/worker.ts:190](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L190)
619
619
 
620
620
  Type-safe AMQP worker for consuming messages from RabbitMQ.
621
621
 
@@ -669,7 +669,7 @@ await worker.close().resultToPromise();
669
669
  close(): Future<Result<void, TechnicalError>>;
670
670
  ```
671
671
 
672
- Defined in: [packages/worker/src/worker.ts:327](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L327)
672
+ Defined in: [packages/worker/src/worker.ts:327](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L327)
673
673
 
674
674
  Close the AMQP channel and connection.
675
675
 
@@ -697,7 +697,7 @@ if (closeResult.isOk()) {
697
697
  static create<TContract>(options): Future<Result<TypedAmqpWorker<TContract>, TechnicalError>>;
698
698
  ```
699
699
 
700
- Defined in: [packages/worker/src/worker.ts:283](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L283)
700
+ Defined in: [packages/worker/src/worker.ts:283](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L283)
701
701
 
702
702
  Create a type-safe AMQP worker from a contract.
703
703
 
@@ -747,7 +747,7 @@ const worker = await TypedAmqpWorker.create({
747
747
  type CreateWorkerOptions<TContract> = object;
748
748
  ```
749
749
 
750
- Defined in: [packages/worker/src/worker.ts:124](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L124)
750
+ Defined in: [packages/worker/src/worker.ts:124](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L124)
751
751
 
752
752
  Options for creating a type-safe AMQP worker.
753
753
 
@@ -801,13 +801,13 @@ const options: CreateWorkerOptions<typeof contract> = {
801
801
 
802
802
  | Property | Type | Description | Defined in |
803
803
  | ------ | ------ | ------ | ------ |
804
- | <a id="connectionoptions"></a> `connectionOptions?` | `AmqpConnectionManagerOptions` | Optional connection configuration (heartbeat, reconnect settings, etc.) | [packages/worker/src/worker.ts:137](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L137) |
805
- | <a id="contract"></a> `contract` | `TContract` | The AMQP contract definition specifying consumers and their message schemas | [packages/worker/src/worker.ts:126](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L126) |
806
- | <a id="handlers"></a> `handlers` | [`WorkerInferSafeConsumerHandlers`](#workerinfersafeconsumerhandlers)\<`TContract`\> | Handlers for each consumer defined in the contract. Handlers must return `Future<Result<void, HandlerError>>` for explicit error handling. Use defineHandler() to create safe handlers, or defineUnsafeHandler() which wraps Promise-based handlers into safe handlers internally. | [packages/worker/src/worker.ts:133](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L133) |
807
- | <a id="logger"></a> `logger?` | `Logger` | Optional logger for logging message consumption and errors | [packages/worker/src/worker.ts:139](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L139) |
808
- | <a id="retry"></a> `retry?` | [`RetryOptions`](#retryoptions) | Retry configuration - when undefined, uses legacy behavior (immediate requeue) | [packages/worker/src/worker.ts:141](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L141) |
809
- | <a id="telemetry"></a> `telemetry?` | `TelemetryProvider` | Optional telemetry provider for tracing and metrics. If not provided, uses the default provider which attempts to load OpenTelemetry. OpenTelemetry instrumentation is automatically enabled if @opentelemetry/api is installed. | [packages/worker/src/worker.ts:147](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L147) |
810
- | <a id="urls"></a> `urls` | `ConnectionUrl`[] | AMQP broker URL(s). Multiple URLs provide failover support | [packages/worker/src/worker.ts:135](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L135) |
804
+ | <a id="connectionoptions"></a> `connectionOptions?` | `AmqpConnectionManagerOptions` | Optional connection configuration (heartbeat, reconnect settings, etc.) | [packages/worker/src/worker.ts:137](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L137) |
805
+ | <a id="contract"></a> `contract` | `TContract` | The AMQP contract definition specifying consumers and their message schemas | [packages/worker/src/worker.ts:126](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L126) |
806
+ | <a id="handlers"></a> `handlers` | [`WorkerInferSafeConsumerHandlers`](#workerinfersafeconsumerhandlers)\<`TContract`\> | Handlers for each consumer defined in the contract. Handlers must return `Future<Result<void, HandlerError>>` for explicit error handling. Use defineHandler() to create safe handlers, or defineUnsafeHandler() which wraps Promise-based handlers into safe handlers internally. | [packages/worker/src/worker.ts:133](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L133) |
807
+ | <a id="logger"></a> `logger?` | `Logger` | Optional logger for logging message consumption and errors | [packages/worker/src/worker.ts:139](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L139) |
808
+ | <a id="retry"></a> `retry?` | [`RetryOptions`](#retryoptions) | Retry configuration - when undefined, uses legacy behavior (immediate requeue) | [packages/worker/src/worker.ts:141](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L141) |
809
+ | <a id="telemetry"></a> `telemetry?` | `TelemetryProvider` | Optional telemetry provider for tracing and metrics. If not provided, uses the default provider which attempts to load OpenTelemetry. OpenTelemetry instrumentation is automatically enabled if @opentelemetry/api is installed. | [packages/worker/src/worker.ts:147](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L147) |
810
+ | <a id="urls"></a> `urls` | `ConnectionUrl`[] | AMQP broker URL(s). Multiple URLs provide failover support | [packages/worker/src/worker.ts:135](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L135) |
811
811
 
812
812
  ***
813
813
 
@@ -819,7 +819,7 @@ type HandlerError =
819
819
  | NonRetryableError;
820
820
  ```
821
821
 
822
- Defined in: [packages/worker/src/errors.ts:83](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/errors.ts#L83)
822
+ Defined in: [packages/worker/src/errors.ts:83](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/errors.ts#L83)
823
823
 
824
824
  Union type representing all handler errors.
825
825
  Use this type when defining handlers that explicitly signal error outcomes.
@@ -832,7 +832,7 @@ Use this type when defining handlers that explicitly signal error outcomes.
832
832
  type RetryOptions = object;
833
833
  ```
834
834
 
835
- Defined in: [packages/worker/src/worker.ts:56](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L56)
835
+ Defined in: [packages/worker/src/worker.ts:56](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L56)
836
836
 
837
837
  Retry configuration options for handling failed message processing.
838
838
 
@@ -843,11 +843,11 @@ RabbitMQ's native TTL + Dead Letter Exchange (DLX) pattern.
843
843
 
844
844
  | Property | Type | Description | Defined in |
845
845
  | ------ | ------ | ------ | ------ |
846
- | <a id="backoffmultiplier"></a> `backoffMultiplier?` | `number` | Exponential backoff multiplier (default: 2) | [packages/worker/src/worker.ts:64](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L64) |
847
- | <a id="initialdelayms"></a> `initialDelayMs?` | `number` | Initial delay in ms before first retry (default: 1000) | [packages/worker/src/worker.ts:60](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L60) |
848
- | <a id="jitter"></a> `jitter?` | `boolean` | Add jitter to prevent thundering herd (default: true) | [packages/worker/src/worker.ts:66](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L66) |
849
- | <a id="maxdelayms"></a> `maxDelayMs?` | `number` | Maximum delay in ms between retries (default: 30000) | [packages/worker/src/worker.ts:62](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L62) |
850
- | <a id="maxretries"></a> `maxRetries?` | `number` | Maximum retry attempts before sending to DLQ (default: 3) | [packages/worker/src/worker.ts:58](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/worker.ts#L58) |
846
+ | <a id="backoffmultiplier"></a> `backoffMultiplier?` | `number` | Exponential backoff multiplier (default: 2) | [packages/worker/src/worker.ts:64](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L64) |
847
+ | <a id="initialdelayms"></a> `initialDelayMs?` | `number` | Initial delay in ms before first retry (default: 1000) | [packages/worker/src/worker.ts:60](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L60) |
848
+ | <a id="jitter"></a> `jitter?` | `boolean` | Add jitter to prevent thundering herd (default: true) | [packages/worker/src/worker.ts:66](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L66) |
849
+ | <a id="maxdelayms"></a> `maxDelayMs?` | `number` | Maximum delay in ms between retries (default: 30000) | [packages/worker/src/worker.ts:62](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L62) |
850
+ | <a id="maxretries"></a> `maxRetries?` | `number` | Maximum retry attempts before sending to DLQ (default: 3) | [packages/worker/src/worker.ts:58](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/worker.ts#L58) |
851
851
 
852
852
  ***
853
853
 
@@ -857,7 +857,7 @@ RabbitMQ's native TTL + Dead Letter Exchange (DLX) pattern.
857
857
  type WorkerInferConsumerBatchHandler<TContract, TName> = WorkerInferUnsafeConsumerBatchHandler<TContract, TName>;
858
858
  ```
859
859
 
860
- Defined in: [packages/worker/src/types.ts:195](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/types.ts#L195)
860
+ Defined in: [packages/worker/src/types.ts:195](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/types.ts#L195)
861
861
 
862
862
  #### Type Parameters
863
863
 
@@ -878,7 +878,7 @@ Use WorkerInferUnsafeConsumerBatchHandler instead
878
878
  type WorkerInferConsumerHandler<TContract, TName> = WorkerInferUnsafeConsumerHandler<TContract, TName>;
879
879
  ```
880
880
 
881
- Defined in: [packages/worker/src/types.ts:187](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/types.ts#L187)
881
+ Defined in: [packages/worker/src/types.ts:187](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/types.ts#L187)
882
882
 
883
883
  #### Type Parameters
884
884
 
@@ -899,7 +899,7 @@ Use WorkerInferUnsafeConsumerHandler instead
899
899
  type WorkerInferConsumerHandlerEntry<TContract, TName> = WorkerInferUnsafeConsumerHandlerEntry<TContract, TName>;
900
900
  ```
901
901
 
902
- Defined in: [packages/worker/src/types.ts:203](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/types.ts#L203)
902
+ Defined in: [packages/worker/src/types.ts:203](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/types.ts#L203)
903
903
 
904
904
  #### Type Parameters
905
905
 
@@ -920,7 +920,7 @@ Use WorkerInferUnsafeConsumerHandlerEntry instead
920
920
  type WorkerInferConsumerHandlers<TContract> = WorkerInferUnsafeConsumerHandlers<TContract>;
921
921
  ```
922
922
 
923
- Defined in: [packages/worker/src/types.ts:211](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/types.ts#L211)
923
+ Defined in: [packages/worker/src/types.ts:211](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/types.ts#L211)
924
924
 
925
925
  #### Type Parameters
926
926
 
@@ -940,7 +940,7 @@ Use WorkerInferUnsafeConsumerHandlers instead
940
940
  type WorkerInferConsumerInput<TContract, TName> = ConsumerInferInput<InferConsumer<TContract, TName>>;
941
941
  ```
942
942
 
943
- Defined in: [packages/worker/src/types.ts:39](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/types.ts#L39)
943
+ Defined in: [packages/worker/src/types.ts:39](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/types.ts#L39)
944
944
 
945
945
  Worker perspective types - for consuming messages
946
946
 
@@ -959,7 +959,7 @@ Worker perspective types - for consuming messages
959
959
  type WorkerInferSafeConsumerBatchHandler<TContract, TName> = (messages) => Future<Result<void, HandlerError>>;
960
960
  ```
961
961
 
962
- Defined in: [packages/worker/src/types.ts:80](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/types.ts#L80)
962
+ Defined in: [packages/worker/src/types.ts:80](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/types.ts#L80)
963
963
 
964
964
  Safe consumer handler type for batch processing.
965
965
  Returns a `Future<Result<void, HandlerError>>` for explicit error handling.
@@ -996,7 +996,7 @@ const handler: WorkerInferSafeConsumerBatchHandler<typeof contract, 'processOrde
996
996
  type WorkerInferSafeConsumerHandler<TContract, TName> = (message) => Future<Result<void, HandlerError>>;
997
997
  ```
998
998
 
999
- Defined in: [packages/worker/src/types.ts:65](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/types.ts#L65)
999
+ Defined in: [packages/worker/src/types.ts:65](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/types.ts#L65)
1000
1000
 
1001
1001
  Safe consumer handler type for a specific consumer.
1002
1002
  Returns a `Future<Result<void, HandlerError>>` for explicit error handling.
@@ -1048,7 +1048,7 @@ type WorkerInferSafeConsumerHandlerEntry<TContract, TName> =
1048
1048
  }];
1049
1049
  ```
1050
1050
 
1051
- Defined in: [packages/worker/src/types.ts:95](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/types.ts#L95)
1051
+ Defined in: [packages/worker/src/types.ts:95](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/types.ts#L95)
1052
1052
 
1053
1053
  Safe handler entry for a consumer - either a function or a tuple of [handler, options].
1054
1054
 
@@ -1072,7 +1072,7 @@ Three patterns are supported:
1072
1072
  type WorkerInferSafeConsumerHandlers<TContract> = { [K in InferConsumerNames<TContract>]: WorkerInferSafeConsumerHandlerEntry<TContract, K> };
1073
1073
  ```
1074
1074
 
1075
- Defined in: [packages/worker/src/types.ts:113](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/types.ts#L113)
1075
+ Defined in: [packages/worker/src/types.ts:113](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/types.ts#L113)
1076
1076
 
1077
1077
  Safe consumer handlers for a contract.
1078
1078
  All handlers return `Future<Result<void, HandlerError>>` for explicit error control.
@@ -1091,7 +1091,7 @@ All handlers return `Future<Result<void, HandlerError>>` for explicit error cont
1091
1091
  type WorkerInferUnsafeConsumerBatchHandler<TContract, TName> = (messages) => Promise<void>;
1092
1092
  ```
1093
1093
 
1094
- Defined in: [packages/worker/src/types.ts:147](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/types.ts#L147)
1094
+ Defined in: [packages/worker/src/types.ts:147](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/types.ts#L147)
1095
1095
 
1096
1096
  Unsafe consumer handler type for batch processing.
1097
1097
  Returns a `Promise<void>` - throws exceptions on error.
@@ -1126,7 +1126,7 @@ Prefer using safe handlers (WorkerInferSafeConsumerBatchHandler) that return
1126
1126
  type WorkerInferUnsafeConsumerHandler<TContract, TName> = (message) => Promise<void>;
1127
1127
  ```
1128
1128
 
1129
- Defined in: [packages/worker/src/types.ts:135](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/types.ts#L135)
1129
+ Defined in: [packages/worker/src/types.ts:135](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/types.ts#L135)
1130
1130
 
1131
1131
  Unsafe consumer handler type for a specific consumer.
1132
1132
  Returns a `Promise<void>` - throws exceptions on error.
@@ -1176,7 +1176,7 @@ type WorkerInferUnsafeConsumerHandlerEntry<TContract, TName> =
1176
1176
  }];
1177
1177
  ```
1178
1178
 
1179
- Defined in: [packages/worker/src/types.ts:157](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/types.ts#L157)
1179
+ Defined in: [packages/worker/src/types.ts:157](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/types.ts#L157)
1180
1180
 
1181
1181
  Unsafe handler entry for a consumer - either a function or a tuple of [handler, options].
1182
1182
 
@@ -1199,7 +1199,7 @@ Prefer using safe handler entries (WorkerInferSafeConsumerHandlerEntry).
1199
1199
  type WorkerInferUnsafeConsumerHandlers<TContract> = { [K in InferConsumerNames<TContract>]: WorkerInferUnsafeConsumerHandlerEntry<TContract, K> };
1200
1200
  ```
1201
1201
 
1202
- Defined in: [packages/worker/src/types.ts:176](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/types.ts#L176)
1202
+ Defined in: [packages/worker/src/types.ts:176](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/types.ts#L176)
1203
1203
 
1204
1204
  Unsafe consumer handlers for a contract.
1205
1205
 
@@ -1226,7 +1226,7 @@ function defineHandler<TContract, TName>(
1226
1226
  handler): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;
1227
1227
  ```
1228
1228
 
1229
- Defined in: [packages/worker/src/handlers.ts:134](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/handlers.ts#L134)
1229
+ Defined in: [packages/worker/src/handlers.ts:134](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/handlers.ts#L134)
1230
1230
 
1231
1231
  Define a type-safe handler for a specific consumer in a contract.
1232
1232
 
@@ -1300,7 +1300,7 @@ function defineHandler<TContract, TName>(
1300
1300
  options): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;
1301
1301
  ```
1302
1302
 
1303
- Defined in: [packages/worker/src/handlers.ts:142](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/handlers.ts#L142)
1303
+ Defined in: [packages/worker/src/handlers.ts:142](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/handlers.ts#L142)
1304
1304
 
1305
1305
  Define a type-safe handler for a specific consumer in a contract.
1306
1306
 
@@ -1378,7 +1378,7 @@ function defineHandler<TContract, TName>(
1378
1378
  options): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;
1379
1379
  ```
1380
1380
 
1381
- Defined in: [packages/worker/src/handlers.ts:151](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/handlers.ts#L151)
1381
+ Defined in: [packages/worker/src/handlers.ts:151](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/handlers.ts#L151)
1382
1382
 
1383
1383
  Define a type-safe handler for a specific consumer in a contract.
1384
1384
 
@@ -1454,7 +1454,7 @@ const validateOrderHandler = defineHandler(
1454
1454
  function defineHandlers<TContract>(contract, handlers): WorkerInferSafeConsumerHandlers<TContract>;
1455
1455
  ```
1456
1456
 
1457
- Defined in: [packages/worker/src/handlers.ts:208](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/handlers.ts#L208)
1457
+ Defined in: [packages/worker/src/handlers.ts:208](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/handlers.ts#L208)
1458
1458
 
1459
1459
  Define multiple type-safe handlers for consumers in a contract.
1460
1460
 
@@ -1512,7 +1512,7 @@ function defineUnsafeHandler<TContract, TName>(
1512
1512
  handler): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;
1513
1513
  ```
1514
1514
 
1515
- Defined in: [packages/worker/src/handlers.ts:272](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/handlers.ts#L272)
1515
+ Defined in: [packages/worker/src/handlers.ts:272](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/handlers.ts#L272)
1516
1516
 
1517
1517
  Define an unsafe handler for a specific consumer in a contract.
1518
1518
 
@@ -1575,7 +1575,7 @@ function defineUnsafeHandler<TContract, TName>(
1575
1575
  options): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;
1576
1576
  ```
1577
1577
 
1578
- Defined in: [packages/worker/src/handlers.ts:280](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/handlers.ts#L280)
1578
+ Defined in: [packages/worker/src/handlers.ts:280](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/handlers.ts#L280)
1579
1579
 
1580
1580
  Define an unsafe handler for a specific consumer in a contract.
1581
1581
 
@@ -1642,7 +1642,7 @@ function defineUnsafeHandler<TContract, TName>(
1642
1642
  options): WorkerInferSafeConsumerHandlerEntry<TContract, TName>;
1643
1643
  ```
1644
1644
 
1645
- Defined in: [packages/worker/src/handlers.ts:289](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/handlers.ts#L289)
1645
+ Defined in: [packages/worker/src/handlers.ts:289](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/handlers.ts#L289)
1646
1646
 
1647
1647
  Define an unsafe handler for a specific consumer in a contract.
1648
1648
 
@@ -1707,7 +1707,7 @@ const processOrderHandler = defineUnsafeHandler(
1707
1707
  function defineUnsafeHandlers<TContract>(contract, handlers): WorkerInferSafeConsumerHandlers<TContract>;
1708
1708
  ```
1709
1709
 
1710
- Defined in: [packages/worker/src/handlers.ts:373](https://github.com/btravers/amqp-contract/blob/f058a24938d9644a82812e57d7995cb683cfd6b5/packages/worker/src/handlers.ts#L373)
1710
+ Defined in: [packages/worker/src/handlers.ts:373](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/packages/worker/src/handlers.ts#L373)
1711
1711
 
1712
1712
  Define multiple unsafe handlers for consumers in a contract.
1713
1713
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amqp-contract/worker",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Worker utilities for consuming messages using amqp-contract",
5
5
  "keywords": [
6
6
  "amqp",
@@ -52,8 +52,8 @@
52
52
  "dependencies": {
53
53
  "@standard-schema/spec": "1.1.0",
54
54
  "@swan-io/boxed": "3.2.1",
55
- "@amqp-contract/contract": "0.9.0",
56
- "@amqp-contract/core": "0.9.0"
55
+ "@amqp-contract/contract": "0.10.0",
56
+ "@amqp-contract/core": "0.10.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/amqplib": "0.10.8",
@@ -67,7 +67,7 @@
67
67
  "typescript": "5.9.3",
68
68
  "vitest": "4.0.16",
69
69
  "zod": "4.3.5",
70
- "@amqp-contract/testing": "0.9.0",
70
+ "@amqp-contract/testing": "0.10.0",
71
71
  "@amqp-contract/tsconfig": "0.1.0",
72
72
  "@amqp-contract/typedoc": "0.1.0"
73
73
  },