npm package: @sonatel-os/openapi-runtime
Description: Enterprise-grade, zero-codegen OpenAPI runtime. Load specs dynamically, execute APIs with type-safe validation, mock responses, and robust authentication.
Author: Sonatel Open Source
Latest version: 0.2.1

<div align="center">

# 🔥 @sonatel-os/openapi-runtime

### *"We deleted your SDK. You're welcome."* 💅

[![npm version](https://img.shields.io/npm/v/@sonatel-os/openapi-runtime?style=for-the-badge&color=ff6b6b)](https://www.npmjs.com/package/@sonatel-os/openapi-runtime)
[![npm downloads](https://img.shields.io/npm/dm/@sonatel-os/openapi-runtime?style=for-the-badge&color=4ecdc4)](https://www.npmjs.com/package/@sonatel-os/openapi-runtime)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge&color=ffe66d)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg?style=for-the-badge&color=95e1d3)](https://www.typescriptlang.org/)
[![Zero Codegen](https://img.shields.io/badge/Codegen-Zero-green.svg?style=for-the-badge&color=a8e6cf)](#)

**The enterprise-grade, zero-codegen OpenAPI runtime.** ✨

*Load specs dynamically. Execute APIs instantly. Sleep peacefully.* 😴

<br>

```diff
- Before: 47 generated files, 12,000 lines, 3 merge conflicts, 1 existential crisis 😭
+ After:  1 import, 2 lines, 0 problems, mass enlightenment 🧘
```

<br>

[Get Started](#-quick-start) · [Features](#-features-that-slap) · [Auth Magic](#-authentication-the-magic-part) · [API Reference](#-api-reference)

</div>

---

## 😤 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

```javascript
await runtime.executeAPI({ name: 'users', api: 'getUser', params: { id: 42 } })
// That's it. That's the tweet. 🐦
```

**Zero generation. Zero maintenance. Zero drama.** 💆

---

## 📦 Installation

```bash
npm install @sonatel-os/openapi-runtime
```

```bash
yarn add @sonatel-os/openapi-runtime
```

```bash
pnpm add @sonatel-os/openapi-runtime
```

> **Requirements:** Node.js 18+ *(we're not savages)* 🦖

---

## 🚀 Quick Start

### Step 1: Load Your Spec 📄

```javascript
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 😎

```javascript
// 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:

```bash
# 🎫 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:

```yaml
# 🎨 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)

```javascript
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

```javascript
// ➕ 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

```javascript
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

```javascript
// 🔍 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 💪)

```javascript
// 🎲 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 🎛️)

```javascript
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

```javascript
// 🌍 Global headers (all specs)
runtime.setGlobalHeaders({
  'X-Correlation-ID': generateCorrelationId()
})

// 🎯 Per-spec headers
runtime.setSpecHeaders('products', {
  'X-Tenant': 'acme-corp'
})
```

---

## ⚛️ React Integration

```javascript
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

```javascript
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:

```javascript
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 `OpenAPIRuntimeError` with `code`, `details`, and `toJSON()` for easy logging

---

## ▲ Next.js Integration

### 🖥️ Server Components (Recommended)

```javascript
// 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

```javascript
// 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 hooks
```

**Design 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. 🧡

```bash
# 📥 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 test
```

### Guidelines 📋

1. 🍴 Fork the repository
2. 🌿 Create a feature branch (`git checkout -b feature/amazing-thing`)
3. 🧪 Write tests for new functionality
4. ✅ Ensure all tests pass
5. 🚀 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](https://github.com/sonatel-os)

---

<div align="center">

### 🔥 Built with mass frustration, deployed with mass relief 🔥

**Stop generating. Start shipping.** 🚀

<br>

*Made with 🧡 by developers who mass mass mass hated regenerating SDKs*

<br>

<!-- ⭐ **Star us on GitHub** — it mass mass mass motivates us to mass mass mass improve! ⭐ -->

</div>