@lovrozagar/honey 0.6.8 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +776 -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 +562 -424
- 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/client/http.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { bindDefaultFetch, parseClientBaseURL } from "./defaults.ts"
|
|
2
|
-
import { ClientError } from "./error.ts"
|
|
2
|
+
import type { ClientError } from "./error.ts"
|
|
3
|
+
import { clientErrorFor } from "./error.ts"
|
|
4
|
+
import { isJsonMediaType, isTextMediaType, parseMediaType } from "../media-type.ts"
|
|
5
|
+
import { interpolatePath } from "./path.ts"
|
|
6
|
+
import { crossOriginHeaders } from "./redirect-policy.ts"
|
|
3
7
|
import type { SSEEvent } from "./sse.ts"
|
|
4
8
|
import { parseSSEStream } from "./sse.ts"
|
|
5
9
|
|
|
10
|
+
export { interpolatePath } from "./path.ts"
|
|
11
|
+
|
|
6
12
|
export type HeadersContext = {
|
|
7
13
|
method: string
|
|
8
14
|
path: string
|
|
@@ -37,15 +43,39 @@ export type OnResponseContext = {
|
|
|
37
43
|
isStale?: boolean
|
|
38
44
|
method: string
|
|
39
45
|
path: string
|
|
40
|
-
request: Request
|
|
46
|
+
readonly request: Request
|
|
41
47
|
response: Response
|
|
48
|
+
/**
|
|
49
|
+
* Send the request once more and resolve with the new response, whatever its status. On
|
|
50
|
+
* a request that is itself a retry, it resolves with the current response (one retry max).
|
|
51
|
+
*/
|
|
42
52
|
retry: () => Promise<Response>
|
|
43
53
|
selector?: string
|
|
44
54
|
state: Record<string, unknown>
|
|
45
55
|
url: string
|
|
46
56
|
}
|
|
47
57
|
|
|
58
|
+
/**
|
|
59
|
+
* - `same-origin` (default): follow redirects that stay on the base URL's origin; a
|
|
60
|
+
* cross-origin redirect is returned unfollowed (a non-2xx result). In browsers, which hide
|
|
61
|
+
* redirect targets from script, the platform follows and applies CORS to the target.
|
|
62
|
+
* - `follow`: also follow cross-origin redirects. Such a hop carries only `Accept`,
|
|
63
|
+
* `Accept-Language` and `User-Agent` (no credentials, auth header, configured, per-call or
|
|
64
|
+
* `onRequest` header) and never replays a body.
|
|
65
|
+
* - `manual` / `error`: passed to `fetch`.
|
|
66
|
+
*/
|
|
67
|
+
export type RedirectPolicy = "error" | "follow" | "manual" | "same-origin"
|
|
68
|
+
|
|
69
|
+
export type AuthExpiredContext = {
|
|
70
|
+
/** The token the 401 rejected (read from the auth header), so concurrent 401s can share one refresh. */
|
|
71
|
+
rejectedToken: string | null
|
|
72
|
+
}
|
|
73
|
+
|
|
48
74
|
export type ClientConfig = {
|
|
75
|
+
/** Header that carries the token from `onAuthExpired`. Default `Authorization`. */
|
|
76
|
+
authHeaderName?: string
|
|
77
|
+
/** Prefix for that header. Default `"Bearer "`. */
|
|
78
|
+
authHeaderPrefix?: string
|
|
49
79
|
/** Absolute `http(s):` / `ws(s):` URL, or a same-origin path such as `"/api"`. */
|
|
50
80
|
baseURL: string
|
|
51
81
|
buildSearchParams?: (query: Record<string, unknown>) => URLSearchParams
|
|
@@ -54,15 +84,25 @@ export type ClientConfig = {
|
|
|
54
84
|
fetch?: typeof fetch
|
|
55
85
|
headers?: HeadersInit
|
|
56
86
|
mode?: RequestMode
|
|
87
|
+
/**
|
|
88
|
+
* Called on a 401. Return a fresh token to retry the request once with it (and use it for
|
|
89
|
+
* later requests); return null or "" to give up. Concurrent 401s share one call. Requests
|
|
90
|
+
* whose body is a stream are not retried.
|
|
91
|
+
*/
|
|
92
|
+
onAuthExpired?: (ctx: AuthExpiredContext) => Promise<string | null | undefined> | string | null | undefined
|
|
57
93
|
onRequest?: Array<(ctx: OnRequestContext) => Promise<void> | void>
|
|
58
94
|
onResponse?: Array<(ctx: OnResponseContext) => Promise<Response | undefined> | Response | undefined>
|
|
95
|
+
/** Redirect handling; see `RedirectPolicy`. Default `"same-origin"`. */
|
|
96
|
+
redirect?: RedirectPolicy
|
|
97
|
+
/** Send an `x-request-id` header on every request. Default `true`; `false` avoids a CORS preflight. */
|
|
98
|
+
requestId?: boolean
|
|
59
99
|
sortSearchParams?: boolean
|
|
60
100
|
state?: Record<string, unknown>
|
|
61
101
|
throwOnError?: boolean
|
|
102
|
+
/** Milliseconds for a whole request, including reading the body. Streams are exempt. */
|
|
62
103
|
timeout?: number
|
|
63
104
|
|
|
64
105
|
/* Realtime hooks — called by the generated runtime module */
|
|
65
|
-
onAuthExpired?: () => Promise<string | null | undefined>
|
|
66
106
|
onReconnecting?: (attempt: number, transport: string) => void
|
|
67
107
|
onReconnected?: () => void
|
|
68
108
|
}
|
|
@@ -70,20 +110,24 @@ export type ClientConfig = {
|
|
|
70
110
|
export type RequestOptions = {
|
|
71
111
|
cookies?: Record<string, string>
|
|
72
112
|
form?: Record<string, unknown>
|
|
73
|
-
headers?: Record<string, string>
|
|
113
|
+
headers?: Record<string, string | undefined>
|
|
74
114
|
json?: unknown
|
|
75
115
|
lastEventId?: string
|
|
76
116
|
params?: Record<string, string>
|
|
77
117
|
search?: Record<string, unknown>
|
|
78
118
|
signal?: AbortSignal
|
|
119
|
+
/** Per-call timeout in milliseconds; overrides `config.timeout`, `0` disables it. */
|
|
120
|
+
timeout?: number
|
|
79
121
|
}
|
|
80
122
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
123
|
+
type Kind = "auto" | "rest" | "stream"
|
|
124
|
+
|
|
125
|
+
type Sent = {
|
|
126
|
+
/** Release timers and listeners. Call once the body has been read or abandoned. */
|
|
127
|
+
done: () => void
|
|
128
|
+
/** Stop the timeout but keep user cancellation (the response turned out to be a stream). */
|
|
129
|
+
exemptFromTimeout: () => void
|
|
130
|
+
response: Response
|
|
87
131
|
}
|
|
88
132
|
|
|
89
133
|
function coerceParam(v: unknown): string | null {
|
|
@@ -93,6 +137,15 @@ function coerceParam(v: unknown): string | null {
|
|
|
93
137
|
return String(v)
|
|
94
138
|
}
|
|
95
139
|
|
|
140
|
+
function coerceFormValue(key: string, v: unknown): string | null {
|
|
141
|
+
if (v !== null && typeof v === "object" && !(v instanceof Date)) {
|
|
142
|
+
throw new TypeError(
|
|
143
|
+
`Form field ${JSON.stringify(key)}: nested objects cannot be form-encoded; pass a string (e.g. JSON.stringify) or use json`,
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
return coerceParam(v)
|
|
147
|
+
}
|
|
148
|
+
|
|
96
149
|
function defaultSearchParams(query: Record<string, unknown>): URLSearchParams {
|
|
97
150
|
const params = new URLSearchParams()
|
|
98
151
|
for (const [k, v] of Object.entries(query)) {
|
|
@@ -110,30 +163,6 @@ function defaultSearchParams(query: Record<string, unknown>): URLSearchParams {
|
|
|
110
163
|
return params
|
|
111
164
|
}
|
|
112
165
|
|
|
113
|
-
function applySearch(url: URL, search: Record<string, unknown>, config: ClientConfig): void {
|
|
114
|
-
const serializer = config.buildSearchParams ?? defaultSearchParams
|
|
115
|
-
const params = serializer(search)
|
|
116
|
-
if (config.sortSearchParams) params.sort()
|
|
117
|
-
for (const [k, v] of params.entries()) {
|
|
118
|
-
url.searchParams.append(k, v)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function buildURL(config: ClientConfig, path: string, opts: RequestOptions): string {
|
|
123
|
-
let resolvedPath = path
|
|
124
|
-
if (opts.params) resolvedPath = interpolatePath(path, opts.params)
|
|
125
|
-
|
|
126
|
-
const baseUrl = parseClientBaseURL(config.baseURL)
|
|
127
|
-
const basePath = baseUrl.pathname.endsWith("/") ? baseUrl.pathname : `${baseUrl.pathname}/`
|
|
128
|
-
const relative = resolvedPath.startsWith("/") ? resolvedPath.slice(1) : resolvedPath
|
|
129
|
-
const url = new URL(`${basePath}${relative}`, baseUrl)
|
|
130
|
-
|
|
131
|
-
for (const [k, v] of baseUrl.searchParams.entries()) url.searchParams.append(k, v)
|
|
132
|
-
if (opts.search) applySearch(url, opts.search, config)
|
|
133
|
-
|
|
134
|
-
return url.toString()
|
|
135
|
-
}
|
|
136
|
-
|
|
137
166
|
/**
|
|
138
167
|
* RFC 4122 §4.4 UUID for `x-request-id`. Prefer `crypto.randomUUID` (browser,
|
|
139
168
|
* Node 19+, Bun, Deno, CF Workers). Fall back to `getRandomValues` when
|
|
@@ -157,219 +186,387 @@ export function newClientRequestId(): string {
|
|
|
157
186
|
throw new Error("honey: no crypto.randomUUID or crypto.getRandomValues in this runtime")
|
|
158
187
|
}
|
|
159
188
|
|
|
160
|
-
|
|
161
|
-
|
|
189
|
+
function isFileLike(v: unknown): boolean {
|
|
190
|
+
return (typeof File !== "undefined" && v instanceof File) || (typeof Blob !== "undefined" && v instanceof Blob)
|
|
191
|
+
}
|
|
162
192
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
193
|
+
function encodeBody(opts: RequestOptions, headers: Headers): BodyInit | undefined {
|
|
194
|
+
if (opts.json !== undefined) {
|
|
195
|
+
headers.set("content-type", "application/json")
|
|
196
|
+
return JSON.stringify(opts.json)
|
|
168
197
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
198
|
+
if (opts.form === undefined) return undefined
|
|
199
|
+
|
|
200
|
+
const entries = Object.entries(opts.form)
|
|
201
|
+
const hasFiles = entries.some(
|
|
202
|
+
([, v]) =>
|
|
203
|
+
isFileLike(v) ||
|
|
204
|
+
(typeof FileList !== "undefined" && v instanceof FileList) ||
|
|
205
|
+
(Array.isArray(v) && v.some(isFileLike)),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
/* Both encodings repeat the key for array items, so a field reads the same either way. */
|
|
209
|
+
if (hasFiles) {
|
|
210
|
+
const fd = new FormData()
|
|
211
|
+
for (const [k, v] of entries) {
|
|
212
|
+
if (v === undefined || v === null) continue
|
|
213
|
+
const items =
|
|
214
|
+
typeof FileList !== "undefined" && v instanceof FileList ? Array.from(v) : Array.isArray(v) ? v : [v]
|
|
215
|
+
for (const item of items) {
|
|
216
|
+
if (isFileLike(item)) fd.append(k, item as Blob)
|
|
217
|
+
else {
|
|
218
|
+
const s = coerceFormValue(k, item)
|
|
219
|
+
if (s !== null) fd.append(k, s)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
174
222
|
}
|
|
223
|
+
/* don't set content-type — fetch sets the multipart boundary */
|
|
224
|
+
return fd
|
|
175
225
|
}
|
|
176
226
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
headers.set("cookie", existing ? `${existing}; ${pairs}` : pairs)
|
|
227
|
+
headers.set("content-type", "application/x-www-form-urlencoded")
|
|
228
|
+
const params = new URLSearchParams()
|
|
229
|
+
for (const [k, v] of entries) {
|
|
230
|
+
for (const item of Array.isArray(v) ? v : [v]) {
|
|
231
|
+
const s = coerceFormValue(k, item)
|
|
232
|
+
if (s !== null) params.append(k, s)
|
|
184
233
|
}
|
|
185
234
|
}
|
|
235
|
+
return params.toString()
|
|
236
|
+
}
|
|
186
237
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
238
|
+
/** A body that can be sent a second time (retry, redirect). Streams are consumed by the first send. */
|
|
239
|
+
function isReplayable(body: BodyInit | null | undefined): boolean {
|
|
240
|
+
return !(typeof ReadableStream !== "undefined" && body instanceof ReadableStream)
|
|
241
|
+
}
|
|
191
242
|
|
|
192
|
-
|
|
243
|
+
export function isEventStream(response: Response): boolean {
|
|
244
|
+
return parseMediaType(response.headers.get("content-type"))?.essence === "text/event-stream"
|
|
193
245
|
}
|
|
194
246
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const userSignal = opts.signal
|
|
201
|
-
/* skip timeout for streams — they're long-lived */
|
|
202
|
-
const timeout = isStream ? undefined : config.timeout
|
|
203
|
-
|
|
204
|
-
if (!timeout) return { cleanup: () => {}, signal: userSignal }
|
|
205
|
-
|
|
206
|
-
const ctrl = new AbortController()
|
|
207
|
-
const timer = setTimeout(() => ctrl.abort(new Error("timeout")), timeout)
|
|
208
|
-
if (typeof (timer as unknown as { unref?: () => void }).unref === "function") {
|
|
209
|
-
;(timer as unknown as { unref: () => void }).unref()
|
|
210
|
-
}
|
|
211
|
-
const onUserAbort = () => ctrl.abort(userSignal?.reason)
|
|
212
|
-
if (userSignal?.aborted) {
|
|
213
|
-
/* already aborted before we subscribed — propagate synchronously */
|
|
214
|
-
ctrl.abort(userSignal.reason)
|
|
215
|
-
} else {
|
|
216
|
-
userSignal?.addEventListener("abort", onUserAbort, { once: true })
|
|
217
|
-
}
|
|
218
|
-
return {
|
|
219
|
-
cleanup: () => {
|
|
220
|
-
clearTimeout(timer)
|
|
221
|
-
userSignal?.removeEventListener("abort", onUserAbort)
|
|
222
|
-
},
|
|
223
|
-
signal: ctrl.signal,
|
|
224
|
-
}
|
|
247
|
+
/* oxlint-disable-next-line no-control-regex -- intentional strip of ASCII control characters */
|
|
248
|
+
const CONTROL_RE = /[\x00-\x1f\x7f]/g
|
|
249
|
+
|
|
250
|
+
function sanitizeMessage(message: string): string {
|
|
251
|
+
return message.replace(CONTROL_RE, "").slice(0, 512)
|
|
225
252
|
}
|
|
226
253
|
|
|
227
|
-
|
|
254
|
+
/** Read an error body: JSON when it parses, else the text, else null. Never throws on the body. */
|
|
255
|
+
async function readErrorBody(response: Response): Promise<unknown> {
|
|
256
|
+
let text: string
|
|
257
|
+
try {
|
|
258
|
+
text = await response.text()
|
|
259
|
+
} catch {
|
|
260
|
+
return null
|
|
261
|
+
}
|
|
262
|
+
if (text.length === 0) return null
|
|
228
263
|
try {
|
|
229
|
-
return
|
|
264
|
+
return JSON.parse(text)
|
|
230
265
|
} catch {
|
|
231
|
-
return
|
|
266
|
+
return text
|
|
232
267
|
}
|
|
233
268
|
}
|
|
234
269
|
|
|
235
|
-
async function
|
|
270
|
+
async function errorFromResponse(response: Response): Promise<ClientError> {
|
|
236
271
|
const preserved = response.clone()
|
|
237
|
-
const body = await
|
|
272
|
+
const body = await readErrorBody(response)
|
|
238
273
|
const msgVal =
|
|
239
274
|
typeof body === "object" && body !== null && "message" in body
|
|
240
275
|
? (body as Record<string, unknown>).message
|
|
241
276
|
: undefined
|
|
242
|
-
const message =
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
277
|
+
const message = typeof msgVal === "string" && msgVal.length > 0 ? sanitizeMessage(msgVal) : `HTTP ${response.status}`
|
|
278
|
+
return clientErrorFor({ body, message, response: preserved, status: response.status })
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Safe-mode error value: the server's error object when the body is a JSON object, else the
|
|
283
|
+
* `ClientError` itself, so `error` is truthy for every non-2xx and always carries `status`.
|
|
284
|
+
*/
|
|
285
|
+
function safeErrorValue(error: ClientError): unknown {
|
|
286
|
+
const body = error.body
|
|
287
|
+
return typeof body === "object" && body !== null && !Array.isArray(body) ? body : error
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function timeoutError(ms: number): Error {
|
|
291
|
+
return typeof DOMException === "function"
|
|
292
|
+
? new DOMException(`Request timed out after ${ms} ms`, "TimeoutError")
|
|
293
|
+
: Object.assign(new Error(`Request timed out after ${ms} ms`), { name: "TimeoutError" })
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Browsers hide redirect targets from script (`opaqueredirect`), so the platform must follow. */
|
|
297
|
+
function platformFollowsRedirects(): boolean {
|
|
298
|
+
const g = globalThis as { document?: unknown; WorkerGlobalScope?: unknown }
|
|
299
|
+
return typeof g.document !== "undefined" || typeof g.WorkerGlobalScope !== "undefined"
|
|
248
300
|
}
|
|
249
301
|
|
|
302
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308])
|
|
303
|
+
const MAX_REDIRECTS = 20
|
|
304
|
+
|
|
250
305
|
export class HTTPClient {
|
|
251
306
|
private _config: ClientConfig
|
|
252
307
|
private _fetch: typeof fetch
|
|
308
|
+
private _base: URL | undefined
|
|
309
|
+
private _token: string | null = null
|
|
310
|
+
private _refreshing: Promise<string | null> | null = null
|
|
253
311
|
|
|
254
312
|
constructor(config: ClientConfig) {
|
|
255
313
|
this._config = config
|
|
256
314
|
this._fetch = bindDefaultFetch(config.fetch)
|
|
257
315
|
}
|
|
258
316
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
317
|
+
/** The base URL, parsed once. */
|
|
318
|
+
private get base(): URL {
|
|
319
|
+
this._base ??= parseClientBaseURL(this._config.baseURL)
|
|
320
|
+
return this._base
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private get authName(): string {
|
|
324
|
+
return this._config.authHeaderName ?? "Authorization"
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private get authPrefix(): string {
|
|
328
|
+
return this._config.authHeaderPrefix ?? "Bearer "
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Resolve `path` against the base URL, keeping the base path and query, and assert it stays there. */
|
|
332
|
+
private _url(path: string, opts: RequestOptions): URL {
|
|
333
|
+
const resolvedPath = opts.params ? interpolatePath(path, opts.params) : interpolatePath(path)
|
|
334
|
+
const base = this.base
|
|
335
|
+
const basePath = base.pathname.endsWith("/") ? base.pathname : `${base.pathname}/`
|
|
336
|
+
const relative = resolvedPath.startsWith("/") ? resolvedPath.slice(1) : resolvedPath
|
|
337
|
+
const url = new URL(base.href)
|
|
338
|
+
url.pathname = `${basePath}${relative}`
|
|
339
|
+
url.hash = ""
|
|
340
|
+
/* Params are validated, so this only fires on a malformed template; never send it anywhere else. */
|
|
341
|
+
if (url.origin !== base.origin || !url.pathname.startsWith(basePath.slice(0, -1))) {
|
|
342
|
+
throw new Error(`Request path ${JSON.stringify(path)} resolves outside the base URL`)
|
|
343
|
+
}
|
|
344
|
+
if (opts.search) {
|
|
345
|
+
const serializer = this._config.buildSearchParams ?? defaultSearchParams
|
|
346
|
+
const params = serializer(opts.search)
|
|
347
|
+
if (this._config.sortSearchParams) params.sort()
|
|
348
|
+
for (const [k, v] of params.entries()) url.searchParams.append(k, v)
|
|
349
|
+
}
|
|
350
|
+
return url
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private async _headers(opts: RequestOptions, ctx: HeadersContext): Promise<Headers> {
|
|
354
|
+
const headers = new Headers()
|
|
355
|
+
const config = this._config
|
|
356
|
+
|
|
357
|
+
if (config.headers) {
|
|
358
|
+
const resolved = typeof config.headers === "function" ? await config.headers(ctx) : config.headers
|
|
359
|
+
for (const [k, v] of Object.entries(resolved)) {
|
|
360
|
+
if (v !== undefined) headers.set(k, v)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* a refreshed token replaces the configured one; per-call headers still win */
|
|
365
|
+
if (this._token !== null) headers.set(this.authName, `${this.authPrefix}${this._token}`)
|
|
366
|
+
|
|
367
|
+
if (opts.headers) {
|
|
368
|
+
for (const [k, v] of Object.entries(opts.headers)) {
|
|
369
|
+
if (v !== undefined) headers.set(k, v)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (opts.cookies) {
|
|
374
|
+
const existing = headers.get("cookie")
|
|
375
|
+
const pairs = Object.entries(opts.cookies)
|
|
376
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
377
|
+
.join("; ")
|
|
378
|
+
if (pairs) {
|
|
379
|
+
headers.set("cookie", existing ? `${existing}; ${pairs}` : pairs)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/* auto correlation id — config/per-call win; onRequest may overwrite later */
|
|
384
|
+
if (config.requestId !== false && !headers.has("x-request-id")) {
|
|
385
|
+
headers.set("x-request-id", newClientRequestId())
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return headers
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** One combined signal for the user's abort and the timeout, alive until `done()`. */
|
|
392
|
+
private _signal(
|
|
262
393
|
opts: RequestOptions,
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
394
|
+
kind: Kind,
|
|
395
|
+
): { done: () => void; exemptFromTimeout: () => void; signal: AbortSignal | undefined } {
|
|
396
|
+
const userSignal = opts.signal
|
|
397
|
+
const timeout = kind === "stream" ? undefined : (opts.timeout ?? this._config.timeout)
|
|
398
|
+
if (!timeout || timeout <= 0) {
|
|
399
|
+
return { done: () => {}, exemptFromTimeout: () => {}, signal: userSignal }
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const ctrl = new AbortController()
|
|
403
|
+
const timer = setTimeout(() => ctrl.abort(timeoutError(timeout)), timeout)
|
|
404
|
+
;(timer as unknown as { unref?: () => void }).unref?.()
|
|
405
|
+
const onUserAbort = () => ctrl.abort(userSignal?.reason)
|
|
406
|
+
if (userSignal?.aborted) ctrl.abort(userSignal.reason)
|
|
407
|
+
else userSignal?.addEventListener("abort", onUserAbort, { once: true })
|
|
408
|
+
return {
|
|
409
|
+
done: () => {
|
|
410
|
+
clearTimeout(timer)
|
|
411
|
+
userSignal?.removeEventListener("abort", onUserAbort)
|
|
412
|
+
},
|
|
413
|
+
exemptFromTimeout: () => clearTimeout(timer),
|
|
414
|
+
signal: ctrl.signal,
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Share one `onAuthExpired` call between concurrent 401s; reuse a token another request already got. */
|
|
419
|
+
private _refresh(rejectedToken: string | null): Promise<string | null> {
|
|
420
|
+
if (this._token !== null && rejectedToken !== this._token) return Promise.resolve(this._token)
|
|
421
|
+
if (this._refreshing) return this._refreshing
|
|
422
|
+
const hook = this._config.onAuthExpired
|
|
423
|
+
if (!hook) return Promise.resolve(null)
|
|
424
|
+
this._refreshing = Promise.resolve()
|
|
425
|
+
.then(() => hook({ rejectedToken }))
|
|
426
|
+
.then((token) => {
|
|
427
|
+
this._token = typeof token === "string" && token.length > 0 ? token : null
|
|
428
|
+
return this._token
|
|
429
|
+
})
|
|
430
|
+
.finally(() => {
|
|
431
|
+
this._refreshing = null
|
|
432
|
+
})
|
|
433
|
+
return this._refreshing
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private async _fetchWithRedirects(url: URL, init: RequestInit): Promise<Response> {
|
|
437
|
+
const policy = this._config.redirect ?? "same-origin"
|
|
438
|
+
if (policy === "manual" || policy === "error" || platformFollowsRedirects()) {
|
|
439
|
+
return this._fetch(url.toString(), policy === "same-origin" ? init : { ...init, redirect: policy })
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const origin = this.base.origin
|
|
443
|
+
let current = url
|
|
444
|
+
let currentInit: RequestInit = { ...init, redirect: "manual" }
|
|
445
|
+
for (let hop = 0; ; hop++) {
|
|
446
|
+
const response = await this._fetch(current.toString(), currentInit)
|
|
447
|
+
const location = response.headers.get("location")
|
|
448
|
+
if (!REDIRECT_STATUSES.has(response.status) || location === null || hop >= MAX_REDIRECTS) return response
|
|
449
|
+
|
|
450
|
+
let target: URL
|
|
451
|
+
try {
|
|
452
|
+
target = new URL(location, current)
|
|
453
|
+
} catch {
|
|
454
|
+
return response
|
|
455
|
+
}
|
|
456
|
+
if (target.protocol !== "http:" && target.protocol !== "https:") return response
|
|
457
|
+
const crossOrigin = target.origin !== origin
|
|
458
|
+
if (crossOrigin && policy !== "follow") return response
|
|
459
|
+
|
|
460
|
+
const nextInit: RequestInit = { ...currentInit, headers: new Headers(currentInit.headers) }
|
|
461
|
+
const keepsBody = response.status === 307 || response.status === 308
|
|
462
|
+
if (keepsBody) {
|
|
463
|
+
if (currentInit.body != null && (crossOrigin || !isReplayable(currentInit.body))) return response
|
|
315
464
|
} else {
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
465
|
+
/* 303, and 301/302 for POST: the browser behavior is GET without a body */
|
|
466
|
+
const method = (currentInit.method ?? "GET").toUpperCase()
|
|
467
|
+
if (response.status === 303 ? method !== "HEAD" : method === "POST") {
|
|
468
|
+
nextInit.method = "GET"
|
|
469
|
+
nextInit.body = undefined
|
|
470
|
+
;(nextInit.headers as Headers).delete("content-type")
|
|
321
471
|
}
|
|
322
|
-
body = params.toString()
|
|
323
472
|
}
|
|
473
|
+
if (crossOrigin) nextInit.headers = crossOriginHeaders(nextInit.headers as Headers)
|
|
474
|
+
await response.body?.cancel().catch(() => {})
|
|
475
|
+
current = target
|
|
476
|
+
currentInit = nextInit
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** Build, send and post-process one request: hooks, auth refresh, redirects. */
|
|
481
|
+
private async _send(
|
|
482
|
+
method: string,
|
|
483
|
+
path: string,
|
|
484
|
+
opts: RequestOptions,
|
|
485
|
+
kind: Kind,
|
|
486
|
+
meta?: RequestMeta,
|
|
487
|
+
isRetry = false,
|
|
488
|
+
): Promise<Sent> {
|
|
489
|
+
const url = this._url(path, opts)
|
|
490
|
+
const headers = await this._headers(opts, { method, path })
|
|
491
|
+
if (kind === "stream") {
|
|
492
|
+
headers.set("accept", "text/event-stream")
|
|
493
|
+
if (opts.lastEventId) headers.set("last-event-id", opts.lastEventId)
|
|
324
494
|
}
|
|
495
|
+
let body = encodeBody(opts, headers)
|
|
325
496
|
|
|
326
|
-
/* onRequest interceptors */
|
|
327
497
|
if (this._config.onRequest) {
|
|
328
|
-
const reqCtx: OnRequestContext = {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
498
|
+
const reqCtx: OnRequestContext = {
|
|
499
|
+
body,
|
|
500
|
+
headers,
|
|
501
|
+
method,
|
|
502
|
+
path,
|
|
503
|
+
state: this._config.state ?? {},
|
|
504
|
+
url: url.toString(),
|
|
505
|
+
}
|
|
506
|
+
if (meta) {
|
|
507
|
+
reqCtx.invalidatedBy = meta.invalidatedBy
|
|
508
|
+
reqCtx.isStale = meta.isStale
|
|
509
|
+
reqCtx.selector = meta.selector
|
|
333
510
|
}
|
|
334
511
|
for (const hook of this._config.onRequest) {
|
|
335
512
|
await hook(reqCtx)
|
|
336
513
|
}
|
|
337
|
-
|
|
514
|
+
body = reqCtx.body
|
|
338
515
|
}
|
|
339
516
|
|
|
340
|
-
const { signal,
|
|
517
|
+
const { signal, done, exemptFromTimeout } = this._signal(opts, kind)
|
|
518
|
+
const cleanups: Array<() => void> = [done]
|
|
519
|
+
const finish = () => {
|
|
520
|
+
for (const fn of cleanups.splice(0)) fn()
|
|
521
|
+
}
|
|
341
522
|
const init: RequestInit = { body, headers, method, signal }
|
|
342
|
-
|
|
343
523
|
if (this._config.credentials) init.credentials = this._config.credentials
|
|
344
524
|
if (this._config.mode) init.mode = this._config.mode
|
|
525
|
+
/* a stream body needs half-duplex on Node's fetch */
|
|
526
|
+
if (body !== undefined && !isReplayable(body)) (init as RequestInit & { duplex?: string }).duplex = "half"
|
|
345
527
|
|
|
346
528
|
try {
|
|
347
|
-
/* fail fast if the signal is already aborted — matches native fetch semantics */
|
|
348
529
|
signal?.throwIfAborted()
|
|
349
|
-
let response = await this.
|
|
530
|
+
let response = await this._fetchWithRedirects(url, init)
|
|
531
|
+
|
|
532
|
+
if (response.status === 401 && this._config.onAuthExpired && !isRetry && isReplayable(body)) {
|
|
533
|
+
const sent = headers.get(this.authName)
|
|
534
|
+
const rejectedToken = sent?.startsWith(this.authPrefix) ? sent.slice(this.authPrefix.length) : (sent ?? null)
|
|
535
|
+
const token = await this._refresh(rejectedToken)
|
|
536
|
+
if (token !== null) {
|
|
537
|
+
const retryHeaders = new Headers(headers)
|
|
538
|
+
retryHeaders.set(this.authName, `${this.authPrefix}${token}`)
|
|
539
|
+
init.headers = retryHeaders
|
|
540
|
+
await response.body?.cancel().catch(() => {})
|
|
541
|
+
response = await this._fetchWithRedirects(url, { ...init })
|
|
542
|
+
}
|
|
543
|
+
}
|
|
350
544
|
|
|
351
|
-
/* onResponse interceptors */
|
|
352
545
|
if (this._config.onResponse) {
|
|
546
|
+
let request: Request | undefined
|
|
353
547
|
const resCtx: OnResponseContext = {
|
|
354
548
|
isRetry,
|
|
355
549
|
method,
|
|
356
550
|
path,
|
|
357
|
-
request
|
|
551
|
+
get request() {
|
|
552
|
+
/* built on demand: constructing a Request per response is not free */
|
|
553
|
+
request ??= new Request(url.toString(), { ...init, body: isReplayable(body) ? body : undefined })
|
|
554
|
+
return request
|
|
555
|
+
},
|
|
358
556
|
response,
|
|
359
|
-
retry: () => {
|
|
360
|
-
if (isRetry)
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
})
|
|
557
|
+
retry: async () => {
|
|
558
|
+
if (isRetry) return resCtx.response
|
|
559
|
+
const again = await this._send(method, path, opts, kind, meta, true)
|
|
560
|
+
cleanups.push(again.done)
|
|
561
|
+
return again.response
|
|
365
562
|
},
|
|
366
563
|
state: this._config.state ?? {},
|
|
367
|
-
url,
|
|
564
|
+
url: url.toString(),
|
|
368
565
|
}
|
|
369
|
-
if (
|
|
370
|
-
resCtx.invalidatedBy =
|
|
371
|
-
resCtx.isStale =
|
|
372
|
-
resCtx.selector =
|
|
566
|
+
if (meta) {
|
|
567
|
+
resCtx.invalidatedBy = meta.invalidatedBy
|
|
568
|
+
resCtx.isStale = meta.isStale
|
|
569
|
+
resCtx.selector = meta.selector
|
|
373
570
|
}
|
|
374
571
|
for (const hook of this._config.onResponse) {
|
|
375
572
|
const result = await hook(resCtx)
|
|
@@ -380,38 +577,84 @@ export class HTTPClient {
|
|
|
380
577
|
}
|
|
381
578
|
}
|
|
382
579
|
|
|
383
|
-
return { response }
|
|
384
|
-
}
|
|
385
|
-
|
|
580
|
+
return { done: finish, exemptFromTimeout, response }
|
|
581
|
+
} catch (e) {
|
|
582
|
+
finish()
|
|
583
|
+
throw e
|
|
386
584
|
}
|
|
387
585
|
}
|
|
388
586
|
|
|
389
|
-
private _parseBody(response: Response): Promise<unknown> {
|
|
390
|
-
if (
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
587
|
+
private async _parseBody(response: Response, method: string): Promise<unknown> {
|
|
588
|
+
if (
|
|
589
|
+
response.status === 204 ||
|
|
590
|
+
response.status === 205 ||
|
|
591
|
+
response.status === 304 ||
|
|
592
|
+
method.toUpperCase() === "HEAD"
|
|
593
|
+
) {
|
|
594
|
+
await response.body?.cancel().catch(() => {})
|
|
595
|
+
return null
|
|
395
596
|
}
|
|
396
|
-
|
|
397
|
-
|
|
597
|
+
const ct = parseMediaType(response.headers.get("content-type"))
|
|
598
|
+
if (isJsonMediaType(ct)) {
|
|
599
|
+
const text = await response.text()
|
|
600
|
+
if (text.length === 0) return null
|
|
601
|
+
try {
|
|
602
|
+
return JSON.parse(text)
|
|
603
|
+
} catch {
|
|
604
|
+
throw clientErrorFor({
|
|
605
|
+
body: text,
|
|
606
|
+
message: "Response body is not valid JSON",
|
|
607
|
+
response,
|
|
608
|
+
status: response.status,
|
|
609
|
+
})
|
|
610
|
+
}
|
|
398
611
|
}
|
|
399
|
-
if (ct
|
|
400
|
-
|
|
612
|
+
if (isTextMediaType(ct)) return response.text()
|
|
613
|
+
const buffer = await response.arrayBuffer()
|
|
614
|
+
return buffer.byteLength === 0 ? null : buffer
|
|
401
615
|
}
|
|
402
616
|
|
|
403
|
-
/**
|
|
404
|
-
async
|
|
405
|
-
const { response } =
|
|
617
|
+
/** Read the response of a REST call into a value (throw mode) or a result tuple (safe mode). */
|
|
618
|
+
private async _finish(sent: Sent, method: string, shouldThrow: boolean): Promise<unknown> {
|
|
619
|
+
const { response } = sent
|
|
620
|
+
try {
|
|
621
|
+
if (!response.ok) {
|
|
622
|
+
const error = await errorFromResponse(response)
|
|
623
|
+
if (shouldThrow) throw error
|
|
624
|
+
return { data: null, error: safeErrorValue(error), response: error.response, status: response.status }
|
|
625
|
+
}
|
|
626
|
+
if (shouldThrow) return await this._parseBody(response, method)
|
|
627
|
+
try {
|
|
628
|
+
const data = await this._parseBody(response, method)
|
|
629
|
+
return { data, error: null, response, status: response.status }
|
|
630
|
+
} catch (e) {
|
|
631
|
+
return { data: null, error: e, response, status: response.status }
|
|
632
|
+
}
|
|
633
|
+
} finally {
|
|
634
|
+
sent.done()
|
|
635
|
+
}
|
|
636
|
+
}
|
|
406
637
|
|
|
407
|
-
|
|
408
|
-
|
|
638
|
+
private _iterate(sent: Sent): AsyncIterable<SSEEvent> {
|
|
639
|
+
return {
|
|
640
|
+
async *[Symbol.asyncIterator]() {
|
|
641
|
+
try {
|
|
642
|
+
if (!sent.response.body) return
|
|
643
|
+
yield* parseSSEStream(sent.response.body)
|
|
644
|
+
} finally {
|
|
645
|
+
sent.done()
|
|
646
|
+
}
|
|
647
|
+
},
|
|
409
648
|
}
|
|
649
|
+
}
|
|
410
650
|
|
|
411
|
-
|
|
651
|
+
/** Throw mode — throws ClientError (a per-status subclass) on non-2xx */
|
|
652
|
+
async request(method: string, path: string, opts: RequestOptions, _requestMeta?: RequestMeta): Promise<unknown> {
|
|
653
|
+
const sent = await this._send(method, path, opts, "rest", _requestMeta)
|
|
654
|
+
return this._finish(sent, method, true)
|
|
412
655
|
}
|
|
413
656
|
|
|
414
|
-
/** Safe mode — returns { data, error, response, status }
|
|
657
|
+
/** Safe mode — returns { data, error, response, status }; `error` is truthy for every non-2xx */
|
|
415
658
|
async requestSafe(
|
|
416
659
|
method: string,
|
|
417
660
|
path: string,
|
|
@@ -423,26 +666,27 @@ export class HTTPClient {
|
|
|
423
666
|
response: Response
|
|
424
667
|
status: number
|
|
425
668
|
}> {
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
response: preserved,
|
|
435
|
-
status: response.status,
|
|
436
|
-
}
|
|
437
|
-
}
|
|
669
|
+
const sent = await this._send(method, path, opts, "rest", _requestMeta)
|
|
670
|
+
return this._finish(sent, method, false) as Promise<{
|
|
671
|
+
data: unknown
|
|
672
|
+
error: unknown
|
|
673
|
+
response: Response
|
|
674
|
+
status: number
|
|
675
|
+
}>
|
|
676
|
+
}
|
|
438
677
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
678
|
+
/**
|
|
679
|
+
* One request whose shape is decided by the response: an event stream becomes an
|
|
680
|
+
* `AsyncIterable<SSEEvent>` (never buffered, exempt from the timeout once headers arrive);
|
|
681
|
+
* anything else is parsed as in `request` / `requestSafe`.
|
|
682
|
+
*/
|
|
683
|
+
async requestAuto(method: string, path: string, opts: RequestOptions, shouldThrow: boolean): Promise<unknown> {
|
|
684
|
+
const sent = await this._send(method, path, opts, "auto")
|
|
685
|
+
if (sent.response.ok && isEventStream(sent.response)) {
|
|
686
|
+
sent.exemptFromTimeout()
|
|
687
|
+
return this._iterate(sent)
|
|
444
688
|
}
|
|
445
|
-
return
|
|
689
|
+
return this._finish(sent, method, shouldThrow)
|
|
446
690
|
}
|
|
447
691
|
|
|
448
692
|
requestStream(method: string, path: string, opts: RequestOptions): AsyncIterable<SSEEvent> {
|
|
@@ -456,46 +700,23 @@ export class HTTPClient {
|
|
|
456
700
|
path: string,
|
|
457
701
|
opts: RequestOptions,
|
|
458
702
|
): AsyncGenerator<SSEEvent, void, undefined> {
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/* no timeout on streams — they're long-lived, but user abort still composes */
|
|
468
|
-
const { signal, cleanup } = buildSignal(this._config, opts, true)
|
|
469
|
-
const init: RequestInit = { headers, method, signal }
|
|
470
|
-
|
|
471
|
-
if (this._config.credentials) init.credentials = this._config.credentials
|
|
472
|
-
if (this._config.mode) init.mode = this._config.mode
|
|
473
|
-
|
|
474
|
-
try {
|
|
475
|
-
signal?.throwIfAborted()
|
|
476
|
-
const response = await this._fetch(url, init)
|
|
477
|
-
|
|
478
|
-
if (!response.ok) {
|
|
479
|
-
throw await parseErrorAsClientError(response)
|
|
703
|
+
const sent = await this._send(method, path, opts, "stream")
|
|
704
|
+
if (!sent.response.ok) {
|
|
705
|
+
try {
|
|
706
|
+
throw await errorFromResponse(sent.response)
|
|
707
|
+
} finally {
|
|
708
|
+
sent.done()
|
|
480
709
|
}
|
|
481
|
-
|
|
482
|
-
if (!response.body) return
|
|
483
|
-
|
|
484
|
-
yield* parseSSEStream(response.body)
|
|
485
|
-
} finally {
|
|
486
|
-
cleanup()
|
|
487
710
|
}
|
|
711
|
+
yield* this._iterate(sent)
|
|
488
712
|
}
|
|
489
713
|
|
|
490
714
|
buildUrl(path: string, opts: RequestOptions): string {
|
|
491
|
-
return
|
|
715
|
+
return this._url(path, opts).toString()
|
|
492
716
|
}
|
|
493
717
|
|
|
494
718
|
buildPath(path: string, opts: RequestOptions): string {
|
|
495
|
-
|
|
496
|
-
if (opts.params) {
|
|
497
|
-
resolved = interpolatePath(path, opts.params)
|
|
498
|
-
}
|
|
719
|
+
const resolved = opts.params ? interpolatePath(path, opts.params) : interpolatePath(path)
|
|
499
720
|
|
|
500
721
|
if (!opts.search) return resolved
|
|
501
722
|
|
|
@@ -508,7 +729,9 @@ export class HTTPClient {
|
|
|
508
729
|
}
|
|
509
730
|
|
|
510
731
|
buildWSUrl(path: string, opts: RequestOptions): string {
|
|
511
|
-
const url =
|
|
512
|
-
|
|
732
|
+
const url = this._url(path, opts)
|
|
733
|
+
if (url.protocol === "https:") url.protocol = "wss:"
|
|
734
|
+
else if (url.protocol === "http:") url.protocol = "ws:"
|
|
735
|
+
return url.toString()
|
|
513
736
|
}
|
|
514
737
|
}
|