alepha 0.9.4 → 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/topic.d.ts CHANGED
@@ -39,7 +39,246 @@ type UnSubscribeFn = () => Promise<void>;
39
39
  //#endregion
40
40
  //#region src/descriptors/$topic.d.ts
41
41
  /**
42
- * Create a new topic.
42
+ * Creates a topic descriptor for pub/sub messaging and event-driven architecture.
43
+ *
44
+ * This descriptor provides a powerful publish/subscribe system that enables decoupled communication
45
+ * between different parts of your application. Topics allow multiple publishers to send messages
46
+ * and multiple subscribers to receive them, creating flexible event-driven architectures with
47
+ * support for real-time messaging and asynchronous event processing.
48
+ *
49
+ * **Key Features**
50
+ *
51
+ * - **Publish/Subscribe Pattern**: Decoupled communication between publishers and subscribers
52
+ * - **Multiple Subscribers**: One-to-many message distribution with automatic fan-out
53
+ * - **Type-Safe Messages**: Full TypeScript support with schema validation using TypeBox
54
+ * - **Real-time Processing**: Immediate message delivery to active subscribers
55
+ * - **Event Filtering**: Subscribe to specific message types using filter functions
56
+ * - **Timeout Support**: Wait for specific messages with configurable timeouts
57
+ * - **Multiple Backends**: Support for in-memory, Redis, and custom topic providers
58
+ * - **Error Resilience**: Built-in error handling and message processing recovery
59
+ *
60
+ * **Use Cases**
61
+ *
62
+ * Perfect for event-driven architectures and real-time communication:
63
+ * - User activity notifications
64
+ * - Real-time chat and messaging systems
65
+ * - System event broadcasting
66
+ * - Microservice communication
67
+ * - Live data updates and synchronization
68
+ * - Application state change notifications
69
+ * - Webhook and external API event handling
70
+ *
71
+ * @example
72
+ * **Basic topic with publish/subscribe:**
73
+ * ```ts
74
+ * import { $topic } from "alepha/topic";
75
+ * import { t } from "alepha";
76
+ *
77
+ * class NotificationService {
78
+ * userActivity = $topic({
79
+ * name: "user-activity",
80
+ * schema: {
81
+ * payload: t.object({
82
+ * userId: t.string(),
83
+ * action: t.enum(["login", "logout", "purchase"]),
84
+ * timestamp: t.number(),
85
+ * metadata: t.optional(t.record(t.string(), t.any()))
86
+ * })
87
+ * },
88
+ * handler: async (message) => {
89
+ * // This subscriber runs automatically for all messages
90
+ * console.log(`User ${message.payload.userId} performed ${message.payload.action}`);
91
+ * }
92
+ * });
93
+ *
94
+ * async trackUserLogin(userId: string) {
95
+ * // Publish event - all subscribers will receive it
96
+ * await this.userActivity.publish({
97
+ * userId,
98
+ * action: "login",
99
+ * timestamp: Date.now(),
100
+ * metadata: { source: "web", ip: "192.168.1.1" }
101
+ * });
102
+ * }
103
+ *
104
+ * async setupAdditionalSubscriber() {
105
+ * // Add another subscriber dynamically
106
+ * await this.userActivity.subscribe(async (message) => {
107
+ * if (message.payload.action === "purchase") {
108
+ * await this.sendPurchaseConfirmation(message.payload.userId);
109
+ * }
110
+ * });
111
+ * }
112
+ * }
113
+ * ```
114
+ *
115
+ * @example
116
+ * **Real-time chat system with multiple subscribers:**
117
+ * ```ts
118
+ * class ChatService {
119
+ * messagesTopic = $topic({
120
+ * name: "chat-messages",
121
+ * description: "Real-time chat messages for all rooms",
122
+ * schema: {
123
+ * payload: t.object({
124
+ * messageId: t.string(),
125
+ * roomId: t.string(),
126
+ * userId: t.string(),
127
+ * content: t.string(),
128
+ * timestamp: t.number(),
129
+ * messageType: t.enum(["text", "image", "file"])
130
+ * })
131
+ * }
132
+ * });
133
+ *
134
+ * async sendMessage(roomId: string, userId: string, content: string) {
135
+ * await this.messagesTopic.publish({
136
+ * messageId: generateId(),
137
+ * roomId,
138
+ * userId,
139
+ * content,
140
+ * timestamp: Date.now(),
141
+ * messageType: "text"
142
+ * });
143
+ * }
144
+ *
145
+ * // Different services can subscribe to the same topic
146
+ * async setupMessageLogging() {
147
+ * await this.messagesTopic.subscribe(async (message) => {
148
+ * // Log all messages for compliance
149
+ * await this.auditLogger.log({
150
+ * action: "message_sent",
151
+ * roomId: message.payload.roomId,
152
+ * userId: message.payload.userId,
153
+ * timestamp: message.payload.timestamp
154
+ * });
155
+ * });
156
+ * }
157
+ *
158
+ * async setupNotificationService() {
159
+ * await this.messagesTopic.subscribe(async (message) => {
160
+ * // Send push notifications to offline users
161
+ * const offlineUsers = await this.getOfflineUsersInRoom(message.payload.roomId);
162
+ * await this.sendPushNotifications(offlineUsers, {
163
+ * title: `New message in ${message.payload.roomId}`,
164
+ * body: message.payload.content
165
+ * });
166
+ * });
167
+ * }
168
+ * }
169
+ * ```
170
+ *
171
+ * @example
172
+ * **Event filtering and waiting for specific messages:**
173
+ * ```ts
174
+ * class OrderService {
175
+ * orderEvents = $topic({
176
+ * name: "order-events",
177
+ * schema: {
178
+ * payload: t.object({
179
+ * orderId: t.string(),
180
+ * status: t.union([
181
+ * t.literal("created"),
182
+ * t.literal("paid"),
183
+ * t.literal("shipped"),
184
+ * t.literal("delivered"),
185
+ * t.literal("cancelled")
186
+ * ]),
187
+ * timestamp: t.number(),
188
+ * data: t.optional(t.record(t.string(), t.any()))
189
+ * })
190
+ * }
191
+ * });
192
+ *
193
+ * async processOrder(orderId: string) {
194
+ * // Publish order created event
195
+ * await this.orderEvents.publish({
196
+ * orderId,
197
+ * status: "created",
198
+ * timestamp: Date.now()
199
+ * });
200
+ *
201
+ * // Wait for payment confirmation with timeout
202
+ * try {
203
+ * const paymentEvent = await this.orderEvents.wait({
204
+ * timeout: [5, "minutes"],
205
+ * filter: (message) =>
206
+ * message.payload.orderId === orderId &&
207
+ * message.payload.status === "paid"
208
+ * });
209
+ *
210
+ * console.log(`Order ${orderId} was paid at ${paymentEvent.payload.timestamp}`);
211
+ *
212
+ * // Continue with shipping...
213
+ * await this.initiateShipping(orderId);
214
+ *
215
+ * } catch (error) {
216
+ * if (error instanceof TopicTimeoutError) {
217
+ * console.log(`Payment timeout for order ${orderId}`);
218
+ * await this.cancelOrder(orderId);
219
+ * }
220
+ * }
221
+ * }
222
+ *
223
+ * async setupOrderTracking() {
224
+ * // Subscribe only to shipping events
225
+ * await this.orderEvents.subscribe(async (message) => {
226
+ * if (message.payload.status === "shipped") {
227
+ * await this.updateTrackingInfo(message.payload.orderId, message.payload.data);
228
+ * await this.notifyCustomer(message.payload.orderId, "Your order has shipped!");
229
+ * }
230
+ * });
231
+ * }
232
+ * }
233
+ * ```
234
+ *
235
+ * @example
236
+ * **Redis-backed topic for distributed systems:**
237
+ * ```ts
238
+ * class DistributedEventSystem {
239
+ * systemEvents = $topic({
240
+ * name: "system-events",
241
+ * provider: RedisTopicProvider, // Use Redis for cross-service communication
242
+ * schema: {
243
+ * payload: t.object({
244
+ * eventType: t.string(),
245
+ * serviceId: t.string(),
246
+ * data: t.record(t.string(), t.any()),
247
+ * timestamp: t.number(),
248
+ * correlationId: t.optional(t.string())
249
+ * })
250
+ * },
251
+ * handler: async (message) => {
252
+ * // Central event handler for all system events
253
+ * await this.processSystemEvent(message.payload);
254
+ * }
255
+ * });
256
+ *
257
+ * async publishServiceHealth(serviceId: string, healthy: boolean) {
258
+ * await this.systemEvents.publish({
259
+ * eventType: "service.health",
260
+ * serviceId,
261
+ * data: { healthy, checkedAt: new Date().toISOString() },
262
+ * timestamp: Date.now()
263
+ * });
264
+ * }
265
+ *
266
+ * async setupHealthMonitoring() {
267
+ * await this.systemEvents.subscribe(async (message) => {
268
+ * if (message.payload.eventType === "service.health") {
269
+ * await this.updateServiceStatus(
270
+ * message.payload.serviceId,
271
+ * message.payload.data.healthy
272
+ * );
273
+ *
274
+ * if (!message.payload.data.healthy) {
275
+ * await this.alertOnCall(`Service ${message.payload.serviceId} is down`);
276
+ * }
277
+ * }
278
+ * });
279
+ * }
280
+ * }
281
+ * ```
43
282
  */
