express-adaptive-rate-limiter 1.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +434 -17
  2. package/dist/algorithms/baseAlgorithm.js +1 -1
  3. package/dist/algorithms/fixedWindow.js +1 -1
  4. package/dist/algorithms/leakyBucket.js +1 -1
  5. package/dist/algorithms/slidingWindowCounter.js +1 -1
  6. package/dist/algorithms/slidingWindowLog.js +1 -1
  7. package/dist/algorithms/tokenBucket.js +1 -1
  8. package/dist/core/blockedKeys.js +1 -0
  9. package/dist/core/coreMiddleware.js +1 -0
  10. package/dist/core/limiters/clusterLimiter.js +1 -0
  11. package/dist/core/limiters/dynamoDbLimiter.js +1 -0
  12. package/dist/core/limiters/etcdLimiter.js +1 -0
  13. package/dist/core/limiters/memcachedLimiter.js +1 -0
  14. package/dist/core/limiters/memoryLimiter.js +1 -0
  15. package/dist/core/limiters/mongoLimiter.js +1 -0
  16. package/dist/core/limiters/mongooseLimiter.js +1 -0
  17. package/dist/core/limiters/mySqlLimiter.js +12 -0
  18. package/dist/core/limiters/nodeClusterLimiter.js +1 -0
  19. package/dist/core/limiters/postgresLimiter.js +13 -0
  20. package/dist/core/limiters/prismaLimiter.js +24 -0
  21. package/dist/core/limiters/redisLimiter.js +16 -0
  22. package/dist/core/limiters/sqliteLimiter.js +13 -0
  23. package/dist/core/limiters/valkeyLimiter.js +16 -0
  24. package/dist/core/rateLimiter.js +1 -0
  25. package/dist/core/rateLimiterRes.js +1 -0
  26. package/dist/core/rateLimiterStore.js +1 -0
  27. package/dist/core/wrappers/blackAndWhiteLimiter.js +1 -0
  28. package/dist/core/wrappers/burstyRateLimiter.js +1 -0
  29. package/dist/core/wrappers/rateLimiterQueue.js +1 -0
  30. package/dist/core/wrappers/rateLimiterUnion.js +1 -0
  31. package/dist/core/wrappers/timeoutLimiter.js +1 -0
  32. package/dist/index.js +1 -1
  33. package/dist/middleware/expressMiddleware.js +1 -1
  34. package/dist/middleware/headers.js +1 -1
  35. package/dist/rateLimit.js +1 -1
  36. package/dist/rateLimitManager.js +1 -1
  37. package/dist/stores/baseStore.js +1 -1
  38. package/dist/stores/luaScripts.js +379 -1
  39. package/dist/stores/memoryStore.js +1 -1
  40. package/dist/stores/redisStore.js +1 -1
  41. package/dist/types/index.d.ts +525 -1
  42. package/dist/utils/errors.js +1 -1
  43. package/dist/utils/graphQLParser.js +1 -1
  44. package/dist/utils/ipUtils.js +1 -1
  45. package/dist/utils/keyGenerator.js +1 -1
  46. package/dist/utils/redisClient.js +1 -0
  47. package/dist/utils/retryAfter.js +1 -1
  48. package/dist/utils/skipUtils.js +1 -1
  49. package/dist/utils/time.js +1 -1
  50. package/dist/utils/validation.js +1 -1
  51. package/package.json +72 -11
