acrylblends 0.4.0 → 0.5.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/LICENSE +73 -0
- package/dist/index.js +56 -2
- package/dist/index.js.map +1 -1
- package/docs/framework/LICENSE +23 -0
- package/docs/framework/README.md +63 -0
- package/docs/framework/UPSTREAM.md +3 -0
- package/docs/framework/agents/index.md +24 -0
- package/docs/framework/api/acryl.md +259 -0
- package/docs/framework/api/context.md +527 -0
- package/docs/framework/api/exception.md +80 -0
- package/docs/framework/api/index.md +12 -0
- package/docs/framework/api/presets.md +65 -0
- package/docs/framework/api/request.md +396 -0
- package/docs/framework/api/routing.md +303 -0
- package/docs/framework/api/summary.md +140 -0
- package/docs/framework/concepts/benchmarks.md +26 -0
- package/docs/framework/concepts/developer-experience.md +5 -0
- package/docs/framework/concepts/middleware.md +21 -0
- package/docs/framework/concepts/motivation.md +14 -0
- package/docs/framework/concepts/routers.md +63 -0
- package/docs/framework/concepts/stacks.md +235 -0
- package/docs/framework/concepts/web-standard.md +37 -0
- package/docs/framework/cordis/acryl-in-a-cordis-app.md +146 -0
- package/docs/framework/cordis/concepts.md +105 -0
- package/docs/framework/cordis/middleware.md +8 -0
- package/docs/framework/cordis/migration-from-0-3.md +111 -0
- package/docs/framework/cordis/migration-from-0-4.md +14 -0
- package/docs/framework/cordis/migration-from-dsh-webserver.md +103 -0
- package/docs/framework/cordis/mounting.md +85 -0
- package/docs/framework/cordis/node-server.md +75 -0
- package/docs/framework/cordis/plugin-lifecycle.md +111 -0
- package/docs/framework/cordis/surfaces.md +71 -0
- package/docs/framework/cordis/transport-and-cancellation.md +57 -0
- package/docs/framework/cordis/validation.md +135 -0
- package/docs/framework/getting-started/basic.md +196 -0
- package/docs/framework/getting-started/bun.md +221 -0
- package/docs/framework/getting-started/nodejs.md +344 -0
- package/docs/framework/getting-started/other-runtimes.md +23 -0
- package/docs/framework/guides/best-practices.md +231 -0
- package/docs/framework/guides/create-acrylblends.md +79 -0
- package/docs/framework/guides/examples.md +3 -0
- package/docs/framework/guides/faq.md +19 -0
- package/docs/framework/guides/helpers.md +33 -0
- package/docs/framework/guides/jsx-dom.md +299 -0
- package/docs/framework/guides/jsx.md +440 -0
- package/docs/framework/guides/middleware.md +253 -0
- package/docs/framework/guides/rpc.md +886 -0
- package/docs/framework/guides/testing.md +133 -0
- package/docs/framework/guides/validation.md +383 -0
- package/docs/framework/helpers/accepts.md +60 -0
- package/docs/framework/helpers/adapter.md +87 -0
- package/docs/framework/helpers/conninfo.md +112 -0
- package/docs/framework/helpers/cookie.md +232 -0
- package/docs/framework/helpers/css.md +285 -0
- package/docs/framework/helpers/dev.md +67 -0
- package/docs/framework/helpers/factory.md +180 -0
- package/docs/framework/helpers/html.md +123 -0
- package/docs/framework/helpers/jwt.md +184 -0
- package/docs/framework/helpers/proxy.md +118 -0
- package/docs/framework/helpers/route.md +143 -0
- package/docs/framework/helpers/ssg.md +393 -0
- package/docs/framework/helpers/streaming.md +119 -0
- package/docs/framework/helpers/testing.md +101 -0
- package/docs/framework/helpers/websocket.md +215 -0
- package/docs/framework/middleware/builtin/basic-auth.md +165 -0
- package/docs/framework/middleware/builtin/bearer-auth.md +138 -0
- package/docs/framework/middleware/builtin/body-limit.md +71 -0
- package/docs/framework/middleware/builtin/cache.md +122 -0
- package/docs/framework/middleware/builtin/combine.md +94 -0
- package/docs/framework/middleware/builtin/compress.md +59 -0
- package/docs/framework/middleware/builtin/context-storage.md +78 -0
- package/docs/framework/middleware/builtin/cors.md +135 -0
- package/docs/framework/middleware/builtin/csrf.md +105 -0
- package/docs/framework/middleware/builtin/etag.md +54 -0
- package/docs/framework/middleware/builtin/ip-restriction.md +69 -0
- package/docs/framework/middleware/builtin/jsx-renderer.md +286 -0
- package/docs/framework/middleware/builtin/jwk.md +192 -0
- package/docs/framework/middleware/builtin/jwt.md +145 -0
- package/docs/framework/middleware/builtin/language.md +232 -0
- package/docs/framework/middleware/builtin/logger.md +74 -0
- package/docs/framework/middleware/builtin/loopback-only.md +31 -0
- package/docs/framework/middleware/builtin/method-not-allowed.md +48 -0
- package/docs/framework/middleware/builtin/method-override.md +80 -0
- package/docs/framework/middleware/builtin/mount.md +106 -0
- package/docs/framework/middleware/builtin/pretty-json.md +53 -0
- package/docs/framework/middleware/builtin/request-id.md +94 -0
- package/docs/framework/middleware/builtin/secure-headers.md +247 -0
- package/docs/framework/middleware/builtin/timeout.md +98 -0
- package/docs/framework/middleware/builtin/timing.md +101 -0
- package/docs/framework/middleware/builtin/trailing-slash.md +74 -0
- package/docs/framework/middleware/third-party.md +4 -0
- package/docs/framework/upstream.json +9 -0
- package/lib/accepts-XBuZPVRI.js +52 -0
- package/lib/accepts.d.ts +33 -0
- package/lib/accepts.js +2 -0
- package/lib/acryl-base.d.ts +2 -0
- package/lib/acryl-base.js +332 -0
- package/lib/adapter.d.ts +12 -0
- package/lib/adapter.js +37 -0
- package/lib/app-BM0rTCp7.js +26 -0
- package/lib/app-CmAYW1Al.d.ts +21 -0
- package/lib/basic-auth.d.ts +72 -0
- package/lib/basic-auth.js +95 -0
- package/lib/bearer-auth.d.ts +89 -0
- package/lib/bearer-auth.js +94 -0
- package/lib/body-limit.d.ts +42 -0
- package/lib/body-limit.js +66 -0
- package/lib/client-BHTr318g.js +266 -0
- package/lib/client.d.ts +3 -0
- package/lib/client.js +2 -0
- package/lib/combine.d.ts +85 -0
- package/lib/combine.js +137 -0
- package/lib/compose-BuW2B6PZ.js +50 -0
- package/lib/compress.d.ts +38 -0
- package/lib/compress.js +79 -0
- package/lib/connection-C7xgP1hP.js +24 -0
- package/lib/conninfo.d.ts +25 -0
- package/lib/conninfo.js +2 -0
- package/lib/constants-Dy7ZvN1H.js +4 -0
- package/lib/context-Bm7rI-oK.d.ts +1234 -0
- package/lib/context-DDO0cfuP.js +365 -0
- package/lib/context-storage.d.ts +38 -0
- package/lib/context-storage.js +52 -0
- package/lib/cookie-ClMaKDQf.d.ts +40 -0
- package/lib/cookie.d.ts +24 -0
- package/lib/cookie.js +82 -0
- package/lib/cors.d.ts +52 -0
- package/lib/cors.js +106 -0
- package/lib/csrf.d.ts +75 -0
- package/lib/csrf.js +101 -0
- package/lib/etag.d.ts +40 -0
- package/lib/etag.js +135 -0
- package/lib/factory.d.ts +35 -0
- package/lib/factory.js +30 -0
- package/lib/headers-tu2YYN8T.d.ts +11 -0
- package/lib/html.d.ts +6 -0
- package/lib/html.js +31 -0
- package/lib/http-exception.d.ts +57 -0
- package/lib/http-exception.js +54 -0
- package/lib/http-status-DaAwGAFL.d.ts +35 -0
- package/lib/index-B-3F4wt2.d.ts +12 -0
- package/lib/index-UTBmM3GX.d.ts +68 -0
- package/lib/index-oOClm_Xk.d.ts +45 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +28 -0
- package/lib/ip-restriction.d.ts +68 -0
- package/lib/ip-restriction.js +166 -0
- package/lib/jwk.d.ts +52 -0
- package/lib/jwk.js +117 -0
- package/lib/jwt-BGJBw5AF.js +403 -0
- package/lib/jwt-Dg493Tai.d.ts +51 -0
- package/lib/jwt.d.ts +72 -0
- package/lib/jwt.js +115 -0
- package/lib/language.d.ts +88 -0
- package/lib/language.js +198 -0
- package/lib/logger.d.ts +22 -0
- package/lib/logger.js +54 -0
- package/lib/loopback-only.d.ts +45 -0
- package/lib/loopback-only.js +90 -0
- package/lib/method-not-allowed.d.ts +47 -0
- package/lib/method-not-allowed.js +82 -0
- package/lib/method-override.d.ts +46 -0
- package/lib/method-override.js +103 -0
- package/lib/mime-BQOYHl-l.d.ts +72 -0
- package/lib/node-server/conninfo.d.ts +11 -0
- package/lib/node-server/conninfo.js +19 -0
- package/lib/node-server/serve-static.d.ts +19 -0
- package/lib/node-server/serve-static.js +2 -0
- package/lib/node-server.d.ts +103 -0
- package/lib/node-server.js +1325 -0
- package/lib/powered-by.d.ts +28 -0
- package/lib/powered-by.js +24 -0
- package/lib/pretty-json.d.ts +40 -0
- package/lib/pretty-json.js +39 -0
- package/lib/reg-exp-router-DC1R3qoY.js +363 -0
- package/lib/request-id.d.ts +45 -0
- package/lib/request-id.js +34 -0
- package/lib/request.d.ts +2 -0
- package/lib/request.js +323 -0
- package/lib/route.d.ts +78 -0
- package/lib/route.js +126 -0
- package/lib/router/linear-router.d.ts +11 -0
- package/lib/router/linear-router.js +96 -0
- package/lib/router/pattern-router.d.ts +11 -0
- package/lib/router/pattern-router.js +41 -0
- package/lib/router/reg-exp-router.d.ts +35 -0
- package/lib/router/reg-exp-router.js +2 -0
- package/lib/router/smart-router.d.ts +15 -0
- package/lib/router/smart-router.js +2 -0
- package/lib/router/trie-router.d.ts +11 -0
- package/lib/router/trie-router.js +2 -0
- package/lib/router-CvUxUTOq.d.ts +99 -0
- package/lib/router.d.ts +2 -0
- package/lib/router.js +35 -0
- package/lib/schema-validator.d.ts +28 -0
- package/lib/schema-validator.js +22 -0
- package/lib/secure-headers.d.ts +125 -0
- package/lib/secure-headers.js +163 -0
- package/lib/serve-static-Dw_m6KxR.js +213 -0
- package/lib/serve-static.d.ts +31 -0
- package/lib/serve-static.js +77 -0
- package/lib/smart-router-C5CZ5XSM.js +49 -0
- package/lib/stream-CPdqZ0Fp.d.ts +34 -0
- package/lib/streaming-DHST3Scg.js +96 -0
- package/lib/streaming.d.ts +23 -0
- package/lib/streaming.js +2 -0
- package/lib/testing.d.ts +10 -0
- package/lib/testing.js +17 -0
- package/lib/timeout.d.ts +30 -0
- package/lib/timeout.js +42 -0
- package/lib/timing.d.ts +130 -0
- package/lib/timing.js +183 -0
- package/lib/trailing-slash.d.ts +91 -0
- package/lib/trailing-slash.js +85 -0
- package/lib/trie-router-CfFbapzL.js +146 -0
- package/lib/types-CDO6I3B0.d.ts +40 -0
- package/lib/types-CHesD1KG.d.ts +195 -0
- package/lib/types-CwrQjlaM.d.ts +105 -0
- package/lib/types-DR95J00T.d.ts +29 -0
- package/lib/types.d.ts +2 -0
- package/lib/types.js +4 -0
- package/lib/utils/accept.d.ts +9 -0
- package/lib/utils/accept.js +193 -0
- package/lib/utils/basic-auth.d.ts +8 -0
- package/lib/utils/basic-auth.js +20 -0
- package/lib/utils/body.d.ts +2 -0
- package/lib/utils/body.js +94 -0
- package/lib/utils/buffer.d.ts +19 -0
- package/lib/utils/buffer.js +51 -0
- package/lib/utils/color.d.ts +24 -0
- package/lib/utils/color.js +37 -0
- package/lib/utils/compress.d.ts +11 -0
- package/lib/utils/compress.js +11 -0
- package/lib/utils/concurrent.d.ts +17 -0
- package/lib/utils/concurrent.js +30 -0
- package/lib/utils/constants.d.ts +7 -0
- package/lib/utils/constants.js +7 -0
- package/lib/utils/cookie.d.ts +2 -0
- package/lib/utils/cookie.js +125 -0
- package/lib/utils/crypto.d.ts +14 -0
- package/lib/utils/crypto.js +34 -0
- package/lib/utils/encode.d.ts +11 -0
- package/lib/utils/encode.js +33 -0
- package/lib/utils/filepath.d.ts +14 -0
- package/lib/utils/filepath.js +25 -0
- package/lib/utils/handler.d.ts +9 -0
- package/lib/utils/handler.js +12 -0
- package/lib/utils/headers.d.ts +2 -0
- package/lib/utils/headers.js +1 -0
- package/lib/utils/html.d.ts +46 -0
- package/lib/utils/html.js +103 -0
- package/lib/utils/http-status.d.ts +2 -0
- package/lib/utils/http-status.js +1 -0
- package/lib/utils/ipaddr.d.ts +56 -0
- package/lib/utils/ipaddr.js +238 -0
- package/lib/utils/jwt.d.ts +24 -0
- package/lib/utils/jwt.js +2 -0
- package/lib/utils/mime.d.ts +2 -0
- package/lib/utils/mime.js +77 -0
- package/lib/utils/stream.d.ts +2 -0
- package/lib/utils/stream.js +88 -0
- package/lib/utils/types.d.ts +2 -0
- package/lib/utils/types.js +1 -0
- package/lib/utils/url.d.ts +46 -0
- package/lib/utils/url.js +197 -0
- package/lib/utils-dw8lxqjL.js +4 -0
- package/lib/validator-D84mq-dO.js +65 -0
- package/lib/validator.d.ts +42 -0
- package/lib/validator.js +2 -0
- package/lib/ws.d.ts +2 -0
- package/lib/ws.js +53 -0
- package/package.json +315 -15
- package/plugins/acryl-inspect/src/report.ts +15 -10
- package/plugins/acryl-inspect/src/service.ts +2 -2
- package/plugins/acryl-inspect/tests/report.spec.ts +9 -3
- package/plugins/acryl-inspect/tests/service.spec.ts +3 -3
- package/plugins/acryl-inspect/upstream.json +2 -2
- package/plugins/acryl-node-server/tests/adapter/app.ts +2 -2
- package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +1 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +1 -1
- package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +2 -2
- package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +4 -4
- package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +5 -5
- package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +2 -2
- package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +20 -20
- package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +2 -2
- package/plugins/acryl-node-server/tests/adapter/server.test.ts +50 -50
- package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +4 -4
- package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +9 -9
- package/plugins/acryl-node-server/tests/server.spec.ts +6 -6
- package/plugins/acryl-node-server/upstream.json +2 -2
- package/plugins/acryl-router/src/client.ts +1 -1
- package/plugins/acryl-router/src/connection.ts +2 -2
- package/plugins/acryl-router/src/core/app-base.ts +29 -29
- package/plugins/acryl-router/src/core/app.test.ts +175 -175
- package/plugins/acryl-router/src/core/app.ts +8 -8
- package/plugins/acryl-router/src/core/client/client.test.ts +76 -76
- package/plugins/acryl-router/src/core/client/client.ts +2 -2
- package/plugins/acryl-router/src/core/client/fetch-result-please.ts +1 -1
- package/plugins/acryl-router/src/core/client/index.ts +1 -1
- package/plugins/acryl-router/src/core/client/types.test.ts +6 -6
- package/plugins/acryl-router/src/core/client/types.ts +16 -16
- package/plugins/acryl-router/src/core/client/utils.test.ts +2 -2
- package/plugins/acryl-router/src/core/context.test.ts +4 -4
- package/plugins/acryl-router/src/core/context.ts +6 -6
- package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +3 -3
- package/plugins/acryl-router/src/core/helper/accepts/index.ts +1 -1
- package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +6 -6
- package/plugins/acryl-router/src/core/helper/adapter/index.ts +1 -1
- package/plugins/acryl-router/src/core/helper/conninfo/index.ts +1 -1
- package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +8 -8
- package/plugins/acryl-router/src/core/helper/cookie/index.ts +1 -1
- package/plugins/acryl-router/src/core/helper/factory/index.test.ts +9 -9
- package/plugins/acryl-router/src/core/helper/factory/index.ts +10 -10
- package/plugins/acryl-router/src/core/helper/html/index.test.ts +4 -4
- package/plugins/acryl-router/src/core/helper/html/index.ts +1 -1
- package/plugins/acryl-router/src/core/helper/route/index.test.ts +7 -7
- package/plugins/acryl-router/src/core/helper/route/index.ts +8 -8
- package/plugins/acryl-router/src/core/helper/streaming/index.ts +1 -1
- package/plugins/acryl-router/src/core/helper/testing/index.test.ts +6 -6
- package/plugins/acryl-router/src/core/helper/testing/index.ts +4 -4
- package/plugins/acryl-router/src/core/helper/websocket/index.ts +2 -1
- package/plugins/acryl-router/src/core/http-exception.ts +1 -1
- package/plugins/acryl-router/src/core/index.ts +9 -9
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +6 -6
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.ts +3 -3
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +5 -5
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.ts +3 -3
- package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +6 -6
- package/plugins/acryl-router/src/core/middleware/body-limit/index.ts +3 -3
- package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +9 -9
- package/plugins/acryl-router/src/core/middleware/combine/index.ts +7 -7
- package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +9 -9
- package/plugins/acryl-router/src/core/middleware/compress/index.ts +3 -3
- package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +5 -5
- package/plugins/acryl-router/src/core/middleware/context-storage/index.ts +4 -4
- package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +7 -7
- package/plugins/acryl-router/src/core/middleware/cors/index.ts +3 -3
- package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +10 -10
- package/plugins/acryl-router/src/core/middleware/csrf/index.ts +3 -3
- package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +42 -42
- package/plugins/acryl-router/src/core/middleware/etag/index.ts +4 -4
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +4 -4
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.ts +6 -6
- package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +16 -16
- package/plugins/acryl-router/src/core/middleware/jwk/jwk.ts +7 -6
- package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +16 -16
- package/plugins/acryl-router/src/core/middleware/jwt/jwt.ts +4 -3
- package/plugins/acryl-router/src/core/middleware/language/index.test.ts +3 -3
- package/plugins/acryl-router/src/core/middleware/language/language.ts +3 -3
- package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +5 -5
- package/plugins/acryl-router/src/core/middleware/logger/index.ts +4 -4
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +24 -24
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.ts +8 -8
- package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +5 -5
- package/plugins/acryl-router/src/core/middleware/method-override/index.ts +6 -6
- package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +4 -4
- package/plugins/acryl-router/src/core/middleware/powered-by/index.ts +6 -6
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +30 -30
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.ts +4 -4
- package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +15 -15
- package/plugins/acryl-router/src/core/middleware/request-id/request-id.ts +3 -3
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +35 -35
- package/plugins/acryl-router/src/core/middleware/secure-headers/secure-headers.ts +3 -3
- package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +23 -23
- package/plugins/acryl-router/src/core/middleware/serve-static/index.ts +1 -1
- package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +2 -2
- package/plugins/acryl-router/src/core/middleware/timeout/index.ts +3 -3
- package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +14 -14
- package/plugins/acryl-router/src/core/middleware/timing/timing.ts +3 -3
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +7 -7
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.ts +7 -7
- package/plugins/acryl-router/src/core/request.test.ts +42 -42
- package/plugins/acryl-router/src/core/request.ts +6 -6
- package/plugins/acryl-router/src/core/router/linear-router/index.ts +1 -1
- package/plugins/acryl-router/src/core/router/pattern-router/index.ts +1 -1
- package/plugins/acryl-router/src/core/router/reg-exp-router/index.ts +1 -1
- package/plugins/acryl-router/src/core/router/smart-router/index.ts +1 -1
- package/plugins/acryl-router/src/core/router/trie-router/index.ts +1 -1
- package/plugins/acryl-router/src/core/types.test.ts +115 -115
- package/plugins/acryl-router/src/core/types.ts +72 -72
- package/plugins/acryl-router/src/core/utils/body.ts +8 -8
- package/plugins/acryl-router/src/core/utils/html.ts +1 -1
- package/plugins/acryl-router/src/core/utils/jwt/jws.ts +3 -3
- package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +2 -2
- package/plugins/acryl-router/src/core/utils/jwt/jwt.ts +3 -3
- package/plugins/acryl-router/src/core/utils/jwt/types.ts +1 -1
- package/plugins/acryl-router/src/core/utils/types.ts +42 -0
- package/plugins/acryl-router/src/core/utils/url.test.ts +2 -2
- package/plugins/acryl-router/src/core/validator/index.ts +1 -1
- package/plugins/acryl-router/src/core/validator/validator.test.ts +28 -28
- package/plugins/acryl-router/src/deprecated.ts +15 -0
- package/plugins/acryl-router/src/index.ts +3 -2
- package/plugins/acryl-router/src/middleware/index.ts +1 -1
- package/plugins/acryl-router/src/middleware/loopback-only.ts +34 -1
- package/plugins/acryl-router/src/service.ts +19 -13
- package/plugins/acryl-router/tests/docs-examples.spec.ts +11 -11
- package/plugins/acryl-router/tests/live-install.spec.ts +2 -2
- package/plugins/acryl-router/tests/loader.spec.ts +3 -3
- package/plugins/acryl-router/tests/loopback-only.spec.ts +80 -0
- package/plugins/acryl-router/tests/no-dom/consumer.ts +11 -0
- package/plugins/acryl-router/tests/no-dom/tsconfig.json +13 -0
- package/plugins/acryl-router/tests/service.spec.ts +39 -22
- package/plugins/acryl-router/upstream.json +3 -3
- package/plugins/acryl-router-http/tests/adapter.spec.ts +3 -3
- package/plugins/acryl-router-http/tests/surfaces.spec.ts +2 -2
- package/plugins/acryl-router-http/upstream.json +2 -2
- package/plugins/acryl-state/src/service.ts +2 -2
- package/plugins/acryl-state/upstream.json +2 -2
- package/plugins/builder/script.ts +3 -3
- package/plugins/extensions/index.ts +4 -3
- package/plugins/extensions/resolve.ts +70 -10
- package/plugins/openai/prompt.ts +7 -7
- package/shared/AGENTS.core.md +61 -0
- package/templates/agent/AGENTS.md +6 -0
- package/templates/agent/README.md +2 -2
- package/templates/agent/blend.yaml +1 -1
- package/templates/agent/src/routes.ts +2 -2
- package/templates/agent/test/model.test.ts +2 -2
- package/templates/agent/tsconfig.json +40 -0
- package/templates/blank/AGENTS.md +5 -0
- package/templates/blank/README.md +1 -1
- package/templates/blank/src/routes.ts +2 -2
- package/templates/todo/AGENTS.md +5 -0
- package/templates/todo/src/todo.ts +6 -4
- package/templates/todo/upstream.json +2 -2
|
@@ -6,7 +6,7 @@ import { Context } from '@deepseek-ai/cordis'
|
|
|
6
6
|
import z from '@deepseek-ai/schemastery'
|
|
7
7
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
8
8
|
import { createMiddleware, schemaValidator, testClient } from '../src/helpers/index.ts'
|
|
9
|
-
import {
|
|
9
|
+
import { Acryl, HTTPException, apply, name } from '../src/index.ts'
|
|
10
10
|
import { methodNotAllowed } from '../src/middleware/index.ts'
|
|
11
11
|
|
|
12
12
|
const contexts: Context[] = []
|
|
@@ -30,7 +30,7 @@ describe('getting-started.md', () => {
|
|
|
30
30
|
name: 'todo',
|
|
31
31
|
inject: ['router'],
|
|
32
32
|
apply(inner) {
|
|
33
|
-
const app = new
|
|
33
|
+
const app = new Acryl()
|
|
34
34
|
.get('/', c => c.json({ ok: true }))
|
|
35
35
|
.get('/todos/:id', c => c.json({ id: c.req.param('id') }))
|
|
36
36
|
inner.router.route('/todo', app)
|
|
@@ -45,7 +45,7 @@ describe('getting-started.md', () => {
|
|
|
45
45
|
|
|
46
46
|
describe('routing.md', () => {
|
|
47
47
|
it('serves the routes an app had at the mount, not ones added later', async () => {
|
|
48
|
-
const app = new
|
|
48
|
+
const app = new Acryl().get('/a', c => c.text('a'))
|
|
49
49
|
const ctx = await withPlugin({ name: 'late', inject: ['router'], apply(inner) { inner.router.route('/x', app) } })
|
|
50
50
|
app.get('/b', c => c.text('b'))
|
|
51
51
|
expect((await ctx.router.request('/x/a')).status).toBe(200)
|
|
@@ -53,9 +53,9 @@ describe('routing.md', () => {
|
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
it('answers a wrong method with 404, or 405 and Allow with methodNotAllowed', async () => {
|
|
56
|
-
const plain = new
|
|
56
|
+
const plain = new Acryl().get('/item', c => c.text('item'))
|
|
57
57
|
expect((await plain.request('/item', { method: 'POST' })).status).toBe(404)
|
|
58
|
-
const strict = new
|
|
58
|
+
const strict = new Acryl()
|
|
59
59
|
strict.use(methodNotAllowed({ app: strict })).get('/item', c => c.text('item'))
|
|
60
60
|
const response = await strict.request('/item', { method: 'POST' })
|
|
61
61
|
expect(response.status).toBe(405)
|
|
@@ -69,7 +69,7 @@ describe('routing.md', () => {
|
|
|
69
69
|
})
|
|
70
70
|
|
|
71
71
|
it('uses a mounted app\'s onError for its routes', async () => {
|
|
72
|
-
const app = new
|
|
72
|
+
const app = new Acryl().get('/boom', () => { throw new Error('boom') })
|
|
73
73
|
app.onError((_error, c) => c.json({ error: 'internal_error' }, 500))
|
|
74
74
|
const ctx = await withPlugin({ name: 'errors', inject: ['router'], apply(inner) { inner.router.route('/', app) } })
|
|
75
75
|
expect(await (await ctx.router.request('/boom')).json()).toEqual({ error: 'internal_error' })
|
|
@@ -82,14 +82,14 @@ describe('context.md and middleware.md', () => {
|
|
|
82
82
|
c.set('user', { name: 'ada' })
|
|
83
83
|
await next()
|
|
84
84
|
})
|
|
85
|
-
const app = new
|
|
85
|
+
const app = new Acryl().use(withUser).get('/me', c => c.json(c.var.user))
|
|
86
86
|
expect(await (await app.request('/me')).json()).toEqual({ name: 'ada' })
|
|
87
87
|
})
|
|
88
88
|
|
|
89
89
|
it('never throws from next(): the error is already a response and is left in c.error', async () => {
|
|
90
90
|
let caught = false
|
|
91
91
|
let seen: unknown
|
|
92
|
-
const app = new
|
|
92
|
+
const app = new Acryl()
|
|
93
93
|
.use(async (c, next) => {
|
|
94
94
|
try {
|
|
95
95
|
await next()
|
|
@@ -105,7 +105,7 @@ describe('context.md and middleware.md', () => {
|
|
|
105
105
|
})
|
|
106
106
|
|
|
107
107
|
it('turns an HTTPException into its own response', async () => {
|
|
108
|
-
const app = new
|
|
108
|
+
const app = new Acryl().get('/', () => { throw new HTTPException(403, { message: 'not yours' }) })
|
|
109
109
|
const response = await app.request('/')
|
|
110
110
|
expect(response.status).toBe(403)
|
|
111
111
|
expect(await response.text()).toBe('not yours')
|
|
@@ -117,7 +117,7 @@ describe('validation.md', () => {
|
|
|
117
117
|
title: z.string().required(),
|
|
118
118
|
done: z.boolean().default(false),
|
|
119
119
|
})
|
|
120
|
-
const app = new
|
|
120
|
+
const app = new Acryl().post('/todos', schemaValidator('json', NewTodo), c => c.json({ todo: c.req.valid('json') }, 201))
|
|
121
121
|
const post = (body: string, type?: string): Promise<Response> =>
|
|
122
122
|
Promise.resolve(app.request('/todos', { method: 'POST', body, headers: type === undefined ? {} : { 'content-type': type } }))
|
|
123
123
|
|
|
@@ -144,7 +144,7 @@ describe('validation.md', () => {
|
|
|
144
144
|
|
|
145
145
|
describe('helpers.md', () => {
|
|
146
146
|
it('calls a chained app through the typed test client', async () => {
|
|
147
|
-
const app = new
|
|
147
|
+
const app = new Acryl().get('/search', c => c.json({ query: c.req.query('q') }))
|
|
148
148
|
const client = testClient(app)
|
|
149
149
|
const response = await client.search.$get({ query: { q: 'cordis' } })
|
|
150
150
|
expect(await response.json()).toEqual({ query: 'cordis' })
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Context, Service } from '@deepseek-ai/cordis'
|
|
9
9
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
10
|
-
import {
|
|
10
|
+
import { Acryl, apply, name } from '../src/index.ts'
|
|
11
11
|
|
|
12
12
|
const contexts: Context[] = []
|
|
13
13
|
const settle = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0))
|
|
@@ -46,7 +46,7 @@ const extension = (): object => ({
|
|
|
46
46
|
name: 'live-ext',
|
|
47
47
|
inject: ['router'],
|
|
48
48
|
apply(ctx: Context): void {
|
|
49
|
-
ctx.router.route('/', new
|
|
49
|
+
ctx.router.route('/', new Acryl().get('/live', c => c.json({ ok: true })))
|
|
50
50
|
},
|
|
51
51
|
})
|
|
52
52
|
|
|
@@ -11,7 +11,7 @@ import { join } from 'node:path'
|
|
|
11
11
|
import { Context, type FiberState } from '@deepseek-ai/cordis'
|
|
12
12
|
import { Loader } from '@deepseek-ai/cordis-plugin-loader'
|
|
13
13
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
14
|
-
import {
|
|
14
|
+
import { Acryl } from '../src/index.ts'
|
|
15
15
|
|
|
16
16
|
const ACTIVE = 2 as FiberState.ACTIVE
|
|
17
17
|
const DISPOSED = 4 as FiberState.DISPOSED
|
|
@@ -46,7 +46,7 @@ describe('acryl-router through a real Loader', () => {
|
|
|
46
46
|
it('activates from the built entry and serves a request in process', async () => {
|
|
47
47
|
const { ctx, entryId } = await mount()
|
|
48
48
|
expect(entryOf(ctx, entryId).fiber?.state).toBe(ACTIVE)
|
|
49
|
-
ctx.router.route('/', new
|
|
49
|
+
ctx.router.route('/', new Acryl().get('/ping', c => c.json({ ok: true })))
|
|
50
50
|
const response = await ctx.router.fetch(new Request('http://acryl.test/ping'))
|
|
51
51
|
expect(await response.json()).toEqual({ ok: true })
|
|
52
52
|
})
|
|
@@ -66,7 +66,7 @@ describe('acryl-router through a real Loader', () => {
|
|
|
66
66
|
expect(ctx.router).not.toBe(first)
|
|
67
67
|
expect(ctx.router.size).toBe(0)
|
|
68
68
|
expect((await first.fetch(new Request('http://acryl.test/x'))).status).toBe(503)
|
|
69
|
-
ctx.router.route('/', new
|
|
69
|
+
ctx.router.route('/', new Acryl().get('/again', c => c.text('ok')))
|
|
70
70
|
expect(await (await ctx.router.fetch(new Request('http://acryl.test/again'))).text()).toBe('ok')
|
|
71
71
|
})
|
|
72
72
|
})
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `loopbackOnly`, the guard of a local app's own routes: the peer and port come from the transport, the `Host` names this app, and the
|
|
3
|
+
* `callers` rule decides whether any local program or only the app's own page may call.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from 'vitest'
|
|
7
|
+
import { Acryl, type ConnectionInfo } from '../src/index.ts'
|
|
8
|
+
import { loopbackOnly, type LoopbackOptions } from '../src/middleware/index.ts'
|
|
9
|
+
|
|
10
|
+
const PORT = 43_120
|
|
11
|
+
const SELF = `http://127.0.0.1:${String(PORT)}`
|
|
12
|
+
const LOCAL: ConnectionInfo = { remoteAddress: '127.0.0.1', localPort: PORT }
|
|
13
|
+
|
|
14
|
+
function guarded(options: LoopbackOptions = {}): Acryl {
|
|
15
|
+
return new Acryl().use(loopbackOnly(options)).get('/x', c => c.text('read')).post('/x', c => c.text('changed'))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function status(
|
|
19
|
+
app: Acryl,
|
|
20
|
+
method: string,
|
|
21
|
+
headers: Record<string, string>,
|
|
22
|
+
connection: ConnectionInfo = LOCAL,
|
|
23
|
+
): Promise<number> {
|
|
24
|
+
const response = await app.fetch(new Request(`${SELF}/x`, { method, headers: { host: `127.0.0.1:${String(PORT)}`, ...headers } }), { connection })
|
|
25
|
+
return response.status
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('loopbackOnly, callers: local (the default)', () => {
|
|
29
|
+
const app = guarded()
|
|
30
|
+
|
|
31
|
+
it('lets a local program read and change, and a page of this app change', async () => {
|
|
32
|
+
expect(await status(app, 'GET', {})).toBe(200)
|
|
33
|
+
expect(await status(app, 'POST', {})).toBe(200)
|
|
34
|
+
expect(await status(app, 'POST', { origin: SELF })).toBe(200)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('refuses another origin\'s change, a foreign peer, a wrong port, a foreign Host and an in-process call', async () => {
|
|
38
|
+
expect(await status(app, 'POST', { origin: 'http://evil.test' })).toBe(403)
|
|
39
|
+
expect(await status(app, 'GET', {}, { remoteAddress: '192.168.1.5', localPort: PORT })).toBe(403)
|
|
40
|
+
expect(await status(app, 'GET', {}, { remoteAddress: '127.0.0.1', localPort: PORT + 1 })).toBe(403)
|
|
41
|
+
expect(await status(app, 'GET', { host: 'evil.test' })).toBe(403)
|
|
42
|
+
expect(await status(app, 'GET', {}, {})).toBe(403)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('lets an in-process call through only when the host says so', async () => {
|
|
46
|
+
expect(await status(guarded({ inProcess: 'allow' }), 'GET', {}, {})).toBe(200)
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('loopbackOnly, callers: own-page', () => {
|
|
51
|
+
const app = guarded({ callers: 'own-page', hostnames: ['127.0.0.1'] })
|
|
52
|
+
|
|
53
|
+
it('lets this app\'s page read and change with its exact Origin', async () => {
|
|
54
|
+
expect(await status(app, 'GET', { origin: SELF, 'sec-fetch-site': 'same-origin' })).toBe(200)
|
|
55
|
+
expect(await status(app, 'POST', { origin: SELF, 'sec-fetch-site': 'same-origin' })).toBe(200)
|
|
56
|
+
expect(await status(app, 'POST', { origin: SELF })).toBe(200)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('lets a same-origin read without Origin through by its Referer, as browsers send it', async () => {
|
|
60
|
+
expect(await status(app, 'GET', { 'sec-fetch-site': 'same-origin', referer: `${SELF}/page?tab=1` })).toBe(200)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('refuses a local program (no Origin), even for a read', async () => {
|
|
64
|
+
expect(await status(app, 'GET', {})).toBe(403)
|
|
65
|
+
expect(await status(app, 'POST', {})).toBe(403)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('refuses a change that only has the read evidence, and Origin forms that are not exact', async () => {
|
|
69
|
+
expect(await status(app, 'POST', { 'sec-fetch-site': 'same-origin', referer: `${SELF}/` })).toBe(403)
|
|
70
|
+
expect(await status(app, 'POST', { origin: `${SELF}/` })).toBe(403)
|
|
71
|
+
expect(await status(app, 'POST', { origin: 'null' })).toBe(403)
|
|
72
|
+
expect(await status(app, 'POST', { origin: SELF, 'sec-fetch-site': 'same-site' })).toBe(403)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('refuses another page, a Referer from elsewhere and a Host this app does not answer to', async () => {
|
|
76
|
+
expect(await status(app, 'GET', { origin: 'http://evil.test', 'sec-fetch-site': 'cross-site' })).toBe(403)
|
|
77
|
+
expect(await status(app, 'GET', { 'sec-fetch-site': 'same-origin', referer: 'http://evil.test/' })).toBe(403)
|
|
78
|
+
expect(await status(app, 'GET', { host: `localhost:${String(PORT)}`, origin: `http://localhost:${String(PORT)}` })).toBe(403)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Host-only consumer: Node types and no DOM library. Typechecking this against the built `lib/types` proves every public entry point's
|
|
3
|
+
* declarations stand without the DOM library, which a server plugin should not need to load.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type * as Router from '../../src/index.ts'
|
|
7
|
+
import type * as Client from '../../src/client.ts'
|
|
8
|
+
import type * as Helpers from '../../src/helpers/index.ts'
|
|
9
|
+
import type * as Middleware from '../../src/middleware/index.ts'
|
|
10
|
+
|
|
11
|
+
export type Entries = [typeof Router, typeof Client, typeof Helpers, typeof Middleware]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2024",
|
|
4
|
+
"lib": ["ES2024"],
|
|
5
|
+
"module": "NodeNext",
|
|
6
|
+
"moduleResolution": "NodeNext",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"exactOptionalPropertyTypes": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"types": ["node"]
|
|
11
|
+
},
|
|
12
|
+
"files": ["consumer.ts"]
|
|
13
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { Context, type FiberState } from '@deepseek-ai/cordis'
|
|
8
8
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
9
9
|
import { streamSSE } from '../src/helpers/index.ts'
|
|
10
|
-
import { apply,
|
|
10
|
+
import { apply, Acryl, Config, connectionOf, HTTPException, name, type Router } from '../src/index.ts'
|
|
11
11
|
|
|
12
12
|
const PENDING = 0 as FiberState.PENDING
|
|
13
13
|
const ACTIVE = 2 as FiberState.ACTIVE
|
|
@@ -42,7 +42,7 @@ function consumer(path: string, label: string) {
|
|
|
42
42
|
name: `consumer${label}`,
|
|
43
43
|
inject: ['router'],
|
|
44
44
|
apply(ctx: Context): void {
|
|
45
|
-
ctx.router.route('/', new
|
|
45
|
+
ctx.router.route('/', new Acryl().get(path, c => c.text(label)))
|
|
46
46
|
},
|
|
47
47
|
}
|
|
48
48
|
}
|
|
@@ -52,7 +52,7 @@ const get = (router: Router, path: string): Promise<Response> => router.fetch(ne
|
|
|
52
52
|
describe('acryl-router mounts', () => {
|
|
53
53
|
it('serves a mounted app under its path, with the app\'s own routes, middleware and error handler', async () => {
|
|
54
54
|
const { router } = await mountRouter()
|
|
55
|
-
const api = new
|
|
55
|
+
const api = new Acryl()
|
|
56
56
|
.use(async (c, next) => { await next(); c.header('x-api', 'yes') })
|
|
57
57
|
.get('/items/:id', c => c.json({ id: c.req.param('id') }))
|
|
58
58
|
.get('/boom', () => { throw new Error('boom') })
|
|
@@ -70,28 +70,28 @@ describe('acryl-router mounts', () => {
|
|
|
70
70
|
|
|
71
71
|
it('removes a mount with its remover, idempotently, and keeps the others', async () => {
|
|
72
72
|
const { router } = await mountRouter()
|
|
73
|
-
const removeA = router.route('/', new
|
|
74
|
-
router.route('/', new
|
|
73
|
+
const removeA = router.route('/', new Acryl().get('/a', c => c.text('a')))
|
|
74
|
+
router.route('/', new Acryl().get('/b', c => c.text('b')))
|
|
75
75
|
expect((await get(router, '/a')).status).toBe(200)
|
|
76
76
|
removeA()
|
|
77
77
|
removeA()
|
|
78
78
|
expect((await get(router, '/a')).status).toBe(404)
|
|
79
79
|
expect(await (await get(router, '/b')).text()).toBe('b')
|
|
80
|
-
router.route('/', new
|
|
80
|
+
router.route('/', new Acryl().get('/a', c => c.text('a2')))
|
|
81
81
|
expect(await (await get(router, '/a')).text()).toBe('a2')
|
|
82
82
|
})
|
|
83
83
|
|
|
84
84
|
it('runs mounts in the order they were made, so an earlier mount\'s middleware wraps a later mount\'s routes', async () => {
|
|
85
85
|
const { router } = await mountRouter()
|
|
86
|
-
router.route('/', new
|
|
87
|
-
router.route('/', new
|
|
86
|
+
router.route('/', new Acryl().use('/guarded/*', async c => c.text('denied', 401)))
|
|
87
|
+
router.route('/', new Acryl().get('/guarded/x', c => c.text('secret')).get('/open', c => c.text('open')))
|
|
88
88
|
expect((await get(router, '/guarded/x')).status).toBe(401)
|
|
89
89
|
expect(await (await get(router, '/open')).text()).toBe('open')
|
|
90
90
|
})
|
|
91
91
|
|
|
92
92
|
it('takes one router-wide error handler and one not-found handler, owned by the caller', async () => {
|
|
93
93
|
const { router } = await mountRouter()
|
|
94
|
-
router.route('/', new
|
|
94
|
+
router.route('/', new Acryl().get('/boom', () => { throw new Error('boom') }).get('/teapot', () => { throw new HTTPException(418, { message: 'short' }) }))
|
|
95
95
|
expect((await get(router, '/boom')).status).toBe(500)
|
|
96
96
|
expect((await get(router, '/teapot')).status).toBe(418)
|
|
97
97
|
|
|
@@ -109,13 +109,13 @@ describe('acryl-router mounts', () => {
|
|
|
109
109
|
const ctx = root()
|
|
110
110
|
ctx.plugin(provider, { trailingSlash: 'ignore', disposeGraceMs: 2000 })
|
|
111
111
|
await settle()
|
|
112
|
-
ctx.router.route('/', new
|
|
112
|
+
ctx.router.route('/', new Acryl().get('/a', c => c.text('a')))
|
|
113
113
|
expect((await get(ctx.router, '/a/')).status).toBe(200)
|
|
114
114
|
})
|
|
115
115
|
|
|
116
116
|
it('hands the transport\'s connection info to the handler and gives an in-process call an empty one', async () => {
|
|
117
117
|
const { router } = await mountRouter()
|
|
118
|
-
router.route('/', new
|
|
118
|
+
router.route('/', new Acryl().get('/who', c => c.json({ remote: connectionOf(c).remoteAddress ?? null, port: connectionOf(c).localPort ?? null })))
|
|
119
119
|
const over = await router.fetch(new Request('http://acryl.test/who'), { remoteAddress: '127.0.0.1', localPort: 4000 })
|
|
120
120
|
expect(await over.json()).toEqual({ remote: '127.0.0.1', port: 4000 })
|
|
121
121
|
const inProcess = await router.request('/who')
|
|
@@ -152,6 +152,23 @@ describe('acryl-router lifecycle through Cordis', () => {
|
|
|
152
152
|
expect(router.size).toBe(1)
|
|
153
153
|
})
|
|
154
154
|
|
|
155
|
+
it('labels a mount by its path with one child per route, so no label grows with the app (an inspector caps label length)', async () => {
|
|
156
|
+
const { ctx, router } = await mountRouter()
|
|
157
|
+
const big = new Acryl().use('/api/*', async (_c, next) => { await next() })
|
|
158
|
+
for (let i = 0; i < 60; i += 1) big.get(`/api/a-fairly-long-resource-name-${String(i)}`, c => c.text('ok'))
|
|
159
|
+
const fiber = ctx.plugin({ name: 'big', inject: ['router'], apply: (inner: Context) => { inner.router.route('/', big) } })
|
|
160
|
+
await settle()
|
|
161
|
+
const [mount, ...rest] = fiber.getEffects()
|
|
162
|
+
expect(rest).toEqual([])
|
|
163
|
+
expect(mount?.label).toBe('acryl-router: route /')
|
|
164
|
+
expect(mount?.children).toHaveLength(61)
|
|
165
|
+
expect(mount?.children[0]?.label).toBe('acryl-router: serves ALL /api/*')
|
|
166
|
+
expect(mount?.children.every(child => child.label.length < 100 && child.children.length === 0)).toBe(true)
|
|
167
|
+
expect(router.size).toBe(1)
|
|
168
|
+
await fiber.dispose()
|
|
169
|
+
expect(router.size).toBe(0)
|
|
170
|
+
})
|
|
171
|
+
|
|
155
172
|
it('replaces the provider without leaving doubled registrations: consumers re-register exactly once', async () => {
|
|
156
173
|
const ctx = root()
|
|
157
174
|
const first = ctx.plugin(provider, config)
|
|
@@ -175,7 +192,7 @@ describe('acryl-router lifecycle through Cordis', () => {
|
|
|
175
192
|
|
|
176
193
|
it('serves the routes an app had when it was mounted, whatever else changes later', async () => {
|
|
177
194
|
const { ctx, router } = await mountRouter()
|
|
178
|
-
const app = new
|
|
195
|
+
const app = new Acryl().get('/early', c => c.text('early'))
|
|
179
196
|
router.route('/', app)
|
|
180
197
|
app.get('/late', c => c.text('late'))
|
|
181
198
|
expect((await get(router, '/early')).status).toBe(200)
|
|
@@ -206,7 +223,7 @@ describe('acryl-router lifecycle through Cordis', () => {
|
|
|
206
223
|
name: 'counting',
|
|
207
224
|
inject: ['router'],
|
|
208
225
|
apply(inner: Context): void {
|
|
209
|
-
inner.router.route('/', new
|
|
226
|
+
inner.router.route('/', new Acryl().use(async (_c, next) => { calls += 1; await next() }).get('/count', c => c.text(String(calls))))
|
|
210
227
|
},
|
|
211
228
|
}
|
|
212
229
|
for (let round = 0; round < 10; round += 1) {
|
|
@@ -244,7 +261,7 @@ describe('acryl-router lifecycle through Cordis', () => {
|
|
|
244
261
|
await settle()
|
|
245
262
|
const router = ctx.router
|
|
246
263
|
let aborted = false
|
|
247
|
-
router.route('/', new
|
|
264
|
+
router.route('/', new Acryl().get('/slow', async c => {
|
|
248
265
|
await new Promise<void>((_resolve, reject) => { c.req.raw.signal.addEventListener('abort', () => { aborted = true; reject(new Error('aborted')) }, { once: true }) })
|
|
249
266
|
return c.text('late')
|
|
250
267
|
}))
|
|
@@ -261,7 +278,7 @@ describe('acryl-router lifecycle through Cordis', () => {
|
|
|
261
278
|
it('forwards the caller\'s own abort to the handler signal', async () => {
|
|
262
279
|
const { router } = await mountRouter()
|
|
263
280
|
let seen = false
|
|
264
|
-
router.route('/', new
|
|
281
|
+
router.route('/', new Acryl().get('/slow', async c => {
|
|
265
282
|
await new Promise<void>((_resolve, reject) => { c.req.raw.signal.addEventListener('abort', () => { seen = true; reject(new Error('aborted')) }, { once: true }) })
|
|
266
283
|
return c.text('x')
|
|
267
284
|
}))
|
|
@@ -278,7 +295,7 @@ describe('acryl-router lifecycle through Cordis', () => {
|
|
|
278
295
|
const fiber = ctx.plugin(provider, config)
|
|
279
296
|
await settle()
|
|
280
297
|
let cleaned = false
|
|
281
|
-
ctx.router.route('/', new
|
|
298
|
+
ctx.router.route('/', new Acryl().get('/events', c => streamSSE(c, async stream => {
|
|
282
299
|
stream.onAbort(() => { cleaned = true })
|
|
283
300
|
await stream.writeSSE({ data: 'first' })
|
|
284
301
|
while (!stream.aborted) await stream.sleep(10)
|
|
@@ -302,7 +319,7 @@ describe('acryl-router lifecycle through Cordis', () => {
|
|
|
302
319
|
name: 'streaming',
|
|
303
320
|
inject: ['router'],
|
|
304
321
|
apply(inner: Context): void {
|
|
305
|
-
inner.router.route('/', new
|
|
322
|
+
inner.router.route('/', new Acryl().get('/events', c => streamSSE(c, async stream => {
|
|
306
323
|
stream.onAbort(() => { cleaned = true })
|
|
307
324
|
await stream.writeSSE({ data: 'first' })
|
|
308
325
|
while (!stream.aborted) await stream.sleep(10)
|
|
@@ -313,7 +330,7 @@ describe('acryl-router lifecycle through Cordis', () => {
|
|
|
313
330
|
name: 'other',
|
|
314
331
|
inject: ['router'],
|
|
315
332
|
apply(inner: Context): void {
|
|
316
|
-
inner.router.route('/', new
|
|
333
|
+
inner.router.route('/', new Acryl().get('/slow', async c => {
|
|
317
334
|
c.req.raw.signal.addEventListener('abort', () => { otherAborted = true }, { once: true })
|
|
318
335
|
await new Promise<void>(resolve => { release = resolve })
|
|
319
336
|
return c.text('done')
|
|
@@ -344,7 +361,7 @@ describe('acryl-router lifecycle through Cordis', () => {
|
|
|
344
361
|
name: 'guard',
|
|
345
362
|
inject: ['router'],
|
|
346
363
|
apply(inner: Context): void {
|
|
347
|
-
inner.router.route('/', new
|
|
364
|
+
inner.router.route('/', new Acryl()
|
|
348
365
|
.use('/held/*', async (c, next) => {
|
|
349
366
|
c.req.raw.signal.addEventListener('abort', () => { guardAborted = true }, { once: true })
|
|
350
367
|
await new Promise<void>((_resolve, reject) => { c.req.raw.signal.addEventListener('abort', () => { reject(new Error('aborted')) }, { once: true }) })
|
|
@@ -357,7 +374,7 @@ describe('acryl-router lifecycle through Cordis', () => {
|
|
|
357
374
|
name: 'handler',
|
|
358
375
|
inject: ['router'],
|
|
359
376
|
apply(inner: Context): void {
|
|
360
|
-
inner.router.route('/', new
|
|
377
|
+
inner.router.route('/', new Acryl()
|
|
361
378
|
.get('/held/x', c => c.text('held'))
|
|
362
379
|
.get('/passed/x', c => streamSSE(c, async stream => {
|
|
363
380
|
stream.onAbort(() => { handlerAborted = true })
|
|
@@ -385,7 +402,7 @@ describe('acryl-router lifecycle through Cordis', () => {
|
|
|
385
402
|
const fiber = ctx.plugin(provider, config)
|
|
386
403
|
await settle()
|
|
387
404
|
let finished = false
|
|
388
|
-
ctx.router.route('/', new
|
|
405
|
+
ctx.router.route('/', new Acryl().get('/later', c => {
|
|
389
406
|
c.executionCtx.waitUntil(new Promise<void>(resolve => { setTimeout(() => { finished = true; resolve() }, 30) }))
|
|
390
407
|
return c.text('now')
|
|
391
408
|
}))
|
|
@@ -399,7 +416,7 @@ describe('acryl-router lifecycle through Cordis', () => {
|
|
|
399
416
|
const ctx = root()
|
|
400
417
|
const fiber = ctx.plugin(provider, { trailingSlash: 'strict', disposeGraceMs: 30 })
|
|
401
418
|
await settle()
|
|
402
|
-
ctx.router.route('/', new
|
|
419
|
+
ctx.router.route('/', new Acryl().get('/stuck', c => { c.executionCtx.waitUntil(new Promise(() => undefined)); return c.text('x') }))
|
|
403
420
|
await get(ctx.router, '/stuck')
|
|
404
421
|
const started = Date.now()
|
|
405
422
|
await fiber.dispose()
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
"source": {
|
|
3
3
|
"repo": "acryldev/acryl",
|
|
4
4
|
"path": "plugins/acryl-router",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "f5151e7f0c4de3b81bddb5cc686b5ef329e48e1c"
|
|
6
6
|
},
|
|
7
7
|
"vendored": [
|
|
8
8
|
"src",
|
|
9
9
|
"tests",
|
|
10
10
|
"LICENSE"
|
|
11
11
|
],
|
|
12
|
-
"rewrittenSpecifiers":
|
|
13
|
-
"digest": "sha256:
|
|
12
|
+
"rewrittenSpecifiers": 4,
|
|
13
|
+
"digest": "sha256:543a5ca99518eea1fe2e319b7708d62af34a6c24ab3daeb17dde48d67d319d6a",
|
|
14
14
|
"testsNotRun": {
|
|
15
15
|
"tests/loader.spec.ts": "loads the built lib/ entry through the real Loader; the library ships source and has no build"
|
|
16
16
|
},
|
|
@@ -8,7 +8,7 @@ import { Context, type FiberState } from '@deepseek-ai/cordis'
|
|
|
8
8
|
import WebServer from '@deepseek-ai/dsh-host-webserver'
|
|
9
9
|
import { apply as routerApply, name as routerName } from '../../acryl-router/src/index.ts'
|
|
10
10
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
11
|
-
import {
|
|
11
|
+
import { Acryl, connectionOf, type RouterHttpService } from '../../acryl-router/src/index.ts'
|
|
12
12
|
import { methodNotAllowed } from '../../acryl-router/src/middleware/index.ts'
|
|
13
13
|
import { apply, inject, name } from '../src/index.ts'
|
|
14
14
|
|
|
@@ -40,13 +40,13 @@ async function stack(): Promise<{ ctx: Context, base: () => string }> {
|
|
|
40
40
|
return { ctx, base: () => `http://127.0.0.1:${String((ctx as unknown as { webServer: { port: number } }).webServer.port)}` }
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
function demo(prefix: string, extra?: (app:
|
|
43
|
+
function demo(prefix: string, extra?: (app: Acryl) => void) {
|
|
44
44
|
return {
|
|
45
45
|
name: `demo${prefix.replaceAll('/', '-')}`,
|
|
46
46
|
inject: ['router', 'routerHttp'],
|
|
47
47
|
apply(ctx: Context): void {
|
|
48
48
|
ctx.routerHttp.mount(prefix)
|
|
49
|
-
const app = new
|
|
49
|
+
const app = new Acryl()
|
|
50
50
|
app.use(methodNotAllowed({ app }))
|
|
51
51
|
app.get('/hello/:who', c => c.json({ hello: c.req.param('who'), q: c.req.query('q') ?? null, host: c.req.header('host') !== undefined }))
|
|
52
52
|
app.post('/echo', async c => c.json({ got: await c.req.text(), type: c.req.header('content-type') ?? null }, 201))
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { Context } from '@deepseek-ai/cordis'
|
|
9
9
|
import WebServer from '@deepseek-ai/dsh-host-webserver'
|
|
10
|
-
import {
|
|
10
|
+
import { Acryl, apply as routerApply, name as routerName } from '../../acryl-router/src/index.ts'
|
|
11
11
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
12
12
|
import { apply as httpApply, inject as httpInject, name as httpName } from '../src/index.ts'
|
|
13
13
|
|
|
@@ -28,7 +28,7 @@ const notes = {
|
|
|
28
28
|
inject: ['router'],
|
|
29
29
|
apply(ctx: Context): void {
|
|
30
30
|
const store = new Map<string, string>()
|
|
31
|
-
const app = new
|
|
31
|
+
const app = new Acryl()
|
|
32
32
|
.post('/:id', async c => {
|
|
33
33
|
const text = await c.req.text()
|
|
34
34
|
store.set(c.req.param('id'), text)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"source": {
|
|
3
3
|
"repo": "acryldev/acryl",
|
|
4
4
|
"path": "plugins/acryl-router-http",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "f5151e7f0c4de3b81bddb5cc686b5ef329e48e1c"
|
|
6
6
|
},
|
|
7
7
|
"vendored": [
|
|
8
8
|
"src",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"LICENSE"
|
|
11
11
|
],
|
|
12
12
|
"rewrittenSpecifiers": 7,
|
|
13
|
-
"digest": "sha256:
|
|
13
|
+
"digest": "sha256:394994528a104c8f788e2d911f8f940ff9f74e9c5a72835ea3ea751651aa9fbd",
|
|
14
14
|
"testsNotRun": {
|
|
15
15
|
"tests/adapter.spec.ts": "needs @deepseek-ai/dsh-host-webserver (the DSH web server); acryl-node-server has its own tests",
|
|
16
16
|
"tests/stream.spec.ts": "needs @deepseek-ai/dsh-host-webserver (the DSH web server)",
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Context, Service } from '@deepseek-ai/cordis'
|
|
9
|
-
import {
|
|
9
|
+
import { Acryl, type Context as RequestContext } from '../../acryl-router/src/index.ts'
|
|
10
10
|
import { streamSSE } from '../../acryl-router/src/helpers/index.ts'
|
|
11
11
|
import { loopbackOnly, methodNotAllowed } from '../../acryl-router/src/middleware/index.ts'
|
|
12
12
|
import { createCell, createDerived, type Cell, type ReadonlyCell } from './cell.ts'
|
|
@@ -104,7 +104,7 @@ export function apply(ctx: Context): void {
|
|
|
104
104
|
// The same cells on the router, read-only, for any surface; present only where a router is. A request over a socket must come from this machine to this
|
|
105
105
|
// app; the terminal calls the router in process and is the host's own code.
|
|
106
106
|
ctx.inject(['router'], inner => {
|
|
107
|
-
const api = new
|
|
107
|
+
const api = new Acryl()
|
|
108
108
|
api.use(loopbackOnly({ inProcess: 'allow' }), methodNotAllowed({ app: api }))
|
|
109
109
|
.get('/', c => c.json({ keys: state.keys() }))
|
|
110
110
|
.get('/:key', c => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"source": {
|
|
3
3
|
"repo": "acryldev/acryl",
|
|
4
4
|
"path": "plugins/acryl-state",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "f5151e7f0c4de3b81bddb5cc686b5ef329e48e1c"
|
|
6
6
|
},
|
|
7
7
|
"vendored": [
|
|
8
8
|
"src",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"NOTICE"
|
|
12
12
|
],
|
|
13
13
|
"rewrittenSpecifiers": 4,
|
|
14
|
-
"digest": "sha256:
|
|
14
|
+
"digest": "sha256:18df69d338a1928fc0f7497c14c245afb98f0c39d4fe6a24bda62bb845d96030",
|
|
15
15
|
"testsNotRun": {
|
|
16
16
|
"tests/react.spec.ts": "needs react and @testing-library/react, which generated apps do not install"
|
|
17
17
|
},
|
|
@@ -18,7 +18,7 @@ const PATH = /^\/[a-z0-9/-]*$/
|
|
|
18
18
|
|
|
19
19
|
export function routeSource(name: string, path: string, text: string): string {
|
|
20
20
|
return `import type { Context } from '@deepseek-ai/cordis'
|
|
21
|
-
import {
|
|
21
|
+
import { Acryl } from 'acrylblends'
|
|
22
22
|
|
|
23
23
|
/** Written by the builder: answers GET ${path}, and checks that it does before the install counts. */
|
|
24
24
|
export const name = '${name}'
|
|
@@ -26,11 +26,11 @@ export const inject = ['router']
|
|
|
26
26
|
|
|
27
27
|
export function apply(ctx: Context) {
|
|
28
28
|
// The app is mounted through this plugin's context, so the route is removed when the plugin is unloaded.
|
|
29
|
-
ctx.router.route('/', new
|
|
29
|
+
ctx.router.route('/', new Acryl().get(${JSON.stringify(path)}, (c) => c.json({ text: ${JSON.stringify(text)} })))
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export async function check(ctx: Context) {
|
|
33
|
-
const reply = await ctx.
|
|
33
|
+
const reply = await ctx.router.request(${JSON.stringify(path)})
|
|
34
34
|
if (!reply.ok) throw new Error(\`GET ${path} answered \${reply.status}\`)
|
|
35
35
|
}
|
|
36
36
|
`
|
|
@@ -109,7 +109,7 @@ interface PluginModule {
|
|
|
109
109
|
readonly name?: unknown
|
|
110
110
|
readonly inject?: unknown
|
|
111
111
|
readonly apply: (...args: unknown[]) => unknown
|
|
112
|
-
/** Optional self-check, run after the plugin starts: it throws (or rejects) when the plugin does not work. */
|
|
112
|
+
/** Optional self-check, run after the plugin starts with the plugin's own context (so `ctx.router` reads as in `apply`): it throws (or rejects) when the plugin does not work. */
|
|
113
113
|
readonly check?: (ctx: Context) => unknown
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -507,6 +507,7 @@ export class Extensions extends Service {
|
|
|
507
507
|
/** Run the extension's own `check`, if it has one, with a time limit. Returns the failure, or `undefined` when it passes. */
|
|
508
508
|
private async verify(
|
|
509
509
|
module: PluginModule,
|
|
510
|
+
ctx: Context,
|
|
510
511
|
): Promise<{ stage: 'verify'; error: string } | undefined> {
|
|
511
512
|
if (typeof module.check !== 'function') return undefined
|
|
512
513
|
let timer: ReturnType<typeof setTimeout> | undefined
|
|
@@ -517,7 +518,7 @@ export class Extensions extends Service {
|
|
|
517
518
|
CHECK_TIMEOUT_MS,
|
|
518
519
|
)
|
|
519
520
|
})
|
|
520
|
-
await Promise.race([Promise.resolve(module.check(
|
|
521
|
+
await Promise.race([Promise.resolve(module.check(ctx)), limit])
|
|
521
522
|
return undefined
|
|
522
523
|
} catch (error) {
|
|
523
524
|
return { stage: 'verify', error: `its check failed: ${message(error)}` }
|
|
@@ -615,7 +616,7 @@ export class Extensions extends Service {
|
|
|
615
616
|
error: message(mounted.error ?? 'the plugin failed to start'),
|
|
616
617
|
}
|
|
617
618
|
: state === 'active'
|
|
618
|
-
? await this.verify(module)
|
|
619
|
+
? await this.verify(module, mounted.fiber.ctx)
|
|
619
620
|
: undefined
|
|
620
621
|
if (failure) {
|
|
621
622
|
await mounted.fiber.dispose().catch(() => undefined)
|