@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.
- package/THIRD_PARTY_NOTICES +1 -1
- package/lib/chunks/{37WWN4MA.js → 2DKR4Z25.js} +1 -1
- package/lib/chunks/{S6NFOZ34.js → 2MCPXKB6.js} +1 -1
- package/lib/chunks/2ZIICV4T.js +7 -0
- package/lib/chunks/3CSDNX6S.js +20 -0
- package/lib/chunks/{KOR5TUJG.js → 4EAF6XUQ.js} +1 -1
- package/lib/chunks/5PR7FNUB.js +59 -0
- package/lib/chunks/{5X35UU2C.js → 654FMWX6.js} +1 -1
- package/lib/chunks/{4AM55DEH.js → 6FADWAOY.js} +1 -1
- package/lib/chunks/{3FXYWWTJ.js → 6MP4DHWK.js} +1 -1
- package/lib/chunks/{SLQPKXO2.js → 7APJVQSB.js} +1 -1
- package/lib/chunks/{KRCCY64H.js → AC62H63B.js} +1 -1
- package/lib/chunks/{ZCVUIIPU.js → ALBI5SPY.js} +1 -1
- package/lib/chunks/{SXBAAS4M.js → B5ZOH4BB.js} +1 -1
- package/lib/chunks/{V2YF33JL.js → BGGUKU7E.js} +1 -1
- package/lib/chunks/{6EGJEFXO.js → BWXAXULF.js} +1 -1
- package/lib/chunks/{3JYQOGV3.js → CEASD5U3.js} +1 -1
- package/lib/chunks/{TBZEEKTV.js → CVPT656W.js} +1 -1
- package/lib/chunks/{5PG3ST2N.js → DL4VP2Z4.js} +2 -2
- package/lib/chunks/DPNI74SB.js +69 -0
- package/lib/chunks/{GWXT3QO6.js → ENIPOAVJ.js} +2 -2
- package/lib/chunks/{22EANOP2.js → EQTXHL74.js} +101 -101
- package/lib/chunks/{3WHXM4DJ.js → FU7GBMUU.js} +1 -1
- package/lib/chunks/{JLVGRO3I.js → FVGFKJ67.js} +7 -7
- package/lib/chunks/{73RBZL53.js → FXKIC5R3.js} +1 -1
- package/lib/chunks/{2OBVZIAW.js → GKCVQWOQ.js} +3 -3
- package/lib/chunks/{MNH2K4WM.js → GKSUZULD.js} +1 -1
- package/lib/chunks/GOTKYHFM.js +20 -0
- package/lib/chunks/{2XK7ML3U.js → GRTUPOKN.js} +1 -1
- package/lib/chunks/{XJE2OFDN.js → HAOBCQWB.js} +358 -19
- package/lib/chunks/{IUSSBOYO.js → HIII45WJ.js} +1 -1
- package/lib/chunks/{2OVI7BIW.js → HYMULX2S.js} +1 -1
- package/lib/chunks/{FDMXHRDK.js → I6NMWBVI.js} +1 -1
- package/lib/chunks/{SHZZKTDE.js → IH3N62Q4.js} +1 -1
- package/lib/chunks/{V3ILUN2S.js → J665DSDI.js} +1 -1
- package/lib/chunks/JQJ5TDVP.js +11 -0
- package/lib/chunks/{HWTYAU4Y.js → L5X7LBV6.js} +1 -1
- package/lib/chunks/{VXONEXYX.js → LCAJ35J6.js} +1 -1
- package/lib/chunks/{US7X4XPD.js → LG3SUXEW.js} +1 -1
- package/lib/chunks/{YDVWNFMS.js → LMO5V2XF.js} +1 -1
- package/lib/chunks/{XFBJHVYU.js → M4NCSK7Q.js} +1 -1
- package/lib/chunks/{RPHIT72T.js → M74SGAZK.js} +1 -1
- package/lib/chunks/{M3XPS7UF.js → MVCGBDOY.js} +1 -1
- package/lib/chunks/{HYE4U6V5.js → N4R4236N.js} +1 -1
- package/lib/chunks/{6NB3FKVN.js → NRWIPPEV.js} +1 -1
- package/lib/chunks/{3C6UUCEW.js → OGNRVBTI.js} +1 -1
- package/lib/chunks/{6MH5ZWLD.js → ONBORCDJ.js} +1 -1
- package/lib/chunks/{ENSYM7KY.js → OS52EARM.js} +1 -1
- package/lib/chunks/{6DO6IN4A.js → QCOPS234.js} +1 -1
- package/lib/chunks/{CQKQB4PJ.js → QWTSCCNH.js} +1 -1
- package/lib/chunks/{4OEXH5EV.js → RRQJCLNO.js} +1 -1
- package/lib/chunks/{HBENVUR4.js → RWDQI2KD.js} +1 -1
- package/lib/chunks/{I5CGSLGR.js → S3W5GQGC.js} +1 -1
- package/lib/chunks/{HUWKDZPP.js → SRRHJLPL.js} +1 -1
- package/lib/chunks/{7WM6VLB3.js → TTE4EAVV.js} +3 -2
- package/lib/chunks/{LRLGVYCG.js → TWFZE4OF.js} +1 -1
- package/lib/chunks/{OP7NIAI7.js → U4RAHOFK.js} +1 -1
- package/lib/chunks/{36IHQANV.js → UC5EDLXL.js} +1 -1
- package/lib/chunks/{TKLMYBPI.js → ULQK2S4O.js} +1 -1
- package/lib/chunks/{FDGP3ZYZ.js → UNOXQ62I.js} +363 -156
- package/lib/chunks/{6WZUCZGV.js → UQW6CW3M.js} +1 -1
- package/lib/chunks/{DS4SCWID.js → VKTRI5YG.js} +1 -1
- package/lib/chunks/{PTNNX4IU.js → VMIHLA3H.js} +1 -1
- package/lib/chunks/{WX24BERR.js → VWF52WKH.js} +1 -1
- package/lib/chunks/WKQJPOYW.js +7 -0
- package/lib/chunks/{SYWAPWW7.js → WQBSNQXL.js} +1 -1
- package/lib/chunks/{CIDSJFJO.js → X6TQQHRM.js} +1 -1
- package/lib/chunks/{XJTGJYVS.js → XDBFHG7X.js} +1 -1
- package/lib/chunks/{LVGVWPDB.js → XYU2LTWU.js} +1 -1
- package/lib/chunks/{2OQM5ARW.js → Z4X6WES7.js} +1 -1
- package/lib/chunks/{3JYY42DL.js → ZFXOCOU6.js} +1 -1
- package/lib/chunks/{3DEJQSYG.js → ZM3M44Q3.js} +1 -1
- package/lib/eject-assets/generators/cli/docs.ts +1 -1
- package/lib/eject-assets/generators/cli/engine-source.ts +1 -1
- package/lib/eject-assets/generators/cli/index.ts +2 -2
- package/lib/eject-assets/generators/cli/render.ts +1 -1
- package/lib/eject-assets/generators/go/client.ts +1 -1
- package/lib/eject-assets/generators/go/descriptor.ts +2 -1
- package/lib/eject-assets/generators/go/index.ts +2 -2
- package/lib/eject-assets/generators/go/models.ts +1 -1
- package/lib/eject-assets/generators/go/naming.ts +1 -1
- package/lib/eject-assets/generators/go/operations.ts +1 -1
- package/lib/eject-assets/generators/go/pagination.ts +1 -1
- package/lib/eject-assets/generators/go/types.ts +1 -1
- package/lib/eject-assets/generators/mock/faker.ts +1 -1
- package/lib/eject-assets/generators/mock/index.ts +2 -2
- package/lib/eject-assets/generators/mock/render.ts +1 -1
- package/lib/eject-assets/generators/mock/sample.ts +1 -1
- package/lib/eject-assets/generators/mock/values.ts +1 -1
- package/lib/eject-assets/generators/php/client.ts +1 -1
- package/lib/eject-assets/generators/php/descriptor.ts +2 -1
- package/lib/eject-assets/generators/php/index.ts +2 -2
- package/lib/eject-assets/generators/php/models.ts +1 -1
- package/lib/eject-assets/generators/php/naming.ts +1 -1
- package/lib/eject-assets/generators/php/operations.ts +1 -1
- package/lib/eject-assets/generators/php/pagination.ts +1 -1
- package/lib/eject-assets/generators/php/types.ts +1 -1
- package/lib/eject-assets/generators/python/client.ts +1 -1
- package/lib/eject-assets/generators/python/descriptor.ts +2 -1
- package/lib/eject-assets/generators/python/index.ts +2 -2
- package/lib/eject-assets/generators/python/models.ts +1 -1
- package/lib/eject-assets/generators/python/naming.ts +1 -1
- package/lib/eject-assets/generators/python/operations.ts +1 -1
- package/lib/eject-assets/generators/python/pagination.ts +1 -1
- package/lib/eject-assets/generators/python/types.ts +1 -1
- package/lib/eject-assets/generators/swr/index.ts +2 -2
- package/lib/eject-assets/generators/swr/render.ts +1 -1
- package/lib/eject-assets/generators/tanstack-query/index.ts +2 -2
- package/lib/eject-assets/generators/tanstack-query/render.ts +56 -9
- package/lib/eject-assets/generators/transformers/index.ts +2 -2
- package/lib/eject-assets/generators/transformers/render.ts +1 -1
- package/lib/eject-assets/generators/typescript/banner.ts +1 -1
- package/lib/eject-assets/generators/typescript/client.ts +1 -1
- package/lib/eject-assets/generators/typescript/descriptor.ts +1 -1
- package/lib/eject-assets/generators/typescript/index.ts +29 -3
- package/lib/eject-assets/generators/typescript/inline-runtime.ts +1 -1
- package/lib/eject-assets/generators/typescript/operation-signature.ts +1 -1
- package/lib/eject-assets/generators/typescript/operation-types.ts +1 -1
- package/lib/eject-assets/generators/typescript/operations.ts +1 -1
- package/lib/eject-assets/generators/typescript/response-headers.ts +1 -1
- package/lib/eject-assets/generators/typescript/type-guards.ts +1 -1
- package/lib/eject-assets/generators/typescript/types.ts +1 -1
- package/lib/eject-assets/generators/zod/index.ts +2 -2
- package/lib/eject-assets/generators/zod/schemas.ts +1 -1
- package/lib/eject-assets/skills/tanstack-query-generator/SKILL.md +5 -2
- package/lib/eject-assets/skills/typescript-generator/SKILL.md +24 -0
- package/lib/index.js +5 -5
- package/package.json +1 -1
- package/lib/chunks/5SIJI5LC.js +0 -42
- package/lib/chunks/7WTV5KH3.js +0 -67
- package/lib/chunks/EDQ6QRIB.js +0 -20
- package/lib/chunks/LCFG5N4V.js +0 -7
- package/lib/chunks/PWGFZ5ZK.js +0 -7
- package/lib/chunks/RU7T4CKG.js +0 -11
- 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"./
|
|
8
|
-
- `));return
|
|
9
|
-
`)}let h=m(g.schema,t.items,n);if(h===void 0)return
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
: signal;
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
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,
|
|
1046
|
+
(await retryOn({ attempt, request: context, response }))
|
|
957
1047
|
) {
|
|
958
|
-
|
|
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
|
-
|
|
962
|
-
|
|
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
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
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
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
)
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
await
|
|
998
|
-
|
|
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
|
-
//
|
|
1828
|
-
// cannot carry \u2014 the client wires those operations to \`pagesByLink
|
|
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
|
|
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
|
|
1901
|
-
*
|
|
1902
|
-
*
|
|
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
|
|
1905
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
: signal;
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
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,
|
|
3871
|
+
(await retryOn({ attempt, request: context, response }))
|
|
3679
3872
|
) {
|
|
3680
|
-
|
|
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
|
-
|
|
3684
|
-
|
|
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
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
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
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
)
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
await
|
|
3720
|
-
|
|
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
|
-
//
|
|
4515
|
-
// cannot carry \u2014 the client wires those operations to \`pagesByLink
|
|
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
|
|
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
|
|
4588
|
-
*
|
|
4589
|
-
*
|
|
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
|
|
4592
|
-
*
|
|
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"],
|
|
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};
|