@lovrozagar/honey 0.1.0 → 0.2.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 +1596 -6
- package/dist/accepts.d.ts +6 -0
- package/dist/accepts.d.ts.map +1 -0
- package/dist/body-limit.d.ts +22 -0
- package/dist/body-limit.d.ts.map +1 -0
- package/dist/build/index.d.ts +42 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/client/error.d.ts +14 -0
- package/dist/client/error.d.ts.map +1 -0
- package/dist/client/http.d.ts +88 -0
- package/dist/client/http.d.ts.map +1 -0
- package/dist/client/index.d.ts +22 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/sdk.d.ts +34 -0
- package/dist/client/sdk.d.ts.map +1 -0
- package/dist/client/sse.d.ts +10 -0
- package/dist/client/sse.d.ts.map +1 -0
- package/dist/client/types.d.ts +124 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/ws.d.ts +15 -0
- package/dist/client/ws.d.ts.map +1 -0
- package/dist/client-mcp/server.d.ts +10 -0
- package/dist/client-mcp/server.d.ts.map +1 -0
- package/dist/client-mcp/types.d.ts +16 -0
- package/dist/client-mcp/types.d.ts.map +1 -0
- package/dist/cloudflare/realtime.d.ts +30 -0
- package/dist/cloudflare/realtime.d.ts.map +1 -0
- package/dist/codegen-go-cli.d.ts +22 -0
- package/dist/codegen-go-cli.d.ts.map +1 -0
- package/dist/codegen-go.d.ts +29 -0
- package/dist/codegen-go.d.ts.map +1 -0
- package/dist/codegen-hash.d.ts +3 -0
- package/dist/codegen-hash.d.ts.map +1 -0
- package/dist/codegen-ir.d.ts +123 -0
- package/dist/codegen-ir.d.ts.map +1 -0
- package/dist/codegen-loaders.d.ts +12 -0
- package/dist/codegen-loaders.d.ts.map +1 -0
- package/dist/codegen-mcp.d.ts +13 -0
- package/dist/codegen-mcp.d.ts.map +1 -0
- package/dist/codegen-python.d.ts +21 -0
- package/dist/codegen-python.d.ts.map +1 -0
- package/dist/codegen-rust.d.ts +18 -0
- package/dist/codegen-rust.d.ts.map +1 -0
- package/dist/codegen-sanitize.d.ts +3 -0
- package/dist/codegen-sanitize.d.ts.map +1 -0
- package/dist/codegen-schema-naming.d.ts +22 -0
- package/dist/codegen-schema-naming.d.ts.map +1 -0
- package/dist/codegen.d.ts +178 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/context.d.ts +56 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/cookie-sign.d.ts +3 -0
- package/dist/cookie-sign.d.ts.map +1 -0
- package/dist/cookie.d.ts +12 -0
- package/dist/cookie.d.ts.map +1 -0
- package/dist/cors.d.ts +13 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/crypto.d.ts +2 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/csrf.d.ts +9 -0
- package/dist/csrf.d.ts.map +1 -0
- package/dist/curl-logger.d.ts +33 -0
- package/dist/curl-logger.d.ts.map +1 -0
- package/dist/detect-runtime.d.ts +16 -0
- package/dist/detect-runtime.d.ts.map +1 -0
- package/dist/error.d.ts +21 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/errors.d.ts +40 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/etag.d.ts +9 -0
- package/dist/etag.d.ts.map +1 -0
- package/dist/feature-load.d.ts +3 -0
- package/dist/feature-load.d.ts.map +1 -0
- package/dist/go-type-emitter.d.ts +33 -0
- package/dist/go-type-emitter.d.ts.map +1 -0
- package/dist/honey-response.d.ts +82 -0
- package/dist/honey-response.d.ts.map +1 -0
- package/dist/i18n-slot.d.ts +7 -0
- package/dist/i18n-slot.d.ts.map +1 -0
- package/dist/i18n.d.ts +26 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/index.d.ts +470 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/init.d.ts +7 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/input.d.ts +6 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/ip-restrict.d.ts +12 -0
- package/dist/ip-restrict.d.ts.map +1 -0
- package/dist/logger.d.ts +45 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/meta-spec.d.ts +81 -0
- package/dist/meta-spec.d.ts.map +1 -0
- package/dist/middleware.d.ts +60 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/node-request.d.ts +59 -0
- package/dist/node-request.d.ts.map +1 -0
- package/dist/node.d.ts +13 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/openapi/register.d.ts +3 -0
- package/dist/openapi/register.d.ts.map +1 -0
- package/dist/openapi/scalar.d.ts +12 -0
- package/dist/openapi/scalar.d.ts.map +1 -0
- package/dist/openapi/spec-factory.d.ts +31 -0
- package/dist/openapi/spec-factory.d.ts.map +1 -0
- package/dist/openapi/spec.d.ts +19 -0
- package/dist/openapi/spec.d.ts.map +1 -0
- package/dist/openapi/swagger.d.ts +12 -0
- package/dist/openapi/swagger.d.ts.map +1 -0
- package/dist/plugin.d.ts +167 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/powered-by.d.ts +7 -0
- package/dist/powered-by.d.ts.map +1 -0
- package/dist/pretty-json.d.ts +10 -0
- package/dist/pretty-json.d.ts.map +1 -0
- package/dist/proxy.d.ts +51 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/python-type-emitter.d.ts +11 -0
- package/dist/python-type-emitter.d.ts.map +1 -0
- package/dist/realtime/buffer.d.ts +18 -0
- package/dist/realtime/buffer.d.ts.map +1 -0
- package/dist/realtime/bus.d.ts +11 -0
- package/dist/realtime/bus.d.ts.map +1 -0
- package/dist/realtime/longpoll.d.ts +23 -0
- package/dist/realtime/longpoll.d.ts.map +1 -0
- package/dist/realtime/protocol.d.ts +50 -0
- package/dist/realtime/protocol.d.ts.map +1 -0
- package/dist/realtime/route.d.ts +42 -0
- package/dist/realtime/route.d.ts.map +1 -0
- package/dist/realtime/runtime.d.ts +74 -0
- package/dist/realtime/runtime.d.ts.map +1 -0
- package/dist/request-id.d.ts +12 -0
- package/dist/request-id.d.ts.map +1 -0
- package/dist/request-to-curl.d.ts +9 -0
- package/dist/request-to-curl.d.ts.map +1 -0
- package/dist/response.d.ts +57 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/rust-type-emitter.d.ts +32 -0
- package/dist/rust-type-emitter.d.ts.map +1 -0
- package/dist/secure-headers.d.ts +16 -0
- package/dist/secure-headers.d.ts.map +1 -0
- package/dist/serve-register.d.ts +2 -0
- package/dist/serve-register.d.ts.map +1 -0
- package/dist/serve-slot.d.ts +6 -0
- package/dist/serve-slot.d.ts.map +1 -0
- package/dist/serve.d.ts +21 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/server-timing.d.ts +9 -0
- package/dist/server-timing.d.ts.map +1 -0
- package/dist/static.d.ts +12 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/telemetry/otel.d.ts +59 -0
- package/dist/telemetry/otel.d.ts.map +1 -0
- package/dist/testing.d.ts +24 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/timeout.d.ts +7 -0
- package/dist/timeout.d.ts.map +1 -0
- package/dist/tree.d.ts +90 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/ts-type-emitter.d.ts +4 -0
- package/dist/ts-type-emitter.d.ts.map +1 -0
- package/dist/type-emitter.d.ts +8 -0
- package/dist/type-emitter.d.ts.map +1 -0
- package/dist/type-extractor.d.ts +39 -0
- package/dist/type-extractor.d.ts.map +1 -0
- package/dist/types.d.ts +451 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/validation.d.ts +18 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/ws/bun.d.ts +28 -0
- package/dist/ws/bun.d.ts.map +1 -0
- package/dist/ws/cloudflare.d.ts +55 -0
- package/dist/ws/cloudflare.d.ts.map +1 -0
- package/dist/ws/deno.d.ts +13 -0
- package/dist/ws/deno.d.ts.map +1 -0
- package/dist/ws/node.d.ts +10 -0
- package/dist/ws/node.d.ts.map +1 -0
- package/dist/yaml.d.ts +4 -0
- package/dist/yaml.d.ts.map +1 -0
- package/docs/api-ref/go.md +82 -82
- package/docs/api-ref/python.md +82 -82
- package/docs/api-ref/rust.md +82 -82
- package/docs/api-ref/typescript.md +82 -82
- package/docs/invalidate-spec.md +13 -13
- package/docs/meta-spec.md +513 -0
- package/docs/packaging.md +85 -0
- package/docs/sdk.md +87 -73
- package/package.json +195 -51
- package/src/body-limit.ts +2 -1
- package/src/build/index.ts +2 -9
- package/src/cli.ts +2 -3
- package/src/client/http.ts +204 -223
- package/src/client/index.ts +6 -14
- package/src/client/sdk.ts +24 -17
- package/src/client/types.ts +4 -9
- package/src/client/ws.ts +3 -5
- package/src/client-mcp/server.ts +1 -4
- package/src/cloudflare/realtime.ts +8 -10
- package/src/codegen-go-cli.ts +74 -67
- package/src/codegen-go.ts +71 -51
- package/src/codegen-ir.ts +16 -27
- package/src/codegen-loaders.ts +5 -8
- package/src/codegen-mcp.ts +9 -27
- package/src/codegen-python.ts +47 -27
- package/src/codegen-rust.ts +110 -66
- package/src/codegen-sanitize.ts +1 -7
- package/src/codegen-schema-naming.ts +8 -2
- package/src/codegen.ts +299 -196
- package/src/context.ts +91 -95
- package/src/cookie-sign.ts +5 -13
- package/src/cookie.ts +1 -2
- package/src/cors.ts +4 -6
- package/src/crypto.ts +1 -4
- package/src/csrf.ts +3 -3
- package/src/curl-logger.ts +14 -32
- package/src/detect-runtime.ts +1 -3
- package/src/errors.ts +1 -3
- package/src/etag.ts +3 -4
- package/src/go-type-emitter.ts +45 -40
- package/src/honey-response.ts +1 -3
- package/src/i18n.ts +3 -12
- package/src/index.ts +1168 -1412
- package/src/ip-restrict.ts +2 -1
- package/src/logger.ts +13 -13
- package/src/meta-spec.ts +0 -0
- package/src/middleware.ts +111 -79
- package/src/node-request.ts +2 -1
- package/src/node.ts +9 -10
- package/src/openapi/register.ts +1 -2
- package/src/openapi/scalar.ts +9 -9
- package/src/openapi/spec-factory.ts +2 -2
- package/src/openapi/spec.ts +26 -19
- package/src/openapi/swagger.ts +9 -9
- package/src/plugin.ts +226 -256
- package/src/powered-by.ts +2 -1
- package/src/pretty-json.ts +2 -1
- package/src/proxy.ts +1 -3
- package/src/python-type-emitter.ts +37 -12
- package/src/realtime/route.ts +1 -1
- package/src/realtime/runtime.ts +9 -12
- package/src/request-id.ts +3 -4
- package/src/request-to-curl.ts +1 -4
- package/src/response.ts +150 -199
- package/src/rust-type-emitter.ts +66 -25
- package/src/secure-headers.ts +2 -1
- package/src/serve.ts +3 -12
- package/src/server-timing.ts +2 -1
- package/src/static.ts +3 -4
- package/src/telemetry/otel.ts +1 -6
- package/src/testing.ts +2 -11
- package/src/timeout.ts +2 -1
- package/src/tree.ts +12 -27
- package/src/ts-type-emitter.ts +71 -15
- package/src/type-emitter.ts +226 -237
- package/src/type-extractor.ts +260 -334
- package/src/types.ts +168 -68
- package/src/validation.ts +7 -30
- package/src/ws/bun.ts +2 -9
- package/src/ws/cloudflare.ts +13 -16
- package/src/ws/deno.ts +31 -40
- package/src/ws/node.ts +4 -19
package/src/codegen-go.ts
CHANGED
|
@@ -47,7 +47,16 @@ let runtimeCache: Map<string, string> | null = null
|
|
|
47
47
|
export function loadGoRuntimeTemplates(): Map<string, string> {
|
|
48
48
|
if (runtimeCache) return runtimeCache
|
|
49
49
|
|
|
50
|
-
const names = [
|
|
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
|
+
]
|
|
51
60
|
const cache = new Map<string, string>()
|
|
52
61
|
for (const name of names) {
|
|
53
62
|
const filePath = fileURLToPath(new URL(`./client-go/${name}`, import.meta.url))
|
|
@@ -304,14 +313,7 @@ function buildGoTypes(spec: OpenApiSpec): string {
|
|
|
304
313
|
|
|
305
314
|
function buildGoMod(modulePath?: string): string {
|
|
306
315
|
const mod = modulePath ?? "example.com/sdk"
|
|
307
|
-
return [
|
|
308
|
-
`module ${mod}`,
|
|
309
|
-
``,
|
|
310
|
-
`go 1.23`,
|
|
311
|
-
``,
|
|
312
|
-
`require nhooyr.io/websocket v1.8.17`,
|
|
313
|
-
``,
|
|
314
|
-
].join("\n")
|
|
316
|
+
return [`module ${mod}`, ``, `go 1.23`, ``, `require nhooyr.io/websocket v1.8.17`, ``].join("\n")
|
|
315
317
|
}
|
|
316
318
|
|
|
317
319
|
/* ── buildGoDoc ── */
|
|
@@ -366,10 +368,7 @@ type MethodInfo = {
|
|
|
366
368
|
}
|
|
367
369
|
|
|
368
370
|
/** Build a MethodInfo from an IROperation + the raw spec op object. */
|
|
369
|
-
function methodInfoFromIROp(
|
|
370
|
-
irOp: IROperation,
|
|
371
|
-
rawOp: Record<string, unknown>,
|
|
372
|
-
): MethodInfo {
|
|
371
|
+
function methodInfoFromIROp(irOp: IROperation, rawOp: Record<string, unknown>): MethodInfo {
|
|
373
372
|
const segments = irOp.id.split(".")
|
|
374
373
|
const isTopLevel = segments.length === 1
|
|
375
374
|
const resource = segments[0] ?? irOp.id
|
|
@@ -401,10 +400,7 @@ function methodInfoFromIROp(
|
|
|
401
400
|
}
|
|
402
401
|
|
|
403
402
|
/** Walk the IR tree and collect all MethodInfos, pairing with raw spec ops. */
|
|
404
|
-
function collectMethodsFromIR(
|
|
405
|
-
ns: IRNamespace,
|
|
406
|
-
spec: OpenApiSpec,
|
|
407
|
-
): MethodInfo[] {
|
|
403
|
+
function collectMethodsFromIR(ns: IRNamespace, spec: OpenApiSpec): MethodInfo[] {
|
|
408
404
|
const rawOpById = new Map<string, Record<string, unknown>>()
|
|
409
405
|
for (const [, pathMethods] of Object.entries(spec.paths ?? {})) {
|
|
410
406
|
for (const [, operation] of Object.entries(pathMethods)) {
|
|
@@ -473,16 +469,17 @@ function buildGoClient(spec: OpenApiSpec, options: GoSDKOptions): string {
|
|
|
473
469
|
body.push(`var invalidationMap = map[string]map[string]serviceEntry{`)
|
|
474
470
|
for (const [resource, actions] of Object.entries(serviceMap).sort(([a], [b]) => a.localeCompare(b))) {
|
|
475
471
|
body.push(`\t"${resource}": {`)
|
|
476
|
-
for (const [action, entry] of Object.entries(actions as Record<string, Record<string, unknown>>).sort(([a], [b]) =>
|
|
472
|
+
for (const [action, entry] of Object.entries(actions as Record<string, Record<string, unknown>>).sort(([a], [b]) =>
|
|
473
|
+
a.localeCompare(b),
|
|
474
|
+
)) {
|
|
477
475
|
const inv = entry.invalidate as string[] | undefined
|
|
478
|
-
const invStr = inv && inv.length > 0
|
|
479
|
-
? `[]string{${inv.map((s) => JSON.stringify(s)).join(", ")}}`
|
|
480
|
-
: `nil`
|
|
476
|
+
const invStr = inv && inv.length > 0 ? `[]string{${inv.map((s) => JSON.stringify(s)).join(", ")}}` : `nil`
|
|
481
477
|
const params = entry.params as string[] | undefined
|
|
482
|
-
const paramsStr =
|
|
483
|
-
? `[]string{${params.map((s) => JSON.stringify(s)).join(", ")}}`
|
|
484
|
-
|
|
485
|
-
|
|
478
|
+
const paramsStr =
|
|
479
|
+
params && params.length > 0 ? `[]string{${params.map((s) => JSON.stringify(s)).join(", ")}}` : `nil`
|
|
480
|
+
body.push(
|
|
481
|
+
`\t\t"${action}": {Method: ${JSON.stringify(String(entry.method))}, Path: ${JSON.stringify(String(entry.path))}, Params: ${paramsStr}, SSE: ${entry.sse === true}, WS: ${entry.ws === true}, Invalidate: ${invStr}},`,
|
|
482
|
+
)
|
|
486
483
|
}
|
|
487
484
|
body.push(`\t},`)
|
|
488
485
|
}
|
|
@@ -570,7 +567,11 @@ function buildGoClient(spec: OpenApiSpec, options: GoSDKOptions): string {
|
|
|
570
567
|
|
|
571
568
|
/* direct methods on this struct */
|
|
572
569
|
for (const [, op] of methodsOf(ns)) {
|
|
573
|
-
const raw =
|
|
570
|
+
const raw =
|
|
571
|
+
methods.find((m) => m.opId === op.id) ??
|
|
572
|
+
(() => {
|
|
573
|
+
throw new Error(`missing MethodInfo for ${op.id}`)
|
|
574
|
+
})()
|
|
574
575
|
body.push(...emitMethod(raw, recvName, `*${structName}`, throwOnError))
|
|
575
576
|
}
|
|
576
577
|
|
|
@@ -613,9 +614,7 @@ function buildGoClient(spec: OpenApiSpec, options: GoSDKOptions): string {
|
|
|
613
614
|
{ line: `var _ = sync.Mutex{}`, symbol: "sync." },
|
|
614
615
|
{ line: `var _ = time.Second`, symbol: "time." },
|
|
615
616
|
]
|
|
616
|
-
const suppressionLines = suppressions
|
|
617
|
-
.filter((s) => !bodyStr.includes(s.symbol))
|
|
618
|
-
.map((s) => s.line)
|
|
617
|
+
const suppressionLines = suppressions.filter((s) => !bodyStr.includes(s.symbol)).map((s) => s.line)
|
|
619
618
|
|
|
620
619
|
const out: string[] = []
|
|
621
620
|
/* blank line after generator header detaches it from package-doc on pkg.go.dev */
|
|
@@ -772,16 +771,19 @@ function emitMethod(m: MethodInfo, recv: string, recvType: string, throwOnError:
|
|
|
772
771
|
for (const qp of sseQueryParams) {
|
|
773
772
|
const fieldName = goPascal(qp.name)
|
|
774
773
|
if (qp.required) {
|
|
775
|
-
l.push(
|
|
774
|
+
l.push(
|
|
775
|
+
`\t\tif opts != nil { q.Set(${JSON.stringify(qp.name)}, ${qpStringExpr(`opts.${fieldName}`, qp.schema)}) }`,
|
|
776
|
+
)
|
|
776
777
|
} else {
|
|
777
|
-
l.push(
|
|
778
|
+
l.push(
|
|
779
|
+
`\t\tif opts != nil && opts.${fieldName} != nil { q.Set(${JSON.stringify(qp.name)}, ${qpStringExpr(`*opts.${fieldName}`, qp.schema)}) }`,
|
|
780
|
+
)
|
|
778
781
|
}
|
|
779
782
|
}
|
|
780
783
|
}
|
|
781
784
|
const urlExpr = pathToGoExpr(pathStr)
|
|
782
|
-
const sseURLExpr =
|
|
783
|
-
? `${cfgAccess}.BaseURL+${urlExpr}+"?"+q.Encode()`
|
|
784
|
-
: `${cfgAccess}.BaseURL+${urlExpr}`
|
|
785
|
+
const sseURLExpr =
|
|
786
|
+
sseQueryParams.length > 0 ? `${cfgAccess}.BaseURL+${urlExpr}+"?"+q.Encode()` : `${cfgAccess}.BaseURL+${urlExpr}`
|
|
785
787
|
l.push(`\t\tcallHeaders := map[string]string{"Accept": "text/event-stream"}`)
|
|
786
788
|
l.push(`\t\tif opts != nil && opts.LastEventID != "" {`)
|
|
787
789
|
l.push(`\t\t\tcallHeaders["Last-Event-ID"] = opts.LastEventID`)
|
|
@@ -793,7 +795,9 @@ function emitMethod(m: MethodInfo, recv: string, recvType: string, throwOnError:
|
|
|
793
795
|
l.push(`\t\tif err != nil { yield(SSEEvent{}, err); return }`)
|
|
794
796
|
l.push(`\t\tfor k, v := range ${cfgAccess}.Headers { req.Header.Set(k, v) }`)
|
|
795
797
|
l.push(`\t\tfor k, v := range callHeaders { req.Header.Set(k, v) }`)
|
|
796
|
-
l.push(
|
|
798
|
+
l.push(
|
|
799
|
+
`\t\tif ${cfgAccess}.BearerToken != "" { req.Header.Set(${cfgAccess}.AuthHeaderName, ${cfgAccess}.AuthHeaderPrefix+${cfgAccess}.BearerToken) }`,
|
|
800
|
+
)
|
|
797
801
|
l.push(`\t\tresp, err := ${cfgAccess}.HTTPClient.Do(req)`)
|
|
798
802
|
l.push(`\t\tif err != nil { yield(SSEEvent{}, err); return }`)
|
|
799
803
|
l.push(`\t\tfor ev, err := range parseSSEStream(ctx, resp) {`)
|
|
@@ -812,9 +816,13 @@ function emitMethod(m: MethodInfo, recv: string, recvType: string, throwOnError:
|
|
|
812
816
|
if (qp.name === "reconnect_token") continue
|
|
813
817
|
const fieldName = goPascal(qp.name)
|
|
814
818
|
if (qp.required) {
|
|
815
|
-
l.push(
|
|
819
|
+
l.push(
|
|
820
|
+
`\tif opts != nil { q.Set(${JSON.stringify(qp.name)}, ${qpStringExpr(`opts.${fieldName}`, qp.schema)}) }`,
|
|
821
|
+
)
|
|
816
822
|
} else {
|
|
817
|
-
l.push(
|
|
823
|
+
l.push(
|
|
824
|
+
`\tif opts != nil && opts.${fieldName} != nil { q.Set(${JSON.stringify(qp.name)}, ${qpStringExpr(`*opts.${fieldName}`, qp.schema)}) }`,
|
|
825
|
+
)
|
|
818
826
|
}
|
|
819
827
|
}
|
|
820
828
|
l.push(`\tif opts != nil && opts.ReconnectToken != "" { q.Set("reconnect_token", opts.ReconnectToken) }`)
|
|
@@ -841,7 +849,9 @@ function emitMethod(m: MethodInfo, recv: string, recvType: string, throwOnError:
|
|
|
841
849
|
if (qp.required) {
|
|
842
850
|
l.push(`\tif opts != nil { q.Set(${JSON.stringify(qp.name)}, ${qpStringExpr(`opts.${fieldName}`, qp.schema)}) }`)
|
|
843
851
|
} else {
|
|
844
|
-
l.push(
|
|
852
|
+
l.push(
|
|
853
|
+
`\tif opts != nil && opts.${fieldName} != nil { q.Set(${JSON.stringify(qp.name)}, ${qpStringExpr(`*opts.${fieldName}`, qp.schema)}) }`,
|
|
854
|
+
)
|
|
845
855
|
}
|
|
846
856
|
}
|
|
847
857
|
|
|
@@ -881,21 +891,26 @@ function emitMethod(m: MethodInfo, recv: string, recvType: string, throwOnError:
|
|
|
881
891
|
* so consumer can flip cfg.Invalidation on/off without regen; tracker's
|
|
882
892
|
* BuildRequestMeta short-circuits to nil when disabled. */
|
|
883
893
|
const hasInvalidate = Array.isArray(m.op["x-invalidate"]) && (m.op["x-invalidate"] as unknown[]).length > 0
|
|
884
|
-
const pathParamsMapExpr =
|
|
885
|
-
|
|
886
|
-
|
|
894
|
+
const pathParamsMapExpr =
|
|
895
|
+
m.pathParams.length > 0
|
|
896
|
+
? `map[string]string{${m.pathParams.map((p) => `${JSON.stringify(p)}: ${p}`).join(", ")}}`
|
|
897
|
+
: `map[string]string(nil)`
|
|
887
898
|
if (hasInvalidate) {
|
|
888
899
|
l.push(`\t_pathParamsMap := ${pathParamsMapExpr}`)
|
|
889
900
|
l.push(`\t_concretePath, _ := interpolatePath(${JSON.stringify(pathStr)}, _pathParamsMap)`)
|
|
890
901
|
l.push(`\t_concreteSelector := ${JSON.stringify(`${httpMethodStr} `)} + _concretePath`)
|
|
891
902
|
l.push(`\tvar _requestMeta *RequestMeta`)
|
|
892
|
-
l.push(
|
|
903
|
+
l.push(
|
|
904
|
+
`\tif ${staleAccess} != nil { _requestMeta = ${staleAccess}.BuildRequestMeta(_concreteSelector, _concretePath, ${JSON.stringify(httpMethodStr)}) }`,
|
|
905
|
+
)
|
|
893
906
|
}
|
|
894
907
|
|
|
895
908
|
const resultBind = m.responseType === "" ? "_, err" : "result, err"
|
|
896
909
|
const operationStr = JSON.stringify(`${httpMethodStr} ${pathStr}`)
|
|
897
910
|
const requestMetaArg = hasInvalidate ? "_requestMeta" : "nil"
|
|
898
|
-
l.push(
|
|
911
|
+
l.push(
|
|
912
|
+
`\t${resultBind} := doRequest(ctx, ${cfgAccess}, "${httpMethodStr}", ${urlExpr}, q, ${bodyArg}, ${bodyReaderArg}, ${contentTypeArg}, callHeaders, ${operationStr}, ${requestMetaArg})`,
|
|
913
|
+
)
|
|
899
914
|
l.push(`\tif err != nil {`)
|
|
900
915
|
|
|
901
916
|
if (throwOnError) {
|
|
@@ -912,7 +927,9 @@ function emitMethod(m: MethodInfo, recv: string, recvType: string, throwOnError:
|
|
|
912
927
|
l.push(`\t\tif apiErr, ok := err.(APIError); ok {`)
|
|
913
928
|
l.push(`\t\t\treturn SDKResult[${m.responseType}]{Err: apiErr, Status: 0, Response: nil}, nil`)
|
|
914
929
|
l.push(`\t\t}`)
|
|
915
|
-
l.push(
|
|
930
|
+
l.push(
|
|
931
|
+
`\t\treturn SDKResult[${m.responseType}]{Err: &StatusError{StatusCode: 0, Message: err.Error()}, Status: 0, Response: nil}, nil`,
|
|
932
|
+
)
|
|
916
933
|
} else {
|
|
917
934
|
l.push(`\t\treturn err`)
|
|
918
935
|
}
|
|
@@ -925,9 +942,13 @@ function emitMethod(m: MethodInfo, recv: string, recvType: string, throwOnError:
|
|
|
925
942
|
* mutation completed (seq-bounded to avoid clobbering newer invalidations). */
|
|
926
943
|
if (hasInvalidate) {
|
|
927
944
|
l.push(`\tif ${staleAccess} != nil {`)
|
|
928
|
-
l.push(
|
|
945
|
+
l.push(
|
|
946
|
+
`\t\t${staleAccess}.MarkStale(invalidationMap["${m.resource}"]["${m.action}"].Invalidate, _pathParamsMap, _concreteSelector)`,
|
|
947
|
+
)
|
|
929
948
|
l.push(`\t\tif _requestMeta != nil && _requestMeta.IsStale {`)
|
|
930
|
-
l.push(
|
|
949
|
+
l.push(
|
|
950
|
+
`\t\t\t${staleAccess}.ClearStale(_concreteSelector, _concretePath, ${JSON.stringify(httpMethodStr)}, _requestMeta.SeqSnapshot)`,
|
|
951
|
+
)
|
|
931
952
|
l.push(`\t\t}`)
|
|
932
953
|
l.push(`\t}`)
|
|
933
954
|
}
|
|
@@ -949,7 +970,9 @@ function emitMethod(m: MethodInfo, recv: string, recvType: string, throwOnError:
|
|
|
949
970
|
l.push(`\tvar out ${m.responseType}`)
|
|
950
971
|
l.push(`\tif len(result.body) > 0 {`)
|
|
951
972
|
l.push(`\t\tif err := json.Unmarshal(result.body, &out); err != nil {`)
|
|
952
|
-
l.push(
|
|
973
|
+
l.push(
|
|
974
|
+
`\t\t\treturn SDKResult[${m.responseType}]{Err: &StatusError{StatusCode: result.status, Body: result.body, Response: result.resp, Message: err.Error()}, Status: result.status, Response: result.resp}, nil`,
|
|
975
|
+
)
|
|
953
976
|
l.push(`\t\t}`)
|
|
954
977
|
l.push(`\t}`)
|
|
955
978
|
l.push(`\treturn SDKResult[${m.responseType}]{Data: &out, Status: result.status, Response: result.resp}, nil`)
|
|
@@ -966,10 +989,7 @@ function emitMethod(m: MethodInfo, recv: string, recvType: string, throwOnError:
|
|
|
966
989
|
* Generates a complete Go SDK module from an OpenAPI 3.1 spec.
|
|
967
990
|
* Returns a file map (filename → content) ready to write to disk as a Go module.
|
|
968
991
|
*/
|
|
969
|
-
export function generateGoSDK(
|
|
970
|
-
spec: Record<string, unknown>,
|
|
971
|
-
options: GoSDKOptions = {},
|
|
972
|
-
): GeneratedGoSDK {
|
|
992
|
+
export function generateGoSDK(spec: Record<string, unknown>, options: GoSDKOptions = {}): GeneratedGoSDK {
|
|
973
993
|
const resolved = resolveRefs(spec as Parameters<typeof resolveRefs>[0]) as unknown as OpenApiSpec
|
|
974
994
|
|
|
975
995
|
const { serviceMap } = collectSDKMethods(spec as Parameters<typeof resolveRefs>[0])
|
package/src/codegen-ir.ts
CHANGED
|
@@ -48,7 +48,7 @@ export type IRBody =
|
|
|
48
48
|
contentType: "multipart/form-data"
|
|
49
49
|
parts: IRMultipartPart[]
|
|
50
50
|
required: boolean
|
|
51
|
-
|
|
51
|
+
}
|
|
52
52
|
|
|
53
53
|
export type IRResponse = {
|
|
54
54
|
status: string /* "200" | "204" | "default" | ... — preserved verbatim */
|
|
@@ -81,9 +81,7 @@ export type IROperation = {
|
|
|
81
81
|
extensions: IROperationExtensions
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
export type IRTreeEntry =
|
|
85
|
-
| { kind: "method"; op: IROperation }
|
|
86
|
-
| { kind: "namespace"; ns: IRNamespace }
|
|
84
|
+
export type IRTreeEntry = { kind: "method"; op: IROperation } | { kind: "namespace"; ns: IRNamespace }
|
|
87
85
|
|
|
88
86
|
export type IRNamespace = {
|
|
89
87
|
entries: Map<string, IRTreeEntry>
|
|
@@ -132,7 +130,7 @@ export function buildResourceTree(operations: IROperation[]): IRNamespace {
|
|
|
132
130
|
if (existing.kind === "method") {
|
|
133
131
|
throw new Error(
|
|
134
132
|
`operationId conflict: "${existing.op.id}" is callable but also a namespace prefix of "${op.id}".\n` +
|
|
135
|
-
|
|
133
|
+
`Rename one. Suggested: "${existing.op.id}" → "${existing.op.id}One" or drop "${op.id}".`,
|
|
136
134
|
)
|
|
137
135
|
}
|
|
138
136
|
cur = existing.ns
|
|
@@ -149,7 +147,7 @@ export function buildResourceTree(operations: IROperation[]): IRNamespace {
|
|
|
149
147
|
const otherId = firstDescendantId(existing.ns)
|
|
150
148
|
throw new Error(
|
|
151
149
|
`operationId conflict: "${op.id}" is callable but also a namespace prefix of "${otherId}".\n` +
|
|
152
|
-
|
|
150
|
+
`Rename one. Suggested: "${op.id}" → "${op.id}One" or drop "${otherId}".`,
|
|
153
151
|
)
|
|
154
152
|
}
|
|
155
153
|
cur.entries.set(leafName, { kind: "method", op })
|
|
@@ -281,7 +279,10 @@ export function schemaToIR(schema: Record<string, unknown> | undefined): IRSchem
|
|
|
281
279
|
if (isStringEnum(schema)) return { enum: enumVals, kind: "scalar", type: "string" }
|
|
282
280
|
if (isIntEnum(schema)) return { enum: enumVals, kind: "scalar", type: "integer" }
|
|
283
281
|
/* mixed enum (string + number values) — emit as union of const literals */
|
|
284
|
-
if (
|
|
282
|
+
if (
|
|
283
|
+
enumVals.length > 0 &&
|
|
284
|
+
enumVals.every((v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean")
|
|
285
|
+
) {
|
|
285
286
|
return { kind: "union", variants: enumVals.map((v) => ({ kind: "const" as const, value: v })) }
|
|
286
287
|
}
|
|
287
288
|
}
|
|
@@ -299,11 +300,7 @@ export function schemaToIR(schema: Record<string, unknown> | undefined): IRSchem
|
|
|
299
300
|
return { items, kind: "array" }
|
|
300
301
|
}
|
|
301
302
|
|
|
302
|
-
if (
|
|
303
|
-
schema.type === "object" ||
|
|
304
|
-
schema.properties !== undefined ||
|
|
305
|
-
schema.additionalProperties !== undefined
|
|
306
|
-
) {
|
|
303
|
+
if (schema.type === "object" || schema.properties !== undefined || schema.additionalProperties !== undefined) {
|
|
307
304
|
const props = (schema.properties ?? {}) as Record<string, Record<string, unknown>>
|
|
308
305
|
const requiredKeys = new Set((schema.required as string[] | undefined) ?? [])
|
|
309
306
|
const fields: IRField[] = Object.entries(props).map(([name, propSchema]) => {
|
|
@@ -363,28 +360,20 @@ function hasBinaryMultipartPart(mediaType: Record<string, unknown> | undefined):
|
|
|
363
360
|
}
|
|
364
361
|
|
|
365
362
|
/** Normalise a multipart/form-data object schema into typed parts. */
|
|
366
|
-
function extractMultipartParts(
|
|
367
|
-
mediaType: Record<string, unknown> | undefined,
|
|
368
|
-
): IRMultipartPart[] {
|
|
363
|
+
function extractMultipartParts(mediaType: Record<string, unknown> | undefined): IRMultipartPart[] {
|
|
369
364
|
const schema = mediaType?.schema as Record<string, unknown> | undefined
|
|
370
365
|
const props = schema?.properties as Record<string, Record<string, unknown>> | undefined
|
|
371
366
|
if (!props) return []
|
|
372
367
|
const parts: IRMultipartPart[] = []
|
|
373
368
|
for (const [name, propSchema] of Object.entries(props)) {
|
|
374
369
|
const ir = schemaToIR(propSchema)
|
|
375
|
-
const isBinary =
|
|
376
|
-
ir.kind === "binary" || (ir.kind === "array" && ir.items.kind === "binary")
|
|
370
|
+
const isBinary = ir.kind === "binary" || (ir.kind === "array" && ir.items.kind === "binary")
|
|
377
371
|
parts.push({ name, schema: ir, type: isBinary ? "file" : "text" })
|
|
378
372
|
}
|
|
379
373
|
return parts
|
|
380
374
|
}
|
|
381
375
|
|
|
382
|
-
function buildOperation(
|
|
383
|
-
id: string,
|
|
384
|
-
method: string,
|
|
385
|
-
path: string,
|
|
386
|
-
op: Record<string, unknown>,
|
|
387
|
-
): IROperation {
|
|
376
|
+
function buildOperation(id: string, method: string, path: string, op: Record<string, unknown>): IROperation {
|
|
388
377
|
const upperMethod = method.toUpperCase()
|
|
389
378
|
|
|
390
379
|
const declaredParams = (op.parameters as Record<string, unknown>[] | undefined) ?? []
|
|
@@ -396,7 +385,9 @@ function buildOperation(
|
|
|
396
385
|
for (const p of declaredParams) {
|
|
397
386
|
const pIn = p.in as string
|
|
398
387
|
const pName = p.name as string
|
|
399
|
-
const pSchema = p.schema
|
|
388
|
+
const pSchema = p.schema
|
|
389
|
+
? schemaToIR(p.schema as Record<string, unknown>)
|
|
390
|
+
: { kind: "scalar" as const, type: "string" as const }
|
|
400
391
|
const param: IRParam = { name: pName, schema: pSchema }
|
|
401
392
|
if (typeof p.description === "string") param.description = p.description
|
|
402
393
|
if (pIn === "path") {
|
|
@@ -458,9 +449,7 @@ function buildOperation(
|
|
|
458
449
|
/* SSE detected by response content-type, not a request extension */
|
|
459
450
|
if (firstCt === "text/event-stream") extensions.sse = true
|
|
460
451
|
const mediaType = content[firstCt]
|
|
461
|
-
const respSchema = mediaType?.schema
|
|
462
|
-
? schemaToIR(mediaType.schema as Record<string, unknown>)
|
|
463
|
-
: undefined
|
|
452
|
+
const respSchema = mediaType?.schema ? schemaToIR(mediaType.schema as Record<string, unknown>) : undefined
|
|
464
453
|
const resp: IRResponse = { contentType: firstCt, status }
|
|
465
454
|
if (respSchema !== undefined) resp.schema = respSchema
|
|
466
455
|
responses[status] = resp
|
package/src/codegen-loaders.ts
CHANGED
|
@@ -9,13 +9,11 @@ export async function loadToJSONSchema(): Promise<
|
|
|
9
9
|
if (toJSONSchemaLoaded) return toJSONSchemaFn
|
|
10
10
|
toJSONSchemaLoaded = true
|
|
11
11
|
try {
|
|
12
|
-
const id = "zod"
|
|
13
|
-
|
|
12
|
+
/* opaque specifier — a plain `const id = "zod"` is constant-folded by rollup/esbuild,
|
|
13
|
+
which pulls the whole optional dependency into webworker-target bundles */
|
|
14
|
+
const zod = await import(["z", "od"].join(""))
|
|
14
15
|
if (typeof zod.toJSONSchema === "function") {
|
|
15
|
-
toJSONSchemaFn = zod.toJSONSchema as (
|
|
16
|
-
schema: unknown,
|
|
17
|
-
opts?: { io?: "input" | "output" },
|
|
18
|
-
) => unknown
|
|
16
|
+
toJSONSchemaFn = zod.toJSONSchema as (schema: unknown, opts?: { io?: "input" | "output" }) => unknown
|
|
19
17
|
}
|
|
20
18
|
} catch {
|
|
21
19
|
/* zod not available */
|
|
@@ -30,8 +28,7 @@ export async function loadEffectJsonSchema(): Promise<((schema: unknown) => unkn
|
|
|
30
28
|
if (effectLoaded) return effectJsonSchemaFn
|
|
31
29
|
effectLoaded = true
|
|
32
30
|
try {
|
|
33
|
-
const
|
|
34
|
-
const effect = await import(id)
|
|
31
|
+
const effect = await import(["eff", "ect"].join(""))
|
|
35
32
|
if (effect.JSONSchema && typeof effect.JSONSchema.make === "function") {
|
|
36
33
|
effectJsonSchemaFn = effect.JSONSchema.make as (schema: unknown) => unknown
|
|
37
34
|
}
|
package/src/codegen-mcp.ts
CHANGED
|
@@ -71,10 +71,7 @@ function toSnakeCase(id: string): string {
|
|
|
71
71
|
.toLowerCase()
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
function resolveSchema(
|
|
75
|
-
spec: SpecRecord,
|
|
76
|
-
schema: SpecRecord | undefined,
|
|
77
|
-
): SpecRecord | undefined {
|
|
74
|
+
function resolveSchema(spec: SpecRecord, schema: SpecRecord | undefined): SpecRecord | undefined {
|
|
78
75
|
if (!schema) return undefined
|
|
79
76
|
if (!schema.$ref) return schema
|
|
80
77
|
const ref = schema.$ref as string
|
|
@@ -108,9 +105,7 @@ function deepResolveSchema(
|
|
|
108
105
|
out[k] = v
|
|
109
106
|
} else if (Array.isArray(v)) {
|
|
110
107
|
out[k] = v.map((item) =>
|
|
111
|
-
item !== null && typeof item === "object"
|
|
112
|
-
? deepResolveSchema(spec, item as SpecRecord, seen)
|
|
113
|
-
: item,
|
|
108
|
+
item !== null && typeof item === "object" ? deepResolveSchema(spec, item as SpecRecord, seen) : item,
|
|
114
109
|
)
|
|
115
110
|
} else if (typeof v === "object") {
|
|
116
111
|
out[k] = deepResolveSchema(spec, v as SpecRecord, seen)
|
|
@@ -169,12 +164,7 @@ function emptyParamGroup(): ParamGroup {
|
|
|
169
164
|
return { props: {}, required: [] }
|
|
170
165
|
}
|
|
171
166
|
|
|
172
|
-
function assignGroup(
|
|
173
|
-
properties: SpecRecord,
|
|
174
|
-
required: string[],
|
|
175
|
-
key: string,
|
|
176
|
-
group: ParamGroup,
|
|
177
|
-
): void {
|
|
167
|
+
function assignGroup(properties: SpecRecord, required: string[], key: string, group: ParamGroup): void {
|
|
178
168
|
if (Object.keys(group.props).length === 0) return
|
|
179
169
|
const out: SpecRecord = { properties: group.props, type: "object" }
|
|
180
170
|
if (group.required.length > 0) {
|
|
@@ -184,10 +174,7 @@ function assignGroup(
|
|
|
184
174
|
properties[key] = out
|
|
185
175
|
}
|
|
186
176
|
|
|
187
|
-
function buildToolInputSchema(
|
|
188
|
-
op: MCPOp,
|
|
189
|
-
spec: OpenApiSpecInput,
|
|
190
|
-
): SpecRecord {
|
|
177
|
+
function buildToolInputSchema(op: MCPOp, spec: OpenApiSpecInput): SpecRecord {
|
|
191
178
|
const properties: SpecRecord = {}
|
|
192
179
|
const required: string[] = []
|
|
193
180
|
const specRec = spec as unknown as SpecRecord
|
|
@@ -220,11 +207,7 @@ function buildToolInputSchema(
|
|
|
220
207
|
return schema
|
|
221
208
|
}
|
|
222
209
|
|
|
223
|
-
function buildToolsGen(
|
|
224
|
-
ops: MCPOp[],
|
|
225
|
-
spec: OpenApiSpecInput,
|
|
226
|
-
projectName: string,
|
|
227
|
-
): string {
|
|
210
|
+
function buildToolsGen(ops: MCPOp[], spec: OpenApiSpecInput, projectName: string): string {
|
|
228
211
|
const l: string[] = []
|
|
229
212
|
l.push(`/* Generated by honey codegen-mcp.ts — do not edit by hand. */`)
|
|
230
213
|
l.push(``)
|
|
@@ -254,7 +237,9 @@ function buildToolsGen(
|
|
|
254
237
|
l.push(`\t\t\thandler: async (args) => {`)
|
|
255
238
|
l.push(`\t\t\t\tconst method = ${callExpr}`)
|
|
256
239
|
l.push(`\t\t\t\tif (typeof method !== "function") {`)
|
|
257
|
-
l.push(
|
|
240
|
+
l.push(
|
|
241
|
+
`\t\t\t\t\treturn { content: [{ type: "text", text: JSON.stringify({ message: "SDK missing method ${op.operationId}" }) }], isError: true }`,
|
|
242
|
+
)
|
|
258
243
|
l.push(`\t\t\t\t}`)
|
|
259
244
|
l.push(`\t\t\t\tconst result = await method.call(sdk, args)`)
|
|
260
245
|
l.push(`\t\t\t\treturn { content: [{ type: "text", text: JSON.stringify(result) }] }`)
|
|
@@ -331,10 +316,7 @@ function buildTsconfig(): string {
|
|
|
331
316
|
)
|
|
332
317
|
}
|
|
333
318
|
|
|
334
|
-
export function generateMCPServer(
|
|
335
|
-
spec: OpenApiSpecInput,
|
|
336
|
-
options: MCPOptions,
|
|
337
|
-
): MCPResult {
|
|
319
|
+
export function generateMCPServer(spec: OpenApiSpecInput, options: MCPOptions): MCPResult {
|
|
338
320
|
toIR(spec)
|
|
339
321
|
const { projectName, sdkClassName, sdkPackageName } = options
|
|
340
322
|
const version = options.version ?? "0.1.0"
|
package/src/codegen-python.ts
CHANGED
|
@@ -53,7 +53,15 @@ interface ParamInfo {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const PYTHON_FUTURE = "from __future__ import annotations"
|
|
56
|
-
const STATIC_FILE_NAMES = [
|
|
56
|
+
const STATIC_FILE_NAMES = [
|
|
57
|
+
"_runtime.py",
|
|
58
|
+
"_errors.py",
|
|
59
|
+
"_invalidation.py",
|
|
60
|
+
"_sse.py",
|
|
61
|
+
"_ws.py",
|
|
62
|
+
"_transport.py",
|
|
63
|
+
"_realtime.py",
|
|
64
|
+
] as const
|
|
57
65
|
const RUNTIME_TEMPLATE_CACHE = new Map<string, string>()
|
|
58
66
|
const WS_EXTENSION = "x-websocket"
|
|
59
67
|
const REALTIME_EXTENSION = "x-realtime"
|
|
@@ -100,10 +108,7 @@ function resolveSchema(
|
|
|
100
108
|
return cur as Record<string, unknown> | undefined
|
|
101
109
|
}
|
|
102
110
|
|
|
103
|
-
function getSuccessType(
|
|
104
|
-
spec: Record<string, unknown>,
|
|
105
|
-
operation: Record<string, unknown>,
|
|
106
|
-
): string {
|
|
111
|
+
function getSuccessType(spec: Record<string, unknown>, operation: Record<string, unknown>): string {
|
|
107
112
|
const responses = operation.responses as Record<string, Record<string, unknown>> | undefined
|
|
108
113
|
if (!responses) return "None"
|
|
109
114
|
const successCodes = ["200", "201", "202", "204"]
|
|
@@ -193,7 +198,12 @@ function collectOperations(spec: Record<string, unknown>): OperationInfo[] {
|
|
|
193
198
|
isWS: !isRealtime && operation[WS_EXTENSION] === true,
|
|
194
199
|
method: httpMethod.toUpperCase(),
|
|
195
200
|
operationId,
|
|
196
|
-
parameters: rawParams as Array<{
|
|
201
|
+
parameters: rawParams as Array<{
|
|
202
|
+
in: string
|
|
203
|
+
name: string
|
|
204
|
+
required: boolean
|
|
205
|
+
schema: Record<string, unknown>
|
|
206
|
+
}>,
|
|
197
207
|
path,
|
|
198
208
|
pathParams,
|
|
199
209
|
queryParams,
|
|
@@ -207,10 +217,12 @@ function collectOperations(spec: Record<string, unknown>): OperationInfo[] {
|
|
|
207
217
|
}
|
|
208
218
|
|
|
209
219
|
function nestedClassName(path: string[]): string {
|
|
210
|
-
const pascal = path
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
220
|
+
const pascal = path
|
|
221
|
+
.map((seg) => {
|
|
222
|
+
const safe = pyIdent(seg)
|
|
223
|
+
return safe.charAt(0).toUpperCase() + safe.slice(1)
|
|
224
|
+
})
|
|
225
|
+
.join("")
|
|
214
226
|
return `_${pascal}Resource`
|
|
215
227
|
}
|
|
216
228
|
|
|
@@ -392,10 +404,7 @@ function buildUrlLine(op: OperationInfo, isTopLevel: boolean): string {
|
|
|
392
404
|
|
|
393
405
|
function buildResponseLines(throwOnError: boolean): string[] {
|
|
394
406
|
if (throwOnError) {
|
|
395
|
-
return [
|
|
396
|
-
` _raise_for_status(_response)`,
|
|
397
|
-
` return _parse_body(_response)`,
|
|
398
|
-
]
|
|
407
|
+
return [` _raise_for_status(_response)`, ` return _parse_body(_response)`]
|
|
399
408
|
}
|
|
400
409
|
return [
|
|
401
410
|
` _body = _parse_body(_response)`,
|
|
@@ -461,7 +470,9 @@ function buildAsyncMethodBody(op: OperationInfo, throwOnError: boolean, isTopLev
|
|
|
461
470
|
} else if (op.isWS) {
|
|
462
471
|
lines.push(` _headers = _build_headers(${configRef}, extra=None)`)
|
|
463
472
|
} else if (op.bodyIsStream) {
|
|
464
|
-
lines.push(
|
|
473
|
+
lines.push(
|
|
474
|
+
` _headers = _build_headers(${configRef}, extra={"Content-Type": "application/octet-stream", **(headers or {})})`,
|
|
475
|
+
)
|
|
465
476
|
} else {
|
|
466
477
|
lines.push(` _headers = _build_headers(${configRef}, extra=headers)`)
|
|
467
478
|
}
|
|
@@ -585,7 +596,8 @@ function buildSyncMethodBody(op: OperationInfo, throwOnError: boolean, isTopLeve
|
|
|
585
596
|
|
|
586
597
|
function buildPyTypes(spec: Record<string, unknown>): string {
|
|
587
598
|
const schemas = (spec.components as Record<string, unknown> | undefined)?.schemas as
|
|
588
|
-
Record<string, Record<string, unknown>>
|
|
599
|
+
| Record<string, Record<string, unknown>>
|
|
600
|
+
| undefined
|
|
589
601
|
|
|
590
602
|
const body: string[] = []
|
|
591
603
|
const allNames: string[] = []
|
|
@@ -656,13 +668,13 @@ function buildPyClient(
|
|
|
656
668
|
const { opsByOpId, topLevel, tree } = collectSDKMethods(spec)
|
|
657
669
|
|
|
658
670
|
const schemas = (spec.components as Record<string, unknown> | undefined)?.schemas as
|
|
659
|
-
Record<string, Record<string, unknown>>
|
|
671
|
+
| Record<string, Record<string, unknown>>
|
|
672
|
+
| undefined
|
|
660
673
|
const schemaNames = schemas ? Object.keys(schemas) : []
|
|
661
674
|
|
|
662
675
|
const body: string[] = []
|
|
663
676
|
|
|
664
|
-
const invEntries = Object.entries(serviceMap)
|
|
665
|
-
.filter(([, v]) => v.invalidate && v.invalidate.length > 0)
|
|
677
|
+
const invEntries = Object.entries(serviceMap).filter(([, v]) => v.invalidate && v.invalidate.length > 0)
|
|
666
678
|
|
|
667
679
|
body.push("_INVALIDATION_MAP: dict[str, dict[str, Any]] = {")
|
|
668
680
|
for (const [opId, entry] of invEntries) {
|
|
@@ -779,7 +791,7 @@ function buildPyClient(
|
|
|
779
791
|
}
|
|
780
792
|
body.push("")
|
|
781
793
|
body.push(" def is_stale(self, method: str, path: str) -> bool:")
|
|
782
|
-
body.push(
|
|
794
|
+
body.push(' """Returns True when (method, path) sits inside an active stale window."""')
|
|
783
795
|
body.push(" return self._stale_tracker.is_stale(method, path)")
|
|
784
796
|
body.push("")
|
|
785
797
|
|
|
@@ -816,7 +828,7 @@ function buildPyClient(
|
|
|
816
828
|
}
|
|
817
829
|
body.push("")
|
|
818
830
|
body.push(" async def is_stale(self, method: str, path: str) -> bool:")
|
|
819
|
-
body.push(
|
|
831
|
+
body.push(' """Returns True when (method, path) sits inside an active stale window."""')
|
|
820
832
|
body.push(" return await self._stale_tracker.is_stale(method, path)")
|
|
821
833
|
body.push("")
|
|
822
834
|
|
|
@@ -845,7 +857,17 @@ function buildPyClient(
|
|
|
845
857
|
if (typingUsed.length > 0) imports.push(`from typing import ${typingUsed.join(", ")}`)
|
|
846
858
|
|
|
847
859
|
const runtimeUsed: string[] = []
|
|
848
|
-
for (const sym of [
|
|
860
|
+
for (const sym of [
|
|
861
|
+
"ClientConfig",
|
|
862
|
+
"InvalidationConfig",
|
|
863
|
+
"SDKResult",
|
|
864
|
+
"_build_headers",
|
|
865
|
+
"_build_url",
|
|
866
|
+
"_do_request_async",
|
|
867
|
+
"_do_request_sync",
|
|
868
|
+
"_parse_body",
|
|
869
|
+
"_raise_for_status",
|
|
870
|
+
]) {
|
|
849
871
|
if (new RegExp(`\\b${sym}\\b`).test(bodyText)) runtimeUsed.push(sym)
|
|
850
872
|
}
|
|
851
873
|
if (runtimeUsed.length > 0) {
|
|
@@ -972,7 +994,8 @@ function buildPyInit(spec: Record<string, unknown>): string {
|
|
|
972
994
|
l.push(")")
|
|
973
995
|
|
|
974
996
|
const schemas = (spec.components as Record<string, unknown> | undefined)?.schemas as
|
|
975
|
-
Record<string, Record<string, unknown>>
|
|
997
|
+
| Record<string, Record<string, unknown>>
|
|
998
|
+
| undefined
|
|
976
999
|
const schemaNames = schemas ? Object.keys(schemas).sort() : []
|
|
977
1000
|
if (schemaNames.length > 0) {
|
|
978
1001
|
l.push("from .types import (")
|
|
@@ -1007,10 +1030,7 @@ function buildServiceMap(spec: Record<string, unknown>): Record<string, ServiceM
|
|
|
1007
1030
|
return result
|
|
1008
1031
|
}
|
|
1009
1032
|
|
|
1010
|
-
export function generatePythonSDK(
|
|
1011
|
-
spec: Record<string, unknown>,
|
|
1012
|
-
options: PySDKOptions = {},
|
|
1013
|
-
): PySDKResult {
|
|
1033
|
+
export function generatePythonSDK(spec: Record<string, unknown>, options: PySDKOptions = {}): PySDKResult {
|
|
1014
1034
|
const templates = loadPythonRuntimeTemplates()
|
|
1015
1035
|
const serviceMap = buildServiceMap(spec)
|
|
1016
1036
|
|