acrylblends 0.3.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 +6 -1
- package/dist/index.js +28 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/plugins/acryl-inspect/plugin.json +2 -1
- package/plugins/acryl-inspect/src/report.ts +16 -13
- package/plugins/acryl-inspect/src/service.ts +9 -7
- package/plugins/acryl-inspect/tests/report.spec.ts +7 -8
- package/plugins/acryl-inspect/tests/service.spec.ts +11 -10
- package/plugins/acryl-inspect/upstream.json +2 -2
- package/plugins/acryl-node-server/LICENSE +26 -0
- package/plugins/acryl-node-server/plugin.json +2 -1
- 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 +19 -6
- 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 +44 -8
- package/plugins/acryl-node-server/upstream.json +3 -3
- package/plugins/acryl-router/LICENSE +26 -0
- package/plugins/acryl-router/plugin.json +2 -1
- 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 -3
- package/plugins/acryl-router/src/helpers/schema-validator.ts +35 -0
- package/plugins/acryl-router/src/index.ts +6 -6
- package/plugins/acryl-router/src/middleware/index.ts +25 -6
- package/plugins/acryl-router/src/middleware/loopback-only.ts +19 -13
- package/plugins/acryl-router/src/service.ts +256 -64
- package/plugins/acryl-router/tests/docs-examples.spec.ts +152 -0
- package/plugins/acryl-router/tests/live-install.spec.ts +2 -2
- package/plugins/acryl-router/tests/loader.spec.ts +3 -2
- package/plugins/acryl-router/tests/service.spec.ts +264 -66
- package/plugins/acryl-router/tests/shadow-state.spec.ts +79 -0
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/plugin.json +2 -1
- package/plugins/acryl-router-http/src/bridge.ts +3 -1
- package/plugins/acryl-router-http/tests/adapter.spec.ts +16 -12
- package/plugins/acryl-router-http/tests/surfaces.spec.ts +12 -10
- package/plugins/acryl-router-http/upstream.json +3 -3
- package/plugins/acryl-state/NOTICE +30 -0
- package/plugins/acryl-state/plugin.json +1 -1
- package/plugins/acryl-state/src/cell.ts +1 -1
- package/plugins/acryl-state/src/service.ts +33 -15
- package/plugins/acryl-state/upstream.json +6 -4
- package/plugins/builder/script.ts +4 -7
- package/plugins/extensions/index.ts +2 -2
- package/plugins/extensions/resolve.ts +28 -3
- package/plugins/openai/prompt.ts +9 -7
- package/plugins/tsconfig.json +5 -4
- package/templates/agent/src/routes.ts +37 -27
- package/templates/agent/test/model.test.ts +2 -1
- package/templates/agent/tsconfig.json +8 -5
- package/templates/blank/src/routes.ts +6 -4
- package/templates/blank/tsconfig.json +2 -4
- package/templates/todo/src/page.ts +1 -1
- package/templates/todo/src/todo.ts +56 -29
- package/templates/todo/tsconfig.json +2 -4
- package/templates/todo/upstream.json +2 -2
- package/plugins/acryl-router/src/compose.ts +0 -21
- package/plugins/acryl-router/src/context.ts +0 -59
- package/plugins/acryl-router/src/helpers/read-body.ts +0 -59
- package/plugins/acryl-router/src/helpers/sse.ts +0 -83
- package/plugins/acryl-router/src/helpers/validate.ts +0 -44
- package/plugins/acryl-router/src/http-error.ts +0 -21
- package/plugins/acryl-router/src/middleware/body-limit.ts +0 -25
- package/plugins/acryl-router/src/middleware/cors.ts +0 -46
- package/plugins/acryl-router/src/middleware/logger.ts +0 -21
- package/plugins/acryl-router/src/middleware/request-id.ts +0 -24
- package/plugins/acryl-router/src/middleware/secure-headers.ts +0 -23
- package/plugins/acryl-router/src/pattern.ts +0 -89
- package/plugins/acryl-router/src/registrar.ts +0 -90
- package/plugins/acryl-router/src/table.ts +0 -156
- package/plugins/acryl-router/src/types.ts +0 -73
- package/plugins/acryl-router/src/util.ts +0 -23
- package/plugins/acryl-router/tests/api.spec.ts +0 -223
- package/plugins/acryl-router/tests/conformance.ts +0 -170
- package/plugins/acryl-router/tests/helpers.spec.ts +0 -172
- package/plugins/acryl-router/tests/middleware.spec.ts +0 -183
- package/plugins/acryl-router/tests/table.spec.ts +0 -47
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
-
"target": "
|
|
4
|
-
"lib": ["
|
|
3
|
+
"target": "ES2024",
|
|
4
|
+
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
|
5
5
|
"module": "NodeNext",
|
|
6
6
|
"moduleResolution": "NodeNext",
|
|
7
7
|
"strict": true,
|
|
8
|
-
"noUncheckedIndexedAccess": true,
|
|
9
|
-
"exactOptionalPropertyTypes": true,
|
|
10
8
|
"noImplicitOverride": true,
|
|
11
9
|
"verbatimModuleSyntax": true,
|
|
12
10
|
"erasableSyntaxOnly": true,
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
"examples/acryl-todo/src/index.ts": "src/todo.ts",
|
|
6
6
|
"examples/acryl-todo/src/page.ts": "src/page.ts"
|
|
7
7
|
},
|
|
8
|
-
"commit": "
|
|
8
|
+
"commit": "df79b188398b6d4f8cceda35a72b88300a10a9ce"
|
|
9
9
|
},
|
|
10
10
|
"rewrittenSpecifiers": 4,
|
|
11
|
-
"digest": "sha256:
|
|
11
|
+
"digest": "sha256:c4efede39387681003c6d2f0f2858e157a84bcaaf9ae94ebe23d10f72a538ffd"
|
|
12
12
|
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { Handler, Middleware, RouteContext } from './types.ts'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Run `middleware` in order around `final`. Each middleware receives `next`, which runs the rest of the chain and
|
|
5
|
-
* resolves to its response. Calling `next` twice in one middleware is a programming error and rejects.
|
|
6
|
-
*/
|
|
7
|
-
export function compose(middleware: readonly Middleware[], final: Handler): Handler {
|
|
8
|
-
return (c: RouteContext): Promise<Response> => {
|
|
9
|
-
const dispatch = async (index: number): Promise<Response> => {
|
|
10
|
-
const current = middleware[index]
|
|
11
|
-
if (current === undefined) return final(c)
|
|
12
|
-
let called = false
|
|
13
|
-
return current(c, () => {
|
|
14
|
-
if (called) return Promise.reject(new Error('router: next() called more than once'))
|
|
15
|
-
called = true
|
|
16
|
-
return dispatch(index + 1)
|
|
17
|
-
})
|
|
18
|
-
}
|
|
19
|
-
return dispatch(0)
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import type { ConnectionInfo, ResponseBody, RouteContext } from './types.ts'
|
|
2
|
-
|
|
3
|
-
const JSON_TYPE = 'application/json; charset=utf-8'
|
|
4
|
-
const TEXT_TYPE = 'text/plain; charset=utf-8'
|
|
5
|
-
const HTML_TYPE = 'text/html; charset=utf-8'
|
|
6
|
-
|
|
7
|
-
/** Statuses that must not carry a body; a `Response` constructed with one throws. */
|
|
8
|
-
const NULL_BODY_STATUSES = new Set([101, 204, 205, 304])
|
|
9
|
-
const bodyFor = <T>(status: number, body: T): T | null => NULL_BODY_STATUSES.has(status) ? null : body
|
|
10
|
-
|
|
11
|
-
/** Build the per-request context. `notFound` is what `c.notFound()` runs: the router's own answer. */
|
|
12
|
-
export function createContext(
|
|
13
|
-
req: Request,
|
|
14
|
-
params: Record<string, string>,
|
|
15
|
-
signal: AbortSignal,
|
|
16
|
-
connection: ConnectionInfo,
|
|
17
|
-
track: (task: Promise<unknown>) => void,
|
|
18
|
-
notFound: (c: RouteContext) => Response | Promise<Response>,
|
|
19
|
-
): RouteContext {
|
|
20
|
-
const values = new Map<string, unknown>()
|
|
21
|
-
const url = new URL(req.url)
|
|
22
|
-
const defaults = new Headers()
|
|
23
|
-
let defaultStatus = 200
|
|
24
|
-
|
|
25
|
-
const respond = (body: ResponseBody, status: number | undefined, type: string | undefined, extra?: Record<string, string>): Response => {
|
|
26
|
-
const code = status ?? defaultStatus
|
|
27
|
-
const headers = new Headers(defaults)
|
|
28
|
-
if (type !== undefined && !headers.has('content-type')) headers.set('content-type', type)
|
|
29
|
-
for (const [name, value] of Object.entries(extra ?? {})) headers.set(name, value)
|
|
30
|
-
return new Response(bodyFor(code, body), { status: code, headers })
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const context: RouteContext = {
|
|
34
|
-
req,
|
|
35
|
-
url,
|
|
36
|
-
params,
|
|
37
|
-
connection,
|
|
38
|
-
signal,
|
|
39
|
-
waitUntil: track,
|
|
40
|
-
get: <T>(key: string) => values.get(key) as T | undefined,
|
|
41
|
-
set: (key, value) => { values.set(key, value) },
|
|
42
|
-
header: (name, value) => { defaults.set(name, value) },
|
|
43
|
-
status: code => { defaultStatus = code },
|
|
44
|
-
query: ((name?: string) => {
|
|
45
|
-
if (name !== undefined) return url.searchParams.get(name) ?? undefined
|
|
46
|
-
const all: Record<string, string> = {}
|
|
47
|
-
for (const [key, value] of url.searchParams) if (!(key in all)) all[key] = value
|
|
48
|
-
return all
|
|
49
|
-
}) as RouteContext['query'],
|
|
50
|
-
param: ((name?: string) => name === undefined ? params : params[name]) as RouteContext['param'],
|
|
51
|
-
json: (data, status) => respond(JSON.stringify(data), status, JSON_TYPE),
|
|
52
|
-
text: (body, status) => respond(body, status, TEXT_TYPE),
|
|
53
|
-
html: (body, status) => respond(body, status, HTML_TYPE),
|
|
54
|
-
body: (data, status, headers) => respond(data, status, undefined, headers),
|
|
55
|
-
redirect: (location, status = 302) => respond(null, status, undefined, { location }),
|
|
56
|
-
notFound: () => notFound(context),
|
|
57
|
-
}
|
|
58
|
-
return context
|
|
59
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { HttpError } from '../http-error.ts'
|
|
2
|
-
import { BODY_LIMIT_KEY } from '../middleware/body-limit.ts'
|
|
3
|
-
import type { RouteContext } from '../types.ts'
|
|
4
|
-
|
|
5
|
-
export interface ReadBodyOptions {
|
|
6
|
-
/** The most bytes to accept. Defaults to the `bodyLimit` middleware's limit when it ran, otherwise 1 MiB. */
|
|
7
|
-
readonly maxBytes?: number
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const DEFAULT_MAX_BYTES = 1024 * 1024
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Read the request body as bytes, counting as it streams, and stop at the limit (a 413 `HttpError`). Stops at once when the request is aborted.
|
|
14
|
-
* A request with no body gives an empty array.
|
|
15
|
-
*/
|
|
16
|
-
export async function readBytes(c: RouteContext, options: ReadBodyOptions = {}): Promise<Uint8Array> {
|
|
17
|
-
const limit = options.maxBytes ?? c.get<number>(BODY_LIMIT_KEY) ?? DEFAULT_MAX_BYTES
|
|
18
|
-
c.signal.throwIfAborted()
|
|
19
|
-
const reader = c.req.body?.getReader()
|
|
20
|
-
if (reader === undefined) return new Uint8Array()
|
|
21
|
-
const onAbort = (): void => { void reader.cancel(c.signal.reason).catch(() => undefined) }
|
|
22
|
-
c.signal.addEventListener('abort', onAbort, { once: true })
|
|
23
|
-
const chunks: Uint8Array[] = []
|
|
24
|
-
let size = 0
|
|
25
|
-
try {
|
|
26
|
-
for (;;) {
|
|
27
|
-
const { done, value } = await reader.read()
|
|
28
|
-
if (done) break
|
|
29
|
-
size += value.byteLength
|
|
30
|
-
if (size > limit) {
|
|
31
|
-
await reader.cancel().catch(() => undefined)
|
|
32
|
-
throw new HttpError(413, { message: 'payload too large' })
|
|
33
|
-
}
|
|
34
|
-
chunks.push(value)
|
|
35
|
-
}
|
|
36
|
-
} finally {
|
|
37
|
-
c.signal.removeEventListener('abort', onAbort)
|
|
38
|
-
}
|
|
39
|
-
c.signal.throwIfAborted()
|
|
40
|
-
const bytes = new Uint8Array(size)
|
|
41
|
-
let offset = 0
|
|
42
|
-
for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength }
|
|
43
|
-
return bytes
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** The body as UTF-8 text, within the limit. */
|
|
47
|
-
export async function readText(c: RouteContext, options?: ReadBodyOptions): Promise<string> {
|
|
48
|
-
return new TextDecoder().decode(await readBytes(c, options))
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** The body parsed as JSON, within the limit. Bad JSON is a 400 `HttpError`. */
|
|
52
|
-
export async function readJson(c: RouteContext, options?: ReadBodyOptions): Promise<unknown> {
|
|
53
|
-
const text = await readText(c, options)
|
|
54
|
-
try {
|
|
55
|
-
return JSON.parse(text) as unknown
|
|
56
|
-
} catch {
|
|
57
|
-
throw new HttpError(400, { message: 'invalid json' })
|
|
58
|
-
}
|
|
59
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import type { RouteContext } from '../types.ts'
|
|
2
|
-
|
|
3
|
-
export interface SseStream {
|
|
4
|
-
/** Send one event. Objects and arrays are sent as JSON; strings as they are. */
|
|
5
|
-
send(data: unknown, options?: { readonly event?: string, readonly id?: string }): void
|
|
6
|
-
/** Send a comment line (a keep-alive that clients ignore). */
|
|
7
|
-
comment(text: string): void
|
|
8
|
-
/** End the stream from the server side. */
|
|
9
|
-
close(): void
|
|
10
|
-
/** Aborted when the client goes away or the router is disposed. */
|
|
11
|
-
readonly signal: AbortSignal
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const HEADERS = {
|
|
15
|
-
'content-type': 'text/event-stream; charset=utf-8',
|
|
16
|
-
'cache-control': 'no-store',
|
|
17
|
-
'x-content-type-options': 'nosniff',
|
|
18
|
-
} as const
|
|
19
|
-
|
|
20
|
-
const encode = (text: string): Uint8Array => new TextEncoder().encode(text)
|
|
21
|
-
|
|
22
|
-
export interface SseOptions {
|
|
23
|
-
/** A comment line is sent this often so proxies and browsers keep an idle stream open. Default 15 seconds. */
|
|
24
|
-
readonly keepAliveMs?: number
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const DEFAULT_KEEP_ALIVE_MS = 15_000
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* A server-sent-event response. `run` starts the stream; whatever cleanup function it returns runs exactly once when the stream
|
|
31
|
-
* ends for any reason (the client disconnects, `close()`, a plugin unload, the router being disposed). The request is held in flight with `waitUntil`
|
|
32
|
-
* until then, so disposal waits for the stream instead of leaking it.
|
|
33
|
-
*/
|
|
34
|
-
export function streamSSE(c: RouteContext, run: (stream: SseStream) => void | (() => void), options: SseOptions = {}): Response {
|
|
35
|
-
let finish!: () => void
|
|
36
|
-
c.waitUntil(new Promise<void>(resolve => { finish = resolve }))
|
|
37
|
-
let controller!: ReadableStreamDefaultController<Uint8Array>
|
|
38
|
-
let cleanup: (() => void) | undefined
|
|
39
|
-
let ended = false
|
|
40
|
-
let keepAlive: ReturnType<typeof setInterval> | undefined
|
|
41
|
-
|
|
42
|
-
const end = (): void => {
|
|
43
|
-
if (ended) return
|
|
44
|
-
ended = true
|
|
45
|
-
clearInterval(keepAlive)
|
|
46
|
-
c.signal.removeEventListener('abort', end)
|
|
47
|
-
try { cleanup?.() } finally {
|
|
48
|
-
try { controller.close() } catch { /* already closed */ }
|
|
49
|
-
finish()
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const body = new ReadableStream<Uint8Array>({
|
|
54
|
-
start: ctrl => {
|
|
55
|
-
controller = ctrl
|
|
56
|
-
const stream: SseStream = {
|
|
57
|
-
signal: c.signal,
|
|
58
|
-
send: (data, options) => {
|
|
59
|
-
if (ended) return
|
|
60
|
-
const lines: string[] = []
|
|
61
|
-
if (options?.id !== undefined) lines.push(`id: ${options.id}`)
|
|
62
|
-
if (options?.event !== undefined) lines.push(`event: ${options.event}`)
|
|
63
|
-
const payload = typeof data === 'string' ? data : JSON.stringify(data)
|
|
64
|
-
for (const line of payload.split('\n')) lines.push(`data: ${line}`)
|
|
65
|
-
try { controller.enqueue(encode(`${lines.join('\n')}\n\n`)) } catch { end() }
|
|
66
|
-
},
|
|
67
|
-
comment: text => {
|
|
68
|
-
if (ended) return
|
|
69
|
-
try { controller.enqueue(encode(`: ${text.replaceAll('\n', ' ')}\n\n`)) } catch { end() }
|
|
70
|
-
},
|
|
71
|
-
close: end,
|
|
72
|
-
}
|
|
73
|
-
if (c.signal.aborted) { end(); return }
|
|
74
|
-
c.signal.addEventListener('abort', end, { once: true })
|
|
75
|
-
keepAlive = setInterval(() => { stream.comment('keepalive') }, options.keepAliveMs ?? DEFAULT_KEEP_ALIVE_MS)
|
|
76
|
-
keepAlive.unref()
|
|
77
|
-
cleanup = run(stream) ?? undefined
|
|
78
|
-
if (ended) { try { cleanup?.() } catch { /* the stream already ended */ } }
|
|
79
|
-
},
|
|
80
|
-
cancel: end,
|
|
81
|
-
})
|
|
82
|
-
return new Response(body, { headers: HEADERS })
|
|
83
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { HttpError } from '../http-error.ts'
|
|
2
|
-
import type { Middleware, RouteContext } from '../types.ts'
|
|
3
|
-
import { readJson } from './read-body.ts'
|
|
4
|
-
|
|
5
|
-
/** The slice of the Standard Schema interface this needs; Schemastery (what Cordis uses), Zod and Valibot all implement it. */
|
|
6
|
-
export interface StandardSchema<T = unknown> {
|
|
7
|
-
readonly '~standard': {
|
|
8
|
-
validate(value: unknown): StandardResult<T> | Promise<StandardResult<T>>
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type StandardResult<T> =
|
|
13
|
-
| { readonly value: T, readonly issues?: undefined }
|
|
14
|
-
| { readonly issues: readonly { readonly message: string, readonly path?: readonly unknown[] | undefined }[] }
|
|
15
|
-
|
|
16
|
-
export type ValidationTarget = 'json' | 'query' | 'param'
|
|
17
|
-
|
|
18
|
-
const key = (target: ValidationTarget): string => `valid:${target}`
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Validate part of the request against a schema before the handler runs. A failure is a 400 with the issues; success stores the
|
|
22
|
-
* parsed value, which the handler reads with `valid(c, target)`. `json` reads the body within the limit; `query` is the first value of each query
|
|
23
|
-
* parameter; `param` is the matched path parameters.
|
|
24
|
-
*/
|
|
25
|
-
export function validate<T>(target: ValidationTarget, schema: StandardSchema<T>): Middleware {
|
|
26
|
-
return async (c, next) => {
|
|
27
|
-
const input = target === 'json' ? await readJson(c) : target === 'query' ? c.query() : { ...c.param() }
|
|
28
|
-
const result = await schema['~standard'].validate(input)
|
|
29
|
-
if (result.issues !== undefined) {
|
|
30
|
-
throw new HttpError(400, {
|
|
31
|
-
response: c.json({ error: `invalid ${target}`, issues: result.issues.map(issue => ({ message: issue.message, path: (issue.path ?? []).map(part => String(typeof part === 'object' && part !== null && 'key' in part ? (part as { key: unknown }).key : part)).join('.') })) }, 400),
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
c.set(key(target), result.value)
|
|
35
|
-
return next()
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** The value `validate(target, schema)` stored. Throws if that middleware did not run on this route. */
|
|
40
|
-
export function valid<T>(c: RouteContext, target: ValidationTarget): T {
|
|
41
|
-
const value = c.get<T>(key(target))
|
|
42
|
-
if (value === undefined) throw new Error(`valid("${target}"): no validate("${target}", schema) ran before this handler`)
|
|
43
|
-
return value
|
|
44
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* An error that carries the response it should become. A handler or middleware throws it to stop a request
|
|
3
|
-
* with a status: `throw new HttpError(401, { message: 'Sign in first' })`. The router answers with `getResponse()`; a registered
|
|
4
|
-
* `onError` handler still sees every other error.
|
|
5
|
-
*/
|
|
6
|
-
export class HttpError extends Error {
|
|
7
|
-
readonly status: number
|
|
8
|
-
private readonly response: Response | undefined
|
|
9
|
-
|
|
10
|
-
constructor(status: number, options: { readonly message?: string, readonly response?: Response, readonly cause?: unknown } = {}) {
|
|
11
|
-
super(options.message ?? `HTTP ${String(status)}`, options.cause === undefined ? undefined : { cause: options.cause })
|
|
12
|
-
this.name = 'HttpError'
|
|
13
|
-
this.status = status
|
|
14
|
-
this.response = options.response
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** The response this error stands for: the one it was given, or its message as plain text with its status. */
|
|
18
|
-
getResponse(): Response {
|
|
19
|
-
return this.response ?? new Response(this.message, { status: this.status, headers: { 'content-type': 'text/plain; charset=utf-8' } })
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { Middleware, RouteContext } from '../types.ts'
|
|
2
|
-
|
|
3
|
-
export interface BodyLimitOptions {
|
|
4
|
-
readonly maxBytes: number
|
|
5
|
-
/** The response for a body that is too large. Defaults to a 413 with a small JSON body. */
|
|
6
|
-
readonly onError?: (c: RouteContext) => Response | Promise<Response>
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/** The context key under which the limit is kept for the body-reading helpers. */
|
|
10
|
-
export const BODY_LIMIT_KEY = 'bodyLimit'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Refuse a request whose declared `content-length` is over the limit, and remember the limit so `readJson` and `readText` enforce it on the bytes
|
|
14
|
-
* they read (a chunked body declares no length). A request body is a stream: only the helpers, or a handler that counts, can stop it midway.
|
|
15
|
-
*/
|
|
16
|
-
export function bodyLimit(options: BodyLimitOptions): Middleware {
|
|
17
|
-
return async (c, next) => {
|
|
18
|
-
const declared = Number(c.req.headers.get('content-length'))
|
|
19
|
-
if (Number.isFinite(declared) && declared > options.maxBytes) {
|
|
20
|
-
return options.onError === undefined ? c.json({ error: 'payload too large', maxBytes: options.maxBytes }, 413) : options.onError(c)
|
|
21
|
-
}
|
|
22
|
-
c.set(BODY_LIMIT_KEY, options.maxBytes)
|
|
23
|
-
return next()
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { Middleware } from '../types.ts'
|
|
2
|
-
import { appendVary, setHeaders } from '../util.ts'
|
|
3
|
-
|
|
4
|
-
export interface CorsOptions {
|
|
5
|
-
/** `'*'`, one origin, a list, or a function that returns the origin to allow (or `undefined` to refuse). */
|
|
6
|
-
readonly origin?: string | readonly string[] | ((origin: string) => string | undefined)
|
|
7
|
-
readonly allowMethods?: readonly string[]
|
|
8
|
-
readonly allowHeaders?: readonly string[]
|
|
9
|
-
readonly exposeHeaders?: readonly string[]
|
|
10
|
-
readonly maxAge?: number
|
|
11
|
-
readonly credentials?: boolean
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const DEFAULT_METHODS = ['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']
|
|
15
|
-
|
|
16
|
-
/** Cross-origin resource sharing, with preflight answered here (a 204 that never reaches the route). */
|
|
17
|
-
export function cors(options: CorsOptions = {}): Middleware {
|
|
18
|
-
const allowed = (origin: string): string | undefined => {
|
|
19
|
-
const rule = options.origin ?? '*'
|
|
20
|
-
if (typeof rule === 'function') return rule(origin)
|
|
21
|
-
if (typeof rule === 'string') return rule === '*' || rule === origin ? rule : undefined
|
|
22
|
-
return rule.includes(origin) ? origin : undefined
|
|
23
|
-
}
|
|
24
|
-
return async (c, next) => {
|
|
25
|
-
const origin = c.req.headers.get('origin')
|
|
26
|
-
const match = origin === null ? undefined : allowed(origin)
|
|
27
|
-
const shared: Record<string, string> = {}
|
|
28
|
-
if (match !== undefined) shared['access-control-allow-origin'] = match
|
|
29
|
-
if (options.credentials === true && match !== undefined) shared['access-control-allow-credentials'] = 'true'
|
|
30
|
-
if (options.exposeHeaders !== undefined && options.exposeHeaders.length > 0) shared['access-control-expose-headers'] = options.exposeHeaders.join(',')
|
|
31
|
-
|
|
32
|
-
if (c.req.method === 'OPTIONS' && c.req.headers.has('access-control-request-method')) {
|
|
33
|
-
const headers = { ...shared }
|
|
34
|
-
if (match !== undefined) {
|
|
35
|
-
headers['access-control-allow-methods'] = (options.allowMethods ?? DEFAULT_METHODS).join(',')
|
|
36
|
-
const requested = c.req.headers.get('access-control-request-headers')
|
|
37
|
-
const allowHeaders = options.allowHeaders ?? (requested === null ? [] : requested.split(',').map(part => part.trim()))
|
|
38
|
-
if (allowHeaders.length > 0) headers['access-control-allow-headers'] = allowHeaders.join(',')
|
|
39
|
-
if (options.maxAge !== undefined) headers['access-control-max-age'] = String(options.maxAge)
|
|
40
|
-
}
|
|
41
|
-
return appendVary(c.body(null, 204, headers), 'Origin')
|
|
42
|
-
}
|
|
43
|
-
const response = await next()
|
|
44
|
-
return appendVary(setHeaders(response, shared), 'Origin')
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { Middleware } from '../types.ts'
|
|
2
|
-
|
|
3
|
-
/** One line to a sink the caller owns, for example `line => ctx.logger.info(line)`. */
|
|
4
|
-
export type LogSink = (line: string) => void
|
|
5
|
-
|
|
6
|
-
/** Log each request on the way in and its status and time on the way out. The sink is required: a framework does not write to the console on its own. */
|
|
7
|
-
export function logger(sink: LogSink): Middleware {
|
|
8
|
-
return async (c, next) => {
|
|
9
|
-
const label = `${c.req.method} ${c.url.pathname}`
|
|
10
|
-
sink(`<-- ${label}`)
|
|
11
|
-
const started = performance.now()
|
|
12
|
-
try {
|
|
13
|
-
const response = await next()
|
|
14
|
-
sink(`--> ${label} ${String(response.status)} ${String(Math.round(performance.now() - started))}ms`)
|
|
15
|
-
return response
|
|
16
|
-
} catch (error) {
|
|
17
|
-
sink(`--> ${label} threw ${error instanceof Error ? error.message : String(error)} ${String(Math.round(performance.now() - started))}ms`)
|
|
18
|
-
throw error
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto'
|
|
2
|
-
import type { Middleware } from '../types.ts'
|
|
3
|
-
import { setHeaders } from '../util.ts'
|
|
4
|
-
|
|
5
|
-
export interface RequestIdOptions {
|
|
6
|
-
/** The header read from the request and written to the response. */
|
|
7
|
-
readonly header?: string
|
|
8
|
-
/** Makes an id when the request brought none. */
|
|
9
|
-
readonly generate?: () => string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const SAFE_ID = /^[\w\-=]{1,255}$/u
|
|
13
|
-
|
|
14
|
-
/** Give every request an id: the caller's, when it is well formed, otherwise a new one. It is `c.get('requestId')` and the response header. */
|
|
15
|
-
export function requestId(options: RequestIdOptions = {}): Middleware {
|
|
16
|
-
const header = options.header ?? 'x-request-id'
|
|
17
|
-
const generate = options.generate ?? randomUUID
|
|
18
|
-
return async (c, next) => {
|
|
19
|
-
const incoming = c.req.headers.get(header)
|
|
20
|
-
const id = incoming !== null && SAFE_ID.test(incoming) ? incoming : generate()
|
|
21
|
-
c.set('requestId', id)
|
|
22
|
-
return setHeaders(await next(), { [header]: id })
|
|
23
|
-
}
|
|
24
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { Middleware } from '../types.ts'
|
|
2
|
-
import { setHeaders } from '../util.ts'
|
|
3
|
-
|
|
4
|
-
export interface SecureHeadersOptions {
|
|
5
|
-
/** Extra or replacement headers, merged over the defaults. A value of `''` removes a default. */
|
|
6
|
-
readonly headers?: Readonly<Record<string, string>>
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const DEFAULTS: Record<string, string> = {
|
|
10
|
-
'x-content-type-options': 'nosniff',
|
|
11
|
-
'x-frame-options': 'SAMEORIGIN',
|
|
12
|
-
'referrer-policy': 'no-referrer',
|
|
13
|
-
'cross-origin-resource-policy': 'same-origin',
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** Add the standard hardening headers to every response (never overriding one the handler already set). */
|
|
17
|
-
export function secureHeaders(options: SecureHeadersOptions = {}): Middleware {
|
|
18
|
-
const wanted = Object.entries({ ...DEFAULTS, ...options.headers }).filter(([, value]) => value !== '')
|
|
19
|
-
return async (_c, next) => {
|
|
20
|
-
const response = await next()
|
|
21
|
-
return setHeaders(response, Object.fromEntries(wanted.filter(([name]) => !response.headers.has(name))))
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import type { TrailingSlash } from './types.ts'
|
|
2
|
-
|
|
3
|
-
/** One piece of a route path. */
|
|
4
|
-
export type Segment =
|
|
5
|
-
| { readonly kind: 'static', readonly value: string }
|
|
6
|
-
| { readonly kind: 'param', readonly name: string }
|
|
7
|
-
| { readonly kind: 'wildcard' }
|
|
8
|
-
|
|
9
|
-
export interface Pattern {
|
|
10
|
-
/** The path as written (normalized for the trailing-slash rule). */
|
|
11
|
-
readonly source: string
|
|
12
|
-
readonly segments: readonly Segment[]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Split a pathname into segments. `/` is the empty list; under `strict` a trailing slash leaves a final empty segment. */
|
|
16
|
-
export function splitPath(path: string, trailingSlash: TrailingSlash): string[] {
|
|
17
|
-
let value = path
|
|
18
|
-
if (trailingSlash === 'ignore' && value.length > 1 && value.endsWith('/')) value = value.slice(0, -1)
|
|
19
|
-
if (value === '/' || value === '') return []
|
|
20
|
-
return value.slice(1).split('/')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Compile `/a/:id/*`. A `*` is allowed only as the last segment; param names must be non-empty and unique. */
|
|
24
|
-
export function compilePattern(path: string, trailingSlash: TrailingSlash): Pattern {
|
|
25
|
-
if (!path.startsWith('/')) throw new TypeError(`router: path "${path}" must start with "/"`)
|
|
26
|
-
const parts = splitPath(path, trailingSlash)
|
|
27
|
-
const names = new Set<string>()
|
|
28
|
-
const segments: Segment[] = parts.map((part, index) => {
|
|
29
|
-
if (part === '*') {
|
|
30
|
-
if (index !== parts.length - 1) throw new TypeError(`router: "*" must be the last segment in "${path}"`)
|
|
31
|
-
return { kind: 'wildcard' }
|
|
32
|
-
}
|
|
33
|
-
if (part.startsWith(':')) {
|
|
34
|
-
const name = part.slice(1)
|
|
35
|
-
if (name === '') throw new TypeError(`router: empty parameter name in "${path}"`)
|
|
36
|
-
if (names.has(name)) throw new TypeError(`router: duplicate parameter ":${name}" in "${path}"`)
|
|
37
|
-
names.add(name)
|
|
38
|
-
return { kind: 'param', name }
|
|
39
|
-
}
|
|
40
|
-
return { kind: 'static', value: part }
|
|
41
|
-
})
|
|
42
|
-
return { source: path, segments }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** The same path with every parameter name erased, so `/a/:id` and `/a/:name` collide as duplicates. */
|
|
46
|
-
export function shapeOf(pattern: Pattern): string {
|
|
47
|
-
return '/' + pattern.segments.map(s => s.kind === 'static' ? s.value : s.kind === 'param' ? ':' : '*').join('/')
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function decode(value: string): string {
|
|
51
|
-
try { return decodeURIComponent(value) } catch { return value }
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Match concrete path segments against a pattern; returns the parameters, or `undefined` when it does not match. */
|
|
55
|
-
export function matchPattern(pattern: Pattern, parts: readonly string[]): Record<string, string> | undefined {
|
|
56
|
-
const params: Record<string, string> = {}
|
|
57
|
-
const { segments } = pattern
|
|
58
|
-
for (let i = 0; i < segments.length; i++) {
|
|
59
|
-
const segment = segments[i]
|
|
60
|
-
if (segment === undefined) return undefined
|
|
61
|
-
if (segment.kind === 'wildcard') {
|
|
62
|
-
params['*'] = parts.slice(i).map(decode).join('/')
|
|
63
|
-
return params
|
|
64
|
-
}
|
|
65
|
-
const part = parts[i]
|
|
66
|
-
if (part === undefined) return undefined
|
|
67
|
-
if (segment.kind === 'static') {
|
|
68
|
-
if (segment.value !== part) return undefined
|
|
69
|
-
} else {
|
|
70
|
-
params[segment.name] = decode(part)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return parts.length === segments.length ? params : undefined
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const SCORE = { static: 3, param: 2, wildcard: 1 } as const
|
|
77
|
-
|
|
78
|
-
/** Compare two matching patterns: the more specific one (static before param before wildcard, left to right) wins. */
|
|
79
|
-
export function compareSpecificity(a: Pattern, b: Pattern): number {
|
|
80
|
-
const length = Math.max(a.segments.length, b.segments.length)
|
|
81
|
-
for (let i = 0; i < length; i++) {
|
|
82
|
-
const left = a.segments[i]
|
|
83
|
-
const right = b.segments[i]
|
|
84
|
-
const l = left === undefined ? 0 : SCORE[left.kind]
|
|
85
|
-
const r = right === undefined ? 0 : SCORE[right.kind]
|
|
86
|
-
if (l !== r) return l - r
|
|
87
|
-
}
|
|
88
|
-
return 0
|
|
89
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The registration API every router scope shares (`get`, `use`, `group`): the router itself is one scope with an empty prefix, and
|
|
3
|
-
* `router.group('/api')` is another with a prefix. A scope never owns anything; every registration is released by the plugin that made it.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { Context } from '@deepseek-ai/cordis'
|
|
7
|
-
import { compose } from './compose.ts'
|
|
8
|
-
import type { Handler, Method, Middleware, RouteMethod } from './types.ts'
|
|
9
|
-
|
|
10
|
-
/** The last argument is the handler; everything before it is middleware that runs for this route only. */
|
|
11
|
-
export type RouteHandlers = readonly [...Middleware[], Handler]
|
|
12
|
-
|
|
13
|
-
export type UseArguments = readonly [Middleware, ...Middleware[]] | readonly [string, Middleware, ...Middleware[]]
|
|
14
|
-
|
|
15
|
-
export interface Registrar {
|
|
16
|
-
/** Register a route owned by the calling plugin. Returns a remover; the route also goes away when the caller unloads. */
|
|
17
|
-
route(method: RouteMethod, path: string, ...handlers: RouteHandlers): () => void
|
|
18
|
-
get(path: string, ...handlers: RouteHandlers): () => void
|
|
19
|
-
post(path: string, ...handlers: RouteHandlers): () => void
|
|
20
|
-
put(path: string, ...handlers: RouteHandlers): () => void
|
|
21
|
-
patch(path: string, ...handlers: RouteHandlers): () => void
|
|
22
|
-
delete(path: string, ...handlers: RouteHandlers): () => void
|
|
23
|
-
/** A route that answers every method no more specific route claims. */
|
|
24
|
-
all(path: string, ...handlers: RouteHandlers): () => void
|
|
25
|
-
/** The same handlers on several methods. Returns one remover for all of them. */
|
|
26
|
-
on(methods: Method | readonly Method[], path: string, ...handlers: RouteHandlers): () => void
|
|
27
|
-
/** Middleware for a path and everything below it (default: everything in this scope), in registration order. */
|
|
28
|
-
use(...args: UseArguments): () => void
|
|
29
|
-
/** A scope whose paths all start with `prefix`; optional middleware runs for everything under it. */
|
|
30
|
-
group(prefix: string, ...middleware: Middleware[]): Registrar
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** What a scope needs from the router: put one route or one middleware into the table, owned by `owner`. */
|
|
34
|
-
export interface Primitives {
|
|
35
|
-
add(owner: Context, method: RouteMethod, path: string, handler: Handler): () => void
|
|
36
|
-
use(owner: Context, path: string, middleware: Middleware): () => void
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const PREFIX = /^\/(?:[^/*:\s]+(?:\/[^/*:\s]+)*)?$/
|
|
40
|
-
|
|
41
|
-
/** Join a scope prefix and a path: `('/api', '/x')` is `/api/x`, `('/api', '/')` is `/api`, and an empty prefix leaves the path alone. */
|
|
42
|
-
export function joinPath(prefix: string, path: string): string {
|
|
43
|
-
if (prefix === '') return path
|
|
44
|
-
if (path === '/' || path === '') return prefix
|
|
45
|
-
if (!path.startsWith('/')) throw new TypeError(`router: path "${path}" must start with "/"`)
|
|
46
|
-
return `${prefix}${path}`
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function validatePrefix(prefix: string): void {
|
|
50
|
-
if (prefix !== '' && !PREFIX.test(prefix)) throw new TypeError(`router: group prefix "${prefix}" must be a plain path like "/api" (no wildcard, no parameter, no trailing slash)`)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function single(handlers: RouteHandlers): Handler {
|
|
54
|
-
const last = handlers.at(-1)
|
|
55
|
-
if (last === undefined) throw new TypeError('router: a route needs a handler')
|
|
56
|
-
const middleware = handlers.slice(0, -1) as Middleware[]
|
|
57
|
-
return middleware.length === 0 ? last as Handler : compose(middleware, last as Handler)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function createRegistrar(owner: Context, prefix: string, primitives: Primitives): Registrar {
|
|
61
|
-
validatePrefix(prefix)
|
|
62
|
-
const route = (method: RouteMethod, path: string, ...handlers: RouteHandlers): (() => void) =>
|
|
63
|
-
primitives.add(owner, method, joinPath(prefix, path), single(handlers))
|
|
64
|
-
const registrar: Registrar = {
|
|
65
|
-
route,
|
|
66
|
-
get: (path, ...h) => route('GET', path, ...h),
|
|
67
|
-
post: (path, ...h) => route('POST', path, ...h),
|
|
68
|
-
put: (path, ...h) => route('PUT', path, ...h),
|
|
69
|
-
patch: (path, ...h) => route('PATCH', path, ...h),
|
|
70
|
-
delete: (path, ...h) => route('DELETE', path, ...h),
|
|
71
|
-
all: (path, ...h) => route('*', path, ...h),
|
|
72
|
-
on: (methods, path, ...h) => {
|
|
73
|
-
const removers = (Array.isArray(methods) ? methods as readonly Method[] : [methods as Method]).map(method => route(method, path, ...h))
|
|
74
|
-
return () => { for (const remove of removers) remove() }
|
|
75
|
-
},
|
|
76
|
-
use: (...args) => {
|
|
77
|
-
const [first, ...rest] = args as readonly [string | Middleware, ...Middleware[]]
|
|
78
|
-
const path = typeof first === 'string' ? first : '/'
|
|
79
|
-
const middleware = typeof first === 'string' ? rest : [first, ...rest]
|
|
80
|
-
const removers = middleware.map(mw => primitives.use(owner, joinPath(prefix, path), mw))
|
|
81
|
-
return () => { for (const remove of removers) remove() }
|
|
82
|
-
},
|
|
83
|
-
group: (nested, ...middleware) => {
|
|
84
|
-
const scope = createRegistrar(owner, joinPath(prefix, nested), primitives)
|
|
85
|
-
if (middleware.length > 0) scope.use(...(middleware as [Middleware, ...Middleware[]]))
|
|
86
|
-
return scope
|
|
87
|
-
},
|
|
88
|
-
}
|
|
89
|
-
return registrar
|
|
90
|
-
}
|