@lovrozagar/honey 0.6.8 → 0.7.1

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 +819 -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 +603 -423
  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,12 +1,14 @@
1
1
  /* Code generated by honey. DO NOT EDIT. */
2
2
 
3
3
  use crate::errors::Error;
4
+ use crate::sse::SseParser;
4
5
  use bytes::Bytes;
5
6
  use serde::{de::DeserializeOwned, Serialize};
6
7
  use std::collections::HashMap;
7
8
  use std::collections::VecDeque;
8
9
  use std::marker::PhantomData;
9
10
  use std::pin::Pin;
11
+ use std::time::Duration;
10
12
 
11
13
  /// TransportKind tags which transport a connection was opened with.
12
14
  /// The behaviour-bearing abstraction is the [`Transport`] trait.
@@ -51,6 +53,12 @@ impl std::fmt::Display for RealtimeError {
51
53
  impl std::error::Error for RealtimeError {}
52
54
 
53
55
  /// ResumableConnectionOpts configures reconnect behaviour and auth.
56
+ ///
57
+ /// `max_reconnect_attempts` caps consecutive failed reconnects (default 5; 0
58
+ /// means unlimited). Reconnects back off exponentially from
59
+ /// `reconnect_delay_ms` (default 1000) up to 30 s, with jitter. `headers` go
60
+ /// on every transport request; the generated SDK fills in config headers and
61
+ /// auth. `http_client` lets SSE / long-poll share the SDK's client.
54
62
  #[derive(Debug, Clone, Default)]
55
63
  pub struct ResumableConnectionOpts {
56
64
  pub reconnect_token: Option<String>,
@@ -59,6 +67,7 @@ pub struct ResumableConnectionOpts {
59
67
  pub reconnect_delay_ms: Option<u64>,
60
68
  pub last_event_id: Option<String>,
61
69
  pub headers: HashMap<String, String>,
70
+ pub http_client: Option<reqwest::Client>,
62
71
  }
63
72
 
64
73
  /// TransportOpts is handed to each `Transport::connect`.
@@ -74,37 +83,35 @@ pub struct TransportOpts {
74
83
  pub reconnect_delay_ms: Option<u64>,
75
84
  }
76
85
 
86
+ const MAX_RECONNECT_DELAY: Duration = Duration::from_secs(30);
87
+ const OPEN_TIMEOUT: Duration = Duration::from_secs(30);
88
+ const POLL_TIMEOUT: Duration = Duration::from_secs(60);
89
+
77
90
  type WsStream = tokio_tungstenite::WebSocketStream<
78
91
  tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
79
92
  >;
80
93
 
81
94
  type ByteStream = Pin<Box<dyn futures_util::Stream<Item = Result<Bytes, reqwest::Error>> + Send>>;
82
95
 
83
- /// SseState holds mutable state for an active SSE resumable connection.
96
+ /// SseState holds mutable state for an active SSE connection.
84
97
  pub struct SseState {
85
98
  pub byte_stream: ByteStream,
86
- pub buf: Vec<u8>,
87
99
  pub last_event_id: Option<String>,
88
100
  pub retry_hint_ms: u64,
89
- /* internal fields for reconnect and send */
101
+ parser: SseParser,
102
+ pending: VecDeque<crate::sse::SseEvent>,
90
103
  http_client: reqwest::Client,
91
104
  url: String,
92
105
  extra_headers: HashMap<String, String>,
93
- max_reconnect_attempts: u32,
94
- reconnect_delay_ms: u64,
95
- reconnect_attempts: u32,
96
106
  }
97
107
 
98
- /// LongpollState holds mutable state for a long-poll resumable connection.
108
+ /// LongpollState holds mutable state for a long-poll connection.
99
109
  pub struct LongpollState {
100
110
  pub client: reqwest::Client,
101
111
  pub url: String,
102
112
  pub cursor: Option<String>,
103
113
  pub pending_events: VecDeque<serde_json::Value>,
104
114
  pub headers: HashMap<String, String>,
105
- max_reconnect_attempts: u32,
106
- reconnect_delay_ms: u64,
107
- reconnect_attempts: u32,
108
115
  }
109
116
 
110
117
  /// TransportConn is the live per-transport connection.
@@ -132,6 +139,15 @@ pub trait Transport: Send + Sync + 'static {
132
139
  ) -> Result<Box<dyn TransportConn>, RealtimeError>;
133
140
  }
