@morojs/moro 1.6.2 → 1.6.4

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 (366) hide show
  1. package/dist/core/http/http-server.js +12 -9
  2. package/dist/core/http/http-server.js.map +1 -1
  3. package/dist/core/http/uws-http-server.js +1 -1
  4. package/dist/core/http/uws-http-server.js.map +1 -1
  5. package/dist/core/middleware/built-in/auth/core.d.ts +78 -0
  6. package/dist/core/middleware/built-in/auth/core.js +358 -0
  7. package/dist/core/middleware/built-in/auth/core.js.map +1 -0
  8. package/dist/core/middleware/built-in/{auth-helpers.js → auth/helpers.js} +1 -1
  9. package/dist/core/middleware/built-in/auth/helpers.js.map +1 -0
  10. package/dist/core/middleware/built-in/auth/hook.d.ts +30 -0
  11. package/dist/core/middleware/built-in/auth/hook.js +99 -0
  12. package/dist/core/middleware/built-in/auth/hook.js.map +1 -0
  13. package/dist/core/middleware/built-in/auth/index.d.ts +7 -0
  14. package/dist/core/middleware/built-in/auth/index.js +15 -0
  15. package/dist/core/middleware/built-in/auth/index.js.map +1 -0
  16. package/dist/core/middleware/built-in/{jwt-helpers.js → auth/jwt-helpers.js} +1 -1
  17. package/dist/core/middleware/built-in/auth/jwt-helpers.js.map +1 -0
  18. package/dist/core/middleware/built-in/auth/middleware.d.ts +23 -0
  19. package/dist/core/middleware/built-in/auth/middleware.js +71 -0
  20. package/dist/core/middleware/built-in/auth/middleware.js.map +1 -0
  21. package/dist/core/middleware/built-in/{auth-providers.d.ts → auth/providers.d.ts} +1 -1
  22. package/dist/core/middleware/built-in/{auth-providers.js → auth/providers.js} +1 -1
  23. package/dist/core/middleware/built-in/auth/providers.js.map +1 -0
  24. package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/file.d.ts +1 -1
  25. package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/file.js +1 -1
  26. package/dist/core/middleware/built-in/cache/adapters/cache/file.js.map +1 -0
  27. package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/index.d.ts +1 -1
  28. package/dist/core/middleware/built-in/cache/adapters/cache/index.js.map +1 -0
  29. package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/memory.d.ts +1 -1
  30. package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/memory.js +1 -1
  31. package/dist/core/middleware/built-in/cache/adapters/cache/memory.js.map +1 -0
  32. package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/redis.d.ts +1 -1
  33. package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/redis.js +2 -2
  34. package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -0
  35. package/dist/core/middleware/built-in/{adapters → cache/adapters}/index.d.ts +0 -2
  36. package/{src/core/middleware/built-in/adapters/index.ts → dist/core/middleware/built-in/cache/adapters/index.js} +1 -3
  37. package/dist/core/middleware/built-in/cache/adapters/index.js.map +1 -0
  38. package/dist/core/middleware/built-in/cache/core.d.ts +37 -0
  39. package/dist/core/middleware/built-in/cache/core.js +87 -0
  40. package/dist/core/middleware/built-in/cache/core.js.map +1 -0
  41. package/dist/core/middleware/built-in/cache/hook.d.ts +20 -0
  42. package/dist/core/middleware/built-in/{cache.js → cache/hook.js} +22 -5
  43. package/dist/core/middleware/built-in/cache/hook.js.map +1 -0
  44. package/dist/core/middleware/built-in/cache/index.d.ts +3 -0
  45. package/dist/core/middleware/built-in/cache/index.js +9 -0
  46. package/dist/core/middleware/built-in/cache/index.js.map +1 -0
  47. package/dist/core/middleware/built-in/cache/middleware.d.ts +17 -0
  48. package/dist/core/middleware/built-in/cache/middleware.js +44 -0
  49. package/dist/core/middleware/built-in/cache/middleware.js.map +1 -0
  50. package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/azure.d.ts +1 -1
  51. package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/azure.js +1 -1
  52. package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.js.map +1 -0
  53. package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/cloudflare.d.ts +1 -1
  54. package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/cloudflare.js +1 -1
  55. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.js.map +1 -0
  56. package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/cloudfront.d.ts +1 -1
  57. package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/cloudfront.js +2 -2
  58. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudfront.js.map +1 -0
  59. package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/index.d.ts +1 -1
  60. package/dist/core/middleware/built-in/cdn/adapters/cdn/index.js.map +1 -0
  61. package/dist/core/middleware/built-in/cdn/adapters/index.d.ts +2 -0
  62. package/dist/core/middleware/built-in/{adapters → cdn/adapters}/index.js +0 -2
  63. package/dist/core/middleware/built-in/cdn/adapters/index.js.map +1 -0
  64. package/dist/core/middleware/built-in/cdn/core.d.ts +43 -0
  65. package/dist/core/middleware/built-in/cdn/core.js +144 -0
  66. package/dist/core/middleware/built-in/cdn/core.js.map +1 -0
  67. package/dist/core/middleware/built-in/cdn/hook.d.ts +22 -0
  68. package/dist/core/middleware/built-in/cdn/hook.js +70 -0
  69. package/dist/core/middleware/built-in/cdn/hook.js.map +1 -0
  70. package/dist/core/middleware/built-in/cdn/index.d.ts +5 -0
  71. package/dist/core/middleware/built-in/cdn/index.js +11 -0
  72. package/dist/core/middleware/built-in/cdn/index.js.map +1 -0
  73. package/dist/core/middleware/built-in/cdn/middleware.d.ts +21 -0
  74. package/dist/core/middleware/built-in/cdn/middleware.js +52 -0
  75. package/dist/core/middleware/built-in/cdn/middleware.js.map +1 -0
  76. package/dist/core/middleware/built-in/cookie/core.d.ts +37 -0
  77. package/dist/core/middleware/built-in/cookie/core.js +83 -0
  78. package/dist/core/middleware/built-in/cookie/core.js.map +1 -0
  79. package/dist/core/middleware/built-in/cookie/hook.d.ts +20 -0
  80. package/dist/core/middleware/built-in/cookie/hook.js +47 -0
  81. package/dist/core/middleware/built-in/cookie/hook.js.map +1 -0
  82. package/dist/core/middleware/built-in/cookie/index.d.ts +3 -0
  83. package/dist/core/middleware/built-in/cookie/index.js +9 -0
  84. package/dist/core/middleware/built-in/cookie/index.js.map +1 -0
  85. package/dist/core/middleware/built-in/cookie/middleware.d.ts +17 -0
  86. package/dist/core/middleware/built-in/cookie/middleware.js +36 -0
  87. package/dist/core/middleware/built-in/cookie/middleware.js.map +1 -0
  88. package/dist/core/middleware/built-in/cors/core.d.ts +23 -0
  89. package/dist/core/middleware/built-in/cors/core.js +51 -0
  90. package/dist/core/middleware/built-in/cors/core.js.map +1 -0
  91. package/dist/core/middleware/built-in/cors/hook.d.ts +17 -0
  92. package/dist/core/middleware/built-in/cors/hook.js +37 -0
  93. package/dist/core/middleware/built-in/cors/hook.js.map +1 -0
  94. package/dist/core/middleware/built-in/cors/index.d.ts +3 -0
  95. package/dist/core/middleware/built-in/cors/index.js +9 -0
  96. package/dist/core/middleware/built-in/cors/index.js.map +1 -0
  97. package/dist/core/middleware/built-in/cors/middleware.d.ts +16 -0
  98. package/dist/core/middleware/built-in/cors/middleware.js +22 -0
  99. package/dist/core/middleware/built-in/cors/middleware.js.map +1 -0
  100. package/dist/core/middleware/built-in/csp/core.d.ts +45 -0
  101. package/dist/core/middleware/built-in/csp/core.js +88 -0
  102. package/dist/core/middleware/built-in/csp/core.js.map +1 -0
  103. package/dist/core/middleware/built-in/csp/hook.d.ts +22 -0
  104. package/dist/core/middleware/built-in/csp/hook.js +47 -0
  105. package/dist/core/middleware/built-in/csp/hook.js.map +1 -0
  106. package/dist/core/middleware/built-in/csp/index.d.ts +3 -0
  107. package/dist/core/middleware/built-in/csp/index.js +9 -0
  108. package/dist/core/middleware/built-in/csp/index.js.map +1 -0
  109. package/dist/core/middleware/built-in/csp/middleware.d.ts +19 -0
  110. package/dist/core/middleware/built-in/csp/middleware.js +29 -0
  111. package/dist/core/middleware/built-in/csp/middleware.js.map +1 -0
  112. package/dist/core/middleware/built-in/csrf/core.d.ts +28 -0
  113. package/dist/core/middleware/built-in/csrf/core.js +69 -0
  114. package/dist/core/middleware/built-in/csrf/core.js.map +1 -0
  115. package/dist/core/middleware/built-in/csrf/hook.d.ts +17 -0
  116. package/dist/core/middleware/built-in/csrf/hook.js +45 -0
  117. package/dist/core/middleware/built-in/csrf/hook.js.map +1 -0
  118. package/dist/core/middleware/built-in/csrf/index.d.ts +3 -0
  119. package/dist/core/middleware/built-in/csrf/index.js +9 -0
  120. package/dist/core/middleware/built-in/csrf/index.js.map +1 -0
  121. package/dist/core/middleware/built-in/csrf/middleware.d.ts +16 -0
  122. package/dist/core/middleware/built-in/csrf/middleware.js +34 -0
  123. package/dist/core/middleware/built-in/csrf/middleware.js.map +1 -0
  124. package/dist/core/middleware/built-in/error-tracker/index.d.ts +1 -0
  125. package/dist/core/middleware/built-in/error-tracker/index.js +4 -0
  126. package/dist/core/middleware/built-in/error-tracker/index.js.map +1 -0
  127. package/dist/core/middleware/built-in/error-tracker/middleware.d.ts +12 -0
  128. package/dist/core/middleware/built-in/{error-tracker.js → error-tracker/middleware.js} +14 -3
  129. package/dist/core/middleware/built-in/error-tracker/middleware.js.map +1 -0
  130. package/dist/core/middleware/built-in/index.d.ts +25 -59
  131. package/dist/core/middleware/built-in/index.js +31 -31
  132. package/dist/core/middleware/built-in/index.js.map +1 -1
  133. package/dist/core/middleware/built-in/performance-monitor/index.d.ts +1 -0
  134. package/dist/core/middleware/built-in/performance-monitor/index.js +4 -0
  135. package/dist/core/middleware/built-in/performance-monitor/index.js.map +1 -0
  136. package/dist/core/middleware/built-in/performance-monitor/middleware.d.ts +12 -0
  137. package/dist/core/middleware/built-in/{performance-monitor.js → performance-monitor/middleware.js} +14 -3
  138. package/dist/core/middleware/built-in/performance-monitor/middleware.js.map +1 -0
  139. package/dist/core/middleware/built-in/rate-limit/core.d.ts +33 -0
  140. package/dist/core/middleware/built-in/rate-limit/core.js +86 -0
  141. package/dist/core/middleware/built-in/rate-limit/core.js.map +1 -0
  142. package/dist/core/middleware/built-in/rate-limit/hook.d.ts +20 -0
  143. package/dist/core/middleware/built-in/{rate-limit.js → rate-limit/hook.js} +22 -16
  144. package/dist/core/middleware/built-in/rate-limit/hook.js.map +1 -0
  145. package/dist/core/middleware/built-in/rate-limit/index.d.ts +3 -0
  146. package/dist/core/middleware/built-in/rate-limit/index.js +9 -0
  147. package/dist/core/middleware/built-in/rate-limit/index.js.map +1 -0
  148. package/dist/core/middleware/built-in/rate-limit/middleware.d.ts +16 -0
  149. package/dist/core/middleware/built-in/rate-limit/middleware.js +35 -0
  150. package/dist/core/middleware/built-in/rate-limit/middleware.js.map +1 -0
  151. package/dist/core/middleware/built-in/request-logger/index.d.ts +1 -0
  152. package/dist/core/middleware/built-in/request-logger/index.js +4 -0
  153. package/dist/core/middleware/built-in/request-logger/index.js.map +1 -0
  154. package/dist/core/middleware/built-in/request-logger/middleware.d.ts +12 -0
  155. package/dist/core/middleware/built-in/{request-logger.js → request-logger/middleware.js} +14 -3
  156. package/dist/core/middleware/built-in/request-logger/middleware.js.map +1 -0
  157. package/dist/core/middleware/built-in/session/core.d.ts +73 -0
  158. package/dist/core/middleware/built-in/session/core.js +227 -0
  159. package/dist/core/middleware/built-in/session/core.js.map +1 -0
  160. package/dist/core/middleware/built-in/session/hook.d.ts +17 -0
  161. package/dist/core/middleware/built-in/session/hook.js +53 -0
  162. package/dist/core/middleware/built-in/session/hook.js.map +1 -0
  163. package/dist/core/middleware/built-in/session/index.d.ts +3 -0
  164. package/dist/core/middleware/built-in/session/index.js +9 -0
  165. package/dist/core/middleware/built-in/session/index.js.map +1 -0
  166. package/dist/core/middleware/built-in/session/middleware.d.ts +17 -0
  167. package/dist/core/middleware/built-in/session/middleware.js +38 -0
  168. package/dist/core/middleware/built-in/session/middleware.js.map +1 -0
  169. package/dist/core/middleware/built-in/sse/core.d.ts +44 -0
  170. package/dist/core/middleware/built-in/sse/core.js +117 -0
  171. package/dist/core/middleware/built-in/sse/core.js.map +1 -0
  172. package/dist/core/middleware/built-in/sse/hook.d.ts +18 -0
  173. package/dist/core/middleware/built-in/sse/hook.js +60 -0
  174. package/dist/core/middleware/built-in/sse/hook.js.map +1 -0
  175. package/dist/core/middleware/built-in/sse/index.d.ts +3 -0
  176. package/dist/core/middleware/built-in/sse/index.js +9 -0
  177. package/dist/core/middleware/built-in/sse/index.js.map +1 -0
  178. package/dist/core/middleware/built-in/sse/middleware.d.ts +18 -0
  179. package/dist/core/middleware/built-in/sse/middleware.js +43 -0
  180. package/dist/core/middleware/built-in/sse/middleware.js.map +1 -0
  181. package/dist/core/middleware/built-in/validation/core.d.ts +23 -0
  182. package/dist/core/middleware/built-in/validation/core.js +93 -0
  183. package/dist/core/middleware/built-in/validation/core.js.map +1 -0
  184. package/dist/core/middleware/built-in/validation/hook.d.ts +13 -0
  185. package/dist/core/middleware/built-in/{validation.js → validation/hook.js} +14 -3
  186. package/dist/core/middleware/built-in/validation/hook.js.map +1 -0
  187. package/dist/core/middleware/built-in/validation/index.d.ts +3 -0
  188. package/dist/core/middleware/built-in/validation/index.js +9 -0
  189. package/dist/core/middleware/built-in/validation/index.js.map +1 -0
  190. package/dist/core/middleware/built-in/validation/middleware.d.ts +16 -0
  191. package/dist/core/middleware/built-in/validation/middleware.js +27 -0
  192. package/dist/core/middleware/built-in/validation/middleware.js.map +1 -0
  193. package/dist/core/middleware/index.js +6 -0
  194. package/dist/core/middleware/index.js.map +1 -1
  195. package/dist/core/routing/unified-router.d.ts +4 -20
  196. package/dist/core/routing/unified-router.js +61 -106
  197. package/dist/core/routing/unified-router.js.map +1 -1
  198. package/dist/index.d.ts +3 -2
  199. package/dist/index.js +3 -2
  200. package/dist/index.js.map +1 -1
  201. package/dist/moro.js +12 -18
  202. package/dist/moro.js.map +1 -1
  203. package/dist/types/hooks.d.ts +3 -0
  204. package/package.json +2 -6
  205. package/dist/core/middleware/built-in/adapters/cache/file.js.map +0 -1
  206. package/dist/core/middleware/built-in/adapters/cache/index.js.map +0 -1
  207. package/dist/core/middleware/built-in/adapters/cache/memory.js.map +0 -1
  208. package/dist/core/middleware/built-in/adapters/cache/redis.js.map +0 -1
  209. package/dist/core/middleware/built-in/adapters/cdn/azure.js.map +0 -1
  210. package/dist/core/middleware/built-in/adapters/cdn/cloudflare.js.map +0 -1
  211. package/dist/core/middleware/built-in/adapters/cdn/cloudfront.js.map +0 -1
  212. package/dist/core/middleware/built-in/adapters/cdn/index.js.map +0 -1
  213. package/dist/core/middleware/built-in/adapters/index.js.map +0 -1
  214. package/dist/core/middleware/built-in/auth-helpers.js.map +0 -1
  215. package/dist/core/middleware/built-in/auth-providers.js.map +0 -1
  216. package/dist/core/middleware/built-in/auth.d.ts +0 -30
  217. package/dist/core/middleware/built-in/auth.js +0 -348
  218. package/dist/core/middleware/built-in/auth.js.map +0 -1
  219. package/dist/core/middleware/built-in/cache.d.ts +0 -3
  220. package/dist/core/middleware/built-in/cache.js.map +0 -1
  221. package/dist/core/middleware/built-in/cdn.d.ts +0 -3
  222. package/dist/core/middleware/built-in/cdn.js +0 -109
  223. package/dist/core/middleware/built-in/cdn.js.map +0 -1
  224. package/dist/core/middleware/built-in/cookie.d.ts +0 -14
  225. package/dist/core/middleware/built-in/cookie.js +0 -64
  226. package/dist/core/middleware/built-in/cookie.js.map +0 -1
  227. package/dist/core/middleware/built-in/cors.d.ts +0 -2
  228. package/dist/core/middleware/built-in/cors.js +0 -25
  229. package/dist/core/middleware/built-in/cors.js.map +0 -1
  230. package/dist/core/middleware/built-in/csp.d.ts +0 -22
  231. package/dist/core/middleware/built-in/csp.js +0 -68
  232. package/dist/core/middleware/built-in/csp.js.map +0 -1
  233. package/dist/core/middleware/built-in/csrf.d.ts +0 -9
  234. package/dist/core/middleware/built-in/csrf.js +0 -60
  235. package/dist/core/middleware/built-in/csrf.js.map +0 -1
  236. package/dist/core/middleware/built-in/error-tracker.d.ts +0 -1
  237. package/dist/core/middleware/built-in/error-tracker.js.map +0 -1
  238. package/dist/core/middleware/built-in/jwt-helpers.js.map +0 -1
  239. package/dist/core/middleware/built-in/performance-monitor.d.ts +0 -1
  240. package/dist/core/middleware/built-in/performance-monitor.js.map +0 -1
  241. package/dist/core/middleware/built-in/rate-limit.d.ts +0 -6
  242. package/dist/core/middleware/built-in/rate-limit.js.map +0 -1
  243. package/dist/core/middleware/built-in/request-logger.d.ts +0 -1
  244. package/dist/core/middleware/built-in/request-logger.js.map +0 -1
  245. package/dist/core/middleware/built-in/session.d.ts +0 -41
  246. package/dist/core/middleware/built-in/session.js +0 -205
  247. package/dist/core/middleware/built-in/session.js.map +0 -1
  248. package/dist/core/middleware/built-in/sse.d.ts +0 -6
  249. package/dist/core/middleware/built-in/sse.js +0 -69
  250. package/dist/core/middleware/built-in/sse.js.map +0 -1
  251. package/dist/core/middleware/built-in/validation.d.ts +0 -2
  252. package/dist/core/middleware/built-in/validation.js.map +0 -1
  253. package/jest.config.mjs +0 -41
  254. package/src/core/auth/README.md +0 -339
  255. package/src/core/auth/morojs-adapter.ts +0 -415
  256. package/src/core/config/config-manager.ts +0 -133
  257. package/src/core/config/config-sources.ts +0 -600
  258. package/src/core/config/config-validator.ts +0 -1116
  259. package/src/core/config/file-loader.ts +0 -150
  260. package/src/core/config/index.ts +0 -109
  261. package/src/core/config/schema.ts +0 -164
  262. package/src/core/config/utils.ts +0 -244
  263. package/src/core/database/README.md +0 -238
  264. package/src/core/database/adapters/drizzle.ts +0 -415
  265. package/src/core/database/adapters/index.ts +0 -42
  266. package/src/core/database/adapters/mongodb.ts +0 -317
  267. package/src/core/database/adapters/mysql.ts +0 -235
  268. package/src/core/database/adapters/postgresql.ts +0 -226
  269. package/src/core/database/adapters/redis.ts +0 -379
  270. package/src/core/database/adapters/sqlite.ts +0 -263
  271. package/src/core/database/index.ts +0 -3
  272. package/src/core/docs/index.ts +0 -231
  273. package/src/core/docs/openapi-generator.ts +0 -576
  274. package/src/core/docs/schema-to-openapi.ts +0 -145
  275. package/src/core/docs/simple-docs.ts +0 -295
  276. package/src/core/docs/swagger-ui.ts +0 -354
  277. package/src/core/docs/zod-to-openapi.ts +0 -532
  278. package/src/core/events/event-bus.ts +0 -231
  279. package/src/core/events/index.ts +0 -12
  280. package/src/core/framework.ts +0 -885
  281. package/src/core/http/http-server.ts +0 -1847
  282. package/src/core/http/index.ts +0 -7
  283. package/src/core/http/uws-http-server.ts +0 -591
  284. package/src/core/logger/filters.ts +0 -153
  285. package/src/core/logger/index.ts +0 -21
  286. package/src/core/logger/logger.ts +0 -1033
  287. package/src/core/logger/outputs.ts +0 -132
  288. package/src/core/middleware/built-in/adapters/cache/file.ts +0 -104
  289. package/src/core/middleware/built-in/adapters/cache/index.ts +0 -23
  290. package/src/core/middleware/built-in/adapters/cache/memory.ts +0 -73
  291. package/src/core/middleware/built-in/adapters/cache/redis.ts +0 -114
  292. package/src/core/middleware/built-in/adapters/cdn/azure.ts +0 -60
  293. package/src/core/middleware/built-in/adapters/cdn/cloudflare.ts +0 -83
  294. package/src/core/middleware/built-in/adapters/cdn/cloudfront.ts +0 -94
  295. package/src/core/middleware/built-in/adapters/cdn/index.ts +0 -23
  296. package/src/core/middleware/built-in/auth-helpers.ts +0 -401
  297. package/src/core/middleware/built-in/auth-providers.ts +0 -480
  298. package/src/core/middleware/built-in/auth.ts +0 -410
  299. package/src/core/middleware/built-in/cache.ts +0 -213
  300. package/src/core/middleware/built-in/cdn.ts +0 -124
  301. package/src/core/middleware/built-in/cookie.ts +0 -85
  302. package/src/core/middleware/built-in/cors.ts +0 -38
  303. package/src/core/middleware/built-in/csp.ts +0 -101
  304. package/src/core/middleware/built-in/csrf.ts +0 -82
  305. package/src/core/middleware/built-in/error-tracker.ts +0 -16
  306. package/src/core/middleware/built-in/index.ts +0 -87
  307. package/src/core/middleware/built-in/jwt-helpers.ts +0 -243
  308. package/src/core/middleware/built-in/performance-monitor.ts +0 -25
  309. package/src/core/middleware/built-in/rate-limit.ts +0 -60
  310. package/src/core/middleware/built-in/request-logger.ts +0 -16
  311. package/src/core/middleware/built-in/session.ts +0 -287
  312. package/src/core/middleware/built-in/sse.ts +0 -88
  313. package/src/core/middleware/built-in/validation.ts +0 -33
  314. package/src/core/middleware/index.ts +0 -177
  315. package/src/core/modules/auto-discovery.ts +0 -726
  316. package/src/core/modules/index.ts +0 -3
  317. package/src/core/modules/modules.ts +0 -135
  318. package/src/core/networking/adapters/index.ts +0 -17
  319. package/src/core/networking/adapters/socketio-adapter.ts +0 -254
  320. package/src/core/networking/adapters/uws-adapter.ts +0 -619
  321. package/src/core/networking/adapters/ws-adapter.ts +0 -429
  322. package/src/core/networking/index.ts +0 -4
  323. package/src/core/networking/service-discovery.ts +0 -303
  324. package/src/core/networking/websocket-adapter.ts +0 -217
  325. package/src/core/networking/websocket-manager.ts +0 -308
  326. package/src/core/pooling/object-pool-manager.ts +0 -630
  327. package/src/core/routing/app-integration.ts +0 -164
  328. package/src/core/routing/index.ts +0 -261
  329. package/src/core/routing/path-matcher.ts +0 -222
  330. package/src/core/routing/router.ts +0 -97
  331. package/src/core/routing/unified-router.ts +0 -870
  332. package/src/core/runtime/aws-lambda-adapter.ts +0 -147
  333. package/src/core/runtime/base-adapter.ts +0 -130
  334. package/src/core/runtime/cloudflare-workers-adapter.ts +0 -152
  335. package/src/core/runtime/index.ts +0 -62
  336. package/src/core/runtime/node-adapter.ts +0 -202
  337. package/src/core/runtime/vercel-edge-adapter.ts +0 -114
  338. package/src/core/utilities/circuit-breaker.ts +0 -46
  339. package/src/core/utilities/container.ts +0 -736
  340. package/src/core/utilities/hooks.ts +0 -142
  341. package/src/core/utilities/index.ts +0 -17
  342. package/src/core/utilities/package-utils.ts +0 -59
  343. package/src/core/validation/adapters.ts +0 -147
  344. package/src/core/validation/index.ts +0 -258
  345. package/src/core/validation/schema-interface.ts +0 -100
  346. package/src/index.ts +0 -233
  347. package/src/moro.ts +0 -1728
  348. package/src/types/auth.ts +0 -440
  349. package/src/types/cache.ts +0 -38
  350. package/src/types/cdn.ts +0 -22
  351. package/src/types/config.ts +0 -229
  352. package/src/types/core.ts +0 -58
  353. package/src/types/database.ts +0 -32
  354. package/src/types/discovery.ts +0 -7
  355. package/src/types/events.ts +0 -82
  356. package/src/types/hooks.ts +0 -47
  357. package/src/types/http.ts +0 -89
  358. package/src/types/logger.ts +0 -102
  359. package/src/types/module.ts +0 -99
  360. package/src/types/runtime.ts +0 -76
  361. package/src/types/session.ts +0 -89
  362. package/tsconfig.json +0 -23
  363. /package/dist/core/middleware/built-in/{auth-helpers.d.ts → auth/helpers.d.ts} +0 -0
  364. /package/dist/core/middleware/built-in/{jwt-helpers.d.ts → auth/jwt-helpers.d.ts} +0 -0
  365. /package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/index.js +0 -0
  366. /package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/index.js +0 -0
