Resilient network communication toolkit — Rust core, TypeScript wrappers, Flutter bindings, cross-platform.
"Catcher" — catches network failures before they reach your business logic.
⚠️ Breaking Changes (0.3.0+)napi packages (Node.js native) Change Before After Entry point client.jsdist/client.jsConfig JSON string only Typed object or JSON string Class names JsHttpClient, JsWsClientHttpClient, WsClientCallback events JSON strings Typed objects (auto-parsed) WS Message data event.dataevent.data_base64 (base64)NAPI-RS types — camelCase fields (elapsedMs, timeoutMs, etc.) TLS (wss://) ❌ napi-ws broken ✅ rustls built-in (v0.3.2)
Change Before After BackoffKind::default()ExponentialFixedRetryConfig::default().backoffExponentialFixedWS deflate_threshold — Renamed → deflate_threshold_bytes WS max_message_size — Renamed → max_payload_bytes WS ping_timeout_ms — Renamed → pong_timeout_ms All config structs snake_case onlysnake_case + camelCase via #[serde(alias)]
See package-specific READMEs for detailed migration instructions.
Platform Package Status Node.js (native) @eric8810/catcher-napi-http / @eric8810/catcher-napi-ws✅ ⭐ 推荐 Node.js (TS) @eric8810/catcher-http / @eric8810/catcher-ws✅ Published Electron same as Node.js ✅ Web @eric8810/catcher-web✅ Published Rust catcher-http / catcher-ws / catcher-core✅ Published Flutter catcher_core (dart:ffi)✅ Published Android + iOS catcher-uniffi (UniFFI)✅ Published
catcher-core (Rust) @eric8810/catcher-core (TS)
│ │
▼ ┌───┴───┐
catcher-dns ▼ ▼
│ @eric8810 @eric8810
┌───┴───┐ /catcher- /catcher-
▼ ▼ http ws
catcher catcher (axios) (ws)
-http -ws (+SSE)
│ │ │ │
│ └──napi-rs──┐ ┌──napi-rs──┘
│ ▼ ▼
│ @eric8810/catcher-napi-http ⭐ Node.js 推荐
│ @eric8810/catcher-napi-ws ⭐ Node.js 推荐
│ (typed TS wrappers + .node)
│
│ ┌─────────────────────────────────┐
├───┤ catcher-ffi (cdylib umbrella) │
│ │ bridges catcher-http + ws │
│ │ exports 25 C ABI symbols │
│ └──────┬──────────┬───────────────┘
│ ▼ ▼
│ dart:ffi UniFFI
│ (Flutter) (Kotlin/Swift)
│ │ │
│ catcher_core catcher-uniffi
└─────────────────────────────────┘
┌─────────────────────┐
│ @eric8810/catcher-web│
│ (Browser, fetch) │
│ HTTP + SSE │
└─────────────────────┘
Package Version Description catcher_coreFlutter dart:ffi bindings
Node.js (native — Rust via napi-rs) ⭐ 推荐 npm install @eric8810/catcher-napi-http @eric8810/catcher-napi-ws Node.js (TS — pure TypeScript) npm install @eric8810/catcher-http @eric8810/catcher-ws npm install @eric8810/catcher-web dependencies :
catcher_core : ^0.3.18 // HTTP — Rust native performance + typed config import { HttpClient } from '@eric8810/catcher-napi-http' const client = new HttpClient ( { base_url : 'https://api.example.com' , // camelCase 也可以: baseUrl connect_timeout_ms : 10000 , retry : { max_attempts : 3 , backoff : 'Fixed' } , circuit_breaker : { failure_threshold : 5 , reset_timeout_ms : 30000 } , dns : { cache_ttl_secs : 300 , stale_on_error : true } , msgpack : true , // auto JSON↔msgpack at transport layer } ) const resp = await client . get ( '/users/1' ) console . log ( resp . status , resp . body . toString ( ) ) // SSE — auto-reconnect 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 ) => { if ( event . type === 'Line' ) console . log ( event . data ) } , ) // WebSocket — typed events import { WsClient } from '@eric8810/catcher-napi-ws' const ws = new WsClient ( { urls : [ 'wss://cn.example.com' , 'wss://sg.example.com' ] , reconnect : { initial_delay_ms : 500 , max_delay_ms : 30000 } , dns : { cache_ttl_secs : 300 , stale_on_error : true } , msgpack : true } , ( event ) => { if ( event . type === 'Message' ) console . log ( Buffer . from ( event . data_base64 , 'base64' ) . toString ( ) ) } , ) ws . send ( JSON . stringify ( { event : 'hello' } ) ) // HTTP — one line to replace axios.create() import { createHttpClient } from '@eric8810/catcher-http' const client = createHttpClient ( { baseURL : 'https://api.example.com' , keepAlive : true , retry : { attempts : 3 } , concurrency : 10 , circuitBreaker : { failureThreshold : 5 , resetTimeout : 30_000 } , } ) const data = await client . get ( '/users/1' ) const result = await client . post ( '/messages' , { text : 'hello' } ) // Per-request overrides await client . get ( '/analytics' , { retry : false , timeout : 5000 } ) // Dynamic interceptors client . interceptors . request . use ( config => { config . headers [ 'Authorization' ] = `Bearer ${ token } ` return config } ) // WebSocket — compression + reconnect + multi-endpoint import { createResilientWS , pack , decodeWSMessage } from '@eric8810/catcher-ws' const ws = createResilientWS ( { url : [ 'wss://cn.example.com' , 'wss://sg.example.com' ] , perMessageDeflate : true , reconnect : { initialDelay : 1000 , maxDelay : 30_000 } , } ) ws . send ( pack ( { event : 'message' , data : msg } ) ) ws . addEventListener ( 'message' , e => console . log ( decodeWSMessage ( e . data ) ) ) // SSE — AI streaming (OpenAI compatible) import { createSSEStream } from '@eric8810/catcher-http' const stream = createSSEStream ( { url : 'https://api.openai.com/v1/chat/completions' , method : 'POST' , headers : { Authorization : `Bearer ${ apiKey } ` } , body : { model : 'gpt-4' , messages : [ { role : 'user' , content : 'Hello' } ] , stream : true } , } ) for await ( const line of stream ) { if ( ! line . startsWith ( 'data:' ) ) continue const payload = line . startsWith ( 'data: ' ) ? line . slice ( 6 ) : line . slice ( 5 ) if ( payload === '[DONE]' ) break // business logic: handle termination yourself process . stdout . write ( JSON . parse ( payload ) . choices [ 0 ] ?. delta ?. content ?? '' ) } // loop ends = connection closed, no manual cleanup // SSE — Rust (reqwest + tokio_stream) // use catcher_http::sse::{SseStream, SseClientConfig}; // let config = SseClientConfig { url: "...".into(), method: SseMethod::POST, ..Default::default() }; // let mut stream = SseStream::connect(config).await?; // while let Some(line) = stream.next().await { // let line = line?; // if let Some(payload) = line.strip_prefix("data: ") { println!("{}", payload); } // } // SSE — long-lived push with auto-reconnect import { createSSEClient } from '@eric8810/catcher-http' const client = createSSEClient ( { url : 'https://api.example.com/events' , headers : { Authorization : 'Bearer xxx' } , reconnect : { initialDelay : 1000 , maxDelay : 30_000 } , } ) for await ( const line of client ) { if ( line . startsWith ( 'data: ' ) ) console . log ( line . slice ( 6 ) ) } // Flutter — HTTP via Rust FFI import 'package:catcher_core/catcher_core.dart' ;
void main () async {
final client = CatcherHttpClient (HttpClientConfig (
baseUrl: 'https://httpbin.org' ,
retry: RetryConfig (maxAttempts: 3 ),
dns: DnsConfig (cacheTtlSecs: 300 , staleOnError: true ),
msgpack: true ,
));
final resp = await client.get ('/get' );
print ('Status: ${resp .status }, Body: ${resp .bodyAsString }' );
// POST final created = await client.post ('/post' , body: {'key' : 'value' });
client.dispose ();
// WebSocket via Rust FFI final ws = CatcherWsClient (WsClientConfig (
urls: ['wss://echo.example.com' ],
reconnect: WsReconnectConfig (initialDelayMs: 1000 ),
dns: DnsConfig (cacheTtlSecs: 300 , staleOnError: true ),
msgpack: true ,
));
ws.events.listen ((event) {
if (event is WsMessageEvent ) print ('Received: ${event .text }' );
});
ws.sendText ('{"event":"hello"}' );
await Future .delayed (Duration (seconds: 5 ));
ws.dispose ();
}Shared HTTP Agent — TCP keep-alive, StaleAwareDnsResolver (676x cache hit speedup, stale-while-revalidate), TLS session reuse, idle socket evictionAuto-retry — exponential backoff with jitter, destroys stale keepAlive sockets on retryCircuit Breaker — trips on consecutive failures, auto-recovers, prevents retry stormsResilient WebSocket — RFC 7692 permessage-deflate, application-layer compression (gzip/zstd), exponential reconnect with message buffering, multi-endpoint racing, heartbeat with fast pong-timeout detectionServer-Sent Events (SSE) — raw line stream, auto-reconnect, Last-Event-ID resume, AbortSignal, cross-platform (Rust + TS + Browser)Binary codec — built-in msgpack: true transport-level codec (10% wire savings), standalone pack/unpack via @eric8810/catcher-napi-ws/codecDart FFI feature parity — Flutter clients can configure DNS cache and enable native MessagePack through DnsConfig, HttpClientConfig.msgpack, WsClientConfig.dns, and WsClientConfig.msgpackPriority queue — POST before prefetch, concurrency-aware schedulingDynamic interceptors — use/eject/clear at runtime, per-request retry/timeout/signal overridesinterceptors → retry → circuit breaker → concurrency queue → HTTP engine
Suite Count Status TS Unit + Integration (http, ws, sse, web) 346/348 ✅ TS E2E (scenarios + rust-vs-vanilla) 38/38 ✅ Rust Unit — catcher-core 23/23 ✅ Rust Unit — catcher-dns 12/12 ✅ Rust Unit — catcher-http 118/118 ✅ Rust Unit — catcher-ws 35/35 ✅ Rust Integration — catcher-ws (compression) 5/5 ✅ NAPI Integration (dns, http, ws, msgpack) 28/28 ✅ NAPI E2E (rust-vs-vanilla S1-S8) 37/37 ✅ NAPI Throughput Benchmark 14/14 ✅ NAPI Chaos (S9-S16 + chaos) 11/11 ✅ Rust FFI Integration (http + sse + codec) 17/17 ✅ Dart Unit Tests 20/20 ✅ Dart Integration (real FFI + httpbin.org) 8/8 ✅
Directory Content docs/arch-ts/TypeScript architecture — overview, module tree, per-module design docs/arch-rs/Rust architecture — cargo workspace, transport, FFI, resilience docs/user-manual/Platform usage guides — Node.js, Browser, Flutter docs/test/Test architecture — proxy, profiles, scenarios docs/research/Research — API gaps, platform support, strategy docs/plan/Implementation plan — phased milestones docs/issues/Bug tracker — code review findings docs/showcase.htmlPerformance showcase page
pnpm install # install all dependencies
pnpm build # build all TS packages
pnpm test # run integration tests (vitest)
pnpm typecheck # type-check all TS packages
pnpm bench # run benchmarks
pnpm test:napi # NAPI integration tests
pnpm test:napi-bench # NAPI throughput benchmark
pnpm bench:napi # NAPI micro-benchmarks (agent + codec)
pnpm test:napi-chaos # NAPI chaos + extreme scenarios# Rustcd packages && cargo build
cd packages && cargo test MIT
This project uses the following third-party libraries: