UNPKG

alepha

Version:

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

575 lines 21.4 kB
import { Alepha, AlephaError, KIND, PipelinePrimitive, PipelinePrimitiveOptions, Primitive, Service, Static, TSchema } from "alepha"; import { DateTimeProvider, DurationLike } from "alepha/datetime"; //#region ../../src/topic/core/errors/TopicTimeoutError.d.ts declare class TopicTimeoutError extends AlephaError { readonly topic: string; readonly timeout: number; constructor(topic: string, timeout: number); } //#endregion //#region ../../src/topic/core/providers/TopicProvider.d.ts /** * Base class for topic providers. */ declare abstract class TopicProvider { protected readonly alepha: Alepha; protected readonly log: import("alepha/logger").Logger; protected readonly dateTimeProvider: DateTimeProvider; /** * Publish a raw message to a topic. * * @param topic - The topic to publish to. * @param message - The message to publish. */ abstract publish(topic: string, message: string, options?: TopicPublishOptions): Promise<void>; /** * Subscribe to a topic with a raw callback. * * @param topic - The topic to subscribe to. * @param callback - The callback to call when a message is received. */ abstract subscribe(topic: string, callback: SubscribeCallback, options?: TopicSubscribeOptions): Promise<UnSubscribeFn>; /** * Unsubscribe from a topic. * * @param topic - The topic to unsubscribe from. */ abstract unsubscribe(topic: string): Promise<void>; /** * Encode and publish a typed message to a topic. */ publishMessage<T extends TopicMessageSchema>(name: string, schema: T["payload"], payload: TopicMessage<T>["payload"], options?: TopicPublishOptions): Promise<void>; /** * Parse a raw message string into a typed topic message. */ parseMessage<T extends TopicMessageSchema>(schema: T["payload"], message: string): TopicMessage<T>; /** * Subscribe a typed handler to a topic, with error wrapping and message parsing. */ subscribeHandler<T extends TopicMessageSchema>(name: string, schema: T["payload"], handler: TopicHandler<T>, options?: TopicSubscribeOptions): Promise<UnSubscribeFn>; /** * Wait for a single message matching an optional filter, with timeout. */ waitForMessage<T extends TopicMessageSchema>(name: string, schema: T["payload"], options?: TopicWaitOptions<T>): Promise<TopicMessage<T>>; /** * The wildcard character used for pattern subscriptions. * Override in subclasses for provider-specific wildcards. */ protected wildcardChar(): string; /** * Returns the list of $subscribers for this provider. */ protected subscribers(): Array<() => Promise<unknown>>; } type SubscribeCallback = (message: string, topic?: string) => Promise<void> | void; type UnSubscribeFn = () => Promise<void>; interface TopicPublishOptions { retain?: boolean; params?: Record<string, string>; } interface TopicSubscribeOptions {} //#endregion //#region ../../src/topic/core/primitives/$topic.d.ts /** * Creates a topic primitive for publish/subscribe messaging and event-driven architecture. * * Enables decoupled communication through a pub/sub pattern where publishers send messages * and multiple subscribers receive them. Supports type-safe messages, real-time delivery, * event filtering, and pluggable backends (memory, Redis, custom providers). * * **Use Cases**: User notifications, real-time chat, event broadcasting, microservice communication * * @example * ```ts * class NotificationService { * userActivity = $topic({ * name: "user-activity", * schema: { * payload: z.object({ * userId: z.text(), * action: z.enum(["login", "logout", "purchase"]), * timestamp: z.number() * }) * }, * handler: async (message) => { * console.log(`User ${message.payload.userId}: ${message.payload.action}`); * } * }); * * async trackLogin(userId: string) { * await this.userActivity.publish({ userId, action: "login", timestamp: Date.now() }); * } * * async subscribeToEvents() { * await this.userActivity.subscribe(async (message) => { * // Additional subscriber logic * }); * } * } * ``` */ declare const $topic: { <T extends TopicMessageSchema>(options: TopicPrimitiveOptions<T>): TopicPrimitive<T>; [KIND]: typeof TopicPrimitive; }; interface TopicPrimitiveOptions<T extends TopicMessageSchema> { /** * Unique name identifier for the topic. * * This name is used for: * - Topic identification across the pub/sub system * - Message routing between publishers and subscribers * - Logging and debugging topic-related operations * - Provider-specific topic management (channels, keys, etc.) * * If not provided, defaults to the property key where the topic is declared. * * **Naming Conventions**: * - Use descriptive, hierarchical names: "user.activity", "order.events" * - Avoid spaces and special characters * - Consider using dot notation for categorization * - Keep names concise but meaningful * * @example "user-activity" * @example "chat.messages" * @example "system.health.checks" * @example "payment.webhooks" */ name?: string; /** * Human-readable description of the topic's purpose and usage. * * Used for: * - Documentation generation and API references * - Developer onboarding and understanding * - Monitoring dashboards and admin interfaces * - Team communication about system architecture * * **Description Best Practices**: * - Explain what events/messages this topic handles * - Mention key use cases and subscribers * - Include any important timing or ordering guarantees * - Note any special processing requirements * * @example "Real-time user activity events for analytics and notifications" * @example "Order lifecycle events from creation to delivery" * @example "Chat messages broadcast to all room participants" * @example "System health checks and service status updates" */ description?: string; /** * Topic provider configuration for message storage and delivery. * * Options: * - **"memory"**: In-memory provider (default for development, lost on restart) * - **Service<TopicProvider>**: Custom provider class (e.g., RedisTopicProvider) * - **undefined**: Uses the default topic provider from dependency injection * * **Provider Selection Guidelines**: * - **Development**: Use "memory" for fast, simple testing without external dependencies * - **Production**: Use Redis or message brokers for persistence and scalability * - **Distributed systems**: Use Redis/RabbitMQ for cross-service communication * - **High-throughput**: Use specialized providers with connection pooling * - **Real-time**: Ensure provider supports low-latency message delivery * * **Provider Capabilities**: * - Message persistence and durability * - Subscriber management and connection handling * - Message ordering and delivery guarantees * - Horizontal scaling and load distribution * * @default Uses injected TopicProvider * @example "memory" * @example RedisTopicProvider * @example RabbitMQTopicProvider */ provider?: "memory" | Service<TopicProvider>; /** * TypeBox schema defining the structure of messages published to this topic. * * The schema must include: * - **payload**: Required schema for the main message data * - **headers**: Optional schema for message metadata * * This schema: * - Validates all messages published to the topic * - Provides full TypeScript type inference for subscribers * - Ensures type safety between publishers and subscribers * - Enables automatic serialization/deserialization * * **Schema Design Best Practices**: * - Keep payload schemas focused and cohesive * - Use optional fields for data that might not always be present * - Include timestamp fields for event ordering * - Consider versioning for schema evolution * - Use union types for different event types in the same topic * * @example * ```ts * { * payload: z.object({ * eventId: z.text(), * eventType: z.enum(["created", "updated"]), * data: z.record(z.text(), z.any()), * timestamp: z.number(), * userId: z.text().optional() * }), * headers: z.object({ * source: z.text(), * correlationId: z.text() * }).optional() * } * ``` */ schema: T; /** * Default subscriber handler function that processes messages published to this topic. * * This handler: * - Automatically subscribes when the topic is initialized * - Receives all messages published to the topic * - Runs for every message without additional subscription setup * - Can be supplemented with additional subscribers via `subscribe()` method * - Should handle errors gracefully to avoid breaking other subscribers * * **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 * - Validate business rules within the handler logic * - Log important processing steps for debugging * * **Error Handling Strategy**: * - Log errors but don't re-throw to avoid affecting other subscribers * - Use try-catch blocks for external service calls * - Consider implementing circuit breakers for resilience * - Monitor error rates and patterns for system health * * @param message - The topic message with validated payload and headers * @param message.payload - The typed message data based on the schema * @returns Promise that resolves when processing is complete * * @example * ```ts * handler: async (message) => { * const { eventType, data, timestamp } = message.payload; * * try { * // Log message receipt * this.logger.info(`Processing ${eventType} event`, { timestamp, data }); * * // Process based on event type * switch (eventType) { * case "created": * await this.handleCreation(data); * break; * case "updated": * await this.handleUpdate(data); * break; * default: * this.logger.warn(`Unknown event type: ${eventType}`); * } * * this.logger.info(`Successfully processed ${eventType} event`); * * } catch (error) { * // Log error but don't re-throw to avoid affecting other subscribers * this.logger.error(`Failed to process ${eventType} event`, { * error: error.message, * eventType, * timestamp, * data * }); * } * } * ``` */ handler?: TopicHandler<T>; /** * Whether the last published message should be retained and delivered to new subscribers. * * When enabled, the provider stores the last message for this topic. * New subscribers immediately receive the retained message upon subscribing. * * Supported by Memory, Redis, and MQTT providers. * * @default false */ retain?: boolean; } declare class TopicPrimitive<T extends TopicMessageSchema> extends Primitive<TopicPrimitiveOptions<T>> { readonly provider: TopicProvider; get name(): string; publish(message: T extends { params: TSchema; } ? { params: Static<T["params"]>; payload: TopicMessage<T>["payload"]; } : TopicMessage<T>["payload"]): Promise<void>; subscribe(handler: TopicHandler<T>): Promise<UnSubscribeFn>; wait(options?: TopicWaitOptions<T>): Promise<TopicMessage<T>>; protected $provider(): TopicProvider; } interface TopicMessage<T extends TopicMessageSchema> { payload: Static<T["payload"]>; params: T extends { params: TSchema; } ? Static<T["params"]> : never; } interface TopicWaitOptions<T extends TopicMessageSchema> { timeout?: DurationLike; filter?: (message: { payload: Static<T["payload"]>; }) => boolean; } interface TopicMessageSchema { payload: TSchema; params?: TSchema; } type TopicHandler<T extends TopicMessageSchema = TopicMessageSchema> = (message: TopicMessage<T>) => unknown; //#endregion //#region ../../src/topic/core/primitives/$subscriber.d.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() * }); * } * } * ``` */ declare const $subscriber: { <T extends TopicMessageSchema>(options: SubscriberPrimitiveOptions<T>): SubscriberPrimitive<T>; [KIND]: typeof SubscriberPrimitive; }; 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>; } declare class SubscriberPrimitive<T extends TopicMessageSchema> extends PipelinePrimitive<SubscriberPrimitiveOptions<T>> {} //#endregion //#region ../../src/topic/core/providers/MemoryTopicProvider.d.ts declare class MemoryTopicProvider extends TopicProvider { protected readonly log: import("alepha/logger").Logger; protected readonly subscriptions: Record<string, SubscribeCallback[]>; protected readonly retained: Record<string, string>; protected readonly start: import("alepha").HookPrimitive<"start">; /** * Publish a message to a topic. * * @param topic * @param message * @param options */ publish(topic: string, message: string, options?: TopicPublishOptions): Promise<void>; /** * Subscribe to a topic. * * @param topic - The topic to subscribe to. * @param callback */ subscribe(topic: string, callback: SubscribeCallback, _options?: TopicSubscribeOptions): Promise<UnSubscribeFn>; /** * Unsubscribe from a topic. * * @param topic - The topic to unsubscribe from. */ unsubscribe(topic: string): Promise<void>; /** * Check if a topic matches a subscription pattern. * Supports `+` single-level wildcard. */ protected topicMatches(pattern: string, topic: string): boolean; } //#endregion //#region ../../src/topic/core/index.d.ts /** * Publish/subscribe messaging for event-driven architectures. * * **Features:** * - Pub/sub topics with type-safe messages * - Topic subscription handlers * - Multiple subscriber support * - Message filtering and routing * - Providers: Memory (dev), Redis (production) * * @module alepha.topic */ declare const AlephaTopic: import("alepha").Service<import("alepha").Module>; //#endregion export { $subscriber, $topic, AlephaTopic, MemoryTopicProvider, SubscribeCallback, SubscriberPrimitive, SubscriberPrimitiveOptions, TopicHandler, TopicMessage, TopicMessageSchema, TopicPrimitive, TopicPrimitiveOptions, TopicProvider, TopicPublishOptions, TopicSubscribeOptions, TopicTimeoutError, TopicWaitOptions, UnSubscribeFn }; //# sourceMappingURL=index.d.ts.map