@@ -1,1847 +0,0 @@
1
- // src/core/http-server.ts
2
- import { IncomingMessage, ServerResponse, createServer, Server } from 'http';
3
- import * as zlib from 'zlib';
4
- import { createReadStream } from 'fs';
5
- import * as crypto from 'crypto';
6
- import { promisify } from 'util';
7
- import { createFrameworkLogger } from '../logger/index.js';
8
- import {
9
- HttpRequest,
10
- HttpResponse,
11
- HttpHandler,
12
- Middleware,
13
- RouteEntry,
14
- } from '../../types/http.js';
15
- import { PathMatcher } from '../routing/path-matcher.js';
16
- import { ObjectPoolManager } from '../pooling/object-pool-manager.js';
17
-
18
- const gzip = promisify(zlib.gzip);
19
- const deflate = promisify(zlib.deflate);
20
-
21
- export class MoroHttpServer {
22
- private server: Server;
23
- private routes: RouteEntry[] = [];
24
- private globalMiddleware: Middleware[] = [];
25
- private compressionEnabled = true;
26
- private compressionThreshold = 1024;
27
- private requestTrackingEnabled = true; // Generate request IDs
28
- private logger = createFrameworkLogger('HttpServer');
29
- private hookManager: any;
30
- private requestCounter = 0;
31
-
32
- // Use shared object pool manager
33
- private poolManager = ObjectPoolManager.getInstance();
34
-
35
- // Pre-compiled response templates for common responses
36
- private static readonly RESPONSE_TEMPLATES = {
37
- notFound: Buffer.from('{"success":false,"error":"Not found"}'),
38
- unauthorized: Buffer.from('{"success":false,"error":"Unauthorized"}'),
39
- forbidden: Buffer.from('{"success":false,"error":"Forbidden"}'),
40
- internalError: Buffer.from('{"success":false,"error":"Internal server error"}'),
41
- methodNotAllowed: Buffer.from('{"success":false,"error":"Method not allowed"}'),
42
- rateLimited: Buffer.from('{"success":false,"error":"Rate limit exceeded"}'),
43
- };
44
-
45
- // Buffer pool for zero-copy operations
46
- private static readonly BUFFER_SIZES = [64, 256, 1024, 4096, 16384];
47
- private static readonly BUFFER_POOLS = new Map<number, Buffer[]>();
48
-
49
- static {
50
- // Pre-allocate buffer pools for zero-allocation responses
51
- for (const size of MoroHttpServer.BUFFER_SIZES) {
52
- MoroHttpServer.BUFFER_POOLS.set(size, []);
53
- for (let i = 0; i < 50; i++) {
54
- // 50 buffers per size
55
- MoroHttpServer.BUFFER_POOLS.get(size)!.push(Buffer.allocUnsafe(size));
56
- }
57
- }
58
- }
59
-
60
- private static getOptimalBuffer(size: number): Buffer {
61
- // Find the smallest buffer that fits
62
- for (const poolSize of MoroHttpServer.BUFFER_SIZES) {
63
- if (size <= poolSize) {
64
- const pool = MoroHttpServer.BUFFER_POOLS.get(poolSize)!;
65
- return pool.length > 0 ? pool.pop()! : Buffer.allocUnsafe(poolSize);
66
- }
67
- }
68
- return Buffer.allocUnsafe(size);
69
- }
70
-
71
- private static returnBuffer(buffer: Buffer): void {
72
- // Return buffer to appropriate pool
73
- const size = buffer.length;
74
- if (MoroHttpServer.BUFFER_POOLS.has(size)) {
75
- const pool = MoroHttpServer.BUFFER_POOLS.get(size)!;
76
- if (pool.length < 50) {
77
- // Don't let pools grow too large
78
- pool.push(buffer);
79
- }
80
- }
81
- }
82
-
83
- constructor() {
84
- this.server = createServer(this.handleRequest.bind(this));
85
-
86
- // Optimize server for high performance (conservative settings for compatibility)
87
- this.server.keepAliveTimeout = 5000; // 5 seconds
88
- this.server.headersTimeout = 6000; // 6 seconds
89
- this.server.timeout = 30000; // 30 seconds request timeout
90
- }
91
-
92
- // Configure server for maximum performance (can disable all overhead)
93
- configurePerformance(
94
- config: {
95
- compression?: { enabled: boolean; threshold?: number };
96
- minimal?: boolean;
97
- } = {}
98
- ) {
99
- if (config.compression !== undefined) {
100
- this.compressionEnabled = config.compression.enabled;
101
- if (config.compression.threshold !== undefined) {
102
- this.compressionThreshold = config.compression.threshold;
103
- }
104
- }
105
-
106
- // Minimal mode - disable ALL overhead for pure speed
107
- if (config.minimal) {
108
- this.compressionEnabled = false;
109
- this.compressionThreshold = Infinity; // Never compress
110
- }
111
- }
112
-
113
- // Configure request tracking (ID generation)
114
- setRequestTracking(enabled: boolean): void {
115
- this.requestTrackingEnabled = enabled;
116
- }
117
-
118
- // Middleware management
119
- use(middleware: Middleware): void {
120
- this.globalMiddleware.push(middleware);
121
- }
122
-
123
- // Set hooks manager for request processing
124
- setHookManager(hookManager: any): void {
125
- this.hookManager = hookManager;
126
- }
127
-
128
- // Routing methods
129
- get(path: string, ...handlers: (Middleware | HttpHandler)[]): void {
130
- this.addRoute('GET', path, handlers);
131
- }
132
-
133
- post(path: string, ...handlers: (Middleware | HttpHandler)[]): void {
134
- this.addRoute('POST', path, handlers);
135
- }
136
-
137
- put(path: string, ...handlers: (Middleware | HttpHandler)[]): void {
138
- this.addRoute('PUT', path, handlers);
139
- }
140
-
141
- delete(path: string, ...handlers: (Middleware | HttpHandler)[]): void {
142
- this.addRoute('DELETE', path, handlers);
143
- }
144
-
145
- patch(path: string, ...handlers: (Middleware | HttpHandler)[]): void {
146
- this.addRoute('PATCH', path, handlers);
147
- }
148
-
149
- private addRoute(method: string, path: string, handlers: (Middleware | HttpHandler)[]): void {
150
- const { pattern, paramNames } = this.pathToRegex(path);
151
- const handler = handlers.pop() as HttpHandler;
152
- const middleware = handlers as Middleware[];
153
-
154
- const route = {
155
- method,
156
- path,
157
- pattern,
158
- paramNames,
159
- handler,
160
- middleware,
161
- };
162
-
163
- this.routes.push(route);
164
-
165
- // Organize routes for optimal lookup
166
- if (paramNames.length === 0) {
167
- // Static route - O(1) lookup
168
- const staticKey = `${method}:${path}`;
169
- this.staticRoutes.set(staticKey, route);
170
- } else {
171
- // Dynamic route - organize by segment count for faster matching
172
- this.dynamicRoutes.push(route);
173
-
174
- const segments = path.split('/').filter(s => s.length > 0);
175
- const segmentCount = segments.length;
176
-
177
- if (!this.routesBySegmentCount.has(segmentCount)) {
178
- this.routesBySegmentCount.set(segmentCount, []);
179
- }
180
- this.routesBySegmentCount.get(segmentCount)!.push(route);
181
- }
182
- }
183
-
184
- private pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {
185
- // Use shared PathMatcher for consistent path compilation
186
- const compiled = PathMatcher.compile(path);
187
- return {
188
- pattern: compiled.pattern || new RegExp(`^${path.replace(/\//g, '\\/')}$`),
189
- paramNames: compiled.paramNames,
190
- };
191
- }
192
-
193
- private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
194
- const httpReq = this.enhanceRequest(req);
195
- const httpRes = this.enhanceResponse(res, httpReq);
196
-
197
- // Store original params for efficient cleanup
198
- const originalParams = httpReq.params;
199
-
200
- try {
201
- // Optimized URL and query parsing with object pooling
202
- const urlString = req.url!;
203
- const queryIndex = urlString.indexOf('?');
204
-
205
- if (queryIndex === -1) {
206
- // No query string
207
- httpReq.path = urlString;
208
- httpReq.query = {};
209
- } else {
210
- // Has query string - parse efficiently with pooled object
211
- httpReq.path = urlString.substring(0, queryIndex);
212
- httpReq.query = this.parseQueryStringPooled(urlString.substring(queryIndex + 1));
213
- }
214
-
215
- // Method checking - avoid array includes
216
- const method = req.method!;
217
- if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
218
- httpReq.body = await this.parseBody(req);
219
- }
220
-
221
- // Execute hooks before request processing
222
- if (this.hookManager) {
223
- await this.hookManager.execute('request', {
224
- request: httpReq,
225
- response: httpRes,
226
- });
227
- }
228
-
229
- // Execute global middleware first
230
- await this.executeMiddleware(this.globalMiddleware, httpReq, httpRes);
231
-
232
- // If middleware handled the request, don't continue
233
- if (httpRes.headersSent) {
234
- return;
235
- }
236
-
237
- // Find matching route
238
- const route = this.findRoute(req.method!, httpReq.path);
239
- if (!route) {
240
- // 404 response with pre-compiled buffer
241
- httpRes.statusCode = 404;
242
- httpRes.setHeader('Content-Type', 'application/json; charset=utf-8');
243
- httpRes.setHeader('Content-Length', MoroHttpServer.RESPONSE_TEMPLATES.notFound.length);
244
- httpRes.end(MoroHttpServer.RESPONSE_TEMPLATES.notFound);
245
- return;
246
- }
247
-
248
- // Extract path parameters - optimized with object pooling
249
- const matches = httpReq.path.match(route.pattern);
250
- if (matches) {
251
- // Use pooled object for parameters
252
- httpReq.params = this.acquireParamObject();
253
- route.paramNames.forEach((name, index) => {
254
- httpReq.params[name] = matches[index + 1];
255
- });
256
- }
257
-
258
- // Execute middleware chain
259
- await this.executeMiddleware(route.middleware, httpReq, httpRes);
260
-
261
- // Execute handler
262
- await route.handler(httpReq, httpRes);
263
- } catch (error) {
264
- // Debug: Log the actual error and where it came from
265
- this.logger.debug('Request error details', 'RequestHandler', {
266
- errorType: typeof error,
267
- errorMessage: error instanceof Error ? error.message : String(error),
268
- errorStack: error instanceof Error ? error.stack : 'No stack trace',
269
- requestPath: req.url,
270
- requestMethod: req.method,
271
- });
272
-
273
- this.logger.error('Request error', 'RequestHandler', {
274
- error: error instanceof Error ? error.message : String(error),
275
- requestId: httpReq.requestId,
276
- method: req.method,
277
- path: req.url,
278
- });
279
-
280
- if (!httpRes.headersSent) {
281
- // Ensure response is properly enhanced before using custom methods
282
- if (typeof httpRes.status === 'function' && typeof httpRes.json === 'function') {
283
- httpRes.status(500).json({
284
- success: false,
285
- error: 'Internal server error',
286
- requestId: httpReq.requestId,
287
- });
288
- } else {
289
- // Defensive fallback - check each method individually
290
- if (typeof httpRes.setHeader === 'function') {
291
- httpRes.statusCode = 500;
292
- httpRes.setHeader('Content-Type', 'application/json');
293
- } else {
294
- // Even setHeader doesn't exist - object is completely wrong
295
- this.logger.error('Response object is not a proper ServerResponse', 'RequestHandler', {
296
- responseType: typeof httpRes,
297
- responseKeys: Object.keys(httpRes),
298
- });
299
- }
300
-
301
- if (typeof httpRes.end === 'function') {
302
- httpRes.end(
303
- JSON.stringify({
304
- success: false,
305
- error: 'Internal server error',
306
- requestId: httpReq.requestId,
307
- })
308
- );
309
- } else {
310
- this.logger.error(
311
- 'Cannot send error response - end() method missing',
312
- 'RequestHandler'
313
- );
314
- }
315
- }
316
- }
317
- } finally {
318
- // CRITICAL: Always release pooled objects back to the pool
319
- // This prevents memory leaks and ensures consistent performance
320
- if (originalParams && Object.keys(originalParams).length === 0) {
321
- this.releaseParamObject(originalParams);
322
- }
323
- if (
324
- httpReq.params &&
325
- httpReq.params !== originalParams &&
326
- Object.keys(httpReq.params).length === 0
327
- ) {
328
- this.releaseParamObject(httpReq.params);
329
- }
330
- }
331
-
332
- // Additional cleanup on response completion to ensure objects are returned to pool
333
- res.once('finish', () => {
334
- if (originalParams && Object.keys(originalParams).length === 0) {
335
- this.releaseParamObject(originalParams);
336
- }
337
- if (
338
- httpReq.params &&
339
- httpReq.params !== originalParams &&
340
- Object.keys(httpReq.params).length === 0
341
- ) {
342
- this.releaseParamObject(httpReq.params);
343
- }
344
- });
345
- }
346
-
347
- // Use shared object pool for parameter objects
348
- private acquireParamObject(): Record<string, string> {
349
- return this.poolManager.acquireParams();
350
- }
351
-
352
- private releaseParamObject(params: Record<string, string>): void {
353
- this.poolManager.releaseParams(params);
354
- }
355
-
356
- // Force cleanup of all pooled objects
357
- private forceCleanupPools(): void {
358
- // Use shared pool manager cleanup
359
- this.poolManager.clearAll();
360
-
361
- // Force garbage collection if available
362
- if (globalThis?.gc) {
363
- globalThis.gc();
364
- }
365
- }
366
-
367
- private acquireBuffer(size: number): Buffer {
368
- return this.poolManager.acquireBuffer(size);
369
- }
370
-
371
- private releaseBuffer(buffer: Buffer): void {
372
- this.poolManager.releaseBuffer(buffer);
373
- }
374
-
375
- private streamLargeResponse(res: any, data: any): void {
376
- res.setHeader('Content-Type', 'application/json; charset=utf-8');
377
- res.setHeader('Transfer-Encoding', 'chunked');
378
-
379
- // Stream the response in chunks
380
- const jsonString = JSON.stringify(data);
381
- const chunkSize = 8192; // 8KB chunks
382
-
383
- for (let i = 0; i < jsonString.length; i += chunkSize) {
384
- const chunk = jsonString.substring(i, i + chunkSize);
385
- res.write(chunk);
386
- }
387
- res.end();
388
- }
389
-
390
- private normalizePath(path: string): string {
391
- // Check cache first
392
- if (this.pathNormalizationCache.has(path)) {
393
- return this.pathNormalizationCache.get(path)!;
394
- }
395
-
396
- // Normalization: remove trailing slash (except root), decode once
397
- let normalized = path;
398
- if (normalized.length > 1 && normalized.endsWith('/')) {
399
- normalized = normalized.slice(0, -1);
400
- }
401
-
402
- // Cache result (limit cache size)
403
- if (this.pathNormalizationCache.size < 200) {
404
- this.pathNormalizationCache.set(path, normalized);
405
- }
406
-
407
- return normalized;
408
- }
409
-
410
- private enhanceRequest(req: IncomingMessage): HttpRequest {
411
- const httpReq = req as HttpRequest;
412
- httpReq.params = this.acquireParamObject();
413
- httpReq.query = {};
414
- httpReq.body = null;
415
- httpReq.path = '';
416
- httpReq.ip = req.socket.remoteAddress || '';
417
- // Request ID generation using pool manager (if enabled)
418
- httpReq.requestId = this.requestTrackingEnabled ? this.poolManager.generateRequestId() : '';
419
- httpReq.headers = req.headers as Record<string, string>;
420
-
421
- // Parse cookies
422
- httpReq.cookies = this.parseCookies(req.headers.cookie || '');
423
-
424
- return httpReq;
425
- }
426
-
427
- private parseCookies(cookieHeader: string): Record<string, string> {
428
- const cookies: Record<string, string> = {};
429
- if (!cookieHeader) return cookies;
430
-
431
- cookieHeader.split(';').forEach(cookie => {
432
- const [name, value] = cookie.trim().split('=');
433
- if (name && value) {
434
- cookies[name] = decodeURIComponent(value);
435
- }
436
- });
437
-
438
- return cookies;
439
- }
440
-
441
- private enhanceResponse(res: ServerResponse, req: HttpRequest): HttpResponse {
442
- const httpRes = res as HttpResponse;
443
-
444
- // Store request reference for access to headers (needed for compression, logging, etc.)
445
- (httpRes as any).req = req;
446
-
447
- // BULLETPROOF status method - always works
448
- httpRes.status = (code: number) => {
449
- httpRes.statusCode = code;
450
- return httpRes;
451
- };
452
-
453
- httpRes.json = async (data: any) => {
454
- if (httpRes.headersSent) return;
455
-
456
- // JSON serialization with zero-copy buffers
457
- let jsonString: string;
458
-
459
- // Enhanced JSON optimization for common API patterns
460
- if (data && typeof data === 'object' && 'success' in data) {
461
- if ('data' in data && 'error' in data && !('total' in data)) {
462
- // {success, data, error} pattern
463
- jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)},"error":${JSON.stringify(data.error)}}`;
464
- } else if ('data' in data && 'total' in data && !('error' in data)) {
465
- // {success, data, total} pattern
466
- jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)},"total":${data.total}}`;
467
- } else if ('data' in data && !('error' in data) && !('total' in data)) {
468
- // {success, data} pattern
469
- jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)}}`;
470
- } else if ('error' in data && !('data' in data) && !('total' in data)) {
471
- // {success, error} pattern
472
- jsonString = `{"success":${data.success},"error":${JSON.stringify(data.error)}}`;
473
- } else {
474
- // Complex object - use standard JSON.stringify
475
- jsonString = JSON.stringify(data);
476
- }
477
- } else {
478
- jsonString = JSON.stringify(data);
479
- }
480
-
481
- // Use buffer pool for zero-allocation responses
482
- const estimatedSize = jsonString.length;
483
- if (estimatedSize > 32768) {
484
- // Large response - stream it
485
- return this.streamLargeResponse(httpRes, data);
486
- }
487
-
488
- const buffer = MoroHttpServer.getOptimalBuffer(estimatedSize);
489
- const actualLength = buffer.write(jsonString, 0, 'utf8');
490
-
491
- // Slice to actual size to avoid sending extra bytes
492
- const finalBuffer =
493
- actualLength === buffer.length ? buffer : buffer.subarray(0, actualLength);
494
-
495
- // Optimized header setting - set multiple headers at once when possible
496
- const headers: Record<string, string | number> = {
497
- 'Content-Type': 'application/json; charset=utf-8',
498
- };
499
-
500
- // Compression with buffer pool
501
- if (this.compressionEnabled && finalBuffer.length > this.compressionThreshold) {
502
- const acceptEncoding = httpRes.req.headers['accept-encoding'] || '';
503
-
504
- if (acceptEncoding.includes('gzip')) {
505
- const compressed = await gzip(finalBuffer);
506
- headers['Content-Encoding'] = 'gzip';
507
- headers['Content-Length'] = compressed.length;
508
-
509
- // Set all headers at once
510
- Object.entries(headers).forEach(([key, value]) => {
511
- httpRes.setHeader(key, value);
512
- });
513
-
514
- httpRes.end(compressed);
515
- // Return buffer to pool after response
516
- process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
517
- return;
518
- } else if (acceptEncoding.includes('deflate')) {
519
- const compressed = await deflate(finalBuffer);
520
- headers['Content-Encoding'] = 'deflate';
521
- headers['Content-Length'] = compressed.length;
522
-
523
- Object.entries(headers).forEach(([key, value]) => {
524
- httpRes.setHeader(key, value);
525
- });
526
-
527
- httpRes.end(compressed);
528
- // Return buffer to pool after response
529
- process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
530
- return;
531
- }
532
- }
533
-
534
- headers['Content-Length'] = finalBuffer.length;
535
-
536
- // Set all headers at once for better performance
537
- Object.entries(headers).forEach(([key, value]) => {
538
- httpRes.setHeader(key, value);
539
- });
540
-
541
- httpRes.end(finalBuffer);
542
-
543
- // Return buffer to pool after response (zero-copy achievement!)
544
- process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
545
- };
546
-
547
- httpRes.send = (data: string | Buffer) => {
548
- if (httpRes.headersSent) return;
549
-
550
- // Auto-detect content type if not already set
551
- if (!httpRes.getHeader('Content-Type')) {
552
- if (typeof data === 'string') {
553
- // Check if it's JSON
554
- try {
555
- JSON.parse(data);
556
- httpRes.setHeader('Content-Type', 'application/json; charset=utf-8');
557
- } catch {
558
- // Default to plain text
559
- httpRes.setHeader('Content-Type', 'text/plain; charset=utf-8');
560
- }
561
- } else {
562
- // Buffer data - default to octet-stream
563
- httpRes.setHeader('Content-Type', 'application/octet-stream');
564
- }
565
- }
566
-
567
- httpRes.end(data);
568
- };
569
-
570
- httpRes.cookie = (name: string, value: string, options: any = {}) => {
571
- if (httpRes.headersSent) {
572
- const isCritical =
573
- options.critical ||
574
- name.includes('session') ||
575
- name.includes('auth') ||
576
- name.includes('csrf');
577
- const message = `Cookie '${name}' could not be set - headers already sent`;
578
-
579
- if (isCritical || options.throwOnLateSet) {
580
- throw new Error(`${message}. This may cause authentication or security issues.`);
581
- } else {
582
- this.logger.warn(message, 'CookieWarning', {
583
- cookieName: name,
584
- critical: isCritical,
585
- stackTrace: new Error().stack,
586
- });
587
- }
588
- return httpRes;
589
- }
590
-
591
- const cookieValue = encodeURIComponent(value);
592
- let cookieString = `${name}=${cookieValue}`;
593
-
594
- if (options.maxAge) cookieString += `; Max-Age=${options.maxAge}`;
595
- if (options.expires) cookieString += `; Expires=${options.expires.toUTCString()}`;
596
- if (options.httpOnly) cookieString += '; HttpOnly';
597
- if (options.secure) cookieString += '; Secure';
598
- if (options.sameSite) cookieString += `; SameSite=${options.sameSite}`;
599
- if (options.domain) cookieString += `; Domain=${options.domain}`;
600
- if (options.path) cookieString += `; Path=${options.path}`;
601
-
602
- const existingCookies = httpRes.getHeader('Set-Cookie') || [];
603
- const cookies = Array.isArray(existingCookies)
604
- ? [...existingCookies]
605
- : [existingCookies as string];
606
- cookies.push(cookieString);
607
- httpRes.setHeader('Set-Cookie', cookies);
608
-
609
- return httpRes;
610
- };
611
-
612
- httpRes.clearCookie = (name: string, options: any = {}) => {
613
- const clearOptions = { ...options, expires: new Date(0), maxAge: 0 };
614
- return httpRes.cookie(name, '', clearOptions);
615
- };
616
-
617
- httpRes.redirect = (url: string, status: number = 302) => {
618
- if (httpRes.headersSent) return;
619
- httpRes.statusCode = status;
620
- httpRes.setHeader('Location', url);
621
- httpRes.end();
622
- };
623
-
624
- httpRes.sendFile = async (filePath: string) => {
625
- if (httpRes.headersSent) return;
626
-
627
- try {
628
- const fs = await import('fs/promises');
629
- const path = await import('path');
630
- const extension = path.extname(filePath);
631
- const mime = await this.getMimeType(extension);
632
-
633
- const stats = await fs.stat(filePath);
634
- const data = await fs.readFile(filePath);
635
-
636
- // Add charset for text-based files
637
- const contentType = this.addCharsetIfNeeded(mime);
638
- httpRes.setHeader('Content-Type', contentType);
639
- httpRes.setHeader('Content-Length', stats.size);
640
-
641
- // Add security headers for file downloads
642
- httpRes.setHeader('X-Content-Type-Options', 'nosniff');
643
-
644
- // Add caching headers
645
- httpRes.setHeader('Last-Modified', stats.mtime.toUTCString());
646
- httpRes.setHeader('Cache-Control', 'public, max-age=31536000'); // 1 year for static files
647
-
648
- httpRes.end(data);
649
- } catch (error) {
650
- httpRes.status(404).json({ success: false, error: 'File not found' });
651
- }
652
- };
653
-
654
- // Header management utilities
655
- httpRes.hasHeader = (name: string): boolean => {
656
- return httpRes.getHeader(name) !== undefined;
657
- };
658
-
659
- // Note: removeHeader is inherited from ServerResponse, we don't override it
660
-
661
- httpRes.setBulkHeaders = (headers: Record<string, string | number>) => {
662
- if (httpRes.headersSent) {
663
- this.logger.warn('Cannot set headers - headers already sent', 'HeaderWarning', {
664
- attemptedHeaders: Object.keys(headers),
665
- });
666
- return httpRes;
667
- }
668
-
669
- Object.entries(headers).forEach(([key, value]) => {
670
- httpRes.setHeader(key, value);
671
- });
672
- return httpRes;
673
- };
674
-
675
- httpRes.appendHeader = (name: string, value: string | string[]) => {
676
- if (httpRes.headersSent) {
677
- this.logger.warn(
678
- `Cannot append to header '${name}' - headers already sent`,
679
- 'HeaderWarning'
680
- );
681
- return httpRes;
682
- }
683
-
684
- const existing = httpRes.getHeader(name);
685
- if (existing) {
686
- const values = Array.isArray(existing) ? existing : [existing.toString()];
687
- const newValues = Array.isArray(value) ? value : [value];
688
- httpRes.setHeader(name, [...values, ...newValues]);
689
- } else {
690
- httpRes.setHeader(name, value);
691
- }
692
- return httpRes;
693
- };
694
-
695
- // Response state utilities
696
- httpRes.canSetHeaders = (): boolean => {
697
- return !httpRes.headersSent;
698
- };
699
-
700
- httpRes.getResponseState = () => {
701
- return {
702
- headersSent: httpRes.headersSent,
703
- statusCode: httpRes.statusCode,
704
- headers: httpRes.getHeaders ? httpRes.getHeaders() : {},
705
- finished: httpRes.finished || false,
706
- writable: httpRes.writable,
707
- };
708
- };
709
-
710
- return httpRes;
711
- }
712
-
713
- private async getMimeType(ext: string): Promise<string> {
714
- const mimeTypes: Record<string, string> = {
715
- '.html': 'text/html',
716
- '.css': 'text/css',
717
- '.js': 'application/javascript',
718
- '.json': 'application/json',
719
- '.png': 'image/png',
720
- '.jpg': 'image/jpeg',
721
- '.jpeg': 'image/jpeg',
722
- '.gif': 'image/gif',
723
- '.svg': 'image/svg+xml',
724
- '.ico': 'image/x-icon',
725
- '.pdf': 'application/pdf',
726
- '.txt': 'text/plain',
727
- '.xml': 'application/xml',
728
- };
729
-
730
- return mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
731
- }
732
-
733
- private addCharsetIfNeeded(mimeType: string): string {
734
- // Add charset for text-based content types
735
- const textTypes = [
736
- 'text/',
737
- 'application/json',
738
- 'application/javascript',
739
- 'application/xml',
740
- 'image/svg+xml',
741
- ];
742
-
743
- const needsCharset = textTypes.some(type => mimeType.startsWith(type));
744
-
745
- if (needsCharset && !mimeType.includes('charset')) {
746
- return `${mimeType}; charset=utf-8`;
747
- }
748
-
749
- return mimeType;
750
- }
751
-
752
- private async parseBody(req: IncomingMessage): Promise<any> {
753
- return new Promise((resolve, reject) => {
754
- const chunks: Buffer[] = [];
755
- let totalLength = 0;
756
- const maxSize = 10 * 1024 * 1024; // 10MB limit
757
-
758
- req.on('data', (chunk: Buffer) => {
759
- totalLength += chunk.length;
760
- if (totalLength > maxSize) {
761
- reject(new Error('Request body too large'));
762
- return;
763
- }
764
- chunks.push(chunk);
765
- });
766
-
767
- req.on('end', () => {
768
- try {
769
- const body = Buffer.concat(chunks);
770
- const contentType = req.headers['content-type'] || '';
771
-
772
- if (contentType.includes('application/json')) {
773
- resolve(JSON.parse(body.toString()));
774
- } else if (contentType.includes('application/x-www-form-urlencoded')) {
775
- resolve(this.parseUrlEncoded(body.toString()));
776
- } else if (contentType.includes('multipart/form-data')) {
777
- resolve(this.parseMultipart(body, contentType));
778
- } else {
779
- resolve(body.toString());
780
- }
781
- } catch (error) {
782
- reject(error);
783
- }
784
- });
785
-
786
- req.on('error', reject);
787
- });
788
- }
789
-
790
- private parseMultipart(
791
- buffer: Buffer,
792
- contentType: string
793
- ): { fields: Record<string, string>; files: Record<string, any> } {
794
- const boundary = contentType.split('boundary=')[1];
795
- if (!boundary) {
796
- throw new Error('Invalid multipart boundary');
797
- }
798
-
799
- const parts = buffer.toString('binary').split('--' + boundary);
800
- const fields: Record<string, string> = {};
801
- const files: Record<string, any> = {};
802
-
803
- for (let i = 1; i < parts.length - 1; i++) {
804
- const part = parts[i];
805
- const [headers, content] = part.split('\r\n\r\n');
806
-
807
- if (!headers || content === undefined) continue;
808
-
809
- const nameMatch = headers.match(/name="([^"]+)"/);
810
- const filenameMatch = headers.match(/filename="([^"]+)"/);
811
- const contentTypeMatch = headers.match(/Content-Type: ([^\r\n]+)/);
812
-
813
- if (nameMatch) {
814
- const name = nameMatch[1];
815
-
816
- if (filenameMatch) {
817
- // This is a file
818
- const filename = filenameMatch[1];
819
- const mimeType = contentTypeMatch ? contentTypeMatch[1] : 'application/octet-stream';
820
- const fileContent = content.substring(0, content.length - 2); // Remove trailing \r\n
821
-
822
- files[name] = {
823
- filename,
824
- mimetype: mimeType,
825
- data: Buffer.from(fileContent, 'binary'),
826
- size: Buffer.byteLength(fileContent, 'binary'),
827
- };
828
- } else {
829
- // This is a regular field
830
- fields[name] = content.substring(0, content.length - 2); // Remove trailing \r\n
831
- }
832
- }
833
- }
834
-
835
- return { fields, files };
836
- }
837
-
838
- private parseUrlEncoded(body: string): Record<string, string> {
839
- const params = new URLSearchParams(body);
840
- const result: Record<string, string> = {};
841
- for (const [key, value] of params) {
842
- result[key] = value;
843
- }
844
- return result;
845
- }
846
-
847
- // Legacy method for backward compatibility
848
- private parseQueryString(queryString: string): Record<string, string> {
849
- return this.parseQueryStringPooled(queryString);
850
- }
851
-
852
- // Optimized query string parser with object pooling
853
- private parseQueryStringPooled(queryString: string): Record<string, string> {
854
- if (!queryString) return {};
855
-
856
- const result = this.poolManager.acquireQuery();
857
- const pairs = queryString.split('&');
858
-
859
- for (let i = 0; i < pairs.length; i++) {
860
- const pair = pairs[i];
861
- const equalIndex = pair.indexOf('=');
862
- if (equalIndex === -1) {
863
- result[decodeURIComponent(pair)] = '';
864
- } else {
865
- const key = decodeURIComponent(pair.substring(0, equalIndex));
866
- const value = decodeURIComponent(pair.substring(equalIndex + 1));
867
- result[key] = value;
868
- }
869
- }
870
- return result;
871
- }
872
-
873
- // Advanced route optimization: cache + static routes + segment grouping
874
- private routeCache = new Map<string, RouteEntry | null>();
875
- private staticRoutes = new Map<string, RouteEntry>();
876
- private dynamicRoutes: RouteEntry[] = [];
877
- private routesBySegmentCount = new Map<number, RouteEntry[]>();
878
- private pathNormalizationCache = new Map<string, string>();
879
-
880
- // CPU cache-friendly optimizations
881
- private routeHitCount = new Map<string, number>(); // Track route popularity for cache optimization
882
- private static readonly HOT_ROUTE_THRESHOLD = 100; // Routes accessed 100+ times get hot path treatment
883
-
884
- private findRoute(method: string, path: string): RouteEntry | null {
885
- // Normalize path for consistent matching
886
- const normalizedPath = this.normalizePath(path);
887
- const cacheKey = `${method}:${normalizedPath}`;
888
-
889
- // Track route popularity for hot path optimization
890
- const hitCount = (this.routeHitCount.get(cacheKey) || 0) + 1;
891
- this.routeHitCount.set(cacheKey, hitCount);
892
-
893
- // Check cache first (hot path optimization)
894
- if (this.routeCache.has(cacheKey)) {
895
- const cachedRoute = this.routeCache.get(cacheKey)!;
896
-
897
- // Promote frequently accessed routes to front of cache (LRU-like)
898
- if (hitCount > MoroHttpServer.HOT_ROUTE_THRESHOLD && this.routeCache.size > 100) {
899
- this.routeCache.delete(cacheKey);
900
- this.routeCache.set(cacheKey, cachedRoute); // Move to end (most recent)
901
- }
902
-
903
- return cachedRoute;
904
- }
905
-
906
- // Phase 1: O(1) static route lookup
907
- const staticRoute = this.staticRoutes.get(cacheKey);
908
- if (staticRoute) {
909
- this.routeCache.set(cacheKey, staticRoute);
910
- return staticRoute;
911
- }
912
-
913
- // Phase 2: Optimized dynamic route matching by segment count
914
- let route: RouteEntry | null = null;
915
- if (this.dynamicRoutes.length > 0) {
916
- const segments = normalizedPath.split('/').filter(s => s.length > 0);
917
- const candidateRoutes = this.routesBySegmentCount.get(segments.length) || this.dynamicRoutes;
918
-
919
- // Only test routes with matching method and segment count
920
- for (const candidateRoute of candidateRoutes) {
921
- if (candidateRoute.method === method && candidateRoute.pattern.test(normalizedPath)) {
922
- route = candidateRoute;
923
- break;
924
- }
925
- }
926
- }
927
-
928
- // Cache result (limit cache size to prevent memory leaks)
929
- if (this.routeCache.size < 500) {
930
- this.routeCache.set(cacheKey, route);
931
- }
932
-
933
- return route;
934
- }
935
-
936
- // Optimized middleware execution with reduced Promise allocation
937
- private async executeMiddleware(
938
- middleware: Middleware[],
939
- req: HttpRequest,
940
- res: HttpResponse
941
- ): Promise<void> {
942
- for (let i = 0; i < middleware.length; i++) {
943
- // Short-circuit if response already sent
944
- if (res.headersSent) return;
945
-
946
- const mw = middleware[i];
947
-
948
- await new Promise<void>((resolve, reject) => {
949
- let resolved = false;
950
-
951
- // Reuse next function to reduce allocations
952
- const next = () => {
953
- if (resolved) return;
954
- resolved = true;
955
- resolve();
956
- };
957
-
958
- try {
959
- const result = mw(req, res, next);
960
-
961
- // Handle async middleware
962
- if (result && typeof (result as any).then === 'function') {
963
- (result as Promise<void>)
964
- .then(() => {
965
- if (!resolved) next();
966
- })
967
- .catch(reject);
968
- } else if (!resolved) {
969
- // Sync middleware that didn't call next
970
- next();
971
- }
972
- } catch (error) {
973
- if (!resolved) {
974
- resolved = true;
975
- reject(error);
976
- }
977
- }
978
- });
979
- }
980
- }
981
-
982
- listen(port: number, callback?: () => void): void;
983
- listen(port: number, host: string, callback?: () => void): void;
984
- listen(port: number, host?: string | (() => void), callback?: () => void): void {
985
- // Handle overloaded parameters (port, callback) or (port, host, callback)
986
- if (typeof host === 'function') {
987
- callback = host;
988
- host = undefined;
989
- }
990
-
991
- if (host) {
992
- this.server.listen(port, host, callback);
993
- } else {
994
- this.server.listen(port, callback);
995
- }
996
- }
997
-
998
- close(): Promise<void> {
999
- return new Promise(resolve => {
1000
- this.server.close(() => resolve());
1001
- });
1002
- }
1003
-
1004
- // Public method to force cleanup
1005
- forceCleanup(): void {
1006
- this.forceCleanupPools();
1007
- }
1008
-
1009
- getServer(): Server {
1010
- return this.server;
1011
- }
1012
-
1013
- // Performance statistics
1014
- getPerformanceStats() {
1015
- const poolStats = this.poolManager.getStats();
1016
- return {
1017
- paramObjectPoolSize: poolStats.paramPool.poolSize,
1018
- queryObjectPoolSize: poolStats.queryPool.poolSize,
1019
- headerObjectPoolSize: poolStats.headerPool.poolSize,
1020
- poolManager: poolStats,
1021
- };
1022
- }
1023
- }
1024
-
1025
- // Built-in middleware
1026
- export const middleware = {
1027
- cors: (options: { origin?: string; credentials?: boolean } = {}): Middleware => {
1028
- return (req, res, next) => {
1029
- res.setHeader('Access-Control-Allow-Origin', options.origin || '*');
1030
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
1031
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
1032
-
1033
- if (options.credentials) {
1034
- res.setHeader('Access-Control-Allow-Credentials', 'true');
1035
- }
1036
-
1037
- if (req.method === 'OPTIONS') {
1038
- res.status(200).send('');
1039
- return;
1040
- }
1041
-
1042
- next();
1043
- };
1044
- },
1045
-
1046
- helmet: (): Middleware => {
1047
- return (req, res, next) => {
1048
- res.setHeader('X-Content-Type-Options', 'nosniff');
1049
- res.setHeader('X-Frame-Options', 'DENY');
1050
- res.setHeader('X-XSS-Protection', '1; mode=block');
1051
- res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
1052
- res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
1053
- res.setHeader('Content-Security-Policy', "default-src 'self'");
1054
- next();
1055
- };
1056
- },
1057
-
1058
- compression: (options: { threshold?: number; level?: number } = {}): Middleware => {
1059
- const threshold = options.threshold || 1024;
1060
- const level = options.level || 6;
1061
-
1062
- return (req, res, next) => {
1063
- const acceptEncoding = req.headers['accept-encoding'] || '';
1064
-
1065
- // Override res.json to compress responses
1066
- const originalJson = res.json;
1067
- const originalSend = res.send;
1068
-
1069
- const compressResponse = (data: any, isJson = false) => {
1070
- const content = isJson ? JSON.stringify(data) : data;
1071
- const buffer = Buffer.from(content);
1072
-
1073
- if (buffer.length < threshold) {
1074
- return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
1075
- }
1076
-
1077
- if (acceptEncoding.includes('gzip')) {
1078
- zlib.gzip(buffer, { level }, (err: any, compressed: Buffer) => {
1079
- if (err) {
1080
- return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
1081
- }
1082
- if (!res.headersSent) {
1083
- res.setHeader('Content-Encoding', 'gzip');
1084
- res.setHeader('Content-Length', compressed.length);
1085
- }
1086
- res.end(compressed);
1087
- });
1088
- } else if (acceptEncoding.includes('deflate')) {
1089
- zlib.deflate(buffer, { level }, (err: any, compressed: Buffer) => {
1090
- if (err) {
1091
- return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
1092
- }
1093
- if (!res.headersSent) {
1094
- res.setHeader('Content-Encoding', 'deflate');
1095
- res.setHeader('Content-Length', compressed.length);
1096
- }
1097
- res.end(compressed);
1098
- });
1099
- } else {
1100
- return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
1101
- }
1102
- };
1103
-
1104
- res.json = function (data: any) {
1105
- // Ensure charset is set for Safari compatibility
1106
- this.setHeader('Content-Type', 'application/json; charset=utf-8');
1107
- compressResponse(data, true);
1108
- return this;
1109
- };
1110
-
1111
- res.send = function (data: any) {
1112
- compressResponse(data, false);
1113
- return this;
1114
- };
1115
-
1116
- next();
1117
- };
1118
- },
1119
-
1120
- requestLogger: (): Middleware => {
1121
- return (req, res, next) => {
1122
- const start = Date.now();
1123
- res.on('finish', () => {
1124
- const duration = Date.now() - start;
1125
- // Request completed - logged by framework
1126
- });
1127
-
1128
- next();
1129
- };
1130
- },
1131
-
1132
- bodySize: (options: { limit?: string } = {}): Middleware => {
1133
- const limit = options.limit || '10mb';
1134
- const limitBytes = parseSize(limit);
1135
-
1136
- return (req, res, next) => {
1137
- const contentLength = parseInt(req.headers['content-length'] || '0');
1138
-
1139
- if (contentLength > limitBytes) {
1140
- res.status(413).json({
1141
- success: false,
1142
- error: 'Request entity too large',
1143
- limit: limit,
1144
- });
1145
- return;
1146
- }
1147
-
1148
- next();
1149
- };
1150
- },
1151
-
1152
- static: (options: {
1153
- root: string;
1154
- maxAge?: number;
1155
- index?: string[];
1156
- dotfiles?: 'allow' | 'deny' | 'ignore';
1157
- etag?: boolean;
1158
- }): Middleware => {
1159
- return async (req, res, next) => {
1160
- // Only handle GET and HEAD requests
1161
- if (req.method !== 'GET' && req.method !== 'HEAD') {
1162
- next();
1163
- return;
1164
- }
1165
-
1166
- try {
1167
- const fs = await import('fs/promises');
1168
- const path = await import('path');
1169
- const crypto = await import('crypto');
1170
-
1171
- let filePath = path.join(options.root, req.path);
1172
-
1173
- // Security: prevent directory traversal
1174
- if (!filePath.startsWith(path.resolve(options.root))) {
1175
- res.status(403).json({ success: false, error: 'Forbidden' });
1176
- return;
1177
- }
1178
-
1179
- // Handle dotfiles
1180
- const basename = path.basename(filePath);
1181
- if (basename.startsWith('.')) {
1182
- if (options.dotfiles === 'deny') {
1183
- res.status(403).json({ success: false, error: 'Forbidden' });
1184
- return;
1185
- } else if (options.dotfiles === 'ignore') {
1186
- next();
1187
- return;
1188
- }
1189
- }
1190
-
1191
- let stats;
1192
- try {
1193
- stats = await fs.stat(filePath);
1194
- } catch (error) {
1195
- next(); // File not found, let other middleware handle
1196
- return;
1197
- }
1198
-
1199
- // Handle directories
1200
- if (stats.isDirectory()) {
1201
- const indexFiles = options.index || ['index.html', 'index.htm'];
1202
- let indexFound = false;
1203
-
1204
- for (const indexFile of indexFiles) {
1205
- const indexPath = path.join(filePath, indexFile);
1206
- try {
1207
- const indexStats = await fs.stat(indexPath);
1208
- if (indexStats.isFile()) {
1209
- filePath = indexPath;
1210
- stats = indexStats;
1211
- indexFound = true;
1212
- break;
1213
- }
1214
- } catch (error) {
1215
- // Continue to next index file
1216
- }
1217
- }
1218
-
1219
- if (!indexFound) {
1220
- next();
1221
- return;
1222
- }
1223
- }
1224
-
1225
- // Set headers with proper mime type and charset
1226
- const ext = path.extname(filePath);
1227
- const mimeTypes: Record<string, string> = {
1228
- '.html': 'text/html',
1229
- '.css': 'text/css',
1230
- '.js': 'application/javascript',
1231
- '.json': 'application/json',
1232
- '.png': 'image/png',
1233
- '.jpg': 'image/jpeg',
1234
- '.jpeg': 'image/jpeg',
1235
- '.gif': 'image/gif',
1236
- '.svg': 'image/svg+xml',
1237
- '.ico': 'image/x-icon',
1238
- '.pdf': 'application/pdf',
1239
- '.txt': 'text/plain',
1240
- '.xml': 'application/xml',
1241
- };
1242
-
1243
- const baseMimeType = mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
1244
-
1245
- // Add charset for text-based files
1246
- const textTypes = [
1247
- 'text/',
1248
- 'application/json',
1249
- 'application/javascript',
1250
- 'application/xml',
1251
- 'image/svg+xml',
1252
- ];
1253
- const needsCharset = textTypes.some(type => baseMimeType.startsWith(type));
1254
- const contentType = needsCharset ? `${baseMimeType}; charset=utf-8` : baseMimeType;
1255
-
1256
- res.setHeader('Content-Type', contentType);
1257
- res.setHeader('Content-Length', stats.size);
1258
-
1259
- // Cache headers
1260
- if (options.maxAge) {
1261
- res.setHeader('Cache-Control', `public, max-age=${options.maxAge}`);
1262
- }
1263
-
1264
- // ETag support
1265
- if (options.etag !== false) {
1266
- const etag = crypto
1267
- .createHash('md5')
1268
- .update(`${stats.mtime.getTime()}-${stats.size}`)
1269
- .digest('hex');
1270
- res.setHeader('ETag', `"${etag}"`);
1271
-
1272
- // Handle conditional requests
1273
- const ifNoneMatch = req.headers['if-none-match'];
1274
- if (ifNoneMatch === `"${etag}"`) {
1275
- res.statusCode = 304;
1276
- res.end();
1277
- return;
1278
- }
1279
- }
1280
-
1281
- // Handle HEAD requests
1282
- if (req.method === 'HEAD') {
1283
- res.end();
1284
- return;
1285
- }
1286
-
1287
- // Send file
1288
- const data = await fs.readFile(filePath);
1289
- res.end(data);
1290
- } catch (error) {
1291
- res.status(500).json({ success: false, error: 'Internal server error' });
1292
- }
1293
- };
1294
- },
1295
-
1296
- upload: (
1297
- options: {
1298
- dest?: string;
1299
- maxFileSize?: number;
1300
- maxFiles?: number;
1301
- allowedTypes?: string[];
1302
- } = {}
1303
- ): Middleware => {
1304
- return (req, res, next) => {
1305
- const contentType = req.headers['content-type'] || '';
1306
-
1307
- if (!contentType.includes('multipart/form-data')) {
1308
- next();
1309
- return;
1310
- }
1311
-
1312
- // File upload handling is now built into parseBody method
1313
- // This middleware can add additional validation
1314
- if (req.body && req.body.files) {
1315
- const files = req.body.files;
1316
- const maxFileSize = options.maxFileSize || 5 * 1024 * 1024; // 5MB default
1317
- const maxFiles = options.maxFiles || 10;
1318
- const allowedTypes = options.allowedTypes;
1319
-
1320
- // Validate file count
1321
- if (Object.keys(files).length > maxFiles) {
1322
- res.status(400).json({
1323
- success: false,
1324
- error: `Too many files. Maximum ${maxFiles} allowed.`,
1325
- });
1326
- return;
1327
- }
1328
-
1329
- // Validate each file
1330
- for (const [fieldName, file] of Object.entries(files)) {
1331
- const fileData = file as any;
1332
-
1333
- // Validate file size
1334
- if (fileData.size > maxFileSize) {
1335
- res.status(400).json({
1336
- success: false,
1337
- error: `File ${fileData.filename} is too large. Maximum ${maxFileSize} bytes allowed.`,
1338
- });
1339
- return;
1340
- }
1341
-
1342
- // Validate file type
1343
- if (allowedTypes && !allowedTypes.includes(fileData.mimetype)) {
1344
- res.status(400).json({
1345
- success: false,
1346
- error: `File type ${fileData.mimetype} not allowed.`,
1347
- });
1348
- return;
1349
- }
1350
- }
1351
-
1352
- // Store files in request for easy access
1353
- req.files = files;
1354
- }
1355
-
1356
- next();
1357
- };
1358
- },
1359
-
1360
- template: (options: {
1361
- views: string;
1362
- engine?: 'moro' | 'handlebars' | 'ejs';
1363
- cache?: boolean;
1364
- defaultLayout?: string;
1365
- }): Middleware => {
1366
- const templateCache = new Map<string, string>();
1367
-
1368
- return async (req, res, next) => {
1369
- // Add render method to response
1370
- res.render = async (template: string, data: any = {}) => {
1371
- try {
1372
- const fs = await import('fs/promises');
1373
- const path = await import('path');
1374
-
1375
- const templatePath = path.join(options.views, `${template}.html`);
1376
-
1377
- let templateContent: string;
1378
-
1379
- // Check cache first
1380
- if (options.cache && templateCache.has(templatePath)) {
1381
- templateContent = templateCache.get(templatePath)!;
1382
- } else {
1383
- templateContent = await fs.readFile(templatePath, 'utf-8');
1384
- if (options.cache) {
1385
- templateCache.set(templatePath, templateContent);
1386
- }
1387
- }
1388
-
1389
- // Simple template engine - replace {{variable}} with values
1390
- let rendered = templateContent;
1391
-
1392
- // Handle basic variable substitution
1393
- rendered = rendered.replace(/\{\{(\w+)\}\}/g, (match: string, key: string) => {
1394
- return data[key] !== undefined ? String(data[key]) : match;
1395
- });
1396
-
1397
- // Handle nested object properties like {{user.name}}
1398
- rendered = rendered.replace(/\{\{([\w.]+)\}\}/g, (match: string, key: string) => {
1399
- const value = key.split('.').reduce((obj: any, prop: string) => obj?.[prop], data);
1400
- return value !== undefined ? String(value) : match;
1401
- });
1402
-
1403
- // Handle loops: {{#each items}}{{name}}{{/each}}
1404
- rendered = rendered.replace(
1405
- /\{\{#each (\w+)\}\}(.*?)\{\{\/each\}\}/gs,
1406
- (match, arrayKey, template) => {
1407
- const array = data[arrayKey];
1408
- if (!Array.isArray(array)) return '';
1409
-
1410
- return array
1411
- .map(item => {
1412
- let itemTemplate = template;
1413
- // Replace variables in the loop template
1414
- itemTemplate = itemTemplate.replace(
1415
- /\{\{(\w+)\}\}/g,
1416
- (match: string, key: string) => {
1417
- return item[key] !== undefined ? String(item[key]) : match;
1418
- }
1419
- );
1420
- return itemTemplate;
1421
- })
1422
- .join('');
1423
- }
1424
- );
1425
-
1426
- // Handle conditionals: {{#if condition}}content{{/if}}
1427
- rendered = rendered.replace(
1428
- /\{\{#if (\w+)\}\}(.*?)\{\{\/if\}\}/gs,
1429
- (match, conditionKey, content) => {
1430
- const condition = data[conditionKey];
1431
- return condition ? content : '';
1432
- }
1433
- );
1434
-
1435
- // Handle layout
1436
- if (options.defaultLayout) {
1437
- const layoutPath = path.join(options.views, 'layouts', `${options.defaultLayout}.html`);
1438
- try {
1439
- let layoutContent: string;
1440
-
1441
- if (options.cache && templateCache.has(layoutPath)) {
1442
- layoutContent = templateCache.get(layoutPath)!;
1443
- } else {
1444
- layoutContent = await fs.readFile(layoutPath, 'utf-8');
1445
- if (options.cache) {
1446
- templateCache.set(layoutPath, layoutContent);
1447
- }
1448
- }
1449
-
1450
- rendered = layoutContent.replace(/\{\{body\}\}/, rendered);
1451
- } catch (error) {
1452
- // Layout not found, use template as-is
1453
- }
1454
- }
1455
-
1456
- res.setHeader('Content-Type', 'text/html');
1457
- res.end(rendered);
1458
- } catch (error) {
1459
- res.status(500).json({ success: false, error: 'Template rendering failed' });
1460
- }
1461
- };
1462
-
1463
- next();
1464
- };
1465
- },
1466
-
1467
- // HTTP/2 Server Push middleware
1468
- http2Push: (
1469
- options: {
1470
- resources?: Array<{ path: string; as: string; type?: string }>;
1471
- condition?: (req: any) => boolean;
1472
- } = {}
1473
- ): Middleware => {
1474
- return (req, res, next) => {
1475
- // Add HTTP/2 push capability to response
1476
- (res as any).push = (path: string, options: any = {}) => {
1477
- // Check if HTTP/2 is supported
1478
- if (req.httpVersion === '2.0' && (res as any).stream && (res as any).stream.pushAllowed) {
1479
- try {
1480
- const pushStream = (res as any).stream.pushStream({
1481
- ':method': 'GET',
1482
- ':path': path,
1483
- ...options.headers,
1484
- });
1485
-
1486
- if (pushStream) {
1487
- // Handle push stream
1488
- return pushStream;
1489
- }
1490
- } catch (error) {
1491
- // Push failed, continue normally
1492
- }
1493
- }
1494
- return null;
1495
- };
1496
-
1497
- // Auto-push configured resources
1498
- if (options.resources && (!options.condition || options.condition(req))) {
1499
- for (const resource of options.resources) {
1500
- (res as any).push?.(resource.path, {
1501
- headers: {
1502
- 'content-type': resource.type || 'text/plain',
1503
- },
1504
- });
1505
- }
1506
- }
1507
-
1508
- next();
1509
- };
1510
- },
1511
-
1512
- // Server-Sent Events middleware
1513
- sse: (
1514
- options: {
1515
- heartbeat?: number;
1516
- retry?: number;
1517
- cors?: boolean;
1518
- } = {}
1519
- ): Middleware => {
1520
- return (req, res, next) => {
1521
- // Only handle SSE requests
1522
- if (req.headers.accept?.includes('text/event-stream')) {
1523
- // Set SSE headers
1524
- if (!res.headersSent) {
1525
- res.writeHead(200, {
1526
- 'Content-Type': 'text/event-stream',
1527
- 'Cache-Control': 'no-cache',
1528
- Connection: 'keep-alive',
1529
- 'Access-Control-Allow-Origin': options.cors ? '*' : undefined,
1530
- 'Access-Control-Allow-Headers': options.cors ? 'Cache-Control' : undefined,
1531
- });
1532
- }
1533
-
1534
- // Add SSE methods to response
1535
- (res as any).sendEvent = (data: any, event?: string, id?: string) => {
1536
- if (id) res.write(`id: ${id}\n`);
1537
- if (event) res.write(`event: ${event}\n`);
1538
- res.write(`data: ${typeof data === 'string' ? data : JSON.stringify(data)}\n\n`);
1539
- };
1540
-
1541
- (res as any).sendComment = (comment: string) => {
1542
- res.write(`: ${comment}\n\n`);
1543
- };
1544
-
1545
- (res as any).sendRetry = (ms: number) => {
1546
- res.write(`retry: ${ms}\n\n`);
1547
- };
1548
-
1549
- // Set up heartbeat if configured
1550
- let heartbeatInterval: NodeJS.Timeout | null = null;
1551
- if (options.heartbeat) {
1552
- heartbeatInterval = setInterval(() => {
1553
- (res as any).sendComment('heartbeat');
1554
- }, options.heartbeat);
1555
- }
1556
-
1557
- // Set retry if configured
1558
- if (options.retry) {
1559
- (res as any).sendRetry(options.retry);
1560
- }
1561
-
1562
- // Clean up on close
1563
- req.on('close', () => {
1564
- if (heartbeatInterval) {
1565
- clearInterval(heartbeatInterval);
1566
- }
1567
- });
1568
-
1569
- // Don't call next() - this middleware handles the response
1570
- return;
1571
- }
1572
-
1573
- next();
1574
- };
1575
- },
1576
-
1577
- // Range request middleware for streaming
1578
- range: (
1579
- options: {
1580
- acceptRanges?: string;
1581
- maxRanges?: number;
1582
- } = {}
1583
- ): Middleware => {
1584
- return async (req, res, next) => {
1585
- // Add range support to response
1586
- (res as any).sendRange = async (filePath: string, stats?: any) => {
1587
- try {
1588
- const fs = await import('fs/promises');
1589
- const path = await import('path');
1590
-
1591
- if (!stats) {
1592
- stats = await fs.stat(filePath);
1593
- }
1594
-
1595
- const fileSize = stats.size;
1596
- const range = req.headers.range;
1597
-
1598
- // Set Accept-Ranges header
1599
- res.setHeader('Accept-Ranges', options.acceptRanges || 'bytes');
1600
-
1601
- if (!range) {
1602
- // No range requested, send entire file
1603
- res.setHeader('Content-Length', fileSize);
1604
- const data = await fs.readFile(filePath);
1605
- res.end(data);
1606
- return;
1607
- }
1608
-
1609
- // Parse range header
1610
- const ranges = range
1611
- .replace(/bytes=/, '')
1612
- .split(',')
1613
- .map(r => {
1614
- const [start, end] = r.split('-');
1615
- return {
1616
- start: start ? parseInt(start) : 0,
1617
- end: end ? parseInt(end) : fileSize - 1,
1618
- };
1619
- });
1620
-
1621
- // Validate ranges
1622
- if (options.maxRanges && ranges.length > options.maxRanges) {
1623
- res.status(416).json({ success: false, error: 'Too many ranges' });
1624
- return;
1625
- }
1626
-
1627
- if (ranges.length === 1) {
1628
- // Single range
1629
- const { start, end } = ranges[0];
1630
- const chunkSize = end - start + 1;
1631
-
1632
- if (start >= fileSize || end >= fileSize) {
1633
- res.status(416);
1634
- res.setHeader('Content-Range', `bytes */${fileSize}`);
1635
- res.json({ success: false, error: 'Range not satisfiable' });
1636
- return;
1637
- }
1638
-
1639
- res.status(206);
1640
- res.setHeader('Content-Range', `bytes ${start}-${end}/${fileSize}`);
1641
- res.setHeader('Content-Length', chunkSize);
1642
-
1643
- // Stream the range
1644
- const stream = createReadStream(filePath, {
1645
- start,
1646
- end,
1647
- });
1648
- stream.pipe(res);
1649
- } else {
1650
- // Multiple ranges - multipart response
1651
- const boundary = 'MULTIPART_BYTERANGES';
1652
- res.status(206);
1653
- res.setHeader('Content-Type', `multipart/byteranges; boundary=${boundary}`);
1654
-
1655
- for (const { start, end } of ranges) {
1656
- if (start >= fileSize || end >= fileSize) continue;
1657
-
1658
- const chunkSize = end - start + 1;
1659
- res.write(`\r\n--${boundary}\r\n`);
1660
- res.write(`Content-Range: bytes ${start}-${end}/${fileSize}\r\n\r\n`);
1661
-
1662
- const stream = createReadStream(filePath, {
1663
- start,
1664
- end,
1665
- });
1666
- await new Promise<void>(resolve => {
1667
- stream.on('end', () => resolve());
1668
- stream.pipe(res, { end: false });
1669
- });
1670
- }
1671
- res.write(`\r\n--${boundary}--\r\n`);
1672
- res.end();
1673
- }
1674
- } catch (error) {
1675
- res.status(500).json({ success: false, error: 'Range request failed' });
1676
- }
1677
- };
1678
-
1679
- next();
1680
- };
1681
- },
1682
-
1683
- // CSRF Protection middleware
1684
- csrf: (
1685
- options: {
1686
- secret?: string;
1687
- tokenLength?: number;
1688
- cookieName?: string;
1689
- headerName?: string;
1690
- ignoreMethods?: string[];
1691
- sameSite?: boolean;
1692
- } = {}
1693
- ): Middleware => {
1694
- const secret = options.secret || 'moro-csrf-secret';
1695
- const tokenLength = options.tokenLength || 32;
1696
- const cookieName = options.cookieName || '_csrf';
1697
- const headerName = options.headerName || 'x-csrf-token';
1698
- const ignoreMethods = options.ignoreMethods || ['GET', 'HEAD', 'OPTIONS'];
1699
-
1700
- const generateToken = () => {
1701
- return crypto.randomBytes(tokenLength).toString('hex');
1702
- };
1703
-
1704
- const verifyToken = (token: string, sessionToken: string) => {
1705
- return token && sessionToken && token === sessionToken;
1706
- };
1707
-
1708
- return (req, res, next) => {
1709
- // Add CSRF token generation method
1710
- (req as any).csrfToken = () => {
1711
- if (!(req as any)._csrfToken) {
1712
- (req as any)._csrfToken = generateToken();
1713
- // Set token in cookie
1714
- res.cookie(cookieName, (req as any)._csrfToken, {
1715
- httpOnly: true,
1716
- sameSite: options.sameSite !== false ? 'strict' : undefined,
1717
- secure: req.headers['x-forwarded-proto'] === 'https' || (req.socket as any).encrypted,
1718
- });
1719
- }
1720
- return (req as any)._csrfToken;
1721
- };
1722
-
1723
- // Skip verification for safe methods
1724
- if (ignoreMethods.includes(req.method!)) {
1725
- next();
1726
- return;
1727
- }
1728
-
1729
- // Get token from header or body
1730
- const token =
1731
- req.headers[headerName] || (req.body && req.body._csrf) || (req.query && req.query._csrf);
1732
-
1733
- // Get session token from cookie
1734
- const sessionToken = req.cookies?.[cookieName];
1735
-
1736
- if (!verifyToken(token as string, sessionToken || '')) {
1737
- res.status(403).json({
1738
- success: false,
1739
- error: 'Invalid CSRF token',
1740
- code: 'CSRF_TOKEN_MISMATCH',
1741
- });
1742
- return;
1743
- }
1744
-
1745
- next();
1746
- };
1747
- },
1748
-
1749
- // Content Security Policy middleware
1750
- csp: (
1751
- options: {
1752
- directives?: {
1753
- defaultSrc?: string[];
1754
- scriptSrc?: string[];
1755
- styleSrc?: string[];
1756
- imgSrc?: string[];
1757
- connectSrc?: string[];
1758
- fontSrc?: string[];
1759
- objectSrc?: string[];
1760
- mediaSrc?: string[];
1761
- frameSrc?: string[];
1762
- childSrc?: string[];
1763
- workerSrc?: string[];
1764
- formAction?: string[];
1765
- upgradeInsecureRequests?: boolean;
1766
- blockAllMixedContent?: boolean;
1767
- };
1768
- reportOnly?: boolean;
1769
- reportUri?: string;
1770
- nonce?: boolean;
1771
- } = {}
1772
- ): Middleware => {
1773
- return (req, res, next) => {
1774
- const directives = options.directives || {
1775
- defaultSrc: ["'self'"],
1776
- scriptSrc: ["'self'"],
1777
- styleSrc: ["'self'", "'unsafe-inline'"],
1778
- imgSrc: ["'self'", 'data:', 'https:'],
1779
- connectSrc: ["'self'"],
1780
- fontSrc: ["'self'"],
1781
- objectSrc: ["'none'"],
1782
- mediaSrc: ["'self'"],
1783
- frameSrc: ["'none'"],
1784
- };
1785
-
1786
- // Generate nonce if requested
1787
- let nonce: string | undefined;
1788
- if (options.nonce) {
1789
- nonce = crypto.randomBytes(16).toString('base64');
1790
- (req as any).cspNonce = nonce;
1791
- }
1792
-
1793
- // Build CSP header value
1794
- const cspParts: string[] = [];
1795
-
1796
- for (const [directive, sources] of Object.entries(directives)) {
1797
- if (directive === 'upgradeInsecureRequests' && sources === true) {
1798
- cspParts.push('upgrade-insecure-requests');
1799
- } else if (directive === 'blockAllMixedContent' && sources === true) {
1800
- cspParts.push('block-all-mixed-content');
1801
- } else if (Array.isArray(sources)) {
1802
- let sourceList = sources.join(' ');
1803
-
1804
- // Add nonce to script-src and style-src if enabled
1805
- if (nonce && (directive === 'scriptSrc' || directive === 'styleSrc')) {
1806
- sourceList += ` 'nonce-${nonce}'`;
1807
- }
1808
-
1809
- // Convert camelCase to kebab-case
1810
- const kebabDirective = directive.replace(/([A-Z])/g, '-$1').toLowerCase();
1811
- cspParts.push(`${kebabDirective} ${sourceList}`);
1812
- }
1813
- }
1814
-
1815
- // Add report-uri if specified
1816
- if (options.reportUri) {
1817
- cspParts.push(`report-uri ${options.reportUri}`);
1818
- }
1819
-
1820
- const cspValue = cspParts.join('; ');
1821
- const headerName = options.reportOnly
1822
- ? 'Content-Security-Policy-Report-Only'
1823
- : 'Content-Security-Policy';
1824
-
1825
- res.setHeader(headerName, cspValue);
1826
-
1827
- next();
1828
- };
1829
- },
1830
- };
1831
-
1832
- function parseSize(size: string): number {
1833
- const units: { [key: string]: number } = {
1834
- b: 1,
1835
- kb: 1024,
1836
- mb: 1024 * 1024,
1837
- gb: 1024 * 1024 * 1024,
1838
- };
1839
-
1840
- const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);
1841
- if (!match) return 1024 * 1024; // Default 1MB
1842
-
1843
- const value = parseFloat(match[1]);
1844
- const unit = match[2] || 'b';
1845
-
1846
- return Math.round(value * units[unit]);
1847
- }