retry-budget-propagation
v1.0.0
Published
Retry Budget Propagation (RBPP) is a lightweight protocol and Node.js library designed to prevent retry amplification across distributed microservices.
Downloads
10
Maintainers
Readme
Retry Budget Propagation (RBPP)
A lightweight TypeScript library implementing the Retry Budget Propagation protocol to prevent retry amplification across distributed microservices.
Overview
RBPP helps prevent retry storms in distributed systems by propagating a retry budget through service calls. Each service decrements the budget when retrying, and when the budget is exhausted, retries are blocked.
Installation
npm install retry-budget-propagationUsage
Axios Integration
Basic Usage
import axios from 'axios'
import { attachRBPPInterceptor } from 'retry-budget-propagation'
const axiosInstance = axios.create()
attachRBPPInterceptor(axiosInstance)
// Use axiosInstance for requests
// The interceptor automatically:
// - Initializes budget if missing (default: 5)
// - Decrements budget on retryable errors (5xx)
// - Blocks retries when budget is exhaustedAdvanced Configuration
import axios from 'axios'
import {
attachRBPPInterceptor,
createStatusCodePredicate,
createConsoleLogger,
} from 'retry-budget-propagation'
const axiosInstance = axios.create()
attachRBPPInterceptor(axiosInstance, {
defaultBudget: 10, // Custom default budget
isRetryableError: createStatusCodePredicate([500, 502, 503]), // Custom retry logic
logger: createConsoleLogger('debug'), // Enable logging
enableBudgetRecovery: true, // Recover budget on success
recoveryAmount: 1, // Recover 1 per success
maxBudget: 20, // Cap budget at 20
events: {
onBudgetExhausted: error => {
// Send alert when budget exhausted
alertingService.send('Budget exhausted!')
},
onBudgetDecremented: (old, newBudget) => {
metrics.record('rbpp.budget.decremented', { old, new: newBudget })
},
},
})Express Integration
Basic Usage
import express from 'express'
import { rbppMiddleware } from 'retry-budget-propagation'
const app = express()
// Add RBPP middleware
app.use(rbppMiddleware())
// Access budget in route handlers
app.get('/api/data', (req, res) => {
console.log('Retry budget:', req.retryBudget)
res.json({ data: 'example' })
})With Error Handling
import express from 'express'
import { rbppMiddleware, rbppErrorHandler } from 'retry-budget-propagation'
const app = express()
// Parse and propagate budget
app.use(
rbppMiddleware({
defaultBudget: 10,
autoDecrementOnError: true, // Auto-decrement on 5xx errors
errorStatusCodes: [500, 502, 503], // Custom error codes
})
)
// Decrement budget on errors
app.use(rbppErrorHandler())
// Your routes
app.get('/api/data', (req, res) => {
// Access req.retryBudget
res.json({ data: 'example' })
})Core Utilities
import {
parseBudget,
decrementBudget,
incrementBudget,
recoverBudget,
isRetryableError,
RETRY_BUDGET,
HEADER_NAME,
} from 'retry-budget-propagation'
// Parse budget from header string
const budget = parseBudget('3', RETRY_BUDGET) // Returns 3 or default if invalid
// Decrement budget
const newBudget = decrementBudget(5) // Returns 4
// Increment budget (with optional cap)
const increased = incrementBudget(3, 2, 10) // Returns 5 (capped at 10)
// Recover budget on success
const recovered = recoverBudget(2, { recoveryAmount: 1, maxBudget: 10 }) // Returns 3
// Check if error is retryable (5xx status codes)
if (isRetryableError(axiosError)) {
// Handle retry
}Custom Retry Logic
import {
createStatusCodePredicate,
createStatusCodeRangePredicate,
combinePredicates,
combinePredicatesOr,
} from 'retry-budget-propagation'
// Retry only on specific status codes
const customPredicate = createStatusCodePredicate([500, 502, 503])
// Retry on status code range
const rangePredicate = createStatusCodeRangePredicate(500, 600)
// Combine predicates (AND logic)
const combined = combinePredicates(
createStatusCodeRangePredicate(500, 600),
error => error.code !== 'ECONNREFUSED'
)
// Combine predicates (OR logic)
const orCombined = combinePredicatesOr(
createStatusCodePredicate([429, 503]),
error => !error.response // Network errors
)API Reference
attachRBPPInterceptor(axiosInstance: AxiosInstance, options?: RBPPConfig): void
Attaches RBPP interceptors to an Axios instance. Automatically handles budget initialization, decrementing, and retry blocking.
Options:
defaultBudget?: number- Default budget when not present (default: 5)isRetryableError?: RetryableErrorPredicate- Custom retry logiclogger?: RBPPLogger- Logger instance for eventsevents?: RBPPEvents- Event handlers for budget lifecycleenableBudgetRecovery?: boolean- Recover budget on success (default: false)recoveryAmount?: number- Amount to recover per success (default: 1)maxBudget?: number- Maximum budget cap (default: Infinity)
rbppMiddleware(options?: RBPPExpressOptions): Middleware
Express middleware factory that:
- Parses retry budget from incoming request headers
- Sets
req.retryBudgetfor use in route handlers - Propagates budget in response headers
- Optionally auto-decrements on error status codes
Options: All RBPPConfig options plus:
autoDecrementOnError?: boolean- Auto-decrement on errors (default: true)errorStatusCodes?: number[]- Status codes that trigger decrement (default: [500, 502, 503, 504])
rbppErrorHandler(options?: RBPPConfig): ErrorHandler
Express error middleware that decrements budget on errors. Use after rbppMiddleware.
parseBudget(value: string, defaultBudget: number): number
Parses a budget value from a string header. Returns defaultBudget if value is invalid or missing.
decrementBudget(budget: number): number
Decrements the budget by 1, ensuring it never goes below 0.
incrementBudget(budget: number, amount: number, maxBudget?: number): number
Increments the budget by amount, capped at maxBudget.
recoverBudget(budget: number, config: { recoveryAmount: number, maxBudget?: number }): number
Recovers budget based on configuration. Used internally for budget recovery feature.
isRetryableError(error: AxiosError): boolean
Determines if an error is retryable. Returns true for:
- Network errors (no response)
- 5xx server errors
Utility Functions
createStatusCodePredicate(statusCodes: number[]): RetryableErrorPredicate- Create predicate for specific status codescreateStatusCodeRangePredicate(min: number, max: number): RetryableErrorPredicate- Create predicate for status code rangecombinePredicates(...predicates): RetryableErrorPredicate- Combine with AND logiccombinePredicatesOr(...predicates): RetryableErrorPredicate- Combine with OR logicnegatePredicate(predicate): RetryableErrorPredicate- Negate a predicatecreateConsoleLogger(level?: 'debug' | 'info' | 'warn' | 'error'): RBPPLogger- Create console logger
Constants
RETRY_BUDGET: Default retry budget (5)HEADER_NAME: Header name for budget propagation ('x-retry-budget')
How It Works
- Request: Each outgoing request includes an
x-retry-budgetheader - Initialization: If no budget is present, it defaults to 5
- Propagation: Services forward the budget header to downstream services
- Decrementing: On retryable errors, the budget is decremented
- Blocking: When budget reaches 0, retries are blocked
TypeScript Support
Full TypeScript support with exported types:
import type {
AxiosError,
AxiosInstance,
InternalAxiosRequestConfig,
} from 'retry-budget-propagation'License
ISC
