Package Exports
- luminara
- luminara/package.json
Readme
๐ Luminara
Luminara is a modern, universal HTTP client built on native fetch, engineered for developers and teams who demand reliability, scalability, and architectural clarity. It provides full lifecycle control over HTTP requests โ from orchestration and interception to retries, deduplication, and analytics โ all with zero external dependencies.
Lightweight by design yet powerful in scope, Luminara enables consistent, predictable network behavior across all environments โ browsers (React, Vue, Angular, Svelte, vanilla JS) and Node.js 18+. Its domain-driven architecture and type-safe foundation make it ideal for enterprise-grade applications that need transparent debugging, real-time visibility, and extendable control over every request.
๐ Links
- ๐จ Interactive Sandbox And Documentation Website: luminara.website
- ๐ฆ npm Package: npmjs.com/package/luminara
- ๐ GitHub Repository: github.com/miller-28/luminara
- โก Performance Benchmarks: luminara.website/benchmark
โจ Features
Core Architecture
- โก Built on modern native
fetch- Zero external dependencies - ๐ Universal compatibility - Browsers + Node.js 18+ with native fetch
- ๐๏ธ Framework-agnostic - Works with React, Vue, Angular, Svelte, vanilla JS, and Node.js
- ๐๏ธ Domain-driven architecture - Feature-based modular structure
- ๐ฆ Dual export support - ESM/CJS compatibility with auto-detection
- ๐ Extensible driver architecture - Custom drivers via forking
- ๐ Ultra-compact footprint
- ๐ชถ Zero dependencies - Truly standalone
- ๐ Same API everywhere - Identical behavior in all environments
Request Lifecycle (Orchestration Layer)
- ๐ Enhanced interceptor architecture - Deterministic order, mutable context, retry-aware
- ๐ช Plugin system - Extensible architecture with official plugins (cookie-jar)
- ๐ Comprehensive stats system - Real-time metrics, analytics, and query interface
- ๐ Verbose logging system - Detailed debugging and request tracing
Pre-Flight Features (Request Dispatcher - Phase 1)
- ๐ Request deduplication - Automatic in-flight duplicate request prevention
- โฑ๏ธ Request debouncing - Intelligent request delay with automatic cancellation
- ๐ฆ Advanced rate limiting - Token bucket algorithm with global, domain, and endpoint scoping
In-Flight Features (Request Execution - Phase 2)
- โฑ๏ธ Configurable timeouts - Request timeouts and abort controller support
- ๐ Comprehensive retry system - 6 backoff strategies (exponential, fibonacci, jitter, etc.)
- ๐๏ธ Request hedging - Race and cancel-and-retry policies for latency optimization
Post-Flight Features (Response Handlers - Phase 3)
- ๐ฏ Response type handling - JSON, text, form data, binary support
- ๐ก๏ธ Robust error handling - Comprehensive error categorization and handling
Developer Experience
- ๐ฏ Fully promise-based with TypeScript support
โ Battle-Tested Reliability
Luminara is validated by a comprehensive test suite covering all features and edge cases:
- โ 241 tests across 17 test suites (100% passing)
- ๐ฏ Programmatic validation - Tests actual behavior, not just API contracts
- ๐งช Framework simulation - React, Vue, Angular usage patterns
- โฑ๏ธ Timing accuracy - Backoff strategies validated to millisecond precision
- ๐ก๏ธ Error scenarios - Comprehensive failure case coverage
- ๐ Integration testing - Feature combinations (retry + timeout + hedging)
- ๐ Real package testing - Tests built distribution, not source files
Test Categories:
- Basic HTTP Operations (8) โข Retry Logic (23) โข Backoff Strategies (17)
- Request Hedging (24) โข Interceptors (12) โข Stats System (23)
- Rate Limiting (7) โข Debouncing (16) โข Deduplication (17)
- Error Handling (21) โข Timeouts (11) โข Response Types (7)
- Custom Drivers (10) โข Edge Cases (15) โข Framework Patterns (8)
- Plugins (7)
๐ View Test Documentation โข Run Tests Locally
๐ฆ Installation
NPM/Yarn (All Frameworks)
# npm
npm install luminara
# yarn
yarn add luminara
# pnpm
pnpm add luminaraCDN (Vanilla JavaScript)
<!-- ES Modules via CDN -->
<script type="module">
import { createLuminara } from 'https://cdn.skypack.dev/luminara';
// Your code here
</script>Framework-Specific Imports
React, Vue, Angular, Svelte (Browser)
import { createLuminara } from 'luminara';Node.js (ESM)
import { createLuminara } from 'luminara';
const api = createLuminara({
baseURL: 'https://api.example.com',
retry: 3,
timeout: 5000
});
const data = await api.getJson('/users');
console.log(data);Node.js (CommonJS)
const { createLuminara } = require('luminara');
const api = createLuminara({
baseURL: 'https://api.example.com'
});
api.getJson('/users')
.then(data => console.log(data))
.catch(err => console.error(err));Vanilla JavaScript (Browser)
import { createLuminara } from 'luminara';๐ Quick Start
Basic Usage
import { createLuminara } from "luminara";
const api = createLuminara();
// GET JSON
const response = await api.getJson("https://api.example.com/users");
console.log(response.data);
// POST JSON
await api.postJson("https://api.example.com/posts", {
title: "Hello Luminara",
content: "A beautiful HTTP client"
});
// GET Text
const textResponse = await api.getText("https://example.com");
// POST Form Data
await api.postForm("https://api.example.com/upload", {
name: "John",
email: "john@example.com"
});
// PUT/PATCH with JSON
await api.putJson("https://api.example.com/users/1", { name: "Updated" });
await api.patchJson("https://api.example.com/users/1", { email: "new@example.com" });
// GET XML/HTML/Binary
const xmlResponse = await api.getXml("https://api.example.com/feed.xml");
const htmlResponse = await api.getHtml("https://example.com");
const blobResponse = await api.getBlob("https://api.example.com/file.pdf");
const bufferResponse = await api.getArrayBuffer("https://api.example.com/data.bin");
// NDJSON (Newline Delimited JSON)
const ndjsonResponse = await api.getNDJSON("https://api.example.com/stream");
// Multipart form data
const formData = new FormData();
formData.append('file', fileBlob);
await api.postMultipart("https://api.example.com/upload", formData);
// SOAP requests
await api.postSoap("https://api.example.com/soap", xmlPayload, {
soapVersion: '1.1' // or '1.2'
});Configuration
const api = createLuminara({
baseURL: "https://api.example.com",
timeout: 10000,
retry: 3,
retryDelay: 1000,
backoffType: "exponential",
backoffMaxDelay: 30000,
retryStatusCodes: [408, 429, 500, 502, 503, 504],
headers: {
"Authorization": "Bearer YOUR_TOKEN"
},
verbose: true, // Enable detailed logging
statsEnabled: true, // Enable request statistics (default: true)
ignoreResponseError: false, // Throw on HTTP errors (default: false)
responseType: "auto", // Auto-detect response type
query: { // Default query parameters
"api_version": "v1"
}
});Rate Limiting
Luminara includes advanced rate limiting with token bucket algorithm and flexible scoping:
const api = createLuminara({
baseURL: "https://api.example.com",
rateLimit: {
rps: 10, // 10 requests per second
burst: 20, // Allow burst of 20 requests
scope: 'domain' // Rate limit per domain
}
});
// Different scoping options
const globalLimiter = createLuminara({
rateLimit: {
rps: 100,
scope: 'global' // Single rate limit across all requests
}
});
const endpointLimiter = createLuminara({
rateLimit: {
rps: 5,
scope: 'endpoint', // Rate limit per unique endpoint
include: ['/api/users/*'], // Only apply to specific patterns
exclude: ['/api/health'] // Exclude certain endpoints
}
});
// Get rate limiting stats
const rateLimitStats = api.getRateLimitStats();
console.log(rateLimitStats);
// Reset rate limiting stats
api.resetRateLimitStats();๐ค Exports & Advanced Usage
Luminara provides multiple export options for different use cases:
Simple Factory (Recommended)
import { createLuminara } from "luminara";
// Creates client with NativeFetchDriver by default
const api = createLuminara({
baseURL: "https://api.example.com",
retry: 3,
backoffType: "exponential"
});Direct Client & Driver Access
import {
LuminaraClient,
NativeFetchDriver
} from "luminara";
// Use native fetch driver (default and only driver)
const driver = NativeFetchDriver({
timeout: 10000,
retry: 5
});
const api = new LuminaraClient(driver);Feature Utilities & Constants
import {
backoffStrategies,
createBackoffHandler,
defaultRetryPolicy,
createRetryPolicy,
parseRetryAfter,
isIdempotentMethod,
IDEMPOTENT_METHODS,
DEFAULT_RETRY_STATUS_CODES,
StatsHub,
METRIC_TYPES,
GROUP_BY_DIMENSIONS,
TIME_WINDOWS
} from "luminara";
// Use backoff strategies directly
const exponentialDelay = backoffStrategies.exponential(3, 1000); // 4000ms
// Create custom retry policy
const customPolicy = createRetryPolicy({
maxRetries: 5,
statusCodes: [408, 429, 500, 502, 503],
methods: ['GET', 'POST', 'PUT']
});
// Check if method is idempotent
if (isIdempotentMethod('GET')) {
console.log('Safe to retry GET requests');
}
// Create standalone stats instance
const stats = new StatsHub();Build System Support
Luminara supports both ESM and CommonJS with automatic format detection:
// ES Modules (modern bundlers, browsers)
import { createLuminara } from "luminara";
// CommonJS (legacy environments)
const { createLuminara } = require("luminara");Build Requirements for Development:
# Required before testing sandbox/examples - generates dist files
npm run build # Production build
npm run dev # Development with watch mode
npm run build:watch # Alternative watch mode commandNote: The dist files (dist/index.mjs and dist/index.cjs) are generated during build and required for the package to work properly. Always run npm run build after making changes to src/ files.
๐ Retry & Backoff Strategies
Luminara includes 6 built-in backoff strategies for intelligent retry handling:
Linear Backoff
Fixed delay between retries.
const api = createLuminara({
retry: 5,
retryDelay: 1000,
backoffType: 'linear'
});Exponential Backoff
Delays grow exponentially (base ร 2^n).
const api = createLuminara({
retry: 5,
retryDelay: 200,
backoffType: 'exponential'
});
// Delays: 200ms, 400ms, 800ms, 1600ms, 3200msExponential Capped
Exponential growth with a maximum delay cap.
const api = createLuminara({
retry: 5,
retryDelay: 300,
backoffType: 'exponentialCapped',
backoffMaxDelay: 3000
});Fibonacci Backoff
Delays follow the Fibonacci sequence.
const api = createLuminara({
retry: 8,
retryDelay: 200,
backoffType: 'fibonacci'
});
// Delays: 200ms, 200ms, 400ms, 600ms, 1000ms, 1600ms...Jitter Backoff
Randomized delays to prevent thundering herd.
const api = createLuminara({
retry: 3,
retryDelay: 500,
backoffType: 'jitter'
});Exponential Jitter
Combines exponential growth with randomization.
const api = createLuminara({
retry: 4,
retryDelay: 300,
backoffType: 'exponentialJitter',
backoffMaxDelay: 5000
});Custom Retry Handler
For full control, provide a custom retry function:
const api = createLuminara({
retry: 4,
retryDelay: (context) => {
const attempt = context.options.retry || 0;
console.log(`Retry attempt ${attempt}`);
return 150; // Custom delay in milliseconds
}
});Retry on Specific Status Codes
const api = createLuminara({
retry: 3,
retryDelay: 500,
retryStatusCodes: [408, 429, 500, 502, 503]
});๐๏ธ Request Hedging
Request hedging sends multiple concurrent or sequential requests to reduce latency by racing against slow responses. This is particularly effective for high-latency scenarios where P99 tail latencies impact user experience.
When to Use Hedging
Use hedging when:
- High P99 latencies are impacting user experience
- Idempotent read operations (GET, HEAD, OPTIONS)
- Server-side variability is high (cloud, microservices)
- Cost of duplicate requests is acceptable
Don't use hedging when:
- Non-idempotent operations (POST, PUT, DELETE)
- Bandwidth is severely constrained
- Server capacity is limited
- Operations have side effects
Basic Race Policy
Race policy sends multiple concurrent requests and uses the first successful response:
const api = createLuminara({
hedging: {
policy: 'race',
hedgeDelay: 1000, // Wait 1s before sending hedge
maxHedges: 2 // Up to 2 hedge requests
}
});
// Timeline:
// T+0ms: Primary request sent
// T+1000ms: Hedge #1 sent (if primary not complete)
// T+2000ms: Hedge #2 sent (if neither complete)
// First successful response wins, others cancelled
await api.get('/api/data');Cancel-and-Retry Policy
Cancel-and-retry policy cancels slow requests and retries sequentially:
const api = createLuminara({
hedging: {
policy: 'cancel-and-retry',
hedgeDelay: 1500, // Wait 1.5s before cancelling
maxHedges: 2 // Up to 2 hedge attempts
}
});
// Timeline:
// T+0ms: Primary request sent
// T+1500ms: Cancel primary, send hedge #1
// T+3000ms: Cancel hedge #1, send hedge #2
// Only one request active at a time
await api.get('/api/data');Exponential Backoff & Jitter
Increase hedge delays exponentially with randomization to prevent thundering herd:
const api = createLuminara({
hedging: {
policy: 'race',
hedgeDelay: 500, // Base delay: 500ms
maxHedges: 3,
exponentialBackoff: true, // Enable exponential backoff
backoffMultiplier: 2, // 2x each time
jitter: true, // Add randomness
jitterRange: 0.3 // ยฑ30% jitter
}
});
// Hedge timing with backoff:
// - Primary: 0ms
// - Hedge 1: ~500ms (500 ยฑ30%)
// - Hedge 2: ~1000ms (1000 ยฑ30%)
// - Hedge 3: ~2000ms (2000 ยฑ30%)HTTP Method Whitelist
By default, only idempotent methods are hedged:
// Default whitelist: ['GET', 'HEAD', 'OPTIONS']
const api = createLuminara({
hedging: {
policy: 'race',
hedgeDelay: 1000,
maxHedges: 2,
includeHttpMethods: ['GET', 'HEAD', 'OPTIONS', 'POST'] // Custom whitelist
}
});
await api.get('/users'); // โ
Hedged (in whitelist)
await api.post('/data', {}); // โ
Hedged (added to whitelist)
await api.put('/users/1', {}); // โ Not hedged (not in whitelist)Per-Request Configuration (Bidirectional Override)
Override hedging settings for specific requests:
// Scenario 1: Global enabled โ disable per-request
const client = createLuminara({
hedging: { policy: 'race', hedgeDelay: 1000 }
});
await client.get('/critical', {
hedging: { enabled: false } // Disable for this request
});
// Scenario 2: Global disabled โ enable per-request
const client2 = createLuminara({ /* no hedging */ });
await client2.get('/slow-endpoint', {
hedging: { // Enable for this request
policy: 'race',
hedgeDelay: 500,
maxHedges: 1
}
});Server Rotation
Distribute hedge requests across multiple servers:
const api = createLuminara({
hedging: {
policy: 'race',
hedgeDelay: 1000,
maxHedges: 2,
servers: [
'https://api1.example.com',
'https://api2.example.com',
'https://api3.example.com'
]
}
});
// Each hedge uses a different server:
// - Primary: api1.example.com/data
// - Hedge 1: api2.example.com/data
// - Hedge 2: api3.example.com/dataHedging vs Retry
Hedging and Retry serve different purposes and can be used together:
| Feature | Hedging | Retry |
|---|---|---|
| Purpose | Reduce latency | Handle failures |
| Trigger | Slow response | Error response |
| Requests | Concurrent/Sequential proactive | Sequential reactive |
| Cost | Higher (multiple requests) | Lower (on error only) |
| Use Case | P99 optimization | Reliability |
// Combined: Hedging for latency + Retry for reliability
const api = createLuminara({
retry: false, // Disable retry (can use false or 0)
hedging: {
policy: 'race',
hedgeDelay: 1000,
maxHedges: 1
}
});Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
implicit | Explicit enable/disable (implicit if config present) |
policy |
string |
'race' |
'race' or 'cancel-and-retry' |
hedgeDelay |
number |
- | Base delay before hedge (ms) |
maxHedges |
number |
1 |
Maximum hedge requests |
exponentialBackoff |
boolean |
false |
Enable exponential backoff |
backoffMultiplier |
number |
2 |
Backoff multiplier |
jitter |
boolean |
false |
Add randomness to delays |
jitterRange |
number |
0.3 |
Jitter range (ยฑ30%) |
includeHttpMethods |
string[] |
['GET', 'HEAD', 'OPTIONS'] |
Hedged HTTP methods |
servers |
string[] |
[] |
Server rotation URLs |
Performance Implications
Bandwidth: Hedging increases bandwidth usage by sending multiple requests. For a maxHedges: 2 configuration:
- Best case: 1 request (primary succeeds quickly)
- Worst case: 3 requests (primary + 2 hedges)
- Average: ~1.5-2 requests depending on latency
Latency Reduction: Typical P99 improvements:
- Race policy: 30-60% reduction in tail latencies
- Cancel-and-retry: 20-40% reduction with lower bandwidth cost
๐ฆ Rate Limiting
Luminara's rate limiting system uses a token bucket algorithm with flexible scoping to control request flow and prevent API abuse.
Token Bucket Algorithm
The rate limiter maintains token buckets that refill at a steady rate:
const api = createLuminara({
rateLimit: {
rps: 10, // Refill rate: 10 tokens per second
burst: 20 // Bucket capacity: 20 tokens max
}
});
// Allows bursts of 20 requests, then sustained 10 req/sec
await api.getJson('/api/data'); // Uses 1 tokenScoping Strategies
Control rate limiting granularity with different scoping options:
Global Scoping
Single rate limit across all requests:
const api = createLuminara({
rateLimit: {
rps: 100,
scope: 'global' // One bucket for everything
}
});Domain Scoping
Separate rate limits per domain:
const api = createLuminara({
rateLimit: {
rps: 50,
scope: 'domain' // api.example.com vs api2.example.com
}
});Endpoint Scoping
Individual rate limits per unique endpoint:
const api = createLuminara({
rateLimit: {
rps: 5,
scope: 'endpoint' // /api/users vs /api/posts
}
});Pattern Matching
Fine-tune rate limiting with include/exclude patterns:
const api = createLuminara({
rateLimit: {
rps: 10,
scope: 'endpoint',
include: [
'/api/users/*', // Rate limit user endpoints
'/api/posts/*' // Rate limit post endpoints
],
exclude: [
'/api/health', // Exclude health checks
'/api/status' // Exclude status checks
]
}
});Real-time Statistics
Monitor rate limiting performance:
const api = createLuminara({
rateLimit: { rps: 10, burst: 20 }
});
// Get current statistics
const stats = api.stats.query()
.select(['totalRequests', 'rateLimitedRequests', 'averageWaitTime'])
.get();
console.log('Rate limit stats:', stats);Dynamic Configuration
Update rate limits at runtime:
// Start with conservative limits
const api = createLuminara({
rateLimit: { rps: 5, burst: 10 }
});
// Increase limits based on server capacity
api.updateConfig({
rateLimit: { rps: 20, burst: 40 }
});Error Handling
Rate limiting integrates seamlessly with Luminara's error system:
try {
await api.getJson('/api/data');
} catch (error) {
if (error.type === 'RATE_LIMITED') {
console.log(`Rate limited. Retry after: ${error.retryAfter}ms`);
}
}๐ช Plugins
Luminara supports an extensible plugin system to add custom functionality. Plugins can extend the client with new features while maintaining full compatibility with all Luminara features.
Cookie Jar Plugin
Package: luminara-cookie-jar
Automatic Cookie / Set-Cookie header management for server-side environments using tough-cookie.
Perfect for Node.js, SSR applications, CLI tools, and test harnesses where cookies aren't automatically managed by the browser.
Installation:
npm install luminara-cookie-jarQuick Start:
import { createLuminara } from 'luminara';
import { cookieJarPlugin } from 'luminara-cookie-jar';
const client = createLuminara({
baseURL: 'https://api.example.com',
plugins: [cookieJarPlugin()]
});
// Login request sets cookies automatically
await client.post('/login', { username: 'user', password: 'pass' });
// Subsequent requests include cookies automatically
await client.get('/profile'); // Cookies sent automatically!
// Access cookie jar directly
const cookies = await client.jar.getCookies('https://api.example.com');
console.log('Cookies:', cookies);Features:
- ๐ Automatic cookie management (stores
Set-Cookie, sendsCookie) - ๐ Universal compatibility (Node.js, SSR, CLI tools)
- ๐ค Shared cookie jars across multiple clients
- ๐ Full TypeScript support
- ๐ฏ RFC 6265 compliant
Documentation:
- ๐ฆ npm Package
- ๐ Full Documentation
- ๐ Plugin Development Guide
๐ Enhanced Interceptor System
Luminara's interceptor architecture provides deterministic execution order and guaranteed flow control with a mutable context object that travels through the entire request lifecycle.
Execution Flow & Order Guarantees
Request โ onRequest[] (LโR) โ Driver โ onResponse[] (RโL) โ Success
โ
onResponseError[] (RโL) โ ErrorOrder Guarantees:
- onRequest: Executes LeftโRight (registration order)
- onResponse: Executes RightโLeft (reverse registration order)
- onResponseError: Executes RightโLeft (reverse registration order)
- On Retry: Re-runs onRequest interceptors for fresh tokens/headers
Mutable Context Object
Each interceptor receives a mutable context object:
{
req: { /* request object */ }, // Mutable request
res: { /* response object */ }, // Mutable response (in onResponse)
error: { /* error object */ }, // Error details (in onResponseError)
attempt: 1, // Current retry attempt number
controller: AbortController, // Request abort controller
meta: {} // Custom metadata storage
}Request Interceptor
Modify requests with guaranteed LeftโRight execution:
// First registered = First executed
api.use({
onRequest(context) {
console.log(`๐ค Attempt ${context.attempt}:`, context.req.method, context.req.url);
// Modify request directly
context.req.headers = {
...(context.req.headers || {}),
'X-Custom-Header': 'Luminara',
'X-Attempt': context.attempt.toString()
};
// Store metadata for later interceptors
context.meta.startTime = Date.now();
// Add fresh auth token (important for retries!)
context.req.headers['Authorization'] = `Bearer ${getFreshToken()}`;
}
});
// Second registered = Second executed
api.use({
onRequest(context) {
console.log('๐ Adding security headers...');
context.req.headers['X-Request-ID'] = generateRequestId();
}
});Response Interceptor
Transform responses with guaranteed RightโLeft execution:
// First registered = LAST executed (reverse order)
api.use({
onResponse(context) {
console.log('๐ฅ Processing response:', context.res.status);
// Transform response data
context.res.data = {
...context.res.data,
timestamp: new Date().toISOString(),
processingTime: Date.now() - context.meta.startTime,
attempt: context.attempt
};
}
});
// Second registered = FIRST executed (reverse order)
api.use({
onResponse(context) {
console.log('โ
Response received, validating...');
// Validate response structure
if (!context.res.data || typeof context.res.data !== 'object') {
throw new Error('Invalid response format');
}
}
});Error Handler
Handle errors with guaranteed RightโLeft execution:
api.use({
onResponseError(context) {
console.error(`โ Request failed (attempt ${context.attempt}):`, context.req.url);
console.error('Error:', context.error.message);
// Log error details
context.meta.errorLogged = true;
// Modify error before it propagates
if (context.error.status === 401) {
context.error.message = 'Authentication failed - please refresh your session';
}
}
});Retry-Aware Authentication
The enhanced system re-runs onRequest interceptors on retry, perfect for refreshing tokens:
api.use({
onRequest(context) {
// This runs EVERY attempt, ensuring fresh tokens
const token = context.attempt === 1
? getCachedToken()
: await refreshToken(); // Fresh token on retry
context.req.headers['Authorization'] = `Bearer ${token}`;
console.log(`๐ Attempt ${context.attempt}: Using ${context.attempt === 1 ? 'cached' : 'fresh'} token`);
}
});Complex Multi-Interceptor Example
Demonstrates guaranteed execution order and context sharing:
// Interceptor 1: Authentication (runs FIRST on request, LAST on response)
api.use({
onRequest(context) {
console.log('1๏ธโฃ [Auth] Adding authentication...');
context.req.headers['Authorization'] = `Bearer ${getToken()}`;
context.meta.authAdded = true;
},
onResponse(context) {
console.log('1๏ธโฃ [Auth] Validating auth response... (LAST)');
if (context.res.status === 401) {
invalidateToken();
}
},
onResponseError(context) {
console.log('1๏ธโฃ [Auth] Handling auth error... (LAST)');
if (context.error.status === 401) {
context.meta.authFailed = true;
}
}
});
// Interceptor 2: Logging (runs SECOND on request, MIDDLE on response)
api.use({
onRequest(context) {
console.log('2๏ธโฃ [Log] Request started...');
context.meta.startTime = performance.now();
},
onResponse(context) {
console.log('2๏ธโฃ [Log] Request completed (MIDDLE)');
const duration = performance.now() - context.meta.startTime;
console.log(`Duration: ${duration.toFixed(2)}ms`);
},
onResponseError(context) {
console.log('2๏ธโฃ [Log] Request failed (MIDDLE)');
const duration = performance.now() - context.meta.startTime;
console.log(`Failed after: ${duration.toFixed(2)}ms`);
}
});
// Interceptor 3: Analytics (runs THIRD on request, FIRST on response)
api.use({
onRequest(context) {
console.log('3๏ธโฃ [Analytics] Tracking request... (LAST)');
analytics.trackRequestStart(context.req.url);
},
onResponse(context) {
console.log('3๏ธโฃ [Analytics] Tracking success... (FIRST)');
analytics.trackRequestSuccess(context.req.url, context.res.status);
},
onResponseError(context) {
console.log('3๏ธโฃ [Analytics] Tracking error... (FIRST)');
analytics.trackRequestError(context.req.url, context.error);
}
});
/*
Execution Order:
Request: 1๏ธโฃ Auth โ 2๏ธโฃ Log โ 3๏ธโฃ Analytics โ HTTP Request
Response: 3๏ธโฃ Analytics โ 2๏ธโฃ Log โ 1๏ธโฃ Auth
Error: 3๏ธโฃ Analytics โ 2๏ธโฃ Log โ 1๏ธโฃ Auth
*/Abort Controller Access
Every request gets an AbortController accessible via context:
api.use({
onRequest(context) {
// Cancel request after 5 seconds
setTimeout(() => {
console.log('โฐ Request taking too long, aborting...');
context.controller.abort();
}, 5000);
},
onResponseError(context) {
if (context.error.name === 'AbortError') {
console.log('๐ซ Request was aborted');
}
}
});โฑ๏ธ Timeout & Abort
Configure Timeout
const api = createLuminara({
timeout: 5000 // 5 seconds
});
// Will throw timeout error if request takes longer than 5s
await api.get('https://slow-api.example.com/data');Manual Abort with AbortController
const controller = new AbortController();
// Start request
const promise = api.get('https://api.example.com/long-task', {
signal: controller.signal
});
// Abort after 2 seconds
setTimeout(() => controller.abort(), 2000);
try {
await promise;
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request was cancelled');
}
}๐ Custom Drivers
Replace the default native fetch driver with your own implementation:
import { LuminaraClient } from "luminara";
const customDriver = () => ({
async request(options) {
const { url, method = 'GET', headers, body, signal } = options;
const response = await fetch(url, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
signal
});
const contentType = response.headers.get('content-type') || '';
const isJson = contentType.includes('application/json');
const data = isJson ? await response.json() : await response.text();
return {
status: response.status,
headers: response.headers,
data
};
}
});
const api = new LuminaraClient(customDriver());๐ Stats System
Luminara includes a comprehensive statistics system that tracks request metrics, performance data, and analytics in real-time. Perfect for monitoring application health and request patterns.
Basic Stats Usage
const api = createLuminara({
baseURL: "https://api.example.com",
statsEnabled: true // enabled by default
});
// Make some requests
await api.getJson('/users');
await api.postJson('/posts', { title: 'Hello' });
// Get basic counters
const counters = api.stats().counters.get();
console.log(counters);
// { total: 2, success: 2, fail: 0, inflight: 0, retried: 0, aborted: 0 }
// Get performance metrics
const timeMetrics = api.stats().time.get();
console.log(timeMetrics);
// { minMs: 150, avgMs: 275, p50Ms: 200, p95Ms: 350, p99Ms: 350, maxMs: 400 }Advanced Query Interface
The stats system provides a powerful query interface for detailed analytics:
// Query stats by endpoint
const endpointStats = api.stats().query({
metrics: ['counters', 'time', 'rate'],
groupBy: 'endpoint',
window: 'since-reset',
limit: 10
});
// Query stats by domain
const domainStats = api.stats().query({
metrics: ['counters', 'error'],
groupBy: 'domain',
where: { method: 'POST' }
});
// Get rate metrics (requests per second/minute)
const rateStats = api.stats().rate.get();
console.log(rateStats);
// { rps: 2.5, rpm: 150, mode: 'ema-30s' }Available Metrics
- Counters:
total,success,fail,inflight,retried,aborted - Time:
minMs,avgMs,p50Ms,p95Ms,p99Ms,maxMs - Rate:
rps(requests/sec),rpm(requests/min),mode - Retry:
count,giveups,avgBackoffMs,successAfterAvg - Error:
byClass(timeout, network, 4xx, 5xx),topCodes
Grouping & Filtering
// Group by method, filter by domain
const methodStats = api.stats().query({
metrics: ['counters'],
groupBy: 'method',
where: { domain: 'api.example.com' },
window: 'rolling-60s'
});
// Group by endpoint with filters
const filteredStats = api.stats().query({
metrics: ['time', 'error'],
groupBy: 'endpoint',
where: {
method: 'GET',
endpointPrefix: '/api/'
},
limit: 5
});Reset & Snapshots
// Reset all stats
api.stats().reset();
// Reset individual modules
api.stats().counters.reset();
api.stats().time.reset();
// Take a snapshot (all metrics, point-in-time)
const snapshot = api.stats().snapshot();
console.log(snapshot);
// { timestamp: "2025-11-04T...", window: "since-start", groups: [...] }Disable/Enable Stats
// Disable stats for performance-critical apps
const api = createLuminara({
baseURL: "https://api.example.com",
statsEnabled: false
});
// Check if stats are enabled
console.log(api.isStatsEnabled()); // false
// When disabled, stats methods return safe defaults
const counters = api.stats().counters.get(); // Returns zero counters๐จ Interactive Sandbox
Luminara includes a beautiful interactive sandbox where you can explore all features with live examples!
๐ Try the Sandbox โข Sandbox Documentation โข Architecture Guide
The sandbox features:
- 86 Interactive Examples across 16 feature categories
- Live Retry Logging - Watch backoff strategies in action
- Individual Test Controls - Run and stop tests independently
- Real-time Feedback - Color-coded outputs with detailed logs
- Clean Architecture - Demonstrates separation of concerns principles
Sandbox Categories:
- ๐ฆ Basic Usage - GET/POST JSON, Text, Form data
- ๐ Base URL & Query Parameters - URL configuration
- โฑ๏ธ Timeout - Success and failure scenarios
- ๐ Retry - Basic retry with status codes
- ๐ Backoff Strategies - All 6 strategies with live visualization
- ๐๏ธ Request Hedging - Race policy, cancel-and-retry, server rotation
- ๐ Interceptors - Request/response/error interceptors
- ๐ก๏ธ Error Handling - Comprehensive error scenarios
- ๐ฏ Response Types - JSON, text, form, binary data handling
- ๐ Stats System - Real-time metrics and analytics
- ๐ Verbose Logging - Detailed debugging and tracing
- ๐ Custom Drivers - Replace the HTTP backend
- ๐ฆ Rate Limiting - Token bucket algorithm examples
- โฑ๏ธ Debouncer - Search debouncing, button spam protection, method filtering
- ๐ Request Deduplicator - Automatic duplicate prevention, key strategies, TTL
- ๐ช Cookie Jar Plugin - Server-side cookie management
Quick Start:
# Run the sandbox locally
npx serve .
# Open http://localhost:3000/sandbox/๐ Framework Examples
React
import { useEffect, useState } from "react";
import { createLuminara } from "luminara";
const api = createLuminara({
baseURL: "https://api.example.com",
retry: 3,
retryDelay: 1000,
backoffType: "exponential"
});
// Add global error handling
api.use({
onError(error) {
console.error("API Error:", error.message);
}
});
export default function UsersList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
api.getJson("/users")
.then(res => {
setUsers(res.data);
setLoading(false);
})
.catch(err => {
console.error(err);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
}Vue 3 (Composition API)
<script setup>
import { ref, onMounted } from 'vue';
import { createLuminara } from 'luminara';
const api = createLuminara({
baseURL: 'https://api.example.com',
retry: 3,
backoffType: 'exponential'
});
const users = ref([]);
const loading = ref(true);
onMounted(async () => {
try {
const response = await api.getJson('/users');
users.value = response.data;
} catch (error) {
console.error('Failed to fetch users:', error);
} finally {
loading.value = false;
}
});
</script>
<template>
<div>
<div v-if="loading">Loading...</div>
<ul v-else>
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</div>
</template>Angular
import { Component, OnInit } from '@angular/core';
import { createLuminara } from 'luminara';
@Component({
selector: 'app-users',
template: `
<div *ngIf="loading">Loading...</div>
<ul *ngIf="!loading">
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
`
})
export class UsersComponent implements OnInit {
users: any[] = [];
loading = true;
private api = createLuminara({
baseURL: 'https://api.example.com',
retry: 3,
backoffType: 'exponential'
});
async ngOnInit() {
try {
const response = await this.api.getJson('/users');
this.users = response.data;
} catch (error) {
console.error('Failed to fetch users:', error);
} finally {
this.loading = false;
}
}
}Pure JavaScript (No Frameworks)
<!DOCTYPE html>
<html>
<head>
<title>Luminara Example</title>
</head>
<body>
<div id="app">
<div id="loading">Loading...</div>
<ul id="users" style="display: none;"></ul>
</div>
<script type="module">
import { createLuminara } from 'https://cdn.skypack.dev/luminara';
const api = createLuminara({
baseURL: 'https://api.example.com',
retry: 3,
backoffType: 'exponential'
});
async function loadUsers() {
try {
const response = await api.getJson('/users');
const loadingEl = document.getElementById('loading');
const usersEl = document.getElementById('users');
loadingEl.style.display = 'none';
usersEl.style.display = 'block';
response.data.forEach(user => {
const li = document.createElement('li');
li.textContent = user.name;
usersEl.appendChild(li);
});
} catch (error) {
console.error('Failed to fetch users:', error);
}
}
loadUsers();
</script>
</body>
</html>๐ Framework Compatibility
Luminara is designed to be completely framework-agnostic and works seamlessly across all modern JavaScript environments:
| Framework | Compatibility | Example |
|---|---|---|
| React | โ Full Support | useEffect(() => { api.getJson('/data') }, []) |
| Vue 3 | โ Full Support | onMounted(() => api.getJson('/data')) |
| Angular | โ Full Support | ngOnInit() { api.getJson('/data') } |
| Svelte | โ Full Support | onMount(() => api.getJson('/data')) |
| Pure JavaScript | โ Full Support | api.getJson('/data').then(...) |
| Next.js | โ Full Support | Client-side data fetching |
| Nuxt.js | โ Full Support | Client-side data fetching |
| Vite | โ Full Support | All frameworks via Vite |
| Webpack | โ Full Support | All bundled applications |
Browser Support
- โ Chrome 88+
- โ Firefox 90+
- โ Safari 14+
- โ Edge 88+
- โ Mobile browsers (iOS Safari, Chrome Mobile)
Node.js Support
- โ Node.js 18.x (LTS - Maintenance)
- โ Node.js 20.x (LTS - Active)
- โ Node.js 22.x (LTS - Current)
Runtime Requirements
- Universal: Works in browsers and Node.js 18+
- Modern
fetchAPI support (native in all supported environments) - ES2020+ JavaScript features
- ES Modules support
Build System
- Dual Exports: Automatic ESM/CJS format support
- Auto-Build:
npm run buildornpm run dev(watch mode) - TypeScript Support: Generated type definitions
- Universal Compatibility: Works across all JavaScript environments
โก Performance & Benchmarks
Luminara includes a comprehensive benchmark suite validated across Node.js and browsers โ from micro-operations to full end-to-end request flows.
Benchmark Suite Features
- 68 Node.js Benchmarks - High-precision measurements with memory profiling (Tinybench 2.9.0)
- 18 Browser Benchmarks - Automated headless testing across Chromium, Firefox, and WebKit
- Interactive Browser UI - Real-time testing with Chart.js visualizations
- Historical Tracking - Performance regression detection and baseline comparison
- Beautiful Reports - HTML reports with charts, trends, and statistical analysis
Performance Characteristics (Latest Results)
| Layer | Node.js (Mean) | Browser (Typical) | Verdict |
|---|---|---|---|
| Core API | 0.15โ7.5 ยตs | 5โ30 ยตs | โก Ideal (microsecond precision) |
| Plugin Orchestration | 26โ120 ยตs | same magnitude | โ Excellent (linear scaling) |
| Driver Layer | 0.09โ60 ยตs | same magnitude | โ Excellent (minimal overhead) |
| Fetch Roundtrip (local mock) | 2โ4 ms | 3โ25 ms | โ๏ธ I/O-bound (network dominates) |
| Feature Utilities | 2โ6 ms | 10โ25 ms | โ Expected (sub-ms overhead) |
| Integrated Scenarios | 2.3โ27 ms | similar envelope | ๐ชถ Balanced (near-zero architectural tax) |
Running Benchmarks
# Node.js benchmarks (all categories)
cd benchmark/node
npm run benchmark # Full suite (68 benchmarks)
# Specific categories
npm run benchmark:core # Core API (createLuminara, use, updateConfig)
npm run benchmark:orchestration # Plugin pipeline, context, signals
npm run benchmark:driver # Pre-flight, in-flight, post-flight
npm run benchmark:features # Retry, stats, rate-limit, hedging
npm run benchmark:integrated # End-to-end scenarios
# Browser benchmarks (interactive)
cd benchmark/browser
npm run dev # Interactive UI with Chart.js
# Headless cross-browser testing
cd benchmark/headless
npm run benchmark # Full suite (Chromium, Firefox, WebKit)
npm run benchmark:quick # Quick test (Chromium only)
# Generate reports
cd benchmark
npm run benchmark:report # HTML report with chartsKey Findings (Node.js v22.14.0)
โ
Microsecond-Scale Core - Client creation ~7.5ยตs, plugin registration ~0.16ยตs, config updates ~0.74ยตs
โ
Linear Plugin Scaling - 10 plugins add only ~120ยตs overhead (vs ~30ยตs for empty pipeline)
โ
Sub-Millisecond Orchestration - Full pipeline execution stays under 0.2ms even with 10 plugins
โ
Network Parity - Single request ~2.35ms (bare), ~2.67ms (all features ON) โ 0.32ms overhead
โ
Predictable Concurrency - 10 concurrent requests: 25ms / 50 concurrent: 130ms (event-loop bound)
โ
Production Validated - 68 benchmarks, millions of iterations, tight percentiles (P99 โค 4ร mean)
๐ View Detailed Performance Analysis
๐ Documentation
- Sandbox Guide - Interactive examples and usage
- Command Line Tests Guide - Cli test usage
- Benchmark Suite Guide - Benchmark usage and configuration
- Performance Benchmarks - Detailed performance analysis
๐ง License
MIT ยฉ 2025 Jonathan Miller โข LinkedIn
๐ช Philosophy
Luminara โ derived from "lumen" (light) โ symbolizes clarity and adaptability.
Like light traveling through space, Luminara guides your HTTP requests with grace, reliability, and cosmic precision across all JavaScript environments. Built with mindfulness for developers who craft with intention.
Framework-Agnostic โข Simple by Design โข Separation of Concerns โข Developer-Friendly โข Extensible