diff --git a/examples/browser/README.md b/examples/browser/README.md new file mode 100644 index 0000000..a6b7862 --- /dev/null +++ b/examples/browser/README.md @@ -0,0 +1,77 @@ +# Browser Example — @codingaryan/smoothapi + +A minimal browser-based example demonstrating how to use `@codingaryan/smoothapi` with retries, fallbacks, and circuit breaker protection. + +The example interacts with the local sandbox server and provides two demos: + +* **Retry Demo** — Uses `/unstable-data` to demonstrate automatic retries and fallback handling. +* **Circuit Breaker Demo** — Uses `/always-fail` to demonstrate circuit breaker behavior and fallback responses. + +## Prerequisites + +* Node.js 18+ +* npm + +## Start the Sandbox + +From the repository root: + +```bash +cd sandbox +npm install +node server.js +``` + +The sandbox will run on: + +```text +http://localhost:3001 +``` + +## Run the Browser Example + +Open a second terminal: + +```bash +cd examples/browser +npm install +npm run dev +``` + +Open the URL shown by Vite (typically `http://localhost:5173`). + +## Retry Demo + +The Retry Demo calls: + +```text +http://localhost:3001/unstable-data +``` + +This endpoint intentionally returns a mix of: + +* 200 responses +* 429 responses +* 500 responses + +SmoothAPI automatically retries retryable failures and returns the final successful response when available. + +## Circuit Breaker Demo + +The Circuit Breaker Demo calls: + +```text +http://localhost:3001/always-fail +``` + +This endpoint always returns a failure response. + +After enough consecutive failures, the circuit breaker opens and returns the configured fallback immediately without making additional network requests. + +## Features Demonstrated + +* Automatic retries +* Exponential backoff +* Fallback responses +* Circuit breaker protection +* Browser integration with SmoothAPI diff --git a/examples/browser/index.html b/examples/browser/index.html new file mode 100644 index 0000000..2312853 --- /dev/null +++ b/examples/browser/index.html @@ -0,0 +1,21 @@ + + + + + + SmoothAPI Browser Example + + +

SmoothAPI Browser Example

+ +

Demonstrates retries, circuit breaker behavior, and fallback handling.

+ + + + +

Output

+
Click a button to begin...
+ + + + \ No newline at end of file diff --git a/examples/browser/main.ts b/examples/browser/main.ts new file mode 100644 index 0000000..17ef04a --- /dev/null +++ b/examples/browser/main.ts @@ -0,0 +1,99 @@ +import { createResilientFetch } from "@codingaryan/smoothapi"; + +const retryButton = document.getElementById("retry-btn"); +const circuitButton = document.getElementById("circuit-btn"); +const output = document.getElementById("output"); + +const retryFallback = { + data: "cached fallback (stale)" +}; + +const circuitFallback = { + data: "circuit-open fallback" +}; + +const retryFetch = createResilientFetch({ + retryOn: [429, 500, 502, 503, 504], + fallback: retryFallback, + fallbackOnNonRetryable: true +}); + +const circuitFetch = createResilientFetch({ + retryOn: [429, 500, 502, 503, 504], + circuitBreaker: { + failureThreshold: 3, + cooldownMs: 5000 + }, + fallback: circuitFallback +}); + +retryButton?.addEventListener("click", async () => { + if (!output) return; + + output.textContent = "Running retry demo..."; + + try { + const result = await retryFetch( + "http://localhost:3001/unstable-data" + ); + + if (result instanceof Response) { + const data = await result.json().catch(() => null); + + output.textContent = JSON.stringify( + { + source: "live", + status: result.status, + data + }, + null, + 2 + ); + } else { + output.textContent = JSON.stringify( + { + source: "fallback", + data: result + }, + null, + 2 + ); + } + } catch (error) { + output.textContent = `Error: ${error}`; + } +}); + +circuitButton?.addEventListener("click", async () => { + if (!output) return; + + output.textContent = "Running circuit breaker demo..."; + + try { + const result = await circuitFetch( + "http://localhost:3001/always-fail" + ); + + if (result instanceof Response) { + output.textContent = JSON.stringify( + { + hitNetwork: true, + status: result.status + }, + null, + 2 + ); + } else { + output.textContent = JSON.stringify( + { + hitNetwork: false, + data: result + }, + null, + 2 + ); + } + } catch (error) { + output.textContent = `Error: ${error}`; + } +}); \ No newline at end of file diff --git a/examples/browser/package.json b/examples/browser/package.json new file mode 100644 index 0000000..55b265c --- /dev/null +++ b/examples/browser/package.json @@ -0,0 +1,18 @@ +{ + "name": "smoothapi-browser-example", + "version": "1.0.0", + "private": true, + "description": "Browser example for @codingaryan/smoothapi", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@codingaryan/smoothapi": "1.0.0" + }, + "devDependencies": { + "typescript": "^5.4.5", + "vite": "^5.4.0" + } +} \ No newline at end of file diff --git a/examples/browser/tsconfig.json b/examples/browser/tsconfig.json new file mode 100644 index 0000000..8b7b7e7 --- /dev/null +++ b/examples/browser/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + } +} \ No newline at end of file diff --git a/sandbox/server.js b/sandbox/server.js index 51fbf51..81534af 100644 --- a/sandbox/server.js +++ b/sandbox/server.js @@ -9,6 +9,11 @@ let requestCount = 0; app.use(express.json()); +app.use((req, res, next) => { + res.header("Access-Control-Allow-Origin", "*"); + next(); +}); + // Always 200. Used by test runners to poll for readiness before the suite // starts. Does not touch requestCount. app.get('/health', (_req, res) => {