@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.
Files changed (263) hide show
  1. package/README.md +1596 -6
  2. package/dist/accepts.d.ts +6 -0
  3. package/dist/accepts.d.ts.map +1 -0
  4. package/dist/body-limit.d.ts +22 -0
  5. package/dist/body-limit.d.ts.map +1 -0
  6. package/dist/build/index.d.ts +42 -0
  7. package/dist/build/index.d.ts.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/client/error.d.ts +14 -0
  11. package/dist/client/error.d.ts.map +1 -0
  12. package/dist/client/http.d.ts +88 -0
  13. package/dist/client/http.d.ts.map +1 -0
  14. package/dist/client/index.d.ts +22 -0
  15. package/dist/client/index.d.ts.map +1 -0
  16. package/dist/client/sdk.d.ts +34 -0
  17. package/dist/client/sdk.d.ts.map +1 -0
  18. package/dist/client/sse.d.ts +10 -0
  19. package/dist/client/sse.d.ts.map +1 -0
  20. package/dist/client/types.d.ts +124 -0
  21. package/dist/client/types.d.ts.map +1 -0
  22. package/dist/client/ws.d.ts +15 -0
  23. package/dist/client/ws.d.ts.map +1 -0
  24. package/dist/client-mcp/server.d.ts +10 -0
  25. package/dist/client-mcp/server.d.ts.map +1 -0
  26. package/dist/client-mcp/types.d.ts +16 -0
  27. package/dist/client-mcp/types.d.ts.map +1 -0
  28. package/dist/cloudflare/realtime.d.ts +30 -0
  29. package/dist/cloudflare/realtime.d.ts.map +1 -0
  30. package/dist/codegen-go-cli.d.ts +22 -0
  31. package/dist/codegen-go-cli.d.ts.map +1 -0
  32. package/dist/codegen-go.d.ts +29 -0
  33. package/dist/codegen-go.d.ts.map +1 -0
  34. package/dist/codegen-hash.d.ts +3 -0
  35. package/dist/codegen-hash.d.ts.map +1 -0
  36. package/dist/codegen-ir.d.ts +123 -0
  37. package/dist/codegen-ir.d.ts.map +1 -0
  38. package/dist/codegen-loaders.d.ts +12 -0
  39. package/dist/codegen-loaders.d.ts.map +1 -0
  40. package/dist/codegen-mcp.d.ts +13 -0
  41. package/dist/codegen-mcp.d.ts.map +1 -0
  42. package/dist/codegen-python.d.ts +21 -0
  43. package/dist/codegen-python.d.ts.map +1 -0
  44. package/dist/codegen-rust.d.ts +18 -0
  45. package/dist/codegen-rust.d.ts.map +1 -0
  46. package/dist/codegen-sanitize.d.ts +3 -0
  47. package/dist/codegen-sanitize.d.ts.map +1 -0
  48. package/dist/codegen-schema-naming.d.ts +22 -0
  49. package/dist/codegen-schema-naming.d.ts.map +1 -0
  50. package/dist/codegen.d.ts +178 -0
  51. package/dist/codegen.d.ts.map +1 -0
  52. package/dist/context.d.ts +56 -0
  53. package/dist/context.d.ts.map +1 -0
  54. package/dist/cookie-sign.d.ts +3 -0
  55. package/dist/cookie-sign.d.ts.map +1 -0
  56. package/dist/cookie.d.ts +12 -0
  57. package/dist/cookie.d.ts.map +1 -0
  58. package/dist/cors.d.ts +13 -0
  59. package/dist/cors.d.ts.map +1 -0
  60. package/dist/crypto.d.ts +2 -0
  61. package/dist/crypto.d.ts.map +1 -0
  62. package/dist/csrf.d.ts +9 -0
  63. package/dist/csrf.d.ts.map +1 -0
  64. package/dist/curl-logger.d.ts +33 -0
  65. package/dist/curl-logger.d.ts.map +1 -0
  66. package/dist/detect-runtime.d.ts +16 -0
  67. package/dist/detect-runtime.d.ts.map +1 -0
  68. package/dist/error.d.ts +21 -0
  69. package/dist/error.d.ts.map +1 -0
  70. package/dist/errors.d.ts +40 -0
  71. package/dist/errors.d.ts.map +1 -0
  72. package/dist/etag.d.ts +9 -0
  73. package/dist/etag.d.ts.map +1 -0
  74. package/dist/feature-load.d.ts +3 -0
  75. package/dist/feature-load.d.ts.map +1 -0
  76. package/dist/go-type-emitter.d.ts +33 -0
  77. package/dist/go-type-emitter.d.ts.map +1 -0
  78. package/dist/honey-response.d.ts +82 -0
  79. package/dist/honey-response.d.ts.map +1 -0
  80. package/dist/i18n-slot.d.ts +7 -0
  81. package/dist/i18n-slot.d.ts.map +1 -0
  82. package/dist/i18n.d.ts +26 -0
  83. package/dist/i18n.d.ts.map +1 -0
  84. package/dist/index.d.ts +470 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/init.d.ts +7 -0
  87. package/dist/init.d.ts.map +1 -0
  88. package/dist/input.d.ts +6 -0
  89. package/dist/input.d.ts.map +1 -0
  90. package/dist/ip-restrict.d.ts +12 -0
  91. package/dist/ip-restrict.d.ts.map +1 -0
  92. package/dist/logger.d.ts +45 -0
  93. package/dist/logger.d.ts.map +1 -0
  94. package/dist/meta-spec.d.ts +81 -0
  95. package/dist/meta-spec.d.ts.map +1 -0
  96. package/dist/middleware.d.ts +60 -0
  97. package/dist/middleware.d.ts.map +1 -0
  98. package/dist/node-request.d.ts +59 -0
  99. package/dist/node-request.d.ts.map +1 -0
  100. package/dist/node.d.ts +13 -0
  101. package/dist/node.d.ts.map +1 -0
  102. package/dist/openapi/register.d.ts +3 -0
  103. package/dist/openapi/register.d.ts.map +1 -0
  104. package/dist/openapi/scalar.d.ts +12 -0
  105. package/dist/openapi/scalar.d.ts.map +1 -0
  106. package/dist/openapi/spec-factory.d.ts +31 -0
  107. package/dist/openapi/spec-factory.d.ts.map +1 -0
  108. package/dist/openapi/spec.d.ts +19 -0
  109. package/dist/openapi/spec.d.ts.map +1 -0
  110. package/dist/openapi/swagger.d.ts +12 -0
  111. package/dist/openapi/swagger.d.ts.map +1 -0
  112. package/dist/plugin.d.ts +167 -0
  113. package/dist/plugin.d.ts.map +1 -0
  114. package/dist/powered-by.d.ts +7 -0
  115. package/dist/powered-by.d.ts.map +1 -0
  116. package/dist/pretty-json.d.ts +10 -0
  117. package/dist/pretty-json.d.ts.map +1 -0
  118. package/dist/proxy.d.ts +51 -0
  119. package/dist/proxy.d.ts.map +1 -0
  120. package/dist/python-type-emitter.d.ts +11 -0
  121. package/dist/python-type-emitter.d.ts.map +1 -0
  122. package/dist/realtime/buffer.d.ts +18 -0
  123. package/dist/realtime/buffer.d.ts.map +1 -0
  124. package/dist/realtime/bus.d.ts +11 -0
  125. package/dist/realtime/bus.d.ts.map +1 -0
  126. package/dist/realtime/longpoll.d.ts +23 -0
  127. package/dist/realtime/longpoll.d.ts.map +1 -0
  128. package/dist/realtime/protocol.d.ts +50 -0
  129. package/dist/realtime/protocol.d.ts.map +1 -0
  130. package/dist/realtime/route.d.ts +42 -0
  131. package/dist/realtime/route.d.ts.map +1 -0
  132. package/dist/realtime/runtime.d.ts +74 -0
  133. package/dist/realtime/runtime.d.ts.map +1 -0
  134. package/dist/request-id.d.ts +12 -0
  135. package/dist/request-id.d.ts.map +1 -0
  136. package/dist/request-to-curl.d.ts +9 -0
  137. package/dist/request-to-curl.d.ts.map +1 -0
  138. package/dist/response.d.ts +57 -0
  139. package/dist/response.d.ts.map +1 -0
  140. package/dist/rust-type-emitter.d.ts +32 -0
  141. package/dist/rust-type-emitter.d.ts.map +1 -0
  142. package/dist/secure-headers.d.ts +16 -0
  143. package/dist/secure-headers.d.ts.map +1 -0
  144. package/dist/serve-register.d.ts +2 -0
  145. package/dist/serve-register.d.ts.map +1 -0
  146. package/dist/serve-slot.d.ts +6 -0
  147. package/dist/serve-slot.d.ts.map +1 -0
  148. package/dist/serve.d.ts +21 -0
  149. package/dist/serve.d.ts.map +1 -0
  150. package/dist/server-timing.d.ts +9 -0
  151. package/dist/server-timing.d.ts.map +1 -0
  152. package/dist/static.d.ts +12 -0
  153. package/dist/static.d.ts.map +1 -0
  154. package/dist/telemetry/otel.d.ts +59 -0
  155. package/dist/telemetry/otel.d.ts.map +1 -0
  156. package/dist/testing.d.ts +24 -0
  157. package/dist/testing.d.ts.map +1 -0
  158. package/dist/timeout.d.ts +7 -0
  159. package/dist/timeout.d.ts.map +1 -0
  160. package/dist/tree.d.ts +90 -0
  161. package/dist/tree.d.ts.map +1 -0
  162. package/dist/ts-type-emitter.d.ts +4 -0
  163. package/dist/ts-type-emitter.d.ts.map +1 -0
  164. package/dist/type-emitter.d.ts +8 -0
  165. package/dist/type-emitter.d.ts.map +1 -0
  166. package/dist/type-extractor.d.ts +39 -0
  167. package/dist/type-extractor.d.ts.map +1 -0
  168. package/dist/types.d.ts +451 -0
  169. package/dist/types.d.ts.map +1 -0
  170. package/dist/validation.d.ts +18 -0
  171. package/dist/validation.d.ts.map +1 -0
  172. package/dist/ws/bun.d.ts +28 -0
  173. package/dist/ws/bun.d.ts.map +1 -0
  174. package/dist/ws/cloudflare.d.ts +55 -0
  175. package/dist/ws/cloudflare.d.ts.map +1 -0
  176. package/dist/ws/deno.d.ts +13 -0
  177. package/dist/ws/deno.d.ts.map +1 -0
  178. package/dist/ws/node.d.ts +10 -0
  179. package/dist/ws/node.d.ts.map +1 -0
  180. package/dist/yaml.d.ts +4 -0
  181. package/dist/yaml.d.ts.map +1 -0
  182. package/docs/api-ref/go.md +82 -82
  183. package/docs/api-ref/python.md +82 -82
  184. package/docs/api-ref/rust.md +82 -82
  185. package/docs/api-ref/typescript.md +82 -82
  186. package/docs/invalidate-spec.md +13 -13
  187. package/docs/meta-spec.md +513 -0
  188. package/docs/packaging.md +85 -0
  189. package/docs/sdk.md +87 -73
  190. package/package.json +195 -51
  191. package/src/body-limit.ts +2 -1
  192. package/src/build/index.ts +2 -9
  193. package/src/cli.ts +2 -3
  194. package/src/client/http.ts +204 -223
  195. package/src/client/index.ts +6 -14
  196. package/src/client/sdk.ts +24 -17
  197. package/src/client/types.ts +4 -9
  198. package/src/client/ws.ts +3 -5
  199. package/src/client-mcp/server.ts +1 -4
  200. package/src/cloudflare/realtime.ts +8 -10
  201. package/src/codegen-go-cli.ts +74 -67
  202. package/src/codegen-go.ts +71 -51
  203. package/src/codegen-ir.ts +16 -27
  204. package/src/codegen-loaders.ts +5 -8
  205. package/src/codegen-mcp.ts +9 -27
  206. package/src/codegen-python.ts +47 -27
  207. package/src/codegen-rust.ts +110 -66
  208. package/src/codegen-sanitize.ts +1 -7
  209. package/src/codegen-schema-naming.ts +8 -2
  210. package/src/codegen.ts +299 -196
  211. package/src/context.ts +91 -95
  212. package/src/cookie-sign.ts +5 -13
  213. package/src/cookie.ts +1 -2
  214. package/src/cors.ts +4 -6
  215. package/src/crypto.ts +1 -4
  216. package/src/csrf.ts +3 -3
  217. package/src/curl-logger.ts +14 -32
  218. package/src/detect-runtime.ts +1 -3
  219. package/src/errors.ts +1 -3
  220. package/src/etag.ts +3 -4
  221. package/src/go-type-emitter.ts +45 -40
  222. package/src/honey-response.ts +1 -3
  223. package/src/i18n.ts +3 -12
  224. package/src/index.ts +1168 -1412
  225. package/src/ip-restrict.ts +2 -1
  226. package/src/logger.ts +13 -13
  227. package/src/meta-spec.ts +0 -0
  228. package/src/middleware.ts +111 -79
  229. package/src/node-request.ts +2 -1
  230. package/src/node.ts +9 -10
  231. package/src/openapi/register.ts +1 -2
  232. package/src/openapi/scalar.ts +9 -9
  233. package/src/openapi/spec-factory.ts +2 -2
  234. package/src/openapi/spec.ts +26 -19
  235. package/src/openapi/swagger.ts +9 -9
  236. package/src/plugin.ts +226 -256
  237. package/src/powered-by.ts +2 -1
  238. package/src/pretty-json.ts +2 -1
  239. package/src/proxy.ts +1 -3
  240. package/src/python-type-emitter.ts +37 -12
  241. package/src/realtime/route.ts +1 -1
  242. package/src/realtime/runtime.ts +9 -12
  243. package/src/request-id.ts +3 -4
  244. package/src/request-to-curl.ts +1 -4
  245. package/src/response.ts +150 -199
  246. package/src/rust-type-emitter.ts +66 -25
  247. package/src/secure-headers.ts +2 -1
  248. package/src/serve.ts +3 -12
  249. package/src/server-timing.ts +2 -1
  250. package/src/static.ts +3 -4
  251. package/src/telemetry/otel.ts +1 -6
  252. package/src/testing.ts +2 -11
  253. package/src/timeout.ts +2 -1
  254. package/src/tree.ts +12 -27
  255. package/src/ts-type-emitter.ts +71 -15
  256. package/src/type-emitter.ts +226 -237
  257. package/src/type-extractor.ts +260 -334
  258. package/src/types.ts +168 -68
  259. package/src/validation.ts +7 -30
  260. package/src/ws/bun.ts +2 -9
  261. package/src/ws/cloudflare.ts +13 -16
  262. package/src/ws/deno.ts +31 -40
  263. package/src/ws/node.ts +4 -19
