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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ACRYL inspector (`ctx.inspect`): what every running plugin requires, provides and registers, read from the live
|
|
3
|
+
* registry. It owns no state and registers nothing of its own beyond an optional read-only pair of router routes, which are
|
|
4
|
+
* reachable in process unless some plugin mounts their prefix over HTTP.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Context, Service } from '@deepseek-ai/cordis'
|
|
8
|
+
import { loopbackOnly } from '../../acryl-router/src/middleware/index.ts'
|
|
9
|
+
import { App } from '../../acryl-router/src/index.ts'
|
|
10
|
+
import { explain, ownerOf, renderReports, reportFiber, type FiberView, type PluginReport } from './report.ts'
|
|
11
|
+
|
|
12
|
+
declare module '@deepseek-ai/cordis' {
|
|
13
|
+
interface Context {
|
|
14
|
+
inspect: Inspect
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Stable Cordis plugin name, equal to the package name and the Loader row id. */
|
|
19
|
+
export const name = 'acryl-inspect'
|
|
20
|
+
|
|
21
|
+
export class Inspect extends Service {
|
|
22
|
+
constructor(ctx: Context) {
|
|
23
|
+
super(ctx, 'inspect')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A report for every live plugin instance, in registry order. */
|
|
27
|
+
plugins(): PluginReport[] {
|
|
28
|
+
const fibers = [...this.ctx.registry.values()].flatMap(runtime => [...runtime.fibers] as unknown as FiberView[])
|
|
29
|
+
// An unnamed inline callback is part of the plugin that wrote it: its routes and mounts are that plugin's.
|
|
30
|
+
const adopted = new Map<FiberView, FiberView[]>()
|
|
31
|
+
const owners: FiberView[] = []
|
|
32
|
+
for (const fiber of fibers) {
|
|
33
|
+
const owner = ownerOf(fiber)
|
|
34
|
+
if (owner === fiber) owners.push(fiber)
|
|
35
|
+
else adopted.set(owner, [...(adopted.get(owner) ?? []), fiber])
|
|
36
|
+
}
|
|
37
|
+
return owners.map(fiber => reportFiber(fiber, adopted.get(fiber) ?? []))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The text explanation of the plugin called `pluginName`, or `undefined` when none is registered. */
|
|
41
|
+
explain(pluginName: string): string | undefined {
|
|
42
|
+
const found = this.plugins().filter(report => report.name === pluginName)
|
|
43
|
+
return found.length === 0 ? undefined : found.map(explain).join('\n\n')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The whole system as text. */
|
|
47
|
+
render(): string {
|
|
48
|
+
return renderReports(this.plugins())
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function apply(ctx: Context): void {
|
|
53
|
+
const inspect = new Inspect(ctx)
|
|
54
|
+
// The same facts on the router, for any surface; present only where a router is. A request over a socket must come from this machine to this app;
|
|
55
|
+
// the terminal calls the router in process and is the host's own code.
|
|
56
|
+
ctx.inject(['router'], inner => {
|
|
57
|
+
const api = new App()
|
|
58
|
+
.use(loopbackOnly({ inProcess: 'allow' }))
|
|
59
|
+
.get('/plugins', c => c.json(inspect.plugins()))
|
|
60
|
+
.get('/plugins/:name', c => {
|
|
61
|
+
const text = inspect.explain(c.req.param('name'))
|
|
62
|
+
return text === undefined ? c.json({ error: 'no such plugin' }, 404) : c.text(text)
|
|
63
|
+
})
|
|
64
|
+
inner.router.route('/api/inspect', api)
|
|
65
|
+
// Over HTTP only where an adapter exists (Web and Desktop); the terminal needs none.
|
|
66
|
+
inner.inject(['routerHttp'], http => { http.routerHttp.mount('/api/inspect') })
|
|
67
|
+
})
|
|
68
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { explain, renderReports, reportFiber, stateName, type FiberView } from '../src/report.ts'
|
|
3
|
+
|
|
4
|
+
function fiber(over: Partial<FiberView> & { effects?: FiberView['getEffects'] extends () => infer R ? R : never } = {}): FiberView {
|
|
5
|
+
const { effects = [], ...rest } = over
|
|
6
|
+
return { state: 2, inject: {}, runtime: { name: 'demo' }, getEffects: () => effects, ...rest }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('reportFiber', () => {
|
|
10
|
+
it('sorts effects into provides, mounts with their routes, and everything else, including nested ones', () => {
|
|
11
|
+
const report = reportFiber(fiber({
|
|
12
|
+
inject: { router: null, acrylSettings: null },
|
|
13
|
+
effects: [
|
|
14
|
+
{ label: 'ctx.provide("things")', children: [] },
|
|
15
|
+
{ label: 'acryl-router: route /api (ALL /api/*, GET /api/things/:id)', children: [] },
|
|
16
|
+
{ label: 'acryl-router: route /api/things (POST /api/things)', children: [{ label: 'inner timer', children: [] }] },
|
|
17
|
+
{ label: 'ctx.on("internal/status")', children: [] },
|
|
18
|
+
],
|
|
19
|
+
}))
|
|
20
|
+
expect(report).toEqual({
|
|
21
|
+
name: 'demo',
|
|
22
|
+
state: 'ACTIVE',
|
|
23
|
+
requires: ['acrylSettings', 'router'],
|
|
24
|
+
provides: ['things'],
|
|
25
|
+
mounts: ['/api', '/api/things'],
|
|
26
|
+
routes: [{ method: 'ALL', path: '/api/*' }, { method: 'GET', path: '/api/things/:id' }, { method: 'POST', path: '/api/things' }],
|
|
27
|
+
other: ['inner timer', 'ctx.on("internal/status")'],
|
|
28
|
+
removedOnUnload: 5,
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('names the state and an anonymous plugin', () => {
|
|
33
|
+
expect(stateName(0)).toBe('PENDING')
|
|
34
|
+
expect(stateName(2)).toBe('ACTIVE')
|
|
35
|
+
expect(stateName(5)).toBe('UNLOADING')
|
|
36
|
+
expect(reportFiber(fiber({ runtime: null })).name).toBe('anonymous')
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('explain', () => {
|
|
41
|
+
it('says what a plugin needs, adds and removes on unload', () => {
|
|
42
|
+
const text = explain(reportFiber(fiber({
|
|
43
|
+
inject: { router: null },
|
|
44
|
+
effects: [{ label: 'acryl-router: route / (GET /a)', children: [] }],
|
|
45
|
+
})))
|
|
46
|
+
expect(text).toContain('demo (ACTIVE)')
|
|
47
|
+
expect(text).toContain('needs: router')
|
|
48
|
+
expect(text).toContain('routes: GET /a')
|
|
49
|
+
expect(text).toContain('unloading: removes 1 registration and nothing else')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('says a pending plugin is waiting for its services', () => {
|
|
53
|
+
const text = explain(reportFiber(fiber({ state: 0, inject: { router: null, webServer: null } })))
|
|
54
|
+
expect(text).toContain('demo (PENDING)')
|
|
55
|
+
expect(text).toContain('it starts when router, webServer are available')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('renders several plugins as blocks', () => {
|
|
59
|
+
const a = reportFiber(fiber({ runtime: { name: 'a' } }))
|
|
60
|
+
const b = reportFiber(fiber({ runtime: { name: 'b' } }))
|
|
61
|
+
expect(renderReports([a, b]).split('\n\n')).toHaveLength(2)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The inspector against real Cordis plugins: what it reports is what Cordis holds, so the tests build a small system and
|
|
3
|
+
* compare the report with what the plugins actually did.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Context } from '@deepseek-ai/cordis'
|
|
7
|
+
import { App, apply as routerApply, name as routerName } from '../../acryl-router/src/index.ts'
|
|
8
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
9
|
+
import { apply, name, type PluginReport } from '../src/index.ts'
|
|
10
|
+
|
|
11
|
+
const contexts: Context[] = []
|
|
12
|
+
const settle = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0))
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
while (contexts.length > 0) await contexts.pop()?.fiber.dispose()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const market = {
|
|
18
|
+
name: 'demo-market',
|
|
19
|
+
inject: ['router'],
|
|
20
|
+
apply(ctx: Context): void {
|
|
21
|
+
ctx.router.route('/api/demo', new App()
|
|
22
|
+
.use(async (_c, next) => { await next() })
|
|
23
|
+
.get('/state', c => c.json({ ok: true }))
|
|
24
|
+
.post('/sources', c => c.json({ ok: true }, 201)))
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function system(): Promise<Context> {
|
|
29
|
+
const ctx = new Context()
|
|
30
|
+
contexts.push(ctx)
|
|
31
|
+
ctx.plugin({ name: routerName, apply: routerApply }, { trailingSlash: 'strict' })
|
|
32
|
+
ctx.plugin({ name, apply })
|
|
33
|
+
await settle()
|
|
34
|
+
return ctx
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const byName = (reports: readonly PluginReport[], pluginName: string): PluginReport => {
|
|
38
|
+
const found = reports.find(report => report.name === pluginName)
|
|
39
|
+
if (found === undefined) throw new Error(`no report for ${pluginName}`)
|
|
40
|
+
return found
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe('acryl-inspect', () => {
|
|
44
|
+
it('counts what an inline ctx.inject callback registers as the owning plugin\'s', async () => {
|
|
45
|
+
const ctx = await system()
|
|
46
|
+
ctx.plugin({
|
|
47
|
+
name: 'demo-inline',
|
|
48
|
+
apply(inner: Context): void {
|
|
49
|
+
inner.inject(['router'], deeper => { deeper.router.route('/', new App().get('/api/inline', c => c.json({ ok: true }))) })
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
await settle()
|
|
53
|
+
const reports = ctx.inspect.plugins()
|
|
54
|
+
expect(byName(reports, 'demo-inline').routes).toEqual([{ method: 'GET', path: '/api/inline' }])
|
|
55
|
+
expect(reports.every(report => report.name !== 'anonymous')).toBe(true)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('reports what a plugin requires, provides and registers, from the running system', async () => {
|
|
59
|
+
const ctx = await system()
|
|
60
|
+
ctx.plugin(market)
|
|
61
|
+
await settle()
|
|
62
|
+
const reports = ctx.inspect.plugins()
|
|
63
|
+
|
|
64
|
+
expect(byName(reports, 'acryl-router')).toMatchObject({ state: 'ACTIVE', requires: [], provides: ['router'] })
|
|
65
|
+
expect(byName(reports, 'acryl-inspect')).toMatchObject({ state: 'ACTIVE', provides: ['inspect'] })
|
|
66
|
+
const demo = byName(reports, 'demo-market')
|
|
67
|
+
expect(demo).toMatchObject({
|
|
68
|
+
state: 'ACTIVE',
|
|
69
|
+
requires: ['router'],
|
|
70
|
+
provides: [],
|
|
71
|
+
mounts: ['/api/demo'],
|
|
72
|
+
routes: [{ method: 'ALL', path: '/api/demo/*' }, { method: 'GET', path: '/api/demo/state' }, { method: 'POST', path: '/api/demo/sources' }],
|
|
73
|
+
removedOnUnload: 1,
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('shows a plugin whose service is missing as PENDING and says what it waits for', async () => {
|
|
78
|
+
const ctx = await system()
|
|
79
|
+
ctx.plugin({ name: 'needs-a-thing', inject: ['neverProvided'], apply() { /* never runs */ } })
|
|
80
|
+
await settle()
|
|
81
|
+
expect(byName(ctx.inspect.plugins(), 'needs-a-thing')).toMatchObject({ state: 'PENDING', requires: ['neverProvided'] })
|
|
82
|
+
expect(ctx.inspect.explain('needs-a-thing')).toContain('it starts when neverProvided is available')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('stops listing a plugin that unloaded, and the router really lost what the report said would go', async () => {
|
|
86
|
+
const ctx = await system()
|
|
87
|
+
const baseline = ctx.router.size
|
|
88
|
+
const fiber = ctx.plugin(market)
|
|
89
|
+
await settle()
|
|
90
|
+
const promised = byName(ctx.inspect.plugins(), 'demo-market').removedOnUnload
|
|
91
|
+
expect(ctx.router.size - baseline).toBe(promised)
|
|
92
|
+
|
|
93
|
+
await fiber.dispose()
|
|
94
|
+
await settle()
|
|
95
|
+
expect(ctx.inspect.plugins().some(report => report.name === 'demo-market' && report.state === 'ACTIVE')).toBe(false)
|
|
96
|
+
expect(ctx.router.size).toBe(baseline)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('serves the same facts on the router, in process', async () => {
|
|
100
|
+
const ctx = await system()
|
|
101
|
+
ctx.plugin(market)
|
|
102
|
+
await settle()
|
|
103
|
+
const list = await ctx.router.fetch(new Request('http://acryl.test/api/inspect/plugins'))
|
|
104
|
+
expect(list.status).toBe(200)
|
|
105
|
+
const reports = await list.json() as PluginReport[]
|
|
106
|
+
expect(byName(reports, 'demo-market').routes).toHaveLength(3)
|
|
107
|
+
|
|
108
|
+
const one = await ctx.router.fetch(new Request('http://acryl.test/api/inspect/plugins/demo-market'))
|
|
109
|
+
expect(await one.text()).toContain('removes 1 registration and nothing else')
|
|
110
|
+
expect((await ctx.router.fetch(new Request('http://acryl.test/api/inspect/plugins/nope'))).status).toBe(404)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('needs no router: without one it still answers in code and registers no routes', async () => {
|
|
114
|
+
const ctx = new Context()
|
|
115
|
+
contexts.push(ctx)
|
|
116
|
+
ctx.plugin({ name, apply })
|
|
117
|
+
await settle()
|
|
118
|
+
expect(byName(ctx.inspect.plugins(), 'acryl-inspect').routes).toEqual([])
|
|
119
|
+
expect(ctx.inspect.render()).toContain('acryl-inspect (ACTIVE)')
|
|
120
|
+
})
|
|
121
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": {
|
|
3
|
+
"repo": "acryldev/acryl",
|
|
4
|
+
"path": "plugins/acryl-inspect",
|
|
5
|
+
"commit": "df79b188398b6d4f8cceda35a72b88300a10a9ce"
|
|
6
|
+
},
|
|
7
|
+
"vendored": [
|
|
8
|
+
"src",
|
|
9
|
+
"tests",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"rewrittenSpecifiers": 3,
|
|
13
|
+
"digest": "sha256:e9887df0d2b5274d4fc5de4c730975528a84fd1d270994ee85a2789bec101db7",
|
|
14
|
+
"testsNotRun": {},
|
|
15
|
+
"notice": [
|
|
16
|
+
"LICENSE"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WebBoxes s.r.o.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
The files under src/adapter and tests/adapter are derived from @hono/node-server v2.1.5 (https://github.com/honojs/node-server), used under this license:
|
|
26
|
+
|
|
27
|
+
MIT License
|
|
28
|
+
|
|
29
|
+
Copyright (c) 2022 - present, Yusuke Wada and Hono contributors
|
|
30
|
+
|
|
31
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
32
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
33
|
+
in the Software without restriction, including without limitation the rights
|
|
34
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
35
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
36
|
+
furnished to do so, subject to the following conditions:
|
|
37
|
+
|
|
38
|
+
The above copyright notice and this permission notice shall be included in all
|
|
39
|
+
copies or substantial portions of the Software.
|
|
40
|
+
|
|
41
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
42
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
43
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
44
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
45
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
46
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
47
|
+
SOFTWARE.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "acryl-node-server",
|
|
3
|
+
"description": "The standalone HTTP server as a Cordis plugin: one node:http listener that sends every request to the router. Port, loopback host and next-free-port scan; ends open streams and frees the port when unloaded. Vendored from ACRYL.",
|
|
4
|
+
"requires": ["acryl-router", "acryl-router-http"],
|
|
5
|
+
"inject": ["router"],
|
|
6
|
+
"provides": ["nodeServer"],
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@deepseek-ai/cordis": "4.0.4",
|
|
9
|
+
"@deepseek-ai/schemastery": "3.18.4"
|
|
10
|
+
},
|
|
11
|
+
"mount": "root.plugin(router); root.plugin(nodeServer, { port: 3000, hostname: '127.0.0.1', scan: 20 }); ctx.nodeServer.url",
|
|
12
|
+
"omit": ["**/*.test.*", "**/*.case.ts"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { GetConnInfo } from '../../../acryl-router/src/helpers/index.ts'
|
|
2
|
+
import type { HttpBindings } from './types.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ConnInfo Helper for Node.js
|
|
6
|
+
* @param c Context
|
|
7
|
+
* @returns ConnInfo
|
|
8
|
+
*/
|
|
9
|
+
export const getConnInfo: GetConnInfo = (c) => {
|
|
10
|
+
const bindings = (c.env.server ? c.env.server : c.env) as HttpBindings
|
|
11
|
+
|
|
12
|
+
const address = bindings.incoming.socket.remoteAddress
|
|
13
|
+
const port = bindings.incoming.socket.remotePort
|
|
14
|
+
const family = bindings.incoming.socket.remoteFamily
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
remote: {
|
|
18
|
+
address,
|
|
19
|
+
port,
|
|
20
|
+
addressType: family === 'IPv4' ? 'IPv4' : family === 'IPv6' ? 'IPv6' : void 0,
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Context, Env, MiddlewareHandler } from '../../../acryl-router/src/index.ts'
|
|
2
|
+
import type { HttpBindings } from './types.ts'
|
|
3
|
+
|
|
4
|
+
export type EarlyHintsOptions<E extends Env = Env> = {
|
|
5
|
+
link: string | string[] | ((c: Context<E>) => string | string[] | undefined)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Early Hints middleware for Node.js
|
|
10
|
+
* Automatically sends a 103 Early Hints informational response with the specified Link header(s).
|
|
11
|
+
*
|
|
12
|
+
* @param options EarlyHintsOptions
|
|
13
|
+
* @returns MiddlewareHandler
|
|
14
|
+
*/
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
export const earlyHints = <E extends Env = any>(
|
|
17
|
+
options: EarlyHintsOptions<E>
|
|
18
|
+
): MiddlewareHandler<E> => {
|
|
19
|
+
let warned = false
|
|
20
|
+
|
|
21
|
+
return async (c, next) => {
|
|
22
|
+
const mode = c.req.header('Sec-Fetch-Mode')
|
|
23
|
+
const dest = c.req.header('Sec-Fetch-Dest')
|
|
24
|
+
|
|
25
|
+
if ((mode && mode !== 'navigate') || (dest && dest !== 'document')) {
|
|
26
|
+
return next()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const env = c.env || {}
|
|
30
|
+
const bindings = ('server' in env ? env.server : env) as HttpBindings
|
|
31
|
+
const outgoing = bindings?.outgoing
|
|
32
|
+
|
|
33
|
+
// Capability check: outgoing.writeEarlyHints exists and is a function.
|
|
34
|
+
// This guard exists for non-Node runtimes and non-HTTP bindings.
|
|
35
|
+
if (typeof outgoing?.writeEarlyHints !== 'function') {
|
|
36
|
+
if (!warned) {
|
|
37
|
+
console.warn(
|
|
38
|
+
'Early Hints Middleware is not supported because writeEarlyHints is not defined.'
|
|
39
|
+
)
|
|
40
|
+
warned = true
|
|
41
|
+
}
|
|
42
|
+
return await next()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!outgoing.headersSent) {
|
|
46
|
+
const link = typeof options.link === 'function' ? options.link(c) : options.link
|
|
47
|
+
|
|
48
|
+
if (link !== undefined && (Array.isArray(link) ? link.length > 0 : Boolean(link))) {
|
|
49
|
+
outgoing.writeEarlyHints({ link })
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await next()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http'
|
|
2
|
+
import { Http2ServerRequest } from 'node:http2'
|
|
3
|
+
|
|
4
|
+
type IncomingHeadersSource = Pick<IncomingMessage | Http2ServerRequest, 'rawHeaders'> & {
|
|
5
|
+
headers?: IncomingMessage['headers']
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Node's HTTP/1 parser already joins ordinary repeated headers with the same
|
|
9
|
+
// separators as WHATWG Headers, so its parsed object is a safe fast path for
|
|
10
|
+
// those names. It discards repeats of this fixed set by default, however, and
|
|
11
|
+
// HTTP/2 has different collapsing rules; resolve those cases from rawHeaders.
|
|
12
|
+
// https://nodejs.org/api/http.html#messageheaders
|
|
13
|
+
// https://github.com/nodejs/node/blob/v26.7.0/lib/_http_incoming.js
|
|
14
|
+
// https://www.rfc-editor.org/rfc/rfc9110.html#section-5.2
|
|
15
|
+
const nonJoinedHeaders = new Set([
|
|
16
|
+
'age',
|
|
17
|
+
'authorization',
|
|
18
|
+
'content-length',
|
|
19
|
+
'content-type',
|
|
20
|
+
'etag',
|
|
21
|
+
'expires',
|
|
22
|
+
'from',
|
|
23
|
+
'host',
|
|
24
|
+
'if-modified-since',
|
|
25
|
+
'if-unmodified-since',
|
|
26
|
+
'last-modified',
|
|
27
|
+
'location',
|
|
28
|
+
'max-forwards',
|
|
29
|
+
'proxy-authorization',
|
|
30
|
+
'referer',
|
|
31
|
+
'retry-after',
|
|
32
|
+
'server',
|
|
33
|
+
'user-agent',
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
// Converted from RFC 9110's `field-name = token` and `token`/`tchar` ABNF.
|
|
37
|
+
// https://www.rfc-editor.org/rfc/rfc9110.html#section-5.1
|
|
38
|
+
// https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.2
|
|
39
|
+
const validHeaderName = /^[!#$%&'*+\-.^_`|~\dA-Za-z]+$/
|
|
40
|
+
|
|
41
|
+
const isHttpWhitespace = (code: number): boolean =>
|
|
42
|
+
code === 0x09 || code === 0x0a || code === 0x0d || code === 0x20
|
|
43
|
+
|
|
44
|
+
const normalizeHeaderValue = (value: string): string => {
|
|
45
|
+
if (
|
|
46
|
+
!isHttpWhitespace(value.charCodeAt(0)) &&
|
|
47
|
+
!isHttpWhitespace(value.charCodeAt(value.length - 1))
|
|
48
|
+
) {
|
|
49
|
+
return value
|
|
50
|
+
}
|
|
51
|
+
let start = 0
|
|
52
|
+
let end = value.length
|
|
53
|
+
while (start < end && isHttpWhitespace(value.charCodeAt(start))) {
|
|
54
|
+
start++
|
|
55
|
+
}
|
|
56
|
+
while (end > start && isHttpWhitespace(value.charCodeAt(end - 1))) {
|
|
57
|
+
end--
|
|
58
|
+
}
|
|
59
|
+
return value.slice(start, end)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const forbiddenHeaderValue = /[\0\r\n]/
|
|
63
|
+
|
|
64
|
+
export const GlobalHeaders = globalThis.Headers
|
|
65
|
+
export type GlobalHeaders = InstanceType<typeof GlobalHeaders>
|
|
66
|
+
|
|
67
|
+
const materializeHeaders = (
|
|
68
|
+
rawHeaders: string[],
|
|
69
|
+
HeadersCtor: typeof GlobalHeaders = GlobalHeaders
|
|
70
|
+
): GlobalHeaders => {
|
|
71
|
+
const headers = new HeadersCtor()
|
|
72
|
+
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
73
|
+
const name = rawHeaders[i]
|
|
74
|
+
if (!name.startsWith(':')) {
|
|
75
|
+
headers.append(name, rawHeaders[i + 1])
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return headers
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class RequestHeaders {
|
|
82
|
+
#incoming: IncomingHeadersSource
|
|
83
|
+
#rawHeaders?: string[]
|
|
84
|
+
#headers?: GlobalHeaders
|
|
85
|
+
#invalidValue?: boolean
|
|
86
|
+
|
|
87
|
+
constructor(incoming: IncomingHeadersSource) {
|
|
88
|
+
this.#incoming = incoming
|
|
89
|
+
if (incoming instanceof Http2ServerRequest) {
|
|
90
|
+
this.#rawHeaders = incoming.rawHeaders.slice()
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get #lazyRawHeaders(): string[] {
|
|
95
|
+
return (this.#rawHeaders ??= this.#incoming.rawHeaders.slice())
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get #native(): GlobalHeaders {
|
|
99
|
+
if (!this.#headers) {
|
|
100
|
+
this.#headers = materializeHeaders(this.#lazyRawHeaders)
|
|
101
|
+
this.#rawHeaders = undefined
|
|
102
|
+
}
|
|
103
|
+
return this.#headers
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#normalizedName(name: string): string | undefined {
|
|
107
|
+
if (typeof name !== 'string') {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
if (!validHeaderName.test(name)) {
|
|
111
|
+
throw new TypeError(`Invalid header name: ${name}`)
|
|
112
|
+
}
|
|
113
|
+
return name.toLowerCase()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// The HTTP/1 fast path trusts Node's parser-produced headers object. Mutating
|
|
117
|
+
// it through the incoming binding is outside this optimization's contract;
|
|
118
|
+
// detecting such changes would require scanning or copying every header.
|
|
119
|
+
#lookupHttp1(lowerName: string): string | null | undefined {
|
|
120
|
+
const headers =
|
|
121
|
+
this.#incoming instanceof Http2ServerRequest ? undefined : this.#incoming.headers
|
|
122
|
+
if (
|
|
123
|
+
!headers ||
|
|
124
|
+
nonJoinedHeaders.has(lowerName) ||
|
|
125
|
+
lowerName === 'set-cookie' ||
|
|
126
|
+
lowerName === '__proto__'
|
|
127
|
+
) {
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!Object.hasOwn(headers, lowerName)) {
|
|
132
|
+
return null
|
|
133
|
+
}
|
|
134
|
+
const rawValue = headers[lowerName]
|
|
135
|
+
if (typeof rawValue === 'string') {
|
|
136
|
+
const value = normalizeHeaderValue(rawValue)
|
|
137
|
+
return forbiddenHeaderValue.test(value) ? undefined : value
|
|
138
|
+
}
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#lookup(rawHeaders: string[], lowerName: string): string | null | undefined {
|
|
143
|
+
const separator = lowerName === 'cookie' ? '; ' : ', '
|
|
144
|
+
let value: string | null = null
|
|
145
|
+
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
146
|
+
const rawName = rawHeaders[i]
|
|
147
|
+
if (rawName.length === lowerName.length && rawName.toLowerCase() === lowerName) {
|
|
148
|
+
const rawValue = normalizeHeaderValue(rawHeaders[i + 1])
|
|
149
|
+
if (forbiddenHeaderValue.test(rawValue)) {
|
|
150
|
+
this.#invalidValue = true
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
value = value === null ? rawValue : value + separator + rawValue
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return value
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
append(name: string, value: string): void {
|
|
161
|
+
this.#native.append(name, value)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
delete(name: string): void {
|
|
165
|
+
this.#native.delete(name)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
get(name: string): string | null {
|
|
169
|
+
const lowerName = this.#normalizedName(name)
|
|
170
|
+
if (lowerName && !this.#headers && !this.#invalidValue) {
|
|
171
|
+
const http1Value = this.#lookupHttp1(lowerName)
|
|
172
|
+
if (http1Value !== undefined) {
|
|
173
|
+
return http1Value
|
|
174
|
+
}
|
|
175
|
+
const value = this.#lookup(this.#lazyRawHeaders, lowerName)
|
|
176
|
+
if (value !== undefined) {
|
|
177
|
+
return value
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return this.#native.get(name)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
has(name: string): boolean {
|
|
184
|
+
const lowerName = this.#normalizedName(name)
|
|
185
|
+
if (lowerName && !this.#headers && !this.#invalidValue) {
|
|
186
|
+
const http1Value = this.#lookupHttp1(lowerName)
|
|
187
|
+
if (http1Value !== undefined) {
|
|
188
|
+
return http1Value !== null
|
|
189
|
+
}
|
|
190
|
+
const value = this.#lookup(this.#lazyRawHeaders, lowerName)
|
|
191
|
+
if (value !== undefined) {
|
|
192
|
+
return value !== null
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return this.#native.has(name)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
set(name: string, value: string): void {
|
|
199
|
+
this.#native.set(name, value)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getSetCookie(): string[] {
|
|
203
|
+
return this.#native.getSetCookie()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
keys(): HeadersIterator<string> {
|
|
207
|
+
return this.#native.keys()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
values(): HeadersIterator<string> {
|
|
211
|
+
return this.#native.values()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
entries(): HeadersIterator<[string, string]> {
|
|
215
|
+
return this.#native.entries()
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
forEach(
|
|
219
|
+
callback: (value: string, key: string, parent: GlobalHeaders) => void,
|
|
220
|
+
thisArg?: unknown
|
|
221
|
+
): void {
|
|
222
|
+
this.#native.forEach((value, key) => {
|
|
223
|
+
callback.call(thisArg, value, key, this as unknown as GlobalHeaders)
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
[Symbol.iterator](): HeadersIterator<[string, string]> {
|
|
228
|
+
return this.entries()
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
Object.defineProperty(RequestHeaders.prototype, Symbol.for('nodejs.util.inspect.custom'), {
|
|
233
|
+
value: function (this: RequestHeaders, depth: number, options: object, inspectFn: Function) {
|
|
234
|
+
const props = Object.fromEntries(this)
|
|
235
|
+
return `Headers (lightweight) ${inspectFn(props, { ...options, depth: depth == null ? null : depth - 1 })}`
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// Keep request headers compatible with the captured Headers constructor so
|
|
240
|
+
// `request.headers instanceof Headers` remains true without Symbol.hasInstance
|
|
241
|
+
// or replacing the global constructor.
|
|
242
|
+
Object.setPrototypeOf(RequestHeaders.prototype, GlobalHeaders.prototype)
|
|
243
|
+
|
|
244
|
+
// Preserve the previous live-global behavior when a consumer installs a
|
|
245
|
+
// Headers polyfill after this module has initialized.
|
|
246
|
+
export const newHeadersFromIncoming = (incoming: IncomingHeadersSource): GlobalHeaders =>
|
|
247
|
+
globalThis.Headers === GlobalHeaders
|
|
248
|
+
? (new RequestHeaders(incoming) as unknown as GlobalHeaders)
|
|
249
|
+
: materializeHeaders(incoming.rawHeaders, globalThis.Headers)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { serve, createAdaptorServer } from './server.ts'
|
|
2
|
+
export { upgradeWebSocket } from './websocket.ts'
|
|
3
|
+
export { getRequestListener } from './listener.ts'
|
|
4
|
+
export { RequestError } from './request.ts'
|
|
5
|
+
export type { HttpBindings, Http2Bindings, ServerType } from './types.ts'
|
|
6
|
+
export type { WebSocketData, WebSocketLike, WebSocketServerLike } from './websocket-types.ts'
|