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/LICENSE +21 -0
- package/README.md +428 -0
- package/dist/chunk-TXAOTXB4.js +543 -0
- package/dist/chunk-TXAOTXB4.js.map +1 -0
- package/dist/index.cjs +2430 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1359 -0
- package/dist/index.d.ts +1359 -0
- package/dist/index.js +1783 -0
- package/dist/index.js.map +1 -0
- package/dist/parser-PMZEKQPK.js +3 -0
- package/dist/parser-PMZEKQPK.js.map +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jorge Roa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
# api-invoke
|
|
2
|
+
|
|
3
|
+
**Call any REST or GraphQL API at runtime. No code generation. No build step.**
|
|
4
|
+
|
|
5
|
+
Think of it as **reflection for APIs** — discover and invoke any API's operations at runtime, whether from a full spec, a GraphQL endpoint, or just a URL, no code generation required.
|
|
6
|
+
|
|
7
|
+
Give it an OpenAPI spec (v2 or v3), a GraphQL endpoint, a raw URL, or a manually defined endpoint — `api-invoke` parses it into a uniform interface, handles authentication, builds requests, classifies errors, and executes calls. Works in Node.js and the browser.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { createClient } from 'api-invoke'
|
|
11
|
+
|
|
12
|
+
// Point at any OpenAPI spec → ready to call
|
|
13
|
+
const github = await createClient('https://api.github.com/openapi.json')
|
|
14
|
+
const stripe = await createClient('https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json')
|
|
15
|
+
|
|
16
|
+
// GraphQL endpoint → introspects schema, one operation per field
|
|
17
|
+
const countries = await createClient('https://countries.trevorblades.com/graphql')
|
|
18
|
+
|
|
19
|
+
// Or just a URL — no spec needed
|
|
20
|
+
const weather = await createClient('https://api.open-meteo.com/v1/forecast?latitude=40.71&longitude=-74.01')
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Why
|
|
24
|
+
|
|
25
|
+
Most API clients are generated at build time from a spec — you run a CLI, it spits out typed functions, and you commit the result. This works well when you know your APIs ahead of time.
|
|
26
|
+
|
|
27
|
+
But some applications need to connect to APIs they've never seen before:
|
|
28
|
+
|
|
29
|
+
- **AI agents** that discover and call APIs on behalf of users
|
|
30
|
+
- **API explorers and testing tools** that load any spec and let users make live calls
|
|
31
|
+
- **Integration platforms** that connect to customer-provided endpoints at runtime
|
|
32
|
+
- **Internal tooling** that needs to hit dozens of microservices without maintaining a client for each
|
|
33
|
+
|
|
34
|
+
For these cases, you need a library that can take a spec (or just a URL), understand what operations are available, and execute them — all at runtime, with no code generation step.
|
|
35
|
+
|
|
36
|
+
`api-invoke` does exactly that. It parses OpenAPI 2 (Swagger), OpenAPI 3, and GraphQL schemas into a spec-agnostic intermediate representation, then executes operations against the live API with built-in auth injection, error classification, middleware, and CORS handling.
|
|
37
|
+
|
|
38
|
+
## How It Compares
|
|
39
|
+
|
|
40
|
+
There are many ways to call APIs from JavaScript — from raw HTTP clients to full code generators. Here's how `api-invoke` fits into the landscape.
|
|
41
|
+
|
|
42
|
+
### Feature comparison
|
|
43
|
+
|
|
44
|
+
| Feature | fetch / axios | got / ky | openapi‑generator | @hey‑api | openapi‑fetch | swagger‑client | openapi‑client‑axios | **api‑invoke** |
|
|
45
|
+
|:--------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|
46
|
+
| No build step required | ✅ | ✅ | ❌ | ❌ | partial ¹ | ✅ | ✅ | ✅ |
|
|
47
|
+
| Parses OpenAPI 3 | — | — | build | build | build ¹ | ✅ | ✅ | ✅ |
|
|
48
|
+
| Parses Swagger 2 | — | — | build | build | ❌ | ✅ | ❌ | ✅ |
|
|
49
|
+
| GraphQL introspection | — | — | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
50
|
+
| Works without any spec | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
51
|
+
| Auto-detects input type | — | — | — | — | — | ❌ | ❌ | ✅ |
|
|
52
|
+
| Operation discovery | ❌ | ❌ | static | static | static | ✅ | ✅ | ✅ |
|
|
53
|
+
| Auth injection from spec | ❌ | ❌ | generated | generated | ❌ | ✅ | ❌ | ✅ |
|
|
54
|
+
| Error classification | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
55
|
+
| Built-in retry | ❌ | ✅ | — | — | ❌ | ❌ | ❌ | ✅ |
|
|
56
|
+
| CORS detection + proxy | ❌ | N/A | — | — | ❌ | ❌ | ❌ | ✅ |
|
|
57
|
+
| Middleware / hooks | interceptors | hooks | — | — | middleware | interceptors | interceptors | ✅ |
|
|
58
|
+
| Manual API definitions | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
59
|
+
| SSE streaming | manual ⁴ | manual ⁴ | ❌ | manual ⁴ | manual ⁴ | ❌ | ❌ | ✅ |
|
|
60
|
+
| Browser + Node.js | ✅ | partial ² | N/A | N/A | ✅ | ✅ | ✅ | ✅ |
|
|
61
|
+
| Static TypeScript types | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | runtime ³ |
|
|
62
|
+
|
|
63
|
+
<sup>¹ openapi-fetch makes runtime fetch calls, but requires running openapi-typescript at build time to generate types.</sup><br/>
|
|
64
|
+
<sup>² got is Node-only; ky is browser-first. Neither works universally out of the box.</sup><br/>
|
|
65
|
+
<sup>³ api-invoke provides TypeScript types for its own API (ParsedAPI, Operation, etc.) but does not generate per-endpoint types from specs. If you know your APIs at build time, code generators give you better IntelliSense.</sup><br/>
|
|
66
|
+
<sup>⁴ Raw stream access is available, but you must bring your own SSE parser (e.g. eventsource-parser). api-invoke includes a built-in WHATWG-compliant SSE parser returning `AsyncIterable<SSEEvent>`.</sup>
|
|
67
|
+
|
|
68
|
+
### Positioning
|
|
69
|
+
|
|
70
|
+
<p align="center">
|
|
71
|
+
<img src="assets/positioning.svg" alt="Where api-invoke fits — quadrant chart showing spec awareness vs runtime/build-time" width="680"/>
|
|
72
|
+
</p>
|
|
73
|
+
|
|
74
|
+
`api-invoke` is the only tool that works across the entire top row — from raw URLs with no spec to full OpenAPI and GraphQL parsing, all at runtime.
|
|
75
|
+
|
|
76
|
+
### Trade-offs
|
|
77
|
+
|
|
78
|
+
We believe in being upfront about where other tools are the better choice:
|
|
79
|
+
|
|
80
|
+
- **Static types** — Code generators like [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts) and [orval](https://orval.dev/) give you full IntelliSense with endpoint-specific types. If you know your APIs at build time, they provide better TypeScript DX. `api-invoke` trades compile-time type safety for runtime flexibility.
|
|
81
|
+
- **OAuth flows** — `api-invoke` intentionally accepts pre-obtained tokens — it doesn't implement auth code, device, or client-credentials flows. Platforms like [Composio](https://composio.dev/) and [Superface](https://superface.ai/) handle the full OAuth lifecycle.
|
|
82
|
+
- **Streaming** — `api-invoke` supports SSE (Server-Sent Events) streaming, which covers most LLM APIs. Raw chunked transfer or WebSocket streaming is not yet supported.
|
|
83
|
+
- **Language support** — [openapi-generator](https://openapi-generator.tech/) covers 40+ languages. `api-invoke` is JavaScript/TypeScript only.
|
|
84
|
+
- **Managed platforms** — Tools like [Superface OneSDK](https://superface.ai/) and [Composio](https://composio.dev/) solve a different problem: managed integration platforms with pre-built connectors, auth management, and monitoring. `api-invoke` is a library you embed in your own code.
|
|
85
|
+
|
|
86
|
+
## Install
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm install api-invoke
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Quick Start
|
|
93
|
+
|
|
94
|
+
### From an OpenAPI spec
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { createClient } from 'api-invoke'
|
|
98
|
+
|
|
99
|
+
const spotify = await createClient(
|
|
100
|
+
'https://developer.spotify.com/_data/documentation/web-api/reference/open-api-schema.yml'
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
// Discover all available operations
|
|
104
|
+
console.log(spotify.operations.map(op => `${op.method} ${op.path}`))
|
|
105
|
+
// ['GET /albums/{id}', 'GET /artists/{id}', 'GET /search', ...]
|
|
106
|
+
|
|
107
|
+
// Authenticate and execute
|
|
108
|
+
spotify.setAuth({ type: 'bearer', token: process.env.SPOTIFY_TOKEN })
|
|
109
|
+
|
|
110
|
+
const result = await spotify.execute('search', {
|
|
111
|
+
q: 'kind of blue',
|
|
112
|
+
type: 'album',
|
|
113
|
+
limit: 5,
|
|
114
|
+
})
|
|
115
|
+
console.log(result.status) // 200
|
|
116
|
+
console.log(result.data) // { albums: { items: [...] } }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### From a raw URL (no spec)
|
|
120
|
+
|
|
121
|
+
No spec? No problem. Pass any URL and `api-invoke` creates a single `query` operation. Query parameters in the URL become configurable operation parameters with their original values as defaults.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const weather = await createClient(
|
|
125
|
+
'https://api.open-meteo.com/v1/forecast?latitude=48.85&longitude=2.35¤t_weather=true'
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
// Execute with defaults from the URL
|
|
129
|
+
const paris = await weather.execute('query')
|
|
130
|
+
console.log(paris.data) // { current_weather: { temperature: 18.2, ... } }
|
|
131
|
+
|
|
132
|
+
// Override parameters
|
|
133
|
+
const tokyo = await weather.execute('query', {
|
|
134
|
+
latitude: 35.68,
|
|
135
|
+
longitude: 139.69,
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### From a GraphQL endpoint
|
|
140
|
+
|
|
141
|
+
Point at any GraphQL endpoint — `api-invoke` runs an introspection query and creates one operation per query/mutation field. Arguments become typed parameters, and queries are auto-generated with depth-limited selection sets.
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const countries = await createClient('https://countries.trevorblades.com/graphql')
|
|
145
|
+
|
|
146
|
+
// Each GraphQL field becomes an operation
|
|
147
|
+
console.log(countries.operations.map(op => op.id))
|
|
148
|
+
// ['continents', 'continent', 'countries', 'country', 'languages', 'language']
|
|
149
|
+
|
|
150
|
+
const result = await countries.execute('country', { code: 'BR' })
|
|
151
|
+
console.log(result.data) // { data: { country: { name: 'Brazil', capital: 'Brasília', ... } } }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
GraphQL returns HTTP 200 even on errors. Use the error helpers to check:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { throwOnGraphQLErrors, getGraphQLErrors } from 'api-invoke'
|
|
158
|
+
|
|
159
|
+
const result = await countries.execute('country', { code: 'INVALID' })
|
|
160
|
+
throwOnGraphQLErrors(result) // throws ApiInvokeError for total failures (no data)
|
|
161
|
+
|
|
162
|
+
// For partial errors (data + errors both present), inspect manually:
|
|
163
|
+
const errors = getGraphQLErrors(result)
|
|
164
|
+
if (errors.length > 0) console.warn('Partial errors:', errors)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### With authentication
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const client = await createClient('https://api.stripe.com/openapi/spec3.json')
|
|
171
|
+
|
|
172
|
+
// Bearer token
|
|
173
|
+
client.setAuth({ type: 'bearer', token: 'sk_live_...' })
|
|
174
|
+
|
|
175
|
+
// API key (header or query)
|
|
176
|
+
client.setAuth({ type: 'apiKey', location: 'header', name: 'X-API-Key', value: 'secret' })
|
|
177
|
+
|
|
178
|
+
// Basic auth
|
|
179
|
+
client.setAuth({ type: 'basic', username: 'user', password: 'pass' })
|
|
180
|
+
|
|
181
|
+
const result = await client.execute('listCustomers', { limit: 10 })
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Examples
|
|
185
|
+
|
|
186
|
+
The [`examples/`](./examples) folder has runnable scripts demonstrating each feature:
|
|
187
|
+
|
|
188
|
+
| Example | What it shows |
|
|
189
|
+
|---------|--------------|
|
|
190
|
+
| [`01-quick-start.ts`](./examples/01-quick-start.ts) | Parse a spec, discover operations, execute a call |
|
|
191
|
+
| [`02-raw-url.ts`](./examples/02-raw-url.ts) | Call any URL with no spec |
|
|
192
|
+
| [`03-parser-executor.ts`](./examples/03-parser-executor.ts) | Use parser and executor separately |
|
|
193
|
+
| [`04-discover-operations.ts`](./examples/04-discover-operations.ts) | Browse operations, parameters, and auth schemes |
|
|
194
|
+
| [`05-authentication.ts`](./examples/05-authentication.ts) | Full auth lifecycle: Bearer, Basic, API key, OAuth2, Cookie |
|
|
195
|
+
| [`06-error-handling.ts`](./examples/06-error-handling.ts) | Error classification and non-throwing mode |
|
|
196
|
+
| [`07-middleware.ts`](./examples/07-middleware.ts) | Retry, logging, and custom middleware |
|
|
197
|
+
| [`08-streaming.ts`](./examples/08-streaming.ts) | Stream real-time SSE events (Wikimedia live edits) |
|
|
198
|
+
| [`browser/index.html`](./examples/browser/index.html) | Browser usage with CORS proxy |
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
npm run build && npx tsx examples/01-quick-start.ts
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Three Tiers of Usage
|
|
205
|
+
|
|
206
|
+
### Tier 1: High-level client (recommended)
|
|
207
|
+
|
|
208
|
+
`createClient` auto-detects the input type (spec URL, GraphQL endpoint, raw URL, or spec object) and returns a ready-to-use client.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const client = await createClient('https://api.github.com/openapi.json')
|
|
212
|
+
const repos = await client.execute('listRepos', { per_page: 5 })
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Tier 2: Parser + executor (more control)
|
|
216
|
+
|
|
217
|
+
Use the parser and executor separately when you need to inspect or transform the parsed API before executing.
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import { parseOpenAPISpec, executeOperation } from 'api-invoke'
|
|
221
|
+
|
|
222
|
+
const api = await parseOpenAPISpec(specObject)
|
|
223
|
+
|
|
224
|
+
// Inspect operations, filter, transform...
|
|
225
|
+
const op = api.operations.find(o => o.id === 'getAlbum')!
|
|
226
|
+
|
|
227
|
+
const result = await executeOperation(api.baseUrl, op, { id: '4aawyAB9vmqN3uQ7FjRGTy' })
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Tier 3: Raw execution (zero spec)
|
|
231
|
+
|
|
232
|
+
For one-off calls where you don't have or need a spec. Still gets error classification, response parsing, and timing.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { executeRaw } from 'api-invoke'
|
|
236
|
+
|
|
237
|
+
const result = await executeRaw('https://api.spacexdata.com/v4/launches/latest')
|
|
238
|
+
console.log(result.data) // { name: 'Crew-9', ... }
|
|
239
|
+
console.log(result.elapsedMs) // 142
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Streaming (SSE)
|
|
243
|
+
|
|
244
|
+
For APIs that return Server-Sent Events — LLM token streaming, live feeds, real-time notifications — use the streaming variants. They return an `AsyncIterable<SSEEvent>` you can consume with `for await`.
|
|
245
|
+
|
|
246
|
+
### Client streaming
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import { ApiInvokeClient, defineAPI } from 'api-invoke'
|
|
250
|
+
|
|
251
|
+
const api = defineAPI('Wikimedia EventStreams')
|
|
252
|
+
.baseUrl('https://stream.wikimedia.org')
|
|
253
|
+
.get('/v2/stream/recentchange', { id: 'recentChanges' })
|
|
254
|
+
.build()
|
|
255
|
+
|
|
256
|
+
const client = new ApiInvokeClient(api)
|
|
257
|
+
const result = await client.executeStream('recentChanges')
|
|
258
|
+
|
|
259
|
+
for await (const event of result.stream) {
|
|
260
|
+
const change = JSON.parse(event.data)
|
|
261
|
+
console.log(`[${change.wiki}] ${change.type}: "${change.title}" by ${change.user}`)
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Raw streaming (no spec)
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { executeRawStream } from 'api-invoke'
|
|
269
|
+
|
|
270
|
+
const result = await executeRawStream('https://api.openai.com/v1/chat/completions', {
|
|
271
|
+
body: JSON.stringify({ model: 'gpt-4o', messages: [{ role: 'user', content: 'Hi' }], stream: true }),
|
|
272
|
+
auth: { type: 'bearer', token: process.env.OPENAI_API_KEY! },
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
for await (const event of result.stream) {
|
|
276
|
+
if (event.data === '[DONE]') break
|
|
277
|
+
const chunk = JSON.parse(event.data)
|
|
278
|
+
process.stdout.write(chunk.choices[0]?.delta?.content ?? '')
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Each `SSEEvent` gives you the raw fields from the stream:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
interface SSEEvent {
|
|
286
|
+
data: string // The data field (always present)
|
|
287
|
+
event?: string // Named event type
|
|
288
|
+
id?: string // Last event ID
|
|
289
|
+
retry?: number // Reconnection interval (ms)
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Middleware
|
|
294
|
+
|
|
295
|
+
Middleware hooks into the request/response lifecycle:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import { createClient, withRetry, corsProxy, logging } from 'api-invoke'
|
|
299
|
+
|
|
300
|
+
const client = await createClient(specUrl, {
|
|
301
|
+
middleware: [
|
|
302
|
+
withRetry({ maxRetries: 3 }),
|
|
303
|
+
corsProxy(),
|
|
304
|
+
logging({ logger: console.log }),
|
|
305
|
+
],
|
|
306
|
+
})
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Built-in middleware
|
|
310
|
+
|
|
311
|
+
| Middleware | Description |
|
|
312
|
+
|---|---|
|
|
313
|
+
| `withRetry(options?)` | Exponential backoff with Retry-After support |
|
|
314
|
+
| `corsProxy(options?)` | Rewrite URLs through a CORS proxy |
|
|
315
|
+
| `logging(options?)` | Log requests, responses, and errors |
|
|
316
|
+
|
|
317
|
+
### Custom middleware
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
import type { Middleware } from 'api-invoke'
|
|
321
|
+
|
|
322
|
+
const timing: Middleware = {
|
|
323
|
+
name: 'timing',
|
|
324
|
+
async onRequest(url, init) {
|
|
325
|
+
console.log(`-> ${init.method} ${url}`)
|
|
326
|
+
return { url, init }
|
|
327
|
+
},
|
|
328
|
+
async onResponse(response) {
|
|
329
|
+
console.log(`<- ${response.status}`)
|
|
330
|
+
return response
|
|
331
|
+
},
|
|
332
|
+
onError(error) {
|
|
333
|
+
console.error('Request failed:', error.message)
|
|
334
|
+
},
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Error Handling
|
|
339
|
+
|
|
340
|
+
Every error is an `ApiInvokeError` with a `kind` for programmatic handling, a human-readable `suggestion`, and a `retryable` flag:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
import { ApiInvokeError, ErrorKind } from 'api-invoke'
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
await client.execute('getUser', { id: '123' })
|
|
347
|
+
} catch (err) {
|
|
348
|
+
if (err instanceof ApiInvokeError) {
|
|
349
|
+
switch (err.kind) {
|
|
350
|
+
case ErrorKind.AUTH: // 401/403 — bad credentials or insufficient permissions
|
|
351
|
+
case ErrorKind.CORS: // Blocked by browser CORS policy
|
|
352
|
+
case ErrorKind.NETWORK: // Connection failed, DNS error, etc.
|
|
353
|
+
case ErrorKind.HTTP: // Other 4xx/5xx responses
|
|
354
|
+
case ErrorKind.RATE_LIMIT: // 429 — too many requests
|
|
355
|
+
case ErrorKind.TIMEOUT: // Request timed out
|
|
356
|
+
case ErrorKind.PARSE: // Response was not valid JSON
|
|
357
|
+
}
|
|
358
|
+
console.log(err.suggestion) // "Check your credentials. The server rejected your authentication."
|
|
359
|
+
console.log(err.retryable) // true for network/rate-limit/timeout, false for auth/cors/parse
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
To get error responses as data instead of exceptions:
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
const result = await executeOperation(baseUrl, operation, args, {
|
|
368
|
+
throwOnHttpError: false,
|
|
369
|
+
})
|
|
370
|
+
// result.status may be 404, 500, etc. — data is still parsed
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Execution Result
|
|
374
|
+
|
|
375
|
+
Every execution returns an `ExecutionResult`:
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
interface ExecutionResult {
|
|
379
|
+
status: number // HTTP status code
|
|
380
|
+
data: unknown // Parsed response body
|
|
381
|
+
headers: Record<string, string> // Response headers
|
|
382
|
+
request: { method: string; url: string; headers: Record<string, string> } // What was sent
|
|
383
|
+
elapsedMs: number // Round-trip time in ms
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Types
|
|
388
|
+
|
|
389
|
+
The parsed API uses spec-agnostic types that work regardless of the source format:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import type {
|
|
393
|
+
ParsedAPI, // Parsed spec with operations, auth schemes, and metadata
|
|
394
|
+
Operation, // Single API operation (id, path, method, parameters, body)
|
|
395
|
+
Parameter, // Path, query, header, or cookie parameter with schema
|
|
396
|
+
RequestBody, // POST/PUT/PATCH body definition
|
|
397
|
+
Auth, // Authentication credentials (bearer, basic, apiKey, oauth2)
|
|
398
|
+
AuthScheme, // Auth scheme detected from the spec
|
|
399
|
+
ExecutionResult, // Response from an executed operation
|
|
400
|
+
SSEEvent, // Single event from an SSE stream
|
|
401
|
+
StreamingExecutionResult, // Response from a streaming execution
|
|
402
|
+
Middleware, // Request/response interceptor
|
|
403
|
+
Enricher, // Post-parse API transformer
|
|
404
|
+
} from 'api-invoke'
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Local Development
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
git clone https://github.com/jorgeroa/api-invoke.git
|
|
411
|
+
cd api-invoke
|
|
412
|
+
pnpm install
|
|
413
|
+
pnpm build
|
|
414
|
+
pnpm test
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
To test local changes in a project that depends on `api-invoke`:
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
cd /path/to/your-project
|
|
421
|
+
pnpm link /path/to/api-invoke
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
This creates a symlink — edits to `api-invoke` source are reflected instantly. Re-run `pnpm link` after every `pnpm install` in the consumer project.
|
|
425
|
+
|
|
426
|
+
## License
|
|
427
|
+
|
|
428
|
+
MIT
|