UNPKG

@unkey/ratelimit

Version:

<div align="center"> <h1 align="center">@unkey/ratelimit</h1> <h5>@unkey/ratelimit is a library for fast global ratelimiting in serverless functions.</h5> </div>

172 lines (168 loc) 4.36 kB
// src/noop.ts var NoopRatelimit = class { limit(_identifier, _opts) { return Promise.resolve({ limit: 0, remaining: 0, reset: 0, success: true }); } }; // src/ratelimit.ts import { Unkey } from "@unkey/api"; import { UnkeyError } from "@unkey/api/models/errors/unkeyerror"; // src/duration.ts function ms(d) { if (typeof d === "number") { return d; } const match = d.match(/^(\d+)\s?(ms|s|m|h|d)$/); if (!match) { throw new Error(`Unable to parse window size: ${d}`); } const time = Number.parseInt(match[1]); const unit = match[2]; switch (unit) { case "ms": return time; case "s": return time * 1e3; case "m": return time * 1e3 * 60; case "h": return time * 1e3 * 60 * 60; case "d": return time * 1e3 * 60 * 60 * 24; default: throw new Error(`Unable to parse window size: ${d}`); } } // src/ratelimit.ts var Ratelimit = class { config; unkey; cache; constructor(config) { this.config = config; this.unkey = new Unkey({ serverURL: config.baseUrl, rootKey: config.rootKey }); this.cache = config.cache ?? /* @__PURE__ */ new Map(); } /** * Limit a specific identifier, you can override a lot of things about this specific request using * the 2nd argument. * * @example * ```ts * const identifier = getIpAddress() // or userId or anything else * const res = await unkey.limit(identifier) * * if (!res.success){ * // reject request * } * // handle request * ``` */ async limit(identifier, opts) { try { return await this._limit( identifier, opts?.limit?.limit ?? this.config.limit, ms(opts?.limit?.duration ?? this.config.duration), opts?.cost ?? 1 ); } catch (e) { if (typeof this.config.onError !== "function") { throw e; } const err = e instanceof UnkeyError ? new Error(e.message) : e instanceof Error ? e : new Error(String(e)); return await this.config.onError(err, identifier); } } // _limit just handles the racing and caching. It must not handle errors, those are handled by limit async _limit(identifier, limit, duration, cost) { const cacheKey = `${this.config.namespace}:${identifier}:${limit}:${duration}`; const naughty = this.cache.get(cacheKey); if (naughty) { if (naughty.reset > Date.now()) { return naughty; } else { this.cache.delete(cacheKey); } } const timeout = this.config.timeout === false ? null : this.config.timeout ?? { ms: 5e3, fallback: () => ({ success: false, limit: 0, remaining: 0, reset: Date.now() }) }; let timeoutId = null; try { const ps = [ this.unkey.ratelimit.limit({ namespace: this.config.namespace, identifier, limit, duration, cost }).then(async (res2) => { return res2.data; }) ]; if (timeout) { ps.push( new Promise((resolve) => { timeoutId = setTimeout(async () => { const resolvedValue = typeof timeout.fallback === "function" ? await timeout.fallback(identifier) : timeout.fallback; resolve(resolvedValue); }, ms(timeout.ms)); }) ); } const res = await Promise.race(ps); if (!res.success) { this.cache.set(cacheKey, res); } return res; } finally { if (timeoutId) { clearTimeout(timeoutId); } } } }; // src/overrides.ts import { Unkey as Unkey2 } from "@unkey/api"; var Overrides = class { unkey; constructor(config) { this.unkey = new Unkey2({ serverURL: config.baseUrl, rootKey: config.rootKey }); } getOverride(request, options) { return this.unkey.ratelimit.getOverride(request, options); } setOverride(request, options) { return this.unkey.ratelimit.setOverride(request, options); } deleteOverride(request, options) { return this.unkey.ratelimit.deleteOverride(request, options); } listOverrides(request, options) { return this.unkey.ratelimit.listOverrides(request, options); } }; export { NoopRatelimit, Overrides, Ratelimit }; //# sourceMappingURL=index.mjs.map