134
141
 
142
+ /// Payloads that are not JSON arrive as a JSON string instead of tearing the connection down.
143
+ fn decode_payload(text: &str) -> serde_json::Value {
144
+ serde_json::from_str(text).unwrap_or_else(|_| serde_json::Value::String(text.to_string()))
145
+ }
146
+
147
+ fn connect_err(e: impl std::fmt::Display) -> RealtimeError {
148
+ RealtimeError::Connect(Error::Other(e.to_string()))
149
+ }
150
+
135
151
  /* ------------------------------------------------------------------ */
136
152
  /* WS adapter */
137
153
  /* ------------------------------------------------------------------ */
@@ -160,19 +176,13 @@ impl Transport for WsTransport {
160
176
  opts: &TransportOpts,
161
177
  ) -> Result<Box<dyn TransportConn>, RealtimeError> {
162
178
  use tokio_tungstenite::tungstenite::client::IntoClientRequest;
163
- let ws_url = url.replacen("https://", "wss://", 1).replacen("http://", "ws://", 1);
164
- let mut req = ws_url
165
- .as_str()
166
- .into_client_request()
167
- .map_err(|e| RealtimeError::Connect(Error::Other(e.to_string())))?;
179
+ let ws_url = crate::runtime::to_ws_url(url);
180
+ let mut req = ws_url.as_str().into_client_request().map_err(connect_err)?;
168
181
  if let Some(ref protos) = opts.protocols {
169
182
  if !protos.is_empty() {
170
183
  req.headers_mut().insert(
171
184
  "Sec-WebSocket-Protocol",
172
- protos
173
- .join(", ")
174
- .parse()
175
- .map_err(|_| RealtimeError::Connect(Error::Other("invalid protocol header".into())))?,
185
+ protos.join(", ").parse().map_err(|_| connect_err("invalid protocol header"))?,
176
186
  );
177
187
  }
178
188
  }
@@ -184,9 +194,7 @@ impl Transport for WsTransport {
184
194
  req.headers_mut().insert(name, val);
185
195
  }
186
196
  }
187
- let (stream, _) = tokio_tungstenite::connect_async(req)
188
- .await
189
- .map_err(|e| RealtimeError::Connect(Error::Other(e.to_string())))?;
197
+ let (stream, _) = tokio_tungstenite::connect_async(req).await.map_err(connect_err)?;
190
198
  Ok(Box::new(WsConn { stream: Box::new(stream) }))
191
199
  }
192
200
  }
@@ -199,10 +207,9 @@ impl TransportConn for WsConn {
199
207
  async fn send_json(&mut self, value: serde_json::Value) -> Result<(), RealtimeError> {
200
208
  use futures_util::SinkExt;
201
209
  use tokio_tungstenite::tungstenite::Message;
202
- let encoded = serde_json::to_vec(&value)
203
- .map_err(|e| RealtimeError::Send(Error::Json(e)))?;
210
+ let encoded = serde_json::to_string(&value).map_err(|e| RealtimeError::Send(Error::Json(e)))?;
204
211
  self.stream
205
- .send(Message::Text(String::from_utf8_lossy(&encoded).into_owned()))
212
+ .send(Message::Text(encoded))
206
213
  .await
207
214
  .map_err(|e| RealtimeError::Send(Error::Other(e.to_string())))
208
215
  }
@@ -213,15 +220,10 @@ impl TransportConn for WsConn {
213
220
  match self.stream.next().await {
214
221
  None => return Err(RealtimeError::Closed),
215
222
  Some(Err(e)) => return Err(RealtimeError::Recv(Error::Other(e.to_string()))),
216
- Some(Ok(Message::Text(t))) => {
217
- return serde_json::from_slice(t.as_bytes())
218
- .map_err(|e| RealtimeError::Recv(Error::Json(e)));
219
- }
220
- Some(Ok(Message::Binary(b))) => {
221
- return serde_json::from_slice(&b)
222
- .map_err(|e| RealtimeError::Recv(Error::Json(e)));
223
- }
224
- Some(Ok(_)) => continue, /* ping/pong/close — skip */
223
+ Some(Ok(Message::Text(t))) => return Ok(decode_payload(&t)),
224
+ Some(Ok(Message::Binary(b))) => return Ok(decode_payload(&String::from_utf8_lossy(&b))),
225
+ Some(Ok(Message::Close(_))) => return Err(RealtimeError::Closed),
226
+ Some(Ok(_)) => continue, /* ping/pong — skip */
225
227
  }
226
228
  }
227
229
  }
