acrylblends 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/dist/index.d.ts +22 -16
  2. package/dist/index.js +65 -20
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/plugins/acryl-inspect/LICENSE +21 -0
  6. package/plugins/acryl-inspect/plugin.json +12 -0
  7. package/plugins/acryl-inspect/src/index.ts +3 -0
  8. package/plugins/acryl-inspect/src/report.ts +126 -0
  9. package/plugins/acryl-inspect/src/service.ts +68 -0
  10. package/plugins/acryl-inspect/tests/report.spec.ts +63 -0
  11. package/plugins/acryl-inspect/tests/service.spec.ts +121 -0
  12. package/plugins/acryl-inspect/upstream.json +18 -0
  13. package/plugins/acryl-node-server/LICENSE +47 -0
  14. package/plugins/acryl-node-server/plugin.json +13 -0
  15. package/plugins/acryl-node-server/src/adapter/conninfo.ts +23 -0
  16. package/plugins/acryl-node-server/src/adapter/early-hints.ts +55 -0
  17. package/plugins/acryl-node-server/src/adapter/error.ts +11 -0
  18. package/plugins/acryl-node-server/src/adapter/headers.ts +249 -0
  19. package/plugins/acryl-node-server/src/adapter/index.ts +6 -0
  20. package/plugins/acryl-node-server/src/adapter/listener.ts +540 -0
  21. package/plugins/acryl-node-server/src/adapter/request.ts +797 -0
  22. package/plugins/acryl-node-server/src/adapter/response.ts +186 -0
  23. package/plugins/acryl-node-server/src/adapter/serve-static.ts +238 -0
  24. package/plugins/acryl-node-server/src/adapter/server.ts +36 -0
  25. package/plugins/acryl-node-server/src/adapter/types.ts +82 -0
  26. package/plugins/acryl-node-server/src/adapter/url.ts +42 -0
  27. package/plugins/acryl-node-server/src/adapter/utils/response/constants.ts +1 -0
  28. package/plugins/acryl-node-server/src/adapter/utils/response.ts +4 -0
  29. package/plugins/acryl-node-server/src/adapter/utils/stream.ts +88 -0
  30. package/plugins/acryl-node-server/src/adapter/utils.ts +96 -0
  31. package/plugins/acryl-node-server/src/adapter/websocket-types.ts +45 -0
  32. package/plugins/acryl-node-server/src/adapter/websocket.ts +375 -0
  33. package/plugins/acryl-node-server/src/index.ts +107 -0
  34. package/plugins/acryl-node-server/tests/adapter/app.ts +76 -0
  35. package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +1 -0
  36. package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
  37. package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +1 -0
  38. package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +1 -0
  39. package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +1 -0
  40. package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +5 -0
  41. package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +1 -0
  42. package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
  43. package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +1 -0
  44. package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +1 -0
  45. package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +1 -0
  46. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +1 -0
  47. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +1 -0
  48. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +1 -0
  49. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +1 -0
  50. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +1 -0
  51. package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +34 -0
  52. package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +248 -0
  53. package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +457 -0
  54. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +23 -0
  55. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +27 -0
  56. package/plugins/acryl-node-server/tests/adapter/headers.test.ts +252 -0
  57. package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +190 -0
  58. package/plugins/acryl-node-server/tests/adapter/listener.test.ts +727 -0
  59. package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +189 -0
  60. package/plugins/acryl-node-server/tests/adapter/request.test.ts +1230 -0
  61. package/plugins/acryl-node-server/tests/adapter/response.test.ts +285 -0
  62. package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +849 -0
  63. package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +790 -0
  64. package/plugins/acryl-node-server/tests/adapter/server.test.ts +1302 -0
  65. package/plugins/acryl-node-server/tests/adapter/setup.ts +16 -0
  66. package/plugins/acryl-node-server/tests/adapter/url.test.ts +76 -0
  67. package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +23 -0
  68. package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +75 -0
  69. package/plugins/acryl-node-server/tests/adapter/utils.test.ts +235 -0
  70. package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +260 -0
  71. package/plugins/acryl-node-server/tests/server.spec.ts +141 -0
  72. package/plugins/acryl-node-server/upstream.json +18 -0
  73. package/plugins/acryl-router/LICENSE +47 -0
  74. package/plugins/acryl-router/plugin.json +13 -0
  75. package/plugins/acryl-router/src/client.ts +2 -0
  76. package/plugins/acryl-router/src/connection.ts +42 -0
  77. package/plugins/acryl-router/src/core/app-base.ts +544 -0
  78. package/plugins/acryl-router/src/core/app.test.ts +3833 -0
  79. package/plugins/acryl-router/src/core/app.ts +34 -0
  80. package/plugins/acryl-router/src/core/client/client.test.ts +2313 -0
  81. package/plugins/acryl-router/src/core/client/client.ts +249 -0
  82. package/plugins/acryl-router/src/core/client/fetch-result-please.ts +95 -0
  83. package/plugins/acryl-router/src/core/client/index.ts +17 -0
  84. package/plugins/acryl-router/src/core/client/types.test.ts +149 -0
  85. package/plugins/acryl-router/src/core/client/types.ts +393 -0
  86. package/plugins/acryl-router/src/core/client/utils.test.ts +405 -0
  87. package/plugins/acryl-router/src/core/client/utils.ts +117 -0
  88. package/plugins/acryl-router/src/core/compose.test.ts +751 -0
  89. package/plugins/acryl-router/src/core/compose.ts +73 -0
  90. package/plugins/acryl-router/src/core/context.test.ts +629 -0
  91. package/plugins/acryl-router/src/core/context.ts +781 -0
  92. package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +344 -0
  93. package/plugins/acryl-router/src/core/helper/accepts/accepts.ts +96 -0
  94. package/plugins/acryl-router/src/core/helper/accepts/index.ts +6 -0
  95. package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +55 -0
  96. package/plugins/acryl-router/src/core/helper/adapter/index.ts +90 -0
  97. package/plugins/acryl-router/src/core/helper/conninfo/index.ts +6 -0
  98. package/plugins/acryl-router/src/core/helper/conninfo/types.ts +45 -0
  99. package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +510 -0
  100. package/plugins/acryl-router/src/core/helper/cookie/index.ts +146 -0
  101. package/plugins/acryl-router/src/core/helper/factory/index.test.ts +434 -0
  102. package/plugins/acryl-router/src/core/helper/factory/index.ts +375 -0
  103. package/plugins/acryl-router/src/core/helper/html/index.test.ts +78 -0
  104. package/plugins/acryl-router/src/core/helper/html/index.ts +57 -0
  105. package/plugins/acryl-router/src/core/helper/route/index.test.ts +259 -0
  106. package/plugins/acryl-router/src/core/helper/route/index.ts +144 -0
  107. package/plugins/acryl-router/src/core/helper/streaming/index.ts +9 -0
  108. package/plugins/acryl-router/src/core/helper/streaming/sse.ts +101 -0
  109. package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +128 -0
  110. package/plugins/acryl-router/src/core/helper/streaming/stream.ts +45 -0
  111. package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +34 -0
  112. package/plugins/acryl-router/src/core/helper/streaming/text.ts +15 -0
  113. package/plugins/acryl-router/src/core/helper/streaming/utils.ts +11 -0
  114. package/plugins/acryl-router/src/core/helper/testing/index.test.ts +42 -0
  115. package/plugins/acryl-router/src/core/helper/testing/index.ts +28 -0
  116. package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +124 -0
  117. package/plugins/acryl-router/src/core/helper/websocket/index.ts +140 -0
  118. package/plugins/acryl-router/src/core/http-exception.test.ts +45 -0
  119. package/plugins/acryl-router/src/core/http-exception.ts +77 -0
  120. package/plugins/acryl-router/src/core/index.ts +53 -0
  121. package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +434 -0
  122. package/plugins/acryl-router/src/core/middleware/basic-auth/index.ts +152 -0
  123. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +1018 -0
  124. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.ts +220 -0
  125. package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +273 -0
  126. package/plugins/acryl-router/src/core/middleware/body-limit/index.ts +110 -0
  127. package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +355 -0
  128. package/plugins/acryl-router/src/core/middleware/combine/index.ts +171 -0
  129. package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +535 -0
  130. package/plugins/acryl-router/src/core/middleware/compress/index.ts +142 -0
  131. package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +43 -0
  132. package/plugins/acryl-router/src/core/middleware/context-storage/index.ts +58 -0
  133. package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +532 -0
  134. package/plugins/acryl-router/src/core/middleware/cors/index.ts +163 -0
  135. package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +534 -0
  136. package/plugins/acryl-router/src/core/middleware/csrf/index.ts +150 -0
  137. package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +21 -0
  138. package/plugins/acryl-router/src/core/middleware/etag/digest.ts +95 -0
  139. package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +503 -0
  140. package/plugins/acryl-router/src/core/middleware/etag/index.ts +135 -0
  141. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +217 -0
  142. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.ts +278 -0
  143. package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +1135 -0
  144. package/plugins/acryl-router/src/core/middleware/jwk/index.ts +1 -0
  145. package/plugins/acryl-router/src/core/middleware/jwk/jwk.ts +195 -0
  146. package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +48 -0
  147. package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +802 -0
  148. package/plugins/acryl-router/src/core/middleware/jwt/index.ts +9 -0
  149. package/plugins/acryl-router/src/core/middleware/jwt/jwt.ts +189 -0
  150. package/plugins/acryl-router/src/core/middleware/language/index.test.ts +552 -0
  151. package/plugins/acryl-router/src/core/middleware/language/index.ts +12 -0
  152. package/plugins/acryl-router/src/core/middleware/language/language.ts +314 -0
  153. package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +204 -0
  154. package/plugins/acryl-router/src/core/middleware/logger/index.ts +95 -0
  155. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +292 -0
  156. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.ts +141 -0
  157. package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +221 -0
  158. package/plugins/acryl-router/src/core/middleware/method-override/index.ts +147 -0
  159. package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +37 -0
  160. package/plugins/acryl-router/src/core/middleware/powered-by/index.ts +35 -0
  161. package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +218 -0
  162. package/plugins/acryl-router/src/core/middleware/pretty-json/index.ts +67 -0
  163. package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +168 -0
  164. package/plugins/acryl-router/src/core/middleware/request-id/index.ts +7 -0
  165. package/plugins/acryl-router/src/core/middleware/request-id/request-id.ts +59 -0
  166. package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +671 -0
  167. package/plugins/acryl-router/src/core/middleware/secure-headers/index.ts +8 -0
  168. package/plugins/acryl-router/src/core/middleware/secure-headers/permissions-policy.ts +101 -0
  169. package/plugins/acryl-router/src/core/middleware/secure-headers/secure-headers.ts +340 -0
  170. package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +450 -0
  171. package/plugins/acryl-router/src/core/middleware/serve-static/index.ts +135 -0
  172. package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +50 -0
  173. package/plugins/acryl-router/src/core/middleware/serve-static/path.ts +25 -0
  174. package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +70 -0
  175. package/plugins/acryl-router/src/core/middleware/timeout/index.ts +58 -0
  176. package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +310 -0
  177. package/plugins/acryl-router/src/core/middleware/timing/index.ts +8 -0
  178. package/plugins/acryl-router/src/core/middleware/timing/timing.ts +256 -0
  179. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +381 -0
  180. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.ts +156 -0
  181. package/plugins/acryl-router/src/core/request/constants.ts +1 -0
  182. package/plugins/acryl-router/src/core/request.test.ts +751 -0
  183. package/plugins/acryl-router/src/core/request.ts +502 -0
  184. package/plugins/acryl-router/src/core/router/common.case.test.ts +1132 -0
  185. package/plugins/acryl-router/src/core/router/linear-router/index.ts +6 -0
  186. package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +80 -0
  187. package/plugins/acryl-router/src/core/router/linear-router/router.ts +152 -0
  188. package/plugins/acryl-router/src/core/router/pattern-router/index.ts +6 -0
  189. package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +46 -0
  190. package/plugins/acryl-router/src/core/router/pattern-router/router.ts +54 -0
  191. package/plugins/acryl-router/src/core/router/reg-exp-router/index.ts +7 -0
  192. package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.ts +33 -0
  193. package/plugins/acryl-router/src/core/router/reg-exp-router/node.ts +170 -0
  194. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +154 -0
  195. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.ts +165 -0
  196. package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +265 -0
  197. package/plugins/acryl-router/src/core/router/reg-exp-router/router.ts +174 -0
  198. package/plugins/acryl-router/src/core/router/reg-exp-router/trie.ts +84 -0
  199. package/plugins/acryl-router/src/core/router/smart-router/index.ts +6 -0
  200. package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +13 -0
  201. package/plugins/acryl-router/src/core/router/smart-router/router.ts +70 -0
  202. package/plugins/acryl-router/src/core/router/trie-router/index.ts +6 -0
  203. package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +896 -0
  204. package/plugins/acryl-router/src/core/router/trie-router/node.ts +221 -0
  205. package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +8 -0
  206. package/plugins/acryl-router/src/core/router/trie-router/router.ts +18 -0
  207. package/plugins/acryl-router/src/core/router/utils.ts +1 -0
  208. package/plugins/acryl-router/src/core/router.ts +103 -0
  209. package/plugins/acryl-router/src/core/types.test.ts +4134 -0
  210. package/plugins/acryl-router/src/core/types.ts +2782 -0
  211. package/plugins/acryl-router/src/core/utils/accept.test.ts +299 -0
  212. package/plugins/acryl-router/src/core/utils/accept.ts +268 -0
  213. package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +56 -0
  214. package/plugins/acryl-router/src/core/utils/basic-auth.ts +26 -0
  215. package/plugins/acryl-router/src/core/utils/body.test.ts +496 -0
  216. package/plugins/acryl-router/src/core/utils/body.ts +263 -0
  217. package/plugins/acryl-router/src/core/utils/buffer.test.ts +147 -0
  218. package/plugins/acryl-router/src/core/utils/buffer.ts +117 -0
  219. package/plugins/acryl-router/src/core/utils/color.test.ts +23 -0
  220. package/plugins/acryl-router/src/core/utils/color.ts +56 -0
  221. package/plugins/acryl-router/src/core/utils/compress.ts +10 -0
  222. package/plugins/acryl-router/src/core/utils/concurrent.test.ts +69 -0
  223. package/plugins/acryl-router/src/core/utils/concurrent.ts +55 -0
  224. package/plugins/acryl-router/src/core/utils/constants.ts +4 -0
  225. package/plugins/acryl-router/src/core/utils/cookie.test.ts +460 -0
  226. package/plugins/acryl-router/src/core/utils/cookie.ts +281 -0
  227. package/plugins/acryl-router/src/core/utils/crypto.test.ts +43 -0
  228. package/plugins/acryl-router/src/core/utils/crypto.ts +58 -0
  229. package/plugins/acryl-router/src/core/utils/encode.test.ts +61 -0
  230. package/plugins/acryl-router/src/core/utils/encode.ts +34 -0
  231. package/plugins/acryl-router/src/core/utils/filepath.test.ts +78 -0
  232. package/plugins/acryl-router/src/core/utils/filepath.ts +60 -0
  233. package/plugins/acryl-router/src/core/utils/handler.ts +15 -0
  234. package/plugins/acryl-router/src/core/utils/headers.ts +355 -0
  235. package/plugins/acryl-router/src/core/utils/html.test.ts +16 -0
  236. package/plugins/acryl-router/src/core/utils/html.ts +182 -0
  237. package/plugins/acryl-router/src/core/utils/http-status.ts +72 -0
  238. package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +149 -0
  239. package/plugins/acryl-router/src/core/utils/ipaddr.ts +374 -0
  240. package/plugins/acryl-router/src/core/utils/jwt/index.ts +7 -0
  241. package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +86 -0
  242. package/plugins/acryl-router/src/core/utils/jwt/jwa.ts +38 -0
  243. package/plugins/acryl-router/src/core/utils/jwt/jws.ts +235 -0
  244. package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +1788 -0
  245. package/plugins/acryl-router/src/core/utils/jwt/jwt.ts +292 -0
  246. package/plugins/acryl-router/src/core/utils/jwt/types.ts +157 -0
  247. package/plugins/acryl-router/src/core/utils/jwt/utf8.ts +7 -0
  248. package/plugins/acryl-router/src/core/utils/mime.test.ts +64 -0
  249. package/plugins/acryl-router/src/core/utils/mime.ts +94 -0
  250. package/plugins/acryl-router/src/core/utils/stream.test.ts +212 -0
  251. package/plugins/acryl-router/src/core/utils/stream.ts +107 -0
  252. package/plugins/acryl-router/src/core/utils/types.test.ts +295 -0
  253. package/plugins/acryl-router/src/core/utils/types.ts +123 -0
  254. package/plugins/acryl-router/src/core/utils/url.test.ts +384 -0
  255. package/plugins/acryl-router/src/core/utils/url.ts +337 -0
  256. package/plugins/acryl-router/src/core/validator/index.ts +8 -0
  257. package/plugins/acryl-router/src/core/validator/utils.test.ts +82 -0
  258. package/plugins/acryl-router/src/core/validator/utils.ts +69 -0
  259. package/plugins/acryl-router/src/core/validator/validator.test.ts +1488 -0
  260. package/plugins/acryl-router/src/core/validator/validator.ts +185 -0
  261. package/plugins/acryl-router/src/helpers/index.ts +13 -0
  262. package/plugins/acryl-router/src/helpers/schema-validator.ts +35 -0
  263. package/plugins/acryl-router/src/index.ts +6 -0
  264. package/plugins/acryl-router/src/middleware/index.ts +25 -0
  265. package/plugins/acryl-router/src/middleware/loopback-only.ts +88 -0
  266. package/plugins/acryl-router/src/service.ts +356 -0
  267. package/plugins/acryl-router/tests/docs-examples.spec.ts +152 -0
  268. package/plugins/acryl-router/tests/live-install.spec.ts +64 -0
  269. package/plugins/acryl-router/tests/loader.spec.ts +72 -0
  270. package/plugins/acryl-router/tests/service.spec.ts +409 -0
  271. package/plugins/acryl-router/tests/shadow-state.spec.ts +79 -0
  272. package/plugins/acryl-router/upstream.json +20 -0
  273. package/plugins/acryl-router-http/LICENSE +21 -0
  274. package/plugins/acryl-router-http/plugin.json +12 -0
  275. package/plugins/acryl-router-http/src/bridge.ts +54 -0
  276. package/plugins/acryl-router-http/src/index.ts +63 -0
  277. package/plugins/acryl-router-http/src/serve.ts +24 -0
  278. package/plugins/acryl-router-http/tests/adapter.spec.ts +174 -0
  279. package/plugins/acryl-router-http/tests/stream.spec.ts +133 -0
  280. package/plugins/acryl-router-http/tests/surfaces.spec.ts +101 -0
  281. package/plugins/acryl-router-http/upstream.json +22 -0
  282. package/plugins/acryl-state/LICENSE +21 -0
  283. package/plugins/acryl-state/NOTICE +30 -0
  284. package/plugins/acryl-state/plugin.json +12 -0
  285. package/plugins/acryl-state/src/cell.ts +67 -0
  286. package/plugins/acryl-state/src/index.ts +3 -0
  287. package/plugins/acryl-state/src/react.ts +11 -0
  288. package/plugins/acryl-state/src/service.ts +119 -0
  289. package/plugins/acryl-state/tests/cell.spec.ts +71 -0
  290. package/plugins/acryl-state/tests/react.spec.ts +39 -0
  291. package/plugins/acryl-state/tests/service.spec.ts +132 -0
  292. package/plugins/acryl-state/upstream.json +22 -0
  293. package/plugins/builder/script.ts +6 -10
  294. package/plugins/extensions/index.ts +20 -3
  295. package/plugins/extensions/resolve.ts +28 -3
  296. package/plugins/openai/prompt.ts +12 -6
  297. package/plugins/tasks/index.ts +14 -0
  298. package/plugins/tsconfig.json +14 -5
  299. package/templates/agent/blend.yaml +5 -7
  300. package/templates/agent/src/app.ts +8 -8
  301. package/templates/agent/src/routes.ts +39 -38
  302. package/templates/agent/template.json +3 -3
  303. package/templates/agent/test/builder.test.ts +22 -14
  304. package/templates/agent/test/model.test.ts +3 -3
  305. package/templates/agent/tsconfig.json +8 -5
  306. package/templates/{app → blank}/blend.yaml +3 -3
  307. package/templates/{app → blank}/src/app.ts +5 -5
  308. package/templates/blank/src/routes.ts +16 -0
  309. package/templates/blank/template.json +1 -0
  310. package/templates/{app → blank}/test/app.test.ts +1 -1
  311. package/templates/{app → blank}/tsconfig.json +2 -4
  312. package/templates/todo/README.md +27 -0
  313. package/templates/todo/blend.yaml +25 -0
  314. package/templates/todo/gitignore +2 -0
  315. package/templates/todo/package.json +23 -0
  316. package/templates/todo/src/app.ts +71 -0
  317. package/templates/todo/src/main.ts +19 -0
  318. package/templates/todo/src/page.ts +28 -0
  319. package/templates/todo/src/todo.ts +123 -0
  320. package/templates/todo/template.json +1 -0
  321. package/templates/todo/test/todo.test.ts +145 -0
  322. package/templates/todo/tsconfig.json +17 -0
  323. package/templates/todo/upstream.json +12 -0
  324. package/plugins/events/index.ts +0 -42
  325. package/plugins/events/plugin.json +0 -9
  326. package/plugins/http/index.ts +0 -64
  327. package/plugins/http/plugin.json +0 -10
  328. package/plugins/server/index.ts +0 -138
  329. package/plugins/server/plugin.json +0 -10
  330. package/templates/app/src/routes.ts +0 -14
  331. package/templates/app/template.json +0 -1
  332. /package/templates/{app → blank}/README.md +0 -0
  333. /package/templates/{app → blank}/gitignore +0 -0
  334. /package/templates/{app → blank}/package.json +0 -0
  335. /package/templates/{app → blank}/src/main.ts +0 -0
