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/queue/redis.d.ts CHANGED
@@ -2,11 +2,11 @@ import * as _alepha_core0 from "alepha";
2
2
  import { Static } from "alepha";
3
3
  import { QueueProvider } from "alepha/queue";
4
4
  import { RedisProvider } from "alepha/redis";
5
- import * as _sinclair_typebox0 from "@sinclair/typebox";
5
+ import * as typebox0 from "typebox";
6
6
 
7
7
  //#region src/providers/RedisQueueProvider.d.ts
8
- declare const envSchema: _sinclair_typebox0.TObject<{
9
- REDIS_QUEUE_PREFIX: _sinclair_typebox0.TString;
8
+ declare const envSchema: typebox0.TObject<{
9
+ REDIS_QUEUE_PREFIX: typebox0.TString;
10
10
  }>;
11
11
  declare class RedisQueueProvider implements QueueProvider {
12
12
  protected readonly env: Static<typeof envSchema>;
package/queue.d.ts CHANGED
@@ -40,16 +40,16 @@ declare const envSchema: _alepha_core1.TObject<{
40
40
  /**
41
41
  * The interval in milliseconds to wait before checking for new messages.
42
42
  */
43
- QUEUE_WORKER_INTERVAL: _alepha_core1.TNumber;
43
+ QUEUE_WORKER_INTERVAL: _alepha_core1.TInteger;
44
44
  /**
45
45
  * The maximum interval in milliseconds to wait before checking for new messages.
46
46
  */
47
- QUEUE_WORKER_MAX_INTERVAL: _alepha_core1.TNumber;
47
+ QUEUE_WORKER_MAX_INTERVAL: _alepha_core1.TInteger;
48
48
  /**
49
49
  * The number of workers to run concurrently. Defaults to 1.
50
50
  * Useful only if you are doing a lot of I/O.
51
51
  */
52
- QUEUE_WORKER_CONCURRENCY: _alepha_core1.TNumber;
52
+ QUEUE_WORKER_CONCURRENCY: _alepha_core1.TInteger;
53
53
  }>;
54
54
  declare module "alepha" {
55
55
  interface Env extends Partial<Static<typeof envSchema>> {}
@@ -65,10 +65,11 @@ declare class WorkerProvider {
65
65
  protected readonly queueProvider: QueueProvider;
66
66
  protected readonly dateTimeProvider: DateTimeProvider;
67
67
  protected workerPromises: Array<Promise<void>>;
68
- protected isWorkersRunning: boolean;
68
+ protected workersRunning: number;
69
69
  protected abortController: AbortController;
70
70
  protected workerIntervals: Record<number, number>;
71
71
  protected consumers: Array<Consumer>;
72
+ get isRunning(): boolean;
72
73
  protected readonly start: _alepha_core1.HookDescriptor<"start">;
73
74
  /**
74
75
  * Start the workers.
@@ -100,7 +101,7 @@ declare class WorkerProvider {
100
101
  */
101
102
  protected stopWorkers(): Promise<void>;
102
103
  /**
103
- * Force the workers to get back to work. zug zug!
104
+ * Force the workers to get back to work.
104
105
  */
105
106
  wakeUp(): void;
106
107
  }
@@ -115,17 +116,238 @@ interface NextMessage {
115
116
  //#endregion
116
117
  //#region src/descriptors/$queue.d.ts
117
118
  /**
118
- * Create a new queue.
119
+ * Creates a queue descriptor for asynchronous message processing with background workers.
120
+ *
121
+ * The $queue descriptor enables powerful asynchronous communication patterns in your application.
122
+ * It provides type-safe message queuing with automatic worker processing, making it perfect for
123
+ * decoupling components and handling background tasks efficiently.
124
+ *
125
+ * **Background Processing**
126
+ * - Automatic worker threads for non-blocking message processing
127
+ * - Built-in retry mechanisms and error handling
128
+ * - Dead letter queues for failed message handling
129
+ * - Graceful shutdown and worker lifecycle management
130
+ *
131
+ * **Type Safety**
132
+ * - Full TypeScript support with schema validation using TypeBox
133
+ * - Type-safe message payloads with automatic inference
134
+ * - Runtime validation of all queued messages
135
+ * - Compile-time errors for invalid message structures
136
+ *
137
+ * **Storage Flexibility**
138
+ * - Memory provider for development and testing
139
+ * - Redis provider for production scalability and persistence
140
+ * - Custom provider support for specialized backends
141
+ * - Automatic failover and connection pooling
142
+ *
143
+ * **Performance & Scalability**
144
+ * - Batch processing support for high-throughput scenarios
145
+ * - Horizontal scaling with distributed queue backends
146
+ * - Configurable concurrency and worker pools
147
+ * - Efficient serialization and message routing
148
+ *
149
+ * **Reliability**
150
+ * - Message persistence across application restarts
151
+ * - Automatic retry with exponential backoff
152
+ * - Dead letter handling for permanently failed messages
153
+ * - Comprehensive logging and monitoring integration
154
+ *
155
+ * @example Basic notification queue
156
+ * ```typescript
157
+ * const emailQueue = $queue({
158
+ * name: "email-notifications",
159
+ * schema: t.object({
160
+ * to: t.string(),
161
+ * subject: t.string(),
162
+ * body: t.string(),
163
+ * priority: t.optional(t.enum(["high", "normal"]))
164
+ * }),
165
+ * handler: async (message) => {
166
+ * await emailService.send(message.payload);
167
+ * console.log(`Email sent to ${message.payload.to}`);
168
+ * }
169
+ * });
170
+ *
171
+ * // Push messages for background processing
172
+ * await emailQueue.push({
173
+ * to: "user@example.com",
174
+ * subject: "Welcome!",
175
+ * body: "Welcome to our platform",
176
+ * priority: "high"
177
+ * });
178
+ * ```
179
+ *
180
+ * @example Batch processing with Redis
181
+ * ```typescript
182
+ * const imageQueue = $queue({
183
+ * name: "image-processing",
184
+ * provider: RedisQueueProvider,
185
+ * schema: t.object({
186
+ * imageId: t.string(),
187
+ * operations: t.array(t.enum(["resize", "compress", "thumbnail"]))
188
+ * }),
189
+ * handler: async (message) => {
190
+ * for (const op of message.payload.operations) {
191
+ * await processImage(message.payload.imageId, op);
192
+ * }
193
+ * }
194
+ * });
195
+ *
196
+ * // Batch processing multiple images
197
+ * await imageQueue.push(
198
+ * { imageId: "img1", operations: ["resize", "thumbnail"] },
199
+ * { imageId: "img2", operations: ["compress"] },
200
+ * { imageId: "img3", operations: ["resize", "compress", "thumbnail"] }
201
+ * );
202
+ * ```
203
+ *
204
+ * @example Development with memory provider
205
+ * ```typescript
206
+ * const taskQueue = $queue({
207
+ * name: "dev-tasks",
208
+ * provider: "memory",
209
+ * schema: t.object({
210
+ * taskType: t.enum(["cleanup", "backup", "report"]),
211
+ * data: t.record(t.string(), t.any())
212
+ * }),
213
+ * handler: async (message) => {
214
+ * switch (message.payload.taskType) {
215
+ * case "cleanup":
216
+ * await performCleanup(message.payload.data);
217
+ * break;
218
+ * case "backup":
219
+ * await createBackup(message.payload.data);
220
+ * break;
221
+ * case "report":
222
+ * await generateReport(message.payload.data);
223
+ * break;
224
+ * }
225
+ * }
226
+ * });
227
+ * ```
119
228
  */
120
229
  declare const $queue: {
121
230
  <T extends TSchema>(options: QueueDescriptorOptions<T>): QueueDescriptor<T>;
122
231
  [KIND]: typeof QueueDescriptor;
123
232
  };
124
233
  interface QueueDescriptorOptions<T extends TSchema> {
234
+ /**
235
+ * Unique name for the queue.
236
+ *
237
+ * This name is used for:
238
+ * - Queue identification across the system
239
+ * - Storage backend key generation
240
+ * - Logging and monitoring
241
+ * - Worker assignment and routing
242
+ *
243
+ * If not provided, defaults to the property key where the queue is declared.
244
+ *
245
+ * @example "email-notifications"
246
+ * @example "image-processing"
247
+ * @example "order-fulfillment"
248
+ */
125
249
  name?: string;
250
+ /**
251
+ * Human-readable description of the queue's purpose.
252
+ *
253
+ * Used for:
254
+ * - Documentation generation
255
+ * - Monitoring dashboards
256
+ * - Development team communication
257
+ * - Queue management interfaces
258
+ *
259
+ * @example "Process user registration emails and welcome sequences"
260
+ * @example "Handle image uploads, resizing, and thumbnail generation"
261
+ * @example "Manage order processing, payment, and shipping workflows"
262
+ */
126
263
  description?: string;
264
+ /**
265
+ * Queue storage provider configuration.
266
+ *
267
+ * Options:
268
+ * - **"memory"**: In-memory queue (default for development, lost on restart)
269
+ * - **Service<QueueProvider>**: Custom provider class (e.g., RedisQueueProvider)
270
+ * - **undefined**: Uses the default queue provider from dependency injection
271
+ *
272
+ * **Provider Selection Guidelines**:
273
+ * - Development: Use "memory" for fast, simple testing
274
+ * - Production: Use Redis or database-backed providers for persistence
275
+ * - High-throughput: Use specialized providers with connection pooling
276
+ * - Distributed systems: Use Redis or message brokers for scalability
277
+ *
278
+ * @default Uses injected QueueProvider
279
+ * @example "memory"
280
+ * @example RedisQueueProvider
281
+ * @example DatabaseQueueProvider
282
+ */
127
283
  provider?: "memory" | Service<QueueProvider>;
284
+ /**
285
+ * TypeBox schema defining the structure of messages in this queue.
286
+ *
287
+ * This schema:
288
+ * - Validates all messages pushed to the queue
289
+ * - Provides full TypeScript type inference
290
+ * - Ensures type safety between producers and consumers
291
+ * - Enables automatic serialization/deserialization
292
+ *
293
+ * **Schema Design Best Practices**:
294
+ * - Keep schemas simple and focused on the specific task
295
+ * - Use optional fields for data that might not always be available
296
+ * - Include version fields for schema evolution
297
+ * - Use union types for different message types in the same queue
298
+ *
299
+ * @example
300
+ * ```ts
301
+ * t.object({
302
+ * userId: t.string(),
303
+ * action: t.enum(["create", "update"]),
304
+ * data: t.record(t.string(), t.any()),
305
+ * timestamp: t.optional(t.number())
306
+ * })
307
+ * ```
308
+ */
128
309
  schema: T;
310
+ /**
311
+ * Message handler function that processes queue messages.
312
+ *
313
+ * This function:
314
+ * - Runs in background worker threads for non-blocking processing
315
+ * - Receives type-safe message payloads based on the schema
316
+ * - Should be idempotent to handle potential retries
317
+ * - Can throw errors to trigger retry mechanisms
318
+ * - Has access to the full Alepha dependency injection container
319
+ *
320
+ * **Handler Best Practices**:
321
+ * - Keep handlers focused on a single responsibility
322
+ * - Use proper error handling and logging
323
+ * - Make operations idempotent when possible
324
+ * - Validate critical business logic within handlers
325
+ * - Consider using transactions for data consistency
326
+ *
327
+ * @param message - The queue message with validated payload
328
+ * @returns Promise that resolves when processing is complete
329
+ *
330
+ * @example
331
+ * ```ts
332
+ * handler: async (message) => {
333
+ * const { userId, email, template } = message.payload;
334
+ *
335
+ * try {
336
+ * await this.emailService.send({
337
+ * to: email,
338
+ * template,
339
+ * data: { userId }
340
+ * });
341
+ *
342
+ * await this.userService.markEmailSent(userId, template);
343
+ * } catch (error) {
344
+ * // Log error and let the queue system handle retries
345
+ * this.logger.error(`Failed to send email to ${email}`, error);
346
+ * throw error;
347
+ * }
348
+ * }
349
+ * ```
350
+ */
129
351
  handler?: (message: QueueMessage<T>) => Promise<void>;
130
352
  }
131
353
  declare class QueueDescriptor<T extends TSchema> extends Descriptor<QueueDescriptorOptions<T>> {
@@ -145,16 +367,377 @@ interface QueueMessage<T extends TSchema> {
145
367
  //#endregion
146
368
  //#region src/descriptors/$consumer.d.ts
147
369
  /**
148
- * Consumer descriptor.
370
+ * Creates a consumer descriptor to process messages from a specific queue.
371
+ *
372
+ * This descriptor creates a dedicated message consumer that connects to a queue and processes
373
+ * its messages using a custom handler function. Consumers provide a clean way to separate
374
+ * message production from consumption, enabling scalable architectures where multiple
375
+ * consumers can process messages from the same queue.
376
+ *
377
+ * **Key Features**
378
+ *
379
+ * - **Queue Integration**: Seamlessly connects to any $queue descriptor
380
+ * - **Type Safety**: Full TypeScript support inherited from the connected queue's schema
381
+ * - **Dedicated Processing**: Isolated message processing logic separate from the queue
382
+ * - **Worker Management**: Automatic integration with the worker system for background processing
383
+ * - **Error Handling**: Built-in error handling and retry mechanisms from the queue system
384
+ * - **Scalability**: Multiple consumers can process the same queue for horizontal scaling
385
+ *
386
+ * **Use Cases**
387
+ *
388
+ * Perfect for creating specialized message processors:
389
+ * - Dedicated email sending services
390
+ * - Image processing workers
391
+ * - Data synchronization tasks
392
+ * - Event handlers for specific domains
393
+ * - Microservice message consumers
394
+ * - Background job processors
395
+ *
396
+ * @example
397
+ * **Basic consumer setup:**
398
+ * ```ts
399
+ * import { $queue, $consumer } from "alepha/queue";
400
+ * import { t } from "alepha";
401
+ *
402
+ * class EmailService {
403
+ * // Define the queue
404
+ * emailQueue = $queue({
405
+ * name: "emails",
406
+ * schema: t.object({
407
+ * to: t.string(),
408
+ * subject: t.string(),
409
+ * body: t.string(),
410
+ * template: t.optional(t.string())
411
+ * })
412
+ * });
413
+ *
414
+ * // Create a dedicated consumer for this queue
415
+ * emailConsumer = $consumer({
416
+ * queue: this.emailQueue,
417
+ * handler: async (message) => {
418
+ * const { to, subject, body, template } = message.payload;
419
+ *
420
+ * if (template) {
421
+ * await this.sendTemplatedEmail(to, template, { subject, body });
422
+ * } else {
423
+ * await this.sendPlainEmail(to, subject, body);
424
+ * }
425
+ *
426
+ * console.log(`Email sent to ${to}: ${subject}`);
427
+ * }
428
+ * });
429
+ *
430
+ * async sendWelcomeEmail(userEmail: string) {
431
+ * // Push to queue - consumer will automatically process it
432
+ * await this.emailQueue.push({
433
+ * to: userEmail,
434
+ * subject: "Welcome!",
435
+ * body: "Thanks for joining our platform.",
436
+ * template: "welcome"
437
+ * });
438
+ * }
439
+ * }
440
+ * ```
441
+ *
442
+ * @example
443
+ * **Multiple specialized consumers for different message types:**
444
+ * ```ts
445
+ * class NotificationService {
446
+ * notificationQueue = $queue({
447
+ * name: "notifications",
448
+ * schema: t.object({
449
+ * type: t.enum(["email", "sms", "push"]),
450
+ * recipient: t.string(),
451
+ * message: t.string(),
452
+ * metadata: t.optional(t.record(t.string(), t.any()))
453
+ * })
454
+ * });
455
+ *
456
+ * // Email-specific consumer
457
+ * emailConsumer = $consumer({
458
+ * queue: this.notificationQueue,
459
+ * handler: async (message) => {
460
+ * if (message.payload.type === "email") {
461
+ * await this.emailProvider.send({
462
+ * to: message.payload.recipient,
463
+ * subject: message.payload.metadata?.subject || "Notification",
464
+ * body: message.payload.message
465
+ * });
466
+ * }
467
+ * }
468
+ * });
469
+ *
470
+ * // SMS-specific consumer
471
+ * smsConsumer = $consumer({
472
+ * queue: this.notificationQueue,
473
+ * handler: async (message) => {
474
+ * if (message.payload.type === "sms") {
475
+ * await this.smsProvider.send({
476
+ * to: message.payload.recipient,
477
+ * message: message.payload.message
478
+ * });
479
+ * }
480
+ * }
481
+ * });
482
+ *
483
+ * // Push notification consumer
484
+ * pushConsumer = $consumer({
485
+ * queue: this.notificationQueue,
486
+ * handler: async (message) => {
487
+ * if (message.payload.type === "push") {
488
+ * await this.pushProvider.send({
489
+ * deviceToken: message.payload.recipient,
490
+ * title: message.payload.metadata?.title || "Notification",
491
+ * body: message.payload.message
492
+ * });
493
+ * }
494
+ * }
495
+ * });
496
+ * }
497
+ * ```
498
+ *
499
+ * @example
500
+ * **Consumer with advanced error handling and logging:**
501
+ * ```ts
502
+ * class OrderProcessor {
503
+ * orderQueue = $queue({
504
+ * name: "order-processing",
505
+ * schema: t.object({
506
+ * orderId: t.string(),
507
+ * customerId: t.string(),
508
+ * items: t.array(t.object({
509
+ * productId: t.string(),
510
+ * quantity: t.number(),
511
+ * price: t.number()
512
+ * }))
513
+ * })
514
+ * });
515
+ *
516
+ * orderConsumer = $consumer({
517
+ * queue: this.orderQueue,
518
+ * handler: async (message) => {
519
+ * const { orderId, customerId, items } = message.payload;
520
+ *
521
+ * try {
522
+ * // Log processing start
523
+ * this.logger.info(`Processing order ${orderId} for customer ${customerId}`);
524
+ *
525
+ * // Validate inventory
526
+ * await this.validateInventory(items);
527
+ *
528
+ * // Process payment
529
+ * const paymentResult = await this.processPayment(orderId, items);
530
+ * if (!paymentResult.success) {
531
+ * throw new Error(`Payment failed: ${paymentResult.error}`);
532
+ * }
533
+ *
534
+ * // Update inventory
535
+ * await this.updateInventory(items);
536
+ *
537
+ * // Create shipment
538
+ * await this.createShipment(orderId, customerId);
539
+ *
540
+ * // Send confirmation
541
+ * await this.sendOrderConfirmation(customerId, orderId);
542
+ *
543
+ * this.logger.info(`Order ${orderId} processed successfully`);
544
+ *
545
+ * } catch (error) {
546
+ * // Log detailed error information
547
+ * this.logger.error(`Failed to process order ${orderId}`, {
548
+ * error: error.message,
549
+ * orderId,
550
+ * customerId,
551
+ * itemCount: items.length
552
+ * });
553
+ *
554
+ * // Re-throw to trigger queue retry mechanism
555
+ * throw error;
556
+ * }
557
+ * }
558
+ * });
559
+ * }
560
+ * ```
561
+ *
562
+ * @example
563
+ * **Consumer for batch processing with performance optimization:**
564
+ * ```ts
565
+ * class DataProcessor {
566
+ * dataQueue = $queue({
567
+ * name: "data-processing",
568
+ * schema: t.object({
569
+ * batchId: t.string(),
570
+ * records: t.array(t.object({
571
+ * id: t.string(),
572
+ * data: t.record(t.string(), t.any())
573
+ * })),
574
+ * processingOptions: t.object({
575
+ * validateData: t.boolean(),
576
+ * generateReport: t.boolean(),
577
+ * notifyCompletion: t.boolean()
578
+ * })
579
+ * })
580
+ * });
581
+ *
582
+ * dataConsumer = $consumer({
583
+ * queue: this.dataQueue,
584
+ * handler: async (message) => {
585
+ * const { batchId, records, processingOptions } = message.payload;
586
+ * const startTime = Date.now();
587
+ *
588
+ * this.logger.info(`Starting batch processing for ${batchId} with ${records.length} records`);
589
+ *
590
+ * try {
591
+ * // Process records in chunks for better performance
592
+ * const chunkSize = 100;
593
+ * const chunks = this.chunkArray(records, chunkSize);
594
+ *
595
+ * for (let i = 0; i < chunks.length; i++) {
596
+ * const chunk = chunks[i];
597
+ *
598
+ * if (processingOptions.validateData) {
599
+ * await this.validateChunk(chunk);
600
+ * }
601
+ *
602
+ * await this.processChunk(chunk);
603
+ *
604
+ * // Log progress
605
+ * const progress = ((i + 1) / chunks.length) * 100;
606
+ * this.logger.debug(`Batch ${batchId} progress: ${progress.toFixed(1)}%`);
607
+ * }
608
+ *
609
+ * if (processingOptions.generateReport) {
610
+ * await this.generateProcessingReport(batchId, records.length);
611
+ * }
612
+ *
613
+ * if (processingOptions.notifyCompletion) {
614
+ * await this.notifyBatchCompletion(batchId);
615
+ * }
616
+ *
617
+ * const duration = Date.now() - startTime;
618
+ * this.logger.info(`Batch ${batchId} completed in ${duration}ms`);
619
+ *
620
+ * } catch (error) {
621
+ * const duration = Date.now() - startTime;
622
+ * this.logger.error(`Batch ${batchId} failed after ${duration}ms`, error);
623
+ * throw error;
624
+ * }
625
+ * }
626
+ * });
627
+ * }
628
+ * ```
149
629
  */
150
630
  declare const $consumer: {
151
631
  <T extends TSchema>(options: ConsumerDescriptorOptions<T>): ConsumerDescriptor<T>;
152
632
  [KIND]: typeof ConsumerDescriptor;
153
633
  };
154
634
  interface ConsumerDescriptorOptions<T extends TSchema> {
635
+ /**
636
+ * The queue descriptor that this consumer will process messages from.
637
+ *
638
+ * This establishes the connection between the consumer and its source queue:
639
+ * - The consumer inherits the queue's message schema for type safety
640
+ * - Messages pushed to the queue will be automatically routed to this consumer
641
+ * - Multiple consumers can be attached to the same queue for parallel processing
642
+ * - The consumer will use the queue's provider and configuration settings
643
+ *
644
+ * **Queue Integration Benefits**:
645
+ * - Type safety: Consumer handler gets fully typed message payloads
646
+ * - Schema validation: Messages are validated before reaching the consumer
647
+ * - Error handling: Failed messages can be retried or moved to dead letter queues
648
+ * - Monitoring: Queue metrics include consumer processing statistics
649
+ *
650
+ * @example
651
+ * ```ts
652
+ * // First, define a queue
653
+ * emailQueue = $queue({
654
+ * name: "emails",
655
+ * schema: t.object({ to: t.string(), subject: t.string() })
656
+ * });
657
+ *
658
+ * // Then, create a consumer for that queue
659
+ * emailConsumer = $consumer({
660
+ * queue: this.emailQueue, // Reference the queue descriptor
661
+ * handler: async (message) => { } // process email
662
+ * });
663
+ * ```
664
+ */
155
665
  queue: QueueDescriptor<T>;
666
+ /**
667
+ * Message handler function that processes individual messages from the queue.
668
+ *
669
+ * This function:
670
+ * - Receives fully typed and validated message payloads from the connected queue
671
+ * - Runs in the background worker system for non-blocking operation
672
+ * - Should implement the core business logic for processing this message type
673
+ * - Can throw errors to trigger the queue's retry mechanisms
674
+ * - Has access to the full Alepha dependency injection container
675
+ * - Should be idempotent to handle potential duplicate deliveries
676
+ *
677
+ * **Handler Design Guidelines**:
678
+ * - Keep handlers focused on a single responsibility
679
+ * - Use proper error handling and meaningful error messages
680
+ * - Log important processing steps for debugging and monitoring
681
+ * - Consider transaction boundaries for data consistency
682
+ * - Make operations idempotent when possible
683
+ * - Validate business rules within the handler logic
684
+ *
685
+ * **Error Handling Strategy**:
686
+ * - Throw errors for temporary failures that should be retried
687
+ * - Log and handle permanent failures gracefully
688
+ * - Use specific error types to control retry behavior
689
+ * - Consider implementing circuit breakers for external service calls
690
+ *
691
+ * @param message - The queue message containing the validated payload
692
+ * @param message.payload - The typed message data based on the queue's schema
693
+ * @returns Promise that resolves when processing is complete
694
+ *
695
+ * @example
696
+ * ```ts
697
+ * handler: async (message) => {
698
+ * const { userId, action, data } = message.payload;
699
+ *
700
+ * try {
701
+ * // Log processing start
702
+ * this.logger.info(`Processing ${action} for user ${userId}`);
703
+ *
704
+ * // Validate business rules
705
+ * if (!await this.userService.exists(userId)) {
706
+ * throw new Error(`User ${userId} not found`);
707
+ * }
708
+ *
709
+ * // Perform the main processing logic
710
+ * switch (action) {
711
+ * case "create":
712
+ * await this.processCreation(userId, data);
713
+ * break;
714
+ * case "update":
715
+ * await this.processUpdate(userId, data);
716
+ * break;
717
+ * default:
718
+ * throw new Error(`Unknown action: ${action}`);
719
+ * }
720
+ *
721
+ * // Log successful completion
722
+ * this.logger.info(`Successfully processed ${action} for user ${userId}`);
723
+ *
724
+ * } catch (error) {
725
+ * // Log error with context
726
+ * this.logger.error(`Failed to process ${action} for user ${userId}`, {
727
+ * error: error.message,
728
+ * userId,
729
+ * action,
730
+ * data
731
+ * });
732
+ *
733
+ * // Re-throw to trigger queue retry mechanism
734
+ * throw error;
735
+ * }
736
+ * }
737
+ * ```
738
+ */
156
739
  handler: (message: {
157
- payload: Static<T["payload"]>;
740
+ payload: Static<T>;
158
741
  }) => Promise<void>;
159
742
  }
160
743
  declare class ConsumerDescriptor<T extends TSchema> extends Descriptor<ConsumerDescriptorOptions<T>> {}