Repository files navigation

APIEngine

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS). It transforms your API manifest into a strongly-typed, easy-to-use communication layer for modern web applications.


1. Installation

npm install @@sandeshnaroju/api-engine
# or
yarn add @@sandeshnaroju/api-engine

2. Configuration (api.yml)

Add all your Apis in the api.yml file. The engine is driven by a central manifest. It supports both relative paths (using baseUrl) and absolute URLs (bypassing baseUrl).

version: "1.0"baseUrl: "https://jsonplaceholder.typicode.com"endpoints:
get_post:
protocol: "REST"path: "/posts/:id"method: "GET"timeout: 3000create_post:
protocol: "REST"path: "/posts"method: "POST"timeout: 5000ws_test:
protocol: "WS"path: "wss://echo.websocket.org"# Full URL for testingautoReconnect: truesse_test:
protocol: "SSE"path: "http://localhost:3000/api-proxy/apps/api/v1/chat/completions"method: "POST"headers:
"Content-Type": "application/json"

4. Usage

Initializing

If you place your api.yml file in your project's public folder, the engine can find it automatically without you needing to import or pass anything. This is ideal if you want to update the API configuration without rebuilding your entire React/Vue app.

// Looks for /api.yml in your web server's root automaticallyconstapi=awaitAPIEngine.init();

This is the most common method in modern development. You keep the api.yml in your src folder or project root and import it. Because bundlers convert these imports into URL strings, init() handles the background fetching for you.

importmanifestUrlfrom'./api.yml';// init() detects the URL string and fetches the contentconstapi=awaitAPIEngine.init(manifestUrl);

If you are fetching your configuration from a custom database, a CMS, or even a text-area in your UI, you can pass the raw YAML text directly. The engine will parse it on the fly.

constrawYaml=`baseUrl: https://api.production.comendpoints: get_users: protocol: REST path: /users`;// Works with the static initializer...constapi=awaitAPIEngine.init(rawYaml);// ...or directly with the constructorconstapi=newAPIEngine(rawYaml);

If you prefer to work with JSON or hardcoded configuration objects, you can skip the YAML parsing entirely. This is the fastest method as it involves no network requests or string parsing.

constconfig={baseUrl: "https://api.production.com",endpoints: {ping: {protocol: "REST",path: "/health"}}};// Pass the object directlyconstapi=newAPIEngine(config);

You can use the api-engine like below:

REST:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// GETconsttodo=awaitapi.call('get_post',{params: {id: 1}});console.log("Post Title:",todo.title);//POSTconstres=awaitapi.call('create_post',{body: {title: 'New Post',userId: 1}});

SSE:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: Listening to a live log streamconststream=api.watch('sse_test');constunsubscribe=stream.subscribe((log)=>{console.log("New Server Log:",log.message);});// Stop listening when leaving the page// unsubscribe();

WebSocket:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: A real-time chat or command consoleconstsocket=api.watch('ws_test');// 1. Listen for incoming messagessocket.subscribe((msg)=>{console.log("Incoming Message:",msg.text);});// 2. Send a message backsocket.send({message: "Hi",});// Close connection when done// socket.close();

4. Options

call and watch methods support below extra options.

PropertyTypeDescription
paramsReplaces path variables (e.g., :id in /users/:id).params: { id: 101 }
queryAppends key-value pairs as query strings (e.g., ?limit=10).query: { limit: 10, page: 1 }
bodyThe request payload (JSON) for POST, PUT, and SSE.body: { name: 'Sandesh' }
headersCustom HTTP headers (e.g., Auth tokens).headers: { 'Authorization': 'Bearer ...' }
timeoutMaximum time (ms) to wait before the request fails.timeout: 5000
signalAn AbortSignal used to manually cancel requests.signal: controller.signal
fetchOptionsPass-through for raw Axios or Fetch configurations.fetchOptions: { withCredentials: true }

5. Framework Implementation Examples

Vanilla HTML / JavaScript

<scripttype="module">import{APIEngine}from'./dist/index.js';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);consttodo=awaitapi.call('get_todo',{params: {id: 1}});conststream=api.watch('live_logs');constunsubSSE=stream.subscribe(data=>console.log("Log Received:",data));constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Message:",msg));socket.send({type: 'hello'});</script>

React (Functional Components)

import{useEffect,useState}from'react';import{api}from'./api-client';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);exportconstDashboard=({ sensorId })=>{const[data,setData]=useState(null);useEffect(()=>{conststream=api.watch('live_logs',{params: {id: sensorId}});constunsubscribe=stream.subscribe(setData);constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("Real-time WS:",msg));return()=>{unsubscribe();socket.close();};},[sensorId]);return<div>{JSON.stringify(data)}</div>;};

Vue 3 (Composition API)

<scriptsetup>import{onMounted,onUnmounted,ref}from'vue';import{api}from'@/services/api';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);constmessages=ref([]);letsseUnsub=null;letsocket=null;onMounted(()=>{conststream=api.watch('live_logs');sseUnsub=stream.subscribe(msg=>messages.value.push(msg));socket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Data:",msg));});onUnmounted(()=>{if(sseUnsub)sseUnsub();if(socket)socket.close();});</script>

6. Core Logic Highlights

Smart URL Resolution

If a path starts with http://, https://, ws://, or wss://, the baseUrl is automatically ignored.

Path Variable Injection

The internal buildUrl utility maps params to :keys in the URL string and converts remaining keys into query strings.

Unified Watcher

Both SSE and WS are accessed via .watch().

  • SSE (Unidirectional)
    • Use .subscribe()
    • Returns an unsubscribe function
  • WebSocket (Bidirectional)
    • Use .subscribe()
    • Use .send()
    • Use .close()

Browser-First

Optimized for browser environments with native WebSocket and EventSource support.

Connection Management

  • Automatic exponential backoff for WebSocket reconnections
  • Clean resource disposal via subscription lifecycle

License

Apache License Version 2.0, January 2004

About

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS).

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

APIEngine

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS). It transforms your API manifest into a strongly-typed, easy-to-use communication layer for modern web applications.


