api-invoke 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1783 @@
1
+ import { ContentType, ParamLocation, AuthType, HeaderName, ApiInvokeError, ErrorKind, HttpMethod, parseError, authError, httpError, SpecFormat, graphqlError, timeoutError, corsError, API_INVOKE_ERROR_NAME, networkError } from './chunk-TXAOTXB4.js';
2
+ export { API_INVOKE_ERROR_NAME, ApiInvokeError, AuthType, ContentType, ErrorKind, HeaderName, HttpMethod, ParamLocation, SpecFormat, authError, corsError, graphqlError, httpError, networkError, parseError, parseGraphQLSchema, timeoutError } from './chunk-TXAOTXB4.js';
3
+ import SwaggerParser from '@apidevtools/swagger-parser';
4
+
5
+ // src/core/sse.ts
6
+ async function* parseSSE(body) {
7
+ const reader = body.getReader();
8
+ const decoder = new TextDecoder("utf-8", { fatal: true });
9
+ let buffer = "";
10
+ let eventType;
11
+ let dataLines = [];
12
+ let id;
13
+ let retry;
14
+ let hasData = false;
15
+ function buildEvent() {
16
+ const event = { data: dataLines.join("\n") };
17
+ if (eventType !== void 0) event.event = eventType;
18
+ if (id !== void 0) event.id = id;
19
+ if (retry !== void 0) event.retry = retry;
20
+ return event;
21
+ }
22
+ function resetAccumulator() {
23
+ eventType = void 0;
24
+ dataLines = [];
25
+ id = void 0;
26
+ retry = void 0;
27
+ hasData = false;
28
+ }
29
+ function processLine(line) {
30
+ if (line === "") {
31
+ if (hasData) {
32
+ const event = buildEvent();
33
+ resetAccumulator();
34
+ return event;
35
+ }
36
+ resetAccumulator();
37
+ return void 0;
38
+ }
39
+ if (line[0] === ":") return void 0;
40
+ const colonIdx = line.indexOf(":");
41
+ let field;
42
+ let value;
43
+ if (colonIdx === -1) {
44
+ field = line;
45
+ value = "";
46
+ } else {
47
+ field = line.slice(0, colonIdx);
48
+ value = line[colonIdx + 1] === " " ? line.slice(colonIdx + 2) : line.slice(colonIdx + 1);
49
+ }
50
+ switch (field) {
51
+ case "data":
52
+ dataLines.push(value);
53
+ hasData = true;
54
+ break;
55
+ case "event":
56
+ eventType = value;
57
+ break;
58
+ case "id":
59
+ if (!value.includes("\0")) id = value;
60
+ break;
61
+ case "retry": {
62
+ const n = parseInt(value, 10);
63
+ if (!isNaN(n) && n >= 0 && String(n) === value) retry = n;
64
+ break;
65
+ }
66
+ }
67
+ return void 0;
68
+ }
69
+ try {
70
+ while (true) {
71
+ const { done, value } = await reader.read();
72
+ if (done) break;
73
+ try {
74
+ buffer += decoder.decode(value, { stream: true });
75
+ } catch (decodeError) {
76
+ throw new Error("SSE stream contains invalid UTF-8 data", { cause: decodeError });
77
+ }
78
+ let startIdx = 0;
79
+ const limit = buffer.length - (buffer[buffer.length - 1] === "\r" ? 1 : 0);
80
+ for (let i = 0; i < limit; i++) {
81
+ if (buffer[i] === "\r" || buffer[i] === "\n") {
82
+ const line = buffer.slice(startIdx, i);
83
+ if (buffer[i] === "\r" && buffer[i + 1] === "\n") i++;
84
+ startIdx = i + 1;
85
+ const event = processLine(line);
86
+ if (event) yield event;
87
+ }
88
+ }
89
+ buffer = buffer.slice(startIdx);
90
+ }
91
+ try {
92
+ buffer += decoder.decode();
93
+ } catch (decodeError) {
94
+ throw new Error("SSE stream contains invalid UTF-8 data", { cause: decodeError });
95
+ }
96
+ if (buffer.length > 0) {
97
+ const event = processLine(buffer);
98
+ if (event) yield event;
99
+ }
100
+ if (hasData) yield buildEvent();
101
+ } finally {
102
+ reader.releaseLock();
103
+ }
104
+ }
105
+
106
+ // src/core/url-builder.ts
107
+ function buildUrl(baseUrl, operation, args) {
108
+ let path = operation.path;
109
+ for (const param of operation.parameters) {
110
+ if (param.in === ParamLocation.PATH && args[param.name] !== void 0) {
111
+ path = path.replace(
112
+ `{${param.name}}`,
113
+ encodeURIComponent(String(args[param.name]))
114
+ );
115
+ }
116
+ }
117
+ const fullBase = baseUrl.replace(/\/$/, "");
118
+ const fullPath = path.startsWith("/") ? path : `/${path}`;
119
+ const url = new URL(`${fullBase}${fullPath}`);
120
+ for (const param of operation.parameters) {
121
+ if (param.in === ParamLocation.QUERY) {
122
+ const value = args[param.name] ?? param.schema.default;
123
+ if (value !== void 0 && value !== null) {
124
+ serializeQueryParam(url, param.name, value);
125
+ }
126
+ }
127
+ }
128
+ return url.toString();
129
+ }
130
+ function deriveBaseUrl(specUrl) {
131
+ try {
132
+ const u = new URL(specUrl);
133
+ u.pathname = u.pathname.replace(/\/[^/]*$/, "");
134
+ const base = u.origin + u.pathname;
135
+ return base.replace(/\/$/, "");
136
+ } catch {
137
+ return "";
138
+ }
139
+ }
140
+ function extractHeaderParams(parameters, args) {
141
+ const headers = {};
142
+ for (const param of parameters) {
143
+ if (param.in === ParamLocation.HEADER && args[param.name] !== void 0) {
144
+ headers[param.name] = String(args[param.name]);
145
+ }
146
+ }
147
+ return headers;
148
+ }
149
+ function extractCookieParams(parameters, args) {
150
+ const cookies = [];
151
+ for (const param of parameters) {
152
+ if (param.in === ParamLocation.COOKIE) {
153
+ const value = args[param.name] ?? param.schema.default;
154
+ if (value !== void 0 && value !== null) {
155
+ cookies.push(`${encodeURIComponent(param.name)}=${encodeURIComponent(String(value))}`);
156
+ }
157
+ }
158
+ }
159
+ return cookies.length > 0 ? cookies.join("; ") : void 0;
160
+ }
161
+ function serializeQueryParam(url, name, value) {
162
+ if (Array.isArray(value)) {
163
+ url.searchParams.set(name, value.map(String).join(","));
164
+ } else if (typeof value === "object" && value !== null) {
165
+ const pairs = Object.entries(value).filter(([, v]) => v !== void 0 && v !== null).map(([k, v]) => {
166
+ if (typeof v === "object" && v !== null) {
167
+ throw new Error(
168
+ `Cannot serialize nested object for query parameter "${name}.${k}". Only flat key-value objects are supported.`
169
+ );
170
+ }
171
+ if (typeof v === "symbol" || typeof v === "function") {
172
+ throw new Error(
173
+ `Cannot serialize ${typeof v} value for query parameter "${name}.${k}". Use a string or number.`
174
+ );
175
+ }
176
+ return `${k},${String(v)}`;
177
+ });
178
+ url.searchParams.set(name, pairs.join(","));
179
+ } else {
180
+ url.searchParams.set(name, String(value));
181
+ }
182
+ }
183
+
184
+ // src/core/auth.ts
185
+ function injectAuth(url, headers, auth) {
186
+ if (Array.isArray(auth)) {
187
+ let result2 = { url, headers: { ...headers } };
188
+ for (const a of auth) {
189
+ result2 = injectAuth(result2.url, result2.headers, a);
190
+ }
191
+ return result2;
192
+ }
193
+ const result = { url, headers: { ...headers } };
194
+ switch (auth.type) {
195
+ case AuthType.BEARER:
196
+ result.headers[HeaderName.AUTHORIZATION] = `Bearer ${auth.token}`;
197
+ break;
198
+ case AuthType.BASIC: {
199
+ const encoded = btoa(`${auth.username}:${auth.password}`);
200
+ result.headers[HeaderName.AUTHORIZATION] = `Basic ${encoded}`;
201
+ break;
202
+ }
203
+ case AuthType.API_KEY:
204
+ if (auth.location === ParamLocation.HEADER) {
205
+ result.headers[auth.name] = auth.value;
206
+ } else if (auth.location === ParamLocation.QUERY) {
207
+ const u = new URL(url);
208
+ u.searchParams.set(auth.name, auth.value);
209
+ result.url = u.toString();
210
+ }
211
+ break;
212
+ case AuthType.OAUTH2:
213
+ result.headers[HeaderName.AUTHORIZATION] = `Bearer ${auth.accessToken}`;
214
+ break;
215
+ case AuthType.COOKIE: {
216
+ const existing = result.headers[HeaderName.COOKIE];
217
+ const cookie = `${encodeURIComponent(auth.name)}=${encodeURIComponent(auth.value)}`;
218
+ result.headers[HeaderName.COOKIE] = existing ? `${existing}; ${cookie}` : cookie;
219
+ break;
220
+ }
221
+ }
222
+ return result;
223
+ }
224
+ async function refreshOAuth2Token(tokenUrl, refreshToken, options) {
225
+ const fetchFn = options?.fetch ?? globalThis.fetch;
226
+ const body = new URLSearchParams({
227
+ grant_type: "refresh_token",
228
+ refresh_token: refreshToken
229
+ });
230
+ if (options?.clientId) body.set("client_id", options.clientId);
231
+ if (options?.clientSecret) body.set("client_secret", options.clientSecret);
232
+ if (options?.scopes?.length) body.set("scope", options.scopes.join(" "));
233
+ const response = await fetchFn(tokenUrl, {
234
+ method: "POST",
235
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
236
+ body: body.toString()
237
+ });
238
+ if (!response.ok) {
239
+ let errorDetail = "";
240
+ try {
241
+ const body2 = await response.text();
242
+ errorDetail = body2 ? `: ${body2.slice(0, 500)}` : "";
243
+ } catch (bodyReadError) {
244
+ errorDetail = ` (error body unreadable: ${bodyReadError instanceof Error ? bodyReadError.message : String(bodyReadError)})`;
245
+ }
246
+ throw new ApiInvokeError({
247
+ kind: ErrorKind.AUTH,
248
+ message: `OAuth2 token refresh failed: ${response.status} ${response.statusText}${errorDetail}`,
249
+ suggestion: "Check the refresh token, client credentials, and token endpoint URL.",
250
+ retryable: response.status >= 500,
251
+ status: response.status
252
+ });
253
+ }
254
+ let data;
255
+ try {
256
+ data = await response.json();
257
+ } catch (parseError2) {
258
+ throw new ApiInvokeError({
259
+ kind: ErrorKind.PARSE,
260
+ message: `OAuth2 token refresh succeeded (${response.status}) but response body is not valid JSON`,
261
+ suggestion: "The token endpoint returned a non-JSON response. Verify the endpoint URL.",
262
+ retryable: false
263
+ });
264
+ }
265
+ const accessToken = data.access_token;
266
+ if (typeof accessToken !== "string" || !accessToken) {
267
+ throw new ApiInvokeError({
268
+ kind: ErrorKind.AUTH,
269
+ message: `OAuth2 token refresh response missing required "access_token" field. Got keys: [${Object.keys(data).join(", ")}]`,
270
+ suggestion: "The token endpoint response did not include a valid access_token. Verify the endpoint and grant type.",
271
+ retryable: false
272
+ });
273
+ }
274
+ return {
275
+ accessToken,
276
+ refreshToken: typeof data.refresh_token === "string" && data.refresh_token ? data.refresh_token : void 0,
277
+ expiresIn: typeof data.expires_in === "number" ? data.expires_in : void 0
278
+ };
279
+ }
280
+ function maskAuth(auth) {
281
+ switch (auth.type) {
282
+ case AuthType.BEARER: {
283
+ if (auth.token.length <= 4) return "Bearer ***";
284
+ return `Bearer ${auth.token.substring(0, 4)}***`;
285
+ }
286
+ case AuthType.BASIC:
287
+ return `Basic ${auth.username}:***`;
288
+ case AuthType.API_KEY:
289
+ return `${auth.name}: ***`;
290
+ case AuthType.OAUTH2:
291
+ return `OAuth2 ***`;
292
+ case AuthType.COOKIE:
293
+ return `Cookie ${auth.name}=***`;
294
+ }
295
+ }
296
+
297
+ // src/core/executor.ts
298
+ var ABORT_ERROR_NAME = "AbortError";
299
+ var OPAQUE_RESPONSE_TYPE = "opaque";
300
+ var NO_CORS_MODE = "no-cors";
301
+ var JSON_SUFFIX = "+json";
302
+ var XML_SUBTYPE = "/xml";
303
+ var XML_SUFFIX = "+xml";
304
+ function buildRequest(baseUrl, operation, args, options = {}) {
305
+ const missing = operation.parameters.filter((p) => p.required && args[p.name] === void 0).map((p) => p.name);
306
+ if (missing.length > 0) {
307
+ throw new Error(
308
+ `Missing required parameter${missing.length > 1 ? "s" : ""}: ${missing.join(", ")} for operation "${operation.id}"`
309
+ );
310
+ }
311
+ let url = buildUrl(baseUrl, operation, args);
312
+ const method = operation.method.toUpperCase();
313
+ const accept = options.accept || operation.responseContentType || ContentType.JSON;
314
+ const headers = {
315
+ [HeaderName.ACCEPT]: accept,
316
+ ...extractHeaderParams(operation.parameters, args)
317
+ };
318
+ const cookieHeader = extractCookieParams(operation.parameters, args);
319
+ if (cookieHeader) {
320
+ headers[HeaderName.COOKIE] = cookieHeader;
321
+ }
322
+ let bodyData = args["body"];
323
+ const allowsBody = method !== HttpMethod.GET && method !== HttpMethod.HEAD && method !== HttpMethod.OPTIONS;
324
+ if (!bodyData && operation.buildBody && allowsBody) {
325
+ bodyData = operation.buildBody(args);
326
+ } else if (!bodyData && operation.requestBody && allowsBody) {
327
+ const bodyProps = operation.requestBody.schema.properties;
328
+ if (bodyProps) {
329
+ const assembled = {};
330
+ for (const propName of Object.keys(bodyProps)) {
331
+ if (args[propName] !== void 0) {
332
+ assembled[propName] = args[propName];
333
+ }
334
+ }
335
+ if (Object.keys(assembled).length > 0) {
336
+ bodyData = assembled;
337
+ }
338
+ }
339
+ }
340
+ let body;
341
+ if (bodyData && allowsBody) {
342
+ const contentType = operation.requestBody?.contentType ?? ContentType.JSON;
343
+ if (contentType === ContentType.FORM_URLENCODED) {
344
+ const params = new URLSearchParams();
345
+ const obj = typeof bodyData === "object" && bodyData !== null ? bodyData : {};
346
+ for (const [key, value] of Object.entries(obj)) {
347
+ if (value !== void 0 && value !== null) {
348
+ params.set(key, String(value));
349
+ }
350
+ }
351
+ body = params.toString();
352
+ headers[HeaderName.CONTENT_TYPE] = ContentType.FORM_URLENCODED;
353
+ } else if (contentType === ContentType.MULTIPART) {
354
+ if (typeof bodyData !== "object" || bodyData === null) {
355
+ throw new Error(
356
+ `Multipart/form-data body for operation "${operation.id}" must be an object, got ${typeof bodyData}`
357
+ );
358
+ }
359
+ const formData = new FormData();
360
+ const obj = bodyData;
361
+ for (const [key, value] of Object.entries(obj)) {
362
+ if (value === void 0 || value === null) continue;
363
+ if (value instanceof Blob) {
364
+ const filename = value instanceof File ? value.name : key;
365
+ formData.append(key, value, filename);
366
+ } else if (value instanceof ArrayBuffer) {
367
+ formData.append(key, new Blob([value]), key);
368
+ } else if (ArrayBuffer.isView(value)) {
369
+ formData.append(key, new Blob([new Uint8Array(value.buffer, value.byteOffset, value.byteLength)]), key);
370
+ } else {
371
+ formData.append(key, String(value));
372
+ }
373
+ }
374
+ body = formData;
375
+ } else {
376
+ body = typeof bodyData === "string" ? bodyData : JSON.stringify(bodyData);
377
+ headers[HeaderName.CONTENT_TYPE] = ContentType.JSON;
378
+ }
379
+ }
380
+ if (options.auth) {
381
+ const authed = injectAuth(url, headers, options.auth);
382
+ url = authed.url;
383
+ Object.assign(headers, authed.headers);
384
+ }
385
+ return { method, url, headers, body };
386
+ }
387
+ async function executeFetch(baseUrl, operation, args, options) {
388
+ const fetchFn = options.fetch ?? globalThis.fetch;
389
+ let { method, url, headers, body } = buildRequest(baseUrl, operation, args, {
390
+ auth: options.auth,
391
+ accept: options.accept
392
+ });
393
+ if (options.headers) {
394
+ Object.assign(headers, options.headers);
395
+ }
396
+ let signal = options.signal;
397
+ let timeoutId;
398
+ let abortHandler;
399
+ if (options.timeoutMs && options.timeoutMs > 0) {
400
+ const controller = new AbortController();
401
+ timeoutId = setTimeout(() => controller.abort(), options.timeoutMs);
402
+ if (options.signal) {
403
+ abortHandler = () => controller.abort();
404
+ options.signal.addEventListener("abort", abortHandler, { once: true });
405
+ }
406
+ signal = controller.signal;
407
+ }
408
+ let init = { method, headers, body, signal, redirect: options.redirect };
409
+ if (options.middleware) {
410
+ for (const mw of options.middleware) {
411
+ if (mw.onRequest) {
412
+ const result = await mw.onRequest(url, init);
413
+ url = result.url;
414
+ init = result.init;
415
+ }
416
+ }
417
+ }
418
+ const start = performance.now();
419
+ let response;
420
+ try {
421
+ response = await fetchFn(url, init);
422
+ } catch (error) {
423
+ if (timeoutId) clearTimeout(timeoutId);
424
+ if (abortHandler && options.signal) options.signal.removeEventListener("abort", abortHandler);
425
+ if (options.middleware) {
426
+ for (const mw of options.middleware) {
427
+ if (mw.onError) {
428
+ const normalized = error instanceof Error ? error : new Error(String(error));
429
+ try {
430
+ mw.onError(normalized);
431
+ } catch (mwError) {
432
+ console.warn(`[api-invoke] middleware "${mw.name ?? "unnamed"}" onError handler threw (suppressed):`, mwError);
433
+ }
434
+ }
435
+ }
436
+ }
437
+ if (error instanceof DOMException && error.name === ABORT_ERROR_NAME) {
438
+ if (options.timeoutMs && options.timeoutMs > 0) {
439
+ throw timeoutError(url);
440
+ }
441
+ throw error;
442
+ }
443
+ if (error instanceof TypeError) {
444
+ if (typeof window !== "undefined") {
445
+ try {
446
+ const probe = await fetchFn(url, { mode: NO_CORS_MODE });
447
+ if (probe.type === OPAQUE_RESPONSE_TYPE) throw corsError(url);
448
+ } catch (probeError) {
449
+ if (probeError instanceof Error && probeError.name === API_INVOKE_ERROR_NAME) throw probeError;
450
+ }
451
+ }
452
+ throw networkError(url);
453
+ }
454
+ throw networkError(url);
455
+ }
456
+ if (timeoutId) clearTimeout(timeoutId);
457
+ if (abortHandler && options.signal) options.signal.removeEventListener("abort", abortHandler);
458
+ const elapsedMs = Math.round(performance.now() - start);
459
+ if (options.middleware) {
460
+ for (const mw of options.middleware) {
461
+ if (mw.onResponse) {
462
+ response = await mw.onResponse(response);
463
+ }
464
+ }
465
+ }
466
+ const responseHeaders = {};
467
+ response.headers.forEach((value, key) => {
468
+ responseHeaders[key] = value;
469
+ });
470
+ return { response, request: { method, url, headers, body }, headers: responseHeaders, elapsedMs };
471
+ }
472
+ async function executeOperation(baseUrl, operation, args, options = {}) {
473
+ const { response, request, headers: responseHeaders, elapsedMs } = await executeFetch(baseUrl, operation, args, options);
474
+ const { method, url, headers, body } = request;
475
+ let data;
476
+ const contentType = response.headers.get(HeaderName.CONTENT_TYPE) || "";
477
+ if (contentType.includes(ContentType.JSON) || contentType.includes(JSON_SUFFIX)) {
478
+ const cloned = response.clone();
479
+ try {
480
+ data = await response.json();
481
+ } catch (jsonError) {
482
+ if (options.throwOnHttpError !== false) throw parseError(url);
483
+ console.warn("[api-invoke] JSON parse failed, falling back to text:", jsonError);
484
+ try {
485
+ data = await cloned.text();
486
+ } catch {
487
+ throw parseError(url);
488
+ }
489
+ }
490
+ } else if (isBinaryContentType(contentType)) {
491
+ try {
492
+ data = await response.arrayBuffer();
493
+ } catch {
494
+ throw parseError(url, "binary");
495
+ }
496
+ } else if (contentType.includes(XML_SUBTYPE) || contentType.includes(XML_SUFFIX)) {
497
+ try {
498
+ data = await response.text();
499
+ } catch {
500
+ throw parseError(url, "XML");
501
+ }
502
+ } else {
503
+ let text;
504
+ try {
505
+ text = await response.text();
506
+ } catch {
507
+ throw parseError(url, "text");
508
+ }
509
+ try {
510
+ data = JSON.parse(text);
511
+ } catch {
512
+ data = text;
513
+ }
514
+ }
515
+ const result = {
516
+ status: response.status,
517
+ data,
518
+ contentType,
519
+ headers: responseHeaders,
520
+ request: { method, url, headers, body },
521
+ elapsedMs
522
+ };
523
+ if (options.throwOnHttpError !== false) {
524
+ if (response.status === 401 || response.status === 403) {
525
+ throw authError(url, response.status, data);
526
+ }
527
+ if (!response.ok) {
528
+ throw httpError(url, response.status, response.statusText, data);
529
+ }
530
+ } else if (!response.ok) {
531
+ if (response.status === 401 || response.status === 403) {
532
+ result.errorKind = ErrorKind.AUTH;
533
+ } else if (response.status === 429) {
534
+ result.errorKind = ErrorKind.RATE_LIMIT;
535
+ } else {
536
+ result.errorKind = ErrorKind.HTTP;
537
+ }
538
+ }
539
+ return result;
540
+ }
541
+ async function executeRaw(url, options = {}) {
542
+ const operation = {
543
+ id: "raw",
544
+ path: "",
545
+ method: options.method ?? HttpMethod.GET,
546
+ parameters: [],
547
+ tags: []
548
+ };
549
+ return executeOperation(url, operation, { body: options.body }, {
550
+ auth: options.auth,
551
+ middleware: options.middleware,
552
+ fetch: options.fetch,
553
+ timeoutMs: options.timeoutMs,
554
+ signal: options.signal,
555
+ accept: options.accept,
556
+ redirect: options.redirect,
557
+ headers: options.headers
558
+ });
559
+ }
560
+ async function executeOperationStream(baseUrl, operation, args, options = {}) {
561
+ const streamOptions = {
562
+ ...options,
563
+ accept: options.accept ?? operation.responseContentType ?? ContentType.SSE
564
+ };
565
+ const { response, request, headers: responseHeaders, elapsedMs } = await executeFetch(baseUrl, operation, args, streamOptions);
566
+ if (!response.ok) {
567
+ let body;
568
+ try {
569
+ const text = await response.text();
570
+ try {
571
+ body = JSON.parse(text);
572
+ } catch {
573
+ body = text;
574
+ }
575
+ } catch (readError) {
576
+ body = `[api-invoke: failed to read error response body: ${readError instanceof Error ? readError.message : String(readError)}]`;
577
+ }
578
+ if (response.status === 401 || response.status === 403) {
579
+ throw authError(request.url, response.status, body);
580
+ }
581
+ throw httpError(request.url, response.status, response.statusText, body);
582
+ }
583
+ if (!response.body) {
584
+ throw parseError(request.url, "SSE (response body is null)");
585
+ }
586
+ const contentType = response.headers.get(HeaderName.CONTENT_TYPE) || "";
587
+ if (contentType && !contentType.includes("text/event-stream")) {
588
+ console.warn(`[api-invoke] Expected content-type text/event-stream but got "${contentType}" \u2014 SSE parsing may produce unexpected results`);
589
+ }
590
+ let stream = parseSSE(response.body);
591
+ if (options.onEvent) {
592
+ const inner = stream;
593
+ const onEvent = options.onEvent;
594
+ stream = (async function* () {
595
+ for await (const event of inner) {
596
+ try {
597
+ onEvent(event);
598
+ } catch (callbackError) {
599
+ throw new Error(
600
+ `onEvent callback threw for event "${event.event ?? "message"}": ${callbackError instanceof Error ? callbackError.message : String(callbackError)}`,
601
+ { cause: callbackError }
602
+ );
603
+ }
604
+ yield event;
605
+ }
606
+ })();
607
+ }
608
+ return {
609
+ status: response.status,
610
+ stream,
611
+ contentType,
612
+ headers: responseHeaders,
613
+ request,
614
+ elapsedMs
615
+ };
616
+ }
617
+ async function executeRawStream(url, options = {}) {
618
+ const operation = {
619
+ id: "raw-stream",
620
+ path: "",
621
+ method: options.method ?? HttpMethod.POST,
622
+ parameters: [],
623
+ tags: []
624
+ };
625
+ return executeOperationStream(url, operation, { body: options.body }, {
626
+ auth: options.auth,
627
+ middleware: options.middleware,
628
+ fetch: options.fetch,
629
+ timeoutMs: options.timeoutMs,
630
+ signal: options.signal,
631
+ accept: options.accept,
632
+ redirect: options.redirect,
633
+ headers: options.headers,
634
+ onEvent: options.onEvent
635
+ });
636
+ }
637
+ var BINARY_CONTENT_PATTERNS = [
638
+ "application/octet-stream",
639
+ "application/pdf",
640
+ "application/zip",
641
+ "audio/",
642
+ "image/",
643
+ "video/"
644
+ ];
645
+ function isBinaryContentType(contentType) {
646
+ return BINARY_CONTENT_PATTERNS.some((p) => contentType.includes(p));
647
+ }
648
+
649
+ // src/adapters/openapi/base-url.ts
650
+ function extractOpenAPI3BaseUrl(api, specUrl) {
651
+ const server = api.servers?.[0];
652
+ if (!server) return "";
653
+ let url = server.url;
654
+ if (server.variables) {
655
+ for (const [name, variable] of Object.entries(server.variables)) {
656
+ const value = variable.default ?? variable.enum?.[0];
657
+ if (value === void 0) return "";
658
+ url = url.replaceAll(`{${name}}`, String(value));
659
+ }
660
+ }
661
+ if (url && !url.startsWith("http")) {
662
+ if (!specUrl) return "";
663
+ try {
664
+ const resolved = new URL(url, specUrl);
665
+ return resolved.origin + resolved.pathname.replace(/\/$/, "");
666
+ } catch {
667
+ return "";
668
+ }
669
+ }
670
+ return url;
671
+ }
672
+ function extractSwagger2BaseUrl(api) {
673
+ const host = api.host;
674
+ if (!host) return "";
675
+ const scheme = api.schemes?.[0] ?? "https";
676
+ const basePath = api.basePath ?? "";
677
+ return `${scheme}://${host}${basePath}`;
678
+ }
679
+
680
+ // src/adapters/openapi/security.ts
681
+ function mapSecuritySchemes(schemes) {
682
+ const results = [];
683
+ for (const [name, scheme] of Object.entries(schemes)) {
684
+ results.push(mapSingleScheme(name, scheme));
685
+ }
686
+ return results;
687
+ }
688
+ function mapSingleScheme(name, scheme) {
689
+ const baseDescription = scheme.description || name;
690
+ if (scheme.type === "apiKey") {
691
+ const apiKeyScheme = scheme;
692
+ if (apiKeyScheme.in === ParamLocation.HEADER) {
693
+ return { name, authType: AuthType.API_KEY, metadata: { headerName: apiKeyScheme.name }, description: baseDescription };
694
+ }
695
+ if (apiKeyScheme.in === ParamLocation.QUERY) {
696
+ return { name, authType: AuthType.QUERY_PARAM, metadata: { paramName: apiKeyScheme.name }, description: baseDescription };
697
+ }
698
+ if (apiKeyScheme.in === ParamLocation.COOKIE) {
699
+ return { name, authType: AuthType.COOKIE, metadata: { cookieName: apiKeyScheme.name }, description: baseDescription };
700
+ }
701
+ return { name, authType: null, metadata: {}, description: `${baseDescription} (unsupported: apiKey in "${apiKeyScheme.in}")` };
702
+ }
703
+ if (scheme.type === "http") {
704
+ const httpScheme = scheme;
705
+ if (httpScheme.scheme === AuthType.BEARER) {
706
+ return { name, authType: AuthType.BEARER, metadata: {}, description: baseDescription };
707
+ }
708
+ if (httpScheme.scheme === AuthType.BASIC) {
709
+ return { name, authType: AuthType.BASIC, metadata: {}, description: baseDescription };
710
+ }
711
+ return { name, authType: null, metadata: {}, description: `${baseDescription} (unsupported: HTTP scheme "${httpScheme.scheme}")` };
712
+ }
713
+ if (scheme.type === "basic") {
714
+ return { name, authType: AuthType.BASIC, metadata: {}, description: baseDescription };
715
+ }
716
+ if (scheme.type === "oauth2") {
717
+ const metadata = {};
718
+ let flowResolved = false;
719
+ if ("flows" in scheme) {
720
+ const oauth3 = scheme;
721
+ const flow = oauth3.flows.authorizationCode ?? oauth3.flows.clientCredentials ?? oauth3.flows.implicit ?? oauth3.flows.password;
722
+ if (flow) {
723
+ flowResolved = true;
724
+ if ("authorizationUrl" in flow && flow.authorizationUrl) metadata.authorizationUrl = flow.authorizationUrl;
725
+ if ("tokenUrl" in flow && flow.tokenUrl) metadata.tokenUrl = flow.tokenUrl;
726
+ if (flow.refreshUrl) metadata.refreshUrl = flow.refreshUrl;
727
+ if (flow.scopes && Object.keys(flow.scopes).length > 0) {
728
+ metadata.scopes = Object.keys(flow.scopes).join(",");
729
+ }
730
+ }
731
+ }
732
+ if ("flow" in scheme) {
733
+ flowResolved = true;
734
+ const oauth2 = scheme;
735
+ if (typeof oauth2.authorizationUrl === "string") metadata.authorizationUrl = oauth2.authorizationUrl;
736
+ if (typeof oauth2.tokenUrl === "string") metadata.tokenUrl = oauth2.tokenUrl;
737
+ const scopes = oauth2.scopes;
738
+ if (scopes && Object.keys(scopes).length > 0) {
739
+ metadata.scopes = Object.keys(scopes).join(",");
740
+ }
741
+ }
742
+ const description = flowResolved ? baseDescription : `${baseDescription} (OAuth2 detected but no supported flow found)`;
743
+ return { name, authType: AuthType.OAUTH2, metadata, description };
744
+ }
745
+ if (scheme.type === "openIdConnect") {
746
+ return { name, authType: null, metadata: {}, description: `${baseDescription} (unsupported: OpenID Connect)` };
747
+ }
748
+ return { name, authType: null, metadata: {}, description: `${baseDescription} (unsupported: unknown scheme type)` };
749
+ }
750
+
751
+ // src/adapters/openapi/parser.ts
752
+ var SUPPORTED_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
753
+ function normalizeType(type, fallback = "string") {
754
+ if (Array.isArray(type)) {
755
+ const nonNull = type.filter((t) => t !== "null");
756
+ return nonNull[0] ?? fallback;
757
+ }
758
+ if (typeof type === "string") return type;
759
+ return fallback;
760
+ }
761
+ async function parseOpenAPISpec(specUrlOrObject, options) {
762
+ try {
763
+ let apiRaw;
764
+ try {
765
+ apiRaw = await SwaggerParser.dereference(specUrlOrObject);
766
+ } catch {
767
+ apiRaw = await SwaggerParser.parse(specUrlOrObject);
768
+ }
769
+ const api = apiRaw;
770
+ const isOpenAPI3 = "openapi" in api;
771
+ const specVersion = isOpenAPI3 ? api.openapi : api.swagger;
772
+ const title = api.info.title;
773
+ const version = api.info.version;
774
+ const sourceUrl = options?.specUrl ?? (typeof specUrlOrObject === "string" ? specUrlOrObject : void 0);
775
+ let baseUrl = isOpenAPI3 ? extractOpenAPI3BaseUrl(api, sourceUrl) : extractSwagger2BaseUrl(api);
776
+ if (!baseUrl && sourceUrl) {
777
+ baseUrl = deriveBaseUrl(sourceUrl);
778
+ }
779
+ const operations = extractOperations(api, isOpenAPI3);
780
+ if (baseUrl) {
781
+ try {
782
+ const basePath = new URL(baseUrl).pathname.replace(/\/$/, "");
783
+ if (basePath && basePath !== "/") {
784
+ const allOverlap = operations.length > 0 && operations.every(
785
+ (op) => op.path === basePath || op.path.startsWith(basePath + "/")
786
+ );
787
+ if (allOverlap) {
788
+ for (const op of operations) {
789
+ op.path = op.path.slice(basePath.length) || "/";
790
+ }
791
+ }
792
+ }
793
+ } catch {
794
+ }
795
+ }
796
+ const authSchemes = extractSecuritySchemes(api, isOpenAPI3);
797
+ return {
798
+ title,
799
+ version,
800
+ baseUrl,
801
+ operations,
802
+ authSchemes,
803
+ specFormat: isOpenAPI3 ? SpecFormat.OPENAPI_3 : SpecFormat.OPENAPI_2,
804
+ rawSpecVersion: specVersion
805
+ };
806
+ } catch (error) {
807
+ const message = error instanceof Error ? error.message : "Unknown error";
808
+ const urlInfo = typeof specUrlOrObject === "string" ? ` from ${specUrlOrObject}` : "";
809
+ throw new Error(`Failed to parse OpenAPI spec${urlInfo}: ${message}`, { cause: error });
810
+ }
811
+ }
812
+ function extractOperations(api, isOpenAPI3) {
813
+ const operations = [];
814
+ if (!api.paths) return operations;
815
+ for (const [path, pathItem] of Object.entries(api.paths)) {
816
+ if (!pathItem) continue;
817
+ const pathLevelParams = "parameters" in pathItem ? pathItem.parameters ?? [] : [];
818
+ for (const method of SUPPORTED_METHODS) {
819
+ if (!(method in pathItem) || !pathItem[method]) continue;
820
+ const op = pathItem[method];
821
+ const operationParams = op.parameters ?? [];
822
+ const allParams = [...pathLevelParams, ...operationParams].filter(
823
+ (p) => p.in !== "body"
824
+ // 'body' is a Swagger 2.0-specific location, not in ParamLocation
825
+ );
826
+ const parameters = allParams.map(
827
+ (param) => parseParameter(param, isOpenAPI3)
828
+ );
829
+ const requestBody = extractRequestBody(op, isOpenAPI3);
830
+ const { primary: responseSchema, all: responseSchemas } = extractResponseSchemas(op, isOpenAPI3);
831
+ const responseContentType = extractResponseContentType(op, isOpenAPI3);
832
+ const errorHints = extractErrorHints(op);
833
+ const id = op.operationId ?? `${method}_${path.replace(/[{}\/]/g, "_").replace(/^_|_$/g, "").replace(/_+/g, "_")}`;
834
+ operations.push({
835
+ id,
836
+ path,
837
+ method: method.toUpperCase(),
838
+ summary: op.summary,
839
+ description: op.description,
840
+ parameters,
841
+ requestBody,
842
+ responseSchema,
843
+ responseSchemas: Object.keys(responseSchemas).length > 0 ? responseSchemas : void 0,
844
+ responseContentType,
845
+ errorHints,
846
+ tags: op.tags ?? []
847
+ });
848
+ }
849
+ }
850
+ return operations;
851
+ }
852
+ function parseParameter(param, isOpenAPI3) {
853
+ const name = param.name;
854
+ const location = param.in;
855
+ const required = param.required ?? location === ParamLocation.PATH;
856
+ const description = param.description ?? "";
857
+ let schema;
858
+ if (isOpenAPI3) {
859
+ const p = param;
860
+ const s = p.schema;
861
+ schema = {
862
+ type: normalizeType(s?.type),
863
+ format: s?.format,
864
+ enum: s?.enum,
865
+ default: s?.default,
866
+ example: p.example ?? s?.example,
867
+ minimum: s?.minimum,
868
+ maximum: s?.maximum,
869
+ maxLength: s?.maxLength
870
+ // TODO: extract s?.items for array parameters (ParameterSchema.items)
871
+ };
872
+ } else {
873
+ const p = param;
874
+ schema = {
875
+ type: ("type" in p ? p.type : void 0) ?? "string",
876
+ format: "format" in p ? p.format : void 0,
877
+ enum: "enum" in p ? p.enum : void 0,
878
+ default: "default" in p ? p.default : void 0,
879
+ example: "x-example" in p ? p["x-example"] : void 0,
880
+ minimum: "minimum" in p ? p.minimum : void 0,
881
+ maximum: "maximum" in p ? p.maximum : void 0,
882
+ maxLength: "maxLength" in p ? p.maxLength : void 0
883
+ // TODO: extract p.items for array parameters (ParameterSchema.items)
884
+ };
885
+ }
886
+ return { name, in: location, required, description, schema };
887
+ }
888
+ var CONTENT_TYPE_PRIORITY = [
889
+ ContentType.JSON,
890
+ ContentType.FORM_URLENCODED,
891
+ ContentType.MULTIPART,
892
+ ContentType.XML,
893
+ ContentType.TEXT,
894
+ ContentType.OCTET_STREAM
895
+ ];
896
+ function extractRequestBody(operation, isOpenAPI3) {
897
+ if (isOpenAPI3) {
898
+ const op = operation;
899
+ if (!op.requestBody) return void 0;
900
+ const body = op.requestBody;
901
+ if (!body.content) return void 0;
902
+ for (const ct of CONTENT_TYPE_PRIORITY) {
903
+ const mediaType = body.content[ct];
904
+ if (mediaType?.schema) {
905
+ return {
906
+ required: body.required ?? false,
907
+ description: body.description,
908
+ contentType: ct,
909
+ schema: flattenSchema(mediaType.schema)
910
+ };
911
+ }
912
+ }
913
+ const firstKey = Object.keys(body.content)[0];
914
+ if (firstKey) {
915
+ const mediaType = body.content[firstKey];
916
+ if (mediaType?.schema) {
917
+ return {
918
+ required: body.required ?? false,
919
+ description: body.description,
920
+ contentType: firstKey,
921
+ schema: flattenSchema(mediaType.schema)
922
+ };
923
+ }
924
+ }
925
+ return void 0;
926
+ } else {
927
+ const op = operation;
928
+ const bodyParam = op.parameters?.find(
929
+ (p) => p.in === "body"
930
+ );
931
+ if (!bodyParam?.schema) return void 0;
932
+ const consumes = op.consumes ?? [];
933
+ let contentType = ContentType.JSON;
934
+ if (consumes.includes(ContentType.FORM_URLENCODED)) {
935
+ contentType = ContentType.FORM_URLENCODED;
936
+ } else if (consumes.includes(ContentType.MULTIPART)) {
937
+ contentType = ContentType.MULTIPART;
938
+ } else if (consumes.length > 0) {
939
+ contentType = consumes[0];
940
+ }
941
+ return {
942
+ required: bodyParam.required ?? false,
943
+ description: bodyParam.description,
944
+ contentType,
945
+ schema: flattenSchema(bodyParam.schema)
946
+ };
947
+ }
948
+ }
949
+ function flattenSchema(schema) {
950
+ const normalizedType = normalizeType(schema.type, "object");
951
+ const result = {
952
+ type: normalizedType,
953
+ raw: schema
954
+ };
955
+ if (normalizedType === "object" && schema.properties) {
956
+ result.properties = {};
957
+ result.required = schema.required;
958
+ for (const [name, propSchema] of Object.entries(schema.properties)) {
959
+ const prop = propSchema;
960
+ const propType = normalizeType(prop.type);
961
+ const isNested = propType === "object" || propType === "array";
962
+ result.properties[name] = {
963
+ type: propType,
964
+ format: prop.format,
965
+ description: prop.description,
966
+ enum: prop.enum,
967
+ default: prop.default,
968
+ example: prop.example,
969
+ nested: isNested || void 0
970
+ };
971
+ }
972
+ }
973
+ return result;
974
+ }
975
+ var RESPONSE_STATUS_CODES = ["200", "201", "202", "204", "2XX", "default"];
976
+ function extractResponseSchemas(operation, isOpenAPI3) {
977
+ const responses = operation.responses;
978
+ if (!responses) return { primary: void 0, all: {} };
979
+ const all = {};
980
+ for (const code of RESPONSE_STATUS_CODES) {
981
+ const resp = responses[code];
982
+ if (!resp) continue;
983
+ let schema;
984
+ if (isOpenAPI3) {
985
+ schema = resp.content?.[ContentType.JSON]?.schema;
986
+ } else {
987
+ schema = resp.schema;
988
+ }
989
+ if (schema) {
990
+ all[code] = schema;
991
+ }
992
+ }
993
+ const primary = all["200"] ?? all["201"] ?? all["202"] ?? all["2XX"];
994
+ return { primary, all };
995
+ }
996
+ var ERROR_STATUS_CODES = ["400", "401", "403", "404", "409", "422", "429", "500"];
997
+ function extractErrorHints(operation) {
998
+ const responses = operation.responses;
999
+ if (!responses) return void 0;
1000
+ const hints = {};
1001
+ for (const code of ERROR_STATUS_CODES) {
1002
+ const resp = responses[code];
1003
+ if (!resp?.description) continue;
1004
+ hints[code] = resp.description;
1005
+ }
1006
+ return Object.keys(hints).length > 0 ? hints : void 0;
1007
+ }
1008
+ function extractResponseContentType(operation, isOpenAPI3) {
1009
+ const responses = operation.responses;
1010
+ if (!responses) return void 0;
1011
+ const successResponse = responses["200"] ?? responses["201"] ?? responses["202"] ?? responses["2XX"];
1012
+ if (!successResponse) return void 0;
1013
+ if (isOpenAPI3) {
1014
+ const resp = successResponse;
1015
+ if (!resp.content) return void 0;
1016
+ const types = Object.keys(resp.content);
1017
+ if (types.includes(ContentType.JSON)) return ContentType.JSON;
1018
+ return types[0];
1019
+ } else {
1020
+ const op = operation;
1021
+ const produces = op.produces;
1022
+ if (produces && produces.length > 0) {
1023
+ if (produces.includes(ContentType.JSON)) return ContentType.JSON;
1024
+ return produces[0];
1025
+ }
1026
+ return void 0;
1027
+ }
1028
+ }
1029
+ function extractSecuritySchemes(api, isOpenAPI3) {
1030
+ if (isOpenAPI3) {
1031
+ const openapi3 = api;
1032
+ const rawSchemes = openapi3.components?.securitySchemes ?? {};
1033
+ const schemes = {};
1034
+ for (const [name, scheme] of Object.entries(rawSchemes)) {
1035
+ if (scheme && !("$ref" in scheme)) {
1036
+ schemes[name] = scheme;
1037
+ }
1038
+ }
1039
+ return mapSecuritySchemes(schemes);
1040
+ } else {
1041
+ const swagger2 = api;
1042
+ return mapSecuritySchemes(swagger2.securityDefinitions ?? {});
1043
+ }
1044
+ }
1045
+
1046
+ // src/adapters/raw/parser.ts
1047
+ function parseRawUrl(url) {
1048
+ return parseRawUrls([{ url }]);
1049
+ }
1050
+ function parseRawUrls(endpoints) {
1051
+ if (endpoints.length === 0) {
1052
+ throw new Error("At least one endpoint is required");
1053
+ }
1054
+ let firstParsed;
1055
+ try {
1056
+ firstParsed = new URL(endpoints[0].url);
1057
+ } catch {
1058
+ throw new Error(`Invalid URL "${endpoints[0].url}". Expected an absolute URL like "https://api.example.com/path".`);
1059
+ }
1060
+ const baseUrl = firstParsed.origin;
1061
+ const operations = endpoints.map((ep) => {
1062
+ let parsed;
1063
+ try {
1064
+ parsed = new URL(ep.url);
1065
+ } catch {
1066
+ throw new Error(`Invalid URL "${ep.url}". Expected an absolute URL like "https://api.example.com/path".`);
1067
+ }
1068
+ if (parsed.origin !== firstParsed.origin) {
1069
+ throw new Error(
1070
+ `All endpoints must share the same origin. Got "${parsed.origin}" but expected "${firstParsed.origin}"`
1071
+ );
1072
+ }
1073
+ const method = (ep.method ?? HttpMethod.GET).toUpperCase();
1074
+ const pathname = parsed.pathname;
1075
+ const parameters = [];
1076
+ const entries = /* @__PURE__ */ new Map();
1077
+ for (const [rawKey, value] of parsed.searchParams.entries()) {
1078
+ const name = rawKey.replace(/(\[\])+$/, "");
1079
+ const isBracket = name !== rawKey;
1080
+ if (!name) continue;
1081
+ const existing = entries.get(name);
1082
+ if (existing) {
1083
+ existing.values.push(value);
1084
+ existing.isBracket = existing.isBracket || isBracket;
1085
+ } else {
1086
+ entries.set(name, { values: [value], isBracket });
1087
+ }
1088
+ }
1089
+ for (const [name, { values, isBracket }] of entries) {
1090
+ const isArray = values.length > 1 || isBracket;
1091
+ parameters.push({
1092
+ name,
1093
+ in: ParamLocation.QUERY,
1094
+ required: false,
1095
+ description: isArray ? `Default: ${JSON.stringify(values)}` : `Default: ${values[0]}`,
1096
+ schema: isArray ? { type: "array", default: values, items: { type: "string" } } : { type: "string", default: values[0] }
1097
+ });
1098
+ }
1099
+ const id = ep.id ?? `${method.toLowerCase()}_${pathname.replace(/^\//, "").replace(/[\/]/g, "_") || "root"}`;
1100
+ return {
1101
+ id,
1102
+ path: pathname,
1103
+ method,
1104
+ summary: ep.summary ?? `${method} ${parsed.hostname}${pathname}`,
1105
+ parameters,
1106
+ tags: []
1107
+ };
1108
+ });
1109
+ return {
1110
+ title: firstParsed.hostname,
1111
+ version: "1.0.0",
1112
+ baseUrl,
1113
+ operations,
1114
+ authSchemes: [],
1115
+ specFormat: SpecFormat.RAW_URL
1116
+ };
1117
+ }
1118
+
1119
+ // src/core/detection.ts
1120
+ function isSpecUrl(url) {
1121
+ const lower = url.toLowerCase();
1122
+ return lower.endsWith("/openapi.json") || lower.endsWith("/openapi.yaml") || lower.endsWith("/openapi.yml") || lower.endsWith("/swagger.json") || lower.endsWith("/swagger.yaml") || lower.endsWith("/swagger.yml") || lower.endsWith("/spec.json") || lower.endsWith("/spec.yaml") || lower.endsWith("/spec.yml") || lower.endsWith("/api-docs") || lower.endsWith("/api-docs.json") || lower.endsWith("/api-docs.yaml") || lower.endsWith("/v2/api-docs") || lower.endsWith("/v3/api-docs") || lower.includes("swagger") || lower.includes("openapi");
1123
+ }
1124
+ function isSpecContent(data) {
1125
+ if (!data || typeof data !== "object" || Array.isArray(data)) return false;
1126
+ const obj = data;
1127
+ return typeof obj.openapi === "string" || typeof obj.swagger === "string";
1128
+ }
1129
+ function isGraphQLUrl(url) {
1130
+ try {
1131
+ return /\/graphql(?:$|[\/\?#])/i.test(new URL(url).pathname);
1132
+ } catch {
1133
+ return false;
1134
+ }
1135
+ }
1136
+
1137
+ // src/client.ts
1138
+ function isGraphQLIntrospection(obj) {
1139
+ if (obj.__schema !== void 0) return true;
1140
+ if (typeof obj.data === "object" && obj.data !== null && "__schema" in obj.data) return true;
1141
+ return false;
1142
+ }
1143
+ var ApiInvokeClient = class {
1144
+ /** The parsed API specification backing this client. */
1145
+ api;
1146
+ auth;
1147
+ middleware;
1148
+ fetchFn;
1149
+ timeoutMs;
1150
+ /**
1151
+ * @param api - Parsed API specification (from any adapter)
1152
+ * @param options - Client configuration (auth, middleware, fetch, timeout)
1153
+ */
1154
+ constructor(api, options = {}) {
1155
+ this.api = api;
1156
+ this.auth = options.auth;
1157
+ this.middleware = options.middleware ?? [];
1158
+ this.fetchFn = options.fetch ?? globalThis.fetch;
1159
+ this.timeoutMs = options.timeoutMs ?? 0;
1160
+ }
1161
+ /** The resolved base URL for this API. */
1162
+ get baseUrl() {
1163
+ return this.api.baseUrl;
1164
+ }
1165
+ /** All available operations from the parsed spec. */
1166
+ get operations() {
1167
+ return this.api.operations;
1168
+ }
1169
+ /** Authentication schemes declared in the spec. Useful for building auth UIs. */
1170
+ get authSchemes() {
1171
+ return this.api.authSchemes;
1172
+ }
1173
+ /**
1174
+ * Set authentication credentials for all subsequent requests.
1175
+ * @param auth - Single credential or array for composing multiple schemes
1176
+ */
1177
+ setAuth(auth) {
1178
+ this.auth = auth;
1179
+ }
1180
+ /**
1181
+ * Clear authentication credentials.
1182
+ */
1183
+ clearAuth() {
1184
+ this.auth = void 0;
1185
+ }
1186
+ /**
1187
+ * Find an operation by its ID.
1188
+ * @param operationId - The operation ID to search for (e.g. 'listUsers', 'get_users_userId')
1189
+ * @returns The operation, or undefined if not found
1190
+ */
1191
+ findOperation(operationId) {
1192
+ return this.api.operations.find((op) => op.id === operationId);
1193
+ }
1194
+ /**
1195
+ * Execute an operation by ID with arguments.
1196
+ *
1197
+ * @param operationId - The operation ID from the parsed spec
1198
+ * @param args - Key-value pairs for path, query, header, and body parameters
1199
+ * @param options - Per-call overrides for auth, accept header, error behavior, and redirect mode
1200
+ * @returns The execution result with status, parsed data, and response metadata
1201
+ * @throws {ApiInvokeError} For network, CORS, timeout, and (by default) HTTP errors
1202
+ * @throws {Error} If the operation ID is not found
1203
+ *
1204
+ * @example
1205
+ * ```ts
1206
+ * const result = await client.execute('getUser', { userId: 123 })
1207
+ * console.log(result.status, result.data)
1208
+ * ```
1209
+ */
1210
+ async execute(operationId, args = {}, options) {
1211
+ const operation = this.findOperation(operationId);
1212
+ if (!operation) {
1213
+ throw new Error(`Operation "${operationId}" not found. Available: ${this.api.operations.map((o) => o.id).join(", ")}`);
1214
+ }
1215
+ return executeOperation(this.api.baseUrl, operation, args, {
1216
+ auth: options?.auth ?? this.auth,
1217
+ middleware: this.middleware,
1218
+ fetch: this.fetchFn,
1219
+ timeoutMs: this.timeoutMs,
1220
+ accept: options?.accept,
1221
+ throwOnHttpError: options?.throwOnHttpError,
1222
+ redirect: options?.redirect
1223
+ });
1224
+ }
1225
+ /**
1226
+ * Execute an operation as a stream, returning an async iterable of SSE events.
1227
+ * Errors always throw (no non-throwing mode for streams).
1228
+ *
1229
+ * @param operationId - The operation ID from the parsed spec
1230
+ * @param args - Key-value pairs for path, query, header, and body parameters
1231
+ * @param options - Per-call overrides for auth, accept header, abort signal, and event callback. The client-level `timeoutMs` applies to the initial connection.
1232
+ * @returns Streaming result with an async iterable `stream` property
1233
+ * @throws {ApiInvokeError} For network, CORS, timeout, and HTTP errors
1234
+ * @throws {Error} If the operation ID is not found
1235
+ *
1236
+ * @example
1237
+ * ```ts
1238
+ * const result = await client.executeStream('chatCompletion', {
1239
+ * model: 'gpt-4', messages: [{ role: 'user', content: 'Hi' }], stream: true,
1240
+ * })
1241
+ * for await (const event of result.stream) {
1242
+ * console.log(event.data)
1243
+ * }
1244
+ * ```
1245
+ */
1246
+ async executeStream(operationId, args = {}, options) {
1247
+ const operation = this.findOperation(operationId);
1248
+ if (!operation) {
1249
+ throw new Error(`Operation "${operationId}" not found. Available: ${this.api.operations.map((o) => o.id).join(", ")}`);
1250
+ }
1251
+ return executeOperationStream(this.api.baseUrl, operation, args, {
1252
+ auth: options?.auth ?? this.auth,
1253
+ middleware: this.middleware,
1254
+ fetch: this.fetchFn,
1255
+ timeoutMs: this.timeoutMs,
1256
+ accept: options?.accept,
1257
+ signal: options?.signal,
1258
+ onEvent: options?.onEvent
1259
+ });
1260
+ }
1261
+ };
1262
+ async function createClient(input, options = {}) {
1263
+ let api;
1264
+ if (typeof input === "string") {
1265
+ if (isSpecUrl(input)) {
1266
+ api = await fetchAndParseSpec(input, options);
1267
+ } else if (isGraphQLUrl(input)) {
1268
+ const { parseGraphQLSchema: parseGraphQLSchema2 } = await import('./parser-PMZEKQPK.js');
1269
+ api = await parseGraphQLSchema2(input, { endpoint: input, fetch: options.fetch });
1270
+ } else {
1271
+ api = await tryContentDetection(input, options);
1272
+ }
1273
+ } else {
1274
+ const obj = input;
1275
+ if (isGraphQLIntrospection(obj)) {
1276
+ const { parseGraphQLSchema: parseGraphQLSchema2 } = await import('./parser-PMZEKQPK.js');
1277
+ api = await parseGraphQLSchema2(input, { endpoint: options.specUrl, fetch: options.fetch });
1278
+ } else {
1279
+ api = await parseOpenAPISpec(input, { specUrl: options.specUrl });
1280
+ }
1281
+ }
1282
+ return finalize(api, options);
1283
+ }
1284
+ async function fetchAndParseSpec(url, options) {
1285
+ const fetchFn = options.fetch ?? globalThis.fetch;
1286
+ const response = await fetchFn(url);
1287
+ if (!response.ok) {
1288
+ throw new Error(`Failed to fetch spec: ${response.status} ${response.statusText}`);
1289
+ }
1290
+ const text = await response.text();
1291
+ let specObject;
1292
+ try {
1293
+ specObject = JSON.parse(text);
1294
+ } catch {
1295
+ return parseOpenAPISpec(url, { specUrl: url });
1296
+ }
1297
+ return parseOpenAPISpec(specObject, { specUrl: url });
1298
+ }
1299
+ async function tryContentDetection(url, options) {
1300
+ const fetchFn = options.fetch ?? globalThis.fetch;
1301
+ let response;
1302
+ try {
1303
+ response = await fetchFn(url);
1304
+ } catch (error) {
1305
+ if (error instanceof TypeError || error instanceof DOMException && error.name === "AbortError") {
1306
+ return parseRawUrl(url);
1307
+ }
1308
+ throw error;
1309
+ }
1310
+ if (!response.ok) {
1311
+ return parseRawUrl(url);
1312
+ }
1313
+ let text;
1314
+ try {
1315
+ text = await response.text();
1316
+ } catch {
1317
+ return parseRawUrl(url);
1318
+ }
1319
+ let parsed;
1320
+ try {
1321
+ parsed = JSON.parse(text);
1322
+ } catch {
1323
+ return parseRawUrl(url);
1324
+ }
1325
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1326
+ return parseRawUrl(url);
1327
+ }
1328
+ const obj = parsed;
1329
+ if (isSpecContent(obj)) {
1330
+ return parseOpenAPISpec(obj, { specUrl: url });
1331
+ }
1332
+ if (isGraphQLIntrospection(obj)) {
1333
+ const { parseGraphQLSchema: parseGraphQLSchema2 } = await import('./parser-PMZEKQPK.js');
1334
+ return parseGraphQLSchema2(obj, { endpoint: url, fetch: options.fetch });
1335
+ }
1336
+ return parseRawUrl(url);
1337
+ }
1338
+ async function finalize(api, options) {
1339
+ if (options.enricher) {
1340
+ api = await Promise.resolve(options.enricher.enrichAPI(api));
1341
+ }
1342
+ return new ApiInvokeClient(api, options);
1343
+ }
1344
+
1345
+ // src/core/auth-config.ts
1346
+ var AuthConfigType = {
1347
+ BEARER: "bearer",
1348
+ HEADER: "header",
1349
+ API_KEY: "apikey",
1350
+ NONE: "none"
1351
+ };
1352
+ function toAuth(config) {
1353
+ switch (config.type) {
1354
+ case AuthConfigType.BEARER:
1355
+ return config.token ? { type: AuthType.BEARER, token: config.token } : void 0;
1356
+ case AuthConfigType.HEADER:
1357
+ return config.headerName && config.headerValue ? { type: AuthType.API_KEY, location: ParamLocation.HEADER, name: config.headerName, value: config.headerValue } : void 0;
1358
+ case AuthConfigType.API_KEY:
1359
+ return config.paramName && config.paramValue ? { type: AuthType.API_KEY, location: ParamLocation.QUERY, name: config.paramName, value: config.paramValue } : void 0;
1360
+ case AuthConfigType.NONE:
1361
+ return void 0;
1362
+ }
1363
+ }
1364
+
1365
+ // src/middleware/retry.ts
1366
+ var DEFAULT_RETRYABLE_STATUSES = [429, 500, 502, 503, 504];
1367
+ function parseRetryAfter(value) {
1368
+ const seconds = Number(value);
1369
+ if (!Number.isNaN(seconds) && seconds >= 0) {
1370
+ return seconds * 1e3;
1371
+ }
1372
+ const date = Date.parse(value);
1373
+ if (!Number.isNaN(date)) {
1374
+ const delayMs = date - Date.now();
1375
+ return delayMs > 0 ? delayMs : 0;
1376
+ }
1377
+ return void 0;
1378
+ }
1379
+ function calculateDelay(attempt, initialDelayMs, multiplier, maxDelayMs, jitter) {
1380
+ const base = initialDelayMs * Math.pow(multiplier, attempt);
1381
+ const capped = Math.min(base, maxDelayMs);
1382
+ const jitterAmount = capped * jitter * (Math.random() * 2 - 1);
1383
+ return Math.max(0, Math.round(capped + jitterAmount));
1384
+ }
1385
+ function withRetry(options = {}, baseFetch) {
1386
+ const {
1387
+ maxRetries = 3,
1388
+ initialDelayMs = 1e3,
1389
+ maxDelayMs = 3e4,
1390
+ multiplier = 2,
1391
+ jitter = 0.1,
1392
+ retryableStatuses = DEFAULT_RETRYABLE_STATUSES,
1393
+ onRetry
1394
+ } = options;
1395
+ const fetchFn = baseFetch ?? globalThis.fetch;
1396
+ return async function retryFetch(input, init) {
1397
+ let lastError;
1398
+ let lastResponse;
1399
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1400
+ try {
1401
+ const response = await fetchFn(input, init);
1402
+ if (retryableStatuses.includes(response.status) && attempt < maxRetries) {
1403
+ lastResponse = response;
1404
+ const retryAfter = response.headers.get("retry-after");
1405
+ let delayMs;
1406
+ if (retryAfter) {
1407
+ const parsed = parseRetryAfter(retryAfter);
1408
+ delayMs = parsed !== void 0 ? Math.min(parsed, maxDelayMs) : calculateDelay(attempt, initialDelayMs, multiplier, maxDelayMs, jitter);
1409
+ } else {
1410
+ delayMs = calculateDelay(attempt, initialDelayMs, multiplier, maxDelayMs, jitter);
1411
+ }
1412
+ onRetry?.(attempt + 1, delayMs, response.status);
1413
+ await sleep(delayMs);
1414
+ continue;
1415
+ }
1416
+ return response;
1417
+ } catch (error) {
1418
+ lastError = error;
1419
+ if (attempt < maxRetries) {
1420
+ const delayMs = calculateDelay(attempt, initialDelayMs, multiplier, maxDelayMs, jitter);
1421
+ onRetry?.(attempt + 1, delayMs);
1422
+ await sleep(delayMs);
1423
+ continue;
1424
+ }
1425
+ }
1426
+ }
1427
+ if (lastResponse) return lastResponse;
1428
+ throw lastError;
1429
+ };
1430
+ }
1431
+ function sleep(ms) {
1432
+ return new Promise((resolve) => setTimeout(resolve, ms));
1433
+ }
1434
+
1435
+ // src/middleware/cors-proxy.ts
1436
+ function defaultRewrite(url) {
1437
+ return `/api-proxy/${encodeURIComponent(url)}`;
1438
+ }
1439
+ function defaultShouldProxy(url) {
1440
+ return url.startsWith("http://") || url.startsWith("https://");
1441
+ }
1442
+ function corsProxy(options = {}) {
1443
+ const rewrite = options.rewrite ?? defaultRewrite;
1444
+ const shouldProxy = options.shouldProxy ?? defaultShouldProxy;
1445
+ return {
1446
+ name: "cors-proxy",
1447
+ onRequest(url, init) {
1448
+ if (shouldProxy(url)) {
1449
+ return { url: rewrite(url), init };
1450
+ }
1451
+ return { url, init };
1452
+ }
1453
+ };
1454
+ }
1455
+
1456
+ // src/middleware/logging.ts
1457
+ var DEFAULT_SENSITIVE_HEADERS = ["authorization", "x-api-key", "cookie"];
1458
+ var DEFAULT_SENSITIVE_PARAMS = ["api_key", "apikey", "key", "token", "access_token"];
1459
+ function maskHeaderValue(name, value) {
1460
+ const lower = name.toLowerCase();
1461
+ if (lower === "authorization") {
1462
+ const space = value.indexOf(" ");
1463
+ if (space > 0) {
1464
+ return `${value.substring(0, space)} ***`;
1465
+ }
1466
+ return "***";
1467
+ }
1468
+ return "***";
1469
+ }
1470
+ function maskUrl(url, sensitiveParams) {
1471
+ try {
1472
+ const parsed = new URL(url);
1473
+ let masked = false;
1474
+ for (const param of sensitiveParams) {
1475
+ if (parsed.searchParams.has(param)) {
1476
+ parsed.searchParams.set(param, "***");
1477
+ masked = true;
1478
+ }
1479
+ }
1480
+ return masked ? parsed.toString() : url;
1481
+ } catch {
1482
+ return url;
1483
+ }
1484
+ }
1485
+ function formatHeaders(init, sensitiveHeaders) {
1486
+ const headers = init.headers;
1487
+ if (!headers) return void 0;
1488
+ const result = {};
1489
+ const sensitiveSet = new Set(sensitiveHeaders.map((h) => h.toLowerCase()));
1490
+ if (headers instanceof Headers) {
1491
+ headers.forEach((value, key) => {
1492
+ result[key] = sensitiveSet.has(key.toLowerCase()) ? maskHeaderValue(key, value) : value;
1493
+ });
1494
+ } else if (Array.isArray(headers)) {
1495
+ for (const [key, value] of headers) {
1496
+ result[key] = sensitiveSet.has(key.toLowerCase()) ? maskHeaderValue(key, value) : value;
1497
+ }
1498
+ } else {
1499
+ for (const [key, value] of Object.entries(headers)) {
1500
+ result[key] = sensitiveSet.has(key.toLowerCase()) ? maskHeaderValue(key, value) : value;
1501
+ }
1502
+ }
1503
+ return Object.keys(result).length > 0 ? result : void 0;
1504
+ }
1505
+ function logging(options = {}) {
1506
+ const {
1507
+ log = console.log,
1508
+ logBody = false,
1509
+ prefix = "api-invoke"
1510
+ } = options;
1511
+ const sensitiveHeaders = [
1512
+ ...DEFAULT_SENSITIVE_HEADERS,
1513
+ ...(options.sensitiveHeaders ?? []).map((h) => h.toLowerCase())
1514
+ ];
1515
+ const sensitiveParams = [
1516
+ ...DEFAULT_SENSITIVE_PARAMS,
1517
+ ...options.sensitiveParams ?? []
1518
+ ];
1519
+ let requestStart = 0;
1520
+ return {
1521
+ name: "logging",
1522
+ onRequest(url, init) {
1523
+ requestStart = performance.now();
1524
+ const maskedUrl = maskUrl(url, sensitiveParams);
1525
+ const method = (init.method ?? HttpMethod.GET).toUpperCase();
1526
+ const headers = formatHeaders(init, sensitiveHeaders);
1527
+ const parts = [`[${prefix}] \u2192 ${method} ${maskedUrl}`];
1528
+ if (headers) {
1529
+ parts.push(` headers: ${JSON.stringify(headers)}`);
1530
+ }
1531
+ if (logBody && init.body) {
1532
+ const bodyStr = typeof init.body === "string" ? init.body : "<binary>";
1533
+ parts.push(` body: ${bodyStr}`);
1534
+ }
1535
+ log(parts.join("\n"));
1536
+ return { url, init };
1537
+ },
1538
+ onResponse(response) {
1539
+ const elapsed = requestStart ? `${Math.round(performance.now() - requestStart)}ms` : "?ms";
1540
+ const status = response.status;
1541
+ const statusText = response.statusText;
1542
+ log(`[${prefix}] \u2190 ${status} ${statusText} (${elapsed})`);
1543
+ return response;
1544
+ },
1545
+ onError(error) {
1546
+ log(`[${prefix}] \u2715 ${error.message}`);
1547
+ }
1548
+ };
1549
+ }
1550
+
1551
+ // src/middleware/oauth-refresh.ts
1552
+ function withOAuthRefresh(options, baseFetch) {
1553
+ const fetchFn = baseFetch ?? globalThis.fetch;
1554
+ let currentRefreshToken = options.refreshToken;
1555
+ let refreshPromise = null;
1556
+ return async function oauthRefreshFetch(input, init) {
1557
+ const response = await fetchFn(input, init);
1558
+ if (response.status !== 401) return response;
1559
+ if (init?.body instanceof ReadableStream || typeof init?.body === "object" && init.body !== null && Symbol.asyncIterator in init.body || typeof init?.body === "object" && init.body !== null && typeof init.body.pipe === "function") {
1560
+ console.warn(
1561
+ "[api-invoke] Cannot retry request with stream body after 401 \u2014 the stream was consumed. Use a string or Blob body for OAuth2-protected requests."
1562
+ );
1563
+ return response;
1564
+ }
1565
+ let tokens;
1566
+ try {
1567
+ if (!refreshPromise) {
1568
+ refreshPromise = refreshOAuth2Token(options.tokenUrl, currentRefreshToken, {
1569
+ clientId: options.clientId,
1570
+ clientSecret: options.clientSecret,
1571
+ scopes: options.scopes,
1572
+ fetch: fetchFn
1573
+ }).then(async (result) => {
1574
+ if (result.refreshToken) currentRefreshToken = result.refreshToken;
1575
+ if (options.onTokenRefresh) {
1576
+ try {
1577
+ await options.onTokenRefresh(result);
1578
+ } catch (callbackError) {
1579
+ console.warn(
1580
+ "[api-invoke] onTokenRefresh callback threw \u2014 new tokens were NOT persisted. The refreshed token is used for this request, but may be lost on restart.",
1581
+ callbackError
1582
+ );
1583
+ }
1584
+ }
1585
+ return result;
1586
+ }).finally(() => {
1587
+ refreshPromise = null;
1588
+ });
1589
+ }
1590
+ tokens = await refreshPromise;
1591
+ } catch (error) {
1592
+ console.warn(
1593
+ "[api-invoke] OAuth2 token refresh failed, returning original 401.",
1594
+ error
1595
+ );
1596
+ return response;
1597
+ }
1598
+ let baseHeaders = {};
1599
+ if (input instanceof Request) {
1600
+ input.headers.forEach((value, key) => {
1601
+ baseHeaders[key] = value;
1602
+ });
1603
+ }
1604
+ const initHeaders = typeof init?.headers === "object" && !Array.isArray(init.headers) && !(init.headers instanceof Headers) ? { ...init.headers } : Object.fromEntries(new Headers(init?.headers).entries());
1605
+ const existingHeaders = { ...baseHeaders, ...initHeaders };
1606
+ for (const key of Object.keys(existingHeaders)) {
1607
+ if (key.toLowerCase() === "authorization") delete existingHeaders[key];
1608
+ }
1609
+ existingHeaders["Authorization"] = `Bearer ${tokens.accessToken}`;
1610
+ return fetchFn(input, { ...init, headers: existingHeaders });
1611
+ };
1612
+ }
1613
+
1614
+ // src/adapters/manual/builder.ts
1615
+ var APIBuilder = class {
1616
+ _title;
1617
+ _version = "1.0.0";
1618
+ _baseUrl = "";
1619
+ _operations = [];
1620
+ constructor(title) {
1621
+ this._title = title;
1622
+ }
1623
+ /**
1624
+ * Set the API version string.
1625
+ * @param v - Version string (e.g. '2.0.0'). Default: '1.0.0'.
1626
+ */
1627
+ version(v) {
1628
+ this._version = v;
1629
+ return this;
1630
+ }
1631
+ /**
1632
+ * Set the base URL for all endpoints.
1633
+ * @param url - Base URL (e.g. 'https://api.example.com/v1'). Required before calling {@link build}.
1634
+ */
1635
+ baseUrl(url) {
1636
+ this._baseUrl = url;
1637
+ return this;
1638
+ }
1639
+ /** Add a GET endpoint. */
1640
+ get(path, options = {}) {
1641
+ return this.endpoint(HttpMethod.GET, path, options);
1642
+ }
1643
+ /** Add a POST endpoint. */
1644
+ post(path, options = {}) {
1645
+ return this.endpoint(HttpMethod.POST, path, options);
1646
+ }
1647
+ /** Add a PUT endpoint. */
1648
+ put(path, options = {}) {
1649
+ return this.endpoint(HttpMethod.PUT, path, options);
1650
+ }
1651
+ /** Add a PATCH endpoint. */
1652
+ patch(path, options = {}) {
1653
+ return this.endpoint(HttpMethod.PATCH, path, options);
1654
+ }
1655
+ /** Add a DELETE endpoint. */
1656
+ delete(path, options = {}) {
1657
+ return this.endpoint(HttpMethod.DELETE, path, options);
1658
+ }
1659
+ /**
1660
+ * Add an endpoint with any HTTP method.
1661
+ * Path parameters are auto-detected from `{placeholder}` segments.
1662
+ *
1663
+ * @param method - HTTP method (e.g. 'GET', 'POST')
1664
+ * @param path - URL path template (e.g. '/users/{userId}')
1665
+ * @param options - Endpoint configuration
1666
+ */
1667
+ endpoint(method, path, options = {}) {
1668
+ const id = options.id ?? `${method.toLowerCase()}_${path.replace(/[{}\/]/g, "_").replace(/^_|_$/g, "").replace(/_+/g, "_")}`;
1669
+ const pathParamNames = [...path.matchAll(/\{(\w+)\}/g)].map((m) => m[1]);
1670
+ const parameters = [];
1671
+ for (const name of pathParamNames) {
1672
+ const explicit = options.params?.[name];
1673
+ const def = typeof explicit === "string" ? { type: explicit } : explicit ?? {};
1674
+ parameters.push({
1675
+ name,
1676
+ in: ParamLocation.PATH,
1677
+ required: true,
1678
+ description: def.description ?? "",
1679
+ schema: { type: def.type ?? "string", default: def.default }
1680
+ });
1681
+ }
1682
+ if (options.params) {
1683
+ for (const [name, raw] of Object.entries(options.params)) {
1684
+ if (pathParamNames.includes(name)) continue;
1685
+ const def = typeof raw === "string" ? { type: raw } : raw;
1686
+ parameters.push({
1687
+ name,
1688
+ in: def.in ?? ParamLocation.QUERY,
1689
+ required: def.required ?? false,
1690
+ description: def.description ?? "",
1691
+ schema: { type: def.type ?? "string", default: def.default }
1692
+ });
1693
+ }
1694
+ }
1695
+ let requestBody;
1696
+ if (options.body) {
1697
+ const properties = {};
1698
+ if (options.body.properties) {
1699
+ for (const [name, raw] of Object.entries(options.body.properties)) {
1700
+ if (typeof raw === "string") {
1701
+ properties[name] = { type: raw };
1702
+ } else {
1703
+ properties[name] = {
1704
+ type: raw.type,
1705
+ description: raw.description,
1706
+ format: raw.format,
1707
+ enum: raw.enum
1708
+ };
1709
+ }
1710
+ }
1711
+ }
1712
+ requestBody = {
1713
+ required: options.body.required ?? true,
1714
+ contentType: options.body.contentType ?? ContentType.JSON,
1715
+ schema: {
1716
+ type: "object",
1717
+ raw: {},
1718
+ properties,
1719
+ required: options.body.requiredFields
1720
+ }
1721
+ };
1722
+ }
1723
+ this._operations.push({
1724
+ id,
1725
+ path,
1726
+ method: method.toUpperCase(),
1727
+ summary: options.summary,
1728
+ description: options.description,
1729
+ parameters,
1730
+ requestBody,
1731
+ responseContentType: options.responseContentType,
1732
+ tags: options.tags ?? []
1733
+ });
1734
+ return this;
1735
+ }
1736
+ /**
1737
+ * Build the {@link ParsedAPI} from the configured endpoints.
1738
+ * @returns A ParsedAPI ready to use with {@link ApiInvokeClient}
1739
+ * @throws {Error} If `baseUrl` is not set or no endpoints are defined
1740
+ */
1741
+ build() {
1742
+ if (!this._baseUrl) {
1743
+ throw new Error('baseUrl is required. Call .baseUrl("https://...") before .build().');
1744
+ }
1745
+ if (this._operations.length === 0) {
1746
+ throw new Error("At least one endpoint is required. Call .get(), .post(), etc. before .build().");
1747
+ }
1748
+ return {
1749
+ title: this._title,
1750
+ version: this._version,
1751
+ baseUrl: this._baseUrl,
1752
+ operations: [...this._operations],
1753
+ authSchemes: [],
1754
+ specFormat: SpecFormat.MANUAL
1755
+ };
1756
+ }
1757
+ };
1758
+ function defineAPI(title) {
1759
+ return new APIBuilder(title);
1760
+ }
1761
+
1762
+ // src/adapters/graphql/errors.ts
1763
+ function hasGraphQLErrors(result) {
1764
+ const body = result.data;
1765
+ return body != null && Array.isArray(body.errors) && body.errors.length > 0;
1766
+ }
1767
+ function getGraphQLErrors(result) {
1768
+ const body = result.data;
1769
+ if (body != null && Array.isArray(body.errors)) return body.errors;
1770
+ return [];
1771
+ }
1772
+ function throwOnGraphQLErrors(result) {
1773
+ const body = result.data;
1774
+ if (body == null || !Array.isArray(body.errors) || body.errors.length === 0) return;
1775
+ if (body.data != null) return;
1776
+ const errors = body.errors;
1777
+ const messages = errors.map((e) => e.message).join("; ");
1778
+ throw graphqlError(messages, result.status, body);
1779
+ }
1780
+
1781
+ export { APIBuilder, ApiInvokeClient, AuthConfigType, buildRequest, buildUrl, corsProxy, createClient, defineAPI, deriveBaseUrl, executeOperation, executeOperationStream, executeRaw, executeRawStream, getGraphQLErrors, hasGraphQLErrors, injectAuth, isGraphQLUrl, isSpecContent, isSpecUrl, logging, maskAuth, parseOpenAPISpec, parseRawUrl, parseRawUrls, parseSSE, refreshOAuth2Token, throwOnGraphQLErrors, toAuth, withOAuthRefresh, withRetry };
1782
+ //# sourceMappingURL=index.js.map
1783
+ //# sourceMappingURL=index.js.map