@rolandsall24/nest-mediator 0.4.3 → 0.5.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.
Files changed (66) hide show
  1. package/README.md +492 -2
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +4 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/behaviors/exception-handling.behavior.d.ts +46 -0
  7. package/dist/lib/behaviors/exception-handling.behavior.d.ts.map +1 -0
  8. package/dist/lib/behaviors/exception-handling.behavior.js +76 -0
  9. package/dist/lib/behaviors/exception-handling.behavior.js.map +1 -0
  10. package/dist/lib/behaviors/index.d.ts +5 -0
  11. package/dist/lib/behaviors/index.d.ts.map +1 -0
  12. package/dist/lib/behaviors/index.js +21 -0
  13. package/dist/lib/behaviors/index.js.map +1 -0
  14. package/dist/lib/behaviors/logging.behavior.d.ts +26 -0
  15. package/dist/lib/behaviors/logging.behavior.d.ts.map +1 -0
  16. package/dist/lib/behaviors/logging.behavior.js +64 -0
  17. package/dist/lib/behaviors/logging.behavior.js.map +1 -0
  18. package/dist/lib/behaviors/performance.behavior.d.ts +41 -0
  19. package/dist/lib/behaviors/performance.behavior.d.ts.map +1 -0
  20. package/dist/lib/behaviors/performance.behavior.js +71 -0
  21. package/dist/lib/behaviors/performance.behavior.js.map +1 -0
  22. package/dist/lib/behaviors/validation.behavior.d.ts +69 -0
  23. package/dist/lib/behaviors/validation.behavior.d.ts.map +1 -0
  24. package/dist/lib/behaviors/validation.behavior.js +137 -0
  25. package/dist/lib/behaviors/validation.behavior.js.map +1 -0
  26. package/dist/lib/decorators/index.d.ts +2 -0
  27. package/dist/lib/decorators/index.d.ts.map +1 -1
  28. package/dist/lib/decorators/index.js +2 -0
  29. package/dist/lib/decorators/index.js.map +1 -1
  30. package/dist/lib/decorators/pipeline-behavior.decorator.d.ts +51 -0
  31. package/dist/lib/decorators/pipeline-behavior.decorator.d.ts.map +1 -0
  32. package/dist/lib/decorators/pipeline-behavior.decorator.js +68 -0
  33. package/dist/lib/decorators/pipeline-behavior.decorator.js.map +1 -0
  34. package/dist/lib/decorators/skip-behavior.decorator.d.ts +52 -0
  35. package/dist/lib/decorators/skip-behavior.decorator.d.ts.map +1 -0
  36. package/dist/lib/decorators/skip-behavior.decorator.js +61 -0
  37. package/dist/lib/decorators/skip-behavior.decorator.js.map +1 -0
  38. package/dist/lib/exceptions/handler-not-found.exception.d.ts +20 -0
  39. package/dist/lib/exceptions/handler-not-found.exception.d.ts.map +1 -0
  40. package/dist/lib/exceptions/handler-not-found.exception.js +33 -0
  41. package/dist/lib/exceptions/handler-not-found.exception.js.map +1 -0
  42. package/dist/lib/exceptions/index.d.ts +3 -0
  43. package/dist/lib/exceptions/index.d.ts.map +1 -0
  44. package/dist/lib/exceptions/index.js +19 -0
  45. package/dist/lib/exceptions/index.js.map +1 -0
  46. package/dist/lib/exceptions/validation.exception.d.ts +62 -0
  47. package/dist/lib/exceptions/validation.exception.d.ts.map +1 -0
  48. package/dist/lib/exceptions/validation.exception.js +66 -0
  49. package/dist/lib/exceptions/validation.exception.js.map +1 -0
  50. package/dist/lib/interfaces/index.d.ts +1 -0
  51. package/dist/lib/interfaces/index.d.ts.map +1 -1
  52. package/dist/lib/interfaces/index.js +1 -0
  53. package/dist/lib/interfaces/index.js.map +1 -1
  54. package/dist/lib/interfaces/pipeline-behavior.interface.d.ts +60 -0
  55. package/dist/lib/interfaces/pipeline-behavior.interface.d.ts.map +1 -0
  56. package/dist/lib/interfaces/pipeline-behavior.interface.js +3 -0
  57. package/dist/lib/interfaces/pipeline-behavior.interface.js.map +1 -0
  58. package/dist/lib/nest-mediator.module.d.ts +74 -3
  59. package/dist/lib/nest-mediator.module.d.ts.map +1 -1
  60. package/dist/lib/nest-mediator.module.js +66 -6
  61. package/dist/lib/nest-mediator.module.js.map +1 -1
  62. package/dist/lib/services/mediator.bus.d.ts +54 -4
  63. package/dist/lib/services/mediator.bus.d.ts.map +1 -1
  64. package/dist/lib/services/mediator.bus.js +104 -9
  65. package/dist/lib/services/mediator.bus.js.map +1 -1
  66. package/package.json +6 -2
