acrylblends 0.3.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 (315) hide show
  1. package/dist/index.d.ts +6 -1
  2. package/dist/index.js +28 -3
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/plugins/acryl-inspect/plugin.json +2 -1
  6. package/plugins/acryl-inspect/src/report.ts +16 -13
  7. package/plugins/acryl-inspect/src/service.ts +9 -7
  8. package/plugins/acryl-inspect/tests/report.spec.ts +7 -8
  9. package/plugins/acryl-inspect/tests/service.spec.ts +11 -10
  10. package/plugins/acryl-inspect/upstream.json +2 -2
  11. package/plugins/acryl-node-server/LICENSE +26 -0
  12. package/plugins/acryl-node-server/plugin.json +2 -1
  13. package/plugins/acryl-node-server/src/adapter/conninfo.ts +23 -0
  14. package/plugins/acryl-node-server/src/adapter/early-hints.ts +55 -0
  15. package/plugins/acryl-node-server/src/adapter/error.ts +11 -0
  16. package/plugins/acryl-node-server/src/adapter/headers.ts +249 -0
  17. package/plugins/acryl-node-server/src/adapter/index.ts +6 -0
  18. package/plugins/acryl-node-server/src/adapter/listener.ts +540 -0
  19. package/plugins/acryl-node-server/src/adapter/request.ts +797 -0
  20. package/plugins/acryl-node-server/src/adapter/response.ts +186 -0
  21. package/plugins/acryl-node-server/src/adapter/serve-static.ts +238 -0
  22. package/plugins/acryl-node-server/src/adapter/server.ts +36 -0
  23. package/plugins/acryl-node-server/src/adapter/types.ts +82 -0
  24. package/plugins/acryl-node-server/src/adapter/url.ts +42 -0
  25. package/plugins/acryl-node-server/src/adapter/utils/response/constants.ts +1 -0
  26. package/plugins/acryl-node-server/src/adapter/utils/response.ts +4 -0
  27. package/plugins/acryl-node-server/src/adapter/utils/stream.ts +88 -0
  28. package/plugins/acryl-node-server/src/adapter/utils.ts +96 -0
  29. package/plugins/acryl-node-server/src/adapter/websocket-types.ts +45 -0
  30. package/plugins/acryl-node-server/src/adapter/websocket.ts +375 -0
  31. package/plugins/acryl-node-server/src/index.ts +19 -6
  32. package/plugins/acryl-node-server/tests/adapter/app.ts +76 -0
  33. package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +1 -0
  34. package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
  35. package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +1 -0
  36. package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +1 -0
  37. package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +1 -0
  38. package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +5 -0
  39. package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +1 -0
  40. package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
  41. package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +1 -0
  42. package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +1 -0
  43. package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +1 -0
  44. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +1 -0
  45. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +1 -0
  46. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +1 -0
  47. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +1 -0
  48. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +1 -0
  49. package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +34 -0
  50. package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +248 -0
  51. package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +457 -0
  52. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +23 -0
  53. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +27 -0
  54. package/plugins/acryl-node-server/tests/adapter/headers.test.ts +252 -0
  55. package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +190 -0
  56. package/plugins/acryl-node-server/tests/adapter/listener.test.ts +727 -0
  57. package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +189 -0
  58. package/plugins/acryl-node-server/tests/adapter/request.test.ts +1230 -0
  59. package/plugins/acryl-node-server/tests/adapter/response.test.ts +285 -0
  60. package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +849 -0
  61. package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +790 -0
  62. package/plugins/acryl-node-server/tests/adapter/server.test.ts +1302 -0
  63. package/plugins/acryl-node-server/tests/adapter/setup.ts +16 -0
  64. package/plugins/acryl-node-server/tests/adapter/url.test.ts +76 -0
  65. package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +23 -0
  66. package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +75 -0
  67. package/plugins/acryl-node-server/tests/adapter/utils.test.ts +235 -0
  68. package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +260 -0
  69. package/plugins/acryl-node-server/tests/server.spec.ts +44 -8
  70. package/plugins/acryl-node-server/upstream.json +3 -3
  71. package/plugins/acryl-router/LICENSE +26 -0
  72. package/plugins/acryl-router/plugin.json +2 -1
  73. package/plugins/acryl-router/src/client.ts +2 -0
  74. package/plugins/acryl-router/src/connection.ts +42 -0
  75. package/plugins/acryl-router/src/core/app-base.ts +544 -0
  76. package/plugins/acryl-router/src/core/app.test.ts +3833 -0
  77. package/plugins/acryl-router/src/core/app.ts +34 -0
  78. package/plugins/acryl-router/src/core/client/client.test.ts +2313 -0
  79. package/plugins/acryl-router/src/core/client/client.ts +249 -0
  80. package/plugins/acryl-router/src/core/client/fetch-result-please.ts +95 -0
  81. package/plugins/acryl-router/src/core/client/index.ts +17 -0
  82. package/plugins/acryl-router/src/core/client/types.test.ts +149 -0
  83. package/plugins/acryl-router/src/core/client/types.ts +393 -0
  84. package/plugins/acryl-router/src/core/client/utils.test.ts +405 -0
  85. package/plugins/acryl-router/src/core/client/utils.ts +117 -0
  86. package/plugins/acryl-router/src/core/compose.test.ts +751 -0
  87. package/plugins/acryl-router/src/core/compose.ts +73 -0
  88. package/plugins/acryl-router/src/core/context.test.ts +629 -0
  89. package/plugins/acryl-router/src/core/context.ts +781 -0
  90. package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +344 -0
  91. package/plugins/acryl-router/src/core/helper/accepts/accepts.ts +96 -0
  92. package/plugins/acryl-router/src/core/helper/accepts/index.ts +6 -0
  93. package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +55 -0
  94. package/plugins/acryl-router/src/core/helper/adapter/index.ts +90 -0
  95. package/plugins/acryl-router/src/core/helper/conninfo/index.ts +6 -0
  96. package/plugins/acryl-router/src/core/helper/conninfo/types.ts +45 -0
  97. package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +510 -0
  98. package/plugins/acryl-router/src/core/helper/cookie/index.ts +146 -0
  99. package/plugins/acryl-router/src/core/helper/factory/index.test.ts +434 -0
  100. package/plugins/acryl-router/src/core/helper/factory/index.ts +375 -0
  101. package/plugins/acryl-router/src/core/helper/html/index.test.ts +78 -0
  102. package/plugins/acryl-router/src/core/helper/html/index.ts +57 -0
  103. package/plugins/acryl-router/src/core/helper/route/index.test.ts +259 -0
  104. package/plugins/acryl-router/src/core/helper/route/index.ts +144 -0
  105. package/plugins/acryl-router/src/core/helper/streaming/index.ts +9 -0
  106. package/plugins/acryl-router/src/core/helper/streaming/sse.ts +101 -0
  107. package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +128 -0
  108. package/plugins/acryl-router/src/core/helper/streaming/stream.ts +45 -0
  109. package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +34 -0
  110. package/plugins/acryl-router/src/core/helper/streaming/text.ts +15 -0
  111. package/plugins/acryl-router/src/core/helper/streaming/utils.ts +11 -0
  112. package/plugins/acryl-router/src/core/helper/testing/index.test.ts +42 -0
  113. package/plugins/acryl-router/src/core/helper/testing/index.ts +28 -0
  114. package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +124 -0
  115. package/plugins/acryl-router/src/core/helper/websocket/index.ts +140 -0
  116. package/plugins/acryl-router/src/core/http-exception.test.ts +45 -0
  117. package/plugins/acryl-router/src/core/http-exception.ts +77 -0
  118. package/plugins/acryl-router/src/core/index.ts +53 -0
  119. package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +434 -0
  120. package/plugins/acryl-router/src/core/middleware/basic-auth/index.ts +152 -0
  121. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +1018 -0
  122. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.ts +220 -0
  123. package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +273 -0
  124. package/plugins/acryl-router/src/core/middleware/body-limit/index.ts +110 -0
  125. package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +355 -0
  126. package/plugins/acryl-router/src/core/middleware/combine/index.ts +171 -0
  127. package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +535 -0
  128. package/plugins/acryl-router/src/core/middleware/compress/index.ts +142 -0
  129. package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +43 -0
  130. package/plugins/acryl-router/src/core/middleware/context-storage/index.ts +58 -0
  131. package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +532 -0
  132. package/plugins/acryl-router/src/core/middleware/cors/index.ts +163 -0
  133. package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +534 -0
  134. package/plugins/acryl-router/src/core/middleware/csrf/index.ts +150 -0
  135. package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +21 -0
  136. package/plugins/acryl-router/src/core/middleware/etag/digest.ts +95 -0
  137. package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +503 -0
  138. package/plugins/acryl-router/src/core/middleware/etag/index.ts +135 -0
  139. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +217 -0
  140. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.ts +278 -0
  141. package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +1135 -0
  142. package/plugins/acryl-router/src/core/middleware/jwk/index.ts +1 -0
  143. package/plugins/acryl-router/src/core/middleware/jwk/jwk.ts +195 -0
  144. package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +48 -0
  145. package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +802 -0
  146. package/plugins/acryl-router/src/core/middleware/jwt/index.ts +9 -0
  147. package/plugins/acryl-router/src/core/middleware/jwt/jwt.ts +189 -0
  148. package/plugins/acryl-router/src/core/middleware/language/index.test.ts +552 -0
  149. package/plugins/acryl-router/src/core/middleware/language/index.ts +12 -0
  150. package/plugins/acryl-router/src/core/middleware/language/language.ts +314 -0
  151. package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +204 -0
  152. package/plugins/acryl-router/src/core/middleware/logger/index.ts +95 -0
  153. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +292 -0
  154. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.ts +141 -0
  155. package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +221 -0
  156. package/plugins/acryl-router/src/core/middleware/method-override/index.ts +147 -0
  157. package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +37 -0
  158. package/plugins/acryl-router/src/core/middleware/powered-by/index.ts +35 -0
  159. package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +218 -0
  160. package/plugins/acryl-router/src/core/middleware/pretty-json/index.ts +67 -0
  161. package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +168 -0
  162. package/plugins/acryl-router/src/core/middleware/request-id/index.ts +7 -0
  163. package/plugins/acryl-router/src/core/middleware/request-id/request-id.ts +59 -0
  164. package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +671 -0
  165. package/plugins/acryl-router/src/core/middleware/secure-headers/index.ts +8 -0
  166. package/plugins/acryl-router/src/core/middleware/secure-headers/permissions-policy.ts +101 -0
  167. package/plugins/acryl-router/src/core/middleware/secure-headers/secure-headers.ts +340 -0
  168. package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +450 -0
  169. package/plugins/acryl-router/src/core/middleware/serve-static/index.ts +135 -0
  170. package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +50 -0
  171. package/plugins/acryl-router/src/core/middleware/serve-static/path.ts +25 -0
  172. package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +70 -0
  173. package/plugins/acryl-router/src/core/middleware/timeout/index.ts +58 -0
  174. package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +310 -0
  175. package/plugins/acryl-router/src/core/middleware/timing/index.ts +8 -0
  176. package/plugins/acryl-router/src/core/middleware/timing/timing.ts +256 -0
  177. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +381 -0
  178. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.ts +156 -0
  179. package/plugins/acryl-router/src/core/request/constants.ts +1 -0
  180. package/plugins/acryl-router/src/core/request.test.ts +751 -0
  181. package/plugins/acryl-router/src/core/request.ts +502 -0
  182. package/plugins/acryl-router/src/core/router/common.case.test.ts +1132 -0
  183. package/plugins/acryl-router/src/core/router/linear-router/index.ts +6 -0
  184. package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +80 -0
  185. package/plugins/acryl-router/src/core/router/linear-router/router.ts +152 -0
  186. package/plugins/acryl-router/src/core/router/pattern-router/index.ts +6 -0
  187. package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +46 -0
  188. package/plugins/acryl-router/src/core/router/pattern-router/router.ts +54 -0
  189. package/plugins/acryl-router/src/core/router/reg-exp-router/index.ts +7 -0
  190. package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.ts +33 -0
  191. package/plugins/acryl-router/src/core/router/reg-exp-router/node.ts +170 -0
  192. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +154 -0
  193. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.ts +165 -0
  194. package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +265 -0
  195. package/plugins/acryl-router/src/core/router/reg-exp-router/router.ts +174 -0
  196. package/plugins/acryl-router/src/core/router/reg-exp-router/trie.ts +84 -0
  197. package/plugins/acryl-router/src/core/router/smart-router/index.ts +6 -0
  198. package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +13 -0
  199. package/plugins/acryl-router/src/core/router/smart-router/router.ts +70 -0
  200. package/plugins/acryl-router/src/core/router/trie-router/index.ts +6 -0
  201. package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +896 -0
  202. package/plugins/acryl-router/src/core/router/trie-router/node.ts +221 -0
  203. package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +8 -0
  204. package/plugins/acryl-router/src/core/router/trie-router/router.ts +18 -0
  205. package/plugins/acryl-router/src/core/router/utils.ts +1 -0
  206. package/plugins/acryl-router/src/core/router.ts +103 -0
  207. package/plugins/acryl-router/src/core/types.test.ts +4134 -0
  208. package/plugins/acryl-router/src/core/types.ts +2782 -0
  209. package/plugins/acryl-router/src/core/utils/accept.test.ts +299 -0
  210. package/plugins/acryl-router/src/core/utils/accept.ts +268 -0
  211. package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +56 -0
  212. package/plugins/acryl-router/src/core/utils/basic-auth.ts +26 -0
  213. package/plugins/acryl-router/src/core/utils/body.test.ts +496 -0
  214. package/plugins/acryl-router/src/core/utils/body.ts +263 -0
  215. package/plugins/acryl-router/src/core/utils/buffer.test.ts +147 -0
  216. package/plugins/acryl-router/src/core/utils/buffer.ts +117 -0
  217. package/plugins/acryl-router/src/core/utils/color.test.ts +23 -0
  218. package/plugins/acryl-router/src/core/utils/color.ts +56 -0
  219. package/plugins/acryl-router/src/core/utils/compress.ts +10 -0
  220. package/plugins/acryl-router/src/core/utils/concurrent.test.ts +69 -0
  221. package/plugins/acryl-router/src/core/utils/concurrent.ts +55 -0
  222. package/plugins/acryl-router/src/core/utils/constants.ts +4 -0
  223. package/plugins/acryl-router/src/core/utils/cookie.test.ts +460 -0
  224. package/plugins/acryl-router/src/core/utils/cookie.ts +281 -0
  225. package/plugins/acryl-router/src/core/utils/crypto.test.ts +43 -0
  226. package/plugins/acryl-router/src/core/utils/crypto.ts +58 -0
  227. package/plugins/acryl-router/src/core/utils/encode.test.ts +61 -0
  228. package/plugins/acryl-router/src/core/utils/encode.ts +34 -0
  229. package/plugins/acryl-router/src/core/utils/filepath.test.ts +78 -0
  230. package/plugins/acryl-router/src/core/utils/filepath.ts +60 -0
  231. package/plugins/acryl-router/src/core/utils/handler.ts +15 -0
  232. package/plugins/acryl-router/src/core/utils/headers.ts +355 -0
  233. package/plugins/acryl-router/src/core/utils/html.test.ts +16 -0
  234. package/plugins/acryl-router/src/core/utils/html.ts +182 -0
  235. package/plugins/acryl-router/src/core/utils/http-status.ts +72 -0
  236. package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +149 -0
  237. package/plugins/acryl-router/src/core/utils/ipaddr.ts +374 -0
  238. package/plugins/acryl-router/src/core/utils/jwt/index.ts +7 -0
  239. package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +86 -0
  240. package/plugins/acryl-router/src/core/utils/jwt/jwa.ts +38 -0
  241. package/plugins/acryl-router/src/core/utils/jwt/jws.ts +235 -0
  242. package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +1788 -0
  243. package/plugins/acryl-router/src/core/utils/jwt/jwt.ts +292 -0
  244. package/plugins/acryl-router/src/core/utils/jwt/types.ts +157 -0
  245. package/plugins/acryl-router/src/core/utils/jwt/utf8.ts +7 -0
  246. package/plugins/acryl-router/src/core/utils/mime.test.ts +64 -0
  247. package/plugins/acryl-router/src/core/utils/mime.ts +94 -0
  248. package/plugins/acryl-router/src/core/utils/stream.test.ts +212 -0
  249. package/plugins/acryl-router/src/core/utils/stream.ts +107 -0
  250. package/plugins/acryl-router/src/core/utils/types.test.ts +295 -0
  251. package/plugins/acryl-router/src/core/utils/types.ts +123 -0
  252. package/plugins/acryl-router/src/core/utils/url.test.ts +384 -0
  253. package/plugins/acryl-router/src/core/utils/url.ts +337 -0
  254. package/plugins/acryl-router/src/core/validator/index.ts +8 -0
  255. package/plugins/acryl-router/src/core/validator/utils.test.ts +82 -0
  256. package/plugins/acryl-router/src/core/validator/utils.ts +69 -0
  257. package/plugins/acryl-router/src/core/validator/validator.test.ts +1488 -0
  258. package/plugins/acryl-router/src/core/validator/validator.ts +185 -0
  259. package/plugins/acryl-router/src/helpers/index.ts +13 -3
  260. package/plugins/acryl-router/src/helpers/schema-validator.ts +35 -0
  261. package/plugins/acryl-router/src/index.ts +6 -6
  262. package/plugins/acryl-router/src/middleware/index.ts +25 -6
  263. package/plugins/acryl-router/src/middleware/loopback-only.ts +19 -13
  264. package/plugins/acryl-router/src/service.ts +256 -64
  265. package/plugins/acryl-router/tests/docs-examples.spec.ts +152 -0
  266. package/plugins/acryl-router/tests/live-install.spec.ts +2 -2
  267. package/plugins/acryl-router/tests/loader.spec.ts +3 -2
  268. package/plugins/acryl-router/tests/service.spec.ts +264 -66
  269. package/plugins/acryl-router/tests/shadow-state.spec.ts +79 -0
  270. package/plugins/acryl-router/upstream.json +2 -2
  271. package/plugins/acryl-router-http/plugin.json +2 -1
  272. package/plugins/acryl-router-http/src/bridge.ts +3 -1
  273. package/plugins/acryl-router-http/tests/adapter.spec.ts +16 -12
  274. package/plugins/acryl-router-http/tests/surfaces.spec.ts +12 -10
  275. package/plugins/acryl-router-http/upstream.json +3 -3
  276. package/plugins/acryl-state/NOTICE +30 -0
  277. package/plugins/acryl-state/plugin.json +1 -1
  278. package/plugins/acryl-state/src/cell.ts +1 -1
  279. package/plugins/acryl-state/src/service.ts +33 -15
  280. package/plugins/acryl-state/upstream.json +6 -4
  281. package/plugins/builder/script.ts +4 -7
  282. package/plugins/extensions/index.ts +2 -2
  283. package/plugins/extensions/resolve.ts +28 -3
  284. package/plugins/openai/prompt.ts +9 -7
  285. package/plugins/tsconfig.json +5 -4
  286. package/templates/agent/src/routes.ts +37 -27
  287. package/templates/agent/test/model.test.ts +2 -1
  288. package/templates/agent/tsconfig.json +8 -5
  289. package/templates/blank/src/routes.ts +6 -4
  290. package/templates/blank/tsconfig.json +2 -4
  291. package/templates/todo/src/page.ts +1 -1
  292. package/templates/todo/src/todo.ts +56 -29
  293. package/templates/todo/tsconfig.json +2 -4
  294. package/templates/todo/upstream.json +2 -2
  295. package/plugins/acryl-router/src/compose.ts +0 -21
  296. package/plugins/acryl-router/src/context.ts +0 -59
  297. package/plugins/acryl-router/src/helpers/read-body.ts +0 -59
  298. package/plugins/acryl-router/src/helpers/sse.ts +0 -83
  299. package/plugins/acryl-router/src/helpers/validate.ts +0 -44
  300. package/plugins/acryl-router/src/http-error.ts +0 -21
  301. package/plugins/acryl-router/src/middleware/body-limit.ts +0 -25
  302. package/plugins/acryl-router/src/middleware/cors.ts +0 -46
  303. package/plugins/acryl-router/src/middleware/logger.ts +0 -21
  304. package/plugins/acryl-router/src/middleware/request-id.ts +0 -24
  305. package/plugins/acryl-router/src/middleware/secure-headers.ts +0 -23
  306. package/plugins/acryl-router/src/pattern.ts +0 -89
  307. package/plugins/acryl-router/src/registrar.ts +0 -90
  308. package/plugins/acryl-router/src/table.ts +0 -156
  309. package/plugins/acryl-router/src/types.ts +0 -73
  310. package/plugins/acryl-router/src/util.ts +0 -23
  311. package/plugins/acryl-router/tests/api.spec.ts +0 -223
  312. package/plugins/acryl-router/tests/conformance.ts +0 -170
  313. package/plugins/acryl-router/tests/helpers.spec.ts +0 -172
  314. package/plugins/acryl-router/tests/middleware.spec.ts +0 -183
  315. package/plugins/acryl-router/tests/table.spec.ts +0 -47
