🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

regulo

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

regulo - npm Package Compare versions

Comparing version
1.3.5
to
1.4.0
+21
-0
CHANGELOG.md

@@ -8,2 +8,23 @@ # Changelog

## [v1.4.0] - 2026-07-05
### Changed (BREAKING)
- **Renamed the circuit breaker's middle state from "half-open" to "probing," everywhere.** While in this state the breaker admits exactly one canary request and rejects every other acquisition — there is no partial throughput — so "half-open" implied a capacity level (half of normal) that never actually existed. "Probing" names what the state does instead of a (misleading) fraction of capacity. This is a sweeping rename across the public API, shipped with no compatibility aliases — the same deliberate choice this project made for the `CircuitBreaker` → `SaturationCircuitBreaker` rename in v1.3.0.
- `CircuitState`: the `'half-open'` member is now `'probing'`.
- `CircuitBreakerStrategy.isHalfOpen` is now `isProbing` (implemented by `SaturationCircuitBreaker`, `NoopCircuitBreaker`, `ManualCircuitBreaker`, and any custom strategy passed via `circuitBreaker`).
- `SemaphoreEvents.CIRCUITHALFOPEN` (`'circuit-half-open'`) is now `SemaphoreEvents.CIRCUITPROBING` (`'circuit-probing'`).
- The `CIRCUIT_HALF_OPEN` error code is now `CIRCUIT_PROBING`.
- `status().status.circuitHalfOpen` is now `circuitProbing`; `SemaphoreMetricsSnapshot.meta.circuitHalfOpen` is now `circuitProbing`; `SemaphoreMetrics.markCircuitHalfOpen()` is now `markCircuitProbing()`.
- The `CIRCUITOPEN` event's `reason: 'half-open-probe-failed'` value is now `reason: 'probe-failed'`.
- Migration: search for `isHalfOpen`, `HALF_OPEN`, `half-open`, and `circuitHalfOpen` and replace with `isProbing`, `PROBING`, `probing`, and `circuitProbing` respectively. Behavior is unchanged — this is a naming-only change.
### Performance
- **`cancel()` is now `O(n)`, down from `O(n log n)`.** It previously rejected each queued task and removed it from the priority heap individually (an `O(log n)` arbitrary-element delete per task, via the same path `abort`/`purge`/`timeout` use). Since `cancel()` discards every queued task unconditionally, it now rejects each one and bulk-clears the heap and the enqueue-ordered list once, matching the `O(n)` pattern `reset()`/`shutdown()` already used.
- **`cancel()`, `reset()`, and `shutdown()` no longer clone the heap into an array before iterating.** All three previously called the heap's `toArray()` (an `O(n)` copy) to get a snapshot to loop over; they now walk the enqueue-ordered list directly, which was already being maintained for this exact purpose. `IndexedBinaryHeap.toArray()` had no remaining callers and has been removed.
- **The circuit breaker's queue eviction (`_evictQueueOnCircuitOpen`) gained a bulk-clear fast path.** A compliant breaker (the built-in `SaturationCircuitBreaker`, or any correctly-implemented custom one) only trips from closed, and a probe can only be queued while probing — so the two conditions never coincide, and eviction never actually needs to preserve a live probe in practice. That common case now bulk-clears in `O(n)`, same as above; the previous per-item `delete()` (`O(n log n)`, needed only to selectively skip a live probe) is retained as a fallback for a breaker that violates that contract.
- **Removed several redundant `Date.now()` reads on hot and burst paths**, reusing an already-current timestamp instead of reading the clock again a few lines later or once per task in a loop: the queued-acquire path, the stale-task purge sweep, the per-task timeout handler (`BackoffTracker.onTimeout()` now takes an optional timestamp, mirroring the existing pattern on `CircuitBreakerStrategy.trackAttempt()`), the scheduler's dispatch loop (one shared read per tick instead of one per dispatched task), and `status()` (shared with its metrics snapshot via `SemaphoreMetrics.getSnapshot()`'s new optional timestamp parameter). None of these change any complexity class; they cut constant-factor clock-read overhead, most visibly under bursts (many tasks purged, timed out, or dispatched in one tick).
- **Evaluated and rejected a change to the queue comparator.** Gating the probe-priority check in `buildComparator()` behind a cheap "is a probe currently queued" pre-check looked promising in an isolated microbenchmark, but end-to-end contended-throughput measurements (ratioed against `p-limit`/`p-queue` in the same run, to control for system-level noise) showed it was a net wash to slightly negative once wired to the real circuit breaker. Not shipped.
## [v1.3.5] - 2026-07-04

@@ -10,0 +31,0 @@

+1
-1

@@ -1,2 +0,2 @@

