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.
- package/dist/index.d.ts +22 -16
- package/dist/index.js +65 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/plugins/acryl-inspect/LICENSE +21 -0
- package/plugins/acryl-inspect/plugin.json +12 -0
- package/plugins/acryl-inspect/src/index.ts +3 -0
- package/plugins/acryl-inspect/src/report.ts +126 -0
- package/plugins/acryl-inspect/src/service.ts +68 -0
- package/plugins/acryl-inspect/tests/report.spec.ts +63 -0
- package/plugins/acryl-inspect/tests/service.spec.ts +121 -0
- package/plugins/acryl-inspect/upstream.json +18 -0
- package/plugins/acryl-node-server/LICENSE +47 -0
- package/plugins/acryl-node-server/plugin.json +13 -0
- package/plugins/acryl-node-server/src/adapter/conninfo.ts +23 -0
- package/plugins/acryl-node-server/src/adapter/early-hints.ts +55 -0
- package/plugins/acryl-node-server/src/adapter/error.ts +11 -0
- package/plugins/acryl-node-server/src/adapter/headers.ts +249 -0
- package/plugins/acryl-node-server/src/adapter/index.ts +6 -0
- package/plugins/acryl-node-server/src/adapter/listener.ts +540 -0
- package/plugins/acryl-node-server/src/adapter/request.ts +797 -0
- package/plugins/acryl-node-server/src/adapter/response.ts +186 -0
- package/plugins/acryl-node-server/src/adapter/serve-static.ts +238 -0
- package/plugins/acryl-node-server/src/adapter/server.ts +36 -0
- package/plugins/acryl-node-server/src/adapter/types.ts +82 -0
- package/plugins/acryl-node-server/src/adapter/url.ts +42 -0
- package/plugins/acryl-node-server/src/adapter/utils/response/constants.ts +1 -0
- package/plugins/acryl-node-server/src/adapter/utils/response.ts +4 -0
- package/plugins/acryl-node-server/src/adapter/utils/stream.ts +88 -0
- package/plugins/acryl-node-server/src/adapter/utils.ts +96 -0
- package/plugins/acryl-node-server/src/adapter/websocket-types.ts +45 -0
- package/plugins/acryl-node-server/src/adapter/websocket.ts +375 -0
- package/plugins/acryl-node-server/src/index.ts +107 -0
- package/plugins/acryl-node-server/tests/adapter/app.ts +76 -0
- package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +5 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +1 -0
- package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +34 -0
- package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +248 -0
- package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +457 -0
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +23 -0
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +27 -0
- package/plugins/acryl-node-server/tests/adapter/headers.test.ts +252 -0
- package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +190 -0
- package/plugins/acryl-node-server/tests/adapter/listener.test.ts +727 -0
- package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +189 -0
- package/plugins/acryl-node-server/tests/adapter/request.test.ts +1230 -0
- package/plugins/acryl-node-server/tests/adapter/response.test.ts +285 -0
- package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +849 -0
- package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +790 -0
- package/plugins/acryl-node-server/tests/adapter/server.test.ts +1302 -0
- package/plugins/acryl-node-server/tests/adapter/setup.ts +16 -0
- package/plugins/acryl-node-server/tests/adapter/url.test.ts +76 -0
- package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +23 -0
- package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +75 -0
- package/plugins/acryl-node-server/tests/adapter/utils.test.ts +235 -0
- package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +260 -0
- package/plugins/acryl-node-server/tests/server.spec.ts +141 -0
- package/plugins/acryl-node-server/upstream.json +18 -0
- package/plugins/acryl-router/LICENSE +47 -0
- package/plugins/acryl-router/plugin.json +13 -0
- package/plugins/acryl-router/src/client.ts +2 -0
- package/plugins/acryl-router/src/connection.ts +42 -0
- package/plugins/acryl-router/src/core/app-base.ts +544 -0
- package/plugins/acryl-router/src/core/app.test.ts +3833 -0
- package/plugins/acryl-router/src/core/app.ts +34 -0
- package/plugins/acryl-router/src/core/client/client.test.ts +2313 -0
- package/plugins/acryl-router/src/core/client/client.ts +249 -0
- package/plugins/acryl-router/src/core/client/fetch-result-please.ts +95 -0
- package/plugins/acryl-router/src/core/client/index.ts +17 -0
- package/plugins/acryl-router/src/core/client/types.test.ts +149 -0
- package/plugins/acryl-router/src/core/client/types.ts +393 -0
- package/plugins/acryl-router/src/core/client/utils.test.ts +405 -0
- package/plugins/acryl-router/src/core/client/utils.ts +117 -0
- package/plugins/acryl-router/src/core/compose.test.ts +751 -0
- package/plugins/acryl-router/src/core/compose.ts +73 -0
- package/plugins/acryl-router/src/core/context.test.ts +629 -0
- package/plugins/acryl-router/src/core/context.ts +781 -0
- package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +344 -0
- package/plugins/acryl-router/src/core/helper/accepts/accepts.ts +96 -0
- package/plugins/acryl-router/src/core/helper/accepts/index.ts +6 -0
- package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +55 -0
- package/plugins/acryl-router/src/core/helper/adapter/index.ts +90 -0
- package/plugins/acryl-router/src/core/helper/conninfo/index.ts +6 -0
- package/plugins/acryl-router/src/core/helper/conninfo/types.ts +45 -0
- package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +510 -0
- package/plugins/acryl-router/src/core/helper/cookie/index.ts +146 -0
- package/plugins/acryl-router/src/core/helper/factory/index.test.ts +434 -0
- package/plugins/acryl-router/src/core/helper/factory/index.ts +375 -0
- package/plugins/acryl-router/src/core/helper/html/index.test.ts +78 -0
- package/plugins/acryl-router/src/core/helper/html/index.ts +57 -0
- package/plugins/acryl-router/src/core/helper/route/index.test.ts +259 -0
- package/plugins/acryl-router/src/core/helper/route/index.ts +144 -0
- package/plugins/acryl-router/src/core/helper/streaming/index.ts +9 -0
- package/plugins/acryl-router/src/core/helper/streaming/sse.ts +101 -0
- package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +128 -0
- package/plugins/acryl-router/src/core/helper/streaming/stream.ts +45 -0
- package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +34 -0
- package/plugins/acryl-router/src/core/helper/streaming/text.ts +15 -0
- package/plugins/acryl-router/src/core/helper/streaming/utils.ts +11 -0
- package/plugins/acryl-router/src/core/helper/testing/index.test.ts +42 -0
- package/plugins/acryl-router/src/core/helper/testing/index.ts +28 -0
- package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +124 -0
- package/plugins/acryl-router/src/core/helper/websocket/index.ts +140 -0
- package/plugins/acryl-router/src/core/http-exception.test.ts +45 -0
- package/plugins/acryl-router/src/core/http-exception.ts +77 -0
- package/plugins/acryl-router/src/core/index.ts +53 -0
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +434 -0
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.ts +152 -0
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +1018 -0
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.ts +220 -0
- package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +273 -0
- package/plugins/acryl-router/src/core/middleware/body-limit/index.ts +110 -0
- package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +355 -0
- package/plugins/acryl-router/src/core/middleware/combine/index.ts +171 -0
- package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +535 -0
- package/plugins/acryl-router/src/core/middleware/compress/index.ts +142 -0
- package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +43 -0
- package/plugins/acryl-router/src/core/middleware/context-storage/index.ts +58 -0
- package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +532 -0
- package/plugins/acryl-router/src/core/middleware/cors/index.ts +163 -0
- package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +534 -0
- package/plugins/acryl-router/src/core/middleware/csrf/index.ts +150 -0
- package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +21 -0
- package/plugins/acryl-router/src/core/middleware/etag/digest.ts +95 -0
- package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +503 -0
- package/plugins/acryl-router/src/core/middleware/etag/index.ts +135 -0
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +217 -0
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.ts +278 -0
- package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +1135 -0
- package/plugins/acryl-router/src/core/middleware/jwk/index.ts +1 -0
- package/plugins/acryl-router/src/core/middleware/jwk/jwk.ts +195 -0
- package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +48 -0
- package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +802 -0
- package/plugins/acryl-router/src/core/middleware/jwt/index.ts +9 -0
- package/plugins/acryl-router/src/core/middleware/jwt/jwt.ts +189 -0
- package/plugins/acryl-router/src/core/middleware/language/index.test.ts +552 -0
- package/plugins/acryl-router/src/core/middleware/language/index.ts +12 -0
- package/plugins/acryl-router/src/core/middleware/language/language.ts +314 -0
- package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +204 -0
- package/plugins/acryl-router/src/core/middleware/logger/index.ts +95 -0
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +292 -0
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.ts +141 -0
- package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +221 -0
- package/plugins/acryl-router/src/core/middleware/method-override/index.ts +147 -0
- package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +37 -0
- package/plugins/acryl-router/src/core/middleware/powered-by/index.ts +35 -0
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +218 -0
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.ts +67 -0
- package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +168 -0
- package/plugins/acryl-router/src/core/middleware/request-id/index.ts +7 -0
- package/plugins/acryl-router/src/core/middleware/request-id/request-id.ts +59 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +671 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.ts +8 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/permissions-policy.ts +101 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/secure-headers.ts +340 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +450 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/index.ts +135 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +50 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/path.ts +25 -0
- package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +70 -0
- package/plugins/acryl-router/src/core/middleware/timeout/index.ts +58 -0
- package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +310 -0
- package/plugins/acryl-router/src/core/middleware/timing/index.ts +8 -0
- package/plugins/acryl-router/src/core/middleware/timing/timing.ts +256 -0
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +381 -0
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.ts +156 -0
- package/plugins/acryl-router/src/core/request/constants.ts +1 -0
- package/plugins/acryl-router/src/core/request.test.ts +751 -0
- package/plugins/acryl-router/src/core/request.ts +502 -0
- package/plugins/acryl-router/src/core/router/common.case.test.ts +1132 -0
- package/plugins/acryl-router/src/core/router/linear-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +80 -0
- package/plugins/acryl-router/src/core/router/linear-router/router.ts +152 -0
- package/plugins/acryl-router/src/core/router/pattern-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +46 -0
- package/plugins/acryl-router/src/core/router/pattern-router/router.ts +54 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/index.ts +7 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.ts +33 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/node.ts +170 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +154 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.ts +165 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +265 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.ts +174 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/trie.ts +84 -0
- package/plugins/acryl-router/src/core/router/smart-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +13 -0
- package/plugins/acryl-router/src/core/router/smart-router/router.ts +70 -0
- package/plugins/acryl-router/src/core/router/trie-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +896 -0
- package/plugins/acryl-router/src/core/router/trie-router/node.ts +221 -0
- package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +8 -0
- package/plugins/acryl-router/src/core/router/trie-router/router.ts +18 -0
- package/plugins/acryl-router/src/core/router/utils.ts +1 -0
- package/plugins/acryl-router/src/core/router.ts +103 -0
- package/plugins/acryl-router/src/core/types.test.ts +4134 -0
- package/plugins/acryl-router/src/core/types.ts +2782 -0
- package/plugins/acryl-router/src/core/utils/accept.test.ts +299 -0
- package/plugins/acryl-router/src/core/utils/accept.ts +268 -0
- package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +56 -0
- package/plugins/acryl-router/src/core/utils/basic-auth.ts +26 -0
- package/plugins/acryl-router/src/core/utils/body.test.ts +496 -0
- package/plugins/acryl-router/src/core/utils/body.ts +263 -0
- package/plugins/acryl-router/src/core/utils/buffer.test.ts +147 -0
- package/plugins/acryl-router/src/core/utils/buffer.ts +117 -0
- package/plugins/acryl-router/src/core/utils/color.test.ts +23 -0
- package/plugins/acryl-router/src/core/utils/color.ts +56 -0
- package/plugins/acryl-router/src/core/utils/compress.ts +10 -0
- package/plugins/acryl-router/src/core/utils/concurrent.test.ts +69 -0
- package/plugins/acryl-router/src/core/utils/concurrent.ts +55 -0
- package/plugins/acryl-router/src/core/utils/constants.ts +4 -0
- package/plugins/acryl-router/src/core/utils/cookie.test.ts +460 -0
- package/plugins/acryl-router/src/core/utils/cookie.ts +281 -0
- package/plugins/acryl-router/src/core/utils/crypto.test.ts +43 -0
- package/plugins/acryl-router/src/core/utils/crypto.ts +58 -0
- package/plugins/acryl-router/src/core/utils/encode.test.ts +61 -0
- package/plugins/acryl-router/src/core/utils/encode.ts +34 -0
- package/plugins/acryl-router/src/core/utils/filepath.test.ts +78 -0
- package/plugins/acryl-router/src/core/utils/filepath.ts +60 -0
- package/plugins/acryl-router/src/core/utils/handler.ts +15 -0
- package/plugins/acryl-router/src/core/utils/headers.ts +355 -0
- package/plugins/acryl-router/src/core/utils/html.test.ts +16 -0
- package/plugins/acryl-router/src/core/utils/html.ts +182 -0
- package/plugins/acryl-router/src/core/utils/http-status.ts +72 -0
- package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +149 -0
- package/plugins/acryl-router/src/core/utils/ipaddr.ts +374 -0
- package/plugins/acryl-router/src/core/utils/jwt/index.ts +7 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +86 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwa.ts +38 -0
- package/plugins/acryl-router/src/core/utils/jwt/jws.ts +235 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +1788 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwt.ts +292 -0
- package/plugins/acryl-router/src/core/utils/jwt/types.ts +157 -0
- package/plugins/acryl-router/src/core/utils/jwt/utf8.ts +7 -0
- package/plugins/acryl-router/src/core/utils/mime.test.ts +64 -0
- package/plugins/acryl-router/src/core/utils/mime.ts +94 -0
- package/plugins/acryl-router/src/core/utils/stream.test.ts +212 -0
- package/plugins/acryl-router/src/core/utils/stream.ts +107 -0
- package/plugins/acryl-router/src/core/utils/types.test.ts +295 -0
- package/plugins/acryl-router/src/core/utils/types.ts +123 -0
- package/plugins/acryl-router/src/core/utils/url.test.ts +384 -0
- package/plugins/acryl-router/src/core/utils/url.ts +337 -0
- package/plugins/acryl-router/src/core/validator/index.ts +8 -0
- package/plugins/acryl-router/src/core/validator/utils.test.ts +82 -0
- package/plugins/acryl-router/src/core/validator/utils.ts +69 -0
- package/plugins/acryl-router/src/core/validator/validator.test.ts +1488 -0
- package/plugins/acryl-router/src/core/validator/validator.ts +185 -0
- package/plugins/acryl-router/src/helpers/index.ts +13 -0
- package/plugins/acryl-router/src/helpers/schema-validator.ts +35 -0
- package/plugins/acryl-router/src/index.ts +6 -0
- package/plugins/acryl-router/src/middleware/index.ts +25 -0
- package/plugins/acryl-router/src/middleware/loopback-only.ts +88 -0
- package/plugins/acryl-router/src/service.ts +356 -0
- package/plugins/acryl-router/tests/docs-examples.spec.ts +152 -0
- package/plugins/acryl-router/tests/live-install.spec.ts +64 -0
- package/plugins/acryl-router/tests/loader.spec.ts +72 -0
- package/plugins/acryl-router/tests/service.spec.ts +409 -0
- package/plugins/acryl-router/tests/shadow-state.spec.ts +79 -0
- package/plugins/acryl-router/upstream.json +20 -0
- package/plugins/acryl-router-http/LICENSE +21 -0
- package/plugins/acryl-router-http/plugin.json +12 -0
- package/plugins/acryl-router-http/src/bridge.ts +54 -0
- package/plugins/acryl-router-http/src/index.ts +63 -0
- package/plugins/acryl-router-http/src/serve.ts +24 -0
- package/plugins/acryl-router-http/tests/adapter.spec.ts +174 -0
- package/plugins/acryl-router-http/tests/stream.spec.ts +133 -0
- package/plugins/acryl-router-http/tests/surfaces.spec.ts +101 -0
- package/plugins/acryl-router-http/upstream.json +22 -0
- package/plugins/acryl-state/LICENSE +21 -0
- package/plugins/acryl-state/NOTICE +30 -0
- package/plugins/acryl-state/plugin.json +12 -0
- package/plugins/acryl-state/src/cell.ts +67 -0
- package/plugins/acryl-state/src/index.ts +3 -0
- package/plugins/acryl-state/src/react.ts +11 -0
- package/plugins/acryl-state/src/service.ts +119 -0
- package/plugins/acryl-state/tests/cell.spec.ts +71 -0
- package/plugins/acryl-state/tests/react.spec.ts +39 -0
- package/plugins/acryl-state/tests/service.spec.ts +132 -0
- package/plugins/acryl-state/upstream.json +22 -0
- package/plugins/builder/script.ts +6 -10
- package/plugins/extensions/index.ts +20 -3
- package/plugins/extensions/resolve.ts +28 -3
- package/plugins/openai/prompt.ts +12 -6
- package/plugins/tasks/index.ts +14 -0
- package/plugins/tsconfig.json +14 -5
- package/templates/agent/blend.yaml +5 -7
- package/templates/agent/src/app.ts +8 -8
- package/templates/agent/src/routes.ts +39 -38
- package/templates/agent/template.json +3 -3
- package/templates/agent/test/builder.test.ts +22 -14
- package/templates/agent/test/model.test.ts +3 -3
- package/templates/agent/tsconfig.json +8 -5
- package/templates/{app → blank}/blend.yaml +3 -3
- package/templates/{app → blank}/src/app.ts +5 -5
- package/templates/blank/src/routes.ts +16 -0
- package/templates/blank/template.json +1 -0
- package/templates/{app → blank}/test/app.test.ts +1 -1
- package/templates/{app → blank}/tsconfig.json +2 -4
- package/templates/todo/README.md +27 -0
- package/templates/todo/blend.yaml +25 -0
- package/templates/todo/gitignore +2 -0
- package/templates/todo/package.json +23 -0
- package/templates/todo/src/app.ts +71 -0
- package/templates/todo/src/main.ts +19 -0
- package/templates/todo/src/page.ts +28 -0
- package/templates/todo/src/todo.ts +123 -0
- package/templates/todo/template.json +1 -0
- package/templates/todo/test/todo.test.ts +145 -0
- package/templates/todo/tsconfig.json +17 -0
- package/templates/todo/upstream.json +12 -0
- package/plugins/events/index.ts +0 -42
- package/plugins/events/plugin.json +0 -9
- package/plugins/http/index.ts +0 -64
- package/plugins/http/plugin.json +0 -10
- package/plugins/server/index.ts +0 -138
- package/plugins/server/plugin.json +0 -10
- package/templates/app/src/routes.ts +0 -14
- package/templates/app/template.json +0 -1
- /package/templates/{app → blank}/README.md +0 -0
- /package/templates/{app → blank}/gitignore +0 -0
- /package/templates/{app → blank}/package.json +0 -0
- /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
|
-
|
|
31
|
-
|
|
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.
|
|
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
|
|
11
|
-
* task owning one task per tool call); `POST /tasks/cancel`.
|
|
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: ['
|
|
17
|
+
inject: ['router', 'agent', 'extensions', 'tasks'],
|
|
16
18
|
apply(ctx: Context) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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 {
|
|
50
|
-
return {
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
}
|
|
@@ -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('
|
|
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
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
170
|
-
'the
|
|
175
|
+
arrived(tasksStream.seen, /event: value\ndata: \[\]/),
|
|
176
|
+
'the stream starts with the current value',
|
|
171
177
|
)
|
|
172
178
|
assert.ok(
|
|
173
|
-
seen
|
|
174
|
-
|
|
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
|
|
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 = ['
|
|
15
|
+
export const inject = ['router']
|
|
15
16
|
export function apply(ctx: Context) {
|
|
16
17
|
let count = 0
|
|
17
|
-
|
|
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": "
|
|
4
|
-
"lib": ["
|
|
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:
|
|
17
|
-
name:
|
|
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
|
|
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 = {
|
|
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.
|
|
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
|
-
['
|
|
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": "
|
|
4
|
-
"lib": ["
|
|
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,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>`
|