@redocly/client-generator 0.3.7 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (617) hide show
  1. package/README.md +32 -54
  2. package/eject-assets/AGENTS.md +137 -0
  3. package/eject-assets/generators/cli/docs.ts +224 -0
  4. package/eject-assets/generators/cli/engine-source.ts +21 -0
  5. package/eject-assets/generators/cli/index.ts +90 -0
  6. package/eject-assets/generators/cli/render.ts +348 -0
  7. package/eject-assets/generators/go/client.ts +57 -0
  8. package/eject-assets/generators/go/descriptor.ts +41 -0
  9. package/eject-assets/generators/go/index.ts +336 -0
  10. package/eject-assets/generators/go/models.ts +179 -0
  11. package/eject-assets/generators/go/naming.ts +49 -0
  12. package/eject-assets/generators/go/operations.ts +296 -0
  13. package/eject-assets/generators/go/pagination.ts +194 -0
  14. package/eject-assets/generators/go/types.ts +60 -0
  15. package/eject-assets/generators/mock/faker.ts +214 -0
  16. package/eject-assets/generators/mock/index.ts +35 -0
  17. package/eject-assets/generators/mock/render.ts +282 -0
  18. package/eject-assets/generators/mock/sample.ts +316 -0
  19. package/eject-assets/generators/mock/values.ts +63 -0
  20. package/eject-assets/generators/php/client.ts +63 -0
  21. package/eject-assets/generators/php/descriptor.ts +58 -0
  22. package/eject-assets/generators/php/index.ts +237 -0
  23. package/eject-assets/generators/php/models.ts +275 -0
  24. package/eject-assets/generators/php/naming.ts +51 -0
  25. package/eject-assets/generators/php/operations.ts +232 -0
  26. package/eject-assets/generators/php/pagination.ts +133 -0
  27. package/eject-assets/generators/php/types.ts +148 -0
  28. package/eject-assets/generators/python/client.ts +123 -0
  29. package/eject-assets/generators/python/descriptor.ts +52 -0
  30. package/eject-assets/generators/python/index.ts +251 -0
  31. package/eject-assets/generators/python/models.ts +242 -0
  32. package/eject-assets/generators/python/naming.ts +46 -0
  33. package/eject-assets/generators/python/operations.ts +147 -0
  34. package/eject-assets/generators/python/pagination.ts +128 -0
  35. package/eject-assets/generators/python/types.ts +54 -0
  36. package/eject-assets/generators/swr/index.ts +37 -0
  37. package/eject-assets/generators/swr/render.ts +78 -0
  38. package/eject-assets/generators/tanstack-query/index.ts +48 -0
  39. package/eject-assets/generators/tanstack-query/render.ts +346 -0
  40. package/eject-assets/generators/transformers/index.ts +46 -0
  41. package/eject-assets/generators/transformers/render.ts +506 -0
  42. package/eject-assets/generators/typescript/banner.ts +35 -0
  43. package/eject-assets/generators/typescript/client.ts +254 -0
  44. package/eject-assets/generators/typescript/descriptor.ts +137 -0
  45. package/eject-assets/generators/typescript/index.ts +101 -0
  46. package/eject-assets/generators/typescript/inline-runtime.ts +135 -0
  47. package/eject-assets/generators/typescript/operation-signature.ts +62 -0
  48. package/eject-assets/generators/typescript/operation-types.ts +17 -0
  49. package/eject-assets/generators/typescript/operations.ts +524 -0
  50. package/eject-assets/generators/typescript/response-headers.ts +74 -0
  51. package/eject-assets/generators/typescript/type-guards.ts +159 -0
  52. package/eject-assets/generators/typescript/types.ts +172 -0
  53. package/eject-assets/generators/zod/index.ts +32 -0
  54. package/eject-assets/generators/zod/schemas.ts +456 -0
  55. package/eject-assets/skills/cli-generator/SKILL.md +121 -0
  56. package/eject-assets/skills/client-generators/SKILL.md +142 -0
  57. package/eject-assets/skills/go-generator/SKILL.md +96 -0
  58. package/eject-assets/skills/mock-generator/SKILL.md +50 -0
  59. package/eject-assets/skills/php-generator/SKILL.md +112 -0
  60. package/eject-assets/skills/python-generator/SKILL.md +110 -0
  61. package/eject-assets/skills/swr-generator/SKILL.md +50 -0
  62. package/eject-assets/skills/tanstack-query-generator/SKILL.md +55 -0
  63. package/eject-assets/skills/transformers-generator/SKILL.md +47 -0
  64. package/eject-assets/skills/typescript-generator/SKILL.md +95 -0
  65. package/eject-assets/skills/zod-generator/SKILL.md +54 -0
  66. package/lib/authoring/index.d.ts +11 -0
  67. package/lib/authoring/index.d.ts.map +1 -0
  68. package/lib/authoring/index.js +42 -0
  69. package/lib/authoring/index.js.map +1 -0
  70. package/lib/authoring/naming.d.ts +34 -0
  71. package/lib/authoring/naming.d.ts.map +1 -0
  72. package/lib/authoring/naming.js +104 -0
  73. package/lib/authoring/naming.js.map +1 -0
  74. package/lib/authoring/operation.d.ts +47 -0
  75. package/lib/authoring/operation.d.ts.map +1 -0
  76. package/lib/authoring/operation.js +86 -0
  77. package/lib/authoring/operation.js.map +1 -0
  78. package/lib/authoring/options.d.ts +9 -0
  79. package/lib/authoring/options.d.ts.map +1 -0
  80. package/lib/authoring/options.js +5 -0
  81. package/lib/authoring/options.js.map +1 -0
  82. package/lib/authoring/pagination.d.ts +19 -0
  83. package/lib/authoring/pagination.d.ts.map +1 -0
  84. package/lib/authoring/pagination.js +45 -0
  85. package/lib/authoring/pagination.js.map +1 -0
  86. package/lib/authoring/printer.d.ts +16 -0
  87. package/lib/authoring/printer.d.ts.map +1 -0
  88. package/lib/authoring/printer.js +36 -0
  89. package/lib/authoring/printer.js.map +1 -0
  90. package/lib/authoring/reference-page.d.ts +31 -0
  91. package/lib/authoring/reference-page.d.ts.map +1 -0
  92. package/lib/authoring/reference-page.js +160 -0
  93. package/lib/authoring/reference-page.js.map +1 -0
  94. package/lib/authoring/schema.d.ts +51 -0
  95. package/lib/authoring/schema.d.ts.map +1 -0
  96. package/lib/authoring/schema.js +190 -0
  97. package/lib/authoring/schema.js.map +1 -0
  98. package/lib/cli-contract.d.ts +126 -0
  99. package/lib/cli-contract.d.ts.map +1 -0
  100. package/lib/cli-contract.js +25 -0
  101. package/lib/cli-contract.js.map +1 -0
  102. package/lib/{emitters/wrapper-support.d.ts → contracts/typescript.d.ts} +13 -24
  103. package/lib/contracts/typescript.d.ts.map +1 -0
  104. package/lib/contracts/typescript.js +87 -0
  105. package/lib/contracts/typescript.js.map +1 -0
  106. package/lib/generate.d.ts +10 -10
  107. package/lib/generate.d.ts.map +1 -1
  108. package/lib/generate.js +25 -106
  109. package/lib/generate.js.map +1 -1
  110. package/lib/generators/cli/docs.d.ts +19 -0
  111. package/lib/generators/cli/docs.d.ts.map +1 -0
  112. package/lib/generators/cli/docs.js +187 -0
  113. package/lib/generators/cli/docs.js.map +1 -0
  114. package/lib/generators/cli/engine-source.d.ts +5 -0
  115. package/lib/generators/cli/engine-source.d.ts.map +1 -0
  116. package/lib/generators/cli/engine-source.js +12 -0
  117. package/lib/generators/cli/engine-source.js.map +1 -0
  118. package/lib/generators/cli/index.d.ts +18 -0
  119. package/lib/generators/cli/index.d.ts.map +1 -0
  120. package/lib/generators/cli/index.js +67 -0
  121. package/lib/generators/cli/index.js.map +1 -0
  122. package/lib/generators/cli/render.d.ts +39 -0
  123. package/lib/generators/cli/render.d.ts.map +1 -0
  124. package/lib/generators/cli/render.js +281 -0
  125. package/lib/generators/cli/render.js.map +1 -0
  126. package/lib/generators/cli/runtime/cli.d.ts +42 -0
  127. package/lib/generators/cli/runtime/cli.d.ts.map +1 -0
  128. package/lib/generators/cli/runtime/cli.js +582 -0
  129. package/lib/generators/cli/runtime/cli.js.map +1 -0
  130. package/lib/generators/compatibility.d.ts +10 -0
  131. package/lib/generators/compatibility.d.ts.map +1 -0
  132. package/lib/generators/compatibility.js +45 -0
  133. package/lib/generators/compatibility.js.map +1 -0
  134. package/lib/generators/go/client.d.ts +5 -0
  135. package/lib/generators/go/client.d.ts.map +1 -0
  136. package/lib/generators/go/client.js +34 -0
  137. package/lib/generators/go/client.js.map +1 -0
  138. package/lib/generators/go/descriptor.d.ts +6 -0
  139. package/lib/generators/go/descriptor.d.ts.map +1 -0
  140. package/lib/generators/go/descriptor.js +26 -0
  141. package/lib/generators/go/descriptor.js.map +1 -0
  142. package/lib/generators/go/index.d.ts +14 -0
  143. package/lib/generators/go/index.d.ts.map +1 -0
  144. package/lib/generators/go/index.js +240 -0
  145. package/lib/generators/go/index.js.map +1 -0
  146. package/lib/generators/go/models.d.ts +4 -0
  147. package/lib/generators/go/models.d.ts.map +1 -0
  148. package/lib/generators/go/models.js +125 -0
  149. package/lib/generators/go/models.js.map +1 -0
  150. package/lib/generators/go/naming.d.ts +15 -0
  151. package/lib/generators/go/naming.d.ts.map +1 -0
  152. package/lib/generators/go/naming.js +36 -0
  153. package/lib/generators/go/naming.js.map +1 -0
  154. package/lib/generators/go/operations.d.ts +16 -0
  155. package/lib/generators/go/operations.d.ts.map +1 -0
  156. package/lib/generators/go/operations.js +200 -0
  157. package/lib/generators/go/operations.js.map +1 -0
  158. package/lib/generators/go/pagination.d.ts +5 -0
  159. package/lib/generators/go/pagination.d.ts.map +1 -0
  160. package/lib/generators/go/pagination.js +93 -0
  161. package/lib/generators/go/pagination.js.map +1 -0
  162. package/lib/generators/go/types.d.ts +4 -0
  163. package/lib/generators/go/types.d.ts.map +1 -0
  164. package/lib/generators/go/types.js +48 -0
  165. package/lib/generators/go/types.js.map +1 -0
  166. package/lib/generators/index.d.ts +2 -3
  167. package/lib/generators/index.d.ts.map +1 -1
  168. package/lib/generators/index.js +31 -53
  169. package/lib/generators/index.js.map +1 -1
  170. package/lib/generators/meta.d.ts +13 -0
  171. package/lib/generators/meta.d.ts.map +1 -0
  172. package/lib/generators/meta.js +157 -0
  173. package/lib/generators/meta.js.map +1 -0
  174. package/lib/{emitters → generators/mock}/faker.d.ts +4 -5
  175. package/lib/generators/mock/faker.d.ts.map +1 -0
  176. package/lib/generators/mock/faker.js +180 -0
  177. package/lib/generators/mock/faker.js.map +1 -0
  178. package/lib/generators/{mock.d.ts → mock/index.d.ts} +2 -2
  179. package/lib/generators/mock/index.d.ts.map +1 -0
  180. package/lib/generators/{mock.js → mock/index.js} +8 -8
  181. package/lib/generators/mock/index.js.map +1 -0
  182. package/lib/{emitters/mock.d.ts → generators/mock/render.d.ts} +2 -3
  183. package/lib/generators/mock/render.d.ts.map +1 -0
  184. package/lib/generators/mock/render.js +228 -0
  185. package/lib/generators/mock/render.js.map +1 -0
  186. package/lib/{emitters → generators/mock}/sample.d.ts +1 -2
  187. package/lib/generators/mock/sample.d.ts.map +1 -0
  188. package/lib/generators/mock/sample.js.map +1 -0
  189. package/lib/generators/mock/values.d.ts +33 -0
  190. package/lib/generators/mock/values.d.ts.map +1 -0
  191. package/lib/generators/mock/values.js +46 -0
  192. package/lib/generators/mock/values.js.map +1 -0
  193. package/lib/generators/options.d.ts +7 -0
  194. package/lib/generators/options.d.ts.map +1 -0
  195. package/lib/generators/options.js +74 -0
  196. package/lib/generators/options.js.map +1 -0
  197. package/lib/generators/php/client.d.ts +5 -0
  198. package/lib/generators/php/client.d.ts.map +1 -0
  199. package/lib/generators/php/client.js +38 -0
  200. package/lib/generators/php/client.js.map +1 -0
  201. package/lib/generators/php/descriptor.d.ts +7 -0
  202. package/lib/generators/php/descriptor.d.ts.map +1 -0
  203. package/lib/generators/php/descriptor.js +42 -0
  204. package/lib/generators/php/descriptor.js.map +1 -0
  205. package/lib/generators/php/index.d.ts +14 -0
  206. package/lib/generators/php/index.d.ts.map +1 -0
  207. package/lib/generators/php/index.js +166 -0
  208. package/lib/generators/php/index.js.map +1 -0
  209. package/lib/generators/php/models.d.ts +8 -0
  210. package/lib/generators/php/models.d.ts.map +1 -0
  211. package/lib/generators/php/models.js +200 -0
  212. package/lib/generators/php/models.js.map +1 -0
  213. package/lib/generators/php/naming.d.ts +16 -0
  214. package/lib/generators/php/naming.d.ts.map +1 -0
  215. package/lib/generators/php/naming.js +31 -0
  216. package/lib/generators/php/naming.js.map +1 -0
  217. package/lib/generators/php/operations.d.ts +21 -0
  218. package/lib/generators/php/operations.d.ts.map +1 -0
  219. package/lib/generators/php/operations.js +156 -0
  220. package/lib/generators/php/operations.js.map +1 -0
  221. package/lib/generators/php/pagination.d.ts +5 -0
  222. package/lib/generators/php/pagination.d.ts.map +1 -0
  223. package/lib/generators/php/pagination.js +68 -0
  224. package/lib/generators/php/pagination.js.map +1 -0
  225. package/lib/generators/php/types.d.ts +27 -0
  226. package/lib/generators/php/types.d.ts.map +1 -0
  227. package/lib/generators/php/types.js +128 -0
  228. package/lib/generators/php/types.js.map +1 -0
  229. package/lib/generators/python/client.d.ts +6 -0
  230. package/lib/generators/python/client.d.ts.map +1 -0
  231. package/lib/generators/python/client.js +80 -0
  232. package/lib/generators/python/client.js.map +1 -0
  233. package/lib/generators/python/descriptor.d.ts +9 -0
  234. package/lib/generators/python/descriptor.d.ts.map +1 -0
  235. package/lib/generators/python/descriptor.js +38 -0
  236. package/lib/generators/python/descriptor.js.map +1 -0
  237. package/lib/generators/python/index.d.ts +15 -0
  238. package/lib/generators/python/index.d.ts.map +1 -0
  239. package/lib/generators/python/index.js +197 -0
  240. package/lib/generators/python/index.js.map +1 -0
  241. package/lib/generators/python/models.d.ts +31 -0
  242. package/lib/generators/python/models.d.ts.map +1 -0
  243. package/lib/generators/python/models.js +203 -0
  244. package/lib/generators/python/models.js.map +1 -0
  245. package/lib/generators/python/naming.d.ts +22 -0
  246. package/lib/generators/python/naming.d.ts.map +1 -0
  247. package/lib/generators/python/naming.js +28 -0
  248. package/lib/generators/python/naming.js.map +1 -0
  249. package/lib/generators/python/operations.d.ts +4 -0
  250. package/lib/generators/python/operations.d.ts.map +1 -0
  251. package/lib/generators/python/operations.js +108 -0
  252. package/lib/generators/python/operations.js.map +1 -0
  253. package/lib/generators/python/pagination.d.ts +5 -0
  254. package/lib/generators/python/pagination.d.ts.map +1 -0
  255. package/lib/generators/python/pagination.js +95 -0
  256. package/lib/generators/python/pagination.js.map +1 -0
  257. package/lib/generators/python/types.d.ts +4 -0
  258. package/lib/generators/python/types.d.ts.map +1 -0
  259. package/lib/generators/python/types.js +45 -0
  260. package/lib/generators/python/types.js.map +1 -0
  261. package/lib/generators/resolve.d.ts.map +1 -1
  262. package/lib/generators/resolve.js +92 -13
  263. package/lib/generators/resolve.js.map +1 -1
  264. package/lib/generators/{swr.d.ts → swr/index.d.ts} +3 -3
  265. package/lib/generators/swr/index.d.ts.map +1 -0
  266. package/lib/generators/{swr.js → swr/index.js} +7 -10
  267. package/lib/generators/swr/index.js.map +1 -0
  268. package/lib/{emitters/swr.d.ts → generators/swr/render.d.ts} +2 -4
  269. package/lib/generators/swr/render.d.ts.map +1 -0
  270. package/lib/generators/swr/render.js +57 -0
  271. package/lib/generators/swr/render.js.map +1 -0
  272. package/lib/generators/{tanstack-query.d.ts → tanstack-query/index.d.ts} +3 -3
  273. package/lib/generators/tanstack-query/index.d.ts.map +1 -0
  274. package/lib/generators/{tanstack-query.js → tanstack-query/index.js} +11 -10
  275. package/lib/generators/tanstack-query/index.js.map +1 -0
  276. package/lib/{emitters/tanstack-query.d.ts → generators/tanstack-query/render.d.ts} +6 -5
  277. package/lib/generators/tanstack-query/render.d.ts.map +1 -0
  278. package/lib/{emitters/tanstack-query.js → generators/tanstack-query/render.js} +33 -21
  279. package/lib/generators/tanstack-query/render.js.map +1 -0
  280. package/lib/generators/{transformers.d.ts → transformers/index.d.ts} +2 -2
  281. package/lib/generators/transformers/index.d.ts.map +1 -0
  282. package/lib/generators/{transformers.js → transformers/index.js} +11 -8
  283. package/lib/generators/transformers/index.js.map +1 -0
  284. package/lib/{emitters/transformers.d.ts → generators/transformers/render.d.ts} +2 -2
  285. package/lib/generators/transformers/render.d.ts.map +1 -0
  286. package/lib/{emitters/transformers.js → generators/transformers/render.js} +125 -165
  287. package/lib/generators/transformers/render.js.map +1 -0
  288. package/lib/generators/types.d.ts +190 -12
  289. package/lib/generators/types.d.ts.map +1 -1
  290. package/lib/generators/typescript/banner.d.ts +12 -0
  291. package/lib/generators/typescript/banner.d.ts.map +1 -0
  292. package/lib/{emitters/emit-options.js → generators/typescript/banner.js} +2 -3
  293. package/lib/generators/typescript/banner.js.map +1 -0
  294. package/lib/generators/typescript/client.d.ts +25 -0
  295. package/lib/generators/typescript/client.d.ts.map +1 -0
  296. package/lib/generators/typescript/client.js +207 -0
  297. package/lib/generators/typescript/client.js.map +1 -0
  298. package/lib/generators/typescript/descriptor.d.ts +11 -0
  299. package/lib/generators/typescript/descriptor.d.ts.map +1 -0
  300. package/lib/generators/typescript/descriptor.js +97 -0
  301. package/lib/generators/typescript/descriptor.js.map +1 -0
  302. package/lib/generators/typescript/index.d.ts +20 -0
  303. package/lib/generators/typescript/index.d.ts.map +1 -0
  304. package/lib/generators/typescript/index.js +80 -0
  305. package/lib/generators/typescript/index.js.map +1 -0
  306. package/lib/generators/typescript/inline-runtime.d.ts +21 -0
  307. package/lib/generators/typescript/inline-runtime.d.ts.map +1 -0
  308. package/lib/generators/typescript/inline-runtime.js +114 -0
  309. package/lib/generators/typescript/inline-runtime.js.map +1 -0
  310. package/lib/generators/typescript/operation-signature.d.ts +23 -0
  311. package/lib/generators/typescript/operation-signature.d.ts.map +1 -0
  312. package/lib/generators/typescript/operation-signature.js +41 -0
  313. package/lib/generators/typescript/operation-signature.js.map +1 -0
  314. package/lib/generators/typescript/operation-types.d.ts +9 -0
  315. package/lib/generators/typescript/operation-types.d.ts.map +1 -0
  316. package/lib/generators/typescript/operation-types.js +11 -0
  317. package/lib/generators/typescript/operation-types.js.map +1 -0
  318. package/lib/generators/typescript/operations.d.ts +60 -0
  319. package/lib/generators/typescript/operations.d.ts.map +1 -0
  320. package/lib/generators/typescript/operations.js +386 -0
  321. package/lib/generators/typescript/operations.js.map +1 -0
  322. package/lib/generators/typescript/response-headers.d.ts +12 -0
  323. package/lib/generators/typescript/response-headers.d.ts.map +1 -0
  324. package/lib/generators/typescript/response-headers.js +40 -0
  325. package/lib/generators/typescript/response-headers.js.map +1 -0
  326. package/lib/generators/typescript/runtime/auth.d.ts.map +1 -0
  327. package/lib/generators/typescript/runtime/auth.js.map +1 -0
  328. package/lib/{runtime → generators/typescript/runtime}/create-client.d.ts +7 -2
  329. package/lib/generators/typescript/runtime/create-client.d.ts.map +1 -0
  330. package/lib/{runtime → generators/typescript/runtime}/create-client.js +84 -30
  331. package/lib/generators/typescript/runtime/create-client.js.map +1 -0
  332. package/lib/generators/typescript/runtime/errors.d.ts.map +1 -0
  333. package/lib/generators/typescript/runtime/errors.js.map +1 -0
  334. package/lib/generators/typescript/runtime/index.d.ts.map +1 -0
  335. package/lib/generators/typescript/runtime/index.js.map +1 -0
  336. package/lib/generators/typescript/runtime/multipart.d.ts.map +1 -0
  337. package/lib/generators/typescript/runtime/multipart.js.map +1 -0
  338. package/lib/{runtime → generators/typescript/runtime}/paginate.d.ts +2 -2
  339. package/lib/generators/typescript/runtime/paginate.d.ts.map +1 -0
  340. package/lib/{runtime → generators/typescript/runtime}/paginate.js +12 -12
  341. package/lib/generators/typescript/runtime/paginate.js.map +1 -0
  342. package/lib/generators/typescript/runtime/parse.d.ts.map +1 -0
  343. package/lib/generators/typescript/runtime/parse.js.map +1 -0
  344. package/lib/generators/typescript/runtime/retry.d.ts.map +1 -0
  345. package/lib/generators/typescript/runtime/retry.js.map +1 -0
  346. package/lib/generators/typescript/runtime/send.d.ts.map +1 -0
  347. package/lib/generators/typescript/runtime/send.js.map +1 -0
  348. package/lib/generators/typescript/runtime/setup.d.ts.map +1 -0
  349. package/lib/generators/typescript/runtime/setup.js.map +1 -0
  350. package/lib/generators/typescript/runtime/sse.d.ts.map +1 -0
  351. package/lib/generators/typescript/runtime/sse.js.map +1 -0
  352. package/lib/{runtime → generators/typescript/runtime}/types.d.ts +17 -95
  353. package/lib/generators/typescript/runtime/types.d.ts.map +1 -0
  354. package/lib/generators/typescript/runtime/types.js.map +1 -0
  355. package/lib/generators/typescript/runtime/url.d.ts.map +1 -0
  356. package/lib/generators/typescript/runtime/url.js.map +1 -0
  357. package/lib/generators/typescript/type-guards.d.ts +4 -0
  358. package/lib/generators/typescript/type-guards.d.ts.map +1 -0
  359. package/lib/{emitters → generators/typescript}/type-guards.js +18 -52
  360. package/lib/generators/typescript/type-guards.js.map +1 -0
  361. package/lib/generators/typescript/types.d.ts +8 -0
  362. package/lib/generators/typescript/types.d.ts.map +1 -0
  363. package/lib/generators/typescript/types.js +132 -0
  364. package/lib/generators/typescript/types.js.map +1 -0
  365. package/lib/generators/{zod.d.ts → zod/index.d.ts} +2 -2
  366. package/lib/generators/zod/index.d.ts.map +1 -0
  367. package/lib/generators/{zod.js → zod/index.js} +5 -7
  368. package/lib/generators/zod/index.js.map +1 -0
  369. package/lib/{emitters/zod.d.ts → generators/zod/schemas.d.ts} +4 -5
  370. package/lib/generators/zod/schemas.d.ts.map +1 -0
  371. package/lib/{emitters/zod.js → generators/zod/schemas.js} +118 -154
  372. package/lib/generators/zod/schemas.js.map +1 -0
  373. package/lib/index.d.ts +8 -3
  374. package/lib/index.d.ts.map +1 -1
  375. package/lib/index.js +18 -11
  376. package/lib/index.js.map +1 -1
  377. package/lib/intermediate-representation/build.d.ts +8 -1
  378. package/lib/intermediate-representation/build.d.ts.map +1 -1
  379. package/lib/intermediate-representation/build.js +59 -4
  380. package/lib/intermediate-representation/build.js.map +1 -1
  381. package/lib/intermediate-representation/model.d.ts +26 -1
  382. package/lib/intermediate-representation/model.d.ts.map +1 -1
  383. package/lib/intermediate-representation/model.js.map +1 -1
  384. package/lib/intermediate-representation/sanitize-identifiers.d.ts +1 -10
  385. package/lib/intermediate-representation/sanitize-identifiers.d.ts.map +1 -1
  386. package/lib/intermediate-representation/sanitize-identifiers.js +37 -38
  387. package/lib/intermediate-representation/sanitize-identifiers.js.map +1 -1
  388. package/lib/{emitters/pagination.d.ts → pagination.d.ts} +39 -15
  389. package/lib/pagination.d.ts.map +1 -0
  390. package/lib/{emitters/pagination.js → pagination.js} +13 -52
  391. package/lib/pagination.js.map +1 -0
  392. package/lib/pipeline.d.ts +17 -0
  393. package/lib/pipeline.d.ts.map +1 -0
  394. package/lib/pipeline.js +244 -0
  395. package/lib/pipeline.js.map +1 -0
  396. package/lib/plugin.d.ts +4 -4
  397. package/lib/plugin.d.ts.map +1 -1
  398. package/lib/plugin.js +12 -9
  399. package/lib/plugin.js.map +1 -1
  400. package/lib/printers/go.d.ts +33 -0
  401. package/lib/printers/go.d.ts.map +1 -0
  402. package/lib/printers/go.js +209 -0
  403. package/lib/printers/go.js.map +1 -0
  404. package/lib/printers/index.d.ts +5 -0
  405. package/lib/printers/index.d.ts.map +1 -0
  406. package/lib/printers/index.js +9 -0
  407. package/lib/printers/index.js.map +1 -0
  408. package/lib/printers/php.d.ts +21 -0
  409. package/lib/printers/php.d.ts.map +1 -0
  410. package/lib/printers/php.js +68 -0
  411. package/lib/printers/php.js.map +1 -0
  412. package/lib/printers/python.d.ts +31 -0
  413. package/lib/printers/python.d.ts.map +1 -0
  414. package/lib/printers/python.js +101 -0
  415. package/lib/printers/python.js.map +1 -0
  416. package/lib/printers/typescript.d.ts +82 -0
  417. package/lib/printers/typescript.d.ts.map +1 -0
  418. package/lib/printers/typescript.js +268 -0
  419. package/lib/printers/typescript.js.map +1 -0
  420. package/lib/reserved-names.d.ts +8 -0
  421. package/lib/reserved-names.d.ts.map +1 -0
  422. package/lib/{emitters/reserved-names.js → reserved-names.js} +16 -27
  423. package/lib/reserved-names.js.map +1 -0
  424. package/lib/runtime-contract.d.ts +62 -2
  425. package/lib/runtime-contract.d.ts.map +1 -1
  426. package/lib/runtime-contract.js +3 -3
  427. package/lib/runtime-contract.js.map +1 -1
  428. package/lib/runtime-sources/go.d.ts +2 -0
  429. package/lib/runtime-sources/go.d.ts.map +1 -0
  430. package/lib/runtime-sources/go.js +3 -0
  431. package/lib/runtime-sources/go.js.map +1 -0
  432. package/lib/runtime-sources/php.d.ts +2 -0
  433. package/lib/runtime-sources/php.d.ts.map +1 -0
  434. package/lib/runtime-sources/php.js +3 -0
  435. package/lib/runtime-sources/php.js.map +1 -0
  436. package/lib/runtime-sources/python.d.ts +12 -0
  437. package/lib/runtime-sources/python.d.ts.map +1 -0
  438. package/lib/runtime-sources/python.js +12 -0
  439. package/lib/runtime-sources/python.js.map +1 -0
  440. package/lib/runtime-sources/typescript.d.ts +36 -0
  441. package/lib/runtime-sources/typescript.d.ts.map +1 -0
  442. package/lib/runtime-sources/typescript.js +152 -0
  443. package/lib/runtime-sources/typescript.js.map +1 -0
  444. package/lib/runtime-sources.d.ts +5 -0
  445. package/lib/runtime-sources.d.ts.map +1 -0
  446. package/lib/runtime-sources.js +10 -0
  447. package/lib/runtime-sources.js.map +1 -0
  448. package/lib/setup-bake.d.ts.map +1 -0
  449. package/lib/{emitters/setup-bake.js → setup-bake.js} +11 -2
  450. package/lib/setup-bake.js.map +1 -0
  451. package/lib/types.d.ts +38 -8
  452. package/lib/types.d.ts.map +1 -1
  453. package/package.json +40 -4
  454. package/lib/emitters/auth.d.ts +0 -14
  455. package/lib/emitters/auth.d.ts.map +0 -1
  456. package/lib/emitters/auth.js +0 -30
  457. package/lib/emitters/auth.js.map +0 -1
  458. package/lib/emitters/client-assembly.d.ts +0 -15
  459. package/lib/emitters/client-assembly.d.ts.map +0 -1
  460. package/lib/emitters/client-assembly.js +0 -358
  461. package/lib/emitters/client-assembly.js.map +0 -1
  462. package/lib/emitters/descriptor.d.ts +0 -22
  463. package/lib/emitters/descriptor.d.ts.map +0 -1
  464. package/lib/emitters/descriptor.js +0 -199
  465. package/lib/emitters/descriptor.js.map +0 -1
  466. package/lib/emitters/emit-options.d.ts +0 -67
  467. package/lib/emitters/emit-options.d.ts.map +0 -1
  468. package/lib/emitters/emit-options.js.map +0 -1
  469. package/lib/emitters/faker.d.ts.map +0 -1
  470. package/lib/emitters/faker.js +0 -221
  471. package/lib/emitters/faker.js.map +0 -1
  472. package/lib/emitters/identifier.d.ts +0 -34
  473. package/lib/emitters/identifier.d.ts.map +0 -1
  474. package/lib/emitters/identifier.js +0 -104
  475. package/lib/emitters/identifier.js.map +0 -1
  476. package/lib/emitters/inline-runtime.d.ts +0 -11
  477. package/lib/emitters/inline-runtime.d.ts.map +0 -1
  478. package/lib/emitters/inline-runtime.js +0 -99
  479. package/lib/emitters/inline-runtime.js.map +0 -1
  480. package/lib/emitters/jsdoc.d.ts +0 -9
  481. package/lib/emitters/jsdoc.d.ts.map +0 -1
  482. package/lib/emitters/jsdoc.js +0 -83
  483. package/lib/emitters/jsdoc.js.map +0 -1
  484. package/lib/emitters/mock.d.ts.map +0 -1
  485. package/lib/emitters/mock.js +0 -265
  486. package/lib/emitters/mock.js.map +0 -1
  487. package/lib/emitters/operation-aliases.d.ts +0 -27
  488. package/lib/emitters/operation-aliases.d.ts.map +0 -1
  489. package/lib/emitters/operation-aliases.js +0 -150
  490. package/lib/emitters/operation-aliases.js.map +0 -1
  491. package/lib/emitters/operation-signature.d.ts +0 -24
  492. package/lib/emitters/operation-signature.d.ts.map +0 -1
  493. package/lib/emitters/operation-signature.js +0 -42
  494. package/lib/emitters/operation-signature.js.map +0 -1
  495. package/lib/emitters/operation-types.d.ts +0 -32
  496. package/lib/emitters/operation-types.d.ts.map +0 -1
  497. package/lib/emitters/operation-types.js +0 -117
  498. package/lib/emitters/operation-types.js.map +0 -1
  499. package/lib/emitters/operations.d.ts +0 -41
  500. package/lib/emitters/operations.d.ts.map +0 -1
  501. package/lib/emitters/operations.js +0 -35
  502. package/lib/emitters/operations.js.map +0 -1
  503. package/lib/emitters/pagination.d.ts.map +0 -1
  504. package/lib/emitters/pagination.js.map +0 -1
  505. package/lib/emitters/reserved-names.d.ts +0 -5
  506. package/lib/emitters/reserved-names.d.ts.map +0 -1
  507. package/lib/emitters/reserved-names.js.map +0 -1
  508. package/lib/emitters/response-headers.d.ts +0 -14
  509. package/lib/emitters/response-headers.d.ts.map +0 -1
  510. package/lib/emitters/response-headers.js +0 -91
  511. package/lib/emitters/response-headers.js.map +0 -1
  512. package/lib/emitters/runtime-sources.d.ts +0 -16
  513. package/lib/emitters/runtime-sources.d.ts.map +0 -1
  514. package/lib/emitters/runtime-sources.js +0 -16
  515. package/lib/emitters/runtime-sources.js.map +0 -1
  516. package/lib/emitters/sample.d.ts.map +0 -1
  517. package/lib/emitters/sample.js.map +0 -1
  518. package/lib/emitters/setup-bake.d.ts.map +0 -1
  519. package/lib/emitters/setup-bake.js.map +0 -1
  520. package/lib/emitters/sse.d.ts +0 -10
  521. package/lib/emitters/sse.d.ts.map +0 -1
  522. package/lib/emitters/sse.js +0 -46
  523. package/lib/emitters/sse.js.map +0 -1
  524. package/lib/emitters/support.d.ts +0 -18
  525. package/lib/emitters/support.d.ts.map +0 -1
  526. package/lib/emitters/support.js +0 -37
  527. package/lib/emitters/support.js.map +0 -1
  528. package/lib/emitters/swr.d.ts.map +0 -1
  529. package/lib/emitters/swr.js +0 -88
  530. package/lib/emitters/swr.js.map +0 -1
  531. package/lib/emitters/tanstack-query.d.ts.map +0 -1
  532. package/lib/emitters/tanstack-query.js.map +0 -1
  533. package/lib/emitters/transformers.d.ts.map +0 -1
  534. package/lib/emitters/transformers.js.map +0 -1
  535. package/lib/emitters/ts.d.ts +0 -42
  536. package/lib/emitters/ts.d.ts.map +0 -1
  537. package/lib/emitters/ts.js +0 -116
  538. package/lib/emitters/ts.js.map +0 -1
  539. package/lib/emitters/type-guards.d.ts +0 -21
  540. package/lib/emitters/type-guards.d.ts.map +0 -1
  541. package/lib/emitters/type-guards.js.map +0 -1
  542. package/lib/emitters/types.d.ts +0 -15
  543. package/lib/emitters/types.d.ts.map +0 -1
  544. package/lib/emitters/types.js +0 -125
  545. package/lib/emitters/types.js.map +0 -1
  546. package/lib/emitters/wrapper-support.d.ts.map +0 -1
  547. package/lib/emitters/wrapper-support.js +0 -127
  548. package/lib/emitters/wrapper-support.js.map +0 -1
  549. package/lib/emitters/zod.d.ts.map +0 -1
  550. package/lib/emitters/zod.js.map +0 -1
  551. package/lib/generators/anchor.d.ts +0 -9
  552. package/lib/generators/anchor.d.ts.map +0 -1
  553. package/lib/generators/anchor.js +0 -10
  554. package/lib/generators/anchor.js.map +0 -1
  555. package/lib/generators/mock.d.ts.map +0 -1
  556. package/lib/generators/mock.js.map +0 -1
  557. package/lib/generators/sdk.d.ts +0 -12
  558. package/lib/generators/sdk.d.ts.map +0 -1
  559. package/lib/generators/sdk.js +0 -26
  560. package/lib/generators/sdk.js.map +0 -1
  561. package/lib/generators/swr.d.ts.map +0 -1
  562. package/lib/generators/swr.js.map +0 -1
  563. package/lib/generators/tanstack-query.d.ts.map +0 -1
  564. package/lib/generators/tanstack-query.js.map +0 -1
  565. package/lib/generators/transformers.d.ts.map +0 -1
  566. package/lib/generators/transformers.js.map +0 -1
  567. package/lib/generators/zod.d.ts.map +0 -1
  568. package/lib/generators/zod.js.map +0 -1
  569. package/lib/runtime/auth.d.ts.map +0 -1
  570. package/lib/runtime/auth.js.map +0 -1
  571. package/lib/runtime/create-client.d.ts.map +0 -1
  572. package/lib/runtime/create-client.js.map +0 -1
  573. package/lib/runtime/errors.d.ts.map +0 -1
  574. package/lib/runtime/errors.js.map +0 -1
  575. package/lib/runtime/index.d.ts.map +0 -1
  576. package/lib/runtime/index.js.map +0 -1
  577. package/lib/runtime/multipart.d.ts.map +0 -1
  578. package/lib/runtime/multipart.js.map +0 -1
  579. package/lib/runtime/paginate.d.ts.map +0 -1
  580. package/lib/runtime/paginate.js.map +0 -1
  581. package/lib/runtime/parse.d.ts.map +0 -1
  582. package/lib/runtime/parse.js.map +0 -1
  583. package/lib/runtime/retry.d.ts.map +0 -1
  584. package/lib/runtime/retry.js.map +0 -1
  585. package/lib/runtime/send.d.ts.map +0 -1
  586. package/lib/runtime/send.js.map +0 -1
  587. package/lib/runtime/setup.d.ts.map +0 -1
  588. package/lib/runtime/setup.js.map +0 -1
  589. package/lib/runtime/sse.d.ts.map +0 -1
  590. package/lib/runtime/sse.js.map +0 -1
  591. package/lib/runtime/types.d.ts.map +0 -1
  592. package/lib/runtime/types.js.map +0 -1
  593. package/lib/runtime/url.d.ts.map +0 -1
  594. package/lib/runtime/url.js.map +0 -1
  595. /package/lib/{emitters → generators/mock}/sample.js +0 -0
  596. /package/lib/{runtime → generators/typescript/runtime}/auth.d.ts +0 -0
  597. /package/lib/{runtime → generators/typescript/runtime}/auth.js +0 -0
  598. /package/lib/{runtime → generators/typescript/runtime}/errors.d.ts +0 -0
  599. /package/lib/{runtime → generators/typescript/runtime}/errors.js +0 -0
  600. /package/lib/{runtime → generators/typescript/runtime}/index.d.ts +0 -0
  601. /package/lib/{runtime → generators/typescript/runtime}/index.js +0 -0
  602. /package/lib/{runtime → generators/typescript/runtime}/multipart.d.ts +0 -0
  603. /package/lib/{runtime → generators/typescript/runtime}/multipart.js +0 -0
  604. /package/lib/{runtime → generators/typescript/runtime}/parse.d.ts +0 -0
  605. /package/lib/{runtime → generators/typescript/runtime}/parse.js +0 -0
  606. /package/lib/{runtime → generators/typescript/runtime}/retry.d.ts +0 -0
  607. /package/lib/{runtime → generators/typescript/runtime}/retry.js +0 -0
  608. /package/lib/{runtime → generators/typescript/runtime}/send.d.ts +0 -0
  609. /package/lib/{runtime → generators/typescript/runtime}/send.js +0 -0
  610. /package/lib/{runtime → generators/typescript/runtime}/setup.d.ts +0 -0
  611. /package/lib/{runtime → generators/typescript/runtime}/setup.js +0 -0
  612. /package/lib/{runtime → generators/typescript/runtime}/sse.d.ts +0 -0
  613. /package/lib/{runtime → generators/typescript/runtime}/sse.js +0 -0
  614. /package/lib/{runtime → generators/typescript/runtime}/types.js +0 -0
  615. /package/lib/{runtime → generators/typescript/runtime}/url.d.ts +0 -0
  616. /package/lib/{runtime → generators/typescript/runtime}/url.js +0 -0
  617. /package/lib/{emitters/setup-bake.d.ts → setup-bake.d.ts} +0 -0
