@lovrozagar/honey 0.7.3 → 1.0.0-rc.3

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