acrylblends 0.3.0 → 0.4.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 (341) hide show
  1. package/dist/index.d.ts +6 -1
  2. package/dist/index.js +84 -5
  3. package/dist/index.js.map +1 -1
  4. package/docs/framework/LICENSE +23 -0
  5. package/docs/framework/README.md +63 -0
  6. package/docs/framework/UPSTREAM.md +3 -0
  7. package/docs/framework/api-summary.md +138 -0
  8. package/docs/framework/concepts.md +105 -0
  9. package/docs/framework/context.md +129 -0
  10. package/docs/framework/getting-started.md +146 -0
  11. package/docs/framework/helpers.md +118 -0
  12. package/docs/framework/middleware.md +154 -0
  13. package/docs/framework/migration-from-0-3.md +108 -0
  14. package/docs/framework/migration-from-dsh-webserver.md +103 -0
  15. package/docs/framework/node-server.md +75 -0
  16. package/docs/framework/plugin-lifecycle.md +111 -0
  17. package/docs/framework/request.md +106 -0
  18. package/docs/framework/routing.md +188 -0
  19. package/docs/framework/surfaces.md +71 -0
  20. package/docs/framework/upstream.json +9 -0
  21. package/docs/framework/validation.md +135 -0
  22. package/package.json +4 -2
  23. package/plugins/acryl-inspect/plugin.json +2 -1
  24. package/plugins/acryl-inspect/src/report.ts +21 -13
  25. package/plugins/acryl-inspect/src/service.ts +9 -7
  26. package/plugins/acryl-inspect/tests/report.spec.ts +13 -8
  27. package/plugins/acryl-inspect/tests/service.spec.ts +11 -10
  28. package/plugins/acryl-inspect/upstream.json +2 -2
  29. package/plugins/acryl-node-server/LICENSE +26 -0
  30. package/plugins/acryl-node-server/plugin.json +2 -1
  31. package/plugins/acryl-node-server/src/adapter/conninfo.ts +23 -0
  32. package/plugins/acryl-node-server/src/adapter/early-hints.ts +55 -0
  33. package/plugins/acryl-node-server/src/adapter/error.ts +11 -0
  34. package/plugins/acryl-node-server/src/adapter/headers.ts +249 -0
  35. package/plugins/acryl-node-server/src/adapter/index.ts +6 -0
  36. package/plugins/acryl-node-server/src/adapter/listener.ts +540 -0
  37. package/plugins/acryl-node-server/src/adapter/request.ts +797 -0
  38. package/plugins/acryl-node-server/src/adapter/response.ts +186 -0
  39. package/plugins/acryl-node-server/src/adapter/serve-static.ts +238 -0
  40. package/plugins/acryl-node-server/src/adapter/server.ts +36 -0
  41. package/plugins/acryl-node-server/src/adapter/types.ts +82 -0
  42. package/plugins/acryl-node-server/src/adapter/url.ts +42 -0
  43. package/plugins/acryl-node-server/src/adapter/utils/response/constants.ts +1 -0
  44. package/plugins/acryl-node-server/src/adapter/utils/response.ts +4 -0
  45. package/plugins/acryl-node-server/src/adapter/utils/stream.ts +88 -0
  46. package/plugins/acryl-node-server/src/adapter/utils.ts +96 -0
  47. package/plugins/acryl-node-server/src/adapter/websocket-types.ts +45 -0
  48. package/plugins/acryl-node-server/src/adapter/websocket.ts +375 -0
  49. package/plugins/acryl-node-server/src/index.ts +19 -6
  50. package/plugins/acryl-node-server/tests/adapter/app.ts +76 -0
  51. package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +1 -0
  52. package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
  53. package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +1 -0
  54. package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +1 -0
  55. package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +1 -0
  56. package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +5 -0
  57. package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +1 -0
  58. package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
  59. package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +1 -0
  60. package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +1 -0
  61. package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +1 -0
  62. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +1 -0
  63. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +1 -0
  64. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +1 -0
  65. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +1 -0
  66. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +1 -0
  67. package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +34 -0
  68. package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +248 -0
  69. package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +457 -0
  70. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +23 -0
  71. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +27 -0
  72. package/plugins/acryl-node-server/tests/adapter/headers.test.ts +252 -0
  73. package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +190 -0
  74. package/plugins/acryl-node-server/tests/adapter/listener.test.ts +727 -0
  75. package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +189 -0
  76. package/plugins/acryl-node-server/tests/adapter/request.test.ts +1230 -0
  77. package/plugins/acryl-node-server/tests/adapter/response.test.ts +285 -0
  78. package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +849 -0
  79. package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +790 -0
  80. package/plugins/acryl-node-server/tests/adapter/server.test.ts +1302 -0
  81. package/plugins/acryl-node-server/tests/adapter/setup.ts +16 -0
  82. package/plugins/acryl-node-server/tests/adapter/url.test.ts +76 -0
  83. package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +23 -0
  84. package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +75 -0
  85. package/plugins/acryl-node-server/tests/adapter/utils.test.ts +235 -0
  86. package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +260 -0
  87. package/plugins/acryl-node-server/tests/server.spec.ts +44 -8
  88. package/plugins/acryl-node-server/upstream.json +3 -3
  89. package/plugins/acryl-router/LICENSE +26 -0
  90. package/plugins/acryl-router/plugin.json +2 -1
  91. package/plugins/acryl-router/src/client.ts +2 -0
  92. package/plugins/acryl-router/src/connection.ts +42 -0
  93. package/plugins/acryl-router/src/core/app-base.ts +544 -0
  94. package/plugins/acryl-router/src/core/app.test.ts +3833 -0
  95. package/plugins/acryl-router/src/core/app.ts +34 -0
  96. package/plugins/acryl-router/src/core/client/client.test.ts +2313 -0
  97. package/plugins/acryl-router/src/core/client/client.ts +249 -0
  98. package/plugins/acryl-router/src/core/client/fetch-result-please.ts +95 -0
  99. package/plugins/acryl-router/src/core/client/index.ts +17 -0
  100. package/plugins/acryl-router/src/core/client/types.test.ts +149 -0
  101. package/plugins/acryl-router/src/core/client/types.ts +393 -0
  102. package/plugins/acryl-router/src/core/client/utils.test.ts +405 -0
  103. package/plugins/acryl-router/src/core/client/utils.ts +117 -0
  104. package/plugins/acryl-router/src/core/compose.test.ts +751 -0
  105. package/plugins/acryl-router/src/core/compose.ts +73 -0
  106. package/plugins/acryl-router/src/core/context.test.ts +629 -0
  107. package/plugins/acryl-router/src/core/context.ts +781 -0
  108. package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +344 -0
  109. package/plugins/acryl-router/src/core/helper/accepts/accepts.ts +96 -0
  110. package/plugins/acryl-router/src/core/helper/accepts/index.ts +6 -0
  111. package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +55 -0
  112. package/plugins/acryl-router/src/core/helper/adapter/index.ts +90 -0
  113. package/plugins/acryl-router/src/core/helper/conninfo/index.ts +6 -0
  114. package/plugins/acryl-router/src/core/helper/conninfo/types.ts +45 -0
  115. package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +510 -0
  116. package/plugins/acryl-router/src/core/helper/cookie/index.ts +146 -0
  117. package/plugins/acryl-router/src/core/helper/factory/index.test.ts +434 -0
  118. package/plugins/acryl-router/src/core/helper/factory/index.ts +375 -0
  119. package/plugins/acryl-router/src/core/helper/html/index.test.ts +78 -0
  120. package/plugins/acryl-router/src/core/helper/html/index.ts +57 -0
  121. package/plugins/acryl-router/src/core/helper/route/index.test.ts +259 -0
  122. package/plugins/acryl-router/src/core/helper/route/index.ts +144 -0
  123. package/plugins/acryl-router/src/core/helper/streaming/index.ts +9 -0
  124. package/plugins/acryl-router/src/core/helper/streaming/sse.ts +101 -0
  125. package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +128 -0
  126. package/plugins/acryl-router/src/core/helper/streaming/stream.ts +45 -0
  127. package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +34 -0
  128. package/plugins/acryl-router/src/core/helper/streaming/text.ts +15 -0
  129. package/plugins/acryl-router/src/core/helper/streaming/utils.ts +11 -0
  130. package/plugins/acryl-router/src/core/helper/testing/index.test.ts +42 -0
  131. package/plugins/acryl-router/src/core/helper/testing/index.ts +28 -0
  132. package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +124 -0
  133. package/plugins/acryl-router/src/core/helper/websocket/index.ts +140 -0
  134. package/plugins/acryl-router/src/core/http-exception.test.ts +45 -0
  135. package/plugins/acryl-router/src/core/http-exception.ts +77 -0
  136. package/plugins/acryl-router/src/core/index.ts +53 -0
  137. package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +434 -0
  138. package/plugins/acryl-router/src/core/middleware/basic-auth/index.ts +152 -0
  139. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +1018 -0
  140. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.ts +220 -0
  141. package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +273 -0
  142. package/plugins/acryl-router/src/core/middleware/body-limit/index.ts +110 -0
  143. package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +355 -0
  144. package/plugins/acryl-router/src/core/middleware/combine/index.ts +171 -0
  145. package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +535 -0
  146. package/plugins/acryl-router/src/core/middleware/compress/index.ts +142 -0
  147. package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +43 -0
  148. package/plugins/acryl-router/src/core/middleware/context-storage/index.ts +58 -0
  149. package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +532 -0
  150. package/plugins/acryl-router/src/core/middleware/cors/index.ts +163 -0
  151. package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +534 -0
  152. package/plugins/acryl-router/src/core/middleware/csrf/index.ts +150 -0
  153. package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +21 -0
  154. package/plugins/acryl-router/src/core/middleware/etag/digest.ts +95 -0
  155. package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +503 -0
  156. package/plugins/acryl-router/src/core/middleware/etag/index.ts +135 -0
  157. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +217 -0
  158. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.ts +278 -0
  159. package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +1135 -0
  160. package/plugins/acryl-router/src/core/middleware/jwk/index.ts +1 -0
  161. package/plugins/acryl-router/src/core/middleware/jwk/jwk.ts +195 -0
  162. package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +48 -0
  163. package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +802 -0
  164. package/plugins/acryl-router/src/core/middleware/jwt/index.ts +9 -0
  165. package/plugins/acryl-router/src/core/middleware/jwt/jwt.ts +189 -0
  166. package/plugins/acryl-router/src/core/middleware/language/index.test.ts +552 -0
  167. package/plugins/acryl-router/src/core/middleware/language/index.ts +12 -0
  168. package/plugins/acryl-router/src/core/middleware/language/language.ts +314 -0
  169. package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +204 -0
  170. package/plugins/acryl-router/src/core/middleware/logger/index.ts +95 -0
  171. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +292 -0
  172. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.ts +141 -0
  173. package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +221 -0
  174. package/plugins/acryl-router/src/core/middleware/method-override/index.ts +147 -0
  175. package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +37 -0
  176. package/plugins/acryl-router/src/core/middleware/powered-by/index.ts +35 -0
  177. package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +218 -0
  178. package/plugins/acryl-router/src/core/middleware/pretty-json/index.ts +67 -0
  179. package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +168 -0
  180. package/plugins/acryl-router/src/core/middleware/request-id/index.ts +7 -0
  181. package/plugins/acryl-router/src/core/middleware/request-id/request-id.ts +59 -0
  182. package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +671 -0
  183. package/plugins/acryl-router/src/core/middleware/secure-headers/index.ts +8 -0
  184. package/plugins/acryl-router/src/core/middleware/secure-headers/permissions-policy.ts +101 -0
  185. package/plugins/acryl-router/src/core/middleware/secure-headers/secure-headers.ts +340 -0
  186. package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +450 -0
  187. package/plugins/acryl-router/src/core/middleware/serve-static/index.ts +135 -0
  188. package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +50 -0
  189. package/plugins/acryl-router/src/core/middleware/serve-static/path.ts +25 -0
  190. package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +70 -0
  191. package/plugins/acryl-router/src/core/middleware/timeout/index.ts +58 -0
  192. package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +310 -0
  193. package/plugins/acryl-router/src/core/middleware/timing/index.ts +8 -0
  194. package/plugins/acryl-router/src/core/middleware/timing/timing.ts +256 -0
  195. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +381 -0
  196. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.ts +156 -0
  197. package/plugins/acryl-router/src/core/request/constants.ts +1 -0
  198. package/plugins/acryl-router/src/core/request.test.ts +751 -0
  199. package/plugins/acryl-router/src/core/request.ts +502 -0
  200. package/plugins/acryl-router/src/core/router/common.case.test.ts +1132 -0
  201. package/plugins/acryl-router/src/core/router/linear-router/index.ts +6 -0
  202. package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +80 -0
  203. package/plugins/acryl-router/src/core/router/linear-router/router.ts +152 -0
  204. package/plugins/acryl-router/src/core/router/pattern-router/index.ts +6 -0
  205. package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +46 -0
  206. package/plugins/acryl-router/src/core/router/pattern-router/router.ts +54 -0
  207. package/plugins/acryl-router/src/core/router/reg-exp-router/index.ts +7 -0
  208. package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.ts +33 -0
  209. package/plugins/acryl-router/src/core/router/reg-exp-router/node.ts +170 -0
  210. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +154 -0
  211. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.ts +165 -0
  212. package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +265 -0
  213. package/plugins/acryl-router/src/core/router/reg-exp-router/router.ts +174 -0
  214. package/plugins/acryl-router/src/core/router/reg-exp-router/trie.ts +84 -0
  215. package/plugins/acryl-router/src/core/router/smart-router/index.ts +6 -0
  216. package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +13 -0
  217. package/plugins/acryl-router/src/core/router/smart-router/router.ts +70 -0
  218. package/plugins/acryl-router/src/core/router/trie-router/index.ts +6 -0
  219. package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +896 -0
  220. package/plugins/acryl-router/src/core/router/trie-router/node.ts +221 -0
  221. package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +8 -0
  222. package/plugins/acryl-router/src/core/router/trie-router/router.ts +18 -0
  223. package/plugins/acryl-router/src/core/router/utils.ts +1 -0
  224. package/plugins/acryl-router/src/core/router.ts +103 -0
  225. package/plugins/acryl-router/src/core/types.test.ts +4134 -0
  226. package/plugins/acryl-router/src/core/types.ts +2782 -0
  227. package/plugins/acryl-router/src/core/utils/accept.test.ts +299 -0
  228. package/plugins/acryl-router/src/core/utils/accept.ts +268 -0
  229. package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +56 -0
  230. package/plugins/acryl-router/src/core/utils/basic-auth.ts +26 -0
  231. package/plugins/acryl-router/src/core/utils/body.test.ts +496 -0
  232. package/plugins/acryl-router/src/core/utils/body.ts +263 -0
  233. package/plugins/acryl-router/src/core/utils/buffer.test.ts +147 -0
  234. package/plugins/acryl-router/src/core/utils/buffer.ts +117 -0
  235. package/plugins/acryl-router/src/core/utils/color.test.ts +23 -0
  236. package/plugins/acryl-router/src/core/utils/color.ts +56 -0
  237. package/plugins/acryl-router/src/core/utils/compress.ts +10 -0
  238. package/plugins/acryl-router/src/core/utils/concurrent.test.ts +69 -0
  239. package/plugins/acryl-router/src/core/utils/concurrent.ts +55 -0
  240. package/plugins/acryl-router/src/core/utils/constants.ts +4 -0
  241. package/plugins/acryl-router/src/core/utils/cookie.test.ts +460 -0
  242. package/plugins/acryl-router/src/core/utils/cookie.ts +281 -0
  243. package/plugins/acryl-router/src/core/utils/crypto.test.ts +43 -0
  244. package/plugins/acryl-router/src/core/utils/crypto.ts +58 -0
  245. package/plugins/acryl-router/src/core/utils/encode.test.ts +61 -0
  246. package/plugins/acryl-router/src/core/utils/encode.ts +34 -0
  247. package/plugins/acryl-router/src/core/utils/filepath.test.ts +78 -0
  248. package/plugins/acryl-router/src/core/utils/filepath.ts +60 -0
  249. package/plugins/acryl-router/src/core/utils/handler.ts +15 -0
  250. package/plugins/acryl-router/src/core/utils/headers.ts +355 -0
  251. package/plugins/acryl-router/src/core/utils/html.test.ts +16 -0
  252. package/plugins/acryl-router/src/core/utils/html.ts +182 -0
  253. package/plugins/acryl-router/src/core/utils/http-status.ts +72 -0
  254. package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +149 -0
  255. package/plugins/acryl-router/src/core/utils/ipaddr.ts +374 -0
  256. package/plugins/acryl-router/src/core/utils/jwt/index.ts +7 -0
  257. package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +86 -0
  258. package/plugins/acryl-router/src/core/utils/jwt/jwa.ts +38 -0
  259. package/plugins/acryl-router/src/core/utils/jwt/jws.ts +235 -0
  260. package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +1788 -0
  261. package/plugins/acryl-router/src/core/utils/jwt/jwt.ts +292 -0
  262. package/plugins/acryl-router/src/core/utils/jwt/types.ts +157 -0
  263. package/plugins/acryl-router/src/core/utils/jwt/utf8.ts +7 -0
  264. package/plugins/acryl-router/src/core/utils/mime.test.ts +64 -0
  265. package/plugins/acryl-router/src/core/utils/mime.ts +94 -0
  266. package/plugins/acryl-router/src/core/utils/stream.test.ts +212 -0
  267. package/plugins/acryl-router/src/core/utils/stream.ts +107 -0
  268. package/plugins/acryl-router/src/core/utils/types.test.ts +295 -0
  269. package/plugins/acryl-router/src/core/utils/types.ts +123 -0
  270. package/plugins/acryl-router/src/core/utils/url.test.ts +384 -0
  271. package/plugins/acryl-router/src/core/utils/url.ts +337 -0
  272. package/plugins/acryl-router/src/core/validator/index.ts +8 -0
  273. package/plugins/acryl-router/src/core/validator/utils.test.ts +82 -0
  274. package/plugins/acryl-router/src/core/validator/utils.ts +69 -0
  275. package/plugins/acryl-router/src/core/validator/validator.test.ts +1488 -0
  276. package/plugins/acryl-router/src/core/validator/validator.ts +185 -0
  277. package/plugins/acryl-router/src/helpers/index.ts +13 -3
  278. package/plugins/acryl-router/src/helpers/schema-validator.ts +35 -0
  279. package/plugins/acryl-router/src/index.ts +6 -6
  280. package/plugins/acryl-router/src/middleware/index.ts +25 -6
  281. package/plugins/acryl-router/src/middleware/loopback-only.ts +53 -14
  282. package/plugins/acryl-router/src/service.ts +262 -64
  283. package/plugins/acryl-router/tests/docs-examples.spec.ts +152 -0
  284. package/plugins/acryl-router/tests/live-install.spec.ts +2 -2
  285. package/plugins/acryl-router/tests/loader.spec.ts +3 -2
  286. package/plugins/acryl-router/tests/loopback-only.spec.ts +80 -0
  287. package/plugins/acryl-router/tests/service.spec.ts +281 -66
  288. package/plugins/acryl-router/tests/shadow-state.spec.ts +79 -0
  289. package/plugins/acryl-router/upstream.json +2 -2
  290. package/plugins/acryl-router-http/plugin.json +2 -1
  291. package/plugins/acryl-router-http/src/bridge.ts +3 -1
  292. package/plugins/acryl-router-http/tests/adapter.spec.ts +16 -12
  293. package/plugins/acryl-router-http/tests/surfaces.spec.ts +12 -10
  294. package/plugins/acryl-router-http/upstream.json +3 -3
  295. package/plugins/acryl-state/NOTICE +30 -0
  296. package/plugins/acryl-state/plugin.json +1 -1
  297. package/plugins/acryl-state/src/cell.ts +1 -1
  298. package/plugins/acryl-state/src/service.ts +33 -15
  299. package/plugins/acryl-state/upstream.json +6 -4
  300. package/plugins/builder/script.ts +4 -7
  301. package/plugins/extensions/index.ts +6 -5
  302. package/plugins/extensions/resolve.ts +28 -3
  303. package/plugins/openai/prompt.ts +9 -7
  304. package/plugins/tsconfig.json +5 -4
  305. package/shared/AGENTS.core.md +54 -0
  306. package/templates/agent/AGENTS.md +6 -0
  307. package/templates/agent/README.md +2 -2
  308. package/templates/agent/blend.yaml +1 -1
  309. package/templates/agent/src/routes.ts +37 -27
  310. package/templates/agent/test/model.test.ts +2 -1
  311. package/templates/agent/tsconfig.json +8 -5
  312. package/templates/blank/AGENTS.md +5 -0
  313. package/templates/blank/README.md +1 -1
  314. package/templates/blank/src/routes.ts +6 -4
  315. package/templates/blank/tsconfig.json +2 -4
  316. package/templates/todo/AGENTS.md +5 -0
  317. package/templates/todo/src/page.ts +1 -1
  318. package/templates/todo/src/todo.ts +58 -29
  319. package/templates/todo/tsconfig.json +2 -4
  320. package/templates/todo/upstream.json +2 -2
  321. package/plugins/acryl-router/src/compose.ts +0 -21
  322. package/plugins/acryl-router/src/context.ts +0 -59
  323. package/plugins/acryl-router/src/helpers/read-body.ts +0 -59
  324. package/plugins/acryl-router/src/helpers/sse.ts +0 -83
  325. package/plugins/acryl-router/src/helpers/validate.ts +0 -44
  326. package/plugins/acryl-router/src/http-error.ts +0 -21
  327. package/plugins/acryl-router/src/middleware/body-limit.ts +0 -25
  328. package/plugins/acryl-router/src/middleware/cors.ts +0 -46
  329. package/plugins/acryl-router/src/middleware/logger.ts +0 -21
  330. package/plugins/acryl-router/src/middleware/request-id.ts +0 -24
  331. package/plugins/acryl-router/src/middleware/secure-headers.ts +0 -23
  332. package/plugins/acryl-router/src/pattern.ts +0 -89
  333. package/plugins/acryl-router/src/registrar.ts +0 -90
  334. package/plugins/acryl-router/src/table.ts +0 -156
  335. package/plugins/acryl-router/src/types.ts +0 -73
  336. package/plugins/acryl-router/src/util.ts +0 -23
  337. package/plugins/acryl-router/tests/api.spec.ts +0 -223
  338. package/plugins/acryl-router/tests/conformance.ts +0 -170
  339. package/plugins/acryl-router/tests/helpers.spec.ts +0 -172
  340. package/plugins/acryl-router/tests/middleware.spec.ts +0 -183
  341. package/plugins/acryl-router/tests/table.spec.ts +0 -47
