@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
@@ -0,0 +1,494 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import re
5
+ import threading
6
+ import time
7
+ from collections import OrderedDict
8
+ from dataclasses import dataclass, field
9
+ from typing import Any
10
+ from urllib.parse import quote
11
+
12
+
13
+ def _now() -> float:
14
+ return time.monotonic()
15
+
16
+
17
+ # `{name}` (any name without braces or slashes) or a `:name` segment; a colon
18
+ # inside a segment (`/ops/{id}:cancel`) is literal.
19
+ _PATH_PARAM_RE = re.compile(r"\{([^{}/]+)\}|(?:(?<=/)|^):([a-zA-Z_][a-zA-Z0-9_]*)")
20
+
21
+
22
+ @dataclass
23
+ class _StaleEntry:
24
+ by: list[str] = field(default_factory=list)
25
+ seq: int = 0
26
+ until: float = 0.0
27
+
28
+
29
+ @dataclass
30
+ class _RequestMeta:
31
+ selector: str
32
+ is_stale: bool
33
+ invalidated_by: list[str]
34
+ seq_snapshot: int
35
+
36
+
37
+ def path_matches_pattern(
38
+ path: str,
39
+ pattern: str,
40
+ _cache: dict[str, re.Pattern[str]] = {},
41
+ ) -> bool:
42
+ """Match *path* against *pattern* containing ``{name}`` / ``:name`` placeholders."""
43
+ if pattern not in _cache:
44
+ escaped_parts: list[str] = []
45
+ last_end = 0
46
+ for match in _PATH_PARAM_RE.finditer(pattern):
47
+ escaped_parts.append(re.escape(pattern[last_end : match.start()]))
48
+ escaped_parts.append("[^/]+")
49
+ last_end = match.end()
50
+ escaped_parts.append(re.escape(pattern[last_end:]))
51
+ _cache[pattern] = re.compile(f"^{''.join(escaped_parts)}$")
52
+ return bool(_cache[pattern].match(path))
53
+
54
+
55
+ def interpolate_path(template: str, params: dict[str, str] | None) -> str:
56
+ """Substitute ``{name}`` / ``:name`` placeholders in *template* using *params*.
57
+
58
+ Raises ValueError if any placeholder has no matching key in *params*.
59
+ """
60
+ if not params:
61
+ if _PATH_PARAM_RE.search(template) is not None:
62
+ raise ValueError(f"Missing path params for template {template!r}")
63
+ return template
64
+
65
+ def _sub(match: re.Match[str]) -> str:
66
+ key = match.group(1) or match.group(2)
67
+ if key not in params or params[key] is None:
68
+ raise ValueError(f"Missing path param: {key}")
69
+ return quote(str(params[key]), safe="")
70
+
71
+ return _PATH_PARAM_RE.sub(_sub, template)
72
+
73
+
74
+ def _has_unresolved(value: str) -> bool:
75
+ return _PATH_PARAM_RE.search(value) is not None
76
+
77
+
78
+ def resolve_invalidation_targets_for_mutation(
79
+ targets: list[str],
80
+ params: dict[str, str] | None,
81
+ ) -> list[str]:
82
+ """Expand ``METHOD /path/{id}`` template invalidation targets to concrete selectors.
83
+
84
+ Targets without placeholders are passed through. A target whose placeholders cannot
85
+ all be resolved stays a pattern, so it marks every instance stale (TS parity).
86
+ """
87
+ resolved: list[str] = []
88
+ for entry in targets:
89
+ space_idx = entry.find(" ")
90
+ if space_idx == -1:
91
+ resolved.append(entry)
92
+ continue
93
+ method = entry[:space_idx]
94
+ template = entry[space_idx + 1 :]
95
+ if not _has_unresolved(template):
96
+ resolved.append(entry)
97
+ continue
98
+ try:
99
+ concrete = interpolate_path(template, params)
100
+ except ValueError:
101
+ resolved.append(entry)
102
+ continue
103
+ resolved.append(f"{method} {concrete}")
104
+ return resolved
105
+
106
+
107
+ def resolve_invalidation_targets(
108
+ invalidate: list[str],
109
+ stale_map: "OrderedDict[str, _StaleEntry] | OrderedDict[str, Any]",
110
+ ) -> list[str]:
111
+ """Return *stale_map* keys matching any 'METHOD /pattern' invalidation entry.
112
+
113
+ Kept for back-compat with existing call sites that pass an already-built map.
114
+ """
115
+ matched: list[str] = []
116
+ for entry in invalidate:
117
+ for key in list(stale_map.keys()):
118
+ parts = key.split(" ", 1)
119
+ if len(parts) != 2:
120
+ continue
121
+ key_method, key_path = parts
122
+ entry_parts = entry.split(" ", 1)
123
+ if len(entry_parts) != 2:
124
+ continue
125
+ entry_method, entry_pattern = entry_parts
126
+ if key_method != entry_method:
127
+ continue
128
+ if path_matches_pattern(key_path, entry_pattern):
129
+ matched.append(key)
130
+ return matched
131
+
132
+
133
+ def lookup_stale(key: str, stale_map: "OrderedDict[str, Any]") -> bool:
134
+ """Return True if *key* exists in *stale_map* and has not yet expired."""
135
+ entry = stale_map.get(key)
136
+ if entry is None:
137
+ return False
138
+ until = entry.until if isinstance(entry, _StaleEntry) else entry["until"]
139
+ return _now() < until
140
+
141
+
142
+ class _StaleStore:
143
+ """Stale state shared by the async and sync trackers; callers hold their own lock.
144
+
145
+ Concrete keys are looked up directly; keys that still hold ``{name}``
146
+ placeholders are also indexed per method, so a lookup scans only the patterns
147
+ of its method, not every entry. Expired concrete entries go on access, expired
148
+ patterns when scanned, and the rest when the map is over capacity.
149
+ """
150
+
151
+ def __init__(
152
+ self, stale_time: float, stale_max_entries: int, max_sources_per_target: int
153
+ ) -> None:
154
+ self.enabled = stale_time > 0
155
+ self.stale_time = max(stale_time, 0.0)
156
+ self.max_entries = max(stale_max_entries, 1)
157
+ self.max_sources = max(max_sources_per_target, 1)
158
+ self.map: OrderedDict[str, _StaleEntry] = OrderedDict()
159
+ self.patterns: dict[str, dict[str, None]] = {}
160
+ self.seq = 0
161
+
162
+ def delete(self, key: str) -> None:
163
+ if self.map.pop(key, None) is None:
164
+ return
165
+ method, path = _split_key(key)
166
+ bucket = self.patterns.get(method)
167
+ if bucket is not None and _has_unresolved(path):
168
+ bucket.pop(key, None)
169
+ if not bucket:
170
+ del self.patterns[method]
171
+
172
+ def upsert(
173
+ self, key: str, until: float, seq: int, mutation_selector: str | None
174
+ ) -> None:
175
+ existing = self.map.get(key)
176
+ if existing is not None:
177
+ existing.until = until
178
+ existing.seq = seq
179
+ if mutation_selector and mutation_selector not in existing.by:
180
+ if len(existing.by) < self.max_sources:
181
+ existing.by.append(mutation_selector)
182
+ self.map.move_to_end(key)
183
+ return
184
+ by = [mutation_selector] if mutation_selector else []
185
+ self.map[key] = _StaleEntry(by=by, seq=seq, until=until)
186
+ method, path = _split_key(key)
187
+ if _has_unresolved(path):
188
+ self.patterns.setdefault(method, {})[key] = None
189
+
190
+ def invalidate(self, patterns: list[str]) -> None:
191
+ self.evict_expired()
192
+ targets = resolve_invalidation_targets(patterns, self.map)
193
+ self.seq += 1
194
+ until = _now() + self.stale_time
195
+ for t in targets:
196
+ self.upsert(t, until, self.seq, None)
197
+ self.enforce_capacity()
198
+
199
+ def mark_stale(
200
+ self,
201
+ invalidate: list[str],
202
+ params: dict[str, str] | None,
203
+ mutation_selector: str,
204
+ ) -> None:
205
+ self.seq += 1
206
+ until = _now() + self.stale_time
207
+ for target in resolve_invalidation_targets_for_mutation(invalidate, params):
208
+ self.upsert(target, until, self.seq, mutation_selector)
209
+ self.enforce_capacity()
210
+
211
+ def mark(self, key: str) -> None:
212
+ self.seq += 1
213
+ self.upsert(key, _now() + self.stale_time, self.seq, None)
214
+ self.enforce_capacity()
215
+
216
+ def lookup(
217
+ self, concrete_selector: str, concrete_path: str, method: str, now: float
218
+ ) -> tuple[list[str], bool]:
219
+ all_by: list[str] = []
220
+ exact = self.map.get(concrete_selector)
221
+ if exact is not None:
222
+ if exact.until > now:
223
+ all_by.extend(exact.by)
224
+ else:
225
+ self.delete(concrete_selector)
226
+ expired: list[str] = []
227
+ for key in self.patterns.get(method, {}):
228
+ entry = self.map[key]
229
+ if entry.until <= now:
230
+ expired.append(key)
231
+ continue
232
+ if key != concrete_selector and path_matches_pattern(
233
+ concrete_path, _split_key(key)[1]
234
+ ):
235
+ all_by.extend(entry.by)
236
+ for key in expired:
237
+ self.delete(key)
238
+ deduped = list(dict.fromkeys(all_by))
239
+ return deduped, len(deduped) > 0
240
+
241
+ def clear(
242
+ self, concrete_selector: str, concrete_path: str, method: str, seq_snapshot: int
243
+ ) -> None:
244
+ exact = self.map.get(concrete_selector)
245
+ if exact and exact.seq <= seq_snapshot:
246
+ self.delete(concrete_selector)
247
+ to_delete = [
248
+ key
249
+ for key in self.patterns.get(method, {})
250
+ if self.map[key].seq <= seq_snapshot
251
+ and path_matches_pattern(concrete_path, _split_key(key)[1])
252
+ ]
253
+ for key in to_delete:
254
+ self.delete(key)
255
+
256
+ def request_meta(
257
+ self, concrete_selector: str, concrete_path: str, method: str
258
+ ) -> _RequestMeta:
259
+ by, is_stale = self.lookup(concrete_selector, concrete_path, method, _now())
260
+ return _RequestMeta(
261
+ selector=concrete_selector,
262
+ is_stale=is_stale,
263
+ invalidated_by=by,
264
+ seq_snapshot=self.seq,
265
+ )
266
+
267
+ def evict_expired(self) -> None:
268
+ now = _now()
269
+ for key in [k for k, v in self.map.items() if now >= v.until]:
270
+ self.delete(key)
271
+
272
+ def enforce_capacity(self) -> None:
273
+ if len(self.map) <= self.max_entries:
274
+ return
275
+ self.evict_expired()
276
+ target = max(self.max_entries // 2, 1)
277
+ while len(self.map) > target:
278
+ self.delete(next(iter(self.map)))
279
+
280
+
281
+ def _split_key(key: str) -> tuple[str, str]:
282
+ space_idx = key.find(" ")
283
+ if space_idx == -1:
284
+ return key, ""
285
+ return key[:space_idx], key[space_idx + 1 :]
286
+
287
+
288
+ def _split_method_path(method_or_key: str, path: str | None) -> tuple[str, str]:
289
+ if path is None:
290
+ return _split_key(method_or_key)
291
+ return method_or_key, path
292
+
293
+
294
+ class _StaleTracker:
295
+ """Async-aware stale tracker backed by ``asyncio.Lock``."""
296
+
297
+ def __init__(
298
+ self,
299
+ stale_time: float,
300
+ stale_max_entries: int = 1000,
301
+ max_sources_per_target: int = 16,
302
+ ) -> None:
303
+ """stale_time == 0 disables the tracker (no stale window applied)."""
304
+ self._store = _StaleStore(stale_time, stale_max_entries, max_sources_per_target)
305
+ self._lock = asyncio.Lock()
306
+
307
+ @property
308
+ def enabled(self) -> bool:
309
+ return self._store.enabled
310
+
311
+ @property
312
+ def _map(self) -> "OrderedDict[str, _StaleEntry]":
313
+ return self._store.map
314
+
315
+ async def invalidate(self, patterns: list[str]) -> None:
316
+ """Mark every existing key matching *patterns* as freshly stale."""
317
+ if not self.enabled:
318
+ return
319
+ async with self._lock:
320
+ self._store.invalidate(patterns)
321
+
322
+ async def mark_stale(
323
+ self,
324
+ invalidate: list[str],
325
+ params: dict[str, str] | None,
326
+ mutation_selector: str,
327
+ ) -> None:
328
+ """Bump seq, expand templated invalidation entries, store stale keys."""
329
+ if not self.enabled or not invalidate:
330
+ return
331
+ async with self._lock:
332
+ self._store.mark_stale(invalidate, params, mutation_selector)
333
+
334
+ async def mark(self, key: str) -> None:
335
+ """Force *key* into the stale map. Used by SSE/WS open hooks and tests."""
336
+ if not self.enabled:
337
+ return
338
+ async with self._lock:
339
+ self._store.mark(key)
340
+
341
+ async def is_stale(self, method_or_key: str, path: str | None = None) -> bool:
342
+ """Return True if (method, path) is currently stale.
343
+
344
+ Accepts either ``is_stale("GET /users")`` or ``is_stale("GET", "/users")``.
345
+ Walks pattern entries too.
346
+ """
347
+ if not self.enabled:
348
+ return False
349
+ method, concrete_path = _split_method_path(method_or_key, path)
350
+ async with self._lock:
351
+ return self._store.lookup(
352
+ f"{method} {concrete_path}", concrete_path, method, _now()
353
+ )[1]
354
+
355
+ async def lookup_stale(
356
+ self,
357
+ concrete_selector: str,
358
+ concrete_path: str,
359
+ method: str,
360
+ ) -> tuple[list[str], bool]:
361
+ """Return (invalidated_by_list, is_stale): exact + pattern lookup with sweep."""
362
+ if not self.enabled:
363
+ return [], False
364
+ async with self._lock:
365
+ return self._store.lookup(concrete_selector, concrete_path, method, _now())
366
+
367
+ async def clear_stale(
368
+ self,
369
+ concrete_selector: str,
370
+ concrete_path: str,
371
+ method: str,
372
+ seq_snapshot: int,
373
+ ) -> None:
374
+ """Drop seq-older entries matching *concrete_path* (exact + pattern keys)."""
375
+ if not self.enabled:
376
+ return
377
+ async with self._lock:
378
+ self._store.clear(concrete_selector, concrete_path, method, seq_snapshot)
379
+
380
+ async def build_request_meta(
381
+ self,
382
+ concrete_selector: str,
383
+ concrete_path: str,
384
+ method: str,
385
+ ) -> _RequestMeta | None:
386
+ if not self.enabled:
387
+ return None
388
+ async with self._lock:
389
+ return self._store.request_meta(concrete_selector, concrete_path, method)
390
+
391
+ @staticmethod
392
+ def _split_key(key: str) -> tuple[str, str]:
393
+ return _split_key(key)
394
+
395
+
396
+ class _StaleTrackerSync:
397
+ """Sync variant backed by ``threading.Lock``; mirrors :class:`_StaleTracker`."""
398
+
399
+ def __init__(
400
+ self,
401
+ stale_time: float,
402
+ stale_max_entries: int = 1000,
403
+ max_sources_per_target: int = 16,
404
+ ) -> None:
405
+ """stale_time == 0 disables the tracker (no stale window applied)."""
406
+ self._store = _StaleStore(stale_time, stale_max_entries, max_sources_per_target)
407
+ self._lock = threading.Lock()
408
+
409
+ @property
410
+ def enabled(self) -> bool:
411
+ return self._store.enabled
412
+
413
+ @property
414
+ def _map(self) -> "OrderedDict[str, _StaleEntry]":
415
+ return self._store.map
416
+
417
+ def invalidate(self, patterns: list[str]) -> None:
418
+ if not self.enabled:
419
+ return
420
+ with self._lock:
421
+ self._store.invalidate(patterns)
422
+
423
+ def mark_stale(
424
+ self,
425
+ invalidate: list[str],
426
+ params: dict[str, str] | None,
427
+ mutation_selector: str,
428
+ ) -> None:
429
+ if not self.enabled or not invalidate:
430
+ return
431
+ with self._lock:
432
+ self._store.mark_stale(invalidate, params, mutation_selector)
433
+
434
+ def mark(self, key: str) -> None:
435
+ if not self.enabled:
436
+ return
437
+ with self._lock:
438
+ self._store.mark(key)
439
+
440
+ def is_stale(self, method_or_key: str, path: str | None = None) -> bool:
441
+ if not self.enabled:
442
+ return False
443
+ method, concrete_path = _split_method_path(method_or_key, path)
444
+ with self._lock:
445
+ return self._store.lookup(
446
+ f"{method} {concrete_path}", concrete_path, method, _now()
447
+ )[1]
448
+
449
+ def lookup_stale(
450
+ self,
451
+ concrete_selector: str,
452
+ concrete_path: str,
453
+ method: str,
454
+ ) -> tuple[list[str], bool]:
455
+ if not self.enabled:
456
+ return [], False
457
+ with self._lock:
458
+ return self._store.lookup(concrete_selector, concrete_path, method, _now())
459
+
460
+ def clear_stale(
461
+ self,
462
+ concrete_selector: str,
463
+ concrete_path: str,
464
+ method: str,
465
+ seq_snapshot: int,
466
+ ) -> None:
467
+ if not self.enabled:
468
+ return
469
+ with self._lock:
470
+ self._store.clear(concrete_selector, concrete_path, method, seq_snapshot)
471
+
472
+ def build_request_meta(
473
+ self,
474
+ concrete_selector: str,
475
+ concrete_path: str,
476
+ method: str,
477
+ ) -> _RequestMeta | None:
478
+ if not self.enabled:
479
+ return None
480
+ with self._lock:
481
+ return self._store.request_meta(concrete_selector, concrete_path, method)
482
+
483
+
484
+ __all__ = [
485
+ "_RequestMeta",
486
+ "_StaleEntry",
487
+ "_StaleTracker",
488
+ "_StaleTrackerSync",
489
+ "interpolate_path",
490
+ "lookup_stale",
491
+ "path_matches_pattern",
492
+ "resolve_invalidation_targets",
493
+ "resolve_invalidation_targets_for_mutation",
494
+ ]
@@ -0,0 +1,179 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import random
5
+ from enum import Enum
6
+ from typing import Any, AsyncIterator
7
+
8
+ from ._transport import (
9
+ TransportAdapter,
10
+ TransportConn,
11
+ TransportKind,
12
+ TransportOpts,
13
+ )
14
+
15
+ MAX_RECONNECT_DELAY = 30.0
16
+
17
+
18
+ class ConnectionState(str, Enum):
19
+ CONNECTING = "connecting"
20
+ CONNECTED = "connected"
21
+ RECONNECTING = "reconnecting"
22
+ DISCONNECTED = "disconnected"
23
+
24
+
25
+ class ResumableConnection:
26
+ """Orchestrates a pluggable TransportAdapter chain.
27
+
28
+ Mirrors TS `createResumableConnection` + Rust `ResumableConnection::connect`:
29
+ - Ordered `transports` chain tried in order on first connect.
30
+ - First successful adapter index memoized in `_proven_index`.
31
+ - On reconnect, proven adapter tried first; full chain on failure.
32
+ - State transitions drive `.state` property.
33
+
34
+ A dropped connection (including a clean end of an SSE stream) reconnects
35
+ with exponential backoff and jitter. The attempt counter resets after every
36
+ delivered message, so `max_reconnect_attempts` limits consecutive failures,
37
+ not lifetime drops. When the attempts run out the iterator raises the last
38
+ error instead of ending silently. Cancellation is never swallowed.
39
+ """
40
+
41
+ def __init__(
42
+ self,
43
+ base_url: str,
44
+ transports: list[TransportAdapter],
45
+ opts: TransportOpts | None = None,
46
+ ) -> None:
47
+ if not transports:
48
+ raise ValueError(
49
+ "ResumableConnection requires at least one transport adapter",
50
+ )
51
+ self._url = base_url
52
+ self._transports = transports
53
+ self._opts = opts or TransportOpts()
54
+ self._state: ConnectionState = ConnectionState.DISCONNECTED
55
+ self._conn: TransportConn | None = None
56
+ self._proven_index: int | None = None
57
+ self._reconnect_attempts: int = 0
58
+ self._closed: bool = False
59
+
60
+ @property
61
+ def state(self) -> ConnectionState:
62
+ return self._state
63
+
64
+ @property
65
+ def proven_transport(self) -> TransportKind | None:
66
+ if self._proven_index is None:
67
+ return None
68
+ if self._proven_index >= len(self._transports):
69
+ return None
70
+ return self._transports[self._proven_index].kind()
71
+
72
+ async def connect(self) -> None:
73
+ if self._closed:
74
+ raise RuntimeError("ResumableConnection is closed")
75
+ if self._state == ConnectionState.CONNECTED:
76
+ return
77
+ has_prior = self._conn is not None or self._proven_index is not None
78
+ self._state = (
79
+ ConnectionState.RECONNECTING if has_prior else ConnectionState.CONNECTING
80
+ )
81
+ await self._open_chain()
82
+
83
+ async def _open_chain(self) -> None:
84
+ last_err: Exception | None = None
85
+ order: list[int] = []
86
+ if self._proven_index is not None and self._proven_index < len(
87
+ self._transports
88
+ ):
89
+ order.append(self._proven_index)
90
+ order.extend(i for i in range(len(self._transports)) if i not in order)
91
+ for idx in order:
92
+ adapter = self._transports[idx]
93
+ try:
94
+ conn = await adapter.connect(self._url, self._opts)
95
+ except Exception as exc: # CancelledError is a BaseException: it propagates
96
+ last_err = exc
97
+ continue
98
+ if self._closed:
99
+ await _close_quietly(conn)
100
+ raise RuntimeError("ResumableConnection is closed")
101
+ self._conn = conn
102
+ self._proven_index = idx
103
+ self._state = ConnectionState.CONNECTED
104
+ return
105
+ self._state = ConnectionState.DISCONNECTED
106
+ if last_err is not None:
107
+ raise last_err
108
+ raise RuntimeError("no transports configured")
109
+
110
+ async def send(self, data: Any) -> None:
111
+ if self._conn is None:
112
+ raise RuntimeError("not connected")
113
+ await self._conn.send(data)
114
+
115
+ async def close(self) -> None:
116
+ self._closed = True
117
+ self._state = ConnectionState.DISCONNECTED
118
+ if self._conn is not None:
119
+ conn = self._conn
120
+ self._conn = None
121
+ await _close_quietly(conn)
122
+
123
+ def _delay(self, attempt: int) -> float:
124
+ base = max(self._opts.reconnect_delay_ms, 0) / 1000.0
125
+ delay = min(base * (2 ** (attempt - 1)), MAX_RECONNECT_DELAY)
126
+ return delay / 2 + random.uniform(0, delay / 2)
127
+
128
+ def __aiter__(self) -> AsyncIterator[Any]:
129
+ return self._iter()
130
+
131
+ async def _iter(self) -> AsyncIterator[Any]:
132
+ if self._conn is None and not self._closed:
133
+ await self.connect()
134
+ while not self._closed:
135
+ conn = self._conn
136
+ if conn is None:
137
+ return
138
+ try:
139
+ value = await conn.recv()
140
+ except asyncio.CancelledError:
141
+ raise
142
+ except Exception as exc:
143
+ # transport died or the stream ended: drop it and reconnect with backoff
144
+ last_err: Exception = exc
145
+ self._conn = None
146
+ self._proven_index = None
147
+ self._state = ConnectionState.RECONNECTING
148
+ await _close_quietly(conn)
149
+ while True:
150
+ if self._closed:
151
+ return
152
+ self._reconnect_attempts += 1
153
+ max_attempts = self._opts.max_reconnect_attempts
154
+ if max_attempts > 0 and self._reconnect_attempts > max_attempts:
155
+ self._state = ConnectionState.DISCONNECTED
156
+ if isinstance(last_err, StopAsyncIteration):
157
+ return
158
+ raise last_err
159
+ await asyncio.sleep(self._delay(self._reconnect_attempts))
160
+ try:
161
+ await self._open_chain()
162
+ break
163
+ except asyncio.CancelledError:
164
+ raise
165
+ except Exception as reopen_exc:
166
+ last_err = reopen_exc
167
+ continue
168
+ self._reconnect_attempts = 0
169
+ yield value
170
+
171
+
172
+ async def _close_quietly(conn: TransportConn) -> None:
173
+ try:
174
+ await conn.close()
175
+ except Exception:
176
+ pass
177
+
178
+
179
+ __all__ = ["ConnectionState", "ResumableConnection"]