'use strict';var a=class extends Error{constructor(e,t){super(e),this.name="SemaphoreError",this.code=t;}};var l=(o,e,t,i,r=true,s=true)=>{if(typeof o!="number"||Number.isNaN(o))throw new a(`${e} must be a valid number`,"INVALID_ARGUMENT");if(r&&!Number.isInteger(o))throw new a(`${e} must be an integer`,"INVALID_ARGUMENT");if(s?o<t:o<=t)throw new a(`${e} must be ${s?">=":">"} ${t}`,"INVALID_ARGUMENT");if(s?o>i:o>=i)throw new a(`${e} must be ${s?"<=":"<"} ${i}`,"INVALID_ARGUMENT");return o};var T=class{constructor(e){this._inFlight=0;this.capacity=e,this._available=e;}get available(){return this._available}get inFlight(){return this._inFlight}get isFull(){return this._available===0}hasCapacityFor(e){return this._available>=e}acquire(e=1){this._available-=e,this._inFlight+=e;}release(e=1){this._available=Math.min(this._available+e,this.capacity),this._inFlight=this._inFlight<e?0:this._inFlight-e;}assertInvariant(e){if(!e)return;let t=this._inFlight+this._available;t!==this.capacity&&console.error(`[Semaphore] Invariant violation: inFlight(${this._inFlight}) + available(${this._available}) = ${t}, expected capacity(${this.capacity})`);}reset(){this._available=this.capacity,this._inFlight=0;}};var q=class{constructor(e,t){this.cachedIndex=-1;this.cachedUntil=0;this.size=e,this.stepMs=t,this.buckets=new Int32Array(e*2),this.timestamps=new Float64Array(e);}idx(e){if(e<this.cachedUntil&&e>=this.cachedUntil-this.stepMs)return this.cachedIndex;let t=Math.floor(e/this.stepMs)*this.stepMs,i=Math.floor(t/this.stepMs)%this.size;return this.timestamps[i]!==t&&(this.timestamps[i]=t,this.buckets[i*2]=0,this.buckets[i*2+1]=0),this.cachedIndex=i,this.cachedUntil=t+this.stepMs,i}addAcquired(e=Date.now()){this.buckets[this.idx(e)*2]++;}addTimeout(){this.buckets[this.idx(Date.now())*2+1]++;}snapshot(){let e=Date.now(),t=Math.floor(e/this.stepMs)*this.stepMs-(this.size-1)*this.stepMs,i=0,r=0;for(let s=0;s<this.size;s++)this.timestamps[s]>=t&&(i+=this.buckets[s*2],r+=this.buckets[s*2+1]);return {acquired:i,timeouts:r}}reset(){this.cachedIndex=-1,this.cachedUntil=0,this.buckets.fill(0),this.timestamps.fill(0);}},v=class{constructor(e={}){this.state="closed";this.openUntil=0;this.probeInFlight=false;this._probeTaskId=null;if(this.threshold=l(e.threshold??.5,"SaturationCircuitBreaker threshold",0,1,false,false),this.window=l(e.window??1e4,"SaturationCircuitBreaker window",1e3,Number.MAX_SAFE_INTEGER,true,true),this.windowBucketWidth=l(e.windowBucketWidth??1e3,"SaturationCircuitBreaker windowBucketWidth",1,Number.MAX_SAFE_INTEGER,true,true),this.windowBucketCount=Math.ceil(this.window/this.windowBucketWidth),this.cooldown=l(e.cooldown??5e3,"SaturationCircuitBreaker cooldown",1e3,Number.MAX_SAFE_INTEGER,true,true),this.minThroughput=l(e.minThroughput??10,"SaturationCircuitBreaker minThroughput",1,Number.MAX_SAFE_INTEGER,true,true),this.minFailures=l(e.minFailures??5,"SaturationCircuitBreaker minFailures",1,Number.MAX_SAFE_INTEGER,true,true),this.window<this.windowBucketWidth)throw new a("SaturationCircuitBreaker window must be >= windowBucketWidth","INVALID_ARGUMENT");if(this.minThroughput<this.minFailures)throw new a("SaturationCircuitBreaker minThroughput must be >= minFailures","INVALID_ARGUMENT");if(this.windowBucketCount<2)throw new a("SaturationCircuitBreaker window must span at least 2 windowBucketWidth buckets","INVALID_ARGUMENT");this.eventWindow=new q(this.windowBucketCount,this.windowBucketWidth);}get isClosed(){return this.state==="closed"}get isOpen(){return this.state==="open"}get isHalfOpen(){return this.state==="half-open"}get hasProbeInFlight(){return this.probeInFlight}get probeTaskId(){return this._probeTaskId}get cooldownRemaining(){return this.isOpen?Math.max(0,this.openUntil-Date.now()):0}trackAttempt(e){this.state==="closed"&&this.eventWindow.addAcquired(e);}recordFailure(){this.eventWindow.addTimeout();}checkAndTransition(){return this.state!=="open"||Date.now()<this.openUntil?false:(this.state="half-open",this.probeInFlight=false,this._probeTaskId=null,true)}evaluateAndTrip(){if(this.state!=="closed")return {tripped:false};let{timeouts:e,acquired:t}=this.eventWindow.snapshot();if(t<this.minThroughput||e<this.minFailures)return {tripped:false};let i=e/t;return i<this.threshold?{tripped:false}:(this.state="open",this.openUntil=Date.now()+this.cooldown,{tripped:true,timeoutRate:i,failures:e,attempts:t})}markProbeInFlight(){this.probeInFlight=true;}claimProbeSlot(e){this.probeInFlight=true,this._probeTaskId=e;}releaseProbeSlot(){this.probeInFlight=false,this._probeTaskId=null;}handleProbeSuccess(){this.state="closed",this.probeInFlight=false,this._probeTaskId=null,this.eventWindow.reset();}handleProbeFailure(){this.state="open",this.probeInFlight=false,this._probeTaskId=null,this.openUntil=Date.now()+this.cooldown;}reset(){this.state="closed",this.openUntil=0,this.probeInFlight=false,this._probeTaskId=null,this.eventWindow.reset();}};var y=class{constructor(e={}){this.delay=0;this.lastTimestamp=0;if(this.initialTimeout=l(e.initialTimeout??50,"BackoffTracker initialTimeout",0,Number.MAX_SAFE_INTEGER,true,true),this.maxTimeout=l(e.maxTimeout??2e3,"BackoffTracker maxTimeout",0,Number.MAX_SAFE_INTEGER,true,true),this.decayFactor=l(e.decayFactor??.5,"BackoffTracker decayFactor",0,1,false,false),this.maxTimeout<this.initialTimeout)throw new a("BackoffTracker maxTimeout must be >= initialTimeout","INVALID_ARGUMENT")}get currentDelay(){if(this.delay===0)return 0;let e=(Date.now()-this.lastTimestamp)/1e3;if(e<=0)return this.delay;let t=this.delay*Math.pow(this.decayFactor,e);return t<1?0:t}onTimeout(){let e=this.currentDelay,t=e>0?e*2:this.initialTimeout;this.delay=Math.min(t,this.maxTimeout),this.lastTimestamp=Date.now();}reset(){this.delay=0,this.lastTimestamp=0;}};var S=class{constructor(e){this.prev=null;this.next=null;this.heapIndex=-1;this.completed=false;this.id=e.id,this.priority=e.priority,this.enqueueTime=e.enqueueTime,this.isProbe=e.isProbe,this.weight=e.weight??1,this._resolve=e.resolve,this._reject=e.reject,this.abortSignal=e.abortSignal;}arm(e){this.abortSignal&&(this.abortListener=()=>{this.claim()&&e();},this.abortSignal.addEventListener("abort",this.abortListener));}dispatch(e){return this.claim()?(this._resolve(e()),true):false}discard(e){return this.claim()?(this._reject(e),true):false}claim(){return this.completed?false:(this.completed=true,this.abortSignal&&this.abortListener&&this.abortSignal.removeEventListener("abort",this.abortListener),true)}reject(e){this._reject(e);}};var k=class{constructor(e){this.heap=[];this.compare=e;}get size(){return this.heap.length}isEmpty(){return this.heap.length===0}peek(){return this.heap[0]}toArray(){return [...this.heap]}has(e){return e.heapIndex>=0&&this.heap[e.heapIndex]===e}clear(){for(let e of this.heap)e.heapIndex=-1;this.heap=[];}insert(e){if(e.heapIndex>=0)throw new a("Item is already in a heap","INVALID_ARGUMENT");let t=this.heap.length;this.heap.push(e),e.heapIndex=t,this._bubbleUp(t);}pop(){if(this.heap.length===0)return;let e=this.heap[0];e.heapIndex=-1;let t=this.heap.pop();return this.heap.length>0&&t!==e&&(this.heap[0]=t,t.heapIndex=0,this._bubbleDown(0)),e}delete(e){let t=e.heapIndex;if(t<0||this.heap[t]!==e)return;e.heapIndex=-1;let i=this.heap.pop();if(t===this.heap.length||i===e)return e;this.heap[t]=i,i.heapIndex=t;let r=this.heap[t-1>>1];return t>0&&this.compare(i,r)<0?this._bubbleUp(t):this._bubbleDown(t),e}_swap(e,t){let i=this.heap[e],r=this.heap[t];this.heap[e]=r,this.heap[t]=i,i.heapIndex=t,r.heapIndex=e;}_bubbleUp(e){for(;e>0;){let t=e-1>>1;if(this.compare(this.heap[e],this.heap[t])<0)this._swap(e,t),e=t;else break}}_bubbleDown(e){let t=this.heap.length;for(;;){let i=e,r=2*e+1,s=2*e+2;if(r<t&&this.compare(this.heap[r],this.heap[i])<0&&(i=r),s<t&&this.compare(this.heap[s],this.heap[i])<0&&(i=s),i===e)break;this._swap(e,i),e=i;}}};var I=class{constructor(){this.head=null;this.tail=null;this._size=0;}get size(){return this._size}isEmpty(){return this._size===0}peekHead(){return this.head===null?void 0:this.head}pushTail(e){e.prev=this.tail,e.next=null,this.tail!==null?this.tail.next=e:this.head=e,this.tail=e,this._size++;}remove(e){e.prev!==null?e.prev.next=e.next:this.head=e.next,e.next!==null?e.next.prev=e.prev:this.tail=e.prev,e.prev=e.next=null,this._size--;}clear(){this.head=this.tail=null,this._size=0;}};var _=class{constructor(e,t){this.cachedIndex=-1;this.cachedUntil=0;this.size=l(e,"CombinedWindow size",1,Number.MAX_SAFE_INTEGER,true,true),this.stepMs=l(t,"CombinedWindow stepMs",1,Number.MAX_SAFE_INTEGER,true,true),this.timestamps=new Float64Array(e),this.acquired=new Int32Array(e),this.released=new Int32Array(e),this.timeouts=new Int32Array(e),this.ifSum=new Float64Array(e),this.ifCount=new Int32Array(e),this.ifMax=new Float64Array(e),this.qSum=new Float64Array(e),this.qCount=new Int32Array(e),this.qMax=new Float64Array(e),this.latSum=new Float64Array(e),this.latCount=new Int32Array(e);}bucket(e){if(e<this.cachedUntil&&e>=this.cachedUntil-this.stepMs)return this.cachedIndex;let t=Math.floor(e/this.stepMs),i=t*this.stepMs,r=t%this.size;return this.timestamps[r]!==i&&(this.timestamps[r]=i,this.acquired[r]=0,this.released[r]=0,this.timeouts[r]=0,this.ifSum[r]=0,this.ifCount[r]=0,this.ifMax[r]=0,this.qSum[r]=0,this.qCount[r]=0,this.qMax[r]=0,this.latSum[r]=0,this.latCount[r]=0),this.cachedIndex=r,this.cachedUntil=i+this.stepMs,r}addInflight(e,t){this.ifSum[e]+=t,this.ifCount[e]++,t>this.ifMax[e]&&(this.ifMax[e]=t);}addQueue(e,t){this.qSum[e]+=t,this.qCount[e]++,t>this.qMax[e]&&(this.qMax[e]=t);}recordAcquire(e,t,i){let r=this.bucket(e);this.acquired[r]++,this.addInflight(r,t),this.addQueue(r,i);}recordAcquireQueued(e,t,i,r){let s=this.bucket(e);this.acquired[s]++,this.latSum[s]+=t,this.latCount[s]++,this.addInflight(s,i),this.addQueue(s,r);}recordRelease(e,t,i){let r=this.bucket(e);this.released[r]++,this.addInflight(r,t),this.addQueue(r,i);}recordTimeoutQueue(e,t){let i=this.bucket(e);this.timeouts[i]++,this.addQueue(i,t);}sampleBoth(e,t,i){let r=this.bucket(e);this.addInflight(r,t),this.addQueue(r,i);}addAcquired(e){this.acquired[this.bucket(e)]++;}addReleased(e){this.released[this.bucket(e)]++;}addTimeout(e){this.timeouts[this.bucket(e)]++;}addLatency(e,t){let i=this.bucket(e);this.latSum[i]+=t,this.latCount[i]++;}sampleInflight(e,t){this.addInflight(this.bucket(e),t);}sampleQueue(e,t){this.addQueue(this.bucket(e),t);}snapshot(e){let t=Math.floor(e/this.stepMs)*this.stepMs-(this.size-1)*this.stepMs,i=0,r=0,s=0,u=0,c=0,d=0,p=0,m=0,b=0,f=0,w=0;for(let h=0;h<this.size;h++)this.timestamps[h]<t||(i+=this.acquired[h],r+=this.released[h],s+=this.timeouts[h],u+=this.ifSum[h],c+=this.ifCount[h],this.ifMax[h]>d&&(d=this.ifMax[h]),p+=this.qSum[h],m+=this.qCount[h],this.qMax[h]>b&&(b=this.qMax[h]),f+=this.latSum[h],w+=this.latCount[h]);return {counts:{acquired:i,released:r,timeouts:s},inflight:{avg:c===0?0:u/c,max:d,samples:c},queue:{avg:m===0?0:p/m,max:b,samples:m},latency:{avg:w===0?0:f/w,count:w,total:f}}}reset(){this.cachedIndex=-1,this.cachedUntil=0,this.timestamps.fill(0),this.acquired.fill(0),this.released.fill(0),this.timeouts.fill(0),this.ifSum.fill(0),this.ifCount.fill(0),this.ifMax.fill(0),this.qSum.fill(0),this.qCount.fill(0),this.qMax.fill(0),this.latSum.fill(0),this.latCount.fill(0);}},F=[{size:60,stepMs:1e3},{size:60,stepMs:5e3},{size:60,stepMs:15e3},{size:60,stepMs:6e4},{size:60,stepMs:144e4}],g=class{constructor(e=F){this._totalAcquiredFast=0;this._totalAcquiredQueued=0;this._totalReleased=0;this._totalTimeouts=0;this._totalPurged=0;this._totalAborts=0;this._capacity=0;this._circuitOpen=false;this._circuitHalfOpen=false;if(e.length===0)throw new a("SemaphoreMetrics requires at least one WindowOptions","INVALID_ARGUMENT");this.windows=e.map(({size:r,stepMs:s})=>new _(r,s)),this.windowLabels=e.map(({size:r,stepMs:s})=>{let u=r*s;return u>=36e5&&u%36e5===0?`${u/36e5}h`:u>=6e4&&u%6e4===0?`${u/6e4}m`:u>=1e3&&u%1e3===0?`${u/1e3}s`:`${u}ms`});let t=new Set;for(let r of this.windowLabels){if(t.has(r))throw new a(`SemaphoreMetrics windows produce duplicate label "${r}" (two windows cover the same horizon)`,"INVALID_ARGUMENT");t.add(r);}let i=0;for(let r=1;r<e.length;r++)e[r].size*e[r].stepMs<e[i].size*e[i].stepMs&&(i=r);this.primaryLabel=this.windowLabels[i],this.primaryWindowMs=e[i].size*e[i].stepMs;}onAcquireFast(e,t,i){this._totalAcquiredFast++;for(let r of this.windows)r.recordAcquire(e,t,i);}onAcquireQueued(e,t,i,r){this._totalAcquiredQueued++;for(let s of this.windows)s.recordAcquireQueued(e,t,i,r);}onRelease(e,t,i){this._totalReleased++;for(let r of this.windows)r.recordRelease(e,t,i);}onTimeout(e,t){this._totalTimeouts++;for(let i of this.windows)i.recordTimeoutQueue(e,t);}onAbort(e,t){this._totalAborts++;for(let i of this.windows)i.sampleQueue(e,t);}onPurge(e,t){this._totalPurged++;for(let i of this.windows)i.sampleQueue(e,t);}sampleGauges(e,t,i){for(let r of this.windows)r.sampleBoth(e,t,i);}sampleQueueDepthAt(e,t){for(let i of this.windows)i.sampleQueue(e,t);}markAcquireFast(){this._totalAcquiredFast++;let e=Date.now();for(let t of this.windows)t.addAcquired(e);}markAcquireQueued(e){this._totalAcquiredQueued++;let t=Date.now();for(let i of this.windows)i.addAcquired(t),i.addLatency(t,e);}markReleased(){this._totalReleased++;let e=Date.now();for(let t of this.windows)t.addReleased(e);}markTimeout(){this._totalTimeouts++;let e=Date.now();for(let t of this.windows)t.addTimeout(e);}markAbort(){this._totalAborts++;}sampleInFlight(e){let t=Date.now();for(let i of this.windows)i.sampleInflight(t,e);}sampleQueueDepth(e){let t=Date.now();for(let i of this.windows)i.sampleQueue(t,e);}markCapacityChange(e){this._capacity=e;}markCircuitOpen(){this._circuitOpen=true,this._circuitHalfOpen=false;}markCircuitHalfOpen(){this._circuitOpen=false,this._circuitHalfOpen=true;}markCircuitClose(){this._circuitOpen=false,this._circuitHalfOpen=false;}getSnapshot(){let e=Date.now(),t={},i=0,r=0;for(let s=0;s<this.windowLabels.length;s++){let u=this.windows[s].snapshot(e);t[this.windowLabels[s]]=u,s===0&&(i=u.inflight.avg,r=u.queue.avg);}return {windows:t,meta:{inFlightLastMinute:Math.round(i),queueDepthLastMinute:Math.round(r),totalAcquiredFast:this._totalAcquiredFast,totalAcquiredQueued:this._totalAcquiredQueued,totalReleased:this._totalReleased,totalTimeouts:this._totalTimeouts,totalPurged:this._totalPurged,totalAborts:this._totalAborts,capacity:this._capacity,circuitOpen:this._circuitOpen,circuitHalfOpen:this._circuitHalfOpen}}}reset(){for(let e of this.windows)e.reset();this._totalAcquiredFast=0,this._totalAcquiredQueued=0,this._totalReleased=0,this._totalTimeouts=0,this._totalPurged=0,this._totalAborts=0,this._capacity=0,this._circuitOpen=false,this._circuitHalfOpen=false;}destroy(){this.reset();}};var E={fifo:(o,e)=>o.id-e.id,lifo:(o,e)=>e.id-o.id,fifoWithPriority:(o,e)=>o.priority-e.priority||o.id-e.id,lifoWithPriority:(o,e)=>o.priority-e.priority||e.id-o.id};function P(o){if(o.comparator!==void 0){if(typeof o.comparator!="function")throw new a("Semaphore comparator must be a function","INVALID_ARGUMENT");return o.comparator}let e=o.queueOrder??"fifoWithPriority",t=E[e];if(t===void 0)throw new a(`Semaphore queueOrder must be one of: ${Object.keys(E).join(", ")} (got ${JSON.stringify(e)})`,"INVALID_ARGUMENT");return t}function R(o){let e=P(o);return (t,i)=>{if(t.isProbe!==i.isProbe)return t.isProbe?-1:1;let r=e(t,i);return typeof r!="number"||Number.isNaN(r)?t.id-i.id:r}}var n={TASKACQUIRE:"task-acquire",TASKRELEASE:"task-release",TASKTIMEOUT:"task-timeout",TASKABORT:"task-abort",QUEUEPURGE:"queue-purge",QUEUEEVICT:"queue-evict",CIRCUITOPEN:"circuit-open",CIRCUITHALFOPEN:"circuit-half-open",CIRCUITCLOSE:"circuit-close",SHUTDOWN:"shutdown"};var M=class{constructor(e,t={}){this.pendingReleaseCount=0;this.releaseGeneration=0;this.scheduled=false;this.isShutdown=false;this.taskIdCounter=0;this.totalAcquired=0;this.totalReleased=0;this.totalTimeouts=0;this.totalPurged=0;this.totalEvictions=0;this.eventListeners=new Map;this.drainPromise=null;this.drainResolve=null;this.purgeIntervalId=null;this.timeoutTimerId=null;this._runSchedulerBound=()=>{this._runScheduler();};this.lastAdmissionWasProbe=false;if(l(e,"Semaphore count",0,Number.MAX_SAFE_INTEGER,true,false),this.purgeIntervalMs=l(t.purgeIntervalMs??3e3,"Semaphore purgeIntervalMs",500,Number.MAX_SAFE_INTEGER,true,true),this.queueMaxTimeout=l(t.queueMaxTimeout??1e4,"Semaphore queueMaxTimeout",1,Number.MAX_SAFE_INTEGER,true,true),this.queueMaxLength=l(t.queueMaxLength??1024,"Semaphore queueMaxLength",1,Number.MAX_SAFE_INTEGER,true,true),this.queueMaxAge=l(t.queueMaxAge??3e4,"Semaphore queueMaxAge",1,Number.MAX_SAFE_INTEGER,true,true),this.rejectOnFull=t.rejectOnFull??false,this.metricsEnabled=t.metricsEnabled??true,this.debug=t.debug??false,t.circuitBreakerFailurePredicate!==void 0&&typeof t.circuitBreakerFailurePredicate!="function")throw new a("Semaphore circuitBreakerFailurePredicate must be a function","INVALID_ARGUMENT");this.failurePredicate=t.circuitBreakerFailurePredicate;let i=R({queueOrder:t.queueOrder,comparator:t.comparator});this.queue=new k(i),this.enqueueOrder=new I,this.permits=new T(e),this.circuit=t.circuitBreaker??new v({threshold:t.circuitBreakerThreshold,window:t.circuitBreakerWindow,windowBucketWidth:t.circuitBreakerWindowBucketWidth,cooldown:t.circuitBreakerCooldown,minThroughput:t.circuitBreakerMinThroughput,minFailures:t.circuitBreakerMinFailures}),this.backoff=new y({initialTimeout:t.backoffInitialTimeout,maxTimeout:t.backoffMaxTimeout,decayFactor:t.backoffDecayFactor}),this.metricsCollector=this.metricsEnabled?new g(t.metricsWindows):void 0,this.metricsCollector?.markCapacityChange(this.permits.capacity),this.metricsCollector?.sampleGauges(Date.now(),this.permits.inFlight,this.queue.size),this._startPurgeInterval();}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(t);}off(e,t){this.eventListeners.get(e)?.delete(t),this.eventListeners.get(e)?.size===0&&this.eventListeners.delete(e);}removeAllListeners(e){e?this.eventListeners.delete(e):this.eventListeners.clear();}hasListeners(e){let t=this.eventListeners.get(e);return t!==void 0&&t.size>0}emit(e,...t){let i=this.eventListeners.get(e);if(!(!i||i.size===0)){if(i.size===1){let r=i.values().next().value;try{r(...t);}catch(s){console.warn(`[Semaphore] Error in listener for "${e}":`,s);}return}for(let r of Array.from(i))try{r(...t);}catch(s){console.warn(`[Semaphore] Error in listener for "${e}":`,s);}}}_tryAcquireFast(e){if(this.circuit.isHalfOpen){if(this.circuit.hasProbeInFlight||!this.permits.hasCapacityFor(e))return null;if(this.circuit.markProbeInFlight(),this.permits.acquire(e),this.totalAcquired++,this.metricsCollector!==void 0){let t=Date.now();this.circuit.trackAttempt(t),this.metricsCollector.onAcquireFast(t,this.permits.inFlight,this.queue.size);}else this.circuit.trackAttempt();return this.hasListeners(n.TASKACQUIRE)&&this.emit(n.TASKACQUIRE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available,probe:true}),this._createRelease(true,e)}if(this.queue.size>0||!this.permits.hasCapacityFor(e))return null;if(this.permits.acquire(e),this.totalAcquired++,this.metricsCollector!==void 0){let t=Date.now();this.circuit.trackAttempt(t),this.metricsCollector.onAcquireFast(t,this.permits.inFlight,this.queue.size);}else this.circuit.trackAttempt();return this.hasListeners(n.TASKACQUIRE)&&this.emit(n.TASKACQUIRE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available}),this._createRelease(false,e)}_createRelease(e=false,t=1){this.pendingReleaseCount++;let i=this.releaseGeneration,r=false;return ()=>{if(r||i!==this.releaseGeneration){this.debug&&console.warn("[Semaphore] release() called after already released (no-op)");return}r=true,this.pendingReleaseCount--,this.permits.release(t),this.totalReleased++,this.metricsCollector?.onRelease(Date.now(),this.permits.inFlight,this.queue.size),e&&this.circuit.isHalfOpen&&(this.circuit.handleProbeSuccess(),this.emit(n.CIRCUITCLOSE),this.metricsCollector?.markCircuitClose(),this.debug&&console.info("[Semaphore] Circuit closed after successful probe")),this.permits.assertInvariant(this.debug),this.hasListeners(n.TASKRELEASE)&&this.emit(n.TASKRELEASE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available}),this._checkDrain(),this.schedule();}}_checkDrain(){this.drainResolve&&this.queue.size===0&&this.permits.available===this.permits.capacity&&(this.drainResolve(),this.drainResolve=null,this.drainPromise=null);}_enqueue(e){this.circuit.trackAttempt(),this.queue.insert(e),this.enqueueOrder.pushTail(e),this._armTimeout();}_dequeue(e){this.queue.delete(e)!==void 0&&(this.enqueueOrder.remove(e),this.enqueueOrder.size===0&&this._clearTimeout());}_armTimeout(){if(this.timeoutTimerId!==null)return;let e=this.enqueueOrder.peekHead();if(e===void 0)return;let t=Math.max(0,e.enqueueTime+this.queueMaxTimeout-Date.now());this.timeoutTimerId=setTimeout(()=>this._fireTimeout(),t);}_clearTimeout(){this.timeoutTimerId!==null&&(clearTimeout(this.timeoutTimerId),this.timeoutTimerId=null);}_fireTimeout(){if(this.timeoutTimerId=null,this.isShutdown)return;let e=Date.now(),t=0,i=this.enqueueOrder.peekHead();for(;i!==void 0&&i.enqueueTime+this.queueMaxTimeout<=e;){let r=i.claim();this._dequeue(i),r&&(this._timeoutTask(i),t++),i=this.enqueueOrder.peekHead();}if(i!==void 0){let r=Math.max(0,i.enqueueTime+this.queueMaxTimeout-e);this.timeoutTimerId=setTimeout(()=>this._fireTimeout(),r);}t>0&&(this._checkDrain(),this.schedule());}_evictQueueOnCircuitOpen(){if(this.queue.size===0)return;let e=0;for(let t of this.queue.toArray()){if(t.isProbe)continue;let i=t.discard(new a("Circuit breaker opened while task was queued","CIRCUIT_OPEN"));this._dequeue(t),i&&(e++,this.totalEvictions++,this.emit(n.QUEUEEVICT,{id:t.id,priority:t.priority,enqueueTime:t.enqueueTime,weight:t.weight}),this.debug&&console.warn(`[Semaphore] Evicted queued task #${t.id}: circuit opened`));}e>0&&this.metricsCollector?.sampleQueueDepthAt(Date.now(),this.queue.size);}_timeoutTask(e){if(this.totalTimeouts++,this.circuit.recordFailure(),this.backoff.onTimeout(),e.isProbe)this.circuit.handleProbeFailure(),this.emit(n.CIRCUITOPEN,{timeoutRate:1,recentTimeouts:1,total:1,reason:"half-open-probe-failed"}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn("[Semaphore] Circuit re-opened: half-open probe timed out");else {let t=this.circuit.evaluateAndTrip();t.tripped&&(this.emit(n.CIRCUITOPEN,{timeoutRate:t.timeoutRate,recentTimeouts:t.failures,total:t.attempts}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn(`[Semaphore] Circuit opened. Rate: ${(t.timeoutRate*100).toFixed(1)}%`),this._evictQueueOnCircuitOpen());}this.metricsCollector?.onTimeout(Date.now(),this.queue.size),this.emit(n.TASKTIMEOUT,{queueLength:this.queue.size,backoffDelay:this.backoff.currentDelay,taskId:e.id}),this.debug&&console.warn(`[Semaphore] Task #${e.id} timed out after ${this.queueMaxTimeout}ms`),e.reject(new a(`Semaphore acquire timed out after ${this.queueMaxTimeout}ms (queue: ${this.queue.size})`,"TIMEOUT"));}_onTaskAbort(e,t){this._dequeue(e),e.isProbe&&this.circuit.releaseProbeSlot(),this.metricsCollector?.onAbort(Date.now(),this.queue.size),this.emit(n.TASKABORT),this.debug&&console.info(`[Semaphore] Task #${e.id} aborted`),t(new a("Semaphore acquire aborted","ABORTED")),this._checkDrain(),this.schedule();}schedule(){if(this.scheduled||this.isShutdown)return;this.scheduled=true;let e=this.backoff.currentDelay;e>0?setTimeout(this._runSchedulerBound,e).unref?.():queueMicrotask(this._runSchedulerBound);}_runScheduler(){if(!this.isShutdown){this.scheduled=false;try{for(;this.queue.size>0&&!this.circuit.isOpen;){let e=this.queue.peek();if(!e||this.circuit.isHalfOpen&&e.id!==this.circuit.probeTaskId||!this.permits.hasCapacityFor(e.weight))break;let t=this.queue.pop();this.enqueueOrder.remove(t),this.enqueueOrder.size===0&&this._clearTimeout(),t.dispatch(()=>{let r=Date.now(),s=Math.max(0,r-t.enqueueTime);return this.permits.acquire(t.weight),this.totalAcquired++,this.metricsCollector?.onAcquireQueued(r,s,this.permits.inFlight,this.queue.size),this.permits.assertInvariant(this.debug),this._createRelease(t.isProbe,t.weight)})&&this.hasListeners(n.TASKACQUIRE)&&this.emit(n.TASKACQUIRE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available,...t.isProbe?{probe:!0}:{}});}this._checkDrain();}catch(e){e instanceof Error?console.error("[Semaphore] Scheduler error:",e.message,e.stack):console.error("[Semaphore] Scheduler error:",e);}}}_startPurgeInterval(){this.purgeIntervalId!==null&&clearInterval(this.purgeIntervalId),this.purgeIntervalId=setInterval(()=>{this.isShutdown||this._purgeStaleTasks();},this.purgeIntervalMs),this.purgeIntervalId.unref?.();}_purgeStaleTasks(){let e=Date.now(),t=this.queue.size,i=this.enqueueOrder.peekHead();for(;i!==void 0&&e-i.enqueueTime>this.queueMaxAge;){this.circuit.probeTaskId===i.id&&this.circuit.releaseProbeSlot();let r=i.discard(new a(`Task purged after ${this.queueMaxAge}ms`,"PURGED"));this._dequeue(i),r&&(this.totalPurged++,this.metricsCollector?.onPurge(Date.now(),this.queue.size),this.emit(n.QUEUEPURGE,{id:i.id,priority:i.priority,enqueueTime:i.enqueueTime,weight:i.weight}),this.debug&&console.warn(`[Semaphore] Purged stale task #${i.id}`)),i=this.enqueueOrder.peekHead();}this.debug&&this.queue.size<t&&console.info(`[Semaphore] Purged ${t-this.queue.size} stale tasks`),this.queue.size<t&&(this._checkDrain(),this.schedule());}tryAcquire(e=1){return this.isShutdown||!Number.isInteger(e)||e<1||e>this.permits.capacity||(this.circuit.checkAndTransition()&&(this.emit(n.CIRCUITHALFOPEN),this.metricsCollector?.markCircuitHalfOpen(),this.debug&&console.info("[Semaphore] Circuit entering half-open"),this.schedule()),this.circuit.isOpen)?null:this._tryAcquireFast(e)}acquire(e,t=0,i=1){return this._acquire(e,t,i)}_acquire(e,t=0,i=1){if(this.lastAdmissionWasProbe=false,this.isShutdown)return Promise.reject(new a("Semaphore is shut down","SHUTDOWN"));if(!Number.isInteger(i)||i<1||i>this.permits.capacity)return Promise.reject(new a(`Invalid weight: ${i} (must be integer in 1..${this.permits.capacity})`,"INVALID_WEIGHT"));if(typeof t!="number"||!Number.isFinite(t))return Promise.reject(new a(`Invalid priority: ${t} (must be a finite number)`,"INVALID_PRIORITY"));if(this.circuit.checkAndTransition()&&(this.emit(n.CIRCUITHALFOPEN),this.metricsCollector?.markCircuitHalfOpen(),this.debug&&console.info("[Semaphore] Circuit entering half-open"),this.schedule()),this.circuit.isOpen)return Promise.reject(new a(`Circuit breaker open, retry in ${this.circuit.cooldownRemaining}ms`,"CIRCUIT_OPEN"));if(this.circuit.isHalfOpen&&this.circuit.hasProbeInFlight)return Promise.reject(new a("Circuit breaker half-open, probe in flight","CIRCUIT_HALF_OPEN"));if(e?.aborted)return Promise.reject(new a("Semaphore acquire aborted before start","ABORTED"));let r=this.circuit.isHalfOpen,s=this._tryAcquireFast(i);if(s)return this.lastAdmissionWasProbe=r,Promise.resolve(s);let u=this.circuit.isHalfOpen&&!this.circuit.hasProbeInFlight;return !u&&this.rejectOnFull?Promise.reject(new a("Semaphore at capacity (rejectOnFull)","QUEUE_FULL")):!u&&this.queue.size>=this.queueMaxLength?Promise.reject(new a(`Queue full (${this.queueMaxLength})`,"QUEUE_FULL")):(this.lastAdmissionWasProbe=u,new Promise((c,d)=>{let p=++this.taskIdCounter,m=Date.now(),b=u,f=new S({id:p,priority:b?Number.MIN_SAFE_INTEGER:t,enqueueTime:m,isProbe:b,resolve:c,reject:d,abortSignal:e,weight:i});f.arm(()=>this._onTaskAbort(f,d)),b&&this.circuit.claimProbeSlot(p),this.metricsCollector?.sampleQueueDepthAt(Date.now(),this.queue.size+1),this._enqueue(f),this.schedule();}))}peekQueue(){let e=[],t=this.enqueueOrder.peekHead();for(;t!==void 0;)e.push({id:t.id,priority:t.priority,enqueueTime:t.enqueueTime,weight:t.weight,isProbe:t.isProbe}),t=t.next??void 0;return e}async use(e,t,i=0,r=1){let s=this._acquire(t,i,r),u=this.failurePredicate!==void 0&&this.lastAdmissionWasProbe,c=await s;try{let d=await e();return c(),d}catch(d){if(this.failurePredicate!==void 0){let p=false;try{p=this.failurePredicate(d)===!0;}catch(m){console.warn("[Semaphore] circuitBreakerFailurePredicate threw (rejection treated as non-matching):",m);}p&&(u&&this.circuit.isHalfOpen?(this.circuit.recordFailure(),this.circuit.handleProbeFailure(),this.emit(n.CIRCUITOPEN,{timeoutRate:1,recentTimeouts:1,total:1,reason:"half-open-probe-failed"}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn("[Semaphore] Circuit re-opened: half-open probe operation failed")):this.reportFailure());}throw c(),d}}drain(e){return this.isShutdown?Promise.reject(new a("Cannot drain: semaphore is shut down","SHUTDOWN")):(e!==void 0&&l(e,"drain timeoutMs",1,Number.MAX_SAFE_INTEGER,true,true),this.drainPromise?this.drainPromise:this.queue.size===0&&this.permits.available===this.permits.capacity?Promise.resolve():(this.drainPromise=new Promise((t,i)=>{if(this.drainResolve=t,e!==void 0){let r=setTimeout(()=>{this.drainResolve=null,this.drainPromise=null,i(new a(`drain() timed out after ${e}ms`,"TIMEOUT"));},e),s=this.drainResolve;this.drainResolve=()=>{clearTimeout(r),s();};}}),this.drainPromise))}reset(e={}){if(this.isShutdown)throw new a("Cannot reset a semaphore that has been shut down","SHUTDOWN");for(let t of this.queue.toArray())t.discard(new a("Semaphore reset","SHUTDOWN"));this.queue.clear(),this.enqueueOrder.clear(),this._clearTimeout(),this.metricsCollector?.reset(),this.permits.reset(),this.backoff.reset(),this.circuit.reset(),this.releaseGeneration++,this.pendingReleaseCount=0,this.scheduled=false,this.isShutdown=false,this.taskIdCounter=0,this.totalAcquired=0,this.totalReleased=0,this.totalTimeouts=0,this.totalPurged=0,this.totalEvictions=0,e.clearListeners&&this.eventListeners.clear(),this.drainResolve&&(this.drainResolve(),this.drainResolve=null,this.drainPromise=null),this._startPurgeInterval(),this.metricsCollector?.markCapacityChange(this.permits.capacity),this.metricsCollector?.sampleGauges(Date.now(),this.permits.inFlight,this.queue.size),this.debug&&console.info("[Semaphore] Reset to initial state");}shutdown(e="Semaphore shutdown"){if(!this.isShutdown){this.isShutdown=true,this.debug&&console.info(`[Semaphore] Shutdown: ${e}`),this.purgeIntervalId!==null&&(clearInterval(this.purgeIntervalId),this.purgeIntervalId=null),this._clearTimeout();for(let t of this.queue.toArray())t.discard(new a(e,"SHUTDOWN"));this.queue.clear(),this.enqueueOrder.clear(),this.releaseGeneration++,this.pendingReleaseCount=0,this.permits.reset(),this.metricsCollector?.destroy(),this.metricsCollector=void 0,this.drainResolve&&(this.drainResolve(),this.drainResolve=null,this.drainPromise=null),this.emit(n.SHUTDOWN,e);}}cancel(){if(this.isShutdown)return;let e=this.queue.toArray();for(let t of e)t.isProbe&&this.circuit.releaseProbeSlot(),t.discard(new a("Semaphore acquire cancelled","CANCELLED")),this._dequeue(t);this.metricsCollector?.sampleQueueDepthAt(Date.now(),this.queue.size),this.debug&&console.info(`[Semaphore] Cancelled ${e.length} queued tasks`),this._checkDrain(),this.schedule();}reportFailure(){if(this.isShutdown)return;this.circuit.recordFailure();let e=this.circuit.evaluateAndTrip();e.tripped&&(this.emit(n.CIRCUITOPEN,{timeoutRate:e.timeoutRate,recentTimeouts:e.failures,total:e.attempts,reason:"reported-failure"}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn(`[Semaphore] Circuit opened via reportFailure(). Rate: ${(e.timeoutRate*100).toFixed(1)}%`),this._evictQueueOnCircuitOpen());}status(){let e=this.metricsCollector?.getSnapshot()??null,t=this.metricsCollector!==void 0?e?.windows?.[this.metricsCollector.primaryLabel]?.counts:void 0,i=(this.metricsCollector?.primaryWindowMs??6e4)/1e3,r=t?.acquired??0,s=t?.timeouts??0,u=this.enqueueOrder.peekHead(),c=u===void 0?0:Math.max(0,Date.now()-u.enqueueTime);return {status:{running:this.permits.capacity-this.permits.available,queued:this.queue.size,available:this.permits.available,inFlight:this.permits.inFlight,pendingReleases:this.pendingReleaseCount,circuitOpen:this.circuit.isOpen,circuitHalfOpen:this.circuit.isHalfOpen,backoffDelay:Math.round(this.backoff.currentDelay),requestsPerSecond:+(r/i).toFixed(2),timeoutRate1m:r+s>0?+(s/(r+s)*100).toFixed(1):0,queueAge:c},lifetime:{totalAcquired:this.totalAcquired,totalReleased:this.totalReleased,totalTimeouts:this.totalTimeouts,totalPurged:this.totalPurged,totalEvictions:this.totalEvictions,circuitBreakerCooldownRemaining:this.circuit.cooldownRemaining},metrics:e}}get availablePermits(){return this.permits.available}isAvailable(){return !this.isShutdown&&!this.circuit.isOpen&&!this.permits.isFull}get capacity(){return this.permits.capacity}get circuitState(){return this.circuit.state}get queueLength(){return this.queue.size}};var x={tripped:false},A=class{constructor(){this.state="closed";this.isOpen=false;this.isHalfOpen=false;this.hasProbeInFlight=false;this.probeTaskId=null;this.cooldownRemaining=0;}checkAndTransition(){return false}trackAttempt(){}recordFailure(){}evaluateAndTrip(){return x}markProbeInFlight(){}claimProbeSlot(){}releaseProbeSlot(){}handleProbeSuccess(){}handleProbeFailure(){}reset(){}};var O={tripped:false},C=class{constructor(){this.state="closed";this.hasProbeInFlight=false;this.probeTaskId=null;}get isOpen(){return this.state==="open"}get isHalfOpen(){return false}get cooldownRemaining(){return 0}open(){this.state="open";}close(){this.state="closed";}checkAndTransition(){return false}trackAttempt(){}recordFailure(){}evaluateAndTrip(){return O}markProbeInFlight(){}claimProbeSlot(){}releaseProbeSlot(){}handleProbeSuccess(){}handleProbeFailure(){}reset(){this.state="closed";}};exports.ManualCircuitBreaker=C;exports.NoopCircuitBreaker=A;exports.QUEUE_ORDERINGS=E;exports.SaturationCircuitBreaker=v;exports.Semaphore=M;exports.SemaphoreError=a;exports.SemaphoreEvents=n;//# sourceMappingURL=index.cjs.map
'use strict';var a=class extends Error{constructor(e,t){super(e),this.name="SemaphoreError",this.code=t;}};var l=(o,e,t,i,r=true,s=true)=>{if(typeof o!="number"||Number.isNaN(o))throw new a(`${e} must be a valid number`,"INVALID_ARGUMENT");if(r&&!Number.isInteger(o))throw new a(`${e} must be an integer`,"INVALID_ARGUMENT");if(s?o<t:o<=t)throw new a(`${e} must be ${s?">=":">"} ${t}`,"INVALID_ARGUMENT");if(s?o>i:o>=i)throw new a(`${e} must be ${s?"<=":"<"} ${i}`,"INVALID_ARGUMENT");return o};var T=class{constructor(e){this._inFlight=0;this.capacity=e,this._available=e;}get available(){return this._available}get inFlight(){return this._inFlight}get isFull(){return this._available===0}hasCapacityFor(e){return this._available>=e}acquire(e=1){this._available-=e,this._inFlight+=e;}release(e=1){this._available=Math.min(this._available+e,this.capacity),this._inFlight=this._inFlight<e?0:this._inFlight-e;}assertInvariant(e){if(!e)return;let t=this._inFlight+this._available;t!==this.capacity&&console.error(`[Semaphore] Invariant violation: inFlight(${this._inFlight}) + available(${this._available}) = ${t}, expected capacity(${this.capacity})`);}reset(){this._available=this.capacity,this._inFlight=0;}};var A=class{constructor(e,t){this.cachedIndex=-1;this.cachedUntil=0;this.size=e,this.stepMs=t,this.buckets=new Int32Array(e*2),this.timestamps=new Float64Array(e);}idx(e){if(e<this.cachedUntil&&e>=this.cachedUntil-this.stepMs)return this.cachedIndex;let t=Math.floor(e/this.stepMs)*this.stepMs,i=Math.floor(t/this.stepMs)%this.size;return this.timestamps[i]!==t&&(this.timestamps[i]=t,this.buckets[i*2]=0,this.buckets[i*2+1]=0),this.cachedIndex=i,this.cachedUntil=t+this.stepMs,i}addAcquired(e=Date.now()){this.buckets[this.idx(e)*2]++;}addTimeout(){this.buckets[this.idx(Date.now())*2+1]++;}snapshot(){let e=Date.now(),t=Math.floor(e/this.stepMs)*this.stepMs-(this.size-1)*this.stepMs,i=0,r=0;for(let s=0;s<this.size;s++)this.timestamps[s]>=t&&(i+=this.buckets[s*2],r+=this.buckets[s*2+1]);return {acquired:i,timeouts:r}}reset(){this.cachedIndex=-1,this.cachedUntil=0,this.buckets.fill(0),this.timestamps.fill(0);}},v=class{constructor(e={}){this.state="closed";this.openUntil=0;this.probeInFlight=false;this._probeTaskId=null;if(this.threshold=l(e.threshold??.5,"SaturationCircuitBreaker threshold",0,1,false,false),this.window=l(e.window??1e4,"SaturationCircuitBreaker window",1e3,Number.MAX_SAFE_INTEGER,true,true),this.windowBucketWidth=l(e.windowBucketWidth??1e3,"SaturationCircuitBreaker windowBucketWidth",1,Number.MAX_SAFE_INTEGER,true,true),this.windowBucketCount=Math.ceil(this.window/this.windowBucketWidth),this.cooldown=l(e.cooldown??5e3,"SaturationCircuitBreaker cooldown",1e3,Number.MAX_SAFE_INTEGER,true,true),this.minThroughput=l(e.minThroughput??10,"SaturationCircuitBreaker minThroughput",1,Number.MAX_SAFE_INTEGER,true,true),this.minFailures=l(e.minFailures??5,"SaturationCircuitBreaker minFailures",1,Number.MAX_SAFE_INTEGER,true,true),this.window<this.windowBucketWidth)throw new a("SaturationCircuitBreaker window must be >= windowBucketWidth","INVALID_ARGUMENT");if(this.minThroughput<this.minFailures)throw new a("SaturationCircuitBreaker minThroughput must be >= minFailures","INVALID_ARGUMENT");if(this.windowBucketCount<2)throw new a("SaturationCircuitBreaker window must span at least 2 windowBucketWidth buckets","INVALID_ARGUMENT");this.eventWindow=new A(this.windowBucketCount,this.windowBucketWidth);}get isClosed(){return this.state==="closed"}get isOpen(){return this.state==="open"}get isProbing(){return this.state==="probing"}get hasProbeInFlight(){return this.probeInFlight}get probeTaskId(){return this._probeTaskId}get cooldownRemaining(){return this.isOpen?Math.max(0,this.openUntil-Date.now()):0}trackAttempt(e){this.state==="closed"&&this.eventWindow.addAcquired(e);}recordFailure(){this.eventWindow.addTimeout();}checkAndTransition(){return this.state!=="open"||Date.now()<this.openUntil?false:(this.state="probing",this.probeInFlight=false,this._probeTaskId=null,true)}evaluateAndTrip(){if(this.state!=="closed")return {tripped:false};let{timeouts:e,acquired:t}=this.eventWindow.snapshot();if(t<this.minThroughput||e<this.minFailures)return {tripped:false};let i=e/t;return i<this.threshold?{tripped:false}:(this.state="open",this.openUntil=Date.now()+this.cooldown,{tripped:true,timeoutRate:i,failures:e,attempts:t})}markProbeInFlight(){this.probeInFlight=true;}claimProbeSlot(e){this.probeInFlight=true,this._probeTaskId=e;}releaseProbeSlot(){this.probeInFlight=false,this._probeTaskId=null;}handleProbeSuccess(){this.state="closed",this.probeInFlight=false,this._probeTaskId=null,this.eventWindow.reset();}handleProbeFailure(){this.state="open",this.probeInFlight=false,this._probeTaskId=null,this.openUntil=Date.now()+this.cooldown;}reset(){this.state="closed",this.openUntil=0,this.probeInFlight=false,this._probeTaskId=null,this.eventWindow.reset();}};var y=class{constructor(e={}){this.delay=0;this.lastTimestamp=0;if(this.initialTimeout=l(e.initialTimeout??50,"BackoffTracker initialTimeout",0,Number.MAX_SAFE_INTEGER,true,true),this.maxTimeout=l(e.maxTimeout??2e3,"BackoffTracker maxTimeout",0,Number.MAX_SAFE_INTEGER,true,true),this.decayFactor=l(e.decayFactor??.5,"BackoffTracker decayFactor",0,1,false,false),this.maxTimeout<this.initialTimeout)throw new a("BackoffTracker maxTimeout must be >= initialTimeout","INVALID_ARGUMENT")}get currentDelay(){return this._decayedDelay(Date.now())}_decayedDelay(e){if(this.delay===0)return 0;let t=(e-this.lastTimestamp)/1e3;if(t<=0)return this.delay;let i=this.delay*Math.pow(this.decayFactor,t);return i<1?0:i}onTimeout(e=Date.now()){let t=this._decayedDelay(e),i=t>0?t*2:this.initialTimeout;this.delay=Math.min(i,this.maxTimeout),this.lastTimestamp=e;}reset(){this.delay=0,this.lastTimestamp=0;}};var S=class{constructor(e){this.prev=null;this.next=null;this.heapIndex=-1;this.completed=false;this.id=e.id,this.priority=e.priority,this.enqueueTime=e.enqueueTime,this.isProbe=e.isProbe,this.weight=e.weight??1,this._resolve=e.resolve,this._reject=e.reject,this.abortSignal=e.abortSignal;}arm(e){this.abortSignal&&(this.abortListener=()=>{this.claim()&&e();},this.abortSignal.addEventListener("abort",this.abortListener));}dispatch(e){return this.claim()?(this._resolve(e()),true):false}discard(e){return this.claim()?(this._reject(e),true):false}claim(){return this.completed?false:(this.completed=true,this.abortSignal&&this.abortListener&&this.abortSignal.removeEventListener("abort",this.abortListener),true)}reject(e){this._reject(e);}};var g=class{constructor(e){this.heap=[];this.compare=e;}get size(){return this.heap.length}isEmpty(){return this.heap.length===0}peek(){return this.heap[0]}has(e){return e.heapIndex>=0&&this.heap[e.heapIndex]===e}clear(){for(let e of this.heap)e.heapIndex=-1;this.heap=[];}insert(e){if(e.heapIndex>=0)throw new a("Item is already in a heap","INVALID_ARGUMENT");let t=this.heap.length;this.heap.push(e),e.heapIndex=t,this._bubbleUp(t);}pop(){if(this.heap.length===0)return;let e=this.heap[0];e.heapIndex=-1;let t=this.heap.pop();return this.heap.length>0&&t!==e&&(this.heap[0]=t,t.heapIndex=0,this._bubbleDown(0)),e}delete(e){let t=e.heapIndex;if(t<0||this.heap[t]!==e)return;e.heapIndex=-1;let i=this.heap.pop();if(t===this.heap.length||i===e)return e;this.heap[t]=i,i.heapIndex=t;let r=this.heap[t-1>>1];return t>0&&this.compare(i,r)<0?this._bubbleUp(t):this._bubbleDown(t),e}_swap(e,t){let i=this.heap[e],r=this.heap[t];this.heap[e]=r,this.heap[t]=i,i.heapIndex=t,r.heapIndex=e;}_bubbleUp(e){for(;e>0;){let t=e-1>>1;if(this.compare(this.heap[e],this.heap[t])<0)this._swap(e,t),e=t;else break}}_bubbleDown(e){let t=this.heap.length;for(;;){let i=e,r=2*e+1,s=2*e+2;if(r<t&&this.compare(this.heap[r],this.heap[i])<0&&(i=r),s<t&&this.compare(this.heap[s],this.heap[i])<0&&(i=s),i===e)break;this._swap(e,i),e=i;}}};var k=class{constructor(){this.head=null;this.tail=null;this._size=0;}get size(){return this._size}isEmpty(){return this._size===0}peekHead(){return this.head===null?void 0:this.head}pushTail(e){e.prev=this.tail,e.next=null,this.tail!==null?this.tail.next=e:this.head=e,this.tail=e,this._size++;}remove(e){e.prev!==null?e.prev.next=e.next:this.head=e.next,e.next!==null?e.next.prev=e.prev:this.tail=e.prev,e.prev=e.next=null,this._size--;}clear(){this.head=this.tail=null,this._size=0;}};var _=class{constructor(e,t){this.cachedIndex=-1;this.cachedUntil=0;this.size=l(e,"CombinedWindow size",1,Number.MAX_SAFE_INTEGER,true,true),this.stepMs=l(t,"CombinedWindow stepMs",1,Number.MAX_SAFE_INTEGER,true,true),this.timestamps=new Float64Array(e),this.acquired=new Int32Array(e),this.released=new Int32Array(e),this.timeouts=new Int32Array(e),this.ifSum=new Float64Array(e),this.ifCount=new Int32Array(e),this.ifMax=new Float64Array(e),this.qSum=new Float64Array(e),this.qCount=new Int32Array(e),this.qMax=new Float64Array(e),this.latSum=new Float64Array(e),this.latCount=new Int32Array(e);}bucket(e){if(e<this.cachedUntil&&e>=this.cachedUntil-this.stepMs)return this.cachedIndex;let t=Math.floor(e/this.stepMs),i=t*this.stepMs,r=t%this.size;return this.timestamps[r]!==i&&(this.timestamps[r]=i,this.acquired[r]=0,this.released[r]=0,this.timeouts[r]=0,this.ifSum[r]=0,this.ifCount[r]=0,this.ifMax[r]=0,this.qSum[r]=0,this.qCount[r]=0,this.qMax[r]=0,this.latSum[r]=0,this.latCount[r]=0),this.cachedIndex=r,this.cachedUntil=i+this.stepMs,r}addInflight(e,t){this.ifSum[e]+=t,this.ifCount[e]++,t>this.ifMax[e]&&(this.ifMax[e]=t);}addQueue(e,t){this.qSum[e]+=t,this.qCount[e]++,t>this.qMax[e]&&(this.qMax[e]=t);}recordAcquire(e,t,i){let r=this.bucket(e);this.acquired[r]++,this.addInflight(r,t),this.addQueue(r,i);}recordAcquireQueued(e,t,i,r){let s=this.bucket(e);this.acquired[s]++,this.latSum[s]+=t,this.latCount[s]++,this.addInflight(s,i),this.addQueue(s,r);}recordRelease(e,t,i){let r=this.bucket(e);this.released[r]++,this.addInflight(r,t),this.addQueue(r,i);}recordTimeoutQueue(e,t){let i=this.bucket(e);this.timeouts[i]++,this.addQueue(i,t);}sampleBoth(e,t,i){let r=this.bucket(e);this.addInflight(r,t),this.addQueue(r,i);}addAcquired(e){this.acquired[this.bucket(e)]++;}addReleased(e){this.released[this.bucket(e)]++;}addTimeout(e){this.timeouts[this.bucket(e)]++;}addLatency(e,t){let i=this.bucket(e);this.latSum[i]+=t,this.latCount[i]++;}sampleInflight(e,t){this.addInflight(this.bucket(e),t);}sampleQueue(e,t){this.addQueue(this.bucket(e),t);}snapshot(e){let t=Math.floor(e/this.stepMs)*this.stepMs-(this.size-1)*this.stepMs,i=0,r=0,s=0,u=0,c=0,d=0,m=0,p=0,b=0,f=0,w=0;for(let h=0;h<this.size;h++)this.timestamps[h]<t||(i+=this.acquired[h],r+=this.released[h],s+=this.timeouts[h],u+=this.ifSum[h],c+=this.ifCount[h],this.ifMax[h]>d&&(d=this.ifMax[h]),m+=this.qSum[h],p+=this.qCount[h],this.qMax[h]>b&&(b=this.qMax[h]),f+=this.latSum[h],w+=this.latCount[h]);return {counts:{acquired:i,released:r,timeouts:s},inflight:{avg:c===0?0:u/c,max:d,samples:c},queue:{avg:p===0?0:m/p,max:b,samples:p},latency:{avg:w===0?0:f/w,count:w,total:f}}}reset(){this.cachedIndex=-1,this.cachedUntil=0,this.timestamps.fill(0),this.acquired.fill(0),this.released.fill(0),this.timeouts.fill(0),this.ifSum.fill(0),this.ifCount.fill(0),this.ifMax.fill(0),this.qSum.fill(0),this.qCount.fill(0),this.qMax.fill(0),this.latSum.fill(0),this.latCount.fill(0);}},R=[{size:60,stepMs:1e3},{size:60,stepMs:5e3},{size:60,stepMs:15e3},{size:60,stepMs:6e4},{size:60,stepMs:144e4}],I=class{constructor(e=R){this._totalAcquiredFast=0;this._totalAcquiredQueued=0;this._totalReleased=0;this._totalTimeouts=0;this._totalPurged=0;this._totalAborts=0;this._capacity=0;this._circuitOpen=false;this._circuitProbing=false;if(e.length===0)throw new a("SemaphoreMetrics requires at least one WindowOptions","INVALID_ARGUMENT");this.windows=e.map(({size:r,stepMs:s})=>new _(r,s)),this.windowLabels=e.map(({size:r,stepMs:s})=>{let u=r*s;return u>=36e5&&u%36e5===0?`${u/36e5}h`:u>=6e4&&u%6e4===0?`${u/6e4}m`:u>=1e3&&u%1e3===0?`${u/1e3}s`:`${u}ms`});let t=new Set;for(let r of this.windowLabels){if(t.has(r))throw new a(`SemaphoreMetrics windows produce duplicate label "${r}" (two windows cover the same horizon)`,"INVALID_ARGUMENT");t.add(r);}let i=0;for(let r=1;r<e.length;r++)e[r].size*e[r].stepMs<e[i].size*e[i].stepMs&&(i=r);this.primaryLabel=this.windowLabels[i],this.primaryWindowMs=e[i].size*e[i].stepMs;}onAcquireFast(e,t,i){this._totalAcquiredFast++;for(let r of this.windows)r.recordAcquire(e,t,i);}onAcquireQueued(e,t,i,r){this._totalAcquiredQueued++;for(let s of this.windows)s.recordAcquireQueued(e,t,i,r);}onRelease(e,t,i){this._totalReleased++;for(let r of this.windows)r.recordRelease(e,t,i);}onTimeout(e,t){this._totalTimeouts++;for(let i of this.windows)i.recordTimeoutQueue(e,t);}onAbort(e,t){this._totalAborts++;for(let i of this.windows)i.sampleQueue(e,t);}onPurge(e,t){this._totalPurged++;for(let i of this.windows)i.sampleQueue(e,t);}sampleGauges(e,t,i){for(let r of this.windows)r.sampleBoth(e,t,i);}sampleQueueDepthAt(e,t){for(let i of this.windows)i.sampleQueue(e,t);}markAcquireFast(){this._totalAcquiredFast++;let e=Date.now();for(let t of this.windows)t.addAcquired(e);}markAcquireQueued(e){this._totalAcquiredQueued++;let t=Date.now();for(let i of this.windows)i.addAcquired(t),i.addLatency(t,e);}markReleased(){this._totalReleased++;let e=Date.now();for(let t of this.windows)t.addReleased(e);}markTimeout(){this._totalTimeouts++;let e=Date.now();for(let t of this.windows)t.addTimeout(e);}markAbort(){this._totalAborts++;}sampleInFlight(e){let t=Date.now();for(let i of this.windows)i.sampleInflight(t,e);}sampleQueueDepth(e){let t=Date.now();for(let i of this.windows)i.sampleQueue(t,e);}markCapacityChange(e){this._capacity=e;}markCircuitOpen(){this._circuitOpen=true,this._circuitProbing=false;}markCircuitProbing(){this._circuitOpen=false,this._circuitProbing=true;}markCircuitClose(){this._circuitOpen=false,this._circuitProbing=false;}getSnapshot(e=Date.now()){let t={},i=0,r=0;for(let s=0;s<this.windowLabels.length;s++){let u=this.windows[s].snapshot(e);t[this.windowLabels[s]]=u,s===0&&(i=u.inflight.avg,r=u.queue.avg);}return {windows:t,meta:{inFlightLastMinute:Math.round(i),queueDepthLastMinute:Math.round(r),totalAcquiredFast:this._totalAcquiredFast,totalAcquiredQueued:this._totalAcquiredQueued,totalReleased:this._totalReleased,totalTimeouts:this._totalTimeouts,totalPurged:this._totalPurged,totalAborts:this._totalAborts,capacity:this._capacity,circuitOpen:this._circuitOpen,circuitProbing:this._circuitProbing}}}reset(){for(let e of this.windows)e.reset();this._totalAcquiredFast=0,this._totalAcquiredQueued=0,this._totalReleased=0,this._totalTimeouts=0,this._totalPurged=0,this._totalAborts=0,this._capacity=0,this._circuitOpen=false,this._circuitProbing=false;}destroy(){this.reset();}};var E={fifo:(o,e)=>o.id-e.id,lifo:(o,e)=>e.id-o.id,fifoWithPriority:(o,e)=>o.priority-e.priority||o.id-e.id,lifoWithPriority:(o,e)=>o.priority-e.priority||e.id-o.id};function x(o){if(o.comparator!==void 0){if(typeof o.comparator!="function")throw new a("Semaphore comparator must be a function","INVALID_ARGUMENT");return o.comparator}let e=o.queueOrder??"fifoWithPriority",t=E[e];if(t===void 0)throw new a(`Semaphore queueOrder must be one of: ${Object.keys(E).join(", ")} (got ${JSON.stringify(e)})`,"INVALID_ARGUMENT");return t}function M(o){let e=x(o);return (t,i)=>{if(t.isProbe!==i.isProbe)return t.isProbe?-1:1;let r=e(t,i);return typeof r!="number"||Number.isNaN(r)?t.id-i.id:r}}var n={TASKACQUIRE:"task-acquire",TASKRELEASE:"task-release",TASKTIMEOUT:"task-timeout",TASKABORT:"task-abort",QUEUEPURGE:"queue-purge",QUEUEEVICT:"queue-evict",CIRCUITOPEN:"circuit-open",CIRCUITPROBING:"circuit-probing",CIRCUITCLOSE:"circuit-close",SHUTDOWN:"shutdown"};var P=class{constructor(e,t={}){this.pendingReleaseCount=0;this.releaseGeneration=0;this.scheduled=false;this.isShutdown=false;this.taskIdCounter=0;this.totalAcquired=0;this.totalReleased=0;this.totalTimeouts=0;this.totalPurged=0;this.totalEvictions=0;this.eventListeners=new Map;this.drainPromise=null;this.drainResolve=null;this.purgeIntervalId=null;this.timeoutTimerId=null;this._runSchedulerBound=()=>{this._runScheduler();};this.lastAdmissionWasProbe=false;if(l(e,"Semaphore count",0,Number.MAX_SAFE_INTEGER,true,false),this.purgeIntervalMs=l(t.purgeIntervalMs??3e3,"Semaphore purgeIntervalMs",500,Number.MAX_SAFE_INTEGER,true,true),this.queueMaxTimeout=l(t.queueMaxTimeout??1e4,"Semaphore queueMaxTimeout",1,Number.MAX_SAFE_INTEGER,true,true),this.queueMaxLength=l(t.queueMaxLength??1024,"Semaphore queueMaxLength",1,Number.MAX_SAFE_INTEGER,true,true),this.queueMaxAge=l(t.queueMaxAge??3e4,"Semaphore queueMaxAge",1,Number.MAX_SAFE_INTEGER,true,true),this.rejectOnFull=t.rejectOnFull??false,this.metricsEnabled=t.metricsEnabled??true,this.debug=t.debug??false,t.circuitBreakerFailurePredicate!==void 0&&typeof t.circuitBreakerFailurePredicate!="function")throw new a("Semaphore circuitBreakerFailurePredicate must be a function","INVALID_ARGUMENT");this.failurePredicate=t.circuitBreakerFailurePredicate;let i=M({queueOrder:t.queueOrder,comparator:t.comparator});this.queue=new g(i),this.enqueueOrder=new k,this.permits=new T(e),this.circuit=t.circuitBreaker??new v({threshold:t.circuitBreakerThreshold,window:t.circuitBreakerWindow,windowBucketWidth:t.circuitBreakerWindowBucketWidth,cooldown:t.circuitBreakerCooldown,minThroughput:t.circuitBreakerMinThroughput,minFailures:t.circuitBreakerMinFailures}),this.backoff=new y({initialTimeout:t.backoffInitialTimeout,maxTimeout:t.backoffMaxTimeout,decayFactor:t.backoffDecayFactor}),this.metricsCollector=this.metricsEnabled?new I(t.metricsWindows):void 0,this.metricsCollector?.markCapacityChange(this.permits.capacity),this.metricsCollector?.sampleGauges(Date.now(),this.permits.inFlight,this.queue.size),this._startPurgeInterval();}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(t);}off(e,t){this.eventListeners.get(e)?.delete(t),this.eventListeners.get(e)?.size===0&&this.eventListeners.delete(e);}removeAllListeners(e){e?this.eventListeners.delete(e):this.eventListeners.clear();}hasListeners(e){let t=this.eventListeners.get(e);return t!==void 0&&t.size>0}emit(e,...t){let i=this.eventListeners.get(e);if(!(!i||i.size===0)){if(i.size===1){let r=i.values().next().value;try{r(...t);}catch(s){console.warn(`[Semaphore] Error in listener for "${e}":`,s);}return}for(let r of Array.from(i))try{r(...t);}catch(s){console.warn(`[Semaphore] Error in listener for "${e}":`,s);}}}_tryAcquireFast(e){if(this.circuit.isProbing){if(this.circuit.hasProbeInFlight||!this.permits.hasCapacityFor(e))return null;if(this.circuit.markProbeInFlight(),this.permits.acquire(e),this.totalAcquired++,this.metricsCollector!==void 0){let t=Date.now();this.circuit.trackAttempt(t),this.metricsCollector.onAcquireFast(t,this.permits.inFlight,this.queue.size);}else this.circuit.trackAttempt();return this.hasListeners(n.TASKACQUIRE)&&this.emit(n.TASKACQUIRE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available,probe:true}),this._createRelease(true,e)}if(this.queue.size>0||!this.permits.hasCapacityFor(e))return null;if(this.permits.acquire(e),this.totalAcquired++,this.metricsCollector!==void 0){let t=Date.now();this.circuit.trackAttempt(t),this.metricsCollector.onAcquireFast(t,this.permits.inFlight,this.queue.size);}else this.circuit.trackAttempt();return this.hasListeners(n.TASKACQUIRE)&&this.emit(n.TASKACQUIRE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available}),this._createRelease(false,e)}_createRelease(e=false,t=1){this.pendingReleaseCount++;let i=this.releaseGeneration,r=false;return ()=>{if(r||i!==this.releaseGeneration){this.debug&&console.warn("[Semaphore] release() called after already released (no-op)");return}r=true,this.pendingReleaseCount--,this.permits.release(t),this.totalReleased++,this.metricsCollector?.onRelease(Date.now(),this.permits.inFlight,this.queue.size),e&&this.circuit.isProbing&&(this.circuit.handleProbeSuccess(),this.emit(n.CIRCUITCLOSE),this.metricsCollector?.markCircuitClose(),this.debug&&console.info("[Semaphore] Circuit closed after successful probe")),this.permits.assertInvariant(this.debug),this.hasListeners(n.TASKRELEASE)&&this.emit(n.TASKRELEASE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available}),this._checkDrain(),this.schedule();}}_checkDrain(){this.drainResolve&&this.queue.size===0&&this.permits.available===this.permits.capacity&&(this.drainResolve(),this.drainResolve=null,this.drainPromise=null);}_enqueue(e){this.circuit.trackAttempt(),this.queue.insert(e),this.enqueueOrder.pushTail(e),this._armTimeout();}_dequeue(e){this.queue.delete(e)!==void 0&&(this.enqueueOrder.remove(e),this.enqueueOrder.size===0&&this._clearTimeout());}_armTimeout(){if(this.timeoutTimerId!==null)return;let e=this.enqueueOrder.peekHead();if(e===void 0)return;let t=Math.max(0,e.enqueueTime+this.queueMaxTimeout-Date.now());this.timeoutTimerId=setTimeout(()=>this._fireTimeout(),t);}_clearTimeout(){this.timeoutTimerId!==null&&(clearTimeout(this.timeoutTimerId),this.timeoutTimerId=null);}_fireTimeout(){if(this.timeoutTimerId=null,this.isShutdown)return;let e=Date.now(),t=0,i=this.enqueueOrder.peekHead();for(;i!==void 0&&i.enqueueTime+this.queueMaxTimeout<=e;){let r=i.claim();this._dequeue(i),r&&(this._timeoutTask(i,e),t++),i=this.enqueueOrder.peekHead();}if(i!==void 0){let r=Math.max(0,i.enqueueTime+this.queueMaxTimeout-e);this.timeoutTimerId=setTimeout(()=>this._fireTimeout(),r);}t>0&&(this._checkDrain(),this.schedule());}_evictTask(e){let t=e.discard(new a("Circuit breaker opened while task was queued","CIRCUIT_OPEN"));return t&&(this.totalEvictions++,this.emit(n.QUEUEEVICT,{id:e.id,priority:e.priority,enqueueTime:e.enqueueTime,weight:e.weight}),this.debug&&console.warn(`[Semaphore] Evicted queued task #${e.id}: circuit opened`)),t}_evictQueueOnCircuitOpen(){if(this.queue.size===0)return;if(this.circuit.probeTaskId===null){let i=0;for(let r=this.enqueueOrder.peekHead();r!==void 0;r=r.next??void 0)this._evictTask(r)&&i++;this.queue.clear(),this.enqueueOrder.clear(),this._clearTimeout(),i>0&&this.metricsCollector?.sampleQueueDepthAt(Date.now(),this.queue.size);return}let e=0,t=this.enqueueOrder.peekHead();for(;t!==void 0;){let i=t.next??void 0;t.isProbe||(this._evictTask(t)&&e++,this._dequeue(t)),t=i;}e>0&&this.metricsCollector?.sampleQueueDepthAt(Date.now(),this.queue.size);}_timeoutTask(e,t){if(this.totalTimeouts++,this.circuit.recordFailure(),this.backoff.onTimeout(t),e.isProbe)this.circuit.handleProbeFailure(),this.emit(n.CIRCUITOPEN,{timeoutRate:1,recentTimeouts:1,total:1,reason:"probe-failed"}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn("[Semaphore] Circuit re-opened: probe timed out");else {let i=this.circuit.evaluateAndTrip();i.tripped&&(this.emit(n.CIRCUITOPEN,{timeoutRate:i.timeoutRate,recentTimeouts:i.failures,total:i.attempts}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn(`[Semaphore] Circuit opened. Rate: ${(i.timeoutRate*100).toFixed(1)}%`),this._evictQueueOnCircuitOpen());}this.metricsCollector?.onTimeout(t,this.queue.size),this.emit(n.TASKTIMEOUT,{queueLength:this.queue.size,backoffDelay:this.backoff.currentDelay,taskId:e.id}),this.debug&&console.warn(`[Semaphore] Task #${e.id} timed out after ${this.queueMaxTimeout}ms`),e.reject(new a(`Semaphore acquire timed out after ${this.queueMaxTimeout}ms (queue: ${this.queue.size})`,"TIMEOUT"));}_onTaskAbort(e,t){this._dequeue(e),e.isProbe&&this.circuit.releaseProbeSlot(),this.metricsCollector?.onAbort(Date.now(),this.queue.size),this.emit(n.TASKABORT),this.debug&&console.info(`[Semaphore] Task #${e.id} aborted`),t(new a("Semaphore acquire aborted","ABORTED")),this._checkDrain(),this.schedule();}schedule(){if(this.scheduled||this.isShutdown)return;this.scheduled=true;let e=this.backoff.currentDelay;e>0?setTimeout(this._runSchedulerBound,e).unref?.():queueMicrotask(this._runSchedulerBound);}_runScheduler(){if(!this.isShutdown){this.scheduled=false;try{let e=Date.now();for(;this.queue.size>0&&!this.circuit.isOpen;){let t=this.queue.peek();if(!t||this.circuit.isProbing&&t.id!==this.circuit.probeTaskId||!this.permits.hasCapacityFor(t.weight))break;let i=this.queue.pop();this.enqueueOrder.remove(i),this.enqueueOrder.size===0&&this._clearTimeout(),i.dispatch(()=>{let s=Math.max(0,e-i.enqueueTime);return this.permits.acquire(i.weight),this.totalAcquired++,this.metricsCollector?.onAcquireQueued(e,s,this.permits.inFlight,this.queue.size),this.permits.assertInvariant(this.debug),this._createRelease(i.isProbe,i.weight)})&&this.hasListeners(n.TASKACQUIRE)&&this.emit(n.TASKACQUIRE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available,...i.isProbe?{probe:!0}:{}});}this._checkDrain();}catch(e){e instanceof Error?console.error("[Semaphore] Scheduler error:",e.message,e.stack):console.error("[Semaphore] Scheduler error:",e);}}}_startPurgeInterval(){this.purgeIntervalId!==null&&clearInterval(this.purgeIntervalId),this.purgeIntervalId=setInterval(()=>{this.isShutdown||this._purgeStaleTasks();},this.purgeIntervalMs),this.purgeIntervalId.unref?.();}_purgeStaleTasks(){let e=Date.now(),t=this.queue.size,i=this.enqueueOrder.peekHead();for(;i!==void 0&&e-i.enqueueTime>this.queueMaxAge;){this.circuit.probeTaskId===i.id&&this.circuit.releaseProbeSlot();let r=i.discard(new a(`Task purged after ${this.queueMaxAge}ms`,"PURGED"));this._dequeue(i),r&&(this.totalPurged++,this.metricsCollector?.onPurge(e,this.queue.size),this.emit(n.QUEUEPURGE,{id:i.id,priority:i.priority,enqueueTime:i.enqueueTime,weight:i.weight}),this.debug&&console.warn(`[Semaphore] Purged stale task #${i.id}`)),i=this.enqueueOrder.peekHead();}this.debug&&this.queue.size<t&&console.info(`[Semaphore] Purged ${t-this.queue.size} stale tasks`),this.queue.size<t&&(this._checkDrain(),this.schedule());}tryAcquire(e=1){return this.isShutdown||!Number.isInteger(e)||e<1||e>this.permits.capacity||(this.circuit.checkAndTransition()&&(this.emit(n.CIRCUITPROBING),this.metricsCollector?.markCircuitProbing(),this.debug&&console.info("[Semaphore] Circuit entering probing"),this.schedule()),this.circuit.isOpen)?null:this._tryAcquireFast(e)}acquire(e,t=0,i=1){return this._acquire(e,t,i)}_acquire(e,t=0,i=1){if(this.lastAdmissionWasProbe=false,this.isShutdown)return Promise.reject(new a("Semaphore is shut down","SHUTDOWN"));if(!Number.isInteger(i)||i<1||i>this.permits.capacity)return Promise.reject(new a(`Invalid weight: ${i} (must be integer in 1..${this.permits.capacity})`,"INVALID_WEIGHT"));if(typeof t!="number"||!Number.isFinite(t))return Promise.reject(new a(`Invalid priority: ${t} (must be a finite number)`,"INVALID_PRIORITY"));if(this.circuit.checkAndTransition()&&(this.emit(n.CIRCUITPROBING),this.metricsCollector?.markCircuitProbing(),this.debug&&console.info("[Semaphore] Circuit entering probing"),this.schedule()),this.circuit.isOpen)return Promise.reject(new a(`Circuit breaker open, retry in ${this.circuit.cooldownRemaining}ms`,"CIRCUIT_OPEN"));if(this.circuit.isProbing&&this.circuit.hasProbeInFlight)return Promise.reject(new a("Circuit breaker is probing, probe already in flight","CIRCUIT_PROBING"));if(e?.aborted)return Promise.reject(new a("Semaphore acquire aborted before start","ABORTED"));let r=this.circuit.isProbing,s=this._tryAcquireFast(i);if(s)return this.lastAdmissionWasProbe=r,Promise.resolve(s);let u=this.circuit.isProbing&&!this.circuit.hasProbeInFlight;return !u&&this.rejectOnFull?Promise.reject(new a("Semaphore at capacity (rejectOnFull)","QUEUE_FULL")):!u&&this.queue.size>=this.queueMaxLength?Promise.reject(new a(`Queue full (${this.queueMaxLength})`,"QUEUE_FULL")):(this.lastAdmissionWasProbe=u,new Promise((c,d)=>{let m=++this.taskIdCounter,p=Date.now(),b=u,f=new S({id:m,priority:b?Number.MIN_SAFE_INTEGER:t,enqueueTime:p,isProbe:b,resolve:c,reject:d,abortSignal:e,weight:i});f.arm(()=>this._onTaskAbort(f,d)),b&&this.circuit.claimProbeSlot(m),this.metricsCollector?.sampleQueueDepthAt(p,this.queue.size+1),this._enqueue(f),this.schedule();}))}peekQueue(){let e=[],t=this.enqueueOrder.peekHead();for(;t!==void 0;)e.push({id:t.id,priority:t.priority,enqueueTime:t.enqueueTime,weight:t.weight,isProbe:t.isProbe}),t=t.next??void 0;return e}async use(e,t,i=0,r=1){let s=this._acquire(t,i,r),u=this.failurePredicate!==void 0&&this.lastAdmissionWasProbe,c=await s;try{let d=await e();return c(),d}catch(d){if(this.failurePredicate!==void 0){let m=false;try{m=this.failurePredicate(d)===!0;}catch(p){console.warn("[Semaphore] circuitBreakerFailurePredicate threw (rejection treated as non-matching):",p);}m&&(u&&this.circuit.isProbing?(this.circuit.recordFailure(),this.circuit.handleProbeFailure(),this.emit(n.CIRCUITOPEN,{timeoutRate:1,recentTimeouts:1,total:1,reason:"probe-failed"}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn("[Semaphore] Circuit re-opened: probe operation failed")):this.reportFailure());}throw c(),d}}drain(e){return this.isShutdown?Promise.reject(new a("Cannot drain: semaphore is shut down","SHUTDOWN")):(e!==void 0&&l(e,"drain timeoutMs",1,Number.MAX_SAFE_INTEGER,true,true),this.drainPromise?this.drainPromise:this.queue.size===0&&this.permits.available===this.permits.capacity?Promise.resolve():(this.drainPromise=new Promise((t,i)=>{if(this.drainResolve=t,e!==void 0){let r=setTimeout(()=>{this.drainResolve=null,this.drainPromise=null,i(new a(`drain() timed out after ${e}ms`,"TIMEOUT"));},e),s=this.drainResolve;this.drainResolve=()=>{clearTimeout(r),s();};}}),this.drainPromise))}reset(e={}){if(this.isShutdown)throw new a("Cannot reset a semaphore that has been shut down","SHUTDOWN");for(let t=this.enqueueOrder.peekHead();t!==void 0;t=t.next??void 0)t.discard(new a("Semaphore reset","SHUTDOWN"));this.queue.clear(),this.enqueueOrder.clear(),this._clearTimeout(),this.metricsCollector?.reset(),this.permits.reset(),this.backoff.reset(),this.circuit.reset(),this.releaseGeneration++,this.pendingReleaseCount=0,this.scheduled=false,this.isShutdown=false,this.taskIdCounter=0,this.totalAcquired=0,this.totalReleased=0,this.totalTimeouts=0,this.totalPurged=0,this.totalEvictions=0,e.clearListeners&&this.eventListeners.clear(),this.drainResolve&&(this.drainResolve(),this.drainResolve=null,this.drainPromise=null),this._startPurgeInterval(),this.metricsCollector?.markCapacityChange(this.permits.capacity),this.metricsCollector?.sampleGauges(Date.now(),this.permits.inFlight,this.queue.size),this.debug&&console.info("[Semaphore] Reset to initial state");}shutdown(e="Semaphore shutdown"){if(!this.isShutdown){this.isShutdown=true,this.debug&&console.info(`[Semaphore] Shutdown: ${e}`),this.purgeIntervalId!==null&&(clearInterval(this.purgeIntervalId),this.purgeIntervalId=null),this._clearTimeout();for(let t=this.enqueueOrder.peekHead();t!==void 0;t=t.next??void 0)t.discard(new a(e,"SHUTDOWN"));this.queue.clear(),this.enqueueOrder.clear(),this.releaseGeneration++,this.pendingReleaseCount=0,this.permits.reset(),this.metricsCollector?.destroy(),this.metricsCollector=void 0,this.drainResolve&&(this.drainResolve(),this.drainResolve=null,this.drainPromise=null),this.emit(n.SHUTDOWN,e);}}cancel(){if(this.isShutdown)return;let e=0;for(let t=this.enqueueOrder.peekHead();t!==void 0;t=t.next??void 0)t.isProbe&&this.circuit.releaseProbeSlot(),t.discard(new a("Semaphore acquire cancelled","CANCELLED")),e++;this.queue.clear(),this.enqueueOrder.clear(),this._clearTimeout(),this.metricsCollector?.sampleQueueDepthAt(Date.now(),this.queue.size),this.debug&&console.info(`[Semaphore] Cancelled ${e} queued tasks`),this._checkDrain(),this.schedule();}reportFailure(){if(this.isShutdown)return;this.circuit.recordFailure();let e=this.circuit.evaluateAndTrip();e.tripped&&(this.emit(n.CIRCUITOPEN,{timeoutRate:e.timeoutRate,recentTimeouts:e.failures,total:e.attempts,reason:"reported-failure"}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn(`[Semaphore] Circuit opened via reportFailure(). Rate: ${(e.timeoutRate*100).toFixed(1)}%`),this._evictQueueOnCircuitOpen());}status(){let e=Date.now(),t=this.metricsCollector?.getSnapshot(e)??null,i=this.metricsCollector!==void 0?t?.windows?.[this.metricsCollector.primaryLabel]?.counts:void 0,r=(this.metricsCollector?.primaryWindowMs??6e4)/1e3,s=i?.acquired??0,u=i?.timeouts??0,c=this.enqueueOrder.peekHead(),d=c===void 0?0:Math.max(0,e-c.enqueueTime);return {status:{running:this.permits.capacity-this.permits.available,queued:this.queue.size,available:this.permits.available,inFlight:this.permits.inFlight,pendingReleases:this.pendingReleaseCount,circuitOpen:this.circuit.isOpen,circuitProbing:this.circuit.isProbing,backoffDelay:Math.round(this.backoff.currentDelay),requestsPerSecond:+(s/r).toFixed(2),timeoutRate1m:s+u>0?+(u/(s+u)*100).toFixed(1):0,queueAge:d},lifetime:{totalAcquired:this.totalAcquired,totalReleased:this.totalReleased,totalTimeouts:this.totalTimeouts,totalPurged:this.totalPurged,totalEvictions:this.totalEvictions,circuitBreakerCooldownRemaining:this.circuit.cooldownRemaining},metrics:t}}get availablePermits(){return this.permits.available}isAvailable(){return !this.isShutdown&&!this.circuit.isOpen&&!this.permits.isFull}get capacity(){return this.permits.capacity}get circuitState(){return this.circuit.state}get queueLength(){return this.queue.size}};var F={tripped:false},q=class{constructor(){this.state="closed";this.isOpen=false;this.isProbing=false;this.hasProbeInFlight=false;this.probeTaskId=null;this.cooldownRemaining=0;}checkAndTransition(){return false}trackAttempt(){}recordFailure(){}evaluateAndTrip(){return F}markProbeInFlight(){}claimProbeSlot(){}releaseProbeSlot(){}handleProbeSuccess(){}handleProbeFailure(){}reset(){}};var O={tripped:false},C=class{constructor(){this.state="closed";this.hasProbeInFlight=false;this.probeTaskId=null;}get isOpen(){return this.state==="open"}get isProbing(){return false}get cooldownRemaining(){return 0}open(){this.state="open";}close(){this.state="closed";}checkAndTransition(){return false}trackAttempt(){}recordFailure(){}evaluateAndTrip(){return O}markProbeInFlight(){}claimProbeSlot(){}releaseProbeSlot(){}handleProbeSuccess(){}handleProbeFailure(){}reset(){this.state="closed";}};exports.ManualCircuitBreaker=C;exports.NoopCircuitBreaker=q;exports.QUEUE_ORDERINGS=E;exports.SaturationCircuitBreaker=v;exports.Semaphore=P;exports.SemaphoreError=a;exports.SemaphoreEvents=n;//# sourceMappingURL=index.cjs.map
//# sourceMappingURL=index.cjs.map

@@ -39,3 +39,3 @@ interface SemaphoreConfig {

* breaker failure (as if `reportFailure()` were called), and — unlike
* `reportFailure()` — it also makes half-open probes fault-aware: a probe
* `reportFailure()` — it also makes probes fault-aware: a probe
* dispatched through `use()` whose operation fails a matching error re-opens

@@ -78,3 +78,3 @@ * the circuit instead of closing it on release.

* dispatched first. Overrides `queueOrder`. Probe tasks (circuit-breaker
* half-open) are always sorted ahead of everything else regardless of this
* probing) are always sorted ahead of everything else regardless of this
* comparator, so it never needs to account for them.

@@ -106,3 +106,3 @@ *

}
type CircuitState = 'closed' | 'open' | 'half-open';
type CircuitState = 'closed' | 'open' | 'probing';
interface CircuitBreakerConfig {

@@ -135,8 +135,8 @@ readonly threshold?: number;

* - `checkAndTransition()` is called at the top of every acquire; return true
* exactly once per open → half-open transition (the semaphore emits
* CIRCUITHALFOPEN when it sees true).
* exactly once per open → probing transition (the semaphore emits
* CIRCUITPROBING when it sees true).
* - `evaluateAndTrip()` may only report `tripped: true` for a closed → open
* transition; the semaphore then emits CIRCUITOPEN and evicts the queue.
* - The probe-slot methods manage the single half-open probe; implementations
* that never enter half-open may treat them as no-ops.
* - The probe-slot methods manage the single probe; implementations
* that never enter probing may treat them as no-ops.
* - Methods must not throw.

@@ -147,7 +147,7 @@ */

readonly isOpen: boolean;
readonly isHalfOpen: boolean;
readonly isProbing: boolean;
readonly hasProbeInFlight: boolean;
readonly probeTaskId: number | null;
readonly cooldownRemaining: number;
/** open → half-open when ready; true if the transition just occurred. */
/** open → probing when ready; true if the transition just occurred. */
checkAndTransition(): boolean;

@@ -175,3 +175,3 @@ /** Record one admission attempt. Called once per actual admission — a granted fast-path acquire (including tryAcquire) or a task enqueued — never for rejected admissions (QUEUE_FULL, null tryAcquire). `now` (epoch ms) is passed when the caller has already read the clock for this admission; read Date.now() yourself if absent and needed. */

readonly CIRCUITOPEN: "circuit-open";
readonly CIRCUITHALFOPEN: "circuit-half-open";
readonly CIRCUITPROBING: "circuit-probing";
readonly CIRCUITCLOSE: "circuit-close";

@@ -211,3 +211,3 @@ readonly SHUTDOWN: "shutdown";

}];
'circuit-half-open': [];
'circuit-probing': [];
'circuit-close': [];

