@dbs-portal/core-api
Version:
HTTP client and API utilities for DBS Portal
166 lines • 4.56 kB
JavaScript
/**
* Rate limiter for API requests
*/
/**
* Token bucket rate limiter
*/
export class RateLimiter {
tokens;
lastRefill;
config;
constructor(config) {
this.config = {
maxRequests: config.maxRequests,
perMilliseconds: config.perMilliseconds,
maxRPS: config.maxRPS || Math.ceil(config.maxRequests / (config.perMilliseconds / 1000)),
};
this.tokens = this.config.maxRequests;
this.lastRefill = Date.now();
}
/**
* Acquires a token (waits if necessary)
*/
async acquire() {
this.refillTokens();
if (this.tokens >= 1) {
this.tokens--;
return;
}
// Wait for next token
const waitTime = this.calculateWaitTime();
if (waitTime > 0) {
await this.delay(waitTime);
return this.acquire();
}
}
/**
* Tries to acquire a token without waiting
*/
tryAcquire() {
this.refillTokens();
if (this.tokens >= 1) {
this.tokens--;
return true;
}
return false;
}
/**
* Gets the current number of available tokens
*/
getAvailableTokens() {
this.refillTokens();
return Math.floor(this.tokens);
}
/**
* Gets the time until next token is available
*/
getTimeUntilNextToken() {
this.refillTokens();
if (this.tokens >= 1) {
return 0;
}
return this.calculateWaitTime();
}
/**
* Updates rate limit configuration
*/
updateConfig(config) {
this.config = {
...this.config,
...config,
maxRPS: config.maxRPS ||
Math.ceil((config.maxRequests || this.config.maxRequests) /
((config.perMilliseconds || this.config.perMilliseconds) / 1000)),
};
// Reset tokens to new maximum
this.tokens = Math.min(this.tokens, this.config.maxRequests);
}
/**
* Gets current configuration
*/
getConfig() {
return { ...this.config };
}
/**
* Resets the rate limiter
*/
reset() {
this.tokens = this.config.maxRequests;
this.lastRefill = Date.now();
}
/**
* Gets rate limiter statistics
*/
getStats() {
return {
availableTokens: this.getAvailableTokens(),
maxTokens: this.config.maxRequests,
refillRate: this.config.maxRequests / this.config.perMilliseconds,
timeUntilNextToken: this.getTimeUntilNextToken(),
};
}
/**
* Refills tokens based on elapsed time
*/
refillTokens() {
const now = Date.now();
const elapsed = now - this.lastRefill;
if (elapsed > 0) {
const tokensToAdd = (elapsed / this.config.perMilliseconds) * this.config.maxRequests;
this.tokens = Math.min(this.config.maxRequests, this.tokens + tokensToAdd);
this.lastRefill = now;
}
}
/**
* Calculates wait time for next token
*/
calculateWaitTime() {
const tokensNeeded = 1 - this.tokens;
const timePerToken = this.config.perMilliseconds / this.config.maxRequests;
return Math.ceil(tokensNeeded * timePerToken);
}
/**
* Creates a delay promise
*/
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Creates a rate limiter for requests per second
*/
static perSecond(requestsPerSecond) {
return new RateLimiter({
maxRequests: requestsPerSecond,
perMilliseconds: 1000,
});
}
/**
* Creates a rate limiter for requests per minute
*/
static perMinute(requestsPerMinute) {
return new RateLimiter({
maxRequests: requestsPerMinute,
perMilliseconds: 60000,
});
}
/**
* Creates a rate limiter for requests per hour
*/
static perHour(requestsPerHour) {
return new RateLimiter({
maxRequests: requestsPerHour,
perMilliseconds: 3600000,
});
}
/**
* Creates a burst rate limiter (allows bursts up to maxBurst, then limits to sustainedRate)
*/
static burst(maxBurst, sustainedRate, perMilliseconds = 1000) {
return new RateLimiter({
maxRequests: maxBurst,
perMilliseconds,
maxRPS: sustainedRate,
});
}
}
//# sourceMappingURL=rate-limiter.js.map