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,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Method Override Middleware for App.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Context, ExecutionContext } from '../../context.ts'
|
|
7
|
+
import type { App } from '../../app.ts'
|
|
8
|
+
import type { MiddlewareHandler } from '../../types.ts'
|
|
9
|
+
import { parseBody } from '../../utils/body.ts'
|
|
10
|
+
|
|
11
|
+
type MethodOverrideOptions = {
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
app: App<any, any, any>
|
|
14
|
+
} & (
|
|
15
|
+
| {
|
|
16
|
+
// Default is 'form' and the value is `_method`
|
|
17
|
+
form?: string
|
|
18
|
+
header?: never
|
|
19
|
+
query?: never
|
|
20
|
+
}
|
|
21
|
+
| {
|
|
22
|
+
form?: never
|
|
23
|
+
header: string
|
|
24
|
+
query?: never
|
|
25
|
+
}
|
|
26
|
+
| {
|
|
27
|
+
form?: never
|
|
28
|
+
header?: never
|
|
29
|
+
query: string
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
const DEFAULT_METHOD_FORM_NAME = '_method'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Method Override Middleware for App.
|
|
37
|
+
*
|
|
38
|
+
*
|
|
39
|
+
* @param {MethodOverrideOptions} options - The options for the method override middleware.
|
|
40
|
+
* @param {App} options.app - The instance of App is used in your application.
|
|
41
|
+
* @param {string} [options.form=_method] - Form key with a value containing the method name.
|
|
42
|
+
* @param {string} [options.header] - Header name with a value containing the method name.
|
|
43
|
+
* @param {string} [options.query] - Query parameter key with a value containing the method name.
|
|
44
|
+
* @returns {MiddlewareHandler} The middleware handler function.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const app = new App()
|
|
49
|
+
*
|
|
50
|
+
* // If no options are specified, the value of `_method` in the form,
|
|
51
|
+
* // e.g. DELETE, is used as the method.
|
|
52
|
+
* app.use('/posts', methodOverride({ app }))
|
|
53
|
+
*
|
|
54
|
+
* app.delete('/posts', (c) => {
|
|
55
|
+
* // ....
|
|
56
|
+
* })
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export const methodOverride = (options: MethodOverrideOptions): MiddlewareHandler =>
|
|
60
|
+
async function methodOverride(c, next) {
|
|
61
|
+
if (c.req.method === 'GET') {
|
|
62
|
+
return await next()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const app = options.app
|
|
66
|
+
// Method override by form
|
|
67
|
+
if (!(options.header || options.query)) {
|
|
68
|
+
const contentType = c.req.header('content-type')
|
|
69
|
+
const methodFormName = options.form || DEFAULT_METHOD_FORM_NAME
|
|
70
|
+
const clonedRequest = c.req.raw.clone()
|
|
71
|
+
const newRequest = clonedRequest.clone()
|
|
72
|
+
// Content-Type is `multipart/form-data`
|
|
73
|
+
if (contentType?.startsWith('multipart/form-data')) {
|
|
74
|
+
const form = await clonedRequest.formData()
|
|
75
|
+
const method = form.get(methodFormName)
|
|
76
|
+
if (method) {
|
|
77
|
+
const newForm = await newRequest.formData()
|
|
78
|
+
newForm.delete(methodFormName)
|
|
79
|
+
const newHeaders = new Headers(clonedRequest.headers)
|
|
80
|
+
newHeaders.delete('content-type')
|
|
81
|
+
newHeaders.delete('content-length')
|
|
82
|
+
const request = new Request(c.req.url, {
|
|
83
|
+
body: newForm,
|
|
84
|
+
headers: newHeaders,
|
|
85
|
+
method: method as string,
|
|
86
|
+
})
|
|
87
|
+
return app.fetch(request, c.env, getExecutionCtx(c))
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Content-Type is `application/x-www-form-urlencoded`
|
|
91
|
+
if (contentType?.startsWith('application/x-www-form-urlencoded')) {
|
|
92
|
+
const params = await parseBody<Record<string, string>>(clonedRequest)
|
|
93
|
+
const method = params[methodFormName]
|
|
94
|
+
if (method) {
|
|
95
|
+
delete params[methodFormName]
|
|
96
|
+
const newParams = new URLSearchParams(params)
|
|
97
|
+
const request = new Request(newRequest, {
|
|
98
|
+
body: newParams,
|
|
99
|
+
method: method as string,
|
|
100
|
+
})
|
|
101
|
+
return app.fetch(request, c.env, getExecutionCtx(c))
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Method override by header
|
|
106
|
+
else if (options.header) {
|
|
107
|
+
const headerName = options.header
|
|
108
|
+
const method = c.req.header(headerName)
|
|
109
|
+
if (method) {
|
|
110
|
+
const newHeaders = new Headers(c.req.raw.headers)
|
|
111
|
+
newHeaders.delete(headerName)
|
|
112
|
+
const request = new Request(c.req.raw, {
|
|
113
|
+
headers: newHeaders,
|
|
114
|
+
method,
|
|
115
|
+
})
|
|
116
|
+
return app.fetch(request, c.env, getExecutionCtx(c))
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Method override by query
|
|
120
|
+
else if (options.query) {
|
|
121
|
+
const queryName = options.query
|
|
122
|
+
const method = c.req.query(queryName)
|
|
123
|
+
if (method) {
|
|
124
|
+
const url = new URL(c.req.url)
|
|
125
|
+
url.searchParams.delete(queryName)
|
|
126
|
+
const requestInit: RequestInit & { duplex?: 'half' } = {
|
|
127
|
+
body: c.req.raw.body,
|
|
128
|
+
headers: c.req.raw.headers,
|
|
129
|
+
method,
|
|
130
|
+
duplex: c.req.raw.body ? 'half' : undefined,
|
|
131
|
+
}
|
|
132
|
+
const request = new Request(url.toString(), requestInit)
|
|
133
|
+
return app.fetch(request, c.env, getExecutionCtx(c))
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
await next()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const getExecutionCtx = (c: Context) => {
|
|
140
|
+
let executionCtx: ExecutionContext | undefined
|
|
141
|
+
try {
|
|
142
|
+
executionCtx = c.executionCtx
|
|
143
|
+
} catch {
|
|
144
|
+
// Do nothing
|
|
145
|
+
}
|
|
146
|
+
return executionCtx
|
|
147
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { poweredBy } from './index.ts'
|
|
2
|
+
import { App } from '../../app.ts'
|
|
3
|
+
|
|
4
|
+
describe('Powered by Middleware', () => {
|
|
5
|
+
const app = new App()
|
|
6
|
+
|
|
7
|
+
app.use('/poweredBy/*', poweredBy())
|
|
8
|
+
app.get('/poweredBy', (c) => c.text('root'))
|
|
9
|
+
|
|
10
|
+
app.use('/poweredBy2/*', poweredBy())
|
|
11
|
+
app.use('/poweredBy2/*', poweredBy())
|
|
12
|
+
app.get('/poweredBy2', (c) => c.text('root'))
|
|
13
|
+
|
|
14
|
+
app.use('/poweredBy3/*', poweredBy({ serverName: 'Foo' }))
|
|
15
|
+
app.get('/poweredBy3', (c) => c.text('root'))
|
|
16
|
+
|
|
17
|
+
it('Should return with X-Powered-By header', async () => {
|
|
18
|
+
const res = await app.request('http://localhost/poweredBy')
|
|
19
|
+
expect(res).not.toBeNull()
|
|
20
|
+
expect(res.status).toBe(200)
|
|
21
|
+
expect(res.headers.get('X-Powered-By')).toBe('ACRYL')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('Should not return duplicate values', async () => {
|
|
25
|
+
const res = await app.request('http://localhost/poweredBy2')
|
|
26
|
+
expect(res).not.toBeNull()
|
|
27
|
+
expect(res.status).toBe(200)
|
|
28
|
+
expect(res.headers.get('X-Powered-By')).toBe('ACRYL')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('Should return custom serverName', async () => {
|
|
32
|
+
const res = await app.request('http://localhost/poweredBy3')
|
|
33
|
+
expect(res).not.toBeNull()
|
|
34
|
+
expect(res.status).toBe(200)
|
|
35
|
+
expect(res.headers.get('X-Powered-By')).toBe('Foo')
|
|
36
|
+
})
|
|
37
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Powered By Middleware for App.
|
|
4
|
+
*/
|
|
5
|
+
import type { MiddlewareHandler } from '../../types.ts'
|
|
6
|
+
|
|
7
|
+
type PoweredByOptions = {
|
|
8
|
+
/**
|
|
9
|
+
* The value for X-Powered-By header.
|
|
10
|
+
* @default App
|
|
11
|
+
*/
|
|
12
|
+
serverName?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Powered By Middleware for App.
|
|
17
|
+
*
|
|
18
|
+
* @param options - The options for the Powered By Middleware.
|
|
19
|
+
* @returns {MiddlewareHandler} The middleware handler function.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { poweredBy } from 'acryl-router/middleware'
|
|
24
|
+
*
|
|
25
|
+
* const app = new App()
|
|
26
|
+
*
|
|
27
|
+
* app.use(poweredBy()) // With options: poweredBy({ serverName: "My Server" })
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export const poweredBy = (options?: PoweredByOptions): MiddlewareHandler => {
|
|
31
|
+
return async function poweredBy(c, next) {
|
|
32
|
+
await next()
|
|
33
|
+
c.res.headers.set('X-Powered-By', options?.serverName ?? 'ACRYL')
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { prettyJSON } from './index.ts'
|
|
2
|
+
import { App } from '../../app.ts'
|
|
3
|
+
|
|
4
|
+
describe('JSON pretty by Middleware', () => {
|
|
5
|
+
it('Should return pretty JSON output', async () => {
|
|
6
|
+
const app = new App()
|
|
7
|
+
app.use('*', prettyJSON())
|
|
8
|
+
app.get('/', (c) => {
|
|
9
|
+
return c.json({ message: 'App!' })
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const res = await app.request('http://localhost/?pretty')
|
|
13
|
+
expect(res).not.toBeNull()
|
|
14
|
+
expect(res.status).toBe(200)
|
|
15
|
+
expect(await res.text()).toBe(`{
|
|
16
|
+
"message": "App!"
|
|
17
|
+
}`)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('Should return pretty JSON output with 4 spaces', async () => {
|
|
21
|
+
const app = new App()
|
|
22
|
+
app.use('*', prettyJSON({ space: 4 }))
|
|
23
|
+
app.get('/', (c) => {
|
|
24
|
+
return c.json({ message: 'App!' })
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const res = await app.request('http://localhost/?pretty')
|
|
28
|
+
expect(res).not.toBeNull()
|
|
29
|
+
expect(res.status).toBe(200)
|
|
30
|
+
expect(await res.text()).toBe(`{
|
|
31
|
+
"message": "App!"
|
|
32
|
+
}`)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('Should return pretty JSON output when middleware received custom query', async () => {
|
|
36
|
+
const targetQuery = 'format'
|
|
37
|
+
|
|
38
|
+
const app = new App()
|
|
39
|
+
app.use(
|
|
40
|
+
'*',
|
|
41
|
+
prettyJSON({
|
|
42
|
+
query: targetQuery,
|
|
43
|
+
})
|
|
44
|
+
)
|
|
45
|
+
app.get('/', (c) =>
|
|
46
|
+
c.json({
|
|
47
|
+
message: 'App!',
|
|
48
|
+
})
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const prettyText = await (await app.request(`?${targetQuery}`)).text()
|
|
52
|
+
expect(prettyText).toBe(`{
|
|
53
|
+
"message": "App!"
|
|
54
|
+
}`)
|
|
55
|
+
const nonPrettyText = await (await app.request('?pretty')).text()
|
|
56
|
+
expect(nonPrettyText).toBe('{"message":"App!"}')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('Should force pretty JSON output when force option is true', async () => {
|
|
60
|
+
const app = new App()
|
|
61
|
+
app.use('*', prettyJSON({ force: true }))
|
|
62
|
+
app.get('/', (c) => {
|
|
63
|
+
return c.json({ message: 'App!' })
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const resWithoutQuery = await (await app.request('http://localhost/')).text()
|
|
67
|
+
expect(resWithoutQuery).toBe(`{
|
|
68
|
+
"message": "App!"
|
|
69
|
+
}`)
|
|
70
|
+
|
|
71
|
+
const resWithQuery = await (await app.request('http://localhost/?pretty')).text()
|
|
72
|
+
expect(resWithQuery).toBe(`{
|
|
73
|
+
"message": "App!"
|
|
74
|
+
}`)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('Should return pretty JSON output for structured JSON content-types (+json)', async () => {
|
|
78
|
+
const app = new App()
|
|
79
|
+
app.use('*', prettyJSON({ force: true }))
|
|
80
|
+
app.get('/problem', (c) => {
|
|
81
|
+
return c.newResponse(JSON.stringify({ type: 'about:blank', title: 'Bad Request' }), 400, {
|
|
82
|
+
'Content-Type': 'application/problem+json',
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
app.get('/jsonapi', (c) => {
|
|
86
|
+
return c.newResponse(JSON.stringify({ data: { id: '1', type: 'articles' } }), 200, {
|
|
87
|
+
'Content-Type': 'application/vnd.api+json',
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const problemRes = await app.request('http://localhost/problem')
|
|
92
|
+
expect(await problemRes.text()).toBe(`{
|
|
93
|
+
"type": "about:blank",
|
|
94
|
+
"title": "Bad Request"
|
|
95
|
+
}`)
|
|
96
|
+
|
|
97
|
+
const jsonApiRes = await app.request('http://localhost/jsonapi')
|
|
98
|
+
expect(await jsonApiRes.text()).toBe(`{
|
|
99
|
+
"data": {
|
|
100
|
+
"id": "1",
|
|
101
|
+
"type": "articles"
|
|
102
|
+
}
|
|
103
|
+
}`)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('Should not touch responses with non-JSON content-types', async () => {
|
|
107
|
+
const app = new App()
|
|
108
|
+
app.use('*', prettyJSON({ force: true }))
|
|
109
|
+
app.get('/text', (c) => c.text('{"message":"App!"}'))
|
|
110
|
+
app.get('/json-seq', (c) => {
|
|
111
|
+
return c.body('{"message":"App!"}', 200, {
|
|
112
|
+
'Content-Type': 'application/json-seq',
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const textRes = await app.request('http://localhost/text')
|
|
117
|
+
expect(await textRes.text()).toBe('{"message":"App!"}')
|
|
118
|
+
|
|
119
|
+
const jsonSeqRes = await app.request('http://localhost/json-seq')
|
|
120
|
+
expect(await jsonSeqRes.text()).toBe('{"message":"App!"}')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('Should not break 204 responses with a JSON content-type', async () => {
|
|
124
|
+
const app = new App()
|
|
125
|
+
app.use('*', prettyJSON({ force: true }))
|
|
126
|
+
app.delete('/x', (c) => {
|
|
127
|
+
c.header('Content-Type', 'application/json')
|
|
128
|
+
return c.body(null, 204)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const res = await app.request('http://localhost/x', { method: 'DELETE' })
|
|
132
|
+
expect(res.status).toBe(204)
|
|
133
|
+
expect(res.body).toBeNull()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('Should not break 304 responses with a JSON content-type', async () => {
|
|
137
|
+
const app = new App()
|
|
138
|
+
app.use('*', prettyJSON({ force: true }))
|
|
139
|
+
app.get('/x', (c) => {
|
|
140
|
+
c.header('Content-Type', 'application/json')
|
|
141
|
+
return c.body(null, 304)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const res = await app.request('http://localhost/x')
|
|
145
|
+
expect(res.status).toBe(304)
|
|
146
|
+
expect(res.body).toBeNull()
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('Should not break empty responses with a JSON content-type', async () => {
|
|
150
|
+
const app = new App()
|
|
151
|
+
app.use('*', prettyJSON())
|
|
152
|
+
app.get('/x', () => new Response('', { headers: { 'Content-Type': 'application/json' } }))
|
|
153
|
+
|
|
154
|
+
const res = await app.request('http://localhost/x?pretty')
|
|
155
|
+
expect(res.status).toBe(200)
|
|
156
|
+
expect(await res.text()).toBe('')
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('Should pass through a response with an invalid JSON body unchanged', async () => {
|
|
160
|
+
const app = new App()
|
|
161
|
+
app.use('*', prettyJSON({ force: true }))
|
|
162
|
+
app.get(
|
|
163
|
+
'/x',
|
|
164
|
+
() =>
|
|
165
|
+
new Response('not json', {
|
|
166
|
+
status: 200,
|
|
167
|
+
headers: {
|
|
168
|
+
'Content-Type': 'application/json',
|
|
169
|
+
'X-Custom': 'custom',
|
|
170
|
+
ETag: '"abc"',
|
|
171
|
+
'Content-Length': '8',
|
|
172
|
+
},
|
|
173
|
+
})
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
const res = await app.request('http://localhost/x')
|
|
177
|
+
expect(res.status).toBe(200)
|
|
178
|
+
expect(res.headers.get('X-Custom')).toBe('custom')
|
|
179
|
+
expect(res.headers.get('ETag')).toBe('"abc"')
|
|
180
|
+
expect(res.headers.get('Content-Length')).toBe('8')
|
|
181
|
+
expect(await res.text()).toBe('not json')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('Should pass through a response whose body was already consumed by another middleware', async () => {
|
|
185
|
+
const app = new App()
|
|
186
|
+
app.use('*', prettyJSON({ force: true }))
|
|
187
|
+
app.use('*', async (c, next) => {
|
|
188
|
+
await next()
|
|
189
|
+
await c.res.text()
|
|
190
|
+
})
|
|
191
|
+
app.get('/x', (c) => c.json({ message: 'App!' }))
|
|
192
|
+
|
|
193
|
+
// The other middleware has read the body, so the bytes are no longer available;
|
|
194
|
+
// pretty JSON must not turn this into a 500 and must leave the response as is.
|
|
195
|
+
const res = await app.request('http://localhost/x')
|
|
196
|
+
expect(res.status).toBe(200)
|
|
197
|
+
expect(res.bodyUsed).toBe(true)
|
|
198
|
+
await expect(res.text()).rejects.toThrow()
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('Should remove a stale Content-Length when the body is prettified', async () => {
|
|
202
|
+
const app = new App()
|
|
203
|
+
app.use('*', prettyJSON({ force: true }))
|
|
204
|
+
app.get(
|
|
205
|
+
'/x',
|
|
206
|
+
() =>
|
|
207
|
+
new Response('{"message":"App!"}', {
|
|
208
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': '19' },
|
|
209
|
+
})
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
const res = await app.request('http://localhost/x')
|
|
213
|
+
expect(await res.text()).toBe(`{
|
|
214
|
+
"message": "App!"
|
|
215
|
+
}`)
|
|
216
|
+
expect(res.headers.get('Content-Length')).toBeNull()
|
|
217
|
+
})
|
|
218
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Pretty JSON Middleware for App.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { MiddlewareHandler } from '../../types.ts'
|
|
7
|
+
|
|
8
|
+
interface PrettyOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Number of spaces for indentation.
|
|
11
|
+
* @default 2
|
|
12
|
+
*/
|
|
13
|
+
space?: number
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Query conditions for when to Pretty.
|
|
17
|
+
* @default 'pretty'
|
|
18
|
+
*/
|
|
19
|
+
query?: string
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Force prettification of JSON responses regardless of query parameters.
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
25
|
+
force?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const jsonContentTypeRegex = /^application\/(?:[a-z0-9._-]+\+)?json(?=$|[;\s])/i
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Pretty JSON Middleware for App.
|
|
32
|
+
*
|
|
33
|
+
*
|
|
34
|
+
* @param options - The options for the pretty JSON middleware.
|
|
35
|
+
* @returns {MiddlewareHandler} The middleware handler function.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* const app = new App()
|
|
40
|
+
*
|
|
41
|
+
* app.use(prettyJSON()) // With options: prettyJSON({ space: 4 })
|
|
42
|
+
* app.get('/', (c) => {
|
|
43
|
+
* return c.json({ message: 'App!' })
|
|
44
|
+
* })
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export const prettyJSON = (options?: PrettyOptions): MiddlewareHandler => {
|
|
48
|
+
const targetQuery = options?.query ?? 'pretty'
|
|
49
|
+
return async function prettyJSON(c, next) {
|
|
50
|
+
const pretty = options?.force || c.req.query(targetQuery) || c.req.query(targetQuery) === ''
|
|
51
|
+
await next()
|
|
52
|
+
const contentType = c.res.headers.get('Content-Type')
|
|
53
|
+
if (pretty && contentType && jsonContentTypeRegex.test(contentType)) {
|
|
54
|
+
// Read from a clone so that the response is kept intact when the body is not parseable as JSON
|
|
55
|
+
let obj: unknown
|
|
56
|
+
try {
|
|
57
|
+
obj = await c.res.clone().json()
|
|
58
|
+
} catch {
|
|
59
|
+
// e.g. an empty 204 response or a body that is not valid JSON
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
c.res = new Response(JSON.stringify(obj, null, options?.space ?? 2), c.res)
|
|
63
|
+
// The length of the body has changed, so remove the stale Content-Length
|
|
64
|
+
c.res.headers.delete('Content-Length')
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { requestId } from './index.ts'
|
|
2
|
+
import type { Context } from '../../context.ts'
|
|
3
|
+
import { App } from '../../app.ts'
|
|
4
|
+
|
|
5
|
+
const regexUUIDv4 = /([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})/
|
|
6
|
+
|
|
7
|
+
describe('Request ID Middleware', () => {
|
|
8
|
+
const app = new App()
|
|
9
|
+
app.use('*', requestId())
|
|
10
|
+
app.get('/requestId', (c) => c.text(c.get('requestId') ?? 'No Request ID'))
|
|
11
|
+
|
|
12
|
+
it('Should return random request id', async () => {
|
|
13
|
+
const res = await app.request('http://localhost/requestId')
|
|
14
|
+
expect(res).not.toBeNull()
|
|
15
|
+
expect(res.status).toBe(200)
|
|
16
|
+
expect(res.headers.get('X-Request-Id')).toMatch(regexUUIDv4)
|
|
17
|
+
expect(await res.text()).match(regexUUIDv4)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('Should return custom request id', async () => {
|
|
21
|
+
const res = await app.request('http://localhost/requestId', {
|
|
22
|
+
headers: {
|
|
23
|
+
'X-Request-Id': 'acryl-is-hot',
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
expect(res).not.toBeNull()
|
|
27
|
+
expect(res.status).toBe(200)
|
|
28
|
+
expect(res.headers.get('X-Request-Id')).toBe('acryl-is-hot')
|
|
29
|
+
expect(await res.text()).toBe('acryl-is-hot')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('Should return custom request id with all valid characters', async () => {
|
|
33
|
+
const validRequestId = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_='
|
|
34
|
+
const res = await app.request('http://localhost/requestId', {
|
|
35
|
+
headers: {
|
|
36
|
+
'X-Request-Id': validRequestId,
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
expect(res).not.toBeNull()
|
|
40
|
+
expect(res.status).toBe(200)
|
|
41
|
+
expect(res.headers.get('X-Request-Id')).toBe(validRequestId)
|
|
42
|
+
expect(await res.text()).toBe(validRequestId)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('Should return random request id without using request header', async () => {
|
|
46
|
+
const res = await app.request('http://localhost/requestId', {
|
|
47
|
+
headers: {
|
|
48
|
+
'X-Request-Id': 'Hello!12345-@*^',
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
expect(res).not.toBeNull()
|
|
52
|
+
expect(res.status).toBe(200)
|
|
53
|
+
expect(res.headers.get('X-Request-Id')).toMatch(regexUUIDv4)
|
|
54
|
+
expect(await res.text()).toMatch(regexUUIDv4)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('Request ID Middleware with custom generator', () => {
|
|
59
|
+
function generateWord() {
|
|
60
|
+
return 'AppIsWebFramework'
|
|
61
|
+
}
|
|
62
|
+
function generateDoubleRequestId(c: Context) {
|
|
63
|
+
const appId = c.req.header('App-Request-Id')
|
|
64
|
+
const ohnoId = c.req.header('Ohno-Request-Id')
|
|
65
|
+
if (appId && ohnoId) {
|
|
66
|
+
return appId + ohnoId
|
|
67
|
+
}
|
|
68
|
+
return crypto.randomUUID()
|
|
69
|
+
}
|
|
70
|
+
const app = new App()
|
|
71
|
+
app.use('/word', requestId({ generator: generateWord }))
|
|
72
|
+
app.use('/doubleRequestId', requestId({ generator: generateDoubleRequestId }))
|
|
73
|
+
app.get('/word', (c) => c.text(c.get('requestId') ?? 'No Request ID'))
|
|
74
|
+
app.get('/doubleRequestId', (c) => c.text(c.get('requestId') ?? 'No Request ID'))
|
|
75
|
+
it('Should return custom request id', async () => {
|
|
76
|
+
const res = await app.request('http://localhost/word')
|
|
77
|
+
expect(res).not.toBeNull()
|
|
78
|
+
expect(res.status).toBe(200)
|
|
79
|
+
expect(res.headers.get('X-Request-Id')).toBe('AppIsWebFramework')
|
|
80
|
+
expect(await res.text()).toBe('AppIsWebFramework')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('Should return complex request id', async () => {
|
|
84
|
+
const res = await app.request('http://localhost/doubleRequestId', {
|
|
85
|
+
headers: {
|
|
86
|
+
'App-Request-Id': 'Hello',
|
|
87
|
+
'Ohno-Request-Id': 'World',
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
expect(res).not.toBeNull()
|
|
91
|
+
expect(res.status).toBe(200)
|
|
92
|
+
expect(res.headers.get('X-Request-Id')).toBe('HelloWorld')
|
|
93
|
+
expect(await res.text()).toBe('HelloWorld')
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('Request ID Middleware with limit length', () => {
|
|
98
|
+
const charactersOf255 = 'h'.repeat(255)
|
|
99
|
+
const charactersOf256 = 'h'.repeat(256)
|
|
100
|
+
|
|
101
|
+
const app = new App()
|
|
102
|
+
app.use('/requestId', requestId())
|
|
103
|
+
app.use('/limit256', requestId({ limitLength: 256 }))
|
|
104
|
+
app.get('/requestId', (c) => c.text(c.get('requestId') ?? 'No Request ID'))
|
|
105
|
+
app.get('/limit256', (c) => c.text(c.get('requestId') ?? 'No Request ID'))
|
|
106
|
+
|
|
107
|
+
it('Should return custom request id', async () => {
|
|
108
|
+
const res = await app.request('http://localhost/requestId', {
|
|
109
|
+
headers: {
|
|
110
|
+
'X-Request-Id': charactersOf255,
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
expect(res).not.toBeNull()
|
|
114
|
+
expect(res.status).toBe(200)
|
|
115
|
+
expect(res.headers.get('X-Request-Id')).toBe(charactersOf255)
|
|
116
|
+
expect(await res.text()).toBe(charactersOf255)
|
|
117
|
+
})
|
|
118
|
+
it('Should return random request id without using request header', async () => {
|
|
119
|
+
const res = await app.request('http://localhost/requestId', {
|
|
120
|
+
headers: {
|
|
121
|
+
'X-Request-Id': charactersOf256,
|
|
122
|
+
},
|
|
123
|
+
})
|
|
124
|
+
expect(res).not.toBeNull()
|
|
125
|
+
expect(res.status).toBe(200)
|
|
126
|
+
expect(res.headers.get('X-Request-Id')).toMatch(regexUUIDv4)
|
|
127
|
+
expect(await res.text()).toMatch(regexUUIDv4)
|
|
128
|
+
})
|
|
129
|
+
it('Should return custom request id with 256 characters', async () => {
|
|
130
|
+
const res = await app.request('http://localhost/limit256', {
|
|
131
|
+
headers: {
|
|
132
|
+
'X-Request-Id': charactersOf256,
|
|
133
|
+
},
|
|
134
|
+
})
|
|
135
|
+
expect(res).not.toBeNull()
|
|
136
|
+
expect(res.status).toBe(200)
|
|
137
|
+
expect(res.headers.get('X-Request-Id')).toBe(charactersOf256)
|
|
138
|
+
expect(await res.text()).toBe(charactersOf256)
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
describe('Request ID Middleware with custom header', () => {
|
|
143
|
+
const app = new App()
|
|
144
|
+
app.use('/requestId', requestId({ headerName: 'App-Request-Id' }))
|
|
145
|
+
app.get('/emptyId', requestId({ headerName: '' }))
|
|
146
|
+
app.get('/requestId', (c) => c.text(c.get('requestId') ?? 'No Request ID'))
|
|
147
|
+
app.get('/emptyId', (c) => c.text(c.get('requestId') ?? 'No Request ID'))
|
|
148
|
+
|
|
149
|
+
it('Should return custom request id', async () => {
|
|
150
|
+
const res = await app.request('http://localhost/requestId', {
|
|
151
|
+
headers: {
|
|
152
|
+
'App-Request-Id': 'acryl-is-hot',
|
|
153
|
+
},
|
|
154
|
+
})
|
|
155
|
+
expect(res).not.toBeNull()
|
|
156
|
+
expect(res.status).toBe(200)
|
|
157
|
+
expect(res.headers.get('App-Request-Id')).toBe('acryl-is-hot')
|
|
158
|
+
expect(await res.text()).toBe('acryl-is-hot')
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('Should not return request id', async () => {
|
|
162
|
+
const res = await app.request('http://localhost/emptyId')
|
|
163
|
+
expect(res).not.toBeNull()
|
|
164
|
+
expect(res.status).toBe(200)
|
|
165
|
+
expect(res.headers.get('X-Request-Id')).toBeNull()
|
|
166
|
+
expect(await res.text()).toMatch(regexUUIDv4)
|
|
167
|
+
})
|
|
168
|
+
})
|