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,96 @@
|
|
|
1
|
+
import type { OutgoingHttpHeaders } from 'node:http'
|
|
2
|
+
import type { Writable } from 'node:stream'
|
|
3
|
+
|
|
4
|
+
export async function readWithoutBlocking(
|
|
5
|
+
readPromise: Promise<ReadableStreamReadResult<Uint8Array>>
|
|
6
|
+
): Promise<ReadableStreamReadResult<Uint8Array> | undefined> {
|
|
7
|
+
return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(undefined))])
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function writeFromReadableStreamDefaultReader(
|
|
11
|
+
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
12
|
+
writable: Writable,
|
|
13
|
+
currentReadPromise?: Promise<ReadableStreamReadResult<Uint8Array>> | undefined
|
|
14
|
+
) {
|
|
15
|
+
const cancel = (error?: unknown) => {
|
|
16
|
+
reader.cancel(error).catch(() => {})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
writable.on('close', cancel)
|
|
20
|
+
writable.on('error', cancel)
|
|
21
|
+
;(currentReadPromise ?? reader.read()).then(flow, handleStreamError)
|
|
22
|
+
|
|
23
|
+
return reader.closed.finally(() => {
|
|
24
|
+
writable.off('close', cancel)
|
|
25
|
+
writable.off('error', cancel)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
function handleStreamError(error: any) {
|
|
30
|
+
if (error) {
|
|
31
|
+
writable.destroy(error)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function onDrain() {
|
|
36
|
+
reader.read().then(flow, handleStreamError)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function flow({ done, value }: ReadableStreamReadResult<Uint8Array>): void | Promise<void> {
|
|
40
|
+
try {
|
|
41
|
+
if (done) {
|
|
42
|
+
writable.end()
|
|
43
|
+
} else if (!writable.write(value)) {
|
|
44
|
+
writable.once('drain', onDrain)
|
|
45
|
+
} else {
|
|
46
|
+
return reader.read().then(flow, handleStreamError)
|
|
47
|
+
}
|
|
48
|
+
} catch (e) {
|
|
49
|
+
handleStreamError(e)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function writeFromReadableStream(stream: ReadableStream<Uint8Array>, writable: Writable) {
|
|
55
|
+
if (stream.locked) {
|
|
56
|
+
throw new TypeError('ReadableStream is locked.')
|
|
57
|
+
} else if (writable.destroyed) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return writeFromReadableStreamDefaultReader(stream.getReader(), writable)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const buildOutgoingHttpHeaders = (
|
|
65
|
+
headers: Headers | HeadersInit | null | undefined,
|
|
66
|
+
defaultContentType: string | undefined
|
|
67
|
+
): OutgoingHttpHeaders => {
|
|
68
|
+
const res: OutgoingHttpHeaders = {}
|
|
69
|
+
if (!(headers instanceof Headers)) {
|
|
70
|
+
headers = new Headers(headers ?? undefined)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (headers.has('set-cookie')) {
|
|
74
|
+
const cookies = []
|
|
75
|
+
for (const [k, v] of headers) {
|
|
76
|
+
if (k === 'set-cookie') {
|
|
77
|
+
cookies.push(v)
|
|
78
|
+
} else {
|
|
79
|
+
res[k] = v
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (cookies.length > 0) {
|
|
83
|
+
res['set-cookie'] = cookies
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
for (const [k, v] of headers) {
|
|
87
|
+
res[k] = v
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (defaultContentType) {
|
|
92
|
+
res['content-type'] ??= defaultContentType
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return res
|
|
96
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is the minimal public interface for WebSocket that is compatible with `ws` (`@types/ws`)
|
|
3
|
+
* https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/ws/index.d.ts
|
|
4
|
+
*
|
|
5
|
+
* If you need more methods, copy the extra types over from the types file linked above.
|
|
6
|
+
* Don't import types from `ws` directly, as it will cause issues with users who have `skipLibCheck` enabled.
|
|
7
|
+
*/
|
|
8
|
+
import type { IncomingMessage } from 'node:http'
|
|
9
|
+
import type { Duplex } from 'node:stream'
|
|
10
|
+
|
|
11
|
+
type WSReadyState = 0 | 1 | 2 | 3
|
|
12
|
+
|
|
13
|
+
export type WebSocketData = string | ArrayBuffer | Uint8Array | readonly Uint8Array[]
|
|
14
|
+
|
|
15
|
+
export type WebSocketSendOptions = {
|
|
16
|
+
compress?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface WebSocketLike {
|
|
20
|
+
protocol: string
|
|
21
|
+
readyState: WSReadyState
|
|
22
|
+
close(code?: number, reason?: string): void
|
|
23
|
+
send(data: string | ArrayBuffer | ArrayBufferView, options?: WebSocketSendOptions): void
|
|
24
|
+
on(event: 'message', listener: (data: WebSocketData, isBinary: boolean) => void): this
|
|
25
|
+
on(event: 'close', listener: (code: number, reason: Uint8Array) => void): this
|
|
26
|
+
on(event: 'error', listener: (error: unknown) => void): this
|
|
27
|
+
off(event: 'message', listener: (data: WebSocketData, isBinary: boolean) => void): this
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface WebSocketServerLike {
|
|
31
|
+
options: {
|
|
32
|
+
noServer?: boolean
|
|
33
|
+
}
|
|
34
|
+
on(event: 'connection', listener: (ws: WebSocketLike, request: IncomingMessage) => void): this
|
|
35
|
+
on(event: 'headers', listener: (headers: string[]) => void): this
|
|
36
|
+
off(event: 'headers', listener: (headers: string[]) => void): this
|
|
37
|
+
emit(event: 'connection', ws: WebSocketLike, request: IncomingMessage): boolean
|
|
38
|
+
handleUpgrade(
|
|
39
|
+
request: IncomingMessage,
|
|
40
|
+
socket: Duplex,
|
|
41
|
+
head: Buffer,
|
|
42
|
+
callback: (ws: WebSocketLike) => void
|
|
43
|
+
): void
|
|
44
|
+
close(): void
|
|
45
|
+
}
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import type { UpgradeWebSocket, WSContext } from '../../../acryl-router/src/helpers/index.ts'
|
|
2
|
+
import { defineWebSocketHelper } from '../../../acryl-router/src/helpers/index.ts'
|
|
3
|
+
import type { IncomingMessage } from 'node:http'
|
|
4
|
+
import { STATUS_CODES } from 'node:http'
|
|
5
|
+
import type { Duplex } from 'node:stream'
|
|
6
|
+
import type { FetchCallback, ServerType } from './types.ts'
|
|
7
|
+
import type { WebSocketData, WebSocketLike, WebSocketServerLike } from './websocket-types.ts'
|
|
8
|
+
|
|
9
|
+
interface CloseEventInit extends EventInit {
|
|
10
|
+
code?: number
|
|
11
|
+
reason?: string
|
|
12
|
+
wasClean?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
|
17
|
+
*/
|
|
18
|
+
export const CloseEvent: typeof globalThis.CloseEvent =
|
|
19
|
+
globalThis.CloseEvent ??
|
|
20
|
+
class extends Event {
|
|
21
|
+
#eventInitDict
|
|
22
|
+
|
|
23
|
+
constructor(type: string, eventInitDict: CloseEventInit = {}) {
|
|
24
|
+
super(type, eventInitDict)
|
|
25
|
+
this.#eventInitDict = eventInitDict
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get wasClean(): boolean {
|
|
29
|
+
return this.#eventInitDict.wasClean ?? false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get code(): number {
|
|
33
|
+
return this.#eventInitDict.code ?? 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get reason(): string {
|
|
37
|
+
return this.#eventInitDict.reason ?? ''
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface ErrorEventInit extends EventInit {
|
|
42
|
+
message?: string
|
|
43
|
+
filename?: string
|
|
44
|
+
lineno?: number
|
|
45
|
+
colno?: number
|
|
46
|
+
error?: unknown
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Node.js has no global `ErrorEvent`, unlike `Event`/`MessageEvent`/`CloseEvent`.
|
|
51
|
+
* @link https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent
|
|
52
|
+
*/
|
|
53
|
+
export const ErrorEvent: typeof globalThis.ErrorEvent =
|
|
54
|
+
globalThis.ErrorEvent ??
|
|
55
|
+
class extends Event {
|
|
56
|
+
#eventInitDict
|
|
57
|
+
|
|
58
|
+
constructor(type: string, eventInitDict: ErrorEventInit = {}) {
|
|
59
|
+
super(type, eventInitDict)
|
|
60
|
+
this.#eventInitDict = eventInitDict
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get message(): string {
|
|
64
|
+
return this.#eventInitDict.message ?? ''
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get filename(): string {
|
|
68
|
+
return this.#eventInitDict.filename ?? ''
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get lineno(): number {
|
|
72
|
+
return this.#eventInitDict.lineno ?? 0
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get colno(): number {
|
|
76
|
+
return this.#eventInitDict.colno ?? 0
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get error(): unknown {
|
|
80
|
+
return this.#eventInitDict.error ?? null
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const generateConnectionSymbol = () => Symbol('connection')
|
|
85
|
+
|
|
86
|
+
type WaitForWebSocket = (
|
|
87
|
+
request: IncomingMessage,
|
|
88
|
+
connectionSymbol: symbol
|
|
89
|
+
) => Promise<WebSocketLike>
|
|
90
|
+
|
|
91
|
+
const CONNECTION_SYMBOL_KEY: unique symbol = Symbol('CONNECTION_SYMBOL_KEY')
|
|
92
|
+
const WAIT_FOR_WEBSOCKET_SYMBOL: unique symbol = Symbol('WAIT_FOR_WEBSOCKET_SYMBOL')
|
|
93
|
+
|
|
94
|
+
export type UpgradeBindings = {
|
|
95
|
+
incoming: IncomingMessage
|
|
96
|
+
outgoing: undefined
|
|
97
|
+
wss: WebSocketServerLike
|
|
98
|
+
[CONNECTION_SYMBOL_KEY]?: symbol
|
|
99
|
+
[WAIT_FOR_WEBSOCKET_SYMBOL]?: WaitForWebSocket
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type UpgradeWebSocketOptions = {
|
|
103
|
+
onError: (err: unknown) => void
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Hop-by-hop headers per RFC 9110 Section 7.6.1
|
|
107
|
+
// (https://www.rfc-editor.org/rfc/rfc9110.html#name-connection) plus
|
|
108
|
+
// `keep-alive` (commonly treated as hop-by-hop by HTTP implementations) and
|
|
109
|
+
// WebSocket handshake headers managed by `ws` itself. These must not be
|
|
110
|
+
// forwarded onto the upgrade response or the handshake will be corrupted.
|
|
111
|
+
const responseHeadersToSkip = new Set([
|
|
112
|
+
'connection',
|
|
113
|
+
'content-length',
|
|
114
|
+
'keep-alive',
|
|
115
|
+
'proxy-authenticate',
|
|
116
|
+
'proxy-authorization',
|
|
117
|
+
'te',
|
|
118
|
+
'trailer',
|
|
119
|
+
'transfer-encoding',
|
|
120
|
+
'upgrade',
|
|
121
|
+
'sec-websocket-accept',
|
|
122
|
+
'sec-websocket-extensions',
|
|
123
|
+
'sec-websocket-protocol',
|
|
124
|
+
])
|
|
125
|
+
|
|
126
|
+
const appendResponseHeaders = (headers: string[], responseHeaders?: Headers) => {
|
|
127
|
+
if (!responseHeaders) {
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
responseHeaders.forEach((value, key) => {
|
|
131
|
+
if (responseHeadersToSkip.has(key.toLowerCase())) {
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
headers.push(`${key}: ${value}`)
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const rejectUpgradeRequest = (socket: Duplex, status: number, responseHeaders?: Headers) => {
|
|
139
|
+
const responseLines = ['Connection: close', 'Content-Length: 0']
|
|
140
|
+
appendResponseHeaders(responseLines, responseHeaders)
|
|
141
|
+
|
|
142
|
+
socket.end(
|
|
143
|
+
`HTTP/1.1 ${status.toString()} ${STATUS_CODES[status] ?? ''}\r\n` +
|
|
144
|
+
`${responseLines.join('\r\n')}\r\n` +
|
|
145
|
+
'\r\n'
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const createUpgradeRequest = (request: IncomingMessage): Request => {
|
|
150
|
+
const protocol = (request.socket as { encrypted?: boolean }).encrypted ? 'https' : 'http'
|
|
151
|
+
const url = new URL(request.url ?? '/', `${protocol}://${request.headers.host ?? 'localhost'}`)
|
|
152
|
+
const headers = new Headers()
|
|
153
|
+
for (const key in request.headers) {
|
|
154
|
+
const value = request.headers[key]
|
|
155
|
+
if (!value) {
|
|
156
|
+
continue
|
|
157
|
+
}
|
|
158
|
+
headers.append(key, Array.isArray(value) ? value[0] : value)
|
|
159
|
+
}
|
|
160
|
+
return new Request(url, {
|
|
161
|
+
headers,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const setupWebSocket = (options: {
|
|
166
|
+
server: ServerType
|
|
167
|
+
fetchCallback: FetchCallback
|
|
168
|
+
wss: WebSocketServerLike
|
|
169
|
+
}): void => {
|
|
170
|
+
const { server, fetchCallback, wss } = options
|
|
171
|
+
|
|
172
|
+
const waiterMap = new Map<
|
|
173
|
+
IncomingMessage,
|
|
174
|
+
{
|
|
175
|
+
resolve: (ws: WebSocketLike) => void
|
|
176
|
+
reject: (reason: Error) => void
|
|
177
|
+
connectionSymbol: symbol
|
|
178
|
+
}
|
|
179
|
+
>()
|
|
180
|
+
|
|
181
|
+
wss.on('connection', (ws, request) => {
|
|
182
|
+
const waiter = waiterMap.get(request)
|
|
183
|
+
if (waiter) {
|
|
184
|
+
waiter.resolve(ws)
|
|
185
|
+
waiterMap.delete(request)
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
const rejectWaiter = (request: IncomingMessage) => {
|
|
190
|
+
const waiter = waiterMap.get(request)
|
|
191
|
+
if (waiter) {
|
|
192
|
+
waiterMap.delete(request)
|
|
193
|
+
waiter.reject(new Error('WebSocket handshake aborted'))
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const waitForWebSocket: WaitForWebSocket = (request, connectionSymbol) => {
|
|
198
|
+
return new Promise<WebSocketLike>((resolve, reject) => {
|
|
199
|
+
waiterMap.set(request, { resolve, reject, connectionSymbol })
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
server.on('upgrade', async (request, socket: Duplex, head) => {
|
|
204
|
+
if (request.headers.upgrade?.toLowerCase() !== 'websocket') {
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const env: UpgradeBindings = {
|
|
209
|
+
incoming: request,
|
|
210
|
+
outgoing: undefined,
|
|
211
|
+
wss,
|
|
212
|
+
[WAIT_FOR_WEBSOCKET_SYMBOL]: waitForWebSocket,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let status = 400
|
|
216
|
+
let responseHeaders: Headers | undefined
|
|
217
|
+
try {
|
|
218
|
+
const response = (await fetchCallback(
|
|
219
|
+
createUpgradeRequest(request),
|
|
220
|
+
env as unknown as Parameters<FetchCallback>[1]
|
|
221
|
+
)) as Response
|
|
222
|
+
if (response instanceof Response) {
|
|
223
|
+
status = response.status
|
|
224
|
+
responseHeaders = response.headers
|
|
225
|
+
}
|
|
226
|
+
} catch {
|
|
227
|
+
if (server.listenerCount('upgrade') === 1) {
|
|
228
|
+
rejectUpgradeRequest(socket, 500)
|
|
229
|
+
}
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const waiter = waiterMap.get(request)
|
|
234
|
+
|
|
235
|
+
if (!waiter || waiter.connectionSymbol !== env[CONNECTION_SYMBOL_KEY]) {
|
|
236
|
+
rejectWaiter(request)
|
|
237
|
+
if (server.listenerCount('upgrade') === 1) {
|
|
238
|
+
rejectUpgradeRequest(socket, status, responseHeaders)
|
|
239
|
+
}
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const addResponseHeaders = (headers: string[]) => {
|
|
244
|
+
appendResponseHeaders(headers, responseHeaders)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const reclaimWaiterOnClose = () => rejectWaiter(request)
|
|
248
|
+
socket.once('close', reclaimWaiterOnClose)
|
|
249
|
+
|
|
250
|
+
wss.on('headers', addResponseHeaders)
|
|
251
|
+
try {
|
|
252
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
253
|
+
socket.off('close', reclaimWaiterOnClose)
|
|
254
|
+
wss.emit('connection', ws, request)
|
|
255
|
+
})
|
|
256
|
+
} finally {
|
|
257
|
+
wss.off('headers', addResponseHeaders)
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
server.on('close', () => {
|
|
262
|
+
wss.close()
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export const upgradeWebSocket: UpgradeWebSocket<WebSocketLike, UpgradeWebSocketOptions> =
|
|
267
|
+
defineWebSocketHelper(async (c, events, options) => {
|
|
268
|
+
if (c.req.header('upgrade')?.toLowerCase() !== 'websocket') {
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const env = c.env as UpgradeBindings
|
|
273
|
+
const waitForWebSocket = env[WAIT_FOR_WEBSOCKET_SYMBOL]
|
|
274
|
+
|
|
275
|
+
if (!waitForWebSocket || !env.incoming) {
|
|
276
|
+
return new Response(null, { status: 500 })
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const connectionSymbol = generateConnectionSymbol()
|
|
280
|
+
env[CONNECTION_SYMBOL_KEY] = connectionSymbol
|
|
281
|
+
;(async () => {
|
|
282
|
+
let ws: WebSocketLike
|
|
283
|
+
try {
|
|
284
|
+
ws = await waitForWebSocket(env.incoming, connectionSymbol)
|
|
285
|
+
} catch {
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const messagesReceivedInStarting: [data: WebSocketData, isBinary: boolean][] = []
|
|
290
|
+
const bufferMessage = (data: WebSocketData, isBinary: boolean) => {
|
|
291
|
+
messagesReceivedInStarting.push([data, isBinary])
|
|
292
|
+
}
|
|
293
|
+
ws.on('message', bufferMessage)
|
|
294
|
+
|
|
295
|
+
// A plain object with the class's public shape: the class's private field is not part of the shape, so it is asserted once.
|
|
296
|
+
const shape: Pick<WSContext<WebSocketLike>, keyof WSContext<WebSocketLike>> = {
|
|
297
|
+
binaryType: 'arraybuffer',
|
|
298
|
+
close(code, reason) {
|
|
299
|
+
ws.close(code, reason)
|
|
300
|
+
},
|
|
301
|
+
protocol: ws.protocol,
|
|
302
|
+
raw: ws,
|
|
303
|
+
get readyState() {
|
|
304
|
+
return ws.readyState
|
|
305
|
+
},
|
|
306
|
+
send(source, opts) {
|
|
307
|
+
ws.send(source, {
|
|
308
|
+
compress: opts?.compress,
|
|
309
|
+
})
|
|
310
|
+
},
|
|
311
|
+
url: new URL(c.req.url),
|
|
312
|
+
}
|
|
313
|
+
const ctx = shape as WSContext<WebSocketLike>
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
events?.onOpen?.(new Event('open'), ctx)
|
|
317
|
+
} catch (e) {
|
|
318
|
+
;(options?.onError ?? console.error)(e)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const handleMessage = (data: WebSocketData, isBinary: boolean) => {
|
|
322
|
+
const datas = Array.isArray(data) ? data : [data]
|
|
323
|
+
for (const data of datas) {
|
|
324
|
+
try {
|
|
325
|
+
events?.onMessage?.(
|
|
326
|
+
new MessageEvent('message', {
|
|
327
|
+
data: isBinary
|
|
328
|
+
? data instanceof ArrayBuffer
|
|
329
|
+
? data
|
|
330
|
+
: data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)
|
|
331
|
+
: typeof data === 'string'
|
|
332
|
+
? data
|
|
333
|
+
: Buffer.from(data).toString('utf-8'),
|
|
334
|
+
}),
|
|
335
|
+
ctx
|
|
336
|
+
)
|
|
337
|
+
} catch (e) {
|
|
338
|
+
;(options?.onError ?? console.error)(e)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
ws.off('message', bufferMessage)
|
|
344
|
+
for (const message of messagesReceivedInStarting) {
|
|
345
|
+
handleMessage(...message)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
ws.on('message', (data, isBinary) => {
|
|
349
|
+
handleMessage(data, isBinary)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
ws.on('close', (code, reason) => {
|
|
353
|
+
try {
|
|
354
|
+
events?.onClose?.(new CloseEvent('close', { code, reason: reason.toString() }), ctx)
|
|
355
|
+
} catch (e) {
|
|
356
|
+
;(options?.onError ?? console.error)(e)
|
|
357
|
+
}
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
ws.on('error', (error) => {
|
|
361
|
+
try {
|
|
362
|
+
events?.onError?.(
|
|
363
|
+
new ErrorEvent('error', {
|
|
364
|
+
error,
|
|
365
|
+
}),
|
|
366
|
+
ctx
|
|
367
|
+
)
|
|
368
|
+
} catch (e) {
|
|
369
|
+
;(options?.onError ?? console.error)(e)
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
})()
|
|
373
|
+
|
|
374
|
+
return new Response()
|
|
375
|
+
})
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The framework's standalone HTTP adapter (spec 046, ported in spec 047), the standalone counterpart of the Host adapter. It owns one
|
|
3
|
+
* `node:http` server built by the ported adapter in `src/adapter/` and hands every request to `ctx.router.fetch`, with the socket's peer
|
|
4
|
+
* address and local port as connection facts.
|
|
5
|
+
* Use it in an app that runs without the ACRYL Host; inside the Host, `acryl-router-http` mounts prefixes on the Host's own web server.
|
|
6
|
+
* One adapter per runtime: a plugin keeps asking `ctx.inject(['routerHttp'], ...)` for a mount, which simply never fires here, because this
|
|
7
|
+
* adapter serves the whole router.
|
|
8
|
+
*
|
|
9
|
+
* A configured port that is taken is followed by the next free one, at most `scan` further ports; port 0 lets the system choose. Disposal ends
|
|
10
|
+
* open streams, closes every connection and returns once the port is free.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { Server } from 'node:http'
|
|
14
|
+
import type { AddressInfo } from 'node:net'
|
|
15
|
+
import { Context, Service } from '@deepseek-ai/cordis'
|
|
16
|
+
import z from '@deepseek-ai/schemastery'
|
|
17
|
+
import type {} from '../../acryl-router/src/index.ts'
|
|
18
|
+
import { Request as AdapterRequest } from './adapter/request.ts'
|
|
19
|
+
import { createAdaptorServer } from './adapter/server.ts'
|
|
20
|
+
|
|
21
|
+
export { serveStatic, type ServeStaticOptions } from './adapter/serve-static.ts'
|
|
22
|
+
export { RequestError } from './adapter/request.ts'
|
|
23
|
+
|
|
24
|
+
/** Stable Cordis plugin name, equal to the package name and the Loader row id. */
|
|
25
|
+
export const name = 'acryl-node-server'
|
|
26
|
+
export const inject = ['router']
|
|
27
|
+
|
|
28
|
+
export interface Config {
|
|
29
|
+
hostname: string
|
|
30
|
+
port: number
|
|
31
|
+
scan: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Every field has a default, so a caller passes only what it changes: `ctx.plugin(plugin, { ... })` takes the partial shape. */
|
|
35
|
+
export const Config: z<Partial<Config>, Config> = z.object({
|
|
36
|
+
hostname: z.string().default('127.0.0.1').description('Address to bind. Loopback by default; binding elsewhere exposes the app to the network.'),
|
|
37
|
+
port: z.number().min(0).max(65535).default(0).description('Port to listen on; 0 lets the system choose. A taken port is followed by the next free one.'),
|
|
38
|
+
scan: z.number().min(0).default(20).description('How many further ports to try when the configured one is taken.'),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
declare module '@deepseek-ai/cordis' {
|
|
42
|
+
interface Context {
|
|
43
|
+
nodeServer: NodeServer
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class NodeServer extends Service {
|
|
48
|
+
readonly hostname: string
|
|
49
|
+
readonly config: Config
|
|
50
|
+
private readonly server: Server
|
|
51
|
+
private bound = 0
|
|
52
|
+
|
|
53
|
+
constructor(ctx: Context, config: Config) {
|
|
54
|
+
super(ctx, 'nodeServer')
|
|
55
|
+
this.config = config
|
|
56
|
+
this.hostname = config.hostname
|
|
57
|
+
const router = ctx.router
|
|
58
|
+
// The handler sees the router's bindings (`c.env.connection`), not the raw `incoming`/`outgoing` pair. The global `Request` and
|
|
59
|
+
// `Response` stay the platform's, because this adapter shares its process with the rest of the app; so the adapter's lightweight
|
|
60
|
+
// request is made a real one (its signal still aborts when the client goes away) before the router re-creates it with its own signal.
|
|
61
|
+
this.server = createAdaptorServer({
|
|
62
|
+
fetch: (request, { incoming }) => router.fetch(new AdapterRequest(request), { remoteAddress: incoming.socket.remoteAddress, localPort: incoming.socket.localPort }),
|
|
63
|
+
hostname: config.hostname,
|
|
64
|
+
overrideGlobalObjects: false,
|
|
65
|
+
}) as Server
|
|
66
|
+
ctx.effect(() => async () => {
|
|
67
|
+
this.server.closeAllConnections()
|
|
68
|
+
if (this.server.listening) await new Promise<void>(resolve => { this.server.close(() => { resolve() }) })
|
|
69
|
+
}, 'acryl-node-server: close listener and open connections')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The port in use, 0 before the server listens. */
|
|
73
|
+
get port(): number {
|
|
74
|
+
return this.bound
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The address clients use. */
|
|
78
|
+
get url(): string {
|
|
79
|
+
return `http://${this.hostname.includes(':') ? `[${this.hostname}]` : this.hostname}:${String(this.bound)}`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Bind, scanning past taken ports. */
|
|
83
|
+
async listen(): Promise<void> {
|
|
84
|
+
const { port, scan, hostname } = this.config
|
|
85
|
+
const last = port === 0 ? 0 : Math.min(65535, port + scan)
|
|
86
|
+
for (let candidate = port; candidate <= last; candidate += 1) {
|
|
87
|
+
try {
|
|
88
|
+
await new Promise<void>((resolve, reject) => {
|
|
89
|
+
const onError = (error: Error): void => { reject(error) }
|
|
90
|
+
this.server.once('error', onError)
|
|
91
|
+
this.server.listen(candidate, hostname, () => { this.server.off('error', onError); resolve() })
|
|
92
|
+
})
|
|
93
|
+
this.bound = (this.server.address() as AddressInfo).port
|
|
94
|
+
return
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if ((error as NodeJS.ErrnoException).code !== 'EADDRINUSE') throw error
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
throw new Error(`acryl-node-server: no free port from ${String(port)} to ${String(last)} on ${hostname}`)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Loads once the server listens, so a plugin that injects `nodeServer` can rely on `url`. A failure to bind fails the plugin. */
|
|
104
|
+
export async function apply(ctx: Context, config: Partial<Config> = {}): Promise<void> {
|
|
105
|
+
const server = new NodeServer(ctx, { hostname: '127.0.0.1', port: 0, scan: 20, ...config })
|
|
106
|
+
await server.listen()
|
|
107
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Response as PonyfillResponse } from '@whatwg-node/fetch'
|
|
2
|
+
import { App } from '../../../acryl-router/src/index.ts'
|
|
3
|
+
|
|
4
|
+
export const app = new App()
|
|
5
|
+
|
|
6
|
+
app.get('/', (c) => c.text('Hello! Node!'))
|
|
7
|
+
app.get('/url', (c) => c.text(c.req.url))
|
|
8
|
+
|
|
9
|
+
app.get('/posts', (c) => {
|
|
10
|
+
return c.text(`Page ${c.req.query('page')}`)
|
|
11
|
+
})
|
|
12
|
+
app.get('/user-agent', (c) => {
|
|
13
|
+
return c.text(c.req.header('user-agent') as string)
|
|
14
|
+
})
|
|
15
|
+
app.post('/posts', (c) => {
|
|
16
|
+
return c.redirect('/posts')
|
|
17
|
+
})
|
|
18
|
+
app.post('/body-consumed', async (c) => {
|
|
19
|
+
return c.text(`Body length: ${(await c.req.text()).length}`)
|
|
20
|
+
})
|
|
21
|
+
app.post('/no-body-consumed', (c) => {
|
|
22
|
+
if (!c.req.raw.body) {
|
|
23
|
+
// force create new request object
|
|
24
|
+
throw new Error('No body consumed')
|
|
25
|
+
}
|
|
26
|
+
return c.text('No body consumed')
|
|
27
|
+
})
|
|
28
|
+
app.post('/body-cancelled', (c) => {
|
|
29
|
+
if (!c.req.raw.body) {
|
|
30
|
+
// force create new request object
|
|
31
|
+
throw new Error('No body consumed')
|
|
32
|
+
}
|
|
33
|
+
c.req.raw.body.cancel()
|
|
34
|
+
return c.text('Body cancelled')
|
|
35
|
+
})
|
|
36
|
+
app.post('/partially-consumed', async (c) => {
|
|
37
|
+
if (!c.req.raw.body) {
|
|
38
|
+
// force create new request object
|
|
39
|
+
throw new Error('No body consumed')
|
|
40
|
+
}
|
|
41
|
+
const reader = c.req.raw.body.getReader()
|
|
42
|
+
await reader.read() // read only one chunk
|
|
43
|
+
return c.text('Partially consumed')
|
|
44
|
+
})
|
|
45
|
+
app.post('/partially-consumed-and-cancelled', async (c) => {
|
|
46
|
+
if (!c.req.raw.body) {
|
|
47
|
+
// force create new request object
|
|
48
|
+
throw new Error('No body consumed')
|
|
49
|
+
}
|
|
50
|
+
const reader = c.req.raw.body.getReader()
|
|
51
|
+
await reader.read() // read only one chunk
|
|
52
|
+
reader.cancel()
|
|
53
|
+
return c.text('Partially consumed and cancelled')
|
|
54
|
+
})
|
|
55
|
+
app.post('/early-413', (c) => {
|
|
56
|
+
if (!c.req.raw.body) {
|
|
57
|
+
// force create new request object
|
|
58
|
+
throw new Error('No body consumed')
|
|
59
|
+
}
|
|
60
|
+
return c.text('Payload Too Large', 413)
|
|
61
|
+
})
|
|
62
|
+
app.delete('/posts/:id', (c) => {
|
|
63
|
+
return c.text(`DELETE ${c.req.param('id')}`)
|
|
64
|
+
})
|
|
65
|
+
// @ts-expect-error the response is string
|
|
66
|
+
app.get('/invalid', () => {
|
|
67
|
+
return '<h1>HTML</h1>'
|
|
68
|
+
})
|
|
69
|
+
app.get('/ponyfill', () => {
|
|
70
|
+
return new PonyfillResponse('Pony')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
app.on('trace', '/', (c) => {
|
|
74
|
+
const headers = c.req.raw.headers // build new request object
|
|
75
|
+
return c.text(`headers: ${JSON.stringify(headers)}`)
|
|
76
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This is plain.txt
|
|
Binary file
|