acrylblends 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6 -1
- package/dist/index.js +84 -5
- package/dist/index.js.map +1 -1
- package/docs/framework/LICENSE +23 -0
- package/docs/framework/README.md +63 -0
- package/docs/framework/UPSTREAM.md +3 -0
- package/docs/framework/api-summary.md +138 -0
- package/docs/framework/concepts.md +105 -0
- package/docs/framework/context.md +129 -0
- package/docs/framework/getting-started.md +146 -0
- package/docs/framework/helpers.md +118 -0
- package/docs/framework/middleware.md +154 -0
- package/docs/framework/migration-from-0-3.md +108 -0
- package/docs/framework/migration-from-dsh-webserver.md +103 -0
- package/docs/framework/node-server.md +75 -0
- package/docs/framework/plugin-lifecycle.md +111 -0
- package/docs/framework/request.md +106 -0
- package/docs/framework/routing.md +188 -0
- package/docs/framework/surfaces.md +71 -0
- package/docs/framework/upstream.json +9 -0
- package/docs/framework/validation.md +135 -0
- package/package.json +4 -2
- package/plugins/acryl-inspect/plugin.json +2 -1
- package/plugins/acryl-inspect/src/report.ts +21 -13
- package/plugins/acryl-inspect/src/service.ts +9 -7
- package/plugins/acryl-inspect/tests/report.spec.ts +13 -8
- package/plugins/acryl-inspect/tests/service.spec.ts +11 -10
- package/plugins/acryl-inspect/upstream.json +2 -2
- package/plugins/acryl-node-server/LICENSE +26 -0
- package/plugins/acryl-node-server/plugin.json +2 -1
- 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 +19 -6
- 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 +44 -8
- package/plugins/acryl-node-server/upstream.json +3 -3
- package/plugins/acryl-router/LICENSE +26 -0
- package/plugins/acryl-router/plugin.json +2 -1
- 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 -3
- package/plugins/acryl-router/src/helpers/schema-validator.ts +35 -0
- package/plugins/acryl-router/src/index.ts +6 -6
- package/plugins/acryl-router/src/middleware/index.ts +25 -6
- package/plugins/acryl-router/src/middleware/loopback-only.ts +53 -14
- package/plugins/acryl-router/src/service.ts +262 -64
- package/plugins/acryl-router/tests/docs-examples.spec.ts +152 -0
- package/plugins/acryl-router/tests/live-install.spec.ts +2 -2
- package/plugins/acryl-router/tests/loader.spec.ts +3 -2
- package/plugins/acryl-router/tests/loopback-only.spec.ts +80 -0
- package/plugins/acryl-router/tests/service.spec.ts +281 -66
- package/plugins/acryl-router/tests/shadow-state.spec.ts +79 -0
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/plugin.json +2 -1
- package/plugins/acryl-router-http/src/bridge.ts +3 -1
- package/plugins/acryl-router-http/tests/adapter.spec.ts +16 -12
- package/plugins/acryl-router-http/tests/surfaces.spec.ts +12 -10
- package/plugins/acryl-router-http/upstream.json +3 -3
- package/plugins/acryl-state/NOTICE +30 -0
- package/plugins/acryl-state/plugin.json +1 -1
- package/plugins/acryl-state/src/cell.ts +1 -1
- package/plugins/acryl-state/src/service.ts +33 -15
- package/plugins/acryl-state/upstream.json +6 -4
- package/plugins/builder/script.ts +4 -7
- package/plugins/extensions/index.ts +6 -5
- package/plugins/extensions/resolve.ts +28 -3
- package/plugins/openai/prompt.ts +9 -7
- package/plugins/tsconfig.json +5 -4
- package/shared/AGENTS.core.md +54 -0
- package/templates/agent/AGENTS.md +6 -0
- package/templates/agent/README.md +2 -2
- package/templates/agent/blend.yaml +1 -1
- package/templates/agent/src/routes.ts +37 -27
- package/templates/agent/test/model.test.ts +2 -1
- package/templates/agent/tsconfig.json +8 -5
- package/templates/blank/AGENTS.md +5 -0
- package/templates/blank/README.md +1 -1
- package/templates/blank/src/routes.ts +6 -4
- package/templates/blank/tsconfig.json +2 -4
- package/templates/todo/AGENTS.md +5 -0
- package/templates/todo/src/page.ts +1 -1
- package/templates/todo/src/todo.ts +58 -29
- package/templates/todo/tsconfig.json +2 -4
- package/templates/todo/upstream.json +2 -2
- package/plugins/acryl-router/src/compose.ts +0 -21
- package/plugins/acryl-router/src/context.ts +0 -59
- package/plugins/acryl-router/src/helpers/read-body.ts +0 -59
- package/plugins/acryl-router/src/helpers/sse.ts +0 -83
- package/plugins/acryl-router/src/helpers/validate.ts +0 -44
- package/plugins/acryl-router/src/http-error.ts +0 -21
- package/plugins/acryl-router/src/middleware/body-limit.ts +0 -25
- package/plugins/acryl-router/src/middleware/cors.ts +0 -46
- package/plugins/acryl-router/src/middleware/logger.ts +0 -21
- package/plugins/acryl-router/src/middleware/request-id.ts +0 -24
- package/plugins/acryl-router/src/middleware/secure-headers.ts +0 -23
- package/plugins/acryl-router/src/pattern.ts +0 -89
- package/plugins/acryl-router/src/registrar.ts +0 -90
- package/plugins/acryl-router/src/table.ts +0 -156
- package/plugins/acryl-router/src/types.ts +0 -73
- package/plugins/acryl-router/src/util.ts +0 -23
- package/plugins/acryl-router/tests/api.spec.ts +0 -223
- package/plugins/acryl-router/tests/conformance.ts +0 -170
- package/plugins/acryl-router/tests/helpers.spec.ts +0 -172
- package/plugins/acryl-router/tests/middleware.spec.ts +0 -183
- package/plugins/acryl-router/tests/table.spec.ts +0 -47
|
@@ -8,7 +8,8 @@ import { Context, type FiberState } from '@deepseek-ai/cordis'
|
|
|
8
8
|
import WebServer from '@deepseek-ai/dsh-host-webserver'
|
|
9
9
|
import { apply as routerApply, name as routerName } from '../../acryl-router/src/index.ts'
|
|
10
10
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
11
|
-
import type
|
|
11
|
+
import { App, connectionOf, type RouterHttpService } from '../../acryl-router/src/index.ts'
|
|
12
|
+
import { methodNotAllowed } from '../../acryl-router/src/middleware/index.ts'
|
|
12
13
|
import { apply, inject, name } from '../src/index.ts'
|
|
13
14
|
|
|
14
15
|
const PENDING = 0 as FiberState.PENDING
|
|
@@ -39,25 +40,28 @@ async function stack(): Promise<{ ctx: Context, base: () => string }> {
|
|
|
39
40
|
return { ctx, base: () => `http://127.0.0.1:${String((ctx as unknown as { webServer: { port: number } }).webServer.port)}` }
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
function demo(prefix: string, extra?: (
|
|
43
|
+
function demo(prefix: string, extra?: (app: App) => void) {
|
|
43
44
|
return {
|
|
44
45
|
name: `demo${prefix.replaceAll('/', '-')}`,
|
|
45
46
|
inject: ['router', 'routerHttp'],
|
|
46
47
|
apply(ctx: Context): void {
|
|
47
48
|
ctx.routerHttp.mount(prefix)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
const app = new App()
|
|
50
|
+
app.use(methodNotAllowed({ app }))
|
|
51
|
+
app.get('/hello/:who', c => c.json({ hello: c.req.param('who'), q: c.req.query('q') ?? null, host: c.req.header('host') !== undefined }))
|
|
52
|
+
app.post('/echo', async c => c.json({ got: await c.req.text(), type: c.req.header('content-type') ?? null }, 201))
|
|
53
|
+
app.get('/cookies', () => {
|
|
51
54
|
const headers = new Headers()
|
|
52
55
|
headers.append('set-cookie', 'a=1; Path=/')
|
|
53
56
|
headers.append('set-cookie', 'b=2; Path=/')
|
|
54
57
|
return new Response('ok', { headers })
|
|
55
58
|
})
|
|
56
|
-
|
|
59
|
+
app.get('/stream', () => new Response(new ReadableStream({
|
|
57
60
|
start(controller) { controller.enqueue(new TextEncoder().encode('one,')); controller.enqueue(new TextEncoder().encode('two')); controller.close() },
|
|
58
61
|
})))
|
|
59
|
-
|
|
60
|
-
extra?.(
|
|
62
|
+
app.get('/peer', c => c.json({ remote: connectionOf(c).remoteAddress ?? null, localPort: connectionOf(c).localPort ?? null }))
|
|
63
|
+
extra?.(app)
|
|
64
|
+
ctx.router.route(prefix, app)
|
|
61
65
|
},
|
|
62
66
|
}
|
|
63
67
|
}
|
|
@@ -106,7 +110,7 @@ describe('acryl-router-http against a real webServer', () => {
|
|
|
106
110
|
await settle(20)
|
|
107
111
|
const missing = await fetch(`${base()}/api/demo/none`)
|
|
108
112
|
expect(missing.status).toBe(404)
|
|
109
|
-
expect(await missing.text()).toBe('Not Found')
|
|
113
|
+
expect(await missing.text()).toBe('404 Not Found')
|
|
110
114
|
expect((await fetch(`${base()}/api/demo/echo`)).status).toBe(405)
|
|
111
115
|
const head = await fetch(`${base()}/api/demo/hello/a`, { method: 'HEAD' })
|
|
112
116
|
expect(head.status).toBe(200)
|
|
@@ -117,9 +121,9 @@ describe('acryl-router-http against a real webServer', () => {
|
|
|
117
121
|
it('aborts the handler signal when the HTTP client disconnects', async () => {
|
|
118
122
|
const { ctx, base } = await stack()
|
|
119
123
|
let aborted = false
|
|
120
|
-
ctx.plugin(demo('/api/demo',
|
|
121
|
-
|
|
122
|
-
await new Promise<void>((_resolve, reject) => { r.signal.addEventListener('abort', () => { aborted = true; reject(new Error('aborted')) }, { once: true }) })
|
|
124
|
+
ctx.plugin(demo('/api/demo', app => {
|
|
125
|
+
app.get('/slow', async r => {
|
|
126
|
+
await new Promise<void>((_resolve, reject) => { r.req.raw.signal.addEventListener('abort', () => { aborted = true; reject(new Error('aborted')) }, { once: true }) })
|
|
123
127
|
return r.text('never')
|
|
124
128
|
})
|
|
125
129
|
}))
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { Context } from '@deepseek-ai/cordis'
|
|
9
9
|
import WebServer from '@deepseek-ai/dsh-host-webserver'
|
|
10
|
-
import { apply as routerApply, name as routerName } from '../../acryl-router/src/index.ts'
|
|
10
|
+
import { App, apply as routerApply, name as routerName } from '../../acryl-router/src/index.ts'
|
|
11
11
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
12
12
|
import { apply as httpApply, inject as httpInject, name as httpName } from '../src/index.ts'
|
|
13
13
|
|
|
@@ -28,15 +28,17 @@ const notes = {
|
|
|
28
28
|
inject: ['router'],
|
|
29
29
|
apply(ctx: Context): void {
|
|
30
30
|
const store = new Map<string, string>()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
const app = new App()
|
|
32
|
+
.post('/:id', async c => {
|
|
33
|
+
const text = await c.req.text()
|
|
34
|
+
store.set(c.req.param('id'), text)
|
|
35
|
+
return c.json({ id: c.req.param('id'), saved: text.length }, 201)
|
|
36
|
+
})
|
|
37
|
+
.get('/:id', c => {
|
|
38
|
+
const text = store.get(c.req.param('id'))
|
|
39
|
+
return text === undefined ? c.json({ error: 'not found' }, 404) : c.json({ id: c.req.param('id'), text })
|
|
40
|
+
})
|
|
41
|
+
ctx.router.route('/api/notes', app)
|
|
40
42
|
ctx.inject(['routerHttp'], inner => { inner.routerHttp.mount('/api/notes') })
|
|
41
43
|
},
|
|
42
44
|
}
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
"source": {
|
|
3
3
|
"repo": "acryldev/acryl",
|
|
4
4
|
"path": "plugins/acryl-router-http",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "64e7c9d2e787f74b2da82492644fbbb46a601074"
|
|
6
6
|
},
|
|
7
7
|
"vendored": [
|
|
8
8
|
"src",
|
|
9
9
|
"tests",
|
|
10
10
|
"LICENSE"
|
|
11
11
|
],
|
|
12
|
-
"rewrittenSpecifiers":
|
|
13
|
-
"digest": "sha256:
|
|
12
|
+
"rewrittenSpecifiers": 7,
|
|
13
|
+
"digest": "sha256:29ba7834cf9d29e9af2f4fac265c7e5e220e06da3346e65c29bcbae41fdfaf9e",
|
|
14
14
|
"testsNotRun": {
|
|
15
15
|
"tests/adapter.spec.ts": "needs @deepseek-ai/dsh-host-webserver (the DSH web server); acryl-node-server has its own tests",
|
|
16
16
|
"tests/stream.spec.ts": "needs @deepseek-ai/dsh-host-webserver (the DSH web server)",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
acryl-state
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WebBoxes s.r.o.
|
|
4
|
+
Licensed under the MIT License (see LICENSE).
|
|
5
|
+
|
|
6
|
+
The cell and derived-cell shape follows VanJS (https://vanjs.org). The code is a new implementation; the notice of that work is kept here.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
VanJS
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2023 VanJS (Vanilla JavaScript)
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Observable cells: the reactive core of the state service, with no Cordis and no DOM. A cell holds one value, tells its
|
|
3
|
-
* subscribers when it changes, and can be derived from other cells. The shape
|
|
3
|
+
* subscribers when it changes, and can be derived from other cells. The shape follows VanJS's `state` and `derive`
|
|
4
4
|
* (spec 046, M5): small enough to read in one sitting, and the same cell serves a React hook, a terminal component and a
|
|
5
5
|
* server-sent stream.
|
|
6
6
|
*/
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Context, Service } from '@deepseek-ai/cordis'
|
|
9
|
-
import type
|
|
9
|
+
import { App, type Context as RequestContext } from '../../acryl-router/src/index.ts'
|
|
10
10
|
import { streamSSE } from '../../acryl-router/src/helpers/index.ts'
|
|
11
|
-
import { loopbackOnly } from '../../acryl-router/src/middleware/index.ts'
|
|
11
|
+
import { loopbackOnly, methodNotAllowed } from '../../acryl-router/src/middleware/index.ts'
|
|
12
12
|
import { createCell, createDerived, type Cell, type ReadonlyCell } from './cell.ts'
|
|
13
13
|
|
|
14
14
|
declare module '@deepseek-ai/cordis' {
|
|
@@ -17,6 +17,8 @@ declare module '@deepseek-ai/cordis' {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
const KEEP_ALIVE_MS = 15_000
|
|
21
|
+
|
|
20
22
|
/** Stable Cordis plugin name, equal to the package name and the Loader row id. */
|
|
21
23
|
export const name = 'acryl-state'
|
|
22
24
|
|
|
@@ -70,15 +72,29 @@ export class State extends Service {
|
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
/**
|
|
73
|
-
* The current value of `key` as a server-sent event stream: the value now, then one event per change, until the
|
|
74
|
-
*
|
|
75
|
+
* The current value of `key` as a server-sent event stream: the value now, then one event per change, until the client cancels or the router
|
|
76
|
+
* is disposed (both abort the stream). A comment line goes out every 15 seconds so proxies and browsers keep an idle stream open.
|
|
75
77
|
*/
|
|
76
|
-
stream(key: string, c:
|
|
78
|
+
stream(key: string, c: RequestContext): Response | undefined {
|
|
77
79
|
const cell = this.find(key)
|
|
78
80
|
if (cell === undefined) return undefined
|
|
79
|
-
return streamSSE(c, stream => {
|
|
80
|
-
stream.
|
|
81
|
-
|
|
81
|
+
return streamSSE(c, async stream => {
|
|
82
|
+
const ended = new Promise<void>(resolve => { stream.onAbort(resolve) })
|
|
83
|
+
// Writes are asynchronous; chaining them keeps events in the order the cell changed.
|
|
84
|
+
let queue: Promise<unknown> = Promise.resolve()
|
|
85
|
+
const send = (value: unknown): void => {
|
|
86
|
+
queue = queue.then(() => stream.writeSSE({ event: 'value', data: typeof value === 'string' ? value : JSON.stringify(value) })).catch(() => {})
|
|
87
|
+
}
|
|
88
|
+
send(cell.get())
|
|
89
|
+
const unsubscribe = cell.subscribe(send)
|
|
90
|
+
const keepAlive = setInterval(() => { queue = queue.then(() => stream.write(': keepalive\n\n')).catch(() => {}) }, KEEP_ALIVE_MS)
|
|
91
|
+
keepAlive.unref()
|
|
92
|
+
try {
|
|
93
|
+
await ended
|
|
94
|
+
} finally {
|
|
95
|
+
clearInterval(keepAlive)
|
|
96
|
+
unsubscribe()
|
|
97
|
+
}
|
|
82
98
|
})
|
|
83
99
|
}
|
|
84
100
|
}
|
|
@@ -88,13 +104,15 @@ export function apply(ctx: Context): void {
|
|
|
88
104
|
// The same cells on the router, read-only, for any surface; present only where a router is. A request over a socket must come from this machine to this
|
|
89
105
|
// app; the terminal calls the router in process and is the host's own code.
|
|
90
106
|
ctx.inject(['router'], inner => {
|
|
91
|
-
const api =
|
|
92
|
-
api.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
const api = new App()
|
|
108
|
+
api.use(loopbackOnly({ inProcess: 'allow' }), methodNotAllowed({ app: api }))
|
|
109
|
+
.get('/', c => c.json({ keys: state.keys() }))
|
|
110
|
+
.get('/:key', c => {
|
|
111
|
+
const cell = state.find(c.req.param('key'))
|
|
112
|
+
return cell === undefined ? c.json({ error: 'no such state' }, 404) : c.json({ key: cell.key, value: cell.get() })
|
|
113
|
+
})
|
|
114
|
+
.get('/:key/events', c => state.stream(c.req.param('key'), c) ?? c.json({ error: 'no such state' }, 404))
|
|
115
|
+
inner.router.route('/api/state', api)
|
|
98
116
|
// Over HTTP only where an adapter exists (Web and Desktop); the terminal needs none.
|
|
99
117
|
inner.inject(['routerHttp'], http => { http.routerHttp.mount('/api/state') })
|
|
100
118
|
})
|
|
@@ -2,19 +2,21 @@
|
|
|
2
2
|
"source": {
|
|
3
3
|
"repo": "acryldev/acryl",
|
|
4
4
|
"path": "plugins/acryl-state",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "64e7c9d2e787f74b2da82492644fbbb46a601074"
|
|
6
6
|
},
|
|
7
7
|
"vendored": [
|
|
8
8
|
"src",
|
|
9
9
|
"tests",
|
|
10
|
-
"LICENSE"
|
|
10
|
+
"LICENSE",
|
|
11
|
+
"NOTICE"
|
|
11
12
|
],
|
|
12
13
|
"rewrittenSpecifiers": 4,
|
|
13
|
-
"digest": "sha256:
|
|
14
|
+
"digest": "sha256:8feb067a67ea2628ff31a9fd2201361be4be5a8c217be3b7169277c0ce472b69",
|
|
14
15
|
"testsNotRun": {
|
|
15
16
|
"tests/react.spec.ts": "needs react and @testing-library/react, which generated apps do not install"
|
|
16
17
|
},
|
|
17
18
|
"notice": [
|
|
18
|
-
"LICENSE"
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"NOTICE"
|
|
19
21
|
]
|
|
20
22
|
}
|
|
@@ -18,22 +18,19 @@ const PATH = /^\/[a-z0-9/-]*$/
|
|
|
18
18
|
|
|
19
19
|
export function routeSource(name: string, path: string, text: string): string {
|
|
20
20
|
return `import type { Context } from '@deepseek-ai/cordis'
|
|
21
|
+
import { App } from 'acryl-router'
|
|
21
22
|
|
|
22
23
|
/** Written by the builder: answers GET ${path}, and checks that it does before the install counts. */
|
|
23
24
|
export const name = '${name}'
|
|
24
25
|
export const inject = ['router']
|
|
25
26
|
|
|
26
|
-
interface Router {
|
|
27
|
-
get(path: string, handler: (c: { json(body: unknown, status?: number): Response }) => Response): unknown
|
|
28
|
-
request(path: string): Promise<Response>
|
|
29
|
-
}
|
|
30
27
|
export function apply(ctx: Context) {
|
|
31
|
-
//
|
|
32
|
-
|
|
28
|
+
// The app is mounted through this plugin's context, so the route is removed when the plugin is unloaded.
|
|
29
|
+
ctx.router.route('/', new App().get(${JSON.stringify(path)}, (c) => c.json({ text: ${JSON.stringify(text)} })))
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
export async function check(ctx: Context) {
|
|
36
|
-
const reply = await
|
|
33
|
+
const reply = await ctx.router.request(${JSON.stringify(path)})
|
|
37
34
|
if (!reply.ok) throw new Error(\`GET ${path} answered \${reply.status}\`)
|
|
38
35
|
}
|
|
39
36
|
`
|
|
@@ -32,7 +32,7 @@ import { type Context, type Fiber, Service } from '@deepseek-ai/cordis'
|
|
|
32
32
|
import type {} from '../instance/index.ts'
|
|
33
33
|
import type { Sandbox } from '../sandbox/index.ts'
|
|
34
34
|
import { appendLedger, type LedgerEntry, readLedger } from './ledger.ts'
|
|
35
|
-
import { resolveFromApp } from './resolve.ts'
|
|
35
|
+
import { resolveFromApp, routerAliases } from './resolve.ts'
|
|
36
36
|
import { hashFolder, prune, stage } from './stage.ts'
|
|
37
37
|
|
|
38
38
|
/** A fiber's lifecycle state, by name (Cordis's own enum is a `const enum`, which does not exist at run time). */
|
|
@@ -109,7 +109,7 @@ interface PluginModule {
|
|
|
109
109
|
readonly name?: unknown
|
|
110
110
|
readonly inject?: unknown
|
|
111
111
|
readonly apply: (...args: unknown[]) => unknown
|
|
112
|
-
/** Optional self-check, run after the plugin starts: it throws (or rejects) when the plugin does not work. */
|
|
112
|
+
/** Optional self-check, run after the plugin starts with the plugin's own context (so `ctx.router` reads as in `apply`): it throws (or rejects) when the plugin does not work. */
|
|
113
113
|
readonly check?: (ctx: Context) => unknown
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -221,7 +221,7 @@ export class Extensions extends Service {
|
|
|
221
221
|
this.ledgerFile = join(home, 'extensions-ledger.jsonl')
|
|
222
222
|
// Effects run their disposers newest first: settle pending operations, then dispose every mounted extension, then drop the hook.
|
|
223
223
|
ctx.effect(
|
|
224
|
-
() => resolveFromApp(this.stageRoot, root),
|
|
224
|
+
() => resolveFromApp(this.stageRoot, root, routerAliases()),
|
|
225
225
|
'extensions: resolve staged imports from the app',
|
|
226
226
|
)
|
|
227
227
|
ctx.effect(
|
|
@@ -507,6 +507,7 @@ export class Extensions extends Service {
|
|
|
507
507
|
/** Run the extension's own `check`, if it has one, with a time limit. Returns the failure, or `undefined` when it passes. */
|
|
508
508
|
private async verify(
|
|
509
509
|
module: PluginModule,
|
|
510
|
+
ctx: Context,
|
|
510
511
|
): Promise<{ stage: 'verify'; error: string } | undefined> {
|
|
511
512
|
if (typeof module.check !== 'function') return undefined
|
|
512
513
|
let timer: ReturnType<typeof setTimeout> | undefined
|
|
@@ -517,7 +518,7 @@ export class Extensions extends Service {
|
|
|
517
518
|
CHECK_TIMEOUT_MS,
|
|
518
519
|
)
|
|
519
520
|
})
|
|
520
|
-
await Promise.race([Promise.resolve(module.check(
|
|
521
|
+
await Promise.race([Promise.resolve(module.check(ctx)), limit])
|
|
521
522
|
return undefined
|
|
522
523
|
} catch (error) {
|
|
523
524
|
return { stage: 'verify', error: `its check failed: ${message(error)}` }
|
|
@@ -615,7 +616,7 @@ export class Extensions extends Service {
|
|
|
615
616
|
error: message(mounted.error ?? 'the plugin failed to start'),
|
|
616
617
|
}
|
|
617
618
|
: state === 'active'
|
|
618
|
-
? await this.verify(module)
|
|
619
|
+
? await this.verify(module, mounted.fiber.ctx)
|
|
619
620
|
: undefined
|
|
620
621
|
if (failure) {
|
|
621
622
|
await mounted.fiber.dispose().catch(() => undefined)
|
|
@@ -3,20 +3,45 @@
|
|
|
3
3
|
* `@deepseek-ai/cordis` would not find the app's `node_modules`. This resolve hook leaves resolution alone, and only when it fails for a
|
|
4
4
|
* module inside the staging folder, resolves the same name from the app's project folder. Uses Node's public `module.registerHooks`
|
|
5
5
|
* (ACRYL needed the same fallback on Deno); the returned function removes the hook.
|
|
6
|
+
*
|
|
7
|
+
* The framework plugins are not packages in an app (they are source under `src/plugins/`), so `aliases` names the files a framework specifier such as
|
|
8
|
+
* `acryl-router` stands for. A staged plugin then imports the same module the app itself runs, so `App` is the very class the router mounts.
|
|
6
9
|
*/
|
|
7
|
-
import { mkdirSync, realpathSync } from 'node:fs'
|
|
10
|
+
import { existsSync, mkdirSync, realpathSync } from 'node:fs'
|
|
8
11
|
import { registerHooks } from 'node:module'
|
|
9
12
|
import { join } from 'node:path'
|
|
10
|
-
import { pathToFileURL } from 'node:url'
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
11
14
|
|
|
12
15
|
const isBare = (specifier: string) => !/^[./]/.test(specifier) && !specifier.includes(':')
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
/** The router's specifiers, as files of the vendored plugin that sits beside this one; empty when the app has no router. */
|
|
18
|
+
export function routerAliases(): Record<string, string> {
|
|
19
|
+
const src = new URL('../acryl-router/src/', import.meta.url)
|
|
20
|
+
const files = {
|
|
21
|
+
'acryl-router': 'index.ts',
|
|
22
|
+
'acryl-router/middleware': 'middleware/index.ts',
|
|
23
|
+
'acryl-router/helpers': 'helpers/index.ts',
|
|
24
|
+
}
|
|
25
|
+
return Object.fromEntries(
|
|
26
|
+
Object.entries(files)
|
|
27
|
+
.map(([specifier, file]) => [specifier, fileURLToPath(new URL(file, src))] as const)
|
|
28
|
+
.filter(([, path]) => existsSync(path)),
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resolveFromApp(
|
|
33
|
+
stageRoot: string,
|
|
34
|
+
appRoot: string,
|
|
35
|
+
aliases: Record<string, string> = {},
|
|
36
|
+
): () => void {
|
|
15
37
|
mkdirSync(stageRoot, { recursive: true })
|
|
16
38
|
const staged = pathToFileURL(`${realpathSync(stageRoot)}/`).href
|
|
17
39
|
const parentURL = pathToFileURL(join(realpathSync(appRoot), 'package.json')).href
|
|
18
40
|
const hook = registerHooks({
|
|
19
41
|
resolve(specifier, context, nextResolve) {
|
|
42
|
+
const alias = aliases[specifier]
|
|
43
|
+
if (alias !== undefined && context.parentURL?.startsWith(staged))
|
|
44
|
+
return nextResolve(pathToFileURL(alias).href, context)
|
|
20
45
|
try {
|
|
21
46
|
return nextResolve(specifier, context)
|
|
22
47
|
} catch (error) {
|
package/plugins/openai/prompt.ts
CHANGED
|
@@ -10,26 +10,28 @@ To add a capability:
|
|
|
10
10
|
A plugin's index.ts is TypeScript that Node runs directly (erasable syntax only: no enums, no parameter properties):
|
|
11
11
|
|
|
12
12
|
import type { Context } from '@deepseek-ai/cordis'
|
|
13
|
+
import { App } from 'acryl-router'
|
|
13
14
|
export const name = 'hello' // must equal the folder name
|
|
14
15
|
export const inject = ['router'] // services it needs; it waits (pending) until they exist
|
|
15
|
-
interface Router { get(path: string, handler: (c: { json(body: unknown, status?: number): Response }) => Response): unknown; request(path: string): Promise<Response> }
|
|
16
16
|
export function apply(ctx: Context) {
|
|
17
|
-
|
|
17
|
+
ctx.router.route('/', new App().get('/hello', (c) => c.json({ text: 'Hello' }))) // removed when the plugin is unloaded
|
|
18
18
|
}
|
|
19
19
|
export async function check(ctx: Context) { // optional: must pass, or the install is rolled back
|
|
20
|
-
const reply = await
|
|
20
|
+
const reply = await ctx.router.request('/hello')
|
|
21
21
|
if (!reply.ok) throw new Error('GET /hello failed')
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
The router: router.get/post/put/delete(path, handler)
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
The router: build an App (import { App } from 'acryl-router'), register routes on it with app.get/post/put/patch/delete(path, handler) and mount it with
|
|
25
|
+
ctx.router.route(prefix, app). A path parameter is :id and is read with c.req.param('id'); c.json(body, status) answers JSON, c.text(...) answers text,
|
|
26
|
+
await c.req.json() reads a JSON body, throw new HTTPException(404, { message }) (also from 'acryl-router') answers an error. Routes added to an app
|
|
27
|
+
after route() are not served: build the whole app first. ctx.router.request('/path', { method: 'POST', body: ... }) calls a route in process (use it in
|
|
28
|
+
check). The mount goes away when the plugin is unloaded.
|
|
27
29
|
|
|
28
30
|
For a capability that is a pure function of data (parse, convert, compute), write a sandboxed TOOL instead of a plugin. It cannot touch the
|
|
29
31
|
host: it runs in a separate process that can read only its own folder. Files: index.ts exporting run(input) and tool.json:
|
|
30
32
|
{ "description": "what it does and its input", "examples": [ { "input": {...}, "output": {...} } ] }
|
|
31
33
|
The examples are run in the sandbox at install and must match exactly, or the install is refused. Use write_plugin and install_plugin for tools too.
|
|
32
34
|
|
|
33
|
-
Rules: import only relative files, node: built-ins
|
|
35
|
+
Rules: import only relative files, node: built-ins, @deepseek-ai/cordis and acryl-router. Provide services with ctx.provide(name, value) and
|
|
34
36
|
use others with ctx.get(name). Put timers, listeners and routes inside ctx.effect(() => cleanup). Use list_plugins and
|
|
35
37
|
read_plugin to see what exists. When you are done, answer in one or two plain sentences saying what you built.`
|
package/plugins/tsconfig.json
CHANGED
|
@@ -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,
|
|
@@ -17,6 +15,9 @@
|
|
|
17
15
|
},
|
|
18
16
|
"include": ["*/**/*.ts"],
|
|
19
17
|
"exclude": [
|
|
18
|
+
"acryl-*/src/**/*.test.ts",
|
|
19
|
+
"acryl-*/src/**/*.case.ts",
|
|
20
|
+
"acryl-node-server/tests/adapter",
|
|
20
21
|
"acryl-router/tests/loader.spec.ts",
|
|
21
22
|
"acryl-state/src/react.ts",
|
|
22
23
|
"acryl-state/tests/react.spec.ts",
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# __APP_NAME__
|
|
2
|
+
|
|
3
|
+
A Cordis app on the ACRYL framework: TypeScript that Node 22.19 or newer runs directly (no build step; erasable syntax only, so no enums and no
|
|
4
|
+
parameter properties). Every part of it is a Cordis plugin.
|
|
5
|
+
|
|
6
|
+
## The framework's API is in `docs/framework/`
|
|
7
|
+
|
|
8
|
+
Start at `docs/framework/README.md`; `api-summary.md` is the one-page summary. Read before you write routes: `routing.md` (the `App`), `request.md` (`c.req`), `context.md` (`c`),
|
|
9
|
+
`validation.md`, `helpers.md` (`streamSSE`), `middleware.md`, `plugin-lifecycle.md` (what a plugin owns and when it is unloaded) and
|
|
10
|
+
`node-server.md`. The framework's source is vendored in `src/plugins/acryl-*/src`: read it, do not edit it (a change belongs in ACRYL, upstream).
|
|
11
|
+
The docs write `from 'acryl-router'`; in this app that is `./plugins/acryl-router/src/index.ts` (relative to `src/`), `acryl-router/middleware`
|
|
12
|
+
is `./plugins/acryl-router/src/middleware/index.ts` and `acryl-router/helpers` is `./plugins/acryl-router/src/helpers/index.ts`.
|
|
13
|
+
A plugin written into `extensions/` while the app runs can import `acryl-router`, `acryl-router/middleware` and `acryl-router/helpers` by those names.
|
|
14
|
+
|
|
15
|
+
## How a feature is built
|
|
16
|
+
|
|
17
|
+
Routes are an ordinary `App` that a plugin mounts once with `ctx.router.route(prefix, app)`. The mount belongs to the plugin and goes away when it unloads.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
21
|
+
import { App, HTTPException } from './plugins/acryl-router/src/index.ts'
|
|
22
|
+
import { schemaValidator, type StandardSchema } from './plugins/acryl-router/src/helpers/index.ts'
|
|
23
|
+
|
|
24
|
+
export const name = 'notes' // a plugin's name; blend.yaml refers to it
|
|
25
|
+
export const inject = ['router'] // services it needs
|
|
26
|
+
export function apply(ctx: Context) {
|
|
27
|
+
const app = new App()
|
|
28
|
+
.get('/', (c) => c.json({ ok: true }))
|
|
29
|
+
.get('/:id', (c) => c.json({ id: c.req.param('id') }))
|
|
30
|
+
.post('/', schemaValidator('json', NewNote), (c) => c.json(c.req.valid('json'), 201)) // NewNote is a Standard Schema
|
|
31
|
+
ctx.router.route('/notes', app) // build the whole app first: routes added after route() are not served
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- A small route goes on the `App` in `src/routes.ts`. A new feature is a new plugin file: add it to `registry` in `src/app.ts`, add a row
|
|
36
|
+
(`- id: notes` / `name: notes`) to `blend.yaml` before the `acryl-node-server` row, and pass runtime values (a data file) as its config in `createApp`.
|
|
37
|
+
- State, when `src/plugins/acryl-state` exists: `const cell = ctx.state.cell<T>('notes.list', initial)` (add `'state'` to `inject`), then
|
|
38
|
+
`cell.get()`, `cell.set(value)`, `cell.update(fn)` and `cell.subscribe(fn)` (returns the unsubscribe). Every cell is also served as JSON at `/api/state/<key>`
|
|
39
|
+
and streamed at `/api/state/<key>/events`.
|
|
40
|
+
- Test a route without a socket: `await ctx.router.request('/notes')`, or boot the app as `test/app.test.ts` does and use `fetch`.
|
|
41
|
+
|
|
42
|
+
## Things that surprise people
|
|
43
|
+
|
|
44
|
+
- A page mounted at `/x` answers `/x`, not `/x/` (trailing slashes are strict); `trimTrailingSlash()` (middleware) redirects `/x/` to `/x` for GET and HEAD.
|
|
45
|
+
- `schemaValidator('json', ...)` needs a JSON `Content-Type`; without it the schema sees `{}`. A failure answers 400 with
|
|
46
|
+
`{ "error": "invalid json", "issues": [{ "message", "path" }] }`: the human text is `issues[0].message`. A `jsonOnly` recipe in `docs/framework/validation.md` answers 415 instead.
|
|
47
|
+
- Throw `new HTTPException(404, { message })` for an error response; the body is the message as plain text (use `c.json({ error }, status)` when a page expects JSON).
|
|
48
|
+
- Handlers get the request as `c.req`: `c.req.param('id')`, `c.req.query('q')`, `await c.req.json()`, `c.req.raw` for the Web `Request`.
|
|
49
|
+
- A server-sent stream is `streamSSE(c, async (stream) => { await stream.writeSSE({ data: JSON.stringify(x) }) ... })`; call `stream.onAbort(cleanup)`.
|
|
50
|
+
- `npm run dev` restarts the app whenever a file changes.
|
|
51
|
+
|
|
52
|
+
## Before you say you are done
|
|
53
|
+
|
|
54
|
+
`npm test` and `npm run check` (type-check) both pass. Put tests in `test/`, written with `node:test` like `test/app.test.ts`.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
|
|
2
|
+
## This app (agent)
|
|
3
|
+
|
|
4
|
+
Besides `src/routes.ts`, the app runs an agent (`POST /ask`) that can write plugins into `extensions/` while the app runs and install them with
|
|
5
|
+
rollback; `GET /extensions` and `GET /tasks` show what it did. A plugin there is a folder with an `index.ts` exporting `name`, `inject`, `apply(ctx)`
|
|
6
|
+
and an optional `check(ctx)` that receives the plugin's own context (so `ctx.router` reads as in `apply`). It may import `acryl-router`.
|
|
@@ -5,7 +5,7 @@ Cordis plugin.
|
|
|
5
5
|
|
|
6
6
|
```sh
|
|
7
7
|
npm install
|
|
8
|
-
npm run dev # prints its URL; data lives in .acrylblends
|
|
8
|
+
npm run dev # prints its URL; data lives in .acrylblends/; it restarts when a file changes (a new port if ACRYLBLENDS_PORT=0)
|
|
9
9
|
npm test
|
|
10
10
|
npm run check # type-check
|
|
11
11
|
|
|
@@ -41,7 +41,7 @@ curl <url>/tasks # every run as a durable task, with the tool calls
|
|
|
41
41
|
|
|
42
42
|
Other commands: `update route <name> <path> <text>`, `break plugin <name>` (shows the rollback), `remove plugin <name>`,
|
|
43
43
|
`list plugins`. What the agent writes lands in `extensions/`, is part of your app, and starts again at the next start.
|
|
44
|
-
- `src/routes.ts` is your own plugin:
|
|
44
|
+
- `src/routes.ts` is your own plugin: it builds an `App` (`import { App } from './plugins/acryl-router/src/index.ts'`) and mounts it with `ctx.router.route(prefix, app)`. Routes added to the app after it is mounted are not served. A page mounted at `/x` answers `/x`, not `/x/` (trailing slashes are strict). The router API is documented at https://acrylblends.github.io/docs/framework/the-acryl-framework and in `docs/framework` of the acrylblends repository.
|
|
45
45
|
- `src/plugins/` holds the plugins copied from the ACRYLBLENDS library. They are yours now; edit them freely.
|
|
46
46
|
Add more with `acrylblends add <plugin>`.
|
|
47
47
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
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
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
|
-
# Order matters:
|
|
3
|
+
# Order matters: acryl-router comes before extensions, because installed extensions start again at boot and may add routes.
|
|
4
4
|
apiVersion: blends.acryl.dev/v1alpha1
|
|
5
5
|
kind: Blueprint
|
|
6
6
|
metadata:
|