acrylblends 0.6.2 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. package/README.md +5 -1
  2. package/docs/framework/README.md +3 -2
  3. package/docs/framework/UPSTREAM.md +1 -1
  4. package/docs/framework/cordis/acryl-in-a-cordis-app.md +6 -0
  5. package/docs/framework/getting-started/basic.md +1 -0
  6. package/docs/framework/getting-started/desktop.md +41 -0
  7. package/docs/framework/guides/jsx-dom.md +0 -3
  8. package/docs/framework/guides/jsx.md +24 -3
  9. package/docs/framework/helpers/css.md +0 -3
  10. package/docs/framework/helpers/ssg.md +3 -6
  11. package/docs/framework/middleware/builtin/jsx-renderer.md +0 -3
  12. package/docs/framework/upstream.json +3 -3
  13. package/lib/accepts.d.ts +1 -1
  14. package/lib/acryl-base.d.ts +1 -1
  15. package/lib/adapter.d.ts +1 -1
  16. package/lib/{app-BdrVHBlJ.d.ts → app-D8riatvv.d.ts} +1 -1
  17. package/lib/base-BpQy2Rk7.d.ts +783 -0
  18. package/lib/base-ddiVmiE7.js +721 -0
  19. package/lib/basic-auth.d.ts +1 -1
  20. package/lib/bearer-auth.d.ts +1 -1
  21. package/lib/body-limit.d.ts +1 -1
  22. package/lib/cache.d.ts +1 -1
  23. package/lib/cache.js +4 -4
  24. package/lib/{client-BHTr318g.js → client-DDLXwhBo.js} +2 -125
  25. package/lib/client.d.ts +2 -2
  26. package/lib/client.js +2 -1
  27. package/lib/combine.d.ts +1 -1
  28. package/lib/common-D8Z4JRCw.d.ts +46 -0
  29. package/lib/components-0o0CBYNv.js +268 -0
  30. package/lib/components-DdI7-ns7.js +24 -0
  31. package/lib/compress.d.ts +1 -1
  32. package/lib/conninfo.d.ts +2 -2
  33. package/lib/constants-BR9jM9BC.js +9 -0
  34. package/lib/{context-B5Y1lLha.d.ts → context-DS3fYyG-.d.ts} +2 -2
  35. package/lib/context-DxFdY_un.d.ts +250 -0
  36. package/lib/context-storage.d.ts +1 -1
  37. package/lib/{cookie-D9_nQbsM.d.ts → cookie-CCelEqky.d.ts} +1 -1
  38. package/lib/cookie.d.ts +3 -3
  39. package/lib/cors.d.ts +1 -1
  40. package/lib/csrf.d.ts +1 -1
  41. package/lib/css-D2jb7X8b.js +320 -0
  42. package/lib/css.d.ts +82 -0
  43. package/lib/css.js +134 -0
  44. package/lib/dev.d.ts +2 -2
  45. package/lib/etag.d.ts +1 -1
  46. package/lib/factory.d.ts +2 -2
  47. package/lib/hooks-BMw6BAyP.js +762 -0
  48. package/lib/html-SllAe0YT.d.ts +46 -0
  49. package/lib/html.d.ts +1 -1
  50. package/lib/{index-m12zcmph.d.ts → index-Bb7YJKCq.d.ts} +3 -3
  51. package/lib/{index-Kqou8Fjh.d.ts → index-BvFh1Gfx.d.ts} +2 -2
  52. package/lib/index-C850MeBA.d.ts +94 -0
  53. package/lib/{index-DtEoak1i.d.ts → index-CTy4N7ng.d.ts} +2 -2
  54. package/lib/index.d.ts +4 -4
  55. package/lib/ip-restriction.d.ts +2 -2
  56. package/lib/jsx/dom/client.d.ts +30 -0
  57. package/lib/jsx/dom/client.js +49 -0
  58. package/lib/jsx/dom/css.d.ts +84 -0
  59. package/lib/jsx/dom/css.js +2 -0
  60. package/lib/jsx/dom/jsx-dev-runtime.d.ts +3 -0
  61. package/lib/jsx/dom/jsx-dev-runtime.js +2 -0
  62. package/lib/jsx/dom/jsx-runtime.d.ts +3 -0
  63. package/lib/jsx/dom/jsx-runtime.js +2 -0
  64. package/lib/jsx/dom/server.d.ts +105 -0
  65. package/lib/jsx/dom/server.js +37 -0
  66. package/lib/jsx/dom.d.ts +5 -0
  67. package/lib/jsx/dom.js +74 -0
  68. package/lib/jsx/jsx-dev-runtime.d.ts +6 -0
  69. package/lib/jsx/jsx-dev-runtime.js +14 -0
  70. package/lib/jsx/jsx-runtime.d.ts +10 -0
  71. package/lib/jsx/jsx-runtime.js +30 -0
  72. package/lib/jsx/streaming.d.ts +2 -0
  73. package/lib/jsx/streaming.js +2 -0
  74. package/lib/jsx-dev-runtime-B22O-Npk.d.ts +7 -0
  75. package/lib/jsx-dev-runtime-D18Y35TI.js +354 -0
  76. package/lib/jsx-renderer.d.ts +75 -0
  77. package/lib/jsx-renderer.js +99 -0
  78. package/lib/jsx-runtime-BVVgDSdq.d.ts +1 -0
  79. package/lib/jsx.d.ts +3 -0
  80. package/lib/jsx.js +47 -0
  81. package/lib/jwk.d.ts +4 -4
  82. package/lib/{jwt-BA65QhnJ.d.ts → jwt-BARhotDz.d.ts} +1 -1
  83. package/lib/jwt.d.ts +4 -4
  84. package/lib/language.d.ts +1 -1
  85. package/lib/logger.d.ts +1 -1
  86. package/lib/loopback-only.d.ts +1 -1
  87. package/lib/method-not-allowed.d.ts +2 -2
  88. package/lib/method-override.d.ts +2 -2
  89. package/lib/mount.d.ts +1 -1
  90. package/lib/node-server/conninfo.d.ts +1 -1
  91. package/lib/node-server/serve-static.d.ts +1 -1
  92. package/lib/node-server/serve-static.js +1 -1
  93. package/lib/node-server.d.ts +1 -1
  94. package/lib/node-server.js +1 -1
  95. package/lib/powered-by.d.ts +1 -1
  96. package/lib/pretty-json.d.ts +1 -1
  97. package/lib/proxy.d.ts +1 -1
  98. package/lib/quick.d.ts +1 -1
  99. package/lib/request-id.d.ts +1 -1
  100. package/lib/request.d.ts +1 -1
  101. package/lib/route.d.ts +1 -1
  102. package/lib/schema-validator.d.ts +1 -1
  103. package/lib/secure-headers.d.ts +1 -1
  104. package/lib/{serve-static-Dw_m6KxR.js → serve-static-B14MF1Va.js} +2 -0
  105. package/lib/serve-static.d.ts +1 -1
  106. package/lib/ssg-BwjcA-Ge.js +389 -0
  107. package/lib/ssg.d.ts +156 -0
  108. package/lib/ssg.js +2 -0
  109. package/lib/streaming.d.ts +1 -1
  110. package/lib/testing.d.ts +4 -4
  111. package/lib/testing.js +1 -1
  112. package/lib/timeout.d.ts +1 -1
  113. package/lib/timing.d.ts +1 -1
  114. package/lib/tiny.d.ts +1 -1
  115. package/lib/trailing-slash.d.ts +1 -1
  116. package/lib/{types-ZM9gIiKh.d.ts → types-Dv_nV2cv.d.ts} +1 -1
  117. package/lib/{types-D9DEXMjE.d.ts → types-Lqx5BxQF.d.ts} +8 -1
  118. package/lib/{types-Bd29gh2o.d.ts → types-zNxyEpG2.d.ts} +3 -3
  119. package/lib/types.d.ts +1 -1
  120. package/lib/utils/body.d.ts +1 -1
  121. package/lib/utils/cookie.d.ts +1 -1
  122. package/lib/utils/crypto.d.ts +1 -1
  123. package/lib/utils/html.d.ts +1 -45
  124. package/lib/utils/ipaddr.d.ts +1 -1
  125. package/lib/utils/jwt.d.ts +1 -1
  126. package/lib/utils/types.d.ts +2 -2
  127. package/lib/utils-Cx61GFMC.js +125 -0
  128. package/lib/validator.d.ts +2 -2
  129. package/lib/ws.d.ts +1 -1
  130. package/package.json +53 -1
  131. package/plugins/acryl-inspect/upstream.json +1 -1
  132. package/plugins/acryl-node-server/upstream.json +1 -1
  133. package/plugins/acryl-router/src/core/helper/css/common.ts +337 -0
  134. package/plugins/acryl-router/src/core/helper/css/index.ts +262 -0
  135. package/plugins/acryl-router/src/core/helper/ssg/index.ts +14 -0
  136. package/plugins/acryl-router/src/core/helper/ssg/middleware.ts +83 -0
  137. package/plugins/acryl-router/src/core/helper/ssg/plugins.ts +72 -0
  138. package/plugins/acryl-router/src/core/helper/ssg/ssg.ts +471 -0
  139. package/plugins/acryl-router/src/core/helper/ssg/utils.ts +118 -0
  140. package/plugins/acryl-router/src/core/jsx/base.ts +490 -0
  141. package/plugins/acryl-router/src/core/jsx/children.ts +20 -0
  142. package/plugins/acryl-router/src/core/jsx/components.ts +257 -0
  143. package/plugins/acryl-router/src/core/jsx/constants.ts +6 -0
  144. package/plugins/acryl-router/src/core/jsx/context.ts +266 -0
  145. package/plugins/acryl-router/src/core/jsx/dom/client.ts +89 -0
  146. package/plugins/acryl-router/src/core/jsx/dom/components.ts +39 -0
  147. package/plugins/acryl-router/src/core/jsx/dom/context.ts +52 -0
  148. package/plugins/acryl-router/src/core/jsx/dom/css.ts +263 -0
  149. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.ts +91 -0
  150. package/plugins/acryl-router/src/core/jsx/dom/index.ts +170 -0
  151. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.ts +438 -0
  152. package/plugins/acryl-router/src/core/jsx/dom/jsx-dev-runtime.ts +23 -0
  153. package/plugins/acryl-router/src/core/jsx/dom/jsx-runtime.ts +8 -0
  154. package/plugins/acryl-router/src/core/jsx/dom/render.ts +879 -0
  155. package/plugins/acryl-router/src/core/jsx/dom/server.ts +76 -0
  156. package/plugins/acryl-router/src/core/jsx/dom/utils.ts +7 -0
  157. package/plugins/acryl-router/src/core/jsx/hooks/index.ts +437 -0
  158. package/plugins/acryl-router/src/core/jsx/index.ts +115 -0
  159. package/plugins/acryl-router/src/core/jsx/intrinsic-element/common.ts +23 -0
  160. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.ts +210 -0
  161. package/plugins/acryl-router/src/core/jsx/intrinsic-elements.ts +938 -0
  162. package/plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts +26 -0
  163. package/plugins/acryl-router/src/core/jsx/jsx-runtime.ts +51 -0
  164. package/plugins/acryl-router/src/core/jsx/streaming.ts +231 -0
  165. package/plugins/acryl-router/src/core/jsx/types.ts +41 -0
  166. package/plugins/acryl-router/src/core/jsx/utils.ts +202 -0
  167. package/plugins/acryl-router/src/core/middleware/cache/index.ts +4 -4
  168. package/plugins/acryl-router/src/core/middleware/jsx-renderer/index.ts +162 -0
  169. package/plugins/acryl-router/src/core/utils/types.ts +6 -0
  170. package/plugins/acryl-router/src/helpers/index.ts +2 -0
  171. package/plugins/acryl-router/src/middleware/index.ts +1 -0
  172. package/plugins/acryl-router/upstream.json +3 -3
  173. package/plugins/acryl-router-http/upstream.json +1 -1
  174. package/plugins/acryl-state/upstream.json +1 -1
  175. package/plugins/desktop/upstream.json +1 -1
  176. package/plugins/desktop-web/upstream.json +1 -1
  177. package/plugins/extensions/resolve.ts +4 -0
  178. package/plugins/save/plugin.json +1 -1
  179. package/shared/AGENTS.core.md +4 -0
  180. package/templates/agent/tsconfig.json +7 -1
  181. package/templates/todo/upstream.json +1 -1
  182. package/plugins/acryl-inspect/tests/report.spec.ts +0 -69
  183. package/plugins/acryl-inspect/tests/service.spec.ts +0 -121
  184. package/plugins/acryl-node-server/tests/adapter/app.ts +0 -76
  185. package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +0 -1
  186. package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
  187. package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +0 -1
  188. package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +0 -1
  189. package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +0 -1
  190. package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +0 -5
  191. package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +0 -1
  192. package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
  193. package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +0 -1
  194. package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +0 -1
  195. package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +0 -1
  196. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +0 -1
  197. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +0 -1
  198. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +0 -1
  199. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +0 -1
  200. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +0 -1
  201. package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +0 -34
  202. package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +0 -248
  203. package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +0 -457
  204. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +0 -23
  205. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +0 -27
  206. package/plugins/acryl-node-server/tests/adapter/headers.test.ts +0 -252
  207. package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +0 -190
  208. package/plugins/acryl-node-server/tests/adapter/listener.test.ts +0 -727
  209. package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +0 -189
  210. package/plugins/acryl-node-server/tests/adapter/request.test.ts +0 -1230
  211. package/plugins/acryl-node-server/tests/adapter/response.test.ts +0 -285
  212. package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +0 -849
  213. package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +0 -790
  214. package/plugins/acryl-node-server/tests/adapter/server.test.ts +0 -1312
  215. package/plugins/acryl-node-server/tests/adapter/setup.ts +0 -16
  216. package/plugins/acryl-node-server/tests/adapter/url.test.ts +0 -76
  217. package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +0 -23
  218. package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +0 -75
  219. package/plugins/acryl-node-server/tests/adapter/utils.test.ts +0 -235
  220. package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +0 -260
  221. package/plugins/acryl-node-server/tests/server.spec.ts +0 -141
  222. package/plugins/acryl-router/src/core/app.test.ts +0 -3833
  223. package/plugins/acryl-router/src/core/client/client.test.ts +0 -2313
  224. package/plugins/acryl-router/src/core/client/types.test.ts +0 -149
  225. package/plugins/acryl-router/src/core/client/utils.test.ts +0 -405
  226. package/plugins/acryl-router/src/core/compose.test.ts +0 -751
  227. package/plugins/acryl-router/src/core/context.test.ts +0 -629
  228. package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +0 -344
  229. package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +0 -55
  230. package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +0 -510
  231. package/plugins/acryl-router/src/core/helper/dev/index.test.ts +0 -181
  232. package/plugins/acryl-router/src/core/helper/factory/index.test.ts +0 -434
  233. package/plugins/acryl-router/src/core/helper/html/index.test.ts +0 -78
  234. package/plugins/acryl-router/src/core/helper/proxy/index.test.ts +0 -356
  235. package/plugins/acryl-router/src/core/helper/route/index.test.ts +0 -259
  236. package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +0 -128
  237. package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +0 -34
  238. package/plugins/acryl-router/src/core/helper/testing/index.test.ts +0 -42
  239. package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +0 -124
  240. package/plugins/acryl-router/src/core/http-exception.test.ts +0 -45
  241. package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +0 -434
  242. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +0 -1018
  243. package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +0 -273
  244. package/plugins/acryl-router/src/core/middleware/cache/index.test.ts +0 -1211
  245. package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +0 -355
  246. package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +0 -535
  247. package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +0 -43
  248. package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +0 -532
  249. package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +0 -534
  250. package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +0 -21
  251. package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +0 -503
  252. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +0 -217
  253. package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +0 -1135
  254. package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +0 -48
  255. package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +0 -802
  256. package/plugins/acryl-router/src/core/middleware/language/index.test.ts +0 -552
  257. package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +0 -204
  258. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +0 -292
  259. package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +0 -221
  260. package/plugins/acryl-router/src/core/middleware/mount/index.test.ts +0 -334
  261. package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +0 -37
  262. package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +0 -218
  263. package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +0 -168
  264. package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +0 -671
  265. package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +0 -450
  266. package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +0 -50
  267. package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +0 -70
  268. package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +0 -310
  269. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +0 -381
  270. package/plugins/acryl-router/src/core/preset/quick.test.ts +0 -42
  271. package/plugins/acryl-router/src/core/preset/tiny.test.ts +0 -42
  272. package/plugins/acryl-router/src/core/request.test.ts +0 -751
  273. package/plugins/acryl-router/src/core/router/common.case.test.ts +0 -1132
  274. package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +0 -80
  275. package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +0 -46
  276. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +0 -154
  277. package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +0 -265
  278. package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +0 -13
  279. package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +0 -896
  280. package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +0 -8
  281. package/plugins/acryl-router/src/core/types.test.ts +0 -4134
  282. package/plugins/acryl-router/src/core/utils/accept.test.ts +0 -299
  283. package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +0 -56
  284. package/plugins/acryl-router/src/core/utils/body.test.ts +0 -496
  285. package/plugins/acryl-router/src/core/utils/buffer.test.ts +0 -147
  286. package/plugins/acryl-router/src/core/utils/color.test.ts +0 -23
  287. package/plugins/acryl-router/src/core/utils/concurrent.test.ts +0 -69
  288. package/plugins/acryl-router/src/core/utils/cookie.test.ts +0 -460
  289. package/plugins/acryl-router/src/core/utils/crypto.test.ts +0 -43
  290. package/plugins/acryl-router/src/core/utils/encode.test.ts +0 -61
  291. package/plugins/acryl-router/src/core/utils/filepath.test.ts +0 -78
  292. package/plugins/acryl-router/src/core/utils/html.test.ts +0 -16
  293. package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +0 -149
  294. package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +0 -86
  295. package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +0 -1788
  296. package/plugins/acryl-router/src/core/utils/mime.test.ts +0 -64
  297. package/plugins/acryl-router/src/core/utils/stream.test.ts +0 -212
  298. package/plugins/acryl-router/src/core/utils/types.test.ts +0 -295
  299. package/plugins/acryl-router/src/core/utils/url.test.ts +0 -384
  300. package/plugins/acryl-router/src/core/validator/utils.test.ts +0 -82
  301. package/plugins/acryl-router/src/core/validator/validator.test.ts +0 -1488
  302. package/plugins/acryl-router/tests/docs-examples.spec.ts +0 -152
  303. package/plugins/acryl-router/tests/live-install.spec.ts +0 -64
  304. package/plugins/acryl-router/tests/loader.spec.ts +0 -72
  305. package/plugins/acryl-router/tests/loopback-only.spec.ts +0 -80
  306. package/plugins/acryl-router/tests/no-dom/consumer.ts +0 -11
  307. package/plugins/acryl-router/tests/no-dom/tsconfig.json +0 -13
  308. package/plugins/acryl-router/tests/service.spec.ts +0 -426
  309. package/plugins/acryl-router/tests/shadow-state.spec.ts +0 -79
  310. package/plugins/acryl-router-http/tests/adapter.spec.ts +0 -174
  311. package/plugins/acryl-router-http/tests/stream.spec.ts +0 -133
  312. package/plugins/acryl-router-http/tests/surfaces.spec.ts +0 -101
  313. package/plugins/acryl-state/src/react.ts +0 -11
  314. package/plugins/acryl-state/tests/cell.spec.ts +0 -71
  315. package/plugins/acryl-state/tests/react.spec.ts +0 -39
  316. package/plugins/acryl-state/tests/service.spec.ts +0 -132