44
283
  declare const $topic: {
45
284
  <T extends TopicMessageSchema>(options: TopicDescriptorOptions<T>): TopicDescriptor<T>;
@@ -47,29 +286,176 @@ declare const $topic: {
47
286
  };
48
287
  interface TopicDescriptorOptions<T extends TopicMessageSchema> {
49
288
  /**
50
- * Topic key.
289
+ * Unique name identifier for the topic.
290
+ *
291
+ * This name is used for:
292
+ * - Topic identification across the pub/sub system
293
+ * - Message routing between publishers and subscribers
294
+ * - Logging and debugging topic-related operations
295
+ * - Provider-specific topic management (channels, keys, etc.)
51
296
  *
52
- * If not provided, the propertyKey is used as the topic name.
297
+ * If not provided, defaults to the property key where the topic is declared.
298
+ *
299
+ * **Naming Conventions**:
300
+ * - Use descriptive, hierarchical names: "user.activity", "order.events"
301
+ * - Avoid spaces and special characters
302
+ * - Consider using dot notation for categorization
303
+ * - Keep names concise but meaningful
304
+ *
305
+ * @example "user-activity"
306
+ * @example "chat.messages"
307
+ * @example "system.health.checks"
308
+ * @example "payment.webhooks"
53
309
  */
54
310
  name?: string;
55
311
  /**
56
- * Describe the topic. For documentation purposes.
312
+ * Human-readable description of the topic's purpose and usage.
313
+ *
314
+ * Used for:
315
+ * - Documentation generation and API references
316
+ * - Developer onboarding and understanding
317
+ * - Monitoring dashboards and admin interfaces
318
+ * - Team communication about system architecture
319
+ *
320
+ * **Description Best Practices**:
321
+ * - Explain what events/messages this topic handles
322
+ * - Mention key use cases and subscribers
323
+ * - Include any important timing or ordering guarantees
324
+ * - Note any special processing requirements
325
+ *
326
+ * @example "Real-time user activity events for analytics and notifications"
327
+ * @example "Order lifecycle events from creation to delivery"
328
+ * @example "Chat messages broadcast to all room participants"
329
+ * @example "System health checks and service status updates"
57
330
  */
58
331
  description?: string;
59
332
  /**
60
- * Override the default topic provider.
333
+ * Topic provider configuration for message storage and delivery.
334
+ *
335
+ * Options:
336
+ * - **"memory"**: In-memory provider (default for development, lost on restart)
337
+ * - **Service<TopicProvider>**: Custom provider class (e.g., RedisTopicProvider)
338
+ * - **undefined**: Uses the default topic provider from dependency injection
339
+ *
340
+ * **Provider Selection Guidelines**:
341
+ * - **Development**: Use "memory" for fast, simple testing without external dependencies
342
+ * - **Production**: Use Redis or message brokers for persistence and scalability
343
+ * - **Distributed systems**: Use Redis/RabbitMQ for cross-service communication
344
+ * - **High-throughput**: Use specialized providers with connection pooling
345
+ * - **Real-time**: Ensure provider supports low-latency message delivery
61
346
  *
62
- * If not provided, the default provider is used.
63
- * If "memory" is provided, the default in-memory provider is used.
64
- * If a class is provided, it must extend `TopicProvider`.
347
+ * **Provider Capabilities**:
348
+ * - Message persistence and durability
349
+ * - Subscriber management and connection handling
350
+ * - Message ordering and delivery guarantees
351
+ * - Horizontal scaling and load distribution
352
+ *
353
+ * @default Uses injected TopicProvider
354
+ * @example "memory"
355
+ * @example RedisTopicProvider
356
+ * @example RabbitMQTopicProvider
65
357
  */
66
358
  provider?: "memory" | Service<TopicProvider>;
67
359
  /**
68
- * Topic message schema.
360
+ * TypeBox schema defining the structure of messages published to this topic.
361
+ *
362
+ * The schema must include:
363
+ * - **payload**: Required schema for the main message data
364
+ * - **headers**: Optional schema for message metadata
365
+ *
366
+ * This schema:
367
+ * - Validates all messages published to the topic
368
+ * - Provides full TypeScript type inference for subscribers
369
+ * - Ensures type safety between publishers and subscribers
370
+ * - Enables automatic serialization/deserialization
371
+ *
372
+ * **Schema Design Best Practices**:
373
+ * - Keep payload schemas focused and cohesive
374
+ * - Use optional fields for data that might not always be present
375
+ * - Include timestamp fields for event ordering
376
+ * - Consider versioning for schema evolution
377
+ * - Use union types for different event types in the same topic
378
+ *
379
+ * @example
380
+ * ```ts
381
+ * {
382
+ * payload: t.object({
383
+ * eventId: t.string(),
384
+ * eventType: t.enum(["created", "updated"]),
385
+ * data: t.record(t.string(), t.any()),
386
+ * timestamp: t.number(),
387
+ * userId: t.optional(t.string())
388
+ * }),
389
+ * headers: t.optional(t.object({
390
+ * source: t.string(),
391
+ * correlationId: t.string()
392
+ * }))
393
+ * }
394
+ * ```
69
395
  */
70
396
  schema: T;
71
397
  /**
72
- * Add a subscriber handler.
398
+ * Default subscriber handler function that processes messages published to this topic.
399
+ *
400
+ * This handler:
401
+ * - Automatically subscribes when the topic is initialized
402
+ * - Receives all messages published to the topic
403
+ * - Runs for every message without additional subscription setup
404
+ * - Can be supplemented with additional subscribers via `subscribe()` method
405
+ * - Should handle errors gracefully to avoid breaking other subscribers
406
+ *
407
+ * **Handler Design Guidelines**:
408
+ * - Keep handlers focused on a single responsibility
409
+ * - Use proper error handling and logging
410
+ * - Consider performance impact for high-frequency topics
411
+ * - Make handlers idempotent when possible
412
+ * - Validate business rules within the handler logic
413
+ * - Log important processing steps for debugging
414
+ *
415
+ * **Error Handling Strategy**:
416
+ * - Log errors but don't re-throw to avoid affecting other subscribers
417
+ * - Use try-catch blocks for external service calls
418
+ * - Consider implementing circuit breakers for resilience
419
+ * - Monitor error rates and patterns for system health
420
+ *
421
+ * @param message - The topic message with validated payload and headers
422
+ * @param message.payload - The typed message data based on the schema
423
+ * @returns Promise that resolves when processing is complete
424
+ *
425
+ * @example
426
+ * ```ts
427
+ * handler: async (message) => {
428
+ * const { eventType, data, timestamp } = message.payload;
429
+ *
430
+ * try {
431
+ * // Log message receipt
432
+ * this.logger.info(`Processing ${eventType} event`, { timestamp, data });
433
+ *
434
+ * // Process based on event type
435
+ * switch (eventType) {
436
+ * case "created":
437
+ * await this.handleCreation(data);
438
+ * break;
439
+ * case "updated":
440
+ * await this.handleUpdate(data);
441
+ * break;
442
+ * default:
443
+ * this.logger.warn(`Unknown event type: ${eventType}`);
444
+ * }
445
+ *
446
+ * this.logger.info(`Successfully processed ${eventType} event`);
447
+ *
448
+ * } catch (error) {
449
+ * // Log error but don't re-throw to avoid affecting other subscribers
450
+ * this.logger.error(`Failed to process ${eventType} event`, {
451
+ * error: error.message,
452
+ * eventType,
453
+ * timestamp,
454
+ * data
455
+ * });
456
+ * }
457
+ * }
458
+ * ```
73
459
  */
74
460
  handler?: TopicHandler<T>;
75
461
  }
@@ -94,21 +480,426 @@ interface TopicWaitOptions<T extends TopicMessageSchema> {
94
480
  }) => boolean;
