{"_id":"@bonapasogit-dev/http-client","name":"@bonapasogit-dev/http-client","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@bonapasogit-dev/http-client","version":"0.0.1","description":"Production-grade HTTP client with circuit breaker, retries, and structured logging","license":"MIT","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","browser":{"import":"./dist/index.js"},"import":"./dist/index.js"},"./transports":{"types":"./dist/transports/index.d.ts","browser":{"import":"./dist/transports/browser.js"},"import":"./dist/transports/index.js"}},"scripts":{"build":"tsc","test":"vitest","test:ui":"vitest --ui","test:run":"vitest run","test:coverage":"vitest run --coverage","benchmark":"tsx ./benchmarks/transport-benchmark.ts","benchmark:real":"tsx ./benchmarks/transport-benchmark.ts","clean":"rimraf dist","lint":"eslint src tests","type-check":"tsc --noEmit"},"keywords":["http","client","fetch","circuit-breaker","retry","logging","typescript"],"engines":{"node":">=18.0.0"},"dependencies":{"undici":"^6.21.3"},"devDependencies":{"@types/node":"^20.0.0","tsx":"^4.19.2","typescript":"^5.3.0","vitest":"^1.0.0","@vitest/ui":"^1.0.0","rimraf":"^5.0.0"},"_id":"@bonapasogit-dev/http-client@0.0.1","gitHead":"9672a39cf698535971c6d0deb171eca9f78c5c16","_nodeVersion":"22.13.1","_npmVersion":"10.9.2","dist":{"integrity":"sha512-B4gOaFDSh5thips+jOJbdfaY+1nRmVj1jnxlBIPxCJhq2HiY6KUR4WZwDH2rDCBtyJeqbWwVlDK4XTYG5V3QvA==","shasum":"5625b7418ec4ad0d39fc4d0a4936be871a4ff152","tarball":"https://registry.npmjs.org/@bonapasogit-dev/http-client/-/http-client-0.0.1.tgz","fileCount":63,"unpackedSize":184047,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIGFhTsmpsOkJvkqxc1pJ6Rqyz5LLZavPS882AWBRn5fqAiAoEQAadIxT952ayYGpyoCrWx1ZusWpCmpMIO93k4+uiw=="}]},"_npmUser":{"name":"vldcreation","email":"vicktordesrony@gmail.com"},"directories":{},"maintainers":[{"name":"vldcreation","email":"vicktordesrony@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/http-client_0.0.1_1774265468575_0.6052184975573958"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-23T11:31:08.380Z","0.0.1":"2026-03-23T11:31:08.732Z","modified":"2026-03-23T11:31:08.995Z"},"maintainers":[{"name":"vldcreation","email":"vicktordesrony@gmail.com"}],"description":"Production-grade HTTP client with circuit breaker, retries, and structured logging","keywords":["http","client","fetch","circuit-breaker","retry","logging","typescript"],"license":"MIT","readme":"# HTTP Client\n\nProduction-grade HTTP client for Node.js with advanced reliability features.\n\n## Features\n\n### Core Capabilities\n- **Type-Safe**: Full TypeScript support with comprehensive interfaces\n- **Automatic Retries**: Exponential backoff with jitter for failed requests\n- **Circuit Breaker**: Prevents cascading failures in distributed systems\n- **Request Timeout**: Configurable timeout per request or globally\n- **Responder Contract**: Success/error responses normalized to `{ message, data, meta, error }`\n- **URL Building**: Query parameter filtering and URL construction\n\n### Reliability\n- **Structured Logging**: Consistent, contextual logging with redaction of secrets\n- **Error Classification**: Specific error types for different failure scenarios\n- **Graceful Degradation**: Circuit breaker with half-open state for recovery\n- **Network Resilience**: Retry support for transient failures\n\n### Developer Experience\n- **Detailed Error Objects**: Rich error information for debugging\n- **Request Tracing**: Unique request IDs for end-to-end tracing\n- **Sensitive Data Protection**: Automatic redaction of secrets in logs\n- **Memory Safe**: Proper cleanup and resource management\n\n## Installation\n\n```bash\nnpm install @bonapasogit-dev/http-client\n```\n\n## Quick Start\n\n```typescript\nimport { HttpClient, errorToLogObject } from '@bonapasogit-dev/http-client';\n\nconst client = new HttpClient({\n  baseUrl: 'https://api.example.com',\n  timeoutMs: 5000,\n  retries: 3,\n});\n\ntry {\n  const response = await client.get('/users/123');\n  console.log(response.message);\n  console.log(response.data);\n  console.log(response.meta);\n  \n  const created = await client.post('/users', {\n    name: 'John',\n    email: 'john@example.com',\n  });\n  console.log(created.message);\n  console.log(created.data);\n} catch (error) {\n  // HttpError carries responder-formatted payload in error.response\n  console.error(errorToLogObject(error));\n} finally {\n  await client.close();\n}\n```\n\n## Configuration\n\n### HttpClientOptions\n\n```typescript\ninterface HttpClientOptions {\n  // Required\n  baseUrl: string;                    // Base URL for all requests\n  \n  // Optional - Behavior\n  timeoutMs?: number;                 // Request timeout (default: 5000ms)\n  retries?: number;                   // Maximum retry attempts (default: 0)\n  \n  // Optional - Headers & Logging\n  headers?: Record<string, string>;   // Default headers\n  logger?: Logger;                    // Custom logger (default: console)\n  logResponseBody?: boolean;          // Log response bodies (default: true)\n  maxLogBodyLength?: number;          // Max response log size (default: 2000)\n  \n  // Optional - Performance\n  maxConnections?: number;            // Connection pool size (default: 100)\n  pipelining?: number;                // HTTP pipelining depth (default: 1)\n  \n  // Optional - Advanced\n  backoff?: BackoffConfig;            // Retry backoff strategy\n  circuitBreaker?: CircuitBreakerConfig;\n  transport?: HttpTransportOptions;   // Hybrid runtime transport strategy\n}\n```\n\n### Transport Configuration (Hybrid Fetch/Undici)\n\n```typescript\ntype TransportMode = 'auto' | 'fetch' | 'undici';\n\ninterface HttpTransportOptions {\n  mode?: TransportMode;\n  fallbackToFetchOnUndiciError?: boolean;\n  undici?: {\n    connections?: number;\n    pipelining?: number;\n    keepAliveTimeout?: number;\n    keepAliveMaxTimeout?: number;\n    headersTimeout?: number;\n    bodyTimeout?: number;\n  };\n}\n```\n\n- `auto` (default): Node.js prefers `undici`; browser/edge uses `fetch`.\n- `fetch`: force runtime-native fetch behavior.\n- `undici`: force undici transport (Node-only runtime target).\n\n### Throughput Benchmark (Fetch vs Undici vs Auto)\n\nRun local synthetic benchmark:\n\n```bash\nnpm run benchmark\n```\n\nRun against real endpoint:\n\n```bash\nnpm run benchmark:real -- \\\n  --target https://api.your-service.com \\\n  --path /health \\\n  --requests 20000 \\\n  --concurrency 300 \\\n  --warmup 2000\n```\n\nDirect invocation is also supported:\n\n```bash\ntsx ./benchmarks/transport-benchmark.ts --target https://api.your-service.com\n```\n\nSupported benchmark flags:\n\n- `--target <url>`: base URL for real workload (omit to use local server)\n- `--path <path>`: request path (default: `/bench`)\n- `--requests <n>`: measured requests (default: 10000)\n- `--concurrency <n>`: concurrent workers (default: 200)\n- `--warmup <n>`: warmup requests (default: 1000)\n- `--mode <all|fetch|undici|auto>`: transport mode set (default: all)\n- `--timeout <ms>`: request timeout in milliseconds (default: 5000)\n- `--connections <n>`: undici max connections (default: auto)\n- `--pipelining <n>`: undici pipelining factor (default: 4)\n\nPrinciple benchmark guidance:\n\n- Use representative payload size and endpoint logic.\n- Run 3-5 iterations and compare median throughput + p95 latency.\n- Always evaluate success rate alongside throughput. High fail rates can make a mode look artificially fast or slow.\n- Benchmark runner disables circuit opening to isolate transport performance; this is intentional for fair mode comparison.\n- For Node service-to-service traffic, `undici` or `auto` is usually best.\n- For browser compatibility and universal code paths, keep `auto` as default.\n\n### Circuit Breaker Configuration\n\n```typescript\ninterface CircuitBreakerConfig {\n  failureThreshold: number;           // Failures before opening (default: 5)\n  openTimeoutMs: number;              // Time in open state (default: 10000ms)\n  halfOpenSuccessRate: number;        // Success probability in half-open (default: 0.1)\n}\n```\n\n### Backoff Configuration\n\n```typescript\ninterface BackoffConfig {\n  baseMs: number;                     // Base milliseconds (default: 50)\n  maxMs: number;                      // Maximum milliseconds (default: 1000)\n}\n```\n\n## API Reference\n\n### HTTP Methods\n\nAll methods return `Promise<HttpSuccess<T>>` where `T` is the type of `data`.\n\n```typescript\n// GET request\nconst response = await client.get<User>('/users/123');\n\n// POST request with body\nconst created = await client.post<User>('/users', { name: 'John' });\n\n// PUT request\nconst updated = await client.put<User>('/users/123', { name: 'Jane' });\n\n// PATCH request\nconst patched = await client.patch<User>('/users/123', { status: 'active' });\n\n// DELETE request\nconst deleted = await client.delete<void>('/users/123');\n\n// HEAD request\nconst head = await client.head('/users/123');\n```\n\n### Request Options\n\nOverride default behavior per request:\n```typescript\n// GET request\nconst response = await client.get('/users', {\n  query: { page: 1, limit: 10 },\n  headers: { 'X-Custom-Header': 'value' },\n  timeoutMs: 10000,\n  requestId: 'custom-trace-id',\n});\n```\n```typescript\n// POST request with body\nconst response = await client.post(\n  '/users',\n  { name: 'John' },\n  {\n    headers: { 'X-Custom-Header': 'value' },\n    timeoutMs: 10000,\n    requestId: 'custom-trace-id',\n  }\n);\n```\n\n### Response Object\n\n```typescript\ninterface ApiResponse<T> {\n  message: string;\n  data: T | null;\n  meta: Record<string, unknown>;\n  error: ApiErrorObject | null;\n}\n\ninterface HttpSuccess<T> {\n  message: string;\n  data: T | null;\n  meta: Record<string, unknown>;\n  error: ApiErrorObject | null;\n  \n  json(): Promise<ApiResponse<T>>;    // Get full responder payload\n  text(): Promise<string>;            // Get payload as JSON string\n}\n```\n\n### Responder Contract Behavior\n\n- If upstream already returns responder shape, it is preserved.\n- If upstream returns raw success body, client wraps it as:\n  - `{ message: <statusText|Success>, data: <raw>, meta: {}, error: null }`\n- If upstream returns raw error body on non-2xx, client throws `HttpError` with:\n  - `error.response = { message, data: null, meta, error: { code, message, details } }`\n\n### Error Handling\n\n```typescript\nimport {\n  HttpError,\n  CircuitOpenError,\n  TimeoutError,\n  NetworkError,\n  errorToLogObject,\n} from '@bonapasogit-dev/http-client';\n\ntry {\n  const response = await client.get<User>('/api/endpoint');\n  console.log(response.data); // strongly typed\n} catch (error) {\n  if (error instanceof HttpError) {\n    console.error(`HTTP ${error.status}`);\n    console.error(error.response?.message);\n    console.error(error.response?.error?.code);\n    console.error(error.response?.error?.details ?? []);\n  } else if (error instanceof TimeoutError) {\n    console.error('Request timed out');\n  } else if (error instanceof CircuitOpenError) {\n    console.error('Service unavailable (circuit open)');\n  } else if (error instanceof NetworkError) {\n    console.error('Network error:', error.message);\n  }\n  \n  // Always available: serialize to log object\n  console.error(errorToLogObject(error));\n}\n```\n\n### Using With Responder (Frontend Pattern)\n\n```typescript\nimport { HttpClient, HttpError } from '@bonapasogit-dev/http-client';\n\ntype User = { id: string; name: string; email: string };\n\nconst client = new HttpClient({\n  baseUrl: 'https://api.example.com',\n});\n\nexport async function fetchUser(userId: string): Promise<User> {\n  try {\n    const res = await client.get<User>(`/users/${userId}`);\n\n    // Responder success envelope\n    // { message, data, meta, error: null }\n    if (!res.data) {\n      throw new Error('User not found');\n    }\n\n    return res.data;\n  } catch (error) {\n    if (error instanceof HttpError && error.response?.error) {\n      // Responder error envelope\n      // { message, data: null, meta, error: { code, message, details } }\n      throw new Error(error.response.error.message);\n    }\n\n    throw error;\n  }\n}\n```\n\n## Logging\n\n### Structured Logging\n\nThe client provides structured logging for all operations:\n\n```typescript\nconst client = new HttpClient({\n  baseUrl: 'https://api.example.com',\n  logger: myLogger, // Must implement Logger interface\n  logResponseBody: true,\n  maxLogBodyLength: 2000,\n});\n```\n\n### Logger Interface\n\n```typescript\ninterface Logger {\n  info(message?: unknown, ...args: unknown[]): void;\n  warn(message?: unknown, ...args: unknown[]): void;\n  error(message?: unknown, ...args: unknown[]): void;\n  debug?(message?: unknown, ...args: unknown[]): void;\n}\n```\n\n### Log Events\n\n- **Request start**: Initial request with parameters\n- **Request success**: Successful response with data\n- **Request error**: Failed request with error details\n- **Retry scheduled**: Scheduled retry with delay\n- **Circuit state change**: Circuit breaker state transitions\n- **Circuit blocked**: Request blocked by circuit breaker\n\n### Security\n\nSensitive headers are automatically redacted:\n- Authorization, Cookie, Token headers\n- API keys, secrets, passwords\n- Custom sensitive keys containing \"secret\", \"password\", \"token\"\n\n## Retry Strategy\n\nRetry logic follows these rules:\n\n1. **Max Retries**: Respects `retries` option\n2. **Retriable Errors**:\n   - Network errors (connection failed, DNS error)\n   - Timeout errors\n   - 5xx server errors\n   - 408 (Request Timeout)\n   - 429 (Too Many Requests)\n3. **Non-Retriable**:\n   - 4xx client errors (400, 401, 403, 404, etc.)\n   - Circuit breaker open errors\n4. **Backoff**: Exponential backoff with jitter\n\n## Circuit Breaker Pattern\n\nProtects against cascading failures:\n\n### States\n\n- **CLOSED**: Normal operation, all requests allowed\n- **OPEN**: Failure threshold reached, requests rejected immediately\n- **HALF_OPEN**: Gradual recovery, some requests allowed\n\n### Behavior\n\n1. Each 5xx error increments failure counter\n2. 4xx errors don't count as failures\n3. When failures reach threshold, circuit opens\n4. After timeout, enters half-open state\n5. In half-open, some requests are allowed (configurable probability)\n6. If requests succeed, circuit closes\n7. If requests fail, circuit reopens\n\n## Example: Advanced Configuration\n\n```typescript\nimport { HttpClient } from '@bonapasogit-dev/http-client';\n\nconst client = new HttpClient({\n  baseUrl: 'https://api.example.com',\n  timeoutMs: 10000,\n  retries: 5,\n  transport: {\n    mode: 'auto',\n    undici: {\n      connections: 200,\n      pipelining: 4,\n      keepAliveTimeout: 10000,\n    },\n  },\n  \n  // Custom headers for all requests\n  headers: {\n    'User-Agent': 'MyApp/1.0.0',\n    'Accept': 'application/json',\n  },\n  \n  // Structured logging\n  logger: winston.createLogger({\n    format: winston.format.json(),\n  }),\n  \n  // Aggressive retry backoff\n  backoff: {\n    baseMs: 100,\n    maxMs: 5000,\n  },\n  \n  // Sensitive circuit breaker\n  circuitBreaker: {\n    failureThreshold: 3,\n    openTimeoutMs: 30000,\n    halfOpenSuccessRate: 0.2,\n  },\n});\n\ntry {\n  const users = await client.get('/users', {\n    query: { page: 1, limit: 50 },\n    timeoutMs: 15000, // Override per-request\n  });\n} finally {\n  await client.close();\n}\n```\n\n## Testing\n\nRun tests with Vitest:\n\n```bash\nnpm test                    # Watch mode\nnpm run test:run           # Single run\nnpm run test:coverage      # With coverage report\nnpm run test:ui            # UI dashboard\n```\n\nTest coverage includes:\n- Error classes and serialization\n- Utility functions and helpers\n- Response parsing and formatting\n- HTTP client core functionality\n- Circuit breaker logic and state management\n- Retry mechanisms and backoff calculation\n- Logging and sensitive data redaction\n\n## Architecture\n\n### Module Structure\n\n```\nsrc/\n├── types.ts              # Core types and interfaces\n├── errors.ts             # Error classes and classification\n├── response.ts           # Response wrapper\n├── httpClient.ts         # Main client implementation\n├── utils.ts              # Utility functions\n├── logger.ts             # Logging utilities\n└── index.ts              # Public API exports\n```\n\n### Design Principles\n\n1. **Type Safety**: Full TypeScript, strict mode enabled\n2. **Composition**: Utilities combined into cohesive client\n3. **Separation of Concerns**: Errors, logging, and networking isolated\n4. **Resource Management**: Proper cleanup and GC considerations\n5. **Observability**: Structured logging with tracing support\n6. **Production Ready**: Error handling, timeouts, and graceful degradation\n\n## Performance\n\n- **Zero External Dependencies**: Minimal bundle footprint\n- **Efficient Backoff**: Jitter prevents thundering herd\n- **Connection Pooling**: Reuses HTTP connections\n- **Memory Efficient**: Proper cleanup and streaming\n- **Lock-Free**: No synchronization overhead in single-threaded Node.js\n\n## Browser Compatibility\n\nThis client is designed to work in environments that provide the standard `fetch` API:\n- **Node.js**: Version 18+ (uses the built-in global `fetch`).\n- **Browsers**: Modern browsers that implement the Fetch API (and related primitives like `AbortController`).\n- **Other runtimes**: Any JavaScript runtime that exposes a compatible `fetch` implementation.\n\nFor older environments without native `fetch`, you must provide a compatible `fetch` implementation (e.g., via a polyfill or dependency injection when constructing the client).\n\n## License\n\nReleased under the MIT License. © @bonapasogit-dev. See the root LICENSE file for full terms.\n","readmeFilename":"README.md","_rev":"1-c48e4d181000514d4572d9caf1aebf50"}