Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | 1x 2x 1x 1x 1x 18x 18x 5x 5x 5x 5x 17x 9x 9x 3x 3x 5x 5x 17x 4x 17x 9x 9x 3x 4x 4x 4x 5x 2x 3x 5x 3x 1x 1x 1x 5x 1x 1x 1x 1x 2x 2x | /*
Copyright (c) 2025 Bernier LLC
This file is licensed to the client under a limited-use license.
The client may use and modify this code *only within the scope of the project it was delivered for*.
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
*/
import { RetryPolicyOptions, RetryPolicyResult, JitterConfig, BackoffConfig } from './types';
/**
* Default retry policy options
*/
export const DEFAULT_RETRY_OPTIONS: Required<RetryPolicyOptions> = {
maxRetries: 5,
initialDelayMs: 1000,
maxDelayMs: 30000,
backoffFactor: 2,
jitter: true,
shouldRetry: () => true,
onRetry: () => {},
onFailure: () => {}
};
/**
* Default jitter configuration
*/
export const DEFAULT_JITTER_CONFIG: JitterConfig = {
type: 'full',
factor: 0.1
};
/**
* Default backoff configuration
*/
export const DEFAULT_BACKOFF_CONFIG: BackoffConfig = {
type: 'exponential',
baseDelay: 1000,
maxDelay: 30000,
factor: 2,
jitter: DEFAULT_JITTER_CONFIG
};
/**
* Core retry policy class that provides atomic retry utilities
*/
export class RetryPolicy {
private options: Required<RetryPolicyOptions>;
private backoffConfig: BackoffConfig;
constructor(options?: Partial<RetryPolicyOptions>, backoffConfig?: Partial<BackoffConfig>) {
this.options = { ...DEFAULT_RETRY_OPTIONS, ...options };
this.backoffConfig = { ...DEFAULT_BACKOFF_CONFIG, ...backoffConfig };
}
/**
* Evaluate whether an operation should be retried based on current attempt and error
*/
evaluateRetry(attempt: number, error: any): RetryPolicyResult {
const shouldRetry = this.shouldRetry(attempt, error);
const delay = shouldRetry ? this.calculateDelay(attempt) : 0;
const isFinalAttempt = attempt >= this.options.maxRetries;
return {
shouldRetry,
delay,
attempt,
isFinalAttempt
};
}
/**
* Calculate the delay for the next retry attempt
*/
calculateDelay(attempt: number): number {
let delay: number;
switch (this.backoffConfig.type) {
case 'exponential':
delay = this.calculateExponentialDelay(attempt);
break;
case 'linear':
delay = this.calculateLinearDelay(attempt);
break;
case 'constant':
delay = this.backoffConfig.baseDelay;
break;
default:
delay = this.calculateExponentialDelay(attempt);
}
// Apply jitter if enabled
if (this.options.jitter && this.backoffConfig.jitter) {
delay = this.applyJitter(delay, this.backoffConfig.jitter);
}
// Ensure delay doesn't exceed maximum
return Math.min(delay, this.backoffConfig.maxDelay);
}
/**
* Calculate exponential backoff delay
*/
private calculateExponentialDelay(attempt: number): number {
const factor = this.backoffConfig.factor || 2;
return this.backoffConfig.baseDelay * Math.pow(factor, attempt);
}
/**
* Calculate linear backoff delay
*/
private calculateLinearDelay(attempt: number): number {
return this.backoffConfig.baseDelay * (attempt + 1);
}
/**
* Apply jitter to a delay value
*/
private applyJitter(delay: number, jitterConfig: JitterConfig): number {
const random = Math.random();
switch (jitterConfig.type) {
case 'full':
// Full jitter: random value between 0 and delay
return delay * random;
case 'equal':
// Equal jitter: random value between delay/2 and delay
return delay * (0.5 + random * 0.5);
case 'decorrelated':
// Decorrelated jitter: random value between delay and delay * 3
return delay * (1 + random * 2);
case 'none':
default:
return delay;
}
}
/**
* Determine if an operation should be retried based on attempt count and error
*/
private shouldRetry(attempt: number, error: any): boolean {
// Don't retry if we've exceeded max attempts
if (attempt >= this.options.maxRetries) {
return false;
}
// Use custom retry condition if provided
return this.options.shouldRetry(error);
}
/**
* Get the current retry policy options
*/
getOptions(): Required<RetryPolicyOptions> {
return { ...this.options };
}
/**
* Get the current backoff configuration
*/
getBackoffConfig(): BackoffConfig {
return { ...this.backoffConfig };
}
/**
* Update retry policy options
*/
updateOptions(options: Partial<RetryPolicyOptions>): void {
this.options = { ...this.options, ...options };
}
/**
* Update backoff configuration
*/
updateBackoffConfig(config: Partial<BackoffConfig>): void {
this.backoffConfig = { ...this.backoffConfig, ...config };
}
}
/**
* Factory function to create a retry policy with default options
*/
export function createRetryPolicy(
options?: Partial<RetryPolicyOptions>,
backoffConfig?: Partial<BackoffConfig>
): RetryPolicy {
return new RetryPolicy(options, backoffConfig);
}
/**
* Utility function to calculate delay for a specific attempt
*/
export function calculateRetryDelay(
attempt: number,
options: Partial<RetryPolicyOptions> = {},
backoffConfig: Partial<BackoffConfig> = {}
): number {
const policy = createRetryPolicy(options, backoffConfig);
return policy.calculateDelay(attempt);
}
/**
* Utility function to determine if an error should trigger a retry
*/
export function shouldRetry(
attempt: number,
error: any,
options: Partial<RetryPolicyOptions> = {}
): boolean {
const policy = createRetryPolicy(options);
return policy.evaluateRetry(attempt, error).shouldRetry;
} |