@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.
Files changed (674) hide show
  1. package/README.md +514 -192
  2. package/bin/honey.js +25 -0
  3. package/dist/accepts.d.ts +7 -1
  4. package/dist/accepts.d.ts.map +1 -1
  5. package/dist/accepts.js +126 -0
  6. package/dist/accepts.js.map +1 -0
  7. package/dist/body-kind.d.ts +26 -0
  8. package/dist/body-kind.d.ts.map +1 -0
  9. package/dist/body-kind.js +70 -0
  10. package/dist/body-kind.js.map +1 -0
  11. package/dist/body-limit.d.ts +5 -1
  12. package/dist/body-limit.d.ts.map +1 -1
  13. package/dist/body-limit.js +126 -0
  14. package/dist/body-limit.js.map +1 -0
  15. package/dist/build/index.d.ts +19 -3
  16. package/dist/build/index.d.ts.map +1 -1
  17. package/dist/build/index.js +173 -0
  18. package/dist/build/index.js.map +1 -0
  19. package/dist/cli-args.d.ts +33 -0
  20. package/dist/cli-args.d.ts.map +1 -0
  21. package/dist/cli-args.js +102 -0
  22. package/dist/cli-args.js.map +1 -0
  23. package/dist/cli-go/auth.go +26 -0
  24. package/dist/cli-go/config.go +192 -0
  25. package/dist/cli-go/errors.go +75 -0
  26. package/dist/cli-go/multipart.go +79 -0
  27. package/dist/cli-go/output.go +163 -0
  28. package/dist/cli-go/root.go +30 -0
  29. package/dist/cli-go/stream.go +88 -0
  30. package/dist/cli-go/version.go +5 -0
  31. package/dist/cli.d.ts +1 -1
  32. package/dist/cli.js +209 -0
  33. package/dist/cli.js.map +1 -0
  34. package/dist/client/defaults.js +48 -0
  35. package/dist/client/defaults.js.map +1 -0
  36. package/dist/client/error.d.ts +35 -0
  37. package/dist/client/error.d.ts.map +1 -1
  38. package/dist/client/error.js +71 -0
  39. package/dist/client/error.js.map +1 -0
  40. package/dist/client/http.d.ts +67 -8
  41. package/dist/client/http.d.ts.map +1 -1
  42. package/dist/client/http.js +571 -0
  43. package/dist/client/http.js.map +1 -0
  44. package/dist/client/index.d.ts +3 -2
  45. package/dist/client/index.d.ts.map +1 -1
  46. package/dist/client/index.js +95 -0
  47. package/dist/client/index.js.map +1 -0
  48. package/dist/client/path.d.ts +26 -0
  49. package/dist/client/path.d.ts.map +1 -0
  50. package/dist/client/path.js +163 -0
  51. package/dist/client/path.js.map +1 -0
  52. package/dist/client/redirect-policy.d.ts +12 -0
  53. package/dist/client/redirect-policy.d.ts.map +1 -0
  54. package/dist/client/redirect-policy.js +20 -0
  55. package/dist/client/redirect-policy.js.map +1 -0
  56. package/dist/client/sdk.d.ts +36 -0
  57. package/dist/client/sdk.d.ts.map +1 -1
  58. package/dist/client/sdk.js +292 -0
  59. package/dist/client/sdk.js.map +1 -0
  60. package/dist/client/sse.d.ts +13 -0
  61. package/dist/client/sse.d.ts.map +1 -1
  62. package/dist/client/sse.js +129 -0
  63. package/dist/client/sse.js.map +1 -0
  64. package/dist/client/types.js +2 -0
  65. package/dist/client/types.js.map +1 -0
  66. package/dist/client/ws.d.ts +9 -2
  67. package/dist/client/ws.d.ts.map +1 -1
  68. package/dist/client/ws.js +112 -0
  69. package/dist/client/ws.js.map +1 -0
  70. package/dist/client-go/errors.go +236 -0
  71. package/dist/client-go/export.go +44 -0
  72. package/dist/client-go/invalidation.go +371 -0
  73. package/dist/client-go/nullable.go +62 -0
  74. package/dist/client-go/realtime.go +354 -0
  75. package/dist/client-go/result.go +20 -0
  76. package/dist/client-go/runtime.go +975 -0
  77. package/dist/client-go/sse.go +159 -0
  78. package/dist/client-go/transport.go +495 -0
  79. package/dist/client-go/ws.go +128 -0
  80. package/dist/client-info.d.ts +36 -0
  81. package/dist/client-info.d.ts.map +1 -0
  82. package/dist/client-info.js +53 -0
  83. package/dist/client-info.js.map +1 -0
  84. package/dist/client-mcp/README.md +10 -0
  85. package/dist/client-mcp/server.ts +221 -0
  86. package/dist/client-mcp/types.ts +19 -0
  87. package/dist/client-python/_errors.py +115 -0
  88. package/dist/client-python/_invalidation.py +494 -0
  89. package/dist/client-python/_realtime.py +179 -0
  90. package/dist/client-python/_runtime.py +860 -0
  91. package/dist/client-python/_sse.py +155 -0
  92. package/dist/client-python/_transport.py +366 -0
  93. package/dist/client-python/_ws.py +159 -0
  94. package/dist/client-python/py.typed +0 -0
  95. package/dist/client-rust/errors.rs +220 -0
  96. package/dist/client-rust/invalidation.rs +412 -0
  97. package/dist/client-rust/invalidation_sync.rs +93 -0
  98. package/dist/client-rust/nullable.rs +17 -0
  99. package/dist/client-rust/realtime.rs +702 -0
  100. package/dist/client-rust/result.rs +22 -0
  101. package/dist/client-rust/runtime.rs +980 -0
  102. package/dist/client-rust/runtime_sync.rs +484 -0
  103. package/dist/client-rust/sse.rs +188 -0
  104. package/dist/client-rust/ws.rs +167 -0
  105. package/dist/codegen-go-cli.d.ts.map +1 -1
  106. package/dist/codegen-go-cli.js +964 -0
  107. package/dist/codegen-go-cli.js.map +1 -0
  108. package/dist/codegen-go.d.ts +15 -3
  109. package/dist/codegen-go.d.ts.map +1 -1
  110. package/dist/codegen-go.js +872 -0
  111. package/dist/codegen-go.js.map +1 -0
  112. package/dist/codegen-hash.js +9 -0
  113. package/dist/codegen-hash.js.map +1 -0
  114. package/dist/codegen-ir.d.ts +69 -2
  115. package/dist/codegen-ir.d.ts.map +1 -1
  116. package/dist/codegen-ir.js +661 -0
  117. package/dist/codegen-ir.js.map +1 -0
  118. package/dist/codegen-lang.d.ts +65 -0
  119. package/dist/codegen-lang.d.ts.map +1 -0
  120. package/dist/codegen-lang.js +526 -0
  121. package/dist/codegen-lang.js.map +1 -0
  122. package/dist/codegen-load.d.ts +31 -0
  123. package/dist/codegen-load.d.ts.map +1 -0
  124. package/dist/codegen-load.js +138 -0
  125. package/dist/codegen-load.js.map +1 -0
  126. package/dist/codegen-loaders.js +55 -0
  127. package/dist/codegen-loaders.js.map +1 -0
  128. package/dist/codegen-mcp.d.ts.map +1 -1
  129. package/dist/codegen-mcp.js +368 -0
  130. package/dist/codegen-mcp.js.map +1 -0
  131. package/dist/codegen-python.d.ts +13 -1
  132. package/dist/codegen-python.d.ts.map +1 -1
  133. package/dist/codegen-python.js +828 -0
  134. package/dist/codegen-python.js.map +1 -0
  135. package/dist/codegen-route-tree-intern.d.ts +15 -5
  136. package/dist/codegen-route-tree-intern.d.ts.map +1 -1
  137. package/dist/codegen-route-tree-intern.js +221 -0
  138. package/dist/codegen-route-tree-intern.js.map +1 -0
  139. package/dist/codegen-rust.d.ts.map +1 -1
  140. package/dist/codegen-rust.js +1293 -0
  141. package/dist/codegen-rust.js.map +1 -0
  142. package/dist/codegen-sanitize.d.ts.map +1 -1
  143. package/dist/codegen-sanitize.js +56 -0
  144. package/dist/codegen-sanitize.js.map +1 -0
  145. package/dist/codegen-schema-naming.d.ts +5 -0
  146. package/dist/codegen-schema-naming.d.ts.map +1 -1
  147. package/dist/codegen-schema-naming.js +150 -0
  148. package/dist/codegen-schema-naming.js.map +1 -0
  149. package/dist/codegen-sdk-model.d.ts +71 -0
  150. package/dist/codegen-sdk-model.d.ts.map +1 -0
  151. package/dist/codegen-sdk-model.js +179 -0
  152. package/dist/codegen-sdk-model.js.map +1 -0
  153. package/dist/codegen.d.ts +41 -23
  154. package/dist/codegen.d.ts.map +1 -1
  155. package/dist/codegen.js +4610 -0
  156. package/dist/codegen.js.map +1 -0
  157. package/dist/context.d.ts +36 -6
  158. package/dist/context.d.ts.map +1 -1
  159. package/dist/context.js +242 -0
  160. package/dist/context.js.map +1 -0
  161. package/dist/cookie-sign.d.ts +16 -2
  162. package/dist/cookie-sign.d.ts.map +1 -1
  163. package/dist/cookie-sign.js +104 -0
  164. package/dist/cookie-sign.js.map +1 -0
  165. package/dist/cookie.d.ts +12 -0
  166. package/dist/cookie.d.ts.map +1 -1
  167. package/dist/cookie.js +138 -0
  168. package/dist/cookie.js.map +1 -0
  169. package/dist/cors.d.ts +1 -0
  170. package/dist/cors.d.ts.map +1 -1
  171. package/dist/cors.js +87 -0
  172. package/dist/cors.js.map +1 -0
  173. package/dist/crypto.d.ts +6 -1
  174. package/dist/crypto.d.ts.map +1 -1
  175. package/dist/crypto.js +38 -0
  176. package/dist/crypto.js.map +1 -0
  177. package/dist/csrf.d.ts +19 -0
  178. package/dist/csrf.d.ts.map +1 -1
  179. package/dist/csrf.js +79 -0
  180. package/dist/csrf.js.map +1 -0
  181. package/dist/curl-logger.d.ts +14 -3
  182. package/dist/curl-logger.d.ts.map +1 -1
  183. package/dist/curl-logger.js +199 -0
  184. package/dist/curl-logger.js.map +1 -0
  185. package/dist/detect-runtime.js +13 -0
  186. package/dist/detect-runtime.js.map +1 -0
  187. package/dist/dict.d.ts +11 -0
  188. package/dist/dict.d.ts.map +1 -0
  189. package/dist/dict.js +13 -0
  190. package/dist/dict.js.map +1 -0
  191. package/dist/error.d.ts.map +1 -1
  192. package/dist/error.js +44 -0
  193. package/dist/error.js.map +1 -0
  194. package/dist/errors.js +39 -0
  195. package/dist/errors.js.map +1 -0
  196. package/dist/etag.d.ts +2 -0
  197. package/dist/etag.d.ts.map +1 -1
  198. package/dist/etag.js +82 -0
  199. package/dist/etag.js.map +1 -0
  200. package/dist/feature-detect.d.ts +25 -0
  201. package/dist/feature-detect.d.ts.map +1 -0
  202. package/dist/feature-detect.js +144 -0
  203. package/dist/feature-detect.js.map +1 -0
  204. package/dist/feature-load.js +36 -0
  205. package/dist/feature-load.js.map +1 -0
  206. package/dist/feature-slots.d.ts +44 -0
  207. package/dist/feature-slots.d.ts.map +1 -0
  208. package/dist/feature-slots.js +48 -0
  209. package/dist/feature-slots.js.map +1 -0
  210. package/dist/fetch-request.d.ts +10 -0
  211. package/dist/fetch-request.d.ts.map +1 -0
  212. package/dist/fetch-request.js +13 -0
  213. package/dist/fetch-request.js.map +1 -0
  214. package/dist/gen-process.d.ts +15 -0
  215. package/dist/gen-process.d.ts.map +1 -0
  216. package/dist/gen-process.js +43 -0
  217. package/dist/gen-process.js.map +1 -0
  218. package/dist/gen-write.d.ts +19 -0
  219. package/dist/gen-write.d.ts.map +1 -0
  220. package/dist/gen-write.js +120 -0
  221. package/dist/gen-write.js.map +1 -0
  222. package/dist/glob.d.ts +21 -0
  223. package/dist/glob.d.ts.map +1 -0
  224. package/dist/glob.js +115 -0
  225. package/dist/glob.js.map +1 -0
  226. package/dist/go-type-emitter.d.ts +18 -4
  227. package/dist/go-type-emitter.d.ts.map +1 -1
  228. package/dist/go-type-emitter.js +594 -0
  229. package/dist/go-type-emitter.js.map +1 -0
  230. package/dist/honey-response.d.ts.map +1 -1
  231. package/dist/honey-response.js +338 -0
  232. package/dist/honey-response.js.map +1 -0
  233. package/dist/i18n-slot.d.ts +8 -0
  234. package/dist/i18n-slot.d.ts.map +1 -1
  235. package/dist/i18n-slot.js +14 -0
  236. package/dist/i18n-slot.js.map +1 -0
  237. package/dist/i18n.d.ts +3 -0
  238. package/dist/i18n.d.ts.map +1 -1
  239. package/dist/i18n.js +250 -0
  240. package/dist/i18n.js.map +1 -0
  241. package/dist/index.d.ts +410 -149
  242. package/dist/index.d.ts.map +1 -1
  243. package/dist/index.js +2324 -0
  244. package/dist/index.js.map +1 -0
  245. package/dist/init.d.ts +3 -5
  246. package/dist/init.d.ts.map +1 -1
  247. package/dist/init.js +140 -0
  248. package/dist/init.js.map +1 -0
  249. package/dist/input.js +4 -0
  250. package/dist/input.js.map +1 -0
  251. package/dist/invalidate-check.d.ts +11 -0
  252. package/dist/invalidate-check.d.ts.map +1 -1
  253. package/dist/invalidate-check.js +163 -0
  254. package/dist/invalidate-check.js.map +1 -0
  255. package/dist/invoke-user.d.ts +35 -0
  256. package/dist/invoke-user.d.ts.map +1 -0
  257. package/dist/invoke-user.js +73 -0
  258. package/dist/invoke-user.js.map +1 -0
  259. package/dist/ip-restrict.d.ts +13 -1
  260. package/dist/ip-restrict.d.ts.map +1 -1
  261. package/dist/ip-restrict.js +60 -0
  262. package/dist/ip-restrict.js.map +1 -0
  263. package/dist/ip.d.ts +39 -0
  264. package/dist/ip.d.ts.map +1 -0
  265. package/dist/ip.js +209 -0
  266. package/dist/ip.js.map +1 -0
  267. package/dist/logger.d.ts.map +1 -1
  268. package/dist/logger.js +113 -0
  269. package/dist/logger.js.map +1 -0
  270. package/dist/media-type.d.ts +30 -0
  271. package/dist/media-type.d.ts.map +1 -0
  272. package/dist/media-type.js +97 -0
  273. package/dist/media-type.js.map +1 -0
  274. package/dist/meta-spec-merge.d.ts +32 -0
  275. package/dist/meta-spec-merge.d.ts.map +1 -0
  276. package/dist/meta-spec-merge.js +68 -0
  277. package/dist/meta-spec-merge.js.map +1 -0
  278. package/dist/meta-spec.d.ts +16 -3
  279. package/dist/meta-spec.d.ts.map +1 -1
  280. package/dist/meta-spec.js +535 -0
  281. package/dist/meta-spec.js.map +1 -0
  282. package/dist/middleware.d.ts +23 -6
  283. package/dist/middleware.d.ts.map +1 -1
  284. package/dist/middleware.js +253 -0
  285. package/dist/middleware.js.map +1 -0
  286. package/dist/node-request.d.ts +57 -12
  287. package/dist/node-request.d.ts.map +1 -1
  288. package/dist/node-request.js +407 -0
  289. package/dist/node-request.js.map +1 -0
  290. package/dist/node.d.ts +20 -3
  291. package/dist/node.d.ts.map +1 -1
  292. package/dist/node.js +396 -0
  293. package/dist/node.js.map +1 -0
  294. package/dist/openapi/collect.d.ts +6 -3
  295. package/dist/openapi/collect.d.ts.map +1 -1
  296. package/dist/openapi/collect.js +30 -0
  297. package/dist/openapi/collect.js.map +1 -0
  298. package/dist/openapi/docs-page.d.ts +42 -0
  299. package/dist/openapi/docs-page.d.ts.map +1 -0
  300. package/dist/openapi/docs-page.js +80 -0
  301. package/dist/openapi/docs-page.js.map +1 -0
  302. package/dist/openapi/document.d.ts +4 -0
  303. package/dist/openapi/document.d.ts.map +1 -1
  304. package/dist/openapi/document.js +785 -0
  305. package/dist/openapi/document.js.map +1 -0
  306. package/dist/openapi/json-schema-slot.d.ts +7 -0
  307. package/dist/openapi/json-schema-slot.d.ts.map +1 -1
  308. package/dist/openapi/json-schema-slot.js +38 -0
  309. package/dist/openapi/json-schema-slot.js.map +1 -0
  310. package/dist/openapi/register.d.ts.map +1 -1
  311. package/dist/openapi/register.js +20 -0
  312. package/dist/openapi/register.js.map +1 -0
  313. package/dist/openapi/scalar.d.ts +3 -1
  314. package/dist/openapi/scalar.d.ts.map +1 -1
  315. package/dist/openapi/scalar.js +21 -0
  316. package/dist/openapi/scalar.js.map +1 -0
  317. package/dist/openapi/serve.d.ts +9 -0
  318. package/dist/openapi/serve.d.ts.map +1 -0
  319. package/dist/openapi/serve.js +36 -0
  320. package/dist/openapi/serve.js.map +1 -0
  321. package/dist/openapi/spec-factory.d.ts +30 -1
  322. package/dist/openapi/spec-factory.d.ts.map +1 -1
  323. package/dist/openapi/spec-factory.js +52 -0
  324. package/dist/openapi/spec-factory.js.map +1 -0
  325. package/dist/openapi/spec.d.ts +14 -5
  326. package/dist/openapi/spec.d.ts.map +1 -1
  327. package/dist/openapi/spec.js +50 -0
  328. package/dist/openapi/spec.js.map +1 -0
  329. package/dist/openapi/swagger.d.ts +3 -1
  330. package/dist/openapi/swagger.d.ts.map +1 -1
  331. package/dist/openapi/swagger.js +22 -0
  332. package/dist/openapi/swagger.js.map +1 -0
  333. package/dist/pattern.d.ts +71 -0
  334. package/dist/pattern.d.ts.map +1 -0
  335. package/dist/pattern.js +239 -0
  336. package/dist/pattern.js.map +1 -0
  337. package/dist/peer.d.ts +17 -0
  338. package/dist/peer.d.ts.map +1 -0
  339. package/dist/peer.js +50 -0
  340. package/dist/peer.js.map +1 -0
  341. package/dist/plugin.d.ts +67 -16
  342. package/dist/plugin.d.ts.map +1 -1
  343. package/dist/plugin.js +649 -0
  344. package/dist/plugin.js.map +1 -0
  345. package/dist/powered-by.d.ts.map +1 -1
  346. package/dist/powered-by.js +11 -0
  347. package/dist/powered-by.js.map +1 -0
  348. package/dist/pretty-json.d.ts.map +1 -1
  349. package/dist/pretty-json.js +47 -0
  350. package/dist/pretty-json.js.map +1 -0
  351. package/dist/producer-stream.d.ts +47 -0
  352. package/dist/producer-stream.d.ts.map +1 -0
  353. package/dist/producer-stream.js +267 -0
  354. package/dist/producer-stream.js.map +1 -0
  355. package/dist/proxy.d.ts +47 -8
  356. package/dist/proxy.d.ts.map +1 -1
  357. package/dist/proxy.js +274 -0
  358. package/dist/proxy.js.map +1 -0
  359. package/dist/python-type-emitter.d.ts +14 -1
  360. package/dist/python-type-emitter.d.ts.map +1 -1
  361. package/dist/python-type-emitter.js +157 -0
  362. package/dist/python-type-emitter.js.map +1 -0
  363. package/dist/realtime/bus.d.ts +17 -1
  364. package/dist/realtime/bus.d.ts.map +1 -1
  365. package/dist/realtime/bus.js +112 -0
  366. package/dist/realtime/bus.js.map +1 -0
  367. package/dist/realtime/register.d.ts +3 -0
  368. package/dist/realtime/register.d.ts.map +1 -0
  369. package/dist/realtime/register.js +10 -0
  370. package/dist/realtime/register.js.map +1 -0
  371. package/dist/realtime/route.d.ts +65 -26
  372. package/dist/realtime/route.d.ts.map +1 -1
  373. package/dist/realtime/route.js +72 -0
  374. package/dist/realtime/route.js.map +1 -0
  375. package/dist/realtime/server.d.ts +43 -0
  376. package/dist/realtime/server.d.ts.map +1 -0
  377. package/dist/realtime/server.js +255 -0
  378. package/dist/realtime/server.js.map +1 -0
  379. package/dist/request-id.d.ts +7 -0
  380. package/dist/request-id.d.ts.map +1 -1
  381. package/dist/request-id.js +16 -0
  382. package/dist/request-id.js.map +1 -0
  383. package/dist/request-limits.d.ts +3 -0
  384. package/dist/request-limits.d.ts.map +1 -0
  385. package/dist/request-limits.js +3 -0
  386. package/dist/request-limits.js.map +1 -0
  387. package/dist/request-path.d.ts +33 -0
  388. package/dist/request-path.d.ts.map +1 -0
  389. package/dist/request-path.js +178 -0
  390. package/dist/request-path.js.map +1 -0
  391. package/dist/request-to-curl.d.ts +7 -1
  392. package/dist/request-to-curl.d.ts.map +1 -1
  393. package/dist/request-to-curl.js +48 -0
  394. package/dist/request-to-curl.js.map +1 -0
  395. package/dist/response.d.ts +25 -2
  396. package/dist/response.d.ts.map +1 -1
  397. package/dist/response.js +313 -0
  398. package/dist/response.js.map +1 -0
  399. package/dist/rust-type-emitter.d.ts +14 -2
  400. package/dist/rust-type-emitter.d.ts.map +1 -1
  401. package/dist/rust-type-emitter.js +463 -0
  402. package/dist/rust-type-emitter.js.map +1 -0
  403. package/dist/secure-headers.d.ts.map +1 -1
  404. package/dist/secure-headers.js +53 -0
  405. package/dist/secure-headers.js.map +1 -0
  406. package/dist/serve-register.d.ts +4 -0
  407. package/dist/serve-register.d.ts.map +1 -1
  408. package/dist/serve-register.js +9 -0
  409. package/dist/serve-register.js.map +1 -0
  410. package/dist/serve-slot.d.ts +3 -0
  411. package/dist/serve-slot.d.ts.map +1 -1
  412. package/dist/serve-slot.js +52 -0
  413. package/dist/serve-slot.js.map +1 -0
  414. package/dist/serve.d.ts +18 -1
  415. package/dist/serve.d.ts.map +1 -1
  416. package/dist/serve.js +157 -0
  417. package/dist/serve.js.map +1 -0
  418. package/dist/server-timing.d.ts.map +1 -1
  419. package/dist/server-timing.js +51 -0
  420. package/dist/server-timing.js.map +1 -0
  421. package/dist/static.d.ts +9 -0
  422. package/dist/static.d.ts.map +1 -1
  423. package/dist/static.js +102 -0
  424. package/dist/static.js.map +1 -0
  425. package/dist/telemetry/otel.d.ts +2 -0
  426. package/dist/telemetry/otel.d.ts.map +1 -1
  427. package/dist/telemetry/otel.js +135 -0
  428. package/dist/telemetry/otel.js.map +1 -0
  429. package/dist/testing.d.ts +12 -0
  430. package/dist/testing.d.ts.map +1 -1
  431. package/dist/testing.js +124 -0
  432. package/dist/testing.js.map +1 -0
  433. package/dist/timeout.d.ts +6 -0
  434. package/dist/timeout.d.ts.map +1 -1
  435. package/dist/timeout.js +36 -0
  436. package/dist/timeout.js.map +1 -0
  437. package/dist/tree.d.ts +133 -36
  438. package/dist/tree.d.ts.map +1 -1
  439. package/dist/tree.js +437 -0
  440. package/dist/tree.js.map +1 -0
  441. package/dist/trust.d.ts +21 -0
  442. package/dist/trust.d.ts.map +1 -0
  443. package/dist/trust.js +100 -0
  444. package/dist/trust.js.map +1 -0
  445. package/dist/ts-type-emitter.d.ts.map +1 -1
  446. package/dist/ts-type-emitter.js +150 -0
  447. package/dist/ts-type-emitter.js.map +1 -0
  448. package/dist/type-emitter.d.ts +16 -2
  449. package/dist/type-emitter.d.ts.map +1 -1
  450. package/dist/type-emitter.js +729 -0
  451. package/dist/type-emitter.js.map +1 -0
  452. package/dist/type-extractor.d.ts +8 -4
  453. package/dist/type-extractor.d.ts.map +1 -1
  454. package/dist/type-extractor.js +776 -0
  455. package/dist/type-extractor.js.map +1 -0
  456. package/dist/types.d.ts +7 -0
  457. package/dist/types.d.ts.map +1 -1
  458. package/dist/types.js +96 -0
  459. package/dist/types.js.map +1 -0
  460. package/dist/validation.d.ts +14 -2
  461. package/dist/validation.d.ts.map +1 -1
  462. package/dist/validation.js +335 -0
  463. package/dist/validation.js.map +1 -0
  464. package/dist/with-headers.d.ts +15 -0
  465. package/dist/with-headers.d.ts.map +1 -0
  466. package/dist/with-headers.js +45 -0
  467. package/dist/with-headers.js.map +1 -0
  468. package/dist/ws/bun.d.ts +11 -5
  469. package/dist/ws/bun.d.ts.map +1 -1
  470. package/dist/ws/bun.js +82 -0
  471. package/dist/ws/bun.js.map +1 -0
  472. package/dist/ws/cloudflare.d.ts +25 -3
  473. package/dist/ws/cloudflare.d.ts.map +1 -1
  474. package/dist/ws/cloudflare.js +117 -0
  475. package/dist/ws/cloudflare.js.map +1 -0
  476. package/dist/ws/deno.d.ts +4 -4
  477. package/dist/ws/deno.d.ts.map +1 -1
  478. package/dist/ws/deno.js +133 -0
  479. package/dist/ws/deno.js.map +1 -0
  480. package/dist/ws/node.d.ts +20 -3
  481. package/dist/ws/node.d.ts.map +1 -1
  482. package/dist/ws/node.js +173 -0
  483. package/dist/ws/node.js.map +1 -0
  484. package/dist/ws/session.d.ts +11 -0
  485. package/dist/ws/session.d.ts.map +1 -0
  486. package/dist/ws/session.js +60 -0
  487. package/dist/ws/session.js.map +1 -0
  488. package/dist/ws/shared.d.ts +65 -0
  489. package/dist/ws/shared.d.ts.map +1 -0
  490. package/dist/ws/shared.js +103 -0
  491. package/dist/ws/shared.js.map +1 -0
  492. package/dist/ws-origin.d.ts +20 -0
  493. package/dist/ws-origin.d.ts.map +1 -0
  494. package/dist/ws-origin.js +41 -0
  495. package/dist/ws-origin.js.map +1 -0
  496. package/dist/yaml.d.ts +15 -1
  497. package/dist/yaml.d.ts.map +1 -1
  498. package/dist/yaml.js +101 -0
  499. package/dist/yaml.js.map +1 -0
  500. package/docs/invalidate-spec.md +13 -1
  501. package/docs/meta-spec.md +36 -12
  502. package/docs/packaging.md +43 -24
  503. package/docs/regression-matrix/README.md +32 -0
  504. package/docs/regression-matrix/review.md +44 -0
  505. package/docs/regression-matrix/ws1-3.md +97 -0
  506. package/docs/regression-matrix/ws4-8.md +107 -0
  507. package/docs/regression-matrix/ws9-12.md +266 -0
  508. package/docs/sdk.md +102 -52
  509. package/package.json +183 -52
  510. package/src/accepts.ts +91 -33
  511. package/src/body-kind.ts +67 -0
  512. package/src/body-limit.ts +102 -87
  513. package/src/build/index.ts +104 -54
  514. package/src/cli-args.ts +123 -0
  515. package/src/cli-go/auth.go +12 -1
  516. package/src/cli-go/config.go +29 -2
  517. package/src/cli-go/errors.go +28 -10
  518. package/src/cli-go/multipart.go +51 -27
  519. package/src/cli-go/output.go +58 -4
  520. package/src/cli-go/root.go +1 -1
  521. package/src/cli-go/version.go +2 -2
  522. package/src/cli.ts +154 -148
  523. package/src/client/error.ts +56 -1
  524. package/src/client/http.ts +479 -256
  525. package/src/client/index.ts +55 -17
  526. package/src/client/path.ts +169 -0
  527. package/src/client/redirect-policy.ts +19 -0
  528. package/src/client/sdk.ts +180 -101
  529. package/src/client/sse.ts +92 -66
  530. package/src/client/ws.ts +67 -31
  531. package/src/client-go/errors.go +41 -12
  532. package/src/client-go/export.go +44 -0
  533. package/src/client-go/invalidation.go +52 -54
  534. package/src/client-go/nullable.go +62 -0
  535. package/src/client-go/realtime.go +147 -110
  536. package/src/client-go/runtime.go +646 -88
  537. package/src/client-go/sse.go +104 -94
  538. package/src/client-go/transport.go +67 -27
  539. package/src/client-go/ws.go +49 -25
  540. package/src/client-info.ts +79 -0
  541. package/src/client-mcp/server.ts +133 -3
  542. package/src/client-python/_invalidation.py +207 -291
  543. package/src/client-python/_realtime.py +63 -40
  544. package/src/client-python/_runtime.py +501 -119
  545. package/src/client-python/_sse.py +130 -74
  546. package/src/client-python/_transport.py +37 -9
  547. package/src/client-python/_ws.py +50 -22
  548. package/src/client-rust/errors.rs +21 -1
  549. package/src/client-rust/invalidation.rs +202 -231
  550. package/src/client-rust/invalidation_sync.rs +16 -229
  551. package/src/client-rust/nullable.rs +17 -0
  552. package/src/client-rust/realtime.rs +217 -217
  553. package/src/client-rust/runtime.rs +602 -177
  554. package/src/client-rust/runtime_sync.rs +169 -145
  555. package/src/client-rust/sse.rs +142 -121
  556. package/src/client-rust/ws.rs +61 -37
  557. package/src/codegen-go-cli.ts +607 -728
  558. package/src/codegen-go.ts +710 -770
  559. package/src/codegen-ir.ts +381 -75
  560. package/src/codegen-lang.ts +517 -0
  561. package/src/codegen-load.ts +150 -0
  562. package/src/codegen-mcp.ts +207 -108
  563. package/src/codegen-python.ts +684 -816
  564. package/src/codegen-route-tree-intern.ts +51 -25
  565. package/src/codegen-rust.ts +946 -1240
  566. package/src/codegen-sanitize.ts +0 -4
  567. package/src/codegen-schema-naming.ts +19 -6
  568. package/src/codegen-sdk-model.ts +228 -0
  569. package/src/codegen.ts +1488 -1140
  570. package/src/context.ts +130 -11
  571. package/src/cookie-sign.ts +85 -16
  572. package/src/cookie.ts +66 -11
  573. package/src/cors.ts +55 -54
  574. package/src/crypto.ts +27 -5
  575. package/src/csrf.ts +57 -25
  576. package/src/curl-logger.ts +155 -114
  577. package/src/dict.ts +12 -0
  578. package/src/error.ts +4 -2
  579. package/src/etag.ts +58 -34
  580. package/src/feature-detect.ts +147 -0
  581. package/src/feature-slots.ts +70 -0
  582. package/src/fetch-request.ts +13 -0
  583. package/src/gen-process.ts +42 -0
  584. package/src/gen-write.ts +127 -0
  585. package/src/glob.ts +109 -0
  586. package/src/go-type-emitter.ts +406 -155
  587. package/src/honey-response.ts +36 -9
  588. package/src/i18n-slot.ts +10 -0
  589. package/src/i18n.ts +98 -23
  590. package/src/index.ts +1893 -1761
  591. package/src/init.ts +40 -28
  592. package/src/invalidate-check.ts +42 -4
  593. package/src/invoke-user.ts +95 -0
  594. package/src/ip-restrict.ts +47 -161
  595. package/src/ip.ts +194 -0
  596. package/src/logger.ts +65 -36
  597. package/src/media-type.ts +104 -0
  598. package/src/meta-spec-merge.ts +80 -0
  599. package/src/meta-spec.ts +0 -0
  600. package/src/middleware.ts +112 -12
  601. package/src/node-request.ts +242 -52
  602. package/src/node.ts +318 -75
  603. package/src/openapi/collect.ts +19 -44
  604. package/src/openapi/docs-page.ts +89 -0
  605. package/src/openapi/document.ts +503 -224
  606. package/src/openapi/json-schema-slot.ts +40 -0
  607. package/src/openapi/register.ts +7 -2
  608. package/src/openapi/scalar.ts +11 -10
  609. package/src/openapi/serve.ts +39 -0
  610. package/src/openapi/spec-factory.ts +50 -1
  611. package/src/openapi/spec.ts +53 -30
  612. package/src/openapi/swagger.ts +12 -11
  613. package/src/pattern.ts +245 -0
  614. package/src/peer.ts +53 -0
  615. package/src/plugin.ts +270 -185
  616. package/src/powered-by.ts +2 -2
  617. package/src/pretty-json.ts +25 -9
  618. package/src/producer-stream.ts +296 -0
  619. package/src/proxy.ts +271 -55
  620. package/src/python-type-emitter.ts +68 -56
  621. package/src/realtime/bus.ts +56 -9
  622. package/src/realtime/register.ts +13 -0
  623. package/src/realtime/route.ts +129 -80
  624. package/src/realtime/server.ts +298 -0
  625. package/src/request-id.ts +13 -3
  626. package/src/request-limits.ts +2 -0
  627. package/src/request-path.ts +165 -0
  628. package/src/request-to-curl.ts +28 -7
  629. package/src/response.ts +143 -103
  630. package/src/rust-type-emitter.ts +265 -170
  631. package/src/secure-headers.ts +8 -4
  632. package/src/serve-register.ts +4 -0
  633. package/src/serve-slot.ts +38 -0
  634. package/src/serve.ts +113 -25
  635. package/src/server-timing.ts +11 -7
  636. package/src/static.ts +85 -32
  637. package/src/telemetry/otel.ts +29 -7
  638. package/src/testing.ts +76 -14
  639. package/src/timeout.ts +15 -7
  640. package/src/tree.ts +405 -333
  641. package/src/trust.ts +117 -0
  642. package/src/ts-type-emitter.ts +18 -12
  643. package/src/type-emitter.ts +227 -89
  644. package/src/type-extractor.ts +562 -424
  645. package/src/types.ts +8 -0
  646. package/src/validation.ts +221 -96
  647. package/src/with-headers.ts +45 -0
  648. package/src/ws/bun.ts +53 -15
  649. package/src/ws/cloudflare.ts +81 -19
  650. package/src/ws/deno.ts +124 -36
  651. package/src/ws/node.ts +173 -79
  652. package/src/ws/session.ts +73 -0
  653. package/src/ws/shared.ts +140 -0
  654. package/src/ws-origin.ts +53 -0
  655. package/src/yaml.ts +45 -22
  656. package/dist/client-mcp/server.d.ts +0 -10
  657. package/dist/client-mcp/server.d.ts.map +0 -1
  658. package/dist/client-mcp/types.d.ts +0 -16
  659. package/dist/client-mcp/types.d.ts.map +0 -1
  660. package/dist/cloudflare/realtime.d.ts +0 -30
  661. package/dist/cloudflare/realtime.d.ts.map +0 -1
  662. package/dist/realtime/buffer.d.ts +0 -18
  663. package/dist/realtime/buffer.d.ts.map +0 -1
  664. package/dist/realtime/longpoll.d.ts +0 -23
  665. package/dist/realtime/longpoll.d.ts.map +0 -1
  666. package/dist/realtime/protocol.d.ts +0 -50
  667. package/dist/realtime/protocol.d.ts.map +0 -1
  668. package/dist/realtime/runtime.d.ts +0 -74
  669. package/dist/realtime/runtime.d.ts.map +0 -1
  670. package/src/cloudflare/realtime.ts +0 -234
  671. package/src/realtime/buffer.ts +0 -101
  672. package/src/realtime/longpoll.ts +0 -148
  673. package/src/realtime/protocol.ts +0 -128
  674. package/src/realtime/runtime.ts +0 -427
