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.
@@ -0,0 +1,543 @@
1
+ // src/core/types.ts
2
+ var HttpMethod = {
3
+ GET: "GET",
4
+ POST: "POST",
5
+ PUT: "PUT",
6
+ PATCH: "PATCH",
7
+ DELETE: "DELETE",
8
+ HEAD: "HEAD",
9
+ OPTIONS: "OPTIONS"
10
+ };
11
+ var ParamLocation = {
12
+ PATH: "path",
13
+ QUERY: "query",
14
+ HEADER: "header",
15
+ COOKIE: "cookie"
16
+ };
17
+ var AuthType = {
18
+ BEARER: "bearer",
19
+ BASIC: "basic",
20
+ API_KEY: "apiKey",
21
+ QUERY_PARAM: "queryParam",
22
+ OAUTH2: "oauth2",
23
+ COOKIE: "cookie"
24
+ };
25
+ var SpecFormat = {
26
+ OPENAPI_3: "openapi-3",
27
+ OPENAPI_2: "openapi-2",
28
+ RAW_URL: "raw-url",
29
+ MANUAL: "manual",
30
+ GRAPHQL: "graphql"
31
+ };
32
+ var HeaderName = {
33
+ ACCEPT: "Accept",
34
+ AUTHORIZATION: "Authorization",
35
+ CONTENT_TYPE: "Content-Type",
36
+ COOKIE: "Cookie"
37
+ };
38
+ var ContentType = {
39
+ JSON: "application/json",
40
+ FORM_URLENCODED: "application/x-www-form-urlencoded",
41
+ MULTIPART: "multipart/form-data",
42
+ XML: "application/xml",
43
+ OCTET_STREAM: "application/octet-stream",
44
+ TEXT: "text/plain",
45
+ SSE: "text/event-stream"
46
+ };
47
+
48
+ // src/core/errors.ts
49
+ var ErrorKind = {
50
+ CORS: "cors",
51
+ NETWORK: "network",
52
+ AUTH: "auth",
53
+ HTTP: "http",
54
+ PARSE: "parse",
55
+ RATE_LIMIT: "rate-limit",
56
+ TIMEOUT: "timeout",
57
+ GRAPHQL: "graphql"
58
+ };
59
+ var API_INVOKE_ERROR_NAME = "ApiInvokeError";
60
+ var ApiInvokeError = class extends Error {
61
+ /** Error classification for programmatic handling. */
62
+ kind;
63
+ /** HTTP status code, if the error originated from an HTTP response. */
64
+ status;
65
+ /** Human-readable suggestion for how to resolve this error. */
66
+ suggestion;
67
+ /** Whether retrying the request might succeed (e.g. true for rate limits, network errors). */
68
+ retryable;
69
+ /** Response body from the API (when available). May be parsed JSON, a string, or binary data depending on the response content type. */
70
+ responseBody;
71
+ constructor(opts) {
72
+ super(opts.message);
73
+ this.name = API_INVOKE_ERROR_NAME;
74
+ this.kind = opts.kind;
75
+ this.suggestion = opts.suggestion;
76
+ this.retryable = opts.retryable ?? false;
77
+ this.status = opts.status;
78
+ this.responseBody = opts.responseBody;
79
+ }
80
+ };
81
+ function corsError(url) {
82
+ return new ApiInvokeError({
83
+ kind: ErrorKind.CORS,
84
+ message: `Cannot access ${url} \u2014 blocked by CORS policy.`,
85
+ suggestion: "This API does not allow browser requests. Use a CORS proxy or server-side execution.",
86
+ retryable: false
87
+ });
88
+ }
89
+ function networkError(url) {
90
+ return new ApiInvokeError({
91
+ kind: ErrorKind.NETWORK,
92
+ message: `Network error while fetching ${url}.`,
93
+ suggestion: "Check your internet connection and verify the URL is correct.",
94
+ retryable: true
95
+ });
96
+ }
97
+ function authError(url, status, responseBody) {
98
+ return new ApiInvokeError({
99
+ kind: ErrorKind.AUTH,
100
+ message: status === 401 ? `Authentication failed for ${url} (401)` : `Authorization denied for ${url} (403)`,
101
+ suggestion: status === 401 ? "Check your credentials. The server rejected your authentication." : "Your credentials are valid but you lack permission for this resource.",
102
+ retryable: false,
103
+ status,
104
+ responseBody
105
+ });
106
+ }
107
+ function httpError(url, status, statusText, responseBody) {
108
+ const retryable = status === 429 || status >= 500;
109
+ const kind = status === 429 ? ErrorKind.RATE_LIMIT : ErrorKind.HTTP;
110
+ let suggestion;
111
+ if (status === 404) {
112
+ suggestion = "The endpoint was not found. Check the URL path.";
113
+ } else if (status === 429) {
114
+ suggestion = "Rate limited. Wait and retry.";
115
+ } else if (status >= 500) {
116
+ suggestion = "The API server is having issues. Try again later.";
117
+ } else {
118
+ suggestion = `The API returned an error (${status}). Verify the request.`;
119
+ }
120
+ return new ApiInvokeError({
121
+ kind,
122
+ message: `API returned ${status} ${statusText} for ${url}.`,
123
+ suggestion,
124
+ retryable,
125
+ status,
126
+ responseBody
127
+ });
128
+ }
129
+ function parseError(url, expectedType = "JSON") {
130
+ return new ApiInvokeError({
131
+ kind: ErrorKind.PARSE,
132
+ message: `Failed to parse response from ${url} as ${expectedType}.`,
133
+ suggestion: `The API response body could not be read as ${expectedType}. Verify the endpoint returns the expected content type.`,
134
+ retryable: false
135
+ });
136
+ }
137
+ function graphqlError(messages, status, responseBody) {
138
+ return new ApiInvokeError({
139
+ kind: ErrorKind.GRAPHQL,
140
+ message: `GraphQL errors: ${messages}`,
141
+ suggestion: "Check the query and variables for correctness.",
142
+ retryable: false,
143
+ status,
144
+ responseBody
145
+ });
146
+ }
147
+ function timeoutError(url) {
148
+ return new ApiInvokeError({
149
+ kind: ErrorKind.TIMEOUT,
150
+ message: `Request to ${url} timed out.`,
151
+ suggestion: "The server took too long to respond. Try again or increase the timeout.",
152
+ retryable: true
153
+ });
154
+ }
155
+
156
+ // src/adapters/graphql/introspection.ts
157
+ var TypeKind = {
158
+ SCALAR: "SCALAR",
159
+ OBJECT: "OBJECT",
160
+ ENUM: "ENUM",
161
+ INPUT_OBJECT: "INPUT_OBJECT",
162
+ NON_NULL: "NON_NULL",
163
+ LIST: "LIST",
164
+ UNION: "UNION",
165
+ INTERFACE: "INTERFACE"
166
+ };
167
+ var INTROSPECTION_QUERY = `query IntrospectionQuery {
168
+ __schema {
169
+ queryType { name }
170
+ mutationType { name }
171
+ subscriptionType { name }
172
+ types {
173
+ kind name description
174
+ fields(includeDeprecated: false) {
175
+ name description
176
+ args { name description type { ...TypeRef } defaultValue }
177
+ type { ...TypeRef }
178
+ }
179
+ inputFields { name description type { ...TypeRef } defaultValue }
180
+ enumValues(includeDeprecated: false) { name description }
181
+ }
182
+ }
183
+ }
184
+ fragment TypeRef on __Type {
185
+ kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } }
186
+ }`;
187
+
188
+ // src/adapters/graphql/query-builder.ts
189
+ var DEFAULT_MAX_DEPTH = 2;
190
+ function formatTypeRef(ref) {
191
+ if (ref.kind === TypeKind.NON_NULL) {
192
+ return ref.ofType ? `${formatTypeRef(ref.ofType)}!` : "unknown!";
193
+ }
194
+ if (ref.kind === TypeKind.LIST) {
195
+ return ref.ofType ? `[${formatTypeRef(ref.ofType)}]` : "[unknown]";
196
+ }
197
+ return ref.name ?? "unknown";
198
+ }
199
+ function unwrapType(ref) {
200
+ let current = ref;
201
+ while ((current.kind === TypeKind.NON_NULL || current.kind === TypeKind.LIST) && current.ofType) {
202
+ current = current.ofType;
203
+ }
204
+ return current;
205
+ }
206
+ function isNonNull(ref) {
207
+ return ref.kind === TypeKind.NON_NULL;
208
+ }
209
+ function buildQueryString(operationType, field, typeMap, maxDepth = DEFAULT_MAX_DEPTH) {
210
+ const varDefs = buildVariableDefinitions(field.args);
211
+ const fieldArgs = buildFieldArguments(field.args);
212
+ const selection = buildSelectionSet(field.type, typeMap, maxDepth, /* @__PURE__ */ new Set());
213
+ const varPart = varDefs ? `(${varDefs})` : "";
214
+ const argPart = fieldArgs ? `(${fieldArgs})` : "";
215
+ const selPart = selection ? ` ${selection}` : "";
216
+ return `${operationType} ${field.name}${varPart} { ${field.name}${argPart}${selPart} }`;
217
+ }
218
+ function buildVariableDefinitions(args) {
219
+ if (args.length === 0) return "";
220
+ return args.map((a) => `$${a.name}: ${formatTypeRef(a.type)}`).join(", ");
221
+ }
222
+ function buildFieldArguments(args) {
223
+ if (args.length === 0) return "";
224
+ return args.map((a) => `${a.name}: $${a.name}`).join(", ");
225
+ }
226
+ function buildSelectionSet(typeRef, typeMap, depth, visited) {
227
+ const base = unwrapType(typeRef);
228
+ if (!base.name) return "";
229
+ const type = typeMap.get(base.name);
230
+ if (!type || !type.fields) return "";
231
+ if (base.kind === TypeKind.UNION || base.kind === TypeKind.INTERFACE) {
232
+ return buildUnionSelection();
233
+ }
234
+ if (base.kind !== TypeKind.OBJECT) return "";
235
+ if (visited.has(base.name)) return "";
236
+ visited.add(base.name);
237
+ const fields = [];
238
+ for (const f of type.fields) {
239
+ const fieldBase = unwrapType(f.type);
240
+ if (fieldBase.kind === TypeKind.SCALAR || fieldBase.kind === TypeKind.ENUM) {
241
+ fields.push(f.name);
242
+ } else if (depth > 0 && (fieldBase.kind === TypeKind.OBJECT || fieldBase.kind === TypeKind.UNION || fieldBase.kind === TypeKind.INTERFACE)) {
243
+ const nested = buildSelectionSet(f.type, typeMap, depth - 1, new Set(visited));
244
+ if (nested) {
245
+ fields.push(`${f.name} ${nested}`);
246
+ }
247
+ }
248
+ }
249
+ visited.delete(base.name);
250
+ if (fields.length === 0) return "";
251
+ return `{ ${fields.join(" ")} }`;
252
+ }
253
+ function buildUnionSelection(type, typeMap, depth, visited) {
254
+ return "{ __typename }";
255
+ }
256
+
257
+ // src/adapters/graphql/parser.ts
258
+ async function parseGraphQLSchema(input, options) {
259
+ const maxDepth = options?.maxDepth ?? 2;
260
+ let schema;
261
+ let endpoint;
262
+ if (typeof input === "string") {
263
+ if (!input.startsWith("http")) {
264
+ throw new ApiInvokeError({
265
+ kind: ErrorKind.PARSE,
266
+ message: "GraphQL input must be an endpoint URL (starting with http) or an introspection JSON object. SDL parsing is not yet supported.",
267
+ suggestion: 'Pass a URL like "https://api.example.com/graphql" or an introspection result object.',
268
+ retryable: false
269
+ });
270
+ }
271
+ endpoint = options?.endpoint ?? input;
272
+ schema = await fetchIntrospection(input, options?.fetch);
273
+ } else {
274
+ schema = extractSchema(input);
275
+ endpoint = options?.endpoint ?? "/graphql";
276
+ }
277
+ const path = extractPath(endpoint);
278
+ const typeMap = buildTypeMap(schema.types);
279
+ const operations = [];
280
+ if (schema.queryType?.name) {
281
+ const queryType = typeMap.get(schema.queryType.name);
282
+ if (queryType?.fields) {
283
+ for (const field of queryType.fields) {
284
+ operations.push(buildOperation(field, "query", path, typeMap, maxDepth));
285
+ }
286
+ }
287
+ }
288
+ if (schema.mutationType?.name) {
289
+ const mutationType = typeMap.get(schema.mutationType.name);
290
+ if (mutationType?.fields) {
291
+ for (const field of mutationType.fields) {
292
+ operations.push(buildOperation(field, "mutation", path, typeMap, maxDepth));
293
+ }
294
+ }
295
+ }
296
+ if (schema.subscriptionType?.name) {
297
+ const subType = typeMap.get(schema.subscriptionType.name);
298
+ if (subType?.fields) {
299
+ for (const field of subType.fields) {
300
+ operations.push(buildOperation(field, "subscription", path, typeMap, maxDepth));
301
+ }
302
+ }
303
+ }
304
+ const title = extractTitle(endpoint);
305
+ return {
306
+ title,
307
+ version: "1.0.0",
308
+ baseUrl: extractBaseUrl(endpoint),
309
+ operations,
310
+ authSchemes: [],
311
+ specFormat: SpecFormat.GRAPHQL
312
+ };
313
+ }
314
+ async function fetchIntrospection(url, fetchFn) {
315
+ const doFetch = fetchFn ?? globalThis.fetch;
316
+ let response;
317
+ try {
318
+ response = await doFetch(url, {
319
+ method: "POST",
320
+ headers: { "Content-Type": "application/json" },
321
+ body: JSON.stringify({ query: INTROSPECTION_QUERY })
322
+ });
323
+ } catch (err) {
324
+ throw new ApiInvokeError({
325
+ kind: ErrorKind.NETWORK,
326
+ message: `Failed to fetch GraphQL introspection from ${url}: ${err instanceof Error ? err.message : String(err)}`,
327
+ suggestion: "Check the endpoint URL and your network connection.",
328
+ retryable: true
329
+ });
330
+ }
331
+ if (!response.ok) {
332
+ throw new ApiInvokeError({
333
+ kind: ErrorKind.HTTP,
334
+ message: `GraphQL introspection failed with HTTP ${response.status} from ${url}.`,
335
+ suggestion: "Verify the endpoint supports introspection and is accessible.",
336
+ retryable: response.status >= 500,
337
+ status: response.status
338
+ });
339
+ }
340
+ let json;
341
+ const cloned = response.clone();
342
+ try {
343
+ json = await response.json();
344
+ } catch (err) {
345
+ let body;
346
+ try {
347
+ body = (await cloned.text()).slice(0, 200);
348
+ } catch {
349
+ }
350
+ throw new ApiInvokeError({
351
+ kind: ErrorKind.PARSE,
352
+ message: `GraphQL introspection response from ${url} is not valid JSON${err instanceof Error ? `: ${err.message}` : ""}.`,
353
+ suggestion: `The endpoint returned a non-JSON response${body ? ` (starts with: "${body}")` : ""}. Verify it is a GraphQL endpoint.`,
354
+ retryable: false
355
+ });
356
+ }
357
+ return extractSchema(json);
358
+ }
359
+ function extractSchema(obj) {
360
+ const record = obj;
361
+ if (record.__schema && typeof record.__schema === "object") {
362
+ return record.__schema;
363
+ }
364
+ if (record.data && typeof record.data === "object") {
365
+ const data = record.data;
366
+ if (data.__schema && typeof data.__schema === "object") {
367
+ return data.__schema;
368
+ }
369
+ }
370
+ throw new ApiInvokeError({
371
+ kind: ErrorKind.PARSE,
372
+ message: "Invalid GraphQL introspection result: expected { __schema: ... } or { data: { __schema: ... } }.",
373
+ suggestion: "Pass a valid introspection result object or a URL to a GraphQL endpoint.",
374
+ retryable: false
375
+ });
376
+ }
377
+ function buildTypeMap(types) {
378
+ const map = /* @__PURE__ */ new Map();
379
+ for (const type of types) {
380
+ map.set(type.name, type);
381
+ }
382
+ return map;
383
+ }
384
+ function extractPath(endpoint) {
385
+ try {
386
+ return new URL(endpoint).pathname;
387
+ } catch {
388
+ return "/graphql";
389
+ }
390
+ }
391
+ function extractBaseUrl(endpoint) {
392
+ try {
393
+ const url = new URL(endpoint);
394
+ return url.origin;
395
+ } catch {
396
+ return "";
397
+ }
398
+ }
399
+ function extractTitle(endpoint, _schema) {
400
+ try {
401
+ const url = new URL(endpoint);
402
+ return `GraphQL API (${url.hostname})`;
403
+ } catch {
404
+ return "GraphQL API";
405
+ }
406
+ }
407
+ function buildOperation(field, operationType, path, typeMap, maxDepth) {
408
+ const id = operationType === "mutation" ? `mutation_${field.name}` : operationType === "subscription" ? `subscription_${field.name}` : field.name;
409
+ const tags = [operationType];
410
+ const queryString = operationType !== "subscription" ? buildQueryString(operationType, field, typeMap, maxDepth) : void 0;
411
+ const requestBody = buildRequestBody(field.args, typeMap);
412
+ const responseSchema = buildResponseSchema(field.type, typeMap);
413
+ const operation = {
414
+ id,
415
+ path,
416
+ method: HttpMethod.POST,
417
+ summary: field.description ?? `GraphQL ${operationType}: ${field.name}`,
418
+ parameters: [],
419
+ requestBody,
420
+ responseSchema,
421
+ tags
422
+ };
423
+ if (queryString) {
424
+ const argNames = new Set(field.args.map((a) => a.name));
425
+ operation.buildBody = (args) => {
426
+ const variables = {};
427
+ for (const [k, v] of Object.entries(args)) {
428
+ if (argNames.has(k)) variables[k] = v;
429
+ }
430
+ return { query: queryString, variables };
431
+ };
432
+ }
433
+ return operation;
434
+ }
435
+ function buildRequestBody(args, typeMap) {
436
+ if (args.length === 0) return void 0;
437
+ const properties = {};
438
+ const required = [];
439
+ for (const arg of args) {
440
+ properties[arg.name] = mapInputValueToProperty(arg, typeMap);
441
+ if (isNonNull(arg.type)) {
442
+ required.push(arg.name);
443
+ }
444
+ }
445
+ return {
446
+ required: required.length > 0,
447
+ contentType: ContentType.JSON,
448
+ schema: {
449
+ type: "object",
450
+ raw: { type: "object", properties },
451
+ properties,
452
+ required: required.length > 0 ? required : void 0
453
+ }
454
+ };
455
+ }
456
+ function mapInputValueToProperty(input, typeMap) {
457
+ const base = unwrapType(input.type);
458
+ const description = input.description ? `${input.description} (${formatTypeRef(input.type)})` : formatTypeRef(input.type);
459
+ const isList = isListType(input.type);
460
+ if (base.kind === TypeKind.SCALAR) {
461
+ const mapped = mapScalarType(base.name ?? "String");
462
+ const prop = {
463
+ type: isList ? "array" : mapped.type,
464
+ description
465
+ };
466
+ if (mapped.format) prop.format = mapped.format;
467
+ if (input.defaultValue != null) prop.default = input.defaultValue;
468
+ return prop;
469
+ }
470
+ if (base.kind === TypeKind.ENUM) {
471
+ const enumType = typeMap.get(base.name ?? "");
472
+ const enumValues = enumType?.enumValues?.map((e) => e.name);
473
+ return {
474
+ type: isList ? "array" : "string",
475
+ description,
476
+ enum: enumValues
477
+ };
478
+ }
479
+ if (base.kind === TypeKind.INPUT_OBJECT) {
480
+ return {
481
+ type: isList ? "array" : "object",
482
+ description,
483
+ nested: true
484
+ };
485
+ }
486
+ return { type: "string", description };
487
+ }
488
+ function isListType(ref) {
489
+ let current = ref;
490
+ while (current) {
491
+ if (current.kind === TypeKind.LIST) return true;
492
+ current = current.ofType;
493
+ }
494
+ return false;
495
+ }
496
+ function mapScalarType(name) {
497
+ switch (name) {
498
+ case "String":
499
+ case "ID":
500
+ return { type: "string" };
501
+ case "Int":
502
+ return { type: "integer" };
503
+ case "Float":
504
+ return { type: "number" };
505
+ case "Boolean":
506
+ return { type: "boolean" };
507
+ default:
508
+ return { type: "string", format: name.toLowerCase() };
509
+ }
510
+ }
511
+ function buildResponseSchema(typeRef, typeMap) {
512
+ const base = unwrapType(typeRef);
513
+ if (!base.name) return void 0;
514
+ if (base.kind === TypeKind.SCALAR) {
515
+ const mapped = mapScalarType(base.name);
516
+ return { type: mapped.type, format: mapped.format };
517
+ }
518
+ if (base.kind === TypeKind.ENUM) {
519
+ const enumType = typeMap.get(base.name);
520
+ return { type: "string", enum: enumType?.enumValues?.map((e) => e.name) };
521
+ }
522
+ if (base.kind === TypeKind.OBJECT) {
523
+ const objType = typeMap.get(base.name);
524
+ if (!objType?.fields) return { type: "object" };
525
+ const properties = {};
526
+ for (const f of objType.fields) {
527
+ const fieldBase = unwrapType(f.type);
528
+ if (fieldBase.kind === TypeKind.SCALAR) {
529
+ properties[f.name] = mapScalarType(fieldBase.name ?? "String");
530
+ } else if (fieldBase.kind === TypeKind.ENUM) {
531
+ properties[f.name] = { type: "string" };
532
+ } else {
533
+ properties[f.name] = { type: "object" };
534
+ }
535
+ }
536
+ return { type: "object", properties };
537
+ }
538
+ return { type: "object" };
539
+ }
540
+
541
+ export { API_INVOKE_ERROR_NAME, ApiInvokeError, AuthType, ContentType, ErrorKind, HeaderName, HttpMethod, ParamLocation, SpecFormat, authError, corsError, graphqlError, httpError, networkError, parseError, parseGraphQLSchema, timeoutError };
542
+ //# sourceMappingURL=chunk-TXAOTXB4.js.map
543
+ //# sourceMappingURL=chunk-TXAOTXB4.js.map