acrylblends 0.2.0 → 0.4.0

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 (335) hide show
  1. package/dist/index.d.ts +22 -16
  2. package/dist/index.js +65 -20
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/plugins/acryl-inspect/LICENSE +21 -0
  6. package/plugins/acryl-inspect/plugin.json +12 -0
  7. package/plugins/acryl-inspect/src/index.ts +3 -0
  8. package/plugins/acryl-inspect/src/report.ts +126 -0
  9. package/plugins/acryl-inspect/src/service.ts +68 -0
  10. package/plugins/acryl-inspect/tests/report.spec.ts +63 -0
  11. package/plugins/acryl-inspect/tests/service.spec.ts +121 -0
  12. package/plugins/acryl-inspect/upstream.json +18 -0
  13. package/plugins/acryl-node-server/LICENSE +47 -0
  14. package/plugins/acryl-node-server/plugin.json +13 -0
  15. package/plugins/acryl-node-server/src/adapter/conninfo.ts +23 -0
  16. package/plugins/acryl-node-server/src/adapter/early-hints.ts +55 -0
  17. package/plugins/acryl-node-server/src/adapter/error.ts +11 -0
  18. package/plugins/acryl-node-server/src/adapter/headers.ts +249 -0
  19. package/plugins/acryl-node-server/src/adapter/index.ts +6 -0
  20. package/plugins/acryl-node-server/src/adapter/listener.ts +540 -0
  21. package/plugins/acryl-node-server/src/adapter/request.ts +797 -0
  22. package/plugins/acryl-node-server/src/adapter/response.ts +186 -0
  23. package/plugins/acryl-node-server/src/adapter/serve-static.ts +238 -0
  24. package/plugins/acryl-node-server/src/adapter/server.ts +36 -0
  25. package/plugins/acryl-node-server/src/adapter/types.ts +82 -0
  26. package/plugins/acryl-node-server/src/adapter/url.ts +42 -0
  27. package/plugins/acryl-node-server/src/adapter/utils/response/constants.ts +1 -0
  28. package/plugins/acryl-node-server/src/adapter/utils/response.ts +4 -0
  29. package/plugins/acryl-node-server/src/adapter/utils/stream.ts +88 -0
  30. package/plugins/acryl-node-server/src/adapter/utils.ts +96 -0
  31. package/plugins/acryl-node-server/src/adapter/websocket-types.ts +45 -0
  32. package/plugins/acryl-node-server/src/adapter/websocket.ts +375 -0
  33. package/plugins/acryl-node-server/src/index.ts +107 -0
  34. package/plugins/acryl-node-server/tests/adapter/app.ts +76 -0
  35. package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +1 -0
  36. package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
  37. package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +1 -0
  38. package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +1 -0
  39. package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +1 -0
  40. package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +5 -0
  41. package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +1 -0
  42. package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
  43. package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +1 -0
  44. package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +1 -0
  45. package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +1 -0
  46. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +1 -0
  47. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +1 -0
  48. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +1 -0
  49. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +1 -0
  50. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +1 -0
  51. package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +34 -0
  52. package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +248 -0
  53. package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +457 -0
  54. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +23 -0
  55. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +27 -0
  56. package/plugins/acryl-node-server/tests/adapter/headers.test.ts +252 -0
  57. package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +190 -0
  58. package/plugins/acryl-node-server/tests/adapter/listener.test.ts +727 -0
  59. package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +189 -0
  60. package/plugins/acryl-node-server/tests/adapter/request.test.ts +1230 -0
  61. package/plugins/acryl-node-server/tests/adapter/response.test.ts +285 -0
  62. package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +849 -0
  63. package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +790 -0
  64. package/plugins/acryl-node-server/tests/adapter/server.test.ts +1302 -0
  65. package/plugins/acryl-node-server/tests/adapter/setup.ts +16 -0
  66. package/plugins/acryl-node-server/tests/adapter/url.test.ts +76 -0
  67. package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +23 -0
  68. package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +75 -0
  69. package/plugins/acryl-node-server/tests/adapter/utils.test.ts +235 -0
  70. package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +260 -0
  71. package/plugins/acryl-node-server/tests/server.spec.ts +141 -0
  72. package/plugins/acryl-node-server/upstream.json +18 -0
  73. package/plugins/acryl-router/LICENSE +47 -0
  74. package/plugins/acryl-router/plugin.json +13 -0
  75. package/plugins/acryl-router/src/client.ts +2 -0
  76. package/plugins/acryl-router/src/connection.ts +42 -0
  77. package/plugins/acryl-router/src/core/app-base.ts +544 -0
  78. package/plugins/acryl-router/src/core/app.test.ts +3833 -0
  79. package/plugins/acryl-router/src/core/app.ts +34 -0
  80. package/plugins/acryl-router/src/core/client/client.test.ts +2313 -0
  81. package/plugins/acryl-router/src/core/client/client.ts +249 -0
  82. package/plugins/acryl-router/src/core/client/fetch-result-please.ts +95 -0
  83. package/plugins/acryl-router/src/core/client/index.ts +17 -0
  84. package/plugins/acryl-router/src/core/client/types.test.ts +149 -0
  85. package/plugins/acryl-router/src/core/client/types.ts +393 -0
  86. package/plugins/acryl-router/src/core/client/utils.test.ts +405 -0
  87. package/plugins/acryl-router/src/core/client/utils.ts +117 -0
  88. package/plugins/acryl-router/src/core/compose.test.ts +751 -0
  89. package/plugins/acryl-router/src/core/compose.ts +73 -0
  90. package/plugins/acryl-router/src/core/context.test.ts +629 -0
  91. package/plugins/acryl-router/src/core/context.ts +781 -0
  92. package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +344 -0
  93. package/plugins/acryl-router/src/core/helper/accepts/accepts.ts +96 -0
  94. package/plugins/acryl-router/src/core/helper/accepts/index.ts +6 -0
  95. package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +55 -0
  96. package/plugins/acryl-router/src/core/helper/adapter/index.ts +90 -0
  97. package/plugins/acryl-router/src/core/helper/conninfo/index.ts +6 -0
  98. package/plugins/acryl-router/src/core/helper/conninfo/types.ts +45 -0
  99. package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +510 -0
  100. package/plugins/acryl-router/src/core/helper/cookie/index.ts +146 -0
  101. package/plugins/acryl-router/src/core/helper/factory/index.test.ts +434 -0
  102. package/plugins/acryl-router/src/core/helper/factory/index.ts +375 -0
  103. package/plugins/acryl-router/src/core/helper/html/index.test.ts +78 -0
  104. package/plugins/acryl-router/src/core/helper/html/index.ts +57 -0
  105. package/plugins/acryl-router/src/core/helper/route/index.test.ts +259 -0
  106. package/plugins/acryl-router/src/core/helper/route/index.ts +144 -0
  107. package/plugins/acryl-router/src/core/helper/streaming/index.ts +9 -0
  108. package/plugins/acryl-router/src/core/helper/streaming/sse.ts +101 -0
  109. package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +128 -0
  110. package/plugins/acryl-router/src/core/helper/streaming/stream.ts +45 -0
  111. package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +34 -0
  112. package/plugins/acryl-router/src/core/helper/streaming/text.ts +15 -0
  113. package/plugins/acryl-router/src/core/helper/streaming/utils.ts +11 -0
  114. package/plugins/acryl-router/src/core/helper/testing/index.test.ts +42 -0
  115. package/plugins/acryl-router/src/core/helper/testing/index.ts +28 -0
  116. package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +124 -0
  117. package/plugins/acryl-router/src/core/helper/websocket/index.ts +140 -0
  118. package/plugins/acryl-router/src/core/http-exception.test.ts +45 -0
  119. package/plugins/acryl-router/src/core/http-exception.ts +77 -0
  120. package/plugins/acryl-router/src/core/index.ts +53 -0
  121. package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +434 -0
  122. package/plugins/acryl-router/src/core/middleware/basic-auth/index.ts +152 -0
  123. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +1018 -0
  124. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.ts +220 -0
  125. package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +273 -0
  126. package/plugins/acryl-router/src/core/middleware/body-limit/index.ts +110 -0
  127. package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +355 -0
  128. package/plugins/acryl-router/src/core/middleware/combine/index.ts +171 -0
  129. package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +535 -0
  130. package/plugins/acryl-router/src/core/middleware/compress/index.ts +142 -0
  131. package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +43 -0
  132. package/plugins/acryl-router/src/core/middleware/context-storage/index.ts +58 -0
  133. package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +532 -0
  134. package/plugins/acryl-router/src/core/middleware/cors/index.ts +163 -0
  135. package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +534 -0
  136. package/plugins/acryl-router/src/core/middleware/csrf/index.ts +150 -0
  137. package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +21 -0
  138. package/plugins/acryl-router/src/core/middleware/etag/digest.ts +95 -0
  139. package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +503 -0
  140. package/plugins/acryl-router/src/core/middleware/etag/index.ts +135 -0
  141. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +217 -0
  142. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.ts +278 -0
  143. package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +1135 -0
  144. package/plugins/acryl-router/src/core/middleware/jwk/index.ts +1 -0
  145. package/plugins/acryl-router/src/core/middleware/jwk/jwk.ts +195 -0
  146. package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +48 -0
  147. package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +802 -0
  148. package/plugins/acryl-router/src/core/middleware/jwt/index.ts +9 -0
  149. package/plugins/acryl-router/src/core/middleware/jwt/jwt.ts +189 -0
  150. package/plugins/acryl-router/src/core/middleware/language/index.test.ts +552 -0
  151. package/plugins/acryl-router/src/core/middleware/language/index.ts +12 -0
  152. package/plugins/acryl-router/src/core/middleware/language/language.ts +314 -0
  153. package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +204 -0
  154. package/plugins/acryl-router/src/core/middleware/logger/index.ts +95 -0
  155. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +292 -0
  156. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.ts +141 -0
  157. package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +221 -0
  158. package/plugins/acryl-router/src/core/middleware/method-override/index.ts +147 -0
  159. package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +37 -0
  160. package/plugins/acryl-router/src/core/middleware/powered-by/index.ts +35 -0
  161. package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +218 -0
  162. package/plugins/acryl-router/src/core/middleware/pretty-json/index.ts +67 -0
  163. package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +168 -0
  164. package/plugins/acryl-router/src/core/middleware/request-id/index.ts +7 -0
  165. package/plugins/acryl-router/src/core/middleware/request-id/request-id.ts +59 -0
  166. package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +671 -0
  167. package/plugins/acryl-router/src/core/middleware/secure-headers/index.ts +8 -0
  168. package/plugins/acryl-router/src/core/middleware/secure-headers/permissions-policy.ts +101 -0
  169. package/plugins/acryl-router/src/core/middleware/secure-headers/secure-headers.ts +340 -0
  170. package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +450 -0
  171. package/plugins/acryl-router/src/core/middleware/serve-static/index.ts +135 -0
  172. package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +50 -0
  173. package/plugins/acryl-router/src/core/middleware/serve-static/path.ts +25 -0
  174. package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +70 -0
  175. package/plugins/acryl-router/src/core/middleware/timeout/index.ts +58 -0
  176. package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +310 -0
  177. package/plugins/acryl-router/src/core/middleware/timing/index.ts +8 -0
  178. package/plugins/acryl-router/src/core/middleware/timing/timing.ts +256 -0
  179. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +381 -0
  180. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.ts +156 -0
  181. package/plugins/acryl-router/src/core/request/constants.ts +1 -0
  182. package/plugins/acryl-router/src/core/request.test.ts +751 -0
  183. package/plugins/acryl-router/src/core/request.ts +502 -0
  184. package/plugins/acryl-router/src/core/router/common.case.test.ts +1132 -0
  185. package/plugins/acryl-router/src/core/router/linear-router/index.ts +6 -0
  186. package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +80 -0
  187. package/plugins/acryl-router/src/core/router/linear-router/router.ts +152 -0
  188. package/plugins/acryl-router/src/core/router/pattern-router/index.ts +6 -0
  189. package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +46 -0
  190. package/plugins/acryl-router/src/core/router/pattern-router/router.ts +54 -0
  191. package/plugins/acryl-router/src/core/router/reg-exp-router/index.ts +7 -0
  192. package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.ts +33 -0
  193. package/plugins/acryl-router/src/core/router/reg-exp-router/node.ts +170 -0
  194. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +154 -0
  195. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.ts +165 -0
  196. package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +265 -0
  197. package/plugins/acryl-router/src/core/router/reg-exp-router/router.ts +174 -0
  198. package/plugins/acryl-router/src/core/router/reg-exp-router/trie.ts +84 -0
  199. package/plugins/acryl-router/src/core/router/smart-router/index.ts +6 -0
  200. package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +13 -0
  201. package/plugins/acryl-router/src/core/router/smart-router/router.ts +70 -0
  202. package/plugins/acryl-router/src/core/router/trie-router/index.ts +6 -0
  203. package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +896 -0
  204. package/plugins/acryl-router/src/core/router/trie-router/node.ts +221 -0
  205. package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +8 -0
  206. package/plugins/acryl-router/src/core/router/trie-router/router.ts +18 -0
  207. package/plugins/acryl-router/src/core/router/utils.ts +1 -0
  208. package/plugins/acryl-router/src/core/router.ts +103 -0
  209. package/plugins/acryl-router/src/core/types.test.ts +4134 -0
  210. package/plugins/acryl-router/src/core/types.ts +2782 -0
  211. package/plugins/acryl-router/src/core/utils/accept.test.ts +299 -0
  212. package/plugins/acryl-router/src/core/utils/accept.ts +268 -0
  213. package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +56 -0
  214. package/plugins/acryl-router/src/core/utils/basic-auth.ts +26 -0
  215. package/plugins/acryl-router/src/core/utils/body.test.ts +496 -0
  216. package/plugins/acryl-router/src/core/utils/body.ts +263 -0
  217. package/plugins/acryl-router/src/core/utils/buffer.test.ts +147 -0
  218. package/plugins/acryl-router/src/core/utils/buffer.ts +117 -0
  219. package/plugins/acryl-router/src/core/utils/color.test.ts +23 -0
  220. package/plugins/acryl-router/src/core/utils/color.ts +56 -0
  221. package/plugins/acryl-router/src/core/utils/compress.ts +10 -0
  222. package/plugins/acryl-router/src/core/utils/concurrent.test.ts +69 -0
  223. package/plugins/acryl-router/src/core/utils/concurrent.ts +55 -0
  224. package/plugins/acryl-router/src/core/utils/constants.ts +4 -0
  225. package/plugins/acryl-router/src/core/utils/cookie.test.ts +460 -0
  226. package/plugins/acryl-router/src/core/utils/cookie.ts +281 -0
  227. package/plugins/acryl-router/src/core/utils/crypto.test.ts +43 -0
  228. package/plugins/acryl-router/src/core/utils/crypto.ts +58 -0
  229. package/plugins/acryl-router/src/core/utils/encode.test.ts +61 -0
  230. package/plugins/acryl-router/src/core/utils/encode.ts +34 -0
  231. package/plugins/acryl-router/src/core/utils/filepath.test.ts +78 -0
  232. package/plugins/acryl-router/src/core/utils/filepath.ts +60 -0
  233. package/plugins/acryl-router/src/core/utils/handler.ts +15 -0
  234. package/plugins/acryl-router/src/core/utils/headers.ts +355 -0
  235. package/plugins/acryl-router/src/core/utils/html.test.ts +16 -0
  236. package/plugins/acryl-router/src/core/utils/html.ts +182 -0
  237. package/plugins/acryl-router/src/core/utils/http-status.ts +72 -0
  238. package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +149 -0
  239. package/plugins/acryl-router/src/core/utils/ipaddr.ts +374 -0
  240. package/plugins/acryl-router/src/core/utils/jwt/index.ts +7 -0
  241. package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +86 -0
  242. package/plugins/acryl-router/src/core/utils/jwt/jwa.ts +38 -0
  243. package/plugins/acryl-router/src/core/utils/jwt/jws.ts +235 -0
  244. package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +1788 -0
  245. package/plugins/acryl-router/src/core/utils/jwt/jwt.ts +292 -0
  246. package/plugins/acryl-router/src/core/utils/jwt/types.ts +157 -0
  247. package/plugins/acryl-router/src/core/utils/jwt/utf8.ts +7 -0
  248. package/plugins/acryl-router/src/core/utils/mime.test.ts +64 -0
  249. package/plugins/acryl-router/src/core/utils/mime.ts +94 -0
  250. package/plugins/acryl-router/src/core/utils/stream.test.ts +212 -0
  251. package/plugins/acryl-router/src/core/utils/stream.ts +107 -0
  252. package/plugins/acryl-router/src/core/utils/types.test.ts +295 -0
  253. package/plugins/acryl-router/src/core/utils/types.ts +123 -0
  254. package/plugins/acryl-router/src/core/utils/url.test.ts +384 -0
  255. package/plugins/acryl-router/src/core/utils/url.ts +337 -0
  256. package/plugins/acryl-router/src/core/validator/index.ts +8 -0
  257. package/plugins/acryl-router/src/core/validator/utils.test.ts +82 -0
  258. package/plugins/acryl-router/src/core/validator/utils.ts +69 -0
  259. package/plugins/acryl-router/src/core/validator/validator.test.ts +1488 -0
  260. package/plugins/acryl-router/src/core/validator/validator.ts +185 -0
  261. package/plugins/acryl-router/src/helpers/index.ts +13 -0
  262. package/plugins/acryl-router/src/helpers/schema-validator.ts +35 -0
  263. package/plugins/acryl-router/src/index.ts +6 -0
  264. package/plugins/acryl-router/src/middleware/index.ts +25 -0
  265. package/plugins/acryl-router/src/middleware/loopback-only.ts +88 -0
  266. package/plugins/acryl-router/src/service.ts +356 -0
  267. package/plugins/acryl-router/tests/docs-examples.spec.ts +152 -0
  268. package/plugins/acryl-router/tests/live-install.spec.ts +64 -0
  269. package/plugins/acryl-router/tests/loader.spec.ts +72 -0
  270. package/plugins/acryl-router/tests/service.spec.ts +409 -0
  271. package/plugins/acryl-router/tests/shadow-state.spec.ts +79 -0
  272. package/plugins/acryl-router/upstream.json +20 -0
  273. package/plugins/acryl-router-http/LICENSE +21 -0
  274. package/plugins/acryl-router-http/plugin.json +12 -0
  275. package/plugins/acryl-router-http/src/bridge.ts +54 -0
  276. package/plugins/acryl-router-http/src/index.ts +63 -0
  277. package/plugins/acryl-router-http/src/serve.ts +24 -0
  278. package/plugins/acryl-router-http/tests/adapter.spec.ts +174 -0
  279. package/plugins/acryl-router-http/tests/stream.spec.ts +133 -0
  280. package/plugins/acryl-router-http/tests/surfaces.spec.ts +101 -0
  281. package/plugins/acryl-router-http/upstream.json +22 -0
  282. package/plugins/acryl-state/LICENSE +21 -0
  283. package/plugins/acryl-state/NOTICE +30 -0
  284. package/plugins/acryl-state/plugin.json +12 -0
  285. package/plugins/acryl-state/src/cell.ts +67 -0
  286. package/plugins/acryl-state/src/index.ts +3 -0
  287. package/plugins/acryl-state/src/react.ts +11 -0
  288. package/plugins/acryl-state/src/service.ts +119 -0
  289. package/plugins/acryl-state/tests/cell.spec.ts +71 -0
  290. package/plugins/acryl-state/tests/react.spec.ts +39 -0
  291. package/plugins/acryl-state/tests/service.spec.ts +132 -0
  292. package/plugins/acryl-state/upstream.json +22 -0
  293. package/plugins/builder/script.ts +6 -10
  294. package/plugins/extensions/index.ts +20 -3
  295. package/plugins/extensions/resolve.ts +28 -3
  296. package/plugins/openai/prompt.ts +12 -6
  297. package/plugins/tasks/index.ts +14 -0
  298. package/plugins/tsconfig.json +14 -5
  299. package/templates/agent/blend.yaml +5 -7
  300. package/templates/agent/src/app.ts +8 -8
  301. package/templates/agent/src/routes.ts +39 -38
  302. package/templates/agent/template.json +3 -3
  303. package/templates/agent/test/builder.test.ts +22 -14
  304. package/templates/agent/test/model.test.ts +3 -3
  305. package/templates/agent/tsconfig.json +8 -5
  306. package/templates/{app → blank}/blend.yaml +3 -3
  307. package/templates/{app → blank}/src/app.ts +5 -5
  308. package/templates/blank/src/routes.ts +16 -0
  309. package/templates/blank/template.json +1 -0
  310. package/templates/{app → blank}/test/app.test.ts +1 -1
  311. package/templates/{app → blank}/tsconfig.json +2 -4
  312. package/templates/todo/README.md +27 -0
  313. package/templates/todo/blend.yaml +25 -0
  314. package/templates/todo/gitignore +2 -0
  315. package/templates/todo/package.json +23 -0
  316. package/templates/todo/src/app.ts +71 -0
  317. package/templates/todo/src/main.ts +19 -0
  318. package/templates/todo/src/page.ts +28 -0
  319. package/templates/todo/src/todo.ts +123 -0
  320. package/templates/todo/template.json +1 -0
  321. package/templates/todo/test/todo.test.ts +145 -0
  322. package/templates/todo/tsconfig.json +17 -0
  323. package/templates/todo/upstream.json +12 -0
  324. package/plugins/events/index.ts +0 -42
  325. package/plugins/events/plugin.json +0 -9
  326. package/plugins/http/index.ts +0 -64
  327. package/plugins/http/plugin.json +0 -10
  328. package/plugins/server/index.ts +0 -138
  329. package/plugins/server/plugin.json +0 -10
  330. package/templates/app/src/routes.ts +0 -14
  331. package/templates/app/template.json +0 -1
  332. /package/templates/{app → blank}/README.md +0 -0
  333. /package/templates/{app → blank}/gitignore +0 -0
  334. /package/templates/{app → blank}/package.json +0 -0
  335. /package/templates/{app → blank}/src/main.ts +0 -0
