UNPKG

vanilla-performance-patterns

Version:

Production-ready performance patterns for vanilla JavaScript. Zero dependencies, maximum performance.

3 lines (2 loc) 29.3 kB
!function(t,s){"object"==typeof exports&&"undefined"!=typeof module?s(exports):"function"==typeof define&&define.amd?define(["exports"],s):s((t="undefined"!=typeof globalThis?globalThis:t||self).VanillaPerformancePatterns={})}(this,function(exports){"use strict";class SmartCache{constructor(t={}){this.options=t,this.cache=new Map,this.registry=null,this.metadata=new Map,this.hits=0,this.misses=0,this.totalAccessTime=0,this.evictions={gc:0,ttl:0,size:0,manual:0,clear:0},this.accessOrder=[],this.options={maxSize:1/0,ttl:void 0,weak:!0,tracking:!0,...t},this.options.weak&&"undefined"!=typeof FinalizationRegistry&&(this.registry=new FinalizationRegistry(t=>{this.handleGarbageCollection(t)}))}set(t,s,i){const e=performance.now();this.cache.size>=(this.options.maxSize??1/0)&&this.evictLRU();this.cache.get(t)&&this.removeFromAccessOrder(t);const h={timestamp:Date.now(),hits:0,lastAccess:Date.now(),size:this.estimateSize(s)};return this.options.weak&&"undefined"!=typeof WeakRef?(h.ref=new WeakRef(s),this.registry&&this.registry.register(s,t,s)):h.value=s,this.cache.set(t,h),this.accessOrder.push(t),this.options.tracking&&(this.totalAccessTime+=performance.now()-e),s}get(t){const s=performance.now(),i=this.cache.get(t);if(!i)return this.misses++,void(this.options.tracking&&(this.totalAccessTime+=performance.now()-s));if(this.options.ttl&&Date.now()-i.timestamp>this.options.ttl)return this.delete(t,"ttl"),this.misses++,void(this.options.tracking&&(this.totalAccessTime+=performance.now()-s));let e;if(i.ref){if(e=i.ref.deref(),!e)return this.cache.delete(t),this.removeFromAccessOrder(t),this.evictions.gc++,this.misses++,void(this.options.tracking&&(this.totalAccessTime+=performance.now()-s))}else e=i.value;return i.hits++,i.lastAccess=Date.now(),this.hits++,this.removeFromAccessOrder(t),this.accessOrder.push(t),this.options.tracking&&(this.totalAccessTime+=performance.now()-s),e}has(t){const s=this.cache.get(t);return!!s&&(this.options.ttl&&Date.now()-s.timestamp>this.options.ttl?(this.delete(t,"ttl"),!1):!(s.ref&&!s.ref.deref())||(this.cache.delete(t),this.removeFromAccessOrder(t),this.evictions.gc++,!1))}delete(t,s="manual"){const i=this.cache.get(t);if(!i)return!1;let e;return e=i.ref?i.ref.deref():i.value,this.options.onEvict&&e&&this.options.onEvict(t,s,e),this.cache.delete(t),this.removeFromAccessOrder(t),this.evictions[s]++,!0}clear(){if(this.options.onEvict)for(const[t,s]of this.cache){let i;i=s.ref?s.ref.deref():s.value,i&&this.options.onEvict(t,"clear",i)}this.evictions.clear+=this.cache.size,this.cache.clear(),this.accessOrder=[],this.metadata.clear()}get size(){return this.cache.size}getStats(){const t=this.hits+this.misses,s=this.calculateMemoryUsage();return{size:this.cache.size,hits:this.hits,misses:this.misses,hitRate:t>0?this.hits/t:0,evictions:{...this.evictions},memoryUsage:s,averageAccessTime:t>0?this.totalAccessTime/t:0}}resetStats(){this.hits=0,this.misses=0,this.totalAccessTime=0,this.evictions={gc:0,ttl:0,size:0,manual:0,clear:0}}keys(){return Array.from(this.cache.keys())}values(){const t=[];for(const s of this.cache.values()){let i;i=s.ref?s.ref.deref():s.value,i&&t.push(i)}return t}forEach(t){for(const[s,i]of this.cache){let e;e=i.ref?i.ref.deref():i.value,e&&t(e,s)}}handleGarbageCollection(t){const s=this.cache.get(t);s?.ref&&!s.ref.deref()&&(this.cache.delete(t),this.removeFromAccessOrder(t),this.evictions.gc++,this.options.onEvict&&this.options.onEvict(t,"gc"))}evictLRU(){if(0===this.accessOrder.length)return;const t=this.accessOrder[0];t&&this.delete(t,"size")}removeFromAccessOrder(t){const s=this.accessOrder.indexOf(t);s>-1&&this.accessOrder.splice(s,1)}estimateSize(t){try{return 2*JSON.stringify(t).length}catch{return 100}}calculateMemoryUsage(){let t=0;for(const s of this.cache.values())t+=s.size||100;return t}}const defaultCache=new SmartCache;class VirtualScroller{constructor(t){this.options=t,this.items=new Map,this.pool=[],this.scrollState={scrollTop:0,scrollHeight:0,clientHeight:0,isScrolling:!1,scrollVelocity:0,lastScrollTime:0},this.visibleRange={start:0,end:0,overscanStart:0,overscanEnd:0},this.scrollRAF=null,this.scrollTimeout=null,this.resizeObserver=null,this.frameCount=0,this.lastFrameTime=0,this.fps=0,this.renderTime=0,this.totalHeight=0,this.itemHeights=null,this.itemOffsets=null,this.handleScroll=()=>{const t=performance.now(),s=this.container.scrollTop,i=t-this.scrollState.lastScrollTime;i>0&&(this.scrollState.scrollVelocity=(s-this.scrollState.scrollTop)/i*1e3),this.scrollState.scrollTop=s,this.scrollState.lastScrollTime=t,this.scrollState.isScrolling=!0,this.scrollTimeout&&clearTimeout(this.scrollTimeout),this.scrollRAF||(this.scrollRAF=requestAnimationFrame(()=>{this.render(),this.scrollRAF=null,this.options.debug&&this.updateFPS()})),this.scrollTimeout=window.setTimeout(()=>{this.scrollState.isScrolling=!1,this.scrollState.scrollVelocity=0,this.render()},150)},this.handleResize=()=>{this.scrollState.clientHeight=this.container.clientHeight,this.render()},this.options={overscan:3,gpuAcceleration:!0,pooling:!0,maxPoolSize:100,scrollThrottle:16,smoothScrolling:!0,debug:!1,...t},this.container=t.container,this.setupDOM(),this.calculateHeights(),this.attachListeners(),this.render(),this.options.debug&&this.startDebugMode()}setupDOM(){this.container.innerHTML="",Object.assign(this.container.style,{position:"relative",overflow:"auto",contain:"layout style paint",willChange:"transform"}),this.options.gpuAcceleration&&Object.assign(this.container.style,{transform:"translateZ(0)",backfaceVisibility:"hidden",perspective:"1000px"}),this.viewport=document.createElement("div"),Object.assign(this.viewport.style,{position:"relative",width:"100%",overflow:"hidden"}),this.content=document.createElement("div"),Object.assign(this.content.style,{position:"relative",width:"100%",height:`${this.totalHeight}px`,pointerEvents:"none"}),this.viewport.appendChild(this.content),this.container.appendChild(this.viewport)}calculateHeights(){const{itemCount:t,itemHeight:s}=this.options;if("number"==typeof s)this.totalHeight=t*s,this.itemHeights=null,this.itemOffsets=null;else{this.itemHeights=[],this.itemOffsets=[0];let i=0;for(let e=0;e<t;e++){const t=s(e);this.itemHeights.push(t),i+=t,this.itemOffsets.push(i)}this.totalHeight=i}this.content&&(this.content.style.height=`${this.totalHeight}px`)}getItemHeight(t){if(this.itemHeights)return this.itemHeights[t]??0;const{itemHeight:s}=this.options;return"number"==typeof s?s:s(t)}getItemOffset(t){if(this.itemOffsets)return this.itemOffsets[t]??0;const{itemHeight:s}=this.options;if("number"==typeof s)return t*s;let i=0;for(let e=0;e<t;e++)i+=s(e);return i}calculateVisibleRange(){const{scrollTop:t}=this.scrollState,{clientHeight:s}=this.container,{itemCount:i,overscan:e=3}=this.options,h=this.scrollState.isScrolling?Math.min(Math.ceil(Math.abs(this.scrollState.scrollVelocity)/100),20):e;let r=0,o=i-1;if("number"==typeof this.options.itemHeight){const i=this.options.itemHeight;r=Math.floor(t/i),o=Math.ceil((t+s)/i)}else r=this.findIndexAtOffset(t),o=this.findIndexAtOffset(t+s);const n=Math.max(0,r-h),a=Math.min(i-1,o+h),c=this.visibleRange.start!==r||this.visibleRange.end!==o;this.visibleRange={start:r,end:o,overscanStart:n,overscanEnd:a},c&&this.options.onRangeChange&&this.options.onRangeChange(r,o)}findIndexAtOffset(t){if(!this.itemOffsets)return 0;let s=0,i=this.itemOffsets.length-1;for(;s<i;){const e=Math.floor((s+i)/2);this.itemOffsets[e]<t?s=e+1:i=e}return Math.max(0,s-1)}acquireElement(){const t=this.pool.find(t=>!t.inUse);if(t)return t.inUse=!0,t;const s=document.createElement("div");Object.assign(s.style,{position:"absolute",left:"0",right:"0",boxSizing:"border-box",contain:"layout style paint",pointerEvents:"auto"}),this.options.gpuAcceleration&&(s.style.willChange="transform",s.style.transform="translateZ(0)");const i={element:s,index:-1,inUse:!0};return this.pool.length<(this.options.maxPoolSize??100)&&this.pool.push(i),i}releaseElement(t){t.inUse=!1,t.index=-1,t.element.innerHTML="",t.element.className="",t.element.removeAttribute("data-index")}render(){const t=performance.now();this.calculateVisibleRange();const{overscanStart:s,overscanEnd:i}=this.visibleRange,e=[];for(const[t,h]of this.items)(t<s||t>i)&&(e.push(t),this.releaseElement(h),h.element.parentNode&&h.element.remove());e.forEach(t=>this.items.delete(t));for(let t=s;t<=i;t++)this.items.has(t)||this.renderItem(t);this.renderTime=performance.now()-t}renderItem(t){const s=this.acquireElement();s.index=t;const i=this.options.renderItem(t);"string"==typeof i?s.element.innerHTML=i:(s.element.innerHTML="",s.element.appendChild(i));const e=this.getItemOffset(t),h=this.getItemHeight(t);s.element.style.height=`${h}px`,s.element.setAttribute("data-index",String(t)),this.options.gpuAcceleration?s.element.style.transform=`translateY(${e}px)`:s.element.style.top=`${e}px`,this.content.appendChild(s.element),this.items.set(t,s)}attachListeners(){this.container.addEventListener("scroll",this.handleScroll,{passive:!0}),"undefined"!=typeof ResizeObserver?(this.resizeObserver=new ResizeObserver(this.handleResize),this.resizeObserver.observe(this.container)):window.addEventListener("resize",this.handleResize),this.scrollState.clientHeight=this.container.clientHeight,this.scrollState.scrollHeight=this.totalHeight}detachListeners(){this.container.removeEventListener("scroll",this.handleScroll),this.resizeObserver?(this.resizeObserver.disconnect(),this.resizeObserver=null):window.removeEventListener("resize",this.handleResize),this.scrollRAF&&(cancelAnimationFrame(this.scrollRAF),this.scrollRAF=null),this.scrollTimeout&&(clearTimeout(this.scrollTimeout),this.scrollTimeout=null)}updateFPS(){const t=performance.now();this.frameCount++,t-this.lastFrameTime>=1e3&&(this.fps=Math.round(1e3*this.frameCount/(t-this.lastFrameTime)),this.frameCount=0,this.lastFrameTime=t)}startDebugMode(){const t=document.createElement("div");Object.assign(t.style,{position:"fixed",top:"10px",right:"10px",padding:"10px",background:"rgba(0, 0, 0, 0.8)",color:"#0f0",fontFamily:"monospace",fontSize:"12px",zIndex:"10000",pointerEvents:"none"}),document.body.appendChild(t),setInterval(()=>{t.innerHTML=`\n <div>FPS: ${this.fps}</div>\n <div>Render: ${this.renderTime.toFixed(2)}ms</div>\n <div>Items: ${this.items.size}</div>\n <div>Pool: ${this.pool.length}</div>\n <div>Range: ${this.visibleRange.start}-${this.visibleRange.end}</div>\n <div>Velocity: ${Math.round(this.scrollState.scrollVelocity)}px/s</div>\n `},100)}scrollToItem(t,s="smooth"){const i=this.getItemOffset(t);this.container.scrollTo({top:i,behavior:s})}setItemCount(t){this.options.itemCount=t,this.calculateHeights(),this.render()}refresh(){for(const[,t]of this.items)this.releaseElement(t),t.element.parentNode&&t.element.remove();this.items.clear(),this.render()}updateItem(t){const s=this.items.get(t);if(s){const i=this.options.renderItem(t);"string"==typeof i?s.element.innerHTML=i:(s.element.innerHTML="",s.element.appendChild(i))}}getScrollPosition(){return this.scrollState.scrollTop}getVisibleRange(){return{start:this.visibleRange.start,end:this.visibleRange.end}}destroy(){this.detachListeners();for(const[,t]of this.items)this.releaseElement(t);this.items.clear(),this.pool=[],this.container&&(this.container.innerHTML="")}}class ObjectPool{constructor(t,s,i={}){this.factory=t,this.reset=s,this.options=i,this.pool=[],this.inUse=new Set,this.stats={created:0,reused:0,growthCount:0},this.options={initialSize:10,maxSize:1/0,autoGrow:!0,growthFactor:2,warmUp:!0,tracking:!1,...i},this.options.warmUp&&this.warmUp(this.options.initialSize??10)}warmUp(t){const s=Math.min(t,this.options.maxSize??1/0);for(let t=this.pool.length;t<s;t++){const t=this.factory();this.reset(t),this.pool.push(t),this.stats.created++}}grow(){const t=this.pool.length+this.inUse.size,s=this.options.maxSize??1/0;if(t>=s)throw new Error(`ObjectPool: Maximum size (${s}) reached`);const i=Math.min(Math.ceil(t*(this.options.growthFactor??2)),s);this.warmUp(t+(i-t)),this.stats.growthCount++}acquire(){let t=this.pool.pop();return t?this.stats.reused++:(this.options.autoGrow&&(this.grow(),t=this.pool.pop()),t||(t=this.factory(),this.stats.created++)),this.options.validate&&!this.options.validate(t)&&(t=this.factory(),this.stats.created++),this.inUse.add(t),t}release(t){if(!this.inUse.has(t))return!1;this.inUse.delete(t);try{"reset"in t&&"function"==typeof t.reset?t.reset():this.reset(t)}catch(t){return!1}return!(this.options.validate&&!this.options.validate(t))&&(this.pool.length<(this.options.maxSize??1/0)&&(this.pool.push(t),!0))}releaseMany(t){let s=0;for(const i of t)this.release(i)&&s++;return s}releaseAll(){const t=Array.from(this.inUse);return this.releaseMany(t)}clear(){this.pool.length=0,this.inUse.clear(),this.stats={created:0,reused:0,growthCount:0}}getStats(){const t=this.stats.created+this.stats.reused;return{size:this.pool.length+this.inUse.size,available:this.pool.length,inUse:this.inUse.size,created:this.stats.created,reused:this.stats.reused,growthCount:this.stats.growthCount,hitRate:t>0?this.stats.reused/t:0}}reserve(t){this.warmUp(this.pool.length+this.inUse.size+t)}shrink(t){const s=t??this.options.initialSize??10,i=this.pool.length-s;return i>0?(this.pool.splice(s),i):0}get size(){return this.pool.length+this.inUse.size}get available(){return this.pool.length}get exhausted(){return 0===this.pool.length}}class DOMPool extends ObjectPool{constructor(t="div",s,i){super(()=>{const i=document.createElement(t);return s&&(i.className=s),i.style.willChange="transform",i.style.transform="translateZ(0)",i.style.position="absolute",i},t=>{t.innerHTML="",t.className=s||"";const i={position:t.style.position,willChange:t.style.willChange,transform:t.style.transform};t.removeAttribute("style"),Object.assign(t.style,i),Array.from(t.attributes).forEach(s=>{"class"!==s.name&&"style"!==s.name&&t.removeAttribute(s.name)});const e=t.cloneNode(!1);t.parentNode&&t.parentNode.replaceChild(e,t)},{initialSize:20,maxSize:200,...i})}}class ArrayPool{constructor(t,s=1048576){this.ArrayConstructor=t,this.maxPooledSize=s,this.pools=new Map}acquire(t){if(t>this.maxPooledSize)return new this.ArrayConstructor(t);const s=Math.pow(2,Math.ceil(Math.log2(t)));let i=this.pools.get(s);return i||(i=new ObjectPool(()=>new this.ArrayConstructor(s),t=>t.fill(0),{initialSize:2,maxSize:10}),this.pools.set(s,i)),i.acquire()}release(t){const s=t.length;if(s>this.maxPooledSize)return!1;const i=this.pools.get(s);return!!i&&i.release(t)}clear(){for(const t of this.pools.values())t.clear();this.pools.clear()}}class CircuitBreaker{constructor(t={}){this.options=t,this.state="closed",this.failures=0,this.successes=0,this.consecutiveSuccesses=0,this.consecutiveFailures=0,this.nextAttempt=0,this.halfOpenRequests=0,this.requestHistory=[],this.options={failureThreshold:50,successThreshold:5,timeout:3e3,resetTimeout:3e4,volumeThreshold:5,rollingWindow:1e4,halfOpenLimit:1,debug:!1,...t}}async execute(t,...s){if("open"===this.state){if(Date.now()<this.nextAttempt){if(this.options.fallback)return this.options.fallback(...s);throw new Error(`Circuit breaker is open until ${new Date(this.nextAttempt).toISOString()}`)}this.transitionTo("half-open")}if("half-open"===this.state&&this.halfOpenRequests>=(this.options.halfOpenLimit??1)){if(this.options.fallback)return this.options.fallback(...s);throw new Error("Circuit breaker is half-open and test limit reached")}"half-open"===this.state&&this.halfOpenRequests++;const i=Date.now();try{const s=await this.executeWithTimeout(t,this.options.timeout);if(this.options.successFilter&&!this.options.successFilter(s))throw new Error("Result failed success filter");return this.recordSuccess(Date.now()-i),s}catch(t){if(this.options.errorFilter&&!this.options.errorFilter(t))throw t;if(this.recordFailure(t,Date.now()-i),this.options.fallback)return this.options.fallback(...s);throw t}}async executeWithTimeout(t,s){return Promise.race([t(),new Promise((t,i)=>setTimeout(()=>i(new Error(`Operation timeout after ${s}ms`)),s))])}recordSuccess(t){const s=Date.now();this.requestHistory.push({timestamp:s,success:!0,duration:t}),this.cleanHistory(),this.successes++,this.consecutiveSuccesses++,this.consecutiveFailures=0,this.lastSuccessTime=s,"half-open"===this.state&&this.consecutiveSuccesses>=(this.options.successThreshold??5)&&this.transitionTo("closed")}recordFailure(t,s){const i=Date.now();if(this.requestHistory.push({timestamp:i,success:!1,duration:s,error:t}),this.cleanHistory(),this.failures++,this.consecutiveFailures++,this.consecutiveSuccesses=0,this.lastFailureTime=i,"closed"===this.state){const t=this.calculateStats();t.totalRequests>=(this.options.volumeThreshold??5)&&t.failureRate>=(this.options.failureThreshold??50)&&this.transitionTo("open")}else"half-open"===this.state&&this.transitionTo("open")}cleanHistory(){const t=Date.now()-(this.options.rollingWindow??1e4);this.requestHistory=this.requestHistory.filter(s=>s.timestamp>t)}calculateStats(){this.cleanHistory();const t=this.requestHistory.length,s=this.requestHistory.filter(t=>!t.success).length;return{state:this.state,failures:s,successes:t-s,totalRequests:t,failureRate:t>0?s/t*100:0,lastFailureTime:this.lastFailureTime,lastSuccessTime:this.lastSuccessTime,nextAttempt:"open"===this.state?this.nextAttempt:void 0,consecutiveSuccesses:this.consecutiveSuccesses,consecutiveFailures:this.consecutiveFailures}}transitionTo(t){const s=this.state;switch(this.state=t,t){case"open":const t=this.options.resetTimeout??3e4,s=1e3*Math.random();this.nextAttempt=Date.now()+t+s,this.halfOpenRequests=0,this.resetTimer&&clearTimeout(this.resetTimer),this.resetTimer=window.setTimeout(()=>{this.transitionTo("half-open")},t+s);break;case"half-open":this.halfOpenRequests=0,this.consecutiveSuccesses=0,this.consecutiveFailures=0;break;case"closed":this.halfOpenRequests=0,this.nextAttempt=0,this.resetTimer&&(clearTimeout(this.resetTimer),this.resetTimer=void 0)}this.options.onStateChange&&this.options.onStateChange(s,t)}protect(t){return async(...s)=>this.execute(()=>Promise.resolve(t(...s)),...s)}protectAsync(t){return async(...s)=>this.execute(()=>t(...s),...s)}open(){this.transitionTo("open")}close(){this.transitionTo("closed")}reset(){this.failures=0,this.successes=0,this.consecutiveSuccesses=0,this.consecutiveFailures=0,this.requestHistory=[],this.lastFailureTime=void 0,this.lastSuccessTime=void 0,this.transitionTo("closed")}getStats(){return this.calculateStats()}getState(){return this.state}isOpen(){return"open"===this.state}isClosed(){return"closed"===this.state}isHealthy(){const t=this.calculateStats();return"closed"===this.state&&t.failureRate<(this.options.failureThreshold??50)}}class BulkheadPool{constructor(t=10,s=100){this.maxConcurrent=t,this.maxQueue=s,this.running=0,this.queue=[]}async execute(t){if(this.running>=this.maxConcurrent){if(this.queue.length>=this.maxQueue)throw new Error(`Bulkhead queue full (${this.maxQueue} items)`);await new Promise(t=>{this.queue.push(t)})}this.running++;try{return await t()}finally{this.running--;const t=this.queue.shift();t&&t()}}getState(){return{running:this.running,queued:this.queue.length}}}class WorkerPool{constructor(t){if(this.options=t,this.workers=new Map,this.taskQueue=[],this.roundRobinIndex=0,this.isTerminated=!1,this.stats={totalTasks:0,completedTasks:0,failedTasks:0,totalResponseTime:0,lastThroughputCheck:Date.now(),lastCompletedTasks:0},this.options={minWorkers:2,maxWorkers:navigator.hardwareConcurrency||4,workerType:"classic",idleTimeout:3e4,taskTimeout:3e4,enableSharedArrayBuffer:!0,maxQueueSize:1e3,strategy:"least-loaded",debug:!1,...t},this.options.minWorkers>this.options.maxWorkers)throw new Error("minWorkers cannot be greater than maxWorkers");if("function"==typeof this.options.workerScript){const t=`(${this.options.workerScript.toString()})()`,s=new Blob([t],{type:"application/javascript"});this.workerBlobUrl=URL.createObjectURL(s),this.options.workerScript=this.workerBlobUrl}this.initializeWorkers()}initializeWorkers(){for(let t=0;t<this.options.minWorkers;t++)this.createWorker()}createWorker(){if(this.workers.size>=this.options.maxWorkers)return null;try{const t=new Worker(this.options.workerScript,{type:this.options.workerType}),s=this.generateId(),i={worker:t,busy:!1,taskCount:0,totalTasks:0,avgResponseTime:0,lastUsed:Date.now()};return t.onmessage=t=>{this.handleWorkerMessage(s,t)},t.onerror=t=>{this.handleWorkerError(s,t)},t.onmessageerror=t=>{},this.workers.set(s,i),i}catch(t){return null}}handleWorkerMessage(t,s){const i=this.workers.get(t);if(!i||!i.currentTask)return;const e=i.currentTask,h=Date.now()-(e.startTime??Date.now());i.totalTasks++,i.avgResponseTime=(i.avgResponseTime*(i.totalTasks-1)+h)/i.totalTasks,i.busy=!1,i.currentTask=void 0,i.lastUsed=Date.now(),this.stats.completedTasks++,this.stats.totalResponseTime+=h,e.timeout&&clearTimeout(e.timeout),e.resolve(s.data),this.scheduleIdleTimeout(t),this.processQueue()}handleWorkerError(t,s){const i=this.workers.get(t);if(!i)return;const e=i.currentTask;e&&((e.retries??0)<3?(e.retries=(e.retries??0)+1,this.taskQueue.unshift(e)):(e.reject(new Error(`Worker error: ${s.message}`)),this.stats.failedTasks++)),i.busy=!1,i.currentTask=void 0,this.restartWorker(t),this.processQueue()}restartWorker(t){const s=this.workers.get(t);s&&(s.worker.terminate(),this.workers.delete(t),this.workers.size<this.options.minWorkers&&this.createWorker())}scheduleIdleTimeout(t){const s=this.workers.get(t);s&&(s.idleTimer&&clearTimeout(s.idleTimer),this.workers.size<=this.options.minWorkers||(s.idleTimer=window.setTimeout(()=>{this.terminateWorker(t)},this.options.idleTimeout)))}terminateWorker(t){const s=this.workers.get(t);s&&!s.busy&&(this.workers.size<=this.options.minWorkers||(s.worker.terminate(),this.workers.delete(t)))}getAvailableWorker(){const t=Array.from(this.workers.values()).filter(t=>!t.busy);if(0===t.length)return null;switch(this.options.strategy){case"round-robin":return this.roundRobinIndex=(this.roundRobinIndex+1)%t.length,t[this.roundRobinIndex];case"least-loaded":return t.reduce((t,s)=>s.taskCount<t.taskCount?s:t);case"random":return t[Math.floor(Math.random()*t.length)];case"sticky":return t.reduce((t,s)=>s.lastUsed<t.lastUsed?s:t);default:return t[0]}}processQueue(){if(0!==this.taskQueue.length)for(this.taskQueue.some(t=>void 0!==t.priority)&&this.taskQueue.sort((t,s)=>(s.priority??0)-(t.priority??0)),this.taskQueue.length>2*this.workers.size&&this.createWorker();this.taskQueue.length>0;){const t=this.getAvailableWorker();if(!t){if(this.workers.size<this.options.maxWorkers&&this.createWorker())continue;break}const s=this.taskQueue.shift();this.executeOnWorker(t,s)}}executeOnWorker(t,s){if(t.busy=!0,t.currentTask=s,t.taskCount++,s.startTime=Date.now(),t.idleTimer&&(clearTimeout(t.idleTimer),t.idleTimer=void 0),s.timeout||this.options.taskTimeout){const i=s.timeout||this.options.taskTimeout;s.timeout=window.setTimeout(()=>{s.reject(new Error(`Task timeout after ${i}ms`)),t.busy=!1,t.currentTask=void 0,this.stats.failedTasks++,this.processQueue()},i)}try{s.transferList&&s.transferList.length>0?t.worker.postMessage(s.data,s.transferList):t.worker.postMessage(s.data)}catch(i){s.reject(i),t.busy=!1,t.currentTask=void 0,this.stats.failedTasks++,this.processQueue()}}generateId(){return`worker-${Date.now()}-${Math.random().toString(36).substr(2,9)}`}async execute(t,s,i){if(this.isTerminated)throw new Error("WorkerPool has been terminated");if(this.taskQueue.length>=this.options.maxQueueSize)throw new Error(`Task queue full (${this.options.maxQueueSize} tasks)`);return new Promise((e,h)=>{const r={id:this.generateId(),data:t,transferList:s,timeout:i?.timeout,priority:i?.priority,resolve:e,reject:h};this.stats.totalTasks++,this.taskQueue.push(r),this.processQueue()})}async executeMany(t){const s=t.map(t=>this.execute(t.data,t.transferList,t.options));return Promise.all(s)}async map(t,s,i){const e=i?.concurrency??this.options.maxWorkers,h=new Array(t.length);let r=0;const o=Array(e).fill(null).map(async()=>{for(;r<t.length;){const e=r++,o=t[e],n=s(o),a=i?.transferList?.(o);h[e]=await this.execute(n,a,{timeout:i?.timeout})}});return await Promise.all(o),h}getStats(){const t=Date.now(),s=(t-this.stats.lastThroughputCheck)/1e3,i=s>0?(this.stats.completedTasks-this.stats.lastCompletedTasks)/s:0;this.stats.lastThroughputCheck=t,this.stats.lastCompletedTasks=this.stats.completedTasks;const e=Array.from(this.workers.values()).filter(t=>!t.busy).length;return{workers:this.workers.size,available:e,busy:this.workers.size-e,queueLength:this.taskQueue.length,totalTasks:this.stats.totalTasks,completedTasks:this.stats.completedTasks,failedTasks:this.stats.failedTasks,avgResponseTime:this.stats.completedTasks>0?this.stats.totalResponseTime/this.stats.completedTasks:0,throughput:i}}setPoolSize(t,s){for(this.options.minWorkers=t,this.options.maxWorkers=s;this.workers.size<t;)this.createWorker();if(this.workers.size>s){const t=this.workers.size-s,i=Array.from(this.workers.entries());for(let s=0;s<t;s++){const[t,e]=i[s];e.busy||this.terminateWorker(t)}}}async terminate(){this.isTerminated=!0;for(const t of this.taskQueue)t.reject(new Error("WorkerPool terminated"));this.taskQueue=[];for(const[t,s]of this.workers)s.idleTimer&&clearTimeout(s.idleTimer),s.worker.terminate();this.workers.clear(),this.workerBlobUrl&&URL.revokeObjectURL(this.workerBlobUrl)}}function createFunctionWorkerPool(fn,options){const workerCode=`\n const fn = ${fn.toString()};\n \n self.onmessage = async (event) => {\n try {\n const result = await fn(event.data);\n self.postMessage({ success: true, result });\n } catch (error) {\n self.postMessage({ \n success: false, \n error: error.message || 'Unknown error' \n });\n }\n };\n `;return new WorkerPool({...options,workerScript:()=>eval(workerCode)})}function debounce(t,s,i={}){let e,h,r,o,n,a,c=0;const{leading:l=!1,trailing:u=!0,maxWait:d}=i,f=void 0!==d,m=f?Math.max(d,s):0;function p(s){const i=o,e=n;return o=n=void 0,c=s,a=t.apply(e,i),a}function v(t){const i=t-(r??0);return void 0===r||i>=s||i<0||f&&t-c>=m}function w(){const t=Date.now();if(v(t))return function(t){e=void 0,u&&o?a=p(t):o=n=void 0}(t);e=window.setTimeout(w,function(t){const i=s-(t-(r??0));return f?Math.min(i,m-(t-c)):i}(t))}function g(){void 0!==e&&clearTimeout(e),h=e=void 0,u&&o?a=p(Date.now()):o=n=void 0}function y(){void 0!==e&&clearTimeout(e),void 0!==h&&clearTimeout(h),c=0,o=r=n=e=h=void 0}function k(...t){const i=Date.now(),u=v(i);o=t,n=this,r=i,u?void 0===e?function(t){c=t,f&&(h=window.setTimeout(g,m)),l&&(a=p(t))}(i):f&&(void 0!==e&&clearTimeout(e),e=window.setTimeout(w,s),a=p(i)):void 0===e&&(e=window.setTimeout(w,s))}return k.cancel=y,k.flush=function(){return void 0===e||(y(),o&&(a=p(Date.now()))),a},k.pending=function(){return void 0!==e},k}function throttle(t,s,i={}){const{leading:e=!0,trailing:h=!0}=i;return debounce(t,s,{leading:e,trailing:h,maxWait:s})}function rafThrottle(t){let s,i,e;function h(){const h=i,r=e;i=e=void 0,s=void 0,t.apply(r,h)}function r(...t){i=t,e=this,void 0===s&&(s=requestAnimationFrame(h))}return r.cancel=()=>{void 0!==s&&(cancelAnimationFrame(s),s=void 0),i=e=void 0},r.flush=()=>{void 0!==s&&(cancelAnimationFrame(s),h())},r.pending=()=>void 0!==s,r}function idleThrottle(t,s){let i,e,h;function r(){const s=e,r=h;e=h=void 0,i=void 0,t.apply(r,s)}function o(...t){e=t,h=this,void 0===i&&"requestIdleCallback"in window?i=requestIdleCallback(r,s):void 0===i&&(i=window.setTimeout(r,1))}return o.cancel=()=>{void 0!==i&&("cancelIdleCallback"in window?cancelIdleCallback(i):clearTimeout(i),i=void 0),e=h=void 0},o.flush=()=>{void 0!==i&&(o.cancel(),r())},o.pending=()=>void 0!==i,o}function memoize(t,s={}){const{keyResolver:i=(...t)=>JSON.stringify(t),maxSize:e=1/0,ttl:h,weak:r=!1}=s,o=r?new WeakMap:new Map,n=new Map;function a(...s){const a=r?s[0]:i(...s);if(o.has(a)){if(!h||r)return o.get(a);{const t=n.get(a);if(!(t&&Date.now()-t>h))return o.get(a);o.delete(a),n.delete(a)}}const c=t.apply(this,s);if(!r&&o.size>=e){const t=o.keys().next().value;o.delete(t),n.delete(t)}return o.set(a,c),h&&!r&&n.set(a,Date.now()),c}return a.cache=o,a}const VERSION="0.1.0",FEATURES={weakRef:"undefined"!=typeof WeakRef,finalizationRegistry:"undefined"!=typeof FinalizationRegistry,requestIdleCallback:"undefined"!=typeof requestIdleCallback,worker:"undefined"!=typeof Worker,sharedArrayBuffer:"undefined"!=typeof SharedArrayBuffer,resizeObserver:"undefined"!=typeof ResizeObserver,intersectionObserver:"undefined"!=typeof IntersectionObserver};var index={VERSION:VERSION,FEATURES:FEATURES};exports.ArrayPool=ArrayPool,exports.BulkheadPool=BulkheadPool,exports.CircuitBreaker=CircuitBreaker,exports.DOMPool=DOMPool,exports.FEATURES=FEATURES,exports.ObjectPool=ObjectPool,exports.SmartCache=SmartCache,exports.VERSION=VERSION,exports.VirtualScroller=VirtualScroller,exports.WorkerPool=WorkerPool,exports.createFunctionWorkerPool=createFunctionWorkerPool,exports.debounce=debounce,exports.default=index,exports.defaultCache=defaultCache,exports.idleThrottle=idleThrottle,exports.memoize=memoize,exports.rafThrottle=rafThrottle,exports.throttle=throttle,Object.defineProperty(exports,"t",{value:!0})}); //# sourceMappingURL=index.umd.min.js.map