@ismael3s/polly
v0.0.2
Published
A TypeScript library for building resilience pipelines with strategies like retries, timeouts, and circuit breakers, inspired by .NET Polly.
Maintainers
Readme
@ismael3s/polly - JavaScript/Typescript Resilience Library
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.
Status
- [x] Retry Strategy
- [x] Timeout Strategy
- [x] Composition of Strategies
- [x] AsyncLocalStorage Context Preservation
- [x] Decorator Support
- [ ] Circuit Breaker Strategy
- [ ] Telemetry Integration
...
Features
- Timeout Strategy: Automatically timeout operations that take too long
- Retry Strategy: Retry failed operations with configurable delay and exponential backoff
- 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
Quick Start
import {
ResiliencePipelineBuilder,
TimeoutError,
RetryExhaustedError,
} 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");
}
}Examples
Basic Timeout
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"
}Retry with Exponential Backoff
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");
}Strategy Composition
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();Decorator Usage
You can also use the @Resilience decorator to apply resilience strategies directly to class methods:
Basic Decorator Usage
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";
}
}TypeScript Configuration for Decorators
To use decorators, make sure your tsconfig.json includes:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}TypeScript Support
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();
}
);Dependencies
- RxJS: For reactive programming and strategy composition
Inspiration
This library is inspired by the excellent Polly library for .NET, bringing similar resilience patterns to the JavaScript/TypeScript ecosystem.
