UNPKG

rate-limiter-algorithms

Version:

A rate limiting library that provides various algorithms

2 lines (1 loc) 6.17 kB
var a=class{limit;windowMs;store;constructor(t){this.limit=t.limit,this.windowMs=t.windowMs,this.store=t.store}async consume(t,i=1){let e=await this.getUpdatedValues(t);return e.points+i>this.limit?{isAllowed:!1,clientData:e}:{isAllowed:!0,clientData:await this.store.set(t,{points:e.points+i,lastWindowResetTimeMs:e.lastWindowResetTimeMs})}}getRemainingPoints(t){return t?this.limit-t.points:this.limit}getResetTime(t){return Math.floor(t?(t.lastWindowResetTimeMs+this.windowMs)/1e3:Date.now()/1e3)}async getUpdatedValues(t){let i=Date.now(),e=await this.store.get(t);return!e||i-e.lastWindowResetTimeMs>=this.windowMs?{points:0,lastWindowResetTimeMs:i}:{points:e.points,lastWindowResetTimeMs:e.lastWindowResetTimeMs}}};var u=class{limit;windowMs;store;constructor(t){this.limit=t.limit,this.windowMs=t.windowMs,this.store=t.store}async consume(t,i=1){let e=Date.now(),s=await this.getUpdatedValues(t,e);return this.getEstimatedPoints(s,e)+i>this.limit?{isAllowed:!1,clientData:s}:{isAllowed:!0,clientData:await this.store.set(t,{...s,currPoints:s.currPoints+i})}}getRemainingPoints(t){let i=Date.now();return!t||i-t.edgeTimeMs>2*this.windowMs?this.limit:i-t.edgeTimeMs>this.windowMs?this.limit-Math.floor(t.currPoints*((this.windowMs-(i-t.edgeTimeMs))/this.windowMs)):this.limit-Math.floor(this.getEstimatedPoints(t,i))}getResetTime(t){return Math.floor(t?(t.edgeTimeMs+this.windowMs)/1e3:Date.now()/1e3)}async getUpdatedValues(t,i){let e=await this.store.get(t);if(!e||i-e.edgeTimeMs>2*this.windowMs)return{prevPoints:0,currPoints:0,edgeTimeMs:i};let{currPoints:s,edgeTimeMs:n}=e;return i-n>this.windowMs?{edgeTimeMs:n+this.windowMs,prevPoints:s,currPoints:0}:e}getEstimatedPoints(t,i){return t.currPoints+t.prevPoints*((this.windowMs-(i-t.edgeTimeMs))/this.windowMs)}};var m=class{limit;windowMs;store;constructor(t){this.limit=t.limit,this.windowMs=t.windowMs,this.store=t.store}async consume(t,i=1){let e=Date.now(),s=await this.getUpdatedValues(t,e);if(s.length+i>this.limit)return{isAllowed:!1,clientData:s};for(let o=0;o<i;o++)s.push(e);return{isAllowed:!0,clientData:await this.store.set(t,s)}}getRemainingPoints(t){return t?this.limit-t.length:this.limit}getResetTime(t){return!t||!t.length?Math.floor(Date.now()/1e3):Math.floor((t.at(-1)-this.windowMs)/1e3)}async getUpdatedValues(t,i){let e=await this.store.get(t);if(!e)return[];let s=0;for(;s<e.length&&!(e[s]+this.windowMs>i);s++);return e.splice(0,s),e}};var d=class{limit;windowMs;store;constructor(t){this.limit=t.limit,this.store=t.store,this.windowMs=t.windowMs}async consume(t,i=1){let e=await this.getUpdatedValues(t);return e.points-i<0?{isAllowed:!1,clientData:e}:{isAllowed:!0,clientData:await this.store.set(t,{points:e.points-i,lastRefillTimeMs:e.lastRefillTimeMs})}}getRemainingPoints(t){if(!t)return this.limit;let i=Math.floor((Date.now()-t.lastRefillTimeMs)/this.windowMs);return Math.min(t.points+i,this.limit)}getResetTime(t){return Math.floor(t?(t.lastRefillTimeMs+this.windowMs)/1e3:Date.now()/1e3)}async getUpdatedValues(t){let i=Date.now(),e=await this.store.get(t);if(!e)return{points:this.limit,lastRefillTimeMs:i};let{points:s,lastRefillTimeMs:n}=e,o=Math.floor((i-n)/this.windowMs),g=Math.min(s+o,this.limit),h=i;return g!==this.limit&&(h=n+o*this.windowMs),{points:g,lastRefillTimeMs:h}}};var l=class{oldClients;activeClients;TTL;interval;constructor(){this.oldClients=new Map,this.activeClients=new Map,this.TTL=3e5}async get(t){let i=this.oldClients.get(t);return i?(this.oldClients.delete(t),this.activeClients.set(t,i),Promise.resolve(i)):Promise.resolve(this.activeClients.get(t))}async set(t,i){return this.activeClients.set(t,i),Promise.resolve(i)}async remove(t){return this.oldClients.delete(t),this.activeClients.delete(t),Promise.resolve()}async reset(){return this.oldClients.clear(),this.activeClients.clear(),Promise.resolve()}setTTL(t){this.TTL=t,this.interval&&clearInterval(this.interval),this.interval=setInterval(()=>{this.clearExpired()},this.TTL),this.interval.unref&&this.interval.unref()}async shutdown(){this.interval&&clearInterval(this.interval),await this.reset()}clearExpired(){this.oldClients=this.activeClients,this.activeClients=new Map}};var w=class{limit;windowMs;store;consume;getRemainingPoints;getResetTime;constructor(t){let i=this.setDefaults(t);this.validateConfig(i);let e=this.getAlgorithm(i);this.limit=e.limit,this.windowMs=e.windowMs,this.store=e.store,this.consume=(s,n=1)=>(this.checkForInvalidClientId(s),e.consume(s,n)),this.getRemainingPoints=e.getRemainingPoints.bind(e),this.getResetTime=e.getResetTime.bind(e)}getHeaders(t){let i=["X-RateLimit-Limit",this.limit.toString()],e=["X-RateLimit-Remaining",this.getRemainingPoints(t).toString()],s=["X-RateLimit-Reset",this.getResetTime(t).toString()];return[i,e,s]}setDefaults(t){let i=Object.assign({},t),e=2*i.windowMs;return i.algorithm==="token-bucket"&&(e*=i.limit),i.store=i.store||new l,i.store.setTTL(e),i}getAlgorithm(t){switch(t.algorithm){case"token-bucket":return new d(t);case"sliding-window-counter":return new u(t);case"sliding-window-logs":return new m(t);case"fixed-window-counter":return new a(t)}}validateConfig(t){if(t.limit<1||!Number.isInteger(t.limit))throw new Error("Limit must be a positive integer");if(t.windowMs<1||!Number.isInteger(t.windowMs))throw new Error("Window time must be a positive integer")}checkForInvalidClientId(t){if(t===void 0)throw new Error("An undefined 'clientId' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.")}};var p=class{prefix;rawCall;TTL;constructor(t){this.prefix=t.prefix??"rla:",this.rawCall=t.rawCall,this.TTL=3e5}async get(t){let i=await this.rawCall("GET",this.prefixedKey(t));return JSON.parse(i)??void 0}async set(t,i){return await this.rawCall("SET",this.prefixedKey(t),JSON.stringify(i),"EX",this.TTL.toString()),i}async remove(t){await this.rawCall("DEL",this.prefixedKey(t))}async reset(){await this.rawCall("EVAL","return redis.call('del', unpack(redis.call('keys', ARGV[1])))","0",this.prefix+"*")}setTTL(t){this.TTL=t}prefixedKey(t){return`${this.prefix}${t}`}async shutdown(){await this.reset()}};export{l as MemoryStore,w as RateLimiter,p as RedisStore};