@@ -0,0 +1,797 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ // Define prototype for lightweight pseudo Request object
3
+
4
+ import type { IncomingMessage } from 'node:http'
5
+ import { Http2ServerRequest } from 'node:http2'
6
+ import { Readable } from 'node:stream'
7
+ import type { ReadableStreamDefaultReader } from 'node:stream/web'
8
+ import type { TLSSocket } from 'node:tls'
9
+ import { RequestError } from './error.ts'
10
+ import { newHeadersFromIncoming } from './headers.ts'
11
+ import type { GlobalHeaders } from './headers.ts'
12
+ import { buildUrl } from './url.ts'
13
+
14
+ export { RequestError }
15
+
16
+ export const toRequestError = (e: unknown): RequestError => {
17
+ if (e instanceof RequestError) {
18
+ return e
19
+ }
20
+ return new RequestError((e as Error).message, { cause: e })
21
+ }
22
+
23
+ export const GlobalRequest = global.Request
24
+ export class Request extends GlobalRequest {
25
+ constructor(input: string | Request, options?: RequestInit) {
26
+ if (typeof input === 'object' && getRequestCache in input) {
27
+ // Match native Request behavior:
28
+ // constructing from a consumed Request is allowed only when init.body is non-null.
29
+ const hasReplacementBody =
30
+ options !== undefined && 'body' in options && (options as RequestInit).body != null
31
+ if ((input as any)[bodyConsumedDirectlyKey] && !hasReplacementBody) {
32
+ throw new TypeError(
33
+ 'Cannot construct a Request with a Request object that has already been used.'
34
+ )
35
+ }
36
+ input = (input as any)[getRequestCache]()
37
+ }
38
+ // Check if body is ReadableStream like. This makes it compatbile with ReadableStream polyfills.
39
+ if (typeof (options?.body as ReadableStream)?.getReader !== 'undefined') {
40
+ // Half duplex mode is required when request body is a stream.
41
+ // If already set, do nothing since a Request object was passed to the options or explicitly set by the user.
42
+ ;(options as any).duplex ??= 'half'
43
+ }
44
+ super(input, options)
45
+ }
46
+ }
47
+
48
+ export type IncomingMessageWithWrapBodyStream = IncomingMessage & { [wrapBodyStream]: boolean }
49
+ export const wrapBodyStream = Symbol('wrapBodyStream')
50
+
51
+ // Encodings whose decode → re-encode round-trip is byte-exact for any input.
52
+ // utf8/utf16le replace invalid sequences with U+FFFD and ascii masks the high
53
+ // bit of every byte, so a body decoded through them cannot be reconstructed
54
+ // reliably — recovery is refused for those instead of returning corrupt bytes.
55
+ const byteExactEncodings = new Set(['latin1', 'binary', 'hex', 'base64', 'base64url'])
56
+
57
+ const isByteExactEncoding = (encoding: BufferEncoding | null): boolean =>
58
+ encoding === null || byteExactEncodings.has(encoding)
59
+
60
+ const bodyBufferedBeforeDisconnectKey = Symbol('bodyBufferedBeforeDisconnect')
61
+ const bodyBufferedLengthBeforeDisconnectKey = Symbol('bodyBufferedLengthBeforeDisconnect')
62
+
63
+ type IncomingWithBodyRecovery = IncomingMessage & {
64
+ [bodyBufferedBeforeDisconnectKey]?: Buffer | Error
65
+ [bodyBufferedLengthBeforeDisconnectKey]?: number
66
+ }
67
+
68
+ // When setEncoding() was called on the stream, chunks arrive as strings in
69
+ // that encoding — decode with it so the original bytes are reconstructed.
70
+ const toBufferChunk = (chunk: Buffer | string, encoding: BufferEncoding | null): Buffer =>
71
+ Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding ?? 'utf8')
72
+
73
+ const isRecoverableDisconnectedIncoming = (
74
+ incoming: IncomingMessage | Http2ServerRequest
75
+ ): incoming is IncomingMessage =>
76
+ !(incoming instanceof Http2ServerRequest) &&
77
+ !!incoming.complete &&
78
+ !!incoming.readableAborted &&
79
+ typeof incoming.read === 'function' &&
80
+ isByteExactEncoding(incoming.readableEncoding)
81
+
82
+ // Remember how much complete HTTP/1 body data remained buffered when the
83
+ // disconnect was observed. A later raw read can drain a destroyed stream
84
+ // without updating readableDidRead, so the length snapshot is needed to keep
85
+ // the Fetch-style body from silently resolving with only the remainder.
86
+ export const recordBodyBufferedBeforeDisconnect = (
87
+ incoming: IncomingMessage | Http2ServerRequest
88
+ ): void => {
89
+ if (incoming.readableDidRead || !isRecoverableDisconnectedIncoming(incoming)) {
90
+ return
91
+ }
92
+
93
+ const incomingWithRecovery = incoming as IncomingWithBodyRecovery
94
+ incomingWithRecovery[bodyBufferedLengthBeforeDisconnectKey] ??= incoming.readableLength
95
+ }
96
+
97
+ // A client may close the connection after Node has parsed the complete HTTP/1
98
+ // request but before the application starts reading it. In that state the
99
+ // IncomingMessage is disturbed/aborted, while the untouched body is still in
100
+ // its internal readable buffer — recover it instead of failing the read.
101
+ //
102
+ // HTTP/2 is excluded deliberately: an RST_STREAM discards the buffered request
103
+ // data at the protocol layer, and Http2ServerRequest#complete is also true for
104
+ // aborted/destroyed streams, so it cannot be trusted as a "fully received"
105
+ // signal.
106
+ //
107
+ // Returns the buffered body, an Error the read must fail with, or undefined
108
+ // when the stream is not in the recoverable state. The result is cached on the
109
+ // incoming object so every read path observes the same outcome.
110
+ const readBodyBufferedBeforeDisconnect = (
111
+ incoming: IncomingMessage | Http2ServerRequest,
112
+ chunks?: Buffer[]
113
+ ): Buffer | Error | undefined => {
114
+ if ((incoming.readableDidRead && !chunks) || !isRecoverableDisconnectedIncoming(incoming)) {
115
+ return undefined
116
+ }
117
+
118
+ const incomingWithRecovery = incoming as IncomingWithBodyRecovery
119
+ if (incomingWithRecovery[bodyBufferedBeforeDisconnectKey] !== undefined) {
120
+ return incomingWithRecovery[bodyBufferedBeforeDisconnectKey]
121
+ }
122
+
123
+ let result: Buffer | Error
124
+ const errored = incoming.errored
125
+ if (errored && (errored as NodeJS.ErrnoException).code !== 'ECONNRESET') {
126
+ // The stream was destroyed with an application-provided error (e.g. a
127
+ // body-size guard calling incoming.destroy(err)). Node's own teardown of a
128
+ // disconnected client uses ECONNRESET errors, which must still recover the
129
+ // body — anything else is surfaced to the reader instead of swallowed.
130
+ result = errored
131
+ } else if (
132
+ incomingWithRecovery[bodyBufferedLengthBeforeDisconnectKey] !== undefined &&
133
+ incoming.readableLength !== incomingWithRecovery[bodyBufferedLengthBeforeDisconnectKey]
134
+ ) {
135
+ result = newBodyUnusableError()
136
+ } else {
137
+ const bodyChunks = chunks ?? []
138
+ const chunk = incoming.read() as Buffer | string | null
139
+ if (chunk !== null) {
140
+ bodyChunks.push(toBufferChunk(chunk, incoming.readableEncoding))
141
+ }
142
+ const buffer = bodyChunks.length === 1 ? bodyChunks[0] : Buffer.concat(bodyChunks)
143
+ result = buffer
144
+ // Validate only canonical digit values: the HTTP parser guarantees this
145
+ // form on real connections, and lenient coercion of synthetic inputs
146
+ // (e.g. '0x10') would validate against a length the header never meant.
147
+ const contentLength = incoming.headers['content-length']
148
+ if (typeof contentLength === 'string' && /^\d+$/.test(contentLength)) {
149
+ const expectedLength = Number(contentLength)
150
+ if (Number.isSafeInteger(expectedLength) && buffer.length !== expectedLength) {
151
+ result = newBodyUnusableError()
152
+ }
153
+ }
154
+ }
155
+ incomingWithRecovery[bodyBufferedBeforeDisconnectKey] = result
156
+ return result
157
+ }
158
+
159
+ const enqueueBufferedBody = (
160
+ controller: ReadableStreamDefaultController,
161
+ buffered: Buffer | Error
162
+ ): void => {
163
+ if (buffered instanceof Error) {
164
+ controller.error(buffered)
165
+ return
166
+ }
167
+ if (buffered.length > 0) {
168
+ controller.enqueue(buffered)
169
+ }
170
+ controller.close()
171
+ }
172
+ const newRequestFromIncoming = (
173
+ method: string,
174
+ url: string,
175
+ headers: GlobalHeaders,
176
+ incoming: IncomingMessage | Http2ServerRequest,
177
+ abortController: AbortController
178
+ ): Request => {
179
+ const init = {
180
+ method: method,
181
+ headers,
182
+ signal: abortController.signal,
183
+ } as RequestInit
184
+
185
+ if (method === 'TRACE') {
186
+ init.method = 'GET'
187
+ const req = new Request(url, init)
188
+ Object.defineProperty(req, 'method', {
189
+ get() {
190
+ return 'TRACE'
191
+ },
192
+ })
193
+ return req
194
+ }
195
+
196
+ if (!(method === 'GET' || method === 'HEAD')) {
197
+ if ('rawBody' in incoming && incoming.rawBody instanceof Buffer) {
198
+ // In some environments (e.g. firebase functions), the body is already consumed.
199
+ // So we need to re-read the request body from `incoming.rawBody` if available.
200
+ init.body = new ReadableStream({
201
+ start(controller) {
202
+ controller.enqueue(incoming.rawBody)
203
+ controller.close()
204
+ },
205
+ })
206
+ } else if ((incoming as IncomingMessageWithWrapBodyStream)[wrapBodyStream]) {
207
+ let reader: ReadableStreamDefaultReader<Uint8Array> | undefined
208
+ init.body = new ReadableStream({
209
+ async pull(controller) {
210
+ try {
211
+ if (!reader) {
212
+ const buffered = readBodyBufferedBeforeDisconnect(incoming)
213
+ if (buffered !== undefined) {
214
+ enqueueBufferedBody(controller, buffered)
215
+ return
216
+ }
217
+ }
218
+ reader ||= Readable.toWeb(incoming).getReader()
219
+ const { done, value } = await reader.read()
220
+ if (done) {
221
+ controller.close()
222
+ } else {
223
+ controller.enqueue(value)
224
+ }
225
+ } catch (error) {
226
+ controller.error(error)
227
+ }
228
+ },
229
+ })
230
+ } else {
231
+ const buffered = readBodyBufferedBeforeDisconnect(incoming)
232
+ if (buffered !== undefined) {
233
+ init.body = new ReadableStream({
234
+ start(controller) {
235
+ enqueueBufferedBody(controller, buffered)
236
+ },
237
+ })
238
+ } else {
239
+ // lazy-consume request body
240
+ init.body = Readable.toWeb(incoming) as ReadableStream<Uint8Array>
241
+ }
242
+ }
243
+ }
244
+
245
+ return new Request(url, init)
246
+ }
247
+
248
+ const getRequestCache = Symbol('getRequestCache')
249
+ const requestCache = Symbol('requestCache')
250
+ const incomingKey = Symbol('incomingKey')
251
+ const urlKey = Symbol('urlKey')
252
+ const methodKey = Symbol('methodKey')
253
+ const headersKey = Symbol('headersKey')
254
+ export const abortControllerKey = Symbol('abortControllerKey')
255
+ export const getAbortController = Symbol('getAbortController')
256
+ export const abortRequest = Symbol('abortRequest')
257
+ const bodyBufferKey = Symbol('bodyBuffer')
258
+ const bodyReadPromiseKey = Symbol('bodyReadPromise')
259
+ const bodyConsumedDirectlyKey = Symbol('bodyConsumedDirectly')
260
+ const bodyLockReaderKey = Symbol('bodyLockReader')
261
+ const abortReasonKey = Symbol('abortReason')
262
+
263
+ const newBodyUnusableError = (): TypeError => {
264
+ return new TypeError('Body is unusable')
265
+ }
266
+
267
+ const rejectBodyUnusable = (): Promise<never> => {
268
+ return Promise.reject(newBodyUnusableError())
269
+ }
270
+
271
+ const textDecoder = new TextDecoder()
272
+
273
+ const consumeBodyDirectOnce = (
274
+ request: Record<string | symbol, any>
275
+ ): Promise<never> | undefined => {
276
+ if (request[bodyConsumedDirectlyKey]) {
277
+ return rejectBodyUnusable()
278
+ }
279
+ request[bodyConsumedDirectlyKey] = true
280
+ return undefined
281
+ }
282
+
283
+ const toArrayBuffer = (buf: Buffer): ArrayBuffer => {
284
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer
285
+ }
286
+
287
+ const contentType = (request: Record<string | symbol, any>): string => {
288
+ return (
289
+ (request[headersKey] ||= newHeadersFromIncoming(request[incomingKey])).get('content-type') || ''
290
+ )
291
+ }
292
+
293
+ type DirectBodyReadMethod = 'text' | 'arrayBuffer' | 'blob'
294
+
295
+ const methodTokenRegExp = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/
296
+
297
+ const normalizeIncomingMethod = (method: unknown): string => {
298
+ if (typeof method !== 'string' || method.length === 0) {
299
+ return 'GET'
300
+ }
301
+
302
+ // fast path for already-uppercase common methods from Node.js.
303
+ switch (method) {
304
+ case 'DELETE':
305
+ case 'GET':
306
+ case 'HEAD':
307
+ case 'OPTIONS':
308
+ case 'PATCH':
309
+ case 'POST':
310
+ case 'PUT':
311
+ case 'QUERY':
312
+ return method
313
+ }
314
+
315
+ const upper = method.toUpperCase()
316
+ // Fetch only normalizes these methods for backwards compatibility.
317
+ // HTTP methods are otherwise case-sensitive, so methods not in this list
318
+ // (including `query`) must retain their original casing.
319
+ switch (upper) {
320
+ case 'DELETE':
321
+ case 'GET':
322
+ case 'HEAD':
323
+ case 'OPTIONS':
324
+ case 'POST':
325
+ case 'PUT':
326
+ return upper
327
+ default:
328
+ return method
329
+ }
330
+ }
331
+
332
+ const validateDirectReadMethod = (method: string): TypeError | undefined => {
333
+ if (!methodTokenRegExp.test(method)) {
334
+ return new TypeError(`'${method}' is not a valid HTTP method.`)
335
+ }
336
+ // Keep TRACE workaround behavior, but preserve native rejection for other
337
+ // forbidden methods when using the direct-read fast path.
338
+ // Only exact upper-case TRACE is treated as the existing workaround target.
339
+ const normalized = method.toUpperCase()
340
+ if (
341
+ normalized === 'CONNECT' ||
342
+ normalized === 'TRACK' ||
343
+ (normalized === 'TRACE' && method !== 'TRACE')
344
+ ) {
345
+ return new TypeError(`'${method}' HTTP method is unsupported.`)
346
+ }
347
+ return undefined
348
+ }
349
+
350
+ const readBodyWithFastPath = <T>(
351
+ request: Record<string | symbol, any>,
352
+ method: DirectBodyReadMethod,
353
+ fromBuffer: (buf: Buffer, request: Record<string | symbol, any>) => T | Promise<T>
354
+ ): Promise<T> => {
355
+ if (request[bodyConsumedDirectlyKey]) {
356
+ return rejectBodyUnusable()
357
+ }
358
+
359
+ const methodName = request.method as string
360
+ if (methodName === 'GET' || methodName === 'HEAD') {
361
+ return request[getRequestCache]()[method]()
362
+ }
363
+
364
+ const methodValidationError = validateDirectReadMethod(methodName)
365
+ if (methodValidationError) {
366
+ return Promise.reject(methodValidationError)
367
+ }
368
+
369
+ if (request[requestCache]) {
370
+ // Keep TRACE direct-read behavior stable even if non-body properties
371
+ // created requestCache earlier (e.g. signal access).
372
+ if (methodName !== 'TRACE') {
373
+ const cachedRequest = request[requestCache] as Request
374
+ return cachedRequest[method]() as Promise<T>
375
+ }
376
+ }
377
+
378
+ const alreadyUsedError = consumeBodyDirectOnce(request)
379
+ if (alreadyUsedError) {
380
+ return alreadyUsedError
381
+ }
382
+
383
+ const raw = readRawBodyIfAvailable(request)
384
+ if (raw) {
385
+ const result = Promise.resolve(fromBuffer(raw, request))
386
+ request[bodyBufferKey] = undefined
387
+ return result
388
+ }
389
+
390
+ return readBodyDirect(request).then((buf) => {
391
+ const result = fromBuffer(buf, request)
392
+ request[bodyBufferKey] = undefined
393
+ return result
394
+ })
395
+ }
396
+
397
+ const readRawBodyIfAvailable = (request: Record<string | symbol, any>): Buffer | undefined => {
398
+ const incoming = request[incomingKey] as IncomingMessage | Http2ServerRequest
399
+ if ('rawBody' in incoming && (incoming as any).rawBody instanceof Buffer) {
400
+ return (incoming as any).rawBody as Buffer
401
+ }
402
+ return undefined
403
+ }
404
+
405
+ // The error a body read fails with after an abort: the stream's own error if
406
+ // it has one, otherwise the abort reason recorded on the request, otherwise a
407
+ // generic disconnect error.
408
+ const normalizeAbortError = (
409
+ request: Record<string | symbol, any>,
410
+ incoming: IncomingMessage | Http2ServerRequest
411
+ ): Error => {
412
+ if (incoming.errored) {
413
+ return incoming.errored
414
+ }
415
+ const reason = request[abortReasonKey]
416
+ if (reason !== undefined) {
417
+ return reason instanceof Error ? reason : new Error(String(reason))
418
+ }
419
+ return new Error('Client connection prematurely closed.')
420
+ }
421
+
422
+ // Read body directly from the IncomingMessage stream, bypassing Request object creation.
423
+ // Precondition: the caller (listener.ts) must ensure that the IncomingMessage stream is
424
+ // properly cleaned up (e.g. via incoming.resume()) when the response ends or the connection
425
+ // closes. This function does not call incoming.destroy() on abort.
426
+ const readBodyDirect = (request: Record<string | symbol, any>): Promise<Buffer> => {
427
+ if (request[bodyBufferKey]) {
428
+ return Promise.resolve(request[bodyBufferKey] as Buffer)
429
+ }
430
+ if (request[bodyReadPromiseKey]) {
431
+ return request[bodyReadPromiseKey] as Promise<Buffer>
432
+ }
433
+
434
+ const incoming = request[incomingKey] as IncomingMessage | Http2ServerRequest
435
+ if (incoming.readableDidRead) {
436
+ return rejectBodyUnusable()
437
+ }
438
+
439
+ const buffered = readBodyBufferedBeforeDisconnect(incoming)
440
+ if (buffered !== undefined) {
441
+ if (buffered instanceof Error) {
442
+ return Promise.reject(buffered)
443
+ }
444
+ request[bodyBufferKey] = buffered
445
+ return Promise.resolve(buffered)
446
+ }
447
+
448
+ const promise = new Promise<Buffer>((resolve, reject) => {
449
+ const chunks: Buffer[] = []
450
+ let settled = false
451
+
452
+ const finish = (callback: () => void) => {
453
+ if (settled) {
454
+ return
455
+ }
456
+ settled = true
457
+ cleanup()
458
+ callback()
459
+ }
460
+
461
+ // readBodyDirect started while the stream was untouched, so it owns every
462
+ // chunk emitted from that point onward. If HTTP/1 parsing completed before
463
+ // the connection closed, combine those chunks with anything still buffered
464
+ // and treat the body as complete. Transport-level ECONNRESET is recoverable;
465
+ // application-provided stream errors are not.
466
+ const recoverCompleteBodyAfterDisconnect = (error?: unknown): boolean => {
467
+ const streamError = incoming.errored ?? error
468
+ if (
469
+ !isRecoverableDisconnectedIncoming(incoming) ||
470
+ (streamError && (streamError as NodeJS.ErrnoException).code !== 'ECONNRESET')
471
+ ) {
472
+ return false
473
+ }
474
+
475
+ finish(() => {
476
+ const recovered = readBodyBufferedBeforeDisconnect(incoming, chunks)
477
+ if (recovered instanceof Error) {
478
+ reject(recovered)
479
+ } else if (recovered === undefined) {
480
+ reject(error ?? normalizeAbortError(request, incoming))
481
+ } else {
482
+ request[bodyBufferKey] = recovered
483
+ resolve(recovered)
484
+ }
485
+ })
486
+ return true
487
+ }
488
+
489
+ const onData = (chunk: Buffer | string) => {
490
+ chunks.push(toBufferChunk(chunk, incoming.readableEncoding))
491
+ }
492
+ const onEnd = () => {
493
+ finish(() => {
494
+ const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks)
495
+ request[bodyBufferKey] = buffer
496
+ resolve(buffer)
497
+ })
498
+ }
499
+ const onError = (error: unknown) => {
500
+ if (recoverCompleteBodyAfterDisconnect(error)) {
501
+ return
502
+ }
503
+ finish(() => {
504
+ reject(error)
505
+ })
506
+ }
507
+ const onClose = () => {
508
+ if (incoming.readableEnded) {
509
+ onEnd()
510
+ return
511
+ }
512
+ if (recoverCompleteBodyAfterDisconnect()) {
513
+ return
514
+ }
515
+ finish(() => {
516
+ reject(normalizeAbortError(request, incoming))
517
+ })
518
+ }
519
+ const cleanup = () => {
520
+ incoming.off('data', onData)
521
+ incoming.off('end', onEnd)
522
+ incoming.off('error', onError)
523
+ incoming.off('close', onClose)
524
+ request[bodyReadPromiseKey] = undefined
525
+ }
526
+
527
+ incoming.on('data', onData)
528
+ incoming.on('end', onEnd)
529
+ incoming.on('error', onError)
530
+ incoming.on('close', onClose)
531
+
532
+ // If the stream has already settled before listeners were attached,
533
+ // no further events will fire, so resolve/reject from the current state.
534
+ queueMicrotask(() => {
535
+ if (settled) {
536
+ return
537
+ }
538
+ if (incoming.readableEnded) {
539
+ onEnd()
540
+ } else if (incoming.errored) {
541
+ onError(incoming.errored)
542
+ } else if (incoming.destroyed) {
543
+ onClose()
544
+ }
545
+ })
546
+ })
547
+
548
+ request[bodyReadPromiseKey] = promise
549
+ return promise
550
+ }
551
+
552
+ const requestPrototype: Record<string | symbol, any> = {
553
+ get method() {
554
+ return this[methodKey]
555
+ },
556
+
557
+ get url() {
558
+ return this[urlKey]
559
+ },
560
+
561
+ get headers() {
562
+ return (this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]))
563
+ },
564
+
565
+ [abortRequest](reason: unknown) {
566
+ if (this[abortReasonKey] === undefined) {
567
+ this[abortReasonKey] = reason
568
+ }
569
+ const abortController = this[abortControllerKey] as AbortController | undefined
570
+ if (abortController && !abortController.signal.aborted) {
571
+ abortController.abort(reason)
572
+ }
573
+ },
574
+
575
+ [getAbortController]() {
576
+ this[abortControllerKey] ||= new AbortController()
577
+ if (this[abortReasonKey] !== undefined && !this[abortControllerKey].signal.aborted) {
578
+ this[abortControllerKey].abort(this[abortReasonKey])
579
+ }
580
+ return this[abortControllerKey]
581
+ },
582
+
583
+ [getRequestCache]() {
584
+ const abortController = this[getAbortController]()
585
+ if (this[requestCache]) {
586
+ return this[requestCache]
587
+ }
588
+
589
+ const method = this.method
590
+
591
+ // If body was already consumed directly, create a minimal Request with an empty body
592
+ // to avoid holding the body buffer in memory via ReadableStream re-wrapping.
593
+ if (this[bodyConsumedDirectlyKey] && !(method === 'GET' || method === 'HEAD')) {
594
+ this[bodyBufferKey] = undefined
595
+ const init = {
596
+ method: method === 'TRACE' ? 'GET' : method,
597
+ headers: this.headers,
598
+ signal: abortController.signal,
599
+ } as RequestInit
600
+ if (method !== 'TRACE') {
601
+ init.body = new ReadableStream({
602
+ start(c) {
603
+ c.close()
604
+ },
605
+ })
606
+ ;(init as any).duplex = 'half'
607
+ }
608
+ const req = new Request(this[urlKey], init)
609
+ if (method === 'TRACE') {
610
+ Object.defineProperty(req, 'method', {
611
+ get() {
612
+ return 'TRACE'
613
+ },
614
+ })
615
+ }
616
+ return (this[requestCache] = req)
617
+ }
618
+
619
+ return (this[requestCache] = newRequestFromIncoming(
620
+ this.method,
621
+ this[urlKey],
622
+ this.headers,
623
+ this[incomingKey],
624
+ abortController
625
+ ))
626
+ },
627
+
628
+ get body() {
629
+ if (!this[bodyConsumedDirectlyKey]) {
630
+ return this[getRequestCache]().body
631
+ }
632
+ const request = this[getRequestCache]()
633
+ if (!this[bodyLockReaderKey] && request.body) {
634
+ // Web standard requires body.locked === true when bodyUsed === true.
635
+ // After direct consumption (text/json/arrayBuffer/blob), getRequestCache() returns
636
+ // a Request with an empty ReadableStream body. We lock it here so that
637
+ // body.locked reflects the consumed state correctly.
638
+ this[bodyLockReaderKey] = request.body.getReader()
639
+ }
640
+ return request.body
641
+ },
642
+
643
+ get bodyUsed() {
644
+ if (this[bodyConsumedDirectlyKey]) {
645
+ return true
646
+ }
647
+ if (this[requestCache]) {
648
+ return this[requestCache].bodyUsed
649
+ }
650
+ return false
651
+ },
652
+ }
653
+
654
+ Object.defineProperty(requestPrototype, 'signal', {
655
+ get() {
656
+ return this[getAbortController]().signal
657
+ },
658
+ })
659
+ ;[
660
+ 'cache',
661
+ 'credentials',
662
+ 'destination',
663
+ 'integrity',
664
+ 'mode',
665
+ 'redirect',
666
+ 'referrer',
667
+ 'referrerPolicy',
668
+ 'keepalive',
669
+ ].forEach((k) => {
670
+ Object.defineProperty(requestPrototype, k, {
671
+ get() {
672
+ return this[getRequestCache]()[k]
673
+ },
674
+ })
675
+ })
676
+ ;['clone', 'formData'].forEach((k) => {
677
+ Object.defineProperty(requestPrototype, k, {
678
+ value: function () {
679
+ if (this[bodyConsumedDirectlyKey]) {
680
+ if (k === 'clone') {
681
+ throw newBodyUnusableError()
682
+ }
683
+ return rejectBodyUnusable()
684
+ }
685
+ return this[getRequestCache]()[k]()
686
+ },
687
+ })
688
+ })
689
+
690
+ // Direct body reading for text/arrayBuffer/blob/json: bypass getRequestCache()
691
+ // → new AbortController() → newHeadersFromIncoming() → new Request(url, init)
692
+ // → Readable.toWeb() chain for common body parsing cases.
693
+ Object.defineProperty(requestPrototype, 'text', {
694
+ value: function (): Promise<string> {
695
+ return readBodyWithFastPath(this, 'text', (buf) => textDecoder.decode(buf))
696
+ },
697
+ })
698
+ Object.defineProperty(requestPrototype, 'arrayBuffer', {
699
+ value: function (): Promise<ArrayBuffer> {
700
+ return readBodyWithFastPath(this, 'arrayBuffer', (buf) => toArrayBuffer(buf))
701
+ },
702
+ })
703
+ Object.defineProperty(requestPrototype, 'blob', {
704
+ value: function (): Promise<Blob> {
705
+ return readBodyWithFastPath(this, 'blob', (buf, request) => {
706
+ const type = contentType(request)
707
+ const init = type ? { headers: { 'content-type': type } } : undefined
708
+ // Buffer is typed over ArrayBufferLike, but request bodies are always
709
+ // backed by an ArrayBuffer, so cast instead of copying the bytes
710
+ return new Response(buf as Uint8Array<ArrayBuffer>, init).blob()
711
+ })
712
+ },
713
+ })
714
+ // json() reuses text() fast path to keep body consumption logic centralized.
715
+ Object.defineProperty(requestPrototype, 'json', {
716
+ value: function (): Promise<any> {
717
+ if (this[bodyConsumedDirectlyKey]) {
718
+ return rejectBodyUnusable()
719
+ }
720
+ return this.text().then(JSON.parse)
721
+ },
722
+ })
723
+
724
+ Object.defineProperty(requestPrototype, Symbol.for('nodejs.util.inspect.custom'), {
725
+ value: function (depth: number, options: object, inspectFn: Function) {
726
+ const props: Record<string, unknown> = {
727
+ method: this.method,
728
+ url: this.url,
729
+ headers: this.headers,
730
+ nativeRequest: this[requestCache],
731
+ }
732
+
733
+ return `Request (lightweight) ${inspectFn(props, { ...options, depth: depth == null ? null : depth - 1 })}`
734
+ },
735
+ })
736
+
737
+ Object.setPrototypeOf(requestPrototype, Request.prototype)
738
+
739
+ export const newRequest = (
740
+ incoming: IncomingMessage | Http2ServerRequest,
741
+ defaultHostname?: string
742
+ ) => {
743
+ const req = Object.create(requestPrototype)
744
+ req[incomingKey] = incoming
745
+ req[methodKey] = normalizeIncomingMethod(incoming.method)
746
+
747
+ const incomingUrl = incoming.url || ''
748
+
749
+ // handle absolute URL in request.url
750
+ if (
751
+ incomingUrl[0] !== '/' && // short-circuit for performance. most requests are relative URL.
752
+ (incomingUrl.startsWith('http://') || incomingUrl.startsWith('https://'))
753
+ ) {
754
+ if (incoming instanceof Http2ServerRequest) {
755
+ throw new RequestError('Absolute URL for :path is not allowed in HTTP/2') // RFC 9113 8.3.1.
756
+ }
757
+
758
+ try {
759
+ const url = new URL(incomingUrl)
760
+ req[urlKey] = url.href
761
+ } catch (e) {
762
+ throw new RequestError('Invalid absolute URL', { cause: e })
763
+ }
764
+
765
+ return req
766
+ }
767
+
768
+ // Otherwise, relative URL
769
+ const host =
770
+ (incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host) ||
771
+ defaultHostname
772
+ if (!host) {
773
+ throw new RequestError('Missing host header')
774
+ }
775
+
776
+ let scheme: string
777
+ if (incoming instanceof Http2ServerRequest) {
778
+ scheme = incoming.scheme
779
+ if (!(scheme === 'http' || scheme === 'https')) {
780
+ throw new RequestError('Unsupported scheme')
781
+ }
782
+ } else {
783
+ scheme = incoming.socket && (incoming.socket as TLSSocket).encrypted ? 'https' : 'http'
784
+ }
785
+
786
+ try {
787
+ req[urlKey] = buildUrl(scheme, host, incomingUrl)
788
+ } catch (e) {
789
+ if (e instanceof RequestError) {
790
+ throw e
791
+ } else {
792
+ throw new RequestError('Invalid URL', { cause: e })
793
+ }
794
+ }
795
+
796
+ return req
797
+ }