@expressots/adapter-express 3.0.0 → 4.0.0-preview.3

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 (244) hide show
  1. package/LICENSE.md +21 -21
  2. package/README.md +61 -118
  3. package/lib/CHANGELOG.md +36 -5
  4. package/lib/README.md +61 -118
  5. package/lib/cjs/adapter-express/application-express.base.js +3 -1
  6. package/lib/cjs/adapter-express/application-express.js +1405 -85
  7. package/lib/cjs/adapter-express/express-utils/conditional-middleware.js +102 -0
  8. package/lib/cjs/adapter-express/express-utils/constants.js +17 -0
  9. package/lib/cjs/adapter-express/express-utils/content-negotiation-decorators.js +129 -0
  10. package/lib/cjs/adapter-express/express-utils/decorators.js +225 -59
  11. package/lib/cjs/adapter-express/express-utils/exception-filter-decorators.js +11 -0
  12. package/lib/cjs/adapter-express/express-utils/guard-context-factory.js +84 -0
  13. package/lib/cjs/adapter-express/express-utils/guard-middleware.js +115 -0
  14. package/lib/cjs/adapter-express/express-utils/guard-utils.js +18 -0
  15. package/lib/cjs/adapter-express/express-utils/http-context-store.js +15 -0
  16. package/lib/cjs/adapter-express/express-utils/http-status-middleware.js +37 -2
  17. package/lib/cjs/adapter-express/express-utils/index.js +67 -1
  18. package/lib/cjs/adapter-express/express-utils/interceptor-middleware.js +132 -0
  19. package/lib/cjs/adapter-express/express-utils/inversify-express-server.js +827 -64
  20. package/lib/cjs/adapter-express/express-utils/lazy-module-middleware.js +241 -0
  21. package/lib/cjs/adapter-express/express-utils/middleware-composition.js +95 -0
  22. package/lib/cjs/adapter-express/express-utils/path-pattern-compat.js +129 -0
  23. package/lib/cjs/adapter-express/express-utils/permission-preloader.middleware.js +48 -0
  24. package/lib/cjs/adapter-express/express-utils/route-constraints.js +104 -0
  25. package/lib/cjs/adapter-express/express-utils/scope-extractor.interface.js +2 -0
  26. package/lib/cjs/adapter-express/express-utils/scope-extractor.js +66 -0
  27. package/lib/cjs/adapter-express/express-utils/setup-authorization.js +71 -0
  28. package/lib/cjs/adapter-express/express-utils/setup-event-system.js +113 -0
  29. package/lib/cjs/adapter-express/express-utils/setup-interceptors.js +103 -0
  30. package/lib/cjs/adapter-express/express-utils/setup-lazy-loading.js +228 -0
  31. package/lib/cjs/adapter-express/express-utils/utils.js +30 -12
  32. package/lib/cjs/adapter-express/express-utils/validation-decorators.js +205 -0
  33. package/lib/cjs/adapter-express/express-utils/validation-service.js +252 -0
  34. package/lib/cjs/adapter-express/index.js +7 -5
  35. package/lib/cjs/adapter-express/micro-api/application-express-micro-route.js +31 -1
  36. package/lib/cjs/adapter-express/micro-api/application-express-micro.js +8 -38
  37. package/lib/cjs/adapter-express/micro-api/gateway/circuit-breaker.js +174 -0
  38. package/lib/cjs/adapter-express/micro-api/gateway/index.js +11 -0
  39. package/lib/cjs/adapter-express/micro-api/gateway/service-proxy.js +214 -0
  40. package/lib/cjs/adapter-express/micro-api/index.js +27 -3
  41. package/lib/cjs/adapter-express/micro-api/micro.js +272 -0
  42. package/lib/cjs/adapter-express/micro-api/queue/index.js +8 -0
  43. package/lib/cjs/adapter-express/micro-api/queue/queue.interface.js +2 -0
  44. package/lib/cjs/adapter-express/micro-api/queue/rabbitmq-consumer.js +255 -0
  45. package/lib/cjs/adapter-express/micro-api/serverless/aws-lambda.adapter.js +183 -0
  46. package/lib/cjs/adapter-express/micro-api/serverless/cloudflare.adapter.js +158 -0
  47. package/lib/cjs/adapter-express/micro-api/serverless/index.js +12 -0
  48. package/lib/cjs/adapter-express/micro-api/serverless/vercel.adapter.js +102 -0
  49. package/lib/cjs/adapter-express/micro-api/service-mesh/index.js +10 -0
  50. package/lib/cjs/adapter-express/micro-api/service-mesh/service-client.js +194 -0
  51. package/lib/cjs/adapter-express/micro-api/service-mesh/service-discovery.js +261 -0
  52. package/lib/cjs/adapter-express/middleware/index.js +21 -0
  53. package/lib/cjs/adapter-express/middleware/request-logging.middleware.js +244 -0
  54. package/lib/cjs/adapter-express/render/engine.js +15 -15
  55. package/lib/cjs/adapter-express/render/index.js +5 -0
  56. package/lib/cjs/adapter-express/studio/index.js +10 -0
  57. package/lib/cjs/adapter-express/studio/studio-integration.js +267 -0
  58. package/lib/cjs/index.js +1 -1
  59. package/lib/cjs/types/adapter-express/application-express.base.d.ts +20 -7
  60. package/lib/cjs/types/adapter-express/application-express.d.ts +316 -33
  61. package/lib/cjs/types/adapter-express/express-utils/base-middleware.d.ts +2 -2
  62. package/lib/cjs/types/adapter-express/express-utils/conditional-middleware.d.ts +97 -0
  63. package/lib/cjs/types/adapter-express/express-utils/constants.d.ts +13 -0
  64. package/lib/cjs/types/adapter-express/express-utils/content-negotiation-decorators.d.ts +94 -0
  65. package/lib/cjs/types/adapter-express/express-utils/decorators.d.ts +54 -6
  66. package/lib/cjs/types/adapter-express/express-utils/exception-filter-decorators.d.ts +6 -0
  67. package/lib/cjs/types/adapter-express/express-utils/guard-context-factory.d.ts +17 -0
  68. package/lib/cjs/types/adapter-express/express-utils/guard-middleware.d.ts +22 -0
  69. package/lib/cjs/types/adapter-express/express-utils/guard-utils.d.ts +11 -0
  70. package/lib/cjs/types/adapter-express/express-utils/http-context-store.d.ts +20 -0
  71. package/lib/cjs/types/adapter-express/express-utils/httpResponseMessage.d.ts +1 -1
  72. package/lib/cjs/types/adapter-express/express-utils/index.d.ts +30 -2
  73. package/lib/cjs/types/adapter-express/express-utils/interceptor-middleware.d.ts +40 -0
  74. package/lib/cjs/types/adapter-express/express-utils/interfaces.d.ts +42 -5
  75. package/lib/cjs/types/adapter-express/express-utils/inversify-express-server.d.ts +114 -2
  76. package/lib/cjs/types/adapter-express/express-utils/lazy-module-middleware.d.ts +122 -0
  77. package/lib/cjs/types/adapter-express/express-utils/middleware-composition.d.ts +85 -0
  78. package/lib/cjs/types/adapter-express/express-utils/path-pattern-compat.d.ts +66 -0
  79. package/lib/cjs/types/adapter-express/express-utils/permission-preloader.middleware.d.ts +10 -0
  80. package/lib/cjs/types/adapter-express/express-utils/route-constraints.d.ts +98 -0
  81. package/lib/cjs/types/adapter-express/express-utils/scope-extractor.d.ts +21 -0
  82. package/lib/cjs/types/adapter-express/express-utils/scope-extractor.interface.d.ts +12 -0
  83. package/lib/cjs/types/adapter-express/express-utils/setup-authorization.d.ts +34 -0
  84. package/lib/cjs/types/adapter-express/express-utils/setup-event-system.d.ts +118 -0
  85. package/lib/cjs/types/adapter-express/express-utils/setup-interceptors.d.ts +115 -0
  86. package/lib/cjs/types/adapter-express/express-utils/setup-lazy-loading.d.ts +123 -0
  87. package/lib/cjs/types/adapter-express/express-utils/utils.d.ts +17 -2
  88. package/lib/cjs/types/adapter-express/express-utils/validation-decorators.d.ts +145 -0
  89. package/lib/cjs/types/adapter-express/express-utils/validation-service.d.ts +88 -0
  90. package/lib/cjs/types/adapter-express/index.d.ts +6 -4
  91. package/lib/cjs/types/adapter-express/micro-api/application-express-micro-route.d.ts +25 -14
  92. package/lib/cjs/types/adapter-express/micro-api/application-express-micro.d.ts +3 -10
  93. package/lib/cjs/types/adapter-express/micro-api/gateway/circuit-breaker.d.ts +111 -0
  94. package/lib/cjs/types/adapter-express/micro-api/gateway/index.d.ts +5 -0
  95. package/lib/cjs/types/adapter-express/micro-api/gateway/service-proxy.d.ts +83 -0
  96. package/lib/cjs/types/adapter-express/micro-api/index.d.ts +7 -1
  97. package/lib/cjs/types/adapter-express/micro-api/micro.d.ts +83 -0
  98. package/lib/cjs/types/adapter-express/micro-api/queue/index.d.ts +5 -0
  99. package/lib/cjs/types/adapter-express/micro-api/queue/queue.interface.d.ts +60 -0
  100. package/lib/cjs/types/adapter-express/micro-api/queue/rabbitmq-consumer.d.ts +86 -0
  101. package/lib/cjs/types/adapter-express/micro-api/serverless/aws-lambda.adapter.d.ts +77 -0
  102. package/lib/cjs/types/adapter-express/micro-api/serverless/cloudflare.adapter.d.ts +64 -0
  103. package/lib/cjs/types/adapter-express/micro-api/serverless/index.d.ts +6 -0
  104. package/lib/cjs/types/adapter-express/micro-api/serverless/vercel.adapter.d.ts +56 -0
  105. package/lib/cjs/types/adapter-express/micro-api/service-mesh/index.d.ts +5 -0
  106. package/lib/cjs/types/adapter-express/micro-api/service-mesh/service-client.d.ts +122 -0
  107. package/lib/cjs/types/adapter-express/micro-api/service-mesh/service-discovery.d.ts +150 -0
  108. package/lib/cjs/types/adapter-express/middleware/index.d.ts +5 -0
  109. package/lib/cjs/types/adapter-express/middleware/request-logging.middleware.d.ts +65 -0
  110. package/lib/cjs/types/adapter-express/render/index.d.ts +1 -0
  111. package/lib/cjs/types/adapter-express/studio/index.d.ts +1 -0
  112. package/lib/cjs/types/adapter-express/studio/studio-integration.d.ts +170 -0
  113. package/lib/cjs/types/index.d.ts +1 -1
  114. package/lib/esm/adapter-express/application-express.base.js +24 -0
  115. package/lib/esm/adapter-express/application-express.js +1656 -0
  116. package/lib/esm/adapter-express/application-express.types.js +1 -0
  117. package/lib/esm/adapter-express/express-utils/base-middleware.js +19 -0
  118. package/lib/esm/adapter-express/express-utils/conditional-middleware.js +96 -0
  119. package/lib/esm/adapter-express/express-utils/constants.js +63 -0
  120. package/lib/esm/adapter-express/express-utils/content/httpContent.js +6 -0
  121. package/lib/esm/adapter-express/express-utils/content-negotiation-decorators.js +120 -0
  122. package/lib/esm/adapter-express/express-utils/decorators.js +604 -0
  123. package/lib/esm/adapter-express/express-utils/exception-filter-decorators.js +6 -0
  124. package/lib/esm/adapter-express/express-utils/guard-context-factory.js +83 -0
  125. package/lib/esm/adapter-express/express-utils/guard-middleware.js +115 -0
  126. package/lib/esm/adapter-express/express-utils/guard-utils.js +14 -0
  127. package/lib/esm/adapter-express/express-utils/http-context-store.js +10 -0
  128. package/lib/esm/adapter-express/express-utils/http-status-middleware.js +116 -0
  129. package/lib/esm/adapter-express/express-utils/httpResponseMessage.js +29 -0
  130. package/lib/esm/adapter-express/express-utils/index.js +24 -0
  131. package/lib/esm/adapter-express/express-utils/interceptor-middleware.js +130 -0
  132. package/lib/esm/adapter-express/express-utils/interfaces.js +1 -0
  133. package/lib/esm/adapter-express/express-utils/inversify-express-server.js +1047 -0
  134. package/lib/esm/adapter-express/express-utils/lazy-module-middleware.js +236 -0
  135. package/lib/esm/adapter-express/express-utils/middleware-composition.js +89 -0
  136. package/lib/esm/adapter-express/express-utils/path-pattern-compat.js +125 -0
  137. package/lib/esm/adapter-express/express-utils/permission-preloader.middleware.js +45 -0
  138. package/lib/esm/adapter-express/express-utils/resolver-multer.js +30 -0
  139. package/lib/esm/adapter-express/express-utils/route-constraints.js +100 -0
  140. package/lib/esm/adapter-express/express-utils/scope-extractor.interface.js +1 -0
  141. package/lib/esm/adapter-express/express-utils/scope-extractor.js +63 -0
  142. package/lib/esm/adapter-express/express-utils/setup-authorization.js +68 -0
  143. package/lib/esm/adapter-express/express-utils/setup-event-system.js +110 -0
  144. package/lib/esm/adapter-express/express-utils/setup-interceptors.js +100 -0
  145. package/lib/esm/adapter-express/express-utils/setup-lazy-loading.js +225 -0
  146. package/lib/esm/adapter-express/express-utils/utils.js +68 -0
  147. package/lib/esm/adapter-express/express-utils/validation-decorators.js +199 -0
  148. package/lib/esm/adapter-express/express-utils/validation-service.js +251 -0
  149. package/lib/esm/adapter-express/index.js +7 -0
  150. package/lib/esm/adapter-express/micro-api/application-express-micro-container.js +48 -0
  151. package/lib/esm/adapter-express/micro-api/application-express-micro-route.js +128 -0
  152. package/lib/esm/adapter-express/micro-api/application-express-micro.js +157 -0
  153. package/lib/esm/adapter-express/micro-api/gateway/circuit-breaker.js +174 -0
  154. package/lib/esm/adapter-express/micro-api/gateway/index.js +5 -0
  155. package/lib/esm/adapter-express/micro-api/gateway/service-proxy.js +210 -0
  156. package/lib/esm/adapter-express/micro-api/index.js +10 -0
  157. package/lib/esm/adapter-express/micro-api/micro.js +266 -0
  158. package/lib/esm/adapter-express/micro-api/queue/index.js +4 -0
  159. package/lib/esm/adapter-express/micro-api/queue/queue.interface.js +1 -0
  160. package/lib/esm/adapter-express/micro-api/queue/rabbitmq-consumer.js +229 -0
  161. package/lib/esm/adapter-express/micro-api/serverless/aws-lambda.adapter.js +180 -0
  162. package/lib/esm/adapter-express/micro-api/serverless/cloudflare.adapter.js +155 -0
  163. package/lib/esm/adapter-express/micro-api/serverless/index.js +6 -0
  164. package/lib/esm/adapter-express/micro-api/serverless/vercel.adapter.js +99 -0
  165. package/lib/esm/adapter-express/micro-api/service-mesh/index.js +5 -0
  166. package/lib/esm/adapter-express/micro-api/service-mesh/service-client.js +191 -0
  167. package/lib/esm/adapter-express/micro-api/service-mesh/service-discovery.js +259 -0
  168. package/lib/esm/adapter-express/middleware/index.js +5 -0
  169. package/lib/esm/adapter-express/middleware/request-logging.middleware.js +239 -0
  170. package/lib/esm/adapter-express/render/constants.js +37 -0
  171. package/lib/esm/adapter-express/render/engine.js +51 -0
  172. package/lib/esm/adapter-express/render/index.js +1 -0
  173. package/lib/esm/adapter-express/render/resolve-render.js +30 -0
  174. package/lib/esm/adapter-express/studio/index.js +1 -0
  175. package/lib/esm/adapter-express/studio/studio-integration.js +236 -0
  176. package/lib/esm/index.mjs +1 -0
  177. package/lib/esm/package.json +3 -0
  178. package/lib/esm/types/adapter-express/application-express.base.d.ts +77 -0
  179. package/lib/esm/types/adapter-express/application-express.d.ts +453 -0
  180. package/lib/esm/types/adapter-express/application-express.types.d.ts +23 -0
  181. package/lib/esm/types/adapter-express/express-utils/base-middleware.d.ts +8 -0
  182. package/lib/esm/types/adapter-express/express-utils/conditional-middleware.d.ts +97 -0
  183. package/lib/esm/types/adapter-express/express-utils/constants.d.ts +57 -0
  184. package/lib/esm/types/adapter-express/express-utils/content/httpContent.d.ts +6 -0
  185. package/lib/esm/types/adapter-express/express-utils/content-negotiation-decorators.d.ts +94 -0
  186. package/lib/esm/types/adapter-express/express-utils/decorators.d.ts +257 -0
  187. package/lib/esm/types/adapter-express/express-utils/exception-filter-decorators.d.ts +6 -0
  188. package/lib/esm/types/adapter-express/express-utils/guard-context-factory.d.ts +17 -0
  189. package/lib/esm/types/adapter-express/express-utils/guard-middleware.d.ts +22 -0
  190. package/lib/esm/types/adapter-express/express-utils/guard-utils.d.ts +11 -0
  191. package/lib/esm/types/adapter-express/express-utils/http-context-store.d.ts +20 -0
  192. package/lib/esm/types/adapter-express/express-utils/http-status-middleware.d.ts +26 -0
  193. package/lib/esm/types/adapter-express/express-utils/httpResponseMessage.d.ts +14 -0
  194. package/lib/esm/types/adapter-express/express-utils/index.d.ts +30 -0
  195. package/lib/esm/types/adapter-express/express-utils/interceptor-middleware.d.ts +40 -0
  196. package/lib/esm/types/adapter-express/express-utils/interfaces.d.ts +115 -0
  197. package/lib/esm/types/adapter-express/express-utils/inversify-express-server.d.ts +172 -0
  198. package/lib/esm/types/adapter-express/express-utils/lazy-module-middleware.d.ts +122 -0
  199. package/lib/esm/types/adapter-express/express-utils/middleware-composition.d.ts +85 -0
  200. package/lib/esm/types/adapter-express/express-utils/path-pattern-compat.d.ts +66 -0
  201. package/lib/esm/types/adapter-express/express-utils/permission-preloader.middleware.d.ts +10 -0
  202. package/lib/esm/types/adapter-express/express-utils/resolver-multer.d.ts +7 -0
  203. package/lib/esm/types/adapter-express/express-utils/route-constraints.d.ts +98 -0
  204. package/lib/esm/types/adapter-express/express-utils/scope-extractor.d.ts +21 -0
  205. package/lib/esm/types/adapter-express/express-utils/scope-extractor.interface.d.ts +12 -0
  206. package/lib/esm/types/adapter-express/express-utils/setup-authorization.d.ts +34 -0
  207. package/lib/esm/types/adapter-express/express-utils/setup-event-system.d.ts +118 -0
  208. package/lib/esm/types/adapter-express/express-utils/setup-interceptors.d.ts +115 -0
  209. package/lib/esm/types/adapter-express/express-utils/setup-lazy-loading.d.ts +123 -0
  210. package/lib/esm/types/adapter-express/express-utils/utils.d.ts +24 -0
  211. package/lib/esm/types/adapter-express/express-utils/validation-decorators.d.ts +145 -0
  212. package/lib/esm/types/adapter-express/express-utils/validation-service.d.ts +88 -0
  213. package/lib/esm/types/adapter-express/index.d.ts +7 -0
  214. package/lib/esm/types/adapter-express/micro-api/application-express-micro-container.d.ts +47 -0
  215. package/lib/esm/types/adapter-express/micro-api/application-express-micro-route.d.ts +104 -0
  216. package/lib/esm/types/adapter-express/micro-api/application-express-micro.d.ts +72 -0
  217. package/lib/esm/types/adapter-express/micro-api/gateway/circuit-breaker.d.ts +111 -0
  218. package/lib/esm/types/adapter-express/micro-api/gateway/index.d.ts +5 -0
  219. package/lib/esm/types/adapter-express/micro-api/gateway/service-proxy.d.ts +83 -0
  220. package/lib/esm/types/adapter-express/micro-api/index.d.ts +7 -0
  221. package/lib/esm/types/adapter-express/micro-api/micro.d.ts +83 -0
  222. package/lib/esm/types/adapter-express/micro-api/queue/index.d.ts +5 -0
  223. package/lib/esm/types/adapter-express/micro-api/queue/queue.interface.d.ts +60 -0
  224. package/lib/esm/types/adapter-express/micro-api/queue/rabbitmq-consumer.d.ts +86 -0
  225. package/lib/esm/types/adapter-express/micro-api/serverless/aws-lambda.adapter.d.ts +77 -0
  226. package/lib/esm/types/adapter-express/micro-api/serverless/cloudflare.adapter.d.ts +64 -0
  227. package/lib/esm/types/adapter-express/micro-api/serverless/index.d.ts +6 -0
  228. package/lib/esm/types/adapter-express/micro-api/serverless/vercel.adapter.d.ts +56 -0
  229. package/lib/esm/types/adapter-express/micro-api/service-mesh/index.d.ts +5 -0
  230. package/lib/esm/types/adapter-express/micro-api/service-mesh/service-client.d.ts +122 -0
  231. package/lib/esm/types/adapter-express/micro-api/service-mesh/service-discovery.d.ts +150 -0
  232. package/lib/esm/types/adapter-express/middleware/index.d.ts +5 -0
  233. package/lib/esm/types/adapter-express/middleware/request-logging.middleware.d.ts +65 -0
  234. package/lib/esm/types/adapter-express/render/constants.d.ts +26 -0
  235. package/lib/esm/types/adapter-express/render/engine.d.ts +20 -0
  236. package/lib/esm/types/adapter-express/render/index.d.ts +5 -0
  237. package/lib/esm/types/adapter-express/render/resolve-render.d.ts +7 -0
  238. package/lib/esm/types/adapter-express/studio/index.d.ts +1 -0
  239. package/lib/esm/types/adapter-express/studio/studio-integration.d.ts +170 -0
  240. package/lib/esm/types/index.d.ts +1 -0
  241. package/lib/package.json +170 -146
  242. package/package.json +170 -146
  243. package/lib/cjs/di/di.interfaces.js +0 -10
  244. package/lib/cjs/types/di/di.interfaces.d.ts +0 -289