1. Installation

npm install @@sandeshnaroju/api-engine
# or
yarn add @@sandeshnaroju/api-engine

2. Configuration (api.yml)

Add all your Apis in the api.yml file. The engine is driven by a central manifest. It supports both relative paths (using baseUrl) and absolute URLs (bypassing baseUrl).

version: "1.0"baseUrl: "https://jsonplaceholder.typicode.com"endpoints:
get_post:
protocol: "REST"path: "/posts/:id"method: "GET"timeout: 3000create_post:
protocol: "REST"path: "/posts"method: "POST"timeout: 5000ws_test:
protocol: "WS"path: "wss://echo.websocket.org"# Full URL for testingautoReconnect: truesse_test:
protocol: "SSE"path: "http://localhost:3000/api-proxy/apps/api/v1/chat/completions"method: "POST"headers:
"Content-Type": "application/json"

4. Usage

Initializing

If you place your api.yml file in your project's public folder, the engine can find it automatically without you needing to import or pass anything. This is ideal if you want to update the API configuration without rebuilding your entire React/Vue app.

// Looks for /api.yml in your web server's root automaticallyconstapi=awaitAPIEngine.init();

This is the most common method in modern development. You keep the api.yml in your src folder or project root and import it. Because bundlers convert these imports into URL strings, init() handles the background fetching for you.

importmanifestUrlfrom'./api.yml';// init() detects the URL string and fetches the contentconstapi=awaitAPIEngine.init(manifestUrl);

If you are fetching your configuration from a custom database, a CMS, or even a text-area in your UI, you can pass the raw YAML text directly. The engine will parse it on the fly.

constrawYaml=`baseUrl: https://api.production.comendpoints: get_users: protocol: REST path: /users`;// Works with the static initializer...constapi=awaitAPIEngine.init(rawYaml);// ...or directly with the constructorconstapi=newAPIEngine(rawYaml);

If you prefer to work with JSON or hardcoded configuration objects, you can skip the YAML parsing entirely. This is the fastest method as it involves no network requests or string parsing.

constconfig={baseUrl: "https://api.production.com",endpoints: {ping: {protocol: "REST",path: "/health"}}};// Pass the object directlyconstapi=newAPIEngine(config);

You can use the api-engine like below:

REST:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// GETconsttodo=awaitapi.call('get_post',{params: {id: 1}});console.log("Post Title:",todo.title);//POSTconstres=awaitapi.call('create_post',{body: {title: 'New Post',userId: 1}});

SSE:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: Listening to a live log streamconststream=api.watch('sse_test');constunsubscribe=stream.subscribe((log)=>{console.log("New Server Log:",log.message);});// Stop listening when leaving the page// unsubscribe();

WebSocket:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: A real-time chat or command consoleconstsocket=api.watch('ws_test');// 1. Listen for incoming messagessocket.subscribe((msg)=>{console.log("Incoming Message:",msg.text);});// 2. Send a message backsocket.send({message: "Hi",});// Close connection when done// socket.close();

4. Options

call and watch methods support below extra options.

PropertyTypeDescription
paramsReplaces path variables (e.g., :id in /users/:id).params: { id: 101 }
queryAppends key-value pairs as query strings (e.g., ?limit=10).query: { limit: 10, page: 1 }
bodyThe request payload (JSON) for POST, PUT, and SSE.body: { name: 'Sandesh' }
headersCustom HTTP headers (e.g., Auth tokens).headers: { 'Authorization': 'Bearer ...' }
timeoutMaximum time (ms) to wait before the request fails.timeout: 5000
signalAn AbortSignal used to manually cancel requests.signal: controller.signal
fetchOptionsPass-through for raw Axios or Fetch configurations.fetchOptions: { withCredentials: true }

5. Framework Implementation Examples

Vanilla HTML / JavaScript

<scripttype="module">import{APIEngine}from'./dist/index.js';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);consttodo=awaitapi.call('get_todo',{params: {id: 1}});conststream=api.watch('live_logs');constunsubSSE=stream.subscribe(data=>console.log("Log Received:",data));constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Message:",msg));socket.send({type: 'hello'});</script>

React (Functional Components)

import{useEffect,useState}from'react';import{api}from'./api-client';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);exportconstDashboard=({ sensorId })=>{const[data,setData]=useState(null);useEffect(()=>{conststream=api.watch('live_logs',{params: {id: sensorId}});constunsubscribe=stream.subscribe(setData);constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("Real-time WS:",msg));return()=>{unsubscribe();socket.close();};},[sensorId]);return<div>{JSON.stringify(data)}</div>;};

Vue 3 (Composition API)

<scriptsetup>import{onMounted,onUnmounted,ref}from'vue';import{api}from'@/services/api';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);constmessages=ref([]);letsseUnsub=null;letsocket=null;onMounted(()=>{conststream=api.watch('live_logs');sseUnsub=stream.subscribe(msg=>messages.value.push(msg));socket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Data:",msg));});onUnmounted(()=>{if(sseUnsub)sseUnsub();if(socket)socket.close();});</script>

6. Core Logic Highlights

Smart URL Resolution

If a path starts with http://, https://, ws://, or wss://, the baseUrl is automatically ignored.

Path Variable Injection

The internal buildUrl utility maps params to :keys in the URL string and converts remaining keys into query strings.

Unified Watcher

Both SSE and WS are accessed via .watch().

  • SSE (Unidirectional)
    • Use .subscribe()
    • Returns an unsubscribe function
  • WebSocket (Bidirectional)
    • Use .subscribe()
    • Use .send()
    • Use .close()

Browser-First

Optimized for browser environments with native WebSocket and EventSource support.

Connection Management

  • Automatic exponential backoff for WebSocket reconnections
  • Clean resource disposal via subscription lifecycle

License

Apache License Version 2.0, January 2004

About

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS).

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

APIEngine

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS). It transforms your API manifest into a strongly-typed, easy-to-use communication layer for modern web applications.