@@ -1,1230 +0,0 @@
1
- import { IncomingMessage } from 'node:http'
2
- import type { ServerHttp2Stream } from 'node:http2'
3
- import { Http2ServerRequest } from 'node:http2'
4
- import { Socket } from 'node:net'
5
- import { Duplex } from 'node:stream'
6
- import {
7
- abortRequest,
8
- newRequest,
9
- Request as LightweightRequest,
10
- GlobalRequest,
11
- getAbortController,
12
- abortControllerKey,
13
- RequestError,
14
- } from '../../src/adapter/request.ts'
15
-
16
- Object.defineProperty(global, 'Request', {
17
- value: LightweightRequest,
18
- })
19
-
20
- describe('Request', () => {
21
- describe('newRequest', () => {
22
- it('Compatibility with standard Request object', async () => {
23
- const req = newRequest({
24
- method: 'GET',
25
- url: '/',
26
- headers: {
27
- host: 'localhost',
28
- },
29
- rawHeaders: ['host', 'localhost'],
30
- } as IncomingMessage)
31
-
32
- expect(req).toBeInstanceOf(global.Request)
33
- expect(req.method).toBe('GET')
34
- expect(req.url).toBe('http://localhost/')
35
- expect(req.headers.get('host')).toBe('localhost')
36
- expect(req.keepalive).toBe(false)
37
- })
38
-
39
- it('Should not generate GlobalRequest when accessing method, url or headers', async () => {
40
- const req = newRequest({
41
- method: 'GET',
42
- url: '/',
43
- headers: {
44
- host: 'localhost',
45
- },
46
- rawHeaders: ['host', 'localhost'],
47
- } as IncomingMessage)
48
-
49
- // keep lightweight request even if accessing method, url or headers
50
- expect(req.method).toBe('GET')
51
- expect(req.url).toBe('http://localhost/')
52
- expect(req.headers.get('host')).toBe('localhost')
53
- expect(req[abortControllerKey]).toBeUndefined()
54
-
55
- // generate GlobalRequest
56
- expect(req.keepalive).toBe(false)
57
- expect(req[abortControllerKey]).toBeDefined()
58
- })
59
-
60
- it('Should resolve double dots in URL', async () => {
61
- const req = newRequest({
62
- headers: {
63
- host: 'localhost',
64
- },
65
- url: '/static/../foo.txt',
66
- } as IncomingMessage)
67
- expect(req).toBeInstanceOf(global.Request)
68
- expect(req.url).toBe('http://localhost/foo.txt')
69
- })
70
-
71
- it('Should accept hostname and port in host header', async () => {
72
- const req = newRequest({
73
- headers: {
74
- host: 'localhost:8080',
75
- },
76
- url: '/static/../foo.txt',
77
- } as IncomingMessage)
78
- expect(req).toBeInstanceOf(global.Request)
79
- expect(req.url).toBe('http://localhost:8080/foo.txt')
80
- })
81
-
82
- it('should generate only one `AbortController` per `Request` object created', async () => {
83
- const req = newRequest({
84
- headers: {
85
- host: 'localhost',
86
- },
87
- rawHeaders: ['host', 'localhost'],
88
- url: '/foo.txt',
89
- } as IncomingMessage)
90
- const req2 = newRequest({
91
- headers: {
92
- host: 'localhost',
93
- },
94
- rawHeaders: ['host', 'localhost'],
95
- url: '/foo.txt',
96
- } as IncomingMessage)
97
-
98
- const x = req[getAbortController]()
99
- const y = req[getAbortController]()
100
- const z = req2[getAbortController]()
101
-
102
- expect(x).toBeInstanceOf(AbortController)
103
- expect(y).toBeInstanceOf(AbortController)
104
- expect(z).toBeInstanceOf(AbortController)
105
- expect(x).toBe(y)
106
- expect(z).not.toBe(x)
107
- expect(z).not.toBe(y)
108
- })
109
-
110
- it('should be able to safely check if an AbortController has been initialized by referencing the abortControllerKey', async () => {
111
- const req = newRequest({
112
- headers: {
113
- host: 'localhost',
114
- },
115
- rawHeaders: ['host', 'localhost'],
116
- url: '/foo.txt',
117
- } as IncomingMessage)
118
-
119
- expect(req[abortControllerKey]).toBeUndefined() // not initialized, do not initialize internal request object automatically
120
-
121
- expect(req[getAbortController]()).toBeDefined()
122
- expect(req[abortControllerKey]).toBeDefined() // initialized
123
- })
124
-
125
- it('should apply abort state lazily when signal is accessed after json() body read', async () => {
126
- const req = newRequest({
127
- method: 'POST',
128
- headers: {
129
- host: 'localhost',
130
- 'content-type': 'application/json',
131
- },
132
- rawHeaders: ['host', 'localhost', 'content-type', 'application/json'],
133
- rawBody: Buffer.from('{"foo":"bar"}'),
134
- url: '/foo.txt',
135
- } as IncomingMessage & { rawBody: Buffer })
136
-
137
- expect(await req.json()).toEqual({ foo: 'bar' })
138
- expect(req[abortControllerKey]).toBeUndefined()
139
-
140
- req[abortRequest]('Client connection prematurely closed.')
141
- expect(req[abortControllerKey]).toBeUndefined()
142
-
143
- expect(req.signal.aborted).toBe(true)
144
- })
145
-
146
- it('should preserve an application error when a complete stream is destroyed mid-read', async () => {
147
- const socket = new Socket()
148
- const incomingMessage = new IncomingMessage(socket)
149
- incomingMessage.method = 'POST'
150
- incomingMessage.headers = {
151
- host: 'localhost',
152
- }
153
- incomingMessage.rawHeaders = ['host', 'localhost']
154
- incomingMessage.url = '/foo.txt'
155
- const req = newRequest(incomingMessage)
156
-
157
- const textPromise = req.json()
158
- const error = new Error('Payload too large')
159
- incomingMessage.complete = true
160
- incomingMessage.destroy(error)
161
-
162
- await expect(textPromise).rejects.toBe(error)
163
- })
164
-
165
- it.each([
166
- ['without a stream error', undefined],
167
- [
168
- 'with a client disconnect error',
169
- Object.assign(new Error('aborted'), { code: 'ECONNRESET' }),
170
- ],
171
- ])(
172
- 'should preserve a complete body when the stream closes mid-read %s',
173
- async (_label, destroyError) => {
174
- const socket = new Socket()
175
- const incomingMessage = new IncomingMessage(socket)
176
- incomingMessage.method = 'POST'
177
- incomingMessage.headers = {
178
- host: 'localhost',
179
- 'content-length': '6',
180
- }
181
- incomingMessage.rawHeaders = ['host', 'localhost', 'content-length', '6']
182
- incomingMessage.url = '/foo.txt'
183
- const req = newRequest(incomingMessage)
184
-
185
- const textPromise = req.text()
186
- incomingMessage.push('foo')
187
- incomingMessage.pause()
188
- incomingMessage.push('bar')
189
- incomingMessage.complete = true
190
- incomingMessage.destroy(destroyError)
191
-
192
- await expect(textPromise).resolves.toBe('foobar')
193
- }
194
- )
195
-
196
- it('should reject direct body read when incoming stream is destroyed without error', async () => {
197
- const socket = new Socket()
198
- const incomingMessage = new IncomingMessage(socket)
199
- incomingMessage.method = 'POST'
200
- incomingMessage.headers = {
201
- host: 'localhost',
202
- }
203
- incomingMessage.rawHeaders = ['host', 'localhost']
204
- incomingMessage.url = '/foo.txt'
205
- const req = newRequest(incomingMessage)
206
-
207
- const textPromise = req.text()
208
- incomingMessage.destroy()
209
-
210
- await expect(textPromise).rejects.toBeInstanceOf(Error)
211
- })
212
-
213
- it('should preserve the connection error when an incomplete stream was destroyed before reading', async () => {
214
- const socket = new Socket()
215
- const incomingMessage = new IncomingMessage(socket)
216
- incomingMessage.method = 'POST'
217
- incomingMessage.headers = {
218
- host: 'localhost',
219
- 'content-length': '6',
220
- }
221
- incomingMessage.rawHeaders = ['host', 'localhost', 'content-length', '6']
222
- incomingMessage.url = '/foo.txt'
223
- incomingMessage.push('foo')
224
- incomingMessage.on('error', () => {})
225
- const req = newRequest(incomingMessage)
226
-
227
- const closed = new Promise<void>((resolve) => {
228
- incomingMessage.once('close', resolve)
229
- })
230
- incomingMessage.destroy(new Error('aborted'))
231
- await closed
232
-
233
- await expect(req.text()).rejects.toThrow('aborted')
234
- })
235
-
236
- const newDestroyedIncomingWithCompleteBody = async (
237
- body: string,
238
- headers: Record<string, string> = {},
239
- destroyError?: Error
240
- ): Promise<IncomingMessage> => {
241
- const socket = new Socket()
242
- const incomingMessage = new IncomingMessage(socket)
243
- incomingMessage.method = 'POST'
244
- incomingMessage.headers = { host: 'localhost', ...headers }
245
- incomingMessage.rawHeaders = ['host', 'localhost', ...Object.entries(headers).flat()]
246
- incomingMessage.url = '/foo.txt'
247
- incomingMessage.push(body)
248
- incomingMessage.complete = true
249
- incomingMessage.on('error', () => {})
250
-
251
- const closed = new Promise<void>((resolve) => {
252
- incomingMessage.once('close', resolve)
253
- })
254
- incomingMessage.destroy(destroyError)
255
- await closed
256
- return incomingMessage
257
- }
258
-
259
- it('should preserve the complete buffered body when the stream was destroyed before reading', async () => {
260
- const incomingMessage = await newDestroyedIncomingWithCompleteBody('foo', {
261
- 'content-length': '3',
262
- })
263
- const req = newRequest(incomingMessage)
264
-
265
- await expect(req.text()).resolves.toBe('foo')
266
- })
267
-
268
- it('should reject when the buffered body does not match content-length', async () => {
269
- const incomingMessage = await newDestroyedIncomingWithCompleteBody('foo', {
270
- 'content-length': '6',
271
- })
272
- const req = newRequest(incomingMessage)
273
-
274
- await expect(req.text()).rejects.toThrow('Body is unusable')
275
- })
276
-
277
- it('should preserve the complete buffered body for standard path reads like formData()', async () => {
278
- const incomingMessage = await newDestroyedIncomingWithCompleteBody('hello=world&foo=bar', {
279
- 'content-type': 'application/x-www-form-urlencoded',
280
- 'content-length': '19',
281
- })
282
- const req = newRequest(incomingMessage)
283
-
284
- const formData = await req.formData()
285
- expect(Object.fromEntries(formData.entries())).toEqual({ hello: 'world', foo: 'bar' })
286
- })
287
-
288
- it('should preserve the complete buffered body when read through the body stream', async () => {
289
- const incomingMessage = await newDestroyedIncomingWithCompleteBody('foo', {
290
- 'content-length': '3',
291
- })
292
- const req = newRequest(incomingMessage)
293
-
294
- await expect(new Response(req.body).text()).resolves.toBe('foo')
295
- })
296
-
297
- it('should not recover only the remaining chunked body after a partial read', async () => {
298
- const socket = new Socket()
299
- const incomingMessage = new IncomingMessage(socket)
300
- incomingMessage.method = 'POST'
301
- incomingMessage.headers = { host: 'localhost', 'transfer-encoding': 'chunked' }
302
- incomingMessage.rawHeaders = ['host', 'localhost', 'transfer-encoding', 'chunked']
303
- incomingMessage.url = '/foo.txt'
304
- incomingMessage.push('first')
305
- expect(incomingMessage.read()).toEqual(Buffer.from('first'))
306
- expect(incomingMessage.readableDidRead).toBe(true)
307
- incomingMessage.push('second')
308
- incomingMessage.complete = true
309
- incomingMessage.on('error', () => {})
310
-
311
- const closed = new Promise<void>((resolve) => {
312
- incomingMessage.once('close', resolve)
313
- })
314
- incomingMessage.destroy()
315
- await closed
316
-
317
- const req = newRequest(incomingMessage)
318
- await expect(async () => new Response(req.body).text()).rejects.toThrow()
319
- })
320
-
321
- it('should reject with the underlying error when a complete stream was destroyed with an application error', async () => {
322
- const incomingMessage = await newDestroyedIncomingWithCompleteBody(
323
- 'foo',
324
- { 'content-length': '3' },
325
- new Error('Payload too large')
326
- )
327
- const req = newRequest(incomingMessage)
328
-
329
- await expect(req.text()).rejects.toThrow('Payload too large')
330
- })
331
-
332
- it('should reject standard path reads when a complete stream was destroyed with an application error', async () => {
333
- const incomingMessage = await newDestroyedIncomingWithCompleteBody(
334
- 'hello=world',
335
- {
336
- 'content-type': 'application/x-www-form-urlencoded',
337
- 'content-length': '11',
338
- },
339
- new Error('Payload too large')
340
- )
341
- const req = newRequest(incomingMessage)
342
-
343
- await expect(req.formData()).rejects.toThrow('Payload too large')
344
- })
345
-
346
- it('should preserve the complete buffered body when destroyed by a client disconnect error', async () => {
347
- const incomingMessage = await newDestroyedIncomingWithCompleteBody(
348
- 'foo',
349
- { 'content-length': '3' },
350
- Object.assign(new Error('aborted'), { code: 'ECONNRESET' })
351
- )
352
- const req = newRequest(incomingMessage)
353
-
354
- await expect(req.text()).resolves.toBe('foo')
355
- })
356
-
357
- it('should preserve the complete buffered body when request properties were accessed after disconnect', async () => {
358
- const incomingMessage = await newDestroyedIncomingWithCompleteBody('foo', {
359
- 'content-length': '3',
360
- })
361
- const req = newRequest(incomingMessage)
362
-
363
- // creates the internal native Request cache before the body is read
364
- void req.cache
365
-
366
- await expect(req.text()).resolves.toBe('foo')
367
- })
368
-
369
- it('should skip content-length validation for non-canonical header values', async () => {
370
- // The HTTP parser guarantees digit-only content-length on real
371
- // connections; synthetic values must not be coerced into a bogus length.
372
- for (const contentLength of ['0x10', '10, 10', '-3']) {
373
- const incomingMessage = await newDestroyedIncomingWithCompleteBody('foo', {
374
- 'content-length': contentLength,
375
- })
376
- const req = newRequest(incomingMessage)
377
-
378
- await expect(req.text()).resolves.toBe('foo')
379
- }
380
- })
381
-
382
- it('should reconstruct the original bytes when setEncoding() was used before disconnect', async () => {
383
- const socket = new Socket()
384
- const incomingMessage = new IncomingMessage(socket)
385
- incomingMessage.method = 'POST'
386
- incomingMessage.headers = { host: 'localhost' }
387
- incomingMessage.rawHeaders = ['host', 'localhost']
388
- incomingMessage.url = '/foo.txt'
389
- incomingMessage.setEncoding('latin1')
390
- incomingMessage.push(Buffer.from('café', 'utf8'))
391
- incomingMessage.complete = true
392
- incomingMessage.on('error', () => {})
393
-
394
- const closed = new Promise<void>((resolve) => {
395
- incomingMessage.once('close', resolve)
396
- })
397
- incomingMessage.destroy()
398
- await closed
399
-
400
- const req = newRequest(incomingMessage)
401
- await expect(req.text()).resolves.toBe('café')
402
- })
403
-
404
- it('should not recover a body buffered with a lossy encoding', async () => {
405
- for (const encoding of ['ascii', 'utf8'] as const) {
406
- const socket = new Socket()
407
- const incomingMessage = new IncomingMessage(socket)
408
- incomingMessage.method = 'POST'
409
- incomingMessage.headers = { host: 'localhost' }
410
- incomingMessage.rawHeaders = ['host', 'localhost']
411
- incomingMessage.url = '/foo.txt'
412
- incomingMessage.setEncoding(encoding)
413
- incomingMessage.push(Buffer.from([0x61, 0xe9, 0x62]))
414
- incomingMessage.complete = true
415
- incomingMessage.on('error', () => {})
416
-
417
- const closed = new Promise<void>((resolve) => {
418
- incomingMessage.once('close', resolve)
419
- })
420
- incomingMessage.destroy()
421
- await closed
422
-
423
- const req = newRequest(incomingMessage)
424
- await expect(req.text()).rejects.toThrow('Client connection prematurely closed.')
425
- }
426
- })
427
-
428
- it('should reconstruct the original bytes when setEncoding() was used on a streamed body', async () => {
429
- const socket = new Socket()
430
- const incomingMessage = new IncomingMessage(socket)
431
- incomingMessage.method = 'POST'
432
- incomingMessage.headers = { host: 'localhost' }
433
- incomingMessage.rawHeaders = ['host', 'localhost']
434
- incomingMessage.url = '/foo.txt'
435
- incomingMessage.setEncoding('latin1')
436
-
437
- const req = newRequest(incomingMessage)
438
- const textPromise = req.text()
439
- incomingMessage.push(Buffer.from('café', 'utf8'))
440
- incomingMessage.push(null)
441
-
442
- await expect(textPromise).resolves.toBe('café')
443
- })
444
-
445
- it('should reject direct body read for unsupported methods like native Request', async () => {
446
- for (const method of ['CONNECT', 'TRACK']) {
447
- const req = newRequest({
448
- method,
449
- headers: {
450
- host: 'localhost',
451
- 'content-type': 'text/plain',
452
- },
453
- rawHeaders: ['host', 'localhost', 'content-type', 'text/plain'],
454
- rawBody: Buffer.from('foo'),
455
- url: '/foo.txt',
456
- } as IncomingMessage & { rawBody: Buffer })
457
-
458
- await expect(req.text()).rejects.toBeInstanceOf(TypeError)
459
- }
460
- })
461
-
462
- it('should allow direct body read even when aborted before rawBody read', async () => {
463
- const req = newRequest({
464
- method: 'POST',
465
- headers: {
466
- host: 'localhost',
467
- 'content-type': 'application/json',
468
- },
469
- rawHeaders: ['host', 'localhost', 'content-type', 'application/json'],
470
- rawBody: Buffer.from('{"foo":"bar"}'),
471
- url: '/foo.txt',
472
- } as IncomingMessage & { rawBody: Buffer })
473
-
474
- req[abortRequest]('Client connection prematurely closed.')
475
-
476
- await expect(req.json()).resolves.toEqual({ foo: 'bar' })
477
- })
478
-
479
- it('should allow json() read even after signal is accessed first and then aborted', async () => {
480
- const req = newRequest({
481
- method: 'POST',
482
- headers: {
483
- host: 'localhost',
484
- 'content-type': 'application/json',
485
- },
486
- rawHeaders: ['host', 'localhost', 'content-type', 'application/json'],
487
- rawBody: Buffer.from('{"foo":"bar"}'),
488
- url: '/foo.txt',
489
- } as IncomingMessage & { rawBody: Buffer })
490
-
491
- req[abortRequest]('Client connection prematurely closed.')
492
- expect(req.signal.aborted).toBe(true)
493
-
494
- await expect(req.json()).resolves.toEqual({ foo: 'bar' })
495
- })
496
-
497
- it('should keep bodyUsed consistent after aborted read regardless of signal access order', async () => {
498
- const reqWithoutSignal = newRequest({
499
- method: 'POST',
500
- headers: {
501
- host: 'localhost',
502
- 'content-type': 'application/json',
503
- },
504
- rawHeaders: ['host', 'localhost', 'content-type', 'application/json'],
505
- rawBody: Buffer.from('{"foo":"bar"}'),
506
- url: '/foo.txt',
507
- } as IncomingMessage & { rawBody: Buffer })
508
-
509
- reqWithoutSignal[abortRequest]('Client connection prematurely closed.')
510
- await expect(reqWithoutSignal.json()).resolves.toEqual({ foo: 'bar' })
511
- expect(reqWithoutSignal.bodyUsed).toBe(true)
512
-
513
- const reqWithSignal = newRequest({
514
- method: 'POST',
515
- headers: {
516
- host: 'localhost',
517
- 'content-type': 'application/json',
518
- },
519
- rawHeaders: ['host', 'localhost', 'content-type', 'application/json'],
520
- rawBody: Buffer.from('{"foo":"bar"}'),
521
- url: '/foo.txt',
522
- } as IncomingMessage & { rawBody: Buffer })
523
-
524
- expect(reqWithSignal.signal.aborted).toBe(false)
525
- reqWithSignal[abortRequest]('Client connection prematurely closed.')
526
- expect(reqWithSignal.signal.aborted).toBe(true)
527
- await expect(reqWithSignal.json()).resolves.toEqual({ foo: 'bar' })
528
- expect(reqWithSignal.bodyUsed).toBe(true)
529
- })
530
-
531
- it('should allow clone() and formData() after abort like native Request', async () => {
532
- const reqForClone = newRequest({
533
- method: 'POST',
534
- headers: {
535
- host: 'localhost',
536
- 'content-type': 'text/plain',
537
- },
538
- rawHeaders: ['host', 'localhost', 'content-type', 'text/plain'],
539
- rawBody: Buffer.from('foo'),
540
- url: '/foo.txt',
541
- } as IncomingMessage & { rawBody: Buffer })
542
-
543
- reqForClone[abortRequest]('Client connection prematurely closed.')
544
- const cloned = reqForClone.clone()
545
- await expect(cloned.text()).resolves.toBe('foo')
546
-
547
- const reqForFormData = newRequest({
548
- method: 'POST',
549
- headers: {
550
- host: 'localhost',
551
- 'content-type': 'application/x-www-form-urlencoded',
552
- },
553
- rawHeaders: ['host', 'localhost', 'content-type', 'application/x-www-form-urlencoded'],
554
- rawBody: Buffer.from('a=1&b=2'),
555
- url: '/foo.txt',
556
- } as IncomingMessage & { rawBody: Buffer })
557
-
558
- reqForFormData[abortRequest]('Client connection prematurely closed.')
559
- const formData = await reqForFormData.formData()
560
- expect(formData.get('a')).toBe('1')
561
- expect(formData.get('b')).toBe('2')
562
- })
563
-
564
- it('should reject direct body read when incoming stream has already been consumed', async () => {
565
- const socket = new Socket()
566
- const incomingMessage = new IncomingMessage(socket)
567
- incomingMessage.method = 'POST'
568
- incomingMessage.headers = {
569
- host: 'localhost',
570
- }
571
- incomingMessage.rawHeaders = ['host', 'localhost']
572
- incomingMessage.url = '/foo.txt'
573
- incomingMessage.push('foo')
574
- incomingMessage.push(null)
575
-
576
- for await (const chunk of incomingMessage) {
577
- // consume body before lightweight request reads it
578
- expect(chunk).toBeDefined()
579
- }
580
-
581
- const req = newRequest(incomingMessage)
582
- await expect(req.text()).rejects.toBeInstanceOf(TypeError)
583
- })
584
-
585
- it('should resolve direct body read when stream already ended before first read', async () => {
586
- const socket = new Socket()
587
- const incomingMessage = new IncomingMessage(socket)
588
- incomingMessage.method = 'POST'
589
- incomingMessage.headers = {
590
- host: 'localhost',
591
- }
592
- incomingMessage.rawHeaders = ['host', 'localhost']
593
- incomingMessage.url = '/foo.txt'
594
-
595
- const req = newRequest(incomingMessage)
596
- const ended = new Promise<void>((resolve) => {
597
- incomingMessage.once('end', () => {
598
- resolve()
599
- })
600
- })
601
- incomingMessage.push(null)
602
- incomingMessage.resume()
603
- await ended
604
-
605
- await expect(req.text()).resolves.toBe('')
606
- expect(req.bodyUsed).toBe(true)
607
- })
608
-
609
- it('should reject on second direct json() read', async () => {
610
- const req = newRequest({
611
- method: 'POST',
612
- headers: {
613
- host: 'localhost',
614
- 'content-type': 'application/json',
615
- },
616
- rawHeaders: ['host', 'localhost', 'content-type', 'application/json'],
617
- rawBody: Buffer.from('{"foo":"bar"}'),
618
- url: '/foo.txt',
619
- } as IncomingMessage & { rawBody: Buffer })
620
-
621
- await expect(req.json()).resolves.toEqual({ foo: 'bar' })
622
- await expect(req.json()).rejects.toBeInstanceOf(TypeError)
623
- })
624
-
625
- it('should set bodyUsed and reject clone() after direct json() read', async () => {
626
- const req = newRequest({
627
- method: 'POST',
628
- headers: {
629
- host: 'localhost',
630
- 'content-type': 'application/json',
631
- },
632
- rawHeaders: ['host', 'localhost', 'content-type', 'application/json'],
633
- rawBody: Buffer.from('{"foo":"bar"}'),
634
- url: '/foo.txt',
635
- } as IncomingMessage & { rawBody: Buffer })
636
-
637
- await expect(req.json()).resolves.toEqual({ foo: 'bar' })
638
- expect(req.bodyUsed).toBe(true)
639
- expect(() => req.clone()).toThrow(TypeError)
640
- await expect(req.text()).rejects.toBeInstanceOf(TypeError)
641
- })
642
-
643
- it('should support UTF-8 BOM in direct json() read', async () => {
644
- const req = newRequest({
645
- method: 'POST',
646
- headers: {
647
- host: 'localhost',
648
- 'content-type': 'application/json',
649
- },
650
- rawHeaders: ['host', 'localhost', 'content-type', 'application/json'],
651
- rawBody: Buffer.concat([Buffer.from([0xef, 0xbb, 0xbf]), Buffer.from('{"foo":"bar"}')]),
652
- url: '/foo.txt',
653
- } as IncomingMessage & { rawBody: Buffer })
654
-
655
- await expect(req.json()).resolves.toEqual({ foo: 'bar' })
656
- })
657
-
658
- it('should set bodyUsed and reject clone() after direct text() read', async () => {
659
- const req = newRequest({
660
- method: 'POST',
661
- headers: {
662
- host: 'localhost',
663
- 'content-type': 'text/plain',
664
- },
665
- rawHeaders: ['host', 'localhost', 'content-type', 'text/plain'],
666
- rawBody: Buffer.from('foo'),
667
- url: '/foo.txt',
668
- } as IncomingMessage & { rawBody: Buffer })
669
-
670
- await expect(req.text()).resolves.toBe('foo')
671
- expect(req.bodyUsed).toBe(true)
672
- expect(() => req.clone()).toThrow(TypeError)
673
- await expect(req.arrayBuffer()).rejects.toBeInstanceOf(TypeError)
674
- })
675
-
676
- it('should reject second direct text() read even after accessing signal', async () => {
677
- const req = newRequest({
678
- method: 'POST',
679
- headers: {
680
- host: 'localhost',
681
- 'content-type': 'text/plain',
682
- },
683
- rawHeaders: ['host', 'localhost', 'content-type', 'text/plain'],
684
- rawBody: Buffer.from('foo'),
685
- url: '/foo.txt',
686
- } as IncomingMessage & { rawBody: Buffer })
687
-
688
- await expect(req.text()).resolves.toBe('foo')
689
- expect(req.signal.aborted).toBe(false)
690
- await expect(req.text()).rejects.toBeInstanceOf(TypeError)
691
- })
692
-
693
- it('should reject second direct json() read even after accessing signal', async () => {
694
- const req = newRequest({
695
- method: 'POST',
696
- headers: {
697
- host: 'localhost',
698
- 'content-type': 'application/json',
699
- },
700
- rawHeaders: ['host', 'localhost', 'content-type', 'application/json'],
701
- rawBody: Buffer.from('{"foo":"bar"}'),
702
- url: '/foo.txt',
703
- } as IncomingMessage & { rawBody: Buffer })
704
-
705
- await expect(req.json()).resolves.toEqual({ foo: 'bar' })
706
- expect(req.signal.aborted).toBe(false)
707
- await expect(req.json()).rejects.toBeInstanceOf(TypeError)
708
- })
709
-
710
- it('should set bodyUsed and reject clone() after direct arrayBuffer() read', async () => {
711
- const req = newRequest({
712
- method: 'POST',
713
- headers: {
714
- host: 'localhost',
715
- 'content-type': 'application/octet-stream',
716
- },
717
- rawHeaders: ['host', 'localhost', 'content-type', 'application/octet-stream'],
718
- rawBody: Buffer.from([1, 2, 3]),
719
- url: '/foo.txt',
720
- } as IncomingMessage & { rawBody: Buffer })
721
-
722
- const ab = await req.arrayBuffer()
723
- expect(new Uint8Array(ab)).toEqual(new Uint8Array([1, 2, 3]))
724
- expect(req.bodyUsed).toBe(true)
725
- expect(() => req.clone()).toThrow(TypeError)
726
- await expect(req.text()).rejects.toBeInstanceOf(TypeError)
727
- })
728
-
729
- it('should preserve content type in blob() result', async () => {
730
- const req = newRequest({
731
- method: 'POST',
732
- headers: {
733
- host: 'localhost',
734
- 'content-type': 'text/plain',
735
- },
736
- rawHeaders: ['host', 'localhost', 'content-type', 'text/plain'],
737
- rawBody: Buffer.from('foo'),
738
- url: '/foo.txt',
739
- } as IncomingMessage & { rawBody: Buffer })
740
-
741
- await expect(req.blob().then((blob: Blob) => blob.type)).resolves.toBe('text/plain')
742
- })
743
-
744
- it('should normalize content type in blob() result like native Request', async () => {
745
- const req = newRequest({
746
- method: 'POST',
747
- headers: {
748
- host: 'localhost',
749
- 'content-type': 'text/plain; charset=UTF-8',
750
- },
751
- rawHeaders: ['host', 'localhost', 'content-type', 'text/plain; charset=UTF-8'],
752
- rawBody: Buffer.from('foo'),
753
- url: '/foo.txt',
754
- } as IncomingMessage & { rawBody: Buffer })
755
-
756
- const expectedType = await new GlobalRequest('http://localhost/foo.txt', {
757
- method: 'POST',
758
- headers: {
759
- 'content-type': 'text/plain; charset=UTF-8',
760
- },
761
- body: 'foo',
762
- })
763
- .blob()
764
- .then((blob: Blob) => blob.type)
765
-
766
- await expect(req.blob().then((blob: Blob) => blob.type)).resolves.toBe(expectedType)
767
- })
768
-
769
- it('should set bodyUsed and reject clone() after direct blob() read', async () => {
770
- const req = newRequest({
771
- method: 'POST',
772
- headers: {
773
- host: 'localhost',
774
- 'content-type': 'text/plain',
775
- },
776
- rawHeaders: ['host', 'localhost', 'content-type', 'text/plain'],
777
- rawBody: Buffer.from('foo'),
778
- url: '/foo.txt',
779
- } as IncomingMessage & { rawBody: Buffer })
780
-
781
- const blob = await req.blob()
782
- expect(blob.type).toBe('text/plain')
783
- expect(await blob.text()).toBe('foo')
784
- expect(req.bodyUsed).toBe(true)
785
- expect(() => req.clone()).toThrow(TypeError)
786
- await expect(req.json()).rejects.toBeInstanceOf(TypeError)
787
- })
788
-
789
- it('should allow constructing Request from consumed lightweight request when body is replaced', async () => {
790
- const req = newRequest({
791
- method: 'POST',
792
- headers: {
793
- host: 'localhost',
794
- 'content-type': 'text/plain',
795
- },
796
- rawHeaders: ['host', 'localhost', 'content-type', 'text/plain'],
797
- rawBody: Buffer.from('foo'),
798
- url: '/foo.txt',
799
- } as IncomingMessage & { rawBody: Buffer })
800
-
801
- await req.text()
802
-
803
- const req2 = new LightweightRequest(req, {
804
- method: 'POST',
805
- body: 'bar',
806
- })
807
- await expect(req2.text()).resolves.toBe('bar')
808
- })
809
-
810
- it('should throw when constructing Request from consumed lightweight request without body replacement', async () => {
811
- const req = newRequest({
812
- method: 'POST',
813
- headers: {
814
- host: 'localhost',
815
- 'content-type': 'text/plain',
816
- },
817
- rawHeaders: ['host', 'localhost', 'content-type', 'text/plain'],
818
- rawBody: Buffer.from('foo'),
819
- url: '/foo.txt',
820
- } as IncomingMessage & { rawBody: Buffer })
821
-
822
- await req.text()
823
-
824
- expect(() => {
825
- new LightweightRequest(req)
826
- }).toThrow(TypeError)
827
- })
828
-
829
- it('should preserve TRACE workaround after direct read when accessing signal', async () => {
830
- const socket = new Socket()
831
- const incomingMessage = new IncomingMessage(socket)
832
- incomingMessage.method = 'TRACE'
833
- incomingMessage.headers = {
834
- host: 'localhost',
835
- 'content-type': 'text/plain',
836
- }
837
- incomingMessage.rawHeaders = ['host', 'localhost', 'content-type', 'text/plain']
838
- incomingMessage.url = '/foo.txt'
839
- ;(incomingMessage as IncomingMessage & { rawBody: Buffer }).rawBody = Buffer.from('foo')
840
- const req = newRequest(incomingMessage)
841
-
842
- await expect(req.text()).resolves.toBe('foo')
843
- expect(() => req.signal).not.toThrow()
844
- })
845
-
846
- it('should keep TRACE direct body read behavior regardless of signal access order', async () => {
847
- const createTraceRequest = () => {
848
- const socket = new Socket()
849
- const incomingMessage = new IncomingMessage(socket)
850
- incomingMessage.method = 'TRACE'
851
- incomingMessage.headers = {
852
- host: 'localhost',
853
- 'content-type': 'text/plain',
854
- }
855
- incomingMessage.rawHeaders = ['host', 'localhost', 'content-type', 'text/plain']
856
- incomingMessage.url = '/foo.txt'
857
- ;(incomingMessage as IncomingMessage & { rawBody: Buffer }).rawBody = Buffer.from('foo')
858
- return newRequest(incomingMessage)
859
- }
860
-
861
- const reqBeforeSignal = createTraceRequest()
862
- await expect(reqBeforeSignal.text()).resolves.toBe('foo')
863
-
864
- const reqAfterSignal = createTraceRequest()
865
- expect(() => reqAfterSignal.signal).not.toThrow()
866
- await expect(reqAfterSignal.text()).resolves.toBe('foo')
867
- })
868
-
869
- it('should reject non-uppercase trace consistently regardless of access order', async () => {
870
- const createTraceLikeRequest = (method: string) => {
871
- const socket = new Socket()
872
- const incomingMessage = new IncomingMessage(socket)
873
- incomingMessage.method = method
874
- incomingMessage.headers = {
875
- host: 'localhost',
876
- 'content-type': 'text/plain',
877
- }
878
- incomingMessage.rawHeaders = ['host', 'localhost', 'content-type', 'text/plain']
879
- incomingMessage.url = '/foo.txt'
880
- ;(incomingMessage as IncomingMessage & { rawBody: Buffer }).rawBody = Buffer.from('foo')
881
- return newRequest(incomingMessage)
882
- }
883
-
884
- for (const method of ['trace', 'TrAcE']) {
885
- const req = createTraceLikeRequest(method)
886
- await expect(req.text()).rejects.toBeInstanceOf(TypeError)
887
-
888
- const reqAfterSignal = createTraceLikeRequest(method)
889
- expect(() => reqAfterSignal.signal).not.toThrow()
890
- await expect(reqAfterSignal.text()).rejects.toBeInstanceOf(TypeError)
891
- }
892
- })
893
-
894
- it('should preserve non-standard method casing like native Request', async () => {
895
- for (const method of ['patch', 'CuStOm']) {
896
- const req = newRequest({
897
- method,
898
- headers: {
899
- host: 'localhost',
900
- },
901
- rawHeaders: ['host', 'localhost'],
902
- url: '/foo.txt',
903
- } as IncomingMessage)
904
-
905
- const expected = new GlobalRequest('http://localhost/foo.txt', { method }).method
906
- expect(req.method).toBe(expected)
907
- }
908
- })
909
-
910
- it('should normalize lowercase methods and keep GET behavior consistent', async () => {
911
- const req = newRequest({
912
- method: 'get',
913
- headers: {
914
- host: 'localhost',
915
- 'content-type': 'text/plain',
916
- },
917
- rawHeaders: ['host', 'localhost', 'content-type', 'text/plain'],
918
- rawBody: Buffer.from('foo'),
919
- url: '/foo.txt',
920
- } as IncomingMessage & { rawBody: Buffer })
921
-
922
- expect(req.method).toBe('GET')
923
- expect(() => req.signal).not.toThrow()
924
- await expect(req.text()).resolves.toBe('')
925
- expect(req.bodyUsed).toBe(false)
926
- })
927
-
928
- it('should keep default GET behavior when incoming.method is missing', async () => {
929
- const req = newRequest({
930
- headers: {
931
- host: 'localhost',
932
- },
933
- rawHeaders: ['host', 'localhost'],
934
- rawBody: Buffer.from('foo'),
935
- url: '/foo.txt',
936
- } as IncomingMessage & { rawBody: Buffer })
937
-
938
- expect(req.method).toBe('GET')
939
- expect(await req.text()).toBe('')
940
- expect(req.bodyUsed).toBe(false)
941
- expect(() => req.signal).not.toThrow()
942
- })
943
-
944
- it('Should throw error if host header contains path', async () => {
945
- expect(() => {
946
- newRequest({
947
- headers: {
948
- host: 'localhost/..',
949
- },
950
- url: '/foo.txt',
951
- } as IncomingMessage)
952
- }).toThrow(RequestError)
953
- })
954
-
955
- it('Should throw error if host header is empty', async () => {
956
- expect(() => {
957
- newRequest({
958
- headers: {
959
- host: '',
960
- },
961
- url: '/foo.txt',
962
- } as IncomingMessage)
963
- }).toThrow(RequestError)
964
- })
965
-
966
- it('Should throw error if host header contains query parameter', async () => {
967
- expect(() => {
968
- newRequest({
969
- headers: {
970
- host: 'localhost?foo=bar',
971
- },
972
- url: '/foo.txt',
973
- } as IncomingMessage)
974
- }).toThrow(RequestError)
975
- })
976
-
977
- it('Should throw error if host header port is invalid', async () => {
978
- expect(() => {
979
- newRequest({
980
- headers: {
981
- host: 'localhost:65536',
982
- },
983
- url: '/foo.txt',
984
- } as IncomingMessage)
985
- }).toThrow(RequestError)
986
- })
987
-
988
- it('Should be created request body from `req.rawBody` if it exists', async () => {
989
- const rawBody = Buffer.from('foo')
990
- const socket = new Socket()
991
- const incomingMessage = new IncomingMessage(socket)
992
- incomingMessage.method = 'POST'
993
- incomingMessage.headers = {
994
- host: 'localhost',
995
- }
996
- incomingMessage.url = '/foo.txt'
997
- ;(incomingMessage as IncomingMessage & { rawBody: Buffer }).rawBody = rawBody
998
- incomingMessage.push(rawBody)
999
- incomingMessage.push(null)
1000
-
1001
- for await (const chunk of incomingMessage) {
1002
- // consume body
1003
- expect(chunk).toBeDefined()
1004
- }
1005
-
1006
- const req = newRequest(incomingMessage)
1007
- const text = await req.text()
1008
- expect(text).toBe('foo')
1009
- })
1010
-
1011
- describe('absolute-form for request-target', () => {
1012
- it('should be created from valid absolute URL', async () => {
1013
- const req = newRequest({
1014
- url: 'http://localhost/path/to/file.html',
1015
- } as IncomingMessage)
1016
- expect(req).toBeInstanceOf(GlobalRequest)
1017
- expect(req.url).toBe('http://localhost/path/to/file.html')
1018
- })
1019
-
1020
- it('should throw error if host header is invalid', async () => {
1021
- expect(() => {
1022
- newRequest({
1023
- url: 'http://',
1024
- } as IncomingMessage)
1025
- }).toThrow(RequestError)
1026
- })
1027
-
1028
- it('should throw error if absolute-form is specified via HTTP/2', async () => {
1029
- expect(() => {
1030
- newRequest(
1031
- new Http2ServerRequest(
1032
- new Duplex() as ServerHttp2Stream,
1033
- {
1034
- ':scheme': 'http',
1035
- ':authority': 'localhost',
1036
- ':path': 'http://localhost/foo.txt',
1037
- },
1038
- {},
1039
- []
1040
- )
1041
- )
1042
- }).toThrow(RequestError)
1043
- })
1044
- })
1045
-
1046
- describe('HTTP/2', () => {
1047
- it('should be created from "http" scheme', async () => {
1048
- const req = newRequest(
1049
- new Http2ServerRequest(
1050
- new Duplex() as ServerHttp2Stream,
1051
- {
1052
- ':scheme': 'http',
1053
- ':authority': 'localhost',
1054
- ':path': '/foo.txt',
1055
- },
1056
- {},
1057
- []
1058
- )
1059
- )
1060
- expect(req).toBeInstanceOf(GlobalRequest)
1061
- expect(req.url).toBe('http://localhost/foo.txt')
1062
- })
1063
-
1064
- it('should be created from "https" scheme', async () => {
1065
- const req = newRequest(
1066
- new Http2ServerRequest(
1067
- new Duplex() as ServerHttp2Stream,
1068
- {
1069
- ':scheme': 'https',
1070
- ':authority': 'localhost',
1071
- ':path': '/foo.txt',
1072
- },
1073
- {},
1074
- []
1075
- )
1076
- )
1077
- expect(req).toBeInstanceOf(GlobalRequest)
1078
- expect(req.url).toBe('https://localhost/foo.txt')
1079
- })
1080
-
1081
- it('should throw error if scheme is missing', async () => {
1082
- expect(() => {
1083
- newRequest(
1084
- new Http2ServerRequest(
1085
- new Duplex() as ServerHttp2Stream,
1086
- {
1087
- ':authority': 'localhost',
1088
- ':path': '/foo.txt',
1089
- },
1090
- {},
1091
- []
1092
- )
1093
- )
1094
- }).toThrow(RequestError)
1095
- })
1096
-
1097
- it('should throw error if unsupported scheme is specified', async () => {
1098
- expect(() => {
1099
- newRequest(
1100
- new Http2ServerRequest(
1101
- new Duplex() as ServerHttp2Stream,
1102
- {
1103
- ':scheme': 'ftp',
1104
- ':authority': 'localhost',
1105
- ':path': '/foo.txt',
1106
- },
1107
- {},
1108
- []
1109
- )
1110
- )
1111
- }).toThrow(RequestError)
1112
- })
1113
- })
1114
- })
1115
-
1116
- describe('GlobalRequest', () => {
1117
- it('should be overrode by Request', () => {
1118
- expect(Request).not.toBe(GlobalRequest)
1119
- })
1120
-
1121
- it('should be instance of GlobalRequest', () => {
1122
- const req = new Request('http://localhost/')
1123
- expect(req).toBeInstanceOf(GlobalRequest)
1124
- })
1125
-
1126
- it('should be success to create instance from old light weight instance', async () => {
1127
- const req = newRequest({
1128
- method: 'GET',
1129
- url: '/',
1130
- headers: {
1131
- host: 'localhost',
1132
- },
1133
- rawHeaders: ['host', 'localhost'],
1134
- } as IncomingMessage)
1135
- const req2 = new Request(req, {
1136
- method: 'POST',
1137
- body: 'foo',
1138
- })
1139
- expect(req2).toBeInstanceOf(GlobalRequest)
1140
- expect(await req2.text()).toBe('foo')
1141
- })
1142
-
1143
- it('should set `duplex: "half"` automatically if body is a ReadableStream', async () => {
1144
- const stream = new ReadableStream({
1145
- start(controller) {
1146
- controller.enqueue(new TextEncoder().encode('bar'))
1147
- controller.close()
1148
- },
1149
- })
1150
- const req2 = new Request('http://localhost', {
1151
- method: 'POST',
1152
- body: stream,
1153
- })
1154
- expect(req2).toBeInstanceOf(GlobalRequest)
1155
- await expect(req2.text()).resolves.toBe('bar')
1156
- })
1157
-
1158
- it('should skip to set `duplex: "half"` if init option is a Request object', async () => {
1159
- const stream = new ReadableStream({
1160
- start(controller) {
1161
- controller.enqueue(new TextEncoder().encode('bar'))
1162
- controller.close()
1163
- },
1164
- })
1165
- const req = new Request('http://localhost', {
1166
- method: 'POST',
1167
- body: stream,
1168
- })
1169
- const req2 = new Request('http://localhost/subapp', req)
1170
- expect(req2).toBeInstanceOf(GlobalRequest)
1171
- await expect(req2.text()).resolves.toBe('bar')
1172
- })
1173
- })
1174
- })
1175
-
1176
- describe('util.inspect', () => {
1177
- it('should show a lightweight request summary without creating a native Request', () => {
1178
- const { inspect } = require('node:util')
1179
- const req = newRequest({
1180
- method: 'GET',
1181
- url: '/',
1182
- headers: {
1183
- host: 'localhost',
1184
- },
1185
- rawHeaders: ['host', 'localhost'],
1186
- } as IncomingMessage)
1187
-
1188
- expect(() => inspect(req)).not.toThrow()
1189
- const result = inspect(req)
1190
- expect(result).toContain('Request (lightweight)')
1191
- expect(result).toContain('GET')
1192
- expect(result).toContain('http://localhost/')
1193
- expect(result).toContain('nativeRequest: undefined')
1194
- expect(req[abortControllerKey]).toBeUndefined()
1195
- expect(req).toBeInstanceOf(global.Request)
1196
- })
1197
-
1198
- it('should include the native Request after cache creation', () => {
1199
- const { inspect } = require('node:util')
1200
- const req = newRequest({
1201
- method: 'GET',
1202
- url: '/',
1203
- headers: {
1204
- host: 'localhost',
1205
- },
1206
- rawHeaders: ['host', 'localhost'],
1207
- } as IncomingMessage)
1208
-
1209
- // Access keepalive to trigger native Request creation via getRequestCache()
1210
- req.keepalive
1211
-
1212
- const result = inspect(req)
1213
- expect(result).toContain('Request (lightweight)')
1214
- expect(result).toContain('nativeRequest: Request {')
1215
- expect(req[abortControllerKey]).toBeDefined()
1216
- })
1217
- })
1218
-
1219
- describe('RequestError', () => {
1220
- it('should have a static name property (class name)', () => {
1221
- expect(RequestError.name).toBe('RequestError')
1222
- expect(Object.hasOwn(RequestError, 'name')).toBe(true)
1223
- })
1224
-
1225
- it('should have an instance name property', () => {
1226
- const error = new RequestError('message')
1227
- expect(error.name).toBe('RequestError')
1228
- expect(Object.hasOwn(error, 'name')).toBe(true)
1229
- })
1230
- })