@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,4610 @@
1
+ import { InternPool } from "./codegen-route-tree-intern.js";
2
+ import { deriveErrorEnvelopeName, deriveSchemaName, isErrorEnvelope, sanitizeComponentName, shortHash, } from "./codegen-schema-naming.js";
3
+ import { effectJsonSchemaFn, loadEffectJsonSchema, loadToJSONSchema, toJSONSchemaFn } from "./codegen-loaders.js";
4
+ import { sanitizeZodJsonSchema } from "./codegen-sanitize.js";
5
+ import { bodiesOf, irErrorEnvelope, irResolver, methodsOf, namespacesOf, openApiPathParams, schemaToIR, toIR, } from "./codegen-ir.js";
6
+ import { isJsonMedia, isTextMedia, mediaEssence } from "./codegen-sdk-model.js";
7
+ import { ERROR_META } from "./errors.js";
8
+ import { publishableMetaKeys } from "./meta-spec.js";
9
+ import { metaSpecOf } from "./meta-spec-merge.js";
10
+ import { generateOpenApiFromTree, } from "./openapi/document.js";
11
+ import { getJsonSchemaConverter, setJsonSchemaConverter } from "./openapi/json-schema-slot.js";
12
+ import { collectRoutes, extractParams } from "./openapi/collect.js";
13
+ import { parsePattern, patternParams } from "./pattern.js";
14
+ import { forEachLeaf, ROUTE_TREE_VERSION } from "./tree.js";
15
+ import { irToTs } from "./ts-type-emitter.js";
16
+ import { createTypeEmitState, emitSchemaType, quoteKey } from "./type-emitter.js";
17
+ import { EMPTY_OBJ, statusKeyToCode } from "./types.js";
18
+ import { CROSS_ORIGIN_SAFE_HEADERS } from "./client/redirect-policy.js";
19
+ export { prepareCodegen } from "./codegen-loaders.js";
20
+ export { toYaml, yamlSiblingPath } from "./yaml.js";
21
+ export { DEFAULT_ERROR_JSON_SCHEMA } from "./openapi/document.js";
22
+ /* Status → typed error subclass. Single source of truth shared between the
23
+ * emitted client (class declarations + status map + footer re-exports) and
24
+ * the index.gen.ts import/export. Order is deterministic for stable output. */
25
+ const STATUS_ERROR_CLASSES = [
26
+ { name: "BadRequestError", status: 400 },
27
+ { name: "UnauthorizedError", status: 401 },
28
+ { name: "ForbiddenError", status: 403 },
29
+ { name: "NotFoundError", status: 404 },
30
+ { name: "ConflictError", status: 409 },
31
+ { name: "UnprocessableEntityError", status: 422 },
32
+ { name: "RateLimitError", status: 429 },
33
+ { name: "InternalServerError", status: 500 },
34
+ { name: "BadGatewayError", status: 502 },
35
+ { name: "ServiceUnavailableError", status: 503 },
36
+ { name: "GatewayTimeoutError", status: 504 },
37
+ ];
38
+ const ERROR_EXPORT_NAMES = [
39
+ "ClientError",
40
+ ...STATUS_ERROR_CLASSES.map((c) => c.name),
41
+ "isClientError",
42
+ ];
43
+ /** Wrappers that make an object entry optional. */
44
+ const VALIBOT_OPTIONAL = new Set(["exact_optional", "nullish", "optional", "undefinedable"]);
45
+ /** Pipe actions with a JSON Schema equivalent. Anything else constrains nothing in the document. */
46
+ function applyValibotPipe(schema, json) {
47
+ const pipe = schema.pipe;
48
+ if (!Array.isArray(pipe))
49
+ return json;
50
+ const formats = {
51
+ email: "email",
52
+ ipv4: "ipv4",
53
+ ipv6: "ipv6",
54
+ iso_date: "date",
55
+ iso_date_time: "date-time",
56
+ iso_timestamp: "date-time",
57
+ url: "uri",
58
+ uuid: "uuid",
59
+ };
60
+ for (const action of pipe.slice(1)) {
61
+ const type = action.type;
62
+ const req = action.requirement;
63
+ if (type in formats)
64
+ json.format = formats[type];
65
+ else if (type === "integer")
66
+ json.type = "integer";
67
+ else if (type === "regex" && req instanceof RegExp)
68
+ json.pattern = req.source;
69
+ else if (type === "min_length" && typeof req === "number")
70
+ json[json.type === "array" ? "minItems" : "minLength"] = req;
71
+ else if (type === "max_length" && typeof req === "number")
72
+ json[json.type === "array" ? "maxItems" : "maxLength"] = req;
73
+ else if (type === "length" && typeof req === "number") {
74
+ json[json.type === "array" ? "minItems" : "minLength"] = req;
75
+ json[json.type === "array" ? "maxItems" : "maxLength"] = req;
76
+ }
77
+ else if (type === "min_value" && typeof req === "number")
78
+ json.minimum = req;
79
+ else if (type === "max_value" && typeof req === "number")
80
+ json.maximum = req;
81
+ else if (type === "description" && typeof action.description === "string")
82
+ json.description = action.description;
83
+ }
84
+ return json;
85
+ }
86
+ function valibotObject(s, depth) {
87
+ const entries = s.entries;
88
+ if (Object.keys(entries).length === 0 && s.type === "object")
89
+ return { type: "object" };
90
+ const properties = {};
91
+ const required = [];
92
+ for (const k of Object.keys(entries)) {
93
+ const entry = entries[k];
94
+ properties[k] = valibotToJsonSchema(entry, depth + 1);
95
+ if (!VALIBOT_OPTIONAL.has(entry.type))
96
+ required.push(k);
97
+ }
98
+ const result = { properties, type: "object" };
99
+ if (required.length > 0)
100
+ result.required = required;
101
+ if (s.type === "strict_object")
102
+ result.additionalProperties = false;
103
+ else if (s.type === "object_with_rest")
104
+ result.additionalProperties = valibotToJsonSchema(s.rest, depth + 1);
105
+ return result;
106
+ }
107
+ function valibotToJsonSchema(schema, depth = 0) {
108
+ /* a lazy schema can recurse without end; past this depth the node is unconstrained */
109
+ if (depth > 32)
110
+ return {};
111
+ const s = schema;
112
+ const walk = (child) => valibotToJsonSchema(child, depth + 1);
113
+ switch (s.type) {
114
+ case "string":
115
+ return applyValibotPipe(s, { type: "string" });
116
+ case "number":
117
+ return applyValibotPipe(s, { type: "number" });
118
+ case "boolean":
119
+ return { type: "boolean" };
120
+ case "bigint":
121
+ return { format: "int64", type: "integer" };
122
+ case "date":
123
+ return { format: "date-time", type: "string" };
124
+ case "file":
125
+ case "blob":
126
+ return { contentEncoding: "binary", format: "binary", type: "string" };
127
+ case "undefined":
128
+ case "void":
129
+ return {};
130
+ case "null":
131
+ return { type: "null" };
132
+ case "any":
133
+ case "unknown":
134
+ return {};
135
+ case "literal":
136
+ return { const: s.literal };
137
+ case "object":
138
+ case "strict_object":
139
+ case "loose_object":
140
+ case "object_with_rest":
141
+ return valibotObject(s, depth);
142
+ case "array":
143
+ return applyValibotPipe(s, { items: walk(s.item), type: "array" });
144
+ case "optional":
145
+ case "exact_optional":
146
+ case "undefinedable":
147
+ case "non_nullable":
148
+ case "non_nullish":
149
+ case "non_optional": {
150
+ const inner = walk(s.wrapped);
151
+ const fallback = s.default;
152
+ return fallback === undefined || typeof fallback === "function" ? inner : { ...inner, default: fallback };
153
+ }
154
+ case "nullable":
155
+ case "nullish":
156
+ return { anyOf: [walk(s.wrapped), { type: "null" }] };
157
+ case "union":
158
+ case "variant":
159
+ return { anyOf: s.options.map(walk) };
160
+ case "intersect":
161
+ return { allOf: s.options.map(walk) };
162
+ case "picklist":
163
+ return { enum: s.options };
164
+ case "enum": {
165
+ const enumObj = s.enum;
166
+ /* a TS numeric enum carries reverse mappings — keep the values */
167
+ return {
168
+ enum: Object.keys(enumObj)
169
+ .filter((k) => typeof enumObj[enumObj[k]] !== "number")
170
+ .map((k) => enumObj[k]),
171
+ };
172
+ }
173
+ case "record":
174
+ return { additionalProperties: walk(s.value), type: "object" };
175
+ case "tuple":
176
+ case "strict_tuple":
177
+ case "loose_tuple":
178
+ case "tuple_with_rest": {
179
+ const items = s.items.map(walk);
180
+ const result = { minItems: items.length, prefixItems: items, type: "array" };
181
+ if (s.type === "tuple_with_rest")
182
+ result.items = walk(s.rest);
183
+ else if (s.type !== "loose_tuple") {
184
+ result.items = false;
185
+ result.maxItems = items.length;
186
+ }
187
+ return result;
188
+ }
189
+ case "lazy": {
190
+ const getter = s.getter;
191
+ return typeof getter === "function" ? walk(getter(undefined)) : {};
192
+ }
193
+ default:
194
+ return {};
195
+ }
196
+ }
197
+ /* ---- ArkType → JSON Schema converter ---- */
198
+ function arkTypeToJsonSchema(schema) {
199
+ const s = schema;
200
+ if (typeof s.toJsonSchema === "function") {
201
+ try {
202
+ return s.toJsonSchema();
203
+ }
204
+ catch {
205
+ /* fall through */
206
+ }
207
+ }
208
+ return {};
209
+ }
210
+ function yupToJsonSchema(schema) {
211
+ const s = schema;
212
+ if (typeof s.describe !== "function")
213
+ return {};
214
+ const desc = s.describe();
215
+ return yupDescToJsonSchema(desc);
216
+ }
217
+ function yupDescToJsonSchema(desc) {
218
+ const base = yupDescBase(desc);
219
+ if (desc.nullable) {
220
+ return { anyOf: [base, { type: "null" }] };
221
+ }
222
+ return base;
223
+ }
224
+ function yupDescBase(desc) {
225
+ switch (desc.type) {
226
+ case "string":
227
+ return { type: "string" };
228
+ case "number":
229
+ return { type: "number" };
230
+ case "boolean":
231
+ return { type: "boolean" };
232
+ case "date":
233
+ return { format: "date-time", type: "string" };
234
+ case "object": {
235
+ if (!desc.fields)
236
+ return { type: "object" };
237
+ const keys = Object.keys(desc.fields);
238
+ if (keys.length === 0)
239
+ return { type: "object" };
240
+ const properties = {};
241
+ const required = [];
242
+ for (const k of keys) {
243
+ const field = desc.fields[k];
244
+ properties[k] = yupDescToJsonSchema(field);
245
+ if (!field.optional)
246
+ required.push(k);
247
+ }
248
+ const result = { properties, type: "object" };
249
+ if (required.length > 0)
250
+ result.required = required;
251
+ return result;
252
+ }
253
+ case "array": {
254
+ if (!desc.innerType || Array.isArray(desc.innerType))
255
+ return { type: "array" };
256
+ return { items: yupDescToJsonSchema(desc.innerType), type: "array" };
257
+ }
258
+ case "tuple": {
259
+ if (!Array.isArray(desc.innerType))
260
+ return { type: "array" };
261
+ const items = desc.innerType.map((i) => yupDescToJsonSchema(i));
262
+ return {
263
+ items: false,
264
+ maxItems: items.length,
265
+ minItems: items.length,
266
+ prefixItems: items,
267
+ type: "array",
268
+ };
269
+ }
270
+ case "mixed": {
271
+ if (desc.oneOf && desc.oneOf.length > 0)
272
+ return { enum: desc.oneOf };
273
+ return {};
274
+ }
275
+ default:
276
+ return {};
277
+ }
278
+ }
279
+ /* ---- Effect → JSON Schema converter ---- */
280
+ function effectToJsonSchema(schema) {
281
+ if (effectJsonSchemaFn) {
282
+ try {
283
+ return effectJsonSchemaFn(schema);
284
+ }
285
+ catch {
286
+ /* fall through */
287
+ }
288
+ }
289
+ return {};
290
+ }
291
+ function isMetaInternal(handler) {
292
+ return handler.mt?.internal === true;
293
+ }
294
+ /* Longest suffix first so `*_not_found` wins over `*_found` (302). */
295
+ const STATUS_KEYS_BY_LENGTH = Object.keys(statusKeyToCode).sort((a, b) => b.length - a.length);
296
+ /*
297
+ * Composed apps keep declared keys on the handler but may not carry the worker
298
+ * factory. Infer HTTP status from a trailing status-key suffix so the document
299
+ * still lists what `c.errors.<key>` actually writes.
300
+ */
301
+ function inferErrorInfo(errorKey) {
302
+ const exact = statusKeyToCode[errorKey];
303
+ if (exact !== undefined) {
304
+ if (exact < 400)
305
+ return null;
306
+ return { errorKey, status: exact, statusKey: errorKey };
307
+ }
308
+ for (const statusKey of STATUS_KEYS_BY_LENGTH) {
309
+ if (!errorKey.endsWith(`_${statusKey}`))
310
+ continue;
311
+ const status = statusKeyToCode[statusKey];
312
+ if (status < 400)
313
+ continue;
314
+ return { errorKey, status, statusKey };
315
+ }
316
+ return null;
317
+ }
318
+ function resolveErrorInfo(errorKey, factory) {
319
+ if (factory?.[errorKey]) {
320
+ try {
321
+ const err = factory[errorKey]();
322
+ return { errorKey, status: err.status, statusKey: err.statusKey };
323
+ }
324
+ catch {
325
+ /* factory call failed — fall through to suffix inference */
326
+ }
327
+ }
328
+ return inferErrorInfo(errorKey) ?? { errorKey, status: 0, statusKey: "unknown" };
329
+ }
330
+ function getErrorFactory(app) {
331
+ return app._errorFactory;
332
+ }
333
+ function getErrorMeta(factory) {
334
+ if (!factory)
335
+ return null;
336
+ return factory[ERROR_META] ?? null;
337
+ }
338
+ function unwrapEntry(entry) {
339
+ if ("_tag" in entry) {
340
+ return entry.schema;
341
+ }
342
+ return entry;
343
+ }
344
+ function introspectSchema(schema) {
345
+ const std = schema["~standard"];
346
+ return {
347
+ types: std.types,
348
+ vendor: std.vendor,
349
+ version: std.version,
350
+ };
351
+ }
352
+ export { normalizeSecurity } from "./meta-spec.js";
353
+ /** Locale-independent string order (UTF-16 code units), so output is the same on every machine. */
354
+ export function compareCodeUnits(a, b) {
355
+ if (a === b)
356
+ return 0;
357
+ return a < b ? -1 : 1;
358
+ }
359
+ export function canonicalizeSchema(schema) {
360
+ return JSON.stringify(schema, (_, value) => {
361
+ if (value && typeof value === "object" && !Array.isArray(value)) {
362
+ return Object.fromEntries(Object.entries(value).sort(([a], [b]) => compareCodeUnits(a, b)));
363
+ }
364
+ return value;
365
+ });
366
+ }
367
+ function isObjectSchema(schema) {
368
+ if (!schema || typeof schema !== "object" || Array.isArray(schema))
369
+ return false;
370
+ const s = schema;
371
+ if (typeof s.$ref === "string")
372
+ return false;
373
+ return s.type === "object" || (typeof s.properties === "object" && s.properties !== null);
374
+ }
375
+ /**
376
+ * A response key as a number: `"200"` → 200, `"default"` → 0, `"4XX"` → 400. Never `NaN`, which
377
+ * would name a schema `…ResponseNaN` and make every `default` response collide.
378
+ */
379
+ function statusNumber(key) {
380
+ if (/^[1-5][0-9][0-9]$/.test(key))
381
+ return Number(key);
382
+ if (/^[1-5]XX$/i.test(key))
383
+ return Number(key[0]) * 100;
384
+ return 0;
385
+ }
386
+ function collectSchemaSlots(paths) {
387
+ const slots = [];
388
+ for (const [pathKey, methods] of Object.entries(paths)) {
389
+ for (const [method, operation] of Object.entries(methods)) {
390
+ const op = operation;
391
+ const m = method.toLowerCase();
392
+ const requestBody = op.requestBody;
393
+ if (requestBody) {
394
+ const content = requestBody.content;
395
+ if (content) {
396
+ for (const [contentType, mediaType] of Object.entries(content)) {
397
+ const schema = mediaType.schema;
398
+ if (schema && !schema.$ref) {
399
+ const context = { method: m, path: pathKey, role: "request" };
400
+ slots.push({ canonical: canonicalizeSchema(schema), contentType, context, pathKey, schema });
401
+ }
402
+ }
403
+ }
404
+ }
405
+ const responses = op.responses;
406
+ if (responses) {
407
+ for (const [status, response] of Object.entries(responses)) {
408
+ const content = response.content;
409
+ if (content) {
410
+ for (const [contentType, mediaType] of Object.entries(content)) {
411
+ const schema = mediaType.schema;
412
+ if (schema && !schema.$ref) {
413
+ const statusNum = statusNumber(status);
414
+ const context = {
415
+ method: m,
416
+ path: pathKey,
417
+ role: "response",
418
+ status: statusNum,
419
+ };
420
+ slots.push({ canonical: canonicalizeSchema(schema), contentType, context, pathKey, schema });
421
+ }
422
+ }
423
+ }
424
+ }
425
+ }
426
+ }
427
+ }
428
+ return slots;
429
+ }
430
+ /**
431
+ * A component name nobody holds yet. A clash gets the content hash, and a clash on the
432
+ * hashed name (24 bits collide) a counter — a name is never handed out twice.
433
+ */
434
+ function uniqueName(base, canonical, taken) {
435
+ if (!taken(base))
436
+ return base;
437
+ const hashed = `${base}_${shortHash(canonical)}`;
438
+ if (!taken(hashed))
439
+ return hashed;
440
+ for (let n = 2;; n++) {
441
+ const candidate = `${hashed}_${n}`;
442
+ if (!taken(candidate))
443
+ return candidate;
444
+ }
445
+ }
446
+ function walkNestedObjects(schema, slotName, fieldPath, nestedMap, isEnvelopeOwner) {
447
+ const props = schema.properties;
448
+ if (!props)
449
+ return;
450
+ for (const key of Object.keys(props)) {
451
+ const val = props[key];
452
+ if (!isObjectSchema(val))
453
+ continue;
454
+ const child = val;
455
+ const childPath = [...fieldPath, key];
456
+ const canonical = canonicalizeSchema(child);
457
+ const existing = nestedMap.get(canonical);
458
+ if (existing) {
459
+ existing.count++;
460
+ existing.samples.push({ fieldPath: childPath, isEnvelopeOwner, slotName });
461
+ }
462
+ else {
463
+ nestedMap.set(canonical, {
464
+ count: 1,
465
+ samples: [{ fieldPath: childPath, isEnvelopeOwner, slotName }],
466
+ schema: child,
467
+ });
468
+ }
469
+ walkNestedObjects(child, slotName, childPath, nestedMap, isEnvelopeOwner);
470
+ }
471
+ }
472
+ function collectHoistedSchemas(slots, slotNames, taken) {
473
+ const nestedMap = new Map();
474
+ for (const slot of slots) {
475
+ const slotName = slotNames.get(slot);
476
+ /* Mark slots whose schema is an error envelope — their nested objects (e.g. `fields`)
477
+ * are env-specific and must not be named after the parent envelope's owner slot. */
478
+ if (slotName)
479
+ walkNestedObjects(slot.schema, slotName, [], nestedMap, isErrorEnvelope(slot.schema));
480
+ }
481
+ const schemas = {};
482
+ const canonicalToName = new Map();
483
+ for (const [canonical, entry] of nestedMap) {
484
+ if (entry.count < 2)
485
+ continue;
486
+ /* Skip hoisting nested objects that live exclusively inside Tier 2 error envelopes.
487
+ * Their names would be derived from whichever envelope "wins" the lex sort, and that
488
+ * winner differs per-spec — causing merge conflicts when multiple specs are combined. */
489
+ if (entry.samples.every((s) => s.isEnvelopeOwner))
490
+ continue;
491
+ const owner = entry.samples.slice().sort((a, b) => {
492
+ const na = a.slotName + a.fieldPath.join("");
493
+ const nb = b.slotName + b.fieldPath.join("");
494
+ return compareCodeUnits(na, nb);
495
+ })[0];
496
+ const fieldPart = owner.fieldPath
497
+ .map((f) => sanitizeComponentName(f))
498
+ .map((f) => f.charAt(0).toUpperCase() + f.slice(1))
499
+ .join("");
500
+ const name = uniqueName(sanitizeComponentName(`${owner.slotName}${fieldPart}`), canonical, (n) => taken(n) || Object.hasOwn(schemas, n));
501
+ schemas[name] = entry.schema;
502
+ canonicalToName.set(canonical, name);
503
+ }
504
+ /* nested hoists inside a hoisted schema point at their component too */
505
+ for (const name of Object.keys(schemas))
506
+ schemas[name] = rewriteNestedRefs(schemas[name], canonicalToName);
507
+ return { canonicalToName, schemas };
508
+ }
509
+ /*
510
+ * Builds a new paths object by shallow-copying only the containers that need schema
511
+ * replacement. Avoids structuredClone (which fails on arktype schemas containing
512
+ * native functions). Original schema objects are dropped; only $ref objects are new.
513
+ * Also rewrites nested inline object properties that were hoisted (canonicalToName),
514
+ * at any depth — a hoisted name nothing points at would be an orphan component.
515
+ */
516
+ function rewriteNestedRefs(schema, canonicalToName) {
517
+ const props = schema.properties;
518
+ if (!props)
519
+ return schema;
520
+ let newProps;
521
+ for (const key of Object.keys(props)) {
522
+ const val = props[key];
523
+ if (!isObjectSchema(val))
524
+ continue;
525
+ const child = val;
526
+ const name = canonicalToName.get(canonicalizeSchema(child));
527
+ const next = name ? { $ref: `#/components/schemas/${name}` } : rewriteNestedRefs(child, canonicalToName);
528
+ if (next === child)
529
+ continue;
530
+ if (!newProps)
531
+ newProps = { ...props };
532
+ newProps[key] = next;
533
+ }
534
+ if (!newProps)
535
+ return schema;
536
+ return { ...schema, properties: newProps };
537
+ }
538
+ function slotKey(pathKey, method, role, status, contentType) {
539
+ return `${pathKey}::${method}::${role}::${status ?? ""}::${contentType}`;
540
+ }
541
+ function rewritePathsToRefs(paths, slots, slotNames, hoistedCanonicalToName) {
542
+ /* keyed by operation + status + content type: JSON and XML bodies of one status are two slots */
543
+ const lookup = new Map();
544
+ for (const slot of slots) {
545
+ const name = slotNames.get(slot);
546
+ if (!name)
547
+ continue;
548
+ const status = slot.context.role === "response" ? slot.context.status : undefined;
549
+ lookup.set(slotKey(slot.pathKey, slot.context.method, slot.context.role, status, slot.contentType), name);
550
+ }
551
+ const rewriteContent = (content, keyFor) => {
552
+ const newContent = {};
553
+ let changed = false;
554
+ for (const [ct, mediaType] of Object.entries(content)) {
555
+ const schema = mediaType.schema;
556
+ if (!schema || schema.$ref) {
557
+ newContent[ct] = mediaType;
558
+ continue;
559
+ }
560
+ const name = lookup.get(keyFor(ct));
561
+ if (name) {
562
+ newContent[ct] = { ...mediaType, schema: { $ref: `#/components/schemas/${name}` } };
563
+ changed = true;
564
+ continue;
565
+ }
566
+ const rewritten = hoistedCanonicalToName.size > 0 ? rewriteNestedRefs(schema, hoistedCanonicalToName) : schema;
567
+ if (rewritten !== schema) {
568
+ newContent[ct] = { ...mediaType, schema: rewritten };
569
+ changed = true;
570
+ }
571
+ else {
572
+ newContent[ct] = mediaType;
573
+ }
574
+ }
575
+ return changed ? newContent : null;
576
+ };
577
+ const newPaths = {};
578
+ for (const [pathKey, methods] of Object.entries(paths)) {
579
+ const newMethods = {};
580
+ for (const [method, operation] of Object.entries(methods)) {
581
+ const op = operation;
582
+ const m = method.toLowerCase();
583
+ let newOp = op;
584
+ const requestBody = op.requestBody;
585
+ const reqContent = requestBody?.content;
586
+ if (requestBody && reqContent) {
587
+ const next = rewriteContent(reqContent, (ct) => slotKey(pathKey, m, "request", undefined, ct));
588
+ if (next)
589
+ newOp = { ...op, requestBody: { ...requestBody, content: next } };
590
+ }
591
+ const responses = newOp.responses;
592
+ if (responses) {
593
+ let newResponses;
594
+ for (const [status, response] of Object.entries(responses)) {
595
+ const content = response.content;
596
+ if (!content)
597
+ continue;
598
+ const statusNum = statusNumber(status);
599
+ const next = rewriteContent(content, (ct) => slotKey(pathKey, m, "response", statusNum, ct));
600
+ if (!next)
601
+ continue;
602
+ if (!newResponses)
603
+ newResponses = { ...responses };
604
+ newResponses[status] = { ...response, content: next };
605
+ }
606
+ if (newResponses)
607
+ newOp = { ...newOp, responses: newResponses };
608
+ }
609
+ newMethods[method] = newOp;
610
+ }
611
+ newPaths[pathKey] = newMethods;
612
+ }
613
+ return newPaths;
614
+ }
615
+ /** Every `#/components/schemas/<name>` a value refers to. */
616
+ function collectComponentRefs(value, out) {
617
+ if (Array.isArray(value)) {
618
+ for (const item of value)
619
+ collectComponentRefs(item, out);
620
+ return;
621
+ }
622
+ if (value === null || typeof value !== "object")
623
+ return;
624
+ for (const [key, child] of Object.entries(value)) {
625
+ if (key === "$ref" && typeof child === "string" && child.startsWith("#/components/schemas/")) {
626
+ out.add(child.slice("#/components/schemas/".length));
627
+ }
628
+ else {
629
+ collectComponentRefs(child, out);
630
+ }
631
+ }
632
+ }
633
+ export function deduplicateSchemas(spec) {
634
+ /* Sort paths lexicographically for determinism — input order must not affect output. */
635
+ const sortedPaths = {};
636
+ for (const key of Object.keys(spec.paths).sort())
637
+ sortedPaths[key] = spec.paths[key];
638
+ const slots = collectSchemaSlots(sortedPaths);
639
+ const existing = spec.components?.schemas ?? {};
640
+ /* Group slots by canonical shape — same shape = one component. */
641
+ const groups = new Map();
642
+ for (const slot of slots) {
643
+ const group = groups.get(slot.canonical);
644
+ if (group)
645
+ group.push(slot);
646
+ else
647
+ groups.set(slot.canonical, [slot]);
648
+ }
649
+ /*
650
+ * Tier resolution per canonical group:
651
+ * Tier 1: unique shape (group.length === 1) → operation-derived name
652
+ * Tier 2: shared error envelope → Err{status}* name
653
+ * Tier 3: shared non-error → first-slot operation-derived name
654
+ * Collision (two canonicals → same tier name, or a name an existing component holds) →
655
+ * hash suffix, re-checked until free.
656
+ */
657
+ const canonicalToName = new Map();
658
+ const nameToCanonical = new Map();
659
+ for (const [canonical, groupSlots] of groups) {
660
+ const firstSlot = groupSlots[0];
661
+ let base;
662
+ if (groupSlots.length > 1 && isErrorEnvelope(firstSlot.schema)) {
663
+ const fallbackStatus = firstSlot.context.role === "response" ? firstSlot.context.status : undefined;
664
+ base = deriveErrorEnvelopeName(firstSlot.schema, fallbackStatus);
665
+ }
666
+ else {
667
+ base = deriveSchemaName(firstSlot.context);
668
+ }
669
+ const name = uniqueName(base, canonical, (n) => Object.hasOwn(existing, n) || (nameToCanonical.has(n) && nameToCanonical.get(n) !== canonical));
670
+ nameToCanonical.set(name, canonical);
671
+ canonicalToName.set(canonical, name);
672
+ }
673
+ /* Build slotNames map (SchemaSlot → resolved name) for hoisting + path rewriting. */
674
+ const slotNames = new Map();
675
+ for (const slot of slots) {
676
+ const name = canonicalToName.get(slot.canonical);
677
+ if (name)
678
+ slotNames.set(slot, name);
679
+ }
680
+ /* Nested hoisting: collect shared nested objects, then rewrite inline → $ref.
681
+ * Error envelope slots (any tier) mark their nested schemas as envelope-owned so
682
+ * envelope-exclusive nesting (e.g. `fields`) is not hoisted under a per-spec name. */
683
+ const { schemas: hoistedSchemas, canonicalToName: hoistedCanonicalToName } = collectHoistedSchemas(slots, slotNames, (n) => Object.hasOwn(existing, n) || nameToCanonical.has(n));
684
+ /* Build components map, rewriting nested fields → $refs where hoisted */
685
+ const extractedSchemas = {};
686
+ for (const slot of slots) {
687
+ const name = slotNames.get(slot);
688
+ if (name && !Object.hasOwn(extractedSchemas, name)) {
689
+ extractedSchemas[name] =
690
+ hoistedCanonicalToName.size > 0 ? rewriteNestedRefs(slot.schema, hoistedCanonicalToName) : slot.schema;
691
+ }
692
+ }
693
+ const hasNewSchemas = Object.keys(extractedSchemas).length > 0 || Object.keys(hoistedSchemas).length > 0;
694
+ /* Nothing to extract and no existing components — return unchanged */
695
+ if (!hasNewSchemas && !spec.components)
696
+ return spec;
697
+ const paths = slotNames.size > 0 || hoistedCanonicalToName.size > 0
698
+ ? rewritePathsToRefs(sortedPaths, slots, slotNames, hoistedCanonicalToName)
699
+ : sortedPaths;
700
+ const mergedSchemas = { ...existing, ...extractedSchemas };
701
+ /* a hoisted nested schema whose every user became a component $ref is still referenced from
702
+ that component; one referenced from nowhere is dropped */
703
+ const referenced = new Set();
704
+ collectComponentRefs(paths, referenced);
705
+ collectComponentRefs(mergedSchemas, referenced);
706
+ for (const [name, schema] of Object.entries(hoistedSchemas)) {
707
+ if (referenced.has(name))
708
+ mergedSchemas[name] = schema;
709
+ }
710
+ const components = {
711
+ ...spec.components,
712
+ ...(Object.keys(mergedSchemas).length > 0 ? { schemas: mergedSchemas } : {}),
713
+ };
714
+ return { ...spec, components, paths };
715
+ }
716
+ export function sanitizeOpenApiSpec(spec, options) {
717
+ const result = cloneJson(spec);
718
+ if (options.stripSecuritySchemes?.length && result.components?.securitySchemes) {
719
+ for (const name of options.stripSecuritySchemes) {
720
+ delete result.components.securitySchemes[name];
721
+ }
722
+ if (Object.keys(result.components.securitySchemes).length === 0) {
723
+ delete result.components.securitySchemes;
724
+ }
725
+ }
726
+ for (const methods of Object.values(result.paths)) {
727
+ for (const operation of Object.values(methods)) {
728
+ const op = operation;
729
+ if (options.stripSecurityRequirements?.length && Array.isArray(op.security)) {
730
+ op.security = op.security.filter((s) => !options.stripSecurityRequirements?.some((name) => name in s));
731
+ if (op.security.length === 0)
732
+ delete op.security;
733
+ }
734
+ if (options.stripXExtensions) {
735
+ const targets = options.stripXExtensions === true
736
+ ? Object.keys(op).filter((k) => k.startsWith("x-"))
737
+ : options.stripXExtensions;
738
+ for (const ext of targets)
739
+ delete op[ext];
740
+ }
741
+ }
742
+ }
743
+ return result;
744
+ }
745
+ function cloneJson(value) {
746
+ try {
747
+ return structuredClone(value);
748
+ }
749
+ catch {
750
+ return JSON.parse(JSON.stringify(value));
751
+ }
752
+ }
753
+ /* Inlining expands each $ref; a graph of shared refs can grow exponentially (`L30` with two
754
+ fields that both point at `L29`, …), so the output is bounded and generation fails loudly. */
755
+ const MAX_INLINED_SCHEMA_NODES = 200_000;
756
+ function chargeInline(budget) {
757
+ if (++budget.nodes > MAX_INLINED_SCHEMA_NODES) {
758
+ throw new Error(`SDK codegen: inlined schemas exceed ${MAX_INLINED_SCHEMA_NODES} nodes (deeply shared $refs); ` +
759
+ "flatten the shared component chain");
760
+ }
761
+ }
762
+ function resolveSchema(schema, schemas, budget, visited = new Set()) {
763
+ if (typeof schema.$ref === "string" && schema.$ref.startsWith("#/components/schemas/")) {
764
+ const name = schema.$ref.slice("#/components/schemas/".length);
765
+ /* circular ref guard — return empty schema which maps to unknown in IR */
766
+ if (visited.has(name))
767
+ return {};
768
+ const resolved = Object.hasOwn(schemas, name) ? schemas[name] : undefined;
769
+ if (!resolved)
770
+ throw new Error(`$ref points to nonexistent component: ${schema.$ref}`);
771
+ const next = new Set(visited);
772
+ next.add(name);
773
+ return resolveSchemaDeep(cloneJson(resolved), schemas, budget, next);
774
+ }
775
+ return resolveSchemaDeep(schema, schemas, budget, visited);
776
+ }
777
+ function resolveSchemaDeep(schema, schemas, budget, visited) {
778
+ chargeInline(budget);
779
+ for (const key of ["oneOf", "anyOf", "allOf"]) {
780
+ const arr = schema[key];
781
+ if (Array.isArray(arr)) {
782
+ schema[key] = arr.map((item) => {
783
+ if (item && typeof item === "object" && !Array.isArray(item)) {
784
+ return resolveSchema(item, schemas, budget, visited);
785
+ }
786
+ return item;
787
+ });
788
+ }
789
+ }
790
+ /* resolve array items */
791
+ if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
792
+ schema.items = resolveSchema(schema.items, schemas, budget, visited);
793
+ }
794
+ /* resolve object properties */
795
+ if (schema.properties && typeof schema.properties === "object") {
796
+ const props = schema.properties;
797
+ for (const [k, v] of Object.entries(props)) {
798
+ if (v && typeof v === "object" && !Array.isArray(v)) {
799
+ props[k] = resolveSchema(v, schemas, budget, visited);
800
+ }
801
+ }
802
+ }
803
+ /* resolve additionalProperties */
804
+ if (schema.additionalProperties &&
805
+ typeof schema.additionalProperties === "object" &&
806
+ !Array.isArray(schema.additionalProperties)) {
807
+ schema.additionalProperties = resolveSchema(schema.additionalProperties, schemas, budget, visited);
808
+ }
809
+ return schema;
810
+ }
811
+ export function resolveRefs(spec) {
812
+ const schemas = spec.components?.schemas;
813
+ if (!schemas || Object.keys(schemas).length === 0)
814
+ return spec;
815
+ const paths = cloneJson(spec.paths);
816
+ const budget = { nodes: 0 };
817
+ for (const methods of Object.values(paths)) {
818
+ for (const operation of Object.values(methods)) {
819
+ const op = operation;
820
+ const requestBody = op.requestBody;
821
+ if (requestBody) {
822
+ const content = requestBody.content;
823
+ if (content) {
824
+ for (const [ct, mediaType] of Object.entries(content)) {
825
+ const schema = mediaType.schema;
826
+ if (schema)
827
+ content[ct] = { ...mediaType, schema: resolveSchema(schema, schemas, budget) };
828
+ }
829
+ }
830
+ }
831
+ const responses = op.responses;
832
+ if (responses) {
833
+ for (const [status, response] of Object.entries(responses)) {
834
+ const content = response.content;
835
+ if (!content)
836
+ continue;
837
+ /* every content type resolved, not only the last one written */
838
+ const resolved = {};
839
+ for (const [ct, mediaType] of Object.entries(content)) {
840
+ const schema = mediaType.schema;
841
+ resolved[ct] = schema ? { ...mediaType, schema: resolveSchema(schema, schemas, budget) } : mediaType;
842
+ }
843
+ responses[status] = { ...response, content: resolved };
844
+ }
845
+ }
846
+ }
847
+ }
848
+ const { schemas: _schemas, ...restComponents } = spec.components ?? {};
849
+ const components = Object.keys(restComponents).length > 0 ? restComponents : undefined;
850
+ return { ...spec, components, paths };
851
+ }
852
+ /**
853
+ * Zod node kinds JSON Schema has no word for. Zod emits `{}` for each of them under
854
+ * `unrepresentable: "any"`; the degrade is per node, and the rest of the schema survives.
855
+ */
856
+ const ZOD_UNREPRESENTABLE = new Set(["custom", "function", "map", "nan", "promise", "set", "symbol", "transform"]);
857
+ /** `override` for Zod's converter: give `Date` and `BigInt` their wire shape, record what degraded. */
858
+ function zodOverride(degraded) {
859
+ return (ctx) => {
860
+ const def = zodDefOf(ctx.zodSchema);
861
+ const kind = String(def?.type ?? "");
862
+ if (kind === "date") {
863
+ /* JSON.stringify writes a Date as an ISO string */
864
+ ctx.jsonSchema.format = "date-time";
865
+ ctx.jsonSchema.type = "string";
866
+ return;
867
+ }
868
+ if (kind === "bigint") {
869
+ ctx.jsonSchema.format = "int64";
870
+ ctx.jsonSchema.type = "integer";
871
+ return;
872
+ }
873
+ if (ZOD_UNREPRESENTABLE.has(kind) && Object.keys(ctx.jsonSchema).length === 0)
874
+ degraded.add(kind);
875
+ };
876
+ }
877
+ /** Zod 4 puts converters on the schema instance. Use those first so Workers
878
+ * (where `import("zod")` does not resolve) still emit real JSON Schema. */
879
+ function zodInstanceToJsonSchema(schema, io, degraded) {
880
+ const libraryOptions = { override: zodOverride(degraded), unrepresentable: "any" };
881
+ const jsonSchema = schema["~standard"].jsonSchema;
882
+ const fromStandard = jsonSchema?.[io];
883
+ if (typeof fromStandard === "function") {
884
+ const result = fromStandard({ libraryOptions, target: "draft-2020-12" });
885
+ if (result && typeof result === "object")
886
+ return result;
887
+ }
888
+ const inst = schema.toJSONSchema;
889
+ if (typeof inst === "function") {
890
+ const result = inst.call(schema, { ...libraryOptions, io });
891
+ if (result && typeof result === "object")
892
+ return result;
893
+ }
894
+ if (toJSONSchemaFn) {
895
+ const convert = toJSONSchemaFn;
896
+ const result = convert(schema, { ...libraryOptions, io });
897
+ if (result && typeof result === "object")
898
+ return result;
899
+ }
900
+ return undefined;
901
+ }
902
+ function applyZodBag(schema, json) {
903
+ const bag = schema._zod?.bag;
904
+ if (!bag)
905
+ return json;
906
+ if (typeof bag.format === "string") {
907
+ if (bag.format === "safeint")
908
+ json.type = "integer";
909
+ else
910
+ json.format = bag.format;
911
+ }
912
+ if (json.type === "string") {
913
+ if (typeof bag.minimum === "number")
914
+ json.minLength = bag.minimum;
915
+ if (typeof bag.maximum === "number")
916
+ json.maxLength = bag.maximum;
917
+ }
918
+ if (json.type === "number" || json.type === "integer") {
919
+ if (typeof bag.minimum === "number")
920
+ json.minimum = bag.minimum;
921
+ if (typeof bag.maximum === "number")
922
+ json.maximum = bag.maximum;
923
+ }
924
+ return json;
925
+ }
926
+ function zodDefOf(schema) {
927
+ const s = schema;
928
+ return s._def ?? s.def ?? s._zod?.def;
929
+ }
930
+ function zodDefIsOptional(def, io) {
931
+ const t = String(def.typeName ?? def.type ?? "");
932
+ if (t === "ZodOptional" || t === "optional")
933
+ return true;
934
+ /* a default fills the value in: optional on the way in, always present on the way out */
935
+ if ((t === "ZodDefault" || t === "default" || t === "prefault") && io === "input")
936
+ return true;
937
+ if (t === "ZodNullable" ||
938
+ t === "nullable" ||
939
+ t === "ZodDefault" ||
940
+ t === "default" ||
941
+ t === "prefault" ||
942
+ t === "ZodCatch" ||
943
+ t === "catch" ||
944
+ t === "ZodReadonly" ||
945
+ t === "readonly") {
946
+ const inner = def.innerType;
947
+ if (inner) {
948
+ const innerDef = zodDefOf(inner);
949
+ if (innerDef)
950
+ return zodDefIsOptional(innerDef, io);
951
+ }
952
+ }
953
+ return false;
954
+ }
955
+ function literalSchema(values) {
956
+ if (values.length === 1)
957
+ return { const: values[0] };
958
+ return { enum: [...values] };
959
+ }
960
+ /** Walk Zod 3/4 internals. Survives Workers bundles that drop `toJSONSchema`. */
961
+ function zodDefToJsonSchema(schema, io = "output", depth = 0) {
962
+ if (depth > 24)
963
+ return {};
964
+ const def = zodDefOf(schema);
965
+ if (!def)
966
+ return undefined;
967
+ const typeName = String(def.typeName ?? def.type ?? "");
968
+ const walk = (child) => zodDefToJsonSchema(child, io, depth + 1) ?? {};
969
+ switch (typeName) {
970
+ case "ZodString":
971
+ case "string":
972
+ return applyZodBag(schema, { type: "string" });
973
+ case "ZodNumber":
974
+ case "number":
975
+ return applyZodBag(schema, { type: "number" });
976
+ case "ZodBoolean":
977
+ case "boolean":
978
+ return { type: "boolean" };
979
+ case "ZodBigInt":
980
+ case "bigint":
981
+ return { format: "int64", type: "integer" };
982
+ case "ZodDate":
983
+ case "date":
984
+ return { format: "date-time", type: "string" };
985
+ case "ZodFile":
986
+ case "file":
987
+ /* so form emit can see format:binary when toJSONSchema is unavailable */
988
+ return { contentEncoding: "binary", format: "binary", type: "string" };
989
+ case "ZodNull":
990
+ case "null":
991
+ return { type: "null" };
992
+ case "ZodUndefined":
993
+ case "ZodVoid":
994
+ case "undefined":
995
+ case "void":
996
+ case "ZodAny":
997
+ case "ZodUnknown":
998
+ case "any":
999
+ case "unknown":
1000
+ return {};
1001
+ case "ZodLiteral":
1002
+ case "literal": {
1003
+ const values = Array.isArray(def.values) ? def.values : [def.value];
1004
+ return literalSchema(values.map((v) => (typeof v === "bigint" ? Number(v) : v)));
1005
+ }
1006
+ case "ZodEnum":
1007
+ case "enum": {
1008
+ const raw = (def.values ?? def.entries);
1009
+ let list;
1010
+ if (Array.isArray(raw))
1011
+ list = raw;
1012
+ else {
1013
+ /* a TS numeric enum carries reverse mappings: { A: 0, "0": "A" } — keep the values */
1014
+ const entries = raw;
1015
+ list = Object.keys(entries)
1016
+ .filter((k) => typeof entries[entries[k]] !== "number")
1017
+ .map((k) => entries[k]);
1018
+ }
1019
+ const types = new Set(list.map((v) => typeof v));
1020
+ if (types.size === 1 && types.has("string"))
1021
+ return { enum: list, type: "string" };
1022
+ if (types.size === 1 && types.has("number"))
1023
+ return { enum: list, type: "number" };
1024
+ return { enum: list };
1025
+ }
1026
+ case "ZodObject":
1027
+ case "object": {
1028
+ const rawShape = def.shape;
1029
+ const shape = typeof rawShape === "function" ? rawShape() : rawShape;
1030
+ if (!shape)
1031
+ return { type: "object" };
1032
+ const properties = {};
1033
+ const required = [];
1034
+ for (const key of Object.keys(shape)) {
1035
+ const prop = shape[key];
1036
+ properties[key] = walk(prop);
1037
+ const propDef = zodDefOf(prop);
1038
+ if (!propDef || !zodDefIsOptional(propDef, io))
1039
+ required.push(key);
1040
+ }
1041
+ const result = { properties, type: "object" };
1042
+ if (io === "output")
1043
+ result.additionalProperties = false;
1044
+ if (required.length > 0)
1045
+ result.required = required;
1046
+ return result;
1047
+ }
1048
+ case "ZodArray":
1049
+ case "array":
1050
+ return { items: walk(def.element ?? def.type), type: "array" };
1051
+ case "ZodOptional":
1052
+ case "optional":
1053
+ return zodDefToJsonSchema(def.innerType, io, depth + 1);
1054
+ case "ZodNullable":
1055
+ case "nullable":
1056
+ return { anyOf: [walk(def.innerType), { type: "null" }] };
1057
+ case "ZodUnion":
1058
+ case "ZodDiscriminatedUnion":
1059
+ case "union":
1060
+ return { anyOf: (def.options ?? []).map(walk) };
1061
+ case "ZodIntersection":
1062
+ case "intersection":
1063
+ return { allOf: [walk(def.left), walk(def.right)] };
1064
+ case "ZodRecord":
1065
+ case "record":
1066
+ return { additionalProperties: walk(def.valueType), type: "object" };
1067
+ case "ZodTuple":
1068
+ case "tuple": {
1069
+ const items = (def.items ?? []).map(walk);
1070
+ const out = { prefixItems: items, type: "array" };
1071
+ if (def.rest)
1072
+ out.items = walk(def.rest);
1073
+ else {
1074
+ out.items = false;
1075
+ out.maxItems = items.length;
1076
+ }
1077
+ out.minItems = items.length;
1078
+ return out;
1079
+ }
1080
+ case "ZodDefault":
1081
+ case "default":
1082
+ case "prefault": {
1083
+ const inner = walk(def.innerType);
1084
+ const value = def.defaultValue;
1085
+ const resolved = typeof value === "function" ? undefined : value;
1086
+ return resolved === undefined ? inner : { ...inner, default: resolved };
1087
+ }
1088
+ case "ZodCatch":
1089
+ case "catch":
1090
+ case "ZodReadonly":
1091
+ case "readonly":
1092
+ return zodDefToJsonSchema(def.innerType, io, depth + 1);
1093
+ case "ZodPipeline":
1094
+ case "pipe":
1095
+ /* a pipe validates `in` and returns `out` */
1096
+ return zodDefToJsonSchema(io === "input" ? def.in : def.out, io, depth + 1);
1097
+ case "ZodBranded":
1098
+ return zodDefToJsonSchema(def.type, io, depth + 1);
1099
+ case "ZodLazy":
1100
+ case "lazy": {
1101
+ const getter = def.getter;
1102
+ return typeof getter === "function" ? zodDefToJsonSchema(getter(), io, depth + 1) : {};
1103
+ }
1104
+ default:
1105
+ return undefined;
1106
+ }
1107
+ }
1108
+ /** How a schema that cannot be converted at all is handled. Set per `generateOpenApi` call. */
1109
+ let schemaFailureMode = "throw";
1110
+ function schemaConversionFailed(what, io, err) {
1111
+ const msg = err instanceof Error ? err.message : String(err);
1112
+ const text = `[honey:codegen] schemaToJsonSchema could not convert ${what} (io=${io}): ${msg}`;
1113
+ if (schemaFailureMode === "throw")
1114
+ throw new Error(text);
1115
+ console.warn(`${text}. Documented as an unconstrained schema.`);
1116
+ return {};
1117
+ }
1118
+ function schemaToJsonSchema(schema, io = "output") {
1119
+ /* Pre-serialized JSON-schema blob or non-schema value — no ~standard marker.
1120
+ * Arktype schemas are functions but still carry the marker, so pass those through too. */
1121
+ if (schema === null ||
1122
+ (typeof schema !== "object" && typeof schema !== "function") ||
1123
+ !("~standard" in schema)) {
1124
+ return schema;
1125
+ }
1126
+ const vendor = schema["~standard"].vendor;
1127
+ if (vendor === "zod") {
1128
+ const kind = schema?._zod?.def?.type ?? "unknown";
1129
+ const degraded = new Set();
1130
+ let result;
1131
+ try {
1132
+ result = zodInstanceToJsonSchema(schema, io, degraded) ?? zodDefToJsonSchema(schema, io);
1133
+ }
1134
+ catch (err) {
1135
+ return schemaConversionFailed(`zod/${kind}`, io, err);
1136
+ }
1137
+ if (!result)
1138
+ return schemaConversionFailed(`zod/${kind}`, io, new Error("no converter available"));
1139
+ if (degraded.size > 0) {
1140
+ console.warn(`[honey:codegen] schemaToJsonSchema: zod/${kind} (io=${io}) has ${[...degraded].sort().join(", ")} ` +
1141
+ "node(s) JSON Schema cannot express; each is documented as an unconstrained schema. " +
1142
+ 'Describe them with .meta({ ... }) or z.toJSONSchema\'s "override".');
1143
+ }
1144
+ return sanitizeZodJsonSchema(result);
1145
+ }
1146
+ try {
1147
+ if (vendor === "valibot")
1148
+ return valibotToJsonSchema(schema);
1149
+ if (vendor === "arktype")
1150
+ return arkTypeToJsonSchema(schema);
1151
+ if (vendor === "yup")
1152
+ return yupToJsonSchema(schema);
1153
+ if (vendor === "effect")
1154
+ return effectToJsonSchema(schema);
1155
+ }
1156
+ catch (err) {
1157
+ return schemaConversionFailed(vendor, io, err);
1158
+ }
1159
+ /* no converter for this vendor: say so once per schema, never emit the introspection object */
1160
+ console.warn(`[honey:codegen] schemaToJsonSchema: no JSON Schema converter for vendor "${vendor}"; ` +
1161
+ "documented as an unconstrained schema.");
1162
+ return {};
1163
+ }
1164
+ export function generateManifest(app, options = {}) {
1165
+ const factory = getErrorFactory(app);
1166
+ const published = options.visibility === "published";
1167
+ const publishable = published ? publishableMetaKeys(metaSpecOf(app)) : null;
1168
+ const collected = collectRoutes(app).filter(({ handler, method, path }) => {
1169
+ if (published && isMetaInternal(handler))
1170
+ return false;
1171
+ return !options.filterRoutes || options.filterRoutes({ meta: handler.mt ?? EMPTY_OBJ, method, path });
1172
+ });
1173
+ /* each key resolved against the factory of a route that declares it — a mounted sub-app keeps its own */
1174
+ const allErrorKeys = new Map();
1175
+ const routes = collected.map(({ handler, method, path }) => {
1176
+ const entry = {
1177
+ errors: Array.from(handler.ek),
1178
+ meta: publishable
1179
+ ? Object.fromEntries(Object.entries(handler.mt ?? EMPTY_OBJ).filter(([key]) => publishable(key)))
1180
+ : (handler.mt ?? EMPTY_OBJ),
1181
+ method,
1182
+ middleware: handler.mw.map((mw) => mw.name || "anonymous"),
1183
+ params: extractParams(path),
1184
+ path,
1185
+ };
1186
+ const routeFactory = handler.fac ?? factory;
1187
+ for (const ek of handler.ek) {
1188
+ if (!allErrorKeys.has(ek))
1189
+ allErrorKeys.set(ek, routeFactory);
1190
+ }
1191
+ if (handler.iv) {
1192
+ entry.input = Object.fromEntries(Object.entries(handler.iv)
1193
+ .filter(([, v]) => v !== undefined)
1194
+ .map(([k, v]) => [k, introspectSchema(unwrapEntry(v))]));
1195
+ }
1196
+ if (handler.os) {
1197
+ entry.output = Object.fromEntries(Object.entries(handler.os)
1198
+ .filter(([, v]) => v !== undefined)
1199
+ .map(([contentType, schemas]) => {
1200
+ if (contentType === "redirect") {
1201
+ return [contentType, schemas];
1202
+ }
1203
+ return [
1204
+ contentType,
1205
+ Object.fromEntries(Object.entries(schemas)
1206
+ .filter(([, s]) => s !== undefined)
1207
+ .map(([statusKey, s]) => [statusKey, introspectSchema(s)])),
1208
+ ];
1209
+ }));
1210
+ }
1211
+ return entry;
1212
+ });
1213
+ return {
1214
+ errors: Array.from(allErrorKeys).map(([key, keyFactory]) => resolveErrorInfo(key, keyFactory)),
1215
+ routes,
1216
+ };
1217
+ }
1218
+ /* ---- OpenAPI generation ---- */
1219
+ export async function generateOpenApi(app, options) {
1220
+ await Promise.all([loadToJSONSchema(), loadEffectJsonSchema()]);
1221
+ const prev = getJsonSchemaConverter();
1222
+ const prevMode = schemaFailureMode;
1223
+ schemaFailureMode = options.onSchemaError ?? "throw";
1224
+ setJsonSchemaConverter((schema, io) => schemaToJsonSchema(schema, io));
1225
+ try {
1226
+ const { onSchemaError: _mode, ...rest } = options;
1227
+ const spec = generateOpenApiFromTree(app, rest);
1228
+ return deduplicateSchemas(spec);
1229
+ }
1230
+ finally {
1231
+ setJsonSchemaConverter(prev);
1232
+ schemaFailureMode = prevMode;
1233
+ }
1234
+ }
1235
+ export function extractSchemas(app) {
1236
+ const collected = collectRoutes(app);
1237
+ const result = {};
1238
+ for (const { handler, method, path } of collected) {
1239
+ const key = `${method} ${path}`;
1240
+ result[key] = {};
1241
+ if (handler.iv) {
1242
+ result[key].input = Object.fromEntries(Object.entries(handler.iv)
1243
+ .filter(([, v]) => v !== undefined)
1244
+ .map(([k, v]) => [k, introspectSchema(unwrapEntry(v))]));
1245
+ }
1246
+ if (handler.os) {
1247
+ result[key].output = Object.fromEntries(Object.entries(handler.os)
1248
+ .filter(([, v]) => v !== undefined)
1249
+ .map(([contentType, schemas]) => [
1250
+ contentType,
1251
+ Object.fromEntries(Object.entries(schemas)
1252
+ .filter(([, s]) => s !== undefined)
1253
+ .map(([statusKey, s]) => [statusKey, introspectSchema(s)])),
1254
+ ]));
1255
+ }
1256
+ }
1257
+ return result;
1258
+ }
1259
+ /* ---- Static route tree codegen ---- */
1260
+ /**
1261
+ * Reject meta a generated file cannot reproduce. A route tree is JSON plus topology: a value
1262
+ * JSON cannot round-trip (`Date`, `RegExp`, `NaN`, `BigInt`, class instances) would be
1263
+ * emitted as something else, or interned together with a different value.
1264
+ */
1265
+ function assertJsonValue(value, where, path) {
1266
+ const fail = (what) => {
1267
+ throw new Error(`${where}: ${path} is ${what} — route trees carry JSON values only`);
1268
+ };
1269
+ if (value === null || typeof value === "string" || typeof value === "boolean")
1270
+ return;
1271
+ if (typeof value === "number") {
1272
+ if (!Number.isFinite(value))
1273
+ fail(String(value));
1274
+ return;
1275
+ }
1276
+ if (value === undefined)
1277
+ return;
1278
+ if (typeof value !== "object")
1279
+ fail(`a ${typeof value}`);
1280
+ if (Array.isArray(value)) {
1281
+ for (let k = 0; k < value.length; k++)
1282
+ assertJsonValue(value[k], where, `${path}[${k}]`);
1283
+ return;
1284
+ }
1285
+ const proto = Object.getPrototypeOf(value);
1286
+ if (proto !== Object.prototype && proto !== null) {
1287
+ const name = value.constructor?.name ?? "an object";
1288
+ fail(`a ${name}`);
1289
+ }
1290
+ for (const key of Object.keys(value)) {
1291
+ assertJsonValue(value[key], where, `${path}.${key}`);
1292
+ }
1293
+ }
1294
+ /** Object-literal key that defines an own property even for `__proto__`. */
1295
+ function keyExpr(key) {
1296
+ return key === "__proto__" ? '["__proto__"]' : JSON.stringify(key);
1297
+ }
1298
+ function methodMapExpr(m, intern) {
1299
+ const parts = Object.keys(m).map((method) => `${keyExpr(method)}: ${intern.expr(m[method])}`);
1300
+ return `S({ ${parts.join(", ")} })`;
1301
+ }
1302
+ function serializeNode(node, intern) {
1303
+ const statics = Object.keys(node.s);
1304
+ const sExpr = statics.length > 0
1305
+ ? `S({ ${statics.map((k) => `${keyExpr(k)}: ${serializeNode(node.s[k], intern)}`).join(", ")} })`
1306
+ : "undefined";
1307
+ const args = [
1308
+ sExpr,
1309
+ node.m !== null ? methodMapExpr(node.m, intern) : "null",
1310
+ node.d !== null ? `{ n: ${JSON.stringify(node.d.n)}, c: ${serializeNode(node.d.c, intern)} }` : "null",
1311
+ node.w !== null ? `{ n: ${JSON.stringify(node.w.n)}, m: ${methodMapExpr(node.w.m, intern)} }` : "null",
1312
+ node.ws !== null ? intern.expr(node.ws) : "null",
1313
+ ];
1314
+ while (args.length > 0 && (args[args.length - 1] === "null" || args[args.length - 1] === "undefined"))
1315
+ args.pop();
1316
+ return `N(${args.join(", ")})`;
1317
+ }
1318
+ function collectMetaShape(entries) {
1319
+ const allKeys = new Map();
1320
+ const keyCount = new Map();
1321
+ let metaHandlerCount = 0;
1322
+ for (const { mt } of entries) {
1323
+ if (mt === null || mt === undefined)
1324
+ continue;
1325
+ metaHandlerCount++;
1326
+ for (const [k, v] of Object.entries(mt)) {
1327
+ if (!allKeys.has(k))
1328
+ allKeys.set(k, new Set());
1329
+ allKeys.get(k)?.add(emitLiteral(v));
1330
+ keyCount.set(k, (keyCount.get(k) ?? 0) + 1);
1331
+ }
1332
+ }
1333
+ return { allKeys, keyCount, metaHandlerCount };
1334
+ }
1335
+ function emitMetaShapeType(shape) {
1336
+ if (shape.allKeys.size === 0)
1337
+ return "";
1338
+ const props = [...shape.allKeys.entries()]
1339
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
1340
+ .map(([key, types]) => {
1341
+ const optional = (shape.keyCount.get(key) ?? 0) < shape.metaHandlerCount ? "?" : "";
1342
+ const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
1343
+ return `\t${safeKey}${optional}: ${[...types].join(" | ")}`;
1344
+ });
1345
+ return `export type MetaShape = {\n${props.join("\n")}\n} & Record<string, unknown>\n`;
1346
+ }
1347
+ /**
1348
+ * Emit a route tree module: topology with `RouteId` leaves plus per-route data (meta, error
1349
+ * keys, boundary key). No handler fields — the app that loads the tree registers its routes,
1350
+ * which bind to these ids; leaves it does not register are delegated to its catch-all.
1351
+ */
1352
+ function emitRouteTree(tree) {
1353
+ const order = [];
1354
+ const seen = new Set();
1355
+ forEachLeaf(tree.root, (_method, _path, id) => {
1356
+ if (!seen.has(id)) {
1357
+ seen.add(id);
1358
+ order.push(id);
1359
+ }
1360
+ });
1361
+ const entries = [];
1362
+ for (const id of order) {
1363
+ const entry = tree.routes[id];
1364
+ if (entry === undefined)
1365
+ throw new Error(`route tree leaf ${id} has no route entry`);
1366
+ const mt = entry.mt ?? null;
1367
+ if (mt !== null)
1368
+ assertJsonValue(mt, `Route meta for ${id}`, "meta");
1369
+ entries.push({
1370
+ bek: entry.bek ?? null,
1371
+ ek: [...(entry.ek ?? [])],
1372
+ id,
1373
+ internalMeta: mt?.internal === true,
1374
+ mt,
1375
+ });
1376
+ }
1377
+ const intern = new InternPool();
1378
+ for (const e of entries) {
1379
+ intern.count(e.bek);
1380
+ if (e.ek.length > 0)
1381
+ intern.count(e.ek);
1382
+ intern.count(e.mt);
1383
+ intern.count(e.id);
1384
+ }
1385
+ for (const e of entries) {
1386
+ intern.force(e.mt, "M");
1387
+ intern.force(e.id, "P");
1388
+ }
1389
+ intern.seal();
1390
+ const metaShape = collectMetaShape(entries);
1391
+ const hasMetaShape = metaShape.allKeys.size > 0;
1392
+ const lines = [];
1393
+ lines.push('import type { RouteTree, TreeNode } from "@lovrozagar/honey/tree"');
1394
+ lines.push("");
1395
+ lines.push("function S<T>(o: Record<string, T>): Record<string, T> {");
1396
+ lines.push("\treturn Object.assign(Object.create(null) as Record<string, T>, o)");
1397
+ lines.push("}");
1398
+ lines.push('function N(s: Record<string, TreeNode> = S({}), m: Record<string, string> | null = null, d: TreeNode["d"] = null, w: TreeNode["w"] = null, ws: string | null = null): TreeNode {');
1399
+ lines.push("\treturn { d, m, s, w, ws }");
1400
+ lines.push("}");
1401
+ const consts = intern.emitConstLines();
1402
+ if (consts.length > 0) {
1403
+ lines.push("");
1404
+ for (const line of consts) {
1405
+ if (hasMetaShape && /^const M\d+ = /.test(line))
1406
+ lines.push(`${line} as unknown as MetaShape`);
1407
+ else
1408
+ lines.push(line);
1409
+ }
1410
+ }
1411
+ lines.push("");
1412
+ lines.push(`export const tree: TreeNode = ${serializeNode(tree.root, intern)}`);
1413
+ lines.push("");
1414
+ const routeLines = entries.map((e) => {
1415
+ const parts = [];
1416
+ if (e.bek !== null)
1417
+ parts.push(`bek: ${intern.expr(e.bek)}`);
1418
+ if (e.ek.length > 0)
1419
+ parts.push(`ek: ${intern.expr(e.ek)}`);
1420
+ if (e.mt !== null)
1421
+ parts.push(`mt: ${intern.expr(e.mt)}`);
1422
+ return `\t[${intern.expr(e.id)}]: ${parts.length > 0 ? `{ ${parts.join(", ")} }` : "{}"}`;
1423
+ });
1424
+ lines.push(`export const routes: RouteTree["routes"] = S({\n${routeLines.join(",\n")}\n})`);
1425
+ lines.push("");
1426
+ let code = lines.join("\n");
1427
+ if (hasMetaShape)
1428
+ code += `\n${emitMetaShapeType(metaShape)}\n`;
1429
+ const selectors = entries.filter((e) => !e.id.startsWith("WS ") && !e.internalMeta).map((e) => e.id);
1430
+ if (selectors.length > 0) {
1431
+ const sorted = [...new Set(selectors)].sort();
1432
+ const parts = sorted.map((sel) => {
1433
+ const ref = intern.id(sel);
1434
+ return ref !== undefined ? `typeof ${ref}` : JSON.stringify(sel);
1435
+ });
1436
+ code += `\nexport type RouteSelector = ${parts.join(" | ")}\n`;
1437
+ }
1438
+ const metaEntries = entries
1439
+ .filter((e) => e.mt !== null && Object.keys(e.mt).length > 0)
1440
+ .map((e) => `\t[${intern.expr(e.id)}]: ${intern.expr(e.mt)}`);
1441
+ const metaType = hasMetaShape ? "Record<string, MetaShape>" : "Record<string, Record<string, unknown>>";
1442
+ if (metaEntries.length > 0) {
1443
+ code += `export const meta: ${metaType} = S({\n${metaEntries.join(",\n")}\n})\n`;
1444
+ }
1445
+ code += `export const routeTree: RouteTree = { v: ${ROUTE_TREE_VERSION}, root: tree, routes, ${metaEntries.length > 0 ? "meta" : "meta: {}"} }\n`;
1446
+ return code;
1447
+ }
1448
+ /** Emit a route tree module from a tree (a `mergeTree` result or an `app.toRouteTree()` snapshot). */
1449
+ export function generateRouteTree(tree) {
1450
+ return emitRouteTree(tree);
1451
+ }
1452
+ export function generateRouteTreeFromApp(app) {
1453
+ return emitRouteTree(app.toRouteTree());
1454
+ }
1455
+ /**
1456
+ * Generate static route tree code from a pre-built RouteTree.
1457
+ * Supports gateway patterns: import service trees → enrich meta → mergeTree → generate.
1458
+ */
1459
+ export function generateRouteTreeFromRouteTree(rt) {
1460
+ return emitRouteTree(rt);
1461
+ }
1462
+ function emitInputType(handler, state) {
1463
+ if (!handler.iv)
1464
+ return "{}";
1465
+ const entries = [];
1466
+ for (const [source, schema] of Object.entries(handler.iv)) {
1467
+ if (schema === undefined)
1468
+ continue;
1469
+ const unwrapped = unwrapEntry(schema);
1470
+ entries.push(`${source}: ${emitSchemaType(unwrapped, state)}`);
1471
+ }
1472
+ if (entries.length === 0)
1473
+ return "{}";
1474
+ return `{ ${entries.join("; ")} }`;
1475
+ }
1476
+ function emitOutputType(handler, state) {
1477
+ if (!handler.os)
1478
+ return "{}";
1479
+ const ctEntries = [];
1480
+ for (const [contentType, schemas] of Object.entries(handler.os)) {
1481
+ if (schemas === undefined)
1482
+ continue;
1483
+ if (contentType === "redirect") {
1484
+ const keys = Object.keys(schemas).filter((k) => schemas[k]);
1485
+ if (keys.length > 0) {
1486
+ ctEntries.push(`"redirect": { ${keys.map((k) => `${quoteKey(k)}: true`).join("; ")} }`);
1487
+ }
1488
+ continue;
1489
+ }
1490
+ const statusEntries = [];
1491
+ for (const [statusKey, schema] of Object.entries(schemas)) {
1492
+ if (schema === undefined)
1493
+ continue;
1494
+ statusEntries.push(`${quoteKey(statusKey)}: ${emitSchemaType(schema, state)}`);
1495
+ }
1496
+ if (statusEntries.length > 0) {
1497
+ ctEntries.push(`${JSON.stringify(contentType)}: { ${statusEntries.join("; ")} }`);
1498
+ }
1499
+ }
1500
+ if (ctEntries.length === 0)
1501
+ return "{}";
1502
+ return `{ ${ctEntries.join("; ")} }`;
1503
+ }
1504
+ function emitMetaType(handler) {
1505
+ if (!handler.mt)
1506
+ return "{}";
1507
+ const entries = [];
1508
+ for (const [k, v] of Object.entries(handler.mt)) {
1509
+ entries.push(`${quoteKey(k)}: ${emitLiteral(v)}`);
1510
+ }
1511
+ if (entries.length === 0)
1512
+ return "{}";
1513
+ return `{ ${entries.join("; ")} }`;
1514
+ }
1515
+ function emitLiteral(value) {
1516
+ if (value === null)
1517
+ return "null";
1518
+ if (value === undefined)
1519
+ return "undefined";
1520
+ if (typeof value === "string")
1521
+ return JSON.stringify(value);
1522
+ if (typeof value === "number")
1523
+ return Number.isFinite(value) ? String(value) : "number";
1524
+ if (typeof value === "boolean")
1525
+ return String(value);
1526
+ if (typeof value === "bigint")
1527
+ return `${value}n`;
1528
+ if (Array.isArray(value)) {
1529
+ if (value.length === 0)
1530
+ return "[]";
1531
+ return `[${value.map((v) => emitLiteral(v)).join(", ")}]`;
1532
+ }
1533
+ if (typeof value === "object") {
1534
+ const obj = value;
1535
+ const keys = Object.keys(obj);
1536
+ if (keys.length === 0)
1537
+ return "{}";
1538
+ return `{ ${keys.map((k) => `${JSON.stringify(k)}: ${emitLiteral(obj[k])}`).join("; ")} }`;
1539
+ }
1540
+ return "unknown";
1541
+ }
1542
+ function emitErrorType(handler) {
1543
+ if (handler.ek.size === 0)
1544
+ return "never";
1545
+ return Array.from(handler.ek)
1546
+ .sort()
1547
+ .map((k) => JSON.stringify(k))
1548
+ .join(" | ");
1549
+ }
1550
+ function emitErrorShapes(handler, meta, state) {
1551
+ if (handler.ek.size === 0)
1552
+ return null;
1553
+ const entries = [];
1554
+ for (const key of Array.from(handler.ek).sort()) {
1555
+ const entry = meta?.[key];
1556
+ if (entry?.schema) {
1557
+ const schemaType = emitSchemaType(entry.schema, state);
1558
+ entries.push(`${quoteKey(key)}: ${schemaType}`);
1559
+ }
1560
+ else {
1561
+ entries.push(`${quoteKey(key)}: null`);
1562
+ }
1563
+ }
1564
+ return `{ ${entries.join("; ")} }`;
1565
+ }
1566
+ function emitErrorsByStatus(handler, meta, factory, state) {
1567
+ if (handler.ek.size === 0)
1568
+ return null;
1569
+ const byStatus = new Map();
1570
+ for (const key of handler.ek) {
1571
+ const info = resolveErrorInfo(key, factory);
1572
+ if (info.status > 0) {
1573
+ let keys = byStatus.get(info.status);
1574
+ if (!keys) {
1575
+ keys = [];
1576
+ byStatus.set(info.status, keys);
1577
+ }
1578
+ keys.push(key);
1579
+ }
1580
+ }
1581
+ const entries = [];
1582
+ for (const [status, keys] of Array.from(byStatus.entries()).sort((a, b) => a[0] - b[0])) {
1583
+ /* build shape union for this status code */
1584
+ const shapes = [];
1585
+ for (const key of keys.sort()) {
1586
+ const entry = meta?.[key];
1587
+ if (entry?.schema) {
1588
+ shapes.push(emitSchemaType(entry.schema, state));
1589
+ }
1590
+ else {
1591
+ shapes.push("null");
1592
+ }
1593
+ }
1594
+ const shapeType = shapes.length === 1 ? shapes[0] : shapes.join(" | ");
1595
+ entries.push(`${status}: ${shapeType}`);
1596
+ }
1597
+ return `{ ${entries.join("; ")} }`;
1598
+ }
1599
+ function emitErrorsCtxType(handler) {
1600
+ if (handler.ek.size === 0)
1601
+ return "";
1602
+ const entries = Array.from(handler.ek)
1603
+ .sort()
1604
+ .map((k) => `${quoteKey(k)}: (opts?: { cause?: unknown; fields?: Record<string, { error_key: string; message: string; path: string }[]>; headers?: Record<string, string>; vars?: Record<string, string | number> }) => HoneyError`);
1605
+ return `readonly errors: { ${entries.join("; ")} }`;
1606
+ }
1607
+ /**
1608
+ * Per-route middleware type additions. The type extractor keys routes by their full path —
1609
+ * basePath included — exactly as the router registers them, so the lookup is exact: a route
1610
+ * with no entry gets the base ctx, never another route's additions.
1611
+ */
1612
+ function resolveRouteMiddleware(routeMiddleware, method, fullPath) {
1613
+ if (!routeMiddleware)
1614
+ return undefined;
1615
+ const key = `${method} ${fullPath}`;
1616
+ return Object.hasOwn(routeMiddleware, key) ? routeMiddleware[key] : undefined;
1617
+ }
1618
+ export function generateTypes(app, options) {
1619
+ const collected = collectRoutes(app, true);
1620
+ const factory = getErrorFactory(app);
1621
+ const errorMeta = getErrorMeta(factory);
1622
+ const baseCtxName = options.baseCtxName ?? "BaseCtx";
1623
+ const hasErrors = collected.some(({ handler }) => handler.ek.size > 0);
1624
+ const lines = [];
1625
+ const importParts = ["HoneyContext", "WithOutput"];
1626
+ if (hasErrors)
1627
+ importParts.push("HoneyError");
1628
+ lines.push(`import type { ${importParts.join(", ")} } from "@lovrozagar/honey"`);
1629
+ lines.push("");
1630
+ const envType = options.inlineEnvType ?? "Record<string, unknown>";
1631
+ const mwPart = options.inlineMiddlewareType ? ` & ${options.inlineMiddlewareType}` : "";
1632
+ if (options.inlineTapsType) {
1633
+ lines.push(`type TapMap = ${options.inlineTapsType}`);
1634
+ lines.push("");
1635
+ }
1636
+ const ctxBase = options.inlineTapsType ? `Omit<HoneyContext<${envType}>, "tap">` : `HoneyContext<${envType}>`;
1637
+ const tapPart = options.inlineTapsType ? " & { tap<K extends keyof TapMap>(key: K, payload: TapMap[K]): void }" : "";
1638
+ lines.push(`export type ${baseCtxName} = ${ctxBase}${mwPart}${tapPart}`);
1639
+ lines.push("");
1640
+ /* group routes by path */
1641
+ const byPath = new Map();
1642
+ for (const { handler, method, path } of collected) {
1643
+ let group = byPath.get(path);
1644
+ if (!group) {
1645
+ group = [];
1646
+ byPath.set(path, group);
1647
+ }
1648
+ group.push({ handler, method: method.toLowerCase() });
1649
+ }
1650
+ /* emit RouteSelector union from route graph */
1651
+ const selectors = new Set();
1652
+ for (const { handler, method, path } of collected) {
1653
+ if (handler._skip || isMetaInternal(handler))
1654
+ continue;
1655
+ selectors.add(`${method} ${path}`);
1656
+ }
1657
+ if (selectors.size > 0) {
1658
+ const sorted = [...selectors].sort();
1659
+ lines.push(`export type RouteSelector = ${sorted.map((s) => JSON.stringify(s)).join(" | ")}`);
1660
+ lines.push("");
1661
+ /* augment the module consumers import — an augmentation of any other name silently applies to nothing */
1662
+ lines.push('declare module "@lovrozagar/honey" {');
1663
+ lines.push("\tinterface HoneyCodegen {");
1664
+ lines.push(`\t\trouteSelector: RouteSelector`);
1665
+ lines.push("\t}");
1666
+ lines.push("}");
1667
+ lines.push("");
1668
+ }
1669
+ /* deduplicate per-route middleware additions into named types,
1670
+ * with sub-property dedup for large repeated property types (e.g. Drizzle DB schemas) */
1671
+ const mwTypeMap = new Map();
1672
+ let mwTypeCounter = 0;
1673
+ /* use structured property data from type-extractor for sub-type dedup */
1674
+ const routeProps = options.routeMiddlewareProps;
1675
+ const propTypeCount = new Map();
1676
+ if (routeProps) {
1677
+ for (const props of Object.values(routeProps)) {
1678
+ for (const prop of props) {
1679
+ if (prop.type.length >= 512) {
1680
+ propTypeCount.set(prop.type, (propTypeCount.get(prop.type) ?? 0) + 1);
1681
+ }
1682
+ }
1683
+ }
1684
+ }
1685
+ /* emit shared sub-type aliases for duplicated property types */
1686
+ const subTypeMap = new Map();
1687
+ let subTypeCounter = 0;
1688
+ for (const [typeStr, count] of propTypeCount) {
1689
+ if (count >= 2) {
1690
+ const alias = `_MW${subTypeCounter++}`;
1691
+ subTypeMap.set(typeStr, alias);
1692
+ lines.push(`type ${alias} = ${typeStr}`);
1693
+ lines.push("");
1694
+ }
1695
+ }
1696
+ function buildMwType(method, path) {
1697
+ const key = `${method} ${path}`;
1698
+ const props = routeProps && Object.hasOwn(routeProps, key) ? routeProps[key] : undefined;
1699
+ if (props?.length) {
1700
+ const entries = props.map((p) => {
1701
+ const typeStr = subTypeMap.get(p.type) ?? p.type;
1702
+ return `${p.name}${p.opt ? "?" : ""}: ${typeStr}`;
1703
+ });
1704
+ return `{ ${entries.join("; ")} }`;
1705
+ }
1706
+ /* fallback to raw string if no structured data */
1707
+ return resolveRouteMiddleware(options.routeMiddleware, method, path);
1708
+ }
1709
+ function dedupeRouteMiddleware(method, path) {
1710
+ const mwType = buildMwType(method, path);
1711
+ if (!mwType)
1712
+ return undefined;
1713
+ let alias = mwTypeMap.get(mwType);
1714
+ if (!alias) {
1715
+ alias = `MwCtx${mwTypeCounter++}`;
1716
+ mwTypeMap.set(mwType, alias);
1717
+ lines.push(`type ${alias} = ${mwType}`);
1718
+ lines.push("");
1719
+ }
1720
+ return alias;
1721
+ }
1722
+ /* pre-scan all routes to emit deduplicated middleware types before Routes */
1723
+ for (const [path, methods] of byPath) {
1724
+ for (const { method } of methods) {
1725
+ dedupeRouteMiddleware(method, path);
1726
+ }
1727
+ }
1728
+ const emitState = createTypeEmitState();
1729
+ const routesLines = [];
1730
+ routesLines.push("export type Routes = {");
1731
+ for (const [path, methods] of byPath) {
1732
+ routesLines.push(`\t${JSON.stringify(path)}: {`);
1733
+ for (const { handler, method } of methods) {
1734
+ const mwType = buildMwType(method, path);
1735
+ const mwAlias = mwType ? mwTypeMap.get(mwType) : undefined;
1736
+ const basePart = mwAlias ? `${baseCtxName} & ${mwAlias}` : baseCtxName;
1737
+ const additions = [];
1738
+ const inputType = emitInputType(handler, emitState);
1739
+ if (inputType !== "{}")
1740
+ additions.push(`input: ${inputType}`);
1741
+ /* the names ctx.params carries at runtime — an unnamed wildcard is "*" */
1742
+ const params = patternParams(parsePattern(path));
1743
+ if (params.length > 0) {
1744
+ const paramEntries = params.map((p) => `${quoteKey(p)}: string`).join("; ");
1745
+ additions.push(`readonly params: { ${paramEntries} }`);
1746
+ }
1747
+ const errorsCtx = emitErrorsCtxType(handler);
1748
+ if (errorsCtx)
1749
+ additions.push(errorsCtx);
1750
+ const ctxType = additions.length === 0 ? basePart : `${basePart} & { ${additions.join("; ")} }`;
1751
+ const errorType = emitErrorType(handler);
1752
+ const metaType = emitMetaType(handler);
1753
+ const outputType = emitOutputType(handler, emitState);
1754
+ routesLines.push(`\t\t${quoteKey(method)}: {`);
1755
+ routesLines.push(`\t\t\tctx: WithOutput<${ctxType}, ${outputType}>`);
1756
+ routesLines.push(`\t\t\terrors: ${errorType}`);
1757
+ /* a mounted sub-app's route keeps its own error factory */
1758
+ const routeFactory = handler.fac ?? factory;
1759
+ const routeErrorMeta = routeFactory === factory ? errorMeta : getErrorMeta(routeFactory);
1760
+ const shapesType = emitErrorShapes(handler, routeErrorMeta, emitState);
1761
+ if (shapesType) {
1762
+ routesLines.push(`\t\t\terrorShapes: ${shapesType}`);
1763
+ const byStatusType = emitErrorsByStatus(handler, routeErrorMeta, routeFactory, emitState);
1764
+ if (byStatusType) {
1765
+ routesLines.push(`\t\t\terrorsByStatus: ${byStatusType}`);
1766
+ }
1767
+ }
1768
+ routesLines.push(`\t\t\tinput: ${inputType}`);
1769
+ routesLines.push(`\t\t\tmeta: ${metaType}`);
1770
+ routesLines.push(`\t\t\toutput: ${outputType}`);
1771
+ routesLines.push("\t\t}");
1772
+ }
1773
+ routesLines.push("\t}");
1774
+ }
1775
+ routesLines.push("}");
1776
+ routesLines.push("");
1777
+ for (const [name, body] of emitState.aliases) {
1778
+ lines.push(`type ${name} = ${body}`);
1779
+ lines.push("");
1780
+ }
1781
+ lines.push(...routesLines);
1782
+ /* route-specific type extractors — services use these instead of typeof app */
1783
+ lines.push("export type RouteCtx<");
1784
+ lines.push("\tTPath extends keyof Routes,");
1785
+ lines.push("\tTMethod extends keyof Routes[TPath] & string");
1786
+ lines.push("> = Routes[TPath][TMethod] extends { ctx: infer C } ? C : never");
1787
+ lines.push("");
1788
+ lines.push("export type RouteInput<");
1789
+ lines.push("\tTPath extends keyof Routes,");
1790
+ lines.push("\tTMethod extends keyof Routes[TPath] & string");
1791
+ lines.push("> = Routes[TPath][TMethod] extends { input: infer I } ? I : never");
1792
+ lines.push("");
1793
+ lines.push("export type RouteOutput<");
1794
+ lines.push("\tTPath extends keyof Routes,");
1795
+ lines.push("\tTMethod extends keyof Routes[TPath] & string");
1796
+ lines.push("> = Routes[TPath][TMethod] extends { output: infer O } ? O : never");
1797
+ lines.push("");
1798
+ lines.push("export type RouteErrors<");
1799
+ lines.push("\tTPath extends keyof Routes,");
1800
+ lines.push("\tTMethod extends keyof Routes[TPath] & string");
1801
+ lines.push("> = Routes[TPath][TMethod] extends { errors: infer E } ? E : never");
1802
+ lines.push("");
1803
+ lines.push("export type RouteErrorShapes<");
1804
+ lines.push("\tTPath extends keyof Routes,");
1805
+ lines.push("\tTMethod extends keyof Routes[TPath] & string");
1806
+ lines.push("> = Routes[TPath][TMethod] extends { errorShapes: infer S } ? S : never");
1807
+ lines.push("");
1808
+ lines.push("export type RouteErrorsByStatus<");
1809
+ lines.push("\tTPath extends keyof Routes,");
1810
+ lines.push("\tTMethod extends keyof Routes[TPath] & string");
1811
+ lines.push("> = Routes[TPath][TMethod] extends { errorsByStatus: infer S } ? S : never");
1812
+ lines.push("");
1813
+ lines.push("export type RouteMeta<");
1814
+ lines.push("\tTPath extends keyof Routes,");
1815
+ lines.push("\tTMethod extends keyof Routes[TPath] & string");
1816
+ lines.push("> = Routes[TPath][TMethod] extends { meta: infer M } ? M : never");
1817
+ lines.push("");
1818
+ return lines.join("\n");
1819
+ }
1820
+ export function mergeSpecs(...specs) {
1821
+ const merged = {
1822
+ info: specs[0]?.info ?? { title: "", version: "" },
1823
+ openapi: specs[0]?.openapi ?? "3.1.0",
1824
+ paths: {},
1825
+ };
1826
+ let mergedComponents;
1827
+ for (const spec of specs) {
1828
+ for (const [path, methods] of Object.entries(spec.paths)) {
1829
+ if (merged.paths[path] === undefined) {
1830
+ merged.paths[path] = {};
1831
+ }
1832
+ for (const [method, operation] of Object.entries(methods)) {
1833
+ if (merged.paths[path][method] !== undefined) {
1834
+ throw new Error(`Merge conflict: duplicate ${method.toUpperCase()} ${path}`);
1835
+ }
1836
+ merged.paths[path][method] = operation;
1837
+ }
1838
+ }
1839
+ if (spec.components) {
1840
+ if (!mergedComponents)
1841
+ mergedComponents = {};
1842
+ if (spec.components.schemas) {
1843
+ if (!mergedComponents.schemas)
1844
+ mergedComponents.schemas = {};
1845
+ for (const [name, schema] of Object.entries(spec.components.schemas)) {
1846
+ if (mergedComponents.schemas[name] !== undefined) {
1847
+ /* allow identical schemas (same content-hash name from same app) */
1848
+ if (canonicalizeSchema(mergedComponents.schemas[name]) !== canonicalizeSchema(schema)) {
1849
+ throw new Error(`Merge conflict: duplicate component schema "${name}"`);
1850
+ }
1851
+ }
1852
+ else {
1853
+ mergedComponents.schemas[name] = schema;
1854
+ }
1855
+ }
1856
+ }
1857
+ if (spec.components.securitySchemes) {
1858
+ if (!mergedComponents.securitySchemes)
1859
+ mergedComponents.securitySchemes = {};
1860
+ for (const [name, scheme] of Object.entries(spec.components.securitySchemes)) {
1861
+ mergedComponents.securitySchemes[name] = scheme;
1862
+ }
1863
+ }
1864
+ }
1865
+ }
1866
+ if (mergedComponents)
1867
+ merged.components = mergedComponents;
1868
+ return merged;
1869
+ }
1870
+ export function scopeSpec(spec, filter) {
1871
+ const result = {
1872
+ info: spec.info,
1873
+ openapi: spec.openapi,
1874
+ paths: {},
1875
+ };
1876
+ const hasFilter = filter.tags !== undefined ||
1877
+ filter.excludeTags !== undefined ||
1878
+ filter.pathPrefix !== undefined ||
1879
+ filter.operationIds !== undefined;
1880
+ if (spec.components)
1881
+ result.components = spec.components;
1882
+ if (!hasFilter) {
1883
+ result.paths = { ...spec.paths };
1884
+ return result;
1885
+ }
1886
+ for (const [path, methods] of Object.entries(spec.paths)) {
1887
+ if (filter.pathPrefix && !path.startsWith(filter.pathPrefix))
1888
+ continue;
1889
+ for (const [method, operation] of Object.entries(methods)) {
1890
+ const op = operation;
1891
+ const opTags = (op.tags ?? []);
1892
+ const opId = op.operationId;
1893
+ if (filter.tags && !opTags.some((t) => filter.tags?.includes(t)))
1894
+ continue;
1895
+ if (filter.excludeTags && opTags.some((t) => filter.excludeTags?.includes(t)))
1896
+ continue;
1897
+ if (filter.operationIds && (!opId || !filter.operationIds.includes(opId)))
1898
+ continue;
1899
+ if (result.paths[path] === undefined) {
1900
+ result.paths[path] = {};
1901
+ }
1902
+ result.paths[path][method] = operation;
1903
+ }
1904
+ }
1905
+ return result;
1906
+ }
1907
+ export function extractOpenApiPathParams(path) {
1908
+ const params = [];
1909
+ /* any name between braces — `{user-id}` is one parameter, not `{user}` and a literal `-id` */
1910
+ const re = /\{([^{}/]+)\}/g;
1911
+ let match = re.exec(path);
1912
+ while (match !== null) {
1913
+ params.push(match[1]);
1914
+ match = re.exec(path);
1915
+ }
1916
+ return params;
1917
+ }
1918
+ export function isSSEOperation(operation) {
1919
+ const responses = operation.responses;
1920
+ if (!responses)
1921
+ return false;
1922
+ for (const response of Object.values(responses)) {
1923
+ const content = response.content;
1924
+ if (content && "text/event-stream" in content)
1925
+ return true;
1926
+ }
1927
+ return false;
1928
+ }
1929
+ /** JSON Schema → TypeScript type string (shim — delegates to IR pipeline). */
1930
+ export function jsonSchemaToTS(schema, depth = 0, binary = "string") {
1931
+ if (!schema || depth > 8)
1932
+ return "unknown";
1933
+ return irToTs(schemaToIR(schema), depth, binary);
1934
+ }
1935
+ /* a file part of a multipart body is sent as a Blob/File; the runtime appends it to FormData as is */
1936
+ const FORM_BINARY_TS = "Blob";
1937
+ /* the TS SDK input field a request body fills, by its media type */
1938
+ function sdkBodyField(body) {
1939
+ if (body.kind === "stream")
1940
+ return "body";
1941
+ if (body.kind === "multipart")
1942
+ return "form";
1943
+ const essence = mediaEssence(body.contentType);
1944
+ if (isJsonMedia(body.contentType))
1945
+ return "json";
1946
+ if (essence === "multipart/form-data" || essence === "application/x-www-form-urlencoded")
1947
+ return "form";
1948
+ return null;
1949
+ }
1950
+ /* extract input type for an operation */
1951
+ function emitSDKInputType(op) {
1952
+ const mandatoryParts = [];
1953
+ const optionalParts = [];
1954
+ /* path params — always required */
1955
+ if (op.params.path.length > 0) {
1956
+ const entries = op.params.path.map((p) => `${sdkSafeName(p.name)}: ${irToTs(p.schema)}`).join("; ");
1957
+ mandatoryParts.push(`params: { ${entries} }`);
1958
+ }
1959
+ /* query params */
1960
+ if (op.params.query.length > 0) {
1961
+ const entries = op.params.query
1962
+ .slice()
1963
+ .sort((a, b) => compareCodeUnits(a.name, b.name))
1964
+ .map((p) => `${sdkSafeName(p.name)}${p.required === true ? "" : "?"}: ${irToTs(p.schema)}`);
1965
+ const searchEntry = `search: { ${entries.join("; ")} }`;
1966
+ if (op.params.query.some((p) => p.required === true))
1967
+ mandatoryParts.push(searchEntry);
1968
+ else
1969
+ optionalParts.push(searchEntry);
1970
+ }
1971
+ /* header and cookie params — typed on top of the free-form per-call `headers` / `cookies` */
1972
+ for (const [declared, field] of [
1973
+ [op.params.header, "headers"],
1974
+ [op.params.cookie ?? [], "cookies"],
1975
+ ]) {
1976
+ if (declared.length === 0)
1977
+ continue;
1978
+ const entries = declared
1979
+ .slice()
1980
+ .sort((a, b) => compareCodeUnits(a.name, b.name))
1981
+ .map((p) => {
1982
+ /* sent as text: a header or cookie value is a string on the wire */
1983
+ const tsType = p.schema.kind === "scalar" && p.schema.enum ? irToTs(p.schema) : "string";
1984
+ return `${quoteKey(p.name)}${p.required === true ? "" : "?"}: ${tsType}`;
1985
+ });
1986
+ const entry = `${field}: { ${entries.join("; ")} }`;
1987
+ if (declared.some((p) => p.required === true))
1988
+ mandatoryParts.push(entry);
1989
+ else
1990
+ optionalParts.push(entry);
1991
+ }
1992
+ /* request body: one input field per kind; the preferred content type of each kind wins */
1993
+ const byField = new Map();
1994
+ for (const body of bodiesOf(op)) {
1995
+ const field = sdkBodyField(body);
1996
+ if (field && !byField.has(field))
1997
+ byField.set(field, body);
1998
+ }
1999
+ /* raw bytes first, then json, then form: the order the fields always had */
2000
+ for (const field of ["body", "json", "form"]) {
2001
+ const body = byField.get(field);
2002
+ if (!body)
2003
+ continue;
2004
+ let entry;
2005
+ if (field === "body")
2006
+ entry = "body: ReadableStream<Uint8Array> | Blob | ArrayBuffer | Uint8Array";
2007
+ else if (field === "json")
2008
+ entry = `json: ${body.kind === "raw" ? irToTs(body.schema) : "unknown"}`;
2009
+ else {
2010
+ const schema = body.kind === "raw" ? body.schema : body.kind === "multipart" ? body.schema : undefined;
2011
+ entry = `form: ${schema ? irToTs(schema, 0, FORM_BINARY_TS) : "Record<string, unknown>"}`;
2012
+ }
2013
+ if (body.required)
2014
+ mandatoryParts.push(entry);
2015
+ else
2016
+ optionalParts.push(entry);
2017
+ }
2018
+ const allParts = [...mandatoryParts, ...optionalParts];
2019
+ const type = allParts.length === 0 ? "{}" : `{ ${allParts.join("; ")} }`;
2020
+ return { hasMandatory: mandatoryParts.length > 0, type };
2021
+ }
2022
+ /* extract response type for an operation */
2023
+ function emitSDKResponseType(op) {
2024
+ /* SSE — shape mirrors runtime _SSEEvent (event/id/retry all optional) */
2025
+ if (op.extensions.sse)
2026
+ return "AsyncIterable<{ data: string; event?: string; id?: string; retry?: number }>";
2027
+ /* collect success response types (2xx) — `default` and `4XX`-style keys are never success */
2028
+ const successTypes = [];
2029
+ for (const [status, response] of Object.entries(op.responses)) {
2030
+ if (!/^2(?:[0-9][0-9]|XX)$/i.test(status))
2031
+ continue;
2032
+ const code = /^2XX$/i.test(status) ? 200 : Number(status);
2033
+ if (code === 204 || code === 205 || !response.contentType) {
2034
+ successTypes.push("null");
2035
+ continue;
2036
+ }
2037
+ /* the same classes the runtime's body parser reads: JSON, text, otherwise bytes */
2038
+ if (isJsonMedia(response.contentType)) {
2039
+ successTypes.push(response.schema ? irToTs(response.schema) : "unknown");
2040
+ }
2041
+ else if (isTextMedia(response.contentType)) {
2042
+ successTypes.push("string");
2043
+ }
2044
+ else {
2045
+ successTypes.push("ArrayBuffer");
2046
+ }
2047
+ }
2048
+ if (successTypes.length === 0)
2049
+ return "void";
2050
+ return [...new Set(successTypes)].join(" | ");
2051
+ }
2052
+ /* detect whether a JSON schema matches the standard error envelope shape */
2053
+ export function isStandardErrEnvelope(schema) {
2054
+ const props = schema.properties;
2055
+ if (!props)
2056
+ return null;
2057
+ const successConst = props.success?.const;
2058
+ if (successConst !== false)
2059
+ return null;
2060
+ const msg = props.message;
2061
+ if (msg?.type !== "string")
2062
+ return null;
2063
+ const sk = props.status_key;
2064
+ if (sk?.type !== "string")
2065
+ return null;
2066
+ const fieldsSchema = props.fields;
2067
+ if (fieldsSchema?.type !== "object")
2068
+ return null;
2069
+ const addl = fieldsSchema?.additionalProperties;
2070
+ if (addl?.type !== "array")
2071
+ return null;
2072
+ const itemSchema = addl?.items;
2073
+ if (itemSchema?.type !== "object")
2074
+ return null;
2075
+ const itemProps = itemSchema?.properties;
2076
+ for (const k of ["error_key", "message", "path"]) {
2077
+ if (itemProps?.[k]?.type !== "string")
2078
+ return null;
2079
+ }
2080
+ const statusEnum = props.status?.enum;
2081
+ if (!Array.isArray(statusEnum) || statusEnum.length !== 1)
2082
+ return null;
2083
+ const statusVal = statusEnum[0];
2084
+ if (typeof statusVal !== "number")
2085
+ return null;
2086
+ const errKeyEnum = props.error_key?.enum;
2087
+ if (!Array.isArray(errKeyEnum) || errKeyEnum.length === 0)
2088
+ return null;
2089
+ if (!errKeyEnum.every((k) => typeof k === "string"))
2090
+ return null;
2091
+ return { keys: errKeyEnum, status: statusVal };
2092
+ }
2093
+ /* extract error types by status code for an operation (non-2xx responses) */
2094
+ function emitSDKErrorsByStatusType(op, resolve) {
2095
+ const entries = [];
2096
+ for (const [status, response] of Object.entries(op.responses)) {
2097
+ /* only concrete error statuses: `default` and `4XX` have no single status to key on */
2098
+ if (!/^[45][0-9][0-9]$/.test(status))
2099
+ continue;
2100
+ if (!response.schema || !response.contentType || !isJsonMedia(response.contentType))
2101
+ continue;
2102
+ const code = Number(status);
2103
+ const envelope = irErrorEnvelope(response.schema, resolve);
2104
+ if (envelope) {
2105
+ const keyUnion = envelope.keys.map((k) => JSON.stringify(k)).join(" | ");
2106
+ entries.push(`${code}: _ErrEnvelope<${envelope.status}, ${keyUnion}>`);
2107
+ }
2108
+ else {
2109
+ entries.push(`${code}: ${irToTs(response.schema)}`);
2110
+ }
2111
+ }
2112
+ if (entries.length === 0)
2113
+ return null;
2114
+ return `{ ${entries.join("; ")} }`;
2115
+ }
2116
+ /*
2117
+ * Whether a response type string is worth hoisting into a `_Res\d` alias.
2118
+ * Skip primitives, void/null, ArrayBuffer, and plain strings — only object or
2119
+ * array shapes benefit from dedupe.
2120
+ */
2121
+ function isHoistableResponseType(t) {
2122
+ if (!t)
2123
+ return false;
2124
+ if (t === "void" || t === "null" || t === "string" || t === "ArrayBuffer")
2125
+ return false;
2126
+ const head = t.trimStart()[0];
2127
+ return head === "{" || head === "[";
2128
+ }
2129
+ function buildMethodSig(m, inpAliases, errAliases, resAliases) {
2130
+ let suffixType;
2131
+ if (m.ws)
2132
+ suffixType = "_WsOpts";
2133
+ else if (m.sse)
2134
+ suffixType = "_SseOpts";
2135
+ else
2136
+ suffixType = "_HttpOpts";
2137
+ const inpAlias = inpAliases.get(m.inputType);
2138
+ const resolvedInp = inpAlias ? `_Expand<${inpAlias}>` : m.inputType;
2139
+ const fullInputType = resolvedInp === "{}" ? suffixType : `${resolvedInp} & ${suffixType}`;
2140
+ const inputArg = `${m.inputHasMandatory ? "input" : "input?"}: ${fullInputType}`;
2141
+ let resolvedErr = null;
2142
+ if (m.errorsByStatusType) {
2143
+ const errAlias = errAliases.get(m.errorsByStatusType);
2144
+ resolvedErr = errAlias ? `_Expand<${errAlias}>` : m.errorsByStatusType;
2145
+ }
2146
+ const resAlias = resAliases.get(m.responseType);
2147
+ const resolvedRes = resAlias ? `_Expand<${resAlias}>` : m.responseType;
2148
+ let returnType;
2149
+ if (m.ws)
2150
+ returnType = "TypedWebSocket";
2151
+ else if (m.sse)
2152
+ returnType = m.responseType;
2153
+ else if (resolvedErr)
2154
+ returnType = `TThrow extends true ? Promise<${resolvedRes}> : Promise<SDKResult<${resolvedRes}, ${resolvedErr}>>`;
2155
+ else
2156
+ returnType = `TThrow extends true ? Promise<${resolvedRes}> : Promise<SDKResult<${resolvedRes}>>`;
2157
+ return { inputArg, returnType };
2158
+ }
2159
+ function buildTypeAliases(types, prefix, minCount) {
2160
+ const counts = new Map();
2161
+ for (const t of types)
2162
+ counts.set(t, (counts.get(t) ?? 0) + 1);
2163
+ const aliases = new Map();
2164
+ const lines = [];
2165
+ let idx = 0;
2166
+ for (const [typeStr, count] of [...counts.entries()].sort(([a], [b]) => compareCodeUnits(a, b))) {
2167
+ if (count >= minCount || typeStr.length > 200) {
2168
+ const alias = `${prefix}${idx++}`;
2169
+ aliases.set(typeStr, alias);
2170
+ lines.push(`type ${alias} = ${typeStr}`);
2171
+ }
2172
+ }
2173
+ return { aliases, lines };
2174
+ }
2175
+ /* TS reserved words that must be quoted when used as object/interface property keys */
2176
+ const SDK_TS_RESERVED = new Set([
2177
+ "break",
2178
+ "case",
2179
+ "catch",
2180
+ "class",
2181
+ "const",
2182
+ "continue",
2183
+ "debugger",
2184
+ "default",
2185
+ "delete",
2186
+ "do",
2187
+ "else",
2188
+ "enum",
2189
+ "export",
2190
+ "extends",
2191
+ "false",
2192
+ "finally",
2193
+ "for",
2194
+ "function",
2195
+ "if",
2196
+ "import",
2197
+ "in",
2198
+ "instanceof",
2199
+ "new",
2200
+ "null",
2201
+ "return",
2202
+ "super",
2203
+ "switch",
2204
+ "this",
2205
+ "throw",
2206
+ "true",
2207
+ "try",
2208
+ "typeof",
2209
+ "var",
2210
+ "void",
2211
+ "while",
2212
+ "with",
2213
+ "yield",
2214
+ "let",
2215
+ "static",
2216
+ "implements",
2217
+ "interface",
2218
+ "package",
2219
+ "private",
2220
+ "protected",
2221
+ "public",
2222
+ "abstract",
2223
+ "as",
2224
+ "async",
2225
+ "await",
2226
+ "constructor",
2227
+ "declare",
2228
+ "from",
2229
+ "get",
2230
+ "infer",
2231
+ "is",
2232
+ "keyof",
2233
+ "module",
2234
+ "namespace",
2235
+ "never",
2236
+ "of",
2237
+ "readonly",
2238
+ "require",
2239
+ "set",
2240
+ "satisfies",
2241
+ "symbol",
2242
+ "type",
2243
+ "unique",
2244
+ "unknown",
2245
+ "override",
2246
+ ]);
2247
+ function sdkSafeName(name) {
2248
+ if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) || SDK_TS_RESERVED.has(name)) {
2249
+ return JSON.stringify(name);
2250
+ }
2251
+ return name;
2252
+ }
2253
+ function buildSDKTypes(sdkName, sdkMethods, tree, methodLookup) {
2254
+ const n = sdkName;
2255
+ const l = [];
2256
+ /* shared error aliases (reduce repetition for standard envelope shape) */
2257
+ l.push("type _ErrField = { error_key: string; message: string; path: string }");
2258
+ l.push("type _ErrEnvelope<TStatus extends number, TKey extends string> = {");
2259
+ l.push("\terror_key: TKey");
2260
+ l.push("\tfields: Record<string, _ErrField[]>");
2261
+ l.push("\tmessage: string");
2262
+ l.push("\tstatus: TStatus");
2263
+ l.push("\tstatus_key: string");
2264
+ l.push("\tsuccess: false");
2265
+ l.push("}");
2266
+ l.push("");
2267
+ /* dedupe repeated errorsByStatusType strings — the primary source of file bloat */
2268
+ const { aliases: errAliases, lines: errLines } = buildTypeAliases(sdkMethods.flatMap((m) => (m.errorsByStatusType ? [m.errorsByStatusType] : [])), "_Errs", 2);
2269
+ if (errLines.length > 0) {
2270
+ for (const line of errLines)
2271
+ l.push(line);
2272
+ l.push("");
2273
+ }
2274
+ /*
2275
+ * Dedupe response type shapes used by non-SSE/WS methods. Each method inlines
2276
+ * its response type TWICE (throw branch + safe branch), so hoisting once per
2277
+ * shape roughly halves the per-method cost. Hoist when count >= 2 OR when a
2278
+ * single literal is long enough (>200 chars) to dominate the line.
2279
+ */
2280
+ const { aliases: resAliases, lines: resLines } = buildTypeAliases(sdkMethods.filter((m) => !m.ws && !m.sse && isHoistableResponseType(m.responseType)).map((m) => m.responseType), "_Res", 2);
2281
+ const { aliases: inpAliases, lines: inpLines } = buildTypeAliases(sdkMethods.filter((m) => m.inputType !== "{}" && isHoistableResponseType(m.inputType)).map((m) => m.inputType), "_Inp", 2);
2282
+ if (resLines.length > 0 || errAliases.size > 0 || inpLines.length > 0) {
2283
+ /*
2284
+ * _Expand forces TS to eagerly resolve aliases in hover tooltips. Without
2285
+ * it, `const b = await sdk.x.y(...)` would show `SDKResult<_Res33, _Errs0>`
2286
+ * instead of the full shape. The mapped type + `& {}` intersection is a
2287
+ * well-known trick: TS evaluates the mapped type for display but keeps the
2288
+ * emitted reference compact, so byte-dedupe is preserved. Two levels deep
2289
+ * so error records (`{ 400: _ErrEnvelope<...>; ... }`) expand their
2290
+ * envelope instantiations too, not just the top-level status keys.
2291
+ */
2292
+ l.push("type _ExpandShallow<T> = T extends object ? { [K in keyof T]: T[K] } & {} : T");
2293
+ l.push("type _Expand<T> = T extends object ? { [K in keyof T]: _ExpandShallow<T[K]> } & {} : T");
2294
+ for (const line of resLines)
2295
+ l.push(line);
2296
+ for (const line of inpLines)
2297
+ l.push(line);
2298
+ l.push("");
2299
+ }
2300
+ /* SDKResult — typed branch trusts TErrorsByStatus to cover every error status (no unknown fallback) */
2301
+ l.push("export type SDKResult<T, TErrorsByStatus = never> =");
2302
+ l.push("\t| { data: T; error: null; response: Response; status: number }");
2303
+ l.push("\t| ([TErrorsByStatus] extends [never]");
2304
+ l.push("\t\t? { data: null; error: unknown; response: Response; status: number }");
2305
+ l.push("\t\t: { [S in keyof TErrorsByStatus & number]: { data: null; error: TErrorsByStatus[S]; response: Response; status: S } }[keyof TErrorsByStatus & number])");
2306
+ l.push("");
2307
+ /* TypedWebSocket */
2308
+ l.push("export type TypedWebSocket = {");
2309
+ l.push("\tclose(code?: number, reason?: string): void");
2310
+ l.push('\toff(event: "close" | "error" | "message" | "open", handler: (...args: never[]) => void): void');
2311
+ l.push('\ton(event: "close", handler: (code: number, reason: string) => void): void');
2312
+ l.push('\ton(event: "error", handler: (error: unknown) => void): void');
2313
+ l.push('\ton(event: "message", handler: (data: string) => void): void');
2314
+ l.push('\ton(event: "open", handler: () => void): void');
2315
+ l.push("\treadonly readyState: number");
2316
+ l.push("\tsend(data: ArrayBuffer | ArrayBufferView | object | string): void");
2317
+ l.push("}");
2318
+ l.push("");
2319
+ /* config type */
2320
+ l.push(`export type ${n}Config<TThrow extends boolean = false> = {`);
2321
+ l.push("\tbaseURL: string");
2322
+ l.push("\tbuildSearchParams?: (query: Record<string, unknown>) => URLSearchParams");
2323
+ l.push("\tcredentials?: RequestCredentials");
2324
+ l.push("\tfetch?: typeof fetch");
2325
+ l.push(`\theaders?: Record<string, string> | ((ctx: { method: string; path: string }) => Record<string, string | undefined> | Promise<Record<string, string | undefined>>)`);
2326
+ l.push("\tinvalidation?: { maxSourcesPerTarget?: number; staleMaxEntries?: number; staleTime: number }");
2327
+ l.push("\tmaxErrorMessageChars?: number");
2328
+ l.push("\tmode?: RequestMode");
2329
+ l.push(`\tonRequest?: Array<(ctx: { body?: BodyInit; headers: Headers; invalidatedBy?: string[]; isStale?: boolean; method: string; path: string; selector?: string; state: Record<string, unknown>; url: string }) => void | Promise<void>>`);
2330
+ l.push(`\tonResponse?: Array<(ctx: { invalidatedBy?: string[]; isRetry: boolean; isStale?: boolean; method: string; path: string; request: Request; response: Response; retry: () => Promise<Response>; selector?: string; state: Record<string, unknown>; url: string }) => Response | undefined | Promise<Response | undefined>>`);
2331
+ l.push("\tonAuthExpired?: (ctx: { rejectedToken: string | null }) => Promise<string | null>");
2332
+ l.push("\tauthHeaderName?: string");
2333
+ l.push("\tauthHeaderPrefix?: string");
2334
+ l.push("\tonLog?: (entry: _LogEntry) => void");
2335
+ l.push('\tredirect?: "error" | "follow" | "manual" | "same-origin"');
2336
+ l.push("\tsortSearchParams?: boolean");
2337
+ l.push("\tsseMaxBufferChars?: number");
2338
+ l.push("\tstate?: Record<string, unknown>");
2339
+ l.push("\tthrowOnError?: TThrow");
2340
+ l.push("\ttimeout?: number");
2341
+ l.push("}");
2342
+ l.push("");
2343
+ l.push(`export type _LogEntry = { level: "debug" | "info" | "warn" | "error"; event: "request_start" | "response_received" | "error" | "hook_executed"; operation: string; duration_ms: number; status?: number; error?: unknown }`);
2344
+ l.push("export type _HttpOpts = { cookies?: Record<string, string>; headers?: Record<string, string>; idempotencyKey?: string; signal?: AbortSignal; timeout?: number }");
2345
+ l.push("export type _SseOpts = { cookies?: Record<string, string>; headers?: Record<string, string>; lastEventId?: string; signal?: AbortSignal; timeout?: number }");
2346
+ l.push("export type _WsOpts = { protocols?: string | string[]; reconnectToken?: string }");
2347
+ l.push("");
2348
+ /* recursive interface emitter — walks IRNamespace tree */
2349
+ function emitNsInterface(ns, indent) {
2350
+ const methods = methodsOf(ns);
2351
+ const namespaces = namespacesOf(ns);
2352
+ for (const [name, op] of methods) {
2353
+ const m = methodLookup.get(op.id);
2354
+ if (!m)
2355
+ continue;
2356
+ /* #R6-27: single _call action → promote resource to callable at top level */
2357
+ if (name === "_call")
2358
+ continue; /* handled by namespace _call promotion below */
2359
+ const { inputArg, returnType } = buildMethodSig(m, inpAliases, errAliases, resAliases);
2360
+ l.push(`${indent}${sdkSafeName(name)}(${inputArg}): ${returnType}`);
2361
+ }
2362
+ for (const [name, childNs] of namespaces) {
2363
+ const childMethods = methodsOf(childNs);
2364
+ /* #R6-27: single _call method in namespace → promote namespace to callable */
2365
+ if (childMethods.length === 1 && childMethods[0] && childMethods[0][0] === "_call") {
2366
+ const [, op] = childMethods[0];
2367
+ const m = methodLookup.get(op.id);
2368
+ if (m) {
2369
+ const { inputArg, returnType } = buildMethodSig(m, inpAliases, errAliases, resAliases);
2370
+ l.push(`${indent}${sdkSafeName(name)}(${inputArg}): ${returnType}`);
2371
+ continue;
2372
+ }
2373
+ }
2374
+ l.push(`${indent}${sdkSafeName(name)}: {`);
2375
+ emitNsInterface(childNs, `${indent}\t`);
2376
+ l.push(`${indent}}`);
2377
+ }
2378
+ }
2379
+ l.push(`export interface ${n}<TThrow extends boolean = false> {`);
2380
+ l.push("\tstate: Record<string, unknown>");
2381
+ l.push("\tdispose(): void");
2382
+ emitNsInterface(tree, "\t");
2383
+ l.push("}");
2384
+ l.push("");
2385
+ return l.join("\n");
2386
+ }
2387
+ function emitNestedMapNode(node, indent) {
2388
+ if (node.kind === "leaf") {
2389
+ const entry = node.entry;
2390
+ const parts = [`method: ${JSON.stringify(entry.method)}`, `path: ${JSON.stringify(entry.path)}`];
2391
+ if (entry.params)
2392
+ parts.push(`params: ${JSON.stringify(entry.params)}`);
2393
+ if (entry.sse)
2394
+ parts.push("sse: true");
2395
+ if (entry.wildcard)
2396
+ parts.push(`wildcard: ${JSON.stringify(entry.wildcard)}`);
2397
+ if (entry.ws)
2398
+ parts.push("ws: true");
2399
+ if (entry.idempotent)
2400
+ parts.push("idempotent: true");
2401
+ if (entry.invalidate)
2402
+ parts.push(`invalidate: ${JSON.stringify(entry.invalidate)}`);
2403
+ return [`{ ${parts.join(", ")} }`];
2404
+ }
2405
+ const lines = ["{"];
2406
+ for (const [key, child] of [...node.children.entries()].sort(([a], [b]) => compareCodeUnits(a, b))) {
2407
+ const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
2408
+ const childLines = emitNestedMapNode(child, `${indent}\t`);
2409
+ if (childLines.length === 1) {
2410
+ lines.push(`${indent}\t${safeKey}: ${childLines[0]},`);
2411
+ }
2412
+ else {
2413
+ lines.push(`${indent}\t${safeKey}: ${childLines[0]}`);
2414
+ for (let i = 1; i < childLines.length - 1; i++)
2415
+ lines.push(childLines[i] ?? "");
2416
+ lines.push(`${indent}\t${childLines[childLines.length - 1]},`);
2417
+ }
2418
+ }
2419
+ lines.push(`${indent}}`);
2420
+ return lines;
2421
+ }
2422
+ function buildSDKMap(nestedMap) {
2423
+ const l = [];
2424
+ l.push("export const serviceMap = {");
2425
+ for (const [key, node] of [...nestedMap.entries()].sort(([a], [b]) => compareCodeUnits(a, b))) {
2426
+ const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
2427
+ const childLines = emitNestedMapNode(node, "\t");
2428
+ if (childLines.length === 1) {
2429
+ l.push(`\t${safeKey}: ${childLines[0]},`);
2430
+ }
2431
+ else {
2432
+ l.push(`\t${safeKey}: ${childLines[0]}`);
2433
+ for (let i = 1; i < childLines.length - 1; i++)
2434
+ l.push(childLines[i] ?? "");
2435
+ l.push(`\t${childLines[childLines.length - 1]},`);
2436
+ }
2437
+ }
2438
+ l.push("} as const");
2439
+ l.push("");
2440
+ return l.join("\n");
2441
+ }
2442
+ function buildSDKIndex(sdkName, stem) {
2443
+ const n = sdkName;
2444
+ const l = [];
2445
+ const errorNames = ERROR_EXPORT_NAMES.join(", ");
2446
+ l.push(`import { ${n} as _${n}Impl, ${errorNames} } from "./${stem}.client.gen"`);
2447
+ l.push(`import type { ${n} as _${n}Interface } from "./${stem}.types.gen"`);
2448
+ l.push("");
2449
+ l.push(`/* declaration merge: Proxy-based class acquires typed resource methods */`);
2450
+ l.push(`class ${n}<TThrow extends boolean = false> extends _${n}Impl<TThrow> {}`);
2451
+ l.push(`interface ${n}<TThrow extends boolean = false> extends _${n}Interface<TThrow> {}`);
2452
+ l.push("");
2453
+ l.push(`export { ${n}, ${errorNames} }`);
2454
+ l.push(`export { serviceMap } from "./${stem}.map.gen"`);
2455
+ l.push(`export type { ${n}Config, SDKResult, TypedWebSocket } from "./${stem}.types.gen"`);
2456
+ l.push("");
2457
+ return l.join("\n");
2458
+ }
2459
+ function buildSDKClient(sdkName, stem) {
2460
+ const n = sdkName;
2461
+ return [
2462
+ sdkClientHeader(n, stem),
2463
+ sdkClientTypes(),
2464
+ sdkClientClientError(),
2465
+ sdkClientClass(n),
2466
+ sdkClientConstructor(n),
2467
+ sdkClientProxy(n),
2468
+ sdkClientInterpolatePath(),
2469
+ sdkClientToColonParams(),
2470
+ sdkClientResolveInvalidationTargets(),
2471
+ sdkClientPathMatchesPattern(),
2472
+ sdkClientLookupStale(),
2473
+ sdkClientCreateTypedWebSocket(),
2474
+ sdkClientSerializeSearch(),
2475
+ sdkClientResolveBaseURL(),
2476
+ sdkClientBuildURL(),
2477
+ sdkClientNewRequestId(),
2478
+ sdkClientBuildHeaders(),
2479
+ sdkClientDoRequest(),
2480
+ sdkClientParseBody(),
2481
+ sdkClientParseErrorBody(),
2482
+ sdkClientParseAsClientError(),
2483
+ sdkClientBuildSignal(),
2484
+ sdkClientRequestThrow(),
2485
+ sdkClientRequestSafe(),
2486
+ sdkClientRequest(),
2487
+ sdkClientRequestSSE(),
2488
+ sdkClientConnectWS(),
2489
+ sdkClientBuildRequestMeta(),
2490
+ sdkClientMarkStale(),
2491
+ sdkClientClearStale(),
2492
+ sdkClientDispose(),
2493
+ sdkClientDoSSE(),
2494
+ sdkClientParseSSEBlock(),
2495
+ sdkClientFooter(),
2496
+ ].join("");
2497
+ }
2498
+ function buildSDKRuntime() {
2499
+ return `/* ------------------------------------------------------------------ */
2500
+ /* ServerFrame (inlined from protocol — zero external imports) */
2501
+ /* ------------------------------------------------------------------ */
2502
+
2503
+ export type ServerFrame =
2504
+ \t| { data: unknown; id: number; t: "msg" }
2505
+ \t| { t: "pong" }
2506
+ \t| { reason: string; t: "bye" }
2507
+ \t| { reconnectToken: string; t: "ready" }
2508
+
2509
+ /* ------------------------------------------------------------------ */
2510
+ /* Error hierarchy */
2511
+ /* ------------------------------------------------------------------ */
2512
+
2513
+ export class RealtimeError extends Error {
2514
+ \treadonly reason: string
2515
+
2516
+ \tconstructor(message: string, reason: string) {
2517
+ \t\tsuper(message)
2518
+ \t\tthis.reason = reason
2519
+ \t\tthis.name = "RealtimeError"
2520
+ \t}
2521
+ }
2522
+
2523
+ export class RealtimeConnectError extends RealtimeError {
2524
+ \tconstructor(message: string, reason: string) {
2525
+ \t\tsuper(message, reason)
2526
+ \t\tthis.name = "RealtimeConnectError"
2527
+ \t}
2528
+ }
2529
+
2530
+ export class RealtimeAuthError extends RealtimeError {
2531
+ \tconstructor(message: string, reason: string) {
2532
+ \t\tsuper(message, reason)
2533
+ \t\tthis.name = "RealtimeAuthError"
2534
+ \t}
2535
+ }
2536
+
2537
+ export class RealtimeKickedError extends RealtimeError {
2538
+ \tconstructor(message: string, reason: string) {
2539
+ \t\tsuper(message, reason)
2540
+ \t\tthis.name = "RealtimeKickedError"
2541
+ \t}
2542
+ }
2543
+
2544
+ export class RealtimeAbortError extends RealtimeError {
2545
+ \tconstructor(message: string, reason: string) {
2546
+ \t\tsuper(message, reason)
2547
+ \t\tthis.name = "RealtimeAbortError"
2548
+ \t}
2549
+ }
2550
+
2551
+ /* ------------------------------------------------------------------ */
2552
+ /* Types */
2553
+ /* ------------------------------------------------------------------ */
2554
+
2555
+ export type Transport = "ws" | "sse" | "longpoll"
2556
+ export type ConnectionState = "idle" | "connecting" | "connected" | "draining" | "reconnecting" | "closed"
2557
+
2558
+ export type TransportAdapter = {
2559
+ \tconnect(url: string, opts: TransportOpts): TransportConnection
2560
+ }
2561
+
2562
+ export type TransportConnection = {
2563
+ \tsend(data: string): void
2564
+ \tclose(): void
2565
+ \tonFrame: (frame: ServerFrame) => void
2566
+ \tonClose: (reason: string) => void
2567
+ \tonError: (err: unknown) => void
2568
+ }
2569
+
2570
+ export type TransportOpts = {
2571
+ \tsignal?: AbortSignal
2572
+ \theaders?: Record<string, string>
2573
+ \tlastId?: number
2574
+ \treconnectToken?: string
2575
+ }
2576
+
2577
+ /* ------------------------------------------------------------------ */
2578
+ /* KeepaliveLoop */
2579
+ /* ------------------------------------------------------------------ */
2580
+
2581
+ export function createKeepaliveLoop(opts: {
2582
+ \ttransport: Transport
2583
+ \tsendPing: () => void
2584
+ \tonDead: () => void
2585
+ \tinterval?: number
2586
+ \ttimeout?: number
2587
+ }): { start(): void; stop(): void; onPong(): void; onFrame(): void } {
2588
+ \tconst interval = opts.interval ?? 25_000
2589
+ \tconst timeout = opts.timeout ?? 60_000
2590
+ \tconst transport = opts.transport
2591
+
2592
+ \tlet pingTimer: ReturnType<typeof setInterval> | null = null
2593
+ \tlet deadTimer: ReturnType<typeof setTimeout> | null = null
2594
+
2595
+ \tfunction clearAll(): void {
2596
+ \t\tif (pingTimer !== null) {
2597
+ \t\t\tclearInterval(pingTimer)
2598
+ \t\t\tpingTimer = null
2599
+ \t\t}
2600
+ \t\tif (deadTimer !== null) {
2601
+ \t\t\tclearTimeout(deadTimer)
2602
+ \t\t\tdeadTimer = null
2603
+ \t\t}
2604
+ \t}
2605
+
2606
+ \tfunction resetDeadTimer(): void {
2607
+ \t\tif (deadTimer !== null) {
2608
+ \t\t\tclearTimeout(deadTimer)
2609
+ \t\t}
2610
+ \t\tdeadTimer = setTimeout(() => {
2611
+ \t\t\topts.onDead()
2612
+ \t\t}, timeout)
2613
+ \t}
2614
+
2615
+ \tfunction start(): void {
2616
+ \t\tclearAll()
2617
+ \t\tif (transport === "longpoll") return
2618
+ \t\tif (transport === "ws") {
2619
+ \t\t\tpingTimer = setInterval(() => {
2620
+ \t\t\t\topts.sendPing()
2621
+ \t\t\t}, interval)
2622
+ \t\t\tresetDeadTimer()
2623
+ \t\t\treturn
2624
+ \t\t}
2625
+ \t\tif (transport === "sse") {
2626
+ \t\t\tresetDeadTimer()
2627
+ \t\t}
2628
+ \t}
2629
+
2630
+ \tfunction stop(): void {
2631
+ \t\tclearAll()
2632
+ \t}
2633
+
2634
+ \tfunction onPong(): void {
2635
+ \t\tif (deadTimer !== null) {
2636
+ \t\t\tresetDeadTimer()
2637
+ \t\t}
2638
+ \t}
2639
+
2640
+ \tfunction onFrame(): void {
2641
+ \t\tif (transport === "sse" && deadTimer !== null) {
2642
+ \t\t\tresetDeadTimer()
2643
+ \t\t}
2644
+ \t}
2645
+
2646
+ \treturn { onFrame, onPong, start, stop }
2647
+ }
2648
+
2649
+ /* ------------------------------------------------------------------ */
2650
+ /* FallbackChain */
2651
+ /* ------------------------------------------------------------------ */
2652
+
2653
+ const DEFAULT_FALLBACK_TIMEOUT = 3000
2654
+
2655
+ export function createFallbackChain(opts: {
2656
+ \ttransports: TransportAdapter[]
2657
+ \ttimeout?: number
2658
+ }): {
2659
+ \tconnect(url: string, transportOpts: TransportOpts): Promise<{ conn: TransportConnection; transport: Transport }>
2660
+ \treadonly provenTransport: Transport | null
2661
+ } {
2662
+ \tconst timeout = opts.timeout ?? DEFAULT_FALLBACK_TIMEOUT
2663
+ \tlet proven: Transport | null = null
2664
+ \tlet provenIndexStore: number | null = null
2665
+
2666
+ \tfunction tryTransport(
2667
+ \t\tadapter: TransportAdapter,
2668
+ \t\turl: string,
2669
+ \t\ttransportOpts: TransportOpts,
2670
+ \t): Promise<TransportConnection> {
2671
+ \t\treturn new Promise((resolve, reject) => {
2672
+ \t\t\tconst conn = adapter.connect(url, transportOpts)
2673
+
2674
+ \t\t\tconst timer = setTimeout(() => {
2675
+ \t\t\t\tconn.close()
2676
+ \t\t\t\treject(new Error("timeout"))
2677
+ \t\t\t}, timeout)
2678
+
2679
+ \t\t\tconst originalOnFrame = conn.onFrame
2680
+ \t\t\tconn.onFrame = (frame: ServerFrame) => {
2681
+ \t\t\t\tif (frame.t === "ready") {
2682
+ \t\t\t\t\tclearTimeout(timer)
2683
+ \t\t\t\t\tconn.onFrame = originalOnFrame
2684
+ \t\t\t\t\tresolve(conn)
2685
+ \t\t\t\t} else {
2686
+ \t\t\t\t\toriginalOnFrame(frame)
2687
+ \t\t\t\t}
2688
+ \t\t\t}
2689
+
2690
+ \t\t\tconn.onError = (err: unknown) => {
2691
+ \t\t\t\tclearTimeout(timer)
2692
+ \t\t\t\tconn.close()
2693
+ \t\t\t\treject(err)
2694
+ \t\t\t}
2695
+
2696
+ \t\t\tconn.onClose = (reason: string) => {
2697
+ \t\t\t\tclearTimeout(timer)
2698
+ \t\t\t\treject(new Error(reason))
2699
+ \t\t\t}
2700
+ \t\t})
2701
+ \t}
2702
+
2703
+ \tasync function connectInternal(
2704
+ \t\turl: string,
2705
+ \t\ttransportOpts: TransportOpts,
2706
+ \t\ttransports: TransportAdapter[],
2707
+ \t): Promise<{ conn: TransportConnection; transport: Transport }> {
2708
+ \t\tif (proven !== null) {
2709
+ \t\t\tif (provenIndexStore !== null && provenIndexStore < transports.length) {
2710
+ \t\t\t\ttry {
2711
+ \t\t\t\t\tconst conn = await tryTransport(transports[provenIndexStore], url, transportOpts)
2712
+ \t\t\t\t\treturn { conn, transport: proven }
2713
+ \t\t\t\t} catch {
2714
+ \t\t\t\t\t/* Proven transport failed — fall through to full chain */
2715
+ \t\t\t\t}
2716
+ \t\t\t}
2717
+ \t\t}
2718
+
2719
+ \t\tfor (let i = 0; i < transports.length; i++) {
2720
+ \t\t\ttry {
2721
+ \t\t\t\tconst conn = await tryTransport(transports[i], url, transportOpts)
2722
+ \t\t\t\tlet transportName: Transport = "longpoll"
2723
+ \t\t\t\tif (i === 0) transportName = "ws"
2724
+ \t\t\t\telse if (i === 1) transportName = "sse"
2725
+ \t\t\t\tproven = transportName
2726
+ \t\t\t\tprovenIndexStore = i
2727
+ \t\t\t\treturn { conn, transport: transportName }
2728
+ \t\t\t} catch {
2729
+ \t\t\t\t/* Try next */
2730
+ \t\t\t}
2731
+ \t\t}
2732
+
2733
+ \t\tthrow new RealtimeConnectError("All transports failed", "all_failed")
2734
+ \t}
2735
+
2736
+ \tfunction connect(
2737
+ \t\turl: string,
2738
+ \t\ttransportOpts: TransportOpts,
2739
+ \t): Promise<{ conn: TransportConnection; transport: Transport }> {
2740
+ \t\tconst transports = opts.transports
2741
+ \t\tif (transports.length === 0) {
2742
+ \t\t\tconst p = Promise.reject(new RealtimeConnectError("No transports configured", "no_transports"))
2743
+ \t\t\tp.catch(() => {})
2744
+ \t\t\treturn p
2745
+ \t\t}
2746
+ \t\tconst p = connectInternal(url, transportOpts, transports)
2747
+ \t\tp.catch(() => {})
2748
+ \t\treturn p
2749
+ \t}
2750
+
2751
+ \treturn {
2752
+ \t\tconnect,
2753
+ \t\tget provenTransport() {
2754
+ \t\t\treturn proven
2755
+ \t\t},
2756
+ \t}
2757
+ }
2758
+
2759
+ /* ------------------------------------------------------------------ */
2760
+ /* ResumableConnection */
2761
+ /* ------------------------------------------------------------------ */
2762
+
2763
+ export type ResumableConnectionOpts = {
2764
+ \turl: string
2765
+ \ttransports: TransportAdapter[]
2766
+ \ttoken?: () => string | Promise<string>
2767
+ \t/** 401 on a request: return a fresh token to retry it once (null = give up). \`rejectedToken\` is the token that failed, so concurrent 401s can share one refresh. */\n\tonAuthExpired?: (ctx: { rejectedToken: string | null }) => Promise<string | null>
2768
+ \tonReconnecting?: (attempt: number, transport: Transport) => void
2769
+ \tonReconnected?: () => void
2770
+ \tsignal?: AbortSignal
2771
+ \tkeepaliveInterval?: number
2772
+ \treconnectDelayMs?: number
2773
+ \tmaxReconnectAttempts?: number
2774
+ \tfallbackTimeout?: number
2775
+ \theaders?: Record<string, string>
2776
+ \treconnectToken?: string
2777
+ \tlastId?: number
2778
+ }
2779
+
2780
+ type QueueEntry =
2781
+ \t| { type: "value"; value: unknown }
2782
+ \t| { type: "done" }
2783
+ \t| { type: "error"; error: unknown }
2784
+
2785
+ export function createResumableConnection(opts: ResumableConnectionOpts): {
2786
+ \treadonly state: ConnectionState
2787
+ \treadonly provenTransport: Transport | null
2788
+ \tsend(data: unknown): void
2789
+ \tclose(reason?: string): void
2790
+ \t[Symbol.asyncIterator](): AsyncIterableIterator<unknown>
2791
+ } {
2792
+ \tconst reconnectDelayMs = opts.reconnectDelayMs ?? 1000
2793
+ \tconst maxReconnectAttempts = opts.maxReconnectAttempts ?? 5
2794
+ \tconst fallbackTimeout = opts.fallbackTimeout ?? 3000
2795
+ \tlet state: ConnectionState = "idle"
2796
+ \tlet currentConn: TransportConnection | null = null
2797
+ \tlet reconnectAttempts = 0
2798
+ \tlet provenIndex: number | null = null
2799
+ \tlet lastId: number | undefined = opts.lastId
2800
+ \tlet reconnectToken: string | undefined = opts.reconnectToken
2801
+ \tlet started = false
2802
+ \tlet closed = false
2803
+
2804
+ \tfunction indexToTransport(i: number): Transport {
2805
+ \t\tif (i === 0) return "ws"
2806
+ \t\tif (i === 1) return "sse"
2807
+ \t\treturn "longpoll"
2808
+ \t}
2809
+
2810
+ \tif (opts.signal?.aborted) {
2811
+ \t\tstate = "closed"
2812
+ \t\tclosed = true
2813
+ \t}
2814
+
2815
+ \tconst queue: QueueEntry[] = []
2816
+ \tlet pending: {
2817
+ \t\tresolve: (result: IteratorResult<unknown>) => void
2818
+ \t\treject: (err: unknown) => void
2819
+ \t} | null = null
2820
+
2821
+ \tfunction enqueue(entry: QueueEntry): void {
2822
+ \t\tif (pending) {
2823
+ \t\t\tconst { resolve, reject } = pending
2824
+ \t\t\tpending = null
2825
+ \t\t\tif (entry.type === "value") {
2826
+ \t\t\t\tresolve({ done: false, value: entry.value })
2827
+ \t\t\t} else if (entry.type === "done") {
2828
+ \t\t\t\tresolve({ done: true, value: undefined })
2829
+ \t\t\t} else {
2830
+ \t\t\t\treject(entry.error)
2831
+ \t\t\t}
2832
+ \t\t} else {
2833
+ \t\t\tqueue.push(entry)
2834
+ \t\t}
2835
+ \t}
2836
+
2837
+ \tfunction detachConn(): void {
2838
+ \t\tif (!currentConn) return
2839
+ \t\tconst conn = currentConn
2840
+ \t\tcurrentConn = null
2841
+ \t\tconn.onFrame = () => {}
2842
+ \t\tconn.onError = () => {}
2843
+ \t\tconn.onClose = () => {}
2844
+ \t\ttry {
2845
+ \t\t\tconn.close()
2846
+ \t\t} catch {
2847
+ \t\t\t/* swallow — adapter close() is best-effort */
2848
+ \t\t}
2849
+ \t}
2850
+
2851
+ \tfunction handleClose(): void {
2852
+ \t\tif (state === "closed") return
2853
+ \t\tstate = "closed"
2854
+ \t\tclosed = true
2855
+ \t\tdetachConn()
2856
+ \t\tenqueue({ type: "done" })
2857
+ \t}
2858
+
2859
+ \tfunction handleAbort(): void {
2860
+ \t\tif (state === "closed") return
2861
+ \t\tstate = "closed"
2862
+ \t\tclosed = true
2863
+ \t\tdetachConn()
2864
+ \t\tenqueue({ error: new RealtimeAbortError("Connection aborted", "aborted"), type: "error" })
2865
+ \t}
2866
+
2867
+ \tif (opts.signal && !opts.signal.aborted) {
2868
+ \t\topts.signal.addEventListener("abort", () => {
2869
+ \t\t\thandleAbort()
2870
+ \t\t}, { once: true })
2871
+ \t}
2872
+
2873
+ \t/* tryAdapterAt dials a single adapter and wires its onFrame/onError/onClose
2874
+ \t * straight into the resumable queue + reconnect loop the moment the adapter
2875
+ \t * returns. No post-ready re-wire hop — frames that arrive between ready and
2876
+ \t * the caller's await-continuation can't be lost. Resolves when ready frame
2877
+ \t * seen, rejects on error/close/timeout. */
2878
+ \tfunction tryAdapterAt(index: number): Promise<TransportConnection> {
2879
+ \t\treturn new Promise((resolve, reject) => {
2880
+ \t\t\tconst adapter = opts.transports[index]
2881
+ \t\t\tif (!adapter) {
2882
+ \t\t\t\treject(new RealtimeConnectError("adapter index out of range", "no_adapter"))
2883
+ \t\t\t\treturn
2884
+ \t\t\t}
2885
+ \t\t\tconst transportOpts: TransportOpts = {
2886
+ \t\t\t\theaders: opts.headers,
2887
+ \t\t\t\tlastId,
2888
+ \t\t\t\treconnectToken,
2889
+ \t\t\t\tsignal: opts.signal,
2890
+ \t\t\t}
2891
+ \t\t\tconst conn = adapter.connect(opts.url, transportOpts)
2892
+ \t\t\tlet ready = false
2893
+ \t\t\tconst timer = setTimeout(() => {
2894
+ \t\t\t\tif (ready) return
2895
+ \t\t\t\ttry { conn.close() } catch { /* ignore */ }
2896
+ \t\t\t\treject(new RealtimeConnectError("transport open timeout", "timeout"))
2897
+ \t\t\t}, fallbackTimeout)
2898
+ \t\t\tconn.onFrame = (frame: ServerFrame) => {
2899
+ \t\t\t\tif (!ready && frame.t === "ready") {
2900
+ \t\t\t\t\tready = true
2901
+ \t\t\t\t\tclearTimeout(timer)
2902
+ \t\t\t\t\treconnectToken = frame.reconnectToken
2903
+ \t\t\t\t\tresolve(conn)
2904
+ \t\t\t\t\treturn
2905
+ \t\t\t\t}
2906
+ \t\t\t\tif (frame.t === "msg") {
2907
+ \t\t\t\t\tlastId = frame.id
2908
+ \t\t\t\t\tenqueue({ type: "value", value: frame.data })
2909
+ \t\t\t\t\treturn
2910
+ \t\t\t\t}
2911
+ \t\t\t\tif (frame.t === "ready") {
2912
+ \t\t\t\t\treconnectToken = frame.reconnectToken
2913
+ \t\t\t\t\treturn
2914
+ \t\t\t\t}
2915
+ \t\t\t\tif (frame.t === "bye") {
2916
+ \t\t\t\t\tif (ready) {
2917
+ \t\t\t\t\t\tdetachConn()
2918
+ \t\t\t\t\t\tscheduleReconnect(new RealtimeError("server closed connection: " + frame.reason, "bye"))
2919
+ \t\t\t\t\t}
2920
+ \t\t\t\t\treturn
2921
+ \t\t\t\t}
2922
+ \t\t\t}
2923
+ \t\t\tconn.onError = (err: unknown) => {
2924
+ \t\t\t\tclearTimeout(timer)
2925
+ \t\t\t\tif (!ready) {
2926
+ \t\t\t\t\ttry { conn.close() } catch { /* ignore */ }
2927
+ \t\t\t\t\treject(err)
2928
+ \t\t\t\t\treturn
2929
+ \t\t\t\t}
2930
+ \t\t\t\tdetachConn()
2931
+ \t\t\t\tscheduleReconnect(err)
2932
+ \t\t\t}
2933
+ \t\t\tconn.onClose = (reason: string) => {
2934
+ \t\t\t\tclearTimeout(timer)
2935
+ \t\t\t\tif (!ready) {
2936
+ \t\t\t\t\treject(new RealtimeConnectError("transport closed before ready: " + reason, "closed_early"))
2937
+ \t\t\t\t\treturn
2938
+ \t\t\t\t}
2939
+ \t\t\t\tdetachConn()
2940
+ \t\t\t\tscheduleReconnect(new RealtimeError("transport closed: " + reason, "closed"))
2941
+ \t\t\t}
2942
+ \t\t})
2943
+ \t}
2944
+
2945
+ \tasync function openChain(): Promise<void> {
2946
+ \t\tif (opts.transports.length === 0) {
2947
+ \t\t\tthrow new RealtimeConnectError("No transports configured", "no_transports")
2948
+ \t\t}
2949
+ \t\t/* Try proven first if memoized */
2950
+ \t\tif (provenIndex !== null && provenIndex < opts.transports.length) {
2951
+ \t\t\ttry {
2952
+ \t\t\t\tconst conn = await tryAdapterAt(provenIndex)
2953
+ \t\t\t\tcurrentConn = conn
2954
+ \t\t\t\treturn
2955
+ \t\t\t} catch {
2956
+ \t\t\t\t/* fall through to full chain */
2957
+ \t\t\t}
2958
+ \t\t}
2959
+ \t\tlet lastErr: unknown = null
2960
+ \t\tfor (let i = 0; i < opts.transports.length; i++) {
2961
+ \t\t\ttry {
2962
+ \t\t\t\tconst conn = await tryAdapterAt(i)
2963
+ \t\t\t\tprovenIndex = i
2964
+ \t\t\t\tcurrentConn = conn
2965
+ \t\t\t\treturn
2966
+ \t\t\t} catch (err) {
2967
+ \t\t\t\tlastErr = err
2968
+ \t\t\t}
2969
+ \t\t}
2970
+ \t\tthrow lastErr ?? new RealtimeConnectError("All transports failed", "all_failed")
2971
+ \t}
2972
+
2973
+ \tfunction scheduleReconnect(_err: unknown): void {
2974
+ \t\tif (closed) return
2975
+ \t\treconnectAttempts += 1
2976
+ \t\tif (maxReconnectAttempts > 0 && reconnectAttempts > maxReconnectAttempts) {
2977
+ \t\t\tstate = "closed"
2978
+ \t\t\tclosed = true
2979
+ \t\t\tenqueue({ type: "done" })
2980
+ \t\t\treturn
2981
+ \t\t}
2982
+ \t\tstate = "reconnecting"
2983
+ \t\t/* invalidate proven — a drop means that transport isn't healthy */
2984
+ \t\tprovenIndex = null
2985
+ \t\tif (opts.onReconnecting) {
2986
+ \t\t\ttry {
2987
+ \t\t\t\topts.onReconnecting(reconnectAttempts, indexToTransport(provenIndex ?? 0))
2988
+ \t\t\t} catch {
2989
+ \t\t\t\t/* user callback must not break the loop */
2990
+ \t\t\t}
2991
+ \t\t}
2992
+ \t\tsetTimeout(() => {
2993
+ \t\t\tif (closed) return
2994
+ \t\t\tvoid connectOnce()
2995
+ \t\t}, reconnectDelayMs)
2996
+ \t}
2997
+
2998
+ \tasync function connectOnce(): Promise<void> {
2999
+ \t\tif (closed) return
3000
+ \t\tif (state === "idle" || state === "closed") state = "connecting"
3001
+ \t\ttry {
3002
+ \t\t\tawait openChain()
3003
+ \t\t\tif (closed) {
3004
+ \t\t\t\tdetachConn()
3005
+ \t\t\t\treturn
3006
+ \t\t\t}
3007
+ \t\t\tstate = "connected"
3008
+ \t\t\treconnectAttempts = 0
3009
+ \t\t\tif (opts.onReconnected) {
3010
+ \t\t\t\ttry { opts.onReconnected() } catch { /* user cb */ }
3011
+ \t\t\t}
3012
+ \t\t} catch (err) {
3013
+ \t\t\tscheduleReconnect(err)
3014
+ \t\t}
3015
+ \t}
3016
+
3017
+ \tfunction startConnection(): void {
3018
+ \t\tif (started || closed) return
3019
+ \t\tstarted = true
3020
+ \t\tstate = "connecting"
3021
+ \t\tvoid connectOnce()
3022
+ \t}
3023
+
3024
+ \tfunction send(data: unknown): void {
3025
+ \t\tif (closed || !currentConn) return
3026
+ \t\tcurrentConn.send(JSON.stringify(data))
3027
+ \t}
3028
+
3029
+ \tfunction close(_reason?: string): void {
3030
+ \t\thandleClose()
3031
+ \t}
3032
+
3033
+ \tfunction next(): Promise<IteratorResult<unknown>> {
3034
+ \t\tif (!started && !closed) {
3035
+ \t\t\tstartConnection()
3036
+ \t\t}
3037
+ \t\tif (queue.length > 0) {
3038
+ \t\t\tconst entry = queue.shift()
3039
+ \t\t\tif (!entry || entry.type === "done") {
3040
+ \t\t\t\treturn Promise.resolve({ done: true, value: undefined })
3041
+ \t\t\t}
3042
+ \t\t\tif (entry.type === "value") {
3043
+ \t\t\t\treturn Promise.resolve({ done: false, value: entry.value })
3044
+ \t\t\t}
3045
+ \t\t\treturn Promise.reject(entry.error)
3046
+ \t\t}
3047
+ \t\tif (closed) {
3048
+ \t\t\treturn Promise.resolve({ done: true, value: undefined })
3049
+ \t\t}
3050
+ \t\tconst promise = new Promise<IteratorResult<unknown>>((resolve, reject) => {
3051
+ \t\t\tpending = { reject, resolve }
3052
+ \t\t})
3053
+ \t\tpromise.catch(() => {})
3054
+ \t\treturn promise
3055
+ \t}
3056
+
3057
+ \tfunction asyncIterator(): AsyncIterableIterator<unknown> {
3058
+ \t\treturn {
3059
+ \t\t\tnext,
3060
+ \t\t\t[Symbol.asyncIterator]() {
3061
+ \t\t\t\treturn this
3062
+ \t\t\t},
3063
+ \t\t}
3064
+ \t}
3065
+
3066
+ \treturn {
3067
+ \t\t[Symbol.asyncIterator]: asyncIterator,
3068
+ \t\tclose,
3069
+ \t\tget provenTransport() {
3070
+ \t\t\treturn provenIndex === null ? null : indexToTransport(provenIndex)
3071
+ \t\t},
3072
+ \t\tsend,
3073
+ \t\tget state() {
3074
+ \t\t\treturn state
3075
+ \t\t},
3076
+ \t}
3077
+ }
3078
+ `;
3079
+ }
3080
+ function sdkClientHeader(n, stem) {
3081
+ return `import type { ${n}Config } from "./${stem}.types.gen"
3082
+ import { serviceMap } from "./${stem}.map.gen"
3083
+ `;
3084
+ }
3085
+ function sdkClientTypes() {
3086
+ return `
3087
+ type _SSEEvent = { data: string; event?: string; id?: string; retry?: number }
3088
+
3089
+ type _TypedWebSocket = {
3090
+ \tclose(code?: number, reason?: string): void
3091
+ \toff(event: "close" | "error" | "message" | "open", handler: (...args: never[]) => void): void
3092
+ \ton(event: "close", handler: (code: number, reason: string) => void): void
3093
+ \ton(event: "error", handler: (error: unknown) => void): void
3094
+ \ton(event: "message", handler: (data: string) => void): void
3095
+ \ton(event: "open", handler: () => void): void
3096
+ \treadonly readyState: number
3097
+ \tsend(data: ArrayBuffer | ArrayBufferView | object | string): void
3098
+ }
3099
+
3100
+ type _ServiceEntry = {
3101
+ \tidempotent?: boolean
3102
+ \tinvalidate?: readonly string[]
3103
+ \tmethod: string
3104
+ \tparams?: readonly string[]
3105
+ \tpath: string
3106
+ \tsse?: boolean
3107
+ \twildcard?: string
3108
+ \tws?: boolean
3109
+ }
3110
+
3111
+ type _ServiceMapNode = _ServiceEntry | { [key: string]: _ServiceMapNode }
3112
+ type _ServiceMap = { [key: string]: _ServiceMapNode }
3113
+
3114
+ type _RequestOptions = {
3115
+ \tbody?: ReadableStream<Uint8Array> | Blob | ArrayBuffer | Uint8Array
3116
+ \tcookies?: Record<string, string>
3117
+ \tform?: Record<string, unknown>
3118
+ \theaders?: Record<string, string>
3119
+ \tidempotencyKey?: string
3120
+ \tjson?: unknown
3121
+ \tlastEventId?: string
3122
+ \tparams?: Record<string, string>
3123
+ \tprotocols?: string | string[]
3124
+ \treconnectToken?: string
3125
+ \tsearch?: Record<string, unknown>
3126
+ \tsignal?: AbortSignal
3127
+ \ttimeout?: number
3128
+ }
3129
+
3130
+ type _RequestMeta = {
3131
+ \tinvalidatedBy: string[]
3132
+ \tisStale: boolean
3133
+ \tselector: string
3134
+ \tseqSnapshot: number
3135
+ }
3136
+
3137
+ type _StaleEntry = { by: string[]; refreshed?: Set<string>; seq: number; until: number }
3138
+
3139
+ /* a path parameter value that would change the request's path once a URL parser normalizes it */
3140
+ class _PathParamError extends Error {
3141
+ \tconstructor(message: string) {
3142
+ \t\tsuper(message)
3143
+ \t\tthis.name = "PathParamError"
3144
+ \t}
3145
+ }
3146
+ `;
3147
+ }
3148
+ function sdkClientClientError() {
3149
+ const subclassDecls = STATUS_ERROR_CLASSES.map(({ name }) => `class _${name} extends _ClientError { constructor(init: ConstructorParameters<typeof _ClientError>[0]) { super(init); this.name = "${name}" } }`).join("\n");
3150
+ const mapEntries = STATUS_ERROR_CLASSES.map(({ name, status }) => `\t${status}: _${name},`).join("\n");
3151
+ return `
3152
+ class _ClientError extends Error {
3153
+ \treadonly body: unknown
3154
+ \treadonly data: unknown
3155
+ \treadonly response: Response
3156
+ \treadonly status: number
3157
+
3158
+ \tconstructor(init: { body: unknown; data: unknown; message: string; response: Response; status: number }) {
3159
+ \t\tsuper(init.message)
3160
+ \t\t;(Error as unknown as { captureStackTrace?: (t: object, c: Function) => void }).captureStackTrace?.(this, _ClientError)
3161
+ \t\tthis.name = "ClientError"
3162
+ \t\tthis.body = init.body
3163
+ \t\tthis.data = init.data
3164
+ \t\tthis.response = init.response
3165
+ \t\tthis.status = init.status
3166
+ \t}
3167
+ }
3168
+
3169
+ ${subclassDecls}
3170
+
3171
+ const _STATUS_ERROR_MAP: Record<number, new (init: ConstructorParameters<typeof _ClientError>[0]) => _ClientError> = {
3172
+ ${mapEntries}
3173
+ }
3174
+ `;
3175
+ }
3176
+ function sdkClientClass(n) {
3177
+ return `
3178
+ export class ${n}<TThrow extends boolean = false> {
3179
+ \tstate: Record<string, unknown>
3180
+ \t#config: ${n}Config<TThrow>
3181
+ \t#fetchFn: typeof fetch
3182
+ \t#resourceCache = new Map<string, Record<string, unknown>>()
3183
+ \t#searchSerializer: (query: Record<string, unknown>) => URLSearchParams
3184
+ \t#staleTime: number
3185
+ \t#staleUntil: Map<string, _StaleEntry> | null
3186
+ \t#stalePatterns = new Map<string, Map<string, _StaleEntry>>()
3187
+ \t#patternRegexCache = new Map<string, RegExp>()
3188
+ \t#invalidationSeq = 0
3189
+ \t#staleMaxEntries: number
3190
+ \t#maxSourcesPerTarget: number
3191
+ \t#maxErrorMessageChars: number
3192
+ \t#sseMaxBufferChars: number
3193
+ \t#disposeCtrl = new AbortController()
3194
+ \t#disposed = false
3195
+ \t#refreshing: Promise<string | null> | null = null
3196
+ \t#token: string | null = null
3197
+ `;
3198
+ }
3199
+ function sdkClientConstructor(n) {
3200
+ return `
3201
+ \tconstructor(config: ${n}Config<TThrow>) {
3202
+ \t\tconst ownState = config.state ?? {}
3203
+ \t\tthis.state = ownState
3204
+ \t\tthis.#config = { ...config, state: ownState }
3205
+ \t\tthis.#fetchFn = config.fetch ?? (typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : globalThis.fetch)
3206
+ \t\tthis.#searchSerializer = config.buildSearchParams ?? ((q: Record<string, unknown>) => this.#serializeSearch(q))
3207
+ \t\tthis.#staleTime = config.invalidation?.staleTime ?? 0
3208
+ \t\tthis.#staleUntil = this.#staleTime > 0 ? new Map<string, _StaleEntry>() : null
3209
+ \t\tthis.#staleMaxEntries = config.invalidation?.staleMaxEntries ?? 1000
3210
+ \t\tthis.#maxSourcesPerTarget = Math.max(config.invalidation?.maxSourcesPerTarget ?? 16, 1)
3211
+ \t\tthis.#maxErrorMessageChars = config.maxErrorMessageChars ?? 512
3212
+ \t\tthis.#sseMaxBufferChars = config.sseMaxBufferChars ?? 1024 * 1024
3213
+ `;
3214
+ }
3215
+ function sdkClientProxy(n) {
3216
+ return `
3217
+ \t\tconst self = this
3218
+ \t\tconst callable = (entry: _ServiceEntry, name: string): ((input?: Record<string, unknown>) => unknown) => {
3219
+ \t\t\tconst entryPath = self.#toColonParams(entry.path, entry.wildcard)
3220
+ \t\t\tlet fn: (input?: Record<string, unknown>) => unknown
3221
+ \t\t\tif (entry.ws) {
3222
+ \t\t\t\tfn = (input?: Record<string, unknown>) => self.#connectWS(entry, entryPath, (input ?? {}) as _RequestOptions)
3223
+ \t\t\t} else if (entry.sse) {
3224
+ \t\t\t\tfn = (input?: Record<string, unknown>) => self.#requestSSE(entry, entryPath, (input ?? {}) as _RequestOptions)
3225
+ \t\t\t} else {
3226
+ \t\t\t\tfn = (input?: Record<string, unknown>) => self.#request(entry, input ?? {})
3227
+ \t\t\t}
3228
+ \t\t\tObject.defineProperty(fn, "name", { value: name })
3229
+ \t\t\treturn fn
3230
+ \t\t}
3231
+ \t\tconst isEntry = (node: unknown): node is _ServiceEntry =>
3232
+ \t\t\ttypeof node === "object" && node !== null && Object.hasOwn(node, "method") && typeof (node as Record<string, unknown>)["method"] === "string"
3233
+ \t\t/* only the map's own keys are operations — never toString, constructor or then */
3234
+ \t\tconst childOf = (node: _ServiceMapNode, key: string): _ServiceMapNode | undefined =>
3235
+ \t\t\tObject.hasOwn(node, key) ? (node as Record<string, _ServiceMapNode>)[key] : undefined
3236
+
3237
+ \t\tfunction makeNodeProxy(node: _ServiceMapNode, path: string[]): object {
3238
+ \t\t\tconst actionCache = new Map<string, (input?: Record<string, unknown>) => unknown>()
3239
+ \t\t\tconst childCache = new Map<string, object>()
3240
+ \t\t\treturn new Proxy({} as Record<string, unknown>, {
3241
+ \t\t\t\tget: (target, key: string | symbol) => {
3242
+ \t\t\t\t\tif (typeof key === "symbol") return Reflect.get(target, key)
3243
+ \t\t\t\t\tconst child = childOf(node, key)
3244
+ \t\t\t\t\tif (child === undefined) return Reflect.get(target, key)
3245
+
3246
+ \t\t\t\t\tif (isEntry(child)) {
3247
+ \t\t\t\t\t\tconst cached = actionCache.get(key)
3248
+ \t\t\t\t\t\tif (cached) return cached
3249
+ \t\t\t\t\t\tconst fn = callable(child, [...path, key].join("."))
3250
+ \t\t\t\t\t\tactionCache.set(key, fn)
3251
+ \t\t\t\t\t\treturn fn
3252
+ \t\t\t\t\t}
3253
+
3254
+ \t\t\t\t\tconst cachedChild = childCache.get(key)
3255
+ \t\t\t\t\tif (cachedChild) return cachedChild
3256
+ \t\t\t\t\tconst childProxy = makeNodeProxy(child, [...path, key])
3257
+ \t\t\t\t\tchildCache.set(key, childProxy)
3258
+ \t\t\t\t\treturn childProxy
3259
+ \t\t\t\t},
3260
+ \t\t\t})
3261
+ \t\t}
3262
+
3263
+ \t\treturn new Proxy(this, {
3264
+ \t\t\tget: (target, key: string | symbol) => {
3265
+ \t\t\t\t/* the client's own members (state, dispose, toString, ...) always win */
3266
+ \t\t\t\tif (typeof key === "symbol" || key in target) return Reflect.get(target, key)
3267
+
3268
+ \t\t\t\tconst cached = target.#resourceCache.get(key)
3269
+ \t\t\t\tif (cached) return cached
3270
+
3271
+ \t\t\t\tconst node = childOf(serviceMap as _ServiceMap, key)
3272
+ \t\t\t\tif (node === undefined) return undefined
3273
+
3274
+ \t\t\t\t/* root-level leaf (single-segment operationId) */
3275
+ \t\t\t\tif (isEntry(node)) {
3276
+ \t\t\t\t\tconst fn = callable(node, key)
3277
+ \t\t\t\t\ttarget.#resourceCache.set(key, fn as unknown as Record<string, unknown>)
3278
+ \t\t\t\t\treturn fn
3279
+ \t\t\t\t}
3280
+
3281
+ \t\t\t\t/* namespace node — check for single _call promotion */
3282
+ \t\t\t\tconst nodeKeys = Object.keys(node)
3283
+ \t\t\t\tconst only = childOf(node, "_call")
3284
+ \t\t\t\tif (nodeKeys.length === 1 && only !== undefined && isEntry(only)) {
3285
+ \t\t\t\t\tconst fn = callable(only, key)
3286
+ \t\t\t\t\ttarget.#resourceCache.set(key, fn as unknown as Record<string, unknown>)
3287
+ \t\t\t\t\treturn fn
3288
+ \t\t\t\t}
3289
+
3290
+ \t\t\t\tconst proxy = makeNodeProxy(node, [key])
3291
+ \t\t\t\ttarget.#resourceCache.set(key, proxy as Record<string, unknown>)
3292
+ \t\t\t\treturn proxy
3293
+ \t\t\t},
3294
+ \t\t}) as ${n}<TThrow>
3295
+ \t}
3296
+ `;
3297
+ }
3298
+ function sdkClientInterpolatePath() {
3299
+ return `
3300
+ \t/* Keep in sync with client/path.ts — :name (whole segment), *name (rest), {name} (inside a segment). */
3301
+ \t#encodeSegmentValue(key: string, value: string): string {
3302
+ \t\tif (value === "" || value === "." || value === "..") {
3303
+ \t\t\tthrow new _PathParamError(\`Invalid path param \${JSON.stringify(key)}: \${JSON.stringify(value)} is not a path segment\`)
3304
+ \t\t}
3305
+ \t\treturn encodeURIComponent(value)
3306
+ \t}
3307
+
3308
+ \t#encodeWildcardValue(key: string, value: string): string {
3309
+ \t\tif (value === "") return ""
3310
+ \t\treturn value.split("/").map((part) => this.#encodeSegmentValue(key, part)).join("/")
3311
+ \t}
3312
+
3313
+ \t#paramValue(params: Record<string, string> | undefined, key: string): string | undefined {
3314
+ \t\tif (!params || !Object.hasOwn(params, key)) return undefined
3315
+ \t\tconst raw = params[key] as unknown
3316
+ \t\tif (raw === undefined || raw === null) return undefined
3317
+ \t\treturn typeof raw === "string" ? raw : String(raw)
3318
+ \t}
3319
+
3320
+ \t#interpolatePath(path: string, params: Record<string, string> | undefined, partial = false): string {
3321
+ \t\tconst segments = path.split("/")
3322
+ \t\tconst out: string[] = []
3323
+ \t\tfor (let i = 0; i < segments.length; i++) {
3324
+ \t\t\tconst seg = segments[i] ?? ""
3325
+ \t\t\tif (seg.length > 1 && seg.charCodeAt(0) === 58) {
3326
+ \t\t\t\tconst optional = seg.endsWith("?")
3327
+ \t\t\t\tconst name = optional ? seg.slice(1, -1) : seg.slice(1)
3328
+ \t\t\t\tconst value = this.#paramValue(params, name)
3329
+ \t\t\t\tif (value === undefined) {
3330
+ \t\t\t\t\tif (partial) { out.push(seg); continue }
3331
+ \t\t\t\t\tif (optional) continue
3332
+ \t\t\t\t\tthrow new _PathParamError(\`Missing path param: \${name}\`)
3333
+ \t\t\t\t}
3334
+ \t\t\t\tout.push(this.#encodeSegmentValue(name, value))
3335
+ \t\t\t\tcontinue
3336
+ \t\t\t}
3337
+ \t\t\tif (seg.charCodeAt(0) === 42 && i === segments.length - 1) {
3338
+ \t\t\t\tconst name = seg.length > 1 ? seg.slice(1) : "*"
3339
+ \t\t\t\tconst value = this.#paramValue(params, name)
3340
+ \t\t\t\tif (value === undefined) {
3341
+ \t\t\t\t\tif (partial) { out.push(seg); continue }
3342
+ \t\t\t\t\tif (seg.length === 1) { out.push(""); continue }
3343
+ \t\t\t\t\tthrow new _PathParamError(\`Missing path param: \${name}\`)
3344
+ \t\t\t\t}
3345
+ \t\t\t\tout.push(this.#encodeWildcardValue(name, value))
3346
+ \t\t\t\tcontinue
3347
+ \t\t\t}
3348
+ \t\t\tout.push(
3349
+ \t\t\t\tseg.replace(/\\{([^{}/]+)\\}/g, (match: string, name: string) => {
3350
+ \t\t\t\t\tconst value = this.#paramValue(params, name)
3351
+ \t\t\t\t\tif (value === undefined) {
3352
+ \t\t\t\t\t\tif (partial) return match
3353
+ \t\t\t\t\t\tthrow new _PathParamError(\`Missing path param: \${name}\`)
3354
+ \t\t\t\t\t}
3355
+ \t\t\t\t\treturn this.#encodeSegmentValue(name, value)
3356
+ \t\t\t\t}),
3357
+ \t\t\t)
3358
+ \t\t}
3359
+ \t\treturn out.join("/")
3360
+ \t}
3361
+
3362
+ \t#hasPlaceholder(path: string): boolean {
3363
+ \t\tfor (const seg of path.split("/")) {
3364
+ \t\t\tif (seg.length > 1 && seg.charCodeAt(0) === 58) return true
3365
+ \t\t\tif (seg.charCodeAt(0) === 42) return true
3366
+ \t\t\tif (/\\{[^{}/]+\\}/.test(seg)) return true
3367
+ \t\t}
3368
+ \t\treturn false
3369
+ \t}
3370
+ `;
3371
+ }
3372
+ function sdkClientToColonParams() {
3373
+ return `
3374
+ \t/* OpenAPI template → route pattern: a whole-segment {name} is :name, the wildcard *name; {name} inside a segment stays */
3375
+ \t#toColonParams(path: string, wildcard?: string): string {
3376
+ \t\treturn path
3377
+ \t\t\t.split("/")
3378
+ \t\t\t.map((seg) => {
3379
+ \t\t\t\tconst m = /^\\{([^{}/]+)\\}$/.exec(seg)
3380
+ \t\t\t\tif (!m) return seg
3381
+ \t\t\t\treturn m[1] === wildcard ? \`*\${m[1]}\` : \`:\${m[1]}\`
3382
+ \t\t\t})
3383
+ \t\t\t.join("/")
3384
+ \t}
3385
+ `;
3386
+ }
3387
+ function sdkClientResolveInvalidationTargets() {
3388
+ return `
3389
+ \t/* params that are present are substituted; a target left partly unresolved stays a narrower pattern */
3390
+ \t#resolveInvalidationTargets(
3391
+ \t\ttargets: readonly string[],
3392
+ \t\tparams: Record<string, string> | undefined,
3393
+ \t): string[] {
3394
+ \t\tconst resolved: string[] = []
3395
+ \t\tfor (const target of targets) {
3396
+ \t\t\tconst spaceIdx = target.indexOf(" ")
3397
+ \t\t\tif (spaceIdx <= 0) continue
3398
+ \t\t\tconst targetMethod = target.slice(0, spaceIdx)
3399
+ \t\t\tconst targetPath = target.slice(spaceIdx + 1)
3400
+ \t\t\ttry {
3401
+ \t\t\t\tresolved.push(\`\${targetMethod} \${this.#interpolatePath(targetPath, params, true)}\`)
3402
+ \t\t\t} catch {
3403
+ \t\t\t\t/* a param value that is not a path segment marks nothing */
3404
+ \t\t\t}
3405
+ \t\t}
3406
+ \t\treturn resolved
3407
+ \t}
3408
+ `;
3409
+ }
3410
+ function sdkClientPathMatchesPattern() {
3411
+ return `
3412
+ \t#pathMatchesPattern(concretePath: string, pattern: string): boolean {
3413
+ \t\tlet re = this.#patternRegexCache.get(pattern)
3414
+ \t\tif (!re) {
3415
+ \t\t\tconst esc = (text: string) => text.replace(/[.*+?^\${}()|[\\]\\\\]/g, "\\\\$&")
3416
+ \t\t\tconst segments = pattern.split("/")
3417
+ \t\t\tlet source = ""
3418
+ \t\t\tfor (let i = 0; i < segments.length; i++) {
3419
+ \t\t\t\tconst seg = segments[i] ?? ""
3420
+ \t\t\t\tconst sep = i === 0 ? "" : "/"
3421
+ \t\t\t\tif (seg.length > 1 && seg.charCodeAt(0) === 58) {
3422
+ \t\t\t\t\tsource += seg.endsWith("?") ? \`(?:\${sep}[^/]+)?\` : \`\${sep}[^/]+\`
3423
+ \t\t\t\t\tcontinue
3424
+ \t\t\t\t}
3425
+ \t\t\t\tif (seg.charCodeAt(0) === 42 && i === segments.length - 1) {
3426
+ \t\t\t\t\tsource += \`(?:\${sep}.*)?\`
3427
+ \t\t\t\t\tcontinue
3428
+ \t\t\t\t}
3429
+ \t\t\t\tsource += sep + seg.split(/\\{[^{}/]+\\}/).map(esc).join("[^/]+")
3430
+ \t\t\t}
3431
+ \t\t\tre = new RegExp(\`^\${source}$\`)
3432
+ \t\t\tif (this.#patternRegexCache.size >= 1024) {
3433
+ \t\t\t\tconst oldest = this.#patternRegexCache.keys().next().value
3434
+ \t\t\t\tif (oldest !== undefined) this.#patternRegexCache.delete(oldest)
3435
+ \t\t\t}
3436
+ \t\t\tthis.#patternRegexCache.set(pattern, re)
3437
+ \t\t}
3438
+ \t\treturn re.test(concretePath)
3439
+ \t}
3440
+ `;
3441
+ }
3442
+ function sdkClientLookupStale() {
3443
+ return `
3444
+ \t/* Keep in sync with client/sdk.ts StaleIndex — exact keys and pattern keys are kept apart, so a
3445
+ \t concrete path containing ":" is never read as a pattern, and a lookup scans only its method. */
3446
+ \t#lookupStale(
3447
+ \t\tconcreteSelector: string,
3448
+ \t\tconcretePath: string,
3449
+ \t\tmethod: string,
3450
+ \t\tnow: number,
3451
+ \t): { by: string[]; isStale: boolean } {
3452
+ \t\tconst by = new Set<string>()
3453
+ \t\tconst exact = this.#staleUntil?.get(concreteSelector)
3454
+ \t\tif (exact) {
3455
+ \t\t\tif (exact.until > now) for (const m of exact.by) by.add(m)
3456
+ \t\t\telse this.#staleUntil?.delete(concreteSelector)
3457
+ \t\t}
3458
+ \t\tconst table = this.#stalePatterns.get(method)
3459
+ \t\tif (table) {
3460
+ \t\t\tfor (const [key, entry] of table) {
3461
+ \t\t\t\tif (entry.until <= now) { table.delete(key); continue }
3462
+ \t\t\t\tif (entry.refreshed?.has(concretePath)) continue
3463
+ \t\t\t\tif (this.#pathMatchesPattern(concretePath, key.slice(key.indexOf(" ") + 1))) {
3464
+ \t\t\t\t\tfor (const m of entry.by) by.add(m)
3465
+ \t\t\t\t}
3466
+ \t\t\t}
3467
+ \t\t}
3468
+ \t\treturn { by: [...by], isStale: by.size > 0 }
3469
+ \t}
3470
+ `;
3471
+ }
3472
+ function sdkClientCreateTypedWebSocket() {
3473
+ return `
3474
+ \t#createTypedWebSocket(url: string, protocols?: string | string[]): _TypedWebSocket {
3475
+ \t\tconst ws = protocols ? new WebSocket(url, protocols) : new WebSocket(url)
3476
+ \t\t/* keyed by event and handler: one function may listen to several events */
3477
+ \t\tconst listenerMap = new Map<string, WeakMap<(...args: never[]) => void, EventListener>>()
3478
+ \t\tconst sendBuffer: Array<ArrayBuffer | ArrayBufferView | string> = []
3479
+ \t\tlet buffering = true
3480
+
3481
+ \t\tws.addEventListener("open", () => {
3482
+ \t\t\tbuffering = false
3483
+ \t\t\tfor (const msg of sendBuffer) ws.send(msg as Parameters<WebSocket["send"]>[0])
3484
+ \t\t\tsendBuffer.length = 0
3485
+ \t\t})
3486
+ \t\t/* a socket that never opens must not hold messages forever */
3487
+ \t\tconst drop = () => {
3488
+ \t\t\tbuffering = false
3489
+ \t\t\tsendBuffer.length = 0
3490
+ \t\t}
3491
+ \t\tws.addEventListener("close", drop)
3492
+ \t\tws.addEventListener("error", drop)
3493
+
3494
+ \t\tfunction close(code?: number, reason?: string) {
3495
+ \t\t\tdrop()
3496
+ \t\t\tws.close(code, reason)
3497
+ \t\t}
3498
+
3499
+ \t\tfunction on(event: string, handler: (...args: never[]) => void): void {
3500
+ \t\t\tlet wrapped: EventListener
3501
+ \t\t\tswitch (event) {
3502
+ \t\t\t\tcase "message":
3503
+ \t\t\t\t\twrapped = (e: Event) => (handler as (data: unknown) => void)((e as MessageEvent).data)
3504
+ \t\t\t\t\tbreak
3505
+ \t\t\t\tcase "open":
3506
+ \t\t\t\t\twrapped = () => (handler as () => void)()
3507
+ \t\t\t\t\tbreak
3508
+ \t\t\t\tcase "close":
3509
+ \t\t\t\t\twrapped = (e: Event) => (handler as (code: number, reason: string) => void)((e as CloseEvent).code, (e as CloseEvent).reason)
3510
+ \t\t\t\t\tbreak
3511
+ \t\t\t\tcase "error":
3512
+ \t\t\t\t\twrapped = (e: Event) => (handler as (error: unknown) => void)(e)
3513
+ \t\t\t\t\tbreak
3514
+ \t\t\t\tdefault:
3515
+ \t\t\t\t\treturn
3516
+ \t\t\t}
3517
+ \t\t\tlet byHandler = listenerMap.get(event)
3518
+ \t\t\tif (!byHandler) {
3519
+ \t\t\t\tbyHandler = new WeakMap()
3520
+ \t\t\t\tlistenerMap.set(event, byHandler)
3521
+ \t\t\t}
3522
+ \t\t\tbyHandler.set(handler, wrapped)
3523
+ \t\t\tws.addEventListener(event, wrapped)
3524
+ \t\t}
3525
+
3526
+ \t\tfunction off(event: string, handler: (...args: never[]) => void): void {
3527
+ \t\t\tconst byHandler = listenerMap.get(event)
3528
+ \t\t\tconst wrapped = byHandler?.get(handler)
3529
+ \t\t\tif (wrapped) {
3530
+ \t\t\t\tws.removeEventListener(event, wrapped)
3531
+ \t\t\t\tbyHandler?.delete(handler)
3532
+ \t\t\t}
3533
+ \t\t}
3534
+
3535
+ \t\tfunction send(data: ArrayBuffer | ArrayBufferView | object | string) {
3536
+ \t\t\tlet payload: ArrayBuffer | ArrayBufferView | string | Blob
3537
+ \t\t\tif (typeof data === "string") {
3538
+ \t\t\t\tpayload = data
3539
+ \t\t\t} else if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
3540
+ \t\t\t\tpayload = data
3541
+ \t\t\t} else if (typeof Blob !== "undefined" && data instanceof Blob) {
3542
+ \t\t\t\tpayload = data
3543
+ \t\t\t} else {
3544
+ \t\t\t\tpayload = JSON.stringify(data)
3545
+ \t\t\t}
3546
+ \t\t\tif (buffering) { sendBuffer.push(payload as ArrayBuffer | ArrayBufferView | string) } else if (ws.readyState === 1) { ws.send(payload as Parameters<WebSocket["send"]>[0]) }
3547
+ \t\t}
3548
+
3549
+ \t\tconst typed: _TypedWebSocket = { close, off, on, get readyState() { return ws.readyState }, send }
3550
+ \t\tObject.defineProperty(typed, "_ws", { enumerable: false, value: ws })
3551
+ \t\treturn typed
3552
+ \t}
3553
+ `;
3554
+ }
3555
+ function sdkClientSerializeSearch() {
3556
+ return `
3557
+ \t#serializeSearch(query: Record<string, unknown>): URLSearchParams {
3558
+ \t\tconst params = new URLSearchParams()
3559
+ \t\tconst coerce = (v: unknown): string | null => {
3560
+ \t\t\tif (v === undefined || v === null) return null
3561
+ \t\t\tif (v instanceof Date) return v.toISOString()
3562
+ \t\t\tif (typeof v === "symbol") return null
3563
+ \t\t\treturn String(v)
3564
+ \t\t}
3565
+ \t\tfor (const [k, v] of Object.entries(query)) {
3566
+ \t\t\tif (Array.isArray(v)) {
3567
+ \t\t\t\tfor (const item of v) { const s = coerce(item); if (s !== null) params.append(k, s) }
3568
+ \t\t\t} else {
3569
+ \t\t\t\tconst s = coerce(v); if (s !== null) params.set(k, s)
3570
+ \t\t\t}
3571
+ \t\t}
3572
+ \t\treturn params
3573
+ \t}
3574
+ `;
3575
+ }
3576
+ /* Keep in sync with client/defaults.ts — generated SDKs have zero runtime imports. */
3577
+ function sdkClientResolveBaseURL() {
3578
+ return `
3579
+ #resolveBaseURL(baseURL: string): URL {
3580
+ if (/^(?:https?|wss?):\\/\\//i.test(baseURL)) return new URL(baseURL)
3581
+ let origin: unknown
3582
+ try {
3583
+ origin = globalThis.location?.origin
3584
+ } catch {
3585
+ origin = undefined
3586
+ }
3587
+ if (typeof origin === "string" && origin !== "" && origin !== "null" && /^(?:https?|wss?):\\/\\//i.test(origin)) {
3588
+ try {
3589
+ return new URL(baseURL, origin)
3590
+ } catch {
3591
+ throw new Error(\`Invalid baseURL \${JSON.stringify(baseURL)}: expected an absolute http(s): or ws(s): URL. Path-only values such as "/api" resolve against location.origin in browsers.\`)
3592
+ }
3593
+ }
3594
+ throw new Error(\`Invalid baseURL \${JSON.stringify(baseURL)}: expected an absolute http(s): or ws(s): URL. Path-only values such as "/api" resolve against location.origin in browsers.\`)
3595
+ }
3596
+ `;
3597
+ }
3598
+ function sdkClientBuildURL() {
3599
+ return `
3600
+ \t#buildURL(path: string, opts: _RequestOptions): string {
3601
+ \t\tconst resolvedPath = this.#interpolatePath(path, opts.params)
3602
+ \t\tconst baseUrl = this.#resolveBaseURL(this.#config.baseURL)
3603
+ \t\tconst basePath = baseUrl.pathname.endsWith("/") ? baseUrl.pathname : \`\${baseUrl.pathname}/\`
3604
+ \t\tconst relative = resolvedPath.startsWith("/") ? resolvedPath.slice(1) : resolvedPath
3605
+ \t\tconst url = new URL(\`\${basePath}\${relative}\`, baseUrl)
3606
+ \t\t/* a request (and its credentials) never leaves the configured origin */
3607
+ \t\tif (url.origin !== baseUrl.origin) {
3608
+ \t\t\tthrow new _PathParamError(\`Invalid path param: \${JSON.stringify(path)} resolves outside \${baseUrl.origin}\`)
3609
+ \t\t}
3610
+ \t\tfor (const [k, v] of baseUrl.searchParams.entries()) url.searchParams.append(k, v)
3611
+ \t\tif (opts.search) {
3612
+ \t\t\tconst sp = this.#searchSerializer(opts.search)
3613
+ \t\t\tif (this.#config.sortSearchParams) sp.sort()
3614
+ \t\t\tfor (const [k, v] of sp.entries()) url.searchParams.append(k, v)
3615
+ \t\t}
3616
+ \t\treturn url.toString()
3617
+ \t}
3618
+ `;
3619
+ }
3620
+ function sdkClientNewRequestId() {
3621
+ return `
3622
+ \t#newRequestId(): string {
3623
+ \t\tconst c = globalThis.crypto as Crypto | undefined
3624
+ \t\tif (c && typeof c.randomUUID === "function") return c.randomUUID()
3625
+ \t\tif (c && typeof c.getRandomValues === "function") {
3626
+ \t\t\tconst bytes = new Uint8Array(16)
3627
+ \t\t\tc.getRandomValues(bytes)
3628
+ \t\t\tbytes[6] = (bytes[6]! & 0x0f) | 0x40
3629
+ \t\t\tbytes[8] = (bytes[8]! & 0x3f) | 0x80
3630
+ \t\t\tlet out = ""
3631
+ \t\t\tfor (let i = 0; i < 16; i++) {
3632
+ \t\t\t\tif (i === 4 || i === 6 || i === 8 || i === 10) out += "-"
3633
+ \t\t\t\tout += bytes[i]!.toString(16).padStart(2, "0")
3634
+ \t\t\t}
3635
+ \t\t\treturn out
3636
+ \t\t}
3637
+ \t\tthrow new Error("honey: no crypto.randomUUID or crypto.getRandomValues in this runtime")
3638
+ \t}
3639
+ `;
3640
+ }
3641
+ function sdkClientBuildHeaders() {
3642
+ return `
3643
+ \tasync #buildHeaders(
3644
+ \t\topts: _RequestOptions,
3645
+ \t\tctx: { method: string; path: string },
3646
+ \t): Promise<Headers> {
3647
+ \t\tconst headers = new Headers()
3648
+
3649
+ \t\tif (this.#config.headers) {
3650
+ \t\t\tconst resolved =
3651
+ \t\t\t\ttypeof this.#config.headers === "function"
3652
+ \t\t\t\t\t? await this.#config.headers(ctx)
3653
+ \t\t\t\t\t: this.#config.headers
3654
+ \t\t\tfor (const [k, v] of Object.entries(resolved)) {
3655
+ \t\t\t\tif (v !== undefined) headers.set(k, v)
3656
+ \t\t\t}
3657
+ \t\t}
3658
+
3659
+ \t\t/* a token from onAuthExpired replaces the configured one until the next refresh */
3660
+ \t\tif (this.#token !== null) {
3661
+ \t\t\theaders.set(this.#config.authHeaderName ?? "Authorization", \`\${this.#config.authHeaderPrefix ?? "Bearer "}\${this.#token}\`)
3662
+ \t\t}
3663
+
3664
+ \t\tif (opts.headers) {
3665
+ \t\t\tfor (const [k, v] of Object.entries(opts.headers)) {
3666
+ \t\t\t\tif (v !== undefined) headers.set(k, v)
3667
+ \t\t\t}
3668
+ \t\t}
3669
+
3670
+ \t\tif (opts.cookies) {
3671
+ \t\t\tconst existing = headers.get("cookie")
3672
+ \t\t\tconst pairs = Object.entries(opts.cookies)
3673
+ \t\t\t\t.map(([k, v]) => \`\${encodeURIComponent(k)}=\${encodeURIComponent(v)}\`)
3674
+ \t\t\t\t.join("; ")
3675
+ \t\t\tif (pairs) {
3676
+ \t\t\t\theaders.set("cookie", existing ? \`\${existing}; \${pairs}\` : pairs)
3677
+ \t\t\t}
3678
+ \t\t}
3679
+
3680
+ \t\tif (!headers.has("x-request-id")) {
3681
+ \t\t\theaders.set("x-request-id", this.#newRequestId())
3682
+ \t\t}
3683
+
3684
+ \t\treturn headers
3685
+ \t}
3686
+ `;
3687
+ }
3688
+ function sdkClientDoRequest() {
3689
+ return `
3690
+ \t/* the body of a request, and the content-type it needs — shared by plain requests and SSE */
3691
+ \t#buildBody(opts: _RequestOptions, headers: Headers): BodyInit | undefined {
3692
+ \t\tif (opts.body !== undefined) {
3693
+ \t\t\tif (!headers.has("content-type")) headers.set("content-type", "application/octet-stream")
3694
+ \t\t\treturn opts.body as BodyInit
3695
+ \t\t}
3696
+ \t\tif (opts.json !== undefined) {
3697
+ \t\t\theaders.set("content-type", "application/json")
3698
+ \t\t\ttry {
3699
+ \t\t\t\treturn JSON.stringify(opts.json)
3700
+ \t\t\t} catch (e) {
3701
+ \t\t\t\tthrow new _ClientError({
3702
+ \t\t\t\t\tbody: opts.json,
3703
+ \t\t\t\t\tdata: null,
3704
+ \t\t\t\t\tmessage: \`JSON serialization failed: \${e instanceof Error ? e.message : String(e)}\`,
3705
+ \t\t\t\t\tresponse: new Response(null, { status: 0 }),
3706
+ \t\t\t\t\tstatus: 0,
3707
+ \t\t\t\t})
3708
+ \t\t\t}
3709
+ \t\t}
3710
+ \t\tif (opts.form === undefined) return undefined
3711
+ \t\tconst isFile = (v: unknown) =>
3712
+ \t\t\t(typeof File !== "undefined" && v instanceof File) || (typeof Blob !== "undefined" && v instanceof Blob)
3713
+ \t\tconst hasFiles = Object.values(opts.form).some(
3714
+ \t\t\t(v) =>
3715
+ \t\t\t\tisFile(v) ||
3716
+ \t\t\t\t(typeof FileList !== "undefined" && v instanceof FileList) ||
3717
+ \t\t\t\t(Array.isArray(v) && v.some(isFile)),
3718
+ \t\t)
3719
+ \t\tconst text = (v: unknown): string | null => {
3720
+ \t\t\tif (v === undefined || v === null || typeof v === "symbol") return null
3721
+ \t\t\tif (v instanceof Date) return v.toISOString()
3722
+ \t\t\tif (typeof v === "object") throw new TypeError("form: nested objects cannot be form-encoded; send json instead")
3723
+ \t\t\treturn String(v)
3724
+ \t\t}
3725
+ \t\tif (hasFiles) {
3726
+ \t\t\tconst fd = new FormData()
3727
+ \t\t\tfor (const [k, v] of Object.entries(opts.form)) {
3728
+ \t\t\t\tif (typeof FileList !== "undefined" && v instanceof FileList) {
3729
+ \t\t\t\t\tfor (let i = 0; i < v.length; i++) { const f = v[i]; if (f) fd.append(k, f) }
3730
+ \t\t\t\t\tcontinue
3731
+ \t\t\t\t}
3732
+ \t\t\t\tfor (const item of Array.isArray(v) ? v : [v]) {
3733
+ \t\t\t\t\tif (isFile(item)) fd.append(k, item as Blob)
3734
+ \t\t\t\t\telse {
3735
+ \t\t\t\t\t\tconst s = text(item)
3736
+ \t\t\t\t\t\tif (s !== null) fd.append(k, s)
3737
+ \t\t\t\t\t}
3738
+ \t\t\t\t}
3739
+ \t\t\t}
3740
+ \t\t\treturn fd
3741
+ \t\t}
3742
+ \t\theaders.set("content-type", "application/x-www-form-urlencoded")
3743
+ \t\tconst sp = new URLSearchParams()
3744
+ \t\t/* arrays repeat the key, as they do in multipart and in the query */
3745
+ \t\tfor (const [k, v] of Object.entries(opts.form)) {
3746
+ \t\t\tfor (const item of Array.isArray(v) ? v : [v]) {
3747
+ \t\t\t\tconst s = text(item)
3748
+ \t\t\t\tif (s !== null) sp.append(k, s)
3749
+ \t\t\t}
3750
+ \t\t}
3751
+ \t\treturn sp.toString()
3752
+ \t}
3753
+
3754
+ \t/* a stream body is sent once and needs duplex: "half" (Node's fetch throws without it) */
3755
+ \t#requestInit(init: RequestInit): RequestInit {
3756
+ \t\tif (typeof ReadableStream !== "undefined" && init.body instanceof ReadableStream) {
3757
+ \t\t\t;(init as RequestInit & { duplex?: string }).duplex = "half"
3758
+ \t\t}
3759
+ \t\treturn init
3760
+ \t}
3761
+
3762
+ \t/* the Request a response hook sees; a stream body was consumed by the send, so it is left off */
3763
+ \t#hookRequest(url: string, init: RequestInit): Request {
3764
+ \t\tconst consumed = typeof ReadableStream !== "undefined" && init.body instanceof ReadableStream
3765
+ \t\treturn new Request(url, consumed ? { ...init, body: null } : init)
3766
+ \t}
3767
+
3768
+ \tasync #doRequest(
3769
+ \t\tmethod: string,
3770
+ \t\tpath: string,
3771
+ \t\topts: _RequestOptions,
3772
+ \t\tisRetry: boolean,
3773
+ \t\trequestMeta?: _RequestMeta,
3774
+ \t): Promise<{ done: () => void; response: Response }> {
3775
+ \t\tconst url = this.#buildURL(path, opts)
3776
+ \t\tconst headers = await this.#buildHeaders(opts, { method, path })
3777
+ \t\tlet body = this.#buildBody(opts, headers)
3778
+
3779
+ \t\tif (this.#config.onRequest) {
3780
+ \t\t\tconst reqCtx: { body?: BodyInit; headers: Headers; invalidatedBy?: string[]; isStale?: boolean; method: string; path: string; selector?: string; state: Record<string, unknown>; url: string } = { body, headers, method, path, state: this.#config.state ?? {}, url }
3781
+ \t\t\tif (requestMeta) {
3782
+ \t\t\t\treqCtx.invalidatedBy = requestMeta.invalidatedBy
3783
+ \t\t\t\treqCtx.isStale = requestMeta.isStale
3784
+ \t\t\t\treqCtx.selector = requestMeta.selector
3785
+ \t\t\t}
3786
+ \t\t\tfor (const hook of this.#config.onRequest) {
3787
+ \t\t\t\tawait hook(reqCtx)
3788
+ \t\t\t}
3789
+ \t\t\tif (reqCtx.body !== body) body = reqCtx.body
3790
+ \t\t}
3791
+
3792
+ \t\tconst { signal, cleanup } = this.#buildSignal(opts)
3793
+ \t\tconst init = this.#requestInit({ body, headers, method, signal })
3794
+ \t\tif (this.#config.credentials) init.credentials = this.#config.credentials
3795
+ \t\tif (this.#config.mode) init.mode = this.#config.mode
3796
+
3797
+ \t\tconst _logOp = \`\${method.toUpperCase()} \${path}\`
3798
+ \t\tconst _logStart = Date.now()
3799
+ \t\tthis.#config.onLog?.({ duration_ms: 0, event: "request_start", level: "debug", operation: _logOp })
3800
+
3801
+ \t\ttry {
3802
+ \t\t\tsignal?.throwIfAborted()
3803
+ \t\t\tlet response = await this.#send(url, init)
3804
+
3805
+ \t\t\t/* FormData, Blob, and strings can be sent again; a stream was consumed by the first attempt */
3806
+ \t\t\tif (response.status === 401 && this.#config.onAuthExpired && !isRetry && !(body instanceof ReadableStream)) {
3807
+ \t\t\t\tconst authName = this.#config.authHeaderName ?? "Authorization"
3808
+ \t\t\t\tconst authPrefix = this.#config.authHeaderPrefix ?? "Bearer "
3809
+ \t\t\t\tconst sent = new Headers(headers).get(authName)
3810
+ \t\t\t\tconst rejectedToken = sent?.startsWith(authPrefix) ? sent.slice(authPrefix.length) : (sent ?? null)
3811
+ \t\t\t\tconst newToken = await this.#refreshToken(rejectedToken)
3812
+ \t\t\t\tif (newToken !== null) {
3813
+ \t\t\t\t\tconst retryHeaders = new Headers(headers)
3814
+ \t\t\t\t\tretryHeaders.set(authName, \`\${authPrefix}\${newToken}\`)
3815
+ \t\t\t\t\tawait response.body?.cancel().catch(() => {})
3816
+ \t\t\t\t\tresponse = await this.#send(url, { ...init, headers: retryHeaders })
3817
+ \t\t\t\t}
3818
+ \t\t\t}
3819
+
3820
+ \t\t\tthis.#config.onLog?.({
3821
+ \t\t\t\tduration_ms: Date.now() - _logStart,
3822
+ \t\t\t\tevent: "response_received",
3823
+ \t\t\t\tlevel: response.status >= 400 ? "warn" : "info",
3824
+ \t\t\t\toperation: _logOp,
3825
+ \t\t\t\tstatus: response.status,
3826
+ \t\t\t})
3827
+
3828
+ \t\t\tif (this.#config.onResponse) {
3829
+ \t\t\t\tconst resCtx: { invalidatedBy?: string[]; isRetry: boolean; isStale?: boolean; method: string; path: string; request: Request; response: Response; retry: () => Promise<Response>; selector?: string; state: Record<string, unknown>; url: string } = {
3830
+ \t\t\t\t\tisRetry,
3831
+ \t\t\t\t\tmethod,
3832
+ \t\t\t\t\tpath,
3833
+ \t\t\t\t\trequest: this.#hookRequest(url, init),
3834
+ \t\t\t\t\tresponse,
3835
+ \t\t\t\t\tretry: () => {
3836
+ \t\t\t\t\t\tif (isRetry) throw new Error("Max 1 retry per request")
3837
+ \t\t\t\t\t\tif (body instanceof ReadableStream) throw new Error("A streamed request body cannot be sent again")
3838
+ \t\t\t\t\t\treturn this.#doRequest(method, path, opts, true, requestMeta)
3839
+ \t\t\t\t\t\t\t.then(async (r) => {
3840
+ \t\t\t\t\t\t\t\tr.done()
3841
+ \t\t\t\t\t\t\t\tif (!r.response.ok) throw await this.#parseAsClientError(r.response)
3842
+ \t\t\t\t\t\t\t\treturn r.response
3843
+ \t\t\t\t\t\t\t})
3844
+ \t\t\t\t\t},
3845
+ \t\t\t\t\tstate: this.#config.state ?? {},
3846
+ \t\t\t\t\turl,
3847
+ \t\t\t\t}
3848
+ \t\t\t\tif (requestMeta) {
3849
+ \t\t\t\t\tresCtx.invalidatedBy = requestMeta.invalidatedBy
3850
+ \t\t\t\t\tresCtx.isStale = requestMeta.isStale
3851
+ \t\t\t\t\tresCtx.selector = requestMeta.selector
3852
+ \t\t\t\t}
3853
+ \t\t\t\tfor (const hook of this.#config.onResponse) {
3854
+ \t\t\t\t\tconst result = await hook(resCtx)
3855
+ \t\t\t\t\tif (result instanceof Response) {
3856
+ \t\t\t\t\t\tresponse = result
3857
+ \t\t\t\t\t\tresCtx.response = result
3858
+ \t\t\t\t\t}
3859
+ \t\t\t\t}
3860
+ \t\t\t}
3861
+
3862
+ \t\t\t/* the caller releases the timer after reading the body: the timeout covers the whole exchange */
3863
+ \t\t\treturn { done: cleanup, response }
3864
+ \t\t} catch (err) {
3865
+ \t\t\tcleanup()
3866
+ \t\t\tconst _errStatus = typeof (err as { status?: unknown })?.status === "number" ? (err as { status: number }).status : undefined
3867
+ \t\t\tthis.#config.onLog?.({
3868
+ \t\t\t\tduration_ms: Date.now() - _logStart,
3869
+ \t\t\t\terror: err,
3870
+ \t\t\t\tevent: "error",
3871
+ \t\t\t\tlevel: "error",
3872
+ \t\t\t\toperation: _logOp,
3873
+ \t\t\t\t...(_errStatus !== undefined ? { status: _errStatus } : {}),
3874
+ \t\t\t})
3875
+ \t\t\tthrow err
3876
+ \t\t}
3877
+ \t}
3878
+
3879
+ \t/* one refresh for every 401 in flight; the new token is kept for later requests */
3880
+ \t#refreshToken(rejectedToken: string | null): Promise<string | null> {
3881
+ \t\tconst hook = this.#config.onAuthExpired
3882
+ \t\tif (!hook) return Promise.resolve(null)
3883
+ \t\tif (rejectedToken !== null && this.#token !== null && rejectedToken !== this.#token) {
3884
+ \t\t\t/* another request already refreshed past the rejected token */
3885
+ \t\t\treturn Promise.resolve(this.#token)
3886
+ \t\t}
3887
+ \t\tthis.#refreshing ??= Promise.resolve()
3888
+ \t\t\t.then(() => hook({ rejectedToken }))
3889
+ \t\t\t.then((token) => {
3890
+ \t\t\t\tthis.#token = typeof token === "string" && token.length > 0 ? token : null
3891
+ \t\t\t\treturn this.#token
3892
+ \t\t\t})
3893
+ \t\t\t.finally(() => {
3894
+ \t\t\t\tthis.#refreshing = null
3895
+ \t\t\t})
3896
+ \t\treturn this.#refreshing
3897
+ \t}
3898
+
3899
+ \t/* Same rules as client/http.ts; the cross-origin header allowlist comes from
3900
+ \t client/redirect-policy.ts. "same-origin" (default) follows a redirect only while it stays on
3901
+ \t the base URL's origin; "follow" also follows cross-origin ones, carrying only allowlisted
3902
+ \t headers and never a replayed body.
3903
+ \t Browsers hide redirect targets from script, so there the platform follows. */
3904
+ \tasync #send(url: string, init: RequestInit): Promise<Response> {
3905
+ \t\tconst policy = this.#config.redirect ?? "same-origin"
3906
+ \t\tconst g = globalThis as { document?: unknown; WorkerGlobalScope?: unknown }
3907
+ \t\tconst platformFollows = typeof g.document !== "undefined" || typeof g.WorkerGlobalScope !== "undefined"
3908
+ \t\tif (policy === "manual" || policy === "error" || platformFollows) {
3909
+ \t\t\treturn this.#fetchFn(url, policy === "same-origin" ? init : { ...init, redirect: policy })
3910
+ \t\t}
3911
+ \t\tconst origin = new URL(url).origin
3912
+ \t\tlet current = new URL(url)
3913
+ \t\tlet currentInit: RequestInit = { ...init, redirect: "manual" }
3914
+ \t\tconst replayable = (b: unknown) =>
3915
+ \t\t\tb == null || typeof b === "string" || (typeof Blob !== "undefined" && b instanceof Blob) || (typeof FormData !== "undefined" && b instanceof FormData) || b instanceof URLSearchParams || b instanceof ArrayBuffer || ArrayBuffer.isView(b)
3916
+ \t\tfor (let hop = 0; ; hop++) {
3917
+ \t\t\tconst response = await this.#fetchFn(current.toString(), currentInit)
3918
+ \t\t\tconst location = response.headers.get("location")
3919
+ \t\t\tif (![301, 302, 303, 307, 308].includes(response.status) || location === null || hop >= 20) return response
3920
+ \t\t\tlet target: URL
3921
+ \t\t\ttry {
3922
+ \t\t\t\ttarget = new URL(location, current)
3923
+ \t\t\t} catch {
3924
+ \t\t\t\treturn response
3925
+ \t\t\t}
3926
+ \t\t\tif (target.protocol !== "http:" && target.protocol !== "https:") return response
3927
+ \t\t\tconst crossOrigin = target.origin !== origin
3928
+ \t\t\tif (crossOrigin && policy !== "follow") return response
3929
+ \t\t\tconst nextInit: RequestInit = { ...currentInit, headers: new Headers(currentInit.headers) }
3930
+ \t\t\tif (response.status === 307 || response.status === 308) {
3931
+ \t\t\t\tif (currentInit.body != null && (crossOrigin || !replayable(currentInit.body))) return response
3932
+ \t\t\t} else {
3933
+ \t\t\t\tconst method = (currentInit.method ?? "GET").toUpperCase()
3934
+ \t\t\t\tif (response.status === 303 ? method !== "HEAD" : method === "POST") {
3935
+ \t\t\t\t\tnextInit.method = "GET"
3936
+ \t\t\t\t\tnextInit.body = undefined
3937
+ \t\t\t\t\t;(nextInit.headers as Headers).delete("content-type")
3938
+ \t\t\t\t}
3939
+ \t\t\t}
3940
+ \t\t\tif (crossOrigin) {
3941
+ \t\t\t\t/* only allowlisted headers cross: no credential, configured, per-call or hook header */
3942
+ \t\t\t\tconst safe = new Headers()
3943
+ \t\t\t\tfor (const name of [${CROSS_ORIGIN_SAFE_HEADERS.map((h) => JSON.stringify(h)).join(", ")}]) {
3944
+ \t\t\t\t\tconst value = (nextInit.headers as Headers).get(name)
3945
+ \t\t\t\t\tif (value !== null) safe.set(name, value)
3946
+ \t\t\t\t}
3947
+ \t\t\t\tnextInit.headers = safe
3948
+ \t\t\t}
3949
+ \t\t\tawait response.body?.cancel().catch(() => {})
3950
+ \t\t\tcurrent = target
3951
+ \t\t\tcurrentInit = nextInit
3952
+ \t\t}
3953
+ \t}
3954
+ `;
3955
+ }
3956
+ function sdkClientParseBody() {
3957
+ return `
3958
+ \t#parseBody(response: Response): Promise<unknown> {
3959
+ \t\tif (response.status === 204) return Promise.resolve(null)
3960
+
3961
+ \t\tconst rawCt = response.headers.get("content-type") ?? ""
3962
+ \t\tconst ct = rawCt.split(";")[0]?.trim().toLowerCase() ?? ""
3963
+ \t\tif (ct === "application/json" || ct.endsWith("+json")) {
3964
+ \t\t\treturn response.json()
3965
+ \t\t}
3966
+ \t\tif (ct === "application/octet-stream" || ct === "application/pdf") {
3967
+ \t\t\treturn response.arrayBuffer()
3968
+ \t\t}
3969
+ \t\t/* the classes the generated types use: text/*, XML, otherwise bytes */
3970
+ \t\tif (ct.startsWith("text/") || ct === "application/xml" || ct.endsWith("+xml")) return response.text()
3971
+ \t\t/* unknown content type \u2014 binary-safe fallback */
3972
+ \t\treturn response.arrayBuffer()
3973
+ \t}
3974
+ `;
3975
+ }
3976
+ function sdkClientParseErrorBody() {
3977
+ return `
3978
+ \tasync #parseErrorBody(response: Response): Promise<unknown> {
3979
+ \t\ttry {
3980
+ \t\t\treturn await response.json()
3981
+ \t\t} catch {
3982
+ \t\t\treturn undefined
3983
+ \t\t}
3984
+ \t}
3985
+ `;
3986
+ }
3987
+ function sdkClientParseAsClientError() {
3988
+ return `
3989
+ \tasync #parseAsClientError(response: Response): Promise<_ClientError> {
3990
+ \t\tconst preserved = response.clone()
3991
+ \t\tconst body = await this.#parseErrorBody(response)
3992
+ \t\tconst msgVal = typeof body === "object" && body !== null && "message" in body
3993
+ \t\t\t? (body as Record<string, unknown>)["message"]
3994
+ \t\t\t: undefined
3995
+ \t\tconst rawMsg = typeof msgVal === "string" ? msgVal : null
3996
+ \t\tconst safeMsg = rawMsg !== null
3997
+ \t\t\t? rawMsg.replace(/[\\x00-\\x1f]/g, "").slice(0, this.#maxErrorMessageChars)
3998
+ \t\t\t: \`HTTP \${response.status}\`
3999
+ \t\tconst Cls = _STATUS_ERROR_MAP[response.status] ?? _ClientError
4000
+ \t\treturn new Cls({ body, data: body, message: safeMsg, response: preserved, status: response.status })
4001
+ \t}
4002
+ `;
4003
+ }
4004
+ function sdkClientBuildSignal() {
4005
+ return `
4006
+ \t#buildSignal(opts: _RequestOptions, isStream?: boolean): { cleanup: () => void; signal: AbortSignal } {
4007
+ \t\tconst userSignal = opts.signal
4008
+ \t\tconst timeout = isStream ? undefined : (opts.timeout ?? this.#config.timeout)
4009
+ \t\tif (!timeout && !userSignal) return { cleanup: () => {}, signal: this.#disposeCtrl.signal }
4010
+ \t\tconst ctrl = new AbortController()
4011
+ \t\tlet timer: ReturnType<typeof setTimeout> | undefined
4012
+ \t\tif (timeout) {
4013
+ \t\t\ttimer = setTimeout(() => ctrl.abort(new Error("timeout")), timeout)
4014
+ \t\t\tif (typeof (timer as unknown as { unref?: () => void }).unref === "function") {
4015
+ \t\t\t\t(timer as unknown as { unref: () => void }).unref()
4016
+ \t\t\t}
4017
+ \t\t}
4018
+ \t\tconst abort = (reason?: unknown) => { if (!ctrl.signal.aborted) ctrl.abort(reason) }
4019
+ \t\tconst onDispose = () => abort(this.#disposeCtrl.signal.reason)
4020
+ \t\tconst onUserAbort = () => abort(userSignal?.reason)
4021
+ \t\tif (this.#disposeCtrl.signal.aborted) {
4022
+ \t\t\tabort(this.#disposeCtrl.signal.reason)
4023
+ \t\t} else {
4024
+ \t\t\tthis.#disposeCtrl.signal.addEventListener("abort", onDispose, { once: true })
4025
+ \t\t}
4026
+ \t\tif (userSignal?.aborted) {
4027
+ \t\t\tabort(userSignal.reason)
4028
+ \t\t} else if (userSignal) {
4029
+ \t\t\tuserSignal.addEventListener("abort", onUserAbort, { once: true })
4030
+ \t\t}
4031
+ \t\treturn {
4032
+ \t\t\tcleanup: () => {
4033
+ \t\t\t\tif (timer !== undefined) clearTimeout(timer)
4034
+ \t\t\t\tthis.#disposeCtrl.signal.removeEventListener("abort", onDispose)
4035
+ \t\t\t\tuserSignal?.removeEventListener("abort", onUserAbort)
4036
+ \t\t\t},
4037
+ \t\t\tsignal: ctrl.signal,
4038
+ \t\t}
4039
+ \t}
4040
+ `;
4041
+ }
4042
+ function sdkClientRequestThrow() {
4043
+ return `
4044
+ \tasync #requestThrow(
4045
+ \t\tmethod: string,
4046
+ \t\tpath: string,
4047
+ \t\topts: _RequestOptions,
4048
+ \t\trequestMeta?: _RequestMeta,
4049
+ \t): Promise<unknown> {
4050
+ \t\tconst { done, response } = await this.#doRequest(method, path, opts, false, requestMeta)
4051
+ \t\ttry {
4052
+ \t\t\tif (!response.ok) throw await this.#parseAsClientError(response)
4053
+ \t\t\treturn await this.#parseBody(response)
4054
+ \t\t} finally {
4055
+ \t\t\tdone()
4056
+ \t\t}
4057
+ \t}
4058
+ `;
4059
+ }
4060
+ function sdkClientRequestSafe() {
4061
+ return `
4062
+ \tasync #requestSafe(
4063
+ \t\tmethod: string,
4064
+ \t\tpath: string,
4065
+ \t\topts: _RequestOptions,
4066
+ \t\trequestMeta?: _RequestMeta,
4067
+ \t): Promise<{
4068
+ \t\tdata: unknown
4069
+ \t\terror: unknown
4070
+ \t\tresponse: Response
4071
+ \t\tstatus: number
4072
+ \t}> {
4073
+ \t\tlet doResponse: { done: () => void; response: Response }
4074
+ \t\ttry {
4075
+ \t\t\tdoResponse = await this.#doRequest(method, path, opts, false, requestMeta)
4076
+ \t\t} catch (e) {
4077
+ \t\t\tif (e instanceof _ClientError) return { data: null, error: e, response: e.response, status: e.status }
4078
+ \t\t\tthrow e
4079
+ \t\t}
4080
+ \t\tconst { done, response } = doResponse
4081
+ \t\ttry {
4082
+ \t\t\tif (!response.ok) {
4083
+ \t\t\t\tconst preserved = response.clone()
4084
+ \t\t\t\tconst parsed = await this.#parseErrorBody(response)
4085
+ \t\t\t\t/* a failed request always has a truthy error: the JSON body, or a ClientError for any other body */
4086
+ \t\t\t\tconst error = parsed !== undefined && parsed !== null && parsed !== false && parsed !== "" && parsed !== 0
4087
+ \t\t\t\t\t? parsed
4088
+ \t\t\t\t\t: await this.#parseAsClientError(preserved.clone())
4089
+ \t\t\t\treturn { data: null, error, response: preserved, status: response.status }
4090
+ \t\t\t}
4091
+ \t\t\tlet data: unknown
4092
+ \t\t\ttry {
4093
+ \t\t\t\tdata = await this.#parseBody(response)
4094
+ \t\t\t} catch (e) {
4095
+ \t\t\t\treturn { data: null, error: e, response, status: response.status }
4096
+ \t\t\t}
4097
+ \t\t\treturn { data, error: null, response, status: response.status }
4098
+ \t\t} finally {
4099
+ \t\t\tdone()
4100
+ \t\t}
4101
+ \t}
4102
+ `;
4103
+ }
4104
+ function sdkClientRequest() {
4105
+ return `
4106
+ \tasync #request(entry: _ServiceEntry, input: Record<string, unknown>): Promise<unknown> {
4107
+ \t\tconst path = this.#toColonParams(entry.path, entry.wildcard)
4108
+ \t\tconst method = entry.method
4109
+ \t\t/* a copy: the caller's input object is never written to, so reusing it never reuses a key */
4110
+ \t\tconst opts = { ...input } as _RequestOptions
4111
+ \t\tif (entry.idempotent) {
4112
+ \t\t\tconst existing = opts.headers?.["Idempotency-Key"] ?? opts.headers?.["idempotency-key"]
4113
+ \t\t\tif (existing === undefined) {
4114
+ \t\t\t\tconst key = opts.idempotencyKey ?? this.#newRequestId()
4115
+ \t\t\t\topts.headers = { ...(opts.headers ?? {}), "Idempotency-Key": key }
4116
+ \t\t\t}
4117
+ \t\t}
4118
+ \t\tconst params = opts.params
4119
+ \t\tlet cp: string
4120
+ \t\ttry {
4121
+ \t\t\tcp = this.#interpolatePath(path, params)
4122
+ \t\t} catch (e) {
4123
+ \t\t\tif (!(e instanceof _PathParamError)) throw e
4124
+ \t\t\tconst err = new _ClientError({
4125
+ \t\t\t\tbody: null,
4126
+ \t\t\t\tdata: null,
4127
+ \t\t\t\tmessage: \`\${e.message} for \${method} \${entry.path}\`,
4128
+ \t\t\t\tresponse: Response.error(),
4129
+ \t\t\t\tstatus: 0,
4130
+ \t\t\t})
4131
+ \t\t\tif (!this.#config.throwOnError) return { data: null, error: err, response: err.response, status: 0 }
4132
+ \t\t\tthrow err
4133
+ \t\t}
4134
+ \t\tconst cs = \`\${method} \${cp}\`
4135
+ \t\tconst requestMeta = this.#buildRequestMeta(cs, cp, method)
4136
+ \t\tif (!this.#config.throwOnError) {
4137
+ \t\t\tconst r = await this.#requestSafe(method, path, opts, requestMeta)
4138
+ \t\t\tif (r.status >= 200 && r.status < 300) {
4139
+ \t\t\t\tthis.#markStale(entry.invalidate??[], params, cs)
4140
+ \t\t\t\tif (requestMeta?.isStale) this.#clearStale(cs, cp, method, requestMeta.seqSnapshot)
4141
+ \t\t\t}
4142
+ \t\t\treturn r
4143
+ \t\t}
4144
+ \t\tconst data = await this.#requestThrow(method, path, opts, requestMeta)
4145
+ \t\tthis.#markStale(entry.invalidate??[], params, cs)
4146
+ \t\tif (requestMeta?.isStale) this.#clearStale(cs, cp, method, requestMeta?.seqSnapshot ?? 0)
4147
+ \t\treturn data
4148
+ \t}
4149
+ `;
4150
+ }
4151
+ function sdkClientRequestSSE() {
4152
+ return `
4153
+ \t#requestSSE(entry: _ServiceEntry, path: string, opts: _RequestOptions): AsyncIterable<_SSEEvent> {
4154
+ \t\treturn { [Symbol.asyncIterator]: () => this.#doSSE(entry, path, opts) }
4155
+ \t}
4156
+ `;
4157
+ }
4158
+ function sdkClientConnectWS() {
4159
+ return `
4160
+ \t#connectWS(entry: _ServiceEntry, path: string, opts: _RequestOptions): _TypedWebSocket {
4161
+ \t\tconst built = new URL(this.#buildURL(path, opts))
4162
+ \t\t/* only the scheme changes: an https URL elsewhere in the query is left alone */
4163
+ \t\tif (built.protocol === "https:") built.protocol = "wss:"
4164
+ \t\telse if (built.protocol === "http:") built.protocol = "ws:"
4165
+ \t\tif (opts.reconnectToken) built.searchParams.append("reconnect_token", opts.reconnectToken)
4166
+ \t\tconst ws = this.#createTypedWebSocket(built.toString(), opts.protocols)
4167
+ \t\tif (entry.invalidate && entry.invalidate.length > 0) {
4168
+ \t\t\tconst invalidate = entry.invalidate
4169
+ \t\t\tconst params = opts.params
4170
+ \t\t\tconst cs = \`WS \${this.#interpolatePath(path, params)}\`
4171
+ \t\t\tws.on("open", () => this.#markStale(invalidate, params, cs))
4172
+ \t\t}
4173
+ \t\treturn ws
4174
+ \t}
4175
+ `;
4176
+ }
4177
+ function sdkClientBuildRequestMeta() {
4178
+ return `
4179
+ \t#buildRequestMeta(concreteSelector: string, concretePath: string, method: string): _RequestMeta | undefined {
4180
+ \t\tif (!this.#staleUntil) return undefined
4181
+ \t\tconst now = Date.now()
4182
+ \t\tconst { by, isStale } = this.#lookupStale(concreteSelector, concretePath, method, now)
4183
+ \t\treturn { invalidatedBy: by, isStale, selector: concreteSelector, seqSnapshot: this.#invalidationSeq }
4184
+ \t}
4185
+ `;
4186
+ }
4187
+ function sdkClientMarkStale() {
4188
+ return `
4189
+ \t#markStale(invalidate: readonly string[], params: Record<string, string> | undefined, mutationSelector: string): void {
4190
+ \t\tconst exactTable = this.#staleUntil
4191
+ \t\tif (!exactTable || invalidate.length === 0) return
4192
+ \t\tconst seq = ++this.#invalidationSeq
4193
+ \t\tconst until = Date.now() + this.#staleTime
4194
+ \t\tfor (const target of this.#resolveInvalidationTargets(invalidate, params)) {
4195
+ \t\t\tconst spaceIdx = target.indexOf(" ")
4196
+ \t\t\tconst method = target.slice(0, spaceIdx)
4197
+ \t\t\tconst isPattern = this.#hasPlaceholder(target.slice(spaceIdx + 1))
4198
+ \t\t\tlet table: Map<string, _StaleEntry>
4199
+ \t\t\tif (isPattern) {
4200
+ \t\t\t\ttable = this.#stalePatterns.get(method) ?? new Map<string, _StaleEntry>()
4201
+ \t\t\t\tthis.#stalePatterns.set(method, table)
4202
+ \t\t\t} else {
4203
+ \t\t\t\ttable = exactTable
4204
+ \t\t\t}
4205
+ \t\t\tconst existing = table.get(target)
4206
+ \t\t\tif (existing) {
4207
+ \t\t\t\tif (!existing.by.includes(mutationSelector)) {
4208
+ \t\t\t\t\texisting.by.push(mutationSelector)
4209
+ \t\t\t\t\tif (existing.by.length > this.#maxSourcesPerTarget) existing.by.shift()
4210
+ \t\t\t\t}
4211
+ \t\t\t\texisting.until = until
4212
+ \t\t\t\texisting.seq = seq
4213
+ \t\t\t\t/* a new mutation re-marks every instance */
4214
+ \t\t\t\texisting.refreshed?.clear()
4215
+ \t\t\t} else {
4216
+ \t\t\t\ttable.set(target, { by: [mutationSelector], refreshed: isPattern ? new Set<string>() : undefined, seq, until })
4217
+ \t\t\t}
4218
+ \t\t}
4219
+ \t\tif (exactTable.size > this.#staleMaxEntries) {
4220
+ \t\t\tconst now = Date.now()
4221
+ \t\t\tfor (const [k, entry] of exactTable) if (entry.until <= now) exactTable.delete(k)
4222
+ \t\t\t/* still over: drop the oldest marks */
4223
+ \t\t\tfor (const k of exactTable.keys()) {
4224
+ \t\t\t\tif (exactTable.size <= this.#staleMaxEntries) break
4225
+ \t\t\t\texactTable.delete(k)
4226
+ \t\t\t}
4227
+ \t\t}
4228
+ \t}
4229
+ `;
4230
+ }
4231
+ function sdkClientClearStale() {
4232
+ return `
4233
+ \t/* a successful stale read clears that resource only: its exact mark, and this path's share of a pattern mark */
4234
+ \t#clearStale(concreteSelector: string, concretePath: string, method: string, seqSnapshot: number): void {
4235
+ \t\tif (!this.#staleUntil) return
4236
+ \t\tconst exact = this.#staleUntil.get(concreteSelector)
4237
+ \t\tif (exact && exact.seq <= seqSnapshot) this.#staleUntil.delete(concreteSelector)
4238
+ \t\tconst table = this.#stalePatterns.get(method)
4239
+ \t\tif (!table) return
4240
+ \t\tfor (const [key, entry] of table) {
4241
+ \t\t\tif (entry.seq > seqSnapshot || !entry.refreshed) continue
4242
+ \t\t\tif (!this.#pathMatchesPattern(concretePath, key.slice(key.indexOf(" ") + 1))) continue
4243
+ \t\t\tif (entry.refreshed.size >= 1024) {
4244
+ \t\t\t\tconst oldest = entry.refreshed.values().next().value
4245
+ \t\t\t\tif (oldest !== undefined) entry.refreshed.delete(oldest)
4246
+ \t\t\t}
4247
+ \t\t\tentry.refreshed.add(concretePath)
4248
+ \t\t}
4249
+ \t}
4250
+ `;
4251
+ }
4252
+ function sdkClientDispose() {
4253
+ return `
4254
+ \tdispose(): void {
4255
+ \t\tif (this.#disposed) return
4256
+ \t\tthis.#disposed = true
4257
+ \t\tthis.#disposeCtrl.abort()
4258
+ \t\tthis.#staleUntil?.clear()
4259
+ \t\tthis.#stalePatterns.clear()
4260
+ \t\tthis.#resourceCache.clear()
4261
+ \t\tthis.#patternRegexCache.clear()
4262
+ \t}
4263
+ `;
4264
+ }
4265
+ function sdkClientDoSSE() {
4266
+ return `
4267
+ \tasync *#doSSE(entry: _ServiceEntry, path: string, opts: _RequestOptions): AsyncGenerator<_SSEEvent> {
4268
+ \t\tconst method = entry.method
4269
+ \t\tconst url = this.#buildURL(path, opts)
4270
+ \t\tconst headers = await this.#buildHeaders(opts, { method, path })
4271
+ \t\theaders.set("accept", "text/event-stream")
4272
+ \t\tif (opts.lastEventId) {
4273
+ \t\t\theaders.set("last-event-id", opts.lastEventId)
4274
+ \t\t}
4275
+ \t\t/* a streamed operation carries its body like any other request */
4276
+ \t\tlet body = this.#buildBody(opts, headers)
4277
+ \t\tconst { signal, cleanup } = this.#buildSignal(opts, true)
4278
+ \t\ttry {
4279
+ \t\t\tif (this.#config.onRequest) {
4280
+ \t\t\t\tconst reqCtx: { body?: BodyInit; headers: Headers; invalidatedBy?: string[]; isStale?: boolean; method: string; path: string; selector?: string; state: Record<string, unknown>; url: string } = { body, headers, method, path, state: this.#config.state ?? {}, url }
4281
+ \t\t\t\tfor (const hook of this.#config.onRequest) {
4282
+ \t\t\t\t\tawait hook(reqCtx)
4283
+ \t\t\t\t}
4284
+ \t\t\t\tif (reqCtx.body !== body) body = reqCtx.body
4285
+ \t\t\t}
4286
+ \t\t\tconst sseInit = this.#requestInit({ body, headers, method, signal })
4287
+ \t\t\tif (this.#config.credentials) sseInit.credentials = this.#config.credentials
4288
+ \t\t\tif (this.#config.mode) sseInit.mode = this.#config.mode
4289
+ \t\t\tsignal?.throwIfAborted()
4290
+ \t\t\tlet response = await this.#fetchFn(url, sseInit)
4291
+ \t\t\tif (this.#config.onResponse) {
4292
+ \t\t\t\tconst resCtx: { invalidatedBy?: string[]; isRetry: boolean; isStale?: boolean; method: string; path: string; request: Request; response: Response; retry: () => Promise<Response>; selector?: string; state: Record<string, unknown>; url: string } = {
4293
+ \t\t\t\t\tisRetry: false, method, path, request: this.#hookRequest(url, sseInit), response,
4294
+ \t\t\t\t\tretry: () => { throw new Error("SSE streams do not support retry") },
4295
+ \t\t\t\t\tstate: this.#config.state ?? {}, url,
4296
+ \t\t\t\t}
4297
+ \t\t\t\tfor (const hook of this.#config.onResponse) {
4298
+ \t\t\t\t\tconst result = await hook(resCtx)
4299
+ \t\t\t\t\tif (result instanceof Response) { response = result; resCtx.response = result }
4300
+ \t\t\t\t}
4301
+ \t\t\t}
4302
+ \t\t\tif (!response.ok) {
4303
+ \t\t\t\tthrow await this.#parseAsClientError(response)
4304
+ \t\t\t}
4305
+ \t\t\tif (entry.invalidate && entry.invalidate.length > 0) {
4306
+ \t\t\t\tconst cs = \`\${method} \${this.#interpolatePath(path, opts.params)}\`
4307
+ \t\t\t\tthis.#markStale(entry.invalidate, opts.params, cs)
4308
+ \t\t\t}
4309
+ \t\t\tif (!response.body) return
4310
+ \t\t\tyield* this.#parseSSE(response.body)
4311
+ \t\t} finally {
4312
+ \t\t\tcleanup()
4313
+ \t\t}
4314
+ \t}
4315
+ `;
4316
+ }
4317
+ function sdkClientParseSSEBlock() {
4318
+ return `
4319
+ \t/* Keep in sync with client/sse.ts — the WHATWG event-stream rules: lines end at CRLF, LF or CR;
4320
+ \t a leading BOM is skipped; id is sticky and ignored if it holds NUL; retry is digits only; an
4321
+ \t event still open when the stream ends is discarded, never dispatched. */
4322
+ \tasync *#parseSSE(stream: ReadableStream<Uint8Array>): AsyncGenerator<_SSEEvent> {
4323
+ \t\tconst decoder = new TextDecoder()
4324
+ \t\tconst reader = stream.getReader()
4325
+ \t\tconst maxBuffer = this.#sseMaxBufferChars
4326
+ \t\tlet pending = ""
4327
+ \t\tlet first = true
4328
+ \t\tlet data: string[] = []
4329
+ \t\tlet dataSize = 0
4330
+ \t\tlet hasData = false
4331
+ \t\tlet event: string | undefined
4332
+ \t\tlet lastId = ""
4333
+ \t\tlet retry: number | undefined
4334
+ \t\tconst dispatch = (): _SSEEvent | undefined => {
4335
+ \t\t\tconst out: _SSEEvent | undefined = hasData ? { data: data.join("\\n") } : undefined
4336
+ \t\t\tif (out) {
4337
+ \t\t\t\tif (event !== undefined && event !== "") out.event = event
4338
+ \t\t\t\tif (lastId !== "") out.id = lastId
4339
+ \t\t\t\tif (retry !== undefined) out.retry = retry
4340
+ \t\t\t\tretry = undefined
4341
+ \t\t\t}
4342
+ \t\t\tdata = []
4343
+ \t\t\tdataSize = 0
4344
+ \t\t\thasData = false
4345
+ \t\t\tevent = undefined
4346
+ \t\t\treturn out
4347
+ \t\t}
4348
+ \t\tconst processLine = (line: string): void => {
4349
+ \t\t\tif (line.charCodeAt(0) === 58) return
4350
+ \t\t\tconst colon = line.indexOf(":")
4351
+ \t\t\tconst field = colon === -1 ? line : line.slice(0, colon)
4352
+ \t\t\tlet value = colon === -1 ? "" : line.slice(colon + 1)
4353
+ \t\t\tif (value.charCodeAt(0) === 32) value = value.slice(1)
4354
+ \t\t\tswitch (field) {
4355
+ \t\t\t\tcase "data":
4356
+ \t\t\t\t\tdataSize += value.length + 1
4357
+ \t\t\t\t\tif (dataSize > maxBuffer) throw new Error(\`SSE event exceeded \${maxBuffer} characters\`)
4358
+ \t\t\t\t\tdata.push(value)
4359
+ \t\t\t\t\thasData = true
4360
+ \t\t\t\t\tbreak
4361
+ \t\t\t\tcase "event":
4362
+ \t\t\t\t\tevent = value
4363
+ \t\t\t\t\tbreak
4364
+ \t\t\t\tcase "id":
4365
+ \t\t\t\t\tif (!value.includes("\\0")) lastId = value
4366
+ \t\t\t\t\tbreak
4367
+ \t\t\t\tcase "retry":
4368
+ \t\t\t\t\tif (/^\\d+$/.test(value)) retry = Number(value)
4369
+ \t\t\t\t\tbreak
4370
+ \t\t\t}
4371
+ \t\t}
4372
+ \t\ttry {
4373
+ \t\t\twhile (true) {
4374
+ \t\t\t\tconst { done, value } = await reader.read()
4375
+ \t\t\t\tif (done) break
4376
+ \t\t\t\tpending += decoder.decode(value, { stream: true })
4377
+ \t\t\t\tif (first && pending.length > 0) {
4378
+ \t\t\t\t\tif (pending.charCodeAt(0) === 0xfeff) pending = pending.slice(1)
4379
+ \t\t\t\t\tfirst = false
4380
+ \t\t\t\t}
4381
+ \t\t\t\tlet start = 0
4382
+ \t\t\t\twhile (start < pending.length) {
4383
+ \t\t\t\t\tlet end = start
4384
+ \t\t\t\t\twhile (end < pending.length) {
4385
+ \t\t\t\t\t\tconst c = pending.charCodeAt(end)
4386
+ \t\t\t\t\t\tif (c === 10 || c === 13) break
4387
+ \t\t\t\t\t\tend++
4388
+ \t\t\t\t\t}
4389
+ \t\t\t\t\tif (end === pending.length) break
4390
+ \t\t\t\t\t/* a CR as the last character may be the first half of CRLF */
4391
+ \t\t\t\t\tif (pending.charCodeAt(end) === 13 && end === pending.length - 1) break
4392
+ \t\t\t\t\tconst line = pending.slice(start, end)
4393
+ \t\t\t\t\tstart = end + (pending.charCodeAt(end) === 13 && pending.charCodeAt(end + 1) === 10 ? 2 : 1)
4394
+ \t\t\t\t\tif (line === "") {
4395
+ \t\t\t\t\t\tconst out = dispatch()
4396
+ \t\t\t\t\t\tif (out) yield out
4397
+ \t\t\t\t\t} else {
4398
+ \t\t\t\t\t\tprocessLine(line)
4399
+ \t\t\t\t\t}
4400
+ \t\t\t\t}
4401
+ \t\t\t\tpending = pending.slice(start)
4402
+ \t\t\t\tif (pending.length > maxBuffer) throw new Error(\`SSE buffer exceeded \${maxBuffer} characters\`)
4403
+ \t\t\t}
4404
+ \t\t\t/* a lone CR held back at the end of the stream still terminates its line */
4405
+ \t\t\tif (pending === "\\r") {
4406
+ \t\t\t\tconst out = dispatch()
4407
+ \t\t\t\tif (out) yield out
4408
+ \t\t\t}
4409
+ \t\t\t/* anything else still open is an incomplete event: discard it */
4410
+ \t\t} finally {
4411
+ \t\t\tawait reader.cancel().catch(() => {})
4412
+ \t\t\treader.releaseLock()
4413
+ \t\t}
4414
+ \t}
4415
+ `;
4416
+ }
4417
+ function sdkClientFooter() {
4418
+ const reexports = ["\t_ClientError as ClientError,"]
4419
+ .concat(STATUS_ERROR_CLASSES.map(({ name }) => `\t_${name} as ${name},`))
4420
+ .join("\n");
4421
+ return `}
4422
+
4423
+ export {
4424
+ ${reexports}
4425
+ }
4426
+
4427
+ export function isClientError(e: unknown): e is _ClientError {
4428
+ \treturn e instanceof _ClientError
4429
+ }
4430
+ `;
4431
+ }
4432
+ /** The IR every TypeScript SDK emitter reads: refs inlined, derived ids, duplicate ids refused. */
4433
+ export function sdkIR(spec) {
4434
+ return toIR(resolveRefs(spec), { deriveOperationIds: true, duplicateOperationIds: "throw" });
4435
+ }
4436
+ /** Flat `resource → action → entry` map of every operation, for SDK result objects. */
4437
+ export function serviceMapOf(ir) {
4438
+ /* null-prototype: an operationId of "__proto__.x" must not reach Object.prototype */
4439
+ const serviceMap = Object.create(null);
4440
+ for (const op of ir.operations) {
4441
+ const segments = op.id.split(".");
4442
+ const resource = segments.length === 1 ? op.id : (segments[0] ?? op.id);
4443
+ const action = segments.length === 1 ? "_call" : segments.slice(1).join(".");
4444
+ if (!Object.hasOwn(serviceMap, resource))
4445
+ serviceMap[resource] = Object.create(null);
4446
+ serviceMap[resource][action] = buildServiceEntryForOp(op);
4447
+ }
4448
+ return serviceMap;
4449
+ }
4450
+ export function collectSDKMethods(spec,
4451
+ /* toIR validates namespace collisions; an operation without an operationId gets a derived
4452
+ one instead of silently vanishing from the SDK */
4453
+ ir = sdkIR(spec)) {
4454
+ const resolve = irResolver(ir.schemas);
4455
+ const methods = [];
4456
+ for (const op of ir.operations) {
4457
+ const operationId = op.id;
4458
+ const segments = operationId.split(".");
4459
+ const isTopLevel = segments.length === 1;
4460
+ const resource = isTopLevel ? operationId : (segments[0] ?? operationId);
4461
+ const action = isTopLevel ? "_call" : segments.slice(1).join(".");
4462
+ const inputResult = emitSDKInputType(op);
4463
+ methods.push({
4464
+ action,
4465
+ errorsByStatusType: emitSDKErrorsByStatusType(op, resolve),
4466
+ id: operationId,
4467
+ inputHasMandatory: inputResult.hasMandatory,
4468
+ inputType: inputResult.type,
4469
+ realtime: op.extensions.realtime === true,
4470
+ resource,
4471
+ responseType: emitSDKResponseType(op),
4472
+ sse: op.extensions.sse === true,
4473
+ ws: op.extensions.websocket === true,
4474
+ });
4475
+ }
4476
+ const nestedMap = buildNestedServiceMap(safeMemberTree(ir.tree, true));
4477
+ return { methods, nestedMap, serviceMap: serviceMapOf(ir) };
4478
+ }
4479
+ function buildServiceEntryForOp(op) {
4480
+ const params = openApiPathParams(op.path);
4481
+ const entry = { method: op.method, path: op.path };
4482
+ if (params.length > 0)
4483
+ entry.params = params;
4484
+ const wildcard = op.params.path.find((p) => p.wildcard === true);
4485
+ if (wildcard)
4486
+ entry.wildcard = wildcard.name;
4487
+ if (op.extensions.sse)
4488
+ entry.sse = true;
4489
+ if (op.extensions.websocket)
4490
+ entry.ws = true;
4491
+ if (op.extensions.realtime)
4492
+ entry.realtime = true;
4493
+ if (op.extensions.idempotencyKey)
4494
+ entry.idempotent = true;
4495
+ const inv = op.extensions.invalidates;
4496
+ if (inv && inv.length > 0)
4497
+ entry.invalidate = inv;
4498
+ return entry;
4499
+ }
4500
+ /** Members a generated client already has; a resource of the same name would hide them. */
4501
+ const SDK_ROOT_MEMBERS = new Set(["dispose", "state"]);
4502
+ /**
4503
+ * Names JavaScript machinery reads off any object: a `then` member makes the client a
4504
+ * thenable, `toString` breaks `String(sdk)`, `__proto__` sets the prototype.
4505
+ */
4506
+ const SDK_OBJECT_MEMBERS = new Set([
4507
+ "__defineGetter__",
4508
+ "__defineSetter__",
4509
+ "__lookupGetter__",
4510
+ "__lookupSetter__",
4511
+ "__proto__",
4512
+ "constructor",
4513
+ "hasOwnProperty",
4514
+ "isPrototypeOf",
4515
+ "propertyIsEnumerable",
4516
+ "then",
4517
+ "toJSON",
4518
+ "toLocaleString",
4519
+ "toString",
4520
+ "valueOf",
4521
+ ]);
4522
+ /**
4523
+ * The resource tree with every member name a client can expose. A name that would shadow a
4524
+ * client member or an `Object.prototype` name gets a trailing `_` (`state` → `state_`), the
4525
+ * same rename in the interface and the service map, so types and runtime agree.
4526
+ */
4527
+ function safeMemberTree(ns, root) {
4528
+ const entries = new Map();
4529
+ const sorted = [...ns.entries.entries()].sort(([a], [b]) => compareCodeUnits(a, b));
4530
+ for (const [key, entry] of sorted) {
4531
+ let name = key;
4532
+ if ((root && SDK_ROOT_MEMBERS.has(key)) || SDK_OBJECT_MEMBERS.has(key)) {
4533
+ name = `${key}_`;
4534
+ while (ns.entries.has(name) || entries.has(name))
4535
+ name += "_";
4536
+ }
4537
+ entries.set(name, entry.kind === "namespace" ? { kind: "namespace", ns: safeMemberTree(entry.ns, false) } : entry);
4538
+ }
4539
+ return { entries };
4540
+ }
4541
+ /**
4542
+ * The member path of every operation on a generated TypeScript client, after the renames
4543
+ * `safeMemberTree` applies: `["state_", "get"]` for an operation `state.get`. A namespace whose
4544
+ * only method is `_call` is callable itself, so its path ends at the namespace.
4545
+ */
4546
+ export function sdkMemberPaths(ir) {
4547
+ const out = new Map();
4548
+ const walk = (ns, prefix) => {
4549
+ const methods = methodsOf(ns);
4550
+ for (const [key, entry] of ns.entries) {
4551
+ if (entry.kind === "namespace") {
4552
+ walk(entry.ns, [...prefix, key]);
4553
+ }
4554
+ else if (key !== "_call") {
4555
+ out.set(entry.op.id, [...prefix, key]);
4556
+ }
4557
+ else if (methods.length === 1) {
4558
+ /* the interface promotes a lone `_call` to the namespace itself and drops any other */
4559
+ out.set(entry.op.id, prefix);
4560
+ }
4561
+ }
4562
+ };
4563
+ walk(safeMemberTree(ir.tree, true), []);
4564
+ return out;
4565
+ }
4566
+ function buildNestedServiceMap(ns) {
4567
+ function walkNs(namespace) {
4568
+ const map = new Map();
4569
+ for (const [key, entry] of [...namespace.entries.entries()].sort(([a], [b]) => compareCodeUnits(a, b))) {
4570
+ if (entry.kind === "method")
4571
+ map.set(key, { entry: buildServiceEntryForOp(entry.op), kind: "leaf" });
4572
+ else
4573
+ map.set(key, { children: walkNs(entry.ns), kind: "ns" });
4574
+ }
4575
+ return map;
4576
+ }
4577
+ return walkNs(ns);
4578
+ }
4579
+ const TS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
4580
+ /** The SDK class name lands in source, the stem in import specifiers and file names. */
4581
+ function validateSdkNames(name, stem) {
4582
+ if (!TS_IDENTIFIER.test(name) || SDK_TS_RESERVED.has(name)) {
4583
+ throw new Error(`generateSDK: name ${JSON.stringify(name)} is not a TypeScript identifier`);
4584
+ }
4585
+ if (!/^[A-Za-z0-9_-][A-Za-z0-9._-]*$/.test(stem)) {
4586
+ throw new Error(`generateSDK: stem ${JSON.stringify(stem)} must be a plain file name (letters, digits, ".", "_", "-")`);
4587
+ }
4588
+ }
4589
+ export function generateSDK(spec, options) {
4590
+ const sdkName = options?.name ?? "SDK";
4591
+ const stem = options?.stem ?? "sdk";
4592
+ validateSdkNames(sdkName, stem);
4593
+ const ir = sdkIR(spec);
4594
+ const { methods: sdkMethods, nestedMap, serviceMap } = collectSDKMethods(spec, ir);
4595
+ const hasRealtime = sdkMethods.some((m) => m.realtime);
4596
+ const fullMethodLookup = new Map();
4597
+ for (const m of sdkMethods)
4598
+ fullMethodLookup.set(m.id, m);
4599
+ return {
4600
+ files: {
4601
+ client: buildSDKClient(sdkName, stem),
4602
+ index: buildSDKIndex(sdkName, stem),
4603
+ map: buildSDKMap(nestedMap),
4604
+ runtime: hasRealtime ? buildSDKRuntime() : null,
4605
+ types: buildSDKTypes(sdkName, sdkMethods, safeMemberTree(ir.tree, true), fullMethodLookup),
4606
+ },
4607
+ serviceMap,
4608
+ };
4609
+ }
4610
+ //# sourceMappingURL=codegen.js.map