@lovrozagar/honey 0.6.5 → 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 +410 -149
- 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 +1893 -1761
- 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 +271 -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/codegen-go.ts
CHANGED
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
/* Go SDK emitter — generates a complete Go module from an OpenAPI 3.1 spec.
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
*
|
|
3
|
+
* Input is the SDK request model (codegen-sdk-model.ts), never the raw spec. Every identifier
|
|
4
|
+
* comes from a NameScope seeded with the runtime's own package-level names, and every spec string
|
|
5
|
+
* lands in source through goString / goComment (codegen-lang.ts).
|
|
6
|
+
*
|
|
7
|
+
* Static runtime files (sse.go, ws.go, etc.) are read from ./client-go/ at module load time and
|
|
8
|
+
* copied verbatim into the output file map.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
import { spawnSync } from "node:child_process"
|
|
8
12
|
import { readFileSync } from "node:fs"
|
|
9
13
|
import { fileURLToPath } from "node:url"
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
+
import type { OpenApiSpecInput } from "./codegen.ts"
|
|
15
|
+
import { serviceMapOf } from "./codegen.ts"
|
|
16
|
+
import { methodsOf, namespacesOf, specAuth } from "./codegen-ir.ts"
|
|
17
|
+
import type { IRAuth, IRInfo, IRNamespace, IRSchema } from "./codegen-ir.ts"
|
|
18
|
+
import {
|
|
19
|
+
GO_KEYWORDS,
|
|
20
|
+
GO_PREDECLARED,
|
|
21
|
+
NameScope,
|
|
22
|
+
cmpCodeUnit,
|
|
23
|
+
goComment,
|
|
24
|
+
goExported,
|
|
25
|
+
goLocal,
|
|
26
|
+
goString,
|
|
27
|
+
} from "./codegen-lang.ts"
|
|
28
|
+
import { buildSdkModel, valueCycleSchemas } from "./codegen-sdk-model.ts"
|
|
29
|
+
import type { SdkModel, SdkOp } from "./codegen-sdk-model.ts"
|
|
30
|
+
import { irRenderTopLevel, irRenderUse } from "./go-type-emitter.ts"
|
|
31
|
+
import type { GoTypeNames, RenderUseCtx } from "./go-type-emitter.ts"
|
|
14
32
|
|
|
15
33
|
/* ── types ── */
|
|
16
34
|
|
|
@@ -30,35 +48,29 @@ export type GeneratedGoSDK = {
|
|
|
30
48
|
serviceMap: Record<string, Record<string, unknown>>
|
|
31
49
|
}
|
|
32
50
|
|
|
33
|
-
type OpenApiSpec = {
|
|
34
|
-
components?: {
|
|
35
|
-
schemas?: Record<string, Record<string, unknown>>
|
|
36
|
-
}
|
|
37
|
-
info: { description?: string; title: string; version: string }
|
|
38
|
-
openapi: string
|
|
39
|
-
paths: Record<string, Record<string, Record<string, unknown>>>
|
|
40
|
-
}
|
|
41
|
-
|
|
42
51
|
/* ── runtime template loader ── */
|
|
43
52
|
|
|
44
53
|
let runtimeCache: Map<string, string> | null = null
|
|
45
54
|
|
|
55
|
+
const RUNTIME_FILES = [
|
|
56
|
+
"result.go",
|
|
57
|
+
"errors.go",
|
|
58
|
+
"runtime.go",
|
|
59
|
+
"invalidation.go",
|
|
60
|
+
"sse.go",
|
|
61
|
+
"ws.go",
|
|
62
|
+
"transport.go",
|
|
63
|
+
"realtime.go",
|
|
64
|
+
"export.go",
|
|
65
|
+
"nullable.go",
|
|
66
|
+
]
|
|
67
|
+
|
|
46
68
|
/** Reads static .go runtime files from ./client-go/ and caches them. */
|
|
47
69
|
export function loadGoRuntimeTemplates(): Map<string, string> {
|
|
48
70
|
if (runtimeCache) return runtimeCache
|
|
49
71
|
|
|
50
|
-
const names = [
|
|
51
|
-
"result.go",
|
|
52
|
-
"errors.go",
|
|
53
|
-
"runtime.go",
|
|
54
|
-
"invalidation.go",
|
|
55
|
-
"sse.go",
|
|
56
|
-
"ws.go",
|
|
57
|
-
"transport.go",
|
|
58
|
-
"realtime.go",
|
|
59
|
-
]
|
|
60
72
|
const cache = new Map<string, string>()
|
|
61
|
-
for (const name of
|
|
73
|
+
for (const name of RUNTIME_FILES) {
|
|
62
74
|
const filePath = fileURLToPath(new URL(`./client-go/${name}`, import.meta.url))
|
|
63
75
|
let content: string
|
|
64
76
|
try {
|
|
@@ -72,17 +84,42 @@ export function loadGoRuntimeTemplates(): Map<string, string> {
|
|
|
72
84
|
return cache
|
|
73
85
|
}
|
|
74
86
|
|
|
87
|
+
let runtimeIdentCache: Set<string> | null = null
|
|
88
|
+
|
|
89
|
+
/** Every package-level identifier the runtime files declare; generated names must avoid them all. */
|
|
90
|
+
export function goRuntimeIdentifiers(): Set<string> {
|
|
91
|
+
if (runtimeIdentCache) return runtimeIdentCache
|
|
92
|
+
const out = new Set<string>()
|
|
93
|
+
for (const src of loadGoRuntimeTemplates().values()) {
|
|
94
|
+
let inBlock = false
|
|
95
|
+
for (const line of src.split("\n")) {
|
|
96
|
+
const top = /^(?:func|type|var|const)\s+([A-Za-z_]\w*)/.exec(line)
|
|
97
|
+
if (top) out.add(top[1])
|
|
98
|
+
if (/^(?:var|const|type)\s*\($/.test(line)) {
|
|
99
|
+
inBlock = true
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
if (inBlock) {
|
|
103
|
+
if (line.startsWith(")")) inBlock = false
|
|
104
|
+
else {
|
|
105
|
+
const m = /^\t([A-Za-z_]\w*)/.exec(line)
|
|
106
|
+
if (m) out.add(m[1])
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
runtimeIdentCache = out
|
|
112
|
+
return out
|
|
113
|
+
}
|
|
114
|
+
|
|
75
115
|
/* ── helpers ── */
|
|
76
116
|
|
|
117
|
+
/** @deprecated kept for callers outside this file; use goLocal / goExported. */
|
|
77
118
|
export function safeResourceName(name: string): string {
|
|
78
119
|
const lower = name.toLowerCase()
|
|
79
120
|
return GO_KEYWORDS.has(lower) ? `${name}_` : name
|
|
80
121
|
}
|
|
81
122
|
|
|
82
|
-
function nestedStructName(path: string[]): string {
|
|
83
|
-
return `${path.map(goPascal).join("")}Resource`
|
|
84
|
-
}
|
|
85
|
-
|
|
86
123
|
/**
|
|
87
124
|
* Derive auth header name + prefix from OpenAPI securitySchemes.
|
|
88
125
|
* Defaults to Authorization + "Bearer " when no scheme is declared, preserving
|
|
@@ -90,222 +127,98 @@ function nestedStructName(path: string[]): string {
|
|
|
90
127
|
* embeds a "Format: <Prefix> {token}" hint (common pattern for APIs like Anyrow
|
|
91
128
|
* that use `Authorization: ApiKey <token>`).
|
|
92
129
|
*/
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const keys = Object.keys(schemes).sort()
|
|
97
|
-
if (keys.length === 0) return { headerName: "Authorization", prefix: "Bearer " }
|
|
98
|
-
const scheme = schemes[keys[0]]
|
|
99
|
-
const type = String(scheme.type ?? "")
|
|
100
|
-
if (type === "http") {
|
|
101
|
-
const httpScheme = String(scheme.scheme ?? "").toLowerCase()
|
|
102
|
-
if (httpScheme === "basic") return { headerName: "Authorization", prefix: "Basic " }
|
|
103
|
-
return { headerName: "Authorization", prefix: "Bearer " }
|
|
104
|
-
}
|
|
105
|
-
if (type === "apiKey" && scheme.in === "header") {
|
|
106
|
-
const headerName = String(scheme.name ?? "Authorization")
|
|
107
|
-
const desc = String(scheme.description ?? "")
|
|
108
|
-
const match = /Format:\s*(\S+)\s+\{/i.exec(desc)
|
|
109
|
-
if (match) return { headerName, prefix: `${match[1]} ` }
|
|
110
|
-
return { headerName, prefix: "" }
|
|
111
|
-
}
|
|
112
|
-
return { headerName: "Authorization", prefix: "Bearer " }
|
|
130
|
+
/** The auth header and prefix the spec's first security scheme asks for (see `IR.auth`). */
|
|
131
|
+
export function detectAuthScheme(spec: Record<string, unknown>): IRAuth {
|
|
132
|
+
return specAuth(spec as unknown as OpenApiSpecInput)
|
|
113
133
|
}
|
|
114
134
|
|
|
115
135
|
function goImportBlock(stdlib: string[], thirdParty: string[]): string {
|
|
116
136
|
const l: string[] = []
|
|
117
137
|
l.push(`import (`)
|
|
118
|
-
for (const p of [...stdlib].sort()) l.push(`\t"${p}"`)
|
|
138
|
+
for (const p of [...new Set(stdlib)].sort(cmpCodeUnit)) l.push(`\t"${p}"`)
|
|
119
139
|
if (thirdParty.length > 0) {
|
|
120
140
|
l.push(``)
|
|
121
|
-
for (const p of [...thirdParty].sort()) l.push(`\t"${p}"`)
|
|
141
|
+
for (const p of [...thirdParty].sort(cmpCodeUnit)) l.push(`\t"${p}"`)
|
|
122
142
|
}
|
|
123
143
|
l.push(`)`)
|
|
124
144
|
return l.join("\n")
|
|
125
145
|
}
|
|
126
146
|
|
|
127
|
-
|
|
128
|
-
function pathToGoExpr(path: string): string {
|
|
129
|
-
const params = extractOpenApiPathParams(path)
|
|
130
|
-
if (params.length === 0) return JSON.stringify(path)
|
|
131
|
-
let expr = JSON.stringify(path)
|
|
132
|
-
for (const p of params) {
|
|
133
|
-
expr = expr.replace(`{${p}}`, `" + url.PathEscape(${p}) + "`)
|
|
134
|
-
}
|
|
135
|
-
/* clean up empty string concatenation at edges */
|
|
136
|
-
expr = expr.replace(/"" \+ /g, "").replace(/ \+ ""/g, "")
|
|
137
|
-
return expr
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** Extracts the Go response type for an operation from its 2xx responses. */
|
|
141
|
-
function goResponseType(op: Record<string, unknown>): string {
|
|
142
|
-
const responses = op.responses as Record<string, Record<string, unknown>> | undefined
|
|
143
|
-
if (!responses) return "json.RawMessage"
|
|
144
|
-
|
|
145
|
-
for (const [status, response] of Object.entries(responses)) {
|
|
146
|
-
const code = Number.parseInt(status, 10)
|
|
147
|
-
if (Number.isNaN(code) || code < 200 || code >= 300) continue
|
|
148
|
-
if (code === 204) return ""
|
|
149
|
-
const content = response.content as Record<string, Record<string, unknown>> | undefined
|
|
150
|
-
if (!content) return ""
|
|
151
|
-
const jsonSchema = content["application/json"]?.schema as Record<string, unknown> | undefined
|
|
152
|
-
if (!jsonSchema) return "json.RawMessage"
|
|
153
|
-
if (jsonSchema.$ref) {
|
|
154
|
-
return goPascal((jsonSchema.$ref as string).split("/").pop() ?? "")
|
|
155
|
-
}
|
|
156
|
-
if (jsonSchema.type === "array" && jsonSchema.items) {
|
|
157
|
-
const items = jsonSchema.items as Record<string, unknown>
|
|
158
|
-
if (items.$ref) {
|
|
159
|
-
return `[]${goPascal((items.$ref as string).split("/").pop() ?? "")}`
|
|
160
|
-
}
|
|
161
|
-
return `[]${renderUse(items, { decls: new Map(), fieldName: "item", parentName: "Body" })}`
|
|
162
|
-
}
|
|
163
|
-
if (jsonSchema.type === "string") return "string"
|
|
164
|
-
if (jsonSchema.type === "integer") return "int64"
|
|
165
|
-
if (jsonSchema.type === "number") return "float64"
|
|
166
|
-
if (jsonSchema.type === "boolean") return "bool"
|
|
167
|
-
return "json.RawMessage"
|
|
168
|
-
}
|
|
169
|
-
return "json.RawMessage"
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** Extracts the Go input type (request body schema name) for an operation. */
|
|
173
|
-
function goBodyType(op: Record<string, unknown>): string | null {
|
|
174
|
-
const requestBody = op.requestBody as Record<string, unknown> | undefined
|
|
175
|
-
if (!requestBody) return null
|
|
176
|
-
const content = requestBody.content as Record<string, Record<string, unknown>> | undefined
|
|
177
|
-
if (!content) return null
|
|
178
|
-
const jsonContent = content["application/json"]
|
|
179
|
-
if (!jsonContent?.schema) return null
|
|
180
|
-
const schema = jsonContent.schema as Record<string, unknown>
|
|
181
|
-
if (schema.$ref) {
|
|
182
|
-
return goPascal((schema.$ref as string).split("/").pop() ?? "")
|
|
183
|
-
}
|
|
184
|
-
return "json.RawMessage"
|
|
185
|
-
}
|
|
147
|
+
/* ── naming ── */
|
|
186
148
|
|
|
187
|
-
/**
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
149
|
+
/** Package-level names for one generated SDK. */
|
|
150
|
+
export type GoNames = {
|
|
151
|
+
scope: NameScope
|
|
152
|
+
types: GoTypeNames
|
|
153
|
+
hoistedDecls: Map<string, string>
|
|
192
154
|
}
|
|
193
155
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
156
|
+
export function buildGoNames(model: SdkModel): GoNames {
|
|
157
|
+
const scope = new NameScope([...goRuntimeIdentifiers(), ...GO_PREDECLARED, "Client", "NewClient", "serviceEntry"])
|
|
158
|
+
const schemaNames = new Map<string, string>()
|
|
159
|
+
/* schemas first: a schema keeps its own name unless the runtime owns it */
|
|
160
|
+
for (const name of model.schemaNames) {
|
|
161
|
+
const base = goExported(name)
|
|
162
|
+
schemaNames.set(name, scope.has(base) ? scope.claim(`${base}Model`) : scope.claim(base))
|
|
163
|
+
}
|
|
164
|
+
const hoistKeys = new Map<string, string>()
|
|
165
|
+
const types: GoTypeNames = {
|
|
166
|
+
claimConst: (base) => scope.claim(base),
|
|
167
|
+
cyclic: valueCycleSchemas(model.ir.schemas),
|
|
168
|
+
hoist: (key, base) => {
|
|
169
|
+
const hit = hoistKeys.get(key)
|
|
170
|
+
if (hit) return hit
|
|
171
|
+
const name = scope.claim(base)
|
|
172
|
+
hoistKeys.set(key, name)
|
|
173
|
+
return name
|
|
174
|
+
},
|
|
175
|
+
ref: (name) => schemaNames.get(name) ?? goExported(name),
|
|
176
|
+
resolve: model.resolve,
|
|
177
|
+
}
|
|
178
|
+
return { hoistedDecls: new Map(), scope, types }
|
|
198
179
|
}
|
|
199
180
|
|
|
200
|
-
|
|
201
|
-
* Produces a Go expression that converts a query-param value to `string`,
|
|
202
|
-
* suitable as the 2nd argument of `url.Values.Set`. Nullable fields should
|
|
203
|
-
* already be dereferenced by the caller — `varExpr` is the concrete value.
|
|
204
|
-
*/
|
|
205
|
-
function qpStringExpr(varExpr: string, schema: Record<string, unknown>): string {
|
|
206
|
-
if (isStringEnum(schema)) return `string(${varExpr})`
|
|
207
|
-
const t = schema.type
|
|
208
|
-
if (t === "integer") return `strconv.FormatInt(${varExpr}, 10)`
|
|
209
|
-
if (t === "number") return `strconv.FormatFloat(${varExpr}, 'f', -1, 64)`
|
|
210
|
-
if (t === "boolean") return `strconv.FormatBool(${varExpr})`
|
|
211
|
-
return varExpr
|
|
212
|
-
}
|
|
181
|
+
/* ── buildGoTypes ── */
|
|
213
182
|
|
|
214
|
-
function
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
183
|
+
function buildGoTypes(model: SdkModel, names: GoNames): string {
|
|
184
|
+
const body: string[] = []
|
|
185
|
+
const decls = names.hoistedDecls
|
|
186
|
+
for (const name of model.schemaNames) {
|
|
187
|
+
const ir = model.ir.schemas[name]
|
|
188
|
+
const goName = names.types.ref(name)
|
|
189
|
+
const description = Object.hasOwn(model.ir.schemaDescriptions, name) ? model.ir.schemaDescriptions[name] : ""
|
|
190
|
+
if (description) body.push(...goComment(description))
|
|
191
|
+
body.push(irRenderTopLevel(goName, ir, decls, undefined, names.types))
|
|
192
|
+
body.push(``)
|
|
220
193
|
}
|
|
221
|
-
return
|
|
194
|
+
return body.join("\n")
|
|
222
195
|
}
|
|
223
196
|
|
|
224
|
-
function
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}))
|
|
234
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
197
|
+
function hoistedDeclsSource(decls: Map<string, string>): string {
|
|
198
|
+
const out: string[] = []
|
|
199
|
+
for (const name of [...decls.keys()].sort(cmpCodeUnit)) {
|
|
200
|
+
const decl = decls.get(name)
|
|
201
|
+
if (!decl) continue
|
|
202
|
+
out.push(decl)
|
|
203
|
+
out.push(``)
|
|
204
|
+
}
|
|
205
|
+
return out.join("\n")
|
|
235
206
|
}
|
|
236
207
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const GO_RUNTIME_TYPES = new Set([
|
|
241
|
-
"APIError",
|
|
242
|
-
"BadGatewayError",
|
|
243
|
-
"BadRequestError",
|
|
244
|
-
"Config",
|
|
245
|
-
"ConflictError",
|
|
246
|
-
"ConnectionState",
|
|
247
|
-
"ForbiddenError",
|
|
248
|
-
"GatewayTimeoutError",
|
|
249
|
-
"InternalServerError",
|
|
250
|
-
"InvalidationConfig",
|
|
251
|
-
"LongpollTransport",
|
|
252
|
-
"NotFoundError",
|
|
253
|
-
"RateLimitError",
|
|
254
|
-
"RequestMeta",
|
|
255
|
-
"ResumableConnection",
|
|
256
|
-
"SDKResult",
|
|
257
|
-
"SSEEvent",
|
|
258
|
-
"ServiceUnavailableError",
|
|
259
|
-
"SseTransport",
|
|
260
|
-
"StaleTracker",
|
|
261
|
-
"StatusError",
|
|
262
|
-
"StatusErrorMeta",
|
|
263
|
-
"Transport",
|
|
264
|
-
"TransportConn",
|
|
265
|
-
"TransportKind",
|
|
266
|
-
"TransportOpts",
|
|
267
|
-
"TypedWebSocket",
|
|
268
|
-
"UnauthorizedError",
|
|
269
|
-
"UnprocessableEntityError",
|
|
270
|
-
"WsTransport",
|
|
271
|
-
])
|
|
272
|
-
|
|
273
|
-
function buildGoTypes(spec: OpenApiSpec): string {
|
|
274
|
-
const schemas = spec.components?.schemas ?? {}
|
|
208
|
+
/** types.go: component schemas, then every hoisted declaration (call after the client is planned). */
|
|
209
|
+
function typesFile(schemaTypes: string, names: GoNames): string {
|
|
210
|
+
const body = `${schemaTypes}\n${hoistedDeclsSource(names.hoistedDecls)}`
|
|
275
211
|
const l: string[] = []
|
|
276
212
|
/* blank line after generator header detaches it from package-doc on pkg.go.dev */
|
|
277
213
|
l.push(`// Code generated by honey. DO NOT EDIT.`)
|
|
278
214
|
l.push(``)
|
|
279
215
|
l.push(`package sdk`)
|
|
280
216
|
l.push(``)
|
|
281
|
-
|
|
282
|
-
const sortedSchemas = Object.entries(schemas)
|
|
283
|
-
.filter(([name]) => !GO_RUNTIME_TYPES.has(name))
|
|
284
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
285
|
-
const body: string[] = []
|
|
286
|
-
const decls = new Map<string, string>()
|
|
287
|
-
for (const [name, schema] of sortedSchemas) {
|
|
288
|
-
const goType = renderTopLevel(name, schema, decls)
|
|
289
|
-
body.push(goType)
|
|
290
|
-
body.push(``)
|
|
291
|
-
}
|
|
292
|
-
/* append hoisted enum decls (deduped by name) after top-level types */
|
|
293
|
-
const hoistedNames = [...decls.keys()].sort((a, b) => a.localeCompare(b))
|
|
294
|
-
for (const hname of hoistedNames) {
|
|
295
|
-
body.push(decls.get(hname) as string)
|
|
296
|
-
body.push(``)
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/* emit encoding/json import only when generated type bodies actually use it
|
|
300
|
-
* (json.RawMessage, json.Unmarshal, etc). Keeps types.go compilable whether
|
|
301
|
-
* schemas produced additionalProperties helpers or not. */
|
|
302
|
-
const bodyStr = body.join("\n")
|
|
303
|
-
if (bodyStr.includes("json.RawMessage") || bodyStr.includes("json.Unmarshal") || bodyStr.includes("json.Marshal")) {
|
|
217
|
+
if (/\bjson\./.test(body)) {
|
|
304
218
|
l.push(`import "encoding/json"`)
|
|
305
219
|
l.push(``)
|
|
306
220
|
}
|
|
307
|
-
l.push(
|
|
308
|
-
|
|
221
|
+
l.push(body)
|
|
309
222
|
return l.join("\n")
|
|
310
223
|
}
|
|
311
224
|
|
|
@@ -313,15 +226,14 @@ function buildGoTypes(spec: OpenApiSpec): string {
|
|
|
313
226
|
|
|
314
227
|
function buildGoMod(modulePath?: string): string {
|
|
315
228
|
const mod = modulePath ?? "example.com/sdk"
|
|
316
|
-
return [`module ${mod}`, ``, `go 1.
|
|
229
|
+
return [`module ${mod}`, ``, `go 1.24`, ``, `require nhooyr.io/websocket v1.8.17`, ``].join("\n")
|
|
317
230
|
}
|
|
318
231
|
|
|
319
232
|
/* ── buildGoDoc ── */
|
|
320
233
|
|
|
321
|
-
function buildGoDoc(
|
|
322
|
-
const title =
|
|
323
|
-
const desc =
|
|
324
|
-
const descSuffix = desc ? ` ${desc}` : ""
|
|
234
|
+
function buildGoDoc(info: IRInfo): string {
|
|
235
|
+
const title = info.title ?? "API"
|
|
236
|
+
const desc = info.description ?? ""
|
|
325
237
|
/* Generator directive on its own line, separated from package-doc by blank
|
|
326
238
|
* line so pkg.go.dev does not aggregate "Code generated..." into Overview.
|
|
327
239
|
* The `// Package sdk ...` line IS attached to `package sdk` (no blank line
|
|
@@ -329,7 +241,7 @@ function buildGoDoc(spec: OpenApiSpec): string {
|
|
|
329
241
|
const l: string[] = []
|
|
330
242
|
l.push(`// Code generated by honey. DO NOT EDIT.`)
|
|
331
243
|
l.push(``)
|
|
332
|
-
l.push(
|
|
244
|
+
l.push(...goComment(`Package sdk is an auto-generated client for ${title}.${desc ? ` ${desc}` : ""}`))
|
|
333
245
|
l.push(`package sdk`)
|
|
334
246
|
l.push(``)
|
|
335
247
|
return l.join("\n")
|
|
@@ -337,471 +249,389 @@ function buildGoDoc(spec: OpenApiSpec): string {
|
|
|
337
249
|
|
|
338
250
|
/* ── buildGoClient ── */
|
|
339
251
|
|
|
340
|
-
type
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
pathParams: string[]
|
|
361
|
-
queryParams: QueryParam[]
|
|
362
|
-
responseType: string
|
|
363
|
-
summary: string
|
|
364
|
-
/* Override for the Pascal-cased method/opts name. Set when a top-level
|
|
365
|
-
* operation's resource name collides with an existing resource bucket,
|
|
366
|
-
* so the emitted `Client` doesn't have a same-named field + method. */
|
|
367
|
-
methodNameOverride?: string
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/** Build a MethodInfo from an IROperation + the raw spec op object. */
|
|
371
|
-
function methodInfoFromIROp(irOp: IROperation, rawOp: Record<string, unknown>): MethodInfo {
|
|
372
|
-
const segments = irOp.id.split(".")
|
|
373
|
-
const isTopLevel = segments.length === 1
|
|
374
|
-
const resource = segments[0] ?? irOp.id
|
|
375
|
-
const action = isTopLevel ? "_call" : segments.slice(1).join(".")
|
|
376
|
-
const namePath = segments.slice(0, -1)
|
|
377
|
-
const methodAction = segments[segments.length - 1] ?? irOp.id
|
|
378
|
-
const isRealtime = rawOp["x-realtime"] === true
|
|
379
|
-
return {
|
|
380
|
-
action,
|
|
381
|
-
bodyIsStream: hasOctetStreamBody(rawOp),
|
|
382
|
-
bodyType: goBodyType(rawOp),
|
|
383
|
-
description: String(rawOp.description ?? ""),
|
|
384
|
-
httpMethod: irOp.method,
|
|
385
|
-
isIdempotent: rawOp["x-idempotency-key"] === true,
|
|
386
|
-
isRealtime,
|
|
387
|
-
isSSE: !isRealtime && isSSEOperation(rawOp),
|
|
388
|
-
isWS: !isRealtime && rawOp["x-websocket"] === true,
|
|
389
|
-
methodAction,
|
|
390
|
-
namePath,
|
|
391
|
-
op: rawOp,
|
|
392
|
-
opId: irOp.id,
|
|
393
|
-
path: irOp.path,
|
|
394
|
-
pathParams: irOp.params.path.map((p) => p.name),
|
|
395
|
-
queryParams: getQueryParams(rawOp),
|
|
396
|
-
resource,
|
|
397
|
-
responseType: goResponseType(rawOp),
|
|
398
|
-
summary: String(rawOp.summary ?? ""),
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/** Walk the IR tree and collect all MethodInfos, pairing with raw spec ops. */
|
|
403
|
-
function collectMethodsFromIR(ns: IRNamespace, spec: OpenApiSpec): MethodInfo[] {
|
|
404
|
-
const rawOpById = new Map<string, Record<string, unknown>>()
|
|
405
|
-
for (const [, pathMethods] of Object.entries(spec.paths ?? {})) {
|
|
406
|
-
for (const [, operation] of Object.entries(pathMethods)) {
|
|
407
|
-
const op = operation as Record<string, unknown>
|
|
408
|
-
const id = op.operationId as string | undefined
|
|
409
|
-
if (id) rawOpById.set(id, op)
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
const results: MethodInfo[] = []
|
|
414
|
-
|
|
415
|
-
function walk(cur: IRNamespace): void {
|
|
416
|
-
for (const [, entry] of methodsOf(cur)) {
|
|
417
|
-
const raw = rawOpById.get(entry.id) ?? {}
|
|
418
|
-
results.push(methodInfoFromIROp(entry, raw))
|
|
419
|
-
}
|
|
420
|
-
for (const [, childNs] of namespacesOf(cur)) {
|
|
421
|
-
walk(childNs)
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
walk(ns)
|
|
426
|
-
return results
|
|
252
|
+
type GoOpPlan = {
|
|
253
|
+
op: SdkOp
|
|
254
|
+
methodName: string
|
|
255
|
+
optsName: string
|
|
256
|
+
/** opts struct field per query/header param */
|
|
257
|
+
optFields: Array<{ goName: string; type: string; param: { name: string }; where: "query" | "header" }>
|
|
258
|
+
/** local identifier per path param */
|
|
259
|
+
pathLocals: Array<{ local: string; name: string }>
|
|
260
|
+
bodyParam?: { local: string; type: string }
|
|
261
|
+
multipartName?: string
|
|
262
|
+
multipartFields?: Array<{
|
|
263
|
+
goName: string
|
|
264
|
+
type: string
|
|
265
|
+
key: string
|
|
266
|
+
file: boolean
|
|
267
|
+
list: boolean
|
|
268
|
+
required: boolean
|
|
269
|
+
}>
|
|
270
|
+
/** Go type returned on success, "" for none */
|
|
271
|
+
resultType: string
|
|
427
272
|
}
|
|
428
273
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
274
|
+
const OPTS_RESERVED = [
|
|
275
|
+
"Headers",
|
|
276
|
+
"IdempotencyKey",
|
|
277
|
+
"LastEventID",
|
|
278
|
+
"ReconnectToken",
|
|
279
|
+
"Protocols",
|
|
280
|
+
"MaxReconnectAttempts",
|
|
281
|
+
"ReconnectDelayMs",
|
|
282
|
+
]
|
|
283
|
+
|
|
284
|
+
/** Locals every emitted method body uses. Path params must not shadow them. */
|
|
285
|
+
const METHOD_LOCALS = [
|
|
286
|
+
"ctx",
|
|
287
|
+
"opts",
|
|
288
|
+
"q",
|
|
289
|
+
"path",
|
|
290
|
+
"err",
|
|
291
|
+
"result",
|
|
292
|
+
"out",
|
|
293
|
+
"resp",
|
|
294
|
+
"req",
|
|
295
|
+
"reqBody",
|
|
296
|
+
"callHeaders",
|
|
297
|
+
"pathParams",
|
|
298
|
+
"concretePath",
|
|
299
|
+
"selector",
|
|
300
|
+
"requestMeta",
|
|
301
|
+
"stale",
|
|
302
|
+
"cfg",
|
|
303
|
+
"yield",
|
|
304
|
+
"ev",
|
|
305
|
+
"conn",
|
|
306
|
+
"dialOpts",
|
|
307
|
+
"wsURL",
|
|
308
|
+
"transports",
|
|
309
|
+
"topts",
|
|
310
|
+
"parts",
|
|
311
|
+
"c",
|
|
312
|
+
"r",
|
|
313
|
+
]
|
|
314
|
+
|
|
315
|
+
function planOp(op: SdkOp, methodName: string, optsName: string, names: GoNames): GoOpPlan {
|
|
316
|
+
const plan: GoOpPlan = { methodName, op, optFields: [], optsName, pathLocals: [], resultType: "" }
|
|
317
|
+
|
|
318
|
+
const locals = new NameScope(METHOD_LOCALS)
|
|
319
|
+
const bodyLocal =
|
|
320
|
+
op.body && op.stream !== "ws" && op.stream !== "realtime"
|
|
321
|
+
? locals.claim(op.body.kind === "form" ? "form" : "body")
|
|
322
|
+
: ""
|
|
323
|
+
for (const p of op.pathParams) plan.pathLocals.push({ local: locals.claim(goLocal(p.name)), name: p.name })
|
|
324
|
+
|
|
325
|
+
if (op.stream !== "realtime") {
|
|
326
|
+
const fieldScope = new NameScope(OPTS_RESERVED)
|
|
327
|
+
const decls = names.hoistedDecls
|
|
328
|
+
const params: Array<[{ name: string; schema: IRSchema; required?: boolean }, "query" | "header"]> = [
|
|
329
|
+
...op.query.map((p): [typeof p, "query"] => [p, "query"]),
|
|
330
|
+
...op.headers.map((p): [typeof p, "header"] => [p, "header"]),
|
|
331
|
+
]
|
|
332
|
+
for (const [p, where] of params) {
|
|
333
|
+
if (op.stream === "sse" && where === "query" && /^last[-_]?event[-_]?id$/i.test(p.name)) continue
|
|
334
|
+
const goName = fieldScope.claim(goExported(p.name))
|
|
335
|
+
const ctx: RenderUseCtx = { decls, fieldName: goName, names: names.types, parentName: optsName }
|
|
336
|
+
const t = irRenderUse(p.schema, ctx)
|
|
337
|
+
const nilable = t.startsWith("*") || t.startsWith("[]") || t.startsWith("map[") || t === "json.RawMessage"
|
|
338
|
+
plan.optFields.push({ goName, param: p, type: p.required || nilable ? t : `*${t}`, where })
|
|
483
339
|
}
|
|
484
|
-
body.push(`\t},`)
|
|
485
340
|
}
|
|
486
|
-
body.push(`}`)
|
|
487
|
-
body.push(``)
|
|
488
341
|
|
|
489
|
-
|
|
490
|
-
body.
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
body.push(`\t${goPascal(safeSeg)} *${nestedStructName([...path, safeSeg])}`)
|
|
553
|
-
}
|
|
554
|
-
body.push(`}`)
|
|
555
|
-
body.push(``)
|
|
556
|
-
|
|
557
|
-
/* constructor helper: newXResource(c) sets client + child fields */
|
|
558
|
-
body.push(`func new${structName}(c *Client) *${structName} {`)
|
|
559
|
-
body.push(`\tr := &${structName}{client: c}`)
|
|
560
|
-
for (const [childSeg] of namespacesOf(ns)) {
|
|
561
|
-
const safeSeg = safeResourceName(childSeg)
|
|
562
|
-
body.push(`\tr.${goPascal(safeSeg)} = new${nestedStructName([...path, safeSeg])}(c)`)
|
|
563
|
-
}
|
|
564
|
-
body.push(`\treturn r`)
|
|
565
|
-
body.push(`}`)
|
|
566
|
-
body.push(``)
|
|
567
|
-
|
|
568
|
-
/* direct methods on this struct */
|
|
569
|
-
for (const [, op] of methodsOf(ns)) {
|
|
570
|
-
const raw =
|
|
571
|
-
methods.find((m) => m.opId === op.id) ??
|
|
572
|
-
(() => {
|
|
573
|
-
throw new Error(`missing MethodInfo for ${op.id}`)
|
|
574
|
-
})()
|
|
575
|
-
body.push(...emitMethod(raw, recvName, `*${structName}`, throwOnError))
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
/* recurse into child namespaces */
|
|
579
|
-
for (const [childSeg, childNs] of namespacesOf(ns)) {
|
|
580
|
-
emitResourceStructs(childNs, [...path, safeResourceName(childSeg)])
|
|
342
|
+
const body = op.body
|
|
343
|
+
if (body && op.stream !== "ws" && op.stream !== "realtime") {
|
|
344
|
+
const local = bodyLocal
|
|
345
|
+
switch (body.kind) {
|
|
346
|
+
case "json": {
|
|
347
|
+
const resolved = body.schema
|
|
348
|
+
let t: string
|
|
349
|
+
if (resolved.kind === "ref") t = names.types.ref(resolved.name)
|
|
350
|
+
else if (resolved.kind === "object" && resolved.fields.length > 0) {
|
|
351
|
+
const name = names.types.hoist(`${op.id}.body`, `${optsName.replace(/Opts$/, "")}Body`)
|
|
352
|
+
if (!names.hoistedDecls.has(name)) {
|
|
353
|
+
names.hoistedDecls.set(name, "")
|
|
354
|
+
names.hoistedDecls.set(name, irRenderTopLevel(name, resolved, names.hoistedDecls, undefined, names.types))
|
|
355
|
+
}
|
|
356
|
+
t = name
|
|
357
|
+
} else {
|
|
358
|
+
t = irRenderUse(resolved, {
|
|
359
|
+
decls: names.hoistedDecls,
|
|
360
|
+
fieldName: "Body",
|
|
361
|
+
names: names.types,
|
|
362
|
+
parentName: optsName.replace(/Opts$/, ""),
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
plan.bodyParam = { local, type: t }
|
|
366
|
+
break
|
|
367
|
+
}
|
|
368
|
+
case "form":
|
|
369
|
+
plan.bodyParam = { local, type: "url.Values" }
|
|
370
|
+
break
|
|
371
|
+
case "binary":
|
|
372
|
+
plan.bodyParam = { local, type: "io.Reader" }
|
|
373
|
+
break
|
|
374
|
+
case "text":
|
|
375
|
+
plan.bodyParam = { local, type: "string" }
|
|
376
|
+
break
|
|
377
|
+
case "multipart": {
|
|
378
|
+
const name = names.types.hoist(`${op.id}.multipart`, `${optsName.replace(/Opts$/, "")}Body`)
|
|
379
|
+
const fieldScope = new NameScope()
|
|
380
|
+
plan.multipartName = name
|
|
381
|
+
plan.multipartFields = body.parts.map((part) => {
|
|
382
|
+
const goName = fieldScope.claim(goExported(part.name))
|
|
383
|
+
const schema = part.schema ? names.types.resolve(part.schema) : { kind: "unknown" as const }
|
|
384
|
+
const list = schema.kind === "array"
|
|
385
|
+
if (part.type === "file") {
|
|
386
|
+
return { file: true, goName, key: part.name, list, required: true, type: list ? "[]FilePart" : "FilePart" }
|
|
387
|
+
}
|
|
388
|
+
const item = list && schema.kind === "array" ? schema.items : schema
|
|
389
|
+
let t = "string"
|
|
390
|
+
if (item.kind === "scalar") {
|
|
391
|
+
t =
|
|
392
|
+
item.type === "integer"
|
|
393
|
+
? "int64"
|
|
394
|
+
: item.type === "number"
|
|
395
|
+
? "float64"
|
|
396
|
+
: item.type === "boolean"
|
|
397
|
+
? "bool"
|
|
398
|
+
: "string"
|
|
399
|
+
}
|
|
400
|
+
return { file: false, goName, key: part.name, list, required: false, type: list ? `[]${t}` : `*${t}` }
|
|
401
|
+
})
|
|
402
|
+
plan.bodyParam = { local, type: name }
|
|
403
|
+
break
|
|
404
|
+
}
|
|
581
405
|
}
|
|
582
406
|
}
|
|
583
407
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
* without polluting generated code with dead suppressions. */
|
|
608
|
-
const suppressions: { line: string; symbol: string }[] = [
|
|
609
|
-
{ line: `var _ json.RawMessage`, symbol: "json." },
|
|
610
|
-
{ line: `var _ = fmt.Sprintf`, symbol: "fmt." },
|
|
611
|
-
{ line: `var _ = io.Discard`, symbol: "io." },
|
|
612
|
-
{ line: `var _ *url.URL`, symbol: "url." },
|
|
613
|
-
{ line: `var _ = strings.NewReader`, symbol: "strings." },
|
|
614
|
-
{ line: `var _ = sync.Mutex{}`, symbol: "sync." },
|
|
615
|
-
{ line: `var _ = time.Second`, symbol: "time." },
|
|
616
|
-
]
|
|
617
|
-
const suppressionLines = suppressions.filter((s) => !bodyStr.includes(s.symbol)).map((s) => s.line)
|
|
618
|
-
|
|
619
|
-
const out: string[] = []
|
|
620
|
-
/* blank line after generator header detaches it from package-doc on pkg.go.dev */
|
|
621
|
-
out.push(`// Code generated by honey. DO NOT EDIT.`)
|
|
622
|
-
out.push(``)
|
|
623
|
-
out.push(`package sdk`)
|
|
624
|
-
out.push(``)
|
|
625
|
-
out.push(goImportBlock([...new Set(stdlib)].sort(), thirdParty))
|
|
626
|
-
out.push(``)
|
|
627
|
-
if (suppressionLines.length > 0) {
|
|
628
|
-
out.push(...suppressionLines)
|
|
629
|
-
out.push(``)
|
|
408
|
+
if (op.stream === null) {
|
|
409
|
+
const s = op.success
|
|
410
|
+
if (s.kind === "json") {
|
|
411
|
+
if (!s.schema) plan.resultType = "json.RawMessage"
|
|
412
|
+
else if (s.schema.kind === "ref") plan.resultType = names.types.ref(s.schema.name)
|
|
413
|
+
else if (s.schema.kind === "object" && s.schema.fields.length > 0) {
|
|
414
|
+
const name = names.types.hoist(`${op.id}.response`, `${optsName.replace(/Opts$/, "")}Response`)
|
|
415
|
+
if (!names.hoistedDecls.has(name)) {
|
|
416
|
+
names.hoistedDecls.set(name, "")
|
|
417
|
+
names.hoistedDecls.set(name, irRenderTopLevel(name, s.schema, names.hoistedDecls, undefined, names.types))
|
|
418
|
+
}
|
|
419
|
+
plan.resultType = name
|
|
420
|
+
} else {
|
|
421
|
+
const t = irRenderUse(s.schema, {
|
|
422
|
+
decls: names.hoistedDecls,
|
|
423
|
+
fieldName: "Response",
|
|
424
|
+
names: names.types,
|
|
425
|
+
parentName: optsName.replace(/Opts$/, ""),
|
|
426
|
+
})
|
|
427
|
+
plan.resultType = t.startsWith("*") ? t.slice(1) : t
|
|
428
|
+
}
|
|
429
|
+
} else if (s.kind === "text") plan.resultType = "string"
|
|
430
|
+
else if (s.kind === "binary") plan.resultType = "[]byte"
|
|
630
431
|
}
|
|
631
|
-
|
|
632
|
-
return out.join("\n")
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
function optsStructName(m: MethodInfo): string {
|
|
636
|
-
if (m.methodNameOverride) return `${m.methodNameOverride}Opts`
|
|
637
|
-
/* For top-level single-segment ops: namePath=[], methodAction=opId → e.g. GetUserOpts
|
|
638
|
-
* For multi-segment: namePath=[checkout,sessions], methodAction=create → CheckoutSessionsCreateOpts */
|
|
639
|
-
const prefix = m.namePath.map(goPascal).join("")
|
|
640
|
-
const suffix = goPascal(m.methodAction)
|
|
641
|
-
return `${prefix}${suffix}Opts`
|
|
432
|
+
return plan
|
|
642
433
|
}
|
|
643
434
|
|
|
644
|
-
function emitOptsStruct(
|
|
435
|
+
function emitOptsStruct(plan: GoOpPlan): string[] {
|
|
645
436
|
const l: string[] = []
|
|
646
|
-
const optsName =
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
437
|
+
const { op, optsName } = plan
|
|
438
|
+
l.push(`// ${optsName} holds the optional inputs of ${plan.methodName}.`)
|
|
439
|
+
if (op.stream === "realtime") {
|
|
440
|
+
/* realtime ops get a fixed opts shape — transports receive
|
|
441
|
+
* ReconnectToken / LastEventID via TransportOpts. */
|
|
650
442
|
l.push(`type ${optsName} struct {`)
|
|
651
443
|
l.push(`\tReconnectToken string`)
|
|
652
444
|
l.push(`\tLastEventID string`)
|
|
653
445
|
l.push(`\tProtocols []string`)
|
|
446
|
+
l.push(`\t// MaxReconnectAttempts caps reconnects after a drop. Zero means 5; negative means unlimited.`)
|
|
654
447
|
l.push(`\tMaxReconnectAttempts int`)
|
|
448
|
+
l.push(`\t// ReconnectDelayMs is the first backoff delay. Zero means 1000.`)
|
|
655
449
|
l.push(`\tReconnectDelayMs int`)
|
|
656
450
|
l.push(`\tHeaders map[string]string`)
|
|
657
451
|
l.push(`}`)
|
|
658
452
|
l.push(``)
|
|
659
453
|
return l
|
|
660
454
|
}
|
|
661
|
-
const decls = new Map<string, string>()
|
|
662
|
-
const fields: string[] = []
|
|
663
|
-
for (const qp of m.queryParams) {
|
|
664
|
-
const fieldName = goPascal(qp.name)
|
|
665
|
-
const goType = renderUse(qp.schema, {
|
|
666
|
-
decls,
|
|
667
|
-
fieldName: qp.name,
|
|
668
|
-
parentName: optsName,
|
|
669
|
-
})
|
|
670
|
-
const ptr = qp.required ? goType : `*${goType}`
|
|
671
|
-
fields.push(`\t${fieldName} ${ptr}`)
|
|
672
|
-
}
|
|
673
|
-
/* hoisted query-param enums emit before the opts struct so refs resolve */
|
|
674
|
-
for (const decl of decls.values()) {
|
|
675
|
-
l.push(decl)
|
|
676
|
-
l.push(``)
|
|
677
|
-
}
|
|
678
455
|
l.push(`type ${optsName} struct {`)
|
|
679
|
-
l.push(
|
|
680
|
-
if (
|
|
681
|
-
|
|
682
|
-
}
|
|
683
|
-
if (m.isWS) {
|
|
456
|
+
for (const f of plan.optFields) l.push(`\t${f.goName} ${f.type}`)
|
|
457
|
+
if (op.stream === "sse") l.push(`\tLastEventID string`)
|
|
458
|
+
if (op.stream === "ws") {
|
|
684
459
|
l.push(`\tReconnectToken string`)
|
|
685
460
|
l.push(`\tProtocols []string`)
|
|
686
461
|
}
|
|
687
|
-
if (
|
|
688
|
-
l.push(`\tIdempotencyKey string`)
|
|
689
|
-
}
|
|
462
|
+
if (op.idempotent && op.stream === null) l.push(`\tIdempotencyKey string`)
|
|
690
463
|
l.push(`\tHeaders map[string]string`)
|
|
691
464
|
l.push(`}`)
|
|
692
465
|
l.push(``)
|
|
693
466
|
return l
|
|
694
467
|
}
|
|
695
468
|
|
|
696
|
-
function
|
|
469
|
+
function emitMultipartStruct(plan: GoOpPlan): string[] {
|
|
470
|
+
if (!plan.multipartName || !plan.multipartFields) return []
|
|
697
471
|
const l: string[] = []
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
472
|
+
l.push(`// ${plan.multipartName} is the multipart/form-data body of ${plan.methodName}.`)
|
|
473
|
+
l.push(`type ${plan.multipartName} struct {`)
|
|
474
|
+
for (const f of plan.multipartFields) l.push(`\t${f.goName} ${f.type}`)
|
|
475
|
+
l.push(`}`)
|
|
476
|
+
l.push(``)
|
|
477
|
+
return l
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function pathExpr(plan: GoOpPlan): string {
|
|
481
|
+
if (plan.pathLocals.length === 0) return ""
|
|
482
|
+
return `map[string]string{${plan.pathLocals.map((p) => `${goString(p.name)}: ${p.local}`).join(", ")}}`
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** Lines that compute `path` (and return on error through `fail`). */
|
|
486
|
+
function emitPath(plan: GoOpPlan, fail: string, indent: string): string[] {
|
|
487
|
+
const l: string[] = []
|
|
488
|
+
if (plan.pathLocals.length === 0) {
|
|
489
|
+
l.push(`${indent}path := ${goString(plan.op.path)}`)
|
|
490
|
+
return l
|
|
708
491
|
}
|
|
492
|
+
l.push(`${indent}path, err := expandPath(${goString(plan.op.path)}, ${pathExpr(plan)})`)
|
|
493
|
+
l.push(`${indent}if err != nil {`)
|
|
494
|
+
l.push(`${indent}\t${fail}`)
|
|
495
|
+
l.push(`${indent}}`)
|
|
496
|
+
return l
|
|
497
|
+
}
|
|
709
498
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
499
|
+
/** Lines that build `q` and `callHeaders` from opts. */
|
|
500
|
+
function emitQueryAndHeaders(plan: GoOpPlan, indent: string, baseHeaders: string): string[] {
|
|
501
|
+
const l: string[] = []
|
|
502
|
+
l.push(`${indent}var q queryList`)
|
|
503
|
+
l.push(`${indent}callHeaders := ${baseHeaders}`)
|
|
504
|
+
l.push(`${indent}if opts != nil {`)
|
|
505
|
+
for (const f of plan.optFields) {
|
|
506
|
+
if (f.where === "query") l.push(`${indent}\tq.set(${goString(f.param.name)}, opts.${f.goName})`)
|
|
507
|
+
}
|
|
508
|
+
l.push(`${indent}\tfor k, v := range opts.Headers {`)
|
|
509
|
+
l.push(`${indent}\t\tcallHeaders[k] = v`)
|
|
510
|
+
l.push(`${indent}\t}`)
|
|
511
|
+
for (const f of plan.optFields) {
|
|
512
|
+
if (f.where === "header")
|
|
513
|
+
l.push(`${indent}\tsetHeaderValue(callHeaders, ${goString(f.param.name)}, opts.${f.goName})`)
|
|
514
|
+
}
|
|
515
|
+
l.push(`${indent}}`)
|
|
516
|
+
return l
|
|
517
|
+
}
|
|
715
518
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
519
|
+
/** Expression for the request body, plus setup lines. */
|
|
520
|
+
function emitBody(plan: GoOpPlan, fail: string, indent: string): { lines: string[]; expr: string } {
|
|
521
|
+
const body = plan.op.body
|
|
522
|
+
const local = plan.bodyParam?.local
|
|
523
|
+
if (!body || !local) return { expr: "noBody()", lines: [] }
|
|
524
|
+
switch (body.kind) {
|
|
525
|
+
case "json":
|
|
526
|
+
return { expr: `jsonBody(${local})`, lines: [] }
|
|
527
|
+
case "form":
|
|
528
|
+
return { expr: `formBody(${local})`, lines: [] }
|
|
529
|
+
case "text":
|
|
530
|
+
return { expr: `rawBody([]byte(${local}), ${goString(body.contentType)})`, lines: [] }
|
|
531
|
+
case "binary":
|
|
532
|
+
return { expr: `readerBody(${local}, ${goString(body.contentType)})`, lines: [] }
|
|
533
|
+
case "multipart": {
|
|
534
|
+
const l: string[] = []
|
|
535
|
+
l.push(`${indent}var parts []multipartField`)
|
|
536
|
+
for (const f of plan.multipartFields ?? []) {
|
|
537
|
+
if (f.file) {
|
|
538
|
+
if (f.list) {
|
|
539
|
+
l.push(`${indent}for _, file := range ${local}.${f.goName} {`)
|
|
540
|
+
l.push(`${indent}\tparts = append(parts, fileField(${goString(f.key)}, file))`)
|
|
541
|
+
l.push(`${indent}}`)
|
|
542
|
+
} else {
|
|
543
|
+
l.push(`${indent}parts = append(parts, fileField(${goString(f.key)}, ${local}.${f.goName}))`)
|
|
544
|
+
}
|
|
545
|
+
} else if (f.list) {
|
|
546
|
+
l.push(`${indent}for _, v := range ${local}.${f.goName} {`)
|
|
547
|
+
l.push(`${indent}\tparts = append(parts, textField(${goString(f.key)}, v))`)
|
|
548
|
+
l.push(`${indent}}`)
|
|
549
|
+
} else {
|
|
550
|
+
l.push(`${indent}if ${local}.${f.goName} != nil {`)
|
|
551
|
+
l.push(`${indent}\tparts = append(parts, textField(${goString(f.key)}, *${local}.${f.goName}))`)
|
|
552
|
+
l.push(`${indent}}`)
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
l.push(`${indent}reqBody, err := multipartBody(parts)`)
|
|
556
|
+
l.push(`${indent}if err != nil {`)
|
|
557
|
+
l.push(`${indent}\t${fail}`)
|
|
558
|
+
l.push(`${indent}}`)
|
|
559
|
+
return { expr: "reqBody", lines: l }
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function emitDoc(plan: GoOpPlan): string[] {
|
|
565
|
+
const l: string[] = []
|
|
566
|
+
const { op } = plan
|
|
567
|
+
const text = [op.summary, op.description].filter((s) => s !== "").join("\n")
|
|
568
|
+
if (text) l.push(...goComment(`${plan.methodName} — ${text}`))
|
|
569
|
+
if (op.deprecated) {
|
|
570
|
+
if (text) l.push(`//`)
|
|
571
|
+
l.push(`// Deprecated: the API marks this operation deprecated.`)
|
|
732
572
|
}
|
|
573
|
+
return l
|
|
574
|
+
}
|
|
733
575
|
|
|
734
|
-
|
|
576
|
+
function emitMethod(plan: GoOpPlan, recv: string, recvType: string, throwOnError: boolean): string[] {
|
|
577
|
+
const l: string[] = []
|
|
578
|
+
const { op, optsName } = plan
|
|
579
|
+
const cfg = recvType === "*Client" ? `${recv}.cfg` : `${recv}.client.cfg`
|
|
580
|
+
const stale = recvType === "*Client" ? `${recv}.stale` : `${recv}.client.stale`
|
|
735
581
|
|
|
736
|
-
|
|
582
|
+
l.push(...emitDoc(plan))
|
|
737
583
|
|
|
738
|
-
|
|
739
|
-
const
|
|
740
|
-
|
|
584
|
+
const params: string[] = [`ctx context.Context`]
|
|
585
|
+
for (const p of plan.pathLocals) params.push(`${p.local} string`)
|
|
586
|
+
if (plan.bodyParam) params.push(`${plan.bodyParam.local} ${plan.bodyParam.type}`)
|
|
587
|
+
params.push(`opts *${optsName}`)
|
|
741
588
|
|
|
742
|
-
if (
|
|
743
|
-
|
|
744
|
-
/* ctx
|
|
745
|
-
* here — consumer binds a ctx at ResumableConnection.Connect(ctx) time. */
|
|
589
|
+
if (op.stream === "realtime") {
|
|
590
|
+
l.push(`func (${recv} ${recvType}) ${plan.methodName}(${params.join(", ")}) (*ResumableConnection, error) {`)
|
|
591
|
+
/* ctx bounds nothing here: ResumableConnection.Connect(ctx) takes its own */
|
|
746
592
|
l.push(`\t_ = ctx`)
|
|
747
|
-
l.push(
|
|
748
|
-
l.push(`\
|
|
593
|
+
l.push(...emitPath(plan, "return nil, err", "\t"))
|
|
594
|
+
l.push(`\trtURL, err := buildURL(${cfg}.BaseURL, path, nil)`)
|
|
595
|
+
l.push(`\tif err != nil {`)
|
|
596
|
+
l.push(`\t\treturn nil, err`)
|
|
597
|
+
l.push(`\t}`)
|
|
598
|
+
l.push(`\tvar extra map[string]string`)
|
|
599
|
+
l.push(`\ttopts := &TransportOpts{HTTPClient: streamClient(${cfg})}`)
|
|
749
600
|
l.push(`\tif opts != nil {`)
|
|
601
|
+
l.push(`\t\textra = opts.Headers`)
|
|
750
602
|
l.push(`\t\ttopts.ReconnectToken = opts.ReconnectToken`)
|
|
751
603
|
l.push(`\t\ttopts.LastEventID = opts.LastEventID`)
|
|
752
604
|
l.push(`\t\ttopts.Protocols = opts.Protocols`)
|
|
753
605
|
l.push(`\t\ttopts.MaxReconnectAttempts = opts.MaxReconnectAttempts`)
|
|
754
606
|
l.push(`\t\ttopts.ReconnectDelayMs = opts.ReconnectDelayMs`)
|
|
755
|
-
l.push(`\t\tif len(opts.Headers) > 0 {`)
|
|
756
|
-
l.push(`\t\t\ttopts.Headers = http.Header{}`)
|
|
757
|
-
l.push(`\t\t\tfor k, v := range opts.Headers { topts.Headers.Set(k, v) }`)
|
|
758
|
-
l.push(`\t\t}`)
|
|
759
607
|
l.push(`\t}`)
|
|
760
|
-
l.push(`\
|
|
608
|
+
l.push(`\ttopts.Headers = authHeaders(${cfg}, extra)`)
|
|
609
|
+
l.push(`\ttransports := []Transport{&WsTransport{}, &SseTransport{}, &LongpollTransport{}}`)
|
|
610
|
+
l.push(`\treturn NewResumableConnection(rtURL, transports, topts), nil`)
|
|
761
611
|
l.push(`}`)
|
|
762
612
|
l.push(``)
|
|
763
613
|
return l
|
|
764
614
|
}
|
|
765
615
|
|
|
766
|
-
if (
|
|
767
|
-
|
|
616
|
+
if (op.stream === "sse") {
|
|
617
|
+
l.push(`func (${recv} ${recvType}) ${plan.methodName}(${params.join(", ")}) iter.Seq2[SSEEvent, error] {`)
|
|
768
618
|
l.push(`\treturn func(yield func(SSEEvent, error) bool) {`)
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
for (const qp of sseQueryParams) {
|
|
772
|
-
const fieldName = goPascal(qp.name)
|
|
773
|
-
if (qp.required) {
|
|
774
|
-
l.push(
|
|
775
|
-
`\t\tif opts != nil { q.Set(${JSON.stringify(qp.name)}, ${qpStringExpr(`opts.${fieldName}`, qp.schema)}) }`,
|
|
776
|
-
)
|
|
777
|
-
} else {
|
|
778
|
-
l.push(
|
|
779
|
-
`\t\tif opts != nil && opts.${fieldName} != nil { q.Set(${JSON.stringify(qp.name)}, ${qpStringExpr(`*opts.${fieldName}`, qp.schema)}) }`,
|
|
780
|
-
)
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
const urlExpr = pathToGoExpr(pathStr)
|
|
785
|
-
const sseURLExpr =
|
|
786
|
-
sseQueryParams.length > 0 ? `${cfgAccess}.BaseURL+${urlExpr}+"?"+q.Encode()` : `${cfgAccess}.BaseURL+${urlExpr}`
|
|
787
|
-
l.push(`\t\tcallHeaders := map[string]string{"Accept": "text/event-stream"}`)
|
|
619
|
+
l.push(...emitPath(plan, "yield(SSEEvent{}, err)\n\t\t\treturn", "\t\t"))
|
|
620
|
+
l.push(...emitQueryAndHeaders(plan, "\t\t", `map[string]string{"Accept": "text/event-stream"}`))
|
|
788
621
|
l.push(`\t\tif opts != nil && opts.LastEventID != "" {`)
|
|
789
622
|
l.push(`\t\t\tcallHeaders["Last-Event-ID"] = opts.LastEventID`)
|
|
790
623
|
l.push(`\t\t}`)
|
|
791
|
-
|
|
792
|
-
l.push(
|
|
624
|
+
const body = emitBody(plan, "yield(SSEEvent{}, err)\n\t\t\treturn", "\t\t")
|
|
625
|
+
l.push(...body.lines)
|
|
626
|
+
l.push(`\t\tresp, err := openStream(ctx, ${cfg}, ${goString(op.method)}, path, q, ${body.expr}, callHeaders)`)
|
|
627
|
+
l.push(`\t\tif err != nil {`)
|
|
628
|
+
l.push(`\t\t\tyield(SSEEvent{}, err)`)
|
|
629
|
+
l.push(`\t\t\treturn`)
|
|
793
630
|
l.push(`\t\t}`)
|
|
794
|
-
l.push(`\t\treq, err := http.NewRequestWithContext(ctx, "GET", ${sseURLExpr}, nil)`)
|
|
795
|
-
l.push(`\t\tif err != nil { yield(SSEEvent{}, err); return }`)
|
|
796
|
-
l.push(`\t\tfor k, v := range ${cfgAccess}.Headers { req.Header.Set(k, v) }`)
|
|
797
|
-
l.push(`\t\tfor k, v := range callHeaders { req.Header.Set(k, v) }`)
|
|
798
|
-
l.push(
|
|
799
|
-
`\t\tif ${cfgAccess}.BearerToken != "" { req.Header.Set(${cfgAccess}.AuthHeaderName, ${cfgAccess}.AuthHeaderPrefix+${cfgAccess}.BearerToken) }`,
|
|
800
|
-
)
|
|
801
|
-
l.push(`\t\tresp, err := ${cfgAccess}.HTTPClient.Do(req)`)
|
|
802
|
-
l.push(`\t\tif err != nil { yield(SSEEvent{}, err); return }`)
|
|
803
631
|
l.push(`\t\tfor ev, err := range parseSSEStream(ctx, resp) {`)
|
|
804
|
-
l.push(`\t\t\tif !yield(ev, err) {
|
|
632
|
+
l.push(`\t\t\tif !yield(ev, err) {`)
|
|
633
|
+
l.push(`\t\t\t\treturn`)
|
|
634
|
+
l.push(`\t\t\t}`)
|
|
805
635
|
l.push(`\t\t}`)
|
|
806
636
|
l.push(`\t}`)
|
|
807
637
|
l.push(`}`)
|
|
@@ -809,180 +639,290 @@ function emitMethod(m: MethodInfo, recv: string, recvType: string, throwOnError:
|
|
|
809
639
|
return l
|
|
810
640
|
}
|
|
811
641
|
|
|
812
|
-
if (
|
|
813
|
-
|
|
814
|
-
l.push(
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
l.push(`\
|
|
829
|
-
l.push(`\
|
|
830
|
-
l.push(`\tif
|
|
831
|
-
l.push(`\
|
|
832
|
-
l.push(`\tif opts != nil && len(opts.Protocols) > 0 { dialOpts.Subprotocols = opts.Protocols }`)
|
|
833
|
-
l.push(`\tif opts != nil && len(opts.Headers) > 0 {`)
|
|
834
|
-
l.push(`\t\tdialOpts.HTTPHeader = http.Header{}`)
|
|
835
|
-
l.push(`\t\tfor k, v := range opts.Headers { dialOpts.HTTPHeader.Set(k, v) }`)
|
|
642
|
+
if (op.stream === "ws") {
|
|
643
|
+
l.push(`func (${recv} ${recvType}) ${plan.methodName}(${params.join(", ")}) (*TypedWebSocket, error) {`)
|
|
644
|
+
l.push(...emitPath(plan, "return nil, err", "\t"))
|
|
645
|
+
l.push(...emitQueryAndHeaders(plan, "\t", "map[string]string{}"))
|
|
646
|
+
l.push(`\tif opts != nil && opts.ReconnectToken != "" {`)
|
|
647
|
+
l.push(`\t\tq.set("reconnect_token", opts.ReconnectToken)`)
|
|
648
|
+
l.push(`\t}`)
|
|
649
|
+
l.push(`\twsURL, err := buildURL(${cfg}.BaseURL, path, q)`)
|
|
650
|
+
l.push(`\tif err != nil {`)
|
|
651
|
+
l.push(`\t\treturn nil, err`)
|
|
652
|
+
l.push(`\t}`)
|
|
653
|
+
l.push(
|
|
654
|
+
`\tdialOpts := &websocket.DialOptions{HTTPClient: streamClient(${cfg}), HTTPHeader: authHeaders(${cfg}, callHeaders)}`,
|
|
655
|
+
)
|
|
656
|
+
l.push(`\tif opts != nil && len(opts.Protocols) > 0 {`)
|
|
657
|
+
l.push(`\t\tdialOpts.Subprotocols = opts.Protocols`)
|
|
658
|
+
l.push(`\t}`)
|
|
659
|
+
l.push(`\tconn, _, err := websocket.Dial(ctx, toWsURL(wsURL), dialOpts)`)
|
|
660
|
+
l.push(`\tif err != nil {`)
|
|
661
|
+
l.push(`\t\treturn nil, err`)
|
|
836
662
|
l.push(`\t}`)
|
|
837
|
-
l.push(`\tconn, _, err := websocket.Dial(ctx, wsURL, dialOpts)`)
|
|
838
|
-
l.push(`\tif err != nil { return nil, err }`)
|
|
839
663
|
l.push(`\treturn newTypedWebSocket(conn), nil`)
|
|
840
664
|
l.push(`}`)
|
|
841
665
|
l.push(``)
|
|
842
666
|
return l
|
|
843
667
|
}
|
|
844
668
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
if (m.isIdempotent && !m.isRealtime && !m.isSSE && !m.isWS) {
|
|
871
|
-
l.push(`\tif opts != nil {`)
|
|
872
|
-
l.push(`\t\t_idemExisting := ""`)
|
|
873
|
-
l.push(`\t\tfor _k, _v := range callHeaders {`)
|
|
874
|
-
l.push(`\t\t\tif strings.EqualFold(_k, "Idempotency-Key") { _idemExisting = _v; break }`)
|
|
875
|
-
l.push(`\t\t}`)
|
|
876
|
-
l.push(`\t\tif _idemExisting == "" {`)
|
|
877
|
-
l.push(`\t\t\tif callHeaders == nil { callHeaders = map[string]string{} }`)
|
|
878
|
-
l.push(`\t\t\tif opts.IdempotencyKey != "" {`)
|
|
879
|
-
l.push(`\t\t\t\tcallHeaders["Idempotency-Key"] = opts.IdempotencyKey`)
|
|
880
|
-
l.push(`\t\t\t} else {`)
|
|
881
|
-
l.push(`\t\t\t\tcallHeaders["Idempotency-Key"] = newUUIDv4()`)
|
|
882
|
-
l.push(`\t\t\t}`)
|
|
669
|
+
const resultType = plan.resultType
|
|
670
|
+
let ret: string
|
|
671
|
+
let failRet: string
|
|
672
|
+
if (resultType === "") {
|
|
673
|
+
ret = "error"
|
|
674
|
+
failRet = "return err"
|
|
675
|
+
} else if (throwOnError) {
|
|
676
|
+
ret = `(*${resultType}, error)`
|
|
677
|
+
failRet = "return nil, err"
|
|
678
|
+
} else {
|
|
679
|
+
ret = `(SDKResult[${resultType}], error)`
|
|
680
|
+
failRet = `return SDKResult[${resultType}]{}, err`
|
|
681
|
+
}
|
|
682
|
+
l.push(`func (${recv} ${recvType}) ${plan.methodName}(${params.join(", ")}) ${ret} {`)
|
|
683
|
+
l.push(`\tvar err error`)
|
|
684
|
+
l.push(...emitPath(plan, failRet, "\t"))
|
|
685
|
+
l.push(...emitQueryAndHeaders(plan, "\t", "map[string]string{}"))
|
|
686
|
+
|
|
687
|
+
/* idempotency-key auto-injection: an explicit header (any casing) wins, then
|
|
688
|
+
* opts.IdempotencyKey, then a fresh UUID — every dispatch carries a key. */
|
|
689
|
+
if (op.idempotent) {
|
|
690
|
+
l.push(`\tif !hasHeader(callHeaders, "Idempotency-Key") {`)
|
|
691
|
+
l.push(`\t\tkey := newUUIDv4()`)
|
|
692
|
+
l.push(`\t\tif opts != nil && opts.IdempotencyKey != "" {`)
|
|
693
|
+
l.push(`\t\t\tkey = opts.IdempotencyKey`)
|
|
883
694
|
l.push(`\t\t}`)
|
|
884
|
-
l.push(`\t
|
|
885
|
-
l.push(`\t\tcallHeaders = map[string]string{"Idempotency-Key": newUUIDv4()}`)
|
|
695
|
+
l.push(`\t\tcallHeaders["Idempotency-Key"] = key`)
|
|
886
696
|
l.push(`\t}`)
|
|
887
697
|
}
|
|
888
698
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
l.push(`\t_concretePath, _ := interpolatePath(${JSON.stringify(pathStr)}, _pathParamsMap)`)
|
|
901
|
-
l.push(`\t_concreteSelector := ${JSON.stringify(`${httpMethodStr} `)} + _concretePath`)
|
|
902
|
-
l.push(`\tvar _requestMeta *RequestMeta`)
|
|
903
|
-
l.push(
|
|
904
|
-
`\tif ${staleAccess} != nil { _requestMeta = ${staleAccess}.BuildRequestMeta(_concreteSelector, _concretePath, ${JSON.stringify(httpMethodStr)}) }`,
|
|
905
|
-
)
|
|
906
|
-
}
|
|
699
|
+
const body = emitBody(plan, failRet, "\t")
|
|
700
|
+
l.push(...body.lines)
|
|
701
|
+
|
|
702
|
+
/* invalidation pre-flight for every call: reads see and clear staleness too */
|
|
703
|
+
const pathParamsExpr = plan.pathLocals.length > 0 ? pathExpr(plan) : "map[string]string(nil)"
|
|
704
|
+
l.push(`\tpathParams := ${pathParamsExpr}`)
|
|
705
|
+
l.push(`\tselector := ${goString(`${op.method} `)} + path`)
|
|
706
|
+
l.push(`\tvar requestMeta *RequestMeta`)
|
|
707
|
+
l.push(`\tif ${stale} != nil {`)
|
|
708
|
+
l.push(`\t\trequestMeta = ${stale}.BuildRequestMeta(selector, path, ${goString(op.method)})`)
|
|
709
|
+
l.push(`\t}`)
|
|
907
710
|
|
|
908
|
-
const resultBind =
|
|
909
|
-
const operationStr = JSON.stringify(`${httpMethodStr} ${pathStr}`)
|
|
910
|
-
const requestMetaArg = hasInvalidate ? "_requestMeta" : "nil"
|
|
711
|
+
const resultBind = resultType === "" ? "_, err =" : "result, err :="
|
|
911
712
|
l.push(
|
|
912
|
-
`\t${resultBind}
|
|
713
|
+
`\t${resultBind} doRequest(ctx, ${cfg}, ${goString(op.method)}, path, q, ${body.expr}, callHeaders, ${goString(`${op.method} ${op.path}`)}, requestMeta)`,
|
|
913
714
|
)
|
|
914
715
|
l.push(`\tif err != nil {`)
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
}
|
|
716
|
+
if (resultType !== "" && !throwOnError) {
|
|
717
|
+
l.push(`\t\tvar apiErr APIError`)
|
|
718
|
+
l.push(`\t\tif errors.As(err, &apiErr) {`)
|
|
719
|
+
l.push(`\t\t\treturn SDKResult[${resultType}]{Err: apiErr, Status: apiErr.Status()}, nil`)
|
|
720
|
+
l.push(`\t\t}`)
|
|
721
|
+
l.push(`\t\treturn SDKResult[${resultType}]{}, err`)
|
|
922
722
|
} else {
|
|
923
|
-
|
|
924
|
-
/* unwrap typed APIError if doRequest produced one, else synthesize
|
|
925
|
-
* a StatusError so callers always see a typed Err without panicking
|
|
926
|
-
* on ctx cancel / network errors. */
|
|
927
|
-
l.push(`\t\tif apiErr, ok := err.(APIError); ok {`)
|
|
928
|
-
l.push(`\t\t\treturn SDKResult[${m.responseType}]{Err: apiErr, Status: 0, Response: nil}, nil`)
|
|
929
|
-
l.push(`\t\t}`)
|
|
930
|
-
l.push(
|
|
931
|
-
`\t\treturn SDKResult[${m.responseType}]{Err: &StatusError{StatusCode: 0, Message: err.Error()}, Status: 0, Response: nil}, nil`,
|
|
932
|
-
)
|
|
933
|
-
} else {
|
|
934
|
-
l.push(`\t\treturn err`)
|
|
935
|
-
}
|
|
723
|
+
l.push(`\t\t${failRet}`)
|
|
936
724
|
}
|
|
937
725
|
l.push(`\t}`)
|
|
938
726
|
|
|
939
|
-
/* invalidation post-flight
|
|
940
|
-
* call's
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
l.push(
|
|
946
|
-
`\t\t${staleAccess}.MarkStale(invalidationMap["${m.resource}"]["${m.action}"].Invalidate, _pathParamsMap, _concreteSelector)`,
|
|
947
|
-
)
|
|
948
|
-
l.push(`\t\tif _requestMeta != nil && _requestMeta.IsStale {`)
|
|
949
|
-
l.push(
|
|
950
|
-
`\t\t\t${staleAccess}.ClearStale(_concreteSelector, _concretePath, ${JSON.stringify(httpMethodStr)}, _requestMeta.SeqSnapshot)`,
|
|
951
|
-
)
|
|
952
|
-
l.push(`\t\t}`)
|
|
953
|
-
l.push(`\t}`)
|
|
727
|
+
/* invalidation post-flight: mark declared targets stale (templated ones expanded
|
|
728
|
+
* with this call's params), then clear this key if the call read through a stale window */
|
|
729
|
+
l.push(`\tif ${stale} != nil {`)
|
|
730
|
+
if (op.invalidates.length > 0) {
|
|
731
|
+
l.push(`\t\t${stale}.MarkStale([]string{${op.invalidates.map(goString).join(", ")}}, pathParams, selector)`)
|
|
732
|
+
} else {
|
|
733
|
+
l.push(`\t\t_ = pathParams`)
|
|
954
734
|
}
|
|
735
|
+
l.push(`\t\tif requestMeta != nil && requestMeta.IsStale {`)
|
|
736
|
+
l.push(`\t\t\t${stale}.ClearStale(selector, path, ${goString(op.method)}, requestMeta.SeqSnapshot)`)
|
|
737
|
+
l.push(`\t\t}`)
|
|
738
|
+
l.push(`\t}`)
|
|
955
739
|
|
|
956
|
-
if (
|
|
740
|
+
if (resultType === "") {
|
|
957
741
|
l.push(`\treturn nil`)
|
|
958
742
|
l.push(`}`)
|
|
959
743
|
l.push(``)
|
|
960
744
|
return l
|
|
961
745
|
}
|
|
962
746
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
l.push(`\
|
|
966
|
-
|
|
967
|
-
l.push(`\
|
|
968
|
-
l.push(`\treturn &out, nil`)
|
|
747
|
+
l.push(`\tvar out ${resultType}`)
|
|
748
|
+
if (op.success.kind === "text") {
|
|
749
|
+
l.push(`\tout = string(result.body)`)
|
|
750
|
+
} else if (op.success.kind === "binary") {
|
|
751
|
+
l.push(`\tout = result.body`)
|
|
969
752
|
} else {
|
|
970
|
-
l.push(`\tvar out ${m.responseType}`)
|
|
971
753
|
l.push(`\tif len(result.body) > 0 {`)
|
|
972
754
|
l.push(`\t\tif err := json.Unmarshal(result.body, &out); err != nil {`)
|
|
973
|
-
|
|
974
|
-
`\t\t\treturn
|
|
975
|
-
|
|
755
|
+
if (throwOnError) {
|
|
756
|
+
l.push(`\t\t\treturn nil, fmt.Errorf("honey: decode response: %w", err)`)
|
|
757
|
+
} else {
|
|
758
|
+
l.push(
|
|
759
|
+
`\t\t\treturn SDKResult[${resultType}]{Err: &StatusError{StatusCode: result.status, Body: result.body, Response: result.resp, Message: err.Error()}, Status: result.status, Response: result.resp}, nil`,
|
|
760
|
+
)
|
|
761
|
+
}
|
|
976
762
|
l.push(`\t\t}`)
|
|
977
763
|
l.push(`\t}`)
|
|
978
|
-
l.push(`\treturn SDKResult[${m.responseType}]{Data: &out, Status: result.status, Response: result.resp}, nil`)
|
|
979
764
|
}
|
|
980
|
-
|
|
765
|
+
if (throwOnError) l.push(`\treturn &out, nil`)
|
|
766
|
+
else l.push(`\treturn SDKResult[${resultType}]{Data: &out, Status: result.status, Response: result.resp}, nil`)
|
|
981
767
|
l.push(`}`)
|
|
982
768
|
l.push(``)
|
|
983
769
|
return l
|
|
984
770
|
}
|
|
985
771
|
|
|
772
|
+
type ResourceNode = {
|
|
773
|
+
structName: string
|
|
774
|
+
ns: IRNamespace
|
|
775
|
+
path: string[]
|
|
776
|
+
children: Array<{ field: string; node: ResourceNode }>
|
|
777
|
+
methods: GoOpPlan[]
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function buildGoClient(
|
|
781
|
+
model: SdkModel,
|
|
782
|
+
names: GoNames,
|
|
783
|
+
options: GoSDKOptions,
|
|
784
|
+
auth: { headerName: string; prefix: string },
|
|
785
|
+
): string {
|
|
786
|
+
const throwOnError = options.throwOnError ?? true
|
|
787
|
+
|
|
788
|
+
/* plan resources top-down so names are claimed in a stable order */
|
|
789
|
+
const allPlans: GoOpPlan[] = []
|
|
790
|
+
function planNs(
|
|
791
|
+
ns: IRNamespace,
|
|
792
|
+
path: string[],
|
|
793
|
+
reserved: string[],
|
|
794
|
+
): { children: ResourceNode["children"]; methods: GoOpPlan[] } {
|
|
795
|
+
const members = new NameScope(reserved)
|
|
796
|
+
const children: ResourceNode["children"] = []
|
|
797
|
+
for (const [seg, childNs] of namespacesOf(ns)) {
|
|
798
|
+
const field = members.claim(goExported(seg))
|
|
799
|
+
const childPath = [...path, seg]
|
|
800
|
+
const structName = names.scope.claim(`${childPath.map(goExported).join("")}Resource`)
|
|
801
|
+
children.push({ field, node: { children: [], methods: [], ns: childNs, path: childPath, structName } })
|
|
802
|
+
}
|
|
803
|
+
const methods: GoOpPlan[] = []
|
|
804
|
+
for (const [seg, irOp] of methodsOf(ns)) {
|
|
805
|
+
const op = model.opsById.get(irOp.id)
|
|
806
|
+
if (!op) continue
|
|
807
|
+
const base = goExported(seg)
|
|
808
|
+
/* Go forbids a field and a method with one name: `extract` + `extract.table` → ExtractCall */
|
|
809
|
+
const methodName = members.has(base) ? members.claim(`${base}Call`) : members.claim(base)
|
|
810
|
+
const optsName = names.scope.claim(`${[...path, seg].map(goExported).join("")}Opts`)
|
|
811
|
+
const plan = planOp(op, methodName, optsName, names)
|
|
812
|
+
methods.push(plan)
|
|
813
|
+
allPlans.push(plan)
|
|
814
|
+
}
|
|
815
|
+
for (const child of children) {
|
|
816
|
+
const sub = planNs(child.node.ns, child.node.path, ["client"])
|
|
817
|
+
child.node.children = sub.children
|
|
818
|
+
child.node.methods = sub.methods
|
|
819
|
+
}
|
|
820
|
+
return { children, methods }
|
|
821
|
+
}
|
|
822
|
+
const root = planNs(model.ir.tree, [], ["cfg", "stale", "IsStale"])
|
|
823
|
+
|
|
824
|
+
const body: string[] = []
|
|
825
|
+
/* ---- Client struct: cfg + stale + one field per root namespace ---- */
|
|
826
|
+
body.push(`// Client is the generated SDK client.`)
|
|
827
|
+
body.push(`type Client struct {`)
|
|
828
|
+
body.push(`\tcfg Config`)
|
|
829
|
+
body.push(`\tstale *StaleTracker`)
|
|
830
|
+
for (const child of root.children) body.push(`\t${child.field} *${child.node.structName}`)
|
|
831
|
+
body.push(`}`)
|
|
832
|
+
body.push(``)
|
|
833
|
+
body.push(emitClientBody(root, allPlans, throwOnError, auth))
|
|
834
|
+
return body.join("\n")
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function emitClientBody(
|
|
838
|
+
root: { children: ResourceNode["children"]; methods: GoOpPlan[] },
|
|
839
|
+
allPlans: GoOpPlan[],
|
|
840
|
+
throwOnError: boolean,
|
|
841
|
+
auth: { headerName: string; prefix: string },
|
|
842
|
+
): string {
|
|
843
|
+
const body: string[] = []
|
|
844
|
+
/* ---- NewClient: init + cascade nested struct init ---- */
|
|
845
|
+
body.push(`// NewClient creates a new SDK client. BaseURL must be non-empty.`)
|
|
846
|
+
body.push(`func NewClient(cfg Config) *Client {`)
|
|
847
|
+
body.push(`\tif cfg.BaseURL == "" {`)
|
|
848
|
+
body.push(`\t\tpanic("honey sdk: Config.BaseURL must not be empty")`)
|
|
849
|
+
body.push(`\t}`)
|
|
850
|
+
body.push(`\tif cfg.AuthHeaderName == "" {`)
|
|
851
|
+
body.push(`\t\tcfg.AuthHeaderName = ${goString(auth.headerName)}`)
|
|
852
|
+
body.push(`\t}`)
|
|
853
|
+
body.push(`\tif cfg.AuthHeaderPrefix == "" {`)
|
|
854
|
+
body.push(`\t\tcfg.AuthHeaderPrefix = ${goString(auth.prefix)}`)
|
|
855
|
+
body.push(`\t}`)
|
|
856
|
+
body.push(`\tcfg = prepareConfig(cfg)`)
|
|
857
|
+
body.push(`\tc := &Client{cfg: cfg}`)
|
|
858
|
+
body.push(`\tc.stale = NewStaleTracker(cfg.Invalidation)`)
|
|
859
|
+
for (const child of root.children) body.push(`\tc.${child.field} = new${child.node.structName}(c)`)
|
|
860
|
+
body.push(`\treturn c`)
|
|
861
|
+
body.push(`}`)
|
|
862
|
+
body.push(``)
|
|
863
|
+
|
|
864
|
+
/* IsStale predicate */
|
|
865
|
+
body.push(`// IsStale reports whether (method, path) currently sits inside an active stale window.`)
|
|
866
|
+
body.push(`func (c *Client) IsStale(method, path string) bool {`)
|
|
867
|
+
body.push(`\tif c.stale == nil {`)
|
|
868
|
+
body.push(`\t\treturn false`)
|
|
869
|
+
body.push(`\t}`)
|
|
870
|
+
body.push(`\treturn c.stale.IsStale(method, path)`)
|
|
871
|
+
body.push(`}`)
|
|
872
|
+
body.push(``)
|
|
873
|
+
|
|
874
|
+
for (const plan of root.methods) body.push(...emitMethod(plan, "c", "*Client", throwOnError))
|
|
875
|
+
|
|
876
|
+
function emitResource(node: ResourceNode): void {
|
|
877
|
+
body.push(`// ${node.structName} groups the ${goString(node.path.join("."))} operations.`)
|
|
878
|
+
body.push(`type ${node.structName} struct {`)
|
|
879
|
+
body.push(`\tclient *Client`)
|
|
880
|
+
for (const child of node.children) body.push(`\t${child.field} *${child.node.structName}`)
|
|
881
|
+
body.push(`}`)
|
|
882
|
+
body.push(``)
|
|
883
|
+
body.push(`func new${node.structName}(c *Client) *${node.structName} {`)
|
|
884
|
+
body.push(`\tr := &${node.structName}{client: c}`)
|
|
885
|
+
for (const child of node.children) body.push(`\tr.${child.field} = new${child.node.structName}(c)`)
|
|
886
|
+
body.push(`\treturn r`)
|
|
887
|
+
body.push(`}`)
|
|
888
|
+
body.push(``)
|
|
889
|
+
for (const plan of node.methods) body.push(...emitMethod(plan, "r", `*${node.structName}`, throwOnError))
|
|
890
|
+
for (const child of node.children) emitResource(child.node)
|
|
891
|
+
}
|
|
892
|
+
for (const child of root.children) emitResource(child.node)
|
|
893
|
+
|
|
894
|
+
for (const plan of [...allPlans].sort((a, b) => cmpCodeUnit(a.optsName, b.optsName))) {
|
|
895
|
+
body.push(...emitMultipartStruct(plan))
|
|
896
|
+
body.push(...emitOptsStruct(plan))
|
|
897
|
+
}
|
|
898
|
+
return body.join("\n")
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function clientFile(model: SdkModel, names: GoNames, options: GoSDKOptions): string {
|
|
902
|
+
const bodyStr = buildGoClient(model, names, options, model.ir.auth)
|
|
903
|
+
|
|
904
|
+
const stdlib = ["context"]
|
|
905
|
+
const uses = (re: RegExp) => re.test(bodyStr)
|
|
906
|
+
if (uses(/\bjson\./)) stdlib.push("encoding/json")
|
|
907
|
+
if (uses(/\berrors\./)) stdlib.push("errors")
|
|
908
|
+
if (uses(/\bfmt\./)) stdlib.push("fmt")
|
|
909
|
+
if (uses(/\bio\./)) stdlib.push("io")
|
|
910
|
+
if (uses(/\biter\./)) stdlib.push("iter")
|
|
911
|
+
if (uses(/\burl\./)) stdlib.push("net/url")
|
|
912
|
+
const thirdParty = uses(/\bwebsocket\./) ? ["nhooyr.io/websocket"] : []
|
|
913
|
+
|
|
914
|
+
const out: string[] = []
|
|
915
|
+
/* blank line after generator header detaches it from package-doc on pkg.go.dev */
|
|
916
|
+
out.push(`// Code generated by honey. DO NOT EDIT.`)
|
|
917
|
+
out.push(``)
|
|
918
|
+
out.push(`package sdk`)
|
|
919
|
+
out.push(``)
|
|
920
|
+
out.push(goImportBlock(stdlib, thirdParty))
|
|
921
|
+
out.push(``)
|
|
922
|
+
out.push(bodyStr)
|
|
923
|
+
return out.join("\n")
|
|
924
|
+
}
|
|
925
|
+
|
|
986
926
|
/* ── public entrypoint ── */
|
|
987
927
|
|
|
988
928
|
/**
|
|
@@ -990,9 +930,10 @@ function emitMethod(m: MethodInfo, recv: string, recvType: string, throwOnError:
|
|
|
990
930
|
* Returns a file map (filename → content) ready to write to disk as a Go module.
|
|
991
931
|
*/
|
|
992
932
|
export function generateGoSDK(spec: Record<string, unknown>, options: GoSDKOptions = {}): GeneratedGoSDK {
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
const
|
|
933
|
+
const input = spec as unknown as OpenApiSpecInput
|
|
934
|
+
const model = buildSdkModel(input)
|
|
935
|
+
const names = buildGoNames(model)
|
|
936
|
+
const serviceMap = serviceMapOf(model.ir)
|
|
996
937
|
|
|
997
938
|
const files: Record<string, string> = {}
|
|
998
939
|
|
|
@@ -1001,13 +942,12 @@ export function generateGoSDK(spec: Record<string, unknown>, options: GoSDKOptio
|
|
|
1001
942
|
files[name] = content
|
|
1002
943
|
}
|
|
1003
944
|
|
|
1004
|
-
/* types
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
files["client.go"] = buildGoClient(spec as unknown as OpenApiSpec, options)
|
|
945
|
+
/* schema types claim their names first; the client then hoists into the same table */
|
|
946
|
+
const schemaTypes = buildGoTypes(model, names)
|
|
947
|
+
files["client.go"] = clientFile(model, names, options)
|
|
948
|
+
files["types.go"] = typesFile(schemaTypes, names)
|
|
1009
949
|
files["go.mod"] = buildGoMod(options.modulePath)
|
|
1010
|
-
files["doc.go"] = buildGoDoc(
|
|
950
|
+
files["doc.go"] = buildGoDoc(model.ir.info)
|
|
1011
951
|
|
|
1012
952
|
/* Opt-in post-process. Consumers publishing to pkg.go.dev pass `gofmt: true`
|
|
1013
953
|
* so struct fields align and single-line `if` blocks expand. No-op when
|