@redocly/cli 2.62.2 → 2.64.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 (135) hide show
  1. package/THIRD_PARTY_NOTICES +1 -1
  2. package/lib/chunks/{37WWN4MA.js → 2DKR4Z25.js} +1 -1
  3. package/lib/chunks/{S6NFOZ34.js → 2MCPXKB6.js} +1 -1
  4. package/lib/chunks/2ZIICV4T.js +7 -0
  5. package/lib/chunks/3CSDNX6S.js +20 -0
  6. package/lib/chunks/{KOR5TUJG.js → 4EAF6XUQ.js} +1 -1
  7. package/lib/chunks/5PR7FNUB.js +59 -0
  8. package/lib/chunks/{5X35UU2C.js → 654FMWX6.js} +1 -1
  9. package/lib/chunks/{4AM55DEH.js → 6FADWAOY.js} +1 -1
  10. package/lib/chunks/{3FXYWWTJ.js → 6MP4DHWK.js} +1 -1
  11. package/lib/chunks/{SLQPKXO2.js → 7APJVQSB.js} +1 -1
  12. package/lib/chunks/{KRCCY64H.js → AC62H63B.js} +1 -1
  13. package/lib/chunks/{ZCVUIIPU.js → ALBI5SPY.js} +1 -1
  14. package/lib/chunks/{SXBAAS4M.js → B5ZOH4BB.js} +1 -1
  15. package/lib/chunks/{V2YF33JL.js → BGGUKU7E.js} +1 -1
  16. package/lib/chunks/{6EGJEFXO.js → BWXAXULF.js} +1 -1
  17. package/lib/chunks/{3JYQOGV3.js → CEASD5U3.js} +1 -1
  18. package/lib/chunks/{TBZEEKTV.js → CVPT656W.js} +1 -1
  19. package/lib/chunks/{5PG3ST2N.js → DL4VP2Z4.js} +2 -2
  20. package/lib/chunks/DPNI74SB.js +69 -0
  21. package/lib/chunks/{GWXT3QO6.js → ENIPOAVJ.js} +2 -2
  22. package/lib/chunks/{22EANOP2.js → EQTXHL74.js} +101 -101
  23. package/lib/chunks/{3WHXM4DJ.js → FU7GBMUU.js} +1 -1
  24. package/lib/chunks/{JLVGRO3I.js → FVGFKJ67.js} +7 -7
  25. package/lib/chunks/{73RBZL53.js → FXKIC5R3.js} +1 -1
  26. package/lib/chunks/{2OBVZIAW.js → GKCVQWOQ.js} +3 -3
  27. package/lib/chunks/{MNH2K4WM.js → GKSUZULD.js} +1 -1
  28. package/lib/chunks/GOTKYHFM.js +20 -0
  29. package/lib/chunks/{2XK7ML3U.js → GRTUPOKN.js} +1 -1
  30. package/lib/chunks/{XJE2OFDN.js → HAOBCQWB.js} +358 -19
  31. package/lib/chunks/{IUSSBOYO.js → HIII45WJ.js} +1 -1
  32. package/lib/chunks/{2OVI7BIW.js → HYMULX2S.js} +1 -1
  33. package/lib/chunks/{FDMXHRDK.js → I6NMWBVI.js} +1 -1
  34. package/lib/chunks/{SHZZKTDE.js → IH3N62Q4.js} +1 -1
  35. package/lib/chunks/{V3ILUN2S.js → J665DSDI.js} +1 -1
  36. package/lib/chunks/JQJ5TDVP.js +11 -0
  37. package/lib/chunks/{HWTYAU4Y.js → L5X7LBV6.js} +1 -1
  38. package/lib/chunks/{VXONEXYX.js → LCAJ35J6.js} +1 -1
  39. package/lib/chunks/{US7X4XPD.js → LG3SUXEW.js} +1 -1
  40. package/lib/chunks/{YDVWNFMS.js → LMO5V2XF.js} +1 -1
  41. package/lib/chunks/{XFBJHVYU.js → M4NCSK7Q.js} +1 -1
  42. package/lib/chunks/{RPHIT72T.js → M74SGAZK.js} +1 -1
  43. package/lib/chunks/{M3XPS7UF.js → MVCGBDOY.js} +1 -1
  44. package/lib/chunks/{HYE4U6V5.js → N4R4236N.js} +1 -1
  45. package/lib/chunks/{6NB3FKVN.js → NRWIPPEV.js} +1 -1
  46. package/lib/chunks/{3C6UUCEW.js → OGNRVBTI.js} +1 -1
  47. package/lib/chunks/{6MH5ZWLD.js → ONBORCDJ.js} +1 -1
  48. package/lib/chunks/{ENSYM7KY.js → OS52EARM.js} +1 -1
  49. package/lib/chunks/{6DO6IN4A.js → QCOPS234.js} +1 -1
  50. package/lib/chunks/{CQKQB4PJ.js → QWTSCCNH.js} +1 -1
  51. package/lib/chunks/{4OEXH5EV.js → RRQJCLNO.js} +1 -1
  52. package/lib/chunks/{HBENVUR4.js → RWDQI2KD.js} +1 -1
  53. package/lib/chunks/{I5CGSLGR.js → S3W5GQGC.js} +1 -1
  54. package/lib/chunks/{HUWKDZPP.js → SRRHJLPL.js} +1 -1
  55. package/lib/chunks/{7WM6VLB3.js → TTE4EAVV.js} +3 -2
  56. package/lib/chunks/{LRLGVYCG.js → TWFZE4OF.js} +1 -1
  57. package/lib/chunks/{OP7NIAI7.js → U4RAHOFK.js} +1 -1
  58. package/lib/chunks/{36IHQANV.js → UC5EDLXL.js} +1 -1
  59. package/lib/chunks/{TKLMYBPI.js → ULQK2S4O.js} +1 -1
  60. package/lib/chunks/{FDGP3ZYZ.js → UNOXQ62I.js} +363 -156
  61. package/lib/chunks/{6WZUCZGV.js → UQW6CW3M.js} +1 -1
  62. package/lib/chunks/{DS4SCWID.js → VKTRI5YG.js} +1 -1
  63. package/lib/chunks/{PTNNX4IU.js → VMIHLA3H.js} +1 -1
  64. package/lib/chunks/{WX24BERR.js → VWF52WKH.js} +1 -1
  65. package/lib/chunks/WKQJPOYW.js +7 -0
  66. package/lib/chunks/{SYWAPWW7.js → WQBSNQXL.js} +1 -1
  67. package/lib/chunks/{CIDSJFJO.js → X6TQQHRM.js} +1 -1
  68. package/lib/chunks/{XJTGJYVS.js → XDBFHG7X.js} +1 -1
  69. package/lib/chunks/{LVGVWPDB.js → XYU2LTWU.js} +1 -1
  70. package/lib/chunks/{2OQM5ARW.js → Z4X6WES7.js} +1 -1
  71. package/lib/chunks/{3JYY42DL.js → ZFXOCOU6.js} +1 -1
  72. package/lib/chunks/{3DEJQSYG.js → ZM3M44Q3.js} +1 -1
  73. package/lib/eject-assets/generators/cli/docs.ts +1 -1
  74. package/lib/eject-assets/generators/cli/engine-source.ts +1 -1
  75. package/lib/eject-assets/generators/cli/index.ts +2 -2
  76. package/lib/eject-assets/generators/cli/render.ts +1 -1
  77. package/lib/eject-assets/generators/go/client.ts +1 -1
  78. package/lib/eject-assets/generators/go/descriptor.ts +2 -1
  79. package/lib/eject-assets/generators/go/index.ts +2 -2
  80. package/lib/eject-assets/generators/go/models.ts +1 -1
  81. package/lib/eject-assets/generators/go/naming.ts +1 -1
  82. package/lib/eject-assets/generators/go/operations.ts +1 -1
  83. package/lib/eject-assets/generators/go/pagination.ts +1 -1
  84. package/lib/eject-assets/generators/go/types.ts +1 -1
  85. package/lib/eject-assets/generators/mock/faker.ts +1 -1
  86. package/lib/eject-assets/generators/mock/index.ts +2 -2
  87. package/lib/eject-assets/generators/mock/render.ts +1 -1
  88. package/lib/eject-assets/generators/mock/sample.ts +1 -1
  89. package/lib/eject-assets/generators/mock/values.ts +1 -1
  90. package/lib/eject-assets/generators/php/client.ts +1 -1
  91. package/lib/eject-assets/generators/php/descriptor.ts +2 -1
  92. package/lib/eject-assets/generators/php/index.ts +2 -2
  93. package/lib/eject-assets/generators/php/models.ts +1 -1
  94. package/lib/eject-assets/generators/php/naming.ts +1 -1
  95. package/lib/eject-assets/generators/php/operations.ts +1 -1
  96. package/lib/eject-assets/generators/php/pagination.ts +1 -1
  97. package/lib/eject-assets/generators/php/types.ts +1 -1
  98. package/lib/eject-assets/generators/python/client.ts +1 -1
  99. package/lib/eject-assets/generators/python/descriptor.ts +2 -1
  100. package/lib/eject-assets/generators/python/index.ts +2 -2
  101. package/lib/eject-assets/generators/python/models.ts +1 -1
  102. package/lib/eject-assets/generators/python/naming.ts +1 -1
  103. package/lib/eject-assets/generators/python/operations.ts +1 -1
  104. package/lib/eject-assets/generators/python/pagination.ts +1 -1
  105. package/lib/eject-assets/generators/python/types.ts +1 -1
  106. package/lib/eject-assets/generators/swr/index.ts +2 -2
  107. package/lib/eject-assets/generators/swr/render.ts +1 -1
  108. package/lib/eject-assets/generators/tanstack-query/index.ts +2 -2
  109. package/lib/eject-assets/generators/tanstack-query/render.ts +56 -9
  110. package/lib/eject-assets/generators/transformers/index.ts +2 -2
  111. package/lib/eject-assets/generators/transformers/render.ts +1 -1
  112. package/lib/eject-assets/generators/typescript/banner.ts +1 -1
  113. package/lib/eject-assets/generators/typescript/client.ts +1 -1
  114. package/lib/eject-assets/generators/typescript/descriptor.ts +1 -1
  115. package/lib/eject-assets/generators/typescript/index.ts +29 -3
  116. package/lib/eject-assets/generators/typescript/inline-runtime.ts +1 -1
  117. package/lib/eject-assets/generators/typescript/operation-signature.ts +1 -1
  118. package/lib/eject-assets/generators/typescript/operation-types.ts +1 -1
  119. package/lib/eject-assets/generators/typescript/operations.ts +1 -1
  120. package/lib/eject-assets/generators/typescript/response-headers.ts +1 -1
  121. package/lib/eject-assets/generators/typescript/type-guards.ts +1 -1
  122. package/lib/eject-assets/generators/typescript/types.ts +1 -1
  123. package/lib/eject-assets/generators/zod/index.ts +2 -2
  124. package/lib/eject-assets/generators/zod/schemas.ts +1 -1
  125. package/lib/eject-assets/skills/tanstack-query-generator/SKILL.md +5 -2
  126. package/lib/eject-assets/skills/typescript-generator/SKILL.md +24 -0
  127. package/lib/index.js +5 -5
  128. package/package.json +1 -1
  129. package/lib/chunks/5SIJI5LC.js +0 -42
  130. package/lib/chunks/7WTV5KH3.js +0 -67
  131. package/lib/chunks/EDQ6QRIB.js +0 -20
  132. package/lib/chunks/LCFG5N4V.js +0 -7
  133. package/lib/chunks/PWGFZ5ZK.js +0 -7
  134. package/lib/chunks/RU7T4CKG.js +0 -11
  135. package/lib/chunks/Z6GHMX6Z.js +0 -20