@@ -218,3 +218,3 @@ 'shutdown': [reason: string];

type SemaphoreEventListener<E extends SemaphoreEventType> = (...args: SemaphoreEventMap[E]) => void;
type SemaphoreErrorCode = 'CIRCUIT_OPEN' | 'CIRCUIT_HALF_OPEN' | 'INVALID_ARGUMENT' | 'INVALID_WEIGHT' | 'INVALID_PRIORITY' | 'QUEUE_FULL' | 'TIMEOUT' | 'ABORTED' | 'CANCELLED' | 'SHUTDOWN' | 'PURGED';
type SemaphoreErrorCode = 'CIRCUIT_OPEN' | 'CIRCUIT_PROBING' | 'INVALID_ARGUMENT' | 'INVALID_WEIGHT' | 'INVALID_PRIORITY' | 'QUEUE_FULL' | 'TIMEOUT' | 'ABORTED' | 'CANCELLED' | 'SHUTDOWN' | 'PURGED';
interface EventWindowSnapshot {

@@ -254,3 +254,3 @@ acquired: number;

circuitOpen: boolean;
circuitHalfOpen: boolean;
circuitProbing: boolean;
};

@@ -308,2 +308,3 @@ }

private _fireTimeout;
private _evictTask;
private _evictQueueOnCircuitOpen;