@@ -1,11 +1,12 @@
1
1
  import { join } from 'node:path'
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { Context, type Fiber } from '@deepseek-ai/cordis'
4
+ import * as nodeServer from './plugins/acryl-node-server/src/index.ts'
5
+ import * as router from './plugins/acryl-router/src/index.ts'
6
+ import * as state from './plugins/acryl-state/src/index.ts'
4
7
  import { agent, memory, model, tools } from './plugins/agent/index.ts'
5
8
  import { builder } from './plugins/builder/index.ts'
6
- import { events } from './plugins/events/index.ts'
7
9
  import { extensions } from './plugins/extensions/index.ts'
8
- import { http } from './plugins/http/index.ts'
9
10
  import { type AppInstance, instance } from './plugins/instance/index.ts'
10
11
  import {
11
12
  blueprintsIn,
@@ -16,7 +17,6 @@ import {
16
17
  import { type OpenAIConfig, openai } from './plugins/openai/index.ts'
17
18
  import { sandboxPlugin } from './plugins/sandbox/index.ts'
18
19
  import { save } from './plugins/save/index.ts'
19
- import { server } from './plugins/server/index.ts'
20
20
  import { tasks } from './plugins/tasks/index.ts'
21
21
  import { routes } from './routes.ts'
22
22
 
@@ -27,14 +27,14 @@ export const registry = {
27
27
  tools,
28
28
  memory,
29
29
  agent,
30
- http,
31
- events,
30
+ 'acryl-router': router,
31
+ 'acryl-state': state,
32
32
  extensions,
33
33
  builder,
34
34
  save,
35
35
  sandbox: sandboxPlugin,
36
36
  routes,
37
- server,
37
+ 'acryl-node-server': nodeServer,
38
38
  }
39
39
 
40
40
  /** The manifest next to this app's source. */
@@ -85,12 +85,12 @@ export async function createApp(
85
85
  // The `approval` parameter in blend.yaml (the extensions row) turns this source into one that asks.
86
86
  extensions: { sources: [{ dir: extensionsDir, trust: 'install' }] },
87
87
  builder: { dir: extensionsDir },
88
- server: { port: chosen.port.start, scan: chosen.port.scan, hostname: chosen.host },
88
+ server: { port: chosen.port.start, scan: chosen.port.scan ? 20 : 0, hostname: chosen.host },
89
89
  })),
