@eric8810/catcher-napi-http

Rust-powered HTTP + SSE client for Node.js via napi-rs. Part of the catcher toolkit.
Wraps catcher-http's HttpTransport — reqwest + retry + circuit breaker, compiled to a native addon. Includes typed TypeScript wrappers with auto-generated .d.ts.
⚠️ Breaking Changes (0.3.0+)
Migrate from 0.2.x → 0.3.x — see napi API docs for full details.
v0.3.2: HttpResponse, RequestOptions, Metrics types now correctly use camelCase (auto-generated by NAPI-RS). JSON config types (HttpClientConfig, etc.) remain snake_case.
| Entry point | client.js / client.d.ts | dist/client.js / dist/client.d.ts |
| Config format | JSON.stringify(config) only | Typed object or JSON string |
| Class names | JsHttpClient, JsSseStream, JsSseClient | HttpClient, SseStream, SseClient |
| Callback events | Raw JSON strings, need JSON.parse() | Typed objects, auto-parsed |
| Default backoff | Exponential | Fixed |
Default connect_timeout_ms | 5000 | 10000 |
Default min_backoff_ms | 500 | 100 |
Default max_backoff_ms | 30000 | 10000 |
| camelCase fields | Not supported | #[serde(alias)] — both snake_case and camelCase accepted |
- const client = require('@eric8810/catcher-napi-http').HttpClient
- const c = new HttpClient(JSON.stringify({ base_url: '...' }))
+ import { HttpClient } from '@eric8810/catcher-napi-http'
+ const c = new HttpClient({ base_url: '...' })
Install
npm install @eric8810/catcher-napi-http
Pre-built binaries available for Linux (x64/arm64 gnu/musl), macOS (x64/arm64), and Windows (x64/arm64). Installed automatically via platform-specific optionalDependencies.
Usage
import { HttpClient } from '@eric8810/catcher-napi-http'
import type { HttpClientConfig, SseEvent } from '@eric8810/catcher-napi-http/types'
const client = new HttpClient({
base_url: 'https://api.example.com',
connect_timeout_ms: 10000,
retry: { max_attempts: 3, backoff: 'Fixed' },
circuit_breaker: { failure_threshold: 5, reset_timeout_ms: 30000 },
dns: {
cache_size: 512,
cache_ttl_secs: 300,
negative_ttl_secs: 60,
stale_ttl_secs: 3600,
stale_on_error: true,
host_mapping: { 'api.internal': '10.0.0.10' },
},
msgpack: true,
})
const resp = await client.get('/users/1')
console.log(resp.status, resp.body.toString())
await client.post('/messages', Buffer.from(JSON.stringify({ text: 'hello' })), {
contentType: 'application/json',
})
console.log(client.circuitBreakerState())
import { SseStream } from '@eric8810/catcher-napi-http/sse'
const stream = new SseStream(
{ url: 'https://stream.example.com/events' },
(event: SseEvent) => {
if (event.type === 'Line') console.log(event.data)
},
)
import { SseClient } from '@eric8810/catcher-napi-http/sse'
const sse = new SseClient(
{
url: 'https://stream.example.com/events',
reconnect: { max_retries: 10, initial_delay_ms: 1000 },
},
(event: SseEvent) => {
if (event.type === 'Line') console.log(event.data)
},
)
API
new HttpClient(config: HttpClientConfig | string)
Create a client from a typed config object or JSON string. All fields are optional with sensible defaults. Supports both snake_case and camelCase field names.
interface HttpClientConfig {
base_url?: string
connect_timeout_ms?: number
response_timeout_ms?: number
pool?: PoolConfig
tls?: TlsConfig
dns?: DnsConfig
retry?: RetryConfig
circuit_breaker?: CircuitBreakerConfig
max_concurrency?: number
default_headers?: Record<string, string>
hostname_override?: string
proxy?: ProxyConfig
redirect?: RedirectConfig
auth?: { username: string; password: string }
bearer_token?: string
msgpack?: boolean
}
interface DnsConfig {
cache_size?: number
cache_ttl_secs?: number
negative_ttl_secs?: number
stale_ttl_secs?: number
stale_on_error?: boolean
nameservers?: string[]
host_mapping?: Record<string, string>
}
When msgpack is enabled, JSON request bodies are encoded as MessagePack and MessagePack responses are decoded back to JSON bytes when the response content type contains msgpack.
Methods
get(url, options?) | async (url: string, options?: RequestOptions) => HttpResponse |
post(url, body?, options?) | async (url: string, body?: Buffer, options?: RequestOptions) => HttpResponse |
put(url, body?, options?) | async (url: string, body?: Buffer, options?: RequestOptions) => HttpResponse |
delete(url, options?) | async (url: string, options?: RequestOptions) => HttpResponse |
patch(url, body?, options?) | async (url: string, body?: Buffer, options?: RequestOptions) => HttpResponse |
circuitBreakerState() | () => 'closed' | 'open' | 'half-open' |
metrics() | () => Metrics |
executeStream(method, url, body?, options?, onChunk?) | (method: string, url: string, body?: Buffer, options?: RequestOptions, onChunk?: (event: StreamEvent) => void) => void |
setAdaptiveTimeout(min, max, mult, win) | (min: number, max: number, mult: number, win: number) => void |
cancelAll() | () => void |
cancelRequest(requestId) | (requestId: number) => boolean |
nextRequestId() | () => number |
RequestOptions
Per-request options (NAPI-RS auto-generated, camelCase fields):
interface RequestOptions {
headers?: Record<string, string>
timeoutMs?: number
contentType?: string
}
HttpResponse
NAPI-RS auto-generated (camelCase fields):
interface HttpResponse {
status: number
headers: Record<string, string>
body: Buffer
elapsedMs: number
}
Metrics
Runtime metrics snapshot (NAPI-RS auto-generated, camelCase fields):
interface Metrics {
httpRequests: number
httpSuccessRate: number
httpAvgLatencyUs: number
httpRetries: number
wsConnectSuccessRate: number
wsDisconnects: number
wsMessagesSent: number
wsMessagesReceived: number
cbOpenCount: number
queueTimeouts: number
}
Note: HttpResponse, RequestOptions, and Metrics are auto-generated by NAPI-RS (camelCase fields). JSON config types like HttpClientConfig use snake_case (serde-based).
SSE
SseStream | One-shot SSE stream (no auto-reconnect) |
SseClient | Long-lived SSE client with auto-reconnect |
new SseStream(config: SseClientConfig | string, onEvent: (event: SseEvent) => void)
new SseClient(config: SseClientConfig | string, onEvent: (event: SseEvent) => void)
type SseEvent =
| { type: 'Line'; data: string }
| { type: 'Error'; message: string }
| { type: 'End' }
StreamEvent
type StreamEvent =
| { type: 'Headers'; status: number; headers: Record<string, string> }
| { type: 'Chunk'; data: string }
| { type: 'Done' }
| { type: 'Error'; message: string }
Build from Source
Requires Rust toolchain.
npm run build
npm run build:ts
License
MIT