@amqp-contract/worker 0.4.0 → 0.5.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 +7 -0
- package/dist/index.mjs +7 -0
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +21 -21
- package/package.json +7 -8
package/dist/index.cjs
CHANGED
|
@@ -166,6 +166,13 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
166
166
|
const handler = this.handlers[consumerName];
|
|
167
167
|
if (!handler) return _swan_io_boxed.Future.value(_swan_io_boxed.Result.Error(new TechnicalError(`Handler for "${String(consumerName)}" not provided`)));
|
|
168
168
|
return _swan_io_boxed.Future.fromPromise(this.amqpClient.channel.consume(consumer.queue.name, async (msg) => {
|
|
169
|
+
if (msg === null) {
|
|
170
|
+
this.logger?.warn("Consumer cancelled by server", {
|
|
171
|
+
consumerName: String(consumerName),
|
|
172
|
+
queueName: consumer.queue.name
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
169
176
|
const parseResult = _swan_io_boxed.Result.fromExecution(() => JSON.parse(msg.content.toString()));
|
|
170
177
|
if (parseResult.isError()) {
|
|
171
178
|
this.logger?.error("Error parsing message", {
|
package/dist/index.mjs
CHANGED
|
@@ -166,6 +166,13 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
166
166
|
const handler = this.handlers[consumerName];
|
|
167
167
|
if (!handler) return Future.value(Result.Error(new TechnicalError(`Handler for "${String(consumerName)}" not provided`)));
|
|
168
168
|
return Future.fromPromise(this.amqpClient.channel.consume(consumer.queue.name, async (msg) => {
|
|
169
|
+
if (msg === null) {
|
|
170
|
+
this.logger?.warn("Consumer cancelled by server", {
|
|
171
|
+
consumerName: String(consumerName),
|
|
172
|
+
queueName: consumer.queue.name
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
169
176
|
const parseResult = Result.fromExecution(() => JSON.parse(msg.content.toString()));
|
|
170
177
|
if (parseResult.isError()) {
|
|
171
178
|
this.logger?.error("Error parsing message", {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["cause?: unknown","consumerName: string","issues: unknown","contract: TContract","amqpClient: AmqpClient","handlers: WorkerInferConsumerHandlers<TContract>","logger?: Logger"],"sources":["../src/errors.ts","../src/worker.ts","../src/handlers.ts"],"sourcesContent":["/**\n * Base error class for worker errors\n */\nabstract class WorkerError extends Error {\n protected constructor(message: string) {\n super(message);\n this.name = \"WorkerError\";\n // Node.js specific stack trace capture\n const ErrorConstructor = Error as unknown as {\n captureStackTrace?: (target: object, constructor: Function) => void;\n };\n if (typeof ErrorConstructor.captureStackTrace === \"function\") {\n ErrorConstructor.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * Error for technical/runtime failures in worker operations\n * This includes validation failures, parsing failures, and processing failures\n */\nexport class TechnicalError extends WorkerError {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"TechnicalError\";\n }\n}\n\n/**\n * Error thrown when message validation fails\n */\nexport class MessageValidationError extends WorkerError {\n constructor(\n public readonly consumerName: string,\n public readonly issues: unknown,\n ) {\n super(`Message validation failed for consumer \"${consumerName}\"`);\n this.name = \"MessageValidationError\";\n }\n}\n","import { AmqpClient, type Logger } from \"@amqp-contract/core\";\nimport type { AmqpConnectionManagerOptions, ConnectionUrl } from \"amqp-connection-manager\";\nimport type { ContractDefinition, InferConsumerNames } from \"@amqp-contract/contract\";\nimport { Future, Result } from \"@swan-io/boxed\";\nimport { MessageValidationError, TechnicalError } from \"./errors.js\";\nimport type { WorkerInferConsumerHandlers, WorkerInferConsumerInput } from \"./types.js\";\n\n/**\n * Options for creating a type-safe AMQP worker.\n *\n * @typeParam TContract - The contract definition type\n *\n * @example\n * ```typescript\n * const options: CreateWorkerOptions<typeof contract> = {\n * contract: myContract,\n * handlers: {\n * processOrder: async (message) => {\n * console.log('Processing order:', message.orderId);\n * }\n * },\n * urls: ['amqp://localhost'],\n * connectionOptions: {\n * heartbeatIntervalInSeconds: 30\n * },\n * logger: myLogger\n * };\n * ```\n */\nexport type CreateWorkerOptions<TContract extends ContractDefinition> = {\n /** The AMQP contract definition specifying consumers and their message schemas */\n contract: TContract;\n /** Handlers for each consumer defined in the contract */\n handlers: WorkerInferConsumerHandlers<TContract>;\n /** AMQP broker URL(s). Multiple URLs provide failover support */\n urls: ConnectionUrl[];\n /** Optional connection configuration (heartbeat, reconnect settings, etc.) */\n connectionOptions?: AmqpConnectionManagerOptions | undefined;\n /** Optional logger for logging message consumption and errors */\n logger?: Logger | undefined;\n};\n\n/**\n * Type-safe AMQP worker for consuming messages from RabbitMQ.\n *\n * This class provides automatic message validation, connection management,\n * and error handling for consuming messages based on a contract definition.\n *\n * @typeParam TContract - The contract definition type\n *\n * @example\n * ```typescript\n * import { TypedAmqpWorker } from '@amqp-contract/worker';\n * import { z } from 'zod';\n *\n * const contract = defineContract({\n * queues: {\n * orderProcessing: defineQueue('order-processing', { durable: true })\n * },\n * consumers: {\n * processOrder: defineConsumer('order-processing', z.object({\n * orderId: z.string(),\n * amount: z.number()\n * }))\n * }\n * });\n *\n * const worker = await TypedAmqpWorker.create({\n * contract,\n * handlers: {\n * processOrder: async (message) => {\n * console.log('Processing order', message.orderId);\n * // Process the order...\n * }\n * },\n * urls: ['amqp://localhost']\n * }).resultToPromise();\n *\n * // Close when done\n * await worker.close().resultToPromise();\n * ```\n */\nexport class TypedAmqpWorker<TContract extends ContractDefinition> {\n private constructor(\n private readonly contract: TContract,\n private readonly amqpClient: AmqpClient,\n private readonly handlers: WorkerInferConsumerHandlers<TContract>,\n private readonly logger?: Logger,\n ) {}\n\n /**\n * Create a type-safe AMQP worker from a contract.\n *\n * Connection management (including automatic reconnection) is handled internally\n * by amqp-connection-manager via the {@link AmqpClient}. The worker will set up\n * consumers for all contract-defined handlers asynchronously in the background\n * once the underlying connection and channels are ready.\n *\n * Connections are automatically shared across clients and workers with the same\n * URLs and connection options, following RabbitMQ best practices.\n *\n * @param options - Configuration options for the worker\n * @returns A Future that resolves to a Result containing the worker or an error\n *\n * @example\n * ```typescript\n * const workerResult = await TypedAmqpWorker.create({\n * contract: myContract,\n * handlers: {\n * processOrder: async (msg) => console.log('Order:', msg.orderId)\n * },\n * urls: ['amqp://localhost']\n * }).resultToPromise();\n *\n * if (workerResult.isError()) {\n * console.error('Failed to create worker:', workerResult.error);\n * }\n * ```\n */\n static create<TContract extends ContractDefinition>({\n contract,\n handlers,\n urls,\n connectionOptions,\n logger,\n }: CreateWorkerOptions<TContract>): Future<Result<TypedAmqpWorker<TContract>, TechnicalError>> {\n const worker = new TypedAmqpWorker(\n contract,\n new AmqpClient(contract, {\n urls,\n connectionOptions,\n }),\n handlers,\n logger,\n );\n\n return worker\n .waitForConnectionReady()\n .flatMapOk(() => worker.consumeAll())\n .mapOk(() => worker);\n }\n\n /**\n * Close the AMQP channel and connection.\n *\n * This gracefully closes the connection to the AMQP broker,\n * stopping all message consumption and cleaning up resources.\n *\n * @returns A Future that resolves to a Result indicating success or failure\n *\n * @example\n * ```typescript\n * const closeResult = await worker.close().resultToPromise();\n * if (closeResult.isOk()) {\n * console.log('Worker closed successfully');\n * }\n * ```\n */\n close(): Future<Result<void, TechnicalError>> {\n return Future.fromPromise(this.amqpClient.close())\n .mapError((error) => new TechnicalError(\"Failed to close AMQP connection\", error))\n .mapOk(() => undefined);\n }\n\n /**\n * Start consuming messages for all consumers\n */\n private consumeAll(): Future<Result<void, TechnicalError>> {\n if (!this.contract.consumers) {\n return Future.value(Result.Error(new TechnicalError(\"No consumers defined in contract\")));\n }\n\n const consumerNames = Object.keys(this.contract.consumers) as InferConsumerNames<TContract>[];\n\n return Future.all(consumerNames.map((consumerName) => this.consume(consumerName)))\n .map(Result.all)\n .mapOk(() => undefined);\n }\n\n 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.handlers[consumerName];\n if (!handler) {\n return Future.value(\n Result.Error(new TechnicalError(`Handler for \"${String(consumerName)}\" not provided`)),\n );\n }\n\n // Start consuming\n return Future.fromPromise(\n this.amqpClient.channel.consume(consumer.queue.name, async (msg) => {\n // Parse message\n const parseResult = Result.fromExecution(() => JSON.parse(msg.content.toString()));\n if (parseResult.isError()) {\n this.logger?.error(\"Error parsing message\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n error: parseResult.error,\n });\n\n // fixme proper error handling strategy\n // Reject message with no requeue (malformed JSON)\n this.amqpClient.channel.nack(msg, false, false);\n return;\n }\n\n const rawValidation = consumer.message.payload[\"~standard\"].validate(parseResult.value);\n await Future.fromPromise(\n rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation),\n )\n .mapOkToResult((validationResult) => {\n if (validationResult.issues) {\n return Result.Error(\n new MessageValidationError(String(consumerName), validationResult.issues),\n );\n }\n\n return Result.Ok(validationResult.value as WorkerInferConsumerInput<TContract, TName>);\n })\n .tapError((error) => {\n this.logger?.error(\"Message validation failed\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n error,\n });\n\n // fixme proper error handling strategy\n // Reject message with no requeue (validation failed)\n this.amqpClient.channel.nack(msg, false, false);\n })\n .flatMapOk((validatedMessage) =>\n Future.fromPromise(handler(validatedMessage)).tapError((error) => {\n this.logger?.error(\"Error processing message\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n error,\n });\n\n // fixme proper error handling strategy\n // Reject message and requeue (handler failed)\n this.amqpClient.channel.nack(msg, false, true);\n }),\n )\n .tapOk(() => {\n this.logger?.info(\"Message consumed successfully\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n });\n\n // Acknowledge message\n this.amqpClient.channel.ack(msg);\n })\n .toPromise();\n }),\n )\n .mapError(\n (error) =>\n new TechnicalError(`Failed to start consuming for \"${String(consumerName)}\"`, error),\n )\n .mapOk(() => undefined);\n }\n}\n","import type { ContractDefinition, InferConsumerNames } from \"@amqp-contract/contract\";\nimport type { WorkerInferConsumerHandler, WorkerInferConsumerHandlers } from \"./types.js\";\n\n/**\n * Define a type-safe handler for a specific consumer in a contract.\n *\n * This utility allows you to define handlers outside of the worker creation,\n * providing better code organization and reusability.\n *\n * @template TContract - The contract definition type\n * @template TName - The consumer name from the contract\n * @param contract - The contract definition containing the consumer\n * @param consumerName - The name of the consumer from the contract\n * @param handler - The async handler function that processes messages\n * @returns A type-safe handler that can be used with TypedAmqpWorker\n *\n * @example\n * ```typescript\n * import { defineHandler } from '@amqp-contract/worker';\n * import { orderContract } from './contract';\n *\n * // Define handler outside of worker creation\n * const processOrderHandler = defineHandler(\n * orderContract,\n * 'processOrder',\n * async (message) => {\n * // message is fully typed based on the contract\n * console.log('Processing order:', message.orderId);\n * await processPayment(message);\n * }\n * );\n *\n * // Use the handler in worker\n * const worker = await TypedAmqpWorker.create({\n * contract: orderContract,\n * handlers: {\n * processOrder: processOrderHandler,\n * },\n * connection: 'amqp://localhost',\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Define multiple handlers\n * const processOrderHandler = defineHandler(\n * orderContract,\n * 'processOrder',\n * async (message) => {\n * await processOrder(message);\n * }\n * );\n *\n * const notifyOrderHandler = defineHandler(\n * orderContract,\n * 'notifyOrder',\n * async (message) => {\n * await sendNotification(message);\n * }\n * );\n *\n * // Compose handlers\n * const worker = await TypedAmqpWorker.create({\n * contract: orderContract,\n * handlers: {\n * processOrder: processOrderHandler,\n * notifyOrder: notifyOrderHandler,\n * },\n * connection: 'amqp://localhost',\n * });\n * ```\n */\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: WorkerInferConsumerHandler<TContract, TName>,\n): WorkerInferConsumerHandler<TContract, TName> {\n // Validate that the consumer exists in the contract\n const consumers = contract.consumers;\n\n if (!consumers || !(consumerName in consumers)) {\n const availableConsumers = consumers ? Object.keys(consumers) : [];\n const available = availableConsumers.length > 0 ? availableConsumers.join(\", \") : \"none\";\n throw new Error(\n `Consumer \"${String(consumerName)}\" not found in contract. Available consumers: ${available}`,\n );\n }\n\n // Return the handler as-is, with type checking enforced\n return handler;\n}\n\n/**\n * Define multiple type-safe handlers for consumers in a contract.\n *\n * This utility allows you to define all handlers at once outside of the worker creation,\n * ensuring type safety and providing better code organization.\n *\n * @template TContract - The contract definition type\n * @param contract - The contract definition containing the consumers\n * @param handlers - An object with async handler functions for each consumer\n * @returns A type-safe handlers object that can be used with TypedAmqpWorker\n *\n * @example\n * ```typescript\n * import { defineHandlers } from '@amqp-contract/worker';\n * import { orderContract } from './contract';\n *\n * // Define all handlers at once\n * const handlers = defineHandlers(orderContract, {\n * processOrder: async (message) => {\n * // message is fully typed based on the contract\n * console.log('Processing order:', message.orderId);\n * await processPayment(message);\n * },\n * notifyOrder: async (message) => {\n * await sendNotification(message);\n * },\n * shipOrder: async (message) => {\n * await prepareShipment(message);\n * },\n * });\n *\n * // Use the handlers in worker\n * const worker = await TypedAmqpWorker.create({\n * contract: orderContract,\n * handlers,\n * connection: 'amqp://localhost',\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Separate handler definitions for better organization\n * async function handleProcessOrder(message: WorkerInferConsumerInput<typeof orderContract, 'processOrder'>) {\n * await processOrder(message);\n * }\n *\n * async function handleNotifyOrder(message: WorkerInferConsumerInput<typeof orderContract, 'notifyOrder'>) {\n * await sendNotification(message);\n * }\n *\n * const handlers = defineHandlers(orderContract, {\n * processOrder: handleProcessOrder,\n * notifyOrder: handleNotifyOrder,\n * });\n * ```\n */\nexport function defineHandlers<TContract extends ContractDefinition>(\n contract: TContract,\n handlers: WorkerInferConsumerHandlers<TContract>,\n): WorkerInferConsumerHandlers<TContract> {\n // Validate that all consumers in handlers exist in the contract\n const consumers = contract.consumers;\n const availableConsumers = Object.keys(consumers ?? {});\n const availableConsumerNames =\n availableConsumers.length > 0 ? availableConsumers.join(\", \") : \"none\";\n\n for (const handlerName of Object.keys(handlers)) {\n if (!consumers || !(handlerName in consumers)) {\n throw new Error(\n `Consumer \"${handlerName}\" not found in contract. Available consumers: ${availableConsumerNames}`,\n );\n }\n }\n\n // Return the handlers as-is, with type checking enforced\n return handlers;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["cause?: unknown","consumerName: string","issues: unknown","contract: TContract","amqpClient: AmqpClient","handlers: WorkerInferConsumerHandlers<TContract>","logger?: Logger"],"sources":["../src/errors.ts","../src/worker.ts","../src/handlers.ts"],"sourcesContent":["/**\n * Base error class for worker errors\n */\nabstract class WorkerError extends Error {\n protected constructor(message: string) {\n super(message);\n this.name = \"WorkerError\";\n // Node.js specific stack trace capture\n const ErrorConstructor = Error as unknown as {\n captureStackTrace?: (target: object, constructor: Function) => void;\n };\n if (typeof ErrorConstructor.captureStackTrace === \"function\") {\n ErrorConstructor.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * Error for technical/runtime failures in worker operations\n * This includes validation failures, parsing failures, and processing failures\n */\nexport class TechnicalError extends WorkerError {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"TechnicalError\";\n }\n}\n\n/**\n * Error thrown when message validation fails\n */\nexport class MessageValidationError extends WorkerError {\n constructor(\n public readonly consumerName: string,\n public readonly issues: unknown,\n ) {\n super(`Message validation failed for consumer \"${consumerName}\"`);\n this.name = \"MessageValidationError\";\n }\n}\n","import { AmqpClient, type Logger } from \"@amqp-contract/core\";\nimport type { AmqpConnectionManagerOptions, ConnectionUrl } from \"amqp-connection-manager\";\nimport type { ContractDefinition, InferConsumerNames } from \"@amqp-contract/contract\";\nimport { Future, Result } from \"@swan-io/boxed\";\nimport { MessageValidationError, TechnicalError } from \"./errors.js\";\nimport type { WorkerInferConsumerHandlers, WorkerInferConsumerInput } from \"./types.js\";\n\n/**\n * Options for creating a type-safe AMQP worker.\n *\n * @typeParam TContract - The contract definition type\n *\n * @example\n * ```typescript\n * const options: CreateWorkerOptions<typeof contract> = {\n * contract: myContract,\n * handlers: {\n * processOrder: async (message) => {\n * console.log('Processing order:', message.orderId);\n * }\n * },\n * urls: ['amqp://localhost'],\n * connectionOptions: {\n * heartbeatIntervalInSeconds: 30\n * },\n * logger: myLogger\n * };\n * ```\n */\nexport type CreateWorkerOptions<TContract extends ContractDefinition> = {\n /** The AMQP contract definition specifying consumers and their message schemas */\n contract: TContract;\n /** Handlers for each consumer defined in the contract */\n handlers: WorkerInferConsumerHandlers<TContract>;\n /** AMQP broker URL(s). Multiple URLs provide failover support */\n urls: ConnectionUrl[];\n /** Optional connection configuration (heartbeat, reconnect settings, etc.) */\n connectionOptions?: AmqpConnectionManagerOptions | undefined;\n /** Optional logger for logging message consumption and errors */\n logger?: Logger | undefined;\n};\n\n/**\n * Type-safe AMQP worker for consuming messages from RabbitMQ.\n *\n * This class provides automatic message validation, connection management,\n * and error handling for consuming messages based on a contract definition.\n *\n * @typeParam TContract - The contract definition type\n *\n * @example\n * ```typescript\n * import { TypedAmqpWorker } from '@amqp-contract/worker';\n * import { z } from 'zod';\n *\n * const contract = defineContract({\n * queues: {\n * orderProcessing: defineQueue('order-processing', { durable: true })\n * },\n * consumers: {\n * processOrder: defineConsumer('order-processing', z.object({\n * orderId: z.string(),\n * amount: z.number()\n * }))\n * }\n * });\n *\n * const worker = await TypedAmqpWorker.create({\n * contract,\n * handlers: {\n * processOrder: async (message) => {\n * console.log('Processing order', message.orderId);\n * // Process the order...\n * }\n * },\n * urls: ['amqp://localhost']\n * }).resultToPromise();\n *\n * // Close when done\n * await worker.close().resultToPromise();\n * ```\n */\nexport class TypedAmqpWorker<TContract extends ContractDefinition> {\n private constructor(\n private readonly contract: TContract,\n private readonly amqpClient: AmqpClient,\n private readonly handlers: WorkerInferConsumerHandlers<TContract>,\n private readonly logger?: Logger,\n ) {}\n\n /**\n * Create a type-safe AMQP worker from a contract.\n *\n * Connection management (including automatic reconnection) is handled internally\n * by amqp-connection-manager via the {@link AmqpClient}. The worker will set up\n * consumers for all contract-defined handlers asynchronously in the background\n * once the underlying connection and channels are ready.\n *\n * Connections are automatically shared across clients and workers with the same\n * URLs and connection options, following RabbitMQ best practices.\n *\n * @param options - Configuration options for the worker\n * @returns A Future that resolves to a Result containing the worker or an error\n *\n * @example\n * ```typescript\n * const workerResult = await TypedAmqpWorker.create({\n * contract: myContract,\n * handlers: {\n * processOrder: async (msg) => console.log('Order:', msg.orderId)\n * },\n * urls: ['amqp://localhost']\n * }).resultToPromise();\n *\n * if (workerResult.isError()) {\n * console.error('Failed to create worker:', workerResult.error);\n * }\n * ```\n */\n static create<TContract extends ContractDefinition>({\n contract,\n handlers,\n urls,\n connectionOptions,\n logger,\n }: CreateWorkerOptions<TContract>): Future<Result<TypedAmqpWorker<TContract>, TechnicalError>> {\n const worker = new TypedAmqpWorker(\n contract,\n new AmqpClient(contract, {\n urls,\n connectionOptions,\n }),\n handlers,\n logger,\n );\n\n return worker\n .waitForConnectionReady()\n .flatMapOk(() => worker.consumeAll())\n .mapOk(() => worker);\n }\n\n /**\n * Close the AMQP channel and connection.\n *\n * This gracefully closes the connection to the AMQP broker,\n * stopping all message consumption and cleaning up resources.\n *\n * @returns A Future that resolves to a Result indicating success or failure\n *\n * @example\n * ```typescript\n * const closeResult = await worker.close().resultToPromise();\n * if (closeResult.isOk()) {\n * console.log('Worker closed successfully');\n * }\n * ```\n */\n close(): Future<Result<void, TechnicalError>> {\n return Future.fromPromise(this.amqpClient.close())\n .mapError((error) => new TechnicalError(\"Failed to close AMQP connection\", error))\n .mapOk(() => undefined);\n }\n\n /**\n * Start consuming messages for all consumers\n */\n private consumeAll(): Future<Result<void, TechnicalError>> {\n if (!this.contract.consumers) {\n return Future.value(Result.Error(new TechnicalError(\"No consumers defined in contract\")));\n }\n\n const consumerNames = Object.keys(this.contract.consumers) as InferConsumerNames<TContract>[];\n\n return Future.all(consumerNames.map((consumerName) => this.consume(consumerName)))\n .map(Result.all)\n .mapOk(() => undefined);\n }\n\n 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.handlers[consumerName];\n if (!handler) {\n return Future.value(\n Result.Error(new TechnicalError(`Handler for \"${String(consumerName)}\" not provided`)),\n );\n }\n\n // Start consuming\n return Future.fromPromise(\n this.amqpClient.channel.consume(consumer.queue.name, async (msg) => {\n // Handle null messages (consumer cancellation)\n if (msg === null) {\n this.logger?.warn(\"Consumer cancelled by server\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n });\n return;\n }\n\n // Parse message\n const parseResult = Result.fromExecution(() => JSON.parse(msg.content.toString()));\n if (parseResult.isError()) {\n this.logger?.error(\"Error parsing message\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n error: parseResult.error,\n });\n\n // fixme proper error handling strategy\n // Reject message with no requeue (malformed JSON)\n this.amqpClient.channel.nack(msg, false, false);\n return;\n }\n\n const rawValidation = consumer.message.payload[\"~standard\"].validate(parseResult.value);\n await Future.fromPromise(\n rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation),\n )\n .mapOkToResult((validationResult) => {\n if (validationResult.issues) {\n return Result.Error(\n new MessageValidationError(String(consumerName), validationResult.issues),\n );\n }\n\n return Result.Ok(validationResult.value as WorkerInferConsumerInput<TContract, TName>);\n })\n .tapError((error) => {\n this.logger?.error(\"Message validation failed\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n error,\n });\n\n // fixme proper error handling strategy\n // Reject message with no requeue (validation failed)\n this.amqpClient.channel.nack(msg, false, false);\n })\n .flatMapOk((validatedMessage) =>\n Future.fromPromise(handler(validatedMessage)).tapError((error) => {\n this.logger?.error(\"Error processing message\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n error,\n });\n\n // fixme proper error handling strategy\n // Reject message and requeue (handler failed)\n this.amqpClient.channel.nack(msg, false, true);\n }),\n )\n .tapOk(() => {\n this.logger?.info(\"Message consumed successfully\", {\n consumerName: String(consumerName),\n queueName: consumer.queue.name,\n });\n\n // Acknowledge message\n this.amqpClient.channel.ack(msg);\n })\n .toPromise();\n }),\n )\n .mapError(\n (error) =>\n new TechnicalError(`Failed to start consuming for \"${String(consumerName)}\"`, error),\n )\n .mapOk(() => undefined);\n }\n}\n","import type { ContractDefinition, InferConsumerNames } from \"@amqp-contract/contract\";\nimport type { WorkerInferConsumerHandler, WorkerInferConsumerHandlers } from \"./types.js\";\n\n/**\n * Define a type-safe handler for a specific consumer in a contract.\n *\n * This utility allows you to define handlers outside of the worker creation,\n * providing better code organization and reusability.\n *\n * @template TContract - The contract definition type\n * @template TName - The consumer name from the contract\n * @param contract - The contract definition containing the consumer\n * @param consumerName - The name of the consumer from the contract\n * @param handler - The async handler function that processes messages\n * @returns A type-safe handler that can be used with TypedAmqpWorker\n *\n * @example\n * ```typescript\n * import { defineHandler } from '@amqp-contract/worker';\n * import { orderContract } from './contract';\n *\n * // Define handler outside of worker creation\n * const processOrderHandler = defineHandler(\n * orderContract,\n * 'processOrder',\n * async (message) => {\n * // message is fully typed based on the contract\n * console.log('Processing order:', message.orderId);\n * await processPayment(message);\n * }\n * );\n *\n * // Use the handler in worker\n * const worker = await TypedAmqpWorker.create({\n * contract: orderContract,\n * handlers: {\n * processOrder: processOrderHandler,\n * },\n * connection: 'amqp://localhost',\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Define multiple handlers\n * const processOrderHandler = defineHandler(\n * orderContract,\n * 'processOrder',\n * async (message) => {\n * await processOrder(message);\n * }\n * );\n *\n * const notifyOrderHandler = defineHandler(\n * orderContract,\n * 'notifyOrder',\n * async (message) => {\n * await sendNotification(message);\n * }\n * );\n *\n * // Compose handlers\n * const worker = await TypedAmqpWorker.create({\n * contract: orderContract,\n * handlers: {\n * processOrder: processOrderHandler,\n * notifyOrder: notifyOrderHandler,\n * },\n * connection: 'amqp://localhost',\n * });\n * ```\n */\nexport function defineHandler<\n TContract extends ContractDefinition,\n TName extends InferConsumerNames<TContract>,\n>(\n contract: TContract,\n consumerName: TName,\n handler: WorkerInferConsumerHandler<TContract, TName>,\n): WorkerInferConsumerHandler<TContract, TName> {\n // Validate that the consumer exists in the contract\n const consumers = contract.consumers;\n\n if (!consumers || !(consumerName in consumers)) {\n const availableConsumers = consumers ? Object.keys(consumers) : [];\n const available = availableConsumers.length > 0 ? availableConsumers.join(\", \") : \"none\";\n throw new Error(\n `Consumer \"${String(consumerName)}\" not found in contract. Available consumers: ${available}`,\n );\n }\n\n // Return the handler as-is, with type checking enforced\n return handler;\n}\n\n/**\n * Define multiple type-safe handlers for consumers in a contract.\n *\n * This utility allows you to define all handlers at once outside of the worker creation,\n * ensuring type safety and providing better code organization.\n *\n * @template TContract - The contract definition type\n * @param contract - The contract definition containing the consumers\n * @param handlers - An object with async handler functions for each consumer\n * @returns A type-safe handlers object that can be used with TypedAmqpWorker\n *\n * @example\n * ```typescript\n * import { defineHandlers } from '@amqp-contract/worker';\n * import { orderContract } from './contract';\n *\n * // Define all handlers at once\n * const handlers = defineHandlers(orderContract, {\n * processOrder: async (message) => {\n * // message is fully typed based on the contract\n * console.log('Processing order:', message.orderId);\n * await processPayment(message);\n * },\n * notifyOrder: async (message) => {\n * await sendNotification(message);\n * },\n * shipOrder: async (message) => {\n * await prepareShipment(message);\n * },\n * });\n *\n * // Use the handlers in worker\n * const worker = await TypedAmqpWorker.create({\n * contract: orderContract,\n * handlers,\n * connection: 'amqp://localhost',\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Separate handler definitions for better organization\n * async function handleProcessOrder(message: WorkerInferConsumerInput<typeof orderContract, 'processOrder'>) {\n * await processOrder(message);\n * }\n *\n * async function handleNotifyOrder(message: WorkerInferConsumerInput<typeof orderContract, 'notifyOrder'>) {\n * await sendNotification(message);\n * }\n *\n * const handlers = defineHandlers(orderContract, {\n * processOrder: handleProcessOrder,\n * notifyOrder: handleNotifyOrder,\n * });\n * ```\n */\nexport function defineHandlers<TContract extends ContractDefinition>(\n contract: TContract,\n handlers: WorkerInferConsumerHandlers<TContract>,\n): WorkerInferConsumerHandlers<TContract> {\n // Validate that all consumers in handlers exist in the contract\n const consumers = contract.consumers;\n const availableConsumers = Object.keys(consumers ?? {});\n const availableConsumerNames =\n availableConsumers.length > 0 ? availableConsumers.join(\", \") : \"none\";\n\n for (const handlerName of Object.keys(handlers)) {\n if (!consumers || !(handlerName in consumers)) {\n throw new Error(\n `Consumer \"${handlerName}\" not found in contract. Available consumers: ${availableConsumerNames}`,\n );\n }\n }\n\n // Return the handlers as-is, with type checking enforced\n return handlers;\n}\n"],"mappings":"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"}
|
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/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/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/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/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/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/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/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/errors.ts#L38) |
|
|
53
53
|
| <a id="message"></a> `message` | `public` | `string` | - | `WorkerError.message` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 |
|
|
54
54
|
| <a id="name"></a> `name` | `public` | `string` | - | `WorkerError.name` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 |
|
|
55
55
|
| <a id="stack"></a> `stack?` | `public` | `string` | - | `WorkerError.stack` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 |
|
|
@@ -159,7 +159,7 @@ WorkerError.prepareStackTrace
|
|
|
159
159
|
|
|
160
160
|
### TechnicalError
|
|
161
161
|
|
|
162
|
-
Defined in: [packages/worker/src/errors.ts:22](https://github.com/btravers/amqp-contract/blob/
|
|
162
|
+
Defined in: [packages/worker/src/errors.ts:22](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/errors.ts#L22)
|
|
163
163
|
|
|
164
164
|
Error for technical/runtime failures in worker operations
|
|
165
165
|
This includes validation failures, parsing failures, and processing failures
|
|
@@ -176,7 +176,7 @@ This includes validation failures, parsing failures, and processing failures
|
|
|
176
176
|
new TechnicalError(message, cause?): TechnicalError;
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
-
Defined in: [packages/worker/src/errors.ts:23](https://github.com/btravers/amqp-contract/blob/
|
|
179
|
+
Defined in: [packages/worker/src/errors.ts:23](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/errors.ts#L23)
|
|
180
180
|
|
|
181
181
|
###### Parameters
|
|
182
182
|
|
|
@@ -199,7 +199,7 @@ WorkerError.constructor
|
|
|
199
199
|
|
|
200
200
|
| Property | Modifier | Type | Description | Inherited from | Defined in |
|
|
201
201
|
| ------ | ------ | ------ | ------ | ------ | ------ |
|
|
202
|
-
| <a id="cause-1"></a> `cause?` | `readonly` | `unknown` | - | `WorkerError.cause` | [packages/worker/src/errors.ts:25](https://github.com/btravers/amqp-contract/blob/
|
|
202
|
+
| <a id="cause-1"></a> `cause?` | `readonly` | `unknown` | - | `WorkerError.cause` | [packages/worker/src/errors.ts:25](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/errors.ts#L25) |
|
|
203
203
|
| <a id="message-1"></a> `message` | `public` | `string` | - | `WorkerError.message` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 |
|
|
204
204
|
| <a id="name-1"></a> `name` | `public` | `string` | - | `WorkerError.name` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 |
|
|
205
205
|
| <a id="stack-1"></a> `stack?` | `public` | `string` | - | `WorkerError.stack` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 |
|
|
@@ -309,7 +309,7 @@ WorkerError.prepareStackTrace
|
|
|
309
309
|
|
|
310
310
|
### TypedAmqpWorker
|
|
311
311
|
|
|
312
|
-
Defined in: [packages/worker/src/worker.ts:83](https://github.com/btravers/amqp-contract/blob/
|
|
312
|
+
Defined in: [packages/worker/src/worker.ts:83](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/worker.ts#L83)
|
|
313
313
|
|
|
314
314
|
Type-safe AMQP worker for consuming messages from RabbitMQ.
|
|
315
315
|
|
|
@@ -363,7 +363,7 @@ await worker.close().resultToPromise();
|
|
|
363
363
|
close(): Future<Result<void, TechnicalError>>;
|
|
364
364
|
```
|
|
365
365
|
|
|
366
|
-
Defined in: [packages/worker/src/worker.ts:159](https://github.com/btravers/amqp-contract/blob/
|
|
366
|
+
Defined in: [packages/worker/src/worker.ts:159](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/worker.ts#L159)
|
|
367
367
|
|
|
368
368
|
Close the AMQP channel and connection.
|
|
369
369
|
|
|
@@ -391,7 +391,7 @@ if (closeResult.isOk()) {
|
|
|
391
391
|
static create<TContract>(options): Future<Result<TypedAmqpWorker<TContract>, TechnicalError>>;
|
|
392
392
|
```
|
|
393
393
|
|
|
394
|
-
Defined in: [packages/worker/src/worker.ts:120](https://github.com/btravers/amqp-contract/blob/
|
|
394
|
+
Defined in: [packages/worker/src/worker.ts:120](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/worker.ts#L120)
|
|
395
395
|
|
|
396
396
|
Create a type-safe AMQP worker from a contract.
|
|
397
397
|
|
|
@@ -445,7 +445,7 @@ if (workerResult.isError()) {
|
|
|
445
445
|
type CreateWorkerOptions<TContract> = object;
|
|
446
446
|
```
|
|
447
447
|
|
|
448
|
-
Defined in: [packages/worker/src/worker.ts:30](https://github.com/btravers/amqp-contract/blob/
|
|
448
|
+
Defined in: [packages/worker/src/worker.ts:30](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/worker.ts#L30)
|
|
449
449
|
|
|
450
450
|
Options for creating a type-safe AMQP worker.
|
|
451
451
|
|
|
@@ -477,11 +477,11 @@ const options: CreateWorkerOptions<typeof contract> = {
|
|
|
477
477
|
|
|
478
478
|
| Property | Type | Description | Defined in |
|
|
479
479
|
| ------ | ------ | ------ | ------ |
|
|
480
|
-
| <a id="connectionoptions"></a> `connectionOptions?` | `AmqpConnectionManagerOptions` | Optional connection configuration (heartbeat, reconnect settings, etc.) | [packages/worker/src/worker.ts:38](https://github.com/btravers/amqp-contract/blob/
|
|
481
|
-
| <a id="contract"></a> `contract` | `TContract` | The AMQP contract definition specifying consumers and their message schemas | [packages/worker/src/worker.ts:32](https://github.com/btravers/amqp-contract/blob/
|
|
482
|
-
| <a id="handlers"></a> `handlers` | [`WorkerInferConsumerHandlers`](#workerinferconsumerhandlers)\<`TContract`\> | Handlers for each consumer defined in the contract | [packages/worker/src/worker.ts:34](https://github.com/btravers/amqp-contract/blob/
|
|
483
|
-
| <a id="logger"></a> `logger?` | `Logger` | Optional logger for logging message consumption and errors | [packages/worker/src/worker.ts:40](https://github.com/btravers/amqp-contract/blob/
|
|
484
|
-
| <a id="urls"></a> `urls` | `ConnectionUrl`[] | AMQP broker URL(s). Multiple URLs provide failover support | [packages/worker/src/worker.ts:36](https://github.com/btravers/amqp-contract/blob/
|
|
480
|
+
| <a id="connectionoptions"></a> `connectionOptions?` | `AmqpConnectionManagerOptions` | Optional connection configuration (heartbeat, reconnect settings, etc.) | [packages/worker/src/worker.ts:38](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/worker.ts#L38) |
|
|
481
|
+
| <a id="contract"></a> `contract` | `TContract` | The AMQP contract definition specifying consumers and their message schemas | [packages/worker/src/worker.ts:32](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/worker.ts#L32) |
|
|
482
|
+
| <a id="handlers"></a> `handlers` | [`WorkerInferConsumerHandlers`](#workerinferconsumerhandlers)\<`TContract`\> | Handlers for each consumer defined in the contract | [packages/worker/src/worker.ts:34](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/worker.ts#L34) |
|
|
483
|
+
| <a id="logger"></a> `logger?` | `Logger` | Optional logger for logging message consumption and errors | [packages/worker/src/worker.ts:40](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/worker.ts#L40) |
|
|
484
|
+
| <a id="urls"></a> `urls` | `ConnectionUrl`[] | AMQP broker URL(s). Multiple URLs provide failover support | [packages/worker/src/worker.ts:36](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/worker.ts#L36) |
|
|
485
485
|
|
|
486
486
|
***
|
|
487
487
|
|
|
@@ -491,7 +491,7 @@ const options: CreateWorkerOptions<typeof contract> = {
|
|
|
491
491
|
type WorkerInferConsumerHandler<TContract, TName> = (message) => Promise<void>;
|
|
492
492
|
```
|
|
493
493
|
|
|
494
|
-
Defined in: [packages/worker/src/types.ts:45](https://github.com/btravers/amqp-contract/blob/
|
|
494
|
+
Defined in: [packages/worker/src/types.ts:45](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/types.ts#L45)
|
|
495
495
|
|
|
496
496
|
Infer consumer handler type for a specific consumer
|
|
497
497
|
|
|
@@ -520,7 +520,7 @@ Infer consumer handler type for a specific consumer
|
|
|
520
520
|
type WorkerInferConsumerHandlers<TContract> = { [K in InferConsumerNames<TContract>]: WorkerInferConsumerHandler<TContract, K> };
|
|
521
521
|
```
|
|
522
522
|
|
|
523
|
-
Defined in: [packages/worker/src/types.ts:53](https://github.com/btravers/amqp-contract/blob/
|
|
523
|
+
Defined in: [packages/worker/src/types.ts:53](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/types.ts#L53)
|
|
524
524
|
|
|
525
525
|
Infer all consumer handlers for a contract
|
|
526
526
|
|
|
@@ -538,7 +538,7 @@ Infer all consumer handlers for a contract
|
|
|
538
538
|
type WorkerInferConsumerInput<TContract, TName> = ConsumerInferInput<InferConsumer<TContract, TName>>;
|
|
539
539
|
```
|
|
540
540
|
|
|
541
|
-
Defined in: [packages/worker/src/types.ts:37](https://github.com/btravers/amqp-contract/blob/
|
|
541
|
+
Defined in: [packages/worker/src/types.ts:37](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/types.ts#L37)
|
|
542
542
|
|
|
543
543
|
Worker perspective types - for consuming messages
|
|
544
544
|
|
|
@@ -560,7 +560,7 @@ function defineHandler<TContract, TName>(
|
|
|
560
560
|
handler): WorkerInferConsumerHandler<TContract, TName>;
|
|
561
561
|
```
|
|
562
562
|
|
|
563
|
-
Defined in: [packages/worker/src/handlers.ts:73](https://github.com/btravers/amqp-contract/blob/
|
|
563
|
+
Defined in: [packages/worker/src/handlers.ts:73](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/handlers.ts#L73)
|
|
564
564
|
|
|
565
565
|
Define a type-safe handler for a specific consumer in a contract.
|
|
566
566
|
|
|
@@ -652,7 +652,7 @@ const worker = await TypedAmqpWorker.create({
|
|
|
652
652
|
function defineHandlers<TContract>(contract, handlers): WorkerInferConsumerHandlers<TContract>;
|
|
653
653
|
```
|
|
654
654
|
|
|
655
|
-
Defined in: [packages/worker/src/handlers.ts:152](https://github.com/btravers/amqp-contract/blob/
|
|
655
|
+
Defined in: [packages/worker/src/handlers.ts:152](https://github.com/btravers/amqp-contract/blob/2337be9d96ffc5b2bb77c6d63ca6e010efd8c2bc/packages/worker/src/handlers.ts#L152)
|
|
656
656
|
|
|
657
657
|
Define multiple type-safe handlers for consumers in a contract.
|
|
658
658
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amqp-contract/worker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Worker utilities for consuming messages using amqp-contract",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"amqp",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@standard-schema/spec": "1.1.0",
|
|
46
46
|
"@swan-io/boxed": "3.2.1",
|
|
47
|
-
"@amqp-contract/contract": "0.
|
|
48
|
-
"@amqp-contract/core": "0.
|
|
47
|
+
"@amqp-contract/contract": "0.5.0",
|
|
48
|
+
"@amqp-contract/core": "0.5.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/amqplib": "0.10.8",
|
|
@@ -53,14 +53,13 @@
|
|
|
53
53
|
"@vitest/coverage-v8": "4.0.16",
|
|
54
54
|
"amqp-connection-manager": "5.0.0",
|
|
55
55
|
"amqplib": "0.10.9",
|
|
56
|
-
"tsdown": "0.18.
|
|
56
|
+
"tsdown": "0.18.3",
|
|
57
57
|
"typedoc": "0.28.15",
|
|
58
58
|
"typedoc-plugin-markdown": "4.9.0",
|
|
59
59
|
"typescript": "5.9.3",
|
|
60
60
|
"vitest": "4.0.16",
|
|
61
61
|
"zod": "4.2.1",
|
|
62
|
-
"@amqp-contract/
|
|
63
|
-
"@amqp-contract/testing": "0.4.0",
|
|
62
|
+
"@amqp-contract/testing": "0.5.0",
|
|
64
63
|
"@amqp-contract/tsconfig": "0.0.0",
|
|
65
64
|
"@amqp-contract/typedoc": "0.0.1"
|
|
66
65
|
},
|
|
@@ -68,9 +67,9 @@
|
|
|
68
67
|
"build": "tsdown src/index.ts --format cjs,esm --dts --clean",
|
|
69
68
|
"build:docs": "typedoc",
|
|
70
69
|
"dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
|
|
71
|
-
"test": "vitest run --project
|
|
70
|
+
"test": "vitest run --project integration",
|
|
72
71
|
"test:integration": "vitest run --project integration",
|
|
73
|
-
"test:watch": "vitest --project
|
|
72
|
+
"test:watch": "vitest --project integration",
|
|
74
73
|
"typecheck": "tsc --noEmit"
|
|
75
74
|
}
|
|
76
75
|
}
|