acrylblends 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/dist/index.d.ts +6 -1
  2. package/dist/index.js +84 -5
  3. package/dist/index.js.map +1 -1
  4. package/docs/framework/LICENSE +23 -0
  5. package/docs/framework/README.md +63 -0
  6. package/docs/framework/UPSTREAM.md +3 -0
  7. package/docs/framework/api-summary.md +138 -0
  8. package/docs/framework/concepts.md +105 -0
  9. package/docs/framework/context.md +129 -0
  10. package/docs/framework/getting-started.md +146 -0
  11. package/docs/framework/helpers.md +118 -0
  12. package/docs/framework/middleware.md +154 -0
  13. package/docs/framework/migration-from-0-3.md +108 -0
  14. package/docs/framework/migration-from-dsh-webserver.md +103 -0
  15. package/docs/framework/node-server.md +75 -0
  16. package/docs/framework/plugin-lifecycle.md +111 -0
  17. package/docs/framework/request.md +106 -0
  18. package/docs/framework/routing.md +188 -0
  19. package/docs/framework/surfaces.md +71 -0
  20. package/docs/framework/upstream.json +9 -0
  21. package/docs/framework/validation.md +135 -0
  22. package/package.json +4 -2
  23. package/plugins/acryl-inspect/plugin.json +2 -1
  24. package/plugins/acryl-inspect/src/report.ts +21 -13
  25. package/plugins/acryl-inspect/src/service.ts +9 -7
  26. package/plugins/acryl-inspect/tests/report.spec.ts +13 -8
  27. package/plugins/acryl-inspect/tests/service.spec.ts +11 -10
  28. package/plugins/acryl-inspect/upstream.json +2 -2
  29. package/plugins/acryl-node-server/LICENSE +26 -0
  30. package/plugins/acryl-node-server/plugin.json +2 -1
  31. package/plugins/acryl-node-server/src/adapter/conninfo.ts +23 -0
  32. package/plugins/acryl-node-server/src/adapter/early-hints.ts +55 -0
  33. package/plugins/acryl-node-server/src/adapter/error.ts +11 -0
  34. package/plugins/acryl-node-server/src/adapter/headers.ts +249 -0
  35. package/plugins/acryl-node-server/src/adapter/index.ts +6 -0
  36. package/plugins/acryl-node-server/src/adapter/listener.ts +540 -0
  37. package/plugins/acryl-node-server/src/adapter/request.ts +797 -0
  38. package/plugins/acryl-node-server/src/adapter/response.ts +186 -0
  39. package/plugins/acryl-node-server/src/adapter/serve-static.ts +238 -0
  40. package/plugins/acryl-node-server/src/adapter/server.ts +36 -0
  41. package/plugins/acryl-node-server/src/adapter/types.ts +82 -0
  42. package/plugins/acryl-node-server/src/adapter/url.ts +42 -0
  43. package/plugins/acryl-node-server/src/adapter/utils/response/constants.ts +1 -0
  44. package/plugins/acryl-node-server/src/adapter/utils/response.ts +4 -0
  45. package/plugins/acryl-node-server/src/adapter/utils/stream.ts +88 -0
  46. package/plugins/acryl-node-server/src/adapter/utils.ts +96 -0
  47. package/plugins/acryl-node-server/src/adapter/websocket-types.ts +45 -0
  48. package/plugins/acryl-node-server/src/adapter/websocket.ts +375 -0
  49. package/plugins/acryl-node-server/src/index.ts +19 -6
  50. package/plugins/acryl-node-server/tests/adapter/app.ts +76 -0
  51. package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +1 -0
  52. package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
  53. package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +1 -0
  54. package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +1 -0
  55. package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +1 -0
  56. package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +5 -0
  57. package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +1 -0
  58. package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
  59. package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +1 -0
  60. package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +1 -0
  61. package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +1 -0
  62. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +1 -0
  63. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +1 -0
  64. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +1 -0
  65. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +1 -0
  66. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +1 -0
  67. package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +34 -0
  68. package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +248 -0
  69. package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +457 -0
  70. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +23 -0
  71. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +27 -0
  72. package/plugins/acryl-node-server/tests/adapter/headers.test.ts +252 -0
  73. package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +190 -0
  74. package/plugins/acryl-node-server/tests/adapter/listener.test.ts +727 -0
  75. package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +189 -0
  76. package/plugins/acryl-node-server/tests/adapter/request.test.ts +1230 -0
  77. package/plugins/acryl-node-server/tests/adapter/response.test.ts +285 -0
  78. package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +849 -0
  79. package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +790 -0
  80. package/plugins/acryl-node-server/tests/adapter/server.test.ts +1302 -0
  81. package/plugins/acryl-node-server/tests/adapter/setup.ts +16 -0
  82. package/plugins/acryl-node-server/tests/adapter/url.test.ts +76 -0
  83. package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +23 -0
  84. package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +75 -0
  85. package/plugins/acryl-node-server/tests/adapter/utils.test.ts +235 -0
  86. package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +260 -0
  87. package/plugins/acryl-node-server/tests/server.spec.ts +44 -8
  88. package/plugins/acryl-node-server/upstream.json +3 -3
  89. package/plugins/acryl-router/LICENSE +26 -0
  90. package/plugins/acryl-router/plugin.json +2 -1
  91. package/plugins/acryl-router/src/client.ts +2 -0
  92. package/plugins/acryl-router/src/connection.ts +42 -0
  93. package/plugins/acryl-router/src/core/app-base.ts +544 -0
  94. package/plugins/acryl-router/src/core/app.test.ts +3833 -0
  95. package/plugins/acryl-router/src/core/app.ts +34 -0
  96. package/plugins/acryl-router/src/core/client/client.test.ts +2313 -0
  97. package/plugins/acryl-router/src/core/client/client.ts +249 -0
  98. package/plugins/acryl-router/src/core/client/fetch-result-please.ts +95 -0
  99. package/plugins/acryl-router/src/core/client/index.ts +17 -0
  100. package/plugins/acryl-router/src/core/client/types.test.ts +149 -0
  101. package/plugins/acryl-router/src/core/client/types.ts +393 -0
  102. package/plugins/acryl-router/src/core/client/utils.test.ts +405 -0
  103. package/plugins/acryl-router/src/core/client/utils.ts +117 -0
  104. package/plugins/acryl-router/src/core/compose.test.ts +751 -0
  105. package/plugins/acryl-router/src/core/compose.ts +73 -0
  106. package/plugins/acryl-router/src/core/context.test.ts +629 -0
  107. package/plugins/acryl-router/src/core/context.ts +781 -0
  108. package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +344 -0
  109. package/plugins/acryl-router/src/core/helper/accepts/accepts.ts +96 -0
  110. package/plugins/acryl-router/src/core/helper/accepts/index.ts +6 -0
  111. package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +55 -0
  112. package/plugins/acryl-router/src/core/helper/adapter/index.ts +90 -0
  113. package/plugins/acryl-router/src/core/helper/conninfo/index.ts +6 -0
  114. package/plugins/acryl-router/src/core/helper/conninfo/types.ts +45 -0
  115. package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +510 -0
  116. package/plugins/acryl-router/src/core/helper/cookie/index.ts +146 -0
  117. package/plugins/acryl-router/src/core/helper/factory/index.test.ts +434 -0
  118. package/plugins/acryl-router/src/core/helper/factory/index.ts +375 -0
  119. package/plugins/acryl-router/src/core/helper/html/index.test.ts +78 -0
  120. package/plugins/acryl-router/src/core/helper/html/index.ts +57 -0
  121. package/plugins/acryl-router/src/core/helper/route/index.test.ts +259 -0
  122. package/plugins/acryl-router/src/core/helper/route/index.ts +144 -0
  123. package/plugins/acryl-router/src/core/helper/streaming/index.ts +9 -0
  124. package/plugins/acryl-router/src/core/helper/streaming/sse.ts +101 -0
  125. package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +128 -0
  126. package/plugins/acryl-router/src/core/helper/streaming/stream.ts +45 -0
  127. package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +34 -0
  128. package/plugins/acryl-router/src/core/helper/streaming/text.ts +15 -0
  129. package/plugins/acryl-router/src/core/helper/streaming/utils.ts +11 -0
  130. package/plugins/acryl-router/src/core/helper/testing/index.test.ts +42 -0
  131. package/plugins/acryl-router/src/core/helper/testing/index.ts +28 -0
  132. package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +124 -0
  133. package/plugins/acryl-router/src/core/helper/websocket/index.ts +140 -0
  134. package/plugins/acryl-router/src/core/http-exception.test.ts +45 -0
  135. package/plugins/acryl-router/src/core/http-exception.ts +77 -0
  136. package/plugins/acryl-router/src/core/index.ts +53 -0
  137. package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +434 -0
  138. package/plugins/acryl-router/src/core/middleware/basic-auth/index.ts +152 -0
  139. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +1018 -0
  140. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.ts +220 -0
  141. package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +273 -0
  142. package/plugins/acryl-router/src/core/middleware/body-limit/index.ts +110 -0
  143. package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +355 -0
  144. package/plugins/acryl-router/src/core/middleware/combine/index.ts +171 -0
  145. package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +535 -0
  146. package/plugins/acryl-router/src/core/middleware/compress/index.ts +142 -0
  147. package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +43 -0
  148. package/plugins/acryl-router/src/core/middleware/context-storage/index.ts +58 -0
  149. package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +532 -0
  150. package/plugins/acryl-router/src/core/middleware/cors/index.ts +163 -0
  151. package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +534 -0
  152. package/plugins/acryl-router/src/core/middleware/csrf/index.ts +150 -0
  153. package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +21 -0
  154. package/plugins/acryl-router/src/core/middleware/etag/digest.ts +95 -0
  155. package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +503 -0
  156. package/plugins/acryl-router/src/core/middleware/etag/index.ts +135 -0
  157. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +217 -0
  158. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.ts +278 -0
  159. package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +1135 -0
  160. package/plugins/acryl-router/src/core/middleware/jwk/index.ts +1 -0
  161. package/plugins/acryl-router/src/core/middleware/jwk/jwk.ts +195 -0
  162. package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +48 -0
  163. package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +802 -0
  164. package/plugins/acryl-router/src/core/middleware/jwt/index.ts +9 -0
  165. package/plugins/acryl-router/src/core/middleware/jwt/jwt.ts +189 -0
  166. package/plugins/acryl-router/src/core/middleware/language/index.test.ts +552 -0
  167. package/plugins/acryl-router/src/core/middleware/language/index.ts +12 -0
  168. package/plugins/acryl-router/src/core/middleware/language/language.ts +314 -0
  169. package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +204 -0
  170. package/plugins/acryl-router/src/core/middleware/logger/index.ts +95 -0
  171. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +292 -0
  172. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.ts +141 -0
  173. package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +221 -0
  174. package/plugins/acryl-router/src/core/middleware/method-override/index.ts +147 -0
  175. package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +37 -0
  176. package/plugins/acryl-router/src/core/middleware/powered-by/index.ts +35 -0
  177. package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +218 -0
  178. package/plugins/acryl-router/src/core/middleware/pretty-json/index.ts +67 -0
  179. package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +168 -0
  180. package/plugins/acryl-router/src/core/middleware/request-id/index.ts +7 -0
  181. package/plugins/acryl-router/src/core/middleware/request-id/request-id.ts +59 -0
  182. package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +671 -0
  183. package/plugins/acryl-router/src/core/middleware/secure-headers/index.ts +8 -0
  184. package/plugins/acryl-router/src/core/middleware/secure-headers/permissions-policy.ts +101 -0
  185. package/plugins/acryl-router/src/core/middleware/secure-headers/secure-headers.ts +340 -0
  186. package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +450 -0
  187. package/plugins/acryl-router/src/core/middleware/serve-static/index.ts +135 -0
  188. package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +50 -0
  189. package/plugins/acryl-router/src/core/middleware/serve-static/path.ts +25 -0
  190. package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +70 -0
  191. package/plugins/acryl-router/src/core/middleware/timeout/index.ts +58 -0
  192. package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +310 -0
  193. package/plugins/acryl-router/src/core/middleware/timing/index.ts +8 -0
  194. package/plugins/acryl-router/src/core/middleware/timing/timing.ts +256 -0
  195. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +381 -0
  196. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.ts +156 -0
  197. package/plugins/acryl-router/src/core/request/constants.ts +1 -0
  198. package/plugins/acryl-router/src/core/request.test.ts +751 -0
  199. package/plugins/acryl-router/src/core/request.ts +502 -0
  200. package/plugins/acryl-router/src/core/router/common.case.test.ts +1132 -0
  201. package/plugins/acryl-router/src/core/router/linear-router/index.ts +6 -0
  202. package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +80 -0
  203. package/plugins/acryl-router/src/core/router/linear-router/router.ts +152 -0
  204. package/plugins/acryl-router/src/core/router/pattern-router/index.ts +6 -0
  205. package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +46 -0
  206. package/plugins/acryl-router/src/core/router/pattern-router/router.ts +54 -0
  207. package/plugins/acryl-router/src/core/router/reg-exp-router/index.ts +7 -0
  208. package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.ts +33 -0
  209. package/plugins/acryl-router/src/core/router/reg-exp-router/node.ts +170 -0
  210. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +154 -0
  211. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.ts +165 -0
  212. package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +265 -0
  213. package/plugins/acryl-router/src/core/router/reg-exp-router/router.ts +174 -0
  214. package/plugins/acryl-router/src/core/router/reg-exp-router/trie.ts +84 -0
  215. package/plugins/acryl-router/src/core/router/smart-router/index.ts +6 -0
  216. package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +13 -0
  217. package/plugins/acryl-router/src/core/router/smart-router/router.ts +70 -0
  218. package/plugins/acryl-router/src/core/router/trie-router/index.ts +6 -0
  219. package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +896 -0
  220. package/plugins/acryl-router/src/core/router/trie-router/node.ts +221 -0
  221. package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +8 -0
  222. package/plugins/acryl-router/src/core/router/trie-router/router.ts +18 -0
  223. package/plugins/acryl-router/src/core/router/utils.ts +1 -0
  224. package/plugins/acryl-router/src/core/router.ts +103 -0
  225. package/plugins/acryl-router/src/core/types.test.ts +4134 -0
  226. package/plugins/acryl-router/src/core/types.ts +2782 -0
  227. package/plugins/acryl-router/src/core/utils/accept.test.ts +299 -0
  228. package/plugins/acryl-router/src/core/utils/accept.ts +268 -0
  229. package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +56 -0
  230. package/plugins/acryl-router/src/core/utils/basic-auth.ts +26 -0
  231. package/plugins/acryl-router/src/core/utils/body.test.ts +496 -0
  232. package/plugins/acryl-router/src/core/utils/body.ts +263 -0
  233. package/plugins/acryl-router/src/core/utils/buffer.test.ts +147 -0
  234. package/plugins/acryl-router/src/core/utils/buffer.ts +117 -0
  235. package/plugins/acryl-router/src/core/utils/color.test.ts +23 -0
  236. package/plugins/acryl-router/src/core/utils/color.ts +56 -0
  237. package/plugins/acryl-router/src/core/utils/compress.ts +10 -0
  238. package/plugins/acryl-router/src/core/utils/concurrent.test.ts +69 -0
  239. package/plugins/acryl-router/src/core/utils/concurrent.ts +55 -0
  240. package/plugins/acryl-router/src/core/utils/constants.ts +4 -0
  241. package/plugins/acryl-router/src/core/utils/cookie.test.ts +460 -0
  242. package/plugins/acryl-router/src/core/utils/cookie.ts +281 -0
  243. package/plugins/acryl-router/src/core/utils/crypto.test.ts +43 -0
  244. package/plugins/acryl-router/src/core/utils/crypto.ts +58 -0
  245. package/plugins/acryl-router/src/core/utils/encode.test.ts +61 -0
  246. package/plugins/acryl-router/src/core/utils/encode.ts +34 -0
  247. package/plugins/acryl-router/src/core/utils/filepath.test.ts +78 -0
  248. package/plugins/acryl-router/src/core/utils/filepath.ts +60 -0
  249. package/plugins/acryl-router/src/core/utils/handler.ts +15 -0
  250. package/plugins/acryl-router/src/core/utils/headers.ts +355 -0
  251. package/plugins/acryl-router/src/core/utils/html.test.ts +16 -0
  252. package/plugins/acryl-router/src/core/utils/html.ts +182 -0
  253. package/plugins/acryl-router/src/core/utils/http-status.ts +72 -0
  254. package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +149 -0
  255. package/plugins/acryl-router/src/core/utils/ipaddr.ts +374 -0
  256. package/plugins/acryl-router/src/core/utils/jwt/index.ts +7 -0
  257. package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +86 -0
  258. package/plugins/acryl-router/src/core/utils/jwt/jwa.ts +38 -0
  259. package/plugins/acryl-router/src/core/utils/jwt/jws.ts +235 -0
  260. package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +1788 -0
  261. package/plugins/acryl-router/src/core/utils/jwt/jwt.ts +292 -0
  262. package/plugins/acryl-router/src/core/utils/jwt/types.ts +157 -0
  263. package/plugins/acryl-router/src/core/utils/jwt/utf8.ts +7 -0
  264. package/plugins/acryl-router/src/core/utils/mime.test.ts +64 -0
  265. package/plugins/acryl-router/src/core/utils/mime.ts +94 -0
  266. package/plugins/acryl-router/src/core/utils/stream.test.ts +212 -0
  267. package/plugins/acryl-router/src/core/utils/stream.ts +107 -0
  268. package/plugins/acryl-router/src/core/utils/types.test.ts +295 -0
  269. package/plugins/acryl-router/src/core/utils/types.ts +123 -0
  270. package/plugins/acryl-router/src/core/utils/url.test.ts +384 -0
  271. package/plugins/acryl-router/src/core/utils/url.ts +337 -0
  272. package/plugins/acryl-router/src/core/validator/index.ts +8 -0
  273. package/plugins/acryl-router/src/core/validator/utils.test.ts +82 -0
  274. package/plugins/acryl-router/src/core/validator/utils.ts +69 -0
  275. package/plugins/acryl-router/src/core/validator/validator.test.ts +1488 -0
  276. package/plugins/acryl-router/src/core/validator/validator.ts +185 -0
  277. package/plugins/acryl-router/src/helpers/index.ts +13 -3
  278. package/plugins/acryl-router/src/helpers/schema-validator.ts +35 -0
  279. package/plugins/acryl-router/src/index.ts +6 -6
  280. package/plugins/acryl-router/src/middleware/index.ts +25 -6
  281. package/plugins/acryl-router/src/middleware/loopback-only.ts +53 -14
  282. package/plugins/acryl-router/src/service.ts +262 -64
  283. package/plugins/acryl-router/tests/docs-examples.spec.ts +152 -0
  284. package/plugins/acryl-router/tests/live-install.spec.ts +2 -2
  285. package/plugins/acryl-router/tests/loader.spec.ts +3 -2
  286. package/plugins/acryl-router/tests/loopback-only.spec.ts +80 -0
  287. package/plugins/acryl-router/tests/service.spec.ts +281 -66
  288. package/plugins/acryl-router/tests/shadow-state.spec.ts +79 -0
  289. package/plugins/acryl-router/upstream.json +2 -2
  290. package/plugins/acryl-router-http/plugin.json +2 -1
  291. package/plugins/acryl-router-http/src/bridge.ts +3 -1
  292. package/plugins/acryl-router-http/tests/adapter.spec.ts +16 -12
  293. package/plugins/acryl-router-http/tests/surfaces.spec.ts +12 -10
  294. package/plugins/acryl-router-http/upstream.json +3 -3
  295. package/plugins/acryl-state/NOTICE +30 -0
  296. package/plugins/acryl-state/plugin.json +1 -1
  297. package/plugins/acryl-state/src/cell.ts +1 -1
  298. package/plugins/acryl-state/src/service.ts +33 -15
  299. package/plugins/acryl-state/upstream.json +6 -4
  300. package/plugins/builder/script.ts +4 -7
  301. package/plugins/extensions/index.ts +6 -5
  302. package/plugins/extensions/resolve.ts +28 -3
  303. package/plugins/openai/prompt.ts +9 -7
  304. package/plugins/tsconfig.json +5 -4
  305. package/shared/AGENTS.core.md +54 -0
  306. package/templates/agent/AGENTS.md +6 -0
  307. package/templates/agent/README.md +2 -2
  308. package/templates/agent/blend.yaml +1 -1
  309. package/templates/agent/src/routes.ts +37 -27
  310. package/templates/agent/test/model.test.ts +2 -1
  311. package/templates/agent/tsconfig.json +8 -5
  312. package/templates/blank/AGENTS.md +5 -0
  313. package/templates/blank/README.md +1 -1
  314. package/templates/blank/src/routes.ts +6 -4
  315. package/templates/blank/tsconfig.json +2 -4
  316. package/templates/todo/AGENTS.md +5 -0
  317. package/templates/todo/src/page.ts +1 -1
  318. package/templates/todo/src/todo.ts +58 -29
  319. package/templates/todo/tsconfig.json +2 -4
  320. package/templates/todo/upstream.json +2 -2
  321. package/plugins/acryl-router/src/compose.ts +0 -21
  322. package/plugins/acryl-router/src/context.ts +0 -59
  323. package/plugins/acryl-router/src/helpers/read-body.ts +0 -59
  324. package/plugins/acryl-router/src/helpers/sse.ts +0 -83
  325. package/plugins/acryl-router/src/helpers/validate.ts +0 -44
  326. package/plugins/acryl-router/src/http-error.ts +0 -21
  327. package/plugins/acryl-router/src/middleware/body-limit.ts +0 -25
  328. package/plugins/acryl-router/src/middleware/cors.ts +0 -46
  329. package/plugins/acryl-router/src/middleware/logger.ts +0 -21
  330. package/plugins/acryl-router/src/middleware/request-id.ts +0 -24
  331. package/plugins/acryl-router/src/middleware/secure-headers.ts +0 -23
  332. package/plugins/acryl-router/src/pattern.ts +0 -89
  333. package/plugins/acryl-router/src/registrar.ts +0 -90
  334. package/plugins/acryl-router/src/table.ts +0 -156
  335. package/plugins/acryl-router/src/types.ts +0 -73
  336. package/plugins/acryl-router/src/util.ts +0 -23
  337. package/plugins/acryl-router/tests/api.spec.ts +0 -223
  338. package/plugins/acryl-router/tests/conformance.ts +0 -170
  339. package/plugins/acryl-router/tests/helpers.spec.ts +0 -172
  340. package/plugins/acryl-router/tests/middleware.spec.ts +0 -183
  341. package/plugins/acryl-router/tests/table.spec.ts +0 -47
