@sonatel-os/openapi-runtime 0.1.0 โ 0.2.1
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/README.md +486 -105
- package/dist/errors.cjs +154 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.esm.js +154 -0
- package/dist/errors.esm.js.map +1 -0
- package/dist/index.cjs +557 -5453
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -74
- package/dist/index.esm.js +560 -5455
- package/dist/index.esm.js.map +1 -1
- package/dist/react/index.cjs +129 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.esm.js +129 -0
- package/dist/react/index.esm.js.map +1 -0
- package/dist/src/auth/config.d.ts +170 -0
- package/dist/src/auth/index.d.ts +3 -0
- package/dist/src/auth/manager.d.ts +51 -0
- package/dist/src/auth/providers.d.ts +178 -0
- package/dist/src/constants.d.ts +30 -0
- package/dist/src/core/client.d.ts +83 -0
- package/dist/src/core/registry.d.ts +116 -0
- package/dist/src/errors.d.ts +126 -0
- package/dist/src/index.d.ts +230 -0
- package/dist/src/mocking/index.d.ts +1 -0
- package/dist/src/mocking/sampler.d.ts +87 -0
- package/dist/src/react/useOpenAPI.d.ts +127 -3
- package/dist/src/utils.d.ts +103 -0
- package/dist/src/validation/index.d.ts +1 -0
- package/dist/src/validation/validator.d.ts +117 -0
- package/package.json +25 -8
- package/dist/src/auth.d.ts +0 -33
- package/dist/src/client.d.ts +0 -20
- package/dist/src/config.d.ts +0 -10
- package/dist/src/http.d.ts +0 -0
- package/dist/src/registry.d.ts +0 -28
- package/dist/src/sampler.d.ts +0 -2
- package/dist/src/validate.d.ts +0 -17
package/README.md
CHANGED
|
@@ -1,29 +1,54 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# ๐ฅ @sonatel-os/openapi-runtime
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-

|
|
7
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
### *"We deleted your SDK. You're welcome."* ๐
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/@sonatel-os/openapi-runtime)
|
|
8
|
+
[](https://www.npmjs.com/package/@sonatel-os/openapi-runtime)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://www.typescriptlang.org/)
|
|
11
|
+
[](#)
|
|
12
|
+
|
|
13
|
+
**The enterprise-grade, zero-codegen OpenAPI runtime.** โจ
|
|
14
|
+
|
|
15
|
+
*Load specs dynamically. Execute APIs instantly. Sleep peacefully.* ๐ด
|
|
16
|
+
|
|
17
|
+
<br>
|
|
18
|
+
|
|
19
|
+
```diff
|
|
20
|
+
- Before: 47 generated files, 12,000 lines, 3 merge conflicts, 1 existential crisis ๐ญ
|
|
21
|
+
+ After: 1 import, 2 lines, 0 problems, mass enlightenment ๐ง
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
<br>
|
|
25
|
+
|
|
26
|
+
[Get Started](#-quick-start) ยท [Features](#-features-that-slap) ยท [Auth Magic](#-authentication-the-magic-part) ยท [API Reference](#-api-reference)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
11
29
|
|
|
12
30
|
---
|
|
13
31
|
|
|
14
|
-
##
|
|
32
|
+
## ๐ค The Problem
|
|
15
33
|
|
|
16
|
-
|
|
17
|
-
1. Backend updates `swagger.json`.
|
|
18
|
-
2. You run `openapi-generator`.
|
|
19
|
-
3. You wait.
|
|
20
|
-
4. You get 50 new files and a git conflict.
|
|
21
|
-
5. Repeat.
|
|
34
|
+
Every time your backend updates their Swagger spec:
|
|
22
35
|
|
|
23
|
-
|
|
24
|
-
1.
|
|
25
|
-
2.
|
|
26
|
-
3.
|
|
36
|
+
```
|
|
37
|
+
1. Run openapi-generator โณ (2 minutes of your life)
|
|
38
|
+
2. Watch 50 files regenerate ๐ฐ (existential dread intensifies)
|
|
39
|
+
3. Resolve merge conflicts ๐ฅ (45 minutes + therapy needed)
|
|
40
|
+
4. Realize half the types broke ๐ฑ (scream internally)
|
|
41
|
+
5. Repeat next sprint ๐ (question career choices)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## ๐ The Solution
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
await runtime.executeAPI({ name: 'users', api: 'getUser', params: { id: 42 } })
|
|
48
|
+
// That's it. That's the tweet. ๐ฆ
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Zero generation. Zero maintenance. Zero drama.** ๐
|
|
27
52
|
|
|
28
53
|
---
|
|
29
54
|
|
|
@@ -31,161 +56,517 @@ Consume APIs, validate payloads, and mock responses instantly ; without regenera
|
|
|
31
56
|
|
|
32
57
|
```bash
|
|
33
58
|
npm install @sonatel-os/openapi-runtime
|
|
34
|
-
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```bash
|
|
35
62
|
yarn add @sonatel-os/openapi-runtime
|
|
36
63
|
```
|
|
37
64
|
|
|
38
|
-
|
|
65
|
+
```bash
|
|
66
|
+
pnpm add @sonatel-os/openapi-runtime
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
> **Requirements:** Node.js 18+ *(we're not savages)* ๐ฆ
|
|
39
70
|
|
|
40
|
-
|
|
71
|
+
---
|
|
41
72
|
|
|
42
|
-
|
|
73
|
+
## ๐ Quick Start
|
|
43
74
|
|
|
44
|
-
|
|
75
|
+
### Step 1: Load Your Spec ๐
|
|
45
76
|
|
|
46
77
|
```javascript
|
|
47
|
-
import * as runtime from '@sonatel-os/openapi-runtime'
|
|
48
|
-
import mySpec from './specs/products.openapi.json'; // or fetch it
|
|
78
|
+
import * as runtime from '@sonatel-os/openapi-runtime'
|
|
49
79
|
|
|
50
|
-
//
|
|
51
|
-
await runtime.save({
|
|
52
|
-
name: 'products',
|
|
53
|
-
specName:
|
|
54
|
-
|
|
55
|
-
});
|
|
56
|
-
```
|
|
80
|
+
// From a file
|
|
81
|
+
await runtime.save({
|
|
82
|
+
name: 'products',
|
|
83
|
+
specName: 'products.openapi.json'
|
|
84
|
+
})
|
|
57
85
|
|
|
58
|
-
|
|
86
|
+
// Or pass the spec directly (for the rebels ๐)
|
|
87
|
+
await runtime.save({
|
|
88
|
+
name: 'products',
|
|
89
|
+
specName: myOpenAPISpecObject
|
|
90
|
+
})
|
|
91
|
+
```
|
|
59
92
|
|
|
60
|
-
|
|
93
|
+
### Step 2: Call APIs Like a Boss ๐
|
|
61
94
|
|
|
62
95
|
```javascript
|
|
63
96
|
// GET /products/{id}
|
|
64
|
-
const
|
|
97
|
+
const product = await runtime.executeAPI({
|
|
65
98
|
name: 'products',
|
|
66
|
-
api: 'getProductById',
|
|
67
|
-
params: { id: 123 },
|
|
68
|
-
|
|
69
|
-
})
|
|
99
|
+
api: 'getProductById', // Uses operationId from your spec
|
|
100
|
+
params: { id: 123 }, // Path + query params
|
|
101
|
+
headers: { 'X-Mood': 'happy' }
|
|
102
|
+
})
|
|
70
103
|
|
|
71
|
-
console.log(
|
|
104
|
+
console.log(product.body) // Your data, served fresh ๐ฝ๏ธ
|
|
72
105
|
```
|
|
73
106
|
|
|
74
|
-
|
|
107
|
+
### Step 3: There Is No Step 3 ๐
|
|
75
108
|
|
|
76
|
-
|
|
109
|
+
You're done. Go grab a coffee. You've earned it. โ
|
|
77
110
|
|
|
78
|
-
|
|
111
|
+
---
|
|
79
112
|
|
|
80
|
-
|
|
113
|
+
## โก Features That Slap
|
|
81
114
|
|
|
82
|
-
|
|
|
83
|
-
|
|
84
|
-
|
|
|
85
|
-
|
|
|
86
|
-
|
|
|
115
|
+
| Feature | What It Does | Vibe |
|
|
116
|
+
|---------|--------------|------|
|
|
117
|
+
| ๐๏ธ **Multi-Spec Registry** | Load products, users, payments... all at once | Juggler mode ๐คน |
|
|
118
|
+
| ๐ฎ **Auto-Auth Magic** | Detects env vars, applies tokens automatically | Mind reader |
|
|
119
|
+
| โ
**Request Validation** | Validate payloads before sending | Preventive care ๐ฅ |
|
|
120
|
+
| ๐ **Response Validation** | Validate what comes back | Trust issues (healthy) |
|
|
121
|
+
| ๐ญ **Mock Generation** | Generate fake responses from schemas | Backend not ready? No problem |
|
|
122
|
+
| ๐ฃ **Interceptors** | Hook into request/response lifecycle | Control freak approved |
|
|
123
|
+
| โ๏ธ **React Hooks** | `useOpenAPI()` for the React gang | Hooks, but make it API |
|
|
124
|
+
| ๐ **TypeScript Ready** | Full JSDoc types, auto-generated .d.ts | IntelliSense heaven ๐ |
|
|
87
125
|
|
|
88
|
-
|
|
89
|
-
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## ๐ Authentication (The Magic Part)
|
|
129
|
+
|
|
130
|
+
The runtime auto-detects your auth setup. No config? No problem. โจ
|
|
131
|
+
|
|
132
|
+
### ๐ฑ Option 1: Environment Variables (Zero Config)
|
|
133
|
+
|
|
134
|
+
Just set env vars following this pattern, and watch the magic happen:
|
|
90
135
|
|
|
91
136
|
```bash
|
|
92
|
-
#
|
|
93
|
-
|
|
137
|
+
# ๐ซ Bearer Token
|
|
138
|
+
BEARER_PRODUCTS_BEARERAUTH=your-jwt-token
|
|
139
|
+
|
|
140
|
+
# ๐ API Key
|
|
141
|
+
OAR_PRODUCTS_APIKEY=your-api-key
|
|
142
|
+
|
|
143
|
+
# ๐ค Basic Auth
|
|
144
|
+
BASIC_USER_PRODUCTS_BASICAUTH=username
|
|
145
|
+
BASIC_PASS_PRODUCTS_BASICAUTH=password
|
|
146
|
+
|
|
147
|
+
# ๐ OAuth2 Client Credentials
|
|
148
|
+
OAUTH_CLIENT_ID_PRODUCTS_OAUTH2=client-id
|
|
149
|
+
OAUTH_CLIENT_SECRET_PRODUCTS_OAUTH2=client-secret
|
|
150
|
+
OAUTH_TOKEN_URL_PRODUCTS_OAUTH2=https://auth.example.com/token
|
|
94
151
|
```
|
|
95
152
|
|
|
96
|
-
|
|
153
|
+
> ๐ **Pattern:** `{TYPE}_{SPECNAME}_{SCHEMENAME}`
|
|
97
154
|
|
|
98
|
-
|
|
155
|
+
The runtime reads your spec's `securitySchemes`, finds matching env vars, and configures everything. You literally do nothing. ๐ช
|
|
99
156
|
|
|
100
|
-
|
|
157
|
+
### ๐ Option 2: YAML Config (More Control)
|
|
101
158
|
|
|
102
|
-
|
|
159
|
+
Create `openapi.auth.yml` in your project root:
|
|
103
160
|
|
|
104
|
-
|
|
161
|
+
```yaml
|
|
162
|
+
# ๐จ openapi.auth.yml
|
|
163
|
+
auth:
|
|
164
|
+
products:
|
|
165
|
+
schemes:
|
|
166
|
+
bearerAuth:
|
|
167
|
+
type: http
|
|
168
|
+
scheme: bearer
|
|
169
|
+
tokenFromEnv: MY_SECRET_TOKEN # ๐คซ
|
|
170
|
+
|
|
171
|
+
oauth2:
|
|
172
|
+
type: oauth2
|
|
173
|
+
flow: client_credentials
|
|
174
|
+
tokenUrl: https://auth.example.com/oauth/token
|
|
175
|
+
clientIdFromEnv: OAUTH_CLIENT_ID
|
|
176
|
+
clientSecretFromEnv: OAUTH_CLIENT_SECRET
|
|
177
|
+
scope: "read write"
|
|
178
|
+
audience: https://api.example.com
|
|
179
|
+
```
|
|
105
180
|
|
|
106
|
-
|
|
107
|
-
|
|
181
|
+
> ๐ก Supports `${ENV_VAR}` interpolation for the extra fancy folks
|
|
182
|
+
|
|
183
|
+
### ๐ฎ Option 3: Programmatic (Full Control)
|
|
108
184
|
|
|
109
185
|
```javascript
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
186
|
+
import { setAuth, bearer, apiKey, basic, clientCredentials } from '@sonatel-os/openapi-runtime'
|
|
187
|
+
|
|
188
|
+
setAuth('products', {
|
|
189
|
+
// ๐ซ Static or dynamic tokens
|
|
190
|
+
bearerAuth: bearer({
|
|
191
|
+
getToken: async () => await fetchTokenFromVault() // ๐
|
|
192
|
+
}),
|
|
193
|
+
|
|
194
|
+
// ๐ API Keys
|
|
195
|
+
apiKeyAuth: apiKey({
|
|
196
|
+
in: 'header',
|
|
197
|
+
name: 'X-API-Key',
|
|
198
|
+
getValue: () => process.env.API_KEY
|
|
199
|
+
}),
|
|
200
|
+
|
|
201
|
+
// ๐ OAuth2 with automatic token refresh
|
|
202
|
+
oauth2: clientCredentials({
|
|
203
|
+
tokenUrl: 'https://auth.example.com/token',
|
|
204
|
+
clientId: 'my-app',
|
|
205
|
+
clientSecret: process.env.CLIENT_SECRET,
|
|
206
|
+
scope: 'read write'
|
|
207
|
+
})
|
|
208
|
+
})
|
|
115
209
|
```
|
|
116
210
|
|
|
117
|
-
**
|
|
118
|
-
|
|
211
|
+
> ๐ก๏ธ **Security note:** OAuth token URLs must use HTTPS. We're not playing games with your credentials.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## ๐ API Reference
|
|
216
|
+
|
|
217
|
+
### ๐๏ธ Spec Management
|
|
119
218
|
|
|
120
219
|
```javascript
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
220
|
+
// โ Register a spec
|
|
221
|
+
await runtime.save({ name: 'api', specName: 'spec.json', autoAuth: true })
|
|
222
|
+
|
|
223
|
+
// ๐ Update a spec
|
|
224
|
+
await runtime.update({ name: 'api', specName: 'spec-v2.json' })
|
|
225
|
+
|
|
226
|
+
// ๐๏ธ Remove a spec
|
|
227
|
+
runtime.remove({ name: 'api' })
|
|
124
228
|
|
|
125
|
-
|
|
229
|
+
// ๐ List all registered specs
|
|
230
|
+
runtime.listSpecs() // ['products', 'users', 'payments']
|
|
231
|
+
|
|
232
|
+
// ๐ List operations in a spec
|
|
233
|
+
runtime.listAPIs({ name: 'products' })
|
|
234
|
+
// [{ operationId: 'getProducts', method: 'get', path: '/products' }, ...]
|
|
235
|
+
```
|
|
126
236
|
|
|
127
|
-
|
|
237
|
+
### ๐ Execution
|
|
128
238
|
|
|
129
239
|
```javascript
|
|
130
|
-
const
|
|
240
|
+
const response = await runtime.executeAPI({
|
|
131
241
|
name: 'products',
|
|
132
242
|
api: 'createProduct',
|
|
133
|
-
|
|
134
|
-
}
|
|
243
|
+
params: { category: 'electronics' }, // ๐ Path/query params
|
|
244
|
+
body: { name: 'Widget', price: 9.99 }, // ๐ฆ Request body
|
|
245
|
+
headers: { 'X-Request-ID': 'abc123' }, // ๐จ Extra headers
|
|
246
|
+
qs: { verbose: true } // โ Query string
|
|
247
|
+
})
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### โ
Validation
|
|
135
251
|
|
|
136
|
-
|
|
252
|
+
```javascript
|
|
253
|
+
// ๐ Validate a response
|
|
254
|
+
const result = runtime.validateResponseData({
|
|
255
|
+
name: 'products',
|
|
256
|
+
api: 'getProduct',
|
|
257
|
+
status: 200,
|
|
258
|
+
data: responseData
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
if (!result.valid) {
|
|
262
|
+
console.error('๐ฅ Schema violations:', result.errors)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ๐ก๏ธ Validate a request body BEFORE sending
|
|
266
|
+
const check = runtime.validateRequestData({
|
|
267
|
+
name: 'products',
|
|
268
|
+
api: 'createProduct',
|
|
269
|
+
body: { name: 'Widget' } // Missing required 'price'? ๐ค
|
|
270
|
+
})
|
|
137
271
|
```
|
|
138
272
|
|
|
139
|
-
###
|
|
273
|
+
### ๐ญ Mocking (Backend Not Ready? We Got You ๐ช)
|
|
140
274
|
|
|
141
|
-
|
|
275
|
+
```javascript
|
|
276
|
+
// ๐ฒ Generate a mock response
|
|
277
|
+
const mockProduct = runtime.mockAPI({
|
|
278
|
+
name: 'products',
|
|
279
|
+
api: 'getProduct',
|
|
280
|
+
status: 200
|
|
281
|
+
})
|
|
282
|
+
// { id: 123, name: 'string', price: 0, ... }
|
|
283
|
+
|
|
284
|
+
// ๐ Get sample request body
|
|
285
|
+
const sampleBody = runtime.showRequestSample({
|
|
286
|
+
name: 'products',
|
|
287
|
+
api: 'createProduct'
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
// ๐ฌ Inspect the raw contract
|
|
291
|
+
const contract = runtime.showContract({
|
|
292
|
+
name: 'products',
|
|
293
|
+
api: 'getProduct'
|
|
294
|
+
})
|
|
295
|
+
// { method: 'get', path: '/products/{id}', parameters: [...], responses: {...} }
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### ๐ฃ Interceptors (For Control Freaks ๐๏ธ)
|
|
142
299
|
|
|
143
300
|
```javascript
|
|
144
301
|
runtime.setInterceptors('products', {
|
|
145
302
|
request: async (req) => {
|
|
146
|
-
console.log(`๐ก Calling ${req.
|
|
147
|
-
|
|
303
|
+
console.log(`๐ก Calling ${req.operationId}`)
|
|
304
|
+
req.headers['X-Timestamp'] = Date.now()
|
|
305
|
+
return req
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
response: async (res) => {
|
|
309
|
+
console.log(`โ
Got ${res.status} from ${res.url}`)
|
|
310
|
+
return res
|
|
148
311
|
},
|
|
312
|
+
|
|
149
313
|
error: (err) => {
|
|
150
|
-
console.error(
|
|
151
|
-
|
|
314
|
+
console.error('๐ฅ API Error:', err)
|
|
315
|
+
logToSentry(err)
|
|
316
|
+
throw err
|
|
152
317
|
}
|
|
153
|
-
})
|
|
318
|
+
})
|
|
154
319
|
```
|
|
155
320
|
|
|
156
|
-
|
|
321
|
+
### ๐จ Headers
|
|
157
322
|
|
|
158
|
-
|
|
323
|
+
```javascript
|
|
324
|
+
// ๐ Global headers (all specs)
|
|
325
|
+
runtime.setGlobalHeaders({
|
|
326
|
+
'X-Correlation-ID': generateCorrelationId()
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
// ๐ฏ Per-spec headers
|
|
330
|
+
runtime.setSpecHeaders('products', {
|
|
331
|
+
'X-Tenant': 'acme-corp'
|
|
332
|
+
})
|
|
333
|
+
```
|
|
159
334
|
|
|
160
|
-
|
|
335
|
+
---
|
|
161
336
|
|
|
162
|
-
|
|
163
|
-
Import directly in `page.jsx` or `layout.jsx`.
|
|
337
|
+
## โ๏ธ React Integration
|
|
164
338
|
|
|
165
339
|
```javascript
|
|
166
|
-
import '
|
|
167
|
-
|
|
168
|
-
|
|
340
|
+
import { useOpenAPI } from '@sonatel-os/openapi-runtime/react'
|
|
341
|
+
|
|
342
|
+
function ProductList() {
|
|
343
|
+
const api = useOpenAPI('products')
|
|
344
|
+
const [products, setProducts] = useState([])
|
|
345
|
+
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
api.executeAPI('getProducts')
|
|
348
|
+
.then(res => setProducts(res.body))
|
|
349
|
+
}, [])
|
|
350
|
+
|
|
351
|
+
return <div>{/* render products ๐จ */}</div>
|
|
352
|
+
}
|
|
169
353
|
```
|
|
170
354
|
|
|
171
|
-
|
|
172
|
-
Do **not** import the runtime directly in Client Components (`"use client"`).
|
|
173
|
-
Instead, fetch data in a Server Action or API Route and pass the pure JSON data to your client component.
|
|
355
|
+
### ๐ With Loading State
|
|
174
356
|
|
|
175
|
-
|
|
357
|
+
```javascript
|
|
358
|
+
import { useOpenAPIWithState } from '@sonatel-os/openapi-runtime/react'
|
|
359
|
+
|
|
360
|
+
function ProductList() {
|
|
361
|
+
const { api, loading, error, execute } = useOpenAPIWithState('products')
|
|
362
|
+
const [products, setProducts] = useState([])
|
|
363
|
+
|
|
364
|
+
const loadProducts = () => execute(
|
|
365
|
+
() => api.executeAPI('getProducts'),
|
|
366
|
+
(res) => setProducts(res.body)
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
if (loading) return <Spinner /> // โณ
|
|
370
|
+
if (error) return <Error message={error.message} /> // ๐ฅ
|
|
371
|
+
|
|
372
|
+
return <div>{/* render products ๐จ */}</div>
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## ๐จ Error Handling (The Grown-Up Way)
|
|
379
|
+
|
|
380
|
+
We don't just throw generic errors. We throw *specific* errors with attitude:
|
|
381
|
+
|
|
382
|
+
```javascript
|
|
383
|
+
import {
|
|
384
|
+
AuthenticationError, // ๐
|
|
385
|
+
SpecNotFoundError, // ๐
|
|
386
|
+
OperationNotFoundError, // ๐ฏ
|
|
387
|
+
ValidationError, // โ
|
|
388
|
+
SecurityError // ๐ก๏ธ
|
|
389
|
+
} from '@sonatel-os/openapi-runtime'
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
await runtime.executeAPI({ name: 'products', api: 'getProduct', params: { id: 1 } })
|
|
393
|
+
} catch (err) {
|
|
394
|
+
if (err instanceof AuthenticationError) {
|
|
395
|
+
console.log('๐ Auth failed:', err.failures)
|
|
396
|
+
// [{ schemes: ['bearer'], error: 'token expired' }]
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (err instanceof ValidationError) {
|
|
400
|
+
console.log('โ Validation errors:', err.errors)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (err instanceof SecurityError) {
|
|
404
|
+
console.log('๐จ Security violation:', err.details.violation)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
> ๐ All errors extend `OpenAPIRuntimeError` with `code`, `details`, and `toJSON()` for easy logging
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## โฒ Next.js Integration
|
|
414
|
+
|
|
415
|
+
### ๐ฅ๏ธ Server Components (Recommended)
|
|
416
|
+
|
|
417
|
+
```javascript
|
|
418
|
+
// app/products/page.jsx
|
|
419
|
+
import 'server-only'
|
|
420
|
+
import * as runtime from '@sonatel-os/openapi-runtime'
|
|
421
|
+
|
|
422
|
+
export default async function ProductsPage() {
|
|
423
|
+
await runtime.save({ name: 'products', specName: 'products.json' })
|
|
424
|
+
const { body } = await runtime.executeAPI({ name: 'products', api: 'getProducts' })
|
|
425
|
+
|
|
426
|
+
return <ProductList products={body} /> // ๐จ
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### โก Server Actions
|
|
431
|
+
|
|
432
|
+
```javascript
|
|
433
|
+
// app/actions.js
|
|
434
|
+
'use server'
|
|
435
|
+
import * as runtime from '@sonatel-os/openapi-runtime'
|
|
436
|
+
|
|
437
|
+
export async function getProducts() {
|
|
438
|
+
const { body } = await runtime.executeAPI({
|
|
439
|
+
name: 'products',
|
|
440
|
+
api: 'getProducts'
|
|
441
|
+
})
|
|
442
|
+
return body // ๐ฆ
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
> โ ๏ธ **Warning:** Don't import the runtime directly in Client Components. Use Server Actions or API Routes instead.
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## ๐๏ธ Architecture
|
|
451
|
+
|
|
452
|
+
```
|
|
453
|
+
src/
|
|
454
|
+
โโโ ๐ index.js # Public API facade
|
|
455
|
+
โโโ ๐ข constants.js # All magic values (none in code!)
|
|
456
|
+
โโโ ๐ฅ errors.js # 9 custom error types
|
|
457
|
+
โโโ ๐ ๏ธ utils.js # DRY utilities
|
|
458
|
+
โโโ ๐ฆ core/
|
|
459
|
+
โ โโโ registry.js # Spec storage (singleton)
|
|
460
|
+
โ โโโ client.js # Swagger client factory
|
|
461
|
+
โโโ ๐ auth/
|
|
462
|
+
โ โโโ providers.js # apiKey, bearer, basic, clientCredentials
|
|
463
|
+
โ โโโ manager.js # Auth application logic
|
|
464
|
+
โ โโโ config.js # YAML config loader
|
|
465
|
+
โโโ โ
validation/
|
|
466
|
+
โ โโโ validator.js # AJV-based schema validation
|
|
467
|
+
โโโ ๐ญ mocking/
|
|
468
|
+
โ โโโ sampler.js # Sample data generation
|
|
469
|
+
โโโ โ๏ธ react/
|
|
470
|
+
โโโ useOpenAPI.js # React hooks
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**Design Principles:** ๐ฏ
|
|
474
|
+
- Single Responsibility per module
|
|
475
|
+
- No circular dependencies
|
|
476
|
+
- All magic values in `constants.js`
|
|
477
|
+
- Custom errors for every failure mode
|
|
478
|
+
- 100% JSDoc coverage
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## ๐ก๏ธ Security
|
|
483
|
+
|
|
484
|
+
We take security seriously (and so should you):
|
|
485
|
+
|
|
486
|
+
| Measure | Implementation |
|
|
487
|
+
|---------|----------------|
|
|
488
|
+
| ๐ **HTTPS Enforcement** | OAuth token URLs must use HTTPS |
|
|
489
|
+
| ๐จ **No Silent Failures** | Auth failures throw with full context |
|
|
490
|
+
| โ
**Input Validation** | Spec names validated against safe patterns |
|
|
491
|
+
| โฐ **Token Caching** | OAuth tokens cached with 30s expiry buffer |
|
|
492
|
+
| ๐คซ **No Secrets in Code** | All secrets via env vars or config files |
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## โก Performance
|
|
497
|
+
|
|
498
|
+
- ๐ค **Lazy Loading:** Specs loaded on-demand
|
|
499
|
+
- ๐๏ธ **Schema Caching:** Compiled validators cached per-operation
|
|
500
|
+
- ๐ซ **Token Caching:** OAuth tokens cached until expiry
|
|
501
|
+
- ๐ซ **Zero Runtime Codegen:** No parsing or generation at runtime
|
|
502
|
+
|
|
503
|
+
---
|
|
176
504
|
|
|
177
505
|
## ๐ค Contributing
|
|
178
506
|
|
|
179
|
-
This project is part of the **Sonatel Open Source** initiative.
|
|
180
|
-
|
|
507
|
+
We welcome contributions! This project is part of the **Sonatel Open Source** initiative. ๐งก
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
# ๐ฅ Clone
|
|
511
|
+
git clone https://github.com/sonatel-os/openapi-runtime.git
|
|
512
|
+
|
|
513
|
+
# ๐ฆ Install
|
|
514
|
+
npm install
|
|
515
|
+
|
|
516
|
+
# ๐ง Dev mode
|
|
517
|
+
npm run dev
|
|
518
|
+
|
|
519
|
+
# ๐๏ธ Build
|
|
520
|
+
npm run build
|
|
521
|
+
|
|
522
|
+
# ๐งช Test
|
|
523
|
+
npm test
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Guidelines ๐
|
|
527
|
+
|
|
528
|
+
1. ๐ด Fork the repository
|
|
529
|
+
2. ๐ฟ Create a feature branch (`git checkout -b feature/amazing-thing`)
|
|
530
|
+
3. ๐งช Write tests for new functionality
|
|
531
|
+
4. โ
Ensure all tests pass
|
|
532
|
+
5. ๐ Submit a PR with a clear description
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## ๐บ๏ธ Roadmap
|
|
537
|
+
|
|
538
|
+
- [x] ๐๏ธ Multi-spec registry
|
|
539
|
+
- [x] ๐ฎ Auto-auth from env vars
|
|
540
|
+
- [x] ๐ YAML config support
|
|
541
|
+
- [x] โ
Request/response validation
|
|
542
|
+
- [x] ๐ญ Mock generation
|
|
543
|
+
- [x] โ๏ธ React hooks
|
|
544
|
+
- [x] ๐ฅ Custom error types
|
|
545
|
+
- [ ] ๐ OpenID Connect support
|
|
546
|
+
- [ ] ๐ Retry with exponential backoff
|
|
547
|
+
- [ ] ๐๏ธ Request caching layer
|
|
548
|
+
- [ ] ๐ฅ๏ธ CLI for spec validation
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## ๐ License
|
|
553
|
+
|
|
554
|
+
MIT ยฉ [Sonatel Open Source](https://github.com/sonatel-os)
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
<div align="center">
|
|
559
|
+
|
|
560
|
+
### ๐ฅ Built with mass frustration, deployed with mass relief ๐ฅ
|
|
561
|
+
|
|
562
|
+
**Stop generating. Start shipping.** ๐
|
|
563
|
+
|
|
564
|
+
<br>
|
|
565
|
+
|
|
566
|
+
*Made with ๐งก by developers who mass mass mass hated regenerating SDKs*
|
|
181
567
|
|
|
182
|
-
|
|
183
|
-
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
184
|
-
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
185
|
-
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
186
|
-
5. Open a Pull Request
|
|
568
|
+
<br>
|
|
187
569
|
|
|
188
|
-
|
|
570
|
+
<!-- โญ **Star us on GitHub** โ it mass mass mass motivates us to mass mass mass improve! โญ -->
|
|
189
571
|
|
|
190
|
-
|
|
191
|
-
MIT ยฉ [Sonatel](#)
|
|
572
|
+
</div>
|