@sonatel-os/openapi-runtime 0.1.1 โ 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 +458 -170
- 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,254 +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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
autoAuth: true // Magic auth enabled
|
|
55
|
-
});
|
|
56
|
-
```
|
|
78
|
+
import * as runtime from '@sonatel-os/openapi-runtime'
|
|
79
|
+
|
|
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
|
-
You
|
|
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
|
-
|
|
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 ๐ |
|
|
83
125
|
|
|
84
|
-
|
|
126
|
+
---
|
|
85
127
|
|
|
86
|
-
|
|
128
|
+
## ๐ Authentication (The Magic Part)
|
|
87
129
|
|
|
88
|
-
|
|
89
|
-
2. **Environment Variables:** Scans `process.env` for matching naming conventions.
|
|
130
|
+
The runtime auto-detects your auth setup. No config? No problem. โจ
|
|
90
131
|
|
|
91
|
-
### 1
|
|
132
|
+
### ๐ฑ Option 1: Environment Variables (Zero Config)
|
|
92
133
|
|
|
93
|
-
|
|
134
|
+
Just set env vars following this pattern, and watch the magic happen:
|
|
94
135
|
|
|
95
|
-
|
|
136
|
+
```bash
|
|
137
|
+
# ๐ซ Bearer Token
|
|
138
|
+
BEARER_PRODUCTS_BEARERAUTH=your-jwt-token
|
|
96
139
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
* **Basic Auth:** `BASIC_USER_MYAPI_BASICAUTH` and `BASIC_PASS_MYAPI_BASICAUTH`
|
|
100
|
-
* **OAuth2 (Client Creds):**
|
|
101
|
-
* `OAUTH_CLIENT_ID_MYAPI_OAUTH`
|
|
102
|
-
* `OAUTH_CLIENT_SECRET_MYAPI_OAUTH`
|
|
103
|
-
* `OAUTH_TOKEN_URL_MYAPI_OAUTH`
|
|
140
|
+
# ๐ API Key
|
|
141
|
+
OAR_PRODUCTS_APIKEY=your-api-key
|
|
104
142
|
|
|
105
|
-
|
|
143
|
+
# ๐ค Basic Auth
|
|
144
|
+
BASIC_USER_PRODUCTS_BASICAUTH=username
|
|
145
|
+
BASIC_PASS_PRODUCTS_BASICAUTH=password
|
|
106
146
|
|
|
107
|
-
|
|
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
|
|
151
|
+
```
|
|
108
152
|
|
|
109
|
-
|
|
153
|
+
> ๐ **Pattern:** `{TYPE}_{SPECNAME}_{SCHEMENAME}`
|
|
110
154
|
|
|
111
|
-
|
|
155
|
+
The runtime reads your spec's `securitySchemes`, finds matching env vars, and configures everything. You literally do nothing. ๐ช
|
|
112
156
|
|
|
113
|
-
|
|
157
|
+
### ๐ Option 2: YAML Config (More Control)
|
|
114
158
|
|
|
115
|
-
|
|
116
|
-
# openapi.auth.yml
|
|
159
|
+
Create `openapi.auth.yml` in your project root:
|
|
117
160
|
|
|
161
|
+
```yaml
|
|
162
|
+
# ๐จ openapi.auth.yml
|
|
118
163
|
auth:
|
|
119
|
-
# Matches the 'name' you passed to runtime.save({ name: 'products', ... })
|
|
120
164
|
products:
|
|
121
165
|
schemes:
|
|
122
|
-
# Matches the name in components.securitySchemes in your Swagger file
|
|
123
166
|
bearerAuth:
|
|
124
167
|
type: http
|
|
125
168
|
scheme: bearer
|
|
126
|
-
tokenFromEnv:
|
|
169
|
+
tokenFromEnv: MY_SECRET_TOKEN # ๐คซ
|
|
127
170
|
|
|
128
|
-
|
|
129
|
-
type: apiKey
|
|
130
|
-
in: header
|
|
131
|
-
name: X-API-KEY
|
|
132
|
-
valueFromEnv: LEGACY_KEY_VAR
|
|
133
|
-
|
|
134
|
-
auth0:
|
|
171
|
+
oauth2:
|
|
135
172
|
type: oauth2
|
|
136
173
|
flow: client_credentials
|
|
137
|
-
tokenUrl: https://
|
|
138
|
-
clientIdFromEnv:
|
|
139
|
-
clientSecretFromEnv:
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
142
179
|
```
|
|
143
180
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
| Type | Config Keys |
|
|
147
|
-
| :--- | :--- |
|
|
148
|
-
| **apiKey** | `in` (header/query), `name`, `value` (hardcoded), `valueFromEnv` |
|
|
149
|
-
| **http (bearer)** | `token`, `tokenFromEnv` |
|
|
150
|
-
| **http (basic)** | `username`, `password`, `usernameFromEnv`, `passwordFromEnv` |
|
|
151
|
-
| **oauth2** | `flow` (only 'client\_credentials'), `tokenUrl`, `clientIdFromEnv`, `clientSecretFromEnv`, `scope`, `audience` |
|
|
152
|
-
|
|
153
|
-
-----
|
|
154
|
-
|
|
155
|
-
### 3\. Manual Binding (Programmatic)
|
|
181
|
+
> ๐ก Supports `${ENV_VAR}` interpolation for the extra fancy folks
|
|
156
182
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
This is useful for Client Components or complex server-side logic.
|
|
183
|
+
### ๐ฎ Option 3: Programmatic (Full Control)
|
|
160
184
|
|
|
161
185
|
```javascript
|
|
162
|
-
import {
|
|
163
|
-
|
|
164
|
-
// 1. Load the spec (disable autoAuth if you want full manual control)
|
|
165
|
-
await save({ name: 'products', specName: 'products.json', autoAuth: false });
|
|
186
|
+
import { setAuth, bearer, apiKey, basic, clientCredentials } from '@sonatel-os/openapi-runtime'
|
|
166
187
|
|
|
167
|
-
// 2. Define providers
|
|
168
|
-
// The keys here must match the security scheme names in your Swagger definition.
|
|
169
188
|
setAuth('products', {
|
|
170
|
-
//
|
|
171
|
-
bearerAuth: bearer({
|
|
172
|
-
|
|
189
|
+
// ๐ซ Static or dynamic tokens
|
|
190
|
+
bearerAuth: bearer({
|
|
191
|
+
getToken: async () => await fetchTokenFromVault() // ๐
|
|
173
192
|
}),
|
|
174
193
|
|
|
175
|
-
//
|
|
176
|
-
internalAuth: bearer({
|
|
177
|
-
getToken: async () => {
|
|
178
|
-
const token = await db.getValidToken();
|
|
179
|
-
return token;
|
|
180
|
-
}
|
|
181
|
-
}),
|
|
182
|
-
|
|
183
|
-
// API Key
|
|
194
|
+
// ๐ API Keys
|
|
184
195
|
apiKeyAuth: apiKey({
|
|
185
196
|
in: 'header',
|
|
186
|
-
name: 'X-
|
|
187
|
-
getValue: () => process.env.
|
|
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'
|
|
188
207
|
})
|
|
189
|
-
})
|
|
208
|
+
})
|
|
190
209
|
```
|
|
191
|
-
-----
|
|
192
210
|
|
|
193
|
-
|
|
211
|
+
> ๐ก๏ธ **Security note:** OAuth token URLs must use HTTPS. We're not playing games with your credentials.
|
|
194
212
|
|
|
195
|
-
|
|
213
|
+
---
|
|
196
214
|
|
|
197
|
-
|
|
215
|
+
## ๐ API Reference
|
|
198
216
|
|
|
199
|
-
|
|
200
|
-
Returns a dummy JSON object based on the schema definition.
|
|
217
|
+
### ๐๏ธ Spec Management
|
|
201
218
|
|
|
202
219
|
```javascript
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
})
|
|
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' })
|
|
228
|
+
|
|
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' }, ...]
|
|
208
235
|
```
|
|
209
236
|
|
|
210
|
-
|
|
211
|
-
The runtime can inspect a contract and generate "safe" dummy data to ping an endpoint.
|
|
237
|
+
### ๐ Execution
|
|
212
238
|
|
|
213
239
|
```javascript
|
|
214
|
-
const
|
|
215
|
-
|
|
240
|
+
const response = await runtime.executeAPI({
|
|
241
|
+
name: 'products',
|
|
242
|
+
api: 'createProduct',
|
|
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
|
+
})
|
|
216
248
|
```
|
|
217
249
|
|
|
218
|
-
###
|
|
219
|
-
|
|
220
|
-
Validate data against the OpenAPI schema without making a network request. Great for form validation.
|
|
250
|
+
### โ
Validation
|
|
221
251
|
|
|
222
252
|
```javascript
|
|
223
|
-
|
|
253
|
+
// ๐ Validate a response
|
|
254
|
+
const result = runtime.validateResponseData({
|
|
224
255
|
name: 'products',
|
|
225
|
-
api: '
|
|
226
|
-
|
|
227
|
-
|
|
256
|
+
api: 'getProduct',
|
|
257
|
+
status: 200,
|
|
258
|
+
data: responseData
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
if (!result.valid) {
|
|
262
|
+
console.error('๐ฅ Schema violations:', result.errors)
|
|
263
|
+
}
|
|
228
264
|
|
|
229
|
-
|
|
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
|
+
})
|
|
230
271
|
```
|
|
231
272
|
|
|
232
|
-
###
|
|
273
|
+
### ๐ญ Mocking (Backend Not Ready? We Got You ๐ช)
|
|
233
274
|
|
|
234
|
-
|
|
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 ๐๏ธ)
|
|
235
299
|
|
|
236
300
|
```javascript
|
|
237
301
|
runtime.setInterceptors('products', {
|
|
238
302
|
request: async (req) => {
|
|
239
|
-
console.log(`๐ก Calling ${req.
|
|
240
|
-
|
|
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
|
|
241
311
|
},
|
|
312
|
+
|
|
242
313
|
error: (err) => {
|
|
243
|
-
console.error(
|
|
244
|
-
|
|
314
|
+
console.error('๐ฅ API Error:', err)
|
|
315
|
+
logToSentry(err)
|
|
316
|
+
throw err
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### ๐จ Headers
|
|
322
|
+
|
|
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
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## โ๏ธ React Integration
|
|
338
|
+
|
|
339
|
+
```javascript
|
|
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
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### ๐ With Loading State
|
|
356
|
+
|
|
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)
|
|
245
401
|
}
|
|
246
|
-
|
|
402
|
+
|
|
403
|
+
if (err instanceof SecurityError) {
|
|
404
|
+
console.log('๐จ Security violation:', err.details.violation)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
247
407
|
```
|
|
248
408
|
|
|
249
|
-
|
|
409
|
+
> ๐ All errors extend `OpenAPIRuntimeError` with `code`, `details`, and `toJSON()` for easy logging
|
|
250
410
|
|
|
251
|
-
|
|
411
|
+
---
|
|
252
412
|
|
|
253
|
-
|
|
413
|
+
## โฒ Next.js Integration
|
|
254
414
|
|
|
255
|
-
|
|
256
|
-
Import directly in `page.jsx` or `layout.jsx`.
|
|
415
|
+
### ๐ฅ๏ธ Server Components (Recommended)
|
|
257
416
|
|
|
258
417
|
```javascript
|
|
259
|
-
|
|
260
|
-
import
|
|
261
|
-
|
|
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
|
+
}
|
|
262
428
|
```
|
|
263
429
|
|
|
264
|
-
|
|
265
|
-
Do **not** import the runtime directly in Client Components (`"use client"`).
|
|
266
|
-
Instead, fetch data in a Server Action or API Route and pass the pure JSON data to your client component.
|
|
430
|
+
### โก Server Actions
|
|
267
431
|
|
|
268
|
-
|
|
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
|
+
---
|
|
269
504
|
|
|
270
505
|
## ๐ค Contributing
|
|
271
506
|
|
|
272
|
-
This project is part of the **Sonatel Open Source** initiative.
|
|
273
|
-
|
|
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*
|
|
274
567
|
|
|
275
|
-
|
|
276
|
-
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
277
|
-
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
278
|
-
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
279
|
-
5. Open a Pull Request
|
|
568
|
+
<br>
|
|
280
569
|
|
|
281
|
-
|
|
570
|
+
<!-- โญ **Star us on GitHub** โ it mass mass mass motivates us to mass mass mass improve! โญ -->
|
|
282
571
|
|
|
283
|
-
|
|
284
|
-
MIT ยฉ [Sonatel](#)
|
|
572
|
+
</div>
|