package/README.md CHANGED
@@ -8,6 +8,8 @@ A lightweight CQRS (Command Query Responsibility Segregation) mediator pattern i
8
8
  - Type-safe handlers with TypeScript
9
9
  - Decorator-based handler registration
10
10
  - Automatic handler discovery and registration
11
+ - **Pipeline Behaviors** for cross-cutting concerns (logging, validation, etc.)
12
+ - Built-in behaviors: Logging, Validation, Exception Handling, Performance Tracking
11
13
  - Built on top of NestJS dependency injection
12
14
  - Zero runtime dependencies beyond NestJS
13
15
 
@@ -30,6 +32,58 @@ This library requires TypeScript decorators to be enabled. Add the following to
30
32
  }
31
33
  ```
32
34
 
35
+ ## Upgrading to v0.5.0
36
+
37
+ Version 0.5.0 introduces **Pipeline Behaviors** while maintaining backward compatibility. Existing code using `NestMediatorModule.forRoot()` will continue to work without changes.
38
+
39
+ ### What's New
40
+
41
+ - Pipeline behaviors for cross-cutting concerns (logging, validation, etc.)
42
+ - Built-in behaviors: `LoggingBehavior`, `ValidationBehavior`, `ExceptionHandlingBehavior`, `PerformanceBehavior`
43
+ - New `forRootAsync()` method for enabling behaviors
44
+ - Custom `HandlerNotFoundException` for better error handling
45
+
46
+ ### Breaking Change Notice
47
+
48
+ The `send()` and `query()` methods now throw `HandlerNotFoundException` instead of a generic `Error` when no handler is registered. This is **backward compatible** since `HandlerNotFoundException` extends `Error`, but you may want to update your error handling for more specific catches:
49
+
50
+ ```typescript
51
+ // Before (still works)
52
+ try {
53
+ await mediator.send(command);
54
+ } catch (error) {
55
+ if (error instanceof Error) {
56
+ console.log(error.message); // Works as before
57
+ }
58
+ }
59
+
60
+ // After (optional - for more specific handling)
61
+ import { HandlerNotFoundException } from '@rolandsall24/nest-mediator';
62
+
63
+ try {
64
+ await mediator.send(command);
65
+ } catch (error) {
66
+ if (error instanceof HandlerNotFoundException) {
67
+ console.log(`No handler for: ${error.requestName}`);
68
+ }
69
+ }
70
+ ```
71
+
72
+ ### No Migration Required
73
+
74
+ If you're using `NestMediatorModule.forRoot()`, no changes are needed. Pipeline behaviors are **opt-in** via `forRootAsync()`:
75
+
76
+ ```typescript
77
+ // Existing code - works exactly as before (no behaviors)
78
+ NestMediatorModule.forRoot()
79
+
80
+ // New - opt-in to behaviors
81
+ NestMediatorModule.forRootAsync({
82
+ enableLogging: true,
83
+ enableValidation: true,
84
+ })
85
+ ```
86
+
33
87
  ## Quick Start
34
88
 
35
89
  ### 1. Import the Module
@@ -44,6 +98,7 @@ import { GetUserQueryHandler } from './handlers/get-user-query.handler';
44
98
 
45
99
  @Module({
46
100
  imports: [
101
+ // Basic setup
47
102
  NestMediatorModule.forRoot(),
48
103
  ],
49
104
  providers: [
@@ -56,7 +111,26 @@ import { GetUserQueryHandler } from './handlers/get-user-query.handler';
56
111
  export class AppModule {}
57
112
  ```
58
113
 