@@ -0,0 +1,36 @@
1
+ export declare const RUNTIME_SOURCES: {
2
+ readonly 'types.ts': "/**\n * The public type surface of the client runtime — `@redocly/client-generator`'s\n * app-facing runtime module. Pure types, no runtime code (excluded from coverage).\n * The generator emits `OPERATIONS` literals typed\n * `satisfies Record<string, OperationDescriptor>` against this module, so an\n * incompatible runtime/generated pair fails the consumer's build (the semver skew guard).\n */\n\n/** How one operation parameter is sent: its location plus OpenAPI query-serialization hints. */\nexport type ParamSpec = {\n name: string;\n in: 'path' | 'query' | 'header' | 'cookie';\n style?: 'form' | 'spaceDelimited' | 'pipeDelimited' | 'deepObject';\n explode?: boolean;\n allowReserved?: boolean;\n};\n\n/** One security scheme, denormalized onto the operation (`scheme` names the spec's scheme). */\nexport type SecuritySpec =\n | { scheme: string; kind: 'bearer' | 'basic' }\n | { scheme: string; kind: 'apiKey'; name: string; in: 'header' | 'query' | 'cookie' };\n\n/**\n * How to auto-iterate a paginated operation (drives its `.pages()`/`.items()` members).\n * `nextCursor` and `items` are RFC 6901 JSON pointers into the page (response) value.\n */\nexport type PaginationSpec =\n | {\n style: 'cursor';\n /** The query param the iterator advances with the response's cursor. */\n param: string;\n /** Optional page-size query param (recorded for tooling; never set by the runtime). */\n limitParam?: string;\n /** Pointer to the next cursor in the page. */\n nextCursor: string;\n /** Optional pointer to a boolean \"more pages\" flag — `false` stops iteration. */\n hasMore?: string;\n /** Pointer to the page's item array. */\n items: string;\n }\n | {\n style: 'offset' | 'page';\n /** The numeric query param the iterator advances. */\n param: string;\n /** Optional page-size query param (recorded for tooling; never set by the runtime). */\n limitParam?: string;\n /** Pointer to the page's item array. */\n items: string;\n }\n | {\n /** RFC 8288: follow the response's `Link` header `rel=\"next\"`; stop when absent. */\n style: 'link';\n /** Optional page-size query param (recorded for tooling; never set by the runtime). */\n limitParam?: string;\n /** Pointer to the page's item array. */\n items: string;\n };\n\n/** The frozen data contract between generated code and the runtime: one operation's wire shape. */\nexport type OperationDescriptor = {\n id: string;\n method: string;\n path: string;\n tags?: readonly string[];\n params?: readonly ParamSpec[];\n /** `multipart: true` marks a typed object body serialized to FormData by the runtime. */\n body?: { contentType: string; multipart?: boolean };\n /** Defaults to `'json'` (content-type negotiation on parse). */\n responseKind?: 'json' | 'text' | 'blob' | 'void' | 'sse';\n sseDataKind?: 'json' | 'text';\n /** OR-alternatives, each an AND-set: the runtime applies the first fully-configured one. */\n security?: readonly (readonly SecuritySpec[])[];\n pagination?: PaginationSpec;\n /**\n * `'grouped'` marks an operation that takes its inputs namespaced by layer even on a\n * `argsStyle: 'flat'` client — the generator sets it where a merged call could not carry\n * one name for two layers, and the operation's own input type says the same.\n */\n argsStyle?: 'grouped';\n /**\n * Declared success-response headers for throw-mode `{ envelope: true }`.\n * `name` is the lowercased wire name; `key` is the camelCase envelope property.\n */\n responseHeaders?: readonly ResponseHeaderSpec[];\n};\n\n/** One declared response header the runtime coerces into the envelope `headers` object. */\nexport type ResponseHeaderSpec = {\n name: string;\n key: string;\n type: 'string' | 'number' | 'boolean';\n};\n\n/** A query value: scalars, arrays of scalars, or objects (serialized as deepObject brackets). */\nexport type QueryValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | Array<string | number | boolean | null | undefined>\n | Record<string, unknown>;\n\n/** A credential: a literal, or a (possibly async) function resolved per request (refresh flows). */\nexport type TokenProvider = string | (() => string | Promise<string>);\n\n/** Per-instance credentials, keyed by the scheme kinds the runtime can inject. */\nexport type AuthCredentials = {\n bearer?: TokenProvider;\n basic?: { username: string; password: string };\n apiKey?: Record<string, TokenProvider>;\n};\n\n/** Backoff shape: 'fixed' = constant delay; 'exponential' = doubling per attempt. */\nexport type RetryStrategy = 'fixed' | 'exponential';\n\n/**\n * The operation's identity, exposed to middleware for targeting (`ctx.operation`).\n * Generated clients instantiate the type parameters with the spec's literal unions\n * (`OperationId`/`OperationPath`/`OperationTag`) so a misspelled operation id in a\n * middleware comparison fails to compile; the string defaults keep every\n * spec-independent consumer (`runtime-contract.ts`, the runtime internals) working\n * with the base shape. `tags` stays mutable (`Tag[]`) so setup-contract types\n * (byte-locked to generated output) remain assignable through middleware callbacks.\n */\nexport type OperationContext<\n Id extends string = string,\n Path extends string = string,\n Tag extends string = string,\n> = { id: Id; path: Path; tags: Tag[] };\n\n/** The mutable request context threaded through the middleware chain. */\nexport type RequestContext<Op extends OperationContext = OperationContext> = {\n url: string;\n method: string;\n headers: Record<string, string>;\n body?: unknown;\n operation: Op;\n};\n\n/** The failed attempt handed to a custom `retryOn`: exactly one of `response`/`error` is set. */\nexport type RetryContext<Op extends OperationContext = OperationContext> = {\n attempt: number;\n request: RequestContext<Op>;\n response?: Response;\n error?: unknown;\n};\n\n/** Opt-in retry policy; a per-call override merges field-by-field over the config policy. */\nexport type RetryConfig<Op extends OperationContext = OperationContext> = {\n retries?: number;\n retryDelay?: number;\n retryStrategy?: RetryStrategy;\n jitter?: boolean;\n retryOn?: (ctx: RetryContext<Op>) => boolean | Promise<boolean>;\n};\n\n/**\n * Structural stand-in for the runtime's ApiError so this module stays import-free\n * (pure types); the real `ApiError` class is assignable to it.\n */\nexport type ApiErrorLike = globalThis.Error & {\n url: string;\n status: number;\n statusText: string;\n body: unknown;\n};\n\n/** One interceptor: any subset of the three hooks. */\nexport type Middleware<Op extends OperationContext = OperationContext> = {\n onRequest?: (ctx: RequestContext<Op>) => void | Promise<void>;\n onResponse?: (\n response: Response,\n ctx: RequestContext<Op>\n ) => Response | void | Promise<Response | void>;\n /** Throw mode only: may map/replace the error. */\n // `globalThis.Error` so a spec schema named `Error` cannot shadow it in inline mode.\n onError?: (\n error: ApiErrorLike,\n ctx: RequestContext<Op>\n ) => globalThis.Error | Promise<globalThis.Error>;\n};\n\n/** Client configuration: transport, defaults, retry policy, middleware, and credentials. */\nexport type ClientConfig<Op extends OperationContext = OperationContext> = {\n serverUrl?: string;\n fetch?: typeof fetch;\n headers?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n retry?: RetryConfig<Op>;\n /** Milliseconds before a request attempt aborts (covers the body read too; each retry\n * attempt gets a fresh budget). Per-call `timeout` overrides it, `0` disables it.\n * SSE streams are long-lived by design and never inherit this value. */\n timeout?: number;\n /** Send an `Idempotency-Key` header on POST/PATCH (one stable key per logical call,\n * reused across retry attempts) — which also makes those retries safe under the\n * default retry policy. `true` generates a UUID per call; a function supplies the key. */\n idempotencyKey?: boolean | (() => string);\n /** Identifies this client to the API via an `X-Redocly-Client` header (the generator\n * bakes a default). Sent only OUTSIDE browsers — a custom header would force a CORS\n * preflight. Override with your own value, or `false` to disable. */\n clientHeader?: string | false;\n middleware?: Middleware<Op>[];\n auth?: AuthCredentials;\n /** Fixed at generate time by the generator (`'throw'` when omitted); `configure()` ignores it. */\n errorMode?: 'throw' | 'result';\n /**\n * How each call spells its inputs: `'grouped'` (the default) namespaces them by layer —\n * `{ path, query, headers, cookies, body }` — and `'flat'` takes one merged object.\n * Fixed at generate time, like `errorMode`, because it shapes the static types.\n */\n argsStyle?: 'grouped' | 'flat';\n onRequest?: Middleware<Op>['onRequest'];\n onResponse?: Middleware<Op>['onResponse'];\n onError?: Middleware<Op>['onError'];\n};\n\n/** Response readers for the per-call `parseAs` override. */\nexport type ParseAs = 'auto' | 'json' | 'text' | 'blob' | 'arrayBuffer' | 'formData' | 'stream';\n\n/** Per-call options: standard `RequestInit` plus a retry override, a timeout override\n * (`0` disables the config default), and a forced reader. */\nexport type RequestOptions = RequestInit & {\n retry?: RetryConfig;\n timeout?: number;\n /** Per-call idempotency key: a literal key, `true` to generate one, `false` to skip. */\n idempotencyKey?: string | boolean | (() => string);\n parseAs?: ParseAs;\n /**\n * Throw mode only: return `{ data, headers, response }` instead of the parsed body;\n * ignored in result mode. The explicit `| undefined` keeps the wrappers' emitted\n * `envelope: undefined` strip legal under `exactOptionalPropertyTypes`.\n */\n envelope?: boolean | undefined;\n};\n\n/** Throw-mode success envelope when `RequestOptions.envelope` is `true`. */\nexport type Envelope<TData, THeaders = Record<string, never>> = {\n data: TData;\n headers: THeaders;\n response: Response;\n};\n\n/** Per-call options for an SSE stream; reconnect defaults to true. */\nexport type SseOptions = RequestInit & { reconnect?: boolean; reconnectDelay?: number };\n\n/** A single decoded Server-Sent Event with its payload typed from the spec. */\nexport type ServerSentEvent<T> = { event?: string; data: T; id?: string; retry?: number };\n\n/** Result-mode return shape: exactly one of `data`/`error` is set. */\nexport type Result<TData, TError> =\n | { data: TData; error: undefined; response: Response }\n | { data: undefined; error: TError; response: Response };\n\n/**\n * The generated `Ops` type's shape: per-operation args/result, plus `kind: 'sse'` for\n * streams and, for paginated operations, `item` (the page's element type) and — on\n * result-mode clients only — `page` (the RAW page type `.pages()` yields, since\n * iteration unwraps the `Result` envelope the one-shot `result` carries).\n */\nexport type OpsShape = Record<\n string,\n {\n args: object;\n result: unknown;\n kind?: 'sse';\n item?: unknown;\n page?: unknown;\n /** Declared success-response headers for `{ envelope: true }` (camelCase keys). */\n headers?: object;\n /** Result-mode entries ignore the throw-only `envelope` option. */\n mode?: 'result';\n }\n>;\n\n/** The always-present client members (assigned after the operation loop — they win collisions). */\nexport type ClientCore<Op extends OperationContext = OperationContext> = {\n /** Merge into the config; note `middleware` REPLACES the chain (use `use()` to compose). */\n configure(config: ClientConfig<Op>): void;\n /** Append interceptors (composes with baked/publisher middleware). */\n use(...middleware: Middleware<Op>[]): void;\n auth: {\n bearer(token: TokenProvider): void;\n basic(username: string, password: string): void;\n apiKey(scheme: string, value: TokenProvider): void;\n };\n};\n\n/**\n * The standard TypeScript optionality probe: `{}` has no required members, so\n * `{} extends A` is true exactly when every member of `A` is optional.\n */\n// oxlint-disable-next-line typescript/no-empty-object-type\ntype NoRequiredKeys<A> = {} extends A ? true : false;\n\n/**\n * The page type `.pages()` yields: the RAW page declared by `page` (the generator\n * writes it only on result-mode paginated entries, whose `result` is the envelope),\n * or the method's own `result` (throw mode — already the raw page).\n */\ntype PageOf<Entry extends OpsShape[string]> = Entry extends { page: unknown }\n ? Entry['page']\n : Entry['result'];\n\n/**\n * The auto-pagination members intersected onto a paginated method — present exactly when\n * the Ops entry declares `item` (the generator writes it only for paginated operations).\n * Args optionality mirrors the method's own; `unknown` otherwise (identity under `&`).\n * Iteration is error-mode-agnostic: `.pages()`/`.items()` yield raw pages/items, and a\n * failed page aborts iteration by throwing `ApiError`, even on result-mode clients; the\n * `onError` middleware hook (throw-mode-only) is not invoked.\n */\ntype Paginated<Entry extends OpsShape[string]> = 'item' extends keyof Entry\n ? NoRequiredKeys<Entry['args']> extends true\n ? {\n pages(args?: Entry['args'], init?: RequestOptions): AsyncGenerator<PageOf<Entry>>;\n items(args?: Entry['args'], init?: RequestOptions): AsyncGenerator<Entry['item']>;\n }\n : {\n pages(args: Entry['args'], init?: RequestOptions): AsyncGenerator<PageOf<Entry>>;\n items(args: Entry['args'], init?: RequestOptions): AsyncGenerator<Entry['item']>;\n }\n : unknown;\n\n/**\n * The stable identity every client method carries: the SPEC operationId (also set as\n * `fn.name`, but `operationId` is the explicit, minification-proof form) — a robust\n * cache key for consumer wrappers (react-query keys and the like).\n */\nexport type OperationMethodIdentity = { readonly operationId: string };\n\n/** Declared response-header bag for an Ops entry; empty object when none are declared. */\ntype HeadersOf<Entry extends OpsShape[string]> = 'headers' extends keyof Entry\n ? NonNullable<Entry['headers']>\n : Record<string, never>;\n\n/**\n * Return type of a throw-mode call: the body by default, `Envelope<…>` for a literal\n * `envelope: true`, their union when the flag is a widened `boolean`. Exact\n * `RequestOptions` stays the body — pre-envelope package-mode flat sugar typed every\n * `init` parameter as `RequestOptions`, and widening that would break upgrades without\n * a regenerate. The `keyof` presence gate keeps `{ headers }` / `{ signal }` as the body\n * (`TInit['envelope']` through `TInit & RequestOptions` would otherwise be\n * `boolean | undefined`).\n */\nexport type EnvelopeResult<\n TData,\n THeaders,\n TInit extends RequestOptions | undefined,\n> = TInit extends undefined\n ? TData\n : RequestOptions extends TInit\n ? TInit extends RequestOptions\n ? TData\n : EnvelopeResultForKnownInit<TData, THeaders, TInit>\n : EnvelopeResultForKnownInit<TData, THeaders, TInit>;\n\ntype EnvelopeResultForKnownInit<TData, THeaders, TInit> = 'envelope' extends keyof TInit\n ? [TInit['envelope' & keyof TInit]] extends [true]\n ? Envelope<TData, THeaders>\n : [TInit['envelope' & keyof TInit]] extends [false | undefined]\n ? TData\n : TData | Envelope<TData, THeaders>\n : TData;\n\n/** A one-shot method whose return shape never varies with per-call options. */\ntype BodyMethod<Entry extends OpsShape[string]> =\n NoRequiredKeys<Entry['args']> extends true\n ? (args?: Entry['args'], init?: RequestOptions) => Promise<Entry['result']>\n : (args: Entry['args'], init?: RequestOptions) => Promise<Entry['result']>;\n\n/**\n * One-shot (non-SSE) method: default returns the body; `{ envelope: true }` returns\n * `{ data, headers, response }` with typed declared headers.\n */\ntype ThrowMethod<Entry extends OpsShape[string]> =\n NoRequiredKeys<Entry['args']> extends true\n ? <Init extends RequestOptions | undefined = undefined>(\n args?: Entry['args'],\n init?: Init\n ) => Promise<EnvelopeResult<Entry['result'], HeadersOf<Entry>, Init>>\n : <Init extends RequestOptions | undefined = undefined>(\n args: Entry['args'],\n init?: Init\n ) => Promise<EnvelopeResult<Entry['result'], HeadersOf<Entry>, Init>>;\n\n/** The typed instance client: one bound method per operation plus the core members. */\nexport type Client<Ops extends OpsShape, Op extends OperationContext = OperationContext> = {\n [K in keyof Ops]: Ops[K] extends { kind: 'sse' }\n ? (NoRequiredKeys<Ops[K]['args']> extends true\n ? (\n args?: Ops[K]['args'],\n init?: SseOptions\n ) => AsyncGenerator<ServerSentEvent<Ops[K]['result']>>\n : (\n args: Ops[K]['args'],\n init?: SseOptions\n ) => AsyncGenerator<ServerSentEvent<Ops[K]['result']>>) &\n OperationMethodIdentity\n : (Ops[K] extends { mode: 'result' } ? BodyMethod<Ops[K]> : ThrowMethod<Ops[K]>) &\n OperationMethodIdentity &\n Paginated<Ops[K]>;\n} & ClientCore<Op>;\n";
3
+ readonly 'errors.ts': "/** The error thrown (throw mode) for a non-2xx response, carrying the decoded error body. */\nexport class ApiError extends Error {\n public readonly url: string;\n public readonly status: number;\n public readonly statusText: string;\n public readonly body: unknown;\n constructor(url: string, status: number, statusText: string, body: unknown) {\n super(`Request failed with status ${status}`);\n this.name = 'ApiError';\n this.url = url;\n this.status = status;\n this.statusText = statusText;\n this.body = body;\n }\n}\n\n/** The error thrown when a request attempt exceeds the configured `timeout` — carries\n * the context a log line needs (which operation, what budget, which attempt). */\nexport class TimeoutError extends Error {\n public readonly operationId: string;\n public readonly timeout: number;\n public readonly attempt: number;\n constructor(operationId: string, timeout: number, attempt: number) {\n super(`Request \"${operationId}\" timed out after ${timeout} ms (attempt ${attempt})`);\n this.name = 'TimeoutError';\n this.operationId = operationId;\n this.timeout = timeout;\n this.attempt = attempt;\n }\n}\n\n/** The error to throw for an aborted request: the caller's abort reason when it is an Error. */\n// `globalThis.Error` (not bare `Error`) so a spec schema named `Error` cannot shadow it\n// when this module is embedded alongside generated types (inline mode).\nexport function abortError(signal: AbortSignal): globalThis.Error {\n const reason = (signal as { reason?: unknown }).reason;\n if (reason instanceof Error) return reason;\n return new DOMException('The operation was aborted.', 'AbortError');\n}\n";
4
+ readonly 'url.ts': "import type { ParamSpec, QueryValue } from './types.js';\n\n/**\n * The RESOLVED OpenAPI serialization spec for one query parameter — callers apply the\n * OpenAPI defaults (`style: 'form'`, `explode: true`) before building one.\n */\nexport type QueryStyle = {\n style: NonNullable<ParamSpec['style']>;\n explode: boolean;\n allowReserved?: boolean;\n};\n\n/**\n * Encode everything except the RFC-3986 reserved set, for `allowReserved: true` params —\n * `filter=a/b` survives instead of `filter=a%2Fb`.\n */\nexport function encodeReserved(value: string): string {\n return encodeURIComponent(value).replace(\n /%(3A|2F|3F|23|5B|5D|40|21|24|26|27|28|29|2A|2B|2C|3B|3D)/g,\n (match) => decodeURIComponent(match)\n );\n}\n\n/** Substitute `{name}` template segments with encoded values; a missing value is a caller bug. */\nexport function substitutePath(template: string, values: Record<string, unknown>): string {\n return template.replace(/\\{([^{}]+)\\}/g, (_match, name: string) => {\n const value = values[name];\n if (value === undefined) throw new Error(`Missing path parameter \"${name}\"`);\n return encodeURIComponent(String(value));\n });\n}\n\n/**\n * Build the request URL: `serverUrl` (trailing slash trimmed) + path + serialized query.\n * Query parameters honor their OpenAPI `style`/`explode`/`allowReserved` (from `styles`);\n * without a spec, arrays repeat the key (`form`+`explode`), objects serialize as\n * `deepObject` brackets, and `null`/`undefined` entries are skipped.\n */\nexport function buildUrl(\n serverUrl: string,\n path: string,\n query?: Record<string, QueryValue>,\n styles?: Record<string, QueryStyle>\n): string {\n // Trim trailing slashes with a scan, not `/\\/+$/` — an anchored `+` regex is\n // quadratic on adversarial many-slash input (the server URL is caller data).\n let end = serverUrl.length;\n while (end > 0 && serverUrl.charCodeAt(end - 1) === 47 /* '/' */) end--;\n const url = serverUrl.slice(0, end) + path;\n if (!query) return url;\n const params = new URLSearchParams();\n const raw: string[] = [];\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n const spec = styles?.[key];\n if (!spec) {\n if (Array.isArray(value)) {\n for (const v of value) {\n if (v !== undefined && v !== null) params.append(key, String(v));\n }\n } else if (Object(value) === value) {\n // Object-valued query params use `deepObject` style: key[subKey]=subValue.\n for (const [subKey, subValue] of Object.entries(value)) {\n if (subValue !== undefined && subValue !== null) {\n params.append(`${key}[${subKey}]`, String(subValue));\n }\n }\n } else {\n params.append(key, String(value));\n }\n continue;\n }\n if (Array.isArray(value)) {\n const items = value.filter((v) => v !== undefined && v !== null).map(String);\n if (spec.style === 'form' && spec.explode) {\n for (const v of items) {\n if (spec.allowReserved) raw.push(`${key}=${encodeReserved(v)}`);\n else params.append(key, v);\n }\n } else {\n // Delimited styles put the LITERAL delimiter on the wire; only the\n // values are encoded. `%20` (not `+`) is the literal space delimiter.\n const delim =\n spec.style === 'pipeDelimited' ? '|' : spec.style === 'spaceDelimited' ? '%20' : ',';\n const enc = spec.allowReserved ? encodeReserved : encodeURIComponent;\n raw.push(`${encodeURIComponent(key)}=${items.map(enc).join(delim)}`);\n }\n } else if (Object(value) === value) {\n // `deepObject` (and any object spec, for now): key[subKey]=subValue.\n for (const [subKey, subValue] of Object.entries(value)) {\n if (subValue !== undefined && subValue !== null) {\n if (spec.allowReserved) raw.push(`${key}[${subKey}]=${encodeReserved(String(subValue))}`);\n else params.append(`${key}[${subKey}]`, String(subValue));\n }\n }\n } else if (spec.allowReserved) {\n raw.push(`${key}=${encodeReserved(String(value))}`);\n } else {\n params.append(key, String(value));\n }\n }\n const qs = [params.toString(), ...raw].filter(Boolean).join('&');\n return qs ? `${url}?${qs}` : url;\n}\n";
5
+ readonly 'parse.ts': "import type { ParseAs } from './types.js';\n\n/**\n * Read the response body per `kind`. `'auto'` negotiates from the content type\n * (JSON, then `text/*`, then Blob); `204` responses read nothing. A `'void'`\n * operation (no declared 2xx content) still returns a JSON body the server\n * actually sends: the static type stays `void`, but silently dropping real data\n * behind a spec gap is the worse failure — consumers can reach it with a cast\n * while the API description catches up.\n */\nexport async function parse(response: Response, kind: ParseAs | 'void'): Promise<unknown> {\n if (kind === 'void') {\n if (response.status === 204 || response.status === 205 || response.status === 304) {\n return undefined;\n }\n const contentType = (response.headers.get('content-type') ?? '').toLowerCase();\n if (!contentType.includes('json')) return undefined;\n // Best-effort: an empty or malformed body on an undeclared response stays undefined.\n const text = await response.text().catch(() => '');\n if (text === '') return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n }\n if (response.status === 204) return undefined;\n if (kind === 'stream') return response.body;\n if (kind === 'blob') return response.blob();\n if (kind === 'arrayBuffer') return response.arrayBuffer();\n if (kind === 'formData') return response.formData();\n if (kind === 'text') return response.text();\n if (kind === 'json') return response.json();\n // 'auto' — negotiate from the response's content type (case-insensitively:\n // `Text/Plain` and `application/JSON` are valid per RFC 9110).\n const contentType = (response.headers.get('content-type') ?? '').toLowerCase();\n if (contentType.includes('json')) return response.json();\n if (contentType.startsWith('text/')) return response.text();\n // An untyped body reads as a Blob — but an EMPTY one resolves to undefined: a 2xx\n // with `Content-Length: 0` must not yield a truthy `new Blob([])` that silently\n // defeats every `!data` guard downstream.\n const blob = await response.blob();\n return blob.size > 0 ? blob : undefined;\n}\n\n/** Best-effort decode of a non-2xx body (JSON when declared, else text; undefined on failure). */\nexport async function readError(response: Response): Promise<unknown> {\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.toLowerCase().includes('json')) {\n return response.json().catch(() => undefined);\n }\n return response.text().catch(() => undefined);\n}\n";
6
+ readonly 'retry.ts': "import { abortError } from './errors.js';\nimport type { RetryConfig, RetryContext } from './types.js';\n\nconst IDEMPOTENT_METHODS = new Set(['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS']);\nconst TRANSIENT_STATUS = new Set([408, 429, 500, 502, 503, 504]);\n\n/**\n * The default retry predicate: idempotent methods — or any request carrying an\n * `Idempotency-Key` header, which makes re-sending safe — on a transport error or a\n * transient status. A custom `retryOn` fully replaces this (no method check kept).\n */\nexport function defaultRetryOn(ctx: RetryContext): boolean {\n const safeToResend =\n IDEMPOTENT_METHODS.has(ctx.request.method.toUpperCase()) ||\n 'Idempotency-Key' in ctx.request.headers ||\n 'idempotency-key' in ctx.request.headers;\n if (!safeToResend) return false;\n return ctx.response === undefined || TRANSIENT_STATUS.has(ctx.response.status);\n}\n\n/**\n * The delay before the next attempt: a `Retry-After` header (seconds or HTTP-date)\n * wins; otherwise fixed/exponential backoff over `retryDelay`, with full jitter\n * unless `jitter === false`.\n */\nexport function retryDelay(retry: RetryConfig, attempt: number, retryAfter: string | null): number {\n if (retryAfter) {\n const seconds = Number(retryAfter);\n if (!Number.isNaN(seconds)) return seconds * 1000;\n const when = Date.parse(retryAfter);\n if (!Number.isNaN(when)) return Math.max(0, when - Date.now());\n }\n const base = retry.retryDelay ?? 1000;\n const raw = retry.retryStrategy === 'fixed' ? base : base * Math.pow(2, attempt - 1);\n return retry.jitter === false ? raw : Math.random() * raw;\n}\n\n/** Abort-aware sleep: resolves after `ms`, rejects with the abort reason immediately on abort. */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortError(signal));\n return;\n }\n const onAbort = () => {\n clearTimeout(timer);\n reject(abortError(signal as AbortSignal));\n };\n const timer = setTimeout(() => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n if (signal) signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n";
7
+ readonly 'multipart.ts': "/**\n * Serialize a plain object into `FormData` for a typed `multipart/form-data` body\n * (capability module — wired into `createClient`, never imported by the send core).\n * `Blob`/`File` and strings pass through; `Date`s become ISO strings; arrays append\n * one field per item; other objects are JSON-encoded; everything else is stringified.\n * `undefined`/`null` entries are skipped.\n */\nexport function toFormData(body: Record<string, unknown>): FormData {\n const fd = new FormData();\n const append = (key: string, value: unknown): void => {\n if (value === undefined || value === null) return;\n if (value instanceof Blob || typeof value === 'string') fd.append(key, value);\n else if (value instanceof Date) fd.append(key, value.toISOString());\n else if (Object(value) === value) fd.append(key, JSON.stringify(value));\n else fd.append(key, String(value));\n };\n for (const [key, value] of Object.entries(body)) {\n if (Array.isArray(value)) for (const item of value) append(key, item);\n else append(key, value);\n }\n return fd;\n}\n";
8
+ readonly 'auth.ts': "import type { ClientConfig, SecuritySpec, TokenProvider } from './types.js';\n\n/** Resolve a credential: a literal passes through; a function is awaited per request. */\nasync function resolveToken(provider: TokenProvider): Promise<string> {\n return typeof provider === 'function' ? await provider() : provider;\n}\n\n/** UTF-8-safe base64: bare `btoa` throws on non-Latin-1 credentials (RFC 7617 allows UTF-8). */\nfunction encodeBase64(text: string): string {\n let binary = '';\n for (const byte of new TextEncoder().encode(text)) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n\n/** Whether a credential for this scheme is configured on the instance. */\nfunction isConfigured(scheme: SecuritySpec, config: ClientConfig): boolean {\n if (scheme.kind === 'apiKey') return config.auth?.apiKey?.[scheme.scheme] !== undefined;\n if (scheme.kind === 'bearer') return config.auth?.bearer !== undefined;\n return config.auth?.basic !== undefined;\n}\n\n/**\n * Build the auth headers/query for one operation's `security` OR-alternatives from the\n * instance credentials (`config.auth`) — capability module, wired into `createClient`.\n * The first alternative whose schemes (an AND-set) are all configured is applied, so\n * \"bearer OR apiKey\" works with either credential and never sends both. When none is\n * fully configured, the first alternative's configured schemes are still sent (the\n * server rejects the request, mirroring the previous behavior).\n * Cookie-borne apiKeys fold into a single `Cookie` header joined with `; `.\n */\nexport async function resolveAuth(\n security: readonly (readonly SecuritySpec[])[],\n config: ClientConfig\n): Promise<{ headers: Record<string, string>; query: Record<string, string> }> {\n const alternative =\n security.find((schemes) => schemes.every((scheme) => isConfigured(scheme, config))) ??\n security[0] ??\n [];\n const headers: Record<string, string> = {};\n const query: Record<string, string> = {};\n const cookies: string[] = [];\n for (const scheme of alternative) {\n if (scheme.kind === 'apiKey') {\n const provider = config.auth?.apiKey?.[scheme.scheme];\n if (provider === undefined) continue;\n const value = await resolveToken(provider);\n if (scheme.in === 'header') headers[scheme.name] = value;\n else if (scheme.in === 'query') query[scheme.name] = value;\n // Cookie values may contain reserved characters (`;`, `=`, space, …); percent-encode\n // so the credential can't break the `Cookie` header syntax.\n else cookies.push(`${scheme.name}=${encodeURIComponent(value)}`);\n } else if (scheme.kind === 'bearer') {\n const provider = config.auth?.bearer;\n if (provider !== undefined) headers.Authorization = `Bearer ${await resolveToken(provider)}`;\n } else {\n const basic = config.auth?.basic;\n if (basic !== undefined) {\n headers.Authorization = `Basic ${encodeBase64(`${basic.username}:${basic.password}`)}`;\n }\n }\n }\n if (cookies.length > 0) headers.Cookie = cookies.join('; ');\n return { headers, query };\n}\n";
9
+ readonly 'setup.ts': "import type { ClientConfig, Middleware } from './types.js';\n\n/**\n * Merge a publisher's baked setup (`defineClientSetup({...})`) with the app's config:\n * app config fields win per-field over baked defaults, while middleware composes —\n * baked middleware runs first, then the app's.\n */\nexport function mergeSetup(\n setup: { config?: ClientConfig; middleware?: Middleware[] } | undefined,\n config: ClientConfig = {}\n): ClientConfig {\n return {\n ...setup?.config,\n ...config,\n middleware: [...(setup?.middleware ?? []), ...(config.middleware ?? [])],\n };\n}\n";
10
+ readonly 'send.ts': "import { abortError, TimeoutError } from './errors.js';\nimport { defaultRetryOn, retryDelay, sleep } from './retry.js';\nimport type {\n ClientConfig,\n Middleware,\n OperationContext,\n RequestContext,\n RequestOptions,\n RetryConfig,\n} from './types.js';\n\n/**\n * Optional behaviors the send core can use but never statically imports — wired by\n * `createClient` (the same seam the future inline-mode assembler relies on).\n */\nexport type SendCapabilities = {\n /** Serialize a typed multipart body (a plain object) to FormData. */\n serializeMultipart?: (body: Record<string, unknown>) => FormData;\n};\n\n/**\n * Normalize a caller's `HeadersInit` (plain record, `Headers` instance, or entry pairs)\n * to a plain record — spreading a `Headers` or an array contributes no entries.\n */\nexport function toHeaderRecord(headers: HeadersInit | undefined): Record<string, string> {\n if (headers === undefined) return {};\n if (headers instanceof Headers) {\n const record: Record<string, string> = {};\n headers.forEach((value, key) => {\n record[key] = value;\n });\n return record;\n }\n if (Array.isArray(headers)) return Object.fromEntries(headers);\n return headers;\n}\n\n/**\n * The effective middleware chain for a request: the single `onRequest`/`onResponse`/\n * `onError` config hooks as one implicit first middleware, then `config.middleware`.\n */\nexport function middlewareChain(config: ClientConfig): Middleware[] {\n const single =\n config.onRequest || config.onResponse || config.onError\n ? [{ onRequest: config.onRequest, onResponse: config.onResponse, onError: config.onError }]\n : [];\n return [...single, ...(config.middleware ?? [])];\n}\n\n/**\n * The fetch core shared by every operation: default + config + per-call headers, the\n * `onRequest` chain (BEFORE body serialization, so mutations are sent), body\n * serialization (JSON, or FormData via the multipart capability), the retry loop\n * (idempotent-only defaults, `Retry-After`, abandoned-body drain), and the reverse\n * `onResponse` onion. Returns the final response plus the request context.\n */\nexport async function send(\n config: ClientConfig,\n op: OperationContext,\n url: string,\n init: RequestOptions,\n body: unknown | undefined,\n bodySpec: { contentType: string; multipart?: boolean } | undefined,\n caps: SendCapabilities,\n accept = 'application/json'\n): Promise<{ response: Response; context: RequestContext }> {\n const { retry: callRetry, timeout: callTimeout, idempotencyKey: callKey, ...fetchInit } = init;\n const retry: RetryConfig = { ...config.retry, ...callRetry };\n const timeout = callTimeout ?? config.timeout;\n const idempotency = callKey ?? config.idempotencyKey;\n const extra = typeof config.headers === 'function' ? await config.headers() : config.headers;\n const headers: Record<string, string> = {\n Accept: accept,\n ...extra,\n ...toHeaderRecord(fetchInit.headers),\n };\n const method = (fetchInit.method ?? 'GET').toUpperCase();\n // One stable key per LOGICAL call — set before the retry loop so every attempt\n // re-sends the same key; a caller-provided header always wins.\n if (\n idempotency !== undefined &&\n idempotency !== false &&\n (method === 'POST' || method === 'PATCH') &&\n !('Idempotency-Key' in headers) &&\n !('idempotency-key' in headers)\n ) {\n headers['Idempotency-Key'] =\n typeof idempotency === 'string'\n ? idempotency\n : typeof idempotency === 'function'\n ? idempotency()\n : crypto.randomUUID();\n }\n // Client identification for the API owner's telemetry — never in browsers, where a\n // custom header would force a CORS preflight the API may not allow.\n if (\n typeof config.clientHeader === 'string' &&\n typeof document === 'undefined' &&\n !('X-Redocly-Client' in headers) &&\n !('x-redocly-client' in headers)\n ) {\n headers['X-Redocly-Client'] = config.clientHeader;\n }\n const context: RequestContext = {\n url,\n method: fetchInit.method ?? 'GET',\n headers,\n body,\n operation: op,\n };\n const middleware = middlewareChain(config);\n for (const mw of middleware) if (mw.onRequest) await mw.onRequest(context);\n // Serialize AFTER onRequest so body mutations (case conversion, enveloping, signing) take effect.\n let payload: BodyInit | undefined;\n if (context.body !== undefined) {\n const value = context.body;\n const isBinary =\n value instanceof Blob ||\n value instanceof ArrayBuffer ||\n ArrayBuffer.isView(value as ArrayBufferView);\n const isFormData = typeof FormData !== 'undefined' && value instanceof FormData;\n const isURLSearchParams = value instanceof URLSearchParams;\n if (isFormData || isURLSearchParams || isBinary || typeof value === 'string') {\n payload = value as BodyInit;\n } else if (bodySpec?.multipart === true) {\n if (!caps.serializeMultipart) {\n throw new Error('Multipart capability not wired: cannot serialize the request body');\n }\n payload = caps.serializeMultipart(value as Record<string, unknown>);\n } else {\n payload = JSON.stringify(value);\n if (!('Content-Type' in context.headers) && !('content-type' in context.headers)) {\n // The spec's declared request content type (e.g. application/merge-patch+json).\n context.headers['Content-Type'] = bodySpec?.contentType ?? 'application/json';\n }\n }\n }\n const doFetch = config.fetch ?? fetch;\n const maxAttempts = 1 + (retry.retries ?? 0);\n const retryOn = retry.retryOn ?? defaultRetryOn;\n const signal = fetchInit.signal ?? undefined;\n\n let attempt = 0;\n while (true) {\n attempt++;\n if (signal?.aborted) throw abortError(signal);\n // A fresh timeout budget per attempt; the caller's signal still wins the race.\n // The composed signal also governs reading the response body.\n const attemptSignal = timeout\n ? signal\n ? AbortSignal.any([signal, AbortSignal.timeout(timeout)])\n : AbortSignal.timeout(timeout)\n : signal;\n let response: Response;\n try {\n response = await doFetch(context.url, {\n ...fetchInit,\n signal: attemptSignal,\n method: context.method,\n headers: context.headers,\n body: payload,\n });\n } catch (error) {\n if (\n attempt < maxAttempts &&\n !signal?.aborted &&\n (await retryOn({ attempt, request: context, error }))\n ) {\n await sleep(retryDelay(retry, attempt, null), signal);\n continue;\n }\n // Our timeout fired (never the caller's own abort — that rethrows untouched):\n // wrap the bare DOMException with the context a log line needs.\n if (\n timeout &&\n !signal?.aborted &&\n error instanceof DOMException &&\n error.name === 'TimeoutError'\n ) {\n throw new TimeoutError(op.id, timeout, attempt);\n }\n throw error;\n }\n // Reverse order: the last-registered middleware wraps closest to the network (onion).\n for (let i = middleware.length - 1; i >= 0; i--) {\n const onResponse = middleware[i].onResponse;\n if (onResponse) {\n const replaced = await onResponse(response, context);\n if (replaced && replaced !== response) {\n // Cancel the abandoned original's body — like the retry path, an unread body\n // keeps its connection checked out under Node/undici.\n await response.body?.cancel().catch(() => undefined);\n response = replaced;\n }\n }\n }\n if (\n !response.ok &&\n attempt < maxAttempts &&\n !signal?.aborted &&\n (await retryOn({ attempt, request: context, response }))\n ) {\n const retryAfter = response.headers.get('retry-after');\n // Drain the abandoned response body before the next attempt: an unread body\n // keeps the connection checked out (and can stall the pool) under Node/undici\n // and other strict HTTP clients. Ignore errors (e.g. a middleware already read it).\n await response.body?.cancel().catch(() => undefined);\n await sleep(retryDelay(retry, attempt, retryAfter), signal);\n continue;\n }\n return { response, context };\n }\n}\n";
11
+ readonly 'sse.ts': "import { ApiError } from './errors.js';\nimport { readError } from './parse.js';\nimport { sleep } from './retry.js';\nimport { send, toHeaderRecord } from './send.js';\nimport type { ClientConfig, OperationContext, ServerSentEvent, SseOptions } from './types.js';\n\n/**\n * A frame delimiter: two consecutive line terminators (each CR, LF, or CRLF, per the SSE\n * spec — so mixed endings like `\\n\\r\\n` are valid boundaries, not just matching pairs).\n */\nconst FRAME_DELIMITER = /(?:\\r\\n|\\r|\\n){2}/;\n\n/**\n * A terminally malformed event stream — unparseable JSON `data` or an unbounded frame.\n * A stable bad payload, not a dropped connection, so the stream never reconnects on it.\n */\nexport class SseParseError extends Error {}\n\n/**\n * Consume a `text/event-stream` operation as typed events (capability module — wired\n * into `createClient`). Auto-reconnects on dropped connections, resuming from the last\n * seen event id via `Last-Event-ID` (backoff: the server's `retry:` value, then\n * `reconnectDelay`, then 1s — exponential with jitter, capped at 30s). A clean stream\n * end flushes a trailing frame and finishes; `break`/abort end the iterator cleanly.\n */\nexport async function* sse<T>(\n config: ClientConfig,\n op: OperationContext,\n prepare: () => Promise<{ url: string; init: SseOptions; body?: unknown }>,\n dataKind: 'json' | 'text' = 'text'\n): AsyncGenerator<ServerSentEvent<T>> {\n let lastEventId: string | undefined;\n let serverRetry: number | undefined;\n let failures = 0;\n while (true) {\n // Re-prepare each attempt so a refresh-style TokenProvider yields a fresh credential\n // on reconnect (the auth is baked into `url` query + `init.headers`). `reconnect`,\n // `reconnectDelay`, and `signal` come from the caller's original options unchanged.\n const { url, init, body: requestBody } = await prepare();\n const { reconnect = true, reconnectDelay, ...rest } = init;\n const signal = rest.signal ?? undefined;\n if (signal?.aborted) return;\n const headers: Record<string, string> = {\n Accept: 'text/event-stream',\n ...toHeaderRecord(rest.headers),\n };\n const sendHeaders =\n lastEventId === undefined ? headers : { ...headers, 'Last-Event-ID': lastEventId };\n try {\n const { response } = await send(\n config,\n op,\n url,\n // `timeout: 0` opts the stream out of a config-level timeout — an event stream\n // is long-lived by design and must not be severed after N milliseconds.\n { ...rest, method: rest.method ?? 'GET', headers: sendHeaders, timeout: 0 },\n requestBody,\n undefined,\n {}\n );\n if (!response.ok) {\n const errorBody = await readError(response);\n throw new ApiError(url, response.status, response.statusText, errorBody);\n }\n failures = 0;\n const body = response.body;\n if (!body) return;\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n while (true) {\n const { done, value } = await reader.read();\n buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });\n let match: RegExpExecArray | null;\n while ((match = FRAME_DELIMITER.exec(buffer)) !== null) {\n const raw = buffer.slice(0, match.index);\n buffer = buffer.slice(match.index + match[0].length);\n const event = parseSseFrame(raw, dataKind);\n if (event) {\n if (event.id !== undefined) lastEventId = event.id;\n if (event.retry !== undefined) serverRetry = event.retry;\n yield event as ServerSentEvent<T>;\n }\n }\n if (done) {\n // Stream closed cleanly. Flush a final event that arrived without a trailing\n // delimiter, then finish — a clean end is not a dropped connection, so do not reconnect.\n const event = buffer.length > 0 ? parseSseFrame(buffer, dataKind) : undefined;\n if (event) {\n if (event.id !== undefined) lastEventId = event.id;\n if (event.retry !== undefined) serverRetry = event.retry;\n yield event as ServerSentEvent<T>;\n }\n return;\n }\n // Bound memory: a server that never sends a frame delimiter would otherwise\n // grow `buffer` without limit. 1 MiB is far above any real SSE frame.\n if (buffer.length > 1048576) {\n throw new SseParseError('SSE frame exceeded 1048576 characters without a delimiter');\n }\n }\n } finally {\n await reader.cancel().catch(() => undefined);\n }\n } catch (error) {\n if (signal?.aborted) return;\n // A non-OK HTTP response (4xx/5xx) or an unparseable JSON payload is a definitive\n // error, not a transient drop — surface it instead of reconnecting in a loop (a\n // stable bad payload would otherwise reconnect forever).\n if (error instanceof ApiError || error instanceof SseParseError) throw error;\n // A transport failure (connect/DNS/reset) when opening the request, or a mid-stream\n // read error, is a dropped connection: fall through to backoff/reconnect when enabled.\n if (!reconnect) throw error;\n }\n // Only the swallowed-drop path reaches here: reconnect is on and the signal not aborted.\n failures++;\n const base = serverRetry ?? reconnectDelay ?? 1000;\n const delay = Math.min(base * Math.pow(2, failures - 1), 30_000);\n try {\n await sleep(Math.random() * delay, signal);\n } catch {\n return; // sleep rejects only on abort — end the iterator cleanly\n }\n }\n}\n\n/** Parse one raw SSE frame (its lines) into an event; returns undefined for comment-only frames. */\nexport function parseSseFrame(\n raw: string,\n dataKind: 'json' | 'text'\n): ServerSentEvent<unknown> | undefined {\n let event: string | undefined;\n const dataLines: string[] = [];\n let id: string | undefined;\n let retry: number | undefined;\n let sawField = false;\n for (const line of raw.split(/\\r\\n|\\n|\\r/)) {\n if (line === '' || line.startsWith(':')) continue;\n const colon = line.indexOf(':');\n const field = colon === -1 ? line : line.slice(0, colon);\n let val = colon === -1 ? '' : line.slice(colon + 1);\n if (val.startsWith(' ')) val = val.slice(1);\n sawField = true;\n if (field === 'event') event = val;\n else if (field === 'data') dataLines.push(val);\n else if (field === 'id') id = val;\n else if (field === 'retry') {\n // ASCII digits only, per the EventSource spec — anything else is ignored\n // (`Number('')` is 0 and would zero the reconnect backoff).\n if (/^\\d+$/.test(val)) retry = Number(val);\n }\n }\n if (!sawField) return undefined;\n const dataText = dataLines.join('\\n');\n let data: unknown = dataText;\n if (dataKind === 'json' && dataText !== '') {\n try {\n data = JSON.parse(dataText);\n } catch (error) {\n throw new SseParseError(\n `Failed to parse SSE event data as JSON: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n return { event, data, id, retry };\n}\n";
12
+ readonly 'create-client.ts': "import { ApiError } from './errors.js';\nimport { parse, readError } from './parse.js';\nimport { middlewareChain, send, toHeaderRecord, type SendCapabilities } from './send.js';\nimport type {\n ApiErrorLike,\n Client,\n ClientConfig,\n Middleware,\n OperationContext,\n OperationDescriptor,\n OpsShape,\n PaginationSpec,\n ParseAs,\n QueryValue,\n RequestOptions,\n ResponseHeaderSpec,\n SecuritySpec,\n ServerSentEvent,\n SseOptions,\n TokenProvider,\n} from './types.js';\nimport { buildUrl, substitutePath, type QueryStyle } from './url.js';\n\n/**\n * The optional behaviors `createClientCore` can dispatch to but never statically\n * imports. The package's public `createClient` wires the full set; the future\n * inline-mode assembler wires only the capabilities a spec needs.\n */\nexport type Capabilities = SendCapabilities & {\n resolveAuth?: (\n security: readonly (readonly SecuritySpec[])[],\n config: ClientConfig\n ) => Promise<{ headers: Record<string, string>; query: Record<string, string> }>;\n sse?: (\n config: ClientConfig,\n op: OperationContext,\n // Re-preparing per (re)connect (not a frozen url/init) lets a refresh-style\n // TokenProvider issue a fresh credential after a dropped stream reconnects.\n prepare: () => Promise<{ url: string; init: SseOptions }>,\n dataKind: 'json' | 'text'\n ) => AsyncGenerator<ServerSentEvent<unknown>>;\n paginate?: {\n pages: (\n call: (args?: OperationArgs, init?: RequestOptions) => Promise<unknown>,\n spec: PaginationSpec,\n args?: OperationArgs,\n init?: RequestOptions\n ) => AsyncGenerator<unknown>;\n items: (\n call: (args?: OperationArgs, init?: RequestOptions) => Promise<unknown>,\n spec: PaginationSpec,\n args?: OperationArgs,\n init?: RequestOptions\n ) => AsyncGenerator<unknown>;\n // The `link`-style iterators need the raw `Link` header + page URL, which the\n // parsed-page call above cannot carry (the shape mirrors paginate's `LinkPageCall`).\n pagesByLink: (\n call: (\n args?: OperationArgs,\n init?: RequestOptions\n ) => Promise<{ page: unknown; linkHeader: string | null; url: string }>,\n args?: OperationArgs,\n init?: RequestOptions\n ) => AsyncGenerator<unknown>;\n itemsByLink: (\n call: (\n args?: OperationArgs,\n init?: RequestOptions\n ) => Promise<{ page: unknown; linkHeader: string | null; url: string }>,\n spec: PaginationSpec,\n args?: OperationArgs,\n init?: RequestOptions\n ) => AsyncGenerator<unknown>;\n };\n};\n\n/**\n * One call's inputs, namespaced by transport layer. `argsStyle: 'flat'` clients accept the\n * merged form instead (every parameter and body property at one level) — `namespaceArgs`\n * converts it to this shape before anything downstream reads it.\n */\nexport type OperationArgs = {\n path?: Record<string, unknown>;\n query?: Record<string, QueryValue>;\n body?: unknown;\n headers?: Record<string, unknown>;\n cookies?: Record<string, unknown>;\n} & Record<string, unknown>;\n\n/** The five layer keys, and the only top-level keys a namespaced call may carry. */\nconst LAYERS: readonly string[] = ['path', 'query', 'body', 'headers', 'cookies'];\n\n/** Where a declared parameter's `in` value puts it. */\nconst LAYER_OF: Record<string, 'path' | 'query' | 'headers' | 'cookies'> = {\n path: 'path',\n query: 'query',\n header: 'headers',\n cookie: 'cookies',\n};\n\n/**\n * Merged (`argsStyle: 'flat'`) args → the namespaced shape. A key that names a declared\n * parameter goes to that parameter's layer; anything else is a property of the request\n * body, which is how a flat call spells an object body. `body` stays reserved for the\n * operations a flat call cannot merge (an array, a scalar, or a binary body).\n */\nfunction namespaceArgs(op: OperationDescriptor, args: OperationArgs): OperationArgs {\n const layers: Record<string, Record<string, unknown>> = {};\n let body: unknown;\n let properties: Record<string, unknown> | undefined;\n const layerOfParam = new Map((op.params ?? []).map((param) => [param.name, param.in]));\n for (const [key, value] of Object.entries(args)) {\n const layer = LAYER_OF[layerOfParam.get(key) ?? ''];\n if (layer !== undefined) {\n (layers[layer] ??= {})[key] = value;\n } else if (key === 'body' && op.body !== undefined) {\n body = value;\n } else if (op.body !== undefined) {\n (properties ??= {})[key] = value;\n } else {\n throw new TypeError(\n `Unknown argument \"${key}\" for operation \"${op.id}\": it names no declared parameter, and the operation takes no request body.`\n );\n }\n }\n const namespaced: OperationArgs = {};\n if (layers.path) namespaced.path = layers.path;\n // The flat surface types every query value, so the collected bag is one by construction.\n if (layers.query) namespaced.query = layers.query as Record<string, QueryValue>;\n if (layers.headers) namespaced.headers = layers.headers;\n if (layers.cookies) namespaced.cookies = layers.cookies;\n if (properties !== undefined) namespaced.body = properties;\n else if (body !== undefined) namespaced.body = body;\n return namespaced;\n}\n\n/** The response reader implied by the descriptor (before any per-call `parseAs` override). */\n/**\n * The `Accept` header matching how the response will be read — a blob/text operation\n * must not ask for `application/json` (a content-negotiating server would 406 or\n * answer with a JSON error body instead of the payload). Caller `init.headers` and\n * `config.headers` still override.\n */\nfunction acceptFor(kind: ParseAs | 'void'): string {\n if (kind === 'text') return 'text/*';\n if (kind === 'blob' || kind === 'arrayBuffer' || kind === 'stream' || kind === 'formData') {\n return '*/*';\n }\n return 'application/json'; // json | auto | void\n}\n\nfunction kindFor(op: OperationDescriptor): ParseAs | 'void' {\n if (op.responseKind === 'void' || op.responseKind === 'blob' || op.responseKind === 'text') {\n return op.responseKind;\n }\n return 'auto';\n}\n\n/**\n * The call's inputs in namespaced form, converting first on a flat-style client. An\n * operation the generator marked `argsStyle: 'grouped'` is already namespaced — its names\n * could not be merged, so its input type never offered the flat shape.\n */\nfunction inputOf(\n op: OperationDescriptor,\n args: OperationArgs,\n config: ClientConfig\n): OperationArgs {\n const merged = config.argsStyle === 'flat' && op.argsStyle !== 'grouped';\n return merged ? namespaceArgs(op, args) : args;\n}\n\n/** Route the namespaced args to the request pieces. */\nfunction splitArgs(op: OperationDescriptor, args: OperationArgs) {\n // An unknown layer key can only be a bug (usually flat-style args on a namespaced\n // client). TypeScript catches it, but a transpiler that skips type-checking would\n // otherwise ship a request that silently drops the value — fail the call loudly.\n for (const key of Object.keys(args)) {\n if (!LAYERS.includes(key)) {\n throw new TypeError(\n `Unknown argument \"${key}\" for operation \"${op.id}\". Inputs are grouped by layer: ${LAYERS.join(', ')}.`\n );\n }\n }\n return {\n path: args.path ?? {},\n query: args.query,\n body: args.body,\n headers: args.headers,\n cookies: args.cookies,\n };\n}\n\n/**\n * The query-serialization hints for the descriptor's query params. A spec is built only\n * when the param deviates from the OpenAPI defaults (`form` + `explode: true`, encoded),\n * and always fully resolved — so `explode: false` or `allowReserved` alone (no `style`)\n * are honored, and an omitted `explode` keeps the exploded default.\n */\nfunction queryStyles(op: OperationDescriptor): Record<string, QueryStyle> | undefined {\n let styles: Record<string, QueryStyle> | undefined;\n for (const param of op.params ?? []) {\n if (param.in !== 'query') continue;\n const deviates =\n (param.style !== undefined && param.style !== 'form') ||\n param.explode === false ||\n param.allowReserved === true;\n if (!deviates) continue;\n styles ??= {};\n styles[param.name] = {\n style: param.style ?? 'form',\n explode: param.explode ?? true,\n allowReserved: param.allowReserved,\n };\n }\n return styles;\n}\n\n/** Stringify caller-supplied extra headers, skipping empty entries. */\nfunction stringHeaders(headers: Record<string, unknown> | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers ?? {})) {\n if (value !== undefined && value !== null) out[key] = String(value);\n }\n return out;\n}\n\n/** Build the request pieces an attempt needs: the final URL and the merged per-call init. */\nasync function prepareRequest(\n config: ClientConfig,\n op: OperationDescriptor,\n args: OperationArgs,\n init: RequestOptions | SseOptions,\n caps: Capabilities\n): Promise<{ url: string; init: RequestOptions; body: unknown }> {\n const { path, query, body, headers, cookies } = splitArgs(op, args);\n const authed: { headers: Record<string, string>; query: Record<string, string> } =\n op.security?.length && caps.resolveAuth\n ? await caps.resolveAuth(op.security, config)\n : { headers: {}, query: {} };\n // Cookie params join the auth-injected cookies in one `Cookie` header (values\n // percent-encoded, like auth cookies). Server-side only — browsers own the header.\n const cookiePairs = Object.entries(cookies ?? {})\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([cookieName, value]) => `${cookieName}=${encodeURIComponent(String(value))}`);\n if (cookiePairs.length > 0) {\n authed.headers.Cookie = [authed.headers.Cookie, ...cookiePairs].filter(Boolean).join('; ');\n }\n const fullQuery: Record<string, QueryValue> = { ...query, ...authed.query };\n const url = buildUrl(\n config.serverUrl ?? '',\n substitutePath(op.path, path),\n Object.keys(fullQuery).length > 0 ? fullQuery : undefined,\n queryStyles(op)\n );\n const mergedInit: RequestOptions = {\n ...init,\n method: op.method.toUpperCase(),\n // Precedence, lowest → highest (later spreads win): injected auth → explicit\n // header params → caller `init.headers` — the caller always overrides both.\n headers: {\n ...authed.headers,\n ...stringHeaders(headers),\n ...toHeaderRecord(init.headers),\n },\n };\n return { url, init: mergedInit, body };\n}\n\n/** Coerce a single declared response header value; omit when absent or unparsable. */\nfunction coerceResponseHeader(\n raw: string | null,\n type: ResponseHeaderSpec['type']\n): string | number | boolean | undefined {\n if (raw === null) return undefined;\n if (type === 'number') {\n if (raw.trim() === '') return undefined;\n const value = Number(raw);\n return Number.isFinite(value) ? value : undefined;\n }\n if (type === 'boolean') {\n const value = raw.trim().toLowerCase();\n if (value === 'true') return true;\n if (value === 'false') return false;\n return undefined;\n }\n return raw;\n}\n\n/** Build the camelCase declared-header bag for a throw-mode envelope. */\nfunction readEnvelopeHeaders(\n response: Response,\n specs: readonly ResponseHeaderSpec[] | undefined\n): Record<string, string | number | boolean> {\n const headers: Record<string, string | number | boolean> = {};\n for (const spec of specs ?? []) {\n const value = coerceResponseHeader(response.headers.get(spec.name), spec.type);\n if (value !== undefined) headers[spec.key] = value;\n }\n return headers;\n}\n\n/** One non-SSE call: send, then branch on the configured error mode. */\nasync function execute(\n config: ClientConfig,\n op: OperationDescriptor,\n args: OperationArgs,\n init: RequestOptions,\n caps: Capabilities\n): Promise<unknown> {\n const prepared = await prepareRequest(config, op, args, init, caps);\n const opCtx: OperationContext = { id: op.id, path: op.path, tags: [...(op.tags ?? [])] };\n // `parseAs` / `envelope` are client options, not fetch RequestInit fields.\n const { parseAs, envelope, ...sendInit } = prepared.init;\n const readKind = parseAs ?? kindFor(op);\n const { response, context } = await send(\n config,\n opCtx,\n prepared.url,\n sendInit,\n prepared.body,\n op.body,\n caps,\n acceptFor(readKind)\n );\n if (config.errorMode === 'result') {\n if (!response.ok) {\n return { data: undefined, error: await readError(response), response };\n }\n return { data: await parse(response, readKind), error: undefined, response };\n }\n if (!response.ok) {\n let error: globalThis.Error = new ApiError(\n context.url,\n response.status,\n response.statusText,\n await readError(response)\n );\n // Thread the error through each middleware's onError in turn (each may replace it).\n for (const mw of middlewareChain(config)) {\n if (mw.onError) error = await mw.onError(error as ApiErrorLike, context);\n }\n throw error;\n }\n const data = await parse(response, readKind);\n if (envelope === true) {\n return {\n data,\n headers: readEnvelopeHeaders(response, op.responseHeaders),\n response,\n };\n }\n return data;\n}\n\n/** The paginate capability, or a descriptive throw when a paginated op is iterated unwired. */\nfunction paginateCapability(caps: Capabilities, op: OperationDescriptor) {\n if (!caps.paginate) {\n throw new Error(`Pagination capability not wired: cannot iterate operation \"${op.id}\"`);\n }\n return caps.paginate;\n}\n\n/**\n * The per-page call the iterators drive: the method itself in throw mode; in result\n * mode a wrapper that unwraps the `{ data, error, response }` envelope — the page\n * pointers are data-rooted — rethrowing a failed page as `ApiError` (iteration is\n * error-mode-agnostic; the throw-mode-only `onError` middleware hook is not invoked).\n */\nfunction pageCall(\n method: (args?: OperationArgs, init?: RequestOptions) => Promise<unknown>,\n config: ClientConfig\n) {\n const callWithoutEnvelope = (args?: OperationArgs, init?: RequestOptions) => {\n if (!init || init.envelope === undefined) return method(args, init);\n const { envelope: _envelope, ...pageInit } = init;\n return method(args, pageInit);\n };\n if (config.errorMode !== 'result') return callWithoutEnvelope;\n return async (args?: OperationArgs, init?: RequestOptions) => {\n const envelope = (await callWithoutEnvelope(args, init)) as {\n data: unknown;\n error: unknown;\n response: Response;\n };\n // Failure is `!response.ok` — NOT `data === undefined`: a successful bodyless page\n // (204/void) also parses to undefined data, and a failed page's `error` can be\n // undefined too (unreadable body). The pointers then miss on the undefined data\n // and iteration stops cleanly, which is the correct semantics for an empty page.\n if (!envelope.response.ok) {\n const { response } = envelope;\n throw new ApiError(response.url, response.status, response.statusText, envelope.error);\n }\n return envelope.data;\n };\n}\n\n/**\n * The per-page call the `link`-style iterators drive: like `execute`, but returning the\n * parsed page together with the raw `Link` header and the page's own URL (for resolving\n * a relative `rel=\"next\"` target). Error-mode-agnostic like all iteration: a failed\n * page throws `ApiError` even on result-mode clients.\n */\nfunction linkPageCall(config: ClientConfig, op: OperationDescriptor, caps: Capabilities) {\n return async (args: OperationArgs = {}, init: RequestOptions = {}) => {\n const prepared = await prepareRequest(config, op, args, init, caps);\n const { parseAs, envelope: _envelope, ...sendInit } = prepared.init;\n const readKind = parseAs ?? kindFor(op);\n const opCtx: OperationContext = { id: op.id, path: op.path, tags: [...(op.tags ?? [])] };\n const { response } = await send(\n config,\n opCtx,\n prepared.url,\n sendInit,\n prepared.body,\n op.body,\n caps,\n acceptFor(readKind)\n );\n if (!response.ok) {\n throw new ApiError(\n prepared.url,\n response.status,\n response.statusText,\n await readError(response)\n );\n }\n return {\n page: await parse(response, readKind),\n linkHeader: response.headers.get('link'),\n // Some `Response` implementations leave `url` empty (mocks, constructed responses).\n url: response.url === '' ? prepared.url : response.url,\n };\n };\n}\n\n/**\n * Build a typed instance client over operation descriptors: one real bound method per\n * operation (attached by a construction-time loop — no Proxy), plus the core members\n * (`configure`/`use`/`auth`), which are assigned AFTER the loop so they win any name\n * collision with an operation. All behavior dispatches through the capability seam.\n */\nexport function createClientCore<\n Ops extends OpsShape,\n Id extends string = string,\n Path extends string = string,\n Tag extends string = string,\n>(\n operations: Record<string, OperationDescriptor>,\n initial: ClientConfig<OperationContext<Id, Path, Tag>> = {},\n caps: Capabilities = {}\n): Client<Ops, OperationContext<Id, Path, Tag>> {\n // The literal-union narrowing is a compile-time DX contract only; internally the\n // runtime works with the base (string-typed) context. One cast at this boundary —\n // `ClientConfig<Narrow>` is not assignable to `ClientConfig` (middleware ctx\n // params are contravariant).\n const given = initial as ClientConfig;\n // Private mutable config; the middleware array is copied so `use()` never mutates the caller's.\n const config: ClientConfig = { ...given, middleware: [...(given.middleware ?? [])] };\n const client = {} as Record<string, unknown>;\n\n for (const [name, op] of Object.entries(operations)) {\n if (op.responseKind === 'sse') {\n const method = (given: OperationArgs = {}, init: SseOptions = {}) => {\n const args = inputOf(op, given, config);\n if (!caps.sse) {\n throw new Error(`SSE capability not wired: cannot stream operation \"${op.id}\"`);\n }\n const stream = caps.sse;\n return (async function* () {\n const opCtx: OperationContext = { id: op.id, path: op.path, tags: [...(op.tags ?? [])] };\n // A thunk the stream re-runs on every (re)connect, so auth (which `prepareRequest`\n // resolves) is refreshed per attempt rather than frozen at the first connect.\n const prepare = async () => {\n const prepared = await prepareRequest(config, op, args, init, caps);\n return { url: prepared.url, init: prepared.init as SseOptions, body: prepared.body };\n };\n yield* stream(config, opCtx, prepare, op.sseDataKind ?? 'text');\n })();\n };\n // Consumers key off the function reference (cache keys, `OPERATIONS[fn.name]`), so\n // each closure carries its operationId instead of an inferred binding name.\n // `operationId` is the explicit, minification-proof form of the same identity\n // (the SPEC operationId — `name` is the emitted key, which a collision may rename).\n Object.defineProperty(method, 'name', { value: name });\n Object.defineProperty(method, 'operationId', { value: op.id });\n client[name] = method;\n } else {\n // `raw` takes namespaced args; `method` is the public entry that accepts whichever\n // style the client was generated with. The iterators namespace once and then drive\n // `raw`, so a flat call is never converted twice.\n const raw = (args: OperationArgs = {}, init: RequestOptions = {}) =>\n execute(config, op, args, init, caps);\n const method = (args: OperationArgs = {}, init: RequestOptions = {}) =>\n raw(inputOf(op, args, config), init);\n Object.defineProperty(method, 'name', { value: name });\n Object.defineProperty(method, 'operationId', { value: op.id });\n const spec = op.pagination;\n // Paginated ops keep their one-shot call and gain `.pages`/`.items`, dispatching\n // through the capability seam (like SSE: absent capability throws descriptively).\n // Iteration is error-mode-agnostic: the iterators' pointers are data-rooted, so on\n // a result-mode client (`errorMode` is fixed at construction — `configure()`\n // ignores it) each page's envelope is unwrapped before it reaches the capability.\n // A failed page aborts iteration by throwing ApiError, even on result-mode\n // clients; the `onError` middleware hook (throw-mode-only) is not invoked.\n client[name] =\n spec === undefined\n ? method\n : spec.style === 'link'\n ? Object.assign(method, {\n pages: (args?: OperationArgs, init?: RequestOptions) =>\n paginateCapability(caps, op).pagesByLink(\n linkPageCall(config, op, caps),\n inputOf(op, args ?? {}, config),\n init\n ),\n items: (args?: OperationArgs, init?: RequestOptions) =>\n paginateCapability(caps, op).itemsByLink(\n linkPageCall(config, op, caps),\n spec,\n inputOf(op, args ?? {}, config),\n init\n ),\n })\n : Object.assign(method, {\n pages: (args?: OperationArgs, init?: RequestOptions) =>\n paginateCapability(caps, op).pages(\n pageCall(raw, config),\n spec,\n inputOf(op, args ?? {}, config),\n init\n ),\n items: (args?: OperationArgs, init?: RequestOptions) =>\n paginateCapability(caps, op).items(\n pageCall(raw, config),\n spec,\n inputOf(op, args ?? {}, config),\n init\n ),\n });\n }\n }\n\n // Core members are assigned AFTER the operation loop — they win over colliding op names.\n client.configure = (next: ClientConfig): void => {\n // `errorMode` and `argsStyle` are fixed at generate time (they shape the static types);\n // flipping either at runtime would silently desync the calls from `Client<Ops>`, so both\n // are ignored here.\n const { errorMode: _fixedMode, argsStyle: _fixedStyle, auth, ...rest } = next;\n Object.assign(config, rest);\n // `auth` merges into existing credentials (like the `auth.*` setters) rather than\n // replacing wholesale — so `configure({ auth: { bearer } })` keeps a previously set\n // basic/apiKey. `apiKey` merges per scheme.\n if (auth) {\n config.auth = {\n ...config.auth,\n ...auth,\n ...(auth.apiKey ? { apiKey: { ...config.auth?.apiKey, ...auth.apiKey } } : {}),\n };\n }\n };\n client.use = (...middleware: Middleware[]): void => {\n // Reassign (don't push) so a caller-provided `middleware` array isn't mutated.\n config.middleware = [...(config.middleware ?? []), ...middleware];\n };\n client.auth = {\n bearer(token: TokenProvider): void {\n config.auth = { ...config.auth, bearer: token };\n },\n basic(username: string, password: string): void {\n config.auth = { ...config.auth, basic: { username, password } };\n },\n apiKey(scheme: string, value: TokenProvider): void {\n config.auth = { ...config.auth, apiKey: { ...config.auth?.apiKey, [scheme]: value } };\n },\n };\n\n return client as Client<Ops, OperationContext<Id, Path, Tag>>;\n}\n";
13
+ readonly 'paginate.ts': "import type { OperationArgs } from './create-client.js';\nimport type { PaginationSpec, QueryValue, RequestOptions } from './types.js';\n\n/**\n * Auto-pagination (capability module — wired into `createClient`, dispatched by the\n * method's `.pages()`/`.items()`): walk an operation's pages by advancing the descriptor's\n * `param` query parameter, per its `style`. The caller's args are never mutated — each\n * request gets a fresh `query` clone — and `init` is forwarded to every call.\n *\n * Iteration is error-mode-agnostic: `call` always resolves to the RAW page (on a\n * result-mode client the attachment unwraps the envelope first), so a failed page\n * aborts iteration by throwing `ApiError`, even on result-mode clients; the `onError`\n * middleware hook (throw-mode-only) is not invoked.\n */\n\n/**\n * Resolve an RFC 6901 JSON pointer (`~1` → `/`, `~0` → `~`) against a value.\n * The empty pointer is the whole document; anything else must start with `/`.\n * Returns `undefined` on any miss (bad token, absent key, non-object step) — never throws.\n */\nexport function resolvePointer(value: unknown, pointer: string): unknown {\n if (pointer === '') return value;\n if (!pointer.startsWith('/')) return undefined;\n let current = value;\n for (const token of pointer.slice(1).split('/')) {\n const key = token.replaceAll('~1', '/').replaceAll('~0', '~');\n if (Array.isArray(current)) {\n if (!/^(0|[1-9]\\d*)$/.test(key)) return undefined;\n current = current[Number(key)];\n } else if (Object(current) === current && key in (current as object)) {\n current = (current as Record<string, unknown>)[key];\n } else {\n return undefined;\n }\n }\n return current;\n}\n\n/**\n * Iterate an operation's full page results. Every page is yielded before the stop\n * condition is evaluated, so the last page always arrives. Cursor style resumes from a\n * caller-provided `query[spec.param]`, stops when the optional `hasMore` pointer\n * resolves to `false` or when `nextCursor` resolves to `undefined`/`null`/`''`, and\n * throws if the next cursor is not a string or number, or\n * if the same cursor comes back twice in a row (infinite-loop guards). Offset/page\n * styles advance by item count / by one and stop when\n * the `items` pointer misses or the array is empty.\n */\nexport async function* pages<TPage>(\n call: (args?: OperationArgs, init?: RequestOptions) => Promise<TPage>,\n spec: PaginationSpec,\n args: OperationArgs = {},\n init?: RequestOptions\n): AsyncGenerator<TPage> {\n if (spec.style === 'cursor') {\n let cursor: unknown = args.query?.[spec.param];\n while (true) {\n const query = { ...args.query };\n if (cursor !== undefined) query[spec.param] = cursor as QueryValue;\n const page = await call({ ...args, query }, init);\n yield page;\n // Connection-style APIs keep a non-null cursor on the last page and signal the\n // end via a boolean flag — honor it before the cursor check to skip the\n // follow-up empty request. Strictly `false`: a missing pointer falls through.\n if (spec.hasMore !== undefined && resolvePointer(page, spec.hasMore) === false) return;\n const next = resolvePointer(page, spec.nextCursor);\n if (next === undefined || next === null || next === '') return;\n if (typeof next !== 'string' && typeof next !== 'number') {\n // A fresh non-scalar cursor never compares equal, so without this guard a lying\n // server would slip past the did-not-advance check into an infinite loop.\n throw new Error(`Pagination cursor at ${spec.nextCursor} is not a string or number`);\n }\n if (next === cursor) {\n throw new Error('Pagination did not advance: operation returned the same cursor twice');\n }\n cursor = next;\n }\n } else if (spec.style === 'link') {\n // `link` iteration needs the response's `Link` header, which the parsed-page `call`\n // cannot carry — the client wires those operations to `pagesByLink` instead.\n throw new Error('link-style pagination iterates via pagesByLink');\n } else {\n // Coerce the starting position to a number: a caller may pass `query[spec.param]` as a\n // string (common from URL/form input), and `+=` on a string would concatenate. `null`\n // and `''` count as absent — `Number` would turn them into 0, but a one-shot call\n // omits the param for those values, so the iterator must not start at position 0.\n const start = args.query?.[spec.param];\n const fallback = spec.style === 'page' ? 1 : 0;\n const absent = start === undefined || start === null || start === '';\n let position = absent || Number.isNaN(Number(start)) ? fallback : Number(start);\n let previousItems: string | undefined;\n while (true) {\n const page = await call({ ...args, query: { ...args.query, [spec.param]: position } }, init);\n const pageItems = resolvePointer(page, spec.items);\n // Some APIs clamp a past-the-end offset/page to the last non-empty page instead\n // of returning an empty one — the repeated page would otherwise loop forever\n // (the position always \"advances\", so a cursor-style token check can't catch it).\n const serialized = Array.isArray(pageItems) ? JSON.stringify(pageItems) : undefined;\n if (serialized !== undefined && serialized === previousItems) {\n throw new Error('Pagination did not advance: the operation returned the same page twice');\n }\n yield page;\n if (!Array.isArray(pageItems) || pageItems.length === 0) return;\n previousItems = serialized;\n position += spec.style === 'page' ? 1 : pageItems.length;\n }\n }\n}\n\n/**\n * Iterate the operation's individual items: each page's `items` pointer, flattened.\n * A cursor-style page whose pointer misses yields nothing but pagination continues;\n * for offset/page styles a miss has already stopped `pages`.\n */\nexport async function* items<TItem>(\n call: (args?: OperationArgs, init?: RequestOptions) => Promise<unknown>,\n spec: PaginationSpec,\n args?: OperationArgs,\n init?: RequestOptions\n): AsyncGenerator<TItem> {\n for await (const page of pages(call, spec, args, init)) {\n const pageItems = resolvePointer(page, spec.items);\n if (Array.isArray(pageItems)) yield* pageItems as TItem[];\n }\n}\n\n/**\n * The per-page call the `link`-style iterators drive: the parsed page plus the raw\n * `Link` header and the page's own URL (for resolving a relative `next` target).\n */\nexport type LinkPageCall = (\n args?: OperationArgs,\n init?: RequestOptions\n) => Promise<{ page: unknown; linkHeader: string | null; url: string }>;\n\n/** The `rel=\"next\"` target of an RFC 8288 `Link` header, or `undefined` when absent. */\nexport function linkNext(header: string | null): string | undefined {\n if (header === null) return undefined;\n // Entries are `<url>; rel=\"next\"`, comma-separated; the URL is inside `<>`, so split\n // on commas that precede a `<` and a comma inside a target cannot break an entry.\n for (const entry of header.split(/,\\s*(?=<)/)) {\n const target = /^\\s*<([^>]*)>(.*)$/.exec(entry);\n if (!target) continue;\n const rel = /;\\s*rel\\s*=\\s*\"?([^\";]+)\"?/i.exec(target[2]);\n // `rel` may carry several space-separated relation types (RFC 8288 §3.3).\n if (rel && rel[1].split(/\\s+/).includes('next')) return target[1];\n }\n return undefined;\n}\n\n/**\n * Iterate a `link`-style operation's pages: follow the `Link` header's `rel=\"next\"`\n * target by merging ITS query params into the next call — every page goes through the\n * same declared endpoint, so auth, middleware, and `serverUrl` handling apply\n * unchanged, and credentials can never be handed to a cross-origin `next` URL.\n * Stops when no `rel=\"next\"` is present; throws when the link does not advance\n * (the same target twice, or a self-link — an infinite-loop guard).\n */\nexport async function* pagesByLink<TPage>(\n call: LinkPageCall,\n args: OperationArgs = {},\n init?: RequestOptions\n): AsyncGenerator<TPage> {\n let query = args.query;\n let previous: string | undefined;\n while (true) {\n const { page, linkHeader, url } = await call({ ...args, query }, init);\n yield page as TPage;\n const target = linkNext(linkHeader);\n if (target === undefined) return;\n // A relative target resolves against the page's own URL (RFC 8288 §3.1) — which may\n // itself be relative (relative `serverUrl`, mocked fetch), so both resolve against a\n // placeholder origin. It never reaches the wire: only the target's query params\n // carry into the next call.\n const pageUrl = new URL(url, 'http://relative.invalid');\n const next = new URL(target, pageUrl).toString();\n if (next === previous || next === pageUrl.toString()) {\n throw new Error('Pagination did not advance: the Link rel=\"next\" target repeats');\n }\n previous = next;\n // A repeated key (`?tag=a&tag=b`) folds into an array — the query serializer\n // expands arrays back into repeated pairs.\n const linkParams: Record<string, string | string[]> = {};\n for (const [key, value] of new URL(next).searchParams) {\n const seen = linkParams[key];\n if (seen === undefined) linkParams[key] = value;\n else if (Array.isArray(seen)) seen.push(value);\n else linkParams[key] = [seen, value];\n }\n query = { ...args.query, ...linkParams };\n }\n}\n\n/** Iterate a `link`-style operation's individual items: each page's `items` pointer, flattened. */\nexport async function* itemsByLink<TItem>(\n call: LinkPageCall,\n spec: PaginationSpec,\n args?: OperationArgs,\n init?: RequestOptions\n): AsyncGenerator<TItem> {\n for await (const page of pagesByLink(call, args, init)) {\n const pageItems = resolvePointer(page, spec.items);\n if (Array.isArray(pageItems)) yield* pageItems as TItem[];\n }\n}\n";
14
+ readonly 'cli.ts': "// The generated-CLI engine: a pure argv parser plus a dispatcher that drives the\n// instance client and maps outcomes to the documented exit-code contract\n// (0 success, 1 API error, 2 auth, 3 validation, 4 usage). Node-only by nature,\n// but every effect (env, stdin, files, output) is injected through the wiring so\n// the module itself stays dependency-free and fully unit-testable; the emitted\n// entry fills the defaults with real `node:fs`/`process` bindings.\n\n/** One flag derived from a query parameter. */\nexport type CliFlag = {\n /** Kebab-cased flag name (`--page-size`). */\n name: string;\n /** Original wire parameter name. */\n param: string;\n type: 'string' | 'number' | 'boolean' | 'array';\n required: boolean;\n enum?: string[];\n description?: string;\n};\n\n/** One executable command, derived from the IR at generate time. Pure data. */\nexport type CliCommand = {\n /** Tag; absent = flat/untagged. */\n group?: string;\n name: string;\n summary?: string;\n method: string;\n path: string;\n /** Path params, in path-template order. Always required — that is what a path is. */\n positionals: Array<{\n name: string;\n type?: CliFlag['type'];\n description?: string;\n }>;\n flags: CliFlag[];\n /**\n * Present when the operation takes a JSON request body. `merged` marks a body whose own\n * properties a flat-style call spells at the top level (the generator decides this from\n * the schema, so the CLI and the client can never disagree).\n */\n body?: { required: boolean; merged?: boolean };\n /**\n * The content type of a request body that is NOT JSON (multipart, url-encoded, binary).\n * `--json` cannot build one, so the command is reported as library-only rather than\n * offered as if it were runnable.\n */\n unsupportedBody?: string;\n paginated?: boolean;\n /** `'grouped'` marks a command whose client method takes namespaced inputs even on a\n * flat-style client, because its merged names would collide. */\n argsStyle?: 'grouped';\n sse?: boolean;\n blob?: boolean;\n /** IR schemas for the `schema` command, serialized verbatim. */\n schemas?: { request?: unknown; response?: unknown };\n};\n\nexport type CliAuthScheme = { key: string; kind: 'bearer' | 'basic' | 'apiKey' };\n\nexport type CliWiring = {\n /** The name the CLI is invoked as, for help output only. The generated entry reads it\n * from `process.argv[1]`, so help never names a command that is not installed. */\n name: string;\n /** Credential variable prefix, constant-cased: `CAFE` gives `CAFE_TOKEN`. Fixed at\n * generation from the output file name, so renaming the binary keeps the variables\n * a published CLI already documents. A composed entry sets one per api alias. */\n envPrefix: string;\n /** The generated instance client. */\n client: Record<string, unknown>;\n /** How that client takes its inputs. Defaults to `'grouped'`, the generated default. */\n argsStyle?: 'grouped' | 'flat';\n configure: (config: Record<string, unknown>) => void;\n /** Security schemes of the API — drives env-var credential resolution. */\n schemes?: CliAuthScheme[];\n env?: Record<string, string | undefined>;\n stdin?: () => string;\n readFile?: (path: string) => string;\n writeFile?: (path: string, data: Uint8Array) => void;\n stdout: (line: string) => void;\n stderr: (line: string) => void;\n};\n\nexport type CliGlobals = {\n serverUrl?: string;\n format?: 'json' | 'ndjson';\n dryRun?: boolean;\n pageAll?: boolean;\n output?: string;\n token?: string;\n json?: string;\n};\n\nexport type CliInvocation =\n | { kind: 'help'; topic?: CliCommand | string }\n | { kind: 'schema'; command: CliCommand }\n | {\n kind: 'run';\n command: CliCommand;\n positionals: Record<string, string>;\n params: Record<string, unknown>;\n globals: CliGlobals;\n }\n | { kind: 'usage-error'; message: string };\n\n/**\n * A hand-written command composed NEXT TO the generated ones: the same data shape plus a\n * `handler`, so it inherits help, parsing, `schema`, and the exit-code contract. This is\n * how behavior that is not in the description (a `login`, a doctor command) joins the\n * binary without the generator ever learning what it does.\n */\nexport type CustomCommand = {\n name: string;\n group?: string;\n summary?: string;\n positionals?: CliCommand['positionals'];\n flags?: CliFlag[];\n /** Returns the process exit code; throwing exits 1 with the standard error JSON. */\n handler: (context: CommandContext) => number | Promise<number>;\n};\n\nexport type CommandContext = {\n positionals: Record<string, string>;\n params: Record<string, unknown>;\n globals: CliGlobals;\n wiring: CliWiring;\n};\n\n/** One API's contribution to a composed binary: its commands behind a namespace, with its\n * OWN wiring (base URL, schemes, credentials). A namespace-less source sits at the root. */\nexport type CommandSource = {\n namespace?: string;\n commands: Array<CliCommand | CustomCommand>;\n /** Absent = inherit the first wired source's — a root `login` shares the binary's identity. */\n wiring?: CliWiring;\n};\n\ntype ResolvedCommand = CliCommand & { handler?: CustomCommand['handler'] };\n\n/** Custom commands as the command shape the parser reads; generated ones pass through. */\nfunction normalizeCommands(commands: Array<CliCommand | CustomCommand>): ResolvedCommand[] {\n return commands.map((command) =>\n 'handler' in command\n ? { method: '', path: '', positionals: [], flags: [], ...command }\n : command\n );\n}\n\n/**\n * The name of a custom command that shadows another command. Rejected at startup: an\n * operator typing an operationId must never silently run something else.\n */\nfunction shadowedCommandName(commands: ResolvedCommand[]): string | undefined {\n const seen = new Map<string, ResolvedCommand[]>();\n for (const command of commands) {\n const key = `${command.group ?? ''}\\u0000${command.name}`;\n seen.set(key, [...(seen.get(key) ?? []), command]);\n }\n for (const clashing of seen.values()) {\n if (clashing.length > 1 && clashing.some((command) => command.handler !== undefined)) {\n return clashing[0].name;\n }\n }\n return undefined;\n}\n\nconst GLOBAL_FLAGS: Record<string, { key: keyof CliGlobals; boolean?: boolean }> = {\n 'server-url': { key: 'serverUrl' },\n format: { key: 'format' },\n 'dry-run': { key: 'dryRun', boolean: true },\n 'page-all': { key: 'pageAll', boolean: true },\n output: { key: 'output' },\n token: { key: 'token' },\n json: { key: 'json' },\n};\n\n/**\n * The name to print in help: the command the CLI was invoked as. A global install resolves\n * `argv[1]` to the bin itself, so its basename is exactly what the user typed. A Windows\n * `.cmd` shim, a `node dist/cafe.cli.js`, and a `tsx client.cli.ts` run all pass the script\n * path instead — printing that would name a command nobody can type, so a script extension\n * and the `.cli` marker come off: `cafe.cli.js` prints `cafe`.\n */\nexport function invokedName(scriptPath: string | undefined, fallback: string): string {\n if (scriptPath === undefined) return fallback;\n const base = scriptPath.replace(/^.*[\\\\/]/, '');\n const withoutExtension = base.replace(/\\.(mjs|cjs|js|mts|cts|ts|cmd|bat|ps1|exe)$/i, '');\n const name = withoutExtension.replace(/\\.cli$/i, '');\n return name === '' ? fallback : name;\n}\n\n/**\n * The shell-typable form of a group name: an OpenAPI tag can contain spaces (\"Some\n * multi-word tag\"), which only resolves if the user quotes it. Commands are addressed by\n * this slug; help still shows the original tag.\n */\nexport function groupSlug(group: string): string {\n return group\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter(Boolean)\n .join('-');\n}\n\n/** A description on ONE line: newlines in an OpenAPI description wreck help alignment. */\nfunction oneLine(text: string): string {\n return text.replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * The parsed argv as one call input, in the style the wired client takes: grouped by layer\n * (the default) or merged into one object.\n */\nfunction callInputs(\n command: CliCommand,\n positionals: Record<string, string>,\n params: Record<string, unknown>,\n body: unknown,\n argsStyle: CliWiring['argsStyle']\n): Record<string, unknown> | undefined {\n const inputs: Record<string, unknown> = {};\n // A command the generator marked `grouped` keeps the namespaced shape even here.\n if (argsStyle === 'flat' && command.argsStyle !== 'grouped') {\n Object.assign(inputs, positionals, params);\n if (body !== undefined) {\n if (command.body?.merged === true) Object.assign(inputs, body as Record<string, unknown>);\n else inputs.body = body;\n }\n } else {\n if (Object.keys(positionals).length > 0) inputs.path = positionals;\n if (Object.keys(params).length > 0) inputs.query = params;\n if (body !== undefined) inputs.body = body;\n }\n return Object.keys(inputs).length > 0 ? inputs : undefined;\n}\n\n/** Resolve argv against the command table. Pure — no I/O, no env. */\nexport function parseInvocation(commands: CliCommand[], argv: string[]): CliInvocation {\n if (argv.length === 0 || argv[0] === '--help') return { kind: 'help' };\n\n if (argv[0] === 'schema') {\n const command = commands.find((candidate) => candidate.name === argv[1]);\n return command\n ? { kind: 'schema', command }\n : { kind: 'usage-error', message: `Unknown command: schema ${argv[1] ?? ''}`.trim() };\n }\n\n const slugs = new Set(commands.filter((c) => c.group).map((c) => groupSlug(c.group as string)));\n // An untagged operation is only ever addressed by its bare name, so when that name is also\n // a group slug the name wins — reading it as the group would leave the command unreachable.\n // A tagged operation in the same position keeps yielding to group help: it is still\n // reachable as `<its group> <name>`.\n const untagged = commands.some((c) => c.group === undefined && c.name === argv[0]);\n let command: CliCommand | undefined;\n let rest: string[];\n if (!untagged && slugs.has(argv[0])) {\n if (argv[1] === '--help' || argv[1] === undefined) return { kind: 'help', topic: argv[0] };\n command = commands.find((c) => c.group && groupSlug(c.group) === argv[0] && c.name === argv[1]);\n if (!command) return { kind: 'usage-error', message: `Unknown command: ${argv[0]} ${argv[1]}` };\n rest = argv.slice(2);\n } else {\n // An ungrouped command, or a bare operationId — knowing the group shouldn't be\n // required when the name alone is unambiguous.\n const named = commands.filter((c) => c.name === argv[0]);\n command =\n named.find((c) => c.group === undefined) ?? (named.length === 1 ? named[0] : undefined);\n if (!command) {\n const ambiguous = named.length > 1;\n return {\n kind: 'usage-error',\n message: ambiguous\n ? `Ambiguous command: ${argv[0]} — prefix it with its group (${named\n .map((c) => groupSlug(c.group as string))\n .join(', ')})`\n : `Unknown command: ${argv[0]}`,\n };\n }\n rest = argv.slice(1);\n }\n if (rest.includes('--help')) return { kind: 'help', topic: command };\n\n const positionals: Record<string, string> = {};\n const params: Record<string, unknown> = {};\n const globals: CliGlobals = {};\n let positionalIndex = 0;\n for (let index = 0; index < rest.length; index++) {\n const token = rest[index];\n if (!token.startsWith('--')) {\n const slot = command.positionals[positionalIndex++];\n if (!slot) return { kind: 'usage-error', message: `Unexpected argument: ${token}` };\n positionals[slot.name] = token;\n continue;\n }\n const equals = token.indexOf('=');\n const flagName = equals === -1 ? token.slice(2) : token.slice(2, equals);\n const inlineValue = equals === -1 ? undefined : token.slice(equals + 1);\n const takeValue = (): string | undefined =>\n inlineValue !== undefined ? inlineValue : rest[++index];\n\n const global = GLOBAL_FLAGS[flagName];\n if (global) {\n if (global.boolean) {\n (globals[global.key] as boolean) = true;\n continue;\n }\n const value = takeValue();\n if (value === undefined) {\n return { kind: 'usage-error', message: `Flag --${flagName} expects a value` };\n }\n if (global.key === 'format' && value !== 'json' && value !== 'ndjson') {\n return { kind: 'usage-error', message: `--format must be one of: json, ndjson` };\n }\n (globals[global.key] as string) = value;\n continue;\n }\n\n const flag = command.flags.find((candidate) => candidate.name === flagName);\n if (!flag) return { kind: 'usage-error', message: `Unknown flag: --${flagName}` };\n if (flag.type === 'boolean') {\n params[flag.param] = true;\n continue;\n }\n const value = takeValue();\n if (value === undefined) {\n return { kind: 'usage-error', message: `Flag --${flagName} expects a value` };\n }\n if (flag.enum && !flag.enum.includes(value)) {\n return {\n kind: 'usage-error',\n message: `--${flagName} must be one of: ${flag.enum.join(', ')}`,\n };\n }\n if (flag.type === 'number') {\n const numeric = Number(value);\n if (Number.isNaN(numeric)) {\n return { kind: 'usage-error', message: `--${flagName} expects a number, got \"${value}\"` };\n }\n params[flag.param] = numeric;\n } else if (flag.type === 'array') {\n const existing = params[flag.param];\n params[flag.param] = Array.isArray(existing) ? [...existing, value] : [value];\n } else {\n params[flag.param] = value;\n }\n }\n\n for (const slot of command.positionals) {\n if (!(slot.name in positionals)) {\n return { kind: 'usage-error', message: `Missing required argument: <${slot.name}>` };\n }\n }\n for (const flag of command.flags) {\n if (flag.required && !(flag.param in params)) {\n return { kind: 'usage-error', message: `Missing required flag: --${flag.name}` };\n }\n }\n if (globals.json !== undefined && !command.body) {\n return { kind: 'usage-error', message: `${command.name} does not accept a request body` };\n }\n if (command.body?.required && globals.json === undefined) {\n return {\n kind: 'usage-error',\n message: `${command.name} requires a request body: pass --json '<json>', --json @file.json, or --json @-`,\n };\n }\n if (globals.pageAll && !command.paginated) {\n return {\n kind: 'usage-error',\n message: `${command.name} is not paginated; --page-all only applies to paginated operations`,\n };\n }\n return { kind: 'run', command, positionals, params, globals };\n}\n\n/** `cafe-api` → `CAFE_API`: the casing of every credential variable this CLI reads. */\nexport function constantCase(value: string): string {\n return value\n .replace(/[^A-Za-z0-9]+/g, '_')\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .toUpperCase();\n}\n\nfunction resolveAuth(wiring: CliWiring, token: string | undefined): Record<string, unknown> {\n const env = wiring.env ?? {};\n const prefix = wiring.envPrefix;\n const auth: Record<string, unknown> = {};\n for (const scheme of wiring.schemes ?? []) {\n if (scheme.kind === 'bearer') {\n const value = token ?? env[`${prefix}_TOKEN`];\n if (value !== undefined) auth.bearer = value;\n } else if (scheme.kind === 'basic') {\n const username = env[`${prefix}_USERNAME`];\n const password = env[`${prefix}_PASSWORD`];\n if (username !== undefined && password !== undefined) auth.basic = { username, password };\n } else {\n const value = env[`${prefix}_API_KEY_${constantCase(scheme.key)}`];\n if (value !== undefined) {\n auth.apiKey = {\n ...(auth.apiKey as Record<string, string> | undefined),\n [scheme.key]: value,\n };\n }\n }\n }\n return auth;\n}\n\n/**\n * One command's complete contract as plain data — what `schema <command>` prints. It has\n * to carry the parameters: 'GET' operations have no body, so without them the output says\n * nothing a caller could act on, and the only alternative is scraping `--help`, which is\n * prose written for humans.\n */\nfunction commandContract(command: CliCommand): Record<string, unknown> {\n return {\n operationId: command.name,\n ...(command.group === undefined ? {} : { group: groupSlug(command.group) }),\n ...(command.summary === undefined ? {} : { summary: oneLine(command.summary) }),\n ...(command.method === '' ? {} : { method: command.method }),\n ...(command.path === '' ? {} : { path: command.path }),\n parameters: {\n path: command.positionals.map((positional) => ({\n name: positional.name,\n type: positional.type ?? 'string',\n required: true,\n ...(positional.description === undefined\n ? {}\n : { description: oneLine(positional.description) }),\n })),\n // `name` is what you type (`--max-total`); `param` is the wire name it becomes.\n query: command.flags.map((flag) => ({\n name: flag.name,\n param: flag.param,\n type: flag.type,\n required: flag.required,\n ...(flag.enum === undefined ? {} : { enum: flag.enum }),\n ...(flag.description === undefined ? {} : { description: oneLine(flag.description) }),\n })),\n },\n ...(command.body === undefined ? {} : { body: command.body }),\n ...(command.unsupportedBody === undefined ? {} : { unsupportedBody: command.unsupportedBody }),\n ...(command.paginated === true ? { paginated: true } : {}),\n ...(command.sse === true ? { sse: true } : {}),\n ...(command.blob === true ? { blob: true } : {}),\n ...(command.schemas ?? {}),\n };\n}\n\nfunction renderHelp(\n commands: CliCommand[],\n name: string,\n schemes: CliAuthScheme[],\n prefix: string,\n topic?: CliCommand | string\n): string[] {\n if (topic !== undefined && typeof topic !== 'string') {\n const command = topic;\n const usage = [\n name,\n ...(command.group ? [groupSlug(command.group)] : []),\n command.name,\n ...command.positionals.map((slot) => `<${slot.name}>`),\n ...(command.flags.length > 0 ? ['[flags]'] : []),\n ...(command.body ? [\"--json '<json>' | @file | @-\"] : []),\n ].join(' ');\n const lines = [`Usage: ${usage}`];\n if (command.summary) lines.push('', command.summary);\n if (command.unsupportedBody !== undefined) {\n lines.push(\n '',\n `This operation takes a ${command.unsupportedBody} body, which the CLI cannot build — call it through the generated client instead.`\n );\n }\n if (command.flags.length > 0) {\n lines.push('', 'Flags:');\n for (const flag of command.flags) {\n const choices = flag.enum ? ` (one of: ${flag.enum.join(', ')})` : '';\n const required = flag.required ? ' [required]' : '';\n lines.push(\n ` --${flag.name} <${flag.type}>${choices}${required} ${oneLine(flag.description ?? '')}`.trimEnd()\n );\n }\n }\n return lines;\n }\n const scope =\n typeof topic === 'string'\n ? commands.filter((c) => c.group && groupSlug(c.group) === topic)\n : commands;\n const lines =\n typeof topic === 'string'\n ? [`Usage: ${name} ${topic} <command> …`, '', 'Commands:']\n : [`Usage: ${name} [group] <command> …`, '', 'Commands:'];\n const seenGroups = new Set<string>();\n const grouped = commands.some((c) => c.group);\n for (const command of scope) {\n if (typeof topic !== 'string' && command.group) {\n const slug = groupSlug(command.group);\n if (seenGroups.has(slug)) continue;\n seenGroups.add(slug);\n // The slug is what you type; the tag is what you recognize.\n const title = slug === command.group ? '' : ` (${command.group})`;\n lines.push(` ${slug} <command>${title}`);\n continue;\n }\n lines.push(\n ` ${[command.group === undefined ? undefined : groupSlug(command.group), command.name]\n .filter(Boolean)\n .join(' ')} ${oneLine(command.summary ?? '')}`.trimEnd()\n );\n }\n // Flags that apply to every command, and the env vars credentials come from: a flag\n // absent from --help may as well not exist — and one this API cannot use should not be\n // listed at all, since the operator would spend the debugging session on their token.\n const kinds = new Set(schemes.map((scheme) => scheme.kind));\n const credentials = [\n ...(kinds.has('bearer') ? [`${prefix}_TOKEN`] : []),\n ...(kinds.has('basic') ? [`${prefix}_USERNAME`, `${prefix}_PASSWORD`] : []),\n ...schemes\n .filter((scheme) => scheme.kind === 'apiKey')\n .map((scheme) => `${prefix}_API_KEY_${constantCase(scheme.key)}`),\n ];\n lines.push(\n '',\n 'Global flags:',\n ' --server-url <url> Override the baked server URL',\n ' --format <json|ndjson> Output format',\n ' --dry-run Print the prepared request without sending it',\n ' --page-all Follow pagination, one JSON page per line',\n ' --output <path> Write the response body to a file (required for binary)',\n ...(kinds.has('bearer') ? [' --token <token> Bearer token'] : []),\n ` --json <json|@file|@-> Request body`,\n ...(credentials.length > 0 ? ['', 'Environment:', ` ${credentials.join(', ')}`] : []),\n '',\n `Run ${name} ${grouped ? '<group> <command>' : '<command>'} --help for command details; ${name} schema <command> prints its schemas.`\n );\n return lines;\n}\n\nfunction loadBody(source: string, wiring: CliWiring): unknown {\n const raw =\n source === '@-'\n ? (wiring.stdin ?? (() => ''))()\n : source.startsWith('@')\n ? (wiring.readFile ?? (() => ''))(source.slice(1))\n : source;\n return JSON.parse(raw);\n}\n\n/** Replace header values containing a known credential with `***`. */\nfunction redactHeaders(headers: Record<string, string>, secrets: string[]): Record<string, string> {\n const redacted: Record<string, string> = {};\n for (const [name, value] of Object.entries(headers)) {\n redacted[name] = secrets.some((secret) => secret !== '' && value.includes(secret))\n ? '***'\n : value;\n }\n return redacted;\n}\n\n/** Parse argv, resolve env auth, dispatch through the client, print, return the exit code. */\nexport async function runCli(\n commands: Array<CliCommand | CustomCommand>,\n wiring: CliWiring,\n argv: string[]\n): Promise<number>;\n/** The composed form: one binary over several sources, each namespaced with its own wiring. */\nexport async function runCli(sources: CommandSource[], argv: string[]): Promise<number>;\nexport async function runCli(\n commandsOrSources: Array<CliCommand | CustomCommand> | CommandSource[],\n wiringOrArgv: CliWiring | string[],\n argv?: string[]\n): Promise<number> {\n if (Array.isArray(wiringOrArgv)) {\n return runSources(commandsOrSources as CommandSource[], wiringOrArgv);\n }\n return runSingle(\n commandsOrSources as Array<CliCommand | CustomCommand>,\n wiringOrArgv,\n argv ?? []\n );\n}\n\n/** Route the first token to its source; the namespace-less source owns the root. */\nasync function runSources(sources: CommandSource[], argv: string[]): Promise<number> {\n // A source without wiring inherits the first wired one, so the documented root-source\n // shape `{ commands: [login] }` works: the login shares the composed binary's identity.\n const inherited = sources.find((source) => source.wiring !== undefined)?.wiring;\n const wiringOf = (source: CommandSource): CliWiring => source.wiring ?? (inherited as CliWiring);\n // Top-level output goes through the first source: with a root source that is the one\n // carrying the shared commands, otherwise the first API listed.\n const top = wiringOf(sources[0]);\n const fail = (code: number, message: string): number => {\n top.stderr(JSON.stringify({ error: { code, message } }));\n return code;\n };\n const namespaced = sources.filter(\n (source): source is CommandSource & { namespace: string } => source.namespace !== undefined\n );\n const root = sources.find((source) => source.namespace === undefined);\n if (root !== undefined) {\n const clash = root.commands.find((command) =>\n namespaced.some((source) => source.namespace === command.name)\n );\n if (clash !== undefined) {\n return fail(\n 4,\n `Root command \"${clash.name}\" collides with the \"${clash.name}\" namespace — rename one of them.`\n );\n }\n }\n if (argv.length === 0 || argv[0] === '--help') {\n for (const line of renderComposedHelp(sources, top.name)) top.stdout(line);\n return 0;\n }\n const source = namespaced.find((candidate) => candidate.namespace === argv[0]);\n if (source !== undefined) return runSingle(source.commands, wiringOf(source), argv.slice(1));\n const rootTakes =\n root !== undefined &&\n (argv[0] === 'schema' ||\n root.commands.some(\n (command) =>\n command.name === argv[0] ||\n (command.group !== undefined && groupSlug(command.group) === argv[0])\n ));\n if (rootTakes)\n return runSingle((root as CommandSource).commands, wiringOf(root as CommandSource), argv);\n return fail(\n 4,\n `Unknown command: ${argv[0]} — expected an API namespace (${namespaced\n .map((candidate) => candidate.namespace)\n .join(', ')})${root !== undefined ? ' or a root command' : ''}`\n );\n}\n\n/** The composed top-level help: namespaces, root commands, and how to descend. */\nfunction renderComposedHelp(sources: CommandSource[], name: string): string[] {\n const lines = [`Usage: ${name} <api> <command> …`, '', 'APIs:'];\n for (const source of sources) {\n if (source.namespace !== undefined) lines.push(` ${source.namespace}`);\n }\n const root = sources.find((source) => source.namespace === undefined);\n if (root !== undefined && root.commands.length > 0) {\n lines.push('', 'Commands:');\n for (const command of root.commands) {\n lines.push(` ${command.name} ${oneLine(command.summary ?? '')}`.trimEnd());\n }\n }\n lines.push('', `Run ${name} <api> --help for that API's commands.`);\n return lines;\n}\n\nasync function runSingle(\n rawCommands: Array<CliCommand | CustomCommand>,\n wiring: CliWiring,\n argv: string[]\n): Promise<number> {\n const { stdout, stderr } = wiring;\n const commands = normalizeCommands(rawCommands);\n const shadowed = shadowedCommandName(commands);\n if (shadowed !== undefined) {\n stderr(\n JSON.stringify({\n error: {\n code: 4,\n message: `Custom command \"${shadowed}\" collides with another command of the same name — rename it.`,\n },\n })\n );\n return 4;\n }\n const fail = (code: number, error: Record<string, unknown>): number => {\n stderr(JSON.stringify({ error: { code, ...error } }));\n return code;\n };\n\n const invocation = parseInvocation(commands, argv);\n if (invocation.kind === 'usage-error') return fail(4, { message: invocation.message });\n if (invocation.kind === 'help') {\n for (const line of renderHelp(\n commands,\n wiring.name,\n wiring.schemes ?? [],\n wiring.envPrefix,\n invocation.topic\n ))\n stdout(line);\n return 0;\n }\n if (invocation.kind === 'schema') {\n stdout(JSON.stringify(commandContract(invocation.command), null, 2));\n return 0;\n }\n\n const { command, positionals, params, globals } = invocation;\n // A credential the user passed explicitly must never be dropped in silence: without a\n // bearer scheme the request would go out unauthenticated and come back 401, which reads\n // as \"my token is wrong\" rather than \"that flag does nothing here\".\n const schemes = wiring.schemes ?? [];\n if (globals.token !== undefined && !schemes.some((scheme) => scheme.kind === 'bearer')) {\n const declared = schemes.map((scheme) => `${scheme.key} (${scheme.kind})`).join(', ');\n return fail(4, {\n message:\n `--token is a bearer credential, and this API declares no bearer scheme. ` +\n (declared === '' ? 'It declares no security schemes at all.' : `It accepts: ${declared}.`),\n });\n }\n if (command.blob && globals.output === undefined) {\n return fail(4, {\n message: `${command.name} downloads a file: pass --output <path>`,\n operationId: command.name,\n });\n }\n let body: unknown;\n if (globals.json !== undefined) {\n try {\n body = loadBody(globals.json, wiring);\n } catch (error) {\n return fail(4, {\n message: `Invalid --json body: ${(error as Error).message}`,\n operationId: command.name,\n });\n }\n }\n\n const auth = resolveAuth(wiring, globals.token);\n if (Object.keys(auth).length > 0) wiring.configure({ auth });\n if (globals.serverUrl !== undefined) wiring.configure({ serverUrl: globals.serverUrl });\n\n // Redaction matches these against header VALUES — so basic auth must contribute the\n // form the wire actually carries (`Basic ${base64(user:pass)}`), not just the raw\n // password, which never appears in the encoded header.\n const basic = auth.basic as { username: string; password: string } | undefined;\n const secrets = [\n ...(typeof auth.bearer === 'string' ? [auth.bearer] : []),\n ...(basic ? [basic.password, btoa(`${basic.username}:${basic.password}`)] : []),\n ...Object.values((auth.apiKey as Record<string, string> | undefined) ?? {}),\n ];\n let captured: Record<string, unknown> | undefined;\n if (globals.dryRun) {\n wiring.configure({\n fetch: async (\n url: string,\n init: { method?: string; headers?: Record<string, string>; body?: unknown }\n ) => {\n captured = {\n url,\n method: init.method,\n headers: redactHeaders(init.headers ?? {}, secrets),\n ...(init.body !== undefined && init.body !== null ? { body: String(init.body) } : {}),\n };\n return new Response('{}', { status: 200, headers: { 'content-type': 'application/json' } });\n },\n });\n }\n\n const argument = callInputs(command, positionals, params, body, wiring.argsStyle);\n\n // The client's methods are typed per-operation; the dispatcher only needs \"callable\n // by name\", so one localized widening here keeps the emitted wiring cast-free.\n const methods = wiring.client as Record<string, unknown>;\n try {\n const resolved = command as ResolvedCommand;\n if (resolved.handler !== undefined) {\n return await resolved.handler({ positionals, params, globals, wiring });\n }\n if (globals.pageAll && !globals.dryRun) {\n const paginated = methods[command.name] as {\n pages: (variables?: unknown) => AsyncIterable<unknown>;\n };\n for await (const page of paginated.pages(argument)) stdout(JSON.stringify(page));\n return 0;\n }\n const method = methods[command.name] as (variables?: unknown) => Promise<unknown>;\n const result = await method(argument);\n if (globals.dryRun) {\n // An SSE method returns a lazy stream — drain the stubbed response so its fetch\n // actually runs and captures the request.\n if (command.sse) for await (const _event of result as AsyncIterable<unknown>);\n stdout(JSON.stringify(captured, null, 2));\n return 0;\n }\n if (command.sse) {\n for await (const event of result as AsyncIterable<unknown>) stdout(JSON.stringify(event));\n return 0;\n }\n if (command.blob) {\n const bytes = new Uint8Array(await (result as Blob).arrayBuffer());\n (wiring.writeFile ?? (() => {}))(globals.output as string, bytes);\n stdout(JSON.stringify({ saved: globals.output, bytes: bytes.length }));\n return 0;\n }\n if (result !== undefined && result !== null) stdout(JSON.stringify(result, null, 2));\n return 0;\n } catch (error) {\n const thrown = error as Error & { status?: number; issues?: unknown };\n const detail = {\n message: thrown.message,\n operationId: command.name,\n ...(thrown.status !== undefined ? { status: thrown.status } : {}),\n ...(thrown.issues !== undefined ? { issues: thrown.issues } : {}),\n };\n if (thrown.name === 'ZodValidationError') return fail(3, detail);\n if (thrown.name === 'ApiError' && (thrown.status === 401 || thrown.status === 403)) {\n return fail(2, detail);\n }\n return fail(1, detail);\n }\n}\n";
15
+ };
16
+ /** Inline-embed variants: imports dropped, `export` stripped outside the kept surface. */
17
+ export declare const RUNTIME_SOURCES_STRIPPED: {
18
+ readonly 'types.ts': "/**\n * The public type surface of the client runtime — `@redocly/client-generator`'s\n * app-facing runtime module. Pure types, no runtime code (excluded from coverage).\n * The generator emits `OPERATIONS` literals typed\n * `satisfies Record<string, OperationDescriptor>` against this module, so an\n * incompatible runtime/generated pair fails the consumer's build (the semver skew guard).\n */\n\n/** How one operation parameter is sent: its location plus OpenAPI query-serialization hints. */\nexport type ParamSpec = {\n name: string;\n in: 'path' | 'query' | 'header' | 'cookie';\n style?: 'form' | 'spaceDelimited' | 'pipeDelimited' | 'deepObject';\n explode?: boolean;\n allowReserved?: boolean;\n};\n\n/** One security scheme, denormalized onto the operation (`scheme` names the spec's scheme). */\nexport type SecuritySpec =\n | { scheme: string; kind: 'bearer' | 'basic' }\n | { scheme: string; kind: 'apiKey'; name: string; in: 'header' | 'query' | 'cookie' };\n\n/**\n * How to auto-iterate a paginated operation (drives its `.pages()`/`.items()` members).\n * `nextCursor` and `items` are RFC 6901 JSON pointers into the page (response) value.\n */\nexport type PaginationSpec =\n | {\n style: 'cursor';\n /** The query param the iterator advances with the response's cursor. */\n param: string;\n /** Optional page-size query param (recorded for tooling; never set by the runtime). */\n limitParam?: string;\n /** Pointer to the next cursor in the page. */\n nextCursor: string;\n /** Optional pointer to a boolean \"more pages\" flag — `false` stops iteration. */\n hasMore?: string;\n /** Pointer to the page's item array. */\n items: string;\n }\n | {\n style: 'offset' | 'page';\n /** The numeric query param the iterator advances. */\n param: string;\n /** Optional page-size query param (recorded for tooling; never set by the runtime). */\n limitParam?: string;\n /** Pointer to the page's item array. */\n items: string;\n }\n | {\n /** RFC 8288: follow the response's `Link` header `rel=\"next\"`; stop when absent. */\n style: 'link';\n /** Optional page-size query param (recorded for tooling; never set by the runtime). */\n limitParam?: string;\n /** Pointer to the page's item array. */\n items: string;\n };\n\n/** The frozen data contract between generated code and the runtime: one operation's wire shape. */\nexport type OperationDescriptor = {\n id: string;\n method: string;\n path: string;\n tags?: readonly string[];\n params?: readonly ParamSpec[];\n /** `multipart: true` marks a typed object body serialized to FormData by the runtime. */\n body?: { contentType: string; multipart?: boolean };\n /** Defaults to `'json'` (content-type negotiation on parse). */\n responseKind?: 'json' | 'text' | 'blob' | 'void' | 'sse';\n sseDataKind?: 'json' | 'text';\n /** OR-alternatives, each an AND-set: the runtime applies the first fully-configured one. */\n security?: readonly (readonly SecuritySpec[])[];\n pagination?: PaginationSpec;\n /**\n * `'grouped'` marks an operation that takes its inputs namespaced by layer even on a\n * `argsStyle: 'flat'` client — the generator sets it where a merged call could not carry\n * one name for two layers, and the operation's own input type says the same.\n */\n argsStyle?: 'grouped';\n /**\n * Declared success-response headers for throw-mode `{ envelope: true }`.\n * `name` is the lowercased wire name; `key` is the camelCase envelope property.\n */\n responseHeaders?: readonly ResponseHeaderSpec[];\n};\n\n/** One declared response header the runtime coerces into the envelope `headers` object. */\nexport type ResponseHeaderSpec = {\n name: string;\n key: string;\n type: 'string' | 'number' | 'boolean';\n};\n\n/** A query value: scalars, arrays of scalars, or objects (serialized as deepObject brackets). */\nexport type QueryValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | Array<string | number | boolean | null | undefined>\n | Record<string, unknown>;\n\n/** A credential: a literal, or a (possibly async) function resolved per request (refresh flows). */\nexport type TokenProvider = string | (() => string | Promise<string>);\n\n/** Per-instance credentials, keyed by the scheme kinds the runtime can inject. */\nexport type AuthCredentials = {\n bearer?: TokenProvider;\n basic?: { username: string; password: string };\n apiKey?: Record<string, TokenProvider>;\n};\n\n/** Backoff shape: 'fixed' = constant delay; 'exponential' = doubling per attempt. */\nexport type RetryStrategy = 'fixed' | 'exponential';\n\n/**\n * The operation's identity, exposed to middleware for targeting (`ctx.operation`).\n * Generated clients instantiate the type parameters with the spec's literal unions\n * (`OperationId`/`OperationPath`/`OperationTag`) so a misspelled operation id in a\n * middleware comparison fails to compile; the string defaults keep every\n * spec-independent consumer (`runtime-contract.ts`, the runtime internals) working\n * with the base shape. `tags` stays mutable (`Tag[]`) so setup-contract types\n * (byte-locked to generated output) remain assignable through middleware callbacks.\n */\nexport type OperationContext<\n Id extends string = string,\n Path extends string = string,\n Tag extends string = string,\n> = { id: Id; path: Path; tags: Tag[] };\n\n/** The mutable request context threaded through the middleware chain. */\nexport type RequestContext<Op extends OperationContext = OperationContext> = {\n url: string;\n method: string;\n headers: Record<string, string>;\n body?: unknown;\n operation: Op;\n};\n\n/** The failed attempt handed to a custom `retryOn`: exactly one of `response`/`error` is set. */\nexport type RetryContext<Op extends OperationContext = OperationContext> = {\n attempt: number;\n request: RequestContext<Op>;\n response?: Response;\n error?: unknown;\n};\n\n/** Opt-in retry policy; a per-call override merges field-by-field over the config policy. */\nexport type RetryConfig<Op extends OperationContext = OperationContext> = {\n retries?: number;\n retryDelay?: number;\n retryStrategy?: RetryStrategy;\n jitter?: boolean;\n retryOn?: (ctx: RetryContext<Op>) => boolean | Promise<boolean>;\n};\n\n/**\n * Structural stand-in for the runtime's ApiError so this module stays import-free\n * (pure types); the real `ApiError` class is assignable to it.\n */\nexport type ApiErrorLike = globalThis.Error & {\n url: string;\n status: number;\n statusText: string;\n body: unknown;\n};\n\n/** One interceptor: any subset of the three hooks. */\nexport type Middleware<Op extends OperationContext = OperationContext> = {\n onRequest?: (ctx: RequestContext<Op>) => void | Promise<void>;\n onResponse?: (\n response: Response,\n ctx: RequestContext<Op>\n ) => Response | void | Promise<Response | void>;\n /** Throw mode only: may map/replace the error. */\n // `globalThis.Error` so a spec schema named `Error` cannot shadow it in inline mode.\n onError?: (\n error: ApiErrorLike,\n ctx: RequestContext<Op>\n ) => globalThis.Error | Promise<globalThis.Error>;\n};\n\n/** Client configuration: transport, defaults, retry policy, middleware, and credentials. */\nexport type ClientConfig<Op extends OperationContext = OperationContext> = {\n serverUrl?: string;\n fetch?: typeof fetch;\n headers?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n retry?: RetryConfig<Op>;\n /** Milliseconds before a request attempt aborts (covers the body read too; each retry\n * attempt gets a fresh budget). Per-call `timeout` overrides it, `0` disables it.\n * SSE streams are long-lived by design and never inherit this value. */\n timeout?: number;\n /** Send an `Idempotency-Key` header on POST/PATCH (one stable key per logical call,\n * reused across retry attempts) — which also makes those retries safe under the\n * default retry policy. `true` generates a UUID per call; a function supplies the key. */\n idempotencyKey?: boolean | (() => string);\n /** Identifies this client to the API via an `X-Redocly-Client` header (the generator\n * bakes a default). Sent only OUTSIDE browsers — a custom header would force a CORS\n * preflight. Override with your own value, or `false` to disable. */\n clientHeader?: string | false;\n middleware?: Middleware<Op>[];\n auth?: AuthCredentials;\n /** Fixed at generate time by the generator (`'throw'` when omitted); `configure()` ignores it. */\n errorMode?: 'throw' | 'result';\n /**\n * How each call spells its inputs: `'grouped'` (the default) namespaces them by layer —\n * `{ path, query, headers, cookies, body }` — and `'flat'` takes one merged object.\n * Fixed at generate time, like `errorMode`, because it shapes the static types.\n */\n argsStyle?: 'grouped' | 'flat';\n onRequest?: Middleware<Op>['onRequest'];\n onResponse?: Middleware<Op>['onResponse'];\n onError?: Middleware<Op>['onError'];\n};\n\n/** Response readers for the per-call `parseAs` override. */\nexport type ParseAs = 'auto' | 'json' | 'text' | 'blob' | 'arrayBuffer' | 'formData' | 'stream';\n\n/** Per-call options: standard `RequestInit` plus a retry override, a timeout override\n * (`0` disables the config default), and a forced reader. */\nexport type RequestOptions = RequestInit & {\n retry?: RetryConfig;\n timeout?: number;\n /** Per-call idempotency key: a literal key, `true` to generate one, `false` to skip. */\n idempotencyKey?: string | boolean | (() => string);\n parseAs?: ParseAs;\n /**\n * Throw mode only: return `{ data, headers, response }` instead of the parsed body;\n * ignored in result mode. The explicit `| undefined` keeps the wrappers' emitted\n * `envelope: undefined` strip legal under `exactOptionalPropertyTypes`.\n */\n envelope?: boolean | undefined;\n};\n\n/** Throw-mode success envelope when `RequestOptions.envelope` is `true`. */\nexport type Envelope<TData, THeaders = Record<string, never>> = {\n data: TData;\n headers: THeaders;\n response: Response;\n};\n\n/** Per-call options for an SSE stream; reconnect defaults to true. */\nexport type SseOptions = RequestInit & { reconnect?: boolean; reconnectDelay?: number };\n\n/** A single decoded Server-Sent Event with its payload typed from the spec. */\nexport type ServerSentEvent<T> = { event?: string; data: T; id?: string; retry?: number };\n\n/** Result-mode return shape: exactly one of `data`/`error` is set. */\nexport type Result<TData, TError> =\n | { data: TData; error: undefined; response: Response }\n | { data: undefined; error: TError; response: Response };\n\n/**\n * The generated `Ops` type's shape: per-operation args/result, plus `kind: 'sse'` for\n * streams and, for paginated operations, `item` (the page's element type) and — on\n * result-mode clients only — `page` (the RAW page type `.pages()` yields, since\n * iteration unwraps the `Result` envelope the one-shot `result` carries).\n */\nexport type OpsShape = Record<\n string,\n {\n args: object;\n result: unknown;\n kind?: 'sse';\n item?: unknown;\n page?: unknown;\n /** Declared success-response headers for `{ envelope: true }` (camelCase keys). */\n headers?: object;\n /** Result-mode entries ignore the throw-only `envelope` option. */\n mode?: 'result';\n }\n>;\n\n/** The always-present client members (assigned after the operation loop — they win collisions). */\nexport type ClientCore<Op extends OperationContext = OperationContext> = {\n /** Merge into the config; note `middleware` REPLACES the chain (use `use()` to compose). */\n configure(config: ClientConfig<Op>): void;\n /** Append interceptors (composes with baked/publisher middleware). */\n use(...middleware: Middleware<Op>[]): void;\n auth: {\n bearer(token: TokenProvider): void;\n basic(username: string, password: string): void;\n apiKey(scheme: string, value: TokenProvider): void;\n };\n};\n\n/**\n * The standard TypeScript optionality probe: `{}` has no required members, so\n * `{} extends A` is true exactly when every member of `A` is optional.\n */\n// oxlint-disable-next-line typescript/no-empty-object-type\ntype NoRequiredKeys<A> = {} extends A ? true : false;\n\n/**\n * The page type `.pages()` yields: the RAW page declared by `page` (the generator\n * writes it only on result-mode paginated entries, whose `result` is the envelope),\n * or the method's own `result` (throw mode — already the raw page).\n */\ntype PageOf<Entry extends OpsShape[string]> = Entry extends { page: unknown }\n ? Entry['page']\n : Entry['result'];\n\n/**\n * The auto-pagination members intersected onto a paginated method — present exactly when\n * the Ops entry declares `item` (the generator writes it only for paginated operations).\n * Args optionality mirrors the method's own; `unknown` otherwise (identity under `&`).\n * Iteration is error-mode-agnostic: `.pages()`/`.items()` yield raw pages/items, and a\n * failed page aborts iteration by throwing `ApiError`, even on result-mode clients; the\n * `onError` middleware hook (throw-mode-only) is not invoked.\n */\ntype Paginated<Entry extends OpsShape[string]> = 'item' extends keyof Entry\n ? NoRequiredKeys<Entry['args']> extends true\n ? {\n pages(args?: Entry['args'], init?: RequestOptions): AsyncGenerator<PageOf<Entry>>;\n items(args?: Entry['args'], init?: RequestOptions): AsyncGenerator<Entry['item']>;\n }\n : {\n pages(args: Entry['args'], init?: RequestOptions): AsyncGenerator<PageOf<Entry>>;\n items(args: Entry['args'], init?: RequestOptions): AsyncGenerator<Entry['item']>;\n }\n : unknown;\n\n/**\n * The stable identity every client method carries: the SPEC operationId (also set as\n * `fn.name`, but `operationId` is the explicit, minification-proof form) — a robust\n * cache key for consumer wrappers (react-query keys and the like).\n */\nexport type OperationMethodIdentity = { readonly operationId: string };\n\n/** Declared response-header bag for an Ops entry; empty object when none are declared. */\ntype HeadersOf<Entry extends OpsShape[string]> = 'headers' extends keyof Entry\n ? NonNullable<Entry['headers']>\n : Record<string, never>;\n\n/**\n * Return type of a throw-mode call: the body by default, `Envelope<…>` for a literal\n * `envelope: true`, their union when the flag is a widened `boolean`. Exact\n * `RequestOptions` stays the body — pre-envelope package-mode flat sugar typed every\n * `init` parameter as `RequestOptions`, and widening that would break upgrades without\n * a regenerate. The `keyof` presence gate keeps `{ headers }` / `{ signal }` as the body\n * (`TInit['envelope']` through `TInit & RequestOptions` would otherwise be\n * `boolean | undefined`).\n */\nexport type EnvelopeResult<\n TData,\n THeaders,\n TInit extends RequestOptions | undefined,\n> = TInit extends undefined\n ? TData\n : RequestOptions extends TInit\n ? TInit extends RequestOptions\n ? TData\n : EnvelopeResultForKnownInit<TData, THeaders, TInit>\n : EnvelopeResultForKnownInit<TData, THeaders, TInit>;\n\ntype EnvelopeResultForKnownInit<TData, THeaders, TInit> = 'envelope' extends keyof TInit\n ? [TInit['envelope' & keyof TInit]] extends [true]\n ? Envelope<TData, THeaders>\n : [TInit['envelope' & keyof TInit]] extends [false | undefined]\n ? TData\n : TData | Envelope<TData, THeaders>\n : TData;\n\n/** A one-shot method whose return shape never varies with per-call options. */\ntype BodyMethod<Entry extends OpsShape[string]> =\n NoRequiredKeys<Entry['args']> extends true\n ? (args?: Entry['args'], init?: RequestOptions) => Promise<Entry['result']>\n : (args: Entry['args'], init?: RequestOptions) => Promise<Entry['result']>;\n\n/**\n * One-shot (non-SSE) method: default returns the body; `{ envelope: true }` returns\n * `{ data, headers, response }` with typed declared headers.\n */\ntype ThrowMethod<Entry extends OpsShape[string]> =\n NoRequiredKeys<Entry['args']> extends true\n ? <Init extends RequestOptions | undefined = undefined>(\n args?: Entry['args'],\n init?: Init\n ) => Promise<EnvelopeResult<Entry['result'], HeadersOf<Entry>, Init>>\n : <Init extends RequestOptions | undefined = undefined>(\n args: Entry['args'],\n init?: Init\n ) => Promise<EnvelopeResult<Entry['result'], HeadersOf<Entry>, Init>>;\n\n/** The typed instance client: one bound method per operation plus the core members. */\nexport type Client<Ops extends OpsShape, Op extends OperationContext = OperationContext> = {\n [K in keyof Ops]: Ops[K] extends { kind: 'sse' }\n ? (NoRequiredKeys<Ops[K]['args']> extends true\n ? (\n args?: Ops[K]['args'],\n init?: SseOptions\n ) => AsyncGenerator<ServerSentEvent<Ops[K]['result']>>\n : (\n args: Ops[K]['args'],\n init?: SseOptions\n ) => AsyncGenerator<ServerSentEvent<Ops[K]['result']>>) &\n OperationMethodIdentity\n : (Ops[K] extends { mode: 'result' } ? BodyMethod<Ops[K]> : ThrowMethod<Ops[K]>) &\n OperationMethodIdentity &\n Paginated<Ops[K]>;\n} & ClientCore<Op>;";
19
+ readonly 'errors.ts': "/** The error thrown (throw mode) for a non-2xx response, carrying the decoded error body. */\nexport class ApiError extends Error {\n public readonly url: string;\n public readonly status: number;\n public readonly statusText: string;\n public readonly body: unknown;\n constructor(url: string, status: number, statusText: string, body: unknown) {\n super(`Request failed with status ${status}`);\n this.name = 'ApiError';\n this.url = url;\n this.status = status;\n this.statusText = statusText;\n this.body = body;\n }\n}\n\n/** The error thrown when a request attempt exceeds the configured `timeout` — carries\n * the context a log line needs (which operation, what budget, which attempt). */\nexport class TimeoutError extends Error {\n public readonly operationId: string;\n public readonly timeout: number;\n public readonly attempt: number;\n constructor(operationId: string, timeout: number, attempt: number) {\n super(`Request \"${operationId}\" timed out after ${timeout} ms (attempt ${attempt})`);\n this.name = 'TimeoutError';\n this.operationId = operationId;\n this.timeout = timeout;\n this.attempt = attempt;\n }\n}\n\n/** The error to throw for an aborted request: the caller's abort reason when it is an Error. */\n// `globalThis.Error` (not bare `Error`) so a spec schema named `Error` cannot shadow it\n// when this module is embedded alongside generated types (inline mode).\nfunction abortError(signal: AbortSignal): globalThis.Error {\n const reason = (signal as { reason?: unknown }).reason;\n if (reason instanceof Error) return reason;\n return new DOMException('The operation was aborted.', 'AbortError');\n}";
20
+ readonly 'url.ts': "/**\n * The RESOLVED OpenAPI serialization spec for one query parameter — callers apply the\n * OpenAPI defaults (`style: 'form'`, `explode: true`) before building one.\n */\ntype QueryStyle = {\n style: NonNullable<ParamSpec['style']>;\n explode: boolean;\n allowReserved?: boolean;\n};\n\n/**\n * Encode everything except the RFC-3986 reserved set, for `allowReserved: true` params —\n * `filter=a/b` survives instead of `filter=a%2Fb`.\n */\nfunction encodeReserved(value: string): string {\n return encodeURIComponent(value).replace(\n /%(3A|2F|3F|23|5B|5D|40|21|24|26|27|28|29|2A|2B|2C|3B|3D)/g,\n (match) => decodeURIComponent(match)\n );\n}\n\n/** Substitute `{name}` template segments with encoded values; a missing value is a caller bug. */\nfunction substitutePath(template: string, values: Record<string, unknown>): string {\n return template.replace(/\\{([^{}]+)\\}/g, (_match, name: string) => {\n const value = values[name];\n if (value === undefined) throw new Error(`Missing path parameter \"${name}\"`);\n return encodeURIComponent(String(value));\n });\n}\n\n/**\n * Build the request URL: `serverUrl` (trailing slash trimmed) + path + serialized query.\n * Query parameters honor their OpenAPI `style`/`explode`/`allowReserved` (from `styles`);\n * without a spec, arrays repeat the key (`form`+`explode`), objects serialize as\n * `deepObject` brackets, and `null`/`undefined` entries are skipped.\n */\nfunction buildUrl(\n serverUrl: string,\n path: string,\n query?: Record<string, QueryValue>,\n styles?: Record<string, QueryStyle>\n): string {\n // Trim trailing slashes with a scan, not `/\\/+$/` — an anchored `+` regex is\n // quadratic on adversarial many-slash input (the server URL is caller data).\n let end = serverUrl.length;\n while (end > 0 && serverUrl.charCodeAt(end - 1) === 47 /* '/' */) end--;\n const url = serverUrl.slice(0, end) + path;\n if (!query) return url;\n const params = new URLSearchParams();\n const raw: string[] = [];\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n const spec = styles?.[key];\n if (!spec) {\n if (Array.isArray(value)) {\n for (const v of value) {\n if (v !== undefined && v !== null) params.append(key, String(v));\n }\n } else if (Object(value) === value) {\n // Object-valued query params use `deepObject` style: key[subKey]=subValue.\n for (const [subKey, subValue] of Object.entries(value)) {\n if (subValue !== undefined && subValue !== null) {\n params.append(`${key}[${subKey}]`, String(subValue));\n }\n }\n } else {\n params.append(key, String(value));\n }\n continue;\n }\n if (Array.isArray(value)) {\n const items = value.filter((v) => v !== undefined && v !== null).map(String);\n if (spec.style === 'form' && spec.explode) {\n for (const v of items) {\n if (spec.allowReserved) raw.push(`${key}=${encodeReserved(v)}`);\n else params.append(key, v);\n }\n } else {\n // Delimited styles put the LITERAL delimiter on the wire; only the\n // values are encoded. `%20` (not `+`) is the literal space delimiter.\n const delim =\n spec.style === 'pipeDelimited' ? '|' : spec.style === 'spaceDelimited' ? '%20' : ',';\n const enc = spec.allowReserved ? encodeReserved : encodeURIComponent;\n raw.push(`${encodeURIComponent(key)}=${items.map(enc).join(delim)}`);\n }\n } else if (Object(value) === value) {\n // `deepObject` (and any object spec, for now): key[subKey]=subValue.\n for (const [subKey, subValue] of Object.entries(value)) {\n if (subValue !== undefined && subValue !== null) {\n if (spec.allowReserved) raw.push(`${key}[${subKey}]=${encodeReserved(String(subValue))}`);\n else params.append(`${key}[${subKey}]`, String(subValue));\n }\n }\n } else if (spec.allowReserved) {\n raw.push(`${key}=${encodeReserved(String(value))}`);\n } else {\n params.append(key, String(value));\n }\n }\n const qs = [params.toString(), ...raw].filter(Boolean).join('&');\n return qs ? `${url}?${qs}` : url;\n}";
21
+ readonly 'parse.ts': "/**\n * Read the response body per `kind`. `'auto'` negotiates from the content type\n * (JSON, then `text/*`, then Blob); `204` responses read nothing. A `'void'`\n * operation (no declared 2xx content) still returns a JSON body the server\n * actually sends: the static type stays `void`, but silently dropping real data\n * behind a spec gap is the worse failure — consumers can reach it with a cast\n * while the API description catches up.\n */\nasync function parse(response: Response, kind: ParseAs | 'void'): Promise<unknown> {\n if (kind === 'void') {\n if (response.status === 204 || response.status === 205 || response.status === 304) {\n return undefined;\n }\n const contentType = (response.headers.get('content-type') ?? '').toLowerCase();\n if (!contentType.includes('json')) return undefined;\n // Best-effort: an empty or malformed body on an undeclared response stays undefined.\n const text = await response.text().catch(() => '');\n if (text === '') return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n }\n if (response.status === 204) return undefined;\n if (kind === 'stream') return response.body;\n if (kind === 'blob') return response.blob();\n if (kind === 'arrayBuffer') return response.arrayBuffer();\n if (kind === 'formData') return response.formData();\n if (kind === 'text') return response.text();\n if (kind === 'json') return response.json();\n // 'auto' — negotiate from the response's content type (case-insensitively:\n // `Text/Plain` and `application/JSON` are valid per RFC 9110).\n const contentType = (response.headers.get('content-type') ?? '').toLowerCase();\n if (contentType.includes('json')) return response.json();\n if (contentType.startsWith('text/')) return response.text();\n // An untyped body reads as a Blob — but an EMPTY one resolves to undefined: a 2xx\n // with `Content-Length: 0` must not yield a truthy `new Blob([])` that silently\n // defeats every `!data` guard downstream.\n const blob = await response.blob();\n return blob.size > 0 ? blob : undefined;\n}\n\n/** Best-effort decode of a non-2xx body (JSON when declared, else text; undefined on failure). */\nasync function readError(response: Response): Promise<unknown> {\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.toLowerCase().includes('json')) {\n return response.json().catch(() => undefined);\n }\n return response.text().catch(() => undefined);\n}";
22
+ readonly 'retry.ts': "const IDEMPOTENT_METHODS = new Set(['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS']);\nconst TRANSIENT_STATUS = new Set([408, 429, 500, 502, 503, 504]);\n\n/**\n * The default retry predicate: idempotent methods — or any request carrying an\n * `Idempotency-Key` header, which makes re-sending safe — on a transport error or a\n * transient status. A custom `retryOn` fully replaces this (no method check kept).\n */\nexport function defaultRetryOn(ctx: RetryContext): boolean {\n const safeToResend =\n IDEMPOTENT_METHODS.has(ctx.request.method.toUpperCase()) ||\n 'Idempotency-Key' in ctx.request.headers ||\n 'idempotency-key' in ctx.request.headers;\n if (!safeToResend) return false;\n return ctx.response === undefined || TRANSIENT_STATUS.has(ctx.response.status);\n}\n\n/**\n * The delay before the next attempt: a `Retry-After` header (seconds or HTTP-date)\n * wins; otherwise fixed/exponential backoff over `retryDelay`, with full jitter\n * unless `jitter === false`.\n */\nfunction retryDelay(retry: RetryConfig, attempt: number, retryAfter: string | null): number {\n if (retryAfter) {\n const seconds = Number(retryAfter);\n if (!Number.isNaN(seconds)) return seconds * 1000;\n const when = Date.parse(retryAfter);\n if (!Number.isNaN(when)) return Math.max(0, when - Date.now());\n }\n const base = retry.retryDelay ?? 1000;\n const raw = retry.retryStrategy === 'fixed' ? base : base * Math.pow(2, attempt - 1);\n return retry.jitter === false ? raw : Math.random() * raw;\n}\n\n/** Abort-aware sleep: resolves after `ms`, rejects with the abort reason immediately on abort. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortError(signal));\n return;\n }\n const onAbort = () => {\n clearTimeout(timer);\n reject(abortError(signal as AbortSignal));\n };\n const timer = setTimeout(() => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n if (signal) signal.addEventListener('abort', onAbort, { once: true });\n });\n}";
23
+ readonly 'multipart.ts': "/**\n * Serialize a plain object into `FormData` for a typed `multipart/form-data` body\n * (capability module — wired into `createClient`, never imported by the send core).\n * `Blob`/`File` and strings pass through; `Date`s become ISO strings; arrays append\n * one field per item; other objects are JSON-encoded; everything else is stringified.\n * `undefined`/`null` entries are skipped.\n */\nfunction toFormData(body: Record<string, unknown>): FormData {\n const fd = new FormData();\n const append = (key: string, value: unknown): void => {\n if (value === undefined || value === null) return;\n if (value instanceof Blob || typeof value === 'string') fd.append(key, value);\n else if (value instanceof Date) fd.append(key, value.toISOString());\n else if (Object(value) === value) fd.append(key, JSON.stringify(value));\n else fd.append(key, String(value));\n };\n for (const [key, value] of Object.entries(body)) {\n if (Array.isArray(value)) for (const item of value) append(key, item);\n else append(key, value);\n }\n return fd;\n}";
24
+ readonly 'auth.ts': "/** Resolve a credential: a literal passes through; a function is awaited per request. */\nasync function resolveToken(provider: TokenProvider): Promise<string> {\n return typeof provider === 'function' ? await provider() : provider;\n}\n\n/** UTF-8-safe base64: bare `btoa` throws on non-Latin-1 credentials (RFC 7617 allows UTF-8). */\nfunction encodeBase64(text: string): string {\n let binary = '';\n for (const byte of new TextEncoder().encode(text)) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n\n/** Whether a credential for this scheme is configured on the instance. */\nfunction isConfigured(scheme: SecuritySpec, config: ClientConfig): boolean {\n if (scheme.kind === 'apiKey') return config.auth?.apiKey?.[scheme.scheme] !== undefined;\n if (scheme.kind === 'bearer') return config.auth?.bearer !== undefined;\n return config.auth?.basic !== undefined;\n}\n\n/**\n * Build the auth headers/query for one operation's `security` OR-alternatives from the\n * instance credentials (`config.auth`) — capability module, wired into `createClient`.\n * The first alternative whose schemes (an AND-set) are all configured is applied, so\n * \"bearer OR apiKey\" works with either credential and never sends both. When none is\n * fully configured, the first alternative's configured schemes are still sent (the\n * server rejects the request, mirroring the previous behavior).\n * Cookie-borne apiKeys fold into a single `Cookie` header joined with `; `.\n */\nasync function resolveAuth(\n security: readonly (readonly SecuritySpec[])[],\n config: ClientConfig\n): Promise<{ headers: Record<string, string>; query: Record<string, string> }> {\n const alternative =\n security.find((schemes) => schemes.every((scheme) => isConfigured(scheme, config))) ??\n security[0] ??\n [];\n const headers: Record<string, string> = {};\n const query: Record<string, string> = {};\n const cookies: string[] = [];\n for (const scheme of alternative) {\n if (scheme.kind === 'apiKey') {\n const provider = config.auth?.apiKey?.[scheme.scheme];\n if (provider === undefined) continue;\n const value = await resolveToken(provider);\n if (scheme.in === 'header') headers[scheme.name] = value;\n else if (scheme.in === 'query') query[scheme.name] = value;\n // Cookie values may contain reserved characters (`;`, `=`, space, …); percent-encode\n // so the credential can't break the `Cookie` header syntax.\n else cookies.push(`${scheme.name}=${encodeURIComponent(value)}`);\n } else if (scheme.kind === 'bearer') {\n const provider = config.auth?.bearer;\n if (provider !== undefined) headers.Authorization = `Bearer ${await resolveToken(provider)}`;\n } else {\n const basic = config.auth?.basic;\n if (basic !== undefined) {\n headers.Authorization = `Basic ${encodeBase64(`${basic.username}:${basic.password}`)}`;\n }\n }\n }\n if (cookies.length > 0) headers.Cookie = cookies.join('; ');\n return { headers, query };\n}";
25
+ readonly 'setup.ts': "/**\n * Merge a publisher's baked setup (`defineClientSetup({...})`) with the app's config:\n * app config fields win per-field over baked defaults, while middleware composes —\n * baked middleware runs first, then the app's.\n */\nexport function mergeSetup(\n setup: { config?: ClientConfig; middleware?: Middleware[] } | undefined,\n config: ClientConfig = {}\n): ClientConfig {\n return {\n ...setup?.config,\n ...config,\n middleware: [...(setup?.middleware ?? []), ...(config.middleware ?? [])],\n };\n}";
26
+ readonly 'send.ts': "/**\n * Optional behaviors the send core can use but never statically imports — wired by\n * `createClient` (the same seam the future inline-mode assembler relies on).\n */\ntype SendCapabilities = {\n /** Serialize a typed multipart body (a plain object) to FormData. */\n serializeMultipart?: (body: Record<string, unknown>) => FormData;\n};\n\n/**\n * Normalize a caller's `HeadersInit` (plain record, `Headers` instance, or entry pairs)\n * to a plain record — spreading a `Headers` or an array contributes no entries.\n */\nfunction toHeaderRecord(headers: HeadersInit | undefined): Record<string, string> {\n if (headers === undefined) return {};\n if (headers instanceof Headers) {\n const record: Record<string, string> = {};\n headers.forEach((value, key) => {\n record[key] = value;\n });\n return record;\n }\n if (Array.isArray(headers)) return Object.fromEntries(headers);\n return headers;\n}\n\n/**\n * The effective middleware chain for a request: the single `onRequest`/`onResponse`/\n * `onError` config hooks as one implicit first middleware, then `config.middleware`.\n */\nfunction middlewareChain(config: ClientConfig): Middleware[] {\n const single =\n config.onRequest || config.onResponse || config.onError\n ? [{ onRequest: config.onRequest, onResponse: config.onResponse, onError: config.onError }]\n : [];\n return [...single, ...(config.middleware ?? [])];\n}\n\n/**\n * The fetch core shared by every operation: default + config + per-call headers, the\n * `onRequest` chain (BEFORE body serialization, so mutations are sent), body\n * serialization (JSON, or FormData via the multipart capability), the retry loop\n * (idempotent-only defaults, `Retry-After`, abandoned-body drain), and the reverse\n * `onResponse` onion. Returns the final response plus the request context.\n */\nasync function send(\n config: ClientConfig,\n op: OperationContext,\n url: string,\n init: RequestOptions,\n body: unknown | undefined,\n bodySpec: { contentType: string; multipart?: boolean } | undefined,\n caps: SendCapabilities,\n accept = 'application/json'\n): Promise<{ response: Response; context: RequestContext }> {\n const { retry: callRetry, timeout: callTimeout, idempotencyKey: callKey, ...fetchInit } = init;\n const retry: RetryConfig = { ...config.retry, ...callRetry };\n const timeout = callTimeout ?? config.timeout;\n const idempotency = callKey ?? config.idempotencyKey;\n const extra = typeof config.headers === 'function' ? await config.headers() : config.headers;\n const headers: Record<string, string> = {\n Accept: accept,\n ...extra,\n ...toHeaderRecord(fetchInit.headers),\n };\n const method = (fetchInit.method ?? 'GET').toUpperCase();\n // One stable key per LOGICAL call — set before the retry loop so every attempt\n // re-sends the same key; a caller-provided header always wins.\n if (\n idempotency !== undefined &&\n idempotency !== false &&\n (method === 'POST' || method === 'PATCH') &&\n !('Idempotency-Key' in headers) &&\n !('idempotency-key' in headers)\n ) {\n headers['Idempotency-Key'] =\n typeof idempotency === 'string'\n ? idempotency\n : typeof idempotency === 'function'\n ? idempotency()\n : crypto.randomUUID();\n }\n // Client identification for the API owner's telemetry — never in browsers, where a\n // custom header would force a CORS preflight the API may not allow.\n if (\n typeof config.clientHeader === 'string' &&\n typeof document === 'undefined' &&\n !('X-Redocly-Client' in headers) &&\n !('x-redocly-client' in headers)\n ) {\n headers['X-Redocly-Client'] = config.clientHeader;\n }\n const context: RequestContext = {\n url,\n method: fetchInit.method ?? 'GET',\n headers,\n body,\n operation: op,\n };\n const middleware = middlewareChain(config);\n for (const mw of middleware) if (mw.onRequest) await mw.onRequest(context);\n // Serialize AFTER onRequest so body mutations (case conversion, enveloping, signing) take effect.\n let payload: BodyInit | undefined;\n if (context.body !== undefined) {\n const value = context.body;\n const isBinary =\n value instanceof Blob ||\n value instanceof ArrayBuffer ||\n ArrayBuffer.isView(value as ArrayBufferView);\n const isFormData = typeof FormData !== 'undefined' && value instanceof FormData;\n const isURLSearchParams = value instanceof URLSearchParams;\n if (isFormData || isURLSearchParams || isBinary || typeof value === 'string') {\n payload = value as BodyInit;\n } else if (bodySpec?.multipart === true) {\n if (!caps.serializeMultipart) {\n throw new Error('Multipart capability not wired: cannot serialize the request body');\n }\n payload = caps.serializeMultipart(value as Record<string, unknown>);\n } else {\n payload = JSON.stringify(value);\n if (!('Content-Type' in context.headers) && !('content-type' in context.headers)) {\n // The spec's declared request content type (e.g. application/merge-patch+json).\n context.headers['Content-Type'] = bodySpec?.contentType ?? 'application/json';\n }\n }\n }\n const doFetch = config.fetch ?? fetch;\n const maxAttempts = 1 + (retry.retries ?? 0);\n const retryOn = retry.retryOn ?? defaultRetryOn;\n const signal = fetchInit.signal ?? undefined;\n\n let attempt = 0;\n while (true) {\n attempt++;\n if (signal?.aborted) throw abortError(signal);\n // A fresh timeout budget per attempt; the caller's signal still wins the race.\n // The composed signal also governs reading the response body.\n const attemptSignal = timeout\n ? signal\n ? AbortSignal.any([signal, AbortSignal.timeout(timeout)])\n : AbortSignal.timeout(timeout)\n : signal;\n let response: Response;\n try {\n response = await doFetch(context.url, {\n ...fetchInit,\n signal: attemptSignal,\n method: context.method,\n headers: context.headers,\n body: payload,\n });\n } catch (error) {\n if (\n attempt < maxAttempts &&\n !signal?.aborted &&\n (await retryOn({ attempt, request: context, error }))\n ) {\n await sleep(retryDelay(retry, attempt, null), signal);\n continue;\n }\n // Our timeout fired (never the caller's own abort — that rethrows untouched):\n // wrap the bare DOMException with the context a log line needs.\n if (\n timeout &&\n !signal?.aborted &&\n error instanceof DOMException &&\n error.name === 'TimeoutError'\n ) {\n throw new TimeoutError(op.id, timeout, attempt);\n }\n throw error;\n }\n // Reverse order: the last-registered middleware wraps closest to the network (onion).\n for (let i = middleware.length - 1; i >= 0; i--) {\n const onResponse = middleware[i].onResponse;\n if (onResponse) {\n const replaced = await onResponse(response, context);\n if (replaced && replaced !== response) {\n // Cancel the abandoned original's body — like the retry path, an unread body\n // keeps its connection checked out under Node/undici.\n await response.body?.cancel().catch(() => undefined);\n response = replaced;\n }\n }\n }\n if (\n !response.ok &&\n attempt < maxAttempts &&\n !signal?.aborted &&\n (await retryOn({ attempt, request: context, response }))\n ) {\n const retryAfter = response.headers.get('retry-after');\n // Drain the abandoned response body before the next attempt: an unread body\n // keeps the connection checked out (and can stall the pool) under Node/undici\n // and other strict HTTP clients. Ignore errors (e.g. a middleware already read it).\n await response.body?.cancel().catch(() => undefined);\n await sleep(retryDelay(retry, attempt, retryAfter), signal);\n continue;\n }\n return { response, context };\n }\n}";
27
+ readonly 'sse.ts': "/**\n * A frame delimiter: two consecutive line terminators (each CR, LF, or CRLF, per the SSE\n * spec — so mixed endings like `\\n\\r\\n` are valid boundaries, not just matching pairs).\n */\nconst FRAME_DELIMITER = /(?:\\r\\n|\\r|\\n){2}/;\n\n/**\n * A terminally malformed event stream — unparseable JSON `data` or an unbounded frame.\n * A stable bad payload, not a dropped connection, so the stream never reconnects on it.\n */\nclass SseParseError extends Error {}\n\n/**\n * Consume a `text/event-stream` operation as typed events (capability module — wired\n * into `createClient`). Auto-reconnects on dropped connections, resuming from the last\n * seen event id via `Last-Event-ID` (backoff: the server's `retry:` value, then\n * `reconnectDelay`, then 1s — exponential with jitter, capped at 30s). A clean stream\n * end flushes a trailing frame and finishes; `break`/abort end the iterator cleanly.\n */\nasync function* sse<T>(\n config: ClientConfig,\n op: OperationContext,\n prepare: () => Promise<{ url: string; init: SseOptions; body?: unknown }>,\n dataKind: 'json' | 'text' = 'text'\n): AsyncGenerator<ServerSentEvent<T>> {\n let lastEventId: string | undefined;\n let serverRetry: number | undefined;\n let failures = 0;\n while (true) {\n // Re-prepare each attempt so a refresh-style TokenProvider yields a fresh credential\n // on reconnect (the auth is baked into `url` query + `init.headers`). `reconnect`,\n // `reconnectDelay`, and `signal` come from the caller's original options unchanged.\n const { url, init, body: requestBody } = await prepare();\n const { reconnect = true, reconnectDelay, ...rest } = init;\n const signal = rest.signal ?? undefined;\n if (signal?.aborted) return;\n const headers: Record<string, string> = {\n Accept: 'text/event-stream',\n ...toHeaderRecord(rest.headers),\n };\n const sendHeaders =\n lastEventId === undefined ? headers : { ...headers, 'Last-Event-ID': lastEventId };\n try {\n const { response } = await send(\n config,\n op,\n url,\n // `timeout: 0` opts the stream out of a config-level timeout — an event stream\n // is long-lived by design and must not be severed after N milliseconds.\n { ...rest, method: rest.method ?? 'GET', headers: sendHeaders, timeout: 0 },\n requestBody,\n undefined,\n {}\n );\n if (!response.ok) {\n const errorBody = await readError(response);\n throw new ApiError(url, response.status, response.statusText, errorBody);\n }\n failures = 0;\n const body = response.body;\n if (!body) return;\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n while (true) {\n const { done, value } = await reader.read();\n buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });\n let match: RegExpExecArray | null;\n while ((match = FRAME_DELIMITER.exec(buffer)) !== null) {\n const raw = buffer.slice(0, match.index);\n buffer = buffer.slice(match.index + match[0].length);\n const event = parseSseFrame(raw, dataKind);\n if (event) {\n if (event.id !== undefined) lastEventId = event.id;\n if (event.retry !== undefined) serverRetry = event.retry;\n yield event as ServerSentEvent<T>;\n }\n }\n if (done) {\n // Stream closed cleanly. Flush a final event that arrived without a trailing\n // delimiter, then finish — a clean end is not a dropped connection, so do not reconnect.\n const event = buffer.length > 0 ? parseSseFrame(buffer, dataKind) : undefined;\n if (event) {\n if (event.id !== undefined) lastEventId = event.id;\n if (event.retry !== undefined) serverRetry = event.retry;\n yield event as ServerSentEvent<T>;\n }\n return;\n }\n // Bound memory: a server that never sends a frame delimiter would otherwise\n // grow `buffer` without limit. 1 MiB is far above any real SSE frame.\n if (buffer.length > 1048576) {\n throw new SseParseError('SSE frame exceeded 1048576 characters without a delimiter');\n }\n }\n } finally {\n await reader.cancel().catch(() => undefined);\n }\n } catch (error) {\n if (signal?.aborted) return;\n // A non-OK HTTP response (4xx/5xx) or an unparseable JSON payload is a definitive\n // error, not a transient drop — surface it instead of reconnecting in a loop (a\n // stable bad payload would otherwise reconnect forever).\n if (error instanceof ApiError || error instanceof SseParseError) throw error;\n // A transport failure (connect/DNS/reset) when opening the request, or a mid-stream\n // read error, is a dropped connection: fall through to backoff/reconnect when enabled.\n if (!reconnect) throw error;\n }\n // Only the swallowed-drop path reaches here: reconnect is on and the signal not aborted.\n failures++;\n const base = serverRetry ?? reconnectDelay ?? 1000;\n const delay = Math.min(base * Math.pow(2, failures - 1), 30_000);\n try {\n await sleep(Math.random() * delay, signal);\n } catch {\n return; // sleep rejects only on abort — end the iterator cleanly\n }\n }\n}\n\n/** Parse one raw SSE frame (its lines) into an event; returns undefined for comment-only frames. */\nfunction parseSseFrame(\n raw: string,\n dataKind: 'json' | 'text'\n): ServerSentEvent<unknown> | undefined {\n let event: string | undefined;\n const dataLines: string[] = [];\n let id: string | undefined;\n let retry: number | undefined;\n let sawField = false;\n for (const line of raw.split(/\\r\\n|\\n|\\r/)) {\n if (line === '' || line.startsWith(':')) continue;\n const colon = line.indexOf(':');\n const field = colon === -1 ? line : line.slice(0, colon);\n let val = colon === -1 ? '' : line.slice(colon + 1);\n if (val.startsWith(' ')) val = val.slice(1);\n sawField = true;\n if (field === 'event') event = val;\n else if (field === 'data') dataLines.push(val);\n else if (field === 'id') id = val;\n else if (field === 'retry') {\n // ASCII digits only, per the EventSource spec — anything else is ignored\n // (`Number('')` is 0 and would zero the reconnect backoff).\n if (/^\\d+$/.test(val)) retry = Number(val);\n }\n }\n if (!sawField) return undefined;\n const dataText = dataLines.join('\\n');\n let data: unknown = dataText;\n if (dataKind === 'json' && dataText !== '') {\n try {\n data = JSON.parse(dataText);\n } catch (error) {\n throw new SseParseError(\n `Failed to parse SSE event data as JSON: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n return { event, data, id, retry };\n}";
28
+ readonly 'create-client.ts': "/**\n * The optional behaviors `createClientCore` can dispatch to but never statically\n * imports. The package's public `createClient` wires the full set; the future\n * inline-mode assembler wires only the capabilities a spec needs.\n */\ntype Capabilities = SendCapabilities & {\n resolveAuth?: (\n security: readonly (readonly SecuritySpec[])[],\n config: ClientConfig\n ) => Promise<{ headers: Record<string, string>; query: Record<string, string> }>;\n sse?: (\n config: ClientConfig,\n op: OperationContext,\n // Re-preparing per (re)connect (not a frozen url/init) lets a refresh-style\n // TokenProvider issue a fresh credential after a dropped stream reconnects.\n prepare: () => Promise<{ url: string; init: SseOptions }>,\n dataKind: 'json' | 'text'\n ) => AsyncGenerator<ServerSentEvent<unknown>>;\n paginate?: {\n pages: (\n call: (args?: OperationArgs, init?: RequestOptions) => Promise<unknown>,\n spec: PaginationSpec,\n args?: OperationArgs,\n init?: RequestOptions\n ) => AsyncGenerator<unknown>;\n items: (\n call: (args?: OperationArgs, init?: RequestOptions) => Promise<unknown>,\n spec: PaginationSpec,\n args?: OperationArgs,\n init?: RequestOptions\n ) => AsyncGenerator<unknown>;\n // The `link`-style iterators need the raw `Link` header + page URL, which the\n // parsed-page call above cannot carry (the shape mirrors paginate's `LinkPageCall`).\n pagesByLink: (\n call: (\n args?: OperationArgs,\n init?: RequestOptions\n ) => Promise<{ page: unknown; linkHeader: string | null; url: string }>,\n args?: OperationArgs,\n init?: RequestOptions\n ) => AsyncGenerator<unknown>;\n itemsByLink: (\n call: (\n args?: OperationArgs,\n init?: RequestOptions\n ) => Promise<{ page: unknown; linkHeader: string | null; url: string }>,\n spec: PaginationSpec,\n args?: OperationArgs,\n init?: RequestOptions\n ) => AsyncGenerator<unknown>;\n };\n};\n\n/**\n * One call's inputs, namespaced by transport layer. `argsStyle: 'flat'` clients accept the\n * merged form instead (every parameter and body property at one level) — `namespaceArgs`\n * converts it to this shape before anything downstream reads it.\n */\ntype OperationArgs = {\n path?: Record<string, unknown>;\n query?: Record<string, QueryValue>;\n body?: unknown;\n headers?: Record<string, unknown>;\n cookies?: Record<string, unknown>;\n} & Record<string, unknown>;\n\n/** The five layer keys, and the only top-level keys a namespaced call may carry. */\nconst LAYERS: readonly string[] = ['path', 'query', 'body', 'headers', 'cookies'];\n\n/** Where a declared parameter's `in` value puts it. */\nconst LAYER_OF: Record<string, 'path' | 'query' | 'headers' | 'cookies'> = {\n path: 'path',\n query: 'query',\n header: 'headers',\n cookie: 'cookies',\n};\n\n/**\n * Merged (`argsStyle: 'flat'`) args → the namespaced shape. A key that names a declared\n * parameter goes to that parameter's layer; anything else is a property of the request\n * body, which is how a flat call spells an object body. `body` stays reserved for the\n * operations a flat call cannot merge (an array, a scalar, or a binary body).\n */\nfunction namespaceArgs(op: OperationDescriptor, args: OperationArgs): OperationArgs {\n const layers: Record<string, Record<string, unknown>> = {};\n let body: unknown;\n let properties: Record<string, unknown> | undefined;\n const layerOfParam = new Map((op.params ?? []).map((param) => [param.name, param.in]));\n for (const [key, value] of Object.entries(args)) {\n const layer = LAYER_OF[layerOfParam.get(key) ?? ''];\n if (layer !== undefined) {\n (layers[layer] ??= {})[key] = value;\n } else if (key === 'body' && op.body !== undefined) {\n body = value;\n } else if (op.body !== undefined) {\n (properties ??= {})[key] = value;\n } else {\n throw new TypeError(\n `Unknown argument \"${key}\" for operation \"${op.id}\": it names no declared parameter, and the operation takes no request body.`\n );\n }\n }\n const namespaced: OperationArgs = {};\n if (layers.path) namespaced.path = layers.path;\n // The flat surface types every query value, so the collected bag is one by construction.\n if (layers.query) namespaced.query = layers.query as Record<string, QueryValue>;\n if (layers.headers) namespaced.headers = layers.headers;\n if (layers.cookies) namespaced.cookies = layers.cookies;\n if (properties !== undefined) namespaced.body = properties;\n else if (body !== undefined) namespaced.body = body;\n return namespaced;\n}\n\n/** The response reader implied by the descriptor (before any per-call `parseAs` override). */\n/**\n * The `Accept` header matching how the response will be read — a blob/text operation\n * must not ask for `application/json` (a content-negotiating server would 406 or\n * answer with a JSON error body instead of the payload). Caller `init.headers` and\n * `config.headers` still override.\n */\nfunction acceptFor(kind: ParseAs | 'void'): string {\n if (kind === 'text') return 'text/*';\n if (kind === 'blob' || kind === 'arrayBuffer' || kind === 'stream' || kind === 'formData') {\n return '*/*';\n }\n return 'application/json'; // json | auto | void\n}\n\nfunction kindFor(op: OperationDescriptor): ParseAs | 'void' {\n if (op.responseKind === 'void' || op.responseKind === 'blob' || op.responseKind === 'text') {\n return op.responseKind;\n }\n return 'auto';\n}\n\n/**\n * The call's inputs in namespaced form, converting first on a flat-style client. An\n * operation the generator marked `argsStyle: 'grouped'` is already namespaced — its names\n * could not be merged, so its input type never offered the flat shape.\n */\nfunction inputOf(\n op: OperationDescriptor,\n args: OperationArgs,\n config: ClientConfig\n): OperationArgs {\n const merged = config.argsStyle === 'flat' && op.argsStyle !== 'grouped';\n return merged ? namespaceArgs(op, args) : args;\n}\n\n/** Route the namespaced args to the request pieces. */\nfunction splitArgs(op: OperationDescriptor, args: OperationArgs) {\n // An unknown layer key can only be a bug (usually flat-style args on a namespaced\n // client). TypeScript catches it, but a transpiler that skips type-checking would\n // otherwise ship a request that silently drops the value — fail the call loudly.\n for (const key of Object.keys(args)) {\n if (!LAYERS.includes(key)) {\n throw new TypeError(\n `Unknown argument \"${key}\" for operation \"${op.id}\". Inputs are grouped by layer: ${LAYERS.join(', ')}.`\n );\n }\n }\n return {\n path: args.path ?? {},\n query: args.query,\n body: args.body,\n headers: args.headers,\n cookies: args.cookies,\n };\n}\n\n/**\n * The query-serialization hints for the descriptor's query params. A spec is built only\n * when the param deviates from the OpenAPI defaults (`form` + `explode: true`, encoded),\n * and always fully resolved — so `explode: false` or `allowReserved` alone (no `style`)\n * are honored, and an omitted `explode` keeps the exploded default.\n */\nfunction queryStyles(op: OperationDescriptor): Record<string, QueryStyle> | undefined {\n let styles: Record<string, QueryStyle> | undefined;\n for (const param of op.params ?? []) {\n if (param.in !== 'query') continue;\n const deviates =\n (param.style !== undefined && param.style !== 'form') ||\n param.explode === false ||\n param.allowReserved === true;\n if (!deviates) continue;\n styles ??= {};\n styles[param.name] = {\n style: param.style ?? 'form',\n explode: param.explode ?? true,\n allowReserved: param.allowReserved,\n };\n }\n return styles;\n}\n\n/** Stringify caller-supplied extra headers, skipping empty entries. */\nfunction stringHeaders(headers: Record<string, unknown> | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers ?? {})) {\n if (value !== undefined && value !== null) out[key] = String(value);\n }\n return out;\n}\n\n/** Build the request pieces an attempt needs: the final URL and the merged per-call init. */\nasync function prepareRequest(\n config: ClientConfig,\n op: OperationDescriptor,\n args: OperationArgs,\n init: RequestOptions | SseOptions,\n caps: Capabilities\n): Promise<{ url: string; init: RequestOptions; body: unknown }> {\n const { path, query, body, headers, cookies } = splitArgs(op, args);\n const authed: { headers: Record<string, string>; query: Record<string, string> } =\n op.security?.length && caps.resolveAuth\n ? await caps.resolveAuth(op.security, config)\n : { headers: {}, query: {} };\n // Cookie params join the auth-injected cookies in one `Cookie` header (values\n // percent-encoded, like auth cookies). Server-side only — browsers own the header.\n const cookiePairs = Object.entries(cookies ?? {})\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([cookieName, value]) => `${cookieName}=${encodeURIComponent(String(value))}`);\n if (cookiePairs.length > 0) {\n authed.headers.Cookie = [authed.headers.Cookie, ...cookiePairs].filter(Boolean).join('; ');\n }\n const fullQuery: Record<string, QueryValue> = { ...query, ...authed.query };\n const url = buildUrl(\n config.serverUrl ?? '',\n substitutePath(op.path, path),\n Object.keys(fullQuery).length > 0 ? fullQuery : undefined,\n queryStyles(op)\n );\n const mergedInit: RequestOptions = {\n ...init,\n method: op.method.toUpperCase(),\n // Precedence, lowest → highest (later spreads win): injected auth → explicit\n // header params → caller `init.headers` — the caller always overrides both.\n headers: {\n ...authed.headers,\n ...stringHeaders(headers),\n ...toHeaderRecord(init.headers),\n },\n };\n return { url, init: mergedInit, body };\n}\n\n/** Coerce a single declared response header value; omit when absent or unparsable. */\nfunction coerceResponseHeader(\n raw: string | null,\n type: ResponseHeaderSpec['type']\n): string | number | boolean | undefined {\n if (raw === null) return undefined;\n if (type === 'number') {\n if (raw.trim() === '') return undefined;\n const value = Number(raw);\n return Number.isFinite(value) ? value : undefined;\n }\n if (type === 'boolean') {\n const value = raw.trim().toLowerCase();\n if (value === 'true') return true;\n if (value === 'false') return false;\n return undefined;\n }\n return raw;\n}\n\n/** Build the camelCase declared-header bag for a throw-mode envelope. */\nfunction readEnvelopeHeaders(\n response: Response,\n specs: readonly ResponseHeaderSpec[] | undefined\n): Record<string, string | number | boolean> {\n const headers: Record<string, string | number | boolean> = {};\n for (const spec of specs ?? []) {\n const value = coerceResponseHeader(response.headers.get(spec.name), spec.type);\n if (value !== undefined) headers[spec.key] = value;\n }\n return headers;\n}\n\n/** One non-SSE call: send, then branch on the configured error mode. */\nasync function execute(\n config: ClientConfig,\n op: OperationDescriptor,\n args: OperationArgs,\n init: RequestOptions,\n caps: Capabilities\n): Promise<unknown> {\n const prepared = await prepareRequest(config, op, args, init, caps);\n const opCtx: OperationContext = { id: op.id, path: op.path, tags: [...(op.tags ?? [])] };\n // `parseAs` / `envelope` are client options, not fetch RequestInit fields.\n const { parseAs, envelope, ...sendInit } = prepared.init;\n const readKind = parseAs ?? kindFor(op);\n const { response, context } = await send(\n config,\n opCtx,\n prepared.url,\n sendInit,\n prepared.body,\n op.body,\n caps,\n acceptFor(readKind)\n );\n if (config.errorMode === 'result') {\n if (!response.ok) {\n return { data: undefined, error: await readError(response), response };\n }\n return { data: await parse(response, readKind), error: undefined, response };\n }\n if (!response.ok) {\n let error: globalThis.Error = new ApiError(\n context.url,\n response.status,\n response.statusText,\n await readError(response)\n );\n // Thread the error through each middleware's onError in turn (each may replace it).\n for (const mw of middlewareChain(config)) {\n if (mw.onError) error = await mw.onError(error as ApiErrorLike, context);\n }\n throw error;\n }\n const data = await parse(response, readKind);\n if (envelope === true) {\n return {\n data,\n headers: readEnvelopeHeaders(response, op.responseHeaders),\n response,\n };\n }\n return data;\n}\n\n/** The paginate capability, or a descriptive throw when a paginated op is iterated unwired. */\nfunction paginateCapability(caps: Capabilities, op: OperationDescriptor) {\n if (!caps.paginate) {\n throw new Error(`Pagination capability not wired: cannot iterate operation \"${op.id}\"`);\n }\n return caps.paginate;\n}\n\n/**\n * The per-page call the iterators drive: the method itself in throw mode; in result\n * mode a wrapper that unwraps the `{ data, error, response }` envelope — the page\n * pointers are data-rooted — rethrowing a failed page as `ApiError` (iteration is\n * error-mode-agnostic; the throw-mode-only `onError` middleware hook is not invoked).\n */\nfunction pageCall(\n method: (args?: OperationArgs, init?: RequestOptions) => Promise<unknown>,\n config: ClientConfig\n) {\n const callWithoutEnvelope = (args?: OperationArgs, init?: RequestOptions) => {\n if (!init || init.envelope === undefined) return method(args, init);\n const { envelope: _envelope, ...pageInit } = init;\n return method(args, pageInit);\n };\n if (config.errorMode !== 'result') return callWithoutEnvelope;\n return async (args?: OperationArgs, init?: RequestOptions) => {\n const envelope = (await callWithoutEnvelope(args, init)) as {\n data: unknown;\n error: unknown;\n response: Response;\n };\n // Failure is `!response.ok` — NOT `data === undefined`: a successful bodyless page\n // (204/void) also parses to undefined data, and a failed page's `error` can be\n // undefined too (unreadable body). The pointers then miss on the undefined data\n // and iteration stops cleanly, which is the correct semantics for an empty page.\n if (!envelope.response.ok) {\n const { response } = envelope;\n throw new ApiError(response.url, response.status, response.statusText, envelope.error);\n }\n return envelope.data;\n };\n}\n\n/**\n * The per-page call the `link`-style iterators drive: like `execute`, but returning the\n * parsed page together with the raw `Link` header and the page's own URL (for resolving\n * a relative `rel=\"next\"` target). Error-mode-agnostic like all iteration: a failed\n * page throws `ApiError` even on result-mode clients.\n */\nfunction linkPageCall(config: ClientConfig, op: OperationDescriptor, caps: Capabilities) {\n return async (args: OperationArgs = {}, init: RequestOptions = {}) => {\n const prepared = await prepareRequest(config, op, args, init, caps);\n const { parseAs, envelope: _envelope, ...sendInit } = prepared.init;\n const readKind = parseAs ?? kindFor(op);\n const opCtx: OperationContext = { id: op.id, path: op.path, tags: [...(op.tags ?? [])] };\n const { response } = await send(\n config,\n opCtx,\n prepared.url,\n sendInit,\n prepared.body,\n op.body,\n caps,\n acceptFor(readKind)\n );\n if (!response.ok) {\n throw new ApiError(\n prepared.url,\n response.status,\n response.statusText,\n await readError(response)\n );\n }\n return {\n page: await parse(response, readKind),\n linkHeader: response.headers.get('link'),\n // Some `Response` implementations leave `url` empty (mocks, constructed responses).\n url: response.url === '' ? prepared.url : response.url,\n };\n };\n}\n\n/**\n * Build a typed instance client over operation descriptors: one real bound method per\n * operation (attached by a construction-time loop — no Proxy), plus the core members\n * (`configure`/`use`/`auth`), which are assigned AFTER the loop so they win any name\n * collision with an operation. All behavior dispatches through the capability seam.\n */\nfunction createClientCore<\n Ops extends OpsShape,\n Id extends string = string,\n Path extends string = string,\n Tag extends string = string,\n>(\n operations: Record<string, OperationDescriptor>,\n initial: ClientConfig<OperationContext<Id, Path, Tag>> = {},\n caps: Capabilities = {}\n): Client<Ops, OperationContext<Id, Path, Tag>> {\n // The literal-union narrowing is a compile-time DX contract only; internally the\n // runtime works with the base (string-typed) context. One cast at this boundary —\n // `ClientConfig<Narrow>` is not assignable to `ClientConfig` (middleware ctx\n // params are contravariant).\n const given = initial as ClientConfig;\n // Private mutable config; the middleware array is copied so `use()` never mutates the caller's.\n const config: ClientConfig = { ...given, middleware: [...(given.middleware ?? [])] };\n const client = {} as Record<string, unknown>;\n\n for (const [name, op] of Object.entries(operations)) {\n if (op.responseKind === 'sse') {\n const method = (given: OperationArgs = {}, init: SseOptions = {}) => {\n const args = inputOf(op, given, config);\n if (!caps.sse) {\n throw new Error(`SSE capability not wired: cannot stream operation \"${op.id}\"`);\n }\n const stream = caps.sse;\n return (async function* () {\n const opCtx: OperationContext = { id: op.id, path: op.path, tags: [...(op.tags ?? [])] };\n // A thunk the stream re-runs on every (re)connect, so auth (which `prepareRequest`\n // resolves) is refreshed per attempt rather than frozen at the first connect.\n const prepare = async () => {\n const prepared = await prepareRequest(config, op, args, init, caps);\n return { url: prepared.url, init: prepared.init as SseOptions, body: prepared.body };\n };\n yield* stream(config, opCtx, prepare, op.sseDataKind ?? 'text');\n })();\n };\n // Consumers key off the function reference (cache keys, `OPERATIONS[fn.name]`), so\n // each closure carries its operationId instead of an inferred binding name.\n // `operationId` is the explicit, minification-proof form of the same identity\n // (the SPEC operationId — `name` is the emitted key, which a collision may rename).\n Object.defineProperty(method, 'name', { value: name });\n Object.defineProperty(method, 'operationId', { value: op.id });\n client[name] = method;\n } else {\n // `raw` takes namespaced args; `method` is the public entry that accepts whichever\n // style the client was generated with. The iterators namespace once and then drive\n // `raw`, so a flat call is never converted twice.\n const raw = (args: OperationArgs = {}, init: RequestOptions = {}) =>\n execute(config, op, args, init, caps);\n const method = (args: OperationArgs = {}, init: RequestOptions = {}) =>\n raw(inputOf(op, args, config), init);\n Object.defineProperty(method, 'name', { value: name });\n Object.defineProperty(method, 'operationId', { value: op.id });\n const spec = op.pagination;\n // Paginated ops keep their one-shot call and gain `.pages`/`.items`, dispatching\n // through the capability seam (like SSE: absent capability throws descriptively).\n // Iteration is error-mode-agnostic: the iterators' pointers are data-rooted, so on\n // a result-mode client (`errorMode` is fixed at construction — `configure()`\n // ignores it) each page's envelope is unwrapped before it reaches the capability.\n // A failed page aborts iteration by throwing ApiError, even on result-mode\n // clients; the `onError` middleware hook (throw-mode-only) is not invoked.\n client[name] =\n spec === undefined\n ? method\n : spec.style === 'link'\n ? Object.assign(method, {\n pages: (args?: OperationArgs, init?: RequestOptions) =>\n paginateCapability(caps, op).pagesByLink(\n linkPageCall(config, op, caps),\n inputOf(op, args ?? {}, config),\n init\n ),\n items: (args?: OperationArgs, init?: RequestOptions) =>\n paginateCapability(caps, op).itemsByLink(\n linkPageCall(config, op, caps),\n spec,\n inputOf(op, args ?? {}, config),\n init\n ),\n })\n : Object.assign(method, {\n pages: (args?: OperationArgs, init?: RequestOptions) =>\n paginateCapability(caps, op).pages(\n pageCall(raw, config),\n spec,\n inputOf(op, args ?? {}, config),\n init\n ),\n items: (args?: OperationArgs, init?: RequestOptions) =>\n paginateCapability(caps, op).items(\n pageCall(raw, config),\n spec,\n inputOf(op, args ?? {}, config),\n init\n ),\n });\n }\n }\n\n // Core members are assigned AFTER the operation loop — they win over colliding op names.\n client.configure = (next: ClientConfig): void => {\n // `errorMode` and `argsStyle` are fixed at generate time (they shape the static types);\n // flipping either at runtime would silently desync the calls from `Client<Ops>`, so both\n // are ignored here.\n const { errorMode: _fixedMode, argsStyle: _fixedStyle, auth, ...rest } = next;\n Object.assign(config, rest);\n // `auth` merges into existing credentials (like the `auth.*` setters) rather than\n // replacing wholesale — so `configure({ auth: { bearer } })` keeps a previously set\n // basic/apiKey. `apiKey` merges per scheme.\n if (auth) {\n config.auth = {\n ...config.auth,\n ...auth,\n ...(auth.apiKey ? { apiKey: { ...config.auth?.apiKey, ...auth.apiKey } } : {}),\n };\n }\n };\n client.use = (...middleware: Middleware[]): void => {\n // Reassign (don't push) so a caller-provided `middleware` array isn't mutated.\n config.middleware = [...(config.middleware ?? []), ...middleware];\n };\n client.auth = {\n bearer(token: TokenProvider): void {\n config.auth = { ...config.auth, bearer: token };\n },\n basic(username: string, password: string): void {\n config.auth = { ...config.auth, basic: { username, password } };\n },\n apiKey(scheme: string, value: TokenProvider): void {\n config.auth = { ...config.auth, apiKey: { ...config.auth?.apiKey, [scheme]: value } };\n },\n };\n\n return client as Client<Ops, OperationContext<Id, Path, Tag>>;\n}";
29
+ readonly 'paginate.ts': "/**\n * Auto-pagination (capability module — wired into `createClient`, dispatched by the\n * method's `.pages()`/`.items()`): walk an operation's pages by advancing the descriptor's\n * `param` query parameter, per its `style`. The caller's args are never mutated — each\n * request gets a fresh `query` clone — and `init` is forwarded to every call.\n *\n * Iteration is error-mode-agnostic: `call` always resolves to the RAW page (on a\n * result-mode client the attachment unwraps the envelope first), so a failed page\n * aborts iteration by throwing `ApiError`, even on result-mode clients; the `onError`\n * middleware hook (throw-mode-only) is not invoked.\n */\n\n/**\n * Resolve an RFC 6901 JSON pointer (`~1` → `/`, `~0` → `~`) against a value.\n * The empty pointer is the whole document; anything else must start with `/`.\n * Returns `undefined` on any miss (bad token, absent key, non-object step) — never throws.\n */\nfunction resolvePointer(value: unknown, pointer: string): unknown {\n if (pointer === '') return value;\n if (!pointer.startsWith('/')) return undefined;\n let current = value;\n for (const token of pointer.slice(1).split('/')) {\n const key = token.replaceAll('~1', '/').replaceAll('~0', '~');\n if (Array.isArray(current)) {\n if (!/^(0|[1-9]\\d*)$/.test(key)) return undefined;\n current = current[Number(key)];\n } else if (Object(current) === current && key in (current as object)) {\n current = (current as Record<string, unknown>)[key];\n } else {\n return undefined;\n }\n }\n return current;\n}\n\n/**\n * Iterate an operation's full page results. Every page is yielded before the stop\n * condition is evaluated, so the last page always arrives. Cursor style resumes from a\n * caller-provided `query[spec.param]`, stops when the optional `hasMore` pointer\n * resolves to `false` or when `nextCursor` resolves to `undefined`/`null`/`''`, and\n * throws if the next cursor is not a string or number, or\n * if the same cursor comes back twice in a row (infinite-loop guards). Offset/page\n * styles advance by item count / by one and stop when\n * the `items` pointer misses or the array is empty.\n */\nasync function* pages<TPage>(\n call: (args?: OperationArgs, init?: RequestOptions) => Promise<TPage>,\n spec: PaginationSpec,\n args: OperationArgs = {},\n init?: RequestOptions\n): AsyncGenerator<TPage> {\n if (spec.style === 'cursor') {\n let cursor: unknown = args.query?.[spec.param];\n while (true) {\n const query = { ...args.query };\n if (cursor !== undefined) query[spec.param] = cursor as QueryValue;\n const page = await call({ ...args, query }, init);\n yield page;\n // Connection-style APIs keep a non-null cursor on the last page and signal the\n // end via a boolean flag — honor it before the cursor check to skip the\n // follow-up empty request. Strictly `false`: a missing pointer falls through.\n if (spec.hasMore !== undefined && resolvePointer(page, spec.hasMore) === false) return;\n const next = resolvePointer(page, spec.nextCursor);\n if (next === undefined || next === null || next === '') return;\n if (typeof next !== 'string' && typeof next !== 'number') {\n // A fresh non-scalar cursor never compares equal, so without this guard a lying\n // server would slip past the did-not-advance check into an infinite loop.\n throw new Error(`Pagination cursor at ${spec.nextCursor} is not a string or number`);\n }\n if (next === cursor) {\n throw new Error('Pagination did not advance: operation returned the same cursor twice');\n }\n cursor = next;\n }\n } else if (spec.style === 'link') {\n // `link` iteration needs the response's `Link` header, which the parsed-page `call`\n // cannot carry — the client wires those operations to `pagesByLink` instead.\n throw new Error('link-style pagination iterates via pagesByLink');\n } else {\n // Coerce the starting position to a number: a caller may pass `query[spec.param]` as a\n // string (common from URL/form input), and `+=` on a string would concatenate. `null`\n // and `''` count as absent — `Number` would turn them into 0, but a one-shot call\n // omits the param for those values, so the iterator must not start at position 0.\n const start = args.query?.[spec.param];\n const fallback = spec.style === 'page' ? 1 : 0;\n const absent = start === undefined || start === null || start === '';\n let position = absent || Number.isNaN(Number(start)) ? fallback : Number(start);\n let previousItems: string | undefined;\n while (true) {\n const page = await call({ ...args, query: { ...args.query, [spec.param]: position } }, init);\n const pageItems = resolvePointer(page, spec.items);\n // Some APIs clamp a past-the-end offset/page to the last non-empty page instead\n // of returning an empty one — the repeated page would otherwise loop forever\n // (the position always \"advances\", so a cursor-style token check can't catch it).\n const serialized = Array.isArray(pageItems) ? JSON.stringify(pageItems) : undefined;\n if (serialized !== undefined && serialized === previousItems) {\n throw new Error('Pagination did not advance: the operation returned the same page twice');\n }\n yield page;\n if (!Array.isArray(pageItems) || pageItems.length === 0) return;\n previousItems = serialized;\n position += spec.style === 'page' ? 1 : pageItems.length;\n }\n }\n}\n\n/**\n * Iterate the operation's individual items: each page's `items` pointer, flattened.\n * A cursor-style page whose pointer misses yields nothing but pagination continues;\n * for offset/page styles a miss has already stopped `pages`.\n */\nasync function* items<TItem>(\n call: (args?: OperationArgs, init?: RequestOptions) => Promise<unknown>,\n spec: PaginationSpec,\n args?: OperationArgs,\n init?: RequestOptions\n): AsyncGenerator<TItem> {\n for await (const page of pages(call, spec, args, init)) {\n const pageItems = resolvePointer(page, spec.items);\n if (Array.isArray(pageItems)) yield* pageItems as TItem[];\n }\n}\n\n/**\n * The per-page call the `link`-style iterators drive: the parsed page plus the raw\n * `Link` header and the page's own URL (for resolving a relative `next` target).\n */\ntype LinkPageCall = (\n args?: OperationArgs,\n init?: RequestOptions\n) => Promise<{ page: unknown; linkHeader: string | null; url: string }>;\n\n/** The `rel=\"next\"` target of an RFC 8288 `Link` header, or `undefined` when absent. */\nfunction linkNext(header: string | null): string | undefined {\n if (header === null) return undefined;\n // Entries are `<url>; rel=\"next\"`, comma-separated; the URL is inside `<>`, so split\n // on commas that precede a `<` and a comma inside a target cannot break an entry.\n for (const entry of header.split(/,\\s*(?=<)/)) {\n const target = /^\\s*<([^>]*)>(.*)$/.exec(entry);\n if (!target) continue;\n const rel = /;\\s*rel\\s*=\\s*\"?([^\";]+)\"?/i.exec(target[2]);\n // `rel` may carry several space-separated relation types (RFC 8288 §3.3).\n if (rel && rel[1].split(/\\s+/).includes('next')) return target[1];\n }\n return undefined;\n}\n\n/**\n * Iterate a `link`-style operation's pages: follow the `Link` header's `rel=\"next\"`\n * target by merging ITS query params into the next call — every page goes through the\n * same declared endpoint, so auth, middleware, and `serverUrl` handling apply\n * unchanged, and credentials can never be handed to a cross-origin `next` URL.\n * Stops when no `rel=\"next\"` is present; throws when the link does not advance\n * (the same target twice, or a self-link — an infinite-loop guard).\n */\nasync function* pagesByLink<TPage>(\n call: LinkPageCall,\n args: OperationArgs = {},\n init?: RequestOptions\n): AsyncGenerator<TPage> {\n let query = args.query;\n let previous: string | undefined;\n while (true) {\n const { page, linkHeader, url } = await call({ ...args, query }, init);\n yield page as TPage;\n const target = linkNext(linkHeader);\n if (target === undefined) return;\n // A relative target resolves against the page's own URL (RFC 8288 §3.1) — which may\n // itself be relative (relative `serverUrl`, mocked fetch), so both resolve against a\n // placeholder origin. It never reaches the wire: only the target's query params\n // carry into the next call.\n const pageUrl = new URL(url, 'http://relative.invalid');\n const next = new URL(target, pageUrl).toString();\n if (next === previous || next === pageUrl.toString()) {\n throw new Error('Pagination did not advance: the Link rel=\"next\" target repeats');\n }\n previous = next;\n // A repeated key (`?tag=a&tag=b`) folds into an array — the query serializer\n // expands arrays back into repeated pairs.\n const linkParams: Record<string, string | string[]> = {};\n for (const [key, value] of new URL(next).searchParams) {\n const seen = linkParams[key];\n if (seen === undefined) linkParams[key] = value;\n else if (Array.isArray(seen)) seen.push(value);\n else linkParams[key] = [seen, value];\n }\n query = { ...args.query, ...linkParams };\n }\n}\n\n/** Iterate a `link`-style operation's individual items: each page's `items` pointer, flattened. */\nasync function* itemsByLink<TItem>(\n call: LinkPageCall,\n spec: PaginationSpec,\n args?: OperationArgs,\n init?: RequestOptions\n): AsyncGenerator<TItem> {\n for await (const page of pagesByLink(call, args, init)) {\n const pageItems = resolvePointer(page, spec.items);\n if (Array.isArray(pageItems)) yield* pageItems as TItem[];\n }\n}";
30
+ readonly 'cli.ts': "// The generated-CLI engine: a pure argv parser plus a dispatcher that drives the\n// instance client and maps outcomes to the documented exit-code contract\n// (0 success, 1 API error, 2 auth, 3 validation, 4 usage). Node-only by nature,\n// but every effect (env, stdin, files, output) is injected through the wiring so\n// the module itself stays dependency-free and fully unit-testable; the emitted\n// entry fills the defaults with real `node:fs`/`process` bindings.\n\n/** One flag derived from a query parameter. */\ntype CliFlag = {\n /** Kebab-cased flag name (`--page-size`). */\n name: string;\n /** Original wire parameter name. */\n param: string;\n type: 'string' | 'number' | 'boolean' | 'array';\n required: boolean;\n enum?: string[];\n description?: string;\n};\n\n/** One executable command, derived from the IR at generate time. Pure data. */\ntype CliCommand = {\n /** Tag; absent = flat/untagged. */\n group?: string;\n name: string;\n summary?: string;\n method: string;\n path: string;\n /** Path params, in path-template order. Always required — that is what a path is. */\n positionals: Array<{\n name: string;\n type?: CliFlag['type'];\n description?: string;\n }>;\n flags: CliFlag[];\n /**\n * Present when the operation takes a JSON request body. `merged` marks a body whose own\n * properties a flat-style call spells at the top level (the generator decides this from\n * the schema, so the CLI and the client can never disagree).\n */\n body?: { required: boolean; merged?: boolean };\n /**\n * The content type of a request body that is NOT JSON (multipart, url-encoded, binary).\n * `--json` cannot build one, so the command is reported as library-only rather than\n * offered as if it were runnable.\n */\n unsupportedBody?: string;\n paginated?: boolean;\n /** `'grouped'` marks a command whose client method takes namespaced inputs even on a\n * flat-style client, because its merged names would collide. */\n argsStyle?: 'grouped';\n sse?: boolean;\n blob?: boolean;\n /** IR schemas for the `schema` command, serialized verbatim. */\n schemas?: { request?: unknown; response?: unknown };\n};\n\ntype CliAuthScheme = { key: string; kind: 'bearer' | 'basic' | 'apiKey' };\n\ntype CliWiring = {\n /** The name the CLI is invoked as, for help output only. The generated entry reads it\n * from `process.argv[1]`, so help never names a command that is not installed. */\n name: string;\n /** Credential variable prefix, constant-cased: `CAFE` gives `CAFE_TOKEN`. Fixed at\n * generation from the output file name, so renaming the binary keeps the variables\n * a published CLI already documents. A composed entry sets one per api alias. */\n envPrefix: string;\n /** The generated instance client. */\n client: Record<string, unknown>;\n /** How that client takes its inputs. Defaults to `'grouped'`, the generated default. */\n argsStyle?: 'grouped' | 'flat';\n configure: (config: Record<string, unknown>) => void;\n /** Security schemes of the API — drives env-var credential resolution. */\n schemes?: CliAuthScheme[];\n env?: Record<string, string | undefined>;\n stdin?: () => string;\n readFile?: (path: string) => string;\n writeFile?: (path: string, data: Uint8Array) => void;\n stdout: (line: string) => void;\n stderr: (line: string) => void;\n};\n\ntype CliGlobals = {\n serverUrl?: string;\n format?: 'json' | 'ndjson';\n dryRun?: boolean;\n pageAll?: boolean;\n output?: string;\n token?: string;\n json?: string;\n};\n\ntype CliInvocation =\n | { kind: 'help'; topic?: CliCommand | string }\n | { kind: 'schema'; command: CliCommand }\n | {\n kind: 'run';\n command: CliCommand;\n positionals: Record<string, string>;\n params: Record<string, unknown>;\n globals: CliGlobals;\n }\n | { kind: 'usage-error'; message: string };\n\n/**\n * A hand-written command composed NEXT TO the generated ones: the same data shape plus a\n * `handler`, so it inherits help, parsing, `schema`, and the exit-code contract. This is\n * how behavior that is not in the description (a `login`, a doctor command) joins the\n * binary without the generator ever learning what it does.\n */\ntype CustomCommand = {\n name: string;\n group?: string;\n summary?: string;\n positionals?: CliCommand['positionals'];\n flags?: CliFlag[];\n /** Returns the process exit code; throwing exits 1 with the standard error JSON. */\n handler: (context: CommandContext) => number | Promise<number>;\n};\n\ntype CommandContext = {\n positionals: Record<string, string>;\n params: Record<string, unknown>;\n globals: CliGlobals;\n wiring: CliWiring;\n};\n\n/** One API's contribution to a composed binary: its commands behind a namespace, with its\n * OWN wiring (base URL, schemes, credentials). A namespace-less source sits at the root. */\ntype CommandSource = {\n namespace?: string;\n commands: Array<CliCommand | CustomCommand>;\n /** Absent = inherit the first wired source's — a root `login` shares the binary's identity. */\n wiring?: CliWiring;\n};\n\ntype ResolvedCommand = CliCommand & { handler?: CustomCommand['handler'] };\n\n/** Custom commands as the command shape the parser reads; generated ones pass through. */\nfunction normalizeCommands(commands: Array<CliCommand | CustomCommand>): ResolvedCommand[] {\n return commands.map((command) =>\n 'handler' in command\n ? { method: '', path: '', positionals: [], flags: [], ...command }\n : command\n );\n}\n\n/**\n * The name of a custom command that shadows another command. Rejected at startup: an\n * operator typing an operationId must never silently run something else.\n */\nfunction shadowedCommandName(commands: ResolvedCommand[]): string | undefined {\n const seen = new Map<string, ResolvedCommand[]>();\n for (const command of commands) {\n const key = `${command.group ?? ''}\\u0000${command.name}`;\n seen.set(key, [...(seen.get(key) ?? []), command]);\n }\n for (const clashing of seen.values()) {\n if (clashing.length > 1 && clashing.some((command) => command.handler !== undefined)) {\n return clashing[0].name;\n }\n }\n return undefined;\n}\n\nconst GLOBAL_FLAGS: Record<string, { key: keyof CliGlobals; boolean?: boolean }> = {\n 'server-url': { key: 'serverUrl' },\n format: { key: 'format' },\n 'dry-run': { key: 'dryRun', boolean: true },\n 'page-all': { key: 'pageAll', boolean: true },\n output: { key: 'output' },\n token: { key: 'token' },\n json: { key: 'json' },\n};\n\n/**\n * The name to print in help: the command the CLI was invoked as. A global install resolves\n * `argv[1]` to the bin itself, so its basename is exactly what the user typed. A Windows\n * `.cmd` shim, a `node dist/cafe.cli.js`, and a `tsx client.cli.ts` run all pass the script\n * path instead — printing that would name a command nobody can type, so a script extension\n * and the `.cli` marker come off: `cafe.cli.js` prints `cafe`.\n */\nfunction invokedName(scriptPath: string | undefined, fallback: string): string {\n if (scriptPath === undefined) return fallback;\n const base = scriptPath.replace(/^.*[\\\\/]/, '');\n const withoutExtension = base.replace(/\\.(mjs|cjs|js|mts|cts|ts|cmd|bat|ps1|exe)$/i, '');\n const name = withoutExtension.replace(/\\.cli$/i, '');\n return name === '' ? fallback : name;\n}\n\n/**\n * The shell-typable form of a group name: an OpenAPI tag can contain spaces (\"Some\n * multi-word tag\"), which only resolves if the user quotes it. Commands are addressed by\n * this slug; help still shows the original tag.\n */\nfunction groupSlug(group: string): string {\n return group\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter(Boolean)\n .join('-');\n}\n\n/** A description on ONE line: newlines in an OpenAPI description wreck help alignment. */\nfunction oneLine(text: string): string {\n return text.replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * The parsed argv as one call input, in the style the wired client takes: grouped by layer\n * (the default) or merged into one object.\n */\nfunction callInputs(\n command: CliCommand,\n positionals: Record<string, string>,\n params: Record<string, unknown>,\n body: unknown,\n argsStyle: CliWiring['argsStyle']\n): Record<string, unknown> | undefined {\n const inputs: Record<string, unknown> = {};\n // A command the generator marked `grouped` keeps the namespaced shape even here.\n if (argsStyle === 'flat' && command.argsStyle !== 'grouped') {\n Object.assign(inputs, positionals, params);\n if (body !== undefined) {\n if (command.body?.merged === true) Object.assign(inputs, body as Record<string, unknown>);\n else inputs.body = body;\n }\n } else {\n if (Object.keys(positionals).length > 0) inputs.path = positionals;\n if (Object.keys(params).length > 0) inputs.query = params;\n if (body !== undefined) inputs.body = body;\n }\n return Object.keys(inputs).length > 0 ? inputs : undefined;\n}\n\n/** Resolve argv against the command table. Pure — no I/O, no env. */\nfunction parseInvocation(commands: CliCommand[], argv: string[]): CliInvocation {\n if (argv.length === 0 || argv[0] === '--help') return { kind: 'help' };\n\n if (argv[0] === 'schema') {\n const command = commands.find((candidate) => candidate.name === argv[1]);\n return command\n ? { kind: 'schema', command }\n : { kind: 'usage-error', message: `Unknown command: schema ${argv[1] ?? ''}`.trim() };\n }\n\n const slugs = new Set(commands.filter((c) => c.group).map((c) => groupSlug(c.group as string)));\n // An untagged operation is only ever addressed by its bare name, so when that name is also\n // a group slug the name wins — reading it as the group would leave the command unreachable.\n // A tagged operation in the same position keeps yielding to group help: it is still\n // reachable as `<its group> <name>`.\n const untagged = commands.some((c) => c.group === undefined && c.name === argv[0]);\n let command: CliCommand | undefined;\n let rest: string[];\n if (!untagged && slugs.has(argv[0])) {\n if (argv[1] === '--help' || argv[1] === undefined) return { kind: 'help', topic: argv[0] };\n command = commands.find((c) => c.group && groupSlug(c.group) === argv[0] && c.name === argv[1]);\n if (!command) return { kind: 'usage-error', message: `Unknown command: ${argv[0]} ${argv[1]}` };\n rest = argv.slice(2);\n } else {\n // An ungrouped command, or a bare operationId — knowing the group shouldn't be\n // required when the name alone is unambiguous.\n const named = commands.filter((c) => c.name === argv[0]);\n command =\n named.find((c) => c.group === undefined) ?? (named.length === 1 ? named[0] : undefined);\n if (!command) {\n const ambiguous = named.length > 1;\n return {\n kind: 'usage-error',\n message: ambiguous\n ? `Ambiguous command: ${argv[0]} — prefix it with its group (${named\n .map((c) => groupSlug(c.group as string))\n .join(', ')})`\n : `Unknown command: ${argv[0]}`,\n };\n }\n rest = argv.slice(1);\n }\n if (rest.includes('--help')) return { kind: 'help', topic: command };\n\n const positionals: Record<string, string> = {};\n const params: Record<string, unknown> = {};\n const globals: CliGlobals = {};\n let positionalIndex = 0;\n for (let index = 0; index < rest.length; index++) {\n const token = rest[index];\n if (!token.startsWith('--')) {\n const slot = command.positionals[positionalIndex++];\n if (!slot) return { kind: 'usage-error', message: `Unexpected argument: ${token}` };\n positionals[slot.name] = token;\n continue;\n }\n const equals = token.indexOf('=');\n const flagName = equals === -1 ? token.slice(2) : token.slice(2, equals);\n const inlineValue = equals === -1 ? undefined : token.slice(equals + 1);\n const takeValue = (): string | undefined =>\n inlineValue !== undefined ? inlineValue : rest[++index];\n\n const global = GLOBAL_FLAGS[flagName];\n if (global) {\n if (global.boolean) {\n (globals[global.key] as boolean) = true;\n continue;\n }\n const value = takeValue();\n if (value === undefined) {\n return { kind: 'usage-error', message: `Flag --${flagName} expects a value` };\n }\n if (global.key === 'format' && value !== 'json' && value !== 'ndjson') {\n return { kind: 'usage-error', message: `--format must be one of: json, ndjson` };\n }\n (globals[global.key] as string) = value;\n continue;\n }\n\n const flag = command.flags.find((candidate) => candidate.name === flagName);\n if (!flag) return { kind: 'usage-error', message: `Unknown flag: --${flagName}` };\n if (flag.type === 'boolean') {\n params[flag.param] = true;\n continue;\n }\n const value = takeValue();\n if (value === undefined) {\n return { kind: 'usage-error', message: `Flag --${flagName} expects a value` };\n }\n if (flag.enum && !flag.enum.includes(value)) {\n return {\n kind: 'usage-error',\n message: `--${flagName} must be one of: ${flag.enum.join(', ')}`,\n };\n }\n if (flag.type === 'number') {\n const numeric = Number(value);\n if (Number.isNaN(numeric)) {\n return { kind: 'usage-error', message: `--${flagName} expects a number, got \"${value}\"` };\n }\n params[flag.param] = numeric;\n } else if (flag.type === 'array') {\n const existing = params[flag.param];\n params[flag.param] = Array.isArray(existing) ? [...existing, value] : [value];\n } else {\n params[flag.param] = value;\n }\n }\n\n for (const slot of command.positionals) {\n if (!(slot.name in positionals)) {\n return { kind: 'usage-error', message: `Missing required argument: <${slot.name}>` };\n }\n }\n for (const flag of command.flags) {\n if (flag.required && !(flag.param in params)) {\n return { kind: 'usage-error', message: `Missing required flag: --${flag.name}` };\n }\n }\n if (globals.json !== undefined && !command.body) {\n return { kind: 'usage-error', message: `${command.name} does not accept a request body` };\n }\n if (command.body?.required && globals.json === undefined) {\n return {\n kind: 'usage-error',\n message: `${command.name} requires a request body: pass --json '<json>', --json @file.json, or --json @-`,\n };\n }\n if (globals.pageAll && !command.paginated) {\n return {\n kind: 'usage-error',\n message: `${command.name} is not paginated; --page-all only applies to paginated operations`,\n };\n }\n return { kind: 'run', command, positionals, params, globals };\n}\n\n/** `cafe-api` → `CAFE_API`: the casing of every credential variable this CLI reads. */\nfunction constantCase(value: string): string {\n return value\n .replace(/[^A-Za-z0-9]+/g, '_')\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .toUpperCase();\n}\n\nfunction resolveAuth(wiring: CliWiring, token: string | undefined): Record<string, unknown> {\n const env = wiring.env ?? {};\n const prefix = wiring.envPrefix;\n const auth: Record<string, unknown> = {};\n for (const scheme of wiring.schemes ?? []) {\n if (scheme.kind === 'bearer') {\n const value = token ?? env[`${prefix}_TOKEN`];\n if (value !== undefined) auth.bearer = value;\n } else if (scheme.kind === 'basic') {\n const username = env[`${prefix}_USERNAME`];\n const password = env[`${prefix}_PASSWORD`];\n if (username !== undefined && password !== undefined) auth.basic = { username, password };\n } else {\n const value = env[`${prefix}_API_KEY_${constantCase(scheme.key)}`];\n if (value !== undefined) {\n auth.apiKey = {\n ...(auth.apiKey as Record<string, string> | undefined),\n [scheme.key]: value,\n };\n }\n }\n }\n return auth;\n}\n\n/**\n * One command's complete contract as plain data — what `schema <command>` prints. It has\n * to carry the parameters: 'GET' operations have no body, so without them the output says\n * nothing a caller could act on, and the only alternative is scraping `--help`, which is\n * prose written for humans.\n */\nfunction commandContract(command: CliCommand): Record<string, unknown> {\n return {\n operationId: command.name,\n ...(command.group === undefined ? {} : { group: groupSlug(command.group) }),\n ...(command.summary === undefined ? {} : { summary: oneLine(command.summary) }),\n ...(command.method === '' ? {} : { method: command.method }),\n ...(command.path === '' ? {} : { path: command.path }),\n parameters: {\n path: command.positionals.map((positional) => ({\n name: positional.name,\n type: positional.type ?? 'string',\n required: true,\n ...(positional.description === undefined\n ? {}\n : { description: oneLine(positional.description) }),\n })),\n // `name` is what you type (`--max-total`); `param` is the wire name it becomes.\n query: command.flags.map((flag) => ({\n name: flag.name,\n param: flag.param,\n type: flag.type,\n required: flag.required,\n ...(flag.enum === undefined ? {} : { enum: flag.enum }),\n ...(flag.description === undefined ? {} : { description: oneLine(flag.description) }),\n })),\n },\n ...(command.body === undefined ? {} : { body: command.body }),\n ...(command.unsupportedBody === undefined ? {} : { unsupportedBody: command.unsupportedBody }),\n ...(command.paginated === true ? { paginated: true } : {}),\n ...(command.sse === true ? { sse: true } : {}),\n ...(command.blob === true ? { blob: true } : {}),\n ...(command.schemas ?? {}),\n };\n}\n\nfunction renderHelp(\n commands: CliCommand[],\n name: string,\n schemes: CliAuthScheme[],\n prefix: string,\n topic?: CliCommand | string\n): string[] {\n if (topic !== undefined && typeof topic !== 'string') {\n const command = topic;\n const usage = [\n name,\n ...(command.group ? [groupSlug(command.group)] : []),\n command.name,\n ...command.positionals.map((slot) => `<${slot.name}>`),\n ...(command.flags.length > 0 ? ['[flags]'] : []),\n ...(command.body ? [\"--json '<json>' | @file | @-\"] : []),\n ].join(' ');\n const lines = [`Usage: ${usage}`];\n if (command.summary) lines.push('', command.summary);\n if (command.unsupportedBody !== undefined) {\n lines.push(\n '',\n `This operation takes a ${command.unsupportedBody} body, which the CLI cannot build — call it through the generated client instead.`\n );\n }\n if (command.flags.length > 0) {\n lines.push('', 'Flags:');\n for (const flag of command.flags) {\n const choices = flag.enum ? ` (one of: ${flag.enum.join(', ')})` : '';\n const required = flag.required ? ' [required]' : '';\n lines.push(\n ` --${flag.name} <${flag.type}>${choices}${required} ${oneLine(flag.description ?? '')}`.trimEnd()\n );\n }\n }\n return lines;\n }\n const scope =\n typeof topic === 'string'\n ? commands.filter((c) => c.group && groupSlug(c.group) === topic)\n : commands;\n const lines =\n typeof topic === 'string'\n ? [`Usage: ${name} ${topic} <command> …`, '', 'Commands:']\n : [`Usage: ${name} [group] <command> …`, '', 'Commands:'];\n const seenGroups = new Set<string>();\n const grouped = commands.some((c) => c.group);\n for (const command of scope) {\n if (typeof topic !== 'string' && command.group) {\n const slug = groupSlug(command.group);\n if (seenGroups.has(slug)) continue;\n seenGroups.add(slug);\n // The slug is what you type; the tag is what you recognize.\n const title = slug === command.group ? '' : ` (${command.group})`;\n lines.push(` ${slug} <command>${title}`);\n continue;\n }\n lines.push(\n ` ${[command.group === undefined ? undefined : groupSlug(command.group), command.name]\n .filter(Boolean)\n .join(' ')} ${oneLine(command.summary ?? '')}`.trimEnd()\n );\n }\n // Flags that apply to every command, and the env vars credentials come from: a flag\n // absent from --help may as well not exist — and one this API cannot use should not be\n // listed at all, since the operator would spend the debugging session on their token.\n const kinds = new Set(schemes.map((scheme) => scheme.kind));\n const credentials = [\n ...(kinds.has('bearer') ? [`${prefix}_TOKEN`] : []),\n ...(kinds.has('basic') ? [`${prefix}_USERNAME`, `${prefix}_PASSWORD`] : []),\n ...schemes\n .filter((scheme) => scheme.kind === 'apiKey')\n .map((scheme) => `${prefix}_API_KEY_${constantCase(scheme.key)}`),\n ];\n lines.push(\n '',\n 'Global flags:',\n ' --server-url <url> Override the baked server URL',\n ' --format <json|ndjson> Output format',\n ' --dry-run Print the prepared request without sending it',\n ' --page-all Follow pagination, one JSON page per line',\n ' --output <path> Write the response body to a file (required for binary)',\n ...(kinds.has('bearer') ? [' --token <token> Bearer token'] : []),\n ` --json <json|@file|@-> Request body`,\n ...(credentials.length > 0 ? ['', 'Environment:', ` ${credentials.join(', ')}`] : []),\n '',\n `Run ${name} ${grouped ? '<group> <command>' : '<command>'} --help for command details; ${name} schema <command> prints its schemas.`\n );\n return lines;\n}\n\nfunction loadBody(source: string, wiring: CliWiring): unknown {\n const raw =\n source === '@-'\n ? (wiring.stdin ?? (() => ''))()\n : source.startsWith('@')\n ? (wiring.readFile ?? (() => ''))(source.slice(1))\n : source;\n return JSON.parse(raw);\n}\n\n/** Replace header values containing a known credential with `***`. */\nfunction redactHeaders(headers: Record<string, string>, secrets: string[]): Record<string, string> {\n const redacted: Record<string, string> = {};\n for (const [name, value] of Object.entries(headers)) {\n redacted[name] = secrets.some((secret) => secret !== '' && value.includes(secret))\n ? '***'\n : value;\n }\n return redacted;\n}\n\n/** Parse argv, resolve env auth, dispatch through the client, print, return the exit code. */\nasync function runCli(\n commands: Array<CliCommand | CustomCommand>,\n wiring: CliWiring,\n argv: string[]\n): Promise<number>;\n/** The composed form: one binary over several sources, each namespaced with its own wiring. */\nasync function runCli(sources: CommandSource[], argv: string[]): Promise<number>;\nasync function runCli(\n commandsOrSources: Array<CliCommand | CustomCommand> | CommandSource[],\n wiringOrArgv: CliWiring | string[],\n argv?: string[]\n): Promise<number> {\n if (Array.isArray(wiringOrArgv)) {\n return runSources(commandsOrSources as CommandSource[], wiringOrArgv);\n }\n return runSingle(\n commandsOrSources as Array<CliCommand | CustomCommand>,\n wiringOrArgv,\n argv ?? []\n );\n}\n\n/** Route the first token to its source; the namespace-less source owns the root. */\nasync function runSources(sources: CommandSource[], argv: string[]): Promise<number> {\n // A source without wiring inherits the first wired one, so the documented root-source\n // shape `{ commands: [login] }` works: the login shares the composed binary's identity.\n const inherited = sources.find((source) => source.wiring !== undefined)?.wiring;\n const wiringOf = (source: CommandSource): CliWiring => source.wiring ?? (inherited as CliWiring);\n // Top-level output goes through the first source: with a root source that is the one\n // carrying the shared commands, otherwise the first API listed.\n const top = wiringOf(sources[0]);\n const fail = (code: number, message: string): number => {\n top.stderr(JSON.stringify({ error: { code, message } }));\n return code;\n };\n const namespaced = sources.filter(\n (source): source is CommandSource & { namespace: string } => source.namespace !== undefined\n );\n const root = sources.find((source) => source.namespace === undefined);\n if (root !== undefined) {\n const clash = root.commands.find((command) =>\n namespaced.some((source) => source.namespace === command.name)\n );\n if (clash !== undefined) {\n return fail(\n 4,\n `Root command \"${clash.name}\" collides with the \"${clash.name}\" namespace — rename one of them.`\n );\n }\n }\n if (argv.length === 0 || argv[0] === '--help') {\n for (const line of renderComposedHelp(sources, top.name)) top.stdout(line);\n return 0;\n }\n const source = namespaced.find((candidate) => candidate.namespace === argv[0]);\n if (source !== undefined) return runSingle(source.commands, wiringOf(source), argv.slice(1));\n const rootTakes =\n root !== undefined &&\n (argv[0] === 'schema' ||\n root.commands.some(\n (command) =>\n command.name === argv[0] ||\n (command.group !== undefined && groupSlug(command.group) === argv[0])\n ));\n if (rootTakes)\n return runSingle((root as CommandSource).commands, wiringOf(root as CommandSource), argv);\n return fail(\n 4,\n `Unknown command: ${argv[0]} — expected an API namespace (${namespaced\n .map((candidate) => candidate.namespace)\n .join(', ')})${root !== undefined ? ' or a root command' : ''}`\n );\n}\n\n/** The composed top-level help: namespaces, root commands, and how to descend. */\nfunction renderComposedHelp(sources: CommandSource[], name: string): string[] {\n const lines = [`Usage: ${name} <api> <command> …`, '', 'APIs:'];\n for (const source of sources) {\n if (source.namespace !== undefined) lines.push(` ${source.namespace}`);\n }\n const root = sources.find((source) => source.namespace === undefined);\n if (root !== undefined && root.commands.length > 0) {\n lines.push('', 'Commands:');\n for (const command of root.commands) {\n lines.push(` ${command.name} ${oneLine(command.summary ?? '')}`.trimEnd());\n }\n }\n lines.push('', `Run ${name} <api> --help for that API's commands.`);\n return lines;\n}\n\nasync function runSingle(\n rawCommands: Array<CliCommand | CustomCommand>,\n wiring: CliWiring,\n argv: string[]\n): Promise<number> {\n const { stdout, stderr } = wiring;\n const commands = normalizeCommands(rawCommands);\n const shadowed = shadowedCommandName(commands);\n if (shadowed !== undefined) {\n stderr(\n JSON.stringify({\n error: {\n code: 4,\n message: `Custom command \"${shadowed}\" collides with another command of the same name — rename it.`,\n },\n })\n );\n return 4;\n }\n const fail = (code: number, error: Record<string, unknown>): number => {\n stderr(JSON.stringify({ error: { code, ...error } }));\n return code;\n };\n\n const invocation = parseInvocation(commands, argv);\n if (invocation.kind === 'usage-error') return fail(4, { message: invocation.message });\n if (invocation.kind === 'help') {\n for (const line of renderHelp(\n commands,\n wiring.name,\n wiring.schemes ?? [],\n wiring.envPrefix,\n invocation.topic\n ))\n stdout(line);\n return 0;\n }\n if (invocation.kind === 'schema') {\n stdout(JSON.stringify(commandContract(invocation.command), null, 2));\n return 0;\n }\n\n const { command, positionals, params, globals } = invocation;\n // A credential the user passed explicitly must never be dropped in silence: without a\n // bearer scheme the request would go out unauthenticated and come back 401, which reads\n // as \"my token is wrong\" rather than \"that flag does nothing here\".\n const schemes = wiring.schemes ?? [];\n if (globals.token !== undefined && !schemes.some((scheme) => scheme.kind === 'bearer')) {\n const declared = schemes.map((scheme) => `${scheme.key} (${scheme.kind})`).join(', ');\n return fail(4, {\n message:\n `--token is a bearer credential, and this API declares no bearer scheme. ` +\n (declared === '' ? 'It declares no security schemes at all.' : `It accepts: ${declared}.`),\n });\n }\n if (command.blob && globals.output === undefined) {\n return fail(4, {\n message: `${command.name} downloads a file: pass --output <path>`,\n operationId: command.name,\n });\n }\n let body: unknown;\n if (globals.json !== undefined) {\n try {\n body = loadBody(globals.json, wiring);\n } catch (error) {\n return fail(4, {\n message: `Invalid --json body: ${(error as Error).message}`,\n operationId: command.name,\n });\n }\n }\n\n const auth = resolveAuth(wiring, globals.token);\n if (Object.keys(auth).length > 0) wiring.configure({ auth });\n if (globals.serverUrl !== undefined) wiring.configure({ serverUrl: globals.serverUrl });\n\n // Redaction matches these against header VALUES — so basic auth must contribute the\n // form the wire actually carries (`Basic ${base64(user:pass)}`), not just the raw\n // password, which never appears in the encoded header.\n const basic = auth.basic as { username: string; password: string } | undefined;\n const secrets = [\n ...(typeof auth.bearer === 'string' ? [auth.bearer] : []),\n ...(basic ? [basic.password, btoa(`${basic.username}:${basic.password}`)] : []),\n ...Object.values((auth.apiKey as Record<string, string> | undefined) ?? {}),\n ];\n let captured: Record<string, unknown> | undefined;\n if (globals.dryRun) {\n wiring.configure({\n fetch: async (\n url: string,\n init: { method?: string; headers?: Record<string, string>; body?: unknown }\n ) => {\n captured = {\n url,\n method: init.method,\n headers: redactHeaders(init.headers ?? {}, secrets),\n ...(init.body !== undefined && init.body !== null ? { body: String(init.body) } : {}),\n };\n return new Response('{}', { status: 200, headers: { 'content-type': 'application/json' } });\n },\n });\n }\n\n const argument = callInputs(command, positionals, params, body, wiring.argsStyle);\n\n // The client's methods are typed per-operation; the dispatcher only needs \"callable\n // by name\", so one localized widening here keeps the emitted wiring cast-free.\n const methods = wiring.client as Record<string, unknown>;\n try {\n const resolved = command as ResolvedCommand;\n if (resolved.handler !== undefined) {\n return await resolved.handler({ positionals, params, globals, wiring });\n }\n if (globals.pageAll && !globals.dryRun) {\n const paginated = methods[command.name] as {\n pages: (variables?: unknown) => AsyncIterable<unknown>;\n };\n for await (const page of paginated.pages(argument)) stdout(JSON.stringify(page));\n return 0;\n }\n const method = methods[command.name] as (variables?: unknown) => Promise<unknown>;\n const result = await method(argument);\n if (globals.dryRun) {\n // An SSE method returns a lazy stream — drain the stubbed response so its fetch\n // actually runs and captures the request.\n if (command.sse) for await (const _event of result as AsyncIterable<unknown>);\n stdout(JSON.stringify(captured, null, 2));\n return 0;\n }\n if (command.sse) {\n for await (const event of result as AsyncIterable<unknown>) stdout(JSON.stringify(event));\n return 0;\n }\n if (command.blob) {\n const bytes = new Uint8Array(await (result as Blob).arrayBuffer());\n (wiring.writeFile ?? (() => {}))(globals.output as string, bytes);\n stdout(JSON.stringify({ saved: globals.output, bytes: bytes.length }));\n return 0;\n }\n if (result !== undefined && result !== null) stdout(JSON.stringify(result, null, 2));\n return 0;\n } catch (error) {\n const thrown = error as Error & { status?: number; issues?: unknown };\n const detail = {\n message: thrown.message,\n operationId: command.name,\n ...(thrown.status !== undefined ? { status: thrown.status } : {}),\n ...(thrown.issues !== undefined ? { issues: thrown.issues } : {}),\n };\n if (thrown.name === 'ZodValidationError') return fail(3, detail);\n if (thrown.name === 'ApiError' && (thrown.status === 401 || thrown.status === 403)) {\n return fail(2, detail);\n }\n return fail(1, detail);\n }\n}";
31
+ };
32
+ export type RuntimeModuleName = keyof typeof RUNTIME_SOURCES;
33
+ /** Top-level declared names of the runtime modules — precomputed so the pipeline
34
+ * builds the reserved-name set without the TypeScript parser. */
35
+ export declare const RUNTIME_DECLARED_NAMES: readonly ["ApiError", "ApiErrorLike", "AuthCredentials", "BodyMethod", "Capabilities", "CliAuthScheme", "CliCommand", "CliFlag", "CliGlobals", "CliInvocation", "CliWiring", "Client", "ClientConfig", "ClientCore", "CommandContext", "CommandSource", "CustomCommand", "Envelope", "EnvelopeResult", "EnvelopeResultForKnownInit", "FRAME_DELIMITER", "GLOBAL_FLAGS", "HeadersOf", "IDEMPOTENT_METHODS", "LAYERS", "LAYER_OF", "LinkPageCall", "Middleware", "NoRequiredKeys", "OperationArgs", "OperationContext", "OperationDescriptor", "OperationMethodIdentity", "OpsShape", "PageOf", "Paginated", "PaginationSpec", "ParamSpec", "ParseAs", "QueryStyle", "QueryValue", "RequestContext", "RequestOptions", "ResolvedCommand", "ResponseHeaderSpec", "Result", "RetryConfig", "RetryContext", "RetryStrategy", "SecuritySpec", "SendCapabilities", "ServerSentEvent", "SseOptions", "SseParseError", "TRANSIENT_STATUS", "ThrowMethod", "TimeoutError", "TokenProvider", "abortError", "acceptFor", "buildUrl", "callInputs", "coerceResponseHeader", "commandContract", "constantCase", "createClientCore", "defaultRetryOn", "encodeBase64", "encodeReserved", "execute", "groupSlug", "inputOf", "invokedName", "isConfigured", "items", "itemsByLink", "kindFor", "linkNext", "linkPageCall", "loadBody", "mergeSetup", "middlewareChain", "namespaceArgs", "normalizeCommands", "oneLine", "pageCall", "pages", "pagesByLink", "paginateCapability", "parse", "parseInvocation", "parseSseFrame", "prepareRequest", "queryStyles", "readEnvelopeHeaders", "readError", "redactHeaders", "renderComposedHelp", "renderHelp", "resolveAuth", "resolvePointer", "resolveToken", "retryDelay", "runCli", "runSingle", "runSources", "send", "shadowedCommandName", "sleep", "splitArgs", "sse", "stringHeaders", "substitutePath", "toFormData", "toHeaderRecord"];
36
+ //# sourceMappingURL=typescript.d.ts.map