Result<T,E> returns
instead of try/catch hell. Retry, circuit breaker, dedup, OpenTelemetry,
GraphQL, WebSocket, SSE — all built-in, zero extra packages.
npm install reixo
import { createClient } from 'reixo';
const api = createClient({ baseURL: 'https://api.example.com' });
// No try/catch. No .catch(). Never throws.
const result = await api.tryGet<User>('/users/42');
if (result.ok) {
console.log(result.data.name); // ✓ User — fully typed
} else {
console.error(result.error.code); // ✓ NetworkError | HttpError
}
// Functional style
const name = result.map(u => u.name).unwrapOr('Anonymous');
Every team ends up writing the same boilerplate. The same fragile error handling. The same half-baked retry logic buried in utils.
// Repeated in every project...
async function fetchUser(id: string) {
try {
const res = await fetch(`/api/users/${id}`, {
headers: { Authorization: `Bearer ${getToken()}` },
});
if (!res.ok) {
// Different error shapes in every codebase
const body = await res.json().catch(() => ({}));
throw new Error(body.message ?? `HTTP ${res.status}`);
}
return res.json() as Promise<User>;
} catch (err) {
// Network error? Timeout? 4xx? 5xx?
// No idea. Log and hope for the best.
console.error('fetchUser failed', err);
throw err;
}
}
// Typed. Never throws. Exhaustive.
const result = await api.tryGet<User>(`/users/${id}`);
if (result.ok) {
return result.data; // User — fully typed ✓
}
// result.error is NetworkError | HttpError — typed too
if (result.error.type === 'HTTP_ERROR') {
return handleStatus(result.error.status);
}
// TypeScript flags if you forget a branch
return fallback;
Every feature is built on the native Fetch API. No hidden transitive dependencies. Fully tree-shakeable.
Every request returns Ok<T> | Err<E>. TypeScript enforces exhaustive handling at compile time. No more silent failures.
Exponential backoff with jitter, per-request override, configurable status codes, and an onRetry hook. No external packages needed.
Opens after a failure threshold, half-opens to probe recovery. Protects your app from cascading failures without any extra libraries.
Identical in-flight GETs are coalesced into one network call. Ten components asking for the same data? One request. Zero config.
Automatic W3C traceparent propagation without @opentelemetry/api. Hooks for full OTLP span export.
First-class GraphQL client. WebSocket manager with auto-reconnect. Server-Sent Events with for await async iterator.
Requests made offline queue locally and auto-replay when connectivity returns. Built on the Network Information API with localStorage persistence. PWA-ready.
In-memory and localStorage adapters built in. Respects Cache-Control or set a custom TTL. Stale-while-revalidate pattern supported.
Resumable chunked uploads with onProgress callbacks. Cursor, offset, and link-header pagination helpers built-in.
Real patterns for real production problems.
import { createClient } from 'reixo';
const api = createClient({
baseURL: 'https://api.example.com',
timeout: 10_000,
headers: { 'X-API-Version': '2' },
});
// GET — typed, never throws
const result = await api.tryGet<User>('/users/1');
if (result.ok) console.log(result.data.name);
// POST
const post = await api.tryPost<Post>('/posts', {
body: { title: 'Hello', content: '...' },
});
// Full REST
await api.tryPut('/users/1', { body: { role: 'admin' } });
await api.tryPatch('/users/1', { body: { active: true } });
await api.tryDelete('/users/1');
import { createClient } from 'reixo';
const api = createClient({
baseURL: 'https://api.example.com',
retry: {
attempts: 3,
strategy: 'exponential', // 'linear' | 'exponential' | 'fixed'
baseDelay: 300, // ms
maxDelay: 5_000,
jitter: true, // prevents thundering herd
retryOn: [408, 429, 503],
onRetry: ({ attempt, error, delay }) => {
console.log(`Retry ${attempt} in ${delay}ms`);
},
},
});
// Retries up to 3x before returning Err
const result = await api.tryGet<Data>('/flaky');
// Per-request override
await api.tryGet('/health', { retry: { attempts: 1 } });
import { createClient, CircuitBreakerState } from 'reixo';
const api = createClient({
baseURL: 'https://payments.example.com',
circuitBreaker: {
threshold: 5, // open after 5 consecutive failures
resetTimeout: 30_000, // probe recovery after 30s
onStateChange: (state: CircuitBreakerState) => {
metrics.gauge('circuit_state', state); // CLOSED | OPEN | HALF_OPEN
},
},
});
const result = await api.tryPost<ChargeResult>('/charge', { body });
if (!result.ok) {
if (result.error.type === 'CIRCUIT_OPEN') {
return { status: 'degraded' }; // fast-fail gracefully
}
}
import { createGraphQLClient } from 'reixo';
const gql = createGraphQLClient({
endpoint: 'https://api.example.com/graphql',
headers: { Authorization: `Bearer ${token}` },
});
// Query
const result = await gql.query<{ user: User }>({
query: `query GetUser($id: ID!) {
user(id: $id) { id name email avatar }
}`,
variables: { id: '42' },
});
if (result.ok) console.log(result.data.user.name);
// Mutation
await gql.mutate<{ updateUser: User }>({
mutation: `mutation Update($id: ID!, $name: String!) {
updateUser(id: $id, name: $name) { id name }
}`,
variables: { id: '42', name: 'Alice' },
});
import { createClient } from 'reixo';
const api = createClient({ baseURL: 'https://api.example.com' });
// Attach JWT on every request
api.interceptors.request.use(async (config) => {
const token = await tokenStore.getAccessToken();
config.headers.Authorization = `Bearer ${token}`;
return config;
});
// Auto-refresh on 401
api.interceptors.response.use(
(res) => res,
async (error, original) => {
if (error.status === 401 && !original._retry) {
original._retry = true;
await tokenStore.refresh();
return api.request(original); // replay with new token
}
return Promise.reject(error);
},
);
import { createClient, createWebSocketManager } from 'reixo';
const api = createClient({ baseURL: 'https://api.example.com' });
// Server-Sent Events — async iterator
for await (const event of api.sse('/stream/events')) {
console.log(event.type, event.data);
if (event.type === 'done') break;
}
// WebSocket with auto-reconnect
const ws = createWebSocketManager('wss://ws.example.com/live', {
reconnect: {
maxAttempts: 10,
delay: 1_000,
backoff: 'exponential',
},
onMessage: (msg) => dispatch(parseMessage(msg)),
onReconnect: (n) => console.log(`Reconnect attempt ${n}`),
onClose: () => setStatus('disconnected'),
});
ws.send({ type: 'subscribe', channels: ['prices', 'orders'] });
Every check below would have been a separate npm package. reixo ships it all.
| Feature | reixo | axios | ky | ofetch | got |
|---|---|---|---|---|---|
| Result<T,E> returns | ✓ | ✗ | ✗ | ✗ | ✗ |
| Zero dependencies | ✓ | ✗ | ✓ | ✓ | ✗ |
| Circuit breaker | ✓ | ✗ | ✗ | ✗ | ✗ |
| Request deduplication | ✓ | ✗ | ✗ | ✗ | ✗ |
| Built-in retry | ✓ | ~ | ✓ | ✓ | ✓ |
| OpenTelemetry tracing | ✓ | ✗ | ✗ | ✗ | ✗ |
| GraphQL support | ✓ | ✗ | ✗ | ✗ | ✗ |
| WebSocket + SSE | ✓ | ✗ | ✗ | ✗ | ✗ |
| Offline queue | ✓ | ✗ | ✗ | ✗ | ✗ |
| Edge / Workers runtime | ✓ | ✗ | ✓ | ✓ | ✗ |
| Mock adapter for tests | ✓ | ✓ | ✗ | ✗ | ✗ |
~ partial/plugin · ✓ built-in · ✗ unavailable
One API, six runtimes, zero adapter switching.
npm install reixo
pnpm add reixo
yarn add reixo
bun add reixo
import { createClient } from 'reixo';
// Create once, export for reuse
const api = createClient({
baseURL: 'https://jsonplaceholder.typicode.com',
timeout: 8_000,
retry: { attempts: 2 },
});
interface Todo { id: number; title: string; completed: boolean }
// Never throws — result is Ok<Todo> | Err<NetworkError | HttpError>
const result = await api.tryGet<Todo>('/todos/1');
if (result.ok) {
console.log('Title:', result.data.title);
console.log('Done:', result.data.completed);
} else {
// TypeScript knows exactly what error type this is
console.error('Error:', result.error.message);
}
reixo handles retries, error typing, circuit breaking, and tracing so you can ship features instead of infrastructure.