59
- **How it works**: The module uses NestJS's `DiscoveryService` to automatically discover and register all providers decorated with `@CommandHandler` or `@QueryHandler`. Simply add your handlers to the module's `providers` array and they will be automatically registered with the mediator!
114
+ Or with built-in pipeline behaviors enabled:
115
+
116
+ ```typescript
117
+ @Module({
118
+ imports: [
119
+ NestMediatorModule.forRootAsync({
120
+ enableLogging: true, // Log request handling with timing
121
+ enableValidation: true, // Validate requests with class-validator
122
+ enableExceptionHandling: true, // Centralized exception logging
123
+ }),
124
+ ],
125
+ providers: [
126
+ CreateUserCommandHandler,
127
+ GetUserQueryHandler,
128
+ ],
129
+ })
130
+ export class AppModule {}
131
+ ```
132
+
133
+ **How it works**: The module uses NestJS's `DiscoveryService` to automatically discover and register all providers decorated with `@CommandHandler`, `@QueryHandler`, or `@PipelineBehavior`. Simply add your handlers to the module's `providers` array and they will be automatically registered with the mediator!
60
134
 
61
135
  ## Usage
62
136
 
@@ -510,7 +584,12 @@ import { UserPersistenceAdapter } from './infrastructure/persistence/user/user-p
510
584
 
