@goatlab/typesense
Version:
Modern TypeScript wrapper for Typesense search engine API
136 lines • 4.83 kB
JavaScript
"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