safe-calls
Version:
A simple rate limit and retry manager
74 lines (72 loc) • 2.06 kB
JavaScript
// src/RateLimitManager.ts
import pLimit from "p-limit";
import pRetry from "p-retry";
import pThrottle from "p-throttle";
var RateLimitManager = class {
limiters;
throttlers;
configs;
constructor(options = {}) {
this.limiters = /* @__PURE__ */ new Map();
this.configs = /* @__PURE__ */ new Map();
this.throttlers = /* @__PURE__ */ new Map();
for (const [service, config] of Object.entries(options)) {
this.#addService(service, config);
}
}
/**
* Wraps an async function with the rate limit and retry logic for the specified service.
*/
wrap(service, fn) {
const limiter = this.limiters.get(service);
const throttler = this.throttlers.get(service);
const config = this.configs.get(service);
if (!limiter || !throttler || !config) {
throw new Error(`No rate limit configured for service: ${service}`);
}
return (...args) => limiter(
async () => await throttler(
() => pRetry(() => fn(...args), {
retries: config.retries ?? 1,
onFailedAttempt: (error) => {
console.warn(
`[${service}] Retry attempt ${error.attemptNumber} failed. ${error.message}`
);
}
})
)()
);
}
/**
* Updates the configuration for an existing service.
*/
updateService(service, config) {
if (!this.limiters.has(service)) {
throw new Error(`No rate limit configured for service: ${service}`);
}
this.#addService(service, config);
}
/**
* Returns the number of pending tasks for a service.
*/
getPendingCount(service) {
return this.limiters.get(service)?.pendingCount ?? 0;
}
/**
* Adds a new service with the given configuration.
*/
#addService(service, config) {
this.throttlers.set(
service,
pThrottle({
limit: config.requestsPerInterval,
interval: config.intervalMs
})
);
this.limiters.set(service, pLimit(config.concurrency));
this.configs.set(service, config);
}
};
export {
RateLimitManager
};