A JavaScript/TypeScript port of the popular .NET Polly library, built with RxJS and compatible with AsyncLocalStorage for context preservation.
Not production-ready yet. Use at your own risk, or just grab the rxjs code and use it directly.
- Timeout Strategy
- Composition of Strategies
- AsyncLocalStorage Context Preservation
- Decorator Support
- Abort Signal Support
- Retry Strategy
- Circuit Breaker Strategy
- Telemetry Integration
...
- Timeout Strategy: Automatically timeout operations that take too long
- Retry Strategy: Retry failed operations with configurable delay and exponential backoff
- AbortController Support: Cancel operations using AbortController/AbortSignal
- AsyncLocalStorage Compatibility: Preserve context across async operations
- Fluent Builder API: Intuitive builder pattern similar to .NET Polly
- RxJS Integration: Built on top of RxJS for powerful reactive programming
- TypeScript Support: Full TypeScript support with proper type definitions
- Decorator Support: Apply resilience strategies using decorators on class methods
import {
ResiliencePipelineBuilder,
TimeoutError,
RetryExhaustedError,
AbortError,
} from "./src/index";
import { AsyncLocalStorage } from "async_hooks";
// Create a simple pipeline with timeout and retry
const pipeline = new ResiliencePipelineBuilder()
.withTimeout(2000) // 2 second timeout
.withRetry(3, 500, 1.5) // 3 retries with exponential backoff
.build();
// Execute a function that might fail
try {
const result = await pipeline.execute(async () => {
// Your potentially unreliable operation
const response = await fetch("https://api.example.com/data");
return await response.json();
});
console.log("Success:", result);
} catch (error) {
if (error instanceof TimeoutError) {
console.log("Operation timed out");
} else if (error instanceof RetryExhaustedError) {
console.log("All retry attempts failed");
}
}const timeoutPipeline = new ResiliencePipelineBuilder()
.withTimeout(1000)
.build();
try {
await timeoutPipeline.execute(async () => {
await new Promise((resolve) => setTimeout(resolve, 2000)); // Will timeout
return "Success";
});
} catch (error) {
console.log(error.message); // "Operation timed out after 1000ms"
}const retryPipeline = new ResiliencePipelineBuilder()
.withRetry(3, 100, 2) // 3 retries: 100ms, 200ms, 400ms delays
.build();
let attempts = 0;
try {
const result = await retryPipeline.execute(async () => {
attempts++;
if (attempts < 3) {
throw new Error(`Attempt ${attempts} failed`);
}
return `Success on attempt ${attempts}`;
});
console.log(result); // "Success on attempt 3"
} catch (error) {
console.log("All retries exhausted");
}const pipeline = new ResiliencePipelineBuilder()
.withTimeout(5000)
.withRetry(3)
.build();
const controller = new AbortController();
// Start operation
const operation = pipeline.execute(async () => {
const response = await fetch("https://api.example.com/data");
return response.json();
}, controller.signal);
// Cancel after 2 seconds
setTimeout(() => controller.abort(), 2000);
try {
const result = await operation;
console.log("Success:", result);
} catch (error) {
if (error instanceof AbortError) {
console.log("Operation was cancelled");
}
}Strategies are applied in the order they are added to the builder:
- Timeout → Retry: Timeout applies to each retry attempt
- Retry → Timeout: Timeout applies to the entire retry sequence
Choose the order based on your requirements:
// Timeout per retry attempt (recommended for most cases)
const pipeline1 = new ResiliencePipelineBuilder()
.withTimeout(1000) // Each attempt times out after 1s
.withRetry(3) // Up to 3 attempts
.build();
// Timeout for entire operation
const pipeline2 = new ResiliencePipelineBuilder()
.withRetry(3) // Up to 3 attempts
.withTimeout(5000) // Entire operation times out after 5s
.build();You can also use the @Resilience decorator to apply resilience strategies directly to class methods:
import { ResiliencePipelineBuilder, Resilience } from "@ismael3s/polly";
class ApiService {
@Resilience(
new ResiliencePipelineBuilder()
.withRetry(2, 50, 1) // 2 retries, 50ms delay, no exponential growth
.build()
)
async fetchUserData(userId: string): Promise<User> {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`);
}
return response.json();
}
@Resilience(
new ResiliencePipelineBuilder()
.withTimeout(1000) // 1 second timeout
.build()
)
async quickOperation(): Promise<string> {
// Fast operation that should complete within 1 second
await new Promise((resolve) => setTimeout(resolve, 100));
return "Operation completed";
}
@Resilience(
new ResiliencePipelineBuilder()
.withTimeout(500) // 500ms per-attempt timeout
.withRetry(2, 100, 1.5) // 2 retries with exponential backoff
.build()
)
async complexOperation(data: any): Promise<any> {
// Complex operation with both timeout and retry
return await this.processData(data);
}
// Works with static methods too
@Resilience(new ResiliencePipelineBuilder().withRetry(1, 100, 1).build())
static async staticOperation(): Promise<string> {
return "Static operation completed";
}
}To use decorators, make sure your tsconfig.json includes:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}This library is written in TypeScript and provides full type safety:
interface ApiResponse {
id: number;
name: string;
}
const result: ApiResponse = await pipeline.execute(
async (): Promise<ApiResponse> => {
const response = await fetch("/api/user");
return await response.json();
}
);- RxJS: For reactive programming and strategy composition
This library is inspired by the excellent Polly library for .NET, bringing similar resilience patterns to the JavaScript/TypeScript ecosystem.