UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

185 lines (177 loc) 6.32 kB
import { createPrimitive, KIND, PipelinePrimitive, type PipelinePrimitiveOptions, type Static, type TSchema, } from "alepha"; import type { QueuePrimitive } from "./$queue.ts"; /** * Creates a consumer primitive to process messages from a specific queue. * * Provides a dedicated message consumer that connects to a queue and processes messages * with custom handler logic, enabling scalable architectures where multiple consumers * can process messages from the same queue. * * **Key Features** * - Seamless integration with any $queue primitive * - Full type safety inherited from queue schema * - Automatic worker management for background processing * - Built-in error handling and retry mechanisms * - Support for multiple consumers per queue for horizontal scaling * * **Common Use Cases** * - Email sending and notification services * - Image and media processing workers * - Data synchronization and background jobs * * @example * ```ts * class EmailService { * emailQueue = $queue({ * name: "emails", * schema: z.object({ * to: z.text(), * subject: z.text(), * body: z.text() * }) * }); * * emailConsumer = $consumer({ * queue: this.emailQueue, * handler: async (message) => { * const { to, subject, body } = message.payload; * await this.sendEmail(to, subject, body); * } * }); * * async sendWelcomeEmail(userEmail: string) { * await this.emailQueue.push({ * to: userEmail, * subject: "Welcome!", * body: "Thanks for joining." * }); * } * } * ``` */ export const $consumer = <T extends TSchema>( options: ConsumerPrimitiveOptions<T>, ): ConsumerPrimitive<T> => { return createPrimitive(ConsumerPrimitive<T>, options); }; // --------------------------------------------------------------------------------------------------------------------- export interface ConsumerPrimitiveOptions<T extends TSchema> extends PipelinePrimitiveOptions { /** * The queue primitive that this consumer will process messages from. * * This establishes the connection between the consumer and its source queue: * - The consumer inherits the queue's message schema for type safety * - Messages pushed to the queue will be automatically routed to this consumer * - Multiple consumers can be attached to the same queue for parallel processing * - The consumer will use the queue's provider and configuration settings * * **Queue Integration Benefits**: * - Type safety: Consumer handler gets fully typed message payloads * - Schema validation: Messages are validated before reaching the consumer * - Error handling: Failed messages can be retried or moved to dead letter queues * - Monitoring: Queue metrics include consumer processing statistics * * @example * ```ts * // First, define a queue * emailQueue = $queue({ * name: "emails", * schema: z.object({ to: z.text(), subject: z.text() }) * }); * * // Then, create a consumer for that queue * emailConsumer = $consumer({ * queue: this.emailQueue, // Reference the queue primitive * handler: async (message) => { } // process email * }); * ``` */ queue: QueuePrimitive<T>; /** * Message handler function that processes individual messages from the queue. * * This function: * - Receives fully typed and validated message payloads from the connected queue * - Runs in the background worker system for non-blocking operation * - Should implement the core business logic for processing this message type * - Can throw errors to trigger the queue's retry mechanisms * - Has access to the full Alepha dependency injection container * - Should be idempotent to handle potential duplicate deliveries * * **Handler Design Guidelines**: * - Keep handlers focused on a single responsibility * - Use proper error handling and meaningful error messages * - Log important processing steps for debugging and monitoring * - Consider transaction boundaries for data consistency * - Make operations idempotent when possible * - Validate business rules within the handler logic * * **Error Handling Strategy**: * - Throw errors for temporary failures that should be retried * - Log and handle permanent failures gracefully * - Use specific error types to control retry behavior * - Consider implementing circuit breakers for external service calls * * @param message - The queue message containing the validated payload * @param message.payload - The typed message data based on the queue's schema * @returns Promise that resolves when processing is complete * * @example * ```ts * handler: async (message) => { * const { userId, action, data } = message.payload; * * try { * // Log processing start * this.logger.info(`Processing ${action} for user ${userId}`); * * // Validate business rules * if (!await this.userService.exists(userId)) { * throw new Error(`User ${userId} not found`); * } * * // Perform the main processing logic * switch (action) { * case "create": * await this.processCreation(userId, data); * break; * case "update": * await this.processUpdate(userId, data); * break; * default: * throw new Error(`Unknown action: ${action}`); * } * * // Log successful completion * this.logger.info(`Successfully processed ${action} for user ${userId}`); * * } catch (error) { * // Log error with context * this.logger.error(`Failed to process ${action} for user ${userId}`, { * error: error.message, * userId, * action, * data * }); * * // Re-throw to trigger queue retry mechanism * throw error; * } * } * ``` */ handler: (message: { payload: Static<T> }) => Promise<void>; } // --------------------------------------------------------------------------------------------------------------------- export class ConsumerPrimitive<T extends TSchema> extends PipelinePrimitive< ConsumerPrimitiveOptions<T> > {} $consumer[KIND] = ConsumerPrimitive;