90
90
  ]
91
91
  return {
92
92
  root,
93
- url: root.server.url,
93
+ url: root.nodeServer.url,
94
94
  async stop() {
95
95
  for (const fiber of [...fibers].reverse()) await fiber.dispose()
96
96
  },
@@ -1,62 +1,63 @@
1
1
  import type { Context } from '@deepseek-ai/cordis'
2
+ import { App } from './plugins/acryl-router/src/index.ts'
3
+ import { bodyLimit } from './plugins/acryl-router/src/middleware/index.ts'
2
4
  import type {} from './plugins/agent/index.ts'
3
5
  import type {} from './plugins/extensions/index.ts'
4
- import type {} from './plugins/http/index.ts'
5
6
  import type {} from './plugins/tasks/index.ts'
6
7
 
7
8
  export const APP_NAME = '__APP_NAME__'
8
9
 
9
10
  /**
10
- * The app's own routes: `POST /ask` for the agent; `GET /extensions` and `GET /tasks` to observe what it did (every run is a durable
11
- * task owning one task per tool call); `POST /tasks/cancel`. Each route lives in a `ctx.effect`.
11
+ * The app's own routes on the router: `POST /ask` for the agent; `GET /extensions` and `GET /tasks` to observe what it did (every run is a
12
+ * durable task owning one task per tool call); `POST /tasks/cancel`. What changes is also streamed: `GET /api/state/tasks/events` and
13
+ * `GET /api/state/extensions/events`. The mount is removed when the plugin is disposed.
12
14
  */
13
15
  export const routes = {
14
16
  name: 'routes',
15
- inject: ['http', 'agent', 'extensions', 'tasks'],
17
+ inject: ['router', 'agent', 'extensions', 'tasks'],
16
18
  apply(ctx: Context) {
17
- ctx.effect(() => ctx.http.route('GET', '/', () => ({ body: { app: APP_NAME, ok: true } })))
18
- ctx.effect(() => ctx.http.route('GET', '/health', () => ({ body: { ok: true } })))
19
- ctx.effect(() =>
20
- ctx.http.route('GET', '/extensions', () => ({
21
- body: {
19
+ // A body here is a name, an id or a question; anything bigger is refused before it is read.
20
+ const json = async (c: {
21
+ req: { json(): Promise<unknown> }
22
+ }): Promise<Record<string, unknown>> => {
23
+ const body = await c.req.json().catch(() => undefined)
24
+ return typeof body === 'object' && body !== null ? (body as Record<string, unknown>) : {}
25
+ }
26
+ const app = new App()
27
+ .use(bodyLimit({ maxSize: 64 * 1024 }))
28
+ .get('/', (c) => c.json({ app: APP_NAME, ok: true }))
29
+ .get('/health', (c) => c.json({ ok: true }))
30
+ .get('/extensions', (c) =>
31
+ c.json({
22
32
  installed: ctx.extensions.list(),
23
33
  notInstalled: ctx.extensions.pending(),
24
34
  waitingForApproval: ctx.extensions.approvals(),
25
- },
26
- })),
27
- )
35
+ }),
36
+ )
28
37
  // The owner's decision on an install that is waiting (the `approval` parameter in blend.yaml).
29
38
  for (const [path, decide] of [
30
39
  ['/extensions/approve', (name: string) => ctx.extensions.approve(name)],
31
40
  ['/extensions/deny', (name: string) => ctx.extensions.deny(name)],
32
41
  ] as const) {
33
- ctx.effect(() =>
34
- ctx.http.route('POST', path, async ({ body }) => {
35
- const name = (body as { name?: unknown } | undefined)?.name
36
- if (typeof name !== 'string')
37
- return { status: 400, body: { error: 'name must be a string' } }
38
- return { body: await decide(name) }
39
- }),
40
- )
42
+ app.post(path, async (c) => {
43
+ const { name } = await json(c)
44
+ if (typeof name !== 'string') return c.json({ error: 'name must be a string' }, 400)
45
+ return c.json(await decide(name))
46
+ })
41
47
  }
42
- ctx.effect(() =>
43
- ctx.http.route('GET', '/tasks', () => ({ body: ctx.tasks.list().slice(0, 100) })),
44
- )
45
- ctx.effect(() =>
46
- ctx.http.route('POST', '/tasks/cancel', ({ body }) => {
47
- const id = (body as { id?: unknown } | undefined)?.id
48
+ app
49
+ .get('/tasks', (c) => c.json(ctx.tasks.list().slice(0, 100)))
50
+ .post('/tasks/cancel', async (c) => {
51
+ const { id } = await json(c)
48
52
  if (typeof id !== 'string' || !ctx.tasks.get(id))
49
- return { status: 404, body: { error: 'no such task' } }
50
- return { body: { cancelled: ctx.tasks.cancel(id) } }
51
- }),
52
- )
53
- ctx.effect(() =>
54
- ctx.http.route('POST', '/ask', async ({ body }) => {
55
- const question = (body as { question?: unknown } | undefined)?.question
56
- if (typeof question !== 'string')
57
- return { status: 400, body: { error: 'question must be a string' } }
58
- return { body: await ctx.agent.run(question) }
59
- }),
60
- )
53
+ return c.json({ error: 'no such task' }, 404)
54
+ return c.json({ cancelled: ctx.tasks.cancel(id) })
55
+ })
56
+ .post('/ask', async (c) => {
57
+ const { question } = await json(c)
58
+ if (typeof question !== 'string') return c.json({ error: 'question must be a string' }, 400)
59
+ return c.json(await ctx.agent.run(question))
60
+ })
61
+ ctx.router.route('/', app)
61
62
  },
62
63
  }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "plugins": [
3
3
  "instance",
4
- "http",
5
- "events",
6
- "server",
4
+ "acryl-router",
5
+ "acryl-state",
6
+ "acryl-node-server",
7
7
  "tasks",
8
8
  "agent",
9
9
  "openai",
@@ -134,51 +134,59 @@ test('the agent builds a sandboxed tool, calls it, and the sandbox refuses a too
134
134
  }
135
135
  })
