UNPKG

@goatlab/typesense

Version:

Modern TypeScript wrapper for Typesense search engine API

136 lines 4.83 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ResiliencePolicy = void 0; class ResiliencePolicy { options; failures = 0; circuitOpenUntil = 0; rateLimitInfo = null; retryAfterUntil = 0; constructor(options = {}) { this.options = options; this.options = { maxFailures: 5, resetTimeout: 60000, // 1 minute retryDelay: 1000, maxRetries: 3, enabled: true, // Enabled by default ...options, }; } isCircuitOpen() { // If circuit breaker is disabled, always return false if (!this.options.enabled) { return false; } if (this.circuitOpenUntil > Date.now()) { return true; } // Reset circuit if timeout has passed if (this.circuitOpenUntil > 0 && this.circuitOpenUntil <= Date.now()) { this.reset(); } return false; } shouldRetry(retryCount, error) { // Don't retry if circuit is open if (this.isCircuitOpen()) { return false; } // Don't retry if we're in a retry-after period if (this.retryAfterUntil > Date.now()) { return false; } // Don't retry beyond max retries if (retryCount >= (this.options.maxRetries || 3)) { return false; } // Don't retry on 4xx errors (except 429) if (error.status >= 400 && error.status < 500 && error.status !== 429) { return false; } return true; } getRetryDelay(retryCount) { // Exponential backoff with jitter const baseDelay = this.options.retryDelay || 1000; const exponentialDelay = baseDelay * 2 ** retryCount; const jitter = Math.random() * 0.1 * exponentialDelay; return exponentialDelay + jitter; } recordSuccess() { const wasOpen = this.circuitOpenUntil > 0; this.failures = 0; this.circuitOpenUntil = 0; if (wasOpen && this.options.onStateChange) { this.options.onStateChange('closed', { previousFailures: this.failures }); } } recordFailure() { this.failures++; if (this.failures >= (this.options.maxFailures || 5)) { const wasOpen = this.circuitOpenUntil > Date.now(); this.circuitOpenUntil = Date.now() + (this.options.resetTimeout || 60000); if (!wasOpen && this.options.onStateChange) { this.options.onStateChange('open', { failures: this.failures, openUntil: new Date(this.circuitOpenUntil), resetTimeout: this.options.resetTimeout || 60000, }); } } } updateRateLimit(headers) { const limit = headers.get('X-RateLimit-Limit'); const remaining = headers.get('X-RateLimit-Remaining'); const resetMs = headers.get('X-RateLimit-ResetMs'); // Typesense uses milliseconds const retryAfter = headers.get('Retry-After'); this.rateLimitInfo = { limit: limit ? Number.parseInt(limit, 10) : undefined, remaining: remaining ? Number.parseInt(remaining, 10) : undefined, resetMs: resetMs ? Number.parseInt(resetMs, 10) : undefined, retryAfter: retryAfter ? Number.parseInt(retryAfter, 10) : undefined, }; // Set retry-after period if present if (retryAfter) { this.retryAfterUntil = Date.now() + Number.parseInt(retryAfter, 10) * 1000; } // Notify about rate limit updates if (this.rateLimitInfo && this.options.onRateLimitUpdate) { this.options.onRateLimitUpdate(this.rateLimitInfo); } } getRateLimit() { return this.rateLimitInfo; } getTimeUntilReset() { if (!this.rateLimitInfo?.resetMs) { return undefined; } const timeUntilReset = this.rateLimitInfo.resetMs - Date.now(); return Math.max(0, timeUntilReset); } isRateLimited() { if (!this.rateLimitInfo) { return false; } return (this.rateLimitInfo.remaining === 0 || this.retryAfterUntil > Date.now()); } reset() { this.failures = 0; this.circuitOpenUntil = 0; this.retryAfterUntil = 0; } getStatus() { return { failures: this.failures, circuitOpen: this.isCircuitOpen(), circuitOpenUntil: this.circuitOpenUntil, rateLimited: this.isRateLimited(), retryAfterUntil: this.retryAfterUntil, rateLimit: this.rateLimitInfo, }; } } exports.ResiliencePolicy = ResiliencePolicy; //# sourceMappingURL=resilience-policy.js.map