@lovrozagar/honey 0.7.3 → 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 -746
- package/dist/index.js +12 -2364
- 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 -780
- 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 -197
- 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 -902
- 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 -558
- 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/src/index.ts
DELETED
|
@@ -1,3829 +0,0 @@
|
|
|
1
|
-
import { bodyKind, headResponse, isProducedStream, rawBodyOf } from "./body-kind.ts"
|
|
2
|
-
import { clientInfo, HoneyContext } from "./context.ts"
|
|
3
|
-
import { dict } from "./dict.ts"
|
|
4
|
-
import { normalizePath, pathOfUrl, searchOfUrl } from "./request-path.ts"
|
|
5
|
-
import { BUN_URL_FROM_HOST, hasValidHost, TRUST_OFF, type TrustSetting } from "./client-info.ts"
|
|
6
|
-
import type { TrustProxy } from "./trust.ts"
|
|
7
|
-
import { loadFeature, requireFeature } from "./feature-slots.ts"
|
|
8
|
-
import { HoneyError } from "./error.ts"
|
|
9
|
-
import { ERROR_META } from "./errors.ts"
|
|
10
|
-
import type { ChainErrorConverter, MiddlewareFn, RuntimeMiddleware } from "./middleware.ts"
|
|
11
|
-
import { collectMiddlewareMeta, compileChain, RESERVED_CTX_KEYS } from "./middleware.ts"
|
|
12
|
-
import type { ProxyConfig } from "./proxy.ts"
|
|
13
|
-
import type { CustomErrorFormatter, ErrorFormatter, ResponseOptions, TypedResponse } from "./response.ts"
|
|
14
|
-
import { createErrorResponse, type HoneyRes } from "./response.ts"
|
|
15
|
-
import type { RouteId, Segment } from "./pattern.ts"
|
|
16
|
-
import {
|
|
17
|
-
canonical,
|
|
18
|
-
expandOptional,
|
|
19
|
-
isStaticPattern,
|
|
20
|
-
joinPatterns,
|
|
21
|
-
normalizePattern,
|
|
22
|
-
parsePattern,
|
|
23
|
-
pathInScope,
|
|
24
|
-
routeId,
|
|
25
|
-
scopeCoverage,
|
|
26
|
-
splitRouteId,
|
|
27
|
-
} from "./pattern.ts"
|
|
28
|
-
import type { OutputValidator, RouteEntry, RouteHandler, RouteTree, TreeNode, WSRouteHandler } from "./tree.ts"
|
|
29
|
-
import {
|
|
30
|
-
assertTreeFormat,
|
|
31
|
-
cloneTree,
|
|
32
|
-
createNode,
|
|
33
|
-
findLeaf,
|
|
34
|
-
forEachLeaf,
|
|
35
|
-
freezeTree,
|
|
36
|
-
hasWsLeaf,
|
|
37
|
-
insertLeaf,
|
|
38
|
-
matchRoute,
|
|
39
|
-
matchWsRoute,
|
|
40
|
-
ROUTE_TREE_VERSION,
|
|
41
|
-
} from "./tree.ts"
|
|
42
|
-
import type { RealtimeBus } from "./realtime/bus.ts"
|
|
43
|
-
import type { RealtimeConfig, RealtimeRouteOpts } from "./realtime/route.ts"
|
|
44
|
-
import type { RealtimePublisher } from "./realtime/server.ts"
|
|
45
|
-
import type {
|
|
46
|
-
ComputeErrorsByStatus,
|
|
47
|
-
DefaultMeta,
|
|
48
|
-
ExtractSchemas,
|
|
49
|
-
FieldError,
|
|
50
|
-
HttpMethod,
|
|
51
|
-
InferInputMap,
|
|
52
|
-
InferOutput,
|
|
53
|
-
HoneyMetaSpec,
|
|
54
|
-
InputSchemasDef,
|
|
55
|
-
MergePath,
|
|
56
|
-
MergeRoute,
|
|
57
|
-
MetaSpecConfig,
|
|
58
|
-
OutputSchemaDef,
|
|
59
|
-
ParamsFromPath,
|
|
60
|
-
PrefixRoutes,
|
|
61
|
-
StandardSchemaLike,
|
|
62
|
-
StatusKey,
|
|
63
|
-
TapContext,
|
|
64
|
-
} from "./types.ts"
|
|
65
|
-
import { codeToStatusKey, EK, EMPTY_OBJ, SK } from "./types.ts"
|
|
66
|
-
import { assertBodySchemaAllowed, assertRequestContentType, validateInput, validateOutput } from "./validation.ts"
|
|
67
|
-
import type { WSAdapter, WSContext, WSHandler } from "./ws/cloudflare.ts"
|
|
68
|
-
import type { InvokeLogger } from "./invoke-user.ts"
|
|
69
|
-
import { type WSOriginPolicy, validateOriginPolicy } from "./ws-origin.ts"
|
|
70
|
-
import { loadHoneyFeature } from "./feature-load.ts"
|
|
71
|
-
import { getI18nRuntime } from "./i18n-slot.ts"
|
|
72
|
-
import { bindInternalHandler, epochCached, getOpenApiRuntime } from "./openapi/spec-factory.ts"
|
|
73
|
-
import type { ServedArtifact } from "./openapi/spec-factory.ts"
|
|
74
|
-
import type { MetaSpecSource } from "./meta-spec-merge.ts"
|
|
75
|
-
import { getServeRuntime, isCodegenProcess } from "./serve-slot.ts"
|
|
76
|
-
import type { HoneyServeOptions, ServeHandle } from "./serve.ts"
|
|
77
|
-
|
|
78
|
-
export { clientInfo, HoneyContext } from "./context.ts"
|
|
79
|
-
export { toFetchRequest } from "./fetch-request.ts"
|
|
80
|
-
export type { WSOriginPolicy } from "./ws-origin.ts"
|
|
81
|
-
export type { ClientInfo } from "./client-info.ts"
|
|
82
|
-
export type { TrustProxy } from "./trust.ts"
|
|
83
|
-
/** HoneyContext without internal backing fields — use this for consumer-facing types */
|
|
84
|
-
export type HoneyCtx<TEnv = Record<string, unknown>> = Omit<
|
|
85
|
-
import("./context.ts").HoneyContext<TEnv>,
|
|
86
|
-
| "_errorToResponse"
|
|
87
|
-
| "_isErrorResponse"
|
|
88
|
-
| "_lzClient"
|
|
89
|
-
| "_lzCookies"
|
|
90
|
-
| "_lzHeaders"
|
|
91
|
-
| "_lzSearch"
|
|
92
|
-
| "_lzSearchAll"
|
|
93
|
-
| "_lzUrlFn"
|
|
94
|
-
| "_rq"
|
|
95
|
-
| "_setErrors"
|
|
96
|
-
>
|
|
97
|
-
export { HoneyError } from "./error.ts"
|
|
98
|
-
export { defineErrors, ERROR_META } from "./errors.ts"
|
|
99
|
-
export type { ErrorMetaEntry } from "./errors.ts"
|
|
100
|
-
export type { MiddlewareFn } from "./middleware.ts"
|
|
101
|
-
export { createMiddleware, defineMiddleware } from "./middleware.ts"
|
|
102
|
-
export type { ProxyConfig } from "./proxy.ts"
|
|
103
|
-
export type { PendingTap, TapContext, TapHandler } from "./types.ts"
|
|
104
|
-
export type {
|
|
105
|
-
CookieOptions,
|
|
106
|
-
CustomErrorFormatter,
|
|
107
|
-
ErrorFormatter,
|
|
108
|
-
ResponseOptions,
|
|
109
|
-
SSEEvent,
|
|
110
|
-
SSEOptions,
|
|
111
|
-
SSEStream,
|
|
112
|
-
TypedResponse,
|
|
113
|
-
} from "./response.ts"
|
|
114
|
-
export { createErrorResponse, HoneyRes } from "./response.ts"
|
|
115
|
-
export { mergeTree } from "./tree.ts"
|
|
116
|
-
export type {
|
|
117
|
-
DefaultMeta,
|
|
118
|
-
HoneyCodegen,
|
|
119
|
-
HoneyMeta,
|
|
120
|
-
InferBasePath,
|
|
121
|
-
InferCtx,
|
|
122
|
-
InferEnv,
|
|
123
|
-
InferErrorFactory,
|
|
124
|
-
InferInputMap,
|
|
125
|
-
InferMeta,
|
|
126
|
-
InferMethods,
|
|
127
|
-
InferOutput,
|
|
128
|
-
InferRouteCtx,
|
|
129
|
-
InferRouteErrors,
|
|
130
|
-
InferRouteInput,
|
|
131
|
-
InferRouteMeta,
|
|
132
|
-
InferRouteMethods,
|
|
133
|
-
InferRouteOutput,
|
|
134
|
-
InferRoutePaths,
|
|
135
|
-
InferRoutes,
|
|
136
|
-
InputSchemasDef,
|
|
137
|
-
MergePath,
|
|
138
|
-
PrefixRoutes,
|
|
139
|
-
HoneyMetaSpec,
|
|
140
|
-
MetaSpecConfig,
|
|
141
|
-
MetaSpecContext,
|
|
142
|
-
MetaSpecEntry,
|
|
143
|
-
MetaSpecMergeConflict,
|
|
144
|
-
MetaSpecExpand,
|
|
145
|
-
MetaSpecProfile,
|
|
146
|
-
MetaSpecSchemaEntry,
|
|
147
|
-
MetaSpecSchemaSearch,
|
|
148
|
-
MetaSpecSchemaSource,
|
|
149
|
-
MetaSpecSingle,
|
|
150
|
-
MetaSpecStrictness,
|
|
151
|
-
MetaSpecTarget,
|
|
152
|
-
OpenApiMeta,
|
|
153
|
-
OutputSchemaDef,
|
|
154
|
-
Overwrite,
|
|
155
|
-
ComputeErrorsByStatus,
|
|
156
|
-
RouteRecord,
|
|
157
|
-
StandardSchemaLike,
|
|
158
|
-
StatusKey,
|
|
159
|
-
SuccessStatusKey,
|
|
160
|
-
} from "./types.ts"
|
|
161
|
-
export type { WSAdapter, WSAdapterOptions, WSContext, WSHandler, WSPreUpgrade } from "./ws/cloudflare.ts"
|
|
162
|
-
export type { ConnContext, RealtimeLimits, RealtimeRouteOpts } from "./realtime/route.ts"
|
|
163
|
-
export type { RealtimePublisher } from "./realtime/server.ts"
|
|
164
|
-
export type { RealtimeBus } from "./realtime/bus.ts"
|
|
165
|
-
export type { HoneyServeOptions, ServeHandle } from "./serve.ts"
|
|
166
|
-
export type { ServeRuntime } from "./detect-runtime.ts"
|
|
167
|
-
export { detectRuntime } from "./detect-runtime.ts"
|
|
168
|
-
|
|
169
|
-
type TelemetryAdapter = {
|
|
170
|
-
onError?(ctx: { duration: number; error: HoneyError; method: string; path: string }): void
|
|
171
|
-
/** `path` is the request path; `route` the matched pattern (`/users/:id`). */
|
|
172
|
-
onHandler?(ctx: { duration: number; method: string; path: string; route: string; status: number }): void
|
|
173
|
-
onMethodNotAllowed?(ctx: { allowed: string[]; method: string; path: string; req: Request }): void
|
|
174
|
-
onMiddleware?(ctx: { duration: number; error?: unknown; name: string }): void
|
|
175
|
-
onNotFound?(ctx: { method: string; path: string; req: Request }): void
|
|
176
|
-
onRequest?(ctx: { env: unknown; req: Request }): void
|
|
177
|
-
/** Fires exactly once per request that fired `onRequest`, whichever way it exits. */
|
|
178
|
-
onResponse?(ctx: { duration: number; req: Request; status: number }): void
|
|
179
|
-
/** `path` is the request path; `route` the matched pattern (`/users/:id`). */
|
|
180
|
-
onRoute?(ctx: { method: string; params: Record<string, string>; path: string; req: Request; route: string }): void
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
type ErrorI18nConfig<TEnv> = {
|
|
184
|
-
errors?: Record<string, Record<string, string>>
|
|
185
|
-
fieldNames?: Record<string, Record<string, string>>
|
|
186
|
-
resolveLocale: (ctx: {
|
|
187
|
-
cookies: Record<string, string>
|
|
188
|
-
env: TEnv
|
|
189
|
-
headers: Record<string, string>
|
|
190
|
-
params: Record<string, string>
|
|
191
|
-
req: Request
|
|
192
|
-
search: Record<string, string>
|
|
193
|
-
}) => string | Promise<string>
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/** The warn side of a structured logger (`warn(obj, msg)` or `warn(msg)`), e.g. honey/logger. */
|
|
197
|
-
type Logger = {
|
|
198
|
-
warn?(objOrMsg: Record<string, unknown> | string, msg?: string): void
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
type ErrorCtx<TEnv> = { env: TEnv; jsonFromError: (err: HoneyError) => Response; req: Request }
|
|
202
|
-
|
|
203
|
-
type OnErrorFn<TEnv> = (
|
|
204
|
-
error: unknown,
|
|
205
|
-
ctx: ErrorCtx<TEnv>,
|
|
206
|
-
) => HoneyError | Response | Promise<HoneyError | Response | undefined | void> | undefined | void
|
|
207
|
-
|
|
208
|
-
type OnMethodNotAllowedFn<TEnv> = (ctx: ErrorCtx<TEnv> & { allowed: string[] }) => Response | Promise<Response>
|
|
209
|
-
|
|
210
|
-
type OnNotFoundFn<TEnv> = (ctx: ErrorCtx<TEnv>) => Response | Promise<Response>
|
|
211
|
-
|
|
212
|
-
type TapFn<TEnv> = (ctx: TapContext<TEnv>, payload: unknown) => void | Promise<void>
|
|
213
|
-
|
|
214
|
-
type ErrorFactoryRecord = Record<string, (...args: never[]) => unknown>
|
|
215
|
-
|
|
216
|
-
type OutputValidationMode = "always" | "dev" | "off"
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Settings of one app — shared by every handle derived from it (`use()`, `basePath()`,
|
|
220
|
-
* `context()`, `meta()`), so a setter called on any handle applies to all of them and
|
|
221
|
-
* serving any handle behaves the same. Records point at the settings of the app they were
|
|
222
|
-
* registered on, so a mounted sub-app's routes keep their own error factory, default errors,
|
|
223
|
-
* boundary and output validation.
|
|
224
|
-
*/
|
|
225
|
-
type AppSettings<TEnv = unknown> = {
|
|
226
|
-
customErrorFormatter: CustomErrorFormatter | null
|
|
227
|
-
customErrorSchema: StandardSchemaLike | null
|
|
228
|
-
defaultBoundaryKey: string | null
|
|
229
|
-
defaultErrorKeys: Set<string>
|
|
230
|
-
errorFactory: ErrorFactoryRecord | null
|
|
231
|
-
errorFormatter: ErrorFormatterFn
|
|
232
|
-
errorI18n: ErrorI18nConfig<TEnv> | null
|
|
233
|
-
errorSchema: StandardSchemaLike | null
|
|
234
|
-
logger: Logger | null
|
|
235
|
-
onError: OnErrorFn<TEnv> | null
|
|
236
|
-
onMethodNotAllowed: OnMethodNotAllowedFn<TEnv> | null
|
|
237
|
-
onNotFound: OnNotFoundFn<TEnv> | null
|
|
238
|
-
/** null = never set: a mounted route follows the mounting app's mode */
|
|
239
|
-
outputValidation: OutputValidationMode | null
|
|
240
|
-
stripPrefix: string | null
|
|
241
|
-
taps: Map<string, TapFn<TEnv>> | null
|
|
242
|
-
telemetry: TelemetryAdapter | null
|
|
243
|
-
trailingSlash: "enforce" | "ignore" | "strip"
|
|
244
|
-
encodedSlashes: "allow" | "reject"
|
|
245
|
-
trust: TrustSetting
|
|
246
|
-
wsAdapter: WSAdapter | null
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function createSettings<TEnv>(): AppSettings<TEnv> {
|
|
250
|
-
return {
|
|
251
|
-
customErrorFormatter: null,
|
|
252
|
-
customErrorSchema: null,
|
|
253
|
-
defaultBoundaryKey: null,
|
|
254
|
-
defaultErrorKeys: new Set(),
|
|
255
|
-
errorFactory: null,
|
|
256
|
-
errorFormatter: defaultErrorFormatter,
|
|
257
|
-
errorI18n: null,
|
|
258
|
-
errorSchema: null,
|
|
259
|
-
logger: null,
|
|
260
|
-
onError: null,
|
|
261
|
-
onMethodNotAllowed: null,
|
|
262
|
-
onNotFound: null,
|
|
263
|
-
outputValidation: null,
|
|
264
|
-
stripPrefix: null,
|
|
265
|
-
taps: null,
|
|
266
|
-
telemetry: null,
|
|
267
|
-
trailingSlash: "ignore",
|
|
268
|
-
encodedSlashes: "reject",
|
|
269
|
-
trust: TRUST_OFF,
|
|
270
|
-
wsAdapter: null,
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/** Join a base path and a route or scope path into one canonical pattern. */
|
|
275
|
-
function mergePath(base: string, path: string): string {
|
|
276
|
-
return joinPatterns(base, path)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Everything a matched route runs, compiled once at finalize: the record (with its resolved
|
|
281
|
-
* error keys, meta and chain) and one function that runs chain + handler and never rejects.
|
|
282
|
-
*/
|
|
283
|
-
type Plan = {
|
|
284
|
-
/** `.context()` values copied onto each request context */
|
|
285
|
-
cv: Record<string, unknown> | null
|
|
286
|
-
/** declared error keys are enforced (the route, its middleware or the app declared any) */
|
|
287
|
-
enf: boolean
|
|
288
|
-
/** preflight runner — chain without input validation or handler; compiled on first preflight */
|
|
289
|
-
pf: CompiledChain | null
|
|
290
|
-
/** what the preflight runner runs */
|
|
291
|
-
pfChain: RuntimeMiddleware[]
|
|
292
|
-
r: RouteHandler | WSRouteHandler
|
|
293
|
-
/** realtime route config when this is a realtime route */
|
|
294
|
-
rt: RealtimeConfig | null
|
|
295
|
-
run: CompiledChain
|
|
296
|
-
/** taps of the serving app, plus those of the app the route came from */
|
|
297
|
-
taps: Map<string, TapFn<unknown>> | null
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
type CompiledChain = (ctx: object) => Response | Promise<Response>
|
|
301
|
-
|
|
302
|
-
/** What one app graph serves, resolved at finalize from records and the loaded tree. */
|
|
303
|
-
type FinalTable = {
|
|
304
|
-
byId: Map<RouteId, RouteHandler>
|
|
305
|
-
convert: ChainErrorConverter
|
|
306
|
-
epoch: number
|
|
307
|
-
/** 404 / 405: the middleware every route runs, plus the scopes covering the request path */
|
|
308
|
-
miss: Plan
|
|
309
|
-
plans: Map<RouteId, Plan>
|
|
310
|
-
/** `METHOD /path` → plan, for routes without params or wildcards */
|
|
311
|
-
statics: Record<string, Plan>
|
|
312
|
-
wsById: Map<RouteId, WSRouteHandler>
|
|
313
|
-
wsPlans: Map<RouteId, Plan>
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/** One `app.use(mw)` — the handle it returns has to register a route, or `mw` runs nowhere. */
|
|
317
|
-
type ChainNode = { mw: RuntimeMiddleware; parent: ChainNode | null; used: boolean }
|
|
318
|
-
|
|
319
|
-
/** A route builder still waiting for `.handler()` — reported at finalize. */
|
|
320
|
-
type PendingRoute = { id: string }
|
|
321
|
-
|
|
322
|
-
/** @internal — runtime entry for a scoped middleware */
|
|
323
|
-
type ScopedEntry = {
|
|
324
|
-
/** cached from mw.errors at registration; undefined when none */
|
|
325
|
-
errors: readonly string[] | undefined
|
|
326
|
-
/** the middleware behind a request-path check, for routes the scope covers only partly */
|
|
327
|
-
guard: RuntimeMiddleware
|
|
328
|
-
mw: RuntimeMiddleware
|
|
329
|
-
/** canonical full-path pattern (already rebased against basePath at .use time) */
|
|
330
|
-
prefix: string
|
|
331
|
-
segs: Segment[]
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* One app graph — shared by every handle derived with `use()`, `basePath()`, `context()`,
|
|
336
|
-
* `meta()`. Records are per graph, keyed by `RouteId`; the tree holds ids only, so a loaded
|
|
337
|
-
* (shared, frozen) tree never carries another app's handlers.
|
|
338
|
-
*/
|
|
339
|
-
type HoneyGraph = {
|
|
340
|
-
/** metaSpec policies of mounted sub-apps, merged under `metaSpec` when codegen reads it */
|
|
341
|
-
absorbedMetaSpecs: MetaSpecSource[]
|
|
342
|
-
/** gateway catch-alls by method — root wildcards registered over a loaded tree that lacks them */
|
|
343
|
-
catchAll: Map<string, RouteHandler>
|
|
344
|
-
/** every `use(mw)` handle — one that never registers a route is reported at finalize */
|
|
345
|
-
chains: ChainNode[]
|
|
346
|
-
/** bumped by every registration; finalize re-runs when it moves */
|
|
347
|
-
epoch: number
|
|
348
|
-
final: FinalTable | null
|
|
349
|
-
/** app-wide middleware that runs before every chain (serve({ cors })), keyed so a re-serve replaces it */
|
|
350
|
-
global: Map<string, RuntimeMiddleware>
|
|
351
|
-
hasWs: boolean
|
|
352
|
-
/** route data of the loaded tree (per-graph copies) — null when no tree was loaded */
|
|
353
|
-
loaded: Map<RouteId, RouteEntry> | null
|
|
354
|
-
/** Codegen-time meta → OpenAPI policy declared on this app. Never read on the request path */
|
|
355
|
-
metaSpec: MetaSpecConfig | null
|
|
356
|
-
/** route builders still waiting for `.handler()` */
|
|
357
|
-
pending: Set<PendingRoute>
|
|
358
|
-
realtimeBus: RealtimeBus | null
|
|
359
|
-
/** `ctx.realtime`, shared by every request of this graph */
|
|
360
|
-
realtimeCtx: RealtimePublisher | null
|
|
361
|
-
realtimeRoutes: Map<string, RealtimeConfig>
|
|
362
|
-
records: Map<RouteId, RouteHandler>
|
|
363
|
-
root: TreeNode
|
|
364
|
-
/** root is a loaded tree (frozen, possibly shared by other apps) — copy before inserting */
|
|
365
|
-
rootShared: boolean
|
|
366
|
-
/** path-scoped middleware, in registration order */
|
|
367
|
-
scoped: ScopedEntry[]
|
|
368
|
-
/** chains of the handles that served a request — decide what 404 and 405 run when there are no routes */
|
|
369
|
-
served: Set<RuntimeMiddleware[]>
|
|
370
|
-
settings: AppSettings<unknown>
|
|
371
|
-
/** registered after routeTree() with no leaf in the loaded tree — stale generated file */
|
|
372
|
-
unexpected: Set<RouteId>
|
|
373
|
-
/** the loaded tree came in while `honey generate` loaded the app: a stale one is replaced, not fatal */
|
|
374
|
-
unloadIfStale: boolean
|
|
375
|
-
wsRecords: Map<RouteId, WSRouteHandler>
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/** Outcome of mounting an internal route: done, or who already owns the path. */
|
|
379
|
-
type MountResult = "internal" | "mounted" | "user"
|
|
380
|
-
|
|
381
|
-
function createGraph(): HoneyGraph {
|
|
382
|
-
return {
|
|
383
|
-
absorbedMetaSpecs: [],
|
|
384
|
-
catchAll: new Map(),
|
|
385
|
-
chains: [],
|
|
386
|
-
epoch: 0,
|
|
387
|
-
final: null,
|
|
388
|
-
global: new Map(),
|
|
389
|
-
hasWs: false,
|
|
390
|
-
loaded: null,
|
|
391
|
-
metaSpec: null,
|
|
392
|
-
pending: new Set(),
|
|
393
|
-
realtimeBus: null,
|
|
394
|
-
realtimeCtx: null,
|
|
395
|
-
realtimeRoutes: new Map(),
|
|
396
|
-
records: new Map(),
|
|
397
|
-
root: createNode(),
|
|
398
|
-
rootShared: false,
|
|
399
|
-
scoped: [],
|
|
400
|
-
served: new Set(),
|
|
401
|
-
settings: createSettings(),
|
|
402
|
-
unexpected: new Set(),
|
|
403
|
-
unloadIfStale: false,
|
|
404
|
-
wsRecords: new Map(),
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/** Fresh copy of a record for another graph — sources are copied, resolved fields recomputed there. */
|
|
409
|
-
function copyRecord<T extends RouteHandler | WSRouteHandler>(r: T): T {
|
|
410
|
-
const out = { ...r, ek: new Set(r.ek) } as T
|
|
411
|
-
if (r.dk !== undefined) out.dk = new Set(r.dk)
|
|
412
|
-
if (r.cm !== undefined) out.cm = [...r.cm]
|
|
413
|
-
if (r.rm !== undefined) out.rm = [...r.rm]
|
|
414
|
-
delete (out as RouteHandler).ca
|
|
415
|
-
return out
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/** Every leaf a pattern occupies: an optional last param places two. */
|
|
419
|
-
function leafVariants(segments: readonly Segment[]): Segment[][] {
|
|
420
|
-
return expandOptional(segments)
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/** Record the error behind an error response on `ctx.error` (read-only for user code). */
|
|
424
|
-
function setCtxError(ctx: HoneyContext<unknown>, error: HoneyError): void {
|
|
425
|
-
;(ctx as unknown as Record<string, unknown>)["error"] = error
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/** What `ctx.error` holds for a throw the boundary could not resolve: the 500 it became. */
|
|
429
|
-
function asHoneyError(thrown: unknown): HoneyError {
|
|
430
|
-
return thrown instanceof HoneyError
|
|
431
|
-
? thrown
|
|
432
|
-
: new HoneyError({ cause: thrown, errorKey: EK.internal_server_error, status: SK.internal_server_error })
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function patternOf(id: RouteId): { method: string; segments: Segment[] } {
|
|
436
|
-
const { method, pattern } = splitRouteId(id)
|
|
437
|
-
return { method, segments: parsePattern(pattern) }
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/** `fn` of a tree leaf no handler serves — dispatch answers 404, codegen still documents it. */
|
|
441
|
-
const NOT_SERVED: RouteHandler["fn"] = () => {
|
|
442
|
-
throw new Error("honey: unserved route reached its handler")
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
function isRootWildcard(segments: readonly Segment[]): boolean {
|
|
446
|
-
return segments.length === 1 && segments[0].k === "wildcard"
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const EMPTY_PARAMS = EMPTY_OBJ as Record<string, string>
|
|
450
|
-
|
|
451
|
-
function requestIsWsUpgrade(request: Request): boolean {
|
|
452
|
-
try {
|
|
453
|
-
return request.headers.get("upgrade")?.toLowerCase() === "websocket"
|
|
454
|
-
} catch {
|
|
455
|
-
/* Deno closes the Request after upgradeWebSocket */
|
|
456
|
-
return false
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/** The info object `Deno.serve` passes as the second handler argument. */
|
|
461
|
-
function isDenoServeInfo(env: unknown): boolean {
|
|
462
|
-
return env !== null && typeof env === "object" && (env as { remoteAddr?: unknown }).remoteAddr !== undefined
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/** `obj[key]` for an own key only; `undefined` for inherited names such as `constructor`. */
|
|
466
|
-
function safeFire(fn: (() => unknown) | undefined, logger?: Logger): void {
|
|
467
|
-
if (fn === undefined) return
|
|
468
|
-
try {
|
|
469
|
-
const result = fn()
|
|
470
|
-
if (result && typeof result === "object" && "catch" in result) {
|
|
471
|
-
;(result as Promise<unknown>).catch((e: unknown) => {
|
|
472
|
-
logger?.warn?.({ err: e }, "telemetry callback failed")
|
|
473
|
-
})
|
|
474
|
-
}
|
|
475
|
-
} catch (e) {
|
|
476
|
-
logger?.warn?.({ err: e }, "telemetry callback failed")
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
/** Per-request state, shared by the dispatcher, the error boundary and terminal handlers. */
|
|
481
|
-
type FetchCtx<TEnv> = {
|
|
482
|
-
/** methods a 405 lists; also marks the miss pipeline as a 405 */
|
|
483
|
-
allowed: string[] | null
|
|
484
|
-
env: TEnv
|
|
485
|
-
executionCtx: { waitUntil?: (p: Promise<unknown>) => void } | undefined
|
|
486
|
-
log: Logger | undefined
|
|
487
|
-
method: string
|
|
488
|
-
path: string
|
|
489
|
-
plan: Plan | null
|
|
490
|
-
request: Request
|
|
491
|
-
startTime: number
|
|
492
|
-
/** the app's trustProxy() setting, read by ctx.ip and clientInfo() */
|
|
493
|
-
trust: TrustSetting
|
|
494
|
-
url: () => URL
|
|
495
|
-
/** matched websocket route, for the upgrade terminal */
|
|
496
|
-
ws: { handler: WSRouteHandler; params: Record<string, string> } | null
|
|
497
|
-
/** Already known from fetch() before Deno.upgradeWebSocket consumes the request. */
|
|
498
|
-
wsUpgrade?: boolean
|
|
499
|
-
/** Headers copied before Deno.upgradeWebSocket closes the Request. */
|
|
500
|
-
headerSnap?: Headers
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function requestWithHeaders(req: Request, headers: Headers): Request {
|
|
504
|
-
return new Proxy(req, {
|
|
505
|
-
get(target, prop) {
|
|
506
|
-
if (prop === "headers") return headers
|
|
507
|
-
const value = Reflect.get(target, prop, target)
|
|
508
|
-
return typeof value === "function" ? (value as (...args: never[]) => unknown).bind(target) : value
|
|
509
|
-
},
|
|
510
|
-
})
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function ctxRequest(fc: FetchCtx<unknown>): Request {
|
|
514
|
-
if (!fc.headerSnap) return fc.request
|
|
515
|
-
try {
|
|
516
|
-
void fc.request.headers.get("upgrade")
|
|
517
|
-
return fc.request
|
|
518
|
-
} catch {
|
|
519
|
-
return requestWithHeaders(fc.request, fc.headerSnap)
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
function defaultErrorFormatter(_error: HoneyError, defaultShape: Record<string, unknown>): Record<string, unknown> {
|
|
524
|
-
return defaultShape
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
type ErrorFormatterFn = ErrorFormatter
|
|
528
|
-
|
|
529
|
-
/* errorKeys the framework throws on its own behalf — input/output validation, content negotiation,
|
|
530
|
-
* routing, and the shipped middleware. Always passes the boundary check; users never declare these via .errors(). */
|
|
531
|
-
const FRAMEWORK_EKS = new Set<string>([
|
|
532
|
-
EK.validation_failed,
|
|
533
|
-
EK.output_validation_failed,
|
|
534
|
-
EK.output_content_type_mismatch,
|
|
535
|
-
EK.unsupported_media_type,
|
|
536
|
-
EK.content_too_large,
|
|
537
|
-
EK.method_not_allowed,
|
|
538
|
-
EK.not_found,
|
|
539
|
-
EK.too_many_requests,
|
|
540
|
-
EK.request_timeout,
|
|
541
|
-
EK.gateway_timeout,
|
|
542
|
-
EK.bad_gateway,
|
|
543
|
-
EK.forbidden,
|
|
544
|
-
EK.bad_request,
|
|
545
|
-
EK.malformed_body,
|
|
546
|
-
])
|
|
547
|
-
|
|
548
|
-
function shouldValidateOutput(mode: OutputValidationMode): boolean {
|
|
549
|
-
if (mode === "always") return true
|
|
550
|
-
if (mode === "off") return false
|
|
551
|
-
return (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV !== "production"
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
/** The scoped middleware behind a request-path check: runs only when the path is inside the scope. */
|
|
555
|
-
function scopeGuard(segs: readonly Segment[], mw: RuntimeMiddleware): RuntimeMiddleware {
|
|
556
|
-
const guard: RuntimeMiddleware = (ctx, next) => (pathInScope(ctx["path"] as string, segs) ? mw(ctx, next) : next())
|
|
557
|
-
Object.defineProperty(guard, "name", { configurable: true, value: mw.name })
|
|
558
|
-
const tagged = mw as { errors?: readonly string[]; meta?: Readonly<Record<string, unknown>> }
|
|
559
|
-
if (tagged.errors) Object.defineProperty(guard, "errors", { value: tagged.errors })
|
|
560
|
-
if (tagged.meta) Object.defineProperty(guard, "meta", { value: tagged.meta })
|
|
561
|
-
return guard
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/** Wrap a middleware so telemetry sees its own duration and its own throw. Built once at finalize. */
|
|
565
|
-
function timedMiddleware(
|
|
566
|
-
mw: RuntimeMiddleware,
|
|
567
|
-
onMw: NonNullable<TelemetryAdapter["onMiddleware"]>,
|
|
568
|
-
log: Logger | undefined,
|
|
569
|
-
): RuntimeMiddleware {
|
|
570
|
-
const name = mw.name || "anonymous"
|
|
571
|
-
return async (ctx, next) => {
|
|
572
|
-
const start = performance.now()
|
|
573
|
-
try {
|
|
574
|
-
const res = await mw(ctx, next)
|
|
575
|
-
safeFire(() => onMw({ duration: performance.now() - start, name }), log)
|
|
576
|
-
return res
|
|
577
|
-
} catch (error) {
|
|
578
|
-
safeFire(() => onMw({ duration: performance.now() - start, error, name }), log)
|
|
579
|
-
throw error
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
function errorsOf(mw: RuntimeMiddleware): readonly string[] | undefined {
|
|
585
|
-
return (mw as { errors?: readonly string[] }).errors
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
/** Longest run of middleware every record starts its chain with — what 404 and 405 run. */
|
|
589
|
-
function commonPrefix(chains: ReadonlyArray<readonly RuntimeMiddleware[]>): RuntimeMiddleware[] {
|
|
590
|
-
if (chains.length === 0) return []
|
|
591
|
-
let out = [...(chains[0] as readonly RuntimeMiddleware[])]
|
|
592
|
-
for (let i = 1; i < chains.length && out.length > 0; i++) {
|
|
593
|
-
const c = chains[i] as readonly RuntimeMiddleware[]
|
|
594
|
-
let n = 0
|
|
595
|
-
while (n < out.length && n < c.length && out[n] === c[n]) n++
|
|
596
|
-
out = out.slice(0, n)
|
|
597
|
-
}
|
|
598
|
-
return out
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
function describeMw(mw: RuntimeMiddleware): string {
|
|
602
|
-
return mw.name ? `"${mw.name}"` : "an anonymous middleware"
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
export class Honey<
|
|
606
|
-
TEnv,
|
|
607
|
-
TCtx = HoneyContext<TEnv>,
|
|
608
|
-
TRoutes = {},
|
|
609
|
-
TMeta = never,
|
|
610
|
-
TErrorFactory = never,
|
|
611
|
-
TDefaultErrors extends string = never,
|
|
612
|
-
TBasePath extends string = "/",
|
|
613
|
-
TTaps extends Record<string, unknown> = {},
|
|
614
|
-
TScopedMw extends readonly ScopedMwEntry[] = [],
|
|
615
|
-
> {
|
|
616
|
-
declare readonly $basePath: TBasePath
|
|
617
|
-
declare readonly $ctx: TCtx
|
|
618
|
-
declare readonly $env: TEnv
|
|
619
|
-
declare readonly $errorFactory: TErrorFactory
|
|
620
|
-
declare readonly $meta: TMeta
|
|
621
|
-
declare readonly $routes: TRoutes
|
|
622
|
-
declare readonly $taps: TTaps
|
|
623
|
-
/* view state — what routes registered through this handle capture */
|
|
624
|
-
private _basePath: string
|
|
625
|
-
private _chain: RuntimeMiddleware[]
|
|
626
|
-
private _chainMeta: Record<string, unknown> | null
|
|
627
|
-
private _contextValues: Record<string, unknown> | null
|
|
628
|
-
private _graph: HoneyGraph
|
|
629
|
-
/** the `use(mw)` call that created this chain — null on the root handle */
|
|
630
|
-
private _node: ChainNode | null
|
|
631
|
-
|
|
632
|
-
constructor(opts?: { graph?: HoneyGraph }) {
|
|
633
|
-
this._basePath = "/"
|
|
634
|
-
this._chain = []
|
|
635
|
-
this._chainMeta = null
|
|
636
|
-
this._contextValues = null
|
|
637
|
-
this._graph = opts?.graph ?? createGraph()
|
|
638
|
-
this._node = null
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
private get _s(): AppSettings<TEnv> {
|
|
642
|
-
return this._graph.settings as AppSettings<TEnv>
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
/**
|
|
646
|
-
* @internal — read by codegen through `metaSpecOf()`: the declared policy and every mounted
|
|
647
|
-
* sub-app's, unmerged. Merged on read, so `metaSpec()` may come before or after `route(sub)`;
|
|
648
|
-
* the merge lives with codegen so a served app never loads it.
|
|
649
|
-
*/
|
|
650
|
-
private get _metaSpecSource(): MetaSpecSource {
|
|
651
|
-
return { absorbed: this._graph.absorbedMetaSpecs, declared: this._graph.metaSpec }
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
/** @internal — read by codegen and OpenAPI */
|
|
655
|
-
get _errorFactory(): ErrorFactoryRecord | null {
|
|
656
|
-
return this._graph.settings.errorFactory
|
|
657
|
-
}
|
|
658
|
-
set _errorFactory(factory: ErrorFactoryRecord | null) {
|
|
659
|
-
this._graph.settings.errorFactory = factory
|
|
660
|
-
this._bumpEpoch()
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
/** @internal — read by OpenAPI */
|
|
664
|
-
get _errorSchema(): StandardSchemaLike | null {
|
|
665
|
-
return this._graph.settings.errorSchema
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
/** @internal — read by OpenAPI */
|
|
669
|
-
get _customErrorSchema(): StandardSchemaLike | null {
|
|
670
|
-
return this._graph.settings.customErrorSchema
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
private get _root(): TreeNode {
|
|
674
|
-
return this._graph.root
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
private get _realtimeBus(): RealtimeBus | null {
|
|
678
|
-
return this._graph.realtimeBus
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
/** A new handle on the same graph, carrying this handle's view. */
|
|
682
|
-
private _derive(chain: RuntimeMiddleware[] = this._chain): Honey<TEnv> {
|
|
683
|
-
const next = new Honey<TEnv>({ graph: this._graph })
|
|
684
|
-
next._basePath = this._basePath
|
|
685
|
-
next._chain = chain
|
|
686
|
-
next._chainMeta = this._chainMeta
|
|
687
|
-
next._contextValues = this._contextValues
|
|
688
|
-
next._node = this._node
|
|
689
|
-
return next
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
/** This handle registers, mounts or serves — every `use()` on its way here is in use. */
|
|
693
|
-
private _markUsed(): void {
|
|
694
|
-
let node = this._node
|
|
695
|
-
while (node !== null && !node.used) {
|
|
696
|
-
node.used = true
|
|
697
|
-
node = node.parent
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
/** @internal — used by RouteBuilder for pre-filtered error factory */
|
|
702
|
-
get _factory(): unknown {
|
|
703
|
-
return this._graph.settings.errorFactory
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
/** @internal — used by runtime error boundary */
|
|
707
|
-
get _boundaryKey(): string | null {
|
|
708
|
-
return this._graph.settings.defaultBoundaryKey
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/** Convert an unknown thrown value to a plain error Response — no boundary, no onError. */
|
|
712
|
-
private _toErrorResponse(thrown: unknown): Response {
|
|
713
|
-
const error =
|
|
714
|
-
thrown instanceof HoneyError
|
|
715
|
-
? thrown
|
|
716
|
-
: new HoneyError({
|
|
717
|
-
cause: thrown,
|
|
718
|
-
errorKey: EK.internal_server_error,
|
|
719
|
-
status: SK.internal_server_error,
|
|
720
|
-
})
|
|
721
|
-
const s = this._s
|
|
722
|
-
return createErrorResponse(error, s.errorFormatter, s.customErrorFormatter)
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
private _createBoundaryError(errorKey: string, cause: unknown, fac: ErrorFactoryRecord | null): HoneyError {
|
|
726
|
-
const factory = fac as Record<string, ((opts?: { cause?: unknown }) => HoneyError) | undefined> | null
|
|
727
|
-
const factoryFn = factory?.[errorKey]
|
|
728
|
-
if (factoryFn) {
|
|
729
|
-
/* check if this is a custom schema error via ERROR_META — boundary must use standard errors only */
|
|
730
|
-
const meta = (factory as Record<symbol, Record<string, { schema: unknown }>>)?.[ERROR_META]
|
|
731
|
-
if (meta?.[errorKey]?.schema) {
|
|
732
|
-
/* custom schema error — cannot be used as boundary, fall through to manual construction */
|
|
733
|
-
} else {
|
|
734
|
-
return factoryFn({ cause })
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
return new HoneyError({
|
|
738
|
-
cause,
|
|
739
|
-
errorKey,
|
|
740
|
-
status: SK.internal_server_error,
|
|
741
|
-
})
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
private _createError(errorKey: string, statusKey: StatusKey): HoneyError {
|
|
745
|
-
const factory = this._s.errorFactory as Record<string, (() => HoneyError) | undefined> | null
|
|
746
|
-
const factoryFn = factory?.[errorKey]
|
|
747
|
-
if (factoryFn) {
|
|
748
|
-
return factoryFn()
|
|
749
|
-
}
|
|
750
|
-
return new HoneyError({ errorKey, status: statusKey })
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
/**
|
|
754
|
-
* The i18n-resolved copy of `honeyError`: message from the locale's template for its
|
|
755
|
-
* errorKey, field paths from the locale's field names. Returns `honeyError` itself when
|
|
756
|
-
* i18n is not configured or nothing matches. Never mutates it: an error instance can be
|
|
757
|
-
* thrown again, on another request, in another locale.
|
|
758
|
-
*/
|
|
759
|
-
private async _resolveI18n(
|
|
760
|
-
honeyError: HoneyError,
|
|
761
|
-
ctx: HoneyContext<TEnv>,
|
|
762
|
-
env: TEnv,
|
|
763
|
-
request: Request,
|
|
764
|
-
log?: Logger,
|
|
765
|
-
): Promise<HoneyError> {
|
|
766
|
-
const i18n = this._s.errorI18n
|
|
767
|
-
if (!i18n) return honeyError
|
|
768
|
-
try {
|
|
769
|
-
const locale = await i18n.resolveLocale({
|
|
770
|
-
cookies: ctx.cookies,
|
|
771
|
-
env,
|
|
772
|
-
headers: ctx.headers,
|
|
773
|
-
params: ctx.params,
|
|
774
|
-
req: request,
|
|
775
|
-
search: ctx.search,
|
|
776
|
-
})
|
|
777
|
-
await loadHoneyFeature("i18n")
|
|
778
|
-
return getI18nRuntime().translateError(honeyError, i18n, locale)
|
|
779
|
-
} catch (e) {
|
|
780
|
-
log?.warn?.({ err: e }, "i18n resolution failed")
|
|
781
|
-
return honeyError
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
/**
|
|
786
|
-
* The error boundary of every `next()`: turns what a handler or middleware threw into the
|
|
787
|
-
* Response that replaces it, so every middleware around it sees a Response. Never rejects.
|
|
788
|
-
*/
|
|
789
|
-
private _convertError(thrown: unknown, ctx: HoneyContext<TEnv>): Response | Promise<Response> {
|
|
790
|
-
const fc = ctx._rq as FetchCtx<TEnv> | null
|
|
791
|
-
const plan = fc?.plan
|
|
792
|
-
if (fc === null || fc === undefined || plan === null || plan === undefined) {
|
|
793
|
-
setCtxError(ctx, asHoneyError(thrown))
|
|
794
|
-
return this._safeErrorResponse(thrown)
|
|
795
|
-
}
|
|
796
|
-
const fallback = (e: unknown): Response => {
|
|
797
|
-
fc.log?.warn?.({ err: e }, "error response failed")
|
|
798
|
-
setCtxError(ctx, asHoneyError(thrown))
|
|
799
|
-
return this._safeErrorResponse(thrown)
|
|
800
|
-
}
|
|
801
|
-
try {
|
|
802
|
-
return this._resolveErrorResponse(thrown, plan, fc, ctx).catch(fallback)
|
|
803
|
-
} catch (e) {
|
|
804
|
-
return fallback(e)
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
/** Last resort when even the configured formatter throws. */
|
|
809
|
-
private _safeErrorResponse(thrown: unknown): Response {
|
|
810
|
-
try {
|
|
811
|
-
return this._toErrorResponse(thrown instanceof HoneyError ? thrown : undefined)
|
|
812
|
-
} catch {
|
|
813
|
-
return new Response(JSON.stringify({ error: { errorKey: EK.internal_server_error } }), {
|
|
814
|
-
headers: { "content-type": "application/json" },
|
|
815
|
-
status: 500,
|
|
816
|
-
})
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
/**
|
|
821
|
-
* Convert thrown value into an error Response — resolves boundary wrapping, i18n
|
|
822
|
-
* translation, the onError callback, and error telemetry. `onResponse` telemetry is not
|
|
823
|
-
* fired here: the request fires it exactly once, on the way out.
|
|
824
|
-
*/
|
|
825
|
-
private async _resolveErrorResponse(
|
|
826
|
-
thrown: unknown,
|
|
827
|
-
plan: Plan,
|
|
828
|
-
fc: FetchCtx<TEnv>,
|
|
829
|
-
ctx: HoneyContext<TEnv>,
|
|
830
|
-
): Promise<Response> {
|
|
831
|
-
const { env, log, request, startTime, method, path } = fc
|
|
832
|
-
const s = this._s
|
|
833
|
-
const record = plan.r
|
|
834
|
-
const fac = (record.fac ?? null) as ErrorFactoryRecord | null
|
|
835
|
-
let honeyError: HoneyError
|
|
836
|
-
const boundaryKey = record.bek
|
|
837
|
-
|
|
838
|
-
if (thrown instanceof HoneyError) {
|
|
839
|
-
/* framework-managed errorKeys (input/output validation, content negotiation, etc.) are always allowed
|
|
840
|
-
* regardless of the declared keys — users never declare them, the framework owns them. */
|
|
841
|
-
if (plan.enf && !FRAMEWORK_EKS.has(thrown.errorKey) && !record.ek.has(thrown.errorKey)) {
|
|
842
|
-
honeyError = boundaryKey
|
|
843
|
-
? this._createBoundaryError(boundaryKey, thrown, fac)
|
|
844
|
-
: new HoneyError({ cause: thrown, errorKey: EK.internal_server_error, status: SK.internal_server_error })
|
|
845
|
-
} else {
|
|
846
|
-
honeyError = thrown
|
|
847
|
-
}
|
|
848
|
-
} else {
|
|
849
|
-
honeyError = boundaryKey
|
|
850
|
-
? this._createBoundaryError(boundaryKey, thrown, fac)
|
|
851
|
-
: new HoneyError({ cause: thrown, errorKey: EK.internal_server_error, status: SK.internal_server_error })
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
honeyError = await this._resolveI18n(honeyError, ctx, env, request, log)
|
|
855
|
-
|
|
856
|
-
if (s.onError) {
|
|
857
|
-
try {
|
|
858
|
-
const customResult = await s.onError(thrown, this._makeErrorCtx(fc))
|
|
859
|
-
if (customResult instanceof HoneyError) {
|
|
860
|
-
/* user-mapped boundary error — re-run i18n against new errorKey,
|
|
861
|
-
* then fall through to the default response path */
|
|
862
|
-
honeyError = await this._resolveI18n(customResult, ctx, env, request, log)
|
|
863
|
-
} else if (customResult) {
|
|
864
|
-
safeFire(
|
|
865
|
-
() => s.telemetry?.onError?.({ duration: performance.now() - startTime, error: honeyError, method, path }),
|
|
866
|
-
log,
|
|
867
|
-
)
|
|
868
|
-
setCtxError(ctx, honeyError)
|
|
869
|
-
ctx._isErrorResponse = true
|
|
870
|
-
return customResult
|
|
871
|
-
}
|
|
872
|
-
/* customResult === undefined | void → fall through to default path */
|
|
873
|
-
} catch (e) {
|
|
874
|
-
log?.warn?.({ err: e }, "onError failed")
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
safeFire(
|
|
879
|
-
() => s.telemetry?.onError?.({ duration: performance.now() - startTime, error: honeyError, method, path }),
|
|
880
|
-
log,
|
|
881
|
-
)
|
|
882
|
-
setCtxError(ctx, honeyError)
|
|
883
|
-
ctx._isErrorResponse = true
|
|
884
|
-
return this._makeErrorCtx(fc).jsonFromError(honeyError)
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
basePath<P extends string>(
|
|
888
|
-
prefix: P,
|
|
889
|
-
): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, MergePath<TBasePath, P>, TTaps, TScopedMw> {
|
|
890
|
-
const next = this._derive()
|
|
891
|
-
next._basePath = mergePath(this._basePath, prefix)
|
|
892
|
-
return next as unknown as Honey<
|
|
893
|
-
TEnv,
|
|
894
|
-
TCtx,
|
|
895
|
-
TRoutes,
|
|
896
|
-
TMeta,
|
|
897
|
-
TErrorFactory,
|
|
898
|
-
TDefaultErrors,
|
|
899
|
-
MergePath<TBasePath, P>,
|
|
900
|
-
TTaps,
|
|
901
|
-
TScopedMw
|
|
902
|
-
>
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
context<TAdds extends Record<string, unknown>>(
|
|
906
|
-
values: TAdds,
|
|
907
|
-
): Honey<TEnv, TCtx & Readonly<TAdds>, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw> {
|
|
908
|
-
for (const key in values) {
|
|
909
|
-
if (RESERVED_CTX_KEYS.has(key)) {
|
|
910
|
-
throw new Error(`context() cannot set reserved key "${key}"`)
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
const next = this._derive()
|
|
914
|
-
next._contextValues = this._contextValues ? { ...this._contextValues, ...values } : { ...values }
|
|
915
|
-
return next as unknown as Honey<
|
|
916
|
-
TEnv,
|
|
917
|
-
TCtx & Readonly<TAdds>,
|
|
918
|
-
TRoutes,
|
|
919
|
-
TMeta,
|
|
920
|
-
TErrorFactory,
|
|
921
|
-
TDefaultErrors,
|
|
922
|
-
TBasePath,
|
|
923
|
-
TTaps,
|
|
924
|
-
TScopedMw
|
|
925
|
-
>
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
logger(logger: Logger): this {
|
|
929
|
-
this._s.logger = logger
|
|
930
|
-
return this
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
outputValidation(mode: OutputValidationMode): this {
|
|
934
|
-
this._s.outputValidation = mode
|
|
935
|
-
this._bumpEpoch()
|
|
936
|
-
return this
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
trailingSlash(mode: "enforce" | "ignore" | "strip"): this {
|
|
940
|
-
this._s.trailingSlash = mode
|
|
941
|
-
return this
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
/**
|
|
945
|
-
* Request paths containing an encoded `/` or `\` (`%2F`, `%5C`) answer 400 by default.
|
|
946
|
-
* `"allow"` keeps them encoded in the path, so they decode into a param value
|
|
947
|
-
* (`/repos/:id` with `/repos/group%2Fproject` gives `id === "group/project"`). Only allow
|
|
948
|
-
* them when nothing downstream (a `proxy()` upstream, a file resolver) treats a decoded
|
|
949
|
-
* slash as a separator.
|
|
950
|
-
*/
|
|
951
|
-
encodedSlashes(mode: "allow" | "reject"): this {
|
|
952
|
-
this._s.encodedSlashes = mode
|
|
953
|
-
return this
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
/**
|
|
957
|
-
* Which reverse proxies in front of the app to believe. Decides `ctx.ip`, `clientInfo()`,
|
|
958
|
-
* `ipRestrict`, and every other feature that needs the client's address, scheme or host.
|
|
959
|
-
*
|
|
960
|
-
* - `false` (default): the peer address the runtime reports is the client; `X-Forwarded-*`
|
|
961
|
-
* headers are ignored, since any client can send them.
|
|
962
|
-
* - a hop count: exactly that many proxies, each appending its peer to `X-Forwarded-For`
|
|
963
|
-
* (nginx `$proxy_add_x_forwarded_for`, most load balancers). The client is the entry the
|
|
964
|
-
* outermost proxy wrote.
|
|
965
|
-
* - addresses and CIDR ranges of your proxies: hops from those addresses are skipped; the
|
|
966
|
-
* first other address is the client.
|
|
967
|
-
*
|
|
968
|
-
* A hop count or list needs `import "@lovrozagar/honey/trust"` in the app entry.
|
|
969
|
-
*/
|
|
970
|
-
trustProxy(value: TrustProxy): this {
|
|
971
|
-
this._s.trust =
|
|
972
|
-
value === false || value === 0 ? TRUST_OFF : requireFeature("trust", "app.trustProxy()").compileTrust(value)
|
|
973
|
-
return this
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
/** Strip a URL path prefix before route matching — boundary-safe (won't strip partial segments), paths without the prefix pass through unchanged */
|
|
977
|
-
stripPrefix(prefix: string): this {
|
|
978
|
-
let normalized = prefix.replace(/\/+$/, "")
|
|
979
|
-
if (normalized.length > 0 && normalized.charCodeAt(0) !== 47) {
|
|
980
|
-
normalized = `/${normalized}`
|
|
981
|
-
}
|
|
982
|
-
this._s.stripPrefix = normalized === "" || normalized === "/" ? null : normalized
|
|
983
|
-
return this
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
wsAdapter(adapter: WSAdapter): this {
|
|
987
|
-
this._s.wsAdapter = adapter
|
|
988
|
-
return this
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
/**
|
|
992
|
-
* Serve this app's OpenAPI document at `${path}.json`, `.yaml` and `.yml` (default
|
|
993
|
-
* `/openapi`), and optionally a docs UI. Each call serves its own document: two calls with
|
|
994
|
-
* different options (say an internal and a public profile) never share a cache. Documents
|
|
995
|
-
* are generated once per route change, serialized once, and sent with an `ETag` and
|
|
996
|
-
* `Cache-Control: no-cache`. Pass `enabled: false` (for example in production) to mount
|
|
997
|
-
* nothing while keeping the call in place.
|
|
998
|
-
*/
|
|
999
|
-
openapi(options: {
|
|
1000
|
-
description?: string
|
|
1001
|
-
docs?: "scalar" | "swagger"
|
|
1002
|
-
docsPath?: string
|
|
1003
|
-
/** Mount the document and docs routes. Default `true`. */
|
|
1004
|
-
enabled?: boolean
|
|
1005
|
-
filterRoutes?: (route: { meta: unknown; method: string; path: string }) => boolean
|
|
1006
|
-
path?: string
|
|
1007
|
-
/** Named metaSpec profile selecting which emitted keys this document carries */
|
|
1008
|
-
profile?: string
|
|
1009
|
-
securitySchemes?: Record<string, unknown>
|
|
1010
|
-
title: string
|
|
1011
|
-
version: string
|
|
1012
|
-
}): this {
|
|
1013
|
-
if (options.enabled === false) return this
|
|
1014
|
-
const stem = options.path ?? "/openapi"
|
|
1015
|
-
const epochOf = (): number => this._graph.epoch
|
|
1016
|
-
/* per call, not per app: each openapi() serves the document its own options describe */
|
|
1017
|
-
const document = epochCached(epochOf, async () => {
|
|
1018
|
-
await loadHoneyFeature("openapi")
|
|
1019
|
-
return getOpenApiRuntime().generateOpenApi(this, {
|
|
1020
|
-
filterRoutes: options.filterRoutes,
|
|
1021
|
-
info: { description: options.description, title: options.title, version: options.version },
|
|
1022
|
-
/* a served document is not an authoring moment — the check belongs to `honey generate` */
|
|
1023
|
-
invalidate: "off",
|
|
1024
|
-
profile: options.profile,
|
|
1025
|
-
securitySchemes: options.securitySchemes,
|
|
1026
|
-
})
|
|
1027
|
-
})
|
|
1028
|
-
/* the document first: it loads the runtime the serializers come from */
|
|
1029
|
-
const json = epochCached(epochOf, async () => {
|
|
1030
|
-
const doc = await document()
|
|
1031
|
-
return getOpenApiRuntime().toServedArtifact(JSON.stringify(doc), "application/json")
|
|
1032
|
-
})
|
|
1033
|
-
const yaml = epochCached(epochOf, async () => {
|
|
1034
|
-
const doc = await document()
|
|
1035
|
-
const runtime = getOpenApiRuntime()
|
|
1036
|
-
return runtime.toServedArtifact(runtime.toYaml(doc), "application/yaml; charset=utf-8")
|
|
1037
|
-
})
|
|
1038
|
-
const jsonPath = `${stem}.json`
|
|
1039
|
-
this._mountArtifact("openapi", jsonPath, json)
|
|
1040
|
-
this._mountArtifact("openapi", `${stem}.yml`, yaml)
|
|
1041
|
-
this._mountArtifact("openapi", `${stem}.yaml`, yaml)
|
|
1042
|
-
if (options.docs) {
|
|
1043
|
-
const docs = options.docs
|
|
1044
|
-
const specPath = mergePath(this._basePath, jsonPath)
|
|
1045
|
-
const ui = async (ctx: { res: HoneyRes }) => {
|
|
1046
|
-
await loadHoneyFeature("openapi")
|
|
1047
|
-
/* the browser asks for the URL it sees: a stripped prefix is part of it */
|
|
1048
|
-
const prefix = this._s.stripPrefix ?? ""
|
|
1049
|
-
return getOpenApiRuntime().docsUi(docs, prefix + specPath)(ctx)
|
|
1050
|
-
}
|
|
1051
|
-
/* without an explicit docsPath, a user route at /docs moves the UI to /reference */
|
|
1052
|
-
const candidates = options.docsPath ? [options.docsPath] : ["/docs", "/reference"]
|
|
1053
|
-
let mounted: MountResult = "user"
|
|
1054
|
-
let tried = candidates[0]
|
|
1055
|
-
for (const p of candidates) {
|
|
1056
|
-
tried = p
|
|
1057
|
-
mounted = this._mountInternalGet(p, ui)
|
|
1058
|
-
if (mounted !== "user") break
|
|
1059
|
-
}
|
|
1060
|
-
if (mounted !== "mounted") {
|
|
1061
|
-
const at = mergePath(this._basePath, tried)
|
|
1062
|
-
throw new Error(
|
|
1063
|
-
mounted === "internal"
|
|
1064
|
-
? `Honey.openapi({ docs }) cannot mount at ${at}: an earlier openapi() or manifest() serves it. Pass docsPath.`
|
|
1065
|
-
: `Honey.openapi({ docs }) cannot mount at ${at}: a route already owns it. Pass docsPath.`,
|
|
1066
|
-
)
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
return this
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
/**
|
|
1073
|
-
* Serve the route manifest at `path` (default `/manifest.json`), filtered by the same
|
|
1074
|
-
* visibility policy as the OpenAPI document. Cached per route change, sent with an `ETag`.
|
|
1075
|
-
* `enabled: false` mounts nothing.
|
|
1076
|
-
*/
|
|
1077
|
-
manifest(options?: { enabled?: boolean; path?: string }): this {
|
|
1078
|
-
if (options?.enabled === false) return this
|
|
1079
|
-
const epochOf = (): number => this._graph.epoch
|
|
1080
|
-
const artifact = epochCached(epochOf, async () => {
|
|
1081
|
-
await loadHoneyFeature("openapi")
|
|
1082
|
-
const runtime = getOpenApiRuntime()
|
|
1083
|
-
return runtime.toServedArtifact(JSON.stringify(await runtime.generateManifest(this)), "application/json")
|
|
1084
|
-
})
|
|
1085
|
-
this._mountArtifact("manifest", options?.path ?? "/manifest.json", artifact)
|
|
1086
|
-
return this
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
/** Mount a served document; a path another route or an earlier call owns is an error. */
|
|
1090
|
-
private _mountArtifact(owner: "manifest" | "openapi", path: string, load: () => Promise<ServedArtifact>): void {
|
|
1091
|
-
const result = this._mountInternalGet(path, async (ctx) => {
|
|
1092
|
-
const c = ctx as unknown as { req: Request; res: HoneyRes }
|
|
1093
|
-
const artifact = await load()
|
|
1094
|
-
return c.res.raw(getOpenApiRuntime().artifactResponse(c.req, artifact))
|
|
1095
|
-
})
|
|
1096
|
-
if (result === "mounted") return
|
|
1097
|
-
const at = mergePath(this._basePath, path)
|
|
1098
|
-
throw new Error(
|
|
1099
|
-
result === "internal"
|
|
1100
|
-
? `Honey.${owner}() cannot mount GET ${at}: an earlier openapi() or manifest() serves it. Pass a different path.`
|
|
1101
|
-
: `Honey.${owner}() cannot mount GET ${at}: a route already owns it. Pass a different path.`,
|
|
1102
|
-
)
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
async serve(options?: HoneyServeOptions): Promise<ServeHandle> {
|
|
1106
|
-
await loadHoneyFeature("serve")
|
|
1107
|
-
return getServeRuntime()(this, options)
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
/**
|
|
1111
|
-
* Mount an internal GET (spec, docs, manifest). Reports who owns the path when it cannot:
|
|
1112
|
-
* a user route (`"user"`) or an earlier internal mount (`"internal"`). Only an exact leaf
|
|
1113
|
-
* counts: a `/:slug` or `/*rest` route does not block a static internal path, which wins
|
|
1114
|
-
* over it on precedence, with backtracking, in both the runtime and a loaded tree.
|
|
1115
|
-
*/
|
|
1116
|
-
private _mountInternalGet(path: string, fn: (ctx: { res: HoneyRes }) => Response | Promise<Response>): MountResult {
|
|
1117
|
-
const fullPath = mergePath(this._basePath, path)
|
|
1118
|
-
const segments = parsePattern(fullPath)
|
|
1119
|
-
const id = routeId("GET", fullPath)
|
|
1120
|
-
const g = this._graph
|
|
1121
|
-
const existing = g.records.get(id)
|
|
1122
|
-
if (existing) return existing._skip === true ? "internal" : "user"
|
|
1123
|
-
const leaf = findLeaf(g.root, segments, "GET")
|
|
1124
|
-
if (leaf !== undefined && leaf !== id) return "user"
|
|
1125
|
-
if (findLeaf(g.root, segments, "ALL") !== undefined) return "user"
|
|
1126
|
-
/* a leaf the loaded tree holds for a user route the app has yet to register */
|
|
1127
|
-
if (leaf === id && g.loaded?.has(id)) return "user"
|
|
1128
|
-
const routeHandler: RouteHandler = {
|
|
1129
|
-
_skip: true,
|
|
1130
|
-
bek: null,
|
|
1131
|
-
cm: [...this._chain],
|
|
1132
|
-
cv: this._contextValues,
|
|
1133
|
-
dk: new Set(),
|
|
1134
|
-
ek: new Set(),
|
|
1135
|
-
fn: (ctx) => fn(ctx as { res: HoneyRes }),
|
|
1136
|
-
iv: null,
|
|
1137
|
-
mt: null,
|
|
1138
|
-
mw: [],
|
|
1139
|
-
os: null,
|
|
1140
|
-
ov: null,
|
|
1141
|
-
own: g.settings,
|
|
1142
|
-
rb: null,
|
|
1143
|
-
rm: [],
|
|
1144
|
-
xm: null,
|
|
1145
|
-
}
|
|
1146
|
-
this._markUsed()
|
|
1147
|
-
this._addRoute("GET", segments, routeHandler)
|
|
1148
|
-
return "mounted"
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
private _bumpEpoch(): void {
|
|
1152
|
-
this._graph.epoch++
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
/** Place `id` at every leaf of `segments`, copying a shared (loaded) root first. */
|
|
1156
|
-
private _placeLeaves(segments: readonly Segment[], method: string, id: RouteId): void {
|
|
1157
|
-
const g = this._graph
|
|
1158
|
-
const variants = leafVariants(segments)
|
|
1159
|
-
if (variants.every((v) => findLeaf(g.root, v, method) === id)) return
|
|
1160
|
-
if (g.rootShared) {
|
|
1161
|
-
g.root = cloneTree(g.root)
|
|
1162
|
-
g.rootShared = false
|
|
1163
|
-
}
|
|
1164
|
-
for (const v of variants) insertLeaf(g.root, v, method, id)
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
/**
|
|
1168
|
-
* @internal — register one route record. Over a loaded tree the record binds to the leaf
|
|
1169
|
-
* the tree already holds for its id; a root wildcard the tree lacks becomes the gateway
|
|
1170
|
-
* catch-all; any other unknown route marks the tree as stale (reported at finalize).
|
|
1171
|
-
*/
|
|
1172
|
-
_addRoute(method: string, segments: readonly Segment[], record: RouteHandler): void {
|
|
1173
|
-
const pattern = canonical(segments)
|
|
1174
|
-
const id = routeId(method, pattern)
|
|
1175
|
-
const g = this._graph
|
|
1176
|
-
if (g.records.has(id) || (isRootWildcard(segments) && g.catchAll.has(method))) {
|
|
1177
|
-
throw new Error(`Duplicate route: ${id}`)
|
|
1178
|
-
}
|
|
1179
|
-
record.id = id
|
|
1180
|
-
record.rp = pattern
|
|
1181
|
-
if (g.loaded !== null && record._skip !== true) {
|
|
1182
|
-
const known = leafVariants(segments).every((v) => findLeaf(g.root, v, method) === id)
|
|
1183
|
-
if (!known) {
|
|
1184
|
-
if (isRootWildcard(segments)) {
|
|
1185
|
-
record.ca = true
|
|
1186
|
-
g.catchAll.set(method, record)
|
|
1187
|
-
this._bumpEpoch()
|
|
1188
|
-
return
|
|
1189
|
-
}
|
|
1190
|
-
g.unexpected.add(id)
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
this._placeLeaves(segments, method, id)
|
|
1194
|
-
g.records.set(id, record)
|
|
1195
|
-
this._bumpEpoch()
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
/** @internal — register one websocket (or realtime) route record. */
|
|
1199
|
-
_addWsRoute(segments: readonly Segment[], record: WSRouteHandler): void {
|
|
1200
|
-
const pattern = canonical(segments)
|
|
1201
|
-
const id = routeId("WS", pattern)
|
|
1202
|
-
const g = this._graph
|
|
1203
|
-
if (g.wsRecords.has(id)) throw new Error(`Duplicate WebSocket route: ${pattern}`)
|
|
1204
|
-
record.id = id
|
|
1205
|
-
record.rp = pattern
|
|
1206
|
-
if (g.loaded !== null && leafVariants(segments).some((v) => findLeaf(g.root, v, "WS") !== id)) {
|
|
1207
|
-
g.unexpected.add(id)
|
|
1208
|
-
}
|
|
1209
|
-
this._placeLeaves(segments, "WS", id)
|
|
1210
|
-
g.wsRecords.set(id, record)
|
|
1211
|
-
g.hasWs = true
|
|
1212
|
-
this._bumpEpoch()
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
/**
|
|
1216
|
-
* Resolve what this graph serves: registered records, live records carried by a loaded
|
|
1217
|
-
* snapshot, and delegated leaves the gateway catch-all serves. Then resolve every record —
|
|
1218
|
-
* its full chain (`chain → scoped → route → input validation`), error keys, meta and error
|
|
1219
|
-
* factory — and compile it once. Re-runs whenever the graph's epoch moved. Throws, naming
|
|
1220
|
-
* every route, when a loaded tree and the registered routes disagree (the generated file is
|
|
1221
|
-
* stale), and when a `use(mw)` handle or a route builder was left unused (the middleware or
|
|
1222
|
-
* route would silently be missing).
|
|
1223
|
-
*/
|
|
1224
|
-
_finalize(): FinalTable {
|
|
1225
|
-
const g = this._graph
|
|
1226
|
-
if (g.final !== null && g.final.epoch === g.epoch) return g.final
|
|
1227
|
-
this._assertNothingDiscarded()
|
|
1228
|
-
const byId = new Map<RouteId, RouteHandler>()
|
|
1229
|
-
const wsById = new Map<RouteId, WSRouteHandler>()
|
|
1230
|
-
for (const [id, r] of g.records) byId.set(id, r)
|
|
1231
|
-
for (const [id, r] of g.wsRecords) wsById.set(id, r)
|
|
1232
|
-
const problems: string[] = []
|
|
1233
|
-
if (g.loaded !== null) {
|
|
1234
|
-
for (const id of g.unexpected) problems.push(`${id} is registered but missing from the loaded route tree`)
|
|
1235
|
-
let delegated = 0
|
|
1236
|
-
for (const [id, entry] of g.loaded) {
|
|
1237
|
-
const { method, pattern } = splitRouteId(id)
|
|
1238
|
-
if (method === "WS") {
|
|
1239
|
-
if (wsById.has(id)) continue
|
|
1240
|
-
if (entry.h !== undefined) {
|
|
1241
|
-
const live = copyRecord(entry.h as WSRouteHandler)
|
|
1242
|
-
live.id = id
|
|
1243
|
-
live.rp = pattern
|
|
1244
|
-
wsById.set(id, live)
|
|
1245
|
-
}
|
|
1246
|
-
/* a websocket leaf nothing serves is not upgraded: the request falls through to HTTP */
|
|
1247
|
-
continue
|
|
1248
|
-
}
|
|
1249
|
-
if (byId.has(id)) continue
|
|
1250
|
-
if (entry.h !== undefined) {
|
|
1251
|
-
const live = copyRecord(entry.h as RouteHandler)
|
|
1252
|
-
live.id = id
|
|
1253
|
-
live.rp = pattern
|
|
1254
|
-
byId.set(id, live)
|
|
1255
|
-
continue
|
|
1256
|
-
}
|
|
1257
|
-
const ca = g.catchAll.get(method) ?? g.catchAll.get("ALL")
|
|
1258
|
-
if (ca !== undefined) {
|
|
1259
|
-
const dl = copyRecord(ca)
|
|
1260
|
-
dl.dl = true
|
|
1261
|
-
dl.id = id
|
|
1262
|
-
dl.rp = pattern
|
|
1263
|
-
dl.xm = entry.mt ? { ...entry.mt } : null
|
|
1264
|
-
dl.iv = entry.iv ?? null
|
|
1265
|
-
dl.os = entry.os ?? null
|
|
1266
|
-
dl.sfac = entry.sfac ?? null
|
|
1267
|
-
dl.dk = new Set([...(ca.dk ?? []), ...(entry.ek ?? [])])
|
|
1268
|
-
if (entry.bek !== undefined && entry.bek !== null) dl.rb = entry.bek
|
|
1269
|
-
byId.set(id, dl)
|
|
1270
|
-
delegated++
|
|
1271
|
-
continue
|
|
1272
|
-
}
|
|
1273
|
-
/* no handler anywhere: documented (served specs list it) but answered with 404 */
|
|
1274
|
-
byId.set(id, {
|
|
1275
|
-
bek: null,
|
|
1276
|
-
cm: [],
|
|
1277
|
-
dk: new Set(entry.ek ?? []),
|
|
1278
|
-
dl: true,
|
|
1279
|
-
ek: new Set(),
|
|
1280
|
-
fn: NOT_SERVED,
|
|
1281
|
-
id,
|
|
1282
|
-
iv: entry.iv ?? null,
|
|
1283
|
-
mt: null,
|
|
1284
|
-
mw: [],
|
|
1285
|
-
os: entry.os ?? null,
|
|
1286
|
-
rb: entry.bek ?? null,
|
|
1287
|
-
rm: [],
|
|
1288
|
-
rp: pattern,
|
|
1289
|
-
sfac: entry.sfac ?? null,
|
|
1290
|
-
xm: entry.mt ? { ...entry.mt } : null,
|
|
1291
|
-
})
|
|
1292
|
-
}
|
|
1293
|
-
if (g.catchAll.size > 0 && delegated === 0) {
|
|
1294
|
-
for (const id of g.catchAll.keys()) {
|
|
1295
|
-
problems.push(`ALL root wildcard (${g.catchAll.get(id)?.id}) is missing from the loaded route tree`)
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
if (problems.length > 0) {
|
|
1300
|
-
/* generation rewrites this file from the registered routes; serving it stale is the error */
|
|
1301
|
-
if (g.unloadIfStale) {
|
|
1302
|
-
this._unloadTree()
|
|
1303
|
-
return this._finalize()
|
|
1304
|
-
}
|
|
1305
|
-
throw new Error(`Route tree out of date — regenerate it (\`honey generate\`):\n ${problems.join("\n ")}`)
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
const convert: ChainErrorConverter = (thrown, ctx) => this._convertError(thrown, ctx as HoneyContext<TEnv>)
|
|
1309
|
-
const plans = new Map<RouteId, Plan>()
|
|
1310
|
-
const wsPlans = new Map<RouteId, Plan>()
|
|
1311
|
-
const statics = Object.create(null) as Record<string, Plan>
|
|
1312
|
-
const userChains: RuntimeMiddleware[][] = []
|
|
1313
|
-
for (const [id, r] of byId) {
|
|
1314
|
-
const plan = this._planRoute(r, convert)
|
|
1315
|
-
plans.set(id, plan)
|
|
1316
|
-
if (r._skip !== true && r.dl !== true) userChains.push(r.cm ?? [])
|
|
1317
|
-
const { method, segments } = patternOf(id)
|
|
1318
|
-
for (const v of leafVariants(segments)) {
|
|
1319
|
-
if (isStaticPattern(v)) statics[`${method} ${canonical(v)}`] = plan
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
for (const [id, r] of wsById) {
|
|
1323
|
-
wsPlans.set(id, this._planWs(r, convert))
|
|
1324
|
-
userChains.push(r.cm ?? [])
|
|
1325
|
-
}
|
|
1326
|
-
/* an app with no routes (static files only) has nothing but the handles that serve it */
|
|
1327
|
-
if (userChains.length === 0) for (const chain of g.served) userChains.push(chain)
|
|
1328
|
-
const miss = this._planMiss(commonPrefix(userChains), convert)
|
|
1329
|
-
g.final = { byId, convert, epoch: g.epoch, miss, plans, statics, wsById, wsPlans }
|
|
1330
|
-
return g.final
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
/** `use(mw)` handles that never registered, mounted or served, and builders without `.handler()`. */
|
|
1334
|
-
private _assertNothingDiscarded(): void {
|
|
1335
|
-
const g = this._graph
|
|
1336
|
-
const problems: string[] = []
|
|
1337
|
-
for (const node of g.chains) {
|
|
1338
|
-
if (node.used) continue
|
|
1339
|
-
problems.push(
|
|
1340
|
-
`app.use(${describeMw(node.mw)}) returned a handle that never registers a route, so the middleware runs nowhere. ` +
|
|
1341
|
-
"use() does not change the handle it is called on: keep the returned value and register routes on it " +
|
|
1342
|
-
"(`const authed = app.use(auth); authed.get(...)`), or chain it (`honey().use(mw).get(...)`).",
|
|
1343
|
-
)
|
|
1344
|
-
}
|
|
1345
|
-
for (const p of g.pending) {
|
|
1346
|
-
problems.push(`${p.id} was declared but never got a .handler() (or .proxy()), so the route is not registered.`)
|
|
1347
|
-
}
|
|
1348
|
-
if (problems.length > 0) throw new Error(`honey: ${problems.join("\n ")}`)
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
/**
|
|
1352
|
-
* Resolve a record against this graph: chain = captured chain, the scopes that cover its
|
|
1353
|
-
* pattern (behind a request-path check where only some of its paths are inside), then its
|
|
1354
|
-
* route middleware. Error keys and contributed meta come from that chain, in run order.
|
|
1355
|
-
* The error factory, default errors and boundary come from the app the route was
|
|
1356
|
-
* registered on when that app has a factory, else from this one.
|
|
1357
|
-
*/
|
|
1358
|
-
private _resolve(
|
|
1359
|
-
r: RouteHandler | WSRouteHandler,
|
|
1360
|
-
everyScope = false,
|
|
1361
|
-
): { enf: boolean; own: AppSettings<unknown>; taps: Map<string, TapFn<unknown>> | null } {
|
|
1362
|
-
const s = this._graph.settings
|
|
1363
|
-
const own = (r.own as AppSettings<unknown> | undefined) ?? s
|
|
1364
|
-
const src = own.errorFactory !== null ? own : s
|
|
1365
|
-
const segments = parsePattern(r.rp ?? "/")
|
|
1366
|
-
const scoped: RuntimeMiddleware[] = []
|
|
1367
|
-
for (const entry of this._graph.scoped) {
|
|
1368
|
-
if (everyScope) {
|
|
1369
|
-
scoped.push(entry.guard)
|
|
1370
|
-
continue
|
|
1371
|
-
}
|
|
1372
|
-
const cov = scopeCoverage(segments, entry.segs)
|
|
1373
|
-
if (cov === "all") scoped.push(entry.mw)
|
|
1374
|
-
else if (cov === "some") scoped.push(entry.guard)
|
|
1375
|
-
}
|
|
1376
|
-
const mw = [...this._graph.global.values(), ...(r.cm ?? []), ...scoped, ...(r.rm ?? [])]
|
|
1377
|
-
/* app defaults, then what the middleware declares in run order, then the route's own */
|
|
1378
|
-
const ek = new Set<string>(src.defaultErrorKeys)
|
|
1379
|
-
for (const m of mw) {
|
|
1380
|
-
const errs = errorsOf(m)
|
|
1381
|
-
if (errs) for (const k of errs) ek.add(k)
|
|
1382
|
-
}
|
|
1383
|
-
if (r.dk !== undefined) for (const k of r.dk) ek.add(k)
|
|
1384
|
-
const bek = r.rb ?? src.defaultBoundaryKey
|
|
1385
|
-
if (bek !== null) ek.add(bek)
|
|
1386
|
-
const enf = ek.size > 0
|
|
1387
|
-
const fac = src.errorFactory
|
|
1388
|
-
let ef: ErrorFactoryRecord | null = fac
|
|
1389
|
-
if (fac !== null && enf) {
|
|
1390
|
-
const subset = Object.create(null) as ErrorFactoryRecord
|
|
1391
|
-
for (const key of ek) {
|
|
1392
|
-
if (key in fac) subset[key] = fac[key] as ErrorFactoryRecord[string]
|
|
1393
|
-
}
|
|
1394
|
-
ef = Object.freeze(subset)
|
|
1395
|
-
}
|
|
1396
|
-
r.mw = mw
|
|
1397
|
-
r.ek = ek
|
|
1398
|
-
r.bek = bek
|
|
1399
|
-
r.fac = fac
|
|
1400
|
-
r.ef = ef
|
|
1401
|
-
r.mt = mergeContributedMeta(collectMiddlewareMeta([mw]), r.xm ? { ...r.xm } : null)
|
|
1402
|
-
/* the serving app's taps win; a route from another app keeps that app's for the rest */
|
|
1403
|
-
const taps =
|
|
1404
|
-
own === s || own.taps === null ? s.taps : s.taps === null ? own.taps : new Map([...own.taps, ...s.taps])
|
|
1405
|
-
return { enf, own, taps }
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
/** Wrap each middleware for `telemetry.onMiddleware`, once per finalize. */
|
|
1409
|
-
private _timed(chain: RuntimeMiddleware[]): RuntimeMiddleware[] {
|
|
1410
|
-
const onMw = this._graph.settings.telemetry?.onMiddleware
|
|
1411
|
-
if (onMw === undefined) return chain
|
|
1412
|
-
const log = this._graph.settings.logger ?? undefined
|
|
1413
|
-
return chain.map((m) => timedMiddleware(m, onMw, log))
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
private _planRoute(r: RouteHandler, convert: ChainErrorConverter): Plan {
|
|
1417
|
-
const { enf, own, taps } = this._resolve(r)
|
|
1418
|
-
const s = this._graph.settings
|
|
1419
|
-
const ovm = own.outputValidation ?? s.outputValidation ?? "off"
|
|
1420
|
-
const base = this._timed(r.mw)
|
|
1421
|
-
const chain = [...base]
|
|
1422
|
-
/* a delegated route's schemas document it; the body is forwarded, never validated here */
|
|
1423
|
-
const iv = r.iv
|
|
1424
|
-
if (iv && r.dl !== true) {
|
|
1425
|
-
chain.push((ctx, next) =>
|
|
1426
|
-
validateInput(
|
|
1427
|
-
iv,
|
|
1428
|
-
ctx["req"] as Request,
|
|
1429
|
-
ctx["params"] as Record<string, string>,
|
|
1430
|
-
ctx as { searchAll?: Record<string, string[]> },
|
|
1431
|
-
).then((validated) => {
|
|
1432
|
-
ctx["input"] = validated
|
|
1433
|
-
return next()
|
|
1434
|
-
}),
|
|
1435
|
-
)
|
|
1436
|
-
}
|
|
1437
|
-
const fn = r.fn
|
|
1438
|
-
const handler: CompiledChain =
|
|
1439
|
-
r.os && ovm !== "off"
|
|
1440
|
-
? (ctx) => {
|
|
1441
|
-
const res = fn(ctx)
|
|
1442
|
-
if (!shouldValidateOutput(ovm)) return res
|
|
1443
|
-
return res instanceof Promise
|
|
1444
|
-
? res.then((v) => this._validateOutput(r, ctx as HoneyContext<TEnv>, v))
|
|
1445
|
-
: this._validateOutput(r, ctx as HoneyContext<TEnv>, res)
|
|
1446
|
-
}
|
|
1447
|
-
: (fn as CompiledChain)
|
|
1448
|
-
return {
|
|
1449
|
-
cv: r.cv ?? null,
|
|
1450
|
-
enf,
|
|
1451
|
-
pf: null,
|
|
1452
|
-
pfChain: base,
|
|
1453
|
-
r,
|
|
1454
|
-
rt: null,
|
|
1455
|
-
run: compileChain(chain, handler, convert),
|
|
1456
|
-
taps,
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
private _planWs(r: WSRouteHandler, convert: ChainErrorConverter): Plan {
|
|
1461
|
-
const { enf } = this._resolve(r)
|
|
1462
|
-
const rt = this._graph.realtimeRoutes.get(r.rp) ?? null
|
|
1463
|
-
const chain = this._timed(r.mw)
|
|
1464
|
-
const iv = r.iv
|
|
1465
|
-
if (iv) {
|
|
1466
|
-
chain.push((ctx, next) =>
|
|
1467
|
-
validateInput(
|
|
1468
|
-
iv,
|
|
1469
|
-
ctx["req"] as Request,
|
|
1470
|
-
ctx["params"] as Record<string, string>,
|
|
1471
|
-
ctx as { searchAll?: Record<string, string[]> },
|
|
1472
|
-
).then((validated) => {
|
|
1473
|
-
ctx["input"] = validated
|
|
1474
|
-
return next()
|
|
1475
|
-
}),
|
|
1476
|
-
)
|
|
1477
|
-
}
|
|
1478
|
-
const terminal: CompiledChain =
|
|
1479
|
-
rt !== null
|
|
1480
|
-
? (ctx) => this._realtimeUpgrade(ctx as HoneyContext<TEnv>, rt)
|
|
1481
|
-
: (ctx) => this._wsUpgrade(ctx as HoneyContext<TEnv>)
|
|
1482
|
-
return {
|
|
1483
|
-
cv: r.cv ?? null,
|
|
1484
|
-
enf,
|
|
1485
|
-
pf: null,
|
|
1486
|
-
pfChain: [],
|
|
1487
|
-
r,
|
|
1488
|
-
rt,
|
|
1489
|
-
run: compileChain(chain, terminal, convert),
|
|
1490
|
-
taps: null,
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
/**
|
|
1495
|
-
* 404 and 405 run through the same pipeline as a route: the middleware every route of this
|
|
1496
|
-
* graph starts with (with no routes: every handle that served a request), then every scope
|
|
1497
|
-
* (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
|
|
1498
|
-
* scope's guard answers before "not found" leaks what exists under it.
|
|
1499
|
-
*/
|
|
1500
|
-
private _planMiss(prefix: RuntimeMiddleware[], convert: ChainErrorConverter): Plan {
|
|
1501
|
-
const r: RouteHandler = {
|
|
1502
|
-
bek: null,
|
|
1503
|
-
cm: prefix,
|
|
1504
|
-
dk: new Set(),
|
|
1505
|
-
ek: new Set(),
|
|
1506
|
-
fn: (ctx) => {
|
|
1507
|
-
const fc = (ctx as HoneyContext<TEnv>)._rq as FetchCtx<TEnv>
|
|
1508
|
-
return fc.allowed !== null ? this._make405(fc, fc.allowed) : this._make404(fc)
|
|
1509
|
-
},
|
|
1510
|
-
mt: null,
|
|
1511
|
-
mw: [],
|
|
1512
|
-
rm: [],
|
|
1513
|
-
rp: "",
|
|
1514
|
-
xm: null,
|
|
1515
|
-
}
|
|
1516
|
-
/* every scope, each behind its path check — the request path decides */
|
|
1517
|
-
const { enf } = this._resolve(r, true)
|
|
1518
|
-
return {
|
|
1519
|
-
cv: null,
|
|
1520
|
-
enf,
|
|
1521
|
-
pf: null,
|
|
1522
|
-
pfChain: [],
|
|
1523
|
-
r,
|
|
1524
|
-
rt: null,
|
|
1525
|
-
run: compileChain(this._timed(r.mw), r.fn as CompiledChain, convert),
|
|
1526
|
-
taps: null,
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
/**
|
|
1531
|
-
* @internal — every served route as one entry per leaf (an optional param yields two
|
|
1532
|
-
* paths), in tree order. Codegen and OpenAPI read routes through this.
|
|
1533
|
-
*/
|
|
1534
|
-
_collectRoutes(includeSkipped = false): Array<{ handler: RouteHandler; method: string; path: string }> {
|
|
1535
|
-
const final = this._finalize()
|
|
1536
|
-
const out: Array<{ handler: RouteHandler; method: string; path: string }> = []
|
|
1537
|
-
forEachLeaf(this._root, (method, path, id) => {
|
|
1538
|
-
if (method === "WS") return
|
|
1539
|
-
const handler = final.byId.get(id)
|
|
1540
|
-
if (handler === undefined) return
|
|
1541
|
-
if (handler._skip && !includeSkipped) return
|
|
1542
|
-
out.push({ handler, method, path })
|
|
1543
|
-
})
|
|
1544
|
-
return out
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
/** @internal — websocket routes, one entry per leaf. */
|
|
1548
|
-
_collectWsRoutes(): Array<{ handler: WSRouteHandler; path: string }> {
|
|
1549
|
-
const final = this._finalize()
|
|
1550
|
-
const out: Array<{ handler: WSRouteHandler; path: string }> = []
|
|
1551
|
-
forEachLeaf(this._root, (method, path, id) => {
|
|
1552
|
-
if (method !== "WS") return
|
|
1553
|
-
const handler = final.wsById.get(id)
|
|
1554
|
-
if (handler !== undefined) out.push({ handler, path })
|
|
1555
|
-
})
|
|
1556
|
-
return out
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
/** @internal — the route-graph epoch; served documents cache against it. */
|
|
1560
|
-
get _epoch(): number {
|
|
1561
|
-
return this._graph.epoch
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
defaultErrorFormatter<TSchema extends StandardSchemaLike>(
|
|
1565
|
-
schema: TSchema,
|
|
1566
|
-
fn: (error: HoneyError) => InferOutput<TSchema>,
|
|
1567
|
-
): this
|
|
1568
|
-
defaultErrorFormatter(fn: ErrorFormatterFn): this
|
|
1569
|
-
defaultErrorFormatter(
|
|
1570
|
-
schemaOrFn: ErrorFormatterFn | StandardSchemaLike,
|
|
1571
|
-
maybeFn?: (error: HoneyError) => unknown,
|
|
1572
|
-
): this {
|
|
1573
|
-
const s = this._s
|
|
1574
|
-
if (typeof schemaOrFn === "function") {
|
|
1575
|
-
s.errorSchema = null
|
|
1576
|
-
s.errorFormatter = schemaOrFn
|
|
1577
|
-
} else {
|
|
1578
|
-
s.errorSchema = schemaOrFn
|
|
1579
|
-
const mapper = maybeFn as (error: HoneyError) => Record<string, unknown>
|
|
1580
|
-
s.errorFormatter = (error) => mapper(error)
|
|
1581
|
-
}
|
|
1582
|
-
return this
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
customErrorFormatter<TSchema extends StandardSchemaLike>(
|
|
1586
|
-
schema: TSchema,
|
|
1587
|
-
fn: (error: HoneyError, data: Record<string, unknown>) => InferOutput<TSchema>,
|
|
1588
|
-
): this
|
|
1589
|
-
customErrorFormatter(fn: CustomErrorFormatter): this
|
|
1590
|
-
customErrorFormatter(
|
|
1591
|
-
schemaOrFn: CustomErrorFormatter | StandardSchemaLike,
|
|
1592
|
-
maybeFn?: (error: HoneyError, data: Record<string, unknown>) => unknown,
|
|
1593
|
-
): this {
|
|
1594
|
-
const s = this._s
|
|
1595
|
-
if (typeof schemaOrFn === "function") {
|
|
1596
|
-
s.customErrorSchema = null
|
|
1597
|
-
s.customErrorFormatter = schemaOrFn
|
|
1598
|
-
} else {
|
|
1599
|
-
s.customErrorSchema = schemaOrFn
|
|
1600
|
-
const mapper = maybeFn as (error: HoneyError, data: Record<string, unknown>) => Record<string, unknown>
|
|
1601
|
-
s.customErrorFormatter = (error, data) => mapper(error, data)
|
|
1602
|
-
}
|
|
1603
|
-
return this
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
errorI18n(config: ErrorI18nConfig<TEnv>): this {
|
|
1607
|
-
this._s.errorI18n = config
|
|
1608
|
-
return this
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
onError(handler: OnErrorFn<TEnv>): this {
|
|
1612
|
-
this._s.onError = handler
|
|
1613
|
-
return this
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
onMethodNotAllowed(handler: OnMethodNotAllowedFn<TEnv>): this {
|
|
1617
|
-
this._s.onMethodNotAllowed = handler
|
|
1618
|
-
return this
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
onNotFound(handler: OnNotFoundFn<TEnv>): this {
|
|
1622
|
-
this._s.onNotFound = handler
|
|
1623
|
-
return this
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
/** Register a tap handler keyed by name — fires after successful handler response */
|
|
1627
|
-
tap<K extends string>(
|
|
1628
|
-
key: K,
|
|
1629
|
-
handler: (ctx: TapContext<TEnv>, payload: K extends keyof TTaps ? TTaps[K] : unknown) => void | Promise<void>,
|
|
1630
|
-
): this {
|
|
1631
|
-
const s = this._s
|
|
1632
|
-
if (s.taps === null) s.taps = new Map()
|
|
1633
|
-
s.taps.set(key, handler as TapFn<TEnv>)
|
|
1634
|
-
this._bumpEpoch()
|
|
1635
|
-
return this
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
telemetry(adapter: TelemetryAdapter): this {
|
|
1639
|
-
this._s.telemetry = adapter
|
|
1640
|
-
/* onMiddleware wraps the compiled chains */
|
|
1641
|
-
this._bumpEpoch()
|
|
1642
|
-
return this
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
/**
|
|
1646
|
-
* Load a route tree — a generated `routes.gen.ts`, an `app.toRouteTree()` snapshot, or a
|
|
1647
|
-
* `mergeTree()` of either. The tree supplies the router topology; what each route runs
|
|
1648
|
-
* comes from the routes this app registers afterwards (bound by `RouteId`), from live
|
|
1649
|
-
* records in a snapshot, or — for leaves with neither — from a root wildcard the app
|
|
1650
|
-
* registers as its gateway catch-all. Must be called before any route is registered.
|
|
1651
|
-
*/
|
|
1652
|
-
routeTree(tree: RouteTree): this {
|
|
1653
|
-
/* `honey generate` loads the app that imports the very file it is about to rewrite: a tree
|
|
1654
|
-
from an older honey is ignored there, and the routes the app registers are used instead */
|
|
1655
|
-
const generating = isCodegenProcess()
|
|
1656
|
-
if (generating) {
|
|
1657
|
-
try {
|
|
1658
|
-
assertTreeFormat(tree)
|
|
1659
|
-
} catch {
|
|
1660
|
-
return this
|
|
1661
|
-
}
|
|
1662
|
-
}
|
|
1663
|
-
assertTreeFormat(tree)
|
|
1664
|
-
const g = this._graph
|
|
1665
|
-
if (g.loaded !== null) throw new Error("routeTree() was already called on this app")
|
|
1666
|
-
for (const r of g.records.values()) {
|
|
1667
|
-
if (r._skip !== true) throw new Error(`routeTree() must be called before routes are registered (${r.id} is)`)
|
|
1668
|
-
}
|
|
1669
|
-
if (g.wsRecords.size > 0 || g.catchAll.size > 0) {
|
|
1670
|
-
throw new Error("routeTree() must be called before routes are registered")
|
|
1671
|
-
}
|
|
1672
|
-
forEachLeaf(tree.root, (method, path, id) => {
|
|
1673
|
-
if (!(id in tree.routes)) throw new Error(`routeTree(): leaf ${method} ${path} has no route entry (${id})`)
|
|
1674
|
-
})
|
|
1675
|
-
const internals = [...g.records.values()]
|
|
1676
|
-
g.records.clear()
|
|
1677
|
-
g.root = freezeTree(tree.root)
|
|
1678
|
-
g.rootShared = true
|
|
1679
|
-
g.loaded = new Map()
|
|
1680
|
-
g.unloadIfStale = generating
|
|
1681
|
-
for (const id of Object.keys(tree.routes)) g.loaded.set(id, { ...tree.routes[id] })
|
|
1682
|
-
if (hasWsLeaf(g.root)) g.hasWs = true
|
|
1683
|
-
/* internal routes mounted before the tree was loaded move onto the loaded topology */
|
|
1684
|
-
for (const r of internals) {
|
|
1685
|
-
const { method, segments } = patternOf(r.id as RouteId)
|
|
1686
|
-
this._addRoute(method, segments, r)
|
|
1687
|
-
}
|
|
1688
|
-
this._bumpEpoch()
|
|
1689
|
-
return this
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
/**
|
|
1693
|
-
* Snapshot this app's routes as a tree: a copy of the topology plus, per route, its data
|
|
1694
|
-
* and its live record. Internal routes (spec, docs, manifest) are left out. Loading the
|
|
1695
|
-
* snapshot into another app serves these records; nothing in it is shared with this app.
|
|
1696
|
-
* Each record keeps this app's settings (error factory, defaults, boundary) and context
|
|
1697
|
-
* values, and carries the scoped middleware that covers it here as part of its chain —
|
|
1698
|
-
* scopes themselves do not travel in a tree.
|
|
1699
|
-
*/
|
|
1700
|
-
toRouteTree(): RouteTree {
|
|
1701
|
-
this._markUsed()
|
|
1702
|
-
const final = this._finalize()
|
|
1703
|
-
const routes = Object.create(null) as Record<RouteId, RouteEntry>
|
|
1704
|
-
const keep = new Set<RouteId>()
|
|
1705
|
-
for (const [id, r] of final.byId) {
|
|
1706
|
-
if (r._skip) continue
|
|
1707
|
-
keep.add(id)
|
|
1708
|
-
routes[id] = {
|
|
1709
|
-
bek: r.bek,
|
|
1710
|
-
ek: [...r.ek],
|
|
1711
|
-
h: this._bake(r),
|
|
1712
|
-
iv: r.iv ?? null,
|
|
1713
|
-
mt: r.mt,
|
|
1714
|
-
os: r.os ?? null,
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
for (const [id, r] of final.wsById) {
|
|
1718
|
-
keep.add(id)
|
|
1719
|
-
routes[id] = { bek: r.bek, ek: [...r.ek], h: this._bake(r), iv: r.iv, mt: r.mt }
|
|
1720
|
-
}
|
|
1721
|
-
const root = createNode()
|
|
1722
|
-
forEachLeaf(this._root, (method, path, id) => {
|
|
1723
|
-
if (keep.has(id)) insertLeaf(root, parsePattern(path), method, id)
|
|
1724
|
-
})
|
|
1725
|
-
return { meta: {}, root, routes, v: ROUTE_TREE_VERSION }
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
/** A resolved record as a standalone source: its resolved chain minus route middleware becomes its chain. */
|
|
1729
|
-
private _bake<T extends RouteHandler | WSRouteHandler>(r: T): T {
|
|
1730
|
-
const h = copyRecord(r)
|
|
1731
|
-
const rm = r.rm ?? []
|
|
1732
|
-
/* app-wide middleware stays with the app; scopes travel as part of the chain */
|
|
1733
|
-
h.cm = r.mw.slice(this._graph.global.size, r.mw.length - rm.length)
|
|
1734
|
-
h.rm = [...rm]
|
|
1735
|
-
h.dk = new Set(r.ek)
|
|
1736
|
-
h.rb = r.bek
|
|
1737
|
-
h.xm = r.mt ? { ...r.mt } : null
|
|
1738
|
-
h.own = r.own ?? this._graph.settings
|
|
1739
|
-
return h
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
/**
|
|
1743
|
-
* @internal — generate-time: copy input/output schemas and the error factory from `source`
|
|
1744
|
-
* onto this app's routes that lack them (a gateway serves its generated tree, which carries
|
|
1745
|
-
* none). A downstream route's error keys resolve against its own app's factory, so the
|
|
1746
|
-
* gateway documents each under the status that app gives it.
|
|
1747
|
-
*/
|
|
1748
|
-
_overlaySchemas(source: RouteTree): void {
|
|
1749
|
-
const g = this._graph
|
|
1750
|
-
for (const [id, entry] of Object.entries(source.routes)) {
|
|
1751
|
-
const h = entry.h as RouteHandler | undefined
|
|
1752
|
-
const iv = entry.iv ?? h?.iv ?? null
|
|
1753
|
-
const os = entry.os ?? h?.os ?? null
|
|
1754
|
-
const sfac = h?.fac ?? null
|
|
1755
|
-
if (iv === null && os === null && sfac === null) continue
|
|
1756
|
-
const target = g.records.get(id) ?? g.loaded?.get(id)
|
|
1757
|
-
if (target === undefined) continue
|
|
1758
|
-
target.iv ??= iv
|
|
1759
|
-
target.os ??= os
|
|
1760
|
-
target.sfac ??= sfac
|
|
1761
|
-
}
|
|
1762
|
-
this._bumpEpoch()
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
/**
|
|
1766
|
-
* @internal — generate-time: the route tree a gateway serves. `source` is the merged
|
|
1767
|
-
* downstream tree (`codegen.mergeTree`); the gateway's own registered routes join it as
|
|
1768
|
-
* ordinary leaves, while its root wildcards stay out of the tree — they are the catch-all
|
|
1769
|
-
* the downstream leaves delegate to. An own route that is also a downstream route, or
|
|
1770
|
-
* whose topology conflicts with one, throws here instead of at request time. The app is
|
|
1771
|
-
* rebound to the result, so codegen that runs next (OpenAPI, manifest) sees what it serves.
|
|
1772
|
-
*/
|
|
1773
|
-
_gatewayTree(source: RouteTree): RouteTree {
|
|
1774
|
-
assertTreeFormat(source)
|
|
1775
|
-
const g = this._graph
|
|
1776
|
-
const root = cloneTree(source.root)
|
|
1777
|
-
const routes = dict<RouteEntry>()
|
|
1778
|
-
for (const id of Object.keys(source.routes)) {
|
|
1779
|
-
const { bek, ek, i, mt } = source.routes[id]
|
|
1780
|
-
routes[id] = { bek, ek, i, mt }
|
|
1781
|
-
}
|
|
1782
|
-
const own: { method: string; segments: Segment[]; id: RouteId }[] = []
|
|
1783
|
-
/* every route id this app serves, root wildcards included */
|
|
1784
|
-
const served = new Set<string>()
|
|
1785
|
-
for (const r of [...g.records.values(), ...g.catchAll.values(), ...g.wsRecords.values()]) {
|
|
1786
|
-
if ((r as RouteHandler)._skip === true) continue
|
|
1787
|
-
const id = r.id as RouteId
|
|
1788
|
-
served.add(id)
|
|
1789
|
-
const { method, segments } = patternOf(id)
|
|
1790
|
-
if (!isRootWildcard(segments)) own.push({ id, method, segments })
|
|
1791
|
-
}
|
|
1792
|
-
const overlap = own.filter((o) => o.id in routes)
|
|
1793
|
-
/* a merge source built from this app itself (`app.toRouteTree()`) holds exactly the routes
|
|
1794
|
-
this app serves; anything else that overlaps is a gateway route shadowing a downstream
|
|
1795
|
-
one — judged by the route sets, never by how many routes overlap */
|
|
1796
|
-
const sourceIds = Object.keys(routes)
|
|
1797
|
-
const isSelf = own.length > 0 && overlap.length === own.length && sourceIds.every((id) => served.has(id))
|
|
1798
|
-
if (overlap.length > 0 && !isSelf) {
|
|
1799
|
-
throw new Error(
|
|
1800
|
-
`Gateway routes are also downstream routes in the merged tree: ${overlap.map((o) => o.id).join(", ")}`,
|
|
1801
|
-
)
|
|
1802
|
-
}
|
|
1803
|
-
if (isSelf) own.length = 0
|
|
1804
|
-
for (const { id, method, segments } of own) {
|
|
1805
|
-
for (const v of leafVariants(segments)) insertLeaf(root, v, method, id)
|
|
1806
|
-
routes[id] = {}
|
|
1807
|
-
}
|
|
1808
|
-
this._replaceLoadedTree({ meta: { ...source.meta }, root, routes, v: ROUTE_TREE_VERSION })
|
|
1809
|
-
/* own routes resolved over the rebound tree carry their final error keys and meta */
|
|
1810
|
-
const snapshot = this.toRouteTree()
|
|
1811
|
-
for (const { id } of own) {
|
|
1812
|
-
const { bek, ek, mt } = snapshot.routes[id]
|
|
1813
|
-
routes[id] = { bek, ek, mt }
|
|
1814
|
-
}
|
|
1815
|
-
const tree: RouteTree = { meta: { ...source.meta }, root, routes, v: ROUTE_TREE_VERSION }
|
|
1816
|
-
this._replaceLoadedTree(tree)
|
|
1817
|
-
return tree
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
/** @internal — drop the loaded tree and route by the registered routes alone. */
|
|
1821
|
-
_unloadTree(): void {
|
|
1822
|
-
const g = this._graph
|
|
1823
|
-
const http = [...g.records.values(), ...g.catchAll.values()]
|
|
1824
|
-
const ws = [...g.wsRecords.values()]
|
|
1825
|
-
g.records.clear()
|
|
1826
|
-
g.catchAll.clear()
|
|
1827
|
-
g.wsRecords.clear()
|
|
1828
|
-
g.unexpected.clear()
|
|
1829
|
-
g.loaded = null
|
|
1830
|
-
g.unloadIfStale = false
|
|
1831
|
-
g.root = createNode()
|
|
1832
|
-
g.rootShared = false
|
|
1833
|
-
g.hasWs = false
|
|
1834
|
-
for (const r of http) {
|
|
1835
|
-
delete r.ca
|
|
1836
|
-
const { method, segments } = patternOf(r.id as RouteId)
|
|
1837
|
-
this._addRoute(method, segments, r)
|
|
1838
|
-
}
|
|
1839
|
-
for (const r of ws) this._addWsRoute(patternOf(r.id as RouteId).segments, r)
|
|
1840
|
-
this._bumpEpoch()
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
/** @internal — swap the loaded tree, re-binding every registered route to it. */
|
|
1844
|
-
_replaceLoadedTree(tree: RouteTree): void {
|
|
1845
|
-
const g = this._graph
|
|
1846
|
-
const http = [...g.records.values(), ...g.catchAll.values()]
|
|
1847
|
-
const ws = [...g.wsRecords.values()]
|
|
1848
|
-
g.records.clear()
|
|
1849
|
-
g.catchAll.clear()
|
|
1850
|
-
g.wsRecords.clear()
|
|
1851
|
-
g.unexpected.clear()
|
|
1852
|
-
g.loaded = null
|
|
1853
|
-
g.root = createNode()
|
|
1854
|
-
g.rootShared = false
|
|
1855
|
-
g.hasWs = false
|
|
1856
|
-
this.routeTree(tree)
|
|
1857
|
-
for (const r of http) {
|
|
1858
|
-
delete r.ca
|
|
1859
|
-
const { method, segments } = patternOf(r.id as RouteId)
|
|
1860
|
-
this._addRoute(method, segments, r)
|
|
1861
|
-
}
|
|
1862
|
-
for (const r of ws) this._addWsRoute(patternOf(r.id as RouteId).segments, r)
|
|
1863
|
-
}
|
|
1864
|
-
|
|
1865
|
-
/**
|
|
1866
|
-
* Set the app's error factory. Like every app setting it applies to every handle of this
|
|
1867
|
-
* app, whenever it is called; routes mounted from another app keep that app's factory.
|
|
1868
|
-
*/
|
|
1869
|
-
errorFactory<TFactory extends Record<string, (...args: never[]) => unknown>>(
|
|
1870
|
-
factory: TFactory,
|
|
1871
|
-
): Honey<TEnv, TCtx, TRoutes, TMeta, TFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw> {
|
|
1872
|
-
this._s.errorFactory = factory
|
|
1873
|
-
this._bumpEpoch()
|
|
1874
|
-
return this as unknown as Honey<TEnv, TCtx, TRoutes, TMeta, TFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
1875
|
-
}
|
|
1876
|
-
|
|
1877
|
-
/** Error keys every route of this app declares — applies to routes registered before and after the call. */
|
|
1878
|
-
defaultErrors<TKeys extends ([TErrorFactory] extends [never] ? never : keyof TErrorFactory & string)>(
|
|
1879
|
-
...keys: TKeys[]
|
|
1880
|
-
): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors | TKeys, TBasePath, TTaps, TScopedMw> {
|
|
1881
|
-
for (const k of keys) this._s.defaultErrorKeys.add(k)
|
|
1882
|
-
this._bumpEpoch()
|
|
1883
|
-
return this as unknown as Honey<
|
|
1884
|
-
TEnv,
|
|
1885
|
-
TCtx,
|
|
1886
|
-
TRoutes,
|
|
1887
|
-
TMeta,
|
|
1888
|
-
TErrorFactory,
|
|
1889
|
-
TDefaultErrors | TKeys,
|
|
1890
|
-
TBasePath,
|
|
1891
|
-
TTaps,
|
|
1892
|
-
TScopedMw
|
|
1893
|
-
>
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
defaultBoundary<TKey extends ([TErrorFactory] extends [never] ? never : keyof TErrorFactory & string)>(
|
|
1897
|
-
key: TKey,
|
|
1898
|
-
): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors | TKey, TBasePath, TTaps, TScopedMw> {
|
|
1899
|
-
this._s.defaultBoundaryKey = key
|
|
1900
|
-
this._s.defaultErrorKeys.add(key)
|
|
1901
|
-
this._bumpEpoch()
|
|
1902
|
-
return this as unknown as Honey<
|
|
1903
|
-
TEnv,
|
|
1904
|
-
TCtx,
|
|
1905
|
-
TRoutes,
|
|
1906
|
-
TMeta,
|
|
1907
|
-
TErrorFactory,
|
|
1908
|
-
TDefaultErrors | TKey,
|
|
1909
|
-
TBasePath,
|
|
1910
|
-
TTaps,
|
|
1911
|
-
TScopedMw
|
|
1912
|
-
>
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
/** Phantom overload — constrains what route-level .meta() accepts */
|
|
1916
|
-
meta<TNewMeta extends Record<string, unknown>>(): Honey<
|
|
1917
|
-
TEnv,
|
|
1918
|
-
TCtx,
|
|
1919
|
-
TRoutes,
|
|
1920
|
-
TNewMeta,
|
|
1921
|
-
TErrorFactory,
|
|
1922
|
-
TDefaultErrors,
|
|
1923
|
-
TBasePath,
|
|
1924
|
-
TTaps,
|
|
1925
|
-
TScopedMw
|
|
1926
|
-
>
|
|
1927
|
-
/** Typed chain-level default meta — constrains route meta and sets runtime defaults in one call */
|
|
1928
|
-
meta<TNewMeta extends Record<string, unknown>, TValues extends Partial<DefaultMeta> & Partial<TNewMeta>>(
|
|
1929
|
-
values: TValues,
|
|
1930
|
-
): Honey<TEnv, TCtx, TRoutes, TNewMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
1931
|
-
/** Chain-level default meta — merged into every route registered on this chain */
|
|
1932
|
-
meta<TValues extends Partial<DefaultMeta> & ([TMeta] extends [never] ? {} : Partial<TMeta>)>(
|
|
1933
|
-
values: TValues,
|
|
1934
|
-
): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
1935
|
-
meta(
|
|
1936
|
-
values?: Record<string, unknown>,
|
|
1937
|
-
): Honey<TEnv, TCtx, TRoutes, unknown, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw> {
|
|
1938
|
-
if (values === undefined) {
|
|
1939
|
-
/* phantom overload — type-only, no runtime effect */
|
|
1940
|
-
return this as Honey<TEnv, TCtx, TRoutes, unknown, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
1941
|
-
}
|
|
1942
|
-
/* chain-level meta — copy-on-write */
|
|
1943
|
-
const next = this._derive()
|
|
1944
|
-
next._chainMeta = this._chainMeta ? { ...this._chainMeta, ...values } : { ...values }
|
|
1945
|
-
return next as unknown as Honey<
|
|
1946
|
-
TEnv,
|
|
1947
|
-
TCtx,
|
|
1948
|
-
TRoutes,
|
|
1949
|
-
unknown,
|
|
1950
|
-
TErrorFactory,
|
|
1951
|
-
TDefaultErrors,
|
|
1952
|
-
TBasePath,
|
|
1953
|
-
TTaps,
|
|
1954
|
-
TScopedMw
|
|
1955
|
-
>
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
/**
|
|
1959
|
-
* Declare what flows from route meta and route schemas into the OpenAPI document.
|
|
1960
|
-
* Codegen-time only — never consulted on the request path. See docs/meta-spec.md.
|
|
1961
|
-
*/
|
|
1962
|
-
metaSpec<TSpec extends HoneyMetaSpec<TMeta>>(spec: TSpec): this {
|
|
1963
|
-
if (this._graph.metaSpec !== null) {
|
|
1964
|
-
throw new Error("metaSpec() was already declared on this app — declare the whole policy in one call")
|
|
1965
|
-
}
|
|
1966
|
-
this._graph.metaSpec = spec as MetaSpecConfig
|
|
1967
|
-
return this
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
|
-
/** Declare tap payload types — auto-extends meta with Partial<T> for meta-driven taps */
|
|
1971
|
-
taps<TNewTaps extends Record<string, unknown>>(): Honey<
|
|
1972
|
-
TEnv,
|
|
1973
|
-
Omit<TCtx, "tap"> & { tap<K extends keyof TNewTaps>(key: K, payload: TNewTaps[K]): void },
|
|
1974
|
-
TRoutes,
|
|
1975
|
-
[TMeta] extends [never] ? Partial<TNewTaps> : TMeta & Partial<TNewTaps>,
|
|
1976
|
-
TErrorFactory,
|
|
1977
|
-
TDefaultErrors,
|
|
1978
|
-
TBasePath,
|
|
1979
|
-
TNewTaps,
|
|
1980
|
-
TScopedMw
|
|
1981
|
-
> {
|
|
1982
|
-
return this as unknown as Honey<
|
|
1983
|
-
TEnv,
|
|
1984
|
-
Omit<TCtx, "tap"> & { tap<K extends keyof TNewTaps>(key: K, payload: TNewTaps[K]): void },
|
|
1985
|
-
TRoutes,
|
|
1986
|
-
[TMeta] extends [never] ? Partial<TNewTaps> : TMeta & Partial<TNewTaps>,
|
|
1987
|
-
TErrorFactory,
|
|
1988
|
-
TDefaultErrors,
|
|
1989
|
-
TBasePath,
|
|
1990
|
-
TNewTaps,
|
|
1991
|
-
TScopedMw
|
|
1992
|
-
>
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
use<TAdds>(
|
|
1996
|
-
mw: MiddlewareFn<TCtx, TAdds>,
|
|
1997
|
-
): Honey<TEnv, TCtx & TAdds, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
1998
|
-
|
|
1999
|
-
use<const TPath extends string, TAdds>(
|
|
2000
|
-
path: TPath,
|
|
2001
|
-
mw: MiddlewareFn<TCtx, TAdds>,
|
|
2002
|
-
): Honey<
|
|
2003
|
-
TEnv,
|
|
2004
|
-
TCtx,
|
|
2005
|
-
TRoutes,
|
|
2006
|
-
TMeta,
|
|
2007
|
-
TErrorFactory,
|
|
2008
|
-
TDefaultErrors,
|
|
2009
|
-
TBasePath,
|
|
2010
|
-
TTaps,
|
|
2011
|
-
readonly [...TScopedMw, { readonly path: MergePath<TBasePath, TPath>; readonly adds: TAdds }]
|
|
2012
|
-
>
|
|
2013
|
-
|
|
2014
|
-
/* oxlint-disable-next-line typescript/no-explicit-any -- overload impl requires erased types */
|
|
2015
|
-
use(pathOrMw: string | MiddlewareFn<any, any>, maybeMw?: MiddlewareFn<any, any>): any {
|
|
2016
|
-
if (typeof pathOrMw !== "string") {
|
|
2017
|
-
/*
|
|
2018
|
-
* Chain middleware: a new handle whose routes run `mw`. This handle is unchanged —
|
|
2019
|
-
* a handle that never registers a route is reported at finalize.
|
|
2020
|
-
*/
|
|
2021
|
-
const mw = pathOrMw as RuntimeMiddleware
|
|
2022
|
-
const node: ChainNode = { mw, parent: this._node, used: false }
|
|
2023
|
-
this._graph.chains.push(node)
|
|
2024
|
-
const next = this._derive([...this._chain, mw])
|
|
2025
|
-
next._node = node
|
|
2026
|
-
return next
|
|
2027
|
-
}
|
|
2028
|
-
|
|
2029
|
-
/* scoped: guards every request path under the prefix, whichever handle registered the route */
|
|
2030
|
-
const prefix = mergePath(this._basePath, normalizePattern(pathOrMw))
|
|
2031
|
-
const mw = maybeMw as RuntimeMiddleware
|
|
2032
|
-
const segs = parsePattern(prefix)
|
|
2033
|
-
this._graph.scoped.push({
|
|
2034
|
-
errors: errorsOf(mw) ? [...(errorsOf(mw) as readonly string[])] : undefined,
|
|
2035
|
-
guard: scopeGuard(segs, mw),
|
|
2036
|
-
mw,
|
|
2037
|
-
prefix,
|
|
2038
|
-
segs,
|
|
2039
|
-
})
|
|
2040
|
-
this._bumpEpoch()
|
|
2041
|
-
return this._derive()
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
/**
|
|
2045
|
-
* Mount a sub-app: its routes are served under this handle's basePath (and under `prefix`
|
|
2046
|
-
* when given — `app.route("/v1", sub)` is `app.basePath("/v1").route(sub)`), and this
|
|
2047
|
-
* handle's chain runs before theirs. The sub's own basePath stays part of each path.
|
|
2048
|
-
*/
|
|
2049
|
-
route<TSubRoutes, TSubMeta, TSubErrorFactory, TSubDefaultErrors extends string, TSubBasePath extends string>(
|
|
2050
|
-
sub: Honey<TEnv, TCtx, TSubRoutes, TSubMeta, TSubErrorFactory, TSubDefaultErrors, TSubBasePath>,
|
|
2051
|
-
): Honey<
|
|
2052
|
-
TEnv,
|
|
2053
|
-
TCtx,
|
|
2054
|
-
TRoutes & PrefixRoutes<TBasePath, TSubRoutes>,
|
|
2055
|
-
TMeta,
|
|
2056
|
-
TErrorFactory,
|
|
2057
|
-
TDefaultErrors,
|
|
2058
|
-
TBasePath,
|
|
2059
|
-
TTaps,
|
|
2060
|
-
TScopedMw
|
|
2061
|
-
>
|
|
2062
|
-
route<
|
|
2063
|
-
const TPrefix extends string,
|
|
2064
|
-
TSubRoutes,
|
|
2065
|
-
TSubMeta,
|
|
2066
|
-
TSubErrorFactory,
|
|
2067
|
-
TSubDefaultErrors extends string,
|
|
2068
|
-
TSubBasePath extends string,
|
|
2069
|
-
>(
|
|
2070
|
-
prefix: TPrefix,
|
|
2071
|
-
sub: Honey<TEnv, TCtx, TSubRoutes, TSubMeta, TSubErrorFactory, TSubDefaultErrors, TSubBasePath>,
|
|
2072
|
-
): Honey<
|
|
2073
|
-
TEnv,
|
|
2074
|
-
TCtx,
|
|
2075
|
-
TRoutes & PrefixRoutes<MergePath<TBasePath, TPrefix>, TSubRoutes>,
|
|
2076
|
-
TMeta,
|
|
2077
|
-
TErrorFactory,
|
|
2078
|
-
TDefaultErrors,
|
|
2079
|
-
TBasePath,
|
|
2080
|
-
TTaps,
|
|
2081
|
-
TScopedMw
|
|
2082
|
-
>
|
|
2083
|
-
route(prefixOrSub: unknown, maybeSub?: unknown): unknown {
|
|
2084
|
-
type Sub = Honey<TEnv, TCtx, unknown, unknown, unknown, string, string>
|
|
2085
|
-
if (typeof prefixOrSub === "string") {
|
|
2086
|
-
if (!(maybeSub instanceof Honey)) throw new TypeError("honey: route(prefix, sub) needs the sub-app")
|
|
2087
|
-
this._markUsed()
|
|
2088
|
-
;(this.basePath(prefixOrSub) as unknown as Honey<TEnv, TCtx>)._mount(maybeSub as Sub)
|
|
2089
|
-
return this
|
|
2090
|
-
}
|
|
2091
|
-
return this._mount(prefixOrSub as Sub)
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
private _mount(sub: Honey<TEnv, TCtx, unknown, unknown, unknown, string, string>): this {
|
|
2095
|
-
this._markUsed()
|
|
2096
|
-
/* skip self-merge: .handler() already registered into the shared graph */
|
|
2097
|
-
if (sub._graph !== this._graph) {
|
|
2098
|
-
/*
|
|
2099
|
-
* Re-register the sub's records under this handle: this handle's chain runs first,
|
|
2100
|
-
* its context values and chain meta apply under the sub's own. Nothing is shared by
|
|
2101
|
-
* reference, so routes the sub registers later stay its own, and two parents
|
|
2102
|
-
* mounting one sub each get their own records. Each record keeps the settings of
|
|
2103
|
-
* the sub (error factory, defaults, boundary, output validation). Internal routes
|
|
2104
|
-
* (spec, docs, manifest) never travel.
|
|
2105
|
-
*/
|
|
2106
|
-
sub._markUsed()
|
|
2107
|
-
const subFinal = sub._finalize()
|
|
2108
|
-
const subSettings = sub._graph.settings
|
|
2109
|
-
const mount = <T extends RouteHandler | WSRouteHandler>(r: T): T => {
|
|
2110
|
-
const c = copyRecord(r)
|
|
2111
|
-
c.cm = [...this._chain, ...(r.cm ?? [])]
|
|
2112
|
-
if (this._contextValues) c.cv = { ...this._contextValues, ...r.cv }
|
|
2113
|
-
if (this._chainMeta) c.xm = { ...this._chainMeta, ...r.xm }
|
|
2114
|
-
c.own = r.own ?? subSettings
|
|
2115
|
-
return c
|
|
2116
|
-
}
|
|
2117
|
-
/* the sub's paths land under this handle's basePath, like Hono's `route("/v1", sub)` */
|
|
2118
|
-
const base = this._basePath
|
|
2119
|
-
const at = (pattern: string): string => (base === "/" ? pattern : mergePath(base, pattern))
|
|
2120
|
-
for (const [id, r] of subFinal.byId) {
|
|
2121
|
-
if (r._skip) continue
|
|
2122
|
-
const { method, pattern } = splitRouteId(id)
|
|
2123
|
-
this._addRoute(method, parsePattern(at(pattern)), mount(r))
|
|
2124
|
-
}
|
|
2125
|
-
for (const [id, r] of subFinal.wsById) {
|
|
2126
|
-
const { pattern } = splitRouteId(id)
|
|
2127
|
-
this._addWsRoute(parsePattern(at(pattern)), mount(r))
|
|
2128
|
-
}
|
|
2129
|
-
/* merged when codegen reads the policy (see `metaSpecOf`): the parent wins
|
|
2130
|
-
* conflicts and keeps its resolved strictness, a sub's `false` still hides */
|
|
2131
|
-
const subSource = sub._metaSpecSource
|
|
2132
|
-
this._graph.absorbedMetaSpecs.push({ absorbed: [...subSource.absorbed], declared: subSource.declared })
|
|
2133
|
-
/* the sub's scopes guard the paths it brought — appended after this app's own */
|
|
2134
|
-
const g = this._graph
|
|
2135
|
-
for (const entry of sub._graph.scoped) {
|
|
2136
|
-
if (base === "/") {
|
|
2137
|
-
if (!g.scoped.includes(entry)) g.scoped.push(entry)
|
|
2138
|
-
continue
|
|
2139
|
-
}
|
|
2140
|
-
const prefix = at(entry.prefix)
|
|
2141
|
-
if (g.scoped.some((e) => e.mw === entry.mw && e.prefix === prefix)) continue
|
|
2142
|
-
const segs = parsePattern(prefix)
|
|
2143
|
-
g.scoped.push({ errors: entry.errors, guard: scopeGuard(segs, entry.mw), mw: entry.mw, prefix, segs })
|
|
2144
|
-
}
|
|
2145
|
-
for (const [subPath, cfg] of sub._graph.realtimeRoutes) {
|
|
2146
|
-
const path = at(subPath)
|
|
2147
|
-
if (g.realtimeRoutes.has(path)) {
|
|
2148
|
-
throw new Error(`Duplicate realtime route: ${path}`)
|
|
2149
|
-
}
|
|
2150
|
-
/* a default namespace is the route's full path, so it follows the mount; a named one is kept */
|
|
2151
|
-
g.realtimeRoutes.set(path, { ...cfg, namespace: cfg.namespaceExplicit ? cfg.namespace : path, path })
|
|
2152
|
-
}
|
|
2153
|
-
if (!g.realtimeBus && sub._graph.realtimeBus) {
|
|
2154
|
-
this._setBus(sub._graph.realtimeBus)
|
|
2155
|
-
}
|
|
2156
|
-
if (subSettings.taps !== null) {
|
|
2157
|
-
const s = this._graph.settings
|
|
2158
|
-
if (s.taps === null) s.taps = new Map()
|
|
2159
|
-
for (const [key, fn] of subSettings.taps) {
|
|
2160
|
-
if (!s.taps.has(key)) s.taps.set(key, fn)
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
this._bumpEpoch()
|
|
2164
|
-
}
|
|
2165
|
-
return this
|
|
2166
|
-
}
|
|
2167
|
-
|
|
2168
|
-
private _setBus(bus: RealtimeBus): RealtimeBus {
|
|
2169
|
-
const g = this._graph
|
|
2170
|
-
g.realtimeBus = bus
|
|
2171
|
-
g.realtimeCtx = requireFeature("realtime", "app.realtime()").createRealtimePublisher(bus, () =>
|
|
2172
|
-
[...g.realtimeRoutes.values()].map((cfg) => cfg.namespace),
|
|
2173
|
-
)
|
|
2174
|
-
return bus
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
private _registerRoute<TPath extends string, TMethod extends HttpMethod | "ALL">(
|
|
2178
|
-
method: TMethod,
|
|
2179
|
-
path: TPath,
|
|
2180
|
-
extraMethods?: (HttpMethod | "ALL")[],
|
|
2181
|
-
): BuilderChain<
|
|
2182
|
-
TEnv,
|
|
2183
|
-
TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
|
|
2184
|
-
{},
|
|
2185
|
-
never,
|
|
2186
|
-
{},
|
|
2187
|
-
MergePath<TBasePath, TPath>,
|
|
2188
|
-
TMethod,
|
|
2189
|
-
TRoutes,
|
|
2190
|
-
never,
|
|
2191
|
-
TCtx,
|
|
2192
|
-
TMeta,
|
|
2193
|
-
InitAccMeta<TMeta>,
|
|
2194
|
-
TErrorFactory,
|
|
2195
|
-
TDefaultErrors,
|
|
2196
|
-
TBasePath,
|
|
2197
|
-
TTaps,
|
|
2198
|
-
TScopedMw
|
|
2199
|
-
> {
|
|
2200
|
-
const fullPath = mergePath(this._basePath, path)
|
|
2201
|
-
const pending: PendingRoute = { id: `${[method, ...(extraMethods ?? [])].join(",")} ${fullPath}` }
|
|
2202
|
-
this._graph.pending.add(pending)
|
|
2203
|
-
return new RouteBuilder<
|
|
2204
|
-
TEnv,
|
|
2205
|
-
TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
|
|
2206
|
-
{},
|
|
2207
|
-
never,
|
|
2208
|
-
{},
|
|
2209
|
-
MergePath<TBasePath, TPath>,
|
|
2210
|
-
TMethod,
|
|
2211
|
-
TRoutes,
|
|
2212
|
-
never,
|
|
2213
|
-
TCtx,
|
|
2214
|
-
TMeta,
|
|
2215
|
-
InitAccMeta<TMeta>,
|
|
2216
|
-
TErrorFactory,
|
|
2217
|
-
TDefaultErrors,
|
|
2218
|
-
TBasePath,
|
|
2219
|
-
TTaps,
|
|
2220
|
-
TScopedMw
|
|
2221
|
-
>({
|
|
2222
|
-
boundaryErrorKey: null,
|
|
2223
|
-
errorKeys: new Set(),
|
|
2224
|
-
extraMethods: extraMethods ?? null,
|
|
2225
|
-
inputSchemas: null,
|
|
2226
|
-
meta: this._chainMeta ? { ...this._chainMeta } : null,
|
|
2227
|
-
method,
|
|
2228
|
-
middlewares: [],
|
|
2229
|
-
outputSchemas: null,
|
|
2230
|
-
parent: this,
|
|
2231
|
-
path: fullPath,
|
|
2232
|
-
pending,
|
|
2233
|
-
}) as unknown as BuilderChain<
|
|
2234
|
-
TEnv,
|
|
2235
|
-
TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
|
|
2236
|
-
{},
|
|
2237
|
-
never,
|
|
2238
|
-
{},
|
|
2239
|
-
MergePath<TBasePath, TPath>,
|
|
2240
|
-
TMethod,
|
|
2241
|
-
TRoutes,
|
|
2242
|
-
never,
|
|
2243
|
-
TCtx,
|
|
2244
|
-
TMeta,
|
|
2245
|
-
InitAccMeta<TMeta>,
|
|
2246
|
-
TErrorFactory,
|
|
2247
|
-
TDefaultErrors,
|
|
2248
|
-
TBasePath,
|
|
2249
|
-
TTaps,
|
|
2250
|
-
TScopedMw
|
|
2251
|
-
>
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
/** @internal — set (or clear, with null) one app-wide middleware; used by `serve()` */
|
|
2255
|
-
_setGlobal(key: string, mw: RuntimeMiddleware | null): void {
|
|
2256
|
-
if (mw === null) {
|
|
2257
|
-
if (!this._graph.global.delete(key)) return
|
|
2258
|
-
} else {
|
|
2259
|
-
this._graph.global.set(key, mw)
|
|
2260
|
-
}
|
|
2261
|
-
this._bumpEpoch()
|
|
2262
|
-
}
|
|
2263
|
-
|
|
2264
|
-
/** @internal — what a route registered through this handle captures */
|
|
2265
|
-
_view(): { chain: RuntimeMiddleware[]; cv: Record<string, unknown> | null; own: AppSettings<unknown> } {
|
|
2266
|
-
return { chain: this._chain, cv: this._contextValues, own: this._graph.settings }
|
|
2267
|
-
}
|
|
2268
|
-
|
|
2269
|
-
/** @internal — a builder got its handler (or was abandoned on purpose) */
|
|
2270
|
-
_settle(pending: PendingRoute): void {
|
|
2271
|
-
this._graph.pending.delete(pending)
|
|
2272
|
-
this._markUsed()
|
|
2273
|
-
}
|
|
2274
|
-
|
|
2275
|
-
on<const TPath extends string, const TMethods extends readonly [HttpMethod | "ALL", ...(HttpMethod | "ALL")[]]>(
|
|
2276
|
-
methods: TMethods,
|
|
2277
|
-
path: TPath,
|
|
2278
|
-
): BuilderChain<
|
|
2279
|
-
TEnv,
|
|
2280
|
-
TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
|
|
2281
|
-
{},
|
|
2282
|
-
never,
|
|
2283
|
-
{},
|
|
2284
|
-
MergePath<TBasePath, TPath>,
|
|
2285
|
-
TMethods[number],
|
|
2286
|
-
TRoutes,
|
|
2287
|
-
never,
|
|
2288
|
-
TCtx,
|
|
2289
|
-
TMeta,
|
|
2290
|
-
InitAccMeta<TMeta>,
|
|
2291
|
-
TErrorFactory,
|
|
2292
|
-
TDefaultErrors,
|
|
2293
|
-
TBasePath,
|
|
2294
|
-
TTaps,
|
|
2295
|
-
TScopedMw
|
|
2296
|
-
> {
|
|
2297
|
-
const [first, ...rest] = methods
|
|
2298
|
-
return this._registerRoute<TPath, TMethods[number]>(
|
|
2299
|
-
first,
|
|
2300
|
-
path,
|
|
2301
|
-
rest.length > 0 ? (rest as (HttpMethod | "ALL")[]) : undefined,
|
|
2302
|
-
)
|
|
2303
|
-
}
|
|
2304
|
-
|
|
2305
|
-
all<const TPath extends string>(path: TPath) {
|
|
2306
|
-
return this._registerRoute<TPath, "ALL">("ALL", path)
|
|
2307
|
-
}
|
|
2308
|
-
delete<const TPath extends string>(path: TPath) {
|
|
2309
|
-
return this._registerRoute<TPath, "DELETE">("DELETE", path)
|
|
2310
|
-
}
|
|
2311
|
-
get<const TPath extends string>(path: TPath) {
|
|
2312
|
-
return this._registerRoute<TPath, "GET">("GET", path)
|
|
2313
|
-
}
|
|
2314
|
-
head<const TPath extends string>(path: TPath) {
|
|
2315
|
-
return this._registerRoute<TPath, "HEAD">("HEAD", path)
|
|
2316
|
-
}
|
|
2317
|
-
options<const TPath extends string>(path: TPath) {
|
|
2318
|
-
return this._registerRoute<TPath, "OPTIONS">("OPTIONS", path)
|
|
2319
|
-
}
|
|
2320
|
-
patch<const TPath extends string>(path: TPath) {
|
|
2321
|
-
return this._registerRoute<TPath, "PATCH">("PATCH", path)
|
|
2322
|
-
}
|
|
2323
|
-
post<const TPath extends string>(path: TPath) {
|
|
2324
|
-
return this._registerRoute<TPath, "POST">("POST", path)
|
|
2325
|
-
}
|
|
2326
|
-
put<const TPath extends string>(path: TPath) {
|
|
2327
|
-
return this._registerRoute<TPath, "PUT">("PUT", path)
|
|
2328
|
-
}
|
|
2329
|
-
|
|
2330
|
-
ws<const TPath extends string>(
|
|
2331
|
-
path: TPath,
|
|
2332
|
-
): WSRouteBuilder<
|
|
2333
|
-
TEnv,
|
|
2334
|
-
TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
|
|
2335
|
-
{},
|
|
2336
|
-
never,
|
|
2337
|
-
Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
2338
|
-
> {
|
|
2339
|
-
const fullPath = mergePath(this._basePath, path)
|
|
2340
|
-
const pending: PendingRoute = { id: `WS ${fullPath}` }
|
|
2341
|
-
this._graph.pending.add(pending)
|
|
2342
|
-
return new WSRouteBuilder<
|
|
2343
|
-
TEnv,
|
|
2344
|
-
TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
|
|
2345
|
-
{},
|
|
2346
|
-
never,
|
|
2347
|
-
Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
2348
|
-
>({
|
|
2349
|
-
errorKeys: new Set(),
|
|
2350
|
-
inputSchemas: null,
|
|
2351
|
-
meta: this._chainMeta ? { ...this._chainMeta } : null,
|
|
2352
|
-
middlewares: [],
|
|
2353
|
-
origins: null,
|
|
2354
|
-
parent: this,
|
|
2355
|
-
path: fullPath,
|
|
2356
|
-
pending,
|
|
2357
|
-
})
|
|
2358
|
-
}
|
|
2359
|
-
|
|
2360
|
-
realtime<const TPath extends string>(
|
|
2361
|
-
path: TPath,
|
|
2362
|
-
opts: RealtimeRouteOpts<TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>>,
|
|
2363
|
-
): this {
|
|
2364
|
-
const fullPath = mergePath(this._basePath, path)
|
|
2365
|
-
const g = this._graph
|
|
2366
|
-
const rt = requireFeature("realtime", "app.realtime()")
|
|
2367
|
-
const config = rt.resolveRealtimeConfig(fullPath, opts)
|
|
2368
|
-
if (g.realtimeRoutes.has(fullPath)) {
|
|
2369
|
-
throw new Error(`Duplicate realtime route: ${fullPath}`)
|
|
2370
|
-
}
|
|
2371
|
-
if (!g.realtimeBus) this._setBus(rt.createBus())
|
|
2372
|
-
g.realtimeRoutes.set(fullPath, config)
|
|
2373
|
-
this._markUsed()
|
|
2374
|
-
this._addWsRoute(parsePattern(fullPath), {
|
|
2375
|
-
bek: null,
|
|
2376
|
-
cm: [...this._chain],
|
|
2377
|
-
cv: this._contextValues,
|
|
2378
|
-
dk: new Set(),
|
|
2379
|
-
ek: new Set(),
|
|
2380
|
-
fn: Object.create(null),
|
|
2381
|
-
iv: null,
|
|
2382
|
-
mt: null,
|
|
2383
|
-
mw: [],
|
|
2384
|
-
own: g.settings,
|
|
2385
|
-
rb: null,
|
|
2386
|
-
rm: (opts.use ?? []).map((fn) => fn as RuntimeMiddleware),
|
|
2387
|
-
rp: fullPath,
|
|
2388
|
-
xm: this._chainMeta ? { ...this._chainMeta } : null,
|
|
2389
|
-
})
|
|
2390
|
-
return this
|
|
2391
|
-
}
|
|
2392
|
-
|
|
2393
|
-
/**
|
|
2394
|
-
* Deno must call `Deno.upgradeWebSocket` in the same turn as the serve
|
|
2395
|
-
* callback. When the adapter implements `preUpgrade`, we do that here
|
|
2396
|
-
* and return the 101 response before any middleware Promise.
|
|
2397
|
-
*/
|
|
2398
|
-
fetch(
|
|
2399
|
-
request: Request,
|
|
2400
|
-
env: TEnv,
|
|
2401
|
-
executionCtx?: { waitUntil?: (p: Promise<unknown>) => void },
|
|
2402
|
-
): Response | Promise<Response> {
|
|
2403
|
-
if (!this._graph.served.has(this._chain)) {
|
|
2404
|
-
this._markUsed()
|
|
2405
|
-
this._graph.served.add(this._chain)
|
|
2406
|
-
this._bumpEpoch()
|
|
2407
|
-
}
|
|
2408
|
-
const wsAdapter = this._graph.settings.wsAdapter
|
|
2409
|
-
const path = normalizePath(pathOfUrl(request.url), this._graph.settings.encodedSlashes)
|
|
2410
|
-
if (path === null) return this._badRequestTarget()
|
|
2411
|
-
/* Deno and Bun before 1.4 build request.url by concatenating Host and the target, so a
|
|
2412
|
-
* `Host: x/admin?` would choose the routed path. `serve()` checks Host on Deno; this covers
|
|
2413
|
-
* `Deno.serve(app.fetch)`, which passes its serve info as env, and every Bun entry point on
|
|
2414
|
-
* an affected version. Node's adapter checks it; Bun 1.4+ routes on the target. */
|
|
2415
|
-
if ((BUN_URL_FROM_HOST || isDenoServeInfo(env)) && !hasValidHost(request)) return this._badRequestTarget()
|
|
2416
|
-
/* an adapter alone (serve() always installs one) does not need the websocket path */
|
|
2417
|
-
if (!this._graph.hasWs) {
|
|
2418
|
-
return this._doFetch(request, env, executionCtx, path)
|
|
2419
|
-
}
|
|
2420
|
-
const isWsUpgrade = request.headers.get("upgrade")?.toLowerCase() === "websocket"
|
|
2421
|
-
const canPreUpgrade =
|
|
2422
|
-
isWsUpgrade &&
|
|
2423
|
-
wsAdapter?.preUpgrade !== undefined &&
|
|
2424
|
-
!this.trailingSlashRedirects(path) &&
|
|
2425
|
-
this._matchWs(this._finalize(), this.pathAfterPrefix(path)) !== null
|
|
2426
|
-
/* Deno closes the Request in preUpgrade: snapshot the headers the chain will read first */
|
|
2427
|
-
const headerSnap = canPreUpgrade ? new Headers(request.headers) : undefined
|
|
2428
|
-
let pre: ReturnType<NonNullable<WSAdapter["preUpgrade"]>>
|
|
2429
|
-
try {
|
|
2430
|
-
pre = canPreUpgrade ? wsAdapter?.preUpgrade?.(request) : undefined
|
|
2431
|
-
} catch (err) {
|
|
2432
|
-
/* the runtime refused the handshake: answer through the normal path, which fails the upgrade */
|
|
2433
|
-
this._graph.settings.logger?.warn?.({ err }, "websocket preUpgrade failed")
|
|
2434
|
-
pre = undefined
|
|
2435
|
-
}
|
|
2436
|
-
/* After Deno.upgradeWebSocket the Request is closed. A sync throw here
|
|
2437
|
-
* used to be boxed by async _doFetch; keep 101 returning either way. */
|
|
2438
|
-
let work: Response | Promise<Response>
|
|
2439
|
-
try {
|
|
2440
|
-
work = this._doFetch(request, env, executionCtx, path, isWsUpgrade === true, headerSnap)
|
|
2441
|
-
} catch (err) {
|
|
2442
|
-
work = Promise.reject(err)
|
|
2443
|
-
}
|
|
2444
|
-
if (!pre) return work
|
|
2445
|
-
void Promise.resolve(work)
|
|
2446
|
-
.then((res) => {
|
|
2447
|
-
if (res.status === 101) return
|
|
2448
|
-
const code = res.status === 401 ? 4401 : 1008
|
|
2449
|
-
const reason = res.status === 401 ? "unauthorized" : "upgrade rejected"
|
|
2450
|
-
const reject = () => {
|
|
2451
|
-
try {
|
|
2452
|
-
pre.socket.close(code, reason)
|
|
2453
|
-
} catch {
|
|
2454
|
-
/* already closed */
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
if (pre.whenOpen) pre.whenOpen(reject)
|
|
2458
|
-
else reject()
|
|
2459
|
-
})
|
|
2460
|
-
.catch(() => {
|
|
2461
|
-
try {
|
|
2462
|
-
pre.socket.close(1011, "internal error")
|
|
2463
|
-
} catch {
|
|
2464
|
-
/* already closed */
|
|
2465
|
-
}
|
|
2466
|
-
})
|
|
2467
|
-
return pre.response
|
|
2468
|
-
}
|
|
2469
|
-
|
|
2470
|
-
/** @internal 400 for a request path the normalization policy rejects (see `normalizePath`), or an invalid `Host`. */
|
|
2471
|
-
_badRequestTarget(): Response {
|
|
2472
|
-
return this._toErrorResponse(this._createError(EK.bad_request, SK.bad_request))
|
|
2473
|
-
}
|
|
2474
|
-
|
|
2475
|
-
private trailingSlashRedirects(path: string): boolean {
|
|
2476
|
-
if (path.length <= 1) return false
|
|
2477
|
-
const mode = this._graph.settings.trailingSlash
|
|
2478
|
-
if (mode === "strip" && path.endsWith("/")) return true
|
|
2479
|
-
if (mode === "enforce" && !path.endsWith("/")) return true
|
|
2480
|
-
return false
|
|
2481
|
-
}
|
|
2482
|
-
|
|
2483
|
-
private pathAfterPrefix(path: string): string {
|
|
2484
|
-
const prefix = this._graph.settings.stripPrefix
|
|
2485
|
-
if (prefix === null) return path
|
|
2486
|
-
if (path === prefix) return "/"
|
|
2487
|
-
if (path.startsWith(prefix) && path.charCodeAt(prefix.length) === 47) {
|
|
2488
|
-
return path.slice(prefix.length)
|
|
2489
|
-
}
|
|
2490
|
-
return path
|
|
2491
|
-
}
|
|
2492
|
-
|
|
2493
|
-
private _doFetch(
|
|
2494
|
-
request: Request,
|
|
2495
|
-
env: TEnv,
|
|
2496
|
-
executionCtx: { waitUntil?: (p: Promise<unknown>) => void } | undefined,
|
|
2497
|
-
fullPath: string,
|
|
2498
|
-
knownWsUpgrade = false,
|
|
2499
|
-
headerSnap?: Headers,
|
|
2500
|
-
): Response | Promise<Response> {
|
|
2501
|
-
const startTime = performance.now()
|
|
2502
|
-
const final = this._finalize()
|
|
2503
|
-
const s = this._graph.settings
|
|
2504
|
-
const rawUrl = request.url
|
|
2505
|
-
|
|
2506
|
-
/* lazily create URL only when actually needed (search params, redirects) */
|
|
2507
|
-
let _url: URL | undefined
|
|
2508
|
-
const getUrl = (): URL => {
|
|
2509
|
-
if (_url === undefined) _url = new URL(rawUrl)
|
|
2510
|
-
return _url
|
|
2511
|
-
}
|
|
2512
|
-
|
|
2513
|
-
const log = s.logger ?? undefined
|
|
2514
|
-
const method = request.method.toUpperCase() as HttpMethod
|
|
2515
|
-
const fc: FetchCtx<TEnv> = {
|
|
2516
|
-
allowed: null,
|
|
2517
|
-
env,
|
|
2518
|
-
executionCtx,
|
|
2519
|
-
headerSnap,
|
|
2520
|
-
log,
|
|
2521
|
-
method,
|
|
2522
|
-
path: fullPath,
|
|
2523
|
-
plan: null,
|
|
2524
|
-
request,
|
|
2525
|
-
startTime,
|
|
2526
|
-
trust: s.trust,
|
|
2527
|
-
url: getUrl,
|
|
2528
|
-
ws: null,
|
|
2529
|
-
wsUpgrade: knownWsUpgrade,
|
|
2530
|
-
}
|
|
2531
|
-
|
|
2532
|
-
if (s.telemetry === null) return this._route(fc, final, fullPath, rawUrl, knownWsUpgrade)
|
|
2533
|
-
safeFire(() => s.telemetry?.onRequest?.({ env, req: request }), log)
|
|
2534
|
-
/* every exit after onRequest — redirects, 426, upgrades, misses, preflights, handlers —
|
|
2535
|
-
* fires onResponse exactly once, here */
|
|
2536
|
-
return this._withOnResponse(fc, this._route(fc, final, fullPath, rawUrl, knownWsUpgrade))
|
|
2537
|
-
}
|
|
2538
|
-
|
|
2539
|
-
private _route(
|
|
2540
|
-
fc: FetchCtx<TEnv>,
|
|
2541
|
-
final: FinalTable,
|
|
2542
|
-
fullPath: string,
|
|
2543
|
-
rawUrl: string,
|
|
2544
|
-
knownWsUpgrade: boolean,
|
|
2545
|
-
): Response | Promise<Response> {
|
|
2546
|
-
const s = this._graph.settings
|
|
2547
|
-
const { method, request } = fc
|
|
2548
|
-
|
|
2549
|
-
/* trailing slash handling — a relative Location: the scheme, host and port the client
|
|
2550
|
-
* used are the ones it keeps, whatever proxy terminated TLS in front of the app */
|
|
2551
|
-
if (this.trailingSlashRedirects(fullPath)) {
|
|
2552
|
-
const target = s.trailingSlash === "strip" ? fullPath.slice(0, -1) : `${fullPath}/`
|
|
2553
|
-
return new Response(null, {
|
|
2554
|
-
headers: { location: target + searchOfUrl(rawUrl) },
|
|
2555
|
-
status: 308,
|
|
2556
|
-
})
|
|
2557
|
-
}
|
|
2558
|
-
|
|
2559
|
-
/* prefix stripping — must run AFTER trailing slash so redirects preserve the full prefixed URL */
|
|
2560
|
-
const path = this.pathAfterPrefix(fullPath)
|
|
2561
|
-
fc.path = path
|
|
2562
|
-
const root = this._graph.root
|
|
2563
|
-
|
|
2564
|
-
/* WebSocket route check — do not re-read headers after Deno.upgradeWebSocket */
|
|
2565
|
-
const isWsUpgrade = knownWsUpgrade || requestIsWsUpgrade(request)
|
|
2566
|
-
if (isWsUpgrade) {
|
|
2567
|
-
const wsMatch = this._matchWs(final, path)
|
|
2568
|
-
if (wsMatch !== null) {
|
|
2569
|
-
return this._handleWs(fc, wsMatch)
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
|
|
2573
|
-
/* O(1) static route lookup — patterns without params or wildcards only */
|
|
2574
|
-
const staticHit =
|
|
2575
|
-
final.statics[`${method} ${path}`] ?? (method === "HEAD" ? final.statics[`GET ${path}`] : undefined)
|
|
2576
|
-
if (staticHit !== undefined) {
|
|
2577
|
-
return this._dispatchRecord(fc, staticHit, EMPTY_PARAMS)
|
|
2578
|
-
}
|
|
2579
|
-
|
|
2580
|
-
const result = matchRoute(root, method, path)
|
|
2581
|
-
if (result === null) {
|
|
2582
|
-
/* no HTTP route: a websocket or realtime route on this path asks for an upgrade */
|
|
2583
|
-
if (!isWsUpgrade && this._graph.hasWs) {
|
|
2584
|
-
const wsMatch = this._matchWs(final, path)
|
|
2585
|
-
if (wsMatch !== null) {
|
|
2586
|
-
if (this._graph.realtimeRoutes.has(wsMatch.handler.rp)) {
|
|
2587
|
-
return new Response(null, { headers: { upgrade: "websocket" }, status: 426 })
|
|
2588
|
-
}
|
|
2589
|
-
return new Response("Upgrade Required", {
|
|
2590
|
-
headers: { connection: "Upgrade", upgrade: "websocket" },
|
|
2591
|
-
status: 426,
|
|
2592
|
-
})
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
return this._handleMiss(fc, null)
|
|
2596
|
-
}
|
|
2597
|
-
if (!result.matched) {
|
|
2598
|
-
if (method === "OPTIONS") {
|
|
2599
|
-
const requested = request.headers.get("access-control-request-method")
|
|
2600
|
-
if (requested) {
|
|
2601
|
-
/* a preflight asks about one method: run that route's own chain */
|
|
2602
|
-
const hit = matchRoute(root, requested.trim().toUpperCase(), path)
|
|
2603
|
-
const plan = hit?.matched ? final.plans.get(hit.id) : undefined
|
|
2604
|
-
if (hit?.matched && plan !== undefined && plan.r.fn !== NOT_SERVED) {
|
|
2605
|
-
return this._handlePreflight(fc, plan, hit.params, result.allowed)
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
return this._handleMiss(fc, result.allowed)
|
|
2610
|
-
}
|
|
2611
|
-
const plan = final.plans.get(result.id)
|
|
2612
|
-
if (plan === undefined) return this._handleMiss(fc, null)
|
|
2613
|
-
return this._dispatchRecord(fc, plan, result.params)
|
|
2614
|
-
}
|
|
2615
|
-
|
|
2616
|
-
private _matchWs(
|
|
2617
|
-
final: FinalTable,
|
|
2618
|
-
path: string,
|
|
2619
|
-
): { handler: WSRouteHandler; params: Record<string, string> } | null {
|
|
2620
|
-
const hit = matchWsRoute(this._graph.root, path)
|
|
2621
|
-
if (hit === null) return null
|
|
2622
|
-
const handler = final.wsById.get(hit.id)
|
|
2623
|
-
return handler === undefined ? null : { handler, params: hit.params }
|
|
2624
|
-
}
|
|
2625
|
-
|
|
2626
|
-
private _dispatchRecord(
|
|
2627
|
-
fc: FetchCtx<TEnv>,
|
|
2628
|
-
plan: Plan,
|
|
2629
|
-
params: Record<string, string>,
|
|
2630
|
-
): Response | Promise<Response> {
|
|
2631
|
-
const record = plan.r as RouteHandler
|
|
2632
|
-
if (record.fn === NOT_SERVED) return this._handleMiss(fc, null)
|
|
2633
|
-
/* a delegated route keeps its own input schemas for docs and the 415 check; the body
|
|
2634
|
-
* itself belongs to whatever the catch-all forwards it to */
|
|
2635
|
-
if (record.dl === true && record.iv) {
|
|
2636
|
-
try {
|
|
2637
|
-
assertRequestContentType(record.iv, fc.request)
|
|
2638
|
-
} catch (thrown) {
|
|
2639
|
-
return this._toErrorResponse(thrown)
|
|
2640
|
-
}
|
|
2641
|
-
}
|
|
2642
|
-
return this._handleMatched(fc, plan, params)
|
|
2643
|
-
}
|
|
2644
|
-
|
|
2645
|
-
private _makeErrorCtx(fc: FetchCtx<TEnv>, allowed?: string[]) {
|
|
2646
|
-
const s = this._s
|
|
2647
|
-
return {
|
|
2648
|
-
env: fc.env,
|
|
2649
|
-
jsonFromError: (err: HoneyError) => createErrorResponse(err, s.errorFormatter, s.customErrorFormatter),
|
|
2650
|
-
req: fc.request,
|
|
2651
|
-
...(allowed ? { allowed } : {}),
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
|
|
2655
|
-
/** A request context for `plan`: route data, context values, `ctx.errors`, `ctx.realtime`. */
|
|
2656
|
-
private _newCtx(fc: FetchCtx<TEnv>, plan: Plan, params: Record<string, string>, req?: Request): HoneyContext<TEnv> {
|
|
2657
|
-
fc.plan = plan
|
|
2658
|
-
const r = plan.r
|
|
2659
|
-
const ctx = new HoneyContext<TEnv>({
|
|
2660
|
-
env: fc.env,
|
|
2661
|
-
executionCtx: fc.executionCtx,
|
|
2662
|
-
meta: r.mt ?? undefined,
|
|
2663
|
-
params,
|
|
2664
|
-
path: fc.path,
|
|
2665
|
-
req: req ?? fc.request,
|
|
2666
|
-
routePattern: r.rp ?? "",
|
|
2667
|
-
urlFn: fc.url,
|
|
2668
|
-
})
|
|
2669
|
-
if (plan.cv !== null) Object.assign(ctx, plan.cv)
|
|
2670
|
-
const rt = this._graph.realtimeCtx
|
|
2671
|
-
if (rt !== null) (ctx as { realtime: unknown }).realtime = rt
|
|
2672
|
-
if (r.ef) ctx._setErrors(r.ef)
|
|
2673
|
-
ctx._rq = fc
|
|
2674
|
-
return ctx
|
|
2675
|
-
}
|
|
2676
|
-
|
|
2677
|
-
private _handleWs(
|
|
2678
|
-
fc: FetchCtx<TEnv>,
|
|
2679
|
-
wsMatch: { handler: WSRouteHandler; params: Record<string, string> },
|
|
2680
|
-
): Response | Promise<Response> {
|
|
2681
|
-
const plan = this._finalize().wsPlans.get(wsMatch.handler.id as RouteId)
|
|
2682
|
-
if (plan === undefined) return this._handleMiss(fc, null)
|
|
2683
|
-
const isUpgrade = fc.wsUpgrade === true || requestIsWsUpgrade(fc.request)
|
|
2684
|
-
if (!isUpgrade) {
|
|
2685
|
-
return new Response(null, {
|
|
2686
|
-
headers: { upgrade: "websocket" },
|
|
2687
|
-
status: 426,
|
|
2688
|
-
})
|
|
2689
|
-
}
|
|
2690
|
-
if (!this._graph.settings.wsAdapter) {
|
|
2691
|
-
fc.log?.warn?.("WebSocket adapter not configured — call .wsAdapter()")
|
|
2692
|
-
return this._toErrorResponse(this._createError(EK.internal_server_error, SK.internal_server_error))
|
|
2693
|
-
}
|
|
2694
|
-
fc.ws = wsMatch
|
|
2695
|
-
const ctx = this._newCtx(fc, plan, wsMatch.params, ctxRequest(fc))
|
|
2696
|
-
return plan.run(ctx)
|
|
2697
|
-
}
|
|
2698
|
-
|
|
2699
|
-
/** Reject a cross-origin upgrade the route's origin policy does not allow (403). */
|
|
2700
|
-
/** The websocket session runtime; a cross-origin upgrade the route does not allow is a 403. */
|
|
2701
|
-
private async _wsSession(finalCtx: HoneyContext<TEnv>, policy: WSOriginPolicy | null) {
|
|
2702
|
-
const ws = await loadFeature("ws", "a websocket route")
|
|
2703
|
-
const headers = finalCtx.req.headers
|
|
2704
|
-
const origin = headers.get("origin")
|
|
2705
|
-
if (origin === null || policy === "*") return ws
|
|
2706
|
-
const credentialed = headers.has("cookie") || headers.has("authorization")
|
|
2707
|
-
if (!ws.originAllowed(policy, origin, clientInfo(finalCtx as unknown as HoneyContext).host, credentialed)) {
|
|
2708
|
-
throw this._createError(EK.forbidden, SK.forbidden)
|
|
2709
|
-
}
|
|
2710
|
-
return ws
|
|
2711
|
-
}
|
|
2712
|
-
|
|
2713
|
-
private async _wsUpgrade(finalCtx: HoneyContext<TEnv>): Promise<Response> {
|
|
2714
|
-
const fc = finalCtx._rq as FetchCtx<TEnv>
|
|
2715
|
-
const wsMatch = fc.ws as { handler: WSRouteHandler; params: Record<string, string> }
|
|
2716
|
-
const ws = await this._wsSession(finalCtx, wsMatch.handler.og ?? null)
|
|
2717
|
-
const wsAdapter = this._graph.settings.wsAdapter as WSAdapter
|
|
2718
|
-
const handler = ws.createWsSession({
|
|
2719
|
-
callbacks: wsMatch.handler.fn,
|
|
2720
|
-
ctx: finalCtx,
|
|
2721
|
-
log: (this._graph.settings.logger ?? null) as InvokeLogger | null,
|
|
2722
|
-
reconnectToken: fc.url().searchParams.get("reconnect_token"),
|
|
2723
|
-
route: wsMatch.handler.rp,
|
|
2724
|
-
})
|
|
2725
|
-
const upgradeResult = await wsAdapter.upgrade(fc.request, fc.env, handler)
|
|
2726
|
-
return upgradeResult.response
|
|
2727
|
-
}
|
|
2728
|
-
|
|
2729
|
-
/** Terminal of a realtime route's chain: identify, upgrade, and attach the connection to the bus. */
|
|
2730
|
-
private async _realtimeUpgrade(finalCtx: HoneyContext<TEnv>, config: RealtimeConfig): Promise<Response> {
|
|
2731
|
-
const fc = finalCtx._rq as FetchCtx<TEnv>
|
|
2732
|
-
await this._wsSession(finalCtx, config.allowedOrigins)
|
|
2733
|
-
const wsAdapter = this._graph.settings.wsAdapter as WSAdapter
|
|
2734
|
-
const rt = requireFeature("realtime", "app.realtime()")
|
|
2735
|
-
const bus = this._graph.realtimeBus ?? this._setBus(rt.createBus())
|
|
2736
|
-
/* a throwing identify rejects the upgrade through the chain's error boundary */
|
|
2737
|
-
const userId = config.identify === null ? null : ((await config.identify(finalCtx)) ?? null)
|
|
2738
|
-
const session = rt.createRealtimeSession({ bus, config, ctx: finalCtx, log: fc.log, userId })
|
|
2739
|
-
const upgradeResult = await wsAdapter.upgrade(fc.request, fc.env, session.handler)
|
|
2740
|
-
/* Node/CF return an open socket from upgrade(); Bun and Deno deliver it through onOpen */
|
|
2741
|
-
if (upgradeResult.socket && upgradeResult.socket.readyState === 1) session.attach(upgradeResult.socket)
|
|
2742
|
-
return upgradeResult.response
|
|
2743
|
-
}
|
|
2744
|
-
|
|
2745
|
-
/**
|
|
2746
|
-
* 404 (`allowed === null`) and 405: one pipeline with a synthetic record — full context,
|
|
2747
|
-
* the app-wide middleware and the scopes covering the path run once, telemetry fires once.
|
|
2748
|
-
*/
|
|
2749
|
-
private _handleMiss(fc: FetchCtx<TEnv>, allowed: string[] | null): Response | Promise<Response> {
|
|
2750
|
-
const s = this._graph.settings
|
|
2751
|
-
const { method, path, request } = fc
|
|
2752
|
-
if (allowed === null) {
|
|
2753
|
-
safeFire(() => s.telemetry?.onNotFound?.({ method, path, req: request }), fc.log)
|
|
2754
|
-
} else {
|
|
2755
|
-
safeFire(() => s.telemetry?.onMethodNotAllowed?.({ allowed, method, path, req: request }), fc.log)
|
|
2756
|
-
}
|
|
2757
|
-
fc.allowed = allowed
|
|
2758
|
-
const plan = this._finalize().miss
|
|
2759
|
-
const ctx = this._newCtx(fc, plan, EMPTY_PARAMS)
|
|
2760
|
-
return plan.run(ctx)
|
|
2761
|
-
}
|
|
2762
|
-
|
|
2763
|
-
/** Fire `onResponse` once for the request's one exit and return. A dispatch that rejects
|
|
2764
|
-
* (an adapter failure past every error boundary) still ends the request: it reports 500. */
|
|
2765
|
-
private _withOnResponse(fc: FetchCtx<TEnv>, res: Response | Promise<Response>): Response | Promise<Response> {
|
|
2766
|
-
const s = this._graph.settings
|
|
2767
|
-
if (s.telemetry?.onResponse === undefined) return res
|
|
2768
|
-
const fire = (status: number): void => {
|
|
2769
|
-
safeFire(
|
|
2770
|
-
() => s.telemetry?.onResponse?.({ duration: performance.now() - fc.startTime, req: fc.request, status }),
|
|
2771
|
-
fc.log,
|
|
2772
|
-
)
|
|
2773
|
-
}
|
|
2774
|
-
if (!(res instanceof Promise)) {
|
|
2775
|
-
fire(res.status)
|
|
2776
|
-
return res
|
|
2777
|
-
}
|
|
2778
|
-
return res.then(
|
|
2779
|
-
(r) => {
|
|
2780
|
-
fire(r.status)
|
|
2781
|
-
return r
|
|
2782
|
-
},
|
|
2783
|
-
(error: unknown) => {
|
|
2784
|
-
fire(500)
|
|
2785
|
-
throw error
|
|
2786
|
-
},
|
|
2787
|
-
)
|
|
2788
|
-
}
|
|
2789
|
-
|
|
2790
|
-
private _make404(fc: FetchCtx<TEnv>): Response | Promise<Response> {
|
|
2791
|
-
const s = this._s
|
|
2792
|
-
if (s.onNotFound) return s.onNotFound(this._makeErrorCtx(fc))
|
|
2793
|
-
return this._makeErrorCtx(fc).jsonFromError(this._createError(EK.not_found, SK.not_found))
|
|
2794
|
-
}
|
|
2795
|
-
|
|
2796
|
-
private async _make405(fc: FetchCtx<TEnv>, allowed: string[]): Promise<Response> {
|
|
2797
|
-
const s = this._s
|
|
2798
|
-
const res = s.onMethodNotAllowed
|
|
2799
|
-
? await s.onMethodNotAllowed(this._makeErrorCtx(fc, allowed) as ErrorCtx<TEnv> & { allowed: string[] })
|
|
2800
|
-
: this._makeErrorCtx(fc).jsonFromError(this._createError(EK.method_not_allowed, SK.method_not_allowed))
|
|
2801
|
-
const responseHeaders = new Headers(res.headers)
|
|
2802
|
-
responseHeaders.set("allow", allowed.join(", "))
|
|
2803
|
-
return new Response(res.body, {
|
|
2804
|
-
headers: responseHeaders,
|
|
2805
|
-
status: res.status,
|
|
2806
|
-
})
|
|
2807
|
-
}
|
|
2808
|
-
|
|
2809
|
-
/**
|
|
2810
|
-
* CORS preflight for a path that has no OPTIONS route: run the chain of the route the
|
|
2811
|
-
* preflight asks about (`Access-Control-Request-Method`) — chain, scoped and route
|
|
2812
|
-
* middleware, never input validation or the handler. Middleware that answers the
|
|
2813
|
-
* preflight (cors) short-circuits; otherwise the answer is the 405 with `Allow`.
|
|
2814
|
-
*/
|
|
2815
|
-
private _handlePreflight(
|
|
2816
|
-
fc: FetchCtx<TEnv>,
|
|
2817
|
-
plan: Plan,
|
|
2818
|
-
params: Record<string, string>,
|
|
2819
|
-
allowed: string[],
|
|
2820
|
-
): Response | Promise<Response> {
|
|
2821
|
-
fc.allowed = allowed
|
|
2822
|
-
const ctx = this._newCtx(fc, plan, params)
|
|
2823
|
-
const final = this._finalize()
|
|
2824
|
-
plan.pf ??= compileChain(
|
|
2825
|
-
plan.pfChain,
|
|
2826
|
-
(c) => {
|
|
2827
|
-
const rq = (c as HoneyContext<TEnv>)._rq as FetchCtx<TEnv>
|
|
2828
|
-
return this._make405(rq, rq.allowed ?? [])
|
|
2829
|
-
},
|
|
2830
|
-
final.convert,
|
|
2831
|
-
)
|
|
2832
|
-
return plan.pf(ctx)
|
|
2833
|
-
}
|
|
2834
|
-
|
|
2835
|
-
private _handleMatched(fc: FetchCtx<TEnv>, plan: Plan, params: Record<string, string>): Response | Promise<Response> {
|
|
2836
|
-
const ctx = this._newCtx(fc, plan, params)
|
|
2837
|
-
const telemetry = this._graph.settings.telemetry
|
|
2838
|
-
if (telemetry !== null && telemetry.onRoute !== undefined) {
|
|
2839
|
-
try {
|
|
2840
|
-
telemetry.onRoute({ method: fc.method, params, path: fc.path, req: fc.request, route: plan.r.rp ?? "" })
|
|
2841
|
-
} catch {
|
|
2842
|
-
/* telemetry must not crash request */
|
|
2843
|
-
}
|
|
2844
|
-
}
|
|
2845
|
-
const res = plan.run(ctx)
|
|
2846
|
-
if (res instanceof Promise) return res.then((r) => this._finishMatched(fc, plan, ctx, r))
|
|
2847
|
-
return this._finishMatched(fc, plan, ctx, res)
|
|
2848
|
-
}
|
|
2849
|
-
|
|
2850
|
-
/** Output validation, inside the chain: an invalid body becomes an error response the middleware sees. */
|
|
2851
|
-
private async _validateOutput(handler: RouteHandler, ctx: HoneyContext<TEnv>, response: Response): Promise<Response> {
|
|
2852
|
-
if (response === undefined || response === null || ctx._isErrorResponse) return response
|
|
2853
|
-
/* read the creation tag, never `response.body`: on Node that would build a stream per response */
|
|
2854
|
-
const kind = bodyKind(response)
|
|
2855
|
-
if (kind === "empty") return response
|
|
2856
|
-
const ct = response.headers.get("content-type")
|
|
2857
|
-
|
|
2858
|
-
/* content-type mismatch check */
|
|
2859
|
-
if (ct && handler.os) {
|
|
2860
|
-
const declaredTypes = Object.keys(handler.os)
|
|
2861
|
-
const matches = declaredTypes.some((t) => ct.startsWith(t))
|
|
2862
|
-
if (!matches) {
|
|
2863
|
-
throw new HoneyError({
|
|
2864
|
-
errorKey: EK.output_content_type_mismatch,
|
|
2865
|
-
status: SK.internal_server_error,
|
|
2866
|
-
})
|
|
2867
|
-
}
|
|
2868
|
-
}
|
|
2869
|
-
|
|
2870
|
-
/* JSON schema validation. A body Honey built from a string is validated from that string
|
|
2871
|
-
* (no stream, no tee); anything else is read from a clone (Bun clone() drains the original).
|
|
2872
|
-
* A stream Honey created (sse, stream, generate) is never buffered: it may never end. */
|
|
2873
|
-
if (ct?.startsWith("application/json") && handler.ov && !isProducedStream(response)) {
|
|
2874
|
-
const sk = codeToStatusKey[response.status]
|
|
2875
|
-
if (sk) {
|
|
2876
|
-
const raw = rawBodyOf(response)
|
|
2877
|
-
if (raw !== null) {
|
|
2878
|
-
const text = typeof raw === "string" ? raw : new TextDecoder().decode(raw)
|
|
2879
|
-
await handler.ov(sk, text.length === 0 ? null : JSON.parse(text))
|
|
2880
|
-
return response
|
|
2881
|
-
}
|
|
2882
|
-
const forReturn = response.clone()
|
|
2883
|
-
const data: unknown = await response.json()
|
|
2884
|
-
await handler.ov(sk, data)
|
|
2885
|
-
return forReturn
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
|
-
return response
|
|
2889
|
-
}
|
|
2890
|
-
|
|
2891
|
-
private _finishMatched(fc: FetchCtx<TEnv>, plan: Plan, ctx: HoneyContext<TEnv>, response: Response): Response {
|
|
2892
|
-
const s = this._graph.settings
|
|
2893
|
-
const handler = plan.r
|
|
2894
|
-
/* taps — fire after successful handler, non-blocking */
|
|
2895
|
-
if (plan.taps !== null && !ctx._isErrorResponse) {
|
|
2896
|
-
const taps = plan.taps
|
|
2897
|
-
const log = fc.log
|
|
2898
|
-
|
|
2899
|
-
/* meta-driven taps — fire for each registered key found in route meta */
|
|
2900
|
-
if (handler.mt !== null) {
|
|
2901
|
-
for (const [key, tapFn] of taps) {
|
|
2902
|
-
const metaValue = handler.mt[key]
|
|
2903
|
-
if (metaValue !== undefined) {
|
|
2904
|
-
ctx.background(
|
|
2905
|
-
Promise.resolve()
|
|
2906
|
-
.then(() => tapFn(ctx as unknown as TapContext<unknown>, metaValue))
|
|
2907
|
-
.catch((e) => log?.warn?.({ err: e, tap: key }, "tap failed")),
|
|
2908
|
-
)
|
|
2909
|
-
}
|
|
2910
|
-
}
|
|
2911
|
-
}
|
|
2912
|
-
|
|
2913
|
-
/* dynamic taps — fire for each c.tap() call */
|
|
2914
|
-
if (ctx._pendingTaps !== null) {
|
|
2915
|
-
for (const pending of ctx._pendingTaps) {
|
|
2916
|
-
const tapFn = taps.get(pending.key)
|
|
2917
|
-
if (tapFn !== undefined) {
|
|
2918
|
-
ctx.background(
|
|
2919
|
-
Promise.resolve()
|
|
2920
|
-
.then(() => tapFn(ctx as unknown as TapContext<unknown>, pending.payload))
|
|
2921
|
-
.catch((e) => log?.warn?.({ err: e, tap: pending.key }, "tap failed")),
|
|
2922
|
-
)
|
|
2923
|
-
}
|
|
2924
|
-
}
|
|
2925
|
-
ctx._pendingTaps = null
|
|
2926
|
-
}
|
|
2927
|
-
}
|
|
2928
|
-
|
|
2929
|
-
if (s.telemetry !== null) {
|
|
2930
|
-
try {
|
|
2931
|
-
const duration = performance.now() - fc.startTime
|
|
2932
|
-
s.telemetry.onHandler?.({
|
|
2933
|
-
duration,
|
|
2934
|
-
method: fc.method,
|
|
2935
|
-
path: fc.path,
|
|
2936
|
-
route: handler.rp ?? "",
|
|
2937
|
-
status: response.status,
|
|
2938
|
-
})
|
|
2939
|
-
} catch {
|
|
2940
|
-
/* telemetry must never crash the response path */
|
|
2941
|
-
}
|
|
2942
|
-
}
|
|
2943
|
-
if (fc.method === "HEAD") return headResponse(response)
|
|
2944
|
-
return response
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
|
-
|
|
2948
|
-
/*
|
|
2949
|
-
* Handler Type Algebra
|
|
2950
|
-
* These types compose the handler context from route configuration.
|
|
2951
|
-
* NarrowMethod → ApplyOutput → ApplyParams → HandlerCtx
|
|
2952
|
-
*/
|
|
2953
|
-
|
|
2954
|
-
/**
|
|
2955
|
-
* When content type IS declared → narrow the method (constrain status keys + body).
|
|
2956
|
-
* When content type NOT declared → remove the method (Omit).
|
|
2957
|
-
* Narrowed methods return TypedResponse<CT, K> for compile-time CT+SK safety.
|
|
2958
|
-
*/
|
|
2959
|
-
/** Resolve body type: schema → InferOutput, plain type → use as-is */
|
|
2960
|
-
type ResolveBody<T, TBody> = TBody extends "infer" ? (T extends StandardSchemaLike ? InferOutput<T> : T) : TBody
|
|
2961
|
-
|
|
2962
|
-
type NarrowMethod<TRes, TSchemas, Method extends string, CT extends string, TBody> = [TSchemas] extends [never]
|
|
2963
|
-
? Omit<TRes, Method>
|
|
2964
|
-
: TSchemas extends Record<string, unknown>
|
|
2965
|
-
? Omit<TRes, Method> & {
|
|
2966
|
-
[M in Method]: <K extends keyof TSchemas & string>(
|
|
2967
|
-
statusKey: K,
|
|
2968
|
-
body: ResolveBody<TSchemas[K], TBody>,
|
|
2969
|
-
opts?: ResponseOptions,
|
|
2970
|
-
) => TypedResponse<CT, K>
|
|
2971
|
-
}
|
|
2972
|
-
: Omit<TRes, Method>
|
|
2973
|
-
|
|
2974
|
-
/**
|
|
2975
|
-
* SSE uses a callback signature, not statusKey+body like other methods.
|
|
2976
|
-
* Gate presence on text/event-stream declaration, preserve original signature.
|
|
2977
|
-
*/
|
|
2978
|
-
type NarrowSSE<TOutput> = [ExtractSchemas<TOutput, "text/event-stream">] extends [never] ? {} : { sse: HoneyRes["sse"] }
|
|
2979
|
-
|
|
2980
|
-
/** Universal methods — always available, not gated by output declaration */
|
|
2981
|
-
type UniversalRes = Pick<HoneyRes, "noContent" | "raw" | "redirect" | "stream">
|
|
2982
|
-
|
|
2983
|
-
/**
|
|
2984
|
-
* Constrain ctx.res when output schemas are declared.
|
|
2985
|
-
* Declared content types → method constrained (status keys + body type).
|
|
2986
|
-
* Undeclared content types → method removed.
|
|
2987
|
-
* Universal methods (noContent, redirect, stream, raw) always available.
|
|
2988
|
-
* HoneyContext has no private brand, so Omit<TCtx, "res"> replaces the wide HoneyRes.
|
|
2989
|
-
*/
|
|
2990
|
-
type ApplyOutput<TCtx, TOutput> = [keyof TOutput] extends [never]
|
|
2991
|
-
? TCtx
|
|
2992
|
-
: Omit<TCtx, "res"> & {
|
|
2993
|
-
readonly res: UniversalRes &
|
|
2994
|
-
NarrowMethod<{}, ExtractSchemas<TOutput, "application/json">, "json", "application/json", "infer"> &
|
|
2995
|
-
NarrowMethod<{}, ExtractSchemas<TOutput, "text/plain">, "text", "text/plain", string> &
|
|
2996
|
-
NarrowMethod<{}, ExtractSchemas<TOutput, "text/html">, "html", "text/html", string> &
|
|
2997
|
-
NarrowMethod<{}, ExtractSchemas<TOutput, "application/xml">, "xml", "application/xml", string> &
|
|
2998
|
-
NarrowMethod<{}, ExtractSchemas<TOutput, "text/csv">, "csv", "text/csv", string> &
|
|
2999
|
-
NarrowMethod<
|
|
3000
|
-
{},
|
|
3001
|
-
ExtractSchemas<TOutput, "application/octet-stream">,
|
|
3002
|
-
"binary",
|
|
3003
|
-
"application/octet-stream",
|
|
3004
|
-
ArrayBuffer | Uint8Array<ArrayBuffer>
|
|
3005
|
-
> &
|
|
3006
|
-
NarrowSSE<TOutput>
|
|
3007
|
-
}
|
|
3008
|
-
|
|
3009
|
-
/** Public alias for codegen — applies output schema constraints to a context type */
|
|
3010
|
-
export type WithOutput<TCtx, TOutput> = ApplyOutput<TCtx, TOutput>
|
|
3011
|
-
|
|
3012
|
-
/** @internal — tuple entry describing a scoped middleware at the type level */
|
|
3013
|
-
type ScopedMwEntry = { readonly path: string; readonly adds: unknown }
|
|
3014
|
-
|
|
3015
|
-
/**
|
|
3016
|
-
* Walk TScopedMw tuple, intersect `adds` for every entry whose `path` is a prefix of TFullPath.
|
|
3017
|
-
* Prefix semantics: TFullPath extends `${P}` (exact) | `${P}/${string}` (descendant).
|
|
3018
|
-
*
|
|
3019
|
-
* Non-literal-path guard: if Head["path"] is the base `string` type (happens when
|
|
3020
|
-
* the user passes a widened variable), skip the entry via `string extends Head["path"] ? {}`.
|
|
3021
|
-
* This prevents the always-true `'/anywhere' extends string` from polluting every route.
|
|
3022
|
-
*/
|
|
3023
|
-
type ApplyScoped<TScopedMw extends readonly ScopedMwEntry[], TFullPath extends string> = TScopedMw extends readonly [
|
|
3024
|
-
infer Head extends ScopedMwEntry,
|
|
3025
|
-
...infer Rest extends readonly ScopedMwEntry[],
|
|
3026
|
-
]
|
|
3027
|
-
? (string extends Head["path"]
|
|
3028
|
-
? {}
|
|
3029
|
-
: TFullPath extends Head["path"]
|
|
3030
|
-
? Head["adds"]
|
|
3031
|
-
: TFullPath extends `${Head["path"] & string}/${string}`
|
|
3032
|
-
? Head["adds"]
|
|
3033
|
-
: {}) &
|
|
3034
|
-
ApplyScoped<Rest, TFullPath>
|
|
3035
|
-
: {}
|
|
3036
|
-
|
|
3037
|
-
/** Apply typed params — override params with specific keys when route has :param segments */
|
|
3038
|
-
type ApplyParams<TCtx, TParams> = [keyof TParams] extends [string]
|
|
3039
|
-
? string extends keyof TParams
|
|
3040
|
-
? TCtx
|
|
3041
|
-
: TCtx & { readonly params: TParams }
|
|
3042
|
-
: TCtx
|
|
3043
|
-
|
|
3044
|
-
/** Typed tap method — constrained to registered tap keys when TTaps is non-empty */
|
|
3045
|
-
type TypedTap<TTaps extends Record<string, unknown>> = [keyof TTaps] extends [never]
|
|
3046
|
-
? { tap(key: string, payload: unknown): void }
|
|
3047
|
-
: { tap<K extends string & keyof TTaps>(key: K, payload: TTaps[K]): void }
|
|
3048
|
-
|
|
3049
|
-
/** Build the full handler context: base ctx + params + input + meta + errors + taps + output-constrained methods */
|
|
3050
|
-
type HandlerCtx<
|
|
3051
|
-
TCtx,
|
|
3052
|
-
TInput,
|
|
3053
|
-
TOutput,
|
|
3054
|
-
TParams,
|
|
3055
|
-
TAccMeta = {},
|
|
3056
|
-
TErrorFactory = never,
|
|
3057
|
-
TErrorKeys extends string = never,
|
|
3058
|
-
TPath extends string = string,
|
|
3059
|
-
TTaps extends Record<string, unknown> = {},
|
|
3060
|
-
> = ApplyOutput<
|
|
3061
|
-
ApplyParams<[keyof TInput] extends [never] ? TCtx : TCtx & { input: TInput }, TParams> & {
|
|
3062
|
-
readonly meta: Readonly<Omit<TAccMeta, "openApi">>
|
|
3063
|
-
readonly routePattern: TPath
|
|
3064
|
-
} & ([TErrorFactory] extends [never]
|
|
3065
|
-
? {}
|
|
3066
|
-
: [TErrorKeys] extends [never]
|
|
3067
|
-
? { readonly errors: TErrorFactory }
|
|
3068
|
-
: {
|
|
3069
|
-
readonly errors: Pick<TErrorFactory, TErrorKeys & keyof TErrorFactory>
|
|
3070
|
-
}) &
|
|
3071
|
-
TypedTap<TTaps>,
|
|
3072
|
-
TOutput
|
|
3073
|
-
>
|
|
3074
|
-
|
|
3075
|
-
/** @internal — exposes private Honey members for RouteBuilder/WSRouteBuilder access */
|
|
3076
|
-
type HoneyInternal = {
|
|
3077
|
-
_addRoute(method: string, segments: readonly Segment[], record: RouteHandler): void
|
|
3078
|
-
_addWsRoute(segments: readonly Segment[], record: WSRouteHandler): void
|
|
3079
|
-
_factory: unknown
|
|
3080
|
-
_settle(pending: PendingRoute): void
|
|
3081
|
-
_view(): { chain: RuntimeMiddleware[]; cv: Record<string, unknown> | null; own: unknown }
|
|
3082
|
-
}
|
|
3083
|
-
|
|
3084
|
-
type RouteBuilderState<TParent> = {
|
|
3085
|
-
boundaryErrorKey: string | null
|
|
3086
|
-
/** keys the route declares itself (`.errors()`, `.boundary()`) */
|
|
3087
|
-
errorKeys: Set<string>
|
|
3088
|
-
extraMethods: (HttpMethod | "ALL")[] | null
|
|
3089
|
-
inputSchemas: InputSchemasDef | null
|
|
3090
|
-
/** explicit meta — chain `.meta()` overlaid by route `.meta()` */
|
|
3091
|
-
meta: Record<string, unknown> | null
|
|
3092
|
-
method: HttpMethod | "ALL"
|
|
3093
|
-
/** route-level middleware */
|
|
3094
|
-
middlewares: RuntimeMiddleware[]
|
|
3095
|
-
outputSchemas: OutputSchemaDef | null
|
|
3096
|
-
parent: TParent
|
|
3097
|
-
/** canonical full pattern */
|
|
3098
|
-
path: string
|
|
3099
|
-
/** reported at finalize until `.handler()` runs */
|
|
3100
|
-
pending: PendingRoute
|
|
3101
|
-
}
|
|
3102
|
-
|
|
3103
|
-
/**
|
|
3104
|
-
* Route meta = middleware-contributed meta, overlaid by everything stated explicitly
|
|
3105
|
-
* (chain `.meta()`, then route `.meta()`). Explicit always wins.
|
|
3106
|
-
*/
|
|
3107
|
-
function mergeContributedMeta(
|
|
3108
|
-
contributed: Record<string, unknown> | null | undefined,
|
|
3109
|
-
explicit: Record<string, unknown> | null,
|
|
3110
|
-
): Record<string, unknown> | null {
|
|
3111
|
-
if (!contributed) return explicit ? Object.freeze(explicit) : null
|
|
3112
|
-
return Object.freeze(explicit ? { ...contributed, ...explicit } : { ...contributed })
|
|
3113
|
-
}
|
|
3114
|
-
|
|
3115
|
-
/** Honey.TMeta defaults to never; never & X is never, so start acc meta at {}. */
|
|
3116
|
-
type InitAccMeta<TMeta> = [TMeta] extends [never] ? {} : TMeta
|
|
3117
|
-
|
|
3118
|
-
/** Skip $routes merge only for a real `{ internal: true }` meta — not for `never`. */
|
|
3119
|
-
type SkipRouteRecord<TAccMeta> = [TAccMeta] extends [never]
|
|
3120
|
-
? false
|
|
3121
|
-
: [TAccMeta] extends [{ internal: true }]
|
|
3122
|
-
? true
|
|
3123
|
-
: false
|
|
3124
|
-
|
|
3125
|
-
/** Return type for handler() — extracted to avoid 3x duplication */
|
|
3126
|
-
type HandlerReturn<
|
|
3127
|
-
TEnv,
|
|
3128
|
-
TBaseCtx,
|
|
3129
|
-
TRoutes,
|
|
3130
|
-
TPath extends string,
|
|
3131
|
-
TMethod extends string,
|
|
3132
|
-
TInput,
|
|
3133
|
-
TOutput,
|
|
3134
|
-
TCtx,
|
|
3135
|
-
TAccMeta,
|
|
3136
|
-
TErrorFactory,
|
|
3137
|
-
_TErrorKeys extends string,
|
|
3138
|
-
TDefaultErrors extends string,
|
|
3139
|
-
TMeta,
|
|
3140
|
-
TBasePath extends string = "/",
|
|
3141
|
-
TTaps extends Record<string, unknown> = {},
|
|
3142
|
-
TScopedMw extends readonly ScopedMwEntry[] = [],
|
|
3143
|
-
> = Honey<
|
|
3144
|
-
TEnv,
|
|
3145
|
-
TBaseCtx,
|
|
3146
|
-
SkipRouteRecord<TAccMeta> extends true
|
|
3147
|
-
? TRoutes
|
|
3148
|
-
: MergeRoute<
|
|
3149
|
-
TRoutes,
|
|
3150
|
-
TPath,
|
|
3151
|
-
TMethod,
|
|
3152
|
-
TInput,
|
|
3153
|
-
TOutput,
|
|
3154
|
-
HandlerCtx<
|
|
3155
|
-
TCtx,
|
|
3156
|
-
TInput,
|
|
3157
|
-
TOutput,
|
|
3158
|
-
ParamsFromPath<TPath>,
|
|
3159
|
-
TAccMeta,
|
|
3160
|
-
TErrorFactory,
|
|
3161
|
-
_TErrorKeys | TDefaultErrors,
|
|
3162
|
-
TPath,
|
|
3163
|
-
TTaps
|
|
3164
|
-
>,
|
|
3165
|
-
TAccMeta,
|
|
3166
|
-
_TErrorKeys | TDefaultErrors,
|
|
3167
|
-
ComputeErrorsByStatus<TErrorFactory, _TErrorKeys | TDefaultErrors, typeof ERROR_META>
|
|
3168
|
-
>,
|
|
3169
|
-
TMeta,
|
|
3170
|
-
TErrorFactory,
|
|
3171
|
-
TDefaultErrors,
|
|
3172
|
-
TBasePath,
|
|
3173
|
-
TTaps,
|
|
3174
|
-
TScopedMw
|
|
3175
|
-
>
|
|
3176
|
-
|
|
3177
|
-
type OneShotKey = "boundary" | "errors" | "input" | "meta" | "output"
|
|
3178
|
-
|
|
3179
|
-
type BuilderChain<
|
|
3180
|
-
TEnv,
|
|
3181
|
-
TCtx,
|
|
3182
|
-
TInput,
|
|
3183
|
-
TErrorKeys extends string,
|
|
3184
|
-
TOutput,
|
|
3185
|
-
TPath extends string,
|
|
3186
|
-
TMethod extends string,
|
|
3187
|
-
TRoutes,
|
|
3188
|
-
TUsed extends string,
|
|
3189
|
-
TBaseCtx,
|
|
3190
|
-
TMeta,
|
|
3191
|
-
TAccMeta,
|
|
3192
|
-
TErrorFactory,
|
|
3193
|
-
TDefaultErrors extends string,
|
|
3194
|
-
TBasePath extends string,
|
|
3195
|
-
TTaps extends Record<string, unknown> = {},
|
|
3196
|
-
TScopedMw extends readonly ScopedMwEntry[] = [],
|
|
3197
|
-
> = Omit<
|
|
3198
|
-
RouteBuilder<
|
|
3199
|
-
TEnv,
|
|
3200
|
-
TCtx,
|
|
3201
|
-
TInput,
|
|
3202
|
-
TErrorKeys,
|
|
3203
|
-
TOutput,
|
|
3204
|
-
TPath,
|
|
3205
|
-
TMethod,
|
|
3206
|
-
TRoutes,
|
|
3207
|
-
TUsed,
|
|
3208
|
-
TBaseCtx,
|
|
3209
|
-
TMeta,
|
|
3210
|
-
TAccMeta,
|
|
3211
|
-
TErrorFactory,
|
|
3212
|
-
TDefaultErrors,
|
|
3213
|
-
TBasePath,
|
|
3214
|
-
TTaps,
|
|
3215
|
-
TScopedMw
|
|
3216
|
-
>,
|
|
3217
|
-
TUsed & OneShotKey
|
|
3218
|
-
>
|
|
3219
|
-
|
|
3220
|
-
class RouteBuilder<
|
|
3221
|
-
TEnv,
|
|
3222
|
-
TCtx,
|
|
3223
|
-
TInput = {},
|
|
3224
|
-
_TErrorKeys extends string = never,
|
|
3225
|
-
TOutput = {},
|
|
3226
|
-
TPath extends string = string,
|
|
3227
|
-
TMethod extends string = string,
|
|
3228
|
-
TRoutes = {},
|
|
3229
|
-
TUsed extends string = never,
|
|
3230
|
-
TBaseCtx = TCtx,
|
|
3231
|
-
TMeta = never,
|
|
3232
|
-
TAccMeta = {},
|
|
3233
|
-
TErrorFactory = never,
|
|
3234
|
-
TDefaultErrors extends string = never,
|
|
3235
|
-
TBasePath extends string = "/",
|
|
3236
|
-
TTaps extends Record<string, unknown> = {},
|
|
3237
|
-
TScopedMw extends readonly ScopedMwEntry[] = [],
|
|
3238
|
-
> {
|
|
3239
|
-
private _s: RouteBuilderState<
|
|
3240
|
-
Honey<TEnv, TBaseCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
3241
|
-
>
|
|
3242
|
-
|
|
3243
|
-
constructor(
|
|
3244
|
-
state: RouteBuilderState<
|
|
3245
|
-
Honey<TEnv, TBaseCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
3246
|
-
>,
|
|
3247
|
-
) {
|
|
3248
|
-
this._s = state
|
|
3249
|
-
}
|
|
3250
|
-
|
|
3251
|
-
errors<
|
|
3252
|
-
TFactory extends Record<string, (...args: never[]) => unknown>,
|
|
3253
|
-
TKeys extends Exclude<keyof TFactory & string, TDefaultErrors>,
|
|
3254
|
-
>(
|
|
3255
|
-
factory: TFactory,
|
|
3256
|
-
...keys: TKeys[]
|
|
3257
|
-
): BuilderChain<
|
|
3258
|
-
TEnv,
|
|
3259
|
-
TCtx,
|
|
3260
|
-
TInput,
|
|
3261
|
-
_TErrorKeys | TKeys,
|
|
3262
|
-
TOutput,
|
|
3263
|
-
TPath,
|
|
3264
|
-
TMethod,
|
|
3265
|
-
TRoutes,
|
|
3266
|
-
TUsed | "errors",
|
|
3267
|
-
TBaseCtx,
|
|
3268
|
-
TMeta,
|
|
3269
|
-
TAccMeta,
|
|
3270
|
-
TErrorFactory,
|
|
3271
|
-
TDefaultErrors,
|
|
3272
|
-
TBasePath,
|
|
3273
|
-
TTaps,
|
|
3274
|
-
TScopedMw
|
|
3275
|
-
>
|
|
3276
|
-
errors<
|
|
3277
|
-
TKeys extends ([TErrorFactory] extends [never] ? never : Exclude<keyof TErrorFactory & string, TDefaultErrors>),
|
|
3278
|
-
>(
|
|
3279
|
-
...keys: TKeys[]
|
|
3280
|
-
): BuilderChain<
|
|
3281
|
-
TEnv,
|
|
3282
|
-
TCtx,
|
|
3283
|
-
TInput,
|
|
3284
|
-
_TErrorKeys | TKeys,
|
|
3285
|
-
TOutput,
|
|
3286
|
-
TPath,
|
|
3287
|
-
TMethod,
|
|
3288
|
-
TRoutes,
|
|
3289
|
-
TUsed | "errors",
|
|
3290
|
-
TBaseCtx,
|
|
3291
|
-
TMeta,
|
|
3292
|
-
TAccMeta,
|
|
3293
|
-
TErrorFactory,
|
|
3294
|
-
TDefaultErrors,
|
|
3295
|
-
TBasePath,
|
|
3296
|
-
TTaps,
|
|
3297
|
-
TScopedMw
|
|
3298
|
-
>
|
|
3299
|
-
errors(
|
|
3300
|
-
...args: unknown[]
|
|
3301
|
-
): BuilderChain<
|
|
3302
|
-
TEnv,
|
|
3303
|
-
TCtx,
|
|
3304
|
-
TInput,
|
|
3305
|
-
string,
|
|
3306
|
-
TOutput,
|
|
3307
|
-
TPath,
|
|
3308
|
-
TMethod,
|
|
3309
|
-
TRoutes,
|
|
3310
|
-
TUsed | "errors",
|
|
3311
|
-
TBaseCtx,
|
|
3312
|
-
TMeta,
|
|
3313
|
-
TAccMeta,
|
|
3314
|
-
TErrorFactory,
|
|
3315
|
-
TDefaultErrors,
|
|
3316
|
-
TBasePath,
|
|
3317
|
-
TTaps,
|
|
3318
|
-
TScopedMw
|
|
3319
|
-
> {
|
|
3320
|
-
const keys = typeof args[0] === "object" && args[0] !== null ? (args.slice(1) as string[]) : (args as string[])
|
|
3321
|
-
for (const k of keys) {
|
|
3322
|
-
this._s.errorKeys.add(k)
|
|
3323
|
-
}
|
|
3324
|
-
return new RouteBuilder<
|
|
3325
|
-
TEnv,
|
|
3326
|
-
TCtx,
|
|
3327
|
-
TInput,
|
|
3328
|
-
string,
|
|
3329
|
-
TOutput,
|
|
3330
|
-
TPath,
|
|
3331
|
-
TMethod,
|
|
3332
|
-
TRoutes,
|
|
3333
|
-
TUsed | "errors",
|
|
3334
|
-
TBaseCtx,
|
|
3335
|
-
TMeta,
|
|
3336
|
-
TAccMeta,
|
|
3337
|
-
TErrorFactory,
|
|
3338
|
-
TDefaultErrors,
|
|
3339
|
-
TBasePath,
|
|
3340
|
-
TTaps,
|
|
3341
|
-
TScopedMw
|
|
3342
|
-
>({
|
|
3343
|
-
...this._s,
|
|
3344
|
-
errorKeys: this._s.errorKeys,
|
|
3345
|
-
})
|
|
3346
|
-
}
|
|
3347
|
-
|
|
3348
|
-
boundary<
|
|
3349
|
-
TKey extends ([TErrorFactory] extends [never] ? never : Exclude<keyof TErrorFactory & string, TDefaultErrors>),
|
|
3350
|
-
>(
|
|
3351
|
-
key: TKey,
|
|
3352
|
-
): BuilderChain<
|
|
3353
|
-
TEnv,
|
|
3354
|
-
TCtx,
|
|
3355
|
-
TInput,
|
|
3356
|
-
_TErrorKeys | TKey,
|
|
3357
|
-
TOutput,
|
|
3358
|
-
TPath,
|
|
3359
|
-
TMethod,
|
|
3360
|
-
TRoutes,
|
|
3361
|
-
TUsed | "boundary",
|
|
3362
|
-
TBaseCtx,
|
|
3363
|
-
TMeta,
|
|
3364
|
-
TAccMeta,
|
|
3365
|
-
TErrorFactory,
|
|
3366
|
-
TDefaultErrors,
|
|
3367
|
-
TBasePath,
|
|
3368
|
-
TTaps,
|
|
3369
|
-
TScopedMw
|
|
3370
|
-
> {
|
|
3371
|
-
this._s.boundaryErrorKey = key
|
|
3372
|
-
this._s.errorKeys.add(key)
|
|
3373
|
-
return new RouteBuilder<
|
|
3374
|
-
TEnv,
|
|
3375
|
-
TCtx,
|
|
3376
|
-
TInput,
|
|
3377
|
-
_TErrorKeys | TKey,
|
|
3378
|
-
TOutput,
|
|
3379
|
-
TPath,
|
|
3380
|
-
TMethod,
|
|
3381
|
-
TRoutes,
|
|
3382
|
-
TUsed | "boundary",
|
|
3383
|
-
TBaseCtx,
|
|
3384
|
-
TMeta,
|
|
3385
|
-
TAccMeta,
|
|
3386
|
-
TErrorFactory,
|
|
3387
|
-
TDefaultErrors,
|
|
3388
|
-
TBasePath,
|
|
3389
|
-
TTaps,
|
|
3390
|
-
TScopedMw
|
|
3391
|
-
>({
|
|
3392
|
-
...this._s,
|
|
3393
|
-
boundaryErrorKey: key,
|
|
3394
|
-
errorKeys: this._s.errorKeys,
|
|
3395
|
-
})
|
|
3396
|
-
}
|
|
3397
|
-
|
|
3398
|
-
handler(
|
|
3399
|
-
fn: (
|
|
3400
|
-
ctx: HandlerCtx<
|
|
3401
|
-
TCtx,
|
|
3402
|
-
TInput,
|
|
3403
|
-
TOutput,
|
|
3404
|
-
ParamsFromPath<TPath>,
|
|
3405
|
-
TAccMeta,
|
|
3406
|
-
TErrorFactory,
|
|
3407
|
-
_TErrorKeys | TDefaultErrors,
|
|
3408
|
-
TPath,
|
|
3409
|
-
TTaps
|
|
3410
|
-
>,
|
|
3411
|
-
) => TypedResponse | Promise<TypedResponse>,
|
|
3412
|
-
): HandlerReturn<
|
|
3413
|
-
TEnv,
|
|
3414
|
-
TBaseCtx,
|
|
3415
|
-
TRoutes,
|
|
3416
|
-
TPath,
|
|
3417
|
-
TMethod,
|
|
3418
|
-
TInput,
|
|
3419
|
-
TOutput,
|
|
3420
|
-
TCtx,
|
|
3421
|
-
TAccMeta,
|
|
3422
|
-
TErrorFactory,
|
|
3423
|
-
_TErrorKeys,
|
|
3424
|
-
TDefaultErrors,
|
|
3425
|
-
TMeta,
|
|
3426
|
-
TBasePath,
|
|
3427
|
-
TTaps,
|
|
3428
|
-
TScopedMw
|
|
3429
|
-
> {
|
|
3430
|
-
let ov: OutputValidator | null = null
|
|
3431
|
-
const outputSchemas = this._s.outputSchemas
|
|
3432
|
-
if (outputSchemas) {
|
|
3433
|
-
const jsonSchemas = outputSchemas["application/json"]
|
|
3434
|
-
if (jsonSchemas) {
|
|
3435
|
-
ov = async (statusKey: string, data: unknown) => {
|
|
3436
|
-
const schema = jsonSchemas[statusKey as keyof typeof jsonSchemas]
|
|
3437
|
-
if (schema) {
|
|
3438
|
-
await validateOutput(schema, statusKey, data)
|
|
3439
|
-
}
|
|
3440
|
-
}
|
|
3441
|
-
}
|
|
3442
|
-
}
|
|
3443
|
-
|
|
3444
|
-
const isInternal = Symbol.for("honey.internal") in fn
|
|
3445
|
-
/* a bound copy per app: one `spec()` or docs handler may be mounted on several apps */
|
|
3446
|
-
if (isInternal) fn = bindInternalHandler(fn, this._s.parent)
|
|
3447
|
-
|
|
3448
|
-
const parent = this._s.parent as unknown as HoneyInternal
|
|
3449
|
-
const view = parent._view()
|
|
3450
|
-
const base: RouteHandler = {
|
|
3451
|
-
_skip: isInternal || undefined,
|
|
3452
|
-
bek: null,
|
|
3453
|
-
cm: [...view.chain],
|
|
3454
|
-
cv: view.cv,
|
|
3455
|
-
dk: new Set(this._s.errorKeys),
|
|
3456
|
-
/* provisional until finalize resolves scopes and app defaults */
|
|
3457
|
-
ek: new Set(this._s.errorKeys),
|
|
3458
|
-
fn: fn as (ctx: unknown) => Response | Promise<Response>,
|
|
3459
|
-
iv: this._s.inputSchemas,
|
|
3460
|
-
mt: mergeContributedMeta(collectMiddlewareMeta([view.chain, this._s.middlewares]), this._s.meta),
|
|
3461
|
-
mw: [],
|
|
3462
|
-
os: this._s.outputSchemas,
|
|
3463
|
-
ov,
|
|
3464
|
-
own: view.own,
|
|
3465
|
-
rb: this._s.boundaryErrorKey,
|
|
3466
|
-
rm: [...this._s.middlewares],
|
|
3467
|
-
xm: this._s.meta ? { ...this._s.meta } : null,
|
|
3468
|
-
}
|
|
3469
|
-
|
|
3470
|
-
/* one record per method — `.on([...])` registers each under its own RouteId */
|
|
3471
|
-
parent._settle(this._s.pending)
|
|
3472
|
-
const segments = parsePattern(this._s.path)
|
|
3473
|
-
const methods = [this._s.method, ...(this._s.extraMethods ?? [])]
|
|
3474
|
-
for (const method of methods) assertBodySchemaAllowed(this._s.inputSchemas, method, this._s.path)
|
|
3475
|
-
for (let i = 0; i < methods.length; i++) {
|
|
3476
|
-
parent._addRoute(methods[i], segments, i === 0 ? base : copyRecord(base))
|
|
3477
|
-
}
|
|
3478
|
-
return this._s.parent as HandlerReturn<
|
|
3479
|
-
TEnv,
|
|
3480
|
-
TBaseCtx,
|
|
3481
|
-
TRoutes,
|
|
3482
|
-
TPath,
|
|
3483
|
-
TMethod,
|
|
3484
|
-
TInput,
|
|
3485
|
-
TOutput,
|
|
3486
|
-
TCtx,
|
|
3487
|
-
TAccMeta,
|
|
3488
|
-
TErrorFactory,
|
|
3489
|
-
_TErrorKeys,
|
|
3490
|
-
TDefaultErrors,
|
|
3491
|
-
TMeta,
|
|
3492
|
-
TBasePath,
|
|
3493
|
-
TTaps,
|
|
3494
|
-
TScopedMw
|
|
3495
|
-
>
|
|
3496
|
-
}
|
|
3497
|
-
|
|
3498
|
-
proxy(
|
|
3499
|
-
config: ProxyConfig<
|
|
3500
|
-
HandlerCtx<
|
|
3501
|
-
TCtx,
|
|
3502
|
-
TInput,
|
|
3503
|
-
TOutput,
|
|
3504
|
-
ParamsFromPath<TPath>,
|
|
3505
|
-
TAccMeta,
|
|
3506
|
-
TErrorFactory,
|
|
3507
|
-
_TErrorKeys | TDefaultErrors,
|
|
3508
|
-
TPath,
|
|
3509
|
-
TTaps
|
|
3510
|
-
>
|
|
3511
|
-
>,
|
|
3512
|
-
): HandlerReturn<
|
|
3513
|
-
TEnv,
|
|
3514
|
-
TBaseCtx,
|
|
3515
|
-
TRoutes,
|
|
3516
|
-
TPath,
|
|
3517
|
-
TMethod,
|
|
3518
|
-
TInput,
|
|
3519
|
-
TOutput,
|
|
3520
|
-
TCtx,
|
|
3521
|
-
TAccMeta,
|
|
3522
|
-
TErrorFactory,
|
|
3523
|
-
_TErrorKeys,
|
|
3524
|
-
TDefaultErrors,
|
|
3525
|
-
TMeta,
|
|
3526
|
-
TBasePath,
|
|
3527
|
-
TTaps,
|
|
3528
|
-
TScopedMw
|
|
3529
|
-
> {
|
|
3530
|
-
const proxyHandler = requireFeature("proxy", ".proxy()").createProxyHandler(config)
|
|
3531
|
-
return this.handler(
|
|
3532
|
-
proxyHandler as (
|
|
3533
|
-
ctx: HandlerCtx<
|
|
3534
|
-
TCtx,
|
|
3535
|
-
TInput,
|
|
3536
|
-
TOutput,
|
|
3537
|
-
ParamsFromPath<TPath>,
|
|
3538
|
-
TAccMeta,
|
|
3539
|
-
TErrorFactory,
|
|
3540
|
-
_TErrorKeys | TDefaultErrors,
|
|
3541
|
-
TPath,
|
|
3542
|
-
TTaps
|
|
3543
|
-
>,
|
|
3544
|
-
) => Promise<TypedResponse>,
|
|
3545
|
-
)
|
|
3546
|
-
}
|
|
3547
|
-
|
|
3548
|
-
input<TSchemas extends InputSchemasDef>(
|
|
3549
|
-
schemas: TSchemas,
|
|
3550
|
-
): BuilderChain<
|
|
3551
|
-
TEnv,
|
|
3552
|
-
TCtx,
|
|
3553
|
-
TInput & InferInputMap<TSchemas>,
|
|
3554
|
-
_TErrorKeys,
|
|
3555
|
-
TOutput,
|
|
3556
|
-
TPath,
|
|
3557
|
-
TMethod,
|
|
3558
|
-
TRoutes,
|
|
3559
|
-
TUsed | "input",
|
|
3560
|
-
TBaseCtx,
|
|
3561
|
-
TMeta,
|
|
3562
|
-
TAccMeta,
|
|
3563
|
-
TErrorFactory,
|
|
3564
|
-
TDefaultErrors,
|
|
3565
|
-
TBasePath,
|
|
3566
|
-
TTaps,
|
|
3567
|
-
TScopedMw
|
|
3568
|
-
> {
|
|
3569
|
-
return new RouteBuilder<
|
|
3570
|
-
TEnv,
|
|
3571
|
-
TCtx,
|
|
3572
|
-
TInput & InferInputMap<TSchemas>,
|
|
3573
|
-
_TErrorKeys,
|
|
3574
|
-
TOutput,
|
|
3575
|
-
TPath,
|
|
3576
|
-
TMethod,
|
|
3577
|
-
TRoutes,
|
|
3578
|
-
TUsed | "input",
|
|
3579
|
-
TBaseCtx,
|
|
3580
|
-
TMeta,
|
|
3581
|
-
TAccMeta,
|
|
3582
|
-
TErrorFactory,
|
|
3583
|
-
TDefaultErrors,
|
|
3584
|
-
TBasePath,
|
|
3585
|
-
TTaps,
|
|
3586
|
-
TScopedMw
|
|
3587
|
-
>({
|
|
3588
|
-
...this._s,
|
|
3589
|
-
inputSchemas: schemas,
|
|
3590
|
-
})
|
|
3591
|
-
}
|
|
3592
|
-
|
|
3593
|
-
meta<TRouteMeta extends Partial<DefaultMeta> & ([TMeta] extends [never] ? {} : TMeta)>(
|
|
3594
|
-
meta: TRouteMeta,
|
|
3595
|
-
): BuilderChain<
|
|
3596
|
-
TEnv,
|
|
3597
|
-
TCtx,
|
|
3598
|
-
TInput,
|
|
3599
|
-
_TErrorKeys,
|
|
3600
|
-
TOutput,
|
|
3601
|
-
TPath,
|
|
3602
|
-
TMethod,
|
|
3603
|
-
TRoutes,
|
|
3604
|
-
TUsed | "meta",
|
|
3605
|
-
TBaseCtx,
|
|
3606
|
-
TMeta,
|
|
3607
|
-
TAccMeta & TRouteMeta,
|
|
3608
|
-
TErrorFactory,
|
|
3609
|
-
TDefaultErrors,
|
|
3610
|
-
TBasePath,
|
|
3611
|
-
TTaps,
|
|
3612
|
-
TScopedMw
|
|
3613
|
-
> {
|
|
3614
|
-
return new RouteBuilder<
|
|
3615
|
-
TEnv,
|
|
3616
|
-
TCtx,
|
|
3617
|
-
TInput,
|
|
3618
|
-
_TErrorKeys,
|
|
3619
|
-
TOutput,
|
|
3620
|
-
TPath,
|
|
3621
|
-
TMethod,
|
|
3622
|
-
TRoutes,
|
|
3623
|
-
TUsed | "meta",
|
|
3624
|
-
TBaseCtx,
|
|
3625
|
-
TMeta,
|
|
3626
|
-
TAccMeta & TRouteMeta,
|
|
3627
|
-
TErrorFactory,
|
|
3628
|
-
TDefaultErrors,
|
|
3629
|
-
TBasePath,
|
|
3630
|
-
TTaps,
|
|
3631
|
-
TScopedMw
|
|
3632
|
-
>({
|
|
3633
|
-
...this._s,
|
|
3634
|
-
meta: (() => {
|
|
3635
|
-
const merged: Record<string, unknown> = { ...this._s.meta, ...meta }
|
|
3636
|
-
const inv = merged["invalidate"]
|
|
3637
|
-
if (Array.isArray(inv) && inv.length > 1) {
|
|
3638
|
-
merged["invalidate"] = [...new Set(inv)]
|
|
3639
|
-
}
|
|
3640
|
-
return merged
|
|
3641
|
-
})(),
|
|
3642
|
-
})
|
|
3643
|
-
}
|
|
3644
|
-
|
|
3645
|
-
output<TOutputSchemas extends OutputSchemaDef>(
|
|
3646
|
-
_schemas: TOutputSchemas,
|
|
3647
|
-
): BuilderChain<
|
|
3648
|
-
TEnv,
|
|
3649
|
-
TCtx,
|
|
3650
|
-
TInput,
|
|
3651
|
-
_TErrorKeys,
|
|
3652
|
-
TOutputSchemas,
|
|
3653
|
-
TPath,
|
|
3654
|
-
TMethod,
|
|
3655
|
-
TRoutes,
|
|
3656
|
-
TUsed | "output",
|
|
3657
|
-
TBaseCtx,
|
|
3658
|
-
TMeta,
|
|
3659
|
-
TAccMeta,
|
|
3660
|
-
TErrorFactory,
|
|
3661
|
-
TDefaultErrors,
|
|
3662
|
-
TBasePath,
|
|
3663
|
-
TTaps,
|
|
3664
|
-
TScopedMw
|
|
3665
|
-
> {
|
|
3666
|
-
return new RouteBuilder<
|
|
3667
|
-
TEnv,
|
|
3668
|
-
TCtx,
|
|
3669
|
-
TInput,
|
|
3670
|
-
_TErrorKeys,
|
|
3671
|
-
TOutputSchemas,
|
|
3672
|
-
TPath,
|
|
3673
|
-
TMethod,
|
|
3674
|
-
TRoutes,
|
|
3675
|
-
TUsed | "output",
|
|
3676
|
-
TBaseCtx,
|
|
3677
|
-
TMeta,
|
|
3678
|
-
TAccMeta,
|
|
3679
|
-
TErrorFactory,
|
|
3680
|
-
TDefaultErrors,
|
|
3681
|
-
TBasePath,
|
|
3682
|
-
TTaps,
|
|
3683
|
-
TScopedMw
|
|
3684
|
-
>({
|
|
3685
|
-
...this._s,
|
|
3686
|
-
outputSchemas: _schemas,
|
|
3687
|
-
})
|
|
3688
|
-
}
|
|
3689
|
-
|
|
3690
|
-
use<TAdds>(
|
|
3691
|
-
mw: MiddlewareFn<TCtx, TAdds>,
|
|
3692
|
-
): BuilderChain<
|
|
3693
|
-
TEnv,
|
|
3694
|
-
TCtx & TAdds,
|
|
3695
|
-
TInput,
|
|
3696
|
-
_TErrorKeys,
|
|
3697
|
-
TOutput,
|
|
3698
|
-
TPath,
|
|
3699
|
-
TMethod,
|
|
3700
|
-
TRoutes,
|
|
3701
|
-
TUsed,
|
|
3702
|
-
TBaseCtx,
|
|
3703
|
-
TMeta,
|
|
3704
|
-
TAccMeta,
|
|
3705
|
-
TErrorFactory,
|
|
3706
|
-
TDefaultErrors,
|
|
3707
|
-
TBasePath,
|
|
3708
|
-
TTaps,
|
|
3709
|
-
TScopedMw
|
|
3710
|
-
> {
|
|
3711
|
-
return new RouteBuilder<
|
|
3712
|
-
TEnv,
|
|
3713
|
-
TCtx & TAdds,
|
|
3714
|
-
TInput,
|
|
3715
|
-
_TErrorKeys,
|
|
3716
|
-
TOutput,
|
|
3717
|
-
TPath,
|
|
3718
|
-
TMethod,
|
|
3719
|
-
TRoutes,
|
|
3720
|
-
TUsed,
|
|
3721
|
-
TBaseCtx,
|
|
3722
|
-
TMeta,
|
|
3723
|
-
TAccMeta,
|
|
3724
|
-
TErrorFactory,
|
|
3725
|
-
TDefaultErrors,
|
|
3726
|
-
TBasePath,
|
|
3727
|
-
TTaps,
|
|
3728
|
-
TScopedMw
|
|
3729
|
-
>({
|
|
3730
|
-
...this._s,
|
|
3731
|
-
middlewares: [...this._s.middlewares, mw as RuntimeMiddleware],
|
|
3732
|
-
})
|
|
3733
|
-
}
|
|
3734
|
-
}
|
|
3735
|
-
|
|
3736
|
-
type WSRouteBuilderState<TParent> = {
|
|
3737
|
-
errorKeys: Set<string>
|
|
3738
|
-
inputSchemas: InputSchemasDef | null
|
|
3739
|
-
origins: WSOriginPolicy | null
|
|
3740
|
-
meta: Record<string, unknown> | null
|
|
3741
|
-
middlewares: RuntimeMiddleware[]
|
|
3742
|
-
parent: TParent
|
|
3743
|
-
path: string
|
|
3744
|
-
pending: PendingRoute
|
|
3745
|
-
}
|
|
3746
|
-
|
|
3747
|
-
class WSRouteBuilder<TEnv, TCtx, TInput = {}, _TErrorKeys extends string = never, TParent = unknown> {
|
|
3748
|
-
private _s: WSRouteBuilderState<TParent>
|
|
3749
|
-
|
|
3750
|
-
constructor(state: WSRouteBuilderState<TParent>) {
|
|
3751
|
-
this._s = state
|
|
3752
|
-
}
|
|
3753
|
-
|
|
3754
|
-
errors<TFactory extends Record<string, (...args: never[]) => unknown>>(
|
|
3755
|
-
factory: TFactory,
|
|
3756
|
-
...keys: Array<keyof TFactory & string>
|
|
3757
|
-
): WSRouteBuilder<TEnv, TCtx, TInput, _TErrorKeys | (keyof TFactory & string), TParent> {
|
|
3758
|
-
void factory
|
|
3759
|
-
for (const k of keys) {
|
|
3760
|
-
this._s.errorKeys.add(k)
|
|
3761
|
-
}
|
|
3762
|
-
return new WSRouteBuilder<TEnv, TCtx, TInput, _TErrorKeys | (keyof TFactory & string), TParent>({
|
|
3763
|
-
...this._s,
|
|
3764
|
-
errorKeys: this._s.errorKeys,
|
|
3765
|
-
})
|
|
3766
|
-
}
|
|
3767
|
-
|
|
3768
|
-
handler(wsHandler: WSHandler<TCtx>): TParent {
|
|
3769
|
-
const parent = this._s.parent as unknown as HoneyInternal
|
|
3770
|
-
const view = parent._view()
|
|
3771
|
-
const routeHandler: WSRouteHandler = {
|
|
3772
|
-
bek: null,
|
|
3773
|
-
cm: [...view.chain],
|
|
3774
|
-
cv: view.cv,
|
|
3775
|
-
dk: new Set(this._s.errorKeys),
|
|
3776
|
-
ek: new Set(),
|
|
3777
|
-
fn: wsHandler as WSHandler<unknown>,
|
|
3778
|
-
iv: this._s.inputSchemas,
|
|
3779
|
-
mt: null,
|
|
3780
|
-
mw: [],
|
|
3781
|
-
og: this._s.origins,
|
|
3782
|
-
own: view.own,
|
|
3783
|
-
rb: null,
|
|
3784
|
-
rm: [...this._s.middlewares],
|
|
3785
|
-
rp: this._s.path,
|
|
3786
|
-
xm: this._s.meta ? { ...this._s.meta } : null,
|
|
3787
|
-
}
|
|
3788
|
-
parent._settle(this._s.pending)
|
|
3789
|
-
parent._addWsRoute(parsePattern(this._s.path), routeHandler)
|
|
3790
|
-
return this._s.parent
|
|
3791
|
-
}
|
|
3792
|
-
|
|
3793
|
-
input<TSchemas extends Pick<InputSchemasDef, "cookies" | "headers" | "search">>(
|
|
3794
|
-
schemas: TSchemas,
|
|
3795
|
-
): WSRouteBuilder<TEnv, TCtx, TInput & InferInputMap<TSchemas>, _TErrorKeys, TParent> {
|
|
3796
|
-
return new WSRouteBuilder<TEnv, TCtx, TInput & InferInputMap<TSchemas>, _TErrorKeys, TParent>({
|
|
3797
|
-
...this._s,
|
|
3798
|
-
inputSchemas: schemas,
|
|
3799
|
-
})
|
|
3800
|
-
}
|
|
3801
|
-
|
|
3802
|
-
meta(meta: Record<string, unknown>): this {
|
|
3803
|
-
this._s.meta = { ...this._s.meta, ...meta }
|
|
3804
|
-
return this
|
|
3805
|
-
}
|
|
3806
|
-
|
|
3807
|
-
/**
|
|
3808
|
-
* Browser origins allowed to open this socket: `"*"`, a list of origins
|
|
3809
|
-
* (`https://app.example.com`), or a predicate. Same-origin always passes. Without it, a
|
|
3810
|
-
* cross-origin upgrade that carries `Cookie` or `Authorization` gets 403.
|
|
3811
|
-
*/
|
|
3812
|
-
origins(policy: WSOriginPolicy): WSRouteBuilder<TEnv, TCtx, TInput, _TErrorKeys, TParent> {
|
|
3813
|
-
return new WSRouteBuilder<TEnv, TCtx, TInput, _TErrorKeys, TParent>({
|
|
3814
|
-
...this._s,
|
|
3815
|
-
origins: validateOriginPolicy(policy),
|
|
3816
|
-
})
|
|
3817
|
-
}
|
|
3818
|
-
|
|
3819
|
-
use<TAdds>(mw: MiddlewareFn<TCtx, TAdds>): WSRouteBuilder<TEnv, TCtx & TAdds, TInput, _TErrorKeys, TParent> {
|
|
3820
|
-
return new WSRouteBuilder<TEnv, TCtx & TAdds, TInput, _TErrorKeys, TParent>({
|
|
3821
|
-
...this._s,
|
|
3822
|
-
middlewares: [...this._s.middlewares, mw as RuntimeMiddleware],
|
|
3823
|
-
})
|
|
3824
|
-
}
|
|
3825
|
-
}
|
|
3826
|
-
|
|
3827
|
-
export function honey<TEnv>(): Honey<TEnv> {
|
|
3828
|
-
return new Honey<TEnv>()
|
|
3829
|
-
}
|