@@ -249,10 +251,17 @@ pub struct SseTransport {
249
251
  }
250
252
 
251
253
  /// SseConn is the live SSE connection returned by [`SseTransport::connect`].
254
+ /// A dropped or ended stream is reported as an error; ResumableConnection reconnects.
252
255
  pub struct SseConn {
253
256
  state: SseState,
254
257
  }
255
258
 
259
+ fn pick_client(own: &Option<reqwest::Client>, opts: &TransportOpts) -> reqwest::Client {
260
+ own.clone()
261
+ .or_else(|| opts.http_client.clone())
262
+ .unwrap_or_else(crate::runtime::default_http_client)
263
+ }
264
+
256
265
  #[async_trait::async_trait]
257
266
  impl Transport for SseTransport {
258
267
  fn name(&self) -> &'static str {
@@ -266,14 +275,8 @@ impl Transport for SseTransport {
266
275
  url: &str,
267
276
  opts: &TransportOpts,
268
277
  ) -> Result<Box<dyn TransportConn>, RealtimeError> {
269
- let client = self
270
- .http_client
271
- .clone()
272
- .or_else(|| opts.http_client.clone())
273
- .unwrap_or_else(reqwest::Client::new);
274
- let state = build_sse_state(url.to_string(), client, opts)
275
- .await
276
- .map_err(|e| RealtimeError::Connect(Error::Other(e.to_string())))?;
278
+ let client = pick_client(&self.http_client, opts);
279
+ let state = build_sse_state(url.to_string(), client, opts).await?;
277
280
  Ok(Box::new(SseConn { state }))
278
281
  }
279
282
  }
@@ -284,75 +287,36 @@ impl TransportConn for SseConn {
284
287
  TransportKind::Sse
285
288
  }
286
289
  async fn send_json(&mut self, value: serde_json::Value) -> Result<(), RealtimeError> {
287
- /* SSE is server → client; send POSTs back to the endpoint. */
288
- self.state
289
- .http_client
290
- .post(self.state.url.as_str())
291
- .json(&value)
292
- .send()
293
- .await
294
- .map_err(|e| RealtimeError::Send(Error::Other(e.to_string())))?;
290
+ /* SSE is server → client; send POSTs back to the endpoint with the same headers. */
291
+ let mut req = self.state.http_client.post(self.state.url.as_str()).json(&value);
292
+ for (k, v) in &self.state.extra_headers {
293
+ req = req.header(k.as_str(), v.as_str());
294
+ }
295
+ let resp = req.send().await.map_err(|e| RealtimeError::Send(Error::Other(e.to_string())))?;
296
+ if !resp.status().is_success() {
297
+ return Err(RealtimeError::Send(Error::Other(format!("status {}", resp.status()))));
298
+ }
295
299
  Ok(())
296
300
  }
