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