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,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Body utility.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AppRequest } from '../request.ts'
|
|
7
|
+
import { bufferToFormData } from './buffer.ts'
|
|
8
|
+
|
|
9
|
+
const MAX_NESTING_DEPTH = 32
|
|
10
|
+
const MAX_NESTED_OBJECTS = 10_000
|
|
11
|
+
|
|
12
|
+
type BodyDataValueDot = { [x: string]: string | File | BodyDataValueDot }
|
|
13
|
+
type BodyDataValueDotAll = {
|
|
14
|
+
[x: string]: string | File | (string | File)[] | BodyDataValueDotAll
|
|
15
|
+
}
|
|
16
|
+
type SimplifyBodyData<T> = {
|
|
17
|
+
[K in keyof T]: string | File | (string | File)[] | BodyDataValueDotAll extends T[K]
|
|
18
|
+
? string | File | (string | File)[] | BodyDataValueDotAll
|
|
19
|
+
: string | File | BodyDataValueDot extends T[K]
|
|
20
|
+
? string | File | BodyDataValueDot
|
|
21
|
+
: string | File | (string | File)[] extends T[K]
|
|
22
|
+
? string | File | (string | File)[]
|
|
23
|
+
: string | File
|
|
24
|
+
} & {}
|
|
25
|
+
|
|
26
|
+
type BodyDataValueComponent<T> =
|
|
27
|
+
| string
|
|
28
|
+
| File
|
|
29
|
+
| (T extends { all: false }
|
|
30
|
+
? never // explicitly set to false
|
|
31
|
+
: T extends { all: true } | { all: boolean }
|
|
32
|
+
? (string | File)[] // use all option
|
|
33
|
+
: never) // without options
|
|
34
|
+
type BodyDataValueObject<T> = { [key: string]: BodyDataValueComponent<T> | BodyDataValueObject<T> }
|
|
35
|
+
type BodyDataValue<T> =
|
|
36
|
+
| BodyDataValueComponent<T>
|
|
37
|
+
| (T extends { dot: false }
|
|
38
|
+
? never // explicitly set to false
|
|
39
|
+
: T extends { dot: true } | { dot: boolean }
|
|
40
|
+
? BodyDataValueObject<T> // use dot option
|
|
41
|
+
: never) // without options
|
|
42
|
+
export type BodyData<T extends Partial<ParseBodyOptions> = {}> = SimplifyBodyData<
|
|
43
|
+
Record<string, BodyDataValue<T>>
|
|
44
|
+
>
|
|
45
|
+
|
|
46
|
+
export type ParseBodyOptions = {
|
|
47
|
+
/**
|
|
48
|
+
* Determines whether all fields with multiple values should be parsed as arrays.
|
|
49
|
+
* @default false
|
|
50
|
+
* @example
|
|
51
|
+
* const data = new FormData()
|
|
52
|
+
* data.append('file', 'aaa')
|
|
53
|
+
* data.append('file', 'bbb')
|
|
54
|
+
* data.append('message', 'hello')
|
|
55
|
+
*
|
|
56
|
+
* If all is false:
|
|
57
|
+
* parseBody should return { file: 'bbb', message: 'hello' }
|
|
58
|
+
*
|
|
59
|
+
* If all is true:
|
|
60
|
+
* parseBody should return { file: ['aaa', 'bbb'], message: 'hello' }
|
|
61
|
+
*/
|
|
62
|
+
all: boolean
|
|
63
|
+
/**
|
|
64
|
+
* Determines whether all fields with dot notation should be parsed as nested objects.
|
|
65
|
+
* @default false
|
|
66
|
+
* @example
|
|
67
|
+
* const data = new FormData()
|
|
68
|
+
* data.append('obj.key1', 'value1')
|
|
69
|
+
* data.append('obj.key2', 'value2')
|
|
70
|
+
*
|
|
71
|
+
* If dot is false:
|
|
72
|
+
* parseBody should return { 'obj.key1': 'value1', 'obj.key2': 'value2' }
|
|
73
|
+
*
|
|
74
|
+
* If dot is true:
|
|
75
|
+
* parseBody should return { obj: { key1: 'value1', key2: 'value2' } }
|
|
76
|
+
*/
|
|
77
|
+
dot: boolean
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const isRawRequest = (request: AppRequest | Request): request is Request => 'headers' in request // 'headers' method exists only on Request, not on AppRequest.
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parses the body of a request based on the provided options.
|
|
84
|
+
*
|
|
85
|
+
* @template T - The type of the parsed body data.
|
|
86
|
+
* @param {AppRequest | Request} request - The request object to parse.
|
|
87
|
+
* @param {Partial<ParseBodyOptions>} [options] - Options for parsing the body.
|
|
88
|
+
* @returns {Promise<T>} The parsed body data.
|
|
89
|
+
*/
|
|
90
|
+
interface ParseBody {
|
|
91
|
+
<Options extends Partial<ParseBodyOptions>, T extends BodyData<Options>>(
|
|
92
|
+
request: AppRequest | Request,
|
|
93
|
+
options?: Options
|
|
94
|
+
): Promise<T>
|
|
95
|
+
<T extends BodyData>(
|
|
96
|
+
request: AppRequest | Request,
|
|
97
|
+
options?: Partial<ParseBodyOptions>
|
|
98
|
+
): Promise<T>
|
|
99
|
+
}
|
|
100
|
+
export const parseBody: ParseBody = async (
|
|
101
|
+
request: AppRequest | Request,
|
|
102
|
+
options = Object.create(null)
|
|
103
|
+
) => {
|
|
104
|
+
const { all = false, dot = false } = options
|
|
105
|
+
|
|
106
|
+
const headers = isRawRequest(request) ? request.headers : request.raw.headers
|
|
107
|
+
const contentType = headers.get('Content-Type')
|
|
108
|
+
|
|
109
|
+
const mediaType = contentType?.split(';')[0].trim().toLowerCase()
|
|
110
|
+
|
|
111
|
+
if (mediaType === 'multipart/form-data' || mediaType === 'application/x-www-form-urlencoded') {
|
|
112
|
+
return parseFormData(request, { all, dot })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parses form data from a request.
|
|
120
|
+
*
|
|
121
|
+
* @template T - The type of the parsed body data.
|
|
122
|
+
* @param {AppRequest | Request} request - The request object containing form data.
|
|
123
|
+
* @param {ParseBodyOptions} options - Options for parsing the form data.
|
|
124
|
+
* @returns {Promise<T>} The parsed body data.
|
|
125
|
+
*/
|
|
126
|
+
async function parseFormData<T extends BodyData>(
|
|
127
|
+
request: AppRequest | Request,
|
|
128
|
+
options: ParseBodyOptions
|
|
129
|
+
): Promise<T> {
|
|
130
|
+
if (!isRawRequest(request) && request.bodyCache.formData) {
|
|
131
|
+
return convertFormDataToBodyData<T>(
|
|
132
|
+
await (request.bodyCache.formData as FormData | Promise<FormData>),
|
|
133
|
+
options
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
const headers = isRawRequest(request) ? request.headers : request.raw.headers
|
|
137
|
+
const arrayBuffer = await (request as Request).arrayBuffer()
|
|
138
|
+
const formDataPromise = bufferToFormData(arrayBuffer, headers.get('Content-Type') || '')
|
|
139
|
+
if (!isRawRequest(request)) {
|
|
140
|
+
// Cache so that a later `c.req.formData()` reuses the already-consumed body
|
|
141
|
+
request.bodyCache.formData = formDataPromise as unknown as FormData
|
|
142
|
+
}
|
|
143
|
+
const formData = await formDataPromise
|
|
144
|
+
|
|
145
|
+
if (formData) {
|
|
146
|
+
return convertFormDataToBodyData<T>(formData, options)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {} as T
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Converts form data to body data based on the provided options.
|
|
154
|
+
*
|
|
155
|
+
* @template T - The type of the parsed body data.
|
|
156
|
+
* @param {FormData} formData - The form data to convert.
|
|
157
|
+
* @param {ParseBodyOptions} options - Options for parsing the form data.
|
|
158
|
+
* @returns {T} The converted body data.
|
|
159
|
+
*/
|
|
160
|
+
function convertFormDataToBodyData<T extends BodyData = BodyData>(
|
|
161
|
+
formData: FormData,
|
|
162
|
+
options: ParseBodyOptions
|
|
163
|
+
): T {
|
|
164
|
+
const form: BodyData = Object.create(null)
|
|
165
|
+
const nestingState = { count: 0 }
|
|
166
|
+
|
|
167
|
+
formData.forEach((value, key) => {
|
|
168
|
+
const shouldParseAllValues = options.all || key.endsWith('[]')
|
|
169
|
+
|
|
170
|
+
if (!shouldParseAllValues) {
|
|
171
|
+
form[key] = value
|
|
172
|
+
} else {
|
|
173
|
+
handleParsingAllValues(form, key, value)
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
if (options.dot) {
|
|
178
|
+
Object.entries(form).forEach(([key, value]) => {
|
|
179
|
+
const shouldParseDotValues = key.includes('.')
|
|
180
|
+
|
|
181
|
+
if (shouldParseDotValues) {
|
|
182
|
+
handleParsingNestedValues(form, key, value, nestingState)
|
|
183
|
+
delete form[key]
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return form as T
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Handles parsing all values for a given key, supporting multiple values as arrays.
|
|
193
|
+
*
|
|
194
|
+
* @param {BodyData} form - The form data object.
|
|
195
|
+
* @param {string} key - The key to parse.
|
|
196
|
+
* @param {FormDataEntryValue} value - The value to assign.
|
|
197
|
+
*/
|
|
198
|
+
const handleParsingAllValues = (
|
|
199
|
+
form: BodyData<{ all: true }>,
|
|
200
|
+
key: string,
|
|
201
|
+
value: FormDataEntryValue
|
|
202
|
+
): void => {
|
|
203
|
+
if (form[key] !== undefined) {
|
|
204
|
+
if (Array.isArray(form[key])) {
|
|
205
|
+
;(form[key] as (string | File)[]).push(value)
|
|
206
|
+
} else {
|
|
207
|
+
form[key] = [form[key] as string | File, value]
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
if (!key.endsWith('[]')) {
|
|
211
|
+
form[key] = value
|
|
212
|
+
} else {
|
|
213
|
+
form[key] = [value]
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Handles parsing nested values using dot notation keys.
|
|
220
|
+
*
|
|
221
|
+
* @param {BodyData} form - The form data object.
|
|
222
|
+
* @param {string} key - The dot notation key.
|
|
223
|
+
* @param {BodyDataValue} value - The value to assign.
|
|
224
|
+
*/
|
|
225
|
+
const handleParsingNestedValues = (
|
|
226
|
+
form: BodyData,
|
|
227
|
+
key: string,
|
|
228
|
+
value: BodyDataValue<Partial<ParseBodyOptions>>,
|
|
229
|
+
state: { count: number }
|
|
230
|
+
): void => {
|
|
231
|
+
if (/(?:^|\.)__proto__\./.test(key)) {
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let nestedForm = form
|
|
236
|
+
const keys = key.split('.', MAX_NESTING_DEPTH + 2)
|
|
237
|
+
if (keys.length > MAX_NESTING_DEPTH + 1) {
|
|
238
|
+
throwNestingLimitExceeded()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
keys.forEach((key, index) => {
|
|
242
|
+
if (index === keys.length - 1) {
|
|
243
|
+
nestedForm[key] = value
|
|
244
|
+
} else {
|
|
245
|
+
if (
|
|
246
|
+
!nestedForm[key] ||
|
|
247
|
+
typeof nestedForm[key] !== 'object' ||
|
|
248
|
+
Array.isArray(nestedForm[key]) ||
|
|
249
|
+
nestedForm[key] instanceof File
|
|
250
|
+
) {
|
|
251
|
+
if (state.count++ >= MAX_NESTED_OBJECTS) {
|
|
252
|
+
throwNestingLimitExceeded()
|
|
253
|
+
}
|
|
254
|
+
nestedForm[key] = Object.create(null)
|
|
255
|
+
}
|
|
256
|
+
nestedForm = nestedForm[key] as unknown as BodyData
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const throwNestingLimitExceeded = (): never => {
|
|
262
|
+
throw new Error('Nesting limit exceeded')
|
|
263
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { bufferToFormData, bufferToString, equal, timingSafeEqual } from './buffer.ts'
|
|
3
|
+
|
|
4
|
+
describe('equal', () => {
|
|
5
|
+
it('should return true for identical ArrayBuffers', () => {
|
|
6
|
+
const buffer1 = new ArrayBuffer(1)
|
|
7
|
+
const buffer2 = buffer1
|
|
8
|
+
expect(equal(buffer1, buffer2)).toBe(true)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('should return false for ArrayBuffers of different lengths', () => {
|
|
12
|
+
const buffer1 = new ArrayBuffer(1)
|
|
13
|
+
const buffer2 = new ArrayBuffer(2)
|
|
14
|
+
expect(equal(buffer1, buffer2)).toBe(false)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('should return false for ArrayBuffers with different content', () => {
|
|
18
|
+
const buffer1 = new Uint8Array([1, 2, 3, 4]).buffer
|
|
19
|
+
const buffer2 = new Uint8Array([2, 2, 3, 4]).buffer
|
|
20
|
+
expect(equal(buffer1, buffer2)).toBe(false)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should return true for ArrayBuffers with identical content', () => {
|
|
24
|
+
const buffer1 = new Uint8Array([1, 2, 3, 4]).buffer
|
|
25
|
+
const buffer2 = new Uint8Array([1, 2, 3, 4]).buffer
|
|
26
|
+
expect(equal(buffer1, buffer2)).toBe(true)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('buffer', () => {
|
|
31
|
+
it('positive', async () => {
|
|
32
|
+
expect(
|
|
33
|
+
await timingSafeEqual(
|
|
34
|
+
'127e6fbfe24a750e72930c220a8e138275656b8e5d8f48a98c3c92df2caba935',
|
|
35
|
+
'127e6fbfe24a750e72930c220a8e138275656b8e5d8f48a98c3c92df2caba935'
|
|
36
|
+
)
|
|
37
|
+
).toBe(true)
|
|
38
|
+
expect(await timingSafeEqual('a', 'a')).toBe(true)
|
|
39
|
+
expect(await timingSafeEqual('', '')).toBe(true)
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
expect(await timingSafeEqual(undefined, undefined)).toBe(true)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('negative', async () => {
|
|
46
|
+
expect(await timingSafeEqual('a', 'b')).toBe(false)
|
|
47
|
+
expect(
|
|
48
|
+
await timingSafeEqual('a', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
|
|
49
|
+
).toBe(false)
|
|
50
|
+
expect(
|
|
51
|
+
await timingSafeEqual('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'a')
|
|
52
|
+
).toBe(false)
|
|
53
|
+
expect(await timingSafeEqual('alpha', 'beta')).toBe(false)
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
55
|
+
// @ts-ignore
|
|
56
|
+
expect(await timingSafeEqual(false, undefined)).toBe(false)
|
|
57
|
+
|
|
58
|
+
expect(
|
|
59
|
+
await timingSafeEqual(
|
|
60
|
+
// well known md5 hash collision
|
|
61
|
+
// https://marc-stevens.nl/research/md5-1block-collision/
|
|
62
|
+
'TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak',
|
|
63
|
+
'TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak',
|
|
64
|
+
(input) => createHash('md5').update(input).digest('hex')
|
|
65
|
+
)
|
|
66
|
+
).toBe(false)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it.skip('comparing variables except string are deprecated', async () => {
|
|
70
|
+
expect(await timingSafeEqual(true, true)).toBe(true)
|
|
71
|
+
expect(await timingSafeEqual(false, false)).toBe(true)
|
|
72
|
+
expect(
|
|
73
|
+
await timingSafeEqual(true, true, (d: boolean) =>
|
|
74
|
+
createHash('sha256').update(d.toString()).digest('hex')
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
expect(await timingSafeEqual(false, true)).toBe(false)
|
|
78
|
+
expect(
|
|
79
|
+
await timingSafeEqual(
|
|
80
|
+
() => {},
|
|
81
|
+
() => {}
|
|
82
|
+
)
|
|
83
|
+
).toBe(false)
|
|
84
|
+
expect(await timingSafeEqual({}, {})).toBe(false)
|
|
85
|
+
expect(await timingSafeEqual({ a: 1 }, { a: 1 })).toBe(false)
|
|
86
|
+
expect(await timingSafeEqual({ a: 1 }, { a: 2 })).toBe(false)
|
|
87
|
+
expect(await timingSafeEqual([1, 2], [1, 2])).toBe(false)
|
|
88
|
+
expect(await timingSafeEqual([1, 2], [1, 2, 3])).toBe(false)
|
|
89
|
+
expect(await timingSafeEqual('a', 'b', () => undefined)).toBe(false)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
describe('bufferToString', () => {
|
|
94
|
+
it('Should return あいうえお', () => {
|
|
95
|
+
const bytes = [227, 129, 130, 227, 129, 132, 227, 129, 134, 227, 129, 136, 227, 129, 138]
|
|
96
|
+
const buffer = Uint8Array.from(bytes).buffer
|
|
97
|
+
expect(bufferToString(buffer)).toBe('あいうえお')
|
|
98
|
+
})
|
|
99
|
+
it('should return the passed arguments as is ', () => {
|
|
100
|
+
const notBuffer = 'あいうえお' as unknown as ArrayBuffer
|
|
101
|
+
expect(bufferToString(notBuffer)).toBe('あいうえお')
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
describe('bufferToFormData', () => {
|
|
106
|
+
it('Should parse multipart/form-data from ArrayBuffer', async () => {
|
|
107
|
+
const encoder = new TextEncoder()
|
|
108
|
+
const testData =
|
|
109
|
+
'--sampleboundary\r\nContent-Disposition: form-data; name="test"\r\n\r\nHello\r\n--sampleboundary--'
|
|
110
|
+
const arrayBuffer = encoder.encode(testData).buffer
|
|
111
|
+
|
|
112
|
+
const result = await bufferToFormData(
|
|
113
|
+
arrayBuffer,
|
|
114
|
+
'multipart/form-data; boundary=sampleboundary'
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
expect(result.get('test')).toBe('Hello')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('Should parse mixed-case multipart/form-data media type while keeping the boundary', async () => {
|
|
121
|
+
const encoder = new TextEncoder()
|
|
122
|
+
const testData =
|
|
123
|
+
'--sampleBoundary\r\nContent-Disposition: form-data; name="test"\r\n\r\nHello\r\n--sampleBoundary--'
|
|
124
|
+
const arrayBuffer = encoder.encode(testData).buffer
|
|
125
|
+
|
|
126
|
+
const result = await bufferToFormData(
|
|
127
|
+
arrayBuffer,
|
|
128
|
+
'Multipart/Form-Data; boundary=sampleBoundary'
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
expect(result.get('test')).toBe('Hello')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('Should parse application/x-www-form-urlencoded from ArrayBuffer', async () => {
|
|
135
|
+
const encoder = new TextEncoder()
|
|
136
|
+
const searchParams = new URLSearchParams()
|
|
137
|
+
searchParams.append('id', '123')
|
|
138
|
+
searchParams.append('title', 'Good title')
|
|
139
|
+
const testData = searchParams.toString()
|
|
140
|
+
const arrayBuffer = encoder.encode(testData).buffer
|
|
141
|
+
|
|
142
|
+
const result = await bufferToFormData(arrayBuffer, 'application/x-www-form-urlencoded')
|
|
143
|
+
|
|
144
|
+
expect(result.get('id')).toBe('123')
|
|
145
|
+
expect(result.get('title')).toBe('Good title')
|
|
146
|
+
})
|
|
147
|
+
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Buffer utility.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { sha256 } from './crypto.ts'
|
|
7
|
+
|
|
8
|
+
export const equal = (a: ArrayBuffer, b: ArrayBuffer): boolean => {
|
|
9
|
+
if (a === b) {
|
|
10
|
+
return true
|
|
11
|
+
}
|
|
12
|
+
if (a.byteLength !== b.byteLength) {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const va = new DataView(a)
|
|
17
|
+
const vb = new DataView(b)
|
|
18
|
+
|
|
19
|
+
let i = va.byteLength
|
|
20
|
+
while (i--) {
|
|
21
|
+
if (va.getUint8(i) !== vb.getUint8(i)) {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const constantTimeEqualString = (a: string, b: string): boolean => {
|
|
30
|
+
const aLen = a.length
|
|
31
|
+
const bLen = b.length
|
|
32
|
+
const maxLen = Math.max(aLen, bLen)
|
|
33
|
+
let out = aLen ^ bLen
|
|
34
|
+
for (let i = 0; i < maxLen; i++) {
|
|
35
|
+
const aChar = i < aLen ? a.charCodeAt(i) : 0
|
|
36
|
+
const bChar = i < bLen ? b.charCodeAt(i) : 0
|
|
37
|
+
out |= aChar ^ bChar
|
|
38
|
+
}
|
|
39
|
+
return out === 0
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type StringHashFunction = (input: string) => string | null | Promise<string | null>
|
|
43
|
+
|
|
44
|
+
const timingSafeEqualString = async (
|
|
45
|
+
a: string,
|
|
46
|
+
b: string,
|
|
47
|
+
hashFunction?: StringHashFunction
|
|
48
|
+
): Promise<boolean> => {
|
|
49
|
+
if (!hashFunction) {
|
|
50
|
+
hashFunction = sha256
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const [sa, sb] = await Promise.all([hashFunction(a), hashFunction(b)])
|
|
54
|
+
|
|
55
|
+
if (sa == null || sb == null || typeof sa !== 'string' || typeof sb !== 'string') {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const hashEqual = constantTimeEqualString(sa, sb)
|
|
60
|
+
const originalEqual = constantTimeEqualString(a, b)
|
|
61
|
+
|
|
62
|
+
return hashEqual && originalEqual
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type TimingSafeEqual = {
|
|
66
|
+
(a: string, b: string, hashFunction?: StringHashFunction): Promise<boolean>
|
|
67
|
+
/**
|
|
68
|
+
* @deprecated object and boolean signatures that take boolean as first and second arguments, and functions with signatures that take non-string arguments have been deprecated
|
|
69
|
+
*/
|
|
70
|
+
(
|
|
71
|
+
a: string | object | boolean,
|
|
72
|
+
b: string | object | boolean,
|
|
73
|
+
hashFunction?: Function
|
|
74
|
+
): Promise<boolean>
|
|
75
|
+
}
|
|
76
|
+
export const timingSafeEqual: TimingSafeEqual = async (
|
|
77
|
+
a,
|
|
78
|
+
b,
|
|
79
|
+
hashFunction?: Function
|
|
80
|
+
): Promise<boolean> => {
|
|
81
|
+
if (typeof a === 'string' && typeof b === 'string') {
|
|
82
|
+
return timingSafeEqualString(a, b, hashFunction as StringHashFunction)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!hashFunction) {
|
|
86
|
+
hashFunction = sha256
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const [sa, sb] = await Promise.all([hashFunction(a), hashFunction(b)])
|
|
90
|
+
|
|
91
|
+
if (!sa || !sb || typeof sa !== 'string' || typeof sb !== 'string') {
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return timingSafeEqualString(sa, sb)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const bufferToString = (buffer: ArrayBuffer): string => {
|
|
99
|
+
if (buffer instanceof ArrayBuffer) {
|
|
100
|
+
const enc = new TextDecoder('utf-8')
|
|
101
|
+
return enc.decode(buffer)
|
|
102
|
+
}
|
|
103
|
+
return buffer
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const bufferToFormData = (
|
|
107
|
+
arrayBuffer: ArrayBuffer,
|
|
108
|
+
contentType: string
|
|
109
|
+
): Promise<FormData> => {
|
|
110
|
+
const response = new Response(arrayBuffer, {
|
|
111
|
+
headers: {
|
|
112
|
+
// Normalize the media type (case-insensitive) while keeping parameters like the boundary
|
|
113
|
+
'Content-Type': contentType.replace(/^[^;]+/, (mediaType) => mediaType.toLowerCase()),
|
|
114
|
+
},
|
|
115
|
+
})
|
|
116
|
+
return response.formData()
|
|
117
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getColorEnabled, getColorEnabledAsync } from './color.ts'
|
|
2
|
+
|
|
3
|
+
describe('getColorEnabled() / getColorEnabledAsync() - With colors enabled', () => {
|
|
4
|
+
it('should return true', async () => {
|
|
5
|
+
expect(getColorEnabled()).toBe(true)
|
|
6
|
+
expect(await getColorEnabledAsync()).toBe(true)
|
|
7
|
+
})
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
describe('getColorEnabled() / getColorEnabledAsync() - With NO_COLOR environment variable set', () => {
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
vi.stubEnv('NO_COLOR', '1')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterAll(() => {
|
|
16
|
+
vi.unstubAllEnvs()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should return false', async () => {
|
|
20
|
+
expect(getColorEnabled()).toBe(false)
|
|
21
|
+
expect(await getColorEnabledAsync()).toBe(false)
|
|
22
|
+
})
|
|
23
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Color utility.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get whether color change on terminal is enabled or disabled.
|
|
8
|
+
* If `NO_COLOR` environment variable is set, this function returns `false`.
|
|
9
|
+
* Unlike getColorEnabledAsync(), this cannot check Cloudflare environment variables.
|
|
10
|
+
* @see {@link https://no-color.org/}
|
|
11
|
+
*
|
|
12
|
+
* @returns {boolean}
|
|
13
|
+
*/
|
|
14
|
+
export function getColorEnabled(): boolean {
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
const { process, Deno } = globalThis as any
|
|
17
|
+
|
|
18
|
+
const isNoColor =
|
|
19
|
+
typeof Deno?.noColor === 'boolean'
|
|
20
|
+
? (Deno.noColor as boolean)
|
|
21
|
+
: process !== undefined
|
|
22
|
+
? // eslint-disable-next-line no-unsafe-optional-chaining
|
|
23
|
+
'NO_COLOR' in process?.env
|
|
24
|
+
: false
|
|
25
|
+
|
|
26
|
+
return !isNoColor
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get whether color change on terminal is enabled or disabled.
|
|
31
|
+
* If `NO_COLOR` environment variable is set, this function returns `false`.
|
|
32
|
+
* @see {@link https://no-color.org/}
|
|
33
|
+
*
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
export async function getColorEnabledAsync(): Promise<boolean> {
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
const { navigator } = globalThis as any
|
|
39
|
+
// Avoid analysis of cloudflare scheme by bundlers
|
|
40
|
+
const cfWorkers = 'cloudflare:workers'
|
|
41
|
+
|
|
42
|
+
const isNoColor =
|
|
43
|
+
navigator !== undefined && navigator.userAgent === 'Cloudflare-Workers'
|
|
44
|
+
? await (async () => {
|
|
45
|
+
try {
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
return 'NO_COLOR' in ((await import(cfWorkers)).env ?? {}) // {} is for backward compat
|
|
49
|
+
} catch {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
})()
|
|
53
|
+
: !getColorEnabled()
|
|
54
|
+
|
|
55
|
+
return !isNoColor
|
|
56
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Constants for compression.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Match for compressible content type.
|
|
8
|
+
*/
|
|
9
|
+
export const COMPRESSIBLE_CONTENT_TYPE_REGEX =
|
|
10
|
+
/^\s*(?:text\/(?!event-stream(?:[;\s]|$))[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|msgpack|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|vnd\.msgpack|wasm|x-httpd-php|x-javascript|x-msgpack|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml|msgpack))(?:[;\s]|$)/i
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createPool } from './concurrent.ts'
|
|
2
|
+
|
|
3
|
+
describe('concurrent execution', () => {
|
|
4
|
+
test.each`
|
|
5
|
+
concurrency | count
|
|
6
|
+
${1} | ${10}
|
|
7
|
+
${10} | ${10}
|
|
8
|
+
${100} | ${10}
|
|
9
|
+
${Infinity} | ${2000}
|
|
10
|
+
`('concurrency $concurrency, count $count', async ({ concurrency, count }) => {
|
|
11
|
+
const running = new Set()
|
|
12
|
+
|
|
13
|
+
const pool = createPool({ concurrency })
|
|
14
|
+
let resolve: (() => void) | undefined
|
|
15
|
+
const promise = new Promise<void>((r) => {
|
|
16
|
+
resolve = r
|
|
17
|
+
})
|
|
18
|
+
const fn = async (i: number) => {
|
|
19
|
+
if (running.size > concurrency) {
|
|
20
|
+
throw new Error('concurrency exceeded')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
running.add(i)
|
|
24
|
+
await promise
|
|
25
|
+
running.delete(i)
|
|
26
|
+
return i
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const jobs = new Array(count).fill(0).map((_, i) => () => fn(i))
|
|
30
|
+
const expectedResults = new Array(count).fill(0).map((_, i) => i)
|
|
31
|
+
const resultPromises = jobs.map((job) => pool.run(job))
|
|
32
|
+
|
|
33
|
+
expect(running.size).toBe(Math.min(concurrency, count))
|
|
34
|
+
resolve?.()
|
|
35
|
+
const results = await Promise.all(resultPromises)
|
|
36
|
+
expect(running.size).toBe(0)
|
|
37
|
+
expect(results).toEqual(expectedResults)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('with interval', () => {
|
|
41
|
+
test.each`
|
|
42
|
+
concurrency | interval
|
|
43
|
+
${1} | ${10}
|
|
44
|
+
${2} | ${10}
|
|
45
|
+
`('concurrency $concurrency, interval $interval', async ({ concurrency, interval }) => {
|
|
46
|
+
const workingTimeQueue: number[] = []
|
|
47
|
+
const pool = createPool({ concurrency, interval })
|
|
48
|
+
const fn = async (i: number) => {
|
|
49
|
+
const now = Date.now()
|
|
50
|
+
if (workingTimeQueue.length >= concurrency) {
|
|
51
|
+
const last = workingTimeQueue.shift()
|
|
52
|
+
// Not so accurate, -1 ms is acceptable
|
|
53
|
+
if (last && now - last < interval - 1) {
|
|
54
|
+
throw new Error('interval violated')
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
workingTimeQueue.push(now)
|
|
58
|
+
return i
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const jobs = new Array(10).fill(0).map((_, i) => () => fn(i))
|
|
62
|
+
const expectedResults = new Array(10).fill(0).map((_, i) => i)
|
|
63
|
+
const resultPromises = jobs.map((job) => pool.run(job))
|
|
64
|
+
|
|
65
|
+
const results = await Promise.all(resultPromises)
|
|
66
|
+
expect(results).toEqual(expectedResults)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
})
|