1. Installation

npm install @@sandeshnaroju/api-engine
# or
yarn add @@sandeshnaroju/api-engine

2. Configuration (api.yml)

Add all your Apis in the api.yml file. The engine is driven by a central manifest. It supports both relative paths (using baseUrl) and absolute URLs (bypassing baseUrl).

version: "1.0"baseUrl: "https://jsonplaceholder.typicode.com"endpoints:
get_post:
protocol: "REST"path: "/posts/:id"method: "GET"timeout: 3000create_post:
protocol: "REST"path: "/posts"method: "POST"timeout: 5000ws_test:
protocol: "WS"path: "wss://echo.websocket.org"# Full URL for testingautoReconnect: truesse_test:
protocol: "SSE"path: "http://localhost:3000/api-proxy/apps/api/v1/chat/completions"method: "POST"headers:
"Content-Type": "application/json"

4. Usage

Initializing

If you place your api.yml file in your project's public folder, the engine can find it automatically without you needing to import or pass anything. This is ideal if you want to update the API configuration without rebuilding your entire React/Vue app.

// Looks for /api.yml in your web server's root automaticallyconstapi=awaitAPIEngine.init();

This is the most common method in modern development. You keep the api.yml in your src folder or project root and import it. Because bundlers convert these imports into URL strings, init() handles the background fetching for you.

importmanifestUrlfrom'./api.yml';// init() detects the URL string and fetches the contentconstapi=awaitAPIEngine.init(manifestUrl);

If you are fetching your configuration from a custom database, a CMS, or even a text-area in your UI, you can pass the raw YAML text directly. The engine will parse it on the fly.

constrawYaml=`baseUrl: https://api.production.comendpoints: get_users: protocol: REST path: /users`;// Works with the static initializer...constapi=awaitAPIEngine.init(rawYaml);// ...or directly with the constructorconstapi=newAPIEngine(rawYaml);

If you prefer to work with JSON or hardcoded configuration objects, you can skip the YAML parsing entirely. This is the fastest method as it involves no network requests or string parsing.

constconfig={baseUrl: "https://api.production.com",endpoints: {ping: {protocol: "REST",path: "/health"}}};// Pass the object directlyconstapi=newAPIEngine(config);

You can use the api-engine like below:

REST:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// GETconsttodo=awaitapi.call('get_post',{params: {id: 1}});console.log("Post Title:",todo.title);//POSTconstres=awaitapi.call('create_post',{body: {title: 'New Post',userId: 1}});

SSE:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: Listening to a live log streamconststream=api.watch('sse_test');constunsubscribe=stream.subscribe((log)=>{console.log("New Server Log:",log.message);});// Stop listening when leaving the page// unsubscribe();

WebSocket:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: A real-time chat or command consoleconstsocket=api.watch('ws_test');// 1. Listen for incoming messagessocket.subscribe((msg)=>{console.log("Incoming Message:",msg.text);});// 2. Send a message backsocket.send({message: "Hi",});// Close connection when done// socket.close();

4. Options

call and watch methods support below extra options.

PropertyTypeDescription
paramsReplaces path variables (e.g., :id in /users/:id).params: { id: 101 }
queryAppends key-value pairs as query strings (e.g., ?limit=10).query: { limit: 10, page: 1 }
bodyThe request payload (JSON) for POST, PUT, and SSE.body: { name: 'Sandesh' }
headersCustom HTTP headers (e.g., Auth tokens).headers: { 'Authorization': 'Bearer ...' }
timeoutMaximum time (ms) to wait before the request fails.timeout: 5000
signalAn AbortSignal used to manually cancel requests.signal: controller.signal
fetchOptionsPass-through for raw Axios or Fetch configurations.fetchOptions: { withCredentials: true }

5. Framework Implementation Examples

Vanilla HTML / JavaScript

<scripttype="module">import{APIEngine}from'./dist/index.js';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);consttodo=awaitapi.call('get_todo',{params: {id: 1}});conststream=api.watch('live_logs');constunsubSSE=stream.subscribe(data=>console.log("Log Received:",data));constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Message:",msg));socket.send({type: 'hello'});</script>

React (Functional Components)

import{useEffect,useState}from'react';import{api}from'./api-client';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);exportconstDashboard=({ sensorId })=>{const[data,setData]=useState(null);useEffect(()=>{conststream=api.watch('live_logs',{params: {id: sensorId}});constunsubscribe=stream.subscribe(setData);constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("Real-time WS:",msg));return()=>{unsubscribe();socket.close();};},[sensorId]);return<div>{JSON.stringify(data)}</div>;};

Vue 3 (Composition API)

<scriptsetup>import{onMounted,onUnmounted,ref}from'vue';import{api}from'@/services/api';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);constmessages=ref([]);letsseUnsub=null;letsocket=null;onMounted(()=>{conststream=api.watch('live_logs');sseUnsub=stream.subscribe(msg=>messages.value.push(msg));socket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Data:",msg));});onUnmounted(()=>{if(sseUnsub)sseUnsub();if(socket)socket.close();});</script>

6. Core Logic Highlights

Smart URL Resolution

If a path starts with http://, https://, ws://, or wss://, the baseUrl is automatically ignored.

Path Variable Injection

The internal buildUrl utility maps params to :keys in the URL string and converts remaining keys into query strings.

Unified Watcher

Both SSE and WS are accessed via .watch().

  • SSE (Unidirectional)
    • Use .subscribe()
    • Returns an unsubscribe function
  • WebSocket (Bidirectional)
    • Use .subscribe()
    • Use .send()
    • Use .close()

Browser-First

Optimized for browser environments with native WebSocket and EventSource support.

Connection Management

  • Automatic exponential backoff for WebSocket reconnections
  • Clean resource disposal via subscription lifecycle

License

Apache License Version 2.0, January 2004

About

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS).

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

APIEngine

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS). It transforms your API manifest into a strongly-typed, easy-to-use communication layer for modern web applications.


1. Installation