95
481
  }
96
482
  interface TopicMessageSchema {
97
- headers?: TSchema;
98
483
  payload: TSchema;
99
484
  }
100
485
  type TopicHandler<T extends TopicMessageSchema = TopicMessageSchema> = (message: TopicMessage<T>) => unknown;
101
486
  //#endregion
102
487
  //#region src/descriptors/$subscriber.d.ts
103
488
  /**
104
- * Subscribe to a $topic.
489
+ * Creates a subscriber descriptor to listen for messages from a specific topic.
490
+ *
491
+ * This descriptor creates a dedicated message subscriber that connects to a topic and processes
492
+ * its messages using a custom handler function. Subscribers provide a clean way to separate
493
+ * message publishing from consumption, enabling scalable pub/sub architectures where multiple
494
+ * subscribers can react to the same events independently.
495
+ *
496
+ * ## Key Features
497
+ *
498
+ * - **Topic Integration**: Seamlessly connects to any $topic descriptor
499
+ * - **Type Safety**: Full TypeScript support inherited from the connected topic's schema
500
+ * - **Dedicated Processing**: Isolated message processing logic separate from the topic
501
+ * - **Real-time Processing**: Immediate message delivery when events are published
502
+ * - **Error Isolation**: Subscriber errors don't affect other subscribers or the topic
503
+ * - **Scalability**: Multiple subscribers can listen to the same topic independently
504
+ *
505
+ * ## Use Cases
506
+ *
507
+ * Perfect for creating specialized event handlers:
508
+ * - Notification services for user events
509
+ * - Analytics and logging systems
510
+ * - Data synchronization between services
511
+ * - Real-time UI updates
512
+ * - Event-driven workflow triggers
513
+ * - Audit and compliance logging
514
+ *
515
+ * @example
516
+ * **Basic subscriber setup:**
517
+ * ```ts
518
+ * import { $topic, $subscriber } from "alepha/topic";
519
+ * import { t } from "alepha";
520
+ *
521
+ * class UserActivityService {
522
+ * // Define the topic
523
+ * userEvents = $topic({
524
+ * name: "user-activity",
525
+ * schema: {
526
+ * payload: t.object({
527
+ * userId: t.string(),
528
+ * action: t.enum(["login", "logout", "purchase"]),
529
+ * timestamp: t.number(),
530
+ * metadata: t.optional(t.record(t.string(), t.any()))
531
+ * })
532
+ * }
533
+ * });
534
+ *
535
+ * // Create a dedicated subscriber for this topic
536
+ * activityLogger = $subscriber({
537
+ * topic: this.userEvents,
538
+ * handler: async (message) => {
539
+ * const { userId, action, timestamp } = message.payload;
540
+ *
541
+ * await this.auditLogger.log({
542
+ * event: 'user_activity',
543
+ * userId,
544
+ * action,
545
+ * timestamp,
546
+ * source: 'user-activity-topic'
547
+ * });
548
+ *
549
+ * this.log.info(`User ${userId} performed ${action} at ${new Date(timestamp).toISOString()}`);
550
+ * }
551
+ * });
552
+ *
553
+ * async trackUserLogin(userId: string, metadata: Record<string, any>) {
554
+ * // Publish to topic - subscriber will automatically process it
555
+ * await this.userEvents.publish({
556
+ * userId,
557
+ * action: "login",
558
+ * timestamp: Date.now(),
559
+ * metadata
560
+ * });
561
+ * }
562
+ * }
563
+ * ```
564
+ *
565
+ * @example
566
+ * **Multiple specialized subscribers for different concerns:**
567
+ * ```ts
568
+ * class OrderEventHandlers {
569
+ * orderEvents = $topic({
570
+ * name: "order-events",
571
+ * schema: {
572
+ * payload: t.object({
573
+ * orderId: t.string(),
574
+ * customerId: t.string(),
575
+ * status: t.union([
576
+ * t.literal("created"),
577
+ * t.literal("paid"),
578
+ * t.literal("shipped"),
579
+ * t.literal("delivered")
580
+ * ]),
581
+ * data: t.optional(t.record(t.string(), t.any()))
582
+ * })
583
+ * }
584
+ * });
585
+ *
586
+ * // Analytics subscriber
587
+ * analyticsSubscriber = $subscriber({
588
+ * topic: this.orderEvents,
589
+ * handler: async (message) => {
590
+ * await this.analytics.track('order_status_changed', {
591
+ * orderId: message.payload.orderId,
592
+ * customerId: message.payload.customerId,
593
+ * status: message.payload.status,
594
+ * timestamp: Date.now()
595
+ * });
596
+ * }
597
+ * });
598
+ *
599
+ * // Email notification subscriber
600
+ * emailSubscriber = $subscriber({
601
+ * topic: this.orderEvents,
602
+ * handler: async (message) => {
603
+ * const { customerId, orderId, status } = message.payload;
604
+ *
605
+ * const templates = {
606
+ * 'paid': 'order-confirmation',
607
+ * 'shipped': 'order-shipped',
608
+ * 'delivered': 'order-delivered'
609
+ * };
610
+ *
611
+ * const template = templates[status];
612
+ * if (template) {
613
+ * await this.emailService.send({
614
+ * customerId,
615
+ * template,
616
+ * data: { orderId, status }
617
+ * });
618
+ * }
619
+ * }
620
+ * });
621
+ *
622
+ * // Inventory management subscriber
623
+ * inventorySubscriber = $subscriber({
624
+ * topic: this.orderEvents,
625
+ * handler: async (message) => {
626
+ * if (message.payload.status === 'paid') {
627
+ * await this.inventoryService.reserveItems(message.payload.orderId);
628
+ * } else if (message.payload.status === 'delivered') {
629
+ * await this.inventoryService.confirmDelivery(message.payload.orderId);
630
+ * }
631
+ * }
632
+ * });
633
+ * }
634
+ * ```
635
+ *
636
+ * @example
637
+ * **Subscriber with advanced error handling and filtering:**
638
+ * ```ts
639
+ * class NotificationSubscriber {
640
+ * systemEvents = $topic({
641
+ * name: "system-events",
642
+ * schema: {
643
+ * payload: t.object({
644
+ * eventType: t.string(),
645
+ * severity: t.enum(["info", "warning", "error"]),
646
+ * serviceId: t.string(),
647
+ * message: t.string(),
648
+ * data: t.optional(t.record(t.string(), t.any()))
649
+ * })
650
+ * }
651
+ * });
652
+ *
653
+ * alertSubscriber = $subscriber({
654
+ * topic: this.systemEvents,
655
+ * handler: async (message) => {
656
+ * const { eventType, severity, serviceId, message: eventMessage, data } = message.payload;
657
+ *
658
+ * try {
659
+ * // Only process error events for alerting
660
+ * if (severity !== 'error') {
661
+ * return;
662
+ * }
663
+ *
664
+ * // Log the event
665
+ * this.logger.error(`System alert from ${serviceId}`, {
666
+ * eventType,
667
+ * message: eventMessage,
668
+ * data
669
+ * });
670
+ *
671
+ * // Send alerts based on service criticality
672
+ * const criticalServices = ['payment', 'auth', 'database'];
673
+ * const isCritical = criticalServices.includes(serviceId);
674
+ *
675
+ * if (isCritical) {
676
+ * // Immediate alert for critical services
677
+ * await this.alertService.sendImmediate({
678
+ * title: `Critical Error in ${serviceId}`,
679
+ * message: eventMessage,
680
+ * severity: 'critical',
681
+ * metadata: { eventType, serviceId, data }
682
+ * });
683
+ * } else {
684
+ * // Queue non-critical alerts for batching
685
+ * await this.alertService.queueAlert({
686
+ * title: `Error in ${serviceId}`,
687
+ * message: eventMessage,
688
+ * severity: 'error',
689
+ * metadata: { eventType, serviceId, data }
690
+ * });
691
+ * }
692
+ *
693
+ * // Update service health status
694
+ * await this.healthMonitor.recordError(serviceId, eventType);
695
+ *
696
+ * } catch (error) {
697
+ * // Log subscriber errors but don't re-throw
698
+ * // This prevents one failing subscriber from affecting others
699
+ * this.log.error(`Alert subscriber failed`, {
700
+ * originalEvent: { eventType, serviceId, severity },
701
+ * subscriberError: error.message
702
+ * });
703
+ * }
704
+ * }
705
+ * });
706
+ * }
707
+ * ```
708
+ *
709
+ * @example
710
+ * **Subscriber for real-time data aggregation:**
711
+ * ```ts
712
+ * class MetricsAggregator {
713
+ * userActivityTopic = $topic({
714
+ * name: "user-metrics",
715
+ * schema: {
716
+ * payload: t.object({
717
+ * userId: t.string(),
718
+ * sessionId: t.string(),
719
+ * eventType: t.string(),
720
+ * timestamp: t.number(),
721
+ * duration: t.optional(t.number()),
722
+ * metadata: t.optional(t.record(t.string(), t.any()))
723
+ * })
724
+ * }
725
+ * });
726
+ *
727
+ * metricsSubscriber = $subscriber({
728
+ * topic: this.userActivityTopic,
729
+ * handler: async (message) => {
730
+ * const { userId, sessionId, eventType, timestamp, duration, metadata } = message.payload;
731
+ *
732
+ * // Update real-time metrics
733
+ * await Promise.all([
734
+ * // Update user activity counters
735
+ * this.metricsStore.increment(`user:${userId}:events:${eventType}`, 1),
736
+ * this.metricsStore.increment(`global:events:${eventType}`, 1),
737
+ *
738
+ * // Track session activity
739
+ * this.sessionStore.updateActivity(sessionId, timestamp),
740
+ *
741
+ * // Record duration metrics if provided
742
+ * duration ? this.metricsStore.recordDuration(`events:${eventType}:duration`, duration) : Promise.resolve(),
743
+ *
744
+ * // Update time-based aggregations
745
+ * this.timeSeriesStore.addPoint({
746
+ * metric: `user_activity.${eventType}`,
747
+ * timestamp,
748
+ * value: 1,
749
+ * tags: { userId, sessionId }
750
+ * })
751
+ * ]);
752
+ *
753
+ * // Trigger real-time dashboard updates
754
+ * await this.dashboardService.updateRealTimeStats({
755
+ * eventType,
756
+ * userId,
757
+ * timestamp
758
+ * });
759
+ *
760
+ * this.logger.debug(`Processed metrics for ${eventType}`, {
761
+ * userId,
762
+ * eventType,
763
+ * timestamp
764
+ * });
765
+ * }
766
+ * });
767
+ * }
768
+ * ```
105
769
  */