@@ -326,3 +327,3 @@ private _timeoutTask;

* Rejects with SemaphoreError on: SHUTDOWN, INVALID_WEIGHT, INVALID_PRIORITY,
* CIRCUIT_OPEN, CIRCUIT_HALF_OPEN, ABORTED, QUEUE_FULL, TIMEOUT, PURGED.
* CIRCUIT_OPEN, CIRCUIT_PROBING, ABORTED, QUEUE_FULL, TIMEOUT, PURGED.
* @param priority Dispatch priority (any finite number; lower dispatches first). Defaults to 0.

@@ -347,3 +348,3 @@ * @param weight Permits to consume (integer in 1..count). Defaults to 1.

* that match the predicate feed the circuit breaker: one failure per match
* (as via reportFailure()), and a matching failure on a half-open probe
* (as via reportFailure()), and a matching failure on a probe
* re-opens the circuit instead of closing it on release.

@@ -388,3 +389,3 @@ * @param weight Permits to consume (integer in 1..count). Defaults to 1.

* Only influences trip decisions while the circuit is closed — probe
* outcomes in half-open remain acquisition-based. No-op after shutdown.
* outcomes while probing remain acquisition-based. No-op after shutdown.
*/

@@ -402,3 +403,3 @@ reportFailure(): void;

circuitOpen: boolean;
circuitHalfOpen: boolean;
circuitProbing: boolean;
backoffDelay: number;

@@ -456,3 +457,3 @@ requestsPerSecond: number;

get isOpen(): boolean;
get isHalfOpen(): boolean;
get isProbing(): boolean;
get hasProbeInFlight(): boolean;

@@ -462,3 +463,3 @@ get probeTaskId(): number | null;

/**
* Records an attempt. Skipped during open/half-open so only closed-circuit
* Records an attempt. Skipped during open/probing so only closed-circuit
* traffic counts toward the failure rate.

@@ -474,4 +475,4 @@ *

/**
* open → half-open if the cooldown has elapsed.
* Returns true if the transition just occurred (caller emits CIRCUITHALFOPEN).
* open → probing if the cooldown has elapsed.
* Returns true if the transition just occurred (caller emits CIRCUITPROBING).
*/

@@ -491,5 +492,5 @@ checkAndTransition(): boolean;

releaseProbeSlot(): void;
/** half-open → closed. Resets the eventWindow so stale failures don't re-trip. */
/** probing → closed. Resets the eventWindow so stale failures don't re-trip. */
handleProbeSuccess(): void;
/** half-open → open. Re-arms the full cooldown. */
/** probing → open. Re-arms the full cooldown. */
handleProbeFailure(): void;

@@ -503,3 +504,3 @@ /** Reset circuit breaker. */

readonly isOpen = false;
readonly isHalfOpen = false;
readonly isProbing = false;
readonly hasProbeInFlight = false;

@@ -523,3 +524,3 @@ readonly probeTaskId: null;

get isOpen(): boolean;
get isHalfOpen(): boolean;
get isProbing(): boolean;
readonly hasProbeInFlight = false;

@@ -526,0 +527,0 @@ readonly probeTaskId: null;

@@ -39,3 +39,3 @@ interface SemaphoreConfig {

* breaker failure (as if `reportFailure()` were called), and — unlike
* `reportFailure()` — it also makes half-open probes fault-aware: a probe
* `reportFailure()` — it also makes probes fault-aware: a probe
* dispatched through `use()` whose operation fails a matching error re-opens

@@ -78,3 +78,3 @@ * the circuit instead of closing it on release.

* dispatched first. Overrides `queueOrder`. Probe tasks (circuit-breaker
* half-open) are always sorted ahead of everything else regardless of this
* probing) are always sorted ahead of everything else regardless of this
* comparator, so it never needs to account for them.

@@ -106,3 +106,3 @@ *

}
type CircuitState = 'closed' | 'open' | 'half-open';
type CircuitState = 'closed' | 'open' | 'probing';
interface CircuitBreakerConfig {

@@ -135,8 +135,8 @@ readonly threshold?: number;

* - `checkAndTransition()` is called at the top of every acquire; return true
* exactly once per open → half-open transition (the semaphore emits
* CIRCUITHALFOPEN when it sees true).
* exactly once per open → probing transition (the semaphore emits
* CIRCUITPROBING when it sees true).
* - `evaluateAndTrip()` may only report `tripped: true` for a closed → open
* transition; the semaphore then emits CIRCUITOPEN and evicts the queue.
* - The probe-slot methods manage the single half-open probe; implementations
* that never enter half-open may treat them as no-ops.
* - The probe-slot methods manage the single probe; implementations
* that never enter probing may treat them as no-ops.
* - Methods must not throw.

@@ -147,7 +147,7 @@ */

readonly isOpen: boolean;
readonly isHalfOpen: boolean;
readonly isProbing: boolean;
readonly hasProbeInFlight: boolean;
readonly probeTaskId: number | null;
readonly cooldownRemaining: number;
/** open → half-open when ready; true if the transition just occurred. */
/** open → probing when ready; true if the transition just occurred. */
checkAndTransition(): boolean;

@@ -175,3 +175,3 @@ /** Record one admission attempt. Called once per actual admission — a granted fast-path acquire (including tryAcquire) or a task enqueued — never for rejected admissions (QUEUE_FULL, null tryAcquire). `now` (epoch ms) is passed when the caller has already read the clock for this admission; read Date.now() yourself if absent and needed. */

readonly CIRCUITOPEN: "circuit-open";
readonly CIRCUITHALFOPEN: "circuit-half-open";
readonly CIRCUITPROBING: "circuit-probing";
readonly CIRCUITCLOSE: "circuit-close";

@@ -211,3 +211,3 @@ readonly SHUTDOWN: "shutdown";

}];
'circuit-half-open': [];
'circuit-probing': [];
'circuit-close': [];

@@ -218,3 +218,3 @@ 'shutdown': [reason: string];

type SemaphoreEventListener<E extends SemaphoreEventType> = (...args: SemaphoreEventMap[E]) => void;
type SemaphoreErrorCode = 'CIRCUIT_OPEN' | 'CIRCUIT_HALF_OPEN' | 'INVALID_ARGUMENT' | 'INVALID_WEIGHT' | 'INVALID_PRIORITY' | 'QUEUE_FULL' | 'TIMEOUT' | 'ABORTED' | 'CANCELLED' | 'SHUTDOWN' | 'PURGED';
type SemaphoreErrorCode = 'CIRCUIT_OPEN' | 'CIRCUIT_PROBING' | 'INVALID_ARGUMENT' | 'INVALID_WEIGHT' | 'INVALID_PRIORITY' | 'QUEUE_FULL' | 'TIMEOUT' | 'ABORTED' | 'CANCELLED' | 'SHUTDOWN' | 'PURGED';
interface EventWindowSnapshot {

@@ -254,3 +254,3 @@ acquired: number;

circuitOpen: boolean;
circuitHalfOpen: boolean;
circuitProbing: boolean;
};

@@ -308,2 +308,3 @@ }

private _fireTimeout;
private _evictTask;
private _evictQueueOnCircuitOpen;

@@ -326,3 +327,3 @@ private _timeoutTask;

* Rejects with SemaphoreError on: SHUTDOWN, INVALID_WEIGHT, INVALID_PRIORITY,
* CIRCUIT_OPEN, CIRCUIT_HALF_OPEN, ABORTED, QUEUE_FULL, TIMEOUT, PURGED.
* CIRCUIT_OPEN, CIRCUIT_PROBING, ABORTED, QUEUE_FULL, TIMEOUT, PURGED.
* @param priority Dispatch priority (any finite number; lower dispatches first). Defaults to 0.

@@ -347,3 +348,3 @@ * @param weight Permits to consume (integer in 1..count). Defaults to 1.

* that match the predicate feed the circuit breaker: one failure per match
* (as via reportFailure()), and a matching failure on a half-open probe
* (as via reportFailure()), and a matching failure on a probe
* re-opens the circuit instead of closing it on release.

@@ -388,3 +389,3 @@ * @param weight Permits to consume (integer in 1..count). Defaults to 1.

* Only influences trip decisions while the circuit is closed — probe
* outcomes in half-open remain acquisition-based. No-op after shutdown.
* outcomes while probing remain acquisition-based. No-op after shutdown.
*/

@@ -402,3 +403,3 @@ reportFailure(): void;

circuitOpen: boolean;
circuitHalfOpen: boolean;
circuitProbing: boolean;
backoffDelay: number;

@@ -456,3 +457,3 @@ requestsPerSecond: number;

get isOpen(): boolean;
get isHalfOpen(): boolean;
get isProbing(): boolean;
get hasProbeInFlight(): boolean;

