acrylblends 0.3.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 +6 -1
- package/dist/index.js +28 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/plugins/acryl-inspect/plugin.json +2 -1
- package/plugins/acryl-inspect/src/report.ts +16 -13
- package/plugins/acryl-inspect/src/service.ts +9 -7
- package/plugins/acryl-inspect/tests/report.spec.ts +7 -8
- package/plugins/acryl-inspect/tests/service.spec.ts +11 -10
- package/plugins/acryl-inspect/upstream.json +2 -2
- package/plugins/acryl-node-server/LICENSE +26 -0
- package/plugins/acryl-node-server/plugin.json +2 -1
- package/plugins/acryl-node-server/src/adapter/conninfo.ts +23 -0
- package/plugins/acryl-node-server/src/adapter/early-hints.ts +55 -0
- package/plugins/acryl-node-server/src/adapter/error.ts +11 -0
- package/plugins/acryl-node-server/src/adapter/headers.ts +249 -0
- package/plugins/acryl-node-server/src/adapter/index.ts +6 -0
- package/plugins/acryl-node-server/src/adapter/listener.ts +540 -0
- package/plugins/acryl-node-server/src/adapter/request.ts +797 -0
- package/plugins/acryl-node-server/src/adapter/response.ts +186 -0
- package/plugins/acryl-node-server/src/adapter/serve-static.ts +238 -0
- package/plugins/acryl-node-server/src/adapter/server.ts +36 -0
- package/plugins/acryl-node-server/src/adapter/types.ts +82 -0
- package/plugins/acryl-node-server/src/adapter/url.ts +42 -0
- package/plugins/acryl-node-server/src/adapter/utils/response/constants.ts +1 -0
- package/plugins/acryl-node-server/src/adapter/utils/response.ts +4 -0
- package/plugins/acryl-node-server/src/adapter/utils/stream.ts +88 -0
- package/plugins/acryl-node-server/src/adapter/utils.ts +96 -0
- package/plugins/acryl-node-server/src/adapter/websocket-types.ts +45 -0
- package/plugins/acryl-node-server/src/adapter/websocket.ts +375 -0
- package/plugins/acryl-node-server/src/index.ts +19 -6
- package/plugins/acryl-node-server/tests/adapter/app.ts +76 -0
- package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +5 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +1 -0
- package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +34 -0
- package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +248 -0
- package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +457 -0
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +23 -0
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +27 -0
- package/plugins/acryl-node-server/tests/adapter/headers.test.ts +252 -0
- package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +190 -0
- package/plugins/acryl-node-server/tests/adapter/listener.test.ts +727 -0
- package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +189 -0
- package/plugins/acryl-node-server/tests/adapter/request.test.ts +1230 -0
- package/plugins/acryl-node-server/tests/adapter/response.test.ts +285 -0
- package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +849 -0
- package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +790 -0
- package/plugins/acryl-node-server/tests/adapter/server.test.ts +1302 -0
- package/plugins/acryl-node-server/tests/adapter/setup.ts +16 -0
- package/plugins/acryl-node-server/tests/adapter/url.test.ts +76 -0
- package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +23 -0
- package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +75 -0
- package/plugins/acryl-node-server/tests/adapter/utils.test.ts +235 -0
- package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +260 -0
- package/plugins/acryl-node-server/tests/server.spec.ts +44 -8
- package/plugins/acryl-node-server/upstream.json +3 -3
- package/plugins/acryl-router/LICENSE +26 -0
- package/plugins/acryl-router/plugin.json +2 -1
- package/plugins/acryl-router/src/client.ts +2 -0
- package/plugins/acryl-router/src/connection.ts +42 -0
- package/plugins/acryl-router/src/core/app-base.ts +544 -0
- package/plugins/acryl-router/src/core/app.test.ts +3833 -0
- package/plugins/acryl-router/src/core/app.ts +34 -0
- package/plugins/acryl-router/src/core/client/client.test.ts +2313 -0
- package/plugins/acryl-router/src/core/client/client.ts +249 -0
- package/plugins/acryl-router/src/core/client/fetch-result-please.ts +95 -0
- package/plugins/acryl-router/src/core/client/index.ts +17 -0
- package/plugins/acryl-router/src/core/client/types.test.ts +149 -0
- package/plugins/acryl-router/src/core/client/types.ts +393 -0
- package/plugins/acryl-router/src/core/client/utils.test.ts +405 -0
- package/plugins/acryl-router/src/core/client/utils.ts +117 -0
- package/plugins/acryl-router/src/core/compose.test.ts +751 -0
- package/plugins/acryl-router/src/core/compose.ts +73 -0
- package/plugins/acryl-router/src/core/context.test.ts +629 -0
- package/plugins/acryl-router/src/core/context.ts +781 -0
- package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +344 -0
- package/plugins/acryl-router/src/core/helper/accepts/accepts.ts +96 -0
- package/plugins/acryl-router/src/core/helper/accepts/index.ts +6 -0
- package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +55 -0
- package/plugins/acryl-router/src/core/helper/adapter/index.ts +90 -0
- package/plugins/acryl-router/src/core/helper/conninfo/index.ts +6 -0
- package/plugins/acryl-router/src/core/helper/conninfo/types.ts +45 -0
- package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +510 -0
- package/plugins/acryl-router/src/core/helper/cookie/index.ts +146 -0
- package/plugins/acryl-router/src/core/helper/factory/index.test.ts +434 -0
- package/plugins/acryl-router/src/core/helper/factory/index.ts +375 -0
- package/plugins/acryl-router/src/core/helper/html/index.test.ts +78 -0
- package/plugins/acryl-router/src/core/helper/html/index.ts +57 -0
- package/plugins/acryl-router/src/core/helper/route/index.test.ts +259 -0
- package/plugins/acryl-router/src/core/helper/route/index.ts +144 -0
- package/plugins/acryl-router/src/core/helper/streaming/index.ts +9 -0
- package/plugins/acryl-router/src/core/helper/streaming/sse.ts +101 -0
- package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +128 -0
- package/plugins/acryl-router/src/core/helper/streaming/stream.ts +45 -0
- package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +34 -0
- package/plugins/acryl-router/src/core/helper/streaming/text.ts +15 -0
- package/plugins/acryl-router/src/core/helper/streaming/utils.ts +11 -0
- package/plugins/acryl-router/src/core/helper/testing/index.test.ts +42 -0
- package/plugins/acryl-router/src/core/helper/testing/index.ts +28 -0
- package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +124 -0
- package/plugins/acryl-router/src/core/helper/websocket/index.ts +140 -0
- package/plugins/acryl-router/src/core/http-exception.test.ts +45 -0
- package/plugins/acryl-router/src/core/http-exception.ts +77 -0
- package/plugins/acryl-router/src/core/index.ts +53 -0
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +434 -0
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.ts +152 -0
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +1018 -0
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.ts +220 -0
- package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +273 -0
- package/plugins/acryl-router/src/core/middleware/body-limit/index.ts +110 -0
- package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +355 -0
- package/plugins/acryl-router/src/core/middleware/combine/index.ts +171 -0
- package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +535 -0
- package/plugins/acryl-router/src/core/middleware/compress/index.ts +142 -0
- package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +43 -0
- package/plugins/acryl-router/src/core/middleware/context-storage/index.ts +58 -0
- package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +532 -0
- package/plugins/acryl-router/src/core/middleware/cors/index.ts +163 -0
- package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +534 -0
- package/plugins/acryl-router/src/core/middleware/csrf/index.ts +150 -0
- package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +21 -0
- package/plugins/acryl-router/src/core/middleware/etag/digest.ts +95 -0
- package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +503 -0
- package/plugins/acryl-router/src/core/middleware/etag/index.ts +135 -0
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +217 -0
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.ts +278 -0
- package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +1135 -0
- package/plugins/acryl-router/src/core/middleware/jwk/index.ts +1 -0
- package/plugins/acryl-router/src/core/middleware/jwk/jwk.ts +195 -0
- package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +48 -0
- package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +802 -0
- package/plugins/acryl-router/src/core/middleware/jwt/index.ts +9 -0
- package/plugins/acryl-router/src/core/middleware/jwt/jwt.ts +189 -0
- package/plugins/acryl-router/src/core/middleware/language/index.test.ts +552 -0
- package/plugins/acryl-router/src/core/middleware/language/index.ts +12 -0
- package/plugins/acryl-router/src/core/middleware/language/language.ts +314 -0
- package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +204 -0
- package/plugins/acryl-router/src/core/middleware/logger/index.ts +95 -0
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +292 -0
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.ts +141 -0
- package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +221 -0
- package/plugins/acryl-router/src/core/middleware/method-override/index.ts +147 -0
- package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +37 -0
- package/plugins/acryl-router/src/core/middleware/powered-by/index.ts +35 -0
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +218 -0
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.ts +67 -0
- package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +168 -0
- package/plugins/acryl-router/src/core/middleware/request-id/index.ts +7 -0
- package/plugins/acryl-router/src/core/middleware/request-id/request-id.ts +59 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +671 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.ts +8 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/permissions-policy.ts +101 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/secure-headers.ts +340 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +450 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/index.ts +135 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +50 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/path.ts +25 -0
- package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +70 -0
- package/plugins/acryl-router/src/core/middleware/timeout/index.ts +58 -0
- package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +310 -0
- package/plugins/acryl-router/src/core/middleware/timing/index.ts +8 -0
- package/plugins/acryl-router/src/core/middleware/timing/timing.ts +256 -0
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +381 -0
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.ts +156 -0
- package/plugins/acryl-router/src/core/request/constants.ts +1 -0
- package/plugins/acryl-router/src/core/request.test.ts +751 -0
- package/plugins/acryl-router/src/core/request.ts +502 -0
- package/plugins/acryl-router/src/core/router/common.case.test.ts +1132 -0
- package/plugins/acryl-router/src/core/router/linear-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +80 -0
- package/plugins/acryl-router/src/core/router/linear-router/router.ts +152 -0
- package/plugins/acryl-router/src/core/router/pattern-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +46 -0
- package/plugins/acryl-router/src/core/router/pattern-router/router.ts +54 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/index.ts +7 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.ts +33 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/node.ts +170 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +154 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.ts +165 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +265 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.ts +174 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/trie.ts +84 -0
- package/plugins/acryl-router/src/core/router/smart-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +13 -0
- package/plugins/acryl-router/src/core/router/smart-router/router.ts +70 -0
- package/plugins/acryl-router/src/core/router/trie-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +896 -0
- package/plugins/acryl-router/src/core/router/trie-router/node.ts +221 -0
- package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +8 -0
- package/plugins/acryl-router/src/core/router/trie-router/router.ts +18 -0
- package/plugins/acryl-router/src/core/router/utils.ts +1 -0
- package/plugins/acryl-router/src/core/router.ts +103 -0
- package/plugins/acryl-router/src/core/types.test.ts +4134 -0
- package/plugins/acryl-router/src/core/types.ts +2782 -0
- package/plugins/acryl-router/src/core/utils/accept.test.ts +299 -0
- package/plugins/acryl-router/src/core/utils/accept.ts +268 -0
- package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +56 -0
- package/plugins/acryl-router/src/core/utils/basic-auth.ts +26 -0
- package/plugins/acryl-router/src/core/utils/body.test.ts +496 -0
- package/plugins/acryl-router/src/core/utils/body.ts +263 -0
- package/plugins/acryl-router/src/core/utils/buffer.test.ts +147 -0
- package/plugins/acryl-router/src/core/utils/buffer.ts +117 -0
- package/plugins/acryl-router/src/core/utils/color.test.ts +23 -0
- package/plugins/acryl-router/src/core/utils/color.ts +56 -0
- package/plugins/acryl-router/src/core/utils/compress.ts +10 -0
- package/plugins/acryl-router/src/core/utils/concurrent.test.ts +69 -0
- package/plugins/acryl-router/src/core/utils/concurrent.ts +55 -0
- package/plugins/acryl-router/src/core/utils/constants.ts +4 -0
- package/plugins/acryl-router/src/core/utils/cookie.test.ts +460 -0
- package/plugins/acryl-router/src/core/utils/cookie.ts +281 -0
- package/plugins/acryl-router/src/core/utils/crypto.test.ts +43 -0
- package/plugins/acryl-router/src/core/utils/crypto.ts +58 -0
- package/plugins/acryl-router/src/core/utils/encode.test.ts +61 -0
- package/plugins/acryl-router/src/core/utils/encode.ts +34 -0
- package/plugins/acryl-router/src/core/utils/filepath.test.ts +78 -0
- package/plugins/acryl-router/src/core/utils/filepath.ts +60 -0
- package/plugins/acryl-router/src/core/utils/handler.ts +15 -0
- package/plugins/acryl-router/src/core/utils/headers.ts +355 -0
- package/plugins/acryl-router/src/core/utils/html.test.ts +16 -0
- package/plugins/acryl-router/src/core/utils/html.ts +182 -0
- package/plugins/acryl-router/src/core/utils/http-status.ts +72 -0
- package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +149 -0
- package/plugins/acryl-router/src/core/utils/ipaddr.ts +374 -0
- package/plugins/acryl-router/src/core/utils/jwt/index.ts +7 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +86 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwa.ts +38 -0
- package/plugins/acryl-router/src/core/utils/jwt/jws.ts +235 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +1788 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwt.ts +292 -0
- package/plugins/acryl-router/src/core/utils/jwt/types.ts +157 -0
- package/plugins/acryl-router/src/core/utils/jwt/utf8.ts +7 -0
- package/plugins/acryl-router/src/core/utils/mime.test.ts +64 -0
- package/plugins/acryl-router/src/core/utils/mime.ts +94 -0
- package/plugins/acryl-router/src/core/utils/stream.test.ts +212 -0
- package/plugins/acryl-router/src/core/utils/stream.ts +107 -0
- package/plugins/acryl-router/src/core/utils/types.test.ts +295 -0
- package/plugins/acryl-router/src/core/utils/types.ts +123 -0
- package/plugins/acryl-router/src/core/utils/url.test.ts +384 -0
- package/plugins/acryl-router/src/core/utils/url.ts +337 -0
- package/plugins/acryl-router/src/core/validator/index.ts +8 -0
- package/plugins/acryl-router/src/core/validator/utils.test.ts +82 -0
- package/plugins/acryl-router/src/core/validator/utils.ts +69 -0
- package/plugins/acryl-router/src/core/validator/validator.test.ts +1488 -0
- package/plugins/acryl-router/src/core/validator/validator.ts +185 -0
- package/plugins/acryl-router/src/helpers/index.ts +13 -3
- package/plugins/acryl-router/src/helpers/schema-validator.ts +35 -0
- package/plugins/acryl-router/src/index.ts +6 -6
- package/plugins/acryl-router/src/middleware/index.ts +25 -6
- package/plugins/acryl-router/src/middleware/loopback-only.ts +19 -13
- package/plugins/acryl-router/src/service.ts +256 -64
- package/plugins/acryl-router/tests/docs-examples.spec.ts +152 -0
- package/plugins/acryl-router/tests/live-install.spec.ts +2 -2
- package/plugins/acryl-router/tests/loader.spec.ts +3 -2
- package/plugins/acryl-router/tests/service.spec.ts +264 -66
- package/plugins/acryl-router/tests/shadow-state.spec.ts +79 -0
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/plugin.json +2 -1
- package/plugins/acryl-router-http/src/bridge.ts +3 -1
- package/plugins/acryl-router-http/tests/adapter.spec.ts +16 -12
- package/plugins/acryl-router-http/tests/surfaces.spec.ts +12 -10
- package/plugins/acryl-router-http/upstream.json +3 -3
- package/plugins/acryl-state/NOTICE +30 -0
- package/plugins/acryl-state/plugin.json +1 -1
- package/plugins/acryl-state/src/cell.ts +1 -1
- package/plugins/acryl-state/src/service.ts +33 -15
- package/plugins/acryl-state/upstream.json +6 -4
- package/plugins/builder/script.ts +4 -7
- package/plugins/extensions/index.ts +2 -2
- package/plugins/extensions/resolve.ts +28 -3
- package/plugins/openai/prompt.ts +9 -7
- package/plugins/tsconfig.json +5 -4
- package/templates/agent/src/routes.ts +37 -27
- package/templates/agent/test/model.test.ts +2 -1
- package/templates/agent/tsconfig.json +8 -5
- package/templates/blank/src/routes.ts +6 -4
- package/templates/blank/tsconfig.json +2 -4
- package/templates/todo/src/page.ts +1 -1
- package/templates/todo/src/todo.ts +56 -29
- package/templates/todo/tsconfig.json +2 -4
- package/templates/todo/upstream.json +2 -2
- package/plugins/acryl-router/src/compose.ts +0 -21
- package/plugins/acryl-router/src/context.ts +0 -59
- package/plugins/acryl-router/src/helpers/read-body.ts +0 -59
- package/plugins/acryl-router/src/helpers/sse.ts +0 -83
- package/plugins/acryl-router/src/helpers/validate.ts +0 -44
- package/plugins/acryl-router/src/http-error.ts +0 -21
- package/plugins/acryl-router/src/middleware/body-limit.ts +0 -25
- package/plugins/acryl-router/src/middleware/cors.ts +0 -46
- package/plugins/acryl-router/src/middleware/logger.ts +0 -21
- package/plugins/acryl-router/src/middleware/request-id.ts +0 -24
- package/plugins/acryl-router/src/middleware/secure-headers.ts +0 -23
- package/plugins/acryl-router/src/pattern.ts +0 -89
- package/plugins/acryl-router/src/registrar.ts +0 -90
- package/plugins/acryl-router/src/table.ts +0 -156
- package/plugins/acryl-router/src/types.ts +0 -73
- package/plugins/acryl-router/src/util.ts +0 -23
- package/plugins/acryl-router/tests/api.spec.ts +0 -223
- package/plugins/acryl-router/tests/conformance.ts +0 -170
- package/plugins/acryl-router/tests/helpers.spec.ts +0 -172
- package/plugins/acryl-router/tests/middleware.spec.ts +0 -183
- package/plugins/acryl-router/tests/table.spec.ts +0 -47
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
// Define lightweight pseudo Response class and replace global.Response with it.
|
|
3
|
+
|
|
4
|
+
import type { OutgoingHttpHeaders } from 'node:http'
|
|
5
|
+
|
|
6
|
+
export const defaultContentType = 'text/plain; charset=UTF-8'
|
|
7
|
+
|
|
8
|
+
const responseCache = Symbol('responseCache')
|
|
9
|
+
const getResponseCache = Symbol('getResponseCache')
|
|
10
|
+
export const cacheKey = Symbol('cache')
|
|
11
|
+
|
|
12
|
+
export type InternalCache = [
|
|
13
|
+
number,
|
|
14
|
+
string | ReadableStream | null,
|
|
15
|
+
Record<string, string> | [string, string][] | Headers | OutgoingHttpHeaders | undefined,
|
|
16
|
+
]
|
|
17
|
+
interface LightResponse {
|
|
18
|
+
[responseCache]?: globalThis.Response
|
|
19
|
+
[cacheKey]?: InternalCache
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const GlobalResponse = global.Response
|
|
23
|
+
export class Response {
|
|
24
|
+
#body?: BodyInit | null
|
|
25
|
+
#init?: ResponseInit;
|
|
26
|
+
|
|
27
|
+
[getResponseCache](): globalThis.Response {
|
|
28
|
+
// If `cacheKey` has been populated with a live `Headers` instance, the
|
|
29
|
+
// user (or middleware) may have mutated it after construction. Use those
|
|
30
|
+
// headers so the GlobalResponse reflects the current state.
|
|
31
|
+
const cache = (this as LightResponse)[cacheKey]
|
|
32
|
+
const liveHeaders = cache && cache[2] instanceof Headers ? cache[2] : undefined
|
|
33
|
+
delete (this as LightResponse)[cacheKey]
|
|
34
|
+
return ((this as LightResponse)[responseCache] ||= new GlobalResponse(
|
|
35
|
+
this.#body,
|
|
36
|
+
liveHeaders
|
|
37
|
+
? {
|
|
38
|
+
status: this.#init?.status,
|
|
39
|
+
statusText: this.#init?.statusText,
|
|
40
|
+
headers: liveHeaders,
|
|
41
|
+
}
|
|
42
|
+
: this.#init
|
|
43
|
+
))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
constructor(body?: BodyInit | null, init?: ResponseInit) {
|
|
47
|
+
let headers: HeadersInit | undefined
|
|
48
|
+
this.#body = body
|
|
49
|
+
if (init instanceof GlobalResponse) {
|
|
50
|
+
const cachedGlobalResponse = (init as any)[responseCache]
|
|
51
|
+
if (cachedGlobalResponse) {
|
|
52
|
+
this.#init = cachedGlobalResponse
|
|
53
|
+
// instantiate GlobalResponse cache and this object always returns value from global.Response
|
|
54
|
+
this[getResponseCache]()
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
this.#init = init instanceof Response ? init.#init : init
|
|
58
|
+
headers = new Headers(init.headers)
|
|
59
|
+
} else {
|
|
60
|
+
this.#init = init
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (
|
|
64
|
+
body == null ||
|
|
65
|
+
typeof body === 'string' ||
|
|
66
|
+
typeof (body as ReadableStream)?.getReader !== 'undefined' ||
|
|
67
|
+
body instanceof Blob ||
|
|
68
|
+
body instanceof Uint8Array
|
|
69
|
+
) {
|
|
70
|
+
;(this as any)[cacheKey] = [init?.status || 200, body ?? null, headers || init?.headers]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get headers(): Headers {
|
|
75
|
+
const cache = (this as LightResponse)[cacheKey] as InternalCache
|
|
76
|
+
if (cache) {
|
|
77
|
+
if (!(cache[2] instanceof Headers)) {
|
|
78
|
+
cache[2] = new Headers(
|
|
79
|
+
(cache[2] ||
|
|
80
|
+
(cache[1] === null ? undefined : { 'content-type': defaultContentType })) as HeadersInit
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
return cache[2]
|
|
84
|
+
}
|
|
85
|
+
return this[getResponseCache]().headers
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get status() {
|
|
89
|
+
return (
|
|
90
|
+
((this as LightResponse)[cacheKey] as InternalCache | undefined)?.[0] ??
|
|
91
|
+
this[getResponseCache]().status
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get ok() {
|
|
96
|
+
const status = this.status
|
|
97
|
+
return status >= 200 && status < 300
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
;['body', 'bodyUsed', 'redirected', 'statusText', 'trailers', 'type', 'url'].forEach((k) => {
|
|
101
|
+
Object.defineProperty(Response.prototype, k, {
|
|
102
|
+
get() {
|
|
103
|
+
return this[getResponseCache]()[k]
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
;['arrayBuffer', 'blob', 'clone', 'formData', 'json', 'text'].forEach((k) => {
|
|
108
|
+
Object.defineProperty(Response.prototype, k, {
|
|
109
|
+
value: function () {
|
|
110
|
+
return this[getResponseCache]()[k]()
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
Object.defineProperty(Response.prototype, Symbol.for('nodejs.util.inspect.custom'), {
|
|
116
|
+
value: function (depth: number, options: object, inspectFn: Function) {
|
|
117
|
+
const props: Record<string, unknown> = {
|
|
118
|
+
status: this.status,
|
|
119
|
+
headers: this.headers,
|
|
120
|
+
ok: this.ok,
|
|
121
|
+
nativeResponse: (this as LightResponse)[responseCache],
|
|
122
|
+
}
|
|
123
|
+
return `Response (lightweight) ${inspectFn(props, { ...options, depth: depth == null ? null : depth - 1 })}`
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
Object.setPrototypeOf(Response, GlobalResponse)
|
|
128
|
+
Object.setPrototypeOf(Response.prototype, GlobalResponse.prototype)
|
|
129
|
+
|
|
130
|
+
// Fast path regex: matches http:// or https:// followed by RFC 3986 allowed chars.
|
|
131
|
+
// Character class covers unreserved + reserved chars plus `%` for percent-encoding.
|
|
132
|
+
// ! #-; = ?-[ ] _ a-z ~ A-Z (A-Z is within ?-[ range but listed for clarity)
|
|
133
|
+
const validRedirectUrl = /^https?:\/\/[!#-;=?-[\]_a-z~A-Z]+$/
|
|
134
|
+
const parseRedirectUrl = (url: string | URL): string => {
|
|
135
|
+
if (url instanceof URL) {
|
|
136
|
+
return url.href
|
|
137
|
+
}
|
|
138
|
+
if (validRedirectUrl.test(url)) {
|
|
139
|
+
return url
|
|
140
|
+
}
|
|
141
|
+
return new URL(url).href
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const validRedirectStatuses = new Set([301, 302, 303, 307, 308])
|
|
145
|
+
|
|
146
|
+
// Override Response.json() and Response.redirect() to return a LightweightResponse
|
|
147
|
+
// so the listener fast-path (cacheKey check) is hit instead of falling through to ReadableStream reading.
|
|
148
|
+
Object.defineProperty(Response, 'redirect', {
|
|
149
|
+
value: function redirect(url: string | URL, status = 302): Response {
|
|
150
|
+
if (!validRedirectStatuses.has(status)) {
|
|
151
|
+
throw new RangeError('Invalid status code')
|
|
152
|
+
}
|
|
153
|
+
return new Response(null, {
|
|
154
|
+
status,
|
|
155
|
+
headers: { location: parseRedirectUrl(url) },
|
|
156
|
+
})
|
|
157
|
+
},
|
|
158
|
+
writable: true,
|
|
159
|
+
configurable: true,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
Object.defineProperty(Response, 'json', {
|
|
163
|
+
value: function json(data?: unknown, init?: ResponseInit): Response {
|
|
164
|
+
const body = JSON.stringify(data)
|
|
165
|
+
if (body === undefined) {
|
|
166
|
+
throw new TypeError('The data is not JSON serializable')
|
|
167
|
+
}
|
|
168
|
+
const initHeaders = init?.headers
|
|
169
|
+
let headers: Record<string, string> | Headers
|
|
170
|
+
if (initHeaders) {
|
|
171
|
+
headers = new Headers(initHeaders)
|
|
172
|
+
if (!headers.has('content-type')) {
|
|
173
|
+
headers.set('content-type', 'application/json')
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
headers = { 'content-type': 'application/json' }
|
|
177
|
+
}
|
|
178
|
+
return new Response(body, {
|
|
179
|
+
status: init?.status ?? 200,
|
|
180
|
+
statusText: init?.statusText,
|
|
181
|
+
headers,
|
|
182
|
+
})
|
|
183
|
+
},
|
|
184
|
+
writable: true,
|
|
185
|
+
configurable: true,
|
|
186
|
+
})
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import type { Context, Env, MiddlewareHandler } from '../../../acryl-router/src/index.ts'
|
|
2
|
+
import { getMimeType } from '../../../acryl-router/src/helpers/index.ts'
|
|
3
|
+
import type { Stats } from 'node:fs'
|
|
4
|
+
import { createReadStream, statSync, existsSync } from 'node:fs'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import { createStreamBody } from './utils/stream.ts'
|
|
7
|
+
|
|
8
|
+
export type ServeStaticOptions<E extends Env = Env> = {
|
|
9
|
+
/**
|
|
10
|
+
* Root path. Relative path is based on current working directory from which the app was started.
|
|
11
|
+
*/
|
|
12
|
+
root?: string
|
|
13
|
+
path?: string
|
|
14
|
+
index?: string // default is 'index.html'
|
|
15
|
+
precompressed?: boolean
|
|
16
|
+
/** Allow percent signs in the routed request path. Defaults to false. */
|
|
17
|
+
allowPercentInPath?: boolean
|
|
18
|
+
rewriteRequestPath?: (path: string, c: Context<E>) => string
|
|
19
|
+
onFound?: (path: string, c: Context<E>) => void | Promise<void>
|
|
20
|
+
onNotFound?: (path: string, c: Context<E>) => void | Promise<void>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const COMPRESSIBLE_CONTENT_TYPE_REGEX =
|
|
24
|
+
/^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i
|
|
25
|
+
const ENCODINGS = {
|
|
26
|
+
br: '.br',
|
|
27
|
+
zstd: '.zst',
|
|
28
|
+
gzip: '.gz',
|
|
29
|
+
} as const
|
|
30
|
+
const ENCODINGS_ORDERED_KEYS = Object.keys(ENCODINGS) as (keyof typeof ENCODINGS)[]
|
|
31
|
+
|
|
32
|
+
const getStats = (path: string) => {
|
|
33
|
+
let stats: Stats | undefined
|
|
34
|
+
try {
|
|
35
|
+
stats = statSync(path)
|
|
36
|
+
} catch {}
|
|
37
|
+
return stats
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type ByteRangeSpec =
|
|
41
|
+
| { type: 'bounded'; start: number; end: number }
|
|
42
|
+
| { type: 'open-ended'; start: number }
|
|
43
|
+
| { type: 'suffix'; length: number }
|
|
44
|
+
|
|
45
|
+
type ByteRange = { start: number; end: number }
|
|
46
|
+
|
|
47
|
+
const BYTE_RANGE_PATTERN = /^(?:bytes=)?(?!-$)(\d*)-(\d*)$/
|
|
48
|
+
|
|
49
|
+
const parseByteRange = (range: string): ByteRangeSpec | undefined => {
|
|
50
|
+
const match = range.match(BYTE_RANGE_PATTERN)
|
|
51
|
+
if (!match) {
|
|
52
|
+
return undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const [, start, end] = match
|
|
56
|
+
|
|
57
|
+
if (start === '') {
|
|
58
|
+
return { type: 'suffix', length: Number(end) }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (end === '') {
|
|
62
|
+
return { type: 'open-ended', start: Number(start) }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { type: 'bounded', start: Number(start), end: Number(end) }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const resolveByteRange = (spec: ByteRangeSpec, size: number): ByteRange | undefined => {
|
|
69
|
+
if (size === 0) {
|
|
70
|
+
return undefined
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (spec.type === 'suffix') {
|
|
74
|
+
if (spec.length === 0) {
|
|
75
|
+
return undefined
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { start: Math.max(size - spec.length, 0), end: size - 1 }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const end = spec.type === 'bounded' ? Math.min(spec.end, size - 1) : size - 1
|
|
82
|
+
if (spec.start >= size || spec.start > end) {
|
|
83
|
+
return undefined
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { start: spec.start, end }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type Decoder = (str: string) => string
|
|
90
|
+
|
|
91
|
+
const tryDecode = (str: string, decoder: Decoder): string => {
|
|
92
|
+
try {
|
|
93
|
+
return decoder(str)
|
|
94
|
+
} catch {
|
|
95
|
+
// Decode only valid %xx sequences in chunks; keep undecodable parts as-is
|
|
96
|
+
return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match) => {
|
|
97
|
+
try {
|
|
98
|
+
return decoder(match)
|
|
99
|
+
} catch {
|
|
100
|
+
return match
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const tryDecodeURI = (str: string) => tryDecode(str, decodeURI)
|
|
107
|
+
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
109
|
+
export const serveStatic = <E extends Env = any>(
|
|
110
|
+
options: ServeStaticOptions<E> = { root: '' }
|
|
111
|
+
): MiddlewareHandler<E> => {
|
|
112
|
+
const root = options.root || ''
|
|
113
|
+
const optionPath = options.path
|
|
114
|
+
|
|
115
|
+
if (root !== '' && !existsSync(root)) {
|
|
116
|
+
console.error(`serveStatic: root path '${root}' is not found, are you sure it's correct?`)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return async (c, next) => {
|
|
120
|
+
// Do nothing if Response is already set
|
|
121
|
+
if (c.finalized) {
|
|
122
|
+
return next()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (c.req.method !== 'GET' && c.req.method !== 'HEAD') {
|
|
126
|
+
return next()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let filename: string
|
|
130
|
+
|
|
131
|
+
if (optionPath) {
|
|
132
|
+
filename = optionPath
|
|
133
|
+
} else {
|
|
134
|
+
try {
|
|
135
|
+
if (!options.allowPercentInPath && c.req.path.includes('%')) {
|
|
136
|
+
throw new Error()
|
|
137
|
+
}
|
|
138
|
+
filename = tryDecodeURI(c.req.path)
|
|
139
|
+
if (/(?:^|[\/\\])\.{1,2}(?:$|[\/\\])|[\/\\]{2,}|\\/.test(filename)) {
|
|
140
|
+
throw new Error()
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
await options.onNotFound?.(c.req.path, c)
|
|
144
|
+
return next()
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let path = join(
|
|
149
|
+
root,
|
|
150
|
+
!optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
let stats = getStats(path)
|
|
154
|
+
|
|
155
|
+
if (stats && stats.isDirectory()) {
|
|
156
|
+
const indexFile = options.index ?? 'index.html'
|
|
157
|
+
path = join(path, indexFile)
|
|
158
|
+
stats = getStats(path)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!stats) {
|
|
162
|
+
await options.onNotFound?.(path, c)
|
|
163
|
+
return next()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const mimeType = getMimeType(path)
|
|
167
|
+
c.header('Content-Type', mimeType || 'application/octet-stream')
|
|
168
|
+
|
|
169
|
+
if (
|
|
170
|
+
options.precompressed &&
|
|
171
|
+
(!mimeType ||
|
|
172
|
+
mimeType === 'application/octet-stream' ||
|
|
173
|
+
COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))
|
|
174
|
+
) {
|
|
175
|
+
const acceptEncodingSet = new Set(
|
|
176
|
+
c.req
|
|
177
|
+
.header('Accept-Encoding')
|
|
178
|
+
?.split(',')
|
|
179
|
+
.map((encoding) => encoding.trim())
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
for (const encoding of ENCODINGS_ORDERED_KEYS) {
|
|
183
|
+
if (!acceptEncodingSet.has(encoding)) {
|
|
184
|
+
continue
|
|
185
|
+
}
|
|
186
|
+
const precompressedStats = getStats(path + ENCODINGS[encoding])
|
|
187
|
+
if (precompressedStats) {
|
|
188
|
+
c.header('Content-Encoding', encoding)
|
|
189
|
+
c.header('Vary', 'Accept-Encoding', { append: true })
|
|
190
|
+
stats = precompressedStats
|
|
191
|
+
path = path + ENCODINGS[encoding]
|
|
192
|
+
break
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let result
|
|
198
|
+
const size = stats.size
|
|
199
|
+
const range = c.req.header('range') || ''
|
|
200
|
+
c.header('Last-Modified', stats.mtime.toUTCString())
|
|
201
|
+
|
|
202
|
+
if (c.req.method === 'HEAD') {
|
|
203
|
+
c.header('Content-Length', size.toString())
|
|
204
|
+
c.status(200)
|
|
205
|
+
result = c.body(null)
|
|
206
|
+
} else if (!range) {
|
|
207
|
+
c.header('Content-Length', size.toString())
|
|
208
|
+
result = c.body(createStreamBody(createReadStream(path)), 200)
|
|
209
|
+
} else {
|
|
210
|
+
c.header('Accept-Ranges', 'bytes')
|
|
211
|
+
|
|
212
|
+
// Preserve the existing behavior of serving the whole representation for
|
|
213
|
+
// a malformed range.
|
|
214
|
+
const rangeSpec: ByteRangeSpec = parseByteRange(range) ?? {
|
|
215
|
+
type: 'open-ended',
|
|
216
|
+
start: 0,
|
|
217
|
+
}
|
|
218
|
+
const resolvedRange = resolveByteRange(rangeSpec, size)
|
|
219
|
+
|
|
220
|
+
if (!resolvedRange) {
|
|
221
|
+
c.header('Content-Range', `bytes */${size}`)
|
|
222
|
+
result = c.body(null, 416)
|
|
223
|
+
} else {
|
|
224
|
+
const { start, end } = resolvedRange
|
|
225
|
+
const chunkSize = end - start + 1
|
|
226
|
+
const stream = createReadStream(path, { start, end })
|
|
227
|
+
|
|
228
|
+
c.header('Content-Length', chunkSize.toString())
|
|
229
|
+
c.header('Content-Range', `bytes ${start}-${end}/${size}`)
|
|
230
|
+
|
|
231
|
+
result = c.body(createStreamBody(stream), 206)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
await options.onFound?.(path, c)
|
|
236
|
+
return result
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createServer as createServerHTTP } from 'node:http'
|
|
2
|
+
import type { AddressInfo } from 'node:net'
|
|
3
|
+
import { getRequestListener } from './listener.ts'
|
|
4
|
+
import type { Options, ServerType } from './types.ts'
|
|
5
|
+
import { setupWebSocket } from './websocket.ts'
|
|
6
|
+
|
|
7
|
+
export const createAdaptorServer = (options: Options): ServerType => {
|
|
8
|
+
const fetchCallback = options.fetch
|
|
9
|
+
const requestListener = getRequestListener(fetchCallback, {
|
|
10
|
+
hostname: options.hostname,
|
|
11
|
+
overrideGlobalObjects: options.overrideGlobalObjects,
|
|
12
|
+
autoCleanupIncoming: options.autoCleanupIncoming,
|
|
13
|
+
})
|
|
14
|
+
// ts will complain about createServerHTTP and createServerHTTP2 not being callable, which works just fine
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
const createServer: any = options.createServer || createServerHTTP
|
|
17
|
+
const server: ServerType = createServer(options.serverOptions || {}, requestListener)
|
|
18
|
+
if (options.websocket && options.websocket.server) {
|
|
19
|
+
if (options.websocket.server.options.noServer !== true)
|
|
20
|
+
throw new Error('WebSocket server must be created with { noServer: true } option')
|
|
21
|
+
setupWebSocket({ server, fetchCallback, wss: options.websocket.server })
|
|
22
|
+
}
|
|
23
|
+
return server
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const serve = (
|
|
27
|
+
options: Options,
|
|
28
|
+
listeningListener?: (info: AddressInfo) => void
|
|
29
|
+
): ServerType => {
|
|
30
|
+
const server = createAdaptorServer(options)
|
|
31
|
+
server.listen(options?.port ?? 3000, options.hostname, () => {
|
|
32
|
+
const serverInfo = server.address() as AddressInfo
|
|
33
|
+
listeningListener && listeningListener(serverInfo)
|
|
34
|
+
})
|
|
35
|
+
return server
|
|
36
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
createServer,
|
|
3
|
+
IncomingMessage,
|
|
4
|
+
Server,
|
|
5
|
+
ServerOptions as HttpServerOptions,
|
|
6
|
+
ServerResponse as HttpServerResponse,
|
|
7
|
+
} from 'node:http'
|
|
8
|
+
import type {
|
|
9
|
+
createSecureServer as createSecureHttp2Server,
|
|
10
|
+
createServer as createHttp2Server,
|
|
11
|
+
Http2ServerRequest,
|
|
12
|
+
Http2Server,
|
|
13
|
+
Http2ServerResponse,
|
|
14
|
+
Http2SecureServer,
|
|
15
|
+
SecureServerOptions as SecureHttp2ServerOptions,
|
|
16
|
+
ServerOptions as Http2ServerOptions,
|
|
17
|
+
} from 'node:http2'
|
|
18
|
+
import type {
|
|
19
|
+
createServer as createHttpsServer,
|
|
20
|
+
ServerOptions as HttpsServerOptions,
|
|
21
|
+
} from 'node:https'
|
|
22
|
+
import type { WebSocketServerLike } from './websocket-types.ts'
|
|
23
|
+
|
|
24
|
+
export type HttpBindings = {
|
|
25
|
+
incoming: IncomingMessage
|
|
26
|
+
outgoing: HttpServerResponse
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type Http2Bindings = {
|
|
30
|
+
incoming: Http2ServerRequest
|
|
31
|
+
outgoing: Http2ServerResponse
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type FetchCallback = (
|
|
35
|
+
request: Request,
|
|
36
|
+
env: HttpBindings | Http2Bindings
|
|
37
|
+
) => Promise<unknown> | unknown
|
|
38
|
+
|
|
39
|
+
export type NextHandlerOption = {
|
|
40
|
+
fetch: FetchCallback
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type ServerType = Server | Http2Server | Http2SecureServer
|
|
44
|
+
|
|
45
|
+
type createHttpOptions = {
|
|
46
|
+
serverOptions?: HttpServerOptions
|
|
47
|
+
createServer?: typeof createServer
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type createHttpsOptions = {
|
|
51
|
+
serverOptions?: HttpsServerOptions
|
|
52
|
+
createServer?: typeof createHttpsServer
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type createHttp2Options = {
|
|
56
|
+
serverOptions?: Http2ServerOptions
|
|
57
|
+
createServer?: typeof createHttp2Server
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type createSecureHttp2Options = {
|
|
61
|
+
serverOptions?: SecureHttp2ServerOptions
|
|
62
|
+
createServer?: typeof createSecureHttp2Server
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type ServerOptions =
|
|
66
|
+
| createHttpOptions
|
|
67
|
+
| createHttpsOptions
|
|
68
|
+
| createHttp2Options
|
|
69
|
+
| createSecureHttp2Options
|
|
70
|
+
|
|
71
|
+
export type Options = {
|
|
72
|
+
fetch: FetchCallback
|
|
73
|
+
overrideGlobalObjects?: boolean
|
|
74
|
+
autoCleanupIncoming?: boolean
|
|
75
|
+
port?: number
|
|
76
|
+
hostname?: string
|
|
77
|
+
websocket?: {
|
|
78
|
+
server: WebSocketServerLike
|
|
79
|
+
}
|
|
80
|
+
} & ServerOptions
|
|
81
|
+
|
|
82
|
+
export type CustomErrorHandler = (err: unknown) => void | Response | Promise<void | Response>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { RequestError } from './error.ts'
|
|
2
|
+
|
|
3
|
+
// Fast-path character validation for request URLs.
|
|
4
|
+
// Matches: ! # $ & ' ( ) * + , - . / 0-9 : ; = ? @ A-Z [ ] _ a-z ~
|
|
5
|
+
// Rejects: control chars, space, " % < > \ ^ ` { | } DEL, non-ASCII → fallback to new URL()
|
|
6
|
+
const reValidRequestUrl = /^\/[!#$&-;=?-\[\]_a-z~]*$/
|
|
7
|
+
|
|
8
|
+
// Dot segments: /. or /.. followed by / ? # or end-of-string
|
|
9
|
+
const reDotSegment = /\/\.\.?(?:[/?#]|$)/
|
|
10
|
+
|
|
11
|
+
// Host validation: a-z 0-9 . - _ with optional port 1000-59999
|
|
12
|
+
const reValidHost = /^[a-z0-9._-]+(?::(?:[1-5]\d{3,4}|[6-9]\d{3}))?$/
|
|
13
|
+
|
|
14
|
+
export const buildUrl = (scheme: string, host: string, incomingUrl: string) => {
|
|
15
|
+
const url = `${scheme}://${host}${incomingUrl}`
|
|
16
|
+
|
|
17
|
+
if (!reValidHost.test(host)) {
|
|
18
|
+
const urlObj = new URL(url)
|
|
19
|
+
|
|
20
|
+
// if suspicious, check by host. host header sometimes contains port.
|
|
21
|
+
if (
|
|
22
|
+
urlObj.hostname.length !== host.length &&
|
|
23
|
+
urlObj.hostname !== (host.includes(':') ? host.replace(/:\d+$/, '') : host).toLowerCase()
|
|
24
|
+
) {
|
|
25
|
+
throw new RequestError('Invalid host header')
|
|
26
|
+
}
|
|
27
|
+
return urlObj.href
|
|
28
|
+
} else if (incomingUrl.length === 0) {
|
|
29
|
+
return url + '/'
|
|
30
|
+
} else {
|
|
31
|
+
if (incomingUrl.charCodeAt(0) !== 0x2f) {
|
|
32
|
+
// '/'
|
|
33
|
+
throw new RequestError('Invalid URL')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!reValidRequestUrl.test(incomingUrl) || reDotSegment.test(incomingUrl)) {
|
|
37
|
+
return new URL(url).href
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return url
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const X_ALREADY_SENT = 'x-acryl-already-sent'
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ReadStream } from 'node:fs'
|
|
2
|
+
import { versions } from 'node:process'
|
|
3
|
+
import { Readable } from 'node:stream'
|
|
4
|
+
|
|
5
|
+
// In Node.js versions that do not have the following PR applied, using Readable.toWeb may cause unexpected exceptions.
|
|
6
|
+
// https://github.com/nodejs/node/pull/54206
|
|
7
|
+
const pr54206Applied = () => {
|
|
8
|
+
const [major, minor] = versions.node.split('.').map((component) => parseInt(component))
|
|
9
|
+
return major >= 23 || (major === 22 && minor >= 7) || (major === 20 && minor >= 18)
|
|
10
|
+
}
|
|
11
|
+
const useReadableToWeb = pr54206Applied()
|
|
12
|
+
|
|
13
|
+
export const createStreamBody = (
|
|
14
|
+
stream: ReadStream,
|
|
15
|
+
useNativeReadableToWeb = useReadableToWeb
|
|
16
|
+
): ReadableStream<Uint8Array> => {
|
|
17
|
+
if (useNativeReadableToWeb) {
|
|
18
|
+
return Readable.toWeb(stream) as ReadableStream<Uint8Array>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let controller: ReadableStreamDefaultController<Uint8Array> | undefined
|
|
22
|
+
let settled = false
|
|
23
|
+
|
|
24
|
+
const cleanup = () => {
|
|
25
|
+
stream.off('data', onData)
|
|
26
|
+
stream.off('error', onError)
|
|
27
|
+
stream.off('end', onTerminate)
|
|
28
|
+
stream.off('close', onTerminate)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const settle = (callback?: () => void) => {
|
|
32
|
+
if (settled) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
settled = true
|
|
36
|
+
cleanup()
|
|
37
|
+
callback?.()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const onData = (chunk: Buffer | string) => {
|
|
41
|
+
if (settled || !controller) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
// createReadStream is called without `encoding`, so chunks are always Buffer.
|
|
45
|
+
controller.enqueue(chunk as Buffer)
|
|
46
|
+
if ((controller.desiredSize ?? 0) <= 0) {
|
|
47
|
+
stream.pause()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const onError = (error: Error) => {
|
|
52
|
+
settle(() => {
|
|
53
|
+
controller?.error(error)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const onTerminate = () => {
|
|
58
|
+
settle(() => {
|
|
59
|
+
controller?.close()
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return new ReadableStream<Uint8Array>({
|
|
64
|
+
start(streamController) {
|
|
65
|
+
controller = streamController
|
|
66
|
+
stream.on('data', onData)
|
|
67
|
+
stream.on('error', onError)
|
|
68
|
+
stream.on('end', onTerminate)
|
|
69
|
+
stream.on('close', onTerminate)
|
|
70
|
+
stream.pause()
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
pull() {
|
|
74
|
+
if (!settled) {
|
|
75
|
+
stream.resume()
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
cancel() {
|
|
80
|
+
settle()
|
|
81
|
+
// Suppress late `error` emitted between destroy() and the terminal `close`.
|
|
82
|
+
const ignoreError = () => {}
|
|
83
|
+
stream.on('error', ignoreError)
|
|
84
|
+
stream.once('close', () => stream.off('error', ignoreError))
|
|
85
|
+
stream.destroy()
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
}
|