@@ -0,0 +1,281 @@
1
+ /**
2
+ * @module
3
+ * Cookie utility.
4
+ */
5
+
6
+ import { tryDecodeURIComponent } from './url.ts'
7
+ import type { BufferSource } from './types.ts'
8
+
9
+ export type Cookie = Record<string, string>
10
+ export type SignedCookie = Record<string, string | false>
11
+
12
+ type PartitionedCookieConstraint =
13
+ | { partitioned: true; secure: true }
14
+ | { partitioned?: boolean; secure?: boolean } // reset to default
15
+ type SecureCookieConstraint = { secure: true }
16
+ type HostCookieConstraint = { secure: true; path: '/'; domain?: undefined }
17
+
18
+ export type CookieOptions = {
19
+ domain?: string
20
+ expires?: Date
21
+ httpOnly?: boolean
22
+ maxAge?: number
23
+ path?: string
24
+ secure?: boolean
25
+ sameSite?: 'Strict' | 'Lax' | 'None' | 'strict' | 'lax' | 'none'
26
+ partitioned?: boolean
27
+ priority?: 'Low' | 'Medium' | 'High' | 'low' | 'medium' | 'high'
28
+ prefix?: CookiePrefixOptions
29
+ } & PartitionedCookieConstraint
30
+ export type CookiePrefixOptions = 'host' | 'secure'
31
+
32
+ export type CookieConstraint<Name> = Name extends `__Secure-${string}`
33
+ ? CookieOptions & SecureCookieConstraint
34
+ : Name extends `__Host-${string}`
35
+ ? CookieOptions & HostCookieConstraint
36
+ : CookieOptions
37
+
38
+ const algorithm = { name: 'HMAC', hash: 'SHA-256' }
39
+
40
+ const getCryptoKey = async (secret: string | BufferSource): Promise<CryptoKey> => {
41
+ const secretBuf = typeof secret === 'string' ? new TextEncoder().encode(secret) : secret
42
+ return await crypto.subtle.importKey('raw', secretBuf, algorithm, false, ['sign', 'verify'])
43
+ }
44
+
45
+ const makeSignature = async (value: string, secret: string | BufferSource): Promise<string> => {
46
+ const key = await getCryptoKey(secret)
47
+ const signature = await crypto.subtle.sign(algorithm.name, key, new TextEncoder().encode(value))
48
+ // the returned base64 encoded signature will always be 44 characters long and end with one or two equal signs
49
+ return btoa(String.fromCharCode(...new Uint8Array(signature)))
50
+ }
51
+
52
+ const verifySignature = async (
53
+ base64Signature: string,
54
+ value: string,
55
+ secret: CryptoKey
56
+ ): Promise<boolean> => {
57
+ try {
58
+ const signatureBinStr = atob(base64Signature)
59
+ const signature = new Uint8Array(signatureBinStr.length)
60
+ for (let i = 0, len = signatureBinStr.length; i < len; i++) {
61
+ signature[i] = signatureBinStr.charCodeAt(i)
62
+ }
63
+ return await crypto.subtle.verify(algorithm, secret, signature, new TextEncoder().encode(value))
64
+ } catch {
65
+ return false
66
+ }
67
+ }
68
+
69
+ // all alphanumeric chars and all of _!#$%&'*.^`|~+-
70
+ // (see: https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1)
71
+ // used only when producing cookies via serialize(); parsing uses the relaxed regex below
72
+ const validCookieNameRegEx = /^[\w!#$%&'*.^`|~+-]+$/
73
+
74
+ // all ASCII chars 33-126 except 34, 59, 61, and 92 (i.e. "!" to "~" but not double quote, semicolon, equals, or backslash)
75
+ // the strict RFC 6265 token rule above is a requirement for cookie producers; when consuming
76
+ // the Cookie header we accept the names other producers emit in the wild, e.g. "paraglide:lang"
77
+ // (see: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.6)
78
+ const relaxedCookieNameRegEx = /^[!#-:<>-[\]-~]+$/
79
+
80
+ // all ASCII chars 32-126 except 34, 59, and 92 (i.e. space to tilde but not double quote, semicolon, or backslash)
81
+ // (see: https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1)
82
+ //
83
+ // note: the spec also prohibits comma and space, but we allow both since they are very common in the real world
84
+ // (see: https://github.com/golang/go/issues/7243)
85
+ const validCookieValueRegEx = /^[ !#-:<-[\]-~]*$/
86
+
87
+ const trimCookieWhitespace = (value: string): string => {
88
+ let start = 0
89
+ let end = value.length
90
+
91
+ while (start < end) {
92
+ const charCode = value.charCodeAt(start)
93
+ if (charCode !== 0x20 && charCode !== 0x09) {
94
+ break
95
+ }
96
+ start++
97
+ }
98
+
99
+ while (end > start) {
100
+ const charCode = value.charCodeAt(end - 1)
101
+ if (charCode !== 0x20 && charCode !== 0x09) {
102
+ break
103
+ }
104
+ end--
105
+ }
106
+
107
+ return start === 0 && end === value.length ? value : value.slice(start, end)
108
+ }
109
+
110
+ export const parse = (cookie: string, name?: string): Cookie => {
111
+ if (name && cookie.indexOf(name) === -1) {
112
+ // Fast-path: return immediately if the demanded-key is not in the cookie string
113
+ return {}
114
+ }
115
+ const pairs = cookie.split(';')
116
+ const parsedCookie: Cookie = Object.create(null)
117
+ for (const pairStr of pairs) {
118
+ const valueStartPos = pairStr.indexOf('=')
119
+ if (valueStartPos === -1) {
120
+ continue
121
+ }
122
+
123
+ const cookieName = trimCookieWhitespace(pairStr.substring(0, valueStartPos))
124
+ if (
125
+ (name && name !== cookieName) ||
126
+ !relaxedCookieNameRegEx.test(cookieName) ||
127
+ cookieName in parsedCookie
128
+ ) {
129
+ continue
130
+ }
131
+
132
+ let cookieValue = trimCookieWhitespace(pairStr.substring(valueStartPos + 1))
133
+ if (cookieValue.startsWith('"') && cookieValue.endsWith('"')) {
134
+ cookieValue = cookieValue.slice(1, -1)
135
+ }
136
+ if (validCookieValueRegEx.test(cookieValue)) {
137
+ parsedCookie[cookieName] = tryDecodeURIComponent(cookieValue)
138
+ if (name) {
139
+ // Fast-path: return only the demanded-key immediately. Other keys are not needed.
140
+ break
141
+ }
142
+ }
143
+ }
144
+ return parsedCookie
145
+ }
146
+
147
+ export const parseSigned = async (
148
+ cookie: string,
149
+ secret: string | BufferSource,
150
+ name?: string
151
+ ): Promise<SignedCookie> => {
152
+ const parsedCookie: SignedCookie = Object.create(null)
153
+ const secretKey = await getCryptoKey(secret)
154
+
155
+ for (const [key, value] of Object.entries(parse(cookie, name))) {
156
+ const signatureStartPos = value.lastIndexOf('.')
157
+ if (signatureStartPos < 0) {
158
+ continue
159
+ }
160
+
161
+ const signedValue = value.substring(0, signatureStartPos)
162
+ const signature = value.substring(signatureStartPos + 1)
163
+ if (signature.length !== 44 || !signature.endsWith('=')) {
164
+ continue
165
+ }
166
+
167
+ const isVerified = await verifySignature(signature, signedValue, secretKey)
168
+ parsedCookie[key] = isVerified ? signedValue : false
169
+ }
170
+
171
+ return parsedCookie
172
+ }
173
+
174
+ const _serialize = (name: string, value: string, opt: CookieOptions = {}): string => {
175
+ if (!validCookieNameRegEx.test(name)) {
176
+ throw new Error('Invalid cookie name')
177
+ }
178
+
179
+ let cookie = `${name}=${value}`
180
+
181
+ if (name.startsWith('__Secure-') && !opt.secure) {
182
+ // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-4.1.3.1
183
+ throw new Error('__Secure- Cookie must have Secure attributes')
184
+ }
185
+
186
+ if (name.startsWith('__Host-')) {
187
+ // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-4.1.3.2
188
+ if (!opt.secure) {
189
+ throw new Error('__Host- Cookie must have Secure attributes')
190
+ }
191
+
192
+ if (opt.path !== '/') {
193
+ throw new Error('__Host- Cookie must have Path attributes with "/"')
194
+ }
195
+
196
+ if (opt.domain) {
197
+ throw new Error('__Host- Cookie must not have Domain attributes')
198
+ }
199
+ }
200
+
201
+ for (const key of ['domain', 'path', 'sameSite', 'priority'] as (keyof CookieOptions)[]) {
202
+ if (opt[key] && /[;\r\n]/.test(opt[key] as string)) {
203
+ throw new Error(`${key} must not contain ";", "\\r", or "\\n"`)
204
+ }
205
+ }
206
+
207
+ if (opt && typeof opt.maxAge === 'number' && opt.maxAge >= 0) {
208
+ if (opt.maxAge > 34560000) {
209
+ // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-5.6.2
210
+ throw new Error(
211
+ 'Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.'
212
+ )
213
+ }
214
+ cookie += `; Max-Age=${opt.maxAge | 0}`
215
+ }
216
+
217
+ if (opt.domain && opt.prefix !== 'host') {
218
+ cookie += `; Domain=${opt.domain}`
219
+ }
220
+
221
+ if (opt.path) {
222
+ cookie += `; Path=${opt.path}`
223
+ }
224
+
225
+ if (opt.expires) {
226
+ if (opt.expires.getTime() - Date.now() > 34560000_000) {
227
+ // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-5.5
228
+ throw new Error(
229
+ 'Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.'
230
+ )
231
+ }
232
+ cookie += `; Expires=${opt.expires.toUTCString()}`
233
+ }
234
+
235
+ if (opt.httpOnly) {
236
+ cookie += '; HttpOnly'
237
+ }
238
+
239
+ if (opt.secure) {
240
+ cookie += '; Secure'
241
+ }
242
+
243
+ if (opt.sameSite) {
244
+ cookie += `; SameSite=${opt.sameSite.charAt(0).toUpperCase() + opt.sameSite.slice(1)}`
245
+ }
246
+
247
+ if (opt.priority) {
248
+ cookie += `; Priority=${opt.priority.charAt(0).toUpperCase() + opt.priority.slice(1)}`
249
+ }
250
+
251
+ if (opt.partitioned) {
252
+ // https://www.ietf.org/archive/id/draft-cutler-httpbis-partitioned-cookies-01.html#section-2.1
253
+ if (!opt.secure) {
254
+ throw new Error('Partitioned Cookie must have Secure attributes')
255
+ }
256
+ cookie += '; Partitioned'
257
+ }
258
+
259
+ return cookie
260
+ }
261
+
262
+ export const serialize = <Name extends string>(
263
+ name: Name,
264
+ value: string,
265
+ opt?: CookieConstraint<Name>
266
+ ): string => {
267
+ value = encodeURIComponent(value)
268
+ return _serialize(name, value, opt)
269
+ }
270
+
271
+ export const serializeSigned = async (
272
+ name: string,
273
+ value: string,
274
+ secret: string | BufferSource,
275
+ opt: CookieOptions = {}
276
+ ): Promise<string> => {
277
+ const signature = await makeSignature(value, secret)
278
+ value = `${value}.${signature}`
279
+ value = encodeURIComponent(value)
280
+ return _serialize(name, value, opt)
281
+ }
@@ -0,0 +1,43 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { md5, sha1, sha256 } from './crypto.ts'
3
+
4
+ describe('crypto', () => {
5
+ it('sha256', async () => {
6
+ expect(await sha256('acryl')).toBe(
7
+ '1722bce316636434b7346179cda6e5918d1265e875548d8393d897d0e8056490'
8
+ )
9
+ expect(await sha256('炎')).toBe(
10
+ '1fddc5a562ee1fbeb4fc6def7d4be4911fcdae4273b02ae3a507b170ba0ea169'
11
+ )
12
+ expect(await sha256('abcdedf')).not.toBe('abcdef')
13
+ })
14
+
15
+ it('sha1', async () => {
16
+ expect(await sha1('acryl')).toBe('a0d04d622e8eee4b96ec3e768158f79849e4feaa')
17
+ expect(await sha1('炎')).toBe('d56e09ae2421b2b8a0b5ee5fdceaed663c8c9472')
18
+ expect(await sha1('abcdedf')).not.toBe('abcdef')
19
+ })
20
+
21
+ // MD5 is not part of the WebCrypto standard.
22
+ // Node.js' Web Crypto API does not support it (But Cloudflare Workers supports it).
23
+ // We should skip this test in a Node.js environment.
24
+ it.skip('md5', async () => {
25
+ expect(await md5('acryl')).toBe('6d6de29f8c2b6b8741b0d549b556de5c')
26
+ expect(await md5('炎')).toBe('f620d89a5a782c22b4420acb39121be3')
27
+ expect(await md5('abcdedf')).not.toBe('abcdef')
28
+ })
29
+
30
+ it('Should not be the same values - compare difference objects', async () => {
31
+ expect(await sha256({ foo: 'bar' })).not.toEqual(
32
+ await sha256({
33
+ bar: 'foo',
34
+ })
35
+ )
36
+ })
37
+
38
+ it('Should create hash for Buffer', async () => {
39
+ const hash = createHash('sha256').update(new Uint8Array(1)).digest('hex')
40
+ expect(await sha256(new Uint8Array(1))).toBe(hash)
41
+ expect(await sha256(new Uint8Array(1))).not.toEqual(await sha256(new Uint8Array(2)))
42
+ })
43
+ })
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @module
3
+ * Crypto utility.
4
+ */
5
+
6
+ import type { JSONValue } from './types.ts'
7
+
8
+ type Algorithm = {
9
+ name: string
10
+ alias: string
11
+ }
12
+
13
+ type Data = string | boolean | number | JSONValue | ArrayBufferView | ArrayBuffer
14
+
15
+ export const sha256 = async (data: Data): Promise<string | null> => {
16
+ const algorithm: Algorithm = { name: 'SHA-256', alias: 'sha256' }
17
+ const hash = await createHash(data, algorithm)
18
+ return hash
19
+ }
20
+
21
+ export const sha1 = async (data: Data): Promise<string | null> => {
22
+ const algorithm: Algorithm = { name: 'SHA-1', alias: 'sha1' }
23
+ const hash = await createHash(data, algorithm)
24
+ return hash
25
+ }
26
+
27
+ export const md5 = async (data: Data): Promise<string | null> => {
28
+ const algorithm: Algorithm = { name: 'MD5', alias: 'md5' }
29
+ const hash = await createHash(data, algorithm)
30
+ return hash
31
+ }
32
+
33
+ export const createHash = async (data: Data, algorithm: Algorithm): Promise<string | null> => {
34
+ let sourceBuffer: ArrayBufferView | ArrayBuffer
35
+
36
+ if (ArrayBuffer.isView(data) || data instanceof ArrayBuffer) {
37
+ sourceBuffer = data
38
+ } else {
39
+ if (typeof data === 'object') {
40
+ data = JSON.stringify(data)
41
+ }
42
+ sourceBuffer = new TextEncoder().encode(String(data))
43
+ }
44
+
45
+ if (crypto && crypto.subtle) {
46
+ const buffer = await crypto.subtle.digest(
47
+ {
48
+ name: algorithm.name,
49
+ },
50
+ sourceBuffer as ArrayBuffer
51
+ )
52
+ const hash = Array.prototype.map
53
+ .call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2))
54
+ .join('')
55
+ return hash
56
+ }
57
+ return null
58
+ }
@@ -0,0 +1,61 @@
1
+ import { decodeBase64Url, encodeBase64Url } from './encode.ts'
2
+
3
+ const toURLBase64 = (base64String: string): string =>
4
+ base64String.replace(/\+|\//g, (m) => ({ '+': '-', '/': '_' })[m] ?? m)
5
+
6
+ const str2UInt8Array = (s: string): Uint8Array<ArrayBuffer> => {
7
+ const buffer = new Uint8Array<ArrayBuffer>(new ArrayBuffer(s.length))
8
+ for (let i = 0, len = buffer.byteLength; i < len; i++) {
9
+ buffer[i] = s.charCodeAt(i)
10
+ }
11
+ return buffer
12
+ }
13
+
14
+ describe('base64', () => {
15
+ const utf8Encoder = new TextEncoder()
16
+ describe.each([
17
+ // basic
18
+ [utf8Encoder.encode('Hello, 世界'), 'SGVsbG8sIOS4lueVjA=='],
19
+ [utf8Encoder.encode('炎'), '54KO'],
20
+ [utf8Encoder.encode('🔥'), '8J+UpQ=='],
21
+ [
22
+ utf8Encoder.encode('http://github.com/acryldev/acryl'),
23
+ 'aHR0cDovL2dpdGh1Yi5jb20vYWNyeWxkZXYvYWNyeWw=',
24
+ ],
25
+
26
+ // RFC 3548 examples
27
+ [str2UInt8Array('\x14\xfb\x9c\x03\xd9\x7e'), 'FPucA9l+'],
28
+ [str2UInt8Array('\x14\xfb\x9c\x03\xd9'), 'FPucA9k='],
29
+ [str2UInt8Array('\x14\xfb\x9c\x03'), 'FPucAw=='],
30
+
31
+ // RFC 4648 examples
32
+ [str2UInt8Array(''), ''],
33
+ [str2UInt8Array('f'), 'Zg=='],
34
+ [str2UInt8Array('fo'), 'Zm8='],
35
+ [str2UInt8Array('foo'), 'Zm9v'],
36
+ [str2UInt8Array('foob'), 'Zm9vYg=='],
37
+ [str2UInt8Array('fooba'), 'Zm9vYmE='],
38
+ [str2UInt8Array('foobar'), 'Zm9vYmFy'],
39
+
40
+ // Wikipedia examples
41
+ [str2UInt8Array('sure.'), 'c3VyZS4='],
42
+ [str2UInt8Array('sure'), 'c3VyZQ=='],
43
+ [str2UInt8Array('sur'), 'c3Vy'],
44
+ [str2UInt8Array('su'), 'c3U='],
45
+ [str2UInt8Array('leasure.'), 'bGVhc3VyZS4='],
46
+ [str2UInt8Array('easure.'), 'ZWFzdXJlLg=='],
47
+ [str2UInt8Array('asure.'), 'YXN1cmUu'],
48
+ [str2UInt8Array('sure.'), 'c3VyZS4='],
49
+ ])('%s, %s', (stdDecoded, stdEncoded) => {
50
+ it('encode', () => {
51
+ const got = encodeBase64Url(stdDecoded.buffer)
52
+ const want = toURLBase64(stdEncoded)
53
+ expect(got).toStrictEqual(want)
54
+ })
55
+ it('decode', () => {
56
+ const got = decodeBase64Url(toURLBase64(stdEncoded))
57
+ const want = stdDecoded
58
+ expect(got).toStrictEqual(want)
59
+ })
60
+ })
61
+ })
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @module
3
+ * Encode utility.
4
+ */
5
+
6
+ export const decodeBase64Url = (str: string): Uint8Array<ArrayBuffer> => {
7
+ return decodeBase64(str.replace(/_|-/g, (m) => ({ _: '/', '-': '+' })[m] ?? m))
8
+ }
9
+
10
+ export const encodeBase64Url = (buf: ArrayBufferLike): string =>
11
+ encodeBase64(buf).replace(/\/|\+/g, (m) => ({ '/': '_', '+': '-' })[m] ?? m)
12
+
13
+ // This approach is written in MDN.
14
+ // btoa does not support utf-8 characters. So we need a little bit hack.
15
+ export const encodeBase64 = (buf: ArrayBufferLike): string => {
16
+ let binary = ''
17
+ const bytes = new Uint8Array(buf)
18
+ for (let i = 0, len = bytes.length; i < len; i++) {
19
+ binary += String.fromCharCode(bytes[i])
20
+ }
21
+ return btoa(binary)
22
+ }
23
+
24
+ // atob does not support utf-8 characters. So we need a little bit hack.
25
+ export const decodeBase64 = (str: string): Uint8Array<ArrayBuffer> => {
26
+ const binary = atob(str)
27
+ const bytes = new Uint8Array<ArrayBuffer>(new ArrayBuffer(binary.length))
28
+ const half = binary.length / 2
29
+ for (let i = 0, j = binary.length - 1; i <= half; i++, j--) {
30
+ bytes[i] = binary.charCodeAt(i)
31
+ bytes[j] = binary.charCodeAt(j)
32
+ }
33
+ return bytes
34
+ }
@@ -0,0 +1,78 @@
1
+ import { getFilePath, getFilePathWithoutDefaultDocument } from './filepath.ts'
2
+
3
+ describe('getFilePathWithoutDefaultDocument', () => {
4
+ it('Should return file path correctly', async () => {
5
+ expect(getFilePathWithoutDefaultDocument({ filename: 'foo.txt' })).toBe('foo.txt')
6
+ expect(getFilePathWithoutDefaultDocument({ filename: 'foo.txt', root: 'bar' })).toBe(
7
+ 'bar/foo.txt'
8
+ )
9
+
10
+ expect(getFilePathWithoutDefaultDocument({ filename: '../foo' })).toBeUndefined()
11
+ expect(getFilePathWithoutDefaultDocument({ filename: '/../foo' })).toBeUndefined()
12
+ expect(getFilePathWithoutDefaultDocument({ filename: './../foo' })).toBeUndefined()
13
+ expect(getFilePathWithoutDefaultDocument({ filename: 'foo..bar.txt' })).toBe('foo..bar.txt')
14
+ expect(getFilePathWithoutDefaultDocument({ filename: '/foo..bar.txt' })).toBe('foo..bar.txt')
15
+ expect(getFilePathWithoutDefaultDocument({ filename: './foo..bar.txt' })).toBe('foo..bar.txt')
16
+ expect(getFilePathWithoutDefaultDocument({ filename: './..foo/bar.txt' })).toBe('..foo/bar.txt')
17
+ expect(getFilePathWithoutDefaultDocument({ filename: './foo../bar.txt' })).toBe('foo../bar.txt')
18
+ expect(getFilePathWithoutDefaultDocument({ filename: './..foo../bar.txt' })).toBe(
19
+ '..foo../bar.txt'
20
+ )
21
+
22
+ expect(
23
+ getFilePathWithoutDefaultDocument({ filename: slashToBackslash('/../foo') })
24
+ ).toBeUndefined()
25
+ expect(
26
+ getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./../foo') })
27
+ ).toBeUndefined()
28
+ expect(getFilePathWithoutDefaultDocument({ filename: slashToBackslash('foo..bar.txt') })).toBe(
29
+ 'foo..bar.txt'
30
+ )
31
+ expect(getFilePathWithoutDefaultDocument({ filename: slashToBackslash('/foo..bar.txt') })).toBe(
32
+ 'foo..bar.txt'
33
+ )
34
+ expect(
35
+ getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./foo..bar.txt') })
36
+ ).toBe('foo..bar.txt')
37
+ expect(
38
+ getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./..foo/bar.txt') })
39
+ ).toBe('..foo/bar.txt')
40
+ expect(
41
+ getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./foo../bar.txt') })
42
+ ).toBe('foo../bar.txt')
43
+ expect(
44
+ getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./..foo../bar.txt') })
45
+ ).toBe('..foo../bar.txt')
46
+
47
+ // Multiple path segments: every backslash must be normalized, not just the first
48
+ expect(
49
+ getFilePathWithoutDefaultDocument({ filename: slashToBackslash('foo/bar/baz.txt') })
50
+ ).toBe('foo/bar/baz.txt')
51
+ expect(
52
+ getFilePathWithoutDefaultDocument({
53
+ filename: slashToBackslash('foo/bar/baz.txt'),
54
+ root: 'assets',
55
+ })
56
+ ).toBe('assets/foo/bar/baz.txt')
57
+ })
58
+ })
59
+
60
+ describe('getFilePath', () => {
61
+ it('Should return file path correctly', async () => {
62
+ expect(getFilePath({ filename: 'foo' })).toBe('foo/index.html')
63
+
64
+ expect(getFilePath({ filename: 'foo', root: 'bar' })).toBe('bar/foo/index.html')
65
+
66
+ expect(getFilePath({ filename: 'foo', defaultDocument: 'index.txt' })).toBe('foo/index.txt')
67
+ expect(getFilePath({ filename: 'foo', root: 'bar', defaultDocument: 'index.txt' })).toBe(
68
+ 'bar/foo/index.txt'
69
+ )
70
+
71
+ expect(getFilePath({ filename: 'filename.suffix_index' })).toBe('filename.suffix_index')
72
+ expect(getFilePath({ filename: 'filename.suffix-index' })).toBe('filename.suffix-index')
73
+ })
74
+ })
75
+
76
+ function slashToBackslash(filename: string) {
77
+ return filename.split('/').join('\\')
78
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @module
3
+ * FilePath utility.
4
+ */
5
+
6
+ type FilePathOptions = {
7
+ filename: string
8
+ root?: string
9
+ defaultDocument?: string
10
+ }
11
+
12
+ export const getFilePath = (options: FilePathOptions): string | undefined => {
13
+ let filename = options.filename
14
+ const defaultDocument = options.defaultDocument || 'index.html'
15
+
16
+ if (filename.endsWith('/')) {
17
+ // /top/ => /top/index.html
18
+ filename = filename.concat(defaultDocument)
19
+ } else if (!filename.match(/\.[a-zA-Z0-9_-]+$/)) {
20
+ // /top => /top/index.html
21
+ filename = filename.concat('/' + defaultDocument)
22
+ }
23
+
24
+ const path = getFilePathWithoutDefaultDocument({
25
+ root: options.root,
26
+ filename,
27
+ })
28
+
29
+ return path
30
+ }
31
+
32
+ export const getFilePathWithoutDefaultDocument = (
33
+ options: Omit<FilePathOptions, 'defaultDocument'>
34
+ ): string | undefined => {
35
+ let root = options.root || ''
36
+ let filename = options.filename
37
+
38
+ if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename)) {
39
+ return
40
+ }
41
+
42
+ // /foo.html => foo.html
43
+ filename = filename.replace(/^\.?[\/\\]/, '')
44
+
45
+ // foo\bar.txt => foo/bar.txt
46
+ filename = filename.replace(/\\/g, '/')
47
+
48
+ // assets/ => assets
49
+ root = root.replace(/\/$/, '')
50
+
51
+ // ./assets/foo.html => assets/foo.html
52
+ let path = root ? root + '/' + filename : filename
53
+ path = path.replace(/^\.?\//, '')
54
+
55
+ if (root[0] !== '/' && path[0] === '/') {
56
+ return
57
+ }
58
+
59
+ return path
60
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @module
3
+ * Handler utility.
4
+ */
5
+
6
+ import { COMPOSED_HANDLER } from './constants.ts'
7
+
8
+ export const isMiddleware = (handler: Function) => handler.length > 1
9
+ export const findTargetHandler = (handler: Function): Function => {
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ return (handler as any)[COMPOSED_HANDLER]
12
+ ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ findTargetHandler((handler as any)[COMPOSED_HANDLER])
14
+ : handler
15
+ }