@@ -462,3 +463,3 @@ get probeTaskId(): number | null;

/**
* Records an attempt. Skipped during open/half-open so only closed-circuit
* Records an attempt. Skipped during open/probing so only closed-circuit
* traffic counts toward the failure rate.

@@ -474,4 +475,4 @@ *

/**
* open → half-open if the cooldown has elapsed.
* Returns true if the transition just occurred (caller emits CIRCUITHALFOPEN).
* open → probing if the cooldown has elapsed.
* Returns true if the transition just occurred (caller emits CIRCUITPROBING).
*/

@@ -491,5 +492,5 @@ checkAndTransition(): boolean;

releaseProbeSlot(): void;
/** half-open → closed. Resets the eventWindow so stale failures don't re-trip. */
/** probing → closed. Resets the eventWindow so stale failures don't re-trip. */
handleProbeSuccess(): void;
/** half-open → open. Re-arms the full cooldown. */
/** probing → open. Re-arms the full cooldown. */
handleProbeFailure(): void;

@@ -503,3 +504,3 @@ /** Reset circuit breaker. */

readonly isOpen = false;
readonly isHalfOpen = false;
readonly isProbing = false;
readonly hasProbeInFlight = false;

@@ -523,3 +524,3 @@ readonly probeTaskId: null;

get isOpen(): boolean;
get isHalfOpen(): boolean;
get isProbing(): boolean;
readonly hasProbeInFlight = false;

@@ -526,0 +527,0 @@ readonly probeTaskId: null;

@@ -1,2 +0,2 @@

