@morojs/moro 1.6.8 → 1.7.1

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 (307) hide show
  1. package/README.md +3 -1
  2. package/dist/core/auth/morojs-adapter.js +16 -6
  3. package/dist/core/auth/morojs-adapter.js.map +1 -1
  4. package/dist/core/config/config-sources.js +27 -15
  5. package/dist/core/config/config-sources.js.map +1 -1
  6. package/dist/core/config/config-validator.js +201 -6
  7. package/dist/core/config/config-validator.js.map +1 -1
  8. package/dist/core/docs/openapi-generator.js +8 -9
  9. package/dist/core/docs/openapi-generator.js.map +1 -1
  10. package/dist/core/events/event-bus.js +1 -1
  11. package/dist/core/events/event-bus.js.map +1 -1
  12. package/dist/core/framework.d.ts +4 -2
  13. package/dist/core/framework.js +25 -24
  14. package/dist/core/framework.js.map +1 -1
  15. package/dist/core/graphql/core.js +34 -8
  16. package/dist/core/graphql/core.js.map +1 -1
  17. package/dist/core/grpc/adapters/grpc-js-adapter.d.ts +28 -0
  18. package/dist/core/grpc/adapters/grpc-js-adapter.js +449 -0
  19. package/dist/core/grpc/adapters/grpc-js-adapter.js.map +1 -0
  20. package/dist/core/grpc/adapters/index.d.ts +1 -0
  21. package/dist/core/grpc/adapters/index.js +6 -0
  22. package/dist/core/grpc/adapters/index.js.map +1 -0
  23. package/dist/core/grpc/grpc-adapter.d.ts +47 -0
  24. package/dist/core/grpc/grpc-adapter.js +4 -0
  25. package/dist/core/grpc/grpc-adapter.js.map +1 -0
  26. package/dist/core/grpc/grpc-manager.d.ts +59 -0
  27. package/dist/core/grpc/grpc-manager.js +218 -0
  28. package/dist/core/grpc/grpc-manager.js.map +1 -0
  29. package/dist/core/grpc/index.d.ts +7 -0
  30. package/dist/core/grpc/index.js +10 -0
  31. package/dist/core/grpc/index.js.map +1 -0
  32. package/dist/core/grpc/middleware/auth.d.ts +22 -0
  33. package/dist/core/grpc/middleware/auth.js +126 -0
  34. package/dist/core/grpc/middleware/auth.js.map +1 -0
  35. package/dist/core/grpc/middleware/logging.d.ts +19 -0
  36. package/dist/core/grpc/middleware/logging.js +57 -0
  37. package/dist/core/grpc/middleware/logging.js.map +1 -0
  38. package/dist/core/grpc/middleware/validation.d.ts +18 -0
  39. package/dist/core/grpc/middleware/validation.js +126 -0
  40. package/dist/core/grpc/middleware/validation.js.map +1 -0
  41. package/dist/core/grpc/types.d.ts +233 -0
  42. package/dist/core/grpc/types.js +36 -0
  43. package/dist/core/grpc/types.js.map +1 -0
  44. package/dist/core/http/http-server.d.ts +13 -84
  45. package/dist/core/http/http-server.js +338 -792
  46. package/dist/core/http/http-server.js.map +1 -1
  47. package/dist/core/http/http2-server.d.ts +131 -0
  48. package/dist/core/http/http2-server.js +936 -0
  49. package/dist/core/http/http2-server.js.map +1 -0
  50. package/dist/core/http/index.d.ts +3 -1
  51. package/dist/core/http/index.js +2 -1
  52. package/dist/core/http/index.js.map +1 -1
  53. package/dist/core/http/uws-http-server.js +154 -26
  54. package/dist/core/http/uws-http-server.js.map +1 -1
  55. package/dist/core/jobs/job-executor.js +6 -1
  56. package/dist/core/jobs/job-executor.js.map +1 -1
  57. package/dist/core/jobs/job-scheduler.js +4 -1
  58. package/dist/core/jobs/job-scheduler.js.map +1 -1
  59. package/dist/core/jobs/leader-election.js +2 -1
  60. package/dist/core/jobs/leader-election.js.map +1 -1
  61. package/dist/core/logger/logger.js +41 -18
  62. package/dist/core/logger/logger.js.map +1 -1
  63. package/dist/core/logger/outputs.js +9 -3
  64. package/dist/core/logger/outputs.js.map +1 -1
  65. package/dist/core/mail/adapters/console-adapter.d.ts +14 -0
  66. package/dist/core/mail/adapters/console-adapter.js +89 -0
  67. package/dist/core/mail/adapters/console-adapter.js.map +1 -0
  68. package/dist/core/mail/adapters/index.d.ts +5 -0
  69. package/dist/core/mail/adapters/index.js +8 -0
  70. package/dist/core/mail/adapters/index.js.map +1 -0
  71. package/dist/core/mail/adapters/nodemailer-adapter.d.ts +18 -0
  72. package/dist/core/mail/adapters/nodemailer-adapter.js +188 -0
  73. package/dist/core/mail/adapters/nodemailer-adapter.js.map +1 -0
  74. package/dist/core/mail/adapters/resend-adapter.d.ts +18 -0
  75. package/dist/core/mail/adapters/resend-adapter.js +169 -0
  76. package/dist/core/mail/adapters/resend-adapter.js.map +1 -0
  77. package/dist/core/mail/adapters/sendgrid-adapter.d.ts +19 -0
  78. package/dist/core/mail/adapters/sendgrid-adapter.js +186 -0
  79. package/dist/core/mail/adapters/sendgrid-adapter.js.map +1 -0
  80. package/dist/core/mail/adapters/ses-adapter.d.ts +18 -0
  81. package/dist/core/mail/adapters/ses-adapter.js +167 -0
  82. package/dist/core/mail/adapters/ses-adapter.js.map +1 -0
  83. package/dist/core/mail/index.d.ts +5 -0
  84. package/dist/core/mail/index.js +8 -0
  85. package/dist/core/mail/index.js.map +1 -0
  86. package/dist/core/mail/mail-adapter.d.ts +62 -0
  87. package/dist/core/mail/mail-adapter.js +83 -0
  88. package/dist/core/mail/mail-adapter.js.map +1 -0
  89. package/dist/core/mail/mail-manager.d.ts +63 -0
  90. package/dist/core/mail/mail-manager.js +302 -0
  91. package/dist/core/mail/mail-manager.js.map +1 -0
  92. package/dist/core/mail/template-engine.d.ts +43 -0
  93. package/dist/core/mail/template-engine.js +239 -0
  94. package/dist/core/mail/template-engine.js.map +1 -0
  95. package/dist/core/mail/types.d.ts +237 -0
  96. package/dist/core/mail/types.js +4 -0
  97. package/dist/core/mail/types.js.map +1 -0
  98. package/dist/core/middleware/built-in/body-size/core.d.ts +12 -0
  99. package/dist/core/middleware/built-in/body-size/core.js +52 -0
  100. package/dist/core/middleware/built-in/body-size/core.js.map +1 -0
  101. package/dist/core/middleware/built-in/body-size/hook.d.ts +2 -0
  102. package/dist/core/middleware/built-in/body-size/hook.js +12 -0
  103. package/dist/core/middleware/built-in/body-size/hook.js.map +1 -0
  104. package/dist/core/middleware/built-in/body-size/index.d.ts +6 -0
  105. package/dist/core/middleware/built-in/body-size/index.js +7 -0
  106. package/dist/core/middleware/built-in/body-size/index.js.map +1 -0
  107. package/dist/core/middleware/built-in/body-size/middleware.d.ts +14 -0
  108. package/dist/core/middleware/built-in/body-size/middleware.js +22 -0
  109. package/dist/core/middleware/built-in/body-size/middleware.js.map +1 -0
  110. package/dist/core/middleware/built-in/cache/core.d.ts +20 -1
  111. package/dist/core/middleware/built-in/cache/core.js.map +1 -1
  112. package/dist/core/middleware/built-in/cache/hook.d.ts +38 -1
  113. package/dist/core/middleware/built-in/cache/hook.js +202 -16
  114. package/dist/core/middleware/built-in/cache/hook.js.map +1 -1
  115. package/dist/core/middleware/built-in/cache/index.js +1 -1
  116. package/dist/core/middleware/built-in/cache/index.js.map +1 -1
  117. package/dist/core/middleware/built-in/compression/core.d.ts +16 -0
  118. package/dist/core/middleware/built-in/compression/core.js +75 -0
  119. package/dist/core/middleware/built-in/compression/core.js.map +1 -0
  120. package/dist/core/middleware/built-in/compression/hook.d.ts +2 -0
  121. package/dist/core/middleware/built-in/compression/hook.js +14 -0
  122. package/dist/core/middleware/built-in/compression/hook.js.map +1 -0
  123. package/dist/core/middleware/built-in/compression/index.d.ts +6 -0
  124. package/dist/core/middleware/built-in/compression/index.js +7 -0
  125. package/dist/core/middleware/built-in/compression/index.js.map +1 -0
  126. package/dist/core/middleware/built-in/compression/middleware.d.ts +20 -0
  127. package/dist/core/middleware/built-in/compression/middleware.js +28 -0
  128. package/dist/core/middleware/built-in/compression/middleware.js.map +1 -0
  129. package/dist/core/middleware/built-in/cookie/core.js +37 -9
  130. package/dist/core/middleware/built-in/cookie/core.js.map +1 -1
  131. package/dist/core/middleware/built-in/helmet/core.d.ts +19 -0
  132. package/dist/core/middleware/built-in/helmet/core.js +70 -0
  133. package/dist/core/middleware/built-in/helmet/core.js.map +1 -0
  134. package/dist/core/middleware/built-in/helmet/hook.d.ts +2 -0
  135. package/dist/core/middleware/built-in/helmet/hook.js +12 -0
  136. package/dist/core/middleware/built-in/helmet/hook.js.map +1 -0
  137. package/dist/core/middleware/built-in/helmet/index.d.ts +6 -0
  138. package/dist/core/middleware/built-in/helmet/index.js +7 -0
  139. package/dist/core/middleware/built-in/helmet/index.js.map +1 -0
  140. package/dist/core/middleware/built-in/helmet/middleware.d.ts +22 -0
  141. package/dist/core/middleware/built-in/helmet/middleware.js +28 -0
  142. package/dist/core/middleware/built-in/helmet/middleware.js.map +1 -0
  143. package/dist/core/middleware/built-in/http2/core.d.ts +35 -0
  144. package/dist/core/middleware/built-in/http2/core.js +128 -0
  145. package/dist/core/middleware/built-in/http2/core.js.map +1 -0
  146. package/dist/core/middleware/built-in/http2/hook.d.ts +5 -0
  147. package/dist/core/middleware/built-in/http2/hook.js +34 -0
  148. package/dist/core/middleware/built-in/http2/hook.js.map +1 -0
  149. package/dist/core/middleware/built-in/http2/index.d.ts +8 -0
  150. package/dist/core/middleware/built-in/http2/index.js +10 -0
  151. package/dist/core/middleware/built-in/http2/index.js.map +1 -0
  152. package/dist/core/middleware/built-in/http2/middleware.d.ts +20 -0
  153. package/dist/core/middleware/built-in/http2/middleware.js +31 -0
  154. package/dist/core/middleware/built-in/http2/middleware.js.map +1 -0
  155. package/dist/core/middleware/built-in/index.d.ts +18 -0
  156. package/dist/core/middleware/built-in/index.js +28 -0
  157. package/dist/core/middleware/built-in/index.js.map +1 -1
  158. package/dist/core/middleware/built-in/range/core.d.ts +16 -0
  159. package/dist/core/middleware/built-in/range/core.js +112 -0
  160. package/dist/core/middleware/built-in/range/core.js.map +1 -0
  161. package/dist/core/middleware/built-in/range/hook.d.ts +2 -0
  162. package/dist/core/middleware/built-in/range/hook.js +12 -0
  163. package/dist/core/middleware/built-in/range/hook.js.map +1 -0
  164. package/dist/core/middleware/built-in/range/index.d.ts +6 -0
  165. package/dist/core/middleware/built-in/range/index.js +7 -0
  166. package/dist/core/middleware/built-in/range/index.js.map +1 -0
  167. package/dist/core/middleware/built-in/range/middleware.d.ts +21 -0
  168. package/dist/core/middleware/built-in/range/middleware.js +27 -0
  169. package/dist/core/middleware/built-in/range/middleware.js.map +1 -0
  170. package/dist/core/middleware/built-in/session/core.js +14 -1
  171. package/dist/core/middleware/built-in/session/core.js.map +1 -1
  172. package/dist/core/middleware/built-in/static/core.d.ts +20 -0
  173. package/dist/core/middleware/built-in/static/core.js +143 -0
  174. package/dist/core/middleware/built-in/static/core.js.map +1 -0
  175. package/dist/core/middleware/built-in/static/hook.d.ts +2 -0
  176. package/dist/core/middleware/built-in/static/hook.js +12 -0
  177. package/dist/core/middleware/built-in/static/hook.js.map +1 -0
  178. package/dist/core/middleware/built-in/static/index.d.ts +6 -0
  179. package/dist/core/middleware/built-in/static/index.js +7 -0
  180. package/dist/core/middleware/built-in/static/index.js.map +1 -0
  181. package/dist/core/middleware/built-in/static/middleware.d.ts +18 -0
  182. package/dist/core/middleware/built-in/static/middleware.js +26 -0
  183. package/dist/core/middleware/built-in/static/middleware.js.map +1 -0
  184. package/dist/core/middleware/built-in/template/core.d.ts +19 -0
  185. package/dist/core/middleware/built-in/template/core.js +108 -0
  186. package/dist/core/middleware/built-in/template/core.js.map +1 -0
  187. package/dist/core/middleware/built-in/template/hook.d.ts +2 -0
  188. package/dist/core/middleware/built-in/template/hook.js +12 -0
  189. package/dist/core/middleware/built-in/template/hook.js.map +1 -0
  190. package/dist/core/middleware/built-in/template/index.d.ts +6 -0
  191. package/dist/core/middleware/built-in/template/index.js +7 -0
  192. package/dist/core/middleware/built-in/template/index.js.map +1 -0
  193. package/dist/core/middleware/built-in/template/middleware.d.ts +21 -0
  194. package/dist/core/middleware/built-in/template/middleware.js +27 -0
  195. package/dist/core/middleware/built-in/template/middleware.js.map +1 -0
  196. package/dist/core/middleware/built-in/upload/core.d.ts +29 -0
  197. package/dist/core/middleware/built-in/upload/core.js +66 -0
  198. package/dist/core/middleware/built-in/upload/core.js.map +1 -0
  199. package/dist/core/middleware/built-in/upload/hook.d.ts +2 -0
  200. package/dist/core/middleware/built-in/upload/hook.js +25 -0
  201. package/dist/core/middleware/built-in/upload/hook.js.map +1 -0
  202. package/dist/core/middleware/built-in/upload/index.d.ts +6 -0
  203. package/dist/core/middleware/built-in/upload/index.js +7 -0
  204. package/dist/core/middleware/built-in/upload/index.js.map +1 -0
  205. package/dist/core/middleware/built-in/upload/middleware.d.ts +18 -0
  206. package/dist/core/middleware/built-in/upload/middleware.js +41 -0
  207. package/dist/core/middleware/built-in/upload/middleware.js.map +1 -0
  208. package/dist/core/middleware/built-in/validation/middleware.js +2 -1
  209. package/dist/core/middleware/built-in/validation/middleware.js.map +1 -1
  210. package/dist/core/networking/adapters/uws-adapter.js +54 -6
  211. package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
  212. package/dist/core/networking/adapters/ws-adapter.js +56 -17
  213. package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
  214. package/dist/core/pooling/object-pool-manager.js +9 -1
  215. package/dist/core/pooling/object-pool-manager.js.map +1 -1
  216. package/dist/core/queue/adapters/bull-adapter.d.ts +86 -0
  217. package/dist/core/queue/adapters/bull-adapter.js +330 -0
  218. package/dist/core/queue/adapters/bull-adapter.js.map +1 -0
  219. package/dist/core/queue/adapters/index.d.ts +9 -0
  220. package/dist/core/queue/adapters/index.js +10 -0
  221. package/dist/core/queue/adapters/index.js.map +1 -0
  222. package/dist/core/queue/adapters/kafka-adapter.d.ts +86 -0
  223. package/dist/core/queue/adapters/kafka-adapter.js +462 -0
  224. package/dist/core/queue/adapters/kafka-adapter.js.map +1 -0
  225. package/dist/core/queue/adapters/memory-adapter.d.ts +87 -0
  226. package/dist/core/queue/adapters/memory-adapter.js +419 -0
  227. package/dist/core/queue/adapters/memory-adapter.js.map +1 -0
  228. package/dist/core/queue/adapters/rabbitmq-adapter.d.ts +86 -0
  229. package/dist/core/queue/adapters/rabbitmq-adapter.js +436 -0
  230. package/dist/core/queue/adapters/rabbitmq-adapter.js.map +1 -0
  231. package/dist/core/queue/adapters/sqs-adapter.d.ts +102 -0
  232. package/dist/core/queue/adapters/sqs-adapter.js +522 -0
  233. package/dist/core/queue/adapters/sqs-adapter.js.map +1 -0
  234. package/dist/core/queue/index.d.ts +11 -0
  235. package/dist/core/queue/index.js +14 -0
  236. package/dist/core/queue/index.js.map +1 -0
  237. package/dist/core/queue/middleware/index.d.ts +7 -0
  238. package/dist/core/queue/middleware/index.js +8 -0
  239. package/dist/core/queue/middleware/index.js.map +1 -0
  240. package/dist/core/queue/middleware/monitoring.d.ts +84 -0
  241. package/dist/core/queue/middleware/monitoring.js +145 -0
  242. package/dist/core/queue/middleware/monitoring.js.map +1 -0
  243. package/dist/core/queue/middleware/priority.d.ts +61 -0
  244. package/dist/core/queue/middleware/priority.js +90 -0
  245. package/dist/core/queue/middleware/priority.js.map +1 -0
  246. package/dist/core/queue/middleware/rate-limit.d.ts +34 -0
  247. package/dist/core/queue/middleware/rate-limit.js +109 -0
  248. package/dist/core/queue/middleware/rate-limit.js.map +1 -0
  249. package/dist/core/queue/queue-adapter.d.ts +73 -0
  250. package/dist/core/queue/queue-adapter.js +20 -0
  251. package/dist/core/queue/queue-adapter.js.map +1 -0
  252. package/dist/core/queue/queue-manager.d.ts +92 -0
  253. package/dist/core/queue/queue-manager.js +327 -0
  254. package/dist/core/queue/queue-manager.js.map +1 -0
  255. package/dist/core/queue/types.d.ts +205 -0
  256. package/dist/core/queue/types.js +6 -0
  257. package/dist/core/queue/types.js.map +1 -0
  258. package/dist/core/routing/index.js +41 -10
  259. package/dist/core/routing/index.js.map +1 -1
  260. package/dist/core/routing/radix-tree.d.ts +48 -0
  261. package/dist/core/routing/radix-tree.js +211 -0
  262. package/dist/core/routing/radix-tree.js.map +1 -0
  263. package/dist/core/routing/router.d.ts +10 -9
  264. package/dist/core/routing/router.js +3 -1
  265. package/dist/core/routing/router.js.map +1 -1
  266. package/dist/core/routing/unified-router.d.ts +18 -12
  267. package/dist/core/routing/unified-router.js +220 -163
  268. package/dist/core/routing/unified-router.js.map +1 -1
  269. package/dist/core/runtime/aws-lambda-adapter.js +21 -10
  270. package/dist/core/runtime/aws-lambda-adapter.js.map +1 -1
  271. package/dist/core/runtime/base-adapter.js +15 -5
  272. package/dist/core/runtime/base-adapter.js.map +1 -1
  273. package/dist/core/runtime/cloudflare-workers-adapter.js +35 -12
  274. package/dist/core/runtime/cloudflare-workers-adapter.js.map +1 -1
  275. package/dist/core/runtime/vercel-edge-adapter.js +16 -6
  276. package/dist/core/runtime/vercel-edge-adapter.js.map +1 -1
  277. package/dist/core/utilities/container.js +3 -1
  278. package/dist/core/utilities/container.js.map +1 -1
  279. package/dist/core/utilities/index.d.ts +2 -0
  280. package/dist/core/utilities/index.js +2 -0
  281. package/dist/core/utilities/index.js.map +1 -1
  282. package/dist/core/utilities/response-helpers.d.ts +280 -0
  283. package/dist/core/utilities/response-helpers.js +359 -0
  284. package/dist/core/utilities/response-helpers.js.map +1 -0
  285. package/dist/core/workers/facade.d.ts +74 -0
  286. package/dist/core/workers/facade.js +98 -0
  287. package/dist/core/workers/facade.js.map +1 -0
  288. package/dist/core/workers/index.d.ts +2 -0
  289. package/dist/core/workers/index.js +6 -0
  290. package/dist/core/workers/index.js.map +1 -0
  291. package/dist/core/workers/worker-manager.d.ts +124 -0
  292. package/dist/core/workers/worker-manager.js +299 -0
  293. package/dist/core/workers/worker-manager.js.map +1 -0
  294. package/dist/core/workers/worker.d.ts +1 -0
  295. package/dist/core/workers/worker.js +225 -0
  296. package/dist/core/workers/worker.js.map +1 -0
  297. package/dist/index.d.ts +10 -2
  298. package/dist/index.js +9 -2
  299. package/dist/index.js.map +1 -1
  300. package/dist/moro.d.ts +345 -13
  301. package/dist/moro.js +820 -221
  302. package/dist/moro.js.map +1 -1
  303. package/dist/types/cache.d.ts +4 -0
  304. package/dist/types/config.d.ts +42 -0
  305. package/dist/types/core.d.ts +18 -1
  306. package/dist/types/http.d.ts +21 -0
  307. package/package.json +98 -24