@@ -6,7 +6,15 @@ import { collectSDKMethods, isSSEOperation, isStandardErrEnvelope } from "./code
6
6
  import { detectAuthScheme } from "./codegen-go.ts"
7
7
  import { methodsOf, namespacesOf, toIR } from "./codegen-ir.ts"
8
8
  import type { IRNamespace, IROperation } from "./codegen-ir.ts"
9
- import { RUST_KEYWORDS, renderTopLevelRust, renderUseRust, rustIdent, rustMethodIdent, rustPascal, rustSnake } from "./rust-type-emitter.ts"
9
+ import {
10
+ RUST_KEYWORDS,
11
+ renderTopLevelRust,
12
+ renderUseRust,
13
+ rustIdent,
14
+ rustMethodIdent,
15
+ rustPascal,
16
+ rustSnake,
17
+ } from "./rust-type-emitter.ts"
10
18
 
11
19
  export type RustSDKOptions = {
12
20
  crateName?: string
@@ -37,7 +45,17 @@ let runtimeCache: Map<string, string> | null = null
37
45
  export function loadRustRuntimeTemplates(): Map<string, string> {
38
46
  if (runtimeCache) return runtimeCache
39
47
 
40
- const names = ["errors.rs", "result.rs", "runtime.rs", "invalidation.rs", "invalidation_sync.rs", "sse.rs", "ws.rs", "realtime.rs", "runtime_sync.rs"]
48
+ const names = [
49
+ "errors.rs",
50
+ "result.rs",
51
+ "runtime.rs",
52
+ "invalidation.rs",
53
+ "invalidation_sync.rs",
54
+ "sse.rs",
55
+ "ws.rs",
56
+ "realtime.rs",
57
+ "runtime_sync.rs",
58
+ ]
41
59
  const cache = new Map<string, string>()
42
60
  for (const name of names) {
43
61
  const filePath = fileURLToPath(new URL(`./client-rust/${name}`, import.meta.url))
@@ -56,9 +74,7 @@ export function loadRustRuntimeTemplates(): Map<string, string> {
56
74
  /** Validates and normalises a crate name. Throws if invalid per cargo rules. */
57
75
  export function validateCrateName(name: string): string {
58
76
  if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
59
- throw new Error(
60
- `Invalid crate name: ${JSON.stringify(name)}. Must match ^[a-z][a-z0-9_-]*$`,
61
- )
77
+ throw new Error(`Invalid crate name: ${JSON.stringify(name)}. Must match ^[a-z][a-z0-9_-]*$`)
62
78
  }
63
79
  return name
64
80
  }
@@ -271,10 +287,7 @@ function invalidatesOf(irOp: IROperation, rawOp: Record<string, unknown>): strin
271
287
  return []
272
288
  }
273
289
 
274
- function methodInfoFromIROp(
275
- irOp: IROperation,
276
- rawOp: Record<string, unknown>,
277
- ): MethodInfo {
290
+ function methodInfoFromIROp(irOp: IROperation, rawOp: Record<string, unknown>): MethodInfo {
278
291
  const segments = irOp.id.split(".")
279
292
  const isTopLevel = segments.length === 1
280
293
  const resource = segments[0] ?? irOp.id
@@ -448,7 +461,9 @@ function emitSSEMethod(
448
461
  if (qp.required) {
449
462
  l.push(`\tquery.push((${JSON.stringify(qp.name)}.to_string(), ${fieldIdent}_owned.clone()));`)
450
463
  } else {
451
- l.push(`\tif let Some(ref v) = ${fieldIdent}_owned { query.push((${JSON.stringify(qp.name)}.to_string(), v.clone())); }`)
464
+ l.push(
465
+ `\tif let Some(ref v) = ${fieldIdent}_owned { query.push((${JSON.stringify(qp.name)}.to_string(), v.clone())); }`,
466
+ )
452
467
  }
453
468
  }
454
469
  l.push(`\tlet cfg_clone = ${cfgAccess}.clone();`)
@@ -504,7 +519,9 @@ function emitWSMethod(
504
519
  const fieldIdent = rustIdent(qp.name)
505
520
  if (qp.required) {
506
521
  l.push(`\tif !query_str.is_empty() { query_str.push('&'); }`)
507
- l.push(`\tquery_str.push_str(&format!("{}={}", ${JSON.stringify(qp.name)}, urlencoding::encode(&opts.${fieldIdent})));`)
522
+ l.push(
523
+ `\tquery_str.push_str(&format!("{}={}", ${JSON.stringify(qp.name)}, urlencoding::encode(&opts.${fieldIdent})));`,
524
+ )
508
525
  } else {
509
526
  l.push(`\tif let Some(ref v) = opts.${fieldIdent} {`)
510
527
  l.push(`\t\tif !query_str.is_empty() { query_str.push('&'); }`)
@@ -530,7 +547,9 @@ function emitWSMethod(
530
547
  l.push(`\t\tif !protos.is_empty() {`)
531
548
  l.push(`\t\t\treq.headers_mut().insert(`)
532
549
  l.push(`\t\t\t\t"Sec-WebSocket-Protocol",`)
533
- l.push(`\t\t\t\tprotos.join(", ").parse().map_err(|e: tokio_tungstenite::tungstenite::http::header::InvalidHeaderValue| Error::Other(e.to_string()))?,`)
550
+ l.push(
551
+ `\t\t\t\tprotos.join(", ").parse().map_err(|e: tokio_tungstenite::tungstenite::http::header::InvalidHeaderValue| Error::Other(e.to_string()))?,`,
552
+ )
534
553
  l.push(`\t\t\t);`)
535
554
  l.push(`\t\t}`)
536
555
  l.push(`\t}`)
@@ -548,11 +567,7 @@ function buildAccessors(selfAccess: "self" | "self.client"): { cfg: string; stal
548
567
  : { cfg: "self.client.cfg", stale: "self.client.stale", httpClient: "self.client.http_client" }
549
568
  }
550
569
 
551
- function emitMethod(
552
- m: MethodInfo,
553
- selfAccess: "self" | "self.client",
554
- throwOnError: boolean,
555
- ): string[] {
570
+ function emitMethod(m: MethodInfo, selfAccess: "self" | "self.client", throwOnError: boolean): string[] {
556
571
  const l: string[] = []
557
572
  const actionFn = rustMethodIdent(m.action === "_call" ? m.resource : m.methodAction)
558
573
  const optsName = optsStructName(m)
@@ -582,9 +597,7 @@ function emitMethod(
582
597
  } else if (bi.kind === "stream") {
583
598
  /* octet-stream body: generic over any `Stream<Item = Result<Bytes, reqwest::Error>>`
584
599
  * (reqwest::Body::wrap_stream bound). Owned body, single-shot, non-retryable on 401. */
585
- asyncGenerics.push(
586
- `S: futures::Stream<Item = Result<bytes::Bytes, reqwest::Error>> + Send + Sync + 'static`,
587
- )
600
+ asyncGenerics.push(`S: futures::Stream<Item = Result<bytes::Bytes, reqwest::Error>> + Send + Sync + 'static`)
588
601
  params.push(`body: S`)
589
602
  }
590
603
 
@@ -612,7 +625,9 @@ function emitMethod(
612
625
  if (qp.required) {
613
626
  l.push(`\tquery.push((${JSON.stringify(qp.name)}.to_string(), ${fieldIdent}_owned.clone()));`)
614
627
  } else {
615
- l.push(`\tif let Some(ref v) = ${fieldIdent}_owned { query.push((${JSON.stringify(qp.name)}.to_string(), v.clone())); }`)
628
+ l.push(
629
+ `\tif let Some(ref v) = ${fieldIdent}_owned { query.push((${JSON.stringify(qp.name)}.to_string(), v.clone())); }`,
630
+ )
616
631
  }
617
632
  }
618
633
  l.push(`\tlet cfg_clone = ${cfgAccess}.clone();`)
@@ -661,7 +676,9 @@ function emitMethod(
661
676
  const fieldIdent = rustIdent(qp.name)
662
677
  if (qp.required) {
663
678
  l.push(`\tif !query_str.is_empty() { query_str.push('&'); }`)
664
- l.push(`\tquery_str.push_str(&format!("{}={}", ${JSON.stringify(qp.name)}, urlencoding::encode(&opts.${fieldIdent})));`)
679
+ l.push(
680
+ `\tquery_str.push_str(&format!("{}={}", ${JSON.stringify(qp.name)}, urlencoding::encode(&opts.${fieldIdent})));`,
681
+ )
665
682
  } else {
666
683
  l.push(`\tif let Some(ref v) = opts.${fieldIdent} {`)
667
684
  l.push(`\t\tif !query_str.is_empty() { query_str.push('&'); }`)
@@ -687,7 +704,9 @@ function emitMethod(
687
704
  l.push(`\t\tif !protos.is_empty() {`)
688
705
  l.push(`\t\t\treq.headers_mut().insert(`)
689
706
  l.push(`\t\t\t\t"Sec-WebSocket-Protocol",`)
690
- l.push(`\t\t\t\tprotos.join(", ").parse().map_err(|e: tokio_tungstenite::tungstenite::http::header::InvalidHeaderValue| Error::Other(e.to_string()))?,`)
707
+ l.push(
708
+ `\t\t\t\tprotos.join(", ").parse().map_err(|e: tokio_tungstenite::tungstenite::http::header::InvalidHeaderValue| Error::Other(e.to_string()))?,`,
709
+ )
691
710
  l.push(`\t\t\t);`)
692
711
  l.push(`\t\t}`)
693
712
  l.push(`\t}`)
@@ -741,14 +760,19 @@ function emitMethod(
741
760
  * with x-invalidate; BuildRequestMeta short-circuits to None when disabled. */
742
761
  const hasInvalidate = m.invalidates.length > 0
743
762
  if (hasInvalidate) {
744
- const pathParamsMapExpr = m.pathParams.length > 0
745
- ? `std::collections::HashMap::from([${m.pathParams.map((p) => `(${JSON.stringify(p)}.to_string(), ${rustSnake(p)}.to_string())`).join(", ")}])`
746
- : `std::collections::HashMap::<String, String>::new()`
763
+ const pathParamsMapExpr =
764
+ m.pathParams.length > 0
765
+ ? `std::collections::HashMap::from([${m.pathParams.map((p) => `(${JSON.stringify(p)}.to_string(), ${rustSnake(p)}.to_string())`).join(", ")}])`
766
+ : `std::collections::HashMap::<String, String>::new()`
747
767
  l.push(`\tlet _path_params_map: std::collections::HashMap<String, String> = ${pathParamsMapExpr};`)
748
- l.push(`\tlet _concrete_path = crate::invalidation::interpolate_path(${JSON.stringify(m.path)}, &_path_params_map).unwrap_or_else(|_| ${JSON.stringify(m.path)}.to_string());`)
768
+ l.push(
769
+ `\tlet _concrete_path = crate::invalidation::interpolate_path(${JSON.stringify(m.path)}, &_path_params_map).unwrap_or_else(|_| ${JSON.stringify(m.path)}.to_string());`,
770
+ )
749
771
  l.push(`\tlet _concrete_selector = format!("${httpMethod} {}", _concrete_path);`)
750
772
  l.push(`\tlet _request_meta: Option<crate::invalidation::RequestMeta> = match ${staleAccess} {`)
751
- l.push(`\t\tSome(ref s) => s.build_request_meta(&_concrete_selector, &_concrete_path, ${JSON.stringify(httpMethod)}).await,`)
773
+ l.push(
774
+ `\t\tSome(ref s) => s.build_request_meta(&_concrete_selector, &_concrete_path, ${JSON.stringify(httpMethod)}).await,`,
775
+ )
752
776
  l.push(`\t\tNone => None,`)
753
777
  l.push(`\t};`)
754
778
  }
@@ -761,7 +785,9 @@ function emitMethod(
761
785
  * Matches TS/Python/Go emitter semantics (9.a.2 / 9.a.3 / 9.a.4). */
762
786
  const emitIdem = m.isIdempotent && !m.isSSE && !m.isWS && !m.isRealtime
763
787
  if (emitIdem) {
764
- l.push(`\tlet mut _call_headers: std::collections::HashMap<String, String> = opts.headers.clone().unwrap_or_default();`)
788
+ l.push(
789
+ `\tlet mut _call_headers: std::collections::HashMap<String, String> = opts.headers.clone().unwrap_or_default();`,
790
+ )
765
791
  l.push(`\tlet _has_idem = _call_headers.keys().any(|k| k.eq_ignore_ascii_case("Idempotency-Key"));`)
766
792
  l.push(`\tif !_has_idem {`)
767
793
  l.push(`\t\tlet _k = opts.idempotency_key.clone().unwrap_or_else(|| uuid::Uuid::new_v4().to_string());`)
@@ -800,7 +826,9 @@ function emitMethod(
800
826
  l.push(`\t\t}`)
801
827
  l.push(`\t\tif let Some(ref meta) = _request_meta {`)
802
828
  l.push(`\t\t\tif meta.is_stale {`)
803
- l.push(`\t\t\t\tstale.clear_stale(&_concrete_selector, &_concrete_path, ${JSON.stringify(httpMethod)}, meta.seq_snapshot).await;`)
829
+ l.push(
830
+ `\t\t\t\tstale.clear_stale(&_concrete_selector, &_concrete_path, ${JSON.stringify(httpMethod)}, meta.seq_snapshot).await;`,
831
+ )
804
832
  l.push(`\t\t\t}`)
805
833
  l.push(`\t\t}`)
806
834
  l.push(`\t}`)
@@ -818,7 +846,9 @@ function emitMethod(
818
846
  l.push(`\tOk(out)`)
819
847
  } else {
820
848
  l.push(`\tlet out: ${m.responseType} = serde_json::from_slice(&result.body)?;`)
821
- l.push(`\tOk(SdkResult { data: Some(out), error: None, status: result.status, response: crate::runtime::ResponseMeta { status: result.status, headers: result.headers.clone(), url: result.url.clone(), body: result.body.clone() } })`)
849
+ l.push(
850
+ `\tOk(SdkResult { data: Some(out), error: None, status: result.status, response: crate::runtime::ResponseMeta { status: result.status, headers: result.headers.clone(), url: result.url.clone(), body: result.body.clone() } })`,
851
+ )
822
852
  }
823
853
 
824
854
  l.push(`}`)
@@ -827,11 +857,7 @@ function emitMethod(
827
857
  }
828
858
 
829
859
  /** emitSyncMethod is the blocking twin of emitMethod — no async, calls do_request_blocking. */
830
- function emitSyncMethod(
831
- m: MethodInfo,
832
- selfAccess: "self" | "self.client",
833
- throwOnError: boolean,
834
- ): string[] {
860
+ function emitSyncMethod(m: MethodInfo, selfAccess: "self" | "self.client", throwOnError: boolean): string[] {
835
861
  /* SSE and WS operations have no sync variant */
836
862
  if (m.isSSE || m.isWS) return []
837
863
 
@@ -902,14 +928,19 @@ function emitSyncMethod(
902
928
  /* invalidation pre-flight (sync) — same shape as async, no .await. */
903
929
  const hasInvalidate = m.invalidates.length > 0
904
930
  if (hasInvalidate) {
905
- const pathParamsMapExpr = m.pathParams.length > 0
906
- ? `std::collections::HashMap::from([${m.pathParams.map((p) => `(${JSON.stringify(p)}.to_string(), ${rustSnake(p)}.to_string())`).join(", ")}])`
907
- : `std::collections::HashMap::<String, String>::new()`
931
+ const pathParamsMapExpr =
932
+ m.pathParams.length > 0
933
+ ? `std::collections::HashMap::from([${m.pathParams.map((p) => `(${JSON.stringify(p)}.to_string(), ${rustSnake(p)}.to_string())`).join(", ")}])`
934
+ : `std::collections::HashMap::<String, String>::new()`
908
935
  l.push(`\tlet _path_params_map: std::collections::HashMap<String, String> = ${pathParamsMapExpr};`)
909
- l.push(`\tlet _concrete_path = crate::invalidation::interpolate_path(${JSON.stringify(m.path)}, &_path_params_map).unwrap_or_else(|_| ${JSON.stringify(m.path)}.to_string());`)
936
+ l.push(
937
+ `\tlet _concrete_path = crate::invalidation::interpolate_path(${JSON.stringify(m.path)}, &_path_params_map).unwrap_or_else(|_| ${JSON.stringify(m.path)}.to_string());`,
938
+ )
910
939
  l.push(`\tlet _concrete_selector = format!("${httpMethod} {}", _concrete_path);`)
911
940
  l.push(`\tlet _request_meta: Option<crate::invalidation::RequestMeta> = match ${staleAccess} {`)
912
- l.push(`\t\tSome(ref s) => s.build_request_meta(&_concrete_selector, &_concrete_path, ${JSON.stringify(httpMethod)}),`)
941
+ l.push(
942
+ `\t\tSome(ref s) => s.build_request_meta(&_concrete_selector, &_concrete_path, ${JSON.stringify(httpMethod)}),`,
943
+ )
913
944
  l.push(`\t\tNone => None,`)
914
945
  l.push(`\t};`)
915
946
  }
@@ -917,7 +948,9 @@ function emitSyncMethod(
917
948
  /* idempotency-key auto-injection (sync mirror of async path — same semantics). */
918
949
  const emitIdem = m.isIdempotent && !m.isSSE && !m.isWS && !m.isRealtime
919
950
  if (emitIdem) {
920
- l.push(`\tlet mut _call_headers: std::collections::HashMap<String, String> = opts.headers.clone().unwrap_or_default();`)
951
+ l.push(
952
+ `\tlet mut _call_headers: std::collections::HashMap<String, String> = opts.headers.clone().unwrap_or_default();`,
953
+ )
921
954
  l.push(`\tlet _has_idem = _call_headers.keys().any(|k| k.eq_ignore_ascii_case("Idempotency-Key"));`)
922
955
  l.push(`\tif !_has_idem {`)
923
956
  l.push(`\t\tlet _k = opts.idempotency_key.clone().unwrap_or_else(|| uuid::Uuid::new_v4().to_string());`)
@@ -952,7 +985,9 @@ function emitSyncMethod(
952
985
  l.push(`\t\t}`)
953
986
  l.push(`\t\tif let Some(ref meta) = _request_meta {`)
954
987
  l.push(`\t\t\tif meta.is_stale {`)
955
- l.push(`\t\t\t\tstale.clear_stale(&_concrete_selector, &_concrete_path, ${JSON.stringify(httpMethod)}, meta.seq_snapshot);`)
988
+ l.push(
989
+ `\t\t\t\tstale.clear_stale(&_concrete_selector, &_concrete_path, ${JSON.stringify(httpMethod)}, meta.seq_snapshot);`,
990
+ )
956
991
  l.push(`\t\t\t}`)
957
992
  l.push(`\t\t}`)
958
993
  l.push(`\t}`)
@@ -970,7 +1005,9 @@ function emitSyncMethod(
970
1005
  l.push(`\tOk(out)`)
971
1006
  } else {
972
1007
  l.push(`\tlet out: ${m.responseType} = serde_json::from_slice(&result.body)?;`)
973
- l.push(`\tOk(SdkResult { data: Some(out), error: None, status: result.status, response: crate::runtime::ResponseMeta { status: result.status, headers: result.headers.clone(), url: result.url.clone(), body: result.body.clone() } })`)
1008
+ l.push(
1009
+ `\tOk(SdkResult { data: Some(out), error: None, status: result.status, response: crate::runtime::ResponseMeta { status: result.status, headers: result.headers.clone(), url: result.url.clone(), body: result.body.clone() } })`,
1010
+ )
974
1011
  }
975
1012
 
976
1013
  l.push(`}`)
@@ -1006,7 +1043,9 @@ function emitQueryParamLoop(qps: QueryParam[], l: string[]): void {
1006
1043
  l.push(`\tquery.push((${JSON.stringify(qp.name)}.to_string(), ${expr}));`)
1007
1044
  } else {
1008
1045
  const expr = rustQueryValueToString(qp.schema, `v`)
1009
- l.push(`\tif let Some(ref v) = opts.${fieldIdent} { query.push((${JSON.stringify(qp.name)}.to_string(), ${expr})); }`)
1046
+ l.push(
1047
+ `\tif let Some(ref v) = opts.${fieldIdent} { query.push((${JSON.stringify(qp.name)}.to_string(), ${expr})); }`,
1048
+ )
1010
1049
  }
1011
1050
  }
1012
1051
  }
@@ -1093,7 +1132,10 @@ function buildRustTypes(spec: OpenApiSpec): string {
1093
1132
  return l.join("\n")
1094
1133
  }
1095
1134
 
1096
- function buildCargoToml(crateName: string, meta: { version: string; description?: string; homepage?: string; repository?: string; license?: string }): string {
1135
+ function buildCargoToml(
1136
+ crateName: string,
1137
+ meta: { version: string; description?: string; homepage?: string; repository?: string; license?: string },
1138
+ ): string {
1097
1139
  const pkgLines = [
1098
1140
  `[package]`,
1099
1141
  `name = "${crateName}"`,
@@ -1186,23 +1228,25 @@ function buildRustClient(spec: OpenApiSpec, options: RustSDKOptions, ir: ReturnT
1186
1228
  body.push(`}`)
1187
1229
  body.push(``)
1188
1230
 
1189
- body.push(`pub(crate) static INVALIDATION_MAP: once_cell::sync::Lazy<std::collections::HashMap<&'static str, std::collections::HashMap<&'static str, ServiceEntry>>> =`)
1231
+ body.push(
1232
+ `pub(crate) static INVALIDATION_MAP: once_cell::sync::Lazy<std::collections::HashMap<&'static str, std::collections::HashMap<&'static str, ServiceEntry>>> =`,
1233
+ )
1190
1234
  body.push(`\tonce_cell::sync::Lazy::new(|| {`)
1191
1235
  body.push(`\t\tlet mut map = std::collections::HashMap::new();`)
1192
1236
  for (const [resource, actions] of Object.entries(serviceMap).sort(([a], [b]) => a.localeCompare(b))) {
1193
1237
  body.push(`\t\t{`)
1194
1238
  body.push(`\t\t\tlet mut resource_map = std::collections::HashMap::new();`)
1195
- for (const [action, entry] of Object.entries(actions as Record<string, Record<string, unknown>>).sort(([a], [b]) => a.localeCompare(b))) {
1239
+ for (const [action, entry] of Object.entries(actions as Record<string, Record<string, unknown>>).sort(([a], [b]) =>
1240
+ a.localeCompare(b),
1241
+ )) {
1196
1242
  const inv = entry.invalidate as string[] | undefined
1197
- const invStr = inv && inv.length > 0
1198
- ? `&[${inv.map((s) => JSON.stringify(s)).join(", ")}]`
1199
- : `&[]`
1243
+ const invStr = inv && inv.length > 0 ? `&[${inv.map((s) => JSON.stringify(s)).join(", ")}]` : `&[]`
1200
1244
  const params = entry.params as string[] | undefined
1201
- const paramsStr = params && params.length > 0
1202
- ? `&[${params.map((s) => JSON.stringify(s)).join(", ")}]`
1203
- : `&[]`
1245
+ const paramsStr = params && params.length > 0 ? `&[${params.map((s) => JSON.stringify(s)).join(", ")}]` : `&[]`
1204
1246
  const isRealtime = realtimeLookup.get(`${resource}.${action}`) ?? false
1205
- body.push(`\t\t\tresource_map.insert(${JSON.stringify(action)}, ServiceEntry { method: ${JSON.stringify(String(entry.method))}, path: ${JSON.stringify(String(entry.path))}, params: ${paramsStr}, sse: ${entry.sse === true}, ws: ${entry.ws === true}, realtime: ${isRealtime}, invalidate: ${invStr} });`)
1247
+ body.push(
1248
+ `\t\t\tresource_map.insert(${JSON.stringify(action)}, ServiceEntry { method: ${JSON.stringify(String(entry.method))}, path: ${JSON.stringify(String(entry.path))}, params: ${paramsStr}, sse: ${entry.sse === true}, ws: ${entry.ws === true}, realtime: ${isRealtime}, invalidate: ${invStr} });`,
1249
+ )
1206
1250
  }
1207
1251
  body.push(`\t\t\tmap.insert(${JSON.stringify(resource)}, resource_map);`)
1208
1252
  body.push(`\t\t}`)
@@ -1256,7 +1300,9 @@ function buildRustClient(spec: OpenApiSpec, options: RustSDKOptions, ir: ReturnT
1256
1300
  body.push(`\t\tClient {`)
1257
1301
  for (const [seg] of namespacesOf(ir.tree)) {
1258
1302
  const safeName = safeResourceName(seg)
1259
- body.push(`\t\t\t${rustSnake(safeName)}: crate::resources::${rustSnake(safeName)}::new_${rustSnake(safeName)}_resource(&inner),`)
1303
+ body.push(
1304
+ `\t\t\t${rustSnake(safeName)}: crate::resources::${rustSnake(safeName)}::new_${rustSnake(safeName)}_resource(&inner),`,
1305
+ )
1260
1306
  }
1261
1307
  body.push(`\t\t\tinner,`)
1262
1308
  body.push(`\t\t}`)
@@ -1353,14 +1399,15 @@ function buildRustClient(spec: OpenApiSpec, options: RustSDKOptions, ir: ReturnT
1353
1399
  body.push(`\t\tSyncClient {`)
1354
1400
  for (const seg of syncNsSegs) {
1355
1401
  const safeName = safeResourceName(seg)
1356
- body.push(`\t\t\t${rustSnake(safeName)}: ${rustPascal(safeName)}ResourceSync { client: std::sync::Arc::clone(&inner) },`)
1402
+ body.push(
1403
+ `\t\t\t${rustSnake(safeName)}: ${rustPascal(safeName)}ResourceSync { client: std::sync::Arc::clone(&inner) },`,
1404
+ )
1357
1405
  }
1358
1406
  body.push(`\t\t\tinner,`)
1359
1407
  body.push(`\t\t}`)
1360
1408
  body.push(`\t}`)
1361
1409
  body.push(``)
1362
1410
 
1363
-
1364
1411
  for (const seg of syncNsSegs) {
1365
1412
  const safeName = safeResourceName(seg)
1366
1413
  const structName = `${rustPascal(safeName)}ResourceSync`
@@ -1539,7 +1586,9 @@ function buildRustResources(
1539
1586
 
1540
1587
  /* constructor free-fn used by Client::new / parent resource constructors */
1541
1588
  const constructorFn = `new_${safePath.map(rustSnake).join("_")}_resource`
1542
- fileLines.push(`pub(crate) fn ${constructorFn}(client: &std::sync::Arc<crate::client::ClientInner>) -> ${structName} {`)
1589
+ fileLines.push(
1590
+ `pub(crate) fn ${constructorFn}(client: &std::sync::Arc<crate::client::ClientInner>) -> ${structName} {`,
1591
+ )
1543
1592
  fileLines.push(`\t${structName} {`)
1544
1593
  fileLines.push(`\t\tclient: std::sync::Arc::clone(client),`)
1545
1594
  for (const childSeg of childSegs) {
@@ -1585,9 +1634,7 @@ function buildRustResources(
1585
1634
 
1586
1635
  /* determine file path: namespace with children → dir/mod.rs, leaf → dir.rs */
1587
1636
  const dirPath = safePath.map(rustSnake).join("/")
1588
- const filePath = hasChildren
1589
- ? `src/resources/${dirPath}/mod.rs`
1590
- : `src/resources/${dirPath}.rs`
1637
+ const filePath = hasChildren ? `src/resources/${dirPath}/mod.rs` : `src/resources/${dirPath}.rs`
1591
1638
  files.set(filePath, fileLines.join("\n"))
1592
1639
 
1593
1640
  /* recurse into children */
@@ -1603,10 +1650,7 @@ function buildRustResources(
1603
1650
  return files
1604
1651
  }
1605
1652
 
1606
- export function generateRustSDK(
1607
- spec: Record<string, unknown>,
1608
- options: RustSDKOptions = {},
1609
- ): GeneratedRustSDK {
1653
+ export function generateRustSDK(spec: Record<string, unknown>, options: RustSDKOptions = {}): GeneratedRustSDK {
1610
1654
  const crateName = validateCrateName(options.crateName ?? "honey-sdk")
1611
1655
 
1612
1656
  /* toIR validates operationId collisions — throws before any codegen runs */
@@ -13,13 +13,7 @@ const SCHEMA_VALUE_KEYS = new Set([
13
13
  "unevaluatedProperties",
14
14
  ])
15
15
 
16
- const SCHEMA_MAP_KEYS = new Set([
17
- "$defs",
18
- "definitions",
19
- "dependentSchemas",
20
- "patternProperties",
21
- "properties",
22
- ])
16
+ const SCHEMA_MAP_KEYS = new Set(["$defs", "definitions", "dependentSchemas", "patternProperties", "properties"])
23
17
 
24
18
  const SCHEMA_ARRAY_KEYS = new Set(["allOf", "anyOf", "oneOf", "prefixItems"])
25
19
 
@@ -114,7 +114,8 @@ export function deriveErrorEnvelopeName(schema: Record<string, unknown>, fallbac
114
114
  const statusProp = props.status
115
115
  let status: number | undefined
116
116
  if (typeof statusProp.const === "number") status = statusProp.const
117
- else if (Array.isArray(statusProp.enum) && statusProp.enum.length === 1 && typeof statusProp.enum[0] === "number") status = statusProp.enum[0]
117
+ else if (Array.isArray(statusProp.enum) && statusProp.enum.length === 1 && typeof statusProp.enum[0] === "number")
118
+ status = statusProp.enum[0]
118
119
  else if (fallbackStatus !== undefined) status = fallbackStatus
119
120
  else throw new Error("deriveErrorEnvelopeName: no status")
120
121
 
@@ -125,7 +126,12 @@ export function deriveErrorEnvelopeName(schema: Record<string, unknown>, fallbac
125
126
 
126
127
  if (keys.length === 0) return `Err${status}`
127
128
  const keysPascal = keys
128
- .map((k) => k.split(/[_-]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(""))
129
+ .map((k) =>
130
+ k
131
+ .split(/[_-]/)
132
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
133
+ .join(""),
134
+ )
129
135
  .join("")
130
136
  return `Err${status}${keysPascal}`
131
137
  }