var a=class extends Error{constructor(e,t){super(e),this.name="SemaphoreError",this.code=t;}};var l=(o,e,t,i,r=true,s=true)=>{if(typeof o!="number"||Number.isNaN(o))throw new a(`${e} must be a valid number`,"INVALID_ARGUMENT");if(r&&!Number.isInteger(o))throw new a(`${e} must be an integer`,"INVALID_ARGUMENT");if(s?o<t:o<=t)throw new a(`${e} must be ${s?">=":">"} ${t}`,"INVALID_ARGUMENT");if(s?o>i:o>=i)throw new a(`${e} must be ${s?"<=":"<"} ${i}`,"INVALID_ARGUMENT");return o};var T=class{constructor(e){this._inFlight=0;this.capacity=e,this._available=e;}get available(){return this._available}get inFlight(){return this._inFlight}get isFull(){return this._available===0}hasCapacityFor(e){return this._available>=e}acquire(e=1){this._available-=e,this._inFlight+=e;}release(e=1){this._available=Math.min(this._available+e,this.capacity),this._inFlight=this._inFlight<e?0:this._inFlight-e;}assertInvariant(e){if(!e)return;let t=this._inFlight+this._available;t!==this.capacity&&console.error(`[Semaphore] Invariant violation: inFlight(${this._inFlight}) + available(${this._available}) = ${t}, expected capacity(${this.capacity})`);}reset(){this._available=this.capacity,this._inFlight=0;}};var q=class{constructor(e,t){this.cachedIndex=-1;this.cachedUntil=0;this.size=e,this.stepMs=t,this.buckets=new Int32Array(e*2),this.timestamps=new Float64Array(e);}idx(e){if(e<this.cachedUntil&&e>=this.cachedUntil-this.stepMs)return this.cachedIndex;let t=Math.floor(e/this.stepMs)*this.stepMs,i=Math.floor(t/this.stepMs)%this.size;return this.timestamps[i]!==t&&(this.timestamps[i]=t,this.buckets[i*2]=0,this.buckets[i*2+1]=0),this.cachedIndex=i,this.cachedUntil=t+this.stepMs,i}addAcquired(e=Date.now()){this.buckets[this.idx(e)*2]++;}addTimeout(){this.buckets[this.idx(Date.now())*2+1]++;}snapshot(){let e=Date.now(),t=Math.floor(e/this.stepMs)*this.stepMs-(this.size-1)*this.stepMs,i=0,r=0;for(let s=0;s<this.size;s++)this.timestamps[s]>=t&&(i+=this.buckets[s*2],r+=this.buckets[s*2+1]);return {acquired:i,timeouts:r}}reset(){this.cachedIndex=-1,this.cachedUntil=0,this.buckets.fill(0),this.timestamps.fill(0);}},v=class{constructor(e={}){this.state="closed";this.openUntil=0;this.probeInFlight=false;this._probeTaskId=null;if(this.threshold=l(e.threshold??.5,"SaturationCircuitBreaker threshold",0,1,false,false),this.window=l(e.window??1e4,"SaturationCircuitBreaker window",1e3,Number.MAX_SAFE_INTEGER,true,true),this.windowBucketWidth=l(e.windowBucketWidth??1e3,"SaturationCircuitBreaker windowBucketWidth",1,Number.MAX_SAFE_INTEGER,true,true),this.windowBucketCount=Math.ceil(this.window/this.windowBucketWidth),this.cooldown=l(e.cooldown??5e3,"SaturationCircuitBreaker cooldown",1e3,Number.MAX_SAFE_INTEGER,true,true),this.minThroughput=l(e.minThroughput??10,"SaturationCircuitBreaker minThroughput",1,Number.MAX_SAFE_INTEGER,true,true),this.minFailures=l(e.minFailures??5,"SaturationCircuitBreaker minFailures",1,Number.MAX_SAFE_INTEGER,true,true),this.window<this.windowBucketWidth)throw new a("SaturationCircuitBreaker window must be >= windowBucketWidth","INVALID_ARGUMENT");if(this.minThroughput<this.minFailures)throw new a("SaturationCircuitBreaker minThroughput must be >= minFailures","INVALID_ARGUMENT");if(this.windowBucketCount<2)throw new a("SaturationCircuitBreaker window must span at least 2 windowBucketWidth buckets","INVALID_ARGUMENT");this.eventWindow=new q(this.windowBucketCount,this.windowBucketWidth);}get isClosed(){return this.state==="closed"}get isOpen(){return this.state==="open"}get isHalfOpen(){return this.state==="half-open"}get hasProbeInFlight(){return this.probeInFlight}get probeTaskId(){return this._probeTaskId}get cooldownRemaining(){return this.isOpen?Math.max(0,this.openUntil-Date.now()):0}trackAttempt(e){this.state==="closed"&&this.eventWindow.addAcquired(e);}recordFailure(){this.eventWindow.addTimeout();}checkAndTransition(){return this.state!=="open"||Date.now()<this.openUntil?false:(this.state="half-open",this.probeInFlight=false,this._probeTaskId=null,true)}evaluateAndTrip(){if(this.state!=="closed")return {tripped:false};let{timeouts:e,acquired:t}=this.eventWindow.snapshot();if(t<this.minThroughput||e<this.minFailures)return {tripped:false};let i=e/t;return i<this.threshold?{tripped:false}:(this.state="open",this.openUntil=Date.now()+this.cooldown,{tripped:true,timeoutRate:i,failures:e,attempts:t})}markProbeInFlight(){this.probeInFlight=true;}claimProbeSlot(e){this.probeInFlight=true,this._probeTaskId=e;}releaseProbeSlot(){this.probeInFlight=false,this._probeTaskId=null;}handleProbeSuccess(){this.state="closed",this.probeInFlight=false,this._probeTaskId=null,this.eventWindow.reset();}handleProbeFailure(){this.state="open",this.probeInFlight=false,this._probeTaskId=null,this.openUntil=Date.now()+this.cooldown;}reset(){this.state="closed",this.openUntil=0,this.probeInFlight=false,this._probeTaskId=null,this.eventWindow.reset();}};var y=class{constructor(e={}){this.delay=0;this.lastTimestamp=0;if(this.initialTimeout=l(e.initialTimeout??50,"BackoffTracker initialTimeout",0,Number.MAX_SAFE_INTEGER,true,true),this.maxTimeout=l(e.maxTimeout??2e3,"BackoffTracker maxTimeout",0,Number.MAX_SAFE_INTEGER,true,true),this.decayFactor=l(e.decayFactor??.5,"BackoffTracker decayFactor",0,1,false,false),this.maxTimeout<this.initialTimeout)throw new a("BackoffTracker maxTimeout must be >= initialTimeout","INVALID_ARGUMENT")}get currentDelay(){if(this.delay===0)return 0;let e=(Date.now()-this.lastTimestamp)/1e3;if(e<=0)return this.delay;let t=this.delay*Math.pow(this.decayFactor,e);return t<1?0:t}onTimeout(){let e=this.currentDelay,t=e>0?e*2:this.initialTimeout;this.delay=Math.min(t,this.maxTimeout),this.lastTimestamp=Date.now();}reset(){this.delay=0,this.lastTimestamp=0;}};var S=class{constructor(e){this.prev=null;this.next=null;this.heapIndex=-1;this.completed=false;this.id=e.id,this.priority=e.priority,this.enqueueTime=e.enqueueTime,this.isProbe=e.isProbe,this.weight=e.weight??1,this._resolve=e.resolve,this._reject=e.reject,this.abortSignal=e.abortSignal;}arm(e){this.abortSignal&&(this.abortListener=()=>{this.claim()&&e();},this.abortSignal.addEventListener("abort",this.abortListener));}dispatch(e){return this.claim()?(this._resolve(e()),true):false}discard(e){return this.claim()?(this._reject(e),true):false}claim(){return this.completed?false:(this.completed=true,this.abortSignal&&this.abortListener&&this.abortSignal.removeEventListener("abort",this.abortListener),true)}reject(e){this._reject(e);}};var k=class{constructor(e){this.heap=[];this.compare=e;}get size(){return this.heap.length}isEmpty(){return this.heap.length===0}peek(){return this.heap[0]}toArray(){return [...this.heap]}has(e){return e.heapIndex>=0&&this.heap[e.heapIndex]===e}clear(){for(let e of this.heap)e.heapIndex=-1;this.heap=[];}insert(e){if(e.heapIndex>=0)throw new a("Item is already in a heap","INVALID_ARGUMENT");let t=this.heap.length;this.heap.push(e),e.heapIndex=t,this._bubbleUp(t);}pop(){if(this.heap.length===0)return;let e=this.heap[0];e.heapIndex=-1;let t=this.heap.pop();return this.heap.length>0&&t!==e&&(this.heap[0]=t,t.heapIndex=0,this._bubbleDown(0)),e}delete(e){let t=e.heapIndex;if(t<0||this.heap[t]!==e)return;e.heapIndex=-1;let i=this.heap.pop();if(t===this.heap.length||i===e)return e;this.heap[t]=i,i.heapIndex=t;let r=this.heap[t-1>>1];return t>0&&this.compare(i,r)<0?this._bubbleUp(t):this._bubbleDown(t),e}_swap(e,t){let i=this.heap[e],r=this.heap[t];this.heap[e]=r,this.heap[t]=i,i.heapIndex=t,r.heapIndex=e;}_bubbleUp(e){for(;e>0;){let t=e-1>>1;if(this.compare(this.heap[e],this.heap[t])<0)this._swap(e,t),e=t;else break}}_bubbleDown(e){let t=this.heap.length;for(;;){let i=e,r=2*e+1,s=2*e+2;if(r<t&&this.compare(this.heap[r],this.heap[i])<0&&(i=r),s<t&&this.compare(this.heap[s],this.heap[i])<0&&(i=s),i===e)break;this._swap(e,i),e=i;}}};var I=class{constructor(){this.head=null;this.tail=null;this._size=0;}get size(){return this._size}isEmpty(){return this._size===0}peekHead(){return this.head===null?void 0:this.head}pushTail(e){e.prev=this.tail,e.next=null,this.tail!==null?this.tail.next=e:this.head=e,this.tail=e,this._size++;}remove(e){e.prev!==null?e.prev.next=e.next:this.head=e.next,e.next!==null?e.next.prev=e.prev:this.tail=e.prev,e.prev=e.next=null,this._size--;}clear(){this.head=this.tail=null,this._size=0;}};var _=class{constructor(e,t){this.cachedIndex=-1;this.cachedUntil=0;this.size=l(e,"CombinedWindow size",1,Number.MAX_SAFE_INTEGER,true,true),this.stepMs=l(t,"CombinedWindow stepMs",1,Number.MAX_SAFE_INTEGER,true,true),this.timestamps=new Float64Array(e),this.acquired=new Int32Array(e),this.released=new Int32Array(e),this.timeouts=new Int32Array(e),this.ifSum=new Float64Array(e),this.ifCount=new Int32Array(e),this.ifMax=new Float64Array(e),this.qSum=new Float64Array(e),this.qCount=new Int32Array(e),this.qMax=new Float64Array(e),this.latSum=new Float64Array(e),this.latCount=new Int32Array(e);}bucket(e){if(e<this.cachedUntil&&e>=this.cachedUntil-this.stepMs)return this.cachedIndex;let t=Math.floor(e/this.stepMs),i=t*this.stepMs,r=t%this.size;return this.timestamps[r]!==i&&(this.timestamps[r]=i,this.acquired[r]=0,this.released[r]=0,this.timeouts[r]=0,this.ifSum[r]=0,this.ifCount[r]=0,this.ifMax[r]=0,this.qSum[r]=0,this.qCount[r]=0,this.qMax[r]=0,this.latSum[r]=0,this.latCount[r]=0),this.cachedIndex=r,this.cachedUntil=i+this.stepMs,r}addInflight(e,t){this.ifSum[e]+=t,this.ifCount[e]++,t>this.ifMax[e]&&(this.ifMax[e]=t);}addQueue(e,t){this.qSum[e]+=t,this.qCount[e]++,t>this.qMax[e]&&(this.qMax[e]=t);}recordAcquire(e,t,i){let r=this.bucket(e);this.acquired[r]++,this.addInflight(r,t),this.addQueue(r,i);}recordAcquireQueued(e,t,i,r){let s=this.bucket(e);this.acquired[s]++,this.latSum[s]+=t,this.latCount[s]++,this.addInflight(s,i),this.addQueue(s,r);}recordRelease(e,t,i){let r=this.bucket(e);this.released[r]++,this.addInflight(r,t),this.addQueue(r,i);}recordTimeoutQueue(e,t){let i=this.bucket(e);this.timeouts[i]++,this.addQueue(i,t);}sampleBoth(e,t,i){let r=this.bucket(e);this.addInflight(r,t),this.addQueue(r,i);}addAcquired(e){this.acquired[this.bucket(e)]++;}addReleased(e){this.released[this.bucket(e)]++;}addTimeout(e){this.timeouts[this.bucket(e)]++;}addLatency(e,t){let i=this.bucket(e);this.latSum[i]+=t,this.latCount[i]++;}sampleInflight(e,t){this.addInflight(this.bucket(e),t);}sampleQueue(e,t){this.addQueue(this.bucket(e),t);}snapshot(e){let t=Math.floor(e/this.stepMs)*this.stepMs-(this.size-1)*this.stepMs,i=0,r=0,s=0,u=0,c=0,d=0,p=0,m=0,b=0,f=0,w=0;for(let h=0;h<this.size;h++)this.timestamps[h]<t||(i+=this.acquired[h],r+=this.released[h],s+=this.timeouts[h],u+=this.ifSum[h],c+=this.ifCount[h],this.ifMax[h]>d&&(d=this.ifMax[h]),p+=this.qSum[h],m+=this.qCount[h],this.qMax[h]>b&&(b=this.qMax[h]),f+=this.latSum[h],w+=this.latCount[h]);return {counts:{acquired:i,released:r,timeouts:s},inflight:{avg:c===0?0:u/c,max:d,samples:c},queue:{avg:m===0?0:p/m,max:b,samples:m},latency:{avg:w===0?0:f/w,count:w,total:f}}}reset(){this.cachedIndex=-1,this.cachedUntil=0,this.timestamps.fill(0),this.acquired.fill(0),this.released.fill(0),this.timeouts.fill(0),this.ifSum.fill(0),this.ifCount.fill(0),this.ifMax.fill(0),this.qSum.fill(0),this.qCount.fill(0),this.qMax.fill(0),this.latSum.fill(0),this.latCount.fill(0);}},F=[{size:60,stepMs:1e3},{size:60,stepMs:5e3},{size:60,stepMs:15e3},{size:60,stepMs:6e4},{size:60,stepMs:144e4}],g=class{constructor(e=F){this._totalAcquiredFast=0;this._totalAcquiredQueued=0;this._totalReleased=0;this._totalTimeouts=0;this._totalPurged=0;this._totalAborts=0;this._capacity=0;this._circuitOpen=false;this._circuitHalfOpen=false;if(e.length===0)throw new a("SemaphoreMetrics requires at least one WindowOptions","INVALID_ARGUMENT");this.windows=e.map(({size:r,stepMs:s})=>new _(r,s)),this.windowLabels=e.map(({size:r,stepMs:s})=>{let u=r*s;return u>=36e5&&u%36e5===0?`${u/36e5}h`:u>=6e4&&u%6e4===0?`${u/6e4}m`:u>=1e3&&u%1e3===0?`${u/1e3}s`:`${u}ms`});let t=new Set;for(let r of this.windowLabels){if(t.has(r))throw new a(`SemaphoreMetrics windows produce duplicate label "${r}" (two windows cover the same horizon)`,"INVALID_ARGUMENT");t.add(r);}let i=0;for(let r=1;r<e.length;r++)e[r].size*e[r].stepMs<e[i].size*e[i].stepMs&&(i=r);this.primaryLabel=this.windowLabels[i],this.primaryWindowMs=e[i].size*e[i].stepMs;}onAcquireFast(e,t,i){this._totalAcquiredFast++;for(let r of this.windows)r.recordAcquire(e,t,i);}onAcquireQueued(e,t,i,r){this._totalAcquiredQueued++;for(let s of this.windows)s.recordAcquireQueued(e,t,i,r);}onRelease(e,t,i){this._totalReleased++;for(let r of this.windows)r.recordRelease(e,t,i);}onTimeout(e,t){this._totalTimeouts++;for(let i of this.windows)i.recordTimeoutQueue(e,t);}onAbort(e,t){this._totalAborts++;for(let i of this.windows)i.sampleQueue(e,t);}onPurge(e,t){this._totalPurged++;for(let i of this.windows)i.sampleQueue(e,t);}sampleGauges(e,t,i){for(let r of this.windows)r.sampleBoth(e,t,i);}sampleQueueDepthAt(e,t){for(let i of this.windows)i.sampleQueue(e,t);}markAcquireFast(){this._totalAcquiredFast++;let e=Date.now();for(let t of this.windows)t.addAcquired(e);}markAcquireQueued(e){this._totalAcquiredQueued++;let t=Date.now();for(let i of this.windows)i.addAcquired(t),i.addLatency(t,e);}markReleased(){this._totalReleased++;let e=Date.now();for(let t of this.windows)t.addReleased(e);}markTimeout(){this._totalTimeouts++;let e=Date.now();for(let t of this.windows)t.addTimeout(e);}markAbort(){this._totalAborts++;}sampleInFlight(e){let t=Date.now();for(let i of this.windows)i.sampleInflight(t,e);}sampleQueueDepth(e){let t=Date.now();for(let i of this.windows)i.sampleQueue(t,e);}markCapacityChange(e){this._capacity=e;}markCircuitOpen(){this._circuitOpen=true,this._circuitHalfOpen=false;}markCircuitHalfOpen(){this._circuitOpen=false,this._circuitHalfOpen=true;}markCircuitClose(){this._circuitOpen=false,this._circuitHalfOpen=false;}getSnapshot(){let e=Date.now(),t={},i=0,r=0;for(let s=0;s<this.windowLabels.length;s++){let u=this.windows[s].snapshot(e);t[this.windowLabels[s]]=u,s===0&&(i=u.inflight.avg,r=u.queue.avg);}return {windows:t,meta:{inFlightLastMinute:Math.round(i),queueDepthLastMinute:Math.round(r),totalAcquiredFast:this._totalAcquiredFast,totalAcquiredQueued:this._totalAcquiredQueued,totalReleased:this._totalReleased,totalTimeouts:this._totalTimeouts,totalPurged:this._totalPurged,totalAborts:this._totalAborts,capacity:this._capacity,circuitOpen:this._circuitOpen,circuitHalfOpen:this._circuitHalfOpen}}}reset(){for(let e of this.windows)e.reset();this._totalAcquiredFast=0,this._totalAcquiredQueued=0,this._totalReleased=0,this._totalTimeouts=0,this._totalPurged=0,this._totalAborts=0,this._capacity=0,this._circuitOpen=false,this._circuitHalfOpen=false;}destroy(){this.reset();}};var E={fifo:(o,e)=>o.id-e.id,lifo:(o,e)=>e.id-o.id,fifoWithPriority:(o,e)=>o.priority-e.priority||o.id-e.id,lifoWithPriority:(o,e)=>o.priority-e.priority||e.id-o.id};function P(o){if(o.comparator!==void 0){if(typeof o.comparator!="function")throw new a("Semaphore comparator must be a function","INVALID_ARGUMENT");return o.comparator}let e=o.queueOrder??"fifoWithPriority",t=E[e];if(t===void 0)throw new a(`Semaphore queueOrder must be one of: ${Object.keys(E).join(", ")} (got ${JSON.stringify(e)})`,"INVALID_ARGUMENT");return t}function R(o){let e=P(o);return (t,i)=>{if(t.isProbe!==i.isProbe)return t.isProbe?-1:1;let r=e(t,i);return typeof r!="number"||Number.isNaN(r)?t.id-i.id:r}}var n={TASKACQUIRE:"task-acquire",TASKRELEASE:"task-release",TASKTIMEOUT:"task-timeout",TASKABORT:"task-abort",QUEUEPURGE:"queue-purge",QUEUEEVICT:"queue-evict",CIRCUITOPEN:"circuit-open",CIRCUITHALFOPEN:"circuit-half-open",CIRCUITCLOSE:"circuit-close",SHUTDOWN:"shutdown"};var M=class{constructor(e,t={}){this.pendingReleaseCount=0;this.releaseGeneration=0;this.scheduled=false;this.isShutdown=false;this.taskIdCounter=0;this.totalAcquired=0;this.totalReleased=0;this.totalTimeouts=0;this.totalPurged=0;this.totalEvictions=0;this.eventListeners=new Map;this.drainPromise=null;this.drainResolve=null;this.purgeIntervalId=null;this.timeoutTimerId=null;this._runSchedulerBound=()=>{this._runScheduler();};this.lastAdmissionWasProbe=false;if(l(e,"Semaphore count",0,Number.MAX_SAFE_INTEGER,true,false),this.purgeIntervalMs=l(t.purgeIntervalMs??3e3,"Semaphore purgeIntervalMs",500,Number.MAX_SAFE_INTEGER,true,true),this.queueMaxTimeout=l(t.queueMaxTimeout??1e4,"Semaphore queueMaxTimeout",1,Number.MAX_SAFE_INTEGER,true,true),this.queueMaxLength=l(t.queueMaxLength??1024,"Semaphore queueMaxLength",1,Number.MAX_SAFE_INTEGER,true,true),this.queueMaxAge=l(t.queueMaxAge??3e4,"Semaphore queueMaxAge",1,Number.MAX_SAFE_INTEGER,true,true),this.rejectOnFull=t.rejectOnFull??false,this.metricsEnabled=t.metricsEnabled??true,this.debug=t.debug??false,t.circuitBreakerFailurePredicate!==void 0&&typeof t.circuitBreakerFailurePredicate!="function")throw new a("Semaphore circuitBreakerFailurePredicate must be a function","INVALID_ARGUMENT");this.failurePredicate=t.circuitBreakerFailurePredicate;let i=R({queueOrder:t.queueOrder,comparator:t.comparator});this.queue=new k(i),this.enqueueOrder=new I,this.permits=new T(e),this.circuit=t.circuitBreaker??new v({threshold:t.circuitBreakerThreshold,window:t.circuitBreakerWindow,windowBucketWidth:t.circuitBreakerWindowBucketWidth,cooldown:t.circuitBreakerCooldown,minThroughput:t.circuitBreakerMinThroughput,minFailures:t.circuitBreakerMinFailures}),this.backoff=new y({initialTimeout:t.backoffInitialTimeout,maxTimeout:t.backoffMaxTimeout,decayFactor:t.backoffDecayFactor}),this.metricsCollector=this.metricsEnabled?new g(t.metricsWindows):void 0,this.metricsCollector?.markCapacityChange(this.permits.capacity),this.metricsCollector?.sampleGauges(Date.now(),this.permits.inFlight,this.queue.size),this._startPurgeInterval();}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(t);}off(e,t){this.eventListeners.get(e)?.delete(t),this.eventListeners.get(e)?.size===0&&this.eventListeners.delete(e);}removeAllListeners(e){e?this.eventListeners.delete(e):this.eventListeners.clear();}hasListeners(e){let t=this.eventListeners.get(e);return t!==void 0&&t.size>0}emit(e,...t){let i=this.eventListeners.get(e);if(!(!i||i.size===0)){if(i.size===1){let r=i.values().next().value;try{r(...t);}catch(s){console.warn(`[Semaphore] Error in listener for "${e}":`,s);}return}for(let r of Array.from(i))try{r(...t);}catch(s){console.warn(`[Semaphore] Error in listener for "${e}":`,s);}}}_tryAcquireFast(e){if(this.circuit.isHalfOpen){if(this.circuit.hasProbeInFlight||!this.permits.hasCapacityFor(e))return null;if(this.circuit.markProbeInFlight(),this.permits.acquire(e),this.totalAcquired++,this.metricsCollector!==void 0){let t=Date.now();this.circuit.trackAttempt(t),this.metricsCollector.onAcquireFast(t,this.permits.inFlight,this.queue.size);}else this.circuit.trackAttempt();return this.hasListeners(n.TASKACQUIRE)&&this.emit(n.TASKACQUIRE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available,probe:true}),this._createRelease(true,e)}if(this.queue.size>0||!this.permits.hasCapacityFor(e))return null;if(this.permits.acquire(e),this.totalAcquired++,this.metricsCollector!==void 0){let t=Date.now();this.circuit.trackAttempt(t),this.metricsCollector.onAcquireFast(t,this.permits.inFlight,this.queue.size);}else this.circuit.trackAttempt();return this.hasListeners(n.TASKACQUIRE)&&this.emit(n.TASKACQUIRE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available}),this._createRelease(false,e)}_createRelease(e=false,t=1){this.pendingReleaseCount++;let i=this.releaseGeneration,r=false;return ()=>{if(r||i!==this.releaseGeneration){this.debug&&console.warn("[Semaphore] release() called after already released (no-op)");return}r=true,this.pendingReleaseCount--,this.permits.release(t),this.totalReleased++,this.metricsCollector?.onRelease(Date.now(),this.permits.inFlight,this.queue.size),e&&this.circuit.isHalfOpen&&(this.circuit.handleProbeSuccess(),this.emit(n.CIRCUITCLOSE),this.metricsCollector?.markCircuitClose(),this.debug&&console.info("[Semaphore] Circuit closed after successful probe")),this.permits.assertInvariant(this.debug),this.hasListeners(n.TASKRELEASE)&&this.emit(n.TASKRELEASE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available}),this._checkDrain(),this.schedule();}}_checkDrain(){this.drainResolve&&this.queue.size===0&&this.permits.available===this.permits.capacity&&(this.drainResolve(),this.drainResolve=null,this.drainPromise=null);}_enqueue(e){this.circuit.trackAttempt(),this.queue.insert(e),this.enqueueOrder.pushTail(e),this._armTimeout();}_dequeue(e){this.queue.delete(e)!==void 0&&(this.enqueueOrder.remove(e),this.enqueueOrder.size===0&&this._clearTimeout());}_armTimeout(){if(this.timeoutTimerId!==null)return;let e=this.enqueueOrder.peekHead();if(e===void 0)return;let t=Math.max(0,e.enqueueTime+this.queueMaxTimeout-Date.now());this.timeoutTimerId=setTimeout(()=>this._fireTimeout(),t);}_clearTimeout(){this.timeoutTimerId!==null&&(clearTimeout(this.timeoutTimerId),this.timeoutTimerId=null);}_fireTimeout(){if(this.timeoutTimerId=null,this.isShutdown)return;let e=Date.now(),t=0,i=this.enqueueOrder.peekHead();for(;i!==void 0&&i.enqueueTime+this.queueMaxTimeout<=e;){let r=i.claim();this._dequeue(i),r&&(this._timeoutTask(i),t++),i=this.enqueueOrder.peekHead();}if(i!==void 0){let r=Math.max(0,i.enqueueTime+this.queueMaxTimeout-e);this.timeoutTimerId=setTimeout(()=>this._fireTimeout(),r);}t>0&&(this._checkDrain(),this.schedule());}_evictQueueOnCircuitOpen(){if(this.queue.size===0)return;let e=0;for(let t of this.queue.toArray()){if(t.isProbe)continue;let i=t.discard(new a("Circuit breaker opened while task was queued","CIRCUIT_OPEN"));this._dequeue(t),i&&(e++,this.totalEvictions++,this.emit(n.QUEUEEVICT,{id:t.id,priority:t.priority,enqueueTime:t.enqueueTime,weight:t.weight}),this.debug&&console.warn(`[Semaphore] Evicted queued task #${t.id}: circuit opened`));}e>0&&this.metricsCollector?.sampleQueueDepthAt(Date.now(),this.queue.size);}_timeoutTask(e){if(this.totalTimeouts++,this.circuit.recordFailure(),this.backoff.onTimeout(),e.isProbe)this.circuit.handleProbeFailure(),this.emit(n.CIRCUITOPEN,{timeoutRate:1,recentTimeouts:1,total:1,reason:"half-open-probe-failed"}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn("[Semaphore] Circuit re-opened: half-open probe timed out");else {let t=this.circuit.evaluateAndTrip();t.tripped&&(this.emit(n.CIRCUITOPEN,{timeoutRate:t.timeoutRate,recentTimeouts:t.failures,total:t.attempts}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn(`[Semaphore] Circuit opened. Rate: ${(t.timeoutRate*100).toFixed(1)}%`),this._evictQueueOnCircuitOpen());}this.metricsCollector?.onTimeout(Date.now(),this.queue.size),this.emit(n.TASKTIMEOUT,{queueLength:this.queue.size,backoffDelay:this.backoff.currentDelay,taskId:e.id}),this.debug&&console.warn(`[Semaphore] Task #${e.id} timed out after ${this.queueMaxTimeout}ms`),e.reject(new a(`Semaphore acquire timed out after ${this.queueMaxTimeout}ms (queue: ${this.queue.size})`,"TIMEOUT"));}_onTaskAbort(e,t){this._dequeue(e),e.isProbe&&this.circuit.releaseProbeSlot(),this.metricsCollector?.onAbort(Date.now(),this.queue.size),this.emit(n.TASKABORT),this.debug&&console.info(`[Semaphore] Task #${e.id} aborted`),t(new a("Semaphore acquire aborted","ABORTED")),this._checkDrain(),this.schedule();}schedule(){if(this.scheduled||this.isShutdown)return;this.scheduled=true;let e=this.backoff.currentDelay;e>0?setTimeout(this._runSchedulerBound,e).unref?.():queueMicrotask(this._runSchedulerBound);}_runScheduler(){if(!this.isShutdown){this.scheduled=false;try{for(;this.queue.size>0&&!this.circuit.isOpen;){let e=this.queue.peek();if(!e||this.circuit.isHalfOpen&&e.id!==this.circuit.probeTaskId||!this.permits.hasCapacityFor(e.weight))break;let t=this.queue.pop();this.enqueueOrder.remove(t),this.enqueueOrder.size===0&&this._clearTimeout(),t.dispatch(()=>{let r=Date.now(),s=Math.max(0,r-t.enqueueTime);return this.permits.acquire(t.weight),this.totalAcquired++,this.metricsCollector?.onAcquireQueued(r,s,this.permits.inFlight,this.queue.size),this.permits.assertInvariant(this.debug),this._createRelease(t.isProbe,t.weight)})&&this.hasListeners(n.TASKACQUIRE)&&this.emit(n.TASKACQUIRE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available,...t.isProbe?{probe:!0}:{}});}this._checkDrain();}catch(e){e instanceof Error?console.error("[Semaphore] Scheduler error:",e.message,e.stack):console.error("[Semaphore] Scheduler error:",e);}}}_startPurgeInterval(){this.purgeIntervalId!==null&&clearInterval(this.purgeIntervalId),this.purgeIntervalId=setInterval(()=>{this.isShutdown||this._purgeStaleTasks();},this.purgeIntervalMs),this.purgeIntervalId.unref?.();}_purgeStaleTasks(){let e=Date.now(),t=this.queue.size,i=this.enqueueOrder.peekHead();for(;i!==void 0&&e-i.enqueueTime>this.queueMaxAge;){this.circuit.probeTaskId===i.id&&this.circuit.releaseProbeSlot();let r=i.discard(new a(`Task purged after ${this.queueMaxAge}ms`,"PURGED"));this._dequeue(i),r&&(this.totalPurged++,this.metricsCollector?.onPurge(Date.now(),this.queue.size),this.emit(n.QUEUEPURGE,{id:i.id,priority:i.priority,enqueueTime:i.enqueueTime,weight:i.weight}),this.debug&&console.warn(`[Semaphore] Purged stale task #${i.id}`)),i=this.enqueueOrder.peekHead();}this.debug&&this.queue.size<t&&console.info(`[Semaphore] Purged ${t-this.queue.size} stale tasks`),this.queue.size<t&&(this._checkDrain(),this.schedule());}tryAcquire(e=1){return this.isShutdown||!Number.isInteger(e)||e<1||e>this.permits.capacity||(this.circuit.checkAndTransition()&&(this.emit(n.CIRCUITHALFOPEN),this.metricsCollector?.markCircuitHalfOpen(),this.debug&&console.info("[Semaphore] Circuit entering half-open"),this.schedule()),this.circuit.isOpen)?null:this._tryAcquireFast(e)}acquire(e,t=0,i=1){return this._acquire(e,t,i)}_acquire(e,t=0,i=1){if(this.lastAdmissionWasProbe=false,this.isShutdown)return Promise.reject(new a("Semaphore is shut down","SHUTDOWN"));if(!Number.isInteger(i)||i<1||i>this.permits.capacity)return Promise.reject(new a(`Invalid weight: ${i} (must be integer in 1..${this.permits.capacity})`,"INVALID_WEIGHT"));if(typeof t!="number"||!Number.isFinite(t))return Promise.reject(new a(`Invalid priority: ${t} (must be a finite number)`,"INVALID_PRIORITY"));if(this.circuit.checkAndTransition()&&(this.emit(n.CIRCUITHALFOPEN),this.metricsCollector?.markCircuitHalfOpen(),this.debug&&console.info("[Semaphore] Circuit entering half-open"),this.schedule()),this.circuit.isOpen)return Promise.reject(new a(`Circuit breaker open, retry in ${this.circuit.cooldownRemaining}ms`,"CIRCUIT_OPEN"));if(this.circuit.isHalfOpen&&this.circuit.hasProbeInFlight)return Promise.reject(new a("Circuit breaker half-open, probe in flight","CIRCUIT_HALF_OPEN"));if(e?.aborted)return Promise.reject(new a("Semaphore acquire aborted before start","ABORTED"));let r=this.circuit.isHalfOpen,s=this._tryAcquireFast(i);if(s)return this.lastAdmissionWasProbe=r,Promise.resolve(s);let u=this.circuit.isHalfOpen&&!this.circuit.hasProbeInFlight;return !u&&this.rejectOnFull?Promise.reject(new a("Semaphore at capacity (rejectOnFull)","QUEUE_FULL")):!u&&this.queue.size>=this.queueMaxLength?Promise.reject(new a(`Queue full (${this.queueMaxLength})`,"QUEUE_FULL")):(this.lastAdmissionWasProbe=u,new Promise((c,d)=>{let p=++this.taskIdCounter,m=Date.now(),b=u,f=new S({id:p,priority:b?Number.MIN_SAFE_INTEGER:t,enqueueTime:m,isProbe:b,resolve:c,reject:d,abortSignal:e,weight:i});f.arm(()=>this._onTaskAbort(f,d)),b&&this.circuit.claimProbeSlot(p),this.metricsCollector?.sampleQueueDepthAt(Date.now(),this.queue.size+1),this._enqueue(f),this.schedule();}))}peekQueue(){let e=[],t=this.enqueueOrder.peekHead();for(;t!==void 0;)e.push({id:t.id,priority:t.priority,enqueueTime:t.enqueueTime,weight:t.weight,isProbe:t.isProbe}),t=t.next??void 0;return e}async use(e,t,i=0,r=1){let s=this._acquire(t,i,r),u=this.failurePredicate!==void 0&&this.lastAdmissionWasProbe,c=await s;try{let d=await e();return c(),d}catch(d){if(this.failurePredicate!==void 0){let p=false;try{p=this.failurePredicate(d)===!0;}catch(m){console.warn("[Semaphore] circuitBreakerFailurePredicate threw (rejection treated as non-matching):",m);}p&&(u&&this.circuit.isHalfOpen?(this.circuit.recordFailure(),this.circuit.handleProbeFailure(),this.emit(n.CIRCUITOPEN,{timeoutRate:1,recentTimeouts:1,total:1,reason:"half-open-probe-failed"}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn("[Semaphore] Circuit re-opened: half-open probe operation failed")):this.reportFailure());}throw c(),d}}drain(e){return this.isShutdown?Promise.reject(new a("Cannot drain: semaphore is shut down","SHUTDOWN")):(e!==void 0&&l(e,"drain timeoutMs",1,Number.MAX_SAFE_INTEGER,true,true),this.drainPromise?this.drainPromise:this.queue.size===0&&this.permits.available===this.permits.capacity?Promise.resolve():(this.drainPromise=new Promise((t,i)=>{if(this.drainResolve=t,e!==void 0){let r=setTimeout(()=>{this.drainResolve=null,this.drainPromise=null,i(new a(`drain() timed out after ${e}ms`,"TIMEOUT"));},e),s=this.drainResolve;this.drainResolve=()=>{clearTimeout(r),s();};}}),this.drainPromise))}reset(e={}){if(this.isShutdown)throw new a("Cannot reset a semaphore that has been shut down","SHUTDOWN");for(let t of this.queue.toArray())t.discard(new a("Semaphore reset","SHUTDOWN"));this.queue.clear(),this.enqueueOrder.clear(),this._clearTimeout(),this.metricsCollector?.reset(),this.permits.reset(),this.backoff.reset(),this.circuit.reset(),this.releaseGeneration++,this.pendingReleaseCount=0,this.scheduled=false,this.isShutdown=false,this.taskIdCounter=0,this.totalAcquired=0,this.totalReleased=0,this.totalTimeouts=0,this.totalPurged=0,this.totalEvictions=0,e.clearListeners&&this.eventListeners.clear(),this.drainResolve&&(this.drainResolve(),this.drainResolve=null,this.drainPromise=null),this._startPurgeInterval(),this.metricsCollector?.markCapacityChange(this.permits.capacity),this.metricsCollector?.sampleGauges(Date.now(),this.permits.inFlight,this.queue.size),this.debug&&console.info("[Semaphore] Reset to initial state");}shutdown(e="Semaphore shutdown"){if(!this.isShutdown){this.isShutdown=true,this.debug&&console.info(`[Semaphore] Shutdown: ${e}`),this.purgeIntervalId!==null&&(clearInterval(this.purgeIntervalId),this.purgeIntervalId=null),this._clearTimeout();for(let t of this.queue.toArray())t.discard(new a(e,"SHUTDOWN"));this.queue.clear(),this.enqueueOrder.clear(),this.releaseGeneration++,this.pendingReleaseCount=0,this.permits.reset(),this.metricsCollector?.destroy(),this.metricsCollector=void 0,this.drainResolve&&(this.drainResolve(),this.drainResolve=null,this.drainPromise=null),this.emit(n.SHUTDOWN,e);}}cancel(){if(this.isShutdown)return;let e=this.queue.toArray();for(let t of e)t.isProbe&&this.circuit.releaseProbeSlot(),t.discard(new a("Semaphore acquire cancelled","CANCELLED")),this._dequeue(t);this.metricsCollector?.sampleQueueDepthAt(Date.now(),this.queue.size),this.debug&&console.info(`[Semaphore] Cancelled ${e.length} queued tasks`),this._checkDrain(),this.schedule();}reportFailure(){if(this.isShutdown)return;this.circuit.recordFailure();let e=this.circuit.evaluateAndTrip();e.tripped&&(this.emit(n.CIRCUITOPEN,{timeoutRate:e.timeoutRate,recentTimeouts:e.failures,total:e.attempts,reason:"reported-failure"}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn(`[Semaphore] Circuit opened via reportFailure(). Rate: ${(e.timeoutRate*100).toFixed(1)}%`),this._evictQueueOnCircuitOpen());}status(){let e=this.metricsCollector?.getSnapshot()??null,t=this.metricsCollector!==void 0?e?.windows?.[this.metricsCollector.primaryLabel]?.counts:void 0,i=(this.metricsCollector?.primaryWindowMs??6e4)/1e3,r=t?.acquired??0,s=t?.timeouts??0,u=this.enqueueOrder.peekHead(),c=u===void 0?0:Math.max(0,Date.now()-u.enqueueTime);return {status:{running:this.permits.capacity-this.permits.available,queued:this.queue.size,available:this.permits.available,inFlight:this.permits.inFlight,pendingReleases:this.pendingReleaseCount,circuitOpen:this.circuit.isOpen,circuitHalfOpen:this.circuit.isHalfOpen,backoffDelay:Math.round(this.backoff.currentDelay),requestsPerSecond:+(r/i).toFixed(2),timeoutRate1m:r+s>0?+(s/(r+s)*100).toFixed(1):0,queueAge:c},lifetime:{totalAcquired:this.totalAcquired,totalReleased:this.totalReleased,totalTimeouts:this.totalTimeouts,totalPurged:this.totalPurged,totalEvictions:this.totalEvictions,circuitBreakerCooldownRemaining:this.circuit.cooldownRemaining},metrics:e}}get availablePermits(){return this.permits.available}isAvailable(){return !this.isShutdown&&!this.circuit.isOpen&&!this.permits.isFull}get capacity(){return this.permits.capacity}get circuitState(){return this.circuit.state}get queueLength(){return this.queue.size}};var x={tripped:false},A=class{constructor(){this.state="closed";this.isOpen=false;this.isHalfOpen=false;this.hasProbeInFlight=false;this.probeTaskId=null;this.cooldownRemaining=0;}checkAndTransition(){return false}trackAttempt(){}recordFailure(){}evaluateAndTrip(){return x}markProbeInFlight(){}claimProbeSlot(){}releaseProbeSlot(){}handleProbeSuccess(){}handleProbeFailure(){}reset(){}};var O={tripped:false},C=class{constructor(){this.state="closed";this.hasProbeInFlight=false;this.probeTaskId=null;}get isOpen(){return this.state==="open"}get isHalfOpen(){return false}get cooldownRemaining(){return 0}open(){this.state="open";}close(){this.state="closed";}checkAndTransition(){return false}trackAttempt(){}recordFailure(){}evaluateAndTrip(){return O}markProbeInFlight(){}claimProbeSlot(){}releaseProbeSlot(){}handleProbeSuccess(){}handleProbeFailure(){}reset(){this.state="closed";}};export{C as ManualCircuitBreaker,A as NoopCircuitBreaker,E as QUEUE_ORDERINGS,v as SaturationCircuitBreaker,M as Semaphore,a as SemaphoreError,n as SemaphoreEvents};//# sourceMappingURL=index.js.map
var a=class extends Error{constructor(e,t){super(e),this.name="SemaphoreError",this.code=t;}};var l=(o,e,t,i,r=true,s=true)=>{if(typeof o!="number"||Number.isNaN(o))throw new a(`${e} must be a valid number`,"INVALID_ARGUMENT");if(r&&!Number.isInteger(o))throw new a(`${e} must be an integer`,"INVALID_ARGUMENT");if(s?o<t:o<=t)throw new a(`${e} must be ${s?">=":">"} ${t}`,"INVALID_ARGUMENT");if(s?o>i:o>=i)throw new a(`${e} must be ${s?"<=":"<"} ${i}`,"INVALID_ARGUMENT");return o};var T=class{constructor(e){this._inFlight=0;this.capacity=e,this._available=e;}get available(){return this._available}get inFlight(){return this._inFlight}get isFull(){return this._available===0}hasCapacityFor(e){return this._available>=e}acquire(e=1){this._available-=e,this._inFlight+=e;}release(e=1){this._available=Math.min(this._available+e,this.capacity),this._inFlight=this._inFlight<e?0:this._inFlight-e;}assertInvariant(e){if(!e)return;let t=this._inFlight+this._available;t!==this.capacity&&console.error(`[Semaphore] Invariant violation: inFlight(${this._inFlight}) + available(${this._available}) = ${t}, expected capacity(${this.capacity})`);}reset(){this._available=this.capacity,this._inFlight=0;}};var A=class{constructor(e,t){this.cachedIndex=-1;this.cachedUntil=0;this.size=e,this.stepMs=t,this.buckets=new Int32Array(e*2),this.timestamps=new Float64Array(e);}idx(e){if(e<this.cachedUntil&&e>=this.cachedUntil-this.stepMs)return this.cachedIndex;let t=Math.floor(e/this.stepMs)*this.stepMs,i=Math.floor(t/this.stepMs)%this.size;return this.timestamps[i]!==t&&(this.timestamps[i]=t,this.buckets[i*2]=0,this.buckets[i*2+1]=0),this.cachedIndex=i,this.cachedUntil=t+this.stepMs,i}addAcquired(e=Date.now()){this.buckets[this.idx(e)*2]++;}addTimeout(){this.buckets[this.idx(Date.now())*2+1]++;}snapshot(){let e=Date.now(),t=Math.floor(e/this.stepMs)*this.stepMs-(this.size-1)*this.stepMs,i=0,r=0;for(let s=0;s<this.size;s++)this.timestamps[s]>=t&&(i+=this.buckets[s*2],r+=this.buckets[s*2+1]);return {acquired:i,timeouts:r}}reset(){this.cachedIndex=-1,this.cachedUntil=0,this.buckets.fill(0),this.timestamps.fill(0);}},v=class{constructor(e={}){this.state="closed";this.openUntil=0;this.probeInFlight=false;this._probeTaskId=null;if(this.threshold=l(e.threshold??.5,"SaturationCircuitBreaker threshold",0,1,false,false),this.window=l(e.window??1e4,"SaturationCircuitBreaker window",1e3,Number.MAX_SAFE_INTEGER,true,true),this.windowBucketWidth=l(e.windowBucketWidth??1e3,"SaturationCircuitBreaker windowBucketWidth",1,Number.MAX_SAFE_INTEGER,true,true),this.windowBucketCount=Math.ceil(this.window/this.windowBucketWidth),this.cooldown=l(e.cooldown??5e3,"SaturationCircuitBreaker cooldown",1e3,Number.MAX_SAFE_INTEGER,true,true),this.minThroughput=l(e.minThroughput??10,"SaturationCircuitBreaker minThroughput",1,Number.MAX_SAFE_INTEGER,true,true),this.minFailures=l(e.minFailures??5,"SaturationCircuitBreaker minFailures",1,Number.MAX_SAFE_INTEGER,true,true),this.window<this.windowBucketWidth)throw new a("SaturationCircuitBreaker window must be >= windowBucketWidth","INVALID_ARGUMENT");if(this.minThroughput<this.minFailures)throw new a("SaturationCircuitBreaker minThroughput must be >= minFailures","INVALID_ARGUMENT");if(this.windowBucketCount<2)throw new a("SaturationCircuitBreaker window must span at least 2 windowBucketWidth buckets","INVALID_ARGUMENT");this.eventWindow=new A(this.windowBucketCount,this.windowBucketWidth);}get isClosed(){return this.state==="closed"}get isOpen(){return this.state==="open"}get isProbing(){return this.state==="probing"}get hasProbeInFlight(){return this.probeInFlight}get probeTaskId(){return this._probeTaskId}get cooldownRemaining(){return this.isOpen?Math.max(0,this.openUntil-Date.now()):0}trackAttempt(e){this.state==="closed"&&this.eventWindow.addAcquired(e);}recordFailure(){this.eventWindow.addTimeout();}checkAndTransition(){return this.state!=="open"||Date.now()<this.openUntil?false:(this.state="probing",this.probeInFlight=false,this._probeTaskId=null,true)}evaluateAndTrip(){if(this.state!=="closed")return {tripped:false};let{timeouts:e,acquired:t}=this.eventWindow.snapshot();if(t<this.minThroughput||e<this.minFailures)return {tripped:false};let i=e/t;return i<this.threshold?{tripped:false}:(this.state="open",this.openUntil=Date.now()+this.cooldown,{tripped:true,timeoutRate:i,failures:e,attempts:t})}markProbeInFlight(){this.probeInFlight=true;}claimProbeSlot(e){this.probeInFlight=true,this._probeTaskId=e;}releaseProbeSlot(){this.probeInFlight=false,this._probeTaskId=null;}handleProbeSuccess(){this.state="closed",this.probeInFlight=false,this._probeTaskId=null,this.eventWindow.reset();}handleProbeFailure(){this.state="open",this.probeInFlight=false,this._probeTaskId=null,this.openUntil=Date.now()+this.cooldown;}reset(){this.state="closed",this.openUntil=0,this.probeInFlight=false,this._probeTaskId=null,this.eventWindow.reset();}};var y=class{constructor(e={}){this.delay=0;this.lastTimestamp=0;if(this.initialTimeout=l(e.initialTimeout??50,"BackoffTracker initialTimeout",0,Number.MAX_SAFE_INTEGER,true,true),this.maxTimeout=l(e.maxTimeout??2e3,"BackoffTracker maxTimeout",0,Number.MAX_SAFE_INTEGER,true,true),this.decayFactor=l(e.decayFactor??.5,"BackoffTracker decayFactor",0,1,false,false),this.maxTimeout<this.initialTimeout)throw new a("BackoffTracker maxTimeout must be >= initialTimeout","INVALID_ARGUMENT")}get currentDelay(){return this._decayedDelay(Date.now())}_decayedDelay(e){if(this.delay===0)return 0;let t=(e-this.lastTimestamp)/1e3;if(t<=0)return this.delay;let i=this.delay*Math.pow(this.decayFactor,t);return i<1?0:i}onTimeout(e=Date.now()){let t=this._decayedDelay(e),i=t>0?t*2:this.initialTimeout;this.delay=Math.min(i,this.maxTimeout),this.lastTimestamp=e;}reset(){this.delay=0,this.lastTimestamp=0;}};var S=class{constructor(e){this.prev=null;this.next=null;this.heapIndex=-1;this.completed=false;this.id=e.id,this.priority=e.priority,this.enqueueTime=e.enqueueTime,this.isProbe=e.isProbe,this.weight=e.weight??1,this._resolve=e.resolve,this._reject=e.reject,this.abortSignal=e.abortSignal;}arm(e){this.abortSignal&&(this.abortListener=()=>{this.claim()&&e();},this.abortSignal.addEventListener("abort",this.abortListener));}dispatch(e){return this.claim()?(this._resolve(e()),true):false}discard(e){return this.claim()?(this._reject(e),true):false}claim(){return this.completed?false:(this.completed=true,this.abortSignal&&this.abortListener&&this.abortSignal.removeEventListener("abort",this.abortListener),true)}reject(e){this._reject(e);}};var g=class{constructor(e){this.heap=[];this.compare=e;}get size(){return this.heap.length}isEmpty(){return this.heap.length===0}peek(){return this.heap[0]}has(e){return e.heapIndex>=0&&this.heap[e.heapIndex]===e}clear(){for(let e of this.heap)e.heapIndex=-1;this.heap=[];}insert(e){if(e.heapIndex>=0)throw new a("Item is already in a heap","INVALID_ARGUMENT");let t=this.heap.length;this.heap.push(e),e.heapIndex=t,this._bubbleUp(t);}pop(){if(this.heap.length===0)return;let e=this.heap[0];e.heapIndex=-1;let t=this.heap.pop();return this.heap.length>0&&t!==e&&(this.heap[0]=t,t.heapIndex=0,this._bubbleDown(0)),e}delete(e){let t=e.heapIndex;if(t<0||this.heap[t]!==e)return;e.heapIndex=-1;let i=this.heap.pop();if(t===this.heap.length||i===e)return e;this.heap[t]=i,i.heapIndex=t;let r=this.heap[t-1>>1];return t>0&&this.compare(i,r)<0?this._bubbleUp(t):this._bubbleDown(t),e}_swap(e,t){let i=this.heap[e],r=this.heap[t];this.heap[e]=r,this.heap[t]=i,i.heapIndex=t,r.heapIndex=e;}_bubbleUp(e){for(;e>0;){let t=e-1>>1;if(this.compare(this.heap[e],this.heap[t])<0)this._swap(e,t),e=t;else break}}_bubbleDown(e){let t=this.heap.length;for(;;){let i=e,r=2*e+1,s=2*e+2;if(r<t&&this.compare(this.heap[r],this.heap[i])<0&&(i=r),s<t&&this.compare(this.heap[s],this.heap[i])<0&&(i=s),i===e)break;this._swap(e,i),e=i;}}};var k=class{constructor(){this.head=null;this.tail=null;this._size=0;}get size(){return this._size}isEmpty(){return this._size===0}peekHead(){return this.head===null?void 0:this.head}pushTail(e){e.prev=this.tail,e.next=null,this.tail!==null?this.tail.next=e:this.head=e,this.tail=e,this._size++;}remove(e){e.prev!==null?e.prev.next=e.next:this.head=e.next,e.next!==null?e.next.prev=e.prev:this.tail=e.prev,e.prev=e.next=null,this._size--;}clear(){this.head=this.tail=null,this._size=0;}};var _=class{constructor(e,t){this.cachedIndex=-1;this.cachedUntil=0;this.size=l(e,"CombinedWindow size",1,Number.MAX_SAFE_INTEGER,true,true),this.stepMs=l(t,"CombinedWindow stepMs",1,Number.MAX_SAFE_INTEGER,true,true),this.timestamps=new Float64Array(e),this.acquired=new Int32Array(e),this.released=new Int32Array(e),this.timeouts=new Int32Array(e),this.ifSum=new Float64Array(e),this.ifCount=new Int32Array(e),this.ifMax=new Float64Array(e),this.qSum=new Float64Array(e),this.qCount=new Int32Array(e),this.qMax=new Float64Array(e),this.latSum=new Float64Array(e),this.latCount=new Int32Array(e);}bucket(e){if(e<this.cachedUntil&&e>=this.cachedUntil-this.stepMs)return this.cachedIndex;let t=Math.floor(e/this.stepMs),i=t*this.stepMs,r=t%this.size;return this.timestamps[r]!==i&&(this.timestamps[r]=i,this.acquired[r]=0,this.released[r]=0,this.timeouts[r]=0,this.ifSum[r]=0,this.ifCount[r]=0,this.ifMax[r]=0,this.qSum[r]=0,this.qCount[r]=0,this.qMax[r]=0,this.latSum[r]=0,this.latCount[r]=0),this.cachedIndex=r,this.cachedUntil=i+this.stepMs,r}addInflight(e,t){this.ifSum[e]+=t,this.ifCount[e]++,t>this.ifMax[e]&&(this.ifMax[e]=t);}addQueue(e,t){this.qSum[e]+=t,this.qCount[e]++,t>this.qMax[e]&&(this.qMax[e]=t);}recordAcquire(e,t,i){let r=this.bucket(e);this.acquired[r]++,this.addInflight(r,t),this.addQueue(r,i);}recordAcquireQueued(e,t,i,r){let s=this.bucket(e);this.acquired[s]++,this.latSum[s]+=t,this.latCount[s]++,this.addInflight(s,i),this.addQueue(s,r);}recordRelease(e,t,i){let r=this.bucket(e);this.released[r]++,this.addInflight(r,t),this.addQueue(r,i);}recordTimeoutQueue(e,t){let i=this.bucket(e);this.timeouts[i]++,this.addQueue(i,t);}sampleBoth(e,t,i){let r=this.bucket(e);this.addInflight(r,t),this.addQueue(r,i);}addAcquired(e){this.acquired[this.bucket(e)]++;}addReleased(e){this.released[this.bucket(e)]++;}addTimeout(e){this.timeouts[this.bucket(e)]++;}addLatency(e,t){let i=this.bucket(e);this.latSum[i]+=t,this.latCount[i]++;}sampleInflight(e,t){this.addInflight(this.bucket(e),t);}sampleQueue(e,t){this.addQueue(this.bucket(e),t);}snapshot(e){let t=Math.floor(e/this.stepMs)*this.stepMs-(this.size-1)*this.stepMs,i=0,r=0,s=0,u=0,c=0,d=0,m=0,p=0,b=0,f=0,w=0;for(let h=0;h<this.size;h++)this.timestamps[h]<t||(i+=this.acquired[h],r+=this.released[h],s+=this.timeouts[h],u+=this.ifSum[h],c+=this.ifCount[h],this.ifMax[h]>d&&(d=this.ifMax[h]),m+=this.qSum[h],p+=this.qCount[h],this.qMax[h]>b&&(b=this.qMax[h]),f+=this.latSum[h],w+=this.latCount[h]);return {counts:{acquired:i,released:r,timeouts:s},inflight:{avg:c===0?0:u/c,max:d,samples:c},queue:{avg:p===0?0:m/p,max:b,samples:p},latency:{avg:w===0?0:f/w,count:w,total:f}}}reset(){this.cachedIndex=-1,this.cachedUntil=0,this.timestamps.fill(0),this.acquired.fill(0),this.released.fill(0),this.timeouts.fill(0),this.ifSum.fill(0),this.ifCount.fill(0),this.ifMax.fill(0),this.qSum.fill(0),this.qCount.fill(0),this.qMax.fill(0),this.latSum.fill(0),this.latCount.fill(0);}},R=[{size:60,stepMs:1e3},{size:60,stepMs:5e3},{size:60,stepMs:15e3},{size:60,stepMs:6e4},{size:60,stepMs:144e4}],I=class{constructor(e=R){this._totalAcquiredFast=0;this._totalAcquiredQueued=0;this._totalReleased=0;this._totalTimeouts=0;this._totalPurged=0;this._totalAborts=0;this._capacity=0;this._circuitOpen=false;this._circuitProbing=false;if(e.length===0)throw new a("SemaphoreMetrics requires at least one WindowOptions","INVALID_ARGUMENT");this.windows=e.map(({size:r,stepMs:s})=>new _(r,s)),this.windowLabels=e.map(({size:r,stepMs:s})=>{let u=r*s;return u>=36e5&&u%36e5===0?`${u/36e5}h`:u>=6e4&&u%6e4===0?`${u/6e4}m`:u>=1e3&&u%1e3===0?`${u/1e3}s`:`${u}ms`});let t=new Set;for(let r of this.windowLabels){if(t.has(r))throw new a(`SemaphoreMetrics windows produce duplicate label "${r}" (two windows cover the same horizon)`,"INVALID_ARGUMENT");t.add(r);}let i=0;for(let r=1;r<e.length;r++)e[r].size*e[r].stepMs<e[i].size*e[i].stepMs&&(i=r);this.primaryLabel=this.windowLabels[i],this.primaryWindowMs=e[i].size*e[i].stepMs;}onAcquireFast(e,t,i){this._totalAcquiredFast++;for(let r of this.windows)r.recordAcquire(e,t,i);}onAcquireQueued(e,t,i,r){this._totalAcquiredQueued++;for(let s of this.windows)s.recordAcquireQueued(e,t,i,r);}onRelease(e,t,i){this._totalReleased++;for(let r of this.windows)r.recordRelease(e,t,i);}onTimeout(e,t){this._totalTimeouts++;for(let i of this.windows)i.recordTimeoutQueue(e,t);}onAbort(e,t){this._totalAborts++;for(let i of this.windows)i.sampleQueue(e,t);}onPurge(e,t){this._totalPurged++;for(let i of this.windows)i.sampleQueue(e,t);}sampleGauges(e,t,i){for(let r of this.windows)r.sampleBoth(e,t,i);}sampleQueueDepthAt(e,t){for(let i of this.windows)i.sampleQueue(e,t);}markAcquireFast(){this._totalAcquiredFast++;let e=Date.now();for(let t of this.windows)t.addAcquired(e);}markAcquireQueued(e){this._totalAcquiredQueued++;let t=Date.now();for(let i of this.windows)i.addAcquired(t),i.addLatency(t,e);}markReleased(){this._totalReleased++;let e=Date.now();for(let t of this.windows)t.addReleased(e);}markTimeout(){this._totalTimeouts++;let e=Date.now();for(let t of this.windows)t.addTimeout(e);}markAbort(){this._totalAborts++;}sampleInFlight(e){let t=Date.now();for(let i of this.windows)i.sampleInflight(t,e);}sampleQueueDepth(e){let t=Date.now();for(let i of this.windows)i.sampleQueue(t,e);}markCapacityChange(e){this._capacity=e;}markCircuitOpen(){this._circuitOpen=true,this._circuitProbing=false;}markCircuitProbing(){this._circuitOpen=false,this._circuitProbing=true;}markCircuitClose(){this._circuitOpen=false,this._circuitProbing=false;}getSnapshot(e=Date.now()){let t={},i=0,r=0;for(let s=0;s<this.windowLabels.length;s++){let u=this.windows[s].snapshot(e);t[this.windowLabels[s]]=u,s===0&&(i=u.inflight.avg,r=u.queue.avg);}return {windows:t,meta:{inFlightLastMinute:Math.round(i),queueDepthLastMinute:Math.round(r),totalAcquiredFast:this._totalAcquiredFast,totalAcquiredQueued:this._totalAcquiredQueued,totalReleased:this._totalReleased,totalTimeouts:this._totalTimeouts,totalPurged:this._totalPurged,totalAborts:this._totalAborts,capacity:this._capacity,circuitOpen:this._circuitOpen,circuitProbing:this._circuitProbing}}}reset(){for(let e of this.windows)e.reset();this._totalAcquiredFast=0,this._totalAcquiredQueued=0,this._totalReleased=0,this._totalTimeouts=0,this._totalPurged=0,this._totalAborts=0,this._capacity=0,this._circuitOpen=false,this._circuitProbing=false;}destroy(){this.reset();}};var E={fifo:(o,e)=>o.id-e.id,lifo:(o,e)=>e.id-o.id,fifoWithPriority:(o,e)=>o.priority-e.priority||o.id-e.id,lifoWithPriority:(o,e)=>o.priority-e.priority||e.id-o.id};function x(o){if(o.comparator!==void 0){if(typeof o.comparator!="function")throw new a("Semaphore comparator must be a function","INVALID_ARGUMENT");return o.comparator}let e=o.queueOrder??"fifoWithPriority",t=E[e];if(t===void 0)throw new a(`Semaphore queueOrder must be one of: ${Object.keys(E).join(", ")} (got ${JSON.stringify(e)})`,"INVALID_ARGUMENT");return t}function M(o){let e=x(o);return (t,i)=>{if(t.isProbe!==i.isProbe)return t.isProbe?-1:1;let r=e(t,i);return typeof r!="number"||Number.isNaN(r)?t.id-i.id:r}}var n={TASKACQUIRE:"task-acquire",TASKRELEASE:"task-release",TASKTIMEOUT:"task-timeout",TASKABORT:"task-abort",QUEUEPURGE:"queue-purge",QUEUEEVICT:"queue-evict",CIRCUITOPEN:"circuit-open",CIRCUITPROBING:"circuit-probing",CIRCUITCLOSE:"circuit-close",SHUTDOWN:"shutdown"};var P=class{constructor(e,t={}){this.pendingReleaseCount=0;this.releaseGeneration=0;this.scheduled=false;this.isShutdown=false;this.taskIdCounter=0;this.totalAcquired=0;this.totalReleased=0;this.totalTimeouts=0;this.totalPurged=0;this.totalEvictions=0;this.eventListeners=new Map;this.drainPromise=null;this.drainResolve=null;this.purgeIntervalId=null;this.timeoutTimerId=null;this._runSchedulerBound=()=>{this._runScheduler();};this.lastAdmissionWasProbe=false;if(l(e,"Semaphore count",0,Number.MAX_SAFE_INTEGER,true,false),this.purgeIntervalMs=l(t.purgeIntervalMs??3e3,"Semaphore purgeIntervalMs",500,Number.MAX_SAFE_INTEGER,true,true),this.queueMaxTimeout=l(t.queueMaxTimeout??1e4,"Semaphore queueMaxTimeout",1,Number.MAX_SAFE_INTEGER,true,true),this.queueMaxLength=l(t.queueMaxLength??1024,"Semaphore queueMaxLength",1,Number.MAX_SAFE_INTEGER,true,true),this.queueMaxAge=l(t.queueMaxAge??3e4,"Semaphore queueMaxAge",1,Number.MAX_SAFE_INTEGER,true,true),this.rejectOnFull=t.rejectOnFull??false,this.metricsEnabled=t.metricsEnabled??true,this.debug=t.debug??false,t.circuitBreakerFailurePredicate!==void 0&&typeof t.circuitBreakerFailurePredicate!="function")throw new a("Semaphore circuitBreakerFailurePredicate must be a function","INVALID_ARGUMENT");this.failurePredicate=t.circuitBreakerFailurePredicate;let i=M({queueOrder:t.queueOrder,comparator:t.comparator});this.queue=new g(i),this.enqueueOrder=new k,this.permits=new T(e),this.circuit=t.circuitBreaker??new v({threshold:t.circuitBreakerThreshold,window:t.circuitBreakerWindow,windowBucketWidth:t.circuitBreakerWindowBucketWidth,cooldown:t.circuitBreakerCooldown,minThroughput:t.circuitBreakerMinThroughput,minFailures:t.circuitBreakerMinFailures}),this.backoff=new y({initialTimeout:t.backoffInitialTimeout,maxTimeout:t.backoffMaxTimeout,decayFactor:t.backoffDecayFactor}),this.metricsCollector=this.metricsEnabled?new I(t.metricsWindows):void 0,this.metricsCollector?.markCapacityChange(this.permits.capacity),this.metricsCollector?.sampleGauges(Date.now(),this.permits.inFlight,this.queue.size),this._startPurgeInterval();}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(t);}off(e,t){this.eventListeners.get(e)?.delete(t),this.eventListeners.get(e)?.size===0&&this.eventListeners.delete(e);}removeAllListeners(e){e?this.eventListeners.delete(e):this.eventListeners.clear();}hasListeners(e){let t=this.eventListeners.get(e);return t!==void 0&&t.size>0}emit(e,...t){let i=this.eventListeners.get(e);if(!(!i||i.size===0)){if(i.size===1){let r=i.values().next().value;try{r(...t);}catch(s){console.warn(`[Semaphore] Error in listener for "${e}":`,s);}return}for(let r of Array.from(i))try{r(...t);}catch(s){console.warn(`[Semaphore] Error in listener for "${e}":`,s);}}}_tryAcquireFast(e){if(this.circuit.isProbing){if(this.circuit.hasProbeInFlight||!this.permits.hasCapacityFor(e))return null;if(this.circuit.markProbeInFlight(),this.permits.acquire(e),this.totalAcquired++,this.metricsCollector!==void 0){let t=Date.now();this.circuit.trackAttempt(t),this.metricsCollector.onAcquireFast(t,this.permits.inFlight,this.queue.size);}else this.circuit.trackAttempt();return this.hasListeners(n.TASKACQUIRE)&&this.emit(n.TASKACQUIRE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available,probe:true}),this._createRelease(true,e)}if(this.queue.size>0||!this.permits.hasCapacityFor(e))return null;if(this.permits.acquire(e),this.totalAcquired++,this.metricsCollector!==void 0){let t=Date.now();this.circuit.trackAttempt(t),this.metricsCollector.onAcquireFast(t,this.permits.inFlight,this.queue.size);}else this.circuit.trackAttempt();return this.hasListeners(n.TASKACQUIRE)&&this.emit(n.TASKACQUIRE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available}),this._createRelease(false,e)}_createRelease(e=false,t=1){this.pendingReleaseCount++;let i=this.releaseGeneration,r=false;return ()=>{if(r||i!==this.releaseGeneration){this.debug&&console.warn("[Semaphore] release() called after already released (no-op)");return}r=true,this.pendingReleaseCount--,this.permits.release(t),this.totalReleased++,this.metricsCollector?.onRelease(Date.now(),this.permits.inFlight,this.queue.size),e&&this.circuit.isProbing&&(this.circuit.handleProbeSuccess(),this.emit(n.CIRCUITCLOSE),this.metricsCollector?.markCircuitClose(),this.debug&&console.info("[Semaphore] Circuit closed after successful probe")),this.permits.assertInvariant(this.debug),this.hasListeners(n.TASKRELEASE)&&this.emit(n.TASKRELEASE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available}),this._checkDrain(),this.schedule();}}_checkDrain(){this.drainResolve&&this.queue.size===0&&this.permits.available===this.permits.capacity&&(this.drainResolve(),this.drainResolve=null,this.drainPromise=null);}_enqueue(e){this.circuit.trackAttempt(),this.queue.insert(e),this.enqueueOrder.pushTail(e),this._armTimeout();}_dequeue(e){this.queue.delete(e)!==void 0&&(this.enqueueOrder.remove(e),this.enqueueOrder.size===0&&this._clearTimeout());}_armTimeout(){if(this.timeoutTimerId!==null)return;let e=this.enqueueOrder.peekHead();if(e===void 0)return;let t=Math.max(0,e.enqueueTime+this.queueMaxTimeout-Date.now());this.timeoutTimerId=setTimeout(()=>this._fireTimeout(),t);}_clearTimeout(){this.timeoutTimerId!==null&&(clearTimeout(this.timeoutTimerId),this.timeoutTimerId=null);}_fireTimeout(){if(this.timeoutTimerId=null,this.isShutdown)return;let e=Date.now(),t=0,i=this.enqueueOrder.peekHead();for(;i!==void 0&&i.enqueueTime+this.queueMaxTimeout<=e;){let r=i.claim();this._dequeue(i),r&&(this._timeoutTask(i,e),t++),i=this.enqueueOrder.peekHead();}if(i!==void 0){let r=Math.max(0,i.enqueueTime+this.queueMaxTimeout-e);this.timeoutTimerId=setTimeout(()=>this._fireTimeout(),r);}t>0&&(this._checkDrain(),this.schedule());}_evictTask(e){let t=e.discard(new a("Circuit breaker opened while task was queued","CIRCUIT_OPEN"));return t&&(this.totalEvictions++,this.emit(n.QUEUEEVICT,{id:e.id,priority:e.priority,enqueueTime:e.enqueueTime,weight:e.weight}),this.debug&&console.warn(`[Semaphore] Evicted queued task #${e.id}: circuit opened`)),t}_evictQueueOnCircuitOpen(){if(this.queue.size===0)return;if(this.circuit.probeTaskId===null){let i=0;for(let r=this.enqueueOrder.peekHead();r!==void 0;r=r.next??void 0)this._evictTask(r)&&i++;this.queue.clear(),this.enqueueOrder.clear(),this._clearTimeout(),i>0&&this.metricsCollector?.sampleQueueDepthAt(Date.now(),this.queue.size);return}let e=0,t=this.enqueueOrder.peekHead();for(;t!==void 0;){let i=t.next??void 0;t.isProbe||(this._evictTask(t)&&e++,this._dequeue(t)),t=i;}e>0&&this.metricsCollector?.sampleQueueDepthAt(Date.now(),this.queue.size);}_timeoutTask(e,t){if(this.totalTimeouts++,this.circuit.recordFailure(),this.backoff.onTimeout(t),e.isProbe)this.circuit.handleProbeFailure(),this.emit(n.CIRCUITOPEN,{timeoutRate:1,recentTimeouts:1,total:1,reason:"probe-failed"}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn("[Semaphore] Circuit re-opened: probe timed out");else {let i=this.circuit.evaluateAndTrip();i.tripped&&(this.emit(n.CIRCUITOPEN,{timeoutRate:i.timeoutRate,recentTimeouts:i.failures,total:i.attempts}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn(`[Semaphore] Circuit opened. Rate: ${(i.timeoutRate*100).toFixed(1)}%`),this._evictQueueOnCircuitOpen());}this.metricsCollector?.onTimeout(t,this.queue.size),this.emit(n.TASKTIMEOUT,{queueLength:this.queue.size,backoffDelay:this.backoff.currentDelay,taskId:e.id}),this.debug&&console.warn(`[Semaphore] Task #${e.id} timed out after ${this.queueMaxTimeout}ms`),e.reject(new a(`Semaphore acquire timed out after ${this.queueMaxTimeout}ms (queue: ${this.queue.size})`,"TIMEOUT"));}_onTaskAbort(e,t){this._dequeue(e),e.isProbe&&this.circuit.releaseProbeSlot(),this.metricsCollector?.onAbort(Date.now(),this.queue.size),this.emit(n.TASKABORT),this.debug&&console.info(`[Semaphore] Task #${e.id} aborted`),t(new a("Semaphore acquire aborted","ABORTED")),this._checkDrain(),this.schedule();}schedule(){if(this.scheduled||this.isShutdown)return;this.scheduled=true;let e=this.backoff.currentDelay;e>0?setTimeout(this._runSchedulerBound,e).unref?.():queueMicrotask(this._runSchedulerBound);}_runScheduler(){if(!this.isShutdown){this.scheduled=false;try{let e=Date.now();for(;this.queue.size>0&&!this.circuit.isOpen;){let t=this.queue.peek();if(!t||this.circuit.isProbing&&t.id!==this.circuit.probeTaskId||!this.permits.hasCapacityFor(t.weight))break;let i=this.queue.pop();this.enqueueOrder.remove(i),this.enqueueOrder.size===0&&this._clearTimeout(),i.dispatch(()=>{let s=Math.max(0,e-i.enqueueTime);return this.permits.acquire(i.weight),this.totalAcquired++,this.metricsCollector?.onAcquireQueued(e,s,this.permits.inFlight,this.queue.size),this.permits.assertInvariant(this.debug),this._createRelease(i.isProbe,i.weight)})&&this.hasListeners(n.TASKACQUIRE)&&this.emit(n.TASKACQUIRE,{queued:this.queue.size,running:this.permits.capacity-this.permits.available,...i.isProbe?{probe:!0}:{}});}this._checkDrain();}catch(e){e instanceof Error?console.error("[Semaphore] Scheduler error:",e.message,e.stack):console.error("[Semaphore] Scheduler error:",e);}}}_startPurgeInterval(){this.purgeIntervalId!==null&&clearInterval(this.purgeIntervalId),this.purgeIntervalId=setInterval(()=>{this.isShutdown||this._purgeStaleTasks();},this.purgeIntervalMs),this.purgeIntervalId.unref?.();}_purgeStaleTasks(){let e=Date.now(),t=this.queue.size,i=this.enqueueOrder.peekHead();for(;i!==void 0&&e-i.enqueueTime>this.queueMaxAge;){this.circuit.probeTaskId===i.id&&this.circuit.releaseProbeSlot();let r=i.discard(new a(`Task purged after ${this.queueMaxAge}ms`,"PURGED"));this._dequeue(i),r&&(this.totalPurged++,this.metricsCollector?.onPurge(e,this.queue.size),this.emit(n.QUEUEPURGE,{id:i.id,priority:i.priority,enqueueTime:i.enqueueTime,weight:i.weight}),this.debug&&console.warn(`[Semaphore] Purged stale task #${i.id}`)),i=this.enqueueOrder.peekHead();}this.debug&&this.queue.size<t&&console.info(`[Semaphore] Purged ${t-this.queue.size} stale tasks`),this.queue.size<t&&(this._checkDrain(),this.schedule());}tryAcquire(e=1){return this.isShutdown||!Number.isInteger(e)||e<1||e>this.permits.capacity||(this.circuit.checkAndTransition()&&(this.emit(n.CIRCUITPROBING),this.metricsCollector?.markCircuitProbing(),this.debug&&console.info("[Semaphore] Circuit entering probing"),this.schedule()),this.circuit.isOpen)?null:this._tryAcquireFast(e)}acquire(e,t=0,i=1){return this._acquire(e,t,i)}_acquire(e,t=0,i=1){if(this.lastAdmissionWasProbe=false,this.isShutdown)return Promise.reject(new a("Semaphore is shut down","SHUTDOWN"));if(!Number.isInteger(i)||i<1||i>this.permits.capacity)return Promise.reject(new a(`Invalid weight: ${i} (must be integer in 1..${this.permits.capacity})`,"INVALID_WEIGHT"));if(typeof t!="number"||!Number.isFinite(t))return Promise.reject(new a(`Invalid priority: ${t} (must be a finite number)`,"INVALID_PRIORITY"));if(this.circuit.checkAndTransition()&&(this.emit(n.CIRCUITPROBING),this.metricsCollector?.markCircuitProbing(),this.debug&&console.info("[Semaphore] Circuit entering probing"),this.schedule()),this.circuit.isOpen)return Promise.reject(new a(`Circuit breaker open, retry in ${this.circuit.cooldownRemaining}ms`,"CIRCUIT_OPEN"));if(this.circuit.isProbing&&this.circuit.hasProbeInFlight)return Promise.reject(new a("Circuit breaker is probing, probe already in flight","CIRCUIT_PROBING"));if(e?.aborted)return Promise.reject(new a("Semaphore acquire aborted before start","ABORTED"));let r=this.circuit.isProbing,s=this._tryAcquireFast(i);if(s)return this.lastAdmissionWasProbe=r,Promise.resolve(s);let u=this.circuit.isProbing&&!this.circuit.hasProbeInFlight;return !u&&this.rejectOnFull?Promise.reject(new a("Semaphore at capacity (rejectOnFull)","QUEUE_FULL")):!u&&this.queue.size>=this.queueMaxLength?Promise.reject(new a(`Queue full (${this.queueMaxLength})`,"QUEUE_FULL")):(this.lastAdmissionWasProbe=u,new Promise((c,d)=>{let m=++this.taskIdCounter,p=Date.now(),b=u,f=new S({id:m,priority:b?Number.MIN_SAFE_INTEGER:t,enqueueTime:p,isProbe:b,resolve:c,reject:d,abortSignal:e,weight:i});f.arm(()=>this._onTaskAbort(f,d)),b&&this.circuit.claimProbeSlot(m),this.metricsCollector?.sampleQueueDepthAt(p,this.queue.size+1),this._enqueue(f),this.schedule();}))}peekQueue(){let e=[],t=this.enqueueOrder.peekHead();for(;t!==void 0;)e.push({id:t.id,priority:t.priority,enqueueTime:t.enqueueTime,weight:t.weight,isProbe:t.isProbe}),t=t.next??void 0;return e}async use(e,t,i=0,r=1){let s=this._acquire(t,i,r),u=this.failurePredicate!==void 0&&this.lastAdmissionWasProbe,c=await s;try{let d=await e();return c(),d}catch(d){if(this.failurePredicate!==void 0){let m=false;try{m=this.failurePredicate(d)===!0;}catch(p){console.warn("[Semaphore] circuitBreakerFailurePredicate threw (rejection treated as non-matching):",p);}m&&(u&&this.circuit.isProbing?(this.circuit.recordFailure(),this.circuit.handleProbeFailure(),this.emit(n.CIRCUITOPEN,{timeoutRate:1,recentTimeouts:1,total:1,reason:"probe-failed"}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn("[Semaphore] Circuit re-opened: probe operation failed")):this.reportFailure());}throw c(),d}}drain(e){return this.isShutdown?Promise.reject(new a("Cannot drain: semaphore is shut down","SHUTDOWN")):(e!==void 0&&l(e,"drain timeoutMs",1,Number.MAX_SAFE_INTEGER,true,true),this.drainPromise?this.drainPromise:this.queue.size===0&&this.permits.available===this.permits.capacity?Promise.resolve():(this.drainPromise=new Promise((t,i)=>{if(this.drainResolve=t,e!==void 0){let r=setTimeout(()=>{this.drainResolve=null,this.drainPromise=null,i(new a(`drain() timed out after ${e}ms`,"TIMEOUT"));},e),s=this.drainResolve;this.drainResolve=()=>{clearTimeout(r),s();};}}),this.drainPromise))}reset(e={}){if(this.isShutdown)throw new a("Cannot reset a semaphore that has been shut down","SHUTDOWN");for(let t=this.enqueueOrder.peekHead();t!==void 0;t=t.next??void 0)t.discard(new a("Semaphore reset","SHUTDOWN"));this.queue.clear(),this.enqueueOrder.clear(),this._clearTimeout(),this.metricsCollector?.reset(),this.permits.reset(),this.backoff.reset(),this.circuit.reset(),this.releaseGeneration++,this.pendingReleaseCount=0,this.scheduled=false,this.isShutdown=false,this.taskIdCounter=0,this.totalAcquired=0,this.totalReleased=0,this.totalTimeouts=0,this.totalPurged=0,this.totalEvictions=0,e.clearListeners&&this.eventListeners.clear(),this.drainResolve&&(this.drainResolve(),this.drainResolve=null,this.drainPromise=null),this._startPurgeInterval(),this.metricsCollector?.markCapacityChange(this.permits.capacity),this.metricsCollector?.sampleGauges(Date.now(),this.permits.inFlight,this.queue.size),this.debug&&console.info("[Semaphore] Reset to initial state");}shutdown(e="Semaphore shutdown"){if(!this.isShutdown){this.isShutdown=true,this.debug&&console.info(`[Semaphore] Shutdown: ${e}`),this.purgeIntervalId!==null&&(clearInterval(this.purgeIntervalId),this.purgeIntervalId=null),this._clearTimeout();for(let t=this.enqueueOrder.peekHead();t!==void 0;t=t.next??void 0)t.discard(new a(e,"SHUTDOWN"));this.queue.clear(),this.enqueueOrder.clear(),this.releaseGeneration++,this.pendingReleaseCount=0,this.permits.reset(),this.metricsCollector?.destroy(),this.metricsCollector=void 0,this.drainResolve&&(this.drainResolve(),this.drainResolve=null,this.drainPromise=null),this.emit(n.SHUTDOWN,e);}}cancel(){if(this.isShutdown)return;let e=0;for(let t=this.enqueueOrder.peekHead();t!==void 0;t=t.next??void 0)t.isProbe&&this.circuit.releaseProbeSlot(),t.discard(new a("Semaphore acquire cancelled","CANCELLED")),e++;this.queue.clear(),this.enqueueOrder.clear(),this._clearTimeout(),this.metricsCollector?.sampleQueueDepthAt(Date.now(),this.queue.size),this.debug&&console.info(`[Semaphore] Cancelled ${e} queued tasks`),this._checkDrain(),this.schedule();}reportFailure(){if(this.isShutdown)return;this.circuit.recordFailure();let e=this.circuit.evaluateAndTrip();e.tripped&&(this.emit(n.CIRCUITOPEN,{timeoutRate:e.timeoutRate,recentTimeouts:e.failures,total:e.attempts,reason:"reported-failure"}),this.metricsCollector?.markCircuitOpen(),this.debug&&console.warn(`[Semaphore] Circuit opened via reportFailure(). Rate: ${(e.timeoutRate*100).toFixed(1)}%`),this._evictQueueOnCircuitOpen());}status(){let e=Date.now(),t=this.metricsCollector?.getSnapshot(e)??null,i=this.metricsCollector!==void 0?t?.windows?.[this.metricsCollector.primaryLabel]?.counts:void 0,r=(this.metricsCollector?.primaryWindowMs??6e4)/1e3,s=i?.acquired??0,u=i?.timeouts??0,c=this.enqueueOrder.peekHead(),d=c===void 0?0:Math.max(0,e-c.enqueueTime);return {status:{running:this.permits.capacity-this.permits.available,queued:this.queue.size,available:this.permits.available,inFlight:this.permits.inFlight,pendingReleases:this.pendingReleaseCount,circuitOpen:this.circuit.isOpen,circuitProbing:this.circuit.isProbing,backoffDelay:Math.round(this.backoff.currentDelay),requestsPerSecond:+(s/r).toFixed(2),timeoutRate1m:s+u>0?+(u/(s+u)*100).toFixed(1):0,queueAge:d},lifetime:{totalAcquired:this.totalAcquired,totalReleased:this.totalReleased,totalTimeouts:this.totalTimeouts,totalPurged:this.totalPurged,totalEvictions:this.totalEvictions,circuitBreakerCooldownRemaining:this.circuit.cooldownRemaining},metrics:t}}get availablePermits(){return this.permits.available}isAvailable(){return !this.isShutdown&&!this.circuit.isOpen&&!this.permits.isFull}get capacity(){return this.permits.capacity}get circuitState(){return this.circuit.state}get queueLength(){return this.queue.size}};var F={tripped:false},q=class{constructor(){this.state="closed";this.isOpen=false;this.isProbing=false;this.hasProbeInFlight=false;this.probeTaskId=null;this.cooldownRemaining=0;}checkAndTransition(){return false}trackAttempt(){}recordFailure(){}evaluateAndTrip(){return F}markProbeInFlight(){}claimProbeSlot(){}releaseProbeSlot(){}handleProbeSuccess(){}handleProbeFailure(){}reset(){}};var O={tripped:false},C=class{constructor(){this.state="closed";this.hasProbeInFlight=false;this.probeTaskId=null;}get isOpen(){return this.state==="open"}get isProbing(){return false}get cooldownRemaining(){return 0}open(){this.state="open";}close(){this.state="closed";}checkAndTransition(){return false}trackAttempt(){}recordFailure(){}evaluateAndTrip(){return O}markProbeInFlight(){}claimProbeSlot(){}releaseProbeSlot(){}handleProbeSuccess(){}handleProbeFailure(){}reset(){this.state="closed";}};export{C as ManualCircuitBreaker,q as NoopCircuitBreaker,E as QUEUE_ORDERINGS,v as SaturationCircuitBreaker,P as Semaphore,a as SemaphoreError,n as SemaphoreEvents};//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map
{
"name": "regulo",
"version": "1.3.5",
"version": "1.4.0",
"description": "Control the heat — a priority-queue semaphore with weighted permits, a saturation circuit breaker, adaptive backoff, and built-in metrics. Zero dependencies.",

@@ -5,0 +5,0 @@ "license": "MIT",

+49
-47

@@ -26,3 +26,3 @@ # **Regulo**

- **⏳ Head-of-line fairness** — once a caller is in line, nobody jumps the queue ahead of it.
- **🪶 Small footprint, no supply-chain surface** — a single ~33 KB file with zero runtime dependencies, so there's nothing transitive to audit, update, or trust. Tree-shakeable ESM.
- **🪶 Small footprint, no supply-chain surface** — a single ~34 KB file with zero runtime dependencies, so there's nothing transitive to audit, update, or trust. Tree-shakeable ESM.
- **🧯 Production-minded** — graceful `drain()`, `reset()`, `cancel()`, and `shutdown()`; stale-task purging; double-release safety; strict-mode TypeScript types.

@@ -204,3 +204,3 @@

Preferred entry point. Acquires a permit, runs `fn()`, and releases — always, even if `fn` throws. With `circuitBreakerFailurePredicate` configured, rejections from `fn()` that match the predicate count as breaker failures, and a matching failure on a half-open probe re-opens the circuit instead of closing it — see [Feeding downstream errors](#feeding-downstream-errors-reportfailure).
Preferred entry point. Acquires a permit, runs `fn()`, and releases — always, even if `fn` throws. With `circuitBreakerFailurePredicate` configured, rejections from `fn()` that match the predicate count as breaker failures, and a matching failure on a probe re-opens the circuit instead of closing it — see [Feeding downstream errors](#feeding-downstream-errors-reportfailure).

@@ -229,3 +229,3 @@ #### `tryAcquire(weight?): (() => void) | null`

Feeds an external failure signal (e.g. a downstream error) into the circuit breaker: records one failure and evaluates the trip condition. A trip behaves exactly like a saturation trip — queued tasks are evicted with `CIRCUIT_OPEN` and `CIRCUITOPEN` fires with `reason: 'reported-failure'`. Only influences trip decisions while the circuit is closed; no-op after `shutdown()`. For `use()`-based workloads, `circuitBreakerFailurePredicate` automates this and additionally makes half-open probes fault-aware. See [Feeding downstream errors](#feeding-downstream-errors-reportfailure).
Feeds an external failure signal (e.g. a downstream error) into the circuit breaker: records one failure and evaluates the trip condition. A trip behaves exactly like a saturation trip — queued tasks are evicted with `CIRCUIT_OPEN` and `CIRCUITOPEN` fires with `reason: 'reported-failure'`. Only influences trip decisions while the circuit is closed; no-op after `shutdown()`. For `use()`-based workloads, `circuitBreakerFailurePredicate` automates this and additionally makes probes fault-aware. See [Feeding downstream errors](#feeding-downstream-errors-reportfailure).

@@ -266,3 +266,3 @@ #### `shutdown(reason?): void`

#### `circuitState: 'closed' | 'open' | 'half-open'`
#### `circuitState: 'closed' | 'open' | 'probing'`

@@ -286,3 +286,3 @@ Current circuit breaker state.

| `circuitBreaker` | `CircuitBreakerStrategy` | — | A breaker instance to use instead of the built-in saturation breaker; overrides all `circuitBreaker*` options. See [Circuit breakers](#circuit-breakers) |
| `circuitBreakerFailurePredicate` | `(error: unknown) => boolean` | — | When set, `use()` counts matching rejections as breaker failures and half-open probes become fault-aware. Must not throw. See [Feeding downstream errors](#feeding-downstream-errors-reportfailure) |
| `circuitBreakerFailurePredicate` | `(error: unknown) => boolean` | — | When set, `use()` counts matching rejections as breaker failures and probes become fault-aware. Must not throw. See [Feeding downstream errors](#feeding-downstream-errors-reportfailure) |
| `backoffInitialTimeout` | `number` | `50` | Initial backoff delay (ms) applied to scheduler wakeup on first timeout |

@@ -348,3 +348,3 @@ | `backoffMaxTimeout` | `number` | `2000` | Max backoff delay (ms) applied to scheduler wakeup |

| `CIRCUITOPEN` | `'circuit-open'` | `{ timeoutRate, recentTimeouts, total, reason? }` |
| `CIRCUITHALFOPEN` | `'circuit-half-open'` | none |
| `CIRCUITPROBING` | `'circuit-probing'` | none |
| `CIRCUITCLOSE` | `'circuit-close'` | none |

@@ -360,3 +360,3 @@ | `SHUTDOWN` | `'shutdown'` | `reason: string` |

| `CIRCUIT_OPEN` | Circuit breaker is open |
| `CIRCUIT_HALF_OPEN` | Circuit is half-open and a probe is already in flight |
| `CIRCUIT_PROBING` | Circuit is probing and a probe is already in flight |
| `INVALID_ARGUMENT` | Invalid constructor/config value, or an invalid `drain()` timeout (thrown synchronously) |

@@ -403,3 +403,3 @@ | `INVALID_WEIGHT` | `weight` is not an integer in `1..count` |

circuitOpen: boolean,
circuitHalfOpen: boolean,
circuitProbing: boolean,
backoffDelay: number, // current backoff delay (ms) applied to scheduler wakeup

@@ -427,2 +427,4 @@ requestsPerSecond: number, // over the shortest metrics window (1m by default)

> **Renamed in 1.3.0:** the former `CircuitBreaker` export is now `SaturationCircuitBreaker`, and its `recordTimeout()` method is now `recordFailure()`. Both are straight renames — behavior is unchanged. See the [CHANGELOG](./CHANGELOG.md) for the migration.
>
> **Renamed in 1.4.0:** the breaker's middle state is "probing," not "half-open" — while in this state the breaker admits exactly one canary request, never a fraction of normal capacity, so the old name was misleading. This touches the full public surface with no compatibility aliases: `CircuitState`'s `'half-open'` is now `'probing'`; `isHalfOpen` is now `isProbing`; the `CIRCUITHALFOPEN`/`'circuit-half-open'` event is now `CIRCUITPROBING`/`'circuit-probing'`; the `CIRCUIT_HALF_OPEN` error code is now `CIRCUIT_PROBING`; `status().status.circuitHalfOpen` and the metrics snapshot's `circuitHalfOpen` are now `circuitProbing`; and the `CIRCUITOPEN` event's `reason: 'half-open-probe-failed'` is now `reason: 'probe-failed'`. Behavior is unchanged — see the [CHANGELOG](./CHANGELOG.md) for the full migration table.

@@ -449,3 +451,3 @@ | Breaker | Behavior |

To write your own, implement `CircuitBreakerStrategy` and pass an instance as `circuitBreaker`. The contract notes live on the exported type's JSDoc — the essentials: `checkAndTransition()` returns `true` exactly once per open → half-open transition, `evaluateAndTrip()` reports closed → open trips (the semaphore then emits `CIRCUITOPEN` and evicts the queue), the probe-slot methods may be no-ops if you never enter half-open, and methods must not throw.
To write your own, implement `CircuitBreakerStrategy` and pass an instance as `circuitBreaker`. The contract notes live on the exported type's JSDoc — the essentials: `checkAndTransition()` returns `true` exactly once per open → probing transition, `evaluateAndTrip()` reports closed → open trips (the semaphore then emits `CIRCUITOPEN` and evicts the queue), the probe-slot methods may be no-ops if you never enter probing, and methods must not throw.

@@ -469,3 +471,3 @@ ### Feeding downstream errors: `reportFailure()`

A trip via `reportFailure()` behaves exactly like a saturation trip: queued tasks are evicted with `CIRCUIT_OPEN` and the `CIRCUITOPEN` event fires with `reason: 'reported-failure'`. Reported failures influence trip decisions only while the circuit is closed — half-open probe outcomes remain acquisition-based.
A trip via `reportFailure()` behaves exactly like a saturation trip: queued tasks are evicted with `CIRCUIT_OPEN` and the `CIRCUITOPEN` event fires with `reason: 'reported-failure'`. Reported failures influence trip decisions only while the circuit is closed — probe outcomes remain acquisition-based.

@@ -484,3 +486,3 @@ For `use()`-based workloads, `circuitBreakerFailurePredicate` does this declaratively — and goes one step further:

With the predicate set, half-open probes become **fault-aware**: a probe dispatched through `use()` whose operation fails with a matching error re-opens the circuit (the `CIRCUITOPEN` event fires with `reason: 'half-open-probe-failed'`) instead of closing it on release. A probe whose rejection does *not* match still counts as a successful probe. The predicate must not throw; a thrown predicate is logged via `console.warn` and the rejection is treated as non-matching. The predicate only observes `use()` — callers using bare `acquire()`/`tryAcquire()` should call `reportFailure()` themselves.
With the predicate set, probes become **fault-aware**: a probe dispatched through `use()` whose operation fails with a matching error re-opens the circuit (the `CIRCUITOPEN` event fires with `reason: 'probe-failed'`) instead of closing it on release. A probe whose rejection does *not* match still counts as a successful probe. The predicate must not throw; a thrown predicate is logged via `console.warn` and the rejection is treated as non-matching. The predicate only observes `use()` — callers using bare `acquire()`/`tryAcquire()` should call `reportFailure()` themselves.

@@ -503,5 +505,5 @@ ### Standalone usage

async function fetch(url: string) {
// Check and transition open → half-open if cooldown elapsed
// Check and transition open → probing if cooldown elapsed
if (circuitBreaker.checkAndTransition()) {
console.log('Circuit entering half-open');
console.log('Circuit entering probing');
}

@@ -513,7 +515,7 @@ if (circuitBreaker.isOpen) throw new Error(`Circuit open, retry in ${circuitBreaker.cooldownRemaining}ms`);

const result = await httpClient.get(url);
if (circuitBreaker.isHalfOpen) circuitBreaker.handleProbeSuccess();
if (circuitBreaker.isProbing) circuitBreaker.handleProbeSuccess();
return result;
} catch (error) {
circuitBreaker.recordFailure();
if (circuitBreaker.isHalfOpen) circuitBreaker.handleProbeFailure();
if (circuitBreaker.isProbing) circuitBreaker.handleProbeFailure();
else circuitBreaker.evaluateAndTrip();

@@ -554,6 +556,6 @@ throw error;

|---|--:|---|
| `tryAcquire` + `release` (with metrics) | 3.03M | 1.77x slower |
| `tryAcquire` + `release` (with metrics) | 3.03M | 1.76x slower |
| `tryAcquire` + `release` | 5.35M | fastest |
| `use()` round-trip | 1.27M | 4.21x slower |
| `use()` round-trip (no metrics) | 1.65M | 3.23x slower |
| `use()` round-trip | 1.16M | 4.61x slower |
| `use()` round-trip (no metrics) | 1.49M | 3.58x slower |

@@ -564,5 +566,5 @@ **🎛️ Weighted acquire, uncontended**

|---|--:|---|
| `use()` weight=1 | 1.34M | 1.00x slower |
| `use()` weight=4 | 1.32M | 1.02x slower |
| `use()` weight=16 | 1.34M | fastest |
| `use()` weight=1 | 1.20M | fastest |
| `use()` weight=4 | 1.18M | 1.01x slower |
| `use()` weight=16 | 1.14M | 1.05x slower |

@@ -576,6 +578,6 @@ Weighted permits add no meaningful overhead regardless of weight — claiming

|---|--:|---|
| concurrency=4 | 762.4k | 1.09x slower |
| concurrency=16 | 786.1k | 1.06x slower |
| concurrency=64 | 833.5k | fastest |
| concurrency=16, random priority | 754.1k | 1.11x slower |
| concurrency=4 | 845.1k | 1.09x slower |
| concurrency=16 | 854.8k | 1.08x slower |
| concurrency=64 | 919.6k | fastest |
| concurrency=16, random priority | 812.9k | 1.13x slower |

@@ -586,5 +588,5 @@ **📈 `status()` snapshot cost**

|---|--:|---|
| 0 | 738.2k | 1.02x slower |
| 100 | 754.3k | fastest |
| 1000 | 741.4k | 1.02x slower |
| 0 | 725.6k | 1.03x slower |
| 100 | 747.4k | fastest |
| 1000 | 739.8k | 1.01x slower |

@@ -600,7 +602,7 @@ `status()` is O(1) in queue depth — the cost is flat across queue depths (within

|---|--:|---|
| cockatiel (bulkhead) | 4.06M | fastest |
| regulo | 1.73M | 2.34x slower |
| regulo (with metrics) | 1.26M | 3.23x slower |
| p-limit | 1.19M | 3.40x slower |
| p-queue | 1.18M | 3.45x slower |
| cockatiel (bulkhead) | 4.17M | fastest |
| regulo | 1.48M | 2.81x slower |
| p-queue | 1.19M | 3.52x slower |
| p-limit | 1.17M | 3.56x slower |
| regulo (with metrics) | 1.14M | 3.64x slower |

@@ -611,7 +613,7 @@ **📊 regulo vs. other libraries — contended throughput @ concurrency=16** (tasks/sec)

|---|--:|---|
| cockatiel (bulkhead) | 1.56M | fastest |
| p-queue | 1.06M | 1.47x slower |
| regulo | 927.1k | 1.68x slower |
| p-limit | 903.5k | 1.72x slower |
| regulo (with metrics) | 686.6k | 2.27x slower |
| cockatiel (bulkhead) | 1.73M | fastest |
| p-queue | 1.07M | 1.62x slower |
| regulo | 984.8k | 1.76x slower |
| regulo (with metrics) | 893.7k | 1.94x slower |
| p-limit | 877.1k | 1.98x slower |

@@ -622,7 +624,7 @@ **🛡️ Circuit breaker overhead — closed/healthy circuit**

|---|--:|---|
| regulo `ManualCircuitBreaker` | 5.10M | fastest |
| regulo `NoopCircuitBreaker` | 4.66M | 1.09x slower |
| regulo `SaturationCircuitBreaker` | 3.25M | 1.57x slower |
| cockatiel (circuitBreaker) | 2.65M | 1.93x slower |
| opossum | 1.54M | 3.31x slower |
| regulo `ManualCircuitBreaker` | 5.18M | fastest |
| regulo `NoopCircuitBreaker` | 4.68M | 1.11x slower |
| regulo `SaturationCircuitBreaker` | 3.92M | 1.32x slower |
| cockatiel (circuitBreaker) | 2.79M | 1.86x slower |
| opossum | 1.61M | 3.21x slower |

@@ -645,3 +647,3 @@ The picture is consistent. Cockatiel's bulkhead is the fastest limiter — and

more expensive than the limiter itself: SSR renders, database queries,
downstream API calls, measured in milliseconds. Even at ~690k tasks/sec
downstream API calls, measured in milliseconds. Even at ~890k tasks/sec
under contention the per-task overhead is a few microseconds against operations

@@ -668,7 +670,7 @@ thousands of times slower. If you only need a bare concurrency cap on cheap

✓ test/breakers-passthrough.test.ts (4 tests)
✓ test/semaphore-edges.test.ts (29 tests)
✓ test/semaphore-edges.test.ts (30 tests)
✓ test/semaphore.test.ts (111 tests)
Test Files 12 passed (12)
Tests 260 passed (260)
Tests 261 passed (261)
```

@@ -681,3 +683,3 @@

----------------|---------|----------|---------|---------|
All files | 99.6 | 97.84 | 100 | 100 |
All files | 99.7 | 97.54 | 100 | 100 |
----------------|---------|----------|---------|---------|

@@ -684,0 +686,0 @@ ```

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display