@@ -1 +1 @@
1
- const _0x54218a=_0x5d52;(function(_0x9abf89,_0x131a03){const _0x2dc514=_0x5d52,_0x219d29=_0x9abf89();while(!![]){try{const _0x56a9fe=-parseInt(_0x2dc514(117))/1+-parseInt(_0x2dc514(132))/2*(parseInt(_0x2dc514(122))/3)+-parseInt(_0x2dc514(130))/4+-parseInt(_0x2dc514(111))/5+-parseInt(_0x2dc514(136))/6*(parseInt(_0x2dc514(110))/7)+-parseInt(_0x2dc514(129))/8*(parseInt(_0x2dc514(112))/9)+parseInt(_0x2dc514(116))/10*(parseInt(_0x2dc514(126))/11);if(_0x56a9fe===_0x131a03)break;else _0x219d29["push"](_0x219d29["shift"]())}catch(_0x13eab6){_0x219d29["push"](_0x219d29["shift"]())}}})(_0x25aa,455024);function _0x25aa(){const _0xf06f78=["CNvUu2nYAxb0","D2L0AeXVy2S","zMXVB3i","Bwf4","C3rVCMu","C2XPzgLUzY13Aw5KB3CTy291BNrLCG","nteXndGZEvDss1Hr","ndeWmJa1mhPRENn0ra","ntrTs2TQvxi","BM9YBwfSAxPLuMvZDwX0","y2vPBa","BM93","nte3menxAMz6uG","nJC1ndC0D2Ljrwnx","D2LUzg93txm","zNvUy3rPB24","z2v0u3rHDgu","BgLTAxq","mJuYmZCYnMTcsLPgtq","CM91BMq","y29UC3vTzq","y3vYCKnVDw50","otKZmdH5wufpzNa","AxnszwrPCW","C2v0","mZaXndmYuvb0A3zw","mZa4oty5nLvTwhHOAW","ChjLDKnVDw50","mMjPC05tCq","z2v0tM93","z2v0","D2LUzg93u3rHCNq","nZjZEwjxC2W"];_0x25aa=function(){return _0xf06f78};return _0x25aa()}import{BaseAlgorithm}from"./baseAlgorithm.js";export class SlidingWindowCounter extends BaseAlgorithm{constructor(_0x134889,_0x323e47){const _0x37330e=_0x5d52;super(_0x134889,_0x323e47),this[_0x37330e(121)]=_0x134889[_0x37330e(121)],this[_0x37330e(118)]=_0x134889[_0x37330e(118)]}async[_0x54218a(124)](_0x3f471b,_0x2fa82c=1){const _0x4b6bb6=_0x54218a;if(this[_0x4b6bb6(127)]()){const _0x108161=typeof this[_0x4b6bb6(141)][_0x4b6bb6(133)]==="function"?this[_0x4b6bb6(141)]["getNow"]():0,_0x148d17=await this["store"][_0x4b6bb6(137)](_0x4b6bb6(109),[_0x3f471b],[this["limit"],this[_0x4b6bb6(118)],_0x2fa82c,_0x108161]);return this[_0x4b6bb6(113)](_0x148d17[0]===1,_0x148d17[1],_0x148d17[2],_0x148d17[3],_0x148d17[4],_0x148d17[5])}return this[_0x4b6bb6(138)](_0x3f471b,async()=>{const _0x565e27=_0x4b6bb6,_0x13f1ff=Date[_0x565e27(115)](),_0x3a46f2=Math[_0x565e27(139)](_0x13f1ff/this[_0x565e27(118)])*this[_0x565e27(118)],_0x12a23e=_0x3a46f2+this["windowMs"],_0x48d89e=await this["store"][_0x565e27(134)](_0x3f471b);let _0x2e4f90=0,_0x43f74a=0;if(_0x48d89e){if(_0x48d89e[_0x565e27(135)]===_0x3a46f2)_0x2e4f90=_0x48d89e["prevCount"]||0,_0x43f74a=_0x48d89e["currCount"]||0;else _0x3a46f2===_0x48d89e[_0x565e27(135)]+this[_0x565e27(118)]&&(_0x2e4f90=_0x48d89e["currCount"]||0,_0x43f74a=0)}const _0x5dc491=_0x13f1ff-_0x3a46f2,_0x557dd3=Math[_0x565e27(140)](0,1-_0x5dc491/this["windowMs"]),_0x472ab2=_0x2e4f90*_0x557dd3+_0x43f74a;if(_0x472ab2+_0x2fa82c<=this["limit"]){_0x43f74a+=_0x2fa82c;const _0x4194f0=Math["round"](_0x472ab2+_0x2fa82c),_0x3d88fb=Math[_0x565e27(140)](0,this[_0x565e27(121)]-_0x4194f0);return await this[_0x565e27(141)]["set"](_0x3f471b,{"windowStart":_0x3a46f2,"prevCount":_0x2e4f90,"currCount":_0x43f74a},this[_0x565e27(118)]*2),this[_0x565e27(113)](!![],this[_0x565e27(121)],_0x3d88fb,_0x4194f0,0,_0x12a23e)}const _0x40536c=Math["round"](_0x472ab2),_0x158c2c=Math[_0x565e27(140)](0,this[_0x565e27(121)]-_0x40536c);let _0x3a86a3=_0x12a23e-_0x13f1ff;if(_0x2e4f90>0){const _0x42bf9a=(this[_0x565e27(121)]-_0x43f74a-_0x2fa82c)/_0x2e4f90;if(_0x42bf9a<1&&_0x42bf9a>=0){const _0x401802=this[_0x565e27(118)]*(1-_0x42bf9a);_0x3a86a3=Math[_0x565e27(140)](1,Math["ceil"](_0x401802-_0x5dc491))}}(_0x3a86a3<=0||_0x3a86a3>this[_0x565e27(118)])&&(_0x3a86a3=Math["max"](1,_0x12a23e-_0x13f1ff));const _0x20eda0=Math[_0x565e27(140)](1,Math[_0x565e27(114)](_0x3a86a3/1000));return await this[_0x565e27(141)][_0x565e27(128)](_0x3f471b,{"windowStart":_0x3a46f2,"prevCount":_0x2e4f90,"currCount":_0x43f74a},this["windowMs"]*2),this[_0x565e27(113)](![],this["limit"],_0x158c2c,_0x40536c,_0x20eda0,_0x12a23e)})}async[_0x54218a(120)](_0x287adb){const _0x3afdbf=_0x54218a;if(this[_0x3afdbf(127)]()){const _0x2a5ad7=typeof this["store"][_0x3afdbf(133)]===_0x3afdbf(119)?this[_0x3afdbf(141)]["getNow"]():0,_0x4ebc6f=await this[_0x3afdbf(141)][_0x3afdbf(137)]("sliding-window-counter",[_0x287adb],[this["limit"],this[_0x3afdbf(118)],0,_0x2a5ad7]);return this[_0x3afdbf(113)](_0x4ebc6f[0]===1,_0x4ebc6f[1],_0x4ebc6f[2],_0x4ebc6f[3],_0x4ebc6f[4],_0x4ebc6f[5])}const _0x4578fd=Date["now"](),_0x5681d4=Math[_0x3afdbf(139)](_0x4578fd/this[_0x3afdbf(118)])*this["windowMs"],_0x24bb0d=_0x5681d4+this["windowMs"],_0x2a0353=await this[_0x3afdbf(141)][_0x3afdbf(134)](_0x287adb);let _0x5ac3fb=0,_0x15c77c=0;if(_0x2a0353){if(_0x2a0353[_0x3afdbf(135)]===_0x5681d4)_0x5ac3fb=_0x2a0353[_0x3afdbf(131)]||0,_0x15c77c=_0x2a0353[_0x3afdbf(125)]||0;else _0x5681d4===_0x2a0353[_0x3afdbf(135)]+this[_0x3afdbf(118)]&&(_0x5ac3fb=_0x2a0353["currCount"]||0,_0x15c77c=0)}const _0x493a76=_0x4578fd-_0x5681d4,_0x59096f=Math[_0x3afdbf(140)](0,1-_0x493a76/this[_0x3afdbf(118)]),_0x26315b=_0x5ac3fb*_0x59096f+_0x15c77c,_0x116a72=Math[_0x3afdbf(123)](_0x26315b),_0x120a08=Math[_0x3afdbf(140)](0,this["limit"]-_0x116a72),_0x2625b7=_0x26315b>=this[_0x3afdbf(121)]?Math[_0x3afdbf(140)](1,Math[_0x3afdbf(114)]((_0x24bb0d-_0x4578fd)/1000)):0;return this["normalizeResult"](_0x26315b<this[_0x3afdbf(121)],this[_0x3afdbf(121)],_0x120a08,_0x116a72,_0x2625b7,_0x24bb0d)}}function _0x5d52(_0x4d80ee,_0x3360fd){_0x4d80ee=_0x4d80ee-109;const _0x25aa2e=_0x25aa();let _0x5d52b0=_0x25aa2e[_0x4d80ee];if(_0x5d52["NVjyDm"]===undefined){var _0x39f122=function(_0x122765){const _0x4a2f8a="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";let _0x13d282="",_0x134889="";for(let _0x323e47=0,_0x3f471b,_0x2fa82c,_0x108161=0;_0x2fa82c=_0x122765["charAt"](_0x108161++);~_0x2fa82c&&(_0x3f471b=_0x323e47%4?_0x3f471b*64+_0x2fa82c:_0x2fa82c,_0x323e47++%4)?_0x13d282+=String["fromCharCode"](255&_0x3f471b>>(-2*_0x323e47&6)):0){_0x2fa82c=_0x4a2f8a["indexOf"](_0x2fa82c)}for(let _0x148d17=0,_0x13f1ff=_0x13d282["length"];_0x148d17<_0x13f1ff;_0x148d17++){_0x134889+="%"+("00"+_0x13d282["charCodeAt"](_0x148d17)["toString"](16))["slice"](-2)}return decodeURIComponent(_0x134889)};_0x5d52["HCvcjo"]=_0x39f122,_0x5d52["SokdXb"]={},_0x5d52["NVjyDm"]=!![]}const _0x26b4db=_0x25aa2e[0];_0x5d52["PEMBVL"]!==_0x26b4db&&(_0x5d52["SokdXb"]={},_0x5d52["PEMBVL"]=_0x26b4db);const _0x25d02c=_0x5d52["SokdXb"][_0x4d80ee];return _0x25d02c===undefined?(_0x5d52b0=_0x5d52["HCvcjo"](_0x5d52b0),_0x5d52["SokdXb"][_0x4d80ee]=_0x5d52b0):_0x5d52b0=_0x25d02c,_0x5d52b0}export default SlidingWindowCounter;
1
+ import{BaseAlgorithm}from'./baseAlgorithm.js';export class SlidingWindowCounter extends BaseAlgorithm{constructor(options,store){super(options,store);this.limit=options.limit;this.windowMs=options.windowMs;}async consume(key,cost=1){if(this.isRedis()){const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('sliding-window-counter',[key],[this.limit,this.windowMs,cost,nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}return this.withLock(key,async()=>{const now=Date.now();const currWindowStart=Math.floor(now/this.windowMs)*this.windowMs;const resetTime=currWindowStart+this.windowMs;const state=await this.store.get(key);let prevCount=0;let currCount=0;if(state){if(state.windowStart===currWindowStart){prevCount=state.prevCount||0;currCount=state.currCount||0;}else if(currWindowStart===state.windowStart+this.windowMs){prevCount=state.currCount||0;currCount=0;}}const elapsed=now-currWindowStart;const weight=Math.max(0,1-elapsed/this.windowMs);const estimated=prevCount*weight+currCount;if(estimated+cost<=this.limit){currCount+=cost;const consumed=Math.round(estimated+cost);const remaining=Math.max(0,this.limit-consumed);await this.store.set(key,{windowStart:currWindowStart,prevCount,currCount},this.windowMs*2);return this.normalizeResult(true,this.limit,remaining,consumed,0,resetTime);}const consumed=Math.round(estimated);const remaining=Math.max(0,this.limit-consumed);let waitMs=resetTime-now;if(prevCount>0){const neededWeight=(this.limit-currCount-cost)/prevCount;if(neededWeight<1&&neededWeight>=0){const t=this.windowMs*(1-neededWeight);waitMs=Math.max(1,Math.ceil(t-elapsed));}}if(waitMs<=0||waitMs>this.windowMs){waitMs=Math.max(1,resetTime-now);}const retryAfter=Math.max(1,Math.ceil(waitMs/1000));await this.store.set(key,{windowStart:currWindowStart,prevCount,currCount},this.windowMs*2);return this.normalizeResult(false,this.limit,remaining,consumed,retryAfter,resetTime);});}async getState(key){if(this.isRedis()){const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('sliding-window-counter',[key],[this.limit,this.windowMs,0,nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}const now=Date.now();const currWindowStart=Math.floor(now/this.windowMs)*this.windowMs;const resetTime=currWindowStart+this.windowMs;const state=await this.store.get(key);let prevCount=0;let currCount=0;if(state){if(state.windowStart===currWindowStart){prevCount=state.prevCount||0;currCount=state.currCount||0;}else if(currWindowStart===state.windowStart+this.windowMs){prevCount=state.currCount||0;currCount=0;}}const elapsed=now-currWindowStart;const weight=Math.max(0,1-elapsed/this.windowMs);const estimated=prevCount*weight+currCount;const consumed=Math.round(estimated);const remaining=Math.max(0,this.limit-consumed);const retryAfter=estimated>=this.limit?Math.max(1,Math.ceil((resetTime-now)/1000)):0;return this.normalizeResult(estimated<this.limit,this.limit,remaining,consumed,retryAfter,resetTime);}}export default SlidingWindowCounter;
@@ -1 +1 @@
1
- function _0x3cf3(_0x51966f,_0x25576){_0x51966f=_0x51966f-258;const _0xe22bdb=_0xe22b();let _0x3cf315=_0xe22bdb[_0x51966f];if(_0x3cf3["hkXgZE"]===undefined){var _0x332122=function(_0x2b1dfb){const _0xbc1d5="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";let _0x222f60="",_0xb0e8cc="";for(let _0x1bf229=0,_0x4d1bc8,_0x4ea25d,_0x1900ee=0;_0x4ea25d=_0x2b1dfb["charAt"](_0x1900ee++);~_0x4ea25d&&(_0x4d1bc8=_0x1bf229%4?_0x4d1bc8*64+_0x4ea25d:_0x4ea25d,_0x1bf229++%4)?_0x222f60+=String["fromCharCode"](255&_0x4d1bc8>>(-2*_0x1bf229&6)):0){_0x4ea25d=_0xbc1d5["indexOf"](_0x4ea25d)}for(let _0x4ffb00=0,_0x7c5444=_0x222f60["length"];_0x4ffb00<_0x7c5444;_0x4ffb00++){_0xb0e8cc+="%"+("00"+_0x222f60["charCodeAt"](_0x4ffb00)["toString"](16))["slice"](-2)}return decodeURIComponent(_0xb0e8cc)};_0x3cf3["uyDunM"]=_0x332122,_0x3cf3["MbIlEQ"]={},_0x3cf3["hkXgZE"]=!![]}const _0x3a24b4=_0xe22bdb[0];_0x3cf3["pfQpbe"]!==_0x3a24b4&&(_0x3cf3["MbIlEQ"]={},_0x3cf3["pfQpbe"]=_0x3a24b4);const _0x3c68e8=_0x3cf3["MbIlEQ"][_0x51966f];return _0x3c68e8===undefined?(_0x3cf315=_0x3cf3["uyDunM"](_0x3cf315),_0x3cf3["MbIlEQ"][_0x51966f]=_0x3cf315):_0x3cf315=_0x3c68e8,_0x3cf315}const _0x1c3e9b=_0x3cf3;(function(_0x161f24,_0x2e1dce){const _0x310491=_0x3cf3,_0xc6a98d=_0x161f24();while(!![]){try{const _0x526499=-parseInt(_0x310491(268))/1*(-parseInt(_0x310491(290))/2)+-parseInt(_0x310491(261))/3*(parseInt(_0x310491(259))/4)+parseInt(_0x310491(294))/5+parseInt(_0x310491(276))/6*(-parseInt(_0x310491(263))/7)+-parseInt(_0x310491(293))/8+-parseInt(_0x310491(288))/9*(parseInt(_0x310491(264))/10)+-parseInt(_0x310491(286))/11*(-parseInt(_0x310491(275))/12);if(_0x526499===_0x2e1dce)break;else _0xc6a98d["push"](_0xc6a98d["shift"]())}catch(_0x299585){_0xc6a98d["push"](_0xc6a98d["shift"]())}}})(_0xe22b,336130);function _0xe22b(){const _0x4db3db=["D2LUzg93txm","nJuWntfcAvfztvq","ntCXotKWt2vRq0nm","BM9YBwfSAxPLuMvZDwX0","AxnbCNjHEq","z2v0","mxDMCvf6qq","C2XPzgLUzY13Aw5KB3CTBg9N","DgLTzq","y29ZDa","C2XPy2u","BM93","BgLTAxq","ntCZnMHrEg5lsW","mJi4uM51s1Lt","y2vPBa","zNvUy3rPB24","AxnszwrPCW","C2v0","Bg9N","CMfUzg9T","BgvUz3rO","CNvUu2nYAxb0","Dg9tDhjPBMC","mJGZmJv2q0L2DvO","ChvZAa","otLWD3bUAxC","C3rVCMu","mJG3otCYqu9JvLjq","D2L0AeXVy2S","zMLSDgvY","ndC0oteYogjLB1jAqq","mZeYnZe5nwrKC0jzzq","Bwf4","Aw5ZCgvJDa","y29UC3vTzq","ne9cyvP1Dq","z2v0u3rHDgu","mJy0ntqZrgvwChPb"];_0xe22b=function(){return _0x4db3db};return _0xe22b()}import{BaseAlgorithm}from"./baseAlgorithm.js";export class SlidingWindowLog extends BaseAlgorithm{constructor(_0xb0e8cc,_0x1bf229){const _0x52b70f=_0x3cf3;super(_0xb0e8cc,_0x1bf229),this[_0x52b70f(274)]=_0xb0e8cc[_0x52b70f(274)],this[_0x52b70f(262)]=_0xb0e8cc["windowMs"]}async[_0x1c3e9b(258)](_0x4d1bc8,_0x4ea25d=1){const _0x4747d2=_0x1c3e9b;if(this[_0x4747d2(279)]()){const _0x1900ee=Date[_0x4747d2(273)]()+":"+Math[_0x4747d2(282)]()[_0x4747d2(285)](36)[_0x4747d2(272)](2,8),_0x4ffb00=typeof this[_0x4747d2(289)]["getNow"]===_0x4747d2(278)?this[_0x4747d2(289)]["getNow"]():0,_0x7c5444=await this[_0x4747d2(289)][_0x4747d2(284)](_0x4747d2(269),[_0x4d1bc8],[this[_0x4747d2(274)],this[_0x4747d2(262)],_0x4ea25d,_0x1900ee,_0x4ffb00]);return this[_0x4747d2(265)](_0x7c5444[0]===1,_0x7c5444[1],_0x7c5444[2],_0x7c5444[3],_0x7c5444[4],_0x7c5444[5])}return this[_0x4747d2(291)](_0x4d1bc8,async()=>{const _0x1b9ac1=_0x4747d2,_0x4f53fd=Date["now"](),_0xa8331b=_0x4f53fd-this[_0x1b9ac1(262)],_0x1fb517=await this[_0x1b9ac1(289)][_0x1b9ac1(267)](_0x4d1bc8);let _0x468d19=[];_0x1fb517&&Array["isArray"](_0x1fb517["log"])&&(_0x468d19=_0x1fb517[_0x1b9ac1(281)][_0x1b9ac1(292)](_0x33c1bc=>_0x33c1bc[_0x1b9ac1(270)]>_0xa8331b));let _0x35bc92=0;for(let _0x1ee7ba=0;_0x1ee7ba<_0x468d19[_0x1b9ac1(283)];_0x1ee7ba++){_0x35bc92+=_0x468d19[_0x1ee7ba][_0x1b9ac1(271)]}if(_0x35bc92+_0x4ea25d<=this[_0x1b9ac1(274)]){_0x468d19[_0x1b9ac1(287)]({"time":_0x4f53fd,"cost":_0x4ea25d});const _0xf05da7=_0x35bc92+_0x4ea25d,_0x369f6b=this[_0x1b9ac1(274)]-_0xf05da7,_0x593cbb=_0x468d19[0]["time"],_0xf31e5=_0x593cbb+this["windowMs"];return await this[_0x1b9ac1(289)][_0x1b9ac1(280)](_0x4d1bc8,{"log":_0x468d19},this["windowMs"]),this[_0x1b9ac1(265)](!![],this["limit"],_0x369f6b,_0xf05da7,0,_0xf31e5)}const _0x25d59f=_0x35bc92+_0x4ea25d-this[_0x1b9ac1(274)];let _0x2e15df=0,_0x590094=_0x4f53fd;for(let _0x1e39a6=0;_0x1e39a6<_0x468d19[_0x1b9ac1(283)];_0x1e39a6++){_0x2e15df+=_0x468d19[_0x1e39a6][_0x1b9ac1(271)];if(_0x2e15df>=_0x25d59f){_0x590094=_0x468d19[_0x1e39a6]["time"];break}}const _0x1e7fe2=_0x590094+this[_0x1b9ac1(262)],_0x39c303=Math[_0x1b9ac1(295)](1,_0x1e7fe2-_0x4f53fd),_0x59ba9b=Math[_0x1b9ac1(295)](1,Math[_0x1b9ac1(277)](_0x39c303/1000)),_0x5e367d=Math[_0x1b9ac1(295)](0,this[_0x1b9ac1(274)]-_0x35bc92);return await this[_0x1b9ac1(289)][_0x1b9ac1(280)](_0x4d1bc8,{"log":_0x468d19},this["windowMs"]),this[_0x1b9ac1(265)](![],this[_0x1b9ac1(274)],_0x5e367d,_0x35bc92,_0x59ba9b,_0x1e7fe2)})}async[_0x1c3e9b(260)](_0xe51c57){const _0x51b840=_0x1c3e9b;if(this["isRedis"]()){const _0xd37f21=typeof this[_0x51b840(289)]["getNow"]===_0x51b840(278)?this[_0x51b840(289)]["getNow"]():0,_0x4127e3=await this[_0x51b840(289)][_0x51b840(284)](_0x51b840(269),[_0xe51c57],[this["limit"],this[_0x51b840(262)],0,_0x51b840(296),_0xd37f21]);return this["normalizeResult"](_0x4127e3[0]===1,_0x4127e3[1],_0x4127e3[2],_0x4127e3[3],_0x4127e3[4],_0x4127e3[5])}const _0x4f280c=Date[_0x51b840(273)](),_0x510353=_0x4f280c-this[_0x51b840(262)],_0x56bd15=await this[_0x51b840(289)]["get"](_0xe51c57);let _0x105544=[];_0x56bd15&&Array[_0x51b840(266)](_0x56bd15[_0x51b840(281)])&&(_0x105544=_0x56bd15[_0x51b840(281)][_0x51b840(292)](_0x53b028=>_0x53b028[_0x51b840(270)]>_0x510353));let _0x393bd6=0;for(let _0x45e68b=0;_0x45e68b<_0x105544[_0x51b840(283)];_0x45e68b++){_0x393bd6+=_0x105544[_0x45e68b]["cost"]}const _0x4c46ed=Math[_0x51b840(295)](0,this[_0x51b840(274)]-_0x393bd6),_0x9393df=_0x105544[_0x51b840(283)]>0?_0x105544[0][_0x51b840(270)]:_0x4f280c,_0x2e13e3=_0x9393df+this[_0x51b840(262)],_0x3d71d1=_0x393bd6>=this[_0x51b840(274)]?Math[_0x51b840(295)](1,Math["ceil"]((_0x2e13e3-_0x4f280c)/1000)):0;return this[_0x51b840(265)](_0x393bd6<this[_0x51b840(274)],this[_0x51b840(274)],_0x4c46ed,_0x393bd6,_0x3d71d1,_0x2e13e3)}}export default SlidingWindowLog;
1
+ import{BaseAlgorithm}from'./baseAlgorithm.js';export class SlidingWindowLog extends BaseAlgorithm{constructor(options,store){super(options,store);this.limit=options.limit;this.windowMs=options.windowMs;}async consume(key,cost=1){if(this.isRedis()){const uid=`${Date.now()}:${Math.random().toString(36).slice(2,8)}`;const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('sliding-window-log',[key],[this.limit,this.windowMs,cost,uid,nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}return this.withLock(key,async()=>{const now=Date.now();const windowStart=now-this.windowMs;const state=await this.store.get(key);let log=[];if(state&&Array.isArray(state.log)){log=state.log.filter(entry=>entry.time>windowStart);}let current=0;for(let i=0;i<log.length;i++){current+=log[i].cost;}if(current+cost<=this.limit){log.push({time:now,cost});const consumed=current+cost;const remaining=this.limit-consumed;const oldestTime=log[0].time;const resetTime=oldestTime+this.windowMs;await this.store.set(key,{log},this.windowMs);return this.normalizeResult(true,this.limit,remaining,consumed,0,resetTime);}const deficit=current+cost-this.limit;let accumulated=0;let expireTime=now;for(let i=0;i<log.length;i++){accumulated+=log[i].cost;if(accumulated>=deficit){expireTime=log[i].time;break;}}const resetTime=expireTime+this.windowMs;const waitMs=Math.max(1,resetTime-now);const retryAfter=Math.max(1,Math.ceil(waitMs/1000));const remaining=Math.max(0,this.limit-current);await this.store.set(key,{log},this.windowMs);return this.normalizeResult(false,this.limit,remaining,current,retryAfter,resetTime);});}async getState(key){if(this.isRedis()){const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('sliding-window-log',[key],[this.limit,this.windowMs,0,'inspect',nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}const now=Date.now();const windowStart=now-this.windowMs;const state=await this.store.get(key);let log=[];if(state&&Array.isArray(state.log)){log=state.log.filter(entry=>entry.time>windowStart);}let current=0;for(let i=0;i<log.length;i++){current+=log[i].cost;}const remaining=Math.max(0,this.limit-current);const oldestTime=log.length>0?log[0].time:now;const resetTime=oldestTime+this.windowMs;const retryAfter=current>=this.limit?Math.max(1,Math.ceil((resetTime-now)/1000)):0;return this.normalizeResult(current<this.limit,this.limit,remaining,current,retryAfter,resetTime);}}export default SlidingWindowLog;
@@ -1 +1 @@
1
- const _0x589a59=_0x57bf;(function(_0x5d1263,_0x3d8256){const _0xf35e81=_0x57bf,_0x3c3b77=_0x5d1263();while(!![]){try{const _0x22a625=-parseInt(_0xf35e81(384))/1+parseInt(_0xf35e81(395))/2+-parseInt(_0xf35e81(393))/3*(parseInt(_0xf35e81(366))/4)+-parseInt(_0xf35e81(396))/5*(parseInt(_0xf35e81(371))/6)+parseInt(_0xf35e81(365))/7*(parseInt(_0xf35e81(378))/8)+-parseInt(_0xf35e81(380))/9*(parseInt(_0xf35e81(368))/10)+parseInt(_0xf35e81(362))/11;if(_0x22a625===_0x3d8256)break;else _0x3c3b77["push"](_0x3c3b77["shift"]())}catch(_0x339805){_0x3c3b77["push"](_0x3c3b77["shift"]())}}})(_0x37e8,363242);function _0x57bf(_0x429af7,_0xc4eada){_0x429af7=_0x429af7-361;const _0x37e890=_0x37e8();let _0x57bf57=_0x37e890[_0x429af7];if(_0x57bf["OEfQQp"]===undefined){var _0x1c32d1=function(_0x1d4b7d){const _0x4d1d58="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";let _0x52288a="",_0x1ee2a8="";for(let _0x4bb8bb=0,_0x1ebdfa,_0x3076bf,_0x30b2c1=0;_0x3076bf=_0x1d4b7d["charAt"](_0x30b2c1++);~_0x3076bf&&(_0x1ebdfa=_0x4bb8bb%4?_0x1ebdfa*64+_0x3076bf:_0x3076bf,_0x4bb8bb++%4)?_0x52288a+=String["fromCharCode"](255&_0x1ebdfa>>(-2*_0x4bb8bb&6)):0){_0x3076bf=_0x4d1d58["indexOf"](_0x3076bf)}for(let _0x4db8fb=0,_0x3c9947=_0x52288a["length"];_0x4db8fb<_0x3c9947;_0x4db8fb++){_0x1ee2a8+="%"+("00"+_0x52288a["charCodeAt"](_0x4db8fb)["toString"](16))["slice"](-2)}return decodeURIComponent(_0x1ee2a8)};_0x57bf["BpwhfA"]=_0x1c32d1,_0x57bf["pBLeHn"]={},_0x57bf["OEfQQp"]=!![]}const _0x4ba82f=_0x37e890[0];_0x57bf["TeequZ"]!==_0x4ba82f&&(_0x57bf["pBLeHn"]={},_0x57bf["TeequZ"]=_0x4ba82f);const _0x3954cf=_0x57bf["pBLeHn"][_0x429af7];return _0x3954cf===undefined?(_0x57bf57=_0x57bf["BpwhfA"](_0x57bf57),_0x57bf["pBLeHn"][_0x429af7]=_0x57bf57):_0x57bf57=_0x3954cf,_0x57bf57}import{BaseAlgorithm}from"./baseAlgorithm.js";export class TokenBucket extends BaseAlgorithm{constructor(_0x1ee2a8,_0x4bb8bb){const _0x41c5c7=_0x57bf;super(_0x1ee2a8,_0x4bb8bb),this[_0x41c5c7(364)]=_0x1ee2a8[_0x41c5c7(364)],this[_0x41c5c7(383)]=_0x1ee2a8[_0x41c5c7(383)],this[_0x41c5c7(386)]=_0x1ee2a8[_0x41c5c7(386)],this[_0x41c5c7(382)]=_0x1ee2a8[_0x41c5c7(382)]!==undefined?_0x1ee2a8[_0x41c5c7(382)]:this[_0x41c5c7(364)],this[_0x41c5c7(361)]=this[_0x41c5c7(383)]/this[_0x41c5c7(386)]}async[_0x589a59(363)](_0x1ebdfa,_0x3076bf=1){const _0x590314=_0x589a59;if(this["isRedis"]()){const _0x30b2c1=typeof this[_0x590314(367)][_0x590314(376)]==="function"?this[_0x590314(367)]["getNow"]():0,_0x4db8fb=await this[_0x590314(367)][_0x590314(373)](_0x590314(372),[_0x1ebdfa],[this[_0x590314(364)],this[_0x590314(383)],this[_0x590314(386)],_0x3076bf,this[_0x590314(382)],_0x30b2c1]);return this[_0x590314(369)](_0x4db8fb[0]===1,_0x4db8fb[1],_0x4db8fb[2],_0x4db8fb[3],_0x4db8fb[4],_0x4db8fb[5])}return this[_0x590314(375)](_0x1ebdfa,async()=>{const _0x1d5355=_0x590314,_0x3c9947=Date["now"](),_0x427ea6=await this[_0x1d5355(367)][_0x1d5355(374)](_0x1ebdfa);let _0x327d8a=this[_0x1d5355(382)],_0x39673a=_0x3c9947;if(_0x427ea6&&typeof _0x427ea6[_0x1d5355(387)]===_0x1d5355(390)){const _0x3d375f=Math[_0x1d5355(381)](0,_0x3c9947-_0x427ea6[_0x1d5355(388)]);_0x327d8a=Math[_0x1d5355(370)](this[_0x1d5355(364)],_0x427ea6[_0x1d5355(387)]+_0x3d375f*this[_0x1d5355(361)]),_0x39673a=_0x3c9947}const _0x26f818=Math[_0x1d5355(381)](1000,Math[_0x1d5355(391)](this[_0x1d5355(364)]/this[_0x1d5355(361)]*2));if(_0x327d8a>=_0x3076bf){_0x327d8a-=_0x3076bf;const _0x582323=this[_0x1d5355(364)]-Math[_0x1d5355(377)](_0x327d8a),_0x3c4d8c=Math[_0x1d5355(377)](_0x327d8a),_0x16e508=_0x3c9947+Math[_0x1d5355(391)]((this["capacity"]-_0x327d8a)/this["rate"]);return await this["store"][_0x1d5355(389)](_0x1ebdfa,{"tokens":_0x327d8a,"lastRefill":_0x39673a},_0x26f818),this["normalizeResult"](!![],this[_0x1d5355(364)],_0x3c4d8c,_0x582323,0,_0x16e508)}const _0x561ad8=this[_0x1d5355(364)]-Math[_0x1d5355(377)](_0x327d8a),_0x4bd033=Math["floor"](_0x327d8a),_0x12a0c6=_0x3076bf-_0x327d8a,_0x260aa7=Math[_0x1d5355(391)](_0x12a0c6/this["rate"]),_0x250918=Math["max"](1,Math[_0x1d5355(391)](_0x260aa7/1000)),_0x1ae4e2=_0x3c9947+Math[_0x1d5355(391)]((this["capacity"]-_0x327d8a)/this["rate"]);return await this["store"][_0x1d5355(389)](_0x1ebdfa,{"tokens":_0x327d8a,"lastRefill":_0x39673a},_0x26f818),this[_0x1d5355(369)](![],this[_0x1d5355(364)],_0x4bd033,_0x561ad8,_0x250918,_0x1ae4e2)})}async[_0x589a59(385)](_0x4c0c1c){const _0x55af31=_0x589a59;if(this[_0x55af31(379)]()){const _0x242127=typeof this[_0x55af31(367)]["getNow"]===_0x55af31(392)?this[_0x55af31(367)][_0x55af31(376)]():0,_0x1ff097=await this["store"][_0x55af31(373)](_0x55af31(372),[_0x4c0c1c],[this[_0x55af31(364)],this[_0x55af31(383)],this[_0x55af31(386)],0,this[_0x55af31(382)],_0x242127]);return this[_0x55af31(369)](_0x1ff097[0]===1,_0x1ff097[1],_0x1ff097[2],_0x1ff097[3],_0x1ff097[4],_0x1ff097[5])}const _0x2b5992=Date[_0x55af31(394)](),_0x562dfc=await this["store"][_0x55af31(374)](_0x4c0c1c);let _0x39d760=this["initialTokens"];if(_0x562dfc&&typeof _0x562dfc[_0x55af31(387)]===_0x55af31(390)){const _0x257804=Math[_0x55af31(381)](0,_0x2b5992-_0x562dfc[_0x55af31(388)]);_0x39d760=Math["min"](this[_0x55af31(364)],_0x562dfc["tokens"]+_0x257804*this["rate"])}const _0x4668a9=this[_0x55af31(364)]-Math[_0x55af31(377)](_0x39d760),_0x1924f1=Math["floor"](_0x39d760),_0x595ba2=_0x2b5992+Math[_0x55af31(391)]((this["capacity"]-_0x39d760)/this[_0x55af31(361)]),_0x77b04d=_0x39d760<1?Math[_0x55af31(381)](1,Math["ceil"]((1-_0x39d760)/this[_0x55af31(361)]/1000)):0;return this["normalizeResult"](_0x39d760>=1,this[_0x55af31(364)],_0x1924f1,_0x4668a9,_0x77b04d,_0x595ba2)}}function _0x37e8(){const _0xd3c747=["zMXVB3i","mZe4mtzUt1bqCeS","AxnszwrPCW","nduZmta1wLbhshvt","Bwf4","Aw5PDgLHBfrVA2vUCW","CMvMAwXSuMf0zq","ntC5ota5rvrUtK1p","z2v0u3rHDgu","CMvMAwXSsw50zxj2ywW","Dg9Rzw5Z","BgfZDfjLzMLSBa","C2v0","BNvTyMvY","y2vPBa","zNvUy3rPB24","mJy5ntHXre52sva","BM93","nti2nZuWBvDiveDm","mti1v0nsB3Lf","CMf0zq","mJeZmZqZmZvIqvz3vNC","y29UC3vTzq","y2fWywnPDhK","nJnWD0LwAM4","mtq4sxPRuuvN","C3rVCMu","mteWugXqy1rz","BM9YBwfSAxPLuMvZDwX0","BwLU","otGYmtrKvwTlvhy","Dg9Rzw4TyNvJA2v0","CNvUu2nYAxb0","z2v0","D2L0AeXVy2S","z2v0tM93"];_0x37e8=function(){return _0xd3c747};return _0x37e8()}export default TokenBucket;
1
+ import{BaseAlgorithm}from'./baseAlgorithm.js';export class TokenBucket extends BaseAlgorithm{constructor(options,store){super(options,store);this.capacity=options.capacity;this.refillRate=options.refillRate;this.refillInterval=options.refillInterval;this.initialTokens=options.initialTokens!==undefined?options.initialTokens:this.capacity;this.rate=this.refillRate/this.refillInterval;}async consume(key,cost=1){if(this.isRedis()){const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('token-bucket',[key],[this.capacity,this.refillRate,this.refillInterval,cost,this.initialTokens,nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}return this.withLock(key,async()=>{const now=Date.now();const state=await this.store.get(key);let tokens=this.initialTokens;let lastRefill=now;if(state&&typeof state.tokens==='number'){const elapsed=Math.max(0,now-state.lastRefill);tokens=Math.min(this.capacity,state.tokens+elapsed*this.rate);lastRefill=now;}const ttlMs=Math.max(1000,Math.ceil(this.capacity/this.rate*2.0));if(tokens>=cost){tokens-=cost;const consumed=this.capacity-Math.floor(tokens);const remaining=Math.floor(tokens);const resetTime=now+Math.ceil((this.capacity-tokens)/this.rate);await this.store.set(key,{tokens,lastRefill},ttlMs);return this.normalizeResult(true,this.capacity,remaining,consumed,0,resetTime);}const consumed=this.capacity-Math.floor(tokens);const remaining=Math.floor(tokens);const deficit=cost-tokens;const waitMs=Math.ceil(deficit/this.rate);const retryAfter=Math.max(1,Math.ceil(waitMs/1000));const resetTime=now+Math.ceil((this.capacity-tokens)/this.rate);await this.store.set(key,{tokens,lastRefill},ttlMs);return this.normalizeResult(false,this.capacity,remaining,consumed,retryAfter,resetTime);});}async getState(key){if(this.isRedis()){const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('token-bucket',[key],[this.capacity,this.refillRate,this.refillInterval,0,this.initialTokens,nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}const now=Date.now();const state=await this.store.get(key);let tokens=this.initialTokens;if(state&&typeof state.tokens==='number'){const elapsed=Math.max(0,now-state.lastRefill);tokens=Math.min(this.capacity,state.tokens+elapsed*this.rate);}const consumed=this.capacity-Math.floor(tokens);const remaining=Math.floor(tokens);const resetTime=now+Math.ceil((this.capacity-tokens)/this.rate);const retryAfter=tokens<1?Math.max(1,Math.ceil((1-tokens)/this.rate/1000)):0;return this.normalizeResult(tokens>=1,this.capacity,remaining,consumed,retryAfter,resetTime);}}export default TokenBucket;
@@ -0,0 +1 @@
1
+ export class BlockedKeys{constructor(){this._keys=new Map();}add(key,durationSec){const durationMs=Math.max(0,Number(durationSec)||0)*1000;this.addMs(key,durationMs);}_sweep(now=Date.now()){for(const[key,expiresAt]of this._keys.entries()){if(expiresAt<=now){this._keys.delete(key);}}}addMs(key,durationMs){const now=Date.now();if(this._keys.size>999){this._sweep(now);}const ms=Math.max(0,Number(durationMs)||0);this._keys.set(key,now+ms);}msBeforeExpire(key){const expiresAt=this._keys.get(key);if(expiresAt===undefined)return 0;const remaining=expiresAt-Date.now();if(remaining<=0){this._keys.delete(key);return 0;}return remaining;}isBlocked(key){return this.msBeforeExpire(key)>0;}delete(key){return this._keys.delete(key);}deleteAll(){this._keys.clear();}get size(){return this._keys.size;}}export default BlockedKeys;
@@ -0,0 +1 @@
1
+ import{defaultKeyGenerator,ipKeyGenerator,graphQLKeyGenerator}from'../utils/keyGenerator.js';import{evaluateSkipRule}from'../utils/skipUtils.js';import{RateLimiterRes}from'../core/rateLimiterRes.js';export function createCoreMiddleware(limiter,options={}){if(!limiter||typeof limiter.consume!=='function'){throw new TypeError('createCoreMiddleware requires a limiter with a consume() method');}let keyGen=defaultKeyGenerator;if(typeof options.keyGenerator==='function'){keyGen=options.keyGenerator;}else if(options.keyGenerator==='ip'){keyGen=ipKeyGenerator;}else if(options.keyGenerator==='graphql'){keyGen=graphQLKeyGenerator;}const rawSkip=options.skip!==undefined?options.skip:options.customSkip!==undefined?options.customSkip:options.customskip;const skipFn=typeof rawSkip==='function'?rawSkip:typeof rawSkip==='object'&&rawSkip!==null?(req,res)=>evaluateSkipRule(rawSkip,req,res,{generateKey:keyGen}):()=>false;const defaultHandler=(req,res,next,opts)=>{res.status(opts.statusCode);const msg=typeof opts.message==='function'?opts.message(req,res):opts.message;if(typeof msg==='object'){res.json(msg);}else{res.send(msg);}};const handler=options.handler||defaultHandler;const resolvedOptions={standardHeaders:options.standardHeaders!==undefined?options.standardHeaders:true,legacyHeaders:options.legacyHeaders!==undefined?options.legacyHeaders:false,...options,statusCode:options.statusCode!==undefined?options.statusCode:429,message:options.message!==undefined?options.message:'Too many requests, please try again later.'};const middleware=async(req,res,next)=>{try{const shouldSkip=await Promise.resolve(skipFn(req,res));if(shouldSkip){return next();}const key=await Promise.resolve(keyGen(req,res));let cost=1;if(typeof options.cost==='function'){cost=await Promise.resolve(options.cost(req,res));}else if(typeof options.cost==='number'){cost=options.cost;}if(!Number.isFinite(cost)||cost<=0){cost=1;}let res1;try{res1=await limiter.consume(key,cost,{req,res});}catch(err){if(err instanceof RateLimiterRes){res1=err;}else if(err&&typeof err==='object'&&Object.values(err).some(v=>v instanceof RateLimiterRes)){const resValues=Object.values(err).filter(v=>v instanceof RateLimiterRes);const maxMsBeforeNext=Math.max(...resValues.map(v=>v.msBeforeNext||0));const minRemaining=Math.min(...resValues.map(v=>v.remainingPoints||0));const maxConsumed=Math.max(...resValues.map(v=>v.consumedPoints||0));res1=new RateLimiterRes(minRemaining,maxMsBeforeNext,maxConsumed,false);res1._unionRejections=err;}else{throw err;}}if(res1&&!(res1 instanceof RateLimiterRes)&&typeof res1==='object'&&Object.values(res1).every(v=>v instanceof RateLimiterRes)){const resValues=Object.values(res1);const maxMsBeforeNext=Math.max(...resValues.map(v=>v.msBeforeNext||0));const minRemaining=Math.min(...resValues.map(v=>v.remainingPoints||0));const maxConsumed=Math.max(...resValues.map(v=>v.consumedPoints||0));res1=new RateLimiterRes(minRemaining,maxMsBeforeNext,maxConsumed,resValues.some(v=>v.isFirstInDuration));}const result=toMiddlewareResult(res1,limiter,cost);req.rateLimit=result;setCoreHeaders(res,result,{...resolvedOptions,limiter});if(result.allowed){return next();}return handler(req,res,next,resolvedOptions);}catch(err){if(options.failOpen){req.rateLimit={allowed:true,limit:limiter.points||60,remaining:1,consumed:0,retryAfter:0,resetTime:new Date(Date.now()+(limiter.msDuration||60000)),failedOpen:true};if(typeof options.onError==='function'){options.onError(err,req,res,next);}return next();}if(typeof options.onError==='function'){return options.onError(err,req,res,next);}return next(err);}};middleware.resetKey=async keyOrReq=>{const k=typeof keyOrReq==='object'&&keyOrReq!==null?keyGen(keyOrReq):keyOrReq;if(typeof limiter.delete==='function'){return limiter.delete(k);}return false;};middleware.getState=async keyOrReq=>{const k=typeof keyOrReq==='object'&&keyOrReq!==null?keyGen(keyOrReq):keyOrReq;if(typeof limiter.get==='function'){return limiter.get(k);}return null;};middleware.limiter=limiter;return middleware;}function toMiddlewareResult(res,limiter,cost){const limit=typeof limiter.points==='number'?limiter.points:typeof res.consumedPoints==='number'&&typeof res.remainingPoints==='number'?res.consumedPoints+res.remainingPoints:60;const consumed=typeof res.consumedPoints==='number'?res.consumedPoints:cost;const msBeforeNext=typeof res.msBeforeNext==='number'?res.msBeforeNext:0;const rawReset=Number.isFinite(msBeforeNext)&&msBeforeNext>0?Date.now()+Math.min(msBeforeNext,31536000000):Date.now();const resetTime=new Date(rawReset);return{allowed:consumed<=limit,limit,remaining:Math.max(0,limit-consumed),consumed,retryAfter:msBeforeNext>0?Math.max(1,Math.ceil(msBeforeNext/1000)):0,resetTime:Number.isNaN(resetTime.getTime())?new Date(Date.now()+3600000):resetTime,cost};}function setCoreHeaders(res,result,options={}){if(!res||typeof res.setHeader!=='function'||res.headersSent){return;}const rawDiff=result.resetTime?(result.resetTime.getTime()-Date.now())/1000:0;const resetSeconds=Number.isFinite(rawDiff)?Math.max(0,Math.ceil(rawDiff)):0;if(options.standardHeaders){res.setHeader('RateLimit-Limit',String(result.limit));res.setHeader('RateLimit-Remaining',String(result.remaining));res.setHeader('RateLimit-Reset',String(resetSeconds));const policyWindowMs=options.windowMs||options.limiter&&(options.limiter.msDuration||(options.limiter.duration?options.limiter.duration*1000:null))||60000;if(options.standardHeaders!=='draft-6'){const windowSec=Math.ceil(policyWindowMs/1000);res.setHeader('RateLimit-Policy',`${result.limit};w=${windowSec}`);}}if(options.legacyHeaders){res.setHeader('X-RateLimit-Limit',String(result.limit));res.setHeader('X-RateLimit-Remaining',String(result.remaining));res.setHeader('X-RateLimit-Reset',String(resetSeconds));}if(!result.allowed){const retryAfter=Math.max(1,Number(result.retryAfter)||resetSeconds||1);res.setHeader('Retry-After',String(retryAfter));}}export default createCoreMiddleware;
@@ -0,0 +1 @@
1
+ import{RedisLimiter}from'./redisLimiter.js';import{RedisConnectionError}from'../../utils/errors.js';export class ClusterLimiter extends RedisLimiter{constructor(opts={}){const client=opts.storeClient||opts.client||opts.redis||null;if(!client){throw new RedisConnectionError('Redis cluster client is not configured. Pass it via the "storeClient" option.');}super({...opts,isCluster:opts.isCluster!==undefined?opts.isCluster:true,hashTag:opts.hashTag!==undefined?Boolean(opts.hashTag):true});}}export const RedisClusterLimiter=ClusterLimiter;export const RateLimiterRedisCluster=ClusterLimiter;export default ClusterLimiter;
@@ -0,0 +1 @@
1
+ import{RateLimiterStore}from'../rateLimiterStore.js';import{ConfigurationError}from'../../utils/errors.js';const DEFAULT_TABLE_NAME='rate-limiter';const AWS_DYNAMODB_MODULE='@aws-sdk/client-dynamodb';const COMMAND_NAMES={UpdateItem:'UpdateItemCommand',GetItem:'GetItemCommand',DeleteItem:'DeleteItemCommand'};const DIRECT_METHODS={UpdateItem:'updateItem',GetItem:'getItem',DeleteItem:'deleteItem'};export class DynamoDbLimiter extends RateLimiterStore{constructor(opts={}){super(opts);this.client=opts.storeClient||opts.client||opts.dynamoDb||null;if(!this.client){throw new ConfigurationError('DynamoDB client is not configured. Pass it via the "storeClient" option.');}this.tableName=opts.tableName!==undefined?opts.tableName:DEFAULT_TABLE_NAME;this._commands=opts.commands||null;this._initPromise=this._init();}async _init(){if(this._commands||typeof this.client.send!=='function'){return;}try{const sdk=await import(AWS_DYNAMODB_MODULE);if(sdk&&sdk.UpdateItemCommand){this._commands={UpdateItemCommand:sdk.UpdateItemCommand,GetItemCommand:sdk.GetItemCommand,DeleteItemCommand:sdk.DeleteItemCommand};}}catch{}}async _send(operation,input){await this._initPromise;const client=this.client;if(typeof client.send==='function'){const CommandClass=this._commands?this._commands[COMMAND_NAMES[operation]]:null;if(CommandClass){return client.send(new CommandClass(input));}return client.send({name:COMMAND_NAMES[operation],input});}const methodName=DIRECT_METHODS[operation];if(typeof client[methodName]==='function'){const res=await client[methodName](input);return res&&typeof res.promise==='function'?res.promise():res;}throw new ConfigurationError('DynamoDB client does not support "send" or the DynamoDB item methods.');}_toResult(item){if(!item){return{consumedPoints:0,expireAt:null};}const points=item.points;const expire=item.expire;return{consumedPoints:points===null||points===undefined?0:Number(points),expireAt:expire===null||expire===undefined?null:Number(expire)};}async _upsert(rlKey,points,msDuration,forceExpire=false,_options={}){const expireAt=msDuration>0?Date.now()+msDuration:null;if(forceExpire){const res=await this._send('UpdateItem',{TableName:this.tableName,Key:{key:rlKey},UpdateExpression:'SET #points = :points, #expire = :expire',ExpressionAttributeNames:{'#points':'points','#expire':'expire'},ExpressionAttributeValues:{':points':points,':expire':expireAt},ReturnValues:'ALL_NEW'});return this._toResult(res&&res.Attributes?res.Attributes:null);}if(points<0){return this._decrement(rlKey,points,expireAt);}const now=Date.now();try{const res=await this._send('UpdateItem',{TableName:this.tableName,Key:{key:rlKey},UpdateExpression:'SET #expire = if_not_exists(#expire, :expire) ADD #points :inc',ConditionExpression:'attribute_not_exists(#expire) OR #expire > :now',ExpressionAttributeNames:{'#points':'points','#expire':'expire'},ExpressionAttributeValues:{':expire':expireAt,':inc':points,':now':now},ReturnValues:'ALL_NEW'});return this._toResult(res&&res.Attributes?res.Attributes:null);}catch(err){if(err&&(err.name==='ConditionalCheckFailedException'||err.code==='ConditionalCheckFailedException'||typeof err.message==='string'&&err.message.includes('ConditionalCheckFailed'))){return this._upsert(rlKey,points,msDuration,true,_options);}throw err;}}async _decrement(rlKey,points,expireAt){const item=await this._getItem(rlKey);const currentPoints=item&&item.points!==null&&item.points!==undefined?Number(item.points):0;const res=await this._send('UpdateItem',{TableName:this.tableName,Key:{key:rlKey},UpdateExpression:'SET #points = :points, #expire = if_not_exists(#expire, :expire)',ExpressionAttributeNames:{'#points':'points','#expire':'expire'},ExpressionAttributeValues:{':points':Math.max(0,currentPoints+points),':expire':expireAt},ReturnValues:'ALL_NEW'});return this._toResult(res&&res.Attributes?res.Attributes:null);}async _getItem(rlKey){const res=await this._send('GetItem',{TableName:this.tableName,Key:{key:rlKey},ConsistentRead:true});return res&&res.Item?res.Item:null;}async _get(rlKey,_options={}){const item=await this._getItem(rlKey);if(!item){return null;}return this._toResult(item);}async _delete(rlKey,_options={}){const res=await this._send('DeleteItem',{TableName:this.tableName,Key:{key:rlKey},ReturnValues:'ALL_OLD'});return Boolean(res&&res.Attributes);}}export default DynamoDbLimiter;
@@ -0,0 +1 @@
1
+ import{RateLimiterStore}from'../rateLimiterStore.js';export class EtcdLimiter extends RateLimiterStore{constructor(opts={}){super(opts);this.client=opts.storeClient||opts.client||opts.etcd||null;if(!this.client){throw new Error('etcd client is not configured. Pass it via the "storeClient" option.');}this.maxRetries=opts.maxRetries!==undefined?opts.maxRetries:10;}async _call(method,args){const fn=this.client[method];if(typeof fn!=='function'){throw new Error(`etcd client does not support "${method}"`);}const result=fn.apply(this.client,args);if(result&&typeof result.then==='function'){return result;}if(result&&typeof result.string==='function'){return result.string();}return result;}async _read(rlKey){const raw=await this._call('get',[rlKey]);if(raw===null||raw===undefined||raw===false)return null;let kv=raw;if(kv&&typeof kv==='object'&&'kv'in kv&&kv.kv){kv=kv.kv;}let text=null;if(typeof kv==='string'){text=kv;}else if(kv&&typeof kv==='object'){text=kv.value!==undefined?kv.value:null;if(text&&typeof text!=='string'){text=String(text);}}if(!text)return null;let parsed;try{parsed=JSON.parse(text);}catch{return null;}if(!parsed||typeof parsed.points!=='number')return null;const revision=kv&&typeof kv==='object'&&kv.mod_revision!==undefined?String(kv.mod_revision):null;return{points:parsed.points,expireAt:typeof parsed.expireAt==='number'?parsed.expireAt:null,revision};}async _write(rlKey,value,msDuration){if(!(msDuration>0)){await this._call('put',[rlKey,value]);return;}const leaseId=await this._grantLease(Math.ceil(msDuration/1000));if(leaseId===null||leaseId===undefined){await this._call('put',[rlKey,value]);return;}await this._call('put',[rlKey,value,leaseId]);}async _grantLease(ttlSeconds){const leaseOwner=this.client.lease||this.client;const grant=leaseOwner.grant;if(typeof grant!=='function'){return null;}const lease=await grant.call(leaseOwner,ttlSeconds);if(lease===null||lease===undefined)return null;if(typeof lease==='number'||typeof lease==='string')return lease;if(lease.ID!==undefined)return lease.ID;if(lease.lease!==undefined)return lease.lease;return null;}async _upsert(rlKey,points,msDuration,forceExpire=false){const now=Date.now();const expireAt=msDuration>0?now+msDuration:null;for(let attempt=0;attempt<this.maxRetries;attempt++){const current=await this._read(rlKey);const isLive=current&&(current.expireAt===null||current.expireAt>now);let nextPoints;let nextExpireAt;if(forceExpire){nextPoints=points;nextExpireAt=expireAt;}else if(isLive){nextPoints=current.points+points;nextExpireAt=current.expireAt;}else{nextPoints=points;nextExpireAt=expireAt;}const committed=await this._compareAndSwap(rlKey,current,nextPoints,nextExpireAt,msDuration);if(committed){return{consumedPoints:nextPoints,expireAt:nextExpireAt};}}throw new Error('etcd: could not update the rate limit counter after several attempts');}async _compareAndSwap(rlKey,current,nextPoints,nextExpireAt,msDuration){const value=JSON.stringify({points:nextPoints,expireAt:nextExpireAt});if(typeof this.client.txn!=='function'){await this._write(rlKey,value,msDuration);return true;}const ttlMs=nextExpireAt!==null?Math.max(0,nextExpireAt-Date.now()):msDuration;const leaseId=ttlMs>0?await this._grantLease(Math.ceil(ttlMs/1000)):null;const txn=this.client.txn();if(!current||current.revision===null||current.revision===undefined){txn.if(this.client.modRevision(rlKey).isNaN());}else{txn.if(this.client.modRevision(rlKey).equals(current.revision));}const response=await txn.then(op=>{let putOp=op.put(rlKey,value);if(leaseId!==null&&leaseId!==undefined){if(typeof putOp.lease==='function'){putOp=putOp.lease(leaseId);}else if(typeof putOp.withLease==='function'){putOp=putOp.withLease(leaseId);}}return putOp;}).commit().catch(()=>null);if(response===null||response===undefined){return false;}if(typeof response.succeeded==='function'){return response.succeeded();}if(typeof response.succeeded==='boolean'){return response.succeeded;}return true;}async _get(rlKey){const current=await this._read(rlKey);if(!current)return null;if(current.expireAt!==null&&current.expireAt<=Date.now()){return null;}return{consumedPoints:current.points,expireAt:current.expireAt};}async _delete(rlKey){const current=await this._read(rlKey);await this._call('delete',[rlKey]).catch(()=>false);return Boolean(current);}}export default EtcdLimiter;
@@ -0,0 +1 @@
1
+ import{RateLimiterStore}from'../rateLimiterStore.js';export class MemcachedLimiter extends RateLimiterStore{constructor(opts={}){super(opts);this.client=opts.storeClient||opts.client||opts.memcached||null;if(!this.client){throw new Error('Memcached client is not configured. Pass it via the "storeClient" option.');}this._expiries=new Map();}_setExpiry(rlKey,expiresAt){if(this._expiries.size>999){const now=Date.now();for(const[key,exp]of this._expiries.entries()){if(exp<=now){this._expiries.delete(key);}}}this._expiries.set(rlKey,expiresAt);}_call(method,args){const fn=this.client[method];if(typeof fn!=='function'){return Promise.reject(new Error(`Memcached client does not support "${method}"`));}const result=fn.apply(this.client,args);if(result&&typeof result.then==='function'){return result;}return new Promise((resolve,reject)=>{fn.call(this.client,...args,(err,value)=>{if(err){reject(err);return;}resolve(value);});});}_toTtlSeconds(msDuration){if(!(msDuration>0))return 0;const seconds=Math.ceil(msDuration/1000);const THIRTY_DAYS=60*60*24*30;if(seconds<=THIRTY_DAYS){return seconds;}return Math.floor(Date.now()/1000)+seconds;}async _upsert(rlKey,points,msDuration,forceExpire=false){const now=Date.now();const ttl=this._toTtlSeconds(msDuration);if(forceExpire){await this._call('set',[rlKey,String(points),ttl]);if(msDuration>0){this._setExpiry(rlKey,now+msDuration);}else{this._expiries.delete(rlKey);}return{consumedPoints:points,expireAt:msDuration>0?now+msDuration:null};}if(points===0){const existing=await this._read(rlKey);if(existing)return existing;}if(points>0){const added=await this._call('add',[rlKey,'0',ttl]).catch(()=>false);if(added){if(msDuration>0){this._setExpiry(rlKey,now+msDuration);}const consumed=await this._call('incr',[rlKey,points]);const value=typeof consumed==='number'?consumed:points;return{consumedPoints:value,expireAt:this._expireAt(rlKey)};}}else{const existing=await this._read(rlKey);if(!existing){const added=await this._call('add',[rlKey,'0',ttl]).catch(()=>false);if(added&&msDuration>0){this._setExpiry(rlKey,now+msDuration);}return{consumedPoints:0,expireAt:this._expireAt(rlKey)};}const decremented=await this._call('decr',[rlKey,Math.abs(points)]);const value=typeof decremented==='number'?Math.max(0,decremented):0;return{consumedPoints:value,expireAt:this._expireAt(rlKey)};}const consumed=await this._call('incr',[rlKey,points]);const value=typeof consumed==='number'?consumed:points;return{consumedPoints:value,expireAt:this._expireAt(rlKey)};}_expireAt(rlKey){const expireAt=this._expiries.get(rlKey);if(expireAt===undefined)return null;return expireAt;}async _read(rlKey){const raw=await this._call('get',[rlKey]);if(raw===null||raw===undefined||raw===false)return null;const consumedPoints=Number(raw);if(!Number.isFinite(consumedPoints))return null;const expireAt=this._expireAt(rlKey);if(expireAt!==null&&expireAt<=Date.now()){await this._call('del',[rlKey]);this._expiries.delete(rlKey);return null;}return{consumedPoints,expireAt};}async _get(rlKey){return this._read(rlKey);}async _delete(rlKey){this._expiries.delete(rlKey);const existed=await this._read(rlKey);const deleted=await this._call('del',[rlKey]).catch(()=>false);return Boolean(existed||deleted);}clear(){this._expiries.clear();}}export default MemcachedLimiter;
@@ -0,0 +1 @@
1
+ import{RateLimiterStore}from'../rateLimiterStore.js';export class MemoryLimiter extends RateLimiterStore{constructor(opts={}){super(opts);this._storage=new Map();this._sweepIntervalMs=opts.sweepIntervalMs!==undefined?opts.sweepIntervalMs:0;this._maxEntries=opts.maxEntries!==undefined?opts.maxEntries:0;if(this._sweepIntervalMs>0){this._sweepTimer=setInterval(()=>this._sweep(),this._sweepIntervalMs);if(this._sweepTimer.unref){this._sweepTimer.unref();}}}_sweep(){const now=Date.now();for(const[key,record]of this._storage){if(record.expireAt!==null&&record.expireAt<=now){this._storage.delete(key);}}}async _upsert(rlKey,points,msDuration,forceExpire=false){const now=Date.now();const existing=this._storage.get(rlKey);if(!forceExpire&&existing&&(existing.expireAt===null||existing.expireAt>now)){existing.consumedPoints+=points;return existing;}const record={consumedPoints:points,expireAt:msDuration>0?now+msDuration:null};if(this._maxEntries>0&&this._storage.size>=this._maxEntries){this._evictOldest();}this._storage.set(rlKey,record);return record;}_evictOldest(){const now=Date.now();for(const[key,record]of this._storage){if(record.expireAt!==null&&record.expireAt<=now){this._storage.delete(key);}}if(this._storage.size<this._maxEntries){return;}for(const key of this._storage.keys()){this._storage.delete(key);break;}}async _get(rlKey){const record=this._storage.get(rlKey);if(!record)return null;if(record.expireAt!==null&&record.expireAt<=Date.now()){this._storage.delete(rlKey);return null;}return record;}async _delete(rlKey){return this._storage.delete(rlKey);}async clear(){this._storage.clear();}dump(){const now=Date.now();const storage=[];for(const[key,record]of this._storage){if(record.expireAt===null||record.expireAt>now){storage.push({key,value:record.consumedPoints,expiresAt:record.expireAt});}}return{version:1,dumpedAt:now,storage};}restore(dumpData,detailResponse=false){const result={invalid:0,expired:0,restored:0};const detailed={invalid:[],expired:[],restored:[]};if(!dumpData||!Array.isArray(dumpData.storage)){return detailResponse?detailed:result;}const now=Date.now();for(const item of dumpData.storage){if(!item||typeof item.key!=='string'||typeof item.value!=='number'||!Number.isFinite(item.value)||item.expiresAt!==null&&item.expiresAt!==undefined&&(typeof item.expiresAt!=='number'||!Number.isFinite(item.expiresAt))){result.invalid++;detailed.invalid.push(item?.key??String(item));continue;}if(item.expiresAt!==null&&item.expiresAt!==undefined&&item.expiresAt<=now){result.expired++;detailed.expired.push(item.key);continue;}this._storage.set(item.key,{consumedPoints:item.value,expireAt:item.expiresAt??null});result.restored++;detailed.restored.push(item.key);}return detailResponse?detailed:result;}async close(){if(this._sweepTimer){clearInterval(this._sweepTimer);this._sweepTimer=null;}this._storage.clear();}}export default MemoryLimiter;
@@ -0,0 +1 @@
1
+ import{RateLimiterStore}from'../rateLimiterStore.js';import{ConfigurationError}from'../../utils/errors.js';const DEFAULT_DB_NAME='express-adaptive-rate-limiter';const DEFAULT_TABLE_NAME='rate-limiter';const MAX_DUPLICATE_KEY_RETRIES=3;export class MongoLimiter extends RateLimiterStore{constructor(opts={}){super(opts);this.client=opts.storeClient||opts.mongo||opts.client||null;if(!this.client){throw new ConfigurationError('Mongo client is not configured. Pass it via the "storeClient" option.');}this.dbName=opts.dbName!==undefined?opts.dbName:DEFAULT_DB_NAME;this.tableName=opts.tableName!==undefined?opts.tableName:DEFAULT_TABLE_NAME;this.disableIndexesCreation=Boolean(opts.disableIndexesCreation);this.clearExpiredByTimeout=opts.clearExpiredByTimeout!==undefined?Boolean(opts.clearExpiredByTimeout):false;this.clearExpiredTimeoutMs=opts.clearExpiredTimeoutMs!==undefined?Number(opts.clearExpiredTimeoutMs):300000;this._clearExpiredTimer=null;if(this.clearExpiredByTimeout&&this.clearExpiredTimeoutMs>0){this._clearExpiredTimer=setInterval(()=>{this.clearExpired().catch(()=>{});},this.clearExpiredTimeoutMs);if(this._clearExpiredTimer.unref){this._clearExpiredTimer.unref();}}this._collection=this._resolveCollection();this._indexesPromise=this._createIndexes();}_resolveCollection(){const db=typeof this.client.db==='function'?this.client.db(this.dbName):this.client;if(!db||typeof db.collection!=='function'){throw new ConfigurationError('Mongo client must expose "db(name)" or be an already resolved database instance.');}return db.collection(this.tableName);}async _createIndexes(){if(this.disableIndexesCreation){return;}try{await this._collection.createIndex({expire:1},{expireAfterSeconds:0});await this._collection.createIndex({key:1},{unique:true});}catch{}}_isDuplicateKeyError(err){return Boolean(err&&(err.code===11000||err.code===11001));}_extractDocument(res){if(!res){return null;}if('value'in res){return res.value;}if(Array.isArray(res.ops)&&res.ops.length>0){return res.ops[0];}return res;}_toResult(doc){if(!doc){return{consumedPoints:0,expireAt:null};}const expire=doc.expire;return{consumedPoints:Number(doc.points)||0,expireAt:expire===null||expire===undefined?null:new Date(expire).getTime()};}async _upsert(rlKey,points,msDuration,forceExpire=false,_options={}){await this._indexesPromise;if(forceExpire){const res=await this._collection.findOneAndUpdate({key:rlKey},{$set:{key:rlKey,points,expire:this._expireDate(msDuration)}},{upsert:true,returnDocument:'after'});return this._toResult(this._extractDocument(res));}return this._increment(rlKey,points,msDuration,0);}async _increment(rlKey,points,msDuration,attempt){const now=Date.now();try{const res=await this._collection.findOneAndUpdate({key:rlKey,$or:[{expire:{$gt:new Date(now)}},{expire:{$eq:null}}]},{$setOnInsert:{key:rlKey,expire:this._expireDate(msDuration,now)},$inc:{points}},{upsert:true,returnDocument:'after'});return this._toResult(this._extractDocument(res));}catch(err){if(!this._isDuplicateKeyError(err)||attempt>=MAX_DUPLICATE_KEY_RETRIES){throw err;}return this._replaceExpired(rlKey,points,msDuration,attempt+1);}}async _replaceExpired(rlKey,points,msDuration,attempt){try{const res=await this._collection.findOneAndUpdate({key:rlKey,$or:[{expire:{$lte:new Date()}},{expire:{$eq:null}}]},{$set:{key:rlKey,points,expire:this._expireDate(msDuration)}},{upsert:true,returnDocument:'after'});return this._toResult(this._extractDocument(res));}catch(err){if(!this._isDuplicateKeyError(err)||attempt>=MAX_DUPLICATE_KEY_RETRIES){throw err;}return this._increment(rlKey,points,msDuration,attempt);}}_expireDate(msDuration,now=Date.now()){return msDuration>0?new Date(now+msDuration):null;}async _get(rlKey,_options={}){await this._indexesPromise;const doc=await this._collection.findOne({key:rlKey,$or:[{expire:{$gt:new Date()}},{expire:{$eq:null}}]});if(!doc){return null;}return this._toResult(doc);}async _delete(rlKey,_options={}){await this._indexesPromise;const res=await this._collection.deleteOne({key:rlKey});return this._extractDeletedCount(res)>0;}_extractDeletedCount(res){if(!res){return 0;}if(typeof res.deletedCount==='number'){return res.deletedCount;}if(res.result&&typeof res.result.n==='number'){return res.result.n;}return 0;}async clearExpired(){await this._indexesPromise;await this._collection.deleteMany({expire:{$lte:new Date(),$ne:null}});}async close(){if(this._clearExpiredTimer){clearInterval(this._clearExpiredTimer);this._clearExpiredTimer=null;}}}export default MongoLimiter;
@@ -0,0 +1 @@
1
+ import{RateLimiterStore}from'../rateLimiterStore.js';import{ConfigurationError}from'../../utils/errors.js';const DEFAULT_MODEL_NAME='RateLimit';const MAX_DUPLICATE_KEY_RETRIES=3;export class MongooseLimiter extends RateLimiterStore{constructor(opts={}){super(opts);this.modelName=opts.modelName!==undefined?opts.modelName:DEFAULT_MODEL_NAME;this.disableIndexesCreation=Boolean(opts.disableIndexesCreation);this.model=opts.model||this._resolveModel(opts);if(!this.model){throw new ConfigurationError('Mongoose model is not configured. Pass it via the "model" or the "storeClient" option.');}this._indexesPromise=this._createIndexes();}_resolveModel(opts){const mongoose=opts.storeClient||opts.mongoose||opts.client||null;if(!mongoose){return null;}const connection=mongoose.connection||mongoose;if(!connection||typeof connection.model!=='function'){return null;}const registry=connection.models||mongoose.models||{};if(registry[this.modelName]){return registry[this.modelName];}try{if(opts.schema){return connection.model(this.modelName,opts.schema);}return connection.model(this.modelName);}catch(err){throw new ConfigurationError(`Unable to resolve the Mongoose model "${this.modelName}": ${err.message}`);}}async _createIndexes(){if(this.disableIndexesCreation){return;}const schema=this.model.schema;if(!schema||typeof schema.index!=='function'){return;}try{schema.index({expire:1},{expireAfterSeconds:0});if(typeof this.model.createIndexes==='function'){await this.model.createIndexes();}else if(typeof this.model.ensureIndexes==='function'){await this.model.ensureIndexes();}}catch{}}_isDuplicateKeyError(err){return Boolean(err&&(err.code===11000||err.code===11001));}_toResult(doc){if(!doc){return{consumedPoints:0,expireAt:null};}const expire=doc.expire;return{consumedPoints:Number(doc.points)||0,expireAt:expire===null||expire===undefined?null:new Date(expire).getTime()};}async _upsert(rlKey,points,msDuration,forceExpire=false,_options={}){await this._indexesPromise;if(forceExpire){const doc=await this.model.findOneAndUpdate({key:rlKey},{$set:{key:rlKey,points,expire:this._expireDate(msDuration)}},{upsert:true,new:true});return this._toResult(doc);}return this._increment(rlKey,points,msDuration,0);}async _increment(rlKey,points,msDuration,attempt){const now=Date.now();try{const doc=await this.model.findOneAndUpdate({key:rlKey,$or:[{expire:{$gt:new Date(now)}},{expire:{$eq:null}}]},{$setOnInsert:{key:rlKey,expire:this._expireDate(msDuration,now)},$inc:{points}},{upsert:true,new:true});return this._toResult(doc);}catch(err){if(!this._isDuplicateKeyError(err)||attempt>=MAX_DUPLICATE_KEY_RETRIES){throw err;}return this._replaceExpired(rlKey,points,msDuration,attempt+1);}}async _replaceExpired(rlKey,points,msDuration,attempt){try{const doc=await this.model.findOneAndUpdate({key:rlKey,$or:[{expire:{$lte:new Date()}},{expire:{$eq:null}}]},{$set:{key:rlKey,points,expire:this._expireDate(msDuration)}},{upsert:true,new:true});return this._toResult(doc);}catch(err){if(!this._isDuplicateKeyError(err)||attempt>=MAX_DUPLICATE_KEY_RETRIES){throw err;}return this._increment(rlKey,points,msDuration,attempt);}}_expireDate(msDuration,now=Date.now()){return msDuration>0?new Date(now+msDuration):null;}async _get(rlKey,_options={}){await this._indexesPromise;const doc=await this.model.findOne({key:rlKey,$or:[{expire:{$gt:new Date()}},{expire:{$eq:null}}]});if(!doc){return null;}return this._toResult(doc);}async _delete(rlKey,_options={}){await this._indexesPromise;const res=await this.model.deleteOne({key:rlKey});return this._extractDeletedCount(res)>0;}_extractDeletedCount(res){if(!res){return 0;}if(typeof res.deletedCount==='number'){return res.deletedCount;}if(res.result&&typeof res.result.n==='number'){return res.result.n;}return 0;}}export default MongooseLimiter;
@@ -0,0 +1,12 @@
1
+ import{RateLimiterStore}from'../rateLimiterStore.js';import{ConfigurationError}from'../../utils/errors.js';const DEFAULT_TABLE_NAME='rate_limiter';const TABLE_NAME_PATTERN=/^[A-Za-z_][A-Za-z0-9_$]*(\.[A-Za-z_][A-Za-z0-9_$]*)?$/;export class MySqlLimiter extends RateLimiterStore{constructor(opts={}){super(opts);this.storeClient=opts.storeClient||opts.client||null;if(!this.storeClient){throw new ConfigurationError('MySQL client is not configured. Pass it via the "storeClient" option.');}this.tableName=opts.tableName!==undefined?opts.tableName:DEFAULT_TABLE_NAME;if(typeof this.tableName!=='string'||!TABLE_NAME_PATTERN.test(this.tableName)){throw new ConfigurationError('tableName must be a plain SQL identifier, optionally prefixed with a schema name');}this.tableCreated=opts.tableCreated!==undefined?Boolean(opts.tableCreated):false;this.clearExpiredByTimeout=opts.clearExpiredByTimeout!==undefined?Boolean(opts.clearExpiredByTimeout):false;this.clearExpiredTimeoutMs=opts.clearExpiredTimeoutMs!==undefined?Number(opts.clearExpiredTimeoutMs):300000;this._clearExpiredTimer=null;if(this.clearExpiredByTimeout&&this.clearExpiredTimeoutMs>0){this._clearExpiredTimer=setInterval(()=>{this.clearExpired().catch(()=>{});},this.clearExpiredTimeoutMs);if(this._clearExpiredTimer.unref){this._clearExpiredTimer.unref();}}if(!this.tableCreated){this.createTable().catch(()=>undefined);}}_quotedTable(){return this.tableName.split('.').map(part=>`\`${part}\``).join('.');}_createTableSql(){return`CREATE TABLE IF NOT EXISTS ${this._quotedTable()} (
2
+ \`key\` VARCHAR(255) NOT NULL,
3
+ \`points\` BIGINT NOT NULL DEFAULT 0,
4
+ \`expire\` BIGINT DEFAULT NULL,
5
+ PRIMARY KEY (\`key\`)
6
+ )`;}_incrementSql(){return`INSERT INTO ${this._quotedTable()} (\`key\`, \`points\`, \`expire\`) VALUES (?, ?, ?)
7
+ ON DUPLICATE KEY UPDATE
8
+ \`points\` = IF(\`expire\` IS NULL OR \`expire\` > ?, \`points\` + VALUES(points), VALUES(points)),
9
+ \`expire\` = IF(\`expire\` IS NULL OR \`expire\` > ?, \`expire\`, VALUES(expire))`;}_forceExpireSql(){return`INSERT INTO ${this._quotedTable()} (\`key\`, \`points\`, \`expire\`) VALUES (?, ?, ?)
10
+ ON DUPLICATE KEY UPDATE
11
+ \`points\` = VALUES(points),
12
+ \`expire\` = VALUES(expire)`;}_selectSql(){return`SELECT \`points\`, \`expire\` FROM ${this._quotedTable()} WHERE \`key\` = ? AND (\`expire\` IS NULL OR \`expire\` > ?)`;}_selectRowSql(){return`SELECT \`points\`, \`expire\` FROM ${this._quotedTable()} WHERE \`key\` = ?`;}async _query(sql,params=[]){const client=this.storeClient;if(typeof client.query==='function'){return this._unwrap(await client.query(sql,params));}if(typeof client.execute==='function'){return this._unwrap(await client.execute(sql,params));}throw new ConfigurationError('MySQL client must expose a query() or execute() method');}_unwrap(result){if(Array.isArray(result)&&result.length===2&&(result[1]===undefined||result[1]===null||Array.isArray(result[1]))){return result[0];}return result;}async _queryRow(sql,params=[]){const result=await this._query(sql,params);if(Array.isArray(result)&&result.length>0){return result[0];}return null;}_toResult(row){if(!row){return{consumedPoints:0,expireAt:null};}const expireAt=row.expire===null||row.expire===undefined?null:Number(row.expire);return{consumedPoints:Number(row.points),expireAt:Number.isFinite(expireAt)?expireAt:null};}async _upsert(rlKey,points,msDuration,forceExpire=false){if(!Number.isInteger(points)){throw new Error('Consuming decimal number of points is not supported by this package');}const now=Date.now();const expire=msDuration>0?now+msDuration:null;if(forceExpire){await this._query(this._forceExpireSql(),[rlKey,points,expire]);return{consumedPoints:points,expireAt:expire};}await this._query(this._incrementSql(),[rlKey,points,expire,now,now]);const row=await this._queryRow(this._selectRowSql(),[rlKey]);return this._toResult(row);}async _get(rlKey){const row=await this._queryRow(this._selectSql(),[rlKey,Date.now()]);if(!row){return null;}return this._toResult(row);}async _delete(rlKey){const result=await this._query(`DELETE FROM ${this._quotedTable()} WHERE \`key\` = ?`,[rlKey]);const affectedRows=result&&typeof result.affectedRows==='number'?result.affectedRows:0;return affectedRows>0;}createTable(){try{return Promise.resolve(this._query(this._createTableSql(),[]));}catch(err){return Promise.reject(err);}}async clear(){await this._query(`DELETE FROM ${this._quotedTable()}`,[]);}async clearExpired(){const now=Date.now();await this._query(`DELETE FROM ${this._quotedTable()} WHERE \`expire\` IS NOT NULL AND \`expire\` <= ?`,[now]);}async close(){if(this._clearExpiredTimer){clearInterval(this._clearExpiredTimer);this._clearExpiredTimer=null;}}}export default MySqlLimiter;
@@ -0,0 +1 @@
1
+ import cluster from'node:cluster';import{RateLimiter,isRateLimiterCompatible}from'../rateLimiter.js';import{RateLimiterRes}from'../rateLimiterRes.js';import{MemoryLimiter}from'./memoryLimiter.js';const IPC_CHANNEL='rate_limiter_flexible';const ALLOWED_IPC_METHODS=new Set(['consume','penalty','reward','block','set','get','delete']);export class RateLimiterClusterMaster{constructor(targetCluster=cluster){if(RateLimiterClusterMaster.instance&&(!targetCluster||RateLimiterClusterMaster.instance.cluster===targetCluster)){return RateLimiterClusterMaster.instance;}if(RateLimiterClusterMaster.instance){RateLimiterClusterMaster.instance.close();}RateLimiterClusterMaster.instance=this;this.cluster=targetCluster;this._limiters=new Map();this._onMessage=(worker,message)=>{try{if(!message||typeof message!=='object'||message.channel!==IPC_CHANNEL){return;}this._handleMessage(worker,message).catch(()=>{});}catch{}};if(this.cluster&&typeof this.cluster.on==='function'){this.cluster.on('message',this._onMessage);}}_getLimiter(limiterOptions={}){const rawPrefix=limiterOptions.keyPrefix||'rl:';const normalizedPrefix=rawPrefix.endsWith(':')?rawPrefix:`${rawPrefix}:`;let limiter=this._limiters.get(normalizedPrefix);if(!limiter){limiter=new MemoryLimiter({...limiterOptions,keyPrefix:normalizedPrefix});this._limiters.set(normalizedPrefix,limiter);}return limiter;}async _handleMessage(worker,message){try{if(!message||typeof message!=='object'){return;}const{id,method,args,limiterOptions}=message;if(!id){return;}if(typeof method!=='string'||!ALLOWED_IPC_METHODS.has(method)){if(worker&&typeof worker.send==='function'){try{worker.send({channel:IPC_CHANNEL,id,resolved:false,data:{message:`Method "${method}" is not allowed`}});}catch{}}return;}const limiter=this._getLimiter(limiterOptions);if(typeof limiter[method]!=='function'){if(worker&&typeof worker.send==='function'){try{worker.send({channel:IPC_CHANNEL,id,resolved:false,data:{message:`Method "${method}" is not supported`}});}catch{}}return;}const safeArgs=Array.isArray(args)?args:[];let res;try{res=await limiter[method](...safeArgs);}catch(err){if(worker&&typeof worker.send==='function'){try{worker.send({channel:IPC_CHANNEL,id,resolved:false,data:err instanceof RateLimiterRes?err.toJSON():err?.message||String(err)});}catch{}}return;}if(worker&&typeof worker.send==='function'){try{worker.send({channel:IPC_CHANNEL,id,resolved:true,data:res instanceof RateLimiterRes?res.toJSON():res});}catch{}}}catch{}}close(){if(this.cluster&&typeof this.cluster.removeListener==='function'){this.cluster.removeListener('message',this._onMessage);}else if(this.cluster&&typeof this.cluster.off==='function'){this.cluster.off('message',this._onMessage);}if(RateLimiterClusterMaster.instance===this){RateLimiterClusterMaster.instance=null;}this._limiters.clear();}}export class RateLimiterClusterMasterPM2{constructor(pm2){this.pm2=pm2;this._limiters=new Map();this._onMessage=packet=>{try{if(!packet||!packet.data||packet.data.channel!==IPC_CHANNEL){return;}this._handlePacket(packet).catch(()=>{});}catch{}};if(this.pm2&&typeof this.pm2.launchBus==='function'){this.pm2.launchBus((err,bus)=>{if(!err&&bus){this._bus=bus;bus.on('process:msg',this._onMessage);}});}else if(typeof process!=='undefined'&&typeof process.on==='function'){this._onProcessMessage=msg=>{try{if(msg&&msg.channel===IPC_CHANNEL){this._handlePacket({data:msg,process:{pm_id:msg.processId}}).catch(()=>{});}}catch{}};process.on('message',this._onProcessMessage);}}_getLimiter(limiterOptions={}){const rawPrefix=limiterOptions.keyPrefix||'rl:';const normalizedPrefix=rawPrefix.endsWith(':')?rawPrefix:`${rawPrefix}:`;let limiter=this._limiters.get(normalizedPrefix);if(!limiter){limiter=new MemoryLimiter({...limiterOptions,keyPrefix:normalizedPrefix});this._limiters.set(normalizedPrefix,limiter);}return limiter;}async _handlePacket(packet){try{if(!packet||!packet.data||typeof packet.data!=='object'){return;}const{id,method,args,limiterOptions}=packet.data;if(!id){return;}if(typeof method!=='string'||!ALLOWED_IPC_METHODS.has(method)){this._reply(packet,{channel:IPC_CHANNEL,id,resolved:false,data:{message:`Method "${method}" is not allowed`}});return;}const limiter=this._getLimiter(limiterOptions);if(typeof limiter[method]!=='function'){this._reply(packet,{channel:IPC_CHANNEL,id,resolved:false,data:{message:`Method "${method}" is not supported`}});return;}const safeArgs=Array.isArray(args)?args:[];let res;try{res=await limiter[method](...safeArgs);}catch(err){this._reply(packet,{channel:IPC_CHANNEL,id,resolved:false,data:err instanceof RateLimiterRes?err.toJSON():err?.message||String(err)});return;}this._reply(packet,{channel:IPC_CHANNEL,id,resolved:true,data:res instanceof RateLimiterRes?res.toJSON():res});}catch{}}_reply(packet,payload){try{if(this.pm2&&typeof this.pm2.sendDataToProcessId==='function'&&packet.process?.pm_id!==undefined){this.pm2.sendDataToProcessId(packet.process.pm_id,payload,()=>{});}else if(typeof process!=='undefined'&&typeof process.send==='function'){process.send(payload);}}catch{}}close(){if(this._bus&&typeof this._bus.removeListener==='function'){this._bus.removeListener('process:msg',this._onMessage);}if(this._onProcessMessage&&typeof process!=='undefined'&&typeof process.removeListener==='function'){process.removeListener('message',this._onProcessMessage);this._onProcessMessage=null;}this._limiters.clear();}}export class RateLimiterCluster extends RateLimiter{constructor(opts={}){super(opts);this.timeoutMs=opts.timeoutMs!==undefined?Number(opts.timeoutMs):5000;this._process=opts.customProcess||(typeof process!=='undefined'?process:null);this._requests=new Map();this._reqId=0;this._onMessage=message=>{if(!message||message.channel!==IPC_CHANNEL||!message.id){return;}const req=this._requests.get(message.id);if(!req)return;this._requests.delete(message.id);if(req.timer){clearTimeout(req.timer);}if(message.resolved){const data=message.data;const res=data&&typeof data==='object'&&('remainingPoints'in data||'consumedPoints'in data)?new RateLimiterRes(data.remainingPoints,data.msBeforeNext,data.consumedPoints,data.isFirstInDuration):data;req.resolve(res);}else{const data=message.data;const err=data&&typeof data==='object'&&('remainingPoints'in data||'consumedPoints'in data)?new RateLimiterRes(data.remainingPoints,data.msBeforeNext,data.consumedPoints,data.isFirstInDuration):typeof data==='string'?new Error(data):data;req.reject(err);}};if(this._process&&typeof this._process.on==='function'){this._process.on('message',this._onMessage);}}_send(method,args){if(!this._process||typeof this._process.send!=='function'){if(isRateLimiterCompatible(this.insuranceLimiter)){return this.insuranceLimiter[method](...args);}return Promise.reject(new Error('Process does not support IPC send or master process is not available'));}const id=++this._reqId;const limiterOptions={points:this.points,duration:this.duration,blockDuration:this.blockDuration,execEvenly:this.execEvenly,execEvenlyMinDelayMs:this.execEvenlyMinDelayMs,keyPrefix:this.keyPrefix};return new Promise((resolve,reject)=>{let timer=null;if(this.timeoutMs>0){timer=setTimeout(()=>{this._requests.delete(id);if(isRateLimiterCompatible(this.insuranceLimiter)){this.insuranceLimiter[method](...args).then(resolve).catch(reject);}else{reject(new Error(`RateLimiterCluster timeout after ${this.timeoutMs}ms`));}},this.timeoutMs);if(timer.unref){timer.unref();}}this._requests.set(id,{resolve,reject,timer});this._process.send({channel:IPC_CHANNEL,id,method,args,limiterOptions});});}consume(key,points=1,options={}){return this._send('consume',[key,points,options]);}penalty(key,points=1,options={}){return this._send('penalty',[key,points,options]);}reward(key,points=1,options={}){return this._send('reward',[key,points,options]);}get(key,options={}){return this._send('get',[key,options]);}set(key,points,secDuration,options={}){return this._send('set',[key,points,secDuration,options]);}block(key,secDuration,options={}){return this._send('block',[key,secDuration,options]);}delete(key,options={}){return this._send('delete',[key,options]);}close(){if(this._process&&typeof this._process.removeListener==='function'){this._process.removeListener('message',this._onMessage);}for(const[,req]of this._requests){if(req.timer)clearTimeout(req.timer);}this._requests.clear();}}export default RateLimiterCluster;
@@ -0,0 +1,13 @@
1
+ import{RateLimiterStore}from'../rateLimiterStore.js';import{ConfigurationError}from'../../utils/errors.js';const DEFAULT_TABLE_NAME='rate_limiter';const TABLE_NAME_PATTERN=/^[A-Za-z_][A-Za-z0-9_$]*(\.[A-Za-z_][A-Za-z0-9_$]*)?$/;export class PostgresLimiter extends RateLimiterStore{constructor(opts={}){super(opts);this.storeClient=opts.storeClient||opts.client||null;if(!this.storeClient){throw new ConfigurationError('PostgreSQL client is not configured. Pass it via the "storeClient" option.');}this.tableName=opts.tableName!==undefined?opts.tableName:DEFAULT_TABLE_NAME;if(typeof this.tableName!=='string'||!TABLE_NAME_PATTERN.test(this.tableName)){throw new ConfigurationError('tableName must be a plain SQL identifier, optionally prefixed with a schema name');}this.tableCreated=opts.tableCreated!==undefined?Boolean(opts.tableCreated):false;this.clearExpiredByTimeout=opts.clearExpiredByTimeout!==undefined?Boolean(opts.clearExpiredByTimeout):false;this.clearExpiredTimeoutMs=opts.clearExpiredTimeoutMs!==undefined?Number(opts.clearExpiredTimeoutMs):300000;this._clearExpiredTimer=null;if(this.clearExpiredByTimeout&&this.clearExpiredTimeoutMs>0){this._clearExpiredTimer=setInterval(()=>{this.clearExpired().catch(()=>{});},this.clearExpiredTimeoutMs);if(this._clearExpiredTimer.unref){this._clearExpiredTimer.unref();}}if(!this.tableCreated){this.createTable().catch(()=>undefined);}}_quotedTable(){return this.tableName.split('.').map(part=>`"${part}"`).join('.');}_createTableSql(){return`CREATE TABLE IF NOT EXISTS ${this._quotedTable()} (
2
+ "key" TEXT PRIMARY KEY,
3
+ "points" BIGINT NOT NULL DEFAULT 0,
4
+ "expire" BIGINT DEFAULT NULL
5
+ )`;}_incrementSql(){const table=this._quotedTable();return`INSERT INTO ${table} ("key", "points", "expire") VALUES ($1, $2, $3)
6
+ ON CONFLICT ("key") DO UPDATE SET
7
+ "points" = CASE WHEN ${table}."expire" IS NULL OR ${table}."expire" > $4 THEN ${table}."points" + EXCLUDED.points ELSE EXCLUDED.points END,
8
+ "expire" = CASE WHEN ${table}."expire" IS NULL OR ${table}."expire" > $4 THEN ${table}."expire" ELSE EXCLUDED."expire" END
9
+ RETURNING "points", "expire"`;}_forceExpireSql(){return`INSERT INTO ${this._quotedTable()} ("key", "points", "expire") VALUES ($1, $2, $3)
10
+ ON CONFLICT ("key") DO UPDATE SET
11
+ "points" = EXCLUDED.points,
12
+ "expire" = EXCLUDED."expire"
13
+ RETURNING "points", "expire"`;}_selectSql(){return`SELECT "points", "expire" FROM ${this._quotedTable()} WHERE "key" = $1 AND ("expire" IS NULL OR "expire" > $2)`;}_deleteSql(){return`DELETE FROM ${this._quotedTable()} WHERE "key" = $1`;}async _query(sql,params=[]){const client=this.storeClient;if(typeof client.query!=='function'){throw new ConfigurationError('PostgreSQL client must expose a query() method');}return client.query(sql,params);}_toResult(row){if(!row){return{consumedPoints:0,expireAt:null};}const expireAt=row.expire===null||row.expire===undefined?null:Number(row.expire);return{consumedPoints:Number(row.points),expireAt:Number.isFinite(expireAt)?expireAt:null};}async _upsert(rlKey,points,msDuration,forceExpire=false){if(!Number.isInteger(points)){throw new Error('Consuming decimal number of points is not supported by this package');}const now=Date.now();const expire=msDuration>0?now+msDuration:null;const sql=forceExpire?this._forceExpireSql():this._incrementSql();const params=forceExpire?[rlKey,points,expire]:[rlKey,points,expire,now,now];const result=await this._query(sql,params);const rows=result&&Array.isArray(result.rows)?result.rows:[];const row=rows.length>0?rows[0]:null;return this._toResult(row);}async _get(rlKey){const result=await this._query(this._selectSql(),[rlKey,Date.now()]);const rows=result&&Array.isArray(result.rows)?result.rows:[];if(rows.length===0){return null;}return this._toResult(rows[0]);}async _delete(rlKey){const result=await this._query(this._deleteSql(),[rlKey]);const rowCount=result&&typeof result.rowCount==='number'?result.rowCount:0;return rowCount>0;}createTable(){try{return Promise.resolve(this._query(this._createTableSql(),[]));}catch(err){return Promise.reject(err);}}async clear(){await this._query(`DELETE FROM ${this._quotedTable()}`,[]);}async clearExpired(){const now=Date.now();await this._query(`DELETE FROM ${this._quotedTable()} WHERE "expire" IS NOT NULL AND "expire" <= $1`,[now]);}async close(){if(this._clearExpiredTimer){clearInterval(this._clearExpiredTimer);this._clearExpiredTimer=null;}}}export default PostgresLimiter;
@@ -0,0 +1,24 @@
1
+ import{RateLimiterStore}from'../rateLimiterStore.js';import{ConfigurationError}from'../../utils/errors.js';const DEFAULT_TABLE_NAME='rate_limiter';const TABLE_NAME_PATTERN=/^[A-Za-z_][A-Za-z0-9_$]*(\.[A-Za-z_][A-Za-z0-9_$]*)?$/;const SUPPORTED_DIALECTS=['postgres','postgresql','mysql'];export class PrismaLimiter extends RateLimiterStore{constructor(opts={}){super(opts);this.storeClient=opts.storeClient||opts.client||opts.prisma||null;if(!this.storeClient){throw new ConfigurationError('Prisma client is not configured. Pass it via the "storeClient" option.');}if(typeof this.storeClient.$queryRawUnsafe!=='function'){throw new ConfigurationError('Prisma client must expose a $queryRawUnsafe() method');}this.dialect=opts.dialect!==undefined?String(opts.dialect).toLowerCase():'postgres';if(!SUPPORTED_DIALECTS.includes(this.dialect)){throw new ConfigurationError('dialect must be "postgres" or "mysql"');}if(this.dialect==='postgresql'){this.dialect='postgres';}this.tableName=opts.tableName!==undefined?opts.tableName:DEFAULT_TABLE_NAME;if(typeof this.tableName!=='string'||!TABLE_NAME_PATTERN.test(this.tableName)){throw new ConfigurationError('tableName must be a plain SQL identifier, optionally prefixed with a schema name');}this.tableCreated=opts.tableCreated!==undefined?Boolean(opts.tableCreated):false;if(!this.tableCreated){this.createTable().catch(()=>undefined);}}get _isMysql(){return this.dialect==='mysql';}_quotedTable(){const quote=this._isMysql?'`':'"';return this.tableName.split('.').map(part=>`${quote}${part}${quote}`).join('.');}_createTableSql(){if(this._isMysql){return`CREATE TABLE IF NOT EXISTS ${this._quotedTable()} (
2
+ \`key\` VARCHAR(255) NOT NULL,
3
+ \`points\` BIGINT NOT NULL DEFAULT 0,
4
+ \`expire\` BIGINT DEFAULT NULL,
5
+ PRIMARY KEY (\`key\`)
6
+ )`;}return`CREATE TABLE IF NOT EXISTS ${this._quotedTable()} (
7
+ "key" TEXT PRIMARY KEY,
8
+ "points" BIGINT NOT NULL DEFAULT 0,
9
+ "expire" BIGINT DEFAULT NULL
10
+ )`;}_incrementSql(){if(this._isMysql){return`INSERT INTO ${this._quotedTable()} (\`key\`, \`points\`, \`expire\`) VALUES (?, ?, ?)
11
+ ON DUPLICATE KEY UPDATE
12
+ \`points\` = IF(\`expire\` IS NULL OR \`expire\` > ?, \`points\` + VALUES(points), VALUES(points)),
13
+ \`expire\` = IF(\`expire\` IS NULL OR \`expire\` > ?, \`expire\`, VALUES(expire))`;}const table=this._quotedTable();return`INSERT INTO ${table} ("key", "points", "expire") VALUES ($1, $2, $3)
14
+ ON CONFLICT ("key") DO UPDATE SET
15
+ "points" = CASE WHEN ${table}."expire" IS NULL OR ${table}."expire" > $4 THEN ${table}."points" + EXCLUDED.points ELSE EXCLUDED.points END,
16
+ "expire" = CASE WHEN ${table}."expire" IS NULL OR ${table}."expire" > $4 THEN ${table}."expire" ELSE EXCLUDED."expire" END
17
+ RETURNING "points", "expire"`;}_forceExpireSql(){if(this._isMysql){return`INSERT INTO ${this._quotedTable()} (\`key\`, \`points\`, \`expire\`) VALUES (?, ?, ?)
18
+ ON DUPLICATE KEY UPDATE
19
+ \`points\` = VALUES(points),
20
+ \`expire\` = VALUES(expire)`;}return`INSERT INTO ${this._quotedTable()} ("key", "points", "expire") VALUES ($1, $2, $3)
21
+ ON CONFLICT ("key") DO UPDATE SET
22
+ "points" = EXCLUDED.points,
23
+ "expire" = EXCLUDED."expire"
24
+ RETURNING "points", "expire"`;}_selectSql(){if(this._isMysql){return`SELECT \`points\`, \`expire\` FROM ${this._quotedTable()} WHERE \`key\` = ? AND (\`expire\` IS NULL OR \`expire\` > ?)`;}return`SELECT "points", "expire" FROM ${this._quotedTable()} WHERE "key" = $1 AND ("expire" IS NULL OR "expire" > $2)`;}_selectRowSql(){if(this._isMysql){return`SELECT \`points\`, \`expire\` FROM ${this._quotedTable()} WHERE \`key\` = ?`;}return`SELECT "points", "expire" FROM ${this._quotedTable()} WHERE "key" = $1`;}_deleteSql(){if(this._isMysql){return`DELETE FROM ${this._quotedTable()} WHERE \`key\` = ?`;}return`DELETE FROM ${this._quotedTable()} WHERE "key" = $1`;}async _queryRows(sql,params=[]){const rows=await this.storeClient.$queryRawUnsafe(sql,...params);return Array.isArray(rows)?rows:[];}async _execute(sql,params=[]){if(typeof this.storeClient.$executeRawUnsafe!=='function'){throw new ConfigurationError('Prisma client must expose an $executeRawUnsafe() method');}const affected=await this.storeClient.$executeRawUnsafe(sql,...params);return typeof affected==='number'?affected:0;}_toResult(row){if(!row){return{consumedPoints:0,expireAt:null};}const expireAt=row.expire===null||row.expire===undefined?null:Number(row.expire);return{consumedPoints:Number(row.points),expireAt:Number.isFinite(expireAt)?expireAt:null};}async _upsert(rlKey,points,msDuration,forceExpire=false){if(!Number.isInteger(points)){throw new Error('Consuming decimal number of points is not supported by this package');}const now=Date.now();const expire=msDuration>0?now+msDuration:null;if(forceExpire){await this._execute(this._forceExpireSql(),[rlKey,points,expire]);return{consumedPoints:points,expireAt:expire};}if(this._isMysql){await this._execute(this._incrementSql(),[rlKey,points,expire,now,now]);const rows=await this._queryRows(this._selectRowSql(),[rlKey]);return this._toResult(rows.length>0?rows[0]:null);}const rows=await this._queryRows(this._incrementSql(),[rlKey,points,expire,now,now]);return this._toResult(rows.length>0?rows[0]:null);}async _get(rlKey){const rows=await this._queryRows(this._selectSql(),[rlKey,Date.now()]);if(rows.length===0){return null;}return this._toResult(rows[0]);}async _delete(rlKey){const affected=await this._execute(this._deleteSql(),[rlKey]);return affected>0;}createTable(){try{return Promise.resolve(this._queryRows(this._createTableSql(),[]));}catch(err){return Promise.reject(err);}}async clear(){await this._execute(`DELETE FROM ${this._quotedTable()}`,[]);}}export default PrismaLimiter;
@@ -0,0 +1,16 @@
1
+ import{RateLimiterStore}from'../rateLimiterStore.js';import{RedisConnectionError}from'../../utils/errors.js';import{RedisClientAdapter}from'../../utils/redisClient.js';const INCR_TTL_LUA=`
2
+ local ttl = redis.call('pttl', KEYS[1])
3
+ if ttl == -2 then
4
+ redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2])
5
+ return {ARGV[1], ARGV[2]}
6
+ end
7
+ local consumed = redis.call('incrby', KEYS[1], ARGV[1])
8
+ if ttl == -1 then
9
+ redis.call('pexpire', KEYS[1], ARGV[2])
10
+ ttl = tonumber(ARGV[2])
11
+ end
12
+ return {consumed, ttl}
13
+ `;const SET_TTL_LUA=`
14
+ redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2])
15
+ return {ARGV[1], ARGV[2]}
16
+ `;export class RedisLimiter extends RateLimiterStore{constructor(opts={}){super(opts);const client=opts.storeClient||opts.client||opts.redis||null;if(!client){throw new RedisConnectionError('Redis client is not configured. Pass it via the "storeClient" option.');}this.client=client;this.adapter=new RedisClientAdapter(client,{clientKind:opts.clientKind,isCluster:opts.isCluster});this.hashTag=Boolean(opts.hashTag);this.rejectIfRedisNotReady=Boolean(opts.rejectIfRedisNotReady);this._incrTtlScript=opts.customIncrTtlLuaScript||INCR_TTL_LUA;this._setTtlScript=opts.customSetTtlLuaScript||SET_TTL_LUA;}getKey(key){const prefixed=super.getKey(key);if(!this.hashTag)return prefixed;if(prefixed.includes('{'))return prefixed;return`{${prefixed}}`;}parseKey(rlKey){if(!rlKey)return rlKey;let key=rlKey;if(this.hashTag&&key.startsWith('{')&&key.endsWith('}')){key=key.slice(1,-1);}return super.parseKey(key);}_isRedisReady(rlKey,isReadonly){if(!this.rejectIfRedisNotReady){return true;}return this.adapter.isReady(rlKey,isReadonly);}async _upsert(rlKey,points,msDuration,forceExpire=false){if(!Number.isInteger(points)){throw new Error('Consuming decimal number of points is not supported by this package');}if(!this._isRedisReady(rlKey,false)){throw new RedisConnectionError('Redis connection is not ready');}const ttl=Math.max(1,Math.floor(msDuration));const script=forceExpire?this._setTtlScript:this._incrTtlScript;const raw=await this.adapter.evalScript(script,[rlKey],[points,ttl]);const consumed=Array.isArray(raw)?Number(raw[0]):Number(raw);const resTtlMs=Array.isArray(raw)?Number(raw[1]):ttl;return{consumedPoints:consumed,expireAt:msDuration>0?Date.now()+resTtlMs:null};}async _get(rlKey){if(!this._isRedisReady(rlKey,true)){throw new RedisConnectionError('Redis connection is not ready');}const entry=await this.adapter.getWithTtl(rlKey);if(!entry){return null;}const consumedPoints=Number(entry.value);if(!Number.isFinite(consumedPoints)){return null;}return{consumedPoints,expireAt:entry.ttlMs>0?Date.now()+entry.ttlMs:null};}async _delete(rlKey){return this.adapter.del(rlKey);}}export default RedisLimiter;
@@ -0,0 +1,13 @@
1
+ import{RateLimiterStore}from'../rateLimiterStore.js';import{ConfigurationError}from'../../utils/errors.js';const DEFAULT_TABLE_NAME='rate_limiter';const TABLE_NAME_PATTERN=/^[A-Za-z_][A-Za-z0-9_$]*(\.[A-Za-z_][A-Za-z0-9_$]*)?$/;export class SqliteLimiter extends RateLimiterStore{constructor(opts={}){super(opts);this.storeClient=opts.storeClient||opts.client||null;if(!this.storeClient){throw new ConfigurationError('SQLite client is not configured. Pass it via the "storeClient" option.');}if(typeof this.storeClient.prepare!=='function'){throw new ConfigurationError('SQLite client must expose a prepare(sql) method (node:sqlite or better-sqlite3)');}this.tableName=opts.tableName!==undefined?opts.tableName:DEFAULT_TABLE_NAME;if(typeof this.tableName!=='string'||!TABLE_NAME_PATTERN.test(this.tableName)){throw new ConfigurationError('tableName must be a plain SQL identifier, optionally prefixed with a schema name');}this.tableCreated=opts.tableCreated!==undefined?Boolean(opts.tableCreated):false;this.clearExpiredByTimeout=opts.clearExpiredByTimeout!==undefined?Boolean(opts.clearExpiredByTimeout):false;this.clearExpiredTimeoutMs=opts.clearExpiredTimeoutMs!==undefined?Number(opts.clearExpiredTimeoutMs):300000;this._clearExpiredTimer=null;if(this.clearExpiredByTimeout&&this.clearExpiredTimeoutMs>0){this._clearExpiredTimer=setInterval(()=>{this.clearExpired().catch(()=>{});},this.clearExpiredTimeoutMs);if(this._clearExpiredTimer.unref){this._clearExpiredTimer.unref();}}this._statements=new Map();if(!this.tableCreated){this.createTable().catch(()=>undefined);}}_quotedTable(){return this.tableName.split('.').map(part=>`"${part}"`).join('.');}_createTableSql(){return`CREATE TABLE IF NOT EXISTS ${this._quotedTable()} (
2
+ "key" TEXT PRIMARY KEY,
3
+ "points" INTEGER NOT NULL DEFAULT 0,
4
+ "expire" INTEGER DEFAULT NULL
5
+ )`;}_incrementSql(){return`INSERT INTO ${this._quotedTable()} ("key", "points", "expire") VALUES (?, ?, ?)
6
+ ON CONFLICT ("key") DO UPDATE SET
7
+ "points" = CASE WHEN "expire" IS NULL OR "expire" > ? THEN "points" + excluded."points" ELSE excluded."points" END,
8
+ "expire" = CASE WHEN "expire" IS NULL OR "expire" > ? THEN "expire" ELSE excluded."expire" END
9
+ RETURNING "points", "expire"`;}_forceExpireSql(){return`INSERT INTO ${this._quotedTable()} ("key", "points", "expire") VALUES (?, ?, ?)
10
+ ON CONFLICT ("key") DO UPDATE SET
11
+ "points" = excluded."points",
12
+ "expire" = excluded."expire"
13
+ RETURNING "points", "expire"`;}_selectSql(){return`SELECT "points", "expire" FROM ${this._quotedTable()} WHERE "key" = ? AND ("expire" IS NULL OR "expire" > ?)`;}_deleteSql(){return`DELETE FROM ${this._quotedTable()} WHERE "key" = ?`;}_prepare(sql){const cached=this._statements.get(sql);if(cached){return cached;}const statement=this.storeClient.prepare(sql);this._statements.set(sql,statement);return statement;}_toResult(row){if(!row){return{consumedPoints:0,expireAt:null};}const expireAt=row.expire===null||row.expire===undefined?null:Number(row.expire);return{consumedPoints:Number(row.points),expireAt:Number.isFinite(expireAt)?expireAt:null};}async _upsert(rlKey,points,msDuration,forceExpire=false){if(!Number.isInteger(points)){throw new Error('Consuming decimal number of points is not supported by this package');}const now=Date.now();const expire=msDuration>0?now+msDuration:null;const sql=forceExpire?this._forceExpireSql():this._incrementSql();const params=forceExpire?[rlKey,points,expire]:[rlKey,points,expire,now,now];const row=this._prepare(sql).get(...params);return this._toResult(row);}async _get(rlKey){const row=this._prepare(this._selectSql()).get(rlKey,Date.now());if(!row){return null;}return this._toResult(row);}async _delete(rlKey){const info=this._prepare(this._deleteSql()).run(rlKey);return Number(info.changes)>0;}createTable(){try{this._prepare(this._createTableSql()).run();return Promise.resolve();}catch(err){return Promise.reject(err);}}async clear(){this._prepare(`DELETE FROM ${this._quotedTable()}`).run();}async clearExpired(){const now=Date.now();this._prepare(`DELETE FROM ${this._quotedTable()} WHERE "expire" IS NOT NULL AND "expire" <= ?`).run(now);}async close(){if(this._clearExpiredTimer){clearInterval(this._clearExpiredTimer);this._clearExpiredTimer=null;}}}export default SqliteLimiter;
@@ -0,0 +1,16 @@
1
+ import{RateLimiterStore}from'../rateLimiterStore.js';import{RedisClientAdapter}from'../../utils/redisClient.js';const INCR_TTL_LUA=`
2
+ local ttl = redis.call('pttl', KEYS[1])
3
+ if ttl == -2 then
4
+ redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2])
5
+ return {ARGV[1], ARGV[2]}
6
+ end
7
+ local consumed = redis.call('incrby', KEYS[1], ARGV[1])
8
+ if ttl == -1 then
9
+ redis.call('pexpire', KEYS[1], ARGV[2])
10
+ ttl = tonumber(ARGV[2])
11
+ end
12
+ return {consumed, ttl}
13
+ `;const SET_TTL_LUA=`
14
+ redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2])
15
+ return {ARGV[1], ARGV[2]}
16
+ `;export class ValkeyLimiter extends RateLimiterStore{constructor(opts={}){super(opts);const client=opts.storeClient||opts.client||opts.valkey||opts.redis||null;if(!client){throw new Error('Valkey client is not configured. Pass it via the "storeClient" option.');}this.client=client;this.adapter=new RedisClientAdapter(client,{clientKind:opts.clientKind});this.rejectIfRedisNotReady=Boolean(opts.rejectIfRedisNotReady);this._incrTtlScript=opts.customIncrTtlLuaScript||INCR_TTL_LUA;this._setTtlScript=opts.customSetTtlLuaScript||SET_TTL_LUA;}async _upsert(rlKey,points,msDuration,forceExpire=false){if(!Number.isInteger(points)){throw new Error('Consuming decimal number of points is not supported by this package');}const ttl=Math.max(1,Math.floor(msDuration));const script=forceExpire?this._setTtlScript:this._incrTtlScript;const raw=await this.adapter.evalScript(script,[rlKey],[points,ttl]);const consumed=Array.isArray(raw)?Number(raw[0]):Number(raw);const resTtlMs=Array.isArray(raw)?Number(raw[1]):ttl;return{consumedPoints:consumed,expireAt:msDuration>0?Date.now()+resTtlMs:null};}async _get(rlKey){const entry=await this.adapter.getWithTtl(rlKey);if(!entry)return null;const consumedPoints=Number(entry.value);if(!Number.isFinite(consumedPoints))return null;return{consumedPoints,expireAt:entry.ttlMs>0?Date.now()+entry.ttlMs:null};}async _delete(rlKey){return this.adapter.del(rlKey);}}export default ValkeyLimiter;
@@ -0,0 +1 @@
1
+ import{ConfigurationError}from'../utils/errors.js';export class RateLimiter{constructor(opts={}){const rawPoints=opts.points!==undefined?opts.points:opts.limit;const rawDuration=opts.duration!==undefined?opts.duration:opts.windowMs!==undefined?opts.windowMs/1000:undefined;this.points=rawPoints!==undefined?rawPoints:60;this.duration=rawDuration!==undefined?rawDuration:60;this.blockDuration=opts.blockDuration!==undefined?opts.blockDuration:0;this.execEvenly=opts.execEvenly!==undefined?Boolean(opts.execEvenly):false;this.execEvenlyMinDelayMs=opts.execEvenlyMinDelayMs;this.keyPrefix=opts.keyPrefix!==undefined?opts.keyPrefix:'rl:';this.insuranceLimiter=opts.insuranceLimiter;}get points(){return this._points;}set points(value){if(typeof value!=='number'||!Number.isFinite(value)||!Number.isInteger(value)||value<0){throw new ConfigurationError('points must be set and must be a non-negative integer');}this._points=value;}get duration(){return this._duration;}set duration(value){if(typeof value!=='number'||!Number.isFinite(value)||value<0){throw new ConfigurationError('duration must be set and must be a finite, non-negative number');}this._duration=value;}get msDuration(){return this.duration*1000;}get blockDuration(){return this._blockDuration;}set blockDuration(value){if(value!==undefined&&(typeof value!=='number'||!Number.isFinite(value)||value<0)){throw new ConfigurationError('blockDuration must be a finite, non-negative number');}this._blockDuration=value===undefined?0:value;}get msBlockDuration(){return this.blockDuration*1000;}get execEvenly(){return this._execEvenly;}set execEvenly(value){this._execEvenly=value===undefined?false:Boolean(value);}get execEvenlyMinDelayMs(){return this._execEvenlyMinDelayMs===undefined?this._getExecEvenlyMinDelayMsDefault():this._execEvenlyMinDelayMs;}set execEvenlyMinDelayMs(value){this._execEvenlyMinDelayMs=value;}_getExecEvenlyMinDelayMsDefault(){return this.points>0?Math.ceil(this.msDuration/this.points):0;}get keyPrefix(){return this._keyPrefix;}set keyPrefix(value){if(value===undefined){value='rl:';}if(typeof value!=='string'){throw new ConfigurationError('keyPrefix must be a string');}this._keyPrefix=value;}get insuranceLimiter(){return this._insuranceLimiter;}set insuranceLimiter(value){if(value!==undefined&&value!==null&&!isRateLimiterCompatible(value)){throw new ConfigurationError('insuranceLimiter must be a RateLimiter instance or expose consume/get/set/block/delete');}this._insuranceLimiter=value||null;if(this._insuranceLimiter){this._insuranceLimiter.blockDuration=this.blockDuration;this._insuranceLimiter.execEvenly=this.execEvenly;}}_getKeySecDuration(options={}){return options&&options.customDuration>=0?options.customDuration:this.duration;}getKey(key){if(this.keyPrefix.length===0)return key;const prefix=this.keyPrefix.endsWith(':')?this.keyPrefix:`${this.keyPrefix}:`;return`${prefix}${key}`;}parseKey(rlKey){if(this.keyPrefix.length===0)return rlKey;const prefix=this.keyPrefix.endsWith(':')?this.keyPrefix:`${this.keyPrefix}:`;return rlKey.startsWith(prefix)?rlKey.substring(prefix.length):rlKey;}}export function isRateLimiterCompatible(limiter){return Boolean(limiter&&typeof limiter.consume==='function'&&typeof limiter.penalty==='function'&&typeof limiter.reward==='function'&&typeof limiter.get==='function'&&typeof limiter.set==='function'&&typeof limiter.block==='function'&&typeof limiter.delete==='function');}export default RateLimiter;
@@ -0,0 +1 @@
1
+ export class RateLimiterRes{constructor(remainingPoints=0,msBeforeNext=0,consumedPoints=0,isFirstInDuration=false){this.remainingPoints=remainingPoints;this.msBeforeNext=msBeforeNext;this.consumedPoints=consumedPoints;this.isFirstInDuration=isFirstInDuration;}get msBeforeNext(){return this._msBeforeNext;}set msBeforeNext(ms){this._msBeforeNext=ms;}get remainingPoints(){return this._remainingPoints;}set remainingPoints(points){this._remainingPoints=points;}get consumedPoints(){return this._consumedPoints;}set consumedPoints(points){this._consumedPoints=points;}get isFirstInDuration(){return this._isFirstInDuration;}set isFirstInDuration(value){this._isFirstInDuration=Boolean(value);}_getDecoratedProperties(){return{remainingPoints:this.remainingPoints,msBeforeNext:this.msBeforeNext,consumedPoints:this.consumedPoints,isFirstInDuration:this.isFirstInDuration};}[Symbol.for('nodejs.util.inspect.custom')](){return this._getDecoratedProperties();}toString(){return JSON.stringify(this._getDecoratedProperties());}toJSON(){return this._getDecoratedProperties();}}export default RateLimiterRes;
@@ -0,0 +1 @@
1
+ import{RateLimiter,isRateLimiterCompatible}from'./rateLimiter.js';import{RateLimiterRes}from'./rateLimiterRes.js';import{BlockedKeys}from'./blockedKeys.js';export class RateLimiterStore extends RateLimiter{constructor(opts={}){super(opts);this._inMemoryBlockedKeys=new BlockedKeys();this.inMemoryBlockOnConsumed=opts.inMemoryBlockOnConsumed;this.inMemoryBlockDuration=opts.inMemoryBlockDuration;}get inMemoryBlockOnConsumed(){return this._inMemoryBlockOnConsumed;}set inMemoryBlockOnConsumed(value){this._inMemoryBlockOnConsumed=value?parseInt(value,10):0;if(this._inMemoryBlockOnConsumed>0&&this.points>this._inMemoryBlockOnConsumed){throw new Error('inMemoryBlockOnConsumed option must be greater or equal "points" option');}}get inMemoryBlockDuration(){return this._inMemoryBlockDuration;}set inMemoryBlockDuration(value){this._inMemoryBlockDuration=value?parseInt(value,10):0;if(this._inMemoryBlockDuration>0&&this._inMemoryBlockOnConsumed===0){throw new Error('inMemoryBlockOnConsumed option must be set up');}}get msInMemoryBlockDuration(){return this.inMemoryBlockDuration*1000;}getInMemoryBlockMsBeforeExpire(rlKey){if(this.inMemoryBlockOnConsumed>0){return this._inMemoryBlockedKeys.msBeforeExpire(rlKey);}return 0;}deleteInMemoryBlocked(key){const rlKey=this.getKey(key);return this._inMemoryBlockedKeys.delete(rlKey);}deleteInMemoryBlockedAll(){this._inMemoryBlockedKeys.deleteAll();}consume(key,pointsToConsume=1,options={}){return this._operation('_consume','consume',[key,pointsToConsume,options]);}penalty(key,points=1,options={}){return this._operation('_penalty','penalty',[key,points,options]);}reward(key,points=1,options={}){return this._operation('_reward','reward',[key,points,options]);}get(key,options={}){return this._operation('_getState','get',[key,options]);}set(key,points,secDuration,options={}){return this._operation('_setState','set',[key,points,secDuration,options]);}block(key,secDuration,options={}){return this._operation('_blockState','block',[key,secDuration,options]);}delete(key,options={}){return this._operation('_deleteState','delete',[key,options]);}_operation(internalName,publicName,params){let promise;try{promise=this[internalName](...params);}catch(syncErr){promise=Promise.reject(syncErr);}return new Promise((resolve,reject)=>{promise.then(res=>resolve(res)).catch(err=>{if(err instanceof RateLimiterRes){reject(err);return;}if(!isRateLimiterCompatible(this.insuranceLimiter)){reject(err);return;}try{Promise.resolve(this.insuranceLimiter[publicName](...params)).then(res=>resolve(res)).catch(fallbackErr=>reject(fallbackErr));}catch(syncErr){reject(syncErr);}});});}_afterConsume(resolve,reject,rlKey,changedPoints,storeResult,options){const res=this._getRateLimiterRes(rlKey,changedPoints,storeResult);if(this.inMemoryBlockOnConsumed>0&&res.consumedPoints>=this.inMemoryBlockOnConsumed){if(this.inMemoryBlockDuration>0){this._inMemoryBlockedKeys.add(rlKey,this.inMemoryBlockDuration);res.msBeforeNext=this.msInMemoryBlockDuration;}else{this._inMemoryBlockedKeys.addMs(rlKey,res.msBeforeNext);}}if(res.consumedPoints>this.points){const shouldBlock=this.blockDuration>0&&res.consumedPoints<=this.points+changedPoints;const blockPromise=shouldBlock?this._blockState(rlKey,this.blockDuration,options).then(()=>{res.msBeforeNext=this.msBlockDuration;}):Promise.resolve();blockPromise.then(()=>reject(res)).catch(err=>reject(err));return;}if(this.execEvenly&&this.points>0&&res.msBeforeNext>0&&!res.isFirstInDuration){let delay=Math.ceil(res.msBeforeNext/(res.remainingPoints+2));if(delay<this.execEvenlyMinDelayMs){delay=res.consumedPoints*this.execEvenlyMinDelayMs;}res.msBeforeNext=Math.max(res.msBeforeNext-delay,0);setTimeout(resolve,delay,res);return;}resolve(res);}async _consume(key,pointsToConsume=1,options={}){if(typeof pointsToConsume==='number'&&Number.isFinite(pointsToConsume)&&!Number.isInteger(pointsToConsume)){throw new Error('Consuming decimal number of points is not supported by this package');}if(typeof pointsToConsume!=='number'||!Number.isFinite(pointsToConsume)||pointsToConsume<0){throw new Error('pointsToConsume must be a non-negative integer');}const rlKey=this.getKey(key);const inMemoryBlockMsBeforeExpire=this.getInMemoryBlockMsBeforeExpire(rlKey);if(inMemoryBlockMsBeforeExpire>0){throw new RateLimiterRes(0,inMemoryBlockMsBeforeExpire);}const storeResult=await this._upsert(rlKey,pointsToConsume,this._getKeySecDuration(options)*1000,false,options);return new Promise((resolve,reject)=>{this._afterConsume(resolve,reject,rlKey,pointsToConsume,storeResult,options);});}async _penalty(key,points=1,options={}){if(typeof points==='number'&&Number.isFinite(points)&&!Number.isInteger(points)){throw new Error('Consuming decimal number of points is not supported by this package');}if(typeof points!=='number'||!Number.isFinite(points)||points<0){throw new Error('points must be a non-negative integer');}const rlKey=this.getKey(key);const storeResult=await this._upsert(rlKey,points,this._getKeySecDuration(options)*1000,false,options);return this._getRateLimiterRes(rlKey,points,storeResult);}async _reward(key,points=1,options={}){if(typeof points==='number'&&Number.isFinite(points)&&!Number.isInteger(points)){throw new Error('Consuming decimal number of points is not supported by this package');}if(typeof points!=='number'||!Number.isFinite(points)||points<0){throw new Error('points must be a non-negative integer');}const rlKey=this.getKey(key);const storeResult=await this._upsert(rlKey,-points,this._getKeySecDuration(options)*1000,false,options);return this._getRateLimiterRes(rlKey,-points,storeResult);}async _getState(key,options={}){const rlKey=this.getKey(key);const storeResult=await this._get(rlKey,options);if(storeResult===null||storeResult===undefined){return null;}return this._getRateLimiterRes(rlKey,0,storeResult,true);}async _deleteState(key,options={}){const rlKey=this.getKey(key);const res=await this._delete(rlKey,options);this._inMemoryBlockedKeys.delete(rlKey);return res;}async _setState(key,points,secDuration,options={}){const rlKey=this.getKey(key);const msDuration=(secDuration>=0?secDuration:this.duration)*1000;await this._upsert(rlKey,points,msDuration,true,options);return new RateLimiterRes(0,msDuration>0?msDuration:-1,points);}async _blockState(key,secDuration,options={}){if(typeof secDuration==='number'&&secDuration<=0){return new RateLimiterRes(this.points,0,0,false);}const rlKey=this.getKey(key);const msDuration=secDuration*1000;const initPoints=this.points+1;await this._upsert(rlKey,initPoints,msDuration,true,options);return new RateLimiterRes(0,msDuration>0?msDuration:-1,initPoints);}_getRateLimiterRes(_rlKey,changedPoints,storeResult,isRead=false){const res=new RateLimiterRes();const rawConsumed=Number(storeResult.consumedPoints);const consumed=Number.isFinite(rawConsumed)?rawConsumed:0;res.consumedPoints=consumed;res.isFirstInDuration=isRead?false:consumed===changedPoints;res.remainingPoints=Math.min(this.points,Math.max(this.points-Math.max(0,consumed),0));const expireAt=storeResult.expireAt;if(expireAt===null||expireAt===undefined){res.msBeforeNext=-1;}else{const expireMs=typeof expireAt==='number'?expireAt:new Date(expireAt).getTime();res.msBeforeNext=Math.max(expireMs-Date.now(),0);}return res;}async _upsert(_rlKey,_points,_msDuration,_forceExpire=false,_options={}){throw new Error("You have to implement the method '_upsert'!");}async _get(_rlKey,_options={}){throw new Error("You have to implement the method '_get'!");}async _delete(_rlKey,_options={}){throw new Error("You have to implement the method '_delete'!");}async close(){}}export default RateLimiterStore;
@@ -0,0 +1 @@
1
+ import{RateLimiterRes}from'../rateLimiterRes.js';export class BlackAndWhiteLimiter{constructor(opts={}){if(!opts.limiter){throw new TypeError('BlackAndWhiteLimiter requires a limiter');}this.limiter=opts.limiter;this.blackList=opts.blackList||[];this.whiteList=opts.whiteList||[];this.isBlackListed=opts.isBlackListed;this.isWhiteListed=opts.isWhiteListed;this.runActionAnyway=opts.runActionAnyway||false;}get points(){return this.limiter.points;}get duration(){return this.limiter.duration;}get msDuration(){return this.limiter.msDuration;}async consume(key,points=1,options={}){if(this._isBlackListed(key)){if(this.runActionAnyway){await this._runAnyway(()=>this.limiter.consume(key,points,options));}throw this._blackListedRes();}if(this._isWhiteListed(key)){if(this.runActionAnyway){await this._runAnyway(()=>this.limiter.consume(key,points,options));}return new RateLimiterRes(Number.MAX_SAFE_INTEGER,0);}return this.limiter.consume(key,points,options);}async penalty(key,points=1,options={}){return this._forward(key,()=>this.limiter.penalty(key,points,options));}async reward(key,points=1,options={}){return this._forward(key,()=>this.limiter.reward(key,points,options));}async get(key,options={}){return this._forward(key,()=>this.limiter.get(key,options));}async set(key,points,secDuration,options={}){return this._forward(key,()=>this.limiter.set(key,points,secDuration,options));}async block(key,secDuration,options={}){return this._forward(key,()=>this.limiter.block(key,secDuration,options));}async delete(key,options={}){return this._forward(key,()=>this.limiter.delete(key,options));}_isBlackListed(key){if(Array.isArray(this.blackList)&&this.blackList.includes(key)){return true;}return typeof this.isBlackListed==='function'&&Boolean(this.isBlackListed(key));}_isWhiteListed(key){if(Array.isArray(this.whiteList)&&this.whiteList.includes(key)){return true;}return typeof this.isWhiteListed==='function'&&Boolean(this.isWhiteListed(key));}_blackListedRes(){return new RateLimiterRes(0,Number.MAX_SAFE_INTEGER,(this.points||0)+1,false);}async _forward(key,operation){if(this._isBlackListed(key)){if(this.runActionAnyway){await this._runAnyway(operation);}throw this._blackListedRes();}return operation();}async _runAnyway(operation){try{await operation();}catch{}}}export default BlackAndWhiteLimiter;
@@ -0,0 +1 @@
1
+ import{RateLimiterRes}from'../rateLimiterRes.js';export class BurstyRateLimiter{constructor(rateLimiter,burstLimiter){if(!rateLimiter||!burstLimiter){throw new Error('BurstyRateLimiter requires a rate limiter and a burst limiter');}this.rateLimiter=rateLimiter;this.burstLimiter=burstLimiter;}get points(){return(this.rateLimiter.points||0)+(this.burstLimiter.points||0);}get duration(){return Math.max(this.rateLimiter.duration||0,this.burstLimiter.duration||0);}get msDuration(){return Math.max(this.rateLimiter.msDuration||0,this.burstLimiter.msDuration||0);}async delete(key,options={}){const[a,b]=await Promise.all([typeof this.rateLimiter.delete==='function'?this.rateLimiter.delete(key,options):true,typeof this.burstLimiter.delete==='function'?this.burstLimiter.delete(key,options):true]);return Boolean(a&&b);}async consume(key,points=1,options={}){try{return await this.rateLimiter.consume(key,points,options);}catch(err){if(!(err instanceof RateLimiterRes)){throw err;}try{const burstRes=await this.burstLimiter.consume(key,points,options);return this._merge(err,burstRes);}catch(burstErr){if(!(burstErr instanceof RateLimiterRes)){throw burstErr;}throw this._merge(err,burstErr);}}}async get(key){const[steadyRes,burstRes]=await Promise.all([this.rateLimiter.get(key),this.burstLimiter.get(key)]);return this._merge(steadyRes,burstRes);}_merge(steadyRes,burstRes){const steady=steadyRes instanceof RateLimiterRes?steadyRes:new RateLimiterRes(0,0);const burst=burstRes instanceof RateLimiterRes?burstRes:new RateLimiterRes(0,0);return new RateLimiterRes(steady.remainingPoints,Math.min(steady.msBeforeNext,burst.msBeforeNext),steady.consumedPoints,steady.isFirstInDuration);}}export default BurstyRateLimiter;
@@ -0,0 +1 @@
1
+ import{RateLimiterError}from'../../utils/errors.js';import{RateLimiterRes}from'../rateLimiterRes.js';export class RateLimiterQueueError extends RateLimiterError{constructor(message='Rate limiter queue error'){super(message);this.name='RateLimiterQueueError';}static[Symbol.hasInstance](instance){return Boolean(instance&&instance.name==='RateLimiterQueueError');}}const DEFAULT_MAX_QUEUE_SIZE=4294967295;const DEFAULT_RETRY_DELAY_MS=100;export class RateLimiterQueue{constructor(limiter,opts={}){if(!limiter||typeof limiter.consume!=='function'||typeof limiter.get!=='function'){throw new TypeError('RateLimiterQueue requires a limiter exposing consume() and get()');}this.limiter=limiter;this.maxQueueSize=opts.maxQueueSize!==undefined?opts.maxQueueSize:DEFAULT_MAX_QUEUE_SIZE;this._queues=new Map();}get points(){return this.limiter.points;}get duration(){return this.limiter.duration;}get msDuration(){return this.limiter.msDuration;}async consume(key='limiter',points=1){const remaining=await this.removeTokens(points,key);const limit=typeof this.points==='number'?this.points:points+remaining;return new RateLimiterRes(remaining,0,Math.max(0,limit-remaining),false);}async get(key='limiter'){return this.limiter.get(key);}async delete(key='limiter'){if(typeof this.limiter.delete==='function'){return this.limiter.delete(key);}return false;}removeTokens(tokens,key='limiter',expiresUnixAt=0){if(typeof this.limiter.points==='number'&&tokens>this.limiter.points){return Promise.reject(new Error(`Can't remove more tokens than points: ${tokens} > ${this.limiter.points}`));}if(this._isExpired(expiresUnixAt)){return Promise.reject(new RateLimiterQueueError('Rate limiter request expired before it was queued'));}const state=this._getState(key);return new Promise((resolve,reject)=>{const item={tokens,expiresUnixAt,resolve,reject,settled:false,expiryTimer:null};state.items.push(item);if(state.items.length>this.maxQueueSize){state.items.pop();this._settle(item,()=>reject(new RateLimiterQueueError('Rate limiter queue is full')));return;}this._armExpiry(item,key);this._kick(key);});}async getTokensRemaining(key='limiter'){const res=await this.limiter.get(key);if(!res){return typeof this.limiter.points==='number'?this.limiter.points:0;}return res.remainingPoints;}_getState(key){let state=this._queues.get(key);if(!state){state={items:[],inFlight:false,retryTimer:null};this._queues.set(key,state);}return state;}_isExpired(expiresUnixAt){return expiresUnixAt>0&&expiresUnixAt*1000<=Date.now();}_kick(key){const state=this._queues.get(key);if(!state||state.inFlight||state.items.length===0){return;}this._drain(key).catch(()=>{});}async _drain(key){const state=this._queues.get(key);if(!state){return;}state.inFlight=true;try{while(state.items.length>0){const item=state.items[0];if(item.settled){state.items.shift();continue;}if(this._isExpired(item.expiresUnixAt)){state.items.shift();this._settle(item,()=>item.reject(new RateLimiterQueueError('Rate limiter request expired while it was queued')));continue;}let res;try{res=await this.limiter.consume(key,item.tokens);}catch(err){if(item.settled){continue;}if(err instanceof RateLimiterRes){const delay=this._retryDelay(err);if(delay<0){state.items.shift();this._settle(item,()=>item.reject(err));continue;}this._scheduleRetry(key,delay);return;}state.items.shift();this._settle(item,()=>item.reject(err));continue;}if(item.settled){continue;}if(state.items[0]===item){state.items.shift();}const remainingPoints=res&&typeof res.remainingPoints==='number'?res.remainingPoints:0;this._settle(item,()=>item.resolve(remainingPoints));}}finally{state.inFlight=false;this._cleanupState(key,state);}}_retryDelay(res){if(res.msBeforeNext>0){return res.msBeforeNext;}if(this.limiter&&this.limiter.duration===0){return-1;}if(res.msBeforeNext===0){return 0;}return DEFAULT_RETRY_DELAY_MS;}_scheduleRetry(key,delay){const state=this._queues.get(key);if(!state||state.retryTimer){return;}state.retryTimer=setTimeout(()=>{state.retryTimer=null;this._kick(key);},delay);if(state.retryTimer.unref){state.retryTimer.unref();}}_armExpiry(item,key){if(item.expiresUnixAt<=0){return;}const delay=item.expiresUnixAt*1000-Date.now();item.expiryTimer=setTimeout(()=>this._expire(item,key),Math.max(delay,0));if(item.expiryTimer.unref){item.expiryTimer.unref();}}_expire(item,key){if(item.settled){return;}const state=this._queues.get(key);if(state){const index=state.items.indexOf(item);if(index!==-1){state.items.splice(index,1);}}this._settle(item,()=>item.reject(new RateLimiterQueueError('Rate limiter request expired while it was queued')));this._cleanupState(key,state);}_settle(item,fn){item.settled=true;if(item.expiryTimer){clearTimeout(item.expiryTimer);item.expiryTimer=null;}fn();}_cleanupState(key,state){if(!state||state.items.length>0||state.inFlight){return;}if(state.retryTimer){clearTimeout(state.retryTimer);state.retryTimer=null;}if(this._queues.get(key)===state){this._queues.delete(key);}}}export default RateLimiterQueue;
@@ -0,0 +1 @@
1
+ import{ConfigurationError}from'../../utils/errors.js';import{isRateLimiterCompatible}from'../rateLimiter.js';export class RateLimiterUnion{constructor(...limiters){if(limiters.length<1){throw new ConfigurationError('RateLimiterUnion requires at least one limiter');}for(const limiter of limiters){if(!isRateLimiterCompatible(limiter)){throw new ConfigurationError('RateLimiterUnion requires limiters exposing consume/penalty/reward/get/set/block/delete');}}this.limiters=limiters;}get points(){const pts=this.limiters.map(l=>typeof l.points==='number'?l.points:Infinity).filter(n=>Number.isFinite(n));return pts.length>0?Math.min(...pts):0;}get duration(){const durs=this.limiters.map(l=>typeof l.duration==='number'?l.duration:0).filter(n=>n>0);return durs.length>0?Math.max(...durs):0;}get msDuration(){return this.duration*1000;}async consume(key,points=1){return this._runAll(limiter=>limiter.consume(key,points));}async get(key){return this._runAll(limiter=>limiter.get(key));}async delete(key){return this._runAll(limiter=>typeof limiter.delete==='function'?limiter.delete(key):true);}async _runAll(operation){const results=await Promise.allSettled(this.limiters.map(limiter=>operation(limiter)));const fulfilled={};const rejected={};results.forEach((result,index)=>{const prefix=this._prefixOf(index);if(result.status==='fulfilled'){fulfilled[prefix]=result.value;}else{rejected[prefix]=result.reason;}});if(Object.keys(rejected).length>0){throw rejected;}return fulfilled;}_prefixOf(index){const limiter=this.limiters[index];if(limiter&&typeof limiter.keyPrefix==='string'&&limiter.keyPrefix.length>0){return limiter.keyPrefix;}return`limiter${index}`;}}export default RateLimiterUnion;
@@ -0,0 +1 @@
1
+ export class TimeoutLimiter{constructor(opts={}){const{limiter,timeoutMs}=opts;if(!limiter){throw new TypeError('TimeoutLimiter requires a limiter');}if(typeof timeoutMs!=='number'||!Number.isFinite(timeoutMs)||timeoutMs<0){throw new TypeError('TimeoutLimiter requires timeoutMs to be a non-negative number');}this.limiter=limiter;this.timeoutMs=timeoutMs;}get points(){return this.limiter.points;}get duration(){return this.limiter.duration;}get msDuration(){return this.limiter.msDuration;}consume(key,points=1,options={}){return this._withTimeout(()=>this.limiter.consume(key,points,options));}penalty(key,points=1,options={}){return this._withTimeout(()=>this.limiter.penalty(key,points,options));}reward(key,points=1,options={}){return this._withTimeout(()=>this.limiter.reward(key,points,options));}get(key,options={}){return this._withTimeout(()=>this.limiter.get(key,options));}set(key,points,secDuration,options={}){return this._withTimeout(()=>this.limiter.set(key,points,secDuration,options));}block(key,secDuration,options={}){return this._withTimeout(()=>this.limiter.block(key,secDuration,options));}delete(key,options={}){return this._withTimeout(()=>this.limiter.delete(key,options));}_withTimeout(operation){return new Promise((resolve,reject)=>{const timer=setTimeout(()=>{reject(new Error('Operation timed out'));},this.timeoutMs);if(timer.unref){timer.unref();}Promise.resolve().then(operation).then(res=>{clearTimeout(timer);resolve(res);},err=>{clearTimeout(timer);reject(err);});});}}export default TimeoutLimiter;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- (function(_0x5c7723,_0x2da9cb){var _0x4c52af=_0x4c0b,_0x56fe13=_0x5c7723();while(!![]){try{var _0x3529d8=-parseInt(_0x4c52af(186))/1*(parseInt(_0x4c52af(188))/2)+parseInt(_0x4c52af(194))/3+parseInt(_0x4c52af(195))/4+-parseInt(_0x4c52af(187))/5*(parseInt(_0x4c52af(189))/6)+parseInt(_0x4c52af(196))/7*(parseInt(_0x4c52af(190))/8)+parseInt(_0x4c52af(192))/9+parseInt(_0x4c52af(193))/10*(-parseInt(_0x4c52af(191))/11);if(_0x3529d8===_0x2da9cb)break;else _0x56fe13["push"](_0x56fe13["shift"]())}catch(_0x3e8a0b){_0x56fe13["push"](_0x56fe13["shift"]())}}})(_0x4ec5,700367);import{rateLimit,createLimiter}from"./rateLimit.js";import{RateLimitManager,createRateLimiters}from"./rateLimitManager.js";import{BaseStore}from"./stores/baseStore.js";function _0x4ec5(){var _0x97c7b4=["mJC2mdGWtK1fELjA","mJa4nZzTtLDxB1K","mtiYodq2mfrxD1jUDq","ntjtt1zdyvu","nKDTwKDrua","ohHgAKHWvq","mti0otCXrgTTzNDI","nZiWndC4ofvWr0rYsq","odeWzuH4q2Lz","mtaWodK3nwjmvwDiqG","ndKZmteXnMDHzLboDW"];_0x4ec5=function(){return _0x97c7b4};return _0x4ec5()}import{MemoryStore}from"./stores/memoryStore.js";import{RedisStore}from"./stores/redisStore.js";import{BaseAlgorithm}from"./algorithms/baseAlgorithm.js";import{FixedWindow}from"./algorithms/fixedWindow.js";import{SlidingWindowLog}from"./algorithms/slidingWindowLog.js";import{SlidingWindowCounter}from"./algorithms/slidingWindowCounter.js";function _0x4c0b(_0x4d280e,_0x5c441c){_0x4d280e=_0x4d280e-186;var _0x4ec5d8=_0x4ec5();var _0x4c0b0a=_0x4ec5d8[_0x4d280e];if(_0x4c0b["LxllXU"]===undefined){var _0x47e1e4=function(_0x40af72){var _0x2a72ba="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";var _0x2d10cf="",_0x527ccf="";for(var _0x3f9ed0=0,_0x1d3694,_0x3b22f2,_0x59a684=0;_0x3b22f2=_0x40af72["charAt"](_0x59a684++);~_0x3b22f2&&(_0x1d3694=_0x3f9ed0%4?_0x1d3694*64+_0x3b22f2:_0x3b22f2,_0x3f9ed0++%4)?_0x2d10cf+=String["fromCharCode"](255&_0x1d3694>>(-2*_0x3f9ed0&6)):0){_0x3b22f2=_0x2a72ba["indexOf"](_0x3b22f2)}for(var _0x275352=0,_0x3107ed=_0x2d10cf["length"];_0x275352<_0x3107ed;_0x275352++){_0x527ccf+="%"+("00"+_0x2d10cf["charCodeAt"](_0x275352)["toString"](16))["slice"](-2)}return decodeURIComponent(_0x527ccf)};_0x4c0b["UsNaEa"]=_0x47e1e4,_0x4c0b["BrJjYo"]={},_0x4c0b["LxllXU"]=!![]}var _0x2b6a5b=_0x4ec5d8[0];_0x4c0b["wvHIWy"]!==_0x2b6a5b&&(_0x4c0b["BrJjYo"]={},_0x4c0b["wvHIWy"]=_0x2b6a5b);var _0x44bf6f=_0x4c0b["BrJjYo"][_0x4d280e];return _0x44bf6f===undefined?(_0x4c0b0a=_0x4c0b["UsNaEa"](_0x4c0b0a),_0x4c0b["BrJjYo"][_0x4d280e]=_0x4c0b0a):_0x4c0b0a=_0x44bf6f,_0x4c0b0a}import{TokenBucket}from"./algorithms/tokenBucket.js";import{LeakyBucket}from"./algorithms/leakyBucket.js";import*as _0x527ccf from"./utils/errors.js";import{defaultKeyGenerator,ipKeyGenerator,getEndpoint,normalizeIp,graphQLKeyGenerator}from"./utils/keyGenerator.js";import{extractGraphQLRootFieldFast}from"./utils/graphQLParser.js";import{validateIpRangeV4,validateSecondaryIps,compileWhitelist,isIpInWhitelist,cleanIp,ipToInt}from"./utils/ipUtils.js";import{evaluateSkipRule,checkHeaderCriteria,checkOriginCriteria,checkIpCriteria,checkKeyCriteria}from"./utils/skipUtils.js";export{rateLimit,rateLimit as default,createLimiter,RateLimitManager,createRateLimiters,BaseStore,MemoryStore,RedisStore,BaseAlgorithm,FixedWindow,SlidingWindowLog,SlidingWindowCounter,TokenBucket,LeakyBucket,_0x527ccf as errors,defaultKeyGenerator,ipKeyGenerator,getEndpoint,normalizeIp,graphQLKeyGenerator,extractGraphQLRootFieldFast,validateIpRangeV4,validateSecondaryIps,compileWhitelist,isIpInWhitelist,cleanIp,ipToInt,evaluateSkipRule,checkHeaderCriteria,checkOriginCriteria,checkIpCriteria,checkKeyCriteria};export*from"./utils/errors.js";
1
+ import{rateLimit,createLimiter}from'./rateLimit.js';import{RateLimitManager,createRateLimiters}from'./rateLimitManager.js';import{BaseStore}from'./stores/baseStore.js';import{MemoryStore}from'./stores/memoryStore.js';import{RedisStore}from'./stores/redisStore.js';import{BaseAlgorithm}from'./algorithms/baseAlgorithm.js';import{FixedWindow}from'./algorithms/fixedWindow.js';import{SlidingWindowLog}from'./algorithms/slidingWindowLog.js';import{SlidingWindowCounter}from'./algorithms/slidingWindowCounter.js';import{TokenBucket}from'./algorithms/tokenBucket.js';import{LeakyBucket}from'./algorithms/leakyBucket.js';import*as errors from'./utils/errors.js';import{defaultKeyGenerator,ipKeyGenerator,getEndpoint,normalizeIp,graphQLKeyGenerator}from'./utils/keyGenerator.js';import{extractGraphQLRootFieldFast}from'./utils/graphQLParser.js';import{validateIpRangeV4,validateSecondaryIps,compileWhitelist,isIpInWhitelist,cleanIp,ipToInt}from'./utils/ipUtils.js';import{evaluateSkipRule,checkHeaderCriteria,checkOriginCriteria,checkIpCriteria,checkKeyCriteria}from'./utils/skipUtils.js';import{RateLimiter}from'./core/rateLimiter.js';import{RateLimiterRes}from'./core/rateLimiterRes.js';import{RateLimiterStore}from'./core/rateLimiterStore.js';import{BlockedKeys}from'./core/blockedKeys.js';import{createCoreMiddleware}from'./core/coreMiddleware.js';import{MemoryLimiter}from'./core/limiters/memoryLimiter.js';import{RedisLimiter}from'./core/limiters/redisLimiter.js';import{ValkeyLimiter}from'./core/limiters/valkeyLimiter.js';import{ClusterLimiter,RedisClusterLimiter,RateLimiterRedisCluster}from'./core/limiters/clusterLimiter.js';import{RateLimiterCluster,RateLimiterClusterMaster,RateLimiterClusterMasterPM2}from'./core/limiters/nodeClusterLimiter.js';import{MongoLimiter}from'./core/limiters/mongoLimiter.js';import{MongooseLimiter}from'./core/limiters/mongooseLimiter.js';import{DynamoDbLimiter}from'./core/limiters/dynamoDbLimiter.js';import{MySqlLimiter}from'./core/limiters/mySqlLimiter.js';import{PostgresLimiter}from'./core/limiters/postgresLimiter.js';import{SqliteLimiter}from'./core/limiters/sqliteLimiter.js';import{PrismaLimiter}from'./core/limiters/prismaLimiter.js';import{MemcachedLimiter}from'./core/limiters/memcachedLimiter.js';import{EtcdLimiter}from'./core/limiters/etcdLimiter.js';import{RateLimiterQueue,RateLimiterQueueError}from'./core/wrappers/rateLimiterQueue.js';import{RateLimiterUnion}from'./core/wrappers/rateLimiterUnion.js';import{BurstyRateLimiter}from'./core/wrappers/burstyRateLimiter.js';import{BlackAndWhiteLimiter}from'./core/wrappers/blackAndWhiteLimiter.js';import{TimeoutLimiter}from'./core/wrappers/timeoutLimiter.js';import{RedisClientAdapter,detectRedisClientKind}from'./utils/redisClient.js';const RateLimiterMemory=MemoryLimiter;const RateLimiterRedis=RedisLimiter;const RateLimiterMongo=MongoLimiter;const RateLimiterMongoose=MongooseLimiter;const RateLimiterMySQL=MySqlLimiter;const RateLimiterPostgres=PostgresLimiter;const RateLimiterSQLite=SqliteLimiter;const RateLimiterPrisma=PrismaLimiter;const RateLimiterMemcache=MemcachedLimiter;const RateLimiterMemcached=MemcachedLimiter;const RateLimiterDynamo=DynamoDbLimiter;const RateLimiterDynamoDb=DynamoDbLimiter;const RateLimiterValkey=ValkeyLimiter;const RateLimiterEtcd=EtcdLimiter;const RateLimiterAbstract=RateLimiter;const RateLimiterStoreAbstract=RateLimiterStore;const RateLimiterBlackAndWhite=BlackAndWhiteLimiter;export{rateLimit,rateLimit as default,createLimiter,RateLimitManager,createRateLimiters,BaseStore,MemoryStore,RedisStore,BaseAlgorithm,FixedWindow,SlidingWindowLog,SlidingWindowCounter,TokenBucket,LeakyBucket,errors,defaultKeyGenerator,ipKeyGenerator,getEndpoint,normalizeIp,graphQLKeyGenerator,extractGraphQLRootFieldFast,validateIpRangeV4,validateSecondaryIps,compileWhitelist,isIpInWhitelist,cleanIp,ipToInt,evaluateSkipRule,checkHeaderCriteria,checkOriginCriteria,checkIpCriteria,checkKeyCriteria,RateLimiter,RateLimiterRes,RateLimiterStore,BlockedKeys,createCoreMiddleware,MemoryLimiter,RedisLimiter,ValkeyLimiter,ClusterLimiter,RedisClusterLimiter,RateLimiterRedisCluster,RateLimiterCluster,RateLimiterClusterMaster,RateLimiterClusterMasterPM2,MongoLimiter,MongooseLimiter,DynamoDbLimiter,MySqlLimiter,PostgresLimiter,SqliteLimiter,PrismaLimiter,MemcachedLimiter,EtcdLimiter,RateLimiterMemory,RateLimiterRedis,RateLimiterMongo,RateLimiterMongoose,RateLimiterMySQL,RateLimiterPostgres,RateLimiterSQLite,RateLimiterPrisma,RateLimiterMemcache,RateLimiterMemcached,RateLimiterDynamo,RateLimiterDynamoDb,RateLimiterValkey,RateLimiterEtcd,RateLimiterAbstract,RateLimiterStoreAbstract,RateLimiterBlackAndWhite,RateLimiterQueue,RateLimiterQueueError,RateLimiterUnion,BurstyRateLimiter,BlackAndWhiteLimiter,TimeoutLimiter,RedisClientAdapter,detectRedisClientKind};export*from'./utils/errors.js';
@@ -1 +1 @@
1
- function _0x4139(){const _0x29e008=["ywXSB3DLza","B2jQzwn0","BwvZC2fNzq","CMf0zuXPBwL0","y2XVC2u","AxaTzw5KCg9PBNq","CMvZB2X2zq","mtu0AhPlEKTV","mtrdt2zJA3O","ow9mrwTsvG","y3vZDg9Tu2TPCa","mta2ndu2ofvguM5guW","CMvZzxq","ndqZmtvxDerKt2y","odiZogDksLHfDa","otGWBuzLBNbt","A2v5r2vUzxjHDg9Y","zNvUy3rPB24","z2v0u3rHDgu","C2TPCa","y29UC3vTzq","z3jHCgHXBa","BNvTyMvY","mZK2odGXmeXPsw1zwq","C3rVCMu","ywXNB3jPDgHT","B25fCNjVCG","mZa4mtzoAfrzz0y","m0zrCfv2qG","nJaYmZCWCvbwsfjw","mty5mZKYufPkwNHm","ANnVBG","y29ZDa","C3rHDhvZq29Kzq","AgfUzgXLCG"];_0x4139=function(){return _0x29e008};return _0x4139()}(function(_0xa36f41,_0x19732a){const _0x5b92d3=_0x564d,_0x22e052=_0xa36f41();while(!![]){try{const _0x3fbbfd=parseInt(_0x5b92d3(447))/1+parseInt(_0x5b92d3(463))/2+-parseInt(_0x5b92d3(462))/3*(-parseInt(_0x5b92d3(464))/4)+-parseInt(_0x5b92d3(449))/5*(parseInt(_0x5b92d3(448))/6)+-parseInt(_0x5b92d3(442))/7*(parseInt(_0x5b92d3(445))/8)+parseInt(_0x5b92d3(443))/9*(parseInt(_0x5b92d3(457))/10)+parseInt(_0x5b92d3(441))/11*(-parseInt(_0x5b92d3(461))/12);if(_0x3fbbfd===_0x19732a)break;else _0x22e052["push"](_0x22e052["shift"]())}catch(_0x123624){_0x22e052["push"](_0x22e052["shift"]())}}})(_0x4139,213527);import{defaultKeyGenerator,ipKeyGenerator,graphQLKeyGenerator}from"../utils/keyGenerator.js";function _0x564d(_0x127cac,_0x51fe35){_0x127cac=_0x127cac-433;const _0x4139fb=_0x4139();let _0x564df8=_0x4139fb[_0x127cac];if(_0x564d["derGuy"]===undefined){var _0x38fe8c=function(_0x3d9777){const _0x11240f="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";let _0x40abe9="",_0x1cb8da="";for(let _0x111dde=0,_0x20857f,_0x321aaa,_0x3b1044=0;_0x321aaa=_0x3d9777["charAt"](_0x3b1044++);~_0x321aaa&&(_0x20857f=_0x111dde%4?_0x20857f*64+_0x321aaa:_0x321aaa,_0x111dde++%4)?_0x40abe9+=String["fromCharCode"](255&_0x20857f>>(-2*_0x111dde&6)):0){_0x321aaa=_0x11240f["indexOf"](_0x321aaa)}for(let _0x595b68=0,_0x5d67af=_0x40abe9["length"];_0x595b68<_0x5d67af;_0x595b68++){_0x1cb8da+="%"+("00"+_0x40abe9["charCodeAt"](_0x595b68)["toString"](16))["slice"](-2)}return decodeURIComponent(_0x1cb8da)};_0x564d["JOlKUe"]=_0x38fe8c,_0x564d["mtzfAt"]={},_0x564d["derGuy"]=!![]}const _0x447413=_0x4139fb[0];_0x564d["lnDeQy"]!==_0x447413&&(_0x564d["mtzfAt"]={},_0x564d["lnDeQy"]=_0x447413);const _0x4c158c=_0x564d["mtzfAt"][_0x127cac];return _0x4c158c===undefined?(_0x564df8=_0x564d["JOlKUe"](_0x564df8),_0x564d["mtzfAt"][_0x127cac]=_0x564df8):_0x564df8=_0x4c158c,_0x564df8}import{setRateLimitHeaders}from"./headers.js";import{evaluateSkipRule}from"../utils/skipUtils.js";export function createMiddleware(_0x1cb8da,_0x111dde){const _0x4f0db4=_0x564d;let _0x20857f=defaultKeyGenerator;if(typeof _0x111dde[_0x4f0db4(450)]===_0x4f0db4(451))_0x20857f=_0x111dde["keyGenerator"];else{if(_0x111dde[_0x4f0db4(450)]==="ip")_0x20857f=ipKeyGenerator;else{if(_0x111dde[_0x4f0db4(450)]===_0x4f0db4(455))_0x20857f=graphQLKeyGenerator;else _0x111dde[_0x4f0db4(450)]===_0x4f0db4(439)&&(_0x20857f=defaultKeyGenerator)}}const _0x321aaa=_0x111dde[_0x4f0db4(453)]!==undefined?_0x111dde[_0x4f0db4(453)]:_0x111dde[_0x4f0db4(444)]!==undefined?_0x111dde[_0x4f0db4(444)]:_0x111dde["customskip"],_0x3b1044=typeof _0x321aaa==="function"?_0x321aaa:typeof _0x321aaa===_0x4f0db4(435)&&_0x321aaa!==null?(_0x200b51,_0x2a18e2)=>evaluateSkipRule(_0x321aaa,_0x200b51,_0x2a18e2,{"generateKey":_0x20857f}):()=>![],_0x595b68=(_0x21fef2,_0x38310e,_0x1d849e,_0x195bb6)=>{const _0x5e5436=_0x4f0db4;_0x38310e["status"](_0x195bb6[_0x5e5436(467)]);const _0x5975ad=typeof _0x195bb6[_0x5e5436(436)]==="function"?_0x195bb6[_0x5e5436(436)](_0x21fef2,_0x38310e):_0x195bb6[_0x5e5436(436)];typeof _0x5975ad===_0x5e5436(435)?_0x38310e[_0x5e5436(465)](_0x5975ad):_0x38310e["send"](_0x5975ad)},_0x5d67af=_0x111dde[_0x4f0db4(433)]||_0x595b68,_0x34779a=async(_0x1d2274,_0x449b0b,_0x34baf8)=>{const _0x143cb1=_0x4f0db4;try{const _0x1c805b=await Promise[_0x143cb1(440)](_0x3b1044(_0x1d2274,_0x449b0b));if(_0x1c805b)return _0x34baf8();const _0xd33e92=await Promise[_0x143cb1(440)](_0x20857f(_0x1d2274,_0x449b0b));let _0x352b7d=1;if(typeof _0x111dde[_0x143cb1(466)]===_0x143cb1(451))_0x352b7d=await Promise[_0x143cb1(440)](_0x111dde[_0x143cb1(466)](_0x1d2274,_0x449b0b));else typeof _0x111dde["cost"]===_0x143cb1(456)&&(_0x352b7d=_0x111dde[_0x143cb1(466)]);(!Number["isFinite"](_0x352b7d)||_0x352b7d<=0)&&(_0x352b7d=1);const _0x57fc26=await _0x1cb8da[_0x143cb1(454)](_0xd33e92,_0x352b7d,{"req":_0x1d2274,"res":_0x449b0b});_0x1d2274["rateLimit"]=_0x57fc26,setRateLimitHeaders(_0x449b0b,_0x57fc26,_0x111dde);if(_0x57fc26[_0x143cb1(434)])return _0x34baf8();return _0x5d67af(_0x1d2274,_0x449b0b,_0x34baf8,_0x111dde)}catch(_0x12dde3){if(_0x111dde["failOpen"])return _0x1d2274[_0x143cb1(437)]={"allowed":!![],"limit":_0x111dde["limit"]||_0x111dde["capacity"]||0,"remaining":1,"consumed":0,"retryAfter":0,"resetTime":new Date(Date["now"]()+60000),"failedOpen":!![]},typeof _0x111dde[_0x143cb1(460)]===_0x143cb1(451)&&_0x111dde["onError"](_0x12dde3,_0x1d2274,_0x449b0b,_0x34baf8),_0x34baf8();if(typeof _0x111dde["onError"]===_0x143cb1(451))return _0x111dde[_0x143cb1(460)](_0x12dde3,_0x1d2274,_0x449b0b,_0x34baf8);return _0x34baf8(_0x12dde3)}};return _0x34779a["resetKey"]=_0x545c44=>{const _0x2f605=_0x4f0db4,_0x2c25aa=typeof _0x545c44===_0x2f605(435)&&_0x545c44!==null?_0x20857f(_0x545c44):_0x545c44;return _0x1cb8da[_0x2f605(446)](_0x2c25aa)},_0x34779a[_0x4f0db4(452)]=_0x387e51=>{const _0x5bdfc3=_0x4f0db4,_0xe7d899=typeof _0x387e51===_0x5bdfc3(435)&&_0x387e51!==null?_0x20857f(_0x387e51):_0x387e51;return _0x1cb8da[_0x5bdfc3(452)](_0xe7d899)},_0x34779a[_0x4f0db4(438)]=()=>_0x1cb8da[_0x4f0db4(438)](),_0x34779a[_0x4f0db4(459)]=_0x1cb8da,_0x34779a[_0x4f0db4(458)]=_0x1cb8da[_0x4f0db4(458)],_0x34779a}
1
+ import{defaultKeyGenerator,ipKeyGenerator,graphQLKeyGenerator}from'../utils/keyGenerator.js';import{setRateLimitHeaders}from'./headers.js';import{evaluateSkipRule}from'../utils/skipUtils.js';export function createMiddleware(algorithm,options){let keyGen=defaultKeyGenerator;if(typeof options.keyGenerator==='function'){keyGen=options.keyGenerator;}else if(options.keyGenerator==='ip'){keyGen=ipKeyGenerator;}else if(options.keyGenerator==='graphql'){keyGen=graphQLKeyGenerator;}else if(options.keyGenerator==='ip-endpoint'){keyGen=defaultKeyGenerator;}const rawSkip=options.skip!==undefined?options.skip:options.customSkip!==undefined?options.customSkip:options.customskip;const skipFn=typeof rawSkip==='function'?rawSkip:typeof rawSkip==='object'&&rawSkip!==null?(req,res)=>evaluateSkipRule(rawSkip,req,res,{generateKey:keyGen}):()=>false;const defaultHandler=(req,res,next,opts)=>{res.status(opts.statusCode);const msg=typeof opts.message==='function'?opts.message(req,res):opts.message;if(typeof msg==='object'){res.json(msg);}else{res.send(msg);}};const handler=options.handler||defaultHandler;const middleware=async(req,res,next)=>{try{const shouldSkip=await Promise.resolve(skipFn(req,res));if(shouldSkip){return next();}const key=await Promise.resolve(keyGen(req,res));let cost=1;if(typeof options.cost==='function'){cost=await Promise.resolve(options.cost(req,res));}else if(typeof options.cost==='number'){cost=options.cost;}if(!Number.isFinite(cost)||cost<=0){cost=1;}const result=await algorithm.consume(key,cost,{req,res});req.rateLimit=result;setRateLimitHeaders(res,result,options);if(result.allowed){return next();}return handler(req,res,next,options);}catch(err){if(options.failOpen){req.rateLimit={allowed:true,limit:options.limit||options.capacity||0,remaining:1,consumed:0,retryAfter:0,resetTime:new Date(Date.now()+60000),failedOpen:true};if(typeof options.onError==='function'){options.onError(err,req,res,next);}return next();}if(typeof options.onError==='function'){return options.onError(err,req,res,next);}return next(err);}};middleware.resetKey=keyOrReq=>{const k=typeof keyOrReq==='object'&&keyOrReq!==null?keyGen(keyOrReq):keyOrReq;return algorithm.reset(k);};middleware.getState=keyOrReq=>{const k=typeof keyOrReq==='object'&&keyOrReq!==null?keyGen(keyOrReq):keyOrReq;return algorithm.getState(k);};middleware.close=()=>algorithm.close();middleware.algorithm=algorithm;middleware.store=algorithm.store;return middleware;}
@@ -1 +1 @@
1
- function _0x4586(_0x121d58,_0x4d3011){_0x121d58=_0x121d58-400;const _0x2fdbdc=_0x2fdb();let _0x4586ac=_0x2fdbdc[_0x121d58];if(_0x4586["aodsQW"]===undefined){var _0x817c5=function(_0x1f9aa1){const _0x3548fa="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";let _0x40c42a="",_0x49fbbc="";for(let _0x21e45e=0,_0x5c9823,_0x14f6b5,_0x25a3dd=0;_0x14f6b5=_0x1f9aa1["charAt"](_0x25a3dd++);~_0x14f6b5&&(_0x5c9823=_0x21e45e%4?_0x5c9823*64+_0x14f6b5:_0x14f6b5,_0x21e45e++%4)?_0x40c42a+=String["fromCharCode"](255&_0x5c9823>>(-2*_0x21e45e&6)):0){_0x14f6b5=_0x3548fa["indexOf"](_0x14f6b5)}for(let _0xf4a74e=0,_0x39e114=_0x40c42a["length"];_0xf4a74e<_0x39e114;_0xf4a74e++){_0x49fbbc+="%"+("00"+_0x40c42a["charCodeAt"](_0xf4a74e)["toString"](16))["slice"](-2)}return decodeURIComponent(_0x49fbbc)};_0x4586["JDDDqb"]=_0x817c5,_0x4586["EytPzm"]={},_0x4586["aodsQW"]=!![]}const _0x3a3903=_0x2fdbdc[0];_0x4586["wvDpzY"]!==_0x3a3903&&(_0x4586["EytPzm"]={},_0x4586["wvDpzY"]=_0x3a3903);const _0x4a5732=_0x4586["EytPzm"][_0x121d58];return _0x4a5732===undefined?(_0x4586ac=_0x4586["JDDDqb"](_0x4586ac),_0x4586["EytPzm"][_0x121d58]=_0x4586ac):_0x4586ac=_0x4a5732,_0x4586ac}(function(_0x169611,_0x43572f){const _0x5262a2=_0x4586,_0x3be8ee=_0x169611();while(!![]){try{const _0x1b2f1a=-parseInt(_0x5262a2(412))/1+-parseInt(_0x5262a2(423))/2*(-parseInt(_0x5262a2(405))/3)+parseInt(_0x5262a2(401))/4*(-parseInt(_0x5262a2(428))/5)+-parseInt(_0x5262a2(425))/6*(parseInt(_0x5262a2(409))/7)+-parseInt(_0x5262a2(413))/8+-parseInt(_0x5262a2(410))/9*(parseInt(_0x5262a2(419))/10)+-parseInt(_0x5262a2(418))/11*(-parseInt(_0x5262a2(414))/12);if(_0x1b2f1a===_0x43572f)break;else _0x3be8ee["push"](_0x3be8ee["shift"]())}catch(_0xa1d63c){_0x3be8ee["push"](_0x3be8ee["shift"]())}}})(_0x2fdb,680679);export function setRateLimitHeaders(_0x49fbbc,_0x21e45e,_0x5c9823={}){const _0x1d8019=_0x4586;if(!_0x49fbbc||typeof _0x49fbbc[_0x1d8019(429)]!=="function"||_0x49fbbc["headersSent"])return;const _0x14f6b5=Date[_0x1d8019(406)](),_0x25a3dd=_0x21e45e[_0x1d8019(404)]instanceof Date?_0x21e45e["resetTime"][_0x1d8019(421)]():Number(_0x21e45e[_0x1d8019(404)]),_0xf4a74e=Math[_0x1d8019(427)](0,Math[_0x1d8019(400)]((_0x25a3dd-_0x14f6b5)/1000));if(_0x5c9823["standardHeaders"]){_0x49fbbc["setHeader"]("RateLimit-Limit",String(_0x21e45e[_0x1d8019(422)])),_0x49fbbc[_0x1d8019(429)](_0x1d8019(415),String(_0x21e45e[_0x1d8019(408)])),_0x49fbbc[_0x1d8019(429)]("RateLimit-Reset",String(_0xf4a74e));if(_0x5c9823[_0x1d8019(402)]!==_0x1d8019(403)&&_0x5c9823[_0x1d8019(426)]){const _0x39e114=Math[_0x1d8019(400)](_0x5c9823[_0x1d8019(426)]/1000);_0x49fbbc[_0x1d8019(429)](_0x1d8019(416),_0x21e45e[_0x1d8019(422)]+_0x1d8019(407)+_0x39e114)}}_0x5c9823[_0x1d8019(417)]&&(_0x49fbbc[_0x1d8019(429)](_0x1d8019(420),String(_0x21e45e[_0x1d8019(422)])),_0x49fbbc[_0x1d8019(429)]("X-RateLimit-Remaining",String(_0x21e45e[_0x1d8019(408)])),_0x49fbbc["setHeader"]("X-RateLimit-Reset",String(Math[_0x1d8019(400)](_0x25a3dd/1000))));if(!_0x21e45e[_0x1d8019(424)]){const _0x5d9228=Math["max"](1,Number(_0x21e45e[_0x1d8019(430)])||_0xf4a74e||1);_0x49fbbc[_0x1d8019(429)](_0x1d8019(411),String(_0x5d9228))}}function _0x2fdb(){const _0x9f2c6=["mtmYnNzJsLjuEG","ywXSB3DLza","nti1nMPMBvHozG","D2LUzg93txm","Bwf4","mJiZmJuZmhnVvKDUuG","C2v0sgvHzgvY","CMv0CNLbzNrLCG","y2vPBa","mtjLBNrJz1y","C3rHBMrHCMrizwfKzxjZ","zhjHzNqTnG","CMvZzxruAw1L","mtC0ovnlEhbiva","BM93","o3C9","CMvTywLUAw5N","mZy4mLnmvwXltq","mJeXotKYm2PhtLfQsW","uMv0CNKTqwz0zxi","mta1mdK1mgXeBfPrDG","mJm4odCXmMTLwwftua","mtjYBMniwxa","uMf0zuXPBwL0lvjLBwfPBMLUzW","uMf0zuXPBwL0lvbVBgLJEq","BgvNywn5sgvHzgvYCW","nta4mZG4otHnu3jduw8","ntbKBfbTy2G","wc1syxrLtgLTAxqTtgLTAxq","z2v0vgLTzq","BgLTAxq"];_0x2fdb=function(){return _0x9f2c6};return _0x2fdb()}
1
+ export function setRateLimitHeaders(res,result,options={}){if(!res||typeof res.setHeader!=='function'||res.headersSent){return;}const now=Date.now();const resetMs=result.resetTime instanceof Date?result.resetTime.getTime():Number(result.resetTime);const resetSeconds=Math.max(0,Math.ceil((resetMs-now)/1000));if(options.standardHeaders){res.setHeader('RateLimit-Limit',String(result.limit));res.setHeader('RateLimit-Remaining',String(result.remaining));res.setHeader('RateLimit-Reset',String(resetSeconds));if(options.standardHeaders!=='draft-6'&&options.windowMs){const windowSec=Math.ceil(options.windowMs/1000);res.setHeader('RateLimit-Policy',`${result.limit};w=${windowSec}`);}}if(options.legacyHeaders){res.setHeader('X-RateLimit-Limit',String(result.limit));res.setHeader('X-RateLimit-Remaining',String(result.remaining));res.setHeader('X-RateLimit-Reset',String(Math.ceil(resetMs/1000)));}if(!result.allowed){const retryAfter=Math.max(1,Number(result.retryAfter)||resetSeconds||1);res.setHeader('Retry-After',String(retryAfter));}}
package/dist/rateLimit.js CHANGED
@@ -1 +1 @@
1
- (function(_0x38af71,_0x1dabd6){const _0x35dd44=_0x5f52,_0x45e326=_0x38af71();while(!![]){try{const _0x370b10=-parseInt(_0x35dd44(420))/1+-parseInt(_0x35dd44(414))/2+parseInt(_0x35dd44(405))/3*(-parseInt(_0x35dd44(406))/4)+parseInt(_0x35dd44(413))/5*(parseInt(_0x35dd44(412))/6)+parseInt(_0x35dd44(408))/7*(parseInt(_0x35dd44(416))/8)+-parseInt(_0x35dd44(418))/9*(-parseInt(_0x35dd44(410))/10)+parseInt(_0x35dd44(407))/11*(parseInt(_0x35dd44(409))/12);if(_0x370b10===_0x1dabd6)break;else _0x45e326["push"](_0x45e326["shift"]())}catch(_0x4cfac9){_0x45e326["push"](_0x45e326["shift"]())}}})(_0x1a0b,266126);import{validateOptions}from"./utils/validation.js";import{MemoryStore}from"./stores/memoryStore.js";import{RedisStore}from"./stores/redisStore.js";function _0x1a0b(){const _0x4be290=["C3rYAw5N","ndbWvNnyCKi","C3rVCMu","nti4m01Ys0nHzG","ywXNB3jPDgHT","mtm1nZCXs1L5qwLo","m0fxAgLyDG","mJeYmti1nLL6AvrYBW","mJiZnJnoDhvgCNG","mtaWoda3uM9pwMvx","ntu5mKPUA1nlyG","mteWzfnPrKzN","CMvKAxm","mtm5ntu0Dvjvy2PT","nvHyruDbDG","mJmZnZC2CNHNthL5"];_0x1a0b=function(){return _0x4be290};return _0x1a0b()}import{FixedWindow}from"./algorithms/fixedWindow.js";import{SlidingWindowLog}from"./algorithms/slidingWindowLog.js";import{SlidingWindowCounter}from"./algorithms/slidingWindowCounter.js";import{TokenBucket}from"./algorithms/tokenBucket.js";import{LeakyBucket}from"./algorithms/leakyBucket.js";import{createMiddleware}from"./middleware/expressMiddleware.js";function _0x5f52(_0x1038c9,_0x594de4){_0x1038c9=_0x1038c9-405;const _0x1a0bba=_0x1a0b();let _0x5f52f6=_0x1a0bba[_0x1038c9];if(_0x5f52["kjncMp"]===undefined){var _0x4ffee0=function(_0x27e807){const _0x1c16f0="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";let _0x3dae53="",_0x38d325="";for(let _0x4463b3=0,_0xd1539d,_0x35b667,_0x2e316c=0;_0x35b667=_0x27e807["charAt"](_0x2e316c++);~_0x35b667&&(_0xd1539d=_0x4463b3%4?_0xd1539d*64+_0x35b667:_0x35b667,_0x4463b3++%4)?_0x3dae53+=String["fromCharCode"](255&_0xd1539d>>(-2*_0x4463b3&6)):0){_0x35b667=_0x1c16f0["indexOf"](_0x35b667)}for(let _0x119c77=0,_0x2386b1=_0x3dae53["length"];_0x119c77<_0x2386b1;_0x119c77++){_0x38d325+="%"+("00"+_0x3dae53["charCodeAt"](_0x119c77)["toString"](16))["slice"](-2)}return decodeURIComponent(_0x38d325)};_0x5f52["dHnXHP"]=_0x4ffee0,_0x5f52["UmAxYd"]={},_0x5f52["kjncMp"]=!![]}const _0x18c151=_0x1a0bba[0];_0x5f52["npznos"]!==_0x18c151&&(_0x5f52["UmAxYd"]={},_0x5f52["npznos"]=_0x18c151);const _0x3c9f95=_0x5f52["UmAxYd"][_0x1038c9];return _0x3c9f95===undefined?(_0x5f52f6=_0x5f52["dHnXHP"](_0x5f52f6),_0x5f52["UmAxYd"][_0x1038c9]=_0x5f52f6):_0x5f52f6=_0x3c9f95,_0x5f52f6}const ALGORITHM_MAP={"fixed-window":FixedWindow,"sliding-window-log":SlidingWindowLog,"sliding-window-counter":SlidingWindowCounter,"token-bucket":TokenBucket,"leaky-bucket":LeakyBucket};export function rateLimit(_0x38d325={}){const _0x5486e6=_0x5f52,_0x4463b3=validateOptions(_0x38d325);let _0xd1539d;if(typeof _0x4463b3[_0x5486e6(417)]===_0x5486e6(415))_0x4463b3["store"]==="redis"?_0xd1539d=new RedisStore({..._0x4463b3,"redis":_0x4463b3[_0x5486e6(411)]||{}}):_0xd1539d=new MemoryStore(_0x4463b3);else _0x4463b3["store"]?_0xd1539d=_0x4463b3[_0x5486e6(417)]:_0xd1539d=new MemoryStore(_0x4463b3);const _0x35b667=ALGORITHM_MAP[_0x4463b3[_0x5486e6(419)]],_0x2e316c=new _0x35b667(_0x4463b3,_0xd1539d);return createMiddleware(_0x2e316c,_0x4463b3)}export const createLimiter=rateLimit;export default rateLimit;
1
+ import{validateOptions}from'./utils/validation.js';import{MemoryStore}from'./stores/memoryStore.js';import{RedisStore}from'./stores/redisStore.js';import{FixedWindow}from'./algorithms/fixedWindow.js';import{SlidingWindowLog}from'./algorithms/slidingWindowLog.js';import{SlidingWindowCounter}from'./algorithms/slidingWindowCounter.js';import{TokenBucket}from'./algorithms/tokenBucket.js';import{LeakyBucket}from'./algorithms/leakyBucket.js';import{createMiddleware}from'./middleware/expressMiddleware.js';const ALGORITHM_MAP={'fixed-window':FixedWindow,'sliding-window-log':SlidingWindowLog,'sliding-window-counter':SlidingWindowCounter,'token-bucket':TokenBucket,'leaky-bucket':LeakyBucket};export function rateLimit(rawOptions={}){const options=validateOptions(rawOptions);let store;if(typeof options.store==='string'){const s=options.store.toLowerCase();if(s==='redis'||s==='redis-cluster'||s==='cluster'){const isClusterStore=s==='redis-cluster'||s==='cluster';store=new RedisStore({...options,isCluster:options.isCluster!==undefined?options.isCluster:isClusterStore,hashTag:options.hashTag!==undefined?options.hashTag:isClusterStore?true:undefined,redis:options.redis||{}});}else{store=new MemoryStore(options);}}else if(options.store){store=options.store;}else if(options.client||options.redisClient||options.clusterClient||options.redisCluster||options.cluster||options.clusters||options.clusterNodes){store=new RedisStore(options);}else{store=new MemoryStore(options);}const AlgorithmClass=ALGORITHM_MAP[options.algorithm];const algorithm=new AlgorithmClass(options,store);return createMiddleware(algorithm,options);}export const createLimiter=rateLimit;export default rateLimit;