@digitaldefiance/node-express-suite 3.11.32 → 3.12.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 (146) hide show
  1. package/README.md +858 -0
  2. package/package.json +1 -1
  3. package/src/controllers/openapi.d.ts +67 -0
  4. package/src/controllers/openapi.d.ts.map +1 -0
  5. package/src/controllers/openapi.js +89 -0
  6. package/src/controllers/openapi.js.map +1 -0
  7. package/src/decorators/auth.d.ts +128 -0
  8. package/src/decorators/auth.d.ts.map +1 -0
  9. package/src/decorators/auth.js +230 -0
  10. package/src/decorators/auth.js.map +1 -0
  11. package/src/decorators/base-controller.d.ts +144 -6
  12. package/src/decorators/base-controller.d.ts.map +1 -1
  13. package/src/decorators/base-controller.js +487 -31
  14. package/src/decorators/base-controller.js.map +1 -1
  15. package/src/decorators/controller.d.ts +63 -7
  16. package/src/decorators/controller.d.ts.map +1 -1
  17. package/src/decorators/controller.js +70 -39
  18. package/src/decorators/controller.js.map +1 -1
  19. package/src/decorators/handler-args.d.ts +68 -0
  20. package/src/decorators/handler-args.d.ts.map +1 -0
  21. package/src/decorators/handler-args.js +83 -0
  22. package/src/decorators/handler-args.js.map +1 -0
  23. package/src/decorators/http-methods.d.ts +143 -0
  24. package/src/decorators/http-methods.d.ts.map +1 -0
  25. package/src/decorators/http-methods.js +265 -0
  26. package/src/decorators/http-methods.js.map +1 -0
  27. package/src/decorators/index.d.ts +22 -0
  28. package/src/decorators/index.d.ts.map +1 -1
  29. package/src/decorators/index.js +56 -0
  30. package/src/decorators/index.js.map +1 -1
  31. package/src/decorators/lifecycle.d.ts +248 -0
  32. package/src/decorators/lifecycle.d.ts.map +1 -0
  33. package/src/decorators/lifecycle.js +301 -0
  34. package/src/decorators/lifecycle.js.map +1 -0
  35. package/src/decorators/metadata-collector.d.ts +175 -0
  36. package/src/decorators/metadata-collector.d.ts.map +1 -0
  37. package/src/decorators/metadata-collector.js +272 -0
  38. package/src/decorators/metadata-collector.js.map +1 -0
  39. package/src/decorators/metadata-keys.d.ts +121 -0
  40. package/src/decorators/metadata-keys.d.ts.map +1 -0
  41. package/src/decorators/metadata-keys.js +116 -0
  42. package/src/decorators/metadata-keys.js.map +1 -0
  43. package/src/decorators/middleware.d.ts +181 -0
  44. package/src/decorators/middleware.d.ts.map +1 -0
  45. package/src/decorators/middleware.js +400 -0
  46. package/src/decorators/middleware.js.map +1 -0
  47. package/src/decorators/openapi-params.d.ts +192 -0
  48. package/src/decorators/openapi-params.d.ts.map +1 -0
  49. package/src/decorators/openapi-params.js +332 -0
  50. package/src/decorators/openapi-params.js.map +1 -0
  51. package/src/decorators/openapi.d.ts +201 -0
  52. package/src/decorators/openapi.d.ts.map +1 -0
  53. package/src/decorators/openapi.js +334 -0
  54. package/src/decorators/openapi.js.map +1 -0
  55. package/src/decorators/params.d.ts +217 -0
  56. package/src/decorators/params.d.ts.map +1 -0
  57. package/src/decorators/params.js +323 -0
  58. package/src/decorators/params.js.map +1 -0
  59. package/src/decorators/response.d.ts +200 -0
  60. package/src/decorators/response.d.ts.map +1 -0
  61. package/src/decorators/response.js +315 -0
  62. package/src/decorators/response.js.map +1 -0
  63. package/src/decorators/schema.d.ts +99 -0
  64. package/src/decorators/schema.d.ts.map +1 -0
  65. package/src/decorators/schema.js +329 -0
  66. package/src/decorators/schema.js.map +1 -0
  67. package/src/decorators/transaction.d.ts +69 -0
  68. package/src/decorators/transaction.d.ts.map +1 -0
  69. package/src/decorators/transaction.js +80 -0
  70. package/src/decorators/transaction.js.map +1 -0
  71. package/src/decorators/validation.d.ts +188 -0
  72. package/src/decorators/validation.d.ts.map +1 -0
  73. package/src/decorators/validation.js +269 -0
  74. package/src/decorators/validation.js.map +1 -0
  75. package/src/decorators/zod-validation.d.ts +164 -4
  76. package/src/decorators/zod-validation.d.ts.map +1 -1
  77. package/src/decorators/zod-validation.js +692 -13
  78. package/src/decorators/zod-validation.js.map +1 -1
  79. package/src/index.d.ts +1 -0
  80. package/src/index.d.ts.map +1 -1
  81. package/src/index.js +1 -0
  82. package/src/index.js.map +1 -1
  83. package/src/interfaces/openApi/decoratorOptions.d.ts +760 -0
  84. package/src/interfaces/openApi/decoratorOptions.d.ts.map +1 -0
  85. package/src/interfaces/openApi/decoratorOptions.js +734 -0
  86. package/src/interfaces/openApi/decoratorOptions.js.map +1 -0
  87. package/src/interfaces/openApi/index.d.ts +1 -0
  88. package/src/interfaces/openApi/index.d.ts.map +1 -1
  89. package/src/interfaces/openApi/index.js +23 -0
  90. package/src/interfaces/openApi/index.js.map +1 -1
  91. package/src/interfaces/openApi/parameter.d.ts +2 -0
  92. package/src/interfaces/openApi/parameter.d.ts.map +1 -1
  93. package/src/interfaces/openApi/parameter.js +3 -1
  94. package/src/interfaces/openApi/parameter.js.map +1 -1
  95. package/src/interfaces/openApi/parameterSchema.d.ts +2 -0
  96. package/src/interfaces/openApi/parameterSchema.d.ts.map +1 -1
  97. package/src/interfaces/openApi/parameterSchema.js +3 -0
  98. package/src/interfaces/openApi/parameterSchema.js.map +1 -1
  99. package/src/openapi/builder.d.ts +249 -0
  100. package/src/openapi/builder.d.ts.map +1 -0
  101. package/src/openapi/builder.js +352 -0
  102. package/src/openapi/builder.js.map +1 -0
  103. package/src/openapi/controller.d.ts +153 -0
  104. package/src/openapi/controller.d.ts.map +1 -0
  105. package/src/openapi/controller.js +331 -0
  106. package/src/openapi/controller.js.map +1 -0
  107. package/src/openapi/index.d.ts +12 -0
  108. package/src/openapi/index.d.ts.map +1 -0
  109. package/src/openapi/index.js +20 -0
  110. package/src/openapi/index.js.map +1 -0
  111. package/src/openapi/markdown-generator.d.ts +52 -0
  112. package/src/openapi/markdown-generator.d.ts.map +1 -0
  113. package/src/openapi/markdown-generator.js +569 -0
  114. package/src/openapi/markdown-generator.js.map +1 -0
  115. package/src/openapi/middleware/index.d.ts +9 -0
  116. package/src/openapi/middleware/index.d.ts.map +1 -0
  117. package/src/openapi/middleware/index.js +15 -0
  118. package/src/openapi/middleware/index.js.map +1 -0
  119. package/src/openapi/middleware/redoc.d.ts +314 -0
  120. package/src/openapi/middleware/redoc.d.ts.map +1 -0
  121. package/src/openapi/middleware/redoc.js +181 -0
  122. package/src/openapi/middleware/redoc.js.map +1 -0
  123. package/src/openapi/middleware/swagger-ui.d.ts +123 -0
  124. package/src/openapi/middleware/swagger-ui.d.ts.map +1 -0
  125. package/src/openapi/middleware/swagger-ui.js +227 -0
  126. package/src/openapi/middleware/swagger-ui.js.map +1 -0
  127. package/src/openapi/schemas.d.ts +170 -0
  128. package/src/openapi/schemas.d.ts.map +1 -0
  129. package/src/openapi/schemas.js +340 -0
  130. package/src/openapi/schemas.js.map +1 -0
  131. package/src/registry/controller-registry.d.ts +78 -0
  132. package/src/registry/controller-registry.d.ts.map +1 -0
  133. package/src/registry/controller-registry.js +86 -0
  134. package/src/registry/controller-registry.js.map +1 -0
  135. package/src/registry/index.d.ts +2 -0
  136. package/src/registry/index.d.ts.map +1 -1
  137. package/src/registry/index.js +3 -1
  138. package/src/registry/index.js.map +1 -1
  139. package/src/routers/api.d.ts +2 -1
  140. package/src/routers/api.d.ts.map +1 -1
  141. package/src/routers/api.js +7 -1
  142. package/src/routers/api.js.map +1 -1
  143. package/src/types.d.ts +1 -0
  144. package/src/types.d.ts.map +1 -1
  145. package/src/types.js +1 -0
  146. package/src/types.js.map +1 -1
