japanpost-api
Version:
Japan Post Servive API client
144 lines • 5.13 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CircuitBreaker = exports.CircuitBreakerState = void 0;
exports.withRetry = withRetry;
const JapanPostAPIError_1 = require("./JapanPostAPIError");
const Logger_1 = require("./Logger");
/**
* Default retry configuration
*/
const DEFAULT_RETRY_OPTIONS = {
maxRetries: 3,
baseDelay: 1000,
backoffMultiplier: 2,
maxDelay: 30000,
jitter: 1000,
retryableErrors: [JapanPostAPIError_1.RateLimitError, JapanPostAPIError_1.ServerError, JapanPostAPIError_1.NetworkError],
};
/**
* Function that executes retry with exponential backoff
*/
async function withRetry(operation, options = {}) {
const config = { ...DEFAULT_RETRY_OPTIONS, ...options };
let lastError = null;
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
try {
return await operation();
}
catch (error) {
const errorInstance = error instanceof Error ? error : new Error(String(error));
lastError = errorInstance;
// If this is the last attempt, re-throw the error
if (attempt === config.maxRetries) {
throw errorInstance;
}
// Check if error is retryable
const isRetryable = config.retryableErrors.some((ErrorType) => errorInstance instanceof ErrorType);
if (!isRetryable) {
throw errorInstance;
}
// For rate limit errors, consider Retry-After header
let delay = calculateDelay(attempt, config);
if (errorInstance instanceof JapanPostAPIError_1.RateLimitError && errorInstance.retryAfter) {
delay = Math.max(delay, errorInstance.retryAfter * 1000);
}
(0, Logger_1.debugLog)(() => `Executing retry: ${attempt + 1}/${config.maxRetries + 1}, ` +
`Error: ${errorInstance.constructor.name}, ` +
`Wait time: ${delay}ms`);
await sleep(delay);
}
}
// Should not reach here, but for type safety
throw lastError || new Error("Unknown error during retry");
}
/**
* Calculate delay time with exponential backoff
*/
function calculateDelay(attempt, config) {
const exponentialDelay = config.baseDelay * Math.pow(config.backoffMultiplier, attempt);
const jitter = Math.random() * config.jitter;
const totalDelay = exponentialDelay + jitter;
return Math.min(totalDelay, config.maxDelay);
}
/**
* Wait for specified milliseconds
*/
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Circuit breaker state
*/
var CircuitBreakerState;
(function (CircuitBreakerState) {
CircuitBreakerState["CLOSED"] = "CLOSED";
CircuitBreakerState["OPEN"] = "OPEN";
CircuitBreakerState["HALF_OPEN"] = "HALF_OPEN";
})(CircuitBreakerState || (exports.CircuitBreakerState = CircuitBreakerState = {}));
/**
* Basic circuit breaker implementation
* Protects the system from cascading failures
*/
class CircuitBreaker {
constructor(options = {}) {
this.state = CircuitBreakerState.CLOSED;
this.failureCount = 0;
this.lastFailureTime = 0;
this.options = {
failureThreshold: 5,
resetTimeout: 60000, // 1 minute
monitoringPeriod: 60000, // 1 minute
...options,
};
}
async execute(operation) {
if (this.state === CircuitBreakerState.OPEN) {
if (this.shouldAttemptReset()) {
this.state = CircuitBreakerState.HALF_OPEN;
(0, Logger_1.debugLog)(() => "Circuit breaker: Transitioning to HALF_OPEN state");
}
else {
throw new Error("Circuit breaker is in OPEN state. Please wait and try again later.");
}
}
try {
const result = await operation();
this.onSuccess();
return result;
}
catch (error) {
this.onFailure();
throw error;
}
}
shouldAttemptReset() {
return Date.now() - this.lastFailureTime >= this.options.resetTimeout;
}
onSuccess() {
this.failureCount = 0;
this.state = CircuitBreakerState.CLOSED;
(0, Logger_1.debugLog)(() => "Circuit breaker: Transitioning to CLOSED state");
}
onFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.failureCount >= this.options.failureThreshold) {
this.state = CircuitBreakerState.OPEN;
(0, Logger_1.debugLog)(() => `Circuit breaker: Transitioning to OPEN state (failure count: ${this.failureCount})`);
}
}
getState() {
return this.state;
}
getFailureCount() {
return this.failureCount;
}
reset() {
this.state = CircuitBreakerState.CLOSED;
this.failureCount = 0;
this.lastFailureTime = 0;
(0, Logger_1.debugLog)(() => "Circuit breaker: Manual reset");
}
}
exports.CircuitBreaker = CircuitBreaker;
//# sourceMappingURL=retry.js.map