@lovrozagar/honey 0.7.2 → 1.0.0-rc.1
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 +21 -0
- package/README.md +36 -1969
- package/bin/honey.js +87 -27
- package/dist/adapters/bun/serve.d.ts +18 -0
- package/dist/adapters/bun/serve.js +89 -0
- package/dist/adapters/fetch-request.d.ts +20 -0
- package/dist/adapters/fetch-request.js +29 -0
- package/dist/adapters/node/headers.d.ts +27 -0
- package/dist/adapters/node/headers.js +86 -0
- package/dist/adapters/node/request.d.ts +16 -0
- package/dist/adapters/node/request.js +214 -0
- package/dist/adapters/node/response.d.ts +11 -0
- package/dist/adapters/node/response.js +126 -0
- package/dist/adapters/node/serve.d.ts +39 -0
- package/dist/adapters/node/serve.js +205 -0
- package/dist/adapters/node/url.d.ts +13 -0
- package/dist/adapters/node/url.js +87 -0
- package/dist/adapters/serve.d.ts +36 -0
- package/dist/adapters/serve.js +52 -0
- package/dist/build/artifacts.d.ts +34 -0
- package/dist/build/artifacts.js +123 -0
- package/dist/build/cli-args.d.ts +30 -0
- package/dist/build/cli-args.js +97 -0
- package/dist/build/cli.d.ts +11 -0
- package/dist/build/cli.js +150 -0
- package/dist/build/config.d.ts +102 -0
- package/dist/build/config.js +89 -0
- package/dist/build/generate.d.ts +19 -0
- package/dist/build/generate.js +80 -0
- package/dist/build/glob.d.ts +8 -0
- package/dist/build/glob.js +97 -0
- package/dist/build/index.d.ts +3 -58
- package/dist/build/index.js +3 -173
- package/dist/build/init.d.ts +3 -0
- package/dist/build/init.js +131 -0
- package/dist/build/loaders.d.ts +33 -0
- package/dist/build/loaders.js +105 -0
- package/dist/build/plugin.d.ts +52 -0
- package/dist/build/plugin.js +146 -0
- package/dist/build/source.d.ts +21 -0
- package/dist/build/source.js +100 -0
- package/dist/build/targets.d.ts +59 -0
- package/dist/build/targets.js +107 -0
- package/dist/context/context.d.ts +111 -0
- package/dist/context/context.js +213 -0
- package/dist/context/cookie-sign.d.ts +38 -0
- package/dist/context/cookie-sign.js +113 -0
- package/dist/context/cookie.d.ts +28 -0
- package/dist/context/cookie.js +142 -0
- package/dist/context/peer.d.ts +34 -0
- package/dist/context/peer.js +190 -0
- package/dist/errors/error.d.ts +60 -0
- package/dist/errors/error.js +84 -0
- package/dist/errors/errors.d.ts +45 -0
- package/dist/errors/errors.js +31 -0
- package/dist/errors/on-error.d.ts +10 -0
- package/dist/errors/on-error.js +204 -0
- package/dist/errors/problem-json.d.ts +14 -0
- package/dist/errors/problem-json.js +58 -0
- package/dist/errors/projector.d.ts +50 -0
- package/dist/errors/projector.js +107 -0
- package/dist/i18n/error-i18n.d.ts +26 -0
- package/dist/i18n/error-i18n.js +77 -0
- package/dist/i18n/i18n.d.ts +21 -0
- package/dist/i18n/i18n.js +40 -0
- package/dist/i18n/icu.d.ts +29 -0
- package/dist/i18n/icu.js +170 -0
- package/dist/i18n/index.d.ts +4 -0
- package/dist/i18n/index.js +3 -0
- package/dist/index.d.ts +30 -744
- package/dist/index.js +12 -2363
- package/dist/input/accepts.d.ts +5 -0
- package/dist/input/accepts.js +105 -0
- package/dist/input/binary.d.ts +20 -0
- package/dist/input/binary.js +40 -0
- package/dist/input/body-kind.d.ts +15 -0
- package/dist/input/body-kind.js +47 -0
- package/dist/input/body-limit.d.ts +21 -0
- package/dist/input/body-limit.js +99 -0
- package/dist/input/cookies.d.ts +7 -0
- package/dist/input/cookies.js +8 -0
- package/dist/input/form.d.ts +10 -0
- package/dist/input/form.js +24 -0
- package/dist/input/headers.d.ts +7 -0
- package/dist/input/headers.js +8 -0
- package/dist/input/input.d.ts +91 -0
- package/dist/input/input.js +88 -0
- package/dist/input/json.d.ts +10 -0
- package/dist/input/json.js +22 -0
- package/dist/input/params.d.ts +7 -0
- package/dist/input/params.js +8 -0
- package/dist/input/query.d.ts +10 -0
- package/dist/input/query.js +17 -0
- package/dist/mcp/mount.d.ts +116 -0
- package/dist/mcp/mount.js +45 -0
- package/dist/mcp/oauth.d.ts +14 -0
- package/dist/mcp/oauth.js +88 -0
- package/dist/mcp/uploads.d.ts +36 -0
- package/dist/mcp/uploads.js +171 -0
- package/dist/mcp/url-files.d.ts +32 -0
- package/dist/mcp/url-files.js +75 -0
- package/dist/meta/meta-spec-apply.d.ts +25 -0
- package/dist/meta/meta-spec-apply.js +83 -0
- package/dist/meta/meta-spec-merge.d.ts +103 -0
- package/dist/meta/meta-spec-merge.js +50 -0
- package/dist/meta/meta-spec-validate.d.ts +61 -0
- package/dist/meta/meta-spec-validate.js +166 -0
- package/dist/meta/meta-spec.d.ts +49 -0
- package/dist/meta/meta-spec.js +197 -0
- package/dist/middleware/compress.d.ts +15 -0
- package/dist/middleware/compress.js +93 -0
- package/dist/middleware/cors.d.ts +18 -0
- package/dist/middleware/cors.js +80 -0
- package/dist/middleware/csrf.d.ts +11 -0
- package/dist/middleware/csrf.js +65 -0
- package/dist/middleware/ip-restrict.d.ts +10 -0
- package/dist/middleware/ip-restrict.js +82 -0
- package/dist/middleware/middleware.d.ts +53 -0
- package/dist/middleware/middleware.js +112 -0
- package/dist/middleware/powered-by.d.ts +6 -0
- package/dist/middleware/powered-by.js +13 -0
- package/dist/middleware/rate-limit.d.ts +40 -0
- package/dist/middleware/rate-limit.js +90 -0
- package/dist/middleware/request-id.d.ts +25 -0
- package/dist/middleware/request-id.js +30 -0
- package/dist/middleware/secure-headers.d.ts +18 -0
- package/dist/middleware/secure-headers.js +36 -0
- package/dist/middleware/server-timing.d.ts +8 -0
- package/dist/middleware/server-timing.js +52 -0
- package/dist/middleware/timeout.d.ts +6 -0
- package/dist/middleware/timeout.js +29 -0
- package/dist/observability/curl-logger.d.ts +46 -0
- package/dist/observability/curl-logger.js +175 -0
- package/dist/observability/request-to-curl.d.ts +17 -0
- package/dist/observability/request-to-curl.js +47 -0
- package/dist/openapi/collect.d.ts +56 -18
- package/dist/openapi/collect.js +109 -23
- package/dist/openapi/components.d.ts +9 -0
- package/dist/openapi/components.js +90 -0
- package/dist/openapi/docs-page.d.ts +20 -18
- package/dist/openapi/docs-page.js +28 -46
- package/dist/openapi/document.d.ts +33 -26
- package/dist/openapi/document.js +21 -776
- package/dist/openapi/extract-schemas.d.ts +11 -0
- package/dist/openapi/extract-schemas.js +31 -0
- package/dist/openapi/generate.d.ts +47 -0
- package/dist/openapi/generate.js +180 -0
- package/dist/openapi/manifest.d.ts +41 -0
- package/dist/openapi/manifest.js +49 -0
- package/dist/openapi/output-keys.d.ts +6 -0
- package/dist/openapi/output-keys.js +24 -0
- package/dist/openapi/paths.d.ts +35 -0
- package/dist/openapi/paths.js +175 -0
- package/dist/openapi/register.d.ts +14 -3
- package/dist/openapi/register.js +13 -20
- package/dist/openapi/responses.d.ts +44 -0
- package/dist/openapi/responses.js +159 -0
- package/dist/openapi/scalar.d.ts +4 -14
- package/dist/openapi/scalar.js +5 -7
- package/dist/openapi/security.d.ts +4 -0
- package/dist/openapi/security.js +25 -0
- package/dist/openapi/serve.d.ts +25 -9
- package/dist/openapi/serve.js +108 -29
- package/dist/openapi/servers.d.ts +19 -0
- package/dist/openapi/servers.js +72 -0
- package/dist/openapi/swagger.d.ts +4 -14
- package/dist/openapi/swagger.js +5 -7
- package/dist/openapi/tags.d.ts +20 -0
- package/dist/openapi/tags.js +20 -0
- package/dist/openapi/webhooks.d.ts +19 -0
- package/dist/openapi/webhooks.js +70 -0
- package/dist/openapi/yaml.d.ts +8 -0
- package/dist/openapi/yaml.js +74 -0
- package/dist/proxy/headers.d.ts +30 -0
- package/dist/proxy/headers.js +86 -0
- package/dist/proxy/proxy.d.ts +39 -0
- package/dist/proxy/proxy.js +103 -0
- package/dist/proxy/upstream.d.ts +30 -0
- package/dist/proxy/upstream.js +116 -0
- package/dist/realtime/bus.d.ts +10 -13
- package/dist/realtime/bus.js +48 -93
- package/dist/realtime/invoke-user.d.ts +36 -0
- package/dist/realtime/invoke-user.js +103 -0
- package/dist/realtime/realtime.d.ts +7 -0
- package/dist/realtime/realtime.js +40 -0
- package/dist/realtime/route.d.ts +33 -27
- package/dist/realtime/route.js +27 -43
- package/dist/realtime/server.d.ts +24 -28
- package/dist/realtime/server.js +202 -225
- package/dist/response/binary.d.ts +18 -0
- package/dist/response/binary.js +10 -0
- package/dist/response/builder.d.ts +73 -0
- package/dist/response/builder.js +214 -0
- package/dist/response/cache-control.d.ts +30 -0
- package/dist/response/cache-control.js +74 -0
- package/dist/response/etag.d.ts +15 -0
- package/dist/response/etag.js +83 -0
- package/dist/response/media-type.d.ts +28 -0
- package/dist/response/media-type.js +97 -0
- package/dist/response/out-headers.d.ts +31 -0
- package/dist/response/out-headers.js +130 -0
- package/dist/response/pretty-json.d.ts +10 -0
- package/dist/response/pretty-json.js +44 -0
- package/dist/response/range.d.ts +19 -0
- package/dist/response/range.js +98 -0
- package/dist/response/response-headers.d.ts +11 -0
- package/dist/response/response-headers.js +21 -0
- package/dist/response/response.d.ts +69 -0
- package/dist/response/response.js +208 -0
- package/dist/response/sse.d.ts +41 -0
- package/dist/response/sse.js +73 -0
- package/dist/response/static.d.ts +20 -0
- package/dist/response/static.js +181 -0
- package/dist/response/status.d.ts +5 -0
- package/dist/response/status.js +68 -0
- package/dist/response/with-headers.d.ts +11 -0
- package/dist/response/with-headers.js +47 -0
- package/dist/routing/app-types.d.ts +181 -0
- package/dist/routing/app-types.js +1 -0
- package/dist/routing/app.d.ts +76 -0
- package/dist/routing/app.js +137 -0
- package/dist/routing/codegen-types.d.ts +17 -0
- package/dist/routing/codegen-types.js +1 -0
- package/dist/routing/delegate.d.ts +9 -0
- package/dist/routing/delegate.js +75 -0
- package/dist/routing/dispatch.d.ts +22 -0
- package/dist/routing/dispatch.js +255 -0
- package/dist/routing/finalize.d.ts +47 -0
- package/dist/routing/finalize.js +169 -0
- package/dist/routing/gateway-tree.d.ts +19 -0
- package/dist/routing/gateway-tree.js +74 -0
- package/dist/routing/invalidate-check.d.ts +58 -0
- package/dist/routing/invalidate-check.js +124 -0
- package/dist/routing/match.d.ts +22 -0
- package/dist/routing/match.js +108 -0
- package/dist/routing/middleware-chain.d.ts +16 -0
- package/dist/routing/middleware-chain.js +41 -0
- package/dist/routing/output-types.d.ts +91 -0
- package/dist/routing/output-types.js +1 -0
- package/dist/routing/pattern.d.ts +55 -0
- package/dist/routing/pattern.js +219 -0
- package/dist/routing/record.d.ts +18 -0
- package/dist/routing/record.js +4 -0
- package/dist/routing/register.d.ts +135 -0
- package/dist/routing/register.js +155 -0
- package/dist/routing/request-path.d.ts +30 -0
- package/dist/routing/request-path.js +173 -0
- package/dist/routing/route-builder.d.ts +160 -0
- package/dist/routing/route-builder.js +86 -0
- package/dist/routing/route-tree.d.ts +18 -0
- package/dist/routing/route-tree.js +91 -0
- package/dist/routing/settings-chain.d.ts +75 -0
- package/dist/routing/settings-chain.js +137 -0
- package/dist/routing/settings.d.ts +90 -0
- package/dist/routing/settings.js +44 -0
- package/dist/routing/tap.d.ts +31 -0
- package/dist/routing/tap.js +77 -0
- package/dist/routing/tree-merge.d.ts +7 -0
- package/dist/routing/tree-merge.js +77 -0
- package/dist/routing/tree.d.ts +85 -0
- package/dist/routing/tree.js +196 -0
- package/dist/routing/types.d.ts +20 -0
- package/dist/routing/types.js +1 -0
- package/dist/routing/ws-builder.d.ts +62 -0
- package/dist/routing/ws-builder.js +109 -0
- package/dist/runtime/detect-runtime.d.ts +16 -0
- package/dist/runtime/detect-runtime.js +11 -0
- package/dist/runtime/optional-peer.d.ts +10 -0
- package/dist/runtime/optional-peer.js +19 -0
- package/dist/schema/arktype.d.ts +7 -0
- package/dist/schema/arktype.js +13 -0
- package/dist/schema/canonicalize.d.ts +8 -0
- package/dist/schema/canonicalize.js +19 -0
- package/dist/schema/effect.d.ts +7 -0
- package/dist/schema/effect.js +4 -0
- package/dist/schema/naming.d.ts +31 -0
- package/dist/schema/naming.js +116 -0
- package/dist/schema/sanitize-spec.d.ts +15 -0
- package/dist/schema/sanitize-spec.js +43 -0
- package/dist/schema/slots.d.ts +15 -0
- package/dist/schema/slots.js +179 -0
- package/dist/schema/to-json-schema.d.ts +34 -0
- package/dist/schema/to-json-schema.js +94 -0
- package/dist/schema/valibot.d.ts +4 -0
- package/dist/schema/valibot.js +153 -0
- package/dist/schema/yup.d.ts +3 -0
- package/dist/schema/yup.js +57 -0
- package/dist/schema/zod.d.ts +6 -0
- package/dist/schema/zod.js +192 -0
- package/dist/security/crypto.d.ts +7 -0
- package/dist/security/crypto.js +30 -0
- package/dist/streaming/producer-stream.d.ts +45 -0
- package/dist/streaming/producer-stream.js +251 -0
- package/dist/telemetry/fields.d.ts +43 -0
- package/dist/telemetry/fields.js +64 -0
- package/dist/telemetry/log-format.d.ts +7 -0
- package/dist/telemetry/log-format.js +43 -0
- package/dist/telemetry/logger.d.ts +44 -0
- package/dist/telemetry/logger.js +122 -0
- package/dist/telemetry/otel-subpath.d.ts +7 -0
- package/dist/telemetry/otel-subpath.js +5 -0
- package/dist/telemetry/otel.d.ts +50 -57
- package/dist/telemetry/otel.js +54 -130
- package/dist/telemetry/request-log.d.ts +22 -0
- package/dist/telemetry/request-log.js +83 -0
- package/dist/telemetry/request-trace.d.ts +123 -0
- package/dist/telemetry/request-trace.js +199 -0
- package/dist/telemetry/span-lifecycle.d.ts +11 -0
- package/dist/telemetry/span-lifecycle.js +114 -0
- package/dist/telemetry/traceparent.d.ts +40 -0
- package/dist/telemetry/traceparent.js +73 -0
- package/dist/testing/testing.d.ts +42 -0
- package/dist/testing/testing.js +101 -0
- package/dist/validation/content-type.d.ts +21 -0
- package/dist/validation/content-type.js +42 -0
- package/dist/validation/standard-schema.d.ts +41 -0
- package/dist/validation/standard-schema.js +5 -0
- package/dist/validation/validate-input.d.ts +45 -0
- package/dist/validation/validate-input.js +174 -0
- package/dist/validation/validate-output.d.ts +34 -0
- package/dist/validation/validate-output.js +107 -0
- package/dist/ws/bun.d.ts +30 -25
- package/dist/ws/bun.js +42 -46
- package/dist/ws/cloudflare.d.ts +6 -75
- package/dist/ws/cloudflare.js +37 -104
- package/dist/ws/contract.d.ts +74 -0
- package/dist/ws/contract.js +83 -0
- package/dist/ws/node.d.ts +12 -21
- package/dist/ws/node.js +103 -151
- package/dist/ws/origin.d.ts +23 -0
- package/dist/ws/origin.js +48 -0
- package/dist/ws/route.d.ts +14 -0
- package/dist/ws/route.js +5 -0
- package/dist/ws/session.d.ts +15 -6
- package/dist/ws/session.js +22 -23
- package/dist/ws/shared.d.ts +23 -33
- package/dist/ws/shared.js +53 -39
- package/package.json +169 -251
- package/dist/accepts.d.ts +0 -12
- package/dist/accepts.d.ts.map +0 -1
- package/dist/accepts.js +0 -126
- package/dist/accepts.js.map +0 -1
- package/dist/body-kind.d.ts +0 -26
- package/dist/body-kind.d.ts.map +0 -1
- package/dist/body-kind.js +0 -70
- package/dist/body-kind.js.map +0 -1
- package/dist/body-limit.d.ts +0 -26
- package/dist/body-limit.d.ts.map +0 -1
- package/dist/body-limit.js +0 -126
- package/dist/body-limit.js.map +0 -1
- package/dist/build/index.d.ts.map +0 -1
- package/dist/build/index.js.map +0 -1
- package/dist/cli-args.d.ts +0 -33
- package/dist/cli-args.d.ts.map +0 -1
- package/dist/cli-args.js +0 -102
- package/dist/cli-args.js.map +0 -1
- package/dist/cli-go/auth.go +0 -26
- package/dist/cli-go/config.go +0 -192
- package/dist/cli-go/errors.go +0 -75
- package/dist/cli-go/multipart.go +0 -79
- package/dist/cli-go/output.go +0 -163
- package/dist/cli-go/root.go +0 -30
- package/dist/cli-go/stream.go +0 -88
- package/dist/cli-go/version.go +0 -5
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -209
- package/dist/cli.js.map +0 -1
- package/dist/client/defaults.d.ts +0 -12
- package/dist/client/defaults.d.ts.map +0 -1
- package/dist/client/defaults.js +0 -48
- package/dist/client/defaults.js.map +0 -1
- package/dist/client/error.d.ts +0 -49
- package/dist/client/error.d.ts.map +0 -1
- package/dist/client/error.js +0 -71
- package/dist/client/error.js.map +0 -1
- package/dist/client/http.d.ts +0 -155
- package/dist/client/http.d.ts.map +0 -1
- package/dist/client/http.js +0 -571
- package/dist/client/http.js.map +0 -1
- package/dist/client/index.d.ts +0 -23
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js +0 -95
- package/dist/client/index.js.map +0 -1
- package/dist/client/path.d.ts +0 -26
- package/dist/client/path.d.ts.map +0 -1
- package/dist/client/path.js +0 -163
- package/dist/client/path.js.map +0 -1
- package/dist/client/redirect-policy.d.ts +0 -12
- package/dist/client/redirect-policy.d.ts.map +0 -1
- package/dist/client/redirect-policy.js +0 -20
- package/dist/client/redirect-policy.js.map +0 -1
- package/dist/client/sdk.d.ts +0 -70
- package/dist/client/sdk.d.ts.map +0 -1
- package/dist/client/sdk.js +0 -292
- package/dist/client/sdk.js.map +0 -1
- package/dist/client/sse.d.ts +0 -23
- package/dist/client/sse.d.ts.map +0 -1
- package/dist/client/sse.js +0 -129
- package/dist/client/sse.js.map +0 -1
- package/dist/client/types.d.ts +0 -124
- package/dist/client/types.d.ts.map +0 -1
- package/dist/client/types.js +0 -2
- package/dist/client/types.js.map +0 -1
- package/dist/client/ws.d.ts +0 -22
- package/dist/client/ws.d.ts.map +0 -1
- package/dist/client/ws.js +0 -112
- package/dist/client/ws.js.map +0 -1
- package/dist/client-go/errors.go +0 -236
- package/dist/client-go/export.go +0 -44
- package/dist/client-go/invalidation.go +0 -371
- package/dist/client-go/nullable.go +0 -62
- package/dist/client-go/realtime.go +0 -354
- package/dist/client-go/result.go +0 -20
- package/dist/client-go/runtime.go +0 -975
- package/dist/client-go/sse.go +0 -159
- package/dist/client-go/transport.go +0 -495
- package/dist/client-go/ws.go +0 -128
- package/dist/client-info.d.ts +0 -36
- package/dist/client-info.d.ts.map +0 -1
- package/dist/client-info.js +0 -53
- package/dist/client-info.js.map +0 -1
- package/dist/client-mcp/README.md +0 -10
- package/dist/client-mcp/server.ts +0 -221
- package/dist/client-mcp/types.ts +0 -19
- package/dist/client-python/_errors.py +0 -115
- package/dist/client-python/_invalidation.py +0 -494
- package/dist/client-python/_realtime.py +0 -179
- package/dist/client-python/_runtime.py +0 -860
- package/dist/client-python/_sse.py +0 -155
- package/dist/client-python/_transport.py +0 -366
- package/dist/client-python/_ws.py +0 -159
- package/dist/client-python/py.typed +0 -0
- package/dist/client-rust/errors.rs +0 -220
- package/dist/client-rust/invalidation.rs +0 -412
- package/dist/client-rust/invalidation_sync.rs +0 -93
- package/dist/client-rust/nullable.rs +0 -17
- package/dist/client-rust/realtime.rs +0 -702
- package/dist/client-rust/result.rs +0 -22
- package/dist/client-rust/runtime.rs +0 -980
- package/dist/client-rust/runtime_sync.rs +0 -484
- package/dist/client-rust/sse.rs +0 -188
- package/dist/client-rust/ws.rs +0 -167
- package/dist/codegen-go-cli.d.ts +0 -22
- package/dist/codegen-go-cli.d.ts.map +0 -1
- package/dist/codegen-go-cli.js +0 -964
- package/dist/codegen-go-cli.js.map +0 -1
- package/dist/codegen-go.d.ts +0 -41
- package/dist/codegen-go.d.ts.map +0 -1
- package/dist/codegen-go.js +0 -872
- package/dist/codegen-go.js.map +0 -1
- package/dist/codegen-hash.d.ts +0 -3
- package/dist/codegen-hash.d.ts.map +0 -1
- package/dist/codegen-hash.js +0 -9
- package/dist/codegen-hash.js.map +0 -1
- package/dist/codegen-ir.d.ts +0 -190
- package/dist/codegen-ir.d.ts.map +0 -1
- package/dist/codegen-ir.js +0 -661
- package/dist/codegen-ir.js.map +0 -1
- package/dist/codegen-lang.d.ts +0 -65
- package/dist/codegen-lang.d.ts.map +0 -1
- package/dist/codegen-lang.js +0 -526
- package/dist/codegen-lang.js.map +0 -1
- package/dist/codegen-load.d.ts +0 -31
- package/dist/codegen-load.d.ts.map +0 -1
- package/dist/codegen-load.js +0 -138
- package/dist/codegen-load.js.map +0 -1
- package/dist/codegen-loaders.d.ts +0 -12
- package/dist/codegen-loaders.d.ts.map +0 -1
- package/dist/codegen-loaders.js +0 -55
- package/dist/codegen-loaders.js.map +0 -1
- package/dist/codegen-mcp.d.ts +0 -13
- package/dist/codegen-mcp.d.ts.map +0 -1
- package/dist/codegen-mcp.js +0 -368
- package/dist/codegen-mcp.js.map +0 -1
- package/dist/codegen-python.d.ts +0 -33
- package/dist/codegen-python.d.ts.map +0 -1
- package/dist/codegen-python.js +0 -828
- package/dist/codegen-python.js.map +0 -1
- package/dist/codegen-route-tree-intern.d.ts +0 -40
- package/dist/codegen-route-tree-intern.d.ts.map +0 -1
- package/dist/codegen-route-tree-intern.js +0 -221
- package/dist/codegen-route-tree-intern.js.map +0 -1
- package/dist/codegen-rust.d.ts +0 -18
- package/dist/codegen-rust.d.ts.map +0 -1
- package/dist/codegen-rust.js +0 -1293
- package/dist/codegen-rust.js.map +0 -1
- package/dist/codegen-sanitize.d.ts +0 -3
- package/dist/codegen-sanitize.d.ts.map +0 -1
- package/dist/codegen-sanitize.js +0 -56
- package/dist/codegen-sanitize.js.map +0 -1
- package/dist/codegen-schema-naming.d.ts +0 -27
- package/dist/codegen-schema-naming.d.ts.map +0 -1
- package/dist/codegen-schema-naming.js +0 -150
- package/dist/codegen-schema-naming.js.map +0 -1
- package/dist/codegen-sdk-model.d.ts +0 -71
- package/dist/codegen-sdk-model.d.ts.map +0 -1
- package/dist/codegen-sdk-model.js +0 -179
- package/dist/codegen-sdk-model.js.map +0 -1
- package/dist/codegen.d.ts +0 -187
- package/dist/codegen.d.ts.map +0 -1
- package/dist/codegen.js +0 -4619
- package/dist/codegen.js.map +0 -1
- package/dist/context.d.ts +0 -86
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -242
- package/dist/context.js.map +0 -1
- package/dist/cookie-sign.d.ts +0 -17
- package/dist/cookie-sign.d.ts.map +0 -1
- package/dist/cookie-sign.js +0 -104
- package/dist/cookie-sign.js.map +0 -1
- package/dist/cookie.d.ts +0 -24
- package/dist/cookie.d.ts.map +0 -1
- package/dist/cookie.js +0 -138
- package/dist/cookie.js.map +0 -1
- package/dist/cors.d.ts +0 -14
- package/dist/cors.d.ts.map +0 -1
- package/dist/cors.js +0 -87
- package/dist/cors.js.map +0 -1
- package/dist/crypto.d.ts +0 -7
- package/dist/crypto.d.ts.map +0 -1
- package/dist/crypto.js +0 -38
- package/dist/crypto.js.map +0 -1
- package/dist/csrf.d.ts +0 -28
- package/dist/csrf.d.ts.map +0 -1
- package/dist/csrf.js +0 -79
- package/dist/csrf.js.map +0 -1
- package/dist/curl-logger.d.ts +0 -44
- package/dist/curl-logger.d.ts.map +0 -1
- package/dist/curl-logger.js +0 -199
- package/dist/curl-logger.js.map +0 -1
- package/dist/detect-runtime.d.ts +0 -16
- package/dist/detect-runtime.d.ts.map +0 -1
- package/dist/detect-runtime.js +0 -13
- package/dist/detect-runtime.js.map +0 -1
- package/dist/dict.d.ts +0 -11
- package/dist/dict.d.ts.map +0 -1
- package/dist/dict.js +0 -13
- package/dist/dict.js.map +0 -1
- package/dist/error.d.ts +0 -21
- package/dist/error.d.ts.map +0 -1
- package/dist/error.js +0 -44
- package/dist/error.js.map +0 -1
- package/dist/errors.d.ts +0 -40
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -39
- package/dist/errors.js.map +0 -1
- package/dist/etag.d.ts +0 -11
- package/dist/etag.d.ts.map +0 -1
- package/dist/etag.js +0 -82
- package/dist/etag.js.map +0 -1
- package/dist/feature-detect.d.ts +0 -25
- package/dist/feature-detect.d.ts.map +0 -1
- package/dist/feature-detect.js +0 -144
- package/dist/feature-detect.js.map +0 -1
- package/dist/feature-load.d.ts +0 -3
- package/dist/feature-load.d.ts.map +0 -1
- package/dist/feature-load.js +0 -36
- package/dist/feature-load.js.map +0 -1
- package/dist/feature-slots.d.ts +0 -44
- package/dist/feature-slots.d.ts.map +0 -1
- package/dist/feature-slots.js +0 -48
- package/dist/feature-slots.js.map +0 -1
- package/dist/fetch-request.d.ts +0 -10
- package/dist/fetch-request.d.ts.map +0 -1
- package/dist/fetch-request.js +0 -13
- package/dist/fetch-request.js.map +0 -1
- package/dist/gen-process.d.ts +0 -15
- package/dist/gen-process.d.ts.map +0 -1
- package/dist/gen-process.js +0 -43
- package/dist/gen-process.js.map +0 -1
- package/dist/gen-write.d.ts +0 -19
- package/dist/gen-write.d.ts.map +0 -1
- package/dist/gen-write.js +0 -120
- package/dist/gen-write.js.map +0 -1
- package/dist/glob.d.ts +0 -21
- package/dist/glob.d.ts.map +0 -1
- package/dist/glob.js +0 -115
- package/dist/glob.js.map +0 -1
- package/dist/go-type-emitter.d.ts +0 -47
- package/dist/go-type-emitter.d.ts.map +0 -1
- package/dist/go-type-emitter.js +0 -594
- package/dist/go-type-emitter.js.map +0 -1
- package/dist/honey-response.d.ts +0 -82
- package/dist/honey-response.d.ts.map +0 -1
- package/dist/honey-response.js +0 -338
- package/dist/honey-response.js.map +0 -1
- package/dist/i18n-slot.d.ts +0 -15
- package/dist/i18n-slot.d.ts.map +0 -1
- package/dist/i18n-slot.js +0 -14
- package/dist/i18n-slot.js.map +0 -1
- package/dist/i18n.d.ts +0 -29
- package/dist/i18n.d.ts.map +0 -1
- package/dist/i18n.js +0 -250
- package/dist/i18n.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/init.d.ts +0 -5
- package/dist/init.d.ts.map +0 -1
- package/dist/init.js +0 -140
- package/dist/init.js.map +0 -1
- package/dist/input.d.ts +0 -6
- package/dist/input.d.ts.map +0 -1
- package/dist/input.js +0 -4
- package/dist/input.js.map +0 -1
- package/dist/invalidate-check.d.ts +0 -61
- package/dist/invalidate-check.d.ts.map +0 -1
- package/dist/invalidate-check.js +0 -163
- package/dist/invalidate-check.js.map +0 -1
- package/dist/invoke-user.d.ts +0 -35
- package/dist/invoke-user.d.ts.map +0 -1
- package/dist/invoke-user.js +0 -73
- package/dist/invoke-user.js.map +0 -1
- package/dist/ip-restrict.d.ts +0 -24
- package/dist/ip-restrict.d.ts.map +0 -1
- package/dist/ip-restrict.js +0 -60
- package/dist/ip-restrict.js.map +0 -1
- package/dist/ip.d.ts +0 -39
- package/dist/ip.d.ts.map +0 -1
- package/dist/ip.js +0 -209
- package/dist/ip.js.map +0 -1
- package/dist/logger.d.ts +0 -45
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -113
- package/dist/logger.js.map +0 -1
- package/dist/media-type.d.ts +0 -30
- package/dist/media-type.d.ts.map +0 -1
- package/dist/media-type.js +0 -97
- package/dist/media-type.js.map +0 -1
- package/dist/meta-spec-merge.d.ts +0 -32
- package/dist/meta-spec-merge.d.ts.map +0 -1
- package/dist/meta-spec-merge.js +0 -68
- package/dist/meta-spec-merge.js.map +0 -1
- package/dist/meta-spec.d.ts +0 -94
- package/dist/meta-spec.d.ts.map +0 -1
- package/dist/meta-spec.js +0 -535
- package/dist/meta-spec.js.map +0 -1
- package/dist/middleware.d.ts +0 -77
- package/dist/middleware.d.ts.map +0 -1
- package/dist/middleware.js +0 -253
- package/dist/middleware.js.map +0 -1
- package/dist/node-request.d.ts +0 -104
- package/dist/node-request.d.ts.map +0 -1
- package/dist/node-request.js +0 -407
- package/dist/node-request.js.map +0 -1
- package/dist/node.d.ts +0 -30
- package/dist/node.d.ts.map +0 -1
- package/dist/node.js +0 -396
- package/dist/node.js.map +0 -1
- package/dist/openapi/collect.d.ts.map +0 -1
- package/dist/openapi/collect.js.map +0 -1
- package/dist/openapi/docs-page.d.ts.map +0 -1
- package/dist/openapi/docs-page.js.map +0 -1
- package/dist/openapi/document.d.ts.map +0 -1
- package/dist/openapi/document.js.map +0 -1
- package/dist/openapi/json-schema-slot.d.ts +0 -12
- package/dist/openapi/json-schema-slot.d.ts.map +0 -1
- package/dist/openapi/json-schema-slot.js +0 -38
- package/dist/openapi/json-schema-slot.js.map +0 -1
- package/dist/openapi/register.d.ts.map +0 -1
- package/dist/openapi/register.js.map +0 -1
- package/dist/openapi/scalar.d.ts.map +0 -1
- package/dist/openapi/scalar.js.map +0 -1
- package/dist/openapi/serve.d.ts.map +0 -1
- package/dist/openapi/serve.js.map +0 -1
- package/dist/openapi/spec-factory.d.ts +0 -65
- package/dist/openapi/spec-factory.d.ts.map +0 -1
- package/dist/openapi/spec-factory.js +0 -52
- package/dist/openapi/spec-factory.js.map +0 -1
- package/dist/openapi/spec.d.ts +0 -29
- package/dist/openapi/spec.d.ts.map +0 -1
- package/dist/openapi/spec.js +0 -50
- package/dist/openapi/spec.js.map +0 -1
- package/dist/openapi/swagger.d.ts.map +0 -1
- package/dist/openapi/swagger.js.map +0 -1
- package/dist/pattern.d.ts +0 -71
- package/dist/pattern.d.ts.map +0 -1
- package/dist/pattern.js +0 -239
- package/dist/pattern.js.map +0 -1
- package/dist/peer.d.ts +0 -17
- package/dist/peer.d.ts.map +0 -1
- package/dist/peer.js +0 -50
- package/dist/peer.js.map +0 -1
- package/dist/plugin.d.ts +0 -228
- package/dist/plugin.d.ts.map +0 -1
- package/dist/plugin.js +0 -666
- package/dist/plugin.js.map +0 -1
- package/dist/powered-by.d.ts +0 -7
- package/dist/powered-by.d.ts.map +0 -1
- package/dist/powered-by.js +0 -11
- package/dist/powered-by.js.map +0 -1
- package/dist/pretty-json.d.ts +0 -10
- package/dist/pretty-json.d.ts.map +0 -1
- package/dist/pretty-json.js +0 -47
- package/dist/pretty-json.js.map +0 -1
- package/dist/producer-stream.d.ts +0 -47
- package/dist/producer-stream.d.ts.map +0 -1
- package/dist/producer-stream.js +0 -267
- package/dist/producer-stream.js.map +0 -1
- package/dist/proxy.d.ts +0 -90
- package/dist/proxy.d.ts.map +0 -1
- package/dist/proxy.js +0 -274
- package/dist/proxy.js.map +0 -1
- package/dist/python-type-emitter.d.ts +0 -24
- package/dist/python-type-emitter.d.ts.map +0 -1
- package/dist/python-type-emitter.js +0 -157
- package/dist/python-type-emitter.js.map +0 -1
- package/dist/realtime/bus.d.ts.map +0 -1
- package/dist/realtime/bus.js.map +0 -1
- package/dist/realtime/register.d.ts +0 -3
- package/dist/realtime/register.d.ts.map +0 -1
- package/dist/realtime/register.js +0 -10
- package/dist/realtime/register.js.map +0 -1
- package/dist/realtime/route.d.ts.map +0 -1
- package/dist/realtime/route.js.map +0 -1
- package/dist/realtime/server.d.ts.map +0 -1
- package/dist/realtime/server.js.map +0 -1
- package/dist/request-id.d.ts +0 -19
- package/dist/request-id.d.ts.map +0 -1
- package/dist/request-id.js +0 -16
- package/dist/request-id.js.map +0 -1
- package/dist/request-limits.d.ts +0 -3
- package/dist/request-limits.d.ts.map +0 -1
- package/dist/request-limits.js +0 -3
- package/dist/request-limits.js.map +0 -1
- package/dist/request-path.d.ts +0 -33
- package/dist/request-path.d.ts.map +0 -1
- package/dist/request-path.js +0 -178
- package/dist/request-path.js.map +0 -1
- package/dist/request-to-curl.d.ts +0 -15
- package/dist/request-to-curl.d.ts.map +0 -1
- package/dist/request-to-curl.js +0 -48
- package/dist/request-to-curl.js.map +0 -1
- package/dist/response.d.ts +0 -86
- package/dist/response.d.ts.map +0 -1
- package/dist/response.js +0 -313
- package/dist/response.js.map +0 -1
- package/dist/rust-type-emitter.d.ts +0 -44
- package/dist/rust-type-emitter.d.ts.map +0 -1
- package/dist/rust-type-emitter.js +0 -463
- package/dist/rust-type-emitter.js.map +0 -1
- package/dist/secure-headers.d.ts +0 -16
- package/dist/secure-headers.d.ts.map +0 -1
- package/dist/secure-headers.js +0 -53
- package/dist/secure-headers.js.map +0 -1
- package/dist/serve-register.d.ts +0 -6
- package/dist/serve-register.d.ts.map +0 -1
- package/dist/serve-register.js +0 -9
- package/dist/serve-register.js.map +0 -1
- package/dist/serve-slot.d.ts +0 -9
- package/dist/serve-slot.d.ts.map +0 -1
- package/dist/serve-slot.js +0 -52
- package/dist/serve-slot.js.map +0 -1
- package/dist/serve.d.ts +0 -38
- package/dist/serve.d.ts.map +0 -1
- package/dist/serve.js +0 -157
- package/dist/serve.js.map +0 -1
- package/dist/server-timing.d.ts +0 -9
- package/dist/server-timing.d.ts.map +0 -1
- package/dist/server-timing.js +0 -51
- package/dist/server-timing.js.map +0 -1
- package/dist/static.d.ts +0 -21
- package/dist/static.d.ts.map +0 -1
- package/dist/static.js +0 -102
- package/dist/static.js.map +0 -1
- package/dist/telemetry/otel.d.ts.map +0 -1
- package/dist/telemetry/otel.js.map +0 -1
- package/dist/testing.d.ts +0 -36
- package/dist/testing.d.ts.map +0 -1
- package/dist/testing.js +0 -124
- package/dist/testing.js.map +0 -1
- package/dist/timeout.d.ts +0 -13
- package/dist/timeout.d.ts.map +0 -1
- package/dist/timeout.js +0 -36
- package/dist/timeout.js.map +0 -1
- package/dist/tree.d.ts +0 -193
- package/dist/tree.d.ts.map +0 -1
- package/dist/tree.js +0 -437
- package/dist/tree.js.map +0 -1
- package/dist/trust.d.ts +0 -21
- package/dist/trust.d.ts.map +0 -1
- package/dist/trust.js +0 -100
- package/dist/trust.js.map +0 -1
- package/dist/ts-type-emitter.d.ts +0 -8
- package/dist/ts-type-emitter.d.ts.map +0 -1
- package/dist/ts-type-emitter.js +0 -150
- package/dist/ts-type-emitter.js.map +0 -1
- package/dist/type-emitter.d.ts +0 -36
- package/dist/type-emitter.d.ts.map +0 -1
- package/dist/type-emitter.js +0 -729
- package/dist/type-emitter.js.map +0 -1
- package/dist/type-extractor.d.ts +0 -43
- package/dist/type-extractor.d.ts.map +0 -1
- package/dist/type-extractor.js +0 -819
- package/dist/type-extractor.js.map +0 -1
- package/dist/types.d.ts +0 -467
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -96
- package/dist/types.js.map +0 -1
- package/dist/validation.d.ts +0 -31
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -335
- package/dist/validation.js.map +0 -1
- package/dist/with-headers.d.ts +0 -15
- package/dist/with-headers.d.ts.map +0 -1
- package/dist/with-headers.js +0 -45
- package/dist/with-headers.js.map +0 -1
- package/dist/ws/bun.d.ts.map +0 -1
- package/dist/ws/bun.js.map +0 -1
- package/dist/ws/cloudflare.d.ts.map +0 -1
- package/dist/ws/cloudflare.js.map +0 -1
- package/dist/ws/deno.d.ts +0 -13
- package/dist/ws/deno.d.ts.map +0 -1
- package/dist/ws/deno.js +0 -133
- package/dist/ws/deno.js.map +0 -1
- package/dist/ws/node.d.ts.map +0 -1
- package/dist/ws/node.js.map +0 -1
- package/dist/ws/session.d.ts.map +0 -1
- package/dist/ws/session.js.map +0 -1
- package/dist/ws/shared.d.ts.map +0 -1
- package/dist/ws/shared.js.map +0 -1
- package/dist/ws-origin.d.ts +0 -20
- package/dist/ws-origin.d.ts.map +0 -1
- package/dist/ws-origin.js +0 -41
- package/dist/ws-origin.js.map +0 -1
- package/dist/yaml.d.ts +0 -18
- package/dist/yaml.d.ts.map +0 -1
- package/dist/yaml.js +0 -101
- package/dist/yaml.js.map +0 -1
- package/docs/api-ref/go.md +0 -399
- package/docs/api-ref/python.md +0 -399
- package/docs/api-ref/rust.md +0 -399
- package/docs/api-ref/typescript.md +0 -399
- package/docs/invalidate-spec.md +0 -333
- package/docs/meta-spec.md +0 -555
- package/docs/packaging.md +0 -104
- package/docs/regression-matrix/README.md +0 -32
- package/docs/regression-matrix/review.md +0 -44
- package/docs/regression-matrix/ws1-3.md +0 -97
- package/docs/regression-matrix/ws4-8.md +0 -107
- package/docs/regression-matrix/ws9-12.md +0 -266
- package/docs/sdk.md +0 -798
- package/src/accepts.ts +0 -136
- package/src/body-kind.ts +0 -67
- package/src/body-limit.ts +0 -152
- package/src/build/index.ts +0 -217
- package/src/cli-args.ts +0 -123
- package/src/cli-go/auth.go +0 -26
- package/src/cli-go/config.go +0 -192
- package/src/cli-go/errors.go +0 -75
- package/src/cli-go/multipart.go +0 -79
- package/src/cli-go/output.go +0 -163
- package/src/cli-go/root.go +0 -30
- package/src/cli-go/stream.go +0 -88
- package/src/cli-go/version.go +0 -5
- package/src/cli.ts +0 -209
- package/src/client/defaults.ts +0 -47
- package/src/client/error.ts +0 -82
- package/src/client/http.ts +0 -737
- package/src/client/index.ts +0 -157
- package/src/client/path.ts +0 -169
- package/src/client/redirect-policy.ts +0 -19
- package/src/client/sdk.ts +0 -351
- package/src/client/sse.ts +0 -128
- package/src/client/types.ts +0 -206
- package/src/client/ws.ts +0 -149
- package/src/client-go/errors.go +0 -236
- package/src/client-go/export.go +0 -44
- package/src/client-go/invalidation.go +0 -371
- package/src/client-go/nullable.go +0 -62
- package/src/client-go/realtime.go +0 -354
- package/src/client-go/result.go +0 -20
- package/src/client-go/runtime.go +0 -975
- package/src/client-go/sse.go +0 -159
- package/src/client-go/transport.go +0 -495
- package/src/client-go/ws.go +0 -128
- package/src/client-info.ts +0 -79
- package/src/client-mcp/README.md +0 -10
- package/src/client-mcp/server.ts +0 -221
- package/src/client-mcp/types.ts +0 -19
- package/src/client-python/_errors.py +0 -115
- package/src/client-python/_invalidation.py +0 -494
- package/src/client-python/_realtime.py +0 -179
- package/src/client-python/_runtime.py +0 -860
- package/src/client-python/_sse.py +0 -155
- package/src/client-python/_transport.py +0 -366
- package/src/client-python/_ws.py +0 -159
- package/src/client-python/py.typed +0 -0
- package/src/client-rust/errors.rs +0 -220
- package/src/client-rust/invalidation.rs +0 -412
- package/src/client-rust/invalidation_sync.rs +0 -93
- package/src/client-rust/nullable.rs +0 -17
- package/src/client-rust/realtime.rs +0 -702
- package/src/client-rust/result.rs +0 -22
- package/src/client-rust/runtime.rs +0 -980
- package/src/client-rust/runtime_sync.rs +0 -484
- package/src/client-rust/sse.rs +0 -188
- package/src/client-rust/ws.rs +0 -167
- package/src/codegen-go-cli.ts +0 -1066
- package/src/codegen-go.ts +0 -970
- package/src/codegen-hash.ts +0 -8
- package/src/codegen-ir.ts +0 -812
- package/src/codegen-lang.ts +0 -517
- package/src/codegen-load.ts +0 -150
- package/src/codegen-loaders.ts +0 -50
- package/src/codegen-mcp.ts +0 -445
- package/src/codegen-python.ts +0 -916
- package/src/codegen-route-tree-intern.ts +0 -198
- package/src/codegen-rust.ts +0 -1402
- package/src/codegen-sanitize.ts +0 -57
- package/src/codegen-schema-naming.ts +0 -165
- package/src/codegen-sdk-model.ts +0 -228
- package/src/codegen.ts +0 -5089
- package/src/context.ts +0 -325
- package/src/cookie-sign.ts +0 -119
- package/src/cookie.ts +0 -143
- package/src/cors.ts +0 -95
- package/src/crypto.ts +0 -38
- package/src/csrf.ts +0 -86
- package/src/curl-logger.ts +0 -258
- package/src/detect-runtime.ts +0 -17
- package/src/dict.ts +0 -12
- package/src/error.ts +0 -55
- package/src/errors.ts +0 -85
- package/src/etag.ts +0 -97
- package/src/feature-detect.ts +0 -147
- package/src/feature-load.ts +0 -23
- package/src/feature-slots.ts +0 -70
- package/src/fetch-request.ts +0 -13
- package/src/gen-process.ts +0 -42
- package/src/gen-write.ts +0 -127
- package/src/glob.ts +0 -109
- package/src/go-type-emitter.ts +0 -660
- package/src/honey-response.ts +0 -379
- package/src/i18n-slot.ts +0 -30
- package/src/i18n.ts +0 -300
- package/src/index.ts +0 -3829
- package/src/init.ts +0 -161
- package/src/input.ts +0 -8
- package/src/invalidate-check.ts +0 -205
- package/src/invoke-user.ts +0 -95
- package/src/ip-restrict.ts +0 -79
- package/src/ip.ts +0 -194
- package/src/logger.ts +0 -178
- package/src/media-type.ts +0 -104
- package/src/meta-spec-merge.ts +0 -80
- package/src/meta-spec.ts +0 -718
- package/src/middleware.ts +0 -331
- package/src/node-request.ts +0 -431
- package/src/node.ts +0 -454
- package/src/openapi/collect.ts +0 -50
- package/src/openapi/docs-page.ts +0 -89
- package/src/openapi/document.ts +0 -893
- package/src/openapi/json-schema-slot.ts +0 -53
- package/src/openapi/register.ts +0 -23
- package/src/openapi/scalar.ts +0 -34
- package/src/openapi/serve.ts +0 -39
- package/src/openapi/spec-factory.ts +0 -100
- package/src/openapi/spec.ts +0 -74
- package/src/openapi/swagger.ts +0 -35
- package/src/pattern.ts +0 -245
- package/src/peer.ts +0 -53
- package/src/plugin.ts +0 -864
- package/src/powered-by.ts +0 -18
- package/src/pretty-json.ts +0 -56
- package/src/producer-stream.ts +0 -296
- package/src/proxy.ts +0 -370
- package/src/python-type-emitter.ts +0 -181
- package/src/realtime/bus.ts +0 -140
- package/src/realtime/register.ts +0 -13
- package/src/realtime/route.ts +0 -153
- package/src/realtime/server.ts +0 -298
- package/src/request-id.ts +0 -32
- package/src/request-limits.ts +0 -2
- package/src/request-path.ts +0 -165
- package/src/request-to-curl.ts +0 -52
- package/src/response.ts +0 -411
- package/src/rust-type-emitter.ts +0 -548
- package/src/secure-headers.ts +0 -74
- package/src/serve-register.ts +0 -12
- package/src/serve-slot.ts +0 -58
- package/src/serve.ts +0 -220
- package/src/server-timing.ts +0 -71
- package/src/static.ts +0 -112
- package/src/telemetry/otel.ts +0 -156
- package/src/testing.ts +0 -167
- package/src/timeout.ts +0 -45
- package/src/tree.ts +0 -554
- package/src/trust.ts +0 -117
- package/src/ts-type-emitter.ts +0 -168
- package/src/type-emitter.ts +0 -760
- package/src/type-extractor.ts +0 -855
- package/src/types.ts +0 -756
- package/src/validation.ts +0 -413
- package/src/with-headers.ts +0 -45
- package/src/ws/bun.ts +0 -120
- package/src/ws/cloudflare.ts +0 -192
- package/src/ws/deno.ts +0 -188
- package/src/ws/node.ts +0 -240
- package/src/ws/session.ts +0 -73
- package/src/ws/shared.ts +0 -140
- package/src/ws-origin.ts +0 -53
- package/src/yaml.ts +0 -103
package/dist/index.js
CHANGED
|
@@ -1,2365 +1,14 @@
|
|
|
1
|
-
import { bodyKind, headResponse, isProducedStream, rawBodyOf } from "./body-kind.js";
|
|
2
|
-
import { clientInfo, HoneyContext } from "./context.js";
|
|
3
|
-
import { dict } from "./dict.js";
|
|
4
|
-
import { normalizePath, pathOfUrl, searchOfUrl } from "./request-path.js";
|
|
5
|
-
import { BUN_URL_FROM_HOST, hasValidHost, TRUST_OFF } from "./client-info.js";
|
|
6
|
-
import { loadFeature, requireFeature } from "./feature-slots.js";
|
|
7
|
-
import { HoneyError } from "./error.js";
|
|
8
|
-
import { ERROR_META } from "./errors.js";
|
|
9
|
-
import { collectMiddlewareMeta, compileChain, RESERVED_CTX_KEYS } from "./middleware.js";
|
|
10
|
-
import { createErrorResponse } from "./response.js";
|
|
11
|
-
import { canonical, expandOptional, isStaticPattern, joinPatterns, normalizePattern, parsePattern, pathInScope, routeId, scopeCoverage, splitRouteId, } from "./pattern.js";
|
|
12
|
-
import { assertTreeFormat, cloneTree, createNode, findLeaf, forEachLeaf, freezeTree, hasWsLeaf, insertLeaf, matchRoute, matchWsRoute, ROUTE_TREE_VERSION, } from "./tree.js";
|
|
13
|
-
import { codeToStatusKey, EK, EMPTY_OBJ, SK } from "./types.js";
|
|
14
|
-
import { assertBodySchemaAllowed, assertRequestContentType, validateInput, validateOutput } from "./validation.js";
|
|
15
|
-
import { validateOriginPolicy } from "./ws-origin.js";
|
|
16
|
-
import { loadHoneyFeature } from "./feature-load.js";
|
|
17
|
-
import { getI18nRuntime } from "./i18n-slot.js";
|
|
18
|
-
import { bindInternalHandler, epochCached, getOpenApiRuntime } from "./openapi/spec-factory.js";
|
|
19
|
-
import { getServeRuntime, isCodegenProcess } from "./serve-slot.js";
|
|
20
|
-
export { clientInfo, HoneyContext } from "./context.js";
|
|
21
|
-
export { toFetchRequest } from "./fetch-request.js";
|
|
22
|
-
export { HoneyError } from "./error.js";
|
|
23
|
-
export { defineErrors, ERROR_META } from "./errors.js";
|
|
24
|
-
export { createMiddleware, defineMiddleware } from "./middleware.js";
|
|
25
|
-
export { createErrorResponse, HoneyRes } from "./response.js";
|
|
26
|
-
export { mergeTree } from "./tree.js";
|
|
27
|
-
export { detectRuntime } from "./detect-runtime.js";
|
|
28
|
-
function createSettings() {
|
|
29
|
-
return {
|
|
30
|
-
customErrorFormatter: null,
|
|
31
|
-
customErrorSchema: null,
|
|
32
|
-
defaultBoundaryKey: null,
|
|
33
|
-
defaultErrorKeys: new Set(),
|
|
34
|
-
errorFactory: null,
|
|
35
|
-
errorFormatter: defaultErrorFormatter,
|
|
36
|
-
errorI18n: null,
|
|
37
|
-
errorSchema: null,
|
|
38
|
-
logger: null,
|
|
39
|
-
onError: null,
|
|
40
|
-
onMethodNotAllowed: null,
|
|
41
|
-
onNotFound: null,
|
|
42
|
-
outputValidation: null,
|
|
43
|
-
stripPrefix: null,
|
|
44
|
-
taps: null,
|
|
45
|
-
telemetry: null,
|
|
46
|
-
trailingSlash: "ignore",
|
|
47
|
-
encodedSlashes: "reject",
|
|
48
|
-
trust: TRUST_OFF,
|
|
49
|
-
wsAdapter: null,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
/** Join a base path and a route or scope path into one canonical pattern. */
|
|
53
|
-
function mergePath(base, path) {
|
|
54
|
-
return joinPatterns(base, path);
|
|
55
|
-
}
|
|
56
|
-
function createGraph() {
|
|
57
|
-
return {
|
|
58
|
-
absorbedMetaSpecs: [],
|
|
59
|
-
catchAll: new Map(),
|
|
60
|
-
chains: [],
|
|
61
|
-
epoch: 0,
|
|
62
|
-
final: null,
|
|
63
|
-
global: new Map(),
|
|
64
|
-
hasWs: false,
|
|
65
|
-
loaded: null,
|
|
66
|
-
metaSpec: null,
|
|
67
|
-
pending: new Set(),
|
|
68
|
-
realtimeBus: null,
|
|
69
|
-
realtimeCtx: null,
|
|
70
|
-
realtimeRoutes: new Map(),
|
|
71
|
-
records: new Map(),
|
|
72
|
-
root: createNode(),
|
|
73
|
-
rootShared: false,
|
|
74
|
-
scoped: [],
|
|
75
|
-
served: new Set(),
|
|
76
|
-
settings: createSettings(),
|
|
77
|
-
unexpected: new Set(),
|
|
78
|
-
unloadIfStale: false,
|
|
79
|
-
wsRecords: new Map(),
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
/** Fresh copy of a record for another graph — sources are copied, resolved fields recomputed there. */
|
|
83
|
-
function copyRecord(r) {
|
|
84
|
-
const out = { ...r, ek: new Set(r.ek) };
|
|
85
|
-
if (r.dk !== undefined)
|
|
86
|
-
out.dk = new Set(r.dk);
|
|
87
|
-
if (r.cm !== undefined)
|
|
88
|
-
out.cm = [...r.cm];
|
|
89
|
-
if (r.rm !== undefined)
|
|
90
|
-
out.rm = [...r.rm];
|
|
91
|
-
delete out.ca;
|
|
92
|
-
return out;
|
|
93
|
-
}
|
|
94
|
-
/** Every leaf a pattern occupies: an optional last param places two. */
|
|
95
|
-
function leafVariants(segments) {
|
|
96
|
-
return expandOptional(segments);
|
|
97
|
-
}
|
|
98
|
-
/** Record the error behind an error response on `ctx.error` (read-only for user code). */
|
|
99
|
-
function setCtxError(ctx, error) {
|
|
100
|
-
;
|
|
101
|
-
ctx["error"] = error;
|
|
102
|
-
}
|
|
103
|
-
/** What `ctx.error` holds for a throw the boundary could not resolve: the 500 it became. */
|
|
104
|
-
function asHoneyError(thrown) {
|
|
105
|
-
return thrown instanceof HoneyError
|
|
106
|
-
? thrown
|
|
107
|
-
: new HoneyError({ cause: thrown, errorKey: EK.internal_server_error, status: SK.internal_server_error });
|
|
108
|
-
}
|
|
109
|
-
function patternOf(id) {
|
|
110
|
-
const { method, pattern } = splitRouteId(id);
|
|
111
|
-
return { method, segments: parsePattern(pattern) };
|
|
112
|
-
}
|
|
113
|
-
/** `fn` of a tree leaf no handler serves — dispatch answers 404, codegen still documents it. */
|
|
114
|
-
const NOT_SERVED = () => {
|
|
115
|
-
throw new Error("honey: unserved route reached its handler");
|
|
116
|
-
};
|
|
117
|
-
function isRootWildcard(segments) {
|
|
118
|
-
return segments.length === 1 && segments[0].k === "wildcard";
|
|
119
|
-
}
|
|
120
|
-
const EMPTY_PARAMS = EMPTY_OBJ;
|
|
121
|
-
function requestIsWsUpgrade(request) {
|
|
122
|
-
try {
|
|
123
|
-
return request.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
/* Deno closes the Request after upgradeWebSocket */
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/** The info object `Deno.serve` passes as the second handler argument. */
|
|
131
|
-
function isDenoServeInfo(env) {
|
|
132
|
-
return env !== null && typeof env === "object" && env.remoteAddr !== undefined;
|
|
133
|
-
}
|
|
134
|
-
/** `obj[key]` for an own key only; `undefined` for inherited names such as `constructor`. */
|
|
135
|
-
function safeFire(fn, logger) {
|
|
136
|
-
if (fn === undefined)
|
|
137
|
-
return;
|
|
138
|
-
try {
|
|
139
|
-
const result = fn();
|
|
140
|
-
if (result && typeof result === "object" && "catch" in result) {
|
|
141
|
-
;
|
|
142
|
-
result.catch((e) => {
|
|
143
|
-
logger?.warn?.({ err: e }, "telemetry callback failed");
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
catch (e) {
|
|
148
|
-
logger?.warn?.({ err: e }, "telemetry callback failed");
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
function requestWithHeaders(req, headers) {
|
|
152
|
-
return new Proxy(req, {
|
|
153
|
-
get(target, prop) {
|
|
154
|
-
if (prop === "headers")
|
|
155
|
-
return headers;
|
|
156
|
-
const value = Reflect.get(target, prop, target);
|
|
157
|
-
return typeof value === "function" ? value.bind(target) : value;
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
function ctxRequest(fc) {
|
|
162
|
-
if (!fc.headerSnap)
|
|
163
|
-
return fc.request;
|
|
164
|
-
try {
|
|
165
|
-
void fc.request.headers.get("upgrade");
|
|
166
|
-
return fc.request;
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
return requestWithHeaders(fc.request, fc.headerSnap);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
function defaultErrorFormatter(_error, defaultShape) {
|
|
173
|
-
return defaultShape;
|
|
174
|
-
}
|
|
175
|
-
/* errorKeys the framework throws on its own behalf — input/output validation, content negotiation,
|
|
176
|
-
* routing, and the shipped middleware. Always passes the boundary check; users never declare these via .errors(). */
|
|
177
|
-
const FRAMEWORK_EKS = new Set([
|
|
178
|
-
EK.validation_failed,
|
|
179
|
-
EK.output_validation_failed,
|
|
180
|
-
EK.output_content_type_mismatch,
|
|
181
|
-
EK.unsupported_media_type,
|
|
182
|
-
EK.content_too_large,
|
|
183
|
-
EK.method_not_allowed,
|
|
184
|
-
EK.not_found,
|
|
185
|
-
EK.too_many_requests,
|
|
186
|
-
EK.request_timeout,
|
|
187
|
-
EK.gateway_timeout,
|
|
188
|
-
EK.bad_gateway,
|
|
189
|
-
EK.forbidden,
|
|
190
|
-
EK.bad_request,
|
|
191
|
-
EK.malformed_body,
|
|
192
|
-
]);
|
|
193
|
-
function shouldValidateOutput(mode) {
|
|
194
|
-
if (mode === "always")
|
|
195
|
-
return true;
|
|
196
|
-
if (mode === "off")
|
|
197
|
-
return false;
|
|
198
|
-
return globalThis.process?.env?.NODE_ENV !== "production";
|
|
199
|
-
}
|
|
200
|
-
/** The scoped middleware behind a request-path check: runs only when the path is inside the scope. */
|
|
201
|
-
function scopeGuard(segs, mw) {
|
|
202
|
-
const guard = (ctx, next) => (pathInScope(ctx["path"], segs) ? mw(ctx, next) : next());
|
|
203
|
-
Object.defineProperty(guard, "name", { configurable: true, value: mw.name });
|
|
204
|
-
const tagged = mw;
|
|
205
|
-
if (tagged.errors)
|
|
206
|
-
Object.defineProperty(guard, "errors", { value: tagged.errors });
|
|
207
|
-
if (tagged.meta)
|
|
208
|
-
Object.defineProperty(guard, "meta", { value: tagged.meta });
|
|
209
|
-
return guard;
|
|
210
|
-
}
|
|
211
|
-
/** Wrap a middleware so telemetry sees its own duration and its own throw. Built once at finalize. */
|
|
212
|
-
function timedMiddleware(mw, onMw, log) {
|
|
213
|
-
const name = mw.name || "anonymous";
|
|
214
|
-
return async (ctx, next) => {
|
|
215
|
-
const start = performance.now();
|
|
216
|
-
try {
|
|
217
|
-
const res = await mw(ctx, next);
|
|
218
|
-
safeFire(() => onMw({ duration: performance.now() - start, name }), log);
|
|
219
|
-
return res;
|
|
220
|
-
}
|
|
221
|
-
catch (error) {
|
|
222
|
-
safeFire(() => onMw({ duration: performance.now() - start, error, name }), log);
|
|
223
|
-
throw error;
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
function errorsOf(mw) {
|
|
228
|
-
return mw.errors;
|
|
229
|
-
}
|
|
230
|
-
/** Longest run of middleware every record starts its chain with — what 404 and 405 run. */
|
|
231
|
-
function commonPrefix(chains) {
|
|
232
|
-
if (chains.length === 0)
|
|
233
|
-
return [];
|
|
234
|
-
let out = [...chains[0]];
|
|
235
|
-
for (let i = 1; i < chains.length && out.length > 0; i++) {
|
|
236
|
-
const c = chains[i];
|
|
237
|
-
let n = 0;
|
|
238
|
-
while (n < out.length && n < c.length && out[n] === c[n])
|
|
239
|
-
n++;
|
|
240
|
-
out = out.slice(0, n);
|
|
241
|
-
}
|
|
242
|
-
return out;
|
|
243
|
-
}
|
|
244
|
-
function describeMw(mw) {
|
|
245
|
-
return mw.name ? `"${mw.name}"` : "an anonymous middleware";
|
|
246
|
-
}
|
|
247
|
-
export class Honey {
|
|
248
|
-
/* view state — what routes registered through this handle capture */
|
|
249
|
-
_basePath;
|
|
250
|
-
_chain;
|
|
251
|
-
_chainMeta;
|
|
252
|
-
_contextValues;
|
|
253
|
-
_graph;
|
|
254
|
-
/** the `use(mw)` call that created this chain — null on the root handle */
|
|
255
|
-
_node;
|
|
256
|
-
constructor(opts) {
|
|
257
|
-
this._basePath = "/";
|
|
258
|
-
this._chain = [];
|
|
259
|
-
this._chainMeta = null;
|
|
260
|
-
this._contextValues = null;
|
|
261
|
-
this._graph = opts?.graph ?? createGraph();
|
|
262
|
-
this._node = null;
|
|
263
|
-
}
|
|
264
|
-
get _s() {
|
|
265
|
-
return this._graph.settings;
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* @internal — read by codegen through `metaSpecOf()`: the declared policy and every mounted
|
|
269
|
-
* sub-app's, unmerged. Merged on read, so `metaSpec()` may come before or after `route(sub)`;
|
|
270
|
-
* the merge lives with codegen so a served app never loads it.
|
|
271
|
-
*/
|
|
272
|
-
get _metaSpecSource() {
|
|
273
|
-
return { absorbed: this._graph.absorbedMetaSpecs, declared: this._graph.metaSpec };
|
|
274
|
-
}
|
|
275
|
-
/** @internal — read by codegen and OpenAPI */
|
|
276
|
-
get _errorFactory() {
|
|
277
|
-
return this._graph.settings.errorFactory;
|
|
278
|
-
}
|
|
279
|
-
set _errorFactory(factory) {
|
|
280
|
-
this._graph.settings.errorFactory = factory;
|
|
281
|
-
this._bumpEpoch();
|
|
282
|
-
}
|
|
283
|
-
/** @internal — read by OpenAPI */
|
|
284
|
-
get _errorSchema() {
|
|
285
|
-
return this._graph.settings.errorSchema;
|
|
286
|
-
}
|
|
287
|
-
/** @internal — read by OpenAPI */
|
|
288
|
-
get _customErrorSchema() {
|
|
289
|
-
return this._graph.settings.customErrorSchema;
|
|
290
|
-
}
|
|
291
|
-
get _root() {
|
|
292
|
-
return this._graph.root;
|
|
293
|
-
}
|
|
294
|
-
get _realtimeBus() {
|
|
295
|
-
return this._graph.realtimeBus;
|
|
296
|
-
}
|
|
297
|
-
/** A new handle on the same graph, carrying this handle's view. */
|
|
298
|
-
_derive(chain = this._chain) {
|
|
299
|
-
const next = new Honey({ graph: this._graph });
|
|
300
|
-
next._basePath = this._basePath;
|
|
301
|
-
next._chain = chain;
|
|
302
|
-
next._chainMeta = this._chainMeta;
|
|
303
|
-
next._contextValues = this._contextValues;
|
|
304
|
-
next._node = this._node;
|
|
305
|
-
return next;
|
|
306
|
-
}
|
|
307
|
-
/** This handle registers, mounts or serves — every `use()` on its way here is in use. */
|
|
308
|
-
_markUsed() {
|
|
309
|
-
let node = this._node;
|
|
310
|
-
while (node !== null && !node.used) {
|
|
311
|
-
node.used = true;
|
|
312
|
-
node = node.parent;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
/** @internal — used by RouteBuilder for pre-filtered error factory */
|
|
316
|
-
get _factory() {
|
|
317
|
-
return this._graph.settings.errorFactory;
|
|
318
|
-
}
|
|
319
|
-
/** @internal — used by runtime error boundary */
|
|
320
|
-
get _boundaryKey() {
|
|
321
|
-
return this._graph.settings.defaultBoundaryKey;
|
|
322
|
-
}
|
|
323
|
-
/** Convert an unknown thrown value to a plain error Response — no boundary, no onError. */
|
|
324
|
-
_toErrorResponse(thrown) {
|
|
325
|
-
const error = thrown instanceof HoneyError
|
|
326
|
-
? thrown
|
|
327
|
-
: new HoneyError({
|
|
328
|
-
cause: thrown,
|
|
329
|
-
errorKey: EK.internal_server_error,
|
|
330
|
-
status: SK.internal_server_error,
|
|
331
|
-
});
|
|
332
|
-
const s = this._s;
|
|
333
|
-
return createErrorResponse(error, s.errorFormatter, s.customErrorFormatter);
|
|
334
|
-
}
|
|
335
|
-
_createBoundaryError(errorKey, cause, fac) {
|
|
336
|
-
const factory = fac;
|
|
337
|
-
const factoryFn = factory?.[errorKey];
|
|
338
|
-
if (factoryFn) {
|
|
339
|
-
/* check if this is a custom schema error via ERROR_META — boundary must use standard errors only */
|
|
340
|
-
const meta = factory?.[ERROR_META];
|
|
341
|
-
if (meta?.[errorKey]?.schema) {
|
|
342
|
-
/* custom schema error — cannot be used as boundary, fall through to manual construction */
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
return factoryFn({ cause });
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
return new HoneyError({
|
|
349
|
-
cause,
|
|
350
|
-
errorKey,
|
|
351
|
-
status: SK.internal_server_error,
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
_createError(errorKey, statusKey) {
|
|
355
|
-
const factory = this._s.errorFactory;
|
|
356
|
-
const factoryFn = factory?.[errorKey];
|
|
357
|
-
if (factoryFn) {
|
|
358
|
-
return factoryFn();
|
|
359
|
-
}
|
|
360
|
-
return new HoneyError({ errorKey, status: statusKey });
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* The i18n-resolved copy of `honeyError`: message from the locale's template for its
|
|
364
|
-
* errorKey, field paths from the locale's field names. Returns `honeyError` itself when
|
|
365
|
-
* i18n is not configured or nothing matches. Never mutates it: an error instance can be
|
|
366
|
-
* thrown again, on another request, in another locale.
|
|
367
|
-
*/
|
|
368
|
-
async _resolveI18n(honeyError, ctx, env, request, log) {
|
|
369
|
-
const i18n = this._s.errorI18n;
|
|
370
|
-
if (!i18n)
|
|
371
|
-
return honeyError;
|
|
372
|
-
try {
|
|
373
|
-
const locale = await i18n.resolveLocale({
|
|
374
|
-
cookies: ctx.cookies,
|
|
375
|
-
env,
|
|
376
|
-
headers: ctx.headers,
|
|
377
|
-
params: ctx.params,
|
|
378
|
-
req: request,
|
|
379
|
-
search: ctx.search,
|
|
380
|
-
});
|
|
381
|
-
await loadHoneyFeature("i18n");
|
|
382
|
-
return getI18nRuntime().translateError(honeyError, i18n, locale);
|
|
383
|
-
}
|
|
384
|
-
catch (e) {
|
|
385
|
-
log?.warn?.({ err: e }, "i18n resolution failed");
|
|
386
|
-
return honeyError;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* The error boundary of every `next()`: turns what a handler or middleware threw into the
|
|
391
|
-
* Response that replaces it, so every middleware around it sees a Response. Never rejects.
|
|
392
|
-
*/
|
|
393
|
-
_convertError(thrown, ctx) {
|
|
394
|
-
const fc = ctx._rq;
|
|
395
|
-
const plan = fc?.plan;
|
|
396
|
-
if (fc === null || fc === undefined || plan === null || plan === undefined) {
|
|
397
|
-
setCtxError(ctx, asHoneyError(thrown));
|
|
398
|
-
return this._safeErrorResponse(thrown);
|
|
399
|
-
}
|
|
400
|
-
const fallback = (e) => {
|
|
401
|
-
fc.log?.warn?.({ err: e }, "error response failed");
|
|
402
|
-
setCtxError(ctx, asHoneyError(thrown));
|
|
403
|
-
return this._safeErrorResponse(thrown);
|
|
404
|
-
};
|
|
405
|
-
try {
|
|
406
|
-
return this._resolveErrorResponse(thrown, plan, fc, ctx).catch(fallback);
|
|
407
|
-
}
|
|
408
|
-
catch (e) {
|
|
409
|
-
return fallback(e);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
/** Last resort when even the configured formatter throws. */
|
|
413
|
-
_safeErrorResponse(thrown) {
|
|
414
|
-
try {
|
|
415
|
-
return this._toErrorResponse(thrown instanceof HoneyError ? thrown : undefined);
|
|
416
|
-
}
|
|
417
|
-
catch {
|
|
418
|
-
return new Response(JSON.stringify({ error: { errorKey: EK.internal_server_error } }), {
|
|
419
|
-
headers: { "content-type": "application/json" },
|
|
420
|
-
status: 500,
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Convert thrown value into an error Response — resolves boundary wrapping, i18n
|
|
426
|
-
* translation, the onError callback, and error telemetry. `onResponse` telemetry is not
|
|
427
|
-
* fired here: the request fires it exactly once, on the way out.
|
|
428
|
-
*/
|
|
429
|
-
async _resolveErrorResponse(thrown, plan, fc, ctx) {
|
|
430
|
-
const { env, log, request, startTime, method, path } = fc;
|
|
431
|
-
const s = this._s;
|
|
432
|
-
const record = plan.r;
|
|
433
|
-
const fac = (record.fac ?? null);
|
|
434
|
-
let honeyError;
|
|
435
|
-
const boundaryKey = record.bek;
|
|
436
|
-
if (thrown instanceof HoneyError) {
|
|
437
|
-
/* framework-managed errorKeys (input/output validation, content negotiation, etc.) are always allowed
|
|
438
|
-
* regardless of the declared keys — users never declare them, the framework owns them. */
|
|
439
|
-
if (plan.enf && !FRAMEWORK_EKS.has(thrown.errorKey) && !record.ek.has(thrown.errorKey)) {
|
|
440
|
-
honeyError = boundaryKey
|
|
441
|
-
? this._createBoundaryError(boundaryKey, thrown, fac)
|
|
442
|
-
: new HoneyError({ cause: thrown, errorKey: EK.internal_server_error, status: SK.internal_server_error });
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
honeyError = thrown;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
else {
|
|
449
|
-
honeyError = boundaryKey
|
|
450
|
-
? this._createBoundaryError(boundaryKey, thrown, fac)
|
|
451
|
-
: new HoneyError({ cause: thrown, errorKey: EK.internal_server_error, status: SK.internal_server_error });
|
|
452
|
-
}
|
|
453
|
-
honeyError = await this._resolveI18n(honeyError, ctx, env, request, log);
|
|
454
|
-
if (s.onError) {
|
|
455
|
-
try {
|
|
456
|
-
const customResult = await s.onError(thrown, this._makeErrorCtx(fc));
|
|
457
|
-
if (customResult instanceof HoneyError) {
|
|
458
|
-
/* user-mapped boundary error — re-run i18n against new errorKey,
|
|
459
|
-
* then fall through to the default response path */
|
|
460
|
-
honeyError = await this._resolveI18n(customResult, ctx, env, request, log);
|
|
461
|
-
}
|
|
462
|
-
else if (customResult) {
|
|
463
|
-
safeFire(() => s.telemetry?.onError?.({ duration: performance.now() - startTime, error: honeyError, method, path }), log);
|
|
464
|
-
setCtxError(ctx, honeyError);
|
|
465
|
-
ctx._isErrorResponse = true;
|
|
466
|
-
return customResult;
|
|
467
|
-
}
|
|
468
|
-
/* customResult === undefined | void → fall through to default path */
|
|
469
|
-
}
|
|
470
|
-
catch (e) {
|
|
471
|
-
log?.warn?.({ err: e }, "onError failed");
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
safeFire(() => s.telemetry?.onError?.({ duration: performance.now() - startTime, error: honeyError, method, path }), log);
|
|
475
|
-
setCtxError(ctx, honeyError);
|
|
476
|
-
ctx._isErrorResponse = true;
|
|
477
|
-
return this._makeErrorCtx(fc).jsonFromError(honeyError);
|
|
478
|
-
}
|
|
479
|
-
basePath(prefix) {
|
|
480
|
-
const next = this._derive();
|
|
481
|
-
next._basePath = mergePath(this._basePath, prefix);
|
|
482
|
-
return next;
|
|
483
|
-
}
|
|
484
|
-
context(values) {
|
|
485
|
-
for (const key in values) {
|
|
486
|
-
if (RESERVED_CTX_KEYS.has(key)) {
|
|
487
|
-
throw new Error(`context() cannot set reserved key "${key}"`);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
const next = this._derive();
|
|
491
|
-
next._contextValues = this._contextValues ? { ...this._contextValues, ...values } : { ...values };
|
|
492
|
-
return next;
|
|
493
|
-
}
|
|
494
|
-
logger(logger) {
|
|
495
|
-
this._s.logger = logger;
|
|
496
|
-
return this;
|
|
497
|
-
}
|
|
498
|
-
outputValidation(mode) {
|
|
499
|
-
this._s.outputValidation = mode;
|
|
500
|
-
this._bumpEpoch();
|
|
501
|
-
return this;
|
|
502
|
-
}
|
|
503
|
-
trailingSlash(mode) {
|
|
504
|
-
this._s.trailingSlash = mode;
|
|
505
|
-
return this;
|
|
506
|
-
}
|
|
507
|
-
/**
|
|
508
|
-
* Request paths containing an encoded `/` or `\` (`%2F`, `%5C`) answer 400 by default.
|
|
509
|
-
* `"allow"` keeps them encoded in the path, so they decode into a param value
|
|
510
|
-
* (`/repos/:id` with `/repos/group%2Fproject` gives `id === "group/project"`). Only allow
|
|
511
|
-
* them when nothing downstream (a `proxy()` upstream, a file resolver) treats a decoded
|
|
512
|
-
* slash as a separator.
|
|
513
|
-
*/
|
|
514
|
-
encodedSlashes(mode) {
|
|
515
|
-
this._s.encodedSlashes = mode;
|
|
516
|
-
return this;
|
|
517
|
-
}
|
|
518
|
-
/**
|
|
519
|
-
* Which reverse proxies in front of the app to believe. Decides `ctx.ip`, `clientInfo()`,
|
|
520
|
-
* `ipRestrict`, and every other feature that needs the client's address, scheme or host.
|
|
521
|
-
*
|
|
522
|
-
* - `false` (default): the peer address the runtime reports is the client; `X-Forwarded-*`
|
|
523
|
-
* headers are ignored, since any client can send them.
|
|
524
|
-
* - a hop count: exactly that many proxies, each appending its peer to `X-Forwarded-For`
|
|
525
|
-
* (nginx `$proxy_add_x_forwarded_for`, most load balancers). The client is the entry the
|
|
526
|
-
* outermost proxy wrote.
|
|
527
|
-
* - addresses and CIDR ranges of your proxies: hops from those addresses are skipped; the
|
|
528
|
-
* first other address is the client.
|
|
529
|
-
*
|
|
530
|
-
* A hop count or list needs `import "@lovrozagar/honey/trust"` in the app entry.
|
|
531
|
-
*/
|
|
532
|
-
trustProxy(value) {
|
|
533
|
-
this._s.trust =
|
|
534
|
-
value === false || value === 0 ? TRUST_OFF : requireFeature("trust", "app.trustProxy()").compileTrust(value);
|
|
535
|
-
return this;
|
|
536
|
-
}
|
|
537
|
-
/** Strip a URL path prefix before route matching — boundary-safe (won't strip partial segments), paths without the prefix pass through unchanged */
|
|
538
|
-
stripPrefix(prefix) {
|
|
539
|
-
let normalized = prefix.replace(/\/+$/, "");
|
|
540
|
-
if (normalized.length > 0 && normalized.charCodeAt(0) !== 47) {
|
|
541
|
-
normalized = `/${normalized}`;
|
|
542
|
-
}
|
|
543
|
-
this._s.stripPrefix = normalized === "" || normalized === "/" ? null : normalized;
|
|
544
|
-
return this;
|
|
545
|
-
}
|
|
546
|
-
wsAdapter(adapter) {
|
|
547
|
-
this._s.wsAdapter = adapter;
|
|
548
|
-
return this;
|
|
549
|
-
}
|
|
550
|
-
/**
|
|
551
|
-
* Serve this app's OpenAPI document at `${path}.json`, `.yaml` and `.yml` (default
|
|
552
|
-
* `/openapi`), and optionally a docs UI. Each call serves its own document: two calls with
|
|
553
|
-
* different options (say an internal and a public profile) never share a cache. Documents
|
|
554
|
-
* are generated once per route change, serialized once, and sent with an `ETag` and
|
|
555
|
-
* `Cache-Control: no-cache`. Pass `enabled: false` (for example in production) to mount
|
|
556
|
-
* nothing while keeping the call in place.
|
|
557
|
-
*/
|
|
558
|
-
openapi(options) {
|
|
559
|
-
if (options.enabled === false)
|
|
560
|
-
return this;
|
|
561
|
-
const stem = options.path ?? "/openapi";
|
|
562
|
-
const epochOf = () => this._graph.epoch;
|
|
563
|
-
/* per call, not per app: each openapi() serves the document its own options describe */
|
|
564
|
-
const document = epochCached(epochOf, async () => {
|
|
565
|
-
await loadHoneyFeature("openapi");
|
|
566
|
-
return getOpenApiRuntime().generateOpenApi(this, {
|
|
567
|
-
filterRoutes: options.filterRoutes,
|
|
568
|
-
info: { description: options.description, title: options.title, version: options.version },
|
|
569
|
-
/* a served document is not an authoring moment — the check belongs to `honey generate` */
|
|
570
|
-
invalidate: "off",
|
|
571
|
-
profile: options.profile,
|
|
572
|
-
securitySchemes: options.securitySchemes,
|
|
573
|
-
});
|
|
574
|
-
});
|
|
575
|
-
/* the document first: it loads the runtime the serializers come from */
|
|
576
|
-
const json = epochCached(epochOf, async () => {
|
|
577
|
-
const doc = await document();
|
|
578
|
-
return getOpenApiRuntime().toServedArtifact(JSON.stringify(doc), "application/json");
|
|
579
|
-
});
|
|
580
|
-
const yaml = epochCached(epochOf, async () => {
|
|
581
|
-
const doc = await document();
|
|
582
|
-
const runtime = getOpenApiRuntime();
|
|
583
|
-
return runtime.toServedArtifact(runtime.toYaml(doc), "application/yaml; charset=utf-8");
|
|
584
|
-
});
|
|
585
|
-
const jsonPath = `${stem}.json`;
|
|
586
|
-
this._mountArtifact("openapi", jsonPath, json);
|
|
587
|
-
this._mountArtifact("openapi", `${stem}.yml`, yaml);
|
|
588
|
-
this._mountArtifact("openapi", `${stem}.yaml`, yaml);
|
|
589
|
-
if (options.docs) {
|
|
590
|
-
const docs = options.docs;
|
|
591
|
-
const specPath = mergePath(this._basePath, jsonPath);
|
|
592
|
-
const ui = async (ctx) => {
|
|
593
|
-
await loadHoneyFeature("openapi");
|
|
594
|
-
/* the browser asks for the URL it sees: a stripped prefix is part of it */
|
|
595
|
-
const prefix = this._s.stripPrefix ?? "";
|
|
596
|
-
return getOpenApiRuntime().docsUi(docs, prefix + specPath)(ctx);
|
|
597
|
-
};
|
|
598
|
-
/* without an explicit docsPath, a user route at /docs moves the UI to /reference */
|
|
599
|
-
const candidates = options.docsPath ? [options.docsPath] : ["/docs", "/reference"];
|
|
600
|
-
let mounted = "user";
|
|
601
|
-
let tried = candidates[0];
|
|
602
|
-
for (const p of candidates) {
|
|
603
|
-
tried = p;
|
|
604
|
-
mounted = this._mountInternalGet(p, ui);
|
|
605
|
-
if (mounted !== "user")
|
|
606
|
-
break;
|
|
607
|
-
}
|
|
608
|
-
if (mounted !== "mounted") {
|
|
609
|
-
const at = mergePath(this._basePath, tried);
|
|
610
|
-
throw new Error(mounted === "internal"
|
|
611
|
-
? `Honey.openapi({ docs }) cannot mount at ${at}: an earlier openapi() or manifest() serves it. Pass docsPath.`
|
|
612
|
-
: `Honey.openapi({ docs }) cannot mount at ${at}: a route already owns it. Pass docsPath.`);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
return this;
|
|
616
|
-
}
|
|
617
|
-
/**
|
|
618
|
-
* Serve the route manifest at `path` (default `/manifest.json`), filtered by the same
|
|
619
|
-
* visibility policy as the OpenAPI document. Cached per route change, sent with an `ETag`.
|
|
620
|
-
* `enabled: false` mounts nothing.
|
|
621
|
-
*/
|
|
622
|
-
manifest(options) {
|
|
623
|
-
if (options?.enabled === false)
|
|
624
|
-
return this;
|
|
625
|
-
const epochOf = () => this._graph.epoch;
|
|
626
|
-
const artifact = epochCached(epochOf, async () => {
|
|
627
|
-
await loadHoneyFeature("openapi");
|
|
628
|
-
const runtime = getOpenApiRuntime();
|
|
629
|
-
return runtime.toServedArtifact(JSON.stringify(await runtime.generateManifest(this)), "application/json");
|
|
630
|
-
});
|
|
631
|
-
this._mountArtifact("manifest", options?.path ?? "/manifest.json", artifact);
|
|
632
|
-
return this;
|
|
633
|
-
}
|
|
634
|
-
/** Mount a served document; a path another route or an earlier call owns is an error. */
|
|
635
|
-
_mountArtifact(owner, path, load) {
|
|
636
|
-
const result = this._mountInternalGet(path, async (ctx) => {
|
|
637
|
-
const c = ctx;
|
|
638
|
-
const artifact = await load();
|
|
639
|
-
return c.res.raw(getOpenApiRuntime().artifactResponse(c.req, artifact));
|
|
640
|
-
});
|
|
641
|
-
if (result === "mounted")
|
|
642
|
-
return;
|
|
643
|
-
const at = mergePath(this._basePath, path);
|
|
644
|
-
throw new Error(result === "internal"
|
|
645
|
-
? `Honey.${owner}() cannot mount GET ${at}: an earlier openapi() or manifest() serves it. Pass a different path.`
|
|
646
|
-
: `Honey.${owner}() cannot mount GET ${at}: a route already owns it. Pass a different path.`);
|
|
647
|
-
}
|
|
648
|
-
async serve(options) {
|
|
649
|
-
await loadHoneyFeature("serve");
|
|
650
|
-
return getServeRuntime()(this, options);
|
|
651
|
-
}
|
|
652
|
-
/**
|
|
653
|
-
* Mount an internal GET (spec, docs, manifest). Reports who owns the path when it cannot:
|
|
654
|
-
* a user route (`"user"`) or an earlier internal mount (`"internal"`). Only an exact leaf
|
|
655
|
-
* counts: a `/:slug` or `/*rest` route does not block a static internal path, which wins
|
|
656
|
-
* over it on precedence, with backtracking, in both the runtime and a loaded tree.
|
|
657
|
-
*/
|
|
658
|
-
_mountInternalGet(path, fn) {
|
|
659
|
-
const fullPath = mergePath(this._basePath, path);
|
|
660
|
-
const segments = parsePattern(fullPath);
|
|
661
|
-
const id = routeId("GET", fullPath);
|
|
662
|
-
const g = this._graph;
|
|
663
|
-
const existing = g.records.get(id);
|
|
664
|
-
if (existing)
|
|
665
|
-
return existing._skip === true ? "internal" : "user";
|
|
666
|
-
const leaf = findLeaf(g.root, segments, "GET");
|
|
667
|
-
if (leaf !== undefined && leaf !== id)
|
|
668
|
-
return "user";
|
|
669
|
-
if (findLeaf(g.root, segments, "ALL") !== undefined)
|
|
670
|
-
return "user";
|
|
671
|
-
/* a leaf the loaded tree holds for a user route the app has yet to register */
|
|
672
|
-
if (leaf === id && g.loaded?.has(id))
|
|
673
|
-
return "user";
|
|
674
|
-
const routeHandler = {
|
|
675
|
-
_skip: true,
|
|
676
|
-
bek: null,
|
|
677
|
-
cm: [...this._chain],
|
|
678
|
-
cv: this._contextValues,
|
|
679
|
-
dk: new Set(),
|
|
680
|
-
ek: new Set(),
|
|
681
|
-
fn: (ctx) => fn(ctx),
|
|
682
|
-
iv: null,
|
|
683
|
-
mt: null,
|
|
684
|
-
mw: [],
|
|
685
|
-
os: null,
|
|
686
|
-
ov: null,
|
|
687
|
-
own: g.settings,
|
|
688
|
-
rb: null,
|
|
689
|
-
rm: [],
|
|
690
|
-
xm: null,
|
|
691
|
-
};
|
|
692
|
-
this._markUsed();
|
|
693
|
-
this._addRoute("GET", segments, routeHandler);
|
|
694
|
-
return "mounted";
|
|
695
|
-
}
|
|
696
|
-
_bumpEpoch() {
|
|
697
|
-
this._graph.epoch++;
|
|
698
|
-
}
|
|
699
|
-
/** Place `id` at every leaf of `segments`, copying a shared (loaded) root first. */
|
|
700
|
-
_placeLeaves(segments, method, id) {
|
|
701
|
-
const g = this._graph;
|
|
702
|
-
const variants = leafVariants(segments);
|
|
703
|
-
if (variants.every((v) => findLeaf(g.root, v, method) === id))
|
|
704
|
-
return;
|
|
705
|
-
if (g.rootShared) {
|
|
706
|
-
g.root = cloneTree(g.root);
|
|
707
|
-
g.rootShared = false;
|
|
708
|
-
}
|
|
709
|
-
for (const v of variants)
|
|
710
|
-
insertLeaf(g.root, v, method, id);
|
|
711
|
-
}
|
|
712
|
-
/**
|
|
713
|
-
* @internal — register one route record. Over a loaded tree the record binds to the leaf
|
|
714
|
-
* the tree already holds for its id; a root wildcard the tree lacks becomes the gateway
|
|
715
|
-
* catch-all; any other unknown route marks the tree as stale (reported at finalize).
|
|
716
|
-
*/
|
|
717
|
-
_addRoute(method, segments, record) {
|
|
718
|
-
const pattern = canonical(segments);
|
|
719
|
-
const id = routeId(method, pattern);
|
|
720
|
-
const g = this._graph;
|
|
721
|
-
if (g.records.has(id) || (isRootWildcard(segments) && g.catchAll.has(method))) {
|
|
722
|
-
throw new Error(`Duplicate route: ${id}`);
|
|
723
|
-
}
|
|
724
|
-
record.id = id;
|
|
725
|
-
record.rp = pattern;
|
|
726
|
-
if (g.loaded !== null && record._skip !== true) {
|
|
727
|
-
const known = leafVariants(segments).every((v) => findLeaf(g.root, v, method) === id);
|
|
728
|
-
if (!known) {
|
|
729
|
-
if (isRootWildcard(segments)) {
|
|
730
|
-
record.ca = true;
|
|
731
|
-
g.catchAll.set(method, record);
|
|
732
|
-
this._bumpEpoch();
|
|
733
|
-
return;
|
|
734
|
-
}
|
|
735
|
-
g.unexpected.add(id);
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
this._placeLeaves(segments, method, id);
|
|
739
|
-
g.records.set(id, record);
|
|
740
|
-
this._bumpEpoch();
|
|
741
|
-
}
|
|
742
|
-
/** @internal — register one websocket (or realtime) route record. */
|
|
743
|
-
_addWsRoute(segments, record) {
|
|
744
|
-
const pattern = canonical(segments);
|
|
745
|
-
const id = routeId("WS", pattern);
|
|
746
|
-
const g = this._graph;
|
|
747
|
-
if (g.wsRecords.has(id))
|
|
748
|
-
throw new Error(`Duplicate WebSocket route: ${pattern}`);
|
|
749
|
-
record.id = id;
|
|
750
|
-
record.rp = pattern;
|
|
751
|
-
if (g.loaded !== null && leafVariants(segments).some((v) => findLeaf(g.root, v, "WS") !== id)) {
|
|
752
|
-
g.unexpected.add(id);
|
|
753
|
-
}
|
|
754
|
-
this._placeLeaves(segments, "WS", id);
|
|
755
|
-
g.wsRecords.set(id, record);
|
|
756
|
-
g.hasWs = true;
|
|
757
|
-
this._bumpEpoch();
|
|
758
|
-
}
|
|
759
|
-
/**
|
|
760
|
-
* Resolve what this graph serves: registered records, live records carried by a loaded
|
|
761
|
-
* snapshot, and delegated leaves the gateway catch-all serves. Then resolve every record —
|
|
762
|
-
* its full chain (`chain → scoped → route → input validation`), error keys, meta and error
|
|
763
|
-
* factory — and compile it once. Re-runs whenever the graph's epoch moved. Throws, naming
|
|
764
|
-
* every route, when a loaded tree and the registered routes disagree (the generated file is
|
|
765
|
-
* stale), and when a `use(mw)` handle or a route builder was left unused (the middleware or
|
|
766
|
-
* route would silently be missing).
|
|
767
|
-
*/
|
|
768
|
-
_finalize() {
|
|
769
|
-
const g = this._graph;
|
|
770
|
-
if (g.final !== null && g.final.epoch === g.epoch)
|
|
771
|
-
return g.final;
|
|
772
|
-
this._assertNothingDiscarded();
|
|
773
|
-
const byId = new Map();
|
|
774
|
-
const wsById = new Map();
|
|
775
|
-
for (const [id, r] of g.records)
|
|
776
|
-
byId.set(id, r);
|
|
777
|
-
for (const [id, r] of g.wsRecords)
|
|
778
|
-
wsById.set(id, r);
|
|
779
|
-
const problems = [];
|
|
780
|
-
if (g.loaded !== null) {
|
|
781
|
-
for (const id of g.unexpected)
|
|
782
|
-
problems.push(`${id} is registered but missing from the loaded route tree`);
|
|
783
|
-
let delegated = 0;
|
|
784
|
-
for (const [id, entry] of g.loaded) {
|
|
785
|
-
const { method, pattern } = splitRouteId(id);
|
|
786
|
-
if (method === "WS") {
|
|
787
|
-
if (wsById.has(id))
|
|
788
|
-
continue;
|
|
789
|
-
if (entry.h !== undefined) {
|
|
790
|
-
const live = copyRecord(entry.h);
|
|
791
|
-
live.id = id;
|
|
792
|
-
live.rp = pattern;
|
|
793
|
-
wsById.set(id, live);
|
|
794
|
-
}
|
|
795
|
-
/* a websocket leaf nothing serves is not upgraded: the request falls through to HTTP */
|
|
796
|
-
continue;
|
|
797
|
-
}
|
|
798
|
-
if (byId.has(id))
|
|
799
|
-
continue;
|
|
800
|
-
if (entry.h !== undefined) {
|
|
801
|
-
const live = copyRecord(entry.h);
|
|
802
|
-
live.id = id;
|
|
803
|
-
live.rp = pattern;
|
|
804
|
-
byId.set(id, live);
|
|
805
|
-
continue;
|
|
806
|
-
}
|
|
807
|
-
const ca = g.catchAll.get(method) ?? g.catchAll.get("ALL");
|
|
808
|
-
if (ca !== undefined) {
|
|
809
|
-
const dl = copyRecord(ca);
|
|
810
|
-
dl.dl = true;
|
|
811
|
-
dl.id = id;
|
|
812
|
-
dl.rp = pattern;
|
|
813
|
-
dl.xm = entry.mt ? { ...entry.mt } : null;
|
|
814
|
-
dl.iv = entry.iv ?? null;
|
|
815
|
-
dl.os = entry.os ?? null;
|
|
816
|
-
dl.dk = new Set([...(ca.dk ?? []), ...(entry.ek ?? [])]);
|
|
817
|
-
if (entry.bek !== undefined && entry.bek !== null)
|
|
818
|
-
dl.rb = entry.bek;
|
|
819
|
-
byId.set(id, dl);
|
|
820
|
-
delegated++;
|
|
821
|
-
continue;
|
|
822
|
-
}
|
|
823
|
-
/* no handler anywhere: documented (served specs list it) but answered with 404 */
|
|
824
|
-
byId.set(id, {
|
|
825
|
-
bek: null,
|
|
826
|
-
cm: [],
|
|
827
|
-
dk: new Set(entry.ek ?? []),
|
|
828
|
-
dl: true,
|
|
829
|
-
ek: new Set(),
|
|
830
|
-
fn: NOT_SERVED,
|
|
831
|
-
id,
|
|
832
|
-
iv: entry.iv ?? null,
|
|
833
|
-
mt: null,
|
|
834
|
-
mw: [],
|
|
835
|
-
os: entry.os ?? null,
|
|
836
|
-
rb: entry.bek ?? null,
|
|
837
|
-
rm: [],
|
|
838
|
-
rp: pattern,
|
|
839
|
-
xm: entry.mt ? { ...entry.mt } : null,
|
|
840
|
-
});
|
|
841
|
-
}
|
|
842
|
-
if (g.catchAll.size > 0 && delegated === 0) {
|
|
843
|
-
for (const id of g.catchAll.keys()) {
|
|
844
|
-
problems.push(`ALL root wildcard (${g.catchAll.get(id)?.id}) is missing from the loaded route tree`);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
if (problems.length > 0) {
|
|
849
|
-
/* generation rewrites this file from the registered routes; serving it stale is the error */
|
|
850
|
-
if (g.unloadIfStale) {
|
|
851
|
-
this._unloadTree();
|
|
852
|
-
return this._finalize();
|
|
853
|
-
}
|
|
854
|
-
throw new Error(`Route tree out of date — regenerate it (\`honey generate\`):\n ${problems.join("\n ")}`);
|
|
855
|
-
}
|
|
856
|
-
const convert = (thrown, ctx) => this._convertError(thrown, ctx);
|
|
857
|
-
const plans = new Map();
|
|
858
|
-
const wsPlans = new Map();
|
|
859
|
-
const statics = Object.create(null);
|
|
860
|
-
const userChains = [];
|
|
861
|
-
for (const [id, r] of byId) {
|
|
862
|
-
const plan = this._planRoute(r, convert);
|
|
863
|
-
plans.set(id, plan);
|
|
864
|
-
if (r._skip !== true && r.dl !== true)
|
|
865
|
-
userChains.push(r.cm ?? []);
|
|
866
|
-
const { method, segments } = patternOf(id);
|
|
867
|
-
for (const v of leafVariants(segments)) {
|
|
868
|
-
if (isStaticPattern(v))
|
|
869
|
-
statics[`${method} ${canonical(v)}`] = plan;
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
for (const [id, r] of wsById) {
|
|
873
|
-
wsPlans.set(id, this._planWs(r, convert));
|
|
874
|
-
userChains.push(r.cm ?? []);
|
|
875
|
-
}
|
|
876
|
-
/* an app with no routes (static files only) has nothing but the handles that serve it */
|
|
877
|
-
if (userChains.length === 0)
|
|
878
|
-
for (const chain of g.served)
|
|
879
|
-
userChains.push(chain);
|
|
880
|
-
const miss = this._planMiss(commonPrefix(userChains), convert);
|
|
881
|
-
g.final = { byId, convert, epoch: g.epoch, miss, plans, statics, wsById, wsPlans };
|
|
882
|
-
return g.final;
|
|
883
|
-
}
|
|
884
|
-
/** `use(mw)` handles that never registered, mounted or served, and builders without `.handler()`. */
|
|
885
|
-
_assertNothingDiscarded() {
|
|
886
|
-
const g = this._graph;
|
|
887
|
-
const problems = [];
|
|
888
|
-
for (const node of g.chains) {
|
|
889
|
-
if (node.used)
|
|
890
|
-
continue;
|
|
891
|
-
problems.push(`app.use(${describeMw(node.mw)}) returned a handle that never registers a route, so the middleware runs nowhere. ` +
|
|
892
|
-
"use() does not change the handle it is called on: keep the returned value and register routes on it " +
|
|
893
|
-
"(`const authed = app.use(auth); authed.get(...)`), or chain it (`honey().use(mw).get(...)`).");
|
|
894
|
-
}
|
|
895
|
-
for (const p of g.pending) {
|
|
896
|
-
problems.push(`${p.id} was declared but never got a .handler() (or .proxy()), so the route is not registered.`);
|
|
897
|
-
}
|
|
898
|
-
if (problems.length > 0)
|
|
899
|
-
throw new Error(`honey: ${problems.join("\n ")}`);
|
|
900
|
-
}
|
|
901
|
-
/**
|
|
902
|
-
* Resolve a record against this graph: chain = captured chain, the scopes that cover its
|
|
903
|
-
* pattern (behind a request-path check where only some of its paths are inside), then its
|
|
904
|
-
* route middleware. Error keys and contributed meta come from that chain, in run order.
|
|
905
|
-
* The error factory, default errors and boundary come from the app the route was
|
|
906
|
-
* registered on when that app has a factory, else from this one.
|
|
907
|
-
*/
|
|
908
|
-
_resolve(r, everyScope = false) {
|
|
909
|
-
const s = this._graph.settings;
|
|
910
|
-
const own = r.own ?? s;
|
|
911
|
-
const src = own.errorFactory !== null ? own : s;
|
|
912
|
-
const segments = parsePattern(r.rp ?? "/");
|
|
913
|
-
const scoped = [];
|
|
914
|
-
for (const entry of this._graph.scoped) {
|
|
915
|
-
if (everyScope) {
|
|
916
|
-
scoped.push(entry.guard);
|
|
917
|
-
continue;
|
|
918
|
-
}
|
|
919
|
-
const cov = scopeCoverage(segments, entry.segs);
|
|
920
|
-
if (cov === "all")
|
|
921
|
-
scoped.push(entry.mw);
|
|
922
|
-
else if (cov === "some")
|
|
923
|
-
scoped.push(entry.guard);
|
|
924
|
-
}
|
|
925
|
-
const mw = [...this._graph.global.values(), ...(r.cm ?? []), ...scoped, ...(r.rm ?? [])];
|
|
926
|
-
/* app defaults, then what the middleware declares in run order, then the route's own */
|
|
927
|
-
const ek = new Set(src.defaultErrorKeys);
|
|
928
|
-
for (const m of mw) {
|
|
929
|
-
const errs = errorsOf(m);
|
|
930
|
-
if (errs)
|
|
931
|
-
for (const k of errs)
|
|
932
|
-
ek.add(k);
|
|
933
|
-
}
|
|
934
|
-
if (r.dk !== undefined)
|
|
935
|
-
for (const k of r.dk)
|
|
936
|
-
ek.add(k);
|
|
937
|
-
const bek = r.rb ?? src.defaultBoundaryKey;
|
|
938
|
-
if (bek !== null)
|
|
939
|
-
ek.add(bek);
|
|
940
|
-
const enf = ek.size > 0;
|
|
941
|
-
const fac = src.errorFactory;
|
|
942
|
-
let ef = fac;
|
|
943
|
-
if (fac !== null && enf) {
|
|
944
|
-
const subset = Object.create(null);
|
|
945
|
-
for (const key of ek) {
|
|
946
|
-
if (key in fac)
|
|
947
|
-
subset[key] = fac[key];
|
|
948
|
-
}
|
|
949
|
-
ef = Object.freeze(subset);
|
|
950
|
-
}
|
|
951
|
-
r.mw = mw;
|
|
952
|
-
r.ek = ek;
|
|
953
|
-
r.bek = bek;
|
|
954
|
-
r.fac = fac;
|
|
955
|
-
r.ef = ef;
|
|
956
|
-
r.mt = mergeContributedMeta(collectMiddlewareMeta([mw]), r.xm ? { ...r.xm } : null);
|
|
957
|
-
/* the serving app's taps win; a route from another app keeps that app's for the rest */
|
|
958
|
-
const taps = own === s || own.taps === null ? s.taps : s.taps === null ? own.taps : new Map([...own.taps, ...s.taps]);
|
|
959
|
-
return { enf, own, taps };
|
|
960
|
-
}
|
|
961
|
-
/** Wrap each middleware for `telemetry.onMiddleware`, once per finalize. */
|
|
962
|
-
_timed(chain) {
|
|
963
|
-
const onMw = this._graph.settings.telemetry?.onMiddleware;
|
|
964
|
-
if (onMw === undefined)
|
|
965
|
-
return chain;
|
|
966
|
-
const log = this._graph.settings.logger ?? undefined;
|
|
967
|
-
return chain.map((m) => timedMiddleware(m, onMw, log));
|
|
968
|
-
}
|
|
969
|
-
_planRoute(r, convert) {
|
|
970
|
-
const { enf, own, taps } = this._resolve(r);
|
|
971
|
-
const s = this._graph.settings;
|
|
972
|
-
const ovm = own.outputValidation ?? s.outputValidation ?? "off";
|
|
973
|
-
const base = this._timed(r.mw);
|
|
974
|
-
const chain = [...base];
|
|
975
|
-
/* a delegated route's schemas document it; the body is forwarded, never validated here */
|
|
976
|
-
const iv = r.iv;
|
|
977
|
-
if (iv && r.dl !== true) {
|
|
978
|
-
chain.push((ctx, next) => validateInput(iv, ctx["req"], ctx["params"], ctx).then((validated) => {
|
|
979
|
-
ctx["input"] = validated;
|
|
980
|
-
return next();
|
|
981
|
-
}));
|
|
982
|
-
}
|
|
983
|
-
const fn = r.fn;
|
|
984
|
-
const handler = r.os && ovm !== "off"
|
|
985
|
-
? (ctx) => {
|
|
986
|
-
const res = fn(ctx);
|
|
987
|
-
if (!shouldValidateOutput(ovm))
|
|
988
|
-
return res;
|
|
989
|
-
return res instanceof Promise
|
|
990
|
-
? res.then((v) => this._validateOutput(r, ctx, v))
|
|
991
|
-
: this._validateOutput(r, ctx, res);
|
|
992
|
-
}
|
|
993
|
-
: fn;
|
|
994
|
-
return {
|
|
995
|
-
cv: r.cv ?? null,
|
|
996
|
-
enf,
|
|
997
|
-
pf: null,
|
|
998
|
-
pfChain: base,
|
|
999
|
-
r,
|
|
1000
|
-
rt: null,
|
|
1001
|
-
run: compileChain(chain, handler, convert),
|
|
1002
|
-
taps,
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
_planWs(r, convert) {
|
|
1006
|
-
const { enf } = this._resolve(r);
|
|
1007
|
-
const rt = this._graph.realtimeRoutes.get(r.rp) ?? null;
|
|
1008
|
-
const chain = this._timed(r.mw);
|
|
1009
|
-
const iv = r.iv;
|
|
1010
|
-
if (iv) {
|
|
1011
|
-
chain.push((ctx, next) => validateInput(iv, ctx["req"], ctx["params"], ctx).then((validated) => {
|
|
1012
|
-
ctx["input"] = validated;
|
|
1013
|
-
return next();
|
|
1014
|
-
}));
|
|
1015
|
-
}
|
|
1016
|
-
const terminal = rt !== null
|
|
1017
|
-
? (ctx) => this._realtimeUpgrade(ctx, rt)
|
|
1018
|
-
: (ctx) => this._wsUpgrade(ctx);
|
|
1019
|
-
return {
|
|
1020
|
-
cv: r.cv ?? null,
|
|
1021
|
-
enf,
|
|
1022
|
-
pf: null,
|
|
1023
|
-
pfChain: [],
|
|
1024
|
-
r,
|
|
1025
|
-
rt,
|
|
1026
|
-
run: compileChain(chain, terminal, convert),
|
|
1027
|
-
taps: null,
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
|
-
/**
|
|
1031
|
-
* 404 and 405 run through the same pipeline as a route: the middleware every route of this
|
|
1032
|
-
* graph starts with (with no routes: every handle that served a request), then every scope
|
|
1033
|
-
* (behind its request-path check), then the 404/405 answer. So a logger or CORS policy on the whole app also covers unknown paths, and a
|
|
1034
|
-
* scope's guard answers before "not found" leaks what exists under it.
|
|
1035
|
-
*/
|
|
1036
|
-
_planMiss(prefix, convert) {
|
|
1037
|
-
const r = {
|
|
1038
|
-
bek: null,
|
|
1039
|
-
cm: prefix,
|
|
1040
|
-
dk: new Set(),
|
|
1041
|
-
ek: new Set(),
|
|
1042
|
-
fn: (ctx) => {
|
|
1043
|
-
const fc = ctx._rq;
|
|
1044
|
-
return fc.allowed !== null ? this._make405(fc, fc.allowed) : this._make404(fc);
|
|
1045
|
-
},
|
|
1046
|
-
mt: null,
|
|
1047
|
-
mw: [],
|
|
1048
|
-
rm: [],
|
|
1049
|
-
rp: "",
|
|
1050
|
-
xm: null,
|
|
1051
|
-
};
|
|
1052
|
-
/* every scope, each behind its path check — the request path decides */
|
|
1053
|
-
const { enf } = this._resolve(r, true);
|
|
1054
|
-
return {
|
|
1055
|
-
cv: null,
|
|
1056
|
-
enf,
|
|
1057
|
-
pf: null,
|
|
1058
|
-
pfChain: [],
|
|
1059
|
-
r,
|
|
1060
|
-
rt: null,
|
|
1061
|
-
run: compileChain(this._timed(r.mw), r.fn, convert),
|
|
1062
|
-
taps: null,
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
/**
|
|
1066
|
-
* @internal — every served route as one entry per leaf (an optional param yields two
|
|
1067
|
-
* paths), in tree order. Codegen and OpenAPI read routes through this.
|
|
1068
|
-
*/
|
|
1069
|
-
_collectRoutes(includeSkipped = false) {
|
|
1070
|
-
const final = this._finalize();
|
|
1071
|
-
const out = [];
|
|
1072
|
-
forEachLeaf(this._root, (method, path, id) => {
|
|
1073
|
-
if (method === "WS")
|
|
1074
|
-
return;
|
|
1075
|
-
const handler = final.byId.get(id);
|
|
1076
|
-
if (handler === undefined)
|
|
1077
|
-
return;
|
|
1078
|
-
if (handler._skip && !includeSkipped)
|
|
1079
|
-
return;
|
|
1080
|
-
out.push({ handler, method, path });
|
|
1081
|
-
});
|
|
1082
|
-
return out;
|
|
1083
|
-
}
|
|
1084
|
-
/** @internal — websocket routes, one entry per leaf. */
|
|
1085
|
-
_collectWsRoutes() {
|
|
1086
|
-
const final = this._finalize();
|
|
1087
|
-
const out = [];
|
|
1088
|
-
forEachLeaf(this._root, (method, path, id) => {
|
|
1089
|
-
if (method !== "WS")
|
|
1090
|
-
return;
|
|
1091
|
-
const handler = final.wsById.get(id);
|
|
1092
|
-
if (handler !== undefined)
|
|
1093
|
-
out.push({ handler, path });
|
|
1094
|
-
});
|
|
1095
|
-
return out;
|
|
1096
|
-
}
|
|
1097
|
-
/** @internal — the route-graph epoch; served documents cache against it. */
|
|
1098
|
-
get _epoch() {
|
|
1099
|
-
return this._graph.epoch;
|
|
1100
|
-
}
|
|
1101
|
-
defaultErrorFormatter(schemaOrFn, maybeFn) {
|
|
1102
|
-
const s = this._s;
|
|
1103
|
-
if (typeof schemaOrFn === "function") {
|
|
1104
|
-
s.errorSchema = null;
|
|
1105
|
-
s.errorFormatter = schemaOrFn;
|
|
1106
|
-
}
|
|
1107
|
-
else {
|
|
1108
|
-
s.errorSchema = schemaOrFn;
|
|
1109
|
-
const mapper = maybeFn;
|
|
1110
|
-
s.errorFormatter = (error) => mapper(error);
|
|
1111
|
-
}
|
|
1112
|
-
return this;
|
|
1113
|
-
}
|
|
1114
|
-
customErrorFormatter(schemaOrFn, maybeFn) {
|
|
1115
|
-
const s = this._s;
|
|
1116
|
-
if (typeof schemaOrFn === "function") {
|
|
1117
|
-
s.customErrorSchema = null;
|
|
1118
|
-
s.customErrorFormatter = schemaOrFn;
|
|
1119
|
-
}
|
|
1120
|
-
else {
|
|
1121
|
-
s.customErrorSchema = schemaOrFn;
|
|
1122
|
-
const mapper = maybeFn;
|
|
1123
|
-
s.customErrorFormatter = (error, data) => mapper(error, data);
|
|
1124
|
-
}
|
|
1125
|
-
return this;
|
|
1126
|
-
}
|
|
1127
|
-
errorI18n(config) {
|
|
1128
|
-
this._s.errorI18n = config;
|
|
1129
|
-
return this;
|
|
1130
|
-
}
|
|
1131
|
-
onError(handler) {
|
|
1132
|
-
this._s.onError = handler;
|
|
1133
|
-
return this;
|
|
1134
|
-
}
|
|
1135
|
-
onMethodNotAllowed(handler) {
|
|
1136
|
-
this._s.onMethodNotAllowed = handler;
|
|
1137
|
-
return this;
|
|
1138
|
-
}
|
|
1139
|
-
onNotFound(handler) {
|
|
1140
|
-
this._s.onNotFound = handler;
|
|
1141
|
-
return this;
|
|
1142
|
-
}
|
|
1143
|
-
/** Register a tap handler keyed by name — fires after successful handler response */
|
|
1144
|
-
tap(key, handler) {
|
|
1145
|
-
const s = this._s;
|
|
1146
|
-
if (s.taps === null)
|
|
1147
|
-
s.taps = new Map();
|
|
1148
|
-
s.taps.set(key, handler);
|
|
1149
|
-
this._bumpEpoch();
|
|
1150
|
-
return this;
|
|
1151
|
-
}
|
|
1152
|
-
telemetry(adapter) {
|
|
1153
|
-
this._s.telemetry = adapter;
|
|
1154
|
-
/* onMiddleware wraps the compiled chains */
|
|
1155
|
-
this._bumpEpoch();
|
|
1156
|
-
return this;
|
|
1157
|
-
}
|
|
1158
|
-
/**
|
|
1159
|
-
* Load a route tree — a generated `routes.gen.ts`, an `app.toRouteTree()` snapshot, or a
|
|
1160
|
-
* `mergeTree()` of either. The tree supplies the router topology; what each route runs
|
|
1161
|
-
* comes from the routes this app registers afterwards (bound by `RouteId`), from live
|
|
1162
|
-
* records in a snapshot, or — for leaves with neither — from a root wildcard the app
|
|
1163
|
-
* registers as its gateway catch-all. Must be called before any route is registered.
|
|
1164
|
-
*/
|
|
1165
|
-
routeTree(tree) {
|
|
1166
|
-
/* `honey generate` loads the app that imports the very file it is about to rewrite: a tree
|
|
1167
|
-
from an older honey is ignored there, and the routes the app registers are used instead */
|
|
1168
|
-
const generating = isCodegenProcess();
|
|
1169
|
-
if (generating) {
|
|
1170
|
-
try {
|
|
1171
|
-
assertTreeFormat(tree);
|
|
1172
|
-
}
|
|
1173
|
-
catch {
|
|
1174
|
-
return this;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
assertTreeFormat(tree);
|
|
1178
|
-
const g = this._graph;
|
|
1179
|
-
if (g.loaded !== null)
|
|
1180
|
-
throw new Error("routeTree() was already called on this app");
|
|
1181
|
-
for (const r of g.records.values()) {
|
|
1182
|
-
if (r._skip !== true)
|
|
1183
|
-
throw new Error(`routeTree() must be called before routes are registered (${r.id} is)`);
|
|
1184
|
-
}
|
|
1185
|
-
if (g.wsRecords.size > 0 || g.catchAll.size > 0) {
|
|
1186
|
-
throw new Error("routeTree() must be called before routes are registered");
|
|
1187
|
-
}
|
|
1188
|
-
forEachLeaf(tree.root, (method, path, id) => {
|
|
1189
|
-
if (!(id in tree.routes))
|
|
1190
|
-
throw new Error(`routeTree(): leaf ${method} ${path} has no route entry (${id})`);
|
|
1191
|
-
});
|
|
1192
|
-
const internals = [...g.records.values()];
|
|
1193
|
-
g.records.clear();
|
|
1194
|
-
g.root = freezeTree(tree.root);
|
|
1195
|
-
g.rootShared = true;
|
|
1196
|
-
g.loaded = new Map();
|
|
1197
|
-
g.unloadIfStale = generating;
|
|
1198
|
-
for (const id of Object.keys(tree.routes))
|
|
1199
|
-
g.loaded.set(id, { ...tree.routes[id] });
|
|
1200
|
-
if (hasWsLeaf(g.root))
|
|
1201
|
-
g.hasWs = true;
|
|
1202
|
-
/* internal routes mounted before the tree was loaded move onto the loaded topology */
|
|
1203
|
-
for (const r of internals) {
|
|
1204
|
-
const { method, segments } = patternOf(r.id);
|
|
1205
|
-
this._addRoute(method, segments, r);
|
|
1206
|
-
}
|
|
1207
|
-
this._bumpEpoch();
|
|
1208
|
-
return this;
|
|
1209
|
-
}
|
|
1210
|
-
/**
|
|
1211
|
-
* Snapshot this app's routes as a tree: a copy of the topology plus, per route, its data
|
|
1212
|
-
* and its live record. Internal routes (spec, docs, manifest) are left out. Loading the
|
|
1213
|
-
* snapshot into another app serves these records; nothing in it is shared with this app.
|
|
1214
|
-
* Each record keeps this app's settings (error factory, defaults, boundary) and context
|
|
1215
|
-
* values, and carries the scoped middleware that covers it here as part of its chain —
|
|
1216
|
-
* scopes themselves do not travel in a tree.
|
|
1217
|
-
*/
|
|
1218
|
-
toRouteTree() {
|
|
1219
|
-
this._markUsed();
|
|
1220
|
-
const final = this._finalize();
|
|
1221
|
-
const routes = Object.create(null);
|
|
1222
|
-
const keep = new Set();
|
|
1223
|
-
for (const [id, r] of final.byId) {
|
|
1224
|
-
if (r._skip)
|
|
1225
|
-
continue;
|
|
1226
|
-
keep.add(id);
|
|
1227
|
-
routes[id] = {
|
|
1228
|
-
bek: r.bek,
|
|
1229
|
-
ek: [...r.ek],
|
|
1230
|
-
h: this._bake(r),
|
|
1231
|
-
iv: r.iv ?? null,
|
|
1232
|
-
mt: r.mt,
|
|
1233
|
-
os: r.os ?? null,
|
|
1234
|
-
};
|
|
1235
|
-
}
|
|
1236
|
-
for (const [id, r] of final.wsById) {
|
|
1237
|
-
keep.add(id);
|
|
1238
|
-
routes[id] = { bek: r.bek, ek: [...r.ek], h: this._bake(r), iv: r.iv, mt: r.mt };
|
|
1239
|
-
}
|
|
1240
|
-
const root = createNode();
|
|
1241
|
-
forEachLeaf(this._root, (method, path, id) => {
|
|
1242
|
-
if (keep.has(id))
|
|
1243
|
-
insertLeaf(root, parsePattern(path), method, id);
|
|
1244
|
-
});
|
|
1245
|
-
return { meta: {}, root, routes, v: ROUTE_TREE_VERSION };
|
|
1246
|
-
}
|
|
1247
|
-
/** A resolved record as a standalone source: its resolved chain minus route middleware becomes its chain. */
|
|
1248
|
-
_bake(r) {
|
|
1249
|
-
const h = copyRecord(r);
|
|
1250
|
-
const rm = r.rm ?? [];
|
|
1251
|
-
/* app-wide middleware stays with the app; scopes travel as part of the chain */
|
|
1252
|
-
h.cm = r.mw.slice(this._graph.global.size, r.mw.length - rm.length);
|
|
1253
|
-
h.rm = [...rm];
|
|
1254
|
-
h.dk = new Set(r.ek);
|
|
1255
|
-
h.rb = r.bek;
|
|
1256
|
-
h.xm = r.mt ? { ...r.mt } : null;
|
|
1257
|
-
h.own = r.own ?? this._graph.settings;
|
|
1258
|
-
return h;
|
|
1259
|
-
}
|
|
1260
|
-
/**
|
|
1261
|
-
* @internal — generate-time: copy input/output schemas from `source` onto this app's
|
|
1262
|
-
* routes that lack them (a gateway serves its generated tree, which carries none).
|
|
1263
|
-
*/
|
|
1264
|
-
_overlaySchemas(source) {
|
|
1265
|
-
const g = this._graph;
|
|
1266
|
-
for (const [id, entry] of Object.entries(source.routes)) {
|
|
1267
|
-
const iv = entry.iv ?? entry.h?.iv ?? null;
|
|
1268
|
-
const os = entry.os ?? entry.h?.os ?? null;
|
|
1269
|
-
if (iv === null && os === null)
|
|
1270
|
-
continue;
|
|
1271
|
-
const record = g.records.get(id);
|
|
1272
|
-
if (record !== undefined) {
|
|
1273
|
-
record.iv ??= iv;
|
|
1274
|
-
record.os ??= os;
|
|
1275
|
-
continue;
|
|
1276
|
-
}
|
|
1277
|
-
const loaded = g.loaded?.get(id);
|
|
1278
|
-
if (loaded !== undefined) {
|
|
1279
|
-
loaded.iv ??= iv;
|
|
1280
|
-
loaded.os ??= os;
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
this._bumpEpoch();
|
|
1284
|
-
}
|
|
1285
|
-
/**
|
|
1286
|
-
* @internal — generate-time: the route tree a gateway serves. `source` is the merged
|
|
1287
|
-
* downstream tree (`codegen.mergeTree`); the gateway's own registered routes join it as
|
|
1288
|
-
* ordinary leaves, while its root wildcards stay out of the tree — they are the catch-all
|
|
1289
|
-
* the downstream leaves delegate to. An own route that is also a downstream route, or
|
|
1290
|
-
* whose topology conflicts with one, throws here instead of at request time. The app is
|
|
1291
|
-
* rebound to the result, so codegen that runs next (OpenAPI, manifest) sees what it serves.
|
|
1292
|
-
*/
|
|
1293
|
-
_gatewayTree(source) {
|
|
1294
|
-
assertTreeFormat(source);
|
|
1295
|
-
const g = this._graph;
|
|
1296
|
-
const root = cloneTree(source.root);
|
|
1297
|
-
const routes = dict();
|
|
1298
|
-
for (const id of Object.keys(source.routes)) {
|
|
1299
|
-
const { bek, ek, i, mt } = source.routes[id];
|
|
1300
|
-
routes[id] = { bek, ek, i, mt };
|
|
1301
|
-
}
|
|
1302
|
-
const own = [];
|
|
1303
|
-
/* every route id this app serves, root wildcards included */
|
|
1304
|
-
const served = new Set();
|
|
1305
|
-
for (const r of [...g.records.values(), ...g.catchAll.values(), ...g.wsRecords.values()]) {
|
|
1306
|
-
if (r._skip === true)
|
|
1307
|
-
continue;
|
|
1308
|
-
const id = r.id;
|
|
1309
|
-
served.add(id);
|
|
1310
|
-
const { method, segments } = patternOf(id);
|
|
1311
|
-
if (!isRootWildcard(segments))
|
|
1312
|
-
own.push({ id, method, segments });
|
|
1313
|
-
}
|
|
1314
|
-
const overlap = own.filter((o) => o.id in routes);
|
|
1315
|
-
/* a merge source built from this app itself (`app.toRouteTree()`) holds exactly the routes
|
|
1316
|
-
this app serves; anything else that overlaps is a gateway route shadowing a downstream
|
|
1317
|
-
one — judged by the route sets, never by how many routes overlap */
|
|
1318
|
-
const sourceIds = Object.keys(routes);
|
|
1319
|
-
const isSelf = own.length > 0 && overlap.length === own.length && sourceIds.every((id) => served.has(id));
|
|
1320
|
-
if (overlap.length > 0 && !isSelf) {
|
|
1321
|
-
throw new Error(`Gateway routes are also downstream routes in the merged tree: ${overlap.map((o) => o.id).join(", ")}`);
|
|
1322
|
-
}
|
|
1323
|
-
if (isSelf)
|
|
1324
|
-
own.length = 0;
|
|
1325
|
-
for (const { id, method, segments } of own) {
|
|
1326
|
-
for (const v of leafVariants(segments))
|
|
1327
|
-
insertLeaf(root, v, method, id);
|
|
1328
|
-
routes[id] = {};
|
|
1329
|
-
}
|
|
1330
|
-
this._replaceLoadedTree({ meta: { ...source.meta }, root, routes, v: ROUTE_TREE_VERSION });
|
|
1331
|
-
/* own routes resolved over the rebound tree carry their final error keys and meta */
|
|
1332
|
-
const snapshot = this.toRouteTree();
|
|
1333
|
-
for (const { id } of own) {
|
|
1334
|
-
const { bek, ek, mt } = snapshot.routes[id];
|
|
1335
|
-
routes[id] = { bek, ek, mt };
|
|
1336
|
-
}
|
|
1337
|
-
const tree = { meta: { ...source.meta }, root, routes, v: ROUTE_TREE_VERSION };
|
|
1338
|
-
this._replaceLoadedTree(tree);
|
|
1339
|
-
return tree;
|
|
1340
|
-
}
|
|
1341
|
-
/** @internal — drop the loaded tree and route by the registered routes alone. */
|
|
1342
|
-
_unloadTree() {
|
|
1343
|
-
const g = this._graph;
|
|
1344
|
-
const http = [...g.records.values(), ...g.catchAll.values()];
|
|
1345
|
-
const ws = [...g.wsRecords.values()];
|
|
1346
|
-
g.records.clear();
|
|
1347
|
-
g.catchAll.clear();
|
|
1348
|
-
g.wsRecords.clear();
|
|
1349
|
-
g.unexpected.clear();
|
|
1350
|
-
g.loaded = null;
|
|
1351
|
-
g.unloadIfStale = false;
|
|
1352
|
-
g.root = createNode();
|
|
1353
|
-
g.rootShared = false;
|
|
1354
|
-
g.hasWs = false;
|
|
1355
|
-
for (const r of http) {
|
|
1356
|
-
delete r.ca;
|
|
1357
|
-
const { method, segments } = patternOf(r.id);
|
|
1358
|
-
this._addRoute(method, segments, r);
|
|
1359
|
-
}
|
|
1360
|
-
for (const r of ws)
|
|
1361
|
-
this._addWsRoute(patternOf(r.id).segments, r);
|
|
1362
|
-
this._bumpEpoch();
|
|
1363
|
-
}
|
|
1364
|
-
/** @internal — swap the loaded tree, re-binding every registered route to it. */
|
|
1365
|
-
_replaceLoadedTree(tree) {
|
|
1366
|
-
const g = this._graph;
|
|
1367
|
-
const http = [...g.records.values(), ...g.catchAll.values()];
|
|
1368
|
-
const ws = [...g.wsRecords.values()];
|
|
1369
|
-
g.records.clear();
|
|
1370
|
-
g.catchAll.clear();
|
|
1371
|
-
g.wsRecords.clear();
|
|
1372
|
-
g.unexpected.clear();
|
|
1373
|
-
g.loaded = null;
|
|
1374
|
-
g.root = createNode();
|
|
1375
|
-
g.rootShared = false;
|
|
1376
|
-
g.hasWs = false;
|
|
1377
|
-
this.routeTree(tree);
|
|
1378
|
-
for (const r of http) {
|
|
1379
|
-
delete r.ca;
|
|
1380
|
-
const { method, segments } = patternOf(r.id);
|
|
1381
|
-
this._addRoute(method, segments, r);
|
|
1382
|
-
}
|
|
1383
|
-
for (const r of ws)
|
|
1384
|
-
this._addWsRoute(patternOf(r.id).segments, r);
|
|
1385
|
-
}
|
|
1386
|
-
/**
|
|
1387
|
-
* Set the app's error factory. Like every app setting it applies to every handle of this
|
|
1388
|
-
* app, whenever it is called; routes mounted from another app keep that app's factory.
|
|
1389
|
-
*/
|
|
1390
|
-
errorFactory(factory) {
|
|
1391
|
-
this._s.errorFactory = factory;
|
|
1392
|
-
this._bumpEpoch();
|
|
1393
|
-
return this;
|
|
1394
|
-
}
|
|
1395
|
-
/** Error keys every route of this app declares — applies to routes registered before and after the call. */
|
|
1396
|
-
defaultErrors(...keys) {
|
|
1397
|
-
for (const k of keys)
|
|
1398
|
-
this._s.defaultErrorKeys.add(k);
|
|
1399
|
-
this._bumpEpoch();
|
|
1400
|
-
return this;
|
|
1401
|
-
}
|
|
1402
|
-
defaultBoundary(key) {
|
|
1403
|
-
this._s.defaultBoundaryKey = key;
|
|
1404
|
-
this._s.defaultErrorKeys.add(key);
|
|
1405
|
-
this._bumpEpoch();
|
|
1406
|
-
return this;
|
|
1407
|
-
}
|
|
1408
|
-
meta(values) {
|
|
1409
|
-
if (values === undefined) {
|
|
1410
|
-
/* phantom overload — type-only, no runtime effect */
|
|
1411
|
-
return this;
|
|
1412
|
-
}
|
|
1413
|
-
/* chain-level meta — copy-on-write */
|
|
1414
|
-
const next = this._derive();
|
|
1415
|
-
next._chainMeta = this._chainMeta ? { ...this._chainMeta, ...values } : { ...values };
|
|
1416
|
-
return next;
|
|
1417
|
-
}
|
|
1418
|
-
/**
|
|
1419
|
-
* Declare what flows from route meta and route schemas into the OpenAPI document.
|
|
1420
|
-
* Codegen-time only — never consulted on the request path. See docs/meta-spec.md.
|
|
1421
|
-
*/
|
|
1422
|
-
metaSpec(spec) {
|
|
1423
|
-
if (this._graph.metaSpec !== null) {
|
|
1424
|
-
throw new Error("metaSpec() was already declared on this app — declare the whole policy in one call");
|
|
1425
|
-
}
|
|
1426
|
-
this._graph.metaSpec = spec;
|
|
1427
|
-
return this;
|
|
1428
|
-
}
|
|
1429
|
-
/** Declare tap payload types — auto-extends meta with Partial<T> for meta-driven taps */
|
|
1430
|
-
taps() {
|
|
1431
|
-
return this;
|
|
1432
|
-
}
|
|
1433
|
-
/* oxlint-disable-next-line typescript/no-explicit-any -- overload impl requires erased types */
|
|
1434
|
-
use(pathOrMw, maybeMw) {
|
|
1435
|
-
if (typeof pathOrMw !== "string") {
|
|
1436
|
-
/*
|
|
1437
|
-
* Chain middleware: a new handle whose routes run `mw`. This handle is unchanged —
|
|
1438
|
-
* a handle that never registers a route is reported at finalize.
|
|
1439
|
-
*/
|
|
1440
|
-
const mw = pathOrMw;
|
|
1441
|
-
const node = { mw, parent: this._node, used: false };
|
|
1442
|
-
this._graph.chains.push(node);
|
|
1443
|
-
const next = this._derive([...this._chain, mw]);
|
|
1444
|
-
next._node = node;
|
|
1445
|
-
return next;
|
|
1446
|
-
}
|
|
1447
|
-
/* scoped: guards every request path under the prefix, whichever handle registered the route */
|
|
1448
|
-
const prefix = mergePath(this._basePath, normalizePattern(pathOrMw));
|
|
1449
|
-
const mw = maybeMw;
|
|
1450
|
-
const segs = parsePattern(prefix);
|
|
1451
|
-
this._graph.scoped.push({
|
|
1452
|
-
errors: errorsOf(mw) ? [...errorsOf(mw)] : undefined,
|
|
1453
|
-
guard: scopeGuard(segs, mw),
|
|
1454
|
-
mw,
|
|
1455
|
-
prefix,
|
|
1456
|
-
segs,
|
|
1457
|
-
});
|
|
1458
|
-
this._bumpEpoch();
|
|
1459
|
-
return this._derive();
|
|
1460
|
-
}
|
|
1461
|
-
route(prefixOrSub, maybeSub) {
|
|
1462
|
-
if (typeof prefixOrSub === "string") {
|
|
1463
|
-
if (!(maybeSub instanceof Honey))
|
|
1464
|
-
throw new TypeError("honey: route(prefix, sub) needs the sub-app");
|
|
1465
|
-
this._markUsed();
|
|
1466
|
-
this.basePath(prefixOrSub)._mount(maybeSub);
|
|
1467
|
-
return this;
|
|
1468
|
-
}
|
|
1469
|
-
return this._mount(prefixOrSub);
|
|
1470
|
-
}
|
|
1471
|
-
_mount(sub) {
|
|
1472
|
-
this._markUsed();
|
|
1473
|
-
/* skip self-merge: .handler() already registered into the shared graph */
|
|
1474
|
-
if (sub._graph !== this._graph) {
|
|
1475
|
-
/*
|
|
1476
|
-
* Re-register the sub's records under this handle: this handle's chain runs first,
|
|
1477
|
-
* its context values and chain meta apply under the sub's own. Nothing is shared by
|
|
1478
|
-
* reference, so routes the sub registers later stay its own, and two parents
|
|
1479
|
-
* mounting one sub each get their own records. Each record keeps the settings of
|
|
1480
|
-
* the sub (error factory, defaults, boundary, output validation). Internal routes
|
|
1481
|
-
* (spec, docs, manifest) never travel.
|
|
1482
|
-
*/
|
|
1483
|
-
sub._markUsed();
|
|
1484
|
-
const subFinal = sub._finalize();
|
|
1485
|
-
const subSettings = sub._graph.settings;
|
|
1486
|
-
const mount = (r) => {
|
|
1487
|
-
const c = copyRecord(r);
|
|
1488
|
-
c.cm = [...this._chain, ...(r.cm ?? [])];
|
|
1489
|
-
if (this._contextValues)
|
|
1490
|
-
c.cv = { ...this._contextValues, ...r.cv };
|
|
1491
|
-
if (this._chainMeta)
|
|
1492
|
-
c.xm = { ...this._chainMeta, ...r.xm };
|
|
1493
|
-
c.own = r.own ?? subSettings;
|
|
1494
|
-
return c;
|
|
1495
|
-
};
|
|
1496
|
-
/* the sub's paths land under this handle's basePath, like Hono's `route("/v1", sub)` */
|
|
1497
|
-
const base = this._basePath;
|
|
1498
|
-
const at = (pattern) => (base === "/" ? pattern : mergePath(base, pattern));
|
|
1499
|
-
for (const [id, r] of subFinal.byId) {
|
|
1500
|
-
if (r._skip)
|
|
1501
|
-
continue;
|
|
1502
|
-
const { method, pattern } = splitRouteId(id);
|
|
1503
|
-
this._addRoute(method, parsePattern(at(pattern)), mount(r));
|
|
1504
|
-
}
|
|
1505
|
-
for (const [id, r] of subFinal.wsById) {
|
|
1506
|
-
const { pattern } = splitRouteId(id);
|
|
1507
|
-
this._addWsRoute(parsePattern(at(pattern)), mount(r));
|
|
1508
|
-
}
|
|
1509
|
-
/* merged when codegen reads the policy (see `metaSpecOf`): the parent wins
|
|
1510
|
-
* conflicts and keeps its resolved strictness, a sub's `false` still hides */
|
|
1511
|
-
const subSource = sub._metaSpecSource;
|
|
1512
|
-
this._graph.absorbedMetaSpecs.push({ absorbed: [...subSource.absorbed], declared: subSource.declared });
|
|
1513
|
-
/* the sub's scopes guard the paths it brought — appended after this app's own */
|
|
1514
|
-
const g = this._graph;
|
|
1515
|
-
for (const entry of sub._graph.scoped) {
|
|
1516
|
-
if (base === "/") {
|
|
1517
|
-
if (!g.scoped.includes(entry))
|
|
1518
|
-
g.scoped.push(entry);
|
|
1519
|
-
continue;
|
|
1520
|
-
}
|
|
1521
|
-
const prefix = at(entry.prefix);
|
|
1522
|
-
if (g.scoped.some((e) => e.mw === entry.mw && e.prefix === prefix))
|
|
1523
|
-
continue;
|
|
1524
|
-
const segs = parsePattern(prefix);
|
|
1525
|
-
g.scoped.push({ errors: entry.errors, guard: scopeGuard(segs, entry.mw), mw: entry.mw, prefix, segs });
|
|
1526
|
-
}
|
|
1527
|
-
for (const [subPath, cfg] of sub._graph.realtimeRoutes) {
|
|
1528
|
-
const path = at(subPath);
|
|
1529
|
-
if (g.realtimeRoutes.has(path)) {
|
|
1530
|
-
throw new Error(`Duplicate realtime route: ${path}`);
|
|
1531
|
-
}
|
|
1532
|
-
/* a default namespace is the route's full path, so it follows the mount; a named one is kept */
|
|
1533
|
-
g.realtimeRoutes.set(path, { ...cfg, namespace: cfg.namespaceExplicit ? cfg.namespace : path, path });
|
|
1534
|
-
}
|
|
1535
|
-
if (!g.realtimeBus && sub._graph.realtimeBus) {
|
|
1536
|
-
this._setBus(sub._graph.realtimeBus);
|
|
1537
|
-
}
|
|
1538
|
-
if (subSettings.taps !== null) {
|
|
1539
|
-
const s = this._graph.settings;
|
|
1540
|
-
if (s.taps === null)
|
|
1541
|
-
s.taps = new Map();
|
|
1542
|
-
for (const [key, fn] of subSettings.taps) {
|
|
1543
|
-
if (!s.taps.has(key))
|
|
1544
|
-
s.taps.set(key, fn);
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
this._bumpEpoch();
|
|
1548
|
-
}
|
|
1549
|
-
return this;
|
|
1550
|
-
}
|
|
1551
|
-
_setBus(bus) {
|
|
1552
|
-
const g = this._graph;
|
|
1553
|
-
g.realtimeBus = bus;
|
|
1554
|
-
g.realtimeCtx = requireFeature("realtime", "app.realtime()").createRealtimePublisher(bus, () => [...g.realtimeRoutes.values()].map((cfg) => cfg.namespace));
|
|
1555
|
-
return bus;
|
|
1556
|
-
}
|
|
1557
|
-
_registerRoute(method, path, extraMethods) {
|
|
1558
|
-
const fullPath = mergePath(this._basePath, path);
|
|
1559
|
-
const pending = { id: `${[method, ...(extraMethods ?? [])].join(",")} ${fullPath}` };
|
|
1560
|
-
this._graph.pending.add(pending);
|
|
1561
|
-
return new RouteBuilder({
|
|
1562
|
-
boundaryErrorKey: null,
|
|
1563
|
-
errorKeys: new Set(),
|
|
1564
|
-
extraMethods: extraMethods ?? null,
|
|
1565
|
-
inputSchemas: null,
|
|
1566
|
-
meta: this._chainMeta ? { ...this._chainMeta } : null,
|
|
1567
|
-
method,
|
|
1568
|
-
middlewares: [],
|
|
1569
|
-
outputSchemas: null,
|
|
1570
|
-
parent: this,
|
|
1571
|
-
path: fullPath,
|
|
1572
|
-
pending,
|
|
1573
|
-
});
|
|
1574
|
-
}
|
|
1575
|
-
/** @internal — set (or clear, with null) one app-wide middleware; used by `serve()` */
|
|
1576
|
-
_setGlobal(key, mw) {
|
|
1577
|
-
if (mw === null) {
|
|
1578
|
-
if (!this._graph.global.delete(key))
|
|
1579
|
-
return;
|
|
1580
|
-
}
|
|
1581
|
-
else {
|
|
1582
|
-
this._graph.global.set(key, mw);
|
|
1583
|
-
}
|
|
1584
|
-
this._bumpEpoch();
|
|
1585
|
-
}
|
|
1586
|
-
/** @internal — what a route registered through this handle captures */
|
|
1587
|
-
_view() {
|
|
1588
|
-
return { chain: this._chain, cv: this._contextValues, own: this._graph.settings };
|
|
1589
|
-
}
|
|
1590
|
-
/** @internal — a builder got its handler (or was abandoned on purpose) */
|
|
1591
|
-
_settle(pending) {
|
|
1592
|
-
this._graph.pending.delete(pending);
|
|
1593
|
-
this._markUsed();
|
|
1594
|
-
}
|
|
1595
|
-
on(methods, path) {
|
|
1596
|
-
const [first, ...rest] = methods;
|
|
1597
|
-
return this._registerRoute(first, path, rest.length > 0 ? rest : undefined);
|
|
1598
|
-
}
|
|
1599
|
-
all(path) {
|
|
1600
|
-
return this._registerRoute("ALL", path);
|
|
1601
|
-
}
|
|
1602
|
-
delete(path) {
|
|
1603
|
-
return this._registerRoute("DELETE", path);
|
|
1604
|
-
}
|
|
1605
|
-
get(path) {
|
|
1606
|
-
return this._registerRoute("GET", path);
|
|
1607
|
-
}
|
|
1608
|
-
head(path) {
|
|
1609
|
-
return this._registerRoute("HEAD", path);
|
|
1610
|
-
}
|
|
1611
|
-
options(path) {
|
|
1612
|
-
return this._registerRoute("OPTIONS", path);
|
|
1613
|
-
}
|
|
1614
|
-
patch(path) {
|
|
1615
|
-
return this._registerRoute("PATCH", path);
|
|
1616
|
-
}
|
|
1617
|
-
post(path) {
|
|
1618
|
-
return this._registerRoute("POST", path);
|
|
1619
|
-
}
|
|
1620
|
-
put(path) {
|
|
1621
|
-
return this._registerRoute("PUT", path);
|
|
1622
|
-
}
|
|
1623
|
-
ws(path) {
|
|
1624
|
-
const fullPath = mergePath(this._basePath, path);
|
|
1625
|
-
const pending = { id: `WS ${fullPath}` };
|
|
1626
|
-
this._graph.pending.add(pending);
|
|
1627
|
-
return new WSRouteBuilder({
|
|
1628
|
-
errorKeys: new Set(),
|
|
1629
|
-
inputSchemas: null,
|
|
1630
|
-
meta: this._chainMeta ? { ...this._chainMeta } : null,
|
|
1631
|
-
middlewares: [],
|
|
1632
|
-
origins: null,
|
|
1633
|
-
parent: this,
|
|
1634
|
-
path: fullPath,
|
|
1635
|
-
pending,
|
|
1636
|
-
});
|
|
1637
|
-
}
|
|
1638
|
-
realtime(path, opts) {
|
|
1639
|
-
const fullPath = mergePath(this._basePath, path);
|
|
1640
|
-
const g = this._graph;
|
|
1641
|
-
const rt = requireFeature("realtime", "app.realtime()");
|
|
1642
|
-
const config = rt.resolveRealtimeConfig(fullPath, opts);
|
|
1643
|
-
if (g.realtimeRoutes.has(fullPath)) {
|
|
1644
|
-
throw new Error(`Duplicate realtime route: ${fullPath}`);
|
|
1645
|
-
}
|
|
1646
|
-
if (!g.realtimeBus)
|
|
1647
|
-
this._setBus(rt.createBus());
|
|
1648
|
-
g.realtimeRoutes.set(fullPath, config);
|
|
1649
|
-
this._markUsed();
|
|
1650
|
-
this._addWsRoute(parsePattern(fullPath), {
|
|
1651
|
-
bek: null,
|
|
1652
|
-
cm: [...this._chain],
|
|
1653
|
-
cv: this._contextValues,
|
|
1654
|
-
dk: new Set(),
|
|
1655
|
-
ek: new Set(),
|
|
1656
|
-
fn: Object.create(null),
|
|
1657
|
-
iv: null,
|
|
1658
|
-
mt: null,
|
|
1659
|
-
mw: [],
|
|
1660
|
-
own: g.settings,
|
|
1661
|
-
rb: null,
|
|
1662
|
-
rm: (opts.use ?? []).map((fn) => fn),
|
|
1663
|
-
rp: fullPath,
|
|
1664
|
-
xm: this._chainMeta ? { ...this._chainMeta } : null,
|
|
1665
|
-
});
|
|
1666
|
-
return this;
|
|
1667
|
-
}
|
|
1668
|
-
/**
|
|
1669
|
-
* Deno must call `Deno.upgradeWebSocket` in the same turn as the serve
|
|
1670
|
-
* callback. When the adapter implements `preUpgrade`, we do that here
|
|
1671
|
-
* and return the 101 response before any middleware Promise.
|
|
1672
|
-
*/
|
|
1673
|
-
fetch(request, env, executionCtx) {
|
|
1674
|
-
if (!this._graph.served.has(this._chain)) {
|
|
1675
|
-
this._markUsed();
|
|
1676
|
-
this._graph.served.add(this._chain);
|
|
1677
|
-
this._bumpEpoch();
|
|
1678
|
-
}
|
|
1679
|
-
const wsAdapter = this._graph.settings.wsAdapter;
|
|
1680
|
-
const path = normalizePath(pathOfUrl(request.url), this._graph.settings.encodedSlashes);
|
|
1681
|
-
if (path === null)
|
|
1682
|
-
return this._badRequestTarget();
|
|
1683
|
-
/* Deno and Bun before 1.4 build request.url by concatenating Host and the target, so a
|
|
1684
|
-
* `Host: x/admin?` would choose the routed path. `serve()` checks Host on Deno; this covers
|
|
1685
|
-
* `Deno.serve(app.fetch)`, which passes its serve info as env, and every Bun entry point on
|
|
1686
|
-
* an affected version. Node's adapter checks it; Bun 1.4+ routes on the target. */
|
|
1687
|
-
if ((BUN_URL_FROM_HOST || isDenoServeInfo(env)) && !hasValidHost(request))
|
|
1688
|
-
return this._badRequestTarget();
|
|
1689
|
-
/* an adapter alone (serve() always installs one) does not need the websocket path */
|
|
1690
|
-
if (!this._graph.hasWs) {
|
|
1691
|
-
return this._doFetch(request, env, executionCtx, path);
|
|
1692
|
-
}
|
|
1693
|
-
const isWsUpgrade = request.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
1694
|
-
const canPreUpgrade = isWsUpgrade &&
|
|
1695
|
-
wsAdapter?.preUpgrade !== undefined &&
|
|
1696
|
-
!this.trailingSlashRedirects(path) &&
|
|
1697
|
-
this._matchWs(this._finalize(), this.pathAfterPrefix(path)) !== null;
|
|
1698
|
-
/* Deno closes the Request in preUpgrade: snapshot the headers the chain will read first */
|
|
1699
|
-
const headerSnap = canPreUpgrade ? new Headers(request.headers) : undefined;
|
|
1700
|
-
let pre;
|
|
1701
|
-
try {
|
|
1702
|
-
pre = canPreUpgrade ? wsAdapter?.preUpgrade?.(request) : undefined;
|
|
1703
|
-
}
|
|
1704
|
-
catch (err) {
|
|
1705
|
-
/* the runtime refused the handshake: answer through the normal path, which fails the upgrade */
|
|
1706
|
-
this._graph.settings.logger?.warn?.({ err }, "websocket preUpgrade failed");
|
|
1707
|
-
pre = undefined;
|
|
1708
|
-
}
|
|
1709
|
-
/* After Deno.upgradeWebSocket the Request is closed. A sync throw here
|
|
1710
|
-
* used to be boxed by async _doFetch; keep 101 returning either way. */
|
|
1711
|
-
let work;
|
|
1712
|
-
try {
|
|
1713
|
-
work = this._doFetch(request, env, executionCtx, path, isWsUpgrade === true, headerSnap);
|
|
1714
|
-
}
|
|
1715
|
-
catch (err) {
|
|
1716
|
-
work = Promise.reject(err);
|
|
1717
|
-
}
|
|
1718
|
-
if (!pre)
|
|
1719
|
-
return work;
|
|
1720
|
-
void Promise.resolve(work)
|
|
1721
|
-
.then((res) => {
|
|
1722
|
-
if (res.status === 101)
|
|
1723
|
-
return;
|
|
1724
|
-
const code = res.status === 401 ? 4401 : 1008;
|
|
1725
|
-
const reason = res.status === 401 ? "unauthorized" : "upgrade rejected";
|
|
1726
|
-
const reject = () => {
|
|
1727
|
-
try {
|
|
1728
|
-
pre.socket.close(code, reason);
|
|
1729
|
-
}
|
|
1730
|
-
catch {
|
|
1731
|
-
/* already closed */
|
|
1732
|
-
}
|
|
1733
|
-
};
|
|
1734
|
-
if (pre.whenOpen)
|
|
1735
|
-
pre.whenOpen(reject);
|
|
1736
|
-
else
|
|
1737
|
-
reject();
|
|
1738
|
-
})
|
|
1739
|
-
.catch(() => {
|
|
1740
|
-
try {
|
|
1741
|
-
pre.socket.close(1011, "internal error");
|
|
1742
|
-
}
|
|
1743
|
-
catch {
|
|
1744
|
-
/* already closed */
|
|
1745
|
-
}
|
|
1746
|
-
});
|
|
1747
|
-
return pre.response;
|
|
1748
|
-
}
|
|
1749
|
-
/** @internal 400 for a request path the normalization policy rejects (see `normalizePath`), or an invalid `Host`. */
|
|
1750
|
-
_badRequestTarget() {
|
|
1751
|
-
return this._toErrorResponse(this._createError(EK.bad_request, SK.bad_request));
|
|
1752
|
-
}
|
|
1753
|
-
trailingSlashRedirects(path) {
|
|
1754
|
-
if (path.length <= 1)
|
|
1755
|
-
return false;
|
|
1756
|
-
const mode = this._graph.settings.trailingSlash;
|
|
1757
|
-
if (mode === "strip" && path.endsWith("/"))
|
|
1758
|
-
return true;
|
|
1759
|
-
if (mode === "enforce" && !path.endsWith("/"))
|
|
1760
|
-
return true;
|
|
1761
|
-
return false;
|
|
1762
|
-
}
|
|
1763
|
-
pathAfterPrefix(path) {
|
|
1764
|
-
const prefix = this._graph.settings.stripPrefix;
|
|
1765
|
-
if (prefix === null)
|
|
1766
|
-
return path;
|
|
1767
|
-
if (path === prefix)
|
|
1768
|
-
return "/";
|
|
1769
|
-
if (path.startsWith(prefix) && path.charCodeAt(prefix.length) === 47) {
|
|
1770
|
-
return path.slice(prefix.length);
|
|
1771
|
-
}
|
|
1772
|
-
return path;
|
|
1773
|
-
}
|
|
1774
|
-
_doFetch(request, env, executionCtx, fullPath, knownWsUpgrade = false, headerSnap) {
|
|
1775
|
-
const startTime = performance.now();
|
|
1776
|
-
const final = this._finalize();
|
|
1777
|
-
const s = this._graph.settings;
|
|
1778
|
-
const rawUrl = request.url;
|
|
1779
|
-
/* lazily create URL only when actually needed (search params, redirects) */
|
|
1780
|
-
let _url;
|
|
1781
|
-
const getUrl = () => {
|
|
1782
|
-
if (_url === undefined)
|
|
1783
|
-
_url = new URL(rawUrl);
|
|
1784
|
-
return _url;
|
|
1785
|
-
};
|
|
1786
|
-
const log = s.logger ?? undefined;
|
|
1787
|
-
const method = request.method.toUpperCase();
|
|
1788
|
-
const fc = {
|
|
1789
|
-
allowed: null,
|
|
1790
|
-
env,
|
|
1791
|
-
executionCtx,
|
|
1792
|
-
headerSnap,
|
|
1793
|
-
log,
|
|
1794
|
-
method,
|
|
1795
|
-
path: fullPath,
|
|
1796
|
-
plan: null,
|
|
1797
|
-
request,
|
|
1798
|
-
startTime,
|
|
1799
|
-
trust: s.trust,
|
|
1800
|
-
url: getUrl,
|
|
1801
|
-
ws: null,
|
|
1802
|
-
wsUpgrade: knownWsUpgrade,
|
|
1803
|
-
};
|
|
1804
|
-
if (s.telemetry === null)
|
|
1805
|
-
return this._route(fc, final, fullPath, rawUrl, knownWsUpgrade);
|
|
1806
|
-
safeFire(() => s.telemetry?.onRequest?.({ env, req: request }), log);
|
|
1807
|
-
/* every exit after onRequest — redirects, 426, upgrades, misses, preflights, handlers —
|
|
1808
|
-
* fires onResponse exactly once, here */
|
|
1809
|
-
return this._withOnResponse(fc, this._route(fc, final, fullPath, rawUrl, knownWsUpgrade));
|
|
1810
|
-
}
|
|
1811
|
-
_route(fc, final, fullPath, rawUrl, knownWsUpgrade) {
|
|
1812
|
-
const s = this._graph.settings;
|
|
1813
|
-
const { method, request } = fc;
|
|
1814
|
-
/* trailing slash handling — a relative Location: the scheme, host and port the client
|
|
1815
|
-
* used are the ones it keeps, whatever proxy terminated TLS in front of the app */
|
|
1816
|
-
if (this.trailingSlashRedirects(fullPath)) {
|
|
1817
|
-
const target = s.trailingSlash === "strip" ? fullPath.slice(0, -1) : `${fullPath}/`;
|
|
1818
|
-
return new Response(null, {
|
|
1819
|
-
headers: { location: target + searchOfUrl(rawUrl) },
|
|
1820
|
-
status: 308,
|
|
1821
|
-
});
|
|
1822
|
-
}
|
|
1823
|
-
/* prefix stripping — must run AFTER trailing slash so redirects preserve the full prefixed URL */
|
|
1824
|
-
const path = this.pathAfterPrefix(fullPath);
|
|
1825
|
-
fc.path = path;
|
|
1826
|
-
const root = this._graph.root;
|
|
1827
|
-
/* WebSocket route check — do not re-read headers after Deno.upgradeWebSocket */
|
|
1828
|
-
const isWsUpgrade = knownWsUpgrade || requestIsWsUpgrade(request);
|
|
1829
|
-
if (isWsUpgrade) {
|
|
1830
|
-
const wsMatch = this._matchWs(final, path);
|
|
1831
|
-
if (wsMatch !== null) {
|
|
1832
|
-
return this._handleWs(fc, wsMatch);
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
/* O(1) static route lookup — patterns without params or wildcards only */
|
|
1836
|
-
const staticHit = final.statics[`${method} ${path}`] ?? (method === "HEAD" ? final.statics[`GET ${path}`] : undefined);
|
|
1837
|
-
if (staticHit !== undefined) {
|
|
1838
|
-
return this._dispatchRecord(fc, staticHit, EMPTY_PARAMS);
|
|
1839
|
-
}
|
|
1840
|
-
const result = matchRoute(root, method, path);
|
|
1841
|
-
if (result === null) {
|
|
1842
|
-
/* no HTTP route: a websocket or realtime route on this path asks for an upgrade */
|
|
1843
|
-
if (!isWsUpgrade && this._graph.hasWs) {
|
|
1844
|
-
const wsMatch = this._matchWs(final, path);
|
|
1845
|
-
if (wsMatch !== null) {
|
|
1846
|
-
if (this._graph.realtimeRoutes.has(wsMatch.handler.rp)) {
|
|
1847
|
-
return new Response(null, { headers: { upgrade: "websocket" }, status: 426 });
|
|
1848
|
-
}
|
|
1849
|
-
return new Response("Upgrade Required", {
|
|
1850
|
-
headers: { connection: "Upgrade", upgrade: "websocket" },
|
|
1851
|
-
status: 426,
|
|
1852
|
-
});
|
|
1853
|
-
}
|
|
1854
|
-
}
|
|
1855
|
-
return this._handleMiss(fc, null);
|
|
1856
|
-
}
|
|
1857
|
-
if (!result.matched) {
|
|
1858
|
-
if (method === "OPTIONS") {
|
|
1859
|
-
const requested = request.headers.get("access-control-request-method");
|
|
1860
|
-
if (requested) {
|
|
1861
|
-
/* a preflight asks about one method: run that route's own chain */
|
|
1862
|
-
const hit = matchRoute(root, requested.trim().toUpperCase(), path);
|
|
1863
|
-
const plan = hit?.matched ? final.plans.get(hit.id) : undefined;
|
|
1864
|
-
if (hit?.matched && plan !== undefined && plan.r.fn !== NOT_SERVED) {
|
|
1865
|
-
return this._handlePreflight(fc, plan, hit.params, result.allowed);
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
return this._handleMiss(fc, result.allowed);
|
|
1870
|
-
}
|
|
1871
|
-
const plan = final.plans.get(result.id);
|
|
1872
|
-
if (plan === undefined)
|
|
1873
|
-
return this._handleMiss(fc, null);
|
|
1874
|
-
return this._dispatchRecord(fc, plan, result.params);
|
|
1875
|
-
}
|
|
1876
|
-
_matchWs(final, path) {
|
|
1877
|
-
const hit = matchWsRoute(this._graph.root, path);
|
|
1878
|
-
if (hit === null)
|
|
1879
|
-
return null;
|
|
1880
|
-
const handler = final.wsById.get(hit.id);
|
|
1881
|
-
return handler === undefined ? null : { handler, params: hit.params };
|
|
1882
|
-
}
|
|
1883
|
-
_dispatchRecord(fc, plan, params) {
|
|
1884
|
-
const record = plan.r;
|
|
1885
|
-
if (record.fn === NOT_SERVED)
|
|
1886
|
-
return this._handleMiss(fc, null);
|
|
1887
|
-
/* a delegated route keeps its own input schemas for docs and the 415 check; the body
|
|
1888
|
-
* itself belongs to whatever the catch-all forwards it to */
|
|
1889
|
-
if (record.dl === true && record.iv) {
|
|
1890
|
-
try {
|
|
1891
|
-
assertRequestContentType(record.iv, fc.request);
|
|
1892
|
-
}
|
|
1893
|
-
catch (thrown) {
|
|
1894
|
-
return this._toErrorResponse(thrown);
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
return this._handleMatched(fc, plan, params);
|
|
1898
|
-
}
|
|
1899
|
-
_makeErrorCtx(fc, allowed) {
|
|
1900
|
-
const s = this._s;
|
|
1901
|
-
return {
|
|
1902
|
-
env: fc.env,
|
|
1903
|
-
jsonFromError: (err) => createErrorResponse(err, s.errorFormatter, s.customErrorFormatter),
|
|
1904
|
-
req: fc.request,
|
|
1905
|
-
...(allowed ? { allowed } : {}),
|
|
1906
|
-
};
|
|
1907
|
-
}
|
|
1908
|
-
/** A request context for `plan`: route data, context values, `ctx.errors`, `ctx.realtime`. */
|
|
1909
|
-
_newCtx(fc, plan, params, req) {
|
|
1910
|
-
fc.plan = plan;
|
|
1911
|
-
const r = plan.r;
|
|
1912
|
-
const ctx = new HoneyContext({
|
|
1913
|
-
env: fc.env,
|
|
1914
|
-
executionCtx: fc.executionCtx,
|
|
1915
|
-
meta: r.mt ?? undefined,
|
|
1916
|
-
params,
|
|
1917
|
-
path: fc.path,
|
|
1918
|
-
req: req ?? fc.request,
|
|
1919
|
-
routePattern: r.rp ?? "",
|
|
1920
|
-
urlFn: fc.url,
|
|
1921
|
-
});
|
|
1922
|
-
if (plan.cv !== null)
|
|
1923
|
-
Object.assign(ctx, plan.cv);
|
|
1924
|
-
const rt = this._graph.realtimeCtx;
|
|
1925
|
-
if (rt !== null)
|
|
1926
|
-
ctx.realtime = rt;
|
|
1927
|
-
if (r.ef)
|
|
1928
|
-
ctx._setErrors(r.ef);
|
|
1929
|
-
ctx._rq = fc;
|
|
1930
|
-
return ctx;
|
|
1931
|
-
}
|
|
1932
|
-
_handleWs(fc, wsMatch) {
|
|
1933
|
-
const plan = this._finalize().wsPlans.get(wsMatch.handler.id);
|
|
1934
|
-
if (plan === undefined)
|
|
1935
|
-
return this._handleMiss(fc, null);
|
|
1936
|
-
const isUpgrade = fc.wsUpgrade === true || requestIsWsUpgrade(fc.request);
|
|
1937
|
-
if (!isUpgrade) {
|
|
1938
|
-
return new Response(null, {
|
|
1939
|
-
headers: { upgrade: "websocket" },
|
|
1940
|
-
status: 426,
|
|
1941
|
-
});
|
|
1942
|
-
}
|
|
1943
|
-
if (!this._graph.settings.wsAdapter) {
|
|
1944
|
-
fc.log?.warn?.("WebSocket adapter not configured — call .wsAdapter()");
|
|
1945
|
-
return this._toErrorResponse(this._createError(EK.internal_server_error, SK.internal_server_error));
|
|
1946
|
-
}
|
|
1947
|
-
fc.ws = wsMatch;
|
|
1948
|
-
const ctx = this._newCtx(fc, plan, wsMatch.params, ctxRequest(fc));
|
|
1949
|
-
return plan.run(ctx);
|
|
1950
|
-
}
|
|
1951
|
-
/** Reject a cross-origin upgrade the route's origin policy does not allow (403). */
|
|
1952
|
-
/** The websocket session runtime; a cross-origin upgrade the route does not allow is a 403. */
|
|
1953
|
-
async _wsSession(finalCtx, policy) {
|
|
1954
|
-
const ws = await loadFeature("ws", "a websocket route");
|
|
1955
|
-
const headers = finalCtx.req.headers;
|
|
1956
|
-
const origin = headers.get("origin");
|
|
1957
|
-
if (origin === null || policy === "*")
|
|
1958
|
-
return ws;
|
|
1959
|
-
const credentialed = headers.has("cookie") || headers.has("authorization");
|
|
1960
|
-
if (!ws.originAllowed(policy, origin, clientInfo(finalCtx).host, credentialed)) {
|
|
1961
|
-
throw this._createError(EK.forbidden, SK.forbidden);
|
|
1962
|
-
}
|
|
1963
|
-
return ws;
|
|
1964
|
-
}
|
|
1965
|
-
async _wsUpgrade(finalCtx) {
|
|
1966
|
-
const fc = finalCtx._rq;
|
|
1967
|
-
const wsMatch = fc.ws;
|
|
1968
|
-
const ws = await this._wsSession(finalCtx, wsMatch.handler.og ?? null);
|
|
1969
|
-
const wsAdapter = this._graph.settings.wsAdapter;
|
|
1970
|
-
const handler = ws.createWsSession({
|
|
1971
|
-
callbacks: wsMatch.handler.fn,
|
|
1972
|
-
ctx: finalCtx,
|
|
1973
|
-
log: (this._graph.settings.logger ?? null),
|
|
1974
|
-
reconnectToken: fc.url().searchParams.get("reconnect_token"),
|
|
1975
|
-
route: wsMatch.handler.rp,
|
|
1976
|
-
});
|
|
1977
|
-
const upgradeResult = await wsAdapter.upgrade(fc.request, fc.env, handler);
|
|
1978
|
-
return upgradeResult.response;
|
|
1979
|
-
}
|
|
1980
|
-
/** Terminal of a realtime route's chain: identify, upgrade, and attach the connection to the bus. */
|
|
1981
|
-
async _realtimeUpgrade(finalCtx, config) {
|
|
1982
|
-
const fc = finalCtx._rq;
|
|
1983
|
-
await this._wsSession(finalCtx, config.allowedOrigins);
|
|
1984
|
-
const wsAdapter = this._graph.settings.wsAdapter;
|
|
1985
|
-
const rt = requireFeature("realtime", "app.realtime()");
|
|
1986
|
-
const bus = this._graph.realtimeBus ?? this._setBus(rt.createBus());
|
|
1987
|
-
/* a throwing identify rejects the upgrade through the chain's error boundary */
|
|
1988
|
-
const userId = config.identify === null ? null : ((await config.identify(finalCtx)) ?? null);
|
|
1989
|
-
const session = rt.createRealtimeSession({ bus, config, ctx: finalCtx, log: fc.log, userId });
|
|
1990
|
-
const upgradeResult = await wsAdapter.upgrade(fc.request, fc.env, session.handler);
|
|
1991
|
-
/* Node/CF return an open socket from upgrade(); Bun and Deno deliver it through onOpen */
|
|
1992
|
-
if (upgradeResult.socket && upgradeResult.socket.readyState === 1)
|
|
1993
|
-
session.attach(upgradeResult.socket);
|
|
1994
|
-
return upgradeResult.response;
|
|
1995
|
-
}
|
|
1996
|
-
/**
|
|
1997
|
-
* 404 (`allowed === null`) and 405: one pipeline with a synthetic record — full context,
|
|
1998
|
-
* the app-wide middleware and the scopes covering the path run once, telemetry fires once.
|
|
1999
|
-
*/
|
|
2000
|
-
_handleMiss(fc, allowed) {
|
|
2001
|
-
const s = this._graph.settings;
|
|
2002
|
-
const { method, path, request } = fc;
|
|
2003
|
-
if (allowed === null) {
|
|
2004
|
-
safeFire(() => s.telemetry?.onNotFound?.({ method, path, req: request }), fc.log);
|
|
2005
|
-
}
|
|
2006
|
-
else {
|
|
2007
|
-
safeFire(() => s.telemetry?.onMethodNotAllowed?.({ allowed, method, path, req: request }), fc.log);
|
|
2008
|
-
}
|
|
2009
|
-
fc.allowed = allowed;
|
|
2010
|
-
const plan = this._finalize().miss;
|
|
2011
|
-
const ctx = this._newCtx(fc, plan, EMPTY_PARAMS);
|
|
2012
|
-
return plan.run(ctx);
|
|
2013
|
-
}
|
|
2014
|
-
/** Fire `onResponse` once for the request's one exit and return. A dispatch that rejects
|
|
2015
|
-
* (an adapter failure past every error boundary) still ends the request: it reports 500. */
|
|
2016
|
-
_withOnResponse(fc, res) {
|
|
2017
|
-
const s = this._graph.settings;
|
|
2018
|
-
if (s.telemetry?.onResponse === undefined)
|
|
2019
|
-
return res;
|
|
2020
|
-
const fire = (status) => {
|
|
2021
|
-
safeFire(() => s.telemetry?.onResponse?.({ duration: performance.now() - fc.startTime, req: fc.request, status }), fc.log);
|
|
2022
|
-
};
|
|
2023
|
-
if (!(res instanceof Promise)) {
|
|
2024
|
-
fire(res.status);
|
|
2025
|
-
return res;
|
|
2026
|
-
}
|
|
2027
|
-
return res.then((r) => {
|
|
2028
|
-
fire(r.status);
|
|
2029
|
-
return r;
|
|
2030
|
-
}, (error) => {
|
|
2031
|
-
fire(500);
|
|
2032
|
-
throw error;
|
|
2033
|
-
});
|
|
2034
|
-
}
|
|
2035
|
-
_make404(fc) {
|
|
2036
|
-
const s = this._s;
|
|
2037
|
-
if (s.onNotFound)
|
|
2038
|
-
return s.onNotFound(this._makeErrorCtx(fc));
|
|
2039
|
-
return this._makeErrorCtx(fc).jsonFromError(this._createError(EK.not_found, SK.not_found));
|
|
2040
|
-
}
|
|
2041
|
-
async _make405(fc, allowed) {
|
|
2042
|
-
const s = this._s;
|
|
2043
|
-
const res = s.onMethodNotAllowed
|
|
2044
|
-
? await s.onMethodNotAllowed(this._makeErrorCtx(fc, allowed))
|
|
2045
|
-
: this._makeErrorCtx(fc).jsonFromError(this._createError(EK.method_not_allowed, SK.method_not_allowed));
|
|
2046
|
-
const responseHeaders = new Headers(res.headers);
|
|
2047
|
-
responseHeaders.set("allow", allowed.join(", "));
|
|
2048
|
-
return new Response(res.body, {
|
|
2049
|
-
headers: responseHeaders,
|
|
2050
|
-
status: res.status,
|
|
2051
|
-
});
|
|
2052
|
-
}
|
|
2053
|
-
/**
|
|
2054
|
-
* CORS preflight for a path that has no OPTIONS route: run the chain of the route the
|
|
2055
|
-
* preflight asks about (`Access-Control-Request-Method`) — chain, scoped and route
|
|
2056
|
-
* middleware, never input validation or the handler. Middleware that answers the
|
|
2057
|
-
* preflight (cors) short-circuits; otherwise the answer is the 405 with `Allow`.
|
|
2058
|
-
*/
|
|
2059
|
-
_handlePreflight(fc, plan, params, allowed) {
|
|
2060
|
-
fc.allowed = allowed;
|
|
2061
|
-
const ctx = this._newCtx(fc, plan, params);
|
|
2062
|
-
const final = this._finalize();
|
|
2063
|
-
plan.pf ??= compileChain(plan.pfChain, (c) => {
|
|
2064
|
-
const rq = c._rq;
|
|
2065
|
-
return this._make405(rq, rq.allowed ?? []);
|
|
2066
|
-
}, final.convert);
|
|
2067
|
-
return plan.pf(ctx);
|
|
2068
|
-
}
|
|
2069
|
-
_handleMatched(fc, plan, params) {
|
|
2070
|
-
const ctx = this._newCtx(fc, plan, params);
|
|
2071
|
-
const telemetry = this._graph.settings.telemetry;
|
|
2072
|
-
if (telemetry !== null && telemetry.onRoute !== undefined) {
|
|
2073
|
-
try {
|
|
2074
|
-
telemetry.onRoute({ method: fc.method, params, path: fc.path, req: fc.request, route: plan.r.rp ?? "" });
|
|
2075
|
-
}
|
|
2076
|
-
catch {
|
|
2077
|
-
/* telemetry must not crash request */
|
|
2078
|
-
}
|
|
2079
|
-
}
|
|
2080
|
-
const res = plan.run(ctx);
|
|
2081
|
-
if (res instanceof Promise)
|
|
2082
|
-
return res.then((r) => this._finishMatched(fc, plan, ctx, r));
|
|
2083
|
-
return this._finishMatched(fc, plan, ctx, res);
|
|
2084
|
-
}
|
|
2085
|
-
/** Output validation, inside the chain: an invalid body becomes an error response the middleware sees. */
|
|
2086
|
-
async _validateOutput(handler, ctx, response) {
|
|
2087
|
-
if (response === undefined || response === null || ctx._isErrorResponse)
|
|
2088
|
-
return response;
|
|
2089
|
-
/* read the creation tag, never `response.body`: on Node that would build a stream per response */
|
|
2090
|
-
const kind = bodyKind(response);
|
|
2091
|
-
if (kind === "empty")
|
|
2092
|
-
return response;
|
|
2093
|
-
const ct = response.headers.get("content-type");
|
|
2094
|
-
/* content-type mismatch check */
|
|
2095
|
-
if (ct && handler.os) {
|
|
2096
|
-
const declaredTypes = Object.keys(handler.os);
|
|
2097
|
-
const matches = declaredTypes.some((t) => ct.startsWith(t));
|
|
2098
|
-
if (!matches) {
|
|
2099
|
-
throw new HoneyError({
|
|
2100
|
-
errorKey: EK.output_content_type_mismatch,
|
|
2101
|
-
status: SK.internal_server_error,
|
|
2102
|
-
});
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
/* JSON schema validation. A body Honey built from a string is validated from that string
|
|
2106
|
-
* (no stream, no tee); anything else is read from a clone (Bun clone() drains the original).
|
|
2107
|
-
* A stream Honey created (sse, stream, generate) is never buffered: it may never end. */
|
|
2108
|
-
if (ct?.startsWith("application/json") && handler.ov && !isProducedStream(response)) {
|
|
2109
|
-
const sk = codeToStatusKey[response.status];
|
|
2110
|
-
if (sk) {
|
|
2111
|
-
const raw = rawBodyOf(response);
|
|
2112
|
-
if (raw !== null) {
|
|
2113
|
-
const text = typeof raw === "string" ? raw : new TextDecoder().decode(raw);
|
|
2114
|
-
await handler.ov(sk, text.length === 0 ? null : JSON.parse(text));
|
|
2115
|
-
return response;
|
|
2116
|
-
}
|
|
2117
|
-
const forReturn = response.clone();
|
|
2118
|
-
const data = await response.json();
|
|
2119
|
-
await handler.ov(sk, data);
|
|
2120
|
-
return forReturn;
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
return response;
|
|
2124
|
-
}
|
|
2125
|
-
_finishMatched(fc, plan, ctx, response) {
|
|
2126
|
-
const s = this._graph.settings;
|
|
2127
|
-
const handler = plan.r;
|
|
2128
|
-
/* taps — fire after successful handler, non-blocking */
|
|
2129
|
-
if (plan.taps !== null && !ctx._isErrorResponse) {
|
|
2130
|
-
const taps = plan.taps;
|
|
2131
|
-
const log = fc.log;
|
|
2132
|
-
/* meta-driven taps — fire for each registered key found in route meta */
|
|
2133
|
-
if (handler.mt !== null) {
|
|
2134
|
-
for (const [key, tapFn] of taps) {
|
|
2135
|
-
const metaValue = handler.mt[key];
|
|
2136
|
-
if (metaValue !== undefined) {
|
|
2137
|
-
ctx.background(Promise.resolve()
|
|
2138
|
-
.then(() => tapFn(ctx, metaValue))
|
|
2139
|
-
.catch((e) => log?.warn?.({ err: e, tap: key }, "tap failed")));
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
/* dynamic taps — fire for each c.tap() call */
|
|
2144
|
-
if (ctx._pendingTaps !== null) {
|
|
2145
|
-
for (const pending of ctx._pendingTaps) {
|
|
2146
|
-
const tapFn = taps.get(pending.key);
|
|
2147
|
-
if (tapFn !== undefined) {
|
|
2148
|
-
ctx.background(Promise.resolve()
|
|
2149
|
-
.then(() => tapFn(ctx, pending.payload))
|
|
2150
|
-
.catch((e) => log?.warn?.({ err: e, tap: pending.key }, "tap failed")));
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
ctx._pendingTaps = null;
|
|
2154
|
-
}
|
|
2155
|
-
}
|
|
2156
|
-
if (s.telemetry !== null) {
|
|
2157
|
-
try {
|
|
2158
|
-
const duration = performance.now() - fc.startTime;
|
|
2159
|
-
s.telemetry.onHandler?.({
|
|
2160
|
-
duration,
|
|
2161
|
-
method: fc.method,
|
|
2162
|
-
path: fc.path,
|
|
2163
|
-
route: handler.rp ?? "",
|
|
2164
|
-
status: response.status,
|
|
2165
|
-
});
|
|
2166
|
-
}
|
|
2167
|
-
catch {
|
|
2168
|
-
/* telemetry must never crash the response path */
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2171
|
-
if (fc.method === "HEAD")
|
|
2172
|
-
return headResponse(response);
|
|
2173
|
-
return response;
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
1
|
/**
|
|
2177
|
-
*
|
|
2178
|
-
*
|
|
2
|
+
* `@lovrozagar/honey`: the app (`honey()`), its context, errors, middleware and responses, the tree merge, and the
|
|
3
|
+
* types an app is read through. Everything optional is its own subpath; nothing here registers itself (H23).
|
|
2179
4
|
*/
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
errors(...args) {
|
|
2191
|
-
const keys = typeof args[0] === "object" && args[0] !== null ? args.slice(1) : args;
|
|
2192
|
-
for (const k of keys) {
|
|
2193
|
-
this._s.errorKeys.add(k);
|
|
2194
|
-
}
|
|
2195
|
-
return new RouteBuilder({
|
|
2196
|
-
...this._s,
|
|
2197
|
-
errorKeys: this._s.errorKeys,
|
|
2198
|
-
});
|
|
2199
|
-
}
|
|
2200
|
-
boundary(key) {
|
|
2201
|
-
this._s.boundaryErrorKey = key;
|
|
2202
|
-
this._s.errorKeys.add(key);
|
|
2203
|
-
return new RouteBuilder({
|
|
2204
|
-
...this._s,
|
|
2205
|
-
boundaryErrorKey: key,
|
|
2206
|
-
errorKeys: this._s.errorKeys,
|
|
2207
|
-
});
|
|
2208
|
-
}
|
|
2209
|
-
handler(fn) {
|
|
2210
|
-
let ov = null;
|
|
2211
|
-
const outputSchemas = this._s.outputSchemas;
|
|
2212
|
-
if (outputSchemas) {
|
|
2213
|
-
const jsonSchemas = outputSchemas["application/json"];
|
|
2214
|
-
if (jsonSchemas) {
|
|
2215
|
-
ov = async (statusKey, data) => {
|
|
2216
|
-
const schema = jsonSchemas[statusKey];
|
|
2217
|
-
if (schema) {
|
|
2218
|
-
await validateOutput(schema, statusKey, data);
|
|
2219
|
-
}
|
|
2220
|
-
};
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
const isInternal = Symbol.for("honey.internal") in fn;
|
|
2224
|
-
/* a bound copy per app: one `spec()` or docs handler may be mounted on several apps */
|
|
2225
|
-
if (isInternal)
|
|
2226
|
-
fn = bindInternalHandler(fn, this._s.parent);
|
|
2227
|
-
const parent = this._s.parent;
|
|
2228
|
-
const view = parent._view();
|
|
2229
|
-
const base = {
|
|
2230
|
-
_skip: isInternal || undefined,
|
|
2231
|
-
bek: null,
|
|
2232
|
-
cm: [...view.chain],
|
|
2233
|
-
cv: view.cv,
|
|
2234
|
-
dk: new Set(this._s.errorKeys),
|
|
2235
|
-
/* provisional until finalize resolves scopes and app defaults */
|
|
2236
|
-
ek: new Set(this._s.errorKeys),
|
|
2237
|
-
fn: fn,
|
|
2238
|
-
iv: this._s.inputSchemas,
|
|
2239
|
-
mt: mergeContributedMeta(collectMiddlewareMeta([view.chain, this._s.middlewares]), this._s.meta),
|
|
2240
|
-
mw: [],
|
|
2241
|
-
os: this._s.outputSchemas,
|
|
2242
|
-
ov,
|
|
2243
|
-
own: view.own,
|
|
2244
|
-
rb: this._s.boundaryErrorKey,
|
|
2245
|
-
rm: [...this._s.middlewares],
|
|
2246
|
-
xm: this._s.meta ? { ...this._s.meta } : null,
|
|
2247
|
-
};
|
|
2248
|
-
/* one record per method — `.on([...])` registers each under its own RouteId */
|
|
2249
|
-
parent._settle(this._s.pending);
|
|
2250
|
-
const segments = parsePattern(this._s.path);
|
|
2251
|
-
const methods = [this._s.method, ...(this._s.extraMethods ?? [])];
|
|
2252
|
-
for (const method of methods)
|
|
2253
|
-
assertBodySchemaAllowed(this._s.inputSchemas, method, this._s.path);
|
|
2254
|
-
for (let i = 0; i < methods.length; i++) {
|
|
2255
|
-
parent._addRoute(methods[i], segments, i === 0 ? base : copyRecord(base));
|
|
2256
|
-
}
|
|
2257
|
-
return this._s.parent;
|
|
2258
|
-
}
|
|
2259
|
-
proxy(config) {
|
|
2260
|
-
const proxyHandler = requireFeature("proxy", ".proxy()").createProxyHandler(config);
|
|
2261
|
-
return this.handler(proxyHandler);
|
|
2262
|
-
}
|
|
2263
|
-
input(schemas) {
|
|
2264
|
-
return new RouteBuilder({
|
|
2265
|
-
...this._s,
|
|
2266
|
-
inputSchemas: schemas,
|
|
2267
|
-
});
|
|
2268
|
-
}
|
|
2269
|
-
meta(meta) {
|
|
2270
|
-
return new RouteBuilder({
|
|
2271
|
-
...this._s,
|
|
2272
|
-
meta: (() => {
|
|
2273
|
-
const merged = { ...this._s.meta, ...meta };
|
|
2274
|
-
const inv = merged["invalidate"];
|
|
2275
|
-
if (Array.isArray(inv) && inv.length > 1) {
|
|
2276
|
-
merged["invalidate"] = [...new Set(inv)];
|
|
2277
|
-
}
|
|
2278
|
-
return merged;
|
|
2279
|
-
})(),
|
|
2280
|
-
});
|
|
2281
|
-
}
|
|
2282
|
-
output(_schemas) {
|
|
2283
|
-
return new RouteBuilder({
|
|
2284
|
-
...this._s,
|
|
2285
|
-
outputSchemas: _schemas,
|
|
2286
|
-
});
|
|
2287
|
-
}
|
|
2288
|
-
use(mw) {
|
|
2289
|
-
return new RouteBuilder({
|
|
2290
|
-
...this._s,
|
|
2291
|
-
middlewares: [...this._s.middlewares, mw],
|
|
2292
|
-
});
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
class WSRouteBuilder {
|
|
2296
|
-
_s;
|
|
2297
|
-
constructor(state) {
|
|
2298
|
-
this._s = state;
|
|
2299
|
-
}
|
|
2300
|
-
errors(factory, ...keys) {
|
|
2301
|
-
void factory;
|
|
2302
|
-
for (const k of keys) {
|
|
2303
|
-
this._s.errorKeys.add(k);
|
|
2304
|
-
}
|
|
2305
|
-
return new WSRouteBuilder({
|
|
2306
|
-
...this._s,
|
|
2307
|
-
errorKeys: this._s.errorKeys,
|
|
2308
|
-
});
|
|
2309
|
-
}
|
|
2310
|
-
handler(wsHandler) {
|
|
2311
|
-
const parent = this._s.parent;
|
|
2312
|
-
const view = parent._view();
|
|
2313
|
-
const routeHandler = {
|
|
2314
|
-
bek: null,
|
|
2315
|
-
cm: [...view.chain],
|
|
2316
|
-
cv: view.cv,
|
|
2317
|
-
dk: new Set(this._s.errorKeys),
|
|
2318
|
-
ek: new Set(),
|
|
2319
|
-
fn: wsHandler,
|
|
2320
|
-
iv: this._s.inputSchemas,
|
|
2321
|
-
mt: null,
|
|
2322
|
-
mw: [],
|
|
2323
|
-
og: this._s.origins,
|
|
2324
|
-
own: view.own,
|
|
2325
|
-
rb: null,
|
|
2326
|
-
rm: [...this._s.middlewares],
|
|
2327
|
-
rp: this._s.path,
|
|
2328
|
-
xm: this._s.meta ? { ...this._s.meta } : null,
|
|
2329
|
-
};
|
|
2330
|
-
parent._settle(this._s.pending);
|
|
2331
|
-
parent._addWsRoute(parsePattern(this._s.path), routeHandler);
|
|
2332
|
-
return this._s.parent;
|
|
2333
|
-
}
|
|
2334
|
-
input(schemas) {
|
|
2335
|
-
return new WSRouteBuilder({
|
|
2336
|
-
...this._s,
|
|
2337
|
-
inputSchemas: schemas,
|
|
2338
|
-
});
|
|
2339
|
-
}
|
|
2340
|
-
meta(meta) {
|
|
2341
|
-
this._s.meta = { ...this._s.meta, ...meta };
|
|
2342
|
-
return this;
|
|
2343
|
-
}
|
|
2344
|
-
/**
|
|
2345
|
-
* Browser origins allowed to open this socket: `"*"`, a list of origins
|
|
2346
|
-
* (`https://app.example.com`), or a predicate. Same-origin always passes. Without it, a
|
|
2347
|
-
* cross-origin upgrade that carries `Cookie` or `Authorization` gets 403.
|
|
2348
|
-
*/
|
|
2349
|
-
origins(policy) {
|
|
2350
|
-
return new WSRouteBuilder({
|
|
2351
|
-
...this._s,
|
|
2352
|
-
origins: validateOriginPolicy(policy),
|
|
2353
|
-
});
|
|
2354
|
-
}
|
|
2355
|
-
use(mw) {
|
|
2356
|
-
return new WSRouteBuilder({
|
|
2357
|
-
...this._s,
|
|
2358
|
-
middlewares: [...this._s.middlewares, mw],
|
|
2359
|
-
});
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
export function honey() {
|
|
2363
|
-
return new Honey();
|
|
2364
|
-
}
|
|
2365
|
-
//# sourceMappingURL=index.js.map
|
|
5
|
+
export { toFetchRequest } from "./adapters/fetch-request.js";
|
|
6
|
+
export { HoneyContext } from "./context/context.js";
|
|
7
|
+
export { HoneyError } from "./errors/error.js";
|
|
8
|
+
export { defineErrors, ERROR_META } from "./errors/errors.js";
|
|
9
|
+
export { createErrorResponse } from "./errors/projector.js";
|
|
10
|
+
export { createMiddleware, defineMiddleware } from "./middleware/middleware.js";
|
|
11
|
+
export { HoneyRes } from "./response/response.js";
|
|
12
|
+
export { Honey, honey } from "./routing/app.js";
|
|
13
|
+
export { mergeTree } from "./routing/tree-merge.js";
|
|
14
|
+
export { detectRuntime } from "./runtime/detect-runtime.js";
|