npm install @@sandeshnaroju/api-engine
# or
yarn add @@sandeshnaroju/api-engine

2. Configuration (api.yml)

Add all your Apis in the api.yml file. The engine is driven by a central manifest. It supports both relative paths (using baseUrl) and absolute URLs (bypassing baseUrl).

version: "1.0"baseUrl: "https://jsonplaceholder.typicode.com"endpoints:
get_post:
protocol: "REST"path: "/posts/:id"method: "GET"timeout: 3000create_post:
protocol: "REST"path: "/posts"method: "POST"timeout: 5000ws_test:
protocol: "WS"path: "wss://echo.websocket.org"# Full URL for testingautoReconnect: truesse_test:
protocol: "SSE"path: "http://localhost:3000/api-proxy/apps/api/v1/chat/completions"method: "POST"headers:
"Content-Type": "application/json"

4. Usage

Initializing

If you place your api.yml file in your project's public folder, the engine can find it automatically without you needing to import or pass anything. This is ideal if you want to update the API configuration without rebuilding your entire React/Vue app.

// Looks for /api.yml in your web server's root automaticallyconstapi=awaitAPIEngine.init();

This is the most common method in modern development. You keep the api.yml in your src folder or project root and import it. Because bundlers convert these imports into URL strings, init() handles the background fetching for you.

importmanifestUrlfrom'./api.yml';// init() detects the URL string and fetches the contentconstapi=awaitAPIEngine.init(manifestUrl);

If you are fetching your configuration from a custom database, a CMS, or even a text-area in your UI, you can pass the raw YAML text directly. The engine will parse it on the fly.

constrawYaml=`baseUrl: https://api.production.comendpoints: get_users: protocol: REST path: /users`;// Works with the static initializer...constapi=awaitAPIEngine.init(rawYaml);// ...or directly with the constructorconstapi=newAPIEngine(rawYaml);

If you prefer to work with JSON or hardcoded configuration objects, you can skip the YAML parsing entirely. This is the fastest method as it involves no network requests or string parsing.

constconfig={baseUrl: "https://api.production.com",endpoints: {ping: {protocol: "REST",path: "/health"}}};// Pass the object directlyconstapi=newAPIEngine(config);

You can use the api-engine like below:

REST:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// GETconsttodo=awaitapi.call('get_post',{params: {id: 1}});console.log("Post Title:",todo.title);//POSTconstres=awaitapi.call('create_post',{body: {title: 'New Post',userId: 1}});

SSE:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: Listening to a live log streamconststream=api.watch('sse_test');constunsubscribe=stream.subscribe((log)=>{console.log("New Server Log:",log.message);});// Stop listening when leaving the page// unsubscribe();

WebSocket:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: A real-time chat or command consoleconstsocket=api.watch('ws_test');// 1. Listen for incoming messagessocket.subscribe((msg)=>{console.log("Incoming Message:",msg.text);});// 2. Send a message backsocket.send({message: "Hi",});// Close connection when done// socket.close();

4. Options

call and watch methods support below extra options.

PropertyTypeDescription
paramsReplaces path variables (e.g., :id in /users/:id).params: { id: 101 }
queryAppends key-value pairs as query strings (e.g., ?limit=10).query: { limit: 10, page: 1 }
bodyThe request payload (JSON) for POST, PUT, and SSE.body: { name: 'Sandesh' }
headersCustom HTTP headers (e.g., Auth tokens).headers: { 'Authorization': 'Bearer ...' }
timeoutMaximum time (ms) to wait before the request fails.timeout: 5000
signalAn AbortSignal used to manually cancel requests.signal: controller.signal
fetchOptionsPass-through for raw Axios or Fetch configurations.fetchOptions: { withCredentials: true }

5. Framework Implementation Examples

Vanilla HTML / JavaScript

<scripttype="module">import{APIEngine}from'./dist/index.js';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);consttodo=awaitapi.call('get_todo',{params: {id: 1}});conststream=api.watch('live_logs');constunsubSSE=stream.subscribe(data=>console.log("Log Received:",data));constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Message:",msg));socket.send({type: 'hello'});</script>

React (Functional Components)

import{useEffect,useState}from'react';import{api}from'./api-client';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);exportconstDashboard=({ sensorId })=>{const[data,setData]=useState(null);useEffect(()=>{conststream=api.watch('live_logs',{params: {id: sensorId}});constunsubscribe=stream.subscribe(setData);constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("Real-time WS:",msg));return()=>{unsubscribe();socket.close();};},[sensorId]);return<div>{JSON.stringify(data)}</div>;};

Vue 3 (Composition API)

<scriptsetup>import{onMounted,onUnmounted,ref}from'vue';import{api}from'@/services/api';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);constmessages=ref([]);letsseUnsub=null;letsocket=null;onMounted(()=>{conststream=api.watch('live_logs');sseUnsub=stream.subscribe(msg=>messages.value.push(msg));socket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Data:",msg));});onUnmounted(()=>{if(sseUnsub)sseUnsub();if(socket)socket.close();});</script>

6. Core Logic Highlights

Smart URL Resolution

If a path starts with http://, https://, ws://, or wss://, the baseUrl is automatically ignored.

Path Variable Injection

The internal buildUrl utility maps params to :keys in the URL string and converts remaining keys into query strings.

Unified Watcher

Both SSE and WS are accessed via .watch().

  • SSE (Unidirectional)
    • Use .subscribe()
    • Returns an unsubscribe function
  • WebSocket (Bidirectional)
    • Use .subscribe()
    • Use .send()
    • Use .close()

Browser-First

Optimized for browser environments with native WebSocket and EventSource support.

Connection Management

  • Automatic exponential backoff for WebSocket reconnections
  • Clean resource disposal via subscription lifecycle

License

Apache License Version 2.0, January 2004

About

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS).

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

APIEngine

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS). It transforms your API manifest into a strongly-typed, easy-to-use communication layer for modern web applications.


1. Installation

