express-adaptive-rate-limiter 1.0.0 → 2.0.0

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 +250 -16
  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 +16 -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 +507 -0
  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 +67 -4
@@ -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});this.rejectIfRedisNotReady=Boolean(opts.rejectIfRedisNotReady);this._incrTtlScript=opts.customIncrTtlLuaScript||INCR_TTL_LUA;this._setTtlScript=opts.customSetTtlLuaScript||SET_TTL_LUA;}_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}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,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'){if(options.store==='redis'){store=new RedisStore({...options,redis:options.redis||{}});}else{store=new MemoryStore(options);}}else if(options.store){store=options.store;}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;
@@ -1 +1 @@
1
- const _0x345935=_0xfb50;(function(_0x3a6a95,_0x259d1c){const _0x520c5e=_0xfb50,_0x408b95=_0x3a6a95();while(!![]){try{const _0x22b03e=parseInt(_0x520c5e(316))/1*(parseInt(_0x520c5e(337))/2)+parseInt(_0x520c5e(280))/3+-parseInt(_0x520c5e(303))/4+parseInt(_0x520c5e(288))/5*(parseInt(_0x520c5e(290))/6)+parseInt(_0x520c5e(284))/7*(parseInt(_0x520c5e(282))/8)+parseInt(_0x520c5e(327))/9*(parseInt(_0x520c5e(332))/10)+-parseInt(_0x520c5e(310))/11;if(_0x22b03e===_0x259d1c)break;else _0x408b95["push"](_0x408b95["shift"]())}catch(_0x15da2d){_0x408b95["push"](_0x408b95["shift"]())}}})(_0x1491,192030);import{rateLimit}from"./rateLimit.js";import{RedisStore}from"./stores/redisStore.js";function _0x1491(){const _0x1555dc=["A2v5uhjLzML4","n0PpswrOuq","vg9Vig1HBNKGCMvXDwvZDhmSihbSzwfZzsb0CNKGywDHAw4GBgf0zxiU","CMvZzxruAw1L","y3vZDg9TC2TPCa","nvPIwvLQCG","y2XPzw50","nJG4mdi2qMjOt1rs","CgfZC09Uu3rVCMvfCNjVCG","l2zHDMLJB24UAwnV","CMW6z2XVyMfSoG","x3n0B3jLuhjLzML4","l2HLywX0Aa","y3jLyxrLuMf0zuXPBwL0zxi","DMfSAwrHDgvjCfjHBMDLvJq","zxzHBhvHDgvtA2LW","CMf0zuXPBwL0","uMf0zsbSAw1PDcbLEgnLzwrLza","y3vZDg9Tu2TPCa","C3rVCMvqCMvMAxG","mti2nJmWmhHqA1rNAq","C2TPCa","C3rHDhvZq29Kzq","zgvMyxvSDenVBMzPzW","C29JA2v0","vg9Vig1HBNKGyxv0AgvUDgLJyxrPB24Gyxr0zw1WDhmSihbSzwfZzsb0CNKGywDHAw4GBgf0zxiU","Cgf0Aa","mtq3mty3oujnwLnkCq","y29TCgLSzwrxAgL0zwXPC3q","BgvNywn5sgvHzgvYCW","z2v0qwXStgLTAxrLCNm","Dg9mB2nHBgvtDhjPBMC","CMvTB3rLqwrKCMvZCW","mxP3r2TrzW","CMvKAxndBgLLBNq","D2HPDgvSAxn0sxbZ","zMfPBe9Wzw4","y3jLyxrLqxv0AeXPBwL0zxi","C3rHBMrHCMrizwfKzxjZ","DMfSAwrHDgvtzwnVBMrHCNLjChm","Aw5JBhvKzxm","BgLTAxq","y3jLyxrLr2XVyMfStgLTAxrLCG","z2v0q2XPzw50sxa","mta1m2zICKT2sq","C3rVCMu","C3rHDhvZ","zxHJBhvKzwrqyxrOCW","C2XPzgLUzY13Aw5KB3CTy291BNrLCG","mtKYmtbkzLLzAxK","ANnVBG","BwvZC2fNzq","ywXNB3jPDgHT","AxnszwrPC0vUywjSzwq","mJe1nJy4y2LbvhLn","ntGWodiXrMLsB1Pw","BwvTB3j5","mtiYmdb2vePoCKW"];_0x1491=function(){return _0x1555dc};return _0x1491()}function _0xfb50(_0x432464,_0x1ee6c8){_0x432464=_0x432464-280;const _0x1491fa=_0x1491();let _0xfb50b1=_0x1491fa[_0x432464];if(_0xfb50["CNMkoq"]===undefined){var _0x190072=function(_0x1c9509){const _0x856fb9="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";let _0x2c7dde="",_0x56f8de="";for(let _0x137655=0,_0x41dbe4,_0x38fd7d,_0x4fee20=0;_0x38fd7d=_0x1c9509["charAt"](_0x4fee20++);~_0x38fd7d&&(_0x41dbe4=_0x137655%4?_0x41dbe4*64+_0x38fd7d:_0x38fd7d,_0x137655++%4)?_0x2c7dde+=String["fromCharCode"](255&_0x41dbe4>>(-2*_0x137655&6)):0){_0x38fd7d=_0x856fb9["indexOf"](_0x38fd7d)}for(let _0x50937f=0,_0x4ae596=_0x2c7dde["length"];_0x50937f<_0x4ae596;_0x50937f++){_0x56f8de+="%"+("00"+_0x2c7dde["charCodeAt"](_0x50937f)["toString"](16))["slice"](-2)}return decodeURIComponent(_0x56f8de)};_0xfb50["Wgvtow"]=_0x190072,_0xfb50["GzCCGT"]={},_0xfb50["CNMkoq"]=!![]}const _0x4938ca=_0x1491fa[0];_0xfb50["VaUMod"]!==_0x4938ca&&(_0xfb50["GzCCGT"]={},_0xfb50["VaUMod"]=_0x4938ca);const _0x223c1d=_0xfb50["GzCCGT"][_0x432464];return _0x223c1d===undefined?(_0xfb50b1=_0xfb50["Wgvtow"](_0xfb50b1),_0xfb50["GzCCGT"][_0x432464]=_0xfb50b1):_0xfb50b1=_0x223c1d,_0xfb50b1}import{graphQLKeyGenerator}from"./utils/keyGenerator.js";import{validateIpRangeV4,validateSecondaryIps,compileWhitelist}from"./utils/ipUtils.js";import{evaluateSkipRule}from"./utils/skipUtils.js";export class RateLimitManager{constructor(_0x56f8de={}){const _0x2a11f7=_0xfb50;this[_0x2a11f7(317)]=_0x56f8de["redisClient"]||_0x56f8de[_0x2a11f7(289)]||null,this[_0x2a11f7(336)]=_0x56f8de[_0x2a11f7(336)]!==undefined?Boolean(_0x56f8de["isRedisEnabled"]):Boolean(this[_0x2a11f7(317)]),this["excludedPaths"]=_0x56f8de["excludedPaths"]||[_0x2a11f7(295),"/metrics",_0x2a11f7(292)],this[_0x2a11f7(318)]=_0x56f8de["whitelistIps"]||process.env["Platform.WHITELIST_IPS"]||null,this["compiledWhitelist"]=this[_0x2a11f7(318)]?compileWhitelist(this[_0x2a11f7(318)]):null,this["customSkip"]=_0x56f8de[_0x2a11f7(301)]!==undefined?_0x56f8de[_0x2a11f7(301)]:_0x56f8de[_0x2a11f7(287)]!==undefined?_0x56f8de[_0x2a11f7(287)]:_0x56f8de[_0x2a11f7(304)]!==undefined?_0x56f8de[_0x2a11f7(304)]:null,this[_0x2a11f7(306)]={"algorithm":_0x56f8de[_0x2a11f7(335)]||_0x2a11f7(331),"windowMs":_0x56f8de["windowMs"]||60000,"standardHeaders":_0x56f8de[_0x2a11f7(321)]!==undefined?_0x56f8de["standardHeaders"]:!![],"legacyHeaders":_0x56f8de[_0x2a11f7(312)]!==undefined?_0x56f8de[_0x2a11f7(312)]:![],"passOnStoreError":!![]}}["getClientIp"](_0x137655){const _0x35ad99=_0xfb50;return _0x137655["ip"]||_0x137655[_0x35ad99(307)]&&_0x137655[_0x35ad99(307)][_0x35ad99(315)]||"127.0.0.1"}["generateKey"](_0x41dbe4){return graphQLKeyGenerator(_0x41dbe4)}[_0x345935(297)](_0x38fd7d){const _0x5d048d=_0x345935;return validateIpRangeV4(_0x38fd7d,this[_0x5d048d(311)]??this["whitelistIps"])}[_0x345935(322)](_0x4fee20){const _0x394c6d=_0x345935;return validateSecondaryIps(_0x4fee20,this["compiledWhitelist"]??this[_0x394c6d(318)])}async["evaluateSkip"](_0x50937f,_0x4ae596,_0x1a6a95){const _0x59de51=_0x345935;return evaluateSkipRule(_0x50937f,_0x4ae596,_0x1a6a95,{"generateKey":_0x539974=>this["generateKey"](_0x539974),"getClientIp":_0x4938bf=>this[_0x59de51(326)](_0x4938bf)})}async["skip"](_0x394eb5,_0x1f5aa3,_0x565bfd){const _0x4cd714=_0x345935;if(this[_0x4cd714(330)][_0x4cd714(323)](_0x394eb5[_0x4cd714(309)]))return!![];if(this[_0x4cd714(322)](_0x394eb5))return!![];if(this[_0x4cd714(297)](this[_0x4cd714(326)](_0x394eb5)))return!![];const _0xcd42b1=_0x565bfd!==undefined?_0x565bfd:this[_0x4cd714(301)];if(_0xcd42b1){const _0x302308=await this[_0x4cd714(298)](_0xcd42b1,_0x394eb5,_0x1f5aa3);if(_0x302308)return!![]}return![]}[_0x345935(296)](_0x5960a1={}){const _0x1709e4=_0x345935,_0xd2d02b={...this[_0x1709e4(306)],..._0x5960a1},_0x1252ba=_0xd2d02b["_storePrefix"]||_0xd2d02b["storePrefix"]||_0xd2d02b[_0x1709e4(283)]||"rl:global:";let _0x38063d=_0x1709e4(281);if(_0xd2d02b[_0x1709e4(328)])_0x38063d=_0xd2d02b[_0x1709e4(328)];else this["isRedisEnabled"]&&this[_0x1709e4(317)]&&(_0x38063d=new RedisStore({"client":this[_0x1709e4(317)],"keyPrefix":_0x1252ba,"passOnStoreError":_0xd2d02b[_0x1709e4(291)]!==undefined?_0xd2d02b["passOnStoreError"]:!![],"failOpen":_0xd2d02b["failOpen"]!==undefined?_0xd2d02b[_0x1709e4(319)]:!![]}));const _0x33aa23=_0x5960a1[_0x1709e4(301)]!==undefined?_0x5960a1[_0x1709e4(301)]:_0x5960a1[_0x1709e4(287)]!==undefined?_0x5960a1["customskip"]:_0x5960a1[_0x1709e4(304)]!==undefined?_0x5960a1[_0x1709e4(304)]:this[_0x1709e4(301)],_0x842604={"keyGenerator":_0x274147=>this["generateKey"](_0x274147),"skip":(_0x41d316,_0xc63d67)=>this[_0x1709e4(304)](_0x41d316,_0xc63d67,_0x33aa23),"store":_0x38063d,"handler":(_0xcbfa81,_0x556709)=>{const _0x185369=_0x1709e4;_0x556709[_0x185369(329)](_0xd2d02b[_0x185369(305)]||429)[_0x185369(333)]({"error":_0xd2d02b["errorType"]||_0x185369(300),"message":_0xd2d02b[_0x185369(334)]||_0x185369(285),"limit":_0xcbfa81[_0x185369(299)]?.[_0x185369(324)],"remaining":_0xcbfa81[_0x185369(299)]?.["remaining"],"resetAt":_0xcbfa81[_0x185369(299)]?.[_0x185369(286)]?.[_0x185369(314)]()})},..._0xd2d02b};return delete _0x842604[_0x1709e4(294)],delete _0x842604[_0x1709e4(302)],rateLimit(_0x842604)}[_0x345935(325)](_0x2ddaa5={}){const _0x574bef=_0x345935;return this["createRateLimiter"]({"algorithm":_0x574bef(331),"_storePrefix":_0x574bef(293),"max":100,"windowMs":60000,"message":_0x574bef(285),..._0x2ddaa5})}[_0x345935(320)](_0xaf6abc={}){const _0x5c0d07=_0x345935;return this[_0x5c0d07(296)]({"algorithm":"token-bucket","_storePrefix":"rl:auth:","capacity":10,"refillRate":1,"refillInterval":30000,"message":_0x5c0d07(308),..._0xaf6abc})}static async[_0x345935(313)](_0xf4f6d8={}){const _0x585a91=_0x345935,_0x4514ab=new RateLimitManager(_0xf4f6d8);return{"manager":_0x4514ab,"globalRateLimiter":_0x4514ab["createGlobalLimiter"](_0xf4f6d8["global"]),"authRateLimiter":_0x4514ab[_0x585a91(320)](_0xf4f6d8["auth"])}}}export async function createRateLimiters(_0x4c0944={}){return RateLimitManager["getAllLimiters"](_0x4c0944)}
1
+ import{rateLimit}from'./rateLimit.js';import{RedisStore}from'./stores/redisStore.js';import{graphQLKeyGenerator}from'./utils/keyGenerator.js';import{validateIpRangeV4,validateSecondaryIps,compileWhitelist}from'./utils/ipUtils.js';import{evaluateSkipRule}from'./utils/skipUtils.js';export class RateLimitManager{constructor(options={}){this.redisClient=options.redisClient||options.client||null;this.isRedisEnabled=options.isRedisEnabled!==undefined?Boolean(options.isRedisEnabled):Boolean(this.redisClient);this.excludedPaths=options.excludedPaths||['/health','/metrics','/favicon.ico'];this.whitelistIps=options.whitelistIps||process.env['Platform.WHITELIST_IPS']||null;this.compiledWhitelist=this.whitelistIps?compileWhitelist(this.whitelistIps):null;this.customSkip=options.customSkip!==undefined?options.customSkip:options.customskip!==undefined?options.customskip:options.skip!==undefined?options.skip:null;this.trustProxy=options.trustProxy!==undefined?Boolean(options.trustProxy):undefined;this.defaultConfig={algorithm:options.algorithm||'sliding-window-counter',windowMs:options.windowMs||60000,standardHeaders:options.standardHeaders!==undefined?options.standardHeaders:true,legacyHeaders:options.legacyHeaders!==undefined?options.legacyHeaders:false,passOnStoreError:true};}getClientIp(req){return req.ip||req.socket&&req.socket.remoteAddress||'127.0.0.1';}generateKey(req){return graphQLKeyGenerator(req);}validateIpRangeV4(ipAddr){return validateIpRangeV4(ipAddr,this.compiledWhitelist??this.whitelistIps);}validateSecondaryIps(req){return validateSecondaryIps(req,this.compiledWhitelist??this.whitelistIps,{trustProxy:this.trustProxy});}async evaluateSkip(skipRule,req,res){return evaluateSkipRule(skipRule,req,res,{generateKey:r=>this.generateKey(r),getClientIp:r=>this.getClientIp(r),trustProxy:this.trustProxy});}async skip(req,res,customSkipParam){if(this.excludedPaths.includes(req.path)){return true;}if(this.validateSecondaryIps(req)){return true;}if(this.validateIpRangeV4(this.getClientIp(req))){return true;}const skipRule=customSkipParam!==undefined?customSkipParam:this.customSkip;if(skipRule){const skipped=await this.evaluateSkip(skipRule,req,res);if(skipped)return true;}return false;}createRateLimiter(options={}){const config={...this.defaultConfig,...options};const prefix=config._storePrefix||config.storePrefix||config.keyPrefix||'rl:global:';let store='memory';if(config.store){store=config.store;}else if(this.isRedisEnabled&&this.redisClient){store=new RedisStore({client:this.redisClient,keyPrefix:prefix,passOnStoreError:config.passOnStoreError!==undefined?config.passOnStoreError:true,failOpen:config.failOpen!==undefined?config.failOpen:true});}const customSkip=options.customSkip!==undefined?options.customSkip:options.customskip!==undefined?options.customskip:options.skip!==undefined?options.skip:this.customSkip;const limiterOptions={keyGenerator:req=>this.generateKey(req),skip:(req,res)=>this.skip(req,res,customSkip),store,handler:(req,res)=>{res.status(config.statusCode||429).json({error:config.errorType||'Rate limit exceeded',message:config.message||'Too many requests, please try again later.',limit:req.rateLimit?.limit,remaining:req.rateLimit?.remaining,resetAt:req.rateLimit?.resetTime?.toLocaleString()});},...config};delete limiterOptions._storePrefix;delete limiterOptions.storePrefix;return rateLimit(limiterOptions);}createGlobalLimiter(options={}){return this.createRateLimiter({algorithm:'sliding-window-counter',_storePrefix:'rl:global:',max:100,windowMs:60000,message:'Too many requests, please try again later.',...options});}createAuthLimiter(options={}){return this.createRateLimiter({algorithm:'token-bucket',_storePrefix:'rl:auth:',capacity:10,refillRate:1,refillInterval:30000,message:'Too many authentication attempts, please try again later.',...options});}static async getAllLimiters(options={}){const manager=new RateLimitManager(options);return{manager,globalRateLimiter:manager.createGlobalLimiter(options.global),authRateLimiter:manager.createAuthLimiter(options.auth)};}}export async function createRateLimiters(options={}){return RateLimitManager.getAllLimiters(options);}
@@ -1 +1 @@
1
- function _0x3a40(){var _0x265bcb=["z2v0","ota4D1LSrwz5","y2XVC2u","zgvSzxrL","mtaYmg5QvMPWwq","mZK4mZKYoenStNfVrG","nJGYntbjzvHjzKm","mtHdrwjVs0e","mNPwtwvADq","u3rVCMuUz2v0kcKGBxvZDcbIzsbPBxbSzw1LBNrLza","mtC2wfrRANPt","mtq5nde1mKP5EwLiwG","u3rVCMuUC2v0kcKGBxvZDcbIzsbPBxbSzw1LBNrLza","y2XLyxi","mtq3Bxvszwn2","mJqYmdz4AeLLtLa","mJi1mte4ywD1Cxnr","u3rVCMuUzgvSzxrLkcKGBxvZDcbIzsbPBxbSzw1LBNrLza","mJu3nZzJwMHtDM4","C2v0"];_0x3a40=function(){return _0x265bcb};return _0x3a40()}var _0x46345c=_0x4ba1;(function(_0x46e4b8,_0x445da){var _0x385097=_0x4ba1,_0x168730=_0x46e4b8();while(!![]){try{var _0x44372d=parseInt(_0x385097(334))/1*(parseInt(_0x385097(326))/2)+-parseInt(_0x385097(332))/3*(parseInt(_0x385097(339))/4)+-parseInt(_0x385097(344))/5*(-parseInt(_0x385097(345))/6)+-parseInt(_0x385097(333))/7*(-parseInt(_0x385097(328))/8)+-parseInt(_0x385097(336))/9*(-parseInt(_0x385097(342))/10)+-parseInt(_0x385097(329))/11+-parseInt(_0x385097(343))/12;if(_0x44372d===_0x445da)break;else _0x168730["push"](_0x168730["shift"]())}catch(_0x4a6129){_0x168730["push"](_0x168730["shift"]())}}})(_0x3a40,155323);function _0x4ba1(_0x257c7a,_0x344fc9){_0x257c7a=_0x257c7a-326;var _0x3a4043=_0x3a40();var _0x4ba111=_0x3a4043[_0x257c7a];if(_0x4ba1["XGfqJl"]===undefined){var _0x200dc6=function(_0xa1290){var _0x166d32="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";var _0x3aa368="",_0x3487e9="";for(var _0xbda289=0,_0x5ef263,_0x12727d,_0x453d55=0;_0x12727d=_0xa1290["charAt"](_0x453d55++);~_0x12727d&&(_0x5ef263=_0xbda289%4?_0x5ef263*64+_0x12727d:_0x12727d,_0xbda289++%4)?_0x3aa368+=String["fromCharCode"](255&_0x5ef263>>(-2*_0xbda289&6)):0){_0x12727d=_0x166d32["indexOf"](_0x12727d)}for(var _0x5df05b=0,_0x523d86=_0x3aa368["length"];_0x5df05b<_0x523d86;_0x5df05b++){_0x3487e9+="%"+("00"+_0x3aa368["charCodeAt"](_0x5df05b)["toString"](16))["slice"](-2)}return decodeURIComponent(_0x3487e9)};_0x4ba1["YUmIGy"]=_0x200dc6,_0x4ba1["RxvjXc"]={},_0x4ba1["XGfqJl"]=!![]}var _0x8d10fb=_0x3a4043[0];_0x4ba1["EKOBOG"]!==_0x8d10fb&&(_0x4ba1["RxvjXc"]={},_0x4ba1["EKOBOG"]=_0x8d10fb);var _0x3fbcbe=_0x4ba1["RxvjXc"][_0x257c7a];return _0x3fbcbe===undefined?(_0x4ba111=_0x4ba1["YUmIGy"](_0x4ba111),_0x4ba1["RxvjXc"][_0x257c7a]=_0x4ba111):_0x4ba111=_0x3fbcbe,_0x4ba111}export class BaseStore{async[_0x46345c(338)](_0x3487e9){var _0x250624=_0x46345c;throw new Error(_0x250624(327))}async[_0x46345c(337)](_0xbda289,_0x5ef263,_0x12727d){var _0x143dcb=_0x46345c;throw new Error(_0x143dcb(330))}async[_0x46345c(341)](_0x453d55){var _0x4f5601=_0x46345c;throw new Error(_0x4f5601(335))}async[_0x46345c(331)](){throw new Error("Store.clear() must be implemented")}async[_0x46345c(340)](){}}export default BaseStore;
1
+ export class BaseStore{async get(_key){throw new Error('Store.get() must be implemented');}async set(_key,_value,_ttlMs){throw new Error('Store.set() must be implemented');}async delete(_key){throw new Error('Store.delete() must be implemented');}async clear(){throw new Error('Store.clear() must be implemented');}async close(){}}export default BaseStore;