package/README.md CHANGED
@@ -11,6 +11,46 @@ It is an 'out of the box' solution with a specific recipe (Mongo, Express, React
11
11
 
12
12
  Part of [Express Suite](https://github.com/Digital-Defiance/express-suite)
13
13
 
14
+ ## What's New in v3.12.0
15
+
16
+ ✨ **Comprehensive Decorator API for Express Controllers** - A complete decorator-based API for defining controllers, routes, validation, and OpenAPI documentation with automatic spec generation.
17
+
18
+ **New Decorators:**
19
+
20
+ | Category | Decorators |
21
+ |----------|------------|
22
+ | Controller | `@Controller`, `@ApiController` |
23
+ | HTTP Methods | `@Get`, `@Post`, `@Put`, `@Delete`, `@Patch` (enhanced with OpenAPI support) |
24
+ | Authentication | `@RequireAuth`, `@RequireCryptoAuth`, `@Public`, `@AuthFailureStatus` |
25
+ | Parameters | `@Param`, `@Body`, `@Query`, `@Header`, `@CurrentUser`, `@EciesUser`, `@Req`, `@Res`, `@Next` |
26
+ | Validation | `@ValidateBody`, `@ValidateParams`, `@ValidateQuery` (Zod & express-validator) |
27
+ | Response | `@Returns`, `@ResponseDoc`, `@RawJson`, `@Paginated` |
28
+ | Middleware | `@UseMiddleware`, `@CacheResponse`, `@RateLimit` |
29
+ | Transaction | `@Transactional` |
30
+ | OpenAPI | `@ApiOperation`, `@ApiTags`, `@ApiSummary`, `@ApiDescription`, `@Deprecated`, `@ApiOperationId`, `@ApiExample` |
31
+ | OpenAPI Params | `@ApiParam`, `@ApiQuery`, `@ApiHeader`, `@ApiRequestBody` |
32
+ | Lifecycle | `@OnSuccess`, `@OnError`, `@Before`, `@After` |
33
+ | Schema | `@ApiSchema`, `@ApiProperty` |
34
+
35
+ **Key Features:**
36
+ - **Full RouteConfig Parity**: Every feature available in manual `RouteConfig` has a decorator equivalent
37
+ - **Automatic OpenAPI Generation**: Decorators automatically generate complete OpenAPI 3.0.3 specifications
38
+ - **Zod Integration**: Zod schemas are automatically converted to OpenAPI request body schemas
39
+ - **Metadata Merging**: Multiple decorators on the same method merge their OpenAPI metadata
40
+ - **Class-Level Inheritance**: Class-level decorators (auth, tags, middleware) apply to all methods unless overridden
41
+ - **Parameter Injection**: Clean, typed parameter injection with `@Param`, `@Body`, `@Query`, `@Header`
42
+ - **Lifecycle Hooks**: `@Before`, `@After`, `@OnSuccess`, `@OnError` for request lifecycle management
43
+
44
+ **Documentation Middleware:**
45
+ - `SwaggerUIMiddleware` - Serve Swagger UI with customization options
46
+ - `ReDocMiddleware` - Serve ReDoc documentation
47
+ - `generateMarkdownDocs()` - Generate markdown documentation from OpenAPI spec
48
+
49
+ **Migration Support:**
50
+ - Full backward compatibility with existing `RouteConfig` approach
51
+ - Comprehensive migration guide in `docs/DECORATOR_MIGRATION.md`
52
+ - Mixed usage supported (decorated + manual routes in same controller)
53
+
14
54
  ## What's New in v3.11.25
15
55
 
16
56
  ✨ **String Key Enum Registration & i18n v4 Integration** - Upgraded to `@digitaldefiance/i18n-lib` v4.0.5 with branded enum translation support and `translateStringKey()` for automatic component ID resolution.
@@ -919,6 +959,710 @@ describe('Cross-Package Integration', () => {
919
959
  });
920
960
  ```
921
961
 
962
+ ## Decorator API
963
+
964
+ The decorator API provides a declarative, type-safe approach to building Express APIs with automatic OpenAPI documentation generation. Decorators eliminate boilerplate while maintaining full feature parity with manual `RouteConfig`.
965
+
966
+ ### Overview
967
+
968
+ | Category | Decorators | Purpose |
969
+ |----------|------------|---------|
970
+ | Controller | `@Controller`, `@ApiController` | Define controller base path and OpenAPI metadata |
971
+ | HTTP Methods | `@Get`, `@Post`, `@Put`, `@Delete`, `@Patch` | Define route handlers with OpenAPI support |
972
+ | Authentication | `@RequireAuth`, `@RequireCryptoAuth`, `@Public`, `@AuthFailureStatus` | Control authentication requirements |
973
+ | Parameters | `@Param`, `@Body`, `@Query`, `@Header`, `@CurrentUser`, `@EciesUser`, `@Req`, `@Res`, `@Next` | Inject request data into handler parameters |
974
+ | Validation | `@ValidateBody`, `@ValidateParams`, `@ValidateQuery` | Validate request data with Zod or express-validator |
975
+ | Response | `@Returns`, `@ResponseDoc`, `@RawJson`, `@Paginated` | Document response types for OpenAPI |
976
+ | Middleware | `@UseMiddleware`, `@CacheResponse`, `@RateLimit` | Attach middleware to routes |
977
+ | Transaction | `@Transactional` | Wrap handlers in MongoDB transactions |
978
+ | OpenAPI | `@ApiOperation`, `@ApiTags`, `@ApiSummary`, `@ApiDescription`, `@Deprecated`, `@ApiOperationId`, `@ApiExample` | Add OpenAPI documentation |
979
+ | OpenAPI Params | `@ApiParam`, `@ApiQuery`, `@ApiHeader`, `@ApiRequestBody` | Document parameters with full OpenAPI metadata |
980
+ | Lifecycle | `@OnSuccess`, `@OnError`, `@Before`, `@After` | Hook into request lifecycle events |
981
+ | Handler Args | `@HandlerArgs` | Pass additional arguments to handlers |
982
+ | Schema | `@ApiSchema`, `@ApiProperty` | Register OpenAPI schemas from classes |
983
+
984
+ ### Quick Start Example
985
+
986
+ ```typescript
987
+ import {
988
+ ApiController,
989
+ Get,
990
+ Post,
991
+ Put,
992
+ Delete,
993
+ RequireAuth,
994
+ Public,
995
+ Param,
996
+ Body,
997
+ Query,
998
+ ValidateBody,
999
+ Returns,
1000
+ ApiTags,
1001
+ Transactional,
1002
+ DecoratorBaseController,
1003
+ } from '@digitaldefiance/node-express-suite';
1004
+ import { z } from 'zod';
1005
+
1006
+ // Define validation schema
1007
+ const CreateUserSchema = z.object({
1008
+ name: z.string().min(1).max(100),
1009
+ email: z.string().email(),
1010
+ role: z.enum(['admin', 'user']).optional(),
1011
+ });
1012
+
1013
+ @RequireAuth() // All routes require authentication by default
1014
+ @ApiTags('Users')
1015
+ @ApiController('/api/users', {
1016
+ description: 'User management endpoints',
1017
+ })
1018
+ class UserController extends DecoratorBaseController {
1019
+ @Public() // Override class-level auth - this route is public
1020
+ @Returns(200, 'User[]', { description: 'List of users' })
1021
+ @Get('/')
1022
+ async listUsers(
1023
+ @Query('page', { schema: { type: 'integer' } }) page: number = 1,
1024
+ @Query('limit') limit: number = 20,
1025
+ ) {
1026
+ return this.userService.findAll({ page, limit });
1027
+ }
1028
+
1029
+ @Returns(200, 'User', { description: 'User details' })
1030
+ @Returns(404, 'ErrorResponse', { description: 'User not found' })
1031
+ @Get('/:id')
1032
+ async getUser(@Param('id', { description: 'User ID' }) id: string) {
1033
+ return this.userService.findById(id);
1034
+ }
1035
+
1036
+ @ValidateBody(CreateUserSchema)
1037
+ @Transactional()
1038
+ @Returns(201, 'User', { description: 'Created user' })
1039
+ @Post('/')
1040
+ async createUser(@Body() data: z.infer<typeof CreateUserSchema>) {
1041
+ return this.userService.create(data);
1042
+ }
1043
+
1044
+ @Transactional()
1045
+ @Returns(200, 'User', { description: 'Updated user' })
1046
+ @Put('/:id')
1047
+ async updateUser(
1048
+ @Param('id') id: string,
1049
+ @Body() data: Partial<z.infer<typeof CreateUserSchema>>,
1050
+ ) {
1051
+ return this.userService.update(id, data);
1052
+ }
1053
+
1054
+ @Transactional()
1055
+ @Returns(204, undefined, { description: 'User deleted' })
1056
+ @Delete('/:id')
1057
+ async deleteUser(@Param('id') id: string) {
1058
+ await this.userService.delete(id);
1059
+ }
1060
+ }
1061
+ ```
1062
+
1063
+ ### Controller Decorators
1064
+
1065
+ ```typescript
1066
+ // Basic controller (no OpenAPI metadata)
1067
+ @Controller('/api/items')
1068
+ class ItemController {}
1069
+
1070
+ // OpenAPI-enabled controller with metadata
1071
+ @ApiController('/api/users', {
1072
+ tags: ['Users', 'Admin'],
1073
+ description: 'User management endpoints',
1074
+ deprecated: false,
1075
+ name: 'UserController', // Optional, defaults to class name
1076
+ })
1077
+ class UserController extends DecoratorBaseController {}
1078
+ ```
1079
+
1080
+ ### HTTP Method Decorators
1081
+
1082
+ All HTTP method decorators support inline OpenAPI options:
1083
+
1084
+ ```typescript
1085
+ @Get('/users/:id', {
1086
+ summary: 'Get user by ID',
1087
+ description: 'Retrieves a user by their unique identifier',
1088
+ tags: ['Users'],
1089
+ operationId: 'getUserById',
1090
+ deprecated: false,
1091
+ auth: true, // Shorthand for @RequireAuth()
1092
+ cryptoAuth: false, // Shorthand for @RequireCryptoAuth()
1093
+ rawJson: false, // Shorthand for @RawJson()
1094
+ transaction: false, // Shorthand for @Transactional()
1095
+ middleware: [], // Express middleware array
1096
+ validation: [], // express-validator chains
1097
+ schema: zodSchema, // Zod schema for body validation
1098
+ })
1099
+ async getUser() {}
1100
+ ```
1101
+
1102
+ ### Authentication Decorators
1103
+
1104
+ ```typescript
1105
+ // Require JWT authentication
1106
+ @RequireAuth()
1107
+ @ApiController('/api/secure')
1108
+ class SecureController {
1109
+ @Get('/data')
1110
+ getData() {} // Requires auth (inherited from class)
1111
+
1112
+ @Public()
1113
+ @Get('/public')
1114
+ getPublic() {} // No auth required (overrides class-level)
1115
+ }
1116
+
1117
+ // Require ECIES crypto authentication
1118
+ @RequireCryptoAuth()
1119
+ @Post('/encrypted')
1120
+ async createEncrypted() {}
1121
+
1122
+ // Custom auth failure status code
1123
+ @AuthFailureStatus(403)
1124
+ @Get('/admin')
1125
+ getAdmin() {} // Returns 403 instead of 401 on auth failure
1126
+ ```
1127
+
1128
+ ### Parameter Injection Decorators
1129
+
1130
+ ```typescript
1131
+ @Get('/:id')
1132
+ async getUser(
1133
+ // Path parameter with OpenAPI documentation
1134
+ @Param('id', { description: 'User ID', schema: { type: 'string', format: 'uuid' } }) id: string,
1135
+
1136
+ // Query parameters
1137
+ @Query('include', { description: 'Fields to include' }) include?: string,
1138
+
1139
+ // Header value
1140
+ @Header('X-Request-ID') requestId?: string,
1141
+
1142
+ // Authenticated user from JWT
1143
+ @CurrentUser() user: AuthenticatedUser,
1144
+
1145
+ // ECIES authenticated member
1146
+ @EciesUser() member: EciesMember,
1147
+
1148
+ // Raw Express objects (use sparingly)
1149
+ @Req() req: Request,
1150
+ @Res() res: Response,
1151
+ @Next() next: NextFunction,
1152
+ ) {}
1153
+
1154
+ @Post('/')
1155
+ async createUser(
1156
+ // Entire request body
1157
+ @Body() data: CreateUserDto,
1158
+
1159
+ // Specific field from body
1160
+ @Body('email') email: string,
1161
+ ) {}
1162
+ ```
1163
+
1164
+ ### Validation Decorators
1165
+
1166
+ ```typescript
1167
+ // Zod schema validation
1168
+ const CreateUserSchema = z.object({
1169
+ name: z.string().min(1),
1170
+ email: z.string().email(),
1171
+ });
1172
+
1173
+ @ValidateBody(CreateUserSchema)
1174
+ @Post('/')
1175
+ async createUser(@Body() data: z.infer<typeof CreateUserSchema>) {}
1176
+
1177
+ // express-validator chains
1178
+ @ValidateBody([
1179
+ body('name').isString().notEmpty(),
1180
+ body('email').isEmail(),
1181
+ ])
1182
+ @Post('/')
1183
+ async createUser() {}
1184
+
1185
+ // Language-aware validation with constants
1186
+ @ValidateBody(function(lang) {
1187
+ return [
1188
+ body('username')
1189
+ .matches(this.constants.UsernameRegex)
1190
+ .withMessage(getTranslation(lang, 'invalidUsername')),
1191
+ ];
1192
+ })
1193
+ @Post('/')
1194
+ async createUser() {}
1195
+
1196
+ // Validate path parameters
1197
+ @ValidateParams(z.object({ id: z.string().uuid() }))
1198
+ @Get('/:id')
1199
+ async getUser() {}
1200
+
1201
+ // Validate query parameters
1202
+ @ValidateQuery(z.object({
1203
+ page: z.coerce.number().int().positive().optional(),
1204
+ limit: z.coerce.number().int().max(100).optional(),
1205
+ }))
1206
+ @Get('/')
1207
+ async listUsers() {}
1208
+ ```
1209
+
1210
+ ### Response Decorators
1211
+
1212
+ ```typescript
1213
+ // Document response types (stackable for multiple status codes)
1214
+ @Returns(200, 'User', { description: 'User found' })
1215
+ @Returns(404, 'ErrorResponse', { description: 'User not found' })
1216
+ @Get('/:id')
1217
+ async getUser() {}
1218
+
1219
+ // Inline schema for simple responses
1220
+ @ResponseDoc(200, {
1221
+ description: 'Health check response',
1222
+ schema: {
1223
+ type: 'object',
1224
+ properties: {
1225
+ status: { type: 'string' },
1226
+ timestamp: { type: 'string', format: 'date-time' },
1227
+ },
1228
+ },
1229
+ })
1230
+ @Get('/health')
1231
+ healthCheck() {}
1232
+
1233
+ // Raw JSON response (bypasses response wrapper)
1234
+ @RawJson()
1235
+ @Get('/raw')
1236
+ getRawData() {}
1237
+
1238
+ // Paginated endpoint (adds page/limit query params to OpenAPI)
1239
+ @Paginated({ defaultPageSize: 20, maxPageSize: 100 })
1240
+ @Returns(200, 'User[]')
1241
+ @Get('/')
1242
+ async listUsers() {}
1243
+
1244
+ // Offset-based pagination
1245
+ @Paginated({ useOffset: true, defaultPageSize: 20 })
1246
+ @Get('/items')
1247
+ async listItems() {}
1248
+ ```
1249
+
1250
+ ### Middleware Decorators
1251
+
1252
+ ```typescript
1253
+ // Attach middleware (class or method level)
1254
+ @UseMiddleware(loggerMiddleware)
1255
+ @ApiController('/api/data')
1256
+ class DataController {
1257
+ @UseMiddleware([validateMiddleware, sanitizeMiddleware])
1258
+ @Post('/')
1259
+ createData() {}
1260
+ }
1261
+
1262
+ // Response caching
1263
+ @CacheResponse({ ttl: 60 }) // Cache for 60 seconds
1264
+ @Get('/static')
1265
+ getStaticData() {}
1266
+
1267
+ @CacheResponse({
1268
+ ttl: 300,
1269
+ varyByUser: true, // Different cache per user
1270
+ varyByQuery: ['page'], // Different cache per query param
1271
+ keyPrefix: 'users', // Custom cache key prefix
1272
+ })
1273
+ @Get('/user-data')
1274
+ getUserData() {}
1275
+
1276
+ // Rate limiting (auto-adds 429 response to OpenAPI)
1277
+ @RateLimit({ requests: 5, window: 60 }) // 5 requests per minute
1278
+ @Post('/login')
1279
+ login() {}
1280
+
1281
+ @RateLimit({
1282
+ requests: 100,
1283
+ window: 3600,
1284
+ byUser: true, // Limit per user instead of IP
1285
+ message: 'Hourly limit exceeded',
1286
+ keyGenerator: (req) => req.ip, // Custom key generator
1287
+ })
1288
+ @Get('/api-data')
1289
+ getApiData() {}
1290
+ ```
1291
+
1292
+ ### Transaction Decorator
1293
+
1294
+ ```typescript
1295
+ // Basic transaction
1296
+ @Transactional()
1297
+ @Post('/')
1298
+ async createOrder() {
1299
+ // this.session available automatically in DecoratorBaseController
1300
+ await this.orderService.create(data, this.session);
1301
+ }
1302
+
1303
+ // Transaction with timeout
1304
+ @Transactional({ timeout: 30000 }) // 30 second timeout
1305
+ @Post('/bulk')
1306
+ async bulkCreate() {}
1307
+ ```
1308
+
1309
+ ### OpenAPI Operation Decorators
1310
+
1311
+ ```typescript
1312
+ // Full operation metadata
1313
+ @ApiOperation({
1314
+ summary: 'Get user by ID',
1315
+ description: 'Retrieves a user by their unique identifier',
1316
+ tags: ['Users'],
1317
+ operationId: 'getUserById',
1318
+ deprecated: false,
1319
+ })
1320
+ @Get('/:id')
1321
+ getUser() {}
1322
+
1323
+ // Individual decorators (composable)
1324
+ @ApiSummary('Get user by ID')
1325
+ @ApiDescription('Retrieves a user by their unique identifier')
1326
+ @ApiTags('Users', 'Public')
1327
+ @ApiOperationId('getUserById')
1328
+ @Deprecated()
1329
+ @Get('/:id')
1330
+ getUser() {}
1331
+
1332
+ // Class-level tags apply to all methods
1333
+ @ApiTags('Users')
1334
+ @ApiController('/api/users')
1335
+ class UserController {
1336
+ @ApiTags('Admin') // Adds to class tags: ['Users', 'Admin']
1337
+ @Get('/admin')
1338
+ adminEndpoint() {}
1339
+ }
1340
+
1341
+ // Add examples
1342
+ @ApiExample({
1343
+ name: 'validUser',
1344
+ summary: 'A valid user response',
1345
+ value: { id: '123', name: 'John Doe', email: 'john@example.com' },
1346
+ type: 'response',
1347
+ statusCode: 200,
1348
+ })
1349
+ @Get('/:id')
1350
+ getUser() {}
1351
+ ```
1352
+
1353
+ ### OpenAPI Parameter Decorators
1354
+
1355
+ ```typescript
1356
+ // Document path parameter with full metadata
1357
+ @ApiParam('id', {
1358
+ description: 'User ID',
1359
+ schema: { type: 'string', format: 'uuid' },
1360
+ example: '123e4567-e89b-12d3-a456-426614174000',
1361
+ })
1362
+ @Get('/:id')
1363
+ getUser(@Param('id') id: string) {}
1364
+
1365
+ // Document query parameters
1366
+ @ApiQuery('page', {
1367
+ description: 'Page number',
1368
+ schema: { type: 'integer', minimum: 1 },
1369
+ required: false,
1370
+ example: 1,
1371
+ })
1372
+ @ApiQuery('sort', {
1373
+ description: 'Sort field',
1374
+ enum: ['name', 'date', 'id'],
1375
+ })
1376
+ @Get('/')
1377
+ listUsers() {}
1378
+
1379
+ // Document headers
1380
+ @ApiHeader('X-Request-ID', {
1381
+ description: 'Request tracking ID',
1382
+ schema: { type: 'string', format: 'uuid' },
1383
+ required: true,
1384
+ })
1385
+ @Get('/')
1386
+ getData() {}
1387
+
1388
+ // Document request body
1389
+ @ApiRequestBody({
1390
+ schema: 'CreateUserDto', // Reference to registered schema
1391
+ description: 'User data to create',
1392
+ required: true,
1393
+ example: { name: 'John', email: 'john@example.com' },
1394
+ })
1395
+ @Post('/')
1396
+ createUser() {}
1397
+
1398
+ // Or with Zod schema
1399
+ @ApiRequestBody({
1400
+ schema: CreateUserSchema, // Zod schema
1401
+ description: 'User data',
1402
+ })
1403
+ @Post('/')
1404
+ createUser() {}
1405
+ ```
1406
+
1407
+ ### Lifecycle Decorators
1408
+
1409
+ ```typescript
1410
+ // Execute after successful response
1411
+ @OnSuccess(({ req, result }) => {
1412
+ console.log(`User ${req.params.id} fetched:`, result);
1413
+ })
1414
+ @Get('/:id')
1415
+ getUser() {}
1416
+
1417
+ // Execute on error
1418
+ @OnError(({ req, error }) => {
1419
+ logger.error(`Error on ${req.path}:`, error);
1420
+ })
1421
+ @Get('/:id')
1422
+ getUser() {}
1423
+
1424
+ // Execute before handler
1425
+ @Before(({ req }) => {
1426
+ console.log(`Incoming request to ${req.path}`);
1427
+ })
1428
+ @Get('/')
1429
+ listUsers() {}
1430
+
1431
+ // Execute after handler (success or error)
1432
+ @After(({ req, result, error }) => {
1433
+ metrics.recordRequest(req.path, error ? 'error' : 'success');
1434
+ })
1435
+ @Get('/')
1436
+ listUsers() {}
1437
+
1438
+ // Class-level hooks apply to all methods
1439
+ @OnError(({ error }) => logger.error(error))
1440
+ @ApiController('/api/users')
1441
+ class UserController {}
1442
+ ```
1443
+
1444
+ ### Handler Args Decorator
1445
+
1446
+ ```typescript
1447
+ // Pass additional arguments to handler
1448
+ @HandlerArgs({ maxItems: 100 })
1449
+ @Get('/')
1450
+ listItems(req: Request, config: { maxItems: number }) {
1451
+ // config.maxItems === 100
1452
+ }
1453
+
1454
+ // Multiple arguments
1455
+ @HandlerArgs('prefix', 42, { option: true })
1456
+ @Post('/')
1457
+ createItem(req: Request, prefix: string, count: number, options: object) {}
1458
+ ```
1459
+
1460
+ ### Schema Decorators
1461
+
1462
+ ```typescript
1463
+ // Register class as OpenAPI schema
1464
+ @ApiSchema({ description: 'User entity' })
1465
+ class User {
1466
+ @ApiProperty({
1467
+ type: 'string',
1468
+ format: 'uuid',
1469
+ description: 'Unique identifier',
1470
+ example: '123e4567-e89b-12d3-a456-426614174000',
1471
+ })
1472
+ id: string;
1473
+
1474
+ @ApiProperty({
1475
+ type: 'string',
1476
+ format: 'email',
1477
+ required: true,
1478
+ })
1479
+ email: string;
1480
+
1481
+ @ApiProperty({
1482
+ type: 'integer',
1483
+ minimum: 0,
1484
+ maximum: 150,
1485
+ })
1486
+ age?: number;
1487
+
1488
+ @ApiProperty({
1489
+ type: 'array',
1490
+ items: 'Role', // Reference to another schema
1491
+ })
1492
+ roles: Role[];
1493
+ }
1494
+
1495
+ // Inheritance is supported
1496
+ @ApiSchema()
1497
+ class AdminUser extends User {
1498
+ @ApiProperty({ type: 'string' })
1499
+ adminLevel: string;
1500
+ }
1501
+ ```
1502
+
1503
+ ### Decorator Composition and Stacking
1504
+
1505
+ Decorators can be freely composed and stacked. Order matters for some decorators:
1506
+
1507
+ ```typescript
1508
+ // Middleware executes top to bottom
1509
+ @UseMiddleware(first)
1510
+ @UseMiddleware(second)
1511
+ @UseMiddleware(third)
1512
+ @Get('/')
1513
+ handler() {} // Executes: first → second → third → handler
1514
+
1515
+ // Multiple @Returns accumulate (don't replace)
1516
+ @Returns(200, 'User')
1517
+ @Returns(400, 'ValidationError')
1518
+ @Returns(404, 'NotFoundError')
1519
+ @Returns(500, 'ServerError')
1520
+ @Get('/:id')
1521
+ getUser() {}
1522
+
1523
+ // Tags merge (class + method)
1524
+ @ApiTags('Users')
1525
+ @ApiController('/api/users')
1526
+ class UserController {
1527
+ @ApiTags('Admin')
1528
+ @Get('/admin')
1529
+ admin() {} // Tags: ['Users', 'Admin']
1530
+ }
1531
+
1532
+ // Method-level overrides class-level for same field
1533
+ @RequireAuth()
1534
+ @ApiController('/api/data')
1535
+ class DataController {
1536
+ @Get('/private')
1537
+ private() {} // Requires auth (inherited)
1538
+
1539
+ @Public()
1540
+ @Get('/public')
1541
+ public() {} // No auth (overridden)
1542
+ }
1543
+ ```
1544
+
1545
+ ### Comparison: Manual RouteConfig vs Decorators
1546
+
1547
+ | RouteConfig Field | Decorator Equivalent |
1548
+ |-------------------|---------------------|
1549
+ | `method` | `@Get`, `@Post`, `@Put`, `@Delete`, `@Patch` |
1550
+ | `path` | Decorator path argument |
1551
+ | `handlerKey` | Decorated method name (automatic) |
1552
+ | `handlerArgs` | `@HandlerArgs(...args)` |
1553
+ | `useAuthentication` | `@RequireAuth()` |
1554
+ | `useCryptoAuthentication` | `@RequireCryptoAuth()` |
1555
+ | `middleware` | `@UseMiddleware(...)` |
1556
+ | `validation` | `@ValidateBody()`, `@ValidateParams()`, `@ValidateQuery()` |
1557
+ | `rawJsonHandler` | `@RawJson()` |
1558
+ | `authFailureStatusCode` | `@AuthFailureStatus(code)` |
1559
+ | `useTransaction` | `@Transactional()` |
1560
+ | `transactionTimeout` | `@Transactional({ timeout })` |
1561
+ | `openapi.summary` | `@ApiSummary(text)` |
1562
+ | `openapi.description` | `@ApiDescription(text)` |
1563
+ | `openapi.tags` | `@ApiTags(...tags)` |
1564
+ | `openapi.operationId` | `@ApiOperationId(id)` |
1565
+ | `openapi.deprecated` | `@Deprecated()` |
1566
+ | `openapi.requestBody` | `@ApiRequestBody(options)` |
1567
+ | `openapi.responses` | `@Returns(code, schema)` |
1568
+ | `openapi.parameters` | `@ApiParam()`, `@ApiQuery()`, `@ApiHeader()` |
1569
+
1570
+ ### Decorator Options TypeScript Types
1571
+
1572
+ ```typescript
1573
+ // Controller options
1574
+ interface ApiControllerOptions {
1575
+ tags?: string[];
1576
+ description?: string;
1577
+ deprecated?: boolean;
1578
+ name?: string;
1579
+ }
1580
+
1581
+ // Route decorator options
1582
+ interface RouteDecoratorOptions<TLanguage> {
1583
+ validation?: ValidationChain[] | ((lang: TLanguage) => ValidationChain[]);
1584
+ schema?: z.ZodSchema;
1585
+ middleware?: RequestHandler[];
1586
+ auth?: boolean;
1587
+ cryptoAuth?: boolean;
1588
+ rawJson?: boolean;
1589
+ transaction?: boolean;
1590
+ transactionTimeout?: number;
1591
+ summary?: string;
1592
+ description?: string;
1593
+ tags?: string[];
1594
+ operationId?: string;
1595
+ deprecated?: boolean;
1596
+ openapi?: OpenAPIRouteMetadata;
1597
+ }
1598
+
1599
+ // Parameter decorator options
1600
+ interface ParamDecoratorOptions {
1601
+ description?: string;
1602
+ example?: unknown;
1603
+ required?: boolean;
1604
+ schema?: OpenAPIParameterSchema;
1605
+ }
1606
+
1607
+ // Cache options
1608
+ interface CacheDecoratorOptions {
1609
+ ttl: number; // Time to live in seconds
1610
+ keyPrefix?: string;
1611
+ varyByUser?: boolean;
1612
+ varyByQuery?: string[];
1613
+ }
1614
+
1615
+ // Rate limit options
1616
+ interface RateLimitDecoratorOptions {
1617
+ requests: number; // Max requests
1618
+ window: number; // Time window in seconds
1619
+ message?: string;
1620
+ byUser?: boolean;
1621
+ keyGenerator?: (req: Request) => string;
1622
+ }
1623
+
1624
+ // Pagination options
1625
+ interface PaginatedDecoratorOptions {
1626
+ defaultPageSize?: number;
1627
+ maxPageSize?: number;
1628
+ useOffset?: boolean; // Use offset/limit instead of page/limit
1629
+ }
1630
+
1631
+ // Transaction options
1632
+ interface TransactionalDecoratorOptions {
1633
+ timeout?: number; // Timeout in milliseconds
1634
+ }
1635
+ ```
1636
+
1637
+ ### Troubleshooting
1638
+
1639
+ **Decorators not working?**
1640
+ - Ensure `experimentalDecorators: true` and `emitDecoratorMetadata: true` in tsconfig.json
1641
+ - Import `reflect-metadata` at the top of your entry file
1642
+ - Extend `DecoratorBaseController` for full decorator support
1643
+
1644
+ **OpenAPI metadata not appearing?**
1645
+ - Use `@ApiController` instead of `@Controller` for OpenAPI support
1646
+ - Ensure decorators are applied before HTTP method decorators (bottom-up execution)
1647
+ - Check that schemas are registered with `@ApiSchema` or `OpenAPISchemaRegistry`
1648
+
1649
+ **Authentication not enforced?**
1650
+ - Verify `@RequireAuth()` is applied at class or method level
1651
+ - Check that `@Public()` isn't overriding at method level
1652
+ - Ensure auth middleware is configured in your application
1653
+
1654
+ **Validation errors not returning 400?**
1655
+ - Validation decorators automatically add 400 response to OpenAPI
1656
+ - Ensure validation middleware is properly configured
1657
+ - Check that Zod schemas or express-validator chains are valid
1658
+
1659
+ **Parameter injection not working?**
1660
+ - Parameter decorators must be on method parameters, not properties
1661
+ - Ensure the handler is called through the decorated controller
1662
+ - Check parameter index matches the decorator position
1663
+
1664
+ For detailed migration instructions, see [docs/DECORATOR_MIGRATION.md](./docs/DECORATOR_MIGRATION.md).
1665
+
922
1666
  ## Documentation
923
1667
 
924
1668
  📚 **Comprehensive documentation is available in the [`docs/`](./docs) directory.**
@@ -1360,6 +2104,120 @@ The following v1.x patterns still work in v2.0:
1360
2104
 
1361
2105
  ## ChangeLog
1362
2106
 
2107
+ ### Version 3.12.0
2108
+
2109
+ **Comprehensive Decorator API for Express Controllers**
2110
+
2111
+ This release introduces a complete decorator-based API for defining controllers, routes, validation, and OpenAPI documentation. The decorator system provides a declarative, type-safe approach to building Express APIs with automatic OpenAPI 3.0.3 specification generation.
2112
+
2113
+ **New Decorator Categories:**
2114
+
2115
+ *Controller Decorators:*
2116
+ - `@Controller(basePath)` - Define controller base path (existing, preserved for backward compatibility)
2117
+ - `@ApiController(basePath, options?)` - Define controller with OpenAPI metadata (tags, description, deprecated)
2118
+
2119
+ *HTTP Method Decorators (Enhanced):*
2120
+ - `@Get`, `@Post`, `@Put`, `@Delete`, `@Patch` - Now support inline OpenAPI options (summary, description, tags, operationId, deprecated)
2121
+
2122
+ *Authentication Decorators:*
2123
+ - `@RequireAuth()` - Require JWT authentication (auto-adds 401 response to OpenAPI)
2124
+ - `@RequireCryptoAuth()` - Require ECIES crypto authentication
2125
+ - `@Public()` - Mark route as public (overrides class-level auth)
2126
+ - `@AuthFailureStatus(code)` - Set custom auth failure status code
2127
+
2128
+ *Parameter Injection Decorators:*
2129
+ - `@Param(name, options?)` - Inject path parameter (auto-adds to OpenAPI)
2130
+ - `@Body(field?)` - Inject request body or specific field
2131
+ - `@Query(name, options?)` - Inject query parameter (auto-adds to OpenAPI)
2132
+ - `@Header(name, options?)` - Inject header value (auto-adds to OpenAPI)
2133
+ - `@CurrentUser()` - Inject authenticated user (`req.user`)
2134
+ - `@EciesUser()` - Inject ECIES authenticated member (`req.eciesUser`)
2135
+ - `@Req()`, `@Res()`, `@Next()` - Inject Express request/response/next
2136
+
2137
+ *Validation Decorators:*
2138
+ - `@ValidateBody(schema)` - Validate request body with Zod or express-validator (auto-adds 400 response)
2139
+ - `@ValidateParams(schema)` - Validate path parameters
2140
+ - `@ValidateQuery(schema)` - Validate query parameters
2141
+
2142
+ *Response Decorators:*
2143
+ - `@Returns(statusCode, schema, options?)` - Document response type (stackable for multiple status codes)
2144
+ - `@ResponseDoc(responses)` - Document multiple responses at once
2145
+ - `@RawJson()` - Bypass standard response wrapper
2146
+ - `@Paginated(options?)` - Add pagination query parameters and response envelope
2147
+
2148
+ *Middleware Decorators:*
2149
+ - `@UseMiddleware(...middleware)` - Attach Express middleware (class or method level)
2150
+ - `@CacheResponse(options)` - Add response caching middleware
2151
+ - `@RateLimit(options)` - Add rate limiting middleware (auto-adds 429 response)
2152
+
2153
+ *Transaction Decorator:*
2154
+ - `@Transactional(options?)` - Wrap handler in MongoDB transaction with optional timeout
2155
+
2156
+ *OpenAPI Operation Decorators:*
2157
+ - `@ApiOperation(metadata)` - Set full OpenAPI operation metadata
2158
+ - `@ApiTags(...tags)` - Add tags (class or method level, additive)
2159
+ - `@ApiSummary(text)` - Set operation summary
2160
+ - `@ApiDescription(text)` - Set operation description
2161
+ - `@Deprecated()` - Mark operation as deprecated
2162
+ - `@ApiOperationId(id)` - Set unique operation ID
2163
+ - `@ApiExample(example)` - Add request/response examples
2164
+
2165
+ *OpenAPI Parameter Decorators:*
2166
+ - `@ApiParam(name, options)` - Document path parameter with full OpenAPI metadata
2167
+ - `@ApiQuery(name, options)` - Document query parameter
2168
+ - `@ApiHeader(name, options)` - Document header parameter
2169
+ - `@ApiRequestBody(options)` - Document request body with schema, example, description
2170
+
2171
+ *Lifecycle Decorators:*
2172
+ - `@OnSuccess(callback)` - Execute after successful response
2173
+ - `@OnError(callback)` - Execute when error occurs
2174
+ - `@Before(callback)` - Execute before handler
2175
+ - `@After(callback)` - Execute after handler (success or error)
2176
+
2177
+ *Handler Args Decorator:*
2178
+ - `@HandlerArgs(...args)` - Pass additional arguments to handler
2179
+
2180
+ *Schema Decorators:*
2181
+ - `@ApiSchema(name?)` - Register class as OpenAPI schema
2182
+ - `@ApiProperty(options)` - Add property metadata (type, description, required, example)
2183
+
2184
+ **New Documentation Middleware:**
2185
+ - `SwaggerUIMiddleware(options)` - Serve Swagger UI with customization (title, favicon, CSS, JS)
2186
+ - `ReDocMiddleware(options)` - Serve ReDoc documentation with customization
2187
+ - `generateMarkdownDocs(spec)` - Generate markdown documentation from OpenAPI spec
2188
+
2189
+ **New Infrastructure:**
2190
+ - `src/decorators/metadata-keys.ts` - Symbol constants for all decorator metadata
2191
+ - `src/decorators/metadata-collector.ts` - Utility for metadata operations
2192
+ - `src/interfaces/openApi/decoratorOptions.ts` - All decorator option interfaces
2193
+ - Enhanced `DecoratorBaseController` with full metadata collection and parameter injection
2194
+
2195
+ **Zod Integration:**
2196
+ - Comprehensive Zod to OpenAPI schema conversion
2197
+ - Support for nested objects, arrays, unions, enums
2198
+ - Automatic extraction of descriptions and examples from Zod schemas
2199
+
2200
+ **Key Features:**
2201
+ - Full feature parity with manual `RouteConfig` approach
2202
+ - Automatic OpenAPI 3.0.3 specification generation
2203
+ - Metadata merging from multiple decorators
2204
+ - Class-level decorator inheritance with method-level overrides
2205
+ - Backward compatible - existing code continues to work unchanged
2206
+
2207
+ **Documentation:**
2208
+ - Updated README with comprehensive Decorator API section
2209
+ - New `docs/DECORATOR_MIGRATION.md` with complete migration guide
2210
+ - Updated `docs/CONTROLLERS.md` featuring decorator-based approach
2211
+ - JSDoc documentation on all public decorators and types
2212
+
2213
+ **Testing:**
2214
+ - Property-based tests for decorator correctness properties
2215
+ - Integration tests for full controller with all decorators
2216
+ - E2E tests for HTTP requests to decorated endpoints
2217
+
2218
+ **Deprecations:**
2219
+ - None. This release is fully backward compatible with existing code.
2220
+
1363
2221
  ### Version 3.11.x (3.11.0 - 3.11.20)
1364
2222
 
1365
2223
  **String Key Enum Registration & translateStringKey Support**