UNPKG

alepha

Version:

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

217 lines (209 loc) 7.65 kB
import { createPrimitive, KIND, PipelinePrimitive, type PipelinePrimitiveOptions, } from "alepha"; import type { TopicHandler, TopicMessageSchema, TopicPrimitive, } from "./$topic.ts"; /** * Creates a subscriber primitive to listen for messages from a specific topic. * * Provides a dedicated message subscriber that connects to a topic and processes messages * with custom handler logic, enabling scalable pub/sub architectures where multiple * subscribers can react to the same events independently. * * **Key Features** * - Seamless integration with any $topic primitive * - Full type safety inherited from topic schema * - Real-time message delivery when events are published * - Error isolation between subscribers * - Support for multiple independent subscribers per topic * * **Common Use Cases** * - Notification services and audit logging * - Analytics and metrics collection * - Data synchronization and real-time UI updates * * @example * ```ts * class UserActivityService { * userEvents = $topic({ * name: "user-activity", * schema: { * payload: z.object({ * userId: z.text(), * action: z.enum(["login", "logout", "purchase"]), * timestamp: z.number() * }) * } * }); * * activityLogger = $subscriber({ * topic: this.userEvents, * handler: async (message) => { * const { userId, action, timestamp } = message.payload; * await this.auditLogger.log({ * userId, * action, * timestamp * }); * } * }); * * async trackUserLogin(userId: string) { * await this.userEvents.publish({ * userId, * action: "login", * timestamp: Date.now() * }); * } * } * ``` */ export const $subscriber = <T extends TopicMessageSchema>( options: SubscriberPrimitiveOptions<T>, ): SubscriberPrimitive<T> => { return createPrimitive(SubscriberPrimitive<T>, options); }; // --------------------------------------------------------------------------------------------------------------------- export interface SubscriberPrimitiveOptions<T extends TopicMessageSchema> extends PipelinePrimitiveOptions { /** * The topic primitive that this subscriber will listen to for messages. * * This establishes the connection between the subscriber and its source topic: * - The subscriber inherits the topic's message schema for type safety * - Messages published to the topic will be automatically delivered to this subscriber * - Multiple subscribers can listen to the same topic independently * - The subscriber will use the topic's provider and configuration settings * * **Topic Integration Benefits**: * - Type safety: Subscriber handler gets fully typed message payloads * - Schema validation: Messages are validated before reaching the subscriber * - Real-time delivery: Messages are delivered immediately upon publication * - Error isolation: Subscriber errors don't affect the topic or other subscribers * - Monitoring: Topic metrics include subscriber processing statistics * * @example * ```ts * // First, define a topic * userEvents = $topic({ * name: "user-activity", * schema: { * payload: z.object({ userId: z.text(), action: z.text() }) * } * }); * * // Then, create a subscriber for that topic * activitySubscriber = $subscriber({ * topic: this.userEvents, // Reference the topic primitive * handler: async (message) => { } // Process messages here * }); * ``` */ topic: TopicPrimitive<T>; /** * Message handler function that processes individual messages from the topic. * * This function: * - Receives fully typed and validated message payloads from the connected topic * - Executes immediately when messages are published to the topic * - Should implement the core business logic for reacting to these events * - Runs independently of other subscribers to the same topic * - Should handle errors gracefully to avoid affecting other subscribers * - Has access to the full Alepha dependency injection container * * **Handler Design Guidelines**: * - Keep handlers focused on a single responsibility * - Use proper error handling and logging * - Consider performance impact for high-frequency topics * - Make handlers idempotent when possible for reliability * - Validate business rules within the handler logic * - Log important processing steps for debugging and monitoring * * **Error Handling Strategy**: * - Log errors but don't re-throw to avoid affecting other subscribers * - Use try-catch blocks for external service calls * - Implement circuit breakers for resilience with external systems * - Monitor error rates and patterns for system health * - Consider implementing retry logic for temporary failures * * **Performance Considerations**: * - Keep handler execution time minimal for high-throughput topics * - Use background queues for heavy processing triggered by events * - Implement batching for efficiency when processing many similar events * - Consider async processing patterns for non-critical operations * * @param message - The topic message with validated payload and optional headers * @param message.payload - The typed message data based on the topic's schema * @returns Promise that resolves when processing is complete * * @example * ```ts * handler: async (message) => { * const { userId, eventType, timestamp } = message.payload; * * try { * // Log event receipt * this.logger.info(`Processing ${eventType} event for user ${userId}`, { * timestamp, * userId, * eventType * }); * * // Perform event-specific processing * switch (eventType) { * case 'user.login': * await this.updateLastLogin(userId, timestamp); * await this.sendWelcomeNotification(userId); * break; * case 'user.logout': * await this.updateSessionDuration(userId, timestamp); * break; * case 'user.purchase': * await this.updateRewardsPoints(userId, message.payload.purchaseAmount); * await this.triggerRecommendations(userId); * break; * default: * this.logger.warn(`Unknown event type: ${eventType}`); * } * * // Update analytics * await this.analytics.track(eventType, { * userId, * timestamp, * source: 'topic-subscriber' * }); * * this.logger.info(`Successfully processed ${eventType} for user ${userId}`); * * } catch (error) { * // Log error but don't re-throw to avoid affecting other subscribers * this.logger.error(`Failed to process ${eventType} for user ${userId}`, { * error: error.message, * stack: error.stack, * userId, * eventType, * timestamp * }); * * // Optionally send to error tracking service * await this.errorTracker.captureException(error, { * context: { userId, eventType, timestamp }, * tags: { component: 'topic-subscriber' } * }); * } * } * ``` */ handler: TopicHandler<T>; } // --------------------------------------------------------------------------------------------------------------------- export class SubscriberPrimitive< T extends TopicMessageSchema, > extends PipelinePrimitive<SubscriberPrimitiveOptions<T>> {} $subscriber[KIND] = SubscriberPrimitive;