297
301
  async fn recv_json(&mut self) -> Result<serde_json::Value, RealtimeError> {
298
- use crate::sse::{find_double_newline, parse_sse_block};
299
302
  use futures_util::StreamExt;
300
303
  loop {
301
- if let Some((start, end)) = find_double_newline(&self.state.buf) {
302
- let block = self.state.buf[..start].to_vec();
303
- self.state.buf.drain(..end);
304
- let block_str = String::from_utf8_lossy(&block);
305
- let trimmed = block_str.trim();
306
- if !trimmed.is_empty() {
307
- if let Some(ev) = parse_sse_block(trimmed) {
308
- if !ev.id.is_empty() {
309
- self.state.last_event_id = Some(ev.id.clone());
310
- }
311
- if ev.retry > 0 {
312
- self.state.retry_hint_ms = ev.retry as u64;
313
- }
314
- return serde_json::from_str(&ev.data)
315
- .map_err(|e| RealtimeError::Recv(Error::Json(e)));
316
- }
304
+ if let Some(ev) = self.state.pending.pop_front() {
305
+ if !ev.id.is_empty() {
306
+ self.state.last_event_id = Some(ev.id.clone());
317
307
  }
318
- continue;
308
+ if ev.retry > 0 {
309
+ self.state.retry_hint_ms = (ev.retry as u64).min(MAX_RECONNECT_DELAY.as_millis() as u64);
310
+ }
311
+ return Ok(decode_payload(&ev.data));
319
312
  }
320
313
  match self.state.byte_stream.next().await {
321
314
  Some(Ok(bytes)) => {
322
- self.state.buf.extend_from_slice(&bytes);
323
- }
324
- Some(Err(e)) => {
325
- return Err(RealtimeError::Recv(Error::Other(e.to_string())));
326
- }
327
- None => {
328
- /* stream ended — attempt transport-level reconnect */
329
- self.state.reconnect_attempts += 1;
330
- if self.state.reconnect_attempts >= self.state.max_reconnect_attempts {
331
- return Err(RealtimeError::Closed);
332
- }
333
- let delay_ms = if self.state.retry_hint_ms > 0 {
334
- self.state.retry_hint_ms
335
- } else {
336
- self.state.reconnect_delay_ms
337
- };
338
- tokio::time::sleep(tokio::time::Duration::from_millis(delay_ms)).await;
339
- let reopen_opts = TransportOpts {
340
- last_event_id: self.state.last_event_id.clone(),
341
- headers: self.state.extra_headers.clone(),
342
- max_reconnect_attempts: Some(self.state.max_reconnect_attempts),
343
- reconnect_delay_ms: Some(self.state.reconnect_delay_ms),
344
- ..Default::default()
345
- };
346
- let new_state = build_sse_state(
347
- self.state.url.clone(),
348
- self.state.http_client.clone(),
349
- &reopen_opts,
350
- )
351
- .await
352
- .map_err(|e| RealtimeError::Connect(Error::Other(e.to_string())))?;
353
- self.state.byte_stream = new_state.byte_stream;
354
- self.state.buf.clear();
315
+ let events = self.state.parser.feed(&bytes).map_err(RealtimeError::Recv)?;
316
+ self.state.pending.extend(events);
355
317
  }
318
+ Some(Err(e)) => return Err(RealtimeError::Recv(Error::Other(e.to_string()))),
319
+ None => return Err(RealtimeError::Closed),
356
320
  }
357
321
  }
358
322
  }
@@ -390,84 +354,83 @@ impl Transport for LongpollTransport {
390
354
  url: &str,
391
355
  opts: &TransportOpts,
392
356
  ) -> Result<Box<dyn TransportConn>, RealtimeError> {
393
- let client = self
394
- .http_client
395
- .clone()
396
- .or_else(|| opts.http_client.clone())
397
- .unwrap_or_else(reqwest::Client::new);
398
357
  let state = LongpollState {
399
- client,
358
+ client: pick_client(&self.http_client, opts),
400
359
  url: url.to_string(),
401
360
  cursor: None,
402
361
  pending_events: VecDeque::new(),
403
362
  headers: opts.headers.clone(),
404
- max_reconnect_attempts: opts.max_reconnect_attempts.unwrap_or(5),
405
- reconnect_delay_ms: opts.reconnect_delay_ms.unwrap_or(1000),
406
- reconnect_attempts: 0,
407
363
  };
408
364
  Ok(Box::new(LongpollConn { state }))
409
365
  }
410
366
  }
411
367
 
368
+ fn with_query(url: &str, key: &str, value: &str) -> String {
369
+ let sep = if url.contains('?') { '&' } else { '?' };
370
+ format!(
371
+ "{}{}{}={}",
372
+ url,
373
+ sep,
374
+ crate::runtime::escape_query(key),
375
+ crate::runtime::escape_query(value)
376
+ )
377
+ }
378
+
412
379
  #[async_trait::async_trait]
413
380
  impl TransportConn for LongpollConn {
414
381
  fn kind(&self) -> TransportKind {
415
382
  TransportKind::Longpoll
416
383
  }
417
384
  async fn send_json(&mut self, value: serde_json::Value) -> Result<(), RealtimeError> {
418
- self.state
419
- .client
420
- .post(self.state.url.as_str())
421
- .json(&value)
422
- .send()
423
- .await
424
- .map_err(|e| RealtimeError::Send(Error::Other(e.to_string())))?;
385
+ let mut req = self.state.client.post(self.state.url.as_str()).json(&value);
386
+ for (k, v) in &self.state.headers {
387
+ req = req.header(k.as_str(), v.as_str());
388
+ }
389
+ let resp = req.send().await.map_err(|e| RealtimeError::Send(Error::Other(e.to_string())))?;
390
+ if !resp.status().is_success() {
391
+ return Err(RealtimeError::Send(Error::Other(format!("status {}", resp.status()))));
392
+ }
425
393
  Ok(())
426
394
  }
395
+ /// recv_json returns the next queued event, polling when the queue is empty.
396
+ /// A failed or non-2xx poll is returned as an error (ResumableConnection backs off).
427
397
  async fn recv_json(&mut self) -> Result<serde_json::Value, RealtimeError> {
428
398
  loop {
429
399
  if let Some(ev) = self.state.pending_events.pop_front() {
430
400
  return Ok(ev);
431
401
  }
432
402
  let request_url = match &self.state.cursor {
433
- Some(c) => format!("{}?cursor={}", self.state.url, c),
403
+ Some(c) => with_query(&self.state.url, "cursor", c),
434
404
  None => self.state.url.clone(),
435
405
  };
436
- let mut req = self.state.client.get(request_url.as_str());
406
+ let mut req = self.state.client.get(request_url.as_str()).timeout(POLL_TIMEOUT);
437
407
  for (k, v) in &self.state.headers {
438
408
  req = req.header(k.as_str(), v.as_str());
439
409
  }
440
- match req.send().await {
441
- Err(e) => {
442
- self.state.reconnect_attempts += 1;
443
- if self.state.reconnect_attempts >= self.state.max_reconnect_attempts {
444
- return Err(RealtimeError::Recv(Error::Other(e.to_string())));
445
- }
446
- tokio::time::sleep(tokio::time::Duration::from_millis(
447
- self.state.reconnect_delay_ms,
448
- ))
449
- .await;
450
- }
451
- Ok(resp) => {
452
- let bytes = resp
453
- .bytes()
454
- .await
455
- .map_err(|e| RealtimeError::Recv(Error::Other(e.to_string())))?;
456
-
457
- #[derive(serde::Deserialize)]
458
- struct PollResponse {
459
- #[serde(default)]
460
- events: Vec<serde_json::Value>,
461
- cursor: Option<String>,
410
+ let resp = req.send().await.map_err(|e| RealtimeError::Recv(Error::Other(e.to_string())))?;
411
+ if !resp.status().is_success() {
412
+ return Err(RealtimeError::Recv(Error::Other(format!(
413
+ "longpoll status {}",
414
+ resp.status()
415
+ ))));
416
+ }
417
+ let bytes = resp.bytes().await.map_err(|e| RealtimeError::Recv(Error::Other(e.to_string())))?;
418
+ if bytes.is_empty() {
419
+ continue;
420
+ }
421
+ match serde_json::from_slice::<serde_json::Value>(&bytes)
422
+ .map_err(|e| RealtimeError::Recv(Error::Json(e)))?
423
+ {
424
+ serde_json::Value::Array(events) => self.state.pending_events.extend(events),
425
+ serde_json::Value::Object(mut obj) => {
426
+ if let Some(c) = obj.get("cursor").and_then(|c| c.as_str()) {
427
+ self.state.cursor = Some(c.to_string());
462
428
  }
463
-
464
- let parsed: PollResponse = serde_json::from_slice(&bytes)
465
- .map_err(|e| RealtimeError::Recv(Error::Json(e)))?;
466
- self.state.cursor = parsed.cursor;
467
- for ev in parsed.events {
468
- self.state.pending_events.push_back(ev);
429
+ if let Some(serde_json::Value::Array(events)) = obj.remove("events") {
430
+ self.state.pending_events.extend(events);
469
431
  }
470
432
  }
433
+ other => self.state.pending_events.push_back(other),
471
434
  }
472
435
  }
473
436
  }
@@ -483,6 +446,10 @@ impl TransportConn for LongpollConn {
483
446
 
484
447
  /// ResumableConnection is a typed, self-reconnecting realtime channel.
485
448
  /// Send/Recv type parameters encode the message types flowing in each direction.
449
+ ///
450
+ /// `recv` is cancel-safe with respect to the connection: if a `recv` future is
451
+ /// dropped mid-reconnect (`select!`, `timeout`), the next `recv` or `send`
452
+ /// reopens the chain instead of reporting the connection closed.
486
453
  pub struct ResumableConnection<Send, Recv> {
487
454
  state: ConnectionState,
488
455
  url: String,
@@ -492,6 +459,7 @@ pub struct ResumableConnection<Send, Recv> {
492
459
  conn: Option<Box<dyn TransportConn>>,
493
460
  proven_index: Option<usize>,
494
461
  reconnect_attempts: u32,
462
+ closed: bool,
495
463
  _send: PhantomData<Send>,
496
464
  _recv: PhantomData<Recv>,
497
465
  }
@@ -509,16 +477,14 @@ where
509
477
  transports: Vec<Box<dyn Transport>>,
510
478
  ) -> Result<Self, RealtimeError> {
511
479
  if transports.is_empty() {
512
- return Err(RealtimeError::Connect(Error::Other(
513
- "no transports configured".into(),
514
- )));
480
+ return Err(RealtimeError::Connect(Error::Other("no transports configured".into())));
515
481
  }
516
482
  let transport_opts = TransportOpts {
517
483
  reconnect_token: opts.reconnect_token.clone(),
518
484
  protocols: opts.protocols.clone(),
519
485
  last_event_id: opts.last_event_id.clone(),
520
486
  headers: opts.headers.clone(),
521
- http_client: None,
487
+ http_client: opts.http_client.clone(),
522
488
  max_reconnect_attempts: opts.max_reconnect_attempts,
523
489
  reconnect_delay_ms: opts.reconnect_delay_ms,
524
490
  };
@@ -531,6 +497,7 @@ where
531
497
  conn: None,
532
498
  proven_index: None,
533
499
  reconnect_attempts: 0,
500
+ closed: false,
534
501
  _send: PhantomData,
535
502
  _recv: PhantomData,
536
503
  };
@@ -560,9 +527,7 @@ where
560
527
 
561
528
  /// proven_transport returns the kind of the last successfully-opened transport.
562
529
  pub fn proven_transport(&self) -> Option<TransportKind> {
563
- self.proven_index
564
- .and_then(|i| self.transports.get(i))
565
- .map(|t| t.kind())
530
+ self.proven_index.and_then(|i| self.transports.get(i)).map(|t| t.kind())
566
531
  }
567
532
 
568
533
  /// current_transport returns the kind of the currently-active transport.
@@ -570,58 +535,52 @@ where
570
535
  self.conn.as_ref().map(|c| c.kind())
571
536
  }
572
537
 
573
- /// send serialises a message and writes it to the active transport.
538
+ /// send serialises a message and writes it to the active transport,
539
+ /// reopening the chain first if a previous reconnect was interrupted.
574
540
  pub async fn send(&mut self, data: &SendMsg) -> Result<(), RealtimeError> {
575
- let conn = self.conn.as_mut().ok_or(RealtimeError::Closed)?;
541
+ self.ensure_open().await?;
576
542
  let v = serde_json::to_value(data).map_err(|e| RealtimeError::Send(Error::Json(e)))?;
543
+ let conn = self.conn.as_mut().ok_or(RealtimeError::Closed)?;
577
544
  conn.send_json(v).await
578
545
  }
579
546
 
580
- /// recv reads the next message from the transport and deserialises it.
581
- /// On transport failure: invalidates `proven_index`, sleeps `reconnect_delay_ms`,
582
- /// re-runs `open_chain()` (full fallback sequence). Mirrors Python `_iter`
583
- /// and TS `createResumableConnection`'s recv loop. Gives up after
584
- /// `max_reconnect_attempts` attempts and returns the last error.
547
+ /// recv reads the next message, reconnecting with exponential backoff and
548
+ /// jitter on transport failure. Gives up after `max_reconnect_attempts`
549
+ /// consecutive failures and returns the last error.
585
550
  pub async fn recv(&mut self) -> Result<RecvMsg, RealtimeError> {
586
- let max_attempts = self.opts.max_reconnect_attempts.unwrap_or(5);
587
- let delay_ms = self.opts.reconnect_delay_ms.unwrap_or(1000);
588
551
  loop {
589
- let result = {
590
- let conn = self.conn.as_mut().ok_or(RealtimeError::Closed)?;
591
- conn.recv_json().await
552
+ self.ensure_open().await?;
553
+ let result = match self.conn.as_mut() {
554
+ Some(conn) => conn.recv_json().await,
555
+ None => continue,
592
556
  };
593
557
  match result {
594
558
  Ok(v) => {
595
559
  self.reconnect_attempts = 0;
596
- return serde_json::from_value(v)
597
- .map_err(|e| RealtimeError::Recv(Error::Json(e)));
560
+ return serde_json::from_value(v).map_err(|e| RealtimeError::Recv(Error::Json(e)));
598
561
  }
599
562
  Err(err) => {
600
- self.reconnect_attempts += 1;
601
- if max_attempts > 0 && self.reconnect_attempts > max_attempts {
602
- self.state = ConnectionState::Disconnected;
603
- return Err(err);
563
+ if self.closed {
564
+ return Err(RealtimeError::Closed);
604
565
  }
605
566
  self.state = ConnectionState::Reconnecting;
606
- /* drop the dead conn + invalidate proven so chain retries from the top */
567
+ /* drop the dead conn + invalidate proven so the chain retries from the top */
607
568
  if let Some(mut c) = self.conn.take() {
608
569
  let _ = c.close().await;
609
570
  }
610
571
  self.proven_index = None;
611
- if delay_ms > 0 {
612
- tokio::time::sleep(tokio::time::Duration::from_millis(delay_ms)).await;
613
- }
614
- if let Err(open_err) = self.open_chain().await {
572
+ if let Err(e) = self.reconnect(err).await {
615
573
  self.state = ConnectionState::Disconnected;
616
- return Err(open_err);
574
+ return Err(e);
617
575
  }
618
576
  }
619
577
  }
620
578
  }
621
579
  }
622
580
 
623
- /// close gracefully shuts down the connection.
581
+ /// close gracefully shuts down the connection; later calls return Closed.
624
582
  pub async fn close(&mut self) -> Result<(), RealtimeError> {
583
+ self.closed = true;
625
584
  if let Some(mut c) = self.conn.take() {
626
585
  let _ = c.close().await;
627
586
  }
@@ -629,74 +588,115 @@ where
629
588
  Ok(())
630
589
  }
631
590
 
591
+ async fn ensure_open(&mut self) -> Result<(), RealtimeError> {
592
+ if self.closed {
593
+ return Err(RealtimeError::Closed);
594
+ }
595
+ if self.conn.is_some() {
596
+ return Ok(());
597
+ }
598
+ /* a reconnect future was dropped mid-way: pick up where it left off */
599
+ self.state = ConnectionState::Reconnecting;
600
+ self.reconnect(RealtimeError::Closed).await
601
+ }
602
+
603
+ async fn reconnect(&mut self, mut last: RealtimeError) -> Result<(), RealtimeError> {
604
+ let max_attempts = self.opts.max_reconnect_attempts.unwrap_or(5);
605
+ loop {
606
+ self.reconnect_attempts += 1;
607
+ if max_attempts > 0 && self.reconnect_attempts > max_attempts {
608
+ return Err(last);
609
+ }
610
+ tokio::time::sleep(self.backoff(self.reconnect_attempts)).await;
611
+ match self.open_chain().await {
612
+ Ok(()) => return Ok(()),
613
+ Err(e) => last = e,
614
+ }
615
+ }
616
+ }
617
+
618
+ fn backoff(&self, attempt: u32) -> Duration {
619
+ let base = Duration::from_millis(self.opts.reconnect_delay_ms.unwrap_or(1000));
620
+ let mut d = base;
621
+ for _ in 1..attempt {
622
+ if d >= MAX_RECONNECT_DELAY {
623
+ break;
624
+ }
625
+ d *= 2;
626
+ }
627
+ let d = d.min(MAX_RECONNECT_DELAY);
628
+ /* jitter in [d/2, d] without a rand dependency */
629
+ let nanos = std::time::SystemTime::now()
630
+ .duration_since(std::time::UNIX_EPOCH)
631
+ .map(|t| t.subsec_nanos() as u64)
632
+ .unwrap_or(0);
633
+ let half = d.as_millis() as u64 / 2;
634
+ Duration::from_millis(half + if half == 0 { 0 } else { nanos % (half + 1) })
635
+ }
636
+
632
637
  async fn open_chain(&mut self) -> Result<(), RealtimeError> {
633
- /* Try proven first if set */
638
+ let mut order: Vec<usize> = Vec::with_capacity(self.transports.len());
634
639
  if let Some(i) = self.proven_index {
635
- if let Some(t) = self.transports.get(i) {
636
- if let Ok(c) = t.connect(&self.url, &self.transport_opts).await {
637
- self.conn = Some(c);
638
- self.state = ConnectionState::Connected;
639
- return Ok(());
640
- }
641
- }
640
+ order.push(i);
642
641
  }
643
- /* Full chain */
642
+ order.extend((0..self.transports.len()).filter(|i| Some(*i) != self.proven_index));
644
643
  let mut last_err: Option<RealtimeError> = None;
645
- for (i, t) in self.transports.iter().enumerate() {
646
- match t.connect(&self.url, &self.transport_opts).await {
647
- Ok(c) => {
644
+ for i in order {
645
+ let Some(t) = self.transports.get(i) else { continue };
646
+ match tokio::time::timeout(OPEN_TIMEOUT, t.connect(&self.url, &self.transport_opts)).await {
647
+ Ok(Ok(c)) => {
648
648
  self.conn = Some(c);
649
649
  self.proven_index = Some(i);
650
650
  self.state = ConnectionState::Connected;
651
651
  return Ok(());
652
652
  }
653
- Err(e) => last_err = Some(e),
653
+ Ok(Err(e)) => last_err = Some(e),
654
+ Err(_) => last_err = Some(connect_err("transport open timed out")),
654
655
  }
655
656
  }
656
- Err(last_err.unwrap_or(RealtimeError::Connect(Error::Other(
657
- "all transports failed".into(),
658
- ))))
657
+ Err(last_err.unwrap_or(RealtimeError::Connect(Error::Other("all transports failed".into()))))
659
658
  }
660
-
661
659
  }
662
660
 
661
+ /// build_sse_state opens the stream; a non-2xx status or a non-event-stream
662
+ /// content type is a connect error, so the chain moves on.
663
663
  async fn build_sse_state(
664
664
  url: String,
665
665
  http_client: reqwest::Client,
666
666
  opts: &TransportOpts,
667
- ) -> Result<SseState, reqwest::Error> {
668
- let mut req_builder = http_client
669
- .get(url.as_str())
670
- .header("Accept", "text/event-stream");
667
+ ) -> Result<SseState, RealtimeError> {
668
+ let mut req_builder = http_client.get(url.as_str()).header("Accept", "text/event-stream");
671
669
  if let Some(ref last_event_id) = opts.last_event_id {
672
670
  req_builder = req_builder.header("Last-Event-ID", last_event_id.as_str());
673
671
  }
674
672
  for (k, v) in &opts.headers {
675
673
  req_builder = req_builder.header(k.as_str(), v.as_str());
676
674
  }
677
- if !opts
678
- .headers
679
- .keys()
680
- .any(|k| k.eq_ignore_ascii_case("x-request-id"))
681
- {
675
+ if !opts.headers.keys().any(|k| k.eq_ignore_ascii_case("x-request-id")) {
682
676
  req_builder = req_builder.header("x-request-id", uuid::Uuid::new_v4().to_string());
683
677
  }
684
- let resp = req_builder.send().await?;
685
- let reconnect_delay_ms = opts.reconnect_delay_ms.unwrap_or(1000);
686
- let max_reconnect_attempts = opts.max_reconnect_attempts.unwrap_or(5);
687
- let last_event_id = opts.last_event_id.clone();
688
- let extra_headers = opts.headers.clone();
678
+ let resp = req_builder.send().await.map_err(connect_err)?;
679
+ if !resp.status().is_success() {
680
+ return Err(connect_err(format!("sse status {}", resp.status())));
681
+ }
682
+ let is_event_stream = resp
683
+ .headers()
684
+ .get(reqwest::header::CONTENT_TYPE)
685
+ .and_then(|v| v.to_str().ok())
686
+ .map(|v| v.to_ascii_lowercase().starts_with("text/event-stream"))
687
+ .unwrap_or(false);
688
+ if !is_event_stream {
689
+ return Err(connect_err("sse response is not text/event-stream"));
690
+ }
689
691
  let byte_stream: ByteStream = Box::pin(resp.bytes_stream());
690
692
  Ok(SseState {
691
693
  byte_stream,
692
- buf: Vec::new(),
693
- last_event_id,
694
- retry_hint_ms: reconnect_delay_ms,
694
+ last_event_id: opts.last_event_id.clone(),
695
+ retry_hint_ms: opts.reconnect_delay_ms.unwrap_or(1000),
696
+ parser: SseParser::new(),
697
+ pending: VecDeque::new(),
695
698
  http_client,
696
699
  url,
697
- extra_headers,
698
- max_reconnect_attempts,
699
- reconnect_delay_ms,
700
- reconnect_attempts: 0,
700
+ extra_headers: opts.headers.clone(),
701
701
  })
702
702
  }