npm install @@sandeshnaroju/api-engine
# or
yarn add @@sandeshnaroju/api-engine

2. Configuration (api.yml)

Add all your Apis in the api.yml file. The engine is driven by a central manifest. It supports both relative paths (using baseUrl) and absolute URLs (bypassing baseUrl).

version: "1.0"baseUrl: "https://jsonplaceholder.typicode.com"endpoints:
get_post:
protocol: "REST"path: "/posts/:id"method: "GET"timeout: 3000create_post:
protocol: "REST"path: "/posts"method: "POST"timeout: 5000ws_test:
protocol: "WS"path: "wss://echo.websocket.org"# Full URL for testingautoReconnect: truesse_test:
protocol: "SSE"path: "http://localhost:3000/api-proxy/apps/api/v1/chat/completions"method: "POST"headers:
"Content-Type": "application/json"

4. Usage

Initializing

If you place your api.yml file in your project's public folder, the engine can find it automatically without you needing to import or pass anything. This is ideal if you want to update the API configuration without rebuilding your entire React/Vue app.

// Looks for /api.yml in your web server's root automaticallyconstapi=awaitAPIEngine.init();

This is the most common method in modern development. You keep the api.yml in your src folder or project root and import it. Because bundlers convert these imports into URL strings, init() handles the background fetching for you.

importmanifestUrlfrom'./api.yml';// init() detects the URL string and fetches the contentconstapi=awaitAPIEngine.init(manifestUrl);

If you are fetching your configuration from a custom database, a CMS, or even a text-area in your UI, you can pass the raw YAML text directly. The engine will parse it on the fly.

constrawYaml=`baseUrl: https://api.production.comendpoints: get_users: protocol: REST path: /users`;// Works with the static initializer...constapi=awaitAPIEngine.init(rawYaml);// ...or directly with the constructorconstapi=newAPIEngine(rawYaml);

If you prefer to work with JSON or hardcoded configuration objects, you can skip the YAML parsing entirely. This is the fastest method as it involves no network requests or string parsing.

constconfig={baseUrl: "https://api.production.com",endpoints: {ping: {protocol: "REST",path: "/health"}}};// Pass the object directlyconstapi=newAPIEngine(config);

You can use the api-engine like below:

REST:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// GETconsttodo=awaitapi.call('get_post',{params: {id: 1}});console.log("Post Title:",todo.title);//POSTconstres=awaitapi.call('create_post',{body: {title: 'New Post',userId: 1}});

SSE:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: Listening to a live log streamconststream=api.watch('sse_test');constunsubscribe=stream.subscribe((log)=>{console.log("New Server Log:",log.message);});// Stop listening when leaving the page// unsubscribe();

WebSocket:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: A real-time chat or command consoleconstsocket=api.watch('ws_test');// 1. Listen for incoming messagessocket.subscribe((msg)=>{console.log("Incoming Message:",msg.text);});// 2. Send a message backsocket.send({message: "Hi",});// Close connection when done// socket.close();

4. Options

call and watch methods support below extra options.

PropertyTypeDescription
paramsReplaces path variables (e.g., :id in /users/:id).params: { id: 101 }
queryAppends key-value pairs as query strings (e.g., ?limit=10).query: { limit: 10, page: 1 }
bodyThe request payload (JSON) for POST, PUT, and SSE.body: { name: 'Sandesh' }
headersCustom HTTP headers (e.g., Auth tokens).headers: { 'Authorization': 'Bearer ...' }
timeoutMaximum time (ms) to wait before the request fails.timeout: 5000
signalAn AbortSignal used to manually cancel requests.signal: controller.signal
fetchOptionsPass-through for raw Axios or Fetch configurations.fetchOptions: { withCredentials: true }

5. Framework Implementation Examples

Vanilla HTML / JavaScript

<scripttype="module">import{APIEngine}from'./dist/index.js';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);consttodo=awaitapi.call('get_todo',{params: {id: 1}});conststream=api.watch('live_logs');constunsubSSE=stream.subscribe(data=>console.log("Log Received:",data));constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Message:",msg));socket.send({type: 'hello'});</script>

React (Functional Components)

import{useEffect,useState}from'react';import{api}from'./api-client';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);exportconstDashboard=({ sensorId })=>{const[data,setData]=useState(null);useEffect(()=>{conststream=api.watch('live_logs',{params: {id: sensorId}});constunsubscribe=stream.subscribe(setData);constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("Real-time WS:",msg));return()=>{unsubscribe();socket.close();};},[sensorId]);return<div>{JSON.stringify(data)}</div>;};

Vue 3 (Composition API)

<scriptsetup>import{onMounted,onUnmounted,ref}from'vue';import{api}from'@/services/api';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);constmessages=ref([]);letsseUnsub=null;letsocket=null;onMounted(()=>{conststream=api.watch('live_logs');sseUnsub=stream.subscribe(msg=>messages.value.push(msg));socket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Data:",msg));});onUnmounted(()=>{if(sseUnsub)sseUnsub();if(socket)socket.close();});</script>

6. Core Logic Highlights

Smart URL Resolution

If a path starts with http://, https://, ws://, or wss://, the baseUrl is automatically ignored.

Path Variable Injection

The internal buildUrl utility maps params to :keys in the URL string and converts remaining keys into query strings.

Unified Watcher

Both SSE and WS are accessed via .watch().

  • SSE (Unidirectional)
    • Use .subscribe()
    • Returns an unsubscribe function
  • WebSocket (Bidirectional)
    • Use .subscribe()
    • Use .send()
    • Use .close()

Browser-First

Optimized for browser environments with native WebSocket and EventSource support.

Connection Management

  • Automatic exponential backoff for WebSocket reconnections
  • Clean resource disposal via subscription lifecycle

License

Apache License Version 2.0, January 2004

About

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS).

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

APIEngine

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS). It transforms your API manifest into a strongly-typed, easy-to-use communication layer for modern web applications.


1. Installation

npm install @@sandeshnaroju/api-engine
# or
yarn add @@sandeshnaroju/api-engine