@@ -1,8 +1,6 @@
1
1
  // src/core/http-server.ts
2
2
  import { createServer } from 'http';
3
3
  import * as zlib from 'zlib';
4
- import { createReadStream } from 'fs';
5
- import * as crypto from 'crypto';
6
4
  import { promisify } from 'util';
7
5
  import { createFrameworkLogger } from '../logger/index.js';
8
6
  import { PathMatcher } from '../routing/path-matcher.js';
@@ -38,44 +36,6 @@ export class MoroHttpServer {
38
36
  methodNotAllowed: Buffer.from('{"success":false,"error":"Method not allowed"}'),
39
37
  rateLimited: Buffer.from('{"success":false,"error":"Rate limit exceeded"}'),
40
38
  };
41
- // Buffer pool for zero-copy operations
42
- static BUFFER_SIZES = [64, 256, 1024, 4096, 16384];
43
- static BUFFER_POOLS = new Map();
44
- static {
45
- // Pre-allocate buffer pools for zero-allocation responses
46
- for (const size of MoroHttpServer.BUFFER_SIZES) {
47
- MoroHttpServer.BUFFER_POOLS.set(size, []);
48
- for (let i = 0; i < 50; i++) {
49
- // 50 buffers per size
50
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
51
- MoroHttpServer.BUFFER_POOLS.get(size).push(Buffer.allocUnsafe(size));
52
- }
53
- }
54
- }
55
- static getOptimalBuffer(size) {
56
- // Find the smallest buffer that fits
57
- for (const poolSize of MoroHttpServer.BUFFER_SIZES) {
58
- if (size <= poolSize) {
59
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
60
- const pool = MoroHttpServer.BUFFER_POOLS.get(poolSize);
61
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
62
- return pool.length > 0 ? pool.pop() : Buffer.allocUnsafe(poolSize);
63
- }
64
- }
65
- return Buffer.allocUnsafe(size);
66
- }
67
- static returnBuffer(buffer) {
68
- // Return buffer to appropriate pool
69
- const size = buffer.length;
70
- if (MoroHttpServer.BUFFER_POOLS.has(size)) {
71
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
72
- const pool = MoroHttpServer.BUFFER_POOLS.get(size);
73
- if (pool.length < 50) {
74
- // Don't let pools grow too large
75
- pool.push(buffer);
76
- }
77
- }
78
- }
79
39
  constructor() {
80
40
  this.server = createServer(this.handleRequest.bind(this));
81
41
  // Optimize server for high performance (conservative settings for compatibility)
@@ -289,9 +249,9 @@ export class MoroHttpServer {
289
249
  }
290
250
  }
