UNPKG

tiny-essentials

Version:

Collection of small, essential scripts designed to be used across various projects. These simple utilities are crafted for speed, ease of use, and versatility.

1 lines 6.1 kB
(()=>{"use strict";var e={d:(t,r)=>{for(var i in r)e.o(r,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:r[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{TinyRateLimiter:()=>r});const r=class{#e=null;#t=null;#r=null;#i=null;#s=null;#n=null;groupData=new Map;lastSeen=new Map;userToGroup=new Map;groupFlags=new Map;groupTTL=new Map;#o=null;setOnMemoryExceeded(e){if("function"!=typeof e)throw new Error("onMemoryExceeded must be a function");this.#o=e}clearOnMemoryExceeded(){this.#o=null}#a=null;setOnGroupExpired(e){if("function"!=typeof e)throw new Error("onGroupExpired must be a function");this.#a=e}clearOnGroupExpired(){this.#a=null}constructor({maxHits:e,interval:t,cleanupInterval:r,maxIdle:i=3e5,maxMemory:s=1e5}){const n=e=>"number"==typeof e&&Number.isFinite(e)&&e>=1&&Number.isInteger(e),o=n(e),a=n(t),u=n(r),l=n(i);if(!o&&!a)throw new Error("RateLimiter requires at least one valid option: 'maxHits' or 'interval'.");if(void 0!==e&&!o)throw new Error("'maxHits' must be a positive integer if defined.");if(void 0!==t&&!a)throw new Error("'interval' must be a positive integer in milliseconds if defined.");if(void 0!==r&&!u)throw new Error("'cleanupInterval' must be a positive integer in milliseconds if defined.");if(!l)throw new Error("'maxIdle' must be a positive integer in milliseconds.");if("number"==typeof s&&Number.isFinite(s)&&s>0)this.#e=Math.floor(s);else{if(null!=s)throw new Error("maxMemory must be a positive number or null");this.#e=null}this.#r=o?e:null,this.#i=a?t:null,this.#s=u?r:null,this.#n=i,null!==this.#s&&(this.#t=setInterval(()=>this._cleanup(),this.#s))}isGroupId(e){const t=this.groupFlags.get(e);return"boolean"==typeof t&&t}getUsersInGroup(e){const t=[];for(const[r,i]of this.userToGroup.entries())i===e&&t.push(r);return t}setGroupTTL(e,t){if("number"!=typeof t||!Number.isFinite(t)||t<=0)throw new Error("TTL must be a positive number in milliseconds");this.groupTTL.set(e,t)}getGroupTTL(e){return this.groupTTL.get(e)??null}deleteGroupTTL(e){this.groupTTL.delete(e)}assignToGroup(e,t){const r=this.userToGroup.get(e);if(r&&r!==t)throw new Error(`User ${e} is already assigned to group ${r}`);if(r===t)return;const i=this.groupData.get(e);if(this.isGroupId(e)){for(const[r,i]of this.userToGroup.entries())i===e&&this.userToGroup.set(r,t);this.userToGroup.delete(e)}else this.userToGroup.set(e,t);if(!i)return;const s=this.groupData.get(t);if(s)for(const e of i)s.push(e);else{const e=[];for(const t of i)e.push(t);this.groupData.set(t,e)}this.lastSeen.set(t,Date.now()),this.groupFlags.delete(e),this.groupData.delete(e),this.lastSeen.delete(e),this.groupTTL.delete(e),this.groupFlags.set(t,!0)}getGroupId(e){return this.userToGroup.get(e)||e}hit(e){const t=this.getGroupId(e),r=Date.now();this.groupData.has(t)||(this.groupData.set(t,[]),this.groupFlags.set(t,!1));const i=this.groupData.get(t);if(!i)throw new Error(`No data found for groupId: ${t}`);if(i.push(r),this.lastSeen.set(t,r),null!==this.#i){const e=r-this.getInterval();for(;i.length&&i[0]<e;)i.shift()}null!==this.#e&&"number"==typeof this.#e&&i.length>this.#e&&(i.splice(0,i.length-this.#e),"function"==typeof this.#o&&this.#o(t))}isRateLimited(e){const t=this.getGroupId(e);if(!this.groupData.has(t))return!1;const r=this.groupData.get(t);if(!r)throw new Error(`No data found for groupId: ${t}`);if(null!==this.#i){const e=Date.now()-this.getInterval();let t=0;for(let i=r.length-1;i>=0&&r[i]>e;i--)t++;return null!==this.#r?t>this.getMaxHits():t>0}return null!==this.#r&&r.length>this.getMaxHits()}resetGroup(e){this.groupFlags.delete(e),this.groupData.delete(e),this.lastSeen.delete(e),this.groupTTL.delete(e)}reset(e){return this.resetUserGroup(e)}resetUserGroup(e){this.userToGroup.delete(e)}setData(e,t){if(!Array.isArray(t))throw new Error("timestamps must be an array of numbers.");for(const e of t)if("number"!=typeof e||!Number.isFinite(e))throw new Error("All timestamps must be finite numbers.");this.groupData.has(e)||this.groupFlags.set(e,!1),this.groupData.set(e,t),this.lastSeen.set(e,Date.now())}hasData(e){return this.groupData.has(e)}getData(e){return this.groupData.get(e)||[]}getMaxIdle(){if("number"!=typeof this.#n||!Number.isFinite(this.#n)||this.#n<0)throw new Error("'maxIdle' must be a non-negative finite number.");return this.#n}setMaxIdle(e){if("number"!=typeof e||!Number.isFinite(e)||e<0)throw new Error("'maxIdle' must be a non-negative finite number.");this.#n=e}_cleanup(){const e=Date.now();for(const[t,r]of this.lastSeen.entries())e-r>(this.getGroupTTL(t)??this.getMaxIdle())&&(this.groupFlags.delete(t),this.groupData.delete(t),this.lastSeen.delete(t),this.groupTTL.delete(t),"function"==typeof this.#a&&this.#a(t))}getActiveGroups(){return Array.from(this.groupData.keys())}getAllUserMappings(){return Object.fromEntries(this.userToGroup)}getInterval(){if("number"!=typeof this.#i||!Number.isFinite(this.#i))throw new Error("'interval' is not a valid finite number.");return this.#i}getMaxHits(){if("number"!=typeof this.#r||!Number.isFinite(this.#r))throw new Error("'maxHits' is not a valid finite number.");return this.#r}getTotalHits(e){const t=this.groupData.get(e);return Array.isArray(t)?t.length:0}getLastHit(e){const t=this.groupData.get(e);return t?.length?t[t.length-1]:null}getTimeSinceLastHit(e){const t=this.getLastHit(e);return null!==t?Date.now()-t:null}_calculateAverageSpacing(e){if(!Array.isArray(e)||e.length<2)return null;let t=0;for(let r=1;r<e.length;r++)t+=e[r]-e[r-1];return t/(e.length-1)}getAverageHitSpacing(e){return this._calculateAverageSpacing(this.groupData.get(e))}getMetrics(e){const t=this.groupData.get(e);if(!Array.isArray(t)||0===t.length)return{totalHits:0,lastHit:null,timeSinceLastHit:null,averageHitSpacing:null};const r=t.length,i=t[r-1];return{totalHits:r,lastHit:i,timeSinceLastHit:Date.now()-i,averageHitSpacing:this._calculateAverageSpacing(t)}}destroy(){this.#t&&clearInterval(this.#t),this._cleanup(),this.groupData.clear(),this.lastSeen.clear(),this.userToGroup.clear(),this.groupTTL.clear(),this.groupFlags.clear()}};window.TinyRateLimiter=t.TinyRateLimiter})();