Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ All types of contributions are welcome:

## Repository Layout

SmoothAPI is a dual-language API resilience and fault-tolerance library. The workspace is organized as follows:
SmoothAPI is a dual-language API robustness and fault-tolerance library. The workspace is organized as follows:

```text
smooth-api/
Expand Down
2 changes: 1 addition & 1 deletion examples/express/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SmoothAPI Express Example

This example demonstrates how to integrate SmoothAPI into an Express.js application to make outbound API requests more resilient using retries, circuit breakers, and fallback responses.
This example demonstrates how to integrate SmoothAPI into an Express.js application to make outbound API requests more robust using retries, circuit breakers, and fallback responses.

## Features

Expand Down
2 changes: 1 addition & 1 deletion examples/fastapi/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SmoothAPI FastAPI Example

This example demonstrates how to integrate **SmoothAPI** into a FastAPI application to make outbound API requests more resilient using retries, circuit breakers, and fallback responses.
This example demonstrates how to integrate **SmoothAPI** into a FastAPI application to make outbound API requests more robust using retries, circuit breakers, and fallback responses.

## Features

Expand Down
53 changes: 29 additions & 24 deletions examples/nextjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,57 @@

A minimal [Next.js](https://nextjs.org/) (App Router, TypeScript) example showing
how to use `@codingaryan/smoothapi` to make calls to an unreliable third-party
API resilient. Real upstream services fail intermittently, rate-limit, and go
down; this example wraps `fetch` with retries, a fallback value, and a circuit
breaker so your route handlers degrade gracefully instead of erroring out.
API robust. Real upstream services fail intermittently, rate-limit, and go
down completely. This example shows how to use SmoothAPI with Next.js App Router API Routes to protect your app.

It demonstrates two route handlers against the project's chaos **sandbox**
server:
## Endpoints in this example

- `/api/resilient` — retry + fallback against `/unstable-data`.
- `/api/circuit-demo` — circuit breaker against `/always-fail`.
- `/api/unstable-data` — simulates an upstream service that fails randomly.
- `/api/always-fail` — simulates an upstream service that is completely down.
- `/api/robust` — retry + fallback against `/unstable-data`.
- `/api/circuit-demo` — trips the circuit breaker against `/always-fail`.

---
## How it works

## Prerequisites & Setup
### `/api/robust` — retry + fallback

### 1. Start the sandbox server
This route points `createSmoothFetch` at `/unstable-data`, which returns a
503 randomly.

The example calls the chaos sandbox on `http://localhost:3001`. From the repo
root:
1. It catches the 503 error.
2. It backs off exponentially and retries.
3. If it succeeds, you get the data.
4. If it fails 3 times, you get the fallback data: `{ status: "degraded", ... }`

```bash
cd sandbox
npm install
node server.js
```
### `/api/circuit-demo` — circuit breaker

Leave this terminal running.
This route points `createSmoothFetch` at `/always-fail`, which always returns
a 503 error.

### 2. Run the example
1. The first 3 requests will be retried (and fail).
2. The circuit breaker trips `OPEN`.
3. The 4th and subsequent requests immediately return the fallback without even trying the network!
4. After the cooldown period (10s), it enters `HALF_OPEN` and tests the endpoint again.

In a second terminal:
## Running the example

```bash
cd examples/nextjs
npm install
npm run dev
```

Then open [http://localhost:3000](http://localhost:3000) and use the two buttons,
or call the routes directly with the curl commands below.
Then visit `http://localhost:3000` to interact with the API routes.

Alternatively, you can test it directly via curl:

```bash
curl http://localhost:3000/api/robust
```
---

## Walkthrough

### `/api/resilient` — retry + fallback

This route points `createResilientFetch` at `/unstable-data`, which returns a
mix of `200`, `429`, and `500` responses. With the default retry settings, a
retryable status (`429`/`500`/...) causes the client to back off and try again
Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { ReactNode } from 'react';

export const metadata = {
title: 'smoothapi Next.js example',
description: 'Resilient fetch demo against the chaos sandbox',
title: 'Next.js App Router API Robustness Demo',
description: 'Robust fetch demo against the chaos sandbox',
};

export default function RootLayout({ children }: { children: ReactNode }) {
Expand Down
8 changes: 5 additions & 3 deletions examples/nextjs/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ export default function Home() {

return (
<main style={{ fontFamily: 'monospace', padding: 24 }}>
<h1>smoothapi Next.js example</h1>
<p>Make sure the sandbox server is running on http://localhost:3001.</p>
<h1>Next.js Robust Fetch Demo</h1>
<p>
These buttons call our internal API routes. Those routes then use <code>createSmoothFetch</code> to call the chaos sandbox. Open your browser console and the server terminal to see the logs!
</p>
<div style={{ display: 'flex', gap: 8 }}>
<button onClick={() => call('/api/resilient')}>Call /api/resilient</button>
<button onClick={() => call('/api/robust')}>Call /api/robust</button>
<button onClick={() => call('/api/circuit-demo')}>Call /api/circuit-demo</button>
</div>
<pre style={{ marginTop: 16, padding: 12, background: '#f0f0f0' }}>{output}</pre>
Expand Down
4 changes: 3 additions & 1 deletion website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
"lint": "eslint"
},
"dependencies": {
"@types/react-syntax-highlighter": "^15.5.13",
"next": "16.2.9",
"react": "19.2.4",
"react-dom": "19.2.4"
"react-dom": "19.2.4",
"react-syntax-highlighter": "^16.1.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
Expand Down
64 changes: 64 additions & 0 deletions website/src/app/docs/api/python/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
export default function PyApiPage() {
return (
<div>
<h2 className="text-3xl font-bold mb-6">API Reference: Python</h2>

<h3 className="text-2xl font-bold mt-8 mb-4 font-mono text-rose-300">@smooth_api(config)</h3>
<p className="text-slate-300 leading-7 text-lg mb-6">
The primary entry point for the Python SDK. A decorator that wraps standard functions or async functions (<code>async def</code>) with self-healing patterns.
</p>

<h4 className="text-xl font-bold mt-6 mb-3">Arguments</h4>
<ul className="list-disc pl-6 text-slate-300 space-y-2 mb-6">
<li><strong>config</strong> (<code>SmoothConfig</code>): The configuration object.</li>
</ul>

<h4 className="text-xl font-bold mt-6 mb-3">Returns</h4>
<p className="text-slate-300 leading-7 text-lg mb-6">
The wrapped function. It catches exceptions (like <code>requests.exceptions.RequestException</code> or <code>httpx.HTTPError</code>) and manages backoff automatically.
</p>

<hr className="border-slate-800 my-8" />

<h3 className="text-2xl font-bold mt-8 mb-4">SmoothConfig (Class)</h3>
<div className="overflow-x-auto mb-8">
<table className="min-w-full divide-y divide-slate-800 text-sm">
<thead>
<tr className="text-slate-400 text-left">
<th className="py-3 px-4 font-mono">Parameter</th>
<th className="py-3 px-4 font-mono">Type</th>
<th className="py-3 px-4">Default</th>
<th className="py-3 px-4">Description</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800">
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">base_delay</td>
<td className="py-3.5 px-4 font-mono text-slate-400">float</td>
<td className="py-3.5 px-4 font-mono text-slate-400">0.1</td>
<td className="py-3.5 px-4 text-slate-300">Initial delay in seconds.</td>
</tr>
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">max_retries</td>
<td className="py-3.5 px-4 font-mono text-slate-400">int</td>
<td className="py-3.5 px-4 font-mono text-slate-400">3</td>
<td className="py-3.5 px-4 text-slate-300">Maximum number of retry attempts.</td>
</tr>
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">failure_threshold</td>
<td className="py-3.5 px-4 font-mono text-slate-400">int</td>
<td className="py-3.5 px-4 font-mono text-slate-400">3</td>
<td className="py-3.5 px-4 text-slate-300">Consecutive failures before tripping the circuit.</td>
</tr>
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">timeout_ms</td>
<td className="py-3.5 px-4 font-mono text-slate-400">int</td>
<td className="py-3.5 px-4 font-mono text-slate-400">None</td>
<td className="py-3.5 px-4 text-slate-300">Automatically abort requests that take longer than this duration.</td>
</tr>
</tbody>
</table>
</div>
</div>
);
}
64 changes: 64 additions & 0 deletions website/src/app/docs/api/typescript/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
export default function TSApiPage() {
return (
<div>
<h2 className="text-3xl font-bold mb-6">API Reference: TypeScript</h2>

<h3 className="text-2xl font-bold mt-8 mb-4 font-mono text-rose-300">createSmoothFetch(config)</h3>
<p className="text-slate-300 leading-7 text-lg mb-6">
The primary entry point for the TypeScript SDK. Returns a decorated fetch function that implements the self-healing patterns configured.
</p>

<h4 className="text-xl font-bold mt-6 mb-3">Arguments</h4>
<ul className="list-disc pl-6 text-slate-300 space-y-2 mb-6">
<li><strong>config</strong> (<code>SmoothFetchConfig</code>): The configuration object.</li>
</ul>

<h4 className="text-xl font-bold mt-6 mb-3">Returns</h4>
<p className="text-slate-300 leading-7 text-lg mb-6">
<code>(input: RequestInfo | URL, init?: RequestInit) =&gt; Promise&lt;Response | any&gt;</code>: A drop-in replacement for the native <code>fetch</code> API. If a fallback is triggered, it may return the fallback object directly instead of a Response object.
</p>

<hr className="border-slate-800 my-8" />

<h3 className="text-2xl font-bold mt-8 mb-4">SmoothFetchConfig (Interface)</h3>
<div className="overflow-x-auto mb-8">
<table className="min-w-full divide-y divide-slate-800 text-sm">
<thead>
<tr className="text-slate-400 text-left">
<th className="py-3 px-4 font-mono">Property</th>
<th className="py-3 px-4 font-mono">Type</th>
<th className="py-3 px-4">Required</th>
<th className="py-3 px-4">Description</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800">
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">backoff</td>
<td className="py-3.5 px-4 font-mono text-slate-400">BackoffConfig</td>
<td className="py-3.5 px-4 font-mono text-slate-400">Yes</td>
<td className="py-3.5 px-4 text-slate-300">Configuration for retries and delays.</td>
</tr>
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">circuitBreaker</td>
<td className="py-3.5 px-4 font-mono text-slate-400">CircuitBreakerConfig</td>
<td className="py-3.5 px-4 font-mono text-slate-400">Yes</td>
<td className="py-3.5 px-4 text-slate-300">Configuration for failure thresholds.</td>
</tr>
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">fallback</td>
<td className="py-3.5 px-4 font-mono text-slate-400">any</td>
<td className="py-3.5 px-4 font-mono text-slate-400">No</td>
<td className="py-3.5 px-4 text-slate-300">Data to return when the circuit trips or errors are exhausted.</td>
</tr>
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">timeoutMs</td>
<td className="py-3.5 px-4 font-mono text-slate-400">number</td>
<td className="py-3.5 px-4 font-mono text-slate-400">No</td>
<td className="py-3.5 px-4 text-slate-300">Automatically abort requests that take longer than this duration (in milliseconds).</td>
</tr>
</tbody>
</table>
</div>
</div>
);
}
78 changes: 78 additions & 0 deletions website/src/app/docs/configuration/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
export default function ConfigurationPage() {
return (
<div>
<h2 className="text-3xl font-bold mb-6">Configuration Options</h2>
<p className="text-slate-300 mb-6">
Customize the behavior of `SmoothAPI` using the following properties when initializing:
</p>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-slate-800 text-sm">
<thead>
<tr className="text-slate-400 text-left">
<th className="py-3 px-4 font-mono">Property</th>
<th className="py-3 px-4 font-mono">Type</th>
<th className="py-3 px-4">Default</th>
<th className="py-3 px-4">Description</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800">
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">backoff.baseDelay</td>
<td className="py-3.5 px-4 font-mono text-slate-400">number</td>
<td className="py-3.5 px-4 font-mono text-slate-400">100 / 0.1</td>
<td className="py-3.5 px-4 text-slate-300">Initial wait time before the first retry (ms in TS, seconds in Python).</td>
</tr>
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">backoff.maxRetries</td>
<td className="py-3.5 px-4 font-mono text-slate-400">number</td>
<td className="py-3.5 px-4 font-mono text-slate-400">3</td>
<td className="py-3.5 px-4 text-slate-300">Maximum number of attempts to resolve the request.</td>
</tr>
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">circuitBreaker.failureThreshold</td>
<td className="py-3.5 px-4 font-mono text-slate-400">number</td>
<td className="py-3.5 px-4 font-mono text-slate-400">3</td>
<td className="py-3.5 px-4 text-slate-300">Consecutive failures needed to trip the circuit to OPEN.</td>
</tr>
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">circuitBreaker.cooldownMs</td>
<td className="py-3.5 px-4 font-mono text-slate-400">number</td>
<td className="py-3.5 px-4 font-mono text-slate-400">10000</td>
<td className="py-3.5 px-4 text-slate-300">Time to wait (in ms) before entering HALF_OPEN probe state.</td>
</tr>
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">fallback</td>
<td className="py-3.5 px-4 font-mono text-slate-400">any</td>
<td className="py-3.5 px-4 font-mono text-slate-400">undefined</td>
<td className="py-3.5 px-4 text-slate-300">Object returned immediately on an OPEN circuit or client error fallback.</td>
</tr>
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">fallbackOnNonRetryable</td>
<td className="py-3.5 px-4 font-mono text-slate-400">boolean</td>
<td className="py-3.5 px-4 font-mono text-slate-400">false</td>
<td className="py-3.5 px-4 text-slate-300">If true, returns fallbacks or mock responses on non-retryable client codes (e.g. 404, 401).</td>
</tr>
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">onNonRetryableError</td>
<td className="py-3.5 px-4 font-mono text-slate-400">function</td>
<td className="py-3.5 px-4 font-mono text-slate-400">undefined</td>
<td className="py-3.5 px-4 text-slate-300">Custom callback fired when a client-error occurs. Disables default browser alerts.</td>
</tr>
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">timeoutMs / timeout_ms</td>
<td className="py-3.5 px-4 font-mono text-slate-400">number</td>
<td className="py-3.5 px-4 font-mono text-slate-400">undefined</td>
<td className="py-3.5 px-4 text-slate-300">Maximum time (in ms) to wait for a request before aborting and retrying.</td>
</tr>
<tr>
<td className="py-3.5 px-4 font-mono font-bold text-rose-300">deduplication</td>
<td className="py-3.5 px-4 font-mono text-slate-400">object</td>
<td className="py-3.5 px-4 font-mono text-slate-400">undefined</td>
<td className="py-3.5 px-4 text-slate-300">Configuration object to enable coalescing concurrent identical requests.</td>
</tr>
</tbody>
</table>
</div>
</div>
);
}
Loading
Loading