291
251
  finally {
292
- // CRITICAL: Always release pooled objects back to the pool
252
+ // Always release pooled objects back to the pool
293
253
  // This prevents memory leaks and ensures consistent performance
294
- // Optimized: Check if object is empty without Object.keys()
254
+ // Check if object is empty without Object.keys()
295
255
  if (originalParams) {
296
256
  let isEmpty = true;
297
257
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -317,7 +277,7 @@ export class MoroHttpServer {
317
277
  }
318
278
  // Additional cleanup on response completion to ensure objects are returned to pool
319
279
  res.once('finish', () => {
320
- // Optimized: Check if object is empty without Object.keys()
280
+ // Check if object is empty without Object.keys()
321
281
  if (originalParams) {
322
282
  let isEmpty = true;
323
283
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -367,14 +327,40 @@ export class MoroHttpServer {
367
327
  streamLargeResponse(res, data) {
368
328
  res.setHeader('Content-Type', 'application/json; charset=utf-8');
369
329
  res.setHeader('Transfer-Encoding', 'chunked');
370
- // Stream the response in chunks
371
- const jsonString = JSON.stringify(data);
372
- const chunkSize = 8192; // 8KB chunks
373
- for (let i = 0; i < jsonString.length; i += chunkSize) {
374
- const chunk = jsonString.substring(i, i + chunkSize);
375
- res.write(chunk);
330
+ // Stream large JSON responses to prevent memory issues
331
+ if (Array.isArray(data) && data.length > 100) {
332
+ // Stream large arrays element by element
333
+ res.write('[');
334
+ // Stream each array element
335
+ let first = true;
336
+ for (const item of data) {
337
+ if (!first)
338
+ res.write(',');
339
+ res.write(JSON.stringify(item));
340
+ first = false;
341
+ }
342
+ // Write closing bracket and end
343
+ res.end(']');
344
+ }
345
+ else if (typeof data === 'object' && data !== null && Object.keys(data).length > 50) {
346
+ // For large objects, stream key-value pairs
347
+ res.write('{');
348
+ const keys = Object.keys(data);
349
+ let first = true;
350
+ for (const key of keys) {
351
+ if (!first)
352
+ res.write(',');
353
+ // Properly escape the key using JSON.stringify
354
+ res.write(`${JSON.stringify(key)}:${JSON.stringify(data[key])}`);
355
+ first = false;
356
+ }
357
+ res.end('}');
358
+ }
359
+ else {
360
+ // For smaller data, still avoid the old chunking approach
361
+ const jsonString = JSON.stringify(data);
362
+ res.end(jsonString);
376
363
  }
377
- res.end();
378
364
  }
379
365
  normalizePath(path) {
380
366
  // Check cache first
@@ -472,76 +458,21 @@ export class MoroHttpServer {
472
458
  httpRes.json = async (data) => {
473
459
  if (httpRes.headersSent)
474
460
  return;
475
- // JSON serialization with zero-copy buffers
476
- let jsonString;
477
- // Enhanced JSON optimization for common API patterns
478
- // Fast path for common 2-3 key objects without Object.keys() overhead
479
- if (data && typeof data === 'object' && 'success' in data) {
480
- // Check for common patterns using 'in' operator (faster than Object.keys for small objects)
481
- const hasData = 'data' in data;
482
- const hasError = 'error' in data;
483
- const hasTotal = 'total' in data;
484
- // Fast path: {success, data} - most common pattern
485
- if (hasData && !hasError && !hasTotal) {
486
- // Verify it's exactly 2 keys by checking no other common keys exist
487
- if (!('message' in data) && !('code' in data) && !('status' in data)) {
488
- jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)}}`;
489
- }
490
- else {
491
- jsonString = JSON.stringify(data);
492
- }
493
- }
494
- else if (hasError && !hasData && !hasTotal) {
495
- // Fast path: {success, error}
496
- if (!('message' in data) && !('code' in data) && !('status' in data)) {
497
- jsonString = `{"success":${data.success},"error":${JSON.stringify(data.error)}}`;
498
- }
499
- else {
500
- jsonString = JSON.stringify(data);
501
- }
502
- }
503
- else if (hasData && hasError && !hasTotal) {
504
- // Fast path: {success, data, error}
505
- if (!('message' in data) && !('code' in data) && !('status' in data)) {
506
- jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)},"error":${JSON.stringify(data.error)}}`;
507
- }
508
- else {
509
- jsonString = JSON.stringify(data);
510
- }
511
- }
512
- else if (hasData && hasTotal && !hasError) {
513
- // Fast path: {success, data, total}
514
- if (!('message' in data) && !('code' in data) && !('status' in data)) {
515
- jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)},"total":${data.total}}`;
516
- }
517
- else {
518
- jsonString = JSON.stringify(data);
519
- }
520
- }
521
- else {
522
- // Complex object - use standard JSON.stringify
523
- jsonString = JSON.stringify(data);
524
- }
525
- }
526
- else {
527
- jsonString = JSON.stringify(data);
528
- }
529
- // Use buffer pool for zero-allocation responses
530
- const estimatedSize = jsonString.length;
531
- if (estimatedSize > 32768) {
461
+ // Simple, optimized JSON serialization - let V8 handle the optimization
462
+ const jsonString = JSON.stringify(data);
463
+ // Large response check - stream if needed
464
+ if (jsonString.length > 32768) {
532
465
  // Large response - stream it
533
466
  return this.streamLargeResponse(httpRes, data);
534
467
  }
535
- const buffer = MoroHttpServer.getOptimalBuffer(estimatedSize);
536
- const actualLength = buffer.write(jsonString, 0, 'utf8');
537
- // Slice to actual size to avoid sending extra bytes
538
- const finalBuffer = actualLength === buffer.length ? buffer : buffer.subarray(0, actualLength);
468
+ // Use efficient buffer allocation - let Node.js handle optimization
469
+ const finalBuffer = Buffer.from(jsonString, 'utf8');
539
470
  // Optimized header setting - set multiple headers at once when possible
540
471
  const headers = {
541
472
  'Content-Type': 'application/json; charset=utf-8',
542
473
  };
543
474
  // Compression with buffer pool - EARLY EXIT if disabled or below threshold
544
- // CRITICAL: Only make this async if compression is actually happening
475
+ // Only make this async if compression is actually happening
545
476
  if (this.compressionEnabled && finalBuffer.length > this.compressionThreshold) {
546
477
  const acceptEncoding = httpRes.req.headers['accept-encoding'];
547
478
  if (acceptEncoding && acceptEncoding.includes('gzip')) {
@@ -552,8 +483,6 @@ export class MoroHttpServer {
552
483
  // Batch write all headers at once (50-100% faster)
553
484
  httpRes.writeHead(httpRes.statusCode || 200, headers);
554
485
  httpRes.end(compressed);
555
- // Return buffer to pool after response
556
- process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
557
486
  });
558
487
  return;
559
488
  }
@@ -565,8 +494,6 @@ export class MoroHttpServer {
565
494
  // Batch write all headers at once
566
495
  httpRes.writeHead(httpRes.statusCode || 200, headers);
567
496
  httpRes.end(compressed);
568
- // Return buffer to pool after response
569
- process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
570
497
  });
571
498
  return;
572
499
  }
@@ -576,8 +503,6 @@ export class MoroHttpServer {
576
503
  // Batch write all headers at once
577
504
  httpRes.writeHead(httpRes.statusCode || 200, headers);
578
505
  httpRes.end(finalBuffer);
579
- // Return buffer to pool after response (zero-copy achievement!)
580
- process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
581
506
  };
582
507
  httpRes.send = (data) => {
583
508
  if (httpRes.headersSent)
@@ -638,15 +563,31 @@ export class MoroHttpServer {
638
563
  if (options.path)
639
564
  cookieString += `; Path=${options.path}`;
640
565
  const existingCookies = httpRes.getHeader('Set-Cookie') || [];
566
+ // Avoid spread operator - direct array manipulation
641
567
  const cookies = Array.isArray(existingCookies)
642
- ? [...existingCookies]
568
+ ? existingCookies
643
569
  : [existingCookies];
644
570
  cookies.push(cookieString);
645
571
  httpRes.setHeader('Set-Cookie', cookies);
646
572
  return httpRes;
647
573
  };
648
574
  httpRes.clearCookie = (name, options = {}) => {
649
- const clearOptions = { ...options, expires: new Date(0), maxAge: 0 };
575
+ // Avoid spread operator - manually set properties
576
+ const clearOptions = {
577
+ expires: new Date(0),
578
+ maxAge: 0,
579
+ };
580
+ // Copy other options manually
581
+ if (options.path !== undefined)
582
+ clearOptions.path = options.path;
583
+ if (options.domain !== undefined)
584
+ clearOptions.domain = options.domain;
585
+ if (options.httpOnly !== undefined)
586
+ clearOptions.httpOnly = options.httpOnly;
587
+ if (options.secure !== undefined)
588
+ clearOptions.secure = options.secure;
589
+ if (options.sameSite !== undefined)
590
+ clearOptions.sameSite = options.sameSite;
650
591
  return httpRes.cookie(name, '', clearOptions);
651
592
  };
652
593
  httpRes.redirect = (url, status = 302) => {
@@ -681,6 +622,139 @@ export class MoroHttpServer {
681
622
  httpRes.status(404).json({ success: false, error: 'File not found' });
682
623
  }
683
624
  };
625
+ // Standardized response helpers
626
+ httpRes.success = (data, message) => {
627
+ const response = {
628
+ success: true,
629
+ data,
630
+ };
631
+ if (message !== undefined) {
632
+ response.message = message;
633
+ }
634
+ httpRes.json(response);
635
+ };
636
+ httpRes.error = (error, code, message) => {
637
+ const response = {
638
+ success: false,
639
+ error,
640
+ };
641
+ if (code !== undefined) {
642
+ response.code = code;
643
+ }
644
+ if (message !== undefined) {
645
+ response.message = message;
646
+ }
647
+ httpRes.json(response);
648
+ };
649
+ // Common HTTP error helpers (automatically set status code)
650
+ httpRes.unauthorized = (message = 'Authentication required') => {
651
+ httpRes.statusCode = 401;
652
+ httpRes.json({
653
+ success: false,
654
+ error: 'Unauthorized',
655
+ code: 'UNAUTHORIZED',
656
+ message,
657
+ });
658
+ };
659
+ httpRes.forbidden = (message = 'Insufficient permissions') => {
660
+ httpRes.statusCode = 403;
661
+ httpRes.json({
662
+ success: false,
663
+ error: 'Forbidden',
664
+ code: 'FORBIDDEN',
665
+ message,
666
+ });
667
+ };
668
+ httpRes.notFound = (resource = 'Resource') => {
669
+ httpRes.statusCode = 404;
670
+ httpRes.json({
671
+ success: false,
672
+ error: 'Not Found',
673
+ code: 'NOT_FOUND',
674
+ message: `${resource} not found`,
675
+ });
676
+ };
677
+ httpRes.badRequest = (message = 'Invalid request') => {
678
+ httpRes.statusCode = 400;
679
+ httpRes.json({
680
+ success: false,
681
+ error: 'Bad Request',
682
+ code: 'BAD_REQUEST',
683
+ message,
684
+ });
685
+ };
686
+ httpRes.conflict = (message) => {
687
+ httpRes.statusCode = 409;
688
+ httpRes.json({
689
+ success: false,
690
+ error: 'Conflict',
691
+ code: 'CONFLICT',
692
+ message,
693
+ });
694
+ };
695
+ httpRes.internalError = (message = 'Internal server error') => {
696
+ httpRes.statusCode = 500;
697
+ httpRes.json({
698
+ success: false,
699
+ error: 'Internal Server Error',
700
+ code: 'INTERNAL_ERROR',
701
+ message,
702
+ });
703
+ };
704
+ httpRes.validationError = (errors) => {
705
+ httpRes.statusCode = 422;
706
+ httpRes.json({
707
+ success: false,
708
+ error: 'Validation Failed',
709
+ code: 'VALIDATION_ERROR',
710
+ errors,
711
+ });
712
+ };
713
+ httpRes.rateLimited = (retryAfter) => {
714
+ httpRes.statusCode = 429;
715
+ if (retryAfter) {
716
+ httpRes.setHeader('Retry-After', retryAfter.toString());
717
+ }
718
+ httpRes.json({
719
+ success: false,
720
+ error: 'Rate Limit Exceeded',
721
+ code: 'RATE_LIMITED',
722
+ message: retryAfter
723
+ ? `Too many requests. Retry after ${retryAfter} seconds.`
724
+ : 'Too many requests',
725
+ retryAfter,
726
+ });
727
+ };
728
+ // Common success patterns
729
+ httpRes.created = (data, location) => {
730
+ httpRes.statusCode = 201;
731
+ if (location) {
732
+ httpRes.setHeader('Location', location);
733
+ }
734
+ httpRes.json({
735
+ success: true,
736
+ data,
737
+ });
738
+ };
739
+ httpRes.noContent = () => {
740
+ httpRes.statusCode = 204;
741
+ httpRes.end();
742
+ };
743
+ httpRes.paginated = (data, pagination) => {
744
+ const totalPages = Math.ceil(pagination.total / pagination.limit);
745
+ httpRes.json({
746
+ success: true,
747
+ data,
748
+ pagination: {
749
+ page: pagination.page,
750
+ limit: pagination.limit,
751
+ total: pagination.total,
752
+ totalPages,
753
+ hasNext: pagination.page < totalPages,
754
+ hasPrev: pagination.page > 1,
755
+ },
756
+ });
757
+ };
684
758
  // Header management utilities
685
759
  httpRes.hasHeader = (name) => {
686
760
  return httpRes.getHeader(name) !== undefined;
@@ -688,15 +762,17 @@ export class MoroHttpServer {
688
762
  // Note: removeHeader is inherited from ServerResponse, we don't override it
689
763
  httpRes.setBulkHeaders = (headers) => {
690
764
  if (httpRes.headersSent) {
765
+ // Only enumerate keys for warning if headers were already sent
766
+ const attemptedHeaderKeys = [];
767
+ for (const key in headers) {
768
+ attemptedHeaderKeys.push(key);
769
+ }
691
770
  this.logger.warn('Cannot set headers - headers already sent', 'HeaderWarning', {
692
- attemptedHeaders: Object.keys(headers),
771
+ attemptedHeaders: attemptedHeaderKeys,
693
772
  });
694
773
  return httpRes;
695
774
  }
696
- const headerKeys = Object.keys(headers);
697
- const headerKeysLen = headerKeys.length;
698
- for (let i = 0; i < headerKeysLen; i++) {
699
- const key = headerKeys[i];
775
+ for (const key in headers) {
700
776
  httpRes.setHeader(key, headers[key]);
701
777
  }
702
778
  return httpRes;
@@ -765,10 +841,18 @@ export class MoroHttpServer {
765
841
  return mimeType;
766
842
  }
767
843
  async parseBody(req) {
844
+ const contentType = req.headers['content-type'] || '';
845
+ const contentLength = parseInt(req.headers['content-length'] || '0');
846
+ const maxSize = 10 * 1024 * 1024; // 10MB limit
847
+ // For very large payloads, return a streaming interface instead of buffering
848
+ if (contentLength > maxSize / 2) {
849
+ // Stream for payloads > 5MB
850
+ return this.createStreamingBodyParser(req, contentType, maxSize);
851
+ }
852
+ // Standard buffered parsing for smaller payloads
768
853
  return new Promise((resolve, reject) => {
769
854
  const chunks = [];
770
855
  let totalLength = 0;
771
- const maxSize = 10 * 1024 * 1024; // 10MB limit
772
856
  req.on('data', (chunk) => {
773
857
  totalLength += chunk.length;
774
858
  if (totalLength > maxSize) {
@@ -780,7 +864,6 @@ export class MoroHttpServer {
780
864
  req.on('end', () => {
781
865
  try {
782
866
  const body = Buffer.concat(chunks);
783
- const contentType = req.headers['content-type'] || '';
784
867
  if (contentType.includes('application/json')) {
785
868
  resolve(JSON.parse(body.toString()));
786
869
  }
@@ -801,6 +884,132 @@ export class MoroHttpServer {
801
884
  req.on('error', reject);
802
885
  });
803
886
  }
887
+ /**
888
+ * Create a streaming body parser for large payloads
889
+ * Returns a streaming interface instead of buffering
890
+ */
891
+ createStreamingBodyParser(req, contentType, maxSize) {
892
+ let totalLength = 0;
893
+ const chunks = [];
894
+ return new Promise((resolve, reject) => {
895
+ const streamParser = {
896
+ // Streaming JSON parser for large JSON payloads
897
+ json: () => this.streamJsonParse(req, maxSize),
898
+ // Streaming form data parser
899
+ form: () => this.streamFormParse(req, maxSize),
900
+ // Raw stream access
901
+ stream: () => ({
902
+ onData: (callback) => {
903
+ req.on('data', (chunk) => {
904
+ totalLength += chunk.length;
905
+ if (totalLength > maxSize) {
906
+ reject(new Error('Request body too large'));
907
+ return;
908
+ }
909
+ callback(chunk);
910
+ });
911
+ },
912
+ onEnd: (callback) => {
913
+ req.on('end', callback);
914
+ },
915
+ onError: (callback) => {
916
+ req.on('error', callback);
917
+ },
918
+ }),
919
+ // Traditional buffered parsing (fallback)
920
+ buffer: async () => {
921
+ return new Promise((resolveBuffer, rejectBuffer) => {
922
+ req.on('data', (chunk) => {
923
+ totalLength += chunk.length;
924
+ if (totalLength > maxSize) {
925
+ rejectBuffer(new Error('Request body too large'));
926
+ return;
927
+ }
928
+ chunks.push(chunk);
929
+ });
930
+ req.on('end', () => {
931
+ try {
932
+ const body = Buffer.concat(chunks);
933
+ if (contentType.includes('application/json')) {
934
+ resolveBuffer(JSON.parse(body.toString()));
935
+ }
936
+ else {
937
+ resolveBuffer(body.toString());
938
+ }
939
+ }
940
+ catch (error) {
941
+ rejectBuffer(error);
942
+ }
943
+ });
944
+ req.on('error', rejectBuffer);
945
+ });
946
+ },
947
+ };
948
+ // Auto-detect and return appropriate parser
949
+ if (contentType.includes('application/json')) {
950
+ resolve({ type: 'json', parser: streamParser.json });
951
+ }
952
+ else if (contentType.includes('application/x-www-form-urlencoded')) {
953
+ resolve({ type: 'form', parser: streamParser.form });
954
+ }
955
+ else {
956
+ resolve({ type: 'stream', parser: streamParser.stream });
957
+ }
958
+ });
959
+ }
960
+ /**
961
+ * Streaming JSON parser for large payloads
962
+ */
963
+ async streamJsonParse(req, maxSize) {
964
+ return new Promise((resolve, reject) => {
965
+ let jsonString = '';
966
+ let totalLength = 0;
967
+ req.on('data', (chunk) => {
968
+ totalLength += chunk.length;
969
+ if (totalLength > maxSize) {
970
+ reject(new Error('Request body too large'));
971
+ return;
972
+ }
973
+ jsonString += chunk.toString();
974
+ });
975
+ req.on('end', () => {
976
+ try {
977
+ // For very large JSON, consider streaming JSON parsing in the future
978
+ resolve(JSON.parse(jsonString));
979
+ }
980
+ catch (error) {
981
+ reject(error);
982
+ }
983
+ });
984
+ req.on('error', reject);
985
+ });
986
+ }
987
+ /**
988
+ * Streaming form data parser
989
+ */
990
+ async streamFormParse(req, maxSize) {
991
+ return new Promise((resolve, reject) => {
992
+ let formData = '';
993
+ let totalLength = 0;
994
+ req.on('data', (chunk) => {
995
+ totalLength += chunk.length;
996
+ if (totalLength > maxSize) {
997
+ reject(new Error('Request body too large'));
998
+ return;
999
+ }
1000
+ formData += chunk.toString();
1001
+ });
1002
+ req.on('end', () => {
1003
+ try {
1004
+ resolve(this.parseUrlEncoded(formData));
1005
+ }
1006
+ catch (error) {
1007
+ reject(error);
1008
+ }
1009
+ });
1010
+ req.on('error', reject);
1011
+ });
1012
+ }
804
1013
  parseMultipart(buffer, contentType) {
805
1014
  const boundary = contentType.split('boundary=')[1];
806
1015
  if (!boundary) {
@@ -898,7 +1107,7 @@ export class MoroHttpServer {
898
1107
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
899
1108
  return this.routeCache.get(normalizedCacheKey);
900
1109
  }
901
- // Phase 1: O(1) static route lookup
1110
+ // O(1) static route lookup
902
1111
  const staticRoute = this.staticRoutes.get(normalizedCacheKey);
903
1112
  if (staticRoute) {
904
1113
  this.routeCache.set(normalizedCacheKey, staticRoute);
@@ -907,7 +1116,7 @@ export class MoroHttpServer {
907
1116
  }
908
1117
  return staticRoute;
909
1118
  }
910
- // Phase 2: Optimized dynamic route matching by segment count
1119
+ // Dynamic route matching by segment count
911
1120
  let route = null;
912
1121
  const dynamicRoutesLen = this.dynamicRoutes.length;
913
1122
  if (dynamicRoutesLen > 0) {
@@ -1011,667 +1220,4 @@ export class MoroHttpServer {
1011
1220
  };
1012
1221
  }
1013
1222
  }
1014
- // Built-in middleware
1015
- export const middleware = {
1016
- cors: (options = {}) => {
1017
- return (req, res, next) => {
1018
- res.setHeader('Access-Control-Allow-Origin', options.origin || '*');
1019
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
1020
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
1021
- if (options.credentials) {
1022
- res.setHeader('Access-Control-Allow-Credentials', 'true');
1023
- }
1024
- if (req.method === 'OPTIONS') {
1025
- res.status(200).send('');
1026
- return;
1027
- }
1028
- next();
1029
- };
1030
- },
1031
- helmet: () => {
1032
- return (req, res, next) => {
1033
- res.setHeader('X-Content-Type-Options', 'nosniff');
1034
- res.setHeader('X-Frame-Options', 'DENY');
1035
- res.setHeader('X-XSS-Protection', '1; mode=block');
1036
- res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
1037
- res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
1038
- res.setHeader('Content-Security-Policy', "default-src 'self'");
1039
- next();
1040
- };
1041
- },
1042
- compression: (options = {}) => {
1043
- const threshold = options.threshold || 1024;
1044
- const level = options.level || 6;
1045
- return (req, res, next) => {
1046
- const acceptEncoding = req.headers['accept-encoding'] || '';
1047
- // Override res.json to compress responses
1048
- const originalJson = res.json;
1049
- const originalSend = res.send;
1050
- const compressResponse = (data, isJson = false) => {
1051
- const content = isJson ? JSON.stringify(data) : data;
1052
- const buffer = Buffer.from(content);
1053
- if (buffer.length < threshold) {
1054
- return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
1055
- }
1056
- if (acceptEncoding.includes('gzip')) {
1057
- zlib.gzip(buffer, { level }, (err, compressed) => {
1058
- if (err) {
1059
- return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
1060
- }
1061
- if (!res.headersSent) {
1062
- res.setHeader('Content-Encoding', 'gzip');
1063
- res.setHeader('Content-Length', compressed.length);
1064
- }
1065
- res.end(compressed);
1066
- });
1067
- }
1068
- else if (acceptEncoding.includes('deflate')) {
1069
- zlib.deflate(buffer, { level }, (err, compressed) => {
1070
- if (err) {
1071
- return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
1072
- }
1073
- if (!res.headersSent) {
1074
- res.setHeader('Content-Encoding', 'deflate');
1075
- res.setHeader('Content-Length', compressed.length);
1076
- }
1077
- res.end(compressed);
1078
- });
1079
- }
1080
- else {
1081
- return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
1082
- }
1083
- };
1084
- res.json = function (data) {
1085
- // Ensure charset is set for Safari compatibility
1086
- this.setHeader('Content-Type', 'application/json; charset=utf-8');
1087
- compressResponse(data, true);
1088
- return this;
1089
- };
1090
- res.send = function (data) {
1091
- compressResponse(data, false);
1092
- return this;
1093
- };
1094
- next();
1095
- };
1096
- },
1097
- requestLogger: () => {
1098
- return (req, res, next) => {
1099
- const start = Date.now();
1100
- res.on('finish', () => {
1101
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1102
- const duration = Date.now() - start;
1103
- // Request completed - logged by framework
1104
- });
1105
- next();
1106
- };
1107
- },
1108
- bodySize: (options = {}) => {
1109
- const limit = options.limit || '10mb';
1110
- const limitBytes = parseSize(limit);
1111
- return (req, res, next) => {
1112
- const contentLength = parseInt(req.headers['content-length'] || '0');
1113
- if (contentLength > limitBytes) {
1114
- res.status(413).json({
1115
- success: false,
1116
- error: 'Request entity too large',
1117
- limit: limit,
1118
- });
1119
- return;
1120
- }
1121
- next();
1122
- };
1123
- },
1124
- static: (options) => {
1125
- return async (req, res, next) => {
1126
- // Only handle GET and HEAD requests
1127
- if (req.method !== 'GET' && req.method !== 'HEAD') {
1128
- next();
1129
- return;
1130
- }
1131
- try {
1132
- const fs = await import('fs/promises');
1133
- const path = await import('path');
1134
- const crypto = await import('crypto');
1135
- let filePath = path.join(options.root, req.path);
1136
- // Security: prevent directory traversal
1137
- if (!filePath.startsWith(path.resolve(options.root))) {
1138
- res.status(403).json({ success: false, error: 'Forbidden' });
1139
- return;
1140
- }
1141
- // Handle dotfiles
1142
- const basename = path.basename(filePath);
1143
- if (basename.startsWith('.')) {
1144
- if (options.dotfiles === 'deny') {
1145
- res.status(403).json({ success: false, error: 'Forbidden' });
1146
- return;
1147
- }
1148
- else if (options.dotfiles === 'ignore') {
1149
- next();
1150
- return;
1151
- }
1152
- }
1153
- let stats;
1154
- try {
1155
- stats = await fs.stat(filePath);
1156
- }
1157
- catch {
1158
- next(); // File not found, let other middleware handle
1159
- return;
1160
- }
1161
- // Handle directories
1162
- if (stats.isDirectory()) {
1163
- const indexFiles = options.index || ['index.html', 'index.htm'];
1164
- let indexFound = false;
1165
- for (const indexFile of indexFiles) {
1166
- const indexPath = path.join(filePath, indexFile);
1167
- try {
1168
- const indexStats = await fs.stat(indexPath);
1169
- if (indexStats.isFile()) {
1170
- filePath = indexPath;
1171
- stats = indexStats;
1172
- indexFound = true;
1173
- break;
1174
- }
1175
- }
1176
- catch {
1177
- // Continue to next index file
1178
- }
1179
- }
1180
- if (!indexFound) {
1181
- next();
1182
- return;
1183
- }
1184
- }
1185
- // Set headers with proper mime type and charset
1186
- const ext = path.extname(filePath);
1187
- const mimeTypes = {
1188
- '.html': 'text/html',
1189
- '.css': 'text/css',
1190
- '.js': 'application/javascript',
1191
- '.json': 'application/json',
1192
- '.png': 'image/png',
1193
- '.jpg': 'image/jpeg',
1194
- '.jpeg': 'image/jpeg',
1195
- '.gif': 'image/gif',
1196
- '.svg': 'image/svg+xml',
1197
- '.ico': 'image/x-icon',
1198
- '.pdf': 'application/pdf',
1199
- '.txt': 'text/plain',
1200
- '.xml': 'application/xml',
1201
- };
1202
- const baseMimeType = mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
1203
- // Add charset for text-based files
1204
- const textTypes = [
1205
- 'text/',
1206
- 'application/json',
1207
- 'application/javascript',
1208
- 'application/xml',
1209
- 'image/svg+xml',
1210
- ];
1211
- const needsCharset = textTypes.some(type => baseMimeType.startsWith(type));
1212
- const contentType = needsCharset ? `${baseMimeType}; charset=utf-8` : baseMimeType;
1213
- res.setHeader('Content-Type', contentType);
1214
- res.setHeader('Content-Length', stats.size);
1215
- // Cache headers
1216
- if (options.maxAge) {
1217
- res.setHeader('Cache-Control', `public, max-age=${options.maxAge}`);
1218
- }
1219
- // ETag support
1220
- if (options.etag !== false) {
1221
- const etag = crypto
1222
- .createHash('md5')
1223
- .update(`${stats.mtime.getTime()}-${stats.size}`)
1224
- .digest('hex');
1225
- res.setHeader('ETag', `"${etag}"`);
1226
- // Handle conditional requests
1227
- const ifNoneMatch = req.headers['if-none-match'];
1228
- if (ifNoneMatch === `"${etag}"`) {
1229
- res.statusCode = 304;
1230
- res.end();
1231
- return;
1232
- }
1233
- }
1234
- // Handle HEAD requests
1235
- if (req.method === 'HEAD') {
1236
- res.end();
1237
- return;
1238
- }
1239
- // Send file
1240
- const data = await fs.readFile(filePath);
1241
- res.end(data);
1242
- }
1243
- catch {
1244
- res.status(500).json({ success: false, error: 'Internal server error' });
1245
- }
1246
- };
1247
- },
1248
- upload: (options = {}) => {
1249
- return (req, res, next) => {
1250
- const contentType = req.headers['content-type'] || '';
1251
- if (!contentType.includes('multipart/form-data')) {
1252
- next();
1253
- return;
1254
- }
1255
- // File upload handling is now built into parseBody method
1256
- // This middleware can add additional validation
1257
- if (req.body && req.body.files) {
1258
- const files = req.body.files;
1259
- const maxFileSize = options.maxFileSize || 5 * 1024 * 1024; // 5MB default
1260
- const maxFiles = options.maxFiles || 10;
1261
- const allowedTypes = options.allowedTypes;
1262
- // Validate file count
1263
- if (Object.keys(files).length > maxFiles) {
1264
- res.status(400).json({
1265
- success: false,
1266
- error: `Too many files. Maximum ${maxFiles} allowed.`,
1267
- });
1268
- return;
1269
- }
1270
- // Validate each file
1271
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1272
- for (const [fieldName, file] of Object.entries(files)) {
1273
- const fileData = file;
1274
- // Validate file size
1275
- if (fileData.size > maxFileSize) {
1276
- res.status(400).json({
1277
- success: false,
1278
- error: `File ${fileData.filename} is too large. Maximum ${maxFileSize} bytes allowed.`,
1279
- });
1280
- return;
1281
- }
1282
- // Validate file type
1283
- if (allowedTypes && !allowedTypes.includes(fileData.mimetype)) {
1284
- res.status(400).json({
1285
- success: false,
1286
- error: `File type ${fileData.mimetype} not allowed.`,
1287
- });
1288
- return;
1289
- }
1290
- }
1291
- // Store files in request for easy access
1292
- req.files = files;
1293
- }
1294
- next();
1295
- };
1296
- },
1297
- template: (options) => {
1298
- const templateCache = new Map();
1299
- return async (req, res, next) => {
1300
- // Add render method to response
1301
- res.render = async (template, data = {}) => {
1302
- try {
1303
- const fs = await import('fs/promises');
1304
- const path = await import('path');
1305
- const templatePath = path.join(options.views, `${template}.html`);
1306
- let templateContent;
1307
- // Check cache first
1308
- if (options.cache && templateCache.has(templatePath)) {
1309
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1310
- templateContent = templateCache.get(templatePath);
1311
- }
1312
- else {
1313
- templateContent = await fs.readFile(templatePath, 'utf-8');
1314
- if (options.cache) {
1315
- templateCache.set(templatePath, templateContent);
1316
- }
1317
- }
1318
- // Simple template engine - replace {{variable}} with values
1319
- let rendered = templateContent;
1320
- // Handle basic variable substitution
1321
- rendered = rendered.replace(/\{\{(\w+)\}\}/g, (match, key) => {
1322
- return data[key] !== undefined ? String(data[key]) : match;
1323
- });
1324
- // Handle nested object properties like {{user.name}}
1325
- rendered = rendered.replace(/\{\{([\w.]+)\}\}/g, (match, key) => {
1326
- const value = key.split('.').reduce((obj, prop) => obj?.[prop], data);
1327
- return value !== undefined ? String(value) : match;
1328
- });
1329
- // Handle loops: {{#each items}}{{name}}{{/each}}
1330
- rendered = rendered.replace(/\{\{#each (\w+)\}\}(.*?)\{\{\/each\}\}/gs, (match, arrayKey, template) => {
1331
- const array = data[arrayKey];
1332
- if (!Array.isArray(array))
1333
- return '';
1334
- return array
1335
- .map(item => {
1336
- let itemTemplate = template;
1337
- // Replace variables in the loop template
1338
- itemTemplate = itemTemplate.replace(/\{\{(\w+)\}\}/g, (match, key) => {
1339
- return item[key] !== undefined ? String(item[key]) : match;
1340
- });
1341
- return itemTemplate;
1342
- })
1343
- .join('');
1344
- });
1345
- // Handle conditionals: {{#if condition}}content{{/if}}
1346
- rendered = rendered.replace(/\{\{#if (\w+)\}\}(.*?)\{\{\/if\}\}/gs, (match, conditionKey, content) => {
1347
- const condition = data[conditionKey];
1348
- return condition ? content : '';
1349
- });
1350
- // Handle layout
1351
- if (options.defaultLayout) {
1352
- const layoutPath = path.join(options.views, 'layouts', `${options.defaultLayout}.html`);
1353
- try {
1354
- let layoutContent;
1355
- if (options.cache && templateCache.has(layoutPath)) {
1356
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1357
- layoutContent = templateCache.get(layoutPath);
1358
- }
1359
- else {
1360
- layoutContent = await fs.readFile(layoutPath, 'utf-8');
1361
- if (options.cache) {
1362
- templateCache.set(layoutPath, layoutContent);
1363
- }
1364
- }
1365
- rendered = layoutContent.replace(/\{\{body\}\}/, rendered);
1366
- }
1367
- catch {
1368
- // Layout not found, use template as-is
1369
- }
1370
- }
1371
- res.setHeader('Content-Type', 'text/html');
1372
- res.end(rendered);
1373
- }
1374
- catch {
1375
- res.status(500).json({ success: false, error: 'Template rendering failed' });
1376
- }
1377
- };
1378
- next();
1379
- };
1380
- },
1381
- // HTTP/2 Server Push middleware
1382
- http2Push: (options = {}) => {
1383
- return (req, res, next) => {
1384
- // Add HTTP/2 push capability to response
1385
- res.push = (path, options = {}) => {
1386
- // Check if HTTP/2 is supported
1387
- if (req.httpVersion === '2.0' && res.stream && res.stream.pushAllowed) {
1388
- try {
1389
- const pushStream = res.stream.pushStream({
1390
- ':method': 'GET',
1391
- ':path': path,
1392
- ...options.headers,
1393
- });
1394
- if (pushStream) {
1395
- // Handle push stream
1396
- return pushStream;
1397
- }
1398
- }
1399
- catch {
1400
- // Push failed, continue normally
1401
- }
1402
- }
1403
- return null;
1404
- };
1405
- // Auto-push configured resources
1406
- if (options.resources && (!options.condition || options.condition(req))) {
1407
- for (const resource of options.resources) {
1408
- res.push?.(resource.path, {
1409
- headers: {
1410
- 'content-type': resource.type || 'text/plain',
1411
- },
1412
- });
1413
- }
1414
- }
1415
- next();
1416
- };
1417
- },
1418
- // Server-Sent Events middleware
1419
- sse: (options = {}) => {
1420
- return (req, res, next) => {
1421
- // Only handle SSE requests
1422
- if (req.headers.accept?.includes('text/event-stream')) {
1423
- // Set SSE headers
1424
- if (!res.headersSent) {
1425
- res.writeHead(200, {
1426
- 'Content-Type': 'text/event-stream',
1427
- 'Cache-Control': 'no-cache',
1428
- Connection: 'keep-alive',
1429
- 'Access-Control-Allow-Origin': options.cors ? '*' : undefined,
1430
- 'Access-Control-Allow-Headers': options.cors ? 'Cache-Control' : undefined,
1431
- });
1432
- }
1433
- // Add SSE methods to response
1434
- res.sendEvent = (data, event, id) => {
1435
- if (id)
1436
- res.write(`id: ${id}\n`);
1437
- if (event)
1438
- res.write(`event: ${event}\n`);
1439
- res.write(`data: ${typeof data === 'string' ? data : JSON.stringify(data)}\n\n`);
1440
- };
1441
- res.sendComment = (comment) => {
1442
- res.write(`: ${comment}\n\n`);
1443
- };
1444
- res.sendRetry = (ms) => {
1445
- res.write(`retry: ${ms}\n\n`);
1446
- };
1447
- // Set up heartbeat if configured
1448
- let heartbeatInterval = null;
1449
- if (options.heartbeat) {
1450
- heartbeatInterval = setInterval(() => {
1451
- res.sendComment('heartbeat');
1452
- }, options.heartbeat);
1453
- }
1454
- // Set retry if configured
1455
- if (options.retry) {
1456
- res.sendRetry(options.retry);
1457
- }
1458
- // Clean up on close
1459
- req.on('close', () => {
1460
- if (heartbeatInterval) {
1461
- clearInterval(heartbeatInterval);
1462
- }
1463
- });
1464
- // Don't call next() - this middleware handles the response
1465
- return;
1466
- }
1467
- next();
1468
- };
1469
- },
1470
- // Range request middleware for streaming
1471
- range: (options = {}) => {
1472
- return async (req, res, next) => {
1473
- // Add range support to response
1474
- res.sendRange = async (filePath, stats) => {
1475
- try {
1476
- const fs = await import('fs/promises');
1477
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1478
- const _path = await import('path');
1479
- if (!stats) {
1480
- stats = await fs.stat(filePath);
1481
- }
1482
- const fileSize = stats.size;
1483
- const range = req.headers.range;
1484
- // Set Accept-Ranges header
1485
- res.setHeader('Accept-Ranges', options.acceptRanges || 'bytes');
1486
- if (!range) {
1487
- // No range requested, send entire file
1488
- res.setHeader('Content-Length', fileSize);
1489
- const data = await fs.readFile(filePath);
1490
- res.end(data);
1491
- return;
1492
- }
1493
- // Parse range header
1494
- const ranges = range
1495
- .replace(/bytes=/, '')
1496
- .split(',')
1497
- .map(r => {
1498
- const [start, end] = r.split('-');
1499
- return {
1500
- start: start ? parseInt(start) : 0,
1501
- end: end ? parseInt(end) : fileSize - 1,
1502
- };
1503
- });
1504
- // Validate ranges
1505
- if (options.maxRanges && ranges.length > options.maxRanges) {
1506
- res.status(416).json({ success: false, error: 'Too many ranges' });
1507
- return;
1508
- }
1509
- if (ranges.length === 1) {
1510
- // Single range
1511
- const { start, end } = ranges[0];
1512
- const chunkSize = end - start + 1;
1513
- if (start >= fileSize || end >= fileSize) {
1514
- res.status(416);
1515
- res.setHeader('Content-Range', `bytes */${fileSize}`);
1516
- res.json({ success: false, error: 'Range not satisfiable' });
1517
- return;
1518
- }
1519
- res.status(206);
1520
- res.setHeader('Content-Range', `bytes ${start}-${end}/${fileSize}`);
1521
- res.setHeader('Content-Length', chunkSize);
1522
- // Stream the range
1523
- const stream = createReadStream(filePath, {
1524
- start,
1525
- end,
1526
- });
1527
- stream.pipe(res);
1528
- }
1529
- else {
1530
- // Multiple ranges - multipart response
1531
- const boundary = 'MULTIPART_BYTERANGES';
1532
- res.status(206);
1533
- res.setHeader('Content-Type', `multipart/byteranges; boundary=${boundary}`);
1534
- for (const { start, end } of ranges) {
1535
- if (start >= fileSize || end >= fileSize)
1536
- continue;
1537
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1538
- const chunkSize = end - start + 1;
1539
- res.write(`\r\n--${boundary}\r\n`);
1540
- res.write(`Content-Range: bytes ${start}-${end}/${fileSize}\r\n\r\n`);
1541
- const stream = createReadStream(filePath, {
1542
- start,
1543
- end,
1544
- });
1545
- await new Promise(resolve => {
1546
- stream.on('end', () => resolve());
1547
- stream.pipe(res, { end: false });
1548
- });
1549
- }
1550
- res.write(`\r\n--${boundary}--\r\n`);
1551
- res.end();
1552
- }
1553
- }
1554
- catch {
1555
- res.status(500).json({ success: false, error: 'Range request failed' });
1556
- }
1557
- };
1558
- next();
1559
- };
1560
- },
1561
- // CSRF Protection middleware
1562
- csrf: (options = {}) => {
1563
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1564
- const secret = options.secret || 'moro-csrf-secret';
1565
- const tokenLength = options.tokenLength || 32;
1566
- const cookieName = options.cookieName || '_csrf';
1567
- const headerName = options.headerName || 'x-csrf-token';
1568
- const ignoreMethods = options.ignoreMethods || ['GET', 'HEAD', 'OPTIONS'];
1569
- const generateToken = () => {
1570
- return crypto.randomBytes(tokenLength).toString('hex');
1571
- };
1572
- const verifyToken = (token, sessionToken) => {
1573
- return token && sessionToken && token === sessionToken;
1574
- };
1575
- return (req, res, next) => {
1576
- // Add CSRF token generation method
1577
- req.csrfToken = () => {
1578
- if (!req._csrfToken) {
1579
- req._csrfToken = generateToken();
1580
- // Set token in cookie
1581
- res.cookie(cookieName, req._csrfToken, {
1582
- httpOnly: true,
1583
- sameSite: options.sameSite !== false ? 'strict' : undefined,
1584
- secure: req.headers['x-forwarded-proto'] === 'https' || req.socket.encrypted,
1585
- });
1586
- }
1587
- return req._csrfToken;
1588
- };
1589
- // Skip verification for safe methods
1590
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1591
- if (ignoreMethods.includes(req.method)) {
1592
- next();
1593
- return;
1594
- }
1595
- // Get token from header or body
1596
- const token = req.headers[headerName] || (req.body && req.body._csrf) || (req.query && req.query._csrf);
1597
- // Get session token from cookie
1598
- const sessionToken = req.cookies?.[cookieName];
1599
- if (!verifyToken(token, sessionToken || '')) {
1600
- res.status(403).json({
1601
- success: false,
1602
- error: 'Invalid CSRF token',
1603
- code: 'CSRF_TOKEN_MISMATCH',
1604
- });
1605
- return;
1606
- }
1607
- next();
1608
- };
1609
- },
1610
- // Content Security Policy middleware
1611
- csp: (options = {}) => {
1612
- return (req, res, next) => {
1613
- const directives = options.directives || {
1614
- defaultSrc: ["'self'"],
1615
- scriptSrc: ["'self'"],
1616
- styleSrc: ["'self'", "'unsafe-inline'"],
1617
- imgSrc: ["'self'", 'data:', 'https:'],
1618
- connectSrc: ["'self'"],
1619
- fontSrc: ["'self'"],
1620
- objectSrc: ["'none'"],
1621
- mediaSrc: ["'self'"],
1622
- frameSrc: ["'none'"],
1623
- };
1624
- // Generate nonce if requested
1625
- let nonce;
1626
- if (options.nonce) {
1627
- nonce = crypto.randomBytes(16).toString('base64');
1628
- req.cspNonce = nonce;
1629
- }
1630
- // Build CSP header value
1631
- const cspParts = [];
1632
- for (const [directive, sources] of Object.entries(directives)) {
1633
- if (directive === 'upgradeInsecureRequests' && sources === true) {
1634
- cspParts.push('upgrade-insecure-requests');
1635
- }
1636
- else if (directive === 'blockAllMixedContent' && sources === true) {
1637
- cspParts.push('block-all-mixed-content');
1638
- }
1639
- else if (Array.isArray(sources)) {
1640
- let sourceList = sources.join(' ');
1641
- // Add nonce to script-src and style-src if enabled
1642
- if (nonce && (directive === 'scriptSrc' || directive === 'styleSrc')) {
1643
- sourceList += ` 'nonce-${nonce}'`;
1644
- }
1645
- // Convert camelCase to kebab-case
1646
- const kebabDirective = directive.replace(/([A-Z])/g, '-$1').toLowerCase();
1647
- cspParts.push(`${kebabDirective} ${sourceList}`);
1648
- }
1649
- }
1650
- // Add report-uri if specified
1651
- if (options.reportUri) {
1652
- cspParts.push(`report-uri ${options.reportUri}`);
1653
- }
1654
- const cspValue = cspParts.join('; ');
1655
- const headerName = options.reportOnly
1656
- ? 'Content-Security-Policy-Report-Only'
1657
- : 'Content-Security-Policy';
1658
- res.setHeader(headerName, cspValue);
1659
- next();
1660
- };
1661
- },
1662
- };
1663
- function parseSize(size) {
1664
- const units = {
1665
- b: 1,
1666
- kb: 1024,
1667
- mb: 1024 * 1024,
1668
- gb: 1024 * 1024 * 1024,
1669
- };
1670
- const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);
1671
- if (!match)
1672
- return 1024 * 1024; // Default 1MB
1673
- const value = parseFloat(match[1]);
1674
- const unit = match[2] || 'b';
1675
- return Math.round(value * units[unit]);
1676
- }
1677
1223
  //# sourceMappingURL=http-server.js.map