136
136
 
137
- test('GET /events streams what happens: the run as a task, and the install as an extension change', async () => {
137
+ test('what happens is streamed from the state cells: the run as a task, and the install as an extension', async () => {
138
138
  const project = await mkdtemp(join(tmpdir(), 'events-project-'))
139
139
  const chosen = withPort(pinnedInstance(join(project, '.acrylblends'), { root: project }), 0)
140
140
  const app = await createApp(chosen)
141
141
  const controller = new AbortController()
142
- try {
143
- const stream = await fetch(`${app.url}/events`, { signal: controller.signal })
142
+ /** Follow one server-sent stream: its text so far. */
143
+ const follow = async (path: string) => {
144
+ const stream = await fetch(`${app.url}${path}`, { signal: controller.signal })
145
+ assert.equal(stream.status, 200, path)
144
146
  const reader = stream.body!.getReader()
145
147
  const decoder = new TextDecoder()
146
- let text = ''
147
- const seen = (what: RegExp) => what.test(text)
148
+ const seen = { text: '' }
148
149
  const pump = (async () => {
149
150
  for (;;) {
150
151
  const { value, done } = await reader.read()
151
152
  if (done) return
152
- text += decoder.decode(value, { stream: true })
153
+ seen.text += decoder.decode(value, { stream: true })
153
154
  }
154
155
  })().catch(() => undefined)
156
+ return { seen, pump }
157
+ }
158
+ try {
159
+ const tasksStream = await follow('/api/state/tasks/events')
160
+ const extensionsStream = await follow('/api/state/extensions/events')
155
161
  await fetch(`${app.url}/ask`, {
156
162
  method: 'POST',
157
163
  headers: { 'content-type': 'application/json' },
158
164
  body: JSON.stringify({ question: 'create route hello /hello Hello' }),
159
165
  })
166
+ const arrived = (stream: { text: string }, what: RegExp) => what.test(stream.text)
160
167
  for (
161
168
  let waited = 0;
162
- waited < 3000 && !seen(/event: extensions\/changed\ndata: [^\n]*"installed"/);
169
+ waited < 3000 && !arrived(extensionsStream.seen, /"name":"hello"[^\n]*"state":"active"/);
163
170
  waited += 25
164
171
  ) {
165
172
  await new Promise((done) => setTimeout(done, 25))
166
173
  }
167
- assert.ok(seen(/event: connected/), 'connected')
168
174
  assert.ok(
169
- seen(/event: tasks\/changed\ndata: [^\n]*"kind":"agent-run"[^\n]*"status":"running"/),
170
- 'the run starts as a task',
175
+ arrived(tasksStream.seen, /event: value\ndata: \[\]/),
176
+ 'the stream starts with the current value',
171
177
  )
172
178
  assert.ok(
173
- seen(/event: tasks\/changed\ndata: [^\n]*"kind":"tool:install_plugin"/),
174
- 'the install is a task',
179
+ arrived(tasksStream.seen, /"kind":"agent-run"[^\n]*"status":"running"/) ||
180
+ arrived(tasksStream.seen, /"kind":"agent-run"/),
181
+ 'the run is a task',
175
182
  )
183
+ assert.ok(arrived(tasksStream.seen, /"kind":"tool:install_plugin"/), 'the install is a task')
176
184
  assert.ok(
177
- seen(/event: extensions\/changed\ndata: \{"args":\["hello","installed"\]\}/),
185
+ arrived(extensionsStream.seen, /"name":"hello"[^\n]*"state":"active"/),
178
186
  'the extension is reported',
179
187
  )
180
188
  controller.abort()
181
- await pump
189
+ await Promise.all([tasksStream.pump, extensionsStream.pump])
182
190
  } finally {
183
191
  await app.stop()
184
192
  await rm(project, { recursive: true, force: true })
@@ -10,12 +10,12 @@ import { pinnedInstance, withPort } from '../src/plugins/instance/index.ts'
10
10
 
11
11
  /** A plugin no scripted command knows: the model writes it from scratch. */
12
12
  const COUNTER = `import type { Context } from '@deepseek-ai/cordis'
13
+ import { App } from 'acryl-router'
13
14
  export const name = 'counter'
14
- export const inject = ['http']
15
+ export const inject = ['router']
15
16
  export function apply(ctx: Context) {
16
17
  let count = 0
17
- const http = ctx.get('http') as { route(m: string, p: string, h: () => { body: unknown }): () => void }
18
- ctx.effect(() => http.route('GET', '/count', () => ({ body: { count: ++count } })))
18
+ ctx.router.route('/', new App().get('/count', (c) => c.json({ count: ++count })))
19
19
  }
20
20
  `
21
21
 
@@ -1,19 +1,22 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ES2023",
4
- "lib": ["ES2023"],
3
+ "target": "ES2024",
4
+ "lib": ["ES2024", "DOM", "DOM.Iterable"],
5
5
  "module": "NodeNext",
6
6
  "moduleResolution": "NodeNext",
7
7
  "strict": true,
8
- "noUncheckedIndexedAccess": true,
9
- "exactOptionalPropertyTypes": true,
10
8
  "noImplicitOverride": true,
11
9
  "verbatimModuleSyntax": true,
12
10
  "erasableSyntaxOnly": true,
13
11
  "allowImportingTsExtensions": true,
14
12
  "noEmit": true,
15
13
  "skipLibCheck": true,
16
- "types": ["node"]
14
+ "types": ["node"],
15
+ "paths": {
16
+ "acryl-router": ["./src/plugins/acryl-router/src/index.ts"],
17
+ "acryl-router/middleware": ["./src/plugins/acryl-router/src/middleware/index.ts"],
18
+ "acryl-router/helpers": ["./src/plugins/acryl-router/src/helpers/index.ts"]
19
+ }
17
20
  },
18
21
  "include": ["src", "test", "extensions"]
19
22
  }
@@ -13,11 +13,11 @@ spec:
13
13
  # The address the server listens on.
14
14
  hostname: { type: string, default: 127.0.0.1 }
15
15
  rows:
16
- - id: http
17
- name: http
16
+ - id: router
17
+ name: acryl-router
18
18
  - id: routes
19
19
  name: routes
20
20
  - id: server
21
- name: server
21
+ name: acryl-node-server
22
22
  config:
23
23
  hostname: "{{parameters.hostname}}"
@@ -1,7 +1,8 @@
1
1
  import { join } from 'node:path'
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { Context, type Fiber } from '@deepseek-ai/cordis'
4
- import { http } from './plugins/http/index.ts'
4
+ import * as nodeServer from './plugins/acryl-node-server/src/index.ts'
5
+ import * as router from './plugins/acryl-router/src/index.ts'
5
6
  import { type AppInstance, instance } from './plugins/instance/index.ts'
6
7
  import {
7
8
  blueprintsIn,
@@ -9,11 +10,10 @@ import {
9
10
  loadManifest,
10
11
  type ParameterValue,
11
12
  } from './plugins/manifest/index.ts'
12
- import { server } from './plugins/server/index.ts'
13
13
  import { routes } from './routes.ts'
14
14
 
15
15
  /** The plugins this app's code provides. `blend.yaml` chooses, orders and configures them. */
16
- export const registry = { http, routes, server }
16
+ export const registry = { 'acryl-router': router, 'acryl-node-server': nodeServer, routes }
17
17
 
18
18
  /** The manifest next to this app's source. */
19
19
  export const MANIFEST = join(fileURLToPath(new URL('..', import.meta.url)), 'blend.yaml')
@@ -48,12 +48,12 @@ export async function createApp(
48
48
  const fibers: Fiber[] = [
49
49
  first,
50
50
  ...(await compose(root, manifest, registry, {
51
- server: { port: chosen.port.start, scan: chosen.port.scan },
51
+ server: { port: chosen.port.start, scan: chosen.port.scan ? 20 : 0 },
52
52
  })),
53
53
  ]
54
54
  return {
55
55
  root,
56
- url: root.server.url,
56
+ url: root.nodeServer.url,
57
57
  async stop() {
58
58
  for (const fiber of [...fibers].reverse()) await fiber.dispose()
59
59
  },
@@ -0,0 +1,16 @@
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ import { App } from './plugins/acryl-router/src/index.ts'
3
+
4
+ export const APP_NAME = '__APP_NAME__'
5
+
6
+ /** The app's own routes: an `App` mounted on the router. The mount is removed when this plugin is disposed. */
7
+ export const routes = {
8
+ name: 'routes',
9
+ inject: ['router'],
10
+ apply(ctx: Context) {
11
+ const app = new App()
12
+ .get('/', (c) => c.json({ app: APP_NAME, ok: true }))
13
+ .get('/health', (c) => c.json({ ok: true }))
14
+ ctx.router.route('/', app)
15
+ },
16
+ }
@@ -0,0 +1 @@
1
+ { "plugins": ["instance", "acryl-router", "acryl-node-server", "manifest", "save"] }
@@ -50,7 +50,7 @@ test('the manifest decides what is mounted: a parameter changes the server, an u
50
50
  const manifest = readManifest()
51
51
  assert.deepEqual(
52
52
  manifest.plugins.map((row) => row.id),
53
- ['http', 'routes', 'server'],
53
+ ['router', 'routes', 'server'],
54
54
  )
55
55
  assert.throws(() => readManifest({ nothing: 1 }), /parameter-override-unknown/)
56
56
  })
@@ -1,12 +1,10 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ES2023",
4
- "lib": ["ES2023"],
3
+ "target": "ES2024",
4
+ "lib": ["ES2024", "DOM", "DOM.Iterable"],
5
5
  "module": "NodeNext",
6
6
  "moduleResolution": "NodeNext",
7
7
  "strict": true,
8
- "noUncheckedIndexedAccess": true,
9
- "exactOptionalPropertyTypes": true,
10
8
  "noImplicitOverride": true,
11
9
  "verbatimModuleSyntax": true,
12
10
  "erasableSyntaxOnly": true,
@@ -0,0 +1,27 @@
1
+ # __APP_NAME__
2
+
3
+ A todo list built on the ACRYL framework: only the router and shared state. The list is a state cell, the routes are on the router, and the list is
4
+ written to a file in the app's home so it survives a restart. `src/todo.ts` is ACRYL's reference app, copied as it is; the files you own are
5
+ `blend.yaml` (what is mounted), `src/app.ts` (the composition) and the tests.
6
+
7
+ ```sh
8
+ npm install
9
+ npm test
10
+ npm run dev # prints the URL, for example http://127.0.0.1:3100
11
+ ```
12
+
13
+ ```sh
14
+ URL=http://127.0.0.1:3100 # the address `npm run dev` printed
15
+ curl -X POST $URL/todo/api -H 'content-type: application/json' -d '{"title":"write the docs"}' # {"id":1,"title":"write the docs","done":false}
16
+ curl $URL/todo/api # the list
17
+ curl -X PATCH $URL/todo/api/1 -H 'content-type: application/json' -d '{"done":true}'
18
+ curl -X DELETE $URL/todo/api/1
19
+ curl -N $URL/todo/events # the list now, then again on every change
20
+ ```
21
+
22
+ The routes only answer a caller on this machine: a request from another site's page (a foreign `Origin`, or `Origin: null`) or addressed to another host name gets `403`.
23
+
24
+ Open `$URL/todo` for the page. Stop the app and start it again: the list is still there (`<home>/todo.json`, `.acrylblends/` unless `ACRYLBLENDS_HOME` says otherwise).
25
+
26
+ - `blend.yaml` is the app as data. `npm run lock` records what it is built from; `npm run save` commits it, refusing secrets and public remotes for a private app.
27
+ - The framework plugins are in `src/plugins/acryl-*/src`. Do not edit them: they are ACRYL's, vendored; the text that explains them is in the ACRYLBLENDS docs.
@@ -0,0 +1,25 @@
1
+ # The app as data, in ACRYL's Blend format (blends.acryl.dev/v1alpha1): which plugins it mounts, in what order, with what config.
2
+ # Edit this, not app.ts, to switch a plugin off (disabled: true) or change its config. `npm run lock` records what the app is built from.
3
+ apiVersion: blends.acryl.dev/v1alpha1
4
+ kind: Blueprint
5
+ metadata:
6
+ id: acrylblends.__APP_NAME__
7
+ name: __APP_NAME__
8
+ version: 0.1.0
9
+ visibility: private # `npm run save` never pushes a private app to a public remote
10
+ spec:
11
+ runtime: cordis
12
+ parameters:
13
+ # The address the server listens on.
14
+ hostname: { type: string, default: 127.0.0.1 }
15
+ rows:
16
+ - id: router
17
+ name: acryl-router
18
+ - id: state
19
+ name: acryl-state
20
+ - id: todo
21
+ name: todo
22
+ - id: server
23
+ name: acryl-node-server
24
+ config:
25
+ hostname: "{{parameters.hostname}}"
@@ -0,0 +1,2 @@
1
+ node_modules/
2
+ .acrylblends/
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=22.19.0"
8
+ },
9
+ "scripts": {
10
+ "start": "node src/main.ts",
11
+ "dev": "node --watch src/main.ts",
12
+ "test": "node --test",
13
+ "check": "tsc --noEmit",
14
+ "lock": "node src/plugins/manifest/lock.ts",
15
+ "save": "node src/plugins/save/cli.ts",
16
+ "upgrade": "node src/plugins/manifest/upgrade-cli.ts"
17
+ },
18
+ "dependencies": {},
19
+ "devDependencies": {
20
+ "@types/node": "^24.0.0",
21
+ "typescript": "6.0.3"
22
+ }
23
+ }
@@ -0,0 +1,71 @@
1
+ import { join } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { Context, type Fiber } from '@deepseek-ai/cordis'
4
+ import * as nodeServer from './plugins/acryl-node-server/src/index.ts'
5
+ import * as router from './plugins/acryl-router/src/index.ts'
6
+ import * as state from './plugins/acryl-state/src/index.ts'
7
+ import { type AppInstance, instance } from './plugins/instance/index.ts'
8
+ import {
9
+ blueprintsIn,
10
+ compose,
11
+ loadManifest,
12
+ type ParameterValue,
13
+ } from './plugins/manifest/index.ts'
14
+ import * as todo from './todo.ts'
15
+
16
+ /** The plugins this app's code provides. `blend.yaml` chooses, orders and configures them. */
17
+ export const registry = {
18
+ 'acryl-router': router,
19
+ 'acryl-state': state,
20
+ todo,
21
+ 'acryl-node-server': nodeServer,
22
+ }
23
+
24
+ export const APP_NAME = '__APP_NAME__'
25
+
26
+ /** The manifest next to this app's source. */
27
+ export const MANIFEST = join(fileURLToPath(new URL('..', import.meta.url)), 'blend.yaml')
28
+
29
+ /**
30
+ * The app's manifest, resolved. A Blend finds its Blueprint in `blueprints/<id>.yaml`; an app that is itself a Blueprint never looks.
31
+ * `createApp` and the tests read it the same way.
32
+ */
33
+ export function readManifest(values?: Readonly<Record<string, ParameterValue>>) {
34
+ return loadManifest(MANIFEST, {
35
+ known: Object.keys(registry),
36
+ getBlueprint: blueprintsIn(join(fileURLToPath(new URL('..', import.meta.url)), 'blueprints')),
37
+ ...(values ? { values } : {}),
38
+ })
39
+ }
40
+
41
+ export interface App {
42
+ readonly root: Context
43
+ readonly url: string
44
+ stop(): Promise<void>
45
+ }
46
+
47
+ /** The app: `instance` first (where it lives, chosen at run time), then the manifest's rows in order. */
48
+ export async function createApp(
49
+ chosen: AppInstance,
50
+ options: { values?: Readonly<Record<string, ParameterValue>> } = {},
51
+ ): Promise<App> {
52
+ const manifest = readManifest(options.values)
53
+ const root = new Context()
54
+ const first = root.plugin(instance, chosen)
55
+ await first.await()
56
+ const fibers: Fiber[] = [
57
+ first,
58
+ ...(await compose(root, manifest, registry, {
59
+ // Runtime values the manifest cannot hold: where the list is kept (in the app's home) and where the server listens.
60
+ todo: { file: join(chosen.home, 'todo.json') },
61
+ server: { port: chosen.port.start, scan: chosen.port.scan ? 20 : 0 },
62
+ })),
63
+ ]
64
+ return {
65
+ root,
66
+ url: root.nodeServer.url,
67
+ async stop() {
68
+ for (const fiber of [...fibers].reverse()) await fiber.dispose()
69
+ },
70
+ }
71
+ }
@@ -0,0 +1,19 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ import { APP_NAME, createApp } from './app.ts'
3
+ import { selectInstance } from './plugins/instance/index.ts'
4
+
5
+ // The composition root: the one place that reads the environment (through selectInstance) and chooses where the app lives.
6
+ const chosen = selectInstance({
7
+ projectRoot: fileURLToPath(new URL('..', import.meta.url)),
8
+ env: process.env,
9
+ })
10
+ const app = await createApp(chosen)
11
+ console.log(`${APP_NAME} listening on ${app.url} (data in ${chosen.home})`)
12
+
13
+ const stop = async () => {
14
+ await app.stop()
15
+ console.log('stopped')
16
+ process.exit(0)
17
+ }
18
+ process.once('SIGINT', stop)
19
+ process.once('SIGTERM', stop)
@@ -0,0 +1,28 @@
1
+ /** The page: plain HTML and a few lines of script; it follows the list through the server-sent event stream. */
2
+ export const PAGE = `<!doctype html>
3
+ <meta charset="utf-8"><title>Todo</title>
4
+ <style>body{font:16px system-ui;max-width:32rem;margin:2rem auto}ul{list-style:none;padding:0}li{display:flex;gap:.5rem;padding:.25rem 0}.done span{text-decoration:line-through;opacity:.6}</style>
5
+ <h1>Todo</h1>
6
+ <form id="add"><input id="title" placeholder="What needs doing?" autofocus> <button>Add</button></form>
7
+ <ul id="list"></ul>
8
+ <script>
9
+ const list = document.getElementById('list')
10
+ const send = (url, method, body) => fetch(url, { method, headers: { 'content-type': 'application/json' }, body: body && JSON.stringify(body) })
11
+ document.getElementById('add').onsubmit = async event => {
12
+ event.preventDefault()
13
+ const input = document.getElementById('title')
14
+ await send('/todo/api', 'POST', { title: input.value })
15
+ input.value = ''
16
+ }
17
+ new EventSource('/todo/events').onmessage = event => {
18
+ list.replaceChildren(...JSON.parse(event.data).map(todo => {
19
+ const item = document.createElement('li')
20
+ item.className = todo.done ? 'done' : ''
21
+ const box = Object.assign(document.createElement('input'), { type: 'checkbox', checked: todo.done, onchange: () => send('/todo/api/' + todo.id, 'PATCH', { done: box.checked }) })
22
+ const title = document.createElement('span'); title.textContent = todo.title
23
+ const remove = Object.assign(document.createElement('button'), { textContent: 'x', onclick: () => send('/todo/api/' + todo.id, 'DELETE') })
24
+ item.append(box, title, remove)
25
+ return item
26
+ }))
27
+ }
28
+ </script>`