@@ -0,0 +1,96 @@
1
+ import type { OutgoingHttpHeaders } from 'node:http'
2
+ import type { Writable } from 'node:stream'
3
+
4
+ export async function readWithoutBlocking(
5
+ readPromise: Promise<ReadableStreamReadResult<Uint8Array>>
6
+ ): Promise<ReadableStreamReadResult<Uint8Array> | undefined> {
7
+ return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(undefined))])
8
+ }
9
+
10
+ export function writeFromReadableStreamDefaultReader(
11
+ reader: ReadableStreamDefaultReader<Uint8Array>,
12
+ writable: Writable,
13
+ currentReadPromise?: Promise<ReadableStreamReadResult<Uint8Array>> | undefined
14
+ ) {
15
+ const cancel = (error?: unknown) => {
16
+ reader.cancel(error).catch(() => {})
17
+ }
18
+
19
+ writable.on('close', cancel)
20
+ writable.on('error', cancel)
21
+ ;(currentReadPromise ?? reader.read()).then(flow, handleStreamError)
22
+
23
+ return reader.closed.finally(() => {
24
+ writable.off('close', cancel)
25
+ writable.off('error', cancel)
26
+ })
27
+
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ function handleStreamError(error: any) {
30
+ if (error) {
31
+ writable.destroy(error)
32
+ }
33
+ }
34
+
35
+ function onDrain() {
36
+ reader.read().then(flow, handleStreamError)
37
+ }
38
+
39
+ function flow({ done, value }: ReadableStreamReadResult<Uint8Array>): void | Promise<void> {
40
+ try {
41
+ if (done) {
42
+ writable.end()
43
+ } else if (!writable.write(value)) {
44
+ writable.once('drain', onDrain)
45
+ } else {
46
+ return reader.read().then(flow, handleStreamError)
47
+ }
48
+ } catch (e) {
49
+ handleStreamError(e)
50
+ }
51
+ }
52
+ }
53
+
54
+ export function writeFromReadableStream(stream: ReadableStream<Uint8Array>, writable: Writable) {
55
+ if (stream.locked) {
56
+ throw new TypeError('ReadableStream is locked.')
57
+ } else if (writable.destroyed) {
58
+ return
59
+ }
60
+
61
+ return writeFromReadableStreamDefaultReader(stream.getReader(), writable)
62
+ }
63
+
64
+ export const buildOutgoingHttpHeaders = (
65
+ headers: Headers | HeadersInit | null | undefined,
66
+ defaultContentType: string | undefined
67
+ ): OutgoingHttpHeaders => {
68
+ const res: OutgoingHttpHeaders = {}
69
+ if (!(headers instanceof Headers)) {
70
+ headers = new Headers(headers ?? undefined)
71
+ }
72
+
73
+ if (headers.has('set-cookie')) {
74
+ const cookies = []
75
+ for (const [k, v] of headers) {
76
+ if (k === 'set-cookie') {
77
+ cookies.push(v)
78
+ } else {
79
+ res[k] = v
80
+ }
81
+ }
82
+ if (cookies.length > 0) {
83
+ res['set-cookie'] = cookies
84
+ }
85
+ } else {
86
+ for (const [k, v] of headers) {
87
+ res[k] = v
88
+ }
89
+ }
90
+
91
+ if (defaultContentType) {
92
+ res['content-type'] ??= defaultContentType
93
+ }
94
+
95
+ return res
96
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * This is the minimal public interface for WebSocket that is compatible with `ws` (`@types/ws`)
3
+ * https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/ws/index.d.ts
4
+ *
5
+ * If you need more methods, copy the extra types over from the types file linked above.
6
+ * Don't import types from `ws` directly, as it will cause issues with users who have `skipLibCheck` enabled.
7
+ */
8
+ import type { IncomingMessage } from 'node:http'
9
+ import type { Duplex } from 'node:stream'
10
+
11
+ type WSReadyState = 0 | 1 | 2 | 3
12
+
13
+ export type WebSocketData = string | ArrayBuffer | Uint8Array | readonly Uint8Array[]
14
+
15
+ export type WebSocketSendOptions = {
16
+ compress?: boolean
17
+ }
18
+
19
+ export interface WebSocketLike {
20
+ protocol: string
21
+ readyState: WSReadyState
22
+ close(code?: number, reason?: string): void
23
+ send(data: string | ArrayBuffer | ArrayBufferView, options?: WebSocketSendOptions): void
24
+ on(event: 'message', listener: (data: WebSocketData, isBinary: boolean) => void): this
25
+ on(event: 'close', listener: (code: number, reason: Uint8Array) => void): this
26
+ on(event: 'error', listener: (error: unknown) => void): this
27
+ off(event: 'message', listener: (data: WebSocketData, isBinary: boolean) => void): this
28
+ }
29
+
30
+ export interface WebSocketServerLike {
31
+ options: {
32
+ noServer?: boolean
33
+ }
34
+ on(event: 'connection', listener: (ws: WebSocketLike, request: IncomingMessage) => void): this
35
+ on(event: 'headers', listener: (headers: string[]) => void): this
36
+ off(event: 'headers', listener: (headers: string[]) => void): this
37
+ emit(event: 'connection', ws: WebSocketLike, request: IncomingMessage): boolean
38
+ handleUpgrade(
39
+ request: IncomingMessage,
40
+ socket: Duplex,
41
+ head: Buffer,
42
+ callback: (ws: WebSocketLike) => void
43
+ ): void
44
+ close(): void
45
+ }
@@ -0,0 +1,375 @@
1
+ import type { UpgradeWebSocket, WSContext } from '../../../acryl-router/src/helpers/index.ts'
2
+ import { defineWebSocketHelper } from '../../../acryl-router/src/helpers/index.ts'
3
+ import type { IncomingMessage } from 'node:http'
4
+ import { STATUS_CODES } from 'node:http'
5
+ import type { Duplex } from 'node:stream'
6
+ import type { FetchCallback, ServerType } from './types.ts'
7
+ import type { WebSocketData, WebSocketLike, WebSocketServerLike } from './websocket-types.ts'
8
+
9
+ interface CloseEventInit extends EventInit {
10
+ code?: number
11
+ reason?: string
12
+ wasClean?: boolean
13
+ }
14
+
15
+ /**
16
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
17
+ */
18
+ export const CloseEvent: typeof globalThis.CloseEvent =
19
+ globalThis.CloseEvent ??
20
+ class extends Event {
21
+ #eventInitDict
22
+
23
+ constructor(type: string, eventInitDict: CloseEventInit = {}) {
24
+ super(type, eventInitDict)
25
+ this.#eventInitDict = eventInitDict
26
+ }
27
+
28
+ get wasClean(): boolean {
29
+ return this.#eventInitDict.wasClean ?? false
30
+ }
31
+
32
+ get code(): number {
33
+ return this.#eventInitDict.code ?? 0
34
+ }
35
+
36
+ get reason(): string {
37
+ return this.#eventInitDict.reason ?? ''
38
+ }
39
+ }
40
+
41
+ interface ErrorEventInit extends EventInit {
42
+ message?: string
43
+ filename?: string
44
+ lineno?: number
45
+ colno?: number
46
+ error?: unknown
47
+ }
48
+
49
+ /**
50
+ * Node.js has no global `ErrorEvent`, unlike `Event`/`MessageEvent`/`CloseEvent`.
51
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent
52
+ */
53
+ export const ErrorEvent: typeof globalThis.ErrorEvent =
54
+ globalThis.ErrorEvent ??
55
+ class extends Event {
56
+ #eventInitDict
57
+
58
+ constructor(type: string, eventInitDict: ErrorEventInit = {}) {
59
+ super(type, eventInitDict)
60
+ this.#eventInitDict = eventInitDict
61
+ }
62
+
63
+ get message(): string {
64
+ return this.#eventInitDict.message ?? ''
65
+ }
66
+
67
+ get filename(): string {
68
+ return this.#eventInitDict.filename ?? ''
69
+ }
70
+
71
+ get lineno(): number {
72
+ return this.#eventInitDict.lineno ?? 0
73
+ }
74
+
75
+ get colno(): number {
76
+ return this.#eventInitDict.colno ?? 0
77
+ }
78
+
79
+ get error(): unknown {
80
+ return this.#eventInitDict.error ?? null
81
+ }
82
+ }
83
+
84
+ const generateConnectionSymbol = () => Symbol('connection')
85
+
86
+ type WaitForWebSocket = (
87
+ request: IncomingMessage,
88
+ connectionSymbol: symbol
89
+ ) => Promise<WebSocketLike>
90
+
91
+ const CONNECTION_SYMBOL_KEY: unique symbol = Symbol('CONNECTION_SYMBOL_KEY')
92
+ const WAIT_FOR_WEBSOCKET_SYMBOL: unique symbol = Symbol('WAIT_FOR_WEBSOCKET_SYMBOL')
93
+
94
+ export type UpgradeBindings = {
95
+ incoming: IncomingMessage
96
+ outgoing: undefined
97
+ wss: WebSocketServerLike
98
+ [CONNECTION_SYMBOL_KEY]?: symbol
99
+ [WAIT_FOR_WEBSOCKET_SYMBOL]?: WaitForWebSocket
100
+ }
101
+
102
+ type UpgradeWebSocketOptions = {
103
+ onError: (err: unknown) => void
104
+ }
105
+
106
+ // Hop-by-hop headers per RFC 9110 Section 7.6.1
107
+ // (https://www.rfc-editor.org/rfc/rfc9110.html#name-connection) plus
108
+ // `keep-alive` (commonly treated as hop-by-hop by HTTP implementations) and
109
+ // WebSocket handshake headers managed by `ws` itself. These must not be
110
+ // forwarded onto the upgrade response or the handshake will be corrupted.
111
+ const responseHeadersToSkip = new Set([
112
+ 'connection',
113
+ 'content-length',
114
+ 'keep-alive',
115
+ 'proxy-authenticate',
116
+ 'proxy-authorization',
117
+ 'te',
118
+ 'trailer',
119
+ 'transfer-encoding',
120
+ 'upgrade',
121
+ 'sec-websocket-accept',
122
+ 'sec-websocket-extensions',
123
+ 'sec-websocket-protocol',
124
+ ])
125
+
126
+ const appendResponseHeaders = (headers: string[], responseHeaders?: Headers) => {
127
+ if (!responseHeaders) {
128
+ return
129
+ }
130
+ responseHeaders.forEach((value, key) => {
131
+ if (responseHeadersToSkip.has(key.toLowerCase())) {
132
+ return
133
+ }
134
+ headers.push(`${key}: ${value}`)
135
+ })
136
+ }
137
+
138
+ const rejectUpgradeRequest = (socket: Duplex, status: number, responseHeaders?: Headers) => {
139
+ const responseLines = ['Connection: close', 'Content-Length: 0']
140
+ appendResponseHeaders(responseLines, responseHeaders)
141
+
142
+ socket.end(
143
+ `HTTP/1.1 ${status.toString()} ${STATUS_CODES[status] ?? ''}\r\n` +
144
+ `${responseLines.join('\r\n')}\r\n` +
145
+ '\r\n'
146
+ )
147
+ }
148
+
149
+ const createUpgradeRequest = (request: IncomingMessage): Request => {
150
+ const protocol = (request.socket as { encrypted?: boolean }).encrypted ? 'https' : 'http'
151
+ const url = new URL(request.url ?? '/', `${protocol}://${request.headers.host ?? 'localhost'}`)
152
+ const headers = new Headers()
153
+ for (const key in request.headers) {
154
+ const value = request.headers[key]
155
+ if (!value) {
156
+ continue
157
+ }
158
+ headers.append(key, Array.isArray(value) ? value[0] : value)
159
+ }
160
+ return new Request(url, {
161
+ headers,
162
+ })
163
+ }
164
+
165
+ export const setupWebSocket = (options: {
166
+ server: ServerType
167
+ fetchCallback: FetchCallback
168
+ wss: WebSocketServerLike
169
+ }): void => {
170
+ const { server, fetchCallback, wss } = options
171
+
172
+ const waiterMap = new Map<
173
+ IncomingMessage,
174
+ {
175
+ resolve: (ws: WebSocketLike) => void
176
+ reject: (reason: Error) => void
177
+ connectionSymbol: symbol
178
+ }
179
+ >()
180
+
181
+ wss.on('connection', (ws, request) => {
182
+ const waiter = waiterMap.get(request)
183
+ if (waiter) {
184
+ waiter.resolve(ws)
185
+ waiterMap.delete(request)
186
+ }
187
+ })
188
+
189
+ const rejectWaiter = (request: IncomingMessage) => {
190
+ const waiter = waiterMap.get(request)
191
+ if (waiter) {
192
+ waiterMap.delete(request)
193
+ waiter.reject(new Error('WebSocket handshake aborted'))
194
+ }
195
+ }
196
+
197
+ const waitForWebSocket: WaitForWebSocket = (request, connectionSymbol) => {
198
+ return new Promise<WebSocketLike>((resolve, reject) => {
199
+ waiterMap.set(request, { resolve, reject, connectionSymbol })
200
+ })
201
+ }
202
+
203
+ server.on('upgrade', async (request, socket: Duplex, head) => {
204
+ if (request.headers.upgrade?.toLowerCase() !== 'websocket') {
205
+ return
206
+ }
207
+
208
+ const env: UpgradeBindings = {
209
+ incoming: request,
210
+ outgoing: undefined,
211
+ wss,
212
+ [WAIT_FOR_WEBSOCKET_SYMBOL]: waitForWebSocket,
213
+ }
214
+
215
+ let status = 400
216
+ let responseHeaders: Headers | undefined
217
+ try {
218
+ const response = (await fetchCallback(
219
+ createUpgradeRequest(request),
220
+ env as unknown as Parameters<FetchCallback>[1]
221
+ )) as Response
222
+ if (response instanceof Response) {
223
+ status = response.status
224
+ responseHeaders = response.headers
225
+ }
226
+ } catch {
227
+ if (server.listenerCount('upgrade') === 1) {
228
+ rejectUpgradeRequest(socket, 500)
229
+ }
230
+ return
231
+ }
232
+
233
+ const waiter = waiterMap.get(request)
234
+
235
+ if (!waiter || waiter.connectionSymbol !== env[CONNECTION_SYMBOL_KEY]) {
236
+ rejectWaiter(request)
237
+ if (server.listenerCount('upgrade') === 1) {
238
+ rejectUpgradeRequest(socket, status, responseHeaders)
239
+ }
240
+ return
241
+ }
242
+
243
+ const addResponseHeaders = (headers: string[]) => {
244
+ appendResponseHeaders(headers, responseHeaders)
245
+ }
246
+
247
+ const reclaimWaiterOnClose = () => rejectWaiter(request)
248
+ socket.once('close', reclaimWaiterOnClose)
249
+
250
+ wss.on('headers', addResponseHeaders)
251
+ try {
252
+ wss.handleUpgrade(request, socket, head, (ws) => {
253
+ socket.off('close', reclaimWaiterOnClose)
254
+ wss.emit('connection', ws, request)
255
+ })
256
+ } finally {
257
+ wss.off('headers', addResponseHeaders)
258
+ }
259
+ })
260
+
261
+ server.on('close', () => {
262
+ wss.close()
263
+ })
264
+ }
265
+
266
+ export const upgradeWebSocket: UpgradeWebSocket<WebSocketLike, UpgradeWebSocketOptions> =
267
+ defineWebSocketHelper(async (c, events, options) => {
268
+ if (c.req.header('upgrade')?.toLowerCase() !== 'websocket') {
269
+ return
270
+ }
271
+
272
+ const env = c.env as UpgradeBindings
273
+ const waitForWebSocket = env[WAIT_FOR_WEBSOCKET_SYMBOL]
274
+
275
+ if (!waitForWebSocket || !env.incoming) {
276
+ return new Response(null, { status: 500 })
277
+ }
278
+
279
+ const connectionSymbol = generateConnectionSymbol()
280
+ env[CONNECTION_SYMBOL_KEY] = connectionSymbol
281
+ ;(async () => {
282
+ let ws: WebSocketLike
283
+ try {
284
+ ws = await waitForWebSocket(env.incoming, connectionSymbol)
285
+ } catch {
286
+ return
287
+ }
288
+
289
+ const messagesReceivedInStarting: [data: WebSocketData, isBinary: boolean][] = []
290
+ const bufferMessage = (data: WebSocketData, isBinary: boolean) => {
291
+ messagesReceivedInStarting.push([data, isBinary])
292
+ }
293
+ ws.on('message', bufferMessage)
294
+
295
+ // A plain object with the class's public shape: the class's private field is not part of the shape, so it is asserted once.
296
+ const shape: Pick<WSContext<WebSocketLike>, keyof WSContext<WebSocketLike>> = {
297
+ binaryType: 'arraybuffer',
298
+ close(code, reason) {
299
+ ws.close(code, reason)
300
+ },
301
+ protocol: ws.protocol,
302
+ raw: ws,
303
+ get readyState() {
304
+ return ws.readyState
305
+ },
306
+ send(source, opts) {
307
+ ws.send(source, {
308
+ compress: opts?.compress,
309
+ })
310
+ },
311
+ url: new URL(c.req.url),
312
+ }
313
+ const ctx = shape as WSContext<WebSocketLike>
314
+
315
+ try {
316
+ events?.onOpen?.(new Event('open'), ctx)
317
+ } catch (e) {
318
+ ;(options?.onError ?? console.error)(e)
319
+ }
320
+
321
+ const handleMessage = (data: WebSocketData, isBinary: boolean) => {
322
+ const datas = Array.isArray(data) ? data : [data]
323
+ for (const data of datas) {
324
+ try {
325
+ events?.onMessage?.(
326
+ new MessageEvent('message', {
327
+ data: isBinary
328
+ ? data instanceof ArrayBuffer
329
+ ? data
330
+ : data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)
331
+ : typeof data === 'string'
332
+ ? data
333
+ : Buffer.from(data).toString('utf-8'),
334
+ }),
335
+ ctx
336
+ )
337
+ } catch (e) {
338
+ ;(options?.onError ?? console.error)(e)
339
+ }
340
+ }
341
+ }
342
+
343
+ ws.off('message', bufferMessage)
344
+ for (const message of messagesReceivedInStarting) {
345
+ handleMessage(...message)
346
+ }
347
+
348
+ ws.on('message', (data, isBinary) => {
349
+ handleMessage(data, isBinary)
350
+ })
351
+
352
+ ws.on('close', (code, reason) => {
353
+ try {
354
+ events?.onClose?.(new CloseEvent('close', { code, reason: reason.toString() }), ctx)
355
+ } catch (e) {
356
+ ;(options?.onError ?? console.error)(e)
357
+ }
358
+ })
359
+
360
+ ws.on('error', (error) => {
361
+ try {
362
+ events?.onError?.(
363
+ new ErrorEvent('error', {
364
+ error,
365
+ }),
366
+ ctx
367
+ )
368
+ } catch (e) {
369
+ ;(options?.onError ?? console.error)(e)
370
+ }
371
+ })
372
+ })()
373
+
374
+ return new Response()
375
+ })
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The framework's standalone HTTP adapter (spec 046, ported in spec 047), the standalone counterpart of the Host adapter. It owns one
3
+ * `node:http` server built by the ported adapter in `src/adapter/` and hands every request to `ctx.router.fetch`, with the socket's peer
4
+ * address and local port as connection facts.
5
+ * Use it in an app that runs without the ACRYL Host; inside the Host, `acryl-router-http` mounts prefixes on the Host's own web server.
6
+ * One adapter per runtime: a plugin keeps asking `ctx.inject(['routerHttp'], ...)` for a mount, which simply never fires here, because this
7
+ * adapter serves the whole router.
8
+ *
9
+ * A configured port that is taken is followed by the next free one, at most `scan` further ports; port 0 lets the system choose. Disposal ends
10
+ * open streams, closes every connection and returns once the port is free.
11
+ */
12
+
13
+ import type { Server } from 'node:http'
14
+ import type { AddressInfo } from 'node:net'
15
+ import { Context, Service } from '@deepseek-ai/cordis'
16
+ import z from '@deepseek-ai/schemastery'
17
+ import type {} from '../../acryl-router/src/index.ts'
18
+ import { Request as AdapterRequest } from './adapter/request.ts'
19
+ import { createAdaptorServer } from './adapter/server.ts'
20
+
21
+ export { serveStatic, type ServeStaticOptions } from './adapter/serve-static.ts'
22
+ export { RequestError } from './adapter/request.ts'
23
+
24
+ /** Stable Cordis plugin name, equal to the package name and the Loader row id. */
25
+ export const name = 'acryl-node-server'
26
+ export const inject = ['router']
27
+
28
+ export interface Config {
29
+ hostname: string
30
+ port: number
31
+ scan: number
32
+ }
33
+
34
+ /** Every field has a default, so a caller passes only what it changes: `ctx.plugin(plugin, { ... })` takes the partial shape. */
35
+ export const Config: z<Partial<Config>, Config> = z.object({
36
+ hostname: z.string().default('127.0.0.1').description('Address to bind. Loopback by default; binding elsewhere exposes the app to the network.'),
37
+ port: z.number().min(0).max(65535).default(0).description('Port to listen on; 0 lets the system choose. A taken port is followed by the next free one.'),
38
+ scan: z.number().min(0).default(20).description('How many further ports to try when the configured one is taken.'),
39
+ })
40
+
41
+ declare module '@deepseek-ai/cordis' {
42
+ interface Context {
43
+ nodeServer: NodeServer
44
+ }
45
+ }
46
+
47
+ export class NodeServer extends Service {
48
+ readonly hostname: string
49
+ readonly config: Config
50
+ private readonly server: Server
51
+ private bound = 0
52
+
53
+ constructor(ctx: Context, config: Config) {
54
+ super(ctx, 'nodeServer')
55
+ this.config = config
56
+ this.hostname = config.hostname
57
+ const router = ctx.router
58
+ // The handler sees the router's bindings (`c.env.connection`), not the raw `incoming`/`outgoing` pair. The global `Request` and
59
+ // `Response` stay the platform's, because this adapter shares its process with the rest of the app; so the adapter's lightweight
60
+ // request is made a real one (its signal still aborts when the client goes away) before the router re-creates it with its own signal.
61
+ this.server = createAdaptorServer({
62
+ fetch: (request, { incoming }) => router.fetch(new AdapterRequest(request), { remoteAddress: incoming.socket.remoteAddress, localPort: incoming.socket.localPort }),
63
+ hostname: config.hostname,
64
+ overrideGlobalObjects: false,
65
+ }) as Server
66
+ ctx.effect(() => async () => {
67
+ this.server.closeAllConnections()
68
+ if (this.server.listening) await new Promise<void>(resolve => { this.server.close(() => { resolve() }) })
69
+ }, 'acryl-node-server: close listener and open connections')
70
+ }
71
+
72
+ /** The port in use, 0 before the server listens. */
73
+ get port(): number {
74
+ return this.bound
75
+ }
76
+
77
+ /** The address clients use. */
78
+ get url(): string {
79
+ return `http://${this.hostname.includes(':') ? `[${this.hostname}]` : this.hostname}:${String(this.bound)}`
80
+ }
81
+
82
+ /** Bind, scanning past taken ports. */
83
+ async listen(): Promise<void> {
84
+ const { port, scan, hostname } = this.config
85
+ const last = port === 0 ? 0 : Math.min(65535, port + scan)
86
+ for (let candidate = port; candidate <= last; candidate += 1) {
87
+ try {
88
+ await new Promise<void>((resolve, reject) => {
89
+ const onError = (error: Error): void => { reject(error) }
90
+ this.server.once('error', onError)
91
+ this.server.listen(candidate, hostname, () => { this.server.off('error', onError); resolve() })
92
+ })
93
+ this.bound = (this.server.address() as AddressInfo).port
94
+ return
95
+ } catch (error) {
96
+ if ((error as NodeJS.ErrnoException).code !== 'EADDRINUSE') throw error
97
+ }
98
+ }
99
+ throw new Error(`acryl-node-server: no free port from ${String(port)} to ${String(last)} on ${hostname}`)
100
+ }
101
+ }
102
+
103
+ /** Loads once the server listens, so a plugin that injects `nodeServer` can rely on `url`. A failure to bind fails the plugin. */
104
+ export async function apply(ctx: Context, config: Partial<Config> = {}): Promise<void> {
105
+ const server = new NodeServer(ctx, { hostname: '127.0.0.1', port: 0, scan: 20, ...config })
106
+ await server.listen()
107
+ }
@@ -0,0 +1,76 @@
1
+ import { Response as PonyfillResponse } from '@whatwg-node/fetch'
2
+ import { App } from '../../../acryl-router/src/index.ts'
3
+
4
+ export const app = new App()
5
+
6
+ app.get('/', (c) => c.text('Hello! Node!'))
7
+ app.get('/url', (c) => c.text(c.req.url))
8
+
9
+ app.get('/posts', (c) => {
10
+ return c.text(`Page ${c.req.query('page')}`)
11
+ })
12
+ app.get('/user-agent', (c) => {
13
+ return c.text(c.req.header('user-agent') as string)
14
+ })
15
+ app.post('/posts', (c) => {
16
+ return c.redirect('/posts')
17
+ })
18
+ app.post('/body-consumed', async (c) => {
19
+ return c.text(`Body length: ${(await c.req.text()).length}`)
20
+ })
21
+ app.post('/no-body-consumed', (c) => {
22
+ if (!c.req.raw.body) {
23
+ // force create new request object
24
+ throw new Error('No body consumed')
25
+ }
26
+ return c.text('No body consumed')
27
+ })
28
+ app.post('/body-cancelled', (c) => {
29
+ if (!c.req.raw.body) {
30
+ // force create new request object
31
+ throw new Error('No body consumed')
32
+ }
33
+ c.req.raw.body.cancel()
34
+ return c.text('Body cancelled')
35
+ })
36
+ app.post('/partially-consumed', async (c) => {
37
+ if (!c.req.raw.body) {
38
+ // force create new request object
39
+ throw new Error('No body consumed')
40
+ }
41
+ const reader = c.req.raw.body.getReader()
42
+ await reader.read() // read only one chunk
43
+ return c.text('Partially consumed')
44
+ })
45
+ app.post('/partially-consumed-and-cancelled', async (c) => {
46
+ if (!c.req.raw.body) {
47
+ // force create new request object
48
+ throw new Error('No body consumed')
49
+ }
50
+ const reader = c.req.raw.body.getReader()
51
+ await reader.read() // read only one chunk
52
+ reader.cancel()
53
+ return c.text('Partially consumed and cancelled')
54
+ })
55
+ app.post('/early-413', (c) => {
56
+ if (!c.req.raw.body) {
57
+ // force create new request object
58
+ throw new Error('No body consumed')
59
+ }
60
+ return c.text('Payload Too Large', 413)
61
+ })
62
+ app.delete('/posts/:id', (c) => {
63
+ return c.text(`DELETE ${c.req.param('id')}`)
64
+ })
65
+ // @ts-expect-error the response is string
66
+ app.get('/invalid', () => {
67
+ return '<h1>HTML</h1>'
68
+ })
69
+ app.get('/ponyfill', () => {
70
+ return new PonyfillResponse('Pony')
71
+ })
72
+
73
+ app.on('trace', '/', (c) => {
74
+ const headers = c.req.raw.headers // build new request object
75
+ return c.text(`headers: ${JSON.stringify(headers)}`)
76
+ })