JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 30
  • Score
    100M100P100Q96900F
  • License MIT

Zero-dependency, high-efficiency HTTP client built on native fetch with a microsecond-scale core. Features a deterministic plugin pipeline, advanced retry/backoff, hedging, rate limiting, deduplication, and a real-time stats engine โ€” all built-in, with no external plugins required. Lightweight, universal, and designed for browsers and Node.js 18+.

Package Exports

  • luminara
  • luminara/package.json

Readme

๐ŸŒŒ Luminara

Website GitHub npm License

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.

โœจ 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 luminara

CDN (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:

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 command

Note: 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, 3200ms

Exponential 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/data

Hedging 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 token

Scoping 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.

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-jar

Quick 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, sends Cookie)
  • ๐ŸŒ Universal compatibility (Node.js, SSR, CLI tools)
  • ๐Ÿค Shared cookie jars across multiple clients
  • ๐Ÿ“ Full TypeScript support
  • ๐ŸŽฏ RFC 6265 compliant

Documentation:


๐Ÿ”Œ 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) โ†’ Error

Order 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:

  1. ๐Ÿ“ฆ Basic Usage - GET/POST JSON, Text, Form data
  2. ๐Ÿ”— Base URL & Query Parameters - URL configuration
  3. โฑ๏ธ Timeout - Success and failure scenarios
  4. ๐Ÿ”„ Retry - Basic retry with status codes
  5. ๐Ÿ“ˆ Backoff Strategies - All 6 strategies with live visualization
  6. ๐ŸŽ๏ธ Request Hedging - Race policy, cancel-and-retry, server rotation
  7. ๐Ÿ”Œ Interceptors - Request/response/error interceptors
  8. ๐Ÿ›ก๏ธ Error Handling - Comprehensive error scenarios
  9. ๐ŸŽฏ Response Types - JSON, text, form, binary data handling
  10. ๐Ÿ“Š Stats System - Real-time metrics and analytics
  11. ๐Ÿ“ Verbose Logging - Detailed debugging and tracing
  12. ๐Ÿš— Custom Drivers - Replace the HTTP backend
  13. ๐Ÿšฆ Rate Limiting - Token bucket algorithm examples
  14. โฑ๏ธ Debouncer - Search debouncing, button spam protection, method filtering
  15. ๐Ÿ” Request Deduplicator - Automatic duplicate prevention, key strategies, TTL
  16. ๐Ÿช 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 fetch API 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 build or npm 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 charts

Key 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


๐Ÿง  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