2. Configuration (api.yml)

Add all your Apis in the api.yml file. The engine is driven by a central manifest. It supports both relative paths (using baseUrl) and absolute URLs (bypassing baseUrl).

version: "1.0"baseUrl: "https://jsonplaceholder.typicode.com"endpoints:
get_post:
protocol: "REST"path: "/posts/:id"method: "GET"timeout: 3000create_post:
protocol: "REST"path: "/posts"method: "POST"timeout: 5000ws_test:
protocol: "WS"path: "wss://echo.websocket.org"# Full URL for testingautoReconnect: truesse_test:
protocol: "SSE"path: "http://localhost:3000/api-proxy/apps/api/v1/chat/completions"method: "POST"headers:
"Content-Type": "application/json"

4. Usage

Initializing

If you place your api.yml file in your project's public folder, the engine can find it automatically without you needing to import or pass anything. This is ideal if you want to update the API configuration without rebuilding your entire React/Vue app.

// Looks for /api.yml in your web server's root automaticallyconstapi=awaitAPIEngine.init();

This is the most common method in modern development. You keep the api.yml in your src folder or project root and import it. Because bundlers convert these imports into URL strings, init() handles the background fetching for you.

importmanifestUrlfrom'./api.yml';// init() detects the URL string and fetches the contentconstapi=awaitAPIEngine.init(manifestUrl);

If you are fetching your configuration from a custom database, a CMS, or even a text-area in your UI, you can pass the raw YAML text directly. The engine will parse it on the fly.

constrawYaml=`baseUrl: https://api.production.comendpoints: get_users: protocol: REST path: /users`;// Works with the static initializer...constapi=awaitAPIEngine.init(rawYaml);// ...or directly with the constructorconstapi=newAPIEngine(rawYaml);

If you prefer to work with JSON or hardcoded configuration objects, you can skip the YAML parsing entirely. This is the fastest method as it involves no network requests or string parsing.

constconfig={baseUrl: "https://api.production.com",endpoints: {ping: {protocol: "REST",path: "/health"}}};// Pass the object directlyconstapi=newAPIEngine(config);

You can use the api-engine like below:

REST:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// GETconsttodo=awaitapi.call('get_post',{params: {id: 1}});console.log("Post Title:",todo.title);//POSTconstres=awaitapi.call('create_post',{body: {title: 'New Post',userId: 1}});

SSE:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: Listening to a live log streamconststream=api.watch('sse_test');constunsubscribe=stream.subscribe((log)=>{console.log("New Server Log:",log.message);});// Stop listening when leaving the page// unsubscribe();

WebSocket:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: A real-time chat or command consoleconstsocket=api.watch('ws_test');// 1. Listen for incoming messagessocket.subscribe((msg)=>{console.log("Incoming Message:",msg.text);});// 2. Send a message backsocket.send({message: "Hi",});// Close connection when done// socket.close();

4. Options

call and watch methods support below extra options.

PropertyTypeDescription
paramsReplaces path variables (e.g., :id in /users/:id).params: { id: 101 }
queryAppends key-value pairs as query strings (e.g., ?limit=10).query: { limit: 10, page: 1 }
bodyThe request payload (JSON) for POST, PUT, and SSE.body: { name: 'Sandesh' }
headersCustom HTTP headers (e.g., Auth tokens).headers: { 'Authorization': 'Bearer ...' }
timeoutMaximum time (ms) to wait before the request fails.timeout: 5000
signalAn AbortSignal used to manually cancel requests.signal: controller.signal
fetchOptionsPass-through for raw Axios or Fetch configurations.fetchOptions: { withCredentials: true }

5. Framework Implementation Examples

Vanilla HTML / JavaScript

<scripttype="module">import{APIEngine}from'./dist/index.js';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);consttodo=awaitapi.call('get_todo',{params: {id: 1}});conststream=api.watch('live_logs');constunsubSSE=stream.subscribe(data=>console.log("Log Received:",data));constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Message:",msg));socket.send({type: 'hello'});</script>

React (Functional Components)

import{useEffect,useState}from'react';import{api}from'./api-client';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);exportconstDashboard=({ sensorId })=>{const[data,setData]=useState(null);useEffect(()=>{conststream=api.watch('live_logs',{params: {id: sensorId}});constunsubscribe=stream.subscribe(setData);constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("Real-time WS:",msg));return()=>{unsubscribe();socket.close();};},[sensorId]);return<div>{JSON.stringify(data)}</div>;};

Vue 3 (Composition API)

<scriptsetup>import{onMounted,onUnmounted,ref}from'vue';import{api}from'@/services/api';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);constmessages=ref([]);letsseUnsub=null;letsocket=null;onMounted(()=>{conststream=api.watch('live_logs');sseUnsub=stream.subscribe(msg=>messages.value.push(msg));socket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Data:",msg));});onUnmounted(()=>{if(sseUnsub)sseUnsub();if(socket)socket.close();});</script>

6. Core Logic Highlights

Smart URL Resolution

If a path starts with http://, https://, ws://, or wss://, the baseUrl is automatically ignored.

Path Variable Injection

The internal buildUrl utility maps params to :keys in the URL string and converts remaining keys into query strings.

Unified Watcher

Both SSE and WS are accessed via .watch().

  • SSE (Unidirectional)
    • Use .subscribe()
    • Returns an unsubscribe function
  • WebSocket (Bidirectional)
    • Use .subscribe()
    • Use .send()
    • Use .close()

Browser-First

Optimized for browser environments with native WebSocket and EventSource support.

Connection Management

  • Automatic exponential backoff for WebSocket reconnections
  • Clean resource disposal via subscription lifecycle

License

Apache License Version 2.0, January 2004

About

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS).

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

APIEngine

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS). It transforms your API manifest into a strongly-typed, easy-to-use communication layer for modern web applications.


1. Installation

npm install @@sandeshnaroju/api-engine
# or
yarn add @@sandeshnaroju/api-engine

2. Configuration (api.yml)