@@ -26,11 +26,68 @@ Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.InversifyExpressServer = void 0;
27
27
  /* eslint-disable @typescript-eslint/no-unused-vars */
28
28
  const express_1 = __importStar(require("express"));
29
- const base_middleware_1 = require("./base-middleware");
30
- const utils_1 = require("./utils");
31
- const constants_1 = require("./constants");
32
- const httpResponseMessage_1 = require("./httpResponseMessage");
33
- const decorators_1 = require("./decorators");
29
+ const core_1 = require("@expressots/core");
30
+ const guard_context_factory_js_1 = require("./guard-context-factory.js");
31
+ const base_middleware_js_1 = require("./base-middleware.js");
32
+ const utils_js_1 = require("./utils.js");
33
+ const validation_decorators_js_1 = require("./validation-decorators.js");
34
+ const constants_js_1 = require("./constants.js");
35
+ const httpResponseMessage_js_1 = require("./httpResponseMessage.js");
36
+ const decorators_js_1 = require("./decorators.js");
37
+ const conditional_middleware_js_1 = require("./conditional-middleware.js");
38
+ const middleware_composition_js_1 = require("./middleware-composition.js");
39
+ const guard_utils_js_1 = require("./guard-utils.js");
40
+ const guard_middleware_js_1 = require("./guard-middleware.js");
41
+ const interceptor_middleware_js_1 = require("./interceptor-middleware.js");
42
+ const http_context_store_js_1 = require("./http-context-store.js");
43
+ // Lazy-load route registry from @expressots/core. Static import because
44
+ // `getRouteRegistry` is part of the core's public surface; we just guard
45
+ // against it being undefined to keep this non-critical (suggestions are
46
+ // optional).
47
+ const core_2 = require("@expressots/core");
48
+ function getRouteRegistryModule() {
49
+ if (typeof core_2.getRouteRegistry !== "function")
50
+ return null;
51
+ return { getRouteRegistry: core_2.getRouteRegistry };
52
+ }
53
+ /**
54
+ * Properly join route path segments, handling:
55
+ * - Controller "/" + method "/users" = "/users"
56
+ * - Controller "/api" + method "users" = "/api/users"
57
+ * - Controller "/api/" + method "/users" = "/api/users"
58
+ * - Controller "/" + method "/" = "/"
59
+ */
60
+ function joinRoutePaths(...segments) {
61
+ const parts = [];
62
+ for (const segment of segments) {
63
+ if (!segment || segment === "")
64
+ continue;
65
+ // For root path only, skip if we have parts
66
+ if (segment === "/" && parts.length > 0)
67
+ continue;
68
+ // Remove trailing slash
69
+ let cleaned = segment.endsWith("/") && segment.length > 1 ? segment.slice(0, -1) : segment;
70
+ // Handle leading slash
71
+ if (parts.length > 0) {
72
+ // Ensure leading slash for non-first segments
73
+ cleaned = cleaned.startsWith("/") ? cleaned : `/${cleaned}`;
74
+ }
75
+ else {
76
+ // First segment - ensure starts with /
77
+ cleaned = cleaned.startsWith("/") ? cleaned : `/${cleaned}`;
78
+ }
79
+ if (cleaned && cleaned !== "/") {
80
+ parts.push(cleaned);
81
+ }
82
+ else if (parts.length === 0) {
83
+ parts.push("/");
84
+ }
85
+ }
86
+ if (parts.length === 0)
87
+ return "/";
88
+ // Join and normalize double slashes
89
+ return parts.join("").replace(/\/+/g, "/");
90
+ }
34
91
  class InversifyExpressServer {
35
92
  /**
36
93
  * Wrapper for the express server.
@@ -47,12 +104,12 @@ class InversifyExpressServer {
47
104
  this._forceControllers = forceControllers;
48
105
  this._router = customRouter || (0, express_1.Router)();
49
106
  this._routingConfig = routingConfig || {
50
- rootPath: constants_1.DEFAULT_ROUTING_ROOT_PATH,
107
+ rootPath: constants_js_1.DEFAULT_ROUTING_ROOT_PATH,
51
108
  };
52
109
  this._app = customApp || (0, express_1.default)();
53
110
  if (authProvider) {
54
111
  this._AuthProvider = authProvider;
55
- container.bind(constants_1.TYPE.AuthProvider).to(this._AuthProvider);
112
+ container.bind(constants_js_1.TYPE.AuthProvider).to(this._AuthProvider);
56
113
  }
57
114
  }
58
115
  /**
@@ -85,46 +142,169 @@ class InversifyExpressServer {
85
142
  * Applies all routes and configuration to the server, returning the express application.
86
143
  */
87
144
  build() {
88
- // The very first middleware to be invoked
89
- // it creates a new httpContext and attaches it to the
90
- // current request as metadata using Reflect
91
- this._app.all("*", (req, res, next) => {
92
- void (async () => {
93
- const httpContext = await this._createHttpContext(req, res, next);
94
- Reflect.defineMetadata(constants_1.METADATA_KEY.httpContext, httpContext, req);
145
+ // The very first middleware to be invoked: create an HttpContext per
146
+ // request and attach it via a WeakMap (see ./http-context-store).
147
+ // This is cheaper than the previous `Reflect.defineMetadata` call
148
+ // because it bypasses reflect-metadata's string-keyed map.
149
+ //
150
+ // We use `app.use(handler)` (no path arg) which runs for every request
151
+ // regardless of method or path — the same behavior as the previous
152
+ // `app.all("*", ...)` registration but without a path-to-regexp pattern,
153
+ // so it is forward-compatible with Express 5 / path-to-regexp v8.
154
+ this._app.use((req, res, next) => {
155
+ this._createHttpContext(req, res, next)
156
+ .then((httpContext) => {
157
+ (0, http_context_store_js_1.setHttpContext)(req, httpContext);
95
158
  next();
96
- })();
159
+ })
160
+ .catch(next);
97
161
  });
98
162
  // register server-level middleware before anything else
99
163
  if (this._configFn) {
100
164
  this._configFn.apply(undefined, [this._app]);
101
165
  }
102
166
  this.registerControllers();
167
+ this.registerNotFoundHandler();
103
168
  // register error handlers after controllers
104
169
  if (this._errorConfigFn) {
105
170
  this._errorConfigFn.apply(undefined, [this._app]);
106
171
  }
107
172
  return this._app;
108
173
  }
174
+ /**
175
+ * Install a catch-all 404 handler that runs after every registered route.
176
+ *
177
+ * When the user has the suggestions feature enabled (default in development),
178
+ * this consults the route registry, computes "Did you mean ...?" suggestions
179
+ * via `getErrorHints` from `@expressots/core`, logs them through the framework
180
+ * Logger, and returns a structured RFC-7807-style JSON 404 instead of the
181
+ * default Express HTML.
182
+ *
183
+ * Users who want the legacy Express HTML 404 can opt out by configuring the
184
+ * Logger with `suggestions.enabled = false` (this also disables the JSON
185
+ * envelope so they can install their own 404 handler in the error-config fn).
186
+ *
187
+ * @private
188
+ */
189
+ registerNotFoundHandler() {
190
+ this._app.use((req, res, next) => {
191
+ if (res.headersSent) {
192
+ return next();
193
+ }
194
+ const suggestionsConfig = this.resolveSuggestionsConfig();
195
+ if (!suggestionsConfig.enabled) {
196
+ return next();
197
+ }
198
+ const requestedPath = req.originalUrl || req.url;
199
+ const requestedMethod = req.method;
200
+ const hints = (0, core_1.getErrorHints)(new Error(`Route '${requestedMethod} ${requestedPath}' not found`), {
201
+ path: requestedPath,
202
+ method: requestedMethod,
203
+ statusCode: 404,
204
+ }, suggestionsConfig);
205
+ if (hints.length > 0) {
206
+ try {
207
+ const formatted = (0, core_1.formatSuggestions)(hints);
208
+ if (formatted) {
209
+ const logger = this.resolveLogger();
210
+ if (logger) {
211
+ logger.warn(`Route not found: ${requestedMethod} ${requestedPath}${formatted}`, "router-404");
212
+ }
213
+ else {
214
+ // eslint-disable-next-line no-console
215
+ console.warn(`[router-404] Route not found: ${requestedMethod} ${requestedPath}${formatted}`);
216
+ }
217
+ }
218
+ }
219
+ catch {
220
+ // Suggestion logging is best-effort; never fail the request because of it.
221
+ }
222
+ }
223
+ const routeSuggestion = hints.find((hint) => hint.type === "route");
224
+ const actionHint = hints.find((hint) => hint.type === "hint");
225
+ const body = {
226
+ type: "https://expressots.dev/errors/not-found",
227
+ title: "Route Not Found",
228
+ status: 404,
229
+ detail: `Route '${requestedMethod} ${requestedPath}' does not exist`,
230
+ instance: requestedPath,
231
+ timestamp: new Date().toISOString(),
232
+ };
233
+ if (routeSuggestion?.routes && routeSuggestion.routes.length > 0) {
234
+ body.suggestions = routeSuggestion.routes.map((suggestion) => ({
235
+ method: suggestion.route.method,
236
+ path: suggestion.route.fullPath || suggestion.route.path,
237
+ similarity: Math.round(suggestion.similarity * 100),
238
+ reason: suggestion.reason,
239
+ }));
240
+ }
241
+ else if (actionHint?.actions && actionHint.actions.length > 0) {
242
+ body.actions = actionHint.actions;
243
+ }
244
+ res.status(404).type("application/json").send(JSON.stringify(body));
245
+ });
246
+ }
247
+ /**
248
+ * Resolve the user-configured suggestions config, falling back to the
249
+ * env-aware default when the Logger is not bound or has no overrides.
250
+ *
251
+ * @private
252
+ */
253
+ resolveSuggestionsConfig() {
254
+ const fallback = (0, core_1.getDefaultSuggestionsConfig)();
255
+ const logger = this.resolveLogger();
256
+ if (!logger) {
257
+ return fallback;
258
+ }
259
+ try {
260
+ const loggerConfig = logger.getConfig?.();
261
+ if (loggerConfig?.suggestions) {
262
+ return { ...fallback, ...loggerConfig.suggestions };
263
+ }
264
+ }
265
+ catch {
266
+ // Logger may not expose getConfig in older versions; fall through.
267
+ }
268
+ return fallback;
269
+ }
270
+ /**
271
+ * Resolve the framework Logger from DI when available.
272
+ *
273
+ * @private
274
+ */
275
+ resolveLogger() {
276
+ try {
277
+ // The `Logger` symbol is the constructor itself in our DI bindings.
278
+ if (this._container.isBound(core_1.Logger)) {
279
+ return this._container.get(core_1.Logger);
280
+ }
281
+ }
282
+ catch {
283
+ // not bound yet
284
+ }
285
+ return undefined;
286
+ }
109
287
  registerControllers() {
110
288
  // Fake HttpContext is needed during registration
111
- this._container.bind(constants_1.TYPE.HttpContext).toConstantValue({});
112
- const constructors = (0, utils_1.getControllersFromMetadata)();
289
+ this._container.bind(constants_js_1.TYPE.HttpContext).toConstantValue({});
290
+ // Initialize interceptor system if not already bound
291
+ this.initializeInterceptorSystem();
292
+ const constructors = (0, utils_js_1.getControllersFromMetadata)();
113
293
  constructors.forEach((constructor) => {
114
294
  const { name } = constructor;
115
- if (this._container.isBoundNamed(constants_1.TYPE.Controller, name)) {
116
- throw new Error((0, constants_1.DUPLICATED_CONTROLLER_NAME)(name));
295
+ if (this._container.isBoundNamed(constants_js_1.TYPE.Controller, name)) {
296
+ throw new Error((0, constants_js_1.DUPLICATED_CONTROLLER_NAME)(name));
117
297
  }
118
298
  this._container
119
- .bind(constants_1.TYPE.Controller)
299
+ .bind(constants_js_1.TYPE.Controller)
120
300
  .to(constructor)
121
301
  .whenTargetNamed(name);
122
302
  });
123
- const controllers = (0, utils_1.getControllersFromContainer)(this._container, this._forceControllers);
303
+ const controllers = (0, utils_js_1.getControllersFromContainer)(this._container, this._forceControllers);
124
304
  controllers.forEach((controller) => {
125
- const controllerMetadata = (0, utils_1.getControllerMetadata)(controller.constructor);
126
- const methodMetadata = (0, utils_1.getControllerMethodMetadata)(controller.constructor);
127
- const parameterMetadata = (0, utils_1.getControllerParameterMetadata)(controller.constructor);
305
+ const controllerMetadata = (0, utils_js_1.getControllerMetadata)(controller.constructor);
306
+ const methodMetadata = (0, utils_js_1.getControllerMethodMetadata)(controller.constructor);
307
+ const parameterMetadata = (0, utils_js_1.getControllerParameterMetadata)(controller.constructor);
128
308
  if (controllerMetadata && methodMetadata) {
129
309
  const controllerMiddleware = this.resolveMiddleware(...controllerMetadata.middleware);
130
310
  methodMetadata.forEach((metadata) => {
@@ -132,21 +312,168 @@ class InversifyExpressServer {
132
312
  if (parameterMetadata) {
133
313
  paramList = parameterMetadata[metadata.key] || [];
134
314
  }
135
- const handler = this.handlerFactory(controllerMetadata.target.name, metadata.key, paramList);
315
+ // Create base handler
316
+ let handler = this.handlerFactory(controllerMetadata.target.name, metadata.key, paramList, controller.constructor, // Pass controller constructor for metadata
317
+ metadata);
318
+ // Wrap handler with interceptor middleware if interceptors are defined
319
+ const interceptors = this.extractInterceptors(controller.constructor, metadata.key);
320
+ if (interceptors.length > 0 && this.isInterceptorSystemReady()) {
321
+ handler = this.wrapWithInterceptors(handler, controller.constructor, metadata.key);
322
+ }
136
323
  const routeMiddleware = this.resolveMiddleware(...metadata.middleware);
137
- this._router[metadata.method](`${controllerMetadata.path}${metadata.path}`, ...controllerMiddleware, ...routeMiddleware, handler);
324
+ // Determine version: method-level version overrides controller-level version
325
+ const version = metadata.version || controllerMetadata.version;
326
+ const versionPrefix = version ? `/${version}` : "";
327
+ // Properly join paths to avoid issues like "/api" + "users" = "/apiusers"
328
+ const routePath = joinRoutePaths(versionPrefix, controllerMetadata.path, metadata.path);
329
+ const fullPath = joinRoutePaths(this._routingConfig.rootPath, routePath);
330
+ // Register route for suggestions system (synchronous approach)
331
+ try {
332
+ const module = getRouteRegistryModule();
333
+ if (module && module.getRouteRegistry) {
334
+ const registry = module.getRouteRegistry();
335
+ registry.register(metadata.method, routePath, fullPath);
336
+ }
337
+ }
338
+ catch {
339
+ // Route registry not available, skip registration (non-critical)
340
+ // This allows the app to work even if suggestions module isn't available
341
+ }
342
+ this._router[metadata.method](routePath, ...controllerMiddleware, ...routeMiddleware, handler);
138
343
  });
139
344
  }
140
345
  });
141
346
  this._app.use(this._routingConfig.rootPath, this._router);
142
347
  }
348
+ /**
349
+ * Initialize the interceptor system by binding required components
350
+ * @private
351
+ */
352
+ initializeInterceptorSystem() {
353
+ try {
354
+ // Bind InterceptorRegistry if not already bound
355
+ if (!this._container.isBound(core_1.InterceptorRegistry)) {
356
+ this._container.bind(core_1.InterceptorRegistry).toSelf().inSingletonScope();
357
+ }
358
+ // Bind InterceptorExecutor if not already bound
359
+ if (!this._container.isBound(core_1.InterceptorExecutor)) {
360
+ this._container.bind(core_1.InterceptorExecutor).toSelf().inSingletonScope();
361
+ }
362
+ // Bind InterceptorMiddleware if not already bound
363
+ if (!this._container.isBound(interceptor_middleware_js_1.InterceptorMiddleware)) {
364
+ this._container.bind(interceptor_middleware_js_1.InterceptorMiddleware).toSelf().inSingletonScope();
365
+ }
366
+ }
367
+ catch {
368
+ // Interceptor system initialization failed (non-critical)
369
+ // Routes will work without interceptors
370
+ }
371
+ }
372
+ /**
373
+ * Check if the interceptor system is properly initialized
374
+ * @private
375
+ */
376
+ isInterceptorSystemReady() {
377
+ try {
378
+ return (this._container.isBound(core_1.InterceptorExecutor) &&
379
+ this._container.isBound(interceptor_middleware_js_1.InterceptorMiddleware));
380
+ }
381
+ catch {
382
+ return false;
383
+ }
384
+ }
385
+ /**
386
+ * Extract interceptors from controller and method metadata
387
+ * @private
388
+ */
389
+ extractInterceptors(controllerClass, methodName) {
390
+ try {
391
+ // Get controller-level interceptors
392
+ const controllerInterceptors = Reflect.getMetadata(core_1.INTERCEPTOR_METADATA_KEY.controllerInterceptors, controllerClass) || [];
393
+ // Get method-level interceptors
394
+ const methodInterceptors = Reflect.getMetadata(core_1.INTERCEPTOR_METADATA_KEY.methodInterceptors, controllerClass, methodName) || [];
395
+ // Combine: controller + method level
396
+ return [...controllerInterceptors, ...methodInterceptors];
397
+ }
398
+ catch {
399
+ return [];
400
+ }
401
+ }
402
+ /**
403
+ * Wrap a handler with interceptor middleware
404
+ * @private
405
+ */
406
+ wrapWithInterceptors(handler, controllerClass, methodName) {
407
+ try {
408
+ const interceptorMiddleware = (0, interceptor_middleware_js_1.createInterceptorMiddleware)(this._container, controllerClass, methodName, handler);
409
+ return interceptorMiddleware;
410
+ }
411
+ catch {
412
+ // Fall back to original handler if interceptor wrapping fails
413
+ return handler;
414
+ }
415
+ }
416
+ /**
417
+ * Checks if a middleware item is a class constructor (not an instance).
418
+ * Handles classes that extend ExpressoMiddleware (which has abstract use method).
419
+ * Note: Abstract methods don't exist at runtime, so we check for concrete implementations.
420
+ */
421
+ isMiddlewareClass(middlewareItem) {
422
+ // Must be a function (class constructor)
423
+ if (typeof middlewareItem !== "function") {
424
+ return false;
425
+ }
426
+ // Must not be a conditional or composed middleware config
427
+ if ((0, conditional_middleware_js_1.isConditionalMiddleware)(middlewareItem) || (0, middleware_composition_js_1.isComposedMiddleware)(middlewareItem)) {
428
+ return false;
429
+ }
430
+ // Must have a prototype
431
+ if (middlewareItem.prototype === undefined) {
432
+ return false;
433
+ }
434
+ // Check if it has a 'use' method in its prototype
435
+ // Classes that extend ExpressoMiddleware must implement the abstract use() method
436
+ // so it will be in the prototype at runtime
437
+ const prototype = middlewareItem.prototype;
438
+ // Check for 'use' method directly in prototype (most common case)
439
+ if ("use" in prototype && typeof prototype.use === "function") {
440
+ return true;
441
+ }
442
+ // Also check prototype chain in case use() is defined in a parent class
443
+ // This handles cases where the method might be inherited (optimized: only if not found directly)
444
+ let currentPrototype = Object.getPrototypeOf(prototype);
445
+ while (currentPrototype && currentPrototype !== Object.prototype) {
446
+ if ("use" in currentPrototype &&
447
+ typeof currentPrototype.use === "function") {
448
+ return true;
449
+ }
450
+ currentPrototype = Object.getPrototypeOf(currentPrototype);
451
+ }
452
+ return false;
453
+ }
143
454
  isExpressoMiddleware(middlewareItem) {
144
455
  return (typeof middlewareItem === "object" &&
456
+ middlewareItem !== null &&
145
457
  "use" in middlewareItem &&
146
- typeof middlewareItem.use === "function");
458
+ typeof middlewareItem.use === "function" &&
459
+ !(0, conditional_middleware_js_1.isConditionalMiddleware)(middlewareItem) &&
460
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
461
+ !this.isMiddlewareClass(middlewareItem));
147
462
  }
148
463
  resolveMiddleware(...middleware) {
149
464
  return middleware.map((middlewareItem) => {
465
+ // Handle composed middleware first (Phase 3: Middleware Composition)
466
+ if ((0, middleware_composition_js_1.isComposedMiddleware)(middlewareItem)) {
467
+ return this.createComposedMiddlewareHandler(middlewareItem);
468
+ }
469
+ // Handle conditional middleware
470
+ if ((0, conditional_middleware_js_1.isConditionalMiddleware)(middlewareItem)) {
471
+ return this.createConditionalMiddlewareHandler(middlewareItem);
472
+ }
473
+ // Handle class constructors (Phase 2: Class Reference Support)
474
+ if (this.isMiddlewareClass(middlewareItem)) {
475
+ return this.createLazyMiddlewareHandler(middlewareItem);
476
+ }
150
477
  if (this.isExpressoMiddleware(middlewareItem)) {
151
478
  return (req, res, next) => {
152
479
  middlewareItem.use(req, res, next);
@@ -156,7 +483,7 @@ class InversifyExpressServer {
156
483
  return middlewareItem;
157
484
  }
158
485
  const middlewareInstance = this._container.get(middlewareItem);
159
- if (middlewareInstance instanceof base_middleware_1.BaseMiddleware) {
486
+ if (middlewareInstance instanceof base_middleware_js_1.BaseMiddleware) {
160
487
  return (req, res, next) => {
161
488
  const mReq = this._container.get(middlewareItem);
162
489
  mReq.httpContext = this._getHttpContext(req);
@@ -166,6 +493,239 @@ class InversifyExpressServer {
166
493
  return middlewareInstance;
167
494
  });
168
495
  }
496
+ /**
497
+ * Creates a lazy middleware handler for class constructors.
498
+ * Supports both container-bound middleware (via @provide()) and direct instantiation.
499
+ *
500
+ * Performance: Instances are created per-request to support request-scoped state.
501
+ * For better performance with stateless middleware, use container-bound middleware
502
+ * with proper scoping (singleton/request scope) via @provide().
503
+ *
504
+ * Note: If container resolution fails (e.g., base class missing @injectable()),
505
+ * falls back to direct instantiation for backward compatibility.
506
+ */
507
+ createLazyMiddlewareHandler(MiddlewareClass) {
508
+ // Pre-check if container-bound at route registration time (performance optimization)
509
+ const isContainerBound = this._container.isBound(MiddlewareClass);
510
+ let containerResolutionFailed = false;
511
+ // Cache instance for non-container-bound middleware (singleton per handler)
512
+ // Container-bound middleware relies on container scoping (singleton/request scope)
513
+ let cachedInstance;
514
+ return (req, res, next) => {
515
+ try {
516
+ let instance;
517
+ // Try container resolution first if bound and not previously failed
518
+ if (isContainerBound && !containerResolutionFailed) {
519
+ try {
520
+ // Resolve from container (supports DI, scoping, etc.)
521
+ // Container handles singleton/request scope automatically
522
+ instance = this._container.get(MiddlewareClass);
523
+ }
524
+ catch (containerError) {
525
+ // Container resolution failed (e.g., base class missing @injectable())
526
+ // Mark as failed and fall back to direct instantiation
527
+ containerResolutionFailed = true;
528
+ try {
529
+ // Create and cache instance if not already cached
530
+ if (!cachedInstance) {
531
+ cachedInstance = new MiddlewareClass();
532
+ }
533
+ instance = cachedInstance;
534
+ }
535
+ catch (instantiationError) {
536
+ next(instantiationError);
537
+ return;
538
+ }
539
+ }
540
+ }
541
+ else {
542
+ // Create instance directly (no DI support or container not available)
543
+ // Cache instance for reuse across requests (singleton per handler)
544
+ if (!cachedInstance) {
545
+ try {
546
+ cachedInstance = new MiddlewareClass();
547
+ }
548
+ catch (instantiationError) {
549
+ next(instantiationError);
550
+ return;
551
+ }
552
+ }
553
+ instance = cachedInstance;
554
+ }
555
+ // Execute middleware (supports both sync and async)
556
+ // The middleware's use() method should call next() itself
557
+ // We pass the next function directly - when middleware calls next(),
558
+ // it will continue the Express middleware chain (or the composition chain)
559
+ try {
560
+ const result = instance.use(req, res, next);
561
+ // Handle async middleware that returns a Promise
562
+ // If it returns a Promise, return it so the chain can await it
563
+ if (result !== undefined && result !== null) {
564
+ const resultObj = result;
565
+ if (typeof resultObj === "object" &&
566
+ resultObj !== null &&
567
+ "then" in resultObj &&
568
+ typeof resultObj.then === "function") {
569
+ // Return the Promise so the chain can await it
570
+ // The middleware should have already called next(), but the chain
571
+ // will wait for the Promise to resolve/reject
572
+ return result.catch((error) => {
573
+ // If the Promise rejects and next() wasn't called with error, call it now
574
+ next(error);
575
+ });
576
+ }
577
+ }
578
+ // Synchronous middleware - returns void, chain relies on next() being called
579
+ }
580
+ catch (useError) {
581
+ // If use() throws synchronously, pass error to next()
582
+ next(useError);
583
+ }
584
+ }
585
+ catch (error) {
586
+ // Catch any other errors
587
+ next(error);
588
+ }
589
+ };
590
+ }
591
+ /**
592
+ * Creates a request handler for conditional middleware.
593
+ * Evaluates the condition and executes the wrapped middleware if condition is true.
594
+ */
595
+ createConditionalMiddlewareHandler(config) {
596
+ // Resolve the wrapped middleware once (at route registration time)
597
+ const wrappedMiddlewareHandlers = this.resolveMiddleware(config.middleware);
598
+ return async (req, res, next) => {
599
+ try {
600
+ // Evaluate the condition (supports both sync and async)
601
+ const conditionResult = await config.condition(req);
602
+ // Determine if middleware should execute based on condition and skipOnFalse flag
603
+ const shouldExecute = config.skipOnFalse !== false ? conditionResult : !conditionResult;
604
+ if (shouldExecute) {
605
+ // Condition met, execute the wrapped middleware
606
+ // The wrapped middleware handlers are already Express RequestHandlers,
607
+ // so we can execute them directly. They will call next() when done,
608
+ // which will continue to the next middleware in the route.
609
+ if (wrappedMiddlewareHandlers.length === 0) {
610
+ // No middleware to execute, just continue
611
+ next();
612
+ }
613
+ else if (wrappedMiddlewareHandlers.length === 1) {
614
+ // Single middleware, execute it directly
615
+ wrappedMiddlewareHandlers[0](req, res, next);
616
+ }
617
+ else {
618
+ // Multiple middleware, execute them sequentially
619
+ await this.executeMiddlewareChain(wrappedMiddlewareHandlers, req, res, next);
620
+ }
621
+ }
622
+ else {
623
+ // Condition not met, skip middleware and continue to next middleware in route
624
+ next();
625
+ }
626
+ }
627
+ catch (error) {
628
+ // If condition evaluation throws, pass error to error handler
629
+ next(error);
630
+ }
631
+ };
632
+ }
633
+ /**
634
+ * Creates a request handler for composed middleware (Phase 3: Middleware Composition).
635
+ * Executes all middleware in the composition sequentially.
636
+ * Both 'combine' and 'sequence' types behave the same way - they execute middleware
637
+ * sequentially and propagate errors normally (Express handles errors via next(error)).
638
+ *
639
+ * @param config - ComposedMiddlewareConfig containing the middleware array and type
640
+ * @returns Express RequestHandler
641
+ */
642
+ createComposedMiddlewareHandler(config) {
643
+ // Resolve all middleware in the composition to Express RequestHandlers
644
+ const resolvedHandlers = config.middleware.flatMap((mw) => this.resolveMiddleware(mw));
645
+ return async (req, res, next) => {
646
+ try {
647
+ if (resolvedHandlers.length === 0) {
648
+ // No middleware to execute, just continue
649
+ next();
650
+ return;
651
+ }
652
+ // Execute all middleware sequentially
653
+ // Both 'combine' and 'sequence' use the same execution logic
654
+ // Express's error handling (via next(error)) naturally stops execution
655
+ await this.executeMiddlewareChain(resolvedHandlers, req, res, next);
656
+ }
657
+ catch (error) {
658
+ // If execution throws an error, pass it to Express error handler
659
+ next(error);
660
+ }
661
+ };
662
+ }
663
+ /**
664
+ * Executes a chain of middleware handlers sequentially.
665
+ * Each middleware calls next() to proceed to the next one.
666
+ * Handles both synchronous and asynchronous middleware.
667
+ */
668
+ executeMiddlewareChain(handlers, req, res, next) {
669
+ return new Promise((resolve, reject) => {
670
+ let index = 0;
671
+ const runNext = (err) => {
672
+ if (err) {
673
+ reject(err);
674
+ return;
675
+ }
676
+ if (index >= handlers.length) {
677
+ // All middleware executed successfully, call Express next() to continue to route handler
678
+ next();
679
+ resolve();
680
+ return;
681
+ }
682
+ const handler = handlers[index++];
683
+ try {
684
+ // Execute the handler
685
+ // Express middleware handlers can:
686
+ // 1. Call next() synchronously
687
+ // 2. Call next() asynchronously
688
+ // 3. Return a Promise
689
+ // 4. Return nothing (void)
690
+ const result = handler(req, res, (err) => {
691
+ if (err) {
692
+ reject(err);
693
+ }
694
+ else {
695
+ // Handler called next() successfully, proceed to next middleware
696
+ runNext();
697
+ }
698
+ });
699
+ // If handler returns a Promise, wait for it
700
+ // Note: Even if handler returns a Promise, it should still call next()
701
+ // But we handle the Promise in case it doesn't
702
+ // Check if result exists and is a Promise-like object (thenable)
703
+ if (result !== undefined && result !== null) {
704
+ const resultObj = result;
705
+ if (typeof resultObj === "object" &&
706
+ resultObj !== null &&
707
+ "then" in resultObj &&
708
+ typeof resultObj.then === "function") {
709
+ result
710
+ .then(() => {
711
+ // If Promise resolves and next wasn't called, proceed
712
+ if (index <= handlers.length) {
713
+ runNext();
714
+ }
715
+ })
716
+ .catch(reject);
717
+ }
718
+ }
719
+ // If handler doesn't return a Promise and doesn't call next(),
720
+ // we rely on the handler to call next() itself
721
+ }
722
+ catch (error) {
723
+ reject(error);
724
+ }
725
+ };
726
+ runNext();
727
+ });
728
+ }
169
729
  copyHeadersTo(headers, target) {
170
730
  for (const name of Object.keys(headers)) {
171
731
  const headerValue = headers[name];
@@ -186,43 +746,245 @@ class InversifyExpressServer {
186
746
  res.sendStatus(message.statusCode);
187
747
  }
188
748
  }
189
- handlerFactory(controllerName, key, parameterMetadata) {
190
- return async (req, res, next) => {
749
+ handlerFactory(controllerName, key, parameterMetadata, controllerConstructor, methodMetadata) {
750
+ // Extract guards from controller and method metadata
751
+ const controllerGuards = controllerConstructor
752
+ ? (0, guard_utils_js_1.getControllerGuards)(controllerConstructor)
753
+ : [];
754
+ const methodGuards = controllerConstructor ? (0, guard_utils_js_1.getMethodGuards)(controllerConstructor, key) : [];
755
+ const allGuards = [...controllerGuards, ...methodGuards];
756
+ // Create guard middleware if guards exist
757
+ let guardMiddleware = null;
758
+ if (allGuards.length > 0) {
191
759
  try {
192
- const args = this.extractParameters(req, res, next, parameterMetadata);
193
- const httpContext = this._getHttpContext(req);
194
- httpContext.container.bind(constants_1.TYPE.HttpContext).toConstantValue(httpContext);
195
- // invoke controller's action
196
- const controller = httpContext.container.getNamed(constants_1.TYPE.Controller, controllerName);
197
- const value = await controller[key](...args);
198
- const { template, defaultData } = (0, decorators_1.getRenderMetadata)(controller, key);
199
- if (template) {
200
- const data = value || defaultData || {};
201
- res.render(template, data);
760
+ // Check if guard system is initialized (use class identifiers, not strings)
761
+ if (this._container.isBound(core_1.GuardExecutor) &&
762
+ this._container.isBound(guard_context_factory_js_1.GuardContextFactory) &&
763
+ this._container.isBound(guard_middleware_js_1.GuardMiddleware)) {
764
+ const guardMiddlewareInstance = this._container.get(guard_middleware_js_1.GuardMiddleware);
765
+ guardMiddleware = guardMiddlewareInstance.execute;
202
766
  }
203
- else if (value instanceof httpResponseMessage_1.HttpResponseMessage) {
204
- await this.handleHttpResponseMessage(value, res);
767
+ }
768
+ catch (error) {
769
+ // Guard system not initialized, continue without guards
770
+ console.error("[Guard System] Failed to initialize:", error);
771
+ }
772
+ }
773
+ // Create handler function
774
+ const handler = async (req, res, next) => {
775
+ try {
776
+ // Attach controller and method metadata to request for exception handler middleware
777
+ // This provides a reliable fallback if route stack extraction fails
778
+ if (controllerConstructor) {
779
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
780
+ req.__expressotsController = controllerConstructor;
781
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
782
+ req.__expressotsMethod = key;
783
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
784
+ req.__expressotsControllerName = controllerName;
785
+ // Attach guards to request for guard middleware
786
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
787
+ req.__expressotsControllerGuards = controllerGuards;
788
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
789
+ req.__expressotsMethodGuards = methodGuards;
205
790
  }
206
- else if ((0, utils_1.instanceOfIHttpActionResult)(value)) {
207
- const httpResponseMessage = await value.executeAsync();
208
- await this.handleHttpResponseMessage(httpResponseMessage, res);
791
+ // Execute guard middleware if guards exist
792
+ if (guardMiddleware && allGuards.length > 0) {
793
+ // Express 5 typings expect the handler to return `void`. We cannot
794
+ // `return` the call because the express handler signature is
795
+ // `(req, res, next) => void | Promise<void>` in v5; chaining the
796
+ // expression off `return` makes TS infer `unknown`. Invoke it
797
+ // for-effect and return.
798
+ guardMiddleware(req, res, async (err) => {
799
+ if (err) {
800
+ next(err);
801
+ return;
802
+ }
803
+ // Guards passed, continue to route handler
804
+ await this.executeRouteHandler(req, res, next, controllerName, key, parameterMetadata, controllerConstructor);
805
+ });
806
+ return;
209
807
  }
210
- else if (value instanceof Function) {
211
- value();
808
+ // No guards, execute route handler directly
809
+ await this.executeRouteHandler(req, res, next, controllerName, key, parameterMetadata, controllerConstructor);
810
+ }
811
+ catch (error) {
812
+ next(error);
813
+ }
814
+ };
815
+ return handler;
816
+ }
817
+ async executeRouteHandler(req, res, next, controllerName, key, parameterMetadata, controllerConstructor) {
818
+ // Get request ID for flow tracking
819
+ const requestContext = core_1.ContextManager.getCurrentContext();
820
+ const requestId = requestContext?.requestId;
821
+ const flowTracker = requestId ? (0, core_1.findFlowTracker)(requestId) : undefined;
822
+ const controllerStepName = `${controllerName}.${key}`;
823
+ try {
824
+ let args = this.extractParameters(req, res, next, parameterMetadata);
825
+ const httpContext = this._getHttpContext(req);
826
+ httpContext.container.bind(constants_js_1.TYPE.HttpContext).toConstantValue(httpContext);
827
+ // Validate parameters if validation service is enabled
828
+ const validationService = this.getValidationService();
829
+ if (validationService?.isEnabled() && controllerConstructor) {
830
+ // Check if there are actually validation metadata (has @validatedBody, @validatedQuery, etc.)
831
+ const validationMetadata = (0, validation_decorators_js_1.getValidationMetadata)(controllerConstructor, key);
832
+ const hasValidatedParams = validationMetadata.length > 0;
833
+ if (hasValidatedParams) {
834
+ // Start validation step only if there are validated parameters
835
+ if (flowTracker?.isEnabled()) {
836
+ flowTracker.startStep("validation", `Validation: ${controllerName}.${key}`);
837
+ }
838
+ const validatedArgs = await validationService.validateParameters(req, res, controllerConstructor, key, args);
839
+ if (validatedArgs === null) {
840
+ // Validation failed, response already sent
841
+ // Create a validation error to store on request
842
+ const validationError = new Error("Validation failed");
843
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
844
+ req.__expressotsFlowError = validationError;
845
+ if (flowTracker?.isEnabled()) {
846
+ flowTracker.failStep(validationError);
847
+ }
848
+ return;
849
+ }
850
+ // End validation step
851
+ if (flowTracker?.isEnabled()) {
852
+ flowTracker.endStep("success");
853
+ }
854
+ args = validatedArgs;
212
855
  }
213
- else if (!res.headersSent) {
214
- if (value !== undefined) {
215
- res.send(value);
856
+ else {
857
+ // No validation metadata, but validation service might still run smart detection
858
+ // Only track if smart detection actually finds something to validate
859
+ const validatedArgs = await validationService.validateParameters(req, res, controllerConstructor, key, args);
860
+ if (validatedArgs === null) {
861
+ // Smart detection found validation errors
862
+ return;
216
863
  }
864
+ args = validatedArgs;
217
865
  }
218
866
  }
219
- catch (err) {
220
- next(err);
867
+ // Start controller step
868
+ if (flowTracker?.isEnabled()) {
869
+ flowTracker.startStep("controller", controllerStepName, {
870
+ controller: controllerName,
871
+ method: key,
872
+ });
221
873
  }
222
- };
874
+ // invoke controller's action
875
+ const controller = httpContext.container.getNamed(constants_js_1.TYPE.Controller, controllerName);
876
+ const value = await controller[key](...args);
877
+ // End controller step
878
+ if (flowTracker?.isEnabled()) {
879
+ flowTracker.endStep("success");
880
+ }
881
+ const { template, defaultData } = (0, decorators_js_1.getRenderMetadata)(controller, key);
882
+ if (template) {
883
+ const data = value || defaultData || {};
884
+ res.render(template, data);
885
+ }
886
+ else if (value instanceof httpResponseMessage_js_1.HttpResponseMessage) {
887
+ await this.handleHttpResponseMessage(value, res);
888
+ }
889
+ else if ((0, utils_js_1.instanceOfIHttpActionResult)(value)) {
890
+ const httpResponseMessage = await value.executeAsync();
891
+ await this.handleHttpResponseMessage(httpResponseMessage, res);
892
+ }
893
+ else if (value instanceof Function) {
894
+ value();
895
+ }
896
+ else if (!res.headersSent) {
897
+ // Smart response handling: Auto-404 for GET requests returning null/undefined
898
+ // This is a common pattern where null means "resource not found"
899
+ if (value === null || value === undefined) {
900
+ const method = req.method.toUpperCase();
901
+ // For GET requests, null/undefined typically means "not found"
902
+ // For DELETE requests, undefined means "successfully deleted" (204 No Content is already set)
903
+ if (method === "GET") {
904
+ // Extract resource info from path for helpful error message
905
+ const pathParts = req.path.split("/").filter(Boolean);
906
+ const resource = pathParts[pathParts.length - 2] || "Resource";
907
+ // Express 5's path-to-regexp v8 widens param values to
908
+ // `string | string[]` because array-style params are now first
909
+ // class. Coerce to string for the NotFoundError signature.
910
+ const rawId = req.params?.id ?? pathParts[pathParts.length - 1];
911
+ const id = Array.isArray(rawId) ? rawId.join("/") : rawId;
912
+ throw new core_1.NotFoundError(resource, id);
913
+ }
914
+ // For other methods (DELETE, PUT, PATCH), undefined is valid (204 No Content)
915
+ // The HttpStatusCodeMiddleware already sets appropriate status codes
916
+ if (method !== "DELETE" && method !== "PUT" && method !== "PATCH") {
917
+ // For POST or other methods with null/undefined, send empty response
918
+ res.end();
919
+ }
920
+ // For DELETE/PUT/PATCH, the middleware already set 204, just end the response
921
+ else {
922
+ res.end();
923
+ }
924
+ }
925
+ else {
926
+ // Try content negotiation if enabled
927
+ const cnMetadata = (0, utils_js_1.getContentNegotiationMetadata)(controller, key);
928
+ const contentNegotiationService = this.getContentNegotiationService();
929
+ if (contentNegotiationService?.isEnabled()) {
930
+ const handled = await contentNegotiationService.handleResponse(req, res, value, cnMetadata.accept || cnMetadata.produces);
931
+ if (handled) {
932
+ return; // Response already sent
933
+ }
934
+ }
935
+ // Fallback to default behavior (backward compatible)
936
+ res.send(value);
937
+ }
938
+ }
939
+ }
940
+ catch (err) {
941
+ // Store error on request for flow tracking
942
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
943
+ req.__expressotsFlowError = err instanceof Error ? err : new Error(String(err));
944
+ // End controller step with failure if not already ended
945
+ if (flowTracker?.isEnabled()) {
946
+ const currentFlow = flowTracker.getFlow();
947
+ const lastStep = currentFlow.steps[currentFlow.steps.length - 1];
948
+ if (lastStep && lastStep.name === controllerStepName && lastStep.status === "success") {
949
+ // Step was already ended, don't end again
950
+ }
951
+ else {
952
+ flowTracker.failStep(err instanceof Error ? err : undefined);
953
+ }
954
+ }
955
+ next(err);
956
+ }
223
957
  }
224
958
  _getHttpContext(req) {
225
- return Reflect.getMetadata(constants_1.METADATA_KEY.httpContext, req);
959
+ return (0, http_context_store_js_1.getHttpContext)(req);
960
+ }
961
+ /**
962
+ * Sets the content negotiation service instance.
963
+ * @param service - Content negotiation service instance
964
+ */
965
+ setContentNegotiationService(service) {
966
+ this._contentNegotiationService = service;
967
+ }
968
+ /**
969
+ * Gets the content negotiation service if available.
970
+ * @returns Content negotiation service or undefined
971
+ */
972
+ getContentNegotiationService() {
973
+ return this._contentNegotiationService;
974
+ }
975
+ /**
976
+ * Sets the validation service instance.
977
+ * @param service - Validation service instance
978
+ */
979
+ setValidationService(service) {
980
+ this._validationService = service;
981
+ }
982
+ /**
983
+ * Gets the validation service if available.
984
+ * @returns Validation service or undefined
985
+ */
986
+ getValidationService() {
987
+ return this._validationService;
226
988
  }
227
989
  async _createHttpContext(req, res, next) {
228
990
  const principal = await this._getCurrentUser(req, res, next);
@@ -236,8 +998,9 @@ class InversifyExpressServer {
236
998
  };
237
999
  }
238
1000
  async _getCurrentUser(req, res, next) {
239
- if (this._AuthProvider !== undefined) {
240
- const authProvider = this._container.get(constants_1.TYPE.AuthProvider);
1001
+ // Check if AuthProvider is available (either via constructor or bound via setupAuthorizationForExpress)
1002
+ if (this._AuthProvider !== undefined || this._container.isBound(constants_js_1.TYPE.AuthProvider)) {
1003
+ const authProvider = this._container.get(constants_js_1.TYPE.AuthProvider);
241
1004
  return authProvider.getUser(req, res, next);
242
1005
  }
243
1006
  return Promise.resolve({
@@ -254,28 +1017,28 @@ class InversifyExpressServer {
254
1017
  }
255
1018
  params.forEach(({ type, index, parameterName, injectRoot }) => {
256
1019
  switch (type) {
257
- case constants_1.PARAMETER_TYPE.REQUEST:
1020
+ case constants_js_1.PARAMETER_TYPE.REQUEST:
258
1021
  args[index] = req;
259
1022
  break;
260
- case constants_1.PARAMETER_TYPE.NEXT:
1023
+ case constants_js_1.PARAMETER_TYPE.NEXT:
261
1024
  args[index] = next;
262
1025
  break;
263
- case constants_1.PARAMETER_TYPE.PARAMS:
1026
+ case constants_js_1.PARAMETER_TYPE.PARAMS:
264
1027
  args[index] = this.getParam(req, "params", injectRoot, parameterName);
265
1028
  break;
266
- case constants_1.PARAMETER_TYPE.QUERY:
1029
+ case constants_js_1.PARAMETER_TYPE.QUERY:
267
1030
  args[index] = this.getParam(req, "query", injectRoot, parameterName);
268
1031
  break;
269
- case constants_1.PARAMETER_TYPE.BODY:
1032
+ case constants_js_1.PARAMETER_TYPE.BODY:
270
1033
  args[index] = req.body;
271
1034
  break;
272
- case constants_1.PARAMETER_TYPE.HEADERS:
1035
+ case constants_js_1.PARAMETER_TYPE.HEADERS:
273
1036
  args[index] = this.getParam(req, "headers", injectRoot, parameterName);
274
1037
  break;
275
- case constants_1.PARAMETER_TYPE.COOKIES:
1038
+ case constants_js_1.PARAMETER_TYPE.COOKIES:
276
1039
  args[index] = this.getParam(req, "cookies", injectRoot, parameterName);
277
1040
  break;
278
- case constants_1.PARAMETER_TYPE.PRINCIPAL:
1041
+ case constants_js_1.PARAMETER_TYPE.PRINCIPAL:
279
1042
  args[index] = this._getPrincipal(req);
280
1043
  break;
281
1044
  default: