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,108 @@
1
+ # Migration from 0.3
2
+
3
+ 0.3.0 had the framework's own small router API: routes registered one at a time on `ctx.router`, and a `RouteContext` with a few
4
+ helpers. 0.4.0 replaces it with the ported application API: a plugin builds an `App` and mounts it. The old calls are removed, not kept
5
+ as aliases, so there is one way to write a route.
6
+
7
+ ## Before and after
8
+
9
+ 0.3.0:
10
+
11
+ ```ts
12
+ import { HttpError } from 'acryl-router'
13
+ import { readJson, streamSSE, valid, validate } from 'acryl-router/helpers'
14
+ import { bodyLimit } from 'acryl-router/middleware'
15
+
16
+ export function apply(ctx: Context): void {
17
+ const api = ctx.router.group('/todo')
18
+ api.use(bodyLimit({ maxBytes: 16 * 1024 }))
19
+ api.get('/todos/:id', c => {
20
+ const todo = find(c.param('id'))
21
+ if (!todo) throw new HttpError(404, { message: 'no such todo' })
22
+ return c.json(todo)
23
+ })
24
+ api.post('/todos', validate('json', NewTodo), c => c.json(create(valid(c, 'json')), 201))
25
+ api.get('/events', c => streamSSE(c, stream => {
26
+ const off = events.on('change', todo => stream.send(todo, { event: 'change' }))
27
+ return off
28
+ }))
29
+ ctx.inject(['routerHttp'], inner => { inner.routerHttp.mount('/todo') })
30
+ }
31
+ ```
32
+
33
+ 0.4.0:
34
+
35
+ ```ts
36
+ import { App, HTTPException } from 'acryl-router'
37
+ import { schemaValidator, streamSSE } from 'acryl-router/helpers'
38
+ import { bodyLimit } from 'acryl-router/middleware'
39
+
40
+ export function apply(ctx: Context): void {
41
+ const app = new App()
42
+ .use(bodyLimit({ maxSize: 16 * 1024 }))
43
+ .get('/todos/:id', c => {
44
+ const todo = find(c.req.param('id'))
45
+ if (!todo) throw new HTTPException(404, { message: 'no such todo' })
46
+ return c.json(todo)
47
+ })
48
+ .post('/todos', schemaValidator('json', NewTodo), c => c.json(create(c.req.valid('json')), 201))
49
+ .get('/events', c => streamSSE(c, async stream => {
50
+ const off = events.on('change', todo => { void stream.writeSSE({ event: 'change', data: JSON.stringify(todo) }) })
51
+ stream.onAbort(off)
52
+ while (!stream.aborted) await stream.sleep(15_000).then(() => stream.write(': keep-alive\n\n'))
53
+ }))
54
+
55
+ ctx.router.route('/todo', app)
56
+ ctx.inject(['routerHttp'], inner => { inner.routerHttp.mount('/todo') })
57
+ }
58
+ ```
59
+
60
+ Public paths do not change: the mount path plus the app's route path is the old group prefix plus the old route path.
61
+
62
+ ## What maps to what
63
+
64
+ | 0.3.0 | 0.4.0 |
65
+ | --- | --- |
66
+ | `ctx.router.get/post/put/patch/delete/all/on(path, ...)` | the same methods on an `App`, then `ctx.router.route(prefix, app)` |
67
+ | `ctx.router.use(...)` | `app.use(...)` on the app it should wrap (or on an app mounted earlier, for a guard in front of other plugins) |
68
+ | `ctx.router.group(prefix, ...middleware)` | one `App` per group mounted at `prefix`, or `app.route(prefix, sub)` / `app.basePath(prefix)` |
69
+ | the remover each registration returned | the one remover `ctx.router.route` returns; the mount is owned by the calling plugin as before |
70
+ | `ctx.router.onError`, `ctx.router.notFound` | unchanged; an app's own `app.onError` now also applies to its routes |
71
+ | `c.param(name)`, `c.params` | `c.req.param(name)`, `c.req.param()` |
72
+ | `c.query(name)`, `c.query()` | `c.req.query(name)`, `c.req.query()` (and `c.req.queries(name)` for repeated values) |
73
+ | `c.url` | `new URL(c.req.url)`, or `c.req.path` |
74
+ | `c.req` (the Web `Request`) | `c.req.raw`; `c.req` is now the request wrapper |
75
+ | `c.connection` | `connectionOf(c)` (or `getConnInfo(c)`); type the app `new App<RouterEnv>()` to read `c.env.connection` |
76
+ | `c.signal` | `c.req.raw.signal` |
77
+ | `c.waitUntil(task)` | `c.executionCtx.waitUntil(task)` |
78
+ | `c.get(key)` / `c.set(key, value)` | unchanged, typed by the app's `Variables` (`c.var.key` also works) |
79
+ | `c.json/text/html/body/redirect/notFound/header/status` | unchanged |
80
+ | `HttpError(status, { message, response, cause })` | `HTTPException(status, { message, res, cause })` |
81
+ | `readJson(c)`, `readText(c)`, `readBytes(c)` with `{ maxBytes }` | `bodyLimit({ maxSize })` in front, then `c.req.json()`, `c.req.text()`, `c.req.arrayBuffer()` |
82
+ | `bodyLimit({ maxBytes })` | `bodyLimit({ maxSize })` |
83
+ | `validate(target, schema)` and `valid(c, target)` | `schemaValidator(target, schema)` and `c.req.valid(target)`, with more targets (`form`, `header`, `cookie`) |
84
+ | `streamSSE(c, stream => cleanup)` with `stream.send(data, { event, id })` | `streamSSE(c, async stream => ...)` with `stream.writeSSE({ data, event, id })`; `data` is a string, so `JSON.stringify` objects |
85
+ | the SSE stream's automatic keep-alive comment | none: write a comment on a timer when a proxy closes idle connections |
86
+ | the cleanup function `run` returned | `stream.onAbort(cleanup)`; the callback's end closes the stream |
87
+ | a 405 with `Allow` for a known path and another method | 404 by default; add the `methodNotAllowed` middleware for 405 |
88
+ | the `Middleware` type, `(c, next) => Response` | `MiddlewareHandler`, `async (c, next) => { await next() }`; change `c.res` after `next()` instead of returning it |
89
+ | `logger`, `cors`, `requestId`, `secureHeaders`, `loopbackOnly` | same names in `acryl-router/middleware`; the first four now take the core's options |
90
+
91
+ ## Behavior changes to check
92
+
93
+ - **A mount is a snapshot.** Routes an app registers after `ctx.router.route(...)` are not served. 0.3.0 registered each call live.
94
+ - **A JSON validator needs a JSON `Content-Type`.** Without it the schema sees `{}`. 0.3.0 read the body whatever its type.
95
+ - **`await next()` does not throw.** A thrown error has already become a response; read it from `c.error`.
96
+ - **Trailing slashes are strict by default, as before**, and `trailingSlash: 'ignore'` still treats `/a` and `/a/` as one path.
97
+ - **Disposal is unchanged:** a plugin's unload ends the requests and streams its code is serving, and router disposal ends all of them,
98
+ bounded by `disposeGraceMs`.
99
+
100
+ ## Checklist
101
+
102
+ For each plugin:
103
+
104
+ - collect its route registrations into one app per mount path and keep the public paths;
105
+ - move middleware and error handling onto the owning app;
106
+ - replace the context and helper calls from the table above;
107
+ - run its route tests, then one activation test that unloads and reloads the plugin;
108
+ - leave no 0.3.0-style calls behind: the old API is gone, and two styles would give new code a second API to copy.
@@ -0,0 +1,103 @@
1
+ # Migration from the DSH webServer
2
+
3
+ ACRYL plugins have registered HTTP routes directly with the DSH `webServer` (or the Host's `acrylWeb` wrapper of it): one Node-style handler
4
+ per exact path or prefix, with no middleware chain. The framework replaces that with an `App`, a mount, and Web-standard requests.
5
+
6
+ ## Before and after
7
+
8
+ Before, a plugin claims a path and writes the Node response itself:
9
+
10
+ ```ts
11
+ export function apply(ctx: Context): void {
12
+ ctx.effect(() => ctx.acrylWeb.register({
13
+ kind: 'exact',
14
+ path: '/api/health',
15
+ handler: (req, res) => {
16
+ res.statusCode = 200
17
+ res.setHeader('content-type', 'application/json')
18
+ res.end(JSON.stringify({ ok: true }))
19
+ },
20
+ }))
21
+ }
22
+ ```
23
+
24
+ After, it builds an app, mounts it, and asks the Host adapter to serve the prefix:
25
+
26
+ ```ts
27
+ import { App } from 'acryl-router'
28
+
29
+ export const inject = ['router']
30
+
31
+ export function apply(ctx: Context): void {
32
+ const app = new App().get('/health', c => c.json({ ok: true }))
33
+ ctx.router.route('/api', app)
34
+ ctx.inject(['routerHttp'], inner => { inner.routerHttp.mount('/api/health') })
35
+ }
36
+ ```
37
+
38
+ `routerHttp.mount(prefix)` claims the prefix on the same web server, so the public path does not change. The terminal UI and tests reach
39
+ the route in process (`ctx.router.request('/api/health')`), which the old handler never allowed.
40
+
41
+ ## What maps to what
42
+
43
+ | webServer | Framework |
44
+ | --- | --- |
45
+ | `register({ kind: 'exact', path, handler })` | a route on an `App`, mounted with `ctx.router.route`; served with `routerHttp.mount(path)` |
46
+ | `register({ kind: 'prefix', path, handler })` | an `App` mounted at `path` with its own routes below it |
47
+ | `req.method` checks in the handler | `app.get`, `app.post`, ... (add `methodNotAllowed` for 405 answers) |
48
+ | `new URL(req.url, ...)` and manual parsing | `c.req.param`, `c.req.query`, `c.req.header` |
49
+ | reading the body stream by hand | `c.req.json()`, `c.req.parseBody()` behind `bodyLimit`, or a validator |
50
+ | `res.statusCode`, `res.setHeader`, `res.end` | `c.json`, `c.text`, `c.body`, `c.header`, or a returned `Response` |
51
+ | `req.socket.remoteAddress` | `connectionOf(c)` / `getConnInfo(c)` |
52
+ | a peer or origin check copied into each handler | `loopbackOnly`, `cors`, `csrf` or a middleware of your own |
53
+ | the disposer returned by `register` | nothing: `route` and `mount` belong to the calling plugin |
54
+
55
+ ## Migration steps
56
+
57
+ Move one capability at a time:
58
+
59
+ 1. Build an `App` for the capability and move its handlers into it without changing their public answers.
60
+ 2. Mount it with `ctx.router.route` and serve the same prefix with `routerHttp.mount`.
61
+ 3. Remove the direct `webServer` registration in the same change: the two cannot claim one prefix, `mount` throws.
62
+ 4. Test the route through the router (`ctx.router.request`) and over the Host, and unload and reload the plugin to confirm the route goes
63
+ away and comes back once.
64
+
65
+ Keep domain calls unchanged until the transport change is green; do not rewrite transport, handlers and authorization in one step.
66
+
67
+ ## A worked example: the workspace
68
+
69
+ `acryl-workspace` (files, git, terminals, projects, agents) was the largest user of the webServer: 24 exact routes. It now builds one app
70
+ per domain folder (`filesRoutes`, `gitRoutes`, `ptyRoutes`, ...), composes them once (`privateApi` in `src/private-route.ts`), mounts the
71
+ result after the effect that builds its services, and serves the one prefix `/api/acryl-workspace`:
72
+
73
+ ```ts
74
+ const built: { api?: App } = {}
75
+ ctx.effect(() => {
76
+ // ... the PTY table, git, files, the project registry ...
77
+ built.api = privateApi(filesRoutes(files, report), gitRoutes(git, report) /* , ... */)
78
+ return async () => { /* release them */ }
79
+ })
80
+ ctx.router.route('/', built.api)
81
+ ctx.routerHttp.mount('/api/acryl-workspace')
82
+ ```
83
+
84
+ Mounting after the effect orders the unload: the mount goes first, so the requests and event streams its code is serving end and are
85
+ waited for, then the services are released. Each route names its guard (`loopbackOnly({ callers: 'own-page' })` for the page, a bearer token
86
+ for the agent hooks) and its body limit; the event stream is `streamSSE`, and its keep-alive is one timer the plugin owns, not one per
87
+ request. The terminal's WebSocket stays a webServer upgrade until the router carries upgrades (spec 047 T009).
88
+
89
+ The answers that changed are recorded with it: a body over a route's limit is 413 on every route (some said 400); a wrong method is 405 with
90
+ an `Allow` header on every route; a missing or wrong agent-hook token is 401 with `WWW-Authenticate` (it was 403); and the hook's `Host`
91
+ must be the app's own loopback address. Its routes refuse an in-process call on purpose (the guard needs a socket to check), so its tests
92
+ run over loopback through the same adapter (`tests/serve-api.ts`), which is the "test over the Host" below.
93
+
94
+ ## Completion check
95
+
96
+ A capability is migrated when:
97
+
98
+ - no handler in it touches Node `req` / `res` objects or the old server types;
99
+ - its public paths and answers are unchanged, or the changes are recorded;
100
+ - it unloads and reloads without duplicate or missing routes;
101
+ - the same handler passes an in-process test and a test over the Host.
102
+
103
+ Typed RPC stays a separate concern. Do not convert RPC methods because their names look like HTTP routes.
@@ -0,0 +1,75 @@
1
+ # Node server
2
+
3
+ `acryl-node-server` serves the whole router over HTTP from a standalone Node.js process. Use it for an app that runs without the ACRYL
4
+ Host. Inside the Host (Web and Desktop), the Host owns the web server and a plugin asks `acryl-router-http` to serve its prefix instead.
5
+
6
+ ## Serve the router
7
+
8
+ The server is a Cordis plugin that provides the `nodeServer` service:
9
+
10
+ ```ts
11
+ import { Context } from '@deepseek-ai/cordis'
12
+ import * as router from 'acryl-router'
13
+ import * as nodeServer from 'acryl-node-server'
14
+ import * as todo from './todo.ts'
15
+
16
+ const root = new Context()
17
+ root.plugin(router)
18
+ root.plugin(nodeServer, { port: 3000 })
19
+ root.plugin(todo)
20
+
21
+ root.inject(['nodeServer'], ctx => {
22
+ console.log(`listening on ${ctx.nodeServer.url}`)
23
+ })
24
+ ```
25
+
26
+ | Option | Default | Meaning |
27
+ | --- | --- | --- |
28
+ | `hostname` | `127.0.0.1` | address to bind; binding elsewhere exposes the app to the network |
29
+ | `port` | `0` | port to listen on; 0 lets the system choose |
30
+ | `scan` | `20` | how many further ports to try when the configured one is taken |
31
+
32
+ The plugin becomes active once the server listens, so a plugin that injects `nodeServer` can rely on `ctx.nodeServer.url` and
33
+ `ctx.nodeServer.port`. A failure to bind fails the plugin.
34
+
35
+ Every request goes to `ctx.router.fetch`, so routes appear and disappear as plugins mount and unload, with no restart. A plugin's
36
+ `ctx.inject(['routerHttp'], ...)` call never fires here: this adapter serves every prefix already.
37
+
38
+ ## Connection information
39
+
40
+ The adapter passes the socket's peer address and local port to the router as connection facts. Handlers read them through
41
+ `connectionOf(c)` or `getConnInfo(c)` and never touch Node socket objects, so the same handler runs in process and through the Host. See
42
+ [Context](./context.md#transport-facts).
43
+
44
+ ## Static files
45
+
46
+ The Node `serveStatic` reads files from disk:
47
+
48
+ ```ts
49
+ import { serveStatic } from 'acryl-node-server'
50
+
51
+ const app = new App()
52
+ .use('/assets/*', serveStatic({ root: './public' }))
53
+ .get('/', serveStatic({ path: './public/index.html' }))
54
+ ```
55
+
56
+ `root` is joined with the request path, relative to the working directory unless absolute. A request path with a `.` or `..` segment, a
57
+ doubled slash, a backslash or (unless `allowPercentInPath`) an encoded `%` is treated as not found and passed on with `next()`. Only `GET`
58
+ and `HEAD` are served. Other options: `rewriteRequestPath`, `precompressed` (serve a `.br`, `.zst` or `.gz` sibling when the client
59
+ accepts it), `index` (default `index.html`), and `onFound` / `onNotFound` hooks. Serve build output, never source folders.
60
+
61
+ ## Shutdown
62
+
63
+ Disposing the plugin (or the root context) ends open streams, closes every connection and returns once the port is free. Disposing a
64
+ plugin that only mounted routes is different: its mount and the requests it is serving end, and the server keeps serving the others (see
65
+ [Plugin lifecycle](./plugin-lifecycle.md#disposal-sequence)).
66
+
67
+ ## Choosing a port
68
+
69
+ Let the process configuration choose the address. Do not hard-code a development port in a reusable plugin: a plugin mounts routes and
70
+ leaves the socket to whichever adapter the app runs with.
71
+
72
+ ## Not yet supported
73
+
74
+ WebSocket upgrades under the router are a later task. The adapter's own WebSocket support exists in `src/adapter/` with its tests but is
75
+ not wired to the router yet.
@@ -0,0 +1,111 @@
1
+ # Plugin lifecycle and disposal
2
+
3
+ Cordis gives every framework registration an owner and a lifetime. This is the main difference between using the application API on its
4
+ own and using it as part of ACRYL.
5
+
6
+ ## Every registration has an owner
7
+
8
+ ```ts
9
+ export const name = 'todo'
10
+ export const inject = ['router']
11
+
12
+ export function apply(ctx: Context): void {
13
+ ctx.router.route('/todo', createTodoApp())
14
+ }
15
+ ```
16
+
17
+ `route`, `onError` and `notFound` register themselves as effects of the plugin that called them, so there is nothing to wrap: when the
18
+ plugin's fiber unloads, its mount goes away, and a reload mounts it again without duplicates. `route` returns a function that removes the
19
+ mount sooner. Never register routes as a side effect of importing a module.
20
+
21
+ Resources an app uses (timers, watchers, connections, subprocesses) belong to one `ctx.effect()` of the plugin, whose disposer releases
22
+ them:
23
+
24
+ ```ts
25
+ export function apply(ctx: Context): void {
26
+ const events = new EventEmitter()
27
+ ctx.effect(() => {
28
+ const timer = setInterval(() => events.emit('tick'), 1000)
29
+ return () => clearInterval(timer)
30
+ }, 'todo: ticker')
31
+ ctx.router.route('/todo', createTodoApp(events))
32
+ }
33
+ ```
34
+
35
+ ## Live composition
36
+
37
+ The router is built for a host whose plugin set changes while it runs. Mounts are added and removed, and each request after a change is
38
+ served by an app composed from the mounts that are live at that time; a request already running keeps the app it started with. The core's
39
+ route tables only grow, so the router rebuilds the composition instead of editing it. Application code does not manage this.
40
+
41
+ A mount is a snapshot of the app's routes at the `route` call. Build the app, then mount it.
42
+
43
+ ## Missing providers
44
+
45
+ A plugin that injects `router` stays PENDING while no router exists. That is a normal Cordis state, not an error: when a router appears the
46
+ plugin activates and mounts. When the router is replaced, the plugin unloads and activates again against the new one, so it must not keep
47
+ the old service object anywhere else.
48
+
49
+ ## Disposal sequence
50
+
51
+ When a plugin that owns a mount unloads:
52
+
53
+ 1. The mount leaves the composition, so new requests no longer reach it.
54
+ 2. The requests its code is still serving end: a request inside one of its handlers or middleware (before `next()` returned), and a
55
+ response body one of its handlers produced. Their signals abort (`c.req.raw.signal`) and their bodies are cancelled, which runs the
56
+ stream helpers' `onAbort`.
57
+ 3. The unload waits for those requests, their bodies and their `c.executionCtx.waitUntil` work, bounded by the router's `disposeGraceMs`.
58
+
59
+ A request the plugin's middleware already passed on to another plugin's handler is not touched. When the router itself is disposed, the
60
+ same happens to every running request, and new requests are answered with 503.
61
+
62
+ Handlers get a chance to release their resources, nothing a plugin started outlives it, and disposal cannot hang forever.
63
+
64
+ ## Services and the shadow context
65
+
66
+ A plugin reaches a service through its own view of it: `ctx.router` in one plugin and in another are two different objects over one
67
+ service. Two facts follow, each with a test as proof.
68
+
69
+ **Fields are shared.** Reading or assigning a service's field through any plugin's view reads or writes the one instance, so every caller
70
+ sees the same value (`plugins/acryl-router/tests/shadow-state.spec.ts`). Keeping changing state in one object, as the router does, is a
71
+ readability choice, not a requirement.
72
+
73
+ **`this.ctx` inside a method is the caller's view, not the service's own context.** It is what lets `route` tie a mount to the plugin
74
+ that called it. It is the wrong context for starting something that should belong to the service: a plugin started with
75
+ `this.ctx.plugin(...)` from a method resolves services as the service's own fiber and fails with "cannot get property router without
76
+ inject". Keep the context the constructor received and use that:
77
+
78
+ ```ts
79
+ class Installer extends Service {
80
+ private readonly owner: Context
81
+
82
+ constructor(ctx: Context) {
83
+ super(ctx, 'installer')
84
+ this.owner = ctx // the service's own context
85
+ }
86
+
87
+ async install(plugin: Plugin): Promise<void> {
88
+ await this.owner.plugin(plugin) // right: behaves like a plugin mounted at startup
89
+ // await this.ctx.plugin(plugin) // wrong: the caller's view, see above
90
+ }
91
+ }
92
+ ```
93
+
94
+ The proof is `plugins/acryl-router/tests/live-install.spec.ts`, which runs both forms against a live router. This sits next to the general
95
+ rule: a plugin started with `this.ctx.plugin(...)` in `apply` is a child of the plugin and unloads with it.
96
+
97
+ ## Module state
98
+
99
+ Do not hide mutable state in module globals. Cordis can reload a plugin and run several surfaces from one host, and a module global
100
+ outlives the plugin that wrote it.
101
+
102
+ ## What to test
103
+
104
+ Test a route-owning plugin through real Cordis activation for:
105
+
106
+ - activation when the router is present, and PENDING until it appears;
107
+ - route removal after the plugin unloads, and a clean mount after reload, without duplicates over repeated cycles;
108
+ - router replacement without stale service references;
109
+ - disposal while a response is still open.
110
+
111
+ `app.request` tests prove handler behavior. Only activation tests prove ownership.
@@ -0,0 +1,106 @@
1
+ # Request
2
+
3
+ Handlers read input through `c.req`, a thin wrapper around the Web `Request`. The original is `c.req.raw`.
4
+
5
+ ## Route parameters
6
+
7
+ ```ts
8
+ app.get('/entry/:id', c => {
9
+ const id = c.req.param('id')
10
+ return c.json({ id })
11
+ })
12
+
13
+ app.get('/entry/:id/comment/:commentId', c => {
14
+ const { id, commentId } = c.req.param()
15
+ return c.json({ id, commentId })
16
+ })
17
+ ```
18
+
19
+ Named reads keep route contracts visible; read the whole map only when a generic logger or adapter needs it.
20
+
21
+ ## Query strings
22
+
23
+ ```ts
24
+ const page = c.req.query('page') // one value
25
+ const all = c.req.query() // every parameter, first value of each
26
+ const tags = c.req.queries('tag') // repeated values: ?tag=work&tag=home
27
+ ```
28
+
29
+ Convert strings to numbers, booleans or dates at the application boundary and reject invalid values explicitly; a validator does both
30
+ (see [Validation](./validation.md)).
31
+
32
+ ## Headers
33
+
34
+ ```ts
35
+ const requestId = c.req.header('X-Request-Id')
36
+ const headers = c.req.header() // every header, keys in lowercase
37
+ ```
38
+
39
+ Header names are case-insensitive for a named read. With no argument the record's keys are lowercase, so `c.req.header()['X-Foo']` is
40
+ always undefined; read `c.req.header('X-Foo')` instead. Optional headers need a deliberate fallback; do not let a missing authentication
41
+ header silently become an anonymous request unless that is the declared behavior.
42
+
43
+ ## Bodies
44
+
45
+ ```ts
46
+ app.post('/entry', async c => {
47
+ const body = await c.req.json() // application/json
48
+ return c.json({ received: body }, 201)
49
+ })
50
+
51
+ await c.req.text()
52
+ await c.req.arrayBuffer()
53
+ await c.req.blob()
54
+ await c.req.formData()
55
+ await c.req.parseBody() // multipart/form-data or application/x-www-form-urlencoded
56
+ ```
57
+
58
+ The reads are cached, so a validator and the handler can both read the body. Read untrusted bodies behind `bodyLimit`, which refuses a body
59
+ over the limit with 413 while it streams instead of after buffering it.
60
+
61
+ ### Forms and uploads
62
+
63
+ `parseBody()` returns `string | File` per field; the last one wins when a name repeats:
64
+
65
+ ```ts
66
+ const body = await c.req.parseBody()
67
+ body['file'] // string | File
68
+ const many = await c.req.parseBody({ all: true }) // repeated names become arrays
69
+ many['files'] // (string | File)[] when several were sent
70
+ const nested = await c.req.parseBody({ dot: true }) // obj.key1 -> { obj: { key1 } }
71
+ ```
72
+
73
+ A field named with a `[]` suffix (`files[]`) is always an array. Treat every uploaded file as untrusted: check its type, size and
74
+ destination, and never use a caller-provided filename as a filesystem path without normalizing it and confining it to the intended
75
+ directory.
76
+
77
+ ## Validated input
78
+
79
+ `c.req.valid(target)` returns what a validator for `json`, `form`, `query`, `header`, `cookie` or `param` produced, typed by it. See
80
+ [Validation](./validation.md).
81
+
82
+ ## Method, path and URL
83
+
84
+ ```ts
85
+ c.req.method // 'GET'
86
+ c.req.path // '/about/me'
87
+ c.req.url // 'http://127.0.0.1:3290/about/me'
88
+ ```
89
+
90
+ Do not parse the URL by hand to recover route parameters or query values the router already exposes. `routePath(c)` and
91
+ `matchedRoutes(c)` from `acryl-router/helpers` give the registered path and the routes that matched.
92
+
93
+ ## Raw Web Request
94
+
95
+ ```ts
96
+ const raw = c.req.raw
97
+ const url = new URL(raw.url)
98
+ ```
99
+
100
+ Use the raw object for standards-based libraries, streaming bodies and signatures over the exact payload. Use `c.req` for normal
101
+ application code.
102
+
103
+ ## Request cancellation
104
+
105
+ `c.req.raw.signal` aborts when the client goes away, when the plugin serving the request unloads and when the router is
106
+ disposed. Work that ignores it can outlive the plugin that owns it; see [Context](./context.md#cancellation-and-background-work).