Add all your Apis in the api.yml file. The engine is driven by a central manifest. It supports both relative paths (using baseUrl) and absolute URLs (bypassing baseUrl).

version: "1.0"baseUrl: "https://jsonplaceholder.typicode.com"endpoints:
get_post:
protocol: "REST"path: "/posts/:id"method: "GET"timeout: 3000create_post:
protocol: "REST"path: "/posts"method: "POST"timeout: 5000ws_test:
protocol: "WS"path: "wss://echo.websocket.org"# Full URL for testingautoReconnect: truesse_test:
protocol: "SSE"path: "http://localhost:3000/api-proxy/apps/api/v1/chat/completions"method: "POST"headers:
"Content-Type": "application/json"

4. Usage

Initializing

If you place your api.yml file in your project's public folder, the engine can find it automatically without you needing to import or pass anything. This is ideal if you want to update the API configuration without rebuilding your entire React/Vue app.

// Looks for /api.yml in your web server's root automaticallyconstapi=awaitAPIEngine.init();

This is the most common method in modern development. You keep the api.yml in your src folder or project root and import it. Because bundlers convert these imports into URL strings, init() handles the background fetching for you.

importmanifestUrlfrom'./api.yml';// init() detects the URL string and fetches the contentconstapi=awaitAPIEngine.init(manifestUrl);

If you are fetching your configuration from a custom database, a CMS, or even a text-area in your UI, you can pass the raw YAML text directly. The engine will parse it on the fly.

constrawYaml=`baseUrl: https://api.production.comendpoints: get_users: protocol: REST path: /users`;// Works with the static initializer...constapi=awaitAPIEngine.init(rawYaml);// ...or directly with the constructorconstapi=newAPIEngine(rawYaml);

If you prefer to work with JSON or hardcoded configuration objects, you can skip the YAML parsing entirely. This is the fastest method as it involves no network requests or string parsing.

constconfig={baseUrl: "https://api.production.com",endpoints: {ping: {protocol: "REST",path: "/health"}}};// Pass the object directlyconstapi=newAPIEngine(config);

You can use the api-engine like below:

REST:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// GETconsttodo=awaitapi.call('get_post',{params: {id: 1}});console.log("Post Title:",todo.title);//POSTconstres=awaitapi.call('create_post',{body: {title: 'New Post',userId: 1}});

SSE:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: Listening to a live log streamconststream=api.watch('sse_test');constunsubscribe=stream.subscribe((log)=>{console.log("New Server Log:",log.message);});// Stop listening when leaving the page// unsubscribe();

WebSocket:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: A real-time chat or command consoleconstsocket=api.watch('ws_test');// 1. Listen for incoming messagessocket.subscribe((msg)=>{console.log("Incoming Message:",msg.text);});// 2. Send a message backsocket.send({message: "Hi",});// Close connection when done// socket.close();

4. Options

call and watch methods support below extra options.

PropertyTypeDescription
paramsReplaces path variables (e.g., :id in /users/:id).params: { id: 101 }
queryAppends key-value pairs as query strings (e.g., ?limit=10).query: { limit: 10, page: 1 }
bodyThe request payload (JSON) for POST, PUT, and SSE.body: { name: 'Sandesh' }
headersCustom HTTP headers (e.g., Auth tokens).headers: { 'Authorization': 'Bearer ...' }
timeoutMaximum time (ms) to wait before the request fails.timeout: 5000
signalAn AbortSignal used to manually cancel requests.signal: controller.signal
fetchOptionsPass-through for raw Axios or Fetch configurations.fetchOptions: { withCredentials: true }

5. Framework Implementation Examples

Vanilla HTML / JavaScript

<scripttype="module">import{APIEngine}from'./dist/index.js';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);consttodo=awaitapi.call('get_todo',{params: {id: 1}});conststream=api.watch('live_logs');constunsubSSE=stream.subscribe(data=>console.log("Log Received:",data));constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Message:",msg));socket.send({type: 'hello'});</script>

React (Functional Components)

import{useEffect,useState}from'react';import{api}from'./api-client';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);exportconstDashboard=({ sensorId })=>{const[data,setData]=useState(null);useEffect(()=>{conststream=api.watch('live_logs',{params: {id: sensorId}});constunsubscribe=stream.subscribe(setData);constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("Real-time WS:",msg));return()=>{unsubscribe();socket.close();};},[sensorId]);return<div>{JSON.stringify(data)}</div>;};

Vue 3 (Composition API)

<scriptsetup>import{onMounted,onUnmounted,ref}from'vue';import{api}from'@/services/api';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);constmessages=ref([]);letsseUnsub=null;letsocket=null;onMounted(()=>{conststream=api.watch('live_logs');sseUnsub=stream.subscribe(msg=>messages.value.push(msg));socket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Data:",msg));});onUnmounted(()=>{if(sseUnsub)sseUnsub();if(socket)socket.close();});</script>

6. Core Logic Highlights

Smart URL Resolution

If a path starts with http://, https://, ws://, or wss://, the baseUrl is automatically ignored.

Path Variable Injection

The internal buildUrl utility maps params to :keys in the URL string and converts remaining keys into query strings.

Unified Watcher

Both SSE and WS are accessed via .watch().

  • SSE (Unidirectional)
    • Use .subscribe()
    • Returns an unsubscribe function
  • WebSocket (Bidirectional)
    • Use .subscribe()
    • Use .send()
    • Use .close()

Browser-First

Optimized for browser environments with native WebSocket and EventSource support.

Connection Management

  • Automatic exponential backoff for WebSocket reconnections
  • Clean resource disposal via subscription lifecycle

License

Apache License Version 2.0, January 2004

About

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS).

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

APIEngine

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS). It transforms your API manifest into a strongly-typed, easy-to-use communication layer for modern web applications.


1. Installation

npm install @@sandeshnaroju/api-engine
# or
yarn add @@sandeshnaroju/api-engine

2. Configuration (api.yml)