@@ -0,0 +1,259 @@
1
+ import { matchedRoutes, routePath, baseRoutePath, basePath } from './index.ts'
2
+ import { Context } from '../../context.ts'
3
+ import { App } from '../../app.ts'
4
+
5
+ const defaultContextOptions = {
6
+ executionCtx: {
7
+ waitUntil: () => {},
8
+ passThroughOnException: () => {},
9
+ props: {},
10
+ },
11
+ env: {},
12
+ }
13
+
14
+ describe('matchedRoutes', () => {
15
+ it('should return matched routes', () => {
16
+ const handlerA = () => {}
17
+ const handlerB = () => {}
18
+ const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')
19
+ const c = new Context(rawRequest, {
20
+ path: '/123/key',
21
+ matchResult: [
22
+ [
23
+ [
24
+ [handlerA, { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' }],
25
+ { id: '123' },
26
+ ],
27
+ [
28
+ [handlerA, { basePath: '/', handler: handlerB, method: 'GET', path: '/:id/:name' }],
29
+ { id: '456', name: 'key' },
30
+ ],
31
+ ],
32
+ ],
33
+ ...defaultContextOptions,
34
+ })
35
+
36
+ expect(matchedRoutes(c)).toEqual([
37
+ { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' },
38
+ { basePath: '/', handler: handlerB, method: 'GET', path: '/:id/:name' },
39
+ ])
40
+ })
41
+ })
42
+
43
+ describe('routePath', () => {
44
+ it('should return route path', () => {
45
+ const handlerA = () => {}
46
+ const handlerB = () => {}
47
+ const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')
48
+ const c = new Context(rawRequest, {
49
+ path: '/123/key',
50
+ matchResult: [
51
+ [
52
+ [
53
+ [handlerA, { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' }],
54
+ { id: '123' },
55
+ ],
56
+ [
57
+ [handlerA, { basePath: '/', handler: handlerB, method: 'GET', path: '/:id/:name' }],
58
+ { id: '456', name: 'key' },
59
+ ],
60
+ ],
61
+ ],
62
+ ...defaultContextOptions,
63
+ })
64
+
65
+ expect(routePath(c)).toBe('/:id')
66
+
67
+ expect(routePath(c, 0)).toBe('/:id')
68
+ expect(routePath(c, 1)).toBe('/:id/:name')
69
+ expect(routePath(c, -1)).toBe('/:id/:name')
70
+ expect(routePath(c, 2)).toBe('')
71
+
72
+ c.req.routeIndex = 1
73
+ expect(routePath(c)).toBe('/:id/:name')
74
+ })
75
+ })
76
+
77
+ describe('baseRoutePath', () => {
78
+ it('should return raw basePath', () => {
79
+ const handlerA = () => {}
80
+ const handlerB = () => {}
81
+ const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')
82
+ const c = new Context(rawRequest, {
83
+ path: '/123/key',
84
+ matchResult: [
85
+ [
86
+ [
87
+ [handlerA, { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' }],
88
+ { id: '123' },
89
+ ],
90
+ [
91
+ [handlerA, { basePath: '/sub', handler: handlerB, method: 'GET', path: '/:id/:name' }],
92
+ { id: '456', name: 'key' },
93
+ ],
94
+ [
95
+ [handlerA, { basePath: '/:sub', handler: handlerB, method: 'GET', path: '/:id/:name' }],
96
+ { id: '456', name: 'key' },
97
+ ],
98
+ ],
99
+ ],
100
+ ...defaultContextOptions,
101
+ })
102
+
103
+ expect(baseRoutePath(c)).toBe('/')
104
+
105
+ expect(baseRoutePath(c, 0)).toBe('/')
106
+ expect(baseRoutePath(c, 1)).toBe('/sub')
107
+ expect(baseRoutePath(c, 2)).toBe('/:sub')
108
+ expect(baseRoutePath(c, -1)).toBe('/:sub')
109
+ expect(baseRoutePath(c, 3)).toBe('')
110
+
111
+ c.req.routeIndex = 1
112
+ expect(baseRoutePath(c)).toBe('/sub')
113
+
114
+ c.req.routeIndex = 2
115
+ expect(baseRoutePath(c)).toBe('/:sub')
116
+ })
117
+ })
118
+
119
+ describe('basePath', () => {
120
+ it('should return basePath without parameters', () => {
121
+ const handlerA = () => {}
122
+ const handlerB = () => {}
123
+ const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')
124
+ const c = new Context(rawRequest, {
125
+ path: '/123/key',
126
+ matchResult: [
127
+ [
128
+ [
129
+ [handlerA, { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' }],
130
+ { id: '123' },
131
+ ],
132
+ [
133
+ [handlerA, { basePath: '/sub', handler: handlerB, method: 'GET', path: '/:id/:name' }],
134
+ { id: '456', name: 'key' },
135
+ ],
136
+ ],
137
+ ],
138
+ ...defaultContextOptions,
139
+ })
140
+
141
+ expect(basePath(c)).toBe('/')
142
+ expect(basePath(c)).toBe('/') // cached value
143
+
144
+ expect(basePath(c, 0)).toBe('/')
145
+ expect(basePath(c, 1)).toBe('/sub')
146
+ expect(basePath(c, -1)).toBe('/sub')
147
+ expect(basePath(c, 2)).toBe('')
148
+
149
+ c.req.routeIndex = 1
150
+ expect(basePath(c)).toBe('/sub')
151
+ })
152
+
153
+ it('should return basePath with embedded parameters', () => {
154
+ const handlerA = () => {}
155
+ const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')
156
+ const c = new Context(rawRequest, {
157
+ path: '/sub-app-path/123/key',
158
+ matchResult: [
159
+ [
160
+ [
161
+ [handlerA, { basePath: '/:sub', handler: handlerA, method: 'GET', path: '/:id' }],
162
+ { id: '123' },
163
+ ],
164
+ ],
165
+ ],
166
+ ...defaultContextOptions,
167
+ })
168
+
169
+ expect(basePath(c)).toBe('/sub-app-path')
170
+ })
171
+
172
+ it('should return basePath with wildcard', () => {
173
+ const handlerA = () => {}
174
+ const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')
175
+ const c = new Context(rawRequest, {
176
+ path: '/sub-app-path/foo/app/123/key',
177
+ matchResult: [
178
+ [
179
+ [
180
+ [
181
+ handlerA,
182
+ { basePath: '/sub-app-path/*/app', handler: handlerA, method: 'GET', path: '/:id' },
183
+ ],
184
+ { id: '123' },
185
+ ],
186
+ ],
187
+ ],
188
+ ...defaultContextOptions,
189
+ })
190
+
191
+ expect(basePath(c)).toBe('/sub-app-path/foo/app')
192
+ })
193
+
194
+ it('should return the fully expanded basePath for nested route() apps', async () => {
195
+ const app = new App()
196
+ const sub1 = new App()
197
+ const sub2 = new App()
198
+ const sub3 = new App()
199
+
200
+ sub3.get('/assets/*', (c) => c.json({ baseRoutePath: baseRoutePath(c), basePath: basePath(c) }))
201
+ sub2.route('/:app/sub3/assets', sub3)
202
+ sub1.route('/:app/sub2/assets', sub2)
203
+ app.route('/:app/sub1/assets', sub1)
204
+
205
+ const res = await app.request(
206
+ '/s1/sub1/assets/ss2/sub2/assets/sss3/sub3/assets/assets/file-hoge/assets-kage.js'
207
+ )
208
+ expect(await res.json()).toEqual({
209
+ baseRoutePath: '/:app/sub1/assets/:app/sub2/assets/:app/sub3/assets',
210
+ basePath: '/s1/sub1/assets/ss2/sub2/assets/sss3/sub3/assets',
211
+ })
212
+ })
213
+
214
+ it('should return basePath with custom regex pattern', () => {
215
+ const handlerA = () => {}
216
+ const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')
217
+ const c = new Context(rawRequest, {
218
+ path: '/sub-app-path/foo/123/key',
219
+ matchResult: [
220
+ [
221
+ [
222
+ [
223
+ handlerA,
224
+ {
225
+ basePath: '/sub-app-path/:sub{foo|bar}',
226
+ handler: handlerA,
227
+ method: 'GET',
228
+ path: '/:id',
229
+ },
230
+ ],
231
+ { sub: 'foo', id: '123' },
232
+ ],
233
+ ],
234
+ ],
235
+ ...defaultContextOptions,
236
+ })
237
+
238
+ expect(basePath(c)).toBe('/sub-app-path/foo')
239
+ })
240
+
241
+ it('should return basePath with custom regex pattern mounted with app.route()', async () => {
242
+ const app = new App()
243
+ const subApp = new App()
244
+
245
+ subApp.get('/posts/:id', (c) => c.text(basePath(c)))
246
+ app.route('/api/:version{v[0-9]+}', subApp)
247
+ app.route('/tags/:tag{foo|bar}', subApp)
248
+ app.route('/users/:name{[a-z]+}/:group', subApp)
249
+
250
+ let res = await app.request('/api/v2/posts/123')
251
+ expect(await res.text()).toBe('/api/v2')
252
+
253
+ res = await app.request('/tags/bar/posts/123')
254
+ expect(await res.text()).toBe('/tags/bar')
255
+
256
+ res = await app.request('/users/abc/def/posts/123')
257
+ expect(await res.text()).toBe('/users/abc/def')
258
+ })
259
+ })
@@ -0,0 +1,144 @@
1
+ import type { Context } from '../../context.ts'
2
+ import { GET_MATCH_RESULT } from '../../request/constants.ts'
3
+ import type { RouterRoute } from '../../types.ts'
4
+ import { getPattern, splitRoutingPath } from '../../utils/url.ts'
5
+
6
+ /**
7
+ * Get matched routes in the handler
8
+ *
9
+ * @param {Context} c - The context object
10
+ * @returns An array of matched routes
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { matchedRoutes } from 'acryl-router/helpers'
15
+ *
16
+ * app.use('*', async function logger(c, next) {
17
+ * await next()
18
+ * matchedRoutes(c).forEach(({ handler, method, path }, i) => {
19
+ * const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')
20
+ * console.log(
21
+ * method,
22
+ * ' ',
23
+ * path,
24
+ * ' '.repeat(Math.max(10 - path.length, 0)),
25
+ * name,
26
+ * i === c.req.routeIndex ? '<- respond from here' : ''
27
+ * )
28
+ * })
29
+ * })
30
+ * ```
31
+ */
32
+ export const matchedRoutes = (c: Context): RouterRoute[] =>
33
+ // @ts-expect-error c.req[GET_MATCH_RESULT] is not typed
34
+ (c.req as unknown)[GET_MATCH_RESULT][0].map(([[, route]]) => route)
35
+
36
+ /**
37
+ * Get the route path registered within the handler
38
+ *
39
+ * @param {Context} c - The context object
40
+ * @param {number} index - The index of the route from which to retrieve the path, similar to Array.prototype.at(), where a negative number is the index counted from the end of the matching route. Defaults to the current route index.
41
+ * @returns The route path registered within the handler
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * import { routePath } from 'acryl-router/helpers'
46
+ *
47
+ * app.use('*', (c, next) => {
48
+ * console.log(routePath(c)) // '*'
49
+ * console.log(routePath(c, -1)) // '/posts/:id'
50
+ * return next()
51
+ * })
52
+ *
53
+ * app.get('/posts/:id', (c) => {
54
+ * return c.text(routePath(c)) // '/posts/:id'
55
+ * })
56
+ * ```
57
+ */
58
+ export const routePath = (c: Context, index?: number): string =>
59
+ matchedRoutes(c).at(index ?? c.req.routeIndex)?.path ?? ''
60
+
61
+ /**
62
+ * Get the basePath of the as-is route specified by routing.
63
+ *
64
+ * @param {Context} c - The context object
65
+ * @param {number} index - The index of the route from which to retrieve the path, similar to Array.prototype.at(), where a negative number is the index counted from the end of the matching route. Defaults to the current route index.
66
+ * @returns The basePath of the as-is route specified by routing.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * import { baseRoutePath } from 'acryl-router/helpers'
71
+ *
72
+ * const app = new App()
73
+ *
74
+ * const subApp = new App()
75
+ * subApp.get('/posts/:id', (c) => {
76
+ * return c.text(baseRoutePath(c)) // '/:sub'
77
+ * })
78
+ *
79
+ * app.route('/:sub', subApp)
80
+ * ```
81
+ */
82
+ export const baseRoutePath = (c: Context, index?: number): string =>
83
+ matchedRoutes(c).at(index ?? c.req.routeIndex)?.basePath ?? ''
84
+
85
+ /**
86
+ * Get the basePath with embedded parameters
87
+ *
88
+ * @param {Context} c - The context object
89
+ * @param {number} index - The index of the route from which to retrieve the path, similar to Array.prototype.at(), where a negative number is the index counted from the end of the matching route. Defaults to the current route index.
90
+ * @returns The basePath with embedded parameters.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * import { basePath } from 'acryl-router/helpers'
95
+ *
96
+ * const app = new App()
97
+ *
98
+ * const subApp = new App()
99
+ * subApp.get('/posts/:id', (c) => {
100
+ * return c.text(basePath(c)) // '/requested-sub-app-path'
101
+ * })
102
+ *
103
+ * app.route('/:sub', subApp)
104
+ * ```
105
+ */
106
+ const basePathCacheMap: WeakMap<Context, Record<number, string>> = new WeakMap()
107
+ export const basePath = (c: Context, index?: number): string => {
108
+ index ??= c.req.routeIndex
109
+
110
+ const cache = basePathCacheMap.get(c) || []
111
+ if (typeof cache[index] === 'string') {
112
+ return cache[index]
113
+ }
114
+
115
+ let result: string
116
+ const rp = baseRoutePath(c, index)
117
+ if (!/[:*]/.test(rp)) {
118
+ result = rp
119
+ } else {
120
+ const paths = splitRoutingPath(rp)
121
+
122
+ const reqPath = c.req.path
123
+ let basePathLength = 0
124
+ for (let i = 0, len = paths.length; i < len; i++) {
125
+ const pattern = getPattern(paths[i], paths[i + 1])
126
+ if (pattern) {
127
+ const re = pattern[2] === true || pattern === '*' ? /[^\/]+/ : pattern[2]
128
+ const restPath = reqPath.substring(basePathLength + 1)
129
+ // like the router, fall back to matching only the current segment
130
+ const segment = restPath.split('/', 1)[0]
131
+ basePathLength += (restPath.match(re)?.[0] ?? (re.test(segment) ? segment : '')).length
132
+ } else {
133
+ basePathLength += paths[i].length
134
+ }
135
+ basePathLength += 1 // for '/'
136
+ }
137
+ result = reqPath.substring(0, basePathLength)
138
+ }
139
+
140
+ cache[index] = result
141
+ basePathCacheMap.set(c, cache)
142
+
143
+ return result
144
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @module
3
+ * Streaming Helper for App.
4
+ */
5
+
6
+ export { stream } from './stream.ts'
7
+ export type { SSEMessage } from './sse.ts'
8
+ export { streamSSE, SSEStreamingApi } from './sse.ts'
9
+ export { streamText } from './text.ts'
@@ -0,0 +1,101 @@
1
+ import type { Context } from '../../context.ts'
2
+ import { HtmlEscapedCallbackPhase, resolveCallback } from '../../utils/html.ts'
3
+ import { StreamingApi } from '../../utils/stream.ts'
4
+ import { isOldBunVersion } from './utils.ts'
5
+
6
+ export interface SSEMessage {
7
+ data: string | Promise<string>
8
+ event?: string
9
+ id?: string
10
+ retry?: number
11
+ }
12
+
13
+ export class SSEStreamingApi extends StreamingApi {
14
+ constructor(writable: WritableStream, readable: ReadableStream) {
15
+ super(writable, readable)
16
+ }
17
+
18
+ async writeSSE(message: SSEMessage) {
19
+ const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {})
20
+ const dataLines = (data as string)
21
+ .split(/\r\n|\r|\n/)
22
+ .map((line) => {
23
+ return `data: ${line}`
24
+ })
25
+ .join('\n')
26
+
27
+ for (const key of ['event', 'id'] as const) {
28
+ const value = message[key]
29
+ if (value && /[\r\n]/.test(value)) {
30
+ throw new Error(`${key} must not contain "\\r" or "\\n"`)
31
+ }
32
+ }
33
+
34
+ const sseData =
35
+ [
36
+ message.event && `event: ${message.event}`,
37
+ dataLines,
38
+ message.id !== undefined && `id: ${message.id}`,
39
+ message.retry !== undefined && `retry: ${message.retry}`,
40
+ ]
41
+ .filter(Boolean)
42
+ .join('\n') + '\n\n'
43
+
44
+ await this.write(sseData)
45
+ }
46
+ }
47
+
48
+ const run = async (
49
+ stream: SSEStreamingApi,
50
+ cb: (stream: SSEStreamingApi) => Promise<void>,
51
+ onError?: (e: Error, stream: SSEStreamingApi) => Promise<void>
52
+ ): Promise<void> => {
53
+ try {
54
+ await cb(stream)
55
+ } catch (e) {
56
+ if (e instanceof Error && onError) {
57
+ await onError(e, stream)
58
+
59
+ await stream.writeSSE({
60
+ event: 'error',
61
+ data: e.message,
62
+ })
63
+ } else {
64
+ console.error(e)
65
+ }
66
+ } finally {
67
+ stream.close()
68
+ }
69
+ }
70
+
71
+ const contextStash: WeakMap<ReadableStream, Context> = new WeakMap<ReadableStream, Context>()
72
+
73
+ export const streamSSE = (
74
+ c: Context,
75
+ cb: (stream: SSEStreamingApi) => Promise<void>,
76
+ onError?: (e: Error, stream: SSEStreamingApi) => Promise<void>
77
+ ): Response => {
78
+ const { readable, writable } = new TransformStream()
79
+ const stream = new SSEStreamingApi(writable, readable)
80
+
81
+ // Until Bun v1.1.27, Bun didn't call cancel() on the ReadableStream for Response objects from Bun.serve()
82
+ if (isOldBunVersion()) {
83
+ c.req.raw.signal.addEventListener('abort', () => {
84
+ if (!stream.closed) {
85
+ stream.abort()
86
+ }
87
+ })
88
+ }
89
+
90
+ // in bun, `c` is destroyed when the request is returned, so hold it until the end of streaming
91
+ contextStash.set(stream.responseReadable, c)
92
+
93
+ c.header('Transfer-Encoding', 'chunked')
94
+ c.header('Content-Type', 'text/event-stream')
95
+ c.header('Cache-Control', 'no-cache')
96
+ c.header('Connection', 'keep-alive')
97
+
98
+ run(stream, cb, onError)
99
+
100
+ return c.newResponse(stream.responseReadable)
101
+ }
@@ -0,0 +1,128 @@
1
+ import { stream } from './index.ts'
2
+ import { Context } from '../../context.ts'
3
+
4
+ describe('Basic Streaming Helper', () => {
5
+ const req = new Request('http://localhost/')
6
+ let c: Context
7
+ beforeEach(() => {
8
+ c = new Context(req)
9
+ })
10
+
11
+ it('Check stream Response', async () => {
12
+ const res = stream(c, async (stream) => {
13
+ for (let i = 0; i < 3; i++) {
14
+ await stream.write(new Uint8Array([i]))
15
+ await stream.sleep(1)
16
+ }
17
+ })
18
+ if (!res.body) {
19
+ throw new Error('Body is null')
20
+ }
21
+ const reader = res.body.getReader()
22
+ for (let i = 0; i < 3; i++) {
23
+ const { value } = await reader.read()
24
+ expect(value).toEqual(new Uint8Array([i]))
25
+ }
26
+ })
27
+
28
+ it('Check stream Response if aborted by client', async () => {
29
+ let aborted = false
30
+ const res = stream(c, async (stream) => {
31
+ stream.onAbort(() => {
32
+ aborted = true
33
+ })
34
+ for (let i = 0; i < 3; i++) {
35
+ await stream.write(new Uint8Array([i]))
36
+ await stream.sleep(1)
37
+ }
38
+ })
39
+ if (!res.body) {
40
+ throw new Error('Body is null')
41
+ }
42
+ const reader = res.body.getReader()
43
+ const { value } = await reader.read()
44
+ expect(value).toEqual(new Uint8Array([0]))
45
+ reader.cancel()
46
+ expect(aborted).toBeTruthy()
47
+ })
48
+
49
+ it('Check stream Response if aborted by abort signal', async () => {
50
+ // Emulate an old version of Bun (version 1.1.0) for this specific test case
51
+ // @ts-expect-error Bun is not typed
52
+ global.Bun = {
53
+ version: '1.1.0',
54
+ }
55
+ const ac = new AbortController()
56
+ const req = new Request('http://localhost/', { signal: ac.signal })
57
+ const c = new Context(req)
58
+
59
+ let aborted = false
60
+ const res = stream(c, async (stream) => {
61
+ stream.onAbort(() => {
62
+ aborted = true
63
+ })
64
+ for (let i = 0; i < 3; i++) {
65
+ await stream.write(new Uint8Array([i]))
66
+ await stream.sleep(1)
67
+ }
68
+ })
69
+ if (!res.body) {
70
+ throw new Error('Body is null')
71
+ }
72
+ const reader = res.body.getReader()
73
+ const { value } = await reader.read()
74
+ expect(value).toEqual(new Uint8Array([0]))
75
+ ac.abort()
76
+ expect(aborted).toBeTruthy()
77
+ // @ts-expect-error Bun is not typed
78
+ delete global.Bun
79
+ })
80
+
81
+ it('Check stream Response if pipe is aborted by abort signal', async () => {
82
+ // Emulate an old version of Bun (version 1.1.0) for this specific test case
83
+ // @ts-expect-error Bun is not typed
84
+ global.Bun = {
85
+ version: '1.1.0',
86
+ }
87
+ const ac = new AbortController()
88
+ const req = new Request('http://localhost/', { signal: ac.signal })
89
+ const c = new Context(req)
90
+
91
+ let aborted = false
92
+ const res = stream(c, async (stream) => {
93
+ stream.onAbort(() => {
94
+ aborted = true
95
+ })
96
+ await stream.pipe(new ReadableStream())
97
+ })
98
+ if (!res.body) {
99
+ throw new Error('Body is null')
100
+ }
101
+ const reader = res.body.getReader()
102
+ const pReading = reader.read()
103
+ ac.abort()
104
+ await pReading
105
+ expect(aborted).toBeTruthy()
106
+ // @ts-expect-error Bun is not typed
107
+ delete global.Bun
108
+ })
109
+
110
+ it('Check stream Response if error occurred', async () => {
111
+ const onError = vi.fn()
112
+ const res = stream(
113
+ c,
114
+ async () => {
115
+ throw new Error('error')
116
+ },
117
+ onError
118
+ )
119
+ if (!res.body) {
120
+ throw new Error('Body is null')
121
+ }
122
+ const reader = res.body.getReader()
123
+ const { value } = await reader.read()
124
+ expect(value).toBeUndefined()
125
+ expect(onError).toBeCalledTimes(1)
126
+ expect(onError).toBeCalledWith(new Error('error'), expect.anything()) // 2nd argument is StreamingApi instance
127
+ })
128
+ })
@@ -0,0 +1,45 @@
1
+ import type { Context } from '../../context.ts'
2
+ import { StreamingApi } from '../../utils/stream.ts'
3
+ import { isOldBunVersion } from './utils.ts'
4
+
5
+ const contextStash: WeakMap<ReadableStream, Context> = new WeakMap<ReadableStream, Context>()
6
+
7
+ export const stream = (
8
+ c: Context,
9
+ cb: (stream: StreamingApi) => Promise<void>,
10
+ onError?: (e: Error, stream: StreamingApi) => Promise<void>
11
+ ): Response => {
12
+ const { readable, writable } = new TransformStream()
13
+ const stream = new StreamingApi(writable, readable)
14
+
15
+ // Until Bun v1.1.27, Bun didn't call cancel() on the ReadableStream for Response objects from Bun.serve()
16
+ if (isOldBunVersion()) {
17
+ c.req.raw.signal.addEventListener('abort', () => {
18
+ if (!stream.closed) {
19
+ stream.abort()
20
+ }
21
+ })
22
+ }
23
+
24
+ // in bun, `c` is destroyed when the request is returned, so hold it until the end of streaming
25
+ contextStash.set(stream.responseReadable, c)
26
+ ;(async () => {
27
+ try {
28
+ await cb(stream)
29
+ } catch (e) {
30
+ if (e === undefined) {
31
+ // If reading is canceled without a reason value (e.g. by StreamingApi)
32
+ // then the .pipeTo() promise will reject with undefined.
33
+ // In this case, do nothing because the stream is already closed.
34
+ } else if (e instanceof Error && onError) {
35
+ await onError(e, stream)
36
+ } else {
37
+ console.error(e)
38
+ }
39
+ } finally {
40
+ stream.close()
41
+ }
42
+ })()
43
+
44
+ return c.newResponse(stream.responseReadable)
45
+ }