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,70 @@
|
|
|
1
|
+
import type { HTTPExceptionFunction } from './index.ts'
|
|
2
|
+
import { timeout } from './index.ts'
|
|
3
|
+
import type { Context } from '../../context.ts'
|
|
4
|
+
import { App } from '../../app.ts'
|
|
5
|
+
import { HTTPException } from '../../http-exception.ts'
|
|
6
|
+
|
|
7
|
+
describe('Timeout API', () => {
|
|
8
|
+
const app = new App()
|
|
9
|
+
|
|
10
|
+
app.use('/slow-endpoint', timeout(1000))
|
|
11
|
+
app.use(
|
|
12
|
+
'/slow-endpoint/custom',
|
|
13
|
+
timeout(
|
|
14
|
+
1100,
|
|
15
|
+
() => new HTTPException(408, { message: 'Request timeout. Please try again later.' })
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
const exception500: HTTPExceptionFunction = (context: Context) =>
|
|
19
|
+
new HTTPException(500, { message: `Internal Server Error at ${context.req.path}` })
|
|
20
|
+
app.use('/slow-endpoint/error', timeout(1200, exception500))
|
|
21
|
+
app.use('/normal-endpoint', timeout(1000))
|
|
22
|
+
|
|
23
|
+
app.get('/slow-endpoint', async (c) => {
|
|
24
|
+
await new Promise((resolve) => setTimeout(resolve, 1100))
|
|
25
|
+
return c.text('This should not show up')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
app.get('/slow-endpoint/custom', async (c) => {
|
|
29
|
+
await new Promise((resolve) => setTimeout(resolve, 1200))
|
|
30
|
+
return c.text('This should not show up')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
app.get('/slow-endpoint/error', async (c) => {
|
|
34
|
+
await new Promise((resolve) => setTimeout(resolve, 1300))
|
|
35
|
+
return c.text('This should not show up')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
app.get('/normal-endpoint', async (c) => {
|
|
39
|
+
await new Promise((resolve) => setTimeout(resolve, 900))
|
|
40
|
+
return c.text('This should not show up')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('Should trigger default timeout exception', async () => {
|
|
44
|
+
const res = await app.request('http://localhost/slow-endpoint')
|
|
45
|
+
expect(res).not.toBeNull()
|
|
46
|
+
expect(res.status).toBe(504)
|
|
47
|
+
expect(await res.text()).toContain('Gateway Timeout')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('Should apply custom exception with function', async () => {
|
|
51
|
+
const res = await app.request('http://localhost/slow-endpoint/custom')
|
|
52
|
+
expect(res).not.toBeNull()
|
|
53
|
+
expect(res.status).toBe(408)
|
|
54
|
+
expect(await res.text()).toContain('Request timeout. Please try again later.')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('Error timeout with custom status code and message', async () => {
|
|
58
|
+
const res = await app.request('http://localhost/slow-endpoint/error')
|
|
59
|
+
expect(res).not.toBeNull()
|
|
60
|
+
expect(res.status).toBe(500)
|
|
61
|
+
expect(await res.text()).toContain('Internal Server Error at /slow-endpoint/error')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('No Timeout should pass', async () => {
|
|
65
|
+
const res = await app.request('http://localhost/normal-endpoint')
|
|
66
|
+
expect(res).not.toBeNull()
|
|
67
|
+
expect(res.status).toBe(200)
|
|
68
|
+
expect(await res.text()).toContain('This should not show up')
|
|
69
|
+
})
|
|
70
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Timeout Middleware for App.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Context } from '../../context.ts'
|
|
7
|
+
import { HTTPException } from '../../http-exception.ts'
|
|
8
|
+
import type { MiddlewareHandler } from '../../types.ts'
|
|
9
|
+
|
|
10
|
+
export type HTTPExceptionFunction = (context: Context) => HTTPException
|
|
11
|
+
|
|
12
|
+
const defaultTimeoutException = new HTTPException(504, {
|
|
13
|
+
message: 'Gateway Timeout',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Timeout Middleware for App.
|
|
18
|
+
*
|
|
19
|
+
* @param {number} duration - The timeout duration in milliseconds.
|
|
20
|
+
* @param {HTTPExceptionFunction | HTTPException} [exception=defaultTimeoutException] - The exception to throw when the timeout occurs. Can be a function that returns an HTTPException or an HTTPException object.
|
|
21
|
+
* @returns {MiddlewareHandler} The middleware handler function.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const app = new App()
|
|
26
|
+
*
|
|
27
|
+
* app.use(
|
|
28
|
+
* '/long-request',
|
|
29
|
+
* timeout(5000) // Set timeout to 5 seconds
|
|
30
|
+
* )
|
|
31
|
+
*
|
|
32
|
+
* app.get('/long-request', async (c) => {
|
|
33
|
+
* await someLongRunningFunction()
|
|
34
|
+
* return c.text('Completed within time limit')
|
|
35
|
+
* })
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export const timeout = (
|
|
39
|
+
duration: number,
|
|
40
|
+
exception: HTTPExceptionFunction | HTTPException = defaultTimeoutException
|
|
41
|
+
): MiddlewareHandler => {
|
|
42
|
+
return async function timeout(context, next) {
|
|
43
|
+
let timer: number | undefined
|
|
44
|
+
const timeoutPromise = new Promise<void>((_, reject) => {
|
|
45
|
+
timer = setTimeout(() => {
|
|
46
|
+
reject(typeof exception === 'function' ? exception(context) : exception)
|
|
47
|
+
}, duration) as unknown as number
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
await Promise.race([next(), timeoutPromise])
|
|
52
|
+
} finally {
|
|
53
|
+
if (timer !== undefined) {
|
|
54
|
+
clearTimeout(timer)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { endTime, setMetric, startTime, timing, wrapTime } from './index.ts'
|
|
2
|
+
import { App } from '../../app.ts'
|
|
3
|
+
|
|
4
|
+
describe('Server-Timing API', () => {
|
|
5
|
+
const app = new App()
|
|
6
|
+
|
|
7
|
+
const totalDescription = 'my total DescRipTion!'
|
|
8
|
+
const name = 'sleep'
|
|
9
|
+
const region = 'region'
|
|
10
|
+
const regionDesc = 'europe-west3'
|
|
11
|
+
|
|
12
|
+
app.use(
|
|
13
|
+
'*',
|
|
14
|
+
timing({
|
|
15
|
+
totalDescription,
|
|
16
|
+
})
|
|
17
|
+
)
|
|
18
|
+
app.get('/', (c) => c.text('/'))
|
|
19
|
+
app.get('/api', async (c) => {
|
|
20
|
+
startTime(c, name)
|
|
21
|
+
await new Promise((r) => setTimeout(r, 30))
|
|
22
|
+
endTime(c, name)
|
|
23
|
+
|
|
24
|
+
return c.text('api!')
|
|
25
|
+
})
|
|
26
|
+
app.get('/api-wrap', async (c) => {
|
|
27
|
+
await wrapTime(c, name, new Promise((r) => setTimeout(r, 30)))
|
|
28
|
+
|
|
29
|
+
return c.text('api!')
|
|
30
|
+
})
|
|
31
|
+
app.get('/api-wrap-throw', async (c) => {
|
|
32
|
+
try {
|
|
33
|
+
await wrapTime(c, name, new Promise((_, r) => setTimeout(r, 30)))
|
|
34
|
+
} catch (e) {
|
|
35
|
+
return c.text(`error :( ${e}`, 500)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return c.text('api!')
|
|
39
|
+
})
|
|
40
|
+
app.get('/cache', async (c) => {
|
|
41
|
+
setMetric(c, region, regionDesc)
|
|
42
|
+
|
|
43
|
+
return c.text('cache!')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const sub = new App()
|
|
47
|
+
|
|
48
|
+
sub.use(timing())
|
|
49
|
+
sub.get('/', (c) => c.text('sub'))
|
|
50
|
+
app.route('/sub', sub)
|
|
51
|
+
|
|
52
|
+
it('Should contain total duration', async () => {
|
|
53
|
+
const res = await app.request('http://localhost/')
|
|
54
|
+
expect(res).not.toBeNull()
|
|
55
|
+
expect(res.headers.has('server-timing')).toBeTruthy()
|
|
56
|
+
expect(res.headers.get('server-timing')?.includes('total;dur=')).toBeTruthy()
|
|
57
|
+
expect(res.headers.get('server-timing')?.includes(totalDescription)).toBeTruthy()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('Should contain value metrics', async () => {
|
|
61
|
+
const res = await app.request('http://localhost/api')
|
|
62
|
+
expect(res).not.toBeNull()
|
|
63
|
+
expect(res.headers.has('server-timing')).toBeTruthy()
|
|
64
|
+
expect(res.headers.get('server-timing')?.includes(`${name};dur=`)).toBeTruthy()
|
|
65
|
+
expect(res.headers.get('server-timing')?.includes(name)).toBeTruthy()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('Should contain value metrics, wrapped', async () => {
|
|
69
|
+
const res = await app.request('http://localhost/api-wrap')
|
|
70
|
+
expect(res).not.toBeNull()
|
|
71
|
+
expect(res.headers.has('server-timing')).toBeTruthy()
|
|
72
|
+
expect(res.headers.get('server-timing')?.includes(`${name};dur=`)).toBeTruthy()
|
|
73
|
+
expect(res.headers.get('server-timing')?.includes(name)).toBeTruthy()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('Should contain value metrics, wrapped throw', async () => {
|
|
77
|
+
const res = await app.request('http://localhost/api-wrap-throw')
|
|
78
|
+
expect(res).not.toBeNull()
|
|
79
|
+
expect(res.headers.has('server-timing')).toBeTruthy()
|
|
80
|
+
expect(res.status).toBeGreaterThanOrEqual(500)
|
|
81
|
+
expect(res.headers.get('server-timing')?.includes(`${name};dur=`)).toBeTruthy()
|
|
82
|
+
expect(res.headers.get('server-timing')?.includes(name)).toBeTruthy()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('Should contain value-less metrics', async () => {
|
|
86
|
+
const res = await app.request('http://localhost/cache')
|
|
87
|
+
expect(res).not.toBeNull()
|
|
88
|
+
expect(res.headers.has('server-timing')).toBeTruthy()
|
|
89
|
+
expect(
|
|
90
|
+
res.headers.get('server-timing')?.includes(`${region};desc="${regionDesc}"`)
|
|
91
|
+
).toBeTruthy()
|
|
92
|
+
expect(res.headers.get('server-timing')?.includes(region)).toBeTruthy()
|
|
93
|
+
expect(res.headers.get('server-timing')?.includes(regionDesc)).toBeTruthy()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('Should not be enabled if the main app has the timing middleware', async () => {
|
|
97
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn')
|
|
98
|
+
const res = await app.request('/sub')
|
|
99
|
+
expect(res.status).toBe(200)
|
|
100
|
+
expect(res.headers.has('server-timing')).toBeTruthy()
|
|
101
|
+
expect(res.headers.get('server-timing')?.includes(totalDescription)).toBeTruthy()
|
|
102
|
+
expect(consoleWarnSpy).not.toHaveBeenCalled()
|
|
103
|
+
consoleWarnSpy.mockRestore()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('Should handle timing options', async () => {
|
|
107
|
+
it('Should handle autoEnd option', async () => {
|
|
108
|
+
const autoEndApp = new App()
|
|
109
|
+
|
|
110
|
+
autoEndApp.use('*', timing())
|
|
111
|
+
autoEndApp.get('/', (c) => {
|
|
112
|
+
startTime(c, 'test')
|
|
113
|
+
return c.text('/')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const res = await autoEndApp.request('/')
|
|
117
|
+
|
|
118
|
+
expect(res.status).toBe(200)
|
|
119
|
+
expect(res.headers.get('server-timing')).toContain('test;dur=')
|
|
120
|
+
|
|
121
|
+
const disabledAutoEndApp = new App()
|
|
122
|
+
|
|
123
|
+
disabledAutoEndApp.use('*', timing({ autoEnd: false }))
|
|
124
|
+
disabledAutoEndApp.get('/', (c) => {
|
|
125
|
+
startTime(c, 'test')
|
|
126
|
+
return c.text('/')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const disabledRes = await disabledAutoEndApp.request('/')
|
|
130
|
+
|
|
131
|
+
expect(disabledRes.status).toBe(200)
|
|
132
|
+
expect(disabledRes.headers.get('server-timing')).not.toContain('test;dur=')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('Should use enabled function return value', async () => {
|
|
136
|
+
const enabledApp = new App()
|
|
137
|
+
const enabled = vi.fn(() => false)
|
|
138
|
+
|
|
139
|
+
enabledApp.use('*', timing({ enabled }))
|
|
140
|
+
enabledApp.get('/', (c) => c.text('/'))
|
|
141
|
+
|
|
142
|
+
const res = await enabledApp.request('/')
|
|
143
|
+
|
|
144
|
+
expect(res.status).toBe(200)
|
|
145
|
+
expect(enabled).toHaveBeenCalled()
|
|
146
|
+
expect(res.headers.has('server-timing')).toBeFalsy()
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('Should handle total false and value-less metrics without description', async () => {
|
|
150
|
+
const metricApp = new App()
|
|
151
|
+
|
|
152
|
+
metricApp.use('*', timing({ total: false }))
|
|
153
|
+
metricApp.get('/', (c) => {
|
|
154
|
+
setMetric(c, 'test')
|
|
155
|
+
return c.text('/')
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const res = await metricApp.request('/')
|
|
159
|
+
|
|
160
|
+
expect(res.status).toBe(200)
|
|
161
|
+
expect(res.headers.get('server-timing')).toBe('test')
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe('Should handle crossOrigin setting', async () => {
|
|
166
|
+
it('Should do nothing when crossOrigin is falsy', async () => {
|
|
167
|
+
const crossOriginApp = new App()
|
|
168
|
+
|
|
169
|
+
crossOriginApp.use(
|
|
170
|
+
'*',
|
|
171
|
+
timing({
|
|
172
|
+
crossOrigin: false,
|
|
173
|
+
})
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
crossOriginApp.get('/', (c) => c.text('/'))
|
|
177
|
+
|
|
178
|
+
const res = await crossOriginApp.request('http://localhost/')
|
|
179
|
+
|
|
180
|
+
expect(res).not.toBeNull()
|
|
181
|
+
expect(res.headers.has('server-timing')).toBeTruthy()
|
|
182
|
+
expect(res.headers.has('timing-allow-origin')).toBeFalsy()
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('Should set Timing-Allow-Origin to * when crossOrigin is true', async () => {
|
|
186
|
+
const crossOriginApp = new App()
|
|
187
|
+
|
|
188
|
+
crossOriginApp.use(
|
|
189
|
+
'*',
|
|
190
|
+
timing({
|
|
191
|
+
crossOrigin: true,
|
|
192
|
+
})
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
crossOriginApp.get('/', (c) => c.text('/'))
|
|
196
|
+
|
|
197
|
+
const res = await crossOriginApp.request('http://localhost/')
|
|
198
|
+
|
|
199
|
+
expect(res).not.toBeNull()
|
|
200
|
+
expect(res.headers.has('server-timing')).toBeTruthy()
|
|
201
|
+
expect(res.headers.has('timing-allow-origin')).toBeTruthy()
|
|
202
|
+
expect(res.headers.get('timing-allow-origin')).toBe('*')
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('Should set Timing-Allow-Origin to the value of crossOrigin when it is a string', async () => {
|
|
206
|
+
const crossOriginApp = new App()
|
|
207
|
+
|
|
208
|
+
crossOriginApp.use(
|
|
209
|
+
'*',
|
|
210
|
+
timing({
|
|
211
|
+
crossOrigin: 'https://example.com',
|
|
212
|
+
})
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
crossOriginApp.get('/', (c) => c.text('/'))
|
|
216
|
+
|
|
217
|
+
const res = await crossOriginApp.request('http://localhost/')
|
|
218
|
+
|
|
219
|
+
expect(res).not.toBeNull()
|
|
220
|
+
expect(res.headers.has('server-timing')).toBeTruthy()
|
|
221
|
+
expect(res.headers.has('timing-allow-origin')).toBeTruthy()
|
|
222
|
+
expect(res.headers.get('timing-allow-origin')).toBe('https://example.com')
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('Should set Timing-Allow-Origin to the return value of crossOrigin when it is a function', async () => {
|
|
226
|
+
const crossOriginApp = new App()
|
|
227
|
+
|
|
228
|
+
crossOriginApp.use(
|
|
229
|
+
'*',
|
|
230
|
+
timing({
|
|
231
|
+
crossOrigin: (c) => c.req.header('origin') ?? '*',
|
|
232
|
+
})
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
crossOriginApp.get('/', (c) => c.text('/'))
|
|
236
|
+
|
|
237
|
+
const res = await crossOriginApp.request('http://localhost/', {
|
|
238
|
+
headers: {
|
|
239
|
+
origin: 'https://example.com',
|
|
240
|
+
},
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
expect(res).not.toBeNull()
|
|
244
|
+
expect(res.headers.has('server-timing')).toBeTruthy()
|
|
245
|
+
expect(res.headers.has('timing-allow-origin')).toBeTruthy()
|
|
246
|
+
expect(res.headers.get('timing-allow-origin')).toBe('https://example.com')
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it("Should use Date.now as backup if performance API isn't available", async () => {
|
|
250
|
+
const originalPerformance = globalThis.performance
|
|
251
|
+
// @ts-expect-error disable performance API for testing
|
|
252
|
+
delete globalThis.performance
|
|
253
|
+
const DateNowSpy = vi.spyOn(Date, 'now').mockReturnValue(123456789)
|
|
254
|
+
|
|
255
|
+
const app = new App().use(timing()).get('/', (c) => {
|
|
256
|
+
startTime(c, 'test')
|
|
257
|
+
endTime(c, 'test')
|
|
258
|
+
return c.text('hello')
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const res = await app.request('/')
|
|
262
|
+
expect(res.status).toBe(200)
|
|
263
|
+
|
|
264
|
+
expect(DateNowSpy).toHaveBeenCalled()
|
|
265
|
+
|
|
266
|
+
DateNowSpy.mockRestore()
|
|
267
|
+
globalThis.performance = originalPerformance
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
describe('Configuration validation', async () => {
|
|
272
|
+
it('Should silently warn when no timing metrics are available', async () => {
|
|
273
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
274
|
+
|
|
275
|
+
const app = new App().get('/', (c) => {
|
|
276
|
+
setMetric(c, 'test', 123)
|
|
277
|
+
startTime(c, 'test')
|
|
278
|
+
endTime(c, 'test')
|
|
279
|
+
return c.text('hello')
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
const res = await app.request('/')
|
|
283
|
+
expect(res.status).toBe(200)
|
|
284
|
+
|
|
285
|
+
expect(consoleWarnSpy).toHaveBeenNthCalledWith(
|
|
286
|
+
2,
|
|
287
|
+
'Metrics not initialized! Please add the `timing()` middleware to this route!'
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
consoleWarnSpy.mockRestore()
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
it('Should silently warn when trying to end a non-existent timer', async () => {
|
|
294
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
295
|
+
|
|
296
|
+
const app = new App().use(timing()).get('/', (c) => {
|
|
297
|
+
setMetric(c, 'region', 'europe-west3')
|
|
298
|
+
endTime(c, 'nonExistentTimer')
|
|
299
|
+
return c.text('hello')
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
const res = await app.request('/')
|
|
303
|
+
expect(res.status).toBe(200)
|
|
304
|
+
|
|
305
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith('Timer "nonExistentTimer" does not exist!')
|
|
306
|
+
|
|
307
|
+
consoleWarnSpy.mockRestore()
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TimingVariables } from './timing.ts'
|
|
2
|
+
|
|
3
|
+
export type { TimingVariables }
|
|
4
|
+
export { timing, setMetric, startTime, endTime, wrapTime } from './timing.ts'
|
|
5
|
+
|
|
6
|
+
declare module '../../context.ts' {
|
|
7
|
+
interface ContextVariableMap extends TimingVariables {}
|
|
8
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Server-Timing Middleware for App.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Context } from '../../context.ts'
|
|
7
|
+
import type { MiddlewareHandler } from '../../types.ts'
|
|
8
|
+
import '../../context.ts'
|
|
9
|
+
|
|
10
|
+
export type TimingVariables = {
|
|
11
|
+
metric?: {
|
|
12
|
+
headers: string[]
|
|
13
|
+
timers: Map<string, Timer>
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface Timer {
|
|
18
|
+
description?: string
|
|
19
|
+
start: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface TimingOptions {
|
|
23
|
+
total?: boolean
|
|
24
|
+
enabled?: boolean | ((c: Context) => boolean)
|
|
25
|
+
totalDescription?: string
|
|
26
|
+
autoEnd?: boolean
|
|
27
|
+
crossOrigin?: boolean | string | ((c: Context) => boolean | string)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const getTime = (): number => {
|
|
31
|
+
try {
|
|
32
|
+
return performance.now()
|
|
33
|
+
} catch {}
|
|
34
|
+
return Date.now()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Server-Timing Middleware for App.
|
|
39
|
+
*
|
|
40
|
+
*
|
|
41
|
+
* @param {TimingOptions} [config] - The options for the timing middleware.
|
|
42
|
+
* @param {boolean} [config.total=true] - Show the total response time.
|
|
43
|
+
* @param {boolean | ((c: Context) => boolean)} [config.enabled=true] - Whether timings should be added to the headers or not.
|
|
44
|
+
* @param {string} [config.totalDescription=Total Response Time] - Description for the total response time.
|
|
45
|
+
* @param {boolean} [config.autoEnd=true] - If `startTime()` should end automatically at the end of the request.
|
|
46
|
+
* @param {boolean | string | ((c: Context) => boolean | string)} [config.crossOrigin=false] - The origin this timings header should be readable.
|
|
47
|
+
* @returns {MiddlewareHandler} The middleware handler function.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const app = new App()
|
|
52
|
+
*
|
|
53
|
+
* // add the middleware to your router
|
|
54
|
+
* app.use(timing());
|
|
55
|
+
*
|
|
56
|
+
* app.get('/', async (c) => {
|
|
57
|
+
* // add custom metrics
|
|
58
|
+
* setMetric(c, 'region', 'europe-west3')
|
|
59
|
+
*
|
|
60
|
+
* // add custom metrics with timing, must be in milliseconds
|
|
61
|
+
* setMetric(c, 'custom', 23.8, 'My custom Metric')
|
|
62
|
+
*
|
|
63
|
+
* // start a new timer
|
|
64
|
+
* startTime(c, 'db');
|
|
65
|
+
*
|
|
66
|
+
* const data = await db.findMany(...);
|
|
67
|
+
*
|
|
68
|
+
* // end the timer
|
|
69
|
+
* endTime(c, 'db');
|
|
70
|
+
*
|
|
71
|
+
* return c.json({ response: data });
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export const timing = (config?: TimingOptions): MiddlewareHandler => {
|
|
76
|
+
const options: TimingOptions = {
|
|
77
|
+
total: true,
|
|
78
|
+
enabled: true,
|
|
79
|
+
totalDescription: 'Total Response Time',
|
|
80
|
+
autoEnd: true,
|
|
81
|
+
crossOrigin: false,
|
|
82
|
+
...config,
|
|
83
|
+
}
|
|
84
|
+
return async function timing(c, next) {
|
|
85
|
+
const headers: string[] = []
|
|
86
|
+
const timers = new Map<string, Timer>()
|
|
87
|
+
|
|
88
|
+
if (c.get('metric')) {
|
|
89
|
+
return await next()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
c.set('metric', { headers, timers })
|
|
93
|
+
|
|
94
|
+
if (options.total) {
|
|
95
|
+
startTime(c, 'total', options.totalDescription)
|
|
96
|
+
}
|
|
97
|
+
await next()
|
|
98
|
+
|
|
99
|
+
if (options.total) {
|
|
100
|
+
endTime(c, 'total')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (options.autoEnd) {
|
|
104
|
+
timers.forEach((_, key) => {
|
|
105
|
+
endTime(c, key)
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const enabled = typeof options.enabled === 'function' ? options.enabled(c) : options.enabled
|
|
110
|
+
|
|
111
|
+
if (enabled) {
|
|
112
|
+
c.res.headers.append('Server-Timing', headers.join(','))
|
|
113
|
+
|
|
114
|
+
const crossOrigin =
|
|
115
|
+
typeof options.crossOrigin === 'function' ? options.crossOrigin(c) : options.crossOrigin
|
|
116
|
+
|
|
117
|
+
if (crossOrigin) {
|
|
118
|
+
c.res.headers.append(
|
|
119
|
+
'Timing-Allow-Origin',
|
|
120
|
+
typeof crossOrigin === 'string' ? crossOrigin : '*'
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface SetMetric {
|
|
128
|
+
(c: Context, name: string, value: number, description?: string, precision?: number): void
|
|
129
|
+
|
|
130
|
+
(c: Context, name: string, description?: string): void
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Set a metric for the timing middleware.
|
|
135
|
+
*
|
|
136
|
+
* @param {Context} c - The context of the request.
|
|
137
|
+
* @param {string} name - The name of the metric.
|
|
138
|
+
* @param {number | string} [valueDescription] - The value or description of the metric.
|
|
139
|
+
* @param {string} [description] - The description of the metric.
|
|
140
|
+
* @param {number} [precision] - The precision of the metric value.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* setMetric(c, 'region', 'europe-west3')
|
|
145
|
+
* setMetric(c, 'custom', 23.8, 'My custom Metric')
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export const setMetric: SetMetric = (
|
|
149
|
+
c: Context,
|
|
150
|
+
name: string,
|
|
151
|
+
valueDescription: number | string | undefined,
|
|
152
|
+
description?: string,
|
|
153
|
+
precision?: number
|
|
154
|
+
) => {
|
|
155
|
+
const metrics = c.get('metric')
|
|
156
|
+
if (!metrics) {
|
|
157
|
+
console.warn('Metrics not initialized! Please add the `timing()` middleware to this route!')
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
if (typeof valueDescription === 'number') {
|
|
161
|
+
const dur = valueDescription.toFixed(precision || 1)
|
|
162
|
+
|
|
163
|
+
const metric = description ? `${name};dur=${dur};desc="${description}"` : `${name};dur=${dur}`
|
|
164
|
+
|
|
165
|
+
metrics.headers.push(metric)
|
|
166
|
+
} else {
|
|
167
|
+
// Value-less metric
|
|
168
|
+
const metric = valueDescription ? `${name};desc="${valueDescription}"` : `${name}`
|
|
169
|
+
|
|
170
|
+
metrics.headers.push(metric)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Start a timer for the timing middleware.
|
|
176
|
+
*
|
|
177
|
+
* @param {Context} c - The context of the request.
|
|
178
|
+
* @param {string} name - The name of the timer.
|
|
179
|
+
* @param {string} [description] - The description of the timer.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* startTime(c, 'db')
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
export const startTime = (c: Context, name: string, description?: string) => {
|
|
187
|
+
const metrics = c.get('metric')
|
|
188
|
+
if (!metrics) {
|
|
189
|
+
console.warn('Metrics not initialized! Please add the `timing()` middleware to this route!')
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
metrics.timers.set(name, { description, start: getTime() })
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* End a timer for the timing middleware.
|
|
197
|
+
*
|
|
198
|
+
* @param {Context} c - The context of the request.
|
|
199
|
+
* @param {string} name - The name of the timer.
|
|
200
|
+
* @param {number} [precision] - The precision of the timer value.
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```ts
|
|
204
|
+
* endTime(c, 'db')
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
export const endTime = (c: Context, name: string, precision?: number) => {
|
|
208
|
+
const metrics = c.get('metric')
|
|
209
|
+
if (!metrics) {
|
|
210
|
+
console.warn('Metrics not initialized! Please add the `timing()` middleware to this route!')
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
const timer = metrics.timers.get(name)
|
|
214
|
+
if (!timer) {
|
|
215
|
+
console.warn(`Timer "${name}" does not exist!`)
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
const { description, start } = timer
|
|
219
|
+
|
|
220
|
+
const duration = getTime() - start
|
|
221
|
+
|
|
222
|
+
setMetric(c, name, duration, description, precision)
|
|
223
|
+
metrics.timers.delete(name)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Wrap a Promise to capture its duration.
|
|
228
|
+
* @param {Context} c - The context of the request.
|
|
229
|
+
* @param {string} name - The name of the timer.
|
|
230
|
+
* @param {Promise<T>} callable - The Promise to time.
|
|
231
|
+
* @param {string} [description] - The description of the timer.
|
|
232
|
+
* @param {number} [precision] - The precision of the timer value.
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```ts
|
|
236
|
+
* // Instead of this:
|
|
237
|
+
* const data = await db.findMany(...);
|
|
238
|
+
*
|
|
239
|
+
* // do this:
|
|
240
|
+
* const data = await wrapTime(c, 'query', db.findMany(...));
|
|
241
|
+
* ```
|
|
242
|
+
* */
|
|
243
|
+
export async function wrapTime<T>(
|
|
244
|
+
c: Context,
|
|
245
|
+
name: string,
|
|
246
|
+
callable: Promise<T>,
|
|
247
|
+
description?: string,
|
|
248
|
+
precision?: number
|
|
249
|
+
): Promise<T> {
|
|
250
|
+
startTime(c, name, description)
|
|
251
|
+
try {
|
|
252
|
+
return await callable
|
|
253
|
+
} finally {
|
|
254
|
+
endTime(c, name, precision)
|
|
255
|
+
}
|
|
256
|
+
}
|