Add all your Apis in the api.yml file. The engine is driven by a central manifest. It supports both relative paths (using baseUrl) and absolute URLs (bypassing baseUrl).

version: "1.0"baseUrl: "https://jsonplaceholder.typicode.com"endpoints:
get_post:
protocol: "REST"path: "/posts/:id"method: "GET"timeout: 3000create_post:
protocol: "REST"path: "/posts"method: "POST"timeout: 5000ws_test:
protocol: "WS"path: "wss://echo.websocket.org"# Full URL for testingautoReconnect: truesse_test:
protocol: "SSE"path: "http://localhost:3000/api-proxy/apps/api/v1/chat/completions"method: "POST"headers:
"Content-Type": "application/json"

4. Usage

Initializing

If you place your api.yml file in your project's public folder, the engine can find it automatically without you needing to import or pass anything. This is ideal if you want to update the API configuration without rebuilding your entire React/Vue app.

// Looks for /api.yml in your web server's root automaticallyconstapi=awaitAPIEngine.init();

This is the most common method in modern development. You keep the api.yml in your src folder or project root and import it. Because bundlers convert these imports into URL strings, init() handles the background fetching for you.

importmanifestUrlfrom'./api.yml';// init() detects the URL string and fetches the contentconstapi=awaitAPIEngine.init(manifestUrl);

If you are fetching your configuration from a custom database, a CMS, or even a text-area in your UI, you can pass the raw YAML text directly. The engine will parse it on the fly.

constrawYaml=`baseUrl: https://api.production.comendpoints: get_users: protocol: REST path: /users`;// Works with the static initializer...constapi=awaitAPIEngine.init(rawYaml);// ...or directly with the constructorconstapi=newAPIEngine(rawYaml);

If you prefer to work with JSON or hardcoded configuration objects, you can skip the YAML parsing entirely. This is the fastest method as it involves no network requests or string parsing.

constconfig={baseUrl: "https://api.production.com",endpoints: {ping: {protocol: "REST",path: "/health"}}};// Pass the object directlyconstapi=newAPIEngine(config);

You can use the api-engine like below:

REST:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// GETconsttodo=awaitapi.call('get_post',{params: {id: 1}});console.log("Post Title:",todo.title);//POSTconstres=awaitapi.call('create_post',{body: {title: 'New Post',userId: 1}});

SSE:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: Listening to a live log streamconststream=api.watch('sse_test');constunsubscribe=stream.subscribe((log)=>{console.log("New Server Log:",log.message);});// Stop listening when leaving the page// unsubscribe();

WebSocket:

importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);// Normal Usage: A real-time chat or command consoleconstsocket=api.watch('ws_test');// 1. Listen for incoming messagessocket.subscribe((msg)=>{console.log("Incoming Message:",msg.text);});// 2. Send a message backsocket.send({message: "Hi",});// Close connection when done// socket.close();

4. Options

call and watch methods support below extra options.

PropertyTypeDescription
paramsReplaces path variables (e.g., :id in /users/:id).params: { id: 101 }
queryAppends key-value pairs as query strings (e.g., ?limit=10).query: { limit: 10, page: 1 }
bodyThe request payload (JSON) for POST, PUT, and SSE.body: { name: 'Sandesh' }
headersCustom HTTP headers (e.g., Auth tokens).headers: { 'Authorization': 'Bearer ...' }
timeoutMaximum time (ms) to wait before the request fails.timeout: 5000
signalAn AbortSignal used to manually cancel requests.signal: controller.signal
fetchOptionsPass-through for raw Axios or Fetch configurations.fetchOptions: { withCredentials: true }

5. Framework Implementation Examples

Vanilla HTML / JavaScript

<scripttype="module">import{APIEngine}from'./dist/index.js';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);consttodo=awaitapi.call('get_todo',{params: {id: 1}});conststream=api.watch('live_logs');constunsubSSE=stream.subscribe(data=>console.log("Log Received:",data));constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Message:",msg));socket.send({type: 'hello'});</script>

React (Functional Components)

import{useEffect,useState}from'react';import{api}from'./api-client';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);exportconstDashboard=({ sensorId })=>{const[data,setData]=useState(null);useEffect(()=>{conststream=api.watch('live_logs',{params: {id: sensorId}});constunsubscribe=stream.subscribe(setData);constsocket=api.watch('field_comms');socket.subscribe(msg=>console.log("Real-time WS:",msg));return()=>{unsubscribe();socket.close();};},[sensorId]);return<div>{JSON.stringify(data)}</div>;};

Vue 3 (Composition API)

<scriptsetup>import{onMounted,onUnmounted,ref}from'vue';import{api}from'@/services/api';importmanifestfrom'./api.yml';constapi=awaitAPIEngine.init(manifest);constmessages=ref([]);letsseUnsub=null;letsocket=null;onMounted(()=>{conststream=api.watch('live_logs');sseUnsub=stream.subscribe(msg=>messages.value.push(msg));socket=api.watch('field_comms');socket.subscribe(msg=>console.log("WS Data:",msg));});onUnmounted(()=>{if(sseUnsub)sseUnsub();if(socket)socket.close();});</script>

6. Core Logic Highlights

Smart URL Resolution

If a path starts with http://, https://, ws://, or wss://, the baseUrl is automatically ignored.

Path Variable Injection

The internal buildUrl utility maps params to :keys in the URL string and converts remaining keys into query strings.

Unified Watcher

Both SSE and WS are accessed via .watch().

  • SSE (Unidirectional)
    • Use .subscribe()
    • Returns an unsubscribe function
  • WebSocket (Bidirectional)
    • Use .subscribe()
    • Use .send()
    • Use .close()

Browser-First

Optimized for browser environments with native WebSocket and EventSource support.

Connection Management

  • Automatic exponential backoff for WebSocket reconnections
  • Clean resource disposal via subscription lifecycle

License

Apache License Version 2.0, January 2004

About

A lightweight, protocol-agnostic API client designed to unify REST, Server-Sent Events (SSE), and WebSockets (WS).

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages