@sonatel-os/openapi-runtime
v0.2.1
Published
Enterprise-grade, zero-codegen OpenAPI runtime. Load specs dynamically, execute APIs with type-safe validation, mock responses, and robust authentication.
Downloads
22
Maintainers
Readme
🔥 @sonatel-os/openapi-runtime
"We deleted your SDK. You're welcome." 💅
The enterprise-grade, zero-codegen OpenAPI runtime. ✨
Load specs dynamically. Execute APIs instantly. Sleep peacefully. 😴
- Before: 47 generated files, 12,000 lines, 3 merge conflicts, 1 existential crisis 😭
+ After: 1 import, 2 lines, 0 problems, mass enlightenment 🧘Get Started · Features · Auth Magic · API Reference
😤 The Problem
Every time your backend updates their Swagger spec:
1. Run openapi-generator ⏳ (2 minutes of your life)
2. Watch 50 files regenerate 😰 (existential dread intensifies)
3. Resolve merge conflicts 🔥 (45 minutes + therapy needed)
4. Realize half the types broke 😱 (scream internally)
5. Repeat next sprint 💀 (question career choices)😎 The Solution
await runtime.executeAPI({ name: 'users', api: 'getUser', params: { id: 42 } })
// That's it. That's the tweet. 🐦Zero generation. Zero maintenance. Zero drama. 💆
📦 Installation
npm install @sonatel-os/openapi-runtimeyarn add @sonatel-os/openapi-runtimepnpm add @sonatel-os/openapi-runtimeRequirements: Node.js 18+ (we're not savages) 🦖
🚀 Quick Start
Step 1: Load Your Spec 📄
import * as runtime from '@sonatel-os/openapi-runtime'
// From a file
await runtime.save({
name: 'products',
specName: 'products.openapi.json'
})
// Or pass the spec directly (for the rebels 😈)
await runtime.save({
name: 'products',
specName: myOpenAPISpecObject
})Step 2: Call APIs Like a Boss 😎
// GET /products/{id}
const product = await runtime.executeAPI({
name: 'products',
api: 'getProductById', // Uses operationId from your spec
params: { id: 123 }, // Path + query params
headers: { 'X-Mood': 'happy' }
})
console.log(product.body) // Your data, served fresh 🍽️Step 3: There Is No Step 3 🎉
You're done. Go grab a coffee. You've earned it. ☕
⚡ Features That Slap
| Feature | What It Does | Vibe |
|---------|--------------|------|
| 🗂️ Multi-Spec Registry | Load products, users, payments... all at once | Juggler mode 🤹 |
| 🔮 Auto-Auth Magic | Detects env vars, applies tokens automatically | Mind reader |
| ✅ Request Validation | Validate payloads before sending | Preventive care 🏥 |
| 🔍 Response Validation | Validate what comes back | Trust issues (healthy) |
| 🎭 Mock Generation | Generate fake responses from schemas | Backend not ready? No problem |
| 🎣 Interceptors | Hook into request/response lifecycle | Control freak approved |
| ⚛️ React Hooks | useOpenAPI() for the React gang | Hooks, but make it API |
| 📝 TypeScript Ready | Full JSDoc types, auto-generated .d.ts | IntelliSense heaven 😇 |
🔐 Authentication (The Magic Part)
The runtime auto-detects your auth setup. No config? No problem. ✨
🌱 Option 1: Environment Variables (Zero Config)
Just set env vars following this pattern, and watch the magic happen:
# 🎫 Bearer Token
BEARER_PRODUCTS_BEARERAUTH=your-jwt-token
# 🔑 API Key
OAR_PRODUCTS_APIKEY=your-api-key
# 👤 Basic Auth
BASIC_USER_PRODUCTS_BASICAUTH=username
BASIC_PASS_PRODUCTS_BASICAUTH=password
# 🔒 OAuth2 Client Credentials
OAUTH_CLIENT_ID_PRODUCTS_OAUTH2=client-id
OAUTH_CLIENT_SECRET_PRODUCTS_OAUTH2=client-secret
OAUTH_TOKEN_URL_PRODUCTS_OAUTH2=https://auth.example.com/token📐 Pattern:
{TYPE}_{SPECNAME}_{SCHEMENAME}
The runtime reads your spec's securitySchemes, finds matching env vars, and configures everything. You literally do nothing. 🪄
📝 Option 2: YAML Config (More Control)
Create openapi.auth.yml in your project root:
# 🎨 openapi.auth.yml
auth:
products:
schemes:
bearerAuth:
type: http
scheme: bearer
tokenFromEnv: MY_SECRET_TOKEN # 🤫
oauth2:
type: oauth2
flow: client_credentials
tokenUrl: https://auth.example.com/oauth/token
clientIdFromEnv: OAUTH_CLIENT_ID
clientSecretFromEnv: OAUTH_CLIENT_SECRET
scope: "read write"
audience: https://api.example.com💡 Supports
${ENV_VAR}interpolation for the extra fancy folks
🎮 Option 3: Programmatic (Full Control)
import { setAuth, bearer, apiKey, basic, clientCredentials } from '@sonatel-os/openapi-runtime'
setAuth('products', {
// 🎫 Static or dynamic tokens
bearerAuth: bearer({
getToken: async () => await fetchTokenFromVault() // 🔐
}),
// 🔑 API Keys
apiKeyAuth: apiKey({
in: 'header',
name: 'X-API-Key',
getValue: () => process.env.API_KEY
}),
// 🔒 OAuth2 with automatic token refresh
oauth2: clientCredentials({
tokenUrl: 'https://auth.example.com/token',
clientId: 'my-app',
clientSecret: process.env.CLIENT_SECRET,
scope: 'read write'
})
})🛡️ Security note: OAuth token URLs must use HTTPS. We're not playing games with your credentials.
📚 API Reference
🗂️ Spec Management
// ➕ Register a spec
await runtime.save({ name: 'api', specName: 'spec.json', autoAuth: true })
// 🔄 Update a spec
await runtime.update({ name: 'api', specName: 'spec-v2.json' })
// 🗑️ Remove a spec
runtime.remove({ name: 'api' })
// 📋 List all registered specs
runtime.listSpecs() // ['products', 'users', 'payments']
// 🔍 List operations in a spec
runtime.listAPIs({ name: 'products' })
// [{ operationId: 'getProducts', method: 'get', path: '/products' }, ...]🚀 Execution
const response = await runtime.executeAPI({
name: 'products',
api: 'createProduct',
params: { category: 'electronics' }, // 📍 Path/query params
body: { name: 'Widget', price: 9.99 }, // 📦 Request body
headers: { 'X-Request-ID': 'abc123' }, // 📨 Extra headers
qs: { verbose: true } // ❓ Query string
})✅ Validation
// 🔍 Validate a response
const result = runtime.validateResponseData({
name: 'products',
api: 'getProduct',
status: 200,
data: responseData
})
if (!result.valid) {
console.error('💥 Schema violations:', result.errors)
}
// 🛡️ Validate a request body BEFORE sending
const check = runtime.validateRequestData({
name: 'products',
api: 'createProduct',
body: { name: 'Widget' } // Missing required 'price'? 🤔
})🎭 Mocking (Backend Not Ready? We Got You 💪)
// 🎲 Generate a mock response
const mockProduct = runtime.mockAPI({
name: 'products',
api: 'getProduct',
status: 200
})
// { id: 123, name: 'string', price: 0, ... }
// 📝 Get sample request body
const sampleBody = runtime.showRequestSample({
name: 'products',
api: 'createProduct'
})
// 🔬 Inspect the raw contract
const contract = runtime.showContract({
name: 'products',
api: 'getProduct'
})
// { method: 'get', path: '/products/{id}', parameters: [...], responses: {...} }🎣 Interceptors (For Control Freaks 🎛️)
runtime.setInterceptors('products', {
request: async (req) => {
console.log(`📡 Calling ${req.operationId}`)
req.headers['X-Timestamp'] = Date.now()
return req
},
response: async (res) => {
console.log(`✅ Got ${res.status} from ${res.url}`)
return res
},
error: (err) => {
console.error('💥 API Error:', err)
logToSentry(err)
throw err
}
})📨 Headers
// 🌍 Global headers (all specs)
runtime.setGlobalHeaders({
'X-Correlation-ID': generateCorrelationId()
})
// 🎯 Per-spec headers
runtime.setSpecHeaders('products', {
'X-Tenant': 'acme-corp'
})⚛️ React Integration
import { useOpenAPI } from '@sonatel-os/openapi-runtime/react'
function ProductList() {
const api = useOpenAPI('products')
const [products, setProducts] = useState([])
useEffect(() => {
api.executeAPI('getProducts')
.then(res => setProducts(res.body))
}, [])
return <div>{/* render products 🎨 */}</div>
}🔄 With Loading State
import { useOpenAPIWithState } from '@sonatel-os/openapi-runtime/react'
function ProductList() {
const { api, loading, error, execute } = useOpenAPIWithState('products')
const [products, setProducts] = useState([])
const loadProducts = () => execute(
() => api.executeAPI('getProducts'),
(res) => setProducts(res.body)
)
if (loading) return <Spinner /> // ⏳
if (error) return <Error message={error.message} /> // 💥
return <div>{/* render products 🎨 */}</div>
}🚨 Error Handling (The Grown-Up Way)
We don't just throw generic errors. We throw specific errors with attitude:
import {
AuthenticationError, // 🔐
SpecNotFoundError, // 🔍
OperationNotFoundError, // 🎯
ValidationError, // ✅
SecurityError // 🛡️
} from '@sonatel-os/openapi-runtime'
try {
await runtime.executeAPI({ name: 'products', api: 'getProduct', params: { id: 1 } })
} catch (err) {
if (err instanceof AuthenticationError) {
console.log('🔐 Auth failed:', err.failures)
// [{ schemes: ['bearer'], error: 'token expired' }]
}
if (err instanceof ValidationError) {
console.log('❌ Validation errors:', err.errors)
}
if (err instanceof SecurityError) {
console.log('🚨 Security violation:', err.details.violation)
}
}🎁 All errors extend
OpenAPIRuntimeErrorwithcode,details, andtoJSON()for easy logging
▲ Next.js Integration
🖥️ Server Components (Recommended)
// app/products/page.jsx
import 'server-only'
import * as runtime from '@sonatel-os/openapi-runtime'
export default async function ProductsPage() {
await runtime.save({ name: 'products', specName: 'products.json' })
const { body } = await runtime.executeAPI({ name: 'products', api: 'getProducts' })
return <ProductList products={body} /> // 🎨
}⚡ Server Actions
// app/actions.js
'use server'
import * as runtime from '@sonatel-os/openapi-runtime'
export async function getProducts() {
const { body } = await runtime.executeAPI({
name: 'products',
api: 'getProducts'
})
return body // 📦
}⚠️ Warning: Don't import the runtime directly in Client Components. Use Server Actions or API Routes instead.
🏗️ Architecture
src/
├── 📄 index.js # Public API facade
├── 🔢 constants.js # All magic values (none in code!)
├── 💥 errors.js # 9 custom error types
├── 🛠️ utils.js # DRY utilities
├── 📦 core/
│ ├── registry.js # Spec storage (singleton)
│ └── client.js # Swagger client factory
├── 🔐 auth/
│ ├── providers.js # apiKey, bearer, basic, clientCredentials
│ ├── manager.js # Auth application logic
│ └── config.js # YAML config loader
├── ✅ validation/
│ └── validator.js # AJV-based schema validation
├── 🎭 mocking/
│ └── sampler.js # Sample data generation
└── ⚛️ react/
└── useOpenAPI.js # React hooksDesign Principles: 🎯
- Single Responsibility per module
- No circular dependencies
- All magic values in
constants.js - Custom errors for every failure mode
- 100% JSDoc coverage
🛡️ Security
We take security seriously (and so should you):
| Measure | Implementation | |---------|----------------| | 🔒 HTTPS Enforcement | OAuth token URLs must use HTTPS | | 🚨 No Silent Failures | Auth failures throw with full context | | ✅ Input Validation | Spec names validated against safe patterns | | ⏰ Token Caching | OAuth tokens cached with 30s expiry buffer | | 🤫 No Secrets in Code | All secrets via env vars or config files |
⚡ Performance
- 💤 Lazy Loading: Specs loaded on-demand
- 🗄️ Schema Caching: Compiled validators cached per-operation
- 🎫 Token Caching: OAuth tokens cached until expiry
- 🚫 Zero Runtime Codegen: No parsing or generation at runtime
🤝 Contributing
We welcome contributions! This project is part of the Sonatel Open Source initiative. 🧡
# 📥 Clone
git clone https://github.com/sonatel-os/openapi-runtime.git
# 📦 Install
npm install
# 🔧 Dev mode
npm run dev
# 🏗️ Build
npm run build
# 🧪 Test
npm testGuidelines 📋
- 🍴 Fork the repository
- 🌿 Create a feature branch (
git checkout -b feature/amazing-thing) - 🧪 Write tests for new functionality
- ✅ Ensure all tests pass
- 🚀 Submit a PR with a clear description
🗺️ Roadmap
- [x] 🗂️ Multi-spec registry
- [x] 🔮 Auto-auth from env vars
- [x] 📝 YAML config support
- [x] ✅ Request/response validation
- [x] 🎭 Mock generation
- [x] ⚛️ React hooks
- [x] 💥 Custom error types
- [ ] 🔑 OpenID Connect support
- [ ] 🔄 Retry with exponential backoff
- [ ] 🗄️ Request caching layer
- [ ] 🖥️ CLI for spec validation
📜 License
MIT © Sonatel Open Source
🔥 Built with mass frustration, deployed with mass relief 🔥
Stop generating. Start shipping. 🚀
Made with 🧡 by developers who mass mass mass hated regenerating SDKs