@@ -4,9 +4,9 @@ import { dirname as __pathDirname } from 'node:path';
4
4
  const require = __createRequire(import.meta.url);
5
5
  var __filename = __fileURLToPath(import.meta.url);
6
6
  var __dirname = __pathDirname(__filename);
7
- import{c as x,ia as R}from"./22EANOP2.js";import{m}from"./UW4UC7ZI.js";function C(e){return e.flatMap(n=>n.operations)}function P(e,n,s){let r=e.specName??e.name;if(s?.exclude?.includes(r))return{};let a=s?.operations?.[r];if(a!==void 0)return y(e,n,a,`pagination.operations["${r}"]`,!0);if(e.paginationExtension!==void 0)return y(e,n,e.paginationExtension,"x-redoclyPagination",!0);if(s?.style!==void 0){let{exclude:o,operations:c,...t}=s;return y(e,n,t,"pagination convention",!1)}return{}}function H(e,n){let s=new Map,r=[];for(let a of C(e.services)){let o=P(a,e,n);o.error!==void 0?r.push(o.error):o.spec!==void 0&&s.set(a.name,{spec:o.spec,itemSchema:o.itemSchema})}if(r.length>0)throw new Error(["Invalid pagination configuration:",...r].join(`
8
- - `));return s}function y(e,n,s,r,a){let o=`Pagination for operation "${e.name}" (${r})`,c=q(s);if(c!==void 0)return{error:`${o}: ${c}`};let t=s,d=i=>a?{error:`${o}: ${i}`}:{};if(e.sse!==void 0)return d("the operation is a Server-Sent Events stream");if(t.style!=="link"){let i=t.style==="cursor"?"cursorParam":"offsetParam",l=t.style==="cursor"?t.cursorParam:t.offsetParam,k=e.queryParams.find(u=>u.name===l);if(!k){let u=e.queryParams.map(E=>E.name).join(", ");return d(`query parameter "${l}" is not declared on the operation`+(u===""?"":` (declared: ${u})`))}let p=O(k.schema,n);if(!(p!==void 0&&(t.style==="cursor"?b(p,n):p.kind==="scalar"&&(p.scalar==="number"||p.scalar==="integer")))){let u=t.style==="cursor"?"a string":"a number";return d(`the "${i}" query parameter "${l}" must accept ${u} (got ${I(p)})`)}}let g=e.successResponses.find(i=>i.contentType.toLowerCase().includes("json"));if(!g)return d("the operation has no JSON success response");if(t.style==="link"){let i=e.successResponseHeaders?.some(l=>l.name==="link")===!0;if(!i&&!a)return{};i||R.warn(`generate-client: ${o.toLowerCase()} follows the Link header, but the success response does not document one.
9
- `)}let h=m(g.schema,t.items,n);if(h===void 0)return d(`the "items" pointer "${t.items}" does not resolve in the success response schema`);if(h.kind!=="array")return d(`the "items" pointer "${t.items}" must point at an array (got ${h.kind})`);if(t.style==="cursor"){let i=m(g.schema,t.nextCursor,n);if(i===void 0)return d(`the "nextCursor" pointer "${t.nextCursor}" does not resolve in the success response schema`);if(!b(i,n))return d(`the "nextCursor" pointer "${t.nextCursor}" must point at a string (got ${i.kind})`);if(t.hasMore!==void 0){let l=m(g.schema,t.hasMore,n);if(l===void 0)return d(`the "hasMore" pointer "${t.hasMore}" does not resolve in the success response schema`);if(l.kind!=="scalar"||l.scalar!=="boolean")return d(`the "hasMore" pointer "${t.hasMore}" must point at a boolean (got ${l.kind})`)}}let f=t.limitParam!==void 0?{limitParam:t.limitParam}:{};return{spec:t.style==="cursor"?{style:"cursor",param:t.cursorParam,...f,nextCursor:t.nextCursor,...t.hasMore!==void 0?{hasMore:t.hasMore}:{},items:t.items}:t.style==="link"?{style:"link",...f,items:t.items}:{style:t.style,param:t.offsetParam,...f,items:t.items},itemSchema:h.items}}function q(e){if(!x(e))return"the rule must be an object";let{style:n,cursorParam:s,nextCursor:r,hasMore:a,offsetParam:o,limitParam:c,items:t}=e;if(n!=="cursor"&&n!=="offset"&&n!=="page"&&n!=="link")return`"style" must be one of "cursor" | "offset" | "page" | "link" (got ${JSON.stringify(n)})`;if(typeof t!="string"||t!==""&&!t.startsWith("/"))return'"items" must be a JSON pointer starting with "/" (or "" when the response body is the item array itself)';if(n==="cursor"){if(typeof s!="string"||s==="")return'cursor style requires a "cursorParam" query parameter name';if(typeof r!="string"||!r.startsWith("/"))return'cursor style requires a "nextCursor" JSON pointer starting with "/"';if(a!==void 0&&(typeof a!="string"||!a.startsWith("/")))return'"hasMore" must be a JSON pointer starting with "/"'}else if((n==="offset"||n==="page")&&(typeof o!="string"||o===""))return`${n} style requires an "offsetParam" query parameter name`;if(c!==void 0&&typeof c!="string")return'"limitParam" must be a query parameter name'}function I(e){return e===void 0?"an unresolvable ref":e.kind==="scalar"||e.kind==="enum"?e.scalar:e.kind}function O(e,n){let s=new Set,r=e;for(;r.kind==="ref";){let{name:a}=r;if(s.has(a))return;s.add(a);let o=n.schemas.find(c=>c.name===a);if(o===void 0)return;r=o.schema}return r}function b(e,n){return e.kind==="scalar"||e.kind==="enum"?e.scalar==="string":e.kind==="literal"?typeof e.value=="string":e.kind==="union"?e.members.every(s=>{let r=O(s,n);return r!==void 0&&(r.kind==="null"||r.kind!=="union"&&b(r,n))}):!1}var G={"types.ts":`/**
7
+ import{c as x,ia as R}from"./EQTXHL74.js";import{m}from"./UW4UC7ZI.js";function C(e){return e.flatMap(n=>n.operations)}function P(e,n,o){let r=e.specName??e.name;if(o?.exclude?.includes(r))return{};let a=o?.operations?.[r];if(a!==void 0)return b(e,n,a,`pagination.operations["${r}"]`,!0);if(e.paginationExtension!==void 0)return b(e,n,e.paginationExtension,"x-redoclyPagination",!0);if(o?.style!==void 0){let{exclude:s,operations:c,...t}=o;return b(e,n,t,"pagination convention",!1)}return{}}function H(e,n){let o=new Map,r=[];for(let a of C(e.services)){let s=P(a,e,n);s.error!==void 0?r.push(s.error):s.spec!==void 0&&o.set(a.name,{spec:s.spec,itemSchema:s.itemSchema})}if(r.length>0)throw new Error(["Invalid pagination configuration:",...r].join(`
8
+ - `));return o}function b(e,n,o,r,a){let s=`Pagination for operation "${e.name}" (${r})`,c=q(o);if(c!==void 0)return{error:`${s}: ${c}`};let t=o,i=d=>a?{error:`${s}: ${d}`}:{};if(e.sse!==void 0)return i("the operation is a Server-Sent Events stream");if(t.style!=="link"&&t.style!=="body-link"){let d=t.style==="cursor"?"cursorParam":"offsetParam",l=t.style==="cursor"?t.cursorParam:t.offsetParam,k=e.queryParams.find(u=>u.name===l);if(!k){let u=e.queryParams.map(T=>T.name).join(", ");return i(`query parameter "${l}" is not declared on the operation`+(u===""?"":` (declared: ${u})`))}let p=O(k.schema,n);if(!(p!==void 0&&(t.style==="cursor"?y(p,n):p.kind==="scalar"&&(p.scalar==="number"||p.scalar==="integer")))){let u=t.style==="cursor"?"a string":"a number";return i(`the "${d}" query parameter "${l}" must accept ${u} (got ${I(p)})`)}}let g=e.successResponses.find(d=>d.contentType.toLowerCase().includes("json"));if(!g)return i("the operation has no JSON success response");if(t.style==="link"){let d=e.successResponseHeaders?.some(l=>l.name==="link")===!0;if(!d&&!a)return{};d||R.warn(`generate-client: ${s.toLowerCase()} follows the Link header, but the success response does not document one.
9
+ `)}let h=m(g.schema,t.items,n);if(h===void 0)return i(`the "items" pointer "${t.items}" does not resolve in the success response schema`);if(h.kind!=="array")return i(`the "items" pointer "${t.items}" must point at an array (got ${h.kind})`);if(t.style==="body-link"){let d=m(g.schema,t.nextLink,n);if(d===void 0)return i(`the "nextLink" pointer "${t.nextLink}" does not resolve in the success response schema`);if(!y(d,n))return i(`the "nextLink" pointer "${t.nextLink}" must point at a string (got ${d.kind})`)}if(t.style==="cursor"){let d=m(g.schema,t.nextCursor,n);if(d===void 0)return i(`the "nextCursor" pointer "${t.nextCursor}" does not resolve in the success response schema`);if(!y(d,n))return i(`the "nextCursor" pointer "${t.nextCursor}" must point at a string (got ${d.kind})`);if(t.hasMore!==void 0){let l=m(g.schema,t.hasMore,n);if(l===void 0)return i(`the "hasMore" pointer "${t.hasMore}" does not resolve in the success response schema`);if(l.kind!=="scalar"||l.scalar!=="boolean")return i(`the "hasMore" pointer "${t.hasMore}" must point at a boolean (got ${l.kind})`)}}let f=t.limitParam!==void 0?{limitParam:t.limitParam}:{};return{spec:t.style==="cursor"?{style:"cursor",param:t.cursorParam,...f,nextCursor:t.nextCursor,...t.hasMore!==void 0?{hasMore:t.hasMore}:{},items:t.items}:t.style==="link"?{style:"link",...f,items:t.items}:t.style==="body-link"?{style:"body-link",...f,nextLink:t.nextLink,items:t.items}:{style:t.style,param:t.offsetParam,...f,items:t.items},itemSchema:h.items}}function q(e){if(!x(e))return"the rule must be an object";let{style:n,cursorParam:o,nextCursor:r,hasMore:a,offsetParam:s,nextLink:c,limitParam:t,items:i}=e;if(n!=="cursor"&&n!=="offset"&&n!=="page"&&n!=="link"&&n!=="body-link")return`"style" must be one of "cursor" | "offset" | "page" | "link" | "body-link" (got ${JSON.stringify(n)})`;if(typeof i!="string"||i!==""&&!i.startsWith("/"))return'"items" must be a JSON pointer starting with "/" (or "" when the response body is the item array itself)';if(n==="cursor"){if(typeof o!="string"||o==="")return'cursor style requires a "cursorParam" query parameter name';if(typeof r!="string"||!r.startsWith("/"))return'cursor style requires a "nextCursor" JSON pointer starting with "/"';if(a!==void 0&&(typeof a!="string"||!a.startsWith("/")))return'"hasMore" must be a JSON pointer starting with "/"'}else if(n==="offset"||n==="page"){if(typeof s!="string"||s==="")return`${n} style requires an "offsetParam" query parameter name`}else if(n==="body-link"&&(typeof c!="string"||!c.startsWith("/")))return'body-link style requires a "nextLink" JSON pointer starting with "/"';if(t!==void 0&&typeof t!="string")return'"limitParam" must be a query parameter name'}function I(e){return e===void 0?"an unresolvable ref":e.kind==="scalar"||e.kind==="enum"?e.scalar:e.kind}function O(e,n){let o=new Set,r=e;for(;r.kind==="ref";){let{name:a}=r;if(o.has(a))return;o.add(a);let s=n.schemas.find(c=>c.name===a);if(s===void 0)return;r=s.schema}return r}function y(e,n){return e.kind==="scalar"||e.kind==="enum"?e.scalar==="string":e.kind==="literal"?typeof e.value=="string":e.kind==="union"?e.members.every(o=>{let r=O(o,n);return r!==void 0&&(r.kind==="null"||r.kind!=="union"&&y(r,n))}):!1}var G={"types.ts":`/**
10
10
  * The public type surface of the client runtime \u2014 \`@redocly/client-generator\`'s
11
11
  * app-facing runtime module. Pure types, no runtime code (excluded from coverage).
12
12
  * The generator emits \`OPERATIONS\` literals typed
@@ -30,7 +30,7 @@ export type SecuritySpec =
30
30
 
31
31
  /**
32
32
  * How to auto-iterate a paginated operation (drives its \`.pages()\`/\`.items()\` members).
33
- * \`nextCursor\` and \`items\` are RFC 6901 JSON pointers into the page (response) value.
33
+ * \`nextCursor\`, \`nextLink\`, and \`items\` are RFC 6901 JSON pointers into the page (response) value.
34
34
  */
35
35
  export type PaginationSpec =
36
36
  | {
@@ -62,6 +62,16 @@ export type PaginationSpec =
62
62
  limitParam?: string;
63
63
  /** Pointer to the page's item array. */
64
64
  items: string;
65
+ }
66
+ | {
67
+ /** Follow the next page's URL at \`nextLink\` in the page; stop when it is absent. */
68
+ style: 'body-link';
69
+ /** Optional page-size query param (recorded for tooling; never set by the runtime). */
70
+ limitParam?: string;
71
+ /** Pointer to the next page's URL in the page. */
72
+ nextLink: string;
73
+ /** Pointer to the page's item array. */
74
+ items: string;
65
75
  };
66
76
 
67
77
  /** The frozen data contract between generated code and the runtime: one operation's wire shape. */
@@ -174,7 +184,14 @@ export type ApiErrorLike = globalThis.Error & {
174
184
  body: unknown;
175
185
  };
176
186
 
177
- /** One interceptor: any subset of the three hooks. */
187
+ export type SettledCall = {
188
+ response?: Response;
189
+ error?: unknown;
190
+ attempts: number;
191
+ durationMs: number;
192
+ };
193
+
194
+ /** One interceptor: any subset of the four hooks. */
178
195
  export type Middleware<Op extends OperationContext = OperationContext> = {
179
196
  onRequest?: (ctx: RequestContext<Op>) => void | Promise<void>;
180
197
  onResponse?: (
@@ -187,6 +204,7 @@ export type Middleware<Op extends OperationContext = OperationContext> = {
187
204
  error: ApiErrorLike,
188
205
  ctx: RequestContext<Op>
189
206
  ) => globalThis.Error | Promise<globalThis.Error>;
207
+ onSettled?: (ctx: RequestContext<Op>, call: SettledCall) => void | Promise<void>;
190
208
  };
191
209
 
192
210
  /** Client configuration: transport, defaults, retry policy, middleware, and credentials. */
@@ -773,6 +791,7 @@ import type {
773
791
  RequestContext,
774
792
  RequestOptions,
775
793
  RetryConfig,
794
+ SettledCall,
776
795
  } from './types.js';
777
796
 
778
797
  /**
@@ -813,13 +832,50 @@ export function middlewareChain(config: ClientConfig): Middleware[] {
813
832
  return [...single, ...(config.middleware ?? [])];
814
833
  }
815
834
 
835
+ /**
836
+ * \`AbortSignal.timeout()\` where the platform has it, else an \`AbortController\` armed by
837
+ * \`setTimeout\`: Chrome before 103, Safari before 16, and Firefox before 100 lack the
838
+ * static, and every request with a timeout would throw before \`fetch\` runs.
839
+ */
840
+ function timeoutSignal(ms: number): AbortSignal {
841
+ if (typeof AbortSignal.timeout === 'function') return AbortSignal.timeout(ms);
842
+ const controller = new AbortController();
843
+ setTimeout(() => {
844
+ controller.abort(new DOMException(\`Timed out after \${ms} ms\`, 'TimeoutError'));
845
+ }, ms);
846
+ return controller.signal;
847
+ }
848
+
849
+ /**
850
+ * \`AbortSignal.any()\` where the platform has it, else a controller that follows whichever
851
+ * signal aborts first: Chrome before 116, Safari before 17.4, and Firefox before 124 lack
852
+ * the static. The listeners come off once one signal fires, so a caller's long-lived
853
+ * signal does not keep one per request.
854
+ */
855
+ function anySignal(signals: AbortSignal[]): AbortSignal {
856
+ if (typeof AbortSignal.any === 'function') return AbortSignal.any(signals);
857
+ const controller = new AbortController();
858
+ const listeners = new Map<AbortSignal, () => void>();
859
+ const abort = (reason: unknown) => {
860
+ for (const [signal, listener] of listeners) signal.removeEventListener('abort', listener);
861
+ controller.abort(reason);
862
+ };
863
+ for (const signal of signals) {
864
+ const listener = () => abort(signal.reason);
865
+ listeners.set(signal, listener);
866
+ signal.addEventListener('abort', listener);
867
+ }
868
+ return controller.signal;
869
+ }
870
+
816
871
  /**
817
872
  * The fetch core shared by every operation: default + config + per-call headers, the
818
873
  * \`onRequest\` chain (BEFORE body serialization, so mutations are sent), body
819
874
  * serialization (JSON, or FormData via the multipart capability; a stream passes
820
875
  * through in one attempt), the retry loop
821
876
  * (idempotent-only defaults, \`Retry-After\`, abandoned-body drain), and the reverse
822
- * \`onResponse\` onion. Returns the final response plus the request context.
877
+ * \`onResponse\` onion, then the \`onSettled\` chain once with the final outcome. Returns the
878
+ * final response plus the request context.
823
879
  */
824
880
  export async function send(
825
881
  config: ClientConfig,
@@ -927,78 +983,117 @@ export async function send(
927
983
  const retryOn = retry.retryOn ?? defaultRetryOn;
928
984
  const signal = fetchInit.signal ?? undefined;
929
985
 
986
+ const startedAt = Date.now();
930
987
  let attempt = 0;
931
- while (true) {
932
- attempt++;
933
- if (signal?.aborted) throw abortError(signal);
934
- // A fresh timeout budget per attempt; the caller's signal still wins the race.
935
- // The composed signal also governs reading the response body.
936
- const attemptSignal = timeout
937
- ? signal
938
- ? AbortSignal.any([signal, AbortSignal.timeout(timeout)])
939
- : AbortSignal.timeout(timeout)
940
- : signal;
941
- let response: Response;
942
- try {
943
- response = await doFetch(context.url, {
944
- ...fetchInit,
945
- // \`fetch\` requires it for a stream body.
946
- ...(isStream ? { duplex: 'half' } : {}),
947
- signal: attemptSignal,
948
- method: context.method,
949
- headers: context.headers,
950
- body: payload,
951
- });
952
- } catch (error) {
988
+ let finalResponse: Response;
989
+ let lastResponse: Response | undefined;
990
+ try {
991
+ while (true) {
992
+ if (signal?.aborted) throw abortError(signal);
993
+ attempt++;
994
+ // A fresh timeout budget per attempt; the caller's signal still wins the race.
995
+ // The composed signal also governs reading the response body.
996
+ const budget = timeout ? timeoutSignal(timeout) : undefined;
997
+ const attemptSignal = budget && signal ? anySignal([signal, budget]) : (budget ?? signal);
998
+ let response: Response;
999
+ try {
1000
+ response = await doFetch(context.url, {
1001
+ ...fetchInit,
1002
+ // \`fetch\` requires it for a stream body.
1003
+ ...(isStream ? { duplex: 'half' } : {}),
1004
+ signal: attemptSignal,
1005
+ method: context.method,
1006
+ headers: context.headers,
1007
+ body: payload,
1008
+ });
1009
+ } catch (error) {
1010
+ // Decided before any await, when no timer can have fired since \`fetch\` rejected: an
1011
+ // aborted budget means this attempt timed out. The abort reason's name is no
1012
+ // evidence, because Chrome 103 to 123 abort a timeout with \`AbortError\`.
1013
+ const timedOut = timeout !== undefined && budget?.aborted && !signal?.aborted;
1014
+ if (
1015
+ attempt < maxAttempts &&
1016
+ !signal?.aborted &&
1017
+ (await retryOn({ attempt, request: context, error }))
1018
+ ) {
1019
+ await sleep(retryDelay(retry, attempt, null), signal);
1020
+ continue;
1021
+ }
1022
+ // Our timeout fired (never the caller's own abort \u2014 that rethrows untouched):
1023
+ // wrap the bare DOMException with the context a log line needs.
1024
+ if (timedOut) throw new TimeoutError(op.id, timeout, attempt);
1025
+ throw error;
1026
+ }
1027
+ lastResponse = response;
1028
+ // Reverse order: the last-registered middleware wraps closest to the network (onion).
1029
+ for (let i = middleware.length - 1; i >= 0; i--) {
1030
+ const onResponse = middleware[i].onResponse;
1031
+ if (onResponse) {
1032
+ const replaced = await onResponse(response, context);
1033
+ if (replaced && replaced !== response) {
1034
+ // Cancel the abandoned original's body \u2014 like the retry path, an unread body
1035
+ // keeps its connection checked out under Node/undici.
1036
+ await response.body?.cancel().catch(() => undefined);
1037
+ response = replaced;
1038
+ lastResponse = replaced;
1039
+ }
1040
+ }
1041
+ }
953
1042
  if (
1043
+ !response.ok &&
954
1044
  attempt < maxAttempts &&
955
1045
  !signal?.aborted &&
956
- (await retryOn({ attempt, request: context, error }))
1046
+ (await retryOn({ attempt, request: context, response }))
957
1047
  ) {
958
- await sleep(retryDelay(retry, attempt, null), signal);
1048
+ const retryAfter = response.headers.get('retry-after');
1049
+ // Drain the abandoned response body before the next attempt: an unread body
1050
+ // keeps the connection checked out (and can stall the pool) under Node/undici
1051
+ // and other strict HTTP clients. Ignore errors (e.g. a middleware already read it).
1052
+ await response.body?.cancel().catch(() => undefined);
1053
+ lastResponse = undefined;
1054
+ await sleep(retryDelay(retry, attempt, retryAfter), signal);
959
1055
  continue;
960
1056
  }
961
- // Our timeout fired (never the caller's own abort \u2014 that rethrows untouched):
962
- // wrap the bare DOMException with the context a log line needs.
963
- if (
964
- timeout &&
965
- !signal?.aborted &&
966
- error instanceof DOMException &&
967
- error.name === 'TimeoutError'
968
- ) {
969
- throw new TimeoutError(op.id, timeout, attempt);
970
- }
971
- throw error;
1057
+ finalResponse = response;
1058
+ break;
972
1059
  }
973
- // Reverse order: the last-registered middleware wraps closest to the network (onion).
974
- for (let i = middleware.length - 1; i >= 0; i--) {
975
- const onResponse = middleware[i].onResponse;
976
- if (onResponse) {
977
- const replaced = await onResponse(response, context);
978
- if (replaced && replaced !== response) {
979
- // Cancel the abandoned original's body \u2014 like the retry path, an unread body
980
- // keeps its connection checked out under Node/undici.
981
- await response.body?.cancel().catch(() => undefined);
982
- response = replaced;
983
- }
1060
+ } catch (error) {
1061
+ const settled: SettledCall = {
1062
+ response: lastResponse,
1063
+ error,
1064
+ attempts: attempt,
1065
+ durationMs: Date.now() - startedAt,
1066
+ };
1067
+ for (const mw of middleware) {
1068
+ if (!mw.onSettled) continue;
1069
+ try {
1070
+ await mw.onSettled(context, settled);
1071
+ } catch (hookError) {
1072
+ globalThis.console?.warn('onSettled hook failed', hookError);
984
1073
  }
985
1074
  }
986
- if (
987
- !response.ok &&
988
- attempt < maxAttempts &&
989
- !signal?.aborted &&
990
- (await retryOn({ attempt, request: context, response }))
991
- ) {
992
- const retryAfter = response.headers.get('retry-after');
993
- // Drain the abandoned response body before the next attempt: an unread body
994
- // keeps the connection checked out (and can stall the pool) under Node/undici
995
- // and other strict HTTP clients. Ignore errors (e.g. a middleware already read it).
996
- await response.body?.cancel().catch(() => undefined);
997
- await sleep(retryDelay(retry, attempt, retryAfter), signal);
998
- continue;
1075
+ throw error;
1076
+ }
1077
+ const settled: SettledCall = {
1078
+ response: finalResponse,
1079
+ attempts: attempt,
1080
+ durationMs: Date.now() - startedAt,
1081
+ };
1082
+ const hookErrors: unknown[] = [];
1083
+ for (const mw of middleware) {
1084
+ if (!mw.onSettled) continue;
1085
+ try {
1086
+ await mw.onSettled(context, settled);
1087
+ } catch (hookError) {
1088
+ hookErrors.push(hookError);
999
1089
  }
1000
- return { response, context };
1001
1090
  }
1091
+ if (hookErrors.length > 0) {
1092
+ // The caller never gets this response, so release its connection.
1093
+ await finalResponse.body?.cancel().catch(() => undefined);
1094
+ throw hookErrors[0];
1095
+ }
1096
+ return { response: finalResponse, context };
1002
1097
  }
1003
1098
  `,"sse.ts":`import { ApiError } from './errors.js';
1004
1099
  import { readError } from './parse.js';
@@ -1228,6 +1323,7 @@ export type Capabilities = SendCapabilities & {
1228
1323
  args?: OperationArgs,
1229
1324
  init?: RequestOptions
1230
1325
  ) => Promise<{ page: unknown; linkHeader: string | null; url: string }>,
1326
+ spec: PaginationSpec,
1231
1327
  args?: OperationArgs,
1232
1328
  init?: RequestOptions
1233
1329
  ) => AsyncGenerator<unknown>;
@@ -1675,11 +1771,12 @@ export function createClientCore<
1675
1771
  client[name] =
1676
1772
  spec === undefined
1677
1773
  ? method
1678
- : spec.style === 'link'
1774
+ : spec.style === 'link' || spec.style === 'body-link'
1679
1775
  ? Object.assign(method, {
1680
1776
  pages: (args?: OperationArgs, init?: RequestOptions) =>
1681
1777
  paginateCapability(caps, op).pagesByLink(
1682
1778
  linkPageCall(config, op, caps),
1779
+ spec,
1683
1780
  inputOf(op, args ?? {}, config),
1684
1781
  init
1685
1782
  ),
@@ -1823,9 +1920,9 @@ export async function* pages<TPage>(
1823
1920
  }
1824
1921
  cursor = next;
1825
1922
  }
1826
- } else if (spec.style === 'link') {
1827
- // \`link\` iteration needs the response's \`Link\` header, which the parsed-page \`call\`
1828
- // cannot carry \u2014 the client wires those operations to \`pagesByLink\` instead.
1923
+ } else if (spec.style === 'link' || spec.style === 'body-link') {
1924
+ // Link iteration needs the response's \`Link\` header and the page URL, which the
1925
+ // parsed-page \`call\` cannot carry \u2014 the client wires those operations to \`pagesByLink\`.
1829
1926
  throw new Error('link-style pagination iterates via pagesByLink');
1830
1927
  } else {
1831
1928
  // Coerce the starting position to a number: a caller may pass \`query[spec.param]\` as a
@@ -1874,7 +1971,7 @@ export async function* items<TItem>(
1874
1971
 
1875
1972
  /**
1876
1973
  * The per-page call the \`link\`-style iterators drive: the parsed page plus the raw
1877
- * \`Link\` header and the page's own URL (for resolving a relative \`next\` target).
1974
+ * \`Link\` header and the page's own URL (for resolving a relative next-page target).
1878
1975
  */
1879
1976
  export type LinkPageCall = (
1880
1977
  args?: OperationArgs,
@@ -1897,25 +1994,32 @@ export function linkNext(header: string | null): string | undefined {
1897
1994
  }
1898
1995
 
1899
1996
  /**
1900
- * Iterate a \`link\`-style operation's pages: follow the \`Link\` header's \`rel="next"\`
1901
- * target by merging ITS query params into the next call \u2014 every page goes through the
1902
- * same declared endpoint, so auth, middleware, and \`serverUrl\` handling apply
1997
+ * Iterate a \`link\`- or \`body-link\`-style operation's pages: follow the next page's URL \u2014
1998
+ * the \`Link\` header's \`rel="next"\` target (\`link\`), or the value at the \`nextLink\` pointer
1999
+ * (\`body-link\`) \u2014 by merging ITS query params into the next call. Every page goes through
2000
+ * the same declared endpoint, so auth, middleware, and \`serverUrl\` handling apply
1903
2001
  * unchanged, and credentials can never be handed to a cross-origin \`next\` URL.
1904
- * Stops when no \`rel="next"\` is present; throws when the link does not advance
1905
- * (the same target twice, or a self-link \u2014 an infinite-loop guard).
2002
+ * Stops when there is no next URL (\`body-link\`: \`undefined\`/\`null\`/\`''\`); throws when a
2003
+ * \`nextLink\` value is not a string, or when the link does not advance (the same target
2004
+ * twice, or a self-link \u2014 an infinite-loop guard).
1906
2005
  */
1907
2006
  export async function* pagesByLink<TPage>(
1908
2007
  call: LinkPageCall,
2008
+ spec: PaginationSpec,
1909
2009
  args: OperationArgs = {},
1910
2010
  init?: RequestOptions
1911
2011
  ): AsyncGenerator<TPage> {
2012
+ const pointer = spec.style === 'body-link' ? spec.nextLink : undefined;
1912
2013
  let query = args.query;
1913
2014
  let previous: string | undefined;
1914
2015
  while (true) {
1915
2016
  const { page, linkHeader, url } = await call({ ...args, query }, init);
1916
2017
  yield page as TPage;
1917
- const target = linkNext(linkHeader);
1918
- if (target === undefined) return;
2018
+ const target = pointer === undefined ? linkNext(linkHeader) : resolvePointer(page, pointer);
2019
+ if (target === undefined || target === null || target === '') return;
2020
+ if (typeof target !== 'string') {
2021
+ throw new Error(\`Pagination link at \${pointer} is not a string\`);
2022
+ }
1919
2023
  // A relative target resolves against the page's own URL (RFC 8288 \xA73.1) \u2014 which may
1920
2024
  // itself be relative (relative \`serverUrl\`, mocked fetch), so both resolve against a
1921
2025
  // placeholder origin. It never reaches the wire: only the target's query params
@@ -1946,7 +2050,7 @@ export async function* itemsByLink<TItem>(
1946
2050
  args?: OperationArgs,
1947
2051
  init?: RequestOptions
1948
2052
  ): AsyncGenerator<TItem> {
1949
- for await (const page of pagesByLink(call, args, init)) {
2053
+ for await (const page of pagesByLink(call, spec, args, init)) {
1950
2054
  const pageItems = resolvePointer(page, spec.items);
1951
2055
  if (Array.isArray(pageItems)) yield* pageItems as TItem[];
1952
2056
  }
@@ -2782,7 +2886,7 @@ export type SecuritySpec =
2782
2886
 
2783
2887
  /**
2784
2888
  * How to auto-iterate a paginated operation (drives its \`.pages()\`/\`.items()\` members).
2785
- * \`nextCursor\` and \`items\` are RFC 6901 JSON pointers into the page (response) value.
2889
+ * \`nextCursor\`, \`nextLink\`, and \`items\` are RFC 6901 JSON pointers into the page (response) value.
2786
2890
  */
2787
2891
  export type PaginationSpec =
2788
2892
  | {
@@ -2814,6 +2918,16 @@ export type PaginationSpec =
2814
2918
  limitParam?: string;
2815
2919
  /** Pointer to the page's item array. */
2816
2920
  items: string;
2921
+ }
2922
+ | {
2923
+ /** Follow the next page's URL at \`nextLink\` in the page; stop when it is absent. */
2924
+ style: 'body-link';
2925
+ /** Optional page-size query param (recorded for tooling; never set by the runtime). */
2926
+ limitParam?: string;
2927
+ /** Pointer to the next page's URL in the page. */
2928
+ nextLink: string;
2929
+ /** Pointer to the page's item array. */
2930
+ items: string;
2817
2931
  };
2818
2932
 
2819
2933
  /** The frozen data contract between generated code and the runtime: one operation's wire shape. */
@@ -2926,7 +3040,14 @@ export type ApiErrorLike = globalThis.Error & {
2926
3040
  body: unknown;
2927
3041
  };
2928
3042
 
2929
- /** One interceptor: any subset of the three hooks. */
3043
+ export type SettledCall = {
3044
+ response?: Response;
3045
+ error?: unknown;
3046
+ attempts: number;
3047
+ durationMs: number;
3048
+ };
3049
+
3050
+ /** One interceptor: any subset of the four hooks. */
2930
3051
  export type Middleware<Op extends OperationContext = OperationContext> = {
2931
3052
  onRequest?: (ctx: RequestContext<Op>) => void | Promise<void>;
2932
3053
  onResponse?: (
@@ -2939,6 +3060,7 @@ export type Middleware<Op extends OperationContext = OperationContext> = {
2939
3060
  error: ApiErrorLike,
2940
3061
  ctx: RequestContext<Op>
2941
3062
  ) => globalThis.Error | Promise<globalThis.Error>;
3063
+ onSettled?: (ctx: RequestContext<Op>, call: SettledCall) => void | Promise<void>;
2942
3064
  };
2943
3065
 
2944
3066
  /** Client configuration: transport, defaults, retry policy, middleware, and credentials. */
@@ -3535,13 +3657,50 @@ function middlewareChain(config: ClientConfig): Middleware[] {
3535
3657
  return [...single, ...(config.middleware ?? [])];
3536
3658
  }
3537
3659
 
3660
+ /**
3661
+ * \`AbortSignal.timeout()\` where the platform has it, else an \`AbortController\` armed by
3662
+ * \`setTimeout\`: Chrome before 103, Safari before 16, and Firefox before 100 lack the
3663
+ * static, and every request with a timeout would throw before \`fetch\` runs.
3664
+ */
3665
+ function timeoutSignal(ms: number): AbortSignal {
3666
+ if (typeof AbortSignal.timeout === 'function') return AbortSignal.timeout(ms);
3667
+ const controller = new AbortController();
3668
+ setTimeout(() => {
3669
+ controller.abort(new DOMException(\`Timed out after \${ms} ms\`, 'TimeoutError'));
3670
+ }, ms);
3671
+ return controller.signal;
3672
+ }
3673
+
3674
+ /**
3675
+ * \`AbortSignal.any()\` where the platform has it, else a controller that follows whichever
3676
+ * signal aborts first: Chrome before 116, Safari before 17.4, and Firefox before 124 lack
3677
+ * the static. The listeners come off once one signal fires, so a caller's long-lived
3678
+ * signal does not keep one per request.
3679
+ */
3680
+ function anySignal(signals: AbortSignal[]): AbortSignal {
3681
+ if (typeof AbortSignal.any === 'function') return AbortSignal.any(signals);
3682
+ const controller = new AbortController();
3683
+ const listeners = new Map<AbortSignal, () => void>();
3684
+ const abort = (reason: unknown) => {
3685
+ for (const [signal, listener] of listeners) signal.removeEventListener('abort', listener);
3686
+ controller.abort(reason);
3687
+ };
3688
+ for (const signal of signals) {
3689
+ const listener = () => abort(signal.reason);
3690
+ listeners.set(signal, listener);
3691
+ signal.addEventListener('abort', listener);
3692
+ }
3693
+ return controller.signal;
3694
+ }
3695
+
3538
3696
  /**
3539
3697
  * The fetch core shared by every operation: default + config + per-call headers, the
3540
3698
  * \`onRequest\` chain (BEFORE body serialization, so mutations are sent), body
3541
3699
  * serialization (JSON, or FormData via the multipart capability; a stream passes
3542
3700
  * through in one attempt), the retry loop
3543
3701
  * (idempotent-only defaults, \`Retry-After\`, abandoned-body drain), and the reverse
3544
- * \`onResponse\` onion. Returns the final response plus the request context.
3702
+ * \`onResponse\` onion, then the \`onSettled\` chain once with the final outcome. Returns the
3703
+ * final response plus the request context.
3545
3704
  */
3546
3705
  async function send(
3547
3706
  config: ClientConfig,
@@ -3649,78 +3808,117 @@ async function send(
3649
3808
  const retryOn = retry.retryOn ?? defaultRetryOn;
3650
3809
  const signal = fetchInit.signal ?? undefined;
3651
3810
 
3811
+ const startedAt = Date.now();
3652
3812
  let attempt = 0;
3653
- while (true) {
3654
- attempt++;
3655
- if (signal?.aborted) throw abortError(signal);
3656
- // A fresh timeout budget per attempt; the caller's signal still wins the race.
3657
- // The composed signal also governs reading the response body.
3658
- const attemptSignal = timeout
3659
- ? signal
3660
- ? AbortSignal.any([signal, AbortSignal.timeout(timeout)])
3661
- : AbortSignal.timeout(timeout)
3662
- : signal;
3663
- let response: Response;
3664
- try {
3665
- response = await doFetch(context.url, {
3666
- ...fetchInit,
3667
- // \`fetch\` requires it for a stream body.
3668
- ...(isStream ? { duplex: 'half' } : {}),
3669
- signal: attemptSignal,
3670
- method: context.method,
3671
- headers: context.headers,
3672
- body: payload,
3673
- });
3674
- } catch (error) {
3813
+ let finalResponse: Response;
3814
+ let lastResponse: Response | undefined;
3815
+ try {
3816
+ while (true) {
3817
+ if (signal?.aborted) throw abortError(signal);
3818
+ attempt++;
3819
+ // A fresh timeout budget per attempt; the caller's signal still wins the race.
3820
+ // The composed signal also governs reading the response body.
3821
+ const budget = timeout ? timeoutSignal(timeout) : undefined;
3822
+ const attemptSignal = budget && signal ? anySignal([signal, budget]) : (budget ?? signal);
3823
+ let response: Response;
3824
+ try {
3825
+ response = await doFetch(context.url, {
3826
+ ...fetchInit,
3827
+ // \`fetch\` requires it for a stream body.
3828
+ ...(isStream ? { duplex: 'half' } : {}),
3829
+ signal: attemptSignal,
3830
+ method: context.method,
3831
+ headers: context.headers,
3832
+ body: payload,
3833
+ });
3834
+ } catch (error) {
3835
+ // Decided before any await, when no timer can have fired since \`fetch\` rejected: an
3836
+ // aborted budget means this attempt timed out. The abort reason's name is no
3837
+ // evidence, because Chrome 103 to 123 abort a timeout with \`AbortError\`.
3838
+ const timedOut = timeout !== undefined && budget?.aborted && !signal?.aborted;
3839
+ if (
3840
+ attempt < maxAttempts &&
3841
+ !signal?.aborted &&
3842
+ (await retryOn({ attempt, request: context, error }))
3843
+ ) {
3844
+ await sleep(retryDelay(retry, attempt, null), signal);
3845
+ continue;
3846
+ }
3847
+ // Our timeout fired (never the caller's own abort \u2014 that rethrows untouched):
3848
+ // wrap the bare DOMException with the context a log line needs.
3849
+ if (timedOut) throw new TimeoutError(op.id, timeout, attempt);
3850
+ throw error;
3851
+ }
3852
+ lastResponse = response;
3853
+ // Reverse order: the last-registered middleware wraps closest to the network (onion).
3854
+ for (let i = middleware.length - 1; i >= 0; i--) {
3855
+ const onResponse = middleware[i].onResponse;
3856
+ if (onResponse) {
3857
+ const replaced = await onResponse(response, context);
3858
+ if (replaced && replaced !== response) {
3859
+ // Cancel the abandoned original's body \u2014 like the retry path, an unread body
3860
+ // keeps its connection checked out under Node/undici.
3861
+ await response.body?.cancel().catch(() => undefined);
3862
+ response = replaced;
3863
+ lastResponse = replaced;
3864
+ }
3865
+ }
3866
+ }
3675
3867
  if (
3868
+ !response.ok &&
3676
3869
  attempt < maxAttempts &&
3677
3870
  !signal?.aborted &&
3678
- (await retryOn({ attempt, request: context, error }))
3871
+ (await retryOn({ attempt, request: context, response }))
3679
3872
  ) {
3680
- await sleep(retryDelay(retry, attempt, null), signal);
3873
+ const retryAfter = response.headers.get('retry-after');
3874
+ // Drain the abandoned response body before the next attempt: an unread body
3875
+ // keeps the connection checked out (and can stall the pool) under Node/undici
3876
+ // and other strict HTTP clients. Ignore errors (e.g. a middleware already read it).
3877
+ await response.body?.cancel().catch(() => undefined);
3878
+ lastResponse = undefined;
3879
+ await sleep(retryDelay(retry, attempt, retryAfter), signal);
3681
3880
  continue;
3682
3881
  }
3683
- // Our timeout fired (never the caller's own abort \u2014 that rethrows untouched):
3684
- // wrap the bare DOMException with the context a log line needs.
3685
- if (
3686
- timeout &&
3687
- !signal?.aborted &&
3688
- error instanceof DOMException &&
3689
- error.name === 'TimeoutError'
3690
- ) {
3691
- throw new TimeoutError(op.id, timeout, attempt);
3692
- }
3693
- throw error;
3882
+ finalResponse = response;
3883
+ break;
3694
3884
  }
3695
- // Reverse order: the last-registered middleware wraps closest to the network (onion).
3696
- for (let i = middleware.length - 1; i >= 0; i--) {
3697
- const onResponse = middleware[i].onResponse;
3698
- if (onResponse) {
3699
- const replaced = await onResponse(response, context);
3700
- if (replaced && replaced !== response) {
3701
- // Cancel the abandoned original's body \u2014 like the retry path, an unread body
3702
- // keeps its connection checked out under Node/undici.
3703
- await response.body?.cancel().catch(() => undefined);
3704
- response = replaced;
3705
- }
3885
+ } catch (error) {
3886
+ const settled: SettledCall = {
3887
+ response: lastResponse,
3888
+ error,
3889
+ attempts: attempt,
3890
+ durationMs: Date.now() - startedAt,
3891
+ };
3892
+ for (const mw of middleware) {
3893
+ if (!mw.onSettled) continue;
3894
+ try {
3895
+ await mw.onSettled(context, settled);
3896
+ } catch (hookError) {
3897
+ globalThis.console?.warn('onSettled hook failed', hookError);
3706
3898
  }
3707
3899
  }
3708
- if (
3709
- !response.ok &&
3710
- attempt < maxAttempts &&
3711
- !signal?.aborted &&
3712
- (await retryOn({ attempt, request: context, response }))
3713
- ) {
3714
- const retryAfter = response.headers.get('retry-after');
3715
- // Drain the abandoned response body before the next attempt: an unread body
3716
- // keeps the connection checked out (and can stall the pool) under Node/undici
3717
- // and other strict HTTP clients. Ignore errors (e.g. a middleware already read it).
3718
- await response.body?.cancel().catch(() => undefined);
3719
- await sleep(retryDelay(retry, attempt, retryAfter), signal);
3720
- continue;
3900
+ throw error;
3901
+ }
3902
+ const settled: SettledCall = {
3903
+ response: finalResponse,
3904
+ attempts: attempt,
3905
+ durationMs: Date.now() - startedAt,
3906
+ };
3907
+ const hookErrors: unknown[] = [];
3908
+ for (const mw of middleware) {
3909
+ if (!mw.onSettled) continue;
3910
+ try {
3911
+ await mw.onSettled(context, settled);
3912
+ } catch (hookError) {
3913
+ hookErrors.push(hookError);
3721
3914
  }
3722
- return { response, context };
3723
3915
  }
3916
+ if (hookErrors.length > 0) {
3917
+ // The caller never gets this response, so release its connection.
3918
+ await finalResponse.body?.cancel().catch(() => undefined);
3919
+ throw hookErrors[0];
3920
+ }
3921
+ return { response: finalResponse, context };
3724
3922
  }`,"sse.ts":`/**
3725
3923
  * A frame delimiter: two consecutive line terminators (each CR, LF, or CRLF, per the SSE
3726
3924
  * spec \u2014 so mixed endings like \`\\n\\r\\n\` are valid boundaries, not just matching pairs).
@@ -3919,6 +4117,7 @@ type Capabilities = SendCapabilities & {
3919
4117
  args?: OperationArgs,
3920
4118
  init?: RequestOptions
3921
4119
  ) => Promise<{ page: unknown; linkHeader: string | null; url: string }>,
4120
+ spec: PaginationSpec,
3922
4121
  args?: OperationArgs,
3923
4122
  init?: RequestOptions
3924
4123
  ) => AsyncGenerator<unknown>;
@@ -4366,11 +4565,12 @@ function createClientCore<
4366
4565
  client[name] =
4367
4566
  spec === undefined
4368
4567
  ? method
4369
- : spec.style === 'link'
4568
+ : spec.style === 'link' || spec.style === 'body-link'
4370
4569
  ? Object.assign(method, {
4371
4570
  pages: (args?: OperationArgs, init?: RequestOptions) =>
4372
4571
  paginateCapability(caps, op).pagesByLink(
4373
4572
  linkPageCall(config, op, caps),
4573
+ spec,
4374
4574
  inputOf(op, args ?? {}, config),
4375
4575
  init
4376
4576
  ),
@@ -4510,9 +4710,9 @@ async function* pages<TPage>(
4510
4710
  }
4511
4711
  cursor = next;
4512
4712
  }
4513
- } else if (spec.style === 'link') {
4514
- // \`link\` iteration needs the response's \`Link\` header, which the parsed-page \`call\`
4515
- // cannot carry \u2014 the client wires those operations to \`pagesByLink\` instead.
4713
+ } else if (spec.style === 'link' || spec.style === 'body-link') {
4714
+ // Link iteration needs the response's \`Link\` header and the page URL, which the
4715
+ // parsed-page \`call\` cannot carry \u2014 the client wires those operations to \`pagesByLink\`.
4516
4716
  throw new Error('link-style pagination iterates via pagesByLink');
4517
4717
  } else {
4518
4718
  // Coerce the starting position to a number: a caller may pass \`query[spec.param]\` as a
@@ -4561,7 +4761,7 @@ async function* items<TItem>(
4561
4761
 
4562
4762
  /**
4563
4763
  * The per-page call the \`link\`-style iterators drive: the parsed page plus the raw
4564
- * \`Link\` header and the page's own URL (for resolving a relative \`next\` target).
4764
+ * \`Link\` header and the page's own URL (for resolving a relative next-page target).
4565
4765
  */
4566
4766
  type LinkPageCall = (
4567
4767
  args?: OperationArgs,
@@ -4584,25 +4784,32 @@ function linkNext(header: string | null): string | undefined {
4584
4784
  }
4585
4785
 
4586
4786
  /**
4587
- * Iterate a \`link\`-style operation's pages: follow the \`Link\` header's \`rel="next"\`
4588
- * target by merging ITS query params into the next call \u2014 every page goes through the
4589
- * same declared endpoint, so auth, middleware, and \`serverUrl\` handling apply
4787
+ * Iterate a \`link\`- or \`body-link\`-style operation's pages: follow the next page's URL \u2014
4788
+ * the \`Link\` header's \`rel="next"\` target (\`link\`), or the value at the \`nextLink\` pointer
4789
+ * (\`body-link\`) \u2014 by merging ITS query params into the next call. Every page goes through
4790
+ * the same declared endpoint, so auth, middleware, and \`serverUrl\` handling apply
4590
4791
  * unchanged, and credentials can never be handed to a cross-origin \`next\` URL.
4591
- * Stops when no \`rel="next"\` is present; throws when the link does not advance
4592
- * (the same target twice, or a self-link \u2014 an infinite-loop guard).
4792
+ * Stops when there is no next URL (\`body-link\`: \`undefined\`/\`null\`/\`''\`); throws when a
4793
+ * \`nextLink\` value is not a string, or when the link does not advance (the same target
4794
+ * twice, or a self-link \u2014 an infinite-loop guard).
4593
4795
  */
4594
4796
  async function* pagesByLink<TPage>(
4595
4797
  call: LinkPageCall,
4798
+ spec: PaginationSpec,
4596
4799
  args: OperationArgs = {},
4597
4800
  init?: RequestOptions
4598
4801
  ): AsyncGenerator<TPage> {
4802
+ const pointer = spec.style === 'body-link' ? spec.nextLink : undefined;
4599
4803
  let query = args.query;
4600
4804
  let previous: string | undefined;
4601
4805
  while (true) {
4602
4806
  const { page, linkHeader, url } = await call({ ...args, query }, init);
4603
4807
  yield page as TPage;
4604
- const target = linkNext(linkHeader);
4605
- if (target === undefined) return;
4808
+ const target = pointer === undefined ? linkNext(linkHeader) : resolvePointer(page, pointer);
4809
+ if (target === undefined || target === null || target === '') return;
4810
+ if (typeof target !== 'string') {
4811
+ throw new Error(\`Pagination link at \${pointer} is not a string\`);
4812
+ }
4606
4813
  // A relative target resolves against the page's own URL (RFC 8288 \xA73.1) \u2014 which may
4607
4814
  // itself be relative (relative \`serverUrl\`, mocked fetch), so both resolve against a
4608
4815
  // placeholder origin. It never reaches the wire: only the target's query params
@@ -4633,7 +4840,7 @@ async function* itemsByLink<TItem>(
4633
4840
  args?: OperationArgs,
4634
4841
  init?: RequestOptions
4635
4842
  ): AsyncGenerator<TItem> {
4636
- for await (const page of pagesByLink(call, args, init)) {
4843
+ for await (const page of pagesByLink(call, spec, args, init)) {
4637
4844
  const pageItems = resolvePointer(page, spec.items);
4638
4845
  if (Array.isArray(pageItems)) yield* pageItems as TItem[];
4639
4846
  }
@@ -5443,4 +5650,4 @@ async function runSingle(
5443
5650
  }
5444
5651
  return fail(1, detail);
5445
5652
  }
5446
- }`},S=["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"];var j=["client","OPERATIONS","Ops","OperationId","OperationPath","OperationTag","createClient","mergeSetup","ApiError","TimeoutError","configure","use","auth","defaultRetryOn","ClientConfig","RequestOptions","SseOptions","Middleware","OperationDescriptor","ServerSentEvent","Result","Envelope","EnvelopeResult","TokenProvider","__redoclySetup"],$=["z","http","HttpResponse","handlers","faker","queryOptions","useSWR","useSWRMutation","__Writable"],N=["AbortSignal","Array","ArrayBuffer","ArrayBufferView","AsyncGenerator","AsyncIterable","Blob","BodyInit","Boolean","DOMException","Date","Error","Extract","FormData","Headers","HeadersInit","JSON","Map","Math","NonNullable","Number","Object","Omit","Partial","Promise","ReadableStream","Record","RegExp","RegExpExecArray","Request","RequestInit","Response","Set","String","Symbol","TextDecoder","TextEncoder","TypeError","Uint8Array","URL","URLSearchParams","btoa","clearTimeout","crypto","decodeURIComponent","document","encodeURIComponent","fetch","globalThis","setTimeout","undefined"],w;function Q(){return w===void 0&&(w=new Set([...j,...$,...N,...S])),w}var A={name:"@redocly/client-generator",version:"0.5.3",description:"Generate typed, zero-dependency TypeScript clients (fetch, auth, retries, middleware, SSE) from OpenAPI descriptions.",type:"module",types:"lib/index.d.ts",sideEffects:!1,exports:{".":{types:"./lib/index.d.ts",import:"./lib/index.js",default:"./lib/index.js"},"./generate":{types:"./lib/generate.d.ts",import:"./lib/generate.js",default:"./lib/generate.js"},"./printers/python":{types:"./lib/printers/python.d.ts",import:"./lib/printers/python.js",default:"./lib/printers/python.js"},"./printers/go":{types:"./lib/printers/go.d.ts",import:"./lib/printers/go.js",default:"./lib/printers/go.js"},"./printers/php":{types:"./lib/printers/php.d.ts",import:"./lib/printers/php.js",default:"./lib/printers/php.js"},"./printers/typescript":{types:"./lib/printers/typescript.d.ts",import:"./lib/printers/typescript.js",default:"./lib/printers/typescript.js"},"./printers":{types:"./lib/printers/index.d.ts",import:"./lib/printers/index.js",default:"./lib/printers/index.js"},"./contracts/typescript":{types:"./lib/contracts/typescript.d.ts",import:"./lib/contracts/typescript.js",default:"./lib/contracts/typescript.js"},"./runtime-sources":{types:"./lib/runtime-sources.d.ts",import:"./lib/runtime-sources.js",default:"./lib/runtime-sources.js"},"./package.json":"./package.json"},engines:{node:">=22.12.0 || >=20.19.0 <21.0.0",npm:">=10"},engineStrict:!0,scripts:{"examples:regen":"node scripts/regenerate-examples.mjs",prepare:"node scripts/generate-runtime-sources.mjs && node scripts/generate-eject-assets.mjs","typecheck:examples":"node scripts/typecheck-examples.mjs"},license:"MIT",repository:{type:"git",url:"git+https://github.com/Redocly/redocly-cli.git"},homepage:"https://github.com/Redocly/redocly-cli",keywords:["OpenAPI","Swagger","TypeScript","client","generator","codegen","fetch"],contributors:["Roman Marshevskyi <roman.marshevskyy@redocly.com> (https://redocly.com/)"],dependencies:{"@redocly/openapi-core":"2.62.2"},peerDependencies:{typescript:">=5.5.0"},peerDependenciesMeta:{typescript:{optional:!0}},devDependencies:{typescript:"6.0.2"},files:["lib","eject-assets"]};var ee=A.version;function T(e){let n=/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(e.trim());return n===null?void 0:[Number(n[1]),Number(n[2]),Number(n[3])]}function v(e,n){return e[0]-n[0]||e[1]-n[1]||e[2]-n[2]}function ne(e,n){let s=/^[\^~]|^>=/.exec(n.trim())?.[0]??"",r=T(n.trim().slice(s.length)),a=T(e);if(!(r===void 0||a===void 0))return v(a,r)<0?!1:s===">="?!0:s===""?v(a,r)===0:s==="~"?a[0]===r[0]&&a[1]===r[1]:r[0]!==0?a[0]===r[0]:r[1]!==0?a[0]===0&&a[1]===r[1]:v(a,r)===0}export{C as a,H as b,G as c,W as d,j as e,Q as f,ee as g,ne as h};
5653
+ }`},S=["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","SettledCall","SseOptions","SseParseError","TRANSIENT_STATUS","ThrowMethod","TimeoutError","TokenProvider","abortError","acceptFor","anySignal","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","timeoutSignal","toFormData","toHeaderRecord"];var j=["client","OPERATIONS","Ops","OperationId","OperationPath","OperationTag","createClient","mergeSetup","ApiError","TimeoutError","configure","use","auth","defaultRetryOn","ClientConfig","RequestOptions","SseOptions","Middleware","OperationDescriptor","ServerSentEvent","Result","Envelope","EnvelopeResult","TokenProvider","__redoclySetup"],$=["z","http","HttpResponse","handlers","faker","queryOptions","useSWR","useSWRMutation","__Writable"],L=["AbortController","AbortSignal","Array","ArrayBuffer","ArrayBufferView","AsyncGenerator","AsyncIterable","Blob","BodyInit","Boolean","DOMException","Date","Error","Extract","FormData","Headers","HeadersInit","JSON","Map","Math","NonNullable","Number","Object","Omit","Partial","Promise","ReadableStream","Record","RegExp","RegExpExecArray","Request","RequestInit","Response","Set","String","Symbol","TextDecoder","TextEncoder","TypeError","Uint8Array","URL","URLSearchParams","btoa","clearTimeout","crypto","decodeURIComponent","document","encodeURIComponent","fetch","globalThis","setTimeout","undefined"],w;function Q(){return w===void 0&&(w=new Set([...j,...$,...L,...S])),w}var A={name:"@redocly/client-generator",version:"0.7.0",description:"Generate typed, zero-dependency TypeScript clients (fetch, auth, retries, middleware, SSE) from OpenAPI descriptions.",type:"module",types:"lib/index.d.ts",sideEffects:!1,exports:{".":{types:"./lib/index.d.ts",import:"./lib/index.js",default:"./lib/index.js"},"./generate":{types:"./lib/generate.d.ts",import:"./lib/generate.js",default:"./lib/generate.js"},"./printers/python":{types:"./lib/printers/python.d.ts",import:"./lib/printers/python.js",default:"./lib/printers/python.js"},"./printers/go":{types:"./lib/printers/go.d.ts",import:"./lib/printers/go.js",default:"./lib/printers/go.js"},"./printers/php":{types:"./lib/printers/php.d.ts",import:"./lib/printers/php.js",default:"./lib/printers/php.js"},"./printers/typescript":{types:"./lib/printers/typescript.d.ts",import:"./lib/printers/typescript.js",default:"./lib/printers/typescript.js"},"./printers":{types:"./lib/printers/index.d.ts",import:"./lib/printers/index.js",default:"./lib/printers/index.js"},"./contracts/typescript":{types:"./lib/contracts/typescript.d.ts",import:"./lib/contracts/typescript.js",default:"./lib/contracts/typescript.js"},"./runtime-sources":{types:"./lib/runtime-sources.d.ts",import:"./lib/runtime-sources.js",default:"./lib/runtime-sources.js"},"./package.json":"./package.json"},engines:{node:">=22.12.0 || >=20.19.0 <21.0.0",npm:">=10"},engineStrict:!0,scripts:{"examples:regen":"node scripts/regenerate-examples.mjs",prepare:"node scripts/generate-runtime-sources.mjs && node scripts/generate-eject-assets.mjs","typecheck:examples":"node scripts/typecheck-examples.mjs"},license:"MIT",repository:{type:"git",url:"git+https://github.com/Redocly/redocly-cli.git"},homepage:"https://github.com/Redocly/redocly-cli",keywords:["OpenAPI","Swagger","TypeScript","client","generator","codegen","fetch"],contributors:["Roman Marshevskyi <roman.marshevskyy@redocly.com> (https://redocly.com/)"],dependencies:{"@redocly/openapi-core":"2.64.0"},peerDependencies:{typescript:">=5.5.0"},peerDependenciesMeta:{typescript:{optional:!0}},devDependencies:{typescript:"6.0.2"},files:["lib","eject-assets"]};var ee=A.version;function E(e){let n=/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(e.trim());return n===null?void 0:[Number(n[1]),Number(n[2]),Number(n[3])]}function v(e,n){return e[0]-n[0]||e[1]-n[1]||e[2]-n[2]}function ne(e,n){let o=/^[\^~]|^>=/.exec(n.trim())?.[0]??"",r=E(n.trim().slice(o.length)),a=E(e);if(!(r===void 0||a===void 0))return v(a,r)<0?!1:o===">="?!0:o===""?v(a,r)===0:o==="~"?a[0]===r[0]&&a[1]===r[1]:r[0]!==0?a[0]===r[0]:r[1]!==0?a[0]===0&&a[1]===r[1]:v(a,r)===0}export{C as a,H as b,G as c,W as d,j as e,Q as f,ee as g,ne as h};