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,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Language module for App.
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '../../context.ts'
|
|
6
|
+
import { setCookie, getCookie } from '../../helper/cookie/index.ts'
|
|
7
|
+
import type { MiddlewareHandler } from '../../types.ts'
|
|
8
|
+
import { parseAccept } from '../../utils/accept.ts'
|
|
9
|
+
|
|
10
|
+
export type DetectorType = 'path' | 'querystring' | 'cookie' | 'header'
|
|
11
|
+
export type CacheType = 'cookie'
|
|
12
|
+
|
|
13
|
+
export interface DetectorOptions {
|
|
14
|
+
/** Order of language detection strategies */
|
|
15
|
+
order: DetectorType[]
|
|
16
|
+
/** Query parameter name for language */
|
|
17
|
+
lookupQueryString: string
|
|
18
|
+
/** Cookie name for language */
|
|
19
|
+
lookupCookie: string
|
|
20
|
+
/** Index in URL path where language code appears */
|
|
21
|
+
lookupFromPathIndex: number
|
|
22
|
+
/** Header key for language detection */
|
|
23
|
+
lookupFromHeaderKey: string
|
|
24
|
+
/** Caching strategies */
|
|
25
|
+
caches: CacheType[] | false
|
|
26
|
+
/** Cookie configuration options */
|
|
27
|
+
cookieOptions?: {
|
|
28
|
+
domain?: string
|
|
29
|
+
path?: string
|
|
30
|
+
sameSite?: 'Strict' | 'Lax' | 'None'
|
|
31
|
+
secure?: boolean
|
|
32
|
+
maxAge?: number
|
|
33
|
+
httpOnly?: boolean
|
|
34
|
+
}
|
|
35
|
+
/** Whether to ignore case in language codes */
|
|
36
|
+
ignoreCase: boolean
|
|
37
|
+
/** Default language if none detected */
|
|
38
|
+
fallbackLanguage: string
|
|
39
|
+
/** List of supported language codes */
|
|
40
|
+
supportedLanguages: string[]
|
|
41
|
+
/** Optional function to transform detected language codes */
|
|
42
|
+
convertDetectedLanguage?: (lang: string) => string
|
|
43
|
+
/** Enable debug logging */
|
|
44
|
+
debug?: boolean
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface LanguageVariables {
|
|
48
|
+
language: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const DEFAULT_OPTIONS: DetectorOptions = {
|
|
52
|
+
order: ['querystring', 'cookie', 'header'],
|
|
53
|
+
lookupQueryString: 'lang',
|
|
54
|
+
lookupCookie: 'language',
|
|
55
|
+
lookupFromHeaderKey: 'accept-language',
|
|
56
|
+
lookupFromPathIndex: 0,
|
|
57
|
+
caches: ['cookie'],
|
|
58
|
+
ignoreCase: true,
|
|
59
|
+
fallbackLanguage: 'en',
|
|
60
|
+
supportedLanguages: ['en'],
|
|
61
|
+
cookieOptions: {
|
|
62
|
+
sameSite: 'Strict',
|
|
63
|
+
secure: true,
|
|
64
|
+
maxAge: 365 * 24 * 60 * 60,
|
|
65
|
+
httpOnly: true,
|
|
66
|
+
},
|
|
67
|
+
debug: false,
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Parse Accept-Language header values with quality scores
|
|
71
|
+
* @param header Accept-Language header string
|
|
72
|
+
* @returns Array of parsed languages with quality scores
|
|
73
|
+
*/
|
|
74
|
+
export function parseAcceptLanguage(header: string): Array<{ lang: string; q: number }> {
|
|
75
|
+
return parseAccept(header).map(({ type, q }) => ({ lang: type, q }))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Validate and normalize language codes
|
|
80
|
+
* @param lang Language code to normalize
|
|
81
|
+
* @param options Detector options
|
|
82
|
+
* @returns Normalized language code or undefined
|
|
83
|
+
*/
|
|
84
|
+
export const normalizeLanguage = (
|
|
85
|
+
lang: string | null | undefined,
|
|
86
|
+
options: DetectorOptions
|
|
87
|
+
): string | undefined => {
|
|
88
|
+
if (!lang) {
|
|
89
|
+
return undefined
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
let normalizedLang = lang.trim()
|
|
94
|
+
if (options.convertDetectedLanguage) {
|
|
95
|
+
normalizedLang = options.convertDetectedLanguage(normalizedLang)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const compLang = options.ignoreCase ? normalizedLang.toLowerCase() : normalizedLang
|
|
99
|
+
const compSupported = options.supportedLanguages.map((l) =>
|
|
100
|
+
options.ignoreCase ? l.toLowerCase() : l
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
// Exact match
|
|
104
|
+
const exactIndex = compSupported.indexOf(compLang)
|
|
105
|
+
if (exactIndex !== -1) {
|
|
106
|
+
return options.supportedLanguages[exactIndex]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Progressive truncation (RFC 4647 Lookup)
|
|
110
|
+
let longestMatchIndex = -1
|
|
111
|
+
let longestMatchLength = -1
|
|
112
|
+
for (let i = 0; i < compSupported.length; i++) {
|
|
113
|
+
const candidate = compSupported[i]
|
|
114
|
+
if (
|
|
115
|
+
candidate.length < compLang.length &&
|
|
116
|
+
candidate.length > longestMatchLength &&
|
|
117
|
+
compLang.startsWith(candidate) &&
|
|
118
|
+
compLang[candidate.length] === '-'
|
|
119
|
+
) {
|
|
120
|
+
longestMatchIndex = i
|
|
121
|
+
longestMatchLength = candidate.length
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (longestMatchIndex !== -1) {
|
|
125
|
+
return options.supportedLanguages[longestMatchIndex]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return undefined
|
|
129
|
+
} catch {
|
|
130
|
+
return undefined
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Detects language from query parameter
|
|
136
|
+
*/
|
|
137
|
+
export const detectFromQuery = (c: Context, options: DetectorOptions): string | undefined => {
|
|
138
|
+
const query = c.req.query(options.lookupQueryString)
|
|
139
|
+
return normalizeLanguage(query, options)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Detects language from cookie
|
|
144
|
+
*/
|
|
145
|
+
export const detectFromCookie = (c: Context, options: DetectorOptions): string | undefined => {
|
|
146
|
+
const cookie = getCookie(c, options.lookupCookie)
|
|
147
|
+
return normalizeLanguage(cookie, options)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Detects language from Accept-Language header
|
|
152
|
+
*/
|
|
153
|
+
export function detectFromHeader(c: Context, options: DetectorOptions): string | undefined {
|
|
154
|
+
try {
|
|
155
|
+
const acceptLanguage = c.req.header(options.lookupFromHeaderKey)
|
|
156
|
+
if (!acceptLanguage) {
|
|
157
|
+
return undefined
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const languages = parseAcceptLanguage(acceptLanguage)
|
|
161
|
+
for (const { lang, q } of languages) {
|
|
162
|
+
// A quality value of 0 means the language is explicitly rejected
|
|
163
|
+
// (RFC 9110 §12.5.4), so it must not be detected.
|
|
164
|
+
if (q === 0) {
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
const normalizedLang = normalizeLanguage(lang, options)
|
|
168
|
+
if (normalizedLang) {
|
|
169
|
+
return normalizedLang
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return undefined
|
|
173
|
+
} catch {
|
|
174
|
+
return undefined
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Detects language from URL path
|
|
180
|
+
*/
|
|
181
|
+
export function detectFromPath(c: Context, options: DetectorOptions): string | undefined {
|
|
182
|
+
const url = new URL(c.req.url)
|
|
183
|
+
const pathSegments = url.pathname.split('/').filter(Boolean)
|
|
184
|
+
const langSegment = pathSegments[options.lookupFromPathIndex]
|
|
185
|
+
return normalizeLanguage(langSegment, options)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Collection of all language detection strategies
|
|
190
|
+
*/
|
|
191
|
+
export const detectors = {
|
|
192
|
+
querystring: detectFromQuery,
|
|
193
|
+
cookie: detectFromCookie,
|
|
194
|
+
header: detectFromHeader,
|
|
195
|
+
path: detectFromPath,
|
|
196
|
+
} as const
|
|
197
|
+
|
|
198
|
+
/** Type for detector functions */
|
|
199
|
+
export type DetectorFunction = (c: Context, options: DetectorOptions) => string | undefined
|
|
200
|
+
|
|
201
|
+
/** Type-safe detector map */
|
|
202
|
+
export type Detectors = Record<keyof typeof detectors, DetectorFunction>
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Validate detector options
|
|
206
|
+
* @param options Detector options to validate
|
|
207
|
+
* @throws Error if options are invalid
|
|
208
|
+
*/
|
|
209
|
+
export function validateOptions(options: DetectorOptions): void {
|
|
210
|
+
if (!options.supportedLanguages.includes(options.fallbackLanguage)) {
|
|
211
|
+
throw new Error('Fallback language must be included in supported languages')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (options.lookupFromPathIndex < 0) {
|
|
215
|
+
throw new Error('Path index must be non-negative')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!options.order.every((detector) => Object.keys(detectors).includes(detector))) {
|
|
219
|
+
throw new Error('Invalid detector type in order array')
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Cache detected language
|
|
225
|
+
*/
|
|
226
|
+
function cacheLanguage(c: Context, language: string, options: DetectorOptions): void {
|
|
227
|
+
if (!Array.isArray(options.caches) || !options.caches.includes('cookie')) {
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
setCookie(c, options.lookupCookie, language, options.cookieOptions)
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (options.debug) {
|
|
235
|
+
console.error('Failed to cache language:', error)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Detect language from request
|
|
242
|
+
*/
|
|
243
|
+
const detectLanguage = (c: Context, options: DetectorOptions): string => {
|
|
244
|
+
let detectedLang: string | undefined
|
|
245
|
+
|
|
246
|
+
for (const detectorName of options.order) {
|
|
247
|
+
const detector = detectors[detectorName]
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
detectedLang = detector(c, options)
|
|
251
|
+
if (detectedLang) {
|
|
252
|
+
if (options.debug) {
|
|
253
|
+
console.log(`Language detected from ${detectorName}: ${detectedLang}`)
|
|
254
|
+
}
|
|
255
|
+
break
|
|
256
|
+
}
|
|
257
|
+
} catch (error) {
|
|
258
|
+
if (options.debug) {
|
|
259
|
+
console.error(`Error in ${detectorName} detector:`, error)
|
|
260
|
+
}
|
|
261
|
+
continue
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const finalLang = detectedLang || options.fallbackLanguage
|
|
266
|
+
|
|
267
|
+
if (detectedLang && options.caches) {
|
|
268
|
+
cacheLanguage(c, finalLang, options)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return finalLang
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Language detector middleware factory
|
|
276
|
+
* @param userOptions Configuration options for the language detector
|
|
277
|
+
* @returns App middleware function
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```ts
|
|
281
|
+
* type Variables = LanguageVariables
|
|
282
|
+
* const app = new App<{ Variables: Variables }>()
|
|
283
|
+
*
|
|
284
|
+
* app.use(
|
|
285
|
+
* languageDetector({
|
|
286
|
+
* supportedLanguages: ['en', 'ja'], // Must include fallback
|
|
287
|
+
* fallbackLanguage: 'en', // Required
|
|
288
|
+
* })
|
|
289
|
+
* )
|
|
290
|
+
*
|
|
291
|
+
* app.get('/', (c) => {
|
|
292
|
+
* const lang = c.get('language')
|
|
293
|
+
* return c.text(`Current language: ${lang}`)
|
|
294
|
+
* })
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
export const languageDetector = (userOptions: Partial<DetectorOptions>): MiddlewareHandler => {
|
|
298
|
+
const options: DetectorOptions = {
|
|
299
|
+
...DEFAULT_OPTIONS,
|
|
300
|
+
...userOptions,
|
|
301
|
+
cookieOptions: {
|
|
302
|
+
...DEFAULT_OPTIONS.cookieOptions,
|
|
303
|
+
...userOptions.cookieOptions,
|
|
304
|
+
},
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
validateOptions(options)
|
|
308
|
+
|
|
309
|
+
return async function languageDetector(ctx, next) {
|
|
310
|
+
const lang = detectLanguage(ctx, options)
|
|
311
|
+
ctx.set('language', lang)
|
|
312
|
+
await next()
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { logger } from './index.ts'
|
|
2
|
+
import { App } from '../../app.ts'
|
|
3
|
+
|
|
4
|
+
describe('Logger by Middleware', () => {
|
|
5
|
+
let app: App
|
|
6
|
+
let log: string
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
function sleep(time: number) {
|
|
10
|
+
return new Promise((resolve) => setTimeout(resolve, time))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
app = new App()
|
|
14
|
+
|
|
15
|
+
const logFn = (str: string) => {
|
|
16
|
+
log = str
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const shortRandomString = 'acryl'
|
|
20
|
+
const longRandomString = 'acryl'.repeat(1000)
|
|
21
|
+
|
|
22
|
+
app.use('*', logger(logFn))
|
|
23
|
+
app.get('/short', (c) => c.text(shortRandomString))
|
|
24
|
+
app.get('/long', (c) => c.text(longRandomString))
|
|
25
|
+
app.get('/seconds', async (c) => {
|
|
26
|
+
await sleep(1000)
|
|
27
|
+
|
|
28
|
+
return c.text(longRandomString)
|
|
29
|
+
})
|
|
30
|
+
app.get('/empty', (c) => c.text(''))
|
|
31
|
+
app.get('/redirect', (c) => {
|
|
32
|
+
return c.redirect('/empty', 301)
|
|
33
|
+
})
|
|
34
|
+
app.get('/server-error', (c) => {
|
|
35
|
+
const res = new Response('', { status: 511 })
|
|
36
|
+
if (c.req.query('status')) {
|
|
37
|
+
// test status code not yet supported by runtime `Response` object
|
|
38
|
+
Object.defineProperty(res, 'status', { value: parseInt(c.req.query('status') as string) })
|
|
39
|
+
}
|
|
40
|
+
return res
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('Log status 200 with empty body', async () => {
|
|
45
|
+
const res = await app.request('http://localhost/empty')
|
|
46
|
+
expect(res).not.toBeNull()
|
|
47
|
+
expect(res.status).toBe(200)
|
|
48
|
+
expect(log.startsWith('--> GET /empty \x1b[32m200\x1b[0m')).toBe(true)
|
|
49
|
+
expect(log).toMatch(/m?s$/)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('Log status 200 with small body', async () => {
|
|
53
|
+
const res = await app.request('http://localhost/short')
|
|
54
|
+
expect(res).not.toBeNull()
|
|
55
|
+
expect(res.status).toBe(200)
|
|
56
|
+
expect(log.startsWith('--> GET /short \x1b[32m200\x1b[0m')).toBe(true)
|
|
57
|
+
expect(log).toMatch(/m?s$/)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('Log status 200 with small body and query param', async () => {
|
|
61
|
+
const res = await app.request('http://localhost/short?foo=bar')
|
|
62
|
+
expect(res).not.toBeNull()
|
|
63
|
+
expect(res.status).toBe(200)
|
|
64
|
+
expect(log.startsWith('--> GET /short?foo=bar \x1b[32m200\x1b[0m')).toBe(true)
|
|
65
|
+
expect(log).toMatch(/m?s$/)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('Log status 200 with big body', async () => {
|
|
69
|
+
const res = await app.request('http://localhost/long')
|
|
70
|
+
expect(res).not.toBeNull()
|
|
71
|
+
expect(res.status).toBe(200)
|
|
72
|
+
expect(log.startsWith('--> GET /long \x1b[32m200\x1b[0m')).toBe(true)
|
|
73
|
+
expect(log).toMatch(/m?s$/)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('Time in seconds', async () => {
|
|
77
|
+
const res = await app.request('http://localhost/seconds')
|
|
78
|
+
expect(res).not.toBeNull()
|
|
79
|
+
expect(res.status).toBe(200)
|
|
80
|
+
expect(log.startsWith('--> GET /seconds \x1b[32m200\x1b[0m')).toBe(true)
|
|
81
|
+
expect(log).toMatch(/1s/)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('Log status 301 with empty body', async () => {
|
|
85
|
+
const res = await app.request('http://localhost/redirect')
|
|
86
|
+
expect(res).not.toBeNull()
|
|
87
|
+
expect(res.status).toBe(301)
|
|
88
|
+
expect(log.startsWith('--> GET /redirect \x1b[36m301\x1b[0m')).toBe(true)
|
|
89
|
+
expect(log).toMatch(/m?s$/)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('Log status 404', async () => {
|
|
93
|
+
const msg = 'Default 404 Not Found'
|
|
94
|
+
app.all('*', (c) => {
|
|
95
|
+
return c.text(msg, 404)
|
|
96
|
+
})
|
|
97
|
+
const res = await app.request('http://localhost/notfound')
|
|
98
|
+
expect(res).not.toBeNull()
|
|
99
|
+
expect(res.status).toBe(404)
|
|
100
|
+
expect(log.startsWith('--> GET /notfound \x1b[33m404\x1b[0m')).toBe(true)
|
|
101
|
+
expect(log).toMatch(/m?s$/)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('Log status 511 with empty body', async () => {
|
|
105
|
+
const res = await app.request('http://localhost/server-error')
|
|
106
|
+
expect(res).not.toBeNull()
|
|
107
|
+
expect(res.status).toBe(511)
|
|
108
|
+
expect(log.startsWith('--> GET /server-error \x1b[31m511\x1b[0m')).toBe(true)
|
|
109
|
+
expect(log).toMatch(/m?s$/)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('Log status 100', async () => {
|
|
113
|
+
const res = await app.request('http://localhost/server-error?status=100')
|
|
114
|
+
expect(res).not.toBeNull()
|
|
115
|
+
expect(res.status).toBe(100)
|
|
116
|
+
expect(log.startsWith('--> GET /server-error?status=100 100')).toBe(true)
|
|
117
|
+
expect(log).toMatch(/m?s$/)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('Log status 700', async () => {
|
|
121
|
+
const res = await app.request('http://localhost/server-error?status=700')
|
|
122
|
+
expect(res).not.toBeNull()
|
|
123
|
+
expect(res.status).toBe(700)
|
|
124
|
+
expect(log.startsWith('--> GET /server-error?status=700 700')).toBe(true)
|
|
125
|
+
expect(log).toMatch(/m?s$/)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
describe('Logger by Middleware in NO_COLOR', () => {
|
|
130
|
+
let app: App
|
|
131
|
+
let log: string
|
|
132
|
+
|
|
133
|
+
beforeEach(() => {
|
|
134
|
+
vi.stubEnv('NO_COLOR', '1')
|
|
135
|
+
function sleep(time: number) {
|
|
136
|
+
return new Promise((resolve) => setTimeout(resolve, time))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
app = new App()
|
|
140
|
+
|
|
141
|
+
const logFn = (str: string) => {
|
|
142
|
+
log = str
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const shortRandomString = 'acryl'
|
|
146
|
+
const longRandomString = 'acryl'.repeat(1000)
|
|
147
|
+
|
|
148
|
+
app.use('*', logger(logFn))
|
|
149
|
+
app.get('/short', (c) => c.text(shortRandomString))
|
|
150
|
+
app.get('/long', (c) => c.text(longRandomString))
|
|
151
|
+
app.get('/seconds', async (c) => {
|
|
152
|
+
await sleep(1000)
|
|
153
|
+
|
|
154
|
+
return c.text(longRandomString)
|
|
155
|
+
})
|
|
156
|
+
app.get('/empty', (c) => c.text(''))
|
|
157
|
+
})
|
|
158
|
+
afterAll(() => {
|
|
159
|
+
vi.unstubAllEnvs()
|
|
160
|
+
})
|
|
161
|
+
it('Log status 200 with empty body', async () => {
|
|
162
|
+
const res = await app.request('http://localhost/empty')
|
|
163
|
+
expect(res).not.toBeNull()
|
|
164
|
+
expect(res.status).toBe(200)
|
|
165
|
+
expect(log.startsWith('--> GET /empty 200')).toBe(true)
|
|
166
|
+
expect(log).toMatch(/m?s$/)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('Log status 200 with small body', async () => {
|
|
170
|
+
const res = await app.request('http://localhost/short')
|
|
171
|
+
expect(res).not.toBeNull()
|
|
172
|
+
expect(res.status).toBe(200)
|
|
173
|
+
expect(log.startsWith('--> GET /short 200')).toBe(true)
|
|
174
|
+
expect(log).toMatch(/m?s$/)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('Log status 200 with big body', async () => {
|
|
178
|
+
const res = await app.request('http://localhost/long')
|
|
179
|
+
expect(res).not.toBeNull()
|
|
180
|
+
expect(res.status).toBe(200)
|
|
181
|
+
expect(log.startsWith('--> GET /long 200')).toBe(true)
|
|
182
|
+
expect(log).toMatch(/m?s$/)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('Time in seconds', async () => {
|
|
186
|
+
const res = await app.request('http://localhost/seconds')
|
|
187
|
+
expect(res).not.toBeNull()
|
|
188
|
+
expect(res.status).toBe(200)
|
|
189
|
+
expect(log.startsWith('--> GET /seconds 200')).toBe(true)
|
|
190
|
+
expect(log).toMatch(/1s/)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('Log status 404', async () => {
|
|
194
|
+
const msg = 'Default 404 Not Found'
|
|
195
|
+
app.all('*', (c) => {
|
|
196
|
+
return c.text(msg, 404)
|
|
197
|
+
})
|
|
198
|
+
const res = await app.request('http://localhost/notfound')
|
|
199
|
+
expect(res).not.toBeNull()
|
|
200
|
+
expect(res.status).toBe(404)
|
|
201
|
+
expect(log.startsWith('--> GET /notfound 404')).toBe(true)
|
|
202
|
+
expect(log).toMatch(/m?s$/)
|
|
203
|
+
})
|
|
204
|
+
})
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Logger Middleware for App.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { MiddlewareHandler } from '../../types.ts'
|
|
7
|
+
import { getColorEnabledAsync } from '../../utils/color.ts'
|
|
8
|
+
|
|
9
|
+
const LogPrefix = {
|
|
10
|
+
Outgoing: '-->',
|
|
11
|
+
Incoming: '<--',
|
|
12
|
+
Error: 'xxx',
|
|
13
|
+
} as const
|
|
14
|
+
type LogPrefix = (typeof LogPrefix)[keyof typeof LogPrefix]
|
|
15
|
+
|
|
16
|
+
const humanize = (times: string[]) => {
|
|
17
|
+
const [delimiter, separator] = [',', '.']
|
|
18
|
+
|
|
19
|
+
const orderTimes = times.map((v) => v.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1' + delimiter))
|
|
20
|
+
|
|
21
|
+
return orderTimes.join(separator)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const time = (start: number) => {
|
|
25
|
+
const delta = Date.now() - start
|
|
26
|
+
return humanize([delta < 1000 ? delta + 'ms' : Math.round(delta / 1000) + 's'])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const colorStatus = async (status: number) => {
|
|
30
|
+
const colorEnabled = await getColorEnabledAsync()
|
|
31
|
+
if (colorEnabled) {
|
|
32
|
+
switch ((status / 100) | 0) {
|
|
33
|
+
case 5: // red = error
|
|
34
|
+
return `\x1b[31m${status}\x1b[0m`
|
|
35
|
+
case 4: // yellow = warning
|
|
36
|
+
return `\x1b[33m${status}\x1b[0m`
|
|
37
|
+
case 3: // cyan = redirect
|
|
38
|
+
return `\x1b[36m${status}\x1b[0m`
|
|
39
|
+
case 2: // green = success
|
|
40
|
+
return `\x1b[32m${status}\x1b[0m`
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Fallback to unsupported status code.
|
|
44
|
+
// E.g.) Bun and Deno supports new Response with 101, but Node.js does not.
|
|
45
|
+
// And those may evolve to accept more status.
|
|
46
|
+
return `${status}`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type PrintFunc = (str: string, ...rest: string[]) => void
|
|
50
|
+
|
|
51
|
+
async function log(
|
|
52
|
+
fn: PrintFunc,
|
|
53
|
+
prefix: string,
|
|
54
|
+
method: string,
|
|
55
|
+
path: string,
|
|
56
|
+
status: number = 0,
|
|
57
|
+
elapsed?: string
|
|
58
|
+
) {
|
|
59
|
+
const out =
|
|
60
|
+
prefix === LogPrefix.Incoming
|
|
61
|
+
? `${prefix} ${method} ${path}`
|
|
62
|
+
: `${prefix} ${method} ${path} ${await colorStatus(status)} ${elapsed}`
|
|
63
|
+
fn(out)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Logger Middleware for App.
|
|
68
|
+
*
|
|
69
|
+
*
|
|
70
|
+
* @param {PrintFunc} [fn=console.log] - Optional function for customized logging behavior.
|
|
71
|
+
* @returns {MiddlewareHandler} The middleware handler function.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const app = new App()
|
|
76
|
+
*
|
|
77
|
+
* app.use(logger())
|
|
78
|
+
* app.get('/', (c) => c.text('Hello App!'))
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export const logger = (fn: PrintFunc = console.log): MiddlewareHandler => {
|
|
82
|
+
return async function logger(c, next) {
|
|
83
|
+
const { method, url } = c.req
|
|
84
|
+
|
|
85
|
+
const path = url.slice(url.indexOf('/', 8))
|
|
86
|
+
|
|
87
|
+
await log(fn, LogPrefix.Incoming, method, path)
|
|
88
|
+
|
|
89
|
+
const start = Date.now()
|
|
90
|
+
|
|
91
|
+
await next()
|
|
92
|
+
|
|
93
|
+
await log(fn, LogPrefix.Outgoing, method, path, c.res.status, time(start))
|
|
94
|
+
}
|
|
95
|
+
}
|