@lovrozagar/honey 0.6.8 → 0.7.0

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