106
770
  declare const $subscriber: {
107
771
  <T extends TopicMessageSchema>(options: SubscriberDescriptorOptions<T>): SubscriberDescriptor<T>;
108
772
  [KIND]: typeof SubscriberDescriptor;
109
773
  };
110
774
  interface SubscriberDescriptorOptions<T extends TopicMessageSchema> {
775
+ /**
776
+ * The topic descriptor that this subscriber will listen to for messages.
777
+ *
778
+ * This establishes the connection between the subscriber and its source topic:
779
+ * - The subscriber inherits the topic's message schema for type safety
780
+ * - Messages published to the topic will be automatically delivered to this subscriber
781
+ * - Multiple subscribers can listen to the same topic independently
782
+ * - The subscriber will use the topic's provider and configuration settings
783
+ *
784
+ * **Topic Integration Benefits**:
785
+ * - Type safety: Subscriber handler gets fully typed message payloads
786
+ * - Schema validation: Messages are validated before reaching the subscriber
787
+ * - Real-time delivery: Messages are delivered immediately upon publication
788
+ * - Error isolation: Subscriber errors don't affect the topic or other subscribers
789
+ * - Monitoring: Topic metrics include subscriber processing statistics
790
+ *
791
+ * @example
792
+ * ```ts
793
+ * // First, define a topic
794
+ * userEvents = $topic({
795
+ * name: "user-activity",
796
+ * schema: {
797
+ * payload: t.object({ userId: t.string(), action: t.string() })
798
+ * }
799
+ * });
800
+ *
801
+ * // Then, create a subscriber for that topic
802
+ * activitySubscriber = $subscriber({
803
+ * topic: this.userEvents, // Reference the topic descriptor
804
+ * handler: async (message) => { } // Process messages here
805
+ * });
806
+ * ```
807
+ */
111
808
  topic: TopicDescriptor<T>;
809
+ /**
810
+ * Message handler function that processes individual messages from the topic.
811
+ *
812
+ * This function:
813
+ * - Receives fully typed and validated message payloads from the connected topic
814
+ * - Executes immediately when messages are published to the topic
815
+ * - Should implement the core business logic for reacting to these events
816
+ * - Runs independently of other subscribers to the same topic
817
+ * - Should handle errors gracefully to avoid affecting other subscribers
818
+ * - Has access to the full Alepha dependency injection container
819
+ *
820
+ * **Handler Design Guidelines**:
821
+ * - Keep handlers focused on a single responsibility
822
+ * - Use proper error handling and logging
823
+ * - Consider performance impact for high-frequency topics
824
+ * - Make handlers idempotent when possible for reliability
825
+ * - Validate business rules within the handler logic
826
+ * - Log important processing steps for debugging and monitoring
827
+ *
828
+ * **Error Handling Strategy**:
829
+ * - Log errors but don't re-throw to avoid affecting other subscribers
830
+ * - Use try-catch blocks for external service calls
831
+ * - Implement circuit breakers for resilience with external systems
832
+ * - Monitor error rates and patterns for system health
833
+ * - Consider implementing retry logic for temporary failures
834
+ *
835
+ * **Performance Considerations**:
836
+ * - Keep handler execution time minimal for high-throughput topics
837
+ * - Use background queues for heavy processing triggered by events
838
+ * - Implement batching for efficiency when processing many similar events
839
+ * - Consider async processing patterns for non-critical operations
840
+ *
841
+ * @param message - The topic message with validated payload and optional headers
842
+ * @param message.payload - The typed message data based on the topic's schema
843
+ * @returns Promise that resolves when processing is complete
844
+ *
845
+ * @example
846
+ * ```ts
847
+ * handler: async (message) => {
848
+ * const { userId, eventType, timestamp } = message.payload;
849
+ *
850
+ * try {
851
+ * // Log event receipt
852
+ * this.logger.info(`Processing ${eventType} event for user ${userId}`, {
853
+ * timestamp,
854
+ * userId,
855
+ * eventType
856
+ * });
857
+ *
858
+ * // Perform event-specific processing
859
+ * switch (eventType) {
860
+ * case 'user.login':
861
+ * await this.updateLastLogin(userId, timestamp);
862
+ * await this.sendWelcomeNotification(userId);
863
+ * break;
864
+ * case 'user.logout':
865
+ * await this.updateSessionDuration(userId, timestamp);
866
+ * break;
867
+ * case 'user.purchase':
868
+ * await this.updateRewardsPoints(userId, message.payload.purchaseAmount);
869
+ * await this.triggerRecommendations(userId);
870
+ * break;
871
+ * default:
872
+ * this.logger.warn(`Unknown event type: ${eventType}`);
873
+ * }
874
+ *
875
+ * // Update analytics
876
+ * await this.analytics.track(eventType, {
877
+ * userId,
878
+ * timestamp,
879
+ * source: 'topic-subscriber'
880
+ * });
881
+ *
882
+ * this.logger.info(`Successfully processed ${eventType} for user ${userId}`);
883
+ *
884
+ * } catch (error) {
885
+ * // Log error but don't re-throw to avoid affecting other subscribers
886
+ * this.logger.error(`Failed to process ${eventType} for user ${userId}`, {
887
+ * error: error.message,
888
+ * stack: error.stack,
889
+ * userId,
890
+ * eventType,
891
+ * timestamp
892
+ * });
893
+ *
894
+ * // Optionally send to error tracking service
895
+ * await this.errorTracker.captureException(error, {
896
+ * context: { userId, eventType, timestamp },
897
+ * tags: { component: 'topic-subscriber' }
898
+ * });
899
+ * }
900
+ * }
901
+ * ```
902
+ */
112
903
  handler: TopicHandler<T>;
113
904
  }
114
905
  declare class SubscriberDescriptor<T extends TopicMessageSchema> extends Descriptor<SubscriberDescriptorOptions<T>> {}