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,259 @@
|
|
|
1
|
+
import { matchedRoutes, routePath, baseRoutePath, basePath } from './index.ts'
|
|
2
|
+
import { Context } from '../../context.ts'
|
|
3
|
+
import { App } from '../../app.ts'
|
|
4
|
+
|
|
5
|
+
const defaultContextOptions = {
|
|
6
|
+
executionCtx: {
|
|
7
|
+
waitUntil: () => {},
|
|
8
|
+
passThroughOnException: () => {},
|
|
9
|
+
props: {},
|
|
10
|
+
},
|
|
11
|
+
env: {},
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('matchedRoutes', () => {
|
|
15
|
+
it('should return matched routes', () => {
|
|
16
|
+
const handlerA = () => {}
|
|
17
|
+
const handlerB = () => {}
|
|
18
|
+
const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')
|
|
19
|
+
const c = new Context(rawRequest, {
|
|
20
|
+
path: '/123/key',
|
|
21
|
+
matchResult: [
|
|
22
|
+
[
|
|
23
|
+
[
|
|
24
|
+
[handlerA, { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' }],
|
|
25
|
+
{ id: '123' },
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
[handlerA, { basePath: '/', handler: handlerB, method: 'GET', path: '/:id/:name' }],
|
|
29
|
+
{ id: '456', name: 'key' },
|
|
30
|
+
],
|
|
31
|
+
],
|
|
32
|
+
],
|
|
33
|
+
...defaultContextOptions,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
expect(matchedRoutes(c)).toEqual([
|
|
37
|
+
{ basePath: '/', handler: handlerA, method: 'GET', path: '/:id' },
|
|
38
|
+
{ basePath: '/', handler: handlerB, method: 'GET', path: '/:id/:name' },
|
|
39
|
+
])
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('routePath', () => {
|
|
44
|
+
it('should return route path', () => {
|
|
45
|
+
const handlerA = () => {}
|
|
46
|
+
const handlerB = () => {}
|
|
47
|
+
const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')
|
|
48
|
+
const c = new Context(rawRequest, {
|
|
49
|
+
path: '/123/key',
|
|
50
|
+
matchResult: [
|
|
51
|
+
[
|
|
52
|
+
[
|
|
53
|
+
[handlerA, { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' }],
|
|
54
|
+
{ id: '123' },
|
|
55
|
+
],
|
|
56
|
+
[
|
|
57
|
+
[handlerA, { basePath: '/', handler: handlerB, method: 'GET', path: '/:id/:name' }],
|
|
58
|
+
{ id: '456', name: 'key' },
|
|
59
|
+
],
|
|
60
|
+
],
|
|
61
|
+
],
|
|
62
|
+
...defaultContextOptions,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
expect(routePath(c)).toBe('/:id')
|
|
66
|
+
|
|
67
|
+
expect(routePath(c, 0)).toBe('/:id')
|
|
68
|
+
expect(routePath(c, 1)).toBe('/:id/:name')
|
|
69
|
+
expect(routePath(c, -1)).toBe('/:id/:name')
|
|
70
|
+
expect(routePath(c, 2)).toBe('')
|
|
71
|
+
|
|
72
|
+
c.req.routeIndex = 1
|
|
73
|
+
expect(routePath(c)).toBe('/:id/:name')
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('baseRoutePath', () => {
|
|
78
|
+
it('should return raw basePath', () => {
|
|
79
|
+
const handlerA = () => {}
|
|
80
|
+
const handlerB = () => {}
|
|
81
|
+
const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')
|
|
82
|
+
const c = new Context(rawRequest, {
|
|
83
|
+
path: '/123/key',
|
|
84
|
+
matchResult: [
|
|
85
|
+
[
|
|
86
|
+
[
|
|
87
|
+
[handlerA, { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' }],
|
|
88
|
+
{ id: '123' },
|
|
89
|
+
],
|
|
90
|
+
[
|
|
91
|
+
[handlerA, { basePath: '/sub', handler: handlerB, method: 'GET', path: '/:id/:name' }],
|
|
92
|
+
{ id: '456', name: 'key' },
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
[handlerA, { basePath: '/:sub', handler: handlerB, method: 'GET', path: '/:id/:name' }],
|
|
96
|
+
{ id: '456', name: 'key' },
|
|
97
|
+
],
|
|
98
|
+
],
|
|
99
|
+
],
|
|
100
|
+
...defaultContextOptions,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
expect(baseRoutePath(c)).toBe('/')
|
|
104
|
+
|
|
105
|
+
expect(baseRoutePath(c, 0)).toBe('/')
|
|
106
|
+
expect(baseRoutePath(c, 1)).toBe('/sub')
|
|
107
|
+
expect(baseRoutePath(c, 2)).toBe('/:sub')
|
|
108
|
+
expect(baseRoutePath(c, -1)).toBe('/:sub')
|
|
109
|
+
expect(baseRoutePath(c, 3)).toBe('')
|
|
110
|
+
|
|
111
|
+
c.req.routeIndex = 1
|
|
112
|
+
expect(baseRoutePath(c)).toBe('/sub')
|
|
113
|
+
|
|
114
|
+
c.req.routeIndex = 2
|
|
115
|
+
expect(baseRoutePath(c)).toBe('/:sub')
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('basePath', () => {
|
|
120
|
+
it('should return basePath without parameters', () => {
|
|
121
|
+
const handlerA = () => {}
|
|
122
|
+
const handlerB = () => {}
|
|
123
|
+
const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')
|
|
124
|
+
const c = new Context(rawRequest, {
|
|
125
|
+
path: '/123/key',
|
|
126
|
+
matchResult: [
|
|
127
|
+
[
|
|
128
|
+
[
|
|
129
|
+
[handlerA, { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' }],
|
|
130
|
+
{ id: '123' },
|
|
131
|
+
],
|
|
132
|
+
[
|
|
133
|
+
[handlerA, { basePath: '/sub', handler: handlerB, method: 'GET', path: '/:id/:name' }],
|
|
134
|
+
{ id: '456', name: 'key' },
|
|
135
|
+
],
|
|
136
|
+
],
|
|
137
|
+
],
|
|
138
|
+
...defaultContextOptions,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
expect(basePath(c)).toBe('/')
|
|
142
|
+
expect(basePath(c)).toBe('/') // cached value
|
|
143
|
+
|
|
144
|
+
expect(basePath(c, 0)).toBe('/')
|
|
145
|
+
expect(basePath(c, 1)).toBe('/sub')
|
|
146
|
+
expect(basePath(c, -1)).toBe('/sub')
|
|
147
|
+
expect(basePath(c, 2)).toBe('')
|
|
148
|
+
|
|
149
|
+
c.req.routeIndex = 1
|
|
150
|
+
expect(basePath(c)).toBe('/sub')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should return basePath with embedded parameters', () => {
|
|
154
|
+
const handlerA = () => {}
|
|
155
|
+
const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')
|
|
156
|
+
const c = new Context(rawRequest, {
|
|
157
|
+
path: '/sub-app-path/123/key',
|
|
158
|
+
matchResult: [
|
|
159
|
+
[
|
|
160
|
+
[
|
|
161
|
+
[handlerA, { basePath: '/:sub', handler: handlerA, method: 'GET', path: '/:id' }],
|
|
162
|
+
{ id: '123' },
|
|
163
|
+
],
|
|
164
|
+
],
|
|
165
|
+
],
|
|
166
|
+
...defaultContextOptions,
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
expect(basePath(c)).toBe('/sub-app-path')
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('should return basePath with wildcard', () => {
|
|
173
|
+
const handlerA = () => {}
|
|
174
|
+
const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')
|
|
175
|
+
const c = new Context(rawRequest, {
|
|
176
|
+
path: '/sub-app-path/foo/app/123/key',
|
|
177
|
+
matchResult: [
|
|
178
|
+
[
|
|
179
|
+
[
|
|
180
|
+
[
|
|
181
|
+
handlerA,
|
|
182
|
+
{ basePath: '/sub-app-path/*/app', handler: handlerA, method: 'GET', path: '/:id' },
|
|
183
|
+
],
|
|
184
|
+
{ id: '123' },
|
|
185
|
+
],
|
|
186
|
+
],
|
|
187
|
+
],
|
|
188
|
+
...defaultContextOptions,
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
expect(basePath(c)).toBe('/sub-app-path/foo/app')
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('should return the fully expanded basePath for nested route() apps', async () => {
|
|
195
|
+
const app = new App()
|
|
196
|
+
const sub1 = new App()
|
|
197
|
+
const sub2 = new App()
|
|
198
|
+
const sub3 = new App()
|
|
199
|
+
|
|
200
|
+
sub3.get('/assets/*', (c) => c.json({ baseRoutePath: baseRoutePath(c), basePath: basePath(c) }))
|
|
201
|
+
sub2.route('/:app/sub3/assets', sub3)
|
|
202
|
+
sub1.route('/:app/sub2/assets', sub2)
|
|
203
|
+
app.route('/:app/sub1/assets', sub1)
|
|
204
|
+
|
|
205
|
+
const res = await app.request(
|
|
206
|
+
'/s1/sub1/assets/ss2/sub2/assets/sss3/sub3/assets/assets/file-hoge/assets-kage.js'
|
|
207
|
+
)
|
|
208
|
+
expect(await res.json()).toEqual({
|
|
209
|
+
baseRoutePath: '/:app/sub1/assets/:app/sub2/assets/:app/sub3/assets',
|
|
210
|
+
basePath: '/s1/sub1/assets/ss2/sub2/assets/sss3/sub3/assets',
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('should return basePath with custom regex pattern', () => {
|
|
215
|
+
const handlerA = () => {}
|
|
216
|
+
const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')
|
|
217
|
+
const c = new Context(rawRequest, {
|
|
218
|
+
path: '/sub-app-path/foo/123/key',
|
|
219
|
+
matchResult: [
|
|
220
|
+
[
|
|
221
|
+
[
|
|
222
|
+
[
|
|
223
|
+
handlerA,
|
|
224
|
+
{
|
|
225
|
+
basePath: '/sub-app-path/:sub{foo|bar}',
|
|
226
|
+
handler: handlerA,
|
|
227
|
+
method: 'GET',
|
|
228
|
+
path: '/:id',
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
{ sub: 'foo', id: '123' },
|
|
232
|
+
],
|
|
233
|
+
],
|
|
234
|
+
],
|
|
235
|
+
...defaultContextOptions,
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
expect(basePath(c)).toBe('/sub-app-path/foo')
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('should return basePath with custom regex pattern mounted with app.route()', async () => {
|
|
242
|
+
const app = new App()
|
|
243
|
+
const subApp = new App()
|
|
244
|
+
|
|
245
|
+
subApp.get('/posts/:id', (c) => c.text(basePath(c)))
|
|
246
|
+
app.route('/api/:version{v[0-9]+}', subApp)
|
|
247
|
+
app.route('/tags/:tag{foo|bar}', subApp)
|
|
248
|
+
app.route('/users/:name{[a-z]+}/:group', subApp)
|
|
249
|
+
|
|
250
|
+
let res = await app.request('/api/v2/posts/123')
|
|
251
|
+
expect(await res.text()).toBe('/api/v2')
|
|
252
|
+
|
|
253
|
+
res = await app.request('/tags/bar/posts/123')
|
|
254
|
+
expect(await res.text()).toBe('/tags/bar')
|
|
255
|
+
|
|
256
|
+
res = await app.request('/users/abc/def/posts/123')
|
|
257
|
+
expect(await res.text()).toBe('/users/abc/def')
|
|
258
|
+
})
|
|
259
|
+
})
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { Context } from '../../context.ts'
|
|
2
|
+
import { GET_MATCH_RESULT } from '../../request/constants.ts'
|
|
3
|
+
import type { RouterRoute } from '../../types.ts'
|
|
4
|
+
import { getPattern, splitRoutingPath } from '../../utils/url.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get matched routes in the handler
|
|
8
|
+
*
|
|
9
|
+
* @param {Context} c - The context object
|
|
10
|
+
* @returns An array of matched routes
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { matchedRoutes } from 'acryl-router/helpers'
|
|
15
|
+
*
|
|
16
|
+
* app.use('*', async function logger(c, next) {
|
|
17
|
+
* await next()
|
|
18
|
+
* matchedRoutes(c).forEach(({ handler, method, path }, i) => {
|
|
19
|
+
* const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')
|
|
20
|
+
* console.log(
|
|
21
|
+
* method,
|
|
22
|
+
* ' ',
|
|
23
|
+
* path,
|
|
24
|
+
* ' '.repeat(Math.max(10 - path.length, 0)),
|
|
25
|
+
* name,
|
|
26
|
+
* i === c.req.routeIndex ? '<- respond from here' : ''
|
|
27
|
+
* )
|
|
28
|
+
* })
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const matchedRoutes = (c: Context): RouterRoute[] =>
|
|
33
|
+
// @ts-expect-error c.req[GET_MATCH_RESULT] is not typed
|
|
34
|
+
(c.req as unknown)[GET_MATCH_RESULT][0].map(([[, route]]) => route)
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the route path registered within the handler
|
|
38
|
+
*
|
|
39
|
+
* @param {Context} c - The context object
|
|
40
|
+
* @param {number} index - The index of the route from which to retrieve the path, similar to Array.prototype.at(), where a negative number is the index counted from the end of the matching route. Defaults to the current route index.
|
|
41
|
+
* @returns The route path registered within the handler
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { routePath } from 'acryl-router/helpers'
|
|
46
|
+
*
|
|
47
|
+
* app.use('*', (c, next) => {
|
|
48
|
+
* console.log(routePath(c)) // '*'
|
|
49
|
+
* console.log(routePath(c, -1)) // '/posts/:id'
|
|
50
|
+
* return next()
|
|
51
|
+
* })
|
|
52
|
+
*
|
|
53
|
+
* app.get('/posts/:id', (c) => {
|
|
54
|
+
* return c.text(routePath(c)) // '/posts/:id'
|
|
55
|
+
* })
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export const routePath = (c: Context, index?: number): string =>
|
|
59
|
+
matchedRoutes(c).at(index ?? c.req.routeIndex)?.path ?? ''
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get the basePath of the as-is route specified by routing.
|
|
63
|
+
*
|
|
64
|
+
* @param {Context} c - The context object
|
|
65
|
+
* @param {number} index - The index of the route from which to retrieve the path, similar to Array.prototype.at(), where a negative number is the index counted from the end of the matching route. Defaults to the current route index.
|
|
66
|
+
* @returns The basePath of the as-is route specified by routing.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* import { baseRoutePath } from 'acryl-router/helpers'
|
|
71
|
+
*
|
|
72
|
+
* const app = new App()
|
|
73
|
+
*
|
|
74
|
+
* const subApp = new App()
|
|
75
|
+
* subApp.get('/posts/:id', (c) => {
|
|
76
|
+
* return c.text(baseRoutePath(c)) // '/:sub'
|
|
77
|
+
* })
|
|
78
|
+
*
|
|
79
|
+
* app.route('/:sub', subApp)
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export const baseRoutePath = (c: Context, index?: number): string =>
|
|
83
|
+
matchedRoutes(c).at(index ?? c.req.routeIndex)?.basePath ?? ''
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the basePath with embedded parameters
|
|
87
|
+
*
|
|
88
|
+
* @param {Context} c - The context object
|
|
89
|
+
* @param {number} index - The index of the route from which to retrieve the path, similar to Array.prototype.at(), where a negative number is the index counted from the end of the matching route. Defaults to the current route index.
|
|
90
|
+
* @returns The basePath with embedded parameters.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* import { basePath } from 'acryl-router/helpers'
|
|
95
|
+
*
|
|
96
|
+
* const app = new App()
|
|
97
|
+
*
|
|
98
|
+
* const subApp = new App()
|
|
99
|
+
* subApp.get('/posts/:id', (c) => {
|
|
100
|
+
* return c.text(basePath(c)) // '/requested-sub-app-path'
|
|
101
|
+
* })
|
|
102
|
+
*
|
|
103
|
+
* app.route('/:sub', subApp)
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
const basePathCacheMap: WeakMap<Context, Record<number, string>> = new WeakMap()
|
|
107
|
+
export const basePath = (c: Context, index?: number): string => {
|
|
108
|
+
index ??= c.req.routeIndex
|
|
109
|
+
|
|
110
|
+
const cache = basePathCacheMap.get(c) || []
|
|
111
|
+
if (typeof cache[index] === 'string') {
|
|
112
|
+
return cache[index]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let result: string
|
|
116
|
+
const rp = baseRoutePath(c, index)
|
|
117
|
+
if (!/[:*]/.test(rp)) {
|
|
118
|
+
result = rp
|
|
119
|
+
} else {
|
|
120
|
+
const paths = splitRoutingPath(rp)
|
|
121
|
+
|
|
122
|
+
const reqPath = c.req.path
|
|
123
|
+
let basePathLength = 0
|
|
124
|
+
for (let i = 0, len = paths.length; i < len; i++) {
|
|
125
|
+
const pattern = getPattern(paths[i], paths[i + 1])
|
|
126
|
+
if (pattern) {
|
|
127
|
+
const re = pattern[2] === true || pattern === '*' ? /[^\/]+/ : pattern[2]
|
|
128
|
+
const restPath = reqPath.substring(basePathLength + 1)
|
|
129
|
+
// like the router, fall back to matching only the current segment
|
|
130
|
+
const segment = restPath.split('/', 1)[0]
|
|
131
|
+
basePathLength += (restPath.match(re)?.[0] ?? (re.test(segment) ? segment : '')).length
|
|
132
|
+
} else {
|
|
133
|
+
basePathLength += paths[i].length
|
|
134
|
+
}
|
|
135
|
+
basePathLength += 1 // for '/'
|
|
136
|
+
}
|
|
137
|
+
result = reqPath.substring(0, basePathLength)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
cache[index] = result
|
|
141
|
+
basePathCacheMap.set(c, cache)
|
|
142
|
+
|
|
143
|
+
return result
|
|
144
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Context } from '../../context.ts'
|
|
2
|
+
import { HtmlEscapedCallbackPhase, resolveCallback } from '../../utils/html.ts'
|
|
3
|
+
import { StreamingApi } from '../../utils/stream.ts'
|
|
4
|
+
import { isOldBunVersion } from './utils.ts'
|
|
5
|
+
|
|
6
|
+
export interface SSEMessage {
|
|
7
|
+
data: string | Promise<string>
|
|
8
|
+
event?: string
|
|
9
|
+
id?: string
|
|
10
|
+
retry?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class SSEStreamingApi extends StreamingApi {
|
|
14
|
+
constructor(writable: WritableStream, readable: ReadableStream) {
|
|
15
|
+
super(writable, readable)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async writeSSE(message: SSEMessage) {
|
|
19
|
+
const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {})
|
|
20
|
+
const dataLines = (data as string)
|
|
21
|
+
.split(/\r\n|\r|\n/)
|
|
22
|
+
.map((line) => {
|
|
23
|
+
return `data: ${line}`
|
|
24
|
+
})
|
|
25
|
+
.join('\n')
|
|
26
|
+
|
|
27
|
+
for (const key of ['event', 'id'] as const) {
|
|
28
|
+
const value = message[key]
|
|
29
|
+
if (value && /[\r\n]/.test(value)) {
|
|
30
|
+
throw new Error(`${key} must not contain "\\r" or "\\n"`)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const sseData =
|
|
35
|
+
[
|
|
36
|
+
message.event && `event: ${message.event}`,
|
|
37
|
+
dataLines,
|
|
38
|
+
message.id !== undefined && `id: ${message.id}`,
|
|
39
|
+
message.retry !== undefined && `retry: ${message.retry}`,
|
|
40
|
+
]
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.join('\n') + '\n\n'
|
|
43
|
+
|
|
44
|
+
await this.write(sseData)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const run = async (
|
|
49
|
+
stream: SSEStreamingApi,
|
|
50
|
+
cb: (stream: SSEStreamingApi) => Promise<void>,
|
|
51
|
+
onError?: (e: Error, stream: SSEStreamingApi) => Promise<void>
|
|
52
|
+
): Promise<void> => {
|
|
53
|
+
try {
|
|
54
|
+
await cb(stream)
|
|
55
|
+
} catch (e) {
|
|
56
|
+
if (e instanceof Error && onError) {
|
|
57
|
+
await onError(e, stream)
|
|
58
|
+
|
|
59
|
+
await stream.writeSSE({
|
|
60
|
+
event: 'error',
|
|
61
|
+
data: e.message,
|
|
62
|
+
})
|
|
63
|
+
} else {
|
|
64
|
+
console.error(e)
|
|
65
|
+
}
|
|
66
|
+
} finally {
|
|
67
|
+
stream.close()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const contextStash: WeakMap<ReadableStream, Context> = new WeakMap<ReadableStream, Context>()
|
|
72
|
+
|
|
73
|
+
export const streamSSE = (
|
|
74
|
+
c: Context,
|
|
75
|
+
cb: (stream: SSEStreamingApi) => Promise<void>,
|
|
76
|
+
onError?: (e: Error, stream: SSEStreamingApi) => Promise<void>
|
|
77
|
+
): Response => {
|
|
78
|
+
const { readable, writable } = new TransformStream()
|
|
79
|
+
const stream = new SSEStreamingApi(writable, readable)
|
|
80
|
+
|
|
81
|
+
// Until Bun v1.1.27, Bun didn't call cancel() on the ReadableStream for Response objects from Bun.serve()
|
|
82
|
+
if (isOldBunVersion()) {
|
|
83
|
+
c.req.raw.signal.addEventListener('abort', () => {
|
|
84
|
+
if (!stream.closed) {
|
|
85
|
+
stream.abort()
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// in bun, `c` is destroyed when the request is returned, so hold it until the end of streaming
|
|
91
|
+
contextStash.set(stream.responseReadable, c)
|
|
92
|
+
|
|
93
|
+
c.header('Transfer-Encoding', 'chunked')
|
|
94
|
+
c.header('Content-Type', 'text/event-stream')
|
|
95
|
+
c.header('Cache-Control', 'no-cache')
|
|
96
|
+
c.header('Connection', 'keep-alive')
|
|
97
|
+
|
|
98
|
+
run(stream, cb, onError)
|
|
99
|
+
|
|
100
|
+
return c.newResponse(stream.responseReadable)
|
|
101
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { stream } from './index.ts'
|
|
2
|
+
import { Context } from '../../context.ts'
|
|
3
|
+
|
|
4
|
+
describe('Basic Streaming Helper', () => {
|
|
5
|
+
const req = new Request('http://localhost/')
|
|
6
|
+
let c: Context
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
c = new Context(req)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('Check stream Response', async () => {
|
|
12
|
+
const res = stream(c, async (stream) => {
|
|
13
|
+
for (let i = 0; i < 3; i++) {
|
|
14
|
+
await stream.write(new Uint8Array([i]))
|
|
15
|
+
await stream.sleep(1)
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
if (!res.body) {
|
|
19
|
+
throw new Error('Body is null')
|
|
20
|
+
}
|
|
21
|
+
const reader = res.body.getReader()
|
|
22
|
+
for (let i = 0; i < 3; i++) {
|
|
23
|
+
const { value } = await reader.read()
|
|
24
|
+
expect(value).toEqual(new Uint8Array([i]))
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('Check stream Response if aborted by client', async () => {
|
|
29
|
+
let aborted = false
|
|
30
|
+
const res = stream(c, async (stream) => {
|
|
31
|
+
stream.onAbort(() => {
|
|
32
|
+
aborted = true
|
|
33
|
+
})
|
|
34
|
+
for (let i = 0; i < 3; i++) {
|
|
35
|
+
await stream.write(new Uint8Array([i]))
|
|
36
|
+
await stream.sleep(1)
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
if (!res.body) {
|
|
40
|
+
throw new Error('Body is null')
|
|
41
|
+
}
|
|
42
|
+
const reader = res.body.getReader()
|
|
43
|
+
const { value } = await reader.read()
|
|
44
|
+
expect(value).toEqual(new Uint8Array([0]))
|
|
45
|
+
reader.cancel()
|
|
46
|
+
expect(aborted).toBeTruthy()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('Check stream Response if aborted by abort signal', async () => {
|
|
50
|
+
// Emulate an old version of Bun (version 1.1.0) for this specific test case
|
|
51
|
+
// @ts-expect-error Bun is not typed
|
|
52
|
+
global.Bun = {
|
|
53
|
+
version: '1.1.0',
|
|
54
|
+
}
|
|
55
|
+
const ac = new AbortController()
|
|
56
|
+
const req = new Request('http://localhost/', { signal: ac.signal })
|
|
57
|
+
const c = new Context(req)
|
|
58
|
+
|
|
59
|
+
let aborted = false
|
|
60
|
+
const res = stream(c, async (stream) => {
|
|
61
|
+
stream.onAbort(() => {
|
|
62
|
+
aborted = true
|
|
63
|
+
})
|
|
64
|
+
for (let i = 0; i < 3; i++) {
|
|
65
|
+
await stream.write(new Uint8Array([i]))
|
|
66
|
+
await stream.sleep(1)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
if (!res.body) {
|
|
70
|
+
throw new Error('Body is null')
|
|
71
|
+
}
|
|
72
|
+
const reader = res.body.getReader()
|
|
73
|
+
const { value } = await reader.read()
|
|
74
|
+
expect(value).toEqual(new Uint8Array([0]))
|
|
75
|
+
ac.abort()
|
|
76
|
+
expect(aborted).toBeTruthy()
|
|
77
|
+
// @ts-expect-error Bun is not typed
|
|
78
|
+
delete global.Bun
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('Check stream Response if pipe is aborted by abort signal', async () => {
|
|
82
|
+
// Emulate an old version of Bun (version 1.1.0) for this specific test case
|
|
83
|
+
// @ts-expect-error Bun is not typed
|
|
84
|
+
global.Bun = {
|
|
85
|
+
version: '1.1.0',
|
|
86
|
+
}
|
|
87
|
+
const ac = new AbortController()
|
|
88
|
+
const req = new Request('http://localhost/', { signal: ac.signal })
|
|
89
|
+
const c = new Context(req)
|
|
90
|
+
|
|
91
|
+
let aborted = false
|
|
92
|
+
const res = stream(c, async (stream) => {
|
|
93
|
+
stream.onAbort(() => {
|
|
94
|
+
aborted = true
|
|
95
|
+
})
|
|
96
|
+
await stream.pipe(new ReadableStream())
|
|
97
|
+
})
|
|
98
|
+
if (!res.body) {
|
|
99
|
+
throw new Error('Body is null')
|
|
100
|
+
}
|
|
101
|
+
const reader = res.body.getReader()
|
|
102
|
+
const pReading = reader.read()
|
|
103
|
+
ac.abort()
|
|
104
|
+
await pReading
|
|
105
|
+
expect(aborted).toBeTruthy()
|
|
106
|
+
// @ts-expect-error Bun is not typed
|
|
107
|
+
delete global.Bun
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('Check stream Response if error occurred', async () => {
|
|
111
|
+
const onError = vi.fn()
|
|
112
|
+
const res = stream(
|
|
113
|
+
c,
|
|
114
|
+
async () => {
|
|
115
|
+
throw new Error('error')
|
|
116
|
+
},
|
|
117
|
+
onError
|
|
118
|
+
)
|
|
119
|
+
if (!res.body) {
|
|
120
|
+
throw new Error('Body is null')
|
|
121
|
+
}
|
|
122
|
+
const reader = res.body.getReader()
|
|
123
|
+
const { value } = await reader.read()
|
|
124
|
+
expect(value).toBeUndefined()
|
|
125
|
+
expect(onError).toBeCalledTimes(1)
|
|
126
|
+
expect(onError).toBeCalledWith(new Error('error'), expect.anything()) // 2nd argument is StreamingApi instance
|
|
127
|
+
})
|
|
128
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Context } from '../../context.ts'
|
|
2
|
+
import { StreamingApi } from '../../utils/stream.ts'
|
|
3
|
+
import { isOldBunVersion } from './utils.ts'
|
|
4
|
+
|
|
5
|
+
const contextStash: WeakMap<ReadableStream, Context> = new WeakMap<ReadableStream, Context>()
|
|
6
|
+
|
|
7
|
+
export const stream = (
|
|
8
|
+
c: Context,
|
|
9
|
+
cb: (stream: StreamingApi) => Promise<void>,
|
|
10
|
+
onError?: (e: Error, stream: StreamingApi) => Promise<void>
|
|
11
|
+
): Response => {
|
|
12
|
+
const { readable, writable } = new TransformStream()
|
|
13
|
+
const stream = new StreamingApi(writable, readable)
|
|
14
|
+
|
|
15
|
+
// Until Bun v1.1.27, Bun didn't call cancel() on the ReadableStream for Response objects from Bun.serve()
|
|
16
|
+
if (isOldBunVersion()) {
|
|
17
|
+
c.req.raw.signal.addEventListener('abort', () => {
|
|
18
|
+
if (!stream.closed) {
|
|
19
|
+
stream.abort()
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// in bun, `c` is destroyed when the request is returned, so hold it until the end of streaming
|
|
25
|
+
contextStash.set(stream.responseReadable, c)
|
|
26
|
+
;(async () => {
|
|
27
|
+
try {
|
|
28
|
+
await cb(stream)
|
|
29
|
+
} catch (e) {
|
|
30
|
+
if (e === undefined) {
|
|
31
|
+
// If reading is canceled without a reason value (e.g. by StreamingApi)
|
|
32
|
+
// then the .pipeTo() promise will reject with undefined.
|
|
33
|
+
// In this case, do nothing because the stream is already closed.
|
|
34
|
+
} else if (e instanceof Error && onError) {
|
|
35
|
+
await onError(e, stream)
|
|
36
|
+
} else {
|
|
37
|
+
console.error(e)
|
|
38
|
+
}
|
|
39
|
+
} finally {
|
|
40
|
+
stream.close()
|
|
41
|
+
}
|
|
42
|
+
})()
|
|
43
|
+
|
|
44
|
+
return c.newResponse(stream.responseReadable)
|
|
45
|
+
}
|