511
585
  @Module({
512
586
  imports: [
513
- NestMediatorModule.forRoot(),
587
+ // Enable pipeline behaviors for logging, validation, and error handling
588
+ NestMediatorModule.forRootAsync({
589
+ enableLogging: true,
590
+ enableValidation: true,
591
+ enableExceptionHandling: true,
592
+ }),
514
593
  ],
515
594
  controllers: [UserController],
516
595
  providers: [
@@ -593,6 +672,16 @@ export interface IQueryHandler<TQuery extends IQuery, TResult = any> {
593
672
  }
594
673
  ```
595
674
 
675
+ #### `IPipelineBehavior<TRequest, TResponse>`
676
+
677
+ Interface for pipeline behaviors (cross-cutting concerns).
678
+
679
+ ```typescript
680
+ export interface IPipelineBehavior<TRequest = any, TResponse = any> {
681
+ handle(request: TRequest, next: () => Promise<TResponse>): Promise<TResponse>;
682
+ }
683
+ ```
684
+
596
685
  ### Decorators
597
686
 
598
687
  #### `@CommandHandler(command)`
@@ -609,6 +698,25 @@ Marks a class as a query handler.
609
698
  - **Parameters**: `query` - The query class this handler handles
610
699
  - **Usage**: Apply to handler classes that implement `IQueryHandler`
611
700
 
701
+ #### `@PipelineBehavior(options?)`
702
+
703
+ Marks a class as a pipeline behavior.
704
+
705
+ - **Parameters**:
706
+ - `options.priority` - Execution order (lower numbers execute first, default: 0)
707
+ - `options.scope` - `'command'`, `'query'`, or `'all'` (default: `'all'`)
708
+ - **Usage**: Apply to behavior classes that implement `IPipelineBehavior`
709
+
710
+ #### `@SkipBehavior(behavior | behaviors[])`
711
+
712
+ Excludes specific pipeline behaviors from a command or query.
713
+
714
+ - **Parameters**:
715
+ - Single behavior class: `@SkipBehavior(PerformanceBehavior)`
716
+ - Array of behavior classes: `@SkipBehavior([PerformanceBehavior, LoggingBehavior])`
717
+ - **Usage**: Apply to command or query classes
718
+ - **Works with**: Both built-in behaviors and custom behaviors
719
+
612
720
  ### Services
613
721
 
614
722
  #### `MediatorBus`
@@ -633,6 +741,388 @@ Executes a query through its registered handler.
633
741
  - **Returns**: Promise that resolves with the query result
634
742
  - **Throws**: Error if no handler is registered for the query
635
743
 
744
+ ### Module Configuration
745
+
746
+ #### `NestMediatorModule.forRoot()`
747
+
748
+ Basic module registration with no built-in behaviors.
749
+
750
+ #### `NestMediatorModule.forRootAsync(options)`
751
+
752
+ Module registration with configuration options.
753
+
754
+ | Option | Type | Default | Description |
755
+ |--------|------|---------|-------------|
756
+ | `enableLogging` | boolean | false | Enable request/response logging |
757
+ | `enableValidation` | boolean | false | Enable class-validator validation |
758
+ | `enableExceptionHandling` | boolean | false | Enable centralized exception logging |
759
+ | `enablePerformanceTracking` | boolean | false | Enable slow request warnings |
760
+ | `performanceThresholdMs` | number | 500 | Threshold for slow request warnings |
761
+ | `behaviors` | Type[] | [] | Additional custom behaviors to register |
762
+
763
+ ## Pipeline Behaviors
764
+
765
+ Pipeline behaviors allow you to add cross-cutting concerns (like logging, validation, caching) that execute around every command and query handler.
766
+
767
+ ### Enabling Built-in Behaviors
768
+
769
+ ```typescript
770
+ import { Module } from '@nestjs/common';
771
+ import { NestMediatorModule } from '@rolandsall24/nest-mediator';
772
+
773
+ @Module({
774
+ imports: [
775
+ NestMediatorModule.forRootAsync({
776
+ enableLogging: true, // Logs request handling with timing
777
+ enableValidation: true, // Validates requests using class-validator
778
+ enableExceptionHandling: true, // Centralized exception logging
779
+ enablePerformanceTracking: true, // Warns on slow requests
780
+ performanceThresholdMs: 500, // Threshold for slow request warnings
781
+ }),
782
+ ],
783
+ })
784
+ export class AppModule {}
785
+ ```
786
+
787
+ ### Built-in Behaviors
788
+
789
+ | Behavior | Priority | Description |
790
+ |----------|----------|-------------|
791
+ | `ExceptionHandlingBehavior` | -100 | Catches and logs all exceptions |
792
+ | `LoggingBehavior` | 0 | Logs request handling with timing |
793
+ | `PerformanceBehavior` | 10 | Warns when requests exceed threshold |
794
+ | `ValidationBehavior` | 100 | Validates requests using class-validator |
795
+
796
+ ### Creating Custom Behaviors
797
+
798
+ ```typescript
799
+ import { Injectable } from '@nestjs/common';
800
+ import { IPipelineBehavior, PipelineBehavior } from '@rolandsall24/nest-mediator';
801
+
802
+ @Injectable()
803
+ @PipelineBehavior({ priority: 50, scope: 'all' })
804
+ export class MyCustomBehavior<TRequest, TResponse>
805
+ implements IPipelineBehavior<TRequest, TResponse> {
806
+
807
+ async handle(
808
+ request: TRequest,
809
+ next: () => Promise<TResponse>
810
+ ): Promise<TResponse> {
811
+ // Pre-processing logic
812
+ console.log('Before handler:', request);
813
+
814
+ // Call the next behavior or handler
815
+ const response = await next();
816
+
817
+ // Post-processing logic
818
+ console.log('After handler:', response);
819
+
820
+ return response;
821
+ }
822
+ }
823
+ ```
824
+
825
+ ### Behavior Options
826
+
827
+ ```typescript
828
+ @PipelineBehavior({
829
+ priority: 50, // Lower numbers execute first (outermost)
830
+ scope: 'command', // 'command', 'query', or 'all'
831
+ })
832
+ ```
833
+
834
+ **Priority Guidelines:**
835
+ - `-100 to -1`: Exception handling (outermost)
836
+ - `0 to 99`: Logging, performance tracking
837
+ - `100 to 199`: Validation
838
+ - `200+`: Transaction/Unit of Work (innermost)
839
+
840
+ ### Registering Custom Behaviors
841
+
842
+ Add your behavior to the module providers:
843
+
844
+ ```typescript
845
+ @Module({
846
+ imports: [
847
+ NestMediatorModule.forRootAsync({
848
+ behaviors: [MyCustomBehavior],
849
+ }),
850
+ ],
851
+ providers: [
852
+ MyCustomBehavior, // Also add to providers for DI
853
+ ],
854
+ })
855
+ export class AppModule {}
856
+ ```
857
+
858
+ Or use the `@PipelineBehavior` decorator and add to providers - it will be auto-discovered:
859
+
860
+ ```typescript
861
+ @Module({
862
+ imports: [NestMediatorModule.forRoot()],
863
+ providers: [MyCustomBehavior], // Auto-discovered via decorator
864
+ })
865
+ export class AppModule {}
866
+ ```
867
+
868
+ ### Skipping Behaviors for Specific Commands/Queries
869
+
870
+ Use `@SkipBehavior` to exclude specific behaviors from a command or query:
871
+
872
+ ```typescript
873
+ import {
874
+ ICommand,
875
+ SkipBehavior,
876
+ PerformanceBehavior,
877
+ LoggingBehavior,
878
+ } from '@rolandsall24/nest-mediator';
879
+
880
+ // Skip a single behavior
881
+ @SkipBehavior(PerformanceBehavior)
882
+ export class HighFrequencyCommand implements ICommand {
883
+ // This command will not trigger performance tracking
884
+ }
885
+
886
+ // Skip multiple behaviors
887
+ @SkipBehavior([PerformanceBehavior, LoggingBehavior])
888
+ export class HealthCheckQuery implements IQuery {
889
+ // This query skips both performance and logging behaviors
890
+ }
891
+ ```
892
+
893
+ This works with both built-in behaviors and custom behaviors:
894
+
895
+ ```typescript
896
+ import { SkipBehavior } from '@rolandsall24/nest-mediator';
897
+ import { MyAuditBehavior } from './behaviors/audit.behavior';
898
+
899
+ @SkipBehavior(MyAuditBehavior)
900
+ export class InternalCommand implements ICommand {
901
+ // Skips your custom audit behavior
902
+ }
903
+ ```
904
+
905
+ ### Custom Behavior with Service Injection
906
+
907
+ Behaviors support full NestJS dependency injection:
908
+
909
+ ```typescript
910
+ import { Injectable, Logger } from '@nestjs/common';
911
+ import { IPipelineBehavior, PipelineBehavior } from '@rolandsall24/nest-mediator';
912
+
913
+ // Service for audit logging
914
+ @Injectable()
915
+ export class AuditService {
916
+ private readonly logger = new Logger(AuditService.name);
917
+
918
+ async logAction(action: string, userId: string): Promise<void> {
919
+ this.logger.log(`[AUDIT] ${action} by ${userId}`);
920
+ // Save to database, send to external service, etc.
921
+ }
922
+ }
923
+
924
+ // Behavior that uses the service
925
+ @Injectable()
926
+ @PipelineBehavior({ priority: 50, scope: 'command' })
927
+ export class AuditLoggingBehavior<TRequest, TResponse>
928
+ implements IPipelineBehavior<TRequest, TResponse> {
929
+
930
+ constructor(private readonly auditService: AuditService) {}
931
+
932
+ async handle(
933
+ request: TRequest,
934
+ next: () => Promise<TResponse>,
935
+ ): Promise<TResponse> {
936
+ const requestName = request.constructor.name;
937
+
938
+ await this.auditService.logAction(`Executing ${requestName}`, 'user-123');
939
+
940
+ const response = await next();
941
+
942
+ await this.auditService.logAction(`Completed ${requestName}`, 'user-123');
943
+
944
+ return response;
945
+ }
946
+ }
947
+
948
+ // Register both in your module
949
+ @Module({
950
+ imports: [NestMediatorModule.forRoot()],
951
+ providers: [
952
+ AuditService, // The service
953
+ AuditLoggingBehavior, // The behavior (auto-discovered)
954
+ ],
955
+ })
956
+ export class AppModule {}
957
+ ```
958
+
959
+ ### Advanced Behavior Examples
960
+
961
+ #### Retry Behavior (for transient failures)
962
+
963
+ ```typescript
964
+ @Injectable()
965
+ @PipelineBehavior({ priority: -50, scope: 'command' })
966
+ export class RetryBehavior<TRequest, TResponse>
967
+ implements IPipelineBehavior<TRequest, TResponse> {
968
+
969
+ private readonly maxRetries = 3;
970
+
971
+ async handle(
972
+ request: TRequest,
973
+ next: () => Promise<TResponse>,
974
+ ): Promise<TResponse> {
975
+ let lastError: Error;
976
+
977
+ for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
978
+ try {
979
+ return await next();
980
+ } catch (error) {
981
+ lastError = error as Error;
982
+
983
+ // Don't retry validation errors
984
+ if (error.name?.includes('Validation')) throw error;
985
+
986
+ if (attempt < this.maxRetries) {
987
+ const delay = 100 * Math.pow(2, attempt - 1); // Exponential backoff
988
+ await new Promise(resolve => setTimeout(resolve, delay));
989
+ }
990
+ }
991
+ }
992
+
993
+ throw lastError!;
994
+ }
995
+ }
996
+ ```
997
+
998
+ #### Caching Behavior (for queries)
999
+
1000
+ ```typescript
1001
+ @Injectable()
1002
+ @PipelineBehavior({ priority: 5, scope: 'query' })
1003
+ export class CachingBehavior<TRequest, TResponse>
1004
+ implements IPipelineBehavior<TRequest, TResponse> {
1005
+
1006
+ private cache = new Map<string, { data: any; expiry: number }>();
1007
+
1008
+ async handle(
1009
+ request: TRequest,
1010
+ next: () => Promise<TResponse>,
1011
+ ): Promise<TResponse> {
1012
+ const key = JSON.stringify(request);
1013
+ const cached = this.cache.get(key);
1014
+
1015
+ if (cached && cached.expiry > Date.now()) {
1016
+ return cached.data; // Cache hit
1017
+ }
1018
+
1019
+ const result = await next();
1020
+
1021
+ this.cache.set(key, {
1022
+ data: result,
1023
+ expiry: Date.now() + 30000, // 30 second TTL
1024
+ });
1025
+
1026
+ return result;
1027
+ }
1028
+ }
1029
+ ```
1030
+
1031
+ #### Authorization Behavior
1032
+
1033
+ ```typescript
1034
+ @Injectable()
1035
+ @PipelineBehavior({ priority: 25, scope: 'all' })
1036
+ export class AuthorizationBehavior<TRequest, TResponse>
1037
+ implements IPipelineBehavior<TRequest, TResponse> {
1038
+
1039
+ constructor(private readonly authService: AuthService) {}
1040
+
1041
+ private readonly adminOnlyCommands = ['DeleteUserCommand'];
1042
+
1043
+ async handle(
1044
+ request: TRequest,
1045
+ next: () => Promise<TResponse>,
1046
+ ): Promise<TResponse> {
1047
+ const requestName = request.constructor.name;
1048
+
1049
+ if (!this.authService.isAuthenticated()) {
1050
+ throw new UnauthorizedException('Authentication required');
1051
+ }
1052
+
1053
+ if (this.adminOnlyCommands.includes(requestName)) {
1054
+ if (!this.authService.hasRole('admin')) {
1055
+ throw new ForbiddenException('Admin role required');
1056
+ }
1057
+ }
1058
+
1059
+ return next();
1060
+ }
1061
+ }
1062
+ ```
1063
+
1064
+ ### Complete Behavior Execution Order Example
1065
+
1066
+ With the following behaviors configured:
1067
+
1068
+ ```typescript
1069
+ // Built-in behaviors (when enabled):
1070
+ // ExceptionHandlingBehavior: priority -100, scope 'all'
1071
+ // LoggingBehavior: priority 0, scope 'all'
1072
+ // PerformanceBehavior: priority 10, scope 'all'
1073
+ // ValidationBehavior: priority 100, scope 'all'
1074
+
1075
+ // Custom behaviors:
1076
+ // RetryBehavior: priority -50, scope 'command'
1077
+ // CachingBehavior: priority 5, scope 'query'
1078
+ // AuthorizationBehavior: priority 25, scope 'all'
1079
+ // AuditLoggingBehavior: priority 50, scope 'command'
1080
+ ```
1081
+
1082
+ **For a Command:**
1083
+ ```
1084
+ Request → Exception(-100) → Retry(-50) → Logging(0) → Performance(10) → Authorization(25) → Audit(50) → Validation(100) → Handler
1085
+ ```
1086
+
1087
+ **For a Query:**
1088
+ ```
1089
+ Request → Exception(-100) → Logging(0) → Caching(5) → Performance(10) → Authorization(25) → Validation(100) → Handler
1090
+ ```
1091
+
1092
+ ### Validation with class-validator
1093
+
1094
+ When `enableValidation` is true, requests are validated using class-validator (if installed):
1095
+
1096
+ ```typescript
1097
+ import { IsEmail, IsString, MinLength } from 'class-validator';
1098
+ import { ICommand } from '@rolandsall24/nest-mediator';
1099
+
1100
+ export class CreateUserCommand implements ICommand {
1101
+ @IsEmail()
1102
+ email: string;
1103
+
1104
+ @IsString()
1105
+ @MinLength(2)
1106
+ name: string;
1107
+
1108
+ constructor(email: string, name: string) {
1109
+ this.email = email;
1110
+ this.name = name;
1111
+ }
1112
+ }
1113
+
1114
+ // If validation fails, ValidationException is thrown with details
1115
+ ```
1116
+
1117
+ ### Pipeline Execution Order
1118
+
1119
+ With behaviors at priorities -100, 0, 10, 100:
1120
+
1121
+ ```
1122
+ Request → ExceptionHandling(-100) → Logging(0) → Performance(10) → Validation(100) → Handler
1123
+ Response ← ExceptionHandling(-100) ← Logging(0) ← Performance(10) ← Validation(100) ← Handler
1124
+ ```
1125
+
636
1126
  ## Best Practices
637
1127
 
638
1128
  1. **Keep Commands and Queries Simple**: They should be simple data containers with minimal logic.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from './lib/interfaces/index.js';
2
2
  export * from './lib/decorators/index.js';
3
3
  export * from './lib/services/index.js';
4
+ export * from './lib/behaviors/index.js';
5
+ export * from './lib/exceptions/index.js';
4
6
  export * from './lib/nest-mediator.module.js';
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,2BAA2B,CAAC;AAG1C,cAAc,2BAA2B,CAAC;AAG1C,cAAc,yBAAyB,CAAC;AAGxC,cAAc,+BAA+B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,2BAA2B,CAAC;AAG1C,cAAc,2BAA2B,CAAC;AAG1C,cAAc,yBAAyB,CAAC;AAGxC,cAAc,0BAA0B,CAAC;AAGzC,cAAc,2BAA2B,CAAC;AAG1C,cAAc,+BAA+B,CAAC"}
package/dist/index.js CHANGED
@@ -20,6 +20,10 @@ __exportStar(require("./lib/interfaces/index.js"), exports);
20
20
  __exportStar(require("./lib/decorators/index.js"), exports);
21
21
  // Services
22
22
  __exportStar(require("./lib/services/index.js"), exports);
23
+ // Behaviors
24
+ __exportStar(require("./lib/behaviors/index.js"), exports);
25
+ // Exceptions
26
+ __exportStar(require("./lib/exceptions/index.js"), exports);
23
27
  // Module
24
28
  __exportStar(require("./lib/nest-mediator.module.js"), exports);
25
29
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,aAAa;AACb,4DAA0C;AAE1C,aAAa;AACb,4DAA0C;AAE1C,WAAW;AACX,0DAAwC;AAExC,SAAS;AACT,gEAA8C"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,aAAa;AACb,4DAA0C;AAE1C,aAAa;AACb,4DAA0C;AAE1C,WAAW;AACX,0DAAwC;AAExC,YAAY;AACZ,2DAAyC;AAEzC,aAAa;AACb,4DAA0C;AAE1C,SAAS;AACT,gEAA8C"}
@@ -0,0 +1,46 @@
1
+ import { IPipelineBehavior } from '../interfaces/pipeline-behavior.interface.js';
2
+ /**
3
+ * Interface for custom exception handlers.
4
+ * Implement this to transform or handle specific exception types.
5
+ */
6
+ export interface IExceptionHandler {
7
+ /**
8
+ * Check if this handler can handle the given error
9
+ */
10
+ canHandle(error: Error): boolean;
11
+ /**
12
+ * Handle the error - can transform it, log it, or rethrow
13
+ */
14
+ handle(error: Error, request: any): Error | Promise<Error>;
15
+ }
16
+ /**
17
+ * Pipeline behavior that provides centralized exception handling.
18
+ * This behavior wraps all other behaviors and handlers, catching any exceptions.
19
+ *
20
+ * Priority: -100 (executes first/outermost to catch all exceptions)
21
+ *
22
+ * Features:
23
+ * - Catches all exceptions from the pipeline
24
+ * - Logs exceptions with request context
25
+ * - Can be extended with custom exception handlers
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * // The behavior automatically catches and logs all exceptions
30
+ * await mediator.send(new CreateUserCommand('invalid'));
31
+ * // If handler throws, ExceptionHandlingBehavior logs:
32
+ * // [MediatorBus] Exception in CreateUserCommand: User validation failed
33
+ * ```
34
+ */
35
+ export declare class ExceptionHandlingBehavior<TRequest = any, TResponse = any> implements IPipelineBehavior<TRequest, TResponse> {
36
+ private readonly logger;
37
+ private readonly exceptionHandlers;
38
+ /**
39
+ * Register a custom exception handler
40
+ */
41
+ registerExceptionHandler(handler: IExceptionHandler): void;
42
+ handle(request: TRequest, next: () => Promise<TResponse>): Promise<TResponse>;
43
+ private processException;
44
+ private getRequestName;
45
+ }
46
+ //# sourceMappingURL=exception-handling.behavior.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exception-handling.behavior.d.ts","sourceRoot":"","sources":["../../../src/lib/behaviors/exception-handling.behavior.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AAGjF;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC;IAEjC;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,GAAG,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;CAC5D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAEa,yBAAyB,CAAC,QAAQ,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,CACpE,YAAW,iBAAiB,CAAC,QAAQ,EAAE,SAAS,CAAC;IAEjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA2B;IAE7D;;OAEG;IACH,wBAAwB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAIpD,MAAM,CACV,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,GAC7B,OAAO,CAAC,SAAS,CAAC;YAYP,gBAAgB;IAuB9B,OAAO,CAAC,cAAc;CAMvB"}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.ExceptionHandlingBehavior = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const pipeline_behavior_decorator_js_1 = require("../decorators/pipeline-behavior.decorator.js");
12
+ /**
13
+ * Pipeline behavior that provides centralized exception handling.
14
+ * This behavior wraps all other behaviors and handlers, catching any exceptions.
15
+ *
16
+ * Priority: -100 (executes first/outermost to catch all exceptions)
17
+ *
18
+ * Features:
19
+ * - Catches all exceptions from the pipeline
20
+ * - Logs exceptions with request context
21
+ * - Can be extended with custom exception handlers
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * // The behavior automatically catches and logs all exceptions
26
+ * await mediator.send(new CreateUserCommand('invalid'));
27
+ * // If handler throws, ExceptionHandlingBehavior logs:
28
+ * // [MediatorBus] Exception in CreateUserCommand: User validation failed
29
+ * ```
30
+ */
31
+ let ExceptionHandlingBehavior = class ExceptionHandlingBehavior {
32
+ constructor() {
33
+ this.logger = new common_1.Logger('MediatorBus');
34
+ this.exceptionHandlers = [];
35
+ }
36
+ /**
37
+ * Register a custom exception handler
38
+ */
39
+ registerExceptionHandler(handler) {
40
+ this.exceptionHandlers.push(handler);
41
+ }
42
+ async handle(request, next) {
43
+ try {
44
+ return await next();
45
+ }
46
+ catch (error) {
47
+ const processedError = await this.processException(error, request);
48
+ throw processedError;
49
+ }
50
+ }
51
+ async processException(error, request) {
52
+ const requestName = this.getRequestName(request);
53
+ // Log the exception
54
+ this.logger.error(`Exception in ${requestName}: ${error.message}`, error.stack);
55
+ // Try custom exception handlers
56
+ for (const handler of this.exceptionHandlers) {
57
+ if (handler.canHandle(error)) {
58
+ return handler.handle(error, request);
59
+ }
60
+ }
61
+ // Return original error if no handler processed it
62
+ return error;
63
+ }
64
+ getRequestName(request) {
65
+ if (request && typeof request === 'object' && request.constructor) {
66
+ return request.constructor.name;
67
+ }
68
+ return 'UnknownRequest';
69
+ }
70
+ };
71
+ exports.ExceptionHandlingBehavior = ExceptionHandlingBehavior;
72
+ exports.ExceptionHandlingBehavior = ExceptionHandlingBehavior = __decorate([
73
+ (0, common_1.Injectable)(),
74
+ (0, pipeline_behavior_decorator_js_1.PipelineBehavior)({ priority: -100, scope: 'all' })
75
+ ], ExceptionHandlingBehavior);
76
+ //# sourceMappingURL=exception-handling.behavior.js.map