@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 +1 -0
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +52 -52
- package/package.json +4 -4
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,
|
package/dist/index.mjs.map
CHANGED
|
@@ -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/
|
|
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/
|
|
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/
|
|
52
|
-
| <a id="issues"></a> `issues` | `readonly` | `unknown` | - | - | [packages/worker/src/errors.ts:38](https://github.com/btravers/amqp-contract/blob/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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.
|
|
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.
|
|
56
|
-
"@amqp-contract/core": "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.
|
|
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
|
},
|