@lovrozagar/honey 0.6.8 → 0.7.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/README.md +514 -192
- package/bin/honey.js +25 -0
- package/dist/accepts.d.ts +7 -1
- package/dist/accepts.d.ts.map +1 -1
- package/dist/accepts.js +126 -0
- package/dist/accepts.js.map +1 -0
- package/dist/body-kind.d.ts +26 -0
- package/dist/body-kind.d.ts.map +1 -0
- package/dist/body-kind.js +70 -0
- package/dist/body-kind.js.map +1 -0
- package/dist/body-limit.d.ts +5 -1
- package/dist/body-limit.d.ts.map +1 -1
- package/dist/body-limit.js +126 -0
- package/dist/body-limit.js.map +1 -0
- package/dist/build/index.d.ts +19 -3
- package/dist/build/index.d.ts.map +1 -1
- package/dist/build/index.js +173 -0
- package/dist/build/index.js.map +1 -0
- package/dist/cli-args.d.ts +33 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +102 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli-go/auth.go +26 -0
- package/dist/cli-go/config.go +192 -0
- package/dist/cli-go/errors.go +75 -0
- package/dist/cli-go/multipart.go +79 -0
- package/dist/cli-go/output.go +163 -0
- package/dist/cli-go/root.go +30 -0
- package/dist/cli-go/stream.go +88 -0
- package/dist/cli-go/version.go +5 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +209 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/defaults.js +48 -0
- package/dist/client/defaults.js.map +1 -0
- package/dist/client/error.d.ts +35 -0
- package/dist/client/error.d.ts.map +1 -1
- package/dist/client/error.js +71 -0
- package/dist/client/error.js.map +1 -0
- package/dist/client/http.d.ts +67 -8
- package/dist/client/http.d.ts.map +1 -1
- package/dist/client/http.js +571 -0
- package/dist/client/http.js.map +1 -0
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +95 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/path.d.ts +26 -0
- package/dist/client/path.d.ts.map +1 -0
- package/dist/client/path.js +163 -0
- package/dist/client/path.js.map +1 -0
- package/dist/client/redirect-policy.d.ts +12 -0
- package/dist/client/redirect-policy.d.ts.map +1 -0
- package/dist/client/redirect-policy.js +20 -0
- package/dist/client/redirect-policy.js.map +1 -0
- package/dist/client/sdk.d.ts +36 -0
- package/dist/client/sdk.d.ts.map +1 -1
- package/dist/client/sdk.js +292 -0
- package/dist/client/sdk.js.map +1 -0
- package/dist/client/sse.d.ts +13 -0
- package/dist/client/sse.d.ts.map +1 -1
- package/dist/client/sse.js +129 -0
- package/dist/client/sse.js.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/ws.d.ts +9 -2
- package/dist/client/ws.d.ts.map +1 -1
- package/dist/client/ws.js +112 -0
- package/dist/client/ws.js.map +1 -0
- package/dist/client-go/errors.go +236 -0
- package/dist/client-go/export.go +44 -0
- package/dist/client-go/invalidation.go +371 -0
- package/dist/client-go/nullable.go +62 -0
- package/dist/client-go/realtime.go +354 -0
- package/dist/client-go/result.go +20 -0
- package/dist/client-go/runtime.go +975 -0
- package/dist/client-go/sse.go +159 -0
- package/dist/client-go/transport.go +495 -0
- package/dist/client-go/ws.go +128 -0
- package/dist/client-info.d.ts +36 -0
- package/dist/client-info.d.ts.map +1 -0
- package/dist/client-info.js +53 -0
- package/dist/client-info.js.map +1 -0
- package/dist/client-mcp/README.md +10 -0
- package/dist/client-mcp/server.ts +221 -0
- package/dist/client-mcp/types.ts +19 -0
- package/dist/client-python/_errors.py +115 -0
- package/dist/client-python/_invalidation.py +494 -0
- package/dist/client-python/_realtime.py +179 -0
- package/dist/client-python/_runtime.py +860 -0
- package/dist/client-python/_sse.py +155 -0
- package/dist/client-python/_transport.py +366 -0
- package/dist/client-python/_ws.py +159 -0
- package/dist/client-python/py.typed +0 -0
- package/dist/client-rust/errors.rs +220 -0
- package/dist/client-rust/invalidation.rs +412 -0
- package/dist/client-rust/invalidation_sync.rs +93 -0
- package/dist/client-rust/nullable.rs +17 -0
- package/dist/client-rust/realtime.rs +702 -0
- package/dist/client-rust/result.rs +22 -0
- package/dist/client-rust/runtime.rs +980 -0
- package/dist/client-rust/runtime_sync.rs +484 -0
- package/dist/client-rust/sse.rs +188 -0
- package/dist/client-rust/ws.rs +167 -0
- package/dist/codegen-go-cli.d.ts.map +1 -1
- package/dist/codegen-go-cli.js +964 -0
- package/dist/codegen-go-cli.js.map +1 -0
- package/dist/codegen-go.d.ts +15 -3
- package/dist/codegen-go.d.ts.map +1 -1
- package/dist/codegen-go.js +872 -0
- package/dist/codegen-go.js.map +1 -0
- package/dist/codegen-hash.js +9 -0
- package/dist/codegen-hash.js.map +1 -0
- package/dist/codegen-ir.d.ts +69 -2
- package/dist/codegen-ir.d.ts.map +1 -1
- package/dist/codegen-ir.js +661 -0
- package/dist/codegen-ir.js.map +1 -0
- package/dist/codegen-lang.d.ts +65 -0
- package/dist/codegen-lang.d.ts.map +1 -0
- package/dist/codegen-lang.js +526 -0
- package/dist/codegen-lang.js.map +1 -0
- package/dist/codegen-load.d.ts +31 -0
- package/dist/codegen-load.d.ts.map +1 -0
- package/dist/codegen-load.js +138 -0
- package/dist/codegen-load.js.map +1 -0
- package/dist/codegen-loaders.js +55 -0
- package/dist/codegen-loaders.js.map +1 -0
- package/dist/codegen-mcp.d.ts.map +1 -1
- package/dist/codegen-mcp.js +368 -0
- package/dist/codegen-mcp.js.map +1 -0
- package/dist/codegen-python.d.ts +13 -1
- package/dist/codegen-python.d.ts.map +1 -1
- package/dist/codegen-python.js +828 -0
- package/dist/codegen-python.js.map +1 -0
- package/dist/codegen-route-tree-intern.d.ts +15 -5
- package/dist/codegen-route-tree-intern.d.ts.map +1 -1
- package/dist/codegen-route-tree-intern.js +221 -0
- package/dist/codegen-route-tree-intern.js.map +1 -0
- package/dist/codegen-rust.d.ts.map +1 -1
- package/dist/codegen-rust.js +1293 -0
- package/dist/codegen-rust.js.map +1 -0
- package/dist/codegen-sanitize.d.ts.map +1 -1
- package/dist/codegen-sanitize.js +56 -0
- package/dist/codegen-sanitize.js.map +1 -0
- package/dist/codegen-schema-naming.d.ts +5 -0
- package/dist/codegen-schema-naming.d.ts.map +1 -1
- package/dist/codegen-schema-naming.js +150 -0
- package/dist/codegen-schema-naming.js.map +1 -0
- package/dist/codegen-sdk-model.d.ts +71 -0
- package/dist/codegen-sdk-model.d.ts.map +1 -0
- package/dist/codegen-sdk-model.js +179 -0
- package/dist/codegen-sdk-model.js.map +1 -0
- package/dist/codegen.d.ts +41 -23
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +4610 -0
- package/dist/codegen.js.map +1 -0
- package/dist/context.d.ts +36 -6
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +242 -0
- package/dist/context.js.map +1 -0
- package/dist/cookie-sign.d.ts +16 -2
- package/dist/cookie-sign.d.ts.map +1 -1
- package/dist/cookie-sign.js +104 -0
- package/dist/cookie-sign.js.map +1 -0
- package/dist/cookie.d.ts +12 -0
- package/dist/cookie.d.ts.map +1 -1
- package/dist/cookie.js +138 -0
- package/dist/cookie.js.map +1 -0
- package/dist/cors.d.ts +1 -0
- package/dist/cors.d.ts.map +1 -1
- package/dist/cors.js +87 -0
- package/dist/cors.js.map +1 -0
- package/dist/crypto.d.ts +6 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +38 -0
- package/dist/crypto.js.map +1 -0
- package/dist/csrf.d.ts +19 -0
- package/dist/csrf.d.ts.map +1 -1
- package/dist/csrf.js +79 -0
- package/dist/csrf.js.map +1 -0
- package/dist/curl-logger.d.ts +14 -3
- package/dist/curl-logger.d.ts.map +1 -1
- package/dist/curl-logger.js +199 -0
- package/dist/curl-logger.js.map +1 -0
- package/dist/detect-runtime.js +13 -0
- package/dist/detect-runtime.js.map +1 -0
- package/dist/dict.d.ts +11 -0
- package/dist/dict.d.ts.map +1 -0
- package/dist/dict.js +13 -0
- package/dist/dict.js.map +1 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +44 -0
- package/dist/error.js.map +1 -0
- package/dist/errors.js +39 -0
- package/dist/errors.js.map +1 -0
- package/dist/etag.d.ts +2 -0
- package/dist/etag.d.ts.map +1 -1
- package/dist/etag.js +82 -0
- package/dist/etag.js.map +1 -0
- package/dist/feature-detect.d.ts +25 -0
- package/dist/feature-detect.d.ts.map +1 -0
- package/dist/feature-detect.js +144 -0
- package/dist/feature-detect.js.map +1 -0
- package/dist/feature-load.js +36 -0
- package/dist/feature-load.js.map +1 -0
- package/dist/feature-slots.d.ts +44 -0
- package/dist/feature-slots.d.ts.map +1 -0
- package/dist/feature-slots.js +48 -0
- package/dist/feature-slots.js.map +1 -0
- package/dist/fetch-request.d.ts +10 -0
- package/dist/fetch-request.d.ts.map +1 -0
- package/dist/fetch-request.js +13 -0
- package/dist/fetch-request.js.map +1 -0
- package/dist/gen-process.d.ts +15 -0
- package/dist/gen-process.d.ts.map +1 -0
- package/dist/gen-process.js +43 -0
- package/dist/gen-process.js.map +1 -0
- package/dist/gen-write.d.ts +19 -0
- package/dist/gen-write.d.ts.map +1 -0
- package/dist/gen-write.js +120 -0
- package/dist/gen-write.js.map +1 -0
- package/dist/glob.d.ts +21 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +115 -0
- package/dist/glob.js.map +1 -0
- package/dist/go-type-emitter.d.ts +18 -4
- package/dist/go-type-emitter.d.ts.map +1 -1
- package/dist/go-type-emitter.js +594 -0
- package/dist/go-type-emitter.js.map +1 -0
- package/dist/honey-response.d.ts.map +1 -1
- package/dist/honey-response.js +338 -0
- package/dist/honey-response.js.map +1 -0
- package/dist/i18n-slot.d.ts +8 -0
- package/dist/i18n-slot.d.ts.map +1 -1
- package/dist/i18n-slot.js +14 -0
- package/dist/i18n-slot.js.map +1 -0
- package/dist/i18n.d.ts +3 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +250 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +408 -148
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2324 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +3 -5
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +140 -0
- package/dist/init.js.map +1 -0
- package/dist/input.js +4 -0
- package/dist/input.js.map +1 -0
- package/dist/invalidate-check.d.ts +11 -0
- package/dist/invalidate-check.d.ts.map +1 -1
- package/dist/invalidate-check.js +163 -0
- package/dist/invalidate-check.js.map +1 -0
- package/dist/invoke-user.d.ts +35 -0
- package/dist/invoke-user.d.ts.map +1 -0
- package/dist/invoke-user.js +73 -0
- package/dist/invoke-user.js.map +1 -0
- package/dist/ip-restrict.d.ts +13 -1
- package/dist/ip-restrict.d.ts.map +1 -1
- package/dist/ip-restrict.js +60 -0
- package/dist/ip-restrict.js.map +1 -0
- package/dist/ip.d.ts +39 -0
- package/dist/ip.d.ts.map +1 -0
- package/dist/ip.js +209 -0
- package/dist/ip.js.map +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +113 -0
- package/dist/logger.js.map +1 -0
- package/dist/media-type.d.ts +30 -0
- package/dist/media-type.d.ts.map +1 -0
- package/dist/media-type.js +97 -0
- package/dist/media-type.js.map +1 -0
- package/dist/meta-spec-merge.d.ts +32 -0
- package/dist/meta-spec-merge.d.ts.map +1 -0
- package/dist/meta-spec-merge.js +68 -0
- package/dist/meta-spec-merge.js.map +1 -0
- package/dist/meta-spec.d.ts +16 -3
- package/dist/meta-spec.d.ts.map +1 -1
- package/dist/meta-spec.js +535 -0
- package/dist/meta-spec.js.map +1 -0
- package/dist/middleware.d.ts +23 -6
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +253 -0
- package/dist/middleware.js.map +1 -0
- package/dist/node-request.d.ts +57 -12
- package/dist/node-request.d.ts.map +1 -1
- package/dist/node-request.js +407 -0
- package/dist/node-request.js.map +1 -0
- package/dist/node.d.ts +20 -3
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +396 -0
- package/dist/node.js.map +1 -0
- package/dist/openapi/collect.d.ts +6 -3
- package/dist/openapi/collect.d.ts.map +1 -1
- package/dist/openapi/collect.js +30 -0
- package/dist/openapi/collect.js.map +1 -0
- package/dist/openapi/docs-page.d.ts +42 -0
- package/dist/openapi/docs-page.d.ts.map +1 -0
- package/dist/openapi/docs-page.js +80 -0
- package/dist/openapi/docs-page.js.map +1 -0
- package/dist/openapi/document.d.ts +4 -0
- package/dist/openapi/document.d.ts.map +1 -1
- package/dist/openapi/document.js +785 -0
- package/dist/openapi/document.js.map +1 -0
- package/dist/openapi/json-schema-slot.d.ts +7 -0
- package/dist/openapi/json-schema-slot.d.ts.map +1 -1
- package/dist/openapi/json-schema-slot.js +38 -0
- package/dist/openapi/json-schema-slot.js.map +1 -0
- package/dist/openapi/register.d.ts.map +1 -1
- package/dist/openapi/register.js +20 -0
- package/dist/openapi/register.js.map +1 -0
- package/dist/openapi/scalar.d.ts +3 -1
- package/dist/openapi/scalar.d.ts.map +1 -1
- package/dist/openapi/scalar.js +21 -0
- package/dist/openapi/scalar.js.map +1 -0
- package/dist/openapi/serve.d.ts +9 -0
- package/dist/openapi/serve.d.ts.map +1 -0
- package/dist/openapi/serve.js +36 -0
- package/dist/openapi/serve.js.map +1 -0
- package/dist/openapi/spec-factory.d.ts +30 -1
- package/dist/openapi/spec-factory.d.ts.map +1 -1
- package/dist/openapi/spec-factory.js +52 -0
- package/dist/openapi/spec-factory.js.map +1 -0
- package/dist/openapi/spec.d.ts +14 -5
- package/dist/openapi/spec.d.ts.map +1 -1
- package/dist/openapi/spec.js +50 -0
- package/dist/openapi/spec.js.map +1 -0
- package/dist/openapi/swagger.d.ts +3 -1
- package/dist/openapi/swagger.d.ts.map +1 -1
- package/dist/openapi/swagger.js +22 -0
- package/dist/openapi/swagger.js.map +1 -0
- package/dist/pattern.d.ts +71 -0
- package/dist/pattern.d.ts.map +1 -0
- package/dist/pattern.js +239 -0
- package/dist/pattern.js.map +1 -0
- package/dist/peer.d.ts +17 -0
- package/dist/peer.d.ts.map +1 -0
- package/dist/peer.js +50 -0
- package/dist/peer.js.map +1 -0
- package/dist/plugin.d.ts +67 -16
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +649 -0
- package/dist/plugin.js.map +1 -0
- package/dist/powered-by.d.ts.map +1 -1
- package/dist/powered-by.js +11 -0
- package/dist/powered-by.js.map +1 -0
- package/dist/pretty-json.d.ts.map +1 -1
- package/dist/pretty-json.js +47 -0
- package/dist/pretty-json.js.map +1 -0
- package/dist/producer-stream.d.ts +47 -0
- package/dist/producer-stream.d.ts.map +1 -0
- package/dist/producer-stream.js +267 -0
- package/dist/producer-stream.js.map +1 -0
- package/dist/proxy.d.ts +47 -8
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +274 -0
- package/dist/proxy.js.map +1 -0
- package/dist/python-type-emitter.d.ts +14 -1
- package/dist/python-type-emitter.d.ts.map +1 -1
- package/dist/python-type-emitter.js +157 -0
- package/dist/python-type-emitter.js.map +1 -0
- package/dist/realtime/bus.d.ts +17 -1
- package/dist/realtime/bus.d.ts.map +1 -1
- package/dist/realtime/bus.js +112 -0
- package/dist/realtime/bus.js.map +1 -0
- package/dist/realtime/register.d.ts +3 -0
- package/dist/realtime/register.d.ts.map +1 -0
- package/dist/realtime/register.js +10 -0
- package/dist/realtime/register.js.map +1 -0
- package/dist/realtime/route.d.ts +65 -26
- package/dist/realtime/route.d.ts.map +1 -1
- package/dist/realtime/route.js +72 -0
- package/dist/realtime/route.js.map +1 -0
- package/dist/realtime/server.d.ts +43 -0
- package/dist/realtime/server.d.ts.map +1 -0
- package/dist/realtime/server.js +255 -0
- package/dist/realtime/server.js.map +1 -0
- package/dist/request-id.d.ts +7 -0
- package/dist/request-id.d.ts.map +1 -1
- package/dist/request-id.js +16 -0
- package/dist/request-id.js.map +1 -0
- package/dist/request-limits.d.ts +3 -0
- package/dist/request-limits.d.ts.map +1 -0
- package/dist/request-limits.js +3 -0
- package/dist/request-limits.js.map +1 -0
- package/dist/request-path.d.ts +33 -0
- package/dist/request-path.d.ts.map +1 -0
- package/dist/request-path.js +178 -0
- package/dist/request-path.js.map +1 -0
- package/dist/request-to-curl.d.ts +7 -1
- package/dist/request-to-curl.d.ts.map +1 -1
- package/dist/request-to-curl.js +48 -0
- package/dist/request-to-curl.js.map +1 -0
- package/dist/response.d.ts +25 -2
- package/dist/response.d.ts.map +1 -1
- package/dist/response.js +313 -0
- package/dist/response.js.map +1 -0
- package/dist/rust-type-emitter.d.ts +14 -2
- package/dist/rust-type-emitter.d.ts.map +1 -1
- package/dist/rust-type-emitter.js +463 -0
- package/dist/rust-type-emitter.js.map +1 -0
- package/dist/secure-headers.d.ts.map +1 -1
- package/dist/secure-headers.js +53 -0
- package/dist/secure-headers.js.map +1 -0
- package/dist/serve-register.d.ts +4 -0
- package/dist/serve-register.d.ts.map +1 -1
- package/dist/serve-register.js +9 -0
- package/dist/serve-register.js.map +1 -0
- package/dist/serve-slot.d.ts +3 -0
- package/dist/serve-slot.d.ts.map +1 -1
- package/dist/serve-slot.js +52 -0
- package/dist/serve-slot.js.map +1 -0
- package/dist/serve.d.ts +18 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +157 -0
- package/dist/serve.js.map +1 -0
- package/dist/server-timing.d.ts.map +1 -1
- package/dist/server-timing.js +51 -0
- package/dist/server-timing.js.map +1 -0
- package/dist/static.d.ts +9 -0
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +102 -0
- package/dist/static.js.map +1 -0
- package/dist/telemetry/otel.d.ts +2 -0
- package/dist/telemetry/otel.d.ts.map +1 -1
- package/dist/telemetry/otel.js +135 -0
- package/dist/telemetry/otel.js.map +1 -0
- package/dist/testing.d.ts +12 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +124 -0
- package/dist/testing.js.map +1 -0
- package/dist/timeout.d.ts +6 -0
- package/dist/timeout.d.ts.map +1 -1
- package/dist/timeout.js +36 -0
- package/dist/timeout.js.map +1 -0
- package/dist/tree.d.ts +133 -36
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +437 -0
- package/dist/tree.js.map +1 -0
- package/dist/trust.d.ts +21 -0
- package/dist/trust.d.ts.map +1 -0
- package/dist/trust.js +100 -0
- package/dist/trust.js.map +1 -0
- package/dist/ts-type-emitter.d.ts.map +1 -1
- package/dist/ts-type-emitter.js +150 -0
- package/dist/ts-type-emitter.js.map +1 -0
- package/dist/type-emitter.d.ts +16 -2
- package/dist/type-emitter.d.ts.map +1 -1
- package/dist/type-emitter.js +729 -0
- package/dist/type-emitter.js.map +1 -0
- package/dist/type-extractor.d.ts +8 -4
- package/dist/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor.js +819 -0
- package/dist/type-extractor.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +96 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +14 -2
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +335 -0
- package/dist/validation.js.map +1 -0
- package/dist/with-headers.d.ts +15 -0
- package/dist/with-headers.d.ts.map +1 -0
- package/dist/with-headers.js +45 -0
- package/dist/with-headers.js.map +1 -0
- package/dist/ws/bun.d.ts +11 -5
- package/dist/ws/bun.d.ts.map +1 -1
- package/dist/ws/bun.js +82 -0
- package/dist/ws/bun.js.map +1 -0
- package/dist/ws/cloudflare.d.ts +25 -3
- package/dist/ws/cloudflare.d.ts.map +1 -1
- package/dist/ws/cloudflare.js +117 -0
- package/dist/ws/cloudflare.js.map +1 -0
- package/dist/ws/deno.d.ts +4 -4
- package/dist/ws/deno.d.ts.map +1 -1
- package/dist/ws/deno.js +133 -0
- package/dist/ws/deno.js.map +1 -0
- package/dist/ws/node.d.ts +20 -3
- package/dist/ws/node.d.ts.map +1 -1
- package/dist/ws/node.js +173 -0
- package/dist/ws/node.js.map +1 -0
- package/dist/ws/session.d.ts +11 -0
- package/dist/ws/session.d.ts.map +1 -0
- package/dist/ws/session.js +60 -0
- package/dist/ws/session.js.map +1 -0
- package/dist/ws/shared.d.ts +65 -0
- package/dist/ws/shared.d.ts.map +1 -0
- package/dist/ws/shared.js +103 -0
- package/dist/ws/shared.js.map +1 -0
- package/dist/ws-origin.d.ts +20 -0
- package/dist/ws-origin.d.ts.map +1 -0
- package/dist/ws-origin.js +41 -0
- package/dist/ws-origin.js.map +1 -0
- package/dist/yaml.d.ts +15 -1
- package/dist/yaml.d.ts.map +1 -1
- package/dist/yaml.js +101 -0
- package/dist/yaml.js.map +1 -0
- package/docs/invalidate-spec.md +13 -1
- package/docs/meta-spec.md +36 -12
- package/docs/packaging.md +43 -24
- package/docs/regression-matrix/README.md +32 -0
- package/docs/regression-matrix/review.md +44 -0
- package/docs/regression-matrix/ws1-3.md +97 -0
- package/docs/regression-matrix/ws4-8.md +107 -0
- package/docs/regression-matrix/ws9-12.md +266 -0
- package/docs/sdk.md +102 -52
- package/package.json +183 -52
- package/src/accepts.ts +91 -33
- package/src/body-kind.ts +67 -0
- package/src/body-limit.ts +102 -87
- package/src/build/index.ts +104 -54
- package/src/cli-args.ts +123 -0
- package/src/cli-go/auth.go +12 -1
- package/src/cli-go/config.go +29 -2
- package/src/cli-go/errors.go +28 -10
- package/src/cli-go/multipart.go +51 -27
- package/src/cli-go/output.go +58 -4
- package/src/cli-go/root.go +1 -1
- package/src/cli-go/version.go +2 -2
- package/src/cli.ts +154 -148
- package/src/client/error.ts +56 -1
- package/src/client/http.ts +479 -256
- package/src/client/index.ts +55 -17
- package/src/client/path.ts +169 -0
- package/src/client/redirect-policy.ts +19 -0
- package/src/client/sdk.ts +180 -101
- package/src/client/sse.ts +92 -66
- package/src/client/ws.ts +67 -31
- package/src/client-go/errors.go +41 -12
- package/src/client-go/export.go +44 -0
- package/src/client-go/invalidation.go +52 -54
- package/src/client-go/nullable.go +62 -0
- package/src/client-go/realtime.go +147 -110
- package/src/client-go/runtime.go +646 -88
- package/src/client-go/sse.go +104 -94
- package/src/client-go/transport.go +67 -27
- package/src/client-go/ws.go +49 -25
- package/src/client-info.ts +79 -0
- package/src/client-mcp/server.ts +133 -3
- package/src/client-python/_invalidation.py +207 -291
- package/src/client-python/_realtime.py +63 -40
- package/src/client-python/_runtime.py +501 -119
- package/src/client-python/_sse.py +130 -74
- package/src/client-python/_transport.py +37 -9
- package/src/client-python/_ws.py +50 -22
- package/src/client-rust/errors.rs +21 -1
- package/src/client-rust/invalidation.rs +202 -231
- package/src/client-rust/invalidation_sync.rs +16 -229
- package/src/client-rust/nullable.rs +17 -0
- package/src/client-rust/realtime.rs +217 -217
- package/src/client-rust/runtime.rs +602 -177
- package/src/client-rust/runtime_sync.rs +169 -145
- package/src/client-rust/sse.rs +142 -121
- package/src/client-rust/ws.rs +61 -37
- package/src/codegen-go-cli.ts +607 -728
- package/src/codegen-go.ts +710 -770
- package/src/codegen-ir.ts +381 -75
- package/src/codegen-lang.ts +517 -0
- package/src/codegen-load.ts +150 -0
- package/src/codegen-mcp.ts +207 -108
- package/src/codegen-python.ts +684 -816
- package/src/codegen-route-tree-intern.ts +51 -25
- package/src/codegen-rust.ts +946 -1240
- package/src/codegen-sanitize.ts +0 -4
- package/src/codegen-schema-naming.ts +19 -6
- package/src/codegen-sdk-model.ts +228 -0
- package/src/codegen.ts +1488 -1140
- package/src/context.ts +130 -11
- package/src/cookie-sign.ts +85 -16
- package/src/cookie.ts +66 -11
- package/src/cors.ts +55 -54
- package/src/crypto.ts +27 -5
- package/src/csrf.ts +57 -25
- package/src/curl-logger.ts +155 -114
- package/src/dict.ts +12 -0
- package/src/error.ts +4 -2
- package/src/etag.ts +58 -34
- package/src/feature-detect.ts +147 -0
- package/src/feature-slots.ts +70 -0
- package/src/fetch-request.ts +13 -0
- package/src/gen-process.ts +42 -0
- package/src/gen-write.ts +127 -0
- package/src/glob.ts +109 -0
- package/src/go-type-emitter.ts +406 -155
- package/src/honey-response.ts +36 -9
- package/src/i18n-slot.ts +10 -0
- package/src/i18n.ts +98 -23
- package/src/index.ts +1887 -1756
- package/src/init.ts +40 -28
- package/src/invalidate-check.ts +42 -4
- package/src/invoke-user.ts +95 -0
- package/src/ip-restrict.ts +47 -161
- package/src/ip.ts +194 -0
- package/src/logger.ts +65 -36
- package/src/media-type.ts +104 -0
- package/src/meta-spec-merge.ts +80 -0
- package/src/meta-spec.ts +0 -0
- package/src/middleware.ts +112 -12
- package/src/node-request.ts +242 -52
- package/src/node.ts +318 -75
- package/src/openapi/collect.ts +19 -44
- package/src/openapi/docs-page.ts +89 -0
- package/src/openapi/document.ts +503 -224
- package/src/openapi/json-schema-slot.ts +40 -0
- package/src/openapi/register.ts +7 -2
- package/src/openapi/scalar.ts +11 -10
- package/src/openapi/serve.ts +39 -0
- package/src/openapi/spec-factory.ts +50 -1
- package/src/openapi/spec.ts +53 -30
- package/src/openapi/swagger.ts +12 -11
- package/src/pattern.ts +245 -0
- package/src/peer.ts +53 -0
- package/src/plugin.ts +270 -185
- package/src/powered-by.ts +2 -2
- package/src/pretty-json.ts +25 -9
- package/src/producer-stream.ts +296 -0
- package/src/proxy.ts +253 -55
- package/src/python-type-emitter.ts +68 -56
- package/src/realtime/bus.ts +56 -9
- package/src/realtime/register.ts +13 -0
- package/src/realtime/route.ts +129 -80
- package/src/realtime/server.ts +298 -0
- package/src/request-id.ts +13 -3
- package/src/request-limits.ts +2 -0
- package/src/request-path.ts +165 -0
- package/src/request-to-curl.ts +28 -7
- package/src/response.ts +143 -103
- package/src/rust-type-emitter.ts +265 -170
- package/src/secure-headers.ts +8 -4
- package/src/serve-register.ts +4 -0
- package/src/serve-slot.ts +38 -0
- package/src/serve.ts +113 -25
- package/src/server-timing.ts +11 -7
- package/src/static.ts +85 -32
- package/src/telemetry/otel.ts +29 -7
- package/src/testing.ts +76 -14
- package/src/timeout.ts +15 -7
- package/src/tree.ts +405 -333
- package/src/trust.ts +117 -0
- package/src/ts-type-emitter.ts +18 -12
- package/src/type-emitter.ts +227 -89
- package/src/type-extractor.ts +603 -423
- package/src/types.ts +8 -0
- package/src/validation.ts +221 -96
- package/src/with-headers.ts +45 -0
- package/src/ws/bun.ts +53 -15
- package/src/ws/cloudflare.ts +81 -19
- package/src/ws/deno.ts +124 -36
- package/src/ws/node.ts +173 -79
- package/src/ws/session.ts +73 -0
- package/src/ws/shared.ts +140 -0
- package/src/ws-origin.ts +53 -0
- package/src/yaml.ts +45 -22
- package/dist/client-mcp/server.d.ts +0 -10
- package/dist/client-mcp/server.d.ts.map +0 -1
- package/dist/client-mcp/types.d.ts +0 -16
- package/dist/client-mcp/types.d.ts.map +0 -1
- package/dist/cloudflare/realtime.d.ts +0 -30
- package/dist/cloudflare/realtime.d.ts.map +0 -1
- package/dist/realtime/buffer.d.ts +0 -18
- package/dist/realtime/buffer.d.ts.map +0 -1
- package/dist/realtime/longpoll.d.ts +0 -23
- package/dist/realtime/longpoll.d.ts.map +0 -1
- package/dist/realtime/protocol.d.ts +0 -50
- package/dist/realtime/protocol.d.ts.map +0 -1
- package/dist/realtime/runtime.d.ts +0 -74
- package/dist/realtime/runtime.d.ts.map +0 -1
- package/src/cloudflare/realtime.ts +0 -234
- package/src/realtime/buffer.ts +0 -101
- package/src/realtime/longpoll.ts +0 -148
- package/src/realtime/protocol.ts +0 -128
- package/src/realtime/runtime.ts +0 -427
package/src/type-extractor.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { existsSync } from "node:fs"
|
|
2
|
-
import { dirname, relative, resolve } from "node:path"
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
+
import { dirname, join, relative, resolve, sep } from "node:path"
|
|
3
3
|
import type { ts } from "ts-morph"
|
|
4
|
+
import { canonical, expandOptional, joinPatterns, parsePattern } from "./pattern.ts"
|
|
5
|
+
import { quoteKey } from "./type-emitter.ts"
|
|
4
6
|
|
|
5
7
|
export type ExtractedBaseCtx = {
|
|
6
8
|
envType: string
|
|
@@ -8,85 +10,105 @@ export type ExtractedBaseCtx = {
|
|
|
8
10
|
tapsType: string | null
|
|
9
11
|
}
|
|
10
12
|
|
|
13
|
+
/**
|
|
14
|
+
* The tsconfig that governs the entry: the nearest one walking up, but never past the
|
|
15
|
+
* project root (the nearest directory with a package.json) — a stray tsconfig in a parent
|
|
16
|
+
* directory or the home folder is not this project's.
|
|
17
|
+
*/
|
|
11
18
|
function findTsConfig(fromPath: string): string | undefined {
|
|
12
19
|
let dir = dirname(resolve(fromPath))
|
|
13
20
|
let prev = ""
|
|
14
21
|
while (dir !== prev) {
|
|
15
22
|
const p = resolve(dir, "tsconfig.json")
|
|
16
23
|
if (existsSync(p)) return p
|
|
24
|
+
if (existsSync(resolve(dir, "package.json"))) return undefined
|
|
17
25
|
prev = dir
|
|
18
26
|
dir = dirname(dir)
|
|
19
27
|
}
|
|
20
28
|
return undefined
|
|
21
29
|
}
|
|
22
30
|
|
|
31
|
+
/* ts-morph bundles a different TS version — structurally identical at runtime */
|
|
32
|
+
function bridge<T>(value: unknown): T {
|
|
33
|
+
return value as T
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type LoadedEntry = {
|
|
37
|
+
/** The app's type */
|
|
38
|
+
appType: ts.Type
|
|
39
|
+
checker: ts.TypeChecker
|
|
40
|
+
compiler: typeof ts
|
|
41
|
+
/** A node inside the entry, for type lookups that need a location */
|
|
42
|
+
node: ts.Node
|
|
43
|
+
project: import("ts-morph").Project
|
|
44
|
+
sourceFile: import("ts-morph").SourceFile
|
|
45
|
+
}
|
|
46
|
+
|
|
23
47
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
48
|
+
* Load the entry and find the app export: `export const app = …`, any other exported
|
|
49
|
+
* declaration of that name, or `export default honey()` for `exportName: "default"`.
|
|
26
50
|
*/
|
|
27
|
-
|
|
51
|
+
async function loadEntry(options: {
|
|
28
52
|
entryPath: string
|
|
29
53
|
exportName: string
|
|
30
|
-
outputDir?: string
|
|
31
54
|
tsconfigPath?: string
|
|
32
|
-
}): Promise<
|
|
33
|
-
const { Project, ts } = await import("ts-morph")
|
|
34
|
-
|
|
55
|
+
}): Promise<LoadedEntry> {
|
|
56
|
+
const { Project, ts: tsLib } = await import("ts-morph")
|
|
35
57
|
const tsConfigFilePath = options.tsconfigPath ?? findTsConfig(options.entryPath)
|
|
36
|
-
|
|
37
|
-
const project = new Project({
|
|
38
|
-
skipAddingFilesFromTsConfig: true,
|
|
39
|
-
tsConfigFilePath,
|
|
40
|
-
})
|
|
41
|
-
|
|
58
|
+
const project = new Project({ skipAddingFilesFromTsConfig: true, tsConfigFilePath })
|
|
42
59
|
project.addSourceFilesAtPaths(options.entryPath)
|
|
43
60
|
project.resolveSourceFileDependencies()
|
|
44
61
|
|
|
45
62
|
const sourceFile = project.getSourceFileOrThrow(options.entryPath)
|
|
46
|
-
const
|
|
47
|
-
|
|
63
|
+
const declarations = sourceFile.getExportedDeclarations().get(options.exportName)
|
|
64
|
+
const decl = declarations?.[0] ?? sourceFile.getVariableDeclaration(options.exportName)
|
|
65
|
+
if (!decl) {
|
|
48
66
|
throw new Error(`Export "${options.exportName}" not found in ${options.entryPath}`)
|
|
49
67
|
}
|
|
50
|
-
|
|
51
|
-
const type = varDecl.getType()
|
|
52
|
-
/* ts-morph bundles different TS version — structurally identical at runtime */
|
|
53
|
-
function bridge<T>(value: unknown): T {
|
|
54
|
-
return value as T
|
|
55
|
-
}
|
|
56
68
|
const checker: ts.TypeChecker = bridge(project.getTypeChecker().compilerObject)
|
|
57
|
-
const node: ts.Node = bridge(
|
|
58
|
-
const
|
|
69
|
+
const node: ts.Node = bridge(decl.compilerNode)
|
|
70
|
+
const compiler: typeof ts = bridge(tsLib)
|
|
71
|
+
/* `export default <expr>` declares an ExportAssignment; its type is the expression's */
|
|
72
|
+
const target = compiler.isExportAssignment(node) ? node.expression : node
|
|
73
|
+
const appType = checker.getTypeAtLocation(target)
|
|
74
|
+
return { appType, checker, compiler, node: target, project, sourceFile }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Uses ts-morph to extract the middleware context type from a Honey app export.
|
|
79
|
+
* Returns inline type strings so the gen file has zero imports from user code.
|
|
80
|
+
*/
|
|
81
|
+
export async function extractBaseCtx(options: {
|
|
82
|
+
entryPath: string
|
|
83
|
+
exportName: string
|
|
84
|
+
outputDir?: string
|
|
85
|
+
tsconfigPath?: string
|
|
86
|
+
}): Promise<ExtractedBaseCtx> {
|
|
87
|
+
const { appType, checker, compiler, node } = await loadEntry(options)
|
|
88
|
+
const ser = new Serializer(checker, node, compiler)
|
|
59
89
|
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
return checker.getTypeOfSymbolAtLocation(
|
|
90
|
+
const typeOfProp = (name: string): ts.Type | null => {
|
|
91
|
+
const sym = appType.getProperty(name)
|
|
92
|
+
return sym ? checker.getTypeOfSymbolAtLocation(sym, node) : null
|
|
63
93
|
}
|
|
64
94
|
|
|
65
95
|
/* extract $env */
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
if (envProp) {
|
|
69
|
-
envType = serialize(getType(envProp), checker, node, tsLib)
|
|
70
|
-
}
|
|
96
|
+
const envT = typeOfProp("$env")
|
|
97
|
+
const envType = envT ? ser.serialize(envT) : "Record<string, unknown>"
|
|
71
98
|
|
|
72
99
|
/* extract $ctx and isolate middleware additions */
|
|
73
|
-
const
|
|
74
|
-
if (!
|
|
100
|
+
const ctxType = typeOfProp("$ctx")
|
|
101
|
+
if (!ctxType) {
|
|
75
102
|
throw new Error(`Export "${options.exportName}" has no $ctx — not a Honey instance`)
|
|
76
103
|
}
|
|
77
|
-
|
|
78
|
-
const ctxType = getType(ctxProp)
|
|
79
|
-
const middlewareType = extractMiddleware(ctxType, checker, node, tsLib)
|
|
104
|
+
const middlewareType = propsType(extractMiddlewareProps(ctxType, ser))
|
|
80
105
|
|
|
81
106
|
/* extract $taps — typed tap payloads declared via .taps<T>() */
|
|
82
107
|
let tapsType: string | null = null
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
if (tapsStr !== "{}" && tapsStr !== "Record<string, unknown>") {
|
|
88
|
-
tapsType = tapsStr
|
|
89
|
-
}
|
|
108
|
+
const tapsT = typeOfProp("$taps")
|
|
109
|
+
if (tapsT) {
|
|
110
|
+
const tapsStr = ser.serialize(tapsT)
|
|
111
|
+
if (tapsStr !== "{}" && tapsStr !== "Record<string, unknown>") tapsType = tapsStr
|
|
90
112
|
}
|
|
91
113
|
|
|
92
114
|
const sanitize = (s: string) => sanitizeImportPaths(s, options.outputDir)
|
|
@@ -116,47 +138,27 @@ const HONEY_CTX_PROPS = new Set([
|
|
|
116
138
|
"tap",
|
|
117
139
|
])
|
|
118
140
|
|
|
119
|
-
|
|
120
|
-
function resolveTypeRefFromDecl(
|
|
121
|
-
decl: ts.Declaration | undefined,
|
|
122
|
-
checker: ts.TypeChecker,
|
|
123
|
-
compiler: typeof ts,
|
|
124
|
-
): string | undefined {
|
|
125
|
-
if (!decl) return undefined
|
|
126
|
-
const typeRef = (decl as { type?: ts.TypeNode }).type
|
|
127
|
-
if (!typeRef || !compiler.isTypeReferenceNode(typeRef)) return undefined
|
|
128
|
-
const typeName = (typeRef as ts.TypeReferenceNode).typeName
|
|
129
|
-
|
|
130
|
-
let refSym = checker.getSymbolAtLocation(typeName)
|
|
131
|
-
while (refSym && refSym.flags & compiler.SymbolFlags.Alias) {
|
|
132
|
-
refSym = checker.getAliasedSymbol(refSym)
|
|
133
|
-
}
|
|
134
|
-
if (!refSym) return undefined
|
|
135
|
-
|
|
136
|
-
const refDecls = refSym.declarations
|
|
137
|
-
if (!refDecls?.length) return undefined
|
|
138
|
-
|
|
139
|
-
const srcFile = refDecls[0].getSourceFile()
|
|
140
|
-
if (!isExportedFromFile(refSym, srcFile, checker)) return undefined
|
|
141
|
+
type MwPropEntry = { name: string; opt: boolean; type: string }
|
|
141
142
|
|
|
142
|
-
|
|
143
|
+
function propsType(props: readonly MwPropEntry[]): string | null {
|
|
144
|
+
if (props.length === 0) return null
|
|
145
|
+
return `{ ${props.map((p) => `${quoteKey(p.name)}${p.opt ? "?" : ""}: ${p.type}`).join("; ")} }`
|
|
143
146
|
}
|
|
144
147
|
|
|
145
|
-
|
|
148
|
+
/** Is a property name something a type literal can spell and a consumer can see? */
|
|
149
|
+
function isPublicName(name: string): boolean {
|
|
150
|
+
/* `#secret` private fields surface as `__#123@#secret` — never part of a type */
|
|
151
|
+
return !name.startsWith("#") && !name.startsWith("__#")
|
|
152
|
+
}
|
|
146
153
|
|
|
147
|
-
function extractMiddlewareProps(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
node: ts.Node,
|
|
151
|
-
compiler: typeof ts,
|
|
152
|
-
): MwPropEntry[] {
|
|
153
|
-
const allProps = ctxType.getProperties()
|
|
154
|
-
const mwProps = allProps.filter((p) => {
|
|
154
|
+
function extractMiddlewareProps(ctxType: ts.Type, ser: Serializer): MwPropEntry[] {
|
|
155
|
+
const { checker, compiler, node } = ser
|
|
156
|
+
const mwProps = ctxType.getProperties().filter((p) => {
|
|
155
157
|
const name = p.getName()
|
|
156
158
|
if (HONEY_CTX_PROPS.has(name)) return false
|
|
157
159
|
/* skip internal/private properties (underscore-prefixed) */
|
|
158
160
|
if (name.startsWith("_")) return false
|
|
159
|
-
return
|
|
161
|
+
return isPublicName(name)
|
|
160
162
|
})
|
|
161
163
|
|
|
162
164
|
return mwProps.map((p) => {
|
|
@@ -166,14 +168,14 @@ function extractMiddlewareProps(
|
|
|
166
168
|
/* 1. try declaration-site TypeReference (explicit annotation on property or variable) */
|
|
167
169
|
let typeStr: string | undefined
|
|
168
170
|
const mwDecl = p.valueDeclaration ?? p.declarations?.[0]
|
|
169
|
-
typeStr =
|
|
171
|
+
typeStr = ser.refFromDeclaration(mwDecl)
|
|
170
172
|
|
|
171
173
|
/* 1b. shorthand property — trace to the underlying variable's type annotation */
|
|
172
174
|
if (!typeStr && mwDecl && compiler.isShorthandPropertyAssignment(mwDecl)) {
|
|
173
175
|
const varSym = checker.getShorthandAssignmentValueSymbol(mwDecl)
|
|
174
176
|
if (varSym) {
|
|
175
177
|
const varDecl = varSym.valueDeclaration ?? varSym.declarations?.[0]
|
|
176
|
-
typeStr =
|
|
178
|
+
typeStr = ser.refFromDeclaration(varDecl)
|
|
177
179
|
|
|
178
180
|
/* 1c. destructured binding (e.g. const { shardDb } = openShardSession(...))
|
|
179
181
|
* — trace through the function return type to find the original type annotation */
|
|
@@ -189,7 +191,7 @@ function extractMiddlewareProps(
|
|
|
189
191
|
const innerSym = checker.getShorthandAssignmentValueSymbol(retProp.valueDeclaration)
|
|
190
192
|
if (innerSym) {
|
|
191
193
|
const innerDecl = innerSym.valueDeclaration ?? innerSym.declarations?.[0]
|
|
192
|
-
typeStr =
|
|
194
|
+
typeStr = ser.refFromDeclaration(innerDecl)
|
|
193
195
|
}
|
|
194
196
|
}
|
|
195
197
|
}
|
|
@@ -198,32 +200,13 @@ function extractMiddlewareProps(
|
|
|
198
200
|
}
|
|
199
201
|
}
|
|
200
202
|
|
|
201
|
-
/* 2.
|
|
202
|
-
|
|
203
|
-
typeStr = serializeAsRef(t, checker, node, compiler) ?? undefined
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/* 3. structural fallback */
|
|
207
|
-
if (!typeStr) {
|
|
208
|
-
typeStr = serialize(t, checker, node, compiler)
|
|
209
|
-
}
|
|
203
|
+
/* 2. a named type as an import() reference; 3. structural */
|
|
204
|
+
typeStr ??= ser.asRef(t) ?? ser.serialize(t)
|
|
210
205
|
|
|
211
206
|
return { name: p.getName(), opt, type: typeStr }
|
|
212
207
|
})
|
|
213
208
|
}
|
|
214
209
|
|
|
215
|
-
function extractMiddleware(
|
|
216
|
-
ctxType: ts.Type,
|
|
217
|
-
checker: ts.TypeChecker,
|
|
218
|
-
node: ts.Node,
|
|
219
|
-
compiler: typeof ts,
|
|
220
|
-
): string | null {
|
|
221
|
-
const props = extractMiddlewareProps(ctxType, checker, node, compiler)
|
|
222
|
-
if (props.length === 0) return null
|
|
223
|
-
const entries = props.map((p) => `${p.name}${p.opt ? "?" : ""}: ${p.type}`)
|
|
224
|
-
return `{ ${entries.join("; ")} }`
|
|
225
|
-
}
|
|
226
|
-
|
|
227
210
|
/** Check if a symbol is exported from its source file */
|
|
228
211
|
function isExportedFromFile(sym: ts.Symbol, sf: ts.SourceFile, checker: ts.TypeChecker): boolean {
|
|
229
212
|
/* .d.ts module types are always accessible */
|
|
@@ -234,216 +217,257 @@ function isExportedFromFile(sym: ts.Symbol, sf: ts.SourceFile, checker: ts.TypeC
|
|
|
234
217
|
return exports.some((e) => e.getName() === sym.getName())
|
|
235
218
|
}
|
|
236
219
|
|
|
220
|
+
/** Lib types every program has in scope; their names are written as is, never imported. */
|
|
221
|
+
function isLibFile(fileName: string): boolean {
|
|
222
|
+
return /(?:^|[\\/])lib\.[\w.-]*\.d\.ts$/.test(fileName) || /[\\/]typescript[\\/]lib[\\/]/.test(fileName)
|
|
223
|
+
}
|
|
224
|
+
|
|
237
225
|
/**
|
|
238
|
-
*
|
|
239
|
-
* Returns null if the type has no usable named symbol.
|
|
226
|
+
* TypeScript types → type source text.
|
|
240
227
|
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
228
|
+
* Named types become `import("…").Name<Args>` references when the declaring module exports
|
|
229
|
+
* them; everything else is written structurally. The structural writer is precise about the
|
|
230
|
+
* things a reader relies on: optional and rest parameters, generic signatures, tuple rest and
|
|
231
|
+
* optional elements, literal types (quoted), boolean literals, quoted keys, and precedence
|
|
232
|
+
* (`(() => void)[]`, `(A | B) & C`). A type it is still expanding when it meets itself again
|
|
233
|
+
* is written as `unknown` there, never expanded without end.
|
|
245
234
|
*/
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
node: ts.Node
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (nonNever.length === 1) {
|
|
259
|
-
return serializeAsRef(nonNever[0], checker, node, compiler, depth)
|
|
260
|
-
}
|
|
235
|
+
class Serializer {
|
|
236
|
+
readonly checker: ts.TypeChecker
|
|
237
|
+
readonly compiler: typeof ts
|
|
238
|
+
readonly node: ts.Node
|
|
239
|
+
readonly #active = new Set<ts.Type>()
|
|
240
|
+
/* type parameters of the signatures being written: inside them a parameter keeps its name */
|
|
241
|
+
readonly #bound = new Set<ts.Symbol>()
|
|
242
|
+
|
|
243
|
+
constructor(checker: ts.TypeChecker, node: ts.Node, compiler: typeof ts) {
|
|
244
|
+
this.checker = checker
|
|
245
|
+
this.node = node
|
|
246
|
+
this.compiler = compiler
|
|
261
247
|
}
|
|
262
248
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
249
|
+
/** An `import()` reference with type arguments for an annotation like `x: Foo<Bar>`. */
|
|
250
|
+
refFromDeclaration(decl: ts.Declaration | undefined): string | undefined {
|
|
251
|
+
const { checker, compiler } = this
|
|
252
|
+
if (!decl) return undefined
|
|
253
|
+
const typeRef = (decl as { type?: ts.TypeNode }).type
|
|
254
|
+
if (!typeRef || !compiler.isTypeReferenceNode(typeRef)) return undefined
|
|
255
|
+
let refSym = checker.getSymbolAtLocation(typeRef.typeName)
|
|
256
|
+
while (refSym && refSym.flags & compiler.SymbolFlags.Alias) refSym = checker.getAliasedSymbol(refSym)
|
|
257
|
+
if (!refSym?.declarations?.length) return undefined
|
|
258
|
+
const srcFile = refSym.declarations[0].getSourceFile()
|
|
259
|
+
if (isLibFile(srcFile.fileName)) return undefined
|
|
260
|
+
if (!isExportedFromFile(refSym, srcFile, checker)) return undefined
|
|
261
|
+
const args = typeRef.typeArguments?.map((a) => {
|
|
262
|
+
const t = checker.getTypeFromTypeNode(a)
|
|
263
|
+
return this.asRef(t) ?? this.serialize(t)
|
|
264
|
+
})
|
|
265
|
+
const argText = args && args.length > 0 ? `<${args.join(", ")}>` : ""
|
|
266
|
+
return `import("${srcFile.fileName}").${refSym.getName()}${argText}`
|
|
267
|
+
}
|
|
277
268
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
269
|
+
/**
|
|
270
|
+
* `import("path").TypeName<Args>` for a named type whose module exports it, else null.
|
|
271
|
+
* Globals (Promise, Map, …) are left to `serialize`.
|
|
272
|
+
*/
|
|
273
|
+
asRef(type: ts.Type, depth = 0): string | null {
|
|
274
|
+
const { checker, compiler } = this
|
|
275
|
+
if (depth > 6) return null
|
|
276
|
+
|
|
277
|
+
/* union with never members (from intersection narrowing) — simplify first */
|
|
278
|
+
if (type.isUnion()) {
|
|
279
|
+
const nonNever = type.types.filter((t) => !(t.flags & compiler.TypeFlags.Never))
|
|
280
|
+
if (nonNever.length === 1) return this.asRef(nonNever[0], depth)
|
|
281
|
+
}
|
|
281
282
|
|
|
282
|
-
|
|
283
|
-
|
|
283
|
+
const sym = type.aliasSymbol ?? type.getSymbol()
|
|
284
|
+
if (!sym) return null
|
|
285
|
+
const name = sym.getName()
|
|
286
|
+
if (!name || name.startsWith("__") || name === "Object") return null
|
|
287
|
+
const decls = sym.declarations
|
|
288
|
+
if (!decls?.length) return null
|
|
289
|
+
const sf = decls[0].getSourceFile()
|
|
290
|
+
if (isLibFile(sf.fileName)) return null
|
|
291
|
+
|
|
292
|
+
const named =
|
|
293
|
+
(sym.flags &
|
|
294
|
+
(compiler.SymbolFlags.TypeAlias |
|
|
295
|
+
compiler.SymbolFlags.Interface |
|
|
296
|
+
compiler.SymbolFlags.Class |
|
|
297
|
+
compiler.SymbolFlags.Enum)) !==
|
|
298
|
+
0
|
|
299
|
+
/* only reference .d.ts symbols or explicitly declared named types from .ts files */
|
|
300
|
+
if (!sf.isDeclarationFile && !named) return null
|
|
301
|
+
|
|
302
|
+
const typeArgs = type.aliasSymbol ? type.aliasTypeArguments : (type as ts.TypeReference).typeArguments
|
|
303
|
+
/* a class or interface reference carries its `this` type as a trailing argument */
|
|
304
|
+
const declared = sym.declarations?.[0]
|
|
305
|
+
const paramCount =
|
|
306
|
+
declared && "typeParameters" in declared
|
|
307
|
+
? ((declared as { typeParameters?: unknown[] }).typeParameters?.length ?? 0)
|
|
308
|
+
: (typeArgs?.length ?? 0)
|
|
309
|
+
const args = (typeArgs ?? []).slice(0, paramCount)
|
|
310
|
+
const withArgs = (ref: string): string =>
|
|
311
|
+
args.length > 0
|
|
312
|
+
? `${ref}<${args.map((arg) => this.asRef(arg, depth + 1) ?? this.serialize(arg)).join(", ")}>`
|
|
313
|
+
: ref
|
|
314
|
+
|
|
315
|
+
/* a global type (an ambient script such as wrangler's worker-configuration.d.ts, or
|
|
316
|
+
* `declare global`) is in scope wherever the app compiles: write its name. Expanding it
|
|
317
|
+
* loses overloads and generics, and the copy is not assignable back to the original. */
|
|
318
|
+
if (named) {
|
|
319
|
+
const globalName = this.#globalName(sym, decls[0])
|
|
320
|
+
if (globalName !== null) return withArgs(globalName)
|
|
321
|
+
}
|
|
322
|
+
/* any other ambient script declaration has no import() path */
|
|
323
|
+
if (sf.isDeclarationFile && !(sf as unknown as { externalModuleIndicator?: unknown }).externalModuleIndicator) {
|
|
324
|
+
return null
|
|
325
|
+
}
|
|
326
|
+
/* skip non-exported symbols — import() on them resolves to `any` */
|
|
327
|
+
if (!isExportedFromFile(sym, sf, checker)) return null
|
|
284
328
|
|
|
285
|
-
|
|
286
|
-
if (isDts) {
|
|
287
|
-
const tsSf = sf as unknown as { externalModuleIndicator?: unknown }
|
|
288
|
-
if (!tsSf.externalModuleIndicator) return null
|
|
329
|
+
return withArgs(`import("${sf.fileName}").${name}`)
|
|
289
330
|
}
|
|
290
331
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
332
|
+
/** The name a global declaration is reachable by (`Cloudflare.Env`), or null if it is module-scoped. */
|
|
333
|
+
#globalName(sym: ts.Symbol, decl: ts.Declaration): string | null {
|
|
334
|
+
const { compiler } = this
|
|
335
|
+
let global = false
|
|
336
|
+
for (let n: ts.Node | undefined = decl.parent; n; n = n.parent) {
|
|
337
|
+
if (compiler.isModuleDeclaration(n) && n.flags & compiler.NodeFlags.GlobalAugmentation) {
|
|
338
|
+
global = true
|
|
339
|
+
break
|
|
340
|
+
}
|
|
341
|
+
if (compiler.isSourceFile(n)) {
|
|
342
|
+
global = !(n as unknown as { externalModuleIndicator?: unknown }).externalModuleIndicator
|
|
343
|
+
break
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (!global) return null
|
|
347
|
+
/* a `declare global` member is qualified as `global.Name`; in scope it is `Name` */
|
|
348
|
+
const name = this.checker.getFullyQualifiedName(sym).replace(/^global\./, "")
|
|
349
|
+
return /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(name) ? name : null
|
|
303
350
|
}
|
|
304
351
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
/* primitives */
|
|
314
|
-
if (type.flags & compiler.TypeFlags.String) return "string"
|
|
315
|
-
if (type.flags & compiler.TypeFlags.Number) return "number"
|
|
316
|
-
if (type.flags & compiler.TypeFlags.Boolean) return "boolean"
|
|
317
|
-
if (type.flags & compiler.TypeFlags.BigInt) return "bigint"
|
|
318
|
-
if (type.flags & compiler.TypeFlags.ESSymbol) return "symbol"
|
|
319
|
-
if (type.flags & compiler.TypeFlags.Void) return "void"
|
|
320
|
-
if (type.flags & compiler.TypeFlags.Undefined) return "undefined"
|
|
321
|
-
if (type.flags & compiler.TypeFlags.Null) return "null"
|
|
322
|
-
if (type.flags & compiler.TypeFlags.Never) return "never"
|
|
323
|
-
if (type.flags & compiler.TypeFlags.Unknown) return "unknown"
|
|
324
|
-
if (type.flags & compiler.TypeFlags.Any) return "unknown"
|
|
325
|
-
|
|
326
|
-
/* literals */
|
|
327
|
-
if (type.isStringLiteral()) return `"${(type as ts.StringLiteralType).value}"`
|
|
328
|
-
if (type.isNumberLiteral()) return `${(type as ts.NumberLiteralType).value}`
|
|
329
|
-
|
|
330
|
-
/* union */
|
|
331
|
-
if (type.isUnion()) {
|
|
332
|
-
return type.types
|
|
333
|
-
.map((t) => {
|
|
334
|
-
const s = next(t)
|
|
335
|
-
/* parenthesize function types inside unions — TS syntax requires it */
|
|
336
|
-
return s.includes("=>") ? `(${s})` : s
|
|
337
|
-
})
|
|
338
|
-
.join(" | ")
|
|
352
|
+
serialize(type: ts.Type): string {
|
|
353
|
+
if (this.#active.has(type)) return "unknown"
|
|
354
|
+
this.#active.add(type)
|
|
355
|
+
try {
|
|
356
|
+
return this.#serialize(type)
|
|
357
|
+
} finally {
|
|
358
|
+
this.#active.delete(type)
|
|
359
|
+
}
|
|
339
360
|
}
|
|
340
361
|
|
|
341
|
-
|
|
342
|
-
|
|
362
|
+
#serialize(type: ts.Type): string {
|
|
363
|
+
const { checker, compiler, node } = this
|
|
364
|
+
const F = compiler.TypeFlags
|
|
365
|
+
const next = (t: ts.Type) => this.serialize(t)
|
|
343
366
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
return el.includes("|") || el.includes("&") ? `(${el})[]` : `${el}[]`
|
|
367
|
+
/* an enum is nominal: keep its name when it can be referenced */
|
|
368
|
+
const enumSym = type.getSymbol()
|
|
369
|
+
if (enumSym && enumSym.flags & compiler.SymbolFlags.Enum) {
|
|
370
|
+
const ref = this.asRef(type)
|
|
371
|
+
if (ref) return ref
|
|
350
372
|
}
|
|
351
|
-
return "unknown[]"
|
|
352
|
-
}
|
|
353
373
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
374
|
+
/* primitives */
|
|
375
|
+
if (type.flags & F.String) return "string"
|
|
376
|
+
if (type.flags & F.Number) return "number"
|
|
377
|
+
if (type.flags & F.Boolean) return "boolean"
|
|
378
|
+
if (type.flags & F.BigInt) return "bigint"
|
|
379
|
+
if (type.flags & F.ESSymbol) return "symbol"
|
|
380
|
+
if (type.flags & F.UniqueESSymbol) return "symbol"
|
|
381
|
+
if (type.flags & F.Void) return "void"
|
|
382
|
+
if (type.flags & F.Undefined) return "undefined"
|
|
383
|
+
if (type.flags & F.Null) return "null"
|
|
384
|
+
if (type.flags & F.Never) return "never"
|
|
385
|
+
if (type.flags & F.Unknown) return "unknown"
|
|
386
|
+
if (type.flags & F.Any) return "unknown"
|
|
387
|
+
if (type.flags & F.NonPrimitive) return "object"
|
|
388
|
+
|
|
389
|
+
/* literals */
|
|
390
|
+
if (type.isStringLiteral()) return JSON.stringify(type.value)
|
|
391
|
+
if (type.isNumberLiteral()) return `${type.value}`
|
|
392
|
+
if (type.flags & F.BigIntLiteral) {
|
|
393
|
+
const v = (type as ts.BigIntLiteralType).value
|
|
394
|
+
return `${v.negative ? "-" : ""}${v.base10Value}n`
|
|
395
|
+
}
|
|
396
|
+
if (type.flags & F.BooleanLiteral) return checker.typeToString(type)
|
|
397
|
+
if (type.flags & F.TemplateLiteral) return "string"
|
|
398
|
+
|
|
399
|
+
/* a type parameter: by name inside the signature that declares it, else its constraint or unknown */
|
|
400
|
+
if (type.flags & F.TypeParameter) {
|
|
401
|
+
if (type.symbol && this.#bound.has(type.symbol)) return type.symbol.getName()
|
|
402
|
+
const constraint = checker.getBaseConstraintOfType(type)
|
|
403
|
+
return constraint && constraint !== type ? next(constraint) : "unknown"
|
|
404
|
+
}
|
|
359
405
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (sigs.length > 0 && type.getProperties().length === 0) {
|
|
363
|
-
const sig = sigs[0]
|
|
364
|
-
const params = sig.getParameters().map((p) => {
|
|
365
|
-
const pt = checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration ?? node)
|
|
366
|
-
const decl = p.valueDeclaration
|
|
367
|
-
const isRest =
|
|
368
|
-
decl !== undefined &&
|
|
369
|
-
compiler.isParameter(decl) &&
|
|
370
|
-
(decl as ts.ParameterDeclaration).dotDotDotToken !== undefined
|
|
371
|
-
return `${isRest ? "..." : ""}${p.getName()}: ${next(pt)}`
|
|
372
|
-
})
|
|
373
|
-
const ret = checker.getReturnTypeOfSignature(sig)
|
|
374
|
-
return `(${params.join(", ")}) => ${next(ret)}`
|
|
375
|
-
}
|
|
406
|
+
/* union */
|
|
407
|
+
if (type.isUnion()) return unionText(type.types.map(next))
|
|
376
408
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
"
|
|
384
|
-
"
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
"FormData",
|
|
391
|
-
"Headers",
|
|
392
|
-
"Map",
|
|
393
|
-
"Promise",
|
|
394
|
-
"ReadableStream",
|
|
395
|
-
"RegExp",
|
|
396
|
-
"Request",
|
|
397
|
-
"Response",
|
|
398
|
-
"Set",
|
|
399
|
-
"SharedArrayBuffer",
|
|
400
|
-
"TransformStream",
|
|
401
|
-
"URL",
|
|
402
|
-
"URLSearchParams",
|
|
403
|
-
"WeakMap",
|
|
404
|
-
"WeakSet",
|
|
405
|
-
"WritableStream",
|
|
406
|
-
])
|
|
407
|
-
if (globals.has(name)) {
|
|
409
|
+
/* intersection */
|
|
410
|
+
if (type.isIntersection()) return intersectionText(type.types.map(next))
|
|
411
|
+
|
|
412
|
+
/* array */
|
|
413
|
+
if (checker.isArrayType(type)) {
|
|
414
|
+
const args = checker.getTypeArguments(type as ts.TypeReference)
|
|
415
|
+
const readonlyArray = type.getSymbol()?.getName() === "ReadonlyArray"
|
|
416
|
+
const el = args.length > 0 ? arrayText(next(args[0])) : "unknown[]"
|
|
417
|
+
return readonlyArray ? `readonly ${el}` : el
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/* tuple — rest and optional elements kept */
|
|
421
|
+
if (checker.isTupleType(type)) {
|
|
408
422
|
const ref = type as ts.TypeReference
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
423
|
+
const args = checker.getTypeArguments(ref)
|
|
424
|
+
const target = ref.target as ts.TupleType
|
|
425
|
+
const parts = args.map((arg, i) => {
|
|
426
|
+
const flags = target.elementFlags?.[i] ?? compiler.ElementFlags.Required
|
|
427
|
+
if (flags & compiler.ElementFlags.Rest) return `...${arrayText(next(arg))}`
|
|
428
|
+
if (flags & compiler.ElementFlags.Variadic) return `...${next(arg)}`
|
|
429
|
+
if (flags & compiler.ElementFlags.Optional) return `${next(arg)}?`
|
|
430
|
+
return next(arg)
|
|
431
|
+
})
|
|
432
|
+
return `${target.readonly ? "readonly " : ""}[${parts.join(", ")}]`
|
|
413
433
|
}
|
|
414
434
|
|
|
415
|
-
/*
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
435
|
+
/* function (pure callable, no data properties) */
|
|
436
|
+
const sigs = type.getCallSignatures()
|
|
437
|
+
if (sigs.length > 0 && type.getProperties().length === 0) {
|
|
438
|
+
const parts = sigs.map((sig) => this.#signature(sig))
|
|
439
|
+
return parts.length === 1 ? parts[0] : parts.map((p) => `(${p})`).join(" & ")
|
|
440
|
+
}
|
|
419
441
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
442
|
+
/* well-known globals — keep name, serialize type args */
|
|
443
|
+
const sym = type.getSymbol()
|
|
444
|
+
if (sym) {
|
|
445
|
+
const name = sym.getName()
|
|
446
|
+
const decl = sym.declarations?.[0]
|
|
447
|
+
if (decl && isLibFile(decl.getSourceFile().fileName) && name !== "Object" && !name.startsWith("__")) {
|
|
448
|
+
const args = (type as ts.TypeReference).typeArguments ?? []
|
|
449
|
+
const declaredParams = (decl as { typeParameters?: unknown[] }).typeParameters?.length ?? args.length
|
|
450
|
+
const kept = args.slice(0, declaredParams)
|
|
451
|
+
return kept.length > 0 ? `${name}<${kept.map(next).join(", ")}>` : name
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/* named types — prefer import() reference over structural expansion */
|
|
455
|
+
if (name && !name.startsWith("__") && name !== "Object") {
|
|
456
|
+
const ref = this.asRef(type)
|
|
457
|
+
if (ref) return ref
|
|
431
458
|
}
|
|
432
459
|
}
|
|
433
|
-
|
|
460
|
+
const aliasRef = type.aliasSymbol ? this.asRef(type) : null
|
|
461
|
+
if (aliasRef) return aliasRef
|
|
434
462
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
463
|
+
/* Record<string, V> — index signature only */
|
|
464
|
+
const stringIdx = type.getStringIndexType()
|
|
465
|
+
if (stringIdx && type.getProperties().length === 0) {
|
|
466
|
+
return `Record<string, ${next(stringIdx)}>`
|
|
467
|
+
}
|
|
440
468
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (props.length > 50) {
|
|
444
|
-
return checker.typeToString(type, node, compiler.TypeFormatFlags.NoTruncation)
|
|
445
|
-
}
|
|
446
|
-
if (props.length > 0) {
|
|
469
|
+
/* object — expand to structural form */
|
|
470
|
+
const props = type.getProperties().filter((p) => isPublicName(p.getName()))
|
|
447
471
|
const entries: string[] = []
|
|
448
472
|
for (const p of props) {
|
|
449
473
|
const decls = p.getDeclarations()
|
|
@@ -455,37 +479,220 @@ function serialize(type: ts.Type, checker: ts.TypeChecker, node: ts.Node, compil
|
|
|
455
479
|
const opt = (p.flags & compiler.SymbolFlags.Optional) !== 0
|
|
456
480
|
const ro =
|
|
457
481
|
decls?.some((d) => (compiler.getCombinedModifierFlags(d) & compiler.ModifierFlags.Readonly) !== 0) ?? false
|
|
458
|
-
entries.push(`${ro ? "readonly " : ""}${p.getName()}${opt ? "?" : ""}: ${next(pt)}`)
|
|
482
|
+
entries.push(`${ro ? "readonly " : ""}${quoteKey(p.getName())}${opt ? "?" : ""}: ${next(pt)}`)
|
|
459
483
|
}
|
|
484
|
+
if (stringIdx) entries.push(`[key: string]: ${next(stringIdx)}`)
|
|
485
|
+
for (const sig of sigs) entries.push(this.#signature(sig, true))
|
|
460
486
|
if (entries.length > 0) return `{ ${entries.join("; ")} }`
|
|
487
|
+
if (props.length === 0) return "{}"
|
|
488
|
+
return "unknown"
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** `<T extends X>(a: A, b?: B, ...rest: C[]) => R`, or the call-signature member form */
|
|
492
|
+
#signature(sig: ts.Signature, member = false): string {
|
|
493
|
+
const typeParams = sig.getTypeParameters() ?? []
|
|
494
|
+
const added = typeParams.map((t) => t.symbol).filter((s) => s !== undefined && !this.#bound.has(s))
|
|
495
|
+
for (const s of added) this.#bound.add(s)
|
|
496
|
+
try {
|
|
497
|
+
return this.#signatureText(sig, typeParams, member)
|
|
498
|
+
} finally {
|
|
499
|
+
for (const s of added) this.#bound.delete(s)
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
#signatureText(sig: ts.Signature, typeParams: readonly ts.TypeParameter[], member: boolean): string {
|
|
504
|
+
const { checker, compiler, node } = this
|
|
505
|
+
const tp =
|
|
506
|
+
typeParams.length > 0
|
|
507
|
+
? `<${typeParams
|
|
508
|
+
.map((t) => {
|
|
509
|
+
const c = t.getConstraint()
|
|
510
|
+
return `${t.symbol.getName()}${c ? ` extends ${this.#typeParamText(c)}` : ""}`
|
|
511
|
+
})
|
|
512
|
+
.join(", ")}>`
|
|
513
|
+
: ""
|
|
514
|
+
const params = sig.getParameters().map((p) => {
|
|
515
|
+
const pt = checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration ?? node)
|
|
516
|
+
const decl = p.valueDeclaration
|
|
517
|
+
const param = decl && compiler.isParameter(decl) ? (decl as ts.ParameterDeclaration) : undefined
|
|
518
|
+
const isRest = param?.dotDotDotToken !== undefined
|
|
519
|
+
const optional = !isRest && param !== undefined && checker.isOptionalParameter(param)
|
|
520
|
+
/* an optional parameter's type carries `| undefined`; `?` already says so */
|
|
521
|
+
const text = optional ? this.#typeParamText(checker.getNonNullableType(pt)) : this.#typeParamText(pt)
|
|
522
|
+
return `${isRest ? "..." : ""}${p.getName()}${optional ? "?" : ""}: ${text}`
|
|
523
|
+
})
|
|
524
|
+
const ret = this.#typeParamText(checker.getReturnTypeOfSignature(sig))
|
|
525
|
+
return member ? `${tp}(${params.join(", ")}): ${ret}` : `${tp}(${params.join(", ")}) => ${ret}`
|
|
461
526
|
}
|
|
462
527
|
|
|
463
|
-
|
|
464
|
-
|
|
528
|
+
/** A type inside a signature: a bare type parameter stays by name, bound by the signature. */
|
|
529
|
+
#typeParamText(t: ts.Type): string {
|
|
530
|
+
if (t.flags & this.compiler.TypeFlags.TypeParameter) return t.symbol.getName()
|
|
531
|
+
return this.asRef(t) ?? this.serialize(t)
|
|
532
|
+
}
|
|
465
533
|
}
|
|
466
534
|
|
|
467
535
|
/**
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
* - node_modules paths → bare package specifiers
|
|
471
|
-
* - project-internal paths → relative from outputDir
|
|
536
|
+
* Does `type` contain `token` outside every bracket and string? Used to parenthesize only
|
|
537
|
+
* where precedence needs it.
|
|
472
538
|
*/
|
|
473
|
-
function
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
539
|
+
function hasTopLevel(type: string, token: "|" | "&" | "=>"): boolean {
|
|
540
|
+
let depth = 0
|
|
541
|
+
let quote: string | null = null
|
|
542
|
+
for (let i = 0; i < type.length; i++) {
|
|
543
|
+
const c = type[i]
|
|
544
|
+
if (quote !== null) {
|
|
545
|
+
if (c === "\\") i++
|
|
546
|
+
else if (c === quote) quote = null
|
|
547
|
+
continue
|
|
548
|
+
}
|
|
549
|
+
if (c === '"' || c === "'" || c === "`") quote = c
|
|
550
|
+
else if (c === "(" || c === "{" || c === "[" || c === "<") depth++
|
|
551
|
+
else if (c === ")" || c === "}" || c === "]" || (c === ">" && type[i - 1] !== "=")) depth--
|
|
552
|
+
else if (depth === 0 && type.startsWith(token, i)) return true
|
|
553
|
+
}
|
|
554
|
+
return false
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function arrayText(el: string): string {
|
|
558
|
+
return hasTopLevel(el, "|") || hasTopLevel(el, "&") || hasTopLevel(el, "=>") || el.startsWith("readonly ")
|
|
559
|
+
? `(${el})[]`
|
|
560
|
+
: `${el}[]`
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function unionText(parts: string[]): string {
|
|
564
|
+
return [...new Set(parts)].map((p) => (hasTopLevel(p, "=>") ? `(${p})` : p)).join(" | ")
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function intersectionText(parts: string[]): string {
|
|
568
|
+
return parts.map((p) => (hasTopLevel(p, "|") || hasTopLevel(p, "=>") ? `(${p})` : p)).join(" & ")
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/* ---- module specifiers ---- */
|
|
572
|
+
|
|
573
|
+
type PackageInfo = { dir: string; json: Record<string, unknown> }
|
|
574
|
+
|
|
575
|
+
const packageCache = new Map<string, PackageInfo | null>()
|
|
576
|
+
|
|
577
|
+
function nearestPackage(fromFile: string): PackageInfo | null {
|
|
578
|
+
let dir = dirname(fromFile)
|
|
579
|
+
const visited: string[] = []
|
|
580
|
+
let found: PackageInfo | null = null
|
|
581
|
+
let prev = ""
|
|
582
|
+
while (dir !== prev) {
|
|
583
|
+
const cached = packageCache.get(dir)
|
|
584
|
+
if (cached !== undefined) {
|
|
585
|
+
found = cached
|
|
586
|
+
break
|
|
587
|
+
}
|
|
588
|
+
visited.push(dir)
|
|
589
|
+
const file = join(dir, "package.json")
|
|
590
|
+
if (existsSync(file)) {
|
|
591
|
+
try {
|
|
592
|
+
const json = JSON.parse(readFileSync(file, "utf8")) as Record<string, unknown>
|
|
593
|
+
/* a package.json without a name (a "type": "module" marker) is not a package boundary */
|
|
594
|
+
if (typeof json.name === "string") {
|
|
595
|
+
found = { dir, json }
|
|
596
|
+
break
|
|
597
|
+
}
|
|
598
|
+
} catch {
|
|
599
|
+
/* unreadable — keep walking */
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
prev = dir
|
|
603
|
+
dir = dirname(dir)
|
|
604
|
+
}
|
|
605
|
+
for (const d of visited) packageCache.set(d, found)
|
|
606
|
+
return found
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/** Every file an `exports` value can point at, with the subpath that names it. */
|
|
610
|
+
function exportTargets(exports: unknown): Array<{ subpath: string; target: string }> {
|
|
611
|
+
const out: Array<{ subpath: string; target: string }> = []
|
|
612
|
+
const collect = (subpath: string, value: unknown): void => {
|
|
613
|
+
if (typeof value === "string") out.push({ subpath, target: value })
|
|
614
|
+
else if (Array.isArray(value)) for (const v of value) collect(subpath, v)
|
|
615
|
+
else if (value !== null && typeof value === "object") {
|
|
616
|
+
for (const v of Object.values(value as Record<string, unknown>)) collect(subpath, v)
|
|
478
617
|
}
|
|
479
|
-
|
|
480
|
-
|
|
618
|
+
}
|
|
619
|
+
if (typeof exports === "string" || Array.isArray(exports)) collect(".", exports)
|
|
620
|
+
else if (exports !== null && typeof exports === "object") {
|
|
621
|
+
const entries = Object.entries(exports as Record<string, unknown>)
|
|
622
|
+
if (entries.length > 0 && entries.every(([k]) => !k.startsWith("."))) collect(".", exports)
|
|
623
|
+
else for (const [k, v] of entries) collect(k, v)
|
|
624
|
+
}
|
|
625
|
+
return out
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function stripTypeExtension(path: string): string {
|
|
629
|
+
return path
|
|
630
|
+
.replace(/\.d\.[mc]?ts$/, "")
|
|
631
|
+
.replace(/\.[mc]?tsx?$/, "")
|
|
632
|
+
.replace(/\.[mc]?js$/, "")
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** `@types/foo` types the `foo` package; `@types/scope__name` types `@scope/name`. */
|
|
636
|
+
function typedPackageName(name: string): string {
|
|
637
|
+
if (!name.startsWith("@types/")) return name
|
|
638
|
+
const bare = name.slice("@types/".length)
|
|
639
|
+
return bare.includes("__") ? `@${bare.replace("__", "/")}` : bare
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* The specifier a generated file should use to import `filePath`.
|
|
644
|
+
*
|
|
645
|
+
* A file of another package is named through that package — its `exports` subpath when one
|
|
646
|
+
* points at the file (any condition: `types`, `default`, or a source condition), its `types`
|
|
647
|
+
* entry, or (no `exports` map) its path without extension; an `@types` package by the package it
|
|
648
|
+
* types. A file of the output's own package is a relative path. Lib files never get here.
|
|
649
|
+
*/
|
|
650
|
+
function moduleSpecifier(filePath: string, outputDir: string | undefined): string {
|
|
651
|
+
const pkg = nearestPackage(filePath)
|
|
652
|
+
const ownPkg = outputDir ? nearestPackage(join(outputDir, "_")) : null
|
|
653
|
+
const relativeSpecifier = (): string => {
|
|
654
|
+
if (!outputDir) return filePath
|
|
655
|
+
let rel = relative(outputDir, filePath).split(sep).join("/")
|
|
481
656
|
if (!rel.startsWith(".")) rel = `./${rel}`
|
|
482
|
-
return
|
|
657
|
+
return rel
|
|
658
|
+
}
|
|
659
|
+
if (!pkg || (ownPkg && pkg.dir === ownPkg.dir)) return relativeSpecifier()
|
|
660
|
+
const name = typedPackageName(String(pkg.json.name))
|
|
661
|
+
const rel = `./${relative(pkg.dir, filePath).split(sep).join("/")}`
|
|
662
|
+
|
|
663
|
+
if (pkg.json.exports !== undefined) {
|
|
664
|
+
const match = exportTargets(pkg.json.exports).find(
|
|
665
|
+
(e) => e.target === rel || stripTypeExtension(e.target) === stripTypeExtension(rel),
|
|
666
|
+
)
|
|
667
|
+
if (match) return match.subpath === "." ? name : `${name}/${match.subpath.replace(/^\.\//, "")}`
|
|
668
|
+
/* the package does not export this file — a path into it would not resolve */
|
|
669
|
+
return relativeSpecifier()
|
|
670
|
+
}
|
|
671
|
+
const typesEntry = pkg.json.types ?? pkg.json.typings
|
|
672
|
+
if (
|
|
673
|
+
typeof typesEntry === "string" &&
|
|
674
|
+
stripTypeExtension(`./${typesEntry.replace(/^\.\//, "")}`) === stripTypeExtension(rel)
|
|
675
|
+
) {
|
|
676
|
+
return name
|
|
677
|
+
}
|
|
678
|
+
const sub = stripTypeExtension(rel.slice(2)).replace(/\/index$/, "")
|
|
679
|
+
return sub === "index" || sub === "" ? name : `${name}/${sub}`
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Rewrite absolute `import("…")` paths into resolvable module specifiers, and drop the
|
|
684
|
+
* `import()` around lib types (`Date`, `Promise`), which are global.
|
|
685
|
+
*/
|
|
686
|
+
function sanitizeImportPaths(str: string, outputDir?: string): string {
|
|
687
|
+
return str.replace(/import\("([^"]+)"\)\./g, (_match, filePath: string) => {
|
|
688
|
+
if (isLibFile(filePath)) return ""
|
|
689
|
+
return `import("${moduleSpecifier(filePath, outputDir)}").`
|
|
483
690
|
})
|
|
484
691
|
}
|
|
485
692
|
|
|
486
693
|
export type ExtractedChainTypes = {
|
|
487
694
|
base: ExtractedBaseCtx
|
|
488
|
-
/** per-route middleware additions keyed by "method /path" */
|
|
695
|
+
/** per-route middleware additions keyed by "method /full/path" (basePath included) */
|
|
489
696
|
routeMiddleware: Record<string, string>
|
|
490
697
|
/** structured per-property middleware data for sub-type dedup */
|
|
491
698
|
routeMiddlewareProps: Record<string, Array<{ name: string; opt: boolean; type: string }>>
|
|
@@ -495,9 +702,13 @@ const HTTP_METHODS = new Set(["all", "delete", "get", "head", "options", "patch"
|
|
|
495
702
|
|
|
496
703
|
/**
|
|
497
704
|
* Extracts base ctx and per-route middleware additions from sub-chains.
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
*
|
|
705
|
+
*
|
|
706
|
+
* Every source file of the program is scanned for HTTP method calls (`.get()`, `.post()`, …)
|
|
707
|
+
* on a Honey instance. A route is keyed by its full path — the receiver's `$basePath` type
|
|
708
|
+
* joined with the literal path, exactly as the router registers it — so `v1.get("/items")`
|
|
709
|
+
* and `pub.get("/items")` never share an entry. A receiver whose base path is not a literal
|
|
710
|
+
* type cannot be keyed and is skipped: a route without an entry gets the base ctx, never
|
|
711
|
+
* another route's.
|
|
501
712
|
*/
|
|
502
713
|
export async function extractChainTypes(options: {
|
|
503
714
|
entryPath: string
|
|
@@ -505,30 +716,8 @@ export async function extractChainTypes(options: {
|
|
|
505
716
|
outputDir?: string
|
|
506
717
|
tsconfigPath?: string
|
|
507
718
|
}): Promise<ExtractedChainTypes> {
|
|
508
|
-
const {
|
|
509
|
-
|
|
510
|
-
const tsConfigFilePath = options.tsconfigPath ?? findTsConfig(options.entryPath)
|
|
511
|
-
|
|
512
|
-
const project = new Project({
|
|
513
|
-
skipAddingFilesFromTsConfig: true,
|
|
514
|
-
tsConfigFilePath,
|
|
515
|
-
})
|
|
516
|
-
|
|
517
|
-
project.addSourceFilesAtPaths(options.entryPath)
|
|
518
|
-
project.resolveSourceFileDependencies()
|
|
519
|
-
|
|
520
|
-
const sourceFile = project.getSourceFileOrThrow(options.entryPath)
|
|
521
|
-
const baseVarDecl = sourceFile.getVariableDeclaration(options.exportName)
|
|
522
|
-
if (!baseVarDecl) {
|
|
523
|
-
throw new Error(`Export "${options.exportName}" not found in ${options.entryPath}`)
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
function bridge<T>(value: unknown): T {
|
|
527
|
-
return value as T
|
|
528
|
-
}
|
|
529
|
-
const checker: ts.TypeChecker = bridge(project.getTypeChecker().compilerObject)
|
|
530
|
-
const baseNode: ts.Node = bridge(baseVarDecl.compilerNode)
|
|
531
|
-
const tsLib: typeof ts = bridge(ts)
|
|
719
|
+
const { appType, checker, compiler, node: baseNode, project } = await loadEntry(options)
|
|
720
|
+
const ser = new Serializer(checker, baseNode, compiler)
|
|
532
721
|
|
|
533
722
|
function getCtxType(tsType: ts.Type, locationNode?: ts.Node): ts.Type | null {
|
|
534
723
|
const sym = tsType.getProperty("$ctx")
|
|
@@ -536,131 +725,122 @@ export async function extractChainTypes(options: {
|
|
|
536
725
|
return checker.getTypeOfSymbolAtLocation(sym, locationNode ?? sym.valueDeclaration ?? baseNode)
|
|
537
726
|
}
|
|
538
727
|
|
|
728
|
+
function basePathOf(tsType: ts.Type, locationNode: ts.Node): string | null {
|
|
729
|
+
const sym = tsType.getProperty("$basePath")
|
|
730
|
+
if (!sym) return null
|
|
731
|
+
const t = checker.getTypeOfSymbolAtLocation(sym, locationNode)
|
|
732
|
+
return t.isStringLiteral() ? t.value : null
|
|
733
|
+
}
|
|
734
|
+
|
|
539
735
|
/* extract base ctx */
|
|
540
|
-
const baseType = bridge<ts.Type>(baseVarDecl.getType().compilerType)
|
|
541
736
|
let envType = "Record<string, unknown>"
|
|
542
|
-
const envSym =
|
|
543
|
-
if (envSym)
|
|
544
|
-
envType = serialize(checker.getTypeOfSymbolAtLocation(envSym, baseNode), checker, baseNode, tsLib)
|
|
545
|
-
}
|
|
737
|
+
const envSym = appType.getProperty("$env")
|
|
738
|
+
if (envSym) envType = ser.serialize(checker.getTypeOfSymbolAtLocation(envSym, baseNode))
|
|
546
739
|
|
|
547
|
-
const baseCtxType = getCtxType(
|
|
740
|
+
const baseCtxType = getCtxType(appType, baseNode)
|
|
548
741
|
if (!baseCtxType) {
|
|
549
742
|
throw new Error(`Export "${options.exportName}" has no $ctx — not a Honey instance`)
|
|
550
743
|
}
|
|
551
|
-
const baseMwProps = extractMiddlewareProps(baseCtxType,
|
|
552
|
-
const baseMiddlewareType =
|
|
553
|
-
baseMwProps.length === 0
|
|
554
|
-
? null
|
|
555
|
-
: `{ ${baseMwProps.map((p) => `${p.name}${p.opt ? "?" : ""}: ${p.type}`).join("; ")} }`
|
|
744
|
+
const baseMwProps = extractMiddlewareProps(baseCtxType, ser)
|
|
745
|
+
const baseMiddlewareType = propsType(baseMwProps)
|
|
556
746
|
|
|
557
747
|
/* build name→type map for reusing base type references in route middleware */
|
|
558
748
|
const basePropTypeByName = new Map<string, string>()
|
|
559
|
-
for (const p of baseMwProps)
|
|
560
|
-
basePropTypeByName.set(p.name, p.type)
|
|
561
|
-
}
|
|
749
|
+
for (const p of baseMwProps) basePropTypeByName.set(p.name, p.type)
|
|
562
750
|
|
|
563
751
|
/* collect base middleware property names for diffing */
|
|
564
752
|
const baseMwPropNames = new Set<string>()
|
|
565
753
|
for (const p of baseCtxType.getProperties()) {
|
|
566
754
|
const name = p.getName()
|
|
567
|
-
if (!HONEY_CTX_PROPS.has(name) && !name.startsWith("_"))
|
|
568
|
-
baseMwPropNames.add(name)
|
|
569
|
-
}
|
|
755
|
+
if (!HONEY_CTX_PROPS.has(name) && !name.startsWith("_")) baseMwPropNames.add(name)
|
|
570
756
|
}
|
|
571
757
|
|
|
572
758
|
const sanitize = (s: string) => sanitizeImportPaths(s, options.outputDir)
|
|
573
759
|
|
|
574
|
-
|
|
575
|
-
const
|
|
576
|
-
const routeMiddlewareProps: Record<string, Array<{ name: string; opt: boolean; type: string }>> = {}
|
|
577
|
-
const compilerSourceFile: ts.SourceFile = bridge(sourceFile.compilerNode)
|
|
760
|
+
const routeMiddleware: Record<string, string> = Object.create(null)
|
|
761
|
+
const routeMiddlewareProps: Record<string, Array<{ name: string; opt: boolean; type: string }>> = Object.create(null)
|
|
578
762
|
|
|
579
763
|
function visitNode(node: ts.Node): void {
|
|
580
|
-
if (
|
|
764
|
+
if (compiler.isCallExpression(node) && compiler.isPropertyAccessExpression(node.expression)) {
|
|
581
765
|
const methodName = node.expression.name.text
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
const extraEntries: MwPropEntry[] = []
|
|
596
|
-
const dupeNames = new Set<string>()
|
|
597
|
-
const members = ctxType.isIntersection() ? (ctxType as ts.IntersectionType).types : [ctxType]
|
|
598
|
-
for (const member of members) {
|
|
599
|
-
const memberProps = extractMiddlewareProps(member, checker, baseNode, tsLib)
|
|
600
|
-
for (const rp of memberProps) {
|
|
601
|
-
if (baseMwPropNames.has(rp.name)) continue
|
|
602
|
-
if (extraEntries.some((e) => e.name === rp.name)) {
|
|
603
|
-
dupeNames.add(rp.name)
|
|
604
|
-
continue
|
|
605
|
-
}
|
|
606
|
-
extraEntries.push(rp)
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
/* for properties that appeared in multiple intersection members
|
|
610
|
-
* (e.g. auth narrowed by a later middleware), resolve from the
|
|
611
|
-
* flattened intersection so TypeScript computes the correct
|
|
612
|
-
* intersected type instead of using the first member's type */
|
|
613
|
-
for (const dupeName of dupeNames) {
|
|
614
|
-
const sym = ctxType.getProperty(dupeName)
|
|
615
|
-
if (!sym) continue
|
|
616
|
-
const t = checker.getTypeOfSymbolAtLocation(sym, sym.valueDeclaration ?? baseNode)
|
|
617
|
-
const typeStr = serializeAsRef(t, checker, baseNode, tsLib) ?? serialize(t, checker, baseNode, tsLib)
|
|
618
|
-
const idx = extraEntries.findIndex((e) => e.name === dupeName)
|
|
619
|
-
if (idx !== -1) {
|
|
620
|
-
extraEntries[idx] = { ...extraEntries[idx], type: typeStr }
|
|
621
|
-
}
|
|
622
|
-
}
|
|
766
|
+
const pathArg = node.arguments[0]
|
|
767
|
+
if (HTTP_METHODS.has(methodName) && pathArg && compiler.isStringLiteralLike(pathArg)) {
|
|
768
|
+
const receiverExpr = node.expression.expression
|
|
769
|
+
const receiverType = checker.getTypeAtLocation(receiverExpr)
|
|
770
|
+
const ctxType = getCtxType(receiverType, receiverExpr)
|
|
771
|
+
const basePath = ctxType ? basePathOf(receiverType, receiverExpr) : null
|
|
772
|
+
if (ctxType && basePath !== null) {
|
|
773
|
+
for (const variant of fullPaths(basePath, pathArg.text)) recordRoute(`${methodName} ${variant}`, ctxType)
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
compiler.forEachChild(node, visitNode)
|
|
778
|
+
}
|
|
623
779
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
780
|
+
/** The paths the router serves for this registration — an optional last param yields two. */
|
|
781
|
+
function fullPaths(basePath: string, path: string): string[] {
|
|
782
|
+
try {
|
|
783
|
+
return expandOptional(parsePattern(joinPatterns(basePath, path))).map((segments) => canonical(segments))
|
|
784
|
+
} catch {
|
|
785
|
+
/* a pattern the router would refuse registers nothing */
|
|
786
|
+
return []
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function recordRoute(key: string, ctxType: ts.Type): void {
|
|
791
|
+
/* extract extra middleware props by iterating intersection MEMBERS individually.
|
|
792
|
+
* This preserves source-level declarations (e.g. ShorthandPropertyAssignment)
|
|
793
|
+
* that get lost when extracting from the flattened intersection. */
|
|
794
|
+
const extraEntries: MwPropEntry[] = []
|
|
795
|
+
const dupeNames = new Set<string>()
|
|
796
|
+
const members = ctxType.isIntersection() ? (ctxType as ts.IntersectionType).types : [ctxType]
|
|
797
|
+
for (const member of members) {
|
|
798
|
+
for (const rp of extractMiddlewareProps(member, ser)) {
|
|
799
|
+
if (baseMwPropNames.has(rp.name)) continue
|
|
800
|
+
if (extraEntries.some((e) => e.name === rp.name)) {
|
|
801
|
+
dupeNames.add(rp.name)
|
|
802
|
+
continue
|
|
646
803
|
}
|
|
804
|
+
extraEntries.push(rp)
|
|
647
805
|
}
|
|
648
806
|
}
|
|
807
|
+
/* a property in several intersection members (e.g. auth narrowed by a later middleware)
|
|
808
|
+
* is read from the flattened intersection, so TypeScript computes the intersected type */
|
|
809
|
+
for (const dupeName of dupeNames) {
|
|
810
|
+
const sym = ctxType.getProperty(dupeName)
|
|
811
|
+
if (!sym) continue
|
|
812
|
+
const t = checker.getTypeOfSymbolAtLocation(sym, sym.valueDeclaration ?? baseNode)
|
|
813
|
+
const typeStr = ser.asRef(t) ?? ser.serialize(t)
|
|
814
|
+
const idx = extraEntries.findIndex((e) => e.name === dupeName)
|
|
815
|
+
if (idx !== -1) extraEntries[idx] = { ...extraEntries[idx], type: typeStr }
|
|
816
|
+
}
|
|
817
|
+
if (extraEntries.length === 0) return
|
|
649
818
|
|
|
650
|
-
|
|
819
|
+
/* reuse compact base type references when the route has the same property
|
|
820
|
+
* (e.g. shardDb: ShardDb from base → reuse import ref instead of structural expansion) */
|
|
821
|
+
const entries = extraEntries.map((e) => {
|
|
822
|
+
const baseRef = basePropTypeByName.get(e.name)
|
|
823
|
+
return baseRef && baseRef.length < e.type.length ? { ...e, type: baseRef } : e
|
|
824
|
+
})
|
|
825
|
+
routeMiddleware[key] = sanitize(propsType(entries) ?? "{}")
|
|
826
|
+
routeMiddlewareProps[key] = entries.map((e) => ({ name: e.name, opt: e.opt, type: sanitize(e.type) }))
|
|
651
827
|
}
|
|
652
828
|
|
|
653
|
-
|
|
829
|
+
/* every source file of the program, not only the entry: routes are often registered in
|
|
830
|
+
modules the entry imports */
|
|
831
|
+
for (const sf of project.getSourceFiles()) {
|
|
832
|
+
const compilerSf: ts.SourceFile = bridge(sf.compilerNode)
|
|
833
|
+
if (compilerSf.isDeclarationFile) continue
|
|
834
|
+
if (/[\\/]node_modules[\\/]/.test(compilerSf.fileName)) continue
|
|
835
|
+
visitNode(compilerSf)
|
|
836
|
+
}
|
|
654
837
|
|
|
655
838
|
/* extract $taps */
|
|
656
839
|
let tapsType: string | null = null
|
|
657
|
-
const tapsSym =
|
|
840
|
+
const tapsSym = appType.getProperty("$taps")
|
|
658
841
|
if (tapsSym) {
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
if (tapsStr !== "{}" && tapsStr !== "Record<string, unknown>") {
|
|
662
|
-
tapsType = tapsStr
|
|
663
|
-
}
|
|
842
|
+
const tapsStr = ser.serialize(checker.getTypeOfSymbolAtLocation(tapsSym, baseNode))
|
|
843
|
+
if (tapsStr !== "{}" && tapsStr !== "Record<string, unknown>") tapsType = tapsStr
|
|
664
844
|
}
|
|
665
845
|
|
|
666
846
|
return {
|
|
@@ -669,7 +849,7 @@ export async function extractChainTypes(options: {
|
|
|
669
849
|
middlewareType: baseMiddlewareType ? sanitize(baseMiddlewareType) : null,
|
|
670
850
|
tapsType: tapsType ? sanitize(tapsType) : null,
|
|
671
851
|
},
|
|
672
|
-
routeMiddleware,
|
|
673
|
-
routeMiddlewareProps,
|
|
852
|
+
routeMiddleware: { ...routeMiddleware },
|
|
853
|
+
routeMiddlewareProps: { ...routeMiddlewareProps },
|
|
674
854
|
}
|
|
675
855
|
}
|