flowshield
v1.0.4
Published
Zero-dependency, TypeScript-first resilience & fault-tolerance toolkit. Composable policies: retry, circuit breaker, timeout, bulkhead, fallback, rate limiter, hedge, cache.
Maintainers
Readme
FlowShield
Zero-dependency, TypeScript-first resilience & fault-tolerance toolkit.
Composable fault-tolerance policies for any async operation. Ship resilient microservices, API clients, and distributed systems with retry, circuit breaker, timeout, bulkhead, fallback, rate limiter, hedge, and cache — all in a lightweight, tree-shakable package.
✨ Features
- 🔁 Retry — Exponential backoff, jitter, configurable strategies
- ⚡ Circuit Breaker — Half-open/open/closed with auto-recovery
- ⏱️ Timeout — AbortSignal-based deadlines
- 🚧 Bulkhead — Concurrency limiting (semaphore pattern)
- 🛡️ Fallback — Type-safe fallback chains
- 🚦 Rate Limiter — Token bucket algorithm
- 🏎️ Hedge — Hedged (parallel) requests for latency reduction
- 💾 Cache — TTL-based memoization with stale-while-revalidate
- 🔗 Composable — Chain any policies together with
pipe()orwrap() - 📦 Zero dependencies — No supply chain risk
- 🌳 Tree-shakable — Only pay for what you use
- 🌐 Edge-ready — Works in Node.js, Bun, Deno, Cloudflare Workers
- 🎯 100% test coverage — Statements, branches, functions, lines
📦 Installation
npm install flowshieldyarn add flowshieldpnpm add flowshield🚀 Quick Start
import { retry, circuitBreaker, timeout, pipe } from 'flowshield';
// Simple retry with exponential backoff
const data = await retry({ maxAttempts: 3, backoff: 'exponential' })(
() => fetch('/api/data').then(r => r.json()),
);
// Compose multiple policies
const resilientFetch = pipe(
timeout({ ms: 5000 }),
retry({ maxAttempts: 3, backoff: 'exponential' }),
);
const result = await resilientFetch(() => fetch('/api/data'));📖 API Reference
Retry
Re-executes a failed operation with configurable backoff strategies.
import { retry } from 'flowshield';
const result = await retry({
maxAttempts: 5, // Default: 3
delay: 200, // Base delay in ms. Default: 200
backoff: 'exponential', // 'constant' | 'linear' | 'exponential' | 'decorrelatedJitter'
maxDelay: 30000, // Cap delay at 30s. Default: 30000
shouldRetry: (err, attempt) => attempt < 3, // Conditional retry
onRetry: (err, attempt, delay) => console.log(`Retry ${attempt} in ${delay}ms`),
signal: controller.signal, // AbortSignal for cancellation
})(
() => fetch('/api/data'),
);Circuit Breaker
Prevents calls to a failing service, allowing it time to recover.
import { circuitBreaker } from 'flowshield';
const cb = circuitBreaker({
failureThreshold: 5, // Failures before opening. Default: 5
successThreshold: 1, // Successes in half-open to close. Default: 1
resetTimeout: 30000, // Time in open state before half-open. Default: 30000
onOpen: () => console.log('Circuit opened!'),
onClose: () => console.log('Circuit closed!'),
onHalfOpen: () => console.log('Circuit half-open...'),
});
const result = await cb.execute(() => fetch('/api/health'));
console.log(cb.handle.state); // 'closed' | 'open' | 'half-open'
cb.handle.reset(); // Manually resetTimeout
Rejects if an operation doesn't complete within a deadline.
import { timeout } from 'flowshield';
const result = await timeout({ ms: 5000 })(
() => fetch('/api/slow-endpoint'),
);
// Throws TimeoutError if not resolved within 5 secondsBulkhead
Limits concurrent executions to prevent resource exhaustion.
import { bulkhead } from 'flowshield';
const bh = bulkhead({
maxConcurrent: 10, // Max parallel executions. Default: 10
maxQueue: 100, // Max queued requests. Default: 0 (no queue)
});
const result = await bh.execute(() => fetch('/api/data'));
console.log(bh.handle.running); // Current active count
console.log(bh.handle.queued); // Current queue sizeFallback
Returns a fallback value when an operation fails.
import { fallback } from 'flowshield';
// Static fallback
const result = await fallback({ fallback: [] })(
() => fetch('/api/items').then(r => r.json()),
);
// Dynamic fallback
const result2 = await fallback({
fallback: async (error) => fetchFromCache(error),
shouldFallback: (err) => err instanceof NetworkError,
})(
() => fetch('/api/data'),
);Rate Limiter
Controls how many operations can execute within a time window.
import { rateLimiter } from 'flowshield';
const rl = rateLimiter({
tokensPerInterval: 10, // Requests per interval. Default: 10
interval: 1000, // Interval in ms. Default: 1000
rejectOnLimit: false, // Queue or reject. Default: false (queue)
});
const result = await rl.execute(() => fetch('/api/data'));
console.log(rl.handle.availableTokens); // Check remaining tokens
rl.handle.reset(); // Reset the bucketHedge
Sends a parallel request if the primary is too slow — first to resolve wins.
import { hedge } from 'flowshield';
const result = await hedge({ hedgeDelay: 2000 })(
() => fetch('/api/data').then(r => r.json()),
);
// If primary doesn't resolve in 2s, a second request is sent.
// The fastest response wins.Cache
Memoizes async operation results with TTL and stale-while-revalidate support.
import { cache } from 'flowshield';
const c = cache({
ttl: 60000, // Time-to-live in ms. Default: 60000
staleWhileRevalidate: true, // Return stale, refresh in background
keyFn: () => 'custom-key', // Custom cache key
onEvict: (key, value) => {}, // Eviction callback
});
const result = await c.execute(() => fetch('/api/data').then(r => r.json()));
c.handle.invalidate('custom-key'); // Invalidate specific key
c.handle.clear(); // Clear allComposing Policies
Combine multiple policies with pipe() (left-to-right) or wrap().
import { pipe, wrap, retry, timeout, circuitBreaker, fallback } from 'flowshield';
// pipe: policies applied left-to-right (outermost first)
const resilient = pipe(
timeout({ ms: 10000 }),
retry({ maxAttempts: 3, backoff: 'exponential' }),
);
const data = await resilient(() => fetch('/api/data'));
// wrap: explicit outer/inner nesting
const policy = wrap(
timeout({ ms: 5000 }),
retry({ maxAttempts: 3 }),
);🛡️ Error Types
FlowShield provides typed errors for each failure mode:
import {
FlowShieldError, // Base error class
TimeoutError, // Operation timed out
CircuitOpenError, // Circuit breaker is open
BulkheadRejectedError, // Bulkhead capacity exceeded
RateLimitExceededError, // Rate limit exceeded
RetryExhaustedError, // All retry attempts failed
} from 'flowshield';
try {
await resilientFetch(() => fetch('/api'));
} catch (error) {
if (error instanceof TimeoutError) {
// Handle timeout
} else if (error instanceof CircuitOpenError) {
// Handle circuit open
} else if (error instanceof RetryExhaustedError) {
console.log(`Failed after ${error.attempts} attempts`);
console.log(`Last error:`, error.cause);
}
}🔧 Requirements
- Node.js ≥ 18.0.0
- TypeScript ≥ 5.0 (for type inference)
📄 License
💖 Support FlowShield
┌─────────────────────────────────────────────────────────────┐
│ │
│ FlowShield guards your async ops — 24/7, no breaks. │
│ If it's saved you from a cascading failure at 3 AM, │
│ consider buying the developer a coffee. ☕ │
│ │
└─────────────────────────────────────────────────────────────┘Open-source resilience infrastructure. Built alone. Maintained with care.
Other ways to keep this shield standing:
- ⭐ Star flowshield — costs nothing, means everything
- 🐛 File issues or request missing fault-tolerance patterns
- 📢 Share FlowShield with your team or in your tech community