@@ -1,11 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import inspect
5
+ import re
4
6
  import threading
5
7
  import time
8
+ import unicodedata
6
9
  import uuid
10
+ from contextlib import asynccontextmanager
7
11
  from dataclasses import dataclass, field
8
- from typing import Any, Awaitable, Callable, Generic, TypeVar
12
+ from decimal import Decimal
13
+ from typing import Any, AsyncIterator, Awaitable, Callable, Generic, TypeVar
14
+ from urllib.parse import quote, urlsplit, urlunsplit
9
15
 
10
16
  import httpx
11
17
 
@@ -15,6 +21,7 @@ from ._invalidation import _RequestMeta
15
21
  T = TypeVar("T")
16
22
 
17
23
  DEFAULT_TIMEOUT = 30.0
24
+ MAX_ERROR_MESSAGE = 512
18
25
 
19
26
 
20
27
  @dataclass
@@ -59,12 +66,22 @@ class InvalidationConfig:
59
66
 
60
67
  @dataclass(frozen=True)
61
68
  class ClientConfig:
69
+ """SDK configuration.
70
+
71
+ ``timeout`` (seconds) bounds a whole regular request; ``None`` disables it.
72
+ Streams (SSE, WebSocket, realtime) are not bounded by it.
73
+
74
+ ``on_auth_expired`` may be a coroutine function or a plain function; it
75
+ returns the new token (or ``None`` to give up). The SDK keeps the refreshed
76
+ token for later calls and refreshes once for concurrent 401s.
77
+ """
78
+
62
79
  base_url: str
63
80
  bearer_token: str | None = None
64
81
  headers: dict[str, str] = field(default_factory=dict)
65
82
  timeout: float | None = DEFAULT_TIMEOUT
66
83
  throw_on_error: bool = True
67
- on_auth_expired: Callable[[], Awaitable[str | None]] | None = None
84
+ on_auth_expired: Callable[[], Awaitable[str | None] | str | None] | None = None
68
85
  auth_header_name: str | None = None
69
86
  auth_header_prefix: str | None = None
70
87
  transport: httpx.AsyncBaseTransport | None = None
@@ -83,7 +100,7 @@ def _emit_log(config: ClientConfig, entry: LogEntry) -> None:
83
100
  try:
84
101
  config.on_log(entry)
85
102
  except Exception:
86
- """ logger must never break request path """
103
+ # logger must never break the request path
87
104
  pass
88
105
 
89
106
 
@@ -104,6 +121,75 @@ class SDKResult(Generic[T]):
104
121
  response: httpx.Response
105
122
 
106
123
 
124
+ class _AuthState:
125
+ """Current bearer token plus a refresh guard, shared by one SDK instance."""
126
+
127
+ def __init__(self, token: str | None) -> None:
128
+ self.token = token
129
+ self._async_lock: asyncio.Lock | None = None
130
+ self._thread_lock = threading.Lock()
131
+
132
+ async def refresh(self, config: ClientConfig, rejected: str | None) -> str | None:
133
+ if self._async_lock is None:
134
+ self._async_lock = asyncio.Lock()
135
+ async with self._async_lock:
136
+ if self.token != rejected:
137
+ return self.token
138
+ token = await _call_auth_hook(config)
139
+ if token is not None:
140
+ self.token = token
141
+ return token
142
+
143
+ def refresh_sync(self, config: ClientConfig, rejected: str | None) -> str | None:
144
+ with self._thread_lock:
145
+ if self.token != rejected:
146
+ return self.token
147
+ token = _call_auth_hook_sync(config)
148
+ if token is not None:
149
+ self.token = token
150
+ return token
151
+
152
+
153
+ async def _call_auth_hook(config: ClientConfig) -> str | None:
154
+ assert config.on_auth_expired is not None
155
+ result = config.on_auth_expired()
156
+ if inspect.isawaitable(result):
157
+ return await result
158
+ return result
159
+
160
+
161
+ def _call_auth_hook_sync(config: ClientConfig) -> str | None:
162
+ assert config.on_auth_expired is not None
163
+ result = config.on_auth_expired()
164
+ if not inspect.isawaitable(result):
165
+ return result
166
+ try:
167
+ asyncio.get_running_loop()
168
+ except RuntimeError:
169
+ return asyncio.run(_await(result))
170
+ # called from inside a running loop (sync SDK used in async code): run the
171
+ # coroutine on its own loop in a worker thread instead of failing silently
172
+ out: list[Any] = []
173
+ errors: list[BaseException] = []
174
+
175
+ def _worker() -> None:
176
+ try:
177
+ out.append(asyncio.run(_await(result)))
178
+ except BaseException as exc: # re-raised in the caller's thread
179
+ errors.append(exc)
180
+
181
+ thread = threading.Thread(target=_worker)
182
+ thread.start()
183
+ thread.join()
184
+ if errors:
185
+ raise errors[0]
186
+ return out[0]
187
+
188
+
189
+ async def _await(value: Awaitable[T]) -> T:
190
+ return await value
191
+
192
+
107
193
  def _auth_header_name(config: ClientConfig) -> str:
108
194
  return config.auth_header_name or "Authorization"
109
195
 
@@ -113,12 +199,24 @@ def _has_header(headers: dict[str, str], name: str) -> bool:
113
199
  return any(k.lower() == target for k in headers)
114
200
 
115
201
 
202
+ def _set_header(headers: dict[str, str], name: str, value: str) -> None:
203
+ """Set *name* replacing any existing casing of it."""
204
+ target = name.lower()
205
+ for key in [k for k in headers if k.lower() == target]:
206
+ del headers[key]
207
+ headers[name] = value
208
+
209
+
116
210
  def _build_headers(
117
211
  config: ClientConfig,
118
212
  extra: dict[str, str] | None = None,
119
213
  bearer_token: str | None = None,
120
214
  ) -> dict[str, str]:
121
- merged: dict[str, str] = {**config.headers}
215
+ """Config headers, then the auth header, then per-call headers. Merging is
216
+ case-insensitive and later wins, so exactly one of each header is sent."""
217
+ merged: dict[str, str] = {}
218
+ for key, value in config.headers.items():
219
+ _set_header(merged, key, value)
122
220
  token = bearer_token if bearer_token is not None else config.bearer_token
123
221
  if token:
124
222
  prefix = (
@@ -126,42 +224,273 @@ def _build_headers(
126
224
  if config.auth_header_prefix is not None
127
225
  else "Bearer "
128
226
  )
129
- merged[_auth_header_name(config)] = f"{prefix}{token}"
130
- if extra:
131
- merged = {**merged, **extra}
227
+ _set_header(merged, _auth_header_name(config), f"{prefix}{token}")
228
+ for key, value in (extra or {}).items():
229
+ if value is None:
230
+ continue
231
+ _set_header(merged, key, str(value))
132
232
  # auto correlation id — config/extra win; on_request hooks may overwrite
133
233
  if not _has_header(merged, "x-request-id"):
134
234
  merged["x-request-id"] = str(uuid.uuid4())
135
235
  return merged
136
236
 
137
237
 
138
- def _build_url(base_url: str, path: str, params: dict[str, Any] | None = None) -> str:
139
- url = base_url.rstrip("/") + path
140
- if params:
141
- query = httpx.QueryParams(
142
- {k: v for k, v in params.items() if v is not None}
238
+ class PathParamError(ValueError):
239
+ """A path parameter that is missing, or that a URL parser would collapse
240
+ ("", "." or "..")."""
241
+
242
+
243
+ def _escape_segment(value: str) -> str:
244
+ # encodeURIComponent's safe set: every SDK language and the TS client send
245
+ # the same bytes
246
+ return quote(value, safe="!~*'()")
247
+
248
+
249
+ def _escape_query(value: str) -> str:
250
+ # URLSearchParams encoding: space becomes "+"
251
+ return quote(value, safe="*").replace("%20", "+").replace("~", "%7E")
252
+
253
+
254
+ def _encode_segment_value(name: str, value: str) -> str:
255
+ if value in ("", ".", ".."):
256
+ raise PathParamError(
257
+ f"Invalid path param {name!r}: {value!r} is not a path segment"
143
258
  )
144
- if query:
145
- url = f"{url}?{query}"
259
+ return _escape_segment(value)
260
+
261
+
262
+ def _expand_path(template: str, params: dict[str, Any]) -> str:
263
+ """Fill a path template with the TS client's grammar: ``:name`` / ``:name?``
264
+ take a whole segment, a final ``*name`` takes the rest, ``{name}`` may sit
265
+ inside a segment. Values are encoded; "", "." and ".." are rejected."""
266
+ segments = template.split("/")
267
+ out: list[str] = []
268
+ for i, seg in enumerate(segments):
269
+ if len(seg) > 1 and seg[0] == ":":
270
+ optional = seg.endswith("?")
271
+ name = seg[1:-1] if optional else seg[1:]
272
+ if params.get(name) is None:
273
+ if optional:
274
+ continue
275
+ raise PathParamError(f"Missing path param: {name}")
276
+ out.append(_encode_segment_value(name, _format_value(params[name])))
277
+ elif seg[:1] == "*" and i == len(segments) - 1:
278
+ name = seg[1:] or "*"
279
+ if params.get(name) is None:
280
+ if seg == "*":
281
+ out.append("")
282
+ continue
283
+ raise PathParamError(f"Missing path param: {name}")
284
+ value = _format_value(params[name])
285
+ out.append(
286
+ ""
287
+ if value == ""
288
+ else "/".join(_encode_segment_value(name, p) for p in value.split("/"))
289
+ )
290
+ else:
291
+
292
+ def _sub(match: re.Match[str]) -> str:
293
+ name = match.group(1)
294
+ if params.get(name) is None:
295
+ raise PathParamError(f"Missing path param: {name}")
296
+ return _encode_segment_value(name, _format_value(params[name]))
297
+
298
+ out.append(_BRACE_RE.sub(_sub, seg))
299
+ return "/".join(out)
300
+
301
+
302
+ _BRACE_RE = re.compile(r"\{([^{}/]+)\}")
303
+
304
+
305
+ def _format_float(value: float) -> str:
306
+ """JavaScript Number#toString formatting, shared by every SDK language."""
307
+ if value != value:
308
+ return "NaN"
309
+ if value in (float("inf"), float("-inf")):
310
+ return "Infinity" if value > 0 else "-Infinity"
311
+ if value == 0:
312
+ return "0"
313
+ magnitude = abs(value)
314
+ if 1e-6 <= magnitude < 1e21:
315
+ text = format(Decimal(repr(value)), "f")
316
+ if "." in text:
317
+ text = text.rstrip("0").rstrip(".")
318
+ return text
319
+ mantissa, _, exponent = repr(value).partition("e")
320
+ if not exponent:
321
+ mantissa, _, exponent = f"{value:e}".partition("e")
322
+ mantissa = mantissa.rstrip("0").rstrip(".") if "." in mantissa else mantissa
323
+ exp = int(exponent)
324
+ return f"{mantissa}e{'+' if exp > 0 else '-'}{abs(exp)}"
325
+
326
+
327
+ def _format_value(value: Any) -> str:
328
+ if isinstance(value, bool):
329
+ return "true" if value else "false"
330
+ if isinstance(value, int):
331
+ return str(value)
332
+ if isinstance(value, float):
333
+ return _format_float(value)
334
+ if isinstance(value, str):
335
+ return value
336
+ if hasattr(value, "value") and isinstance(getattr(value, "value"), (str, int)):
337
+ return _format_value(value.value)
338
+ import json as _json
339
+
340
+ return _json.dumps(value, separators=(",", ":"))
341
+
342
+
343
+ def _query_pairs(params: dict[str, Any] | None) -> list[tuple[str, str]]:
344
+ """Flatten params in order: None skipped, lists and tuples repeat the key."""
345
+ pairs: list[tuple[str, str]] = []
346
+ for key, value in (params or {}).items():
347
+ if value is None:
348
+ continue
349
+ if isinstance(value, (list, tuple)):
350
+ pairs.extend((key, _format_value(v)) for v in value if v is not None)
351
+ else:
352
+ pairs.append((key, _format_value(value)))
353
+ return pairs
354
+
355
+
356
+ def _encode_query(pairs: list[tuple[str, str]]) -> str:
357
+ return "&".join(f"{_escape_query(k)}={_escape_query(v)}" for k, v in pairs)
358
+
359
+
360
+ def _build_url(base_url: str, path: str, params: dict[str, Any] | None = None) -> str:
361
+ """Join *base_url* and an escaped *path*, keeping the base path and query,
362
+ then append *params* (lists repeat the key)."""
363
+ parts = urlsplit(base_url)
364
+ if not parts.scheme or not parts.netloc:
365
+ raise ValueError(f"base_url {base_url!r} needs a scheme and host")
366
+ if not path.startswith("/"):
367
+ path = "/" + path
368
+ joined_path = parts.path.rstrip("/") + path
369
+ query = parts.query
370
+ extra = _encode_query(_query_pairs(params))
371
+ if extra:
372
+ query = f"{query}&{extra}" if query else extra
373
+ return urlunsplit((parts.scheme, parts.netloc, joined_path, query, ""))
374
+
375
+
376
+ def _append_query(url: str, params: dict[str, Any] | None) -> str:
377
+ extra = _encode_query(_query_pairs(params))
378
+ if not extra:
379
+ return url
380
+ return f"{url}{'&' if '?' in url else '?'}{extra}"
381
+
382
+
383
+ # One multipart file: raw bytes, a binary file object, or a
384
+ # (filename, content[, content_type]) tuple.
385
+ FileInput = Any
386
+
387
+
388
+ def _form_content(body: Any) -> bytes:
389
+ """application/x-www-form-urlencoded bytes; lists repeat the key."""
390
+ return _encode_query(_query_pairs(dict(body or {}))).encode()
391
+
392
+
393
+ def _multipart_parts(
394
+ body: Any,
395
+ files: list[str],
396
+ list_files: list[str],
397
+ ) -> tuple[None, list[tuple[str, Any]]]:
398
+ """Encode a multipart body as httpx ``files`` entries (``data`` is always ``None``).
399
+
400
+ httpx 0.28 cannot combine a list of ``data`` tuples with ``files`` (a text
401
+ field raises ``TypeError``, and an empty list sends an empty body), so text
402
+ fields go in as ``(None, value)`` parts: same wire format, and repeated keys
403
+ keep their order.
404
+ """
405
+ out_files: list[tuple[str, Any]] = []
406
+ for key, value in dict(body or {}).items():
407
+ if value is None:
408
+ continue
409
+ if key in files:
410
+ out_files.append((key, value))
411
+ elif key in list_files:
412
+ out_files.extend((key, item) for item in value)
413
+ elif isinstance(value, (list, tuple)):
414
+ out_files.extend((key, (None, _format_value(v))) for v in value)
415
+ else:
416
+ out_files.append((key, (None, _format_value(value))))
417
+ return None, out_files
418
+
419
+
420
+ def _body_args(
421
+ content: Any,
422
+ data: Any,
423
+ files: Any,
424
+ headers: dict[str, str],
425
+ ) -> tuple[Any, Any, Any, dict[str, str]]:
426
+ """httpx sends ``files=[]`` as no body at all. A multipart operation called
427
+ with no fields still sends a multipart body: the empty one, a lone closing
428
+ boundary."""
429
+ if files is not None and len(files) == 0 and content is None and not data:
430
+ boundary = uuid.uuid4().hex
431
+ out = dict(headers)
432
+ _set_header(out, "Content-Type", f"multipart/form-data; boundary={boundary}")
433
+ return f"--{boundary}--\r\n".encode(), None, None, out
434
+ return content, data, files, headers
435
+
436
+
437
+ def _to_ws_url(url: str) -> str:
438
+ """Swap only the scheme; a URL inside the query string is untouched."""
439
+ if url.startswith("https://"):
440
+ return "wss://" + url[len("https://") :]
441
+ if url.startswith("http://"):
442
+ return "ws://" + url[len("http://") :]
146
443
  return url
147
444
 
148
445
 
149
- def _parse_body(response: httpx.Response) -> Any:
446
+ def _is_json_media(content_type: str) -> bool:
447
+ essence = content_type.split(";", 1)[0].strip().lower()
448
+ return essence == "application/json" or essence.endswith("+json")
449
+
450
+
451
+ def _parse_body(response: httpx.Response, kind: str = "json") -> Any:
452
+ """Decode a response per the operation's declared success kind.
453
+
454
+ ``binary`` returns bytes, ``text`` returns str, ``none`` returns None; JSON
455
+ (including ``+json`` media types) is decoded, anything else is returned as
456
+ text.
457
+ """
458
+ if kind == "binary":
459
+ return response.content
460
+ if kind == "none":
461
+ return None
150
462
  content_type = response.headers.get("content-type", "")
151
- if "application/json" in content_type:
463
+ if kind == "text":
464
+ return response.text
465
+ if _is_json_media(content_type):
466
+ if not response.content:
467
+ return None
152
468
  try:
153
469
  return response.json()
154
470
  except Exception:
155
471
  return None
472
+ if not response.content:
473
+ return None
156
474
  return response.text
157
475
 
158
476
 
477
+ def _clean_message(text: str) -> str:
478
+ cleaned = "".join(
479
+ ch for ch in text if ch in " " or unicodedata.category(ch)[0] != "C"
480
+ )
481
+ if len(cleaned) > MAX_ERROR_MESSAGE:
482
+ cleaned = cleaned[:MAX_ERROR_MESSAGE] + "…"
483
+ return cleaned
484
+
485
+
159
486
  def _raise_for_status(response: httpx.Response, body: Any = None) -> None:
160
- if response.status_code < 400:
487
+ """Raise the typed error for any non-2xx status. A 3xx counts: the SDK does
488
+ not follow redirects, so a redirect is not a successful response."""
489
+ status = response.status_code
490
+ if 200 <= status < 300:
161
491
  return
162
492
  parsed = body if body is not None else _parse_body(response)
163
493
  raw = response.content
164
- status = response.status_code
165
494
  msg_val = parsed.get("message") if isinstance(parsed, dict) else None
166
495
  if isinstance(msg_val, str):
167
496
  message = msg_val
@@ -177,7 +506,7 @@ def _raise_for_status(response: httpx.Response, body: Any = None) -> None:
177
506
  body=raw,
178
507
  data=parsed,
179
508
  response=response,
180
- message=message[:512] if message else f"HTTP {status}",
509
+ message=_clean_message(message) or f"HTTP {status}",
181
510
  )
182
511
 
183
512
 
@@ -190,7 +519,8 @@ def _apply_meta_to_request_ctx(ctx: RequestContext, meta: _RequestMeta | None) -
190
519
 
191
520
 
192
521
  def _apply_meta_to_response_ctx(
193
- ctx: ResponseContext, meta: _RequestMeta | None,
522
+ ctx: ResponseContext,
523
+ meta: _RequestMeta | None,
194
524
  ) -> None:
195
525
  if meta is None:
196
526
  return
@@ -199,6 +529,15 @@ def _apply_meta_to_response_ctx(
199
529
  ctx.invalidated_by = meta.invalidated_by
200
530
 
201
531
 
532
+ def _replayable(content: Any) -> bool:
533
+ """A body can be sent again after an auth refresh unless it is a one-shot stream."""
534
+ return content is None or isinstance(content, (bytes, bytearray, str))
535
+
536
+
537
+ def _timeout(value: float | None) -> httpx.Timeout:
538
+ return httpx.Timeout(value)
539
+
540
+
202
541
  async def _do_request_async(
203
542
  client: httpx.AsyncClient,
204
543
  config: ClientConfig,
@@ -212,11 +551,15 @@ async def _do_request_async(
212
551
  cancel_token: threading.Event | None = None,
213
552
  operation: str = "",
214
553
  request_meta: _RequestMeta | None = None,
554
+ data: Any = None,
555
+ files: Any = None,
556
+ auth: _AuthState | None = None,
215
557
  ) -> httpx.Response:
216
558
  if cancel_token is not None and cancel_token.is_set():
217
559
  raise asyncio.CancelledError()
218
560
  timeout_val = timeout if timeout is not None else config.timeout
219
561
  start = time.monotonic()
562
+ url = _append_query(url, params)
220
563
 
221
564
  req_ctx = RequestContext(method=method, url=url, headers=dict(headers), body=json)
222
565
  _apply_meta_to_request_ctx(req_ctx, request_meta)
@@ -227,51 +570,66 @@ async def _do_request_async(
227
570
  _emit_log(
228
571
  config,
229
572
  LogEntry(
230
- level="debug",
231
- event="request_start",
232
- operation=operation,
233
- duration_ms=0,
573
+ level="debug", event="request_start", operation=operation, duration_ms=0
234
574
  ),
235
575
  )
236
576
 
237
- try:
238
- response = await client.request(
239
- req_ctx.method,
240
- req_ctx.url,
241
- headers=req_ctx.headers,
242
- json=req_ctx.body if content is None else None,
243
- content=content,
244
- params={k: v for k, v in (params or {}).items() if v is not None} or None,
245
- timeout=httpx.Timeout(timeout_val) if timeout_val is not None else None,
577
+ async def _send(ctx: RequestContext) -> httpx.Response:
578
+ b_content, b_data, b_files, b_headers = _body_args(
579
+ content, data, files, ctx.headers
580
+ )
581
+ return await client.request(
582
+ ctx.method,
583
+ ctx.url,
584
+ headers=b_headers,
585
+ json=ctx.body
586
+ if content is None and data is None and files is None
587
+ else None,
588
+ content=b_content,
589
+ data=b_data,
590
+ files=b_files,
591
+ timeout=_timeout(timeout_val),
246
592
  )
247
593
 
594
+ async def _observe(response: httpx.Response, is_retry: bool) -> None:
248
595
  if config.on_response:
249
596
  raw = response.content
250
597
  parsed = _parse_body(response)
251
- resp_ctx = ResponseContext(response=response, body=raw, parsed=parsed)
598
+ resp_ctx = ResponseContext(
599
+ response=response, body=raw, parsed=parsed, is_retry=is_retry
600
+ )
252
601
  _apply_meta_to_response_ctx(resp_ctx, request_meta)
253
602
  for hook in config.on_response:
254
603
  await hook(resp_ctx)
255
604
 
256
- if response.status_code == 401 and config.on_auth_expired is not None:
257
- new_token = await config.on_auth_expired()
605
+ try:
606
+ response = await _send(req_ctx)
607
+ await _observe(response, False)
608
+
609
+ if (
610
+ response.status_code == 401
611
+ and config.on_auth_expired is not None
612
+ and _replayable(content)
613
+ ):
614
+ rejected = auth.token if auth is not None else config.bearer_token
615
+ new_token = (
616
+ await auth.refresh(config, rejected)
617
+ if auth is not None
618
+ else await _call_auth_hook(config)
619
+ )
258
620
  if new_token is not None:
259
621
  current_auth_name = _auth_header_name(config).lower()
260
622
  extra_no_auth = {
261
- k: v for k, v in req_ctx.headers.items()
623
+ k: v
624
+ for k, v in req_ctx.headers.items()
262
625
  if k.lower() != current_auth_name
263
626
  }
264
- retry_headers = _build_headers(
265
- config, extra=extra_no_auth or None, bearer_token=new_token,
266
- )
267
- retry_params = {
268
- k: v for k, v in (params or {}).items() if v is not None
269
- } or None
270
-
271
627
  retry_ctx = RequestContext(
272
628
  method=req_ctx.method,
273
629
  url=req_ctx.url,
274
- headers=retry_headers,
630
+ headers=_build_headers(
631
+ config, extra=extra_no_auth or None, bearer_token=new_token
632
+ ),
275
633
  body=req_ctx.body,
276
634
  is_retry=True,
277
635
  )
@@ -279,27 +637,8 @@ async def _do_request_async(
279
637
  if config.on_request:
280
638
  for hook in config.on_request:
281
639
  await hook(retry_ctx)
282
-
283
- response = await client.request(
284
- retry_ctx.method,
285
- retry_ctx.url,
286
- headers=retry_ctx.headers,
287
- json=retry_ctx.body,
288
- params=retry_params,
289
- timeout=(
290
- httpx.Timeout(timeout_val) if timeout_val is not None else None
291
- ),
292
- )
293
-
294
- if config.on_response:
295
- raw = response.content
296
- parsed = _parse_body(response)
297
- resp_ctx = ResponseContext(
298
- response=response, body=raw, parsed=parsed, is_retry=True,
299
- )
300
- _apply_meta_to_response_ctx(resp_ctx, request_meta)
301
- for hook in config.on_response:
302
- await hook(resp_ctx)
640
+ response = await _send(retry_ctx)
641
+ await _observe(response, True)
303
642
 
304
643
  _emit_log(
305
644
  config,
@@ -340,11 +679,15 @@ def _do_request_sync(
340
679
  cancel_token: threading.Event | None = None,
341
680
  operation: str = "",
342
681
  request_meta: _RequestMeta | None = None,
682
+ data: Any = None,
683
+ files: Any = None,
684
+ auth: _AuthState | None = None,
343
685
  ) -> httpx.Response:
344
686
  if cancel_token is not None and cancel_token.is_set():
345
687
  raise asyncio.CancelledError()
346
688
  timeout_val = timeout if timeout is not None else config.timeout
347
689
  start = time.monotonic()
690
+ url = _append_query(url, params)
348
691
 
349
692
  req_ctx = RequestContext(method=method, url=url, headers=dict(headers), body=json)
350
693
  _apply_meta_to_request_ctx(req_ctx, request_meta)
@@ -355,58 +698,66 @@ def _do_request_sync(
355
698
  _emit_log(
356
699
  config,
357
700
  LogEntry(
358
- level="debug",
359
- event="request_start",
360
- operation=operation,
361
- duration_ms=0,
701
+ level="debug", event="request_start", operation=operation, duration_ms=0
362
702
  ),
363
703
  )
364
704
 
365
- try:
366
- response = client.request(
367
- req_ctx.method,
368
- req_ctx.url,
369
- headers=req_ctx.headers,
370
- json=req_ctx.body if content is None else None,
371
- content=content,
372
- params={k: v for k, v in (params or {}).items() if v is not None} or None,
373
- timeout=httpx.Timeout(timeout_val) if timeout_val is not None else None,
705
+ def _send(ctx: RequestContext) -> httpx.Response:
706
+ b_content, b_data, b_files, b_headers = _body_args(
707
+ content, data, files, ctx.headers
708
+ )
709
+ return client.request(
710
+ ctx.method,
711
+ ctx.url,
712
+ headers=b_headers,
713
+ json=ctx.body
714
+ if content is None and data is None and files is None
715
+ else None,
716
+ content=b_content,
717
+ data=b_data,
718
+ files=b_files,
719
+ timeout=_timeout(timeout_val),
374
720
  )
375
721
 
722
+ def _observe(response: httpx.Response, is_retry: bool) -> None:
376
723
  if config.on_response_sync:
377
724
  raw = response.content
378
725
  parsed = _parse_body(response)
379
- resp_ctx = ResponseContext(response=response, body=raw, parsed=parsed)
726
+ resp_ctx = ResponseContext(
727
+ response=response, body=raw, parsed=parsed, is_retry=is_retry
728
+ )
380
729
  _apply_meta_to_response_ctx(resp_ctx, request_meta)
381
730
  for hook in config.on_response_sync:
382
731
  hook(resp_ctx)
383
732
 
384
- if response.status_code == 401 and config.on_auth_expired is not None:
385
- try:
386
- _loop = asyncio.new_event_loop()
387
- try:
388
- new_token = _loop.run_until_complete(config.on_auth_expired())
389
- finally:
390
- _loop.close()
391
- except RuntimeError:
392
- new_token = None
733
+ try:
734
+ response = _send(req_ctx)
735
+ _observe(response, False)
736
+
737
+ if (
738
+ response.status_code == 401
739
+ and config.on_auth_expired is not None
740
+ and _replayable(content)
741
+ ):
742
+ rejected = auth.token if auth is not None else config.bearer_token
743
+ new_token = (
744
+ auth.refresh_sync(config, rejected)
745
+ if auth is not None
746
+ else _call_auth_hook_sync(config)
747
+ )
393
748
  if new_token is not None:
394
749
  current_auth_name = _auth_header_name(config).lower()
395
750
  extra_no_auth = {
396
- k: v for k, v in req_ctx.headers.items()
751
+ k: v
752
+ for k, v in req_ctx.headers.items()
397
753
  if k.lower() != current_auth_name
398
754
  }
399
- retry_headers = _build_headers(
400
- config, extra=extra_no_auth or None, bearer_token=new_token,
401
- )
402
- retry_params = {
403
- k: v for k, v in (params or {}).items() if v is not None
404
- } or None
405
-
406
755
  retry_ctx = RequestContext(
407
756
  method=req_ctx.method,
408
757
  url=req_ctx.url,
409
- headers=retry_headers,
758
+ headers=_build_headers(
759
+ config, extra=extra_no_auth or None, bearer_token=new_token
760
+ ),
410
761
  body=req_ctx.body,
411
762
  is_retry=True,
412
763
  )
@@ -414,27 +765,8 @@ def _do_request_sync(
414
765
  if config.on_request_sync:
415
766
  for hook in config.on_request_sync:
416
767
  hook(retry_ctx)
417
-
418
- response = client.request(
419
- retry_ctx.method,
420
- retry_ctx.url,
421
- headers=retry_ctx.headers,
422
- json=retry_ctx.body,
423
- params=retry_params,
424
- timeout=(
425
- httpx.Timeout(timeout_val) if timeout_val is not None else None
426
- ),
427
- )
428
-
429
- if config.on_response_sync:
430
- raw = response.content
431
- parsed = _parse_body(response)
432
- resp_ctx = ResponseContext(
433
- response=response, body=raw, parsed=parsed, is_retry=True,
434
- )
435
- _apply_meta_to_response_ctx(resp_ctx, request_meta)
436
- for hook in config.on_response_sync:
437
- hook(resp_ctx)
768
+ response = _send(retry_ctx)
769
+ _observe(response, True)
438
770
 
439
771
  _emit_log(
440
772
  config,
@@ -462,17 +794,67 @@ def _do_request_sync(
462
794
  raise
463
795
 
464
796
 
797
+ @asynccontextmanager
798
+ async def _open_stream(
799
+ client: httpx.AsyncClient,
800
+ config: ClientConfig,
801
+ method: str,
802
+ url: str,
803
+ headers: dict[str, str],
804
+ json: Any = None,
805
+ content: Any = None,
806
+ data: Any = None,
807
+ files: Any = None,
808
+ ) -> AsyncIterator[httpx.Response]:
809
+ """Open a streaming response through the request hooks. The read timeout is
810
+ disabled (a stream may idle), connect/write/pool use the config timeout. The
811
+ response is closed on every exit path, including a raised status error."""
812
+ req_ctx = RequestContext(method=method, url=url, headers=dict(headers), body=json)
813
+ if config.on_request:
814
+ for hook in config.on_request:
815
+ await hook(req_ctx)
816
+ timeout = httpx.Timeout(config.timeout, read=None)
817
+ b_content, b_data, b_files, b_headers = _body_args(
818
+ content, data, files, req_ctx.headers
819
+ )
820
+ request = client.build_request(
821
+ req_ctx.method,
822
+ req_ctx.url,
823
+ headers=b_headers,
824
+ json=req_ctx.body
825
+ if content is None and data is None and files is None
826
+ else None,
827
+ content=b_content,
828
+ data=b_data,
829
+ files=b_files,
830
+ timeout=timeout,
831
+ )
832
+ response = await client.send(request, stream=True)
833
+ try:
834
+ if not 200 <= response.status_code < 300:
835
+ await response.aread()
836
+ _raise_for_status(response)
837
+ yield response
838
+ finally:
839
+ await response.aclose()
840
+
841
+
465
842
  __all__ = [
466
843
  "ClientConfig",
844
+ "FileInput",
467
845
  "InvalidationConfig",
468
846
  "LogEntry",
847
+ "PathParamError",
469
848
  "RequestContext",
470
849
  "ResponseContext",
471
850
  "SDKResult",
851
+ "_AuthState",
472
852
  "_build_headers",
473
853
  "_build_url",
474
854
  "_do_request_async",
475
855
  "_do_request_sync",
856
+ "_expand_path",
857
+ "_open_stream",
476
858
  "_parse_body",
477
859
  "_raise_for_status",
478
860
  ]